Compare commits

..

224 Commits

Author SHA1 Message Date
Bogdan
e188c9aac0 Don't die when trying to open file with nullable path
Closes #3012
2023-10-19 17:35:23 +03:00
Bogdan
a3ae2359f5 Fixed: Ignore case when cleansing announce URLs
(cherry picked from commit 41ed300899e8d7de82b1113d13ac6f6cf28cec17)
2023-10-19 17:06:40 +03:00
Bogdan
5b92905dd4 Bump version to 0.3.9 2023-10-15 07:51:31 +03:00
Weblate
fc402743aa Multiple Translations updated by Weblate
ignore-downstream

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

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

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

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

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

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

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

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

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

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

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

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

Closes #2908
2023-09-08 05:08:09 +03:00
Bogdan
75efbd45e1 Fixed: Calculating seed time for qBittorrent
(cherry picked from commit 1b3ff64cc521396f9f1623617052c497649325a8)
2023-09-08 04:17:10 +03:00
Weblate
00cac507ad Multiple Translations updated by Weblate
ignore-downstream

Co-authored-by: DavidJares <david.jares@me.com>
Co-authored-by: Havok Dan <havokdan@yahoo.com.br>
Co-authored-by: 宿命 <331874545@qq.com>
Translate-URL: https://translate.servarr.com/projects/servarr/readarr/cs/
Translate-URL: https://translate.servarr.com/projects/servarr/readarr/pt_BR/
Translate-URL: https://translate.servarr.com/projects/servarr/readarr/zh_CN/
Translation: Servarr/Readarr
2023-09-07 11:58:51 +03:00
Bogdan
c4850505b0 New: Add Plex Media Server notifications 2023-09-07 10:18:24 +03:00
Bogdan
75213c86a1 Bump dotnet to 6.0.21 2023-09-05 16:30:21 +03:00
Bogdan
b8c3a42643 Migrate to merged proposals now included in babel/present-env
Closes #2899
2023-09-05 03:03:33 +03:00
Bogdan
8acb034aa6 Use not allowed cursor for disabled select options
(cherry picked from commit 229a4bba05d1f42089aa92b1d938747e152590b2)
2023-09-05 02:58:09 +03:00
Bogdan
889d32552b Update UI dev packages 2023-09-01 14:29:20 +03:00
Bogdan
adc5f4db97 Fixed: Increase timeout when downloading updates
(cherry picked from commit 467ce70291c793042ffb3ed8942c42e7bc1424d5)
2023-09-01 04:05:16 +03:00
Weblate
9d08050f96 Multiple Translations updated by Weblate
ignore-downstream

Co-authored-by: Havok Dan <havokdan@yahoo.com.br>
Co-authored-by: He Zhu <zhuhe202@qq.com>
Co-authored-by: monopolo11 <bernardorn21@gmail.com>
Translate-URL: https://translate.servarr.com/projects/servarr/readarr/es/
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-08-31 20:59:43 +03:00
Weblate
f8cffbb4cf Multiple Translations updated by Weblate
ignore-downstream

Co-authored-by: AlexR-sf <omg.portal.supp@gmail.com>
Co-authored-by: Anonymous <noreply@weblate.org>
Co-authored-by: DavidJares <david.jares@me.com>
Co-authored-by: Fixer <ygj59783@zslsz.com>
Co-authored-by: Havok Dan <havokdan@yahoo.com.br>
Co-authored-by: He Zhu <zhuhe202@qq.com>
Co-authored-by: Renan da Mota Ciciliato <renanciciliato@gmail.com>
Co-authored-by: Weblate <noreply@weblate.org>
Co-authored-by: brokje1988 <brokje1988@gmail.com>
Translate-URL: https://translate.servarr.com/projects/servarr/readarr/cs/
Translate-URL: https://translate.servarr.com/projects/servarr/readarr/nl/
Translate-URL: https://translate.servarr.com/projects/servarr/readarr/pt_BR/
Translate-URL: https://translate.servarr.com/projects/servarr/readarr/ro/
Translate-URL: https://translate.servarr.com/projects/servarr/readarr/ru/
Translate-URL: https://translate.servarr.com/projects/servarr/readarr/zh_CN/
Translation: Servarr/Readarr
2023-08-29 18:12:42 +03:00
Weblate
14aeb66142 Multiple Translations updated by Weblate
ignore-downstream

Co-authored-by: He Zhu <zhuhe202@qq.com>
Translate-URL: https://translate.servarr.com/projects/servarr/readarr/zh_CN/
Translation: Servarr/Readarr
2023-08-27 19:22:06 +03:00
Bogdan
37e8e11e31 Ensure the correct icons are spinning when refreshing authors and books 2023-08-25 21:51:52 +03:00
Bogdan
bdb2f14936 Prevent NullRef in GetChangedAuthors when metadata is down 2023-08-25 21:49:15 +03:00
Mark McDowall
a97af657be Improved UI error messages (stack trace and version)
(cherry picked from commit 37c355da51b654cea7309678c32a83a5cbe43d1f)

Closes #2207
2023-08-24 21:15:47 +03:00
Servarr
301127e6dc Automated API Docs update 2023-08-24 00:44:35 +03:00
Bogdan
1f95bcae4e New: Async HttpClient
(cherry picked from commit 0feee191462dd3e5dde66e476e8b4b46a85ec4f0)
2023-08-24 00:38:31 +03:00
Bogdan
29118cda45 New: Use HTTP/2 in HttpClient
(cherry picked from commit 78593f428acc578785f9ecfdd41fbf2443d93d84)
2023-08-24 00:38:31 +03:00
Bogdan
09beaa939d Fixed: (FileList) Prevent double query escaping in search requests 2023-08-24 00:38:31 +03:00
Bogdan
2107624f1c Prevent health checks warnings for disabled notifications
(cherry picked from commit 5a7f42a63e25d6abdb187c37e92a908a6b85fb4d)
2023-08-23 04:58:22 +03:00
Weblate
c1c2076e5c 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-08-23 04:57:55 +03:00
Bogdan
c31a797bd8 Revert "Switch to Parallel.ForEach for list processing with MaxParallelism"
This reverts commit ebb2b4eca3.
2023-08-22 06:03:14 +03:00
Qstick
ebb2b4eca3 Switch to Parallel.ForEach for list processing with MaxParallelism
(cherry picked from commit 0f93e04186f24abdb0cf0b3ba6a3505fda834e06)
2023-08-21 04:44:59 +03:00
Qstick
3ec5d9b9fe Use default MemoryAllocator for ImageSharp resizing
(cherry picked from commit c1a3a8249befde0a1b68e7845d5d2346066457a1)
2023-08-21 04:42:59 +03:00
Qstick
1ad84a7c44 Fixed: Ignore case when comparing torrent infohash
(cherry picked from commit 7986488c6d1687b0810b3bcac2c1dae725e770ac)
2023-08-20 16:10:26 -05:00
Bogdan
9d67c18254 Bump version to 0.3.4 2023-08-20 12:24:38 +03:00
Qstick
2e39c7340c Windows installer improvements
Co-Authored-By: Mark McDowall <markus101@users.noreply.github.com>
(cherry picked from commit 02c95658c4a5d38e1d0997ed5ef9bbd219ffc32c)
2023-08-19 18:41:06 +03:00
Qstick
f75add984f Cleanup distribution files
(cherry picked from commit 8b291d932f687297f18491469c44751e37e81173)
2023-08-19 18:41:06 +03:00
Bogdan
f0f6c3eb35 Update azure pipelines 2023-08-19 18:41:06 +03:00
Robin Dadswell
d94f866aeb Adds Pipeline testing for Postgres15 Databases 2023-08-19 18:41:06 +03:00
Robin Dadswell
618f07d138 Bump Npgsql to 7.0.4 2023-08-19 18:41:06 +03:00
Bogdan
3db33c988a Align logs filename with upstream 2023-08-19 18:33:37 +03:00
Bogdan
28e38b7f17 Prevent new builds on API docs updates 2023-08-19 14:12:03 +03:00
Servarr
ca403e6f31 Automated API Docs update [skip ci] 2023-08-19 13:56:48 +03:00
Weblate
51351dee1d 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: Robert A. Viana <robert.abreu@outlook.com>
Co-authored-by: Weblate <noreply@weblate.org>
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/bn/
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/hu/
Translate-URL: https://translate.servarr.com/projects/servarr/readarr/ja/
Translate-URL: https://translate.servarr.com/projects/servarr/readarr/ko/
Translate-URL: https://translate.servarr.com/projects/servarr/readarr/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/
Translation: Servarr/Readarr
2023-08-19 13:56:29 +03:00
Mark McDowall
2081f2e321 Fixed: Allow decimals for Custom Format size
(cherry picked from commit 7f5ddff568ce9f87bd45420cbd36690b190bd633)

Closes #2839
2023-08-19 13:31:48 +03:00
Stevie Robinson
c10a32534c Add info box to Remote Path Mappings Settings
(cherry picked from commit d8f3d7d3eafeafd8d4372db0076a63f935a29218)

Closes #2835
2023-08-19 13:30:36 +03:00
Mark McDowall
0e415c6ce3 New: Status message when downloading metadata in qBittorrent
(cherry picked from commit 8aa872edf4737798d4836f68fbd0697ee0511c41)
2023-08-19 13:28:19 +03:00
Mark McDowall
a8eb674071 Fixed: Ignore IOException deleting download folder after import
(cherry picked from commit d05cb40088a51eef5a2830bf2b55f5d05955578f)
2023-08-19 13:28:08 +03:00
Mark McDowall
7f8a1cf849 New: Success check mark on blue buttons is now white instead of green
(cherry picked from commit 566fae9d5857a10bd69c718368e7847e5a733faa)
2023-08-19 13:27:57 +03:00
Stevie Robinson
a3c0d10240 Translate Updated and Connection Lost Modals in frontend
(cherry picked from commit 074aa6f4457bf83173e6ba7209c452a6e0659a35)

Closes #2806
2023-08-19 02:20:04 +03:00
Bogdan
3ddeaaefe2 Use named tokens in frontend translate function 2023-08-19 02:14:53 +03:00
Bogdan
6d99de4fe0 Add default update branches as autocomplete values 2023-08-19 02:14:53 +03:00
Bogdan
8b36a5ce92 Don't block update UI settings under docker 2023-08-19 02:14:53 +03:00
Bogdan
1202a43466 Show warning when using the docker update mechanism
(cherry picked from commit cc538c4b2d33a1734c45c0667776d946596107e9)

Closes #2805
2023-08-19 02:14:49 +03:00
Mark McDowall
82bc2d1aa4 Fixed: Don't block updates under docker unless configured in package_info
(cherry picked from commit 5a7e34e291c2715aa67161e5c455d25e80f498df)

