Migrate to oxlint + oxfmt

This commit is contained in:
schroda
2026-03-10 22:28:19 +01:00
parent 98622ad6cd
commit 5dda4304d9
40 changed files with 1232 additions and 1315 deletions

1
.gitignore vendored
View File

@@ -8,3 +8,4 @@ build/*
dev-dist/*
src/lib/graphql/schema.json
.oxlintrc.jsonc

10
.oxfmtrc.json Normal file
View File

@@ -0,0 +1,10 @@
{
"$schema": "./node_modules/oxfmt/configuration_schema.json",
"printWidth": 120,
"tabWidth": 4,
"useTabs": false,
"semi": true,
"singleQuote": true,
"trailingComma": "all",
"ignorePatterns": ["src/lib/graphql/generated/**", ".github/**"]
}

483
.oxlintrc.jsonc Normal file
View File

@@ -0,0 +1,483 @@
{
"plugins": [
"react",
"typescript",
"import",
"jsx-a11y"
],
"jsPlugins": [
"eslint-plugin-lingui",
"eslint-plugin-no-relative-import-paths"
],
"categories": {
"correctness": "off"
},
"rules": {
// ── Core JS: correctness (from js.configs.recommended) ──
"constructor-super": "error",
"for-direction": "error",
"getter-return": "error",
"no-async-promise-executor": "error",
"no-case-declarations": "error",
"no-class-assign": "error",
"no-compare-neg-zero": "error",
"no-cond-assign": "error",
"no-const-assign": "error",
"no-constant-binary-expression": "error",
"no-constant-condition": "error",
"no-control-regex": "error",
"no-debugger": "error",
"no-delete-var": "error",
"no-dupe-class-members": "error",
"no-dupe-else-if": "error",
"no-dupe-keys": "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-fallthrough": "error",
"no-func-assign": "error",
"no-global-assign": "error",
"no-import-assign": "error",
"no-invalid-regexp": "error",
"no-irregular-whitespace": "error",
"no-loss-of-precision": "error",
"no-misleading-character-class": "error",
"no-new-native-nonconstructor": "error",
"no-nonoctal-decimal-escape": "error",
"no-obj-calls": "error",
"no-prototype-builtins": "error",
"no-redeclare": "error",
"no-regex-spaces": "error",
"no-self-assign": "error",
"no-setter-return": "error",
"no-shadow-restricted-names": "error",
"no-sparse-arrays": "error",
"no-this-before-super": "error",
"no-undef": "error",
"no-unreachable": "error",
"no-unsafe-finally": "error",
"no-unsafe-negation": "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",
"valid-typeof": "error",
"no-array-constructor": "error",
"no-unused-expressions": "error",
// ── Core JS: Airbnb-style rules ──
"no-console": "warn",
"no-nested-ternary": "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-proto": "error",
"no-extend-native": "error",
"no-caller": "error",
"no-alert": "warn",
"no-void": "error",
"no-bitwise": "error",
"no-continue": "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"
}
],
"guard-for-in": "error",
"default-case": [
"error",
{
"commentPattern": "^no default$"
}
],
"default-case-last": "error",
"radix": "error",
"yoda": "error",
"max-classes-per-file": [
"error",
1
],
"prefer-template": "error",
"prefer-destructuring": [
"error",
{
"VariableDeclarator": {
"array": false,
"object": true
},
"AssignmentExpression": {
"array": true,
"object": false
}
},
{
"enforceForRenamedProperties": false
}
],
"prefer-exponentiation-operator": "error",
"prefer-object-spread": "error",
"prefer-promise-reject-errors": [
"error",
{
"allowEmptyReject": true
}
],
"no-param-reassign": [
"error",
{
"props": true,
"ignorePropertyModificationsForRegex": [
"^draft"
]
}
],
"no-plusplus": [
"error",
{
"allowForLoopAfterthoughts": true
}
],
"no-restricted-imports": [
"error",
{
"patterns": [
{
"group": [
"@mui/*",
"!@mui/material/",
"!@mui/icons-material/",
"!@mui/x-date-pickers/"
]
},
{
"group": [
"@mui/*/*/*"
]
}
]
}
],
// ── 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",
// ── 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": "warn",
"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",
// ── Lingui (via jsPlugin) ──
"lingui/t-call-in-function": "error",
"lingui/no-single-tag-to-translate": "warn",
"lingui/no-single-variables-to-translate": "warn",
"lingui/no-trans-inside-trans": "warn",
"lingui/no-expression-in-message": "off",
// ── Header — handled by minimal ESLint fallback ──
// ── No relative import paths (via jsPlugin) ──
"no-relative-import-paths/no-relative-import-paths": [
"error",
{
"rootDir": "src",
"prefix": "@"
}
],
// ── 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-direct-mutation-state": "error",
"react/no-find-dom-node": "error",
"react/no-is-mounted": "error",
"react/no-render-return-value": "error",
"react/no-string-refs": "error",
"react/no-unescaped-entities": "error",
"react/no-unknown-property": "error",
"react/require-render-return": "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": "warn",
"react/no-this-in-sfc": "error",
"react/state-in-constructor": [
"error",
"always"
],
// ── 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"
},
"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/components/settings/NumberSetting.tsx",
"src/base/utils/MediaQuery.tsx",
"src/features/authentication/AuthManager.ts",
"src/features/browse/extensions/components/ExtensionCard.tsx",
"src/features/chapter/services/Chapters.ts",
"src/features/extension/info/components/ActionButton.tsx",
"src/features/manga/components/TrackMangaButton.tsx",
"src/features/manga/services/Mangas.ts",
"src/features/metadata/services/MetadataMigrations.ts",
"src/features/migration/components/MigrateDialog.tsx",
"src/features/navigation-bar/components/MobileBottomBar.tsx",
"src/features/reader/services/ReaderControls.ts",
"src/features/reader/services/ReaderService.ts",
"src/features/settings/screens/ImageProcessingSetting.tsx",
"src/features/source/browse/components/SourceOptions.tsx",
"src/features/source/browse/components/filters/SelectFilter.tsx",
"src/features/source/services/Sources.ts",
"src/lib/dayjs/LocaleImporter.ts",
"src/lib/dnd-kit/DndKitUtil.ts",
"src/lib/requests/RequestManager.ts",
"src/lib/requests/client/GraphQLClient.ts",
"src/lib/service-worker/ImageCache.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/**"
]
}

View File

@@ -1,7 +0,0 @@
{
"tabWidth": 4,
"singleQuote": true,
"printWidth": 120,
"semi": true,
"trailingComma": "all"
}

View File

@@ -7,12 +7,14 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).
## [Unreleased] (Preview)
### Added
- (**Theme**) Add an option to save the dynamic color theme on the manga page as a custom theme
- (**WebUI Update**) Add an option to (partially) disable showing information when the webUI got updated
- (**Server Update**) Add an option to disable showing information when the server got updated
- (**Manga**) Add an option to include client data during migration
### Changed
- (**General**) Preserve refresh token (UI Login) over sessions
- (**General**) Reduce base font size
- (**General**) Save selected app language on the server
@@ -24,6 +26,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).
- (**Browse**) Merge languages filter of the sources and extensions into one
### Fixed
- (**General**) Fix saving large client data on the server (e.g., custom source filters)
- (**Library**) Fix total library size chip color in light mode
- (**Browse**) Fix missing pinned sources in the source language filter
@@ -34,6 +37,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).
## [20251230.01] (r2937) - 2025-12-30
### Added
- (**Navigation**) Show extension update information in app navigation
- (**General**) Add support for "ui login" authentication mode
- (**General**) Add support for hosting on subpaths
@@ -50,6 +54,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).
- (**Browse**) Add "open in webview" button in source browse page
### Changed
- (**General**) Improve loading of images
- (**Category**) Prevent creating categories without a name
- (**WebUI Update**) Do not require a forced page refresh when an update has been detected in case the app just got opened
@@ -57,6 +62,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).
- (**Settings**) Move "Clear cache" setting to new "Images" setting page
### Fixed
- (**General**) Fix tooltips sometimes causing a layout shift
- (**General**) Fix back button under some specific conditions (e.g., `library category X``mange``reader``manga` → back button → `library`; should have opened `library category X`)
- (**Manga**) Fix failing migration with disabled "tracking" data
@@ -79,14 +85,17 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).
- (**Reader**) Fix broken scrolling in continuous horizontal reading mode
### Translations
Feel free to translate the project on [Weblate](https://hosted.weblate.org/projects/suwayomi/suwayomi-webui/)
Thanks to everyone that contributed to the translation of this project.
#### Added
- Russian (by Micka149)
#### Updated
- Tamil (by தமிழ்நேரம்)
- Polish (by UnknownSkyrimPasserby)
- Korean (by Kim KKAng, jun)
@@ -102,6 +111,7 @@ Thanks to everyone that contributed to the translation of this project.
- Turkish (by Metin)
### Contributors
Thanks to everyone that contributed to this release
@schroda, @github-actions[bot], @weblate, @cpiber, @ginocic, @infyProductions, @Metincloup, @Micka149, @dejavui, @TheRay82, @plum7x, @aizhimoran, @zeedif, @AdrianRSouza, @ritma-bws, @CBujeda, @rogervc, @UnknownSkyrimPasserby, @mrintrepide, @q1114938967, @sksper1-stack, @renjfk, @Smileskun, @bornav, @9811pc, @imammansyur, @cho558, @marimo-nekomimi, @Dusky-dev
@@ -109,9 +119,11 @@ Thanks to everyone that contributed to this release
## [20250801.01] (r2717) - 2025-08-01
### Fixed
- (**General**) Fix white screen on page load
### Contributors
Thanks to everyone that contributed to this release
@schroda
@@ -119,6 +131,7 @@ Thanks to everyone that contributed to this release
## [20250731.01] (r2715) - 2025-07-31
### Added
- (**General**) Add support for the suwayomi WebView
- (**Settings**) Add new OPDS server settings
- (**Settings**) Add new "simple login" authentication setting
@@ -128,6 +141,7 @@ Thanks to everyone that contributed to this release
- (**Manga**) Add support for private track bindings
### Fixed
- (**General**) Fix drag and drop on touch devices
- (**Reader**) Fix auto scrolling with static overlay
- (**Extension**) Fix clicking on action button (install, uninstall, update, ...) opening the extension info page
@@ -136,11 +150,13 @@ Thanks to everyone that contributed to this release
- (**Chapter**) Fix updating read status of already read chapters when using the mark previous as read option
### Translations
Feel free to translate the project on [Weblate](https://hosted.weblate.org/projects/suwayomi/suwayomi-webui/)
Thanks to everyone that contributed to the translation of this project.
#### Updated
- Vietnamese (by Nguyễn Trung Đức)
- Chinese (Simplified) (by 清水汐音)
- Japanese (by 9811pc)
@@ -148,6 +164,7 @@ Thanks to everyone that contributed to the translation of this project.
- German (by Constantin Piber)
### Contributors
Thanks to everyone that contributed to this release
@weblate, @cpiber, @EugeneCage, @dejavui, @UnknownSkyrimPasserby, @aizhimoran, @schroda, @9811pc, @AlirezaGh1993, @gianlucalauro, @junmusk, @JiPaix, @leollo98, @LycusCoder, @Sawadikhap, @yutthaphon, @Oxara, @Mamotromico, @plum7x, @jintaxi, @TamilNeram, @shirishsaxena
@@ -155,6 +172,7 @@ Thanks to everyone that contributed to this release
## [20250703.01] (r2643) - 2025-07-03
### Added
- (**Manga**) Add functionality to click on title/artist/author/genre to trigger library/source/global search
- (**Library**) Add option to perform current search globally
- (**Source**) Add option to disable sources of an extension
@@ -174,6 +192,7 @@ Thanks to everyone that contributed to this release
- (**Chapter**) Show missing chapters info between chapters in the chapter list
### Changed
- (**Manga**) Require confirmation before removing an entry from your library
- (**Chapter**) Require confirmation for the following actions
- Download
@@ -200,9 +219,11 @@ Thanks to everyone that contributed to this release
- (**Setting**) Prevent setting up basic auth with both an empty username and password. This is an issue on iOS whose native basic auth prompt disables the "login" button in case both fields are empty.
### Removed
- (**Source**) Remove "popular" button on source card
### Fixed
- (**General**) Fix custom long press causing native mobile long press menu to get opened
- (**General**) Fix refreshing data after importing a backup
- (**Reader**) Fix each key press triggering a keybind (example: "n" → next page, "ctrl+n" → next chapter - previously "ctrl+n" would have triggered both keybinds)
@@ -221,14 +242,17 @@ Thanks to everyone that contributed to this release
- (**Download**) Fix downloads start/stop button not updating
### Translations
Feel free to translate the project on [Weblate](https://hosted.weblate.org/projects/suwayomi/suwayomi-webui/)
Thanks to everyone that contributed to the translation of this project.
#### Added
- Portuguese (Brazil) (by Psico, Jorge Adriano Cavalcante Alves)
#### Updated
- Chinese (Traditional) (by plum7x)
- Vietnamese (by Nguyễn Trung Đức)
- Italian (by xAizawa)
@@ -242,6 +266,7 @@ Thanks to everyone that contributed to the translation of this project.
- German (by Constantin Piber)
### Contributors
Thanks to everyone that contributed to this release
@weblate, @9811pc, @cpiber, @yutthaphon, @dejavui, @UnknownSkyrimPasserby, @dpkass, @marimo-nekomimi, @TamilNeram, @aizhimoran, @schroda, @junmusk, @adriano1816, @Zereef, @plum7x, @FedericoRossiIT, @Well2333, @ketw09, @IDika31, @kosmik7, @letroll, @KrachDev, @MageSneaky, @Tankudoraiba, @tizio04, @xAizawa
@@ -249,17 +274,21 @@ Thanks to everyone that contributed to this release
## [1.5.1] (r2467) - 2025-04-07
### Fixed
- (**Server update**) Fix detection of available server update
### Translations
Feel free to translate the project on [Weblate](https://hosted.weblate.org/projects/suwayomi/suwayomi-webui/)
Thanks to everyone that contributed to the translation of this project.
#### Updated
- Polish (by UnknownSkyrimPasserby)
### Contributors
Thanks to everyone that contributed to this release
@schroda, @weblate, @UnknownSkyrimPasserby
@@ -267,13 +296,16 @@ Thanks to everyone that contributed to this release
## [1.5.0] (r2461) - 2025-04-05
> ### Required action
>
> - If you have installed the web app as a PWA you need to reinstall the app after it has been updated to the latest version
### Highlights
- Completely new reader
- Use predefined UI themes or create your own
### Added
- (**General**) Add labels to the navigation side bar icons
- (**General**) Add option to expand and collapse the navigation side bar
- (**General**) Update app styling
@@ -336,6 +368,7 @@ Thanks to everyone that contributed to this release
- (**Migrate**) Add sort options for migratable sources (by source name, manga count; order ascending/descending)
### Changed
- (**General**) Introduce "More" page
- Remove "Settings" from navigation bar
- Remove "Downloads" from mobile navigation bar
@@ -350,9 +383,11 @@ Thanks to everyone that contributed to this release
- (**Settings**) Move link to "Category settings" to "library settings" and "More" page
### Removed
- (**Settings**) Remove "graphql debug level" setting (was removed by the server)
### Fixed
- (**General**) Fix setting browser locale for used date library (some supported browser locales were not correctly detected)
- (**General**) Fix server subscriptions not updating to new server after changing the server address
- (**General**) Fix missing polyfill to support older browsers
@@ -417,15 +452,18 @@ Thanks to everyone that contributed to this release
- (**Category**) Fix missing error message on category update/creation failure
### Translations
Feel free to translate the project on [Weblate](https://hosted.weblate.org/projects/suwayomi/suwayomi-webui/)
Thanks to everyone that contributed to the translation of this project.
#### Added
- Tamil (by தமிழ்நேரம்)
- Polish (by UnknownSkyrimPasserby)
#### Updated
- Chinese (Simplified) (by 清水汐音, Joshua Astray)
- Chinese (Traditional) (by plum7x, aaron mo)
- French (by Jean-Philippe ALLEGRO, Tycoon3819)
@@ -437,6 +475,7 @@ Thanks to everyone that contributed to the translation of this project.
- Vietnamese (by Nguyễn Trung Đức, PandaKewt)
#### Removed (less than 75% translated)
- "Arabic (ar)" (71.7%)
- "Bengali (bn)" (1.8%)
- "Danish (da)" (13.6%)
@@ -455,6 +494,7 @@ Thanks to everyone that contributed to the translation of this project.
- "Cantonese (Traditional Han script) (yue-Hant)" (0%)
### Contributors
Thanks to everyone that contributed to this release
@schroda, @weblate, @cpiber, @infyProductions, @leollo98, @dejavui, @UnknownSkyrimPasserby, @plum7x, @aizhimoran, @ykxkykx, @HanJoJ, @jicedtea, @Daviid-P, @kalebC, @TamilNeram, @Duoduo12138, @crystailx, @Banchon999, @jesusFx, @Runkandel, @lengzero, @tizio04, @zeedif, @Wybxc, @Mamotromico, @CladZo91, @senseimon1, @krizopraz, @mipo89-dev, @Robonau, @Meliodas-Sama, @991n1nd12, @RikCost, @JiPaix, @LycusCoder, @Oxara, @PandaKewt, @BrutuZ, @Yinr, @leqord, @RickyLam11, @Hada45, @gianlucalauro, @lucaschristofaro, @marimo-nekomimi
@@ -462,12 +502,14 @@ Thanks to everyone that contributed to this release
## [1.1.0] (r1689) - 2024-06-14
### Highlights
- Tracking support
- Different UI settings per device (e.g. reader settings)
- Save searches in source browse
- Chapter download selection improvement
### Added
- (**Internationalization**) Apply right to left styling for languages that are read right to left
- (**Library**) New setting to remove manga from categories when removing them from the library
- (**Library**) Filter library for manga that have bookmarked chapters
@@ -484,7 +526,7 @@ Thanks to everyone that contributed to this release
- From library
- (**Manga**) Copy manga title on long press in manga page
- (**Settings**) UI specific settings that are stored on the server are now saved per device (devices can be managed in the settings)
- A device name is allowed to have 16 chars (a-Z, 0-9, -, _) (e.g. "My_Phone-1")
- A device name is allowed to have 16 chars (a-Z, 0-9, -, \_) (e.g. "My_Phone-1")
- Device specific settings
- Reader
- Default settings
@@ -527,12 +569,14 @@ Thanks to everyone that contributed to this release
- Refreshes the tab on close
### Changed
- (**Library**) The category selection dialog is not shown when adding a manga to the library without having created categories
- (**Library**) The current library manga selection now gets unselected after triggering an action
- (**Manga**) The continue read/resume button now uses the first unread chapter as the resume point
- (**Download**) The download queue clear button is now always enabled
### Fixed
- (**General**) Fix white screen on older browsers
- (**General**) Fix old data still being shown after
- Changing the server url
@@ -564,11 +608,13 @@ Thanks to everyone that contributed to this release
- (**Manga grid**) Fix jittering/flickering of manga grid items
### Translations
Feel free to translate the project on [Weblate](https://hosted.weblate.org/projects/suwayomi/suwayomi-webui/)
Thanks to everyone that contributed to the translation of this project.
#### Added
- Norwegian Bokmål (by VR Kek)
- Swedish (by Alexander)
- Turkish (by Efe Devirgen)
@@ -576,6 +622,7 @@ Thanks to everyone that contributed to the translation of this project.
- Bengali (by Akhlak Ur Rahman)
#### Updated
- Chinese (Simplified) (by 清水汐音, Kouki Kitamura)
- Italian (by tizio04, Roberto Palmese)
- Japanese (by marimo, Siamese)
@@ -589,6 +636,7 @@ Thanks to everyone that contributed to the translation of this project.
- Indonesian (by Axel C)
### Contributors
Thanks to everyone that contributed to this release
@leollo98, @kiritsumafuyu, @marimo-nekomimi, @tizio04, @aizhimoran, @schroda, @akhlakurrahman1011, @xxx1SET1xxx, @jesusFx, @SySen04, @dejavui, @JoHena, @jintaxi, @okpo2188513, @axlchr12, @Lafrend, @EfeDevirgen, @HugoLeBoennec, @PandaKewt, @rpalmese, @ZerOri, @Xsrt251, @plum7x, @GR2066878693, @taos15, @Topru333, @chancez, @MageSneaky, @Azokul01, @gabrielssevero, @0QwQ0, @Rintan, @zyzz8520, @guohuageng
@@ -596,6 +644,7 @@ Thanks to everyone that contributed to this release
## [1.0.0] (r1411) - 2024-02-23
### Added
- (**General**) Added internationalization (help translating on [Weblate](https://hosted.weblate.org/projects/suwayomi/suwayomi-webui/))
- (**General**) Get notified when a new server version has been released
- (**General**) Manually check for new versions (server and webUI)
@@ -629,14 +678,17 @@ Thanks to everyone that contributed to this release
- (**Settings**) Server settings can now be changed from the UI
### Fixed
- A lot (and added new ones for the future, lul)
### Translations
Feel free to translate the project on [Weblate](https://hosted.weblate.org/projects/suwayomi/suwayomi-webui/).
Thanks to everyone that contributed to the translation of this project.
#### Added
- Arabic (by abdelbasset jabrane, Bander AL-shreef)
- Chinese (Simplified) (by misaka10843, 蓝云Reyes, Nite07, 志明, ccms, 宮河ひより, 清水汐音, DevCoz)
- Chinese (Traditional) (by plum7x, 蓝云Reyes)
@@ -652,6 +704,7 @@ Thanks to everyone that contributed to the translation of this project.
- Vietnamese (by xconkhi9x)
### Contributors
Thanks to everyone that contributed to this release
@schroda, @jesusFx, @QuietBlade, @anvstin, @guohuageng, @plum7x, @HiyoriTUK, @aizhimoran, @JiPaix, @Yuhyeong, @a18ccms, @chancez, @rickymcmuffin, @zmmx, @alexandrejournet, @ibaraki-douji, @nitezs, @misaka10843, @Becods, @skrewde, @xconkhi9x, @cnmorocho, @Wip-Sama, @Kefir2105, @RafieHardinur, @SuperMario229, @Alexandre-P-J, @AriaMoradi, @NathanBnm, @FumoVite, @JoHena, @bandysharif, @DevCoz, @comradekingu, @Zereef, @akabhirav

View File

@@ -1,20 +1,26 @@
# Contributing
## Where should I start?
Everything from https://github.com/Suwayomi/Suwayomi-Server/blob/master/CONTRIBUTING.md#where-should-i-start applies here.
## About this project
### Building the app
See [BUILDING.md](./BUILDING.md) for more information
## WebUI to [Server](https://github.com/Suwayomi/Suwayomi-Server) mapping
### Explanation
For the server to be able to automatically download the latest compatible WebUI version, the [version to server version mapping file](versionToServerVersionMapping.json) has to be provided.<br/>
The order of the version mapping is important and has to be sorted by latest WebUI version to the oldest version.<br/>
The latest version will always be `PREVIEW`.
### When to update
- changes get added that require a new minimum server version
- **update:** the mapped server version for the `PREVIEW` version
- releasing a new version
@@ -23,7 +29,9 @@ The latest version will always be `PREVIEW`.
- changed: add a new entry below the `PREVIEW` version with the mapped server version from `PREVIEW`
## Coding Style Guide
**Note:** Some of the bellow are new, refactor the code to match the style guide where you see inconsistency.
- Don't use relative imports.
- We are using MUI v5, the all stylings must be applied with the new system.
- Never use the `style` prop, there's always a cleaner solution with `sx` or `styled`.

View File

@@ -1,10 +1,12 @@
# Suwayomi-WebUI
This is the repository of the default client of [Suwayomi-Server](https://github.com/Suwayomi/Suwayomi-Server).
The server has this web app bundled by default and is able to automatically update to the latest versions.
Thus, there is no need to manually download any builds unless you want to host the app yourself instead of having it hosted by the Suwayomi-Server.
## Features
- Library management
- Library page - manga management
- Filter/Sort/Search your manga
@@ -47,6 +49,7 @@ Thus, there is no need to manually download any builds unless you want to host t
- Dynamic theme on manga pages
## Preview
An ongoing changelog of all relevant changes since the last stable release can be found [here](https://github.com/Suwayomi/Suwayomi-WebUI/issues/749)
To use the preview version you can select the PREVIEW channel in the settings of your Suwayomi-Server.
@@ -58,12 +61,14 @@ In case your server is outdated, it will automatically downgrade to the latest c
Minified builds of WebUI can be found here [Suwayomi-WebUI-preview](https://github.com/Suwayomi/Suwayomi-WebUI-preview).
Additionally, there is an online build of the WebUI preview version that is available [here](https://suwayomi-webui-preview.github.io/).
*Make sure to set your Suwayomi-Server hostname in Settings or you'll get infinite loading.* Also note that its the **latest** revision of WebUI and might not work correctly if you connect to a stable build of Suwayomi-Server.
_Make sure to set your Suwayomi-Server hostname in Settings or you'll get infinite loading._ Also note that its the **latest** revision of WebUI and might not work correctly if you connect to a stable build of Suwayomi-Server.
## Contributing and Technical info
See [CONTRIBUTING.md](./CONTRIBUTING.md).
## Translation
Feel free to translate the project on [Weblate](https://hosted.weblate.org/projects/suwayomi/suwayomi-webui/)
<details><summary>Translation Progress</summary>

View File

@@ -1,187 +1,23 @@
import js from '@eslint/js';
import globals from 'globals';
/*
* Minimal ESLint config — only rules not yet supported natively in oxlint.
* The bulk of linting is done by oxlint (.oxlintrc.json).
*/
import tseslint from 'typescript-eslint';
import reactPlugin from 'eslint-plugin-react';
import reactHooksPlugin from 'eslint-plugin-react-hooks';
import jsxA11y from 'eslint-plugin-jsx-a11y';
import importX, { importXResolverCompat } from 'eslint-plugin-import-x';
import * as tsResolver from 'eslint-import-resolver-typescript';
import unusedImports from 'eslint-plugin-unused-imports';
import noRelativeImportPaths from 'eslint-plugin-no-relative-import-paths';
import header from '@tony.ganchev/eslint-plugin-header';
import lingui from 'eslint-plugin-lingui';
import prettierRecommended from 'eslint-plugin-prettier/recommended';
export default tseslint.config(
{ ignores: ['src/lib/graphql/generated/**'] },
{ ignores: ['src/lib/graphql/generated/**', 'eslint.config.js'] },
// Base configs
js.configs.recommended,
...tseslint.configs.recommended,
reactPlugin.configs.flat.recommended,
reactPlugin.configs.flat['jsx-runtime'],
reactHooksPlugin.configs['recommended-latest'],
jsxA11y.flatConfigs.recommended,
importX.flatConfigs.recommended,
lingui.configs['flat/recommended'],
// Main rules
{
files: ['**/*.{ts,tsx,js,jsx}'],
languageOptions: {
globals: { ...globals.browser },
parserOptions: {
projectService: true,
tsconfigRootDir: import.meta.dirname,
},
},
settings: {
react: { version: 'detect' },
'import-x/extensions': ['.ts', '.tsx', '.js', '.jsx'],
'import-x/external-module-folders': ['node_modules', 'node_modules/@types'],
'import-x/parsers': {
'@typescript-eslint/parser': ['.ts', '.tsx'],
},
'import-x/resolver-next': [
importXResolverCompat(tsResolver, {
extensions: ['.ts', '.tsx', '.js', '.jsx'],
}),
],
parser: tseslint.parser,
},
plugins: {
'unused-imports': unusedImports,
'no-relative-import-paths': noRelativeImportPaths,
header,
},
rules: {
// Lingui
'lingui/no-expression-in-message': 'off',
// Core JS rules from Airbnb
'no-console': 'warn',
'no-nested-ternary': '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-proto': 'error',
'no-extend-native': 'error',
'no-caller': 'error',
'no-alert': 'warn',
'no-void': 'error',
'no-bitwise': 'error',
'no-continue': '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-unreachable-loop': 'error',
'no-useless-concat': 'error',
'no-useless-return': 'error',
'no-useless-computed-key': 'error',
'no-useless-rename': 'error',
'no-underscore-dangle': 'error',
'no-restricted-exports': ['error', { restrictedNamedExports: ['default', 'then'] }],
eqeqeq: ['error', 'always', { null: 'ignore' }],
'consistent-return': 'error',
'guard-for-in': 'error',
'default-case': ['error', { commentPattern: '^no default$' }],
'default-case-last': 'error',
radix: 'error',
yoda: 'error',
'one-var': ['error', 'never'],
'max-classes-per-file': ['error', 1],
'prefer-template': 'error',
'prefer-destructuring': [
'error',
{
VariableDeclarator: { array: false, object: true },
AssignmentExpression: { array: true, object: false },
},
{ enforceForRenamedProperties: false },
],
'prefer-exponentiation-operator': 'error',
'prefer-object-spread': 'error',
'prefer-promise-reject-errors': ['error', { allowEmptyReject: true }],
'prefer-regex-literals': ['error', { disallowRedundantWrapping: true }],
'object-shorthand': ['error', 'always', { ignoreConstructors: false, avoidQuotes: true }],
'no-param-reassign': ['error', { props: true, ignorePropertyModificationsForRegex: ['^draft'] }],
'no-plusplus': ['error', { allowForLoopAfterthoughts: true }],
'class-methods-use-this': 'off',
// TypeScript rules (non-type-checked)
'no-shadow': 'off',
'@typescript-eslint/no-shadow': 'error',
'no-loop-func': 'off',
'@typescript-eslint/no-loop-func': 'error',
'@typescript-eslint/no-use-before-define': ['error', { functions: false, classes: true, variables: true }],
'default-param-last': 'off',
'@typescript-eslint/default-param-last': 'error',
// TypeScript rules (type-checked)
'no-implied-eval': 'off',
'@typescript-eslint/no-implied-eval': 'error',
'@typescript-eslint/only-throw-error': 'error',
'dot-notation': 'off',
'@typescript-eslint/dot-notation': 'error',
'@typescript-eslint/return-await': ['error', 'in-try-catch'],
'@typescript-eslint/naming-convention': [
'error',
{ selector: 'variable', format: ['camelCase', 'PascalCase', 'UPPER_CASE'] },
{ selector: 'function', format: ['camelCase', 'PascalCase'] },
{ selector: 'typeLike', format: ['PascalCase'] },
],
// TypeScript — relax rules not effectively enforced in old config
'@typescript-eslint/no-explicit-any': 'off',
'@typescript-eslint/no-unused-vars': [
'error',
{
vars: 'all',
args: 'after-used',
ignoreRestSiblings: true,
argsIgnorePattern: '^_',
varsIgnorePattern: '^_',
caughtErrorsIgnorePattern: '^_|^e$|^error$',
},
],
'@typescript-eslint/no-empty-object-type': 'off',
'@typescript-eslint/no-unsafe-function-type': 'off',
'@typescript-eslint/ban-ts-comment': 'off',
// Unused imports
'unused-imports/no-unused-imports': 'error',
// Import rules from Airbnb
'import-x/no-cycle': 'off', // TODO: enable after resolving circular dependencies
'import-x/no-mutable-exports': 'error',
'import-x/first': 'error',
'import-x/newline-after-import': 'error',
'import-x/no-amd': 'error',
'import-x/no-absolute-path': 'error',
'import-x/no-self-import': 'error',
'import-x/no-useless-path-segments': ['error', { noUselessIndex: true }],
'import-x/no-extraneous-dependencies': 'error',
'import-x/order': ['error', { groups: ['builtin', 'external', 'internal', 'parent', 'sibling', 'index'] }],
// Import rules (import → import-x)
'import-x/prefer-default-export': 'off',
'import-x/no-default-export': 'error',
'import-x/extensions': 'off',
'import-x/no-named-as-default': 'off',
'import-x/no-named-as-default-member': 'off',
'import-x/named': 'off',
'import-x/no-unresolved': ['error', { ignore: ['^@dnd-kit/core/dist', '^i18next$', '^public/'] }],
// Header
// MPL 2.0 license header
'header/header': [
'error',
'block',
@@ -196,136 +32,6 @@ export default tseslint.config(
],
2,
],
// Prettier
'prettier/prettier': 'error',
// React hooks — relax rules that are stricter in v5
'react-hooks/exhaustive-deps': 'off',
// React rules from Airbnb
'react/destructuring-assignment': ['error', 'always'],
'react/no-unused-prop-types': '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': 'warn',
'react/no-this-in-sfc': 'error',
'react/state-in-constructor': ['error', 'always'],
// React — match Airbnb overrides
'react/jsx-uses-react': 'off',
'react/react-in-jsx-scope': 'off',
'react/jsx-no-bind': 'off',
'react/jsx-props-no-spreading': 'off',
'react/require-default-props': 'off',
'react/function-component-definition': 'off',
'react/display-name': 'off',
'react/no-unstable-nested-components': ['error', { allowAsProps: true }],
// jsx-a11y — match Airbnb (allow autoFocus on non-DOM elements)
'jsx-a11y/no-autofocus': ['error', { ignoreNonDOM: true }],
// Restricted imports (MUI pattern)
'no-restricted-imports': [
'error',
{
patterns: [
{
group: ['@mui/*', '!@mui/material/', '!@mui/icons-material/', '!@mui/x-date-pickers/'],
},
{
group: ['@mui/*/*/*'],
},
],
},
],
// Restricted syntax (SxProps check)
'no-restricted-syntax': [
'error',
{
selector: 'TSTypeReference[typeName.name="SxProps"]:not([typeArguments])',
message: 'SxProps must have Theme parameter to avoid significant compiler slowdown.',
},
],
// No relative import paths
'no-relative-import-paths/no-relative-import-paths': [
'error',
{
rootDir: 'src',
prefix: '@',
},
],
},
},
// Files using hooks in class static methods (valid namespace pattern, flagged by react-hooks v5)
{
files: [
'src/base/components/settings/NumberSetting.tsx',
'src/base/utils/MediaQuery.tsx',
'src/features/authentication/AuthManager.ts',
'src/features/browse/extensions/components/ExtensionCard.tsx',
'src/features/chapter/services/Chapters.ts',
'src/features/extension/info/components/ActionButton.tsx',
'src/features/manga/components/TrackMangaButton.tsx',
'src/features/manga/services/Mangas.ts',
'src/features/metadata/services/MetadataMigrations.ts',
'src/features/migration/components/MigrateDialog.tsx',
'src/features/navigation-bar/components/MobileBottomBar.tsx',
'src/features/reader/services/ReaderControls.ts',
'src/features/reader/services/ReaderService.ts',
'src/features/settings/screens/ImageProcessingSetting.tsx',
'src/features/source/browse/components/SourceOptions.tsx',
'src/features/source/browse/components/filters/SelectFilter.tsx',
'src/features/source/services/Sources.ts',
'src/lib/dayjs/LocaleImporter.ts',
'src/lib/dnd-kit/DndKitUtil.ts',
'src/lib/requests/RequestManager.ts',
'src/lib/requests/client/GraphQLClient.ts',
'src/lib/service-worker/ImageCache.ts',
'src/lib/virtuoso/Virtuoso.util.tsx',
],
rules: {
'react-hooks/rules-of-hooks': 'off',
},
},
// Tools/scripts override
{
files: ['tools/scripts/**/*'],
rules: {
'no-console': 'off',
'no-relative-import-paths/no-relative-import-paths': 'off',
'import-x/no-extraneous-dependencies': ['error', { devDependencies: true }],
'@typescript-eslint/naming-convention': 'off',
},
},
// Config files — not part of tsconfig, disable type-checked linting and project-specific rules
{
files: ['eslint.config.js'],
...tseslint.configs.disableTypeChecked,
rules: {
...tseslint.configs.disableTypeChecked.rules,
'header/header': 'off',
'import-x/no-default-export': 'off',
'import-x/no-extraneous-dependencies': 'off',
'import-x/default': 'off',
},
},
// Prettier must be last
prettierRecommended,
);

