Compare commits

..

138 Commits

Author SHA1 Message Date
Bogdan
2fbeec8291 New: Add Year specification to Auto Tagging
(cherry picked from commit c69b5fc72aa681b954550d578b6f96f44b708a5f)
2023-08-01 23:59:18 +00:00
Mark McDowall
704635f758 New: Book interactive search modal size
(cherry picked from commit 1f619e27f1e8905bc96ce54c483171469d204650)

Closes #2243
2023-08-01 18:32:14 +03:00
Bogdan
263e807de2 Ensure yarn packages are installed when running only LintUI 2023-07-31 08:33:52 +03:00
Mark McDowall
9ac9bd25c1 Re-order frontend build steps
(cherry picked from commit 97ad6682f7d54af8886144bc5a179fa7242f1f1f)
2023-07-31 08:01:50 +03:00
Bogdan
4ead5186ae Bump version to 0.3.1 2023-07-30 09:12:56 +03:00
Bogdan
dea797c375 Fixed: (UI) Ensure root folders are populated in Author Editor 2023-07-30 05:10:21 +03:00
PearsonFlyer
58ba24762b Fixed: Correctly calculate books count on Author page
Closes #1931
2023-07-30 05:10:07 +03:00
Servarr
fbd7b4fe33 Automated API Docs update [skip ci] 2023-07-30 05:09:25 +03:00
Mark McDowall
fee7fbbff6 New: Add result to commands to report commands that did not complete successfully
(cherry picked from commit 103ce3def4636ef891e72bd687ef8f46b5125233)
2023-07-30 03:44:14 +03:00
Taloth Saldono
18253a298e Log Goodreads connection failures with more info.
(cherry picked from commit 6672650b6b5e152e82fb3ad38a0a158d66c0b83d)
2023-07-30 03:44:14 +03:00
Bogdan
22f92150c3 Ensure original data is shown when no matches are made 2023-07-29 18:32:54 +03:00
Bogdan
4d7a762ee8 Fix book tests 2023-07-29 14:59:47 +03:00
Stevie Robinson
b11517e2ac Extend InlineMarkdown to handle code blocks in backticks
(cherry picked from commit e1c5533efa397632becc606c17232f97055e371b)
2023-07-29 09:59:50 +03:00
Bogdan
d5af254f47 Fix AuthorLookupFixture 2023-07-29 09:04:22 +03:00
Bogdan
f09da06f80 More test fixes 2023-07-29 07:48:47 +03:00
Bogdan
d3443510b4 Rename formatPreferredWordScore to formatCustomFormatScore
Closes #2731
2023-07-29 06:35:19 +03:00
Bogdan
d73eb1b5f9 Validation for Custom Format specifications
Co-authored-by: Qstick <qstick@gmail.com>
(cherry picked from commit 3d6cf24d7c91f8ff697c34264c249f7450894106)

Closes #2726
2023-07-29 06:32:29 +03:00
Bogdan
39778a95bf Dedupe releases based on indexer priority
(cherry picked from commit 38c717bcef6fa5fcd2ff1c7901639eb888a94a8a)

Closes #2727
2023-07-29 06:28:23 +03:00
Taloth Saldono
9fccca1154 Fixed up some errors and do the guid cache fix on the module instead of backend coz that would cause other issues.
(cherry picked from commit 8eaab46488f00a74197c517c6ef773626aec5173)
2023-07-29 06:27:34 +03:00
Mark McDowall
e165663616 Fixed: Sorting in Interactive search duplicates results
(cherry picked from commit a6637b2911f7818e596c1518e94bd111cff0120b)

Closes #739
Closes #743
2023-07-29 06:27:31 +03:00
Bogdan
b49d2312ab Fixed: Check only enabled Jackett indexers for '/all' endpoint
(cherry picked from commit ae3dd5730e05c5229e7f7092f15c33859524863b)

Closes #2730
2023-07-29 05:38:15 +03:00
Bogdan
52221c7cf4 Fixed: Ensure failing indexers are marked as failed when testing all
(cherry picked from commit b407eba61284d5fb855df6a2868805853aa6f448)

Closes #2735
2023-07-29 05:36:23 +03:00
bakerboy448
ad7b110a0b New: Use better page size for Newznab/Torznab (up to 100) when supported by the indexer
(cherry picked from commit ddb25b109575cc378462a1c3a64705f2003f01f0)

Closes #2181
2023-07-29 05:33:52 +03:00
Weblate
b04b483f86 Multiple Translations updated by Weblate
ignore-downstream

Co-authored-by: Fixer <ygj59783@zslsz.com>
Translate-URL: https://translate.servarr.com/projects/servarr/readarr/ro/
Translation: Servarr/Readarr
2023-07-28 12:55:31 +03:00
Bogdan
b79941e0a1 Fix tests 2023-07-26 14:00:10 +03:00
Weblate
84d47b1f23 Multiple Translations updated by Weblate
ignore-downstream

Co-authored-by: Fixer <ygj59783@zslsz.com>
Translate-URL: https://translate.servarr.com/projects/servarr/readarr/ro/
Translation: Servarr/Readarr
2023-07-26 07:53:53 +03:00
Weblate
17df4d47fb Multiple Translations updated by Weblate
ignore-downstream

Co-authored-by: Havok Dan <havokdan@yahoo.com.br>
Co-authored-by: SHUAI.W <x@ousui.org>
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-07-25 01:04:44 +03:00
Bogdan
b9f89dddc9 Bump version to 0.3.0 2023-07-23 09:38:17 +03:00
Weblate
e3fc469cd3 Multiple Translations updated by Weblate
ignore-downstream

Co-authored-by: Weblate <noreply@weblate.org>
Translate-URL: https://translate.servarr.com/projects/servarr/readarr/
Translation: Servarr/Readarr
2023-07-23 05:05:02 +03:00
Bogdan
4304685a65 Add support for deprecated values in field select options
(cherry picked from commit d9786887f3fe30ef60ad9c50b3272bf60dfef309)

Closes #2718
2023-07-23 05:03:40 +03:00
Bogdan
7d77b1fbe5 Trim spaces from a split list in GetValueConverter 2023-07-23 05:02:18 +03:00
Bogdan
1989174801 Fix typo in SkipRedownload
Closes #2711
2023-07-23 05:01:08 +03:00
Bogdan
ac4ae9bb4d Fixed: Ensure Monitoring Options resets to No Change
(cherry picked from commit 180153cd8440df88c9aa5694c67c6cae537dc595)
2023-07-23 04:52:30 +03:00
Bogdan
f399d27470 Cache busting for CSS files
(cherry picked from commit 38f263931ff8faba050762abe5fb692a5bc0d515)
2023-07-23 03:03:51 +03:00
Weblate
c5fd2e3aa0 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-07-21 13:37:45 +03:00
bakerboy448
e971d68d67 New: Log when testing for matching Remote Path Mapping
(cherry picked from commit 360d989cb047d0f752dd71b806aa0a746e3b5f3d)
2023-07-21 07:51:17 +03:00
Mark McDowall
af858ac4aa Fix chunk IDs and source map file names
(cherry picked from commit bb8fed94eb2c44040031643e8c20ff72de759535)
2023-07-20 00:56:17 +03:00
Weblate
63ea253a6b 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: Qstick <qstick@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/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/de/
Translate-URL: https://translate.servarr.com/projects/servarr/readarr/el/
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/hr/
Translate-URL: https://translate.servarr.com/projects/servarr/readarr/hu/
Translate-URL: https://translate.servarr.com/projects/servarr/readarr/id/
Translate-URL: https://translate.servarr.com/projects/servarr/readarr/it/
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/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/ru/
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/uk/
Translation: Servarr/Readarr
2023-07-19 15:11:56 +03:00
Bogdan
484f2eb3ec Fixed: Error when selecting different Quality Profile
(cherry picked from commit 5e19478266b33905e88b2e769269e44e5dd98e4b)

Closes #2694
2023-07-18 07:31:27 +03:00
Bogdan
15190aa61a Use 2 spaces indentation for ts/tsx files
(cherry picked from commit bc374f07cebc8463c6416630d0fb06f011f21c31)
2023-07-18 07:29:00 +03:00
Bogdan
a3aac90bf7 Fixed: (ImportLists) Removed minimum refresh interval for FetchSingleList 2023-07-18 02:17:48 +03:00
Servarr
dd9cbc4f54 Automated API Docs update [skip ci] 2023-07-17 06:37:31 +03:00
Bogdan
4bca0d77b7 New: Show tooltips with Custom Formats in History and Queue
Closes #2676
2023-07-17 05:18:03 +03:00
Weblate
1316b388ad Multiple Translations updated by Weblate
ignore-downstream

Co-authored-by: Anonymous <noreply@weblate.org>
Translate-URL: https://translate.servarr.com/projects/servarr/readarr/ar/
Translate-URL: https://translate.servarr.com/projects/servarr/readarr/bg/
Translation: Servarr/Readarr
2023-07-17 04:39:11 +03:00
Servarr
243c88ce56 Automated API Docs update [skip ci] 2023-07-17 04:37:01 +03:00
Bogdan
921f170234 Use named keys for apply tags help text
Closes #2673
2023-07-17 04:36:24 +03:00
Weblate
3e102627f5 Multiple Translations updated by Weblate
ignore-downstream

Co-authored-by: Weblate <noreply@weblate.org>
Translate-URL: https://translate.servarr.com/projects/servarr/readarr/
Translation: Servarr/Readarr
2023-07-17 04:36:19 +03:00
Weblate
f3b5f0c5cb Multiple Translations updated by Weblate
ignore-downstream

Co-authored-by: Weblate <noreply@weblate.org>
Translate-URL: https://translate.servarr.com/projects/servarr/readarr/
Translation: Servarr/Readarr
2023-07-17 04:35:09 +03:00
Weblate
a53516e821 Multiple Translations updated by Weblate
ignore-downstream

Co-authored-by: Anonymous <noreply@weblate.org>
Co-authored-by: Godwhitelight <godwhitelight1@gmail.com>
Co-authored-by: Guy Porat <guyporatmail@gmail.com>
Co-authored-by: Havok Dan <havokdan@yahoo.com.br>
Co-authored-by: Weblate <noreply@weblate.org>
Co-authored-by: liimee <git.taaa@fedora.email>
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/he/
Translate-URL: https://translate.servarr.com/projects/servarr/readarr/id/
Translate-URL: https://translate.servarr.com/projects/servarr/readarr/pt_BR/
Translation: Servarr/Readarr
2023-07-17 04:34:08 +03:00
Qstick
f0f95be57f New: Download Client Tags
(cherry picked from commit f6ae9fd6c5173cbf1540341fa99d2f120be1d28e)
2023-07-17 04:30:26 +03:00
Bogdan
f436d730fe New: Bulk Manage Applications, Download Clients
Co-authored-by: Qstick <qstick@gmail.com>
2023-07-17 04:30:26 +03:00
Mark McDowall
f7c135faaf Fixed: Ensure translations are fetched before loading app
(cherry picked from commit ad2721dc55f3233e4c299babe5744418bc530418)

Closes #2670
Closes #2674
Closes #2679
2023-07-17 03:41:06 +03:00
Taloth Saldono
8bb52105fd New: Per download client setting to Remove Completed/Failed downloads instead of global setting
(cherry picked from commit 2dba5ef4b431bee0a061be67354c9a7a612a03c8)
2023-07-16 23:45:15 +03:00
Bogdan
e5a1b7a72e Add missing seed criteria validation 2023-07-16 21:25:14 +03:00
Bogdan
2f2a521391 Fixed: (Nyaa) Update default filtered category 2023-07-16 21:03:40 +03:00
Qstick
304d1e3462 TagSelect field type
(cherry picked from commit 09347f79c5c486ccb88d732c1bac1cacc668536c)

Closes #558
2023-07-16 19:58:00 +03:00
Bogdan
1d1cc6526d Bump version to 0.2.4 2023-07-16 08:19:33 +03:00
Weblate
690e0b5d96 Multiple Translations updated by Weblate
ignore-downstream

Co-authored-by: Havok Dan <havokdan@yahoo.com.br>
Co-authored-by: Leliene <lhena.gardien@gmail.com>
Co-authored-by: 君禹渊 <taoxu2870@outlook.com>
Co-authored-by: 無情天 <kofzhanganguo@126.com>
Translate-URL: https://translate.servarr.com/projects/servarr/readarr/fr/
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-07-15 18:58:53 +03:00
Servarr
212eedd345 Automated API Docs update [skip ci] 2023-07-14 06:29:56 +03:00
Bogdan
0b38743292 Update webpack, eslint and core-js 2023-07-14 06:25:01 +03:00
jack-mil
1def54f246 New: Custom Format Score column in queue
(cherry picked from commit a6f2db9139c4a6b01d162ccf8884fc02c874b4cf)

Closes #2647
Closes #2658
2023-07-14 05:11:28 +03:00
bakerboy448
0eeaa1e443 Import list logging improvements
(cherry picked from commit d2be869d9cee2ee6452a53dcabe20c1598fd115a)

Closes #2654
2023-07-14 05:03:13 +03:00
Zak Saunders
7beee07a2c New: Make Release Group Outline Not Show as Required
(cherry picked from commit 89e363fd145ae6c531e6b3fa4fde258af05afca0)