Closes #2772
2023-08-19 02:14:18 +03:00
Bogdan
ed9af393b7 Fix flaky automation tests 2023-08-19 01:51:37 +03:00
bakerboy448
0799cfc885 Remove reddit from readme 2023-08-18 20:27:16 +03:00
Mark McDowall
331d0c9a9c New: Ignore inaccessible files with getting files
(cherry picked from commit e5aa8584100d96a2077c57f74ae5b2ceab63de19)
2023-08-18 18:37:26 +03:00
Bogdan
03c93c9c84 Fix test in DiskSpaceServiceFixture 2023-08-18 14:15:33 +03:00
Mark McDowall
60f6ed030b Fix GetBestRootFolderPath tests
(cherry picked from commit 63a911a9a549749b5460c2b9fea48a25e78c52a4)
2023-08-18 14:15:33 +03:00
Mark McDowall
cc70d61735 Fixed: UI loading when author or root folder path is for wrong OS
(cherry picked from commit 5f7217844533907d7fc6287a48efb31987736c4c)
2023-08-18 14:15:33 +03:00
Bogdan
a7b965100d Fix BookInfoProxySearchFixture test 2023-08-18 13:28:18 +03:00
Bogdan
8901118aef Add default schema values for root folders
(cherry picked from commit 7ae82a982ce17efa46c0a0b23256a26edf90babb)
2023-08-18 12:58:06 +03:00
Bogdan
99c17d7698 Improve messaging for Interactive Search
(cherry picked from commit 7893fdde104959c4f3c32d9e1000e2479f7a5b12)

Closes #2752
2023-08-18 12:49:56 +03:00
Bogdan
b84e83b082 Replace docker detection for cgroup v2
(cherry picked from commit 78d4dee4610c5f3f90cc69469004008aa64900b8)
2023-08-18 10:58:16 +03:00
Qstick
4249f5324a Cleanup other provider status code
(cherry picked from commit c281a7818adce8db728d2a104f4444cb9c0baf2c)
2023-08-16 12:18:20 +03:00
Qstick
9e1630e9a4 New: Notifications (Connect) Status
(cherry picked from commit e3545801721e00d4e5cac3fa534e66dcbe9d2d05)
(cherry picked from commit cb27b05a6c046ca0a6e4998f7e7ecd6b45add1a2)
2023-08-16 12:18:20 +03:00
Weblate
68b2773913 Multiple Translations updated by Weblate
ignore-downstream

Co-authored-by: David Molero <contact@dolvem.com>
Co-authored-by: Fixer <ygj59783@zslsz.com>
Co-authored-by: Havok Dan <havokdan@yahoo.com.br>
Co-authored-by: deepserket <deepserket@gmail.com>
Translate-URL: https://translate.servarr.com/projects/servarr/readarr/es/
Translate-URL: https://translate.servarr.com/projects/servarr/readarr/it/
Translate-URL: https://translate.servarr.com/projects/servarr/readarr/pt_BR/
Translate-URL: https://translate.servarr.com/projects/servarr/readarr/ro/
Translation: Servarr/Readarr
2023-08-15 20:08:44 +03:00
Bogdan
ad446b358e Fix combined search tests 2023-08-13 14:17:32 +03:00
Bogdan
423a8ecbe1 Bump version to 0.3.3 2023-08-13 13:40:33 +03:00
Bogdan
29a12aa3b0 Add one minute back-off level for all providers
(cherry picked from commit d8f314ff0ef64e8d90b21b7865e46be74db5e570)

Closes #2792
2023-08-12 10:52:44 +03:00
Weblate
695781dde5 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-08-11 21:02:05 +03:00
Servarr
4e8ddd3018 Automated API Docs update [skip ci] 2023-08-11 20:04:43 +03:00
Bogdan
eb67231a45 New: Show successful grabs in Interactive Search with green icon
(cherry picked from commit 366b2b8b52d8375f1f41719a09893136009a5b48)

Closes #2780
2023-08-11 19:51:21 +03:00
Mark McDowall
3d3a458828 New: Add additional logging when renaming extra files
(cherry picked from commit 1ae0dc81f73ef74078f07fd5536a7d9058df649d)

Closes #2782
2023-08-11 19:48:46 +03:00
Bogdan
a11930a03f add @types/lodash 2023-08-11 19:40:21 +03:00
Bogdan
abaf39d67e Add simplified translations 2023-08-11 19:40:01 +03:00
Bogdan
894a5943e4 Simplify column translations
(cherry picked from commit 551ea18caf50353c4c8dbeba5e42d266dbbfb54d)

Closes #2759
2023-08-11 19:14:44 +03:00
Bogdan
be26647afb New: More translations for columns
(cherry picked from commit aee8579d1823b7dfb94c0055fe33b5fb5a7fbf17)

Towards #2733
2023-08-11 18:56:33 +03:00
Mark McDowall
b319a4bce7 Fixed: Translations for columns
(cherry picked from commit 6d53d2a153a98070c42d0619c15902b6bd5dfab4)

Closes #2702
2023-08-11 18:53:50 +03:00
Mark McDowall
f03fd7e95e Fixed: Improve translation loading
(cherry picked from commit 73c5ec1da4dd00301e1b0dddbcea37590a99b045)

Closes #2699
2023-08-11 18:52:34 +03:00
Mark McDowall
7f25a3c4b1 UI loading improvements
Fixed: Caching for dynamically loaded JS files
Fixed: Incorrect caching of initialize.js
(cherry picked from commit f0cb5b81f140c67fa84162e094cc4e0f3476f5da)

Closes #2690
Closes #2696
2023-08-11 18:21:47 +03:00
Weblate
e3247dc505 Multiple Translations updated by Weblate
ignore-downstream

Co-authored-by: Fixer <ygj59783@zslsz.com>
Co-authored-by: Havok Dan <havokdan@yahoo.com.br>
Co-authored-by: Ivan Mazzoli <dreadtank27@gmail.com>
Co-authored-by: Nir Israel Hen <nirisraelh@gmail.com>
Co-authored-by: Weblate <noreply@weblate.org>
Co-authored-by: byakurau <byakurau1@gmail.com>
Co-authored-by: wilfriedarma <wilfriedarma.collet@gmail.com>
Translate-URL: https://translate.servarr.com/projects/servarr/readarr/
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/it/
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/
Translation: Servarr/Readarr
2023-08-10 19:59:14 +03:00
Weblate
3677fd6d34 Multiple Translations updated by Weblate
ignore-downstream

Co-authored-by: Havok Dan <havokdan@yahoo.com.br>
Co-authored-by: Ivan Mazzoli <dreadtank27@gmail.com>
Co-authored-by: Weblate <noreply@weblate.org>
Co-authored-by: byakurau <byakurau1@gmail.com>
Translate-URL: https://translate.servarr.com/projects/servarr/readarr/it/
Translate-URL: https://translate.servarr.com/projects/servarr/readarr/pl/
Translate-URL: https://translate.servarr.com/projects/servarr/readarr/pt_BR/
Translation: Servarr/Readarr
2023-08-10 19:58:44 +03:00
Bogdan
4f6901b1ff Fixed: Ensure failing providers are marked as failed when testing all
(cherry picked from commit f6c05d4456a5667398319e249614e2eed115621e)
2023-08-10 19:58:25 +03:00
Bogdan
ce820f6f73 Fixed: Detect Docker when using control group v2 2023-08-09 10:48:37 +03:00
Bogdan
53e6cb24b7 Bump version to 0.3.2 2023-08-06 19:27:59 +03:00
Bogdan
7c1ca8acc1 New: Health check for indexers with invalid download client
(cherry picked from commit 377fce6fe15c0875c4bd33f1371a31af79c9310c)

Closes #2760
2023-08-06 19:25:00 +03:00
Bogdan
5e9e578101 Ensure path is valid before watching it
(cherry picked from commit 1245b2c58b5a1b5fb4aee9a4f974ecfb131de2bd)
2023-08-06 19:24:58 +03:00
Bogdan
156407c541 Add @types/redux-actions 2023-08-06 19:12:55 +03:00
Mark McDowall
1ef6c60318 Sync Popover with upstream
(cherry picked from commit bdcfef80d627e777d7932c54cda04cbe7c656ffc)
2023-08-06 19:12:04 +03:00
servarr[bot]
73b3b1848b Filter user issues from Sentry
(cherry picked from commit 03d361f5537bfc0caba1b86085f974570942fdbc)

Co-authored-by: Qstick <qstick@gmail.com>
2023-08-05 21:56:25 +03:00
Weblate
33fbd95707 Multiple Translations updated by Weblate
ignore-downstream

Co-authored-by: Albert <zuozl1992@foxmail.com>
Co-authored-by: Fixer <ygj59783@zslsz.com>
Co-authored-by: Havok Dan <havokdan@yahoo.com.br>
Co-authored-by: Magnus <magnus.fladvad@gmail.com>
Co-authored-by: Stjepan <stjepstjepanovic@gmail.com>
Co-authored-by: Thirrian <matthiaslantermann@gmail.com>
Co-authored-by: stormaac <yxc.frank@gmail.com>
Translate-URL: https://translate.servarr.com/projects/servarr/readarr/fr/
Translate-URL: https://translate.servarr.com/projects/servarr/readarr/hr/
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/pt_BR/
Translate-URL: https://translate.servarr.com/projects/servarr/readarr/ro/
Translate-URL: https://translate.servarr.com/projects/servarr/readarr/zh_CN/
Translation: Servarr/Readarr
2023-08-03 22:55:45 +03: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
573 changed files with 14549 additions and 5661 deletions

View File

@@ -275,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

3
.gitattributes vendored
View File

