From 34efed7470ed9ce2735dc2d7907c458ab1e41ece Mon Sep 17 00:00:00 2001 From: schroda <50052685+schroda@users.noreply.github.com> Date: Wed, 13 May 2026 23:31:48 +0200 Subject: [PATCH] Revert "Support "produced characters" as hotkeys" This reverts commit bc66d52701dde6c7cb2245087300ffa417776692. "useKey" is broken https://github.com/JohannesKlauss/react-hotkeys-hook/issues/1318 --- .oxlintrc.jsonc | 870 +++++++++--------- CHANGELOG.md | 1 - src/base/components/AppbarSearch.tsx | 12 +- src/features/reader/hotkeys/ReaderHotkeys.tsx | 39 +- .../settings/components/RecordHotkey.tsx | 36 +- src/i18n/locales/en.po | 12 - src/lib/react-hotkeys-hook/useHotkeys.ts | 24 - 7 files changed, 486 insertions(+), 508 deletions(-) delete mode 100644 src/lib/react-hotkeys-hook/useHotkeys.ts diff --git a/.oxlintrc.jsonc b/.oxlintrc.jsonc index 5f6de9d0..64e3f19a 100644 --- a/.oxlintrc.jsonc +++ b/.oxlintrc.jsonc @@ -1,419 +1,461 @@ { - "plugins": ["react", "unicorn", "typescript", "import", "jsx-a11y"], - "jsPlugins": [ - "eslint-plugin-lingui", - "eslint-plugin-no-relative-import-paths", - "eslint-plugin-unused-imports", - "eslint-plugin-no-only-tests", - "@tony.ganchev/eslint-plugin-header", + "plugins": [ + "react", + "unicorn", + "typescript", + "import", + "jsx-a11y" + ], + "jsPlugins": [ + "eslint-plugin-lingui", + "eslint-plugin-no-relative-import-paths", + "eslint-plugin-unused-imports", + "eslint-plugin-no-only-tests", + "@tony.ganchev/eslint-plugin-header" + ], + "categories": { + "correctness": "off" + }, + "rules": { + // ── Core JS: correctness (from js.configs.recommended) ── + // Rules redundant with TypeScript strict mode have been removed. + "for-direction": "error", + "no-async-promise-executor": "error", + "no-case-declarations": "error", + "no-compare-neg-zero": "error", + "no-cond-assign": "error", + "no-constant-binary-expression": "error", + "no-constant-condition": "error", + "no-control-regex": "error", + "no-debugger": "error", + "no-delete-var": "error", + "no-dupe-else-if": "error", + "no-duplicate-case": "error", + "no-empty": "error", + "no-empty-character-class": "error", + "no-empty-pattern": "error", + "no-empty-static-block": "error", + "no-ex-assign": "error", + "no-extra-boolean-cast": "error", + "no-invalid-regexp": "error", + "no-irregular-whitespace": "error", + "no-loss-of-precision": "error", + "no-misleading-character-class": "error", + "no-nonoctal-decimal-escape": "error", + "no-prototype-builtins": "error", + "no-regex-spaces": "error", + "no-self-assign": "error", + "no-shadow-restricted-names": "error", + "no-sparse-arrays": "error", + "no-unsafe-finally": "error", + "no-unsafe-optional-chaining": "error", + "no-unused-labels": "error", + "no-unused-private-class-members": "error", + "no-unused-vars": [ + "error", + { + "vars": "all", + "args": "after-used", + "ignoreRestSiblings": true, + "argsIgnorePattern": "^_", + "varsIgnorePattern": "^_", + "caughtErrorsIgnorePattern": "^_|^e$|^error$" + } ], - "categories": { - "correctness": "off", - }, - "rules": { - // ── Core JS: correctness (from js.configs.recommended) ── - // Rules redundant with TypeScript strict mode have been removed. - "for-direction": "error", - "no-async-promise-executor": "error", - "no-case-declarations": "error", - "no-compare-neg-zero": "error", - "no-cond-assign": "error", - "no-constant-binary-expression": "error", - "no-constant-condition": "error", - "no-control-regex": "error", - "no-debugger": "error", - "no-delete-var": "error", - "no-dupe-else-if": "error", - "no-duplicate-case": "error", - "no-empty": "error", - "no-empty-character-class": "error", - "no-empty-pattern": "error", - "no-empty-static-block": "error", - "no-ex-assign": "error", - "no-extra-boolean-cast": "error", - "no-invalid-regexp": "error", - "no-irregular-whitespace": "error", - "no-loss-of-precision": "error", - "no-misleading-character-class": "error", - "no-nonoctal-decimal-escape": "error", - "no-prototype-builtins": "error", - "no-regex-spaces": "error", - "no-self-assign": "error", - "no-shadow-restricted-names": "error", - "no-sparse-arrays": "error", - "no-unsafe-finally": "error", - "no-unsafe-optional-chaining": "error", - "no-unused-labels": "error", - "no-unused-private-class-members": "error", - "no-unused-vars": [ - "error", - { - "vars": "all", - "args": "after-used", - "ignoreRestSiblings": true, - "argsIgnorePattern": "^_", - "varsIgnorePattern": "^_", - "caughtErrorsIgnorePattern": "^_|^e$|^error$", - }, - ], - "no-useless-backreference": "error", - "no-useless-catch": "error", - "no-useless-escape": "error", - "no-with": "error", - "require-yield": "error", - "use-isnan": "error", - "no-array-constructor": "error", - "no-unused-expressions": "error", + "no-useless-backreference": "error", + "no-useless-catch": "error", + "no-useless-escape": "error", + "no-with": "error", + "require-yield": "error", + "use-isnan": "error", + "no-array-constructor": "error", + "no-unused-expressions": "error", - // ── Core JS: style & best practices ── - "no-console": "error", - "no-else-return": [ - "error", - { - "allowElseIf": false, - }, - ], - "no-lonely-if": "error", - "no-unneeded-ternary": [ - "error", - { - "defaultAssignment": false, - }, - ], - "no-multi-assign": "error", - "no-return-assign": ["error", "always"], - "no-sequences": "error", - "no-new": "error", - "no-new-func": "error", - "no-new-wrappers": "error", - "no-eval": "error", - "no-script-url": "error", - "no-self-compare": "error", - "no-extend-native": "error", - "no-alert": "error", - "no-restricted-globals": ["error", "isFinite", "isNaN"], - "no-await-in-loop": "error", - "no-promise-executor-return": "error", - "no-template-curly-in-string": "error", - "no-constructor-return": "error", - "no-useless-concat": "error", - "no-useless-return": "error", - "no-useless-computed-key": "error", - "no-useless-rename": "error", - "eqeqeq": [ - "error", - "always", - { - "null": "ignore", - }, - ], - "default-case": [ - "error", - { - "commentPattern": "^no default$", - }, - ], - "default-case-last": "error", - "yoda": "error", - "prefer-template": "error", - "prefer-exponentiation-operator": "error", - "prefer-object-spread": "error", - "arrow-body-style": ["error", "as-needed"], - "prefer-destructuring": "error", - "no-nested-ternary": "error", - "prefer-promise-reject-errors": [ - "error", - { - "allowEmptyReject": true, - }, - ], - "no-param-reassign": [ - "error", - { - "props": true, - "ignorePropertyModificationsForRegex": ["^draft"], - }, - ], - "no-restricted-imports": [ - "error", - { - "paths": [ - { - "name": "react-hotkeys-hook", - "importNames": ["useHotkeys"], - "message": "Use src/lib/react-hotkey-hook/useHotkeys instead", - }, - ], - "patterns": [ - { - "group": ["@mui/*", "!@mui/material/", "!@mui/icons-material/", "!@mui/x-date-pickers/"], - }, - { - "group": ["@mui/*/*/*"], - }, - { - "group": ["lodash", "lodash/*", "!lodash/fp/*"], - "message": "Use lodash-fp instead.", - }, - ], - }, - ], - - // ── New: modern JS essentials ── - "prefer-const": "error", - "no-var": "error", - "curly": "error", - "no-warning-comments": [ - "error", - { - "terms": ["@nocommit"], - }, - ], - - // ── TypeScript rules (non-type-checked) ── - "no-shadow": "error", - "no-loop-func": "error", - "no-use-before-define": [ - "error", - { - "functions": false, - "classes": true, - "variables": true, - }, - ], - "default-param-last": "error", - - // ── TypeScript rules from recommended ── - "typescript/no-duplicate-enum-values": "error", - "typescript/no-extra-non-null-assertion": "error", - "typescript/no-misused-new": "error", - "typescript/no-namespace": "error", - "typescript/no-non-null-asserted-optional-chain": "error", - "typescript/no-require-imports": "error", - "typescript/no-this-alias": "error", - "typescript/no-unnecessary-type-constraint": "error", - "typescript/no-unsafe-declaration-merging": "error", - "typescript/no-wrapper-object-types": "error", - "typescript/prefer-as-const": "error", - "typescript/prefer-namespace-keyword": "error", - "typescript/triple-slash-reference": "error", - - // ── New: TypeScript safety ── - "typescript/no-explicit-any": "off", - "typescript/no-empty-object-type": "error", - "typescript/no-unsafe-function-type": "error", - "typescript/consistent-type-imports": "error", - "typescript/switch-exhaustiveness-check": "error", - - // ── TypeScript rules (type-checked) ── - "typescript/no-implied-eval": "error", - "typescript/only-throw-error": "error", - "typescript/dot-notation": "error", - "typescript/return-await": ["error", "in-try-catch"], - - // ── Import rules ── - "import/namespace": "error", - "import/default": "error", - "import/export": "error", - "import/no-duplicates": "error", - "import/no-mutable-exports": "error", - "import/first": "error", - "import/no-amd": "error", - "import/no-absolute-path": "error", - "import/no-self-import": "error", - "import/no-default-export": "error", - - // ── Unicorn: modern JS (auto-fixable) ── - "unicorn/prefer-array-flat-map": "error", - "unicorn/prefer-structured-clone": "error", - "unicorn/prefer-string-replace-all": "error", - "unicorn/consistent-function-scoping": "error", - - // ── React rules ── - "react/jsx-key": "error", - "react/jsx-no-comment-textnodes": "error", - "react/jsx-no-duplicate-props": "error", - "react/jsx-no-target-blank": "error", - "react/jsx-no-undef": "error", - "react/no-children-prop": "error", - "react/no-danger-with-children": "error", - "react/no-render-return-value": "error", - "react/no-unescaped-entities": "error", - "react/no-unknown-property": "error", - "react/rules-of-hooks": "error", - "react/no-array-index-key": "error", - "react/jsx-no-useless-fragment": "error", - "react/jsx-no-constructed-context-values": "error", - "react/self-closing-comp": "error", - "react/jsx-boolean-value": ["error", "never"], - "react/jsx-pascal-case": [ - "error", - { - "allowAllCaps": true, - }, - ], - "react/button-has-type": "error", - "react/jsx-curly-brace-presence": [ - "error", - { - "props": "never", - "children": "never", - }, - ], - "react/jsx-fragments": ["error", "syntax"], - "react/jsx-no-script-url": "error", - "react/style-prop-object": "error", - "react/void-dom-elements-no-children": "error", - "react/no-danger": "error", - - // ── jsx-a11y rules ── - "jsx_a11y/alt-text": "error", - "jsx_a11y/anchor-has-content": "error", - "jsx_a11y/anchor-is-valid": "error", - "jsx_a11y/aria-activedescendant-has-tabindex": "error", - "jsx_a11y/aria-props": "error", - "jsx_a11y/aria-proptypes": "error", - "jsx_a11y/aria-role": "error", - "jsx_a11y/aria-unsupported-elements": "error", - "jsx_a11y/autocomplete-valid": "error", - "jsx_a11y/click-events-have-key-events": "error", - "jsx_a11y/heading-has-content": "error", - "jsx_a11y/html-has-lang": "error", - "jsx_a11y/iframe-has-title": "error", - "jsx_a11y/img-redundant-alt": "error", - "jsx_a11y/label-has-associated-control": "error", - "jsx_a11y/media-has-caption": "error", - "jsx_a11y/mouse-events-have-key-events": "error", - "jsx_a11y/no-access-key": "error", - "jsx_a11y/no-autofocus": [ - "error", - { - "ignoreNonDOM": true, - }, - ], - "jsx_a11y/no-distracting-elements": "error", - "jsx_a11y/no-noninteractive-tabindex": [ - "error", - { - "tags": [], - "roles": ["tabpanel"], - "allowExpressionValues": true, - }, - ], - "jsx_a11y/no-redundant-roles": "error", - "jsx_a11y/no-static-element-interactions": [ - "error", - { - "allowExpressionValues": true, - "handlers": ["onClick", "onMouseDown", "onMouseUp", "onKeyPress", "onKeyDown", "onKeyUp"], - }, - ], - "jsx_a11y/role-has-required-aria-props": "error", - "jsx_a11y/role-supports-aria-props": "error", - "jsx_a11y/scope": "error", - "jsx_a11y/tabindex-no-positive": "error", - - // ── Lingui rules ── - "lingui/t-call-in-function": "error", - "lingui/no-single-tag-to-translate": "error", - "lingui/no-single-variables-to-translate": "error", - "lingui/no-trans-inside-trans": "error", - "lingui/no-expression-in-message": "off", - - // ── No relative import paths ── - "no-relative-import-paths/no-relative-import-paths": "error", - - // ── Unused imports ── - "unused-imports/no-unused-imports": "error", - - // ── No .only in tests ── - "no-only-tests/no-only-tests": "error", - - // ── License header ── - "@tony.ganchev/header/header": [ - "error", - "block", - [ - "", - " * Copyright (C) Contributors to the Suwayomi project", - " *", - " * This Source Code Form is subject to the terms of the Mozilla Public", - " * License, v. 2.0. If a copy of the MPL was not distributed with this", - " * file, You can obtain one at https://mozilla.org/MPL/2.0/.", - " ", - ], - 2, - ], - }, - "settings": { - "jsx-a11y": { - "components": {}, - "attributes": {}, - }, - "next": { - "rootDir": [], - }, - "react": { - "formComponents": [], - "linkComponents": [], - "version": "19.2.0", - "componentWrapperFunctions": [], - }, - "jsdoc": { - "ignorePrivate": false, - "ignoreInternal": false, - "ignoreReplacesDocs": true, - "overrideReplacesDocs": true, - "augmentsExtendsReplacesDocs": false, - "implementsReplacesDocs": false, - "exemptDestructuredRootsFromChecks": false, - "tagNamePreference": {}, - }, - "vitest": { - "typecheck": false, - }, - }, - "env": { - "browser": true, - "builtin": true, - }, - "globals": {}, - "overrides": [ - { - "files": [ - "src/base/utils/MediaQuery.tsx", - "src/features/authentication/AuthManager.ts", - "src/features/chapter/services/Chapters.ts", - "src/features/metadata/services/MetadataMigrations.ts", - "src/features/migration/MigrationManager.ts", - "src/features/reader/services/ReaderControls.ts", - "src/features/reader/services/ReaderService.ts", - "src/features/source/services/Sources.ts", - "src/lib/dnd-kit/DndKitUtil.ts", - "src/lib/requests/RequestManager.ts", - "src/lib/requests/client/GraphQLClient.ts", - "src/lib/virtuoso/Virtuoso.util.tsx", - ], - "rules": { - "react/rules-of-hooks": "off", - }, - }, - { - "files": ["**/*.config.ts", "**/*.config.js"], - "rules": { - "import/no-default-export": "off", - }, - }, - { - "files": ["tools/scripts/**/*"], - "env": { - "node": true, - }, - "rules": { - "no-console": "off", - "no-relative-import-paths/no-relative-import-paths": "off", - }, - }, + // ── Core JS: style & best practices ── + "no-console": "error", + "no-else-return": [ + "error", + { + "allowElseIf": false + } ], - "ignorePatterns": ["src/lib/graphql/generated/**", "tools/eslint-plugin-header.cjs"], + "no-lonely-if": "error", + "no-unneeded-ternary": [ + "error", + { + "defaultAssignment": false + } + ], + "no-multi-assign": "error", + "no-return-assign": [ + "error", + "always" + ], + "no-sequences": "error", + "no-new": "error", + "no-new-func": "error", + "no-new-wrappers": "error", + "no-eval": "error", + "no-script-url": "error", + "no-self-compare": "error", + "no-extend-native": "error", + "no-alert": "error", + "no-restricted-globals": [ + "error", + "isFinite", + "isNaN" + ], + "no-await-in-loop": "error", + "no-promise-executor-return": "error", + "no-template-curly-in-string": "error", + "no-constructor-return": "error", + "no-useless-concat": "error", + "no-useless-return": "error", + "no-useless-computed-key": "error", + "no-useless-rename": "error", + "eqeqeq": [ + "error", + "always", + { + "null": "ignore" + } + ], + "default-case": [ + "error", + { + "commentPattern": "^no default$" + } + ], + "default-case-last": "error", + "yoda": "error", + "prefer-template": "error", + "prefer-exponentiation-operator": "error", + "prefer-object-spread": "error", + "arrow-body-style": ["error", "as-needed"], + "prefer-destructuring": "error", + "no-nested-ternary": "error", + "prefer-promise-reject-errors": [ + "error", + { + "allowEmptyReject": true + } + ], + "no-param-reassign": [ + "error", + { + "props": true, + "ignorePropertyModificationsForRegex": [ + "^draft" + ] + } + ], + "no-restricted-imports": [ + "error", + { + "patterns": [ + { + "group": [ + "@mui/*", + "!@mui/material/", + "!@mui/icons-material/", + "!@mui/x-date-pickers/" + ], + }, + { + "group": [ + "@mui/*/*/*" + ] + }, + { + "group": [ + "lodash", + "lodash/*", + "!lodash/fp/*" + ], + "message": "Use lodash-fp instead." + } + ] + } + ], + + // ── New: modern JS essentials ── + "prefer-const": "error", + "no-var": "error", + "curly": "error", + "no-warning-comments": [ + "error", + { + "terms": [ + "@nocommit" + ] + } + ], + + // ── TypeScript rules (non-type-checked) ── + "no-shadow": "error", + "no-loop-func": "error", + "no-use-before-define": [ + "error", + { + "functions": false, + "classes": true, + "variables": true + } + ], + "default-param-last": "error", + + // ── TypeScript rules from recommended ── + "typescript/no-duplicate-enum-values": "error", + "typescript/no-extra-non-null-assertion": "error", + "typescript/no-misused-new": "error", + "typescript/no-namespace": "error", + "typescript/no-non-null-asserted-optional-chain": "error", + "typescript/no-require-imports": "error", + "typescript/no-this-alias": "error", + "typescript/no-unnecessary-type-constraint": "error", + "typescript/no-unsafe-declaration-merging": "error", + "typescript/no-wrapper-object-types": "error", + "typescript/prefer-as-const": "error", + "typescript/prefer-namespace-keyword": "error", + "typescript/triple-slash-reference": "error", + + // ── New: TypeScript safety ── + "typescript/no-explicit-any": "off", + "typescript/no-empty-object-type": "error", + "typescript/no-unsafe-function-type": "error", + "typescript/consistent-type-imports": "error", + "typescript/switch-exhaustiveness-check": "error", + + // ── TypeScript rules (type-checked) ── + "typescript/no-implied-eval": "error", + "typescript/only-throw-error": "error", + "typescript/dot-notation": "error", + "typescript/return-await": [ + "error", + "in-try-catch" + ], + + // ── Import rules ── + "import/namespace": "error", + "import/default": "error", + "import/export": "error", + "import/no-duplicates": "error", + "import/no-mutable-exports": "error", + "import/first": "error", + "import/no-amd": "error", + "import/no-absolute-path": "error", + "import/no-self-import": "error", + "import/no-default-export": "error", + + // ── Unicorn: modern JS (auto-fixable) ── + "unicorn/prefer-array-flat-map": "error", + "unicorn/prefer-structured-clone": "error", + "unicorn/prefer-string-replace-all": "error", + "unicorn/consistent-function-scoping": "error", + + // ── React rules ── + "react/jsx-key": "error", + "react/jsx-no-comment-textnodes": "error", + "react/jsx-no-duplicate-props": "error", + "react/jsx-no-target-blank": "error", + "react/jsx-no-undef": "error", + "react/no-children-prop": "error", + "react/no-danger-with-children": "error", + "react/no-render-return-value": "error", + "react/no-unescaped-entities": "error", + "react/no-unknown-property": "error", + "react/rules-of-hooks": "error", + "react/no-array-index-key": "error", + "react/jsx-no-useless-fragment": "error", + "react/jsx-no-constructed-context-values": "error", + "react/self-closing-comp": "error", + "react/jsx-boolean-value": [ + "error", + "never" + ], + "react/jsx-pascal-case": [ + "error", + { + "allowAllCaps": true + } + ], + "react/button-has-type": "error", + "react/jsx-curly-brace-presence": [ + "error", + { + "props": "never", + "children": "never" + } + ], + "react/jsx-fragments": [ + "error", + "syntax" + ], + "react/jsx-no-script-url": "error", + "react/style-prop-object": "error", + "react/void-dom-elements-no-children": "error", + "react/no-danger": "error", + + // ── jsx-a11y rules ── + "jsx_a11y/alt-text": "error", + "jsx_a11y/anchor-has-content": "error", + "jsx_a11y/anchor-is-valid": "error", + "jsx_a11y/aria-activedescendant-has-tabindex": "error", + "jsx_a11y/aria-props": "error", + "jsx_a11y/aria-proptypes": "error", + "jsx_a11y/aria-role": "error", + "jsx_a11y/aria-unsupported-elements": "error", + "jsx_a11y/autocomplete-valid": "error", + "jsx_a11y/click-events-have-key-events": "error", + "jsx_a11y/heading-has-content": "error", + "jsx_a11y/html-has-lang": "error", + "jsx_a11y/iframe-has-title": "error", + "jsx_a11y/img-redundant-alt": "error", + "jsx_a11y/label-has-associated-control": "error", + "jsx_a11y/media-has-caption": "error", + "jsx_a11y/mouse-events-have-key-events": "error", + "jsx_a11y/no-access-key": "error", + "jsx_a11y/no-autofocus": [ + "error", + { + "ignoreNonDOM": true + } + ], + "jsx_a11y/no-distracting-elements": "error", + "jsx_a11y/no-noninteractive-tabindex": [ + "error", + { + "tags": [], + "roles": [ + "tabpanel" + ], + "allowExpressionValues": true + } + ], + "jsx_a11y/no-redundant-roles": "error", + "jsx_a11y/no-static-element-interactions": [ + "error", + { + "allowExpressionValues": true, + "handlers": [ + "onClick", + "onMouseDown", + "onMouseUp", + "onKeyPress", + "onKeyDown", + "onKeyUp" + ] + } + ], + "jsx_a11y/role-has-required-aria-props": "error", + "jsx_a11y/role-supports-aria-props": "error", + "jsx_a11y/scope": "error", + "jsx_a11y/tabindex-no-positive": "error", + + // ── Lingui rules ── + "lingui/t-call-in-function": "error", + "lingui/no-single-tag-to-translate": "error", + "lingui/no-single-variables-to-translate": "error", + "lingui/no-trans-inside-trans": "error", + "lingui/no-expression-in-message": "off", + + // ── No relative import paths ── + "no-relative-import-paths/no-relative-import-paths": "error", + + // ── Unused imports ── + "unused-imports/no-unused-imports": "error", + + // ── No .only in tests ── + "no-only-tests/no-only-tests": "error", + + // ── License header ── + "@tony.ganchev/header/header": ["error", "block", [ + "", + " * Copyright (C) Contributors to the Suwayomi project", + " *", + " * This Source Code Form is subject to the terms of the Mozilla Public", + " * License, v. 2.0. If a copy of the MPL was not distributed with this", + " * file, You can obtain one at https://mozilla.org/MPL/2.0/.", + " " + ], 2] + }, + "settings": { + "jsx-a11y": { + "components": {}, + "attributes": {} + }, + "next": { + "rootDir": [] + }, + "react": { + "formComponents": [], + "linkComponents": [], + "version": "19.2.0", + "componentWrapperFunctions": [] + }, + "jsdoc": { + "ignorePrivate": false, + "ignoreInternal": false, + "ignoreReplacesDocs": true, + "overrideReplacesDocs": true, + "augmentsExtendsReplacesDocs": false, + "implementsReplacesDocs": false, + "exemptDestructuredRootsFromChecks": false, + "tagNamePreference": {} + }, + "vitest": { + "typecheck": false + } + }, + "env": { + "browser": true, + "builtin": true + }, + "globals": {}, + "overrides": [ + { + "files": [ + "src/base/utils/MediaQuery.tsx", + "src/features/authentication/AuthManager.ts", + "src/features/chapter/services/Chapters.ts", + "src/features/metadata/services/MetadataMigrations.ts", + "src/features/migration/MigrationManager.ts", + "src/features/reader/services/ReaderControls.ts", + "src/features/reader/services/ReaderService.ts", + "src/features/source/services/Sources.ts", + "src/lib/dnd-kit/DndKitUtil.ts", + "src/lib/requests/RequestManager.ts", + "src/lib/requests/client/GraphQLClient.ts", + "src/lib/virtuoso/Virtuoso.util.tsx" + ], + "rules": { + "react/rules-of-hooks": "off" + } + }, + { + "files": [ + "**/*.config.ts", + "**/*.config.js" + ], + "rules": { + "import/no-default-export": "off" + } + }, + { + "files": [ + "tools/scripts/**/*" + ], + "env": { + "node": true + }, + "rules": { + "no-console": "off", + "no-relative-import-paths/no-relative-import-paths": "off" + } + } + ], + "ignorePatterns": [ + "src/lib/graphql/generated/**", + "tools/eslint-plugin-header.cjs" + ] } diff --git a/CHANGELOG.md b/CHANGELOG.md index 027478c7..494f1b09 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,7 +12,6 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/). - (**Migration**) Keep the migration page open when aborting during the actual migration execution - (**Migration**) Prevent concurrent requests to the same tracker - (**Migration**) Allow only 2 tracker requests per second -- (**Reader**) Support "produced characters" as hotkeys. E.g. "!", or "/" ### Fixed diff --git a/src/base/components/AppbarSearch.tsx b/src/base/components/AppbarSearch.tsx index 59a38e90..5bb057f8 100644 --- a/src/base/components/AppbarSearch.tsx +++ b/src/base/components/AppbarSearch.tsx @@ -12,11 +12,11 @@ import IconButton from '@mui/material/IconButton'; import { useQueryParam, StringParam } from 'use-query-params'; import { useLocation } from 'react-router-dom'; import { useTheme } from '@mui/material/styles'; +import { useHotkeys } from 'react-hotkeys-hook'; import { useLingui } from '@lingui/react/macro'; import { CustomTooltip } from '@/base/components/CustomTooltip.tsx'; import { SearchTextField } from '@/base/components/inputs/SearchTextField.tsx'; import { SearchParam } from '@/base/Base.types.ts'; -import { useHotkeys } from '@/lib/react-hotkeys-hook/useHotkeys.ts'; interface IProps { isClosable?: boolean; @@ -79,9 +79,13 @@ export const AppbarSearch: React.FunctionComponent = (props) => { } }; - useHotkeys('ctrl+f, F3', () => { - updateSearchOpenState(true); - }); + useHotkeys( + 'ctrl+f, F3', + () => { + updateSearchOpenState(true); + }, + { preventDefault: true }, + ); if (isOpen) { return ( diff --git a/src/features/reader/hotkeys/ReaderHotkeys.tsx b/src/features/reader/hotkeys/ReaderHotkeys.tsx index fed49486..fbdf6324 100644 --- a/src/features/reader/hotkeys/ReaderHotkeys.tsx +++ b/src/features/reader/hotkeys/ReaderHotkeys.tsx @@ -6,7 +6,7 @@ * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ -import { useHotkeysContext } from 'react-hotkeys-hook'; +import { useHotkeys as useHotKeysHook, useHotkeysContext } from 'react-hotkeys-hook'; import { useEffect } from 'react'; import { HOTKEY_SCOPES } from '@/features/hotkeys/Hotkeys.constants.ts'; import { ReaderService } from '@/features/reader/services/ReaderService.ts'; @@ -30,11 +30,10 @@ import { getReaderSettingsStore, useReaderSettingsStore, } from '@/features/reader/stores/ReaderStore.ts'; -import { useHotkeys } from '@/lib/react-hotkeys-hook/useHotkeys.ts'; -const useReaderHotkeys = (...args: Parameters): ReturnType => { +const useHotkeys = (...args: Parameters): ReturnType => { const [keys, callback, options, dependencies] = args; - return useHotkeys(keys, callback, { ...options, ...HOTKEY_SCOPES.reader }, dependencies); + return useHotKeysHook(keys, callback, { preventDefault: true, ...options, ...HOTKEY_SCOPES.reader }, dependencies); }; const updateSettingCycleThrough = ( @@ -70,9 +69,9 @@ export const ReaderHotkeys = ({ const hotkeys = useReaderSettingsStore('hotkeys'); const exitReader = ReaderService.useExit(); - useReaderHotkeys(hotkeys[ReaderHotkey.PREVIOUS_PAGE], () => ReaderControls.openPage('previous')); - useReaderHotkeys(hotkeys[ReaderHotkey.NEXT_PAGE], () => ReaderControls.openPage('next')); - useReaderHotkeys(hotkeys[ReaderHotkey.SCROLL_BACKWARD], () => { + useHotkeys(hotkeys[ReaderHotkey.PREVIOUS_PAGE], () => ReaderControls.openPage('previous')); + useHotkeys(hotkeys[ReaderHotkey.NEXT_PAGE], () => ReaderControls.openPage('next')); + useHotkeys(hotkeys[ReaderHotkey.SCROLL_BACKWARD], () => { const autoScroll = getReaderAutoScrollStore(); const { readingMode, readingDirection, scrollAmount } = getReaderSettingsStore(); @@ -94,7 +93,7 @@ export const ReaderHotkeys = ({ scrollAmount, ); }); - useReaderHotkeys(hotkeys[ReaderHotkey.SCROLL_FORWARD], () => { + useHotkeys(hotkeys[ReaderHotkey.SCROLL_FORWARD], () => { const autoScroll = getReaderAutoScrollStore(); const { readingMode, readingDirection, scrollAmount } = getReaderSettingsStore(); @@ -116,20 +115,20 @@ export const ReaderHotkeys = ({ scrollAmount, ); }); - useReaderHotkeys( + useHotkeys( hotkeys[ReaderHotkey.PREVIOUS_CHAPTER], () => ReaderControls.openChapter(getOptionForDirection('previous', 'next', readerThemeDirection)), [readerThemeDirection], ); - useReaderHotkeys( + useHotkeys( hotkeys[ReaderHotkey.NEXT_CHAPTER], () => ReaderControls.openChapter(getOptionForDirection('next', 'previous', readerThemeDirection)), [readerThemeDirection], ); - useReaderHotkeys(hotkeys[ReaderHotkey.TOGGLE_MENU], () => + useHotkeys(hotkeys[ReaderHotkey.TOGGLE_MENU], () => getReaderOverlayStore().setIsVisible(!getReaderOverlayStore().isVisible), ); - useReaderHotkeys( + useHotkeys( hotkeys[ReaderHotkey.CYCLE_SCALE_TYPE], () => { updateSettingCycleThrough( @@ -142,13 +141,13 @@ export const ReaderHotkeys = ({ }, [], ); - useReaderHotkeys(hotkeys[ReaderHotkey.STRETCH_IMAGE], () => + useHotkeys(hotkeys[ReaderHotkey.STRETCH_IMAGE], () => ReaderService.updateSetting('shouldStretchPage', !getReaderSettingsStore().shouldStretchPage.value), ); - useReaderHotkeys(hotkeys[ReaderHotkey.OFFSET_SPREAD_PAGES], () => + useHotkeys(hotkeys[ReaderHotkey.OFFSET_SPREAD_PAGES], () => ReaderService.setOffsetDoubleSpreads(!getReaderSettingsStore().shouldOffsetDoubleSpreads.value), ); - useReaderHotkeys(hotkeys[ReaderHotkey.CYCLE_READING_MODE], () => { + useHotkeys(hotkeys[ReaderHotkey.CYCLE_READING_MODE], () => { updateSettingCycleThrough( 'readingMode', getReaderSettingsStore().readingMode.value, @@ -157,7 +156,7 @@ export const ReaderHotkeys = ({ true, ); }); - useReaderHotkeys(hotkeys[ReaderHotkey.CYCLE_READING_DIRECTION], () => { + useHotkeys(hotkeys[ReaderHotkey.CYCLE_READING_DIRECTION], () => { updateSettingCycleThrough( 'readingDirection', getReaderSettingsStore().readingDirection.value, @@ -166,20 +165,20 @@ export const ReaderHotkeys = ({ true, ); }); - useReaderHotkeys(hotkeys[ReaderHotkey.TOGGLE_AUTO_SCROLL], () => getReaderAutoScrollStore().toggleActive(), {}); - useReaderHotkeys(hotkeys[ReaderHotkey.AUTO_SCROLL_SPEED_DECREASE], () => + useHotkeys(hotkeys[ReaderHotkey.TOGGLE_AUTO_SCROLL], () => getReaderAutoScrollStore().toggleActive(), {}); + useHotkeys(hotkeys[ReaderHotkey.AUTO_SCROLL_SPEED_DECREASE], () => ReaderService.updateSetting('autoScroll', { ...getReaderSettingsStore().autoScroll, value: Math.min(AUTO_SCROLL_SPEED.max, getReaderSettingsStore().autoScroll.value + AUTO_SCROLL_SPEED.step), }), ); - useReaderHotkeys(hotkeys[ReaderHotkey.AUTO_SCROLL_SPEED_INCREASE], () => + useHotkeys(hotkeys[ReaderHotkey.AUTO_SCROLL_SPEED_INCREASE], () => ReaderService.updateSetting('autoScroll', { ...getReaderSettingsStore().autoScroll, value: Math.max(AUTO_SCROLL_SPEED.min, getReaderSettingsStore().autoScroll.value - AUTO_SCROLL_SPEED.step), }), ); - useReaderHotkeys(hotkeys[ReaderHotkey.EXIT_READER], exitReader, [exitReader]); + useHotkeys(hotkeys[ReaderHotkey.EXIT_READER], exitReader, [exitReader]); useEffect(() => { enableScope(HotkeyScope.READER); diff --git a/src/features/reader/hotkeys/settings/components/RecordHotkey.tsx b/src/features/reader/hotkeys/settings/components/RecordHotkey.tsx index c96345c3..517dca88 100644 --- a/src/features/reader/hotkeys/settings/components/RecordHotkey.tsx +++ b/src/features/reader/hotkeys/settings/components/RecordHotkey.tsx @@ -6,7 +6,7 @@ * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ -import { useEffect, useState } from 'react'; +import { useEffect } from 'react'; import Stack from '@mui/material/Stack'; import { useRecordHotkeys } from 'react-hotkeys-hook'; import Dialog from '@mui/material/Dialog'; @@ -17,7 +17,6 @@ import Button from '@mui/material/Button'; import Typography from '@mui/material/Typography'; import { Trans, useLingui } from '@lingui/react/macro'; import { Hotkey } from '@/features/reader/hotkeys/settings/components/Hotkey.tsx'; -import { CheckboxInput } from '@/base/components/inputs/CheckboxInput.tsx'; export const RecordHotkey = ({ onClose, @@ -30,10 +29,7 @@ export const RecordHotkey = ({ }) => { const { t } = useLingui(); - const [recordPhysicalKeys, setRecordPhysicalKeys] = useState(true); - - const [recordedKeys, { start, stop, resetKeys }] = useRecordHotkeys(!recordPhysicalKeys); - + const [recordedKeys, { start, stop, resetKeys }] = useRecordHotkeys(); const keys = [[...recordedKeys].join('+')]; const isExistingKey = keys.some((key) => existingKeys.map((existingKey) => existingKey.toLowerCase()).includes(key.toLowerCase()), @@ -45,7 +41,7 @@ export const RecordHotkey = ({ return () => { stop(); }; - }, [start, stop]); + }, []); return ( @@ -58,32 +54,6 @@ export const RecordHotkey = ({ {isExistingKey && {t`Hotkey already exists`}} - - {t`Record physical keys`} - - - enabled: records when the user presses Shift+1 - - - - - disabled: records when the user presses Shift+1 on a US - layout - - - - } - checked={recordPhysicalKeys} - onChange={(_, checked) => setRecordPhysicalKeys(checked)} - sx={{ mt: 2, alignItems: 'start' }} - /> when the user presses Shift+1 on a US layout" -msgstr "disabled: records <0/> when the user presses Shift+1 on a US layout" - #: src/features/settings/components/koreaderSync/KoreaderSyncSettings.tsx msgid "Disconnect from KOReader Sync server" msgstr "Disconnect from KOReader Sync server" @@ -1504,10 +1500,6 @@ msgstr "Enable debug logs" msgid "Enable page read progress" msgstr "Enable page read progress" -#: src/features/reader/hotkeys/settings/components/RecordHotkey.tsx -msgid "enabled: records <0/> when the user presses Shift+1" -msgstr "enabled: records <0/> when the user presses Shift+1" - #: src/features/downloads/screens/DownloadSettings.tsx msgid "Entries in excluded categories will not be downloaded even if they are also in included categories" msgstr "Entries in excluded categories will not be downloaded even if they are also in included categories" @@ -2787,10 +2779,6 @@ msgstr "Recently read" msgid "Record keybind" msgstr "Record keybind" -#: src/features/reader/hotkeys/settings/components/RecordHotkey.tsx -msgid "Record physical keys" -msgstr "Record physical keys" - #. placeholder {0}: recordedKeys.size ? : {t`Press keys`} #: src/features/reader/hotkeys/settings/components/RecordHotkey.tsx msgid "Recorded hotkeys: {0}" diff --git a/src/lib/react-hotkeys-hook/useHotkeys.ts b/src/lib/react-hotkeys-hook/useHotkeys.ts deleted file mode 100644 index 1bc1c5e5..00000000 --- a/src/lib/react-hotkeys-hook/useHotkeys.ts +++ /dev/null @@ -1,24 +0,0 @@ -/* - * Copyright (C) Contributors to the Suwayomi project - * - * This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at https://mozilla.org/MPL/2.0/. - */ - -// oxlint-disable-next-line no-restricted-imports -import { useHotkeys as orgUseHotkeys } from 'react-hotkeys-hook'; - -export const useHotkeys = (...args: Parameters): ReturnType => { - const [keys, callback, options, dependencies] = args; - return orgUseHotkeys( - keys, - callback, - { - preventDefault: true, - useKey: true, - ...options, - }, - dependencies, - ); -};