View File

@@ -1,29 +1,26 @@
<!DOCTYPE html>
<!doctype html>
<html>
<head>
<head>
<script>
(() => {
if (document.querySelector('base')) {
return
return;
}
const base = document.createElement('base');
base.href = '/';
document.head.prepend(base);
})()
})();
</script>
<title>Suwayomi</title>
<meta name="apple-mobile-web-app-title" content="Suwayomi" />
<meta
name="description"
content="A manga reader that runs tachiyomi's extensions"
/>
<meta name="description" content="A manga reader that runs tachiyomi's extensions" />
<meta charset="utf-8"/>
<meta charset="utf-8" />
<meta name='viewport' content='minimum-scale=1, initial-scale=1, viewport-fit=cover, width=device-width'>
<meta name="mobile-web-app-capable" content="yes">
<meta name="apple-mobile-web-app-status-bar-style" content="black-translucent">
<meta name="viewport" content="minimum-scale=1, initial-scale=1, viewport-fit=cover, width=device-width" />
<meta name="mobile-web-app-capable" content="yes" />
<meta name="apple-mobile-web-app-status-bar-style" content="black-translucent" />
<link rel="icon" type="image/png" href="/favicon-96x96.png" sizes="96x96" />
<link rel="icon" type="image/svg+xml" href="/favicon.svg" />
@@ -34,18 +31,18 @@
user's mobile device or desktop. See https://developers.google.com/web/fundamentals/web-app-manifest/
-->
<link rel="manifest" href="/site.webmanifest" crossorigin="use-credentials" />
</head>
<body>
<noscript>You need to enable JavaScript to run this app.</noscript>
<div id="root"></div>
<script type="module" src="./src/index.tsx"></script>
<script>
</head>
<body>
<noscript>You need to enable JavaScript to run this app.</noscript>
<div id="root"></div>
<script type="module" src="./src/index.tsx"></script>
<script>
const backgroundColor = (() => {
const storageValue = window.localStorage.getItem("theme_background")
const storageValue = window.localStorage.getItem('theme_background');
try {
return JSON.parse(storageValue)
return JSON.parse(storageValue);
} catch (e) {
return storageValue
return storageValue;
}
})();
@@ -62,8 +59,8 @@
themeColorMeta.setAttribute('content', backgroundColor);
}
}
</script>
<!--
</script>
<!--
This HTML file is a template.
If you open it directly in the browser, you will see an empty page.
@@ -73,5 +70,5 @@
To begin the development, run `npm start` or `yarn start`.
To create a production bundle, use `npm run build` or `yarn build`.
-->
</body>
</body>
</html>

