Fix desktop long press action
After the long press action, the set onClick action was triggered as well. This caused issues like opening the manga page in the source browse page when adding the manga via a long press to the library
This commit is contained in:
870
.oxlintrc.jsonc
870
.oxlintrc.jsonc
@@ -1,461 +1,419 @@
|
||||
{
|
||||
"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$"
|
||||
}
|
||||
"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",
|
||||
],
|
||||
"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",
|
||||
{
|
||||
"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"
|
||||
"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$",
|
||||
},
|
||||
],
|
||||
"allowExpressionValues": true
|
||||
}
|
||||
"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",
|
||||
{
|
||||
"patterns": [
|
||||
{
|
||||
"group": ["@mui/*", "!@mui/material/", "!@mui/icons-material/", "!@mui/x-date-pickers/"],
|
||||
},
|
||||
{
|
||||
"group": ["@mui/*/*/*"],
|
||||
},
|
||||
{
|
||||
"group": ["lodash", "lodash/*", "!lodash/fp/*"],
|
||||
"message": "Use lodash-fp instead.",
|
||||
},
|
||||
],
|
||||
"paths": [
|
||||
{
|
||||
"name": "use-long-press",
|
||||
"importNames": ["useLongPress"],
|
||||
"message": "Use the wrapper \"usePress\" 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",
|
||||
},
|
||||
},
|
||||
],
|
||||
"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"
|
||||
]
|
||||
"ignorePatterns": ["src/lib/graphql/generated/**", "tools/eslint-plugin-header.cjs"],
|
||||
}
|
||||
|
||||
@@ -36,6 +36,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).
|
||||
|
||||
### Fixed
|
||||
|
||||
- (**General**) Fix long press actions on desktop
|
||||
- (**Migration**) Fix aborting bulk migration search/execution while webUI is served on a subpath
|
||||
- (**Migration**) Fix retry button never being shown for failed search/migration entries
|
||||
- (**Migration**) Fix showing an empty "Available" source group header when all sources are selected
|
||||
|
||||
71
src/base/hooks/usePress.ts
Normal file
71
src/base/hooks/usePress.ts
Normal file
@@ -0,0 +1,71 @@
|
||||
/*
|
||||
* 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/.
|
||||
*/
|
||||
|
||||
import { useCallback, useRef } from 'react';
|
||||
import type {
|
||||
LongPressCallbackMeta,
|
||||
LongPressMouseHandlers,
|
||||
LongPressPointerHandlers,
|
||||
LongPressReactEvents,
|
||||
LongPressResult,
|
||||
LongPressTouchHandlers,
|
||||
} from 'use-long-press';
|
||||
// oxlint-disable-next-line no-restricted-imports
|
||||
import { useLongPress } from 'use-long-press';
|
||||
|
||||
export type UsePressResult = LongPressResult<
|
||||
(LongPressPointerHandlers | LongPressMouseHandlers | LongPressTouchHandlers) & {
|
||||
onClick: (event: React.MouseEvent | React.TouchEvent) => void;
|
||||
}
|
||||
>;
|
||||
|
||||
export const usePress = (
|
||||
options: Omit<Parameters<typeof useLongPress>[1], 'onCancel' | 'onStart'> & {
|
||||
onLongPress: NonNullable<Parameters<typeof useLongPress>[0]>;
|
||||
onPress: (event: React.MouseEvent | React.TouchEvent) => void;
|
||||
},
|
||||
): UsePressResult => {
|
||||
const { onLongPress, onPress, ...actualOptions } = options;
|
||||
|
||||
const hasLongPressRef = useRef(false);
|
||||
|
||||
const onCancel = useCallback(() => {
|
||||
if (hasLongPressRef.current) {
|
||||
hasLongPressRef.current = false;
|
||||
}
|
||||
}, []);
|
||||
|
||||
const bind = useLongPress(
|
||||
useCallback(
|
||||
(event: LongPressReactEvents<Element>, meta: LongPressCallbackMeta<unknown>) => {
|
||||
hasLongPressRef.current = true;
|
||||
onLongPress(event, meta);
|
||||
},
|
||||
[onLongPress],
|
||||
),
|
||||
{
|
||||
...actualOptions,
|
||||
onCancel,
|
||||
onStart: onCancel,
|
||||
},
|
||||
);
|
||||
|
||||
return useCallback(
|
||||
(context?: unknown) => ({
|
||||
...bind(context),
|
||||
onClick: (event: React.MouseEvent | React.TouchEvent) => {
|
||||
if (!hasLongPressRef.current) {
|
||||
onPress(event);
|
||||
} else {
|
||||
event.preventDefault();
|
||||
}
|
||||
},
|
||||
}),
|
||||
[bind, onPress],
|
||||
);
|
||||
};
|
||||
@@ -18,7 +18,6 @@ import type { MouseEvent, TouchEvent } from 'react';
|
||||
import React, { memo, useRef } from 'react';
|
||||
import { Link } from 'react-router-dom';
|
||||
import PopupState, { bindMenu, bindTrigger } from 'material-ui-popup-state';
|
||||
import { useLongPress } from 'use-long-press';
|
||||
import { useLingui } from '@lingui/react/macro';
|
||||
import { CustomTooltip } from '@/base/components/CustomTooltip.tsx';
|
||||
import { getDateString } from '@/base/utils/DateHelper.ts';
|
||||
@@ -43,6 +42,7 @@ import type {
|
||||
} from '@/features/chapter/Chapter.types.ts';
|
||||
import { MediaQuery } from '@/base/utils/MediaQuery.tsx';
|
||||
import type { ChapterType } from '@/lib/graphql/generated/graphql-base.types.ts';
|
||||
import { usePress } from '@/base/hooks/usePress.ts';
|
||||
|
||||
type TChapter = ChapterIdInfo &
|
||||
ChapterMangaInfo &
|
||||
@@ -104,15 +104,18 @@ export const ChapterCard = memo((props: IProps) => {
|
||||
onSelect(chapter.id, !selected, event.shiftKey);
|
||||
};
|
||||
|
||||
const longPressBind = useLongPress((event, { context: openMenu }) => {
|
||||
if (!isSelecting && !!menuButtonRef.current) {
|
||||
handleClickOpenMenu(event, () => (openMenu as (event: Element) => void)?.(menuButtonRef.current!));
|
||||
return;
|
||||
}
|
||||
const longPressBind = usePress({
|
||||
onLongPress: (event, { context: openMenu }) => {
|
||||
if (!isSelecting && !!menuButtonRef.current) {
|
||||
handleClickOpenMenu(event, () => (openMenu as (event: Element) => void)?.(menuButtonRef.current!));
|
||||
return;
|
||||
}
|
||||
|
||||
// oxlint-disable-next-line no-param-reassign
|
||||
event.shiftKey = true;
|
||||
handleClick(event);
|
||||
// oxlint-disable-next-line no-param-reassign
|
||||
event.shiftKey = true;
|
||||
handleClick(event);
|
||||
},
|
||||
onPress: handleClick,
|
||||
});
|
||||
|
||||
return (
|
||||
@@ -136,7 +139,6 @@ export const ChapterCard = memo((props: IProps) => {
|
||||
}}
|
||||
state={Chapters.getReaderOpenChapterLocationState(chapter, true)}
|
||||
replace={mode === 'reader'}
|
||||
onClick={(e) => handleClick(e)}
|
||||
{...longPressBind(popupState.open)}
|
||||
>
|
||||
<ListCardContent>
|
||||
|
||||
@@ -6,7 +6,6 @@
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
import type { LongPressPointerHandlers, LongPressResult } from 'use-long-press';
|
||||
import type { PopupState } from 'material-ui-popup-state/hooks';
|
||||
import type { JSX } from 'react';
|
||||
import type { SelectableCollectionReturnType } from '@/base/collection/hooks/useSelectableCollection.ts';
|
||||
@@ -29,6 +28,7 @@ import type {
|
||||
SourceType,
|
||||
TrackRecordType,
|
||||
} from '@/lib/graphql/generated/graphql-base.types.ts';
|
||||
import type { UsePressResult } from '@/base/hooks/usePress.ts';
|
||||
|
||||
export type MangaCardMode = 'default' | 'source' | 'migrate.select.bulk' | 'migrate.select.single' | 'duplicate';
|
||||
|
||||
@@ -75,9 +75,8 @@ export interface MangaCardProps {
|
||||
export type SpecificMangaCardProps = Omit<MangaCardProps, 'manga'> &
|
||||
Pick<ReturnType<typeof useManageMangaLibraryState>, 'isInLibrary'> & {
|
||||
manga: MangaCardSpecificProps;
|
||||
longPressBind: LongPressResult<LongPressPointerHandlers>;
|
||||
longPressBind: UsePressResult;
|
||||
popupState: PopupState;
|
||||
handleClick: (event: React.MouseEvent | React.TouchEvent) => void;
|
||||
mangaLinkTo: string;
|
||||
continueReadingButton: JSX.Element;
|
||||
mangaBadges: JSX.Element;
|
||||
|
||||
@@ -8,7 +8,6 @@
|
||||
|
||||
import PopupState, { bindMenu } from 'material-ui-popup-state';
|
||||
import { memo, useCallback, useMemo } from 'react';
|
||||
import { useLongPress } from 'use-long-press';
|
||||
import type { SingleModeProps } from '@/features/manga/components/MangaActionMenuItems.tsx';
|
||||
import { MangaActionMenuItems } from '@/features/manga/components/MangaActionMenuItems.tsx';
|
||||
import { Menu } from '@/base/components/menu/Menu.tsx';
|
||||
@@ -32,6 +31,7 @@ import { MangaMigration } from '@/features/migration/MangaMigration.ts';
|
||||
import { MANGA_ACTION_TO_TRANSLATION } from '@/features/manga/Manga.constants.ts';
|
||||
import { getErrorMessage } from '@/lib/HelperFunctions.ts';
|
||||
import { assertIsDefined } from '@/base/Asserts.ts';
|
||||
import { usePress } from '@/base/hooks/usePress.ts';
|
||||
|
||||
const getMangaLinkTo = (mode: MangaCardMode, mangaId: number): string => {
|
||||
switch (mode) {
|
||||
@@ -149,8 +149,8 @@ export const MangaCard = memo((props: MangaCardProps) => {
|
||||
[mode, selected, updateLibraryState, handleSelection, migrationSourceMangaId],
|
||||
);
|
||||
|
||||
const longPressBind = useLongPress(
|
||||
useCallback(
|
||||
const longPressBind = usePress({
|
||||
onLongPress: useCallback(
|
||||
(e: any, { context }: any) => {
|
||||
// oxlint-disable-next-line no-param-reassign
|
||||
e.shiftKey = true;
|
||||
@@ -158,7 +158,8 @@ export const MangaCard = memo((props: MangaCardProps) => {
|
||||
},
|
||||
[handleClick],
|
||||
),
|
||||
);
|
||||
onPress: handleClick,
|
||||
});
|
||||
|
||||
const MangaCardComponent = useMemo(
|
||||
() => (gridLayout === GridLayout.List ? MangaListCard : MangaGridCard),
|
||||
@@ -173,7 +174,6 @@ export const MangaCard = memo((props: MangaCardProps) => {
|
||||
{...props}
|
||||
longPressBind={longPressBind}
|
||||
popupState={popupState}
|
||||
handleClick={handleClick}
|
||||
mangaLinkTo={mangaLinkTo}
|
||||
isInLibrary={isInLibrary}
|
||||
inLibraryIndicator={inLibraryIndicator}
|
||||
|
||||
@@ -45,7 +45,6 @@ export const MangaGridCard = memo(
|
||||
manga,
|
||||
longPressBind,
|
||||
popupState,
|
||||
handleClick,
|
||||
mangaLinkTo,
|
||||
selected,
|
||||
inLibraryIndicator,
|
||||
@@ -65,7 +64,6 @@ export const MangaGridCard = memo(
|
||||
<Link
|
||||
component={RouterLink}
|
||||
{...longPressBind(() => popupState.open(optionButtonRef.current))}
|
||||
onClick={handleClick}
|
||||
to={mangaLinkTo}
|
||||
state={Mangas.createLocationState(manga, mode)}
|
||||
onContextMenu={preventMobileContextMenu}
|
||||
|
||||
@@ -26,7 +26,6 @@ export const MangaListCard = memo(
|
||||
manga,
|
||||
longPressBind,
|
||||
popupState,
|
||||
handleClick,
|
||||
mangaLinkTo,
|
||||
selected,
|
||||
inLibraryIndicator,
|
||||
@@ -48,7 +47,6 @@ export const MangaListCard = memo(
|
||||
component={RouterLink}
|
||||
to={mangaLinkTo}
|
||||
state={Mangas.createLocationState(manga, mode)}
|
||||
onClick={handleClick}
|
||||
{...longPressBind(() => popupState.open(optionButtonRef.current))}
|
||||
onContextMenu={preventMobileContextMenu}
|
||||
sx={{
|
||||
|
||||
Reference in New Issue
Block a user