Compare commits

..

540 Commits

Author SHA1 Message Date
Mark McDowall
53e7718fcf Fixed: Release profiles not saving if Must (Not) Contain is empty
(cherry picked from commit 0abd52d6beba82f9c9af0d6469aa1a7157128537)
2021-10-05 16:25:49 +00:00
Qstick
d14a64caf9 Bump System.Data.Sqlite to 1.0.115.0-0 2021-10-03 19:23:19 -05:00
Mark McDowall
af2e743994 New: Log which DB is being migrated
(cherry picked from commit 07c95f06d3b9b32aaeb923d4c678f10a2bf1141a)
2021-10-03 10:22:46 -04:00
Kevin Lau
398ab902dc New: Change Today color in calendar for better visibility
(cherry picked from commit d9e9b72a894a238e58d40cc0a6aa83c416db1a44)
2021-10-02 11:06:53 -04:00
Qstick
007edcba9e Weblate Updates 2021-10-02 10:02:22 -05:00
Qstick
26b0034bbb Create swagger.json 2021-09-26 19:42:27 -05:00
ta264
e842137385 Fix backend analyze 2021-09-20 19:33:43 +01:00
Thomas White
294346db35 Fixed: Null reference error when import list adds new book to existing author
Fixes #1235

(cherry picked from commit 5e3045db86748d0ab407a6785007cb6bbd4f46a6)
2021-09-20 17:43:47 +01:00
Peter Sarossy
859dd091f3 Fix README typo (#1237) [skip CI] 2021-09-18 20:22:06 -05:00
ta264
72ae466892 Fixed: Calculate author statistics correctly
Fixes #1213
2021-09-02 21:12:14 +01:00
ta264
12c67891fb Fixed: Better book status column in author details 2021-09-02 21:12:14 +01:00
ta264
ffc97d8489 Fixed: Use track title tag if album tag not set 2021-09-02 21:12:14 +01:00
ta264
bdcee8c7c1 New: Try matching with "Unabridged" removed from title 2021-09-02 21:12:14 +01:00
ta264
6325432a34 Fixed: Filter by page count after filtering editions 2021-09-02 21:12:14 +01:00
ta264
1e82c7a67a New: Add book, series and files counts to author details page 2021-09-02 21:12:14 +01:00
ta264
8b19f02ade Fixed: Don't repeatedly refresh queue when author deleted 2021-09-02 21:12:14 +01:00
ta264
3c4c6b855e Fix add new book prop validation 2021-09-02 21:12:14 +01:00
ta264
9d78e5bfd8 Fixed: Bookshelf not updating after adjusting monitored options 2021-09-02 21:12:13 +01:00
Taloth Saldono
0e5f45a457 Send signalr message for episode monitored flag changes
(cherry picked from commit 9e81d41f262fb1f9d798374673b0a0427bf1a6e3)

Closes #1049
2021-09-02 21:12:13 +01:00
Robin Dadswell
2317665f33 New: Added book monitoring to author details
(cherry picked from commit 0ff889c3be1e8ee48759b233aeabf1bf1c4b4ff4)
(cherry picked from commit 0bd74098f9)
2021-09-02 21:12:13 +01:00
ta264
73950ca431 Fixed: Show metadata profile in mass editor by default 2021-09-02 21:12:13 +01:00
ta264
a746a9a4b1 Fixed: Cannot edit root folder calibre option after creation
Fixes #1188
2021-09-02 21:12:13 +01:00
ta264
7a969a63e4 New: Use ISO 639-3 language codes in metadata profile 2021-09-02 21:12:13 +01:00
ta264
7c10f3a74d New: Speed up metadata refresh 2021-09-02 21:12:13 +01:00
ta264
8edadbbfa2 Separate out Goodreads search from Author/Book data 2021-09-02 21:12:13 +01:00
ta264
dc2de62b03 Fixed: Use both album artist and track artist fields to pick up author for audiobooks 2021-09-01 21:04:39 +01:00
ta264
cdf8b0bc8f Fixed: Split concatenated authors when calculating match 2021-09-01 21:04:39 +01:00
ta264
17535bd8d6 Fixed: Better detection of series in search results 2021-09-01 21:04:39 +01:00
ta264
5f1be9e447 Fixed: Previous / Next author buttons sort by last name 2021-09-01 21:04:39 +01:00
ta264
c9cb0a9774 New: Search bar searches books as well as authors 2021-09-01 21:04:39 +01:00
Taloth Saldono
ba9f618405 Lazy Loading fuse-worker and fixed some potential timing issues when it's slow. Also keep last result while typing.
(cherry picked from commit 1e98002b8f3b01e41bff011644a9345c37c259c1)
2021-09-01 21:04:39 +01:00
ta264
468ebc3307 New: Load all books on page load and store in the browser 2021-09-01 21:04:39 +01:00
ta264
2558660b7b New: Add index views for all books 2021-09-01 21:04:39 +01:00
ta264
a774cf0682 Fixed: Bookshelf jump bar 2021-09-01 21:04:39 +01:00
Mark McDowall
924e393d1a Fixed: Log active indexers instead of implying all indexers are searched
(cherry picked from commit e19d4cf85b5a48ef823b9a983591d2bf7e72c621)
2021-09-01 09:35:00 -04:00
bakerboy448
98cc8f0a4b update bug report [skip ci]
(cherry picked from commit e912e14cbbc3fedf2242281609a96760a716d16f)
2021-08-27 21:08:27 -05:00
ta264
665d87b8d7 Fixed: Prefer 13 character ISBN from epub metadata
Fixes #1210
2021-08-21 14:36:44 +01:00
nitsua
7c662d4628 Add a link to the github issue if it is added to the change notes
Database Migration
NO

Description
Add a link to the github issue if it is added to the change notes
2021-08-18 16:23:52 +01:00
ta264
d5199ebcd7 Don't fail builds if sentry down 2021-08-17 21:43:29 +01:00
Thomas White
466876da62 Fixed: "Specific Book" setting for readarr list import (#1200) 2021-08-18 14:14:54 +01:00
bakerboy448
109fd22d1f New: Add missing Part Number and Part Count Examples 2021-08-14 18:08:58 -05:00
bakerboy448
001e24aaae Fixed: Corrected Indexer Category Help Text 2021-08-14 16:38:07 -05:00
Qstick
bbff60a2b0 Fixed: Avoid fetching quality definitions twice 2021-08-12 21:17:08 -04:00
Qstick
4b88cc5ea5 Fixed: Cleanup of unused tags for Import lists
Fixes #1181
2021-08-12 20:50:44 -04:00
Servarr
cf49404e13 Translated using Weblate (Portuguese (Brazil)) [skip ci] (#1147)
Currently translated at 99.7% (720 of 722 strings)

Translated using Weblate (Portuguese) [skip ci]

Currently translated at 77.9% (563 of 722 strings)

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

Currently translated at 99.7% (720 of 722 strings)

Translated using Weblate (Arabic) [skip ci]

Currently translated at 66.0% (477 of 722 strings)

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

Currently translated at 99.3% (717 of 722 strings)

Translated using Weblate (Portuguese) [skip ci]

Currently translated at 77.9% (563 of 722 strings)

Translated using Weblate (Hungarian) [skip ci]

Currently translated at 100.0% (722 of 722 strings)

Co-authored-by: Csaba <csab0825@gmail.com>
Co-authored-by: Havok Dan <havokdan@yahoo.com.br>
Co-authored-by: Weblate <noreply@weblate.org>
Co-authored-by: Will Segatto <segatto.w@gmail.com>
Co-authored-by: bison529 <abshalsh@gmail.com>
Translate-URL: https://translate.servarr.com/projects/servarr/readarr/ar/
Translate-URL: https://translate.servarr.com/projects/servarr/readarr/hu/
Translate-URL: https://translate.servarr.com/projects/servarr/readarr/pt/
Translate-URL: https://translate.servarr.com/projects/servarr/readarr/pt_BR/
Translation: Servarr/Readarr

Co-authored-by: Weblate <noreply@weblate.org>
Co-authored-by: Csaba <csab0825@gmail.com>
Co-authored-by: Havok Dan <havokdan@yahoo.com.br>
Co-authored-by: Will Segatto <segatto.w@gmail.com>
Co-authored-by: bison529 <abshalsh@gmail.com>
2021-08-11 19:45:36 -05:00
ta264
8a074c61f0 Fixed: Allow repeated import attempts until downloaded files appear
(cherry picked from commit 9f1d4446e82e9c5efde3d0339be2f7dde40ba1ac)
2021-08-04 21:11:21 +01:00
ta264
8dd6049603 Fixed: Don't mark downloads as failed if no files found 2021-08-04 21:09:20 +01:00
ta264
9249f0ca5d Fixed: ArgumentNullException importing against a newly added edition with no series links
- Fixes Sentry READARR-2CY

(cherry picked from commit 4eebd4f2050ca646869b657bb365e11b1eea4d0c)
2021-08-03 21:48:49 +01:00
ta264
4712fedb0e New: Refresh books added to match existing files
Fixes #1146

(cherry picked from commit 40c99fb67587b5890d4d2a3a3af1d61034598e1f)
2021-08-03 21:48:44 +01:00
ta264
d8d09c2517 Fixed: Books added to match existing files have correct monitored status
(cherry picked from commit c98b22723aa8bf0bba2ce9f5b9ec74608c354751)
2021-08-03 21:48:37 +01:00
ta264
59c1bf0b1f Fixed: Prevent books being added with AuthorMetadataId 0
(cherry picked from commit 0c227d0f21e3803c6293fd5988e48ea010483cad)
2021-08-03 21:48:28 +01:00
bakerboy448
1571ef4172 git updates [skip ci] 2021-08-01 21:24:30 +01:00
Mark McDowall
60866f4af6 Fixed: Peers filtering in Interactive Search results
(cherry picked from commit dca2cfcecd543ef93d9cb3735fefb7dbc2277107)
2021-08-01 21:23:39 +01:00
ta264
4541d3d3b0 Fixed: Parse search results using edition titles also
Fixes #1154
2021-07-30 19:51:21 +01:00
Alex Thomson
9150f6889f Remove duplicate call to DeleteTorrent
(cherry picked from commit 94417402d8364e435c35365a75278914d5405465)
2021-07-27 16:45:58 +01:00
ta264
474699b67d Fixed: Error refreshing author if manually added book edition has been removed from Goodreads
Fixes Sentry READARR-Z6
2021-07-26 22:02:30 +01:00
ta264
2a05112d7d Fixed: Set correct titleslug for a book 2021-07-26 22:00:16 +01:00
ta264
c1a846dd2b Fixed: Detect more variations of series book naming 2021-07-26 21:21:45 +01:00
ta264
1f22cae4f8 Fixed: Import highest quality file first, not largest size 2021-07-26 21:12:49 +01:00
ta264
b1e92e7f73 Fixed: Computing most common author now we have multiple authors 2021-07-27 07:06:06 +01:00
ta264
85e945430b Fixed: Read multiple authors from audio tags 2021-07-27 07:06:06 +01:00
ta264
2e5a9b8dd4 Fixed: Don't try to audio tag ebook files 2021-07-27 07:06:06 +01:00
ta264
fcd5005502 Fixed: Default naming scheme includes PartNumber for new databases also 2021-07-22 21:00:45 +01:00
ta264
331ef56e9a Update frontend packages 2021-07-22 20:37:43 +01:00
ta264
19549098a7 Fixed: Reinstate position in series table 2021-07-22 20:32:35 +01:00
ta264
848b183ab8 Fixed: Column settings for series table 2021-07-22 20:25:02 +01:00
Weblate
6adadc0ade Translated using Weblate (Japanese) [skip ci]
Currently translated at 66.2% (476 of 718 strings)

Translated using Weblate (Catalan) [skip ci]

Currently translated at 1.5% (11 of 718 strings)

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

Currently translated at 99.8% (717 of 718 strings)

Translated using Weblate (Portuguese) [skip ci]

Currently translated at 78.1% (561 of 718 strings)

Translated using Weblate (German) [skip ci]

Currently translated at 74.0% (532 of 718 strings)

Translated using Weblate (Arabic) [skip ci]

Currently translated at 66.4% (477 of 718 strings)

Added translation using Weblate (Chinese (Min Nan)) [skip ci]

Translated using Weblate (Chinese (Simplified) (zh_CN)) [skip ci]

Currently translated at 64.7% (465 of 718 strings)

Co-authored-by: Anonymous <noreply@weblate.org>
Co-authored-by: Havok Dan <havokdan@yahoo.com.br>
Co-authored-by: Sean <zhangshuyan@fuji.waseda.jp>
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/ca/
Translate-URL: https://translate.servarr.com/projects/servarr/readarr/de/
Translate-URL: https://translate.servarr.com/projects/servarr/readarr/ja/
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
2021-07-22 20:54:47 +01:00
bakerboy448
9ee1b5482a Update bug report template [skip ci]
(cherry picked from commit 4659a8366d8a1565890d3b72442bd35c6eb8176e)
2021-07-22 20:50:21 +01:00
bakerboy448
ae88bc30e1 Fixed: Provider Wiki Links (supported-X => supported#X) 2021-07-22 20:49:48 +01:00
ta264
af9f8a9a18 Fixed: Tidy up the Author / Book files table
Closes #1089
2021-07-22 20:35:01 +01:00
ta264
71a34c7650 Remove unused frontend stuff 2021-07-22 20:35:01 +01:00
ta264
7f8dc3d2b4 New: Optionally display authors as LastName, FirstName in index
Fixes #1062
2021-07-22 20:35:01 +01:00
ta264
332997aefe Clean up sort menu 2021-07-22 20:35:01 +01:00
ta264
0a20188508 Fixed: Bad Author -> Book lazy lookup 2021-07-20 21:41:57 +01:00
ta264
493ce1b20c Fixed: Deleting multiple books at once from author page 2021-07-20 21:41:57 +01:00
ta264
b3dd116d27 Fixed: Deleting author removes books from Calibre
Fixes #1144
2021-07-20 21:41:57 +01:00
Mark McDowall
ca7ba125d2 Fixed: Remove selected in queue
(cherry picked from commit 4ed5fefcc6e362cb5c62ec639ee64571c97f9b5b)
2021-07-20 19:31:28 +01:00
Mark McDowall
1a3bb381b1 Fixed: Queue refresh closing manual import from queue if items change
(cherry picked from commit e9818b9982d868648d714a37527f1b066486c5df)
2021-07-20 19:31:28 +01:00
Mark McDowall
8d6e0c6bc3 Fixed: Queue refresh closing manual import from queue if items change
(cherry picked from commit f5d690aa7b703e6fc0cd7eeced177a28cfed4962)
2021-07-20 19:31:28 +01:00
Taloth Saldono
6cb2bf6b5f Fixed post-install update check not running
(cherry picked from commit eea6be459d5dbfafb9a5285046282c25c4697242)
2021-07-20 19:31:28 +01:00
ta264
e791f22333 Fixed: Don't redirect //api route to homepage 2021-07-20 16:34:18 +01:00
ta264
eaa1578c65 Fixed: Error trying to write calibre tags when calibre not enabled 2021-07-20 16:34:18 +01:00
Weblate
e0236e781a Translated using Weblate (French) [skip ci]
Currently translated at 66.0% (474 of 718 strings)

Translated using Weblate (Catalan) [skip ci]

Currently translated at 0.2% (2 of 718 strings)

Translated using Weblate (Vietnamese) [skip ci]

Currently translated at 66.2% (476 of 718 strings)

Translated using Weblate (Korean) [skip ci]

Currently translated at 66.2% (476 of 718 strings)

Translated using Weblate (Hungarian) [skip ci]

Currently translated at 100.0% (718 of 718 strings)

Translated using Weblate (Greek) [skip ci]

Currently translated at 66.2% (476 of 718 strings)

Translated using Weblate (German) [skip ci]

Currently translated at 73.9% (531 of 718 strings)

Translated using Weblate (Danish) [skip ci]

Currently translated at 66.2% (476 of 718 strings)

Translated using Weblate (Czech) [skip ci]

Currently translated at 66.2% (476 of 718 strings)

Translated using Weblate (Bulgarian) [skip ci]

Currently translated at 66.2% (476 of 718 strings)

Translated using Weblate (Spanish) [skip ci]

Currently translated at 66.5% (478 of 718 strings)

Translated using Weblate (Dutch) [skip ci]

Currently translated at 66.5% (478 of 718 strings)

Added translation using Weblate (Catalan) [skip ci]

Co-authored-by: Anonymous <noreply@weblate.org>
Co-authored-by: Csaba <csab0825@gmail.com>
Co-authored-by: Weblate <noreply@weblate.org>
Co-authored-by: dtalens <databio@gmail.com>
Co-authored-by: foXaCe <foxace66@gmail.com>
Translate-URL: https://translate.servarr.com/projects/servarr/readarr/bg/
Translate-URL: https://translate.servarr.com/projects/servarr/readarr/ca/
Translate-URL: https://translate.servarr.com/projects/servarr/readarr/cs/
Translate-URL: https://translate.servarr.com/projects/servarr/readarr/da/
Translate-URL: https://translate.servarr.com/projects/servarr/readarr/de/
Translate-URL: https://translate.servarr.com/projects/servarr/readarr/el/
Translate-URL: https://translate.servarr.com/projects/servarr/readarr/es/
Translate-URL: https://translate.servarr.com/projects/servarr/readarr/fr/
Translate-URL: https://translate.servarr.com/projects/servarr/readarr/hu/
Translate-URL: https://translate.servarr.com/projects/servarr/readarr/ko/
Translate-URL: https://translate.servarr.com/projects/servarr/readarr/nl/
Translate-URL: https://translate.servarr.com/projects/servarr/readarr/vi/
Translation: Servarr/Readarr
2021-07-16 12:05:32 +01:00
Mark McDowall
92a9b5b451 Fixed: Unable to close indexer category select input on mobile
Closes #4296

(cherry picked from commit e42d1af5ff8f3eb987195caa7ec8a0bbaf4a00c3)

# Conflicts:
#	frontend/src/Components/Form/EnhancedSelectInput.js
2021-07-16 12:04:02 +01:00
Mark McDowall
226ad22748 Author: Mark McDowall <mark@mcdowall.ca>
Author date:	6 months ago (1/7/2021 1:01:30 AM)
Committer:		PearsonFlyer <PearsonFlyer@github.com>
Commit date:	19 minutes ago (7/9/2021 3:21:25 PM)
Commit hash:	0a104a9cf8a1c5421a0beed9120bfc84fac896a0
Child:			Commit index
Parent:			4f986de3

New: Allow quality size limits to be closer together

(cherry picked from commit 8a3027fa7c99af21a56ac1cfe9a5a0846de9c474)
(cherry picked from commit ef5d1b29467ea24d6721e54aec4b35062ab72e4f)
Contained in branches:
qual_size_limits
Contained in no tag

Derives from tag: v0.7.1.1381 + 815 commits
2021-07-16 12:02:23 +01:00
ta264
fe942524a2 Frontend package updates 2021-07-14 21:55:24 +01:00
ta264
8917b61ea9 Bump to .NET 5.0.8 and upgrade backend packages 2021-07-14 21:53:40 +01:00
ta264
60a49e3a03 Fixed: Error getting candidates if parsed author is null 2021-07-14 21:44:33 +01:00
bakerboy448
b8a3f09891 Fixed: Notifiarr Health Issue Level
ref Sonarr 5938c38bc3a76d1f1e105fb54d5d7f59aa207278

(cherry picked from commit 6f0a2de5053ccb859b4ffbe1e84b4f3e0f7ef9c9)
2021-07-14 21:10:33 -05:00
bakerboy448
6e7f0deef5 Update PULL_REQUEST_TEMPLATE.md [skip ci] (#1127) 2021-07-10 15:24:55 -05:00
PearsonFlyer
4f986de366 Fixed: Add 'FilterAuthor' token and update manual import text box 2021-07-08 21:49:22 -04:00
Weblate
4bf658e239 Translated using Weblate (Portuguese (Brazil)) [skip ci]
Currently translated at 100.0% (717 of 717 strings)

Translated using Weblate (Portuguese) [skip ci]

Currently translated at 75.8% (544 of 717 strings)

Translated using Weblate (Hungarian) [skip ci]

Currently translated at 100.0% (717 of 717 strings)

Translated using Weblate (Finnish) [skip ci]

Currently translated at 66.3% (476 of 717 strings)

Translated using Weblate (German) [skip ci]

Currently translated at 73.9% (530 of 717 strings)

Co-authored-by: Anonymous <noreply@weblate.org>
Co-authored-by: Csaba <csab0825@gmail.com>
Co-authored-by: Lizandra Candido da Silva <lizandra.c.s@gmail.com>
Translate-URL: https://translate.servarr.com/projects/servarr/readarr/de/
Translate-URL: https://translate.servarr.com/projects/servarr/readarr/fi/
Translate-URL: https://translate.servarr.com/projects/servarr/readarr/hu/
Translate-URL: https://translate.servarr.com/projects/servarr/readarr/pt/
Translate-URL: https://translate.servarr.com/projects/servarr/readarr/pt_BR/
Translation: Servarr/Readarr
2021-07-07 23:10:04 -04:00
servarr[bot]
45d0e20608 New: Add Size column to Activity: Queue (#1116)
* New: Add Size column to Activity: Queue

(cherry picked from commit 43cd1032487743c72fe0503a293343b8811def4d)

* fixup! Lint

Co-authored-by: Nathaniel Peiffer <nathaniel@peiffer.com.au>
Co-authored-by: Qstick <qstick@gmail.com>
2021-07-04 14:50:35 -04:00
PearsonFlyer
438b75e8cf Fixed: "Profile" to "Indexer" on Clone Button (
(cherry picked from commit 058ab4b4a8
2021-07-04 07:47:58 -05:00
Servarr
d590769607 Translated using Weblate (Portuguese (Brazil)) [skip ci] (#1107)
Currently translated at 98.3% (704 of 716 strings)

Translated using Weblate (Portuguese) [skip ci]

Currently translated at 72.2% (517 of 716 strings)

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

Currently translated at 98.1% (703 of 716 strings)

Translated using Weblate (Portuguese) [skip ci]

Currently translated at 70.2% (503 of 716 strings)

Translated using Weblate (Portuguese) [skip ci]

Currently translated at 69.6% (498 of 715 strings)

Translated using Weblate (German) [skip ci]

Currently translated at 71.8% (514 of 715 strings)

Co-authored-by: Lizandra Candido da Silva <lizandra.c.s@gmail.com>
Co-authored-by: Weblate <noreply@weblate.org>
Co-authored-by: reloxx <reloxx@interia.pl>
Translate-URL: https://translate.servarr.com/projects/servarr/readarr/de/
Translate-URL: https://translate.servarr.com/projects/servarr/readarr/pt/
Translate-URL: https://translate.servarr.com/projects/servarr/readarr/pt_BR/
Translation: Servarr/Readarr

Co-authored-by: Weblate <noreply@weblate.org>
Co-authored-by: Lizandra Candido da Silva <lizandra.c.s@gmail.com>
Co-authored-by: reloxx <reloxx@interia.pl>
2021-07-04 07:44:53 -05:00
Hugo Cortes
104d65b4ea fix: add SortName to author name regex validation 2021-07-04 13:21:15 +01:00
bakerboy448
25cb29e325 sync contributing from radarr [skip ci] (#1112)
* Update contributing.md Github docs URL [skip ci]

Closes #845

(cherry picked from commit 4e81b330061a758ec8ad37dbe648694187226687)

* sync contributing from radarr [skip ci]
2021-07-04 02:34:19 -04:00
Qstick
d3f9474eab PublishEvent after StartupContext to try fix integration tests 2021-07-04 02:25:09 -04:00
Mark McDowall
d5a74e7064 Return max tooltip width
Fixes #602

(cherry picked from commit fe0d8bb7da27e0f34b5db166ee54033f292b1006)
2021-07-04 01:33:05 -04:00
Mark McDowall
80ba57d4b5 Fix tooltip max width on larger screens
Fixes #510

(cherry picked from commit f4f2a6f5fc14244f9acf8186cbacda7f9c1e0481)
2021-07-04 01:32:57 -04:00
Mark McDowall
9fe1663267 Fixed: Episode history details tooltip jumping around
Fixes #503

(cherry picked from commit 796c5e8b6b9e2ba2b97c6144f64a756e76a947a9)
2021-07-04 01:32:29 -04:00
Qstick
0d860de88a Fixed: Tooltips overflowing the screen width
Fixes #430
Fixes #432

(cherry picked from commit 0a66e86cccc29d7fef6da7876b3b761deb414648)
2021-07-04 01:27:39 -04:00
Taloth Saldono
38a6553654 Added IsTorrentLoaded to tests
Fixes #911

(cherry picked from commit 7272c5b7fcada0b346e1dd72fb2d1caa6b6e4c63)
2021-07-04 00:49:27 -04:00
Taloth Saldono
916391edc3 Fixed: Qbittorrent api errors when only one of two seed criteria was configured
closes #907

(cherry picked from commit 652d44722b96a7a830ec45fe83260ddcecc525a7)
2021-07-04 00:48:26 -04:00
Taloth Saldono
ac36675c6e Fixed: Setting seed criteria while torrent is still being loaded by qbittorrent
closes #896

(cherry picked from commit 67e97f7aee761d19af6fe1a086691a9934635a6d)
2021-07-04 00:44:42 -04:00
bakerboy448
d76f8e715d Fixed flaky test.
(cherry picked from commit f846e0c031d74914d3a02626597df583422164e0)
Closes #304
2021-07-04 00:24:21 -04:00
bakerboy448
27e799b624 Log contents on api errors during tests.
(cherry picked from commit f2e1b4e43549695afacee5c87cca368e8fa30905)

Closes #295
2021-07-04 00:24:21 -04:00
Mark McDowall
d34e588588 New: Queue status icon is purple when download is waiting to import or importing
Fixed: Time Left on Queue won't show when completed
Queue status/timeleft improvements

Closes #281

(cherry picked from commit 910de6d94a4482e82e295153e01c299460af2d2d)
2021-07-04 00:24:21 -04:00
Mark McDowall
1be94da583 New: Don't close manual import when clicking outside the modal
Closes #278

(cherry picked from commit fd608fd4113bd1d6fde42bca4d9879f826a8c6a9)
2021-07-04 00:24:21 -04:00
rg9400
4066aa1472 Fixed: Indicate unchecking Replace Illegal Characters will remove them
Closes #251
2021-07-04 00:24:21 -04:00
beyondmeat
050dd67972 Fixed: Empty list message for System: Events
(cherry picked from commit a23639e62ee5e2106831c10d51b6a4d6ba569fdc)

Closes #159
2021-07-04 00:24:21 -04:00
bakerboy448
ed1935c85d fixup! Update wiki links 2021-07-04 00:24:21 -04:00
Qstick
b625bd00ea fix import sort in appActions 2021-07-04 00:17:48 -04:00
Qstick
a7c71c2093 fix wiki fragment for PackageGlobalMessageCheck 2021-07-04 00:15:46 -04:00
Qstick
a6774eed6e fixup LocalizationService in PackageGlobalMessageCheck.cs 2021-07-04 00:07:24 -04:00
Qstick
a1f740646f Fixed: Database migration failure when database was manually repaired in a certain way
Fixes #903
2021-07-03 23:53:09 -04:00
Taloth Saldono
759b78e816 Added mechanism for package maintainers to produce a health check error.
Fixes #859

(cherry picked from commit 7da02c236aa03e6aef011130526040c1cb8399fc)
(cherry picked from commit 024000275df3b2d3b884c2c2fbf0b86bd36a631a)
2021-07-03 23:50:05 -04:00
Taloth Saldono
740e0edc04 Added update check early in startup if the package requested a post-install update check 2021-07-03 23:47:56 -04:00
Mark McDowall
46fa7e80a0 Fixed: Restoring a backup with a different API didn't reload properly
Fixes #853

(cherry picked from commit 13ff2d4c70bc564525842c31d184127a3934e178)
2021-07-03 23:20:47 -04:00
Mark McDowall
45aa744b0f Fixed: Removal of previous service
Fixes #851

(cherry picked from commit cd28af98eed831e00a400e66b652e6a8d6f9c442)
2021-07-03 23:19:18 -04:00
Qstick
3e72770ec5 Handle 303 and 307 redirects in Http Requests
Fixes #849
2021-07-03 23:08:43 -04:00
Qstick
529ffa8864 Revert "Fixed: Corrupt image files when downloading from redirecting Url"
This reverts commit 79dfd8f14d.
2021-07-03 23:08:38 -04:00
servarr[bot]
99a2e1f0fa Fixed: Refresh queue count when navigating Activity: Queue
(cherry picked from commit 0a2b109a3fe101e260b623d0768240ef8b7a47ae)

Co-authored-by: Mark McDowall <mark@mcdowall.ca>
Closes #975
2021-07-03 20:04:57 -05:00
Taloth Saldono
9ddd08b0de Increased max redirects from 3 to 5
closes #3449

(cherry picked from commit d421ff973692a35d55e3773b2dc82e0a0d717546)
2021-07-03 19:19:27 -04:00
Taloth Saldono
79dfd8f14d Fixed: Corrupt image files when downloading from redirecting Url
(cherry picked from commit e28b2e83284f689d3620d8d394ef35fdfd55646f)
Closes #108
2021-07-03 19:19:27 -04:00
Mark McDowall
db885a5111 Fixed: Manual import from queue showing error when download name failed to parse
(cherry picked from commit 079a0b56c3e124616fef6e2f81c19f67c13acb96)

Closes #67
2021-07-03 19:19:27 -04:00
Qstick
b23e225271 Update wiki links 2021-07-03 14:55:53 -04:00
Qstick
68dd6bc98e Align version header with others
Fixes #865
Fixes #866
2021-07-03 14:36:51 -04:00
Qstick
966e9f43f1 Fixed: 'iso-8859-2' is not a supported encoding name 2021-07-03 14:13:49 -04:00
Mark McDowall
45671c89f9 Fixed: Show feed URL if incorrect mime type is found
Fixes #571
Fixes #572

(cherry picked from commit fa2e70d571cc7658611a0c51b8603247a22e6a2e)
2021-07-03 14:00:20 -04:00
Mark McDowall
8ad917e94b New: Warning when combining preferred words with a specific indexer
Fixes #534

(cherry picked from commit fae38a107f738eff148271499b571b93ed192e84)
2021-07-03 13:59:12 -04:00
Qstick
3de0df2162 Fixed: Root folder custom filter in Mass Editor
Fixes #260

Co-Authored-By: Mark McDowall <markus101@users.noreply.github.com>
2021-07-03 13:52:48 -04:00
Taloth Saldono
e4225aa2c2 Fixed enter in modal confirmation dialogs
Fixes #463
Fixes #464

(cherry picked from commit 930742ae2c69a530afe60f76a5824f2722540df8)
2021-07-03 13:45:12 -04:00
Qstick
a877c5b25d Fixed: Can ignore queue items with unknown book 2021-07-03 13:45:12 -04:00
Qstick
1f06c4f4e2 Fixed: Tooltip for existing author/book on add new item 2021-07-03 13:45:12 -04:00
Mark McDowall
c31bbb63fb Fix checkingUP qbit status unit test
Fixes #227
2021-07-03 13:45:12 -04:00
Mark McDowall
92715ac850 Fixed: Treated checkingUP status from Qbit as queued in case it fails to validate
Fixes #220

Co-Authored-By: Mark McDowall <markus101@users.noreply.github.com>
2021-07-03 13:45:12 -04:00
Qstick
cbf382625f Fixed: Preferred word can't have a term that is empty or only spaces
Fixes #218

Co-Authored-By: Mark McDowall <markus101@users.noreply.github.com>
2021-07-03 13:45:12 -04:00
Mark McDowall
e6250bfe0f New: Don't forcibly retest indexers/download clients/connections on save if previously enabled
Fixes #448

(cherry picked from commit b2b1600ebe7f022a3248ea12b69553e2d51a3a7c)
2021-07-03 13:45:11 -04:00
Qstick
ca00b8a3c7 New: Handle missingFiles status from qBit
Fixes #442

(cherry picked from commit 9a2cee3104b1e8d32f2df68d3ca75967aba41868)
2021-07-03 13:45:11 -04:00
Mark McDowall
39b2326bc5 Fixed: Replace : with _ when getting output path from Transmission
Fixes #433

(cherry picked from commit 0f792f9eb9517a4165a54d7c4551f4f68822d18e)
2021-07-03 13:45:11 -04:00
PearsonFlyer
907b7dc429 New: Add Readarr list sync
Closes #438
2021-07-03 12:34:48 -05:00
PearsonFlyer
9a78c8b512 Fixed: Compatibility with the new Download Station API 2021-07-03 06:55:48 -05:00
Servarr
5dc328346c Translated using Weblate (Portuguese (Brazil)) [skip ci] (#1092)
Currently translated at 98.3% (703 of 715 strings)

Translated using Weblate (Portuguese) [skip ci]

Currently translated at 69.3% (496 of 715 strings)

Translated using Weblate (Portuguese) [skip ci]

Currently translated at 69.3% (496 of 715 strings)

Translated using Weblate (Portuguese) [skip ci]

Currently translated at 69.3% (496 of 715 strings)

Translated using Weblate (Portuguese) [skip ci]

Currently translated at 69.2% (495 of 715 strings)

Translated using Weblate (Hungarian) [skip ci]

Currently translated at 100.0% (715 of 715 strings)

Co-authored-by: Csaba <csab0825@gmail.com>
Co-authored-by: Havok Dan <havokdan@yahoo.com.br>
Co-authored-by: Lizandra Candido da Silva <lizandra.c.s@gmail.com>
Co-authored-by: Weblate <noreply@weblate.org>
Translate-URL: https://translate.servarr.com/projects/servarr/readarr/hu/
Translate-URL: https://translate.servarr.com/projects/servarr/readarr/pt/
Translate-URL: https://translate.servarr.com/projects/servarr/readarr/pt_BR/
Translation: Servarr/Readarr

Co-authored-by: Weblate <noreply@weblate.org>
Co-authored-by: Csaba <csab0825@gmail.com>
Co-authored-by: Havok Dan <havokdan@yahoo.com.br>
Co-authored-by: Lizandra Candido da Silva <lizandra.c.s@gmail.com>
2021-07-03 06:55:00 -05:00
Taloth Saldono
79bd4728bf Removed extraneous enum hints in dropdown
(cherry picked from commit 91fe47ef31249f23c19a553df569ef6451e6ca5d)
2021-07-02 21:18:54 -04:00
bakerboy448
eb26b38f2c New: Replace SmtpClient with Mailkit
Closes #723
2021-07-02 21:16:05 -04:00
Taloth Saldono
811c84a845 Multiple Email Address
Fixes #854
Fixes #884
Fixes #951
Fixes #954

(cherry picked from commit a8b6f70be1860aa502795f0dd30299c87d54dbbe)
2021-07-02 21:16:05 -04:00
cicomalieran
214fa4c06e Fixed: Parsing RSS with null values
(cherry picked from commit 8175f19442273e13517b2d507e7a4c33587f1b15)
(cherry picked from commit 6842c561ea36d1d2661cb87c517d6c155d65c4d3)

Closes #982
2021-07-02 21:16:05 -04:00
Skyler Mäntysaari
fc714182ad New: Mailgun connection
(cherry picked from commit 55752a6c6213c1d83d347ba0f6870aa6c1cc0770)
(cherry picked from commit 0b60ca68fcb5778d7b9eb1ae23e298458b4ab98d)
closes #981
closes #980
2021-07-02 21:16:05 -04:00
PearsonFlyer
7c96ab88d1 Really ignore ResizeObserver loop errors 2021-07-02 19:34:47 -04:00
bakerboy448
d6de4fc41f fix concurrency so runs don't cancel [skip ci] 2021-07-01 22:05:57 -04:00
PearsonFlyer
33b4ed44de Fixed: Corrected Naming Examples 2021-07-01 18:55:31 -05:00
ta264
81b12a7441 Fixed: Log files should not be cached 2021-07-01 16:22:57 +01:00
Qstick
8406dbe49d Prevent sync jobs from running in parallel
[skip ci]
2021-06-29 21:52:33 -04:00
servarr[bot]
cbaf973eeb New: Translate Activity Queue & Rename Timeleft to Time Left
New: Translate Activity Queue

(cherry picked from radarr commit 7d644aa54430eb6712e294956e35c5601077e398)
add some missing keys/fixups as well

Co-authored-by: bakerboy448 <55419169+bakerboy448@users.noreply.github.com>
2021-06-28 20:34:40 -05:00
bakerboy448
44711d7585 add wiki link to bug template [skip ci]
(cherry picked from commit 15a99ab650ce2d5ca9e21a58847bf9f7307299ea)

Closes #1085
2021-06-28 20:11:59 -05:00
Mark McDowall
c00d760cb0 Fix spelling of separated
(cherry picked from commit a4dea0aa62a6c0eb78c89d9ac2e80548fbceb615)

Closes #944
2021-06-28 20:11:59 -05:00
bakerboy448
9ad580effa Git template update [skip ci]
* FR template update [skip ci]

* PR Template update [skip ci]

Closes #1022

(cherry picked from commit ec3dbea7fc592801bf4e42e17e14ea4a66bed46e)
2021-06-28 20:11:59 -05:00
Qstick
2acc3f4584 Fixed: NZBGet Settings hint mentions Sabnzbd
closes #1044

(cherrypicked from Sonarr 2ad4e21aadbb2b6c94e4af529b2329e224ce4f8e)
2021-06-28 20:11:59 -05:00
Robin Dadswell
3fac1c23b2 Fix: Consistent SSL option for Download Clients
Closes #873

(cherrypicked from Sonarr 85f4cbe94c6f00352e0d4a38f337fb9417d3032b)
2021-06-28 20:11:59 -05:00
Qstick
a596a1fe9a Create azuresync.yml 2021-06-26 22:20:58 -04:00
Jake Soenneker
1835edcb67 New: Manual Import rejection column is sortable
(cherry picked from commit 2f366bc3b7274200ff9d0bf1aa96408dc92206f3)
2021-06-26 16:46:56 -04:00
Weblate
f928e2c3a8 Translated using Weblate (Portuguese) [skip ci]
Currently translated at 70.5% (496 of 703 strings)

Translated using Weblate (Chinese (Simplified)) [skip ci]

Currently translated at 0.1% (1 of 703 strings)

Translated using Weblate (Chinese (Simplified) (zh_CN)) [skip ci]

Currently translated at 65.8% (463 of 703 strings)

Translated using Weblate (Portuguese) [skip ci]

Currently translated at 70.5% (496 of 703 strings)

Translated using Weblate (Vietnamese) [skip ci]

Currently translated at 66.0% (464 of 703 strings)

Translated using Weblate (Turkish) [skip ci]

Currently translated at 66.0% (464 of 703 strings)

Translated using Weblate (Thai) [skip ci]

Currently translated at 66.0% (464 of 703 strings)

Translated using Weblate (Swedish) [skip ci]

Currently translated at 66.0% (464 of 703 strings)

Translated using Weblate (Russian) [skip ci]

Currently translated at 66.0% (464 of 703 strings)

Translated using Weblate (Romanian) [skip ci]

Currently translated at 66.0% (464 of 703 strings)

Translated using Weblate (Polish) [skip ci]

Currently translated at 66.0% (464 of 703 strings)

Translated using Weblate (Korean) [skip ci]

Currently translated at 66.0% (464 of 703 strings)

Translated using Weblate (Japanese) [skip ci]

Currently translated at 66.0% (464 of 703 strings)

Translated using Weblate (Italian) [skip ci]

Currently translated at 65.8% (463 of 703 strings)

Translated using Weblate (Icelandic) [skip ci]

Currently translated at 66.0% (464 of 703 strings)

Translated using Weblate (Hindi) [skip ci]

Currently translated at 66.0% (464 of 703 strings)

Translated using Weblate (Hebrew) [skip ci]

Currently translated at 66.0% (464 of 703 strings)

Translated using Weblate (French) [skip ci]

Currently translated at 67.4% (474 of 703 strings)

Translated using Weblate (Finnish) [skip ci]

Currently translated at 66.0% (464 of 703 strings)

Translated using Weblate (Greek) [skip ci]

Currently translated at 66.0% (464 of 703 strings)

Translated using Weblate (German) [skip ci]

Currently translated at 73.1% (514 of 703 strings)

Translated using Weblate (Danish) [skip ci]

Currently translated at 66.0% (464 of 703 strings)

Translated using Weblate (Czech) [skip ci]

Currently translated at 66.0% (464 of 703 strings)

Translated using Weblate (Bulgarian) [skip ci]

Currently translated at 66.0% (464 of 703 strings)

Translated using Weblate (Arabic) [skip ci]

Currently translated at 66.1% (465 of 703 strings)

Translated using Weblate (Spanish) [skip ci]

Currently translated at 66.2% (466 of 703 strings)

Translated using Weblate (Dutch) [skip ci]

Currently translated at 66.1% (465 of 703 strings)

Added translation using Weblate (Chinese (Simplified)) [skip ci]

Co-authored-by: Anonymous <noreply@weblate.org>
Co-authored-by: Lizandra Candido da Silva <lizandra.c.s@gmail.com>
Co-authored-by: Weblate <noreply@weblate.org>
Co-authored-by: angelsky11 <angelsky11@gmail.com>
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/hi/
Translate-URL: https://translate.servarr.com/projects/servarr/readarr/is/
Translate-URL: https://translate.servarr.com/projects/servarr/readarr/it/
Translate-URL: https://translate.servarr.com/projects/servarr/readarr/ja/
Translate-URL: https://translate.servarr.com/projects/servarr/readarr/ko/
Translate-URL: https://translate.servarr.com/projects/servarr/readarr/nl/
Translate-URL: https://translate.servarr.com/projects/servarr/readarr/pl/
Translate-URL: https://translate.servarr.com/projects/servarr/readarr/pt/
Translate-URL: https://translate.servarr.com/projects/servarr/readarr/ro/
Translate-URL: https://translate.servarr.com/projects/servarr/readarr/ru/
Translate-URL: https://translate.servarr.com/projects/servarr/readarr/sv/
Translate-URL: https://translate.servarr.com/projects/servarr/readarr/th/
Translate-URL: https://translate.servarr.com/projects/servarr/readarr/tr/
Translate-URL: https://translate.servarr.com/projects/servarr/readarr/vi/
Translate-URL: https://translate.servarr.com/projects/servarr/readarr/zh_CN/
Translate-URL: https://translate.servarr.com/projects/servarr/readarr/zh_Hans/
Translation: Servarr/Readarr
2021-06-26 16:42:32 -04:00
Qstick
e48ed1806c Update Discord Notification Job
[skip ci]
2021-06-26 16:37:26 -04:00
Robin Dadswell
2f13fe0760 New: Added Prowlarr Donation link 2021-06-22 12:00:56 +01:00
RobinDadswell
d01645052d fixups 2021-06-22 11:18:53 +01:00
Mark McDowall
d822523394 Fixed broken tests
(cherry picked from commit 16156192c5f1abd929bcd825186b9f153507a46e)
2021-06-22 11:18:53 +01:00
Mark McDowall
0ffc44f3da Pull Sonarr commit 'Fixed: Removing completed download from SABnzbd'
(cherry picked from c669be317fffa252d59851e9a8ca9e56032a01fb)
2021-06-22 11:18:53 +01:00
Evan J
5fff24bf69 Update login.html
(cherry picked from commit e8f58eb9be583639909c0ac9b3dc3b40db8c7a53)
2021-06-11 14:38:57 +01:00
Servarr
a34853c754 Translations update from Weblate (#1056)
* Translated using Weblate (Hungarian) [skip ci]

Currently translated at 100.0% (695 of 695 strings)

Translation: Servarr/Readarr
Translate-URL: https://translate.servarr.com/projects/servarr/readarr/hu/

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

Currently translated at 100.0% (695 of 695 strings)

Translation: Servarr/Readarr
Translate-URL: https://translate.servarr.com/projects/servarr/readarr/pt_BR/

* Added translation using Weblate (Chinese (Traditional) (zh_TW)) [skip ci]

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

Currently translated at 100.0% (695 of 695 strings)

Translation: Servarr/Readarr
Translate-URL: https://translate.servarr.com/projects/servarr/readarr/pt_BR/

* Translated using Weblate (Hungarian) [skip ci]

Currently translated at 100.0% (703 of 703 strings)

Translation: Servarr/Readarr
Translate-URL: https://translate.servarr.com/projects/servarr/readarr/hu/

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

Currently translated at 100.0% (703 of 703 strings)

Translation: Servarr/Readarr
Translate-URL: https://translate.servarr.com/projects/servarr/readarr/pt_BR/

* Translated using Weblate (Chinese (Simplified) (zh_CN)) [skip ci]

Currently translated at 64.8% (456 of 703 strings)

Translation: Servarr/Readarr
Translate-URL: https://translate.servarr.com/projects/servarr/readarr/zh_CN/

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

Currently translated at 100.0% (703 of 703 strings)

Translation: Servarr/Readarr
Translate-URL: https://translate.servarr.com/projects/servarr/readarr/pt_BR/

* Translated using Weblate (French) [skip ci]

Currently translated at 66.0% (464 of 703 strings)

Translation: Servarr/Readarr
Translate-URL: https://translate.servarr.com/projects/servarr/readarr/fr/

* Translated using Weblate (German) [skip ci]

Currently translated at 71.6% (504 of 703 strings)

Translation: Servarr/Readarr
Translate-URL: https://translate.servarr.com/projects/servarr/readarr/de/

* Translated using Weblate (Portuguese) [skip ci]

Currently translated at 66.7% (469 of 703 strings)

Translation: Servarr/Readarr
Translate-URL: https://translate.servarr.com/projects/servarr/readarr/pt/

Co-authored-by: Csaba <csab0825@gmail.com>
Co-authored-by: Lizandra Candido da Silva <lizandra.c.s@gmail.com>
Co-authored-by: Weblate <noreply@weblate.org>
Co-authored-by: muihiuwev <muihiuwev@outlook.com>
Co-authored-by: Simon Willcock <simon.willcock@kogan.com.au>
Co-authored-by: doob187 <amderkum@gmail.com>
2021-06-11 14:38:35 +01:00
Taloth Saldono
cc13f7126c Make it clearer that Maximum size is the global limit.
(cherry picked from commit a848e575cd37eb3cc421a66fc6b4bbfb26782d8e)
2021-06-11 14:38:07 +01:00
Robin Dadswell
218a82998a Fixed: Real IP logging when IPv4 is mapped as IPv6 2021-06-11 14:36:48 +01:00
ta264
ee81ad2add Fixed: Bad login redirect using a reverse proxy
(cherry picked from commit b0f04bb9d79a9f9f0021d179ea7273998be7cab3)
2021-06-11 14:36:15 +01:00
ta264
648a41ed7b Fixed: RSS sync paging back too far for some indexers
(cherry picked from commit 7fcee7173450fc24f7733ea907b0886c1aa7d735)
2021-06-11 14:36:15 +01:00
ta264
eb9b9d57ed Fixed: Error on bulk delete from queue 2021-06-11 14:36:15 +01:00
ta264
3ab29eee60 Fixed: Reinstate total book count in author stats 2021-06-11 14:36:15 +01:00
ta264
fe13823b43 Fixed: Incorrectly looking up books by EditionId 2021-06-11 14:36:15 +01:00
ta264
13d8554e7e New: More book title naming tokens 2021-06-11 14:36:15 +01:00
ta264
4d840d6f43 New: PartNumber and PartCount naming tokens 2021-06-11 14:36:15 +01:00
ta264
a0e2747004 New: More granular book year naming tokens 2021-06-11 14:36:15 +01:00
ta264
c484a29099 Clean up naming tokens page 2021-06-11 14:36:15 +01:00
ta264
23772ce312 New: Author SortName token 2021-06-11 14:36:15 +01:00
ta264
63cfec517d Bump to .NET 5.0.7 and upgrade packages 2021-06-11 14:36:15 +01:00
ta264
e1465f5336 Fixed: Renaming multi-part books 2021-06-11 14:36:15 +01:00
ta264
7fda41c18b New: Better matching of books with subtitles 2021-06-11 14:36:15 +01:00
ta264
aa45bc3938 New: Neater filling of author images to aspect ratio 2021-06-11 14:36:15 +01:00
ta264
2ca55ae729 Fixed: Remove useless Banners view 2021-06-11 14:36:15 +01:00
ta264
977c4e653b Adjust default quality profiles 2021-05-28 06:49:08 +01:00
Mark McDowall
c052363368 Fix webpack memory leak when copying HTML files
(cherry picked from commit 2804a961cb457cc94d3eaa1fe8ee2f71d9d1261c)
2021-05-28 06:49:08 +01:00
ta264
9fb7a1051e Rename MP3-320 to MP3 2021-05-28 06:49:08 +01:00
ta264
3abda061ba New: Detect audio vs text from newznab categories 2021-05-28 06:49:08 +01:00
ta264
065f03a01a Add M4B and Unknown Audio qualities 2021-05-28 06:49:08 +01:00
ta264
f6a04f7890 New: Basic audiobook support 2021-05-28 06:49:08 +01:00
ta264
62928b227b Fixed: Better detection of part books and sets 2021-05-28 06:49:08 +01:00
Robin Dadswell
c93870ff60 New: Indexer Categories no longer an advanced option (#1052) 2021-05-23 23:45:22 +01:00
Robin Dadswell
0bcad2e57b Fixed: Root Folder Downloads check giving errors when RuTorrent is used (#1051)
* Fixed: Root Folder Downloads check giving errors when RuTorrent is used

* removed unnecessary if statement from RemotePathMappingCheck
2021-05-23 23:41:16 +01:00
Robin Dadswell
6acd42e82b Show User Agent in System->Tasks for externally triggered commands (#1047)
* Show User Agent in System->Tasks for externally triggered commands

(cherry picked from commit fe8f319f7bfdadb7218b6313ada6cae1d2a35ad8)

* Fix ESLint

(cherry picked from commit c3837c9f7b50534e3eafe1d9fbaf360fee4588b7)

* fixup

Co-authored-by: Taloth Saldono <Taloth@users.noreply.github.com>
Co-authored-by: Mark McDowall <mark@mcdowall.ca>
2021-05-23 20:52:14 +01:00
servarr[bot]
8711c5d824 New: Remove completed downloads from disk when removing from SABnzbd
(cherry picked from commit ac8283d7339b3d9e8ccf70f3d1ff031d1b0a71d1)

Fixes #947

Co-authored-by: Mark McDowall <mark@mcdowall.ca>
2021-05-16 23:05:18 +01:00
Servarr
ba6b52c4c9 Translations update from Weblate (#1015)
* Translated using Weblate (Hungarian) [skip ci]

Currently translated at 100.0% (692 of 692 strings)

Translation: Servarr/Readarr
Translate-URL: https://translate.servarr.com/projects/servarr/readarr/hu/

* Translated using Weblate (Italian) [skip ci]

Currently translated at 66.6% (461 of 692 strings)

Translation: Servarr/Readarr
Translate-URL: https://translate.servarr.com/projects/servarr/readarr/it/

* Translated using Weblate (Dutch) [skip ci]

Currently translated at 66.9% (463 of 692 strings)

Translation: Servarr/Readarr
Translate-URL: https://translate.servarr.com/projects/servarr/readarr/nl/

* Added translation using Weblate (Chinese (Traditional)) [skip ci]

* Translated using Weblate (Danish) [skip ci]

Currently translated at 66.9% (463 of 692 strings)

Translation: Servarr/Readarr
Translate-URL: https://translate.servarr.com/projects/servarr/readarr/da/

* Deleted translation using Weblate (Chinese (Traditional)) [skip ci]

* Translated using Weblate (Hungarian) [skip ci]

Currently translated at 100.0% (694 of 694 strings)

Translation: Servarr/Readarr
Translate-URL: https://translate.servarr.com/projects/servarr/readarr/hu/

* Translated using Weblate (German) [skip ci]

Currently translated at 68.7% (478 of 695 strings)

Translation: Servarr/Readarr
Translate-URL: https://translate.servarr.com/projects/servarr/readarr/de/

* Translated using Weblate (Russian) [skip ci]

Currently translated at 66.6% (463 of 695 strings)

Translation: Servarr/Readarr
Translate-URL: https://translate.servarr.com/projects/servarr/readarr/ru/

Co-authored-by: Csaba <csab0825@gmail.com>
Co-authored-by: memnos <fabrassi@gmail.com>
Co-authored-by: ProjectHydra31 <kay@timmerman.io>
Co-authored-by: Weblate <noreply@weblate.org>
Co-authored-by: lechuck <theghostpirate@gmail.com>
Co-authored-by: Qstick <qstick@gmail.com>
Co-authored-by: reloxx <reloxx@interia.pl>
Co-authored-by: kingii98 <konoff2@gmail.com>
2021-05-15 09:13:22 +01:00
ta264
96db74494a Fixed: Sort authors by lastname, firstname 2021-05-15 08:51:35 +01:00
ta264
62221c2a7f Don't run build for weblate PRs
[common]
2021-05-13 21:16:40 +01:00
nitsua
80b0c594b5 Add a donations section to System for each arr
(cherry picked from commit 1b34244143)
2021-05-13 21:44:18 +01:00
nitsua
6612dab547 Adjust CSS so titles are wrap instead of truncate
(cherry picked from commit d7e91f39b4)
2021-05-13 21:44:02 +01:00
Robin Dadswell
292aacc766 New: Localization support on Health Checks 2021-05-14 10:32:10 +01:00
Robin Dadswell
a4755631c3 New: Health Check for Downloads to Root Folder
removed using localization

updated ShouldBeWarning to include wikifragment check

fixed linting issues

slight tidy up
2021-05-14 10:32:10 +01:00
bakerboy448
c5dcb22c01 New: DiscordNotifier is now Notifiarr
Fixes #1014
2021-05-14 10:30:24 +01:00
bakerboy448
1db7ee5111 Fixed: Cleanse Tracker Announce Keys from logs
Fixes #844
2021-05-14 10:30:24 +01:00
bakerboy448
90d610b33a Fixed: Cleanse Notifiarr APIKey from logs
fixes #937
2021-05-14 10:30:24 +01:00
ta264
2984e2e1ca Fixed: Entity too large with big calibre library and digest authentication
Fixes #840
2021-05-13 21:27:16 +01:00
ta264
36080d4665 Fixed: Jumpbar after going back to artist index page 2021-05-13 21:15:11 +01:00
ta264
93f0f33e84 Fixed: restoring scroll position when going back to index page 2021-05-13 21:15:11 +01:00
ta264
129591de61 Revert "Ignore update test temporarily"
This reverts commit fd291aeb96.
2021-05-13 19:55:14 +01:00
ta264
3767c830d5 Fixed: Better detection of existing files 2021-05-13 19:55:14 +01:00
ta264
774180262b Better tracking of CalibreId 2021-05-13 19:55:14 +01:00
ta264
dc843ec63e Fixed: Handle filename changes when retagging with calibre 2021-05-13 19:55:14 +01:00
ta264
bfb42929a2 Fixed: SeriesBookLink housekeeping task 2021-05-13 19:55:14 +01:00
ta264
be09385c9d Fixed: Null reference processing failed import for unknown book 2021-05-13 19:55:14 +01:00
ta264
3c3f3a4d90 Use custom coverlet 2021-05-13 19:55:14 +01:00
ta264
222236bbde Update sentry 2021-05-13 19:55:14 +01:00
ta264
85f93eba98 Update nuget packages, dotnet 5.0.6 2021-05-13 19:55:14 +01:00
ta264
4181334d06 No clean 2021-05-13 19:55:14 +01:00
ta264
d1ed249039 More stringent cache key 2021-05-13 19:55:14 +01:00
ta264
0416b47b69 Remove the backend change optimization 2021-05-13 19:55:14 +01:00
ta264
d7f92daf08 Speed up upload, don't wait on non-windows builds 2021-05-13 19:55:14 +01:00
ta264
a146f6d223 Cache nuget packages 2021-05-13 19:55:14 +01:00
ta264
8864b6b2c8 Run coverage on linux 2021-05-13 19:55:14 +01:00
Qstick
1dff396b9e Fix lint 2021-05-11 21:40:36 +01:00
ta264
a28595887a Update signalr, connected react router, webpack, mini-css-extract 2021-05-10 22:34:15 +01:00
Mark McDowall
bdbbcd7fac Updated react-dnd and added touch support
Fixed: Drag and drop on mobile devices
Closes ##4429
2021-05-10 22:24:22 +01:00
ta264
d668bd5277 Update redux and react-redux 2021-05-10 22:24:22 +01:00
ta264
651808497f Update react-addons-shallow-compare, react-autosuggest, react-focus-lock, react-lazyload, react-slider and react-tabs 2021-05-10 22:24:22 +01:00
ta264
ebdc51b821 Updated react and react-dom packages 2021-05-10 22:24:22 +01:00
ta264
755068a44e Upgrade fontawesome, sentry, classnames, clipboard, filesize, fuse.js, jquery, lodash, mobile-detect, moment, qs, core-js 2021-05-10 22:24:22 +01:00
Mark McDowall
3f5668705c Update postcss packages 2021-05-10 22:24:22 +01:00
ta264
9571c992df Update linter packages 2021-05-10 22:24:22 +01:00
ta264
7b3f7bb582 Upgrade babel packages 2021-05-10 22:24:22 +01:00
Qstick
ab1928ee95 Update to webpack 5, remove gulp
Co-Authored-By: Mark McDowall <markus101@users.noreply.github.com>
(cherry picked from commit 4ef2174226a0210f756f180dded8567d659589e2)
(cherry picked from commit 06730363c0fe1e2bf376122f50943fbf90ab60e8)
(cherry picked from commit 533f1a61d52cc5d2b7c879de4b2291dbad833cbf)
2021-05-10 22:24:22 +01:00
Mark McDowall
7d4c92cc96 Fixed files that were using incorrect imports
(cherry picked from commit a3bb2f1c32fc1e0c49d0d1fe24c04940453f5431)
(cherry picked from commit 9ebdc5364c69971fde5a0c854a2d46d6b027fd5f)
2021-05-10 22:20:27 +01:00
ta264
00e4555736 New: Add min pages and ignored terms to metadata profiles 2021-05-10 21:54:50 +01:00
servarr[bot]
23142a59d7 New: Add rel="noreferrer" to all external links
* New: Add rel="noreferrer" to all external links

(cherry picked from commit c722e9112496062313d09df16b169873f910f2a1)

* Update Link.js

Co-authored-by: Mark McDowall <mark@mcdowall.ca>
Co-authored-by: Qstick <qstick@gmail.com>
2021-05-10 23:48:38 -04:00
Qstick
5dfd8d1a50 Github workflow for Issue lock and Support bot 2021-05-08 17:44:38 -04:00
Robin Dadswell
f45cc29816 Fixed: Import Lists provider message in UI 2021-05-08 15:40:56 -04:00
ta264
fd291aeb96 Ignore update test temporarily 2021-05-07 19:41:49 +01:00
ta264
11577b6db9 Fixed: Identification failing if book metadata has no authors 2021-05-07 18:54:33 +01:00
ta264
146fe04cce Fix bulk queue and blacklisk endpoints 2021-05-05 21:23:15 +01:00
bakerboy448
4a7b14fa39 Update indexer category help text … (and make cats advanced)
closes #979

(cherrypicked from sonarr 8d2d9078ff8f6daf50aef2dded3f96dae93252cc)
2021-05-05 09:30:24 +01:00
bakerboy448
8748b18ae9 discord link fix 2021-05-05 09:29:55 +01:00
bakerboy448
3b84b5da1d update bug template [skip ci]
closes #919
2021-05-05 09:29:31 +01:00
Anonymous
d57ef9805d Translated using Weblate (French) [skip ci]
Currently translated at 67.0% (464 of 692 strings)

Translation: Servarr/Readarr
Translate-URL: https://translate.servarr.com/projects/servarr/readarr/fr/
2021-05-01 20:37:27 -04:00
Anonymous
54f336f3fd Translated using Weblate (Polish) [skip ci]
Currently translated at 66.9% (463 of 692 strings)

Translation: Servarr/Readarr
Translate-URL: https://translate.servarr.com/projects/servarr/readarr/pl/
2021-05-01 20:37:27 -04:00
Anonymous
7a129a4f89 Translated using Weblate (Vietnamese) [skip ci]
Currently translated at 66.9% (463 of 692 strings)

Translation: Servarr/Readarr
Translate-URL: https://translate.servarr.com/projects/servarr/readarr/vi/
2021-05-01 20:37:27 -04:00
Anonymous
877f40116a Translated using Weblate (Chinese (Simplified) (zh_CN)) [skip ci]
Currently translated at 66.0% (457 of 692 strings)

Translation: Servarr/Readarr
Translate-URL: https://translate.servarr.com/projects/servarr/readarr/zh_CN/
2021-05-01 20:37:27 -04:00
Anonymous
aa043a53b1 Translated using Weblate (Arabic) [skip ci]
Currently translated at 67.0% (464 of 692 strings)

Translation: Servarr/Readarr
Translate-URL: https://translate.servarr.com/projects/servarr/readarr/ar/
2021-05-01 20:37:27 -04:00
Anonymous
51e25b2a3c Translated using Weblate (Korean) [skip ci]
Currently translated at 66.9% (463 of 692 strings)

Translation: Servarr/Readarr
Translate-URL: https://translate.servarr.com/projects/servarr/readarr/ko/
2021-05-01 20:37:27 -04:00
Anonymous
99cedbf96e Translated using Weblate (Swedish) [skip ci]
Currently translated at 66.9% (463 of 692 strings)

Translation: Servarr/Readarr
Translate-URL: https://translate.servarr.com/projects/servarr/readarr/sv/
2021-05-01 20:37:27 -04:00
Anonymous
97e6a353fa Translated using Weblate (Romanian) [skip ci]
Currently translated at 66.9% (463 of 692 strings)

Translation: Servarr/Readarr
Translate-URL: https://translate.servarr.com/projects/servarr/readarr/ro/
2021-05-01 20:37:27 -04:00
Qstick
5996f4d000 Translated using Weblate (Spanish) [skip ci]
Currently translated at 67.0% (464 of 692 strings)

Translation: Servarr/Readarr
Translate-URL: https://translate.servarr.com/projects/servarr/readarr/es/
2021-05-01 20:37:27 -04:00
Anonymous
2067f655bd Translated using Weblate (Spanish) [skip ci]
Currently translated at 67.0% (464 of 692 strings)

Translation: Servarr/Readarr
Translate-URL: https://translate.servarr.com/projects/servarr/readarr/es/
2021-05-01 20:37:27 -04:00
Anonymous
3798ff19e5 Translated using Weblate (Japanese) [skip ci]
Currently translated at 66.9% (463 of 692 strings)

Translation: Servarr/Readarr
Translate-URL: https://translate.servarr.com/projects/servarr/readarr/ja/
2021-05-01 20:37:27 -04:00
Anonymous
273680aed6 Translated using Weblate (German) [skip ci]
Currently translated at 67.0% (464 of 692 strings)

Translation: Servarr/Readarr
Translate-URL: https://translate.servarr.com/projects/servarr/readarr/de/
2021-05-01 20:37:27 -04:00
Anonymous
2421c57bac Translated using Weblate (Icelandic) [skip ci]
Currently translated at 66.9% (463 of 692 strings)

Translation: Servarr/Readarr
Translate-URL: https://translate.servarr.com/projects/servarr/readarr/is/
2021-05-01 20:37:27 -04:00
Anonymous
d6d41ac20d Translated using Weblate (Hindi) [skip ci]
Currently translated at 66.9% (463 of 692 strings)

Translation: Servarr/Readarr
Translate-URL: https://translate.servarr.com/projects/servarr/readarr/hi/
2021-05-01 20:37:27 -04:00
Anonymous
5de68ad8d4 Translated using Weblate (Hebrew) [skip ci]
Currently translated at 66.9% (463 of 692 strings)

Translation: Servarr/Readarr
Translate-URL: https://translate.servarr.com/projects/servarr/readarr/he/
2021-05-01 20:37:27 -04:00
Anonymous
089e327596 Translated using Weblate (Italian) [skip ci]
Currently translated at 66.6% (461 of 692 strings)

Translation: Servarr/Readarr
Translate-URL: https://translate.servarr.com/projects/servarr/readarr/it/
2021-05-01 20:37:27 -04:00
Anonymous
bddf4d9801 Translated using Weblate (Russian) [skip ci]
Currently translated at 66.9% (463 of 692 strings)

Translation: Servarr/Readarr
Translate-URL: https://translate.servarr.com/projects/servarr/readarr/ru/
2021-05-01 20:37:27 -04:00
Anonymous
88a8f33bdb Translated using Weblate (Bulgarian) [skip ci]
Currently translated at 66.9% (463 of 692 strings)

Translation: Servarr/Readarr
Translate-URL: https://translate.servarr.com/projects/servarr/readarr/bg/
2021-05-01 20:37:27 -04:00
Anonymous
a2668edabf Translated using Weblate (Czech) [skip ci]
Currently translated at 66.9% (463 of 692 strings)

Translation: Servarr/Readarr
Translate-URL: https://translate.servarr.com/projects/servarr/readarr/cs/
2021-05-01 20:37:27 -04:00
Anonymous
c435b3853b Translated using Weblate (Portuguese) [skip ci]
Currently translated at 66.6% (461 of 692 strings)

Translation: Servarr/Readarr
Translate-URL: https://translate.servarr.com/projects/servarr/readarr/pt/
2021-05-01 20:37:27 -04:00
Anonymous
c226abf184 Translated using Weblate (Turkish) [skip ci]
Currently translated at 66.9% (463 of 692 strings)

Translation: Servarr/Readarr
Translate-URL: https://translate.servarr.com/projects/servarr/readarr/tr/
2021-05-01 20:37:27 -04:00
Anonymous
ca5fbf7cc3 Translated using Weblate (Danish) [skip ci]
Currently translated at 66.9% (463 of 692 strings)

Translation: Servarr/Readarr
Translate-URL: https://translate.servarr.com/projects/servarr/readarr/da/
2021-05-01 20:37:27 -04:00
Anonymous
395bb7cacf Translated using Weblate (Thai) [skip ci]
Currently translated at 66.9% (463 of 692 strings)

Translation: Servarr/Readarr
Translate-URL: https://translate.servarr.com/projects/servarr/readarr/th/
2021-05-01 20:37:27 -04:00
Anonymous
13e06ffcfd Translated using Weblate (Hungarian) [skip ci]
Currently translated at 88.4% (612 of 692 strings)

Translation: Servarr/Readarr
Translate-URL: https://translate.servarr.com/projects/servarr/readarr/hu/
2021-05-01 20:37:27 -04:00
Csaba
4b3bb051a2 Translated using Weblate (Hungarian) [skip ci]
Currently translated at 88.4% (612 of 692 strings)

Translation: Servarr/Readarr
Translate-URL: https://translate.servarr.com/projects/servarr/readarr/hu/
2021-05-01 20:37:27 -04:00
Anonymous
68a7417760 Translated using Weblate (Finnish) [skip ci]
Currently translated at 66.9% (463 of 692 strings)

Translation: Servarr/Readarr
Translate-URL: https://translate.servarr.com/projects/servarr/readarr/fi/
2021-05-01 20:37:27 -04:00
Anonymous
1deb9d576f Translated using Weblate (Dutch) [skip ci]
Currently translated at 66.9% (463 of 692 strings)

Translation: Servarr/Readarr
Translate-URL: https://translate.servarr.com/projects/servarr/readarr/nl/
2021-05-01 20:37:27 -04:00
Anonymous
3fa656433c Translated using Weblate (Greek) [skip ci]
Currently translated at 66.9% (463 of 692 strings)

Translation: Servarr/Readarr
Translate-URL: https://translate.servarr.com/projects/servarr/readarr/el/
2021-05-01 20:37:27 -04:00
Anonymous
a1e90bef2e Translated using Weblate (Portuguese (Brazil)) [skip ci]
Currently translated at 66.7% (462 of 692 strings)

Translation: Servarr/Readarr
Translate-URL: https://translate.servarr.com/projects/servarr/readarr/pt_BR/
2021-05-01 20:37:27 -04:00
Qstick
63b8dcc2e3 Consistent node ver between build_frontend and lint_frontend 2021-04-30 00:20:55 -04:00
Anonymous
12588e8e08 Translated using Weblate (Vietnamese) [skip ci]
Currently translated at 65.7% (455 of 692 strings)

Translation: Servarr/Readarr
Translate-URL: https://translate.servarr.com/projects/servarr/readarr/vi/
2021-04-30 02:09:28 +00:00
Weblate
aab35b1a48 Added translation using Weblate (Chinese (Simplified) (zh_CN)) [skip ci] 2021-04-30 02:02:48 +00:00
Weblate
0792e7c016 Added translation using Weblate (Portuguese (Brazil)) [skip ci] 2021-04-30 02:01:45 +00:00
Weblate
ed12e2e534 Added translation using Weblate (Portuguese) [skip ci] 2021-04-30 02:00:55 +00:00
Anonymous
5cd8574792 Translated using Weblate (Korean) [skip ci]
Currently translated at 65.7% (455 of 692 strings)

Translation: Servarr/Readarr
Translate-URL: https://translate.servarr.com/projects/servarr/readarr/ko/
2021-04-30 01:58:29 +00:00
Anonymous
2c5c7789dc Translated using Weblate (Italian) [skip ci]
Currently translated at 65.3% (452 of 692 strings)

Translation: Servarr/Readarr
Translate-URL: https://translate.servarr.com/projects/servarr/readarr/it/
2021-04-30 01:55:27 +00:00
Anonymous
bbcc26f489 Translated using Weblate (Hungarian) [skip ci]
Currently translated at 65.8% (456 of 692 strings)

Translation: Servarr/Readarr
Translate-URL: https://translate.servarr.com/projects/servarr/readarr/hu/
2021-04-30 01:53:00 +00:00
Qstick
d61106aee8 Added translation using Weblate (Vietnamese) [skip ci] 2021-04-30 01:52:37 +00:00
Qstick
c7b11a83b9 Added translation using Weblate (Turkish) [skip ci] 2021-04-30 01:51:58 +00:00
Qstick
1a1d99f206 Added translation using Weblate (Thai) [skip ci] 2021-04-30 01:51:05 +00:00
Anonymous
ed44f83e27 Translated using Weblate (Arabic) [skip ci]
Currently translated at 66.9% (463 of 692 strings)

Translation: Servarr/Readarr
Translate-URL: https://translate.servarr.com/projects/servarr/readarr/ar/
2021-04-30 01:50:13 +00:00
Qstick
e2f380df25 Added translation using Weblate (Swedish) [skip ci] 2021-04-30 01:50:11 +00:00
Qstick
d2e2f68cf9 Added translation using Weblate (Russian) [skip ci] 2021-04-30 01:49:31 +00:00
Qstick
694cd1b178 Added translation using Weblate (Romanian) [skip ci] 2021-04-30 01:49:01 +00:00
Qstick
5b1277daff Added translation using Weblate (Polish) [skip ci] 2021-04-30 01:48:30 +00:00
Qstick
4b31a143a9 Added translation using Weblate (Korean) [skip ci] 2021-04-30 01:47:57 +00:00
Qstick
36f718dde5 Added translation using Weblate (Japanese) [skip ci] 2021-04-30 01:47:18 +00:00
Qstick
82f477c239 Added translation using Weblate (Italian) [skip ci] 2021-04-30 01:46:47 +00:00
Qstick
c5e51248d2 Added translation using Weblate (Icelandic) [skip ci] 2021-04-30 01:46:12 +00:00
Qstick
d4fb6824b6 Added translation using Weblate (Hungarian) [skip ci] 2021-04-30 01:45:32 +00:00
Qstick
cb3cdd6980 Added translation using Weblate (Hindi) [skip ci] 2021-04-30 01:44:59 +00:00
Anonymous
d441385a74 Translated using Weblate (Greek) [skip ci]
Currently translated at 66.0% (457 of 692 strings)

Translation: Servarr/Readarr
Translate-URL: https://translate.servarr.com/projects/servarr/readarr/el/
2021-04-30 01:42:29 +00:00
Qstick
1b680d9faa Added translation using Weblate (Hebrew) [skip ci] 2021-04-30 01:42:10 +00:00
Qstick
0d6119a804 Added translation using Weblate (French) [skip ci] 2021-04-30 01:41:24 +00:00
Anonymous
c26b787b6f Translated using Weblate (Arabic) [skip ci]
Currently translated at 66.9% (463 of 692 strings)

Translation: Servarr/Readarr
Translate-URL: https://translate.servarr.com/projects/servarr/readarr/ar/
2021-04-30 01:38:27 +00:00
Qstick
482815629b Added translation using Weblate (Finnish) [skip ci] 2021-04-30 01:38:25 +00:00
Qstick
19ad139cf8 Added translation using Weblate (Greek) [skip ci] 2021-04-30 01:37:54 +00:00
Qstick
c18f41e81c Added translation using Weblate (German) [skip ci] 2021-04-30 01:37:23 +00:00
Anonymous
6826ff1d4b Translated using Weblate (Arabic) [skip ci]
Currently translated at 66.0% (457 of 692 strings)

Translation: Servarr/Readarr
Translate-URL: https://translate.servarr.com/projects/servarr/readarr/ar/
2021-04-30 01:34:45 +00:00
Qstick
22a17201ff Added translation using Weblate (Danish) [skip ci] 2021-04-30 01:34:28 +00:00
Qstick
ddf5ec5f66 Added translation using Weblate (Czech) [skip ci] 2021-04-30 01:33:16 +00:00
Qstick
26cd77f1c5 Added translation using Weblate (Bulgarian) [skip ci] 2021-04-30 01:32:25 +00:00
Qstick
20e1874787 Added translation using Weblate (Arabic) [skip ci] 2021-04-30 01:31:50 +00:00
Qstick
a85d25cb82 Added translation using Weblate (Spanish) [skip ci] 2021-04-30 01:23:15 +00:00
Qstick
afb9f3809f Added translation using Weblate (Dutch) 2021-04-29 12:17:51 +00:00
nitsua
d87bf5ae63 Localization framework 2021-04-29 08:58:43 +01:00
ta264
144134446d Fail build on BSD integration test fails
(cherry picked from commit aa07f3039e039387a54acabed2acdcd7d6d4830d)
2021-04-27 20:07:18 +01:00
ta264
23f2bbc700 Don't start integration tests too soon
(cherry picked from commit b284f40716269dc4f641323b8293f40dfda8a424)
(cherry picked from commit d549975d96f04cc6b197d719d8389110cb24b8e6)
2021-04-27 20:07:00 +01:00
ta264
a8388e12c1 Remove legacy code that accessed config.xml without a lock
(cherry picked from commit 7a4adea14ae18a5204226237eded56005a76893e)
(cherry picked from commit ba9aab1640358b4097997b6cd32e2edc36a5f696)
2021-04-27 20:02:40 +01:00
bakerboy448
4f40ad0480 New: ISO 8601 date format in log files
Closes #985
2021-04-24 22:43:03 -04:00
ta264
27bc97a1a6 Reinstate missing login mapper 2021-04-21 21:44:47 +01:00
ta264
73f81465e9 Fixed: Forms login page uses urlbase for logo
(cherry picked from commit e0f30c3eaeab7d863b4b4104a9f0d501bd693f69)
2021-04-20 21:58:20 +01:00
ta264
a8c91f2bc8 Fixed: Forms login with urlbase
(cherry picked from commit 811a9d4c6123643e0a2ae26c1ccf06717cb5f47b)
2021-04-20 21:58:11 +01:00
ta264
4b0586bd3d Fixed: UI not updating on upgrade
(cherry picked from commit d566c1efd42f9a94c524db311e8fa99bc6e0323f)
2021-04-20 21:57:24 +01:00
ta264
8f3f90d407 Tidy conversion to aspnetcore
(cherry picked from commit 490f6e2e6aa3f220cc98f257a3ca3b2bea48fb80)
2021-04-20 21:21:42 +01:00
ta264
47b23417e0 Fixed: Memory leak
Explicitly register concrete types as transient

(cherry picked from commit f097d30b095d89681eb1aede2e88c4fdefcab516)
2021-04-20 21:21:32 +01:00
ta264
5a9d75857d Fixed: Rogue scrollbar on large screens 2021-04-12 21:30:44 +01:00
ta264
2691cb3fce Fixed: Misaligned Book/Author details on large screens 2021-04-12 21:14:02 +01:00
ta264
9d77337726 Fixed: Retriggering marquee titles 2021-04-12 21:11:15 +01:00
ta264
5192c76717 New: Swipe left/right to navigate authors/books on mobile 2021-04-12 19:49:32 +01:00
ta264
f884a2689a Rework marquee 2021-04-12 19:49:32 +01:00
ta264
5714f1c913 Fix prop validation 2021-04-12 19:49:32 +01:00
ta264
a84725f867 Fixed: Author/Book overview overflows
Bump react-measure to v2.5.2
2021-04-12 19:49:32 +01:00
ta264
106a1c339b New: Scroll long book titles 2021-04-12 19:49:32 +01:00
ta264
72de94308b Fixed: Setting file dates
Fixes #940
2021-04-12 19:49:32 +01:00
ta264
400f77584d New: Add details and delete buttons to file editor table 2021-04-12 19:49:32 +01:00
ta264
427f76fbe0 Fixed: Better formatting of the author books page 2021-04-12 19:49:32 +01:00
ta264
8a7765c855 Fixed: Only pick up supported formats from calibre 2021-04-12 19:49:32 +01:00
ta264
fea34add4b New: Support kepub (as epub) 2021-04-12 19:49:32 +01:00
ta264
399ee8d2e7 Fixed: Book files disappearing 2021-04-12 19:49:32 +01:00
ta264
3940d4aa28 Fixed: Run import identification even for unparsable releases 2021-04-12 19:49:32 +01:00
ta264
f0742e3750 Fixed: File change history appearing against wrong book 2021-04-12 19:49:32 +01:00
ta264
41f5f0f2d4 New: Search for new editions from goodreads when identifying 2021-04-12 19:49:32 +01:00
ta264
c1f2ea6c8a Fixed: Don't distort author images in search bar results 2021-04-12 19:49:32 +01:00
ta264
2d7aa20448 New: Auto rescan on remote path mapping change for Calibre 2021-04-12 19:49:32 +01:00
ta264
9be948b7cc Fixed: Parse series for search results 2021-04-12 19:49:32 +01:00
ta264
110e867bd3 New: Show book file details in interactive import and unmapped files 2021-04-12 19:49:32 +01:00
ta264
9f37b1c484 New: Get more candidates and include ISBN/ASIN in distance calculation 2021-04-12 19:49:32 +01:00
ta264
d078dacaab New: Cache searches for 5 days 2021-04-12 19:49:32 +01:00
ta264
996841db45 Fixed: Slow initial author load 2021-04-12 19:49:32 +01:00
ta264
404da4ae22 Fixed: Handle parsing books with multiple authors properly 2021-04-12 19:49:32 +01:00
ta264
fa25324463 Fixed: Refreshing / Deleting Series 2021-04-12 19:49:32 +01:00
ta264
652fdae7d9 New: Use ISBN / ASIN in preference to goodreads id 2021-04-12 19:49:32 +01:00
ta264
6a61702a91 Fixed: Ensure correct book edition is in the database before importing book 2021-04-12 19:49:32 +01:00
ta264
8bc44f2a29 Fixed: Don't accidentally remove book editions when refreshing a book 2021-04-12 19:49:32 +01:00
ta264
8ec13f5ead Fixed: Better aggregation of calibre data 2021-04-12 19:49:32 +01:00
ta264
f584d2d8d2 New: Allow keeping calibre in sync with goodreads 2021-04-12 19:49:32 +01:00
ta264
7072b913a6 New: Allow retagging book files with calibre 2021-04-12 19:49:32 +01:00
ta264
e29b0c318e Fixed: Prevent renaming calibre files 2021-04-12 19:49:32 +01:00
ta264
dd341ef1e1 Fixed: Disable calibre duplicate check when adding new books 2021-04-12 19:49:32 +01:00
Jared Gesser
2bea1965c6 fix spelling, fix punctuation 2021-04-11 20:39:30 +01:00
njordan
0252617730 Add support for Flood 2021-04-07 13:19:44 +01:00
Robin Dadswell
f6e87858a8 New: Added logo to loading page 2021-04-07 12:05:03 +01:00
Mark McDowall
6fa56b8873 Fixed: Interactive import modal horizontal scrolling on Firefox mobile
(cherry picked from commit 6c505937dacfa82e965adaaac407c888a1caacce)
2021-04-02 00:24:32 -04:00
bakerboy448
c749a660e7 Fixed: Debatable typos in Naming Modal
(cherry picked from commit f2f1039c5ed5fdf42683d64a777cdac410e45fd6)
2021-03-28 19:22:47 -04:00
bakerboy448
e6992da18c Fixed: Cleanse Goodreads Token and Token Secret from Log Files 2021-03-23 12:54:03 +00:00
ta264
80e8d5e5e7 Secret Dev Settings Page 2021-03-22 21:34:39 +00:00
Taloth Saldono
61fdb6eba2 Fixed: Unnecessary idle cpu usage
(cherry picked from commit 5a69801877eb72899dd9867c39a1b88b7114fe5b)
(cherry picked from commit 4eb6cb9dae)
2021-03-22 21:34:38 +00:00
ta264
98611c7d02 New: Release parser improvements 2021-03-22 21:28:14 +00:00
ta264
ad2b3e5cc5 Fix tests 2021-03-19 19:28:58 +00:00
ta264
34bd1a5876 New: Warn if user tries to connect to a calibre-web server 2021-03-18 22:38:52 +00:00
ta264
b652cf9563 New: Support for deleting books from calibre 2021-03-18 21:54:07 +00:00
ta264
fa459ea7ac Fixed: Cutoff delete button in unmapped files 2021-03-17 21:03:33 +00:00
ta264
acb6fc01b3 Fixed: Gracefully handle Goodreads search error
Fixes #897
2021-03-17 21:35:42 +00:00
ta264
1e0e8adc77 More mono cleanup 2021-03-17 21:30:57 +00:00
ta264
35ab21ab04 Fixed: Marking history as failed 2021-03-17 21:25:06 +00:00
Taloth Saldono
d61daeac8e Generalized RateLimit logic to all indexers based on indexer id 2021-03-16 21:52:21 +00:00
Taloth Saldono
c8c37435be Fixed: Jackett indexer search performance 2021-03-16 21:52:21 +00:00
ta264
a6f8391f40 Fix deleting providers 2021-03-16 21:52:21 +00:00
ta264
025cb04035 Fixed: FolderWritable check for CIFS shares mounted in Unix
See https://github.com/dotnet/runtime/issues/42790

Implemented workaround in https://github.com/dotnet/runtime/issues/42790#issuecomment-700362617
2021-03-15 23:33:19 -04:00
Qstick
3fc60dc56d Run Analysis on Linux main build (leaving in failed state to test)
(cherry picked from commit be40a0d7381278c3225e9faa0230cb1dcceeb794)
2021-03-12 12:55:48 +00:00
ta264
f5b2acdbdf New: .NET 5.0.4 2021-03-12 12:55:48 +00:00
Mark McDowall
9a0b247c8f Use createHandleActions for adding/removing commands so itemMap is synced properly
(cherry picked from commit 99be6a7e4065b77c910df6444a468fedc23e90cc)
2021-03-12 12:55:48 +00:00
ta264
8c50b866ff Fixed: Adding book from list 2021-03-12 12:55:48 +00:00
ta264
d6170dbfed New: Use native dotnet host and DryIoc 2021-03-12 12:55:48 +00:00
ta264
58ddbcd77e New: Use ASP.NET Core instead of Nancy 2021-03-12 12:55:48 +00:00
ta264
d348232e0d Unused 2021-03-12 12:55:48 +00:00
ta264
940da91ca4 Disable new warning in 5.0.200
[common]
2021-03-12 12:55:48 +00:00
ta264
543fe6729a More mono cleanup 2021-03-12 12:55:48 +00:00
ta264
6bcede5064 Fix formatting 2021-03-12 12:55:48 +00:00
Taloth Saldono
fbfbe4a931 Cleanse more /home/username scenarios
(cherry picked from commit 23047623ee89944260b6073813ff1f4e5223be71)
2021-02-27 17:28:18 +00:00
Qstick
5a0d52aef9 Update Discord link on MoreInfo page 2021-02-26 12:42:04 -05:00
ta264
2bf86248af Fixed: Ensure SSL cert exists before saving config
Trap missing certificate exception to avoid bootloop

(cherry picked from commit 78c7372a0d64e15734b14b0ca9852ae7c0a47132)
2021-02-25 04:34:42 +00:00
Qstick
96cb26050b Update Readme Discord Link [skip ci] 2021-02-24 21:54:18 -05:00
ta264
32833b5fc4 Fixed: Parsing [book] by [author] 2021-02-12 19:47:15 +00:00
ta264
1cdcfe25c0 Fixed: Improve parser when release has colons in title 2021-02-12 19:41:18 +00:00
ta264
c61315b90e Fixed: Restart button in UI
Fixes #633
2021-02-11 22:06:16 +00:00
ta264
d22ca1fe4f Fixed: Adding book where the same author appears twice
Fixes #815
2021-02-12 16:04:15 +00:00
ta264
791bba471f Fixed: Trying to get book details from filename when already obtained from tags 2021-02-12 16:04:15 +00:00
ta264
446a0591db Fixed: Duplicates in wanted page 2021-02-12 16:04:15 +00:00
ta264
69773db77a Fixed: Errors converting to book resource 2021-02-12 16:04:15 +00:00
ta264
d51af026fb Move test harness packages to Directory.Build.props 2021-02-12 16:04:15 +00:00
ta264
fb130fd0e9 New: Drop mono support 2021-02-12 16:04:15 +00:00
ta264
760de88e7c New: .NET 5 support for FreeBSD 11+ 2021-02-12 16:04:15 +00:00
ta264
4a1b2af535 New: Log out body for bad API requests 2021-02-12 16:04:15 +00:00
ta264
5cc0331c75 Fixed: Adding indexers from presets 2021-02-12 16:04:15 +00:00
ta264
d3e8c7e0c9 New: Use System.Text.Json for Nancy and SignalR 2021-02-12 16:04:15 +00:00
Qstick
16b3817202 Fixed: Don't ignore default Boolean in db serialization 2021-02-12 16:04:15 +00:00
ta264
eaf46b0550 Hack: ignore wonky depedencies in signalR js client 2021-02-12 16:04:15 +00:00
ta264
da1686b53c New: Build with NET5 2021-02-12 16:04:15 +00:00
Mark McDowall
52337350d9 Fixed: Restoring backup from zip file on disk
(cherry picked from commit 5960035d5d660a923e11b0300833b60c64271522)
2021-02-08 13:02:52 -05:00
Mark McDowall
faf8fbbc2a Update column properties when restoring persisted state
(cherry picked from commit 653db8290e0a7737348d911d322c4218c3b5b677)
2021-02-07 20:41:40 -05:00
Mark McDowall
e6ceafe0b7 Fixed: Table column order resetting after refresh
#4297

(cherry picked from commit 044cb563a6488c16916ea7617d1f91404330b06f)
2021-02-07 15:35:17 -05:00
Qstick
cf42e02586 Fixed: Settings fields being altered during save
(cherry picked from commit dd61480d60e067e851982b0cc98f03f752b80673)
2021-02-07 10:15:28 -05:00
Qstick
93eca6a749 Fix DownloadStation integration in DSM 7
(cherry picked from commit b0753ab153a79203de10a928e99fcec5b6a7c895)
2021-02-07 09:20:54 -05:00
bakerboy448
d7813c2255 bump chrome driver from 86 to 88
[common]
2021-02-06 16:05:31 -05:00
bakerboy448
239240768b update FR template [skip ci] 2021-02-06 11:23:49 -05:00
Taloth Saldono
36b8df87d2 Fixed: Validation of new qbittorrent max-ratio action config
(cherry picked from commit d1c3ae17491726320c58561548a21d83dae7fe7d)
2021-02-04 23:31:25 -05:00
Qstick
7c14bd1c80 Add crossorigin use-credentials attribute to manifest tag
(cherry picked from commit 70e4324a7c6a78e893a5c135cdfdedbb2398b892)
2021-02-04 23:24:11 -05:00
bakerboy448
fd81ca86db New: Update all wiki links to point to the Servarr Wiki 2021-02-03 22:41:59 -05:00
Qstick
42262877b0 Quick fix for Queue sort by Author SortName
Signed-off-by: bakerboy448 <55419169+bakerboy448@users.noreply.github.com>
2021-01-30 18:11:14 -05:00
Mark McDowall
843e46f890 New: Renamed Quick Import to Move Automatically 2021-01-30 15:07:59 -05:00
bakerboy448
5ae4899ff2 update bug template [skip ci]
Closes #751
2021-01-30 15:07:19 -05:00
Qstick
bc4aed17a2 Revert "Fix GoodReads Search Test, New First Book"
This reverts commit c8263fd856.
2021-01-30 13:38:23 -05:00
Qstick
8adf67ed5a Fixed: Prevent Bookshelf error if items is undefined 2021-01-30 13:14:30 -05:00
Qstick
7cc9a67b74 Fixed: PendingRelese deletes wrong value on author delete 2021-01-28 23:43:19 -05:00
Qstick
c8263fd856 Fix GoodReads Search Test, New First Book 2021-01-28 23:43:19 -05:00
ta264
d7cdbbecc8 Fixed: Set musl status at compile time 2021-01-28 23:43:19 -05:00
ta264
64e2f6457d Fixed: Goodreads bookshelves not shown after authentication 2021-01-28 23:43:19 -05:00
Qstick
93ba5ade9e Cleanup Conflicts in Sonarr/Lidarr Pulls
Co-Authored-By: Robin Dadswell <19610103+RobinDadswell@users.noreply.github.com>
2021-01-28 23:43:19 -05:00
ta264
ffc12656ee Add SortKey validation
Signed-off-by: Robin Dadswell <robin@dadswell.email>
2021-01-28 23:43:19 -05:00
Qstick
1e735da9f1 Fixed: False Positives for RemotePath check with Deluge
Signed-off-by: Robin Dadswell <robin@dadswell.email>
2021-01-28 23:43:19 -05:00
Mark McDowall
96072e61e0 Fixed: Show TLS errors in UI when testing download clients
Signed-off-by: Robin Dadswell <robin@dadswell.email>
2021-01-28 23:43:19 -05:00
Qstick
b05b8c9e7a Resource missing from Gotify call
Signed-off-by: Robin Dadswell <robin@dadswell.email>
2021-01-28 23:43:19 -05:00
Qstick
f73b9491ad Gotify token as query parameter
Signed-off-by: Robin Dadswell <robin@dadswell.email>
2021-01-28 23:43:19 -05:00
Qstick
7842acc76e Convert Notifications from RestSharp to HttpClient
Signed-off-by: Robin Dadswell <robin@dadswell.email>
2021-01-28 23:43:19 -05:00
Qstick
ad11ef9d2a Fixed: Manual Import Fails on failed Import Items
Signed-off-by: Robin Dadswell <robin@dadswell.email>
2021-01-28 23:43:19 -05:00
Taloth Saldono
70c572534a Fixed binary execute permissions for osx and Radarr
Signed-off-by: Robin Dadswell <robin@dadswell.email>
2021-01-28 23:43:19 -05:00
Taloth Saldono
4236afe850 Fixed disk permission tests
Signed-off-by: Robin Dadswell <robin@dadswell.email>
2021-01-28 23:43:19 -05:00
Taloth Saldono
1ff5d814f4 New: Displaying folder-based permissions in UI rather than file-based permissions and with selectable sane presets
Fixed: Preserve setgid when applying unix permissions
Signed-off-by: Robin Dadswell <robin@dadswell.email>
2021-01-28 23:43:19 -05:00
Taloth Saldono
7921a228ad Readded 0 cat to the end of the Newznab list
Signed-off-by: Robin Dadswell <robin@dadswell.email>
2021-01-28 23:43:19 -05:00
Qstick
3af8051e3c Improve use of All() for Path related queries
Signed-off-by: Robin Dadswell <robin@dadswell.email>
2021-01-28 23:43:19 -05:00
Qstick
cf0439d4c5 Mass Editor size and options
Co-Authored-By: Mark McDowall <markus101@users.noreply.github.com>
Signed-off-by: Robin Dadswell <robin@dadswell.email>
2021-01-28 23:43:19 -05:00
Qstick
5176bdc786 Fixed: Size on disk sorting and display
Co-Authored-By: Mark McDowall <markus101@users.noreply.github.com>
Signed-off-by: Robin Dadswell <robin@dadswell.email>
2021-01-28 23:43:19 -05:00
Mark McDowall
0c8ad37a8f New: Differentiate between short term and long term (more than 6 hours) indexer failures
Signed-off-by: Robin Dadswell <robin@dadswell.email>
2021-01-28 23:43:19 -05:00
Qstick
4fe6029be7 Fixed: (Windows) clean up extraneous files in build folder during installation
Co-Authored-By: Mark McDowall <markus101@users.noreply.github.com>
Signed-off-by: Robin Dadswell <robin@dadswell.email>
2021-01-28 23:43:19 -05:00
Mark McDowall
05bc9f11ee New: Bulk remove from Blacklist
Signed-off-by: Robin Dadswell <robin@dadswell.email>
2021-01-28 23:43:19 -05:00
Qstick
267bdb4cdf New: Show .net version in UI
Signed-off-by: Robin Dadswell <robin@dadswell.email>
2021-01-28 23:43:19 -05:00
Mark McDowall
6d9126bca4 Fixed: Cleanse account and passwd from Download Station URLs
Signed-off-by: Robin Dadswell <robin@dadswell.email>
2021-01-28 23:43:19 -05:00
Mark McDowall
8853f1cfb3 Fixed: Webhooks using lower case event types (in the future this could change)
Signed-off-by: Robin Dadswell <robin@dadswell.email>
2021-01-28 23:43:19 -05:00
Mark McDowall
5afe37e929 New: Health events for Webhooks
Signed-off-by: Robin Dadswell <robin@dadswell.email>
2021-01-28 23:43:19 -05:00
Qstick
755fec154b Fixed: Failing file copy when running in docker on synology with btrfs
Co-Authored-By: Taloth <Taloth@users.noreply.github.com>
Signed-off-by: Robin Dadswell <robin@dadswell.email>
2021-01-28 23:43:19 -05:00
Taloth Saldono
2d1573251b Fixed: Regression causing updater to fail (manual update required if on 3.0.3.971, see forums)
Signed-off-by: Robin Dadswell <robin@dadswell.email>
2021-01-28 23:43:19 -05:00
Taloth Saldono
da5cdd6661 Fixed: Dataloss when moving series folder to root folder with only different casing
Signed-off-by: Robin Dadswell <robin@dadswell.email>
2021-01-28 23:43:19 -05:00
Taloth
7f64162a7a New: Newznab/Torznab categories dropdown with indexer provided category names
Signed-off-by: Robin Dadswell <robin@dadswell.email>
2021-01-28 23:43:19 -05:00
Taloth Saldono
b4289664dc Handle ratelimit api response for newznab caps endpoint on certain newznab indexers that have caps behind the apikey
Signed-off-by: Robin Dadswell <robin@dadswell.email>
2021-01-28 23:43:19 -05:00
Qstick
4fb13a64b1 Fixed: Preview rename tip wording
Co-Authored-By: Mark McDowall <markus101@users.noreply.github.com>
Signed-off-by: Robin Dadswell <robin@dadswell.email>
2021-01-28 23:43:19 -05:00
Qstick
a06ceeb31e Fixed: Artist/Album navigation buttons hidden with some titles
Co-Authored-By: Mark McDowall <markus101@users.noreply.github.com>
Signed-off-by: Robin Dadswell <robin@dadswell.email>
2021-01-28 23:43:19 -05:00
Qstick
18a9f69f60 Fixed: Links and already added icons overflowing on add artist/album search results
Co-Authored-By: Mark McDowall <markus101@users.noreply.github.com>
Signed-off-by: Robin Dadswell <robin@dadswell.email>
2021-01-28 23:43:19 -05:00
Taloth Saldono
36685edd49 Fixed: Exception when parsing Quality in release title with colon
Signed-off-by: Robin Dadswell <robin@dadswell.email>
2021-01-28 23:43:19 -05:00
Qstick
e4ca35d2d2 Fixed: Long paths overflowing in artist history
Co-Authored-By: Mark McDowall <markus101@users.noreply.github.com>
Signed-off-by: Robin Dadswell <robin@dadswell.email>
2021-01-28 23:43:19 -05:00
Mark McDowall
c534ab570f Don't process queue item without details
Signed-off-by: Robin Dadswell <robin@dadswell.email>
2021-01-28 23:43:19 -05:00
Mark McDowall
1f88450045 Fixed: Show more information in UI when testing SAB fails in some cases
Signed-off-by: Robin Dadswell <robin@dadswell.email>
2021-01-28 23:43:19 -05:00
Ryan
b15ab3ae27 Fixed: Typo/unclear text in backup retention
Signed-off-by: Robin Dadswell <robin@dadswell.email>
2021-01-28 23:43:19 -05:00
Taloth Saldono
360e68a793 Remove stacktrace if hardlink resulted in EXDEV.
Signed-off-by: Robin Dadswell <robin@dadswell.email>
2021-01-28 23:43:19 -05:00
Taloth Saldono
4aebf02d14 Fixed: Performance of symbolic link detection and infinite recursion
Signed-off-by: Robin Dadswell <robin@dadswell.email>
2021-01-28 23:43:19 -05:00
Taloth Saldono
7002628514 New: Fast copy using reflink on btrfs volumes
Signed-off-by: Robin Dadswell <robin@dadswell.email>
2021-01-28 23:43:19 -05:00
Taloth Saldono
4af4d45873 Fixed: Removed hardlink-based transactional file transfer logic (instead relying on explicit copy+delete for cifs)
Signed-off-by: Robin Dadswell <robin@dadswell.email>
2021-01-28 23:43:19 -05:00
Qstick
fe85e18a62 Fixed: Sorting of queue by artist title when unknown items are included
Co-Authored-By: Mark McDowall <markus101@users.noreply.github.com>
Signed-off-by: Robin Dadswell <robin@dadswell.email>
2021-01-28 23:43:19 -05:00
Qstick
4d2781d128 Moved Windows-only Permission function to Lidarr.Windows
Co-Authored-By: Taloth <Taloth@users.noreply.github.com>
Signed-off-by: Robin Dadswell <robin@dadswell.email>
2021-01-28 23:43:19 -05:00
Qstick
5a49fb9a14 fix modifiers for various classes
Co-Authored-By: Taloth <Taloth@users.noreply.github.com>
Signed-off-by: Robin Dadswell <robin@dadswell.email>
2021-01-28 23:43:19 -05:00
Mark McDowall
5ca32a7e84 Fixed: Indexer being disabled due to download client rejecting it
Signed-off-by: Robin Dadswell <robin@dadswell.email>
2021-01-28 23:43:19 -05:00
Taloth Saldono
d6634e7da1 Added PrivacyLevel option to FieldDefinition for later usage
Signed-off-by: Robin Dadswell <robin@dadswell.email>
2021-01-28 23:43:19 -05:00
Taloth Saldono
a6ec2f5367 Added MultiSelect input control for provider settings
Signed-off-by: Robin Dadswell <robin@dadswell.email>
2021-01-28 23:43:19 -05:00
Taloth Saldono
459dcc2ed6 New: Added FileList.io indexer support
Signed-off-by: Robin Dadswell <robin@dadswell.email>
2021-01-28 23:43:19 -05:00
Arthur Bols
acd5796d87 New: Removed chown and simplified chmod options for linux/osx
Signed-off-by: Robin Dadswell <robin@dadswell.email>
2021-01-28 23:43:19 -05:00
Taloth Saldono
e18f4bb71c Allow inline markdown in the changelog for linking to wiki
Signed-off-by: Robin Dadswell <robin@dadswell.email>
2021-01-28 23:43:19 -05:00
Qstick
051af8a9a6 Fixed: Not removing seeded download if it was manual imported in some cases
Co-Authored-By: Mark McDowall <markus101@users.noreply.github.com>
Signed-off-by: Robin Dadswell <robin@dadswell.email>
2021-01-28 23:43:19 -05:00
Mark McDowall
47f9572f83 Fixed: Rejections custom filter for Interactive Search (now Rejections Count)
Signed-off-by: Robin Dadswell <robin@dadswell.email>
2021-01-28 23:43:19 -05:00
Qstick
4ca774182a Improve root folder health check
Co-Authored-By: Mark McDowall <markus101@users.noreply.github.com>
Signed-off-by: Robin Dadswell <robin@dadswell.email>
2021-01-28 23:43:19 -05:00
Skyler Mäntysaari
53527c518b New: SendGrid Notifications
Signed-off-by: Robin Dadswell <robin@dadswell.email>
2021-01-28 23:43:19 -05:00
Qstick
84ff9abf44 Fixed: Added .org to website url filtering in parser
Co-Authored-By: Taloth <Taloth@users.noreply.github.com>
Signed-off-by: Robin Dadswell <robin@dadswell.email>
2021-01-28 23:43:19 -05:00
Qstick
ceef604201 Fixed recursion issue when emptying recycle bin
Co-Authored-By: Taloth <Taloth@users.noreply.github.com>
Signed-off-by: Robin Dadswell <robin@dadswell.email>
2021-01-28 23:43:19 -05:00
Qstick
6b8d2a60a7 Fixed: Tag details list series in alphabetical order
Co-Authored-By: Mark McDowall <markus101@users.noreply.github.com>
Signed-off-by: Robin Dadswell <robin@dadswell.email>
2021-01-28 23:43:19 -05:00
Qstick
3b656e05a2 Added UserAgent to api request trace log
Co-Authored-By: Taloth <Taloth@users.noreply.github.com>
Signed-off-by: Robin Dadswell <robin@dadswell.email>
2021-01-28 23:43:19 -05:00
Qstick
a9e03ed1cc New: Add DownloadClient and DownloadId to Webhook notifications
Co-Authored-By: Mark McDowall <markus101@users.noreply.github.com>
Signed-off-by: Robin Dadswell <robin@dadswell.email>
2021-01-28 23:43:19 -05:00
Qstick
bb504ec275 Clarify that Post-Import Category torrents are not monitored by Sonarr.
Co-Authored-By: Taloth <Taloth@users.noreply.github.com>
Signed-off-by: Robin Dadswell <robin@dadswell.email>
2021-01-28 23:43:19 -05:00
Qstick
19a496c31c Fixed: Windows installer won't create shortcut if unchecked
Co-Authored-By: Mark McDowall <markus101@users.noreply.github.com>
Signed-off-by: Robin Dadswell <robin@dadswell.email>
2021-01-28 23:43:19 -05:00
Taloth Saldono
db51254827 Improved error message when nzb download contains an newznab error instead
Signed-off-by: Robin Dadswell <robin@dadswell.email>
2021-01-28 23:43:19 -05:00
Qstick
24ab9768e9 Fixed: Ended overlay on artist posters
Co-Authored-By: Mark McDowall <markus101@users.noreply.github.com>
Signed-off-by: Robin Dadswell <robin@dadswell.email>
2021-01-28 23:43:19 -05:00
Qstick
79cff81ffd Rename FilterFiles to FilterPaths
Signed-off-by: Robin Dadswell <robin@dadswell.email>
2021-01-28 23:43:19 -05:00
Qstick
8d43d5d7b0 Fixed: Queue not always clearing checked items when updated
Signed-off-by: Robin Dadswell <robin@dadswell.email>
2021-01-28 23:43:19 -05:00
Qstick
97e6240177 Fixed: Remove website post fix before parsing
Signed-off-by: Robin Dadswell <robin@dadswell.email>
2021-01-28 23:43:19 -05:00
Taloth Saldono
371ef929f3 Linting error
Signed-off-by: Robin Dadswell <robin@dadswell.email>
2021-01-28 23:43:19 -05:00
Qstick
ebcde8f602 Fix Release Push log statement
Signed-off-by: Robin Dadswell <robin@dadswell.email>
2021-01-28 23:43:19 -05:00
Jacob
ea4044f237 New: Added option to filter Release Profile to a specific indexer
Signed-off-by: Robin Dadswell <robin@dadswell.email>
2021-01-28 23:43:19 -05:00
Mark McDowall
739ebf25c0 New: Clone indexer button
Signed-off-by: Robin Dadswell <robin@dadswell.email>
2021-01-28 23:43:19 -05:00
Qstick
0c6a51c7f7 Fixed: Manual Import sorting by quality
Co-Authored-By: Mark McDowall <markus101@users.noreply.github.com>
Signed-off-by: Robin Dadswell <robin@dadswell.email>
2021-01-28 23:43:19 -05:00
Mark McDowall
4fcc463d6a Fixed: Prompt to restart after resetting API key
Signed-off-by: Robin Dadswell <robin@dadswell.email>
2021-01-28 23:43:19 -05:00
Qstick
0d8c259237 Fixed: Sorting by track count
Signed-off-by: Robin Dadswell <robin@dadswell.email>
2021-01-28 23:43:19 -05:00
Taloth Saldono
6943cc0011 Added Norwegian Bokmal alias
Signed-off-by: Robin Dadswell <robin@dadswell.email>
2021-01-28 23:43:19 -05:00
Taloth Saldono
3a5752f3cc Fixed: Representation of episode start time when not starting at the full hour in am/pm notation
Signed-off-by: Robin Dadswell <robin@dadswell.email>
2021-01-28 23:43:19 -05:00
Qstick
7e00dd731c Fixed: RestClient does not use global proxy settings
Signed-off-by: Robin Dadswell <robin@dadswell.email>
2021-01-28 23:43:19 -05:00
Mark McDowall
bb66af7185 New: Limit recent folders in Manual import to 10 and descending order
Signed-off-by: Robin Dadswell <robin@dadswell.email>
2021-01-28 23:43:19 -05:00
Mark McDowall
16b2458903 Fix proptype warning for id of EnhancedSelectInputOption
Signed-off-by: Robin Dadswell <robin@dadswell.email>
2021-01-28 23:43:19 -05:00
Mark McDowall
d33d27a55f Remove website prefixes with dashes in URL
Signed-off-by: Robin Dadswell <robin@dadswell.email>
2021-01-28 23:43:19 -05:00
Mark McDowall
1e7efbe3d6 Fixed: Details for episode history flashing on mobile devices
Signed-off-by: Robin Dadswell <robin@dadswell.email>
2021-01-28 23:43:19 -05:00
Fossil
230198c1e7 Remove PFMonkey.com from Presets
Signed-off-by: Robin Dadswell <robin@dadswell.email>
2021-01-28 23:43:19 -05:00
Qstick
d803847342 Fixed: Test All not clearing health error
Co-Authored-By: Mark McDowall <markus101@users.noreply.github.com>
Signed-off-by: Robin Dadswell <robin@dadswell.email>
2021-01-28 23:43:19 -05:00
Qstick
7693593230 Improved some log messages
Signed-off-by: Robin Dadswell <robin@dadswell.email>
2021-01-28 23:43:19 -05:00
Qstick
f36eee0dc2 Fixed: Delete files from Artist Mass Editor not actually deleting files
Co-Authored-By: Mark McDowall <markus101@users.noreply.github.com>
Signed-off-by: Robin Dadswell <robin@dadswell.email>
2021-01-28 23:43:19 -05:00
Taloth Saldono
0db2f38dfe Tiny fix in test, left-over from my on-windows test.
Signed-off-by: Robin Dadswell <robin@dadswell.email>
2021-01-28 23:43:19 -05:00
Taloth Saldono
c58be51a03 Fixed: File imports on cloud drives slow due to transaction logic
Signed-off-by: Robin Dadswell <robin@dadswell.email>
2021-01-28 23:43:19 -05:00
Qstick
7ea1bf71dd Fixed: Use Proxy for MediaCovers and Metadata
Signed-off-by: Robin Dadswell <robin@dadswell.email>
2021-01-28 23:43:19 -05:00
Mark McDowall
1d6749ef52 Fixed: Set permissions on extra and subtitle files
Signed-off-by: Robin Dadswell <robin@dadswell.email>
2021-01-28 23:43:19 -05:00
Mark McDowall
9216fe28d0 Fixed: Include releases that failed to parse in search results
Signed-off-by: Robin Dadswell <robin@dadswell.email>
2021-01-28 23:43:19 -05:00
Qstick
649ecd94ea New: Event Driven HealthCheck Support
Co-Authored-By: Taloth <Taloth@users.noreply.github.com>
Signed-off-by: Robin Dadswell <robin@dadswell.email>
2021-01-28 23:43:19 -05:00
Qstick
634153b658 Fixed: Disregard Real when user disabled proper preference
Co-Authored-By: Taloth <Taloth@users.noreply.github.com>
Signed-off-by: Robin Dadswell <robin@dadswell.email>
2021-01-28 23:43:19 -05:00
Qstick
3101544484 Simplify ManualImportModule null check
Signed-off-by: Robin Dadswell <robin@dadswell.email>
2021-01-28 23:43:19 -05:00
Qstick
ff40d82ef1 Fixed: Edge case where import fails due to DB relationship mismatch
Signed-off-by: Robin Dadswell <robin@dadswell.email>
2021-01-28 23:43:19 -05:00
Qstick
d4383d4180 Fixed: Improved failed series search messaging
Signed-off-by: Robin Dadswell <robin@dadswell.email>
2021-01-28 23:43:19 -05:00
Qstick
72314c4388 Fixed: Manage Tracks not showing whether language/quality meets cutoff
Signed-off-by: Robin Dadswell <robin@dadswell.email>
2021-01-28 23:43:19 -05:00
Qstick
17b5187729 Fixed: Delay profile being ignored for non-revision upgrades
Signed-off-by: Robin Dadswell <robin@dadswell.email>
2021-01-28 23:43:19 -05:00
Qstick
e30c078962 Remove unnecessary usings
Signed-off-by: Robin Dadswell <robin@dadswell.email>
2021-01-28 23:43:19 -05:00
Qstick
d808934cf4 Remove Dotnet Framework Version Checks
Signed-off-by: Robin Dadswell <robin@dadswell.email>
2021-01-28 23:43:19 -05:00
nitsua
b3b4955fb2 New: Add label to disk usage progress bar
(cherry picked from commit 7c8ac300777583cb93d9deeed1328bcffaef555c)
Signed-off-by: Robin Dadswell <robin@dadswell.email>
2021-01-28 23:43:19 -05:00
Qstick
4765236940 Catchup Linting
Signed-off-by: Robin Dadswell <robin@dadswell.email>
2021-01-28 23:43:19 -05:00
Mark McDowall
32a49083e7 New: Show health warning if system time is off expected time 2021-01-28 23:43:19 -05:00
Qstick
9c096aae10 Fixed: Performance issue when scanning large root folder 2021-01-28 23:43:19 -05:00
Dyson Parkes
90b59a56e1 Update MonitorToggleButton.js
Fix toggle monitored state tooltip spelling.
2021-01-28 23:43:19 -05:00
Qstick
134523247c Misc UI Cleanup 2021-01-28 23:43:19 -05:00
Qstick
e5ba8af3ce Fixed: Styling issues in Quality Profile and Release Profiles 2021-01-28 23:43:19 -05:00
Qstick
10fd15b50a Fixed: Tag inputs respect non-QWERTY layouts 2021-01-28 23:43:19 -05:00
Qstick
dd93531432 Fixed: Deleting row from middle of filter builder leading to error 2021-01-28 23:43:19 -05:00
bakerboy448
762a085f9a Fixed: Rotating mobile device when modal is open won't reset modal 2021-01-28 23:43:19 -05:00
Qstick
8d63da1cfb Fixed: Toolbar button collapsing includes separator 2021-01-28 23:43:19 -05:00
Qstick
7a95cedbad New: Indicator when Filter Applied 2021-01-28 23:43:19 -05:00
ta264
44d73f3e7e New: Warn if UI won't update due to SignalR errors 2021-01-28 23:43:19 -05:00
Taloth Saldono
786247e9bc Added Plex url to cleanser 2021-01-28 23:43:19 -05:00
Taloth Saldono
c152cc2517 Fixed typo in Cleanse IP 2021-01-28 23:43:19 -05:00
Taloth Saldono
3d86e29972 Cleanse remote IP Address from trace log file 2021-01-28 23:43:19 -05:00
Taloth Saldono
e3c239e848 Cleanse getnzb url 2021-01-28 23:43:19 -05:00
1302 changed files with 74690 additions and 25993 deletions

View File

@@ -110,13 +110,13 @@ dotnet_diagnostic.SA1643.severity = none
dotnet_diagnostic.SA1648.severity = none
dotnet_diagnostic.SA1649.severity = none
dotnet_diagnostic.SA1651.severity = none
dotnet_diagnostic.SX1101.severity = warning
dotnet_diagnostic.SX1309.severity = warning
# Microsoft Analyzers that fail and need to be sorted thru
dotnet_diagnostic.ASP0000.severity = suggestion
dotnet_diagnostic.CA1000.severity = suggestion
dotnet_diagnostic.CA1001.severity = suggestion
dotnet_diagnostic.CA1002.severity = suggestion
dotnet_diagnostic.CA1003.severity = suggestion
dotnet_diagnostic.CA1008.severity = suggestion
dotnet_diagnostic.CA1010.severity = suggestion
@@ -163,10 +163,16 @@ dotnet_diagnostic.CA1304.severity = suggestion
dotnet_diagnostic.CA1305.severity = suggestion
dotnet_diagnostic.CA1307.severity = suggestion
dotnet_diagnostic.CA1308.severity = suggestion
dotnet_diagnostic.CA1309.severity = suggestion
dotnet_diagnostic.CA1310.severity = suggestion
dotnet_diagnostic.CA1401.severity = suggestion
dotnet_diagnostic.CA1416.severity = suggestion
dotnet_diagnostic.CA1507.severity = suggestion
dotnet_diagnostic.CA1508.severity = suggestion
dotnet_diagnostic.CA1707.severity = suggestion
dotnet_diagnostic.CA1708.severity = suggestion
dotnet_diagnostic.CA1710.severity = suggestion
dotnet_diagnostic.CA1711.severity = suggestion
dotnet_diagnostic.CA1712.severity = suggestion
dotnet_diagnostic.CA1714.severity = suggestion
dotnet_diagnostic.CA1715.severity = suggestion
@@ -175,12 +181,14 @@ dotnet_diagnostic.CA1717.severity = suggestion
dotnet_diagnostic.CA1720.severity = suggestion
dotnet_diagnostic.CA1721.severity = suggestion
dotnet_diagnostic.CA1724.severity = suggestion
dotnet_diagnostic.CA1725.severity = suggestion
dotnet_diagnostic.CA1801.severity = suggestion
dotnet_diagnostic.CA1802.severity = suggestion
dotnet_diagnostic.CA1805.severity = suggestion
dotnet_diagnostic.CA1806.severity = suggestion
dotnet_diagnostic.CA1810.severity = suggestion
dotnet_diagnostic.CA1812.severity = suggestion
dotnet_diagnostic.CA1813.severity = suggestion
dotnet_diagnostic.CA1814.severity = suggestion
dotnet_diagnostic.CA1815.severity = suggestion
dotnet_diagnostic.CA1816.severity = suggestion
@@ -210,6 +218,7 @@ dotnet_diagnostic.CA2101.severity = suggestion
dotnet_diagnostic.CA2119.severity = suggestion
dotnet_diagnostic.CA2153.severity = suggestion
dotnet_diagnostic.CA2200.severity = suggestion
dotnet_diagnostic.CA2201.severity = suggestion
dotnet_diagnostic.CA2207.severity = suggestion
dotnet_diagnostic.CA2208.severity = suggestion
dotnet_diagnostic.CA2211.severity = suggestion
@@ -255,6 +264,8 @@ dotnet_diagnostic.CA5374.severity = suggestion
dotnet_diagnostic.CA5379.severity = suggestion
dotnet_diagnostic.CA5384.severity = suggestion
dotnet_diagnostic.CA5385.severity = suggestion
dotnet_diagnostic.CA5392.severity = suggestion
dotnet_diagnostic.CA5394.severity = suggestion
dotnet_diagnostic.CA5397.severity = suggestion

View File

@@ -1,35 +0,0 @@
---
name: Bug Report
about: Support Requests will be closed immediately, if you are unsure go to our Reddit or Discord first. Exceptions do not mean you found a bug!
title: ''
labels: bug
assignees: ''
---
**Describe the bug**
A clear and concise description of what the bug is.
**To Reproduce**
Steps to reproduce the behavior:
1. Go to '...'
2. Click on '....'
3. Scroll down to '....'
4. See error
**Expected behavior**
A clear and concise description of what you expected to happen.
**Screenshots**
If applicable, add screenshots to help explain your problem.
**Platform Information (please complete the following information):**
- OS: [e.g. Windows 10 2004 / Ubuntu 20.10]
- Docker: [Yes/No]
- Mono or.NET Core Version: [e.g. Mono 5.8 or .Net Core 3.1.10] (found under System -> Status)
- Browser and Version [e.g. chrome 86.0.4240.198] (Only needed for UI issues)
- Readarr Version [e.g. 0.3.0.430]
- Readarr Branch [e.g. master]
**Trace Logs**
Turn on Trace logs under Settings -> General and wait for the bug to occur again. **Upload the full log file here (or another site (e.g. pastebin) and link it). Issues will be closed, if they do not include this!**

75
.github/ISSUE_TEMPLATE/bug_report.yml vendored Normal file
View File

@@ -0,0 +1,75 @@
name: Bug Report
title: "[BUG]: "
description: 'Report a new bug, if you are not 100% certain this is a bug please go to our Reddit or Discord first'
labels: ['Type: Bug', 'Status: Needs Triage']
body:
- type: checkboxes
attributes:
label: Is there an existing issue for this?
description: Please search to see if an issue already exists for the bug you encountered.
options:
- label: I have searched the existing issues
required: true
- type: textarea
attributes:
label: Current Behavior
description: A concise description of what you're experiencing.
validations:
required: true
- type: textarea
attributes:
label: Expected Behavior
description: A concise description of what you expected to happen.
validations:
required: true
- type: textarea
attributes:
label: Steps To Reproduce
description: Steps to reproduce the behavior.
placeholder: |
1. In this environment...
2. With this config...
3. Run '...'
4. See error...
validations:
required: false
- type: textarea
attributes:
label: Environment
description: |
examples:
- **OS**: Ubuntu 20.04
- **Readarr**: Readarr 0.1.0.432
- **Docker Install**: Yes
- **Using Reverse Proxy**: No
- **Browser**: Firefox 90 (If UI related)
value: |
- OS:
- Readarr:
- Docker Install:
- Using Reverse Proxy:
- Browser:
render: markdown
validations:
required: true
- type: dropdown
attributes:
label: What branch are you running?
options:
- Master
- Develop
- Nightly
- Other (This issue will be closed)
validations:
required: true
- type: textarea
attributes:
label: Anything else?
description: |
Trace Logs (https://wiki.servarr.com/readarr/troubleshooting#logging-and-log-files)
Links? References? Anything that will give us more context about the issue you are encountering!
***Generally speaking, all bug reports must have trace logs provided.***
Tip: You can attach images or log files by clicking this area to highlight it and then dragging files in.
validations:
required: true

View File

@@ -1,7 +1,7 @@
blank_issues_enabled: false
contact_links:
- name: Support via Discord
url: https://discord.gg/rkEXY2Rbgn
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

View File

@@ -1,17 +0,0 @@
---
name: Feature request
about: Suggest an idea for this project
---
**Is your feature request related to a problem? Please describe.**
A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
**Describe the solution you'd like**
A clear and concise description of what you want to happen.
**Describe alternatives you've considered**
A clear and concise description of any alternative solutions or features you've considered.
**Additional context**
Add any other context or screenshots about the feature request here.

View File

@@ -0,0 +1,39 @@
name: Feature Request
title: "[FEAT]: "
description: 'Suggest an idea for Readarr'
labels: ['Type: Feature Request', 'Status: Needs Triage']
body:
- type: checkboxes
attributes:
label: Is there an existing issue for this?
description: Please search to see if an issue already exists for the feature you are requesting.
options:
- label: I have searched the existing issues
required: true
- type: textarea
attributes:
label: Is your feature request related to a problem? Please describe
description: A clear and concise description of what the problem is.
validations:
required: true
- type: textarea
attributes:
label: Describe the solution you'd like
description: A clear and concise description of what you want to happen.
validations:
required: true
- type: textarea
attributes:
label: Describe alternatives you've considered
description: A clear and concise description of any alternative solutions or features you've considered.
validations:
required: true
- type: textarea
attributes:
label: Anything else?
description: |
Links? References? Mockups? Anything that will give us more context about the feature you are encountering!
Tip: You can attach images or log files by clicking this area to highlight it and then dragging files in.
validations:
required: true

View File

@@ -1,14 +1,16 @@
#### Database Migration
YES | NO
YES - XXXX | NO
#### Description
A few sentences describing the overall goals of the pull request's commits.
#### Screenshot (if UI related)
#### Todos
- [ ] Tests
- [ ] Wiki Updates
- [ ] Translation Keys (./src/NzbDrone.Core/Localization/Core/en.json)
- [ ] [Wiki Updates](https://wiki.servarr.com)
#### Issues Fixed or Closed by this PR
*
* Fixes #XXXX

41
.github/workflows/azuresync.yml vendored Normal file
View File

@@ -0,0 +1,41 @@
name: Sync issue to Azure DevOps work item
on:
issues:
types:
[opened, edited, deleted, closed, reopened, labeled, unlabeled, assigned]
concurrency: azuresync-${{ github.event.issue.number }}
jobs:
alert:
runs-on: ubuntu-latest
steps:
- uses: danhellem/github-actions-issue-to-work-item@master
if: "${{ contains(github.event.issue.labels.*.name, 'Type: Bug') == true }}"
env:
ado_token: "${{ secrets.ADO_PERSONAL_ACCESS_TOKEN }}"
github_token: "${{ github.token }}"
ado_organization: "Servarr"
ado_project: "Servarr"
ado_area_path: "Servarr\\Readarr"
ado_wit: "Bug"
ado_new_state: "New"
ado_active_state: "Active"
ado_close_state: "Closed"
ado_bypassrules: true
log_level: 100
- uses: danhellem/github-actions-issue-to-work-item@master
if: "${{ contains(github.event.issue.labels.*.name, 'Type: Bug') == false }}"
env:
ado_token: "${{ secrets.ADO_PERSONAL_ACCESS_TOKEN }}"
github_token: "${{ github.token }}"
ado_organization: "Servarr"
ado_project: "Servarr"
ado_area_path: "Servarr\\Readarr"
ado_wit: "User Story"
ado_new_state: "New"
ado_active_state: "Active"
ado_close_state: "Closed"
ado_bypassrules: true
log_level: 100

21
.github/workflows/lock.yml vendored Normal file
View File

@@ -0,0 +1,21 @@
name: 'Lock threads'
on:
workflow_dispatch:
schedule:
- cron: '0 0 * * *'
jobs:
lock:
runs-on: ubuntu-latest
steps:
- uses: dessant/lock-threads@v2
with:
github-token: ${{ github.token }}
issue-lock-inactive-days: '90'
issue-exclude-created-before: ''
issue-exclude-labels: ''
issue-lock-labels: ''
issue-lock-comment: ''
issue-lock-reason: 'resolved'
process-only: ''

21
.github/workflows/support.yml vendored Normal file
View File

@@ -0,0 +1,21 @@
name: 'Support requests'
on:
issues:
types: [labeled, unlabeled, reopened]
jobs:
support:
runs-on: ubuntu-latest
steps:
- uses: dessant/support-requests@v2
with:
github-token: ${{ github.token }}
support-label: 'Type: Support'
issue-comment: >
:wave: @{issue-author}, we use the issue tracker exclusively
for bug reports and feature requests. However, this issue appears
to be a support request. Please hop over onto our [Discord](https://readarr.com/discord)
or [Subreddit](https://reddit.com/r/readarr)
close-issue: true
lock-issue: false

View File

@@ -2,8 +2,10 @@
We're always looking for people to help make Readarr even better, there are a number of ways to contribute.
This file is updated on an ad-hoc basis, for the latest details please see the [contributing wiki page](https://wiki.servarr.com/readarr/contributing).
## Documentation ##
Setup guides, FAQ, the more information we have on the [wiki](https://wiki.servarr.com/Readarr) the better.
Setup guides, FAQ, the more information we have on the [wiki](https://wiki.servarr.com/readarr) the better.
## Development ##
@@ -11,20 +13,19 @@ Setup guides, FAQ, the more information we have on the [wiki](https://wiki.serva
- Visual Studio 2019 or higher (https://www.visualstudio.com/vs/). The community version is free and works (https://www.visualstudio.com/downloads/).
- HTML/Javascript editor of choice (VS Code/Sublime Text/Webstorm/Atom/etc)
- [Git](https://git-scm.com/downloads)
- [NodeJS](https://nodejs.org/en/download/) (Node 10.X.X or higher)
- [NodeJS](https://nodejs.org/en/download/) (Node 12.X.X or higher)
- [Yarn](https://yarnpkg.com/)
- .NET Core 3.1.
- .NET Core 5.0.
### Getting started ###
1. Fork Readarr
2. Clone the repository into your development machine. [*info*](https://help.github.com/articles/working-with-repositories)
3. Grab the submodules `git submodule init && git submodule update`
4. Install the required Node Packages `yarn install`
5. Start gulp to monitor your dev environment for any changes that need post processing using `yarn start` command.
6. Build the project in Visual Studio, Setting startup project to `Readarr.Console` and framework to `netcoreapp31`
7. Debug the project in Visual Studio
8. Open http://localhost:8787
2. Clone the repository into your development machine. [*info*](https://docs.github.com/en/github/creating-cloning-and-archiving-repositories/cloning-a-repository-from-github)
3. Install the required Node Packages `yarn install`
4. Start gulp to monitor your dev environment for any changes that need post processing using `yarn start` command.
5. Build the project in Visual Studio, Setting startup project to `Readarr.Console` and framework to `net5.0`
6. Debug the project in Visual Studio
7. Open http://localhost:8787
### Contributing Code ###
- If you're adding a new, already requested feature, please comment on [Github Issues](https://github.com/Readarr/Readarr/issues "Github Issues") so work is not duplicated (If you want to add something not already on there, please talk to us first)

View File

@@ -2,18 +2,18 @@
[![Build Status](https://dev.azure.com/Readarr/Readarr/_apis/build/status/Readarr.Readarr?branchName=develop)](https://dev.azure.com/Readarr/Readarr/_build/latest?definitionId=1&branchName=develop)
[![Docker Pulls](https://img.shields.io/docker/pulls/hotio/readarr)](https://hub.docker.com/r/hotio/readarr)
![Github Downloads](https://img.shields.io/github/downloads/readarr/readarr/total.svg)
[![Backers on Open Collective](https://opencollective.com/readarr/backers/badge.svg)](#backers) [![Sponsors on Open Collective](https://opencollective.com/readarr/sponsors/badge.svg)](#sponsors)
### Readarr is in early stages of development, alpha/beta binary builds are not yet available. Use of any test builds isn't recommend, and may have detrimental effects on your library.
### Readarr is currently in beta testing and is generally still in a work in progress. Features may be broken, incomplete, or cause spontaneous combustion.
Readarr is a ebook (and maybe eventually magazine/audiobook) collection manager for Usenet and BitTorrent users. It can monitor multiple RSS feeds for new books from your favorite authors and will grab, sort and rename them.
Readarr is an ebook and audiobook collection manager for Usenet and BitTorrent users. It can monitor multiple RSS feeds for new books from your favorite authors and will grab, sort and rename them.
Note that only one type of a given book is supported. If you want both an audiobook and ebook of a given book you will need multiple instances.
## Major Features Include:
* Support for major platforms: Windows, Linux, macOS, Raspberry Pi, etc.
* Automatically detects new books.
* Can scan your existing library and download any missing books.
* Automatically detects new books
* Can scan your existing library and download any missing books
* Automatic failed download handling will try another release if one fails
* Manual search so you can pick any release or to see why a release was not downloaded automatically
* Fully configurable book renaming
@@ -23,9 +23,12 @@ Readarr is a ebook (and maybe eventually magazine/audiobook) collection manager
## Support
[![Discord](https://img.shields.io/badge/discord-chat-7289DA.svg?maxAge=60)](https://discord.gg/rkEXY2Rbgn)
[![GitHub](https://img.shields.io/badge/github-issues-red.svg?maxAge=60)](https://github.com/Readarr/Readarr/issues)
[![Wiki](https://img.shields.io/badge/servarr-wiki-181717.svg?maxAge=60)](https://wiki.servarr.com/Readarr)
Note: GitHub Issues are for Bugs and Feature Requests Only
[![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)
[![GitHub - Bugs and Feature Requests Only](https://img.shields.io/badge/github-issues-red.svg?maxAge=60)](https://github.com/Readarr/Readarr/issues)
[![Wiki](https://img.shields.io/badge/servarr-wiki-181717.svg?maxAge=60)](https://wiki.servarr.com/readarr)
## Contributors

View File

@@ -13,8 +13,9 @@ variables:
buildName: '$(Build.SourceBranchName).$(readarrVersion)'
sentryOrg: 'servarr'
sentryUrl: 'https://sentry.servarr.com'
dotnetVersion: '3.1.401'
dotnetVersion: '5.0.302'
yarnCacheFolder: $(Pipeline.Workspace)/.yarn
nugetCacheFolder: $(Pipeline.Workspace)/.nuget/packages
trigger:
branches:
@@ -23,35 +24,92 @@ trigger:
- master
pr:
- develop
branches:
include:
- develop
paths:
exclude:
- src/NzbDrone.Core/Localization/Core
stages:
- stage: Setup
displayName: Setup
- stage: Build_Backend_Windows
displayName: Build Backend
dependsOn: []
jobs:
- job:
displayName: Build Variables
- job: Backend
strategy:
matrix:
Windows:
osName: 'Windows'
imageName: 'windows-2019'
enableAnalysis: 'false'
pool:
vmImage: 'ubuntu-18.04'
vmImage: $(imageName)
variables:
# Disable stylecop here - linting errors get caught by the analyze task
EnableAnalyzers: $(enableAnalysis)
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: |
if [[ $BUILD_REASON == "PullRequest" ]]; then
git diff origin/develop...HEAD --name-only | grep -E "^(src/|azure-pipelines.yml)"
echo $? > not_backend_update
BUNDLEDVERSIONS=${AGENT_TOOLSDIRECTORY}/dotnet/sdk/${DOTNETVERSION}/Microsoft.NETCoreSdk.BundledVersions.props
echo $BUNDLEDVERSIONS
grep osx-x64 $BUNDLEDVERSIONS
if grep -q freebsd-x64 $BUNDLEDVERSIONS; then
echo "BSD already enabled"
else
echo 0 > not_backend_update
echo "Enabling BSD support"
sed -i.ORI 's/osx-x64/osx-x64;freebsd-x64/' $BUNDLEDVERSIONS
fi
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
displayName: Enable FreeBSD 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\net5.0*,_output\*.Update\* -Recurse | Where { $_.Fullname -notlike "*\publish\*" -and $_.attributes -notlike "*directory*" } | Remove-Item
displayName: Clean up intermediate output
- task: PublishPipelineArtifact@1
inputs:
path: $(outputFolder)
artifact: '$(osName)Backend'
artifactType: 'pipeline'
parallel: true
parallelCount: 100
displayName: Publish Backend
- publish: '$(testsFolder)/net5.0/win-x64/publish'
artifact: WindowsCoreTests
displayName: Publish Windows Test Package
- publish: '$(testsFolder)/net5.0/linux-x64/publish'
artifact: LinuxCoreTests
displayName: Publish Linux Test Package
- publish: '$(testsFolder)/net5.0/linux-musl-x64/publish'
artifact: LinuxMuslCoreTests
displayName: Publish Linux Musl Test Package
- publish: '$(testsFolder)/net5.0/freebsd-x64/publish'
artifact: FreebsdCoreTests
displayName: Publish FreeBSD Test Package
- publish: '$(testsFolder)/net5.0/osx-x64/publish'
artifact: MacCoreTests
displayName: Publish MacOS Test Package
- stage: Build_Backend_Other
displayName: Build Backend (Other OS)
dependsOn: []
jobs:
- job: Backend
strategy:
@@ -59,18 +117,17 @@ stages:
Linux:
osName: 'Linux'
imageName: 'ubuntu-18.04'
enableAnalysis: 'true'
Mac:
osName: 'Mac'
imageName: 'macos-10.14'
Windows:
osName: 'Windows'
imageName: 'windows-2019'
enableAnalysis: 'false'
pool:
vmImage: $(imageName)
variables:
# Disable stylecop here - linting errors get caught by the analyze task
EnableAnalyzers: 'false'
EnableAnalyzers: $(enableAnalysis)
steps:
- checkout: self
submodules: true
@@ -79,43 +136,30 @@ stages:
displayName: 'Install .net core'
inputs:
version: $(dotnetVersion)
- bash: ./build.sh --backend
displayName: Build Readarr Backend
- bash: |
find ${OUTPUTFOLDER} -type f ! -path "*/publish/*" -exec rm -rf {} \;
find ${OUTPUTFOLDER} -depth -empty -type d -exec rm -r "{}" \;
find ${TESTSFOLDER} -type f ! -path "*/publish/*" -exec rm -rf {} \;
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)/netcoreapp3.1/win-x64/publish'
artifact: WindowsCoreTests
displayName: Publish Windows Test Package
condition: and(succeeded(), eq(variables['osName'], 'Windows'))
- publish: '$(testsFolder)/net462/linux-x64/publish'
artifact: LinuxTests
displayName: Publish Linux Mono Test Package
condition: and(succeeded(), eq(variables['osName'], 'Windows'))
- publish: '$(testsFolder)/netcoreapp3.1/linux-x64/publish'
artifact: LinuxCoreTests
displayName: Publish Linux Test Package
condition: and(succeeded(), eq(variables['osName'], 'Windows'))
- publish: '$(testsFolder)/netcoreapp3.1/linux-musl-x64/publish'
artifact: LinuxMuslCoreTests
displayName: Publish Linux Musl Test Package
condition: and(succeeded(), eq(variables['osName'], 'Windows'))
- publish: '$(testsFolder)/netcoreapp3.1/osx-x64/publish'
artifact: MacCoreTests
displayName: Publish MacOS Test Package
condition: and(succeeded(), eq(variables['osName'], 'Windows'))
BUNDLEDVERSIONS=${AGENT_TOOLSDIRECTORY}/dotnet/sdk/${DOTNETVERSION}/Microsoft.NETCoreSdk.BundledVersions.props
echo $BUNDLEDVERSIONS
grep osx-x64 $BUNDLEDVERSIONS
if grep -q freebsd-x64 $BUNDLEDVERSIONS; then
echo "BSD already enabled"
else
echo "Enabling BSD support"
sed -i.ORI 's/osx-x64/osx-x64;freebsd-x64/' $BUNDLEDVERSIONS
fi
displayName: Enable FreeBSD 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)
- stage: Build_Frontend
displayName: Frontend
dependsOn: Setup
dependsOn: []
jobs:
- job: Build
strategy:
@@ -135,7 +179,7 @@ stages:
- task: NodeTool@0
displayName: Set Node.js version
inputs:
versionSpec: '10.x'
versionSpec: '12.x'
- checkout: self
submodules: true
fetchDepth: 1
@@ -159,7 +203,7 @@ stages:
- stage: Installer
dependsOn:
- Build_Backend
- Build_Backend_Windows
- Build_Frontend
jobs:
- job: Windows_Installer
@@ -184,12 +228,12 @@ stages:
- bash: ./build.sh --packages
displayName: Create Packages
- bash: |
setup/inno/ISCC.exe setup/readarr.iss //DFramework=netcoreapp3.1 //DRuntime=win-x86
cp setup/output/Readarr.*windows.netcoreapp3.1.exe ${BUILD_ARTIFACTSTAGINGDIRECTORY}/Readarr.${BUILDNAME}.windows-core-x86-installer.exe
setup/inno/ISCC.exe setup/readarr.iss //DFramework=net5.0 //DRuntime=win-x86
cp setup/output/Readarr.*windows.net5.0.exe ${BUILD_ARTIFACTSTAGINGDIRECTORY}/Readarr.${BUILDNAME}.windows-core-x86-installer.exe
displayName: Create .NET Core Windows installer
- bash: |
setup/inno/ISCC.exe setup/readarr.iss //DFramework=netcoreapp3.1 //DRuntime=win-x64
cp setup/output/Readarr.*windows.netcoreapp3.1.exe ${BUILD_ARTIFACTSTAGINGDIRECTORY}/Readarr.${BUILDNAME}.windows-core-x64-installer.exe
setup/inno/ISCC.exe setup/readarr.iss //DFramework=net5.0 //DRuntime=win-x64
cp setup/output/Readarr.*windows.net5.0.exe ${BUILD_ARTIFACTSTAGINGDIRECTORY}/Readarr.${BUILDNAME}.windows-core-x64-installer.exe
displayName: Create .NET Core Windows installer
- publish: $(Build.ArtifactStagingDirectory)
artifact: 'WindowsInstaller'
@@ -197,7 +241,7 @@ stages:
- stage: Packages
dependsOn:
- Build_Backend
- Build_Backend_Windows
- Build_Frontend
jobs:
- job: Other_Packages
@@ -219,7 +263,7 @@ stages:
artifactName: WindowsFrontend
targetPath: _output
displayName: Fetch Frontend
- bash: ./build.sh --packages
- bash: ./build.sh --packages --enable-bsd
displayName: Create Packages
- bash: |
find . -name "fpcalc" -exec chmod a+x {} \;
@@ -232,21 +276,21 @@ stages:
archiveFile: '$(Build.ArtifactStagingDirectory)/Readarr.$(buildName).windows-core-x64.zip'
archiveType: 'zip'
includeRootFolder: false
rootFolderOrFile: $(artifactsFolder)/win-x64/netcoreapp3.1
rootFolderOrFile: $(artifactsFolder)/win-x64/net5.0
- task: ArchiveFiles@2
displayName: Create Windows x86 Core zip
inputs:
archiveFile: '$(Build.ArtifactStagingDirectory)/Readarr.$(buildName).windows-core-x86.zip'
archiveType: 'zip'
includeRootFolder: false
rootFolderOrFile: $(artifactsFolder)/win-x86/netcoreapp3.1
rootFolderOrFile: $(artifactsFolder)/win-x86/net5.0
- task: ArchiveFiles@2
displayName: Create MacOS Core app
inputs:
archiveFile: '$(Build.ArtifactStagingDirectory)/Readarr.$(buildName).osx-app-core-x64.zip'
archiveType: 'zip'
includeRootFolder: false
rootFolderOrFile: $(artifactsFolder)/macos-app/netcoreapp3.1
rootFolderOrFile: $(artifactsFolder)/macos-app/net5.0
- task: ArchiveFiles@2
displayName: Create MacOS Core tar
inputs:
@@ -254,15 +298,7 @@ stages:
archiveType: 'tar'
tarCompression: 'gz'
includeRootFolder: false
rootFolderOrFile: $(artifactsFolder)/macos/netcoreapp3.1
- task: ArchiveFiles@2
displayName: Create Linux Mono tar
inputs:
archiveFile: '$(Build.ArtifactStagingDirectory)/Readarr.$(buildName).linux.tar.gz'
archiveType: 'tar'
tarCompression: 'gz'
includeRootFolder: false
rootFolderOrFile: $(artifactsFolder)/linux-x64/net462
rootFolderOrFile: $(artifactsFolder)/macos/net5.0
- task: ArchiveFiles@2
displayName: Create Linux Core tar
inputs:
@@ -270,7 +306,7 @@ stages:
archiveType: 'tar'
tarCompression: 'gz'
includeRootFolder: false
rootFolderOrFile: $(artifactsFolder)/linux-x64/netcoreapp3.1
rootFolderOrFile: $(artifactsFolder)/linux-x64/net5.0
- task: ArchiveFiles@2
displayName: Create Linux Musl Core tar
inputs:
@@ -278,7 +314,7 @@ stages:
archiveType: 'tar'
tarCompression: 'gz'
includeRootFolder: false
rootFolderOrFile: $(artifactsFolder)/linux-musl-x64/netcoreapp3.1
rootFolderOrFile: $(artifactsFolder)/linux-musl-x64/net5.0
- task: ArchiveFiles@2
displayName: Create ARM32 Linux Core tar
inputs:
@@ -286,7 +322,7 @@ stages:
archiveType: 'tar'
tarCompression: 'gz'
includeRootFolder: false
rootFolderOrFile: $(artifactsFolder)/linux-arm/netcoreapp3.1
rootFolderOrFile: $(artifactsFolder)/linux-arm/net5.0
- task: ArchiveFiles@2
displayName: Create Linux Core tar
inputs:
@@ -294,7 +330,7 @@ stages:
archiveType: 'tar'
tarCompression: 'gz'
includeRootFolder: false
rootFolderOrFile: $(artifactsFolder)/linux-arm64/netcoreapp3.1
rootFolderOrFile: $(artifactsFolder)/linux-arm64/net5.0
- task: ArchiveFiles@2
displayName: Create ARM64 Linux Musl Core tar
inputs:
@@ -302,7 +338,15 @@ stages:
archiveType: 'tar'
tarCompression: 'gz'
includeRootFolder: false
rootFolderOrFile: $(artifactsFolder)/linux-musl-arm64/netcoreapp3.1
rootFolderOrFile: $(artifactsFolder)/linux-musl-arm64/net5.0
- task: ArchiveFiles@2
displayName: Create FreeBSD Core Core tar
inputs:
archiveFile: '$(Build.ArtifactStagingDirectory)/Readarr.$(buildName).freebsd-core-x64.tar.gz'
archiveType: 'tar'
tarCompression: 'gz'
includeRootFolder: false
rootFolderOrFile: $(artifactsFolder)/freebsd-x64/net5.0
- publish: $(Build.ArtifactStagingDirectory)
artifact: 'Packages'
displayName: Publish Packages
@@ -318,6 +362,10 @@ stages:
else
sentry-cli releases deploys "${RELEASENAME}" new -e production
fi
if [ $? -gt 0 ]; then
echo "##vso[task.logissue type=warning]Error uploading source maps."
fi
exit 0
displayName: Publish Sentry Source Maps
condition: |
or
@@ -332,44 +380,39 @@ stages:
- stage: Unit_Test
displayName: Unit Tests
dependsOn: Build_Backend
dependsOn: Build_Backend_Windows
condition: succeeded()
jobs:
- job: Prepare
pool:
vmImage: 'ubuntu-18.04'
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
strategy:
matrix:
MacCore:
osName: 'Mac'
testName: 'MacCore'
poolName: 'Azure Pipelines'
imageName: 'macos-10.14'
WindowsCore:
osName: 'Windows'
testName: 'WindowsCore'
poolName: 'Azure Pipelines'
imageName: 'windows-2019'
LinuxCore:
osName: 'Linux'
testName: 'LinuxCore'
poolName: 'Azure Pipelines'
imageName: 'ubuntu-18.04'
pattern: 'Readarr.**.linux-core-x64.tar.gz'
FreebsdCore:
osName: 'Linux'
testName: 'FreebsdCore'
poolName: 'FreeBSD'
imageName:
pool:
name: $(poolName)
vmImage: $(imageName)
steps:
@@ -378,6 +421,7 @@ stages:
displayName: 'Install .net core'
inputs:
version: $(dotnetVersion)
condition: ne(variables['poolName'], 'FreeBSD')
- task: DownloadPipelineArtifact@2
displayName: Download Test Artifact
inputs:
@@ -388,20 +432,9 @@ stages:
displayName: Enable Windows Test Service
condition: and(succeeded(), eq(variables['osName'], 'Windows'))
- bash: |
wget https://github.com/acoustid/chromaprint/releases/download/v1.4.3/chromaprint-fpcalc-1.4.3-linux-x86_64.tar.gz
sudo tar xf chromaprint-fpcalc-1.4.3-linux-x86_64.tar.gz --strip-components=1 --directory /usr/bin
chmod a+x _tests/fpcalc
displayName: Install fpcalc
condition: and(succeeded(), eq(variables['osName'], 'Linux'))
- bash: |
SYMLINK=6_6_0
MONOPREFIX=/Library/Frameworks/Mono.framework/Versions/$SYMLINK
echo "##vso[task.setvariable variable=MONOPREFIX;]$MONOPREFIX"
echo "##vso[task.setvariable variable=PKG_CONFIG_PATH;]$MONOPREFIX/lib/pkgconfig:$MONOPREFIX/share/pkgconfig:$PKG_CONFIG_PATH"
echo "##vso[task.setvariable variable=PATH;]$MONOPREFIX/bin:$PATH"
chmod a+x _tests/fpcalc
displayName: Set Mono Version and make fpcalc Executable
condition: and(succeeded(), eq(variables['osName'], 'Mac'))
displayName: Make fpcalc Executable
condition: and(succeeded(), or(eq(variables['osName'], 'Mac'), eq(variables['testName'], 'LinuxCore')))
- bash: find ${TESTSFOLDER} -name "Readarr.Test.Dummy" -exec chmod a+x {} \;
displayName: Make Test Dummy Executable
condition: and(succeeded(), ne(variables['osName'], 'Windows'))
@@ -421,22 +454,8 @@ stages:
- job: Unit_Docker
displayName: Unit Docker
dependsOn: Prepare
condition: and(succeeded(), eq(dependencies.Prepare.outputs['setVar.backendNotUpdated'], '0'))
strategy:
matrix:
mono520:
testName: 'Mono 5.20'
artifactName: LinuxTests
containerImage: ghcr.io/servarr/testimages:mono-5.20
mono610:
testName: 'Mono 6.10'
artifactName: LinuxTests
containerImage: ghcr.io/servarr/testimages:mono-6.10
mono612:
testName: 'Mono 6.12'
artifactName: LinuxTests
containerImage: ghcr.io/servarr/testimages:mono-6.12
alpine:
testName: 'Musl Net Core'
artifactName: LinuxMuslCoreTests
@@ -481,53 +500,30 @@ stages:
displayName: Integration
dependsOn: Packages
jobs:
- job: Prepare
pool:
vmImage: 'ubuntu-18.04'
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:
osName: 'Mac'
testName: 'MacCore'
imageName: 'macos-10.14'
pattern: 'Readarr.**.osx-core-x64.tar.gz'
pattern: 'Readarr.*.osx-core-x64.tar.gz'
WindowsCore:
osName: 'Windows'
testName: 'WindowsCore'
imageName: 'windows-2019'
pattern: 'Readarr.**.windows-core-x64.zip'
pattern: 'Readarr.*.windows-core-x64.zip'
LinuxCore:
osName: 'Linux'
testName: 'LinuxCore'
imageName: 'ubuntu-18.04'
pattern: 'Readarr.**.linux-core-x64.tar.gz'
pattern: 'Readarr.*.linux-core-x64.tar.gz'
pool:
vmImage: $(imageName)
steps:
- bash: |
SYMLINK=6_6_0
MONOPREFIX=/Library/Frameworks/Mono.framework/Versions/$SYMLINK
echo "##vso[task.setvariable variable=MONOPREFIX;]$MONOPREFIX"
echo "##vso[task.setvariable variable=PKG_CONFIG_PATH;]$MONOPREFIX/lib/pkgconfig:$MONOPREFIX/share/pkgconfig:$PKG_CONFIG_PATH"
echo "##vso[task.setvariable variable=PATH;]$MONOPREFIX/bin:$PATH"
displayName: Set Mono Version
condition: and(succeeded(), eq(variables['osName'], 'Mac'))
- task: UseDotNet@2
displayName: 'Install .net core'
inputs:
@@ -567,32 +563,59 @@ stages:
failTaskOnFailedTests: true
displayName: Publish Test Results
- job: Integration_FreeBSD
displayName: Integration Native FreeBSD
workspace:
clean: all
variables:
pattern: 'Readarr.*.freebsd-core-x64.tar.gz'
pool:
name: 'FreeBSD'
steps:
- checkout: none
- task: DownloadPipelineArtifact@2
displayName: Download Test Artifact
inputs:
buildType: 'current'
artifactName: 'FreebsdCoreTests'
targetPath: $(testsFolder)
- task: DownloadPipelineArtifact@2
displayName: Download Build Artifact
inputs:
buildType: 'current'
artifactName: Packages
itemPattern: '/$(pattern)'
targetPath: $(Build.ArtifactStagingDirectory)
- bash: |
mkdir -p ${BUILD_ARTIFACTSTAGINGDIRECTORY}/bin
tar xf ${BUILD_ARTIFACTSTAGINGDIRECTORY}/$(pattern) -C ${BUILD_ARTIFACTSTAGINGDIRECTORY}/bin
displayName: Extract Package
- bash: |
mkdir -p ./bin/
cp -r -v ${BUILD_ARTIFACTSTAGINGDIRECTORY}/bin/Readarr/. ./bin/
displayName: Move Package Contents
- 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: 'FreeBSD Integration Tests'
failTaskOnFailedTests: true
displayName: Publish Test Results
- job: Integration_Docker
displayName: Integration Docker
dependsOn: Prepare
condition: and(succeeded(), eq(dependencies.Prepare.outputs['setVar.backendNotUpdated'], '0'))
strategy:
matrix:
mono520:
testName: 'Mono 5.20'
artifactName: LinuxTests
containerImage: ghcr.io/servarr/testimages:mono-5.20
pattern: 'Readarr.**.linux.tar.gz'
mono610:
testName: 'Mono 6.10'
artifactName: LinuxTests
containerImage: ghcr.io/servarr/testimages:mono-6.10
pattern: 'Readarr.**.linux.tar.gz'
mono612:
testName: 'Mono 6.12'
artifactName: LinuxTests
containerImage: ghcr.io/servarr/testimages:mono-6.12
pattern: 'Readarr.**.linux.tar.gz'
alpine:
testName: 'Musl Net Core'
artifactName: LinuxMuslCoreTests
containerImage: ghcr.io/servarr/testimages:alpine
pattern: 'Readarr.**.linux-musl-core-x64.tar.gz'
pattern: 'Readarr.*.linux-musl-core-x64.tar.gz'
pool:
vmImage: 'ubuntu-18.04'
@@ -652,15 +675,15 @@ stages:
Linux:
osName: 'Linux'
imageName: 'ubuntu-18.04'
pattern: 'Readarr.**.linux-core-x64.tar.gz'
pattern: 'Readarr.*.linux-core-x64.tar.gz'
Mac:
osName: 'Mac'
imageName: 'macos-10.14'
pattern: 'Readarr.**.osx-core-x64.tar.gz'
pattern: 'Readarr.*.osx-core-x64.tar.gz'
Windows:
osName: 'Windows'
imageName: 'windows-2019'
pattern: 'Readarr.**.windows-core-x64.zip'
pattern: 'Readarr.*.windows-core-x64.zip'
pool:
vmImage: $(imageName)
@@ -697,6 +720,17 @@ stages:
chmod a+x ${TESTSFOLDER}/test.sh
${TESTSFOLDER}/test.sh ${OSNAME} Automation Test
displayName: Run Automation Tests
- task: CopyFiles@2
displayName: 'Copy Screenshot to: $(Build.ArtifactStagingDirectory)'
inputs:
SourceFolder: '$(Build.SourcesDirectory)'
Contents: |
**/*_test_screenshot.png
TargetFolder: '$(Build.ArtifactStagingDirectory)/screenshots'
- publish: $(Build.ArtifactStagingDirectory)/screenshots
artifact: '$(osName)AutomationScreenshots'
condition: and(succeeded(), eq(variables['System.JobAttempt'], '1'))
displayName: Publish Screenshot Bundle
- task: PublishTestResults@2
inputs:
testResultsFormat: 'NUnit'
@@ -706,23 +740,9 @@ stages:
displayName: Publish Test Results
- stage: Analyze
dependsOn:
- Setup
dependsOn: []
displayName: Analyze
jobs:
- job: Prepare
pool:
vmImage: 'ubuntu-18.04'
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:
@@ -739,7 +759,7 @@ stages:
- task: NodeTool@0
displayName: Set Node.js version
inputs:
versionSpec: '10.x'
versionSpec: '12.x'
- checkout: self
submodules: true
fetchDepth: 1
@@ -780,23 +800,33 @@ stages:
- job: Analyze_Backend
displayName: Backend
dependsOn: Prepare
condition: and(succeeded(), eq(dependencies.Prepare.outputs['setVar.backendNotUpdated'], '0'))
variables:
disable.coverage.autogenerate: 'true'
pool:
vmImage: windows-2019
vmImage: ubuntu-18.04
steps:
- task: UseDotNet@2
displayName: 'Install .net core'
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'
inputs:
version: $(dotnetVersion)
- checkout: self # Need history for Sonar analysis
submodules: true
- powershell: Set-Service SCardSvr -StartupType Manual
displayName: Enable Windows Test Service
- task: Cache@2
inputs:
key: 'nuget | "$(Agent.OS)" | $(Build.SourcesDirectory)/src/Directory.Packages.props'
path: $(nugetCacheFolder)
displayName: Cache NuGet packages
- task: SonarCloudPrepare@1
condition: eq(variables['System.PullRequest.IsFork'], 'False')
inputs:
@@ -807,14 +837,16 @@ stages:
projectName: 'Readarr'
projectVersion: '$(readarrVersion)'
extraProperties: |
sonar.exclusions=**/obj/**,**/*.dll,**/NzbDrone.Core.Test/Files/**/*,./frontend/**,**/ExternalModules/**,./src/Libraries/**
sonar.coverage.exclusions=**/Readarr.Api.V1/**/*,**/MonoTorrent/**/*,**/Marr.Data/**/*
sonar.exclusions=**/obj/**,**/*.dll,**/NzbDrone.Core.Test/Files/**/*,./frontend/**,./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 netcoreapp3.1 -r win-x64
TEST_DIR=_tests/netcoreapp3.1/win-x64/publish/ ./test.sh Windows Unit Coverage
./build.sh --backend -f net5.0 -r linux-x64
TEST_DIR=_tests/net5.0/linux-x64/publish/ ./test.sh Linux Unit Coverage
displayName: Coverage Unit Tests
env:
NUGET_PACKAGES: $(nugetCacheFolder)
- task: SonarCloudAnalyze@1
condition: eq(variables['System.PullRequest.IsFork'], 'False')
displayName: Publish SonarCloud Results
@@ -837,13 +869,14 @@ stages:
- Unit_Test
- Integration
- Automation
- Build_Backend_Other
condition: eq(variables['system.pullrequest.isfork'], false)
displayName: Build Status Report
jobs:
- job:
displayName: Discord Notification
pool:
vmImage: 'windows-2019'
vmImage: 'ubuntu-18.04'
steps:
- task: DownloadPipelineArtifact@2
continueOnError: true
@@ -853,7 +886,7 @@ stages:
artifactName: 'WindowsAutomationScreenshots'
targetPath: $(Build.SourcesDirectory)
- checkout: none
- powershell: |
- pwsh: |
iex ((New-Object System.Net.WebClient).DownloadString('https://raw.githubusercontent.com/Servarr/AzureDiscordNotify/master/DiscordNotify.ps1'))
env:
SYSTEM_ACCESSTOKEN: $(System.AccessToken)

View File

@@ -1,4 +1,4 @@
#! /bin/bash
#! /usr/bin/env bash
set -e
outputFolder='_output'
@@ -27,6 +27,18 @@ UpdateVersionNumber()
fi
}
EnableBsdSupport()
{
#todo enable sdk with
#SDK_PATH=$(dotnet --list-sdks | grep -P '5\.\d\.\d+' | head -1 | sed 's/\(5\.[0-9]*\.[0-9]*\).*\[\(.*\)\]/\2\/\1/g')
# BUNDLED_VERSIONS="${SDK_PATH}/Microsoft.NETCoreSdk.BundledVersions.props"
if grep -qv freebsd-x64 src/Directory.Build.props; then
sed -i'' -e "s^<RuntimeIdentifiers>\(.*\)</RuntimeIdentifiers>^<RuntimeIdentifiers>\1;freebsd-x64</RuntimeIdentifiers>^g" src/Directory.Build.props
sed -i'' -e "s^<ExcludedRuntimeFrameworkPairs>\(.*\)</ExcludedRuntimeFrameworkPairs>^<ExcludedRuntimeFrameworkPairs>\1;freebsd-x64:net472</ExcludedRuntimeFrameworkPairs>^g" src/Directory.Build.props
fi
}
LintUI()
{
ProgressStart 'ESLint'
@@ -57,9 +69,6 @@ Build()
platform=Posix
fi
dotnet clean $slnFile -c Debug
dotnet clean $slnFile -c Release
if [[ -z "$RID" || -z "$FRAMEWORK" ]];
then
dotnet msbuild -restore $slnFile -p:Configuration=Release -p:Platform=$platform -t:PublishAllRids
@@ -77,11 +86,11 @@ YarnInstall()
ProgressEnd 'yarn install'
}
RunGulp()
RunWebpack()
{
ProgressStart 'Running gulp'
yarn run build --production
ProgressEnd 'Running gulp'
ProgressStart 'Running webpack'
yarn run build --env production
ProgressEnd 'Running webpack'
}
PackageFiles()
@@ -120,7 +129,7 @@ PackageLinux()
echo "Adding Readarr.Mono to UpdatePackage"
cp $folder/Readarr.Mono.* $folder/Readarr.Update
if [ "$framework" = "netcoreapp3.1" ]; then
if [ "$framework" = "net5.0" ]; then
cp $folder/Mono.Posix.NETStandard.* $folder/Readarr.Update
cp $folder/libMonoPosixHelper.* $folder/Readarr.Update
fi
@@ -138,11 +147,6 @@ PackageMacOS()
PackageFiles "$folder" "$framework" "osx-x64"
if [ "$framework" = "net462" ]; then
echo "Adding Startup script"
cp macOS/Readarr $folder
fi
echo "Removing Service helpers"
rm -f $folder/ServiceUninstall.*
rm -f $folder/ServiceInstall.*
@@ -152,7 +156,7 @@ PackageMacOS()
echo "Adding Readarr.Mono to UpdatePackage"
cp $folder/Readarr.Mono.* $folder/Readarr.Update
if [ "$framework" = "netcoreapp3.1" ]; then
if [ "$framework" = "net5.0" ]; then
cp $folder/Mono.Posix.NETStandard.* $folder/Readarr.Update
cp $folder/libMonoPosixHelper.* $folder/Readarr.Update
fi
@@ -186,12 +190,13 @@ PackageWindows()
{
local framework="$1"
local runtime="$2"
ProgressStart "Creating Windows Package for $framework"
ProgressStart "Creating $runtime Package for $framework"
local folder=$artifactsFolder/$runtime/$framework/Readarr
PackageFiles "$folder" "$framework" "$runtime"
cp -r $outputFolder/$framework-windows/$runtime/publish/* $folder
echo "Removing Readarr.Mono"
rm -f $folder/Readarr.Mono.*
@@ -201,7 +206,7 @@ PackageWindows()
echo "Adding Readarr.Windows to UpdatePackage"
cp $folder/Readarr.Windows.* $folder/Readarr.Update
ProgressEnd 'Creating Windows Package'
ProgressEnd "Creating $runtime Package for $framework"
}
Package()
@@ -213,7 +218,7 @@ Package()
IFS='-' read -ra SPLIT <<< "$runtime"
case "${SPLIT[0]}" in
linux)
linux|freebsd*)
PackageLinux "$framework" "$runtime"
;;
win)
@@ -258,6 +263,7 @@ if [ $# -eq 0 ]; then
FRONTEND=YES
PACKAGES=YES
LINT=YES
ENABLE_BSD=NO
fi
while [[ $# -gt 0 ]]
@@ -269,6 +275,10 @@ case $key in
BACKEND=YES
shift # past argument
;;
--enable-bsd)
ENABLE_BSD=YES
shift # past argument
;;
-r|--runtime)
RID="$2"
shift # past argument
@@ -309,15 +319,22 @@ set -- "${POSITIONAL[@]}" # restore positional parameters
if [ "$BACKEND" = "YES" ];
then
UpdateVersionNumber
if [ "$ENABLE_BSD" = "YES" ];
then
EnableBsdSupport
fi
Build
if [[ -z "$RID" || -z "$FRAMEWORK" ]];
then
PackageTests "netcoreapp3.1" "win-x64"
PackageTests "netcoreapp3.1" "win-x86"
PackageTests "netcoreapp3.1" "linux-x64"
PackageTests "netcoreapp3.1" "linux-musl-x64"
PackageTests "netcoreapp3.1" "osx-x64"
PackageTests "net462" "linux-x64"
PackageTests "net5.0" "win-x64"
PackageTests "net5.0" "win-x86"
PackageTests "net5.0" "linux-x64"
PackageTests "net5.0" "linux-musl-x64"
PackageTests "net5.0" "osx-x64"
if [ "$ENABLE_BSD" = "YES" ];
then
PackageTests "net5.0" "freebsd-x64"
fi
else
PackageTests "$FRAMEWORK" "$RID"
fi
@@ -326,7 +343,7 @@ fi
if [ "$FRONTEND" = "YES" ];
then
YarnInstall
RunGulp
RunWebpack
fi
if [ "$LINT" = "YES" ];
@@ -345,15 +362,18 @@ then
if [[ -z "$RID" || -z "$FRAMEWORK" ]];
then
Package "netcoreapp3.1" "win-x64"
Package "netcoreapp3.1" "win-x86"
Package "netcoreapp3.1" "linux-x64"
Package "netcoreapp3.1" "linux-musl-x64"
Package "netcoreapp3.1" "linux-arm64"
Package "netcoreapp3.1" "linux-musl-arm64"
Package "netcoreapp3.1" "linux-arm"
Package "netcoreapp3.1" "osx-x64"
Package "net462" "linux-x64"
Package "net5.0" "win-x64"
Package "net5.0" "win-x86"
Package "net5.0" "linux-x64"
Package "net5.0" "linux-musl-x64"
Package "net5.0" "linux-arm64"
Package "net5.0" "linux-musl-arm64"
Package "net5.0" "linux-arm"
Package "net5.0" "osx-x64"
if [ "$ENABLE_BSD" = "YES" ];
then
Package "net5.0" "freebsd-x64"
fi
else
Package "$FRAMEWORK" "$RID"
fi

View File

@@ -6,8 +6,10 @@ const dirs = fs
.map((dirent) => dirent.name)
.join('|');
const frontendFolder = __dirname;
module.exports = {
parser: 'babel-eslint',
parser: '@babel/eslint-parser',
env: {
browser: true,
@@ -25,6 +27,9 @@ module.exports = {
parserOptions: {
ecmaVersion: 6,
sourceType: 'module',
babelOptions: {
configFile: `${frontendFolder}/babel.config.js`,
},
ecmaFeatures: {
modules: true,
impliedStrict: true
@@ -271,7 +276,7 @@ module.exports = {
// ImportSort
'simple-import-sort/sort': 'error',
'simple-import-sort/imports': 'error',
'import/newline-after-import': 'error',
// React
@@ -309,7 +314,7 @@ module.exports = {
{
files: ['*.js'],
rules: {
'simple-import-sort/sort': [
'simple-import-sort/imports': [
'error',
{
groups: [

View File

@@ -0,0 +1,270 @@
const path = require('path');
const webpack = require('webpack');
const FileManagerPlugin = require('filemanager-webpack-plugin');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const LiveReloadPlugin = require('webpack-livereload-plugin');
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
const TerserPlugin = require('terser-webpack-plugin');
module.exports = (env) => {
const uiFolder = 'UI';
const frontendFolder = path.join(__dirname, '..');
const srcFolder = path.join(frontendFolder, 'src');
const isProduction = !!env.production;
const isProfiling = isProduction && !!env.profile;
const inlineWebWorkers = 'no-fallback';
const distFolder = path.resolve(frontendFolder, '..', '_output', uiFolder);
console.log('Source Folder:', srcFolder);
console.log('Output Folder:', distFolder);
console.log('isProduction:', isProduction);
console.log('isProfiling:', isProfiling);
const config = {
mode: isProduction ? 'production' : 'development',
devtool: isProduction ? 'source-map' : 'eval-source-map',
stats: {
children: false
},
watchOptions: {
ignored: /node_modules/
},
entry: {
index: 'index.js'
},
resolve: {
modules: [
srcFolder,
path.join(srcFolder, 'Shims'),
'node_modules'
],
alias: {
jquery: 'jquery/src/jquery'
},
fallback: {
buffer: false,
http: false,
https: false,
url: false,
util: false,
net: false
}
},
output: {
path: distFolder,
publicPath: '/',
filename: '[name].js',
sourceMapFilename: '[file].map'
},
optimization: {
moduleIds: 'deterministic',
chunkIds: 'named',
splitChunks: {
chunks: 'initial',
name: 'vendors'
}
},
performance: {
hints: false
},
plugins: [
new webpack.DefinePlugin({
__DEV__: !isProduction,
'process.env.NODE_ENV': isProduction ? JSON.stringify('production') : JSON.stringify('development')
}),
new MiniCssExtractPlugin({
filename: 'Content/styles.css'
}),
new HtmlWebpackPlugin({
template: 'frontend/src/index.ejs',
filename: 'index.html',
publicPath: '/'
}),
new FileManagerPlugin({
events: {
onEnd: {
copy: [
// HTML
{
source: 'frontend/src/*.html',
destination: distFolder
},
// Fonts
{
source: 'frontend/src/Content/Fonts/*.*',
destination: path.join(distFolder, 'Content/Fonts')
},
// Icon Images
{
source: 'frontend/src/Content/Images/Icons/*.*',
destination: path.join(distFolder, 'Content/Images/Icons')
},
// Images
{
source: 'frontend/src/Content/Images/*.*',
destination: path.join(distFolder, 'Content/Images')
},
// Robots
{
source: 'frontend/src/Content/robots.txt',
destination: path.join(distFolder, 'Content/robots.txt')
}
]
}
}
}),
new LiveReloadPlugin()
],
resolveLoader: {
modules: [
'node_modules',
'frontend/build/webpack/'
]
},
module: {
rules: [
{
test: /\.worker\.js$/,
use: {
loader: 'worker-loader',
options: {
filename: '[name].js',
inline: inlineWebWorkers
}
}
},
{
test: /\.js?$/,
exclude: /(node_modules|JsLibraries)/,
use: [
{
loader: 'babel-loader',
options: {
configFile: `${frontendFolder}/babel.config.js`,
envName: isProduction ? 'production' : 'development',
presets: [
[
'@babel/preset-env',
{
modules: false,
loose: true,
debug: false,
useBuiltIns: 'entry',
corejs: 3
}
]
]
}
}
]
},
// CSS Modules
{
test: /\.css$/,
exclude: /(node_modules|globals.css)/,
use: [
{ loader: MiniCssExtractPlugin.loader },
{
loader: 'css-loader',
options: {
importLoaders: 1,
modules: {
localIdentName: '[name]/[local]/[hash:base64:5]'
}
}
},
{
loader: 'postcss-loader',
options: {
postcssOptions: {
config: 'frontend/postcss.config.js'
}
}
}
]
},
// Global styles
{
test: /\.css$/,
include: /(node_modules|globals.css)/,
use: [
'style-loader',
{
loader: 'css-loader'
}
]
},
// Fonts
{
test: /\.woff(2)?(\?v=[0-9]\.[0-9]\.[0-9])?$/,
use: [
{
loader: 'url-loader',
options: {
limit: 10240,
mimetype: 'application/font-woff',
emitFile: false,
name: 'Content/Fonts/[name].[ext]'
}
}
]
},
{
test: /\.(ttf|eot|eot?#iefix|svg)(\?v=[0-9]\.[0-9]\.[0-9])?$/,
use: [
{
loader: 'file-loader',
options: {
emitFile: false,
name: 'Content/Fonts/[name].[ext]'
}
}
]
}
]
}
};
if (isProfiling) {
config.resolve.alias['react-dom$'] = 'react-dom/profiling';
config.resolve.alias['scheduler/tracing'] = 'scheduler/tracing-profiling';
config.optimization.minimizer = [
new TerserPlugin({
cache: true,
parallel: true,
sourceMap: true, // Must be set to true if using source-maps in production
terserOptions: {
mangle: false,
keep_classnames: true,
keep_fnames: true
}
})
];
}
return config;
};

View File

@@ -1,18 +0,0 @@
const gulp = require('gulp');
require('./clean');
require('./copy');
require('./webpack');
gulp.task('build',
gulp.series('clean',
gulp.parallel(
'webpack',
'copyHtml',
'copyFonts',
'copyImages',
'copyRobots'
)
)
);

View File

@@ -1,8 +0,0 @@
const gulp = require('gulp');
const del = require('del');
const paths = require('./helpers/paths');
gulp.task('clean', () => {
return del([paths.dest.root]);
});

View File

@@ -1,42 +0,0 @@
const path = require('path');
const gulp = require('gulp');
const print = require('gulp-print').default;
const cache = require('gulp-cached');
const livereload = require('gulp-livereload');
const paths = require('./helpers/paths.js');
gulp.task('copyHtml', () => {
return gulp.src(paths.src.html, { base: paths.src.root })
.pipe(cache('copyHtml'))
.pipe(print())
.pipe(gulp.dest(paths.dest.root))
.pipe(livereload());
});
gulp.task('copyFonts', () => {
return gulp.src(
path.join(paths.src.fonts, '**', '*.*'), { base: paths.src.root }
)
.pipe(cache('copyFonts'))
.pipe(print())
.pipe(gulp.dest(paths.dest.root))
.pipe(livereload());
});
gulp.task('copyImages', () => {
return gulp.src(
path.join(paths.src.images, '**', '*.*'), { base: paths.src.root }
)
.pipe(cache('copyImages'))
.pipe(print())
.pipe(gulp.dest(paths.dest.root))
.pipe(livereload());
});
gulp.task('copyRobots', () => {
return gulp.src(paths.src.robots, { base: paths.src.root })
.pipe(cache('copyRobots'))
.pipe(print())
.pipe(gulp.dest(paths.dest.root))
.pipe(livereload());
});

View File

@@ -1,5 +0,0 @@
require('./build.js');
require('./clean.js');
require('./copy.js');
require('./watch.js');
require('./webpack.js');

View File

@@ -1,6 +0,0 @@
const colors = require('ansi-colors');
module.exports = function errorHandler(error) {
console.log(colors.red(`Error (${error.plugin}): ${error.message}`));
this.emit('end');
};

View File

@@ -1,24 +0,0 @@
const root = './frontend/src';
const paths = {
src: {
root,
html: `${root}/*.html`,
scripts: `${root}/**/*.js`,
content: `${root}/Content/`,
fonts: `${root}/Content/Fonts/`,
images: `${root}/Content/Images/`,
robots: `${root}/Content/robots.txt`,
exclude: {
libs: `!${root}/JsLibraries/**`
}
},
dest: {
root: './_output/UI/',
content: './_output/UI/Content/',
fonts: './_output/UI/Content/Fonts/',
images: './_output/UI/Content/Images/'
}
};
module.exports = paths;

View File

@@ -1,19 +0,0 @@
const gulp = require('gulp');
const livereload = require('gulp-livereload');
const gulpWatch = require('gulp-watch');
const paths = require('./helpers/paths.js');
require('./copy.js');
require('./webpack.js');
function watch() {
livereload.listen({ start: true });
gulp.task('webpackWatch')();
gulpWatch(paths.src.html, gulp.series('copyHtml'));
gulpWatch(`${paths.src.fonts}**/*.*`, gulp.series('copyFonts'));
gulpWatch(`${paths.src.images}**/*.*`, gulp.series('copyImages'));
gulpWatch(paths.src.robots, gulp.series('copyRobots'));
}
gulp.task('watch', gulp.series('build', watch));

View File

@@ -1,271 +0,0 @@
const gulp = require('gulp');
const webpackStream = require('webpack-stream');
const livereload = require('gulp-livereload');
const path = require('path');
const webpack = require('webpack');
const errorHandler = require('./helpers/errorHandler');
const OptimizeCssAssetsPlugin = require('optimize-css-assets-webpack-plugin');
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const HtmlWebpackPluginHtmlTags = require('html-webpack-plugin/lib/html-tags');
const TerserPlugin = require('terser-webpack-plugin');
const uiFolder = 'UI';
const frontendFolder = path.join(__dirname, '..');
const srcFolder = path.join(frontendFolder, 'src');
const isProduction = process.argv.indexOf('--production') > -1;
const isProfiling = isProduction && process.argv.indexOf('--profile') > -1;
const inlineWebWorkers = 'no-fallback';
const distFolder = path.resolve(frontendFolder, '..', '_output', uiFolder);
console.log('Source Folder:', srcFolder);
console.log('Output Folder:', distFolder);
console.log('isProduction:', isProduction);
console.log('isProfiling:', isProfiling);
const cssVarsFiles = [
'../src/Styles/Variables/colors',
'../src/Styles/Variables/dimensions',
'../src/Styles/Variables/fonts',
'../src/Styles/Variables/animations',
'../src/Styles/Variables/zIndexes'
].map(require.resolve);
// Override the way HtmlWebpackPlugin injects the scripts
// TODO: Find a better way to get these paths without
HtmlWebpackPlugin.prototype.injectAssetsIntoHtml = function(html, assets, assetTags) {
const head = assetTags.headTags.map((v) => {
const href = v.attributes.href
.replace('\\', '/')
.replace('%5C', '/');
v.attributes = { rel: 'stylesheet', type: 'text/css', href: `/${href}` };
return HtmlWebpackPluginHtmlTags.htmlTagObjectToString(v, this.options.xhtml);
});
const body = assetTags.bodyTags.map((v) => {
v.attributes = { src: `/${v.attributes.src}` };
return HtmlWebpackPluginHtmlTags.htmlTagObjectToString(v, this.options.xhtml);
});
return html
.replace('<!-- webpack bundles head -->', head.join('\r\n '))
.replace('<!-- webpack bundles body -->', body.join('\r\n '));
};
const plugins = [
new OptimizeCssAssetsPlugin({}),
new webpack.DefinePlugin({
__DEV__: !isProduction,
'process.env.NODE_ENV': isProduction ? JSON.stringify('production') : JSON.stringify('development')
}),
new MiniCssExtractPlugin({
filename: path.join('Content', 'styles.css')
}),
new HtmlWebpackPlugin({
template: 'frontend/src/index.html',
filename: 'index.html'
})
];
const config = {
mode: isProduction ? 'production' : 'development',
devtool: '#source-map',
stats: {
children: false
},
watchOptions: {
ignored: /node_modules/
},
entry: {
index: 'index.js'
},
resolve: {
modules: [
srcFolder,
path.join(srcFolder, 'Shims'),
'node_modules'
],
alias: {
jquery: 'jquery/src/jquery'
}
},
output: {
path: distFolder,
filename: '[name].js',
sourceMapFilename: '[file].map'
},
optimization: {
chunkIds: 'named',
splitChunks: {
chunks: 'initial'
}
},
performance: {
hints: false
},
plugins,
resolveLoader: {
modules: [
'node_modules',
'frontend/gulp/webpack/'
]
},
module: {
rules: [
{
test: /\.worker\.js$/,
use: {
loader: 'worker-loader',
options: {
filename: '[name].js',
inline: inlineWebWorkers
}
}
},
{
test: /\.js?$/,
exclude: /(node_modules|JsLibraries)/,
use: [
{
loader: 'babel-loader',
options: {
configFile: `${frontendFolder}/babel.config.js`,
envName: isProduction ? 'production' : 'development',
presets: [
[
'@babel/preset-env',
{
modules: false,
loose: true,
debug: false,
useBuiltIns: 'entry',
corejs: 3
}
]
]
}
}
]
},
// CSS Modules
{
test: /\.css$/,
exclude: /(node_modules|globals.css)/,
use: [
{ loader: MiniCssExtractPlugin.loader },
{
loader: 'css-loader',
options: {
importLoaders: 1,
modules: {
localIdentName: '[name]/[local]/[hash:base64:5]'
}
}
},
{
loader: 'postcss-loader',
options: {
ident: 'postcss',
config: {
ctx: {
cssVarsFiles
},
path: 'frontend/postcss.config.js'
}
}
}
]
},
// Global styles
{
test: /\.css$/,
include: /(node_modules|globals.css)/,
use: [
'style-loader',
{
loader: 'css-loader'
}
]
},
// Fonts
{
test: /\.woff(2)?(\?v=[0-9]\.[0-9]\.[0-9])?$/,
use: [
{
loader: 'url-loader',
options: {
limit: 10240,
mimetype: 'application/font-woff',
emitFile: false,
name: 'Content/Fonts/[name].[ext]'
}
}
]
},
{
test: /\.(ttf|eot|eot?#iefix|svg)(\?v=[0-9]\.[0-9]\.[0-9])?$/,
use: [
{
loader: 'file-loader',
options: {
emitFile: false,
name: 'Content/Fonts/[name].[ext]'
}
}
]
}
]
}
};
if (isProfiling) {
config.resolve.alias['react-dom$'] = 'react-dom/profiling';
config.resolve.alias['scheduler/tracing'] = 'scheduler/tracing-profiling';
config.optimization.minimizer = [
new TerserPlugin({
cache: true,
parallel: true,
sourceMap: true, // Must be set to true if using source-maps in production
terserOptions: {
mangle: false,
keep_classnames: true,
keep_fnames: true
}
})
];
}
gulp.task('webpack', () => {
return webpackStream(config)
.pipe(gulp.dest('_output/UI'));
});
gulp.task('webpackWatch', () => {
config.watch = true;
return webpackStream(config, webpack)
.on('error', errorHandler)
.pipe(gulp.dest('_output/UI'))
.on('error', errorHandler)
.pipe(livereload())
.on('error', errorHandler);
});

View File

@@ -1,23 +1,32 @@
const reload = require('require-nocache')(module);
module.exports = (ctx, configPath, options) => {
const config = {
plugins: {
'postcss-mixins': {
mixinsDir: [
'frontend/src/Styles/Mixins'
]
},
'postcss-simple-vars': {
variables: () =>
ctx.options.cssVarsFiles.reduce((acc, vars) => {
return Object.assign(acc, reload(vars));
}, {})
},
'postcss-color-function': {},
'postcss-nested': {}
}
};
const cssVarsFiles = [
'./src/Styles/Variables/colors',
'./src/Styles/Variables/dimensions',
'./src/Styles/Variables/fonts',
'./src/Styles/Variables/animations',
'./src/Styles/Variables/zIndexes'
].map(require.resolve);
return config;
const mixinsFiles = [
'frontend/src/Styles/Mixins/cover.css',
'frontend/src/Styles/Mixins/linkOverlay.css',
'frontend/src/Styles/Mixins/scroller.css',
'frontend/src/Styles/Mixins/truncate.css'
];
module.exports = {
plugins: [
['postcss-mixins', {
mixinsFiles
}],
['postcss-simple-vars', {
variables: () =>
cssVarsFiles.reduce((acc, vars) => {
return Object.assign(acc, reload(vars));
}, {})
}],
'postcss-color-function',
'postcss-nested'
]
};

View File

@@ -14,6 +14,7 @@ import TablePager from 'Components/Table/TablePager';
import { align, icons, kinds } from 'Helpers/Props';
import getRemovedItems from 'Utilities/Object/getRemovedItems';
import hasDifferentItems from 'Utilities/Object/hasDifferentItems';
import translate from 'Utilities/String/translate';
import getSelectedIds from 'Utilities/Table/getSelectedIds';
import removeOldSelectedState from 'Utilities/Table/removeOldSelectedState';
import selectAll from 'Utilities/Table/selectAll';
@@ -120,11 +121,11 @@ class Blacklist extends Component {
const selectedIds = this.getSelectedIds();
return (
<PageContent title="Blacklist">
<PageContent title={translate('Blacklist')}>
<PageToolbar>
<PageToolbarSection>
<PageToolbarButton
label="Remove Selected"
label={translate('RemoveSelected')}
iconName={icons.REMOVE}
isDisabled={!selectedIds.length}
isSpinning={isRemoving}
@@ -132,7 +133,7 @@ class Blacklist extends Component {
/>
<PageToolbarButton
label="Clear"
label={translate('Clear')}
iconName={icons.CLEAR}
isSpinning={isClearingBlacklistExecuting}
onPress={onClearBlacklistPress}
@@ -145,7 +146,7 @@ class Blacklist extends Component {
columns={columns}
>
<PageToolbarButton
label="Options"
label={translate('Options')}
iconName={icons.TABLE}
/>
</TableOptionsModalWrapper>
@@ -160,7 +161,9 @@ class Blacklist extends Component {
{
!isAnyFetching && !!error &&
<div>Unable to load blacklist</div>
<div>
{translate('UnableToLoadBlacklist')}
</div>
}
{
@@ -210,9 +213,9 @@ class Blacklist extends Component {
<ConfirmModal
isOpen={isConfirmRemoveModalOpen}
kind={kinds.DANGER}
title="Remove Selected"
message={'Are you sure you want to remove the selected items from the blacklist?'}
confirmLabel="Remove Selected"
title={translate('RemoveSelected')}
message={translate('RemoveSelectedMessageText')}
confirmLabel={translate('RemoveSelected')}
onConfirm={this.onRemoveSelectedConfirmed}
onCancel={this.onConfirmRemoveModalClose}
/>

View File

@@ -8,6 +8,7 @@ import ModalBody from 'Components/Modal/ModalBody';
import ModalContent from 'Components/Modal/ModalContent';
import ModalFooter from 'Components/Modal/ModalFooter';
import ModalHeader from 'Components/Modal/ModalHeader';
import translate from 'Utilities/String/translate';
class BlacklistDetailsModal extends Component {
@@ -39,19 +40,19 @@ class BlacklistDetailsModal extends Component {
<ModalBody>
<DescriptionList>
<DescriptionListItem
title="Name"
title={translate('Name')}
data={sourceTitle}
/>
<DescriptionListItem
title="Protocol"
title={translate('Protocol')}
data={protocol}
/>
{
!!message &&
<DescriptionListItem
title="Indexer"
title={translate('Indexer')}
data={indexer}
/>
}
@@ -59,7 +60,7 @@ class BlacklistDetailsModal extends Component {
{
!!message &&
<DescriptionListItem
title="Message"
title={translate('Message')}
data={message}
/>
}

View File

@@ -8,6 +8,7 @@ import TableRowCell from 'Components/Table/Cells/TableRowCell';
import TableSelectCell from 'Components/Table/Cells/TableSelectCell';
import TableRow from 'Components/Table/TableRow';
import { icons, kinds } from 'Helpers/Props';
import translate from 'Utilities/String/translate';
import BlacklistDetailsModal from './BlacklistDetailsModal';
import styles from './BlacklistRow.css';
@@ -77,7 +78,7 @@ class BlacklistRow extends Component {
return null;
}
if (name === 'authors.sortName') {
if (name === 'authorMetadata.sortName') {
return (
<TableRowCell key={name}>
<AuthorNameLink
@@ -141,7 +142,7 @@ class BlacklistRow extends Component {
/>
<IconButton
title="Remove from blacklist"
title={translate('RemoveFromBlacklist')}
name={icons.REMOVE}
kind={kinds.DANGER}
onPress={onRemovePress}

View File

@@ -9,6 +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 translate from 'Utilities/String/translate';
import styles from './HistoryDetails.css';
function getDetailedList(statusMessages) {
@@ -77,14 +78,14 @@ function HistoryDetails(props) {
<DescriptionList>
<DescriptionListItem
descriptionClassName={styles.description}
title="Name"
title={translate('Name')}
data={sourceTitle}
/>
{
!!indexer &&
<DescriptionListItem
title="Indexer"
title={translate('Indexer')}
data={indexer}
/>
}
@@ -93,7 +94,7 @@ function HistoryDetails(props) {
!!releaseGroup &&
<DescriptionListItem
descriptionClassName={styles.description}
title="Release Group"
title={translate('ReleaseGroup')}
data={releaseGroup}
/>
}
@@ -114,7 +115,7 @@ function HistoryDetails(props) {
{
!!downloadClient &&
<DescriptionListItem
title="Download Client"
title={translate('DownloadClient')}
data={downloadClient}
/>
}
@@ -122,7 +123,7 @@ function HistoryDetails(props) {
{
!!downloadId &&
<DescriptionListItem
title="Grab ID"
title={translate('GrabID')}
data={downloadId}
/>
}
@@ -130,7 +131,7 @@ function HistoryDetails(props) {
{
!!indexer &&
<DescriptionListItem
title="Age (when grabbed)"
title={translate('AgeWhenGrabbed')}
data={formatAge(age, ageHours, ageMinutes)}
/>
}
@@ -138,7 +139,7 @@ function HistoryDetails(props) {
{
!!publishedDate &&
<DescriptionListItem
title="Published Date"
title={translate('PublishedDate')}
data={formatDateTime(publishedDate, shortDateFormat, timeFormat, { includeSeconds: true })}
/>
}
@@ -155,14 +156,14 @@ function HistoryDetails(props) {
<DescriptionList>
<DescriptionListItem
descriptionClassName={styles.description}
title="Name"
title={translate('Name')}
data={sourceTitle}
/>
{
!!message &&
<DescriptionListItem
title="Message"
title={translate('Message')}
data={message}
/>
}
@@ -180,7 +181,7 @@ function HistoryDetails(props) {
<DescriptionList>
<DescriptionListItem
descriptionClassName={styles.description}
title="Name"
title={translate('Name')}
data={sourceTitle}
/>
@@ -188,7 +189,7 @@ function HistoryDetails(props) {
!!droppedPath &&
<DescriptionListItem
descriptionClassName={styles.description}
title="Source"
title={translate('Source')}
data={droppedPath}
/>
}
@@ -197,7 +198,7 @@ function HistoryDetails(props) {
!!importedPath &&
<DescriptionListItem
descriptionClassName={styles.description}
title="Imported To"
title={translate('ImportedTo')}
data={importedPath}
/>
}
@@ -229,12 +230,12 @@ function HistoryDetails(props) {
return (
<DescriptionList>
<DescriptionListItem
title="Name"
title={translate('Name')}
data={sourceTitle}
/>
<DescriptionListItem
title="Reason"
title={translate('Reason')}
data={reasonMessage}
/>
</DescriptionList>
@@ -250,12 +251,12 @@ function HistoryDetails(props) {
return (
<DescriptionList>
<DescriptionListItem
title="Source Path"
title={translate('SourcePath')}
data={sourcePath}
/>
<DescriptionListItem
title="Destination Path"
title={translate('DestinationPath')}
data={path}
/>
</DescriptionList>
@@ -271,7 +272,7 @@ function HistoryDetails(props) {
return (
<DescriptionList>
<DescriptionListItem
title="Path"
title={translate('Path')}
data={sourceTitle}
/>
{
@@ -286,7 +287,7 @@ function HistoryDetails(props) {
})
}
<DescriptionListItem
title="Existing tags scrubbed"
title={translate('ExistingTagsScrubbed')}
data={tagsScrubbed === 'True' ? <Icon name={icons.CHECK} /> : <Icon name={icons.REMOVE} />}
/>
</DescriptionList>
@@ -301,14 +302,14 @@ function HistoryDetails(props) {
return (
<DescriptionList>
<DescriptionListItem
title="Name"
title={translate('Name')}
data={sourceTitle}
/>
{
!!statusMessages &&
<DescriptionListItem
title="Import failures"
title={translate('ImportFailures')}
data={getDetailedList(JSON.parse(statusMessages))}
/>
}
@@ -332,14 +333,14 @@ function HistoryDetails(props) {
return (
<DescriptionList>
<DescriptionListItem
title="Name"
title={translate('Name')}
data={sourceTitle}
/>
{
!!indexer &&
<DescriptionListItem
title="Indexer"
title={translate('Indexer')}
data={indexer}
/>
}
@@ -347,7 +348,7 @@ function HistoryDetails(props) {
{
!!releaseGroup &&
<DescriptionListItem
title="Release Group"
title={translate('ReleaseGroup')}
data={releaseGroup}
/>
}
@@ -368,7 +369,7 @@ function HistoryDetails(props) {
{
!!downloadClient &&
<DescriptionListItem
title="Download Client"
title={translate('DownloadClient')}
data={downloadClient}
/>
}
@@ -376,7 +377,7 @@ function HistoryDetails(props) {
{
!!downloadId &&
<DescriptionListItem
title="Grab ID"
title={translate('GrabID')}
data={downloadId}
/>
}
@@ -384,7 +385,7 @@ function HistoryDetails(props) {
{
!!indexer &&
<DescriptionListItem
title="Age (when grabbed)"
title={translate('AgeWhenGrabbed')}
data={formatAge(age, ageHours, ageMinutes)}
/>
}
@@ -392,7 +393,7 @@ function HistoryDetails(props) {
{
!!publishedDate &&
<DescriptionListItem
title="Published Date"
title={translate('PublishedDate')}
data={formatDateTime(publishedDate, shortDateFormat, timeFormat, { includeSeconds: true })}
/>
}
@@ -409,14 +410,14 @@ function HistoryDetails(props) {
<DescriptionList>
<DescriptionListItem
descriptionClassName={styles.description}
title="Name"
title={translate('Name')}
data={sourceTitle}
/>
{
!!message &&
<DescriptionListItem
title="Message"
title={translate('Message')}
data={message}
/>
}
@@ -428,7 +429,7 @@ function HistoryDetails(props) {
<DescriptionList>
<DescriptionListItem
descriptionClassName={styles.description}
title="Name"
title={translate('Name')}
data={sourceTitle}
/>
</DescriptionList>

View File

@@ -12,32 +12,11 @@ import TableBody from 'Components/Table/TableBody';
import TableOptionsModalWrapper from 'Components/Table/TableOptions/TableOptionsModalWrapper';
import TablePager from 'Components/Table/TablePager';
import { align, icons } from 'Helpers/Props';
import hasDifferentItems from 'Utilities/Object/hasDifferentItems';
import translate from 'Utilities/String/translate';
import HistoryRowConnector from './HistoryRowConnector';
class History extends Component {
//
// Lifecycle
shouldComponentUpdate(nextProps) {
// Don't update when fetching has completed if items have changed,
// before books start fetching or when books start fetching.
if (
(
this.props.isFetching &&
nextProps.isPopulated &&
hasDifferentItems(this.props.items, nextProps.items)
) ||
(!this.props.isBooksFetching && nextProps.isBooksFetching)
) {
return false;
}
return true;
}
//
// Render
@@ -66,11 +45,11 @@ class History extends Component {
const hasError = error || booksError;
return (
<PageContent title="History">
<PageContent title={translate('History')}>
<PageToolbar>
<PageToolbarSection>
<PageToolbarButton
label="Refresh"
label={translate('Refresh')}
iconName={icons.REFRESH}
isSpinning={isFetching}
onPress={onFirstPagePress}
@@ -83,7 +62,7 @@ class History extends Component {
columns={columns}
>
<PageToolbarButton
label="Options"
label={translate('Options')}
iconName={icons.TABLE}
/>
</TableOptionsModalWrapper>
@@ -106,7 +85,9 @@ class History extends Component {
{
!isFetchingAny && hasError &&
<div>Unable to load history</div>
<div>
{translate('UnableToLoadHistory')}
</div>
}
{

View File

@@ -3,10 +3,7 @@ import React, { Component } from 'react';
import { connect } from 'react-redux';
import { createSelector } from 'reselect';
import withCurrentPage from 'Components/withCurrentPage';
import { clearBooks, fetchBooks } from 'Store/Actions/bookActions';
import * as historyActions from 'Store/Actions/historyActions';
import hasDifferentItems from 'Utilities/Object/hasDifferentItems';
import selectUniqueIds from 'Utilities/Object/selectUniqueIds';
import { registerPagePopulator, unregisterPagePopulator } from 'Utilities/pagePopulator';
import History from './History';
@@ -29,9 +26,7 @@ function createMapStateToProps() {
}
const mapDispatchToProps = {
...historyActions,
fetchBooks,
clearBooks
...historyActions
};
class HistoryConnector extends Component {
@@ -55,21 +50,9 @@ class HistoryConnector extends Component {
}
}
componentDidUpdate(prevProps) {
if (hasDifferentItems(prevProps.items, this.props.items)) {
const bookIds = selectUniqueIds(this.props.items, 'bookId');
if (bookIds.length) {
this.props.fetchBooks({ bookIds });
} else {
this.props.clearBooks();
}
}
}
componentWillUnmount() {
unregisterPagePopulator(this.repopulate);
this.props.clearHistory();
this.props.clearBooks();
}
//
@@ -150,9 +133,7 @@ HistoryConnector.propTypes = {
setHistorySort: PropTypes.func.isRequired,
setHistoryFilter: PropTypes.func.isRequired,
setHistoryTableOption: PropTypes.func.isRequired,
clearHistory: PropTypes.func.isRequired,
fetchBooks: PropTypes.func.isRequired,
clearBooks: PropTypes.func.isRequired
clearHistory: PropTypes.func.isRequired
};
export default withCurrentPage(

View File

@@ -93,7 +93,7 @@ class HistoryRow extends Component {
);
}
if (name === 'authors.sortName') {
if (name === 'authorMetadata.sortName') {
return (
<TableRowCell key={name}>
<AuthorNameLink

View File

@@ -15,6 +15,7 @@ import TablePager from 'Components/Table/TablePager';
import { align, icons } from 'Helpers/Props';
import getRemovedItems from 'Utilities/Object/getRemovedItems';
import hasDifferentItems from 'Utilities/Object/hasDifferentItems';
import translate from 'Utilities/String/translate';
import getSelectedIds from 'Utilities/Table/getSelectedIds';
import removeOldSelectedState from 'Utilities/Table/removeOldSelectedState';
import selectAll from 'Utilities/Table/selectAll';
@@ -31,6 +32,8 @@ class Queue extends Component {
constructor(props, context) {
super(props, context);
this._shouldBlockRefresh = false;
this.state = {
allSelected: false,
allUnselected: false,
@@ -42,6 +45,14 @@ class Queue extends Component {
};
}
shouldComponentUpdate() {
if (this._shouldBlockRefresh) {
return false;
}
return true;
}
componentDidUpdate(prevProps) {
const {
items,
@@ -84,6 +95,10 @@ class Queue extends Component {
//
// Listeners
onQueueRowModalOpenOrClose = (isOpen) => {
this._shouldBlockRefresh = isOpen;
}
onSelectAllChange = ({ value }) => {
this.setState(selectAll(this.state.selectedState, value));
}
@@ -99,15 +114,19 @@ class Queue extends Component {
}
onRemoveSelectedPress = () => {
this.setState({ isConfirmRemoveModalOpen: true });
this.setState({ isConfirmRemoveModalOpen: true }, () => {
this._shouldBlockRefresh = true;
});
}
onRemoveSelectedConfirmed = (payload) => {
this._shouldBlockRefresh = false;
this.props.onRemoveSelectedPress({ ids: this.getSelectedIds(), ...payload });
this.setState({ isConfirmRemoveModalOpen: false });
}
onConfirmRemoveModalClose = () => {
this._shouldBlockRefresh = false;
this.setState({ isConfirmRemoveModalOpen: false });
}
@@ -119,7 +138,6 @@ class Queue extends Component {
isFetching,
isPopulated,
error,
items,
isAuthorFetching,
isAuthorPopulated,
isBooksFetching,
@@ -139,7 +157,8 @@ class Queue extends Component {
allUnselected,
selectedState,
isConfirmRemoveModalOpen,
isPendingSelected
isPendingSelected,
items
} = this.state;
const isRefreshing = isFetching || isAuthorFetching || isBooksFetching || isRefreshMonitoredDownloadsExecuting;
@@ -150,11 +169,11 @@ class Queue extends Component {
const disableSelectedActions = selectedCount === 0;
return (
<PageContent title="Queue">
<PageContent title={translate('Queue')}>
<PageToolbar>
<PageToolbarSection>
<PageToolbarButton
label="Refresh"
label={translate('Refresh')}
iconName={icons.REFRESH}
isSpinning={isRefreshing}
onPress={onRefreshPress}
@@ -163,7 +182,7 @@ class Queue extends Component {
<PageToolbarSeparator />
<PageToolbarButton
label="Grab Selected"
label={translate('GrabSelected')}
iconName={icons.DOWNLOAD}
isDisabled={disableSelectedActions || !isPendingSelected}
isSpinning={isGrabbing}
@@ -171,7 +190,7 @@ class Queue extends Component {
/>
<PageToolbarButton
label="Remove Selected"
label={translate('RemoveSelected')}
iconName={icons.REMOVE}
isDisabled={disableSelectedActions}
isSpinning={isRemoving}
@@ -188,7 +207,7 @@ class Queue extends Component {
optionsComponent={QueueOptionsConnector}
>
<PageToolbarButton
label="Options"
label={translate('Options')}
iconName={icons.TABLE}
/>
</TableOptionsModalWrapper>
@@ -209,7 +228,7 @@ class Queue extends Component {
}
{
isPopulated && !hasError && !items.length &&
isAllPopulated && !hasError && !items.length &&
<div>
Queue is empty
</div>
@@ -238,6 +257,7 @@ class Queue extends Component {
columns={columns}
{...item}
onSelectedChange={this.onSelectedChange}
onQueueRowModalOpenOrClose={this.onQueueRowModalOpenOrClose}
/>
);
})

View File

@@ -4,12 +4,9 @@ import { connect } from 'react-redux';
import { createSelector } from 'reselect';
import * as commandNames from 'Commands/commandNames';
import withCurrentPage from 'Components/withCurrentPage';
import { clearBooks, fetchBooks } from 'Store/Actions/bookActions';
import { executeCommand } from 'Store/Actions/commandActions';
import * as queueActions from 'Store/Actions/queueActions';
import createCommandExecutingSelector from 'Store/Selectors/createCommandExecutingSelector';
import hasDifferentItems from 'Utilities/Object/hasDifferentItems';
import selectUniqueIds from 'Utilities/Object/selectUniqueIds';
import { registerPagePopulator, unregisterPagePopulator } from 'Utilities/pagePopulator';
import Queue from './Queue';
@@ -37,8 +34,6 @@ function createMapStateToProps() {
const mapDispatchToProps = {
...queueActions,
fetchBooks,
clearBooks,
executeCommand
};
@@ -51,6 +46,7 @@ class QueueConnector extends Component {
const {
useCurrentPage,
fetchQueue,
fetchQueueStatus,
gotoQueueFirstPage
} = this.props;
@@ -61,19 +57,11 @@ class QueueConnector extends Component {
} else {
gotoQueueFirstPage();
}
fetchQueueStatus();
}
componentDidUpdate(prevProps) {
if (hasDifferentItems(prevProps.items, this.props.items)) {
const bookIds = selectUniqueIds(this.props.items, 'bookId');
if (bookIds.length) {
this.props.fetchBooks({ bookIds });
} else {
this.props.clearBooks();
}
}
if (
this.props.includeUnknownAuthorItems !==
prevProps.includeUnknownAuthorItems
@@ -84,8 +72,6 @@ class QueueConnector extends Component {
componentWillUnmount() {
unregisterPagePopulator(this.repopulate);
this.props.clearQueue();
this.props.clearBooks();
}
//
@@ -171,6 +157,7 @@ QueueConnector.propTypes = {
items: PropTypes.arrayOf(PropTypes.object).isRequired,
includeUnknownAuthorItems: PropTypes.bool.isRequired,
fetchQueue: PropTypes.func.isRequired,
fetchQueueStatus: PropTypes.func.isRequired,
gotoQueueFirstPage: PropTypes.func.isRequired,
gotoQueuePreviousPage: PropTypes.func.isRequired,
gotoQueueNextPage: PropTypes.func.isRequired,
@@ -181,8 +168,6 @@ QueueConnector.propTypes = {
clearQueue: PropTypes.func.isRequired,
grabQueueItems: PropTypes.func.isRequired,
removeQueueItems: PropTypes.func.isRequired,
fetchBooks: PropTypes.func.isRequired,
clearBooks: PropTypes.func.isRequired,
executeCommand: PropTypes.func.isRequired
};

View File

@@ -3,6 +3,7 @@ import PropTypes from 'prop-types';
import React from 'react';
import Icon from 'Components/Icon';
import { icons, kinds } from 'Helpers/Props';
import translate from 'Utilities/String/translate';
function QueueDetails(props) {
const {
@@ -23,7 +24,7 @@ function QueueDetails(props) {
return (
<Icon
name={icons.PENDING}
title={`Release will be processed ${moment(estimatedCompletionTime).fromNow()}`}
title={translate('ReleaseWillBeProcessedInterp', [moment(estimatedCompletionTime).fromNow()])}
/>
);
}
@@ -34,7 +35,7 @@ function QueueDetails(props) {
<Icon
name={icons.DOWNLOAD}
kind={kinds.DANGER}
title={`Import failed: ${errorMessage}`}
title={translate('ImportFailedInterp', [errorMessage])}
/>
);
}
@@ -47,7 +48,7 @@ function QueueDetails(props) {
<Icon
name={icons.DOWNLOADING}
kind={kinds.DANGER}
title={`Download failed: ${errorMessage}`}
title={translate('DownloadFailedInterp', [errorMessage])}
/>
);
}
@@ -57,7 +58,7 @@ function QueueDetails(props) {
<Icon
name={icons.DOWNLOADING}
kind={kinds.DANGER}
title="Download failed: check download client for more details"
title={translate('DownloadFailedCheckDownloadClientForMoreDetails')}
/>
);
}
@@ -67,7 +68,7 @@ function QueueDetails(props) {
<Icon
name={icons.DOWNLOADING}
kind={kinds.WARNING}
title="Download warning: check download client for more details"
title={translate('DownloadWarningCheckDownloadClientForMoreDetails')}
/>
);
}
@@ -76,7 +77,7 @@ function QueueDetails(props) {
return (
<Icon
name={icons.DOWNLOADING}
title={`Book is downloading - ${progress.toFixed(1)}% ${title}`}
title={translate('BookIsDownloadingInterp', [progress.toFixed(1), title])}
/>
);
}

View File

@@ -4,6 +4,7 @@ import FormGroup from 'Components/Form/FormGroup';
import FormInputGroup from 'Components/Form/FormInputGroup';
import FormLabel from 'Components/Form/FormLabel';
import { inputTypes } from 'Helpers/Props';
import translate from 'Utilities/String/translate';
class QueueOptions extends Component {
@@ -54,13 +55,15 @@ class QueueOptions extends Component {
return (
<Fragment>
<FormGroup>
<FormLabel>Show Unknown Author Items</FormLabel>
<FormLabel>
{translate('ShowUnknownAuthorItems')}
</FormLabel>
<FormInputGroup
type={inputTypes.CHECK}
name="includeUnknownAuthorItems"
value={includeUnknownAuthorItems}
helpText="Show items without a author in the queue, this could include removed authors, movies or anything else in Readarr's category"
helpText={translate('IncludeUnknownAuthorItemsHelpText')}
onChange={this.onOptionChange}
/>
</FormGroup>

View File

@@ -15,6 +15,8 @@ import TableRow from 'Components/Table/TableRow';
import Popover from 'Components/Tooltip/Popover';
import { icons, kinds, tooltipPositions } from 'Helpers/Props';
import InteractiveImportModal from 'InteractiveImport/InteractiveImportModal';
import formatBytes from 'Utilities/Number/formatBytes';
import translate from 'Utilities/String/translate';
import QueueStatusCell from './QueueStatusCell';
import RemoveQueueItemModal from './RemoveQueueItemModal';
import TimeleftCell from './TimeleftCell';
@@ -42,19 +44,32 @@ class QueueRow extends Component {
}
onRemoveQueueItemModalConfirmed = (blacklist, skipredownload) => {
this.props.onRemoveQueueItemPress(blacklist, skipredownload);
const {
onRemoveQueueItemPress,
onQueueRowModalOpenOrClose
} = this.props;
onQueueRowModalOpenOrClose(false);
onRemoveQueueItemPress(blacklist, skipredownload);
this.setState({ isRemoveQueueItemModalOpen: false });
}
onRemoveQueueItemModalClose = () => {
this.props.onQueueRowModalOpenOrClose(false);
this.setState({ isRemoveQueueItemModalOpen: false });
}
onInteractiveImportPress = () => {
this.props.onQueueRowModalOpenOrClose(true);
this.setState({ isInteractiveImportModalOpen: true });
}
onInteractiveImportModalClose = () => {
this.props.onQueueRowModalOpenOrClose(false);
this.setState({ isInteractiveImportModalOpen: false });
}
@@ -137,7 +152,7 @@ class QueueRow extends Component {
);
}
if (name === 'authors.sortName') {
if (name === 'authorMetadata.sortName') {
return (
<TableRowCell key={name}>
{
@@ -229,6 +244,12 @@ class QueueRow extends Component {
);
}
if (name === 'size') {
return (
<TableRowCell key={name}>{formatBytes(size)}</TableRowCell>
);
}
if (name === 'outputPath') {
return (
<TableRowCell key={name}>
@@ -285,7 +306,7 @@ class QueueRow extends Component {
kind={kinds.DANGER}
/>
}
title="Manual Download"
title={translate('ManualDownload')}
body="This release failed parsing checks and was manually downloaded from an interactive search. Import is likely to fail."
position={tooltipPositions.LEFT}
/>
@@ -310,7 +331,7 @@ class QueueRow extends Component {
}
<SpinnerIconButton
title="Remove from queue"
title={translate('RemoveFromQueue')}
name={icons.REMOVE}
isSpinning={isRemoving}
onPress={this.onRemoveQueueItemPress}
@@ -335,7 +356,7 @@ class QueueRow extends Component {
<RemoveQueueItemModal
isOpen={isRemoveQueueItemModalOpen}
sourceTitle={title}
canIgnore={!!(author && book)}
canIgnore={!!author}
onRemovePress={this.onRemoveQueueItemModalConfirmed}
onModalClose={this.onRemoveQueueItemModalClose}
/>
@@ -376,7 +397,8 @@ QueueRow.propTypes = {
columns: PropTypes.arrayOf(PropTypes.object).isRequired,
onSelectedChange: PropTypes.func.isRequired,
onGrabPress: PropTypes.func.isRequired,
onRemoveQueueItemPress: PropTypes.func.isRequired
onRemoveQueueItemPress: PropTypes.func.isRequired,
onQueueRowModalOpenOrClose: PropTypes.func.isRequired
};
QueueRow.defaultProps = {

View File

@@ -4,6 +4,7 @@ import Icon from 'Components/Icon';
import TableRowCell from 'Components/Table/Cells/TableRowCell';
import Popover from 'Components/Tooltip/Popover';
import { icons, kinds, tooltipPositions } from 'Helpers/Props';
import translate from 'Utilities/String/translate';
import styles from './QueueStatusCell.css';
function getDetailedPopoverBody(statusMessages) {
@@ -49,11 +50,7 @@ function QueueStatusCell(props) {
// status === 'downloading'
let iconName = icons.DOWNLOADING;
let iconKind = kinds.DEFAULT;
let title = 'Downloading';
if (hasWarning) {
iconKind = kinds.WARNING;
}
let title = translate('Title');
if (status === 'paused') {
iconName = icons.PAUSED;
@@ -71,17 +68,24 @@ function QueueStatusCell(props) {
if (trackedDownloadState === 'importPending') {
title += ' - Waiting to Import';
iconKind = kinds.PURPLE;
}
if (trackedDownloadState === 'importing') {
title += ' - Importing';
iconKind = kinds.PURPLE;
}
if (trackedDownloadState === 'failedPending') {
title += ' - Waiting to Process';
iconKind = kinds.DANGER;
}
}
if (hasWarning) {
iconKind = kinds.WARNING;
}
if (status === 'delay') {
iconName = icons.PENDING;
title = 'Pending';

View File

@@ -10,6 +10,7 @@ import ModalContent from 'Components/Modal/ModalContent';
import ModalFooter from 'Components/Modal/ModalFooter';
import ModalHeader from 'Components/Modal/ModalHeader';
import { inputTypes, kinds, sizes } from 'Helpers/Props';
import translate from 'Utilities/String/translate';
class RemoveQueueItemModal extends Component {
@@ -95,26 +96,30 @@ class RemoveQueueItemModal extends Component {
</div>
<FormGroup>
<FormLabel>Remove From Download Client</FormLabel>
<FormLabel>
{translate('RemoveFromDownloadClient')}
</FormLabel>
<FormInputGroup
type={inputTypes.CHECK}
name="remove"
value={remove}
helpTextWarning="Removing will remove the download and the file(s) from the download client."
helpTextWarning={translate('RemoveHelpTextWarning')}
isDisabled={!canIgnore}
onChange={this.onRemoveChange}
/>
</FormGroup>
<FormGroup>
<FormLabel>Blacklist Release</FormLabel>
<FormLabel>
{translate('BlacklistRelease')}
</FormLabel>
<FormInputGroup
type={inputTypes.CHECK}
name="blacklist"
value={blacklist}
helpText="Prevents Readarr from automatically grabbing this release again"
helpText={translate('BlacklistHelpText')}
onChange={this.onBlacklistChange}
/>
</FormGroup>
@@ -122,12 +127,14 @@ class RemoveQueueItemModal extends Component {
{
blacklist &&
<FormGroup>
<FormLabel>Skip Redownload</FormLabel>
<FormLabel>
{translate('SkipRedownload')}
</FormLabel>
<FormInputGroup
type={inputTypes.CHECK}
name="skipredownload"
value={skipredownload}
helpText="Prevents Readarr from trying download an alternative release for this item"
helpText={translate('SkipredownloadHelpText')}
onChange={this.onSkipReDownloadChange}
/>
</FormGroup>

View File

@@ -10,6 +10,7 @@ import ModalContent from 'Components/Modal/ModalContent';
import ModalFooter from 'Components/Modal/ModalFooter';
import ModalHeader from 'Components/Modal/ModalHeader';
import { inputTypes, kinds, sizes } from 'Helpers/Props';
import translate from 'Utilities/String/translate';
import styles from './RemoveQueueItemsModal.css';
class RemoveQueueItemsModal extends Component {
@@ -96,13 +97,15 @@ class RemoveQueueItemsModal extends Component {
</div>
<FormGroup>
<FormLabel>Remove From Download Client</FormLabel>
<FormLabel>
{translate('RemoveFromDownloadClient')}
</FormLabel>
<FormInputGroup
type={inputTypes.CHECK}
name="remove"
value={remove}
helpTextWarning="Removing will remove the download and the file(s) from the download client."
helpTextWarning={translate('RemoveHelpTextWarning')}
isDisabled={!canIgnore}
onChange={this.onRemoveChange}
/>
@@ -117,7 +120,7 @@ class RemoveQueueItemsModal extends Component {
type={inputTypes.CHECK}
name="blacklist"
value={blacklist}
helpText="Prevents Readarr from automatically grabbing these files again"
helpText={translate('BlacklistHelpText')}
onChange={this.onBlacklistChange}
/>
</FormGroup>
@@ -125,12 +128,14 @@ class RemoveQueueItemsModal extends Component {
{
blacklist &&
<FormGroup>
<FormLabel>Skip Redownload</FormLabel>
<FormLabel>
{translate('SkipRedownload')}
</FormLabel>
<FormInputGroup
type={inputTypes.CHECK}
name="skipredownload"
value={skipredownload}
helpText="Prevents Readarr from trying download alternative releases for the removed items"
helpText={translate('SkipredownloadHelpText')}
onChange={this.onSkipReDownloadChange}
/>
</FormGroup>

View File

@@ -5,6 +5,7 @@ import formatTime from 'Utilities/Date/formatTime';
import formatTimeSpan from 'Utilities/Date/formatTimeSpan';
import getRelativeDate from 'Utilities/Date/getRelativeDate';
import formatBytes from 'Utilities/Number/formatBytes';
import translate from 'Utilities/String/translate';
import styles from './TimeleftCell.css';
function TimeleftCell(props) {
@@ -19,35 +20,35 @@ function TimeleftCell(props) {
timeFormat
} = props;
if (status === 'Delay') {
if (status === 'delay') {
const date = getRelativeDate(estimatedCompletionTime, shortDateFormat, showRelativeDates);
const time = formatTime(estimatedCompletionTime, timeFormat, { includeMinuteZero: true });
return (
<TableRowCell
className={styles.timeleft}
title={`Delaying download until ${date} at ${time}`}
title={translate('DelayingDownloadUntilInterp', [date, time])}
>
-
</TableRowCell>
);
}
if (status === 'DownloadClientUnavailable') {
if (status === 'downloadClientUnavailable') {
const date = getRelativeDate(estimatedCompletionTime, shortDateFormat, showRelativeDates);
const time = formatTime(estimatedCompletionTime, timeFormat, { includeMinuteZero: true });
return (
<TableRowCell
className={styles.timeleft}
title={`Retrying download ${date} at ${time}`}
title={translate('RetryingDownloadInterp', [date, time])}
>
-
</TableRowCell>
);
}
if (!timeleft) {
if (!timeleft || status === 'completed' || status === 'failed') {
return (
<TableRowCell className={styles.timeleft}>
-

View File

@@ -1,42 +1,43 @@
import React from 'react';
import DescriptionList from 'Components/DescriptionList/DescriptionList';
import DescriptionListItem from 'Components/DescriptionList/DescriptionListItem';
import translate from 'Utilities/String/translate';
function AuthorMonitoringOptionsPopoverContent() {
return (
<DescriptionList>
<DescriptionListItem
title="All Books"
title={translate('AllBooks')}
data="Monitor all books"
/>
<DescriptionListItem
title="Future Books"
title={translate('FutureBooks')}
data="Monitor books that have not released yet"
/>
<DescriptionListItem
title="Missing Books"
title={translate('MissingBooks')}
data="Monitor books that do not have files or have not released yet"
/>
<DescriptionListItem
title="Existing Books"
title={translate('ExistingBooks')}
data="Monitor books that have files or have not released yet"
/>
<DescriptionListItem
title="First Book"
title={translate('FirstBook')}
data="Monitor the first book. All other books will be ignored"
/>
<DescriptionListItem
title="Latest Book"
title={translate('LatestBook')}
data="Monitor the latest book and future books"
/>
<DescriptionListItem
title="None"
title={translate('None')}
data="No books will be monitored"
/>
</DescriptionList>

View File

@@ -8,11 +8,13 @@ import AuthorDetailsPageConnector from 'Author/Details/AuthorDetailsPageConnecto
import AuthorEditorConnector from 'Author/Editor/AuthorEditorConnector';
import AuthorIndexConnector from 'Author/Index/AuthorIndexConnector';
import BookDetailsPageConnector from 'Book/Details/BookDetailsPageConnector';
import BookIndexConnector from 'Book/Index/BookIndexConnector';
import BookshelfConnector from 'Bookshelf/BookshelfConnector';
import CalendarPageConnector from 'Calendar/CalendarPageConnector';
import NotFound from 'Components/NotFound';
import Switch from 'Components/Router/Switch';
import AddNewItemConnector from 'Search/AddNewItemConnector';
import DevelopmentSettingsConnector from 'Settings/Development/DevelopmentSettingsConnector';
import DownloadClientSettingsConnector from 'Settings/DownloadClients/DownloadClientSettingsConnector';
import GeneralSettingsConnector from 'Settings/General/GeneralSettingsConnector';
import ImportListSettingsConnector from 'Settings/ImportLists/ImportListSettingsConnector';
@@ -70,6 +72,11 @@ function AppRoutes(props) {
/>
}
<Route
path="/authors"
component={AuthorIndexConnector}
/>
<Route
path="/add/search"
component={AddNewItemConnector}
@@ -81,10 +88,17 @@ function AppRoutes(props) {
/>
<Route
path="/bookshelf"
exact={true}
path="/shelf"
component={BookshelfConnector}
/>
<Route
exact={true}
path="/books"
component={BookIndexConnector}
/>
<Route
path="/unmapped"
component={UnmappedFilesTableConnector}
@@ -207,6 +221,11 @@ function AppRoutes(props) {
component={UISettingsConnector}
/>
<Route
path="/settings/development"
component={DevelopmentSettingsConnector}
/>
{/*
System
*/}

View File

@@ -8,6 +8,7 @@ import ModalFooter from 'Components/Modal/ModalFooter';
import ModalHeader from 'Components/Modal/ModalHeader';
import { kinds } from 'Helpers/Props';
import UpdateChanges from 'System/Updates/UpdateChanges';
import translate from 'Utilities/String/translate';
import styles from './AppUpdatedModalContent.css';
function AppUpdatedModalContent(props) {
@@ -49,12 +50,12 @@ function AppUpdatedModalContent(props) {
</div>
<UpdateChanges
title="New"
title={translate('New')}
changes={update.changes.new}
/>
<UpdateChanges
title="Fixed"
title={translate('Fixed')}
changes={update.changes.fixed}
/>
</div>

View File

@@ -122,9 +122,17 @@ class AuthorImage extends Component {
placeholder,
size,
lazy,
overflow
overflow,
blurBackground
} = this.props;
const blurStyle = {
...style,
objectFit: 'fill',
filter: 'blur(8px)',
WebkitFilter: 'blur(8px)'
};
const {
url,
hasError,
@@ -168,13 +176,26 @@ class AuthorImage extends Component {
}
return (
<img
className={className}
style={style}
src={isLoaded ? url : placeholder}
onError={this.onError}
onLoad={this.onLoad}
/>
<>
{
blurBackground ?
<img
style={blurStyle}
src={isLoaded ? url : placeholder}
onError={this.onError}
onLoad={this.onLoad}
/> :
null
}
<img
className={className}
style={style}
src={isLoaded ? url : placeholder}
onError={this.onError}
onLoad={this.onLoad}
/>
</>
);
}
}
@@ -188,6 +209,7 @@ AuthorImage.propTypes = {
size: PropTypes.number.isRequired,
lazy: PropTypes.bool.isRequired,
overflow: PropTypes.bool.isRequired,
blurBackground: PropTypes.bool.isRequired,
onError: PropTypes.func,
onLoad: PropTypes.func
};
@@ -195,7 +217,8 @@ AuthorImage.propTypes = {
AuthorImage.defaultProps = {
size: 250,
lazy: true,
overflow: false
overflow: false,
blurBackground: false
};
export default AuthorImage;

View File

@@ -8,7 +8,6 @@ function AuthorPoster(props) {
return (
<AuthorImage
{...props}
coverType="poster"
placeholder={posterPlaceholder}
/>
);
@@ -19,7 +18,8 @@ AuthorPoster.propTypes = {
};
AuthorPoster.defaultProps = {
size: 250
size: 250,
coverType: 'poster'
};
export default AuthorPoster;

View File

@@ -11,6 +11,7 @@ import ModalFooter from 'Components/Modal/ModalFooter';
import ModalHeader from 'Components/Modal/ModalHeader';
import { icons, inputTypes, kinds } from 'Helpers/Props';
import formatBytes from 'Utilities/Number/formatBytes';
import translate from 'Utilities/String/translate';
import styles from './DeleteAuthorModalContent.css';
class DeleteAuthorModalContent extends Component {
@@ -67,7 +68,7 @@ class DeleteAuthorModalContent extends Component {
const addImportListExclusion = this.state.addImportListExclusion;
let deleteFilesLabel = `Delete ${bookFileCount} Book Files`;
let deleteFilesHelpText = 'Delete the book files and author folder';
let deleteFilesHelpText = translate('DeleteFilesHelpText');
if (bookFileCount === 0) {
deleteFilesLabel = 'Delete Author Folder';
@@ -106,13 +107,15 @@ class DeleteAuthorModalContent extends Component {
</FormGroup>
<FormGroup>
<FormLabel>Add List Exclusion</FormLabel>
<FormLabel>
{translate('AddListExclusion')}
</FormLabel>
<FormInputGroup
type={inputTypes.CHECK}
name="addImportListExclusion"
value={addImportListExclusion}
helpText="Prevent author from being added to Readarr by Import lists"
helpText={translate('AddImportListExclusionHelpText')}
kind={kinds.DANGER}
onChange={this.onAddImportListExclusionChange}
/>
@@ -121,7 +124,9 @@ class DeleteAuthorModalContent extends Component {
{
deleteFiles &&
<div className={styles.deleteFilesMessage}>
<div>The author folder <strong>{path}</strong> and all of its content will be deleted.</div>
<div>
{translate('TheAuthorFolderAndAllOfItsContentWillBeDeleted', [path])}
</div>
{
!!bookFileCount &&

View File

@@ -2,55 +2,12 @@
padding: 0;
}
.header {
position: relative;
width: 100%;
height: 310px;
}
.errorMessage {
margin-top: 20px;
text-align: center;
font-size: 20px;
}
.backdrop {
position: absolute;
z-index: -1;
width: 100%;
height: 100%;
background-size: cover;
}
.backdropOverlay {
position: absolute;
width: 100%;
height: 100%;
background: $black;
opacity: 0.7;
}
.headerContent {
display: flex;
padding: 30px;
width: 100%;
height: 100%;
color: $white;
}
.poster {
flex-shrink: 0;
margin-right: 35px;
height: 250px;
}
.info {
display: flex;
flex-direction: column;
flex-grow: 1;
overflow: hidden;
}
.metadataMessage {
color: $helpTextColor;
text-align: center;
@@ -58,96 +15,6 @@
font-size: 20px;
}
.titleRow {
position: relative;
display: flex;
justify-content: space-between;
flex: 0 0 auto;
}
.titleContainer {
display: flex;
margin-bottom: 5px;
}
.title {
font-weight: 300;
font-size: 50px;
line-height: 50px;
}
.toggleMonitoredContainer {
align-self: center;
margin-right: 10px;
}
.monitorToggleButton {
composes: toggleButton from '~Components/MonitorToggleButton.css';
width: 40px;
&:hover {
color: $iconButtonHoverLightColor;
}
}
.alternateTitlesIconContainer {
align-self: flex-end;
margin-left: 20px;
}
.artistNavigationButtons {
position: absolute;
right: 0;
white-space: nowrap;
}
.authorNavigationButton {
composes: button from '~Components/Link/IconButton.css';
margin-left: 5px;
width: 30px;
color: #e1e2e3;
white-space: nowrap;
&:hover {
color: $iconButtonHoverLightColor;
}
}
.details {
margin-bottom: 8px;
font-weight: 300;
font-size: 20px;
}
.runtime {
margin-right: 15px;
}
.detailsLabel {
composes: label from '~Components/Label.css';
margin: 5px 10px 5px 0;
}
.path,
.sizeOnDisk,
.qualityProfileName,
.links,
.tags {
margin-left: 8px;
font-weight: 300;
font-size: 17px;
}
.overview {
flex: 1 0 auto;
margin-top: 8px;
min-height: 0;
font-size: $intermediateFontSize;
}
.contentContainer {
padding: 20px;
}
@@ -177,18 +44,43 @@
margin-top: 20px;
}
.authorNavigationButtons {
position: absolute;
right: 0;
z-index: 1;
margin-top: 10px;
padding: 30px;
white-space: nowrap;
}
.authorUpButton,
.authorNavigationButton {
composes: button from '~Components/Link/IconButton.css';
margin-left: 5px;
width: 30px;
color: #e1e2e3;
white-space: nowrap;
&:hover {
color: $iconButtonHoverLightColor;
}
}
@media only screen and (max-width: $breakpointSmall) {
.contentContainer {
padding: 20px 0;
}
.headerContent {
.authorNavigationButtons {
padding: 15px;
}
}
@media only screen and (max-width: $breakpointLarge) {
.poster {
.authorNavigationButtons {
margin-top: 5px;
}
.authorNavigationButton {
display: none;
}
}

View File

@@ -1,57 +1,35 @@
import _ from 'lodash';
import PropTypes from 'prop-types';
import React, { Component } from 'react';
import { Tab, TabList, TabPanel, Tabs } from 'react-tabs';
import TextTruncate from 'react-text-truncate';
import AuthorPoster from 'Author/AuthorPoster';
import DeleteAuthorModal from 'Author/Delete/DeleteAuthorModal';
import EditAuthorModalConnector from 'Author/Edit/EditAuthorModalConnector';
import AuthorHistoryTable from 'Author/History/AuthorHistoryTable';
import MonitoringOptionsModal from 'Author/MonitoringOptions/MonitoringOptionsModal';
import BookFileEditorTable from 'BookFile/Editor/BookFileEditorTable';
import HeartRating from 'Components/HeartRating';
import Icon from 'Components/Icon';
import Label from 'Components/Label';
import IconButton from 'Components/Link/IconButton';
import Link from 'Components/Link/Link';
import LoadingIndicator from 'Components/Loading/LoadingIndicator';
import MonitorToggleButton from 'Components/MonitorToggleButton';
import PageContent from 'Components/Page/PageContent';
import PageContentBody from 'Components/Page/PageContentBody';
import PageToolbar from 'Components/Page/Toolbar/PageToolbar';
import PageToolbarButton from 'Components/Page/Toolbar/PageToolbarButton';
import PageToolbarSection from 'Components/Page/Toolbar/PageToolbarSection';
import PageToolbarSeparator from 'Components/Page/Toolbar/PageToolbarSeparator';
import Popover from 'Components/Tooltip/Popover';
import Tooltip from 'Components/Tooltip/Tooltip';
import { align, icons, kinds, sizes, tooltipPositions } from 'Helpers/Props';
import SwipeHeaderConnector from 'Components/Swipe/SwipeHeaderConnector';
import { align, icons } from 'Helpers/Props';
import InteractiveSearchFilterMenuConnector from 'InteractiveSearch/InteractiveSearchFilterMenuConnector';
import InteractiveSearchTable from 'InteractiveSearch/InteractiveSearchTable';
import OrganizePreviewModalConnector from 'Organize/OrganizePreviewModalConnector';
import RetagPreviewModalConnector from 'Retag/RetagPreviewModalConnector';
import QualityProfileNameConnector from 'Settings/Profiles/Quality/QualityProfileNameConnector';
import fonts from 'Styles/Variables/fonts';
import formatBytes from 'Utilities/Number/formatBytes';
import translate from 'Utilities/String/translate';
import selectAll from 'Utilities/Table/selectAll';
import toggleSelected from 'Utilities/Table/toggleSelected';
import InteractiveImportModal from '../../InteractiveImport/InteractiveImportModal';
import AuthorAlternateTitles from './AuthorAlternateTitles';
import AuthorDetailsLinks from './AuthorDetailsLinks';
import AuthorDetailsHeaderConnector from './AuthorDetailsHeaderConnector';
import AuthorDetailsSeasonConnector from './AuthorDetailsSeasonConnector';
import AuthorDetailsSeriesConnector from './AuthorDetailsSeriesConnector';
import AuthorTagsConnector from './AuthorTagsConnector';
import styles from './AuthorDetails.css';
const defaultFontSize = parseInt(fonts.defaultFontSize);
const lineHeight = parseFloat(fonts.lineHeight);
function getFanartUrl(images) {
const fanartImage = _.find(images, { coverType: 'fanart' });
if (fanartImage) {
// Remove protocol
return fanartImage.url.replace(/^https?:/, '');
}
}
function getExpandedState(newState) {
return {
allExpanded: newState.allSelected,
@@ -74,6 +52,7 @@ class AuthorDetails extends Component {
isEditAuthorModalOpen: false,
isDeleteAuthorModalOpen: false,
isInteractiveImportModalOpen: false,
isMonitorOptionsModalOpen: false,
allExpanded: false,
allCollapsed: false,
expandedState: {},
@@ -127,6 +106,14 @@ class AuthorDetails extends Component {
this.setState({ isDeleteAuthorModalOpen: false });
}
onMonitorOptionsPress = () => {
this.setState({ isMonitorOptionsModalOpen: true });
}
onMonitorOptionsClose = () => {
this.setState({ isMonitorOptionsModalOpen: false });
}
onExpandAllPress = () => {
const {
allExpanded,
@@ -161,18 +148,8 @@ class AuthorDetails extends Component {
const {
id,
authorName,
ratings,
path,
statistics,
qualityProfileId,
monitored,
status,
overview,
links,
images,
alternateTitles,
tags,
isSaving,
isRefreshing,
isSearching,
isFetching,
@@ -186,38 +163,24 @@ class AuthorDetails extends Component {
hasBookFiles,
previousAuthor,
nextAuthor,
onMonitorTogglePress,
onRefreshPress,
onSearchPress
onSearchPress,
statistics
} = this.props;
const {
bookFileCount,
sizeOnDisk
} = statistics;
const {
isOrganizeModalOpen,
isRetagModalOpen,
isEditAuthorModalOpen,
isDeleteAuthorModalOpen,
isInteractiveImportModalOpen,
isMonitorOptionsModalOpen,
allExpanded,
allCollapsed,
expandedState,
selectedTabIndex
} = this.state;
const continuing = status === 'continuing';
let bookFilesCountMessage = 'No book files';
if (bookFileCount === 1) {
bookFilesCountMessage = '1 book file';
} else if (bookFileCount > 1) {
bookFilesCountMessage = `${bookFileCount} book files`;
}
let expandIcon = icons.EXPAND_INDETERMINATE;
if (allExpanded) {
@@ -231,41 +194,41 @@ class AuthorDetails extends Component {
<PageToolbar>
<PageToolbarSection>
<PageToolbarButton
label="Refresh & Scan"
label={translate('RefreshScan')}
iconName={icons.REFRESH}
spinningName={icons.REFRESH}
title="Refresh information and scan disk"
title={translate('RefreshInformationAndScanDisk')}
isSpinning={isRefreshing}
onPress={onRefreshPress}
/>
<PageToolbarButton
label="Search Monitored"
label={translate('SearchMonitored')}
iconName={icons.SEARCH}
isDisabled={!monitored || !hasMonitoredBooks || !hasBooks}
isSpinning={isSearching}
title={hasMonitoredBooks ? undefined : 'No monitored books for this author'}
title={hasMonitoredBooks ? undefined : translate('HasMonitoredBooksNoMonitoredBooksForThisAuthor')}
onPress={onSearchPress}
/>
<PageToolbarSeparator />
<PageToolbarButton
label="Preview Rename"
label={translate('PreviewRename')}
iconName={icons.ORGANIZE}
isDisabled={!hasBookFiles}
onPress={this.onOrganizePress}
/>
{/* <PageToolbarButton */}
{/* label="Preview Retag" */}
{/* iconName={icons.RETAG} */}
{/* isDisabled={!hasBookFiles} */}
{/* onPress={this.onRetagPress} */}
{/* /> */}
<PageToolbarButton
label={translate('PreviewRetag')}
iconName={icons.RETAG}
isDisabled={!hasBookFiles}
onPress={this.onRetagPress}
/>
<PageToolbarButton
label="Manual Import"
label={translate('ManualImport')}
iconName={icons.INTERACTIVE}
onPress={this.onInteractiveImportPress}
/>
@@ -273,13 +236,19 @@ class AuthorDetails extends Component {
<PageToolbarSeparator />
<PageToolbarButton
label="Edit"
label={translate('BookMonitoring')}
iconName={icons.MONITORED}
onPress={this.onMonitorOptionsPress}
/>
<PageToolbarButton
label={translate('Edit')}
iconName={icons.EDIT}
onPress={this.onEditAuthorPress}
/>
<PageToolbarButton
label="Delete"
label={translate('Delete')}
iconName={icons.DELETE}
onPress={this.onDeleteAuthorPress}
/>
@@ -287,7 +256,7 @@ class AuthorDetails extends Component {
<PageToolbarSection alignContent={align.RIGHT}>
<PageToolbarButton
label={allExpanded ? 'Collapse All' : 'Expand All'}
label={allExpanded ? translate('AllExpandedCollapseAll') : translate('AllExpandedExpandAll')}
iconName={expandIcon}
onPress={this.onExpandAllPress}
/>
@@ -295,234 +264,43 @@ class AuthorDetails extends Component {
</PageToolbar>
<PageContentBody innerClassName={styles.innerContentBody}>
<div className={styles.header}>
<div
className={styles.backdrop}
style={{
backgroundImage: `url(${getFanartUrl(images)})`
}}
>
<div className={styles.backdropOverlay} />
</div>
<div className={styles.headerContent}>
<AuthorPoster
className={styles.poster}
images={images}
size={250}
lazy={false}
<SwipeHeaderConnector
className={styles.header}
nextLink={`/author/${nextAuthor.titleSlug}`}
nextComponent={(width) => <AuthorDetailsHeaderConnector authorId={nextAuthor.id} width={width} />}
prevLink={`/author/${previousAuthor.titleSlug}`}
prevComponent={(width) => <AuthorDetailsHeaderConnector authorId={previousAuthor.id} width={width} />}
currentComponent={(width) => <AuthorDetailsHeaderConnector authorId={id} width={width} />}
>
<div className={styles.authorNavigationButtons}>
<IconButton
className={styles.authorNavigationButton}
name={icons.ARROW_LEFT}
size={30}
title={translate('GoToInterp', [previousAuthor.authorName])}
to={`/author/${previousAuthor.titleSlug}`}
/>
<div className={styles.info}>
<div className={styles.titleRow}>
<div className={styles.titleContainer}>
<div className={styles.toggleMonitoredContainer}>
<MonitorToggleButton
className={styles.monitorToggleButton}
monitored={monitored}
isSaving={isSaving}
size={40}
onPress={onMonitorTogglePress}
/>
</div>
<IconButton
className={styles.authorUpButton}
name={icons.ARROW_UP}
size={30}
title={translate('GoToAuthorListing')}
to={{
pathname: '/',
state: { restoreScrollPosition: true }
}}
/>
<div className={styles.title}>
{authorName}
</div>
{
!!alternateTitles.length &&
<div className={styles.alternateTitlesIconContainer}>
<Popover
anchor={
<Icon
name={icons.ALTERNATE_TITLES}
size={20}
/>
}
title="Alternate Titles"
body={<AuthorAlternateTitles alternateTitles={alternateTitles} />}
position={tooltipPositions.BOTTOM}
/>
</div>
}
</div>
<div className={styles.authorNavigationButtons}>
<IconButton
className={styles.authorNavigationButton}
name={icons.ARROW_LEFT}
size={30}
title={`Go to ${previousAuthor.authorName}`}
to={`/author/${previousAuthor.titleSlug}`}
/>
<IconButton
className={styles.authorNavigationButton}
name={icons.ARROW_UP}
size={30}
title={'Go to author listing'}
to={'/'}
/>
<IconButton
className={styles.authorNavigationButton}
name={icons.ARROW_RIGHT}
size={30}
title={`Go to ${nextAuthor.authorName}`}
to={`/author/${nextAuthor.titleSlug}`}
/>
</div>
</div>
<div className={styles.details}>
<div>
<HeartRating
rating={ratings.value}
iconSize={20}
/>
</div>
</div>
<div className={styles.detailsLabels}>
<Label
className={styles.detailsLabel}
size={sizes.LARGE}
>
<Icon
name={icons.FOLDER}
size={17}
/>
<span className={styles.path}>
{path}
</span>
</Label>
<Label
className={styles.detailsLabel}
title={bookFilesCountMessage}
size={sizes.LARGE}
>
<Icon
name={icons.DRIVE}
size={17}
/>
<span className={styles.sizeOnDisk}>
{
formatBytes(sizeOnDisk || 0)
}
</span>
</Label>
<Label
className={styles.detailsLabel}
title="Quality Profile"
size={sizes.LARGE}
>
<Icon
name={icons.PROFILE}
size={17}
/>
<span className={styles.qualityProfileName}>
{
<QualityProfileNameConnector
qualityProfileId={qualityProfileId}
/>
}
</span>
</Label>
<Label
className={styles.detailsLabel}
size={sizes.LARGE}
>
<Icon
name={monitored ? icons.MONITORED : icons.UNMONITORED}
size={17}
/>
<span className={styles.qualityProfileName}>
{monitored ? 'Monitored' : 'Unmonitored'}
</span>
</Label>
<Label
className={styles.detailsLabel}
title={continuing ? 'More books are expected' : 'No additional books are expected'}
size={sizes.LARGE}
>
<Icon
name={continuing ? icons.AUTHOR_CONTINUING : icons.AUTHOR_ENDED}
size={17}
/>
<span className={styles.qualityProfileName}>
{continuing ? 'Continuing' : 'Deceased'}
</span>
</Label>
<Tooltip
anchor={
<Label
className={styles.detailsLabel}
size={sizes.LARGE}
>
<Icon
name={icons.EXTERNAL_LINK}
size={17}
/>
<span className={styles.links}>
Links
</span>
</Label>
}
tooltip={
<AuthorDetailsLinks
links={links}
/>
}
kind={kinds.INVERSE}
position={tooltipPositions.BOTTOM}
/>
{
!!tags.length &&
<Tooltip
anchor={
<Label
className={styles.detailsLabel}
size={sizes.LARGE}
>
<Icon
name={icons.TAGS}
size={17}
/>
<span className={styles.tags}>
Tags
</span>
</Label>
}
tooltip={<AuthorTagsConnector authorId={id} />}
kind={kinds.INVERSE}
position={tooltipPositions.BOTTOM}
/>
}
</div>
<div className={styles.overview}>
<TextTruncate
line={Math.floor(125 / (defaultFontSize * lineHeight))}
text={overview}
/>
</div>
</div>
<IconButton
className={styles.authorNavigationButton}
name={icons.ARROW_RIGHT}
size={30}
title={translate('GoToInterp', [nextAuthor.authorName])}
to={`/author/${nextAuthor.titleSlug}`}
/>
</div>
</div>
</SwipeHeaderConnector>
<div className={styles.contentContainer}>
{
@@ -532,12 +310,16 @@ class AuthorDetails extends Component {
{
!isFetching && booksError &&
<div>Loading books failed</div>
<div>
{translate('LoadingBooksFailed')}
</div>
}
{
!isFetching && bookFilesError &&
<div>Loading book files failed</div>
<div>
{translate('LoadingBookFilesFailed')}
</div>
}
{
@@ -550,35 +332,35 @@ class AuthorDetails extends Component {
className={styles.tab}
selectedClassName={styles.selectedTab}
>
Books
{translate('BooksTotal', [statistics.totalBookCount])}
</Tab>
<Tab
className={styles.tab}
selectedClassName={styles.selectedTab}
>
Series
{translate('SeriesTotal', [series.length])}
</Tab>
<Tab
className={styles.tab}
selectedClassName={styles.selectedTab}
>
History
{translate('History')}
</Tab>
<Tab
className={styles.tab}
selectedClassName={styles.selectedTab}
>
Search
{translate('Search')}
</Tab>
<Tab
className={styles.tab}
selectedClassName={styles.selectedTab}
>
Files
{translate('FilesTotal', [statistics.bookFileCount])}
</Tab>
{
@@ -644,10 +426,10 @@ class AuthorDetails extends Component {
</div>
<div className={styles.metadataMessage}>
Missing or too many books? Modify or create a new
<Link to='/settings/profiles'> Metadata Profile </Link>
{translate('TooManyBooks')}
<Link to='/settings/profiles'> {translate('MetadataProfile')} </Link>
or manually
<Link to={`/add/search?term=${encodeURIComponent(authorName)}`}> Search </Link>
<Link to={`/add/search?term=${encodeURIComponent(authorName)}`}> {translate('Search')} </Link>
for new items!
</div>
@@ -685,6 +467,12 @@ class AuthorDetails extends Component {
showImportMode={false}
onModalClose={this.onInteractiveImportModalClose}
/>
<MonitoringOptionsModal
isOpen={isMonitorOptionsModalOpen}
authorId={id}
onModalClose={this.onMonitorOptionsClose}
/>
</PageContentBody>
</PageContent>
);
@@ -719,6 +507,7 @@ AuthorDetails.propTypes = {
hasBookFiles: PropTypes.bool.isRequired,
previousAuthor: PropTypes.object.isRequired,
nextAuthor: PropTypes.object.isRequired,
isSmallScreen: PropTypes.bool.isRequired,
onMonitorTogglePress: PropTypes.func.isRequired,
onRefreshPress: PropTypes.func.isRequired,
onSearchPress: PropTypes.func.isRequired

View File

@@ -6,7 +6,6 @@ import { connect } from 'react-redux';
import { createSelector } from 'reselect';
import * as commandNames from 'Commands/commandNames';
import { toggleAuthorMonitored } from 'Store/Actions/authorActions';
import { clearBooks, fetchBooks } from 'Store/Actions/bookActions';
import { clearBookFiles, fetchBookFiles } from 'Store/Actions/bookFileActions';
import { executeCommand } from 'Store/Actions/commandActions';
import { clearQueueDetails, fetchQueueDetails } from 'Store/Actions/queueActions';
@@ -14,6 +13,7 @@ import { cancelFetchReleases, clearReleases } from 'Store/Actions/releaseActions
import { clearSeries, fetchSeries } from 'Store/Actions/seriesActions';
import createAllAuthorSelector from 'Store/Selectors/createAllAuthorsSelector';
import createCommandsSelector from 'Store/Selectors/createCommandsSelector';
import createDimensionsSelector from 'Store/Selectors/createDimensionsSelector';
import createSortedSectionSelector from 'Store/Selectors/createSortedSectionSelector';
import { findCommand, isCommandExecuting } from 'Utilities/Command';
import { registerPagePopulator, unregisterPagePopulator } from 'Utilities/pagePopulator';
@@ -94,8 +94,9 @@ function createMapStateToProps() {
selectBookFiles,
createAllAuthorSelector(),
createCommandsSelector(),
(titleSlug, books, series, bookFiles, allAuthors, commands) => {
const sortedAuthor = _.orderBy(allAuthors, 'sortName');
createDimensionsSelector(),
(titleSlug, books, series, bookFiles, allAuthors, commands, dimensions) => {
const sortedAuthor = _.orderBy(allAuthors, 'sortNameLastFirst');
const authorIndex = _.findIndex(sortedAuthor, { titleSlug });
const author = sortedAuthor[authorIndex];
@@ -176,15 +177,14 @@ function createMapStateToProps() {
series: seriesItems,
hasBookFiles,
previousAuthor,
nextAuthor
nextAuthor,
isSmallScreen: dimensions.isSmallScreen
};
}
);
}
const mapDispatchToProps = {
fetchBooks,
clearBooks,
fetchSeries,
clearSeries,
fetchBookFiles,
@@ -245,7 +245,6 @@ class AuthorDetailsConnector extends Component {
populate = () => {
const authorId = this.props.id;
this.props.fetchBooks({ authorId });
this.props.fetchSeries({ authorId });
this.props.fetchBookFiles({ authorId });
this.props.fetchQueueDetails({ authorId });
@@ -253,7 +252,6 @@ class AuthorDetailsConnector extends Component {
unpopulate = () => {
this.props.cancelFetchReleases();
this.props.clearBooks();
this.props.clearSeries();
this.props.clearBookFiles();
this.props.clearQueueDetails();
@@ -307,8 +305,6 @@ AuthorDetailsConnector.propTypes = {
isRefreshing: PropTypes.bool.isRequired,
isRenamingFiles: PropTypes.bool.isRequired,
isRenamingAuthor: PropTypes.bool.isRequired,
fetchBooks: PropTypes.func.isRequired,
clearBooks: PropTypes.func.isRequired,
fetchSeries: PropTypes.func.isRequired,
clearSeries: PropTypes.func.isRequired,
fetchBookFiles: PropTypes.func.isRequired,

View File

@@ -0,0 +1,148 @@
.header {
position: relative;
width: 100%;
height: 310px;
}
.backdrop {
position: absolute;
z-index: -1;
width: 100%;
height: 100%;
background-size: cover;
}
.backdropOverlay {
position: absolute;
width: 100%;
height: 100%;
background: $black;
opacity: 0.7;
}
.headerContent {
display: flex;
padding: 30px;
width: 100%;
height: 100%;
color: $white;
}
.poster {
flex-shrink: 0;
margin-right: 35px;
height: 250px;
}
.info {
display: flex;
flex-direction: column;
flex-grow: 1;
overflow: hidden;
}
.titleRow {
position: relative;
display: flex;
justify-content: space-between;
flex: 0 0 auto;
}
.titleContainer {
display: flex;
margin-top: -5px;
}
.title {
font-weight: 300;
font-size: 50px;
line-height: 60px;
}
.toggleMonitoredContainer {
align-self: center;
}
.monitorToggleButton {
composes: toggleButton from '~Components/MonitorToggleButton.css';
width: 40px;
&:hover {
color: $iconButtonHoverLightColor;
}
}
.alternateTitlesIconContainer {
align-self: flex-end;
margin-left: 20px;
}
.authorNavigationButtons {
position: absolute;
right: 0;
z-index: 1;
margin-top: 10px;
padding: 30px;
white-space: nowrap;
}
.authorUpButton,
.authorNavigationButton {
composes: button from '~Components/Link/IconButton.css';
margin-left: 5px;
width: 30px;
color: #e1e2e3;
white-space: nowrap;
&:hover {
color: $iconButtonHoverLightColor;
}
}
.details {
margin-bottom: 8px;
font-weight: 300;
font-size: 20px;
}
.detailsLabel {
composes: label from '~Components/Label.css';
margin: 5px 10px 5px 0;
}
.path,
.sizeOnDisk,
.qualityProfileName,
.links,
.tags {
margin-left: 8px;
font-weight: 300;
font-size: 17px;
}
.overview {
flex: 1 1 auto;
margin-top: 8px;
min-height: 0;
font-size: $intermediateFontSize;
}
@media only screen and (max-width: $breakpointSmall) {
.headerContent {
padding: 15px;
}
.title {
font-size: 30px;
line-height: 50px;
}
}
@media only screen and (max-width: $breakpointLarge) {
.poster {
display: none;
}
}

View File

@@ -0,0 +1,340 @@
import PropTypes from 'prop-types';
import React, { Component } from 'react';
import TextTruncate from 'react-text-truncate';
import AuthorPoster from 'Author/AuthorPoster';
import HeartRating from 'Components/HeartRating';
import Icon from 'Components/Icon';
import Label from 'Components/Label';
import Marquee from 'Components/Marquee';
import Measure from 'Components/Measure';
import MonitorToggleButton from 'Components/MonitorToggleButton';
import Popover from 'Components/Tooltip/Popover';
import Tooltip from 'Components/Tooltip/Tooltip';
import { icons, kinds, sizes, tooltipPositions } from 'Helpers/Props';
import QualityProfileNameConnector from 'Settings/Profiles/Quality/QualityProfileNameConnector';
import fonts from 'Styles/Variables/fonts';
import formatBytes from 'Utilities/Number/formatBytes';
import stripHtml from 'Utilities/String/stripHtml';
import translate from 'Utilities/String/translate';
import AuthorAlternateTitles from './AuthorAlternateTitles';
import AuthorDetailsLinks from './AuthorDetailsLinks';
import AuthorTagsConnector from './AuthorTagsConnector';
import styles from './AuthorDetailsHeader.css';
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?:/, '');
}
}
class AuthorDetailsHeader extends Component {
//
// Lifecyle
constructor(props) {
super(props);
this.state = {
overviewHeight: 0,
titleWidth: 0
};
}
//
// Listeners
onOverviewMeasure = ({ height }) => {
this.setState({ overviewHeight: height });
}
onTitleMeasure = ({ width }) => {
this.setState({ titleWidth: width });
}
//
// Render
render() {
const {
id,
width,
authorName,
ratings,
path,
statistics,
qualityProfileId,
monitored,
status,
overview,
links,
images,
alternateTitles,
tags,
isSaving,
isSmallScreen,
onMonitorTogglePress
} = this.props;
const {
bookFileCount,
sizeOnDisk
} = statistics;
const {
overviewHeight,
titleWidth
} = this.state;
const marqueeWidth = titleWidth - (isSmallScreen ? 85 : 160);
const continuing = status === 'continuing';
let bookFilesCountMessage = translate('BookFilesCountMessage');
if (bookFileCount === 1) {
bookFilesCountMessage = '1 book file';
} else if (bookFileCount > 1) {
bookFilesCountMessage = `${bookFileCount} book files`;
}
return (
<div className={styles.header} style={{ width }} >
<div
className={styles.backdrop}
style={{
backgroundImage: `url(${getFanartUrl(images)})`
}}
>
<div className={styles.backdropOverlay} />
</div>
<div className={styles.headerContent}>
<AuthorPoster
className={styles.poster}
images={images}
size={250}
lazy={false}
/>
<div className={styles.info}>
<Measure
className={styles.titleRow}
onMeasure={this.onTitleMeasure}
>
<div className={styles.titleContainer}>
<div className={styles.toggleMonitoredContainer}>
<MonitorToggleButton
className={styles.monitorToggleButton}
monitored={monitored}
isSaving={isSaving}
size={isSmallScreen ? 30: 40}
onPress={onMonitorTogglePress}
/>
</div>
<div className={styles.title} style={{ width: marqueeWidth }}>
<Marquee text={authorName} />
</div>
{
!!alternateTitles.length &&
<div className={styles.alternateTitlesIconContainer}>
<Popover
anchor={
<Icon
name={icons.ALTERNATE_TITLES}
size={20}
/>
}
title={translate('AlternateTitles')}
body={<AuthorAlternateTitles alternateTitles={alternateTitles} />}
position={tooltipPositions.BOTTOM}
/>
</div>
}
</div>
</Measure>
<div className={styles.details}>
<div>
<HeartRating
rating={ratings.value}
iconSize={20}
/>
</div>
</div>
<div className={styles.detailsLabels}>
<Label
className={styles.detailsLabel}
size={sizes.LARGE}
>
<Icon
name={icons.FOLDER}
size={17}
/>
<span className={styles.path}>
{path}
</span>
</Label>
<Label
className={styles.detailsLabel}
title={bookFilesCountMessage}
size={sizes.LARGE}
>
<Icon
name={icons.DRIVE}
size={17}
/>
<span className={styles.sizeOnDisk}>
{
formatBytes(sizeOnDisk || 0)
}
</span>
</Label>
<Label
className={styles.detailsLabel}
title={translate('QualityProfile')}
size={sizes.LARGE}
>
<Icon
name={icons.PROFILE}
size={17}
/>
<span className={styles.qualityProfileName}>
{
<QualityProfileNameConnector
qualityProfileId={qualityProfileId}
/>
}
</span>
</Label>
<Label
className={styles.detailsLabel}
size={sizes.LARGE}
>
<Icon
name={monitored ? icons.MONITORED : icons.UNMONITORED}
size={17}
/>
<span className={styles.qualityProfileName}>
{monitored ? 'Monitored' : 'Unmonitored'}
</span>
</Label>
<Label
className={styles.detailsLabel}
title={continuing ? translate('ContinuingMoreBooksAreExpected') : translate('ContinuingNoAdditionalBooksAreExpected')}
size={sizes.LARGE}
>
<Icon
name={continuing ? icons.AUTHOR_CONTINUING : icons.AUTHOR_ENDED}
size={17}
/>
<span className={styles.qualityProfileName}>
{continuing ? 'Continuing' : 'Deceased'}
</span>
</Label>
<Tooltip
anchor={
<Label
className={styles.detailsLabel}
size={sizes.LARGE}
>
<Icon
name={icons.EXTERNAL_LINK}
size={17}
/>
<span className={styles.links}>
Links
</span>
</Label>
}
tooltip={
<AuthorDetailsLinks
links={links}
/>
}
kind={kinds.INVERSE}
position={tooltipPositions.BOTTOM}
/>
{
!!tags.length &&
<Tooltip
anchor={
<Label
className={styles.detailsLabel}
size={sizes.LARGE}
>
<Icon
name={icons.TAGS}
size={17}
/>
<span className={styles.tags}>
Tags
</span>
</Label>
}
tooltip={<AuthorTagsConnector authorId={id} />}
kind={kinds.INVERSE}
position={tooltipPositions.BOTTOM}
/>
}
</div>
<Measure
onMeasure={this.onOverviewMeasure}
className={styles.overview}
>
<TextTruncate
line={Math.floor(overviewHeight / (defaultFontSize * lineHeight))}
text={stripHtml(overview)}
/>
</Measure>
</div>
</div>
</div>
);
}
}
AuthorDetailsHeader.propTypes = {
id: PropTypes.number.isRequired,
width: PropTypes.number.isRequired,
authorName: PropTypes.string.isRequired,
ratings: PropTypes.object.isRequired,
path: PropTypes.string.isRequired,
statistics: PropTypes.object.isRequired,
qualityProfileId: PropTypes.number.isRequired,
monitored: PropTypes.bool.isRequired,
status: PropTypes.string.isRequired,
overview: PropTypes.string,
links: PropTypes.arrayOf(PropTypes.object).isRequired,
images: PropTypes.arrayOf(PropTypes.object).isRequired,
alternateTitles: PropTypes.arrayOf(PropTypes.string).isRequired,
tags: PropTypes.arrayOf(PropTypes.number).isRequired,
isSaving: PropTypes.bool.isRequired,
isSmallScreen: PropTypes.bool.isRequired,
onMonitorTogglePress: PropTypes.func.isRequired
};
export default AuthorDetailsHeader;

View File

@@ -0,0 +1,71 @@
/* eslint max-params: 0 */
import _ from 'lodash';
import PropTypes from 'prop-types';
import React, { Component } from 'react';
import { connect } from 'react-redux';
import { createSelector } from 'reselect';
import { toggleAuthorMonitored } from 'Store/Actions/authorActions';
import createAuthorSelector from 'Store/Selectors/createAuthorSelector';
import createDimensionsSelector from 'Store/Selectors/createDimensionsSelector';
import AuthorDetailsHeader from './AuthorDetailsHeader';
function createMapStateToProps() {
return createSelector(
(state) => state.authors,
createAuthorSelector(),
createDimensionsSelector(),
(authors, author, dimensions) => {
const alternateTitles = _.reduce(author.alternateTitles, (acc, alternateTitle) => {
if ((alternateTitle.seasonNumber === -1 || alternateTitle.seasonNumber === undefined) &&
(alternateTitle.sceneSeasonNumber === -1 || alternateTitle.sceneSeasonNumber === undefined)) {
acc.push(alternateTitle.title);
}
return acc;
}, []);
return {
...author,
isSaving: authors.isSaving,
alternateTitles,
isSmallScreen: dimensions.isSmallScreen
};
}
);
}
const mapDispatchToProps = {
toggleAuthorMonitored
};
class AuthorDetailsHeaderConnector extends Component {
//
// Listeners
onMonitorTogglePress = (monitored) => {
this.props.toggleAuthorMonitored({
authorId: this.props.authorId,
monitored
});
}
//
// Render
render() {
return (
<AuthorDetailsHeader
{...this.props}
onMonitorTogglePress={this.onMonitorTogglePress}
/>
);
}
}
AuthorDetailsHeaderConnector.propTypes = {
authorId: PropTypes.number.isRequired,
toggleAuthorMonitored: PropTypes.func.isRequired
};
export default connect(createMapStateToProps, mapDispatchToProps)(AuthorDetailsHeaderConnector);

View File

@@ -9,6 +9,7 @@ import NotFound from 'Components/NotFound';
import PageContent from 'Components/Page/PageContent';
import PageContentBody from 'Components/Page/PageContentBody';
import getErrorMessage from 'Utilities/Object/getErrorMessage';
import translate from 'Utilities/String/translate';
import AuthorDetailsConnector from './AuthorDetailsConnector';
import styles from './AuthorDetails.css';
@@ -92,7 +93,7 @@ class AuthorDetailsPageConnector extends Component {
if (!titleSlug) {
return (
<NotFound
message="Sorry, that author cannot be found."
message={translate('SorryThatAuthorCannotBeFound')}
/>
);
}

View File

@@ -4,24 +4,22 @@ import PropTypes from 'prop-types';
import React, { Component } from 'react';
import { connect } from 'react-redux';
import { createSelector } from 'reselect';
import { setBooksSort, setBooksTableOption, toggleBooksMonitored } from 'Store/Actions/bookActions';
import { setAuthorDetailsId, setAuthorDetailsSort } from 'Store/Actions/authorDetailsActions';
import { setBooksTableOption, toggleBooksMonitored } from 'Store/Actions/bookActions';
import { executeCommand } from 'Store/Actions/commandActions';
import createAuthorSelector from 'Store/Selectors/createAuthorSelector';
import createClientSideCollectionSelector from 'Store/Selectors/createClientSideCollectionSelector';
import createCommandsSelector from 'Store/Selectors/createCommandsSelector';
import createDimensionsSelector from 'Store/Selectors/createDimensionsSelector';
import createUISettingsSelector from 'Store/Selectors/createUISettingsSelector';
import AuthorDetailsSeason from './AuthorDetailsSeason';
function createMapStateToProps() {
return createSelector(
(state, { label }) => label,
createClientSideCollectionSelector('books'),
createClientSideCollectionSelector('books', 'authorDetails'),
createAuthorSelector(),
createCommandsSelector(),
createDimensionsSelector(),
createUISettingsSelector(),
(label, books, author, commands, dimensions, uiSettings) => {
(books, author, dimensions, uiSettings) => {
const booksInGroup = books.items;
@@ -47,14 +45,22 @@ function createMapStateToProps() {
}
const mapDispatchToProps = {
setAuthorDetailsId,
setAuthorDetailsSort,
toggleBooksMonitored,
setBooksTableOption,
dispatchSetBookSort: setBooksSort,
executeCommand
};
class AuthorDetailsSeasonConnector extends Component {
//
// Lifecycle
componentDidMount() {
this.props.setAuthorDetailsId({ authorId: this.props.authorId });
}
//
// Listeners
@@ -63,7 +69,7 @@ class AuthorDetailsSeasonConnector extends Component {
}
onSortPress = (sortKey) => {
this.props.dispatchSetBookSort({ sortKey });
this.props.setAuthorDetailsSort({ sortKey });
}
onMonitorBookPress = (bookIds, monitored) => {
@@ -92,7 +98,8 @@ AuthorDetailsSeasonConnector.propTypes = {
authorId: PropTypes.number.isRequired,
toggleBooksMonitored: PropTypes.func.isRequired,
setBooksTableOption: PropTypes.func.isRequired,
dispatchSetBookSort: PropTypes.func.isRequired,
setAuthorDetailsId: PropTypes.func.isRequired,
setAuthorDetailsSort: PropTypes.func.isRequired,
executeCommand: PropTypes.func.isRequired
};

View File

@@ -100,12 +100,6 @@
composes: actionButton;
margin-right: 15px;
/* position: absolute; */
/* top: 50%; */
/* left: 90%; */
/* margin-top: -12px; */
/* margin-left: -15px; */
}
.noBooks {

View File

@@ -8,6 +8,7 @@ import MonitorToggleButton from 'Components/MonitorToggleButton';
import Table from 'Components/Table/Table';
import TableBody from 'Components/Table/TableBody';
import { icons, sortDirections } from 'Helpers/Props';
import translate from 'Utilities/String/translate';
import getToggledRange from 'Utilities/Table/getToggledRange';
import BookRowConnector from './BookRowConnector';
import styles from './AuthorDetailsSeries.css';
@@ -152,7 +153,7 @@ class AuthorDetailsSeries extends Component {
<Icon
className={styles.expandButtonIcon}
name={isExpanded ? icons.COLLAPSE : icons.EXPAND}
title={isExpanded ? 'Hide books' : 'Show books'}
title={isExpanded ? translate('IsExpandedHideBooks') : translate('IsExpandedShowBooks')}
size={24}
/>
@@ -198,7 +199,7 @@ class AuthorDetailsSeries extends Component {
iconClassName={styles.collapseButtonIcon}
name={icons.COLLAPSE}
size={20}
title="Hide books"
title={translate('HideBooks')}
onPress={this.onExpandPress}
/>
</div>

View File

@@ -4,13 +4,12 @@ import PropTypes from 'prop-types';
import React, { Component } from 'react';
import { connect } from 'react-redux';
import { createSelector } from 'reselect';
import { setBooksTableOption, toggleBooksMonitored } from 'Store/Actions/bookActions';
import { toggleBooksMonitored } from 'Store/Actions/bookActions';
import { executeCommand } from 'Store/Actions/commandActions';
import { setSeriesSort } from 'Store/Actions/seriesActions';
import { setSeriesSort, setSeriesTableOption } from 'Store/Actions/seriesActions';
import createAuthorSelector from 'Store/Selectors/createAuthorSelector';
import createCommandsSelector from 'Store/Selectors/createCommandsSelector';
import createDimensionsSelector from 'Store/Selectors/createDimensionsSelector';
// import createClientSideCollectionSelector from 'Store/Selectors/createClientSideCollectionSelector';
import createUISettingsSelector from 'Store/Selectors/createUISettingsSelector';
import AuthorDetailsSeries from './AuthorDetailsSeries';
@@ -70,7 +69,7 @@ function createMapStateToProps() {
const mapDispatchToProps = {
toggleBooksMonitored,
setBooksTableOption,
setSeriesTableOption,
dispatchSetSeriesSort: setSeriesSort,
executeCommand
};
@@ -81,7 +80,7 @@ class AuthorDetailsSeasonConnector extends Component {
// Listeners
onTableOptionChange = (payload) => {
this.props.setBooksTableOption(payload);
this.props.setSeriesTableOption(payload);
}
onSortPress = (sortKey) => {
@@ -113,7 +112,7 @@ class AuthorDetailsSeasonConnector extends Component {
AuthorDetailsSeasonConnector.propTypes = {
authorId: PropTypes.number.isRequired,
toggleBooksMonitored: PropTypes.func.isRequired,
setBooksTableOption: PropTypes.func.isRequired,
setSeriesTableOption: PropTypes.func.isRequired,
dispatchSetSeriesSort: PropTypes.func.isRequired,
executeCommand: PropTypes.func.isRequired
};

View File

@@ -1,7 +1,5 @@
.title {
composes: cell from '~Components/Table/Cells/TableRowCell.css';
white-space: nowrap;
}
.monitored {
@@ -10,8 +8,22 @@
width: 42px;
}
.position,
.rating,
.status {
composes: cell from '~Components/Table/Cells/TableRowCell.css';
width: 100px;
}
.releaseDate {
composes: cell from '~Components/Table/Cells/TableRowCell.css';
width: 120px;
}
.pageCount {
composes: cell from '~Components/Table/Cells/TableRowCell.css';
width: 80px;
}

View File

@@ -2,27 +2,14 @@ import PropTypes from 'prop-types';
import React, { Component } from 'react';
import BookSearchCellConnector from 'Book/BookSearchCellConnector';
import BookTitleLink from 'Book/BookTitleLink';
import Label from 'Components/Label';
import MonitorToggleButton from 'Components/MonitorToggleButton';
import StarRating from 'Components/StarRating';
import RelativeDateCellConnector from 'Components/Table/Cells/RelativeDateCellConnector';
import TableRowCell from 'Components/Table/Cells/TableRowCell';
import TableRow from 'Components/Table/TableRow';
import { kinds, sizes } from 'Helpers/Props';
import BookStatus from './BookStatus';
import styles from './BookRow.css';
function getBookCountKind(monitored, bookFileCount, bookCount) {
if (bookFileCount === bookCount && bookCount > 0) {
return kinds.SUCCESS;
}
if (!monitored) {
return kinds.WARNING;
}
return kinds.DANGER;
}
class BookRow extends Component {
//
@@ -68,7 +55,6 @@ class BookRow extends Component {
id,
authorId,
monitored,
statistics,
releaseDate,
title,
seriesTitle,
@@ -78,14 +64,12 @@ class BookRow extends Component {
isSaving,
authorMonitored,
titleSlug,
bookFiles,
columns
} = this.props;
const {
bookCount,
bookFileCount,
totalBookCount
} = statistics;
const bookFile = bookFiles[0];
const isAvailable = Date.parse(releaseDate) < new Date();
return (
<TableRow>
@@ -145,7 +129,7 @@ class BookRow extends Component {
return (
<TableRowCell
key={name}
className={styles.title}
className={styles.position}
>
{position || ''}
</TableRowCell>
@@ -154,7 +138,10 @@ class BookRow extends Component {
if (name === 'rating') {
return (
<TableRowCell key={name}>
<TableRowCell
key={name}
className={styles.rating}
>
{
<StarRating
rating={ratings.value}
@@ -168,6 +155,7 @@ class BookRow extends Component {
if (name === 'releaseDate') {
return (
<RelativeDateCellConnector
className={styles.releaseDate}
key={name}
date={releaseDate}
/>
@@ -178,6 +166,7 @@ class BookRow extends Component {
return (
<TableRowCell
key={name}
className={styles.pageCount}
>
{pageCount || ''}
</TableRowCell>
@@ -190,15 +179,11 @@ class BookRow extends Component {
key={name}
className={styles.status}
>
<Label
title={`${totalBookCount} books total. ${bookFileCount} books with files.`}
kind={getBookCountKind(monitored, bookFileCount, bookCount)}
size={sizes.MEDIUM}
>
{
<span>{bookFileCount} / {bookCount}</span>
}
</Label>
<BookStatus
isAvailable={isAvailable}
monitored={monitored}
bookFile={bookFile}
/>
</TableRowCell>
);
}
@@ -228,22 +213,15 @@ BookRow.propTypes = {
releaseDate: PropTypes.string,
title: PropTypes.string.isRequired,
seriesTitle: PropTypes.string.isRequired,
position: PropTypes.number,
position: PropTypes.string,
pageCount: PropTypes.number,
ratings: PropTypes.object.isRequired,
titleSlug: PropTypes.string.isRequired,
isSaving: PropTypes.bool,
authorMonitored: PropTypes.bool.isRequired,
statistics: PropTypes.object.isRequired,
bookFiles: PropTypes.arrayOf(PropTypes.object).isRequired,
columns: PropTypes.arrayOf(PropTypes.object).isRequired,
onMonitorBookPress: PropTypes.func.isRequired
};
BookRow.defaultProps = {
statistics: {
bookCount: 0,
bookFileCount: 0
}
};
export default BookRow;

View File

@@ -2,17 +2,38 @@
import { connect } from 'react-redux';
import { createSelector } from 'reselect';
import createAuthorSelector from 'Store/Selectors/createAuthorSelector';
import createBookFileSelector from 'Store/Selectors/createBookFileSelector';
import BookRow from './BookRow';
const selectBookFiles = createSelector(
(state) => state.bookFiles,
(bookFiles) => {
const {
items
} = bookFiles;
const bookFileDict = items.reduce((acc, file) => {
const bookId = file.bookId;
if (!acc.hasOwnProperty(bookId)) {
acc[bookId] = [];
}
acc[bookId].push(file);
return acc;
}, {});
return bookFileDict;
}
);
function createMapStateToProps() {
return createSelector(
createAuthorSelector(),
createBookFileSelector(),
(author = {}, bookFile) => {
selectBookFiles,
(state, { id }) => id,
(author = {}, bookFiles, bookId) => {
return {
authorMonitored: author.monitored,
bookFilePath: bookFile ? bookFile.path : null
bookFiles: bookFiles[bookId] ?? []
};
}
);

View File

@@ -1,3 +1,4 @@
.center {
display: flex;
justify-content: center;

View File

@@ -0,0 +1,78 @@
import PropTypes from 'prop-types';
import React from 'react';
import BookQuality from 'Book/BookQuality';
import Label from 'Components/Label';
import { kinds } from 'Helpers/Props';
import translate from 'Utilities/String/translate';
import styles from './BookStatus.css';
function BookStatus(props) {
const {
isAvailable,
monitored,
bookFile
} = props;
const hasBookFile = !!bookFile;
if (hasBookFile) {
const quality = bookFile.quality;
return (
<div className={styles.center}>
<BookQuality
title={quality.quality.name}
size={bookFile.size}
quality={quality}
isMonitored={monitored}
isCutoffNotMet={bookFile.qualityCutoffNotMet}
/>
</div>
);
}
if (!monitored) {
return (
<div className={styles.center}>
<Label
title={translate('NotMonitored')}
kind={kinds.WARNING}
>
{translate('NotMonitored')}
</Label>
</div>
);
}
if (isAvailable) {
return (
<div className={styles.center}>
<Label
title={translate('BookAvailableButMissing')}
kind={kinds.DANGER}
>
{translate('Missing')}
</Label>
</div>
);
}
return (
<div className={styles.center}>
<Label
title={translate('NotAvailable')}
kind={kinds.INFO}
>
{translate('NotAvailable')}
</Label>
</div>
);
}
BookStatus.propTypes = {
isAvailable: PropTypes.bool,
monitored: PropTypes.bool.isRequired,
bookFile: PropTypes.object
};
export default BookStatus;

View File

@@ -15,6 +15,7 @@ import ModalFooter from 'Components/Modal/ModalFooter';
import ModalHeader from 'Components/Modal/ModalHeader';
import Popover from 'Components/Tooltip/Popover';
import { icons, inputTypes, kinds, tooltipPositions } from 'Helpers/Props';
import translate from 'Utilities/String/translate';
import styles from './EditAuthorModalContent.css';
class EditAuthorModalContent extends Component {
@@ -87,19 +88,23 @@ class EditAuthorModalContent extends Component {
<ModalBody>
<Form {...otherProps}>
<FormGroup>
<FormLabel>Monitored</FormLabel>
<FormLabel>
{translate('Monitored')}
</FormLabel>
<FormInputGroup
type={inputTypes.CHECK}
name="monitored"
helpText="Download monitored books from this author"
helpText={translate('MonitoredHelpText')}
{...monitored}
onChange={onInputChange}
/>
</FormGroup>
<FormGroup>
<FormLabel>Quality Profile</FormLabel>
<FormLabel>
{translate('QualityProfile')}
</FormLabel>
<FormInputGroup
type={inputTypes.QUALITY_PROFILE_SELECT}
@@ -122,7 +127,7 @@ class EditAuthorModalContent extends Component {
name={icons.INFO}
/>
}
title="Metadata Profile"
title={translate('MetadataProfile')}
body={<AuthorMetadataProfilePopoverContent />}
position={tooltipPositions.RIGHT}
/>
@@ -132,7 +137,7 @@ class EditAuthorModalContent extends Component {
<FormInputGroup
type={inputTypes.METADATA_PROFILE_SELECT}
name="metadataProfileId"
helpText="Changes will take place on next author refresh"
helpText={translate('MetadataProfileIdHelpText')}
includeNone={true}
{...metadataProfileId}
onChange={onInputChange}
@@ -141,7 +146,9 @@ class EditAuthorModalContent extends Component {
}
<FormGroup>
<FormLabel>Path</FormLabel>
<FormLabel>
{translate('Path')}
</FormLabel>
<FormInputGroup
type={inputTypes.PATH}
@@ -152,7 +159,9 @@ class EditAuthorModalContent extends Component {
</FormGroup>
<FormGroup>
<FormLabel>Tags</FormLabel>
<FormLabel>
{translate('Tags')}
</FormLabel>
<FormInputGroup
type={inputTypes.TAG}

View File

@@ -6,3 +6,25 @@
margin-top: 20px;
margin-bottom: 10px;
}
.searchForNewBookLabelContainer {
display: flex;
margin-top: 2px;
}
.searchForNewBookLabel {
margin-right: 8px;
font-weight: normal;
}
.searchForNewBookContainer {
composes: container from '~Components/Form/CheckInput.css';
flex: 0 1 0;
}
.searchForNewBookInput {
composes: input from '~Components/Form/CheckInput.css';
margin-top: 0;
}

View File

@@ -1,6 +1,7 @@
import PropTypes from 'prop-types';
import React from 'react';
import React, { Component } from 'react';
import Alert from 'Components/Alert';
import CheckInput from 'Components/Form/CheckInput';
import Icon from 'Components/Icon';
import Button from 'Components/Link/Button';
import ModalBody from 'Components/Modal/ModalBody';
@@ -10,58 +11,114 @@ import ModalHeader from 'Components/Modal/ModalHeader';
import { icons, kinds } from 'Helpers/Props';
import styles from './RetagAuthorModalContent.css';
function RetagAuthorModalContent(props) {
const {
authorNames,
onModalClose,
onRetagAuthorPress
} = props;
class RetagAuthorModalContent extends Component {
return (
<ModalContent onModalClose={onModalClose}>
<ModalHeader>
Retag Selected Author
</ModalHeader>
//
// Lifecycle
<ModalBody>
<Alert>
Tip: To preview the tags that will be written... select "Cancel" then click any author name and use the
<Icon
className={styles.retagIcon}
name={icons.RETAG}
/>
</Alert>
constructor(props, context) {
super(props, context);
<div className={styles.message}>
Are you sure you want to re-tag all files in the {authorNames.length} selected author?
</div>
<ul>
{
authorNames.map((authorName) => {
return (
<li key={authorName}>
{authorName}
</li>
);
})
}
</ul>
</ModalBody>
this.state = {
updateCovers: false,
embedMetadata: false
};
}
<ModalFooter>
<Button onPress={onModalClose}>
Cancel
</Button>
//
// Listeners
<Button
kind={kinds.DANGER}
onPress={onRetagAuthorPress}
>
Retag
</Button>
</ModalFooter>
</ModalContent>
);
onCheckInputChange = ({ name, value }) => {
this.setState({ [name]: value });
}
onRetagAuthorPress = () => {
this.props.onRetagAuthorPress(this.state.updateCovers, this.state.embedMetadata);
}
//
// Render
render() {
const {
authorNames,
onModalClose
} = this.props;
return (
<ModalContent onModalClose={onModalClose}>
<ModalHeader>
Retag Selected Author
</ModalHeader>
<ModalBody>
<Alert>
Tip: To preview the tags that will be written... select "Cancel" then click any author name and use the
<Icon
className={styles.retagIcon}
name={icons.RETAG}
/>
</Alert>
<div className={styles.message}>
Are you sure you want to re-tag all files in the {authorNames.length} selected author?
</div>
<ul>
{
authorNames.map((authorName) => {
return (
<li key={authorName}>
{authorName}
</li>
);
})
}
</ul>
</ModalBody>
<ModalFooter>
<label className={styles.searchForNewBookLabelContainer}>
<span className={styles.searchForNewBookLabel}>
Update Covers
</span>
<CheckInput
containerClassName={styles.searchForNewBookContainer}
className={styles.searchForNewBookInput}
name="updateCovers"
value={this.state.updateCovers}
onChange={this.onCheckInputChange}
/>
</label>
<label className={styles.searchForNewBookLabelContainer}>
<span className={styles.searchForNewBookLabel}>
Embed Metadata
</span>
<CheckInput
containerClassName={styles.searchForNewBookContainer}
className={styles.searchForNewBookInput}
name="embedMetadata"
value={this.state.embedMetadata}
onChange={this.onCheckInputChange}
/>
</label>
<Button onPress={onModalClose}>
Cancel
</Button>
<Button
kind={kinds.DANGER}
onPress={this.onRetagAuthorPress}
>
Retag
</Button>
</ModalFooter>
</ModalContent>
);
}
}
RetagAuthorModalContent.propTypes = {

View File

@@ -36,10 +36,12 @@ class RetagAuthorModalContentConnector extends Component {
//
// Listeners
onRetagAuthorPress = () => {
onRetagAuthorPress = (updateCovers, embedMetadata) => {
this.props.executeCommand({
name: commandNames.RETAG_AUTHOR,
authorIds: this.props.authorIds
authorIds: this.props.authorIds,
updateCovers,
embedMetadata
});
this.props.onModalClose(true);

View File

@@ -14,6 +14,7 @@ import TableBody from 'Components/Table/TableBody';
import TableOptionsModalWrapper from 'Components/Table/TableOptions/TableOptionsModalWrapper';
import { align, icons, sortDirections } from 'Helpers/Props';
import getErrorMessage from 'Utilities/Object/getErrorMessage';
import translate from 'Utilities/String/translate';
import getSelectedIds from 'Utilities/Table/getSelectedIds';
import selectAll from 'Utilities/Table/selectAll';
import toggleSelected from 'Utilities/Table/toggleSelected';
@@ -143,7 +144,7 @@ class AuthorEditor extends Component {
const selectedAuthorIds = this.getSelectedIds();
return (
<PageContent title="Author Editor">
<PageContent title={translate('AuthorEditor')}>
<PageToolbar>
<PageToolbarSection />
<PageToolbarSection alignContent={align.RIGHT}>
@@ -152,7 +153,7 @@ class AuthorEditor extends Component {
onTableOptionChange={onTableOptionChange}
>
<PageToolbarButton
label="Options"
label={translate('Options')}
iconName={icons.TABLE}
/>
</TableOptionsModalWrapper>

View File

@@ -8,6 +8,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 translate from 'Utilities/String/translate';
import AuthorEditorFooterLabel from './AuthorEditorFooterLabel';
import DeleteAuthorModal from './Delete/DeleteAuthorModal';
import TagsModal from './Tags/TagsModal';
@@ -147,7 +148,6 @@ class AuthorEditorFooter extends Component {
monitored,
qualityProfileId,
metadataProfileId,
bookFolder,
rootFolderPath,
savingTags,
isTagsModalOpen,
@@ -162,17 +162,11 @@ class AuthorEditorFooter extends Component {
{ key: 'unmonitored', value: 'Unmonitored' }
];
const bookFolderOptions = [
{ key: NO_CHANGE, value: 'No Change', disabled: true },
{ key: 'yes', value: 'Yes' },
{ key: 'no', value: 'No' }
];
return (
<PageContentFooter>
<div className={styles.inputContainer}>
<AuthorEditorFooterLabel
label="Monitor Author"
label={translate('MonitorAuthor')}
isSaving={isSaving && monitored !== NO_CHANGE}
/>
@@ -203,7 +197,7 @@ class AuthorEditorFooter extends Component {
className={styles.inputContainer}
>
<AuthorEditorFooterLabel
label="Quality Profile"
label={translate('QualityProfile')}
isSaving={isSaving && qualityProfileId !== NO_CHANGE}
/>
@@ -225,7 +219,7 @@ class AuthorEditorFooter extends Component {
className={styles.inputContainer}
>
<AuthorEditorFooterLabel
label="Metadata Profile"
label={translate('MetadataProfile')}
isSaving={isSaving && metadataProfileId !== NO_CHANGE}
/>
@@ -240,28 +234,6 @@ class AuthorEditorFooter extends Component {
);
}
if (name === 'bookFolder') {
return (
<div
key={name}
className={styles.inputContainer}
>
<AuthorEditorFooterLabel
label="Book Folder"
isSaving={isSaving && bookFolder !== NO_CHANGE}
/>
<SelectInput
name="bookFolder"
value={bookFolder}
values={bookFolderOptions}
isDisabled={!selectedCount}
onChange={this.onInputChange}
/>
</div>
);
}
if (name === 'path') {
return (
<div
@@ -269,7 +241,7 @@ class AuthorEditorFooter extends Component {
className={styles.inputContainer}
>
<AuthorEditorFooterLabel
label="Root Folder"
label={translate('RootFolder')}
isSaving={isSaving && rootFolderPath !== NO_CHANGE}
/>
@@ -292,7 +264,7 @@ class AuthorEditorFooter extends Component {
<div className={styles.buttonContainer}>
<div className={styles.buttonContainerContent}>
<AuthorEditorFooterLabel
label={`${selectedCount} Author(s) Selected`}
label={translate('SelectedCountAuthorsSelectedInterp', [selectedCount])}
isSaving={false}
/>

View File

@@ -2,13 +2,11 @@ import PropTypes from 'prop-types';
import React, { Component } from 'react';
import AuthorNameLink from 'Author/AuthorNameLink';
import AuthorStatusCell from 'Author/Index/Table/AuthorStatusCell';
import CheckInput from 'Components/Form/CheckInput';
import TableRowCell from 'Components/Table/Cells/TableRowCell';
import TableSelectCell from 'Components/Table/Cells/TableSelectCell';
import TableRow from 'Components/Table/TableRow';
import TagListConnector from 'Components/TagListConnector';
import formatBytes from 'Utilities/Number/formatBytes';
import styles from './AuthorEditorRow.css';
class AuthorEditorRow extends Component {
@@ -27,10 +25,8 @@ class AuthorEditorRow extends Component {
const {
id,
status,
foreignAuthorId,
titleSlug,
authorName,
authorType,
bookFolder,
monitored,
metadataProfile,
qualityProfile,
@@ -38,9 +34,7 @@ class AuthorEditorRow extends Component {
statistics,
tags,
columns,
isSaving,
isSelected,
onAuthorMonitoredPress,
onSelectedChange
} = this.props;
@@ -67,11 +61,8 @@ class AuthorEditorRow extends Component {
return (
<AuthorStatusCell
key={name}
authorType={authorType}
monitored={monitored}
status={status}
isSaving={isSaving}
onMonitoredPress={onAuthorMonitoredPress}
/>
);
}
@@ -80,10 +71,9 @@ class AuthorEditorRow extends Component {
return (
<TableRowCell
key={name}
className={styles.title}
>
<AuthorNameLink
foreignAuthorId={foreignAuthorId}
titleSlug={titleSlug}
authorName={authorName}
/>
</TableRowCell>
@@ -106,22 +96,6 @@ class AuthorEditorRow extends Component {
);
}
if (name === 'bookFolder') {
return (
<TableRowCell
key={name}
className={styles.bookFolder}
>
<CheckInput
name="bookFolder"
value={bookFolder}
isDisabled={true}
onChange={this.onBookFolderChange}
/>
</TableRowCell>
);
}
if (name === 'path') {
return (
<TableRowCell key={name}>
@@ -159,11 +133,8 @@ class AuthorEditorRow extends Component {
AuthorEditorRow.propTypes = {
id: PropTypes.number.isRequired,
status: PropTypes.string.isRequired,
foreignAuthorId: PropTypes.string.isRequired,
titleSlug: PropTypes.string.isRequired,
authorName: PropTypes.string.isRequired,
authorType: PropTypes.string,
bookFolder: PropTypes.string,
monitored: PropTypes.bool.isRequired,
metadataProfile: PropTypes.object.isRequired,
qualityProfile: PropTypes.object.isRequired,
@@ -171,9 +142,7 @@ AuthorEditorRow.propTypes = {
statistics: PropTypes.object.isRequired,
tags: PropTypes.arrayOf(PropTypes.number).isRequired,
columns: PropTypes.arrayOf(PropTypes.object).isRequired,
isSaving: PropTypes.bool.isRequired,
isSelected: PropTypes.bool,
onAuthorMonitoredPress: PropTypes.func.isRequired,
onSelectedChange: PropTypes.func.isRequired
};

View File

@@ -25,7 +25,7 @@ function OrganizeAuthorModalContent(props) {
<ModalBody>
<Alert>
Tip: To preview a rename, select "Cancel", then select any artist name and use the
Tip: To preview a rename, select "Cancel", then select any author name and use the
<Icon
className={styles.renameIcon}
name={icons.ORGANIZE}

View File

@@ -12,6 +12,7 @@ import ModalContent from 'Components/Modal/ModalContent';
import ModalFooter from 'Components/Modal/ModalFooter';
import ModalHeader from 'Components/Modal/ModalHeader';
import { inputTypes, kinds, sizes } from 'Helpers/Props';
import translate from 'Utilities/String/translate';
import styles from './TagsModalContent.css';
class TagsModalContent extends Component {
@@ -74,7 +75,9 @@ class TagsModalContent extends Component {
<ModalBody>
<Form>
<FormGroup>
<FormLabel>Tags</FormLabel>
<FormLabel>
{translate('Tags')}
</FormLabel>
<FormInputGroup
type={inputTypes.TAG}
@@ -85,7 +88,9 @@ class TagsModalContent extends Component {
</FormGroup>
<FormGroup>
<FormLabel>Apply Tags</FormLabel>
<FormLabel>
{translate('ApplyTags')}
</FormLabel>
<FormInputGroup
type={inputTypes.SELECT}
@@ -93,17 +98,19 @@ class TagsModalContent extends Component {
value={applyTags}
values={applyTagsOptions}
helpTexts={[
'How to apply tags to the selected author',
'Add: Add the tags the existing list of tags',
'Remove: Remove the entered tags',
'Replace: Replace the tags with the entered tags (enter no tags to clear all tags)'
translate('ApplyTagsHelpTexts1'),
translate('ApplyTagsHelpTexts2'),
translate('ApplyTagsHelpTexts3'),
translate('ApplyTagsHelpTexts4')
]}
onChange={this.onInputChange}
/>
</FormGroup>
<FormGroup>
<FormLabel>Result</FormLabel>
<FormLabel>
{translate('Result')}
</FormLabel>
<div className={styles.result}>
{
@@ -120,7 +127,7 @@ class TagsModalContent extends Component {
return (
<Label
key={tag.id}
title={removeTag ? 'Removing tag' : 'Existing tag'}
title={removeTag ? translate('RemoveTagRemovingTag') : translate('RemoveTagExistingTag')}
kind={removeTag ? kinds.INVERSE : kinds.INFO}
size={sizes.LARGE}
>
@@ -146,7 +153,7 @@ class TagsModalContent extends Component {
return (
<Label
key={tag.id}
title={'Adding tag'}
title={translate('AddingTag')}
kind={kinds.SUCCESS}
size={sizes.LARGE}
>

View File

@@ -11,6 +11,7 @@ import TableRowCell from 'Components/Table/Cells/TableRowCell';
import TableRow from 'Components/Table/TableRow';
import Popover from 'Components/Tooltip/Popover';
import { icons, kinds, tooltipPositions } from 'Helpers/Props';
import translate from 'Utilities/String/translate';
import styles from './AuthorHistoryRow.css';
function getTitle(eventType) {
@@ -132,7 +133,7 @@ class AuthorHistoryRow extends Component {
{
eventType === 'grabbed' &&
<IconButton
title="Mark as failed"
title={translate('MarkAsFailed')}
name={icons.REMOVE}
onPress={this.onMarkAsFailedPress}
/>
@@ -142,9 +143,9 @@ class AuthorHistoryRow extends Component {
<ConfirmModal
isOpen={isMarkAsFailedModalOpen}
kind={kinds.DANGER}
title="Mark as Failed"
message={`Are you sure you want to mark '${sourceTitle}' as failed?`}
confirmLabel="Mark as Failed"
title={translate('MarkAsFailed')}
message={translate('MarkAsFailedMessageText', [sourceTitle])}
confirmLabel={translate('MarkAsFailed')}
onConfirm={this.onConfirmMarkAsFailed}
onCancel={this.onMarkAsFailedModalClose}
/>

View File

@@ -3,6 +3,7 @@ import React, { Component } from 'react';
import LoadingIndicator from 'Components/Loading/LoadingIndicator';
import Table from 'Components/Table/Table';
import TableBody from 'Components/Table/TableBody';
import translate from 'Utilities/String/translate';
import AuthorHistoryRowConnector from './AuthorHistoryRowConnector';
const columns = [
@@ -69,12 +70,16 @@ class AuthorHistoryTableContent extends Component {
{
!isFetching && !!error &&
<div>Unable to load history.</div>
<div>
{translate('UnableToLoadHistory')}
</div>
}
{
isPopulated && !hasItems && !error &&
<div>No history.</div>
<div>
{translate('NoHistory')}
</div>
}
{

View File

@@ -14,9 +14,8 @@ import TableOptionsModalWrapper from 'Components/Table/TableOptions/TableOptions
import { align, icons, sortDirections } from 'Helpers/Props';
import getErrorMessage from 'Utilities/Object/getErrorMessage';
import hasDifferentItemsOrOrder from 'Utilities/Object/hasDifferentItemsOrOrder';
import translate from 'Utilities/String/translate';
import AuthorIndexFooterConnector from './AuthorIndexFooterConnector';
import AuthorIndexBannersConnector from './Banners/AuthorIndexBannersConnector';
import AuthorIndexBannerOptionsModal from './Banners/Options/AuthorIndexBannerOptionsModal';
import AuthorIndexFilterMenu from './Menus/AuthorIndexFilterMenu';
import AuthorIndexSortMenu from './Menus/AuthorIndexSortMenu';
import AuthorIndexViewMenu from './Menus/AuthorIndexViewMenu';
@@ -33,10 +32,6 @@ function getViewComponent(view) {
return AuthorIndexPostersConnector;
}
if (view === 'banners') {
return AuthorIndexBannersConnector;
}
if (view === 'overview') {
return AuthorIndexOverviewsConnector;
}
@@ -57,7 +52,6 @@ class AuthorIndex extends Component {
jumpBarItems: { order: [] },
jumpToCharacter: null,
isPosterOptionsModalOpen: false,
isBannerOptionsModalOpen: false,
isOverviewOptionsModalOpen: false
};
}
@@ -100,13 +94,13 @@ class AuthorIndex extends Component {
} = this.props;
// Reset if not sorting by sortName
if (sortKey !== 'sortName') {
if (sortKey !== 'sortName' && sortKey !== 'sortNameLastFirst') {
this.setState({ jumpBarItems: { order: [] } });
return;
}
const characters = _.reduce(items, (acc, item) => {
let char = item.sortName.charAt(0);
let char = item[sortKey].charAt(0);
if (!isNaN(char)) {
char = '#';
@@ -147,14 +141,6 @@ class AuthorIndex extends Component {
this.setState({ isPosterOptionsModalOpen: false });
}
onBannerOptionsPress = () => {
this.setState({ isBannerOptionsModalOpen: true });
}
onBannerOptionsModalClose = () => {
this.setState({ isBannerOptionsModalOpen: false });
}
onOverviewOptionsPress = () => {
this.setState({ isOverviewOptionsModalOpen: true });
}
@@ -200,7 +186,6 @@ class AuthorIndex extends Component {
jumpBarItems,
jumpToCharacter,
isPosterOptionsModalOpen,
isBannerOptionsModalOpen,
isOverviewOptionsModalOpen
} = this.state;
@@ -213,7 +198,7 @@ class AuthorIndex extends Component {
<PageToolbar>
<PageToolbarSection>
<PageToolbarButton
label="Update all"
label={translate('UpdateAll')}
iconName={icons.REFRESH}
spinningName={icons.REFRESH}
isSpinning={isRefreshingAuthor}
@@ -221,7 +206,7 @@ class AuthorIndex extends Component {
/>
<PageToolbarButton
label="RSS Sync"
label={translate('RSSSync')}
iconName={icons.RSS}
isSpinning={isRssSyncExecuting}
isDisabled={hasNoAuthor}
@@ -242,7 +227,7 @@ class AuthorIndex extends Component {
optionsComponent={AuthorIndexTableOptionsConnector}
>
<PageToolbarButton
label="Options"
label={translate('Options')}
iconName={icons.TABLE}
/>
</TableOptionsModalWrapper> :
@@ -252,7 +237,7 @@ class AuthorIndex extends Component {
{
view === 'posters' ?
<PageToolbarButton
label="Options"
label={translate('Options')}
iconName={icons.POSTER}
isDisabled={hasNoAuthor}
onPress={this.onPosterOptionsPress}
@@ -260,21 +245,10 @@ class AuthorIndex extends Component {
null
}
{
view === 'banners' ?
<PageToolbarButton
label="Options"
iconName={icons.POSTER}
isDisabled={hasNoAuthor}
onPress={this.onBannerOptionsPress}
/> :
null
}
{
view === 'overview' ?
<PageToolbarButton
label="Options"
label={translate('Options')}
iconName={icons.OVERVIEW}
isDisabled={hasNoAuthor}
onPress={this.onOverviewOptionsPress}
@@ -282,11 +256,7 @@ class AuthorIndex extends Component {
null
}
{
(view === 'posters' || view === 'banners' || view === 'overview') &&
<PageToolbarSeparator />
}
<PageToolbarSeparator />
<AuthorIndexViewMenu
view={view}
@@ -367,12 +337,6 @@ class AuthorIndex extends Component {
onModalClose={this.onPosterOptionsModalClose}
/>
<AuthorIndexBannerOptionsModal
isOpen={isBannerOptionsModalOpen}
onModalClose={this.onBannerOptionsModalClose}
/>
<AuthorIndexOverviewOptionsModal
isOpen={isOverviewOptionsModalOpen}
onModalClose={this.onOverviewOptionsModalClose}

View File

@@ -5,6 +5,7 @@ import { ColorImpairedConsumer } from 'App/ColorImpairedContext';
import DescriptionList from 'Components/DescriptionList/DescriptionList';
import DescriptionListItem from 'Components/DescriptionList/DescriptionListItem';
import formatBytes from 'Utilities/Number/formatBytes';
import translate from 'Utilities/String/translate';
import styles from './AuthorIndexFooter.css';
class AuthorIndexFooter extends PureComponent {
@@ -60,7 +61,9 @@ class AuthorIndexFooter extends PureComponent {
enableColorImpairedMode && 'colorImpaired'
)}
/>
<div>Continuing (All books downloaded)</div>
<div>
{translate('ContinuingAllBooksDownloaded')}
</div>
</div>
<div className={styles.legendItem}>
@@ -70,7 +73,9 @@ class AuthorIndexFooter extends PureComponent {
enableColorImpairedMode && 'colorImpaired'
)}
/>
<div>Ended (All books downloaded)</div>
<div>
{translate('EndedAllBooksDownloaded')}
</div>
</div>
<div className={styles.legendItem}>
@@ -80,7 +85,9 @@ class AuthorIndexFooter extends PureComponent {
enableColorImpairedMode && 'colorImpaired'
)}
/>
<div>Missing Books (Author monitored)</div>
<div>
{translate('MissingBooksAuthorMonitored')}
</div>
</div>
<div className={styles.legendItem}>
@@ -90,55 +97,57 @@ class AuthorIndexFooter extends PureComponent {
enableColorImpairedMode && 'colorImpaired'
)}
/>
<div>Missing Books (Author not monitored)</div>
<div>
{translate('MissingBooksAuthorNotMonitored')}
</div>
</div>
</div>
<div className={styles.statistics}>
<DescriptionList>
<DescriptionListItem
title="Authors"
title={translate('Authors')}
data={count}
/>
<DescriptionListItem
title="Ended"
title={translate('Ended')}
data={ended}
/>
<DescriptionListItem
title="Continuing"
title={translate('Continuing')}
data={continuing}
/>
</DescriptionList>
<DescriptionList>
<DescriptionListItem
title="Monitored"
title={translate('Monitored')}
data={monitored}
/>
<DescriptionListItem
title="Unmonitored"
title={translate('Unmonitored')}
data={count - monitored}
/>
</DescriptionList>
<DescriptionList>
<DescriptionListItem
title="Books"
title={translate('Books')}
data={books}
/>
<DescriptionListItem
title="Files"
title={translate('Files')}
data={bookFiles}
/>
</DescriptionList>
<DescriptionList>
<DescriptionListItem
title="Total File Size"
title={translate('TotalFileSize')}
data={formatBytes(totalFileSize)}
/>
</DescriptionList>

View File

@@ -34,16 +34,16 @@ function AuthorIndexSortMenu(props) {
sortDirection={sortDirection}
onPress={onSortSelect}
>
Name
First Name
</SortMenuItem>
<SortMenuItem
name="authorType"
name="sortNameLastFirst"
sortKey={sortKey}
sortDirection={sortDirection}
onPress={onSortSelect}
>
Type
Last Name
</SortMenuItem>
<SortMenuItem
@@ -101,21 +101,12 @@ function AuthorIndexSortMenu(props) {
</SortMenuItem>
<SortMenuItem
name="trackProgress"
name="bookProgress"
sortKey={sortKey}
sortDirection={sortDirection}
onPress={onSortSelect}
>
Books
</SortMenuItem>
<SortMenuItem
name="bookCount"
sortKey={sortKey}
sortDirection={sortDirection}
onPress={onSortSelect}
>
Book Count
Books Progress
</SortMenuItem>
<SortMenuItem

View File

@@ -34,14 +34,6 @@ function AuthorIndexViewMenu(props) {
Posters
</ViewMenuItem>
<ViewMenuItem
name="banners"
selectedView={view}
onPress={onViewSelect}
>
Banners
</ViewMenuItem>
<ViewMenuItem
name="overview"
selectedView={view}

View File

@@ -14,11 +14,14 @@ $hoverScale: 1.05;
}
.poster {
position: relative;
position: absolute;
top: 0;
left: 0;
}
.posterContainer {
position: relative;
overflow: hidden;
}
.link {

View File

@@ -11,6 +11,8 @@ import SpinnerIconButton from 'Components/Link/SpinnerIconButton';
import { icons } from 'Helpers/Props';
import dimensions from 'Styles/Variables/dimensions';
import fonts from 'Styles/Variables/fonts';
import stripHtml from 'Utilities/String/stripHtml';
import translate from 'Utilities/String/translate';
import AuthorIndexOverviewInfo from './AuthorIndexOverviewInfo';
import styles from './AuthorIndexOverview.css';
@@ -72,6 +74,7 @@ class AuthorIndexOverview extends Component {
const {
id,
authorName,
authorNameLastFirst,
overview,
monitored,
status,
@@ -123,31 +126,30 @@ class AuthorIndexOverview extends Component {
return (
<div className={styles.container}>
<div className={styles.content}>
<div className={styles.poster}>
<div className={styles.posterContainer}>
{
status === 'ended' &&
<div
className={styles.ended}
title="Ended"
/>
}
<Link
className={styles.link}
style={elementStyle}
to={link}
>
<AuthorPoster
className={styles.poster}
style={elementStyle}
images={images}
size={250}
lazy={false}
overflow={true}
<div className={styles.posterContainer}>
{
status === 'ended' &&
<div
className={styles.ended}
title={translate('Ended')}
/>
</Link>
</div>
}
<Link
className={styles.link}
style={elementStyle}
to={link}
>
<AuthorPoster
className={styles.poster}
style={elementStyle}
images={images}
size={250}
lazy={false}
overflow={true}
blurBackground={true}
/>
</Link>
<AuthorIndexProgressBar
monitored={monitored}
@@ -166,13 +168,13 @@ class AuthorIndexOverview extends Component {
className={styles.title}
to={link}
>
{authorName}
{overviewOptions.showTitle === 'firstLast' ? authorName : authorNameLastFirst}
</Link>
<div className={styles.actions}>
<SpinnerIconButton
name={icons.REFRESH}
title="Refresh Author"
title={translate('RefreshAuthor')}
isSpinning={isRefreshingAuthor}
onPress={onRefreshAuthorPress}
/>
@@ -182,7 +184,7 @@ class AuthorIndexOverview extends Component {
<SpinnerIconButton
className={styles.action}
name={icons.SEARCH}
title="Search for monitored books"
title={translate('SearchForMonitoredBooks')}
isSpinning={isSearchingAuthor}
onPress={onSearchPress}
/>
@@ -190,7 +192,7 @@ class AuthorIndexOverview extends Component {
<IconButton
name={icons.EDIT}
title="Edit Author"
title={translate('EditAuthor')}
onPress={this.onEditAuthorPress}
/>
</div>
@@ -204,7 +206,7 @@ class AuthorIndexOverview extends Component {
>
<TextTruncate
line={Math.floor(overviewHeight / (defaultFontSize * lineHeight))}
text={overview}
text={stripHtml(overview)}
/>
</Link>
@@ -246,7 +248,8 @@ class AuthorIndexOverview extends Component {
AuthorIndexOverview.propTypes = {
id: PropTypes.number.isRequired,
authorName: PropTypes.string.isRequired,
overview: PropTypes.string.isRequired,
authorNameLastFirst: PropTypes.string.isRequired,
overview: PropTypes.string,
monitored: PropTypes.bool.isRequired,
status: PropTypes.string.isRequired,
titleSlug: PropTypes.string.isRequired,

View File

@@ -60,7 +60,8 @@ class AuthorIndexOverviews extends Component {
columnCount: 1,
posterWidth: 238,
posterHeight: 238,
rowHeight: calculateRowHeight(238, null, props.isSmallScreen, {})
rowHeight: calculateRowHeight(238, null, props.isSmallScreen, {}),
scrollRestored: false
};
this._grid = null;
@@ -71,12 +72,14 @@ class AuthorIndexOverviews extends Component {
items,
sortKey,
overviewOptions,
jumpToCharacter
jumpToCharacter,
scrollTop
} = this.props;
const {
width,
rowHeight
rowHeight,
scrollRestored
} = this.state;
if (prevProps.sortKey !== sortKey ||
@@ -87,13 +90,19 @@ class AuthorIndexOverviews extends Component {
if (this._grid &&
(prevState.width !== width ||
prevState.rowHeight !== rowHeight ||
hasDifferentItemsOrOrder(prevProps.items, items))) {
hasDifferentItemsOrOrder(prevProps.items, items) ||
prevProps.overviewOptions.showTitle !== overviewOptions.showTitle)) {
// recomputeGridSize also forces Grid to discard its cache of rendered cells
this._grid.recomputeGridSize();
}
if (this._grid && scrollTop !== 0 && !scrollRestored) {
this.setState({ scrollRestored: true });
this._grid.scrollToPosition({ scrollTop });
}
if (jumpToCharacter != null && jumpToCharacter !== prevProps.jumpToCharacter) {
const index = getIndexOfFirstCharacter(items, jumpToCharacter);
const index = getIndexOfFirstCharacter(items, sortKey, jumpToCharacter);
if (this._grid && index != null) {
@@ -204,7 +213,6 @@ class AuthorIndexOverviews extends Component {
return (
<Measure
whitelist={['width']}
onMeasure={this.onMeasure}
>
<WindowScroller

View File

@@ -11,6 +11,12 @@ import ModalContent from 'Components/Modal/ModalContent';
import ModalFooter from 'Components/Modal/ModalFooter';
import ModalHeader from 'Components/Modal/ModalHeader';
import { inputTypes } from 'Helpers/Props';
import translate from 'Utilities/String/translate';
const nameOptions = [
{ key: 'firstLast', value: translate('NameFirstLast') },
{ key: 'lastFirst', value: translate('NameLastFirst') }
];
const posterSizeOptions = [
{ key: 'small', value: 'Small' },
@@ -27,6 +33,7 @@ class AuthorIndexOverviewOptionsModalContent extends Component {
super(props, context);
this.state = {
showTitle: props.showTitle,
detailedProgressBar: props.detailedProgressBar,
size: props.size,
showMonitored: props.showMonitored,
@@ -42,6 +49,7 @@ class AuthorIndexOverviewOptionsModalContent extends Component {
componentDidUpdate(prevProps) {
const {
showTitle,
detailedProgressBar,
size,
showMonitored,
@@ -56,6 +64,10 @@ class AuthorIndexOverviewOptionsModalContent extends Component {
const state = {};
if (showTitle !== prevProps.showTitle) {
state.showTitle = showTitle;
}
if (detailedProgressBar !== prevProps.detailedProgressBar) {
state.detailedProgressBar = detailedProgressBar;
}
@@ -121,6 +133,7 @@ class AuthorIndexOverviewOptionsModalContent extends Component {
} = this.props;
const {
showTitle,
detailedProgressBar,
size,
showMonitored,
@@ -142,7 +155,23 @@ class AuthorIndexOverviewOptionsModalContent extends Component {
<ModalBody>
<Form>
<FormGroup>
<FormLabel>Poster Size</FormLabel>
<FormLabel>
{translate('NameStyle')}
</FormLabel>
<FormInputGroup
type={inputTypes.SELECT}
name="showTitle"
value={showTitle}
values={nameOptions}
onChange={this.onChangeOverviewOption}
/>
</FormGroup>
<FormGroup>
<FormLabel>
{translate('PosterSize')}
</FormLabel>
<FormInputGroup
type={inputTypes.SELECT}
@@ -154,19 +183,23 @@ class AuthorIndexOverviewOptionsModalContent extends Component {
</FormGroup>
<FormGroup>
<FormLabel>Detailed Progress Bar</FormLabel>
<FormLabel>
{translate('DetailedProgressBar')}
</FormLabel>
<FormInputGroup
type={inputTypes.CHECK}
name="detailedProgressBar"
value={detailedProgressBar}
helpText="Show text on progess bar"
helpText={translate('DetailedProgressBarHelpText')}
onChange={this.onChangeOverviewOption}
/>
</FormGroup>
<FormGroup>
<FormLabel>Show Monitored</FormLabel>
<FormLabel>
{translate('ShowMonitored')}
</FormLabel>
<FormInputGroup
type={inputTypes.CHECK}
@@ -178,7 +211,9 @@ class AuthorIndexOverviewOptionsModalContent extends Component {
<FormGroup>
<FormLabel>Show Quality Profile</FormLabel>
<FormLabel>
{translate('ShowQualityProfile')}
</FormLabel>
<FormInputGroup
type={inputTypes.CHECK}
@@ -189,7 +224,9 @@ class AuthorIndexOverviewOptionsModalContent extends Component {
</FormGroup>
<FormGroup>
<FormLabel>Show Last Book</FormLabel>
<FormLabel>
{translate('ShowLastBook')}
</FormLabel>
<FormInputGroup
type={inputTypes.CHECK}
@@ -200,7 +237,9 @@ class AuthorIndexOverviewOptionsModalContent extends Component {
</FormGroup>
<FormGroup>
<FormLabel>Show Date Added</FormLabel>
<FormLabel>
{translate('ShowDateAdded')}
</FormLabel>
<FormInputGroup
type={inputTypes.CHECK}
@@ -211,7 +250,9 @@ class AuthorIndexOverviewOptionsModalContent extends Component {
</FormGroup>
<FormGroup>
<FormLabel>Show Book Count</FormLabel>
<FormLabel>
{translate('ShowBookCount')}
</FormLabel>
<FormInputGroup
type={inputTypes.CHECK}
@@ -222,7 +263,9 @@ class AuthorIndexOverviewOptionsModalContent extends Component {
</FormGroup>
<FormGroup>
<FormLabel>Show Path</FormLabel>
<FormLabel>
{translate('ShowPath')}
</FormLabel>
<FormInputGroup
type={inputTypes.CHECK}
@@ -233,7 +276,9 @@ class AuthorIndexOverviewOptionsModalContent extends Component {
</FormGroup>
<FormGroup>
<FormLabel>Show Size on Disk</FormLabel>
<FormLabel>
{translate('ShowSizeOnDisk')}
</FormLabel>
<FormInputGroup
type={inputTypes.CHECK}
@@ -244,13 +289,15 @@ class AuthorIndexOverviewOptionsModalContent extends Component {
</FormGroup>
<FormGroup>
<FormLabel>Show Search</FormLabel>
<FormLabel>
{translate('ShowSearch')}
</FormLabel>
<FormInputGroup
type={inputTypes.CHECK}
name="showSearchAction"
value={showSearchAction}
helpText="Show search button"
helpText={translate('ShowSearchActionHelpText')}
onChange={this.onChangeOverviewOption}
/>
</FormGroup>
@@ -270,6 +317,7 @@ class AuthorIndexOverviewOptionsModalContent extends Component {
}
AuthorIndexOverviewOptionsModalContent.propTypes = {
showTitle: PropTypes.string.isRequired,
size: PropTypes.string.isRequired,
detailedProgressBar: PropTypes.bool.isRequired,
showMonitored: PropTypes.bool.isRequired,

View File

@@ -17,6 +17,13 @@ $hoverScale: 1.05;
.posterContainer {
position: relative;
overflow: hidden;
}
.poster {
position: absolute;
top: 0;
left: 0;
}
.link {

View File

@@ -10,6 +10,7 @@ import Link from 'Components/Link/Link';
import SpinnerIconButton from 'Components/Link/SpinnerIconButton';
import { icons } from 'Helpers/Props';
import getRelativeDate from 'Utilities/Date/getRelativeDate';
import translate from 'Utilities/String/translate';
import AuthorIndexPosterInfo from './AuthorIndexPosterInfo';
import styles from './AuthorIndexPoster.css';
@@ -69,6 +70,7 @@ class AuthorIndexPoster extends Component {
const {
id,
authorName,
authorNameLastFirst,
monitored,
titleSlug,
status,
@@ -122,7 +124,7 @@ class AuthorIndexPoster extends Component {
<SpinnerIconButton
className={styles.action}
name={icons.REFRESH}
title="Refresh Author"
title={translate('RefreshAuthor')}
isSpinning={isRefreshingAuthor}
onPress={onRefreshAuthorPress}
/>
@@ -132,7 +134,7 @@ class AuthorIndexPoster extends Component {
<SpinnerIconButton
className={styles.action}
name={icons.SEARCH}
title="Search for monitored books"
title={translate('SearchForMonitoredBooks')}
isSpinning={isSearchingAuthor}
onPress={onSearchPress}
/>
@@ -141,7 +143,7 @@ class AuthorIndexPoster extends Component {
<IconButton
className={styles.action}
name={icons.EDIT}
title="Edit Author"
title={translate('EditAuthor')}
onPress={this.onEditAuthorPress}
/>
</Label>
@@ -150,7 +152,7 @@ class AuthorIndexPoster extends Component {
status === 'ended' &&
<div
className={styles.ended}
title="Ended"
title={translate('Ended')}
/>
}
@@ -166,6 +168,7 @@ class AuthorIndexPoster extends Component {
size={250}
lazy={false}
overflow={true}
blurBackground={true}
onError={this.onPosterLoadError}
onLoad={this.onPosterLoad}
/>
@@ -191,9 +194,9 @@ class AuthorIndexPoster extends Component {
/>
{
showTitle &&
showTitle !== 'no' &&
<div className={styles.title}>
{authorName}
{showTitle === 'firstLast' ? authorName : authorNameLastFirst}
</div>
}
@@ -258,6 +261,7 @@ class AuthorIndexPoster extends Component {
AuthorIndexPoster.propTypes = {
id: PropTypes.number.isRequired,
authorName: PropTypes.string.isRequired,
authorNameLastFirst: PropTypes.string.isRequired,
monitored: PropTypes.bool.isRequired,
status: PropTypes.string.isRequired,
titleSlug: PropTypes.string.isRequired,
@@ -267,7 +271,7 @@ AuthorIndexPoster.propTypes = {
posterWidth: PropTypes.number.isRequired,
posterHeight: PropTypes.number.isRequired,
detailedProgressBar: PropTypes.bool.isRequired,
showTitle: PropTypes.bool.isRequired,
showTitle: PropTypes.string.isRequired,
showMonitored: PropTypes.bool.isRequired,
showQualityProfile: PropTypes.bool.isRequired,
qualityProfile: PropTypes.object.isRequired,

View File

@@ -50,7 +50,7 @@ function calculateRowHeight(posterHeight, sortKey, isSmallScreen, posterOptions)
isSmallScreen ? columnPaddingSmallScreen : columnPadding
];
if (showTitle) {
if (showTitle !== 'no') {
heights.push(19);
}
@@ -100,7 +100,8 @@ class AuthorIndexPosters extends Component {
columnCount: 1,
posterWidth: 238,
posterHeight: 238,
rowHeight: calculateRowHeight(238, null, props.isSmallScreen, {})
rowHeight: calculateRowHeight(238, null, props.isSmallScreen, {}),
scrollRestored: false
};
this._isInitialized = false;
@@ -114,15 +115,16 @@ class AuthorIndexPosters extends Component {
sortKey,
posterOptions,
jumpToCharacter,
scrollTop,
isSmallScreen
isSmallScreen,
scrollTop
} = this.props;
const {
width,
columnWidth,
columnCount,
rowHeight
rowHeight,
scrollRestored
} = this.state;
if (prevProps.sortKey !== sortKey ||
@@ -135,13 +137,19 @@ class AuthorIndexPosters extends Component {
prevState.columnWidth !== columnWidth ||
prevState.columnCount !== columnCount ||
prevState.rowHeight !== rowHeight ||
hasDifferentItemsOrOrder(prevProps.items, items))) {
hasDifferentItemsOrOrder(prevProps.items, items)) ||
prevProps.posterOptions.showTitle !== posterOptions.showTitle) {
// recomputeGridSize also forces Grid to discard its cache of rendered cells
this._grid.recomputeGridSize();
}
if (this._grid && scrollTop !== 0 && !scrollRestored) {
this.setState({ scrollRestored: true });
this._grid.scrollToPosition({ scrollTop });
}
if (jumpToCharacter != null && jumpToCharacter !== prevProps.jumpToCharacter) {
const index = getIndexOfFirstCharacter(items, jumpToCharacter);
const index = getIndexOfFirstCharacter(items, sortKey, jumpToCharacter);
if (this._grid && index != null) {
const row = Math.floor(index / columnCount);
@@ -152,10 +160,6 @@ class AuthorIndexPosters extends Component {
});
}
}
if (this._grid && scrollTop !== 0) {
this._grid.scrollToPosition({ scrollTop });
}
}
//
@@ -275,7 +279,6 @@ class AuthorIndexPosters extends Component {
return (
<Measure
whitelist={['width']}
onMeasure={this.onMeasure}
>
<WindowScroller

View File

@@ -11,6 +11,7 @@ import ModalContent from 'Components/Modal/ModalContent';
import ModalFooter from 'Components/Modal/ModalFooter';
import ModalHeader from 'Components/Modal/ModalHeader';
import { inputTypes } from 'Helpers/Props';
import translate from 'Utilities/String/translate';
const posterSizeOptions = [
{ key: 'small', value: 'Small' },
@@ -18,6 +19,12 @@ const posterSizeOptions = [
{ key: 'large', value: 'Large' }
];
const nameOptions = [
{ key: 'no', value: translate('NoName') },
{ key: 'firstLast', value: translate('NameFirstLast') },
{ key: 'lastFirst', value: translate('NameLastFirst') }
];
class AuthorIndexPosterOptionsModalContent extends Component {
//
@@ -114,7 +121,9 @@ class AuthorIndexPosterOptionsModalContent extends Component {
<ModalBody>
<Form>
<FormGroup>
<FormLabel>Poster Size</FormLabel>
<FormLabel>
{translate('PosterSize')}
</FormLabel>
<FormInputGroup
type={inputTypes.SELECT}
@@ -126,61 +135,72 @@ class AuthorIndexPosterOptionsModalContent extends Component {
</FormGroup>
<FormGroup>
<FormLabel>Detailed Progress Bar</FormLabel>
<FormLabel>
{translate('DetailedProgressBar')}
</FormLabel>
<FormInputGroup
type={inputTypes.CHECK}
name="detailedProgressBar"
value={detailedProgressBar}
helpText="Show text on progess bar"
helpText={translate('DetailedProgressBarHelpText')}
onChange={this.onChangePosterOption}
/>
</FormGroup>
<FormGroup>
<FormLabel>Show Name</FormLabel>
<FormLabel>
{translate('ShowName')}
</FormLabel>
<FormInputGroup
type={inputTypes.CHECK}
type={inputTypes.SELECT}
name="showTitle"
value={showTitle}
helpText="Show author name under poster"
values={nameOptions}
helpText={translate('ShowTitleHelpText')}
onChange={this.onChangePosterOption}
/>
</FormGroup>
<FormGroup>
<FormLabel>Show Monitored</FormLabel>
<FormLabel>
{translate('ShowMonitored')}
</FormLabel>
<FormInputGroup
type={inputTypes.CHECK}
name="showMonitored"
value={showMonitored}
helpText="Show monitored status under poster"
helpText={translate('ShowMonitoredHelpText')}
onChange={this.onChangePosterOption}
/>
</FormGroup>
<FormGroup>
<FormLabel>Show Quality Profile</FormLabel>
<FormLabel>
{translate('ShowQualityProfile')}
</FormLabel>
<FormInputGroup
type={inputTypes.CHECK}
name="showQualityProfile"
value={showQualityProfile}
helpText="Show quality profile under poster"
helpText={translate('ShowQualityProfileHelpText')}
onChange={this.onChangePosterOption}
/>
</FormGroup>
<FormGroup>
<FormLabel>Show Search</FormLabel>
<FormLabel>
{translate('ShowSearch')}
</FormLabel>
<FormInputGroup
type={inputTypes.CHECK}
name="showSearchAction"
value={showSearchAction}
helpText="Show search button on hover"
helpText={translate('ShowSearchActionHelpText')}
onChange={this.onChangePosterOption}
/>
</FormGroup>
@@ -201,7 +221,7 @@ class AuthorIndexPosterOptionsModalContent extends Component {
AuthorIndexPosterOptionsModalContent.propTypes = {
size: PropTypes.string.isRequired,
showTitle: PropTypes.bool.isRequired,
showTitle: PropTypes.string.isRequired,
showMonitored: PropTypes.bool.isRequired,
showQualityProfile: PropTypes.bool.isRequired,
detailedProgressBar: PropTypes.bool.isRequired,

View File

@@ -3,6 +3,7 @@ import React from 'react';
import ProgressBar from 'Components/ProgressBar';
import { sizes } from 'Helpers/Props';
import getProgressBarKind from 'Utilities/Author/getProgressBarKind';
import translate from 'Utilities/String/translate';
import styles from './AuthorIndexProgressBar.css';
function AuthorIndexProgressBar(props) {
@@ -28,7 +29,7 @@ function AuthorIndexProgressBar(props) {
size={detailedProgressBar ? sizes.MEDIUM : sizes.SMALL}
showText={detailedProgressBar}
text={text}
title={`${bookFileCount} / ${bookCount} (Total: ${totalBookCount})`}
title={translate('BookFileCountBookCountTotalTotalBookCountInterp', [bookFileCount, bookCount, totalBookCount])}
width={posterWidth}
/>
);

View File

@@ -6,6 +6,7 @@ import IconButton from 'Components/Link/IconButton';
import SpinnerIconButton from 'Components/Link/SpinnerIconButton';
import VirtualTableRowCell from 'Components/Table/Cells/VirtualTableRowCell';
import { icons } from 'Helpers/Props';
import translate from 'Utilities/String/translate';
class AuthorIndexActionsCell extends Component {
@@ -65,14 +66,14 @@ class AuthorIndexActionsCell extends Component {
>
<SpinnerIconButton
name={icons.REFRESH}
title="Refresh Author"
title={translate('RefreshAuthor')}
isSpinning={isRefreshingAuthor}
onPress={onRefreshAuthorPress}
/>
<IconButton
name={icons.EDIT}
title="Edit Author"
title={translate('EditAuthor')}
onPress={this.onEditAuthorPress}
/>

View File

@@ -18,12 +18,6 @@
flex-grow: 1;
}
.authorType {
composes: headerCell from '~Components/Table/VirtualTableHeaderCell.css';
flex: 0 0 100px;
}
.qualityProfileId,
.metadataProfileId {
composes: headerCell from '~Components/Table/VirtualTableHeaderCell.css';

View File

@@ -17,12 +17,6 @@
flex: 4 0 110px;
}
.authorType {
composes: cell;
flex: 0 0 100px;
}
.banner {
flex: 0 0 379px;
}

View File

@@ -17,6 +17,7 @@ import TagListConnector from 'Components/TagListConnector';
import { icons } from 'Helpers/Props';
import getProgressBarKind from 'Utilities/Author/getProgressBarKind';
import formatBytes from 'Utilities/Number/formatBytes';
import translate from 'Utilities/String/translate';
import AuthorStatusCell from './AuthorStatusCell';
import hasGrowableColumns from './hasGrowableColumns';
import styles from './AuthorIndexRow.css';
@@ -81,6 +82,7 @@ class AuthorIndexRow extends Component {
monitored,
status,
authorName,
authorNameLastFirst,
titleSlug,
qualityProfile,
metadataProfile,
@@ -94,6 +96,7 @@ class AuthorIndexRow extends Component {
tags,
images,
showBanners,
showTitle,
showSearchAction,
columns,
isRefreshingAuthor,
@@ -168,14 +171,14 @@ class AuthorIndexRow extends Component {
{
hasBannerError &&
<div className={styles.overlayTitle}>
{authorName}
{showTitle === 'firstLast' ? authorName : authorNameLastFirst}
</div>
}
</Link> :
<AuthorNameLink
titleSlug={titleSlug}
authorName={authorName}
authorName={showTitle === 'firstLast' ? authorName : authorNameLastFirst}
/>
}
</VirtualTableRowCell>
@@ -278,7 +281,7 @@ class AuthorIndexRow extends Component {
kind={getProgressBarKind(status, monitored, progress)}
showText={true}
text={`${bookFileCount} / ${bookCount}`}
title={`${bookFileCount} / ${bookCount} (Total: ${totalBookCount})`}
title={translate('BookFileCountBookCountTotalTotalBookCountInterp', [bookFileCount, bookCount, totalBookCount])}
width={125}
/>
</VirtualTableRowCell>
@@ -356,7 +359,7 @@ class AuthorIndexRow extends Component {
>
<SpinnerIconButton
name={icons.REFRESH}
title="Refresh Author"
title={translate('RefreshAuthor')}
isSpinning={isRefreshingAuthor}
onPress={onRefreshAuthorPress}
/>
@@ -366,7 +369,7 @@ class AuthorIndexRow extends Component {
<SpinnerIconButton
className={styles.action}
name={icons.SEARCH}
title="Search for monitored books"
title={translate('SearchForMonitoredBooks')}
isSpinning={isSearchingAuthor}
onPress={onSearchPress}
/>
@@ -374,7 +377,7 @@ class AuthorIndexRow extends Component {
<IconButton
name={icons.EDIT}
title="Edit Author"
title={translate('EditAuthor')}
onPress={this.onEditAuthorPress}
/>
</VirtualTableRowCell>
@@ -407,6 +410,7 @@ AuthorIndexRow.propTypes = {
monitored: PropTypes.bool.isRequired,
status: PropTypes.string.isRequired,
authorName: PropTypes.string.isRequired,
authorNameLastFirst: PropTypes.string.isRequired,
titleSlug: PropTypes.string.isRequired,
qualityProfile: PropTypes.object.isRequired,
metadataProfile: PropTypes.object.isRequired,
@@ -421,6 +425,7 @@ AuthorIndexRow.propTypes = {
tags: PropTypes.arrayOf(PropTypes.number).isRequired,
images: PropTypes.arrayOf(PropTypes.object).isRequired,
showBanners: PropTypes.bool.isRequired,
showTitle: PropTypes.string.isRequired,
showSearchAction: PropTypes.bool.isRequired,
columns: PropTypes.arrayOf(PropTypes.object).isRequired,
isRefreshingAuthor: PropTypes.bool.isRequired,

View File

@@ -25,12 +25,13 @@ class AuthorIndexTable extends Component {
componentDidUpdate(prevProps) {
const {
items,
sortKey,
jumpToCharacter
} = this.props;
if (jumpToCharacter != null && jumpToCharacter !== prevProps.jumpToCharacter) {
const scrollIndex = getIndexOfFirstCharacter(items, jumpToCharacter);
const scrollIndex = getIndexOfFirstCharacter(items, sortKey, jumpToCharacter);
if (scrollIndex != null) {
this.setState({ scrollIndex });
@@ -47,7 +48,8 @@ class AuthorIndexTable extends Component {
const {
items,
columns,
showBanners
showBanners,
showTitle
} = this.props;
const author = items[rowIndex];
@@ -66,6 +68,7 @@ class AuthorIndexTable extends Component {
qualityProfileId={author.qualityProfileId}
metadataProfileId={author.metadataProfileId}
showBanners={showBanners}
showTitle={showTitle}
/>
</VirtualTableRow>
);
@@ -83,7 +86,8 @@ class AuthorIndexTable extends Component {
showBanners,
isSmallScreen,
onSortPress,
scroller
scroller,
scrollTop
} = this.props;
return (
@@ -91,6 +95,7 @@ class AuthorIndexTable extends Component {
className={styles.tableContainer}
items={items}
scrollIndex={this.state.scrollIndex}
scrollTop={scrollTop}
isSmallScreen={isSmallScreen}
scroller={scroller}
rowHeight={showBanners ? 70 : 38}
@@ -116,10 +121,12 @@ class AuthorIndexTable extends Component {
AuthorIndexTable.propTypes = {
items: PropTypes.arrayOf(PropTypes.object).isRequired,
columns: PropTypes.arrayOf(PropTypes.object).isRequired,
sortKey: PropTypes.string,
sortKey: PropTypes.string.isRequired,
sortDirection: PropTypes.oneOf(sortDirections.all),
showBanners: PropTypes.bool.isRequired,
showTitle: PropTypes.string.isRequired,
jumpToCharacter: PropTypes.string,
scrollTop: PropTypes.number,
scroller: PropTypes.instanceOf(Element).isRequired,
isSmallScreen: PropTypes.bool.isRequired,
onSortPress: PropTypes.func.isRequired

View File

@@ -12,6 +12,7 @@ function createMapStateToProps() {
return {
isSmallScreen: dimensions.isSmallScreen,
showBanners: tableOptions.showBanners,
showTitle: tableOptions.showTitle,
columns
};
}

View File

@@ -4,6 +4,12 @@ import FormGroup from 'Components/Form/FormGroup';
import FormInputGroup from 'Components/Form/FormInputGroup';
import FormLabel from 'Components/Form/FormLabel';
import { inputTypes } from 'Helpers/Props';
import translate from 'Utilities/String/translate';
const nameOptions = [
{ key: 'firstLast', value: translate('NameFirstLast') },
{ key: 'lastFirst', value: translate('NameLastFirst') }
];
class AuthorIndexTableOptions extends Component {
@@ -15,23 +21,27 @@ class AuthorIndexTableOptions extends Component {
this.state = {
showBanners: props.showBanners,
showSearchAction: props.showSearchAction
showSearchAction: props.showSearchAction,
showTitle: props.showTitle
};
}
componentDidUpdate(prevProps) {
const {
showBanners,
showSearchAction
showSearchAction,
showTitle
} = this.props;
if (
showBanners !== prevProps.showBanners ||
showSearchAction !== prevProps.showSearchAction
showSearchAction !== prevProps.showSearchAction ||
showTitle !== prevProps.showTitle
) {
this.setState({
showBanners,
showSearchAction
showSearchAction,
showTitle
});
}
}
@@ -58,31 +68,50 @@ class AuthorIndexTableOptions extends Component {
render() {
const {
showBanners,
showSearchAction
showSearchAction,
showTitle
} = this.state;
return (
<Fragment>
<FormGroup>
<FormLabel>Show Banners</FormLabel>
<FormLabel>
{translate('NameStyle')}
</FormLabel>
<FormInputGroup
type={inputTypes.CHECK}
name="showBanners"
value={showBanners}
helpText="Show banners instead of names"
type={inputTypes.SELECT}
name="showTitle"
value={showTitle}
values={nameOptions}
onChange={this.onTableOptionChange}
/>
</FormGroup>
<FormGroup>
<FormLabel>Show Search</FormLabel>
<FormLabel>
{translate('ShowBanners')}
</FormLabel>
<FormInputGroup
type={inputTypes.CHECK}
name="showBanners"
value={showBanners}
helpText={translate('ShowBannersHelpText')}
onChange={this.onTableOptionChange}
/>
</FormGroup>
<FormGroup>
<FormLabel>
{translate('ShowSearch')}
</FormLabel>
<FormInputGroup
type={inputTypes.CHECK}
name="showSearchAction"
value={showSearchAction}
helpText="Show search button on hover"
helpText={translate('ShowSearchActionHelpText')}
onChange={this.onTableOptionChange}
/>
</FormGroup>
@@ -92,6 +121,7 @@ class AuthorIndexTableOptions extends Component {
}
AuthorIndexTableOptions.propTypes = {
showTitle: PropTypes.string.isRequired,
showBanners: PropTypes.bool.isRequired,
showSearchAction: PropTypes.bool.isRequired,
onTableOptionChange: PropTypes.func.isRequired

View File

@@ -3,6 +3,7 @@ import React from 'react';
import Icon from 'Components/Icon';
import VirtualTableRowCell from 'Components/Table/Cells/TableRowCell';
import { icons } from 'Helpers/Props';
import translate from 'Utilities/String/translate';
import styles from './AuthorStatusCell.css';
function AuthorStatusCell(props) {
@@ -22,13 +23,13 @@ function AuthorStatusCell(props) {
<Icon
className={styles.statusIcon}
name={monitored ? icons.MONITORED : icons.UNMONITORED}
title={monitored ? 'Author is monitored' : 'Author is unmonitored'}
title={monitored ? translate('MonitoredAuthorIsMonitored') : translate('MonitoredAuthorIsUnmonitored')}
/>
<Icon
className={styles.statusIcon}
name={status === 'ended' ? icons.AUTHOR_ENDED : icons.AUTHOR_CONTINUING}
title={status === 'ended' ? 'Deceased' : 'Continuing'}
title={status === 'ended' ? translate('StatusEndedDeceased') : translate('StatusEndedContinuing')}
/>
</Component>
);

View File

@@ -0,0 +1,39 @@
import PropTypes from 'prop-types';
import React, { Component } from 'react';
import { connect } from 'react-redux';
import { clearPendingChanges } from 'Store/Actions/baseActions';
import MonitoringOptionsModal from './EditAuthorModal';
const mapDispatchToProps = {
clearPendingChanges
};
class MonitoringOptionsModalConnector extends Component {
//
// Listeners
onModalClose = () => {
this.props.clearPendingChanges({ section: 'authors' });
this.props.onModalClose();
}
//
// Render
render() {
return (
<MonitoringOptionsModal
{...this.props}
onModalClose={this.onModalClose}
/>
);
}
}
MonitoringOptionsModalConnector.propTypes = {
onModalClose: PropTypes.func.isRequired,
clearPendingChanges: PropTypes.func.isRequired
};
export default connect(undefined, mapDispatchToProps)(MonitoringOptionsModalConnector);

View File

@@ -0,0 +1,25 @@
import PropTypes from 'prop-types';
import React from 'react';
import Modal from 'Components/Modal/Modal';
import MonitoringOptionsModalContentConnector from './MonitoringOptionsModalContentConnector';
function MonitoringOptionsModal({ isOpen, onModalClose, ...otherProps }) {
return (
<Modal
isOpen={isOpen}
onModalClose={onModalClose}
>
<MonitoringOptionsModalContentConnector
{...otherProps}
onModalClose={onModalClose}
/>
</Modal>
);
}
MonitoringOptionsModal.propTypes = {
isOpen: PropTypes.bool.isRequired,
onModalClose: PropTypes.func.isRequired
};
export default MonitoringOptionsModal;

View File

@@ -0,0 +1,142 @@
import PropTypes from 'prop-types';
import React, { Component } from 'react';
import Form from 'Components/Form/Form';
import FormGroup from 'Components/Form/FormGroup';
import FormInputGroup from 'Components/Form/FormInputGroup';
import FormLabel from 'Components/Form/FormLabel';
import Button from 'Components/Link/Button';
import SpinnerButton from 'Components/Link/SpinnerButton';
import ModalBody from 'Components/Modal/ModalBody';
import ModalContent from 'Components/Modal/ModalContent';
import ModalFooter from 'Components/Modal/ModalFooter';
import ModalHeader from 'Components/Modal/ModalHeader';
import { inputTypes } from 'Helpers/Props';
import translate from 'Utilities/String/translate';
const NO_CHANGE = 'noChange';
class MonitoringOptionsModalContent extends Component {
//
// Lifecycle
constructor(props, context) {
super(props, context);
this.state = {
monitor: NO_CHANGE
};
}
componentDidUpdate(prevProps) {
const {
isSaving,
saveError
} = prevProps;
if (prevProps.isSaving && !isSaving && !saveError) {
this.setState({
monitor: NO_CHANGE
});
}
}
onInputChange = ({ name, value }) => {
this.setState({ [name]: value });
}
//
// Listeners
onSavePress = () => {
const {
onSavePress,
isSaving
} = this.props;
const {
monitor
} = this.state;
if (monitor !== NO_CHANGE) {
onSavePress({ monitor });
}
if (!isSaving) {
this.onModalClose();
}
}
onModalClose = () => {
this.props.onModalClose();
}
//
// Render
render() {
const {
isSaving,
onInputChange,
onModalClose,
...otherProps
} = this.props;
const {
monitor
} = this.state;
return (
<ModalContent onModalClose={onModalClose}>
<ModalHeader>
{translate('MonitorBook')}
</ModalHeader>
<ModalBody>
<Form {...otherProps}>
<FormGroup>
<FormLabel>{translate('Monitoring')}</FormLabel>
<FormInputGroup
type={inputTypes.MONITOR_BOOKS_SELECT}
name="monitor"
value={monitor}
includeNoChange={true}
onChange={this.onInputChange}
/>
</FormGroup>
</Form>
</ModalBody>
<ModalFooter>
<Button
onPress={onModalClose}
>
{translate('Cancel')}
</Button>
<SpinnerButton
isSpinning={isSaving}
onPress={this.onSavePress}
>
{translate('Save')}
</SpinnerButton>
</ModalFooter>
</ModalContent>
);
}
}
MonitoringOptionsModalContent.propTypes = {
authorId: PropTypes.number.isRequired,
saveError: PropTypes.object,
isSaving: PropTypes.bool.isRequired,
onInputChange: PropTypes.func.isRequired,
onSavePress: PropTypes.func.isRequired,
onModalClose: PropTypes.func.isRequired
};
MonitoringOptionsModalContent.defaultProps = {
isSaving: false
};
export default MonitoringOptionsModalContent;

View File

@@ -0,0 +1,77 @@
import PropTypes from 'prop-types';
import React, { Component } from 'react';
import { connect } from 'react-redux';
import { createSelector } from 'reselect';
import { updateBookMonitor } from 'Store/Actions/authorActions';
import MonitoringOptionsModalContent from './MonitoringOptionsModalContent';
function createMapStateToProps() {
return createSelector(
(state) => state.authors,
(authorState) => {
const {
isSaving,
saveError
} = authorState;
return {
isSaving,
saveError
};
}
);
}
const mapDispatchToProps = {
dispatchUpdateMonitoringOptions: updateBookMonitor
};
class MonitoringOptionsModalContentConnector extends Component {
//
// Lifecycle
componentDidUpdate(prevProps, prevState) {
if (prevProps.isSaving && !this.props.isSaving && !this.props.saveError) {
this.props.onModalClose(true);
}
}
//
// Listeners
onInputChange = ({ name, value }) => {
this.setState({ name, value });
}
onSavePress = ({ monitor }) => {
this.props.dispatchUpdateMonitoringOptions({
id: this.props.authorId,
monitor
});
}
//
// Render
render() {
return (
<MonitoringOptionsModalContent
{...this.props}
onInputChange={this.onInputChange}
onSavePress={this.onSavePress}
/>
);
}
}
MonitoringOptionsModalContentConnector.propTypes = {
authorId: PropTypes.number.isRequired,
isSaving: PropTypes.bool.isRequired,
saveError: PropTypes.object,
dispatchUpdateMonitoringOptions: PropTypes.func.isRequired,
onModalClose: PropTypes.func.isRequired,
onSavePress: PropTypes.func
};
export default connect(createMapStateToProps, mapDispatchToProps)(MonitoringOptionsModalContentConnector);

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