View File

@@ -13,7 +13,9 @@
"test": "node -e \"console.log('imagine')\"",
"build-md5": "find build -type f | sort | xargs md5sum | awk '{ print $1 }' | tr -d '\n' | md5sum| awk '{ print $1 }' > buildZip/md5sum ",
"build-zip": "cd build && rev=$(git rev-list HEAD --count) && echo r$rev > revision && zip -9 -r ../buildZip/Suwayomi-WebUI-r$rev *",
"lint": "eslint src tools --max-warnings=0 --cache",
"lint": "oxlint src tools && eslint src tools --max-warnings=0",
"format": "oxfmt --write .",
"format:check": "oxfmt --check .",
"createCommitChangelog": "tsx tools/scripts/release/createCommitChangelog.ts",
"createTranslationChangelog": "tsx tools/scripts/release/createTranslationChangelog.ts",
"createReleaseChangelog": "tsx tools/scripts/release/createReleaseChangelog.ts",
@@ -31,15 +33,6 @@
"tsc:legacy": "tsc",
"prepare": "husky"
},
"engines": {
"node": ">=24"
},
"lint-staged": {
"*.{ts,tsx,js,jsx}": "eslint --fix"
},
"resolutions": {
"@swc/core": "1.15.11"
},
"dependencies": {
"@apollo/client": "4.1.6",
"@dnd-kit/core": "6.3.1",
@@ -102,6 +95,7 @@
"@lingui/format-po": "5.9.2",
"@lingui/swc-plugin": "5.11.0",
"@lingui/vite-plugin": "5.9.2",
"@tony.ganchev/eslint-plugin-header": "3.2.6",
"@types/node": "24.10.1",
"@types/react": "19.2.14",
"@types/react-beautiful-dnd": "13.1.8",
@@ -110,29 +104,20 @@
"@types/stylis": "4.2.7",
"@types/webfontloader": "1.6.38",
"@types/yargs": "17.0.35",
"@tony.ganchev/eslint-plugin-header": "3.2.6",
"@typescript/native-preview": "7.0.0-dev.20260309.1",
"@vitejs/plugin-legacy": "7.2.1",
"@vitejs/plugin-react-swc": "4.2.3",
"dotenv": "17.3.1",
"eslint": "9.39.4",
"eslint-config-prettier": "10.1.8",
"eslint-import-resolver-typescript": "4.4.4",
"eslint-plugin-import-x": "4.16.1",
"eslint-plugin-jsx-a11y": "6.10.2",
"eslint-plugin-lingui": "0.11.0",
"eslint-plugin-no-relative-import-paths": "1.6.1",
"eslint-plugin-prettier": "5.5.5",
"eslint-plugin-react": "7.37.5",
"eslint-plugin-react-hooks": "5.2.0",
"eslint-plugin-unused-imports": "4.4.1",
"globals": "15.15.0",
"husky": "9.1.7",
"lint-staged": "16.3.2",
"prettier": "3.8.1",
"oxfmt": "0.37.0",
"oxlint": "1.52.0",
"syncyarnlock": "1.0.19",
"terser": "5.46.0",
"tsx": "4.21.0",
"@typescript/native-preview": "7.0.0-dev.20260309.1",
"typescript": "5.9.3",
"typescript-eslint": "8.57.0",
"vite": "7.3.1",
@@ -142,5 +127,19 @@
"workbox-build": "7.4.0",
"workbox-window": "7.4.0",
"yargs": "18.0.0"
},
"resolutions": {
"@swc/core": "1.15.11"
},
"lint-staged": {
"*.{ts,tsx,js,jsx}": [
"oxfmt --write",
"oxlint --fix",
"eslint --fix"
],
"*.{json,md,yml,yaml,css,scss,html,graphql}": "oxfmt --write"
},
"engines": {
"node": ">=24"
}
}

