Revert "Support "produced characters" as hotkeys"

This reverts commit bc66d52701.

"useKey" is broken https://github.com/JohannesKlauss/react-hotkeys-hook/issues/1318
This commit is contained in:
schroda
2026-05-13 23:31:48 +02:00
parent bc66d52701
commit 34efed7470
7 changed files with 486 additions and 508 deletions

View File

@@ -1,419 +1,461 @@
{ {
"plugins": ["react", "unicorn", "typescript", "import", "jsx-a11y"], "plugins": [
"jsPlugins": [ "react",
"eslint-plugin-lingui", "unicorn",
"eslint-plugin-no-relative-import-paths", "typescript",
"eslint-plugin-unused-imports", "import",
"eslint-plugin-no-only-tests", "jsx-a11y"
"@tony.ganchev/eslint-plugin-header", ],
"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": { "no-useless-backreference": "error",
"correctness": "off", "no-useless-catch": "error",
}, "no-useless-escape": "error",
"rules": { "no-with": "error",
// ── Core JS: correctness (from js.configs.recommended) ── "require-yield": "error",
// Rules redundant with TypeScript strict mode have been removed. "use-isnan": "error",
"for-direction": "error", "no-array-constructor": "error",
"no-async-promise-executor": "error", "no-unused-expressions": "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",
// ── Core JS: style & best practices ── // ── Core JS: style & best practices ──
"no-console": "error", "no-console": "error",
"no-else-return": [ "no-else-return": [
"error", "error",
{ {
"allowElseIf": false, "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",
},
},
], ],
"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"
]
} }

View File

@@ -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**) Keep the migration page open when aborting during the actual migration execution
- (**Migration**) Prevent concurrent requests to the same tracker - (**Migration**) Prevent concurrent requests to the same tracker
- (**Migration**) Allow only 2 tracker requests per second - (**Migration**) Allow only 2 tracker requests per second
- (**Reader**) Support "produced characters" as hotkeys. E.g. "!", or "/"
### Fixed ### Fixed

View File

