diff --git a/web/config/next/lib/addWebpackPlugin.ts b/web/config/next/lib/addWebpackPlugin.ts index 6ea4bf3611..ff4e9f10ee 100644 --- a/web/config/next/lib/addWebpackPlugin.ts +++ b/web/config/next/lib/addWebpackPlugin.ts @@ -1,4 +1,3 @@ -/* eslint-disable @typescript-eslint/no-explicit-any */ import type { NextConfig } from 'next' import type { WebpackConfigContext } from 'next/dist/server/config-shared' import type { Compiler, Configuration, WebpackPluginFunction, WebpackPluginInstance } from 'webpack' @@ -7,6 +6,7 @@ import { addWebpackConfig } from './addWebpackConfig' export function addWebpackPlugin( nextConfig: NextConfig, + // eslint-disable-next-line @typescript-eslint/no-redundant-type-constituents,@typescript-eslint/no-explicit-any plugin: WebpackPluginInstance | WebpackPluginFunction | ((this: Compiler, compiler: Compiler) => void) | any, ) { return addWebpackConfig( diff --git a/web/config/next/withCopy.ts b/web/config/next/withCopy.ts index cc067ed860..6256f97232 100644 --- a/web/config/next/withCopy.ts +++ b/web/config/next/withCopy.ts @@ -1,4 +1,5 @@ import { NextConfig } from 'next' +// eslint-disable-next-line import/default import CopyPlugin, { PluginOptions as CopyPluginOptions } from 'copy-webpack-plugin' import { addWebpackPlugin } from './lib/addWebpackPlugin' diff --git a/web/config/next/withFriendlyConsole.ts b/web/config/next/withFriendlyConsole.ts index 872e6f317f..44ce414391 100644 --- a/web/config/next/withFriendlyConsole.ts +++ b/web/config/next/withFriendlyConsole.ts @@ -18,8 +18,8 @@ function cleanup() { function stripProjectRoot(projectRoot: string) { return (error: FriendlyErrorsWebpackPluginError) => ({ ...error, - message: error && error.message && error.message.replace(`${projectRoot}/`, ''), - file: error && error.file && error.file.replace(`${projectRoot}/`, ''), + message: error?.message?.replace(`${projectRoot}/`, ''), + file: error?.file?.replace(`${projectRoot}/`, ''), }) } diff --git a/web/config/next/withRobotsTxt.ts b/web/config/next/withRobotsTxt.ts index c20cffefe1..fc66f79abf 100644 --- a/web/config/next/withRobotsTxt.ts +++ b/web/config/next/withRobotsTxt.ts @@ -1,6 +1,6 @@ import { NextConfig } from 'next' -import { addWebpackPlugin } from './lib/addWebpackPlugin' import EmitFilePlugin from 'emit-file-webpack-plugin' +import { addWebpackPlugin } from './lib/addWebpackPlugin' export const getWithRobotsTxt = (content: string) => (nextConfig: NextConfig) => { return addWebpackPlugin( diff --git a/web/config/next/withTypeChecking.ts b/web/config/next/withTypeChecking.ts index df47b6869b..4966dc37b8 100644 --- a/web/config/next/withTypeChecking.ts +++ b/web/config/next/withTypeChecking.ts @@ -2,10 +2,10 @@ import path from 'path' import ForkTsCheckerWebpackPlugin from 'fork-ts-checker-webpack-plugin' import type { NextConfig } from 'next' -import { addWebpackPlugin } from './lib/addWebpackPlugin' import { findModuleRoot } from '../../lib/findModuleRoot' import tsConfig from '../../tsconfig.json' +import { addWebpackPlugin } from './lib/addWebpackPlugin' const { moduleRoot } = findModuleRoot() diff --git a/web/eslint.config.mjs b/web/eslint.config.mjs index 300a3f33ea..1856d8cfca 100644 --- a/web/eslint.config.mjs +++ b/web/eslint.config.mjs @@ -1,324 +1,323 @@ -import { fixupConfigRules, fixupPluginRules } from "@eslint/compat"; -import arrayFunc from "eslint-plugin-array-func"; -import cflint from "eslint-plugin-cflint"; -import _import from "eslint-plugin-import"; -import jest from "eslint-plugin-jest"; -import jsxA11Y from "eslint-plugin-jsx-a11y"; -import lodash from "eslint-plugin-lodash"; -import noLoops from "eslint-plugin-no-loops"; -import noSecrets from "eslint-plugin-no-secrets"; -import node from "eslint-plugin-node"; -import onlyAscii from "eslint-plugin-only-ascii"; -import promise from "eslint-plugin-promise"; -import react from "eslint-plugin-react"; -import reactHooks from "eslint-plugin-react-hooks"; -import reactPerf from "eslint-plugin-react-perf"; -import security from "eslint-plugin-security"; -import sonarjs from "eslint-plugin-sonarjs"; -import unicorn from "eslint-plugin-unicorn"; -import onlyWarn from "eslint-plugin-only-warn"; -import typescriptEslint from "@typescript-eslint/eslint-plugin"; -import prettier from "eslint-plugin-prettier"; -import globals from "globals"; -import tsParser from "@typescript-eslint/parser"; -import path from "node:path"; -import { fileURLToPath } from "node:url"; -import js from "@eslint/js"; -import { FlatCompat } from "@eslint/eslintrc"; - -const __filename = fileURLToPath(import.meta.url); -const __dirname = path.dirname(__filename); +import { fixupConfigRules, fixupPluginRules } from '@eslint/compat' +import arrayFunc from 'eslint-plugin-array-func' +import cflint from 'eslint-plugin-cflint' +import importPlugin from 'eslint-plugin-import' +import jsxA11Y from 'eslint-plugin-jsx-a11y' +import noLoops from 'eslint-plugin-no-loops' +import noSecrets from 'eslint-plugin-no-secrets' +import node from 'eslint-plugin-node' +import onlyAscii from 'eslint-plugin-only-ascii' +import promise from 'eslint-plugin-promise' +import react from 'eslint-plugin-react' +import reactPerf from 'eslint-plugin-react-perf' +import security from 'eslint-plugin-security' +import sonarjs from 'eslint-plugin-sonarjs' +import unicorn from 'eslint-plugin-unicorn' +import onlyWarn from 'eslint-plugin-only-warn' +import globals from 'globals' +import tsParser from '@typescript-eslint/parser' +import path from 'node:path' +import { fileURLToPath } from 'node:url' +import js from '@eslint/js' +import tseslint from 'typescript-eslint' +import { FlatCompat } from '@eslint/eslintrc' + +const __filename = fileURLToPath(import.meta.url) +const __dirname = path.dirname(__filename) const compat = new FlatCompat({ - baseDirectory: __dirname, - recommendedConfig: js.configs.recommended, - allConfig: js.configs.all -}); + baseDirectory: __dirname, + recommendedConfig: js.configs.recommended, + allConfig: js.configs.all, +}) -export default [{ +export default [ + { ignores: [ - "**/3rdparty", - "**/.build", - "**/.cache", - "**/.env", - "**/.eslintrc.js", - "**/.github", - "**/.idea", - "**/.ignore", - "**/.reports", - "**/.vscode", - "config/next/lib/EmitFilePlugin.js", - "infra/lambda-at-edge/basicAuth.js", - "**/node_modules", - "**/public", - "**/styles", - "**/tsconfig.json", + '**/3rdparty', + '**/.build', + '**/.next', + '**/.coverage', + '**/.playwright-report', + '**/.cache', + '**/.env', + '**/.github', + '**/.idea', + '**/.ignore', + '**/.reports', + '**/.vscode', + 'infra/lambda-at-edge/basicAuth.js', + '**/node_modules', + '**/public', + '**/styles', + '**/tsconfig.json', ], -}, ...fixupConfigRules(compat.extends( - "eslint:recommended", - "airbnb", - "airbnb-typescript", - "airbnb/hooks", - "react-app", - "next/core-web-vitals", - "plugin:@typescript-eslint/eslint-recommended", - "plugin:@typescript-eslint/recommended", - "plugin:@typescript-eslint/recommended-requiring-type-checking", - "plugin:array-func/all", - "plugin:import/errors", - "plugin:import/typescript", - "plugin:import/warnings", - "plugin:jest/recommended", - "plugin:jest/style", - "plugin:jsx-a11y/recommended", - "plugin:lodash/recommended", - "plugin:promise/recommended", - "plugin:react/recommended", - "plugin:react-perf/all", - "plugin:security/recommended", - "plugin:sonarjs/recommended", - "plugin:unicorn/recommended", - "plugin:prettier/recommended", -)), { + }, + js.configs.recommended, + ...tseslint.configs.recommendedTypeChecked, + ...tseslint.configs.stylisticTypeChecked, + arrayFunc.configs.all, + // TODO: there is an issue with sonar that should soon be fixed: https://community.sonarsource.com/t/eslint-plugin-sonarjs-doesn-t-work-with-eslint-9-15-0/130771 + // once this is fixed, uncomment the line below and remove sonarjs from the plugins. See https://github.com/hodcroftlab/covariants/issues/439 + // sonarjs.configs.recommended, + security.configs.recommended, + unicorn.configs['flat/recommended'], + reactPerf.configs.flat.all, + react.configs.flat.recommended, + promise.configs['flat/recommended'], + importPlugin.flatConfigs.errors, + importPlugin.flatConfigs.warnings, + importPlugin.flatConfigs.typescript, + jsxA11Y['flatConfigs'].recommended, + + ...fixupConfigRules( + compat.extends( + 'plugin:@next/eslint-plugin-next/core-web-vitals', + 'plugin:lodash/recommended', + 'plugin:prettier/recommended', + 'plugin:react-hooks/recommended', + ), + ), + { plugins: { - "array-func": fixupPluginRules(arrayFunc), - cflint, - import: fixupPluginRules(_import), - jest: fixupPluginRules(jest), - "jsx-a11y": fixupPluginRules(jsxA11Y), - lodash: fixupPluginRules(lodash), - "no-loops": noLoops, - "no-secrets": noSecrets, - node, - "only-ascii": onlyAscii, - promise: fixupPluginRules(promise), - react: fixupPluginRules(react), - "react-hooks": fixupPluginRules(reactHooks), - "react-perf": fixupPluginRules(reactPerf), - security: fixupPluginRules(security), - sonarjs: fixupPluginRules(sonarjs), - unicorn: fixupPluginRules(unicorn), - "only-warn": onlyWarn, - "@typescript-eslint": fixupPluginRules(typescriptEslint), - prettier: fixupPluginRules(prettier), + 'array-func': arrayFunc, + 'cflint': fixupPluginRules(cflint), + 'no-loops': noLoops, + 'no-secrets': noSecrets, + node, + 'only-ascii': onlyAscii, + sonarjs, + 'only-warn': onlyWarn, }, linterOptions: { - reportUnusedDisableDirectives: true, + reportUnusedDisableDirectives: true, }, languageOptions: { - globals: { - ...globals.browser, - ...globals.jest, - ...globals.node, + globals: { + ...globals.browser, + ...globals.node, + }, + + parser: tsParser, + ecmaVersion: 'latest', + sourceType: 'module', + + parserOptions: { + ecmaFeatures: { + jsx: true, }, - parser: tsParser, - ecmaVersion: "latest", - sourceType: "module", - - parserOptions: { - ecmaFeatures: { - jsx: true, - globalReturn: false, - }, - - project: ["/home/simon/eth/covariants/web/tsconfig.eslint.json"], - tsconfigRootDir: "/home/simon/eth/covariants/web", - warnOnUnsupportedTypeScriptVersion: true, - }, + project: ['./tsconfig.eslint.json'], + warnOnUnsupportedTypeScriptVersion: true, + }, }, settings: { - react: { - version: "detect", - }, + 'react': { + version: 'detect', + }, - "import/parsers": { - "@typescript-eslint/parser": [".js", ".jsx", ".ts", ".tsx"], - }, + 'import/parsers': { + '@typescript-eslint/parser': ['.js', '.jsx', '.ts', '.tsx'], + }, - "import/resolver": { - typescript: { - alwaysTryTypes: true, - }, + 'import/resolver': { + typescript: { + alwaysTryTypes: true, }, + }, }, rules: { - "@next/next/no-title-in-document-head": "off", - "@typescript-eslint/array-type": "off", - "@typescript-eslint/explicit-function-return-type": "off", - "@typescript-eslint/explicit-module-boundary-types": "off", - "@typescript-eslint/lines-between-class-members": "off", - "@typescript-eslint/naming-convention": "off", - "@typescript-eslint/no-empty-function": "off", - "@typescript-eslint/no-shadow": "off", - "@typescript-eslint/unbound-method": ["off"], - "array-func/prefer-array-from": "off", - camelcase: "warn", - "cflint/no-substr": "warn", - "cflint/no-this-assignment": "warn", - - "import/extensions": ["warn", "ignorePackages", { - js: "never", - jsx: "never", - mjs: "never", - ts: "never", - tsx: "never", - }], - - "import/no-extraneous-dependencies": ["warn", { - devDependencies: true, - }], - - "import/no-webpack-loader-syntax": "off", - "import/no-cycle": "off", - "import/order": "warn", - "import/prefer-default-export": "off", - "jest/consistent-test-it": "warn", - "jest/expect-expect": "warn", - "jest/no-done-callback": "warn", - - "jsx-a11y/label-has-associated-control": ["warn", { - assert: "either", - }], - - "lodash/chaining": "off", - "lodash/import-scope": "off", - "lodash/prefer-constant": "off", - "lodash/prefer-lodash-chain": "off", - "lodash/prefer-lodash-method": "off", - "lodash/prefer-lodash-typecheck": "off", - "lodash/prefer-noop": "off", - "lodash/prop-shorthand": "off", - "max-classes-per-file": "off", - - "no-console": ["warn", { - allow: ["info", "warn", "error"], - }], - - "no-loops/no-loops": "warn", + '@next/next/no-title-in-document-head': 'off', + '@typescript-eslint/array-type': 'off', + '@typescript-eslint/explicit-function-return-type': 'off', + '@typescript-eslint/explicit-module-boundary-types': 'off', + '@typescript-eslint/lines-between-class-members': 'off', + '@typescript-eslint/naming-convention': 'off', + '@typescript-eslint/no-empty-function': 'off', + '@typescript-eslint/no-shadow': 'off', + '@typescript-eslint/restrict-template-expressions': 'off', + '@typescript-eslint/unbound-method': ['off'], + 'camelcase': 'warn', + 'cflint/no-substr': 'warn', + 'cflint/no-this-assignment': 'warn', + + 'import/extensions': [ + 'warn', + 'ignorePackages', + { + js: 'never', + jsx: 'never', + mjs: 'never', + ts: 'never', + tsx: 'never', + }, + ], - "no-param-reassign": ["warn", { - ignorePropertyModificationsFor: ["draft"], - }], + 'import/no-extraneous-dependencies': [ + 'warn', + { + devDependencies: true, + }, + ], - "no-secrets/no-secrets": ["warn", { - tolerance: 5, - }], + 'import/no-webpack-loader-syntax': 'off', + 'import/no-cycle': 'off', + 'import/order': 'warn', + 'import/prefer-default-export': 'off', - "no-shadow": "off", - "only-ascii/only-ascii": "warn", - "prefer-for-of": "off", - "prettier/prettier": "warn", - "react/jsx-curly-brace-presence": "off", + 'jsx-a11y/label-has-associated-control': [ + 'warn', + { + assert: 'either', + }, + ], + + 'lodash/chaining': 'off', + 'lodash/import-scope': 'off', + 'lodash/prefer-constant': 'off', + 'lodash/prefer-lodash-chain': 'off', + 'lodash/prefer-lodash-method': 'off', + 'lodash/prefer-lodash-typecheck': 'off', + 'lodash/prefer-noop': 'off', + 'lodash/prop-shorthand': 'off', + 'max-classes-per-file': 'off', + + 'no-console': [ + 'warn', + { + allow: ['info', 'warn', 'error'], + }, + ], - "react/jsx-filename-extension": ["warn", { - extensions: [".js", ".jsx", ".ts", ".tsx"], - }], + 'no-loops/no-loops': 'warn', - "react/jsx-props-no-spreading": "off", - "react/no-unused-prop-types": "off", - "react/prop-types": "off", - "react/require-default-props": "off", - "react/state-in-constructor": "off", - "security/detect-non-literal-fs-filename": "off", - "security/detect-object-injection": "off", - "sonarjs/cognitive-complexity": ["warn", 20], - "unicorn/escape-case": "off", - "unicorn/filename-case": "off", - "unicorn/new-for-builtins": "off", - "unicorn/no-abusive-eslint-disable": "warn", - "unicorn/no-array-callback-reference": "off", - "unicorn/no-array-for-each": "off", - "unicorn/no-array-reduce": "off", - "unicorn/no-fn-reference-in-iterator": "off", - "unicorn/no-null": "off", - "unicorn/no-reduce": "off", - "unicorn/no-useless-undefined": "off", - "unicorn/no-zero-fractions": "off", - "unicorn/prefer-node-protocol": "off", - "unicorn/prefer-query-selector": "off", - "unicorn/prefer-spread": "off", - "unicorn/prevent-abbreviations": "off", + 'no-param-reassign': [ + 'warn', + { + ignorePropertyModificationsFor: ['draft'], + }, + ], - "lines-between-class-members": ["warn", "always", { - exceptAfterSingleLine: true, - }], + 'no-secrets/no-secrets': [ + 'warn', + { + tolerance: 5, + }, + ], + + 'no-shadow': 'off', + 'only-ascii/only-ascii': 'warn', + 'prefer-for-of': 'off', + 'prettier/prettier': 'warn', + 'react/jsx-curly-brace-presence': 'off', + + 'react/jsx-filename-extension': [ + 'warn', + { + extensions: ['.js', '.jsx', '.ts', '.tsx'], + }, + ], + + 'react/jsx-props-no-spreading': 'off', + 'react/no-unused-prop-types': 'off', + 'react/prop-types': 'off', + 'react/require-default-props': 'off', + 'react/state-in-constructor': 'off', + 'security/detect-non-literal-fs-filename': 'off', + 'security/detect-object-injection': 'off', + 'sonarjs/cognitive-complexity': ['warn', 20], + 'unicorn/escape-case': 'off', + 'unicorn/filename-case': 'off', + 'unicorn/new-for-builtins': 'off', + 'unicorn/no-abusive-eslint-disable': 'warn', + 'unicorn/no-array-callback-reference': 'off', + 'unicorn/no-array-for-each': 'off', + 'unicorn/no-array-reduce': 'off', + 'unicorn/no-fn-reference-in-iterator': 'off', + 'unicorn/no-null': 'off', + 'unicorn/no-reduce': 'off', + 'unicorn/no-useless-undefined': 'off', + 'unicorn/no-zero-fractions': 'off', + 'unicorn/prefer-node-protocol': 'off', + 'unicorn/prefer-query-selector': 'off', + 'unicorn/prefer-spread': 'off', + 'unicorn/prevent-abbreviations': 'off', + + 'lines-between-class-members': [ + 'warn', + 'always', + { + exceptAfterSingleLine: true, + }, + ], - "require-await": "off", - "@typescript-eslint/require-await": "off", - "no-unused-expressions": "off", - "@typescript-eslint/no-unused-expressions": "warn", - "@typescript-eslint/no-duplicate-imports": "off", + 'require-await': 'off', + '@typescript-eslint/require-await': 'off', + 'no-unused-expressions': 'off', + '@typescript-eslint/no-unused-expressions': 'warn', + '@typescript-eslint/no-duplicate-imports': 'off', }, -}, { - files: ["src/pages/**/*", "src/types/**/*"], + }, + { + files: ['config/**/*'], rules: { - "no-restricted-exports": "off", + '@typescript-eslint/no-unused-vars': 'off', }, -}, { - files: ["**/*.d.ts"], + }, + { + files: ['src/pages/**/*', 'src/types/**/*'], rules: { - "@typescript-eslint/ban-types": ["warn", { - extendDefaults: true, - - types: { - object: false, - }, - }], - - "@typescript-eslint/no-explicit-any": "off", - "@typescript-eslint/no-unused-vars": "off", - "import/no-duplicates": "off", - "no-useless-constructor": "off", - "react/prefer-stateless-function": "off", + 'no-restricted-exports': 'off', }, -}, { - files: ["!src/**/*"], + }, + { + files: ['**/*.d.ts'], rules: { - "@typescript-eslint/no-unsafe-argument": "off", - "@typescript-eslint/no-unsafe-assignment": "off", - "@typescript-eslint/no-unsafe-call": "off", - "@typescript-eslint/no-unsafe-member-access": "off", - "@typescript-eslint/no-unsafe-return": "off", - "@typescript-eslint/no-var-requires": "off", - "@typescript-eslint/restrict-template-expressions": "off", - "global-require": "off", - "import/extensions": "off", - "import/no-anonymous-default-export": "off", - "import/no-import-module-exports": "off", - "security/detect-child-process": "off", - "sonarjs/cognitive-complexity": ["warn", 50], - "unicorn/prefer-module": "off", + 'no-unused-vars': 'off', + '@typescript-eslint/no-explicit-any': 'off', + '@typescript-eslint/no-unused-vars': 'off', + 'import/no-duplicates': 'off', + 'no-useless-constructor': 'off', + 'react/prefer-stateless-function': 'off', }, -}, { - files: ["config/jest/mocks/**/*.js"], + }, + { + files: ['!src/**/*'], rules: { - "no-constructor-return": "off", - "react/display-name": "off", + '@typescript-eslint/no-unsafe-argument': 'off', + '@typescript-eslint/no-unsafe-assignment': 'off', + '@typescript-eslint/no-unsafe-call': 'off', + '@typescript-eslint/no-unsafe-member-access': 'off', + '@typescript-eslint/no-unsafe-return': 'off', + '@typescript-eslint/no-var-requires': 'off', + '@typescript-eslint/restrict-template-expressions': 'off', + 'global-require': 'off', + 'import/extensions': 'off', + 'import/no-anonymous-default-export': 'off', + 'import/no-import-module-exports': 'off', + 'security/detect-child-process': 'off', + 'sonarjs/cognitive-complexity': ['warn', 50], + 'unicorn/prefer-module': 'off', }, -}, { - files: [ - "**/*.test.*", - "**/__test__/**", - "**/__tests__/**", - "**/test/**", - "**/tests/**", - ], + }, + { + files: ['**/*.test.*', '**/__test__/**', '**/__tests__/**', '**/test/**', '**/tests/**'], rules: { - "@typescript-eslint/no-unsafe-assignment": "off", - "@typescript-eslint/no-unsafe-call": "off", - "@typescript-eslint/no-unsafe-member-access": "off", - "@typescript-eslint/no-unsafe-return": "off", - "@typescript-eslint/restrict-template-expressions": "off", - "sonarjs/no-duplicate-string": "off", - "sonarjs/no-identical-functions": "off", + '@typescript-eslint/no-unsafe-assignment': 'error', + '@typescript-eslint/no-unsafe-call': 'off', + '@typescript-eslint/no-unsafe-member-access': 'off', + '@typescript-eslint/no-unsafe-return': 'off', + '@typescript-eslint/restrict-template-expressions': 'off', + 'sonarjs/no-duplicate-string': 'off', + 'sonarjs/no-identical-functions': 'off', }, -}]; \ No newline at end of file + }, +] diff --git a/web/lib/findModuleRoot.js b/web/lib/findModuleRoot.js index 062552a3f4..960c9b7dea 100644 --- a/web/lib/findModuleRoot.js +++ b/web/lib/findModuleRoot.js @@ -1,8 +1,8 @@ -import fs from 'fs-extra' import path from 'path' import { fileURLToPath } from 'url' +import fs from 'fs-extra' -/* eslint-disable no-loops/no-loops,no-param-reassign,no-plusplus */ +/* eslint-disable no-loops/no-loops,no-param-reassign */ export function findModuleRoot(maxDepth = 10) { let moduleRoot = fileURLToPath(new URL('.', import.meta.url)) while (--maxDepth) { diff --git a/web/lib/getenv.js b/web/lib/getenv.js index 246cc0bcba..4ba737a7fb 100644 --- a/web/lib/getenv.js +++ b/web/lib/getenv.js @@ -3,7 +3,7 @@ import EnvVarError from './EnvVarError' function getenv(key, defaultValue) { const value = process.env[key] if (!value) { - if (typeof defaultValue !== 'undefined') { + if (defaultValue !== undefined) { return defaultValue } @@ -15,7 +15,7 @@ function getenv(key, defaultValue) { function getbool(key, defaultValue) { const value = process.env[key] if (!value) { - if (typeof defaultValue !== 'undefined') { + if (defaultValue !== undefined) { return defaultValue } diff --git a/web/next.config.ts b/web/next.config.ts index 6ecb82cccb..3465b0b5d6 100644 --- a/web/next.config.ts +++ b/web/next.config.ts @@ -1,5 +1,5 @@ -import { NextConfig } from 'next' import path from 'path' +import { NextConfig } from 'next' import { uniq } from 'lodash' @@ -143,7 +143,7 @@ const withMDX = getWithMDX({ const withFriendlyConsole = getWithFriendlyConsole({ clearConsole: false, projectRoot: path.resolve(moduleRoot), - packageName: pkg.name || 'web', + packageName: pkg.name ?? 'web', progressBarColor: '#6529ff', }) diff --git a/web/package.json b/web/package.json index e5e0d3eb49..82ed1c0ae8 100644 --- a/web/package.json +++ b/web/package.json @@ -53,12 +53,10 @@ "test:unit:nowatch": "vitest run src", "test:unit:coverage": "vitest src --coverage", "test:e2e": "playwright test", - "run:node": "cross-env NODE_ENV=development BABEL_ENV=development babel-node --config-file \"./babel-node.config.js\" --extensions '.ts'", - "run:node:prod": "cross-env NODE_ENV=production BABEL_ENV=production babel-node --config-file \"./babel-node.config.js\" --extensions '.ts'", - "stills": "yarn run:node tools/generateStillImages.ts", - "i18n:extract": "yarn i18next -c config/i18next/i18next.config.js", - "i18n:addkeys": "babel-node --config-file \"./babel-node.config.js\" --extensions \".ts\" tools/addLocaleKeys.ts", - "i18n:fix": "babel-node --config-file \"./babel-node.config.js\" --extensions \".ts\" tools/fixLocales.ts", + "stills": "tsx tools/generateStillImages.ts", + "i18n:extract": "i18next -c config/i18next/i18next.config.js", + "i18n:addkeys": "tsx tools/addLocaleKeys.ts", + "i18n:fix": "tsx tools/fixLocales.ts", "i18n:translate": "json-autotranslate --config=json-autotranslate.json --input=src/i18n/resources --service=amazon-translate --matcher=i18next --fix-inconsistencies --delete-unused-strings" }, "dependencies": { @@ -184,10 +182,10 @@ "@types/use-sync-external-store": "^0.0.6", "@types/webpack": "^5.28.5", "@types/webpackbar": "^4.0.6", - "@typescript-eslint/visitor-keys": "^8.17.0", "@typescript-eslint/eslint-plugin": "^8.15.0", "@typescript-eslint/parser": "^8.15.0", "@typescript-eslint/typescript-estree": "^8.15.0", + "@typescript-eslint/visitor-keys": "^8.17.0", "@vitejs/plugin-react": "^4.3.4", "@vitest/coverage-v8": "^2.1.8", "allow-methods": "^6.2.1", @@ -272,11 +270,16 @@ "set.prototype.tojson": "^0.1.1", "source-map-loader": "^5.0.0", "style-loader": "^4.0.0", + "stylelint": "^16.11.0", + "stylelint-config-sass-guidelines": "^12.1.0", + "stylelint-config-standard": "^36.0.1", "svgo": "^3.3.2", "ts-essentials": "^10.0.3", "ts-node": "^10.9.2", "ts-toolbelt": "^9.6.0", + "tsx": "^4.19.2", "typescript": "^5.6.3", + "typescript-eslint": "8.17.0", "url-loader": "^4.1.1", "utility-types": "^3.11.0", "vite-tsconfig-paths": "^5.1.3", diff --git a/web/src/components/Acknowledgements/AcknowledgementEpiIsl.tsx b/web/src/components/Acknowledgements/AcknowledgementEpiIsl.tsx index 11290c8f1d..db2a2da0e9 100644 --- a/web/src/components/Acknowledgements/AcknowledgementEpiIsl.tsx +++ b/web/src/components/Acknowledgements/AcknowledgementEpiIsl.tsx @@ -1,12 +1,12 @@ import React, { useMemo, useState, useCallback } from 'react' -import Axios from 'axios' -import get from 'lodash/get.js' +import axios from 'axios' +import get from 'lodash/get' import { useQuery } from '@tanstack/react-query' -import { AcknowledgementsError } from 'src/components/Acknowledgements/AcknowledgementsError' -import styled from 'styled-components' +import { styled } from 'styled-components' import { Popover as PopoverBase, PopoverBody as PopoverBodyBase, PopoverHeader as PopoverHeaderBase } from 'reactstrap' import { ThreeDots as ThreeDotsLoader } from 'react-loader-spinner' +import { AcknowledgementsError } from 'src/components/Acknowledgements/AcknowledgementsError' import { useTranslationSafe } from 'src/helpers/useTranslationSafe' export const Popover = styled(PopoverBase)` @@ -61,7 +61,7 @@ export function getEpiIslUrl(epiIsl: string) { } function getString(obj: unknown, objPath: string, defaultValue?: string): string { - const property: string | unknown = get(obj, objPath, defaultValue) + const property: unknown = get(obj, objPath, defaultValue) if (typeof property !== 'string') { return defaultValue ?? '-' } @@ -86,7 +86,7 @@ export function useQueryAcknowledgementData(epiIsl: string) { `Unable to fetch acknowledgements data from GISAID: Unable to construct request URL: EPI ISL is incorrect: "${epiIsl}"`, ) } - const res = await Axios.get(url) + const res = await axios.get(url) if (!res.data) { throw new Error( `Unable to fetch acknowledgements data from GISAID: request to URL "${url}" resulted in no data`, diff --git a/web/src/components/Acknowledgements/AcknowledgementsCard.tsx b/web/src/components/Acknowledgements/AcknowledgementsCard.tsx index 7a916f9c63..319c5b9da6 100644 --- a/web/src/components/Acknowledgements/AcknowledgementsCard.tsx +++ b/web/src/components/Acknowledgements/AcknowledgementsCard.tsx @@ -5,12 +5,12 @@ import { Oval as OvalLoader } from 'react-loader-spinner' import { useQuery } from '@tanstack/react-query' import PaginationComponent from 'react-reactstrap-pagination' import { CardBody } from 'reactstrap' +import { styled } from 'styled-components' import { AcknowledgementEpiIsl } from 'src/components/Acknowledgements/AcknowledgementEpiIsl' import { AcknowledgementsError } from 'src/components/Acknowledgements/AcknowledgementsError' import { CardCollapsible } from 'src/components/Common/CardCollapsible' import type { ClusterDatum } from 'src/io/getClusters' -import styled from 'styled-components' export interface AcknowledgementsKeysDatum { numChunks: number diff --git a/web/src/components/Acknowledgements/AcknowledgementsError.tsx b/web/src/components/Acknowledgements/AcknowledgementsError.tsx index 6b0ca944eb..151c81cfc8 100644 --- a/web/src/components/Acknowledgements/AcknowledgementsError.tsx +++ b/web/src/components/Acknowledgements/AcknowledgementsError.tsx @@ -1,7 +1,7 @@ import React from 'react' import { UncontrolledAlert } from 'reactstrap' -import get from 'lodash/get.js' +import get from 'lodash/get' import { LinkExternal } from 'src/components/Link/LinkExternal' import { URL_GITHUB } from 'src/constants' diff --git a/web/src/components/Acknowledgements/AcknowledgementsPage.tsx b/web/src/components/Acknowledgements/AcknowledgementsPage.tsx index 57e4456698..cd2f9b6333 100644 --- a/web/src/components/Acknowledgements/AcknowledgementsPage.tsx +++ b/web/src/components/Acknowledgements/AcknowledgementsPage.tsx @@ -1,20 +1,19 @@ import axios from 'axios' import React, { useMemo } from 'react' -import get from 'lodash/get.js' +import get from 'lodash/get' import { Oval as OvalLoader } from 'react-loader-spinner' import { useQuery } from '@tanstack/react-query' -import { AcknowledgementsError } from 'src/components/Acknowledgements/AcknowledgementsError' -import styled from 'styled-components' +import { styled } from 'styled-components' import { Col, Container, Row } from 'reactstrap' +import { PageHeading } from '../Common/PageHeading' +import AcknowledgementsContent from './AcknowledgementsContent.md' +import { AcknowledgementsError } from 'src/components/Acknowledgements/AcknowledgementsError' import { getClusters } from 'src/io/getClusters' import { Layout } from 'src/components/Layout/Layout' import { AcknowledgementsCard, AcknowledgementsKeysJson } from 'src/components/Acknowledgements/AcknowledgementsCard' -import AcknowledgementsContent from './AcknowledgementsContent.md' -import { PageHeading } from '../Common/PageHeading' - export const AcknowledgementsPageContainer = styled(Container)` max-width: 1200px; padding: 0 0.5rem; diff --git a/web/src/components/Cases/CasesPage.tsx b/web/src/components/Cases/CasesPage.tsx index ec076b09b1..ce4353eafe 100644 --- a/web/src/components/Cases/CasesPage.tsx +++ b/web/src/components/Cases/CasesPage.tsx @@ -1,6 +1,7 @@ import React, { useCallback, useMemo } from 'react' import { Col, Row } from 'reactstrap' import { useRecoilState } from 'recoil' +import { CasesPlotCard } from './CasesPlotCard' import { CenteredEditable, Editable } from 'src/components/Common/Editable' import { ColCustom } from 'src/components/Common/ColCustom' import { Layout } from 'src/components/Layout/Layout' @@ -21,7 +22,6 @@ import { CountryFlag } from 'src/components/Common/CountryFlag' import { PageHeading } from 'src/components/Common/PageHeading' import { SharingPanel } from 'src/components/Common/SharingPanel' import { DistributionSidebar } from 'src/components/DistributionSidebar/DistributionSidebar' -import { CasesPlotCard } from './CasesPlotCard' const enabledFilters = ['clusters', 'countriesWithIcons'] diff --git a/web/src/components/Cases/CasesPlot.tsx b/web/src/components/Cases/CasesPlot.tsx index a4a1a6f4c5..febc76fa4f 100644 --- a/web/src/components/Cases/CasesPlot.tsx +++ b/web/src/components/Cases/CasesPlot.tsx @@ -5,6 +5,8 @@ import dynamic from 'next/dynamic' import { AreaChart, Area, XAxis, YAxis, CartesianGrid, Tooltip, Label } from 'recharts' import { DateTime } from 'luxon' +import { useTheme } from 'styled-components' +import { CasesPlotTooltip } from './CasesPlotTooltip' import type { PerCountryCasesDistributionDatum } from 'src/io/getPerCountryCasesData' import { useTranslationSafe } from 'src/helpers/useTranslationSafe' import { ticks, timeDomain } from 'src/io/getParams' @@ -13,8 +15,6 @@ import { formatDateHumanely } from 'src/helpers/format' import { adjustTicks } from 'src/helpers/adjustTicks' import { PlotPlaceholder } from 'src/components/Common/PlotPlaceholder' import { ChartContainer } from 'src/components/Common/ChartContainer' -import { useTheme } from 'styled-components' -import { CasesPlotTooltip } from './CasesPlotTooltip' const CHART_MARGIN = { left: 10, top: 12, bottom: 6, right: 12 } const ALLOW_ESCAPE_VIEW_BOX = { x: false, y: true } diff --git a/web/src/components/Cases/CasesPlotCard.tsx b/web/src/components/Cases/CasesPlotCard.tsx index a13f6f6a23..7477e7111b 100644 --- a/web/src/components/Cases/CasesPlotCard.tsx +++ b/web/src/components/Cases/CasesPlotCard.tsx @@ -1,7 +1,7 @@ /* eslint-disable camelcase */ import React from 'react' import { Card, CardBody, CardHeader, Col, Row } from 'reactstrap' -import styled from 'styled-components' +import { styled } from 'styled-components' import type { PerCountryCasesDistributionDatum } from 'src/io/getPerCountryCasesData' import { PlotCardTitle } from 'src/components/Common/PlotCardTitle' diff --git a/web/src/components/Cases/CasesPlotTooltip.tsx b/web/src/components/Cases/CasesPlotTooltip.tsx index ecceba224d..7cda4d26ab 100644 --- a/web/src/components/Cases/CasesPlotTooltip.tsx +++ b/web/src/components/Cases/CasesPlotTooltip.tsx @@ -1,13 +1,13 @@ import React from 'react' import { sortBy, reverse } from 'lodash' -import styled from 'styled-components' +import { styled } from 'styled-components' import { Props as DefaultTooltipContentProps } from 'recharts/types/component/DefaultTooltipContent' +import { ColoredBox } from '../Common/ColoredBox' import { useTranslationSafe } from 'src/helpers/useTranslationSafe' import { formatDateBiweekly, formatInteger, formatProportion } from 'src/helpers/format' import { getClusterColor } from 'src/io/getClusters' -import { ColoredBox } from '../Common/ColoredBox' const EPSILON = 1e-2 diff --git a/web/src/components/ClusterButtonPanel/ClusterButton.tsx b/web/src/components/ClusterButtonPanel/ClusterButton.tsx index 5c4e61b354..46591cd76a 100644 --- a/web/src/components/ClusterButtonPanel/ClusterButton.tsx +++ b/web/src/components/ClusterButtonPanel/ClusterButton.tsx @@ -1,9 +1,9 @@ /* eslint-disable camelcase */ import React from 'react' -import { ClusterDatum } from 'src/io/getClusters' -import styled from 'styled-components' +import { styled } from 'styled-components' import { Link } from '../Link/Link' +import { ClusterDatum } from 'src/io/getClusters' const ClusterButtonComponent = styled(Link)<{ $isCurrent: boolean; $color: string }>` display: flex; diff --git a/web/src/components/ClusterButtonPanel/ClusterButtonGroup.tsx b/web/src/components/ClusterButtonPanel/ClusterButtonGroup.tsx index ebf13d623f..ee59c17381 100644 --- a/web/src/components/ClusterButtonPanel/ClusterButtonGroup.tsx +++ b/web/src/components/ClusterButtonPanel/ClusterButtonGroup.tsx @@ -1,6 +1,6 @@ import React, { useMemo, useState } from 'react' -import styled from 'styled-components' +import { styled } from 'styled-components' import { Button } from 'reactstrap' import type { ClusterDatum } from 'src/io/getClusters' @@ -38,8 +38,7 @@ export interface ClusterButtonArrayProps { export function ClusterButtonOptional({ cluster, isCurrent, showNonImportant }: ClusterButtonArrayProps) { const shouldShow = useMemo( - // prettier-ignore - () => cluster.important || showNonImportant || isCurrent, + () => (cluster.important ?? false) || showNonImportant || isCurrent, [cluster.important, isCurrent, showNonImportant], ) @@ -59,7 +58,7 @@ export function ClusterButtonGroup({ clusterGroup, currentCluster }: ClusterButt const { t } = useTranslationSafe() const [showNonImportant, setShowNonImportant] = useState(false) - const toggleShowNonImportant = useMemo(() => (_: unknown) => setShowNonImportant(!showNonImportant), [showNonImportant]); // prettier-ignore + const toggleShowNonImportant = useMemo(() => () => setShowNonImportant(!showNonImportant), [showNonImportant]) return ( diff --git a/web/src/components/ClusterButtonPanel/ClusterButtonPanel.tsx b/web/src/components/ClusterButtonPanel/ClusterButtonPanel.tsx index 91e6f9fecb..ed6f107a26 100644 --- a/web/src/components/ClusterButtonPanel/ClusterButtonPanel.tsx +++ b/web/src/components/ClusterButtonPanel/ClusterButtonPanel.tsx @@ -1,8 +1,8 @@ import React from 'react' import { Card, CardBody, CardHeader, Row } from 'reactstrap' -import styled from 'styled-components' -import get from 'lodash/get.js' +import { styled } from 'styled-components' +import get from 'lodash/get' import { ClusterButtonGroup } from 'src/components/ClusterButtonPanel/ClusterButtonGroup' import { ClusterDatum, getClusters, getClustersGrouped } from 'src/io/getClusters' import { useTranslationSafe } from 'src/helpers/useTranslationSafe' diff --git a/web/src/components/ClusterButtonPanel/ClusterButtonPanelLayout.tsx b/web/src/components/ClusterButtonPanel/ClusterButtonPanelLayout.tsx index 3d144a8d0e..b31b9c583d 100644 --- a/web/src/components/ClusterButtonPanel/ClusterButtonPanelLayout.tsx +++ b/web/src/components/ClusterButtonPanel/ClusterButtonPanelLayout.tsx @@ -1,9 +1,9 @@ import React, { PropsWithChildren } from 'react' -import styled from 'styled-components' +import { styled } from 'styled-components' -import type { ClusterDatum } from 'src/io/getClusters' import { ClusterButtonPanel } from './ClusterButtonPanel' +import type { ClusterDatum } from 'src/io/getClusters' const FlexContainer = styled.div` display: flex; diff --git a/web/src/components/ClusterButtonPanel/__tests__/ClusterButtonGroup.test.tsx b/web/src/components/ClusterButtonPanel/__tests__/ClusterButtonGroup.test.tsx new file mode 100644 index 0000000000..5f8ad2de55 --- /dev/null +++ b/web/src/components/ClusterButtonPanel/__tests__/ClusterButtonGroup.test.tsx @@ -0,0 +1,73 @@ +/* eslint-disable camelcase */ +import { describe, expect, test } from 'vitest' +import { screen, fireEvent } from '@testing-library/react' +import React from 'react' +import { ClusterButtonGroup } from '../ClusterButtonGroup' +import { ClusterDatum } from 'src/io/getClusters' +import { renderWithThemeAndTranslations } from 'src/helpers/__tests__/theme' + +describe('ClusterButtonGroup', () => { + describe('show more / show less button', () => { + test('toggles unimportant clusters', () => { + // Arrange + const importantClusters: ClusterDatum[] = [ + { + build_name: 'foo', + col: 'bla', + display_name: 'foo', + snps: [1, 2, 3], + important: true, + }, + { + build_name: 'bar', + col: 'bla', + display_name: 'bar', + snps: [1, 2, 3], + important: true, + }, + { + build_name: 'badum', + col: 'bla', + display_name: 'badum', + snps: [1, 2, 3], + important: true, + }, + ] + const unimportantClusters: ClusterDatum[] = [ + { + build_name: 'baz', + col: 'bla', + display_name: 'baz', + snps: [1, 2, 3], + important: false, + }, + { + build_name: 'bun', + col: 'bla', + display_name: 'bun', + snps: [1, 2, 3], + }, + ] + const clusters = importantClusters.concat(unimportantClusters) + + // Assert + renderWithThemeAndTranslations() + let visibleClusters = screen.getAllByRole('link') + expect(visibleClusters.length).toEqual(importantClusters.length) + + // Act + fireEvent.click(screen.getByText('Show more')) + + // Assert + visibleClusters = screen.getAllByRole('link') + expect(visibleClusters.length).toEqual(clusters.length) + + // Act + fireEvent.click(screen.getByText('Show less')) + + // Assert + visibleClusters = screen.getAllByRole('link') + expect(visibleClusters.length).toEqual(importantClusters.length) + }) + }) +}) diff --git a/web/src/components/ClusterDistribution/ClusterDistributionPage.tsx b/web/src/components/ClusterDistribution/ClusterDistributionPage.tsx index 827ee460ea..4643ae8812 100644 --- a/web/src/components/ClusterDistribution/ClusterDistributionPage.tsx +++ b/web/src/components/ClusterDistribution/ClusterDistributionPage.tsx @@ -1,13 +1,13 @@ import React, { useCallback, useMemo } from 'react' import { Card, CardBody, Col, Form, FormGroup, Input, Label, Row } from 'reactstrap' import { useRecoilState } from 'recoil' +import { styled } from 'styled-components' import { SharingPanel } from 'src/components/Common/SharingPanel' import { disableAllClusters, enableAllClusters, toggleCluster } from 'src/state/Clusters' import { clustersForPerClusterDataAtom } from 'src/state/ClustersForPerClusterData' import { disableAllCountries, enableAllCountries, toggleContinent, toggleCountry } from 'src/state/Places' import { usePlacesPerCluster } from 'src/state/PlacesForPerClusterData' import { tooltipSortAtom, TooltipSortCriterion } from 'src/state/TooltipSort' -import styled from 'styled-components' import { MdxContent } from 'src/i18n/getMdxContent' import { useTranslationSafe } from 'src/helpers/useTranslationSafe' import { usePerClusterData, filterClusters, filterCountries } from 'src/io/getPerClusterData' @@ -124,7 +124,7 @@ export function ClusterDistributionPage() { ` width: 100px; diff --git a/web/src/components/Common/ChartContainer.tsx b/web/src/components/Common/ChartContainer.tsx index 3a4b096786..1c8dae31b2 100644 --- a/web/src/components/Common/ChartContainer.tsx +++ b/web/src/components/Common/ChartContainer.tsx @@ -2,11 +2,11 @@ import React, { useMemo } from 'react' import { useResizeDetector, useResizeDetectorProps } from 'react-resize-detector' import { useInView } from 'react-intersection-observer' -import { theme } from 'src/theme' import { ChartContainerInner, ChartContainerOuter } from './PlotLayout' import { FadeIn } from './FadeIn' +import { theme } from 'src/theme' -type ChartContainerDimensions = { +interface ChartContainerDimensions { width: number height: number } diff --git a/web/src/components/Common/Christmas.tsx b/web/src/components/Common/Christmas.tsx index 9bc6f9a084..aad05107e1 100644 --- a/web/src/components/Common/Christmas.tsx +++ b/web/src/components/Common/Christmas.tsx @@ -1,4 +1,4 @@ -import styled from 'styled-components' +import { styled } from 'styled-components' import React, { CSSProperties, HTMLProps, useMemo } from 'react' import { useResizeDetector } from 'react-resize-detector' import SnowfallBase from 'react-snowfall' @@ -38,7 +38,7 @@ function ChristmasLightRopeImpl(props: HTMLProps) { if (!width) { return null } - return [...Array(Math.ceil(width / (15 + 8)) + 1).keys()].map((i) =>
  • ) + return Array.from(Array(Math.ceil(width / (15 + 8)) + 1).keys(), (i) =>
  • ) }, [width]) return ( diff --git a/web/src/components/Common/ClusterSidebarLayout.tsx b/web/src/components/Common/ClusterSidebarLayout.tsx index a0b85a3d20..2436ef7ccd 100644 --- a/web/src/components/Common/ClusterSidebarLayout.tsx +++ b/web/src/components/Common/ClusterSidebarLayout.tsx @@ -1,4 +1,4 @@ -import styled from 'styled-components' +import { styled } from 'styled-components' export const NarrowPageContainer = styled.div` display: flex; diff --git a/web/src/components/Common/ColoredBox.tsx b/web/src/components/Common/ColoredBox.tsx index f639020f12..00de040a29 100644 --- a/web/src/components/Common/ColoredBox.tsx +++ b/web/src/components/Common/ColoredBox.tsx @@ -1,4 +1,4 @@ -import styled from 'styled-components' +import { styled } from 'styled-components' export const ColoredBox = styled.div<{ $color: string; $size: number; $aspect: number }>` display: inline-block; diff --git a/web/src/components/Common/ColoredCircle.tsx b/web/src/components/Common/ColoredCircle.tsx index d0b076448d..93c48d8a2d 100644 --- a/web/src/components/Common/ColoredCircle.tsx +++ b/web/src/components/Common/ColoredCircle.tsx @@ -1,4 +1,4 @@ -import styled from 'styled-components' +import { styled } from 'styled-components' export const ColoredCircle = styled.div<{ $color: string; $size: number }>` display: inline-block; diff --git a/web/src/components/Common/CountryFlag.tsx b/web/src/components/Common/CountryFlag.tsx index ad368c59fc..2fea57a4a3 100644 --- a/web/src/components/Common/CountryFlag.tsx +++ b/web/src/components/Common/CountryFlag.tsx @@ -4,6 +4,7 @@ import React, { FC, ReactElement, SVGProps, useMemo } from 'react' import iso3311a2 from 'iso-3166-1-alpha-2' import Flags from 'country-flag-icons/react/3x2' +import { FlagWrapper } from './FlagWrapper' import Africa from 'src/assets/images/continents/Africa.svg' import Asia from 'src/assets/images/continents/Asia.svg' import Europe from 'src/assets/images/continents/Europe.svg' @@ -11,8 +12,6 @@ import NorthAmerica from 'src/assets/images/continents/North America.svg' import Oceania from 'src/assets/images/continents/Oceania.svg' import SouthAmerica from 'src/assets/images/continents/South America.svg' -import { FlagWrapper } from './FlagWrapper' - export const missingCountryCodes: Record = { 'Bolivia': 'BO', 'Bonaire': 'BQ', @@ -77,6 +76,6 @@ export interface CountryFlagProps extends SVGProps { export function CountryFlag({ country, withFallback = false }: CountryFlagProps) { const Flag = useMemo(() => getFlagComponent(country, withFallback), [country, withFallback]) - // eslint-disable-next-line react/jsx-no-useless-fragment + return <>{Flag} } diff --git a/web/src/components/Common/Dropdown.tsx b/web/src/components/Common/Dropdown.tsx index dcac49e7ec..22808de93c 100644 --- a/web/src/components/Common/Dropdown.tsx +++ b/web/src/components/Common/Dropdown.tsx @@ -1,8 +1,7 @@ import React, { useCallback } from 'react' import Select from 'react-select' -import type { ActionMeta, OnChangeValue } from 'react-select' -import type { Props as StateManagerProps } from 'react-select' +import type { OnChangeValue, Props as StateManagerProps } from 'react-select' import { DropdownOption } from 'src/components/Common/DropdownOption' @@ -26,7 +25,7 @@ export function Dropdown({ ...restProps }: DropdownProps) { const handleChange = useCallback( - (option: OnChangeValue, IsMultiValue>, _actionMeta: ActionMeta>) => { + (option: OnChangeValue, IsMultiValue>) => { if (option) { onValueChange?.(option.value) onOptionChange?.(option) diff --git a/web/src/components/Common/Editable.tsx b/web/src/components/Common/Editable.tsx index 91e9cb1a16..9350b819ab 100644 --- a/web/src/components/Common/Editable.tsx +++ b/web/src/components/Common/Editable.tsx @@ -1,6 +1,6 @@ import React, { PropsWithChildren, useMemo } from 'react' -import styled from 'styled-components' +import { styled } from 'styled-components' import { FaGithub } from 'react-icons/fa' import { URL_GITHUB } from 'src/constants' diff --git a/web/src/components/Common/FadeIn.tsx b/web/src/components/Common/FadeIn.tsx index af4db8992b..10f8e37d96 100644 --- a/web/src/components/Common/FadeIn.tsx +++ b/web/src/components/Common/FadeIn.tsx @@ -1,7 +1,7 @@ // https://www.joshwcomeau.com/snippets/react-components/fade-in/ import React, { PropsWithChildren } from 'react' -import styled, { keyframes } from 'styled-components' +import { styled, keyframes } from 'styled-components' export interface FadeInProps { duration?: number diff --git a/web/src/components/Common/FlagWrapper.tsx b/web/src/components/Common/FlagWrapper.tsx index 6fef9f136c..57c3b42d35 100644 --- a/web/src/components/Common/FlagWrapper.tsx +++ b/web/src/components/Common/FlagWrapper.tsx @@ -1,4 +1,4 @@ -import styled from 'styled-components' +import { styled } from 'styled-components' export const FlagWrapper = styled.div` height: calc(1em + 2px); diff --git a/web/src/components/Common/LastUpdated.tsx b/web/src/components/Common/LastUpdated.tsx index c409f775f7..9dd6885fd1 100644 --- a/web/src/components/Common/LastUpdated.tsx +++ b/web/src/components/Common/LastUpdated.tsx @@ -1,5 +1,5 @@ import React, { HTMLProps, useMemo } from 'react' -import styled from 'styled-components' +import { styled } from 'styled-components' import classNames from 'classnames' import { getLastUpdatedDate, getLastUpdatedFull } from 'src/io/getLastUpdatedDate' diff --git a/web/src/components/Common/LinkOpenInNewTab.tsx b/web/src/components/Common/LinkOpenInNewTab.tsx index 206d87580d..00d9ca55c5 100644 --- a/web/src/components/Common/LinkOpenInNewTab.tsx +++ b/web/src/components/Common/LinkOpenInNewTab.tsx @@ -1,6 +1,6 @@ import React, { PropsWithChildren } from 'react' -import styled from 'styled-components' +import { styled } from 'styled-components' import { GoLinkExternal } from 'react-icons/go' import { LinkExternal } from 'src/components/Link/LinkExternal' @@ -30,7 +30,7 @@ export interface LinkOpenInNewTabProps { text?: string } -export function LinkOpenInNewTab({ href, text, children, ...restProps }: PropsWithChildren) { +export function LinkOpenInNewTab({ href, text }: PropsWithChildren) { const { t } = useTranslationSafe() return ( diff --git a/web/src/components/Common/MdxComponents.tsx b/web/src/components/Common/MdxComponents.tsx index fcc0c63a4c..d7c15dbba6 100644 --- a/web/src/components/Common/MdxComponents.tsx +++ b/web/src/components/Common/MdxComponents.tsx @@ -1,5 +1,5 @@ import { MDXComponents } from 'mdx/types' -import styled from 'styled-components' +import { styled } from 'styled-components' import { AaMut, Lin, Mut, NucMut, Var, Who } from 'src/components/Common/MutationBadge' import { LinkSmart } from 'src/components/Link/LinkSmart' diff --git a/web/src/components/Common/MdxWrapper.tsx b/web/src/components/Common/MdxWrapper.tsx index 5abca6f037..db6f9f6550 100644 --- a/web/src/components/Common/MdxWrapper.tsx +++ b/web/src/components/Common/MdxWrapper.tsx @@ -1,6 +1,6 @@ import React from 'react' -import styled from 'styled-components' +import { styled } from 'styled-components' export const MdxSection = styled.section` margin: 10px 5px; diff --git a/web/src/components/Common/MutationBadge.tsx b/web/src/components/Common/MutationBadge.tsx index 96eaadca2a..060f2bce90 100644 --- a/web/src/components/Common/MutationBadge.tsx +++ b/web/src/components/Common/MutationBadge.tsx @@ -1,9 +1,9 @@ /* eslint-disable camelcase */ import React, { useMemo } from 'react' -import get from 'lodash/get.js' +import get from 'lodash/get' +import { styled } from 'styled-components' import { parseVariant } from 'src/components/Common/parseVariant' -import styled from 'styled-components' import type { Mutation, MutationColors } from 'src/types' import { theme } from 'src/theme' @@ -366,7 +366,7 @@ const whoRainbow = rainbow(Object.keys(GREEK_ALPHABET).length, { rgb: true, lum: export function getWhoBadgeColor(name: string): string { const i = Object.keys(GREEK_ALPHABET).indexOf(name.toLowerCase().trim()) - if (i < 0 || i > whoRainbow.length) { + if (i === -1 || i > whoRainbow.length) { return theme.gray500 } return whoRainbow[i] diff --git a/web/src/components/Common/NameTable.tsx b/web/src/components/Common/NameTable.tsx index b8537b0a20..4c659648ac 100644 --- a/web/src/components/Common/NameTable.tsx +++ b/web/src/components/Common/NameTable.tsx @@ -1,13 +1,13 @@ import React, { ReactNode, useMemo } from 'react' import { Table as TableBase, Thead, Tbody, Tr, Th, Td } from 'react-super-responsive-table' -import styled from 'styled-components' +import { styled } from 'styled-components' +import { LineageLinkBadge, Var, WhoBadge } from './MutationBadge' import { useTranslationSafe } from 'src/helpers/useTranslationSafe' import { LinkExternal } from 'src/components/Link/LinkExternal' import type { NameTableDatum, NameTableEntry } from 'src/io/getNameTable' import { NAME_TABLE } from 'src/io/getNameTable' -import { LineageLinkBadge, Var, WhoBadge } from './MutationBadge' const Table = styled(TableBase)` max-width: 800px; diff --git a/web/src/components/Common/NextstrainLogo.tsx b/web/src/components/Common/NextstrainLogo.tsx index 0e93e0be00..96030517e4 100644 --- a/web/src/components/Common/NextstrainLogo.tsx +++ b/web/src/components/Common/NextstrainLogo.tsx @@ -1,6 +1,6 @@ import React from 'react' -import styled from 'styled-components' +import { styled } from 'styled-components' import NextstrainIconBase from 'src/assets/images/nextstrain_logo.svg' @@ -42,7 +42,6 @@ export function NextstrainLogo() { {'Nextstrain'.split('').map((letter, i) => ( - // eslint-disable-next-line react/no-array-index-key {letter} diff --git a/web/src/components/Common/PageHeading.tsx b/web/src/components/Common/PageHeading.tsx index ceea17ae8d..dcbfbfc726 100644 --- a/web/src/components/Common/PageHeading.tsx +++ b/web/src/components/Common/PageHeading.tsx @@ -1,4 +1,4 @@ -import styled from 'styled-components' +import { styled } from 'styled-components' export const PageHeading = styled.h1` text-align: center; @@ -11,6 +11,6 @@ export const PageHeading = styled.h1` @media (min-width: 1120px) { font-size: 3rem; - margin-top 0; + margin-top: 0; } ` diff --git a/web/src/components/Common/Plausible.tsx b/web/src/components/Common/Plausible.tsx index b367644b60..b440559d29 100644 --- a/web/src/components/Common/Plausible.tsx +++ b/web/src/components/Common/Plausible.tsx @@ -33,5 +33,5 @@ export function Plausible({ domain, plausibleDomain = PLAUSIBLE_DOMAIN_DEFAULT } export type PlausibleHookResult = (event: string) => void export function usePlausible() { - return get(window, 'plausible', noop) as PlausibleHookResult + return get(globalThis, 'plausible', noop) as PlausibleHookResult } diff --git a/web/src/components/Common/PlotCardTitle.tsx b/web/src/components/Common/PlotCardTitle.tsx index aca64b38f3..476ac96ba2 100644 --- a/web/src/components/Common/PlotCardTitle.tsx +++ b/web/src/components/Common/PlotCardTitle.tsx @@ -1,4 +1,4 @@ -import styled from 'styled-components' +import { styled } from 'styled-components' export const PlotCardTitle = styled.h1` margin: auto; diff --git a/web/src/components/Common/PlotLayout.tsx b/web/src/components/Common/PlotLayout.tsx index 00eff46326..2a05f4dc51 100644 --- a/web/src/components/Common/PlotLayout.tsx +++ b/web/src/components/Common/PlotLayout.tsx @@ -1,4 +1,4 @@ -import styled from 'styled-components' +import { styled } from 'styled-components' export const WrapperFlex = styled.section` display: flex; diff --git a/web/src/components/Common/PlotPlaceholder.tsx b/web/src/components/Common/PlotPlaceholder.tsx index 2531ce8acf..9de4c48702 100644 --- a/web/src/components/Common/PlotPlaceholder.tsx +++ b/web/src/components/Common/PlotPlaceholder.tsx @@ -1,11 +1,11 @@ import React from 'react' -import styled from 'styled-components' +import { styled } from 'styled-components' import { ThreeDots as ThreeDotsLoader } from 'react-loader-spinner' import { ResponsiveContainer } from 'recharts' -import { theme } from 'src/theme' import { ChartContainerOuter, ChartContainerInner } from './PlotLayout' +import { theme } from 'src/theme' const LoadingSpinner = styled(ThreeDotsLoader)` display: flex; diff --git a/web/src/components/Common/PoweredBy.tsx b/web/src/components/Common/PoweredBy.tsx index d7fea4f92f..6e1e3a6d19 100644 --- a/web/src/components/Common/PoweredBy.tsx +++ b/web/src/components/Common/PoweredBy.tsx @@ -1,12 +1,12 @@ import React from 'react' import { Col, Row } from 'reactstrap' -import styled from 'styled-components' +import { styled } from 'styled-components' +import { NextstrainLogo } from './NextstrainLogo' import { PROJECT_NAME, TEAM_NAME } from 'src/constants' import { useTranslationSafe } from 'src/helpers/useTranslationSafe' import { LinkExternal } from 'src/components/Link/LinkExternal' import VercelLogo from 'src/assets/images/vercel_logo.svg' import NextJsLogo from 'src/assets/images/nextjs_logo.svg' -import { NextstrainLogo } from './NextstrainLogo' const Flex = styled.section` max-width: 700px; diff --git a/web/src/components/Common/SeoApp.tsx b/web/src/components/Common/SeoApp.tsx index e7ce690c83..93e2fb8d73 100644 --- a/web/src/components/Common/SeoApp.tsx +++ b/web/src/components/Common/SeoApp.tsx @@ -3,7 +3,7 @@ import React, { useMemo } from 'react' import Head from 'next/head' import { useRouter } from 'next/router' import urljoin from 'url-join' -import get from 'lodash/get.js' +import get from 'lodash/get' import { useRecoilValue } from 'recoil' import { Helmet } from 'react-helmet' import { DOMAIN, SOCIAL_IMAGE_HEIGHT, SOCIAL_IMAGE_WIDTH, TWITTER_USERNAME_FRIENDLY } from 'src/constants' diff --git a/web/src/components/Common/SharingPanel.tsx b/web/src/components/Common/SharingPanel.tsx index 9d63fe0cac..e0a096521e 100644 --- a/web/src/components/Common/SharingPanel.tsx +++ b/web/src/components/Common/SharingPanel.tsx @@ -2,7 +2,7 @@ import React, { useCallback, useMemo, useState } from 'react' import urljoin from 'url-join' import { useRouter } from 'next/router' -import styled from 'styled-components' +import { styled } from 'styled-components' import { Button, Col, Row } from 'reactstrap' import { CopyToClipboard } from 'react-copy-to-clipboard' import { FaClipboard as FaClipboardBase, FaClipboardCheck as FaClipboardCheckBase } from 'react-icons/fa' diff --git a/web/src/components/Common/TableSlim.tsx b/web/src/components/Common/TableSlim.tsx index 671e0e0c79..4bc966e917 100644 --- a/web/src/components/Common/TableSlim.tsx +++ b/web/src/components/Common/TableSlim.tsx @@ -1,7 +1,7 @@ import React, { HTMLProps } from 'react' import { Table as ReactstrapTable } from 'reactstrap' -import styled from 'styled-components' +import { styled } from 'styled-components' export const TableSlim = styled(ReactstrapTable)` & td { diff --git a/web/src/components/Common/TeamCredits.tsx b/web/src/components/Common/TeamCredits.tsx index 2c4fcddf31..4eb4226e3f 100644 --- a/web/src/components/Common/TeamCredits.tsx +++ b/web/src/components/Common/TeamCredits.tsx @@ -1,7 +1,7 @@ import React from 'react' import { Col, Row } from 'reactstrap' -import styled from 'styled-components' +import { styled } from 'styled-components' import { FaGithub, FaTwitter } from 'react-icons/fa' import { LinkExternal } from 'src/components/Link/LinkExternal' diff --git a/web/src/components/Common/TeamCreditsContributor.tsx b/web/src/components/Common/TeamCreditsContributor.tsx index 22500fab6e..6f790a0c64 100644 --- a/web/src/components/Common/TeamCreditsContributor.tsx +++ b/web/src/components/Common/TeamCreditsContributor.tsx @@ -1,10 +1,9 @@ import React from 'react' import type { ContributorData } from 'json-loader!src/../../.all-contributorsrc' +import { styled } from 'styled-components' import { LinkExternal as LinkExternalBase } from 'src/components/Link/LinkExternal' -import styled from 'styled-components' - const FlexOuter = styled.section` display: flex; flex: 0 0 150px; diff --git a/web/src/components/Common/Toggle.tsx b/web/src/components/Common/Toggle.tsx index 432a5acb8d..9ffec6ea86 100644 --- a/web/src/components/Common/Toggle.tsx +++ b/web/src/components/Common/Toggle.tsx @@ -1,7 +1,7 @@ import React, { useCallback } from 'react' import type { StrictOmit } from 'ts-essentials' -import styled from 'styled-components' +import { styled } from 'styled-components' import ReactToggle, { ToggleProps as ReactToggleProps } from 'react-toggle' import 'react-toggle/style.css' @@ -34,7 +34,7 @@ export interface TogglePropsWithoutChildren extends StrictOmit -export function Toggle({ identifier, className, onCheckedChanged, children, ...props }: ToggleProps) { +export function Toggle({ identifier, onCheckedChanged, children, ...props }: ToggleProps) { const onChange = useCallback( (e: React.ChangeEvent) => { onCheckedChanged(e.target.checked) diff --git a/web/src/components/Common/ToggleTwoLabels.tsx b/web/src/components/Common/ToggleTwoLabels.tsx index 04d74d8517..0eabd6def4 100644 --- a/web/src/components/Common/ToggleTwoLabels.tsx +++ b/web/src/components/Common/ToggleTwoLabels.tsx @@ -1,6 +1,6 @@ import React, { ReactNode, useCallback } from 'react' -import styled from 'styled-components' +import { styled } from 'styled-components' import ReactToggle, { ToggleProps as ReactToggleProps } from 'react-toggle' import 'react-toggle/style.css' import { StrictOmit } from 'ts-essentials' diff --git a/web/src/components/Common/USStateCode.tsx b/web/src/components/Common/USStateCode.tsx index 7470ebbf38..c855ff3f4a 100644 --- a/web/src/components/Common/USStateCode.tsx +++ b/web/src/components/Common/USStateCode.tsx @@ -1,5 +1,5 @@ import React, { SVGProps } from 'react' -import styled from 'styled-components' +import { styled } from 'styled-components' import { FlagWrapper } from './FlagWrapper' diff --git a/web/src/components/Common/__tests__/parseAminoacidMutation.test.ts b/web/src/components/Common/__tests__/parseAminoacidMutation.test.ts index fe7b72afd7..1f0208ab40 100644 --- a/web/src/components/Common/__tests__/parseAminoacidMutation.test.ts +++ b/web/src/components/Common/__tests__/parseAminoacidMutation.test.ts @@ -1,5 +1,5 @@ -import { parseAminoacidMutation } from 'src/components/Common/parseAminoacidMutation' import { describe, it, expect } from 'vitest' +import { parseAminoacidMutation } from 'src/components/Common/parseAminoacidMutation' describe('parseAminoacidMutation', () => { it('should parse gene, ref, position, right', () => { diff --git a/web/src/components/Common/__tests__/parseNucleotideMutation.test.ts b/web/src/components/Common/__tests__/parseNucleotideMutation.test.ts index 97bee1e721..31710cee73 100644 --- a/web/src/components/Common/__tests__/parseNucleotideMutation.test.ts +++ b/web/src/components/Common/__tests__/parseNucleotideMutation.test.ts @@ -1,5 +1,5 @@ -import { parseNucleotideMutation } from 'src/components/Common/parseNucleotideMutation' import { describe, it, expect } from 'vitest' +import { parseNucleotideMutation } from 'src/components/Common/parseNucleotideMutation' describe('parseNucleotideMutation', () => { it('should parse left, position, right', () => { diff --git a/web/src/components/Common/__tests__/parseVariant.test.ts b/web/src/components/Common/__tests__/parseVariant.test.ts index c5cbb76053..42209ca3eb 100644 --- a/web/src/components/Common/__tests__/parseVariant.test.ts +++ b/web/src/components/Common/__tests__/parseVariant.test.ts @@ -1,5 +1,5 @@ -import { parseVariant } from 'src/components/Common/parseVariant' import { describe, it, expect } from 'vitest' +import { parseVariant } from 'src/components/Common/parseVariant' describe('parseVariant', () => { it('should accept "20A.EU2"', async () => { diff --git a/web/src/components/Common/parseAminoacidMutation.ts b/web/src/components/Common/parseAminoacidMutation.ts index 20317a93e7..e8a9013fe5 100644 --- a/web/src/components/Common/parseAminoacidMutation.ts +++ b/web/src/components/Common/parseAminoacidMutation.ts @@ -1,6 +1,6 @@ /* eslint-disable security/detect-unsafe-regex */ -import type { Mutation } from 'src/types' import { parsePosition } from './parsePosition' +import type { Mutation } from 'src/types' export function parseAminoacid(raw: string | undefined | null) { if (!raw || raw.length === 0) { diff --git a/web/src/components/Common/parseNucleotideMutation.ts b/web/src/components/Common/parseNucleotideMutation.ts index f28396258e..c647aea57d 100644 --- a/web/src/components/Common/parseNucleotideMutation.ts +++ b/web/src/components/Common/parseNucleotideMutation.ts @@ -1,5 +1,5 @@ -import type { Mutation } from 'src/types' import { parsePosition } from './parsePosition' +import type { Mutation } from 'src/types' export function parseNucleotide(raw: string | undefined | null) { if (!raw || raw.length === 0) { diff --git a/web/src/components/Common/parseVariant.ts b/web/src/components/Common/parseVariant.ts index 5a9e8bc6f6..fe856a80da 100644 --- a/web/src/components/Common/parseVariant.ts +++ b/web/src/components/Common/parseVariant.ts @@ -1,6 +1,6 @@ /* eslint-disable security/detect-unsafe-regex */ -import type { Mutation } from 'src/types' import { parsePosition } from './parsePosition' +import type { Mutation } from 'src/types' export function parseNonEmpty(raw: string | undefined | null) { if (!raw || raw.length === 0) { diff --git a/web/src/components/CountryDistribution/CountryDistributionPlot.tsx b/web/src/components/CountryDistribution/CountryDistributionPlot.tsx index 7fc2e7d32c..513bcf7d35 100644 --- a/web/src/components/CountryDistribution/CountryDistributionPlot.tsx +++ b/web/src/components/CountryDistribution/CountryDistributionPlot.tsx @@ -4,6 +4,7 @@ import React, { useMemo } from 'react' import { AreaChart, Area, XAxis, YAxis, CartesianGrid, Tooltip } from 'recharts' import { DateTime } from 'luxon' +import { CountryDistributionPlotTooltip } from './CountryDistributionPlotTooltip' import type { CountryDistributionDatum } from 'src/io/getPerCountryData' import { theme } from 'src/theme' import { ticks, timeDomain } from 'src/io/getParams' @@ -11,7 +12,6 @@ import { CLUSTER_NAME_OTHERS, getClusterColor } from 'src/io/getClusters' import { formatDateHumanely, formatProportion } from 'src/helpers/format' import { adjustTicks } from 'src/helpers/adjustTicks' import { ChartContainer } from 'src/components/Common/ChartContainer' -import { CountryDistributionPlotTooltip } from './CountryDistributionPlotTooltip' const allowEscapeViewBox = { x: false, y: true } diff --git a/web/src/components/CountryDistribution/CountryDistributionPlotCard.tsx b/web/src/components/CountryDistribution/CountryDistributionPlotCard.tsx index b99adb5015..03ba7288ed 100644 --- a/web/src/components/CountryDistribution/CountryDistributionPlotCard.tsx +++ b/web/src/components/CountryDistribution/CountryDistributionPlotCard.tsx @@ -1,7 +1,7 @@ /* eslint-disable camelcase */ import React from 'react' import { Card, CardBody, CardHeader, Col, Row } from 'reactstrap' -import styled from 'styled-components' +import { styled } from 'styled-components' import { useTranslationSafe } from 'src/helpers/useTranslationSafe' import type { CountryDistributionDatum } from 'src/io/getPerCountryData' diff --git a/web/src/components/CountryDistribution/CountryDistributionPlotTooltip.tsx b/web/src/components/CountryDistribution/CountryDistributionPlotTooltip.tsx index 804706a8ac..f249e0c8ba 100644 --- a/web/src/components/CountryDistribution/CountryDistributionPlotTooltip.tsx +++ b/web/src/components/CountryDistribution/CountryDistributionPlotTooltip.tsx @@ -1,13 +1,13 @@ import React from 'react' import { sortBy, reverse } from 'lodash' -import styled from 'styled-components' +import { styled } from 'styled-components' import { Props as DefaultTooltipContentProps } from 'recharts/types/component/DefaultTooltipContent' +import { ColoredBox } from '../Common/ColoredBox' import { useTranslationSafe } from 'src/helpers/useTranslationSafe' import { formatDateBiweekly, formatInteger, formatProportion } from 'src/helpers/format' import { getClusterColor } from 'src/io/getClusters' -import { ColoredBox } from '../Common/ColoredBox' const EPSILON = 1e-2 diff --git a/web/src/components/CountryDistribution/RegionSwitcher.tsx b/web/src/components/CountryDistribution/RegionSwitcher.tsx index d86418a262..8169e601db 100644 --- a/web/src/components/CountryDistribution/RegionSwitcher.tsx +++ b/web/src/components/CountryDistribution/RegionSwitcher.tsx @@ -1,8 +1,8 @@ import React, { useCallback } from 'react' import { Button, Col, Row } from 'reactstrap' +import { styled } from 'styled-components' import { safeZip } from 'src/helpers/safeZip' -import styled from 'styled-components' import { useTranslationSafe } from 'src/helpers/useTranslationSafe' export const RegionSwitcherContainer = styled.div` diff --git a/web/src/components/DistributionSidebar/ClusterFilters.tsx b/web/src/components/DistributionSidebar/ClusterFilters.tsx index 23714d1da9..6bd5cfb3c8 100644 --- a/web/src/components/DistributionSidebar/ClusterFilters.tsx +++ b/web/src/components/DistributionSidebar/ClusterFilters.tsx @@ -12,11 +12,11 @@ import { Row, } from 'reactstrap' +import { styled } from 'styled-components' import type { Cluster } from 'src/state/Clusters' import { getClusterColor } from 'src/io/getClusters' import { ColoredBox } from 'src/components/Common/ColoredBox' import { CardCollapsible } from 'src/components/Common/CardCollapsible' -import styled from 'styled-components' import { useTranslationSafe } from 'src/helpers/useTranslationSafe' export const FormGroup = styled(FormGroupBase)` diff --git a/web/src/components/DistributionSidebar/CountryFilters.tsx b/web/src/components/DistributionSidebar/CountryFilters.tsx index fc133db245..4cab7d67f3 100644 --- a/web/src/components/DistributionSidebar/CountryFilters.tsx +++ b/web/src/components/DistributionSidebar/CountryFilters.tsx @@ -11,13 +11,13 @@ import { Label, Row, } from 'reactstrap' +import { styled, useTheme } from 'styled-components' +import { ColoredHorizontalLineIcon } from '../Common/ColoredHorizontalLineIcon' import { Continent, Country } from 'src/state/Places' -import styled, { useTheme } from 'styled-components' import { useTranslationSafe } from 'src/helpers/useTranslationSafe' import type { CountryFlagProps } from 'src/components/Common/CountryFlag' import { getCountryColor, getCountryStrokeDashArray } from 'src/io/getCountryColor' import { CardCollapsible } from 'src/components/Common/CardCollapsible' -import { ColoredHorizontalLineIcon } from '../Common/ColoredHorizontalLineIcon' export const CardBody = styled(CardBodyBase)`` diff --git a/web/src/components/DistributionSidebar/DistributionSidebar.tsx b/web/src/components/DistributionSidebar/DistributionSidebar.tsx index 91a91079a0..3e8dcc43c1 100644 --- a/web/src/components/DistributionSidebar/DistributionSidebar.tsx +++ b/web/src/components/DistributionSidebar/DistributionSidebar.tsx @@ -1,14 +1,13 @@ -import get from 'lodash/get.js' +import get from 'lodash/get' import React, { useState, useMemo } from 'react' import { Col, Row } from 'reactstrap' +import { CountryFlagProps } from '../Common/CountryFlag' +import { ClusterFilters } from './ClusterFilters' +import { CountryFilters } from './CountryFilters' import { Cluster } from 'src/state/Clusters' import type { Continent, Country } from 'src/state/Places' import { sortClusters } from 'src/io/getClusters' -import { ClusterFilters } from './ClusterFilters' -import { CountryFilters } from './CountryFilters' - -import { CountryFlagProps } from '../Common/CountryFlag' export interface DistributionSidebarProps { countries: Country[] @@ -49,7 +48,7 @@ export function DistributionSidebar({ const [countriesCollapsed, setCountriesCollapsed] = useState(countriesCollapsedByDefault) const clustersSorted = useMemo(() => sortClusters(clusters ?? []), [clusters]) - const availableFilters: { [key: string]: React.ReactNode } = useMemo( + const availableFilters: Record = useMemo( () => ({ countries: ( - {/* eslint-disable-next-line jsx-a11y/anchor-is-valid */} {children} ) diff --git a/web/src/components/Link/LinkExternal.tsx b/web/src/components/Link/LinkExternal.tsx index 7c4c10b107..2eacffc085 100644 --- a/web/src/components/Link/LinkExternal.tsx +++ b/web/src/components/Link/LinkExternal.tsx @@ -1,6 +1,6 @@ import React, { PropsWithChildren, HTMLProps } from 'react' -import styled from 'styled-components' +import { styled } from 'styled-components' import { GoLinkExternal } from 'react-icons/go' const LinkExternalIconWrapper = styled.span<{ $color?: string }>` diff --git a/web/src/components/Link/LinkTwitter.tsx b/web/src/components/Link/LinkTwitter.tsx index 11bfdd1474..f5deb2de9f 100644 --- a/web/src/components/Link/LinkTwitter.tsx +++ b/web/src/components/Link/LinkTwitter.tsx @@ -1,6 +1,6 @@ import React, { PropsWithChildren } from 'react' -import styled from 'styled-components' +import { styled } from 'styled-components' import { FaTwitterSquare } from 'react-icons/fa' import type { LinkExternalProps } from './LinkExternal' @@ -16,7 +16,7 @@ export interface LinkTwitterProps extends LinkExternalProps { iconSize?: number } -export function LinkTwitter({ username, children, iconSize = 20, ...restProps }: PropsWithChildren) { +export function LinkTwitter({ username, iconSize = 20, ...restProps }: PropsWithChildren) { const href = `https://twitter.com/${username}` const text = `@${username}` diff --git a/web/src/components/Loading/Loading.tsx b/web/src/components/Loading/Loading.tsx index 675d7a9546..82ff3019f3 100644 --- a/web/src/components/Loading/Loading.tsx +++ b/web/src/components/Loading/Loading.tsx @@ -1,7 +1,7 @@ import React, { useEffect, useState } from 'react' +import { styled } from 'styled-components' import LogoNextstrain from 'src/assets/images/logo.svg' -import styled from 'styled-components' const Container = styled.div` display: flex; diff --git a/web/src/components/MutationCounts/MutationCountsSummaryCard.tsx b/web/src/components/MutationCounts/MutationCountsSummaryCard.tsx index 124e589da9..85b174c1ca 100644 --- a/web/src/components/MutationCounts/MutationCountsSummaryCard.tsx +++ b/web/src/components/MutationCounts/MutationCountsSummaryCard.tsx @@ -2,13 +2,13 @@ import React, { useMemo } from 'react' import { useQuery } from '@tanstack/react-query' import { Row, Col, CardHeader, Card, CardBody } from 'reactstrap' +import { styled } from 'styled-components' import { LinkExternal } from 'src/components/Link/LinkExternal' import type { ClusterDatum } from 'src/io/getClusters' import { getMutationCounts, MutationCountsDatum, MutationCountsGeneRecord } from 'src/io/getMutationCounts' import { AminoacidMutationBadge } from 'src/components/Common/MutationBadge' import { TableSlim } from 'src/components/Common/TableSlim' -import styled from 'styled-components' import { useTranslationSafe } from 'src/helpers/useTranslationSafe' const MutationCountsSummaryCardBody = styled(CardBody)` diff --git a/web/src/components/SharedMutations/SharedMutations.tsx b/web/src/components/SharedMutations/SharedMutations.tsx index 4d8de1f47e..31c64135b7 100644 --- a/web/src/components/SharedMutations/SharedMutations.tsx +++ b/web/src/components/SharedMutations/SharedMutations.tsx @@ -1,6 +1,6 @@ import React, { useMemo, useState } from 'react' -import styled from 'styled-components' +import { styled } from 'styled-components' import { useTranslationSafe } from 'src/helpers/useTranslationSafe' import type { MutationShared } from 'src/io/getMutationComparison' diff --git a/web/src/components/SharedMutations/SharedMutationsPage.tsx b/web/src/components/SharedMutations/SharedMutationsPage.tsx index 8032af1c5c..4da4623ec6 100644 --- a/web/src/components/SharedMutations/SharedMutationsPage.tsx +++ b/web/src/components/SharedMutations/SharedMutationsPage.tsx @@ -1,14 +1,14 @@ import React from 'react' import { Col, Container, Row } from 'reactstrap' -import styled from 'styled-components' +import { styled } from 'styled-components' +import { PageHeading } from '../Common/PageHeading' +import { SharedMutations } from './SharedMutations' import { useTranslationSafe } from 'src/helpers/useTranslationSafe' import { MdxContent } from 'src/i18n/getMdxContent' import { Editable, CenteredEditable } from 'src/components/Common/Editable' import { Layout } from 'src/components/Layout/Layout' -import { SharedMutations } from './SharedMutations' -import { PageHeading } from '../Common/PageHeading' export const SharedMutationsPageContainer = styled(Container)` max-width: 1200px; diff --git a/web/src/components/Variants/AquariaLinksCard.tsx b/web/src/components/Variants/AquariaLinksCard.tsx index 753f608413..0332f56327 100644 --- a/web/src/components/Variants/AquariaLinksCard.tsx +++ b/web/src/components/Variants/AquariaLinksCard.tsx @@ -1,6 +1,6 @@ import React, { useMemo } from 'react' -import styled from 'styled-components' +import { styled } from 'styled-components' import { Card, CardBody, CardHeader, Col, Row } from 'reactstrap' diff --git a/web/src/components/Variants/CladeSchema.tsx b/web/src/components/Variants/CladeSchema.tsx index 2be24b8c38..48522b8ae1 100644 --- a/web/src/components/Variants/CladeSchema.tsx +++ b/web/src/components/Variants/CladeSchema.tsx @@ -1,8 +1,7 @@ import React from 'react' +import { styled } from 'styled-components' import { LinkExternal } from 'src/components/Link/LinkExternal' -import styled from 'styled-components' - import CladeSchemaSvg from 'src/assets/images/clades.svg' import { useTranslationSafe } from 'src/helpers/useTranslationSafe' diff --git a/web/src/components/Variants/DefiningMutations.tsx b/web/src/components/Variants/DefiningMutations.tsx index f711561ca9..2522a40141 100644 --- a/web/src/components/Variants/DefiningMutations.tsx +++ b/web/src/components/Variants/DefiningMutations.tsx @@ -1,11 +1,11 @@ import React from 'react' -import styled from 'styled-components' +import { styled } from 'styled-components' +import { Row, Col } from 'reactstrap' import type { ClusterDatum } from 'src/io/getClusters' import { useTranslationSafe } from 'src/helpers/useTranslationSafe' import { AminoacidMutationBadge, NucleotideMutationBadge } from 'src/components/Common/MutationBadge' -import { Row, Col } from 'reactstrap' const Container = styled.div` width: 100%; diff --git a/web/src/components/Variants/PlotCard.tsx b/web/src/components/Variants/PlotCard.tsx index 0c4fa65612..8d5a56b8b4 100644 --- a/web/src/components/Variants/PlotCard.tsx +++ b/web/src/components/Variants/PlotCard.tsx @@ -1,15 +1,14 @@ import React, { useMemo } from 'react' -import { useClusterDistribution, useCountryNames } from 'src/io/getPerClusterData' - -import styled from 'styled-components' +import { styled } from 'styled-components' import { GoGraph } from 'react-icons/go' import { Card, CardBody, Col, Row } from 'reactstrap' +import { Link } from '../Link/Link' +import { useClusterDistribution, useCountryNames } from 'src/io/getPerClusterData' import { useTranslationSafe } from 'src/helpers/useTranslationSafe' import { theme } from 'src/theme' import { ClusterDistributionPlot } from 'src/components/ClusterDistribution/ClusterDistributionPlot' import { ClusterDatum } from 'src/io/getClusters' -import { Link } from '../Link/Link' const PlotCardTitleIcon = styled(GoGraph)` margin: auto 5px; diff --git a/web/src/components/Variants/ProteinCard.tsx b/web/src/components/Variants/ProteinCard.tsx index 6ed28ae8f8..be95326629 100644 --- a/web/src/components/Variants/ProteinCard.tsx +++ b/web/src/components/Variants/ProteinCard.tsx @@ -1,6 +1,6 @@ import React, { useCallback, useEffect, useMemo, useState } from 'react' -import styled from 'styled-components' +import { styled } from 'styled-components' import { SiMoleculer } from 'react-icons/si' import { Card, CardBody, CardHeader, Col, Container, Row } from 'reactstrap' import Image from 'next/image' diff --git a/web/src/components/Variants/VariantTitle.tsx b/web/src/components/Variants/VariantTitle.tsx index fb7fe795dc..065aa1fb1b 100644 --- a/web/src/components/Variants/VariantTitle.tsx +++ b/web/src/components/Variants/VariantTitle.tsx @@ -1,9 +1,8 @@ import React, { useMemo } from 'react' +import { styled } from 'styled-components' import { useTranslationSafe } from 'src/helpers/useTranslationSafe' import { ClusterDatum } from 'src/io/getClusters' -import styled from 'styled-components' - const VariantTitleWrapper = styled.header` text-align: center; min-height: 90px; diff --git a/web/src/components/Variants/VariantsPage.tsx b/web/src/components/Variants/VariantsPage.tsx index 55cc12e5e5..d637d410ef 100644 --- a/web/src/components/Variants/VariantsPage.tsx +++ b/web/src/components/Variants/VariantsPage.tsx @@ -1,10 +1,13 @@ import React, { useMemo } from 'react' import { useRouter } from 'next/router' +import { styled } from 'styled-components' +import { Col, Row } from 'reactstrap' +import { PlotCard } from './PlotCard' +import { AquariaLinksCard } from './AquariaLinksCard' +import { ProteinCard } from './ProteinCard' import { ClusterButtonPanelLayout } from 'src/components/ClusterButtonPanel/ClusterButtonPanelLayout' import { MutationCountsSummaryCard } from 'src/components/MutationCounts/MutationCountsSummaryCard' -import styled from 'styled-components' -import { Col, Row } from 'reactstrap' import { theme } from 'src/theme' import { MdxContent } from 'src/i18n/getMdxContent' @@ -20,10 +23,6 @@ import { VariantTitle } from 'src/components/Variants/VariantTitle' import NextstrainIconBase from 'src/assets/images/nextstrain_logo.svg' -import { PlotCard } from './PlotCard' -import { AquariaLinksCard } from './AquariaLinksCard' -import { ProteinCard } from './ProteinCard' - const clusters = getClusters() const clusterRedirects = getClusterRedirects() @@ -65,7 +64,7 @@ export function useCurrentClusterName(clusterName?: string) { if (clusterName) { const clusterNewName = clusterRedirects.get(clusterName) if (clusterNewName) { - void router.replace(`/variants/${clusterNewName}`) // eslint-disable-line no-void + void router.replace(`/variants/${clusterNewName}`) return clusterNewName } } diff --git a/web/src/components/Variants/VariantsPageIndex.tsx b/web/src/components/Variants/VariantsPageIndex.tsx index e88512c3d0..057e2ec45f 100644 --- a/web/src/components/Variants/VariantsPageIndex.tsx +++ b/web/src/components/Variants/VariantsPageIndex.tsx @@ -2,13 +2,13 @@ import React from 'react' import { Col, Row } from 'reactstrap' +import { ClusterButtonPanelLayout } from '../ClusterButtonPanel/ClusterButtonPanelLayout' import { MdxContent } from 'src/i18n/getMdxContent' import { useTranslationSafe } from 'src/helpers/useTranslationSafe' import { NarrowPageContainer } from 'src/components/Common/ClusterSidebarLayout' import { Editable } from 'src/components/Common/Editable' import { Layout } from 'src/components/Layout/Layout' import { PageHeading } from 'src/components/Common/PageHeading' -import { ClusterButtonPanelLayout } from '../ClusterButtonPanel/ClusterButtonPanelLayout' export function VariantsPageIndex() { const { t } = useTranslationSafe() diff --git a/web/src/constants.ts b/web/src/constants.ts index 246c635f61..49dd60e6c0 100644 --- a/web/src/constants.ts +++ b/web/src/constants.ts @@ -1,8 +1,8 @@ -export const PROJECT_NAME = 'CoVariants' as const -export const PROJECT_DESCRIPTION = 'SARS-CoV-2 Mutations and Variants of Interest' as const -export const COPYRIGHT_YEAR_START = 2020 as const -export const COMPANY_NAME = 'Emma Hodcroft' as const -export const TEAM_NAME = 'hodcroftlab' as const +export const PROJECT_NAME = 'CoVariants' +export const PROJECT_DESCRIPTION = 'SARS-CoV-2 Mutations and Variants of Interest' +export const COPYRIGHT_YEAR_START = 2020 +export const COMPANY_NAME = 'Emma Hodcroft' +export const TEAM_NAME = 'hodcroftlab' export const DOMAIN = process.env.DOMAIN ?? '' export const DOMAIN_STRIPPED = process.env.DOMAIN_STRIPPED ?? '' @@ -11,11 +11,11 @@ export const SOCIAL_IMAGE_WIDTH = '1200' export const SOCIAL_IMAGE_HEIGHT = '630' export const URL_MANIFEST_JSON = `${DOMAIN}/manifest.json` -export const URL_GITHUB = 'https://github.com/hodcroftlab/covariants' as const -export const URL_GITHUB_FRIENDLY = 'github.com/hodcroftlab/covariants' as const +export const URL_GITHUB = 'https://github.com/hodcroftlab/covariants' +export const URL_GITHUB_FRIENDLY = 'github.com/hodcroftlab/covariants' export const URL_GITHUB_ISSUES = 'https://github.com/hodcroftlab/covariants/issues' -export const TWITTER_USERNAME_RAW = 'firefoxx66' as const +export const TWITTER_USERNAME_RAW = 'firefoxx66' export const TWITTER_USERNAME_FRIENDLY = `@${TWITTER_USERNAME_RAW}` export const TWITTER_HASHTAGS = [PROJECT_NAME] export const TWITTER_RELATED = [TWITTER_USERNAME_RAW] diff --git a/web/src/helpers/__tests__/theme.tsx b/web/src/helpers/__tests__/theme.tsx new file mode 100644 index 0000000000..8efc969a20 --- /dev/null +++ b/web/src/helpers/__tests__/theme.tsx @@ -0,0 +1,14 @@ +import React from 'react' +import { render } from '@testing-library/react' +import { ThemeProvider } from 'styled-components' +import { I18nextProvider } from 'react-i18next' +import { theme } from 'src/theme' +import i18n from 'src/i18n/i18n' + +export const renderWithThemeAndTranslations = (component: React.JSX.Element) => { + return render( + + {component} + , + ) +} diff --git a/web/src/helpers/colorHash.ts b/web/src/helpers/colorHash.ts index 79c91fad5b..8e2abd3e2b 100644 --- a/web/src/helpers/colorHash.ts +++ b/web/src/helpers/colorHash.ts @@ -1,4 +1,4 @@ -/* eslint-disable no-param-reassign,no-plusplus,no-loops/no-loops,prefer-destructuring,no-else-return,unicorn/prefer-code-point */ +/* eslint-disable no-param-reassign,no-loops/no-loops,unicorn/prefer-code-point */ /** * Color Hash @@ -88,7 +88,7 @@ function HSL2RGB(H: number, S: number, L: number): [number, number, number] { export { HSL2RGB as testFroHSL2RGB } -export type Options = { +export interface Options { lightness?: number | number[] saturation?: number | number[] hue?: number | { min: number; max: number } | { min: number; max: number }[] @@ -120,13 +120,13 @@ class ColorHash { if (typeof options.hue === 'object' && !Array.isArray(options.hue)) { options.hue = [options.hue] } - if (typeof options.hue === 'undefined') { + if (options.hue === undefined) { options.hue = [] } this.hueRanges = options.hue.map((range) => { return { - min: typeof range.min === 'undefined' ? 0 : range.min, - max: typeof range.max === 'undefined' ? 360 : range.max, + min: range.min ?? 0, + max: range.max ?? 360, } }) diff --git a/web/src/helpers/colorRainbow.ts b/web/src/helpers/colorRainbow.ts index d66ce0121b..d2cbbe31a3 100644 --- a/web/src/helpers/colorRainbow.ts +++ b/web/src/helpers/colorRainbow.ts @@ -1,4 +1,4 @@ -/* eslint-disable no-param-reassign,no-multi-assign,no-underscore-dangle,no-plusplus,no-loops/no-loops */ +/* eslint-disable no-param-reassign,no-loops/no-loops */ function hslToRgb(h: number, s: number, l: number) { let r @@ -34,7 +34,7 @@ export interface RainbowOptions { } export function rainbow(num: number, config?: RainbowOptions) { - const _config = config || { + const _config = config ?? { lum: 50, sat: 50, } diff --git a/web/src/helpers/errorPrototypeTojson.ts b/web/src/helpers/errorPrototypeTojson.ts index 90c3a06a56..09e1e4155c 100644 --- a/web/src/helpers/errorPrototypeTojson.ts +++ b/web/src/helpers/errorPrototypeTojson.ts @@ -1,4 +1,4 @@ -/* eslint-disable no-inner-declarations,cflint/no-this-assignment,@typescript-eslint/ban-ts-comment,no-extend-native */ +/* eslint-disable cflint/no-this-assignment,@typescript-eslint/ban-ts-comment */ // @ts-ignore import { appendDash } from './appendDash' diff --git a/web/src/helpers/functionPrototypeTojson.ts b/web/src/helpers/functionPrototypeTojson.ts index c254abde9d..5dd023c5e8 100644 --- a/web/src/helpers/functionPrototypeTojson.ts +++ b/web/src/helpers/functionPrototypeTojson.ts @@ -1,6 +1,7 @@ -/* eslint-disable @typescript-eslint/ban-ts-comment,no-inner-declarations,@typescript-eslint/ban-types,no-extend-native */ +/* eslint-disable @typescript-eslint/ban-ts-comment */ // @ts-ignore if (!Function.prototype.toJSON) { + // eslint-disable-next-line @typescript-eslint/no-unsafe-function-type function toJSON(this: Function) { return this.toString() } diff --git a/web/src/helpers/parseUrl.ts b/web/src/helpers/parseUrl.ts index a57ae96c10..4a951856cc 100644 --- a/web/src/helpers/parseUrl.ts +++ b/web/src/helpers/parseUrl.ts @@ -2,20 +2,20 @@ import { ParsedUrlQuery } from 'querystring' /** Borrowed from Next.js */ export function getLocationOrigin() { - const { protocol, hostname, port } = window.location - return `${protocol}//${hostname}${port ? `:${port}` : ''}` // eslint-disable-line sonarjs/no-nested-template-literals + const { protocol, hostname, port } = globalThis.location + return `${protocol}//${hostname}${port ? `:${port}` : ''}` } /** Borrowed from Next.js */ export function searchParamsToUrlQuery(searchParams: URLSearchParams): ParsedUrlQuery { const query: ParsedUrlQuery = {} searchParams.forEach((value, key) => { - if (typeof query[key] === 'undefined') { + if (query[key] === undefined) { query[key] = value } else if (Array.isArray(query[key])) { - ;(query[key] as string[]).push(value) + query[key].push(value) } else { - query[key] = [query[key] as string, value] + query[key] = [query[key], value] } }) return query @@ -23,7 +23,7 @@ export function searchParamsToUrlQuery(searchParams: URLSearchParams): ParsedUrl /** Borrowed from Next.js */ export function parseRelativeUrl(url: string, base?: string) { - const globalBase = new URL(typeof window === 'undefined' ? 'http://n' : getLocationOrigin()) + const globalBase = new URL(globalThis === undefined ? 'http://n' : getLocationOrigin()) const resolvedBase = base ? new URL(base, globalBase) : globalBase const { pathname, searchParams, search, hash, href, origin } = new URL(url, resolvedBase) if (origin !== globalBase.origin) { diff --git a/web/src/helpers/safeZip.ts b/web/src/helpers/safeZip.ts index fd1349b445..97f099f502 100644 --- a/web/src/helpers/safeZip.ts +++ b/web/src/helpers/safeZip.ts @@ -1,4 +1,4 @@ -import { zip } from 'lodash' +import zip from 'lodash/zip' export function safeZip(first: T[], second: U[]) { const firstLen = first.length diff --git a/web/src/helpers/setOperations.ts b/web/src/helpers/setOperations.ts index 01c19d6cee..1ae4803a6b 100644 --- a/web/src/helpers/setOperations.ts +++ b/web/src/helpers/setOperations.ts @@ -1,3 +1,3 @@ export function intersection(a: Set, b: Set) { - return new Set([...a].filter((i) => b.has(i))) + return new Set(Array.from(a).filter((i) => b.has(i))) } diff --git a/web/src/helpers/urlQuery.ts b/web/src/helpers/urlQuery.ts index 72462e3e92..228da9dc89 100644 --- a/web/src/helpers/urlQuery.ts +++ b/web/src/helpers/urlQuery.ts @@ -1,9 +1,7 @@ +import type { ParsedUrlQuery } from 'querystring' import { omit } from 'lodash' -// TODO: remove this once issue: https://github.com/hodcroftlab/covariants/issues/399 is resolved -// @ts-ignore import { merge } from 'merge-anything' import Router from 'next/router' -import type { ParsedUrlQuery } from 'querystring' import { parseUrl } from 'src/helpers/parseUrl' export async function setUrlQuery(query: ParsedUrlQuery) { @@ -18,7 +16,7 @@ export async function removeFromUrlQuery(key: string) { export async function updateUrlQuery(newQuery: ParsedUrlQuery) { const { query: oldQuery } = parseUrl(Router.asPath) - const query = merge(oldQuery, newQuery) as ParsedUrlQuery + const query = merge(oldQuery, newQuery) return setUrlQuery(query) } diff --git a/web/src/i18n/detectLocale.ts b/web/src/i18n/detectLocale.ts index 4fc33e3d1d..7ec7d92332 100644 --- a/web/src/i18n/detectLocale.ts +++ b/web/src/i18n/detectLocale.ts @@ -13,7 +13,7 @@ export function detectLocale({ defaultLanguage, availableLocales, shorten = true return defaultLanguage } - const navigatorLocal = (navigator ?? window.navigator) as NavigatorPlus | undefined + const navigatorLocal = (navigator ?? globalThis.navigator) as NavigatorPlus | undefined let language language = diff --git a/web/src/i18n/getMdxContent.tsx b/web/src/i18n/getMdxContent.tsx index 666b5ceba6..5ba59ae702 100644 --- a/web/src/i18n/getMdxContent.tsx +++ b/web/src/i18n/getMdxContent.tsx @@ -1,8 +1,8 @@ import React, { useMemo } from 'react' import dynamic from 'next/dynamic' -import { DEFAULT_LOCALE_KEY } from 'src/i18n/i18n' import { useRecoilValue } from 'recoil' +import { DEFAULT_LOCALE_KEY } from 'src/i18n/i18n' import { localeAtom } from 'src/state/locale.state' export interface MdxContentProps { diff --git a/web/src/i18n/i18n.ts b/web/src/i18n/i18n.ts index 5baf103888..95b49ee74a 100644 --- a/web/src/i18n/i18n.ts +++ b/web/src/i18n/i18n.ts @@ -3,7 +3,7 @@ import { ElementType, FC } from 'react' import type { StrictOmit } from 'ts-essentials' import { get, isNil, mapValues } from 'lodash' -import i18nOriginal, { i18n as I18N, Resource } from 'i18next' +import { i18n as I18N, Resource, use as i18use } from 'i18next' import { initReactI18next } from 'react-i18next' import { Settings as LuxonSettings } from 'luxon' @@ -157,7 +157,7 @@ export function i18nInit({ localeKey }: I18NInitParams) { numbro.registerLanguage({ ...enUS, ...languageRaw }) }) - const i18n = i18nOriginal.use(initReactI18next).createInstance({ + const i18n = i18use(initReactI18next).createInstance({ resources, lng: localeKey, fallbackLng: DEFAULT_LOCALE_KEY, @@ -168,7 +168,6 @@ export function i18nInit({ localeKey }: I18NInitParams) { initImmediate: true, }) - // eslint-disable-next-line no-void void i18n.init() const locale = locales[localeKey] diff --git a/web/src/i18n/resources/zh/common.json b/web/src/i18n/resources/zh/common.json index 8000f518a6..4ec2c14e6d 100644 --- a/web/src/i18n/resources/zh/common.json +++ b/web/src/i18n/resources/zh/common.json @@ -59,7 +59,7 @@ "Country": "国家", "Frequency": "频率", "{{asterisk}} Interpolated values": "{{asterisk}} 内插值", - "Dedicated {{nextstrain}} build for {{variant}}": "{{variant}}的{{nextstrain}} 构建", + "Dedicated {{nextstrain}} build for {{variant}}": "{{variant}} 的 {{nextstrain}} 构建", "Reversed": "倒排", "Defining mutations": "定义突变", "None. See notes below": "没有。参见下面的注释", @@ -67,7 +67,7 @@ "Synonymous": "同义词", "Compare": "比较", "Distribution of {{variant}} per country": "{{variant}} 每个国家的分布", - "Protein visualisation for {{variant}} by {{aquaria}}": " {{variant}} 蛋白质可视化,由 {{aquaria}}提供", + "Protein visualisation for {{variant}} by {{aquaria}}": " {{variant}} 蛋白质可视化,由 {{aquaria}} 提供", "Count": "计数", "Data is from {{source}}": "数据来自 {{source}}", "Gene S": "刺突基因", @@ -85,7 +85,7 @@ "Phylogenetic relationships of SARS-CoV-2 clades as defined by {{nextstrain}}": "SARS-CoV-2 进化论的系统发育关系定义为 {{nextstrain}}", "source": "资源", "No dedicated {{nextstrain}} build is available": "没有专用 {{nextstrain}} 版本可用", - "{{nextstrain}} Clade": "{{nextstrain}}进化枝", + "{{nextstrain}} Clade": "{{nextstrain}} 进化枝", "Mutations": "突变", "Show less": "显示更少", "Show more": "显示更多", diff --git a/web/src/io/axiosFetch.ts b/web/src/io/axiosFetch.ts index 6ff605aff9..1090f07fe0 100644 --- a/web/src/io/axiosFetch.ts +++ b/web/src/io/axiosFetch.ts @@ -1,4 +1,4 @@ -import axios, { AxiosError, AxiosRequestConfig, AxiosResponse } from 'axios' +import axios, { AxiosError, AxiosRequestConfig, AxiosResponse, isAxiosError } from 'axios' import { isNil } from 'lodash' import serializeJavascript from 'serialize-javascript' import { ErrorInternal } from 'src/helpers/ErrorInternal' @@ -27,7 +27,7 @@ export async function axiosFetch( try { res = await axios.get(url, options) } catch (error) { - throw axios.isAxiosError(error) ? new HttpRequestError(error) : sanitizeError(error) + throw isAxiosError(error) ? new HttpRequestError(error) : sanitizeError(error) } if (!res?.data) { diff --git a/web/src/io/getClusters.ts b/web/src/io/getClusters.ts index 7f56a0b245..c4308ffa7f 100644 --- a/web/src/io/getClusters.ts +++ b/web/src/io/getClusters.ts @@ -8,14 +8,14 @@ import { theme } from 'src/theme' import clustersJson from 'src/../public/data/clusters.json' -export const CLUSTER_NAME_OTHERS = 'others' as const +export const CLUSTER_NAME_OTHERS = 'others' export interface AquariaDatum { gene: string url: string } -export type ClusterDatum = { +export interface ClusterDatum { build_name: string old_build_names?: string[] nextstrain_url?: string diff --git a/web/src/io/getLastUpdatedDate.ts b/web/src/io/getLastUpdatedDate.ts index b5a0b93790..89baaf11bd 100644 --- a/web/src/io/getLastUpdatedDate.ts +++ b/web/src/io/getLastUpdatedDate.ts @@ -1,6 +1,5 @@ -import updateJson from 'src/../public/data/update.json' - import { DateTime } from 'luxon' +import updateJson from 'src/../public/data/update.json' export function getLastUpdatedDate() { const utc = DateTime.fromISO(updateJson.lastUpdated, { zone: 'UTC' }) diff --git a/web/src/io/getMutationCounts.ts b/web/src/io/getMutationCounts.ts index 93f60f5acb..99fbd69930 100644 --- a/web/src/io/getMutationCounts.ts +++ b/web/src/io/getMutationCounts.ts @@ -1,6 +1,6 @@ +import { mapValues, sortBy } from 'lodash' import type { Mutation } from 'src/types' import { parseAminoacidMutation } from 'src/components/Common/parseAminoacidMutation' -import { mapValues, sortBy } from 'lodash' export interface MutationCountsDatum { mut: Mutation @@ -49,8 +49,8 @@ export function parseMutationCounts(data: MutationCountsJsonGeneRecord) { export async function getMutationCounts(clusterBuildName: string): Promise<{ result: MutationCountsData | undefined }> { try { - const data = (await import(`../../public/data/mutationCounts/${clusterBuildName}.json`)) // eslint-disable-line @typescript-eslint/no-unsafe-member-access - .default as MutationCountsJson // eslint-disable-line unicorn/no-await-expression-member + const data = (await import(`../../public/data/mutationCounts/${clusterBuildName}.json`)) + .default as MutationCountsJson // eslint-disable-line unicorn/no-await-expression-member, @typescript-eslint/no-unsafe-member-access const result = mapValues(data, (datum) => parseMutationCounts(datum)) return { result } } catch { diff --git a/web/src/io/getParams.ts b/web/src/io/getParams.ts index 292dd0dd8e..fea81207ba 100644 --- a/web/src/io/getParams.ts +++ b/web/src/io/getParams.ts @@ -18,8 +18,8 @@ export const params = getParams() export function getTimeDomain(): [number, number] { const minDate = dateStringToSeconds(params.min_date) const maxDate = dateStringToSeconds(params.max_date) - if (isNaN(minDate) || isNaN(maxDate)) { - throw new Error(INVALID_PARAMS) + if (Number.isNaN(minDate) || Number.isNaN(maxDate)) { + throw new TypeError(INVALID_PARAMS) } return [minDate, maxDate] } diff --git a/web/src/io/getPerClusterData.ts b/web/src/io/getPerClusterData.ts index 3f9b11c9b3..d2cdd5d984 100644 --- a/web/src/io/getPerClusterData.ts +++ b/web/src/io/getPerClusterData.ts @@ -8,15 +8,9 @@ import { getClusters } from 'src/io/getClusters' export interface ClusterDistributionDatum { week: string - frequencies: { - [country: string]: number | undefined - } - interp: { - [country: string]: boolean | undefined - } - orig: { - [country: string]: boolean | undefined - } + frequencies: Record + interp: Record + orig: Record } export interface ClusterDistribution { @@ -47,7 +41,7 @@ export function usePerClusterData(): PerClusterData & { setClusters: SetterOrUpd const perClusterData = usePerClusterDataRaw() const [clusters, setClusters] = useRecoilState(clustersForPerClusterDataAtom) - const clusterBuildNames: Map = new Map(getClusters().map((c) => [c.display_name, c.build_name])) + const clusterBuildNames = new Map(getClusters().map((c) => [c.display_name, c.build_name])) const clusterDistributions: ClusterDistribution[] = perClusterData.distributions return { diff --git a/web/src/io/getPerCountryCasesData.ts b/web/src/io/getPerCountryCasesData.ts index f2b7c9a307..624f396082 100644 --- a/web/src/io/getPerCountryCasesData.ts +++ b/web/src/io/getPerCountryCasesData.ts @@ -24,9 +24,7 @@ export interface PerCountryCasesDataRaw { export interface PerCountryCasesDistributionDatum { week: string stand_total_cases: number - stand_estimated_cases: { - [key: string]: number | undefined - } + stand_estimated_cases: Record } export interface PerCountryCasesDistribution { @@ -34,9 +32,7 @@ export interface PerCountryCasesDistribution { distribution: PerCountryCasesDistributionDatum[] } -export interface ClusterState { - [key: string]: { enabled: boolean } -} +export type ClusterState = Record export interface PerCountryCasesData { clusterNames: string[] diff --git a/web/src/io/getPerCountryData.ts b/web/src/io/getPerCountryData.ts index bd91a54d73..573fd784ba 100644 --- a/web/src/io/getPerCountryData.ts +++ b/web/src/io/getPerCountryData.ts @@ -23,9 +23,7 @@ export interface PerCountryDataRaw { export interface CountryDistributionDatum { week: string total_sequences: number - cluster_counts: { - [key: string]: number | undefined - } + cluster_counts: Record } export interface CountryDistribution { diff --git a/web/src/pages/_app.tsx b/web/src/pages/_app.tsx index 6aad7347dd..f380828cd0 100644 --- a/web/src/pages/_app.tsx +++ b/web/src/pages/_app.tsx @@ -10,10 +10,10 @@ import { QueryClientProvider } from '@tanstack/react-query' import { ReactQueryDevtools } from '@tanstack/react-query-devtools' import { I18nextProvider } from 'react-i18next' import type { AppProps } from 'next/app' -import { LOADING } from 'src/components/Loading/Loading' -import { FETCHER } from 'src/hooks/useAxiosQuery' import { ThemeProvider } from 'styled-components' import { MDXProvider } from '@mdx-js/react' +import { LOADING } from 'src/components/Loading/Loading' +import { FETCHER } from 'src/hooks/useAxiosQuery' import i18n, { changeLocale, getLocaleWithKey } from 'src/i18n/i18n' import { theme } from 'src/theme' import { DOMAIN_STRIPPED } from 'src/constants' @@ -30,7 +30,6 @@ export function RecoilStateInitializer() { const snapShotRelease = snapshot.retain() const { getPromise } = snapshot - // eslint-disable-next-line no-void void Promise.resolve() // eslint-disable-next-line promise/always-return .then(async () => { @@ -55,7 +54,7 @@ export function RecoilStateInitializer() { RecoilEnv.RECOIL_DUPLICATE_ATOM_KEY_CHECKING_ENABLED = false -function MyApp({ Component, pageProps, router }: AppProps) { +function MyApp({ Component, pageProps }: AppProps) { const initializeState = useCallback(() => {}, []) // // NOTE: We do manual parsing here, because router.query is randomly empty on the first few renders. diff --git a/web/src/pages/_document.tsx b/web/src/pages/_document.tsx index 8cc8e33bf1..783f2f49e4 100644 --- a/web/src/pages/_document.tsx +++ b/web/src/pages/_document.tsx @@ -29,7 +29,7 @@ export const AppleIcons = [57, 60, 72, 76, 114, 120, 144, 152, 180].map((size) = diff --git a/web/src/pages/variants/[clusterName].tsx b/web/src/pages/variants/[clusterName].tsx index 66ebe0fef4..144439c9cc 100644 --- a/web/src/pages/variants/[clusterName].tsx +++ b/web/src/pages/variants/[clusterName].tsx @@ -17,7 +17,7 @@ export async function getStaticProps(context: GetStaticPropsContext): Promise { return { paths: [...clusterBuildNames, ...clusterOldBuildNames].map((clusterName) => `/variants/${clusterName}`), diff --git a/web/src/polyfills.ts b/web/src/polyfills.ts index ced6e8ad45..a9466d2676 100644 --- a/web/src/polyfills.ts +++ b/web/src/polyfills.ts @@ -1,8 +1,9 @@ export async function loadPolyfills() { - if (typeof window === 'undefined') { + if (globalThis === undefined) { return } - if (typeof window.IntersectionObserver === 'undefined') { + + if (globalThis.IntersectionObserver === undefined) { await import('intersection-observer') } } diff --git a/web/src/state/Clusters.ts b/web/src/state/Clusters.ts index 0cd7c0e6b9..3cbb22f888 100644 --- a/web/src/state/Clusters.ts +++ b/web/src/state/Clusters.ts @@ -32,7 +32,6 @@ export function updateUrlOnClustersSet({ onSet }: AtomEffectParams) { // If all clusters are enabled, we will remove cluster url params const hasAllEnabled = clusters.every((cluster) => cluster.enabled) - // eslint-disable-next-line no-void void updateUrlQuery({ variant: hasAllEnabled ? [] : clusters.filter((cluster) => cluster.enabled).map((cluster) => cluster.cluster), }) diff --git a/web/src/state/ClustersForCaseData.ts b/web/src/state/ClustersForCaseData.ts index 6e29c04cda..b15ce559fc 100644 --- a/web/src/state/ClustersForCaseData.ts +++ b/web/src/state/ClustersForCaseData.ts @@ -1,11 +1,11 @@ -import { get } from 'lodash' import { ParsedUrlQuery } from 'querystring' +import { get } from 'lodash' import { atom } from 'recoil' +import type { Cluster } from './Clusters' import { convertToArrayMaybe, includesCaseInsensitive } from 'src/helpers/array' import { updateUrlQuery } from 'src/helpers/urlQuery' import { getPerCountryCasesData } from 'src/io/getPerCountryCasesData' -import type { Cluster } from './Clusters' function getAllClusters(): Cluster[] { return getPerCountryCasesData().clusters @@ -42,7 +42,6 @@ export const clustersCasesAtom = atom({ // If all clusters are enabled, we will remove cluster url params const hasAllEnabled = clusters.every((cluster) => cluster.enabled) - // eslint-disable-next-line no-void void updateUrlQuery({ variant: hasAllEnabled ? [] : clusters.filter((cluster) => cluster.enabled).map((cluster) => cluster.cluster), }) diff --git a/web/src/state/ClustersForPerClusterData.ts b/web/src/state/ClustersForPerClusterData.ts index 11c07ae93d..00cb8fb82c 100644 --- a/web/src/state/ClustersForPerClusterData.ts +++ b/web/src/state/ClustersForPerClusterData.ts @@ -1,11 +1,11 @@ import Router from 'next/router' +import { get } from 'lodash' import { convertToArrayMaybe, includesCaseInsensitive } from 'src/helpers/array' import { parseUrl } from 'src/helpers/parseUrl' import { Cluster, updateUrlOnClustersSet } from 'src/state/Clusters' import { fetchPerClusterDataRaw } from 'src/io/getPerClusterData' import { atomAsync } from 'src/state/utils/atomAsync' import { sortClusters } from 'src/io/getClusters' -import { get } from 'lodash' export const clustersForPerClusterDataAtom = atomAsync({ key: 'clustersForPerClusterDataAtom', diff --git a/web/src/state/PlacesForCaseData.ts b/web/src/state/PlacesForCaseData.ts index d4af991746..14c69f8158 100644 --- a/web/src/state/PlacesForCaseData.ts +++ b/web/src/state/PlacesForCaseData.ts @@ -1,14 +1,13 @@ -import { get } from 'lodash' import { ParsedUrlQuery } from 'querystring' +import { get } from 'lodash' import { atom, DefaultValue, selector } from 'recoil' +import regionCountryJson from '../../public/data/region_country.json' import { convertToArrayMaybe, includesCaseInsensitive } from 'src/helpers/array' import type { Continent, Country } from 'src/state/Places' import { updateUrlQuery } from 'src/helpers/urlQuery' import { getPerCountryCasesData } from 'src/io/getPerCountryCasesData' -import regionCountryJson from '../../public/data/region_country.json' - /** * Converts values incoming from URL query into region, countries and continents. * To be used during app startup. @@ -120,7 +119,6 @@ export const countriesCasesAtom = atom({ // If all countries are enabled, we will remove country url params const hasAllEnabled = countries.every((country) => country.enabled) - // eslint-disable-next-line no-void void updateUrlQuery({ country: hasAllEnabled ? [] @@ -133,8 +131,8 @@ export const countriesCasesAtom = atom({ /** * Represents a list of currently enabled continents. - * NOTE: this is a selector and it's value is tied to the countries atom. - * NOTE: this selector is mutable, i.e. it can be set(). When this happens, it also modifies the countries atom. + * NOTE: this is a selector, and it's value is tied to the countries' atom. + * NOTE: this selector is mutable, i.e. it can be set(). When this happens, it also modifies the countries' atom. */ export const continentsCasesAtom = selector({ key: 'casesContinents', @@ -142,6 +140,7 @@ export const continentsCasesAtom = selector({ const countries = get(countriesCasesAtom) return getContinentsFromCountries(countries) }, + // eslint-disable-next-line @typescript-eslint/no-unused-vars set: ({ set, get, reset }, continentsOrDefault) => { const countriesOld = get(countriesCasesAtom) const continents = continentsOrDefault instanceof DefaultValue ? getAllContinents() : continentsOrDefault diff --git a/web/src/state/PlacesForPerClusterData.ts b/web/src/state/PlacesForPerClusterData.ts index a566eefd76..580a233c7f 100644 --- a/web/src/state/PlacesForPerClusterData.ts +++ b/web/src/state/PlacesForPerClusterData.ts @@ -59,8 +59,6 @@ const countriesAtom = atomAsync({ onSet((countries) => { // If all countries are enabled, we will remove country url params const hasAllEnabled = countries.every((country) => country.enabled) - - // eslint-disable-next-line no-void void updateUrlQuery({ country: hasAllEnabled ? [] diff --git a/web/src/state/PlacesForPerCountryData.ts b/web/src/state/PlacesForPerCountryData.ts index 140d9d129a..ee48627edc 100644 --- a/web/src/state/PlacesForPerCountryData.ts +++ b/web/src/state/PlacesForPerCountryData.ts @@ -47,7 +47,6 @@ const regionAtom = atomAsync({ ({ onSet }) => { onSet((region) => { // NOTE: This will overwrite the query entirely - // eslint-disable-next-line no-void void setUrlQuery({ region }) }) }, @@ -85,7 +84,6 @@ const countriesAtom = atomFamilyAsync({ // If all countries are enabled, we will remove country url params const hasAllEnabled = countries.every((country) => country.enabled) - // eslint-disable-next-line no-void void updateUrlQuery({ country: hasAllEnabled ? [] @@ -122,7 +120,7 @@ export const continentsAtom = selectorFamily({ export async function validateRegion(regionRaw: string) { const { regions } = await fetchPerCountryDataRaw() const allRegions = regions.map((region) => region.region) - const region = allRegions.find((candidate) => candidate.toLowerCase() === regionRaw?.toLowerCase()) + const region = allRegions.find((candidate) => candidate.toLowerCase() === regionRaw.toLowerCase()) if (!region) { const availableRegionsMsg = allRegions.map((region) => `'${region}'`).join(', ') throw new Error( diff --git a/web/src/styles/_theme.scss b/web/src/styles/_theme.scss index 64c4d86d83..877edc7bef 100644 --- a/web/src/styles/_theme.scss +++ b/web/src/styles/_theme.scss @@ -1,7 +1,7 @@ // Some of the things are borrowed from bootswatch Materia theme // https://github.com/thomaspark/bootswatch -/* stylelint-disable selector-no-qualifying-type, plugin/stylelint-no-indistinguishable-colors, plugin/no-low-performance-animation-properties */ +/* stylelint-disable selector-no-qualifying-type */ @use './variables' as vars; @@ -25,6 +25,7 @@ transition: background 0.5s, opacity 1s; + @include gradient-radial($color 10%, transparent 10.01%); } @@ -46,7 +47,7 @@ .navbar { border: 0; - box-shadow: 0 1px 2px rgba(0, 0, 0, 0.3); + box-shadow: 0 1px 2px rgb(0 0 0 / 30%); padding: 0 1rem; &-brand { @@ -66,14 +67,14 @@ input[type='number'], input[type='tel'] { color: #fff; - box-shadow: inset 0 -1px 0 rgba(255, 255, 255, 0.5); + box-shadow: inset 0 -1px 0 rgb(255 255 255 / 50%); &:focus { box-shadow: inset 0 -2px 0 #fff; } &::placeholder { - color: rgba(255, 255, 255, 0.5); + color: rgb(255 255 255 / 50%); } } } @@ -85,16 +86,16 @@ .btn-#{$class} { &:focus { background-color: $bg; - box-shadow: 0 0 0 2px rgba(204, 204, 204, 0.5); + box-shadow: 0 0 0 2px rgb(204 204 204 / 50%); } &:hover, &:active:hover { - background-color: darken($bg, 6%); + background-color: darken($bg, 6%); /* stylelint-disable-line scss/no-global-function-names, function-no-unknown */ } &:active { - box-shadow: 2px 2px 4px rgba(0, 0, 0, 0.4); + box-shadow: 2px 2px 4px rgb(0 0 0 / 40%); } @include ripple($color); @@ -116,8 +117,8 @@ .btn { border: 0; - box-shadow: 0 1px 4px rgba(0, 0, 0, 0.4); - transition: all 0.4s; // stylelint-disable-line declaration-property-value-blacklist + box-shadow: 0 1px 4px rgb(0 0 0 / 40%); + transition: all 0.4s; &-link { color: vars.$link-color; @@ -146,13 +147,13 @@ &.disabled, &[disabled], fieldset[disabled] & { - color: rgba(0, 0, 0, 0.4); - background-color: rgba(0, 0, 0, 0.1); + color: rgb(0 0 0 / 40%); + background-color: rgb(0 0 0 / 10%); opacity: 1; &:hover, &:focus { - background-color: rgba(0, 0, 0, 0.1); + background-color: rgb(0 0 0 / 10%); } } } @@ -198,7 +199,7 @@ .btn + .btn, .btn + .btn-group > .dropdown-toggle { - box-shadow: 1px 1px 4px rgba(0, 0, 0, 0.4); + box-shadow: 1px 1px 4px rgb(0 0 0 / 40%); } } @@ -220,7 +221,7 @@ p { } a { - transition: all 0.2s; // stylelint-disable-line declaration-property-value-blacklist + transition: all 0.2s; } .text-secondary { @@ -233,10 +234,11 @@ a { > tbody > tr, > tbody > tr > th, > tbody > tr > td { - transition: all 0.2s; // stylelint-disable-line declaration-property-value-blacklist + transition: all 0.2s; } } +/* stylelint-disable-next-line no-descending-specificity */ .thead-inverse th { color: vars.$white; background-color: vars.$primary; @@ -259,10 +261,10 @@ a { textarea, textarea.form-control, input.form-control, -input[type='text'], -input[type='password'], -input[type='email'], -input[type='number'], +input[type='text'], /* stylelint-disable-line no-descending-specificity */ +input[type='password'], /* stylelint-disable-line no-descending-specificity */ +input[type='email'], /* stylelint-disable-line no-descending-specificity */ +input[type='number'], /* stylelint-disable-line no-descending-specificity */ [type='text'].form-control, [type='password'].form-control, [type='email'].form-control, @@ -277,7 +279,7 @@ input[type='number'], .dropdown-menu { margin-top: 0; border: 0; - box-shadow: 0 1px 4px rgba(0, 0, 0, 0.3); + box-shadow: 0 1px 4px rgb(0 0 0 / 30%); } .nav-tabs { @@ -292,7 +294,7 @@ input[type='number'], background-color: transparent; border: 0; box-shadow: inset 0 -1px 0 #ddd; - transition: all 0.2s; // stylelint-disable-line declaration-property-value-blacklist + transition: all 0.2s; &:hover { color: vars.$primary; @@ -318,10 +320,10 @@ input[type='number'], } &.nav-justified { - .nav-link, + .nav-link, /* stylelint-disable-line no-descending-specificity */ .nav-link:hover, .nav-link:focus, - .nav-link.active, + .nav-link.active, /* stylelint-disable-line no-descending-specificity */ .nav-link.active:hover, .nav-link.active:focus { border: 0; @@ -357,7 +359,7 @@ input[type='number'], .close { line-height: 0.5; opacity: 0.6; - transition: all 0.2s; // stylelint-disable-line declaration-property-value-blacklist + transition: all 0.2s; &:hover { opacity: 1; @@ -365,7 +367,7 @@ input[type='number'], } .card { - box-shadow: 0 1px 4px rgba(0, 0, 0, 0.4); + box-shadow: 0 1px 4px rgb(0 0 0 / 40%); &.border-primary, &.border-secondary, @@ -385,6 +387,19 @@ input[type='number'], } } +.carousel { + &-caption { + h1, + h2, + h3, + h4, + h5, + h6 { + color: inherit; + } + } +} + .list-group { &-item-action.active { h1, @@ -400,23 +415,10 @@ input[type='number'], .modal-content { border-radius: 0.2rem; - box-shadow: 0 6px 36px rgba(0, 0, 0, 0.3); + box-shadow: 0 6px 36px rgb(0 0 0 / 30%); } .popover { border: 0; - box-shadow: 0 1px 4px rgba(0, 0, 0, 0.3); -} - -.carousel { - &-caption { - h1, - h2, - h3, - h4, - h5, - h6 { - color: inherit; - } - } + box-shadow: 0 1px 4px rgb(0 0 0 / 30%); } diff --git a/web/src/styles/_variables.scss b/web/src/styles/_variables.scss index d870ebe03e..6aae5347cb 100644 --- a/web/src/styles/_variables.scss +++ b/web/src/styles/_variables.scss @@ -3,8 +3,6 @@ // Some of the things are borrowed from bootswatch Materia theme // https://github.com/thomaspark/bootswatch -/* stylelint-disable primer/no-unused-vars */ - $grid-breakpoints: ( xs: 0, sm: 576px, @@ -36,12 +34,11 @@ $font-family-default: $font-family-sans-serif; $font-family-base: $font-family-default; $font-size-base: 1rem; -$white: #ffffff; -$gray-100: #f8f9fa; // stylelint-disable-line plugin/stylelint-no-indistinguishable-colors -$gray-150: #eff1f3; +$white: #fff; +$gray-100: #f8f9fa; $gray-200: #e9ecef; $gray-250: #e5e8ea; -$gray-300: #dee2e6; // stylelint-disable-line plugin/stylelint-no-indistinguishable-colors +$gray-300: #dee2e6; $gray-400: #ced4da; $gray-500: #adb5bd; $gray-600: #7b838a; @@ -67,7 +64,7 @@ $success: $green; $info: $cyan; $warning: $yellow; $danger: $red; -$light: $white; // stylelint-disable-line plugin/stylelint-no-indistinguishable-colors +$light: $white; $dark: $gray-700; $yiq-contrasted-threshold: 170; @@ -84,10 +81,10 @@ $code-font-size: $font-size-base * 0.9; $blockquote-font-size: $font-size-base * 0.9; $blockquote-bg-color: #fff7df; -$navbar-dark-color: rgba($white, 0.75); // stylelint-disable-line plugin/stylelint-no-indistinguishable-colors -$navbar-dark-hover-color: rgba($white, 1); // stylelint-disable-line plugin/stylelint-no-indistinguishable-colors +$navbar-dark-color: rgba($white, 0.75); +$navbar-dark-hover-color: rgba($white, 1); -$navbar-light-hover-color: rgba($black, 0.9); // stylelint-disable-line plugin/stylelint-no-indistinguishable-colors +$navbar-light-hover-color: rgba($black, 0.9); $alert-border-width: 0; $progress-height: 0.5rem; diff --git a/web/src/styles/global.scss b/web/src/styles/global.scss index 17f3af2268..c65654e22c 100644 --- a/web/src/styles/global.scss +++ b/web/src/styles/global.scss @@ -2,19 +2,17 @@ @use 'typeface-droid-sans-mono' as droid-sans; // only works because includePaths option is set in next.config.ts @use 'bootstrap-icons/font/bootstrap-icons.css'; // only works because includePaths option is set in next.config.ts @use './font-family-system'; - @use './lightrope'; - @use 'react-aspect-ratio/aspect-ratio.css'; @use 'react-gif-player/src/GifPlayer.scss'; @use 'react-super-responsive-table/dist/SuperResponsiveTableStyle.css'; - @use './components/LanguageSwitcher'; @import './variables'; @import 'bootstrap/scss/bootstrap'; @import './theme'; +/* stylelint-disable-next-line selector-class-pattern */ .gif_player { margin: 1rem; @@ -38,6 +36,7 @@ body { background-color: $body-bg; } +/* stylelint-disable-next-line selector-class-pattern, selector-id-pattern, selector-max-id */ #__next { height: 100%; } diff --git a/web/src/styles/lightrope.scss b/web/src/styles/lightrope.scss index dbc1fa67a8..77d5cd0f86 100644 --- a/web/src/styles/lightrope.scss +++ b/web/src/styles/lightrope.scss @@ -4,9 +4,9 @@ $globe-spacing: 15px; $globe-spread: 3px; $light-off-opacity: 0.2; -$globe-color-1: rgb(164, 215, 9); -$globe-color-2: rgb(100, 126, 235); -$globe-color-3: rgb(210, 61, 27); +$globe-color-1: rgb(164 215 9); +$globe-color-2: rgb(100 126 235); +$globe-color-3: rgb(210 61 27); .lightrope { text-align: center; @@ -14,7 +14,7 @@ $globe-color-3: rgb(210, 61, 27); overflow: hidden; position: absolute; z-index: 1; - margin: -15px 0 0 0; + margin: -15px 0 0; padding: 0; pointer-events: none; width: 100%; @@ -57,7 +57,7 @@ $globe-color-3: rgb(210, 61, 27); animation-duration: 1.2s; } - &:before { + &::before { content: ''; position: absolute; background: #222; @@ -68,7 +68,7 @@ $globe-color-3: rgb(210, 61, 27); left: 1px; } - &:after { + &::after { content: ''; top: (0 - calc($globe-height/2)); left: $globe-width - 3; @@ -79,7 +79,7 @@ $globe-color-3: rgb(210, 61, 27); border-radius: 50%; } - &:last-child:after { + &:last-child::after { content: none; } @@ -95,6 +95,7 @@ $globe-color-3: rgb(210, 61, 27); background: rgba($globe-color-1, 1); box-shadow: 0 calc($globe-height/6) $globe-width * 2 $globe-spread rgba($globe-color-1, 1); } + 50% { background: rgba($globe-color-1, $light-off-opacity); box-shadow: 0 calc($globe-height/6) $globe-width * 2 $globe-spread rgba($globe-color-1, 0.2); @@ -107,6 +108,7 @@ $globe-color-3: rgb(210, 61, 27); background: rgba($globe-color-2, 1); box-shadow: 0 calc($globe-height/6) $globe-width * 2 $globe-spread rgba($globe-color-2, 1); } + 50% { background: rgba($globe-color-2, $light-off-opacity); box-shadow: 0 calc($globe-height/6) $globe-width * 2 $globe-spread rgba($globe-color-2, 0.2); @@ -119,6 +121,7 @@ $globe-color-3: rgb(210, 61, 27); background: rgba($globe-color-3, 1); box-shadow: 0 calc($globe-height/6) $globe-width * 2 $globe-spread rgba($globe-color-3, 1); } + 50% { background: rgba($globe-color-3, $light-off-opacity); box-shadow: 0 calc($globe-height/6) $globe-width * 2 $globe-spread rgba($globe-color-3, 0.2); diff --git a/web/src/types/md-module.d.ts b/web/src/types/md-module.d.ts index b5c924eba3..1f4cc4cce9 100644 --- a/web/src/types/md-module.d.ts +++ b/web/src/types/md-module.d.ts @@ -1,4 +1,4 @@ declare module '*.md' { - const MDXComponent: () => JSX.Element + const MDXComponent: () => React.JSX.Element export default MDXComponent } diff --git a/web/src/types/mdx-module.d.ts b/web/src/types/mdx-module.d.ts index 0314ad66b0..7fb54dfb64 100644 --- a/web/src/types/mdx-module.d.ts +++ b/web/src/types/mdx-module.d.ts @@ -1,4 +1,4 @@ declare module '*.mdx' { - const MDXComponent: () => JSX.Element + const MDXComponent: () => React.JSX.Element export default MDXComponent } diff --git a/web/src/types/styled.d.ts b/web/src/types/styled.d.ts index ab00fd5a2c..2ad9a61886 100644 --- a/web/src/types/styled.d.ts +++ b/web/src/types/styled.d.ts @@ -1,6 +1,6 @@ -/* eslint-disable @typescript-eslint/no-empty-interface */ import { Theme } from 'src/theme' declare module 'styled-components' { + // eslint-disable-next-line @typescript-eslint/no-empty-object-type export declare interface DefaultTheme extends Theme {} } diff --git a/web/stylelint.config.js b/web/stylelint.config.js new file mode 100644 index 0000000000..584f4a0795 --- /dev/null +++ b/web/stylelint.config.js @@ -0,0 +1,15 @@ +/** @type {import('stylelint').Config} */ +export default { + extends: ['stylelint-config-standard', 'stylelint-config-sass-guidelines'], + rules: { + 'no-invalid-position-at-import-rule': [ + true, + { + ignoreAtRules: ['use'], + }, + ], + 'import-notation': 'string', + 'selector-max-compound-selectors': 4, + 'max-nesting-depth': 3, + }, +} diff --git a/web/tests/home.spec.ts b/web/tests/home.spec.ts index 056a6efb01..b194176fa8 100644 --- a/web/tests/home.spec.ts +++ b/web/tests/home.spec.ts @@ -1,17 +1,17 @@ import { test, expect } from '@playwright/test' test.describe('The Home page', () => { -test.beforeEach(async ({ page }) => { - await page.goto('/') -}) + test.beforeEach(async ({ page }) => { + await page.goto('/') + }) -test('has title', async ({ page }) => { - await expect(page).toHaveTitle(/CoVariants/) -}) + test('has title', async ({ page }) => { + await expect(page).toHaveTitle(/CoVariants/) + }) -test('navbar link', async ({ page }) => { - await page.getByRole('link', { name: 'FAQ' }).click() + test('navbar link', async ({ page }) => { + await page.getByRole('link', { name: 'FAQ' }).click() - await expect(page.getByRole('heading', { name: 'Frequently asked questions' })).toBeVisible() + await expect(page.getByRole('heading', { name: 'Frequently asked questions' })).toBeVisible() + }) }) -}); diff --git a/web/tools/addLocaleKeys.ts b/web/tools/addLocaleKeys.ts index 3ddbcf3b8a..365e2a6e32 100644 --- a/web/tools/addLocaleKeys.ts +++ b/web/tools/addLocaleKeys.ts @@ -1,6 +1,7 @@ -import fs from 'fs-extra' import path from 'path' -import { sortBy, uniqBy } from 'lodash' +import fs from 'fs-extra' +import sortBy from 'lodash/sortBy' +import uniqBy from 'lodash/uniqBy' import { I18N_RESOURCES_DIR, I18N_RESOURCES_DEFAULT_LOCALE_FILE, readJson } from './fixLocales' const ADDITIONAL_KEYS_FILE = path.join(I18N_RESOURCES_DIR, '../additional_keys.json') diff --git a/web/tools/fixLocales.ts b/web/tools/fixLocales.ts index bf24ec81ba..f71b7e31cc 100644 --- a/web/tools/fixLocales.ts +++ b/web/tools/fixLocales.ts @@ -1,8 +1,13 @@ -import fs from 'fs-extra' import path from 'path' -import { difference, isObject, padStart, isEmpty, get } from 'lodash' -import { notUndefined } from '../src/helpers/notUndefined' -import { safeZip } from '../src/helpers/safeZip' +import * as url from 'node:url' +import fs from 'fs-extra' +import difference from 'lodash/difference' +import isObject from 'lodash/isObject' +import padStart from 'lodash/padStart' +import isEmpty from 'lodash/isEmpty' +import get from 'lodash/get' +import { notUndefined } from 'src/helpers/notUndefined' +import { safeZip } from 'src/helpers/safeZip' export const DEFAULT_LOCALE_KEY = 'en' export const I18N_RESOURCES_DIR = 'src/i18n/resources/' @@ -51,13 +56,13 @@ export function getReferenceKeys() { } export function fixSpaces(s: string) { - let fixed = s.replace(/([^ !#$%&()*,.;=_`{}~-]){{/gim, '$1 {{') - fixed = fixed.replace(/}}([^ !#$%&()*,./:;=_`{}~-])/gim, '}} $1') - fixed = fixed.replace(/ +/gim, ' ') - fixed = fixed.replace(/" (.*) "/gim, '"$1"') - fixed = fixed.replace(/" (.*)"/gim, '"$1"') - fixed = fixed.replace(/"(.*) "/gim, '"$1"') - fixed = fixed.replace(/ :/gim, ':') + let fixed = s.replaceAll(/([^ !#$%&()*,.;=_`{}~-]){{/gim, '$1 {{') + fixed = fixed.replaceAll(/}}([^ !#$%&()*,./:;=_`{}~-])/gim, '}} $1') + fixed = fixed.replaceAll(/ +/gim, ' ') + fixed = fixed.replaceAll(/" (.*) "/gim, '"$1"') + fixed = fixed.replaceAll(/" (.*)"/gim, '"$1"') + fixed = fixed.replaceAll(/"(.*) "/gim, '"$1"') + fixed = fixed.replaceAll(/ :/gim, ':') return fixed } @@ -162,6 +167,7 @@ function main() { }) } -if (require.main === module) { +const modulePath = url.fileURLToPath(import.meta.url) +if (process.argv[1] === modulePath) { main() } diff --git a/web/tools/generateStillImages.ts b/web/tools/generateStillImages.ts index 7d221e95e8..1efb5f7da1 100644 --- a/web/tools/generateStillImages.ts +++ b/web/tools/generateStillImages.ts @@ -1,5 +1,5 @@ -import fs from 'fs-extra' import path from 'path' +import fs from 'fs-extra' import gifFrames from 'gif-frames' import { concurrent } from 'fasy' @@ -28,8 +28,4 @@ export async function generateStillImages() { await concurrent.forEach(generateStillImage(GIF_DIR, OUT_DIR), filenames) } -export async function main() { - return generateStillImages() -} - -main().catch(console.error) +await generateStillImages().catch(console.error) diff --git a/web/tools/server/server.ts b/web/tools/server/server.ts index 431a44f593..7a820f38e4 100644 --- a/web/tools/server/server.ts +++ b/web/tools/server/server.ts @@ -29,9 +29,7 @@ const { moduleRoot } = findModuleRoot() const buildDir = path.join(moduleRoot, '.build', 'production', 'web') const nextDir = path.join(buildDir, '_next') -export interface NewHeaders { - [key: string]: { key: string; value: string }[] -} +export type NewHeaders = Record function main() { const app = express() diff --git a/web/yarn.lock b/web/yarn.lock index 8654193e0c..a5bea5e47c 100644 --- a/web/yarn.lock +++ b/web/yarn.lock @@ -1818,16 +1818,21 @@ "@csstools/color-helpers" "^5.0.1" "@csstools/css-calc" "^2.1.0" -"@csstools/css-parser-algorithms@^3.0.4": +"@csstools/css-parser-algorithms@^3.0.1", "@csstools/css-parser-algorithms@^3.0.4": version "3.0.4" resolved "https://registry.yarnpkg.com/@csstools/css-parser-algorithms/-/css-parser-algorithms-3.0.4.tgz#74426e93bd1c4dcab3e441f5cc7ba4fb35d94356" integrity sha512-Up7rBoV77rv29d3uKHUIVubz1BTcgyUK72IvCQAbfbMv584xHcGKCKbWh7i8hPrRJ7qU4Y8IO3IY9m+iTB7P3A== -"@csstools/css-tokenizer@^3.0.3": +"@csstools/css-tokenizer@^3.0.1", "@csstools/css-tokenizer@^3.0.3": version "3.0.3" resolved "https://registry.yarnpkg.com/@csstools/css-tokenizer/-/css-tokenizer-3.0.3.tgz#a5502c8539265fecbd873c1e395a890339f119c2" integrity sha512-UJnjoFsmxfKUdNYdWgOB0mWUypuLvAfQPH1+pyvRJs6euowbFkFC6P13w1l8mJyi3vxYMxc9kld5jZEGRQs6bw== +"@csstools/media-query-list-parser@^3.0.1": + version "3.0.1" + resolved "https://registry.yarnpkg.com/@csstools/media-query-list-parser/-/media-query-list-parser-3.0.1.tgz#9474e08e6d7767cf68c56bf1581b59d203360cb0" + integrity sha512-HNo8gGD02kHmcbX6PvCoUuOQvn4szyB9ca63vZHKX5A81QytgDG4oxG4IaEfHTlEZSZ6MjPEMWIVU+zF2PZcgw== + "@csstools/media-query-list-parser@^4.0.2": version "4.0.2" resolved "https://registry.yarnpkg.com/@csstools/media-query-list-parser/-/media-query-list-parser-4.0.2.tgz#e80e17eba1693fceafb8d6f2cfc68c0e7a9ab78a" @@ -2122,6 +2127,11 @@ resolved "https://registry.yarnpkg.com/@discoveryjs/json-ext/-/json-ext-0.5.7.tgz#1d572bfbbe14b7704e0ba0f39b74815b84870d70" integrity sha512-dBVuXR082gk3jsFp7Rd/JI4kytwGHecnCoTtXFb7DB6CNHp4rg5k1bhg0nWdLGLnOV71lmDzGQaLMy8iPLY0pw== +"@dual-bundle/import-meta-resolve@^4.1.0": + version "4.1.0" + resolved "https://registry.yarnpkg.com/@dual-bundle/import-meta-resolve/-/import-meta-resolve-4.1.0.tgz#519c1549b0e147759e7825701ecffd25e5819f7b" + integrity sha512-+nxncfwHM5SgAtrVzgpzJOI1ol0PkumhVo469KCf9lUi21IGcY90G98VuHm9VRrUypmAzawAHO9bs6hqeADaVg== + "@emnapi/runtime@^1.2.0": version "1.3.1" resolved "https://registry.yarnpkg.com/@emnapi/runtime/-/runtime-1.3.1.tgz#0fcaa575afc31f455fd33534c19381cfce6c6f60" @@ -3663,6 +3673,20 @@ "@smithy/util-buffer-from" "^3.0.0" tslib "^2.6.2" +"@stylistic/stylelint-plugin@^3.0.1": + version "3.1.1" + resolved "https://registry.yarnpkg.com/@stylistic/stylelint-plugin/-/stylelint-plugin-3.1.1.tgz#2503ef353d50bbdf495db4bb1b87ca7f639f16d6" + integrity sha512-XagAHHIa528EvyGybv8EEYGK5zrVW74cHpsjhtovVATbhDRuJYfE+X4HCaAieW9lCkwbX6L+X0I4CiUG3w/hFw== + dependencies: + "@csstools/css-parser-algorithms" "^3.0.1" + "@csstools/css-tokenizer" "^3.0.1" + "@csstools/media-query-list-parser" "^3.0.1" + is-plain-object "^5.0.0" + postcss-selector-parser "^6.1.2" + postcss-value-parser "^4.2.0" + style-search "^0.1.0" + stylelint "^16.8.2" + "@svgr/babel-plugin-add-jsx-attribute@8.0.0": version "8.0.0" resolved "https://registry.yarnpkg.com/@svgr/babel-plugin-add-jsx-attribute/-/babel-plugin-add-jsx-attribute-8.0.0.tgz#4001f5d5dd87fa13303e36ee106e3ff3a7eb8b22" @@ -4462,6 +4486,21 @@ natural-compare "^1.4.0" ts-api-utils "^1.3.0" +"@typescript-eslint/eslint-plugin@8.17.0": + version "8.17.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.17.0.tgz#2ee073c421f4e81e02d10e731241664b6253b23c" + integrity sha512-HU1KAdW3Tt8zQkdvNoIijfWDMvdSweFYm4hWh+KwhPstv+sCmWb89hCIP8msFm9N1R/ooh9honpSuvqKWlYy3w== + dependencies: + "@eslint-community/regexpp" "^4.10.0" + "@typescript-eslint/scope-manager" "8.17.0" + "@typescript-eslint/type-utils" "8.17.0" + "@typescript-eslint/utils" "8.17.0" + "@typescript-eslint/visitor-keys" "8.17.0" + graphemer "^1.4.0" + ignore "^5.3.1" + natural-compare "^1.4.0" + ts-api-utils "^1.3.0" + "@typescript-eslint/eslint-plugin@^5.4.2 || ^6.0.0 || ^7.0.0 || ^8.0.0": version "8.15.0" resolved "https://registry.yarnpkg.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.15.0.tgz#c95c6521e70c8b095a684d884d96c0c1c63747d2" @@ -4515,6 +4554,17 @@ dependencies: "@typescript-eslint/utils" "5.62.0" +"@typescript-eslint/parser@8.17.0": + version "8.17.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/parser/-/parser-8.17.0.tgz#2ee972bb12fa69ac625b85813dc8d9a5a053ff52" + integrity sha512-Drp39TXuUlD49F7ilHHCG7TTg8IkA+hxCuULdmzWYICxGXvDXmDmWEjJYZQYgf6l/TFfYNE167m7isnc3xlIEg== + dependencies: + "@typescript-eslint/scope-manager" "8.17.0" + "@typescript-eslint/types" "8.17.0" + "@typescript-eslint/typescript-estree" "8.17.0" + "@typescript-eslint/visitor-keys" "8.17.0" + debug "^4.3.4" + "@typescript-eslint/parser@^5.4.2 || ^6.0.0 || ^7.0.0 || ^8.0.0": version "8.15.0" resolved "https://registry.yarnpkg.com/@typescript-eslint/parser/-/parser-8.15.0.tgz#92610da2b3af702cfbc02a46e2a2daa6260a9045" @@ -4579,6 +4629,14 @@ "@typescript-eslint/types" "8.16.0" "@typescript-eslint/visitor-keys" "8.16.0" +"@typescript-eslint/scope-manager@8.17.0": + version "8.17.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/scope-manager/-/scope-manager-8.17.0.tgz#a3f49bf3d4d27ff8d6b2ea099ba465ef4dbcaa3a" + integrity sha512-/ewp4XjvnxaREtqsZjF4Mfn078RD/9GmiEAtTeLQ7yFdKnqwTOgRMSvFz4et9U5RiJQ15WTGXPLj89zGusvxBg== + dependencies: + "@typescript-eslint/types" "8.17.0" + "@typescript-eslint/visitor-keys" "8.17.0" + "@typescript-eslint/type-utils@5.62.0": version "5.62.0" resolved "https://registry.yarnpkg.com/@typescript-eslint/type-utils/-/type-utils-5.62.0.tgz#286f0389c41681376cdad96b309cedd17d70346a" @@ -4619,6 +4677,16 @@ debug "^4.3.4" ts-api-utils "^1.3.0" +"@typescript-eslint/type-utils@8.17.0": + version "8.17.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/type-utils/-/type-utils-8.17.0.tgz#d326569f498cdd0edf58d5bb6030b4ad914e63d3" + integrity sha512-q38llWJYPd63rRnJ6wY/ZQqIzPrBCkPdpIsaCfkR3Q4t3p6sb422zougfad4TFW9+ElIFLVDzWGiGAfbb/v2qw== + dependencies: + "@typescript-eslint/typescript-estree" "8.17.0" + "@typescript-eslint/utils" "8.17.0" + debug "^4.3.4" + ts-api-utils "^1.3.0" + "@typescript-eslint/types@5.62.0": version "5.62.0" resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-5.62.0.tgz#258607e60effa309f067608931c3df6fed41fd2f" @@ -4699,6 +4767,20 @@ semver "^7.6.0" ts-api-utils "^1.3.0" +"@typescript-eslint/typescript-estree@8.17.0": + version "8.17.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-8.17.0.tgz#40b5903bc929b1e8dd9c77db3cb52cfb199a2a34" + integrity sha512-JqkOopc1nRKZpX+opvKqnM3XUlM7LpFMD0lYxTqOTKQfCWAmxw45e3qlOCsEqEB2yuacujivudOFpCnqkBDNMw== + dependencies: + "@typescript-eslint/types" "8.17.0" + "@typescript-eslint/visitor-keys" "8.17.0" + debug "^4.3.4" + fast-glob "^3.3.2" + is-glob "^4.0.3" + minimatch "^9.0.4" + semver "^7.6.0" + ts-api-utils "^1.3.0" + "@typescript-eslint/utils@5.62.0", "@typescript-eslint/utils@^5.58.0": version "5.62.0" resolved "https://registry.yarnpkg.com/@typescript-eslint/utils/-/utils-5.62.0.tgz#141e809c71636e4a75daa39faed2fb5f4b10df86" @@ -4743,6 +4825,16 @@ "@typescript-eslint/types" "8.16.0" "@typescript-eslint/typescript-estree" "8.16.0" +"@typescript-eslint/utils@8.17.0": + version "8.17.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/utils/-/utils-8.17.0.tgz#41c05105a2b6ab7592f513d2eeb2c2c0236d8908" + integrity sha512-bQC8BnEkxqG8HBGKwG9wXlZqg37RKSMY7v/X8VEWD8JG2JuTHuNK0VFvMPMUKQcbk6B+tf05k+4AShAEtCtJ/w== + dependencies: + "@eslint-community/eslint-utils" "^4.4.0" + "@typescript-eslint/scope-manager" "8.17.0" + "@typescript-eslint/types" "8.17.0" + "@typescript-eslint/typescript-estree" "8.17.0" + "@typescript-eslint/visitor-keys@5.62.0": version "5.62.0" resolved "https://registry.yarnpkg.com/@typescript-eslint/visitor-keys/-/visitor-keys-5.62.0.tgz#2174011917ce582875954ffe2f6912d5931e353e" @@ -4775,7 +4867,7 @@ "@typescript-eslint/types" "8.16.0" eslint-visitor-keys "^4.2.0" -"@typescript-eslint/visitor-keys@^8.17.0": +"@typescript-eslint/visitor-keys@8.17.0", "@typescript-eslint/visitor-keys@^8.17.0": version "8.17.0" resolved "https://registry.yarnpkg.com/@typescript-eslint/visitor-keys/-/visitor-keys-8.17.0.tgz#4dbcd0e28b9bf951f4293805bf34f98df45e1aa8" integrity sha512-1Hm7THLpO6ww5QU6H/Qp+AusUUl+z/CAm3cNZZ0jQvon9yicgO7Rwd+/WWRpMKLYV6p2UvdbR27c86rzCPpreg== @@ -5120,7 +5212,7 @@ ajv@^6.1.0, ajv@^6.12.3, ajv@^6.12.4, ajv@^6.12.5: json-schema-traverse "^0.4.1" uri-js "^4.2.2" -ajv@^8.0.0, ajv@^8.9.0: +ajv@^8.0.0, ajv@^8.0.1, ajv@^8.9.0: version "8.17.1" resolved "https://registry.yarnpkg.com/ajv/-/ajv-8.17.1.tgz#37d9a5c776af6bc92d7f4f9510eba4c0a60d11a6" integrity sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g== @@ -5379,6 +5471,11 @@ ast-types-flow@^0.0.8: resolved "https://registry.yarnpkg.com/ast-types-flow/-/ast-types-flow-0.0.8.tgz#0a85e1c92695769ac13a428bb653e7538bea27d6" integrity sha512-OH/2E5Fg20h2aPrbe+QL8JZQFko0YZaF+j4mnQ7BGhfavO7OpSLa8a0y9sBwomHdSbkhTS8TQNayBfnW5DwbvQ== +astral-regex@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/astral-regex/-/astral-regex-2.0.0.tgz#483143c567aeed4785759c0865786dc77d7d2e31" + integrity sha512-Z7tMw1ytTXt5jqMcOP+OQteU1VuNK9Y02uuJtKQ1Sv69jXQKKg5cibLwGJow8yzZP+eAc18EmLGPal0bp36rvQ== + asynckit@^0.4.0: version "0.4.0" resolved "https://registry.yarnpkg.com/asynckit/-/asynckit-0.4.0.tgz#c79ed97f7f34cb8f2ba1bc9790bcc366474b4b79" @@ -5534,6 +5631,11 @@ balanced-match@^1.0.0: resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.2.tgz#e83e3a7e3f300b34cb9d87f615fa0cbf357690ee" integrity sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw== +balanced-match@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-2.0.0.tgz#dc70f920d78db8b858535795867bf48f820633d9" + integrity sha512-1ugUSr8BHXRnK23KfuYS+gVMC3LB8QGH9W1iGtDPsNWoQbgtXSExkBu2aDR4epiGWZOjZsj6lDl/N/AqqTC3UA== + bare-events@^2.2.0: version "2.5.0" resolved "https://registry.yarnpkg.com/bare-events/-/bare-events-2.5.0.tgz#305b511e262ffd8b9d5616b056464f8e1b3329cc" @@ -6062,6 +6164,11 @@ color@^4.2.3: color-convert "^2.0.1" color-string "^1.9.0" +colord@^2.9.3: + version "2.9.3" + resolved "https://registry.yarnpkg.com/colord/-/colord-2.9.3.tgz#4f8ce919de456f1d5c1c368c307fe20f3e59fb43" + integrity sha512-jeC1axXpnb0/2nn/Y1LPuLdgXBLH7aDcHu4KEKfqw3CUhX7ZpfBSlPKyqXE6btIgEzfWtrX3/tyBCaCvXvMkOw== + colorette@^2.0.14: version "2.0.20" resolved "https://registry.yarnpkg.com/colorette/-/colorette-2.0.20.tgz#9eb793e6833067f7235902fcd3b09917a000a95a" @@ -6342,6 +6449,11 @@ css-color-keywords@^1.0.0: resolved "https://registry.yarnpkg.com/css-color-keywords/-/css-color-keywords-1.0.0.tgz#fea2616dc676b2962686b3af8dbdbe180b244e05" integrity sha512-FyyrDHZKEjXDpNJYvVsV960FiqQyXc/LlYmsxl2BcdMb2WPx0OGRVgTg55rPSyLSNMqP52R9r8geSp7apN3Ofg== +css-functions-list@^3.2.3: + version "3.2.3" + resolved "https://registry.yarnpkg.com/css-functions-list/-/css-functions-list-3.2.3.tgz#95652b0c24f0f59b291a9fc386041a19d4f40dbe" + integrity sha512-IQOkD3hbR5KrN93MtcYuad6YPuTSUhntLHDuLEbFWE+ff2/XSZNdZG+LcbbIW5AXKg/WFIfYItIzVoHngHXZzA== + css-has-pseudo@^7.0.1: version "7.0.1" resolved "https://registry.yarnpkg.com/css-has-pseudo/-/css-has-pseudo-7.0.1.tgz#adbb51821e51f7a7c1d2df4d12827870cc311137" @@ -6398,6 +6510,14 @@ css-tree@^2.3.1: mdn-data "2.0.30" source-map-js "^1.0.1" +css-tree@^3.0.1: + version "3.0.1" + resolved "https://registry.yarnpkg.com/css-tree/-/css-tree-3.0.1.tgz#bea6deaea60bb5bcf416adfb1ecf607a8d9471f6" + integrity sha512-8Fxxv+tGhORlshCdCwnNJytvlvq46sOLSYEx2ZIGurahWvMucSRnyjPA3AmrMq4VPRYbHVpWj5VkiVasrM2H4Q== + dependencies: + mdn-data "2.12.1" + source-map-js "^1.0.1" + css-tree@~2.2.0: version "2.2.1" resolved "https://registry.yarnpkg.com/css-tree/-/css-tree-2.2.1.tgz#36115d382d60afd271e377f9c5f67d02bd48c032" @@ -7121,7 +7241,7 @@ esbuild@^0.21.3: "@esbuild/win32-ia32" "0.21.5" "@esbuild/win32-x64" "0.21.5" -esbuild@^0.23.0: +esbuild@^0.23.0, esbuild@~0.23.0: version "0.23.1" resolved "https://registry.yarnpkg.com/esbuild/-/esbuild-0.23.1.tgz#40fdc3f9265ec0beae6f59824ade1bd3d3d2dab8" integrity sha512-VVNz/9Sa0bs5SELtn3f7qhJCDPCF5oMEl5cO9/SSinpE9hbPVvxbd572HH5AKiP7WD8INO53GgfDDhRjkylHEg== @@ -7958,7 +8078,7 @@ fast-xml-parser@4.4.1: dependencies: strnum "^1.0.5" -fastest-levenshtein@^1.0.12: +fastest-levenshtein@^1.0.12, fastest-levenshtein@^1.0.16: version "1.0.16" resolved "https://registry.yarnpkg.com/fastest-levenshtein/-/fastest-levenshtein-1.0.16.tgz#210e61b6ff181de91ea9b3d1b84fdedd47e034e5" integrity sha512-eRnCtTTtGZFpQCwhJiUOuxPQWRXVKYDn0b2PeHfXL6/Zi53SLAzAHfVhVWK2AryC/WH05kGfxhFIPvTF0SXQzg== @@ -7989,6 +8109,13 @@ file-entry-cache@^8.0.0: dependencies: flat-cache "^4.0.0" +file-entry-cache@^9.1.0: + version "9.1.0" + resolved "https://registry.yarnpkg.com/file-entry-cache/-/file-entry-cache-9.1.0.tgz#2e66ad98ce93f49aed1b178c57b0b5741591e075" + integrity sha512-/pqPFG+FdxWQj+/WSuzXSDaNzxgTLr/OrR1QuqfEZzDakpdYE70PwUxL7BPUa8hpjbvY1+qvCl8k+8Tq34xJgg== + dependencies: + flat-cache "^5.0.0" + file-loader@^6.2.0: version "6.2.0" resolved "https://registry.yarnpkg.com/file-loader/-/file-loader-6.2.0.tgz#baef7cf8e1840df325e4390b4484879480eebe4d" @@ -8067,12 +8194,20 @@ flat-cache@^4.0.0: flatted "^3.2.9" keyv "^4.5.4" +flat-cache@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/flat-cache/-/flat-cache-5.0.0.tgz#26c4da7b0f288b408bb2b506b2cb66c240ddf062" + integrity sha512-JrqFmyUl2PnPi1OvLyTVHnQvwQ0S+e6lGSwu8OkAZlSaNIZciTY2H/cOOROxsBA1m/LZNHDsqAgDZt6akWcjsQ== + dependencies: + flatted "^3.3.1" + keyv "^4.5.4" + flat@^5.0.2: version "5.0.2" resolved "https://registry.yarnpkg.com/flat/-/flat-5.0.2.tgz#8ca6fe332069ffa9d324c327198c598259ceb241" integrity sha512-b6suED+5/3rTpUBdG1gupIl8MPFCAMA0QXwmljLhvCUKcUvdE4gWky9zpuGCcXHOsz4J9wPGNWq6OKpmIzz3hQ== -flatted@^3.2.9: +flatted@^3.2.9, flatted@^3.3.1: version "3.3.2" resolved "https://registry.yarnpkg.com/flatted/-/flatted-3.3.2.tgz#adba1448a9841bec72b42c532ea23dbbedef1a27" integrity sha512-AiwGJM8YcNOaobumgtng+6NHuOqC3A7MixFeDafM3X9cIUM+xUXoS5Vfgf+OihAYe20fxqNM9yPBXJzRtZ/4eA== @@ -8450,6 +8585,22 @@ glob@^7.1.2, glob@^7.1.3: once "^1.3.0" path-is-absolute "^1.0.0" +global-modules@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/global-modules/-/global-modules-2.0.0.tgz#997605ad2345f27f51539bea26574421215c7780" + integrity sha512-NGbfmJBp9x8IxyJSd1P+otYK8vonoJactOogrVfFRIAEY1ukil8RSKDz2Yo7wh1oihl51l/r6W4epkeKJHqL8A== + dependencies: + global-prefix "^3.0.0" + +global-prefix@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/global-prefix/-/global-prefix-3.0.0.tgz#fc85f73064df69f50421f47f883fe5b913ba9b97" + integrity sha512-awConJSVCHVGND6x3tmMaKcQvwXLhjdkmomy2W+Goaui8YPgYgXJZewhg3fWC+DlfqqQuWg8AwqjGTD2nAPVWg== + dependencies: + ini "^1.3.5" + kind-of "^6.0.2" + which "^1.3.1" + globals@^11.1.0: version "11.12.0" resolved "https://registry.yarnpkg.com/globals/-/globals-11.12.0.tgz#ab8795338868a0babd8525758018c2a7eb95c42e" @@ -8502,6 +8653,11 @@ globby@^14.0.0: slash "^5.1.0" unicorn-magic "^0.1.0" +globjoin@^0.1.4: + version "0.1.4" + resolved "https://registry.yarnpkg.com/globjoin/-/globjoin-0.1.4.tgz#2f4494ac8919e3767c5cbb691e9f463324285d43" + integrity sha512-xYfnw62CKG8nLkZBfWbhWwDw02CHty86jfPcc2cr3ZfeuK9ysoVPPEUxf21bAD/rWAgk52SuBrLJlefNy8mvFg== + globrex@^0.1.2: version "0.1.2" resolved "https://registry.yarnpkg.com/globrex/-/globrex-0.1.2.tgz#dd5d9ec826232730cd6793a5e33a9302985e6098" @@ -8792,7 +8948,7 @@ html-parse-stringify@^3.0.1: dependencies: void-elements "3.1.0" -html-tags@^3.0.0: +html-tags@^3.0.0, html-tags@^3.3.1: version "3.3.1" resolved "https://registry.yarnpkg.com/html-tags/-/html-tags-3.3.1.tgz#a04026a18c882e4bba8a01a3d39cfe465d40b5ce" integrity sha512-ztqyC3kLto0e9WbNp0aeP+M3kTt+nbaIveGmUxAtZa+8iFgKLUOD4YKM5j+f3QD89bra7UeumolZHKuOXnTmeQ== @@ -8955,6 +9111,11 @@ ignore@^5.1.1, ignore@^5.2.0, ignore@^5.2.4, ignore@^5.3.1: resolved "https://registry.yarnpkg.com/ignore/-/ignore-5.3.2.tgz#3cd40e729f3643fd87cb04e50bf0eb722bc596f5" integrity sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g== +ignore@^6.0.2: + version "6.0.2" + resolved "https://registry.yarnpkg.com/ignore/-/ignore-6.0.2.tgz#77cccb72a55796af1b6d2f9eb14fa326d24f4283" + integrity sha512-InwqeHHN2XpumIkMvpl/DCJVrAHgCsG5+cn1XlnLWGwtZBm8QJfSusItfrwx81CTp5agNZqpKU2J/ccC5nGT4A== + immutable@^5.0.2, immutable@^5.0.3: version "5.0.3" resolved "https://registry.yarnpkg.com/immutable/-/immutable-5.0.3.tgz#aa037e2313ea7b5d400cd9298fa14e404c933db1" @@ -8999,7 +9160,7 @@ inherits@2, inherits@2.0.4, inherits@^2.0.0, inherits@^2.0.3, inherits@^2.0.4, i resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c" integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ== -ini@~1.3.0: +ini@^1.3.5, ini@~1.3.0: version "1.3.8" resolved "https://registry.yarnpkg.com/ini/-/ini-1.3.8.tgz#a29da425b48806f34767a4efce397269af28432c" integrity sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew== @@ -9287,6 +9448,11 @@ is-plain-object@^2.0.4: dependencies: isobject "^3.0.1" +is-plain-object@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/is-plain-object/-/is-plain-object-5.0.0.tgz#4427f50ab3429e9025ea7d52e9043a9ef4159344" + integrity sha512-VRSzKkbMm5jMDoKLbltAkFQ5Qr7VDiTFGXxYFXXowVj387GeGNOCsOH6Msy00SGZ3Fp84b1Naa1psqgcCIEP5Q== + is-port-reachable@4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/is-port-reachable/-/is-port-reachable-4.0.0.tgz#dac044091ef15319c8ab2f34604d8794181f8c2d" @@ -9740,6 +9906,11 @@ kind-of@^6.0.2: resolved "https://registry.yarnpkg.com/kind-of/-/kind-of-6.0.3.tgz#07c05034a6c349fa06e24fa35aa76db4580ce4dd" integrity sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw== +known-css-properties@^0.35.0: + version "0.35.0" + resolved "https://registry.yarnpkg.com/known-css-properties/-/known-css-properties-0.35.0.tgz#f6f8e40ab4e5700fa32f5b2ef5218a56bc853bd6" + integrity sha512-a/RAk2BfKk+WFGhhOCAYqSiFLc34k8Mt/6NWRI4joER0EYUzXIcFivjjnoD3+XU1DggLn/tZc3DOAgke7l8a4A== + language-subtag-registry@^0.3.20: version "0.3.23" resolved "https://registry.yarnpkg.com/language-subtag-registry/-/language-subtag-registry-0.3.23.tgz#23529e04d9e3b74679d70142df3fd2eb6ec572e7" @@ -9852,6 +10023,11 @@ lodash.merge@^4.6.2: resolved "https://registry.yarnpkg.com/lodash.merge/-/lodash.merge-4.6.2.tgz#558aa53b43b661e1925a0afdfa36a9a1085fe57a" integrity sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ== +lodash.truncate@^4.4.2: + version "4.4.2" + resolved "https://registry.yarnpkg.com/lodash.truncate/-/lodash.truncate-4.4.2.tgz#5a350da0b1113b837ecfffd5812cbe58d6eae193" + integrity sha512-jttmRe7bRse52OsWIMDLaXxWqRAmtIUccAQ3garviCqJjafXOfNMO0yMfNpdD6zbGaTU0P5Nz7e7gAT6cKmJRw== + lodash.uniq@4.5.0, lodash.uniq@^4.5.0: version "4.5.0" resolved "https://registry.yarnpkg.com/lodash.uniq/-/lodash.uniq-4.5.0.tgz#d0225373aeb652adc1bc82e4945339a842754773" @@ -9994,6 +10170,11 @@ matcher-collection@^2.0.0: "@types/minimatch" "^3.0.3" minimatch "^3.0.2" +mathml-tag-names@^2.1.3: + version "2.1.3" + resolved "https://registry.yarnpkg.com/mathml-tag-names/-/mathml-tag-names-2.1.3.tgz#4ddadd67308e780cf16a47685878ee27b736a0a3" + integrity sha512-APMBEanjybaPzUrfqU0IMU5I0AswKMH7k8OTLs0vvV4KZpExkTkY87nR/zpbuTPj+gARop7aGUbl11pnDfW6xg== + mdast-squeeze-paragraphs@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/mdast-squeeze-paragraphs/-/mdast-squeeze-paragraphs-4.0.0.tgz#7c4c114679c3bee27ef10b58e2e015be79f1ef97" @@ -10131,6 +10312,16 @@ mdn-data@2.0.30: resolved "https://registry.yarnpkg.com/mdn-data/-/mdn-data-2.0.30.tgz#ce4df6f80af6cfbe218ecd5c552ba13c4dfa08cc" integrity sha512-GaqWWShW4kv/G9IEucWScBx9G1/vsFZZJUO+tD26M8J8z3Kw5RDQjaoZe03YAClgeS/SWPOcb4nkFBTEi5DUEA== +mdn-data@2.12.1: + version "2.12.1" + resolved "https://registry.yarnpkg.com/mdn-data/-/mdn-data-2.12.1.tgz#10cb462215c13d95c92ff60d0fb3becac1bbb924" + integrity sha512-rsfnCbOHjqrhWxwt5/wtSLzpoKTzW7OXdT5lLOIH1OTYhWu9rRJveGq0sKvDZODABH7RX+uoR+DYcpFnq4Tf6Q== + +mdn-data@^2.12.2: + version "2.13.0" + resolved "https://registry.yarnpkg.com/mdn-data/-/mdn-data-2.13.0.tgz#10af1de5d0d5e4ceb4fe01f3086b34f1178473d9" + integrity sha512-OmD1FDyP706JqPqtLqgev/QCK0qudBdUuKKag6InQ/elEw3Cm2AhXYktcSggdc/vWniYqIsofkcteMEOioW5vQ== + mdurl@^1.0.0: version "1.0.1" resolved "https://registry.yarnpkg.com/mdurl/-/mdurl-1.0.1.tgz#fe85b2ec75a59037f2adfec100fd6c601761152e" @@ -10168,6 +10359,11 @@ memorystream@^0.3.1: resolved "https://registry.yarnpkg.com/memorystream/-/memorystream-0.3.1.tgz#86d7090b30ce455d63fbae12dda51a47ddcaf9b2" integrity sha512-S3UwM3yj5mtUSEfP41UZmt/0SCoVYUcU1rkXv+BQ5Ig8ndL4sPoJNBUJERafdPb5jjHJGuMgytgKvKIf58XNBw== +meow@^13.2.0: + version "13.2.0" + resolved "https://registry.yarnpkg.com/meow/-/meow-13.2.0.tgz#6b7d63f913f984063b3cc261b6e8800c4cd3474f" + integrity sha512-pxQJQzB6djGPXh08dacEloMFopsOqGVRKFPYvPOt9XDZ1HasbgDZA74CJGreSU4G3Ak7EFJGoiH2auq+yXISgA== + merge-anything@^6.0.2: version "6.0.2" resolved "https://registry.yarnpkg.com/merge-anything/-/merge-anything-6.0.2.tgz#b6758c8b6d2d0d0b582a6cdf1120044e2aa42d96" @@ -10407,7 +10603,7 @@ micromark@^4.0.0: micromark-util-symbol "^2.0.0" micromark-util-types "^2.0.0" -micromatch@^4.0.4, micromatch@^4.0.5: +micromatch@^4.0.4, micromatch@^4.0.5, micromatch@^4.0.8: version "4.0.8" resolved "https://registry.yarnpkg.com/micromatch/-/micromatch-4.0.8.tgz#d66fa18f3a47076789320b9b1af32bd86d9fa202" integrity sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA== @@ -11385,6 +11581,11 @@ postcss-logical@^8.0.0: dependencies: postcss-value-parser "^4.2.0" +postcss-media-query-parser@^0.2.3: + version "0.2.3" + resolved "https://registry.yarnpkg.com/postcss-media-query-parser/-/postcss-media-query-parser-0.2.3.tgz#27b39c6f4d94f81b1a73b8f76351c609e5cef244" + integrity sha512-3sOlxmbKcSHMjlUXQZKQ06jOswE7oVkXPxmZdoB1r5l0q6gTFTQSHxNxOrCccElbW7dxNytifNEo8qidX2Vsig== + postcss-modules-extract-imports@^3.1.0: version "3.1.0" resolved "https://registry.yarnpkg.com/postcss-modules-extract-imports/-/postcss-modules-extract-imports-3.1.0.tgz#b4497cb85a9c0c4b5aabeb759bb25e8d89f15002" @@ -11527,6 +11728,21 @@ postcss-replace-overflow-wrap@^4.0.0: resolved "https://registry.yarnpkg.com/postcss-replace-overflow-wrap/-/postcss-replace-overflow-wrap-4.0.0.tgz#d2df6bed10b477bf9c52fab28c568b4b29ca4319" integrity sha512-KmF7SBPphT4gPPcKZc7aDkweHiKEEO8cla/GjcBK+ckKxiZslIu3C4GCRW3DNfL0o7yW7kMQu9xlZ1kXRXLXtw== +postcss-resolve-nested-selector@^0.1.6: + version "0.1.6" + resolved "https://registry.yarnpkg.com/postcss-resolve-nested-selector/-/postcss-resolve-nested-selector-0.1.6.tgz#3d84dec809f34de020372c41b039956966896686" + integrity sha512-0sglIs9Wmkzbr8lQwEyIzlDOOC9bGmfVKcJTaxv3vMmd3uo4o4DerC3En0bnmgceeql9BfC8hRkp7cg0fjdVqw== + +postcss-safe-parser@^7.0.1: + version "7.0.1" + resolved "https://registry.yarnpkg.com/postcss-safe-parser/-/postcss-safe-parser-7.0.1.tgz#36e4f7e608111a0ca940fd9712ce034718c40ec0" + integrity sha512-0AioNCJZ2DPYz5ABT6bddIqlhgwhpHZ/l65YAYo0BCIn0xiDpsnTHz0gnoTGk0OXZW0JRs+cDwL8u/teRdz+8A== + +postcss-scss@^4.0.9: + version "4.0.9" + resolved "https://registry.yarnpkg.com/postcss-scss/-/postcss-scss-4.0.9.tgz#a03c773cd4c9623cb04ce142a52afcec74806685" + integrity sha512-AjKOeiwAitL/MXxQW2DliT28EKukvvbEWx3LBmJIRN8KfBGZbRTxNYW0kSqi1COiTZ57nZ9NW06S6ux//N1c9A== + postcss-selector-not@^8.0.1: version "8.0.1" resolved "https://registry.yarnpkg.com/postcss-selector-not/-/postcss-selector-not-8.0.1.tgz#f2df9c6ac9f95e9fe4416ca41a957eda16130172" @@ -11534,6 +11750,14 @@ postcss-selector-not@^8.0.1: dependencies: postcss-selector-parser "^7.0.0" +postcss-selector-parser@^6.1.2: + version "6.1.2" + resolved "https://registry.yarnpkg.com/postcss-selector-parser/-/postcss-selector-parser-6.1.2.tgz#27ecb41fb0e3b6ba7a1ec84fff347f734c7929de" + integrity sha512-Q8qQfPiZ+THO/3ZrOrO0cJJKfpYCagtMUkXbnEfmgUjwXg6z/WBeOyS9APBBPCTSiDV+s4SwQGu8yFsiMRIudg== + dependencies: + cssesc "^3.0.0" + util-deprecate "^1.0.2" + postcss-selector-parser@^7.0.0: version "7.0.0" resolved "https://registry.yarnpkg.com/postcss-selector-parser/-/postcss-selector-parser-7.0.0.tgz#41bd8b56f177c093ca49435f65731befe25d6b9c" @@ -11565,7 +11789,7 @@ postcss@8.4.38: picocolors "^1.0.0" source-map-js "^1.2.0" -postcss@^8.4, postcss@^8.4.33, postcss@^8.4.43: +postcss@^8.4, postcss@^8.4.33, postcss@^8.4.43, postcss@^8.4.49: version "8.4.49" resolved "https://registry.yarnpkg.com/postcss/-/postcss-8.4.49.tgz#4ea479048ab059ab3ae61d082190fabfd994fe19" integrity sha512-OCVPnIObs4N29kxTjzLfUryOkvZEq+pf8jTF0lg8E7uETuWHA+v7j3c/xJmiqpX450191LlmZfUKkXxkTry7nA== @@ -12930,6 +13154,15 @@ slash@^5.1.0: resolved "https://registry.yarnpkg.com/slash/-/slash-5.1.0.tgz#be3adddcdf09ac38eebe8dcdc7b1a57a75b095ce" integrity sha512-ZA6oR3T/pEyuqwMgAKT0/hAv8oAXckzbkmR0UkUosQ+Mc4RxGoJkRmwHgHufaenlyAgE1Mxgpdcrf75y6XcnDg== +slice-ansi@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/slice-ansi/-/slice-ansi-4.0.0.tgz#500e8dd0fd55b05815086255b3195adf2a45fe6b" + integrity sha512-qMCMfhY040cVHT43K9BFygqYbUPFZKHOg7K73mtTWJRb8pyP3fzf4Ixd5SzdEJQ6MRUg/WBnOLxghZtKKurENQ== + dependencies: + ansi-styles "^4.0.0" + astral-regex "^2.0.0" + is-fullwidth-code-point "^3.0.0" + snake-case@^3.0.4: version "3.0.4" resolved "https://registry.yarnpkg.com/snake-case/-/snake-case-3.0.4.tgz#4f2bbd568e9935abdfd593f34c691dadb49c452c" @@ -13286,6 +13519,11 @@ style-loader@^4.0.0: resolved "https://registry.yarnpkg.com/style-loader/-/style-loader-4.0.0.tgz#0ea96e468f43c69600011e0589cb05c44f3b17a5" integrity sha512-1V4WqhhZZgjVAVJyt7TdDPZoPBPNHbekX4fWnCJL1yQukhCeZhJySUL+gL9y6sNdN95uEOS83Y55SqHcP7MzLA== +style-search@^0.1.0: + version "0.1.0" + resolved "https://registry.yarnpkg.com/style-search/-/style-search-0.1.0.tgz#7958c793e47e32e07d2b5cafe5c0bf8e12e77902" + integrity sha512-Dj1Okke1C3uKKwQcetra4jSuk0DqbzbYtXipzFlFMZtowbF1x7BKJwB9AayVMyFARvU8EDrZdcax4At/452cAg== + style-to-object@0.3.0, style-to-object@^0.3.0: version "0.3.0" resolved "https://registry.yarnpkg.com/style-to-object/-/style-to-object-0.3.0.tgz#b1b790d205991cc783801967214979ee19a76e46" @@ -13315,6 +13553,85 @@ styled-jsx@5.1.6: dependencies: client-only "0.0.1" +stylelint-config-recommended@^14.0.1: + version "14.0.1" + resolved "https://registry.yarnpkg.com/stylelint-config-recommended/-/stylelint-config-recommended-14.0.1.tgz#d25e86409aaf79ee6c6085c2c14b33c7e23c90c6" + integrity sha512-bLvc1WOz/14aPImu/cufKAZYfXs/A/owZfSMZ4N+16WGXLoX5lOir53M6odBxvhgmgdxCVnNySJmZKx73T93cg== + +stylelint-config-sass-guidelines@^12.1.0: + version "12.1.0" + resolved "https://registry.yarnpkg.com/stylelint-config-sass-guidelines/-/stylelint-config-sass-guidelines-12.1.0.tgz#6c7f108f2293684b9a6db5b290d293475d9fde22" + integrity sha512-NTxEtVT6uNSqRvq+A3ScyKhjUrY/Z845TnpWEwnMgIPZ/+/Waa4+51r6OPuQRMu4XZS3D8DK1UaT4TWFBvuuAw== + dependencies: + "@stylistic/stylelint-plugin" "^3.0.1" + postcss-scss "^4.0.9" + stylelint-scss "^6.2.1" + +stylelint-config-standard@^36.0.1: + version "36.0.1" + resolved "https://registry.yarnpkg.com/stylelint-config-standard/-/stylelint-config-standard-36.0.1.tgz#727cbb2a1ef3e210f5ce8329cde531129f156609" + integrity sha512-8aX8mTzJ6cuO8mmD5yon61CWuIM4UD8Q5aBcWKGSf6kg+EC3uhB+iOywpTK4ca6ZL7B49en8yanOFtUW0qNzyw== + dependencies: + stylelint-config-recommended "^14.0.1" + +stylelint-scss@^6.2.1: + version "6.10.0" + resolved "https://registry.yarnpkg.com/stylelint-scss/-/stylelint-scss-6.10.0.tgz#ba5b807793e145421e9879dd15ae672af6820a45" + integrity sha512-y03if6Qw9xBMoVaf7tzp5BbnYhYvudIKzURkhSHzcHG0bW0fAYvQpTUVJOe7DyhHaxeThBil4ObEMvGbV7+M+w== + dependencies: + css-tree "^3.0.1" + is-plain-object "^5.0.0" + known-css-properties "^0.35.0" + mdn-data "^2.12.2" + postcss-media-query-parser "^0.2.3" + postcss-resolve-nested-selector "^0.1.6" + postcss-selector-parser "^7.0.0" + postcss-value-parser "^4.2.0" + +stylelint@^16.11.0, stylelint@^16.8.2: + version "16.11.0" + resolved "https://registry.yarnpkg.com/stylelint/-/stylelint-16.11.0.tgz#7eb653b007dc0b4366dc3aa7bfa94ced0c52f087" + integrity sha512-zrl4IrKmjJQ+h9FoMp69UMCq5SxeHk0URhxUBj4d3ISzo/DplOFBJZc7t7Dr6otB+1bfbbKNLOmCDpzKSlW+Nw== + dependencies: + "@csstools/css-parser-algorithms" "^3.0.4" + "@csstools/css-tokenizer" "^3.0.3" + "@csstools/media-query-list-parser" "^4.0.2" + "@csstools/selector-specificity" "^5.0.0" + "@dual-bundle/import-meta-resolve" "^4.1.0" + balanced-match "^2.0.0" + colord "^2.9.3" + cosmiconfig "^9.0.0" + css-functions-list "^3.2.3" + css-tree "^3.0.1" + debug "^4.3.7" + fast-glob "^3.3.2" + fastest-levenshtein "^1.0.16" + file-entry-cache "^9.1.0" + global-modules "^2.0.0" + globby "^11.1.0" + globjoin "^0.1.4" + html-tags "^3.3.1" + ignore "^6.0.2" + imurmurhash "^0.1.4" + is-plain-object "^5.0.0" + known-css-properties "^0.35.0" + mathml-tag-names "^2.1.3" + meow "^13.2.0" + micromatch "^4.0.8" + normalize-path "^3.0.0" + picocolors "^1.1.1" + postcss "^8.4.49" + postcss-resolve-nested-selector "^0.1.6" + postcss-safe-parser "^7.0.1" + postcss-selector-parser "^7.0.0" + postcss-value-parser "^4.2.0" + resolve-from "^5.0.0" + string-width "^4.2.3" + supports-hyperlinks "^3.1.0" + svg-tags "^1.0.0" + table "^6.8.2" + write-file-atomic "^5.0.1" + stylis@4.2.0: version "4.2.0" resolved "https://registry.yarnpkg.com/stylis/-/stylis-4.2.0.tgz#79daee0208964c8fe695a42fcffcac633a211a51" @@ -13332,7 +13649,7 @@ supports-color@^5.3.0, supports-color@^5.5.0: dependencies: has-flag "^3.0.0" -supports-color@^7.1.0: +supports-color@^7.0.0, supports-color@^7.1.0: version "7.2.0" resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-7.2.0.tgz#1b7dcdcb32b8138801b3e478ba6a51caa89648da" integrity sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw== @@ -13346,6 +13663,14 @@ supports-color@^8.0.0: dependencies: has-flag "^4.0.0" +supports-hyperlinks@^3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/supports-hyperlinks/-/supports-hyperlinks-3.1.0.tgz#b56150ff0173baacc15f21956450b61f2b18d3ac" + integrity sha512-2rn0BZ+/f7puLOHZm1HOJfwBggfaHXUpPUSSG/SWM4TWp5KCfmNYwnC3hruy2rZlMnmWZ+QAGpZfchu3f3695A== + dependencies: + has-flag "^4.0.0" + supports-color "^7.0.0" + supports-preserve-symlinks-flag@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz#6eda4bd344a3c94aea376d4cc31bc77311039e09" @@ -13361,6 +13686,11 @@ svg-parser@^2.0.4: resolved "https://registry.yarnpkg.com/svg-parser/-/svg-parser-2.0.4.tgz#fdc2e29e13951736140b76cb122c8ee6630eb6b5" integrity sha512-e4hG1hRwoOdRb37cIMSgzNsxyzKfayW6VOflrwvR+/bzrkyxY/31WkbgnQpgtrNp1SdpJvpUAGTa/ZoiPNDuRQ== +svg-tags@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/svg-tags/-/svg-tags-1.0.0.tgz#58f71cee3bd519b59d4b2a843b6c7de64ac04764" + integrity sha512-ovssysQTa+luh7A5Weu3Rta6FJlFBBbInjOh722LIt6klpU2/HtdUbszju/G4devcvk8PGt7FCLv5wftu3THUA== + svgo@^3.0.2, svgo@^3.3.2: version "3.3.2" resolved "https://registry.yarnpkg.com/svgo/-/svgo-3.3.2.tgz#ad58002652dffbb5986fc9716afe52d869ecbda8" @@ -13392,6 +13722,17 @@ synckit@^0.9.1: "@pkgr/core" "^0.1.0" tslib "^2.6.2" +table@^6.8.2: + version "6.9.0" + resolved "https://registry.yarnpkg.com/table/-/table-6.9.0.tgz#50040afa6264141c7566b3b81d4d82c47a8668f5" + integrity sha512-9kY+CygyYM6j02t5YFHbNz2FN5QmYGv9zAjVp4lCDjlCw7amdckXlEt/bjMhUIfj4ThGRE4gCUH5+yGnNuPo5A== + dependencies: + ajv "^8.0.1" + lodash.truncate "^4.4.2" + slice-ansi "^4.0.0" + string-width "^4.2.3" + strip-ansi "^6.0.1" + tapable@^2.1.1, tapable@^2.2.0, tapable@^2.2.1: version "2.2.1" resolved "https://registry.yarnpkg.com/tapable/-/tapable-2.2.1.tgz#1967a73ef4060a82f12ab96af86d52fdb76eeca0" @@ -13667,6 +14008,16 @@ tsutils@^3.21.0: dependencies: tslib "^1.8.1" +tsx@^4.19.2: + version "4.19.2" + resolved "https://registry.yarnpkg.com/tsx/-/tsx-4.19.2.tgz#2d7814783440e0ae42354d0417d9c2989a2ae92c" + integrity sha512-pOUl6Vo2LUq/bSa8S5q7b91cgNSjctn9ugq/+Mvow99qW6x/UZYwzxy/3NmqoT66eHYfCVvFvACC58UBPFf28g== + dependencies: + esbuild "~0.23.0" + get-tsconfig "^4.7.5" + optionalDependencies: + fsevents "~2.3.3" + tunnel-agent@^0.6.0: version "0.6.0" resolved "https://registry.yarnpkg.com/tunnel-agent/-/tunnel-agent-0.6.0.tgz#27a5dea06b36b04a0a9966774b290868f0fc40fd" @@ -13777,6 +14128,15 @@ typeface-open-sans@^1.1.13: resolved "https://registry.yarnpkg.com/typeface-open-sans/-/typeface-open-sans-1.1.13.tgz#32a09ebd7df59601e01ad81216f98ce641eeafd1" integrity sha512-lVGVHvYl7UJDFB9vN8r7NHw3sVm7Rjeow6b9AeABc/J+2mDaCkmcdVtw3QZnsJW39P+xm5zeggIj9gLHYGn9Iw== +typescript-eslint@8.17.0: + version "8.17.0" + resolved "https://registry.yarnpkg.com/typescript-eslint/-/typescript-eslint-8.17.0.tgz#fa4033c26b3b40f778287bc12918d985481b220b" + integrity sha512-409VXvFd/f1br1DCbuKNFqQpXICoTB+V51afcwG1pn1a3Cp92MqAUges3YjwEdQ0cMUoCIodjVDAYzyD8h3SYA== + dependencies: + "@typescript-eslint/eslint-plugin" "8.17.0" + "@typescript-eslint/parser" "8.17.0" + "@typescript-eslint/utils" "8.17.0" + typescript@5.6.2: version "5.6.2" resolved "https://registry.yarnpkg.com/typescript/-/typescript-5.6.2.tgz#d1de67b6bef77c41823f822df8f0b3bcff60a5a0" @@ -14601,7 +14961,7 @@ which-typed-array@^1.1.13, which-typed-array@^1.1.14, which-typed-array@^1.1.15: gopd "^1.0.1" has-tostringtag "^1.0.2" -which@^1.2.9: +which@^1.2.9, which@^1.3.1: version "1.3.1" resolved "https://registry.yarnpkg.com/which/-/which-1.3.1.tgz#a45043d54f5805316da8d62f9f50918d3da70b0a" integrity sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ== @@ -14672,6 +15032,14 @@ wrappy@1: resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f" integrity sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ== +write-file-atomic@^5.0.1: + version "5.0.1" + resolved "https://registry.yarnpkg.com/write-file-atomic/-/write-file-atomic-5.0.1.tgz#68df4717c55c6fa4281a7860b4c2ba0a6d2b11e7" + integrity sha512-+QU2zd6OTD8XWIJCbffaiQeH9U73qIqafo1x6V1snCWYGJf6cVE0cDR4D8xRzcEnfI21IFrUPzPGtcPf8AC+Rw== + dependencies: + imurmurhash "^0.1.4" + signal-exit "^4.0.1" + ws@^8.18.0: version "8.18.0" resolved "https://registry.yarnpkg.com/ws/-/ws-8.18.0.tgz#0d7505a6eafe2b0e712d232b42279f53bc289bbc"