View File

@@ -10,7 +10,6 @@ declare module 'apollo-upload-client/UploadHttpLink.mjs' {
import { ApolloLink } from '@apollo/client';
import type { BaseHttpLink } from '@apollo/client/link/http';
// eslint-disable-next-line import-x/no-default-export
export default class UploadHttpLink extends ApolloLink {
constructor(options?: BaseHttpLink.ConstructorOptions);
}

View File

@@ -22,7 +22,7 @@ interface State {
}
class RealErrorBoundary extends Component<Props, State> {
// eslint-disable-next-line react/state-in-constructor
// oxlint-disable-next-line react/state-in-constructor
public state: State = { error: null };
private prevPath: string = '';
@@ -45,10 +45,9 @@ class RealErrorBoundary extends Component<Props, State> {
}
public componentDidCatch(error: Error, errorInfo: ErrorInfo) {
// eslint-disable-next-line no-console
// oxlint-disable-next-line no-console
console.error('Uncaught error:', error, errorInfo);
// eslint-disable-next-line react/destructuring-assignment
this.props.setTrackPathChange(true);
}

View File

@@ -27,7 +27,7 @@ export class AuthManager {
private static refreshingToken: boolean = false;
private static subscribe(callback: () => void): () => void {
// eslint-disable-next-line no-plusplus
// oxlint-disable-next-line no-plusplus
const key = AuthManager.subscribedCount++;
this.subscribers.set(key, callback);

View File

@@ -103,7 +103,7 @@ export const ChapterCard = memo((props: IProps) => {
return;
}
// eslint-disable-next-line no-param-reassign
// oxlint-disable-next-line no-param-reassign
event.shiftKey = true;
handleClick(event);
});

View File

@@ -76,10 +76,7 @@ const querySearchManga = (
{ title, genre: genres, description, artist, author, source, sourceId }: TMangaQueryFilter,
): boolean =>
performSearch([query], [title]) ||
performSearch(
query?.split(','),
genres.map((genre) => enhancedCleanup(genre)),
) ||
performSearch(query?.split(','), genres.map((genre) => enhancedCleanup(genre))) ||
performSearch([query], [description]) ||
performSearch([query], [artist]) ||
performSearch([query], [author]) ||

View File

@@ -158,7 +158,7 @@ export const SOURCES_BY_MANGA_TYPE: Record<MangaType, string[]> = {
* The actual matching data is in {@link MANGA_TAGS_BY_MANGA_TYPE} below.
*/
// @ts-ignore - see comment
// eslint-disable-next-line @typescript-eslint/no-unused-vars
// oxlint-disable-next-line no-unused-vars
const MANGA_TAG_DESCRIPTORS_BY_MANGA_TYPE: Record<MangaType, MessageDescriptor[]> = {
[MangaType.MANGA]: [msg`Manga`],
[MangaType.COMIC]: [msg`Comic`],

View File

@@ -169,7 +169,7 @@ const VerticalGrid = ({
/>
</Box>
{/* render div to prevent UI jumping around when showing/hiding loading placeholder */}
{/* eslint-disable no-nested-ternary */}
{/* oxlint-disable no-nested-ternary */}
{isSelectModeActive && gridLayout === GridLayout.List ? (
<Box sx={{ paddingBottom: DEFAULT_FULL_FAB_HEIGHT }} />
) : isLoading ? (
@@ -177,7 +177,7 @@ const VerticalGrid = ({
) : hasNextPage ? (
<div style={{ height: '75px' }} />
) : null}
{/* eslint-enable no-nested-ternary */}
{/* oxlint-enable no-nested-ternary */}
</>
);

View File

@@ -96,7 +96,7 @@ export const MangaCard = memo((props: MangaCardProps) => {
const longPressBind = useLongPress(
useCallback(
(e: any, { context }: any) => {
// eslint-disable-next-line no-param-reassign
// oxlint-disable-next-line no-param-reassign
e.shiftKey = true;
handleClick(e, context as () => {});
},

View File

@@ -549,7 +549,7 @@ export class Mangas {
// the migration actions (copy, cleanup) are supposed to be run sequentially to ensure that the cleanup
// only happens in case the copy succeeded
// eslint-disable-next-line no-await-in-loop
// oxlint-disable-next-line no-await-in-loop
await performMigrationAction(migrationAction, ...actions);
}
};

View File

@@ -6,7 +6,6 @@
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
// eslint-disable-next-line no-restricted-imports
import deepmerge from '@mui/utils/deepmerge';
import { AppMetadataKeys, IMetadataMigration } from '@/features/metadata/Metadata.types.ts';
import {

View File

@@ -120,7 +120,7 @@ export class ReaderTapZoneService {
context.beginPath();
/* eslint-disable no-param-reassign */
/* oxlint-disable no-param-reassign */
context.rect(x, y, width, height);
context.fillStyle = color;
context.strokeStyle = color;
@@ -141,7 +141,7 @@ export class ReaderTapZoneService {
context.lineWidth = 1;
context.fillStyle = 'white';
context.fillText(text, rectCenterX, rectCenterY);
/* eslint-enable no-param-reassign */
/* oxlint-enable no-param-reassign */
});
}

View File

@@ -162,7 +162,7 @@ const BaseReaderChapterViewer = ({
isCurrentChapterRef.current = isCurrentChapter;
if (isCurrentChapter) {
// eslint-disable-next-line no-param-reassign
// oxlint-disable-next-line no-param-reassign
globalImageRefs.current = imageRefs.current;
}

View File

@@ -86,7 +86,6 @@ const BaseReaderTransitionPage = ({
handleBack,
}: Pick<NavbarContextType, 'readerNavBarWidth'> & {
// gets used in the "source props creators" of the "withPropsFrom" call
// eslint-disable-next-line react/no-unused-prop-types
chapterId: ChapterIdInfo['id'];
currentChapterName?: ChapterType['name'];
currentChapterScanlator?: ChapterType['scanlator'];

View File

@@ -87,7 +87,7 @@ const BaseBasePager = ({
const setRef = useCallback(
(pagesIndex: number, element: HTMLElement | null) => {
// eslint-disable-next-line no-param-reassign
// oxlint-disable-next-line no-param-reassign
imageRefs.current[pagesIndex] = element;
},
[imageRefs],

View File

@@ -123,7 +123,7 @@ export const addStableIdToKeyValueItems = (
items: (SettingsDownloadConversionHeader | TSettingsDownloadConversionKeyValueItem)[],
): TSettingsDownloadConversionKeyValueItem[] =>
items.map((item) => ({
// eslint-disable-next-line no-plusplus
// oxlint-disable-next-line no-plusplus
id: (item as TSettingsDownloadConversionKeyValueItem).id ?? COUNTER++,
...item,
}));
@@ -132,7 +132,7 @@ export const addStableIdToConversions = (
conversions: (SettingsDownloadConversion | TSettingsDownloadConversion)[],
): TSettingsDownloadConversion[] =>
conversions.map((conversion) => ({
// eslint-disable-next-line no-plusplus
// oxlint-disable-next-line no-plusplus
id: (conversion as TSettingsDownloadConversion).id ?? COUNTER++,
...conversion,
mode: getTargetMode(normalizeMimeType(conversion.target)),

View File

@@ -51,7 +51,6 @@ export const getMetadataServerSettings = async (): Promise<MetadataServerSetting
const { data, error } = await requestManager.getGlobalMeta().response;
if (error) {
// eslint-disable-next-line @typescript-eslint/only-throw-error
throw error;
}

View File

@@ -50,7 +50,7 @@ export const TriStateFilter: React.FC<Props> = (props) => {
const [val, setval] = React.useState(convertTriStateToNumber(state));
const handleChange = (checked: boolean | null | undefined) => {
// eslint-disable-next-line no-nested-ternary
// oxlint-disable-next-line no-nested-ternary
const newState = checked === undefined ? 0 : checked ? 1 : 2;
setval(newState);
const upd = update.filter(

View File

@@ -16,7 +16,7 @@ import {
useTheme,
} from '@mui/material/styles';
import { useCallback } from 'react';
// eslint-disable-next-line no-restricted-imports
// oxlint-disable-next-line no-restricted-imports
import { deepmerge } from '@mui/utils';
import { complement, hsl, parseToHsl } from 'polished';
import { HslaColor, HslColor } from 'polished/lib/types/color';

View File

@@ -87,7 +87,7 @@ export class ThemeFontLoader {
result[font] = new Set([...(result[font] ?? []), ...weights].toSorted((a, b) => a - b));
});
// eslint-disable-next-line no-continue
// oxlint-disable-next-line no-continue
continue;
}

View File

@@ -13,6 +13,6 @@ export const defaultPromiseErrorHandler =
return;
}
// eslint-disable-next-line no-console
// oxlint-disable-next-line no-console
console.error(`${name} failed due to`, error);
};

View File

@@ -30,7 +30,7 @@ export const importDayJsLocale = async (locale: string): Promise<DayJsLocale> =>
const dayjsLocale = getDayJsLocale(locale);
try {
// eslint-disable-next-line @typescript-eslint/no-use-before-define
// oxlint-disable-next-line no-use-before-define
await localesToImport[dayjsLocale]();
} catch (e) {
// ignore - dayjs falls back to "en" by default

View File

@@ -103,7 +103,7 @@ export abstract class BaseClient<Client, ClientConfig, Fetcher> {
return SubpathUtil.getApiBaseUrl(serverBaseURL);
}
// eslint-disable-next-line @typescript-eslint/no-unused-vars
// oxlint-disable-next-line no-unused-vars
protected shouldQueueRequest(operationName?: string): boolean {
return AuthManager.shouldQueueRequests();
}

View File

@@ -6,7 +6,7 @@
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
/* eslint-disable max-classes-per-file */
/* oxlint-disable max-classes-per-file */
import { jsonSaveParse } from '@/lib/HelperFunctions.ts';
export class Storage {

View File

@@ -1,8 +1,6 @@
{
"extends": "../../tsconfig.json",
"include": [
"./**/*"
],
"include": ["./**/*"],
"compilerOptions": {
"moduleResolution": "node"
}

View File

@@ -1,18 +1,11 @@
{
"compilerOptions": {
"paths": {
"@/*": [
"./src/*"
]
"@/*": ["./src/*"]
},
"target": "ES2020",
"useDefineForClassFields": true,
"lib": [
"ESNEXT",
"ES2020",
"DOM",
"DOM.Iterable"
],
"lib": ["ESNEXT", "ES2020", "DOM", "DOM.Iterable"],
"module": "ESNext",
"skipLibCheck": true,
/* Bundler mode */
@@ -31,9 +24,7 @@
"forceConsistentCasingInFileNames": true,
"noFallthroughCasesInSwitch": true
},
"include": [
"src"
],
"include": ["src"],
"exclude": ["**/node_modules"],
"references": [
{

View File

@@ -6,9 +6,5 @@
"moduleResolution": "bundler",
"allowSyntheticDefaultImports": true
},
"include": [
"vite.config.ts",
"gql_codegen.ts",
"lingui.config.ts"
]
"include": ["vite.config.ts", "gql_codegen.ts", "lingui.config.ts"]
}

View File

@@ -6,8 +6,6 @@
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
/* eslint-disable import-x/no-extraneous-dependencies */
import path from 'path';
import { defineConfig } from 'vite';
import react from '@vitejs/plugin-react-swc';
@@ -18,7 +16,6 @@ import { lingui } from '@lingui/vite-plugin';
import 'dotenv/config';
import { d } from 'koration';
// eslint-disable-next-line import-x/no-default-export
export default defineConfig(({ command }) => ({
base: command === 'serve' ? process.env.VITE_SUBPATH || './' : './',
build: {

820
yarn.lock

File diff suppressed because it is too large Load Diff