@@ -3,8 +3,7 @@
# Explicitly set bash scripts to have unix endings
*.sh text eol=lf
distribution/debian/* text eol=lf
macOS/Readarr text eol=lf
distribution/osx/Readarr text eol=lf
# Custom for Visual Studio
*.cs diff=csharp

View File

@@ -1,5 +1,5 @@
name: Bug Report
description: 'Report a new bug, if you are not 100% certain this is a bug please go to our Reddit or Discord first'
description: 'Report a new bug, if you are not 100% certain this is a bug please go to our Discord first'
labels: ['Type: Bug', 'Status: Needs Triage']
body:
- type: checkboxes
@@ -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

@@ -3,6 +3,3 @@ contact_links:
- name: Support via Discord
url: https://readarr.com/discord
about: Chat with users and devs on support and setup related topics.
- name: Support via Reddit
url: https://reddit.com/r/Readarr
about: Discuss and search thru support topics.

View File

@@ -15,8 +15,7 @@ jobs:
issue-comment: >
:wave: @{issue-author}, we use the issue tracker exclusively
for bug reports and feature requests. However, this issue appears
to be a support request. Please hop over onto our [Discord](https://readarr.com/discord)
or [Subreddit](https://reddit.com/r/readarr)
to be a support request. Please hop over onto our [Discord](https://readarr.com/discord).
close-issue: true
lock-issue: false
- uses: dessant/support-requests@v3

View File

@@ -30,7 +30,6 @@ Note that only one type of a given book is supported. If you want both an audiob
[![Wiki](https://img.shields.io/badge/servarr-wiki-181717.svg?maxAge=60)](https://wiki.servarr.com/readarr)
[![Discord](https://img.shields.io/badge/discord-chat-7289DA.svg?maxAge=60)](https://readarr.com/discord)
[![Reddit](https://img.shields.io/badge/reddit-discussion-FF4500.svg?maxAge=60)](https://www.reddit.com/r/readarr)
Note: GitHub Issues are for Bugs and Feature Requests Only

View File

@@ -9,13 +9,13 @@ variables:
testsFolder: './_tests'
yarnCacheFolder: $(Pipeline.Workspace)/.yarn
nugetCacheFolder: $(Pipeline.Workspace)/.nuget/packages
majorVersion: '0.2.0'
majorVersion: '0.3.9'
minorVersion: $[counter('minorVersion', 1)]
readarrVersion: '$(majorVersion).$(minorVersion)'
buildName: '$(Build.SourceBranchName).$(readarrVersion)'
sentryOrg: 'servarr'
sentryUrl: 'https://sentry.servarr.com'
dotnetVersion: '6.0.408'
dotnetVersion: '6.0.413'
nodeVersion: '16.X'
innoVersion: '6.2.0'
windowsImage: 'windows-2022'
@@ -27,6 +27,10 @@ trigger:
include:
- develop
- master
paths:
exclude:
- .github
- src/Readarr.Api.*/openapi.json
pr:
branches:
@@ -34,82 +38,37 @@ pr:
- develop
paths:
exclude:
- .github
- src/NzbDrone.Core/Localization/Core
- src/Readarr.Api.*/openapi.json
stages:
- stage: Build_Backend_Windows
displayName: Build Backend
dependsOn: []
- stage: Setup
displayName: Setup
jobs:
- job: Backend
strategy:
matrix:
Windows:
osName: 'Windows'
imageName: ${{ variables.windowsImage }}
enableAnalysis: 'false'
- job:
displayName: Build Variables
pool:
vmImage: $(imageName)
variables:
# Disable stylecop here - linting errors get caught by the analyze task
EnableAnalyzers: $(enableAnalysis)
vmImage: ${{ variables.linuxImage }}
steps:
# Set the build name properly. The 'name' property won't recursively expand so hack here:
- bash: echo "##vso[build.updatebuildnumber]$READARRVERSION"
displayName: Set Build Name
- checkout: self
submodules: true
fetchDepth: 1
- task: UseDotNet@2
displayName: 'Install .net core'
inputs:
version: $(dotnetVersion)
- bash: |
SDK_PATH="${AGENT_TOOLSDIRECTORY}/dotnet/sdk/${DOTNETVERSION}"
BUNDLEDVERSIONS="${SDK_PATH}/Microsoft.NETCoreSdk.BundledVersions.props"
if ! grep -q freebsd-x64 $BUNDLEDVERSIONS; then
sed -i.ORI 's/osx-x64/osx-x64;freebsd-x64;linux-x86/' $BUNDLEDVERSIONS
if [[ $BUILD_REASON == "PullRequest" ]]; then
git diff origin/develop...HEAD --name-only | grep -E "^(src/|azure-pipelines.yml)"
echo $? > not_backend_update
else
echo 0 > not_backend_update
fi
displayName: Extra Platform Support
- task: Cache@2
inputs:
key: 'nuget | "$(Agent.OS)" | $(Build.SourcesDirectory)/src/Directory.Packages.props'
path: $(nugetCacheFolder)
displayName: Cache NuGet packages
- bash: ./build.sh --backend --enable-bsd
displayName: Build Readarr Backend
env:
NUGET_PACKAGES: $(nugetCacheFolder)
- powershell: Get-ChildItem _output\net6.0*,_output\*.Update\* -Recurse | Where { $_.Fullname -notlike "*\publish\*" -and $_.attributes -notlike "*directory*" } | Remove-Item
displayName: Clean up intermediate output
- publish: $(outputFolder)
artifact: '$(osName)Backend'
displayName: Publish Backend
- publish: '$(testsFolder)/net6.0/win-x64/publish'
artifact: win-x64-tests
displayName: Publish win-x64 Test Package
- publish: '$(testsFolder)/net6.0/linux-x64/publish'
artifact: linux-x64-tests
displayName: Publish linux-x64 Test Package
- publish: '$(testsFolder)/net6.0/linux-x86/publish'
artifact: linux-x86-tests
displayName: Publish linux-x86 Test Package
- publish: '$(testsFolder)/net6.0/linux-musl-x64/publish'
artifact: linux-musl-x64-tests
displayName: Publish linux-musl-x64 Test Package
- publish: '$(testsFolder)/net6.0/freebsd-x64/publish'
artifact: freebsd-x64-tests
displayName: Publish freebsd-x64 Test Package
- publish: '$(testsFolder)/net6.0/osx-x64/publish'
artifact: osx-x64-tests
displayName: Publish osx-x64 Test Package
- stage: Build_Backend_Other
displayName: Build Backend (Other OS)
dependsOn: []
cat not_backend_update
displayName: Check for Backend File Changes
- publish: not_backend_update
artifact: not_backend_update
displayName: Publish update type
- stage: Build_Backend
displayName: Build Backend
dependsOn: Setup
jobs:
- job: Backend
strategy:
@@ -122,6 +81,10 @@ stages:
osName: 'Mac'
imageName: ${{ variables.macImage }}
enableAnalysis: 'false'
Windows:
osName: 'Windows'
imageName: ${{ variables.windowsImage }}
enableAnalysis: 'false'
pool:
vmImage: $(imageName)
@@ -137,22 +100,17 @@ stages:
inputs:
version: $(dotnetVersion)
- bash: |
SDK_PATH="${AGENT_TOOLSDIRECTORY}/dotnet/sdk/${DOTNETVERSION}"
BUNDLEDVERSIONS="${SDK_PATH}/Microsoft.NETCoreSdk.BundledVersions.props"
if ! grep -q freebsd-x64 $BUNDLEDVERSIONS; then
BUNDLEDVERSIONS=${AGENT_TOOLSDIRECTORY}/dotnet/sdk/${DOTNETVERSION}/Microsoft.NETCoreSdk.BundledVersions.props
echo $BUNDLEDVERSIONS
if grep -q freebsd-x64 $BUNDLEDVERSIONS; then
echo "Extra platforms already enabled"
else
echo "Enabling extra platform support"
sed -i.ORI 's/osx-x64/osx-x64;freebsd-x64;linux-x86/' $BUNDLEDVERSIONS
fi
displayName: Extra Platform Support
- task: Cache@2
inputs:
key: 'nuget | "$(Agent.OS)" | $(Build.SourcesDirectory)/src/Directory.Packages.props'
path: $(nugetCacheFolder)
displayName: Cache NuGet packages
displayName: Enable Extra Platform Support
- bash: ./build.sh --backend --enable-extra-platforms
displayName: Build Readarr Backend
env:
NUGET_PACKAGES: $(nugetCacheFolder)
- bash: |
find ${OUTPUTFOLDER} -type f ! -path "*/publish/*" -exec rm -rf {} \;
find ${OUTPUTFOLDER} -depth -empty -type d -exec rm -r "{}" \;
@@ -160,10 +118,38 @@ stages:
find ${TESTSFOLDER} -depth -empty -type d -exec rm -r "{}" \;
displayName: Clean up intermediate output
condition: and(succeeded(), ne(variables['osName'], 'Windows'))
- publish: $(outputFolder)
artifact: '$(osName)Backend'
displayName: Publish Backend
condition: and(succeeded(), eq(variables['osName'], 'Windows'))
- publish: '$(testsFolder)/net6.0/win-x64/publish'
artifact: win-x64-tests
displayName: Publish win-x64 Test Package
condition: and(succeeded(), eq(variables['osName'], 'Windows'))
- publish: '$(testsFolder)/net6.0/linux-x64/publish'
artifact: linux-x64-tests
displayName: Publish linux-x64 Test Package
condition: and(succeeded(), eq(variables['osName'], 'Windows'))
- publish: '$(testsFolder)/net6.0/linux-x86/publish'
artifact: linux-x86-tests
displayName: Publish linux-x86 Test Package
condition: and(succeeded(), eq(variables['osName'], 'Windows'))
- publish: '$(testsFolder)/net6.0/linux-musl-x64/publish'
artifact: linux-musl-x64-tests
displayName: Publish linux-musl-x64 Test Package
condition: and(succeeded(), eq(variables['osName'], 'Windows'))
- publish: '$(testsFolder)/net6.0/freebsd-x64/publish'
artifact: freebsd-x64-tests
displayName: Publish freebsd-x64 Test Package
condition: and(succeeded(), eq(variables['osName'], 'Windows'))
- publish: '$(testsFolder)/net6.0/osx-x64/publish'
artifact: osx-x64-tests
displayName: Publish osx-x64 Test Package
condition: and(succeeded(), eq(variables['osName'], 'Windows'))
- stage: Build_Frontend
displayName: Frontend
dependsOn: []
dependsOn: Setup
jobs:
- job: Build
strategy:
@@ -192,7 +178,6 @@ stages:
key: 'yarn | "$(osName)" | yarn.lock'
restoreKeys: |
yarn | "$(osName)"
yarn
path: $(yarnCacheFolder)
displayName: Cache Yarn packages
- bash: ./build.sh --frontend
@@ -204,10 +189,10 @@ stages:
artifact: '$(osName)Frontend'
displayName: Publish Frontend
condition: and(succeeded(), eq(variables['osName'], 'Windows'))
- stage: Installer
dependsOn:
- Build_Backend_Windows
- Build_Backend
- Build_Frontend
jobs:
- job: Windows_Installer
@@ -231,8 +216,8 @@ stages:
displayName: Fetch Frontend
- bash: |
./build.sh --packages --installer
cp setup/output/Readarr.*win-x64.exe ${BUILD_ARTIFACTSTAGINGDIRECTORY}/Readarr.${BUILDNAME}.windows-core-x64-installer.exe
cp setup/output/Readarr.*win-x86.exe ${BUILD_ARTIFACTSTAGINGDIRECTORY}/Readarr.${BUILDNAME}.windows-core-x86-installer.exe
cp distribution/windows/setup/output/Readarr.*win-x64.exe ${BUILD_ARTIFACTSTAGINGDIRECTORY}/Readarr.${BUILDNAME}.windows-core-x64-installer.exe
cp distribution/windows/setup/output/Readarr.*win-x86.exe ${BUILD_ARTIFACTSTAGINGDIRECTORY}/Readarr.${BUILDNAME}.windows-core-x86-installer.exe
displayName: Create Installers
- publish: $(Build.ArtifactStagingDirectory)
artifact: 'WindowsInstaller'
@@ -240,7 +225,7 @@ stages:
- stage: Packages
dependsOn:
- Build_Backend_Windows
- Build_Backend
- Build_Frontend
jobs:
- job: Other_Packages
@@ -382,7 +367,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}"
@@ -406,14 +391,29 @@ stages:
SENTRY_AUTH_TOKEN: $(sentryAuthTokenServarr)
SENTRY_ORG: $(sentryOrg)
SENTRY_URL: $(sentryUrl)
- stage: Unit_Test
displayName: Unit Tests
dependsOn: Build_Backend_Windows
condition: succeeded()
dependsOn: Build_Backend
jobs:
- job: Prepare
pool:
vmImage: ${{ variables.linuxImage }}
steps:
- checkout: none
- task: DownloadPipelineArtifact@2
inputs:
buildType: 'current'
artifactName: 'not_backend_update'
targetPath: '.'
- bash: echo "##vso[task.setvariable variable=backendNotUpdated;isOutput=true]$(cat not_backend_update)"
name: setVar
- job: Unit
displayName: Unit Native
dependsOn: Prepare
condition: and(succeeded(), eq(dependencies.Prepare.outputs['setVar.backendNotUpdated'], '0'))
workspace:
clean: all
@@ -479,6 +479,8 @@ stages:
- job: Unit_Docker
displayName: Unit Docker
dependsOn: Prepare
condition: and(succeeded(), eq(dependencies.Prepare.outputs['setVar.backendNotUpdated'], '0'))
strategy:
matrix:
alpine:
@@ -492,11 +494,11 @@ stages:
pool:
vmImage: ${{ variables.linuxImage }}
container: $[ variables['containerImage'] ]
timeoutInMinutes: 10
steps:
- task: UseDotNet@2
displayName: 'Install .NET'
@@ -530,12 +532,14 @@ stages:
testResultsFiles: '**/TestResult.xml'
testRunTitle: '$(testName) Unit Tests'
failTaskOnFailedTests: true
- job: Unit_LinuxCore_Postgres
displayName: Unit Native LinuxCore with Postgres Database
- job: Unit_LinuxCore_Postgres14
displayName: Unit Native LinuxCore with Postgres14 Database
dependsOn: Prepare
condition: and(succeeded(), eq(dependencies.Prepare.outputs['setVar.backendNotUpdated'], '0'))
variables:
pattern: 'Readarr.*.linux-core-x64.tar.gz'
artifactName: LinuxCoreTests
artifactName: linux-x64-tests
Readarr__Postgres__Host: 'localhost'
Readarr__Postgres__Port: '5432'
Readarr__Postgres__User: 'readarr'
@@ -545,7 +549,7 @@ stages:
vmImage: ${{ variables.linuxImage }}
timeoutInMinutes: 10
steps:
- task: UseDotNet@2
displayName: 'Install .net core'
@@ -556,7 +560,7 @@ stages:
displayName: Download Test Artifact
inputs:
buildType: 'current'
artifactName: 'linux-x64-Tests'
artifactName: $(artifactName)
targetPath: $(testsFolder)
- bash: find ${TESTSFOLDER} -name "Readarr.Test.Dummy" -exec chmod a+x {} \;
displayName: Make Test Dummy Executable
@@ -579,15 +583,84 @@ stages:
inputs:
testResultsFormat: 'NUnit'
testResultsFiles: '**/TestResult.xml'
testRunTitle: 'LinuxCore Postgres Unit Tests'
testRunTitle: 'LinuxCore Postgres14 Unit Tests'
failTaskOnFailedTests: true
- job: Unit_LinuxCore_Postgres15
displayName: Unit Native LinuxCore with Postgres15 Database
dependsOn: Prepare
condition: and(succeeded(), eq(dependencies.Prepare.outputs['setVar.backendNotUpdated'], '0'))
variables:
pattern: 'Readarr.*.linux-core-x64.tar.gz'
artifactName: linux-x64-tests
Readarr__Postgres__Host: 'localhost'
Readarr__Postgres__Port: '5432'
Readarr__Postgres__User: 'readarr'
Readarr__Postgres__Password: 'readarr'
pool:
vmImage: ${{ variables.linuxImage }}
timeoutInMinutes: 10
steps:
- task: UseDotNet@2
displayName: 'Install .net core'
inputs:
version: $(dotnetVersion)
- checkout: none
- task: DownloadPipelineArtifact@2
displayName: Download Test Artifact
inputs:
buildType: 'current'
artifactName: $(artifactName)
targetPath: $(testsFolder)
- bash: find ${TESTSFOLDER} -name "Readarr.Test.Dummy" -exec chmod a+x {} \;
displayName: Make Test Dummy Executable
condition: and(succeeded(), ne(variables['osName'], 'Windows'))
- bash: |
docker run -d --name=postgres15 \
-e POSTGRES_PASSWORD=readarr \
-e POSTGRES_USER=readarr \
-p 5432:5432/tcp \
-v /usr/share/zoneinfo/America/Chicago:/etc/localtime:ro \
postgres:15
displayName: Start postgres
- bash: |
chmod a+x ${TESTSFOLDER}/test.sh
ls -lR ${TESTSFOLDER}
${TESTSFOLDER}/test.sh Linux Unit Test
displayName: Run Tests
- task: PublishTestResults@2
displayName: Publish Test Results
inputs:
testResultsFormat: 'NUnit'
testResultsFiles: '**/TestResult.xml'
testRunTitle: 'LinuxCore Postgres15 Unit Tests'
failTaskOnFailedTests: true
- stage: Integration
displayName: Integration
dependsOn: Packages
jobs:
- job: Prepare
pool:
vmImage: ${{ variables.linuxImage }}
steps:
- checkout: none
- task: DownloadPipelineArtifact@2
inputs:
buildType: 'current'
artifactName: 'not_backend_update'
targetPath: '.'
- bash: echo "##vso[task.setvariable variable=backendNotUpdated;isOutput=true]$(cat not_backend_update)"
name: setVar
- job: Integration_Native
displayName: Integration Native
dependsOn: Prepare
condition: and(succeeded(), eq(dependencies.Prepare.outputs['setVar.backendNotUpdated'], '0'))
strategy:
matrix:
MacCore:
@@ -608,7 +681,7 @@ stages:
pool:
vmImage: $(imageName)
steps:
- task: UseDotNet@2
displayName: 'Install .net core'
@@ -630,7 +703,7 @@ stages:
targetPath: $(Build.ArtifactStagingDirectory)
- task: ExtractFiles@1
inputs:
archiveFilePatterns: '$(Build.ArtifactStagingDirectory)/**/$(pattern)'
archiveFilePatterns: '$(Build.ArtifactStagingDirectory)/**/$(pattern)'
destinationFolder: '$(Build.ArtifactStagingDirectory)/bin'
displayName: Extract Package
- bash: |
@@ -649,8 +722,10 @@ stages:
failTaskOnFailedTests: true
displayName: Publish Test Results
- job: Integration_LinuxCore_Postgres
displayName: Integration Native LinuxCore with Postgres Database
- job: Integration_LinuxCore_Postgres14
displayName: Integration Native LinuxCore with Postgres14 Database
dependsOn: Prepare
condition: and(succeeded(), eq(dependencies.Prepare.outputs['setVar.backendNotUpdated'], '0'))
variables:
pattern: 'Readarr.*.linux-core-x64.tar.gz'
Readarr__Postgres__Host: 'localhost'
@@ -682,7 +757,7 @@ stages:
targetPath: $(Build.ArtifactStagingDirectory)
- task: ExtractFiles@1
inputs:
archiveFilePatterns: '$(Build.ArtifactStagingDirectory)/**/$(pattern)'
archiveFilePatterns: '$(Build.ArtifactStagingDirectory)/**/$(pattern)'
destinationFolder: '$(Build.ArtifactStagingDirectory)/bin'
displayName: Extract Package
- bash: |
@@ -705,12 +780,77 @@ stages:
inputs:
testResultsFormat: 'NUnit'
testResultsFiles: '**/TestResult.xml'
testRunTitle: 'Integration LinuxCore Postgres Database Integration Tests'
testRunTitle: 'Integration LinuxCore Postgres14 Database Integration Tests'
failTaskOnFailedTests: true
displayName: Publish Test Results
- job: Integration_LinuxCore_Postgres15
displayName: Integration Native LinuxCore with Postgres Database
dependsOn: Prepare
condition: and(succeeded(), eq(dependencies.Prepare.outputs['setVar.backendNotUpdated'], '0'))
variables:
pattern: 'Readarr.*.linux-core-x64.tar.gz'
Readarr__Postgres__Host: 'localhost'
Readarr__Postgres__Port: '5432'
Readarr__Postgres__User: 'readarr'
Readarr__Postgres__Password: 'readarr'
pool:
vmImage: ${{ variables.linuxImage }}
steps:
- task: UseDotNet@2
displayName: 'Install .net core'
inputs:
version: $(dotnetVersion)
- checkout: none
- task: DownloadPipelineArtifact@2
displayName: Download Test Artifact
inputs:
buildType: 'current'
artifactName: 'linux-x64-tests'
targetPath: $(testsFolder)
- task: DownloadPipelineArtifact@2
displayName: Download Build Artifact
inputs:
buildType: 'current'
artifactName: Packages
itemPattern: '**/$(pattern)'
targetPath: $(Build.ArtifactStagingDirectory)
- task: ExtractFiles@1
inputs:
archiveFilePatterns: '$(Build.ArtifactStagingDirectory)/**/$(pattern)'
destinationFolder: '$(Build.ArtifactStagingDirectory)/bin'
displayName: Extract Package
- bash: |
mkdir -p ./bin/
cp -r -v ${BUILD_ARTIFACTSTAGINGDIRECTORY}/bin/Readarr/. ./bin/
displayName: Move Package Contents
- bash: |
docker run -d --name=postgres15 \
-e POSTGRES_PASSWORD=readarr \
-e POSTGRES_USER=readarr \
-p 5432:5432/tcp \
-v /usr/share/zoneinfo/America/Chicago:/etc/localtime:ro \
postgres:15
displayName: Start postgres
- bash: |
chmod a+x ${TESTSFOLDER}/test.sh
${TESTSFOLDER}/test.sh Linux Integration Test
displayName: Run Integration Tests
- task: PublishTestResults@2
inputs:
testResultsFormat: 'NUnit'
testResultsFiles: '**/TestResult.xml'
testRunTitle: 'Integration LinuxCore Postgres15 Database Integration Tests'
failTaskOnFailedTests: true
displayName: Publish Test Results
- job: Integration_FreeBSD
displayName: Integration Native FreeBSD
dependsOn: Prepare
condition: and(succeeded(), eq(dependencies.Prepare.outputs['setVar.backendNotUpdated'], '0'))
workspace:
clean: all
variables:
@@ -755,6 +895,8 @@ stages:
- job: Integration_Docker
displayName: Integration Docker
dependsOn: Prepare
condition: and(succeeded(), eq(dependencies.Prepare.outputs['setVar.backendNotUpdated'], '0'))
strategy:
matrix:
alpine:
@@ -773,7 +915,7 @@ stages:
container: $[ variables['containerImage'] ]
timeoutInMinutes: 15
steps:
- task: UseDotNet@2
displayName: 'Install .NET'
@@ -801,7 +943,7 @@ stages:
targetPath: $(Build.ArtifactStagingDirectory)
- task: ExtractFiles@1
inputs:
archiveFilePatterns: '$(Build.ArtifactStagingDirectory)/**/$(pattern)'
archiveFilePatterns: '$(Build.ArtifactStagingDirectory)/**/$(pattern)'
destinationFolder: '$(Build.ArtifactStagingDirectory)/bin'
displayName: Extract Package
- bash: |
@@ -823,7 +965,7 @@ stages:
- stage: Automation
displayName: Automation
dependsOn: Packages
jobs:
- job: Automation
strategy:
@@ -833,20 +975,23 @@ stages:
artifactName: 'linux-x64'
imageName: ${{ variables.linuxImage }}
pattern: 'Readarr.*.linux-core-x64.tar.gz'
failBuild: true
Mac:
osName: 'Mac'
artifactName: 'osx-x64'
imageName: ${{ variables.macImage }}
pattern: 'Readarr.*.osx-core-x64.tar.gz'
failBuild: true
Windows:
osName: 'Windows'
artifactName: 'win-x64'
imageName: ${{ variables.windowsImage }}
pattern: 'Readarr.*.windows-core-x64.zip'
failBuild: true
pool:
vmImage: $(imageName)
steps:
- task: UseDotNet@2
displayName: 'Install .net core'
@@ -868,7 +1013,7 @@ stages:
targetPath: $(Build.ArtifactStagingDirectory)
- task: ExtractFiles@1
inputs:
archiveFilePatterns: '$(Build.ArtifactStagingDirectory)/**/$(pattern)'
archiveFilePatterns: '$(Build.ArtifactStagingDirectory)/**/$(pattern)'
destinationFolder: '$(Build.ArtifactStagingDirectory)/bin'
displayName: Extract Package
- bash: |
@@ -888,20 +1033,35 @@ stages:
TargetFolder: '$(Build.ArtifactStagingDirectory)/screenshots'
- publish: $(Build.ArtifactStagingDirectory)/screenshots
artifact: '$(osName)AutomationScreenshots'
condition: and(succeeded(), eq(variables['System.JobAttempt'], '1'))
displayName: Publish Screenshot Bundle
condition: and(succeeded(), eq(variables['System.JobAttempt'], '1'))
- task: PublishTestResults@2
inputs:
testResultsFormat: 'NUnit'
testResultsFiles: '**/TestResult.xml'
testRunTitle: '$(osName) Automation Tests'
failTaskOnFailedTests: true
failTaskOnFailedTests: $(failBuild)
displayName: Publish Test Results
- stage: Analyze
dependsOn: []
dependsOn:
- Setup
displayName: Analyze
jobs:
- job: Prepare
pool:
vmImage: ${{ variables.linuxImage }}
steps:
- checkout: none
- task: DownloadPipelineArtifact@2
inputs:
buildType: 'current'
artifactName: 'not_backend_update'
targetPath: '.'
- bash: echo "##vso[task.setvariable variable=backendNotUpdated;isOutput=true]$(cat not_backend_update)"
name: setVar
- job: Lint_Frontend
displayName: Lint Frontend
strategy:
@@ -927,7 +1087,6 @@ stages:
key: 'yarn | "$(osName)" | yarn.lock'
restoreKeys: |
yarn | "$(osName)"
yarn
path: $(yarnCacheFolder)
displayName: Cache Yarn packages
- bash: ./build.sh --lint
@@ -956,11 +1115,16 @@ stages:
cliProjectVersion: '$(readarrVersion)'
cliSources: './frontend'
- task: SonarCloudAnalyze@1
- job: Api_Docs
displayName: API Docs
dependsOn: Prepare
condition: |
and(succeeded(), eq(variables['Build.SourceBranch'], 'refs/heads/develop'))
and
(
and(succeeded(), eq(variables['Build.SourceBranch'], 'refs/heads/develop')),
and(succeeded(), eq(dependencies.Prepare.outputs['setVar.backendNotUpdated'], '0'))
)
pool:
vmImage: ${{ variables.windowsImage }}
@@ -973,7 +1137,7 @@ stages:
- checkout: self
submodules: true
persistCredentials: true
fetchDepth: 1
fetchDepth: 1
- bash: ./docs.sh Windows
displayName: Create openapi.json
- bash: |
@@ -981,10 +1145,9 @@ stages:
git config --global user.name "Servarr"
git checkout -b api-docs
git add .
git status
if git status | grep modified
if git status | grep -q modified
then
git commit -am 'Automated API Docs update [skip ci]'
git commit -am 'Automated API Docs update'
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
@@ -1008,33 +1171,25 @@ stages:
- job: Analyze_Backend
displayName: Backend
dependsOn: Prepare
condition: and(succeeded(), eq(dependencies.Prepare.outputs['setVar.backendNotUpdated'], '0'))
variables:
disable.coverage.autogenerate: 'true'
EnableAnalyzers: 'false'
pool:
vmImage: ${{ variables.linuxImage }}
vmImage: ${{ variables.windowsImage }}
steps:
- task: UseDotNet@2
displayName: 'Install .net core 2.1'
inputs:
version: 2.1.815
- task: UseDotNet@2
displayName: 'Install .net core 3.1'
inputs:
version: 3.1.413
- task: UseDotNet@2
displayName: 'Install .net core 5.0'
displayName: 'Install .net core'
inputs:
version: $(dotnetVersion)
- checkout: self # Need history for Sonar analysis
submodules: true
- task: Cache@2
inputs:
key: 'nuget | "$(Agent.OS)" | $(Build.SourcesDirectory)/src/Directory.Packages.props'
path: $(nugetCacheFolder)
displayName: Cache NuGet packages
- powershell: Set-Service SCardSvr -StartupType Manual
displayName: Enable Windows Test Service
- task: SonarCloudPrepare@1
condition: eq(variables['System.PullRequest.IsFork'], 'False')
inputs:
@@ -1045,16 +1200,14 @@ stages:
projectName: 'Readarr'
projectVersion: '$(readarrVersion)'
extraProperties: |
sonar.exclusions=**/obj/**,**/*.dll,**/NzbDrone.Core.Test/Files/**/*,./frontend/**,./src/Libraries/**
sonar.exclusions=**/obj/**,**/*.dll,**/NzbDrone.Core.Test/Files/**/*,./frontend/**,**/ExternalModules/**,./src/Libraries/**
sonar.coverage.exclusions=**/Readarr.Api.V1/**/*
sonar.cs.opencover.reportsPaths=$(Build.SourcesDirectory)/CoverageResults/**/coverage.opencover.xml
sonar.cs.nunit.reportsPaths=$(Build.SourcesDirectory)/TestResult.xml
- bash: |
./build.sh --backend -f net6.0 -r linux-x64
TEST_DIR=_tests/net6.0/linux-x64/publish/ ./test.sh Linux Unit Coverage
./build.sh --backend -f net6.0 -r win-x64
TEST_DIR=_tests/net6.0/win-x64/publish/ ./test.sh Windows Unit Coverage
displayName: Coverage Unit Tests
env:
NUGET_PACKAGES: $(nugetCacheFolder)
- task: SonarCloudAnalyze@1
condition: eq(variables['System.PullRequest.IsFork'], 'False')
displayName: Publish SonarCloud Results
@@ -1077,7 +1230,6 @@ stages:
- Unit_Test
- Integration
- Automation
- Build_Backend_Other
condition: eq(variables['system.pullrequest.isfork'], false)
displayName: Build Status Report
jobs:
@@ -1101,3 +1253,4 @@ stages:
DISCORDCHANNELID: $(discordChannelId)
DISCORDWEBHOOKKEY: $(discordWebhookKey)
DISCORDTHREADID: $(discordThreadId)

