workspace: update eslint rule (#1027)

This commit is contained in:
undefined authored and GitHub committed 2025-08-06 18:51:25 +01:00
1 parent 05c3b8f2bd
commit cee3c00ddb
150 files changed
+1069 -1043

No files matched your search

+36 -17
View File
@@ -1,20 +1,31 @@
/* eslint-disable max-len */ /* eslint-disable max-len */
/* eslint-disable @typescript-eslint/naming-convention */ /* eslint-disable ts/naming-convention */
import path from 'node:path'; import path from 'node:path';
import { defineConfig, globalIgnores } from 'eslint/config';
import globals from 'globals'; import globals from 'globals';
import react from '@hydrooj/eslint-config'; import react from '@hydrooj/eslint-config';
export default defineConfig([globalIgnores([ const config = react({
'**/{public,files}/**/*.js', ignores: [
'**/dist', '**/{public,files}/**/*.js',
'**/*.d.ts', '**/dist',
'**/node_modules', '**/*.d.ts',
'**/.*.js', '**/node_modules',
'packages/ui-default/public', '**/.*.js',
]), { 'packages/ui-default/public',
extends: [react], 'packages/hydrojudge/vendor',
],
stylistic: {
indent: 4,
},
jsonc: false,
rules: {
'yaml/indent': ['warn', 2],
// There are too many `global` and `Function` in codebase already
'no-restricted-globals': 'off',
'ts/no-unsafe-function-type': 'off',
},
}, {
languageOptions: { languageOptions: {
ecmaVersion: 5, ecmaVersion: 5,
sourceType: 'module', sourceType: 'module',
@@ -105,16 +116,17 @@ export default defineConfig([globalIgnores([
rules: { rules: {
'github/array-foreach': 0, 'github/array-foreach': 0,
'@typescript-eslint/no-invalid-this': 0, 'ts/no-invalid-this': 0,
// FIXME A bug with eslint-parser // FIXME A bug with eslint-parser
// 'template-curly-spacing': 'off', // 'template-curly-spacing': 'off',
'@stylistic/indent': [ 'style/indent': [
'warn', 'warn',
2, 2,
{ SwitchCase: 1 }, { SwitchCase: 1 },
], ],
'style/indent-binary-ops': ['warn', 2],
'function-paren-newline': 'off', 'function-paren-newline': 'off',
'no-mixed-operators': 'off', 'no-mixed-operators': 'off',
'no-await-in-loop': 'off', 'no-await-in-loop': 'off',
@@ -139,14 +151,21 @@ export default defineConfig([globalIgnores([
}, },
}, { }, {
files: ['packages/ui-next/src/**/*.{ts,tsx}'], files: ['packages/ui-next/src/**/*.{ts,tsx}'],
languageOptions: { languageOptions: {
globals: { globals: {
...globals.browser, ...globals.browser,
}, },
}, },
rules: { rules: {
'@stylistic/indent': ['warn', 2], 'style/indent': ['warn', 2],
'style/indent-binary-ops': ['warn', 2],
}, },
}]); }, {
files: ['**/*.yaml', '**/*.yml'],
rules: {
'yaml/plain-scalar': 'off',
'style/no-trailing-spaces': 'off',
'max-len': 'off',
},
});
export default config;
+366 -385
View File
@@ -1,409 +1,144 @@
import { FlatCompat } from '@eslint/eslintrc'; /* eslint perfectionist/sort-objects: error */
import antfu from '@antfu/eslint-config';
import eslintReact from '@eslint-react/eslint-plugin'; import eslintReact from '@eslint-react/eslint-plugin';
import stylistic from '@stylistic/eslint-plugin';
import typescriptEslint from '@typescript-eslint/eslint-plugin';
import tsParser from '@typescript-eslint/parser'; import tsParser from '@typescript-eslint/parser';
import { defineConfig, globalIgnores } from 'eslint/config';
import deMorgan from 'eslint-plugin-de-morgan'; import deMorgan from 'eslint-plugin-de-morgan';
import github from 'eslint-plugin-github'; import github from 'eslint-plugin-github';
import jsxA11Y from 'eslint-plugin-jsx-a11y';
import reactRefresh from 'eslint-plugin-react-refresh'; import reactRefresh from 'eslint-plugin-react-refresh';
import simpleImportSort from 'eslint-plugin-simple-import-sort'; import simpleImportSort from 'eslint-plugin-simple-import-sort';
const compat = new FlatCompat({}); /** @type {typeof antfu} */
const base = (option, ...args) => antfu(
export default defineConfig([
globalIgnores([
'**/*.d.ts',
'**/node_modules',
'**/.git',
]),
{ {
extends: [ markdown: false,
compat.extends('airbnb-base'), typescript: true,
compat.extends('airbnb/hooks'), vue: true,
github.getFlatConfigs().react, ...option,
deMorgan.configs.recommended, gitignore: false,
eslintReact.configs['recommended-typescript'], ignores: [
'**/*.d.ts',
'**/.git',
'**/.pnp.*',
...(option.ignores || []),
], ],
settings: {
'import/parsers': {
'@typescript-eslint/parser': ['.ts', '.tsx', '.d.ts'],
},
'import/resolver': {
node: {
extensions: ['.mjs', '.js', '.json', '.ts', '.d.ts'],
},
},
'import/extensions': ['.js', '.mjs', '.jsx', '.ts', '.tsx', '.d.ts'],
'import/external-module-folders': ['node_modules', 'node_modules/@types'],
},
plugins: {
'@typescript-eslint': typescriptEslint,
'simple-import-sort': simpleImportSort,
// import: fixupPluginRules(_import),
'react-refresh': reactRefresh,
'jsx-a11y': jsxA11Y,
'@stylistic': stylistic,
},
languageOptions: { languageOptions: {
globals: { globals: {
Atomics: 'readonly', Atomics: 'readonly',
SharedArrayBuffer: 'readonly',
BigInt: 'readonly', BigInt: 'readonly',
SharedArrayBuffer: 'readonly',
...(option.languageOptions?.globals || {}),
}, },
...(option.languageOptions || {}),
parser: tsParser, },
lessOpinionated: true,
plugins: {
'@eslint-react': eslintReact,
'react-refresh': reactRefresh,
'simple-import-sort': simpleImportSort,
...(option.plugins || {}),
}, },
rules: { rules: {
'brace-style': 'off', '@eslint-react/dom/no-missing-button-type': 0,
// to allow `javascript:;`
'@eslint-react/dom/no-script-url': 0,
'@eslint-react/hooks-extra/no-direct-set-state-in-use-effect': 0,
'@eslint-react/hooks-extra/no-unnecessary-use-prefix': 0,
'@eslint-react/no-array-index-key': 0,
'@stylistic/brace-style': ['error', '1tbs', { // ui-default is currently on react 18
allowSingleLine: true, '@eslint-react/no-prop-types': 0,
}], '@eslint-react/no-string-refs': 0,
camelcase: 'off', 'antfu/consistent-chaining': 'off',
'comma-dangle': 'off', 'antfu/consistent-list-newline': 'off',
'@stylistic/comma-dangle': ['error', {
arrays: 'always-multiline',
objects: 'always-multiline',
imports: 'always-multiline',
exports: 'always-multiline',
functions: 'always-multiline',
enums: 'always-multiline',
generics: 'always-multiline',
tuples: 'always-multiline',
}],
'comma-spacing': 'off',
'@stylistic/comma-spacing': ['error', {
before: false,
after: true,
}],
'class-methods-use-this': 0,
'consistent-return': 1,
curly: ['error', 'multi-line'],
'default-param-last': 'off', 'default-param-last': 'off',
'func-call-spacing': 'off',
'function-call-spacing': 'off',
'@stylistic/function-call-spacing': ['error', 'never'],
indent: 'off',
'@stylistic/indent': ['warn', 4, {
SwitchCase: 1,
VariableDeclarator: 1,
outerIIFEBody: 1,
FunctionDeclaration: {
parameters: 1,
body: 1,
},
FunctionExpression: {
parameters: 1,
body: 1,
},
CallExpression: {
arguments: 1,
},
ArrayExpression: 1,
ObjectExpression: 1,
ImportDeclaration: 1,
flatTernaryExpressions: false,
ignoredNodes: [
'JSXElement',
'JSXElement > *',
'JSXAttribute',
'JSXIdentifier',
'JSXNamespacedName',
'JSXMemberExpression',
'JSXSpreadAttribute',
'JSXExpressionContainer',
'JSXOpeningElement',
'JSXClosingElement',
'JSXFragment',
'JSXOpeningFragment',
'JSXClosingFragment',
'JSXText',
'JSXEmptyExpression',
'JSXSpreadChild',
],
ignoreComments: false,
}],
'keyword-spacing': 'off',
'@stylistic/keyword-spacing': ['error', {
before: true,
after: true,
overrides: {
return: {
after: true,
},
throw: {
after: true,
},
case: {
after: true,
},
},
}],
'lines-between-class-members': 'off',
'@stylistic/lines-between-class-members': ['error', 'always', {
exceptAfterSingleLine: true,
}],
'no-array-constructor': 'off',
'@typescript-eslint/no-array-constructor': 'error',
'no-dupe-class-members': 'off',
'@typescript-eslint/no-dupe-class-members': 'error',
'no-empty-function': 'off',
'@typescript-eslint/no-empty-function': ['error', {
allow: ['arrowFunctions', 'functions', 'methods'],
}],
'no-extra-parens': 'off',
'@typescript-eslint/no-extra-parens': ['off', 'all', {
conditionalAssign: true,
nestedBinaryExpressions: false,
returnAssign: false,
ignoreJSX: 'all',
enforceForArrowConditionals: false,
}],
'no-extra-semi': 'off',
'@stylistic/no-extra-semi': 'error',
'no-new-func': 'off',
'no-loss-of-precision': 'off',
'@typescript-eslint/no-loss-of-precision': 'error',
'no-loop-func': 'off',
'@typescript-eslint/no-loop-func': 'error',
'no-magic-numbers': 'off',
'@typescript-eslint/no-magic-numbers': ['off', {
ignore: [],
ignoreArrayIndexes: true,
enforceConst: true,
detectObjects: false,
}],
'no-redeclare': 'off',
'@typescript-eslint/no-redeclare': 'error',
'no-shadow': 'off',
'@typescript-eslint/no-shadow': 'error',
'space-before-blocks': 'off',
'@stylistic/space-before-blocks': 'error',
'no-unused-expressions': 'off',
'@typescript-eslint/no-unused-expressions': ['error', {
allowShortCircuit: false,
allowTernary: false,
allowTaggedTemplates: false,
}],
'no-unused-vars': 'off',
'@typescript-eslint/no-unused-vars': ['error', {
vars: 'all',
args: 'after-used',
ignoreRestSiblings: true,
caughtErrorsIgnorePattern: '^_|e',
argsIgnorePattern: '^_',
}],
'no-use-before-define': 'off',
'@typescript-eslint/no-use-before-define': ['error', {
functions: true,
classes: true,
variables: true,
}],
'no-useless-constructor': 'off',
'@typescript-eslint/no-useless-constructor': 'error',
quotes: 'off',
'@stylistic/quotes': ['warn', 'single', {
avoidEscape: true,
}],
semi: 'off',
'@stylistic/semi': ['error', 'always'],
'space-before-function-paren': 'off',
'@stylistic/space-before-function-paren': ['error', {
anonymous: 'always',
named: 'never',
asyncArrow: 'always',
}],
'require-await': 'off',
'@typescript-eslint/require-await': 'off',
'space-infix-ops': 'off',
'@stylistic/space-infix-ops': 'error',
'object-curly-spacing': 'off',
'@stylistic/object-curly-spacing': ['error', 'always'],
'function-call-argument-newline': 0,
'no-implicit-coercion': ['warn', {
allow: ['!!', '+'],
}],
'no-extra-boolean-cast': ['warn', {
enforceForLogicalOperands: true,
}],
'no-empty-pattern': 0,
'no-multi-str': 0,
'dot-notation': 0, 'dot-notation': 0,
'@typescript-eslint/naming-convention': [ 'eslint-comments/no-unlimited-disable': 'off',
'warn', 'func-names': 0,
{ 'function-call-argument-newline': 0,
selector: 'default', 'function-paren-newline': 0,
format: ['camelCase'], 'github/a11y-svg-has-accessible-name': 0,
leadingUnderscore: 'allowSingleOrDouble', 'github/array-foreach': 1,
}, {
selector: 'default',
filter: {
regex: '^([A-Z]|_+id|__call__)$',
match: true,
},
format: null,
}, {
selector: 'variable',
modifiers: ['destructured'],
format: null,
}, {
selector: 'import',
format: null,
}, {
selector: 'variable',
format: ['camelCase', 'PascalCase', 'UPPER_CASE'],
modifiers: ['const'],
leadingUnderscore: 'allowSingleOrDouble',
}, {
selector: 'property',
format: ['camelCase', 'PascalCase', 'UPPER_CASE', 'snake_case'],
leadingUnderscore: 'allowSingleOrDouble',
}, {
selector: ['function', 'parameter', 'parameterProperty', 'objectLiteralMethod'],
filter: {
regex: '__call__',
match: false,
},
format: ['camelCase', 'PascalCase'],
leadingUnderscore: 'allowSingleOrDouble',
}, {
selector: ['objectLiteralProperty'],
filter: {
regex: '^[\\d]+$',
match: true,
},
format: null,
}, {
selector: ['typeProperty', 'typeMethod', 'objectLiteralProperty'],
modifiers: ['requiresQuotes'],
format: null,
}, {
selector: ['typeMethod'],
format: ['camelCase', 'PascalCase'],
}, {
selector: ['enum', 'class', 'interface'],
format: ['PascalCase'],
}, {
selector: ['typeAlias', 'enumMember', 'typeParameter'],
format: ['PascalCase', 'camelCase', 'UPPER_CASE'],
},
],
'@typescript-eslint/no-invalid-this': 0,
'class-methods-use-this': 0,
'global-require': 0, 'global-require': 0,
'guard-for-in': 0, 'guard-for-in': 0,
'implicit-arrow-linebreak': 0, 'implicit-arrow-linebreak': 0,
'import/consistent-type-specifier-style': 'off',
'import/extensions': 0, 'import/extensions': 0,
'import/order': 0,
'consistent-return': 1,
'func-names': 0,
'import/newline-after-import': 0, 'import/newline-after-import': 0,
'import/no-cycle': 0, 'import/no-cycle': 0,
'import/no-extraneous-dependencies': 0, 'import/no-extraneous-dependencies': 0,
'import/no-named-as-default': 0, 'import/no-named-as-default': 0,
'import/order': 0,
'import/prefer-default-export': 0, 'import/prefer-default-export': 0,
'jsx-a11y/anchor-is-valid': 0,
'jsx-a11y/click-events-have-key-events': 0,
'jsx-a11y/label-has-associated-control': 0,
'jsx-a11y/no-static-element-interactions': 0,
'logical-assignment-operators': ['warn', 'always', { 'logical-assignment-operators': ['warn', 'always', {
enforceForIfStatements: true, enforceForIfStatements: true,
}], }],
'max-classes-per-file': 0, 'max-classes-per-file': 0,
'max-len': ['warn', 150], 'max-len': ['warn', 150],
'newline-per-chained-call': 0, 'newline-per-chained-call': 0,
'no-await-in-loop': 1,
'no-bitwise': 0, 'no-bitwise': 0,
'no-console': 0, 'no-console': 0,
'no-continue': 0, 'no-continue': 0,
'no-extend-native': 0,
'no-empty': ['warn', { 'no-empty': ['warn', {
allowEmptyCatch: true, allowEmptyCatch: true,
}], }],
'no-empty-pattern': 0,
'no-await-in-loop': 1, 'no-extend-native': 0,
'no-extra-boolean-cast': ['warn', {
enforceForLogicalOperands: true,
}],
'no-implicit-coercion': ['warn', {
allow: ['!!', '+'],
}],
'no-multi-assign': 0, 'no-multi-assign': 0,
'no-return-await': 0, 'no-multi-str': 0,
'no-nested-ternary': 0, 'no-nested-ternary': 0,
'no-param-reassign': 0, 'no-param-reassign': 0,
'no-plusplus': 0, 'no-plusplus': 0,
'no-underscore-dangle': 0,
'no-unmodified-loop-condition': 1,
'prefer-destructuring': 0,
'function-paren-newline': 0,
'simple-import-sort/exports': 0,
'no-restricted-syntax': [ 'no-restricted-syntax': [
'error', { 'error', {
selector: 'LabeledStatement',
message: 'Labels are a form of GOTO; using them makes code confusing and hard to maintain and understand.', message: 'Labels are a form of GOTO; using them makes code confusing and hard to maintain and understand.',
selector: 'LabeledStatement',
}, { }, {
selector: 'WithStatement',
message: '`with` is disallowed in strict mode because it makes code impossible to predict and optimize.', message: '`with` is disallowed in strict mode because it makes code impossible to predict and optimize.',
selector: 'WithStatement',
}, },
], ],
'no-return-await': 0,
'no-underscore-dangle': 0,
'no-unmodified-loop-condition': 1,
'generator-star-spacing': 0, 'node/prefer-global/buffer': 'off',
'node/prefer-global/process': 'off',
'perfectionist/sort-imports': 'off',
'perfectionist/sort-named-imports': 'off',
'github/array-foreach': 1, 'prefer-destructuring': 0,
'github/a11y-svg-has-accessible-name': 0,
'quote-props': 0, 'quote-props': 0,
'@stylistic/quote-props': [0, 'as-needed'], 'react-hooks/exhaustive-deps': 0,
'react-refresh/only-export-components': ['warn', {
allowConstantExport: true,
}],
'regexp/prefer-d': 'off',
'regexp/prefer-w': 'off',
'regexp/use-ignore-case': 'off',
'simple-import-sort/exports': 0,
'simple-import-sort/imports': ['warn', { 'simple-import-sort/imports': ['warn', {
groups: [ groups: [
['^\\u0000'], ['^\\u0000'],
@@ -416,50 +151,290 @@ export default defineConfig([
], ],
}], }],
// ui-default is currently on react 18 'style/arrow-parens': ['error', 'always'],
'@eslint-react/no-prop-types': 0, 'style/brace-style': ['error', '1tbs', {
'@eslint-react/no-string-refs': 0, allowSingleLine: true,
// to allow `javascript:;`
'@eslint-react/dom/no-script-url': 0,
'@eslint-react/no-array-index-key': 0,
'@eslint-react/dom/no-missing-button-type': 0,
'@eslint-react/dom/no-dangerously-set-innerhtml': 0,
'@eslint-react/hooks-extra/no-direct-set-state-in-use-effect': 0,
'@eslint-react/hooks-extra/no-unnecessary-use-prefix': 0,
'react-hooks/exhaustive-deps': 0,
// 'react/prefer-stateless-function': 0,
// 'react/function-component-definition': 0,
// 'react/static-property-placement': 0,
// 'react/self-closing-comp': 0,
// 'react/prop-types': 0,
// 'react/jsx-filename-extension': 0,
// 'react/jsx-one-expression-per-line': 0,
// 'react/jsx-props-no-spreading': 0,
// 'react/no-string-refs': 0,
// 'react/require-default-props': 0,
// 'react/react-in-jsx-scope': 0,
// 'react/destructuring-assignment': 0,
// 'react/button-has-type': 0,
// 'react/forbid-prop-types': 0,
'jsx-a11y/no-static-element-interactions': 0,
'jsx-a11y/anchor-is-valid': 0,
'jsx-a11y/click-events-have-key-events': 0,
'jsx-a11y/label-has-associated-control': 0,
'react-refresh/only-export-components': ['warn', {
allowConstantExport: true,
}], }],
'style/comma-dangle': ['error', {
arrays: 'always-multiline',
enums: 'always-multiline',
exports: 'always-multiline',
functions: 'always-multiline',
generics: 'always-multiline',
imports: 'always-multiline',
objects: 'always-multiline',
tuples: 'always-multiline',
}],
'style/comma-spacing': ['error', {
after: true,
before: false,
}],
'style/function-call-spacing': ['error', 'never'],
'style/generator-star-spacing': ['error', {
after: true,
anonymous: { after: true, before: false },
before: false,
method: { after: false, before: true },
}],
'style/indent': ['warn', option.stylistic?.indent ?? 4, {
ArrayExpression: 1,
CallExpression: {
arguments: 1,
},
flatTernaryExpressions: false,
FunctionDeclaration: {
body: 1,
parameters: 1,
},
FunctionExpression: {
body: 1,
parameters: 1,
},
ignoreComments: false,
ignoredNodes: [
// 'JSXElement',
// 'JSXElement > *',
// 'JSXAttribute',
// 'JSXIdentifier',
// 'JSXNamespacedName',
// 'JSXMemberExpression',
// 'JSXSpreadAttribute',
// 'JSXExpressionContainer',
// 'JSXOpeningElement',
// 'JSXClosingElement',
// 'JSXFragment',
// 'JSXOpeningFragment',
// 'JSXClosingFragment',
'JSXText',
// 'JSXEmptyExpression',
// 'JSXSpreadChild',
],
ImportDeclaration: 1,
ObjectExpression: 1,
outerIIFEBody: 1,
SwitchCase: 1,
VariableDeclarator: 1,
}],
'style/jsx-closing-bracket-location': 'off',
'style/jsx-closing-tag-location': 'off',
'style/jsx-function-call-newline': 'off',
'style/jsx-indent-props': 'off',
'style/jsx-one-expression-per-line': 'off',
'style/jsx-wrap-multilines': 'off',
'style/keyword-spacing': ['error', {
after: true,
before: true,
overrides: {
case: {
after: true,
},
return: {
after: true,
},
throw: {
after: true,
},
},
}],
'style/lines-between-class-members': ['error', 'always', {
exceptAfterSingleLine: true,
}],
'style/max-statements-per-line': ['error', { max: 2 }],
'style/member-delimiter-style': ['error', {
multiline: {
delimiter: 'semi',
requireLast: true,
},
multilineDetection: 'brackets',
singleline: {
delimiter: 'comma',
requireLast: false,
},
}],
'style/multiline-ternary': 'off',
'style/no-extra-semi': 'error',
'style/object-curly-spacing': ['error', 'always'],
'style/operator-linebreak': ['warn', 'before', {
overrides: {
'=': 'after',
},
}],
'style/quote-props': ['warn', 'as-needed'],
'style/quotes': ['warn', 'single', {
avoidEscape: true,
}],
'style/space-before-blocks': 'error',
'style/space-before-function-paren': ['error', {
anonymous: 'always',
asyncArrow: 'always',
named: 'never',
}],
'style/space-infix-ops': 'error',
'test/no-import-node-test': 'off',
'ts/ban-ts-comment': 'off',
'ts/consistent-type-imports': 'off',
'ts/method-signature-style': 'off',
'ts/no-array-constructor': 'error',
'ts/no-dupe-class-members': 'error',
'ts/no-empty-function': ['error', {
allow: ['arrowFunctions', 'functions', 'methods'],
}],
'ts/no-extra-parens': ['off', 'all', {
conditionalAssign: true,
enforceForArrowConditionals: false,
ignoreJSX: 'all',
nestedBinaryExpressions: false,
returnAssign: false,
}],
'ts/no-invalid-this': 'off',
'ts/no-loop-func': 'error',
'ts/no-loss-of-precision': 'error',
'ts/no-magic-numbers': 'off',
'ts/no-namespace': 'off',
'ts/no-redeclare': 'off',
'ts/no-require-imports': 'off',
'ts/no-shadow': 'error',
'ts/no-this-alias': 'off',
'ts/no-unused-expressions': ['error', {
allowShortCircuit: false,
allowTaggedTemplates: false,
allowTernary: false,
}],
'ts/no-unused-vars': ['error', {
args: 'after-used',
argsIgnorePattern: '^_',
caughtErrorsIgnorePattern: '^_|e',
ignoreRestSiblings: true,
vars: 'all',
}],
'ts/no-use-before-define': ['error', {
classes: true,
functions: true,
variables: true,
}],
'ts/no-useless-constructor': 'error',
'ts/prefer-as-const': 'off',
'ts/require-await': 'off',
'unicorn/error-message': 'off',
'unicorn/no-instanceof-builtins': 'off',
'unicorn/prefer-node-protocol': 'off',
'unused-imports/no-unused-vars': 'off',
...(option.rules || {}),
},
settings: {
'import/extensions': ['.js', '.mjs', '.jsx', '.ts', '.tsx', '.d.ts'],
'import/external-module-folders': ['node_modules', 'node_modules/@types'],
'import/resolver': {
node: {
extensions: ['.mjs', '.js', '.json', '.ts', '.d.ts'],
},
},
...(option.settings || {}),
},
stylistic: {
indent: 4,
quotes: undefined,
semi: true,
...(typeof option.stylistic === 'object' ? option.stylistic : {}),
},
},
github.getFlatConfigs().react,
deMorgan.configs.recommended,
{
files: ['**/*.vue'],
rules: {
'vue/block-order': ['warn', { order: ['template', 'script[setup]', 'script:not([setup])', 'style[scoped]', 'style:not([scoped])'] }],
'vue/singleline-html-element-content-newline': 'off',
}, },
}, },
{ {
files: ['**/*.ts', '**/*.tsx'], files: ['**/*.ts', '**/*.tsx', '**/*.js', '**/*.jsx', '**/*.cjs', '**/*.cts', '**/*.mjs', '**/*.mts'],
languageOptions: {
parser: tsParser,
},
rules: {
'ts/naming-convention': [
'warn',
{
format: ['camelCase'],
leadingUnderscore: 'allowSingleOrDouble',
selector: 'default',
}, {
filter: {
match: true,
regex: '^([A-Z]|_+id|__call__)$',
},
format: null,
selector: 'default',
}, {
format: null,
modifiers: ['destructured'],
selector: 'variable',
}, {
format: null,
selector: 'import',
}, {
format: ['camelCase', 'PascalCase', 'UPPER_CASE'],
leadingUnderscore: 'allowSingleOrDouble',
modifiers: ['const'],
selector: 'variable',
}, {
format: ['camelCase', 'PascalCase', 'UPPER_CASE', 'snake_case'],
leadingUnderscore: 'allowSingleOrDouble',
selector: 'property',
}, {
filter: {
match: false,
regex: '__call__',
},
format: ['camelCase', 'PascalCase'],
leadingUnderscore: 'allowSingleOrDouble',
selector: ['function', 'parameter', 'parameterProperty', 'objectLiteralMethod'],
}, {
filter: {
match: true,
regex: '^[\\d]+$',
},
format: null,
selector: ['objectLiteralProperty'],
}, {
format: null,
modifiers: ['requiresQuotes'],
selector: ['typeProperty', 'typeMethod', 'objectLiteralProperty'],
}, {
format: ['camelCase', 'PascalCase'],
selector: ['typeMethod'],
}, {
format: ['PascalCase'],
selector: ['enum', 'class', 'interface'],
}, {
format: ['PascalCase', 'camelCase', 'UPPER_CASE'],
selector: ['typeAlias', 'enumMember', 'typeParameter'],
},
],
},
},
{
files: ['**/*.ts', '**/*.tsx', '**/*.cts', '**/*.mts'],
languageOptions: {
parser: tsParser,
},
// Hand over to tsc // Hand over to tsc
rules: { rules: {
'constructor-super': 'off', 'constructor-super': 'off',
'getter-return': 'off', 'getter-return': 'off',
'import/named': 'off',
'import/no-named-as-default-member': 'off',
'import/no-unresolved': 'off',
'no-const-assign': 'off', 'no-const-assign': 'off',
'no-dupe-args': 'off', 'no-dupe-args': 'off',
'no-dupe-class-members': 'off', 'no-dupe-class-members': 'off',
@@ -475,9 +450,15 @@ export default defineConfig([
'no-unreachable': 'off', 'no-unreachable': 'off',
'no-unsafe-negation': 'off', 'no-unsafe-negation': 'off',
'valid-typeof': 'off', 'valid-typeof': 'off',
'import/named': 'off',
'import/no-named-as-default-member': 'off',
'import/no-unresolved': 'off',
}, },
}, },
]); {
files: ['**/*.yaml', '**/*.yml'],
rules: {
'style/no-multi-spaces': 'off',
},
},
...args,
);
export default base;
+4 -10
View File
@@ -1,22 +1,16 @@
{ {
"name": "@hydrooj/eslint-config", "name": "@hydrooj/eslint-config",
"version": "2.0.3", "version": "3.0.0",
"license": "MIT", "license": "MIT",
"main": "base.mjs", "main": "base.mjs",
"repository": "https://github.com/hydro-dev/Hydro", "repository": "https://github.com/hydro-dev/Hydro",
"dependencies": { "dependencies": {
"@antfu/eslint-config": "^5.2.0",
"@eslint-react/eslint-plugin": "^1.52.3", "@eslint-react/eslint-plugin": "^1.52.3",
"@eslint/compat": "^1.3.1", "@typescript-eslint/eslint-plugin": "^8.39.0",
"@eslint/eslintrc": "^3.3.1", "@typescript-eslint/parser": "^8.39.0",
"@stylistic/eslint-plugin": "^5.2.2",
"@typescript-eslint/eslint-plugin": "^8.38.0",
"@typescript-eslint/parser": "^8.38.0",
"eslint-config-airbnb": "^19.0.4",
"eslint-config-airbnb-base": "^15.0.0",
"eslint-plugin-de-morgan": "^1.3.1", "eslint-plugin-de-morgan": "^1.3.1",
"eslint-plugin-github": "^6.0.0", "eslint-plugin-github": "^6.0.0",
"eslint-plugin-import": "2.31.0",
"eslint-plugin-jsx-a11y": "^6.10.2",
"eslint-plugin-react-hooks": "^5.2.0", "eslint-plugin-react-hooks": "^5.2.0",
"eslint-plugin-react-refresh": "^0.4.20", "eslint-plugin-react-refresh": "^0.4.20",
"eslint-plugin-simple-import-sort": "^12.1.1", "eslint-plugin-simple-import-sort": "^12.1.1",
+5 -6
View File
@@ -11,20 +11,20 @@ const REDIRECT = Symbol.for('hydro.api.response.redirect');
type MaybePromise<T> = T | Promise<T>; type MaybePromise<T> = T | Promise<T>;
export type ApiType = 'Query' | 'Mutation' | 'Subscription'; export type ApiType = 'Query' | 'Mutation' | 'Subscription';
export type ApiCall<Type extends ApiType, Arg, Res, Progress = never> = { export interface ApiCall<Type extends ApiType, Arg, Res, Progress = never> {
readonly type: Type; readonly type: Type;
readonly input: Schema<Arg>; readonly input: Schema<Arg>;
readonly func: (Type extends 'Subscription' readonly func: (Type extends 'Subscription'
? (context: any, args: Arg, emit: (payload: Res) => void) => (() => MaybePromise<void>) ? (context: any, args: Arg, emit: (payload: Res) => void) => (() => MaybePromise<void>)
: (context: any, args: Arg) => MaybePromise<Res | AsyncGenerator<Progress, Res, never>>); : (context: any, args: Arg) => MaybePromise<Res | AsyncGenerator<Progress, Res, never>>);
readonly hooks: ApiCall<'Query', Arg, void>[]; readonly hooks: ApiCall<'Query', Arg, void>[];
}; }
export const _get = <Type extends ApiType>(type: Type) => <Arg, Res, Progress>( export const _get = <Type extends ApiType>(type: Type) => <Arg, Res, Progress>(
schema: Schema<Arg>, schema: Schema<Arg>,
func: ApiCall<Type, Arg, Res, Progress>['func'], func: ApiCall<Type, Arg, Res, Progress>['func'],
hooks: ApiCall<'Query', Arg, void, never>[] = [], hooks: ApiCall<'Query', Arg, void, never>[] = [],
): ApiCall<Type, Arg, Res, Progress> => ({ input: schema, func, hooks, type } as const); // eslint-disable-line ): ApiCall<Type, Arg, Res, Progress> => ({ input: schema, func, hooks, type } as const);
export const Query = _get('Query'); export const Query = _get('Query');
export const Mutation = _get('Mutation'); export const Mutation = _get('Mutation');
@@ -57,7 +57,7 @@ export interface Apis {
builtin: { builtin: {
'query.batch': ApiCall<'Query', { op: string, args: any }[], { [key: string]: any }>; 'query.batch': ApiCall<'Query', { op: string, args: any }[], { [key: string]: any }>;
'mutation.batch': ApiCall<'Mutation', { op: string, args: any }[], { [key: string]: any }>; 'mutation.batch': ApiCall<'Mutation', { op: string, args: any }[], { [key: string]: any }>;
} };
test: typeof TestApis; test: typeof TestApis;
} }
export type FlattenedApis = Apis[keyof Apis]; export type FlattenedApis = Apis[keyof Apis];
@@ -68,8 +68,7 @@ type MKeyOf<T> = T extends any ? keyof T : never;
type MId<T> = { [K in MKeyOf<T>]: T[K] } & {}; type MId<T> = { [K in MKeyOf<T>]: T[K] } & {};
type ProjectionSchema<T> = T extends Array<infer U> type ProjectionSchema<T> = T extends Array<infer U>
? ProjectionSchema<U> ? ProjectionSchema<U>
: | { [K in keyof T]?: ProjectionSchemaId | ProjectionSchema<T[K]> } : { [K in keyof T]?: ProjectionSchemaId | ProjectionSchema<T[K]> } | Record<keyof any, ProjectionSchemaId | object>;
| Record<keyof any, ProjectionSchemaId | object>;
type AsKeys<T> = T extends Array<infer U extends string> ? Record<U, 1> : T; type AsKeys<T> = T extends Array<infer U extends string> ? Record<U, 1> : T;
type Projection<T, S> = S extends ProjectionSchemaId type Projection<T, S> = S extends ProjectionSchemaId
? T : T extends Array<infer U> ? Array<MId<Projection<U, S>>> : { ? T : T extends Array<infer U> ? Array<MId<Projection<U, S>>> : {
+5 -5
View File
@@ -8,11 +8,11 @@ type MethodDecorator = (target: any, funcName: string, obj: any) => any;
type ClassDecorator = <T extends new (...args: any[]) => any>(Class: T) => T extends new (...args: infer R) => infer S type ClassDecorator = <T extends new (...args: any[]) => any>(Class: T) => T extends new (...args: infer R) => infer S
? new (...args: R) => S : never; ? new (...args: R) => S : never;
export interface ParamOption<T> { export interface ParamOption<T> {
name: string, name: string;
source: 'all' | 'get' | 'post' | 'route', source: 'all' | 'get' | 'post' | 'route';
isOptional?: boolean | 'convert', isOptional?: boolean | 'convert';
convert?: Converter<T>, convert?: Converter<T>;
validate?: Validator, validate?: Validator;
} }
const kSchema = Symbol.for('schemastery'); const kSchema = Symbol.for('schemastery');
+2 -2
View File
@@ -1,5 +1,5 @@
interface IHydroError { interface IHydroError {
new(...args: any[]): HydroError new(...args: any[]): HydroError;
} }
export class HydroError extends Error { export class HydroError extends Error {
@@ -32,7 +32,7 @@ const Err = (name: string, Class: IHydroError, ...info: Array<(() => string) | s
msg = item; msg = item;
} }
} }
// eslint-disable-next-line @typescript-eslint/no-shadow // eslint-disable-next-line ts/no-shadow
return class HydroError extends Class { return class HydroError extends Class {
name = name; name = name;
constructor(...args: any[]) { constructor(...args: any[]) {
+7 -7
View File
@@ -1,8 +1,8 @@
export * from './server';
export { default as serializer } from './serializer';
export * from './decorators';
export * from './validator';
export * from './error';
export * from './router';
export * from './interface';
export * from './api'; export * from './api';
export * from './decorators';
export * from './error';
export * from './interface';
export * from './router';
export { default as serializer } from './serializer';
export * from './server';
export * from './validator';
+14 -14
View File
@@ -3,7 +3,7 @@ import type { ApiHandler } from './api';
import type { ConnectionHandler, Handler, NotFoundHandler } from './server'; import type { ConnectionHandler, Handler, NotFoundHandler } from './server';
export interface KnownHandlers { export interface KnownHandlers {
'NotFound': NotFoundHandler; NotFound: NotFoundHandler;
} }
type MapHandlerEvents<N extends string, H extends Handler<any>> = type MapHandlerEvents<N extends string, H extends Handler<any>> =
@@ -24,19 +24,19 @@ export type Methods = 'get' | 'post' | 'put' | 'delete' | 'patch';
export type HookWithMethod = Exclude<HookType, 'before-operation'>; export type HookWithMethod = Exclude<HookType, 'before-operation'>;
export interface ServerEvents<C extends Context> extends KnownHandlerEvents, HandlerEvents<C> { export interface ServerEvents<C extends Context> extends KnownHandlerEvents, HandlerEvents<C> {
'handler/create': (thisArg: Handler<C> | ConnectionHandler<C>, type: 'ws' | 'http') => VoidReturn 'handler/create': (thisArg: Handler<C> | ConnectionHandler<C>, type: 'ws' | 'http') => VoidReturn;
'handler/create/http': (thisArg: Handler<C>) => VoidReturn 'handler/create/http': (thisArg: Handler<C>) => VoidReturn;
'handler/create/ws': (thisArg: ConnectionHandler<C>) => VoidReturn 'handler/create/ws': (thisArg: ConnectionHandler<C>) => VoidReturn;
'handler/init': (thisArg: Handler<C>) => VoidReturn 'handler/init': (thisArg: Handler<C>) => VoidReturn;
'handler/error': (thisArg: Handler<C> | ConnectionHandler<C>, e: Error) => VoidReturn 'handler/error': (thisArg: Handler<C> | ConnectionHandler<C>, e: Error) => VoidReturn;
'handler/api/before': (thisArg: ApiHandler<C>) => VoidReturn 'handler/api/before': (thisArg: ApiHandler<C>) => VoidReturn;
'api/before': (args: any) => VoidReturn 'api/before': (args: any) => VoidReturn;
[k: `handler/${HookType}/${string}`]: (thisArg: any) => VoidReturn [k: `handler/${HookType}/${string}`]: (thisArg: any) => VoidReturn;
[k: `handler/${HookWithMethod}/${string}/${Methods}`]: (thisArg: any) => VoidReturn [k: `handler/${HookWithMethod}/${string}/${Methods}`]: (thisArg: any) => VoidReturn;
[k: `handler/register/${string}`]: (HandlerClass: new (...args: any[]) => any) => VoidReturn [k: `handler/register/${string}`]: (HandlerClass: new (...args: any[]) => any) => VoidReturn;
[k: `handler/error/${string}`]: (thisArg: Handler<C>, e: Error) => VoidReturn [k: `handler/error/${string}`]: (thisArg: Handler<C>, e: Error) => VoidReturn;
[k: `handler/api/before/${string}`]: (thisArg: ApiHandler<C>) => VoidReturn [k: `handler/api/before/${string}`]: (thisArg: ApiHandler<C>) => VoidReturn;
[k: `api/before/${string}`]: (args: any) => VoidReturn [k: `api/before/${string}`]: (args: any) => VoidReturn;
} }
declare module 'cordis' { declare module 'cordis' {
+1 -1
View File
@@ -10,7 +10,7 @@
"dependencies": { "dependencies": {
"@hydrooj/utils": "workspace:^", "@hydrooj/utils": "workspace:^",
"emoji-regex": "^10.4.0", "emoji-regex": "^10.4.0",
"fs-extra": "^11.3.0", "fs-extra": "^11.3.1",
"koa": "^3.0.1", "koa": "^3.0.1",
"koa-body": "^6.0.1", "koa-body": "^6.0.1",
"koa-compress": "^5.1.1", "koa-compress": "^5.1.1",
+14 -11
View File
@@ -1,3 +1,4 @@
/* eslint-disable ts/no-unsafe-declaration-merging */
import http from 'http'; import http from 'http';
import { tmpdir } from 'os'; import { tmpdir } from 'os';
import { join } from 'path'; import { join } from 'path';
@@ -77,7 +78,8 @@ export interface HydroResponse {
type: string; type: string;
status: number; status: number;
template?: string; template?: string;
/** If set, and pjax content was request from client, /**
* If set, and pjax content was request from client,
* The template will be used for rendering. * The template will be used for rendering.
*/ */
pjax?: string; pjax?: string;
@@ -87,14 +89,14 @@ export interface HydroResponse {
attachment: (name: string, stream?: any) => void; attachment: (name: string, stream?: any) => void;
addHeader: (name: string, value: string) => void; addHeader: (name: string, value: string) => void;
} }
type HydroContext = { interface HydroContext {
request: HydroRequest; request: HydroRequest;
response: HydroResponse; response: HydroResponse;
args: Record<string, any>; args: Record<string, any>;
UiContext: Record<string, any>; UiContext: Record<string, any>;
domain: { _id: string }; domain: { _id: string };
user: { _id: number }; user: { _id: number };
}; }
export type KoaContext = Koa.Context & { export type KoaContext = Koa.Context & {
HydroContext: HydroContext; HydroContext: HydroContext;
handler: any; handler: any;
@@ -103,14 +105,14 @@ export type KoaContext = Koa.Context & {
holdFiles: (string | File)[]; holdFiles: (string | File)[];
}; };
export type TextRenderer = { export interface TextRenderer {
output: 'html' | 'json' | 'text'; output: 'html' | 'json' | 'text';
render: (name: string, args: Record<string, any>, context: Record<string, any>) => string | Promise<string>; render: (name: string, args: Record<string, any>, context: Record<string, any>) => string | Promise<string>;
}; }
export type BinaryRenderer = { export interface BinaryRenderer {
output: 'binary'; output: 'binary';
render: (name: string, args: Record<string, any>, context: Record<string, any>) => Buffer | Promise<Buffer>; render: (name: string, args: Record<string, any>, context: Record<string, any>) => Buffer | Promise<Buffer>;
}; }
export type Renderer = (BinaryRenderer | TextRenderer) & { export type Renderer = (BinaryRenderer | TextRenderer) & {
name: string; name: string;
accept: readonly string[]; accept: readonly string[];
@@ -139,7 +141,7 @@ export interface UserModel {
_id: number; _id: number;
} }
export interface HandlerCommon<C> { } // eslint-disable-line @typescript-eslint/no-unused-vars export interface HandlerCommon<C> { } // eslint-disable-line ts/no-unused-vars
export class HandlerCommon<C> { export class HandlerCommon<C> {
static [kHandler]: string | boolean = 'HandlerCommon'; static [kHandler]: string | boolean = 'HandlerCommon';
session: Record<string, any>; session: Record<string, any>;
@@ -507,7 +509,8 @@ ${c.response.status} ${endTime - startTime}ms ${c.response.length}`);
this.activeHandlers.set(h, { start: Date.now(), name }); this.activeHandlers.set(h, { start: Date.now(), name });
try { try {
const operation = (method === 'post' && ctx.request.body?.operation) const operation = (method === 'post' && ctx.request.body?.operation)
? `_${ctx.request.body.operation}`.replace(/_([a-z])/gm, (s) => s[1].toUpperCase()) // eslint-disable-next-line regexp/no-unused-capturing-group
? `_${ctx.request.body.operation}`.replace(/_([a-z])/g, (s) => s[1].toUpperCase())
: ''; : '';
// FIXME: should pass type check // FIXME: should pass type check
@@ -777,7 +780,7 @@ ${c.response.status} ${endTime - startTime}ms ${c.response.length}`);
this.ctx.on(`handler/register/${name}`, callback as any); this.ctx.on(`handler/register/${name}`, callback as any);
} }
// eslint-disable-next-line @typescript-eslint/naming-convention // eslint-disable-next-line ts/naming-convention
public Route(name: string, path: string, RouteHandler: typeof Handler<C>, ...permPrivChecker) { public Route(name: string, path: string, RouteHandler: typeof Handler<C>, ...permPrivChecker) {
// if (name === 'contest_scoreboard') { // if (name === 'contest_scoreboard') {
// console.log('+++', this.ctx); // console.log('+++', this.ctx);
@@ -786,7 +789,7 @@ ${c.response.status} ${endTime - startTime}ms ${c.response.length}`);
return this.register('route', name, path, RouteHandler, ...permPrivChecker); return this.register('route', name, path, RouteHandler, ...permPrivChecker);
} }
// eslint-disable-next-line @typescript-eslint/naming-convention // eslint-disable-next-line ts/naming-convention
public Connection(name: string, path: string, RouteHandler: typeof ConnectionHandler<C>, ...permPrivChecker) { public Connection(name: string, path: string, RouteHandler: typeof ConnectionHandler<C>, ...permPrivChecker) {
return this.register('conn', name, path, RouteHandler, ...permPrivChecker); return this.register('conn', name, path, RouteHandler, ...permPrivChecker);
} }
+8 -8
View File
@@ -90,23 +90,23 @@ const saslprepString = <T = string>(regex?: RegExp, cb?: (i: string) => boolean,
export const Types = { export const Types = {
Content: [(v) => v.toString().trim(), (v) => v?.toString()?.trim() && v.toString().trim().length < 65536], Content: [(v) => v.toString().trim(), (v) => v?.toString()?.trim() && v.toString().trim().length < 65536],
Key: saslprepString(/^[a-zA-Z0-9-_]{1,255}$/), Key: saslprepString(/^[\w-]{1,255}$/),
/** @deprecated */ /** @deprecated */
Name: saslprepString(/^.{1,255}$/), Name: saslprepString(/^.{1,255}$/),
Filename: saslprepString(/^[^\\/?#~!|*]{1,255}$/, (i) => sanitize(i) === i), Filename: saslprepString(/^[^\\/?#~!|*]{1,255}$/, (i) => sanitize(i) === i),
UidOrName: saslprepString(/^(.{3,31}|[\u4e00-\u9fa5]{2}|-?[0-9]+)$/), UidOrName: saslprepString(/^(?:.{3,31}|[\u4E00-\u9FA5]{2}|-?[0-9]+)$/),
Username: saslprepString(/^(.{3,31}|[\u4e00-\u9fa5]{2})$/), Username: saslprepString(/^(?:.{3,31}|[\u4E00-\u9FA5]{2})$/),
Password: basicString(/^.{6,255}$/), Password: basicString(/^.{6,255}$/),
ProblemId: saslprepString(/^([a-zA-Z0-9]{1,10}-)?[a-zA-Z0-9]+$/i, () => true, (s) => (Number.isSafeInteger(+s) ? +s : s)), ProblemId: saslprepString(/^(?:[a-z0-9]{1,10}-)?[a-z0-9]+$/i, () => true, (s) => (Number.isSafeInteger(+s) ? +s : s)),
Email: saslprepString(/^[a-zA-Z0-9_.+-]+@[a-zA-Z0-9-]+(?:\.[a-zA-Z0-9-]+)+$/i), Email: saslprepString(/^[\w.+-]+@[a-z0-9-]+(?:\.[a-z0-9-]+)+$/i),
DomainId: saslprepString(/^[a-zA-Z][a-zA-Z0-9_]{3,31}$/), DomainId: saslprepString(/^[a-zA-Z]\w{3,31}$/),
Role: saslprepString(/^[_0-9A-Za-z\u4e00-\u9fa5]{1,31}$/i), Role: saslprepString(/^[\w\u4E00-\u9FA5]{1,31}$/),
Title: basicString(/^.{1,64}$/, (i) => !!i.trim()), Title: basicString(/^.{1,64}$/, (i) => !!i.trim()),
ShortString: basicString(/^.{1,255}$/), ShortString: basicString(/^.{1,255}$/),
String: basicString(), String: basicString(),
Int: [(v) => +v, (v) => /^[+-]?[0-9]+$/.test(v.toString().trim()) && Number.isSafeInteger(+v)], Int: [(v) => +v, (v) => /^[+-]?[0-9]+$/.test(v.toString().trim()) && Number.isSafeInteger(+v)],
UnsignedInt: [(v) => +v, (v) => /^(-0|\+?[0-9]+)$/.test(v.toString().trim()) && Number.isSafeInteger(+v)], UnsignedInt: [(v) => +v, (v) => /^(?:-0|\+?[0-9]+)$/.test(v.toString().trim()) && Number.isSafeInteger(+v)],
PositiveInt: [(v) => +v, (v) => /^\+?[1-9][0-9]*$/.test(v.toString().trim()) && Number.isSafeInteger(+v)], PositiveInt: [(v) => +v, (v) => /^\+?[1-9][0-9]*$/.test(v.toString().trim()) && Number.isSafeInteger(+v)],
Float: [(v) => +v, (v) => Number.isFinite(+v)], Float: [(v) => +v, (v) => Number.isFinite(+v)],
+4 -3
View File
@@ -1,3 +1,4 @@
/* eslint-disable node/no-deprecated-api */
const map = {}; const map = {};
require('source-map-support').install({ require('source-map-support').install({
handleUncaughtExceptions: false, handleUncaughtExceptions: false,
@@ -27,7 +28,7 @@ const remove = [
/(const|let|var)\s+__dirname\s*=\s*(path\.)?dirname\s*\(\s*__filename\s*\)\s*;?/g, /(const|let|var)\s+__dirname\s*=\s*(path\.)?dirname\s*\(\s*__filename\s*\)\s*;?/g,
// by tsdown // by tsdown
/(const|let|var)\s+getFilename\s*=\s*\(\s*\)\s*=>\s*fileURLToPath\s*\(\s*import\.meta\.url\s*\)\s*;?/g, /(const|let|var)\s+getFilename\s*=\s*\(\s*\)\s*=>\s*fileURLToPath\s*\(\s*import\.meta\.url\s*\)\s*;?/g,
/(const|let|var)\s+__filename\s*=\s*(\/\*\s*@__PURE__\s*\*\/)?\s*getFilename\s*\(\s*\)\s*;?/g, /(const|let|var)\s+__filename\s*=\s*(?:(\/\*\s*@__PURE__\s*\*\/)\s*)?getFilename\s*\(\s*\)\s*;?/g,
]; ];
function tryTransform(filename, content, tsx = true) { function tryTransform(filename, content, tsx = true) {
for (const regex of remove) content = content.replace(regex, ''); for (const regex of remove) content = content.replace(regex, '');
@@ -93,10 +94,10 @@ require.extensions['.jsc'] = function loader(module, filename) {
if (![12, 13, 14, 15, 16, 17].filter((i) => process.version.startsWith(`v${i}`)).length) { if (![12, 13, 14, 15, 16, 17].filter((i) => process.version.startsWith(`v${i}`)).length) {
bytecode.subarray(16, 20).copy(buf, 16); bytecode.subarray(16, 20).copy(buf, 16);
} }
// eslint-disable-next-line no-return-assign
const length = buf.subarray(8, 12).reduce((sum, number, power) => sum += number * (256 ** power), 0); const length = buf.subarray(8, 12).reduce((sum, number, power) => sum += number * (256 ** power), 0);
let dummyCode = ''; let dummyCode = '';
if (length > 1) dummyCode = `"${'\u200b'.repeat(length - 2)}"`; if (length > 1) dummyCode = `"${'\u200B'.repeat(length - 2)}"`;
const script = new vm.Script(dummyCode, { const script = new vm.Script(dummyCode, {
filename, filename,
lineOffset: 0, lineOffset: 0,
+3 -3
View File
@@ -21,8 +21,8 @@
"test": "node -r @hydrooj/register packages/common/tests/subtask.spec.ts && node test/entry.js", "test": "node -r @hydrooj/register packages/common/tests/subtask.spec.ts && node test/entry.js",
"benchmark": "cross-env BENCHMARK=true node test/entry.js", "benchmark": "cross-env BENCHMARK=true node test/entry.js",
"oxlint": "cd framework/eslint-config && node -r @hydrooj/register build.ts && cd ../../ && oxlint --config framework/eslint-config/.oxlintrc.json", "oxlint": "cd framework/eslint-config && node -r @hydrooj/register build.ts && cd ../../ && oxlint --config framework/eslint-config/.oxlintrc.json",
"lint": "eslint --flag v10_config_lookup_from_file packages plugins modules framework --ext .ts,.tsx,.jsx --fix", "lint": "eslint --flag v10_config_lookup_from_file packages plugins modules framework --ext .ts,.tsx,.jsx,.vue,.yaml,.yml --fix",
"lint:ci": "eslint --flag v10_config_lookup_from_file packages plugins modules framework --ext .ts,.tsx,.jsx --max-warnings=0", "lint:ci": "eslint --flag v10_config_lookup_from_file packages plugins modules framework --ext .ts,.tsx,.jsx,.vue,.yaml,.yml --max-warnings=0",
"debug": "node --trace-warnings --inspect=192.168.99.234:1111 --async-stack-traces --trace-deprecation packages/hydrooj/bin/hydrooj --debug --template", "debug": "node --trace-warnings --inspect=192.168.99.234:1111 --async-stack-traces --trace-deprecation packages/hydrooj/bin/hydrooj --debug --template",
"start": "packages/hydrooj/bin/hydrooj.js", "start": "packages/hydrooj/bin/hydrooj.js",
"postinstall": "node build/prepare.js", "postinstall": "node build/prepare.js",
@@ -46,7 +46,7 @@
"eslint": "^9.32.0", "eslint": "^9.32.0",
"eslint-import-resolver-typescript": "^4.4.4", "eslint-import-resolver-typescript": "^4.4.4",
"eslint-import-resolver-webpack": "^0.13.10", "eslint-import-resolver-webpack": "^0.13.10",
"fs-extra": "^11.3.0", "fs-extra": "^11.3.1",
"globals": "^16.3.0", "globals": "^16.3.0",
"globby": "~14.0.2", "globby": "~14.0.2",
"inspectpack": "^4.7.1", "inspectpack": "^4.7.1",
+1 -1
View File
@@ -167,7 +167,7 @@ export async function startPerformanceTest(args: { enable5: boolean }, report) {
await ProblemModel.addTestdata('system', docId, 'config.yaml', Buffer.from(yaml.dump({ await ProblemModel.addTestdata('system', docId, 'config.yaml', Buffer.from(yaml.dump({
time: '3s', time: '3s',
memory: args.enable5 ? '2g' : '512m', memory: args.enable5 ? '2g' : '512m',
cases: new Array(20).fill({ cases: Array.from({ length: 20 }).fill({
input: '1.in', input: '1.in',
output: '1.out', output: '1.out',
}), }),
+1 -1
View File
@@ -1,7 +1,7 @@
import { findFileSync, parseMemoryMB, parseTimeMS } from '@hydrooj/utils'; import { findFileSync, parseMemoryMB, parseTimeMS } from '@hydrooj/utils';
import { CompilableSource, ProblemConfigFile } from './types'; import { CompilableSource, ProblemConfigFile } from './types';
// eslint-disable-next-line @typescript-eslint/no-unused-vars // eslint-disable-next-line ts/no-unused-vars
export async function readYamlCases(cfg: ProblemConfigFile = {}, checkFile = (s: CompilableSource, errMsg: string) => s) { export async function readYamlCases(cfg: ProblemConfigFile = {}, checkFile = (s: CompilableSource, errMsg: string) => s) {
const config: any = { const config: any = {
checker_type: cfg.checker_type || 'default', checker_type: cfg.checker_type || 'default',
+1 -1
View File
@@ -1,4 +1,4 @@
export * from './lang'; export * from './lang';
export * from './status'; export * from './status';
export * from './types';
export * from './subtask'; export * from './subtask';
export * from './types';
+8 -7
View File
@@ -2,18 +2,18 @@ import { parseMemoryMB, parseTimeMS } from '@hydrooj/utils/lib/common';
export function convertIniConfig(ini: string) { export function convertIniConfig(ini: string) {
const f = ini.split('\n'); const f = ini.split('\n');
const count = parseInt(f[0], 10); const count = Number.parseInt(f[0], 10);
const res = { subtasks: [] }; const res = { subtasks: [] };
for (let i = 1; i <= count; i++) { for (let i = 1; i <= count; i++) {
if (!f[i]?.trim()) throw new Error('Testdata count incorrect.'); if (!f[i]?.trim()) throw new Error('Testdata count incorrect.');
const [input, output, time, score, memory] = f[i].split('|'); const [input, output, time, score, memory] = f[i].split('|');
const cur = { const cur = {
cases: [{ input, output }], cases: [{ input, output }],
score: parseInt(score, 10), score: Number.parseInt(score, 10),
time: `${time}s`, time: `${time}s`,
memory: '256m', memory: '256m',
}; };
if (!Number.isNaN(parseInt(memory, 10))) cur.memory = `${Math.floor(parseInt(memory, 10) / 1024)}m`; if (!Number.isNaN(Number.parseInt(memory, 10))) cur.memory = `${Math.floor(Number.parseInt(memory, 10) / 1024)}m`;
res.subtasks.push(cur); res.subtasks.push(cur);
} }
return res; return res;
@@ -29,7 +29,8 @@ interface MatchRule {
const SubtaskMatcher: MatchRule[] = [ const SubtaskMatcher: MatchRule[] = [
{ {
regex: /^(([A-Za-z0-9._-]*?)(?:(\d*)[-_])?(\d+))\.(in|IN|txt|TXT|in\.txt|IN\.TXT)$/, // eslint-disable-next-line regexp/no-super-linear-backtracking
regex: /^(([\w.-]*?)(?:(\d*)[-_])?(\d+))\.(in|IN|txt|TXT|in\.txt|IN\.TXT)$/,
output: (a) => ['out', 'ans'] output: (a) => ['out', 'ans']
.flatMap((i) => [i, i.toUpperCase(), `${i}.txt`, `${i.toUpperCase()}.TXT`]) .flatMap((i) => [i, i.toUpperCase(), `${i}.txt`, `${i.toUpperCase()}.TXT`])
.flatMap((i) => [`${a[1]}.${i}`, `${a[1]}.${i}`.replace(/input/g, 'output').replace(/INPUT/g, 'OUTPUT')]) .flatMap((i) => [`${a[1]}.${i}`, `${a[1]}.${i}`.replace(/input/g, 'output').replace(/INPUT/g, 'OUTPUT')])
@@ -39,7 +40,7 @@ const SubtaskMatcher: MatchRule[] = [
preferredScorerType: (a) => (a[3] ? 'min' : 'sum'), preferredScorerType: (a) => (a[3] ? 'min' : 'sum'),
}, },
{ {
regex: /^([^\d]*)\.(in|IN)(\d+)$/, regex: /^(\D*)\.(in|IN)(\d+)$/,
output: (a) => [ output: (a) => [
`${a[1]}.${a[2] === 'in' ? 'ou' : 'OU'}${a[3]}`, `${a[1]}.${a[2] === 'in' ? 'ou' : 'OU'}${a[3]}`,
`${a[1]}.${a[2] === 'in' ? 'out' : 'OUT'}${a[3]}`, `${a[1]}.${a[2] === 'in' ? 'out' : 'OUT'}${a[3]}`,
@@ -49,14 +50,14 @@ const SubtaskMatcher: MatchRule[] = [
preferredScorerType: () => 'sum', preferredScorerType: () => 'sum',
}, },
{ {
regex: /^([^\d]*)([0-9]+)([-_])([0-9]+)\.(in|IN)$/, regex: /^(\D*)([0-9]+)([-_])([0-9]+)\.(in|IN)$/,
output: (a) => ['out', 'ans', 'OUT', 'ANS'].flatMap((i) => `${a[1]}${a[2]}${a[3]}${a[4]}.${i}`), output: (a) => ['out', 'ans', 'OUT', 'ANS'].flatMap((i) => `${a[1]}${a[2]}${a[3]}${a[4]}.${i}`),
id: (a) => +a[4], id: (a) => +a[4],
subtask: (a) => +a[2], subtask: (a) => +a[2],
preferredScorerType: () => 'min', preferredScorerType: () => 'min',
}, },
{ {
regex: /^(([0-9]+)[-_](?:.*))\.(in|IN)$/, regex: /^(([0-9]+)[-_].*)\.(in|IN)$/,
output: (a) => ['out', 'ans', 'OUT', 'ANS'].flatMap((i) => `${a[1]}.${i}`), output: (a) => ['out', 'ans', 'OUT', 'ANS'].flatMap((i) => `${a[1]}.${i}`),
id: (a) => +a[2], id: (a) => +a[2],
subtask: () => 1, subtask: () => 1,
+6 -6
View File
@@ -66,13 +66,13 @@ export interface ProblemConfigFile {
export interface FileInfo { export interface FileInfo {
/** storage path */ /** storage path */
_id: string, _id: string;
/** filename */ /** filename */
name: string, name: string;
/** file size (in bytes) */ /** file size (in bytes) */
size: number, size: number;
etag: string, etag: string;
lastModified: Date, lastModified: Date;
} }
export interface JudgeMeta { export interface JudgeMeta {
@@ -113,7 +113,7 @@ export interface RecordPayload extends RecordJudgeInfo {
/** 0 if pretest&script */ /** 0 if pretest&script */
contest?: string; contest?: string;
files?: Record<string, string> files?: Record<string, string>;
} }
export interface JudgeRequest extends Omit<RecordPayload, 'testCases'> { export interface JudgeRequest extends Omit<RecordPayload, 'testCases'> {
@@ -91,7 +91,7 @@ function MonacoContainer({
if (!editor) return; if (!editor) return;
const current = editor.getValue({ lineEnding: '\n', preserveBOM: false }); const current = editor.getValue({ lineEnding: '\n', preserveBOM: false });
const diff = diffLines(current, config); const diff = diffLines(current, config);
const ops: { range: any; text: string }[] = []; const ops: { range: any, text: string }[] = [];
let cursor = 1; let cursor = 1;
for (const line of diff) { for (const line of diff) {
if (line.added) { if (line.added) {
@@ -109,11 +109,7 @@ function MonacoContainer({
} }
model.pushEditOperations([], ops, undefined); model.pushEditOperations([], ops, undefined);
}, [editor, config]); }, [editor, config]);
return ( return <div ref={initializeEditor} style={{ width: '100%', height: '500px' }} />;
<div ref={initializeEditor} style={{
width: '100%', height: '500px',
}} />
);
} }
export default function ConfigEditor({ export default function ConfigEditor({
@@ -166,8 +162,12 @@ export default function ConfigEditor({
<Allotment onChange={setSize}> <Allotment onChange={setSize}>
<Allotment.Pane> <Allotment.Pane>
<MonacoContainer <MonacoContainer
editorCallback={registerAction} schema={schema} monaco={monaco} editorCallback={registerAction}
config={stringConfig} setValue={updateFromMonaco} setError={setInfo} schema={schema}
monaco={monaco}
config={stringConfig}
setValue={updateFromMonaco}
setError={setInfo}
size={size[0]} size={size[0]}
dark={document.documentElement.className.includes('theme--dark')} dark={document.documentElement.className.includes('theme--dark')}
/> />
@@ -79,7 +79,7 @@ function DraggableSelection({
const AutoComplete = forwardRef(function Impl<T>(props: AutoCompleteProps<T>, ref: React.Ref<AutoCompleteHandle<T>>) { const AutoComplete = forwardRef(function Impl<T>(props: AutoCompleteProps<T>, ref: React.Ref<AutoCompleteHandle<T>>) {
const { const {
multi = false, width = '100%', height = 'auto', multi = false, width = '100%', height = 'auto',
freeSolo = false, allowEmptyQuery = false, listStyle = {}, // eslint-disable-line @eslint-react/no-unstable-default-props freeSolo = false, allowEmptyQuery = false, listStyle = {},
disabled = false, disabledHint = '', draggable = multi, disabled = false, disabledHint = '', draggable = multi,
} = props; } = props;
const queryItems = props.queryItems ?? (() => []); const queryItems = props.queryItems ?? (() => []);
@@ -87,7 +87,7 @@ const AutoComplete = forwardRef(function Impl<T>(props: AutoCompleteProps<T>, re
const itemText = props.itemText ?? ((item) => item.toString()); const itemText = props.itemText ?? ((item) => item.toString());
const itemKey = props.itemKey ?? itemText; const itemKey = props.itemKey ?? itemText;
const onChange = props.onChange ?? (() => { }); const onChange = props.onChange ?? (() => { });
const freeSoloConverter = freeSolo ? props.freeSoloConverter ?? ((i) => i) : ((i) => i); const freeSoloConverter = freeSolo ? props.freeSoloConverter ?? ((i) => i) : (i) => i;
const [focused, setFocused] = useState(false); // is focused const [focused, setFocused] = useState(false); // is focused
const [selectedKeys, setSelectedKeys] = useState(props.selectedKeys || []); // keys of selected items const [selectedKeys, setSelectedKeys] = useState(props.selectedKeys || []); // keys of selected items
+3 -3
View File
@@ -1,6 +1,6 @@
export { default as ComponentsProvider } from './provider'; export { default as AutoComplete, type AutoCompleteHandle, type AutoCompleteProps } from './autocomplete/AutoComplete';
export { default as CustomSelectAutoComplete } from './autocomplete/CustomSelectAutoComplete';
export { default as ConfigEditor } from './ConfigEditor'; export { default as ConfigEditor } from './ConfigEditor';
export { default as Icon } from './Icon'; export { default as Icon } from './Icon';
export { default as Notification } from './Notification'; export { default as Notification } from './Notification';
export { default as AutoComplete, type AutoCompleteHandle, type AutoCompleteProps } from './autocomplete/AutoComplete'; export { default as ComponentsProvider } from './provider';
export { default as CustomSelectAutoComplete } from './autocomplete/CustomSelectAutoComplete';
+1 -1
View File
@@ -21,7 +21,7 @@
"@types/diff": "^8.0.0", "@types/diff": "^8.0.0",
"@types/js-yaml": "^4.0.9", "@types/js-yaml": "^4.0.9",
"@types/lodash": "^4.17.20", "@types/lodash": "^4.17.20",
"sass": "^1.89.2" "sass": "^1.90.0"
}, },
"peerDependencies": { "peerDependencies": {
"react": "*", "react": "*",
+1 -1
View File
@@ -27,7 +27,7 @@ class FpsProblemImportHandler extends Handler {
samples: p.sample_input?.map((input, i) => [input, p.sample_output[i]]), samples: p.sample_input?.map((input, i) => [input, p.sample_output[i]]),
hint: p.hint?.[0], hint: p.hint?.[0],
source: p.source?.join(' '), source: p.source?.join(' '),
}, 'html', (s) => this.translate(s)).replace(/<math xm<x>lns=/g, '<math xmlns=').replace(/\[\/?md]/g, ''); }, 'html', (s) => this.translate(s)).replace(/<math xm<x>lns=/g, '<math xmlns=').replace(/\[\/?md\]/g, '');
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,
+5 -5
View File
@@ -7,11 +7,11 @@ const buffer = fs.readFileSync(findFileSync('@hydrooj/geoip/GeoLite2-City.mmdb')
const reader = new Reader(buffer); const reader = new Reader(buffer);
export interface Result { export interface Result {
location?: string, location?: string;
continent?: string, continent?: string;
country?: string, country?: string;
city?: string, city?: string;
display: string display: string;
} }
export default class GeoIPService extends Service { export default class GeoIPService extends Service {
+1 -1
View File
@@ -1 +1 @@
geoip_locale: zh-CN geoip_locale: zh-CN
+1 -1
View File
@@ -1 +1 @@
geoip_locale: zh-CN geoip_locale: zh-CN
+26 -26
View File
@@ -1,38 +1,38 @@
c: c:
compile: /usr/bin/gcc -O2 -Wall -std=c99 -o foo foo.c -lm compile: /usr/bin/gcc -O2 -Wall -std=c99 -o foo foo.c -lm
cc: cc:
compile: /usr/bin/g++ -O2 -Wall -std=c++11 -o foo foo.cc -lm compile: /usr/bin/g++ -O2 -Wall -std=c++11 -o foo foo.cc -lm
pas: pas:
compile: /usr/bin/fpc -O2 -ofoo foo.pas compile: /usr/bin/fpc -O2 -ofoo foo.pas
java: java:
compile: /usr/bin/bash -c "javac -d /w -encoding utf8 ./Main.java && jar cvf Main.jar *.class >/dev/null" compile: /usr/bin/bash -c "javac -d /w -encoding utf8 ./Main.java && jar cvf Main.jar *.class >/dev/null"
code_file: Main.java code_file: Main.java
target: Main.jar target: Main.jar
execute: /usr/bin/java -Dfile.encoding="UTF-8" -cp Main.jar Main execute: /usr/bin/java -Dfile.encoding="UTF-8" -cp Main.jar Main
time_limit_rate: 2 time_limit_rate: 2
compile_time_limit: 30000 compile_time_limit: 30000
compile_memory_limit: 1024 compile_memory_limit: 1024
py3: py3:
compile: /usr/bin/python3 -c "import py_compile; py_compile.compile('/w/foo.py', '/w/foo', doraise=True)" compile: /usr/bin/python3 -c "import py_compile; py_compile.compile('/w/foo.py', '/w/foo', doraise=True)"
code_file: foo.py code_file: foo.py
execute: /usr/bin/python3 foo execute: /usr/bin/python3 foo
php: php:
execute: /usr/bin/php foo.php execute: /usr/bin/php foo.php
rs: rs:
compile: /usr/bin/rustc -O -o /w/foo /w/foo.rs compile: /usr/bin/rustc -O -o /w/foo /w/foo.rs
compile_time_limit: 30000 compile_time_limit: 30000
hs: hs:
compile: /usr/bin/ghc -O -outputdir /tmp -o /w/foo /w/foo.hs compile: /usr/bin/ghc -O -outputdir /tmp -o /w/foo /w/foo.hs
compile_time_limit: 30000 compile_time_limit: 30000
js: js:
execute: /usr/bin/jsc foo.js execute: /usr/bin/jsc foo.js
go: go:
compile: /usr/bin/go build -o /w/foo /w/foo.go compile: /usr/bin/go build -o /w/foo /w/foo.go
compile_time_limit: 30000 compile_time_limit: 30000
rb: rb:
execute: /usr/bin/ruby foo.rb execute: /usr/bin/ruby foo.rb
compile_time_limit: 30000 compile_time_limit: 30000
cs: cs:
compile: /usr/bin/mcs csc -optimize+ -out:/w/foo.exe /w/foo.cs compile: /usr/bin/mcs csc -optimize+ -out:/w/foo.exe /w/foo.cs
execute: /usr/bin/mono foo.exe execute: /usr/bin/mono foo.exe
compile_time_limit: 30000 compile_time_limit: 30000
+1 -1
View File
@@ -36,4 +36,4 @@ Unknown language type.: 未知的语言类型。
Unsupported language {0}.: 不支持的语言 {0}。 Unsupported language {0}.: 不支持的语言 {0}。
User output longer than standard answer.: 选手输出比标准输出长。 User output longer than standard answer.: 选手输出比标准输出长。
You did not specify a checker.: 你没有指定比较器文件。 You did not specify a checker.: 你没有指定比较器文件。
Your program returned {0}.: 您的程序返回了{0}。 Your program returned {0}.: 您的程序返回了{0}。
+1 -1
View File
@@ -8,7 +8,7 @@
"dependencies": { "dependencies": {
"@hydrooj/common": "workspace:^", "@hydrooj/common": "workspace:^",
"@hydrooj/utils": "workspace:^", "@hydrooj/utils": "workspace:^",
"@zip.js/zip.js": "^2.7.71", "@zip.js/zip.js": "^2.7.72",
"cac": "^6.7.14", "cac": "^6.7.14",
"p-queue": "^8.1.0", "p-queue": "^8.1.0",
"schemastery": "^3.16.1", "schemastery": "^3.16.1",
+3 -4
View File
@@ -17,9 +17,9 @@ export interface CheckConfig {
} }
type Checker = (config: CheckConfig) => Promise<{ type Checker = (config: CheckConfig) => Promise<{
status: number, status: number;
score: number, score: number;
message: string, message: string;
}>; }>;
function parseDiffMsg(msg: string) { function parseDiffMsg(msg: string) {
@@ -206,7 +206,6 @@ const checkers: Record<string, Checker> = new Proxy({
* stderr:输出错误报告 * stderr:输出错误报告
*/ */
async syzoj(config) { async syzoj(config) {
// eslint-disable-next-line prefer-const
let { status, stdout, stderr } = await runQueued(config.execute, { let { status, stdout, stderr } = await runQueued(config.execute, {
copyIn: { copyIn: {
input: config.input, input: config.input,
+3 -3
View File
@@ -1,9 +1,9 @@
import { STATUS_TEXTS } from '@hydrooj/common'; import { STATUS_TEXTS } from '@hydrooj/common';
interface CompileErrorInfo { interface CompileErrorInfo {
stdout?: string, stdout?: string;
stderr?: string, stderr?: string;
status?: number status?: number;
} }
export class CompileError extends Error { export class CompileError extends Error {
+1 -1
View File
@@ -10,7 +10,7 @@ interface Task {
compile: () => Promise<void>; compile: () => Promise<void>;
judgeCase: (c: NormalizedCase) => ( judgeCase: (c: NormalizedCase) => (
(ctx: Context, ctxSubtask: ContextSubTask, runner?: Function) => Promise<JudgeResultBody['case']> (ctx: Context, ctxSubtask: ContextSubTask, runner?: Function) => Promise<JudgeResultBody['case']>
) );
} }
const Score = { const Score = {
+1 -1
View File
@@ -33,7 +33,7 @@ export async function judge({
if (!Object.keys(config.answers).length) throw new FormatError('Invalid standard answer.'); if (!Object.keys(config.answers).length) throw new FormatError('Invalid standard answer.');
for (const key in config.answers) { for (const key in config.answers) {
const ansInfo = config.answers[key] as [string | string[], number] | Record<string, number>; const ansInfo = config.answers[key] as [string | string[], number] | Record<string, number>;
// eslint-disable-next-line @typescript-eslint/no-loop-func // eslint-disable-next-line ts/no-loop-func
const report = (status: STATUS, score: number, message: string) => { const report = (status: STATUS, score: number, message: string) => {
const [subtaskId, caseId] = key.split('-').map(Number); const [subtaskId, caseId] = key.split('-').map(Number);
totalScore += score; totalScore += score;
+2 -2
View File
@@ -27,8 +27,8 @@ export class Stream extends EventEmitter {
break; break;
case 2: case 2:
this.emit('output', { this.emit('output', {
index: (data[1] >> 4) & 0xf, index: (data[1] >> 4) & 0xF,
fd: (data[1]) & 0xf, fd: (data[1]) & 0xF,
content: data.subarray(2), content: data.subarray(2),
}); });
break; break;
+1 -1
View File
@@ -1,6 +1,6 @@
import { DetailType, STATUS } from '@hydrooj/common'; import { DetailType, STATUS } from '@hydrooj/common';
const operation = /^\s*(status|score)\((\d+)\)\s*(.*)$/m; const operation = /^\s*(status|score)\((\d+)\)\s*((\S|$).*)$/m;
export function parse(output: string, fullscore: number, detail: DetailType) { export function parse(output: string, fullscore: number, detail: DetailType) {
let status = STATUS.STATUS_WRONG_ANSWER; let status = STATUS.STATUS_WRONG_ANSWER;
+2 -2
View File
@@ -4,7 +4,7 @@ import { CompilableSource } from '@hydrooj/common';
import { fs } from '@hydrooj/utils'; import { fs } from '@hydrooj/utils';
import { FormatError } from './error'; import { FormatError } from './error';
const EMPTY_STR = /^[ \r\n\t]*$/i; const EMPTY_STR = /^[ \r\n\t]*$/;
export const cmd = parse; export const cmd = parse;
@@ -34,7 +34,7 @@ export function compilerText(...messages: string[]) {
function restrictFile(p: string) { function restrictFile(p: string) {
if (!p) return '/'; if (!p) return '/';
if (p[0] === '/') p = ''; if (p[0] === '/') p = '';
return p.replace(/\.\./gmi, ''); return p.replace(/\.\./g, '');
} }
export function ensureFile(folder: string) { export function ensureFile(folder: string) {
-1
View File
@@ -1,4 +1,3 @@
/* eslint-disable import/no-dynamic-require */
import cac from 'cac'; import cac from 'cac';
import { getAddons } from '../src/options'; import { getAddons } from '../src/options';
+1 -1
View File
@@ -114,4 +114,4 @@ user_lostpass: Lost Password
user_register: Register user_register: Register
user_sudo: Entering Sudo Mode user_sudo: Entering Sudo Mode
wiki_about: About wiki_about: About
wiki_help: Help wiki_help: Help
-3
View File
@@ -41,9 +41,6 @@ __langname: 한국어
(All Problems): (전체 문제) (All Problems): (전체 문제)
(None): (N/A) (None): (N/A)
(Not changed): 수정되지 않음 (Not changed): 수정되지 않음
#Sample Input: Sample Input
#Sample Output: Sample Output
#Star: Star
Aborted: 중지됨 Aborted: 중지됨
About {0}: '{0}에 대하여' About {0}: '{0}에 대하여'
About Markdown: Markdown에 대하여 About Markdown: Markdown에 대하여
+5 -5
View File
@@ -3,7 +3,7 @@ __id: zh
__interface: yes __interface: yes
__langname: 简体中文 __langname: 简体中文
'"{data}" copied to clipboard!': “{data}”已复制到剪贴板! '"{data}" copied to clipboard!': “{data}”已复制到剪贴板!
'{0} {1} delete your discussion reply {2} in "{3}"({4}).' : '{0} {1} 从讨论 {3}({4}) 删除了你的回复 {2}。' '{0} {1} delete your discussion reply {2} in "{3}"({4}).': '{0} {1} 从讨论 {3}({4}) 删除了你的回复 {2}。'
'{0} comments': '{0} 条评论' '{0} comments': '{0} 条评论'
'{0} discussions': '{0} 条讨论' '{0} discussions': '{0} 条讨论'
'{0} is a moderator of this domain.': '{}是这个域的管理者之一。' '{0} is a moderator of this domain.': '{}是这个域的管理者之一。'
@@ -38,7 +38,7 @@ __langname: 简体中文
'The value `{1}` of {0} already exists.': '{0} 的值 `{1}` 已经存在。' 'The value `{1}` of {0} already exists.': '{0} 的值 `{1}` 已经存在。'
'Yes': 是 'Yes': 是
"Effects only when Difficulty is not 'Use algorithm calculated'.": 仅当难度不为“使用算法计算”时才起效。 "Effects only when Difficulty is not 'Use algorithm calculated'.": 仅当难度不为“使用算法计算”时才起效。
"Or upload a file:": 或者上传一个文件: 'Or upload a file:': 或者上传一个文件:
"Split by ',' '.'": 由“, ”或“,”分隔。 "Split by ',' '.'": 由“, ”或“,”分隔。
"What's file?": 什么是文件? "What's file?": 什么是文件?
(All Problems): (所有题目) (All Problems): (所有题目)
@@ -64,8 +64,8 @@ Add new data: 添加新数据
Add User: 添加用户 Add User: 添加用户
Add: 添加 Add: 添加
Additional File: 附加文件 Additional File: 附加文件
Admin {0} delete your discussion "{1}". : 管理员 {0} 删除了你的讨论 "{1}"。 Admin {0} delete your discussion "{1}".: 管理员 {0} 删除了你的讨论 "{1}"。
Admin {0} delete your discussion tail reply {1} in "{2}"({3}). : 管理员 {0} 从讨论 {2}({3}) 中删除了你的楼中楼回复 {1}。 Admin {0} delete your discussion tail reply {1} in "{2}"({3}).: 管理员 {0} 从讨论 {2}({3}) 中删除了你的楼中楼回复 {1}。
All {0} Contests: 所有 {0} 比赛 All {0} Contests: 所有 {0} 比赛
All Contests: 所有比赛 All Contests: 所有比赛
All Homeworks: 所有作业 All Homeworks: 所有作业
@@ -947,4 +947,4 @@ You've already attended this contest.: 您已经参加本次比赛。
You've already claimed this homework.: 您已认领过该作业。 You've already claimed this homework.: 您已认领过该作业。
You've already voted.: 您已经投过票。 You've already voted.: 您已经投过票。
Your permission: 您的权限 Your permission: 您的权限
Your role: 您的角色 Your role: 您的角色
+1 -1
View File
@@ -563,4 +563,4 @@ You've already attended this contest.: 您已經參加本次比賽。
You've already claimed this homework.: 您已認領該功課。 You've already claimed this homework.: 您已認領該功課。
You've already voted.: 您已經投過票。 You've already voted.: 您已經投過票。
Your permission: 您的許可權 Your permission: 您的許可權
Your role: 您的角色 Your role: 您的角色
+2 -2
View File
@@ -23,14 +23,14 @@
"@hydrooj/utils": "workspace:^", "@hydrooj/utils": "workspace:^",
"@simplewebauthn/server": "13.1.2", "@simplewebauthn/server": "13.1.2",
"@undefined-moe/require-resolve-hook": "^1.1.0", "@undefined-moe/require-resolve-hook": "^1.1.0",
"@zip.js/zip.js": "^2.7.71", "@zip.js/zip.js": "^2.7.72",
"adm-zip": "0.5.16", "adm-zip": "0.5.16",
"cac": "^6.7.14", "cac": "^6.7.14",
"cordis": "4.0.0-beta.5", "cordis": "4.0.0-beta.5",
"csv-stringify": "^6.6.0", "csv-stringify": "^6.6.0",
"emoji-regex": "^10.4.0", "emoji-regex": "^10.4.0",
"emojis-list": "2.1.0", "emojis-list": "2.1.0",
"fs-extra": "^11.3.0", "fs-extra": "^11.3.1",
"js-yaml": "^4.1.0", "js-yaml": "^4.1.0",
"koa-proxies": "^0.12.4", "koa-proxies": "^0.12.4",
"koa-static-cache": "^5.1.4", "koa-static-cache": "^5.1.4",
+3 -3
View File
@@ -22,13 +22,13 @@ langs:
__hint__: __hint__:
- ~ All keys starting with _ will be ignored - ~ All keys starting with _ will be ignored
- ~ Entries named like [foo], [foo].[bar] [foo].[baz] - ~ Entries named like [foo], [foo].[bar] [foo].[baz]
- will be merged into one category. - . will be merged into one category.
- ~ A entry named [foo].[bar] will inhert all options - ~ A entry named [foo].[bar] will inhert all options
- defined in entry [foo] if not overwritten - . defined in entry [foo] if not overwritten
- ~ code_file option defaults to foo.[name] - ~ code_file option defaults to foo.[name]
- ~ highlight option defaults to [name] - ~ highlight option defaults to [name]
- ~ if highlight option was set, monaco option defaults - ~ if highlight option was set, monaco option defaults
- to [highlight], otherwise defaults to [name] - . to [highlight], otherwise defaults to [name]
- ~ Compiling be be ignored if compile option wasn't set - ~ Compiling be be ignored if compile option wasn't set
- ~ Key marked with a ? means the option is optional - ~ Key marked with a ? means the option is optional
- (name): - (name):
+2 -1
View File
@@ -1,3 +1,4 @@
/* eslint-disable ts/no-unsafe-declaration-merging */
import LoggerService from '@cordisjs/plugin-logger'; import LoggerService from '@cordisjs/plugin-logger';
import { TimerService } from '@cordisjs/plugin-timer'; import { TimerService } from '@cordisjs/plugin-timer';
import * as cordis from 'cordis'; import * as cordis from 'cordis';
@@ -25,7 +26,7 @@ function provideModule<T extends keyof ModuleInterfaces>(type: T, id: string, mo
export type Fiber = cordis.Fiber<Context>; export type Fiber = cordis.Fiber<Context>;
export { Disposable, Plugin, FiberState } from 'cordis'; export { Disposable, FiberState, Plugin } from 'cordis';
export interface Context extends cordis.Context { export interface Context extends cordis.Context {
[Context.events]: EventMap & cordis.Events<Context>; [Context.events]: EventMap & cordis.Events<Context>;
+2 -2
View File
@@ -13,8 +13,8 @@ import {
const argv = cac().parse(); const argv = cac().parse();
const tmpdir = path.resolve(os.tmpdir(), 'hydro'); const tmpdir = path.resolve(os.tmpdir(), 'hydro');
const COMMENTS = /((\/\/.*$)|(\/\*[\s\S]*?\*\/))/mg; const COMMENTS = /((\/\/.*$)|(\/\*[\s\S]*?\*\/))/gm;
const ARR = /=>.*$/mg; const ARR = /=>.*$/gm;
function parseParameters(fn: Function) { function parseParameters(fn: Function) {
const code = fn.toString() const code = fn.toString()
.replace(COMMENTS, '') .replace(COMMENTS, '')
-1
View File
@@ -1,4 +1,3 @@
/* eslint-disable import/no-dynamic-require */
/* eslint-disable no-await-in-loop */ /* eslint-disable no-await-in-loop */
import '../lib/index'; import '../lib/index';
+7 -7
View File
@@ -42,7 +42,7 @@ export class ContestListHandler extends Handler {
if (group && !groups.includes(group)) throw new NotAssignedError(group); if (group && !groups.includes(group)) throw new NotAssignedError(group);
const rules = Object.keys(contest.RULES).filter((i) => !contest.RULES[i].hidden); const rules = Object.keys(contest.RULES).filter((i) => !contest.RULES[i].hidden);
const escaped = escapeRegExp(q.toLowerCase()); const escaped = escapeRegExp(q.toLowerCase());
const $regex = new RegExp(q.length >= 2 ? escaped : `\\A${escaped}`, 'gmi'); const $regex = new RegExp(q.length >= 2 ? escaped : `\\A${escaped}`, 'gim');
const filter = { const filter = {
...(this.user.hasPerm(PERM.PERM_VIEW_HIDDEN_CONTEST) && !group) ...(this.user.hasPerm(PERM.PERM_VIEW_HIDDEN_CONTEST) && !group)
? {} ? {}
@@ -626,10 +626,10 @@ export class ContestBalloonHandler extends ContestManagementBaseHandler {
} }
} }
type BuiltinInput = { interface BuiltinInput {
tdoc: Tdoc; tdoc: Tdoc;
groups: any[]; groups: any[];
}; }
type AnyFunction = (...args: any) => any; type AnyFunction = (...args: any) => any;
type ParseArgs<T extends { [key: string]: keyof BuiltinInput | AnyFunction | Type<any> }> = { type ParseArgs<T extends { [key: string]: keyof BuiltinInput | AnyFunction | Type<any> }> = {
[key in keyof T]: T[key] extends keyof BuiltinInput ? BuiltinInput[T[key]] : T[key] extends AnyFunction ? ReturnType<T[key]> : any [key in keyof T]: T[key] extends keyof BuiltinInput ? BuiltinInput[T[key]] : T[key] extends AnyFunction ? ReturnType<T[key]> : any
@@ -696,9 +696,9 @@ class ScoreboardService extends Service {
addView<T extends { [key: string]: keyof BuiltinInput | AnyFunction | Type<any> }>( addView<T extends { [key: string]: keyof BuiltinInput | AnyFunction | Type<any> }>(
id: string, name: string, args: T, id: string, name: string, args: T,
{ display, supportedRules, cacheTime }: { { display, supportedRules, cacheTime }: {
display: (this: ContestScoreboardHandler, args: ParseArgs<T>) => Promise<void>, display: (this: ContestScoreboardHandler, args: ParseArgs<T>) => Promise<void>;
supportedRules: string[], supportedRules: string[];
cacheTime?: number, cacheTime?: number;
}, },
) { ) {
if (this.views[id]) throw new Error(`View ${id} already exists`); if (this.views[id]) throw new Error(`View ${id} already exists`);
@@ -766,7 +766,7 @@ export async function apply(ctx: Context) {
config.lockAt = this.tdoc.lockAt; config.lockAt = this.tdoc.lockAt;
} }
const [, rows, udict, pdict] = await contest.getScoreboard.call(this, tdoc.domainId, tdoc._id, config); const [, rows, udict, pdict] = await contest.getScoreboard.call(this, tdoc.domainId, tdoc._id, config);
// eslint-disable-next-line @typescript-eslint/naming-convention // eslint-disable-next-line ts/naming-convention
const page_name = tdoc.rule === 'homework' const page_name = tdoc.rule === 'homework'
? 'homework_scoreboard' ? 'homework_scoreboard'
: 'contest_scoreboard'; : 'contest_scoreboard';
+1 -1
View File
@@ -95,7 +95,7 @@ class DiscussionNodeHandler extends DiscussionHandler {
async get(domainId: string, type: string, _name: string, page = 1) { async get(domainId: string, type: string, _name: string, page = 1) {
let name: ObjectId | string | number; let name: ObjectId | string | number;
if (ObjectId.isValid(_name)) name = new ObjectId(_name); if (ObjectId.isValid(_name)) name = new ObjectId(_name);
else if (isSafeInteger(parseInt(_name, 10))) name = parseInt(_name, 10); else if (isSafeInteger(Number.parseInt(_name, 10))) name = Number.parseInt(_name, 10);
else name = _name; else name = _name;
const hidden = this.user.own(this.vnode) || this.user.hasPerm(PERM.PERM_EDIT_DISCUSSION) ? {} : { hidden: false }; const hidden = this.user.own(this.vnode) || this.user.hasPerm(PERM.PERM_EDIT_DISCUSSION) ? {} : { hidden: false };
const [ddocs, dpcount] = await this.paginate( const [ddocs, dpcount] = await this.paginate(
+1 -1
View File
@@ -534,7 +534,7 @@ class HomeDomainCreateHandler extends Handler {
@param('name', Types.Title) @param('name', Types.Title)
@param('bulletin', Types.Content) @param('bulletin', Types.Content)
@param('avatar', Types.Content, true) @param('avatar', Types.Content, true)
// eslint-disable-next-line @typescript-eslint/no-shadow // eslint-disable-next-line ts/no-shadow
async post(_: string, id: string, name: string, bulletin: string, avatar: string) { async post(_: string, id: string, name: string, bulletin: string, avatar: string) {
const doc = await domain.get(id); const doc = await domain.get(id);
if (doc) throw new DomainAlreadyExistsError(id); if (doc) throw new DomainAlreadyExistsError(id);
+1 -1
View File
@@ -52,7 +52,7 @@ class HomeworkMainHandler extends Handler {
], ],
}, },
...group ? { assign: { $in: [group] } } : {}, ...group ? { assign: { $in: [group] } } : {},
...q ? { title: { $regex: new RegExp(q.length >= 2 ? escaped : `\\A${escaped}`, 'gmi') } } : {}, ...q ? { title: { $regex: new RegExp(q.length >= 2 ? escaped : `\\A${escaped}`, 'gim') } } : {},
}).sort({ }).sort({
penaltySince: -1, endAt: -1, beginAt: -1, _id: -1, penaltySince: -1, endAt: -1, beginAt: -1, _id: -1,
}); });
+1 -1
View File
@@ -79,7 +79,7 @@ export class JudgeResultCallbackContext {
private relatedId = new ObjectId(); private relatedId = new ObjectId();
private meta: { rejudge?: JudgeMeta['rejudge'] }; private meta: { rejudge?: JudgeMeta['rejudge'] };
constructor(public ctx: Context, public readonly task: Omit<Task, '_id'> & { type: string }) { // eslint-disable-line @typescript-eslint/no-shadow constructor(public ctx: Context, public readonly task: Omit<Task, '_id'> & { type: string }) { // eslint-disable-line ts/no-shadow
this.meta = task.meta as JudgeMeta || {}; this.meta = task.meta as JudgeMeta || {};
this.finishPromise = new Promise((resolve) => { this.finishPromise = new Promise((resolve) => {
this.resolve = resolve; this.resolve = resolve;
+1 -1
View File
@@ -239,7 +239,7 @@ class SystemUserImportHandler extends SystemHandler {
@param('draft', Types.Boolean) @param('draft', Types.Boolean)
async post(domainId: string, _users: string, draft: boolean) { async post(domainId: string, _users: string, draft: boolean) {
const users = _users.split('\n'); const users = _users.split('\n');
const udocs: { email: string, username: string, password: string, displayName?: string, [key: string]: any; }[] = []; const udocs: { email: string, username: string, password: string, displayName?: string, [key: string]: any }[] = [];
const messages = []; const messages = [];
const mapping = Object.create(null); const mapping = Object.create(null);
const groups: Record<string, string[]> = Object.create(null); const groups: Record<string, string[]> = Object.create(null);
+6 -6
View File
@@ -56,7 +56,7 @@ function buildQuery(udoc: User) {
const defaultSearch = async (domainId: string, q: string, options?: ProblemSearchOptions) => { const defaultSearch = async (domainId: string, q: string, options?: ProblemSearchOptions) => {
const escaped = escapeRegExp(q.toLowerCase()); const escaped = escapeRegExp(q.toLowerCase());
const projection: (keyof ProblemDoc)[] = ['domainId', 'docId', 'pid']; const projection: (keyof ProblemDoc)[] = ['domainId', 'docId', 'pid'];
const $regex = new RegExp(q.length >= 2 ? escaped : `\\A${escaped}`, 'gmi'); const $regex = new RegExp(q.length >= 2 ? escaped : `\\A${escaped}`, 'gim');
const filter = { $or: [{ pid: { $regex } }, { title: { $regex } }, { tag: q }] }; const filter = { $or: [{ pid: { $regex } }, { title: { $regex } }, { tag: q }] };
const pdocs = await problem.getMulti(domainId, filter, projection) const pdocs = await problem.getMulti(domainId, filter, projection)
.skip(options.skip || 0).limit(options.limit || system.get('pagination.problem')).toArray(); .skip(options.skip || 0).limit(options.limit || system.get('pagination.problem')).toArray();
@@ -109,7 +109,7 @@ export class ProblemMainHandler extends Handler {
this.response.template = 'problem_main.html'; this.response.template = 'problem_main.html';
if (!limit || limit > this.ctx.setting.get('pagination.problem') || page > 1) limit = this.ctx.setting.get('pagination.problem'); if (!limit || limit > this.ctx.setting.get('pagination.problem') || page > 1) limit = this.ctx.setting.get('pagination.problem');
this.queryContext.query = buildQuery(this.user); this.queryContext.query = buildQuery(this.user);
// eslint-disable-next-line @typescript-eslint/no-shadow // eslint-disable-next-line ts/no-shadow
const query = this.queryContext.query; const query = this.queryContext.query;
const psdict = {}; const psdict = {};
const search = Object.values(global.Hydro.module.problemSearch)[0] || defaultSearch; const search = Object.values(global.Hydro.module.problemSearch)[0] || defaultSearch;
@@ -607,7 +607,7 @@ export class ProblemEditHandler extends ProblemManageHandler {
@route('pid', Types.ProblemId) @route('pid', Types.ProblemId)
@post('title', Types.Title) @post('title', Types.Title)
@post('content', Types.Content) @post('content', Types.Content)
@post('pid', Types.ProblemId, true, (i) => /^([a-zA-Z0-9]{1,10}-)?[a-zA-Z]+[a-zA-Z0-9]*$/i.test(i)) @post('pid', Types.ProblemId, true, (i) => /^(?:[a-z0-9]{1,10}-)?[a-z][a-z0-9]*$/i.test(i))
@post('hidden', Types.Boolean) @post('hidden', Types.Boolean)
@post('tag', Types.Content, true, null, parseCategory) @post('tag', Types.Content, true, null, parseCategory)
@post('difficulty', Types.PositiveInt, (i) => +i <= 10, true) @post('difficulty', Types.PositiveInt, (i) => +i <= 10, true)
@@ -837,7 +837,7 @@ export class ProblemSolutionHandler extends ProblemDetailHandler {
if (!accepted || !this.user.hasPerm(PERM.PERM_VIEW_PROBLEM_SOLUTION_ACCEPT)) { if (!accepted || !this.user.hasPerm(PERM.PERM_VIEW_PROBLEM_SOLUTION_ACCEPT)) {
this.checkPerm(PERM.PERM_VIEW_PROBLEM_SOLUTION); this.checkPerm(PERM.PERM_VIEW_PROBLEM_SOLUTION);
} }
// eslint-disable-next-line prefer-const
let [psdocs, pcount, pscount] = await this.paginate( let [psdocs, pcount, pscount] = await this.paginate(
solution.getMulti(domainId, this.pdoc.docId), solution.getMulti(domainId, this.pdoc.docId),
page, page,
@@ -995,7 +995,7 @@ export class ProblemCreateHandler extends Handler {
@post('title', Types.Title) @post('title', Types.Title)
@post('content', Types.Content) @post('content', Types.Content)
@post('pid', Types.ProblemId, true, (i) => /^([a-zA-Z0-9]{1,10}-)?[a-zA-Z]+[a-zA-Z0-9]*$/i.test(i)) @post('pid', Types.ProblemId, true, (i) => /^(?:[a-z0-9]{1,10}-)?[a-z][a-z0-9]*$/i.test(i))
@post('hidden', Types.Boolean) @post('hidden', Types.Boolean)
@post('difficulty', Types.PositiveInt, (i) => +i <= 10, true) @post('difficulty', Types.PositiveInt, (i) => +i <= 10, true)
@post('tag', Types.Content, true, null, parseCategory) @post('tag', Types.Content, true, null, parseCategory)
@@ -1006,7 +1006,7 @@ export class ProblemCreateHandler extends Handler {
if (typeof pid !== 'string') pid = `P${pid}`; if (typeof pid !== 'string') pid = `P${pid}`;
if (pid && await problem.get(domainId, pid)) throw new ProblemAlreadyExistError(pid); if (pid && await problem.get(domainId, pid)) throw new ProblemAlreadyExistError(pid);
const docId = await problem.add(domainId, pid, title, content, this.user._id, tag ?? [], { hidden, difficulty }); const docId = await problem.add(domainId, pid, title, content, this.user._id, tag ?? [], { hidden, difficulty });
const files = new Set(Array.from(content.matchAll(/file:\/\/([a-zA-Z0-9_-]+\.[a-zA-Z0-9]+)/g)).map((i) => i[1])); const files = new Set(Array.from(content.matchAll(/file:\/\/([\w-]+\.[a-zA-Z0-9]+)/g)).map((i) => i[1]));
const tasks = []; const tasks = [];
for (const file of files) { for (const file of files) {
if (this.user._files.find((i) => i.name === file)) { if (this.user._files.find((i) => i.name === file)) {
+1 -2
View File
@@ -70,7 +70,7 @@ class RecordListHandler extends ContestDetailBaseHandler {
} }
if (pid) { if (pid) {
if (typeof pid === 'string' && tdoc && /^[A-Z]$/.test(pid)) { if (typeof pid === 'string' && tdoc && /^[A-Z]$/.test(pid)) {
pid = tdoc.pids[parseInt(pid, 36) - 10]; pid = tdoc.pids[Number.parseInt(pid, 36) - 10];
} }
const pdoc = await problem.get(domainId, pid); const pdoc = await problem.get(domainId, pid);
if (pdoc) q.pid = pdoc.docId; if (pdoc) q.pid = pdoc.docId;
@@ -341,7 +341,6 @@ class RecordMainConnectionHandler extends ConnectionHandler {
if (typeof this.pid === 'number' && rdoc.pid !== this.pid) return; if (typeof this.pid === 'number' && rdoc.pid !== this.pid) return;
if (typeof this.uid === 'number' && rdoc.uid !== this.uid) return; if (typeof this.uid === 'number' && rdoc.uid !== this.uid) return;
// eslint-disable-next-line prefer-const
let [udoc, pdoc] = await Promise.all([ let [udoc, pdoc] = await Promise.all([
user.getById(this.args.domainId, rdoc.uid), user.getById(this.args.domainId, rdoc.uid),
problem.get(rdoc.domainId, rdoc.pid), problem.get(rdoc.domainId, rdoc.pid),
+1 -1
View File
@@ -1,4 +1,4 @@
const versionNum = +process.version.replace(/v/gim, '').split('.')[0]; const versionNum = +process.version.replace(/v/gi, '').split('.')[0];
if (versionNum < 22) throw new Error('NodeJS >=22 required'); if (versionNum < 22) throw new Error('NodeJS >=22 required');
console.log('Process', process.pid, 'running as', process.env.NODE_APP_INSTANCE === '0' ? 'master' : 'worker'); console.log('Process', process.pid, 'running as', process.env.NODE_APP_INSTANCE === '0' ? 'master' : 'worker');
+114 -113
View File
@@ -1,3 +1,4 @@
/* eslint-disable perfectionist/sort-exports */
import type { AttestationFormat, CredentialDeviceType } from '@simplewebauthn/server'; import type { AttestationFormat, CredentialDeviceType } from '@simplewebauthn/server';
import type { ParsedAuthenticatorData } from '@simplewebauthn/server/helpers'; import type { ParsedAuthenticatorData } from '@simplewebauthn/server/helpers';
import type fs from 'fs'; import type fs from 'fs';
@@ -16,43 +17,43 @@ export * from '@hydrooj/common/types';
type document = typeof import('./model/document'); type document = typeof import('./model/document');
export interface System { export interface System {
_id: string, _id: string;
value: any, value: any;
} }
export interface SystemKeys { export interface SystemKeys {
'smtp.user': string, 'smtp.user': string;
'smtp.from': string, 'smtp.from': string;
'smtp.pass': string, 'smtp.pass': string;
'smtp.host': string, 'smtp.host': string;
'smtp.port': number, 'smtp.port': number;
'smtp.secure': boolean, 'smtp.secure': boolean;
'installid': string, installid: string;
'server.name': string, 'server.name': string;
'server.url': string, 'server.url': string;
'server.xff': string, 'server.xff': string;
'server.xhost': string, 'server.xhost': string;
'server.host': string, 'server.host': string;
'server.port': number, 'server.port': number;
'server.language': string, 'server.language': string;
'limit.problem_files_max': number, 'limit.problem_files_max': number;
'problem.categories': string, 'problem.categories': string;
'session.keys': string[], 'session.keys': string[];
'session.saved_expire_seconds': number, 'session.saved_expire_seconds': number;
'session.unsaved_expire_seconds': number, 'session.unsaved_expire_seconds': number;
'user.quota': number, 'user.quota': number;
} }
export interface Setting { export interface Setting {
family: string, family: string;
key: string, key: string;
range: [string, string][] | Record<string, string>, range: [string, string][] | Record<string, string>;
value: any, value: any;
type: string, type: string;
subType?: string, subType?: string;
name: string, name: string;
desc: string, desc: string;
flag: number, flag: number;
} }
export interface Authenticator { export interface Authenticator {
@@ -119,7 +120,7 @@ export interface UserPreferenceDoc {
content: string; content: string;
} }
export type ownerInfo = { owner: number, maintainer?: number[] }; export interface OwnerInfo { owner: number, maintainer?: number[] }
export type User = import('./model/user').User; export type User = import('./model/user').User;
export type Udict = Record<number, User>; export type Udict = Record<number, User>;
@@ -190,11 +191,11 @@ export type { ProblemDoc } from './model/problem';
export type ProblemDict = NumericDictionary<ProblemDoc>; export type ProblemDict = NumericDictionary<ProblemDoc>;
export interface StatusDocBase { export interface StatusDocBase {
_id: ObjectId, _id: ObjectId;
docId: any, docId: any;
docType: number, docType: number;
domainId: string, domainId: string;
uid: number, uid: number;
} }
export interface ProblemStatusDoc extends StatusDocBase { export interface ProblemStatusDoc extends StatusDocBase {
@@ -241,10 +242,10 @@ export type ScoreboardRow = ScoreboardNode[] & { raw?: any };
export type PenaltyRules = Dictionary<number>; export type PenaltyRules = Dictionary<number>;
export interface TrainingNode { export interface TrainingNode {
_id: number, _id: number;
title: string, title: string;
requireNids: number[], requireNids: number[];
pids: number[], pids: number[];
} }
export interface Tdoc extends Document { export interface Tdoc extends Document {
@@ -287,28 +288,28 @@ export interface Tdoc extends Document {
} }
export interface TrainingDoc extends Omit<Tdoc, 'docType'> { export interface TrainingDoc extends Omit<Tdoc, 'docType'> {
docType: document['TYPE_TRAINING'], docType: document['TYPE_TRAINING'];
description: string; description: string;
pin?: number; pin?: number;
dag: TrainingNode[]; dag: TrainingNode[];
} }
export interface DomainDoc extends Record<string, any> { export interface DomainDoc extends Record<string, any> {
_id: string, _id: string;
owner: number, owner: number;
roles: Dictionary<string>, roles: Dictionary<string>;
avatar: string, avatar: string;
bulletin: string, bulletin: string;
_join?: any, _join?: any;
host?: string[], host?: string[];
} }
// Message // Message
export interface MessageDoc { export interface MessageDoc {
from: number, from: number;
to: number, to: number;
content: string, content: string;
flag: number, flag: number;
} }
// Blacklist // Blacklist
@@ -382,22 +383,22 @@ export interface ContestClarificationDoc extends Document {
} }
export interface TokenDoc { export interface TokenDoc {
_id: string, _id: string;
tokenType: number, tokenType: number;
createAt: Date, createAt: Date;
updateAt: Date, updateAt: Date;
expireAt: Date, expireAt: Date;
[key: string]: any, [key: string]: any;
} }
export interface OplogDoc extends Record<string, any> { export interface OplogDoc extends Record<string, any> {
_id: ObjectId, _id: ObjectId;
type: string, type: string;
} }
export interface ContestStat extends Record<string, any> { export interface ContestStat extends Record<string, any> {
detail: Record<number, Record<string, any>>, detail: Record<number, Record<string, any>>;
unrank?: boolean, unrank?: boolean;
} }
export interface ScoreboardConfig { export interface ScoreboardConfig {
@@ -438,9 +439,9 @@ export type ContestRules = Dictionary<ContestRule>;
export type ProblemImporter = (url: string, handler: any) => Promise<[ProblemDoc, fs.ReadStream?]> | [ProblemDoc, fs.ReadStream?]; export type ProblemImporter = (url: string, handler: any) => Promise<[ProblemDoc, fs.ReadStream?]> | [ProblemDoc, fs.ReadStream?];
export interface Script { export interface Script {
run: (args: any, report: Function) => any, run: (args: any, report: Function) => any;
description: string, description: string;
validate: any, validate: any;
} }
export interface Task { export interface Task {
@@ -528,66 +529,66 @@ export interface LockDoc {
declare module './service/db' { declare module './service/db' {
interface Collections { interface Collections {
'blacklist': BlacklistDoc; blacklist: BlacklistDoc;
'domain': DomainDoc; domain: DomainDoc;
'domain.user': any; 'domain.user': any;
'record': RecordDoc; record: RecordDoc;
'record.stat': RecordStatDoc; 'record.stat': RecordStatDoc;
'record.history': RecordHistoryDoc; 'record.history': RecordHistoryDoc;
'document': any; document: any;
'document.status': StatusDocBase & { 'document.status': StatusDocBase & {
[K in keyof DocStatusType]: { docType: K } & DocStatusType[K]; [K in keyof DocStatusType]: { docType: K } & DocStatusType[K];
}[keyof DocStatusType]; }[keyof DocStatusType];
'discussion.history': DiscussionHistoryDoc; 'discussion.history': DiscussionHistoryDoc;
'user': Udoc; user: Udoc;
'user.preference': UserPreferenceDoc; 'user.preference': UserPreferenceDoc;
'vuser': VUdoc; vuser: VUdoc;
'user.group': GDoc; 'user.group': GDoc;
'check': System; check: System;
'message': MessageDoc; message: MessageDoc;
'token': TokenDoc; token: TokenDoc;
'status': any; status: any;
'oauth': OauthMap; oauth: OauthMap;
'system': System; system: System;
'task': Task; task: Task;
'storage': FileNode; storage: FileNode;
'oplog': OplogDoc; oplog: OplogDoc;
'event': EventDoc; event: EventDoc;
'opcount': OpCountDoc; opcount: OpCountDoc;
'schedule': Schedule; schedule: Schedule;
'contest.balloon': ContestBalloonDoc; 'contest.balloon': ContestBalloonDoc;
'lock': LockDoc; lock: LockDoc;
} }
} }
export interface Model { export interface Model {
blacklist: typeof import('./model/blacklist').default, blacklist: typeof import('./model/blacklist').default;
builtin: typeof import('./model/builtin'), builtin: typeof import('./model/builtin');
contest: typeof import('./model/contest'), contest: typeof import('./model/contest');
discussion: typeof import('./model/discussion'), discussion: typeof import('./model/discussion');
document: Omit<typeof import('./model/document'), 'apply'>, document: Omit<typeof import('./model/document'), 'apply'>;
domain: typeof import('./model/domain').default, domain: typeof import('./model/domain').default;
message: typeof import('./model/message').default, message: typeof import('./model/message').default;
opcount: typeof import('./model/opcount'), opcount: typeof import('./model/opcount');
problem: typeof import('./model/problem').default, problem: typeof import('./model/problem').default;
record: typeof import('./model/record').default, record: typeof import('./model/record').default;
setting: typeof import('./model/setting'), setting: typeof import('./model/setting');
solution: typeof import('./model/solution').default, solution: typeof import('./model/solution').default;
system: typeof import('./model/system'), system: typeof import('./model/system');
task: typeof import('./model/task').default, task: typeof import('./model/task').default;
schedule: typeof import('./model/schedule').default; schedule: typeof import('./model/schedule').default;
oplog: typeof import('./model/oplog'), oplog: typeof import('./model/oplog');
token: typeof import('./model/token').default, token: typeof import('./model/token').default;
training: typeof import('./model/training'), training: typeof import('./model/training');
user: typeof import('./model/user').default, user: typeof import('./model/user').default;
oauth: typeof import('./model/oauth').default, oauth: typeof import('./model/oauth').default;
storage: typeof import('./model/storage').default, storage: typeof import('./model/storage').default;
rp: typeof import('./script/rating').RpTypes, rp: typeof import('./script/rating').RpTypes;
} }
export interface GeoIP { export interface GeoIP {
provider: string, provider: string;
lookup: (ip: string, locale?: string) => any, lookup: (ip: string, locale?: string) => any;
} }
export interface ProblemSearchResponse { export interface ProblemSearchResponse {
@@ -604,9 +605,9 @@ export type ProblemSearch = (domainId: string, q: string, options?: ProblemSearc
export type UIInjectableFields = 'ProblemAdd' | 'Notification' | 'Nav' | 'UserDropdown' | 'DomainManage' | 'ControlPanel'; export type UIInjectableFields = 'ProblemAdd' | 'Notification' | 'Nav' | 'UserDropdown' | 'DomainManage' | 'ControlPanel';
export interface UI { export interface UI {
nodes: Record<UIInjectableFields, any[]>, nodes: Record<UIInjectableFields, any[]>;
getNodes: typeof import('./lib/ui').getNodes, getNodes: typeof import('./lib/ui').getNodes;
inject: typeof import('./lib/ui').inject, inject: typeof import('./lib/ui').inject;
} }
export interface ModuleInterfaces { export interface ModuleInterfaces {
@@ -629,8 +630,8 @@ export interface HydroGlobal {
declare global { declare global {
namespace NodeJS { namespace NodeJS {
interface Global { interface Global {
Hydro: HydroGlobal, Hydro: HydroGlobal;
addons: string[], addons: string[];
} }
} }
/** @deprecated */ /** @deprecated */
+1 -1
View File
@@ -6,7 +6,7 @@ type AvatarProvider = (src: string, size: number) => string;
export const providers: Record<string, AvatarProvider> = { export const providers: Record<string, AvatarProvider> = {
gravatar: (email, size) => `${global.Hydro.model.system.get('avatar.gravatar_url')}` gravatar: (email, size) => `${global.Hydro.model.system.get('avatar.gravatar_url')}`
+ `${md5((email || '').toString().trim().toLowerCase())}?d=mm&s=${size || 32}`, + `${md5((email || '').toString().trim().toLowerCase())}?d=mm&s=${size || 32}`,
qq: (id) => `//q1.qlogo.cn/g?b=qq&nk=${(/(\d+)/g.exec(id) || ['', ''])[1]}&s=160`, qq: (id) => `//q1.qlogo.cn/g?b=qq&nk=${(/(\d+)/.exec(id) || ['', ''])[1]}&s=160`,
github: (id, size) => `//github.com/${id}.png?size=${Math.min(size, 460)}`, github: (id, size) => `//github.com/${id}.png?size=${Math.min(size, 460)}`,
url: (url) => url, url: (url) => url,
}; };
+7 -7
View File
@@ -1,11 +1,11 @@
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, samplesRaw?: string;
hint?: string, hint?: string;
source?: string, source?: string;
} }
export function buildContent(source: ProblemSource, type: 'markdown' | 'html' = 'markdown', translate?: (s: string) => string) { export function buildContent(source: ProblemSource, type: 'markdown' | 'html' = 'markdown', translate?: (s: string) => string) {
+5 -5
View File
@@ -91,14 +91,14 @@ class RatingCalculator {
} }
interface RatingInputUser { interface RatingInputUser {
old: number, old: number;
uid: number, uid: number;
rank: number, rank: number;
} }
interface RatingOutputUser { interface RatingOutputUser {
new: number, new: number;
uid: number, uid: number;
} }
function calculate(users: RatingInputUser[]): RatingOutputUser[] { function calculate(users: RatingInputUser[]): RatingOutputUser[] {
+4 -4
View File
@@ -5,13 +5,13 @@ import { ObjectId as ObjectID } from 'mongodb';
import Schema from 'schemastery'; import Schema from 'schemastery';
import superagent from 'superagent'; import superagent from 'superagent';
import { Context } from './context'; import { Context } from './context';
export { ObjectId, Filter } from 'mongodb';
export { WebSocket, WebSocketServer } from '@hydrooj/framework';
export * from './utils';
export * from './interface'; export * from './interface';
export * from './typeutils'; export * from './typeutils';
export * from './utils';
export { WebSocket, WebSocketServer } from '@hydrooj/framework';
export { Filter, ObjectId } from 'mongodb';
export { export {
Schema, superagent, _, ObjectID, Zip, _, ObjectID, Schema, superagent, Zip,
}; };
/** @deprecated Use ZipReader/ZipWriter instead */ /** @deprecated Use ZipReader/ZipWriter instead */
export const AdmZip = _AdmZip; export const AdmZip = _AdmZip;
+2 -3
View File
@@ -1,4 +1,3 @@
/* eslint-disable import/no-dynamic-require */
/* eslint-disable consistent-return */ /* eslint-disable consistent-return */
/* eslint-disable simple-import-sort/imports */ /* eslint-disable simple-import-sort/imports */
import './init'; import './init';
@@ -181,7 +180,7 @@ export async function load() {
if (process.env.DEV) { if (process.env.DEV) {
const q = await simpleGit().listRemote(['--get-url']); const q = await simpleGit().listRemote(['--get-url']);
if (!q.includes('hydro-dev/Hydro')) { if (!q.includes('hydro-dev/Hydro')) {
console.warn('\x1b[93m'); console.warn('\x1B[93m');
console.warn('DISCLAIMER:'); console.warn('DISCLAIMER:');
console.warn(' You are under development mode.'); console.warn(' You are under development mode.');
console.warn(' The Hydro project is licensed under AGPL3,'); console.warn(' The Hydro project is licensed under AGPL3,');
@@ -195,7 +194,7 @@ export async function load() {
console.warn(' 这意味着除非你获得了原作者的其他授权,'); console.warn(' 这意味着除非你获得了原作者的其他授权,');
console.warn(' 你需要同样以 AGPL3 协议开源所有的修改,'); console.warn(' 你需要同样以 AGPL3 协议开源所有的修改,');
console.warn(' 并保留所有的版权声明。'); console.warn(' 并保留所有的版权声明。');
console.warn('\x1b[39m'); console.warn('\x1B[39m');
console.log(''); console.log('');
console.log('Hydro will start in 5s.'); console.log('Hydro will start in 5s.');
console.log('Hydro 将在五秒后继续启动。'); console.log('Hydro 将在五秒后继续启动。');
+1 -1
View File
@@ -641,7 +641,7 @@ const homework = buildContestRule({
); );
if (exceedSeconds < 0) return rate * jdoc.score; if (exceedSeconds < 0) return rate * jdoc.score;
let coefficient = 1; let coefficient = 1;
const keys = Object.keys(tdoc.penaltyRules).map(parseFloat).sort((a, b) => a - b); const keys = Object.keys(tdoc.penaltyRules).map(Number.parseFloat).sort((a, b) => a - b);
for (const i of keys) { for (const i of keys) {
if (i * 3600 <= exceedSeconds) coefficient = tdoc.penaltyRules[i]; if (i * 3600 <= exceedSeconds) coefficient = tdoc.penaltyRules[i];
else break; else break;
+2 -3
View File
@@ -1,4 +1,3 @@
/* eslint-disable object-curly-newline */
import assert from 'assert'; import assert from 'assert';
import { import {
Filter, FindCursor, ObjectId, OnlyFieldsOfType, PushOperator, UpdateFilter, Filter, FindCursor, ObjectId, OnlyFieldsOfType, PushOperator, UpdateFilter,
@@ -43,9 +42,9 @@ export interface DocType {
} }
export interface DocStatusType { export interface DocStatusType {
[TYPE_PROBLEM]: ProblemStatusDoc, [TYPE_PROBLEM]: ProblemStatusDoc;
// FIXME: this need to be typed // FIXME: this need to be typed
[key: number]: any [key: number]: any;
} }
export async function add<T extends keyof DocType, K extends DocType[T]['docId']>( export async function add<T extends keyof DocType, K extends DocType[T]['docId']>(
+3 -3
View File
@@ -15,8 +15,8 @@ const collUser = db.collection('domain.user');
const cache = new LRUCache<string, DomainDoc>({ max: 1000, ttl: 300 * 1000 }); const cache = new LRUCache<string, DomainDoc>({ max: 1000, ttl: 300 * 1000 });
interface DomainUserArg { interface DomainUserArg {
_id: number, _id: number;
priv: number, priv: number;
} }
class DomainModel { class DomainModel {
@@ -282,7 +282,7 @@ class DomainModel {
@ArgMethod @ArgMethod
static async getPrefixSearch(prefix: string, limit: number = 50) { static async getPrefixSearch(prefix: string, limit: number = 50) {
const $regex = new RegExp(escapeRegExp(prefix), 'mi'); const $regex = new RegExp(escapeRegExp(prefix), 'im');
const ddocs = await coll.find({ const ddocs = await coll.find({
$or: [{ _id: { $regex } }, { name: { $regex } }], $or: [{ _id: { $regex } }, { name: { $regex } }],
}).limit(limit).toArray(); }).limit(limit).toArray();
+5 -6
View File
@@ -1,4 +1,3 @@
/* eslint-disable object-curly-newline */
import { pick, sum } from 'lodash'; import { pick, sum } from 'lodash';
import moment from 'moment-timezone'; import moment from 'moment-timezone';
import { import {
@@ -133,11 +132,11 @@ export default class RecordModel {
domainId: string, pid: number, uid: number, domainId: string, pid: number, uid: number,
lang: string, code: string, addTask: boolean, lang: string, code: string, addTask: boolean,
args: { args: {
contest?: ObjectId, contest?: ObjectId;
input?: string, input?: string;
files?: Record<string, string>, files?: Record<string, string>;
hackTarget?: ObjectId, hackTarget?: ObjectId;
type: 'judge' | 'rejudge' | 'pretest' | 'hack' | 'generate', type: 'judge' | 'rejudge' | 'pretest' | 'hack' | 'generate';
} = { type: 'judge' }, } = { type: 'judge' },
) { ) {
const data: RecordDoc = { const data: RecordDoc = {
+1 -1
View File
@@ -69,7 +69,7 @@ export const Setting = (
declare global { declare global {
namespace Schemastery { namespace Schemastery {
interface Meta<T> { // eslint-disable-line @typescript-eslint/no-unused-vars interface Meta<T> { // eslint-disable-line ts/no-unused-vars
family?: string; family?: string;
secret?: boolean; secret?: boolean;
} }
+2 -2
View File
@@ -97,8 +97,8 @@ export async function get(domainId: string, tid: ObjectId) {
if (!tdoc) throw new TrainingNotFoundError(domainId, tid); if (!tdoc) throw new TrainingNotFoundError(domainId, tid);
for (const i in tdoc.dag) { for (const i in tdoc.dag) {
for (const j in tdoc.dag[i].pids) { for (const j in tdoc.dag[i].pids) {
if (Number.isSafeInteger(parseInt(tdoc.dag[i].pids[j], 10))) { if (Number.isSafeInteger(Number.parseInt(tdoc.dag[i].pids[j], 10))) {
tdoc.dag[i].pids[j] = parseInt(tdoc.dag[i].pids[j], 10); tdoc.dag[i].pids[j] = Number.parseInt(tdoc.dag[i].pids[j], 10);
} }
} }
} }
+5 -5
View File
@@ -5,7 +5,7 @@ import { serializer } from '@hydrooj/framework';
import { LoginError, UserAlreadyExistError, UserNotFoundError } from '../error'; import { LoginError, UserAlreadyExistError, UserNotFoundError } from '../error';
import { import {
Authenticator, BaseUserDict, FileInfo, GDoc, Authenticator, BaseUserDict, FileInfo, GDoc,
ownerInfo, Udict, Udoc, VUdoc, OwnerInfo, Udict, Udoc, VUdoc,
} from '../interface'; } from '../interface';
import avatar from '../lib/avatar'; import avatar from '../lib/avatar';
import pwhash from '../lib/hash.hydro'; import pwhash from '../lib/hash.hydro';
@@ -116,9 +116,9 @@ export class User {
return this; return this;
} }
own<T extends ownerInfo>(doc: T, checkPerm: bigint): boolean; own<T extends OwnerInfo>(doc: T, checkPerm: bigint): boolean;
own<T extends ownerInfo>(doc: T, exact: boolean): boolean; own<T extends OwnerInfo>(doc: T, exact: boolean): boolean;
own<T extends ownerInfo>(doc: T): boolean; own<T extends OwnerInfo>(doc: T): boolean;
own<T extends { owner: number, maintainer?: number[] }>(doc: T): boolean; own<T extends { owner: number, maintainer?: number[] }>(doc: T): boolean;
own(doc: any, arg1: any = false): boolean { own(doc: any, arg1: any = false): boolean {
if (typeof arg1 === 'bigint' && !this.hasPerm(arg1)) return false; if (typeof arg1 === 'bigint' && !this.hasPerm(arg1)) return false;
@@ -143,7 +143,7 @@ export class User {
throw new LoginError(this.uname); throw new LoginError(this.uname);
} }
if (this.hashType !== 'hydro') { if (this.hashType !== 'hydro') {
// eslint-disable-next-line @typescript-eslint/no-use-before-define // eslint-disable-next-line ts/no-use-before-define
UserModel.setPassword(this._id, password); UserModel.setPassword(this._id, password);
} }
} }
+1 -1
View File
@@ -43,7 +43,7 @@ export async function iterateAllPsdoc(filter: Filter<ProblemStatusDoc>, cb: (psd
} }
interface PartialProblemDoc extends ProblemDoc { interface PartialProblemDoc extends ProblemDoc {
[key: string]: any, [key: string]: any;
} }
export async function iterateAllProblemInDomain( export async function iterateAllProblemInDomain(
+7 -6
View File
@@ -1,12 +1,13 @@
/* eslint-disable perfectionist/sort-exports */
import pwsh from './lib/hash.hydro'; import pwsh from './lib/hash.hydro';
import db from './service/db'; import db from './service/db';
export { nanoid } from 'nanoid'; export { nanoid } from 'nanoid';
export { default as moment, isMoment } from 'moment-timezone'; export { isMoment, default as moment } from 'moment-timezone';
export { export {
WebService, Router, HandlerCommon, httpServer, Apis, APIS, HandlerCommon, httpServer,
Query, Mutation, Subscription, Apis, APIS, Mutation, Query, Router, Subscription, WebService,
} from '@hydrooj/framework'; } from '@hydrooj/framework';
export * from './pipelineUtils'; export * from './pipelineUtils';
@@ -37,10 +38,10 @@ export { default as TaskModel } from './model/task';
export * from './model/builtin'; export * from './model/builtin';
/** @deprecated */ /** @deprecated */
export * as JudgeHandler from './handler/judge'; export * as JudgeHandler from './handler/judge';
export { postJudge, JudgeResultCallbackContext } from './handler/judge'; export { JudgeResultCallbackContext, postJudge } from './handler/judge';
export { Collections } from './service/db'; export { Collections } from './service/db';
export { Handler, ConnectionHandler, requireSudo } from './service/server'; export { ConnectionHandler, Handler, requireSudo } from './service/server';
export { Service, Context } from './context'; export { Context, Service } from './context';
export { buildContent } from './lib/content'; export { buildContent } from './lib/content';
export { default as mime } from './lib/mime'; export { default as mime } from './lib/mime';
export { default as difficultyAlgorithm } from './lib/difficulty'; export { default as difficultyAlgorithm } from './lib/difficulty';
+1 -1
View File
@@ -1,4 +1,4 @@
/* eslint-disable @typescript-eslint/no-use-before-define */ /* eslint-disable ts/no-use-before-define */
import { ObjectId } from 'mongodb'; import { ObjectId } from 'mongodb';
import Schema from 'schemastery'; import Schema from 'schemastery';
import blacklist from '../model/blacklist'; import blacklist from '../model/blacklist';
+1 -1
View File
@@ -14,7 +14,7 @@ async function getRemoteVersion(id: string) {
} }
} }
export async function run(_: {}, report: Function) { export async function run(_: void, report: (data: any) => void) {
const current = global.Hydro.version; const current = global.Hydro.version;
const message = ['']; const message = [''];
for (const name in current) { for (const name in current) {
+5 -4
View File
@@ -17,9 +17,10 @@ import db from '../service/db';
export const description = 'Calculate rp of a domain, or all domains'; export const description = 'Calculate rp of a domain, or all domains';
type ND = NumericDictionary<number>; type ND = NumericDictionary<number>;
type Report = (data: any) => void;
interface RpDef { interface RpDef {
run(domainIds: string[], udict: ND, report: Function): Promise<void>; run(domainIds: string[], udict: ND, report: Report): Promise<void>;
hidden: boolean; hidden: boolean;
base: number; base: number;
} }
@@ -101,7 +102,7 @@ export const RpTypes: Record<string, RpDef> = {
}; };
global.Hydro.model.rp = RpTypes; global.Hydro.model.rp = RpTypes;
export async function calcLevel(domainId: string, report: Function) { export async function calcLevel(domainId: string, report: Report) {
await domain.setMultiUserInDomain(domainId, {}, { level: 0, rank: null }); await domain.setMultiUserInDomain(domainId, {}, { level: 0, rank: null });
let last = { rp: null }; let last = { rp: null };
let rank = 0; let rank = 0;
@@ -135,7 +136,7 @@ export async function calcLevel(domainId: string, report: Function) {
await bulk.execute(); await bulk.execute();
} }
async function runInDomain(domainId: string, report: Function) { async function runInDomain(domainId: string, report: Report) {
const results: Record<keyof typeof RpTypes, ND> = {}; const results: Record<keyof typeof RpTypes, ND> = {};
const udict = Counter(); const udict = Counter();
await db.collection('domain.user').updateMany({ domainId }, { $set: { rpInfo: {} } }); await db.collection('domain.user').updateMany({ domainId }, { $set: { rpInfo: {} } });
@@ -160,7 +161,7 @@ async function runInDomain(domainId: string, report: Function) {
await calcLevel(domainId, report); await calcLevel(domainId, report);
} }
export async function run({ domainId }, report: Function) { export async function run({ domainId }, report: Report) {
if (!domainId) { if (!domainId) {
const domains = await domain.getMulti().toArray(); const domains = await domain.getMulti().toArray();
await report({ message: `Found ${domains.length} domains` }); await report({ message: `Found ${domains.length} domains` });
+61 -61
View File
@@ -15,95 +15,95 @@ export type Disposable = () => void;
export type VoidReturn = Promise<any> | any; export type VoidReturn = Promise<any> | any;
export interface EventMap { export interface EventMap {
'app/listen': () => void 'app/listen': () => void;
'app/started': () => void 'app/started': () => void;
'app/ready': () => VoidReturn 'app/ready': () => VoidReturn;
'app/exit': () => VoidReturn 'app/exit': () => VoidReturn;
'app/before-reload': (entries: Set<string>) => VoidReturn 'app/before-reload': (entries: Set<string>) => VoidReturn;
'app/reload': (entries: Set<string>) => VoidReturn 'app/reload': (entries: Set<string>) => VoidReturn;
'subscription/init': (h: ConnectionHandler<Context>, privileged: boolean) => VoidReturn 'subscription/init': (h: ConnectionHandler<Context>, privileged: boolean) => VoidReturn;
'subscription/subscribe': (channel: string, user: User, metadata: Record<string, string>) => VoidReturn 'subscription/subscribe': (channel: string, user: User, metadata: Record<string, string>) => VoidReturn;
'subscription/enable': ( 'subscription/enable': (
channel: string, h: ConnectionHandler<Context>, privileged: boolean, onDispose: (disposable: () => void) => void channel: string, h: ConnectionHandler<Context>, privileged: boolean, onDispose: (disposable: () => void) => void
) => VoidReturn ) => VoidReturn;
'app/watch/change': (path: string) => VoidReturn 'app/watch/change': (path: string) => VoidReturn;
'app/watch/unlink': (path: string) => VoidReturn 'app/watch/unlink': (path: string) => VoidReturn;
'database/connect': (db: Db) => void 'database/connect': (db: Db) => void;
'database/config': () => VoidReturn 'database/config': () => VoidReturn;
'system/setting': (args: Record<string, any>) => VoidReturn 'system/setting': (args: Record<string, any>) => VoidReturn;
'bus/broadcast': (event: keyof EventMap, payload: any, trace?: string) => VoidReturn 'bus/broadcast': (event: keyof EventMap, payload: any, trace?: string) => VoidReturn;
'monitor/update': (type: 'server' | 'judge', $set: any) => VoidReturn 'monitor/update': (type: 'server' | 'judge', $set: any) => VoidReturn;
'monitor/collect': (info: any) => VoidReturn 'monitor/collect': (info: any) => VoidReturn;
'api/update': () => void; 'api/update': () => void;
'task/daily': () => void; 'task/daily': () => void;
'task/daily/finish': (pref: Record<string, number>) => void; 'task/daily/finish': (pref: Record<string, number>) => void;
'user/message': (uid: number[], mdoc: Omit<MessageDoc, 'to'>) => void 'user/message': (uid: number[], mdoc: Omit<MessageDoc, 'to'>) => void;
'user/get': (udoc: User) => void 'user/get': (udoc: User) => void;
'user/delcache': (content: string | true) => void 'user/delcache': (content: string | true) => void;
'user/import/parse': (payload: any) => VoidReturn 'user/import/parse': (payload: any) => VoidReturn;
'user/import/create': (uid: number, udoc: any) => VoidReturn 'user/import/create': (uid: number, udoc: any) => VoidReturn;
'domain/create': (ddoc: DomainDoc) => VoidReturn 'domain/create': (ddoc: DomainDoc) => VoidReturn;
'domain/before-get': (query: Filter<DomainDoc>) => VoidReturn 'domain/before-get': (query: Filter<DomainDoc>) => VoidReturn;
'domain/get': (ddoc: DomainDoc) => VoidReturn 'domain/get': (ddoc: DomainDoc) => VoidReturn;
'domain/before-update': (domainId: string, $set: Partial<DomainDoc>) => VoidReturn 'domain/before-update': (domainId: string, $set: Partial<DomainDoc>) => VoidReturn;
'domain/update': (domainId: string, $set: Partial<DomainDoc>, ddoc: DomainDoc) => VoidReturn 'domain/update': (domainId: string, $set: Partial<DomainDoc>, ddoc: DomainDoc) => VoidReturn;
'domain/delete': (domainId: string) => VoidReturn 'domain/delete': (domainId: string) => VoidReturn;
'domain/delete-cache': (domainId: string) => VoidReturn 'domain/delete-cache': (domainId: string) => VoidReturn;
'document/add': (doc: any) => VoidReturn 'document/add': (doc: any) => VoidReturn;
'document/set': <T extends keyof DocType>( 'document/set': <T extends keyof DocType>(
domainId: string, docType: T, docId: DocType[T], domainId: string, docType: T, docId: DocType[T],
$set: any, $unset: OnlyFieldsOfType<DocType[T], any, true | '' | 1> $set: any, $unset: OnlyFieldsOfType<DocType[T], any, true | '' | 1>
) => VoidReturn ) => VoidReturn;
'discussion/before-add': (payload: Partial<DiscussionDoc>) => VoidReturn 'discussion/before-add': (payload: Partial<DiscussionDoc>) => VoidReturn;
'discussion/add': (payload: Partial<DiscussionDoc>) => VoidReturn 'discussion/add': (payload: Partial<DiscussionDoc>) => VoidReturn;
'problem/before-add': (domainId: string, content: string, owner: number, docId: number, doc: Partial<ProblemDoc>) => VoidReturn 'problem/before-add': (domainId: string, content: string, owner: number, docId: number, doc: Partial<ProblemDoc>) => VoidReturn;
'problem/add': (doc: Partial<ProblemDoc>, docId: number) => VoidReturn 'problem/add': (doc: Partial<ProblemDoc>, docId: number) => VoidReturn;
'problem/before-edit': (doc: Partial<ProblemDoc>, $unset: OnlyFieldsOfType<ProblemDoc, any, true | '' | 1>) => VoidReturn 'problem/before-edit': (doc: Partial<ProblemDoc>, $unset: OnlyFieldsOfType<ProblemDoc, any, true | '' | 1>) => VoidReturn;
'problem/edit': (doc: ProblemDoc) => VoidReturn 'problem/edit': (doc: ProblemDoc) => VoidReturn;
'problem/before-del': (domainId: string, docId: number) => VoidReturn 'problem/before-del': (domainId: string, docId: number) => VoidReturn;
'problem/del': (domainId: string, docId: number) => VoidReturn 'problem/del': (domainId: string, docId: number) => VoidReturn;
'problem/list': (query: Filter<ProblemDoc>, handler: any, sort?: string[]) => VoidReturn 'problem/list': (query: Filter<ProblemDoc>, handler: any, sort?: string[]) => VoidReturn;
'problem/get': (doc: ProblemDoc, handler: any) => VoidReturn 'problem/get': (doc: ProblemDoc, handler: any) => VoidReturn;
'problem/delete': (domainId: string, docId: number) => VoidReturn 'problem/delete': (domainId: string, docId: number) => VoidReturn;
'problem/addTestdata': (domainId: string, docId: number, name: string, payload: Omit<FileInfo, '_id'>) => VoidReturn 'problem/addTestdata': (domainId: string, docId: number, name: string, payload: Omit<FileInfo, '_id'>) => VoidReturn;
'problem/renameTestdata': (domainId: string, docId: number, name: string, newName: string) => VoidReturn 'problem/renameTestdata': (domainId: string, docId: number, name: string, newName: string) => VoidReturn;
'problem/delTestdata': (domainId: string, docId: number, name: string[]) => VoidReturn 'problem/delTestdata': (domainId: string, docId: number, name: string[]) => VoidReturn;
'problem/addAdditionalFile': (domainId: string, docId: number, name: string, payload: Omit<FileInfo, '_id'>) => VoidReturn 'problem/addAdditionalFile': (domainId: string, docId: number, name: string, payload: Omit<FileInfo, '_id'>) => VoidReturn;
'problem/renameAdditionalFile': (domainId: string, docId: number, name: string, newName: string) => VoidReturn 'problem/renameAdditionalFile': (domainId: string, docId: number, name: string, newName: string) => VoidReturn;
'problem/delAdditionalFile': (domainId: string, docId: number, name: string[]) => VoidReturn 'problem/delAdditionalFile': (domainId: string, docId: number, name: string[]) => VoidReturn;
'contest/before-add': (payload: Partial<Tdoc>) => VoidReturn 'contest/before-add': (payload: Partial<Tdoc>) => VoidReturn;
'contest/add': (payload: Partial<Tdoc>, id: ObjectId) => VoidReturn 'contest/add': (payload: Partial<Tdoc>, id: ObjectId) => VoidReturn;
'contest/edit': (payload: Tdoc) => VoidReturn 'contest/edit': (payload: Tdoc) => VoidReturn;
'contest/list': (query: Filter<Tdoc>, handler: any) => VoidReturn 'contest/list': (query: Filter<Tdoc>, handler: any) => VoidReturn;
'contest/scoreboard': (tdoc: Tdoc, rows: ScoreboardRow[], udict: BaseUserDict, pdict: ProblemDict) => VoidReturn 'contest/scoreboard': (tdoc: Tdoc, rows: ScoreboardRow[], udict: BaseUserDict, pdict: ProblemDict) => VoidReturn;
'contest/balloon': (domainId: string, tid: ObjectId, bdoc: ContestBalloonDoc) => VoidReturn 'contest/balloon': (domainId: string, tid: ObjectId, bdoc: ContestBalloonDoc) => VoidReturn;
'contest/del': (domainId: string, tid: ObjectId) => VoidReturn 'contest/del': (domainId: string, tid: ObjectId) => VoidReturn;
'oplog/log': (type: string, handler: Handler<Context> | ConnectionHandler<Context>, args: any, data: any) => VoidReturn; 'oplog/log': (type: string, handler: Handler<Context> | ConnectionHandler<Context>, args: any, data: any) => VoidReturn;
'training/list': (query: Filter<TrainingDoc>, handler: any) => VoidReturn 'training/list': (query: Filter<TrainingDoc>, handler: any) => VoidReturn;
'training/get': (tdoc: TrainingDoc, handler: any) => VoidReturn 'training/get': (tdoc: TrainingDoc, handler: any) => VoidReturn;
'record/change': (rdoc: RecordDoc, $set?: any, $push?: any, body?: any) => void 'record/change': (rdoc: RecordDoc, $set?: any, $push?: any, body?: any) => void;
'record/judge': (rdoc: RecordDoc, updated: boolean, pdoc?: ProblemDoc, updater?: any) => VoidReturn 'record/judge': (rdoc: RecordDoc, updated: boolean, pdoc?: ProblemDoc, updater?: any) => VoidReturn;
} }
export function apply(ctx: Context) { export function apply(ctx: Context) {
try { try {
if (!process.send || !pm2 || process.env.exec_mode !== 'cluster_mode') throw new Error(); if (!process.send || !pm2 || process.env.exec_mode !== 'cluster_mode') throw new Error('not in cluster mode');
pm2.launchBus((err, bus) => { pm2.launchBus((err, bus) => {
if (err) throw new Error(); if (err) throw new Error('cannot launch pm2 bus');
bus.on('hydro:broadcast', (packet) => { bus.on('hydro:broadcast', (packet) => {
(app.parallel as any)(packet.data.event, ...BSON.EJSON.parse(packet.data.payload)); (app.parallel as any)(packet.data.event, ...BSON.EJSON.parse(packet.data.payload));
}); });
+10 -10
View File
@@ -14,16 +14,16 @@ const logger = new Logger('mongo');
export interface Collections { } export interface Collections { }
interface MongoConfig { interface MongoConfig {
protocol?: string, protocol?: string;
username?: string, username?: string;
password?: string, password?: string;
host?: string, host?: string;
port?: string, port?: string;
name?: string, name?: string;
url?: string, url?: string;
uri?: string, uri?: string;
prefix?: string, prefix?: string;
collectionMap?: Record<string, string>, collectionMap?: Record<string, string>;
} }
declare module '../context' { declare module '../context' {
+1 -2
View File
@@ -1,7 +1,6 @@
// This file was adapted from @koishijs, MIT licensed. // This file was adapted from @koishijs, MIT licensed.
/* eslint-disable consistent-return */ /* eslint-disable consistent-return */
/* eslint-disable import/no-dynamic-require */ /* eslint-disable ts/no-shadow */
/* eslint-disable @typescript-eslint/no-shadow */
import { readFileSync } from 'fs'; import { readFileSync } from 'fs';
import { relative } from 'path'; import { relative } from 'path';
import { codeFrameColumns } from '@babel/code-frame'; import { codeFrameColumns } from '@babel/code-frame';
+3 -3
View File
@@ -28,7 +28,7 @@ const ignoredLimit = `,${argv.options.ignoredLimit},`;
const logger = new Logger('server'); const logger = new Logger('server');
declare module '@hydrooj/framework' { declare module '@hydrooj/framework' {
export interface HandlerCommon<C> { // eslint-disable-line @typescript-eslint/no-unused-vars export interface HandlerCommon<C> { // eslint-disable-line ts/no-unused-vars
domain: DomainDoc; domain: DomainDoc;
paginate<T>(cursor: FindCursor<T>, page: number, key: string): Promise<[docs: T[], numPages: number, count: number]>; paginate<T>(cursor: FindCursor<T>, page: number, key: string): Promise<[docs: T[], numPages: number, count: number]>;
@@ -39,9 +39,9 @@ declare module '@hydrooj/framework' {
} }
} }
export { Mutation, Query } from '@hydrooj/framework/api';
export * from '@hydrooj/framework/decorators'; export * from '@hydrooj/framework/decorators';
export * from '@hydrooj/framework/validator'; export * from '@hydrooj/framework/validator';
export { Query, Mutation } from '@hydrooj/framework/api';
/* /*
* For security concern, some API requires sudo privilege to access. * For security concern, some API requires sudo privilege to access.
@@ -153,7 +153,7 @@ export async function apply(ctx: Context) {
? (!host.includes(this.request.host)) ? (!host.includes(this.request.host))
: this.request.host !== host) : this.request.host !== host)
)) withDomainId ||= domainId; )) withDomainId ||= domainId;
res = server.router.url.call(server.router, name, args, { query }).toString(); res = server.router.url(name, args, { query }).toString();
if (anchor) res = `${res}#${anchor}`; if (anchor) res = `${res}#${anchor}`;
if (withDomainId) res = `/d/${withDomainId}${res}`; if (withDomainId) res = `/d/${withDomainId}${res}`;
} catch (e) { } catch (e) {
+4 -4
View File
@@ -1,7 +1,7 @@
/* eslint-disable consistent-return */ /* eslint-disable consistent-return */
/* eslint-disable @typescript-eslint/no-unused-vars */ /* eslint-disable ts/no-unused-vars */
/* eslint-disable no-await-in-loop */ /* eslint-disable no-await-in-loop */
/* eslint-disable @typescript-eslint/naming-convention */ /* eslint-disable ts/naming-convention */
import yaml from 'js-yaml'; import yaml from 'js-yaml';
import { ObjectId } from 'mongodb'; import { ObjectId } from 'mongodb';
import { randomstring, sleep } from '@hydrooj/utils'; import { randomstring, sleep } from '@hydrooj/utils';
@@ -51,7 +51,7 @@ export const coreScripts: MigrationScript[] = [
return true; return true;
}, },
// Init // Init
...new Array(47).fill(unsupportedUpgrade), // oxlint-disable-line no-new-array ...Array.from({ length: 47 }).fill(unsupportedUpgrade) as any,
async function _48_49() { async function _48_49() {
await RecordModel.coll.updateMany({ input: { $exists: true } }, { $set: { contest: new ObjectId('000000000000000000000000') } }); await RecordModel.coll.updateMany({ input: { $exists: true } }, { $set: { contest: new ObjectId('000000000000000000000000') } });
return true; return true;
@@ -221,7 +221,7 @@ export const coreScripts: MigrationScript[] = [
config.answers[cnt] = l; config.answers[cnt] = l;
} }
function processSingleLanguage(content: string) { // eslint-disable-line no-inner-declarations function processSingleLanguage(content: string) {
let text = ''; let text = '';
try { try {
let scnt = 0; let scnt = 0;
+1 -1
View File
@@ -3,7 +3,7 @@ import {
} from 'hydrooj'; } from 'hydrooj';
function unescapedString(escapedString: string) { function unescapedString(escapedString: string) {
escapedString += new Array(5 - (escapedString.length % 4)).join('='); escapedString += Array.from({ length: 5 - (escapedString.length % 4) }).join('=');
return escapedString.replace(/-/g, '+').replace(/_/g, '/'); return escapedString.replace(/-/g, '+').replace(/_/g, '/');
} }
+4 -4
View File
@@ -1,4 +1,4 @@
/* eslint-disable no-tabs */ /* eslint-disable style/no-tabs */
/* eslint-disable no-await-in-loop */ /* eslint-disable no-await-in-loop */
import path from 'path'; import path from 'path';
import mariadb from 'mariadb'; import mariadb from 'mariadb';
@@ -70,7 +70,7 @@ export async function run({
host = 'localhost', port = 3306, name = 'jol', host = 'localhost', port = 3306, name = 'jol',
username, password, domainId, contestType = 'oi', username, password, domainId, contestType = 'oi',
dataDir, uploadDir = '/home/judge/src/web/upload/', rerun = true, randomMail = false, dataDir, uploadDir = '/home/judge/src/web/upload/', rerun = true, randomMail = false,
}, report: Function) { }, report: (data: any) => void) {
let remoteUsed = false; let remoteUsed = false;
const src = await mariadb.createConnection({ const src = await mariadb.createConnection({
host, host,
@@ -191,7 +191,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=').replace(/\[\/?md]/g, ''); }, '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 {
@@ -295,7 +295,7 @@ hydrooj install https://hydro.ac/hydroac-client.zip
await Promise.all(Object.keys(files).map((filename) => addContestFile(domainId, tid, filename, files[filename]))); await Promise.all(Object.keys(files).map((filename) => addContestFile(domainId, tid, filename, files[filename])));
if (Object.keys(files).length) report({ message: `move ${Object.keys(files).length} file for contest ${tidMap[tdoc.contest_id]}` }); if (Object.keys(files).length) report({ message: `move ${Object.keys(files).length} file for contest ${tidMap[tdoc.contest_id]}` });
const allowedUser:{ user_id:string }[] = await query(`SELECT * FROM privilege WHERE rightstr = 'c${tdoc.contest_id}';`); const allowedUser: { user_id: string }[] = await query(`SELECT * FROM privilege WHERE rightstr = 'c${tdoc.contest_id}';`);
const assignUserList = allowedUser.map((i) => uidMap[i.user_id]).filter((i) => i); const assignUserList = allowedUser.map((i) => uidMap[i.user_id]).filter((i) => i);
if (isAssignMode) { if (isAssignMode) {
await ContestModel.edit(domainId, tid, { await ContestModel.edit(domainId, tid, {
+4 -2
View File
@@ -1,5 +1,6 @@
/* eslint-disable no-tabs */
/* eslint-disable no-await-in-loop */ /* eslint-disable no-await-in-loop */
/* eslint-disable style/no-tabs */
import path from 'path'; import path from 'path';
import zlib from 'zlib'; import zlib from 'zlib';
import mariadb from 'mariadb'; import mariadb from 'mariadb';
@@ -34,7 +35,7 @@ export async function run({
host = 'localhost', port = 3306, name, host = 'localhost', port = 3306, name,
username, password, domainId, contestType = 'oi', username, password, domainId, contestType = 'oi',
dataDir, imageDir, rerun = true, randomMail = false, dataDir, imageDir, rerun = true, randomMail = false,
}, report: Function) { }, report: (data: any) => void) {
const src = await mariadb.createConnection({ const src = await mariadb.createConnection({
host, host,
port, port,
@@ -150,6 +151,7 @@ export async function run({
hint: pdoc.hint, hint: pdoc.hint,
source: pdoc.source, source: pdoc.source,
}, 'html'); }, 'html');
// eslint-disable-next-line regexp/no-super-linear-backtracking
const uploadFiles = content.matchAll(/(?:src|href)="\/images\/([^"]+\/([^"]+))"/g); const uploadFiles = content.matchAll(/(?:src|href)="\/images\/([^"]+\/([^"]+))"/g);
for (const file of uploadFiles) { for (const file of uploadFiles) {
try { try {
+2 -2
View File
@@ -75,7 +75,7 @@ export async function run({
host = 'localhost', port = 3306, name = 'syzoj', host = 'localhost', port = 3306, name = 'syzoj',
username, password, domainId, dataDir, username, password, domainId, dataDir,
rerun = true, randomMail = 'never', rerun = true, randomMail = 'never',
}, report: Function) { }, report: (data: any) => void) {
const src = await mariadb.createConnection({ const src = await mariadb.createConnection({
host, host,
port, port,
@@ -199,7 +199,7 @@ export async function run({
`publicize_time` datetime NULL DEFAULT NULL, 公开时间 `publicize_time` datetime NULL DEFAULT NULL, 公开时间
`type` enum('traditional','submit-answer','interaction') CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT 'traditional', `type` enum('traditional','submit-answer','interaction') CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT 'traditional',
*/ */
const fileReg = /\[.*\]\((\/problem\/(\d+)\/testdata\/download\/(.*))\)/gm; const fileReg = /\[.*\]\((\/problem\/(\d+)\/testdata\/download\/(.*))\)/g;
const pidMap: Record<string, number> = {}; const pidMap: Record<string, number> = {};
const configMap: Record<string, string> = {}; const configMap: Record<string, string> = {};
const problemAdditionalFile = {}; const problemAdditionalFile = {};
+5 -5
View File
@@ -45,7 +45,7 @@ export async function run({
host = '172.17.0.2', port = 3306, name = 'app_uoj233', host = '172.17.0.2', port = 3306, name = 'app_uoj233',
username, password, domainId, dataDir, username, password, domainId, dataDir,
rerun = true, randomMail = false, rerun = true, randomMail = false,
}, report: Function) { }, report: (data: any) => void) {
const src = await mariadb.createConnection({ const src = await mariadb.createConnection({
host, host,
port, port,
@@ -506,10 +506,10 @@ export async function run({
if (confInfo.n_subtasks if (confInfo.n_subtasks
&& Object.keys(confInfo.subtask_end).length === +confInfo.n_subtasks && Object.keys(confInfo.subtask_end).length === +confInfo.n_subtasks
&& Object.keys(confInfo.subtask_score).length === +confInfo.n_subtasks) { && Object.keys(confInfo.subtask_score).length === +confInfo.n_subtasks) {
config.subtasks.push(...[...new Array(+confInfo.n_subtasks)].map((v, i) => i + 1).map((i) => ({ config.subtasks.push(...[...Array.from({ length: +confInfo.n_subtasks })].map((v, i) => i + 1).map((i) => ({
id: +i, id: +i,
score: confInfo.subtask_score[i], score: confInfo.subtask_score[i],
cases: [...new Array(confInfo.subtask_end[i] - (confInfo.subtask_end[i - 1] || 0))].map((v, j) => ({ cases: [...Array.from({ length: confInfo.subtask_end[i] - (confInfo.subtask_end[i - 1] || 0) })].map((v, j) => ({
input: `${confInfo.input_pre}${j + (confInfo.subtask_end[i - 1] || 0) + 1}.${confInfo.input_suf}`, input: `${confInfo.input_pre}${j + (confInfo.subtask_end[i - 1] || 0) + 1}.${confInfo.input_suf}`,
output: `${confInfo.output_pre}${j + (confInfo.subtask_end[i - 1] || 0) + 1}.${confInfo.output_suf}`, output: `${confInfo.output_pre}${j + (confInfo.subtask_end[i - 1] || 0) + 1}.${confInfo.output_suf}`,
})), })),
@@ -521,7 +521,7 @@ export async function run({
id: 1, id: 1,
score: 97, score: 97,
type: 'sum' as SubtaskType, type: 'sum' as SubtaskType,
cases: [...new Array(+confInfo.n_tests)].map((v, i) => i + 1).map((i) => ({ cases: [...Array.from({ length: +confInfo.n_tests })].map((v, i) => i + 1).map((i) => ({
input: `${confInfo.input_pre}${i}.${confInfo.input_suf}`, input: `${confInfo.input_pre}${i}.${confInfo.input_suf}`,
output: `${confInfo.output_pre}${i}.${confInfo.output_suf}`, output: `${confInfo.output_pre}${i}.${confInfo.output_suf}`,
})), })),
@@ -530,7 +530,7 @@ export async function run({
config.subtasks.push({ config.subtasks.push({
id: Math.max(...config.subtasks.map((i) => i.id)) + 1, id: Math.max(...config.subtasks.map((i) => i.id)) + 1,
score: 3, score: 3,
cases: [...new Array(+confInfo.n_ex_tests)].map((v, i) => i + 1).map((i) => ({ cases: [...Array.from({ length: +confInfo.n_ex_tests })].map((v, i) => i + 1).map((i) => ({
input: `ex_${confInfo.input_pre}${i}.${confInfo.input_suf}`, input: `ex_${confInfo.input_pre}${i}.${confInfo.input_suf}`,
output: `ex_${confInfo.output_pre}${i}.${confInfo.output_suf}`, output: `ex_${confInfo.output_pre}${i}.${confInfo.output_suf}`,
})), })),
+11 -9
View File
@@ -1,4 +1,4 @@
/* eslint-disable @typescript-eslint/naming-convention */ /* eslint-disable ts/naming-convention */
/* eslint-disable no-await-in-loop */ /* eslint-disable no-await-in-loop */
import mongodb, { Db, FindCursor } from 'mongodb'; import mongodb, { Db, FindCursor } from 'mongodb';
import { import {
@@ -13,6 +13,8 @@ const pid = (id) => {
return id; return id;
}; };
type Report = (args: { progress?: number, message?: string }) => void;
const tasks = { const tasks = {
user: async (doc) => ({ user: async (doc) => ({
_id: doc._id, _id: doc._id,
@@ -93,7 +95,7 @@ const tasks = {
processer: (rule) => { processer: (rule) => {
const n = {}; const n = {};
for (const key in rule) { for (const key in rule) {
n[parseInt(key, 10) / 3600] = rule; n[Number.parseInt(key, 10) / 3600] = rule;
} }
return n; return n;
}, },
@@ -282,7 +284,7 @@ const cursor: NodeJS.Dict<CursorGetter> = {
file: (s) => s.collection('fs.files').find(), file: (s) => s.collection('fs.files').find(),
}; };
async function discussionNode(src: Db, report: Function) { async function discussionNode(src: Db, report: Report) {
const count = await src.collection('document').countDocuments({ doc_type: 20 }); const count = await src.collection('document').countDocuments({ doc_type: 20 });
await report({ progress: 1, message: `discussion.node: ${count}` }); await report({ progress: 1, message: `discussion.node: ${count}` });
const total = Math.floor(count / 5); const total = Math.floor(count / 5);
@@ -327,7 +329,7 @@ async function fix(doc) {
); );
} }
async function fixProblem(report: Function) { async function fixProblem(report: Report) {
const count = await dst.collection('document').countDocuments({ docType: 10 }); const count = await dst.collection('document').countDocuments({ docType: 10 });
await report({ progress: 1, message: `Fix pid: ${count}` }); await report({ progress: 1, message: `Fix pid: ${count}` });
const total = Math.floor(count / 50); const total = Math.floor(count / 50);
@@ -349,7 +351,7 @@ function objid(ts: Date) {
} }
// FIXME this seems not working // FIXME this seems not working
async function message(src: Db, report: Function) { async function message(src: Db, report: Report) {
const count = await src.collection('message').countDocuments(); const count = await src.collection('message').countDocuments();
await report({ progress: 1, message: `Messages: ${count}` }); await report({ progress: 1, message: `Messages: ${count}` });
const total = Math.floor(count / 50); const total = Math.floor(count / 50);
@@ -373,7 +375,7 @@ async function message(src: Db, report: Function) {
} }
} }
async function removeInvalidPid(report: Function) { async function removeInvalidPid(report: Report) {
const count = await dst.collection('document').countDocuments({ docType: 10 }); const count = await dst.collection('document').countDocuments({ docType: 10 });
const bulk = dst.collection('document').initializeUnorderedBulkOp(); const bulk = dst.collection('document').initializeUnorderedBulkOp();
await report({ progress: 1, message: `Remove pid: ${count}` }); await report({ progress: 1, message: `Remove pid: ${count}` });
@@ -383,7 +385,7 @@ async function removeInvalidPid(report: Function) {
.find({ docType: 10 }).skip(i * 50).limit(50) .find({ docType: 10 }).skip(i * 50).limit(50)
.toArray(); .toArray();
for (const doc of docs) { for (const doc of docs) {
const id = parseInt(doc.pid, 10); const id = Number.parseInt(doc.pid, 10);
if (Number.isSafeInteger(id)) { if (Number.isSafeInteger(id)) {
bulk.find({ _id: doc._id }).updateOne({ $unset: { pid: '' } }); bulk.find({ _id: doc._id }).updateOne({ $unset: { pid: '' } });
} }
@@ -392,7 +394,7 @@ async function removeInvalidPid(report: Function) {
} }
} }
async function task(name: any, src: Db, report: Function) { async function task(name: any, src: Db, report: Report) {
await report({ progress: 1, message: `${name}` }); await report({ progress: 1, message: `${name}` });
let lastProgress = -1; let lastProgress = -1;
for (let i = 0; ; i++) { for (let i = 0; ; i++) {
@@ -466,7 +468,7 @@ async function task(name: any, src: Db, report: Function) {
export async function run({ export async function run({
host = 'localhost', port = 27017, name = 'vijos4', username, password, host = 'localhost', port = 27017, name = 'vijos4', username, password,
}, report: Function) { }, report: Report) {
let mongourl = 'mongodb://'; let mongourl = 'mongodb://';
if (username) mongourl += `${username}:${password}@`; if (username) mongourl += `${username}:${password}@`;
mongourl += `${host}:${port}/${name}`; mongourl += `${host}:${port}/${name}`;
+7 -7
View File
@@ -139,25 +139,25 @@ export function apply(ctx: Context) {
solved: +i === STATUS.STATUS_ACCEPTED, solved: +i === STATUS.STATUS_ACCEPTED,
})), })),
getFeed('languages', { getFeed('languages', {
id: 'c', name: 'C', 'entry_point_required': false, extensions: ['c'], id: 'c', name: 'C', entry_point_required: false, extensions: ['c'],
}), }),
getFeed('languages', { getFeed('languages', {
id: 'cpp', name: 'C++', 'entry_point_required': false, extensions: ['cpp', 'cc', 'cxx', 'c++'], id: 'cpp', name: 'C++', entry_point_required: false, extensions: ['cpp', 'cc', 'cxx', 'c++'],
}), }),
getFeed('languages', { getFeed('languages', {
id: 'java', name: 'Java', 'entry_point_required': true, extensions: ['java'], id: 'java', name: 'Java', entry_point_required: true, extensions: ['java'],
}), }),
getFeed('languages', { getFeed('languages', {
id: 'python3', name: 'Python 3', 'entry_point_required': false, 'entry_point_name': 'Main file', extensions: ['py', 'py3'], id: 'python3', name: 'Python 3', entry_point_required: false, entry_point_name: 'Main file', extensions: ['py', 'py3'],
}), }),
getFeed('languages', { getFeed('languages', {
id: 'kotlin', name: 'Kotlin', 'entry_point_required': true, extensions: ['kt'], id: 'kotlin', name: 'Kotlin', entry_point_required: true, extensions: ['kt'],
}), }),
getFeed('languages', { getFeed('languages', {
id: 'rust', name: 'Rust', 'entry_point_required': true, extensions: ['rs'], id: 'rust', name: 'Rust', entry_point_required: true, extensions: ['rs'],
}), }),
getFeed('languages', { getFeed('languages', {
id: 'go', name: 'Go', 'entry_point_required': true, extensions: ['go'], id: 'go', name: 'Go', entry_point_required: true, extensions: ['go'],
}), }),
getFeed('groups', { id: 'participants', name: '正式队伍' }), getFeed('groups', { id: 'participants', name: '正式队伍' }),
getFeed('groups', { id: 'observers', name: '打星队伍' }), getFeed('groups', { id: 'observers', name: '打星队伍' }),
+5 -5
View File
@@ -4,8 +4,8 @@ import {
} 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');
const indexJs = file.match(/index-([A-Za-z0-9_-]+)\.js"/)?.[1]; const indexJs = file.match(/index-([\w-]+)\.js"/)?.[1];
const indexCss = file.match(/index-([A-Za-z0-9_-]+)\.css"/)?.[1]; const indexCss = file.match(/index-([\w-]+)\.css"/)?.[1];
const status = { const status = {
[STATUS.STATUS_WRONG_ANSWER]: 'WRONG_ANSWER', [STATUS.STATUS_WRONG_ANSWER]: 'WRONG_ANSWER',
[STATUS.STATUS_ACCEPTED]: 'CORRECT', [STATUS.STATUS_ACCEPTED]: 'CORRECT',
@@ -72,9 +72,9 @@ export async function apply(ctx: Context) {
}, },
organization: 'School', organization: 'School',
status_time_display: { status_time_display: {
'correct': true, correct: true,
'incorrect': true, incorrect: true,
'pending': true, pending: true,
}, },
medal: { medal: {
official: { official: {
+10 -10
View File
@@ -1,23 +1,23 @@
/* eslint-disable import/first */ /* eslint-disable import/first */
export * from './utils';
export { default as Notification } from './components/notification';
export * from './components/dialog'; export * from './components/dialog';
export { Context, Service, ctx } from './context';
export { AnsiUp } from 'ansi_up';
export { default as loadMonaco } from './components/monaco/loader'; export { default as loadMonaco } from './components/monaco/loader';
export { default as Notification } from './components/notification';
export { default as Socket } from './components/socket/index'; export { default as Socket } from './components/socket/index';
export { default as uploadFiles } from './components/upload'; export { default as uploadFiles } from './components/upload';
export * as redux from 'react-redux';
export * from './components/zipDownloader'; export * from './components/zipDownloader';
export * from '@hydrooj/utils/lib/common';
export { default as download } from './components/zipDownloader'; export { default as download } from './components/zipDownloader';
export { Context, ctx, Service } from './context';
export { initPageLoader } from './hydro';
export * from './lazyload';
export * from './misc/Page';
export * from './utils';
export * from '@hydrooj/utils/lib/common';
export { AnsiUp } from 'ansi_up';
export { default as $ } from 'jquery'; export { default as $ } from 'jquery';
export { default as _ } from 'lodash'; export { default as _ } from 'lodash';
export { default as React } from 'react'; export { default as React } from 'react';
export { default as ReactDOM } from 'react-dom/client'; export { default as ReactDOM } from 'react-dom/client';
export * from './misc/Page'; export * as redux from 'react-redux';
export { initPageLoader } from './hydro';
export * from './lazyload';
import { load } from './lazyload'; import { load } from './lazyload';
export default load; export default load;
@@ -30,7 +30,7 @@ import ProblemSelectAutoComplete from './components/autocomplete/ProblemSelectAu
import UserSelectAutoComplete from './components/autocomplete/UserSelectAutoComplete'; import UserSelectAutoComplete from './components/autocomplete/UserSelectAutoComplete';
export { export {
AutoComplete, UserSelectAutoComplete, ProblemSelectAutoComplete, DomainSelectAutoComplete, CustomSelectAutoComplete, AutoComplete, CustomSelectAutoComplete, DomainSelectAutoComplete, ProblemSelectAutoComplete, UserSelectAutoComplete,
}; };
export function addPage(page: import('./misc/Page').Page | (() => Promise<void> | void)) { export function addPage(page: import('./misc/Page').Page | (() => Promise<void> | void)) {
window.Hydro.extraPages.push(page); window.Hydro.extraPages.push(page);
+2 -2
View File
@@ -10,7 +10,7 @@ import esbuild from 'esbuild';
declare module 'hydrooj' { declare module 'hydrooj' {
interface UI { interface UI {
esbuildPlugins?: esbuild.Plugin[] esbuildPlugins?: esbuild.Plugin[];
} }
interface SystemKeys { interface SystemKeys {
'ui-default.nav_logo_dark': string; 'ui-default.nav_logo_dark': string;
@@ -91,7 +91,7 @@ export async function buildUI() {
const entryPoints: string[] = []; const entryPoints: string[] = [];
const lazyModules: string[] = []; const lazyModules: string[] = [];
const newFiles = ['entry.js']; const newFiles = ['entry.js'];
for (const addon of Object.values(global.addons)) { for (const addon of Object.values(global.addons) as string[]) {
let publicPath = resolve(addon, 'frontend'); let publicPath = resolve(addon, 'frontend');
if (!fs.existsSync(publicPath)) publicPath = resolve(addon, 'public'); if (!fs.existsSync(publicPath)) publicPath = resolve(addon, 'public');
if (!fs.existsSync(publicPath)) continue; if (!fs.existsSync(publicPath)) continue;
@@ -8,7 +8,7 @@ function parseNextNumber(str, pos, max) {
value: '', value: '',
}; };
let code = str.charCodeAt(pos); let code = str.charCodeAt(pos);
while (pos < max && (code >= 0x30 /* 0 */ && code <= 0x39 /* 9 */) || code === 0x25 /* % */) { while (pos < max && ((code >= 0x30 /* 0 */ && code <= 0x39 /* 9 */) || code === 0x25 /* % */)) {
code = str.charCodeAt(++pos); code = str.charCodeAt(++pos);
} }
result.ok = true; result.ok = true;
@@ -24,9 +24,9 @@ function parseImageSize(str, pos, max) {
width: '', width: '',
height: '', height: '',
}; };
if (pos >= max) { return result; } if (pos >= max) return result;
let code = str.charCodeAt(pos); let code = str.charCodeAt(pos);
if (code !== 0x3d /* = */) { return result; } if (code !== 0x3D /* = */) return result;
pos++; pos++;
code = str.charCodeAt(pos); code = str.charCodeAt(pos);
if (code !== 0x78 /* x */ && (code < 0x30 || code > 0x39) /* [0-9] */) { if (code !== 0x78 /* x */ && (code < 0x30 || code > 0x39) /* [0-9] */) {
@@ -67,14 +67,14 @@ export default function plugin(md) {
const oldPos = state.pos; const oldPos = state.pos;
const max = state.posMax; const max = state.posMax;
if (state.src.charCodeAt(state.pos) !== 0x21/* ! */) { return false; } if (state.src.charCodeAt(state.pos) !== 0x21/* ! */) return false;
if (state.src.charCodeAt(state.pos + 1) !== 0x5B/* [ */) { return false; } if (state.src.charCodeAt(state.pos + 1) !== 0x5B/* [ */) return false;
const labelStart = state.pos + 2; const labelStart = state.pos + 2;
const labelEnd = md.helpers.parseLinkLabel(state, state.pos + 1, false); const labelEnd = md.helpers.parseLinkLabel(state, state.pos + 1, false);
// parser failed to find ']', so it's not a valid link // parser failed to find ']', so it's not a valid link
if (labelEnd < 0) { return false; } if (labelEnd < 0) return false;
pos = labelEnd + 1; pos = labelEnd + 1;
if (pos < max && state.src.charCodeAt(pos) === 0x28/* ( */) { if (pos < max && state.src.charCodeAt(pos) === 0x28/* ( */) {
@@ -89,7 +89,7 @@ export default function plugin(md) {
code = state.src.charCodeAt(pos); code = state.src.charCodeAt(pos);
if (code !== 0x20 && code !== 0x0A) { break; } if (code !== 0x20 && code !== 0x0A) { break; }
} }
if (pos >= max) { return false; } if (pos >= max) return false;
// [link]( <href> "title" ) // [link]( <href> "title" )
// ^^^^^^ parsing link destination // ^^^^^^ parsing link destination
@@ -162,7 +162,7 @@ export default function plugin(md) {
// //
// Link reference // Link reference
// //
if (typeof state.env.references === 'undefined') { return false; } if (typeof state.env.references === 'undefined') return false;
// [foo] [bar] // [foo] [bar]
// ^^ optional whitespace (can include newlines) // ^^ optional whitespace (can include newlines)
@@ -112,7 +112,7 @@ export default function plugin(md) {
const render = (latex: string, displayMode = false) => { const render = (latex: string, displayMode = false) => {
options.displayMode = displayMode; options.displayMode = displayMode;
try { try {
latex = latex.replace(/\\def{\\([a-zA-Z0-9]+)}/g, '\\def\\$1'); latex = latex.replace(/\\def\{\\([a-zA-Z0-9]+)\}/g, '\\def\\$1');
return katex.renderToString(latex, options); return katex.renderToString(latex, options);
} catch (error) { } catch (error) {
if (options.throwOnError) console.error(error); if (options.throwOnError) console.error(error);
@@ -1,4 +1,7 @@
/* eslint-disable max-len */ /* eslint-disable max-len */
/* eslint-disable regexp/prefer-question-quantifier */
/* eslint-disable regexp/no-useless-non-capturing-group */
/* eslint-disable regexp/optimal-quantifier-concatenation */
import type MarkdownIt from 'markdown-it'; import type MarkdownIt from 'markdown-it';
import { v4 as uuid } from 'uuid'; import { v4 as uuid } from 'uuid';
@@ -10,6 +13,7 @@ function youtubeParser(url: string) {
const match = url.match(ytRegex); const match = url.match(ytRegex);
return match && match[7].length === 11 ? match[7] : url; return match && match[7].length === 11 ? match[7] : url;
} }
// eslint-disable-next-line regexp/no-empty-alternative
const vimeoRegex = /https?:\/\/(?:www\.|player\.)?vimeo.com\/(?:channels\/(?:\w+\/)?|groups\/([^/]*)\/videos\/|album\/(\d+)\/video\/|)(\d+)(?:$|\/|\?)/; const vimeoRegex = /https?:\/\/(?:www\.|player\.)?vimeo.com\/(?:channels\/(?:\w+\/)?|groups\/([^/]*)\/videos\/|album\/(\d+)\/video\/|)(\d+)(?:$|\/|\?)/;
function vimeoParser(url: string) { function vimeoParser(url: string) {
const match = url.match(vimeoRegex); const match = url.match(vimeoRegex);
@@ -25,7 +29,7 @@ function preziParser(url: string) {
const match = url.match(preziRegex); const match = url.match(preziRegex);
return match ? match[1] : url; return match ? match[1] : url;
} }
const EMBED_REGEX = /@\[([a-zA-Z].+?)]\((.*?)[)]/im; const EMBED_REGEX = /@\[([a-zA-Z].+?)\]\((.*?)\)/;
function extractVideoParameters(url: string) { function extractVideoParameters(url: string) {
const parameterMap = new Map(); const parameterMap = new Map();
const params = url.replace(/&amp;/gi, '&').split(/[#?&]/); const params = url.replace(/&amp;/gi, '&').split(/[#?&]/);
@@ -83,7 +87,7 @@ declare module 'hydrooj' {
interface ModuleInterfaces { interface ModuleInterfaces {
richmedia: { richmedia: {
get: (service: string, src: string, md: MarkdownIt) => string | null; get: (service: string, src: string, md: MarkdownIt) => string | null;
} };
} }
} }
@@ -19,7 +19,7 @@ const depedentTags = {
dt: ['dl'], dt: ['dl'],
dd: ['dl'], dd: ['dl'],
}; };
const whitelistClasses = ['row', 'columns', 'typo', 'note', 'warn'].concat(new Array(12).fill(0).map((_, i) => `medium-${i + 1}`)); const whitelistClasses = ['row', 'columns', 'typo', 'note', 'warn'].concat(Array.from({ length: 12 }).fill(0).map((_, i) => `medium-${i + 1}`));
const tagCheck = new FilterXSS({ const tagCheck = new FilterXSS({
css: false, css: false,
+1 -1
View File
@@ -1,7 +1,7 @@
import { moment, ObjectId } from 'hydrooj'; import { moment, ObjectId } from 'hydrooj';
import { AnsiUp } from 'ansi_up'; import { AnsiUp } from 'ansi_up';
export { size, formatSeconds } from 'hydrooj'; export { formatSeconds, size } from 'hydrooj';
const AU = new AnsiUp(); const AU = new AnsiUp();
+3 -3
View File
@@ -30,7 +30,7 @@ async function getFiles(folder: string, base = ''): Promise<string[]> {
files.push(...await getFiles(path.join(folder, i), path.join(base, i))); files.push(...await getFiles(path.join(folder, i), path.join(base, i)));
} else files.push(path.join(base, i)); } else files.push(path.join(base, i));
} }
return files.map((item) => item.replace(/\\/gmi, '/')); return files.map((item) => item.replace(/\\/g, '/'));
} }
function locateFile(basePath: string, filenames: string[]) { function locateFile(basePath: string, filenames: string[]) {
@@ -47,7 +47,7 @@ nunjucks.runtime.memberLookup = function memberLookup(obj, val) {
if (obj === undefined || obj === null) return undefined; if (obj === undefined || obj === null) return undefined;
if (typeof obj[val] === 'function') { if (typeof obj[val] === 'function') {
const fn = function (...args) { const fn = function (...args) {
return obj[val].call(obj, ...args); return obj[val].call(obj, ...args); // eslint-disable-line no-useless-call
}; };
fn._original = obj[val]; fn._original = obj[val];
return fn; return fn;
@@ -255,7 +255,7 @@ export class TemplateService extends Service {
const pending = Object.values(global.addons); const pending = Object.values(global.addons);
const logger = this.ctx.logger('template'); const logger = this.ctx.logger('template');
for (const i of pending) { for (const i of pending) {
const p = locateFile(i, ['template', 'templates']); const p = locateFile(i as string, ['template', 'templates']);
if (p && (await fs.stat(p)).isDirectory()) { if (p && (await fs.stat(p)).isDirectory()) {
try { try {
const files = await getFiles(p); const files = await getFiles(p);
+1 -1
View File
@@ -108,7 +108,7 @@ export default async function (env: { watch?: boolean, production?: boolean, mea
devtool: env.production ? 'source-map' : false, devtool: env.production ? 'source-map' : false,
entry: { entry: {
[`hydro-${version}`]: './entry.js', [`hydro-${version}`]: './entry.js',
'sentry': './sentry.ts', sentry: './sentry.ts',
'default.theme': './theme/default.js', 'default.theme': './theme/default.js',
'service-worker': './service-worker.ts', 'service-worker': './service-worker.ts',
}, },
+1 -2
View File
@@ -1,4 +1,3 @@
/* eslint-disable import/no-import-module-exports */
import path from 'path'; import path from 'path';
import { size } from '@hydrooj/utils/lib/utils'; import { size } from '@hydrooj/utils/lib/utils';
import cac from 'cac'; import cac from 'cac';
@@ -106,7 +105,7 @@ async function main() {
classNamePrefix: 'icon', classNamePrefix: 'icon',
fontName: 'hydro-icons', fontName: 'hydro-icons',
css: true, css: true,
startUnicode: 0xea01, startUnicode: 0xEA01,
svg2ttf: { svg2ttf: {
timestamp: 1577836800, // 2020-1-1 timestamp: 1577836800, // 2020-1-1
}, },
@@ -118,7 +118,6 @@ export default class DOMAttachedObject {
} }
constructor($dom: JQuery<HTMLElement>, monitorDetach = false) { constructor($dom: JQuery<HTMLElement>, monitorDetach = false) {
// eslint-disable-next-line no-constructor-return
if ($dom == null) return null; if ($dom == null) return null;
this.$dom = $dom; this.$dom = $dom;
this.id = ++DOMAttachedObject.uniqueIdCounter; this.id = ++DOMAttachedObject.uniqueIdCounter;
@@ -2,7 +2,7 @@ import { CustomSelectAutoComplete as CustomSelectAutoCompleteFC } from '@hydrooj
import AutoComplete, { AutoCompleteOptions } from '.'; import AutoComplete, { AutoCompleteOptions } from '.';
interface CustomSelectOptions { interface CustomSelectOptions {
data: any[] data: any[];
} }
export default class CustomSelectAutoComplete<Multi extends boolean> extends AutoComplete<CustomSelectOptions> { export default class CustomSelectAutoComplete<Multi extends boolean> extends AutoComplete<CustomSelectOptions> {
@@ -19,7 +19,7 @@ export interface AutoCompleteOptions<Multi extends boolean = boolean> {
text?: () => string; text?: () => string;
} }
export default class AutoComplete<Options extends Record<string, any> = {}, Multi extends boolean = boolean> extends DOMAttachedObject { export default class AutoComplete<Options extends Record<string, any> = object, Multi extends boolean = boolean> extends DOMAttachedObject {
static DOMAttachKey = 'ucwAutoCompleteInstance'; static DOMAttachKey = 'ucwAutoCompleteInstance';
ref = null; ref = null;
container = document.createElement('div'); container = document.createElement('div');
@@ -215,7 +215,7 @@ export default class Calendar {
const daysByWeek = _.chunk(days, 7); const daysByWeek = _.chunk(days, 7);
const numberOfWeeks = days.length / 7; const numberOfWeeks = days.length / 7;
const bannersByWeek = _.fill(new Array(numberOfWeeks), 1).map(() => []); const bannersByWeek = _.fill(Array.from({ length: numberOfWeeks }), 1).map(() => []);
const beginDate = days[0].date.clone(); const beginDate = days[0].date.clone();
const endDate = _.last(days).date.clone(); const endDate = _.last(days).date.clone();
@@ -268,7 +268,7 @@ export default class Calendar {
])) ]))
.map((banners) => { .map((banners) => {
const dayBitmap = _ const dayBitmap = _
.fill(new Array(7), 1) .fill(Array.from({ length: 7 }), 1)
.map(() => []); .map(() => []);
banners.forEach((banner) => { banners.forEach((banner) => {
const beginDay = banner.beginAt.day(); const beginDay = banner.beginAt.day();
@@ -292,7 +292,7 @@ export default class Calendar {
// merge adjacent cells and arrange banners by vertical index // 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 = _ const weekBanners = _
.fill(new Array(vMaxLength), 1) .fill(Array.from({ length: vMaxLength }), 1)
.map(() => []); .map(() => []);
for (let vIndex = 0; vIndex < vMaxLength; ++vIndex) { for (let vIndex = 0; vIndex < vMaxLength; ++vIndex) {
let last = { span: 1, banner: dayBitmap[0][vIndex] }; let last = { span: 1, banner: dayBitmap[0][vIndex] };
Loaded 100 of 150 files, more files were not shown because too many files have changed in this diff. Show more