From d9df2f215ac79ad250a2571f9bb727b177db3bdc Mon Sep 17 00:00:00 2001 From: Zephyr Lykos Date: Sun, 19 Jan 2025 14:15:08 +0800 Subject: [PATCH] cleanup: wip 6 --- .devcontainer/docker-compose.yml | 5 +- .eslintignore | 9 - .gitpod.yml | 27 +- .vscode/launch.json | 72 +- eslint.config.js | 11 + package.json | 132 +- public/.gitignore | 1 + public/hot | 1 + resources/assets/src/components/Alert.tsx | 18 +- .../assets/src/components/ButtonEdit.tsx | 5 +- resources/assets/src/components/Captcha.tsx | 90 +- .../assets/src/components/DarkModeButton.tsx | 2 +- .../assets/src/components/EmailSuggestion.tsx | 107 +- resources/assets/src/components/FileInput.tsx | 7 +- resources/assets/src/components/Modal.tsx | 90 +- resources/assets/src/components/ModalBody.tsx | 3 +- .../assets/src/components/ModalContent.tsx | 5 +- .../assets/src/components/ModalFooter.tsx | 42 +- .../assets/src/components/ModalHeader.tsx | 32 +- .../assets/src/components/ModalInput.tsx | 62 +- .../assets/src/components/Pagination.tsx | 102 +- .../assets/src/components/PaginationItem.tsx | 2 +- resources/assets/src/components/Toast.tsx | 6 +- resources/assets/src/components/Viewer.tsx | 27 +- resources/assets/src/index.tsx | 21 +- resources/assets/src/scripts/app.ts | 5 +- resources/assets/src/scripts/darkMode.tsx | 2 +- .../assets/src/scripts/emailVerification.tsx | 2 +- resources/assets/src/scripts/event.ts | 9 +- .../src/scripts/hooks/useBlessingExtra.ts | 2 +- .../src/scripts/hooks/useIsLargeScreen.ts | 2 +- .../assets/src/scripts/hooks/useTexture.ts | 2 +- .../assets/src/scripts/hooks/useTween.ts | 2 +- resources/assets/src/scripts/i18n.ts | 9 +- resources/assets/src/scripts/init.ts | 2 +- resources/assets/src/scripts/logout.ts | 2 +- resources/assets/src/scripts/modal.tsx | 12 +- resources/assets/src/scripts/net.ts | 6 +- resources/assets/src/scripts/notification.tsx | 4 +- resources/assets/src/scripts/notify.ts | 2 +- resources/assets/src/scripts/textureUtils.ts | 4 +- resources/assets/src/scripts/toast.tsx | 34 +- resources/assets/src/shims.d.ts | 56 +- .../assets/src/views/admin/Customization.ts | 28 +- resources/assets/src/views/admin/Dashboard.ts | 12 +- .../views/admin/PlayersManagement/Card.tsx | 29 +- .../admin/PlayersManagement/LoadingCard.tsx | 2 +- .../PlayersManagement/ModalUpdateTexture.tsx | 8 +- .../src/views/admin/PlayersManagement/Row.tsx | 16 +- .../views/admin/PlayersManagement/index.tsx | 102 +- .../views/admin/PlayersManagement/styles.ts | 2 +- .../views/admin/PluginsManagement/InfoBox.tsx | 13 +- .../views/admin/PluginsManagement/index.tsx | 63 +- .../src/views/admin/PluginsMarket/Row.tsx | 122 +- .../src/views/admin/PluginsMarket/index.tsx | 97 +- .../admin/ReportsManagement/ImageBox.tsx | 42 +- .../views/admin/ReportsManagement/index.tsx | 56 +- .../src/views/admin/Translations/Row.tsx | 6 +- .../src/views/admin/Translations/index.tsx | 58 +- resources/assets/src/views/admin/Update.ts | 6 +- .../src/views/admin/UsersManagement/Card.tsx | 27 +- .../views/admin/UsersManagement/Header.tsx | 2 +- .../admin/UsersManagement/LoadingCard.tsx | 4 +- .../src/views/admin/UsersManagement/Row.tsx | 32 +- .../src/views/admin/UsersManagement/index.tsx | 118 +- .../src/views/admin/UsersManagement/styles.ts | 4 +- resources/assets/src/views/auth/Forgot.tsx | 26 +- resources/assets/src/views/auth/Login.tsx | 38 +- .../assets/src/views/auth/Registration.tsx | 103 +- resources/assets/src/views/auth/Reset.tsx | 24 +- .../src/views/skinlib/Show/addClosetItem.ts | 6 +- .../assets/src/views/skinlib/Show/index.tsx | 288 +- .../src/views/skinlib/SkinLibrary/Button.tsx | 4 +- .../skinlib/SkinLibrary/FilterSelector.tsx | 8 +- .../src/views/skinlib/SkinLibrary/Item.tsx | 14 +- .../src/views/skinlib/SkinLibrary/index.tsx | 88 +- resources/assets/src/views/skinlib/Upload.tsx | 57 +- .../src/views/user/Closet/ClosetItem.tsx | 13 +- .../src/views/user/Closet/ModalApply.tsx | 92 +- .../src/views/user/Closet/Previewer.tsx | 20 +- .../assets/src/views/user/Closet/index.tsx | 98 +- .../src/views/user/Closet/removeClosetItem.ts | 4 +- .../src/views/user/Dashboard/InfoBox.tsx | 7 +- .../src/views/user/Dashboard/SignButton.tsx | 6 +- .../assets/src/views/user/Dashboard/index.tsx | 54 +- .../src/views/user/Dashboard/scoreUtils.ts | 4 +- .../src/views/user/OAuth/ModalCreate.tsx | 8 +- resources/assets/src/views/user/OAuth/Row.tsx | 2 +- .../assets/src/views/user/OAuth/index.tsx | 28 +- .../src/views/user/Players/ModalAddPlayer.tsx | 14 +- .../src/views/user/Players/ModalReset.tsx | 8 +- .../src/views/user/Players/Previewer.tsx | 36 +- .../assets/src/views/user/Players/Row.tsx | 13 +- .../src/views/user/Players/Viewer2d.tsx | 18 +- .../assets/src/views/user/Players/index.tsx | 70 +- .../assets/src/views/user/profile/index.ts | 12 +- .../assets/src/views/user/profile/password.ts | 2 +- .../src/views/user/profile/resetAvatar.ts | 2 +- .../src/views/widgets/EmailVerification.tsx | 32 +- .../src/views/widgets/NotificationsList.tsx | 17 +- resources/assets/src/vite-env.d.ts | 2 + resources/assets/src/webpack.d.ts | 15 - resources/assets/tests/__mocks__/file.ts | 2 +- .../assets/tests/__mocks__/lodash.debounce.ts | 2 +- resources/assets/tests/__mocks__/reaptcha.tsx | 20 +- .../assets/tests/__mocks__/skinview-utils.ts | 4 +- .../assets/tests/__mocks__/skinview3d.ts | 75 +- resources/assets/tests/__mocks__/style.ts | 2 +- .../assets/tests/components/Captcha.test.tsx | 8 +- .../tests/components/DarkModeButton.test.tsx | 10 +- .../tests/components/EmailSuggestion.test.tsx | 18 +- .../tests/components/FileInput.test.tsx | 32 +- .../assets/tests/components/Modal.test.tsx | 144 +- .../tests/components/Pagination.test.tsx | 47 +- .../assets/tests/components/Viewer.test.tsx | 24 +- .../tests/scripts/cli/AptCommand.test.ts | 4 +- .../tests/scripts/cli/ClosetCommand.test.ts | 4 +- .../tests/scripts/cli/DnfCommand.test.ts | 4 +- .../tests/scripts/cli/PacmanCommand.test.ts | 6 +- .../tests/scripts/cli/RmCommand.test.ts | 12 +- .../assets/tests/scripts/cli/Spinner.test.ts | 4 +- resources/assets/tests/scripts/event.test.ts | 6 +- .../assets/tests/scripts/homePage.test.ts | 8 +- resources/assets/tests/scripts/i18n.test.ts | 4 +- resources/assets/tests/scripts/logout.test.ts | 2 +- resources/assets/tests/scripts/modal.test.ts | 6 +- resources/assets/tests/scripts/net.test.ts | 14 +- resources/assets/tests/scripts/toast.test.ts | 20 +- resources/assets/tests/setup.ts | 55 +- resources/assets/tests/ts-shims/net.ts | 22 +- resources/assets/tests/ts-shims/notify.ts | 12 +- .../assets/tests/ts-shims/textureUtils.ts | 2 +- resources/assets/tests/tsconfig.json | 8 +- resources/assets/tests/utils.ts | 22 +- .../tests/views/admin/Customization.test.ts | 14 +- .../views/admin/PlayersManagement.test.tsx | 111 +- .../views/admin/PluginsManagement.test.tsx | 52 +- .../tests/views/admin/PluginsMarket.test.tsx | 57 +- .../views/admin/ReportsManagement.test.tsx | 53 +- .../tests/views/admin/Translations.test.tsx | 37 +- .../assets/tests/views/admin/Update.test.ts | 8 +- .../views/admin/UsersManagement.test.tsx | 173 +- .../assets/tests/views/auth/Forgot.test.tsx | 10 +- .../assets/tests/views/auth/Login.test.tsx | 37 +- .../tests/views/auth/Registration.test.tsx | 37 +- .../assets/tests/views/auth/Reset.test.tsx | 20 +- .../assets/tests/views/skinlib/Show.test.tsx | 94 +- .../tests/views/skinlib/SkinLibrary.test.tsx | 76 +- .../tests/views/skinlib/Upload.test.tsx | 37 +- .../assets/tests/views/user/Closet.test.tsx | 101 +- .../tests/views/user/Dashboard.test.tsx | 30 +- .../assets/tests/views/user/OAuth.test.tsx | 51 +- .../assets/tests/views/user/Players.test.tsx | 105 +- .../views/user/profile/deleteAccount.test.ts | 8 +- .../tests/views/user/profile/email.test.ts | 8 +- .../tests/views/user/profile/nickname.test.ts | 8 +- .../tests/views/user/profile/password.test.ts | 8 +- .../views/user/profile/resetAvatar.test.ts | 6 +- .../views/widgets/EmailVerification.test.tsx | 17 +- .../views/widgets/NotificationsList.test.tsx | 12 +- resources/lang/de_DE/admin.yml | 6 +- resources/lang/de_DE/front-end.yml | 2 +- resources/lang/de_DE/options.yml | 2 +- resources/lang/de_DE/setup.yml | 2 +- resources/lang/de_DE/validation.yml | 36 +- resources/lang/el_GR/admin.yml | 6 +- resources/lang/el_GR/front-end.yml | 2 +- resources/lang/el_GR/options.yml | 2 +- resources/lang/el_GR/setup.yml | 2 +- resources/lang/el_GR/validation.yml | 36 +- resources/lang/en/admin.yml | 8 +- resources/lang/en/options.yml | 6 +- resources/lang/en/setup.yml | 2 +- resources/lang/en/validation.yml | 6 +- resources/lang/es_ES/admin.yml | 12 +- resources/lang/es_ES/front-end.yml | 34 +- resources/lang/es_ES/options.yml | 4 +- resources/lang/es_ES/setup.yml | 2 +- resources/lang/es_ES/skinlib.yml | 2 +- resources/lang/es_ES/user.yml | 4 +- resources/lang/es_ES/validation.yml | 36 +- resources/lang/fr_FR/admin.yml | 4 +- resources/lang/fr_FR/front-end.yml | 2 +- resources/lang/fr_FR/options.yml | 2 +- resources/lang/fr_FR/setup.yml | 2 +- resources/lang/fr_FR/validation.yml | 36 +- resources/lang/it_IT/admin.yml | 6 +- resources/lang/it_IT/front-end.yml | 2 +- resources/lang/it_IT/options.yml | 2 +- resources/lang/it_IT/setup.yml | 2 +- resources/lang/it_IT/validation.yml | 36 +- resources/lang/ja_JP/admin.yml | 6 +- resources/lang/ja_JP/front-end.yml | 4 +- resources/lang/ja_JP/options.yml | 2 +- resources/lang/ja_JP/setup.yml | 2 +- resources/lang/ja_JP/validation.yml | 36 +- resources/lang/ko_KR/admin.yml | 6 +- resources/lang/ko_KR/front-end.yml | 4 +- resources/lang/ko_KR/options.yml | 2 +- resources/lang/ko_KR/setup.yml | 2 +- resources/lang/ko_KR/validation.yml | 36 +- resources/lang/nl_NL/admin.yml | 6 +- resources/lang/nl_NL/front-end.yml | 2 +- resources/lang/nl_NL/options.yml | 2 +- resources/lang/nl_NL/setup.yml | 2 +- resources/lang/nl_NL/validation.yml | 36 +- resources/lang/pt_PT/admin.yml | 6 +- resources/lang/pt_PT/front-end.yml | 2 +- resources/lang/pt_PT/options.yml | 2 +- resources/lang/pt_PT/setup.yml | 2 +- resources/lang/pt_PT/validation.yml | 36 +- resources/lang/ru_RU/admin.yml | 6 +- resources/lang/ru_RU/front-end.yml | 2 +- resources/lang/ru_RU/options.yml | 4 +- resources/lang/ru_RU/setup.yml | 2 +- resources/lang/ru_RU/validation.yml | 36 +- resources/lang/zh_CN/admin.yml | 6 +- resources/lang/zh_CN/front-end.yml | 36 +- resources/lang/zh_CN/options.yml | 4 +- resources/lang/zh_CN/setup.yml | 4 +- resources/lang/zh_CN/validation.yml | 36 +- resources/lang/zh_TW/admin.yml | 6 +- resources/lang/zh_TW/auth.yml | 2 +- resources/lang/zh_TW/front-end.yml | 26 +- resources/lang/zh_TW/options.yml | 4 +- resources/lang/zh_TW/setup.yml | 4 +- resources/lang/zh_TW/validation.yml | 36 +- resources/views/admin/base.twig | 4 +- resources/views/home.twig | 2 +- resources/views/shared/foot.twig | 3 +- resources/views/shared/footer.twig | 2 +- resources/views/shared/header.twig | 28 +- resources/views/shared/languages.twig | 2 +- resources/views/shared/sidebar.twig | 8 +- resources/views/shared/user-menu.twig | 2 +- resources/views/user/base.twig | 8 +- tools/generateUrls.ts | 27 +- tsconfig.json | 9 +- vite.config.ts | 99 +- yarn.lock | 5667 ++++++++--------- 240 files changed, 5338 insertions(+), 6130 deletions(-) delete mode 100644 .eslintignore create mode 100644 eslint.config.js create mode 100644 public/hot create mode 100644 resources/assets/src/vite-env.d.ts delete mode 100644 resources/assets/src/webpack.d.ts diff --git a/.devcontainer/docker-compose.yml b/.devcontainer/docker-compose.yml index 4af89ce7e..10cc8d4ae 100644 --- a/.devcontainer/docker-compose.yml +++ b/.devcontainer/docker-compose.yml @@ -9,10 +9,9 @@ services: # Update 'VARIANT' to pick a version of PHP version: 8, 8.1, 8.0, 7, 7.4 # Append -bullseye or -buster to pin to an OS version. # Use -bullseye variants on local arm64/Apple Silicon. - VARIANT: "8-bullseye" + VARIANT: 8-bullseye # Optional Node.js version - NODE_VERSION: "lts/*" - + NODE_VERSION: 'lts/*' volumes: - ..:/workspace:cached diff --git a/.eslintignore b/.eslintignore deleted file mode 100644 index 97be921f8..000000000 --- a/.eslintignore +++ /dev/null @@ -1,9 +0,0 @@ -public/ -vendor/ -coverage/ -plugins/ -node_modules/ -*.d.ts -resources/assets/tests/__mocks__/ -resources/assets/tests/ts-shims/ -resources/assets/tests/*.ts diff --git a/.gitpod.yml b/.gitpod.yml index 3ee4f675c..02c75cde2 100644 --- a/.gitpod.yml +++ b/.gitpod.yml @@ -13,28 +13,15 @@ tasks: php artisan serve --host=0.0.0.0 - command: gp ports await 8080 && gp preview $(gp url 8000) -github: - prebuilds: - # enable for the master/default branch (defaults to true) - master: true - # enable for all branches in this repo (defaults to false) - branches: false - # enable for pull requests coming from this repo (defaults to true) - pullRequests: true - # add a check to pull requests (defaults to true) - addCheck: true - # add a "Review in Gitpod" button as a comment to pull requests (defaults to false) - addComment: false - vscode: extensions: - - 'editorconfig.editorconfig' - - 'eamodio.gitlens' - - 'bmewburn.vscode-intelephense-client' - - 'esbenp.prettier-vscode' - - 'jpoissonnier.vscode-styled-components' - - 'mblode.twig-language-2' - - 'felixfbecker.php-debug' + - editorconfig.editorconfig + - eamodio.gitlens + - bmewburn.vscode-intelephense-client + - esbenp.prettier-vscode + - jpoissonnier.vscode-styled-components + - mblode.twig-language-2 + - felixfbecker.php-debug ports: - port: 8080 diff --git a/.vscode/launch.json b/.vscode/launch.json index 3ce44757b..c19d94689 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -1,38 +1,38 @@ { - "version": "0.2.0", - "configurations": [ - { - "type": "node", - "request": "launch", - "name": "Launch Jest Tests", - "program": "${workspaceFolder}/node_modules/.bin/jest", - "args": ["${file}"], - "internalConsoleOptions": "openOnSessionStart", - "skipFiles": [ - "/**" - ] - }, - { - "type": "php", - "request": "launch", - "name": "Launch with XDebug", - "ignore": [ - "**/vendor/**/*.php" - ] - }, - { - "type": "firefox", - "request": "launch", - "reAttach": true, - "name": "Launch with Firefox Debugger", - "url": "http://localhost/", - "webRoot": "${workspaceFolder}", - "pathMappings": [ - { - "url": "webpack:///", - "path": "${workspaceFolder}/" - } - ] - } - ] + "version": "0.2.0", + "configurations": [ + { + "type": "node", + "request": "launch", + "name": "Launch Jest Tests", + "program": "${workspaceFolder}/node_modules/.bin/jest", + "args": ["${file}"], + "internalConsoleOptions": "openOnSessionStart", + "skipFiles": [ + "/**" + ] + }, + { + "type": "php", + "request": "launch", + "name": "Launch with XDebug", + "ignore": [ + "**/vendor/**/*.php" + ] + }, + { + "type": "firefox", + "request": "launch", + "reAttach": true, + "name": "Launch with Firefox Debugger", + "url": "http://localhost/", + "webRoot": "${workspaceFolder}", + "pathMappings": [ + { + "url": "webpack:///", + "path": "${workspaceFolder}/" + } + ] + } + ] } diff --git a/eslint.config.js b/eslint.config.js new file mode 100644 index 000000000..a0eeff230 --- /dev/null +++ b/eslint.config.js @@ -0,0 +1,11 @@ +import {configBuilder} from '@mochaa/eslintrc'; + +export default configBuilder({ + ignores: [ + 'public/', + 'vendor/', + 'vendor/', + 'plugins/', + 'storage/', + ], +}); diff --git a/package.json b/package.json index 011dafa81..e9aa7f30d 100644 --- a/package.json +++ b/package.json @@ -1,15 +1,16 @@ { "name": "blessing-skin-server", + "type": "module", "version": "6.0.2", "private": true, + "packageManager": "yarn@1.22.22+sha512.a6b2f7906b721bba3d67d4aff083df04dad64c399707841b7acf00f6b133b7ac24255f2652fa22ae3534329dc6180534e98d17432037ff6fd140556e2bb3137e", "description": "A web application brings your custom skins back in offline Minecraft servers.", + "author": "printempw", + "license": "MIT", "repository": { "type": "git", "url": "https://github.com/bs-community/blessing-skin-server" }, - "license": "MIT", - "author": "printempw", - "type": "module", "scripts": { "build": "vite build", "build:urls": "ts-node tools/generateUrls.ts", @@ -22,109 +23,64 @@ "iOS >= 12.5", "Chrome >= 87" ], - "eslintConfig": { - "env": { - "es2024": true - }, - "parserOptions": { - "ecmaVersion": "latest", - "sourceType": "module" - }, - "extends": [ - "xo", - "xo-react", - "plugin:react/jsx-runtime", - "./node_modules/xo/config/plugins.cjs" - ], - "rules": { - "import/extensions": "off", - "import/no-named-as-default": "off", - "n/file-extension-in-import": "off", - "unicorn/filename-case": "off", - "n/prefer-global/process": "off" - }, - "overrides": [ - { - "files": [ - "*.ts", - "*.tsx" - ], - "extends": [ - "xo-typescript" - ], - "rules": { - "@typescript-eslint/ban-types": "off", - "@typescript-eslint/consistent-type-definitions": "warn", - "@typescript-eslint/naming-convention": "warn" - } - } - ], - "ignorePatterns": [ - "dist", - "public" - ], - "root": true - }, - "resolutions": { - "kleur": "^4.1.3" - }, "dependencies": { - "@emotion/react": "^11.11.4", - "@emotion/styled": "^11.0.0", - "@fortawesome/fontawesome-free": "^6.3.0", - "@tweenjs/tween.js": "^23.1.1", - "admin-lte": "next", + "@emotion/react": "^11.14.0", + "@emotion/styled": "^11.14.0", + "@fortawesome/fontawesome-free": "^6.7.2", + "@tweenjs/tween.js": "^25.0.0", + "admin-lte": "4.0.0-beta3", "bootstrap": "^5.3.3", - "clsx": "^2.1.0", - "echarts": "^5.5.0", - "immer": "^10.0.3", + "clsx": "^2.1.1", + "downshift": "^9.0.8", + "echarts": "^5.6.0", + "immer": "^10.1.1", "jquery": "^3.6.0", "lodash-es": "^4.0.8", - "nanoid": "^5.0.6", + "nanoid": "^5.0.9", "prompts": "^2.4.0", - "react": "^18.2.0", - "react-autosuggest": "^10.0.2", - "react-dom": "^18.2.0", + "react": "^18.0.0", + "react-dom": "^18.0.0", "react-draggable": "^4.4.2", - "react-loading-skeleton": "^3.4.0", - "react-use": "^17.5.0", + "react-loading-skeleton": "^3.5.0", + "react-use": "^17.6.0", "reaptcha": "^1.7.2", "rxjs": "^7.8.1", "skinview-utils": "^0.7.1", - "skinview3d": "^3.0.0-alpha.1", - "spectre.css": "npm:@angular-package/spectre.css", - "use-immer": "^0.9.0" + "skinview3d": "^3.1.0", + "spectre.css": "github:angular-package/spectre.css", + "use-immer": "^0.11.0" }, "devDependencies": { - "@testing-library/jest-dom": "^6.4.2", - "@testing-library/react": "^14.2.1", - "@tsconfig/vite-react": "^3.0.0", + "@eslint-react/eslint-plugin": "^1.23.2", + "@mochaa/eslintrc": "^0.1.12", + "@testing-library/jest-dom": "^6.6.3", + "@testing-library/react": "^16.2.0", + "@tsconfig/vite-react": "^3.4.0", "@types/bootstrap": "^5.2.10", - "@types/jquery": "^3.5.13", + "@types/jquery": "^3.5.32", "@types/js-yaml": "^4.0.9", "@types/lodash-es": "^4.0.6", "@types/prompts": "^2.0.9", - "@types/react": "^18.2.62", - "@types/react-autosuggest": "^10.1.11", - "@types/react-dom": "^18.2.19", + "@types/react": "^18", + "@types/react-dom": "^18", "@types/tween.js": "^18.5.0", - "@vitejs/plugin-react-swc": "^3.6.0", - "autoprefixer": "^10.4.18", - "browserslist": "^4.23.0", + "@vitejs/plugin-react-swc": "^3.7.2", + "autoprefixer": "^10.4.20", + "browserslist": "^4.24.4", "browserslist-to-esbuild": "^2.1.1", - "eslint-config-xo-react": "^0.27.0", - "eslint-plugin-react": "^7.34.0", - "eslint-plugin-react-hooks": "^4.6.0", + "eslint": "^9.18.0", + "eslint-plugin-react-hooks": "^5.1.0", + "eslint-plugin-react-refresh": "^0.4.18", "js-yaml": "^4.1.0", - "laravel-vite-plugin": "^1.0.2", - "postcss": "^8.4.35", - "sass": "^1.71.1", - "typescript": "^5.3.3", - "vite": "^5.1.5", - "vite-plugin-top-level-await": "^1.4.1", - "vite-plugin-wasm": "^3.3.0", - "vitest": "^1.3.1", - "xo": "^0.57.0" + "laravel-vite-plugin": "^1.1.1", + "postcss": "^8.5.1", + "sass": "^1.83.4", + "typescript": "^5.7.3", + "vite": "^6.0.7", + "vitest": "^3.0.2" + }, + "resolutions": { + "kleur": "^4.1.3" }, "postcss": { "plugins": { diff --git a/public/.gitignore b/public/.gitignore index 2afdd76e7..d4d56bbca 100644 --- a/public/.gitignore +++ b/public/.gitignore @@ -1,2 +1,3 @@ app/ build/ +hot/ diff --git a/public/hot b/public/hot new file mode 100644 index 000000000..f762bcfc0 --- /dev/null +++ b/public/hot @@ -0,0 +1 @@ +http://[::1]:5173 \ No newline at end of file diff --git a/resources/assets/src/components/Alert.tsx b/resources/assets/src/components/Alert.tsx index e05b914bb..9e5875405 100644 --- a/resources/assets/src/components/Alert.tsx +++ b/resources/assets/src/components/Alert.tsx @@ -1,4 +1,3 @@ - type AlertType = 'success' | 'info' | 'warning' | 'danger'; const icons = new Map([ @@ -13,16 +12,17 @@ type Properties = { readonly children?: React.ReactNode; }; -const Alert: React.FC = properties => { - const {type} = properties; +const Alert: React.FC = ({type, children}) => { const icon = icons.get(type); - return properties.children ? ( -
- - {properties.children} -
- ) : null; + return children === '' + ? null + : ( +
+ + {children} +
+ ); }; export default Alert; diff --git a/resources/assets/src/components/ButtonEdit.tsx b/resources/assets/src/components/ButtonEdit.tsx index 076134e7c..4f5c11f36 100644 --- a/resources/assets/src/components/ButtonEdit.tsx +++ b/resources/assets/src/components/ButtonEdit.tsx @@ -1,11 +1,10 @@ - type Properties = { readonly title?: string; readonly onClick: React.MouseEventHandler; }; -const ButtonEdit: React.FC = properties => ( - +const ButtonEdit: React.FC = ({title, onClick}) => ( + ); diff --git a/resources/assets/src/components/Captcha.tsx b/resources/assets/src/components/Captcha.tsx index d18a16982..dd1a58312 100644 --- a/resources/assets/src/components/Captcha.tsx +++ b/resources/assets/src/components/Captcha.tsx @@ -1,11 +1,10 @@ -/** @jsxImportSource @emotion/react */ -import React from 'react'; -import Reaptcha from 'reaptcha'; import {emit, on} from '@/scripts/event'; import {t} from '@/scripts/i18n'; import * as cssUtils from '@/styles/utils'; +import React from 'react'; +import Reaptcha from 'reaptcha'; -const eventId = Symbol(); +const eventId = Symbol('EventId'); type State = { value: string; @@ -16,20 +15,22 @@ type State = { class Captcha extends React.Component, State> { state: State; - ref: React.MutableRefObject; + // eslint-disable-next-line ts/no-restricted-types + ref: React.RefObject; constructor(properties: Record) { super(properties); this.state = { value: '', time: Date.now(), - sitekey: blessing.extra.recaptcha, - invisible: blessing.extra.invisible, + sitekey: blessing.extra.recaptcha as string, + invisible: blessing.extra.invisible as boolean, }; - this.ref = React.createRef(); + this.ref = React.createRef(); } - execute = async () => { + // eslint-disable-next-line react/no-unused-class-component-members + async execute() { const recaptcha = this.ref.current; if (recaptcha && this.state.invisible) { return new Promise(resolve => { @@ -37,21 +38,22 @@ class Captcha extends React.Component, State> { resolve(value); off(); }); - recaptcha.execute(); + void recaptcha.execute(); }); } return this.state.value; - }; + } - reset = () => { + // eslint-disable-next-line react/no-unused-class-component-members + reset() { const recaptcha = this.ref.current; if (recaptcha) { - recaptcha.reset(); + void recaptcha.reset(); } else { this.setState({time: Date.now()}); } - }; + } handleValueChange = (event: React.ChangeEvent) => { this.setState({value: event.target.value}); @@ -67,37 +69,39 @@ class Captcha extends React.Component, State> { }; render() { - return this.state.sitekey ? ( -
- -
- ) : ( -
-
- + +
+ ) + : ( +
+
+ +
+ {t('auth.captcha')}
- {t('auth.captcha')} -
- ); + ); } } diff --git a/resources/assets/src/components/DarkModeButton.tsx b/resources/assets/src/components/DarkModeButton.tsx index a8313b246..55b146910 100644 --- a/resources/assets/src/components/DarkModeButton.tsx +++ b/resources/assets/src/components/DarkModeButton.tsx @@ -1,5 +1,5 @@ -import {useState} from 'react'; import * as fetch from '@/scripts/net'; +import {useState} from 'react'; type Properties = { readonly initMode: boolean; diff --git a/resources/assets/src/components/EmailSuggestion.tsx b/resources/assets/src/components/EmailSuggestion.tsx index 72f16c89d..dec3d1c97 100644 --- a/resources/assets/src/components/EmailSuggestion.tsx +++ b/resources/assets/src/components/EmailSuggestion.tsx @@ -1,10 +1,9 @@ -/** @jsxImportSource @emotion/react */ - -import {useState, useEffect} from 'react'; -import Autosuggest from 'react-autosuggest'; -import {css} from '@emotion/react'; import {emit} from '@/scripts/event'; import {pointerCursor} from '@/styles/utils'; +import {css} from '@emotion/react'; +import clsx from 'clsx'; +import {useCombobox} from 'downshift'; +import {useEffect, useState} from 'react'; const styles = css` .dropdown-menu li { @@ -14,75 +13,53 @@ const styles = css` const domainNames = new Set(['qq.com', '163.com', 'gmail.com', 'hotmail.com']); -type Properties = Omit, 'onChange'> & { - onChange(value: string): void; +type Properties = Omit, 'onChange'> & { + onChange: (value: string) => void; }; -const EmailSuggestion: React.FC = properties => { - const [suggestions, setSuggestions] = useState([]); - +const EmailSuggestion: React.FC = props => { useEffect(() => { emit('emailDomainsSuggestion', domainNames); }, []); + const [inputItems, setInputItems] = useState([]); + + const { + isOpen, + getLabelProps, + getMenuProps, + getInputProps, + highlightedIndex, + getItemProps, + } = useCombobox({ + items: inputItems, + onInputValueChange({inputValue: value}) { + setInputItems([...domainNames].map(name => `${value.split('@')[0]}@${name}`)); + if (value.length === 0 || value.includes('@')) { + setInputItems([]); + } + + const {onChange} = props; + onChange(value); + }, + }); - const handleSuggestionsFetchRequested: Autosuggest.SuggestionsFetchRequested - = ({value}) => { - const segments = value.split('@'); - setSuggestions([...domainNames].map(name => `${segments[0]}@${name}`)); - }; - - const handleSuggestionsClearRequested = () => { - setSuggestions([]); - }; - - const shouldRenderSuggestions = (value: string) => { - const isSelecting = [...domainNames].some(name => - value.endsWith(`@${name}`), - ); - - return isSelecting || (value.length > 0 && !value.includes('@')); - }; - - const getSuggestionValue = (value: string) => value; - - const renderSuggestion = (suggestion: string) => suggestion; - - const handleChange = (_: React.FormEvent, event: Autosuggest.ChangeEvent) => { - properties.onChange(event.newValue); - }; - - const renderInputComponent = ( - properties_: Omit, 'onChange'>, - ) => ( -
- -
-
+ return ( +
+
+ +
-
- ); - - return ( -
- 0 ? 'show' : ''}`, - suggestionHighlighted: 'active', - }} - onSuggestionsFetchRequested={handleSuggestionsFetchRequested} - onSuggestionsClearRequested={handleSuggestionsClearRequested} - /> +
+
    0 && 'show')} {...getMenuProps()}> + {isOpen && inputItems.length > 0 && inputItems.map((item, index) => ( +
  • + {item} +
  • + ))} +
+
); }; diff --git a/resources/assets/src/components/FileInput.tsx b/resources/assets/src/components/FileInput.tsx index 29f4f8f6d..0313daf3c 100644 --- a/resources/assets/src/components/FileInput.tsx +++ b/resources/assets/src/components/FileInput.tsx @@ -1,7 +1,6 @@ -/** @jsxImportSource @emotion/react */ -import {useRef} from 'react'; -import {css} from '@emotion/react'; import {t} from '@/scripts/i18n'; +import {css} from '@emotion/react'; +import {useRef} from 'react'; const hideRawBrowseButton = css` ::after { @@ -12,7 +11,7 @@ const hideRawBrowseButton = css` type Properties = { file: File | undefined; accept?: string; - onChange(event: React.ChangeEvent): void; + onChange: (event: React.ChangeEvent) => void; }; const FileInput: React.FC = properties => { diff --git a/resources/assets/src/components/Modal.tsx b/resources/assets/src/components/Modal.tsx index 4ac8427f9..07a5ce206 100644 --- a/resources/assets/src/components/Modal.tsx +++ b/resources/assets/src/components/Modal.tsx @@ -1,16 +1,16 @@ -import {useState, useEffect, useRef} from 'react'; -import $ from 'jquery'; -import 'bootstrap'; +import {Modal as BootstrapModal} from 'bootstrap'; +import clsx from 'clsx'; +import {useEffect, useRef, useState} from 'react'; import {t} from '../scripts/i18n'; -import ModalHeader, {type Props as HeaderProperties} from './ModalHeader'; import ModalBody, {type Props as BodyProperties} from './ModalBody'; import ModalFooter, {type Props as FooterProperties} from './ModalFooter'; +import ModalHeader, {type Props as HeaderProperties} from './ModalHeader'; type BasicOptions = { readonly mode?: 'alert' | 'confirm' | 'prompt'; readonly show?: boolean; readonly input?: string; - validator?(value: any): string | boolean | undefined; + validator?: (value: any) => string | boolean | undefined; readonly type?: string; readonly showHeader?: boolean; readonly center?: boolean; @@ -23,9 +23,9 @@ type Properties = { readonly id?: string; readonly children?: React.ReactNode; readonly footer?: React.ReactNode; - onConfirm?(payload: {value: string}): void; - onDismiss?(): void; - onClose?(): void; + onConfirm?: (payload: {value: string}) => void; + onDismiss?: () => void; + onClose?: () => void; }; export type ModalResult = { @@ -49,14 +49,36 @@ const Modal: React.FC = properties => { cancelButtonText = t('general.cancel'), cancelButtonType = 'secondary', flexFooter = false, + footer, + show, + onClose, + onDismiss, + id, + validator, + onConfirm, + children, + choices, + dangerousHTML: html, } = properties; const [value, setValue] = useState(input); const [valid, setValid] = useState(true); const [validatorMessage, setValidatorMessage] = useState(''); const reference = useRef(null); + const [modal, setModal] = useState(); + + useEffect(() => { + if (!reference.current) { + return; + } - const {show, onClose} = properties; + const _modal = new BootstrapModal(reference.current); + setModal(_modal); + + return () => { + _modal.dispose(); + }; + }, [reference]); useEffect(() => { if (!show) { @@ -64,23 +86,26 @@ const Modal: React.FC = properties => { } const onHidden = () => { - onClose ? onClose() : void 0; + onClose?.(); }; - const element = $(reference.current!); - element.on('hidden.bs.modal', onHidden); + const element = reference.current; + if (!element) { + return; + } + + element.addEventListener('hidden.bs.modal', onHidden); return () => { - element.off('hidden.bs.modal', onHidden); + element.removeEventListener('hidden.bs.modal', onHidden); }; - }, [show, onClose]); + }, [reference, show, onClose]); const handleInputChange = (event: React.ChangeEvent) => { setValue(event.target.value); }; const confirm = () => { - const {validator} = properties; if (typeof validator === 'function') { const result = validator(value); if (typeof result === 'string') { @@ -90,49 +115,54 @@ const Modal: React.FC = properties => { } } - properties.onConfirm?.({value}); - $(reference.current!).modal('hide'); + onConfirm?.({value}); + modal?.hide(); // The "hidden.bs.modal" event can't be trigged automatically when testing. - if (process.env.NODE_ENV === 'test') { + if (import.meta.env.NODE_ENV === 'test') { $(reference.current!).trigger('hidden.bs.modal'); } }; const dismiss = () => { - properties.onDismiss?.(); - $(reference.current!).modal('hide'); + onDismiss?.(); + modal?.hide(); - if (process.env.NODE_ENV === 'test') { + if (import.meta.env.NODE_ENV === 'test') { $(reference.current!).trigger('hidden.bs.modal'); } }; useEffect(() => { - if (show) { - setTimeout(() => $(reference.current!).modal('show'), 50); + if (show && modal) { + const timeout = setTimeout(() => { + modal.show(); + }, 50); + return () => { + clearTimeout(timeout); + }; } - }, [show]); + }, [show, modal]); if (!show) { return null; } return ( -