View File

@@ -23,7 +23,7 @@ UpdateVersionNumber()
echo "Updating Version Info"
sed -i'' -e "s/<AssemblyVersion>[0-9.*]\+<\/AssemblyVersion>/<AssemblyVersion>$READARRVERSION<\/AssemblyVersion>/g" src/Directory.Build.props
sed -i'' -e "s/<AssemblyConfiguration>[\$()A-Za-z-]\+<\/AssemblyConfiguration>/<AssemblyConfiguration>${BUILD_SOURCEBRANCHNAME}<\/AssemblyConfiguration>/g" src/Directory.Build.props
sed -i'' -e "s/<string>10.0.0.0<\/string>/<string>$READARRVERSION<\/string>/g" macOS/Readarr.app/Contents/Info.plist
sed -i'' -e "s/<string>10.0.0.0<\/string>/<string>$READARRVERSION<\/string>/g" distribution/osx/Readarr.app/Contents/Info.plist
fi
}
@@ -183,7 +183,7 @@ PackageMacOSApp()
rm -rf $folder
mkdir -p $folder
cp -r macOS/Readarr.app $folder
cp -r distribution/osx/Readarr.app $folder
mkdir -p $folder/Readarr.app/Contents/MacOS
echo "Copying Binaries"
@@ -245,7 +245,7 @@ BuildInstaller()
local framework="$1"
local runtime="$2"
./_inno/ISCC.exe setup/readarr.iss "//DFramework=$framework" "//DRuntime=$runtime"
./_inno/ISCC.exe distribution/windows/setup/readarr.iss "//DFramework=$framework" "//DRuntime=$runtime"
}
InstallInno()
@@ -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