@@ -12,11 +12,11 @@ import IconButton from '@mui/material/IconButton';
import { useQueryParam, StringParam } from 'use-query-params'; import { useQueryParam, StringParam } from 'use-query-params';
import { useLocation } from 'react-router-dom'; import { useLocation } from 'react-router-dom';
import { useTheme } from '@mui/material/styles'; import { useTheme } from '@mui/material/styles';
import { useHotkeys } from 'react-hotkeys-hook';
import { useLingui } from '@lingui/react/macro'; import { useLingui } from '@lingui/react/macro';
import { CustomTooltip } from '@/base/components/CustomTooltip.tsx'; import { CustomTooltip } from '@/base/components/CustomTooltip.tsx';
import { SearchTextField } from '@/base/components/inputs/SearchTextField.tsx'; import { SearchTextField } from '@/base/components/inputs/SearchTextField.tsx';
import { SearchParam } from '@/base/Base.types.ts'; import { SearchParam } from '@/base/Base.types.ts';
import { useHotkeys } from '@/lib/react-hotkeys-hook/useHotkeys.ts';
interface IProps { interface IProps {
isClosable?: boolean; isClosable?: boolean;
@@ -79,9 +79,13 @@ export const AppbarSearch: React.FunctionComponent<IProps> = (props) => {
} }
}; };
useHotkeys('ctrl+f, F3', () => { useHotkeys(
updateSearchOpenState(true); 'ctrl+f, F3',
}); () => {
updateSearchOpenState(true);
},
{ preventDefault: true },
);
if (isOpen) { if (isOpen) {
return ( return (

View File

@@ -6,7 +6,7 @@
* file, You can obtain one at https://mozilla.org/MPL/2.0/. * 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 { useEffect } from 'react';
import { HOTKEY_SCOPES } from '@/features/hotkeys/Hotkeys.constants.ts'; import { HOTKEY_SCOPES } from '@/features/hotkeys/Hotkeys.constants.ts';
import { ReaderService } from '@/features/reader/services/ReaderService.ts'; import { ReaderService } from '@/features/reader/services/ReaderService.ts';
@@ -30,11 +30,10 @@ import {
getReaderSettingsStore, getReaderSettingsStore,
useReaderSettingsStore, useReaderSettingsStore,
} from '@/features/reader/stores/ReaderStore.ts'; } from '@/features/reader/stores/ReaderStore.ts';
import { useHotkeys } from '@/lib/react-hotkeys-hook/useHotkeys.ts';
const useReaderHotkeys = (...args: Parameters<typeof useHotkeys>): ReturnType<typeof useHotkeys> => { const useHotkeys = (...args: Parameters<typeof useHotKeysHook>): ReturnType<typeof useHotKeysHook> => {
const [keys, callback, options, dependencies] = args; 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 = <Setting extends keyof IReaderSettings>( const updateSettingCycleThrough = <Setting extends keyof IReaderSettings>(
@@ -70,9 +69,9 @@ export const ReaderHotkeys = ({
const hotkeys = useReaderSettingsStore('hotkeys'); const hotkeys = useReaderSettingsStore('hotkeys');
const exitReader = ReaderService.useExit(); const exitReader = ReaderService.useExit();
useReaderHotkeys(hotkeys[ReaderHotkey.PREVIOUS_PAGE], () => ReaderControls.openPage('previous')); useHotkeys(hotkeys[ReaderHotkey.PREVIOUS_PAGE], () => ReaderControls.openPage('previous'));
useReaderHotkeys(hotkeys[ReaderHotkey.NEXT_PAGE], () => ReaderControls.openPage('next')); useHotkeys(hotkeys[ReaderHotkey.NEXT_PAGE], () => ReaderControls.openPage('next'));
useReaderHotkeys(hotkeys[ReaderHotkey.SCROLL_BACKWARD], () => { useHotkeys(hotkeys[ReaderHotkey.SCROLL_BACKWARD], () => {
const autoScroll = getReaderAutoScrollStore(); const autoScroll = getReaderAutoScrollStore();
const { readingMode, readingDirection, scrollAmount } = getReaderSettingsStore(); const { readingMode, readingDirection, scrollAmount } = getReaderSettingsStore();
@@ -94,7 +93,7 @@ export const ReaderHotkeys = ({
scrollAmount, scrollAmount,
); );
}); });
useReaderHotkeys(hotkeys[ReaderHotkey.SCROLL_FORWARD], () => { useHotkeys(hotkeys[ReaderHotkey.SCROLL_FORWARD], () => {
const autoScroll = getReaderAutoScrollStore(); const autoScroll = getReaderAutoScrollStore();
const { readingMode, readingDirection, scrollAmount } = getReaderSettingsStore(); const { readingMode, readingDirection, scrollAmount } = getReaderSettingsStore();
@@ -116,20 +115,20 @@ export const ReaderHotkeys = ({
scrollAmount, scrollAmount,
); );
}); });
useReaderHotkeys( useHotkeys(
hotkeys[ReaderHotkey.PREVIOUS_CHAPTER], hotkeys[ReaderHotkey.PREVIOUS_CHAPTER],
() => ReaderControls.openChapter(getOptionForDirection('previous', 'next', readerThemeDirection)), () => ReaderControls.openChapter(getOptionForDirection('previous', 'next', readerThemeDirection)),
[readerThemeDirection], [readerThemeDirection],
); );
useReaderHotkeys( useHotkeys(
hotkeys[ReaderHotkey.NEXT_CHAPTER], hotkeys[ReaderHotkey.NEXT_CHAPTER],
() => ReaderControls.openChapter(getOptionForDirection('next', 'previous', readerThemeDirection)), () => ReaderControls.openChapter(getOptionForDirection('next', 'previous', readerThemeDirection)),
[readerThemeDirection], [readerThemeDirection],
); );
useReaderHotkeys(hotkeys[ReaderHotkey.TOGGLE_MENU], () => useHotkeys(hotkeys[ReaderHotkey.TOGGLE_MENU], () =>
getReaderOverlayStore().setIsVisible(!getReaderOverlayStore().isVisible), getReaderOverlayStore().setIsVisible(!getReaderOverlayStore().isVisible),
); );
useReaderHotkeys( useHotkeys(
hotkeys[ReaderHotkey.CYCLE_SCALE_TYPE], hotkeys[ReaderHotkey.CYCLE_SCALE_TYPE],
() => { () => {
updateSettingCycleThrough( updateSettingCycleThrough(
@@ -142,13 +141,13 @@ export const ReaderHotkeys = ({
}, },
[], [],
); );
useReaderHotkeys(hotkeys[ReaderHotkey.STRETCH_IMAGE], () => useHotkeys(hotkeys[ReaderHotkey.STRETCH_IMAGE], () =>
ReaderService.updateSetting('shouldStretchPage', !getReaderSettingsStore().shouldStretchPage.value), ReaderService.updateSetting('shouldStretchPage', !getReaderSettingsStore().shouldStretchPage.value),
); );
useReaderHotkeys(hotkeys[ReaderHotkey.OFFSET_SPREAD_PAGES], () => useHotkeys(hotkeys[ReaderHotkey.OFFSET_SPREAD_PAGES], () =>
ReaderService.setOffsetDoubleSpreads(!getReaderSettingsStore().shouldOffsetDoubleSpreads.value), ReaderService.setOffsetDoubleSpreads(!getReaderSettingsStore().shouldOffsetDoubleSpreads.value),
); );
useReaderHotkeys(hotkeys[ReaderHotkey.CYCLE_READING_MODE], () => { useHotkeys(hotkeys[ReaderHotkey.CYCLE_READING_MODE], () => {
updateSettingCycleThrough( updateSettingCycleThrough(
'readingMode', 'readingMode',
getReaderSettingsStore().readingMode.value, getReaderSettingsStore().readingMode.value,
@@ -157,7 +156,7 @@ export const ReaderHotkeys = ({
true, true,
); );
}); });
useReaderHotkeys(hotkeys[ReaderHotkey.CYCLE_READING_DIRECTION], () => { useHotkeys(hotkeys[ReaderHotkey.CYCLE_READING_DIRECTION], () => {
updateSettingCycleThrough( updateSettingCycleThrough(
'readingDirection', 'readingDirection',
getReaderSettingsStore().readingDirection.value, getReaderSettingsStore().readingDirection.value,
@@ -166,20 +165,20 @@ export const ReaderHotkeys = ({
true, true,
); );
}); });
useReaderHotkeys(hotkeys[ReaderHotkey.TOGGLE_AUTO_SCROLL], () => getReaderAutoScrollStore().toggleActive(), {}); useHotkeys(hotkeys[ReaderHotkey.TOGGLE_AUTO_SCROLL], () => getReaderAutoScrollStore().toggleActive(), {});
useReaderHotkeys(hotkeys[ReaderHotkey.AUTO_SCROLL_SPEED_DECREASE], () => useHotkeys(hotkeys[ReaderHotkey.AUTO_SCROLL_SPEED_DECREASE], () =>
ReaderService.updateSetting('autoScroll', { ReaderService.updateSetting('autoScroll', {
...getReaderSettingsStore().autoScroll, ...getReaderSettingsStore().autoScroll,
value: Math.min(AUTO_SCROLL_SPEED.max, getReaderSettingsStore().autoScroll.value + AUTO_SCROLL_SPEED.step), 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', { ReaderService.updateSetting('autoScroll', {
...getReaderSettingsStore().autoScroll, ...getReaderSettingsStore().autoScroll,
value: Math.max(AUTO_SCROLL_SPEED.min, getReaderSettingsStore().autoScroll.value - AUTO_SCROLL_SPEED.step), 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(() => { useEffect(() => {
enableScope(HotkeyScope.READER); enableScope(HotkeyScope.READER);

View File

@@ -6,7 +6,7 @@
* file, You can obtain one at https://mozilla.org/MPL/2.0/. * 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 Stack from '@mui/material/Stack';
import { useRecordHotkeys } from 'react-hotkeys-hook'; import { useRecordHotkeys } from 'react-hotkeys-hook';
import Dialog from '@mui/material/Dialog'; import Dialog from '@mui/material/Dialog';
@@ -17,7 +17,6 @@ import Button from '@mui/material/Button';
import Typography from '@mui/material/Typography'; import Typography from '@mui/material/Typography';
import { Trans, useLingui } from '@lingui/react/macro'; import { Trans, useLingui } from '@lingui/react/macro';
import { Hotkey } from '@/features/reader/hotkeys/settings/components/Hotkey.tsx'; import { Hotkey } from '@/features/reader/hotkeys/settings/components/Hotkey.tsx';
import { CheckboxInput } from '@/base/components/inputs/CheckboxInput.tsx';
export const RecordHotkey = ({ export const RecordHotkey = ({
onClose, onClose,
@@ -30,10 +29,7 @@ export const RecordHotkey = ({
}) => { }) => {
const { t } = useLingui(); const { t } = useLingui();
const [recordPhysicalKeys, setRecordPhysicalKeys] = useState(true); const [recordedKeys, { start, stop, resetKeys }] = useRecordHotkeys();
const [recordedKeys, { start, stop, resetKeys }] = useRecordHotkeys(!recordPhysicalKeys);
const keys = [[...recordedKeys].join('+')]; const keys = [[...recordedKeys].join('+')];
const isExistingKey = keys.some((key) => const isExistingKey = keys.some((key) =>
existingKeys.map((existingKey) => existingKey.toLowerCase()).includes(key.toLowerCase()), existingKeys.map((existingKey) => existingKey.toLowerCase()).includes(key.toLowerCase()),
@@ -45,7 +41,7 @@ export const RecordHotkey = ({
return () => { return () => {
stop(); stop();
}; };
}, [start, stop]); }, []);
return ( return (
<Dialog open onClose={onClose} fullWidth> <Dialog open onClose={onClose} fullWidth>
@@ -58,32 +54,6 @@ export const RecordHotkey = ({
</Trans> </Trans>
</Stack> </Stack>
{isExistingKey && <Typography color="error">{t`Hotkey already exists`}</Typography>} {isExistingKey && <Typography color="error">{t`Hotkey already exists`}</Typography>}
<CheckboxInput
label={
<Stack
sx={{
// Padding comes from the MUI Checkbox component
pt: '9px',
}}
>
<Typography>{t`Record physical keys`}</Typography>
<Typography variant="body2" color="textSecondary" sx={{ display: 'flex', gap: 1 }}>
<Trans>
enabled: records <Hotkey keys={['shift+1']} /> when the user presses Shift+1
</Trans>
</Typography>
<Typography variant="body2" color="textSecondary" sx={{ display: 'flex', gap: 1 }}>
<Trans>
disabled: records <Hotkey keys={['!']} /> when the user presses Shift+1 on a US
layout
</Trans>
</Typography>
</Stack>
}
checked={recordPhysicalKeys}
onChange={(_, checked) => setRecordPhysicalKeys(checked)}
sx={{ mt: 2, alignItems: 'start' }}
/>
</DialogContent> </DialogContent>
<DialogActions> <DialogActions>
<Stack <Stack

View File

@@ -1329,10 +1329,6 @@ msgstr "Disable authentication"
msgid "Disabled" msgid "Disabled"
msgstr "Disabled" msgstr "Disabled"
#: src/features/reader/hotkeys/settings/components/RecordHotkey.tsx
msgid "disabled: records <0/> 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 #: src/features/settings/components/koreaderSync/KoreaderSyncSettings.tsx
msgid "Disconnect from KOReader Sync server" msgid "Disconnect from KOReader Sync server"
msgstr "Disconnect from KOReader Sync server" msgstr "Disconnect from KOReader Sync server"
@@ -1504,10 +1500,6 @@ msgstr "Enable debug logs"
msgid "Enable page read progress" msgid "Enable page read progress"
msgstr "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 #: src/features/downloads/screens/DownloadSettings.tsx
msgid "Entries in excluded categories will not be downloaded even if they are also in included categories" 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" 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" msgid "Record keybind"
msgstr "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 ? <Hotkey keys={keys} /> : <Typography>{t`Press keys`}</Typography> #. placeholder {0}: recordedKeys.size ? <Hotkey keys={keys} /> : <Typography>{t`Press keys`}</Typography>
#: src/features/reader/hotkeys/settings/components/RecordHotkey.tsx #: src/features/reader/hotkeys/settings/components/RecordHotkey.tsx
msgid "Recorded hotkeys: {0}" msgid "Recorded hotkeys: {0}"

View File

@@ -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<typeof orgUseHotkeys>): ReturnType<typeof orgUseHotkeys> => {
const [keys, callback, options, dependencies] = args;
return orgUseHotkeys(
keys,
callback,
{
preventDefault: true,
useKey: true,
...options,
},
dependencies,
);
};