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
141 files changed
+1015 -989

No files matched your search

+30 -11
View File
@@ -1,20 +1,31 @@
/* eslint-disable max-len */
/* eslint-disable @typescript-eslint/naming-convention */
/* eslint-disable ts/naming-convention */
import path from 'node:path';
import { defineConfig, globalIgnores } from 'eslint/config';
import globals from 'globals';
import react from '@hydrooj/eslint-config';
export default defineConfig([globalIgnores([
const config = react({
ignores: [
'**/{public,files}/**/*.js',
'**/dist',
'**/*.d.ts',
'**/node_modules',
'**/.*.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: {
ecmaVersion: 5,
sourceType: 'module',
@@ -105,16 +116,17 @@ export default defineConfig([globalIgnores([
rules: {
'github/array-foreach': 0,
'@typescript-eslint/no-invalid-this': 0,
'ts/no-invalid-this': 0,
// FIXME A bug with eslint-parser
// 'template-curly-spacing': 'off',
'@stylistic/indent': [
'style/indent': [
'warn',
2,
{ SwitchCase: 1 },
],
'style/indent-binary-ops': ['warn', 2],
'function-paren-newline': 'off',
'no-mixed-operators': 'off',
'no-await-in-loop': 'off',
@@ -139,14 +151,21 @@ export default defineConfig([globalIgnores([
},
}, {
files: ['packages/ui-next/src/**/*.{ts,tsx}'],
languageOptions: {
globals: {
...globals.browser,
},
},
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 stylistic from '@stylistic/eslint-plugin';
import typescriptEslint from '@typescript-eslint/eslint-plugin';
import tsParser from '@typescript-eslint/parser';
import { defineConfig, globalIgnores } from 'eslint/config';
import deMorgan from 'eslint-plugin-de-morgan';
import github from 'eslint-plugin-github';
import jsxA11Y from 'eslint-plugin-jsx-a11y';
import reactRefresh from 'eslint-plugin-react-refresh';
import simpleImportSort from 'eslint-plugin-simple-import-sort';
const compat = new FlatCompat({});
export default defineConfig([
globalIgnores([
'**/*.d.ts',
'**/node_modules',
'**/.git',
]),
/** @type {typeof antfu} */
const base = (option, ...args) => antfu(
{
extends: [
compat.extends('airbnb-base'),
compat.extends('airbnb/hooks'),
github.getFlatConfigs().react,
deMorgan.configs.recommended,
eslintReact.configs['recommended-typescript'],
markdown: false,
typescript: true,
vue: true,
...option,
gitignore: false,
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: {
globals: {
Atomics: 'readonly',
SharedArrayBuffer: 'readonly',
BigInt: 'readonly',
SharedArrayBuffer: 'readonly',
...(option.languageOptions?.globals || {}),
},
parser: tsParser,
...(option.languageOptions || {}),
},
lessOpinionated: true,
plugins: {
'@eslint-react': eslintReact,
'react-refresh': reactRefresh,
'simple-import-sort': simpleImportSort,
...(option.plugins || {}),
},
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', {
allowSingleLine: true,
}],
// ui-default is currently on react 18
'@eslint-react/no-prop-types': 0,
'@eslint-react/no-string-refs': 0,
camelcase: 'off',
'comma-dangle': '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,
}],
'antfu/consistent-chaining': 'off',
'antfu/consistent-list-newline': 'off',
'class-methods-use-this': 0,
'consistent-return': 1,
curly: ['error', 'multi-line'],
'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,
'@typescript-eslint/naming-convention': [
'warn',
{
selector: 'default',
format: ['camelCase'],
leadingUnderscore: 'allowSingleOrDouble',
}, {
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,
'eslint-comments/no-unlimited-disable': 'off',
'func-names': 0,
'function-call-argument-newline': 0,
'function-paren-newline': 0,
'github/a11y-svg-has-accessible-name': 0,
'github/array-foreach': 1,
'global-require': 0,
'guard-for-in': 0,
'implicit-arrow-linebreak': 0,
'import/consistent-type-specifier-style': 'off',
'import/extensions': 0,
'import/order': 0,
'consistent-return': 1,
'func-names': 0,
'import/newline-after-import': 0,
'import/no-cycle': 0,
'import/no-extraneous-dependencies': 0,
'import/no-named-as-default': 0,
'import/order': 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', {
enforceForIfStatements: true,
}],
'max-classes-per-file': 0,
'max-len': ['warn', 150],
'newline-per-chained-call': 0,
'no-await-in-loop': 1,
'no-bitwise': 0,
'no-console': 0,
'no-continue': 0,
'no-extend-native': 0,
'no-empty': ['warn', {
allowEmptyCatch: true,
}],
'no-await-in-loop': 1,
'no-empty-pattern': 0,
'no-extend-native': 0,
'no-extra-boolean-cast': ['warn', {
enforceForLogicalOperands: true,
}],
'no-implicit-coercion': ['warn', {
allow: ['!!', '+'],
}],
'no-multi-assign': 0,
'no-return-await': 0,
'no-multi-str': 0,
'no-nested-ternary': 0,
'no-param-reassign': 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': [
'error', {
selector: 'LabeledStatement',
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.',
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,
'github/a11y-svg-has-accessible-name': 0,
'prefer-destructuring': 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', {
groups: [
['^\\u0000'],
@@ -416,50 +151,290 @@ export default defineConfig([
],
}],
// ui-default is currently on react 18
'@eslint-react/no-prop-types': 0,
'@eslint-react/no-string-refs': 0,
// 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/arrow-parens': ['error', 'always'],
'style/brace-style': ['error', '1tbs', {
allowSingleLine: 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
rules: {
'constructor-super': 'off',
'getter-return': 'off',
'import/named': 'off',
'import/no-named-as-default-member': 'off',
'import/no-unresolved': 'off',
'no-const-assign': 'off',
'no-dupe-args': 'off',
'no-dupe-class-members': 'off',
@@ -475,9 +450,15 @@ export default defineConfig([
'no-unreachable': 'off',
'no-unsafe-negation': '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",
"version": "2.0.3",
"version": "3.0.0",
"license": "MIT",
"main": "base.mjs",
"repository": "https://github.com/hydro-dev/Hydro",
"dependencies": {
"@antfu/eslint-config": "^5.2.0",
"@eslint-react/eslint-plugin": "^1.52.3",
"@eslint/compat": "^1.3.1",
"@eslint/eslintrc": "^3.3.1",
"@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",
"@typescript-eslint/eslint-plugin": "^8.39.0",
"@typescript-eslint/parser": "^8.39.0",
"eslint-plugin-de-morgan": "^1.3.1",
"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-refresh": "^0.4.20",
"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>;
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 input: Schema<Arg>;
readonly func: (Type extends 'Subscription'
? (context: any, args: Arg, emit: (payload: Res) => void) => (() => MaybePromise<void>)
: (context: any, args: Arg) => MaybePromise<Res | AsyncGenerator<Progress, Res, never>>);
readonly hooks: ApiCall<'Query', Arg, void>[];
};
}
export const _get = <Type extends ApiType>(type: Type) => <Arg, Res, Progress>(
schema: Schema<Arg>,
func: ApiCall<Type, Arg, Res, Progress>['func'],
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 Mutation = _get('Mutation');
@@ -57,7 +57,7 @@ export interface Apis {
builtin: {
'query.batch': ApiCall<'Query', { op: string, args: any }[], { [key: string]: any }>;
'mutation.batch': ApiCall<'Mutation', { op: string, args: any }[], { [key: string]: any }>;
}
};
test: typeof TestApis;
}
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 ProjectionSchema<T> = T extends Array<infer U>
? ProjectionSchema<U>
: | { [K in keyof T]?: ProjectionSchemaId | ProjectionSchema<T[K]> }
| Record<keyof any, ProjectionSchemaId | object>;
: { [K in keyof T]?: ProjectionSchemaId | ProjectionSchema<T[K]> } | Record<keyof any, ProjectionSchemaId | object>;
type AsKeys<T> = T extends Array<infer U extends string> ? Record<U, 1> : T;
type Projection<T, S> = S extends ProjectionSchemaId
? 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
? new (...args: R) => S : never;
export interface ParamOption<T> {
name: string,
source: 'all' | 'get' | 'post' | 'route',
isOptional?: boolean | 'convert',
convert?: Converter<T>,
validate?: Validator,
name: string;
source: 'all' | 'get' | 'post' | 'route';
isOptional?: boolean | 'convert';
convert?: Converter<T>;
validate?: Validator;
}
const kSchema = Symbol.for('schemastery');
+2 -2
View File
@@ -1,5 +1,5 @@
interface IHydroError {
new(...args: any[]): HydroError
new(...args: any[]): HydroError;
}
export class HydroError extends Error {
@@ -32,7 +32,7 @@ const Err = (name: string, Class: IHydroError, ...info: Array<(() => string) | s
msg = item;
}
}
// eslint-disable-next-line @typescript-eslint/no-shadow
// eslint-disable-next-line ts/no-shadow
return class HydroError extends Class {
name = name;
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 './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';
export interface KnownHandlers {
'NotFound': NotFoundHandler;
NotFound: NotFoundHandler;
}
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 interface ServerEvents<C extends Context> extends KnownHandlerEvents, HandlerEvents<C> {
'handler/create': (thisArg: Handler<C> | ConnectionHandler<C>, type: 'ws' | 'http') => VoidReturn
'handler/create/http': (thisArg: Handler<C>) => VoidReturn
'handler/create/ws': (thisArg: ConnectionHandler<C>) => VoidReturn
'handler/init': (thisArg: Handler<C>) => VoidReturn
'handler/error': (thisArg: Handler<C> | ConnectionHandler<C>, e: Error) => VoidReturn
'handler/api/before': (thisArg: ApiHandler<C>) => VoidReturn
'api/before': (args: any) => VoidReturn
[k: `handler/${HookType}/${string}`]: (thisArg: any) => VoidReturn
[k: `handler/${HookWithMethod}/${string}/${Methods}`]: (thisArg: any) => VoidReturn
[k: `handler/register/${string}`]: (HandlerClass: new (...args: any[]) => any) => VoidReturn
[k: `handler/error/${string}`]: (thisArg: Handler<C>, e: Error) => VoidReturn
[k: `handler/api/before/${string}`]: (thisArg: ApiHandler<C>) => VoidReturn
[k: `api/before/${string}`]: (args: any) => VoidReturn
'handler/create': (thisArg: Handler<C> | ConnectionHandler<C>, type: 'ws' | 'http') => VoidReturn;
'handler/create/http': (thisArg: Handler<C>) => VoidReturn;
'handler/create/ws': (thisArg: ConnectionHandler<C>) => VoidReturn;
'handler/init': (thisArg: Handler<C>) => VoidReturn;
'handler/error': (thisArg: Handler<C> | ConnectionHandler<C>, e: Error) => VoidReturn;
'handler/api/before': (thisArg: ApiHandler<C>) => VoidReturn;
'api/before': (args: any) => VoidReturn;
[k: `handler/${HookType}/${string}`]: (thisArg: any) => VoidReturn;
[k: `handler/${HookWithMethod}/${string}/${Methods}`]: (thisArg: any) => VoidReturn;
[k: `handler/register/${string}`]: (HandlerClass: new (...args: any[]) => any) => VoidReturn;
[k: `handler/error/${string}`]: (thisArg: Handler<C>, e: Error) => VoidReturn;
[k: `handler/api/before/${string}`]: (thisArg: ApiHandler<C>) => VoidReturn;
[k: `api/before/${string}`]: (args: any) => VoidReturn;
}
declare module 'cordis' {
+1 -1
View File
@@ -10,7 +10,7 @@
"dependencies": {
"@hydrooj/utils": "workspace:^",
"emoji-regex": "^10.4.0",
"fs-extra": "^11.3.0",
"fs-extra": "^11.3.1",
"koa": "^3.0.1",
"koa-body": "^6.0.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 { tmpdir } from 'os';
import { join } from 'path';
@@ -77,7 +78,8 @@ export interface HydroResponse {
type: string;
status: number;
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.
*/
pjax?: string;
@@ -87,14 +89,14 @@ export interface HydroResponse {
attachment: (name: string, stream?: any) => void;
addHeader: (name: string, value: string) => void;
}
type HydroContext = {
interface HydroContext {
request: HydroRequest;
response: HydroResponse;
args: Record<string, any>;
UiContext: Record<string, any>;
domain: { _id: string };
user: { _id: number };
};
}
export type KoaContext = Koa.Context & {
HydroContext: HydroContext;
handler: any;
@@ -103,14 +105,14 @@ export type KoaContext = Koa.Context & {
holdFiles: (string | File)[];
};
export type TextRenderer = {
export interface TextRenderer {
output: 'html' | 'json' | 'text';
render: (name: string, args: Record<string, any>, context: Record<string, any>) => string | Promise<string>;
};
export type BinaryRenderer = {
}
export interface BinaryRenderer {
output: 'binary';
render: (name: string, args: Record<string, any>, context: Record<string, any>) => Buffer | Promise<Buffer>;
};
}
export type Renderer = (BinaryRenderer | TextRenderer) & {
name: string;
accept: readonly string[];
@@ -139,7 +141,7 @@ export interface UserModel {
_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> {
static [kHandler]: string | boolean = 'HandlerCommon';
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 });
try {
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
@@ -777,7 +780,7 @@ ${c.response.status} ${endTime - startTime}ms ${c.response.length}`);
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) {
// if (name === 'contest_scoreboard') {
// 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);
}
// 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) {
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 = {
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 */
Name: saslprepString(/^.{1,255}$/),
Filename: saslprepString(/^[^\\/?#~!|*]{1,255}$/, (i) => sanitize(i) === i),
UidOrName: saslprepString(/^(.{3,31}|[\u4e00-\u9fa5]{2}|-?[0-9]+)$/),
Username: saslprepString(/^(.{3,31}|[\u4e00-\u9fa5]{2})$/),
UidOrName: saslprepString(/^(?:.{3,31}|[\u4E00-\u9FA5]{2}|-?[0-9]+)$/),
Username: saslprepString(/^(?:.{3,31}|[\u4E00-\u9FA5]{2})$/),
Password: basicString(/^.{6,255}$/),
ProblemId: saslprepString(/^([a-zA-Z0-9]{1,10}-)?[a-zA-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),
DomainId: saslprepString(/^[a-zA-Z][a-zA-Z0-9_]{3,31}$/),
Role: saslprepString(/^[_0-9A-Za-z\u4e00-\u9fa5]{1,31}$/i),
ProblemId: saslprepString(/^(?:[a-z0-9]{1,10}-)?[a-z0-9]+$/i, () => true, (s) => (Number.isSafeInteger(+s) ? +s : s)),
Email: saslprepString(/^[\w.+-]+@[a-z0-9-]+(?:\.[a-z0-9-]+)+$/i),
DomainId: saslprepString(/^[a-zA-Z]\w{3,31}$/),
Role: saslprepString(/^[\w\u4E00-\u9FA5]{1,31}$/),
Title: basicString(/^.{1,64}$/, (i) => !!i.trim()),
ShortString: basicString(/^.{1,255}$/),
String: basicString(),
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)],
Float: [(v) => +v, (v) => Number.isFinite(+v)],
+4 -3
View File
@@ -1,3 +1,4 @@
/* eslint-disable node/no-deprecated-api */
const map = {};
require('source-map-support').install({
handleUncaughtExceptions: false,
@@ -27,7 +28,7 @@ const remove = [
/(const|let|var)\s+__dirname\s*=\s*(path\.)?dirname\s*\(\s*__filename\s*\)\s*;?/g,
// 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+__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) {
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) {
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);
let dummyCode = '';
if (length > 1) dummyCode = `"${'\u200b'.repeat(length - 2)}"`;
if (length > 1) dummyCode = `"${'\u200B'.repeat(length - 2)}"`;
const script = new vm.Script(dummyCode, {
filename,
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",
"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",
"lint": "eslint --flag v10_config_lookup_from_file packages plugins modules framework --ext .ts,.tsx,.jsx --fix",
"lint:ci": "eslint --flag v10_config_lookup_from_file packages plugins modules framework --ext .ts,.tsx,.jsx --max-warnings=0",
"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,.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",
"start": "packages/hydrooj/bin/hydrooj.js",
"postinstall": "node build/prepare.js",
@@ -46,7 +46,7 @@
"eslint": "^9.32.0",
"eslint-import-resolver-typescript": "^4.4.4",
"eslint-import-resolver-webpack": "^0.13.10",
"fs-extra": "^11.3.0",
"fs-extra": "^11.3.1",
"globals": "^16.3.0",
"globby": "~14.0.2",
"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({
time: '3s',
memory: args.enable5 ? '2g' : '512m',
cases: new Array(20).fill({
cases: Array.from({ length: 20 }).fill({
input: '1.in',
output: '1.out',
}),
+1 -1
View File
@@ -1,7 +1,7 @@
import { findFileSync, parseMemoryMB, parseTimeMS } from '@hydrooj/utils';
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) {
const config: any = {
checker_type: cfg.checker_type || 'default',
+1 -1
View File
@@ -1,4 +1,4 @@
export * from './lang';
export * from './status';
export * from './types';
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) {
const f = ini.split('\n');
const count = parseInt(f[0], 10);
const count = Number.parseInt(f[0], 10);
const res = { subtasks: [] };
for (let i = 1; i <= count; i++) {
if (!f[i]?.trim()) throw new Error('Testdata count incorrect.');
const [input, output, time, score, memory] = f[i].split('|');
const cur = {
cases: [{ input, output }],
score: parseInt(score, 10),
score: Number.parseInt(score, 10),
time: `${time}s`,
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);
}
return res;
@@ -29,7 +29,8 @@ interface 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']
.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')])
@@ -39,7 +40,7 @@ const SubtaskMatcher: MatchRule[] = [
preferredScorerType: (a) => (a[3] ? 'min' : 'sum'),
},
{
regex: /^([^\d]*)\.(in|IN)(\d+)$/,
regex: /^(\D*)\.(in|IN)(\d+)$/,
output: (a) => [
`${a[1]}.${a[2] === 'in' ? 'ou' : 'OU'}${a[3]}`,
`${a[1]}.${a[2] === 'in' ? 'out' : 'OUT'}${a[3]}`,
@@ -49,14 +50,14 @@ const SubtaskMatcher: MatchRule[] = [
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}`),
id: (a) => +a[4],
subtask: (a) => +a[2],
preferredScorerType: () => 'min',
},
{
regex: /^(([0-9]+)[-_](?:.*))\.(in|IN)$/,
regex: /^(([0-9]+)[-_].*)\.(in|IN)$/,
output: (a) => ['out', 'ans', 'OUT', 'ANS'].flatMap((i) => `${a[1]}.${i}`),
id: (a) => +a[2],
subtask: () => 1,
+6 -6
View File
@@ -66,13 +66,13 @@ export interface ProblemConfigFile {
export interface FileInfo {
/** storage path */
_id: string,
_id: string;
/** filename */
name: string,
name: string;
/** file size (in bytes) */
size: number,
etag: string,
lastModified: Date,
size: number;
etag: string;
lastModified: Date;
}
export interface JudgeMeta {
@@ -113,7 +113,7 @@ export interface RecordPayload extends RecordJudgeInfo {
/** 0 if pretest&script */
contest?: string;
files?: Record<string, string>
files?: Record<string, string>;
}
export interface JudgeRequest extends Omit<RecordPayload, 'testCases'> {
@@ -91,7 +91,7 @@ function MonacoContainer({
if (!editor) return;
const current = editor.getValue({ lineEnding: '\n', preserveBOM: false });
const diff = diffLines(current, config);
const ops: { range: any; text: string }[] = [];
const ops: { range: any, text: string }[] = [];
let cursor = 1;
for (const line of diff) {
if (line.added) {
@@ -109,11 +109,7 @@ function MonacoContainer({
}
model.pushEditOperations([], ops, undefined);
}, [editor, config]);
return (
<div ref={initializeEditor} style={{
width: '100%', height: '500px',
}} />
);
return <div ref={initializeEditor} style={{ width: '100%', height: '500px' }} />;
}
export default function ConfigEditor({
@@ -166,8 +162,12 @@ export default function ConfigEditor({
<Allotment onChange={setSize}>
<Allotment.Pane>
<MonacoContainer
editorCallback={registerAction} schema={schema} monaco={monaco}
config={stringConfig} setValue={updateFromMonaco} setError={setInfo}
editorCallback={registerAction}
schema={schema}
monaco={monaco}
config={stringConfig}
setValue={updateFromMonaco}
setError={setInfo}
size={size[0]}
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 {
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,
} = props;
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 itemKey = props.itemKey ?? itemText;
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 [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 Icon } from './Icon';
export { default as Notification } from './Notification';
export { default as AutoComplete, type AutoCompleteHandle, type AutoCompleteProps } from './autocomplete/AutoComplete';
export { default as CustomSelectAutoComplete } from './autocomplete/CustomSelectAutoComplete';
export { default as ComponentsProvider } from './provider';
+1 -1
View File
@@ -21,7 +21,7 @@
"@types/diff": "^8.0.0",
"@types/js-yaml": "^4.0.9",
"@types/lodash": "^4.17.20",
"sass": "^1.89.2"
"sass": "^1.90.0"
},
"peerDependencies": {
"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]]),
hint: p.hint?.[0],
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 = {
time: p.time_limit[0]._ + p.time_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);
export interface Result {
location?: string,
continent?: string,
country?: string,
city?: string,
display: string
location?: string;
continent?: string;
country?: string;
city?: string;
display: string;
}
export default class GeoIPService extends Service {
+1 -1
View File
@@ -8,7 +8,7 @@
"dependencies": {
"@hydrooj/common": "workspace:^",
"@hydrooj/utils": "workspace:^",
"@zip.js/zip.js": "^2.7.71",
"@zip.js/zip.js": "^2.7.72",
"cac": "^6.7.14",
"p-queue": "^8.1.0",
"schemastery": "^3.16.1",
+3 -4
View File
@@ -17,9 +17,9 @@ export interface CheckConfig {
}
type Checker = (config: CheckConfig) => Promise<{
status: number,
score: number,
message: string,
status: number;
score: number;
message: string;
}>;
function parseDiffMsg(msg: string) {
@@ -206,7 +206,6 @@ const checkers: Record<string, Checker> = new Proxy({
* stderr:输出错误报告
*/
async syzoj(config) {
// eslint-disable-next-line prefer-const
let { status, stdout, stderr } = await runQueued(config.execute, {
copyIn: {
input: config.input,
+3 -3
View File
@@ -1,9 +1,9 @@
import { STATUS_TEXTS } from '@hydrooj/common';
interface CompileErrorInfo {
stdout?: string,
stderr?: string,
status?: number
stdout?: string;
stderr?: string;
status?: number;
}
export class CompileError extends Error {
+1 -1
View File
@@ -10,7 +10,7 @@ interface Task {
compile: () => Promise<void>;
judgeCase: (c: NormalizedCase) => (
(ctx: Context, ctxSubtask: ContextSubTask, runner?: Function) => Promise<JudgeResultBody['case']>
)
);
}
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.');
for (const key in config.answers) {
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 [subtaskId, caseId] = key.split('-').map(Number);
totalScore += score;
+2 -2
View File
@@ -27,8 +27,8 @@ export class Stream extends EventEmitter {
break;
case 2:
this.emit('output', {
index: (data[1] >> 4) & 0xf,
fd: (data[1]) & 0xf,
index: (data[1] >> 4) & 0xF,
fd: (data[1]) & 0xF,
content: data.subarray(2),
});
break;
+1 -1
View File
@@ -1,6 +1,6 @@
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) {
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 { FormatError } from './error';
const EMPTY_STR = /^[ \r\n\t]*$/i;
const EMPTY_STR = /^[ \r\n\t]*$/;
export const cmd = parse;
@@ -34,7 +34,7 @@ export function compilerText(...messages: string[]) {
function restrictFile(p: string) {
if (!p) return '/';
if (p[0] === '/') p = '';
return p.replace(/\.\./gmi, '');
return p.replace(/\.\./g, '');
}
export function ensureFile(folder: string) {
-1
View File
@@ -1,4 +1,3 @@
/* eslint-disable import/no-dynamic-require */
import cac from 'cac';
import { getAddons } from '../src/options';
-3
View File
@@ -41,9 +41,6 @@ __langname: 한국어
(All Problems): (전체 문제)
(None): (N/A)
(Not changed): 수정되지 않음
#Sample Input: Sample Input
#Sample Output: Sample Output
#Star: Star
Aborted: 중지됨
About {0}: '{0}에 대하여'
About Markdown: Markdown에 대하여
+4 -4
View File
@@ -3,7 +3,7 @@ __id: zh
__interface: yes
__langname: 简体中文
'"{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} discussions': '{0} 条讨论'
'{0} is a moderator of this domain.': '{}是这个域的管理者之一。'
@@ -38,7 +38,7 @@ __langname: 简体中文
'The value `{1}` of {0} already exists.': '{0} 的值 `{1}` 已经存在。'
'Yes': 是
"Effects only when Difficulty is not 'Use algorithm calculated'.": 仅当难度不为“使用算法计算”时才起效。
"Or upload a file:": 或者上传一个文件:
'Or upload a file:': 或者上传一个文件:
"Split by ',' '.'": 由“, ”或“,”分隔。
"What's file?": 什么是文件?
(All Problems): (所有题目)
@@ -64,8 +64,8 @@ Add new data: 添加新数据
Add User: 添加用户
Add: 添加
Additional File: 附加文件
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 "{1}".: 管理员 {0} 删除了你的讨论 "{1}"。
Admin {0} delete your discussion tail reply {1} in "{2}"({3}).: 管理员 {0} 从讨论 {2}({3}) 中删除了你的楼中楼回复 {1}。
All {0} Contests: 所有 {0} 比赛
All Contests: 所有比赛
All Homeworks: 所有作业
+2 -2
View File
@@ -23,14 +23,14 @@
"@hydrooj/utils": "workspace:^",
"@simplewebauthn/server": "13.1.2",
"@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",
"cac": "^6.7.14",
"cordis": "4.0.0-beta.5",
"csv-stringify": "^6.6.0",
"emoji-regex": "^10.4.0",
"emojis-list": "2.1.0",
"fs-extra": "^11.3.0",
"fs-extra": "^11.3.1",
"js-yaml": "^4.1.0",
"koa-proxies": "^0.12.4",
"koa-static-cache": "^5.1.4",
+3 -3
View File
@@ -22,13 +22,13 @@ langs:
__hint__:
- ~ All keys starting with _ will be ignored
- ~ 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
- defined in entry [foo] if not overwritten
- . defined in entry [foo] if not overwritten
- ~ code_file option defaults to foo.[name]
- ~ highlight option defaults to [name]
- ~ 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
- ~ Key marked with a ? means the option is optional
- (name):
+2 -1
View File
@@ -1,3 +1,4 @@
/* eslint-disable ts/no-unsafe-declaration-merging */
import LoggerService from '@cordisjs/plugin-logger';
import { TimerService } from '@cordisjs/plugin-timer';
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 { Disposable, Plugin, FiberState } from 'cordis';
export { Disposable, FiberState, Plugin } from 'cordis';
export interface Context extends cordis.Context {
[Context.events]: EventMap & cordis.Events<Context>;
+2 -2
View File
@@ -13,8 +13,8 @@ import {
const argv = cac().parse();
const tmpdir = path.resolve(os.tmpdir(), 'hydro');
const COMMENTS = /((\/\/.*$)|(\/\*[\s\S]*?\*\/))/mg;
const ARR = /=>.*$/mg;
const COMMENTS = /((\/\/.*$)|(\/\*[\s\S]*?\*\/))/gm;
const ARR = /=>.*$/gm;
function parseParameters(fn: Function) {
const code = fn.toString()
.replace(COMMENTS, '')
-1
View File
@@ -1,4 +1,3 @@
/* eslint-disable import/no-dynamic-require */
/* eslint-disable no-await-in-loop */
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);
const rules = Object.keys(contest.RULES).filter((i) => !contest.RULES[i].hidden);
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 = {
...(this.user.hasPerm(PERM.PERM_VIEW_HIDDEN_CONTEST) && !group)
? {}
@@ -626,10 +626,10 @@ export class ContestBalloonHandler extends ContestManagementBaseHandler {
}
}
type BuiltinInput = {
interface BuiltinInput {
tdoc: Tdoc;
groups: any[];
};
}
type AnyFunction = (...args: any) => 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
@@ -696,9 +696,9 @@ class ScoreboardService extends Service {
addView<T extends { [key: string]: keyof BuiltinInput | AnyFunction | Type<any> }>(
id: string, name: string, args: T,
{ display, supportedRules, cacheTime }: {
display: (this: ContestScoreboardHandler, args: ParseArgs<T>) => Promise<void>,
supportedRules: string[],
cacheTime?: number,
display: (this: ContestScoreboardHandler, args: ParseArgs<T>) => Promise<void>;
supportedRules: string[];
cacheTime?: number;
},
) {
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;
}
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'
? 'homework_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) {
let name: ObjectId | string | number;
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;
const hidden = this.user.own(this.vnode) || this.user.hasPerm(PERM.PERM_EDIT_DISCUSSION) ? {} : { hidden: false };
const [ddocs, dpcount] = await this.paginate(
+1 -1
View File
@@ -534,7 +534,7 @@ class HomeDomainCreateHandler extends Handler {
@param('name', Types.Title)
@param('bulletin', Types.Content)
@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) {
const doc = await domain.get(id);
if (doc) throw new DomainAlreadyExistsError(id);
+1 -1
View File
@@ -52,7 +52,7 @@ class HomeworkMainHandler extends Handler {
],
},
...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({
penaltySince: -1, endAt: -1, beginAt: -1, _id: -1,
});
+1 -1
View File
@@ -79,7 +79,7 @@ export class JudgeResultCallbackContext {
private relatedId = new ObjectId();
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.finishPromise = new Promise((resolve) => {
this.resolve = resolve;
+1 -1
View File
@@ -239,7 +239,7 @@ class SystemUserImportHandler extends SystemHandler {
@param('draft', Types.Boolean)
async post(domainId: string, _users: string, draft: boolean) {
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 mapping = 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 escaped = escapeRegExp(q.toLowerCase());
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 pdocs = await problem.getMulti(domainId, filter, projection)
.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';
if (!limit || limit > this.ctx.setting.get('pagination.problem') || page > 1) limit = this.ctx.setting.get('pagination.problem');
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 psdict = {};
const search = Object.values(global.Hydro.module.problemSearch)[0] || defaultSearch;
@@ -607,7 +607,7 @@ export class ProblemEditHandler extends ProblemManageHandler {
@route('pid', Types.ProblemId)
@post('title', Types.Title)
@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('tag', Types.Content, true, null, parseCategory)
@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)) {
this.checkPerm(PERM.PERM_VIEW_PROBLEM_SOLUTION);
}
// eslint-disable-next-line prefer-const
let [psdocs, pcount, pscount] = await this.paginate(
solution.getMulti(domainId, this.pdoc.docId),
page,
@@ -995,7 +995,7 @@ export class ProblemCreateHandler extends Handler {
@post('title', Types.Title)
@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('difficulty', Types.PositiveInt, (i) => +i <= 10, true)
@post('tag', Types.Content, true, null, parseCategory)
@@ -1006,7 +1006,7 @@ export class ProblemCreateHandler extends Handler {
if (typeof pid !== 'string') pid = `P${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 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 = [];
for (const file of files) {
if (this.user._files.find((i) => i.name === file)) {
+1 -2
View File
@@ -70,7 +70,7 @@ class RecordListHandler extends ContestDetailBaseHandler {
}
if (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);
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.uid === 'number' && rdoc.uid !== this.uid) return;
// eslint-disable-next-line prefer-const
let [udoc, pdoc] = await Promise.all([
user.getById(this.args.domainId, rdoc.uid),
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');
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 { ParsedAuthenticatorData } from '@simplewebauthn/server/helpers';
import type fs from 'fs';
@@ -16,43 +17,43 @@ export * from '@hydrooj/common/types';
type document = typeof import('./model/document');
export interface System {
_id: string,
value: any,
_id: string;
value: any;
}
export interface SystemKeys {
'smtp.user': string,
'smtp.from': string,
'smtp.pass': string,
'smtp.host': string,
'smtp.port': number,
'smtp.secure': boolean,
'installid': string,
'server.name': string,
'server.url': string,
'server.xff': string,
'server.xhost': string,
'server.host': string,
'server.port': number,
'server.language': string,
'limit.problem_files_max': number,
'problem.categories': string,
'session.keys': string[],
'session.saved_expire_seconds': number,
'session.unsaved_expire_seconds': number,
'user.quota': number,
'smtp.user': string;
'smtp.from': string;
'smtp.pass': string;
'smtp.host': string;
'smtp.port': number;
'smtp.secure': boolean;
installid: string;
'server.name': string;
'server.url': string;
'server.xff': string;
'server.xhost': string;
'server.host': string;
'server.port': number;
'server.language': string;
'limit.problem_files_max': number;
'problem.categories': string;
'session.keys': string[];
'session.saved_expire_seconds': number;
'session.unsaved_expire_seconds': number;
'user.quota': number;
}
export interface Setting {
family: string,
key: string,
range: [string, string][] | Record<string, string>,
value: any,
type: string,
subType?: string,
name: string,
desc: string,
flag: number,
family: string;
key: string;
range: [string, string][] | Record<string, string>;
value: any;
type: string;
subType?: string;
name: string;
desc: string;
flag: number;
}
export interface Authenticator {
@@ -119,7 +120,7 @@ export interface UserPreferenceDoc {
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 Udict = Record<number, User>;
@@ -190,11 +191,11 @@ export type { ProblemDoc } from './model/problem';
export type ProblemDict = NumericDictionary<ProblemDoc>;
export interface StatusDocBase {
_id: ObjectId,
docId: any,
docType: number,
domainId: string,
uid: number,
_id: ObjectId;
docId: any;
docType: number;
domainId: string;
uid: number;
}
export interface ProblemStatusDoc extends StatusDocBase {
@@ -241,10 +242,10 @@ export type ScoreboardRow = ScoreboardNode[] & { raw?: any };
export type PenaltyRules = Dictionary<number>;
export interface TrainingNode {
_id: number,
title: string,
requireNids: number[],
pids: number[],
_id: number;
title: string;
requireNids: number[];
pids: number[];
}
export interface Tdoc extends Document {
@@ -287,28 +288,28 @@ export interface Tdoc extends Document {
}
export interface TrainingDoc extends Omit<Tdoc, 'docType'> {
docType: document['TYPE_TRAINING'],
docType: document['TYPE_TRAINING'];
description: string;
pin?: number;
dag: TrainingNode[];
}
export interface DomainDoc extends Record<string, any> {
_id: string,
owner: number,
roles: Dictionary<string>,
avatar: string,
bulletin: string,
_join?: any,
host?: string[],
_id: string;
owner: number;
roles: Dictionary<string>;
avatar: string;
bulletin: string;
_join?: any;
host?: string[];
}
// Message
export interface MessageDoc {
from: number,
to: number,
content: string,
flag: number,
from: number;
to: number;
content: string;
flag: number;
}
// Blacklist
@@ -382,22 +383,22 @@ export interface ContestClarificationDoc extends Document {
}
export interface TokenDoc {
_id: string,
tokenType: number,
createAt: Date,
updateAt: Date,
expireAt: Date,
[key: string]: any,
_id: string;
tokenType: number;
createAt: Date;
updateAt: Date;
expireAt: Date;
[key: string]: any;
}
export interface OplogDoc extends Record<string, any> {
_id: ObjectId,
type: string,
_id: ObjectId;
type: string;
}
export interface ContestStat extends Record<string, any> {
detail: Record<number, Record<string, any>>,
unrank?: boolean,
detail: Record<number, Record<string, any>>;
unrank?: boolean;
}
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 interface Script {
run: (args: any, report: Function) => any,
description: string,
validate: any,
run: (args: any, report: Function) => any;
description: string;
validate: any;
}
export interface Task {
@@ -528,66 +529,66 @@ export interface LockDoc {
declare module './service/db' {
interface Collections {
'blacklist': BlacklistDoc;
'domain': DomainDoc;
blacklist: BlacklistDoc;
domain: DomainDoc;
'domain.user': any;
'record': RecordDoc;
record: RecordDoc;
'record.stat': RecordStatDoc;
'record.history': RecordHistoryDoc;
'document': any;
document: any;
'document.status': StatusDocBase & {
[K in keyof DocStatusType]: { docType: K } & DocStatusType[K];
}[keyof DocStatusType];
'discussion.history': DiscussionHistoryDoc;
'user': Udoc;
user: Udoc;
'user.preference': UserPreferenceDoc;
'vuser': VUdoc;
vuser: VUdoc;
'user.group': GDoc;
'check': System;
'message': MessageDoc;
'token': TokenDoc;
'status': any;
'oauth': OauthMap;
'system': System;
'task': Task;
'storage': FileNode;
'oplog': OplogDoc;
'event': EventDoc;
'opcount': OpCountDoc;
'schedule': Schedule;
check: System;
message: MessageDoc;
token: TokenDoc;
status: any;
oauth: OauthMap;
system: System;
task: Task;
storage: FileNode;
oplog: OplogDoc;
event: EventDoc;
opcount: OpCountDoc;
schedule: Schedule;
'contest.balloon': ContestBalloonDoc;
'lock': LockDoc;
lock: LockDoc;
}
}
export interface Model {
blacklist: typeof import('./model/blacklist').default,
builtin: typeof import('./model/builtin'),
contest: typeof import('./model/contest'),
discussion: typeof import('./model/discussion'),
document: Omit<typeof import('./model/document'), 'apply'>,
domain: typeof import('./model/domain').default,
message: typeof import('./model/message').default,
opcount: typeof import('./model/opcount'),
problem: typeof import('./model/problem').default,
record: typeof import('./model/record').default,
setting: typeof import('./model/setting'),
solution: typeof import('./model/solution').default,
system: typeof import('./model/system'),
task: typeof import('./model/task').default,
blacklist: typeof import('./model/blacklist').default;
builtin: typeof import('./model/builtin');
contest: typeof import('./model/contest');
discussion: typeof import('./model/discussion');
document: Omit<typeof import('./model/document'), 'apply'>;
domain: typeof import('./model/domain').default;
message: typeof import('./model/message').default;
opcount: typeof import('./model/opcount');
problem: typeof import('./model/problem').default;
record: typeof import('./model/record').default;
setting: typeof import('./model/setting');
solution: typeof import('./model/solution').default;
system: typeof import('./model/system');
task: typeof import('./model/task').default;
schedule: typeof import('./model/schedule').default;
oplog: typeof import('./model/oplog'),
token: typeof import('./model/token').default,
training: typeof import('./model/training'),
user: typeof import('./model/user').default,
oauth: typeof import('./model/oauth').default,
storage: typeof import('./model/storage').default,
rp: typeof import('./script/rating').RpTypes,
oplog: typeof import('./model/oplog');
token: typeof import('./model/token').default;
training: typeof import('./model/training');
user: typeof import('./model/user').default;
oauth: typeof import('./model/oauth').default;
storage: typeof import('./model/storage').default;
rp: typeof import('./script/rating').RpTypes;
}
export interface GeoIP {
provider: string,
lookup: (ip: string, locale?: string) => any,
provider: string;
lookup: (ip: string, locale?: string) => any;
}
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 interface UI {
nodes: Record<UIInjectableFields, any[]>,
getNodes: typeof import('./lib/ui').getNodes,
inject: typeof import('./lib/ui').inject,
nodes: Record<UIInjectableFields, any[]>;
getNodes: typeof import('./lib/ui').getNodes;
inject: typeof import('./lib/ui').inject;
}
export interface ModuleInterfaces {
@@ -629,8 +630,8 @@ export interface HydroGlobal {
declare global {
namespace NodeJS {
interface Global {
Hydro: HydroGlobal,
addons: string[],
Hydro: HydroGlobal;
addons: string[];
}
}
/** @deprecated */
+1 -1
View File
@@ -6,7 +6,7 @@ type AvatarProvider = (src: string, size: number) => string;
export const providers: Record<string, AvatarProvider> = {
gravatar: (email, size) => `${global.Hydro.model.system.get('avatar.gravatar_url')}`
+ `${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)}`,
url: (url) => url,
};
+7 -7
View File
@@ -1,11 +1,11 @@
export interface ProblemSource {
description?: string,
input?: string,
output?: string,
samples?: [string, string][],
samplesRaw?: string,
hint?: string,
source?: string,
description?: string;
input?: string;
output?: string;
samples?: [string, string][];
samplesRaw?: string;
hint?: string;
source?: 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 {
old: number,
uid: number,
rank: number,
old: number;
uid: number;
rank: number;
}
interface RatingOutputUser {
new: number,
uid: number,
new: number;
uid: number;
}
function calculate(users: RatingInputUser[]): RatingOutputUser[] {
+4 -4
View File
@@ -5,13 +5,13 @@ import { ObjectId as ObjectID } from 'mongodb';
import Schema from 'schemastery';
import superagent from 'superagent';
import { Context } from './context';
export { ObjectId, Filter } from 'mongodb';
export { WebSocket, WebSocketServer } from '@hydrooj/framework';
export * from './utils';
export * from './interface';
export * from './typeutils';
export * from './utils';
export { WebSocket, WebSocketServer } from '@hydrooj/framework';
export { Filter, ObjectId } from 'mongodb';
export {
Schema, superagent, _, ObjectID, Zip,
_, ObjectID, Schema, superagent, Zip,
};
/** @deprecated Use ZipReader/ZipWriter instead */
export const AdmZip = _AdmZip;
+2 -3
View File
@@ -1,4 +1,3 @@
/* eslint-disable import/no-dynamic-require */
/* eslint-disable consistent-return */
/* eslint-disable simple-import-sort/imports */
import './init';
@@ -181,7 +180,7 @@ export async function load() {
if (process.env.DEV) {
const q = await simpleGit().listRemote(['--get-url']);
if (!q.includes('hydro-dev/Hydro')) {
console.warn('\x1b[93m');
console.warn('\x1B[93m');
console.warn('DISCLAIMER:');
console.warn(' You are under development mode.');
console.warn(' The Hydro project is licensed under AGPL3,');
@@ -195,7 +194,7 @@ export async function load() {
console.warn(' 这意味着除非你获得了原作者的其他授权,');
console.warn(' 你需要同样以 AGPL3 协议开源所有的修改,');
console.warn(' 并保留所有的版权声明。');
console.warn('\x1b[39m');
console.warn('\x1B[39m');
console.log('');
console.log('Hydro will start in 5s.');
console.log('Hydro 将在五秒后继续启动。');
+1 -1
View File
@@ -641,7 +641,7 @@ const homework = buildContestRule({
);
if (exceedSeconds < 0) return rate * jdoc.score;
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) {
if (i * 3600 <= exceedSeconds) coefficient = tdoc.penaltyRules[i];
else break;
+2 -3
View File
@@ -1,4 +1,3 @@
/* eslint-disable object-curly-newline */
import assert from 'assert';
import {
Filter, FindCursor, ObjectId, OnlyFieldsOfType, PushOperator, UpdateFilter,
@@ -43,9 +42,9 @@ export interface DocType {
}
export interface DocStatusType {
[TYPE_PROBLEM]: ProblemStatusDoc,
[TYPE_PROBLEM]: ProblemStatusDoc;
// 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']>(
+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 });
interface DomainUserArg {
_id: number,
priv: number,
_id: number;
priv: number;
}
class DomainModel {
@@ -282,7 +282,7 @@ class DomainModel {
@ArgMethod
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({
$or: [{ _id: { $regex } }, { name: { $regex } }],
}).limit(limit).toArray();
+5 -6
View File
@@ -1,4 +1,3 @@
/* eslint-disable object-curly-newline */
import { pick, sum } from 'lodash';
import moment from 'moment-timezone';
import {
@@ -133,11 +132,11 @@ export default class RecordModel {
domainId: string, pid: number, uid: number,
lang: string, code: string, addTask: boolean,
args: {
contest?: ObjectId,
input?: string,
files?: Record<string, string>,
hackTarget?: ObjectId,
type: 'judge' | 'rejudge' | 'pretest' | 'hack' | 'generate',
contest?: ObjectId;
input?: string;
files?: Record<string, string>;
hackTarget?: ObjectId;
type: 'judge' | 'rejudge' | 'pretest' | 'hack' | 'generate';
} = { type: 'judge' },
) {
const data: RecordDoc = {
+1 -1
View File
@@ -69,7 +69,7 @@ export const Setting = (
declare global {
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;
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);
for (const i in tdoc.dag) {
for (const j in tdoc.dag[i].pids) {
if (Number.isSafeInteger(parseInt(tdoc.dag[i].pids[j], 10))) {
tdoc.dag[i].pids[j] = parseInt(tdoc.dag[i].pids[j], 10);
if (Number.isSafeInteger(Number.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 {
Authenticator, BaseUserDict, FileInfo, GDoc,
ownerInfo, Udict, Udoc, VUdoc,
OwnerInfo, Udict, Udoc, VUdoc,
} from '../interface';
import avatar from '../lib/avatar';
import pwhash from '../lib/hash.hydro';
@@ -116,9 +116,9 @@ export class User {
return this;
}
own<T extends ownerInfo>(doc: T, checkPerm: bigint): boolean;
own<T extends ownerInfo>(doc: T, exact: boolean): boolean;
own<T extends ownerInfo>(doc: T): 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): boolean;
own<T extends { owner: number, maintainer?: number[] }>(doc: T): boolean;
own(doc: any, arg1: any = false): boolean {
if (typeof arg1 === 'bigint' && !this.hasPerm(arg1)) return false;
@@ -143,7 +143,7 @@ export class User {
throw new LoginError(this.uname);
}
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);
}
}
+1 -1
View File
@@ -43,7 +43,7 @@ export async function iterateAllPsdoc(filter: Filter<ProblemStatusDoc>, cb: (psd
}
interface PartialProblemDoc extends ProblemDoc {
[key: string]: any,
[key: string]: any;
}
export async function iterateAllProblemInDomain(
+7 -6
View File
@@ -1,12 +1,13 @@
/* eslint-disable perfectionist/sort-exports */
import pwsh from './lib/hash.hydro';
import db from './service/db';
export { nanoid } from 'nanoid';
export { default as moment, isMoment } from 'moment-timezone';
export { isMoment, default as moment } from 'moment-timezone';
export {
WebService, Router, HandlerCommon, httpServer,
Query, Mutation, Subscription, Apis, APIS,
Apis, APIS, HandlerCommon, httpServer,
Mutation, Query, Router, Subscription, WebService,
} from '@hydrooj/framework';
export * from './pipelineUtils';
@@ -37,10 +38,10 @@ export { default as TaskModel } from './model/task';
export * from './model/builtin';
/** @deprecated */
export * as JudgeHandler from './handler/judge';
export { postJudge, JudgeResultCallbackContext } from './handler/judge';
export { JudgeResultCallbackContext, postJudge } from './handler/judge';
export { Collections } from './service/db';
export { Handler, ConnectionHandler, requireSudo } from './service/server';
export { Service, Context } from './context';
export { ConnectionHandler, Handler, requireSudo } from './service/server';
export { Context, Service } from './context';
export { buildContent } from './lib/content';
export { default as mime } from './lib/mime';
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 Schema from 'schemastery';
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 message = [''];
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';
type ND = NumericDictionary<number>;
type Report = (data: any) => void;
interface RpDef {
run(domainIds: string[], udict: ND, report: Function): Promise<void>;
run(domainIds: string[], udict: ND, report: Report): Promise<void>;
hidden: boolean;
base: number;
}
@@ -101,7 +102,7 @@ export const RpTypes: Record<string, RpDef> = {
};
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 });
let last = { rp: null };
let rank = 0;
@@ -135,7 +136,7 @@ export async function calcLevel(domainId: string, report: Function) {
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 udict = Counter();
await db.collection('domain.user').updateMany({ domainId }, { $set: { rpInfo: {} } });
@@ -160,7 +161,7 @@ async function runInDomain(domainId: string, report: Function) {
await calcLevel(domainId, report);
}
export async function run({ domainId }, report: Function) {
export async function run({ domainId }, report: Report) {
if (!domainId) {
const domains = await domain.getMulti().toArray();
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 interface EventMap {
'app/listen': () => void
'app/started': () => void
'app/ready': () => VoidReturn
'app/exit': () => VoidReturn
'app/before-reload': (entries: Set<string>) => VoidReturn
'app/reload': (entries: Set<string>) => VoidReturn
'app/listen': () => void;
'app/started': () => void;
'app/ready': () => VoidReturn;
'app/exit': () => VoidReturn;
'app/before-reload': (entries: Set<string>) => VoidReturn;
'app/reload': (entries: Set<string>) => VoidReturn;
'subscription/init': (h: ConnectionHandler<Context>, privileged: boolean) => VoidReturn
'subscription/subscribe': (channel: string, user: User, metadata: Record<string, string>) => VoidReturn
'subscription/init': (h: ConnectionHandler<Context>, privileged: boolean) => VoidReturn;
'subscription/subscribe': (channel: string, user: User, metadata: Record<string, string>) => VoidReturn;
'subscription/enable': (
channel: string, h: ConnectionHandler<Context>, privileged: boolean, onDispose: (disposable: () => void) => void
) => VoidReturn
) => VoidReturn;
'app/watch/change': (path: string) => VoidReturn
'app/watch/unlink': (path: string) => VoidReturn
'app/watch/change': (path: string) => VoidReturn;
'app/watch/unlink': (path: string) => VoidReturn;
'database/connect': (db: Db) => void
'database/config': () => VoidReturn
'database/connect': (db: Db) => void;
'database/config': () => VoidReturn;
'system/setting': (args: Record<string, any>) => VoidReturn
'bus/broadcast': (event: keyof EventMap, payload: any, trace?: string) => VoidReturn
'monitor/update': (type: 'server' | 'judge', $set: any) => VoidReturn
'monitor/collect': (info: any) => VoidReturn
'system/setting': (args: Record<string, any>) => VoidReturn;
'bus/broadcast': (event: keyof EventMap, payload: any, trace?: string) => VoidReturn;
'monitor/update': (type: 'server' | 'judge', $set: any) => VoidReturn;
'monitor/collect': (info: any) => VoidReturn;
'api/update': () => void;
'task/daily': () => void;
'task/daily/finish': (pref: Record<string, number>) => void;
'user/message': (uid: number[], mdoc: Omit<MessageDoc, 'to'>) => void
'user/get': (udoc: User) => void
'user/delcache': (content: string | true) => void
'user/message': (uid: number[], mdoc: Omit<MessageDoc, 'to'>) => void;
'user/get': (udoc: User) => void;
'user/delcache': (content: string | true) => void;
'user/import/parse': (payload: any) => VoidReturn
'user/import/create': (uid: number, udoc: any) => VoidReturn
'user/import/parse': (payload: any) => VoidReturn;
'user/import/create': (uid: number, udoc: any) => VoidReturn;
'domain/create': (ddoc: DomainDoc) => VoidReturn
'domain/before-get': (query: Filter<DomainDoc>) => VoidReturn
'domain/get': (ddoc: DomainDoc) => VoidReturn
'domain/before-update': (domainId: string, $set: Partial<DomainDoc>) => VoidReturn
'domain/update': (domainId: string, $set: Partial<DomainDoc>, ddoc: DomainDoc) => VoidReturn
'domain/delete': (domainId: string) => VoidReturn
'domain/delete-cache': (domainId: string) => VoidReturn
'domain/create': (ddoc: DomainDoc) => VoidReturn;
'domain/before-get': (query: Filter<DomainDoc>) => VoidReturn;
'domain/get': (ddoc: DomainDoc) => VoidReturn;
'domain/before-update': (domainId: string, $set: Partial<DomainDoc>) => VoidReturn;
'domain/update': (domainId: string, $set: Partial<DomainDoc>, ddoc: DomainDoc) => VoidReturn;
'domain/delete': (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>(
domainId: string, docType: T, docId: DocType[T],
$set: any, $unset: OnlyFieldsOfType<DocType[T], any, true | '' | 1>
) => VoidReturn
) => VoidReturn;
'discussion/before-add': (payload: Partial<DiscussionDoc>) => VoidReturn
'discussion/add': (payload: Partial<DiscussionDoc>) => VoidReturn
'discussion/before-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/add': (doc: Partial<ProblemDoc>, docId: number) => VoidReturn
'problem/before-edit': (doc: Partial<ProblemDoc>, $unset: OnlyFieldsOfType<ProblemDoc, any, true | '' | 1>) => VoidReturn
'problem/edit': (doc: ProblemDoc) => VoidReturn
'problem/before-del': (domainId: string, docId: number) => VoidReturn
'problem/del': (domainId: string, docId: number) => VoidReturn
'problem/list': (query: Filter<ProblemDoc>, handler: any, sort?: string[]) => VoidReturn
'problem/get': (doc: ProblemDoc, handler: any) => VoidReturn
'problem/delete': (domainId: string, docId: number) => 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/delTestdata': (domainId: string, docId: number, name: string[]) => 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/delAdditionalFile': (domainId: string, docId: number, name: string[]) => 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/before-edit': (doc: Partial<ProblemDoc>, $unset: OnlyFieldsOfType<ProblemDoc, any, true | '' | 1>) => VoidReturn;
'problem/edit': (doc: ProblemDoc) => VoidReturn;
'problem/before-del': (domainId: string, docId: number) => VoidReturn;
'problem/del': (domainId: string, docId: number) => VoidReturn;
'problem/list': (query: Filter<ProblemDoc>, handler: any, sort?: string[]) => VoidReturn;
'problem/get': (doc: ProblemDoc, handler: any) => VoidReturn;
'problem/delete': (domainId: string, docId: number) => 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/delTestdata': (domainId: string, docId: number, name: string[]) => 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/delAdditionalFile': (domainId: string, docId: number, name: string[]) => VoidReturn;
'contest/before-add': (payload: Partial<Tdoc>) => VoidReturn
'contest/add': (payload: Partial<Tdoc>, id: ObjectId) => VoidReturn
'contest/edit': (payload: Tdoc) => VoidReturn
'contest/list': (query: Filter<Tdoc>, handler: any) => VoidReturn
'contest/scoreboard': (tdoc: Tdoc, rows: ScoreboardRow[], udict: BaseUserDict, pdict: ProblemDict) => VoidReturn
'contest/balloon': (domainId: string, tid: ObjectId, bdoc: ContestBalloonDoc) => VoidReturn
'contest/del': (domainId: string, tid: ObjectId) => VoidReturn
'contest/before-add': (payload: Partial<Tdoc>) => VoidReturn;
'contest/add': (payload: Partial<Tdoc>, id: ObjectId) => VoidReturn;
'contest/edit': (payload: Tdoc) => VoidReturn;
'contest/list': (query: Filter<Tdoc>, handler: any) => VoidReturn;
'contest/scoreboard': (tdoc: Tdoc, rows: ScoreboardRow[], udict: BaseUserDict, pdict: ProblemDict) => VoidReturn;
'contest/balloon': (domainId: string, tid: ObjectId, bdoc: ContestBalloonDoc) => VoidReturn;
'contest/del': (domainId: string, tid: ObjectId) => VoidReturn;
'oplog/log': (type: string, handler: Handler<Context> | ConnectionHandler<Context>, args: any, data: any) => VoidReturn;
'training/list': (query: Filter<TrainingDoc>, handler: any) => VoidReturn
'training/get': (tdoc: TrainingDoc, handler: any) => VoidReturn
'training/list': (query: Filter<TrainingDoc>, handler: any) => VoidReturn;
'training/get': (tdoc: TrainingDoc, handler: any) => VoidReturn;
'record/change': (rdoc: RecordDoc, $set?: any, $push?: any, body?: any) => void
'record/judge': (rdoc: RecordDoc, updated: boolean, pdoc?: ProblemDoc, updater?: any) => VoidReturn
'record/change': (rdoc: RecordDoc, $set?: any, $push?: any, body?: any) => void;
'record/judge': (rdoc: RecordDoc, updated: boolean, pdoc?: ProblemDoc, updater?: any) => VoidReturn;
}
export function apply(ctx: Context) {
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) => {
if (err) throw new Error();
if (err) throw new Error('cannot launch pm2 bus');
bus.on('hydro:broadcast', (packet) => {
(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 { }
interface MongoConfig {
protocol?: string,
username?: string,
password?: string,
host?: string,
port?: string,
name?: string,
url?: string,
uri?: string,
prefix?: string,
collectionMap?: Record<string, string>,
protocol?: string;
username?: string;
password?: string;
host?: string;
port?: string;
name?: string;
url?: string;
uri?: string;
prefix?: string;
collectionMap?: Record<string, string>;
}
declare module '../context' {
+1 -2
View File
@@ -1,7 +1,6 @@
// This file was adapted from @koishijs, MIT licensed.
/* eslint-disable consistent-return */
/* eslint-disable import/no-dynamic-require */
/* eslint-disable @typescript-eslint/no-shadow */
/* eslint-disable ts/no-shadow */
import { readFileSync } from 'fs';
import { relative } from 'path';
import { codeFrameColumns } from '@babel/code-frame';
+3 -3
View File
@@ -28,7 +28,7 @@ const ignoredLimit = `,${argv.options.ignoredLimit},`;
const logger = new Logger('server');
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;
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/validator';
export { Query, Mutation } from '@hydrooj/framework/api';
/*
* 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))
: this.request.host !== host)
)) 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 (withDomainId) res = `/d/${withDomainId}${res}`;
} catch (e) {
+4 -4
View File
@@ -1,7 +1,7 @@
/* 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 @typescript-eslint/naming-convention */
/* eslint-disable ts/naming-convention */
import yaml from 'js-yaml';
import { ObjectId } from 'mongodb';
import { randomstring, sleep } from '@hydrooj/utils';
@@ -51,7 +51,7 @@ export const coreScripts: MigrationScript[] = [
return true;
},
// Init
...new Array(47).fill(unsupportedUpgrade), // oxlint-disable-line no-new-array
...Array.from({ length: 47 }).fill(unsupportedUpgrade) as any,
async function _48_49() {
await RecordModel.coll.updateMany({ input: { $exists: true } }, { $set: { contest: new ObjectId('000000000000000000000000') } });
return true;
@@ -221,7 +221,7 @@ export const coreScripts: MigrationScript[] = [
config.answers[cnt] = l;
}
function processSingleLanguage(content: string) { // eslint-disable-line no-inner-declarations
function processSingleLanguage(content: string) {
let text = '';
try {
let scnt = 0;
+1 -1
View File
@@ -3,7 +3,7 @@ import {
} from 'hydrooj';
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, '/');
}
+4 -4
View File
@@ -1,4 +1,4 @@
/* eslint-disable no-tabs */
/* eslint-disable style/no-tabs */
/* eslint-disable no-await-in-loop */
import path from 'path';
import mariadb from 'mariadb';
@@ -70,7 +70,7 @@ export async function run({
host = 'localhost', port = 3306, name = 'jol',
username, password, domainId, contestType = 'oi',
dataDir, uploadDir = '/home/judge/src/web/upload/', rerun = true, randomMail = false,
}, report: Function) {
}, report: (data: any) => void) {
let remoteUsed = false;
const src = await mariadb.createConnection({
host,
@@ -191,7 +191,7 @@ export async function run({
samples: [[pdoc.sample_input.trim(), pdoc.sample_output.trim()]],
hint: pdoc.hint,
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);
for (const file of uploadFiles) {
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])));
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);
if (isAssignMode) {
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 style/no-tabs */
import path from 'path';
import zlib from 'zlib';
import mariadb from 'mariadb';
@@ -34,7 +35,7 @@ export async function run({
host = 'localhost', port = 3306, name,
username, password, domainId, contestType = 'oi',
dataDir, imageDir, rerun = true, randomMail = false,
}, report: Function) {
}, report: (data: any) => void) {
const src = await mariadb.createConnection({
host,
port,
@@ -150,6 +151,7 @@ export async function run({
hint: pdoc.hint,
source: pdoc.source,
}, 'html');
// eslint-disable-next-line regexp/no-super-linear-backtracking
const uploadFiles = content.matchAll(/(?:src|href)="\/images\/([^"]+\/([^"]+))"/g);
for (const file of uploadFiles) {
try {
+2 -2
View File
@@ -75,7 +75,7 @@ export async function run({
host = 'localhost', port = 3306, name = 'syzoj',
username, password, domainId, dataDir,
rerun = true, randomMail = 'never',
}, report: Function) {
}, report: (data: any) => void) {
const src = await mariadb.createConnection({
host,
port,
@@ -199,7 +199,7 @@ export async function run({
`publicize_time` datetime NULL DEFAULT NULL, 公开时间
`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 configMap: Record<string, string> = {};
const problemAdditionalFile = {};
+5 -5
View File
@@ -45,7 +45,7 @@ export async function run({
host = '172.17.0.2', port = 3306, name = 'app_uoj233',
username, password, domainId, dataDir,
rerun = true, randomMail = false,
}, report: Function) {
}, report: (data: any) => void) {
const src = await mariadb.createConnection({
host,
port,
@@ -506,10 +506,10 @@ export async function run({
if (confInfo.n_subtasks
&& Object.keys(confInfo.subtask_end).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,
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}`,
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,
score: 97,
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}`,
output: `${confInfo.output_pre}${i}.${confInfo.output_suf}`,
})),
@@ -530,7 +530,7 @@ export async function run({
config.subtasks.push({
id: Math.max(...config.subtasks.map((i) => i.id)) + 1,
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}`,
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 */
import mongodb, { Db, FindCursor } from 'mongodb';
import {
@@ -13,6 +13,8 @@ const pid = (id) => {
return id;
};
type Report = (args: { progress?: number, message?: string }) => void;
const tasks = {
user: async (doc) => ({
_id: doc._id,
@@ -93,7 +95,7 @@ const tasks = {
processer: (rule) => {
const n = {};
for (const key in rule) {
n[parseInt(key, 10) / 3600] = rule;
n[Number.parseInt(key, 10) / 3600] = rule;
}
return n;
},
@@ -282,7 +284,7 @@ const cursor: NodeJS.Dict<CursorGetter> = {
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 });
await report({ progress: 1, message: `discussion.node: ${count}` });
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 });
await report({ progress: 1, message: `Fix pid: ${count}` });
const total = Math.floor(count / 50);
@@ -349,7 +351,7 @@ function objid(ts: Date) {
}
// 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();
await report({ progress: 1, message: `Messages: ${count}` });
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 bulk = dst.collection('document').initializeUnorderedBulkOp();
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)
.toArray();
for (const doc of docs) {
const id = parseInt(doc.pid, 10);
const id = Number.parseInt(doc.pid, 10);
if (Number.isSafeInteger(id)) {
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}` });
let lastProgress = -1;
for (let i = 0; ; i++) {
@@ -466,7 +468,7 @@ async function task(name: any, src: Db, report: Function) {
export async function run({
host = 'localhost', port = 27017, name = 'vijos4', username, password,
}, report: Function) {
}, report: Report) {
let mongourl = 'mongodb://';
if (username) mongourl += `${username}:${password}@`;
mongourl += `${host}:${port}/${name}`;
+7 -7
View File
@@ -139,25 +139,25 @@ export function apply(ctx: Context) {
solved: +i === STATUS.STATUS_ACCEPTED,
})),
getFeed('languages', {
id: 'c', name: 'C', 'entry_point_required': false, extensions: ['c'],
id: 'c', name: 'C', entry_point_required: false, extensions: ['c'],
}),
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', {
id: 'java', name: 'Java', 'entry_point_required': true, extensions: ['java'],
id: 'java', name: 'Java', entry_point_required: true, extensions: ['java'],
}),
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', {
id: 'kotlin', name: 'Kotlin', 'entry_point_required': true, extensions: ['kt'],
id: 'kotlin', name: 'Kotlin', entry_point_required: true, extensions: ['kt'],
}),
getFeed('languages', {
id: 'rust', name: 'Rust', 'entry_point_required': true, extensions: ['rs'],
id: 'rust', name: 'Rust', entry_point_required: true, extensions: ['rs'],
}),
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: 'observers', name: '打星队伍' }),
+5 -5
View File
@@ -4,8 +4,8 @@ import {
} from 'hydrooj';
const file = fs.readFileSync(path.join(__dirname, 'public/assets/board.html'), 'utf8');
const indexJs = file.match(/index-([A-Za-z0-9_-]+)\.js"/)?.[1];
const indexCss = file.match(/index-([A-Za-z0-9_-]+)\.css"/)?.[1];
const indexJs = file.match(/index-([\w-]+)\.js"/)?.[1];
const indexCss = file.match(/index-([\w-]+)\.css"/)?.[1];
const status = {
[STATUS.STATUS_WRONG_ANSWER]: 'WRONG_ANSWER',
[STATUS.STATUS_ACCEPTED]: 'CORRECT',
@@ -72,9 +72,9 @@ export async function apply(ctx: Context) {
},
organization: 'School',
status_time_display: {
'correct': true,
'incorrect': true,
'pending': true,
correct: true,
incorrect: true,
pending: true,
},
medal: {
official: {
+10 -10
View File
@@ -1,23 +1,23 @@
/* eslint-disable import/first */
export * from './utils';
export { default as Notification } from './components/notification';
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 Notification } from './components/notification';
export { default as Socket } from './components/socket/index';
export { default as uploadFiles } from './components/upload';
export * as redux from 'react-redux';
export * from './components/zipDownloader';
export * from '@hydrooj/utils/lib/common';
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 'lodash';
export { default as React } from 'react';
export { default as ReactDOM } from 'react-dom/client';
export * from './misc/Page';
export { initPageLoader } from './hydro';
export * from './lazyload';
export * as redux from 'react-redux';
import { load } from './lazyload';
export default load;
@@ -30,7 +30,7 @@ import ProblemSelectAutoComplete from './components/autocomplete/ProblemSelectAu
import UserSelectAutoComplete from './components/autocomplete/UserSelectAutoComplete';
export {
AutoComplete, UserSelectAutoComplete, ProblemSelectAutoComplete, DomainSelectAutoComplete, CustomSelectAutoComplete,
AutoComplete, CustomSelectAutoComplete, DomainSelectAutoComplete, ProblemSelectAutoComplete, UserSelectAutoComplete,
};
export function addPage(page: import('./misc/Page').Page | (() => Promise<void> | void)) {
window.Hydro.extraPages.push(page);
+2 -2
View File
@@ -10,7 +10,7 @@ import esbuild from 'esbuild';
declare module 'hydrooj' {
interface UI {
esbuildPlugins?: esbuild.Plugin[]
esbuildPlugins?: esbuild.Plugin[];
}
interface SystemKeys {
'ui-default.nav_logo_dark': string;
@@ -91,7 +91,7 @@ export async function buildUI() {
const entryPoints: string[] = [];
const lazyModules: string[] = [];
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');
if (!fs.existsSync(publicPath)) publicPath = resolve(addon, 'public');
if (!fs.existsSync(publicPath)) continue;
@@ -8,7 +8,7 @@ function parseNextNumber(str, pos, max) {
value: '',
};
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);
}
result.ok = true;
@@ -24,9 +24,9 @@ function parseImageSize(str, pos, max) {
width: '',
height: '',
};
if (pos >= max) { return result; }
if (pos >= max) return result;
let code = str.charCodeAt(pos);
if (code !== 0x3d /* = */) { return result; }
if (code !== 0x3D /* = */) return result;
pos++;
code = str.charCodeAt(pos);
if (code !== 0x78 /* x */ && (code < 0x30 || code > 0x39) /* [0-9] */) {
@@ -67,14 +67,14 @@ export default function plugin(md) {
const oldPos = state.pos;
const max = state.posMax;
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) !== 0x21/* ! */) return false;
if (state.src.charCodeAt(state.pos + 1) !== 0x5B/* [ */) return false;
const labelStart = state.pos + 2;
const labelEnd = md.helpers.parseLinkLabel(state, state.pos + 1, false);
// parser failed to find ']', so it's not a valid link
if (labelEnd < 0) { return false; }
if (labelEnd < 0) return false;
pos = labelEnd + 1;
if (pos < max && state.src.charCodeAt(pos) === 0x28/* ( */) {
@@ -89,7 +89,7 @@ export default function plugin(md) {
code = state.src.charCodeAt(pos);
if (code !== 0x20 && code !== 0x0A) { break; }
}
if (pos >= max) { return false; }
if (pos >= max) return false;
// [link]( <href> "title" )
// ^^^^^^ parsing link destination
@@ -162,7 +162,7 @@ export default function plugin(md) {
//
// Link reference
//
if (typeof state.env.references === 'undefined') { return false; }
if (typeof state.env.references === 'undefined') return false;
// [foo] [bar]
// ^^ optional whitespace (can include newlines)
@@ -112,7 +112,7 @@ export default function plugin(md) {
const render = (latex: string, displayMode = false) => {
options.displayMode = displayMode;
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);
} catch (error) {
if (options.throwOnError) console.error(error);
@@ -1,4 +1,7 @@
/* 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 { v4 as uuid } from 'uuid';
@@ -10,6 +13,7 @@ function youtubeParser(url: string) {
const match = url.match(ytRegex);
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+)(?:$|\/|\?)/;
function vimeoParser(url: string) {
const match = url.match(vimeoRegex);
@@ -25,7 +29,7 @@ function preziParser(url: string) {
const match = url.match(preziRegex);
return match ? match[1] : url;
}
const EMBED_REGEX = /@\[([a-zA-Z].+?)]\((.*?)[)]/im;
const EMBED_REGEX = /@\[([a-zA-Z].+?)\]\((.*?)\)/;
function extractVideoParameters(url: string) {
const parameterMap = new Map();
const params = url.replace(/&amp;/gi, '&').split(/[#?&]/);
@@ -83,7 +87,7 @@ declare module 'hydrooj' {
interface ModuleInterfaces {
richmedia: {
get: (service: string, src: string, md: MarkdownIt) => string | null;
}
};
}
}
@@ -19,7 +19,7 @@ const depedentTags = {
dt: ['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({
css: false,
+1 -1
View File
@@ -1,7 +1,7 @@
import { moment, ObjectId } from 'hydrooj';
import { AnsiUp } from 'ansi_up';
export { size, formatSeconds } from 'hydrooj';
export { formatSeconds, size } from 'hydrooj';
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)));
} 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[]) {
@@ -47,7 +47,7 @@ nunjucks.runtime.memberLookup = function memberLookup(obj, val) {
if (obj === undefined || obj === null) return undefined;
if (typeof obj[val] === 'function') {
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];
return fn;
@@ -255,7 +255,7 @@ export class TemplateService extends Service {
const pending = Object.values(global.addons);
const logger = this.ctx.logger('template');
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()) {
try {
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,
entry: {
[`hydro-${version}`]: './entry.js',
'sentry': './sentry.ts',
sentry: './sentry.ts',
'default.theme': './theme/default.js',
'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 { size } from '@hydrooj/utils/lib/utils';
import cac from 'cac';
@@ -106,7 +105,7 @@ async function main() {
classNamePrefix: 'icon',
fontName: 'hydro-icons',
css: true,
startUnicode: 0xea01,
startUnicode: 0xEA01,
svg2ttf: {
timestamp: 1577836800, // 2020-1-1
},
@@ -118,7 +118,6 @@ export default class DOMAttachedObject {
}
constructor($dom: JQuery<HTMLElement>, monitorDetach = false) {
// eslint-disable-next-line no-constructor-return
if ($dom == null) return null;
this.$dom = $dom;
this.id = ++DOMAttachedObject.uniqueIdCounter;
@@ -2,7 +2,7 @@ import { CustomSelectAutoComplete as CustomSelectAutoCompleteFC } from '@hydrooj
import AutoComplete, { AutoCompleteOptions } from '.';
interface CustomSelectOptions {
data: any[]
data: any[];
}
export default class CustomSelectAutoComplete<Multi extends boolean> extends AutoComplete<CustomSelectOptions> {
@@ -19,7 +19,7 @@ export interface AutoCompleteOptions<Multi extends boolean = boolean> {
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';
ref = null;
container = document.createElement('div');
@@ -215,7 +215,7 @@ export default class Calendar {
const daysByWeek = _.chunk(days, 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 endDate = _.last(days).date.clone();
@@ -268,7 +268,7 @@ export default class Calendar {
]))
.map((banners) => {
const dayBitmap = _
.fill(new Array(7), 1)
.fill(Array.from({ length: 7 }), 1)
.map(() => []);
banners.forEach((banner) => {
const beginDay = banner.beginAt.day();
@@ -292,7 +292,7 @@ export default class Calendar {
// merge adjacent cells and arrange banners by vertical index
const vMaxLength = _.max(_.range(0, 7).map((day) => dayBitmap[day].length));
const weekBanners = _
.fill(new Array(vMaxLength), 1)
.fill(Array.from({ length: vMaxLength }), 1)
.map(() => []);
for (let vIndex = 0; vIndex < vMaxLength; ++vIndex) {
let last = { span: 1, banner: dayBitmap[0][vIndex] };
@@ -27,12 +27,14 @@ export default new AutoloadPage('customFont', (pageName) => {
if (!dialog) {
const fonts = await getAvailableFonts();
dialog = new ActionDialog({
$body: $(tpl(<CustomSelectAutoComplete
$body: $(tpl(
<CustomSelectAutoComplete
ref={(el) => { handle = el; }}
allowEmptyQuery={false}
data={fonts}
renderItem={(item) => <div style={{ fontFamily: item.id }}>{item.name}</div>}
/>, true)),
/>, true,
)),
onDispatch: (action) => {
selection = handle.getSelectedItems()?.[0]?.id;
return action !== 'ok' || selection;
@@ -144,24 +144,29 @@ export async function prompt<T extends string>(title: string, fields: Record<T,
<div className="textbox-container">
{field.type === 'text'
? <input
type="text" className="textbox" data-autofocus={field.autofocus}
type="text"
className="textbox"
data-autofocus={field.autofocus}
defaultValue={field.default}
onChange={(e) => setValues({ ...values, [name]: e.target.value })}
/>
: field.type === 'user'
? <UserSelectAutoComplete
data-autofocus={field.autofocus} selectedKeys={values[name] ? [values[name].toString()] : []}
data-autofocus={field.autofocus}
selectedKeys={values[name] ? [values[name].toString()] : []}
onChange={(e) => setValues({ ...values, [name]: +e })}
byId
/>
: <DomainSelectAutoComplete
data-autofocus={field.autofocus} selectedKeys={values[name] ? [values[name]] : []}
data-autofocus={field.autofocus}
selectedKeys={values[name] ? [values[name]] : []}
onChange={(e) => setValues({ ...values, [name]: e })}
/>}
</div>
</label>}
{field.type === 'checkbox' && <label className="checkbox">
<input type="checkbox"
<input
type="checkbox"
defaultChecked={field.default === 'true'}
onChange={(e) => setValues({ ...values, [name]: !!e.target.checked })}
/>
@@ -62,8 +62,8 @@ function Reaction({ payload, ele }) {
<input name="emojiSuggest" onFocus={() => updateFocus(true)} onBlur={() => updateFocus(false)}></input>
</div>
</div>
</div>
}>
</div>}
>
<span className="icon icon-emoji"></span>
</Popover>
);
@@ -169,9 +169,9 @@ export default class Editor extends DOMAttachedObject {
const [val, setVal] = React.useState(value);
that.setMarkdownEditorValue = setVal;
return <MdEditor
className='textbox'
className="textbox"
autoFocus={hasFocus}
codeTheme='github'
codeTheme="github"
codeStyleReverse={false}
ref={renderCallback}
value={val}
@@ -73,7 +73,7 @@ function addInvisibles(grammar) {
for (const name in grammar) {
if (Object.prototype.hasOwnProperty.call(grammar, name) && !invisibles[name]) {
if (name === 'rest') addInvisibles(grammar.rest);
else handlerInvisiblesToken(grammar, name); // eslint-disable-line @typescript-eslint/no-use-before-define
else handlerInvisiblesToken(grammar, name); // eslint-disable-line ts/no-use-before-define
}
}
}
@@ -23,7 +23,6 @@ class Marker {
}
constructor() {
// eslint-disable-next-line no-constructor-return
if (Marker.exists()) return markerInstance;
if (markerInstance) {
markerInstance.destroy();
Loaded 100 of 141 files, more files were not shown because too many files have changed in this diff. Show more