@@ -44,16 +44,16 @@ Name: "english"; MessagesFile: "compiler:Default.isl"
[Tasks]
Name: "desktopIcon"; Description: "{cm:CreateDesktopIcon}"
Name: "windowsService"; Description: "Install Windows Service (Starts when the computer starts as the LocalService user, you will need to change the user to access network shares)"; GroupDescription: "Start automatically"; Flags: exclusive unchecked
Name: "startupShortcut"; Description: "Create shortcut in Startup folder (Starts when you log into Windows)"; GroupDescription: "Start automatically"; Flags: exclusive
Name: "windowsService"; Description: "Install Windows Service (Starts when the computer starts as the LocalService user, you will need to change the user to access network shares)"; GroupDescription: "Start automatically"; Flags: exclusive
Name: "startupShortcut"; Description: "Create shortcut in Startup folder (Starts when you log into Windows)"; GroupDescription: "Start automatically"; Flags: exclusive unchecked
Name: "none"; Description: "Do not start automatically"; GroupDescription: "Start automatically"; Flags: exclusive unchecked
[Dirs]
Name: "{app}"; Permissions: users-modify
[Files]
Source: "..\_artifacts\{#Runtime}\{#Framework}\Readarr\Readarr.exe"; DestDir: "{app}\bin"; Flags: ignoreversion
Source: "..\_artifacts\{#Runtime}\{#Framework}\Readarr\*"; Excludes: "Readarr.Update"; DestDir: "{app}\bin"; Flags: ignoreversion recursesubdirs createallsubdirs
Source: "..\..\..\_artifacts\{#Runtime}\{#Framework}\Readarr\Readarr.exe"; DestDir: "{app}\bin"; Flags: ignoreversion
Source: "..\..\..\_artifacts\{#Runtime}\{#Framework}\Readarr\*"; Excludes: "Readarr.Update"; DestDir: "{app}\bin"; Flags: ignoreversion recursesubdirs createallsubdirs
; NOTE: Don't use "Flags: ignoreversion" on any shared system files
[Icons]
@@ -72,12 +72,13 @@ Filename: "{app}\bin\Readarr.exe"; Description: "Open Readarr Web UI"; Flags: po
Filename: "{app}\bin\Readarr.exe"; Description: "Start Readarr"; Flags: postinstall skipifsilent nowait; Tasks: startupShortcut none;
[UninstallRun]
Filename: "{app}\bin\Readarr.Console.exe"; Parameters: "/u"; Flags: waituntilterminated skipifdoesntexist
Filename: "{app}\bin\readarr.console.exe"; Parameters: "/u"; Flags: waituntilterminated skipifdoesntexist
[Code]
function PrepareToInstall(var NeedsRestart: Boolean): String;
var
ResultCode: Integer;
begin
Exec(ExpandConstant('{commonappdata}\Readarr\bin\Readarr.Console.exe'), '/u', '', 0, ewWaitUntilTerminated, ResultCode)
Exec('net', 'stop readarr', '', 0, ewWaitUntilTerminated, ResultCode)
Exec('sc', 'delete readarr', '', 0, ewWaitUntilTerminated, ResultCode)
end;

