Compare commits

..

213 Commits

Author SHA1 Message Date
Bogdan c77d820763 Fix tests for storing last search time for books 2024-02-17 23:47:11 +02:00
Servarr 3327ed0f49 Automated API Docs update 2024-02-17 23:47:03 +02:00
Mark McDowall 44009e980b Fixed: A potential issue when extra files for multiple authors have the same relative path
(cherry picked from commit a6a68b4cae7688506c45ff6cf10989fe6596c274)

Closes #1650
2024-02-17 23:09:31 +02:00
Mark McDowall 02fd733223 Fixed: Don't convert author/book selection filter to lower case in state
(cherry picked from commit ca52eb76ca2e286479f1803f399d5f5b563cfb41)

Closes #692
2024-02-17 23:06:18 +02:00
Bogdan 2fa9576d05 New: Missing/Cutoff Unmet searches will search for books that haven't been searched recently first
Closes #2088

Simplify filter expression for cutoff unmet album search
2024-02-17 23:04:22 +02:00
Mark McDowall c7ee278ee4 New: Store last search time for BookSearch
(cherry picked from commit 9af57c6786eedd9beda4e1c6b8cdca20d165b622)
2024-02-17 22:58:01 +02:00
Bogdan d72c27ceed Fixed: Refresh tags state to clear removed tags by housekeeping
(cherry picked from commit 2510f44c25bee6fede27d9fa2b9614176d12cb55)

(cherry picked from commit ed27bcf213bdbc5cede650f89eb65593dc9631b4)
2024-02-14 03:11:46 +02:00
Bogdan 7a20fe2288 Improve messaging on indexer specified download client is not available
(cherry picked from commit 84e657482d37eed35f09c6dab3c2b8b5ebd5bac4)
2024-02-14 03:11:36 +02:00
Bogdan 042b62a2a5 Show download client ID as hint in select options
(cherry picked from commit c0b17d9345367ab6500b7cca6bb70c1e3b930284)
2024-02-14 03:11:22 +02:00
abcasada 88141e9d63 Hints for week column and short dates in UI settings
(cherry picked from commit 4558f552820b52bb1f9cd97fdabe03654ce9924a)

(cherry picked from commit f1d343218cdbd5a63abeb2eb97bba1105dc8035d)
2024-02-14 03:11:11 +02:00
Weblate 7fa1114edf Multiple Translations updated by Weblate
ignore-downstream

Co-authored-by: Magyar <kochnorbert@icloud.com>
Co-authored-by: Weblate <noreply@weblate.org>
Translate-URL: https://translate.servarr.com/projects/servarr/readarr/
Translate-URL: https://translate.servarr.com/projects/servarr/readarr/hu/
Translation: Servarr/Readarr
2024-02-12 00:54:08 +02:00
Bogdan d4262532e2 Ignore tests temporarily 2024-02-12 00:52:33 +02:00
Bogdan a21f83aae1 Some translations for Manual Import dropdowns 2024-02-12 00:05:20 +02:00
Bogdan d659e86a7d Fixed: Progress bar for authors and books 2024-02-12 00:04:45 +02:00
Weblate 0b924005ec Multiple Translations updated by Weblate
ignore-downstream

Co-authored-by: Havok Dan <havokdan@yahoo.com.br>
Co-authored-by: Hicabi Erdem <bilgi@hicabierdem.com>
Co-authored-by: Magyar <kochnorbert@icloud.com>
Co-authored-by: Oskari Lavinto <olavinto@protonmail.com>
Co-authored-by: aghus <aghus.m@outlook.com>
Co-authored-by: bai0012 <baicongrui@gmail.com>
Co-authored-by: savin-msk <ns@a77.io>
Translate-URL: https://translate.servarr.com/projects/servarr/readarr/es/
Translate-URL: https://translate.servarr.com/projects/servarr/readarr/fi/
Translate-URL: https://translate.servarr.com/projects/servarr/readarr/hu/
Translate-URL: https://translate.servarr.com/projects/servarr/readarr/pt_BR/
Translate-URL: https://translate.servarr.com/projects/servarr/readarr/ru/
Translate-URL: https://translate.servarr.com/projects/servarr/readarr/tr/
Translate-URL: https://translate.servarr.com/projects/servarr/readarr/zh_CN/
Translation: Servarr/Readarr
2024-02-11 05:10:12 +02:00
Mark McDowall ba2fad5d9c Fixed: Don't use sub folder to check for free disk space for update
(cherry picked from commit f722d49b3a9efefa65bef1b24d90be9332ca62ea)

Closes #3299
2024-02-07 09:00:22 +02:00
Mark McDowall 58416cee67 New: Log database engine version on startup
(cherry picked from commit 6ab1d8e16b29e98b4d2ebb68e0356f6f2d3a2c10)
2024-02-07 08:58:46 +02:00
Mark McDowall 38124313c7 Fixed: Redirecting after login
(cherry picked from commit 745b92daf4bf4b9562ffe52dad84a12a5561add5)
2024-02-07 08:58:33 +02:00
Bogdan 3fc9f6c0a4 Bump version to 0.3.19 2024-02-04 12:55:21 +02:00
Weblate 79ce5abd53 Multiple Translations updated by Weblate
ignore-downstream

Co-authored-by: Havok Dan <havokdan@yahoo.com.br>
Co-authored-by: aghus <aghus.m@outlook.com>
Translate-URL: https://translate.servarr.com/projects/servarr/readarr/es/
Translate-URL: https://translate.servarr.com/projects/servarr/readarr/pt_BR/
Translation: Servarr/Readarr
2024-02-03 22:42:17 +02:00
Weblate 7f01d597cb Multiple Translations updated by Weblate
ignore-downstream

Co-authored-by: Crocmou <slaanesh8854@gmail.com>
Co-authored-by: Magyar <kochnorbert@icloud.com>
Co-authored-by: Oskari Lavinto <olavinto@protonmail.com>
Co-authored-by: Stas Panasiuk <temnyip@gmail.com>
Co-authored-by: fordas <fordas15@gmail.com>
Translate-URL: https://translate.servarr.com/projects/servarr/readarr/es/
Translate-URL: https://translate.servarr.com/projects/servarr/readarr/fi/
Translate-URL: https://translate.servarr.com/projects/servarr/readarr/fr/
Translate-URL: https://translate.servarr.com/projects/servarr/readarr/hu/
Translate-URL: https://translate.servarr.com/projects/servarr/readarr/uk/
Translation: Servarr/Readarr
2024-02-01 08:22:34 +02:00
Bogdan 31f35df71d Only bind shortcut for pending changes confirmation when it's shown
(cherry picked from commit ded7c3c6e2459f041297d479c788febc5d061854)
2024-02-01 08:21:58 +02:00
Mark McDowall faeb78801c Fixed: Monitored status being reset after refresh when author is edited manually
Resolves #54
2024-01-30 19:36:11 +02:00
Bogdan bd5695f2dd Bump version to 0.3.18 2024-01-28 09:10:32 +02:00
Weblate 5375cbe1c2 Multiple Translations updated by Weblate
ignore-downstream

Co-authored-by: Anonymous <noreply@weblate.org>
Co-authored-by: Crocmou <slaanesh8854@gmail.com>
Co-authored-by: Havok Dan <havokdan@yahoo.com.br>
Co-authored-by: Oskari Lavinto <olavinto@protonmail.com>
Co-authored-by: diaverso <alexito_perez.95@hotmail.com>
Co-authored-by: fordas <fordas15@gmail.com>
Translate-URL: https://translate.servarr.com/projects/servarr/readarr/ar/
Translate-URL: https://translate.servarr.com/projects/servarr/readarr/cs/
Translate-URL: https://translate.servarr.com/projects/servarr/readarr/da/
Translate-URL: https://translate.servarr.com/projects/servarr/readarr/es/
Translate-URL: https://translate.servarr.com/projects/servarr/readarr/fi/
Translate-URL: https://translate.servarr.com/projects/servarr/readarr/fr/
Translate-URL: https://translate.servarr.com/projects/servarr/readarr/pt_BR/
Translation: Servarr/Readarr
2024-01-27 17:59:45 +02:00
Mark McDowall d0b797ea61 Fixed: History retention for Newsbin
(cherry picked from commit 0ea189d03c8c5e02c00b96a4281dd9e668d6a9ae)
2024-01-27 10:26:00 +02:00
Servarr e76f160695 Automated API Docs update 2024-01-25 08:16:50 +02:00
Weblate ef71fc1b41 Multiple Translations updated by Weblate
ignore-downstream

Co-authored-by: Alexander <a.burdun@gmail.com>
Co-authored-by: Dani Talens <databio@gmail.com>
Co-authored-by: Havok Dan <havokdan@yahoo.com.br>
Co-authored-by: Magyar <kochnorbert@icloud.com>
Co-authored-by: Oskari Lavinto <olavinto@protonmail.com>
Co-authored-by: fordas <fordas15@gmail.com>
Co-authored-by: horvi28 <horvi28@gmail.com>
Co-authored-by: reloxx <reloxx@interia.pl>
Co-authored-by: wilfriedarma <wilfriedarma.collet@gmail.com>
Co-authored-by: zichichi <sollami@gmail.com>
Translate-URL: https://translate.servarr.com/projects/servarr/readarr/ca/
Translate-URL: https://translate.servarr.com/projects/servarr/readarr/de/
Translate-URL: https://translate.servarr.com/projects/servarr/readarr/es/
Translate-URL: https://translate.servarr.com/projects/servarr/readarr/fi/
Translate-URL: https://translate.servarr.com/projects/servarr/readarr/fr/
Translate-URL: https://translate.servarr.com/projects/servarr/readarr/hu/
Translate-URL: https://translate.servarr.com/projects/servarr/readarr/it/
Translate-URL: https://translate.servarr.com/projects/servarr/readarr/pt_BR/
Translate-URL: https://translate.servarr.com/projects/servarr/readarr/uk/
Translation: Servarr/Readarr
2024-01-25 08:13:18 +02:00
Mark McDowall 14f14e5da4 New: Optionally remove from queue by changing category to 'Post-Import Category' when configured
(cherry picked from commit 345854d0fe9b65a561fdab12aac688782a420aa5)

Closes #3260
2024-01-25 08:10:18 +02:00
Mark McDowall bd265e47fa Fixed: Don't try to remove the same item from queue multiple times
(cherry picked from commit 2491da067815e129df3a3a79c0cc7221a9d87094)

Closes #2087
2024-01-25 08:01:16 +02:00
Mark McDowall 333d344c0b New: Add FileId to History data for import events
(cherry picked from commit 952a7248c962908fc5da92762507421923a06e17)

Closes #788
2024-01-25 07:49:21 +02:00
Mark McDowall db6712f030 New: Add size to more history events
(cherry picked from commit 0d064181941fc6d149fc2f891661e059758d5428)

Closes #3250
2024-01-25 07:46:52 +02:00
Bogdan 1065a6283c Update database migration version translation token
(cherry picked from commit 7d0d503a5e132cda3c03d6f7cd7b51c9c80740de)

Closes #3257
2024-01-25 07:42:27 +02:00
Stevie Robinson 1b40c5c7ce Add Regular Expression Custom Format translation
(cherry picked from commit 9f50166fa62a71d0a23e2c2d331651792285dc0e)

Closes #3256
2024-01-25 07:38:34 +02:00
Mark McDowall a8de87300e New: Add download client name to pending items waiting for a specific client
(cherry picked from commit 3cd4c67ba12cd5e8cc00d3df8929555fc0ad5918)

Closes #3254
2024-01-25 07:32:46 +02:00
Qstick f260078ac8 Fixed: Allow restore to process backups up to ~1000MB 2024-01-25 07:19:51 +02:00
Mark McDowall 5a6486be21 Don't use TestCase for single test
(cherry picked from commit 541d3307e1466b0353dc4149f502a4b62b4de616)
2024-01-24 20:36:47 -06:00
Bogdan 2e9de3cb86 Fixed: Sorting by name in Manage Indexer and Download Client modals
(cherry picked from commit 31baed4b2c2406e48b8defa51352a13adb6d470f)
2024-01-23 07:35:49 +02:00
bakerboy448 a259684916 Improve Release Title Custom Format debugging
(cherry picked from commit ec40bc6eea1eb282cb804b8dd5461bf5ade332e9)

Closes #3235
2024-01-21 08:17:34 +02:00
Qstick 5704adfbc5 New: Improve All Authors call by using dictionary for stats iteration
(cherry picked from commit e792db4d3355fedd3ea9e35b3f5e1e30394d9ee3)

Closes #3230
2024-01-21 08:13:22 +02:00
Bogdan 6cfaab07ba Wrap values in log messages in FileListParser
Closes #3229
2024-01-21 08:08:21 +02:00
Stevie Robinson b36085a3cc New: Drop commands table content before postgres migration
Signed-off-by: Stevie Robinson <stevie.robinson@gmail.com>
(cherry picked from commit 8dd3b45c90209136c0bd0a861061c6d20837d62f)

Closes #3225
2024-01-21 08:06:54 +02:00
Bogdan 0afa0977b0 Bump version to 0.3.17 2024-01-21 08:01:27 +02:00
Bogdan 4a174e559f Transpile logical assignment operators with babel
(cherry picked from commit 3cf4d2907e32e81050f35cda042dcc2b4641d40d)
2024-01-21 03:55:02 +02:00
servarr[bot] 0fb8ab2280 New: Log warning if less than 1 GB free space during update
* New: Log warning if less than 1 GB free space during update

(cherry picked from commit e66ba84fc0b5b120dd4e87f6b8ae1b3c038ee72b)

---------

Co-authored-by: Mark McDowall <mark@mcdowall.ca>
2024-01-21 03:54:39 +02:00
Mark McDowall 261b0f398b Fixed: Don't clone indexer API Key
(cherry picked from commit d336aaf3f04136471970155b5a7cc876770c64ff)
2024-01-20 07:43:57 +02:00
Weblate d1fea384a7 Multiple Translations updated by Weblate
ignore-downstream

Co-authored-by: Julian Baquero <julian-baquero@upc.edu.co>
Co-authored-by: Koch Norbert <kochnorbert@icloud.com>
Co-authored-by: MaddionMax <kovacs.tamas@ius.hu>
Co-authored-by: brn <barantsenkul@gmail.com>
Translate-URL: https://translate.servarr.com/projects/servarr/readarr/es/
Translate-URL: https://translate.servarr.com/projects/servarr/readarr/hu/
Translate-URL: https://translate.servarr.com/projects/servarr/readarr/tr/
Translation: Servarr/Readarr
2024-01-20 02:06:38 +02:00
Stevie Robinson 9542ea0d2e Round off the seeded ratio when checking for removal candidates
Signed-off-by: Stevie Robinson <stevie.robinson@gmail.com>

(cherry picked from commit c6bb6ad8788fb1c20ed466a495f2b47034947145)
2024-01-19 08:15:19 +02:00
Weblate e1d697c561 Multiple Translations updated by Weblate
ignore-downstream

Co-authored-by: Dani Talens <databio@gmail.com>
Translate-URL: https://translate.servarr.com/projects/servarr/readarr/ca/
Translation: Servarr/Readarr
2024-01-18 00:03:31 +02:00
Bogdan 22ed847849 Replace support-requests with label-actions 2024-01-16 23:55:43 +02:00
Stevie Robinson 2faef704b4 Fixed: Replacing 'appName' translation token
(cherry picked from commit 2e51b8792db0d3ec402672dc92c95f3cb886ef44)

Closes #3058
Fixes #3221
2024-01-16 23:50:14 +02:00
Bogdan a566c3e21f Check Content-Type in FileList parser 2024-01-16 21:52:40 +02:00
Stevie Robinson cc0d2a84ae Sort Custom Filters
(cherry picked from commit e4b5d559df2d5f3d55e16aae5922509e84f31e64)
2024-01-16 08:08:39 +02:00
Qstick 1c3d2ce4e5 Improved http timeout handling
(cherry picked from commit f87a66fcba6ca9ca972fa1c747a940b216e0e5e3)
2024-01-16 08:08:26 +02:00
Weblate 57f614f4cd Multiple Translations updated by Weblate
ignore-downstream

Co-authored-by: Daniele Prevedello <dprevedello86@gmail.com>
Co-authored-by: DimitriDR <dimitridroeck@gmail.com>
Co-authored-by: Havok Dan <havokdan@yahoo.com.br>
Co-authored-by: Oskari Lavinto <olavinto@protonmail.com>
Co-authored-by: crayon3shawn <crayon3shawn@gmail.com>
Co-authored-by: hansaudun <hans@n5.no>
Translate-URL: https://translate.servarr.com/projects/servarr/readarr/fi/
Translate-URL: https://translate.servarr.com/projects/servarr/readarr/fr/
Translate-URL: https://translate.servarr.com/projects/servarr/readarr/it/
Translate-URL: https://translate.servarr.com/projects/servarr/readarr/nb_NO/
Translate-URL: https://translate.servarr.com/projects/servarr/readarr/pt_BR/
Translate-URL: https://translate.servarr.com/projects/servarr/readarr/zh_TW/
Translation: Servarr/Readarr
2024-01-15 20:01:07 +02:00
Bogdan 9d2efe0944 Fixed: Cutoff unmet showing Unmonitored books 2024-01-15 16:43:12 +02:00
Bogdan e032be48e0 Fixed: Wanted Missing showing Unmonitored books 2024-01-15 16:41:03 +02:00
Bogdan cd66de1992 Bump version to 0.3.16 2024-01-14 07:12:55 +02:00
Bogdan 3066dd92d7 Ignore tests temporarily 2024-01-13 03:36:01 +02:00
Servarr 467a87baec Automated API Docs update 2024-01-13 02:04:59 +02:00
Bogdan 80fb077c94 Fix log typo in release/push 2024-01-13 01:28:08 +02:00
Bogdan 07433d69ca New: Resolve download client by name using 'downloadClient' for pushed releases
Closes #3053
2024-01-13 01:27:36 +02:00
Mark McDowall 3b3ebe463c Fixed: Pushed releases not being properly rejected
(cherry picked from commit 07f816ffb18ac34090c2f8ba25147737299b361d)

Closes #2943
2024-01-13 01:26:15 +02:00
Mark McDowall 03392ca635 New: Optional 'downloadClientId' for pushed releases
(cherry picked from commit fa5bfc3742c24c5730b77bf8178a423d98fdf50e)