Closes #2277
2023-07-14 04:56:05 +03:00
dependabot[bot]
924f739d1f Bump semver from 5.7.1 to 5.7.2
Bumps [semver](https://github.com/npm/node-semver) from 5.7.1 to 5.7.2.
- [Release notes](https://github.com/npm/node-semver/releases)
- [Changelog](https://github.com/npm/node-semver/blob/v5.7.2/CHANGELOG.md)
- [Commits](https://github.com/npm/node-semver/compare/v5.7.1...v5.7.2)

---
updated-dependencies:
- dependency-name: semver
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-07-14 04:53:23 +03:00
Weblate
b187fb23e3 Multiple Translations updated by Weblate
ignore-downstream

Co-authored-by: Oskari Lavinto <olavinto@protonmail.com>
Translate-URL: https://translate.servarr.com/projects/servarr/readarr/fi/
Translation: Servarr/Readarr
2023-07-12 00:58:12 +03:00
Bogdan
ca043b3820 Bump version to 0.2.3 2023-07-09 15:02:39 +03:00
Bogdan
c3c9b9afbb Add db migration for Metadata Profile's Ignored to List 2023-07-09 03:17:21 +03:00
Bogdan
f225a742cc New: (UI) Minor improvements to metadata profiles listing 2023-07-08 22:39:39 +03:00
Servarr
f4fd36061c Automated API Docs update [skip ci] 2023-07-08 22:15:39 +03:00
Bogdan
38e39449aa Fixed: (MetadataProfile) Allow usage of Must Not Contain 2023-07-08 22:10:57 +03:00
Bogdan
484c255fd4 Add validation for MinPopularity and MinPages in metadata profiles 2023-07-08 21:19:40 +03:00
dependabot[bot]
f341b5f449 Bump tough-cookie from 4.1.2 to 4.1.3
Bumps [tough-cookie](https://github.com/salesforce/tough-cookie) from 4.1.2 to 4.1.3.
- [Release notes](https://github.com/salesforce/tough-cookie/releases)
- [Changelog](https://github.com/salesforce/tough-cookie/blob/master/CHANGELOG.md)
- [Commits](https://github.com/salesforce/tough-cookie/compare/v4.1.2...v4.1.3)

---
updated-dependencies:
- dependency-name: tough-cookie
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-07-08 20:10:05 +03:00
Weblate
eb5654c634 Multiple Translations updated by Weblate
ignore-downstream

Co-authored-by: Kevin Orel Edry <techg9@gmail.com>
Co-authored-by: Tacit <1750630216@qq.com>
Co-authored-by: reloxx <reloxx@interia.pl>
Co-authored-by: 阿卡林刘 <ScottLiu_NonWin@outlook.com>
Translate-URL: https://translate.servarr.com/projects/servarr/readarr/de/
Translate-URL: https://translate.servarr.com/projects/servarr/readarr/he/
Translate-URL: https://translate.servarr.com/projects/servarr/readarr/zh_CN/
Translation: Servarr/Readarr
2023-07-08 20:09:49 +03:00
dependabot[bot]
e843046d76 Bump stylelint from 15.6.1 to 15.10.1
Bumps [stylelint](https://github.com/stylelint/stylelint) from 15.6.1 to 15.10.1.
- [Release notes](https://github.com/stylelint/stylelint/releases)
- [Changelog](https://github.com/stylelint/stylelint/blob/main/CHANGELOG.md)
- [Commits](https://github.com/stylelint/stylelint/compare/15.6.1...15.10.1)

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

Signed-off-by: dependabot[bot] <support@github.com>
2023-07-08 01:50:20 +03:00
Qstick
ef57545221 Add package to Sentry release to ensure apps don't mix 2023-07-04 12:16:37 -05:00
Bogdan
09d44726a4 New: Speed up API add by reworking AuthorExistsValidator
Co-authored-by: Qstick <qstick@gmail.com>
2023-07-03 17:19:19 +03:00
Bogdan
0e2d39f580 Create overload for ToJson() with Formatting param
(cherry picked from commit aa2b0031671b6846eaa75e0914cd03ae7bbb0da7)
2023-07-03 11:19:05 +03:00
Qstick
dbcb0e77a8 Fixed: Allow restore to process backups up to ~500MB
(cherry picked from commit 551edb9e655d2a541a2232f85e79a5e3f7b433aa)
2023-07-03 11:18:52 +03:00
Bogdan
0186900a54 Bump version to 0.2.2 2023-07-02 12:03:41 +03:00
Weblate
941b30edac Multiple Translations updated by Weblate
ignore-downstream

Co-authored-by: BeardedWatermelon <periklis.karantonis@gmail.com>
Translate-URL: https://translate.servarr.com/projects/servarr/readarr/el/
Translation: Servarr/Readarr
2023-06-30 13:56:01 +03:00
Weblate
5c61b6ceb3 Translated using Weblate (Portuguese (Brazil)) [skip ci]
Currently translated at 100.0% (945 of 945 strings)

Translated using Weblate (Thai) [skip ci]

Currently translated at 61.2% (579 of 945 strings)

Translated using Weblate (Icelandic) [skip ci]

Currently translated at 61.4% (581 of 945 strings)

Translated using Weblate (Hindi) [skip ci]

Currently translated at 61.3% (580 of 945 strings)

Translated using Weblate (Hebrew) [skip ci]

Currently translated at 63.9% (604 of 945 strings)

Translated using Weblate (Finnish) [skip ci]

Currently translated at 87.9% (831 of 945 strings)

Translated using Weblate (Danish) [skip ci]

Currently translated at 61.9% (585 of 945 strings)

Translated using Weblate (Czech) [skip ci]

Currently translated at 61.5% (582 of 945 strings)

Translated using Weblate (Bulgarian) [skip ci]

Currently translated at 61.4% (581 of 945 strings)

Translated using Weblate (Arabic) [skip ci]

Currently translated at 61.5% (582 of 945 strings)

Translated using Weblate (Spanish) [skip ci]

Currently translated at 68.1% (644 of 945 strings)

Co-authored-by: Anonymous <noreply@weblate.org>
Co-authored-by: Havok Dan <havokdan@yahoo.com.br>
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/he/
Translate-URL: https://translate.servarr.com/projects/servarr/readarr/hi/
Translate-URL: https://translate.servarr.com/projects/servarr/readarr/is/
Translate-URL: https://translate.servarr.com/projects/servarr/readarr/pt_BR/
Translate-URL: https://translate.servarr.com/projects/servarr/readarr/th/
Translation: Servarr/Readarr
2023-06-28 13:56:13 +03:00
Bogdan
55959e1112 New: Improve empty list messaging
(cherry picked from commit ee843259bca2d9764a3919b123524a51a3a16cce)

Closes #2621
2023-06-27 06:43:45 +03:00
Bogdan
07451cbcde Fixed: Invalid image URL if Author/Book is missing background image
(cherry picked from commit dd096e0fda71b3afa9b09c2900abbf226d8a5204)

Closes #2625
2023-06-27 06:36:21 +03:00
Bogdan
1ebdffcd26 Allow array of string as value in EnhancedSelectInput
(cherry picked from commit 6816767fad9e4e839e77c5fe40aece97033cd052)
2023-06-27 04:00:24 +03:00
Weblate
75119ce9df Translated using Weblate (Hungarian) [skip ci]
Currently translated at 97.2% (916 of 942 strings)

Co-authored-by: sutoramon <sutoramon@gmail.com>
Translate-URL: https://translate.servarr.com/projects/servarr/readarr/hu/
Translation: Servarr/Readarr
2023-06-26 02:26:01 +03:00
Bogdan
668dc6dfde Fix translation in RemoveQueueItemModal 2023-06-26 02:08:41 +03:00
Bogdan
ee989c9c67 Bump version to 0.2.1 2023-06-25 09:04:44 +03:00
servarr[bot]
acac3bd680 Add more trace logs related info to bug_report.yml [skip ci] 2023-06-22 16:40:17 +03:00
Servarr
3b18f3206d Automated API Docs update [skip ci] 2023-06-21 08:16:49 +03:00
Bogdan
fcf057a019 Remove not implemented endpoints from API docs 2023-06-21 08:00:04 +03:00
Servarr
c7399cdd2b Automated API Docs update [skip ci] 2023-06-21 07:45:48 +03:00
Bogdan
08a3682b89 Limit search input to first character matching when only one character is typed 2023-06-21 07:25:09 +03:00
Bogdan
3da00f75dc Remove not implemented endpoints from API docs
Closes #2613
2023-06-21 07:09:01 +03:00
Bogdan
60abb298b2 Convert to 'using' declaration in Housekeeping Tasks
Closes #2612
2023-06-21 07:03:13 +03:00
Bogdan
c710b117ab Prevent NullRef when deleting missing backups
(cherry picked from commit 0ff0fe2e68f3abf7b8e4d6bf0c1e9dee4eb68227)

Closes #2610
2023-06-21 06:37:09 +03:00
Weblate
816f53b36b Translated using Weblate (Chinese (Traditional) (zh_TW)) [skip ci]
Currently translated at 3.5% (33 of 942 strings)

Translated using Weblate (Portuguese (Brazil)) [skip ci]

Currently translated at 100.0% (942 of 942 strings)

Translated using Weblate (Vietnamese) [skip ci]

Currently translated at 60.6% (571 of 942 strings)

Translated using Weblate (German) [skip ci]

Currently translated at 97.5% (919 of 942 strings)

Co-authored-by: Havok Dan <havokdan@yahoo.com.br>
Co-authored-by: KHng0284 <giakhang021109@gmail.com>
Co-authored-by: reloxx <reloxx@interia.pl>
Co-authored-by: ted09080037 <ted09080037@gmail.com>
Translate-URL: https://translate.servarr.com/projects/servarr/readarr/de/
Translate-URL: https://translate.servarr.com/projects/servarr/readarr/pt_BR/
Translate-URL: https://translate.servarr.com/projects/servarr/readarr/vi/
Translate-URL: https://translate.servarr.com/projects/servarr/readarr/zh_TW/
Translation: Servarr/Readarr
2023-06-21 02:28:40 +03:00
bakerboy448
749684e24a New: Indexer Messaging and Error Improvements
(cherry picked from commit 3b505d8734dcbe3fa53acba7f94f1361151e6a44)
2023-06-19 16:12:15 +03:00
Bogdan
3a0ca45aa9 Fix sorting queue items by size 2023-06-18 15:00:49 +03:00
Bogdan
595efd498e Close database connections in housekeeping tasks
Co-authored-by: ferencmarkizay <ferencmarkizay@gmail.com>
2023-06-18 15:00:11 +03:00
Bogdan
dea1060d61 Bump version to 0.2.0 2023-06-18 07:18:42 +03:00
Weblate
f6049b8bf2 Translated using Weblate (Portuguese (Brazil)) [skip ci]
Currently translated at 100.0% (942 of 942 strings)

Co-authored-by: Havok Dan <havokdan@yahoo.com.br>
Translate-URL: https://translate.servarr.com/projects/servarr/readarr/pt_BR/
Translation: Servarr/Readarr
2023-06-18 04:53:21 +03:00
Bogdan
53ced38221 New: Improved page loading errors
Closes #2605
2023-06-17 00:51:10 +03:00
Tristan Kennedy
3a3cf8511e Added padding to search tab to maintain visual consistancy
(cherry picked from commit 55ef505d740a9aadc7f161274006e150b0d9cf8f)
2023-06-17 00:50:55 +03:00
Servarr
9ec913337d Automated API Docs update [skip ci] 2023-06-15 13:26:07 +03:00
Weblate
9a2120ae92 Update translation files [skip ci]
Updated by "Cleanup translation files" hook in Weblate.

Translated using Weblate (Norwegian Bokmål) [skip ci]

Currently translated at 16.0% (151 of 942 strings)

Translated using Weblate (Portuguese (Brazil)) [skip ci]

Currently translated at 100.0% (942 of 942 strings)

Translated using Weblate (Turkish) [skip ci]

Currently translated at 61.6% (581 of 942 strings)

Translated using Weblate (Romanian) [skip ci]

Currently translated at 59.9% (565 of 942 strings)

Translated using Weblate (Polish) [skip ci]

Currently translated at 65.9% (621 of 942 strings)

Translated using Weblate (Korean) [skip ci]

Currently translated at 60.9% (574 of 942 strings)

Translated using Weblate (Japanese) [skip ci]

Currently translated at 61.4% (579 of 942 strings)

Translated using Weblate (Icelandic) [skip ci]

Currently translated at 61.5% (580 of 942 strings)

Translated using Weblate (Hindi) [skip ci]

Currently translated at 61.4% (579 of 942 strings)

Translated using Weblate (Hebrew) [skip ci]

Currently translated at 64.0% (603 of 942 strings)

Translated using Weblate (French) [skip ci]

Currently translated at 78.5% (740 of 942 strings)

Translated using Weblate (Finnish) [skip ci]

Currently translated at 88.1% (830 of 942 strings)

Translated using Weblate (Danish) [skip ci]

Currently translated at 61.9% (584 of 942 strings)

Translated using Weblate (Czech) [skip ci]

Currently translated at 61.6% (581 of 942 strings)

Translated using Weblate (Bulgarian) [skip ci]

Currently translated at 61.5% (580 of 942 strings)

Translated using Weblate (Arabic) [skip ci]

Currently translated at 61.6% (581 of 942 strings)

Translated using Weblate (Spanish) [skip ci]

Currently translated at 68.2% (643 of 942 strings)

Co-authored-by: Anonymous <noreply@weblate.org>
Co-authored-by: Havok Dan <havokdan@yahoo.com.br>
Co-authored-by: Weblate <noreply@weblate.org>
Co-authored-by: fatalicus <fatalicus@gmail.com>
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/hi/
Translate-URL: https://translate.servarr.com/projects/servarr/readarr/is/
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/nb_NO/
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/tr/
Translation: Servarr/Readarr
2023-06-15 13:16:52 +03:00
Bogdan
818d3a94d5 Add skip ci to API docs update commit 2023-06-15 13:15:37 +03:00
Bogdan
4e493b74e6 Update cleansing rules for RSS TL feed and homedir for Mac
(cherry picked from commit e5ff4aafa3f0b855fec332788e9fc490a03dfce3)

Closes #2593
2023-06-15 13:06:26 +03:00
Bogdan
c7eaf1e85c Update translations
(cherry picked from commit 26031389757f6b5270bbe5591101b08e58debb73)

Closes #2599
2023-06-15 13:02:50 +03:00
Bogdan
31fe15c911 Add HelpTextWarning support in FieldDefinition
(cherry picked from commit 0e07d54ee77d5f83716e17b6757e23f38ff73694)

Closes #2595
2023-06-15 13:00:42 +03:00
Bogdan
2c36a6c25f Require ApiKey for all actions in SonarrImport
(cherry picked from commit 19b8fbe13bf584b915a05fe9fc87622adbaee0b7)

Closes #2600
2023-06-15 12:58:43 +03:00
Bogdan
6af56f7a15 Fixed: Treat redirects as errors in Readarr Import List
(cherry picked from commit 059a156f4a34c6b9cbe139fa1973b814e8a534ae)

Closes #2601
2023-06-15 12:57:09 +03:00
Qstick
6e13191c25 Fixed: Correctly handle 302 and 303 redirects in HttpClient
(cherry picked from commit ed7c5a937f4b50fcdf819e8fe347c8c0bc6bd2e7)

(cherry picked from commit 11bd764a75d3b97117098738d3489c4b3329738f)
2023-06-14 08:13:20 +03:00
bakerboy448
921ddfc962 Fixed: Handle checkingResumeData state form qBittorrent
(cherry picked from commit 8d8a16225ff7772ccb57784f272ca31e28bb8455)
2023-06-14 08:12:58 +03:00
Weblate
22f977401a Update translation files [skip ci]
Updated by "Cleanup translation files" hook in Weblate.

Co-authored-by: Weblate <noreply@weblate.org>
Translate-URL: https://translate.servarr.com/projects/servarr/readarr/
Translation: Servarr/Readarr
2023-06-13 01:38:53 +03:00
Bogdan
113d9a07ef Update translations 2023-06-13 01:35:03 +03:00
Qstick
0560d65ea1 Update Remote Path Mapping delete modal title
(cherry picked from commit 18716a00516a971f7f2eb369b920266bea24fe08)

Closes #2588
Fixes #2587
2023-06-12 22:31:04 +03:00
Weblate
94ff105104 Translated using Weblate (Indonesian) [skip ci]
Currently translated at 3.7% (35 of 928 strings)

Co-authored-by: liimee <git.taaa@fedora.email>
Translate-URL: https://translate.servarr.com/projects/servarr/readarr/id/
Translation: Servarr/Readarr
2023-06-12 21:58:09 +03:00
Bogdan
9bcf258aa9 Bump version to 0.1.9 2023-06-11 09:38:16 +03:00
Bogdan
54985bd4ca Use more specific styling for kinds in ProgressBar
(cherry picked from commit dd31c913d2a974d95f3be251714ce749cfd99a72)
2023-06-10 02:07:40 +03:00
Weblate
9e4d551f08 Translated using Weblate (Russian) [skip ci]
Currently translated at 67.7% (629 of 928 strings)

Co-authored-by: Андрей <andryfly7@gmail.com>
Translate-URL: https://translate.servarr.com/projects/servarr/readarr/ru/
Translation: Servarr/Readarr
2023-06-10 02:02:58 +03:00
Weblate
8390da1c2a Translated using Weblate (Greek) [skip ci]
Currently translated at 99.8% (927 of 928 strings)

Translated using Weblate (French) [skip ci]

Currently translated at 78.4% (728 of 928 strings)

Translated using Weblate (Portuguese) [skip ci]

Currently translated at 77.0% (715 of 928 strings)

Translated using Weblate (German) [skip ci]

Currently translated at 98.3% (913 of 928 strings)

Translated using Weblate (German) [skip ci]

Currently translated at 98.3% (913 of 928 strings)

Translated using Weblate (Indonesian) [skip ci]

Currently translated at 3.5% (33 of 928 strings)

Translated using Weblate (Croatian) [skip ci]

Currently translated at 15.7% (146 of 928 strings)

Translated using Weblate (Ukrainian) [skip ci]

Currently translated at 66.7% (619 of 928 strings)

Translated using Weblate (Norwegian Bokmål) [skip ci]

Currently translated at 15.7% (146 of 928 strings)

Translated using Weblate (Catalan) [skip ci]

Currently translated at 67.7% (629 of 928 strings)

Translated using Weblate (Portuguese (Brazil)) [skip ci]

Currently translated at 100.0% (928 of 928 strings)

Translated using Weblate (Portuguese (Brazil)) [skip ci]

Currently translated at 100.0% (928 of 928 strings)

Translated using Weblate (Portuguese) [skip ci]

Currently translated at 76.9% (714 of 928 strings)

Translated using Weblate (Vietnamese) [skip ci]

Currently translated at 61.5% (571 of 928 strings)

Translated using Weblate (Turkish) [skip ci]

Currently translated at 61.6% (572 of 928 strings)

Translated using Weblate (Swedish) [skip ci]

Currently translated at 86.7% (805 of 928 strings)

Translated using Weblate (Russian) [skip ci]

Currently translated at 67.6% (628 of 928 strings)

Translated using Weblate (Polish) [skip ci]

Currently translated at 65.7% (610 of 928 strings)

Translated using Weblate (Japanese) [skip ci]

Currently translated at 61.5% (571 of 928 strings)

Translated using Weblate (Italian) [skip ci]

Currently translated at 73.0% (678 of 928 strings)

Translated using Weblate (Hungarian) [skip ci]

Currently translated at 98.0% (910 of 928 strings)

Translated using Weblate (French) [skip ci]

Currently translated at 78.3% (727 of 928 strings)

Translated using Weblate (Greek) [skip ci]

Currently translated at 98.5% (915 of 928 strings)

Translated using Weblate (German) [skip ci]

Currently translated at 98.1% (911 of 928 strings)

Translated using Weblate (German) [skip ci]

Currently translated at 98.1% (911 of 928 strings)

Translated using Weblate (Dutch) [skip ci]

Currently translated at 68.2% (633 of 928 strings)

Translated using Weblate (Dutch) [skip ci]

Currently translated at 68.2% (633 of 928 strings)

Translated using Weblate (French) [skip ci]

Currently translated at 78.3% (727 of 928 strings)

Translated using Weblate (Thai) [skip ci]

Currently translated at 61.5% (571 of 928 strings)

Translated using Weblate (Romanian) [skip ci]

Currently translated at 60.0% (557 of 928 strings)

Translated using Weblate (Icelandic) [skip ci]

Currently translated at 61.5% (571 of 928 strings)

Translated using Weblate (Hindi) [skip ci]

Currently translated at 61.5% (571 of 928 strings)

Translated using Weblate (Hebrew) [skip ci]

Currently translated at 64.0% (594 of 928 strings)

Translated using Weblate (Finnish) [skip ci]

Currently translated at 88.2% (819 of 928 strings)

Translated using Weblate (Danish) [skip ci]

Currently translated at 61.9% (575 of 928 strings)

Translated using Weblate (Bulgarian) [skip ci]

Currently translated at 61.5% (571 of 928 strings)

Translated using Weblate (Spanish) [skip ci]

Currently translated at 67.9% (631 of 928 strings)

Translated using Weblate (Arabic) [skip ci]

Currently translated at 61.6% (572 of 928 strings)

Translated using Weblate (Czech) [skip ci]

Currently translated at 61.6% (572 of 928 strings)

Update translation files [skip ci]

Updated by "Cleanup translation files" hook in Weblate.

Co-authored-by: Anonymous <noreply@weblate.org>
Co-authored-by: Havok Dan <havokdan@yahoo.com.br>
Co-authored-by: Jens <jensmahnke@me.com>
Co-authored-by: MoowGlax <matthieu.derouet.pro@gmail.com>
Co-authored-by: Thijs Waalen <contact@thijswaalen.com>
Co-authored-by: Thodoris Kalatzis <teo.kal@hotmail.com>
Co-authored-by: Weblate <noreply@weblate.org>
Co-authored-by: emacsdias <emacs.dias@gmail.com>
Co-authored-by: foXaCe <foxace66@gmail.com>
Co-authored-by: reloxx <reloxx@interia.pl>
Co-authored-by: splifter <a.strahlke@gmail.com>
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/cs/
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/el/
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/hi/
Translate-URL: https://translate.servarr.com/projects/servarr/readarr/hr/
Translate-URL: https://translate.servarr.com/projects/servarr/readarr/hu/
Translate-URL: https://translate.servarr.com/projects/servarr/readarr/id/
Translate-URL: https://translate.servarr.com/projects/servarr/readarr/is/
Translate-URL: https://translate.servarr.com/projects/servarr/readarr/it/
Translate-URL: https://translate.servarr.com/projects/servarr/readarr/ja/
Translate-URL: https://translate.servarr.com/projects/servarr/readarr/nb_NO/
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/ru/
Translate-URL: https://translate.servarr.com/projects/servarr/readarr/sv/
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/uk/
Translate-URL: https://translate.servarr.com/projects/servarr/readarr/vi/
Translation: Servarr/Readarr
2023-06-07 19:21:06 +03:00
Qstick
44ae043c58 Bump version to 0.1.8 2023-06-04 14:48:35 -05:00
Bogdan
bb7e2fc70c Fixed: Don't log handled exceptions in API 2023-05-31 06:51:57 +03:00
Bogdan
b05938a9a8 Revert "Fixed: Don't log handled exceptions in API"
This reverts commit fecb3895ed.
2023-05-31 06:51:56 +03:00
Weblate
1e42ac572e Update translation files [skip ci]
Updated by "Cleanup translation files" hook in Weblate.

Co-authored-by: Weblate <noreply@weblate.org>
Translate-URL: https://translate.servarr.com/projects/servarr/readarr/
Translation: Servarr/Readarr
2023-05-28 22:28:24 +03:00
Servarr
649dd0bda0 Automated API Docs update 2023-05-28 22:27:52 +03:00
Bogdan
de24aef059 Deserialize asynchronously in LocalizationService
(cherry picked from commit 86a7f7bd54aa733b0e3abd3ec9463a85dd348118)

Closes #2561
2023-05-28 22:15:01 +03:00
Qstick
10766dd227 Faster tag view in UI for large libraries
(cherry picked from commit b050e1d2eb3bff9e28e7a1545d121be091789308)

Closes #2571
2023-05-28 22:08:34 +03:00
Bogdan
257d279e43 Fixed: Enforce validation warnings
(cherry picked from commit 48ee1158ad4213fd0690842e2672f52d08f7ad26)

Closes #2570
2023-05-28 22:02:41 +03:00
Bogdan
1db333088a Add minimum length as const in ApiKeyValidationCheck
(cherry picked from commit b06269544cfa11015f3fb938fd8c2ef07d9cac4a)

Closes #2565
2023-05-28 22:00:31 +03:00
Bogdan
d1aff31593 Use 'var' instead of explicit type
(cherry picked from commit 12374f7f0038e5b25548f5ab3f71122410832393)

Closes #2559
2023-05-28 21:59:25 +03:00
Bogdan
89dd4d3271 Inline 'out' variable declarations
(cherry picked from commit 281add47de1d3940990156c841362125dea9cc7d)

Closes #2558
2023-05-28 21:55:43 +03:00
Bogdan
fc6c78a54e Standardize variable declaration
(cherry picked from commit 909f2ded6b75998fa8e1addd0dcf849279e7b120)

Closes #2556
Closes #2557
2023-05-28 21:47:48 +03:00
Bogdan
c98f4512df Enforce rule IDE0005 on build
(cherry picked from commit 6b1e4ef81938d264a2ddc8b626b0502f799aa640)

Closes #2555
2023-05-28 21:46:59 +03:00
Matthew Strapp
0a43481aed Fixed: Use relative paths instead of absolute paths for webmanifest
(cherry picked from commit 8e771f95ade919a8f1ed7b48675f032a6c508cb2)
2023-05-28 21:42:27 +03:00
Bogdan
df6c142250 Simplify ShouldHaveApiKey and HasErrors
(cherry picked from commit 7343616a47cd538bba4c9128d2c1094561f9b3a5)
2023-05-28 21:41:07 +03:00
Weblate
58cf93e360 Update translation files [skip ci]
Updated by "Cleanup translation files" hook in Weblate.

Translated using Weblate (Icelandic) [skip ci]

Currently translated at 61.4% (567 of 922 strings)

Translated using Weblate (Hindi) [skip ci]

Currently translated at 61.4% (567 of 922 strings)

Translated using Weblate (Hebrew) [skip ci]

Currently translated at 63.9% (590 of 922 strings)

Translated using Weblate (Finnish) [skip ci]

Currently translated at 88.3% (815 of 922 strings)

Translated using Weblate (Danish) [skip ci]

Currently translated at 61.9% (571 of 922 strings)

Translated using Weblate (Czech) [skip ci]

Currently translated at 61.6% (568 of 922 strings)

Translated using Weblate (Bulgarian) [skip ci]

Currently translated at 61.4% (567 of 922 strings)

Translated using Weblate (Arabic) [skip ci]

Currently translated at 61.6% (568 of 922 strings)

Translated using Weblate (Spanish) [skip ci]

Currently translated at 68.0% (627 of 922 strings)

Update translation files [skip ci]

Updated by "Cleanup translation files" hook in Weblate.

Co-authored-by: Anonymous <noreply@weblate.org>
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/he/
Translate-URL: https://translate.servarr.com/projects/servarr/readarr/hi/
Translate-URL: https://translate.servarr.com/projects/servarr/readarr/is/
Translation: Servarr/Readarr
2023-05-28 10:25:24 -05:00
Qstick
7be282ad12 Bump version to 0.1.7 2023-05-28 09:27:02 -05:00
479 changed files with 9516 additions and 4249 deletions

View File

@@ -36,12 +36,18 @@ dotnet_naming_style.instance_field_style.capitalization = camel_case
dotnet_naming_style.instance_field_style.required_prefix = _
# Prefer "var" everywhere
csharp_style_var_for_built_in_types = true:suggestion
csharp_style_var_when_type_is_apparent = true:suggestion
csharp_style_var_elsewhere = true:suggestion
csharp_style_var_for_built_in_types = true
csharp_style_var_when_type_is_apparent = true
csharp_style_var_elsewhere = true
# Prefer "out" variables to be declared inline
csharp_style_inlined_variable_declaration = true
# Using directive is unnecessary.
dotnet_diagnostic.IDE0005.severity = error
# Use var instead of explicit type
dotnet_diagnostic.IDE0007.severity = error
# Inline variable declaration
dotnet_diagnostic.IDE0018.severity = error
# Stylecop Rules
dotnet_diagnostic.SA0001.severity = none
@@ -269,7 +275,7 @@ dotnet_diagnostic.CA5397.severity = suggestion
[*.{js,html,js,hbs,less,css}]
[*.{js,html,hbs,less,css,ts,tsx}]
charset = utf-8
trim_trailing_whitespace = true
insert_final_newline = true

View File

@@ -76,7 +76,7 @@ body:
- type: checkboxes
attributes:
label: Trace Logs have been provided as applicable. Reports may be closed if the required logs are not provided.
description: Trace logs are generally required for all bug reports
description: Trace logs are generally required for all bug reports and contain `trace`. Info logs are invalid for bug reports and do not contain `debug` nor `trace`
options:
- label: I have followed the steps in the wiki link above and provided the required trace logs that are relevant and show this issue.
- label: I have read and followed the steps in the wiki link above and provided the required trace logs - the logs contain `trace` - that are relevant and show this issue.
required: true

View File

@@ -9,7 +9,7 @@ variables:
testsFolder: './_tests'
yarnCacheFolder: $(Pipeline.Workspace)/.yarn
nugetCacheFolder: $(Pipeline.Workspace)/.nuget/packages
majorVersion: '0.1.6'
majorVersion: '0.3.1'
minorVersion: $[counter('minorVersion', 1)]
readarrVersion: '$(majorVersion).$(minorVersion)'
buildName: '$(Build.SourceBranchName).$(readarrVersion)'
@@ -382,7 +382,7 @@ stages:
- bash: |
echo "Uploading source maps to sentry"
curl -sL https://sentry.io/get-cli/ | bash
RELEASENAME="${READARRVERSION}-${BUILD_SOURCEBRANCHNAME}"
RELEASENAME="Readarr@${READARRVERSION}-${BUILD_SOURCEBRANCHNAME}"
sentry-cli releases new --finalize -p readarr -p readarr-ui -p readarr-update "${RELEASENAME}"
sentry-cli releases -p readarr-ui files "${RELEASENAME}" upload-sourcemaps _output/UI/ --rewrite
sentry-cli releases set-commits --auto "${RELEASENAME}"
@@ -984,7 +984,7 @@ stages:
git status
if git status | grep modified
then
git commit -am 'Automated API Docs update'
git commit -am 'Automated API Docs update [skip ci]'
git push -f --set-upstream origin api-docs
curl -X POST -H "Authorization: token ${GITHUBTOKEN}" -H "Accept: application/vnd.github.v3+json" https://api.github.com/repos/readarr/readarr/pulls -d '{"head":"api-docs","base":"develop","title":"Update API docs"}'
else

View File

@@ -391,22 +391,21 @@ then
fi
fi
if [ "$FRONTEND" = "YES" ];
if [[ "$LINT" = "YES" || "$FRONTEND" = "YES" ]];
then
YarnInstall
RunWebpack
fi
if [ "$LINT" = "YES" ];
then
if [ -z "$FRONTEND" ];
then
YarnInstall
fi
LintUI
fi
if [ "$FRONTEND" = "YES" ];
then
RunWebpack
fi
if [ "$PACKAGES" = "YES" ];
then
UpdateVersionNumber

View File

@@ -67,23 +67,23 @@ module.exports = (env) => {
output: {
path: distFolder,
publicPath: '/',
filename: '[name].js',
filename: '[name]-[contenthash].js',
sourceMapFilename: '[file].map'
},
optimization: {
moduleIds: 'deterministic',
chunkIds: 'named',
splitChunks: {
chunks: 'initial',
name: 'vendors'
}
chunkIds: isProduction ? 'deterministic' : 'named'
},
performance: {
hints: false
},
experiments: {
topLevelAwait: true
},
plugins: [
new webpack.DefinePlugin({
__DEV__: !isProduction,
@@ -91,7 +91,8 @@ module.exports = (env) => {
}),
new MiniCssExtractPlugin({
filename: 'Content/styles.css'
filename: 'Content/styles.css',
chunkFilename: 'Content/[id]-[chunkhash].css'
}),
new HtmlWebpackPlugin({

View File

@@ -1,5 +1,6 @@
import PropTypes from 'prop-types';
import React, { Component } from 'react';
import Alert from 'Components/Alert';
import LoadingIndicator from 'Components/Loading/LoadingIndicator';
import ConfirmModal from 'Components/Modal/ConfirmModal';
import PageContent from 'Components/Page/PageContent';
@@ -161,16 +162,16 @@ class Blocklist extends Component {
{
!isAnyFetching && !!error &&
<div>
<Alert kind={kinds.DANGER}>
{translate('UnableToLoadBlocklist')}
</div>
</Alert>
}
{
isAllPopulated && !error && !items.length &&
<div>
<Alert kind={kinds.INFO}>
{translate('NoHistoryBlocklist')}
</div>
</Alert>
}
{
@@ -214,7 +215,7 @@ class Blocklist extends Component {
isOpen={isConfirmRemoveModalOpen}
kind={kinds.DANGER}
title={translate('RemoveSelected')}
message={translate('RemoveSelectedMessageText')}
message={translate('RemoveSelectedItemBlocklistMessageText')}
confirmLabel={translate('RemoveSelected')}
onConfirm={this.onRemoveSelectedConfirmed}
onCancel={this.onConfirmRemoveModalClose}

View File

@@ -9,7 +9,7 @@ import Link from 'Components/Link/Link';
import { icons } from 'Helpers/Props';
import formatDateTime from 'Utilities/Date/formatDateTime';
import formatAge from 'Utilities/Number/formatAge';
import formatPreferredWordScore from 'Utilities/Number/formatPreferredWordScore';
import formatCustomFormatScore from 'Utilities/Number/formatCustomFormatScore';
import translate from 'Utilities/String/translate';
import styles from './HistoryDetails.css';
@@ -108,7 +108,7 @@ function HistoryDetails(props) {
customFormatScore && customFormatScore !== '0' ?
<DescriptionListItem
title={translate('CustomFormatScore')}
data={formatPreferredWordScore(customFormatScore)}
data={formatCustomFormatScore(customFormatScore)}
/> :
null
}
@@ -225,7 +225,7 @@ function HistoryDetails(props) {
customFormatScore && customFormatScore !== '0' ?
<DescriptionListItem
title={translate('CustomFormatScore')}
data={formatPreferredWordScore(customFormatScore)}
data={formatCustomFormatScore(customFormatScore)}
/> :
null
}
@@ -271,7 +271,7 @@ function HistoryDetails(props) {
customFormatScore && customFormatScore !== '0' ?
<DescriptionListItem
title={translate('CustomFormatScore')}
data={formatPreferredWordScore(customFormatScore)}
data={formatCustomFormatScore(customFormatScore)}
/> :
null
}

View File

@@ -1,5 +1,6 @@
import PropTypes from 'prop-types';
import React, { Component } from 'react';
import Alert from 'Components/Alert';
import LoadingIndicator from 'Components/Loading/LoadingIndicator';
import FilterMenu from 'Components/Menu/FilterMenu';
import PageContent from 'Components/Page/PageContent';
@@ -11,7 +12,7 @@ import Table from 'Components/Table/Table';
import TableBody from 'Components/Table/TableBody';
import TableOptionsModalWrapper from 'Components/Table/TableOptions/TableOptionsModalWrapper';
import TablePager from 'Components/Table/TablePager';
import { align, icons } from 'Helpers/Props';
import { align, icons, kinds } from 'Helpers/Props';
import translate from 'Utilities/String/translate';
import HistoryRowConnector from './HistoryRowConnector';
@@ -85,9 +86,9 @@ class History extends Component {
{
!isFetchingAny && hasError &&
<div>
<Alert kind={kinds.DANGER}>
{translate('UnableToLoadHistory')}
</div>
</Alert>
}
{
@@ -95,9 +96,9 @@ class History extends Component {
// wait for the books to populate because they are never coming.
isPopulated && !hasError && !items.length &&
<div>
No history found
</div>
<Alert kind={kinds.INFO}>
{translate('NoHistory')}
</Alert>
}
{

View File

@@ -8,8 +8,9 @@ import IconButton from 'Components/Link/IconButton';
import RelativeDateCellConnector from 'Components/Table/Cells/RelativeDateCellConnector';
import TableRowCell from 'Components/Table/Cells/TableRowCell';
import TableRow from 'Components/Table/TableRow';
import { icons } from 'Helpers/Props';
import formatPreferredWordScore from 'Utilities/Number/formatPreferredWordScore';
import Tooltip from 'Components/Tooltip/Tooltip';
import { icons, tooltipPositions } from 'Helpers/Props';
import formatCustomFormatScore from 'Utilities/Number/formatCustomFormatScore';
import HistoryDetailsModal from './Details/HistoryDetailsModal';
import HistoryEventTypeCell from './HistoryEventTypeCell';
import styles from './HistoryRow.css';
@@ -57,6 +58,7 @@ class HistoryRow extends Component {
book,
quality,
customFormats,
customFormatScore,
qualityCutoffNotMet,
eventType,
sourceTitle,
@@ -177,7 +179,14 @@ class HistoryRow extends Component {
key={name}
className={styles.customFormatScore}
>
{formatPreferredWordScore(data.customFormatScore)}
<Tooltip
anchor={formatCustomFormatScore(
customFormatScore,
customFormats.length
)}
tooltip={<BookFormats formats={customFormats} />}
position={tooltipPositions.BOTTOM}
/>
</TableRowCell>
);
}
@@ -244,6 +253,7 @@ HistoryRow.propTypes = {
book: PropTypes.object,
quality: PropTypes.object.isRequired,
customFormats: PropTypes.arrayOf(PropTypes.object),
customFormatScore: PropTypes.number.isRequired,
qualityCutoffNotMet: PropTypes.bool.isRequired,
eventType: PropTypes.string.isRequired,
sourceTitle: PropTypes.string.isRequired,
@@ -257,4 +267,8 @@ HistoryRow.propTypes = {
onMarkAsFailedPress: PropTypes.func.isRequired
};
HistoryRow.defaultProps = {
customFormats: []
};
export default HistoryRow;

View File

@@ -1,6 +1,7 @@
import _ from 'lodash';
import PropTypes from 'prop-types';
import React, { Component } from 'react';
import Alert from 'Components/Alert';
import LoadingIndicator from 'Components/Loading/LoadingIndicator';
import PageContent from 'Components/Page/PageContent';
import PageContentBody from 'Components/Page/PageContentBody';
@@ -12,7 +13,7 @@ import Table from 'Components/Table/Table';
import TableBody from 'Components/Table/TableBody';
import TableOptionsModalWrapper from 'Components/Table/TableOptions/TableOptionsModalWrapper';
import TablePager from 'Components/Table/TablePager';
import { align, icons } from 'Helpers/Props';
import { align, icons, kinds } from 'Helpers/Props';
import getRemovedItems from 'Utilities/Object/getRemovedItems';
import hasDifferentItems from 'Utilities/Object/hasDifferentItems';
import translate from 'Utilities/String/translate';
@@ -233,17 +234,17 @@ class Queue extends Component {
{
!isRefreshing && hasError ?
<div>
<Alert kind={kinds.DANGER}>
{translate('FailedToLoadQueue')}
</div> :
</Alert> :
null
}
{
isAllPopulated && !hasError && !items.length ?
<div>
<Alert kind={kinds.INFO}>
{translate('QueueIsEmpty')}
</div> :
</Alert> :
null
}

View File

@@ -16,6 +16,12 @@
width: 150px;
}
.customFormatScore {
composes: cell from '~Components/Table/Cells/TableRowCell.css';
width: 55px;
}
.actions {
composes: cell from '~Components/Table/Cells/TableRowCell.css';

View File

@@ -2,6 +2,7 @@
// Please do not change this file!
interface CssExports {
'actions': string;
'customFormatScore': string;
'progress': string;
'protocol': string;
'quality': string;

View File

@@ -14,9 +14,11 @@ import TableRowCell from 'Components/Table/Cells/TableRowCell';
import TableSelectCell from 'Components/Table/Cells/TableSelectCell';
import TableRow from 'Components/Table/TableRow';
import Popover from 'Components/Tooltip/Popover';
import Tooltip from 'Components/Tooltip/Tooltip';
import { icons, kinds, tooltipPositions } from 'Helpers/Props';
import InteractiveImportModal from 'InteractiveImport/InteractiveImportModal';
import formatBytes from 'Utilities/Number/formatBytes';
import formatCustomFormatScore from 'Utilities/Number/formatCustomFormatScore';
import translate from 'Utilities/String/translate';
import QueueStatusCell from './QueueStatusCell';
import RemoveQueueItemModal from './RemoveQueueItemModal';
@@ -44,14 +46,14 @@ class QueueRow extends Component {
this.setState({ isRemoveQueueItemModalOpen: true });
};
onRemoveQueueItemModalConfirmed = (blocklist, skipredownload) => {
onRemoveQueueItemModalConfirmed = (blocklist, skipRedownload) => {
const {
onRemoveQueueItemPress,
onQueueRowModalOpenOrClose
} = this.props;
onQueueRowModalOpenOrClose(false);
onRemoveQueueItemPress(blocklist, skipredownload);
onRemoveQueueItemPress(blocklist, skipRedownload);
this.setState({ isRemoveQueueItemModalOpen: false });
};
@@ -91,6 +93,7 @@ class QueueRow extends Component {
book,
quality,
customFormats,
customFormatScore,
protocol,
indexer,
outputPath,
@@ -222,6 +225,24 @@ class QueueRow extends Component {
);
}
if (name === 'customFormatScore') {
return (
<TableRowCell
key={name}
className={styles.customFormatScore}
>
<Tooltip
anchor={formatCustomFormatScore(
customFormatScore,
customFormats.length
)}
tooltip={<BookFormats formats={customFormats} />}
position={tooltipPositions.BOTTOM}
/>
</TableRowCell>
);
}
if (name === 'protocol') {
return (
<TableRowCell key={name}>
@@ -392,6 +413,7 @@ QueueRow.propTypes = {
book: PropTypes.object,
quality: PropTypes.object.isRequired,
customFormats: PropTypes.arrayOf(PropTypes.object),
customFormatScore: PropTypes.number.isRequired,
protocol: PropTypes.string.isRequired,
indexer: PropTypes.string,
outputPath: PropTypes.string,
@@ -416,6 +438,7 @@ QueueRow.propTypes = {
};
QueueRow.defaultProps = {
customFormats: [],
isGrabbing: false,
isRemoving: false
};

View File

@@ -23,7 +23,7 @@ class RemoveQueueItemModal extends Component {
this.state = {
remove: true,
blocklist: false,
skipredownload: false
skipRedownload: false
};
}
@@ -34,7 +34,7 @@ class RemoveQueueItemModal extends Component {
this.setState({
remove: true,
blocklist: false,
skipredownload: false
skipRedownload: false
});
};
@@ -49,8 +49,8 @@ class RemoveQueueItemModal extends Component {
this.setState({ blocklist: value });
};
onSkipReDownloadChange = ({ value }) => {
this.setState({ skipredownload: value });
onSkipRedownloadChange = ({ value }) => {
this.setState({ skipRedownload: value });
};
onRemoveConfirmed = () => {
@@ -76,7 +76,7 @@ class RemoveQueueItemModal extends Component {
isPending
} = this.props;
const { remove, blocklist, skipredownload } = this.state;
const { remove, blocklist, skipRedownload } = this.state;
return (
<Modal
@@ -124,7 +124,7 @@ class RemoveQueueItemModal extends Component {
type={inputTypes.CHECK}
name="blocklist"
value={blocklist}
helpText={translate('BlocklistHelpText')}
helpText={translate('BlocklistReleaseHelpText')}
onChange={this.onBlocklistChange}
/>
</FormGroup>
@@ -137,10 +137,10 @@ class RemoveQueueItemModal extends Component {
</FormLabel>
<FormInputGroup
type={inputTypes.CHECK}
name="skipredownload"
value={skipredownload}
helpText={translate('SkipredownloadHelpText')}
onChange={this.onSkipReDownloadChange}
name="skipRedownload"
value={skipRedownload}
helpText={translate('SkipRedownloadHelpText')}
onChange={this.onSkipRedownloadChange}
/>
</FormGroup>
}

View File

@@ -24,7 +24,7 @@ class RemoveQueueItemsModal extends Component {
this.state = {
remove: true,
blocklist: false,
skipredownload: false
skipRedownload: false
};
}
@@ -35,7 +35,7 @@ class RemoveQueueItemsModal extends Component {
this.setState({
remove: true,
blocklist: false,
skipredownload: false
skipRedownload: false
});
};
@@ -50,8 +50,8 @@ class RemoveQueueItemsModal extends Component {
this.setState({ blocklist: value });
};
onSkipReDownloadChange = ({ value }) => {
this.setState({ skipredownload: value });
onSkipRedownloadChange = ({ value }) => {
this.setState({ skipRedownload: value });
};
onRemoveConfirmed = () => {
@@ -77,7 +77,7 @@ class RemoveQueueItemsModal extends Component {
allPending
} = this.props;
const { remove, blocklist, skipredownload } = this.state;
const { remove, blocklist, skipRedownload } = this.state;
return (
<Modal
@@ -89,12 +89,12 @@ class RemoveQueueItemsModal extends Component {
onModalClose={this.onModalClose}
>
<ModalHeader>
Remove Selected Item{selectedCount > 1 ? 's' : ''}
{selectedCount > 1 ? translate('RemoveSelectedItems') : translate('RemoveSelectedItem')}
</ModalHeader>
<ModalBody>
<div className={styles.message}>
Are you sure you want to remove {selectedCount} item{selectedCount > 1 ? 's' : ''} from the queue?
{selectedCount > 1 ? translate('RemoveSelectedItemsQueueMessageText', selectedCount) : translate('RemoveSelectedItemQueueMessageText')}
</div>
{
@@ -118,14 +118,14 @@ class RemoveQueueItemsModal extends Component {
<FormGroup>
<FormLabel>
Add Release{selectedCount > 1 ? 's' : ''} To Blocklist
{selectedCount > 1 ? translate('BlocklistReleases') : translate('BlocklistRelease')}
</FormLabel>
<FormInputGroup
type={inputTypes.CHECK}
name="blocklist"
value={blocklist}
helpText={translate('BlocklistHelpText')}
helpText={translate('BlocklistReleaseHelpText')}
onChange={this.onBlocklistChange}
/>
</FormGroup>
@@ -138,10 +138,10 @@ class RemoveQueueItemsModal extends Component {
</FormLabel>
<FormInputGroup
type={inputTypes.CHECK}
name="skipredownload"
value={skipredownload}
helpText={translate('SkipredownloadHelpText')}
onChange={this.onSkipReDownloadChange}
name="skipRedownload"
value={skipRedownload}
helpText={translate('SkipRedownloadHelpText')}
onChange={this.onSkipRedownloadChange}
/>
</FormGroup>
}
@@ -150,14 +150,14 @@ class RemoveQueueItemsModal extends Component {
<ModalFooter>
<Button onPress={this.onModalClose}>
Close
{translate('Close')}
</Button>
<Button
kind={kinds.DANGER}
onPress={this.onRemoveConfirmed}
>
Remove
{translate('Remove')}
</Button>
</ModalFooter>
</ModalContent>

View File

@@ -7,13 +7,13 @@ import PageConnector from 'Components/Page/PageConnector';
import ApplyTheme from './ApplyTheme';
import AppRoutes from './AppRoutes';
function App({ store, history }) {
function App({ store, history, hasTranslationsError }) {
return (
<DocumentTitle title={window.Readarr.instanceName}>
<Provider store={store}>
<ConnectedRouter history={history}>
<ApplyTheme>
<PageConnector>
<PageConnector hasTranslationsError={hasTranslationsError}>
<AppRoutes app={App} />
</PageConnector>
</ApplyTheme>
@@ -25,7 +25,8 @@ function App({ store, history }) {
App.propTypes = {
store: PropTypes.object.isRequired,
history: PropTypes.object.isRequired
history: PropTypes.object.isRequired,
hasTranslationsError: PropTypes.bool.isRequired
};
export default App;

View File

@@ -0,0 +1,5 @@
interface ModelBase {
id: number;
}
export default ModelBase;

View File

@@ -0,0 +1,48 @@
import SortDirection from 'Helpers/Props/SortDirection';
export interface Error {
responseJSON: {
message: string;
};
}
export interface AppSectionDeleteState {
isDeleting: boolean;
deleteError: Error;
}
export interface AppSectionSaveState {
isSaving: boolean;
saveError: Error;
}
export interface PagedAppSectionState {
pageSize: number;
}
export interface AppSectionSchemaState<T> {
isSchemaFetching: boolean;
isSchemaPopulated: boolean;
schemaError: Error;
schema: {
items: T[];
};
}
export interface AppSectionItemState<T> {
isFetching: boolean;
isPopulated: boolean;
error: Error;
item: T;
}
interface AppSectionState<T> {
isFetching: boolean;
isPopulated: boolean;
error: Error;
items: T[];
sortKey: string;
sortDirection: SortDirection;
}
export default AppSectionState;

View File

@@ -0,0 +1,41 @@
import SettingsAppState from './SettingsAppState';
import TagsAppState from './TagsAppState';
interface FilterBuilderPropOption {
id: string;
name: string;
}
export interface FilterBuilderProp<T> {
name: string;
label: string;
type: string;
valueType?: string;
optionsSelector?: (items: T[]) => FilterBuilderPropOption[];
}
export interface PropertyFilter {
key: string;
value: boolean | string | number | string[] | number[];
type: string;
}
export interface Filter {
key: string;
label: string;
filers: PropertyFilter[];
}
export interface CustomFilter {
id: number;
type: string;
label: string;
filers: PropertyFilter[];
}
interface AppState {
settings: SettingsAppState;
tags: TagsAppState;
}
export default AppState;

View File

@@ -0,0 +1,40 @@
import AppSectionState, {
AppSectionDeleteState,
AppSectionSaveState,
} from 'App/State/AppSectionState';
import DownloadClient from 'typings/DownloadClient';
import ImportList from 'typings/ImportList';
import Indexer from 'typings/Indexer';
import Notification from 'typings/Notification';
import { UiSettings } from 'typings/UiSettings';
export interface DownloadClientAppState
extends AppSectionState<DownloadClient>,
AppSectionDeleteState,
AppSectionSaveState {}
export interface ImportListAppState
extends AppSectionState<ImportList>,
AppSectionDeleteState,
AppSectionSaveState {}
export interface IndexerAppState
extends AppSectionState<Indexer>,
AppSectionDeleteState,
AppSectionSaveState {}
export interface NotificationAppState
extends AppSectionState<Notification>,
AppSectionDeleteState {}
export type UiSettingsAppState = AppSectionState<UiSettings>;
interface SettingsAppState {
downloadClients: DownloadClientAppState;
importLists: ImportListAppState;
indexers: IndexerAppState;
notifications: NotificationAppState;
uiSettings: UiSettingsAppState;
}
export default SettingsAppState;

View File

@@ -0,0 +1,12 @@
import ModelBase from 'App/ModelBase';
import AppSectionState, {
AppSectionDeleteState,
} from 'App/State/AppSectionState';
export interface Tag extends ModelBase {
label: string;
}
interface TagsAppState extends AppSectionState<Tag>, AppSectionDeleteState {}
export default TagsAppState;

View File

@@ -392,10 +392,7 @@ class AuthorDetails extends Component {
name={icons.ARROW_UP}
size={30}
title={translate('GoToAuthorListing')}
to={{
pathname: '/',
state: { restoreScrollPosition: true }
}}
to={'/'}
/>
<IconButton

View File

@@ -92,6 +92,7 @@ class AuthorDetailsHeader extends Component {
titleWidth
} = this.state;
const fanartUrl = getFanartUrl(images);
const marqueeWidth = titleWidth - (isSmallScreen ? 85 : 160);
const continuing = status === 'continuing';
@@ -108,9 +109,11 @@ class AuthorDetailsHeader extends Component {
<div className={styles.header} style={{ width }} >
<div
className={styles.backdrop}
style={{
backgroundImage: `url(${getFanartUrl(images)})`
}}
style={
fanartUrl ?
{ backgroundImage: `url(${fanartUrl})` } :
null
}
>
<div className={styles.backdropOverlay} />
</div>

View File

@@ -1,5 +1,6 @@
import PropTypes from 'prop-types';
import React, { Component } from 'react';
import { connect } from 'react-redux';
import MoveAuthorModal from 'Author/MoveAuthor/MoveAuthorModal';
import MetadataProfileSelectInputConnector from 'Components/Form/MetadataProfileSelectInputConnector';
import MonitorNewItemsSelectInput from 'Components/Form/MonitorNewItemsSelectInput';
@@ -9,6 +10,7 @@ import SelectInput from 'Components/Form/SelectInput';
import SpinnerButton from 'Components/Link/SpinnerButton';
import PageContentFooter from 'Components/Page/PageContentFooter';
import { kinds } from 'Helpers/Props';
import { fetchRootFolders } from 'Store/Actions/Settings/rootFolders';
import translate from 'Utilities/String/translate';
import AuthorEditorFooterLabel from './AuthorEditorFooterLabel';
import DeleteAuthorModal from './Delete/DeleteAuthorModal';
@@ -17,6 +19,10 @@ import styles from './AuthorEditorFooter.css';
const NO_CHANGE = 'noChange';
const mapDispatchToProps = {
dispatchFetchRootFolders: fetchRootFolders
};
class AuthorEditorFooter extends Component {
//
@@ -39,6 +45,13 @@ class AuthorEditorFooter extends Component {
};
}
//
// Lifecycle
componentDidMount() {
this.props.dispatchFetchRootFolders();
}
componentDidUpdate(prevProps) {
const {
isSaving,
@@ -160,9 +173,9 @@ class AuthorEditorFooter extends Component {
} = this.state;
const monitoredOptions = [
{ key: NO_CHANGE, value: 'No Change', disabled: true },
{ key: 'monitored', value: 'Monitored' },
{ key: 'unmonitored', value: 'Unmonitored' }
{ key: NO_CHANGE, value: translate('NoChange'), disabled: true },
{ key: 'monitored', value: translate('Monitored') },
{ key: 'unmonitored', value: translate('Unmonitored') }
];
return (
@@ -341,7 +354,8 @@ AuthorEditorFooter.propTypes = {
showMetadataProfile: PropTypes.bool.isRequired,
onSaveSelected: PropTypes.func.isRequired,
onOrganizeAuthorPress: PropTypes.func.isRequired,
onRetagAuthorPress: PropTypes.func.isRequired
onRetagAuthorPress: PropTypes.func.isRequired,
dispatchFetchRootFolders: PropTypes.func.isRequired
};
export default AuthorEditorFooter;
export default connect(undefined, mapDispatchToProps)(AuthorEditorFooter);

View File

@@ -98,10 +98,10 @@ class TagsModalContent extends Component {
value={applyTags}
values={applyTagsOptions}
helpTexts={[
translate('ApplyTagsHelpTexts1'),
translate('ApplyTagsHelpTexts2'),
translate('ApplyTagsHelpTexts3'),
translate('ApplyTagsHelpTexts4')
translate('ApplyTagsHelpTextHowToApplyAuthors'),
translate('ApplyTagsHelpTextAdd'),
translate('ApplyTagsHelpTextRemove'),
translate('ApplyTagsHelpTextReplace')
]}
onChange={this.onInputChange}
/>

View File

@@ -1,8 +1,10 @@
import PropTypes from 'prop-types';
import React, { Component } from 'react';
import Alert from 'Components/Alert';
import LoadingIndicator from 'Components/Loading/LoadingIndicator';
import Table from 'Components/Table/Table';
import TableBody from 'Components/Table/TableBody';
import { kinds } from 'Helpers/Props';
import translate from 'Utilities/String/translate';
import AuthorHistoryRowConnector from './AuthorHistoryRowConnector';
@@ -70,9 +72,9 @@ class AuthorHistoryTableContent extends Component {
{
!isFetching && !!error &&
<div>
<Alert kind={kinds.DANGER}>
{translate('UnableToLoadHistory')}
</div>
</Alert>
}
{

View File

@@ -17,8 +17,8 @@ function AuthorIndexProgressBar(props) {
detailedProgressBar
} = props;
const progress = bookCount ? bookFileCount / bookCount * 100 : 100;
const text = `${bookFileCount} / ${bookCount}`;
const progress = bookCount ? bookCount / totalBookCount * 100 : 100;
const text = `${bookCount} / ${totalBookCount}`;
return (
<ProgressBar

View File

@@ -297,7 +297,7 @@ class AuthorIndexRow extends Component {
progress={progress}
kind={getProgressBarKind(status, monitored, progress)}
showText={true}
text={`${bookFileCount} / ${bookCount}`}
text={`${bookCount} / ${totalBookCount}`}
title={translate('BookFileCountBookCountTotalTotalBookCountInterp', [bookFileCount, bookCount, totalBookCount])}
width={125}
/>

View File

@@ -33,7 +33,7 @@ class MonitoringOptionsModalContent extends Component {
const {
isSaving,
saveError
} = prevProps;
} = this.props;
if (prevProps.isSaving && !isSaving && !saveError) {
this.setState({

View File

@@ -83,15 +83,18 @@ class BookDetailsHeader extends Component {
titleWidth
} = this.state;
const fanartUrl = getFanartUrl(author.images);
const marqueeWidth = titleWidth - (isSmallScreen ? 85 : 160);
return (
<div className={styles.header} style={{ width }}>
<div
className={styles.backdrop}
style={{
backgroundImage: `url(${getFanartUrl(author.images)})`
}}
style={
fanartUrl ?
{ backgroundImage: `url(${fanartUrl})` } :
null
}
>
<div className={styles.backdropOverlay} />
</div>

View File

@@ -89,9 +89,9 @@ class BookEditorFooter extends Component {
} = this.state;
const monitoredOptions = [
{ key: NO_CHANGE, value: 'No Change', disabled: true },
{ key: 'monitored', value: 'Monitored' },
{ key: 'unmonitored', value: 'Unmonitored' }
{ key: NO_CHANGE, value: translate('NoChange'), disabled: true },
{ key: 'monitored', value: translate('Monitored') },
{ key: 'unmonitored', value: translate('Unmonitored') }
];
return (

View File

@@ -1,6 +1,7 @@
import PropTypes from 'prop-types';
import React from 'react';
import Modal from 'Components/Modal/Modal';
import { sizes } from 'Helpers/Props';
import BookInteractiveSearchModalContent from './BookInteractiveSearchModalContent';
function BookInteractiveSearchModal(props) {
@@ -14,6 +15,7 @@ function BookInteractiveSearchModal(props) {
return (
<Modal
isOpen={isOpen}
size={sizes.EXTRA_LARGE}
closeOnBackgroundClick={false}
onModalClose={onModalClose}
>

View File

@@ -30,7 +30,7 @@ class BookshelfFooter extends Component {
const {
isSaving,
saveError
} = prevProps;
} = this.props;
if (prevProps.isSaving && !isSaving && !saveError) {
this.setState({
@@ -88,9 +88,9 @@ class BookshelfFooter extends Component {
} = this.state;
const monitoredOptions = [
{ key: NO_CHANGE, value: 'No Change', disabled: true },
{ key: 'monitored', value: 'Monitored' },
{ key: 'unmonitored', value: 'Unmonitored' }
{ key: NO_CHANGE, value: translate('NoChange'), disabled: true },
{ key: 'monitored', value: translate('Monitored') },
{ key: 'unmonitored', value: translate('Unmonitored') }
];
const noChanges = monitored === NO_CHANGE &&

View File

@@ -1,6 +1,8 @@
import PropTypes from 'prop-types';
import React, { Component } from 'react';
import Alert from 'Components/Alert';
import LoadingIndicator from 'Components/Loading/LoadingIndicator';
import { kinds } from 'Helpers/Props';
import translate from 'Utilities/String/translate';
import AgendaConnector from './Agenda/AgendaConnector';
import * as calendarViews from './calendarViews';
@@ -31,9 +33,9 @@ class Calendar extends Component {
{
!isFetching && !!error &&
<div>
<Alert kind={kinds.DANGER}>
{translate('UnableToLoadTheCalendar')}
</div>
</Alert>
}
{

View File

@@ -4,7 +4,9 @@ import React from 'react';
import { kinds } from 'Helpers/Props';
import styles from './Alert.css';
function Alert({ className, kind, children, ...otherProps }) {
function Alert(props) {
const { className, kind, children, ...otherProps } = props;
return (
<div
className={classNames(
@@ -19,8 +21,8 @@ function Alert({ className, kind, children, ...otherProps }) {
}
Alert.propTypes = {
className: PropTypes.string.isRequired,
kind: PropTypes.oneOf(kinds.all).isRequired,
className: PropTypes.string,
kind: PropTypes.oneOf(kinds.all),
children: PropTypes.node.isRequired
};

View File

@@ -16,4 +16,9 @@
color: var(--textColor);
font-size: 21px;
line-height: inherit;
&.small {
color: #909293;
font-size: 18px;
}
}

View File

@@ -3,6 +3,7 @@
interface CssExports {
'fieldSet': string;
'legend': string;
'small': string;
}
export const cssExports: CssExports;
export default cssExports;

View File

@@ -1,5 +1,7 @@
import classNames from 'classnames';
import PropTypes from 'prop-types';
import React, { Component } from 'react';
import { sizes } from 'Helpers/Props';
import styles from './FieldSet.css';
class FieldSet extends Component {
@@ -9,13 +11,14 @@ class FieldSet extends Component {
render() {
const {
size,
legend,
children
} = this.props;
return (
<fieldset className={styles.fieldSet}>
<legend className={styles.legend}>
<legend className={classNames(styles.legend, (size === sizes.SMALL) && styles.small)}>
{legend}
</legend>
{children}
@@ -26,8 +29,13 @@ class FieldSet extends Component {
}
FieldSet.propTypes = {
size: PropTypes.oneOf(sizes.all).isRequired,
legend: PropTypes.oneOfType([PropTypes.node, PropTypes.string]),
children: PropTypes.node
};
FieldSet.defaultProps = {
size: sizes.MEDIUM
};
export default FieldSet;

View File

@@ -210,7 +210,7 @@ class FilterBuilderRow extends Component {
key: availablePropFilter.name,
value: availablePropFilter.label
};
});
}).sort((a, b) => a.value.localeCompare(b.value));
const ValueComponent = getRowValueConnector(selectedFilterBuilderProp);

View File

@@ -1,4 +1,6 @@
.tag {
display: flex;
&.isLastTag {
.or {
display: none;

View File

@@ -6,7 +6,7 @@ import styles from './FilterBuilderRowValueTag.css';
function FilterBuilderRowValueTag(props) {
return (
<span
<div
className={styles.tag}
>
<TagInputTag
@@ -15,12 +15,13 @@ function FilterBuilderRowValueTag(props) {
/>
{
!props.isLastTag &&
<span className={styles.or}>
props.isLastTag ?
null :
<div className={styles.or}>
or
</span>
</div>
}
</span>
</div>
);
}

View File

@@ -59,6 +59,7 @@
display: flex;
justify-content: center;
max-width: 90%;
max-height: 100%;
width: 350px !important;
height: auto !important;
}

View File

@@ -578,7 +578,7 @@ EnhancedSelectInput.propTypes = {
className: PropTypes.string,
disabledClassName: PropTypes.string,
name: PropTypes.string.isRequired,
value: PropTypes.oneOfType([PropTypes.string, PropTypes.number, PropTypes.arrayOf(PropTypes.number)]).isRequired,
value: PropTypes.oneOfType([PropTypes.string, PropTypes.number, PropTypes.arrayOf(PropTypes.string), PropTypes.arrayOf(PropTypes.number)]).isRequired,
values: PropTypes.arrayOf(PropTypes.object).isRequired,
isDisabled: PropTypes.bool.isRequired,
isFetching: PropTypes.bool.isRequired,

View File

@@ -1,7 +1,7 @@
import PropTypes from 'prop-types';
import React from 'react';
import Link from 'Components/Link/Link';
import { inputTypes } from 'Helpers/Props';
import { inputTypes, kinds } from 'Helpers/Props';
import translate from 'Utilities/String/translate';
// import translate from 'Utilities/String/translate';
import AutoCompleteInput from './AutoCompleteInput';
@@ -26,6 +26,7 @@ import PathInputConnector from './PathInputConnector';
import QualityProfileSelectInputConnector from './QualityProfileSelectInputConnector';
import RootFolderSelectInputConnector from './RootFolderSelectInputConnector';
import TagInputConnector from './TagInputConnector';
import TagSelectInputConnector from './TagSelectInputConnector';
import TextArea from './TextArea';
import TextInput from './TextInput';
import TextTagInputConnector from './TextTagInputConnector';
@@ -103,6 +104,9 @@ function getComponent(type) {
case inputTypes.TEXT_TAG:
return TextTagInputConnector;
case inputTypes.TAG_SELECT:
return TagSelectInputConnector;
case inputTypes.UMASK:
return UMaskInput;
@@ -266,16 +270,27 @@ FormInputGroup.propTypes = {
className: PropTypes.string.isRequired,
containerClassName: PropTypes.string.isRequired,
inputClassName: PropTypes.string,
name: PropTypes.string.isRequired,
value: PropTypes.any,
values: PropTypes.arrayOf(PropTypes.any),
type: PropTypes.string.isRequired,
kind: PropTypes.oneOf(kinds.all),
min: PropTypes.number,
max: PropTypes.number,
unit: PropTypes.string,
buttons: PropTypes.oneOfType([PropTypes.node, PropTypes.arrayOf(PropTypes.node)]),
helpText: PropTypes.string,
helpTexts: PropTypes.arrayOf(PropTypes.string),
helpTextWarning: PropTypes.string,
helpLink: PropTypes.string,
autoFocus: PropTypes.bool,
includeNoChange: PropTypes.bool,
includeNoChangeDisabled: PropTypes.bool,
selectedValueOptions: PropTypes.object,
pending: PropTypes.bool,
errors: PropTypes.arrayOf(PropTypes.object),
warnings: PropTypes.arrayOf(PropTypes.object)
warnings: PropTypes.arrayOf(PropTypes.object),
onChange: PropTypes.func.isRequired
};
FormInputGroup.defaultProps = {

View File

@@ -4,16 +4,18 @@ import React from 'react';
import { sizes } from 'Helpers/Props';
import styles from './FormLabel.css';
function FormLabel({
children,
className,
errorClassName,
size,
name,
hasError,
isAdvanced,
...otherProps
}) {
function FormLabel(props) {
const {
children,
className,
errorClassName,
size,
name,
hasError,
isAdvanced,
...otherProps
} = props;
return (
<label
{...otherProps}
@@ -31,13 +33,13 @@ function FormLabel({
}
FormLabel.propTypes = {
children: PropTypes.node.isRequired,
children: PropTypes.oneOfType([PropTypes.node, PropTypes.string]).isRequired,
className: PropTypes.string,
errorClassName: PropTypes.string,
size: PropTypes.oneOf(sizes.all),
name: PropTypes.string,
hasError: PropTypes.bool,
isAdvanced: PropTypes.bool.isRequired
isAdvanced: PropTypes.bool
};
FormLabel.defaultProps = {

View File

@@ -6,15 +6,17 @@ import { createSelector } from 'reselect';
import { metadataProfileNames } from 'Helpers/Props';
import createSortedSectionSelector from 'Store/Selectors/createSortedSectionSelector';
import sortByName from 'Utilities/Array/sortByName';
import translate from 'Utilities/String/translate';
import SelectInput from './SelectInput';
function createMapStateToProps() {
return createSelector(
createSortedSectionSelector('settings.metadataProfiles', sortByName),
(state, { includeNoChange }) => includeNoChange,
(state, { includeNoChangeDisabled }) => includeNoChangeDisabled,
(state, { includeMixed }) => includeMixed,
(state, { includeNone }) => includeNone,
(metadataProfiles, includeNoChange, includeMixed, includeNone) => {
(metadataProfiles, includeNoChange, includeNoChangeDisabled = true, includeMixed, includeNone) => {
const profiles = metadataProfiles.items.filter((item) => item.name !== metadataProfileNames.NONE);
const noneProfile = metadataProfiles.items.find((item) => item.name === metadataProfileNames.NONE);
@@ -36,8 +38,8 @@ function createMapStateToProps() {
if (includeNoChange) {
values.unshift({
key: 'noChange',
value: 'No Change',
disabled: true
value: translate('NoChange'),
disabled: includeNoChangeDisabled
});
}
@@ -68,8 +70,8 @@ class MetadataProfileSelectInputConnector extends Component {
values
} = this.props;
if (!value || !_.some(values, (option) => parseInt(option.key) === value)) {
const firstValue = _.find(values, (option) => !isNaN(parseInt(option.key)));
if (!value || !values.some((option) => option.key === value || parseInt(option.key) === value)) {
const firstValue = values.find((option) => !isNaN(parseInt(option.key)));
if (firstValue) {
this.onChange({ name, value: firstValue.key });
@@ -81,7 +83,7 @@ class MetadataProfileSelectInputConnector extends Component {
// Listeners
onChange = ({ name, value }) => {
this.props.onChange({ name, value: parseInt(value) });
this.props.onChange({ name, value: value === 'noChange' ? value : parseInt(value) });
};
//
@@ -107,7 +109,8 @@ MetadataProfileSelectInputConnector.propTypes = {
};
MetadataProfileSelectInputConnector.defaultProps = {
includeNoChange: false
includeNoChange: false,
includeNone: true
};
export default connect(createMapStateToProps)(MetadataProfileSelectInputConnector);

View File

@@ -1,6 +1,7 @@
import PropTypes from 'prop-types';
import React from 'react';
import monitorOptions from 'Utilities/Author/monitorOptions';
import translate from 'Utilities/String/translate';
import SelectInput from './SelectInput';
function MonitorBooksSelectInput(props) {
@@ -16,7 +17,7 @@ function MonitorBooksSelectInput(props) {
if (includeNoChange) {
values.unshift({
key: 'noChange',
value: 'No Change',
value: translate('NoChange'),
disabled: true
});
}

View File

@@ -1,6 +1,7 @@
import PropTypes from 'prop-types';
import React from 'react';
import monitorNewItemsOptions from 'Utilities/Author/monitorNewItemsOptions';
import translate from 'Utilities/String/translate';
import SelectInput from './SelectInput';
function MonitorNewItemsSelectInput(props) {
@@ -15,7 +16,7 @@ function MonitorNewItemsSelectInput(props) {
if (includeNoChange) {
values.unshift({
key: 'noChange',
value: 'No Change',
value: translate('NoChange'),
disabled: true
});
}

View File

@@ -10,7 +10,7 @@ function parseValue(props, value) {
} = props;
if (value == null || value === '') {
return min;
return null;
}
let newValue = isFloat ? parseFloat(value) : parseInt(value);

View File

@@ -31,6 +31,8 @@ function getType({ type, selectOptionsProviderAction }) {
return inputTypes.SELECT;
case 'tag':
return inputTypes.TEXT_TAG;
case 'tagSelect':
return inputTypes.TAG_SELECT;
case 'textbox':
return inputTypes.TEXT;
case 'oAuth':
@@ -62,6 +64,7 @@ function ProviderFieldFormGroup(props) {
name,
label,
helpText,
helpTextWarning,
helpLink,
placeholder,
value,
@@ -95,6 +98,7 @@ function ProviderFieldFormGroup(props) {
name={name}
label={label}
helpText={helpText}
helpTextWarning={helpTextWarning}
helpLink={helpLink}
placeholder={placeholder}
value={value}
@@ -121,6 +125,7 @@ ProviderFieldFormGroup.propTypes = {
name: PropTypes.string.isRequired,
label: PropTypes.string.isRequired,
helpText: PropTypes.string,
helpTextWarning: PropTypes.string,
helpLink: PropTypes.string,
placeholder: PropTypes.string,
value: PropTypes.any,

View File

@@ -5,14 +5,16 @@ import { connect } from 'react-redux';
import { createSelector } from 'reselect';
import createSortedSectionSelector from 'Store/Selectors/createSortedSectionSelector';
import sortByName from 'Utilities/Array/sortByName';
import translate from 'Utilities/String/translate';
import SelectInput from './SelectInput';
function createMapStateToProps() {
return createSelector(
createSortedSectionSelector('settings.qualityProfiles', sortByName),
(state, { includeNoChange }) => includeNoChange,
(state, { includeNoChangeDisabled }) => includeNoChangeDisabled,
(state, { includeMixed }) => includeMixed,
(qualityProfiles, includeNoChange, includeMixed) => {
(qualityProfiles, includeNoChange, includeNoChangeDisabled = true, includeMixed) => {
const values = _.map(qualityProfiles.items, (qualityProfile) => {
return {
key: qualityProfile.id,
@@ -23,8 +25,8 @@ function createMapStateToProps() {
if (includeNoChange) {
values.unshift({
key: 'noChange',
value: 'No Change',
disabled: true
value: translate('NoChange'),
disabled: includeNoChangeDisabled
});
}
@@ -55,8 +57,8 @@ class QualityProfileSelectInputConnector extends Component {
values
} = this.props;
if (!value || !_.some(values, (option) => parseInt(option.key) === value)) {
const firstValue = _.find(values, (option) => !isNaN(parseInt(option.key)));
if (!value || !values.some((option) => option.key === value || parseInt(option.key) === value)) {
const firstValue = values.find((option) => !isNaN(parseInt(option.key)));
if (firstValue) {
this.onChange({ name, value: firstValue.key });
@@ -68,7 +70,7 @@ class QualityProfileSelectInputConnector extends Component {
// Listeners
onChange = ({ name, value }) => {
this.props.onChange({ name, value: parseInt(value) });
this.props.onChange({ name, value: value === 'noChange' ? value : parseInt(value) });
};
//

View File

@@ -63,7 +63,7 @@ class RootFolderSelectInput extends Component {
render() {
const {
value,
includeNoChange,
...otherProps
} = this.props;
@@ -71,7 +71,6 @@ class RootFolderSelectInput extends Component {
<div>
<EnhancedSelectInput
{...otherProps}
value={value || ''}
selectedValueComponent={RootFolderSelectInputSelectedValue}
optionComponent={RootFolderSelectInputOption}
onChange={this.onChange}
@@ -93,7 +92,12 @@ RootFolderSelectInput.propTypes = {
values: PropTypes.arrayOf(PropTypes.object).isRequired,
isSaving: PropTypes.bool.isRequired,
saveError: PropTypes.object,
includeNoChange: PropTypes.bool.isRequired,
onChange: PropTypes.func.isRequired
};
RootFolderSelectInput.defaultProps = {
includeNoChange: false
};
export default RootFolderSelectInput;

View File

@@ -2,6 +2,7 @@ import PropTypes from 'prop-types';
import React, { Component } from 'react';
import { connect } from 'react-redux';
import { createSelector } from 'reselect';
import translate from 'Utilities/String/translate';
import RootFolderSelectInput from './RootFolderSelectInput';
const ADD_NEW_KEY = 'addNew';
@@ -12,7 +13,8 @@ function createMapStateToProps() {
(state, { value }) => value,
(state, { includeMissingValue }) => includeMissingValue,
(state, { includeNoChange }) => includeNoChange,
(rootFolders, value, includeMissingValue, includeNoChange) => {
(state, { includeNoChangeDisabled }) => includeNoChangeDisabled,
(rootFolders, value, includeMissingValue, includeNoChange, includeNoChangeDisabled = true) => {
const values = rootFolders.items.map((rootFolder) => {
return {
key: rootFolder.path,
@@ -27,8 +29,8 @@ function createMapStateToProps() {
values.unshift({
key: 'noChange',
value: '',
name: 'No Change',
isDisabled: true,
name: translate('NoChange'),
isDisabled: includeNoChangeDisabled,
isMissing: false
});
}

View File

@@ -75,7 +75,7 @@ SelectInput.propTypes = {
className: PropTypes.string,
disabledClassName: PropTypes.string,
name: PropTypes.string.isRequired,
value: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
value: PropTypes.oneOfType([PropTypes.string, PropTypes.number]).isRequired,
values: PropTypes.arrayOf(PropTypes.object).isRequired,
isDisabled: PropTypes.bool,
hasError: PropTypes.bool,

View File

@@ -75,6 +75,18 @@ class TagInput extends Component {
//
// Listeners
onTagEdit = ({ value, ...otherProps }) => {
const currentValue = this.state.value;
if (currentValue && this.props.onTagReplace) {
this.props.onTagReplace(otherProps, { name: currentValue });
} else {
this.props.onTagDelete(otherProps);
}
this.setState({ value });
};
onInputContainerPress = () => {
this._autosuggestRef.input.focus();
};
@@ -188,6 +200,7 @@ class TagInput extends Component {
const {
tags,
kind,
canEdit,
tagComponent,
onTagDelete
} = this.props;
@@ -199,8 +212,10 @@ class TagInput extends Component {
kind={kind}
inputProps={inputProps}
isFocused={this.state.isFocused}
canEdit={canEdit}
tagComponent={tagComponent}
onTagDelete={onTagDelete}
onTagEdit={this.onTagEdit}
onInputContainerPress={this.onInputContainerPress}
/>
);
@@ -225,7 +240,7 @@ class TagInput extends Component {
<AutoSuggestInput
{...otherProps}
forwardedRef={this._setAutosuggestRef}
className={styles.internalInput}
className={className}
inputContainerClassName={classNames(
inputContainerClassName,
isFocused && styles.isFocused,
@@ -262,11 +277,13 @@ TagInput.propTypes = {
placeholder: PropTypes.string.isRequired,
delimiters: PropTypes.arrayOf(PropTypes.string).isRequired,
minQueryLength: PropTypes.number.isRequired,
canEdit: PropTypes.bool,
hasError: PropTypes.bool,
hasWarning: PropTypes.bool,
tagComponent: PropTypes.elementType.isRequired,
onTagAdd: PropTypes.func.isRequired,
onTagDelete: PropTypes.func.isRequired
onTagDelete: PropTypes.func.isRequired,
onTagReplace: PropTypes.func
};
TagInput.defaultProps = {
@@ -277,6 +294,7 @@ TagInput.defaultProps = {
placeholder: '',
delimiters: ['Tab', 'Enter', ' ', ','],
minQueryLength: 1,
canEdit: false,
tagComponent: TagInputTag
};

View File

@@ -138,6 +138,7 @@ class TagInputConnector extends Component {
<TagInput
onTagAdd={this.onTagAdd}
onTagDelete={this.onTagDelete}
onTagReplace={this.onTagReplace}
{...this.props}
/>
);

View File

@@ -28,8 +28,10 @@ class TagInputInput extends Component {
tags,
inputProps,
kind,
canEdit,
tagComponent: TagComponent,
onTagDelete
onTagDelete,
onTagEdit
} = this.props;
return (
@@ -46,8 +48,10 @@ class TagInputInput extends Component {
index={index}
tag={tag}
kind={kind}
canEdit={canEdit}
isLastTag={index === tags.length - 1}
onDelete={onTagDelete}
onEdit={onTagEdit}
/>
);
})
@@ -66,8 +70,10 @@ TagInputInput.propTypes = {
inputProps: PropTypes.object.isRequired,
kind: PropTypes.oneOf(kinds.all).isRequired,
isFocused: PropTypes.bool.isRequired,
canEdit: PropTypes.bool.isRequired,
tagComponent: PropTypes.elementType.isRequired,
onTagDelete: PropTypes.func.isRequired,
onTagEdit: PropTypes.func.isRequired,
onInputContainerPress: PropTypes.func.isRequired
};

View File

@@ -1,5 +1,34 @@
.tag {
composes: link from '~Components/Link/Link.css';
display: flex;
justify-content: center;
flex-direction: column;
max-width: 100%;
height: 31px;
}
.link {
max-width: 100%;
}
.linkWithEdit {
max-width: calc(100% - 9px - 4px - 2px);
}
.editContainer {
display: inline-block;
margin-left: 4px;
padding-left: 2px;
border-left: 1px solid #eee;
}
.editButton {
composes: button from '~Components/Link/IconButton.css';
width: 9px;
}
.label {
composes: label from '~Components/Label.css';
max-width: 100%;
}

View File

@@ -1,6 +1,11 @@
// This file is automatically generated.
// Please do not change this file!
interface CssExports {
'editButton': string;
'editContainer': string;
'label': string;
'link': string;
'linkWithEdit': string;
'tag': string;
}
export const cssExports: CssExports;

View File

@@ -1,8 +1,9 @@
import PropTypes from 'prop-types';
import React, { Component } from 'react';
import Label from 'Components/Label';
import IconButton from 'Components/Link/IconButton';
import Link from 'Components/Link/Link';
import { kinds } from 'Helpers/Props';
import { icons, kinds } from 'Helpers/Props';
import tagShape from 'Helpers/Props/Shapes/tagShape';
import styles from './TagInputTag.css';
@@ -24,24 +25,61 @@ class TagInputTag extends Component {
});
};
onEdit = () => {
const {
index,
tag,
onEdit
} = this.props;
onEdit({
index,
id: tag.id,
value: tag.name
});
};
//
// Render
render() {
const {
tag,
kind
kind,
canEdit
} = this.props;
return (
<Link
<div
className={styles.tag}
tabIndex={-1}
onPress={this.onDelete}
>
<Label kind={kind}>
{tag.name}
<Label
className={styles.label}
kind={kind}
>
<Link
className={canEdit ? styles.linkWithEdit : styles.link}
tabIndex={-1}
onPress={this.onDelete}
>
{tag.name}
</Link>
{
canEdit ?
<div className={styles.editContainer}>
<IconButton
className={styles.editButton}
name={icons.EDIT}
size={9}
onPress={this.onEdit}
/>
</div> :
null
}
</Label>
</Link>
</div>
);
}
}
@@ -50,7 +88,9 @@ TagInputTag.propTypes = {
index: PropTypes.number.isRequired,
tag: PropTypes.shape(tagShape),
kind: PropTypes.oneOf(kinds.all).isRequired,
onDelete: PropTypes.func.isRequired
canEdit: PropTypes.bool.isRequired,
onDelete: PropTypes.func.isRequired,
onEdit: PropTypes.func.isRequired
};
export default TagInputTag;

View File

@@ -0,0 +1,102 @@
import _ from 'lodash';
import PropTypes from 'prop-types';
import React, { Component } from 'react';
import { connect } from 'react-redux';
import { createSelector } from 'reselect';
import TagInput from './TagInput';
function createMapStateToProps() {
return createSelector(
(state, { value }) => value,
(state, { values }) => values,
(tags, tagList) => {
const sortedTags = _.sortBy(tagList, 'value');
return {
tags: tags.reduce((acc, tag) => {
const matchingTag = _.find(tagList, { key: tag });
if (matchingTag) {
acc.push({
id: tag,
name: matchingTag.value
});
}
return acc;
}, []),
tagList: sortedTags.map(({ key: id, value: name }) => {
return {
id,
name
};
}),
allTags: sortedTags
};
}
);
}
class TagSelectInputConnector extends Component {
//
// Listeners
onTagAdd = (tag) => {
const {
name,
value,
allTags
} = this.props;
const existingTag =_.some(allTags, { key: tag.id });
const newValue = value.slice();
if (existingTag) {
newValue.push(tag.id);
}
this.props.onChange({ name, value: newValue });
};
onTagDelete = ({ index }) => {
const {
name,
value
} = this.props;
const newValue = value.slice();
newValue.splice(index, 1);
this.props.onChange({
name,
value: newValue
});
};
//
// Render
render() {
return (
<TagInput
onTagAdd={this.onTagAdd}
onTagDelete={this.onTagDelete}
{...this.props}
/>
);
}
}
TagSelectInputConnector.propTypes = {
name: PropTypes.string.isRequired,
value: PropTypes.arrayOf(PropTypes.number).isRequired,
values: PropTypes.arrayOf(PropTypes.object).isRequired,
allTags: PropTypes.arrayOf(PropTypes.object).isRequired,
onChange: PropTypes.func.isRequired
};
export default connect(createMapStateToProps)(TagSelectInputConnector);

View File

@@ -71,6 +71,20 @@ class TextTagInputConnector extends Component {
});
};
onTagReplace = (tagToReplace, newTag) => {
const {
name,
valueArray,
onChange
} = this.props;
const newValue = [...valueArray];
newValue.splice(tagToReplace.index, 1);
newValue.push(newTag.name.trim());
onChange({ name, value: newValue });
};
//
// Render
@@ -80,6 +94,7 @@ class TextTagInputConnector extends Component {
tagList={[]}
onTagAdd={this.onTagAdd}
onTagDelete={this.onTagDelete}
onTagReplace={this.onTagReplace}
{...this.props}
/>
);

View File

@@ -31,6 +31,7 @@ function Label(props) {
Label.propTypes = {
className: PropTypes.string.isRequired,
title: PropTypes.string,
kind: PropTypes.oneOf(kinds.all).isRequired,
size: PropTypes.oneOf(sizes.all).isRequired,
outline: PropTypes.bool.isRequired,

View File

@@ -39,11 +39,13 @@ function IconButton(props) {
}
IconButton.propTypes = {
...Link.propTypes,
className: PropTypes.string.isRequired,
iconClassName: PropTypes.string,
kind: PropTypes.string,
name: PropTypes.object.isRequired,
size: PropTypes.number,
title: PropTypes.string,
isSpinning: PropTypes.bool,
isDisabled: PropTypes.bool
};

View File

@@ -1,110 +0,0 @@
import classNames from 'classnames';
import PropTypes from 'prop-types';
import React, { Component } from 'react';
import { Link as RouterLink } from 'react-router-dom';
import styles from './Link.css';
class Link extends Component {
//
// Listeners
onClick = (event) => {
const {
isDisabled,
onPress
} = this.props;
if (!isDisabled && onPress) {
onPress(event);
}
};
//
// Render
render() {
const {
className,
component,
to,
target,
isDisabled,
noRouter,
onPress,
...otherProps
} = this.props;
const linkProps = { target };
let el = component;
if (to && typeof to === 'string') {
if ((/\w+?:\/\//).test(to)) {
el = 'a';
linkProps.href = to;
linkProps.target = target || '_blank';
linkProps.rel = 'noreferrer';
} else if (noRouter) {
el = 'a';
linkProps.href = to;
linkProps.target = target || '_self';
} else {
el = RouterLink;
linkProps.to = `${window.Readarr.urlBase}/${to.replace(/^\//, '')}`;
linkProps.target = target;
}
} else if (to && typeof to === 'object') {
el = RouterLink;
linkProps.target = target;
if (to.pathname.startsWith(`${window.Readarr.urlBase}/`)) {
linkProps.to = to;
} else {
const pathname = `${window.Readarr.urlBase}/${to.pathname.replace(/^\//, '')}`;
linkProps.to = {
...to,
pathname
};
}
}
if (el === 'button' || el === 'input') {
linkProps.type = otherProps.type || 'button';
linkProps.disabled = isDisabled;
}
linkProps.className = classNames(
className,
styles.link,
to && styles.to,
isDisabled && 'isDisabled'
);
const props = {
...otherProps,
...linkProps
};
props.onClick = this.onClick;
return (
React.createElement(el, props)
);
}
}
Link.propTypes = {
className: PropTypes.string,
component: PropTypes.oneOfType([PropTypes.string, PropTypes.func]),
to: PropTypes.oneOfType([PropTypes.string, PropTypes.object]),
target: PropTypes.string,
isDisabled: PropTypes.bool,
noRouter: PropTypes.bool,
onPress: PropTypes.func
};
Link.defaultProps = {
component: 'button',
noRouter: false
};
export default Link;

View File

@@ -0,0 +1,96 @@
import classNames from 'classnames';
import React, {
ComponentClass,
FunctionComponent,
SyntheticEvent,
useCallback,
} from 'react';
import { Link as RouterLink } from 'react-router-dom';
import styles from './Link.css';
interface ReactRouterLinkProps {
to?: string;
}
export interface LinkProps extends React.HTMLProps<HTMLAnchorElement> {
className?: string;
component?:
| string
| FunctionComponent<LinkProps>
| ComponentClass<LinkProps, unknown>;
to?: string;
target?: string;
isDisabled?: boolean;
noRouter?: boolean;
onPress?(event: SyntheticEvent): void;
}
function Link(props: LinkProps) {
const {
className,
component = 'button',
to,
target,
type,
isDisabled,
noRouter = false,
onPress,
...otherProps
} = props;
const onClick = useCallback(
(event: SyntheticEvent) => {
if (!isDisabled && onPress) {
onPress(event);
}
},
[isDisabled, onPress]
);
const linkProps: React.HTMLProps<HTMLAnchorElement> & ReactRouterLinkProps = {
target,
};
let el = component;
if (to) {
if (/\w+?:\/\//.test(to)) {
el = 'a';
linkProps.href = to;
linkProps.target = target || '_blank';
linkProps.rel = 'noreferrer';
} else if (noRouter) {
el = 'a';
linkProps.href = to;
linkProps.target = target || '_self';
} else {
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
el = RouterLink;
linkProps.to = `${window.Readarr.urlBase}/${to.replace(/^\//, '')}`;
linkProps.target = target;
}
}
if (el === 'button' || el === 'input') {
linkProps.type = type || 'button';
linkProps.disabled = isDisabled;
}
linkProps.className = classNames(
className,
styles.link,
to && styles.to,
isDisabled && 'isDisabled'
);
const elementProps = {
...otherProps,
type,
...linkProps,
};
elementProps.onClick = onClick;
return React.createElement(el, elementProps);
}
export default Link;

View File

@@ -42,6 +42,7 @@ function SpinnerButton(props) {
}
SpinnerButton.propTypes = {
...Button.Props,
className: PropTypes.string.isRequired,
isSpinning: PropTypes.bool.isRequired,
isDisabled: PropTypes.bool,

View File

@@ -13,24 +13,51 @@ class InlineMarkdown extends Component {
data
} = this.props;
// For now only replace links
// For now only replace links or code blocks (not both)
const markdownBlocks = [];
if (data) {
const regex = RegExp(/\[(.+?)\]\((.+?)\)/g);
const linkRegex = RegExp(/\[(.+?)\]\((.+?)\)/g);
let endIndex = 0;
let match = null;
while ((match = regex.exec(data)) !== null) {
while ((match = linkRegex.exec(data)) !== null) {
if (match.index > endIndex) {
markdownBlocks.push(data.substr(endIndex, match.index - endIndex));
}
markdownBlocks.push(<Link key={match.index} to={match[2]}>{match[1]}</Link>);
endIndex = match.index + match[0].length;
}
if (endIndex !== data.length) {
if (endIndex !== data.length && markdownBlocks.length > 0) {
markdownBlocks.push(data.substr(endIndex, data.length - endIndex));
}
const codeRegex = RegExp(/(?=`)`(?!`)[^`]*(?=`)`(?!`)/g);
endIndex = 0;
match = null;
let matchedCode = false;
while ((match = codeRegex.exec(data)) !== null) {
matchedCode = true;
if (match.index > endIndex) {
markdownBlocks.push(data.substr(endIndex, match.index - endIndex));
}
markdownBlocks.push(<code key={`code-${match.index}`}>{match[0].substring(1, match[0].length - 1)}</code>);
endIndex = match.index + match[0].length;
}
if (endIndex !== data.length && markdownBlocks.length > 0 && matchedCode) {
markdownBlocks.push(data.substr(endIndex, data.length - endIndex));
}
if (markdownBlocks.length === 0) {
markdownBlocks.push(data);
}
}
return <span className={className}>{markdownBlocks}</span>;

View File

@@ -7,6 +7,7 @@ function ErrorPage(props) {
const {
version,
isLocalStorageSupported,
hasTranslationsError,
authorError,
customFiltersError,
tagsError,
@@ -20,6 +21,8 @@ function ErrorPage(props) {
if (!isLocalStorageSupported) {
errorMessage = 'Local Storage is not supported or disabled. A plugin or private browsing may have disabled it.';
} else if (hasTranslationsError) {
errorMessage = 'Failed to load translations from API';
} else if (authorError) {
errorMessage = getErrorMessage(authorError, 'Failed to load author from API');
} else if (customFiltersError) {
@@ -52,6 +55,7 @@ function ErrorPage(props) {
ErrorPage.propTypes = {
version: PropTypes.string.isRequired,
isLocalStorageSupported: PropTypes.bool.isRequired,
hasTranslationsError: PropTypes.bool.isRequired,
authorError: PropTypes.object,
customFiltersError: PropTypes.object,
tagsError: PropTypes.object,

View File

@@ -26,6 +26,7 @@ function createCleanAuthorSelector() {
sortName,
titleSlug,
images,
firstCharacter: authorName.charAt(0).toLowerCase(),
tags: tags.reduce((acc, id) => {
const matchingTag = allTags.find((tag) => tag.id === id);
@@ -58,6 +59,7 @@ function createCleanBookSelector() {
sortName: title,
titleSlug,
images,
firstCharacter: title.charAt(0).toLowerCase(),
tags: []
};
});

View File

@@ -53,10 +53,7 @@ class PageHeader extends Component {
<div className={styles.logoContainer}>
<Link
className={styles.logoLink}
to={{
pathname: '/',
state: { restoreScrollPosition: true }
}}
to={'/'}
>
<img
className={styles.logo}

View File

@@ -15,9 +15,36 @@ const fuseOptions = {
function getSuggestions(items, value) {
const limit = 10;
let suggestions = [];
const fuse = new Fuse(items, fuseOptions);
return fuse.search(value, { limit });
if (value.length === 1) {
for (let i = 0; i < items.length; i++) {
const s = items[i];
if (s.firstCharacter === value.toLowerCase()) {
suggestions.push({
item: items[i],
indices: [
[0, 0]
],
matches: [
{
value: s.title,
key: 'title'
}
],
arrayIndex: 0
});
if (suggestions.length > limit) {
break;
}
}
}
} else {
const fuse = new Fuse(items, fuseOptions);
suggestions = fuse.search(value, { limit });
}
return suggestions;
}
onmessage = function(e) {

View File

@@ -225,6 +225,7 @@ class PageConnector extends Component {
render() {
const {
hasTranslationsError,
isPopulated,
hasError,
dispatchFetchAuthor,
@@ -239,11 +240,12 @@ class PageConnector extends Component {
...otherProps
} = this.props;
if (hasError || !this.state.isLocalStorageSupported) {
if (hasTranslationsError || hasError || !this.state.isLocalStorageSupported) {
return (
<ErrorPage
{...this.state}
{...otherProps}
hasTranslationsError={hasTranslationsError}
/>
);
}
@@ -264,6 +266,7 @@ class PageConnector extends Component {
}
PageConnector.propTypes = {
hasTranslationsError: PropTypes.bool.isRequired,
isPopulated: PropTypes.bool.isRequired,
hasError: PropTypes.bool.isRequired,
isSidebarVisible: PropTypes.bool.isRequired,

View File

@@ -1,6 +1,8 @@
import PropTypes from 'prop-types';
import React from 'react';
import Alert from 'Components/Alert';
import LoadingIndicator from 'Components/Loading/LoadingIndicator';
import { kinds } from 'Helpers/Props';
function PageSectionContent(props) {
const {
@@ -17,7 +19,7 @@ function PageSectionContent(props) {
);
} else if (!isFetching && !!error) {
return (
<div>{errorMessage}</div>
<Alert kind={kinds.DANGER}>{errorMessage}</Alert>
);
} else if (isPopulated && !error) {
return (

View File

@@ -16,6 +16,38 @@
box-shadow: inset 0 -1px 0 rgba(0, 0, 0, 0.15);
color: var(--white);
transition: width 0.6s ease;
&.primary {
background-color: var(--primaryColor);
}
&.danger {
background-color: var(--dangerColor);
&:global(.colorImpaired) {
background: repeating-linear-gradient(90deg, color(#f05050 shade(5%)), color(#f05050 shade(5%)) 5px, color(#f05050 shade(15%)) 5px, color(#f05050 shade(15%)) 10px);
}
}
&.success {
background-color: var(--successColor);
}
&.purple {
background-color: var(--purple);
}
&.warning {
background-color: var(--warningColor);
&:global(.colorImpaired) {
background: repeating-linear-gradient(45deg, #ffa500, #ffa500 5px, color(#ffa500 tint(15%)) 5px, color(#ffa500 tint(15%)) 10px);
}
}
&.info {
background-color: var(--infoColor);
}
}
.frontTextContainer {
@@ -45,38 +77,6 @@
cursor: default;
}
.primary {
background-color: var(--primaryColor);
}
.danger {
background-color: var(--dangerColor);
&:global(.colorImpaired) {
background: repeating-linear-gradient(90deg, color(#f05050 shade(5%)), color(#f05050 shade(5%)) 5px, color(#f05050 shade(15%)) 5px, color(#f05050 shade(15%)) 10px);
}
}
.success {
background-color: var(--successColor);
}
.purple {
background-color: var(--purple);
}
.warning {
background-color: var(--warningColor);
&:global(.colorImpaired) {
background: repeating-linear-gradient(45deg, #ffa500, #ffa500 5px, color(#ffa500 tint(15%)) 5px, color(#ffa500 tint(15%)) 10px);
}
}
.info {
background-color: var(--infoColor);
}
.small {
height: $progressBarSmallHeight;

View File

@@ -38,7 +38,7 @@ function ProgressBar(props) {
{
showText && width ?
<div
className={styles.backTextContainer}
className={classNames(styles.backTextContainer, styles[kind])}
style={{ width: actualWidth }}
>
<div className={styles.backText}>
@@ -67,7 +67,7 @@ function ProgressBar(props) {
{
showText ?
<div
className={styles.frontTextContainer}
className={classNames(styles.frontTextContainer, styles[kind])}
style={{ width: progressPercent }}
>
<div

View File

@@ -0,0 +1,12 @@
import React from 'react';
interface Column {
name: string;
label: string | React.ReactNode;
columnLabel?: string;
isSortable?: boolean;
isVisible: boolean;
isModifiable?: boolean;
}
export default Column;

View File

@@ -52,6 +52,7 @@ function Table(props) {
scrollDirections.HORIZONTAL :
scrollDirections.NONE
}
autoFocus={false}
>
<table className={className}>
<TableHeader>
@@ -120,6 +121,7 @@ function Table(props) {
}
Table.propTypes = {
...TableHeaderCell.props,
className: PropTypes.string,
horizontalScroll: PropTypes.bool.isRequired,
selectAll: PropTypes.bool.isRequired,

View File

@@ -1,18 +1,19 @@
{
"name": "",
"name": "Readarr",
"icons": [
{
"src": "/Content/Images/Icons/android-chrome-192x192.png",
"src": "android-chrome-192x192.png",
"sizes": "192x192",
"type": "image/png"
},
{
"src": "/Content/Images/Icons/android-chrome-512x512.png",
"src": "android-chrome-512x512.png",
"sizes": "512x512",
"type": "image/png"
}
],
"start_url": "../../../../",
"theme_color": "#3a3f51",
"background_color": "#3a3f51",
"display": "standalone"
}
}

View File

@@ -0,0 +1,11 @@
import { useEffect, useRef } from 'react';
export default function usePrevious<T>(value: T): T | undefined {
const ref = useRef<T>();
useEffect(() => {
ref.current = value;
}, [value]);
return ref.current;
}

View File

@@ -0,0 +1,113 @@
import { cloneDeep } from 'lodash';
import { useReducer } from 'react';
import ModelBase from 'App/ModelBase';
import areAllSelected from 'Utilities/Table/areAllSelected';
import selectAll from 'Utilities/Table/selectAll';
import toggleSelected from 'Utilities/Table/toggleSelected';
export type SelectedState = Record<number, boolean>;
export interface SelectState {
selectedState: SelectedState;
lastToggled: number | null;
allSelected: boolean;
allUnselected: boolean;
}
export type SelectAction =
| { type: 'reset' }
| { type: 'selectAll'; items: ModelBase[] }
| { type: 'unselectAll'; items: ModelBase[] }
| {
type: 'toggleSelected';
id: number;
isSelected: boolean;
shiftKey: boolean;
items: ModelBase[];
}
| {
type: 'removeItem';
id: number;
}
| {
type: 'updateItems';
items: ModelBase[];
};
export type Dispatch = (action: SelectAction) => void;
const initialState = {
selectedState: {},
lastToggled: null,
allSelected: false,
allUnselected: true,
items: [],
};
function getSelectedState(items: ModelBase[], existingState: SelectedState) {
return items.reduce((acc: SelectedState, item) => {
const id = item.id;
acc[id] = existingState[id] ?? false;
return acc;
}, {});
}
function selectReducer(state: SelectState, action: SelectAction): SelectState {
const { selectedState } = state;
switch (action.type) {
case 'reset': {
return cloneDeep(initialState);
}
case 'selectAll': {
return {
...selectAll(selectedState, true),
};
}
case 'unselectAll': {
return {
...selectAll(selectedState, false),
};
}
case 'toggleSelected': {
const result = {
...toggleSelected(
state,
action.items,
action.id,
action.isSelected,
action.shiftKey
),
};
return result;
}
case 'updateItems': {
const nextSelectedState = getSelectedState(action.items, selectedState);
return {
...state,
...areAllSelected(nextSelectedState),
selectedState: nextSelectedState,
};
}
default: {
throw new Error(`Unhandled action type: ${action.type}`);
}
}
}
export default function useSelectState(): [SelectState, Dispatch] {
const selectedState = getSelectedState([], {});
const [state, dispatch] = useReducer(selectReducer, {
selectedState,
lastToggled: null,
allSelected: false,
allUnselected: true,
});
return [state, dispatch];
}

View File

@@ -0,0 +1,6 @@
enum SortDirection {
Ascending = 'ascending',
Descending = 'descending',
}
export default SortDirection;

View File

@@ -68,6 +68,7 @@ import {
faInfoCircle as fasInfoCircle,
faLaptop as fasLaptop,
faLevelUpAlt as fasLevelUpAlt,
faListCheck as fasListCheck,
faLongArrowAltRight as fasLongArrowAltRight,
faMedkit as fasMedkit,
faMinus as fasMinus,
@@ -166,6 +167,7 @@ export const INFO = fasInfoCircle;
export const INTERACTIVE = fasUser;
export const KEYBOARD = farKeyboard;
export const LOGOUT = fasSignOutAlt;
export const MANAGE = fasListCheck;
export const MEDIA_INFO = farFileInvoice;
export const MISSING = fasExclamationTriangle;
export const MONITORED = fasBookmark;

View File

@@ -22,6 +22,7 @@ export const TAG = 'tag';
export const TEXT = 'text';
export const TEXT_AREA = 'textArea';
export const TEXT_TAG = 'textTag';
export const TAG_SELECT = 'tagSelect';
export const UMASK = 'umask';
export const all = [
@@ -49,5 +50,6 @@ export const all = [
TEXT,
TEXT_AREA,
TEXT_TAG,
TAG_SELECT,
UMASK
];

View File

@@ -258,7 +258,9 @@ class InteractiveImportRow extends Component {
>
{
showReleaseGroupPlaceholder ?
<InteractiveImportRowCellPlaceholder /> :
<InteractiveImportRowCellPlaceholder
isOptional={true}
/> :
releaseGroup
}
</TableRowCellButton>

View File

@@ -5,3 +5,7 @@
height: 25px;
border: 2px dashed var(--dangerColor);
}
.optional {
border: 2px dashed var(--gray);
}

View File

@@ -1,6 +1,7 @@
// This file is automatically generated.
// Please do not change this file!
interface CssExports {
'optional': string;
'placeholder': string;
}
export const cssExports: CssExports;

View File

@@ -1,10 +0,0 @@
import React from 'react';
import styles from './InteractiveImportRowCellPlaceholder.css';
function InteractiveImportRowCellPlaceholder() {
return (
<span className={styles.placeholder} />
);
}
export default InteractiveImportRowCellPlaceholder;

View File

@@ -0,0 +1,22 @@
import classNames from 'classnames';
import React from 'react';
import styles from './InteractiveImportRowCellPlaceholder.css';
interface InteractiveImportRowCellPlaceholderProps {
isOptional?: boolean;
}
function InteractiveImportRowCellPlaceholder(
props: InteractiveImportRowCellPlaceholderProps
) {
return (
<span
className={classNames(
styles.placeholder,
props.isOptional && styles.optional
)}
/>
);
}
export default InteractiveImportRowCellPlaceholder;

View File

@@ -1,5 +1,6 @@
import PropTypes from 'prop-types';
import React, { Component } from 'react';
import Alert from 'Components/Alert';
import Form from 'Components/Form/Form';
import FormGroup from 'Components/Form/FormGroup';
import FormInputGroup from 'Components/Form/FormInputGroup';
@@ -92,9 +93,9 @@ class SelectQualityModalContent extends Component {
{
!isFetching && !!error &&
<div>
<Alert kind={kinds.DANGER}>
{translate('UnableToLoadQualities')}
</div>
</Alert>
}
{

View File

@@ -7,3 +7,9 @@
.filteredMessage {
margin-top: 10px;
}
.blankpad {
padding-top: 10px;
padding-bottom: 10px;
padding-left: 2em;
}

View File

@@ -1,6 +1,7 @@
// This file is automatically generated.
// Please do not change this file!
interface CssExports {
'blankpad': string;
'filterMenuContainer': string;
'filteredMessage': string;
}

View File

@@ -104,7 +104,7 @@ function InteractiveSearch(props) {
{
!isFetching && error ?
<div>
<div className={styles.blankpad}>
Unable to load results for this book search. Try again later
</div> :
null
@@ -112,7 +112,7 @@ function InteractiveSearch(props) {
{
!isFetching && isPopulated && !totalReleasesCount ?
<div>
<div className={styles.blankpad}>
No results found
</div> :
null
@@ -120,7 +120,7 @@ function InteractiveSearch(props) {
{
!!totalReleasesCount && isPopulated && !items.length ?
<div>
<div className={styles.blankpad}>
All results are hidden by the applied filter
</div> :
null

View File

@@ -15,7 +15,7 @@ import { icons, kinds, tooltipPositions } from 'Helpers/Props';
import formatDateTime from 'Utilities/Date/formatDateTime';
import formatAge from 'Utilities/Number/formatAge';
import formatBytes from 'Utilities/Number/formatBytes';
import formatPreferredWordScore from 'Utilities/Number/formatPreferredWordScore';
import formatCustomFormatScore from 'Utilities/Number/formatCustomFormatScore';
import translate from 'Utilities/String/translate';
import Peers from './Peers';
import styles from './InteractiveSearchRow.css';
@@ -172,7 +172,7 @@ class InteractiveSearchRow extends Component {
<TableRowCell className={styles.customFormatScore}>
<Tooltip
anchor={
formatPreferredWordScore(customFormatScore, customFormats.length)
formatCustomFormatScore(customFormatScore, customFormats.length)
}
tooltip={<BookFormats formats={customFormats} />}
position={tooltipPositions.BOTTOM}

View File

@@ -78,7 +78,7 @@ class Specification extends Component {
<IconButton
className={styles.cloneButton}
title={translate('Clone')}
title={translate('CloneCondition')}
name={icons.CLONE}
onPress={this.onCloneSpecificationPress}
/>
@@ -92,14 +92,14 @@ class Specification extends Component {
{
negate &&
<Label kind={kinds.DANGER}>
Negated
{translate('Negated')}
</Label>
}
{
required &&
<Label kind={kinds.SUCCESS}>
Required
{translate('Required')}
</Label>
}
</div>
@@ -114,8 +114,8 @@ class Specification extends Component {
<ConfirmModal
isOpen={this.state.isDeleteSpecificationModalOpen}
kind={kinds.DANGER}
title={translate('DeleteFormat')}
message={translate('DeleteFormatMessageText', [name])}
title={translate('DeleteCondition')}
message={translate('DeleteConditionMessageText', [name])}
confirmLabel={translate('Delete')}
onConfirm={this.onConfirmDeleteSpecification}
onCancel={this.onDeleteSpecificationModalClose}

View File

@@ -8,6 +8,7 @@ import { icons } from 'Helpers/Props';
import SettingsToolbarConnector from 'Settings/SettingsToolbarConnector';
import translate from 'Utilities/String/translate';
import DownloadClientsConnector from './DownloadClients/DownloadClientsConnector';
import ManageDownloadClientsModal from './DownloadClients/Manage/ManageDownloadClientsModal';
import DownloadClientOptionsConnector from './Options/DownloadClientOptionsConnector';
import RemotePathMappingsConnector from './RemotePathMappings/RemotePathMappingsConnector';
@@ -23,7 +24,8 @@ class DownloadClientSettings extends Component {
this.state = {
isSaving: false,
hasPendingChanges: false
hasPendingChanges: false,
isManageDownloadClientsOpen: false
};
}
@@ -38,6 +40,14 @@ class DownloadClientSettings extends Component {
this.setState(payload);
};
onManageDownloadClientsPress = () => {
this.setState({ isManageDownloadClientsOpen: true });
};
onManageDownloadClientsModalClose = () => {
this.setState({ isManageDownloadClientsOpen: false });
};
onSavePress = () => {
if (this._saveCallback) {
this._saveCallback();
@@ -55,7 +65,8 @@ class DownloadClientSettings extends Component {
const {
isSaving,
hasPendingChanges
hasPendingChanges,
isManageDownloadClientsOpen
} = this.state;
return (
@@ -73,6 +84,12 @@ class DownloadClientSettings extends Component {
isSpinning={isTestingAll}
onPress={dispatchTestAllDownloadClients}
/>
<PageToolbarButton
label={translate('ManageClients')}
iconName={icons.MANAGE}
onPress={this.onManageDownloadClientsPress}
/>
</Fragment>
}
onSavePress={this.onSavePress}
@@ -87,6 +104,11 @@ class DownloadClientSettings extends Component {
/>
<RemotePathMappingsConnector />
<ManageDownloadClientsModal
isOpen={isManageDownloadClientsOpen}
onModalClose={this.onManageDownloadClientsModalClose}
/>
</PageContentBody>
</PageContent>
);

View File

@@ -3,6 +3,7 @@ import React, { Component } from 'react';
import Card from 'Components/Card';
import Label from 'Components/Label';
import ConfirmModal from 'Components/Modal/ConfirmModal';
import TagList from 'Components/TagList';
import { kinds } from 'Helpers/Props';
import translate from 'Utilities/String/translate';
import EditDownloadClientModalConnector from './EditDownloadClientModalConnector';
@@ -56,7 +57,9 @@ class DownloadClient extends Component {
id,
name,
enable,
priority
priority,
tags,
tagList
} = this.props;
return (
@@ -94,6 +97,11 @@ class DownloadClient extends Component {
}
</div>
<TagList
tags={tags}
tagList={tagList}
/>
<EditDownloadClientModalConnector
id={id}
isOpen={this.state.isEditDownloadClientModalOpen}
@@ -120,6 +128,8 @@ DownloadClient.propTypes = {
name: PropTypes.string.isRequired,
enable: PropTypes.bool.isRequired,
priority: PropTypes.number.isRequired,
tags: PropTypes.arrayOf(PropTypes.number).isRequired,
tagList: PropTypes.arrayOf(PropTypes.object).isRequired,
onConfirmDeleteDownloadClient: PropTypes.func.isRequired
};

View File

@@ -50,6 +50,7 @@ class DownloadClients extends Component {
const {
items,
onConfirmDeleteDownloadClient,
tagList,
...otherProps
} = this.props;
@@ -71,6 +72,7 @@ class DownloadClients extends Component {
<DownloadClient
key={item.id}
{...item}
tagList={tagList}
onConfirmDeleteDownloadClient={onConfirmDeleteDownloadClient}
/>
);
@@ -109,6 +111,7 @@ DownloadClients.propTypes = {
isFetching: PropTypes.bool.isRequired,
error: PropTypes.object,
items: PropTypes.arrayOf(PropTypes.object).isRequired,
tagList: PropTypes.arrayOf(PropTypes.object).isRequired,
onConfirmDeleteDownloadClient: PropTypes.func.isRequired
};

View File

@@ -4,13 +4,20 @@ import { connect } from 'react-redux';
import { createSelector } from 'reselect';
import { deleteDownloadClient, fetchDownloadClients } from 'Store/Actions/settingsActions';
import createSortedSectionSelector from 'Store/Selectors/createSortedSectionSelector';
import createTagsSelector from 'Store/Selectors/createTagsSelector';
import sortByName from 'Utilities/Array/sortByName';
import DownloadClients from './DownloadClients';
function createMapStateToProps() {
return createSelector(
createSortedSectionSelector('settings.downloadClients', sortByName),
(downloadClients) => downloadClients
createTagsSelector(),
(downloadClients, tagList) => {
return {
...downloadClients,
tagList
};
}
);
}

View File

@@ -1,6 +1,7 @@
import PropTypes from 'prop-types';
import React, { Component } from 'react';
import Alert from 'Components/Alert';
import FieldSet from 'Components/FieldSet';
import Form from 'Components/Form/Form';
import FormGroup from 'Components/Form/FormGroup';
import FormInputGroup from 'Components/Form/FormInputGroup';
@@ -13,7 +14,7 @@ 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 } from 'Helpers/Props';
import { inputTypes, kinds, sizes } from 'Helpers/Props';
import translate from 'Utilities/String/translate';
import styles from './EditDownloadClientModalContent.css';
@@ -45,8 +46,12 @@ class EditDownloadClientModalContent extends Component {
implementationName,
name,
enable,
protocol,
priority,
removeCompletedDownloads,
removeFailedDownloads,
fields,
tags,
message
} = item;
@@ -142,6 +147,49 @@ class EditDownloadClientModalContent extends Component {
/>
</FormGroup>
<FormGroup>
<FormLabel>{translate('Tags')}</FormLabel>
<FormInputGroup
type={inputTypes.TAG}
name="tags"
helpText={translate('DownloadClientTagHelpText')}
{...tags}
onChange={onInputChange}
/>
</FormGroup>
<FieldSet
size={sizes.SMALL}
legend={translate('CompletedDownloadHandling')}
>
<FormGroup>
<FormLabel>{translate('RemoveCompleted')}</FormLabel>
<FormInputGroup
type={inputTypes.CHECK}
name="removeCompletedDownloads"
helpText={translate('RemoveCompletedDownloadsHelpText')}
{...removeCompletedDownloads}
onChange={onInputChange}
/>
</FormGroup>
{
protocol.value !== 'torrent' &&
<FormGroup>
<FormLabel>{translate('RemoveFailed')}</FormLabel>
<FormInputGroup
type={inputTypes.CHECK}
name="removeFailedDownloads"
helpText={translate('RemoveFailedDownloadsHelpText')}
{...removeFailedDownloads}
onChange={onInputChange}
/>
</FormGroup>
}
</FieldSet>
</Form>
}
</ModalBody>

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