View File

@@ -4,14 +4,14 @@ module.exports = {
plugins: [
// Stage 1
'@babel/plugin-proposal-export-default-from',
['@babel/plugin-proposal-optional-chaining', { loose }],
['@babel/plugin-proposal-nullish-coalescing-operator', { loose }],
['@babel/plugin-transform-optional-chaining', { loose }],
['@babel/plugin-transform-nullish-coalescing-operator', { loose }],
// Stage 2
'@babel/plugin-proposal-export-namespace-from',
'@babel/plugin-transform-export-namespace-from',
// Stage 3
['@babel/plugin-proposal-class-properties', { loose }],
['@babel/plugin-transform-class-properties', { loose }],
'@babel/plugin-syntax-dynamic-import'
],
env: {

View File

@@ -36,7 +36,7 @@ module.exports = (env) => {
},
entry: {
index: 'index.js'
index: 'index.ts'
},
resolve: {
@@ -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,13 +91,15 @@ module.exports = (env) => {
}),
new MiniCssExtractPlugin({
filename: 'Content/styles.css'
filename: 'Content/styles.css',
chunkFilename: 'Content/[id]-[chunkhash].css'
}),
new HtmlWebpackPlugin({
template: 'frontend/src/index.ejs',
filename: 'index.html',
publicPath: '/'
publicPath: '/',
inject: false
}),
new FileManagerPlugin({

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

@@ -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

@@ -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
@@ -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>
}

View File

@@ -1,6 +1,7 @@
.version {
margin: 0 3px;
font-weight: bold;
font-family: var(--defaultFontFamily);
}
.maintenance {

View File

@@ -2,6 +2,7 @@ import PropTypes from 'prop-types';
import React from 'react';
import Button from 'Components/Link/Button';
import LoadingIndicator from 'Components/Loading/LoadingIndicator';
import InlineMarkdown from 'Components/Markdown/InlineMarkdown';
import ModalBody from 'Components/Modal/ModalBody';
import ModalContent from 'Components/Modal/ModalContent';
import ModalFooter from 'Components/Modal/ModalFooter';
@@ -64,12 +65,12 @@ function AppUpdatedModalContent(props) {
return (
<ModalContent onModalClose={onModalClose}>
<ModalHeader>
Readarr Updated
{translate('AppUpdated', { appName: 'Readarr' })}
</ModalHeader>
<ModalBody>
<div>
Version <span className={styles.version}>{version}</span> of Readarr has been installed, in order to get the latest changes you'll need to reload Readarr.
<InlineMarkdown data={translate('AppUpdatedVersion', { appName: 'Readarr', version })} blockClassName={styles.version} />
</div>
{
@@ -77,16 +78,14 @@ function AppUpdatedModalContent(props) {
<div>
{
!update.changes &&
<div className={styles.maintenance}>
{translate('MaintenanceRelease')}
</div>
<div className={styles.maintenance}>{translate('MaintenanceRelease')}</div>
}
{
!!update.changes &&
<div>
<div className={styles.changes}>
What's new?
{translate('WhatsNew')}
</div>
<UpdateChanges
@@ -113,14 +112,14 @@ function AppUpdatedModalContent(props) {
<Button
onPress={onSeeChangesPress}
>
Recent Changes
{translate('RecentChanges')}
</Button>
<Button
kind={kinds.PRIMARY}
onPress={onModalClose}
>
Reload
{translate('Reload')}
</Button>
</ModalFooter>
</ModalContent>

View File

@@ -7,6 +7,7 @@ import ModalContent from 'Components/Modal/ModalContent';
import ModalFooter from 'Components/Modal/ModalFooter';
import ModalHeader from 'Components/Modal/ModalHeader';
import { kinds } from 'Helpers/Props';
import translate from 'Utilities/String/translate';
import styles from './ConnectionLostModal.css';
function ConnectionLostModal(props) {
@@ -22,16 +23,16 @@ function ConnectionLostModal(props) {
>
<ModalContent onModalClose={onModalClose}>
<ModalHeader>
Connection Lost
{translate('ConnectionLost')}
</ModalHeader>
<ModalBody>
<div>
Readarr has lost its connection to the backend and will need to be reloaded to restore functionality.
{translate('ConnectionLostToBackend', { appName: 'Readarr' })}
</div>
<div className={styles.automatic}>
Readarr will try to connect automatically, or you can click reload below.
{translate('ConnectionLostReconnect', { appName: 'Readarr' })}
</div>
</ModalBody>
<ModalFooter>
@@ -39,7 +40,7 @@ function ConnectionLostModal(props) {
kind={kinds.PRIMARY}
onPress={onModalClose}
>
Reload
{translate('Reload')}
</Button>
</ModalFooter>
</ModalContent>

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

@@ -7,13 +7,10 @@ function findImage(images, coverType) {
}
function getUrl(image, coverType, size) {
if (image) {
// Remove protocol
let url = image.url;
const imageUrl = image?.url;
url = url.replace(`${coverType}.jpg`, `${coverType}-${size}.jpg`);
return url;
if (imageUrl) {
return imageUrl.replace(`${coverType}.jpg`, `${coverType}-${size}.jpg`);
}
}

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

@@ -25,12 +25,7 @@ const defaultFontSize = parseInt(fonts.defaultFontSize);
const lineHeight = parseFloat(fonts.lineHeight);
function getFanartUrl(images) {
const fanartImage = images.find((x) => x.coverType === 'fanart');
if (fanartImage) {
// Remove protocol
return fanartImage.url.replace(/^https?:/, '');
}
return images.find((x) => x.coverType === 'fanart')?.url;
}
class AuthorDetailsHeader extends Component {
@@ -92,6 +87,7 @@ class AuthorDetailsHeader extends Component {
titleWidth
} = this.state;
const fanartUrl = getFanartUrl(images);
const marqueeWidth = titleWidth - (isSmallScreen ? 85 : 160);
const continuing = status === 'continuing';
@@ -108,9 +104,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

@@ -16,7 +16,7 @@ import AuthorIndex from './AuthorIndex';
function createMapStateToProps() {
return createSelector(
createAuthorClientSideCollectionItemsSelector('authorIndex'),
createCommandExecutingSelector(commandNames.REFRESH_AUTHOR),
createCommandExecutingSelector(commandNames.BULK_REFRESH_AUTHOR),
createCommandExecutingSelector(commandNames.RSS_SYNC),
createCommandExecutingSelector(commandNames.RENAME_AUTHOR),
createCommandExecutingSelector(commandNames.RETAG_AUTHOR),
@@ -24,17 +24,17 @@ function createMapStateToProps() {
(
author,
isRefreshingAuthor,
isRssSyncExecuting,
isOrganizingAuthor,
isRetaggingAuthor,
isRssSyncExecuting,
dimensionsState
) => {
return {
...author,
isRefreshingAuthor,
isRssSyncExecuting,
isOrganizingAuthor,
isRetaggingAuthor,
isRssSyncExecuting,
isSmallScreen: dimensionsState.isSmallScreen
};
}

View File

@@ -14,14 +14,39 @@ import { inputTypes } from 'Helpers/Props';
import translate from 'Utilities/String/translate';
const nameOptions = [
{ key: 'firstLast', value: translate('NameFirstLast') },
{ key: 'lastFirst', value: translate('NameLastFirst') }
{
key: 'firstLast',
get value() {
return translate('NameFirstLast');
}
},
{
key: 'lastFirst',
get value() {
return translate('NameLastFirst');
}
}
];
const posterSizeOptions = [
{ key: 'small', value: 'Small' },
{ key: 'medium', value: 'Medium' },
{ key: 'large', value: 'Large' }
{
key: 'small',
get value() {
return translate('Small');
}
},
{
key: 'medium',
get value() {
return translate('Medium');
}
},
{
key: 'large',
get value() {
return translate('Large');
}
}
];
class AuthorIndexOverviewOptionsModalContent extends Component {

View File

@@ -14,15 +14,45 @@ import { inputTypes } from 'Helpers/Props';
import translate from 'Utilities/String/translate';
const posterSizeOptions = [
{ key: 'small', value: 'Small' },
{ key: 'medium', value: 'Medium' },
{ key: 'large', value: 'Large' }
{
key: 'small',
get value() {
return translate('Small');
}
},
{
key: 'medium',
get value() {
return translate('Medium');
}
},
{
key: 'large',
get value() {
return translate('Large');
}
}
];
const nameOptions = [
{ key: 'no', value: translate('NoName') },
{ key: 'firstLast', value: translate('NameFirstLast') },
{ key: 'lastFirst', value: translate('NameLastFirst') }
{
key: 'no',
get value() {
return translate('NoName');
}
},
{
key: 'firstLast',
get value() {
return translate('NameFirstLast');
}
},
{
key: 'lastFirst',
get value() {
return translate('NameLastFirst');
}
}
];
class AuthorIndexPosterOptionsModalContent extends Component {

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

@@ -7,8 +7,18 @@ import { inputTypes } from 'Helpers/Props';
import translate from 'Utilities/String/translate';
const nameOptions = [
{ key: 'firstLast', value: translate('NameFirstLast') },
{ key: 'lastFirst', value: translate('NameLastFirst') }
{
key: 'firstLast',
get value() {
return translate('NameFirstLast');
}
},
{
key: 'lastFirst',
get value() {
return translate('NameLastFirst');
}
}
];
class AuthorIndexTableOptions extends Component {

View File

@@ -6,4 +6,5 @@
.statusIcon {
width: 20px !important;
text-align: center;
}

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

@@ -21,12 +21,7 @@ const defaultFontSize = parseInt(fonts.defaultFontSize);
const lineHeight = parseFloat(fonts.lineHeight);
function getFanartUrl(images) {
const fanartImage = images.find((x) => x.coverType === 'fanart');
if (fanartImage) {
// Remove protocol
return fanartImage.url.replace(/^https?:/, '');
}
return images.find((x) => x.coverType === 'fanart')?.url;
}
class BookDetailsHeader extends Component {
@@ -83,15 +78,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

@@ -16,8 +16,8 @@ import BookIndex from './BookIndex';
function createMapStateToProps() {
return createSelector(
createBookClientSideCollectionItemsSelector('bookIndex'),
createCommandExecutingSelector(commandNames.REFRESH_AUTHOR),
createCommandExecutingSelector(commandNames.REFRESH_BOOK),
createCommandExecutingSelector(commandNames.BULK_REFRESH_AUTHOR),
createCommandExecutingSelector(commandNames.BULK_REFRESH_BOOK),
createCommandExecutingSelector(commandNames.RSS_SYNC),
createCommandExecutingSelector(commandNames.CUTOFF_UNMET_BOOK_SEARCH),
createCommandExecutingSelector(commandNames.MISSING_BOOK_SEARCH),

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 &&
@@ -143,7 +143,7 @@ class BookshelfFooter extends Component {
<div>
<div className={styles.label}>
{selectedCount} Author(s) Selected
{translate('CountAuthorsSelected', { selectedCount })}
</div>
<SpinnerButton

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

@@ -25,6 +25,10 @@
white-space: pre-wrap;
}
.version {
margin-top: 20px;
}
@media only screen and (max-width: $breakpointMedium) {
.image {
height: 250px;

View File

@@ -6,6 +6,7 @@ interface CssExports {
'image': string;
'imageContainer': string;
'message': string;
'version': string;
}
export const cssExports: CssExports;
export default cssExports;

View File

@@ -1,60 +0,0 @@
import PropTypes from 'prop-types';
import React from 'react';
import styles from './ErrorBoundaryError.css';
function ErrorBoundaryError(props) {
const {
className,
messageClassName,
detailsClassName,
message,
error,
info
} = props;
return (
<div className={className}>
<div className={messageClassName}>
{message}
</div>
<div className={styles.imageContainer}>
<img
className={styles.image}
src={`${window.Readarr.urlBase}/Content/Images/error.png`}
/>
</div>
<details className={detailsClassName}>
{
error &&
<div>
{error.toString()}
</div>
}
<div className={styles.info}>
{info.componentStack}
</div>
</details>
</div>
);
}
ErrorBoundaryError.propTypes = {
className: PropTypes.string.isRequired,
messageClassName: PropTypes.string.isRequired,
detailsClassName: PropTypes.string.isRequired,
message: PropTypes.string.isRequired,
error: PropTypes.object.isRequired,
info: PropTypes.object.isRequired
};
ErrorBoundaryError.defaultProps = {
className: styles.container,
messageClassName: styles.message,
detailsClassName: styles.details,
message: 'There was an error loading this content'
};
export default ErrorBoundaryError;

View File

@@ -0,0 +1,77 @@
import React, { useEffect, useState } from 'react';
import StackTrace from 'stacktrace-js';
import translate from 'Utilities/String/translate';
import styles from './ErrorBoundaryError.css';
interface ErrorBoundaryErrorProps {
className: string;
messageClassName: string;
detailsClassName: string;
message: string;
error: Error;
info: {
componentStack: string;
};
}
function ErrorBoundaryError(props: ErrorBoundaryErrorProps) {
const {
className = styles.container,
messageClassName = styles.message,
detailsClassName = styles.details,
message = translate('ErrorLoadingContent'),
error,
info,
} = props;
const [detailedError, setDetailedError] = useState<
StackTrace.StackFrame[] | null
>(null);
useEffect(() => {
if (error) {
StackTrace.fromError(error).then((de) => {
setDetailedError(de);
});
} else {
setDetailedError(null);
}
}, [error, setDetailedError]);
return (
<div className={className}>
<div className={messageClassName}>{message}</div>
<div className={styles.imageContainer}>
<img
className={styles.image}
src={`${window.Readarr.urlBase}/Content/Images/error.png`}
/>
</div>
<details className={detailsClassName}>
{error ? <div>{error.message}</div> : null}
{detailedError ? (
detailedError.map((d, index) => {
return (
<div key={index}>
{` at ${d.functionName} (${d.fileName}:${d.lineNumber}:${d.columnNumber})`}
</div>
);
})
) : (
<div>{info.componentStack}</div>
)}
{
<div className={styles.version}>
Version: {window.Readarr.version}
</div>
}
</details>
</div>
);
}
export default ErrorBoundaryError;

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

@@ -206,11 +206,13 @@ class FilterBuilderRow extends Component {
const selectedFilterBuilderProp = this.selectedFilterBuilderProp;
const keyOptions = filterBuilderProps.map((availablePropFilter) => {
const { name, label } = availablePropFilter;
return {
key: availablePropFilter.name,
value: availablePropFilter.label
key: name,
value: typeof label === 'function' ? label() : 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

@@ -9,6 +9,10 @@
&:hover {
background-color: var(--inputHoverBackgroundColor);
}
&.isDisabled {
cursor: not-allowed;
}
}
.optionCheck {

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);
@@ -41,7 +41,7 @@ class NumberInput extends Component {
componentDidUpdate(prevProps, prevState) {
const { value } = this.props;
if (value !== prevProps.value && !this.state.isFocused) {
if (!isNaN(value) && value !== prevProps.value && !this.state.isFocused) {
this.setState({
value: value == null ? '' : value.toString()
});

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':

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

@@ -61,7 +61,7 @@ class SelectInput extends Component {
value={key}
{...otherOptionProps}
>
{optionValue}
{typeof optionValue === 'function' ? optionValue() : optionValue}
</option>
);
})
@@ -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, PropTypes.func]).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

@@ -41,7 +41,7 @@ class Icon extends PureComponent {
return (
<span
className={containerClassName}
title={title}
title={typeof title === 'function' ? title() : title}
>
{icon}
</span>
@@ -58,7 +58,7 @@ Icon.propTypes = {
name: PropTypes.object.isRequired,
kind: PropTypes.string.isRequired,
size: PropTypes.number.isRequired,
title: PropTypes.string,
title: PropTypes.oneOfType([PropTypes.string, PropTypes.func]),
isSpinning: PropTypes.bool.isRequired,
fixedWidth: PropTypes.bool.isRequired
};

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

@@ -97,6 +97,7 @@ class SpinnerErrorButton extends Component {
render() {
const {
kind,
isSpinning,
error,
children,
@@ -112,7 +113,7 @@ class SpinnerErrorButton extends Component {
const showIcon = wasSuccessful || hasWarning || hasError;
let iconName = icons.CHECK;
let iconKind = kinds.SUCCESS;
let iconKind = kind === kinds.PRIMARY ? kinds.DEFAULT : kinds.SUCCESS;
if (hasWarning) {
iconName = icons.WARNING;
@@ -126,6 +127,7 @@ class SpinnerErrorButton extends Component {
return (
<SpinnerButton
kind={kind}
isSpinning={isSpinning}
{...otherProps}
>
@@ -154,6 +156,7 @@ class SpinnerErrorButton extends Component {
}
SpinnerErrorButton.propTypes = {
kind: PropTypes.oneOf(kinds.all),
isSpinning: PropTypes.bool.isRequired,
error: PropTypes.object,
children: PropTypes.node.isRequired

View File

@@ -10,27 +10,55 @@ class InlineMarkdown extends Component {
render() {
const {
className,
data
data,
blockClassName
} = 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}`} className={blockClassName ?? null}>{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>;
@@ -39,7 +67,8 @@ class InlineMarkdown extends Component {
InlineMarkdown.propTypes = {
className: PropTypes.string,
data: PropTypes.string
data: PropTypes.string,
blockClassName: PropTypes.string
};
export default InlineMarkdown;

View File

@@ -32,7 +32,7 @@ class FilterMenuContent extends Component {
selectedFilterKey={selectedFilterKey}
onPress={onFilterSelect}
>
{filter.label}
{typeof filter.label === 'function' ? filter.label() : filter.label}
</FilterMenuItem>
);
})

View File

@@ -7,6 +7,7 @@ function ErrorPage(props) {
const {
version,
isLocalStorageSupported,
translationsError,
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 (translationsError) {
errorMessage = getErrorMessage(translationsError, '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,
translationsError: PropTypes.object,
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

@@ -3,7 +3,7 @@ import React, { Component } from 'react';
import { connect } from 'react-redux';
import { withRouter } from 'react-router-dom';
import { createSelector } from 'reselect';
import { saveDimensions, setIsSidebarVisible } from 'Store/Actions/appActions';
import { fetchTranslations, saveDimensions, setIsSidebarVisible } from 'Store/Actions/appActions';
import { fetchAuthor } from 'Store/Actions/authorActions';
import { fetchBooks } from 'Store/Actions/bookActions';
import { fetchCustomFilters } from 'Store/Actions/customFilterActions';
@@ -52,6 +52,7 @@ const selectIsPopulated = createSelector(
(state) => state.settings.metadataProfiles.isPopulated,
(state) => state.settings.importLists.isPopulated,
(state) => state.system.status.isPopulated,
(state) => state.app.translations.isPopulated,
(
customFiltersIsPopulated,
tagsIsPopulated,
@@ -60,7 +61,8 @@ const selectIsPopulated = createSelector(
qualityProfilesIsPopulated,
metadataProfilesIsPopulated,
importListsIsPopulated,
systemStatusIsPopulated
systemStatusIsPopulated,
translationsIsPopulated
) => {
return (
customFiltersIsPopulated &&
@@ -70,7 +72,8 @@ const selectIsPopulated = createSelector(
qualityProfilesIsPopulated &&
metadataProfilesIsPopulated &&
importListsIsPopulated &&
systemStatusIsPopulated
systemStatusIsPopulated &&
translationsIsPopulated
);
}
);
@@ -84,6 +87,7 @@ const selectErrors = createSelector(
(state) => state.settings.metadataProfiles.error,
(state) => state.settings.importLists.error,
(state) => state.system.status.error,
(state) => state.app.translations.error,
(
customFiltersError,
tagsError,
@@ -92,7 +96,8 @@ const selectErrors = createSelector(
qualityProfilesError,
metadataProfilesError,
importListsError,
systemStatusError
systemStatusError,
translationsError
) => {
const hasError = !!(
customFiltersError ||
@@ -102,7 +107,8 @@ const selectErrors = createSelector(
qualityProfilesError ||
metadataProfilesError ||
importListsError ||
systemStatusError
systemStatusError ||
translationsError
);
return {
@@ -114,7 +120,8 @@ const selectErrors = createSelector(
qualityProfilesError,
metadataProfilesError,
importListsError,
systemStatusError
systemStatusError,
translationsError
};
}
);
@@ -176,6 +183,9 @@ function createMapDispatchToProps(dispatch, props) {
dispatchFetchStatus() {
dispatch(fetchStatus());
},
dispatchFetchTranslations() {
dispatch(fetchTranslations());
},
onResize(dimensions) {
dispatch(saveDimensions(dimensions));
},
@@ -210,6 +220,7 @@ class PageConnector extends Component {
this.props.dispatchFetchImportLists();
this.props.dispatchFetchUISettings();
this.props.dispatchFetchStatus();
this.props.dispatchFetchTranslations();
}
}
@@ -236,6 +247,7 @@ class PageConnector extends Component {
dispatchFetchImportLists,
dispatchFetchUISettings,
dispatchFetchStatus,
dispatchFetchTranslations,
...otherProps
} = this.props;
@@ -277,6 +289,7 @@ PageConnector.propTypes = {
dispatchFetchImportLists: PropTypes.func.isRequired,
dispatchFetchUISettings: PropTypes.func.isRequired,
dispatchFetchStatus: PropTypes.func.isRequired,
dispatchFetchTranslations: PropTypes.func.isRequired,
onSidebarVisibleChange: PropTypes.func.isRequired
};

View File

@@ -21,28 +21,28 @@ const SIDEBAR_WIDTH = parseInt(dimensions.sidebarWidth);
const links = [
{
iconName: icons.AUTHOR_CONTINUING,
title: 'Library',
title: () => translate('Library'),
to: '/',
alias: '/authors',
children: [
{
title: 'Authors',
title: () => translate('Authors'),
to: '/authors'
},
{
title: 'Books',
title: () => translate('Books'),
to: '/books'
},
{
title: 'Add New',
title: () => translate('AddNew'),
to: '/add/search'
},
{
title: 'Bookshelf',
title: () => translate('Bookshelf'),
to: '/shelf'
},
{
title: 'Unmapped Files',
title: () => translate('UnmappedFiles'),
to: '/unmapped'
}
]
@@ -50,26 +50,26 @@ const links = [
{
iconName: icons.CALENDAR,
title: 'Calendar',
title: () => translate('Calendar'),
to: '/calendar'
},
{
iconName: icons.ACTIVITY,
title: 'Activity',
title: () => translate('Activity'),
to: '/activity/queue',
children: [
{
title: 'Queue',
title: () => translate('Queue'),
to: '/activity/queue',
statusComponent: QueueStatusConnector
},
{
title: 'History',
title: () => translate('History'),
to: '/activity/history'
},
{
title: 'Blocklist',
title: () => translate('Blocklist'),
to: '/activity/blocklist'
}
]
@@ -77,15 +77,15 @@ const links = [
{
iconName: icons.WARNING,
title: 'Wanted',
title: () => translate('Wanted'),
to: '/wanted/missing',
children: [
{
title: 'Missing',
title: () => translate('Missing'),
to: '/wanted/missing'
},
{
title: 'Cutoff Unmet',
title: () => translate('CutoffUnmet'),
to: '/wanted/cutoffunmet'
}
]
@@ -93,55 +93,55 @@ const links = [
{
iconName: icons.SETTINGS,
title: 'Settings',
title: () => translate('Settings'),
to: '/settings',
children: [
{
title: 'Media Management',
title: () => translate('MediaManagement'),
to: '/settings/mediamanagement'
},
{
title: 'Profiles',
title: () => translate('Profiles'),
to: '/settings/profiles'
},
{
title: 'Quality',
title: () => translate('Quality'),
to: '/settings/quality'
},
{
title: translate('CustomFormats'),
title: () => translate('CustomFormats'),
to: '/settings/customformats'
},
{
title: translate('Indexers'),
title: () => translate('Indexers'),
to: '/settings/indexers'
},
{
title: 'Download Clients',
title: () => translate('DownloadClients'),
to: '/settings/downloadclients'
},
{
title: 'Import Lists',
title: () => translate('ImportLists'),
to: '/settings/importlists'
},
{
title: 'Connect',
title: () => translate('Connect'),
to: '/settings/connect'
},
{
title: 'Metadata',
title: () => translate('Metadata'),
to: '/settings/metadata'
},
{
title: 'Tags',
title: () => translate('Tags'),
to: '/settings/tags'
},
{
title: 'General',
title: () => translate('General'),
to: '/settings/general'
},
{
title: 'UI',
title: () => translate('Ui'),
to: '/settings/ui'
}
]
@@ -149,32 +149,32 @@ const links = [
{
iconName: icons.SYSTEM,
title: 'System',
title: () => translate('System'),
to: '/system/status',
children: [
{
title: 'Status',
title: () => translate('Status'),
to: '/system/status',
statusComponent: HealthStatusConnector
},
{
title: 'Tasks',
title: () => translate('Tasks'),
to: '/system/tasks'
},
{
title: 'Backup',
title: () => translate('Backup'),
to: '/system/backup'
},
{
title: 'Updates',
title: () => translate('Updates'),
to: '/system/updates'
},
{
title: 'Events',
title: () => translate('Events'),
to: '/system/events'
},
{
title: 'Log Files',
title: () => translate('LogFiles'),
to: '/system/logs/files'
}
]

View File

@@ -64,7 +64,7 @@ class PageSidebarItem extends Component {
}
<span className={isChildItem ? styles.noIcon : null}>
{title}
{typeof title === 'function' ? title() : title}
</span>
{
@@ -88,7 +88,7 @@ class PageSidebarItem extends Component {
PageSidebarItem.propTypes = {
iconName: PropTypes.object,
title: PropTypes.string.isRequired,
title: PropTypes.oneOfType([PropTypes.string, PropTypes.func]).isRequired,
to: PropTypes.string.isRequired,
isActive: PropTypes.bool,
isActiveParent: PropTypes.bool,

View File

@@ -0,0 +1,14 @@
import React from 'react';
type PropertyFunction<T> = () => T;
interface Column {
name: string;
label: string | PropertyFunction<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>
@@ -106,7 +107,7 @@ function Table(props) {
{...getTableHeaderCellProps(otherProps)}
{...column}
>
{column.label}
{typeof column.label === 'function' ? column.label() : column.label}
</TableHeaderCell>
);
})
@@ -120,6 +121,7 @@ function Table(props) {
}
Table.propTypes = {
...TableHeaderCell.props,
className: PropTypes.string,
horizontalScroll: PropTypes.bool.isRequired,
selectAll: PropTypes.bool.isRequired,

View File

@@ -30,6 +30,7 @@ class TableHeaderCell extends Component {
const {
className,
name,
label,
columnLabel,
isSortable,
isVisible,
@@ -53,7 +54,8 @@ class TableHeaderCell extends Component {
{...otherProps}
component="th"
className={className}
title={columnLabel}
label={typeof label === 'function' ? label() : label}
title={typeof columnLabel === 'function' ? columnLabel() : columnLabel}
onPress={this.onPress}
>
{children}
@@ -77,7 +79,8 @@ class TableHeaderCell extends Component {
TableHeaderCell.propTypes = {
className: PropTypes.string,
name: PropTypes.string.isRequired,
columnLabel: PropTypes.string,
label: PropTypes.oneOfType([PropTypes.string, PropTypes.func, PropTypes.node]),
columnLabel: PropTypes.oneOfType([PropTypes.string, PropTypes.func]),
isSortable: PropTypes.bool,
isVisible: PropTypes.bool,
isModifiable: PropTypes.bool,

View File

@@ -35,7 +35,7 @@ function TableOptionsColumn(props) {
isDisabled={isModifiable === false}
onChange={onVisibleChange}
/>
{label}
{typeof label === 'function' ? label() : label}
</label>
{
@@ -56,7 +56,7 @@ function TableOptionsColumn(props) {
TableOptionsColumn.propTypes = {
name: PropTypes.string.isRequired,
label: PropTypes.string.isRequired,
label: PropTypes.oneOfType([PropTypes.string, PropTypes.func]).isRequired,
isVisible: PropTypes.bool.isRequired,
isModifiable: PropTypes.bool.isRequired,
index: PropTypes.number.isRequired,

View File

@@ -112,7 +112,7 @@ class TableOptionsColumnDragSource extends Component {
<TableOptionsColumn
name={name}
label={label}
label={typeof label === 'function' ? label() : label}
isVisible={isVisible}
isModifiable={isModifiable}
index={index}
@@ -138,7 +138,7 @@ class TableOptionsColumnDragSource extends Component {
TableOptionsColumnDragSource.propTypes = {
name: PropTypes.string.isRequired,
label: PropTypes.string.isRequired,
label: PropTypes.oneOfType([PropTypes.string, PropTypes.func]).isRequired,
isVisible: PropTypes.bool.isRequired,
isModifiable: PropTypes.bool.isRequired,
index: PropTypes.number.isRequired,

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