Closes #2934
2024-01-13 01:23:11 +02:00
Bogdan d23ce9ecc2 Allow to override download client
Towards #2331
2024-01-13 01:18:11 +02:00
Bogdan e968fcaff6 Fixed: Filter history by multiple event types in PG 2024-01-12 22:10:48 +02:00
Gavin Mogan 31da559f89 Fixed: Database type when PG host contains ".db" (#3186)
Previously was looking for a ".db" in the connection string, which is
the typical sqlite filename. The problem is if your connection string has a
.db anywhere in it, such as postgres.db.internal it'll think its a
sqlite file

Solution borrowed from sonarr:

https://github.com/Sonarr/Sonarr/blob/develop/src/NzbDrone.Core/Datastore/Database.cs#L43
2024-01-12 13:29:06 +02:00
Servarr a093290792 Automated API Docs update 2024-01-12 04:02:47 +02:00
Mark McDowall 9e3dfc510d Paging params in API docs
(cherry picked from commit bfaa7291e14a8d3847ef2154a52c363944560803)

Closes #2975
Closes #2991
2024-01-12 03:52:11 +02:00
Bogdan 9d27c172ac Fixed: Improve torrent blocklist matching
Closes #3184
2024-01-12 03:28:57 +02:00
Bogdan 518dbe53eb Fixed: Release source for release/push
Closes #3182
2024-01-12 03:27:39 +02:00
ilike2burnthing f9ba00c9e7 Remove unsupported pagination for Nyaa
(cherry picked from commit fef525ddb8b5f91bb36b3c9e652663fccb098a00)

Closes #3180
2024-01-12 03:25:39 +02:00
ilike2burnthing 4aec7a0ea7 Remove dead Torznab API key whitelist
(cherry picked from commit 3454f1c9ed11fbb9aa66e16524a529e924e5a77e)

Closes #3179
2024-01-12 03:23:37 +02:00
Weblate fc4cf8e81e Multiple Translations updated by Weblate
ignore-downstream

Co-authored-by: Watashi <drazy24@gmail.com>
Translate-URL: https://translate.servarr.com/projects/servarr/readarr/fr/
Translation: Servarr/Readarr
2024-01-12 03:21:34 +02:00
Weblate 143de3b220 Multiple Translations updated by Weblate
ignore-downstream

Co-authored-by: Aleksandr <alyarmak@gmail.com>
Co-authored-by: Anonymous <noreply@weblate.org>
Co-authored-by: Bradley BARBIER <bradley.barbier@outlook.fr>
Co-authored-by: Dani Talens <databio@gmail.com>
Co-authored-by: DimitriDR <dimitridroeck@gmail.com>
Co-authored-by: Havok Dan <havokdan@yahoo.com.br>
Co-authored-by: HuaBing <admin@hbcraft.cn>
Co-authored-by: JJonttuu <oikeaihminen@protonmail.com>
Co-authored-by: Juan Lores <juan.lores@gmail.com>
Co-authored-by: Oskari Lavinto <olavinto@protonmail.com>
Co-authored-by: Piotr Komborski <piotr+github@kombor.ski>
Co-authored-by: RicardoVelaC <ricardovelac@gmail.com>
Co-authored-by: Weblate <noreply@weblate.org>
Co-authored-by: boan51204 <je.991707@gmail.com>
Co-authored-by: reloxx <reloxx@interia.pl>
Translate-URL: https://translate.servarr.com/projects/servarr/readarr/
Translate-URL: https://translate.servarr.com/projects/servarr/readarr/ar/
Translate-URL: https://translate.servarr.com/projects/servarr/readarr/bg/
Translate-URL: https://translate.servarr.com/projects/servarr/readarr/ca/
Translate-URL: https://translate.servarr.com/projects/servarr/readarr/da/
Translate-URL: https://translate.servarr.com/projects/servarr/readarr/de/
Translate-URL: https://translate.servarr.com/projects/servarr/readarr/es/
Translate-URL: https://translate.servarr.com/projects/servarr/readarr/fi/
Translate-URL: https://translate.servarr.com/projects/servarr/readarr/fr/
Translate-URL: https://translate.servarr.com/projects/servarr/readarr/pl/
Translate-URL: https://translate.servarr.com/projects/servarr/readarr/pt_BR/
Translate-URL: https://translate.servarr.com/projects/servarr/readarr/uk/
Translate-URL: https://translate.servarr.com/projects/servarr/readarr/zh_CN/
Translate-URL: https://translate.servarr.com/projects/servarr/readarr/zh_TW/
Translation: Servarr/Readarr
2024-01-10 21:05:13 +02:00
Servarr e1a07d01b2 Automated API Docs update 2024-01-10 19:13:31 +02:00
Bogdan 27e498bb14 Fixed: Add ForeignEditionId to books endpoint 2024-01-10 19:07:29 +02:00
Bogdan b9f1882a57 Fixed: Refresh book files after renaming 2024-01-09 19:27:16 +02:00
ta264 2392573c39 New: Freeleech tokens support for Gazelle 2024-01-09 16:14:27 +02:00
Mark McDowall 2351efd013 Fixed: Blocklisting torrents from indexers that do not provide torrent hash
(cherry picked from commit 3541cd7ba877fb785c7f97123745abf51162eb8e)

Closes #3082
2024-01-09 16:07:26 +02:00
Weblate 526429bde4 Multiple Translations updated by Weblate
ignore-downstream

Co-authored-by: Dani Talens <databio@gmail.com>
Co-authored-by: DimitriDR <dimitridroeck@gmail.com>
Co-authored-by: JJonttuu <oikeaihminen@protonmail.com>
Co-authored-by: RicardoVelaC <ricardovelac@gmail.com>
Translate-URL: https://translate.servarr.com/projects/servarr/readarr/ca/
Translate-URL: https://translate.servarr.com/projects/servarr/readarr/es/
Translate-URL: https://translate.servarr.com/projects/servarr/readarr/fi/
Translate-URL: https://translate.servarr.com/projects/servarr/readarr/fr/
Translation: Servarr/Readarr
2024-01-09 15:48:13 +02:00
Servarr abd44b59bc Automated API Docs update 2024-01-09 15:46:24 +02:00
Bogdan 9942457ffc Rename to books 2024-01-09 03:32:05 +02:00
Mark McDowall 073342ef39 New: Download client option for redownloading failed releases from Interactive Search
(cherry picked from commit 87e0a7983a437a4d166aa8b9c9eaf78ea5431969)

Closes #2987
2024-01-09 03:14:48 +02:00
Bogdan b455708f2e Add release source for releases
Towards #2130
2024-01-09 03:10:52 +02:00
Bogdan 622b02c478 Use last history item in FailedDownloadService 2024-01-09 03:05:52 +02:00
Bogdan 8effba383d Bump version to 0.3.15 2024-01-07 11:11:32 +02:00
Bogdan 2749479283 Fix possible enumerations in TrackGroupingService 2024-01-06 19:41:17 +02:00
Bogdan 4cbafa76d8 New: Custom formats in book history
(cherry picked from commit cd2ce34f10007efacd8631d3ce3ac4f4a6212966)

Closes #2134
Closes #3163
2024-01-06 19:41:17 +02:00
Bogdan 73782cc233 Remove duplicated source title in history 2024-01-06 19:41:17 +02:00
Weblate de396fe9be Multiple Translations updated by Weblate
ignore-downstream

Co-authored-by: Havok Dan <havokdan@yahoo.com.br>
Co-authored-by: Mario Rodriguez <mario2423@gmail.com>
Co-authored-by: Norbi <kovinor123@gmail.com>
Translate-URL: https://translate.servarr.com/projects/servarr/readarr/es/
Translate-URL: https://translate.servarr.com/projects/servarr/readarr/hu/
Translate-URL: https://translate.servarr.com/projects/servarr/readarr/pt_BR/
Translation: Servarr/Readarr
2024-01-01 14:11:59 +02:00
Mark McDowall 71cb9e1dd7 Fixed: Disable SSL on start if certificate path is not set
(cherry picked from commit 4e19fec123900b8ba1252b640f26f2a4983683ff)
2024-01-01 06:58:14 +02:00
Mark McDowall ee5ed57fcc New: Add qBittorrent option for Content Layout
(cherry picked from commit 4b22200708ca120cfdcf9cb796be92183adb95d1)

Closes #3140
2023-12-31 11:12:25 +02:00
Stevie Robinson d20a049a5a Translate fields on the backend
(cherry picked from commit 48b12f5b00429a7cd218d23f0544641b0da62a06)
2023-12-31 11:10:45 +02:00
Stevie Robinson a9f77ace37 Fixed: Fallback to English translations if invalid UI language in config
(cherry picked from commit 4c7201741276eccaea2fb1f33daecc31e8b2d54e)

Closes #2882
2023-12-31 11:07:58 +02:00
Mark McDowall 0341a2ec26 Initial support to use named tokens for backend translations
Towards #3003

(cherry picked from commit 11f96c31048c2d1aafca0c91736d439f7f9a95a8)
2023-12-31 11:03:44 +02:00
Stevie Robinson d6796bbe1a New: Show Proper or Repack tag in interactive search
(cherry picked from commit efb000529b5dff42829df3ef151e4750a7b15cf6)

Closes #3141
2023-12-31 10:57:25 +02:00
Bogdan 9066f8558c New: Retry on failed downloads of torrent and nzb files
(cherry picked from commit bc20ef73bdd47b7cdad43d4c7d4b4bd534e49252)

Closes #3151
2023-12-31 10:52:37 +02:00
Weblate c4e37528ee Multiple Translations updated by Weblate
ignore-downstream

Co-authored-by: Dimitri <dimitridroeck@gmail.com>
Co-authored-by: Havok Dan <havokdan@yahoo.com.br>
Co-authored-by: Koch Norbert <kochnorbert@icloud.com>
Co-authored-by: Nicola <nicola.neri@gmail.com>
Co-authored-by: SunStorm <me@sunstorm.rocks>
Co-authored-by: Weblate <noreply@weblate.org>
Co-authored-by: chiral-lab <jan.eltner@googlemail.com>
Co-authored-by: chrizl <chrizl@gmail.com>
Co-authored-by: resi23 <x-resistant-x@gmx.de>
Co-authored-by: slammingdeath <sebastianbrudny97@gmail.com>
Co-authored-by: ube <ube@alienautopsy.net>
Translate-URL: https://translate.servarr.com/projects/servarr/readarr/
Translate-URL: https://translate.servarr.com/projects/servarr/readarr/de/
Translate-URL: https://translate.servarr.com/projects/servarr/readarr/fr/
Translate-URL: https://translate.servarr.com/projects/servarr/readarr/hu/
Translate-URL: https://translate.servarr.com/projects/servarr/readarr/it/
Translate-URL: https://translate.servarr.com/projects/servarr/readarr/nl/
Translate-URL: https://translate.servarr.com/projects/servarr/readarr/pl/
Translate-URL: https://translate.servarr.com/projects/servarr/readarr/pt_BR/
Translate-URL: https://translate.servarr.com/projects/servarr/readarr/sv/
Translation: Servarr/Readarr
2023-12-31 06:25:13 +02:00
Bogdan 5937c952af Fixed: Ignore empty tags when adding items to Flood
(cherry picked from commit 0a5200766ea80fc1c97bfa497cdfed31b9af687f)
2023-12-31 06:24:43 +02:00
Stevie Robinson 0f4bd3c472 New: Add sorting to Manage Indexer and Download Client modals
(cherry picked from commit 91053ca51ded804739f94ee936c1376a755dbe11)
2023-12-31 06:24:21 +02:00
Qstick cf415e61de New: Bulk Delete for Unmapped Files
(cherry picked from commit 71c1edd47c5377bcdeeb68e9cededf122a6ce6b4)
2023-12-27 03:17:41 +02:00
Bogdan 9865e92cea Add error message for invalid Root Folder in Ebook Tag Service 2023-12-25 01:53:21 +02:00
Bogdan 1cf956a9d9 Don't use empty file path from Calibre 2023-12-25 01:53:21 +02:00
Bogdan 8989c55c8c Bump version to 0.3.14 2023-12-24 09:12:40 +02:00
Bogdan dc83e0127e Fixed: Minor UI improvements to author and book details 2023-12-24 09:05:46 +02:00
Bogdan 34eb312426 Fixed: File Count on Books page 2023-12-24 07:22:10 +02:00
Bogdan 9d5cdebdb2 Fixed: Displaying Import List Exclusion actions on mobile 2023-12-24 05:31:34 +02:00
Bogdan a0ab224acd Log payload for set fields request in Calibre 2023-12-24 02:08:31 +02:00
Weblate 05aa35a54d Multiple Translations updated by Weblate
ignore-downstream

Co-authored-by: Aitzol Garmendia <aitzolgarmendia@gmail.com>
Co-authored-by: Anonymous <noreply@weblate.org>
Co-authored-by: Havok Dan <havokdan@yahoo.com.br>
Co-authored-by: Menno Liefstingh <mennoliefstingh@gmail.com>
Co-authored-by: Pietro Ribeiro <xxb1exuv6@mozmail.com>
Co-authored-by: ROSERAT Ugo <roserat.ugo@gmail.com>
Co-authored-by: RicardoVelaC <ricardovelac@gmail.com>
Co-authored-by: SHUAI.W <x@ousui.org>
Co-authored-by: VisoTC <szlytlyt@outlook.com>
Co-authored-by: Weblate <noreply@weblate.org>
Translate-URL: https://translate.servarr.com/projects/servarr/readarr/
Translate-URL: https://translate.servarr.com/projects/servarr/readarr/ar/
Translate-URL: https://translate.servarr.com/projects/servarr/readarr/bg/
Translate-URL: https://translate.servarr.com/projects/servarr/readarr/cs/
Translate-URL: https://translate.servarr.com/projects/servarr/readarr/da/
Translate-URL: https://translate.servarr.com/projects/servarr/readarr/es/
Translate-URL: https://translate.servarr.com/projects/servarr/readarr/fi/
Translate-URL: https://translate.servarr.com/projects/servarr/readarr/fr/
Translate-URL: https://translate.servarr.com/projects/servarr/readarr/he/
Translate-URL: https://translate.servarr.com/projects/servarr/readarr/hu/
Translate-URL: https://translate.servarr.com/projects/servarr/readarr/ja/
Translate-URL: https://translate.servarr.com/projects/servarr/readarr/ko/
Translate-URL: https://translate.servarr.com/projects/servarr/readarr/lv/
Translate-URL: https://translate.servarr.com/projects/servarr/readarr/nl/
Translate-URL: https://translate.servarr.com/projects/servarr/readarr/pl/
Translate-URL: https://translate.servarr.com/projects/servarr/readarr/pt/
Translate-URL: https://translate.servarr.com/projects/servarr/readarr/pt_BR/
Translate-URL: https://translate.servarr.com/projects/servarr/readarr/ro/
Translate-URL: https://translate.servarr.com/projects/servarr/readarr/th/
Translate-URL: https://translate.servarr.com/projects/servarr/readarr/tr/
Translate-URL: https://translate.servarr.com/projects/servarr/readarr/zh_CN/
Translation: Servarr/Readarr
2023-12-23 03:22:45 +02:00
Mark McDowall ca7f8775f5 Fixed: Increase width and truncate long names on Import List Exclusions
(cherry picked from commit 2d0541c03b761a0ec5e10711d6bd577e07141517)
2023-12-23 03:20:23 +02:00
Mark McDowall 2a01e9b445 Fixed: Don't grab propers/repacks when item in queue meets cutoff and propers/repacks are not downloaded automatically
(cherry picked from commit cf00fecbe410caf1a57d561e458f2e58921eef05)

Closes #2210
2023-12-23 03:11:19 +02:00
Mark McDowall 7d30c7d1ea Fixed: Parsing similar author names with common words at end
(cherry picked from commit 0fe24539625f8397dfb63d4618611db99c3c137a)
2023-12-23 03:01:34 +02:00
Mark McDowall 50be87e5a4 Fixed: Error checking if files should be deleted after import won't leave import in limbo
(cherry picked from commit 88ad6f9544110a2e825ebe6b2cde17e9f05475cc)

Closes #824
2023-12-23 03:00:03 +02:00
Bakerboy448 0572d1ac80 QualityParser - Simplify new expression (IDE0090)
(cherry picked from commit 9ae647d9d23bcd53ef34ba6eeffd0cf17248404d)

Closes #2320
2023-12-23 02:56:30 +02:00
Stevie Robinson d2240514d7 Fixed: Reduce font size for title on author and book details
(cherry picked from commit 03f5174a4b2a005aab8d1a1540f4bcb272682f2e)

Closes #3009
2023-12-22 03:32:28 +02:00
Weblate ad47dc032d Multiple Translations updated by Weblate
ignore-downstream

Co-authored-by: Aitzol Garmendia <aitzolgarmendia@gmail.com>
Co-authored-by: Anonymous <noreply@weblate.org>
Co-authored-by: Havok Dan <havokdan@yahoo.com.br>
Co-authored-by: Menno Liefstingh <mennoliefstingh@gmail.com>
Co-authored-by: ROSERAT Ugo <roserat.ugo@gmail.com>
Co-authored-by: RicardoVelaC <ricardovelac@gmail.com>
Co-authored-by: SHUAI.W <x@ousui.org>
Co-authored-by: VisoTC <szlytlyt@outlook.com>
Co-authored-by: Weblate <noreply@weblate.org>
Translate-URL: https://translate.servarr.com/projects/servarr/readarr/
Translate-URL: https://translate.servarr.com/projects/servarr/readarr/ar/
Translate-URL: https://translate.servarr.com/projects/servarr/readarr/bg/
Translate-URL: https://translate.servarr.com/projects/servarr/readarr/cs/
Translate-URL: https://translate.servarr.com/projects/servarr/readarr/da/
Translate-URL: https://translate.servarr.com/projects/servarr/readarr/es/
Translate-URL: https://translate.servarr.com/projects/servarr/readarr/fi/
Translate-URL: https://translate.servarr.com/projects/servarr/readarr/fr/
Translate-URL: https://translate.servarr.com/projects/servarr/readarr/he/
Translate-URL: https://translate.servarr.com/projects/servarr/readarr/hu/
Translate-URL: https://translate.servarr.com/projects/servarr/readarr/ja/
Translate-URL: https://translate.servarr.com/projects/servarr/readarr/ko/
Translate-URL: https://translate.servarr.com/projects/servarr/readarr/lv/
Translate-URL: https://translate.servarr.com/projects/servarr/readarr/nl/
Translate-URL: https://translate.servarr.com/projects/servarr/readarr/pl/
Translate-URL: https://translate.servarr.com/projects/servarr/readarr/pt_BR/
Translate-URL: https://translate.servarr.com/projects/servarr/readarr/ro/
Translate-URL: https://translate.servarr.com/projects/servarr/readarr/th/
Translate-URL: https://translate.servarr.com/projects/servarr/readarr/tr/
Translate-URL: https://translate.servarr.com/projects/servarr/readarr/zh_CN/
Translation: Servarr/Readarr
2023-12-22 03:19:00 +02:00
Bogdan 6c6df7d7d9 Fixed: Abort old fetch items requests in manual import 2023-12-22 02:40:41 +02:00
Bogdan 2121204064 New: Remember sorting for manual import 2023-12-22 02:39:31 +02:00
Mark McDowall 61004ea33f New: Natural Sorting Manual Import Paths
(cherry picked from commit bdd5865876796bc203c8117418a5389afc8b5f11)

Closes #1641
2023-12-22 02:38:14 +02:00
ta264 54c1c7862e New: Sort interactive import by path by default
(cherry picked from commit 4e41c3b237de596496523fcd3671d8d3c4192c27)
2023-12-22 02:37:10 +02:00
Mark McDowall 43dfdc8bf5 Fix tests for ImportListSyncService
(cherry picked from commit b271b3b694081a2889d75d39dc0296d53c734aaf)

Closes #2843
2023-12-19 00:27:21 +02:00
Mark McDowall 0d1ae0ca4e New: Less logging when no import lists are enabled
(cherry picked from commit 7be4840f028f24e3920bd395a4e15eb5e643e46f)

Closes #2836
2023-12-18 23:53:34 +02:00
Taloth Saldono 9902889a30 Fixed metadata images containing html content
(cherry picked from commit 87a64cdacbb0945c89b878d02a7eb2ac28427026)
2023-12-18 22:16:41 +02:00
Bogdan 04d7061030 Bump version to 0.3.13 2023-12-17 15:55:59 +02:00
Bogdan fd201912a9 Fix help text for Import Extra Files
Co-authored-by: zakary <zak@ary.dev>
2023-12-16 16:39:40 +02:00
Mark McDowall c412701a3d Fixed: Imported books updating on Calendar
(cherry picked from commit 5a3bc49392b700650a34536ff3794bce614f64a4)

Closes #3126
2023-12-15 19:55:00 +02:00
Agneev Mukherjee 7451a66365 Enable browser navigation buttons for PWA
(cherry picked from commit da9a60691f363323565a293ed9eaeb6349ceccb6)

Closes #3122
2023-12-15 19:52:06 +02:00
Bogdan a6431fdb0b OZnzb removed
Closes #3123
2023-12-15 19:51:56 +02:00
Qstick 060b133f6d Fixed: Correctly handle Migration when PG Host has ".db"
(cherry picked from commit 97ee24507f4306e3b62c3d00cd3ade6a09d1b957)

Closes #3116
2023-12-12 15:47:30 +02:00
Bogdan 5ed13b942b Implement DatabaseConnectionInfo
Co-authored-by: Qstick <qstick@gmail.com>
2023-12-12 15:46:13 +02:00
Bogdan 89f3d8167b Prevent NullRef on header assert 2023-12-10 16:31:30 +02:00
Bogdan 77b027374f Increase the wait timeout for integration tests init 2023-12-10 16:30:33 +02:00
Bogdan 650490abb2 Bump dotnet to 6.0.25 2023-12-10 15:43:22 +02:00
Bogdan 7d2e215d61 Bump version to 0.3.12 2023-12-10 13:45:43 +02:00
Bogdan 65ff890c74 Ignore tests temporarily 2023-12-09 14:12:18 +02:00
Mark McDowall 50c0b0dbaa Always validate Custom Script path
(cherry picked from commit c922cc5dc617dd776d4523cbf62376821c5a4ad9)
2023-12-09 13:12:49 +02:00
Weblate d5f36d0144 Multiple Translations updated by Weblate
ignore-downstream

Co-authored-by: Anonymous <noreply@weblate.org>
Co-authored-by: Hajiroxx <luypanda@163.com>
Co-authored-by: Jurriaan Den Toonder <jur.den.toonder@gmail.com>
Co-authored-by: RicardoVelaC <ricardovelac@gmail.com>
Co-authored-by: Weblate <noreply@weblate.org>
Translate-URL: https://translate.servarr.com/projects/servarr/readarr/
Translate-URL: https://translate.servarr.com/projects/servarr/readarr/cs/
Translate-URL: https://translate.servarr.com/projects/servarr/readarr/es/
Translate-URL: https://translate.servarr.com/projects/servarr/readarr/nl/
Translate-URL: https://translate.servarr.com/projects/servarr/readarr/zh_CN/
Translation: Servarr/Readarr
2023-12-09 13:11:48 +02:00
Bogdan fab7558bd4 Fixed: Don't write audio tags if there are no updates
(cherry picked from commit 1e147580729e24fbb6d8707d2a0ddfc8bd036d43)
2023-12-07 15:23:49 +02:00
Weblate 3dc86b3a01 Multiple Translations updated by Weblate
ignore-downstream

Co-authored-by: Augusto Poletti <augustpolet@gmail.com>
Co-authored-by: Dominika Matějková <dominika.matejkova@outlook.cz>
Co-authored-by: VisoTC <szlytlyt@outlook.com>
Co-authored-by: Weblate <noreply@weblate.org>
Translate-URL: https://translate.servarr.com/projects/servarr/readarr/
Translate-URL: https://translate.servarr.com/projects/servarr/readarr/cs/
Translate-URL: https://translate.servarr.com/projects/servarr/readarr/es/
Translate-URL: https://translate.servarr.com/projects/servarr/readarr/zh_CN/
Translation: Servarr/Readarr
2023-12-06 16:01:34 +02:00
Taloth Saldono 24ad6134e3 Small helper in UI to access Readarr API more easily
(cherry picked from commit 090cdc364ef335fbfea8cf540696af813f6ecea4)

Closes #677
2023-12-06 12:15:07 +02:00
dependabot[bot] 033f8c40af Bump @adobe/css-tools from 4.3.1 to 4.3.2
Bumps [@adobe/css-tools](https://github.com/adobe/css-tools) from 4.3.1 to 4.3.2.
- [Changelog](https://github.com/adobe/css-tools/blob/main/History.md)
- [Commits](https://github.com/adobe/css-tools/commits)

---
updated-dependencies:
- dependency-name: "@adobe/css-tools"
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-12-03 13:51:48 +02:00
Weblate 4c73a619eb Multiple Translations updated by Weblate
ignore-downstream

Co-authored-by: Appoxo <appoxo@appoxo.de>
Co-authored-by: David Molero <contact@dolvem.com>
Co-authored-by: Dimitri <dimitridroeck@gmail.com>
Co-authored-by: Patatra <patrice.chevreau@gmail.com>
Co-authored-by: Weblate <noreply@weblate.org>
Co-authored-by: Zalhera <tobias.bechen@gmail.com>
Co-authored-by: liimee <git.taaa@fedora.email>
Co-authored-by: resi23 <x-resistant-x@gmx.de>
Translate-URL: https://translate.servarr.com/projects/servarr/readarr/de/
Translate-URL: https://translate.servarr.com/projects/servarr/readarr/es/
Translate-URL: https://translate.servarr.com/projects/servarr/readarr/fr/
Translate-URL: https://translate.servarr.com/projects/servarr/readarr/id/
Translation: Servarr/Readarr
2023-12-01 04:02:04 +02:00
bakerboy448 3ca798e983 Fixed: RootFolderWatchingService Logging 2023-12-01 04:01:32 +02:00
bmarinov d9827fd6a6 Fixed: Filter unchanged files using UTC timestamps
(cherry picked from commit 9fc66e9b985a1eabd05f324ac631dfac39d2aebc)
2023-11-26 09:35:42 +02:00
Stevie Robinson f4f03a853f New: Remove defunct Boxcar notifications
(cherry picked from commit c6ad2396bb98dc8eb1ad47bf5d066b227a47f8b5)

Closes #3103
2023-11-25 22:30:43 +02:00
servarr[bot] 4f4e4bf2ca Fixed: Disable SSL when using the removed SslCertHash configuration (#3088)
(cherry picked from commit d95660d3c78d1ee11a7966d58e78a82a8df01393)

Co-authored-by: Mark McDowall <mark@mcdowall.ca>
2023-11-20 08:13:17 +02:00
Bogdan 413a70a312 Wrap long lines in description lists
(cherry picked from commit 4e048bf4999230f9c75d98ef2d8a1201d5ed68ed)
2023-11-20 07:38:05 +02:00
Weblate a8f2b91010 Multiple Translations updated by Weblate
ignore-downstream

Co-authored-by: Havok Dan <havokdan@yahoo.com.br>
Translate-URL: https://translate.servarr.com/projects/servarr/readarr/pt_BR/
Translation: Servarr/Readarr
2023-11-17 03:31:51 +02:00
Mark McDowall 68a4ee6000 Rename 'ReturnUrl' to 'returnUrl' for forms auth redirection
(cherry picked from commit 812712e2843a738054c065a6d5c1b7c81c5f8e7b)
2023-11-17 03:31:00 +02:00
Bogdan 5196ce311b Fixed: Enforce validation warnings when testing providers
(cherry picked from commit c3b4126d0c4f449a41e2cf7ea438b20e25370995)
2023-11-17 02:39:58 +02:00
Bogdan ae92b22727 Fixed: Record status for notifications on tests
(cherry picked from commit 3d05913534e40e1b9ff217798d806d0b7c170d2d)
2023-11-10 19:12:48 +02:00
Weblate 0bccffef01 Multiple Translations updated by Weblate
ignore-downstream

Co-authored-by: Francisco Cachado <franciscomcachado@gmail.com>
Co-authored-by: Javier Parada <jparada@gmail.com>
Co-authored-by: Nesego <nesego@gmail.com>
Co-authored-by: RicardoVelaC <ricardovelac@gmail.com>
Co-authored-by: Weblate <noreply@weblate.org>
Translate-URL: https://translate.servarr.com/projects/servarr/readarr/es/
Translate-URL: https://translate.servarr.com/projects/servarr/readarr/fr/
Translate-URL: https://translate.servarr.com/projects/servarr/readarr/pt/
Translation: Servarr/Readarr
2023-11-10 03:17:40 +02:00
Mark McDowall bca899b9c0 Don't store successful results for invalid providers
(cherry picked from commit de23182d593e2284972103d505e66dd8d812dfdb)
2023-11-10 03:16:30 +02:00
Bogdan 2bb576a94b Bump version to 0.3.11 2023-11-05 11:15:33 +02:00
Mark McDowall bb49949853 Fixed: Blocking unknown indexers from pushed releases
(cherry picked from commit 44d8dbaac81706691124ae5f8317289f0a3e5d73)
2023-10-31 00:04:49 +02:00
Bogdan a093061b29 Use variable for App name in translations
Towards #2925
2023-10-30 23:07:03 +02:00
Weblate df876707c4 Multiple Translations updated by Weblate
ignore-downstream

Co-authored-by: Baptiste Mongin <baptiste.mongin@gmail.com>
Co-authored-by: RicardoVelaC <ricardovelac@gmail.com>
Co-authored-by: Ruben Lourenco <ruben.lourenco01@gmail.com>
Co-authored-by: Weblate <noreply@weblate.org>
Translate-URL: https://translate.servarr.com/projects/servarr/readarr/
Translate-URL: https://translate.servarr.com/projects/servarr/readarr/es/
Translate-URL: https://translate.servarr.com/projects/servarr/readarr/fr/
Translate-URL: https://translate.servarr.com/projects/servarr/readarr/pt/
Translation: Servarr/Readarr
2023-10-30 23:03:37 +02:00
Bogdan 2af33143ba New: Add Download Client validation for indexers
(cherry picked from commit e53b7f8c945e3597ca1719961e82540f1f01f0e9)

Closes #3033
2023-10-29 01:24:56 +03:00
Bogdan c3c5a47776 New: Set busy timeout for SQLite
(cherry picked from commit 192eb7b62ae60f300a9371ce3ed2e0056b5a1f4d)

Closes #3039
2023-10-29 01:24:14 +03:00
Weblate a21abe0838 Multiple Translations updated by Weblate
ignore-downstream

Co-authored-by: Anonymous <noreply@weblate.org>
Co-authored-by: Havok Dan <havokdan@yahoo.com.br>
Co-authored-by: ID-86 <id86dev@gmail.com>
Co-authored-by: Jordy <prive@jordyhoebergen.nl>
Co-authored-by: Lizandra Candido da Silva <lizandra.c.s@gmail.com>
Co-authored-by: Weblate <noreply@weblate.org>
Co-authored-by: bai0012 <baicongrui@gmail.com>
Translate-URL: https://translate.servarr.com/projects/servarr/readarr/cs/
Translate-URL: https://translate.servarr.com/projects/servarr/readarr/el/
Translate-URL: https://translate.servarr.com/projects/servarr/readarr/fr/
Translate-URL: https://translate.servarr.com/projects/servarr/readarr/id/
Translate-URL: https://translate.servarr.com/projects/servarr/readarr/nl/
Translate-URL: https://translate.servarr.com/projects/servarr/readarr/pt/
Translate-URL: https://translate.servarr.com/projects/servarr/readarr/pt_BR/
Translate-URL: https://translate.servarr.com/projects/servarr/readarr/ro/
Translate-URL: https://translate.servarr.com/projects/servarr/readarr/zh_CN/
Translation: Servarr/Readarr
2023-10-29 01:22:50 +03:00
Bogdan a32f5f6639 Allow 0 as valid value in QualityProfileExistsValidator
(cherry picked from commit 36ca24e55a5eda859047d82855f65c401cc0b30f)
2023-10-29 01:22:30 +03:00
Bogdan 4cd45ecc21 Sort Custom Formats by name
(cherry picked from commit e9bb1d52a72b20a58d1a672ecfa3797eda6f081a)
2023-10-29 01:22:19 +03:00
Bogdan 2c8e0b1ca4 Add default value for Queue count to avoid failed prop type
(cherry picked from commit 43ed7730f08de7baddbdafcccd99370258593221)
2023-10-29 01:22:07 +03:00
Weblate bd25c9e3e0 Multiple Translations updated by Weblate
ignore-downstream

Co-authored-by: Fixer <ygj59783@zslsz.com>
Co-authored-by: Lizandra Candido da Silva <lizandra.c.s@gmail.com>
Translate-URL: https://translate.servarr.com/projects/servarr/readarr/pt_BR/
Translate-URL: https://translate.servarr.com/projects/servarr/readarr/ro/
Translation: Servarr/Readarr
2023-10-22 10:04:08 +03:00
Bogdan ee64b8788b Bump version to 0.3.10 2023-10-22 09:35:38 +03:00
Weblate 7aeada2089 Multiple Translations updated by Weblate
ignore-downstream

Co-authored-by: Dlgeri123 <bornemiszageri@gmail.com>
Co-authored-by: Lizandra Candido da Silva <lizandra.c.s@gmail.com>
Co-authored-by: Weblate <noreply@weblate.org>
Co-authored-by: jianl <jianjianfengyun@126.com>
Co-authored-by: 宿命 <331874545@qq.com>
Translate-URL: https://translate.servarr.com/projects/servarr/readarr/hu/
Translate-URL: https://translate.servarr.com/projects/servarr/readarr/pt_BR/
Translate-URL: https://translate.servarr.com/projects/servarr/readarr/zh_CN/
Translation: Servarr/Readarr
2023-10-22 09:34:13 +03:00
Bogdan e188c9aac0 Don't die when trying to open file with nullable path
Closes #3012
2023-10-19 17:35:23 +03:00
Bogdan a3ae2359f5 Fixed: Ignore case when cleansing announce URLs
(cherry picked from commit 41ed300899e8d7de82b1113d13ac6f6cf28cec17)
2023-10-19 17:06:40 +03:00
Bogdan 5b92905dd4 Bump version to 0.3.9 2023-10-15 07:51:31 +03:00
Weblate fc402743aa Multiple Translations updated by Weblate
ignore-downstream

Co-authored-by: DavidHenryThoreau <sorau@protonmail.com>
Translate-URL: https://translate.servarr.com/projects/servarr/readarr/fr/
Translation: Servarr/Readarr
2023-10-13 12:37:22 +03:00
Bogdan b9d53ed732 Add PostgreSQL specific query for cleaning multiple monitored editions
Fixes #2995
2023-10-12 02:20:28 +03:00
Bogdan d248747635 Fixed: Avoid logging evaluations when not using any Remote Path Mappings
(cherry picked from commit 44eb729ccc13237f4439006159bd616e8bdb5750)
2023-10-10 07:12:09 +03:00
Bogdan d70224c811 Add status test all button for IndexerLongTermStatusCheck
(cherry picked from commit 4ffa1816bd2305550abee20cea27e1296a99ddf6)
2023-10-10 07:11:58 +03:00
Bogdan acdf8c8aa8 Bump version to 0.3.8 2023-10-08 07:09:11 +03:00
Bogdan 3ed41554ce Log Notifiarr errors as warnings 2023-10-07 22:58:40 +03:00
Bogdan ce808c6d7b Prevent mapping null metadata responses
Fixes #2971
2023-10-07 01:40:49 +03:00
Weblate 63b1b56a4f Multiple Translations updated by Weblate
ignore-downstream

Co-authored-by: Anonymous <noreply@weblate.org>
Co-authored-by: Dimitri <dimitridroeck@gmail.com>
Co-authored-by: Garkus98 <ivan12061998@gmail.com>
Co-authored-by: Havok Dan <havokdan@yahoo.com.br>
Co-authored-by: Oskari Lavinto <olavinto@protonmail.com>
Co-authored-by: RicardoVelaC <ricardovelac@gmail.com>
Co-authored-by: Weblate <noreply@weblate.org>
Co-authored-by: blankhang <blankhang@gmail.com>
Co-authored-by: 宿命 <331874545@qq.com>
Translate-URL: https://translate.servarr.com/projects/servarr/readarr/bg/
Translate-URL: https://translate.servarr.com/projects/servarr/readarr/cs/
Translate-URL: https://translate.servarr.com/projects/servarr/readarr/es/
Translate-URL: https://translate.servarr.com/projects/servarr/readarr/fi/
Translate-URL: https://translate.servarr.com/projects/servarr/readarr/fr/
Translate-URL: https://translate.servarr.com/projects/servarr/readarr/he/
Translate-URL: https://translate.servarr.com/projects/servarr/readarr/pt_BR/
Translate-URL: https://translate.servarr.com/projects/servarr/readarr/ru/
Translate-URL: https://translate.servarr.com/projects/servarr/readarr/tr/
Translate-URL: https://translate.servarr.com/projects/servarr/readarr/zh_CN/
Translation: Servarr/Readarr
2023-10-07 01:04:37 +03:00
Bogdan a5647bedc8 Remove reddit from support issues 2023-10-07 00:25:19 +03:00
Servarr fe659bb79d Automated API Docs update 2023-10-04 06:59:02 +03:00
MxMarx 9918535509 New: Author Added notification
(cherry picked from commit https://github.com/Radarr/Radarr/commit/f890aadffa5ae579bcf65abdcf3e3948837084a9)
2023-10-04 06:47:23 +03:00
William Brockhus f9a6db40b8 Fixed: Ignore timezone when comparing tag dates 2023-10-04 06:26:08 +03:00
Bogdan 6273d69ed6 Fix tests 2023-10-04 05:50:57 +03:00
dependabot[bot] 7012380e95 Bump postcss from 8.4.23 to 8.4.31
Bumps [postcss](https://github.com/postcss/postcss) from 8.4.23 to 8.4.31.
- [Release notes](https://github.com/postcss/postcss/releases)
- [Changelog](https://github.com/postcss/postcss/blob/main/CHANGELOG.md)
- [Commits](https://github.com/postcss/postcss/compare/8.4.23...8.4.31)

---
updated-dependencies:
- dependency-name: postcss
  dependency-type: direct:development
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-10-04 04:05:39 +03:00
Bogdan b001ecd698 Preserve the protocol for fanart images
Closes #2944
2023-10-01 17:29:46 +03:00
Bogdan e28becdda4 Preserve the protocol in Author Image
Closes #2942
2023-10-01 17:27:38 +03:00
Mark McDowall eae06695e8 Fixed: Completed downloads in Qbit missing import path
(cherry picked from commit 35365665cfd436ac276dd9591e23333bd26cf789)

Closes #2959
2023-10-01 17:23:21 +03:00
Weblate 54a9af2ced Multiple Translations updated by Weblate
ignore-downstream

Co-authored-by: Havok Dan <havokdan@yahoo.com.br>
Co-authored-by: Stevie Robinson <stevie.robinson@gmail.com>
Co-authored-by: Weblate <noreply@weblate.org>
Co-authored-by: mr cmuc <github@nextcos.de>
Co-authored-by: 宿命 <331874545@qq.com>
Translate-URL: https://translate.servarr.com/projects/servarr/readarr/de/
Translate-URL: https://translate.servarr.com/projects/servarr/readarr/nl/
Translate-URL: https://translate.servarr.com/projects/servarr/readarr/pt_BR/
Translate-URL: https://translate.servarr.com/projects/servarr/readarr/zh_CN/
Translation: Servarr/Readarr
2023-10-01 17:20:42 +03:00
Stevie Robinson c9b55266fc Fixed: qBittorent history retention to allow at least 14 days seeding
(cherry picked from commit 33b87acabf2b4c71ee24cda1a466dec6f4f76996)
2023-10-01 17:20:16 +03:00
bakerboy448 05b64406a4 Fixed: Only apply remote path mappings for completed items in Qbit
(cherry picked from commit 583eb52ddc01b608ab6cb17e863a8830c17b7b75)
2023-10-01 17:20:06 +03:00
Bogdan 1f37c5387b Revert "Avoid returning null in static resource mapper Task"
This reverts commit d7305b9753.
2023-10-01 03:26:00 +03:00
Stevie Robinson 4a6c7042fe Fixed: SABnzbd history retention to allow at least 14 days
(cherry picked from commit a3938d8e0264b48b35f4715cbc15329fb489218a)
2023-09-27 19:47:13 +03:00
Bogdan d7305b9753 Avoid returning null in static resource mapper Task
(cherry picked from commit a1ea7accb32bc72f61ed4531d109f76fad843939)
2023-09-27 18:54:41 +03:00
Bogdan bd56643eaa Bump version to 0.3.7 2023-09-24 16:24:12 +03:00
Stevie Robinson 44e6de2e23 Add health check for dl clients removing completed downloads + enable for sab and qbit
(cherry picked from commit 7f2cd8a0e99b537a1c616998514bacdd8468a016)

Closes #2939
2023-09-19 21:44:45 +03:00
Mark McDowall b209d047fa Fixed: Don't try to create metadata images if source files doesn't exist
(cherry picked from commit 9a1022386a031c928fc0495d6ab990ebce605ec1)

Closes #2933
2023-09-19 21:37:09 +03:00
Mark McDowall fd5ab27df6 New: Don't treat 400 responses from Notifiarr as errors
(cherry picked from commit 5eb420bbe12f59d0a5392abf3d351be28ca210e6)

Closes #2938
2023-09-19 21:35:26 +03:00
Bogdan 4a89befd79 Log request failures in Notifiarr 2023-09-19 21:34:12 +03:00
Bogdan 1a30293c33 Check for empty description as well in ParseQuality 2023-09-19 21:33:26 +03:00
Bogdan f5c2a6bf51 Fix use of empty Author SortName in filename 2023-09-19 18:55:24 +03:00
Weblate f3d90fdaf1 Multiple Translations updated by Weblate
ignore-downstream

Co-authored-by: Akashi2020 <dieux02400@gmail.com>
Co-authored-by: Anthony Veaudry <anthonyveaudry@gmail.com>
Co-authored-by: Gyuyeop Kim <rlarbduq777@gmail.com>
Co-authored-by: Havok Dan <havokdan@yahoo.com.br>
Co-authored-by: Herve Lauwerier <hervelauwerier@gmail.com>
Co-authored-by: Richard de Souza Leite <rs9010482@gmail.com>
Co-authored-by: Weblate <noreply@weblate.org>
Co-authored-by: mati300m <mateusz.smolec@gmail.com>
Co-authored-by: 宿命 <331874545@qq.com>
Translate-URL: https://translate.servarr.com/projects/servarr/readarr/el/
Translate-URL: https://translate.servarr.com/projects/servarr/readarr/fr/
Translate-URL: https://translate.servarr.com/projects/servarr/readarr/ko/
Translate-URL: https://translate.servarr.com/projects/servarr/readarr/pl/
Translate-URL: https://translate.servarr.com/projects/servarr/readarr/pt/
Translate-URL: https://translate.servarr.com/projects/servarr/readarr/pt_BR/
Translate-URL: https://translate.servarr.com/projects/servarr/readarr/zh_CN/
Translation: Servarr/Readarr
2023-09-19 14:32:26 +03:00
MxMarx 04c5671a0a Fixed: Release Push api broken when no indexer id is specified 2023-09-19 14:31:44 +03:00
Qstick 22cc88c5e7 Fixed: Show correct error on unauthorized caps call
(cherry picked from commit f2b0fc946e1fb1b4649f1b46a003bd2add09a461)
2023-09-19 14:28:41 +03:00
Bogdan ca0c95a2d2 Fixed: Skip parsing releases without title
(cherry picked from commit c7824bb593291634bf14a5f7aa689666969b03bf)
2023-09-19 14:28:14 +03:00
Mark McDowall 419f790d66 Fixed: Don't allow quality profile to be created without all qualities
(cherry picked from commit 32e1ae2f64827272d351991838200884876e52b4)
2023-09-19 14:28:01 +03:00
Bogdan 9fe08429bc Use await on reading the response content
(cherry picked from commit 82d586e7015d7ea06356ca436024a8af5a4fb677)
2023-09-18 03:24:30 +03:00
Bogdan 71f4a88ab3 Bump version to 0.3.6 2023-09-17 12:02:41 +03:00
Bogdan 30b283eda3 Fixed: Ignore inaccessible mount points
(cherry picked from commit 60f18249b05daa20523542beef54bc126d963d1e)
2023-09-14 06:40:05 +03:00
Bogdan e23d0bbfa1 Add housekeeping task to unmonitor multiple monitored editions 2023-09-10 20:30:55 +03:00
Weblate 765a2aa01b Multiple Translations updated by Weblate
ignore-downstream

Co-authored-by: Qstick <qstick@gmail.com>
Translate-URL: https://translate.servarr.com/projects/servarr/readarr/da/
Translation: Servarr/Readarr
2023-09-10 20:30:40 +03:00
Bogdan 64895c3210 Bump version to 0.3.5 2023-09-10 09:04:03 +03:00
Weblate 03ab84a814 Multiple Translations updated by Weblate
ignore-downstream

Co-authored-by: DavidJares <david.jares@me.com>
Co-authored-by: Havok Dan <havokdan@yahoo.com.br>
Co-authored-by: He Zhu <zhuhe202@qq.com>
Co-authored-by: Weblate <noreply@weblate.org>
Co-authored-by: 宿命 <331874545@qq.com>
Translate-URL: https://translate.servarr.com/projects/servarr/readarr/cs/
Translate-URL: https://translate.servarr.com/projects/servarr/readarr/pt_BR/
Translate-URL: https://translate.servarr.com/projects/servarr/readarr/zh_CN/
Translation: Servarr/Readarr
2023-09-10 00:33:32 -05:00
Bogdan b77e5b14e1 Fixed: macOS version detection
(cherry picked from commit 060be6177a5477c94823e6a423c42064dedc1afb)

Closes #2908
2023-09-08 05:08:09 +03:00
351 changed files with 5956 additions and 3151 deletions
+1 -1
View File
@@ -1,5 +1,5 @@
name: Bug Report name: Bug Report
description: 'Report a new bug, if you are not 100% certain this is a bug please go to our Reddit or Discord first' description: 'Report a new bug, if you are not 100% certain this is a bug please go to our Discord first'
labels: ['Type: Bug', 'Status: Needs Triage'] labels: ['Type: Bug', 'Status: Needs Triage']
body: body:
- type: checkboxes - type: checkboxes
-3
View File
@@ -3,6 +3,3 @@ contact_links:
- name: Support via Discord - name: Support via Discord
url: https://readarr.com/discord url: https://readarr.com/discord
about: Chat with users and devs on support and setup related topics. about: Chat with users and devs on support and setup related topics.
- name: Support via Reddit
url: https://reddit.com/r/Readarr
about: Discuss and search thru support topics.
+16
View File
@@ -0,0 +1,16 @@
# Configuration for Label Actions - https://github.com/dessant/label-actions
'Type: Support':
comment: >
:wave: @{issue-author}, we use the issue tracker exclusively
for bug reports and feature requests. However, this issue appears
to be a support request. Please hop over onto our [Discord](https://readarr.com/discord).
close: true
close-reason: 'not planned'
'Status: Logs Needed':
comment: >
:wave: @{issue-author}, In order to help you further we'll need to see logs.
You'll need to enable trace logging and replicate the problem that you encountered.
Guidance on how to enable trace logging can be found in
our [troubleshooting guide](https://wiki.servarr.com/readarr/troubleshooting#logging-and-log-files).
+17
View File
@@ -0,0 +1,17 @@
name: 'Label Actions'
on:
issues:
types: [labeled, unlabeled]
permissions:
contents: read
issues: write
jobs:
action:
runs-on: ubuntu-latest
steps:
- uses: dessant/label-actions@v3
with:
process-only: 'issues'
-32
View File
@@ -1,32 +0,0 @@
name: 'Support requests'
on:
issues:
types: [labeled, unlabeled, reopened]
jobs:
support:
runs-on: ubuntu-latest
steps:
- uses: dessant/support-requests@v3
with:
github-token: ${{ github.token }}
support-label: 'Type: Support'
issue-comment: >
:wave: @{issue-author}, we use the issue tracker exclusively
for bug reports and feature requests. However, this issue appears
to be a support request. Please hop over onto our [Discord](https://readarr.com/discord)
or [Subreddit](https://reddit.com/r/readarr)
close-issue: true
lock-issue: false
- uses: dessant/support-requests@v3
with:
github-token: ${{ github.token }}
support-label: 'Status: Logs Needed'
issue-comment: >
:wave: @{issue-author}, In order to help you further we'll need to see logs.
You'll need to enable trace logging and replicate the problem that you encountered.
Guidance on how to enable trace logging can be found in
our [troubleshooting guide](https://wiki.servarr.com/readarr/troubleshooting#logging-and-log-files).
close-issue: false
lock-issue: false
+2 -2
View File
@@ -9,13 +9,13 @@ variables:
testsFolder: './_tests' testsFolder: './_tests'
yarnCacheFolder: $(Pipeline.Workspace)/.yarn yarnCacheFolder: $(Pipeline.Workspace)/.yarn
nugetCacheFolder: $(Pipeline.Workspace)/.nuget/packages nugetCacheFolder: $(Pipeline.Workspace)/.nuget/packages
majorVersion: '0.3.4' majorVersion: '0.3.19'
minorVersion: $[counter('minorVersion', 1)] minorVersion: $[counter('minorVersion', 1)]
readarrVersion: '$(majorVersion).$(minorVersion)' readarrVersion: '$(majorVersion).$(minorVersion)'
buildName: '$(Build.SourceBranchName).$(readarrVersion)' buildName: '$(Build.SourceBranchName).$(readarrVersion)'
sentryOrg: 'servarr' sentryOrg: 'servarr'
sentryUrl: 'https://sentry.servarr.com' sentryUrl: 'https://sentry.servarr.com'
dotnetVersion: '6.0.413' dotnetVersion: '6.0.417'
nodeVersion: '16.X' nodeVersion: '16.X'
innoVersion: '6.2.0' innoVersion: '6.2.0'
windowsImage: 'windows-2022' windowsImage: 'windows-2022'
+2
View File
@@ -2,6 +2,8 @@ const loose = true;
module.exports = { module.exports = {
plugins: [ plugins: [
'@babel/plugin-transform-logical-assignment-operators',
// Stage 1 // Stage 1
'@babel/plugin-proposal-export-default-from', '@babel/plugin-proposal-export-default-from',
['@babel/plugin-transform-optional-chaining', { loose }], ['@babel/plugin-transform-optional-chaining', { loose }],
+14 -3
View File
@@ -23,7 +23,7 @@ import selectAll from 'Utilities/Table/selectAll';
import toggleSelected from 'Utilities/Table/toggleSelected'; import toggleSelected from 'Utilities/Table/toggleSelected';
import QueueOptionsConnector from './QueueOptionsConnector'; import QueueOptionsConnector from './QueueOptionsConnector';
import QueueRowConnector from './QueueRowConnector'; import QueueRowConnector from './QueueRowConnector';
import RemoveQueueItemsModal from './RemoveQueueItemsModal'; import RemoveQueueItemModal from './RemoveQueueItemModal';
class Queue extends Component { class Queue extends Component {
@@ -289,9 +289,16 @@ class Queue extends Component {
} }
</PageContentBody> </PageContentBody>
<RemoveQueueItemsModal <RemoveQueueItemModal
isOpen={isConfirmRemoveModalOpen} isOpen={isConfirmRemoveModalOpen}
selectedCount={selectedCount} selectedCount={selectedCount}
canChangeCategory={isConfirmRemoveModalOpen && (
selectedIds.every((id) => {
const item = items.find((i) => i.id === id);
return !!(item && item.downloadClientHasPostImportCategory);
})
)}
canIgnore={isConfirmRemoveModalOpen && ( canIgnore={isConfirmRemoveModalOpen && (
selectedIds.every((id) => { selectedIds.every((id) => {
const item = items.find((i) => i.id === id); const item = items.find((i) => i.id === id);
@@ -299,7 +306,7 @@ class Queue extends Component {
return !!(item && item.authorId && item.bookId); return !!(item && item.authorId && item.bookId);
}) })
)} )}
allPending={isConfirmRemoveModalOpen && ( pending={isConfirmRemoveModalOpen && (
selectedIds.every((id) => { selectedIds.every((id) => {
const item = items.find((i) => i.id === id); const item = items.find((i) => i.id === id);
@@ -338,4 +345,8 @@ Queue.propTypes = {
onRemoveSelectedPress: PropTypes.func.isRequired onRemoveSelectedPress: PropTypes.func.isRequired
}; };
Queue.defaultProps = {
count: 0
};
export default Queue; export default Queue;
+3
View File
@@ -98,6 +98,7 @@ class QueueRow extends Component {
indexer, indexer,
outputPath, outputPath,
downloadClient, downloadClient,
downloadClientHasPostImportCategory,
downloadForced, downloadForced,
estimatedCompletionTime, estimatedCompletionTime,
timeleft, timeleft,
@@ -389,6 +390,7 @@ class QueueRow extends Component {
<RemoveQueueItemModal <RemoveQueueItemModal
isOpen={isRemoveQueueItemModalOpen} isOpen={isRemoveQueueItemModalOpen}
sourceTitle={title} sourceTitle={title}
canChangeCategory={!!downloadClientHasPostImportCategory}
canIgnore={!!author} canIgnore={!!author}
isPending={isPending} isPending={isPending}
onRemovePress={this.onRemoveQueueItemModalConfirmed} onRemovePress={this.onRemoveQueueItemModalConfirmed}
@@ -418,6 +420,7 @@ QueueRow.propTypes = {
indexer: PropTypes.string, indexer: PropTypes.string,
outputPath: PropTypes.string, outputPath: PropTypes.string,
downloadClient: PropTypes.string, downloadClient: PropTypes.string,
downloadClientHasPostImportCategory: PropTypes.bool,
downloadForced: PropTypes.bool.isRequired, downloadForced: PropTypes.bool.isRequired,
estimatedCompletionTime: PropTypes.string, estimatedCompletionTime: PropTypes.string,
timeleft: PropTypes.string, timeleft: PropTypes.string,
@@ -1,177 +0,0 @@
import PropTypes from 'prop-types';
import React, { Component } from 'react';
import FormGroup from 'Components/Form/FormGroup';
import FormInputGroup from 'Components/Form/FormInputGroup';
import FormLabel from 'Components/Form/FormLabel';
import Button from 'Components/Link/Button';
import Modal from 'Components/Modal/Modal';
import ModalBody from 'Components/Modal/ModalBody';
import ModalContent from 'Components/Modal/ModalContent';
import ModalFooter from 'Components/Modal/ModalFooter';
import ModalHeader from 'Components/Modal/ModalHeader';
import { inputTypes, kinds, sizes } from 'Helpers/Props';
import translate from 'Utilities/String/translate';
class RemoveQueueItemModal extends Component {
//
// Lifecycle
constructor(props, context) {
super(props, context);
this.state = {
remove: true,
blocklist: false,
skipRedownload: false
};
}
//
// Control
resetState = function() {
this.setState({
remove: true,
blocklist: false,
skipRedownload: false
});
};
//
// Listeners
onRemoveChange = ({ value }) => {
this.setState({ remove: value });
};
onBlocklistChange = ({ value }) => {
this.setState({ blocklist: value });
};
onSkipRedownloadChange = ({ value }) => {
this.setState({ skipRedownload: value });
};
onRemoveConfirmed = () => {
const state = this.state;
this.resetState();
this.props.onRemovePress(state);
};
onModalClose = () => {
this.resetState();
this.props.onModalClose();
};
//
// Render
render() {
const {
isOpen,
sourceTitle,
canIgnore,
isPending
} = this.props;
const { remove, blocklist, skipRedownload } = this.state;
return (
<Modal
isOpen={isOpen}
size={sizes.MEDIUM}
onModalClose={this.onModalClose}
>
<ModalContent
onModalClose={this.onModalClose}
>
<ModalHeader>
Remove - {sourceTitle}
</ModalHeader>
<ModalBody>
<div>
Are you sure you want to remove '{sourceTitle}' from the queue?
</div>
{
isPending ?
null :
<FormGroup>
<FormLabel>
{translate('RemoveFromDownloadClient')}
</FormLabel>
<FormInputGroup
type={inputTypes.CHECK}
name="remove"
value={remove}
helpTextWarning={translate('RemoveHelpTextWarning')}
isDisabled={!canIgnore}
onChange={this.onRemoveChange}
/>
</FormGroup>
}
<FormGroup>
<FormLabel>
{translate('BlocklistRelease')}
</FormLabel>
<FormInputGroup
type={inputTypes.CHECK}
name="blocklist"
value={blocklist}
helpText={translate('BlocklistReleaseHelpText')}
onChange={this.onBlocklistChange}
/>
</FormGroup>
{
blocklist &&
<FormGroup>
<FormLabel>
{translate('SkipRedownload')}
</FormLabel>
<FormInputGroup
type={inputTypes.CHECK}
name="skipRedownload"
value={skipRedownload}
helpText={translate('SkipRedownloadHelpText')}
onChange={this.onSkipRedownloadChange}
/>
</FormGroup>
}
</ModalBody>
<ModalFooter>
<Button onPress={this.onModalClose}>
Close
</Button>
<Button
kind={kinds.DANGER}
onPress={this.onRemoveConfirmed}
>
Remove
</Button>
</ModalFooter>
</ModalContent>
</Modal>
);
}
}
RemoveQueueItemModal.propTypes = {
isOpen: PropTypes.bool.isRequired,
sourceTitle: PropTypes.string.isRequired,
canIgnore: PropTypes.bool.isRequired,
isPending: PropTypes.bool.isRequired,
onRemovePress: PropTypes.func.isRequired,
onModalClose: PropTypes.func.isRequired
};
export default RemoveQueueItemModal;
@@ -0,0 +1,230 @@
import React, { useCallback, useMemo, useState } from 'react';
import FormGroup from 'Components/Form/FormGroup';
import FormInputGroup from 'Components/Form/FormInputGroup';
import FormLabel from 'Components/Form/FormLabel';
import Button from 'Components/Link/Button';
import Modal from 'Components/Modal/Modal';
import ModalBody from 'Components/Modal/ModalBody';
import ModalContent from 'Components/Modal/ModalContent';
import ModalFooter from 'Components/Modal/ModalFooter';
import ModalHeader from 'Components/Modal/ModalHeader';
import { inputTypes, kinds, sizes } from 'Helpers/Props';
import translate from 'Utilities/String/translate';
import styles from './RemoveQueueItemModal.css';
interface RemovePressProps {
remove: boolean;
changeCategory: boolean;
blocklist: boolean;
skipRedownload: boolean;
}
interface RemoveQueueItemModalProps {
isOpen: boolean;
sourceTitle: string;
canChangeCategory: boolean;
canIgnore: boolean;
isPending: boolean;
selectedCount?: number;
onRemovePress(props: RemovePressProps): void;
onModalClose: () => void;
}
type RemovalMethod = 'removeFromClient' | 'changeCategory' | 'ignore';
type BlocklistMethod =
| 'doNotBlocklist'
| 'blocklistAndSearch'
| 'blocklistOnly';
function RemoveQueueItemModal(props: RemoveQueueItemModalProps) {
const {
isOpen,
sourceTitle,
canIgnore,
canChangeCategory,
isPending,
selectedCount,
onRemovePress,
onModalClose,
} = props;
const multipleSelected = selectedCount && selectedCount > 1;
const [removalMethod, setRemovalMethod] =
useState<RemovalMethod>('removeFromClient');
const [blocklistMethod, setBlocklistMethod] =
useState<BlocklistMethod>('doNotBlocklist');
const { title, message } = useMemo(() => {
if (!selectedCount) {
return {
title: translate('RemoveQueueItem', { sourceTitle }),
message: translate('RemoveQueueItemConfirmation', { sourceTitle }),
};
}
if (selectedCount === 1) {
return {
title: translate('RemoveSelectedItem'),
message: translate('RemoveSelectedItemQueueMessageText'),
};
}
return {
title: translate('RemoveSelectedItems'),
message: translate('RemoveSelectedItemsQueueMessageText', {
selectedCount,
}),
};
}, [sourceTitle, selectedCount]);
const removalMethodOptions = useMemo(() => {
return [
{
key: 'removeFromClient',
value: translate('RemoveFromDownloadClient'),
hint: multipleSelected
? translate('RemoveMultipleFromDownloadClientHint')
: translate('RemoveFromDownloadClientHint'),
},
{
key: 'changeCategory',
value: translate('ChangeCategory'),
isDisabled: !canChangeCategory,
hint: multipleSelected
? translate('ChangeCategoryMultipleHint')
: translate('ChangeCategoryHint'),
},
{
key: 'ignore',
value: multipleSelected
? translate('IgnoreDownloads')
: translate('IgnoreDownload'),
isDisabled: !canIgnore,
hint: multipleSelected
? translate('IgnoreDownloadsHint')
: translate('IgnoreDownloadHint'),
},
];
}, [canChangeCategory, canIgnore, multipleSelected]);
const blocklistMethodOptions = useMemo(() => {
return [
{
key: 'doNotBlocklist',
value: translate('DoNotBlocklist'),
hint: translate('DoNotBlocklistHint'),
},
{
key: 'blocklistAndSearch',
value: translate('BlocklistAndSearch'),
hint: multipleSelected
? translate('BlocklistAndSearchMultipleHint')
: translate('BlocklistAndSearchHint'),
},
{
key: 'blocklistOnly',
value: translate('BlocklistOnly'),
hint: multipleSelected
? translate('BlocklistMultipleOnlyHint')
: translate('BlocklistOnlyHint'),
},
];
}, [multipleSelected]);
const handleRemovalMethodChange = useCallback(
({ value }: { value: RemovalMethod }) => {
setRemovalMethod(value);
},
[setRemovalMethod]
);
const handleBlocklistMethodChange = useCallback(
({ value }: { value: BlocklistMethod }) => {
setBlocklistMethod(value);
},
[setBlocklistMethod]
);
const handleConfirmRemove = useCallback(() => {
onRemovePress({
remove: removalMethod === 'removeFromClient',
changeCategory: removalMethod === 'changeCategory',
blocklist: blocklistMethod !== 'doNotBlocklist',
skipRedownload: blocklistMethod === 'blocklistOnly',
});
setRemovalMethod('removeFromClient');
setBlocklistMethod('doNotBlocklist');
}, [
removalMethod,
blocklistMethod,
setRemovalMethod,
setBlocklistMethod,
onRemovePress,
]);
const handleModalClose = useCallback(() => {
setRemovalMethod('removeFromClient');
setBlocklistMethod('doNotBlocklist');
onModalClose();
}, [setRemovalMethod, setBlocklistMethod, onModalClose]);
return (
<Modal isOpen={isOpen} size={sizes.MEDIUM} onModalClose={handleModalClose}>
<ModalContent onModalClose={handleModalClose}>
<ModalHeader>{title}</ModalHeader>
<ModalBody>
<div className={styles.message}>{message}</div>
{isPending ? null : (
<FormGroup>
<FormLabel>{translate('RemoveQueueItemRemovalMethod')}</FormLabel>
<FormInputGroup
type={inputTypes.SELECT}
name="removalMethod"
value={removalMethod}
values={removalMethodOptions}
isDisabled={!canChangeCategory && !canIgnore}
helpTextWarning={translate(
'RemoveQueueItemRemovalMethodHelpTextWarning'
)}
onChange={handleRemovalMethodChange}
/>
</FormGroup>
)}
<FormGroup>
<FormLabel>
{multipleSelected
? translate('BlocklistReleases')
: translate('BlocklistRelease')}
</FormLabel>
<FormInputGroup
type={inputTypes.SELECT}
name="blocklistMethod"
value={blocklistMethod}
values={blocklistMethodOptions}
helpText={translate('BlocklistReleaseHelpText')}
onChange={handleBlocklistMethodChange}
/>
</FormGroup>
</ModalBody>
<ModalFooter>
<Button onPress={handleModalClose}>{translate('Close')}</Button>
<Button kind={kinds.DANGER} onPress={handleConfirmRemove}>
{translate('Remove')}
</Button>
</ModalFooter>
</ModalContent>
</Modal>
);
}
export default RemoveQueueItemModal;
@@ -1,178 +0,0 @@
import PropTypes from 'prop-types';
import React, { Component } from 'react';
import FormGroup from 'Components/Form/FormGroup';
import FormInputGroup from 'Components/Form/FormInputGroup';
import FormLabel from 'Components/Form/FormLabel';
import Button from 'Components/Link/Button';
import Modal from 'Components/Modal/Modal';
import ModalBody from 'Components/Modal/ModalBody';
import ModalContent from 'Components/Modal/ModalContent';
import ModalFooter from 'Components/Modal/ModalFooter';
import ModalHeader from 'Components/Modal/ModalHeader';
import { inputTypes, kinds, sizes } from 'Helpers/Props';
import translate from 'Utilities/String/translate';
import styles from './RemoveQueueItemsModal.css';
class RemoveQueueItemsModal extends Component {
//
// Lifecycle
constructor(props, context) {
super(props, context);
this.state = {
remove: true,
blocklist: false,
skipRedownload: false
};
}
//
// Control
resetState = function() {
this.setState({
remove: true,
blocklist: false,
skipRedownload: false
});
};
//
// Listeners
onRemoveChange = ({ value }) => {
this.setState({ remove: value });
};
onBlocklistChange = ({ value }) => {
this.setState({ blocklist: value });
};
onSkipRedownloadChange = ({ value }) => {
this.setState({ skipRedownload: value });
};
onRemoveConfirmed = () => {
const state = this.state;
this.resetState();
this.props.onRemovePress(state);
};
onModalClose = () => {
this.resetState();
this.props.onModalClose();
};
//
// Render
render() {
const {
isOpen,
selectedCount,
canIgnore,
allPending
} = this.props;
const { remove, blocklist, skipRedownload } = this.state;
return (
<Modal
isOpen={isOpen}
size={sizes.MEDIUM}
onModalClose={this.onModalClose}
>
<ModalContent
onModalClose={this.onModalClose}
>
<ModalHeader>
{selectedCount > 1 ? translate('RemoveSelectedItems') : translate('RemoveSelectedItem')}
</ModalHeader>
<ModalBody>
<div className={styles.message}>
{selectedCount > 1 ? translate('RemoveSelectedItemsQueueMessageText', selectedCount) : translate('RemoveSelectedItemQueueMessageText')}
</div>
{
allPending ?
null :
<FormGroup>
<FormLabel>
{translate('RemoveFromDownloadClient')}
</FormLabel>
<FormInputGroup
type={inputTypes.CHECK}
name="remove"
value={remove}
helpTextWarning={translate('RemoveHelpTextWarning')}
isDisabled={!canIgnore}
onChange={this.onRemoveChange}
/>
</FormGroup>
}
<FormGroup>
<FormLabel>
{selectedCount > 1 ? translate('BlocklistReleases') : translate('BlocklistRelease')}
</FormLabel>
<FormInputGroup
type={inputTypes.CHECK}
name="blocklist"
value={blocklist}
helpText={translate('BlocklistReleaseHelpText')}
onChange={this.onBlocklistChange}
/>
</FormGroup>
{
blocklist &&
<FormGroup>
<FormLabel>
{translate('SkipRedownload')}
</FormLabel>
<FormInputGroup
type={inputTypes.CHECK}
name="skipRedownload"
value={skipRedownload}
helpText={translate('SkipRedownloadHelpText')}
onChange={this.onSkipRedownloadChange}
/>
</FormGroup>
}
</ModalBody>
<ModalFooter>
<Button onPress={this.onModalClose}>
{translate('Close')}
</Button>
<Button
kind={kinds.DANGER}
onPress={this.onRemoveConfirmed}
>
{translate('Remove')}
</Button>
</ModalFooter>
</ModalContent>
</Modal>
);
}
}
RemoveQueueItemsModal.propTypes = {
isOpen: PropTypes.bool.isRequired,
selectedCount: PropTypes.number.isRequired,
canIgnore: PropTypes.bool.isRequired,
allPending: PropTypes.bool.isRequired,
onRemovePress: PropTypes.func.isRequired,
onModalClose: PropTypes.func.isRequired
};
export default RemoveQueueItemsModal;
+3 -6
View File
@@ -7,13 +7,10 @@ function findImage(images, coverType) {
} }
function getUrl(image, coverType, size) { function getUrl(image, coverType, size) {
if (image) { const imageUrl = image?.url;
// Remove protocol
let url = image.url;
url = url.replace(`${coverType}.jpg`, `${coverType}-${size}.jpg`); if (imageUrl) {
return imageUrl.replace(`${coverType}.jpg`, `${coverType}-${size}.jpg`);
return url;
} }
} }
@@ -44,6 +44,10 @@
margin-top: 20px; margin-top: 20px;
} }
.filterIcon {
float: right;
}
.authorNavigationButtons { .authorNavigationButtons {
position: absolute; position: absolute;
right: 0; right: 0;
+1
View File
@@ -6,6 +6,7 @@ interface CssExports {
'authorUpButton': string; 'authorUpButton': string;
'contentContainer': string; 'contentContainer': string;
'errorMessage': string; 'errorMessage': string;
'filterIcon': string;
'innerContentBody': string; 'innerContentBody': string;
'metadataMessage': string; 'metadataMessage': string;
'selectedTab': string; 'selectedTab': string;
+8 -3
View File
@@ -239,9 +239,14 @@ class AuthorDetails extends Component {
saveError, saveError,
isDeleting, isDeleting,
deleteError, deleteError,
statistics statistics = {}
} = this.props; } = this.props;
const {
bookFileCount = 0,
totalBookCount = 0
} = statistics;
const { const {
isOrganizeModalOpen, isOrganizeModalOpen,
isRetagModalOpen, isRetagModalOpen,
@@ -435,7 +440,7 @@ class AuthorDetails extends Component {
className={styles.tab} className={styles.tab}
selectedClassName={styles.selectedTab} selectedClassName={styles.selectedTab}
> >
{translate('BooksTotal', [statistics.totalBookCount])} {translate('BooksTotal', [totalBookCount])}
</Tab> </Tab>
<Tab <Tab
@@ -463,7 +468,7 @@ class AuthorDetails extends Component {
className={styles.tab} className={styles.tab}
selectedClassName={styles.selectedTab} selectedClassName={styles.selectedTab}
> >
{translate('FilesTotal', [statistics.bookFileCount])} {translate('FilesTotal', [bookFileCount])}
</Tab> </Tab>
{ {
@@ -155,7 +155,6 @@ function createMapStateToProps() {
const isRefreshing = isAuthorRefreshing || allAuthorRefreshing; const isRefreshing = isAuthorRefreshing || allAuthorRefreshing;
const isSearching = isCommandExecuting(findCommand(commands, { name: commandNames.AUTHOR_SEARCH, authorId: author.id })); const isSearching = isCommandExecuting(findCommand(commands, { name: commandNames.AUTHOR_SEARCH, authorId: author.id }));
const isRenamingFiles = isCommandExecuting(findCommand(commands, { name: commandNames.RENAME_FILES, authorId: author.id })); const isRenamingFiles = isCommandExecuting(findCommand(commands, { name: commandNames.RENAME_FILES, authorId: author.id }));
const isRenamingAuthorCommand = findCommand(commands, { name: commandNames.RENAME_AUTHOR }); const isRenamingAuthorCommand = findCommand(commands, { name: commandNames.RENAME_AUTHOR });
const isRenamingAuthor = ( const isRenamingAuthor = (
isCommandExecuting(isRenamingAuthorCommand) && isCommandExecuting(isRenamingAuthorCommand) &&
@@ -136,8 +136,9 @@
} }
.title { .title {
font-weight: 300;
font-size: 30px; font-size: 30px;
line-height: 50px; line-height: 30px;
} }
} }
@@ -25,12 +25,7 @@ const defaultFontSize = parseInt(fonts.defaultFontSize);
const lineHeight = parseFloat(fonts.lineHeight); const lineHeight = parseFloat(fonts.lineHeight);
function getFanartUrl(images) { function getFanartUrl(images) {
const fanartImage = images.find((x) => x.coverType === 'fanart'); return images.find((x) => x.coverType === 'fanart')?.url;
if (fanartImage) {
// Remove protocol
return fanartImage.url.replace(/^https?:/, '');
}
} }
class AuthorDetailsHeader extends Component { class AuthorDetailsHeader extends Component {
@@ -1,6 +1,7 @@
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import React from 'react'; import React from 'react';
import Modal from 'Components/Modal/Modal'; import Modal from 'Components/Modal/Modal';
import { sizes } from 'Helpers/Props';
import AuthorHistoryContentConnector from './AuthorHistoryContentConnector'; import AuthorHistoryContentConnector from './AuthorHistoryContentConnector';
import AuthorHistoryModalContent from './AuthorHistoryModalContent'; import AuthorHistoryModalContent from './AuthorHistoryModalContent';
@@ -14,6 +15,7 @@ function AuthorHistoryModal(props) {
return ( return (
<Modal <Modal
isOpen={isOpen} isOpen={isOpen}
size={sizes.EXTRA_LARGE}
onModalClose={onModalClose} onModalClose={onModalClose}
> >
<AuthorHistoryContentConnector <AuthorHistoryContentConnector
@@ -5,6 +5,7 @@ import ModalBody from 'Components/Modal/ModalBody';
import ModalContent from 'Components/Modal/ModalContent'; import ModalContent from 'Components/Modal/ModalContent';
import ModalFooter from 'Components/Modal/ModalFooter'; import ModalFooter from 'Components/Modal/ModalFooter';
import ModalHeader from 'Components/Modal/ModalHeader'; import ModalHeader from 'Components/Modal/ModalHeader';
import translate from 'Utilities/String/translate';
import AuthorHistoryTableContent from './AuthorHistoryTableContent'; import AuthorHistoryTableContent from './AuthorHistoryTableContent';
class AuthorHistoryModalContent extends Component { class AuthorHistoryModalContent extends Component {
@@ -20,7 +21,7 @@ class AuthorHistoryModalContent extends Component {
return ( return (
<ModalContent onModalClose={onModalClose}> <ModalContent onModalClose={onModalClose}>
<ModalHeader> <ModalHeader>
History {translate('History')}
</ModalHeader> </ModalHeader>
<ModalBody> <ModalBody>
@@ -31,7 +32,7 @@ class AuthorHistoryModalContent extends Component {
<ModalFooter> <ModalFooter>
<Button onPress={onModalClose}> <Button onPress={onModalClose}>
Close {translate('Close')}
</Button> </Button>
</ModalFooter> </ModalFooter>
</ModalContent> </ModalContent>
@@ -4,7 +4,6 @@
word-break: break-word; word-break: break-word;
} }
.details,
.actions { .actions {
composes: cell from '~Components/Table/Cells/TableRowCell.css'; composes: cell from '~Components/Table/Cells/TableRowCell.css';
-1
View File
@@ -2,7 +2,6 @@
// Please do not change this file! // Please do not change this file!
interface CssExports { interface CssExports {
'actions': string; 'actions': string;
'details': string;
'sourceTitle': string; 'sourceTitle': string;
} }
export const cssExports: CssExports; export const cssExports: CssExports;
@@ -2,6 +2,7 @@ import PropTypes from 'prop-types';
import React, { Component } from 'react'; import React, { Component } from 'react';
import HistoryDetailsConnector from 'Activity/History/Details/HistoryDetailsConnector'; import HistoryDetailsConnector from 'Activity/History/Details/HistoryDetailsConnector';
import HistoryEventTypeCell from 'Activity/History/HistoryEventTypeCell'; import HistoryEventTypeCell from 'Activity/History/HistoryEventTypeCell';
import BookFormats from 'Book/BookFormats';
import BookQuality from 'Book/BookQuality'; import BookQuality from 'Book/BookQuality';
import Icon from 'Components/Icon'; import Icon from 'Components/Icon';
import IconButton from 'Components/Link/IconButton'; import IconButton from 'Components/Link/IconButton';
@@ -11,6 +12,7 @@ import TableRowCell from 'Components/Table/Cells/TableRowCell';
import TableRow from 'Components/Table/TableRow'; import TableRow from 'Components/Table/TableRow';
import Popover from 'Components/Tooltip/Popover'; import Popover from 'Components/Tooltip/Popover';
import { icons, kinds, tooltipPositions } from 'Helpers/Props'; import { icons, kinds, tooltipPositions } from 'Helpers/Props';
import formatCustomFormatScore from 'Utilities/Number/formatCustomFormatScore';
import translate from 'Utilities/String/translate'; import translate from 'Utilities/String/translate';
import styles from './AuthorHistoryRow.css'; import styles from './AuthorHistoryRow.css';
@@ -75,6 +77,8 @@ class AuthorHistoryRow extends Component {
sourceTitle, sourceTitle,
quality, quality,
qualityCutoffNotMet, qualityCutoffNotMet,
customFormats,
customFormatScore,
date, date,
data, data,
book book
@@ -106,11 +110,19 @@ class AuthorHistoryRow extends Component {
/> />
</TableRowCell> </TableRowCell>
<TableRowCell>
<BookFormats formats={customFormats} />
</TableRowCell>
<TableRowCell>
{formatCustomFormatScore(customFormatScore, customFormats.length)}
</TableRowCell>
<RelativeDateCellConnector <RelativeDateCellConnector
date={date} date={date}
/> />
<TableRowCell className={styles.details}> <TableRowCell className={styles.actions}>
<Popover <Popover
anchor={ anchor={
<Icon <Icon
@@ -127,14 +139,13 @@ class AuthorHistoryRow extends Component {
} }
position={tooltipPositions.LEFT} position={tooltipPositions.LEFT}
/> />
</TableRowCell>
<TableRowCell className={styles.actions}>
{ {
eventType === 'grabbed' && eventType === 'grabbed' &&
<IconButton <IconButton
title={translate('MarkAsFailed')} title={translate('MarkAsFailed')}
name={icons.REMOVE} name={icons.REMOVE}
size={14}
onPress={this.onMarkAsFailedPress} onPress={this.onMarkAsFailedPress}
/> />
} }
@@ -160,6 +171,8 @@ AuthorHistoryRow.propTypes = {
sourceTitle: PropTypes.string.isRequired, sourceTitle: PropTypes.string.isRequired,
quality: PropTypes.object.isRequired, quality: PropTypes.object.isRequired,
qualityCutoffNotMet: PropTypes.bool.isRequired, qualityCutoffNotMet: PropTypes.bool.isRequired,
customFormats: PropTypes.arrayOf(PropTypes.object),
customFormatScore: PropTypes.number.isRequired,
date: PropTypes.string.isRequired, date: PropTypes.string.isRequired,
data: PropTypes.object.isRequired, data: PropTypes.object.isRequired,
fullAuthor: PropTypes.bool.isRequired, fullAuthor: PropTypes.bool.isRequired,
@@ -0,0 +1,9 @@
.container {
border: 1px solid var(--borderColor);
border-radius: 4px;
background-color: var(--inputBackgroundColor);
&:last-of-type {
margin-bottom: 0;
}
}
@@ -0,0 +1,7 @@
// This file is automatically generated.
// Please do not change this file!
interface CssExports {
'container': string;
}
export const cssExports: CssExports;
export default cssExports;
@@ -1,6 +1,7 @@
import React from 'react'; import React from 'react';
import AuthorHistoryContentConnector from 'Author/History/AuthorHistoryContentConnector'; import AuthorHistoryContentConnector from 'Author/History/AuthorHistoryContentConnector';
import AuthorHistoryTableContent from 'Author/History/AuthorHistoryTableContent'; import AuthorHistoryTableContent from 'Author/History/AuthorHistoryTableContent';
import styles from './AuthorHistoryTable.css';
function AuthorHistoryTable(props) { function AuthorHistoryTable(props) {
const { const {
@@ -8,10 +9,12 @@ function AuthorHistoryTable(props) {
} = props; } = props;
return ( return (
<AuthorHistoryContentConnector <div className={styles.container}>
component={AuthorHistoryTableContent} <AuthorHistoryContentConnector
{...otherProps} component={AuthorHistoryTableContent}
/> {...otherProps}
/>
</div>
); );
} }
@@ -0,0 +1,5 @@
.blankpad {
padding-top: 10px;
padding-bottom: 10px;
padding-left: 2em;
}
@@ -0,0 +1,7 @@
// This file is automatically generated.
// Please do not change this file!
interface CssExports {
'blankpad': string;
}
export const cssExports: CssExports;
export default cssExports;
@@ -1,12 +1,14 @@
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import React, { Component } from 'react'; import React, { Component } from 'react';
import Alert from 'Components/Alert'; import Alert from 'Components/Alert';
import Icon from 'Components/Icon';
import LoadingIndicator from 'Components/Loading/LoadingIndicator'; import LoadingIndicator from 'Components/Loading/LoadingIndicator';
import Table from 'Components/Table/Table'; import Table from 'Components/Table/Table';
import TableBody from 'Components/Table/TableBody'; import TableBody from 'Components/Table/TableBody';
import { kinds } from 'Helpers/Props'; import { icons, kinds } from 'Helpers/Props';
import translate from 'Utilities/String/translate'; import translate from 'Utilities/String/translate';
import AuthorHistoryRowConnector from './AuthorHistoryRowConnector'; import AuthorHistoryRowConnector from './AuthorHistoryRowConnector';
import styles from './AuthorHistoryTableContent.css';
const columns = [ const columns = [
{ {
@@ -15,32 +17,41 @@ const columns = [
}, },
{ {
name: 'book', name: 'book',
label: 'Book', label: () => translate('Book'),
isVisible: true isVisible: true
}, },
{ {
name: 'sourceTitle', name: 'sourceTitle',
label: 'Source Title', label: () => translate( 'SourceTitle'),
isVisible: true isVisible: true
}, },
{ {
name: 'quality', name: 'quality',
label: 'Quality', label: () => translate('Quality'),
isVisible: true
},
{
name: 'customFormats',
label: () => translate('CustomFormats'),
isSortable: false,
isVisible: true
},
{
name: 'customFormatScore',
label: React.createElement(Icon, {
name: icons.SCORE,
title: () => translate('CustomFormatScore')
}),
isSortable: true,
isVisible: true isVisible: true
}, },
{ {
name: 'date', name: 'date',
label: 'Date', label: () => translate('Date'),
isVisible: true
},
{
name: 'details',
label: 'Details',
isVisible: true isVisible: true
}, },
{ {
name: 'actions', name: 'actions',
label: 'Actions',
isVisible: true isVisible: true
} }
]; ];
@@ -64,7 +75,7 @@ class AuthorHistoryTableContent extends Component {
const hasItems = !!items.length; const hasItems = !!items.length;
return ( return (
<> <div>
{ {
isFetching && isFetching &&
<LoadingIndicator /> <LoadingIndicator />
@@ -79,7 +90,7 @@ class AuthorHistoryTableContent extends Component {
{ {
isPopulated && !hasItems && !error && isPopulated && !hasItems && !error &&
<div> <div className={styles.blankpad}>
{translate('NoHistory')} {translate('NoHistory')}
</div> </div>
} }
@@ -103,7 +114,7 @@ class AuthorHistoryTableContent extends Component {
</TableBody> </TableBody>
</Table> </Table>
} }
</> </div>
); );
} }
} }
@@ -90,7 +90,7 @@ class AuthorIndexOverview extends Component {
status, status,
titleSlug, titleSlug,
nextAiring, nextAiring,
statistics, statistics = {},
images, images,
posterWidth, posterWidth,
posterHeight, posterHeight,
@@ -113,10 +113,11 @@ class AuthorIndexOverview extends Component {
} = this.props; } = this.props;
const { const {
bookCount, bookCount = 0,
sizeOnDisk, availableBookCount = 0,
bookFileCount, bookFileCount = 0,
totalBookCount totalBookCount = 0,
sizeOnDisk = 0
} = statistics; } = statistics;
const { const {
@@ -179,6 +180,7 @@ class AuthorIndexOverview extends Component {
monitored={monitored} monitored={monitored}
status={status} status={status}
bookCount={bookCount} bookCount={bookCount}
availableBookCount={availableBookCount}
bookFileCount={bookFileCount} bookFileCount={bookFileCount}
totalBookCount={totalBookCount} totalBookCount={totalBookCount}
posterWidth={posterWidth} posterWidth={posterWidth}
@@ -85,7 +85,7 @@ class AuthorIndexPoster extends Component {
titleSlug, titleSlug,
status, status,
nextAiring, nextAiring,
statistics, statistics = {},
images, images,
posterWidth, posterWidth,
posterHeight, posterHeight,
@@ -110,10 +110,11 @@ class AuthorIndexPoster extends Component {
} = this.props; } = this.props;
const { const {
bookCount, bookCount = 0,
sizeOnDisk, availableBookCount = 0,
bookFileCount, bookFileCount = 0,
totalBookCount totalBookCount = 0,
sizeOnDisk = 0
} = statistics; } = statistics;
const { const {
@@ -213,6 +214,7 @@ class AuthorIndexPoster extends Component {
monitored={monitored} monitored={monitored}
status={status} status={status}
bookCount={bookCount} bookCount={bookCount}
availableBookCount={availableBookCount}
bookFileCount={bookFileCount} bookFileCount={bookFileCount}
totalBookCount={totalBookCount} totalBookCount={totalBookCount}
posterWidth={posterWidth} posterWidth={posterWidth}
@@ -11,14 +11,15 @@ function AuthorIndexProgressBar(props) {
monitored, monitored,
status, status,
bookCount, bookCount,
availableBookCount,
bookFileCount, bookFileCount,
totalBookCount, totalBookCount,
posterWidth, posterWidth,
detailedProgressBar detailedProgressBar
} = props; } = props;
const progress = bookCount ? bookCount / totalBookCount * 100 : 100; const progress = bookCount ? (availableBookCount / bookCount) * 100 : 100;
const text = `${bookCount} / ${totalBookCount}`; const text = `${availableBookCount} / ${bookCount}`;
return ( return (
<ProgressBar <ProgressBar
@@ -29,7 +30,7 @@ function AuthorIndexProgressBar(props) {
size={detailedProgressBar ? sizes.MEDIUM : sizes.SMALL} size={detailedProgressBar ? sizes.MEDIUM : sizes.SMALL}
showText={detailedProgressBar} showText={detailedProgressBar}
text={text} text={text}
title={translate('BookFileCountBookCountTotalTotalBookCountInterp', [bookFileCount, bookCount, totalBookCount])} title={translate('AuthorProgressBarText', { bookCount, availableBookCount, bookFileCount, totalBookCount })}
width={posterWidth} width={posterWidth}
/> />
); );
@@ -39,6 +40,7 @@ AuthorIndexProgressBar.propTypes = {
monitored: PropTypes.bool.isRequired, monitored: PropTypes.bool.isRequired,
status: PropTypes.string.isRequired, status: PropTypes.string.isRequired,
bookCount: PropTypes.number.isRequired, bookCount: PropTypes.number.isRequired,
availableBookCount: PropTypes.number.isRequired,
bookFileCount: PropTypes.number.isRequired, bookFileCount: PropTypes.number.isRequired,
totalBookCount: PropTypes.number.isRequired, totalBookCount: PropTypes.number.isRequired,
posterWidth: PropTypes.number.isRequired, posterWidth: PropTypes.number.isRequired,
@@ -90,7 +90,7 @@ class AuthorIndexRow extends Component {
nextBook, nextBook,
lastBook, lastBook,
added, added,
statistics, statistics = {},
genres, genres,
ratings, ratings,
path, path,
@@ -110,10 +110,11 @@ class AuthorIndexRow extends Component {
} = this.props; } = this.props;
const { const {
bookCount, bookCount = 0,
bookFileCount, availableBookCount = 0,
totalBookCount, bookFileCount = 0,
sizeOnDisk totalBookCount = 0,
sizeOnDisk = 0
} = statistics; } = statistics;
const { const {
@@ -286,7 +287,7 @@ class AuthorIndexRow extends Component {
} }
if (name === 'bookProgress') { if (name === 'bookProgress') {
const progress = bookCount ? bookFileCount / bookCount * 100 : 100; const progress = bookCount ? (availableBookCount / bookCount) * 100 : 100;
return ( return (
<VirtualTableRowCell <VirtualTableRowCell
@@ -297,8 +298,8 @@ class AuthorIndexRow extends Component {
progress={progress} progress={progress}
kind={getProgressBarKind(status, monitored, progress)} kind={getProgressBarKind(status, monitored, progress)}
showText={true} showText={true}
text={`${bookCount} / ${totalBookCount}`} text={`${availableBookCount} / ${bookCount}`}
title={translate('BookFileCountBookCountTotalTotalBookCountInterp', [bookFileCount, bookCount, totalBookCount])} title={translate('AuthorProgressBarText', { bookCount, availableBookCount, bookFileCount, totalBookCount })}
width={125} width={125}
/> />
</VirtualTableRowCell> </VirtualTableRowCell>
+46 -10
View File
@@ -3,6 +3,7 @@ import React from 'react';
import Label from 'Components/Label'; import Label from 'Components/Label';
import { kinds } from 'Helpers/Props'; import { kinds } from 'Helpers/Props';
import formatBytes from 'Utilities/Number/formatBytes'; import formatBytes from 'Utilities/Number/formatBytes';
import translate from 'Utilities/String/translate';
function getTooltip(title, quality, size, isMonitored, isCutoffNotMet) { function getTooltip(title, quality, size, isMonitored, isCutoffNotMet) {
const revision = quality.revision; const revision = quality.revision;
@@ -28,6 +29,36 @@ function getTooltip(title, quality, size, isMonitored, isCutoffNotMet) {
return title; return title;
} }
function revisionLabel(className, quality, showRevision) {
if (!showRevision) {
return;
}
if (quality.revision.isRepack) {
return (
<Label
className={className}
kind={kinds.PRIMARY}
title={translate('Repack')}
>
R
</Label>
);
}
if (quality.revision.version && quality.revision.version > 1) {
return (
<Label
className={className}
kind={kinds.PRIMARY}
title={translate('Proper')}
>
P
</Label>
);
}
}
function BookQuality(props) { function BookQuality(props) {
const { const {
className, className,
@@ -35,7 +66,8 @@ function BookQuality(props) {
quality, quality,
size, size,
isMonitored, isMonitored,
isCutoffNotMet isCutoffNotMet,
showRevision
} = props; } = props;
let kind = kinds.DEFAULT; let kind = kinds.DEFAULT;
@@ -50,13 +82,15 @@ function BookQuality(props) {
} }
return ( return (
<Label <span>
className={className} <Label
kind={kind} className={className}
title={getTooltip(title, quality, size, isMonitored, isCutoffNotMet)} kind={kind}
> title={getTooltip(title, quality, size, isMonitored, isCutoffNotMet)}
{quality.quality.name} >
</Label> {quality.quality.name}
</Label>{revisionLabel(className, quality, showRevision)}
</span>
); );
} }
@@ -66,12 +100,14 @@ BookQuality.propTypes = {
quality: PropTypes.object.isRequired, quality: PropTypes.object.isRequired,
size: PropTypes.number, size: PropTypes.number,
isMonitored: PropTypes.bool, isMonitored: PropTypes.bool,
isCutoffNotMet: PropTypes.bool isCutoffNotMet: PropTypes.bool,
showRevision: PropTypes.bool
}; };
BookQuality.defaultProps = { BookQuality.defaultProps = {
title: '', title: '',
isMonitored: true isMonitored: true,
showRevision: false
}; };
export default BookQuality; export default BookQuality;
+10 -4
View File
@@ -99,9 +99,14 @@ class BookDetails extends Component {
nextBook, nextBook,
isSearching, isSearching,
onRefreshPress, onRefreshPress,
onSearchPress onSearchPress,
statistics = {}
} = this.props; } = this.props;
const {
bookFileCount = 0
} = statistics;
const { const {
isOrganizeModalOpen, isOrganizeModalOpen,
isRetagModalOpen, isRetagModalOpen,
@@ -238,21 +243,21 @@ class BookDetails extends Component {
className={styles.tab} className={styles.tab}
selectedClassName={styles.selectedTab} selectedClassName={styles.selectedTab}
> >
History {translate('History')}
</Tab> </Tab>
<Tab <Tab
className={styles.tab} className={styles.tab}
selectedClassName={styles.selectedTab} selectedClassName={styles.selectedTab}
> >
Search {translate('Search')}
</Tab> </Tab>
<Tab <Tab
className={styles.tab} className={styles.tab}
selectedClassName={styles.selectedTab} selectedClassName={styles.selectedTab}
> >
Files {translate('FilesTotal', [bookFileCount])}
</Tab> </Tab>
{ {
@@ -335,6 +340,7 @@ BookDetails.propTypes = {
ratings: PropTypes.object.isRequired, ratings: PropTypes.object.isRequired,
images: PropTypes.arrayOf(PropTypes.object).isRequired, images: PropTypes.arrayOf(PropTypes.object).isRequired,
links: PropTypes.arrayOf(PropTypes.object).isRequired, links: PropTypes.arrayOf(PropTypes.object).isRequired,
statistics: PropTypes.object.isRequired,
monitored: PropTypes.bool.isRequired, monitored: PropTypes.bool.isRequired,
shortDateFormat: PropTypes.string.isRequired, shortDateFormat: PropTypes.string.isRequired,
isSaving: PropTypes.bool.isRequired, isSaving: PropTypes.bool.isRequired,
@@ -69,16 +69,21 @@ function createMapStateToProps() {
const previousBook = sortedBooks[bookIndex - 1] || _.last(sortedBooks); const previousBook = sortedBooks[bookIndex - 1] || _.last(sortedBooks);
const nextBook = sortedBooks[bookIndex + 1] || _.first(sortedBooks); const nextBook = sortedBooks[bookIndex + 1] || _.first(sortedBooks);
const isRefreshingCommand = findCommand(commands, { name: commandNames.REFRESH_BOOK });
const isRefreshing = (
isCommandExecuting(isRefreshingCommand) &&
isRefreshingCommand.body.bookId === book.id
);
const isSearchingCommand = findCommand(commands, { name: commandNames.BOOK_SEARCH }); const isSearchingCommand = findCommand(commands, { name: commandNames.BOOK_SEARCH });
const isSearching = ( const isSearching = (
isCommandExecuting(isSearchingCommand) && isCommandExecuting(isSearchingCommand) &&
isSearchingCommand.body.bookIds.indexOf(book.id) > -1 isSearchingCommand.body.bookIds.indexOf(book.id) > -1
); );
const isRenamingFiles = isCommandExecuting(findCommand(commands, { name: commandNames.RENAME_FILES, authorId: author.id }));
const isRefreshingCommand = findCommand(commands, { name: commandNames.REFRESH_BOOK }); const isRenamingAuthorCommand = findCommand(commands, { name: commandNames.RENAME_AUTHOR });
const isRefreshing = ( const isRenamingAuthor = (
isCommandExecuting(isRefreshingCommand) && isCommandExecuting(isRenamingAuthorCommand) &&
isRefreshingCommand.body.bookId === book.id isRenamingAuthorCommand.body.authorIds.indexOf(author.id) > -1
); );
const isFetching = isBookFilesFetching || editions.isFetching; const isFetching = isBookFilesFetching || editions.isFetching;
@@ -90,6 +95,8 @@ function createMapStateToProps() {
author, author,
isRefreshing, isRefreshing,
isSearching, isSearching,
isRenamingFiles,
isRenamingAuthor,
isFetching, isFetching,
isPopulated, isPopulated,
bookFilesError, bookFilesError,
@@ -125,9 +132,27 @@ class BookDetailsConnector extends Component {
} }
componentDidUpdate(prevProps) { componentDidUpdate(prevProps) {
if (prevProps.id !== this.props.id || const {
id,
anyReleaseOk,
isRenamingFiles,
isRenamingAuthor
} = this.props;
if (
(prevProps.isRenamingFiles && !isRenamingFiles) ||
(prevProps.isRenamingAuthor && !isRenamingAuthor) ||
!_.isEqual(getMonitoredEditions(prevProps), getMonitoredEditions(this.props)) || !_.isEqual(getMonitoredEditions(prevProps), getMonitoredEditions(this.props)) ||
(prevProps.anyReleaseOk === false && this.props.anyReleaseOk === true)) { (prevProps.anyReleaseOk === false && anyReleaseOk === true)
) {
this.unpopulate();
this.populate();
}
// If the id has changed we need to clear the book
// files and fetch from the server.
if (prevProps.id !== id) {
this.unpopulate(); this.unpopulate();
this.populate(); this.populate();
} }
@@ -197,6 +222,8 @@ class BookDetailsConnector extends Component {
BookDetailsConnector.propTypes = { BookDetailsConnector.propTypes = {
id: PropTypes.number, id: PropTypes.number,
anyReleaseOk: PropTypes.bool, anyReleaseOk: PropTypes.bool,
isRenamingFiles: PropTypes.bool.isRequired,
isRenamingAuthor: PropTypes.bool.isRequired,
isBookFetching: PropTypes.bool, isBookFetching: PropTypes.bool,
isBookPopulated: PropTypes.bool, isBookPopulated: PropTypes.bool,
titleSlug: PropTypes.string.isRequired, titleSlug: PropTypes.string.isRequired,
@@ -117,8 +117,9 @@
} }
.title { .title {
font-weight: 300;
font-size: 30px; font-size: 30px;
line-height: 50px; line-height: 30px;
} }
} }
@@ -21,12 +21,7 @@ const defaultFontSize = parseInt(fonts.defaultFontSize);
const lineHeight = parseFloat(fonts.lineHeight); const lineHeight = parseFloat(fonts.lineHeight);
function getFanartUrl(images) { function getFanartUrl(images) {
const fanartImage = images.find((x) => x.coverType === 'fanart'); return images.find((x) => x.coverType === 'fanart')?.url;
if (fanartImage) {
// Remove protocol
return fanartImage.url.replace(/^https?:/, '');
}
} }
class BookDetailsHeader extends Component { class BookDetailsHeader extends Component {
@@ -16,8 +16,8 @@ function BookIndexProgressBar(props) {
detailedProgressBar detailedProgressBar
} = props; } = props;
const progress = bookCount ? bookFileCount / totalBookCount * 100 : 0; const progress = bookFileCount && bookCount ? (totalBookCount / bookCount) * 100 : 0;
const text = `${bookFileCount} / ${bookCount}`; const text = `${bookFileCount ? bookCount : 0} / ${totalBookCount}`;
return ( return (
<ProgressBar <ProgressBar
@@ -28,7 +28,11 @@ function BookIndexProgressBar(props) {
size={detailedProgressBar ? sizes.MEDIUM : sizes.SMALL} size={detailedProgressBar ? sizes.MEDIUM : sizes.SMALL}
showText={detailedProgressBar} showText={detailedProgressBar}
text={text} text={text}
title={translate('BookFileCountBookCountTotalTotalBookCountInterp', [bookFileCount, bookCount, totalBookCount])} title={translate('BookProgressBarText', {
bookCount: bookFileCount ? bookCount : 0,
bookFileCount,
totalBookCount
})}
width={posterWidth} width={posterWidth}
/> />
); );
@@ -229,7 +229,6 @@ class BookIndexRow extends Component {
className={styles[name]} className={styles[name]}
> >
{bookFileCount} {bookFileCount}
</VirtualTableRowCell> </VirtualTableRowCell>
); );
} }
@@ -0,0 +1,9 @@
.container {
border: 1px solid var(--borderColor);
border-radius: 4px;
background-color: var(--inputBackgroundColor);
&:last-of-type {
margin-bottom: 0;
}
}
@@ -0,0 +1,7 @@
// This file is automatically generated.
// Please do not change this file!
interface CssExports {
'container': string;
}
export const cssExports: CssExports;
export default cssExports;
@@ -1,5 +1,6 @@
import React from 'react'; import React from 'react';
import BookFileEditorTableContentConnector from './BookFileEditorTableContentConnector'; import BookFileEditorTableContentConnector from './BookFileEditorTableContentConnector';
import styles from './BookFileEditorTable.css';
function BookFileEditorTable(props) { function BookFileEditorTable(props) {
const { const {
@@ -7,9 +8,11 @@ function BookFileEditorTable(props) {
} = props; } = props;
return ( return (
<BookFileEditorTableContentConnector <div className={styles.container}>
{...otherProps} <BookFileEditorTableContentConnector
/> {...otherProps}
/>
</div>
); );
} }
@@ -1,6 +1,6 @@
.filesTable { .filesTable {
margin-bottom: 20px; margin: 10px;
padding-top: 15px; padding-top: 5px;
border: 1px solid var(--borderColor); border: 1px solid var(--borderColor);
border-top: 1px solid var(--borderColor); border-top: 1px solid var(--borderColor);
border-radius: 4px; border-radius: 4px;
@@ -13,9 +13,15 @@
.actions { .actions {
display: flex; display: flex;
margin-right: auto; margin: 10px;
} }
.selectInput { .selectInput {
margin-left: 10px; margin-left: 10px;
} }
.blankpad {
padding-top: 10px;
padding-bottom: 10px;
padding-left: 2em;
}
@@ -2,6 +2,7 @@
// Please do not change this file! // Please do not change this file!
interface CssExports { interface CssExports {
'actions': string; 'actions': string;
'blankpad': string;
'filesTable': string; 'filesTable': string;
'selectInput': string; 'selectInput': string;
} }
@@ -1,6 +1,7 @@
import _ from 'lodash'; import _ from 'lodash';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import React, { Component } from 'react'; import React, { Component } from 'react';
import Alert from 'Components/Alert';
import SelectInput from 'Components/Form/SelectInput'; import SelectInput from 'Components/Form/SelectInput';
import SpinnerButton from 'Components/Link/SpinnerButton'; import SpinnerButton from 'Components/Link/SpinnerButton';
import LoadingIndicator from 'Components/Loading/LoadingIndicator'; import LoadingIndicator from 'Components/Loading/LoadingIndicator';
@@ -120,7 +121,7 @@ class BookFileEditorTableContent extends Component {
const hasSelectedFiles = this.getSelectedIds().length > 0; const hasSelectedFiles = this.getSelectedIds().length > 0;
return ( return (
<> <div>
{ {
isFetching && !isPopulated ? isFetching && !isPopulated ?
<LoadingIndicator /> : <LoadingIndicator /> :
@@ -129,13 +130,13 @@ class BookFileEditorTableContent extends Component {
{ {
!isFetching && error ? !isFetching && error ?
<div>{error}</div> : <Alert kind={kinds.DANGER}>{error}</Alert> :
null null
} }
{ {
isPopulated && !items.length ? isPopulated && !items.length ?
<div> <div className={styles.blankpad}>
No book files to manage. No book files to manage.
</div> : </div> :
null null
@@ -173,26 +174,30 @@ class BookFileEditorTableContent extends Component {
null null
} }
<div className={styles.actions}> {
<SpinnerButton isPopulated && items.length ? (
kind={kinds.DANGER} <div className={styles.actions}>
isSpinning={isDeleting} <SpinnerButton
isDisabled={!hasSelectedFiles} kind={kinds.DANGER}
onPress={this.onDeletePress} isSpinning={isDeleting}
> isDisabled={!hasSelectedFiles}
Delete onPress={this.onDeletePress}
</SpinnerButton> >
{translate('Delete')}
</SpinnerButton>
<div className={styles.selectInput}> <div className={styles.selectInput}>
<SelectInput <SelectInput
name="quality" name="quality"
value="selectQuality" value="selectQuality"
values={qualityOptions} values={qualityOptions}
isDisabled={!hasSelectedFiles} isDisabled={!hasSelectedFiles}
onChange={this.onQualityChange} onChange={this.onQualityChange}
/> />
</div> </div>
</div> </div>
) : null
}
<ConfirmModal <ConfirmModal
isOpen={isConfirmDeleteModalOpen} isOpen={isConfirmDeleteModalOpen}
@@ -203,7 +208,7 @@ class BookFileEditorTableContent extends Component {
onConfirm={this.onConfirmDelete} onConfirm={this.onConfirmDelete}
onCancel={this.onConfirmDeleteModalClose} onCancel={this.onConfirmDeleteModalClose}
/> />
</> </div>
); );
} }
} }
+11 -6
View File
@@ -27,14 +27,15 @@ class BookshelfBook extends Component {
title, title,
disambiguation, disambiguation,
monitored, monitored,
statistics, statistics = {},
isSaving isSaving
} = this.props; } = this.props;
const { const {
bookFileCount, bookCount = 0,
totalBookCount, bookFileCount = 0,
percentOfBooks totalBookCount = 0,
percentOfBooks = 0
} = statistics; } = statistics;
return ( return (
@@ -59,10 +60,14 @@ class BookshelfBook extends Component {
percentOfBooks < 100 && monitored && styles.missingWanted, percentOfBooks < 100 && monitored && styles.missingWanted,
percentOfBooks === 100 && styles.allBooks percentOfBooks === 100 && styles.allBooks
)} )}
title={translate('BookFileCounttotalBookCountBooksDownloadedInterp', [bookFileCount, totalBookCount])} title={translate('BookProgressBarText', {
bookCount: bookFileCount ? bookCount : 0,
bookFileCount,
totalBookCount
})}
> >
{ {
totalBookCount === 0 ? '0/0' : `${bookFileCount}/${totalBookCount}` totalBookCount === 0 ? '0/0' : `${bookFileCount ? bookCount : 0}/${totalBookCount}`
} }
</div> </div>
</div> </div>
+1 -1
View File
@@ -47,7 +47,7 @@ class CalendarConnector extends Component {
gotoCalendarToday gotoCalendarToday
} = this.props; } = this.props;
registerPagePopulator(this.repopulate); registerPagePopulator(this.repopulate, ['bookFileUpdated', 'bookFileDeleted']);
if (useCurrentPage) { if (useCurrentPage) {
fetchCalendar(); fetchCalendar();
@@ -1,9 +1,7 @@
.description {
line-height: $lineHeight;
}
.description { .description {
margin-left: 0; margin-left: 0;
line-height: $lineHeight;
overflow-wrap: break-word;
} }
@media (min-width: 768px) { @media (min-width: 768px) {
@@ -29,22 +29,24 @@ function CustomFiltersModalContent(props) {
<ModalBody> <ModalBody>
{ {
customFilters.map((customFilter) => { customFilters
return ( .sort((a, b) => a.label.localeCompare(b.label))
<CustomFilter .map((customFilter) => {
key={customFilter.id} return (
id={customFilter.id} <CustomFilter
label={customFilter.label} key={customFilter.id}
filters={customFilter.filters} id={customFilter.id}
selectedFilterKey={selectedFilterKey} label={customFilter.label}
isDeleting={isDeleting} filters={customFilter.filters}
deleteError={deleteError} selectedFilterKey={selectedFilterKey}
dispatchSetFilter={dispatchSetFilter} isDeleting={isDeleting}
dispatchDeleteCustomFilter={dispatchDeleteCustomFilter} deleteError={deleteError}
onEditPress={onEditCustomFilter} dispatchSetFilter={dispatchSetFilter}
/> dispatchDeleteCustomFilter={dispatchDeleteCustomFilter}
); onEditPress={onEditCustomFilter}
}) />
);
})
} }
<div className={styles.addButtonContainer}> <div className={styles.addButtonContainer}>
@@ -25,7 +25,8 @@ function createMapStateToProps() {
const values = _.map(filteredItems.sort(sortByName), (downloadClient) => { const values = _.map(filteredItems.sort(sortByName), (downloadClient) => {
return { return {
key: downloadClient.id, key: downloadClient.id,
value: downloadClient.name value: downloadClient.name,
hint: `(${downloadClient.id})`
}; };
}); });
@@ -273,6 +273,7 @@ FormInputGroup.propTypes = {
name: PropTypes.string.isRequired, name: PropTypes.string.isRequired,
value: PropTypes.any, value: PropTypes.any,
values: PropTypes.arrayOf(PropTypes.any), values: PropTypes.arrayOf(PropTypes.any),
isDisabled: PropTypes.bool,
type: PropTypes.string.isRequired, type: PropTypes.string.isRequired,
kind: PropTypes.oneOf(kinds.all), kind: PropTypes.oneOf(kinds.all),
min: PropTypes.number, min: PropTypes.number,
+3 -1
View File
@@ -2,8 +2,10 @@
display: flex; display: flex;
justify-content: flex-end; justify-content: flex-end;
margin-right: $formLabelRightMarginWidth; margin-right: $formLabelRightMarginWidth;
padding-top: 8px;
min-height: 35px;
text-align: end;
font-weight: bold; font-weight: bold;
line-height: 35px;
} }
.hasError { .hasError {
@@ -39,18 +39,26 @@ class FilterMenuContent extends Component {
} }
{ {
customFilters.map((filter) => { customFilters.length > 0 ?
return ( <MenuItemSeparator /> :
<FilterMenuItem null
key={filter.id} }
filterKey={filter.id}
selectedFilterKey={selectedFilterKey} {
onPress={onFilterSelect} customFilters
> .sort((a, b) => a.label.localeCompare(b.label))
{filter.label} .map((filter) => {
</FilterMenuItem> return (
); <FilterMenuItem
}) key={filter.id}
filterKey={filter.id}
selectedFilterKey={selectedFilterKey}
onPress={onFilterSelect}
>
{filter.label}
</FilterMenuItem>
);
})
} }
{ {
@@ -202,6 +202,8 @@ class SignalRConnector extends Component {
this.props.dispatchUpdateItem({ section, ...body.resource }); this.props.dispatchUpdateItem({ section, ...body.resource });
} else if (body.action === 'deleted') { } else if (body.action === 'deleted') {
this.props.dispatchRemoveItem({ section, id: body.resource.id }); this.props.dispatchRemoveItem({ section, id: body.resource.id });
repopulatePage('bookFileDeleted');
} }
// Repopulate the page to handle recently imported file // Repopulate the page to handle recently imported file
@@ -15,5 +15,5 @@
"start_url": "../../../../", "start_url": "../../../../",
"theme_color": "#3a3f51", "theme_color": "#3a3f51",
"background_color": "#3a3f51", "background_color": "#3a3f51",
"display": "standalone" "display": "minimal-ui"
} }
+120
View File
@@ -0,0 +1,120 @@
import createAjaxRequest from 'Utilities/createAjaxRequest';
// This file contains some helpers for power users in a browser console
let hasWarned = false;
function checkActivationWarning() {
if (!hasWarned) {
console.log('Activated ReadarrApi console helpers.');
console.warn('Be warned: There will be no further confirmation checks.');
hasWarned = true;
}
}
function attachAsyncActions(promise) {
promise.filter = function() {
const args = arguments;
const res = this.then((d) => d.filter(...args));
attachAsyncActions(res);
return res;
};
promise.map = function() {
const args = arguments;
const res = this.then((d) => d.map(...args));
attachAsyncActions(res);
return res;
};
promise.all = function() {
const res = this.then((d) => Promise.all(d));
attachAsyncActions(res);
return res;
};
promise.forEach = function(action) {
const res = this.then((d) => Promise.all(d.map(action)));
attachAsyncActions(res);
return res;
};
}
class ResourceApi {
constructor(api, url) {
this.api = api;
this.url = url;
}
single(id) {
return this.api.fetch(`${this.url}/${id}`);
}
all() {
return this.api.fetch(this.url);
}
filter(pred) {
return this.all().filter(pred);
}
update(resource) {
return this.api.fetch(`${this.url}/${resource.id}`, { method: 'PUT', data: resource });
}
delete(resource) {
if (typeof resource === 'object' && resource !== null && resource.id) {
resource = resource.id;
}
if (!resource || !Number.isInteger(resource)) {
throw Error('Invalid resource', resource);
}
return this.api.fetch(`${this.url}/${resource}`, { method: 'DELETE' });
}
fetch(url, options) {
return this.api.fetch(`${this.url}${url}`, options);
}
}
class ConsoleApi {
constructor() {
this.author = new ResourceApi(this, '/author');
}
resource(url) {
return new ResourceApi(this, url);
}
fetch(url, options) {
checkActivationWarning();
options = options || {};
const req = {
url,
method: options.method || 'GET'
};
if (options.data) {
req.dataType = 'json';
req.data = JSON.stringify(options.data);
}
const promise = createAjaxRequest(req).request;
promise.fail((xhr) => {
console.error(`Failed to fetch ${url}`, xhr);
});
attachAsyncActions(promise);
return promise;
}
}
window.ReadarrApi = new ConsoleApi();
export default ConsoleApi;
@@ -29,7 +29,7 @@ class SelectAuthorModalContent extends Component {
// Listeners // Listeners
onFilterChange = ({ value }) => { onFilterChange = ({ value }) => {
this.setState({ filter: value.toLowerCase() }); this.setState({ filter: value });
}; };
// //
@@ -43,6 +43,7 @@ class SelectAuthorModalContent extends Component {
} = this.props; } = this.props;
const filter = this.state.filter; const filter = this.state.filter;
const filterLower = filter.toLowerCase();
return ( return (
<ModalContent onModalClose={onModalClose}> <ModalContent onModalClose={onModalClose}>
@@ -66,7 +67,7 @@ class SelectAuthorModalContent extends Component {
<Scroller className={styles.scroller}> <Scroller className={styles.scroller}>
{ {
items.map((item) => { items.map((item) => {
return item.authorName.toLowerCase().includes(filter) ? return item.authorName.toLowerCase().includes(filterLower) ?
( (
<SelectAuthorRow <SelectAuthorRow
key={item.id} key={item.id}
@@ -52,7 +52,7 @@ class SelectBookModalContent extends Component {
// Listeners // Listeners
onFilterChange = ({ value }) => { onFilterChange = ({ value }) => {
this.setState({ filter: value.toLowerCase() }); this.setState({ filter: value });
}; };
// //
@@ -68,6 +68,7 @@ class SelectBookModalContent extends Component {
} = this.props; } = this.props;
const filter = this.state.filter; const filter = this.state.filter;
const filterLower = filter.toLowerCase();
return ( return (
<ModalContent onModalClose={onModalClose}> <ModalContent onModalClose={onModalClose}>
@@ -101,7 +102,7 @@ class SelectBookModalContent extends Component {
<TableBody> <TableBody>
{ {
items.map((item) => { items.map((item) => {
return item.title.toLowerCase().includes(filter) ? return item.title.toLowerCase().includes(filterLower) ?
( (
<SelectBookRow <SelectBookRow
key={item.id} key={item.id}
@@ -295,11 +295,11 @@ class InteractiveImportModalContent extends Component {
const errorMessage = getErrorMessage(error, 'Unable to load manual import items'); const errorMessage = getErrorMessage(error, 'Unable to load manual import items');
const bulkSelectOptions = [ const bulkSelectOptions = [
{ key: SELECT, value: 'Select...', disabled: true }, { key: SELECT, value: translate('SelectDropdown'), disabled: true },
{ key: BOOK, value: 'Select Book' }, { key: BOOK, value: translate('SelectBook') },
{ key: EDITION, value: 'Select Edition' }, { key: EDITION, value: translate('SelectEdition') },
{ key: QUALITY, value: 'Select Quality' }, { key: QUALITY, value: translate('SelectQuality') },
{ key: RELEASE_GROUP, value: 'Select ReleaseGroup' } { key: RELEASE_GROUP, value: translate('SelectReleaseGroup') }
]; ];
if (allowAuthorChange) { if (allowAuthorChange) {
@@ -28,6 +28,10 @@
text-align: center; text-align: center;
} }
.quality {
white-space: nowrap;
}
.customFormatScore { .customFormatScore {
composes: cell from '~Components/Table/Cells/TableRowCell.css'; composes: cell from '~Components/Table/Cells/TableRowCell.css';
@@ -178,7 +178,7 @@ class InteractiveSearchRow extends Component {
</TableRowCell> </TableRowCell>
<TableRowCell className={styles.quality}> <TableRowCell className={styles.quality}>
<BookQuality quality={quality} /> <BookQuality quality={quality} showRevision={true} />
</TableRowCell> </TableRowCell>
<TableRowCell className={styles.customFormatScore}> <TableRowCell className={styles.customFormatScore}>
@@ -49,7 +49,7 @@ function EditSpecificationModalContent(props) {
{...otherProps} {...otherProps}
> >
{ {
fields && fields.some((x) => x.label === 'Regular Expression') && fields && fields.some((x) => x.label === translate('CustomFormatsSpecificationRegularExpression')) &&
<Alert kind={kinds.INFO}> <Alert kind={kinds.INFO}>
<div> <div>
<div dangerouslySetInnerHTML={{ __html: 'This condition matches using Regular Expressions. Note that the characters <code>\\^$.|?*+()[{</code> have special meanings and need escaping with a <code>\\</code>' }} /> <div dangerouslySetInnerHTML={{ __html: 'This condition matches using Regular Expressions. Note that the characters <code>\\^$.|?*+()[{</code> have special meanings and need escaping with a <code>\\</code>' }} />
@@ -14,9 +14,11 @@ import Table from 'Components/Table/Table';
import TableBody from 'Components/Table/TableBody'; import TableBody from 'Components/Table/TableBody';
import useSelectState from 'Helpers/Hooks/useSelectState'; import useSelectState from 'Helpers/Hooks/useSelectState';
import { kinds } from 'Helpers/Props'; import { kinds } from 'Helpers/Props';
import SortDirection from 'Helpers/Props/SortDirection';
import { import {
bulkDeleteDownloadClients, bulkDeleteDownloadClients,
bulkEditDownloadClients, bulkEditDownloadClients,
setManageDownloadClientsSort,
} from 'Store/Actions/settingsActions'; } from 'Store/Actions/settingsActions';
import createClientSideCollectionSelector from 'Store/Selectors/createClientSideCollectionSelector'; import createClientSideCollectionSelector from 'Store/Selectors/createClientSideCollectionSelector';
import { SelectStateInputProps } from 'typings/props'; import { SelectStateInputProps } from 'typings/props';
@@ -80,6 +82,8 @@ const COLUMNS = [
interface ManageDownloadClientsModalContentProps { interface ManageDownloadClientsModalContentProps {
onModalClose(): void; onModalClose(): void;
sortKey?: string;
sortDirection?: SortDirection;
} }
function ManageDownloadClientsModalContent( function ManageDownloadClientsModalContent(
@@ -94,6 +98,8 @@ function ManageDownloadClientsModalContent(
isSaving, isSaving,
error, error,
items, items,
sortKey,
sortDirection,
}: DownloadClientAppState = useSelector( }: DownloadClientAppState = useSelector(
createClientSideCollectionSelector('settings.downloadClients') createClientSideCollectionSelector('settings.downloadClients')
); );
@@ -114,6 +120,13 @@ function ManageDownloadClientsModalContent(
const selectedCount = selectedIds.length; const selectedCount = selectedIds.length;
const onSortPress = useCallback(
(value: string) => {
dispatch(setManageDownloadClientsSort({ sortKey: value }));
},
[dispatch]
);
const onDeletePress = useCallback(() => { const onDeletePress = useCallback(() => {
setIsDeleteModalOpen(true); setIsDeleteModalOpen(true);
}, [setIsDeleteModalOpen]); }, [setIsDeleteModalOpen]);
@@ -219,6 +232,9 @@ function ManageDownloadClientsModalContent(
allSelected={allSelected} allSelected={allSelected}
allUnselected={allUnselected} allUnselected={allUnselected}
onSelectAllChange={onSelectAllChange} onSelectAllChange={onSelectAllChange}
sortKey={sortKey}
sortDirection={sortDirection}
onSortPress={onSortPress}
> >
<TableBody> <TableBody>
{items.map((item) => { {items.map((item) => {
@@ -61,8 +61,12 @@ function DownloadClientOptions(props) {
legend={translate('FailedDownloadHandling')} legend={translate('FailedDownloadHandling')}
> >
<Form> <Form>
<FormGroup size={sizes.MEDIUM}> <FormGroup
<FormLabel>{translate('RedownloadFailed')}</FormLabel> advancedSettings={advancedSettings}
isAdvanced={true}
size={sizes.MEDIUM}
>
<FormLabel>{translate('AutoRedownloadFailed')}</FormLabel>
<FormInputGroup <FormInputGroup
type={inputTypes.CHECK} type={inputTypes.CHECK}
@@ -72,7 +76,28 @@ function DownloadClientOptions(props) {
{...settings.autoRedownloadFailed} {...settings.autoRedownloadFailed}
/> />
</FormGroup> </FormGroup>
{
settings.autoRedownloadFailed.value ?
<FormGroup
advancedSettings={advancedSettings}
isAdvanced={true}
size={sizes.MEDIUM}
>
<FormLabel>{translate('AutoRedownloadFailedFromInteractiveSearch')}</FormLabel>
<FormInputGroup
type={inputTypes.CHECK}
name="autoRedownloadFailedFromInteractiveSearch"
helpText={translate('AutoRedownloadFailedFromInteractiveSearchHelpText')}
onChange={onInputChange}
{...settings.autoRedownloadFailedFromInteractiveSearch}
/>
</FormGroup> :
null
}
</Form> </Form>
<Alert kind={kinds.INFO}> <Alert kind={kinds.INFO}>
{translate('RemoveDownloadsAlert')} {translate('RemoveDownloadsAlert')}
</Alert> </Alert>
@@ -8,11 +8,13 @@
} }
.name { .name {
flex: 1 0 300px; @add-mixin truncate;
flex: 0 1 600px;
} }
.foreignId { .foreignId {
flex: 0 0 200px; flex: 0 0 100px;
} }
.actions { .actions {
@@ -4,12 +4,12 @@
font-weight: bold; font-weight: bold;
} }
.foreignId { .name {
flex: 0 0 200px; flex: 0 1 600px;
} }
.name { .foreignId {
flex: 1 0 300px; flex: 0 0 100px;
} }
.addImportListExclusion { .addImportListExclusion {
@@ -14,9 +14,11 @@ import Table from 'Components/Table/Table';
import TableBody from 'Components/Table/TableBody'; import TableBody from 'Components/Table/TableBody';
import useSelectState from 'Helpers/Hooks/useSelectState'; import useSelectState from 'Helpers/Hooks/useSelectState';
import { kinds } from 'Helpers/Props'; import { kinds } from 'Helpers/Props';
import SortDirection from 'Helpers/Props/SortDirection';
import { import {
bulkDeleteIndexers, bulkDeleteIndexers,
bulkEditIndexers, bulkEditIndexers,
setManageIndexersSort,
} from 'Store/Actions/settingsActions'; } from 'Store/Actions/settingsActions';
import createClientSideCollectionSelector from 'Store/Selectors/createClientSideCollectionSelector'; import createClientSideCollectionSelector from 'Store/Selectors/createClientSideCollectionSelector';
import { SelectStateInputProps } from 'typings/props'; import { SelectStateInputProps } from 'typings/props';
@@ -80,6 +82,8 @@ const COLUMNS = [
interface ManageIndexersModalContentProps { interface ManageIndexersModalContentProps {
onModalClose(): void; onModalClose(): void;
sortKey?: string;
sortDirection?: SortDirection;
} }
function ManageIndexersModalContent(props: ManageIndexersModalContentProps) { function ManageIndexersModalContent(props: ManageIndexersModalContentProps) {
@@ -92,6 +96,8 @@ function ManageIndexersModalContent(props: ManageIndexersModalContentProps) {
isSaving, isSaving,
error, error,
items, items,
sortKey,
sortDirection,
}: IndexerAppState = useSelector( }: IndexerAppState = useSelector(
createClientSideCollectionSelector('settings.indexers') createClientSideCollectionSelector('settings.indexers')
); );
@@ -112,6 +118,13 @@ function ManageIndexersModalContent(props: ManageIndexersModalContentProps) {
const selectedCount = selectedIds.length; const selectedCount = selectedIds.length;
const onSortPress = useCallback(
(value: string) => {
dispatch(setManageIndexersSort({ sortKey: value }));
},
[dispatch]
);
const onDeletePress = useCallback(() => { const onDeletePress = useCallback(() => {
setIsDeleteModalOpen(true); setIsDeleteModalOpen(true);
}, [setIsDeleteModalOpen]); }, [setIsDeleteModalOpen]);
@@ -214,6 +227,9 @@ function ManageIndexersModalContent(props: ManageIndexersModalContentProps) {
allSelected={allSelected} allSelected={allSelected}
allUnselected={allUnselected} allUnselected={allUnselected}
onSelectAllChange={onSelectAllChange} onSelectAllChange={onSelectAllChange}
sortKey={sortKey}
sortDirection={sortDirection}
onSortPress={onSortPress}
> >
<TableBody> <TableBody>
{items.map((item) => { {items.map((item) => {
@@ -212,26 +212,24 @@ class MediaManagement extends Component {
</FormGroup> </FormGroup>
{ {
settings.importExtraFiles.value && settings.importExtraFiles.value ?
<FormGroup <FormGroup
advancedSettings={advancedSettings} advancedSettings={advancedSettings}
isAdvanced={true} isAdvanced={true}
> >
<FormLabel> <FormLabel>{translate('ImportExtraFiles')}</FormLabel>
{translate('ImportExtraFiles')}
</FormLabel>
<FormInputGroup <FormInputGroup
type={inputTypes.TEXT} type={inputTypes.TEXT}
name="extraFileExtensions" name="extraFileExtensions"
helpTexts={[ helpTexts={[
translate('ExtraFileExtensionsHelpTexts1'), translate('ExtraFileExtensionsHelpText'),
translate('ExtraFileExtensionsHelpTexts2') translate('ExtraFileExtensionsHelpTextsExamples')
]} ]}
onChange={onInputChange} onChange={onInputChange}
{...settings.extraFileExtensions} {...settings.extraFileExtensions}
/> />
</FormGroup> </FormGroup> : null
} }
</FieldSet> </FieldSet>
} }
@@ -60,6 +60,7 @@ class Notification extends Component {
onReleaseImport, onReleaseImport,
onUpgrade, onUpgrade,
onRename, onRename,
onAuthorAdded,
onAuthorDelete, onAuthorDelete,
onBookDelete, onBookDelete,
onBookFileDelete, onBookFileDelete,
@@ -73,6 +74,7 @@ class Notification extends Component {
supportsOnReleaseImport, supportsOnReleaseImport,
supportsOnUpgrade, supportsOnUpgrade,
supportsOnRename, supportsOnRename,
supportsOnAuthorAdded,
supportsOnAuthorDelete, supportsOnAuthorDelete,
supportsOnBookDelete, supportsOnBookDelete,
supportsOnBookFileDelete, supportsOnBookFileDelete,
@@ -136,6 +138,14 @@ class Notification extends Component {
null null
} }
{
supportsOnAuthorAdded && onAuthorAdded ?
<Label kind={kinds.SUCCESS}>
{translate('OnAuthorAdded')}
</Label> :
null
}
{ {
supportsOnAuthorDelete && onAuthorDelete ? supportsOnAuthorDelete && onAuthorDelete ?
<Label kind={kinds.SUCCESS}> <Label kind={kinds.SUCCESS}>
@@ -244,6 +254,7 @@ Notification.propTypes = {
onReleaseImport: PropTypes.bool.isRequired, onReleaseImport: PropTypes.bool.isRequired,
onUpgrade: PropTypes.bool.isRequired, onUpgrade: PropTypes.bool.isRequired,
onRename: PropTypes.bool.isRequired, onRename: PropTypes.bool.isRequired,
onAuthorAdded: PropTypes.bool.isRequired,
onAuthorDelete: PropTypes.bool.isRequired, onAuthorDelete: PropTypes.bool.isRequired,
onBookDelete: PropTypes.bool.isRequired, onBookDelete: PropTypes.bool.isRequired,
onBookFileDelete: PropTypes.bool.isRequired, onBookFileDelete: PropTypes.bool.isRequired,
@@ -257,6 +268,7 @@ Notification.propTypes = {
supportsOnReleaseImport: PropTypes.bool.isRequired, supportsOnReleaseImport: PropTypes.bool.isRequired,
supportsOnUpgrade: PropTypes.bool.isRequired, supportsOnUpgrade: PropTypes.bool.isRequired,
supportsOnRename: PropTypes.bool.isRequired, supportsOnRename: PropTypes.bool.isRequired,
supportsOnAuthorAdded: PropTypes.bool.isRequired,
supportsOnAuthorDelete: PropTypes.bool.isRequired, supportsOnAuthorDelete: PropTypes.bool.isRequired,
supportsOnBookDelete: PropTypes.bool.isRequired, supportsOnBookDelete: PropTypes.bool.isRequired,
supportsOnBookFileDelete: PropTypes.bool.isRequired, supportsOnBookFileDelete: PropTypes.bool.isRequired,
@@ -19,6 +19,7 @@ function NotificationEventItems(props) {
onReleaseImport, onReleaseImport,
onUpgrade, onUpgrade,
onRename, onRename,
onAuthorAdded,
onAuthorDelete, onAuthorDelete,
onBookDelete, onBookDelete,
onBookFileDelete, onBookFileDelete,
@@ -32,6 +33,7 @@ function NotificationEventItems(props) {
supportsOnReleaseImport, supportsOnReleaseImport,
supportsOnUpgrade, supportsOnUpgrade,
supportsOnRename, supportsOnRename,
supportsOnAuthorAdded,
supportsOnAuthorDelete, supportsOnAuthorDelete,
supportsOnBookDelete, supportsOnBookDelete,
supportsOnBookFileDelete, supportsOnBookFileDelete,
@@ -123,6 +125,17 @@ function NotificationEventItems(props) {
/> />
</div> </div>
<div>
<FormInputGroup
type={inputTypes.CHECK}
name="onAuthorAdded"
helpText={translate('OnAuthorAddedHelpText')}
isDisabled={!supportsOnAuthorAdded.value}
{...onAuthorAdded}
onChange={onInputChange}
/>
</div>
<div> <div>
<FormInputGroup <FormInputGroup
type={inputTypes.CHECK} type={inputTypes.CHECK}
+10 -4
View File
@@ -14,12 +14,17 @@ function PendingChangesModal(props) {
isOpen, isOpen,
onConfirm, onConfirm,
onCancel, onCancel,
bindShortcut bindShortcut,
unbindShortcut
} = props; } = props;
useEffect(() => { useEffect(() => {
bindShortcut('enter', onConfirm); if (isOpen) {
}, [bindShortcut, onConfirm]); bindShortcut('enter', onConfirm);
return () => unbindShortcut('enter', onConfirm);
}
}, [bindShortcut, unbindShortcut, isOpen, onConfirm]);
return ( return (
<Modal <Modal
@@ -60,7 +65,8 @@ PendingChangesModal.propTypes = {
kind: PropTypes.oneOf(kinds.all), kind: PropTypes.oneOf(kinds.all),
onConfirm: PropTypes.func.isRequired, onConfirm: PropTypes.func.isRequired,
onCancel: PropTypes.func.isRequired, onCancel: PropTypes.func.isRequired,
bindShortcut: PropTypes.func.isRequired bindShortcut: PropTypes.func.isRequired,
unbindShortcut: PropTypes.func.isRequired
}; };
PendingChangesModal.defaultProps = { PendingChangesModal.defaultProps = {
+5 -1
View File
@@ -3,7 +3,7 @@ import React, { Component } from 'react';
import { connect } from 'react-redux'; import { connect } from 'react-redux';
import { createSelector } from 'reselect'; import { createSelector } from 'reselect';
import { fetchDelayProfiles, fetchDownloadClients, fetchImportLists, fetchIndexers, fetchNotifications, fetchReleaseProfiles } from 'Store/Actions/settingsActions'; import { fetchDelayProfiles, fetchDownloadClients, fetchImportLists, fetchIndexers, fetchNotifications, fetchReleaseProfiles } from 'Store/Actions/settingsActions';
import { fetchTagDetails } from 'Store/Actions/tagActions'; import { fetchTagDetails, fetchTags } from 'Store/Actions/tagActions';
import Tags from './Tags'; import Tags from './Tags';
function createMapStateToProps() { function createMapStateToProps() {
@@ -25,6 +25,7 @@ function createMapStateToProps() {
} }
const mapDispatchToProps = { const mapDispatchToProps = {
dispatchFetchTags: fetchTags,
dispatchFetchTagDetails: fetchTagDetails, dispatchFetchTagDetails: fetchTagDetails,
dispatchFetchDelayProfiles: fetchDelayProfiles, dispatchFetchDelayProfiles: fetchDelayProfiles,
dispatchFetchImportLists: fetchImportLists, dispatchFetchImportLists: fetchImportLists,
@@ -41,6 +42,7 @@ class MetadatasConnector extends Component {
componentDidMount() { componentDidMount() {
const { const {
dispatchFetchTags,
dispatchFetchTagDetails, dispatchFetchTagDetails,
dispatchFetchDelayProfiles, dispatchFetchDelayProfiles,
dispatchFetchImportLists, dispatchFetchImportLists,
@@ -50,6 +52,7 @@ class MetadatasConnector extends Component {
dispatchFetchDownloadClients dispatchFetchDownloadClients
} = this.props; } = this.props;
dispatchFetchTags();
dispatchFetchTagDetails(); dispatchFetchTagDetails();
dispatchFetchDelayProfiles(); dispatchFetchDelayProfiles();
dispatchFetchImportLists(); dispatchFetchImportLists();
@@ -72,6 +75,7 @@ class MetadatasConnector extends Component {
} }
MetadatasConnector.propTypes = { MetadatasConnector.propTypes = {
dispatchFetchTags: PropTypes.func.isRequired,
dispatchFetchTagDetails: PropTypes.func.isRequired, dispatchFetchTagDetails: PropTypes.func.isRequired,
dispatchFetchDelayProfiles: PropTypes.func.isRequired, dispatchFetchDelayProfiles: PropTypes.func.isRequired,
dispatchFetchImportLists: PropTypes.func.isRequired, dispatchFetchImportLists: PropTypes.func.isRequired,
+17 -10
View File
@@ -21,19 +21,19 @@ export const firstDayOfWeekOptions = [
]; ];
export const weekColumnOptions = [ export const weekColumnOptions = [
{ key: 'ddd M/D', value: 'Tue 3/25' }, { key: 'ddd M/D', value: 'Tue 3/25', hint: 'ddd M/D' },
{ key: 'ddd MM/DD', value: 'Tue 03/25' }, { key: 'ddd MM/DD', value: 'Tue 03/25', hint: 'ddd MM/DD' },
{ key: 'ddd D/M', value: 'Tue 25/3' }, { key: 'ddd D/M', value: 'Tue 25/3', hint: 'ddd D/M' },
{ key: 'ddd DD/MM', value: 'Tue 25/03' } { key: 'ddd DD/MM', value: 'Tue 25/03', hint: 'ddd DD/MM' }
]; ];
const shortDateFormatOptions = [ const shortDateFormatOptions = [
{ key: 'MMM D YYYY', value: 'Mar 25 2014' }, { key: 'MMM D YYYY', value: 'Mar 25 2014', hint: 'MMM D YYYY' },
{ key: 'DD MMM YYYY', value: '25 Mar 2014' }, { key: 'DD MMM YYYY', value: '25 Mar 2014', hint: 'DD MMM YYYY' },
{ key: 'MM/D/YYYY', value: '03/25/2014' }, { key: 'MM/D/YYYY', value: '03/25/2014', hint: 'MM/D/YYYY' },
{ key: 'MM/DD/YYYY', value: '03/25/2014' }, { key: 'MM/DD/YYYY', value: '03/25/2014', hint: 'MM/DD/YYYY' },
{ key: 'DD/MM/YYYY', value: '25/03/2014' }, { key: 'DD/MM/YYYY', value: '25/03/2014', hint: 'DD/MM/YYYY' },
{ key: 'YYYY-MM-DD', value: '2014-03-25' } { key: 'YYYY-MM-DD', value: '2014-03-25', hint: 'YYYY-MM-DD' }
]; ];
const longDateFormatOptions = [ const longDateFormatOptions = [
@@ -223,6 +223,13 @@ class UISettings extends Component {
helpTextWarning={translate('UILanguageHelpTextWarning')} helpTextWarning={translate('UILanguageHelpTextWarning')}
onChange={onInputChange} onChange={onInputChange}
{...settings.uiLanguage} {...settings.uiLanguage}
errors={
languages.some((language) => language.key === settings.uiLanguage.value) ?
settings.uiLanguage.errors :
[
...settings.uiLanguage.errors,
{ message: translate('InvalidUILanguage') }
]}
/> />
</FormGroup> </FormGroup>
</FieldSet> </FieldSet>
@@ -1,4 +1,5 @@
import { createAction } from 'redux-actions'; import { createAction } from 'redux-actions';
import { sortDirections } from 'Helpers/Props';
import createBulkEditItemHandler from 'Store/Actions/Creators/createBulkEditItemHandler'; import createBulkEditItemHandler from 'Store/Actions/Creators/createBulkEditItemHandler';
import createBulkRemoveItemHandler from 'Store/Actions/Creators/createBulkRemoveItemHandler'; import createBulkRemoveItemHandler from 'Store/Actions/Creators/createBulkRemoveItemHandler';
import createFetchHandler from 'Store/Actions/Creators/createFetchHandler'; import createFetchHandler from 'Store/Actions/Creators/createFetchHandler';
@@ -7,6 +8,7 @@ import createRemoveItemHandler from 'Store/Actions/Creators/createRemoveItemHand
import createSaveProviderHandler, { createCancelSaveProviderHandler } from 'Store/Actions/Creators/createSaveProviderHandler'; import createSaveProviderHandler, { createCancelSaveProviderHandler } from 'Store/Actions/Creators/createSaveProviderHandler';
import createTestAllProvidersHandler from 'Store/Actions/Creators/createTestAllProvidersHandler'; import createTestAllProvidersHandler from 'Store/Actions/Creators/createTestAllProvidersHandler';
import createTestProviderHandler, { createCancelTestProviderHandler } from 'Store/Actions/Creators/createTestProviderHandler'; import createTestProviderHandler, { createCancelTestProviderHandler } from 'Store/Actions/Creators/createTestProviderHandler';
import createSetClientSideCollectionSortReducer from 'Store/Actions/Creators/Reducers/createSetClientSideCollectionSortReducer';
import createSetProviderFieldValueReducer from 'Store/Actions/Creators/Reducers/createSetProviderFieldValueReducer'; import createSetProviderFieldValueReducer from 'Store/Actions/Creators/Reducers/createSetProviderFieldValueReducer';
import createSetSettingValueReducer from 'Store/Actions/Creators/Reducers/createSetSettingValueReducer'; import createSetSettingValueReducer from 'Store/Actions/Creators/Reducers/createSetSettingValueReducer';
import { createThunk } from 'Store/thunks'; import { createThunk } from 'Store/thunks';
@@ -33,6 +35,7 @@ export const CANCEL_TEST_DOWNLOAD_CLIENT = 'settings/downloadClients/cancelTestD
export const TEST_ALL_DOWNLOAD_CLIENTS = 'settings/downloadClients/testAllDownloadClients'; export const TEST_ALL_DOWNLOAD_CLIENTS = 'settings/downloadClients/testAllDownloadClients';
export const BULK_EDIT_DOWNLOAD_CLIENTS = 'settings/downloadClients/bulkEditDownloadClients'; export const BULK_EDIT_DOWNLOAD_CLIENTS = 'settings/downloadClients/bulkEditDownloadClients';
export const BULK_DELETE_DOWNLOAD_CLIENTS = 'settings/downloadClients/bulkDeleteDownloadClients'; export const BULK_DELETE_DOWNLOAD_CLIENTS = 'settings/downloadClients/bulkDeleteDownloadClients';
export const SET_MANAGE_DOWNLOAD_CLIENTS_SORT = 'settings/downloadClients/setManageDownloadClientsSort';
// //
// Action Creators // Action Creators
@@ -49,6 +52,7 @@ export const cancelTestDownloadClient = createThunk(CANCEL_TEST_DOWNLOAD_CLIENT)
export const testAllDownloadClients = createThunk(TEST_ALL_DOWNLOAD_CLIENTS); export const testAllDownloadClients = createThunk(TEST_ALL_DOWNLOAD_CLIENTS);
export const bulkEditDownloadClients = createThunk(BULK_EDIT_DOWNLOAD_CLIENTS); export const bulkEditDownloadClients = createThunk(BULK_EDIT_DOWNLOAD_CLIENTS);
export const bulkDeleteDownloadClients = createThunk(BULK_DELETE_DOWNLOAD_CLIENTS); export const bulkDeleteDownloadClients = createThunk(BULK_DELETE_DOWNLOAD_CLIENTS);
export const setManageDownloadClientsSort = createAction(SET_MANAGE_DOWNLOAD_CLIENTS_SORT);
export const setDownloadClientValue = createAction(SET_DOWNLOAD_CLIENT_VALUE, (payload) => { export const setDownloadClientValue = createAction(SET_DOWNLOAD_CLIENT_VALUE, (payload) => {
return { return {
@@ -88,7 +92,14 @@ export default {
isTesting: false, isTesting: false,
isTestingAll: false, isTestingAll: false,
items: [], items: [],
pendingChanges: {} pendingChanges: {},
sortKey: 'name',
sortDirection: sortDirections.ASCENDING,
sortPredicates: {
name: function(item) {
return item.name.toLowerCase();
}
}
}, },
// //
@@ -121,7 +132,10 @@ export default {
return selectedSchema; return selectedSchema;
}); });
} },
[SET_MANAGE_DOWNLOAD_CLIENTS_SORT]: createSetClientSideCollectionSortReducer(section)
} }
}; };
@@ -1,4 +1,5 @@
import { createAction } from 'redux-actions'; import { createAction } from 'redux-actions';
import { sortDirections } from 'Helpers/Props';
import createBulkEditItemHandler from 'Store/Actions/Creators/createBulkEditItemHandler'; import createBulkEditItemHandler from 'Store/Actions/Creators/createBulkEditItemHandler';
import createBulkRemoveItemHandler from 'Store/Actions/Creators/createBulkRemoveItemHandler'; import createBulkRemoveItemHandler from 'Store/Actions/Creators/createBulkRemoveItemHandler';
import createFetchHandler from 'Store/Actions/Creators/createFetchHandler'; import createFetchHandler from 'Store/Actions/Creators/createFetchHandler';
@@ -7,6 +8,7 @@ import createRemoveItemHandler from 'Store/Actions/Creators/createRemoveItemHand
import createSaveProviderHandler, { createCancelSaveProviderHandler } from 'Store/Actions/Creators/createSaveProviderHandler'; import createSaveProviderHandler, { createCancelSaveProviderHandler } from 'Store/Actions/Creators/createSaveProviderHandler';
import createTestAllProvidersHandler from 'Store/Actions/Creators/createTestAllProvidersHandler'; import createTestAllProvidersHandler from 'Store/Actions/Creators/createTestAllProvidersHandler';
import createTestProviderHandler, { createCancelTestProviderHandler } from 'Store/Actions/Creators/createTestProviderHandler'; import createTestProviderHandler, { createCancelTestProviderHandler } from 'Store/Actions/Creators/createTestProviderHandler';
import createSetClientSideCollectionSortReducer from 'Store/Actions/Creators/Reducers/createSetClientSideCollectionSortReducer';
import createSetProviderFieldValueReducer from 'Store/Actions/Creators/Reducers/createSetProviderFieldValueReducer'; import createSetProviderFieldValueReducer from 'Store/Actions/Creators/Reducers/createSetProviderFieldValueReducer';
import createSetSettingValueReducer from 'Store/Actions/Creators/Reducers/createSetSettingValueReducer'; import createSetSettingValueReducer from 'Store/Actions/Creators/Reducers/createSetSettingValueReducer';
import { createThunk } from 'Store/thunks'; import { createThunk } from 'Store/thunks';
@@ -36,6 +38,7 @@ export const CANCEL_TEST_INDEXER = 'settings/indexers/cancelTestIndexer';
export const TEST_ALL_INDEXERS = 'settings/indexers/testAllIndexers'; export const TEST_ALL_INDEXERS = 'settings/indexers/testAllIndexers';
export const BULK_EDIT_INDEXERS = 'settings/indexers/bulkEditIndexers'; export const BULK_EDIT_INDEXERS = 'settings/indexers/bulkEditIndexers';
export const BULK_DELETE_INDEXERS = 'settings/indexers/bulkDeleteIndexers'; export const BULK_DELETE_INDEXERS = 'settings/indexers/bulkDeleteIndexers';
export const SET_MANAGE_INDEXERS_SORT = 'settings/indexers/setManageIndexersSort';
// //
// Action Creators // Action Creators
@@ -53,6 +56,7 @@ export const cancelTestIndexer = createThunk(CANCEL_TEST_INDEXER);
export const testAllIndexers = createThunk(TEST_ALL_INDEXERS); export const testAllIndexers = createThunk(TEST_ALL_INDEXERS);
export const bulkEditIndexers = createThunk(BULK_EDIT_INDEXERS); export const bulkEditIndexers = createThunk(BULK_EDIT_INDEXERS);
export const bulkDeleteIndexers = createThunk(BULK_DELETE_INDEXERS); export const bulkDeleteIndexers = createThunk(BULK_DELETE_INDEXERS);
export const setManageIndexersSort = createAction(SET_MANAGE_INDEXERS_SORT);
export const setIndexerValue = createAction(SET_INDEXER_VALUE, (payload) => { export const setIndexerValue = createAction(SET_INDEXER_VALUE, (payload) => {
return { return {
@@ -92,7 +96,14 @@ export default {
isTesting: false, isTesting: false,
isTestingAll: false, isTestingAll: false,
items: [], items: [],
pendingChanges: {} pendingChanges: {},
sortKey: 'name',
sortDirection: sortDirections.ASCENDING,
sortPredicates: {
name: function(item) {
return item.name.toLowerCase();
}
}
}, },
// //
@@ -140,7 +151,13 @@ export default {
delete selectedSchema.name; delete selectedSchema.name;
selectedSchema.fields = selectedSchema.fields.map((field) => { selectedSchema.fields = selectedSchema.fields.map((field) => {
return { ...field }; const newField = { ...field };
if (newField.privacy === 'apiKey' || newField.privacy === 'password') {
newField.value = '';
}
return newField;
}); });
newState.selectedSchema = selectedSchema; newState.selectedSchema = selectedSchema;
@@ -151,7 +168,10 @@ export default {
}; };
return updateSectionState(state, section, newState); return updateSectionState(state, section, newState);
} },
[SET_MANAGE_INDEXERS_SORT]: createSetClientSideCollectionSortReducer(section)
} }
}; };
@@ -106,6 +106,7 @@ export default {
selectedSchema.onReleaseImport = selectedSchema.supportsOnReleaseImport; selectedSchema.onReleaseImport = selectedSchema.supportsOnReleaseImport;
selectedSchema.onUpgrade = selectedSchema.supportsOnUpgrade; selectedSchema.onUpgrade = selectedSchema.supportsOnUpgrade;
selectedSchema.onRename = selectedSchema.supportsOnRename; selectedSchema.onRename = selectedSchema.supportsOnRename;
selectedSchema.onAuthorAdded = selectedSchema.supportsOnAuthorAdded;
selectedSchema.onAuthorDelete = selectedSchema.supportsOnAuthorDelete; selectedSchema.onAuthorDelete = selectedSchema.supportsOnAuthorDelete;
selectedSchema.onBookDelete = selectedSchema.supportsOnBookDelete; selectedSchema.onBookDelete = selectedSchema.supportsOnBookDelete;
selectedSchema.onBookFileDelete = selectedSchema.supportsOnBookFileDelete; selectedSchema.onBookFileDelete = selectedSchema.supportsOnBookFileDelete;
@@ -41,6 +41,14 @@ export const defaultState = {
}, },
columns: [ columns: [
{
name: 'select',
columnLabel: 'Select',
isSortable: false,
isVisible: true,
isModifiable: false,
isHidden: true
},
{ {
name: 'path', name: 'path',
label: 'Path', label: 'Path',
@@ -158,7 +158,7 @@ export const defaultState = {
bookFileCount: function(item) { bookFileCount: function(item) {
const { statistics = {} } = item; const { statistics = {} } = item;
return statistics.bookCount || 0; return statistics.bookFileCount || 0;
}, },
ratings: function(item) { ratings: function(item) {
@@ -84,11 +84,6 @@ export const defaultState = {
label: 'Source Title', label: 'Source Title',
isVisible: false isVisible: false
}, },
{
name: 'sourceTitle',
label: 'Source Title',
isVisible: false
},
{ {
name: 'customFormatScore', name: 'customFormatScore',
columnLabel: 'Custom Format Score', columnLabel: 'Custom Format Score',
@@ -5,6 +5,7 @@ import { sortDirections } from 'Helpers/Props';
import { createThunk, handleThunks } from 'Store/thunks'; import { createThunk, handleThunks } from 'Store/thunks';
import createAjaxRequest from 'Utilities/createAjaxRequest'; import createAjaxRequest from 'Utilities/createAjaxRequest';
import updateSectionState from 'Utilities/State/updateSectionState'; import updateSectionState from 'Utilities/State/updateSectionState';
import naturalExpansion from 'Utilities/String/naturalExpansion';
import { set, update, updateItem } from './baseActions'; import { set, update, updateItem } from './baseActions';
import createFetchHandler from './Creators/createFetchHandler'; import createFetchHandler from './Creators/createFetchHandler';
import createHandleActions from './Creators/createHandleActions'; import createHandleActions from './Creators/createHandleActions';
@@ -17,6 +18,7 @@ export const section = 'interactiveImport';
const booksSection = `${section}.books`; const booksSection = `${section}.books`;
const bookFilesSection = `${section}.bookFiles`; const bookFilesSection = `${section}.bookFiles`;
let abortCurrentFetchRequest = null;
let abortCurrentRequest = null; let abortCurrentRequest = null;
let currentIds = []; let currentIds = [];
@@ -32,15 +34,17 @@ export const defaultState = {
error: null, error: null,
items: [], items: [],
pendingChanges: {}, pendingChanges: {},
sortKey: 'quality', sortKey: 'path',
sortDirection: sortDirections.DESCENDING, sortDirection: sortDirections.ASCENDING,
secondarySortKey: 'path',
secondarySortDirection: sortDirections.ASCENDING,
recentFolders: [], recentFolders: [],
importMode: 'chooseImportMode', importMode: 'chooseImportMode',
sortPredicates: { sortPredicates: {
path: function(item, direction) { path: function(item, direction) {
const path = item.path; const path = item.path;
return path.toLowerCase(); return naturalExpansion(path.toLowerCase());
}, },
author: function(item, direction) { author: function(item, direction) {
@@ -74,6 +78,8 @@ export const defaultState = {
}; };
export const persistState = [ export const persistState = [
'interactiveImport.sortKey',
'interactiveImport.sortDirection',
'interactiveImport.recentFolders', 'interactiveImport.recentFolders',
'interactiveImport.importMode' 'interactiveImport.importMode'
]; ];
@@ -122,6 +128,11 @@ export const clearInteractiveImportBookFiles = createAction(CLEAR_INTERACTIVE_IM
// Action Handlers // Action Handlers
export const actionHandlers = handleThunks({ export const actionHandlers = handleThunks({
[FETCH_INTERACTIVE_IMPORT_ITEMS]: function(getState, payload, dispatch) { [FETCH_INTERACTIVE_IMPORT_ITEMS]: function(getState, payload, dispatch) {
if (abortCurrentFetchRequest) {
abortCurrentFetchRequest();
abortCurrentFetchRequest = null;
}
if (!payload.downloadId && !payload.folder) { if (!payload.downloadId && !payload.folder) {
dispatch(set({ section, error: { message: '`downloadId` or `folder` is required.' } })); dispatch(set({ section, error: { message: '`downloadId` or `folder` is required.' } }));
return; return;
@@ -129,12 +140,14 @@ export const actionHandlers = handleThunks({
dispatch(set({ section, isFetching: true })); dispatch(set({ section, isFetching: true }));
const promise = createAjaxRequest({ const { request, abortRequest } = createAjaxRequest({
url: '/manualimport', url: '/manualimport',
data: payload data: payload
}).request; });
promise.done((data) => { abortCurrentFetchRequest = abortRequest;
request.done((data) => {
dispatch(batchActions([ dispatch(batchActions([
update({ section, data }), update({ section, data }),
@@ -147,7 +160,11 @@ export const actionHandlers = handleThunks({
])); ]));
}); });
promise.fail((xhr) => { request.fail((xhr) => {
if (xhr.aborted) {
return;
}
dispatch(set({ dispatch(set({
section, section,
isFetching: false, isFetching: false,
+6 -4
View File
@@ -371,13 +371,14 @@ export const actionHandlers = handleThunks({
id, id,
remove, remove,
blocklist, blocklist,
skipRedownload skipRedownload,
changeCategory
} = payload; } = payload;
dispatch(updateItem({ section: paged, id, isRemoving: true })); dispatch(updateItem({ section: paged, id, isRemoving: true }));
const promise = createAjaxRequest({ const promise = createAjaxRequest({
url: `/queue/${id}?removeFromClient=${remove}&blocklist=${blocklist}&skipRedownload=${skipRedownload}`, url: `/queue/${id}?removeFromClient=${remove}&blocklist=${blocklist}&skipRedownload=${skipRedownload}&changeCategory=${changeCategory}`,
method: 'DELETE' method: 'DELETE'
}).request; }).request;
@@ -395,7 +396,8 @@ export const actionHandlers = handleThunks({
ids, ids,
remove, remove,
blocklist, blocklist,
skipRedownload skipRedownload,
changeCategory
} = payload; } = payload;
dispatch(batchActions([ dispatch(batchActions([
@@ -411,7 +413,7 @@ export const actionHandlers = handleThunks({
])); ]));
const promise = createAjaxRequest({ const promise = createAjaxRequest({
url: `/queue/bulk?removeFromClient=${remove}&blocklist=${blocklist}&skipRedownload=${skipRedownload}`, url: `/queue/bulk?removeFromClient=${remove}&blocklist=${blocklist}&skipRedownload=${skipRedownload}&changeCategory=${changeCategory}`,
method: 'DELETE', method: 'DELETE',
dataType: 'json', dataType: 'json',
data: JSON.stringify({ ids }) data: JSON.stringify({ ids })
+6 -6
View File
@@ -22,9 +22,9 @@ class About extends Component {
isNetCore, isNetCore,
isDocker, isDocker,
runtimeVersion, runtimeVersion,
migrationVersion,
databaseVersion, databaseVersion,
databaseType, databaseType,
migrationVersion,
appData, appData,
startupPath, startupPath,
mode, mode,
@@ -66,13 +66,13 @@ class About extends Component {
} }
<DescriptionListItem <DescriptionListItem
title={translate('DBMigration')} title={translate('Database')}
data={migrationVersion} data={`${titleCase(databaseType)} ${databaseVersion}`}
/> />
<DescriptionListItem <DescriptionListItem
title={translate('Database')} title={translate('DatabaseMigration')}
data={`${titleCase(databaseType)} ${databaseVersion}`} data={migrationVersion}
/> />
<DescriptionListItem <DescriptionListItem
@@ -114,9 +114,9 @@ About.propTypes = {
isNetCore: PropTypes.bool.isRequired, isNetCore: PropTypes.bool.isRequired,
runtimeVersion: PropTypes.string.isRequired, runtimeVersion: PropTypes.string.isRequired,
isDocker: PropTypes.bool.isRequired, isDocker: PropTypes.bool.isRequired,
migrationVersion: PropTypes.number.isRequired,
databaseType: PropTypes.string.isRequired, databaseType: PropTypes.string.isRequired,
databaseVersion: PropTypes.string.isRequired, databaseVersion: PropTypes.string.isRequired,
migrationVersion: PropTypes.number.isRequired,
appData: PropTypes.string.isRequired, appData: PropTypes.string.isRequired,
startupPath: PropTypes.string.isRequired, startupPath: PropTypes.string.isRequired,
mode: PropTypes.string.isRequired, mode: PropTypes.string.isRequired,
@@ -71,6 +71,7 @@ function getInternalLink(source) {
function getTestLink(source, props) { function getTestLink(source, props) {
switch (source) { switch (source) {
case 'IndexerStatusCheck': case 'IndexerStatusCheck':
case 'IndexerLongTermStatusCheck':
return ( return (
<SpinnerIconButton <SpinnerIconButton
name={icons.TEST} name={icons.TEST}
@@ -1,5 +1,6 @@
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import React, { Component } from 'react'; import React, { Component } from 'react';
import Alert from 'Components/Alert';
import LoadingIndicator from 'Components/Loading/LoadingIndicator'; import LoadingIndicator from 'Components/Loading/LoadingIndicator';
import PageContent from 'Components/Page/PageContent'; import PageContent from 'Components/Page/PageContent';
import PageContentBody from 'Components/Page/PageContentBody'; import PageContentBody from 'Components/Page/PageContentBody';
@@ -9,8 +10,12 @@ import PageToolbarSection from 'Components/Page/Toolbar/PageToolbarSection';
import TableOptionsModalWrapper from 'Components/Table/TableOptions/TableOptionsModalWrapper'; import TableOptionsModalWrapper from 'Components/Table/TableOptions/TableOptionsModalWrapper';
import VirtualTable from 'Components/Table/VirtualTable'; import VirtualTable from 'Components/Table/VirtualTable';
import VirtualTableRow from 'Components/Table/VirtualTableRow'; import VirtualTableRow from 'Components/Table/VirtualTableRow';
import { align, icons, sortDirections } from 'Helpers/Props'; import { align, icons, kinds, sortDirections } from 'Helpers/Props';
import hasDifferentItemsOrOrder from 'Utilities/Object/hasDifferentItemsOrOrder';
import translate from 'Utilities/String/translate'; import translate from 'Utilities/String/translate';
import getSelectedIds from 'Utilities/Table/getSelectedIds';
import selectAll from 'Utilities/Table/selectAll';
import toggleSelected from 'Utilities/Table/toggleSelected';
import UnmappedFilesTableHeader from './UnmappedFilesTableHeader'; import UnmappedFilesTableHeader from './UnmappedFilesTableHeader';
import UnmappedFilesTableRow from './UnmappedFilesTableRow'; import UnmappedFilesTableRow from './UnmappedFilesTableRow';
@@ -23,10 +28,43 @@ class UnmappedFilesTable extends Component {
super(props, context); super(props, context);
this.state = { this.state = {
scroller: null scroller: null,
allSelected: false,
allUnselected: false,
lastToggled: null,
selectedState: {}
}; };
} }
componentDidMount() {
this.setSelectedState();
}
componentDidUpdate(prevProps) {
const {
items,
sortKey,
sortDirection,
isDeleting,
deleteError
} = this.props;
if (sortKey !== prevProps.sortKey ||
sortDirection !== prevProps.sortDirection ||
hasDifferentItemsOrOrder(prevProps.items, items)
) {
this.setSelectedState();
}
const hasFinishedDeleting = prevProps.isDeleting &&
!isDeleting &&
!deleteError;
if (hasFinishedDeleting) {
this.onSelectAllChange({ value: false });
}
}
// //
// Control // Control
@@ -34,6 +72,68 @@ class UnmappedFilesTable extends Component {
this.setState({ scroller: ref }); this.setState({ scroller: ref });
}; };
getSelectedIds = () => {
if (this.state.allUnselected) {
return [];
}
return getSelectedIds(this.state.selectedState);
};
setSelectedState() {
const {
items
} = this.props;
const {
selectedState
} = this.state;
const newSelectedState = {};
items.forEach((file) => {
const isItemSelected = selectedState[file.id];
if (isItemSelected) {
newSelectedState[file.id] = isItemSelected;
} else {
newSelectedState[file.id] = false;
}
});
const selectedCount = getSelectedIds(newSelectedState).length;
const newStateCount = Object.keys(newSelectedState).length;
let isAllSelected = false;
let isAllUnselected = false;
if (selectedCount === 0) {
isAllUnselected = true;
} else if (selectedCount === newStateCount) {
isAllSelected = true;
}
this.setState({ selectedState: newSelectedState, allSelected: isAllSelected, allUnselected: isAllUnselected });
}
onSelectAllChange = ({ value }) => {
this.setState(selectAll(this.state.selectedState, value));
};
onSelectAllPress = () => {
this.onSelectAllChange({ value: !this.state.allSelected });
};
onSelectedChange = ({ id, value, shiftKey = false }) => {
this.setState((state) => {
return toggleSelected(state, this.props.items, id, value, shiftKey);
});
};
onDeleteUnmappedFilesPress = () => {
const selectedIds = this.getSelectedIds();
this.props.deleteUnmappedFiles(selectedIds);
};
rowRenderer = ({ key, rowIndex, style }) => { rowRenderer = ({ key, rowIndex, style }) => {
const { const {
items, items,
@@ -41,6 +141,10 @@ class UnmappedFilesTable extends Component {
deleteUnmappedFile deleteUnmappedFile
} = this.props; } = this.props;
const {
selectedState
} = this.state;
const item = items[rowIndex]; const item = items[rowIndex];
return ( return (
@@ -51,6 +155,8 @@ class UnmappedFilesTable extends Component {
<UnmappedFilesTableRow <UnmappedFilesTableRow
key={item.id} key={item.id}
columns={columns} columns={columns}
isSelected={selectedState[item.id]}
onSelectedChange={this.onSelectedChange}
deleteUnmappedFile={deleteUnmappedFile} deleteUnmappedFile={deleteUnmappedFile}
{...item} {...item}
/> />
@@ -63,6 +169,7 @@ class UnmappedFilesTable extends Component {
const { const {
isFetching, isFetching,
isPopulated, isPopulated,
isDeleting,
error, error,
items, items,
columns, columns,
@@ -72,13 +179,19 @@ class UnmappedFilesTable extends Component {
onSortPress, onSortPress,
isScanningFolders, isScanningFolders,
onAddMissingAuthorsPress, onAddMissingAuthorsPress,
deleteUnmappedFiles,
...otherProps ...otherProps
} = this.props; } = this.props;
const { const {
scroller scroller,
allSelected,
allUnselected,
selectedState
} = this.state; } = this.state;
const selectedTrackFileIds = this.getSelectedIds();
return ( return (
<PageContent title={translate('UnmappedFiles')}> <PageContent title={translate('UnmappedFiles')}>
<PageToolbar> <PageToolbar>
@@ -90,6 +203,13 @@ class UnmappedFilesTable extends Component {
isSpinning={isScanningFolders} isSpinning={isScanningFolders}
onPress={onAddMissingAuthorsPress} onPress={onAddMissingAuthorsPress}
/> />
<PageToolbarButton
label={translate('DeleteSelected')}
iconName={icons.DELETE}
isDisabled={selectedTrackFileIds.length === 0}
isSpinning={isDeleting}
onPress={this.onDeleteUnmappedFilesPress}
/>
</PageToolbarSection> </PageToolbarSection>
<PageToolbarSection alignContent={align.RIGHT}> <PageToolbarSection alignContent={align.RIGHT}>
@@ -117,9 +237,9 @@ class UnmappedFilesTable extends Component {
{ {
isPopulated && !error && !items.length && isPopulated && !error && !items.length &&
<div> <Alert kind={kinds.INFO}>
Success! My work is done, all files on disk are matched to known books. Success! My work is done, all files on disk are matched to known books.
</div> </Alert>
} }
{ {
@@ -138,8 +258,12 @@ class UnmappedFilesTable extends Component {
sortDirection={sortDirection} sortDirection={sortDirection}
onTableOptionChange={onTableOptionChange} onTableOptionChange={onTableOptionChange}
onSortPress={onSortPress} onSortPress={onSortPress}
allSelected={allSelected}
allUnselected={allUnselected}
onSelectAllChange={this.onSelectAllChange}
/> />
} }
selectedState={selectedState}
sortKey={sortKey} sortKey={sortKey}
sortDirection={sortDirection} sortDirection={sortDirection}
/> />
@@ -153,6 +277,8 @@ class UnmappedFilesTable extends Component {
UnmappedFilesTable.propTypes = { UnmappedFilesTable.propTypes = {
isFetching: PropTypes.bool.isRequired, isFetching: PropTypes.bool.isRequired,
isPopulated: PropTypes.bool.isRequired, isPopulated: PropTypes.bool.isRequired,
isDeleting: PropTypes.bool.isRequired,
deleteError: PropTypes.object,
error: PropTypes.object, error: PropTypes.object,
items: PropTypes.arrayOf(PropTypes.object).isRequired, items: PropTypes.arrayOf(PropTypes.object).isRequired,
columns: PropTypes.arrayOf(PropTypes.object).isRequired, columns: PropTypes.arrayOf(PropTypes.object).isRequired,
@@ -161,6 +287,7 @@ UnmappedFilesTable.propTypes = {
onTableOptionChange: PropTypes.func.isRequired, onTableOptionChange: PropTypes.func.isRequired,
onSortPress: PropTypes.func.isRequired, onSortPress: PropTypes.func.isRequired,
deleteUnmappedFile: PropTypes.func.isRequired, deleteUnmappedFile: PropTypes.func.isRequired,
deleteUnmappedFiles: PropTypes.func.isRequired,
isScanningFolders: PropTypes.bool.isRequired, isScanningFolders: PropTypes.bool.isRequired,
onAddMissingAuthorsPress: PropTypes.func.isRequired onAddMissingAuthorsPress: PropTypes.func.isRequired
}; };
@@ -5,7 +5,7 @@ import { connect } from 'react-redux';
import { createSelector } from 'reselect'; import { createSelector } from 'reselect';
import * as commandNames from 'Commands/commandNames'; import * as commandNames from 'Commands/commandNames';
import withCurrentPage from 'Components/withCurrentPage'; import withCurrentPage from 'Components/withCurrentPage';
import { deleteBookFile, fetchBookFiles, setBookFilesSort, setBookFilesTableOption } from 'Store/Actions/bookFileActions'; import { deleteBookFile, deleteBookFiles, fetchBookFiles, setBookFilesSort, setBookFilesTableOption } from 'Store/Actions/bookFileActions';
import { executeCommand } from 'Store/Actions/commandActions'; import { executeCommand } from 'Store/Actions/commandActions';
import createClientSideCollectionSelector from 'Store/Selectors/createClientSideCollectionSelector'; import createClientSideCollectionSelector from 'Store/Selectors/createClientSideCollectionSelector';
import createCommandExecutingSelector from 'Store/Selectors/createCommandExecutingSelector'; import createCommandExecutingSelector from 'Store/Selectors/createCommandExecutingSelector';
@@ -28,7 +28,9 @@ function createMapStateToProps() {
items, items,
...otherProps ...otherProps
} = bookFiles; } = bookFiles;
const unmappedFiles = _.filter(items, { bookId: 0 }); const unmappedFiles = _.filter(items, { bookId: 0 });
return { return {
items: unmappedFiles, items: unmappedFiles,
...otherProps, ...otherProps,
@@ -57,6 +59,10 @@ function createMapDispatchToProps(dispatch, props) {
dispatch(deleteBookFile({ id })); dispatch(deleteBookFile({ id }));
}, },
deleteUnmappedFiles(bookFileIds) {
dispatch(deleteBookFiles({ bookFileIds }));
},
onAddMissingAuthorsPress() { onAddMissingAuthorsPress() {
dispatch(executeCommand({ dispatch(executeCommand({
name: commandNames.RESCAN_FOLDERS, name: commandNames.RESCAN_FOLDERS,
@@ -106,7 +112,8 @@ UnmappedFilesTableConnector.propTypes = {
onSortPress: PropTypes.func.isRequired, onSortPress: PropTypes.func.isRequired,
onTableOptionChange: PropTypes.func.isRequired, onTableOptionChange: PropTypes.func.isRequired,
fetchUnmappedFiles: PropTypes.func.isRequired, fetchUnmappedFiles: PropTypes.func.isRequired,
deleteUnmappedFile: PropTypes.func.isRequired deleteUnmappedFile: PropTypes.func.isRequired,
deleteUnmappedFiles: PropTypes.func.isRequired
}; };
export default withCurrentPage( export default withCurrentPage(
@@ -4,6 +4,7 @@ import IconButton from 'Components/Link/IconButton';
import TableOptionsModalWrapper from 'Components/Table/TableOptions/TableOptionsModalWrapper'; import TableOptionsModalWrapper from 'Components/Table/TableOptions/TableOptionsModalWrapper';
import VirtualTableHeader from 'Components/Table/VirtualTableHeader'; import VirtualTableHeader from 'Components/Table/VirtualTableHeader';
import VirtualTableHeaderCell from 'Components/Table/VirtualTableHeaderCell'; import VirtualTableHeaderCell from 'Components/Table/VirtualTableHeaderCell';
import VirtualTableSelectAllHeaderCell from 'Components/Table/VirtualTableSelectAllHeaderCell';
import { icons } from 'Helpers/Props'; import { icons } from 'Helpers/Props';
// import hasGrowableColumns from './hasGrowableColumns'; // import hasGrowableColumns from './hasGrowableColumns';
import styles from './UnmappedFilesTableHeader.css'; import styles from './UnmappedFilesTableHeader.css';
@@ -12,6 +13,9 @@ function UnmappedFilesTableHeader(props) {
const { const {
columns, columns,
onTableOptionChange, onTableOptionChange,
allSelected,
allUnselected,
onSelectAllChange,
...otherProps ...otherProps
} = props; } = props;
@@ -30,6 +34,17 @@ function UnmappedFilesTableHeader(props) {
return null; return null;
} }
if (name === 'select') {
return (
<VirtualTableSelectAllHeaderCell
key={name}
allSelected={allSelected}
allUnselected={allUnselected}
onSelectAllChange={onSelectAllChange}
/>
);
}
if (name === 'actions') { if (name === 'actions') {
return ( return (
<VirtualTableHeaderCell <VirtualTableHeaderCell
@@ -71,6 +86,9 @@ function UnmappedFilesTableHeader(props) {
UnmappedFilesTableHeader.propTypes = { UnmappedFilesTableHeader.propTypes = {
columns: PropTypes.arrayOf(PropTypes.object).isRequired, columns: PropTypes.arrayOf(PropTypes.object).isRequired,
allSelected: PropTypes.bool.isRequired,
allUnselected: PropTypes.bool.isRequired,
onSelectAllChange: PropTypes.func.isRequired,
onTableOptionChange: PropTypes.func.isRequired onTableOptionChange: PropTypes.func.isRequired
}; };
@@ -20,3 +20,9 @@
flex: 0 0 100px; flex: 0 0 100px;
} }
.checkInput {
composes: input from '~Components/Form/CheckInput.css';
margin-top: 0;
}
@@ -2,6 +2,7 @@
// Please do not change this file! // Please do not change this file!
interface CssExports { interface CssExports {
'actions': string; 'actions': string;
'checkInput': string;
'dateAdded': string; 'dateAdded': string;
'path': string; 'path': string;
'quality': string; 'quality': string;
@@ -6,6 +6,7 @@ import IconButton from 'Components/Link/IconButton';
import ConfirmModal from 'Components/Modal/ConfirmModal'; import ConfirmModal from 'Components/Modal/ConfirmModal';
import RelativeDateCellConnector from 'Components/Table/Cells/RelativeDateCellConnector'; import RelativeDateCellConnector from 'Components/Table/Cells/RelativeDateCellConnector';
import VirtualTableRowCell from 'Components/Table/Cells/VirtualTableRowCell'; import VirtualTableRowCell from 'Components/Table/Cells/VirtualTableRowCell';
import VirtualTableSelectCell from 'Components/Table/Cells/VirtualTableSelectCell';
import { icons, kinds } from 'Helpers/Props'; import { icons, kinds } from 'Helpers/Props';
import InteractiveImportModal from 'InteractiveImport/InteractiveImportModal'; import InteractiveImportModal from 'InteractiveImport/InteractiveImportModal';
import formatBytes from 'Utilities/Number/formatBytes'; import formatBytes from 'Utilities/Number/formatBytes';
@@ -69,7 +70,9 @@ class UnmappedFilesTableRow extends Component {
size, size,
dateAdded, dateAdded,
quality, quality,
columns columns,
isSelected,
onSelectedChange
} = this.props; } = this.props;
const folder = path.substring(0, Math.max(path.lastIndexOf('/'), path.lastIndexOf('\\'))); const folder = path.substring(0, Math.max(path.lastIndexOf('/'), path.lastIndexOf('\\')));
@@ -93,6 +96,19 @@ class UnmappedFilesTableRow extends Component {
return null; return null;
} }
if (name === 'select') {
return (
<VirtualTableSelectCell
inputClassName={styles.checkInput}
id={id}
key={name}
isSelected={isSelected}
isDisabled={false}
onSelectedChange={onSelectedChange}
/>
);
}
if (name === 'path') { if (name === 'path') {
return ( return (
<VirtualTableRowCell <VirtualTableRowCell
@@ -208,6 +224,8 @@ UnmappedFilesTableRow.propTypes = {
quality: PropTypes.object.isRequired, quality: PropTypes.object.isRequired,
dateAdded: PropTypes.string.isRequired, dateAdded: PropTypes.string.isRequired,
columns: PropTypes.arrayOf(PropTypes.object).isRequired, columns: PropTypes.arrayOf(PropTypes.object).isRequired,
isSelected: PropTypes.bool,
onSelectedChange: PropTypes.func.isRequired,
deleteUnmappedFile: PropTypes.func.isRequired deleteUnmappedFile: PropTypes.func.isRequired
}; };
@@ -0,0 +1,11 @@
const regex = /\d+/g;
function naturalExpansion(input) {
if (!input) {
return '';
}
return input.replace(regex, (n) => n.padStart(8, '0'));
}
export default naturalExpansion;
+3 -1
View File
@@ -1,9 +1,11 @@
const regex = /\b\w+/g;
function titleCase(input) { function titleCase(input) {
if (!input) { if (!input) {
return ''; return '';
} }
return input.replace(/\b\w+/g, (match) => { return input.replace(regex, (match) => {
return match.charAt(0).toUpperCase() + match.substr(1).toLowerCase(); return match.charAt(0).toUpperCase() + match.substr(1).toLowerCase();
}); });
} }
+9 -11
View File
@@ -25,20 +25,18 @@ export async function fetchTranslations(): Promise<boolean> {
export default function translate( export default function translate(
key: string, key: string,
tokens?: Record<string, string | number | boolean> tokens: Record<string, string | number | boolean> = {}
) { ) {
const translation = translations[key] || key; const translation = translations[key] || key;
if (tokens) { tokens.appName = 'Readarr';
// Fallback to the old behaviour for translations not yet updated to use named tokens
Object.values(tokens).forEach((value, index) => {
tokens[index] = value;
});
return translation.replace(/\{([a-z0-9]+?)\}/gi, (match, tokenMatch) => // Fallback to the old behaviour for translations not yet updated to use named tokens
String(tokens[tokenMatch] ?? match) Object.values(tokens).forEach((value, index) => {
); tokens[index] = value;
} });
return translation; return translation.replace(/\{([a-z0-9]+?)\}/gi, (match, tokenMatch) =>
String(tokens[tokenMatch] ?? match)
);
} }
@@ -53,7 +53,7 @@ class CutoffUnmetConnector extends Component {
gotoCutoffUnmetFirstPage gotoCutoffUnmetFirstPage
} = this.props; } = this.props;
registerPagePopulator(this.repopulate, ['bookFileUpdated']); registerPagePopulator(this.repopulate, ['bookFileUpdated', 'bookFileDeleted']);
if (useCurrentPage) { if (useCurrentPage) {
fetchCutoffUnmet(); fetchCutoffUnmet();
@@ -50,7 +50,7 @@ class MissingConnector extends Component {
gotoMissingFirstPage gotoMissingFirstPage
} = this.props; } = this.props;
registerPagePopulator(this.repopulate, ['bookFileUpdated']); registerPagePopulator(this.repopulate, ['bookFileUpdated', 'bookFileDeleted']);
if (useCurrentPage) { if (useCurrentPage) {
fetchMissing(); fetchMissing();
+2
View File
@@ -4,6 +4,8 @@ import { render } from 'react-dom';
import createAppStore from 'Store/createAppStore'; import createAppStore from 'Store/createAppStore';
import App from './App/App'; import App from './App/App';
import 'Diag/ConsoleApi';
export async function bootstrap() { export async function bootstrap() {
const history = createBrowserHistory(); const history = createBrowserHistory();
const store = createAppStore(history); const store = createAppStore(history);
+3 -2
View File
@@ -30,7 +30,7 @@
"@fortawesome/free-regular-svg-icons": "6.4.0", "@fortawesome/free-regular-svg-icons": "6.4.0",
"@fortawesome/free-solid-svg-icons": "6.4.0", "@fortawesome/free-solid-svg-icons": "6.4.0",
"@fortawesome/react-fontawesome": "0.2.0", "@fortawesome/react-fontawesome": "0.2.0",
"@microsoft/signalr": "6.0.21", "@microsoft/signalr": "6.0.25",
"@sentry/browser": "7.51.2", "@sentry/browser": "7.51.2",
"@sentry/integrations": "7.51.2", "@sentry/integrations": "7.51.2",
"@types/node": "18.16.16", "@types/node": "18.16.16",
@@ -95,6 +95,7 @@
"@babel/preset-react": "7.22.5", "@babel/preset-react": "7.22.5",
"@babel/preset-typescript": "7.22.11", "@babel/preset-typescript": "7.22.11",
"@types/lodash": "4.14.197", "@types/lodash": "4.14.197",
"@types/react-lazyload": "3.2.1",
"@types/redux-actions": "2.6.2", "@types/redux-actions": "2.6.2",
"@typescript-eslint/eslint-plugin": "6.5.0", "@typescript-eslint/eslint-plugin": "6.5.0",
"@typescript-eslint/parser": "6.5.0", "@typescript-eslint/parser": "6.5.0",
@@ -120,7 +121,7 @@
"html-webpack-plugin": "5.5.3", "html-webpack-plugin": "5.5.3",
"loader-utils": "^3.2.1", "loader-utils": "^3.2.1",
"mini-css-extract-plugin": "2.7.6", "mini-css-extract-plugin": "2.7.6",
"postcss": "8.4.23", "postcss": "8.4.31",
"postcss-color-function": "4.1.0", "postcss-color-function": "4.1.0",
"postcss-loader": "7.3.0", "postcss-loader": "7.3.0",
"postcss-mixins": "9.0.4", "postcss-mixins": "9.0.4",

Some files were not shown because too many files have changed in this diff Show More