Compare commits

...

1633 Commits

Author SHA1 Message Date
advplyr
48f232790a Merge branch 'master' of https://github.com/advplyr/audiobookshelf 2024-09-01 15:41:19 -05:00
advplyr
3c55aa5f43 Version bump v2.13.2 2024-09-01 15:41:11 -05:00
advplyr
8c1edb30a6 Merge pull request #3356 from weblate/weblate-audiobookshelf-abs-web-client
Translations update from Hosted Weblate
2024-09-01 15:35:29 -05:00
Andrej Kralj
5e64af4448 Translated using Weblate (Slovenian)
Currently translated at 45.9% (448 of 974 strings)

Translation: Audiobookshelf/Abs Web Client
Translate-URL: https://hosted.weblate.org/projects/audiobookshelf/abs-web-client/sl/
2024-09-01 22:32:04 +02:00
advplyr
9f60017cfe Update:Remove oldSeries model 2024-09-01 15:26:43 -05:00
advplyr
b6a86d11d2 Fix:Toasts for item details updated 2024-09-01 15:11:06 -05:00
advplyr
db86bfd63d Fix:New authors not setting lastFirst column, updates for new Series model 2024-09-01 15:08:56 -05:00
advplyr
7ff72a8920 Merge pull request #3355 from weblate/weblate-audiobookshelf-abs-web-client
Translations update from Hosted Weblate
2024-09-01 10:19:57 -05:00
Andrej Kralj
2c4f86d148 Translated using Weblate (Slovenian)
Currently translated at 26.2% (256 of 974 strings)

Translation: Audiobookshelf/Abs Web Client
Translate-URL: https://hosted.weblate.org/projects/audiobookshelf/abs-web-client/sl/
2024-09-01 15:17:26 +00:00
advplyr
1a9f26e804 Version bump v2.13.1 2024-09-01 07:45:46 -05:00
advplyr
42f8194bde Add:Slovenian language option 2024-09-01 07:45:32 -05:00
Weblate (bot)
8634b7058c Translations update from Hosted Weblate (#3351)
* Translated using Weblate (Bengali)

Currently translated at 78.8% (768 of 974 strings)

Translation: Audiobookshelf/Abs Web Client
Translate-URL: https://hosted.weblate.org/projects/audiobookshelf/abs-web-client/bn/

* Translated using Weblate (Gujarati)

Currently translated at 16.1% (157 of 974 strings)

Translation: Audiobookshelf/Abs Web Client
Translate-URL: https://hosted.weblate.org/projects/audiobookshelf/abs-web-client/gu/

* Translated using Weblate (Hungarian)

Currently translated at 75.3% (734 of 974 strings)

Translation: Audiobookshelf/Abs Web Client
Translate-URL: https://hosted.weblate.org/projects/audiobookshelf/abs-web-client/hu/

* Translated using Weblate (French)

Currently translated at 92.6% (902 of 974 strings)

Translation: Audiobookshelf/Abs Web Client
Translate-URL: https://hosted.weblate.org/projects/audiobookshelf/abs-web-client/fr/

* Translated using Weblate (French)

Currently translated at 92.6% (902 of 974 strings)

Translation: Audiobookshelf/Abs Web Client
Translate-URL: https://hosted.weblate.org/projects/audiobookshelf/abs-web-client/fr/

* Translated using Weblate (Russian)

Currently translated at 83.7% (816 of 974 strings)

Translation: Audiobookshelf/Abs Web Client
Translate-URL: https://hosted.weblate.org/projects/audiobookshelf/abs-web-client/ru/

* Translated using Weblate (French)

Currently translated at 93.8% (914 of 974 strings)

Translation: Audiobookshelf/Abs Web Client
Translate-URL: https://hosted.weblate.org/projects/audiobookshelf/abs-web-client/fr/

* Translated using Weblate (French)

Currently translated at 93.8% (914 of 974 strings)

Translation: Audiobookshelf/Abs Web Client
Translate-URL: https://hosted.weblate.org/projects/audiobookshelf/abs-web-client/fr/

* Translated using Weblate (Russian)

Currently translated at 84.3% (822 of 974 strings)

Translation: Audiobookshelf/Abs Web Client
Translate-URL: https://hosted.weblate.org/projects/audiobookshelf/abs-web-client/ru/

* Translated using Weblate (French)

Currently translated at 94.1% (917 of 974 strings)

Translation: Audiobookshelf/Abs Web Client
Translate-URL: https://hosted.weblate.org/projects/audiobookshelf/abs-web-client/fr/

* Translated using Weblate (Russian)

Currently translated at 85.3% (831 of 974 strings)

Translation: Audiobookshelf/Abs Web Client
Translate-URL: https://hosted.weblate.org/projects/audiobookshelf/abs-web-client/ru/

* Translated using Weblate (French)

Currently translated at 94.4% (920 of 974 strings)

Translation: Audiobookshelf/Abs Web Client
Translate-URL: https://hosted.weblate.org/projects/audiobookshelf/abs-web-client/fr/

* Translated using Weblate (French)

Currently translated at 94.4% (920 of 974 strings)

Translation: Audiobookshelf/Abs Web Client
Translate-URL: https://hosted.weblate.org/projects/audiobookshelf/abs-web-client/fr/

* Translated using Weblate (Russian)

Currently translated at 85.9% (837 of 974 strings)

Translation: Audiobookshelf/Abs Web Client
Translate-URL: https://hosted.weblate.org/projects/audiobookshelf/abs-web-client/ru/

* Translated using Weblate (French)

Currently translated at 94.8% (924 of 974 strings)

Translation: Audiobookshelf/Abs Web Client
Translate-URL: https://hosted.weblate.org/projects/audiobookshelf/abs-web-client/fr/

* Translated using Weblate (French)

Currently translated at 94.8% (924 of 974 strings)

Translation: Audiobookshelf/Abs Web Client
Translate-URL: https://hosted.weblate.org/projects/audiobookshelf/abs-web-client/fr/

* Translated using Weblate (Russian)

Currently translated at 86.2% (840 of 974 strings)

Translation: Audiobookshelf/Abs Web Client
Translate-URL: https://hosted.weblate.org/projects/audiobookshelf/abs-web-client/ru/

* Translated using Weblate (French)

Currently translated at 96.8% (943 of 974 strings)

Translation: Audiobookshelf/Abs Web Client
Translate-URL: https://hosted.weblate.org/projects/audiobookshelf/abs-web-client/fr/

* Translated using Weblate (French)

Currently translated at 96.8% (943 of 974 strings)

Translation: Audiobookshelf/Abs Web Client
Translate-URL: https://hosted.weblate.org/projects/audiobookshelf/abs-web-client/fr/

* Translated using Weblate (Russian)

Currently translated at 86.8% (846 of 974 strings)

Translation: Audiobookshelf/Abs Web Client
Translate-URL: https://hosted.weblate.org/projects/audiobookshelf/abs-web-client/ru/

* Translated using Weblate (German)

Currently translated at 100.0% (974 of 974 strings)

Translation: Audiobookshelf/Abs Web Client
Translate-URL: https://hosted.weblate.org/projects/audiobookshelf/abs-web-client/de/

* Translated using Weblate (French)

Currently translated at 100.0% (974 of 974 strings)

Translation: Audiobookshelf/Abs Web Client
Translate-URL: https://hosted.weblate.org/projects/audiobookshelf/abs-web-client/fr/

* Translated using Weblate (French)

Currently translated at 100.0% (974 of 974 strings)

Translation: Audiobookshelf/Abs Web Client
Translate-URL: https://hosted.weblate.org/projects/audiobookshelf/abs-web-client/fr/

* Translated using Weblate (Russian)

Currently translated at 100.0% (974 of 974 strings)

Translation: Audiobookshelf/Abs Web Client
Translate-URL: https://hosted.weblate.org/projects/audiobookshelf/abs-web-client/ru/

* Added translation using Weblate (Slovenian)

* Translated using Weblate (Croatian)

Currently translated at 100.0% (974 of 974 strings)

Translation: Audiobookshelf/Abs Web Client
Translate-URL: https://hosted.weblate.org/projects/audiobookshelf/abs-web-client/hr/

* Translated using Weblate (Spanish)

Currently translated at 99.7% (972 of 974 strings)

Translation: Audiobookshelf/Abs Web Client
Translate-URL: https://hosted.weblate.org/projects/audiobookshelf/abs-web-client/es/

* Translated using Weblate (Croatian)

Currently translated at 100.0% (974 of 974 strings)

Translation: Audiobookshelf/Abs Web Client
Translate-URL: https://hosted.weblate.org/projects/audiobookshelf/abs-web-client/hr/

* Translated using Weblate (Slovenian)

Currently translated at 21.1% (206 of 974 strings)

Translation: Audiobookshelf/Abs Web Client
Translate-URL: https://hosted.weblate.org/projects/audiobookshelf/abs-web-client/sl/

---------

Co-authored-by: Nicholas W <nicholaslwallace@gmail.com>
Co-authored-by: Pierrick Guillaume <pierguill@gmail.com>
Co-authored-by: Charlie <Machou@users.noreply.hosted.weblate.org>
Co-authored-by: Dmitry <dmitry@naboychenko.ru>
Co-authored-by: Valentin <valentin.bartschies@gmail.com>
Co-authored-by: biuklija <ivan@biuklija.com>
Co-authored-by: gallegonovato <fran-carro@hotmail.es>
Co-authored-by: Andrej Kralj <andrej.kralj@gmail.com>
2024-09-01 07:38:57 -05:00
advplyr
fc276b330a Fix:Server crash when uploading or adding new podcast #3353 2024-09-01 07:35:05 -05:00
advplyr
5b22d7430a Version bump v2.13.0 2024-08-31 15:39:50 -05:00
advplyr
8883debc74 Merge pull request #3350 from nichwall/untranslated_strings_cleanup
Delete untranslated strings
2024-08-31 15:09:57 -05:00
Nicholas Wallace
c92cb08f6f Delete untranslated strings 2024-08-31 13:04:09 -07:00
advplyr
1254b668de Merge branch 'master' of https://github.com/advplyr/audiobookshelf 2024-08-31 15:03:50 -05:00
advplyr
48b703bf9f Update:Global search author card and library stat author name links to author page 2024-08-31 15:03:42 -05:00
advplyr
064679c057 Update:Author number of books sort fallsback to sort on name when num books is the same 2024-08-31 14:59:42 -05:00
Weblate (bot)
ba23d258e7 Translations update from Hosted Weblate (#3342)
* Translated using Weblate (Croatian)

Currently translated at 69.8% (611 of 875 strings)

Translation: Audiobookshelf/Abs Web Client
Translate-URL: https://hosted.weblate.org/projects/audiobookshelf/abs-web-client/hr/

* Translated using Weblate (Croatian)

Currently translated at 92.1% (806 of 875 strings)

Translation: Audiobookshelf/Abs Web Client
Translate-URL: https://hosted.weblate.org/projects/audiobookshelf/abs-web-client/hr/

* Update translation files

Updated by "Cleanup translation files" add-on in Weblate.

Translation: Audiobookshelf/Abs Web Client
Translate-URL: https://hosted.weblate.org/projects/audiobookshelf/abs-web-client/

* Translated using Weblate (German)

Currently translated at 94.5% (921 of 974 strings)

Translation: Audiobookshelf/Abs Web Client
Translate-URL: https://hosted.weblate.org/projects/audiobookshelf/abs-web-client/de/

* Translated using Weblate (Spanish)

Currently translated at 90.9% (886 of 974 strings)

Translation: Audiobookshelf/Abs Web Client
Translate-URL: https://hosted.weblate.org/projects/audiobookshelf/abs-web-client/es/

* Translated using Weblate (French)

Currently translated at 90.8% (885 of 974 strings)

Translation: Audiobookshelf/Abs Web Client
Translate-URL: https://hosted.weblate.org/projects/audiobookshelf/abs-web-client/fr/

* Translated using Weblate (Croatian)

Currently translated at 100.0% (974 of 974 strings)

Translation: Audiobookshelf/Abs Web Client
Translate-URL: https://hosted.weblate.org/projects/audiobookshelf/abs-web-client/hr/

* Translated using Weblate (French)

Currently translated at 92.4% (900 of 974 strings)

Translation: Audiobookshelf/Abs Web Client
Translate-URL: https://hosted.weblate.org/projects/audiobookshelf/abs-web-client/fr/

* Translated using Weblate (French)

Currently translated at 93.5% (911 of 974 strings)

Translation: Audiobookshelf/Abs Web Client
Translate-URL: https://hosted.weblate.org/projects/audiobookshelf/abs-web-client/fr/

---------

Co-authored-by: biuklija <ivan@biuklija.com>
Co-authored-by: Mario <leet31337@web.de>
Co-authored-by: gallegonovato <fran-carro@hotmail.es>
Co-authored-by: Charlie <Machou@users.noreply.hosted.weblate.org>
2024-08-31 14:32:40 -05:00
Nicholas W
98cd19d440 Config issue workflow (#3348)
* Intial: issue comments workflow

* Update: formatting

* Additional common search terms
2024-08-31 13:35:14 -05:00
advplyr
4c8b91e9d9 Merge branch 'master' of https://github.com/advplyr/audiobookshelf 2024-08-31 13:27:54 -05:00
advplyr
ba742563c2 Remove old Author object & fix issue deleting empty authors 2024-08-31 13:27:48 -05:00
Nicholas W
f0e70ed27b Translation strings added (#3304)
* Update: `pages/items/_id` toast messages

* Update: account modal strings

* Update: audio file data modal strings

* Update: sleep timer set string

* Update: loading indicator string

* Update: lazy book card strings

* Reorder keys

* Fix: syntax error in LazyBookCard

* Fix: json ordering

* Fix: fix double message definition

* Update: login form toast strings

* Update: batch delete toast

* Update: collection add toast messages

* Replace: toasts in BookShelfToolbar

* Update: playlist edit toasts

* Update: Details tab

* Add: title required string

* Update: ereader toasts

* Update: author toasts, title and name required toasts

* Clean up "no updates" strings

* Change: slug strings

* Update: cover modal toasts

* Change: cancel encode toasts

* Change: failed to share toasts

* Simplify: "renameFail" and "removeFail" toasts

* Fix: ordering

* Change: chapters remove toast

* Update: notification strings

* Revert: loading indicator (error in browser)

* Update: collectionBooksTable toast

* Update: "failed to get" strings

* Update: backup strings

* Update: custom provider strings

* Update: sessions strings

* Update: email strings

* Update sort display translation strings, update podcast episode queue strings to use translation

* Fix loading indicator please wait translation

* Consolidate translations and reduce number of toasts

---------

Co-authored-by: advplyr <advplyr@protonmail.com>
2024-08-30 17:47:49 -05:00
advplyr
acc4bdbc5e Add:Podcast latest page includes Mark as Finished button #3321 2024-08-29 17:27:52 -05:00
advplyr
c45c82306e Remove old library, folder and librarysettings model 2024-08-28 17:26:23 -05:00
advplyr
fd827b2214 Merge pull request #3265 from weblate/weblate-audiobookshelf-abs-web-client
Translations update from Hosted Weblate
2024-08-28 04:55:44 -05:00
biuklija
df1c157994 Translated using Weblate (Croatian)
Currently translated at 65.8% (576 of 875 strings)

Translation: Audiobookshelf/Abs Web Client
Translate-URL: https://hosted.weblate.org/projects/audiobookshelf/abs-web-client/hr/
2024-08-27 21:56:31 +00:00
biuklija
a92e417581 Translated using Weblate (Croatian)
Currently translated at 65.6% (574 of 875 strings)

Translation: Audiobookshelf/Abs Web Client
Translate-URL: https://hosted.weblate.org/projects/audiobookshelf/abs-web-client/hr/
2024-08-27 21:56:30 +00:00
biuklija
6ad0719880 Translated using Weblate (Croatian)
Currently translated at 65.6% (574 of 875 strings)

Translation: Audiobookshelf/Abs Web Client
Translate-URL: https://hosted.weblate.org/projects/audiobookshelf/abs-web-client/hr/
2024-08-27 21:56:30 +00:00
biuklija
5383d0b5f7 Translated using Weblate (Croatian)
Currently translated at 65.6% (574 of 875 strings)

Translation: Audiobookshelf/Abs Web Client
Translate-URL: https://hosted.weblate.org/projects/audiobookshelf/abs-web-client/hr/
2024-08-27 21:56:29 +00:00
Tom Redd
b3cefc075d Translated using Weblate (Norwegian Bokmål)
Currently translated at 82.2% (720 of 875 strings)

Translation: Audiobookshelf/Abs Web Client
Translate-URL: https://hosted.weblate.org/projects/audiobookshelf/abs-web-client/nb_NO/
2024-08-27 21:56:29 +00:00
Christian Wia
ac62d18007 Translated using Weblate (French)
Currently translated at 99.8% (874 of 875 strings)

Translation: Audiobookshelf/Abs Web Client
Translate-URL: https://hosted.weblate.org/projects/audiobookshelf/abs-web-client/fr/
2024-08-27 21:56:28 +00:00
Ahetek
fe14c26782 Translated using Weblate (Polish)
Currently translated at 90.7% (794 of 875 strings)

Translation: Audiobookshelf/Abs Web Client
Translate-URL: https://hosted.weblate.org/projects/audiobookshelf/abs-web-client/pl/
2024-08-27 21:56:28 +00:00
Mario
b33a3cabf9 Translated using Weblate (German)
Currently translated at 100.0% (875 of 875 strings)

Translation: Audiobookshelf/Abs Web Client
Translate-URL: https://hosted.weblate.org/projects/audiobookshelf/abs-web-client/de/
2024-08-27 21:56:27 +00:00
gallegonovato
6224163ecd Translated using Weblate (Spanish)
Currently translated at 100.0% (875 of 875 strings)

Translation: Audiobookshelf/Abs Web Client
Translate-URL: https://hosted.weblate.org/projects/audiobookshelf/abs-web-client/es/
2024-08-27 21:56:27 +00:00
Mario
05aabb2843 Translated using Weblate (German)
Currently translated at 100.0% (874 of 874 strings)

Translation: Audiobookshelf/Abs Web Client
Translate-URL: https://hosted.weblate.org/projects/audiobookshelf/abs-web-client/de/
2024-08-27 21:56:26 +00:00
gallegonovato
7d2d5f6bf4 Translated using Weblate (Spanish)
Currently translated at 100.0% (874 of 874 strings)

Translation: Audiobookshelf/Abs Web Client
Translate-URL: https://hosted.weblate.org/projects/audiobookshelf/abs-web-client/es/
2024-08-27 21:56:26 +00:00
Illia Pyshniak
c938685679 Translated using Weblate (Ukrainian)
Currently translated at 100.0% (872 of 872 strings)

Translation: Audiobookshelf/Abs Web Client
Translate-URL: https://hosted.weblate.org/projects/audiobookshelf/abs-web-client/uk/
2024-08-27 21:56:25 +00:00
lecoq
e6ecc28001 Translated using Weblate (French)
Currently translated at 99.8% (871 of 872 strings)

Translation: Audiobookshelf/Abs Web Client
Translate-URL: https://hosted.weblate.org/projects/audiobookshelf/abs-web-client/fr/
2024-08-27 21:56:24 +00:00
kuci-JK
93fa6ba466 Translated using Weblate (Czech)
Currently translated at 96.6% (843 of 872 strings)

Translation: Audiobookshelf/Abs Web Client
Translate-URL: https://hosted.weblate.org/projects/audiobookshelf/abs-web-client/cs/
2024-08-27 21:56:24 +00:00
Fredrik Lindqvist
a8f459e4fa Translated using Weblate (Swedish)
Currently translated at 83.6% (729 of 872 strings)

Translation: Audiobookshelf/Abs Web Client
Translate-URL: https://hosted.weblate.org/projects/audiobookshelf/abs-web-client/sv/
2024-08-27 21:56:24 +00:00
gallegonovato
2441bb1cec Translated using Weblate (Spanish)
Currently translated at 100.0% (872 of 872 strings)

Translation: Audiobookshelf/Abs Web Client
Translate-URL: https://hosted.weblate.org/projects/audiobookshelf/abs-web-client/es/
2024-08-27 21:56:23 +00:00
Vito0912
25cc24fca5 Translated using Weblate (German)
Currently translated at 100.0% (872 of 872 strings)

Translation: Audiobookshelf/Abs Web Client
Translate-URL: https://hosted.weblate.org/projects/audiobookshelf/abs-web-client/de/
2024-08-27 21:56:23 +00:00
Vito0912
ff4cbc6d5f Translated using Weblate (German)
Currently translated at 100.0% (872 of 872 strings)

Translation: Audiobookshelf/Abs Web Client
Translate-URL: https://hosted.weblate.org/projects/audiobookshelf/abs-web-client/de/
2024-08-27 21:56:22 +00:00
Charlie
f79bfae95d Translated using Weblate (French)
Currently translated at 99.8% (871 of 872 strings)

Translation: Audiobookshelf/Abs Web Client
Translate-URL: https://hosted.weblate.org/projects/audiobookshelf/abs-web-client/fr/
2024-08-27 21:56:22 +00:00
SunSpring
2f99efcc60 Translated using Weblate (Chinese (Simplified))
Currently translated at 100.0% (872 of 872 strings)

Translation: Audiobookshelf/Abs Web Client
Translate-URL: https://hosted.weblate.org/projects/audiobookshelf/abs-web-client/zh_Hans/
2024-08-27 21:56:21 +00:00
burghy86
45b13571a5 Translated using Weblate (Italian)
Currently translated at 100.0% (872 of 872 strings)

Translation: Audiobookshelf/Abs Web Client
Translate-URL: https://hosted.weblate.org/projects/audiobookshelf/abs-web-client/it/
2024-08-27 21:56:21 +00:00
Mario
04da8812df Translated using Weblate (German)
Currently translated at 99.8% (871 of 872 strings)

Translation: Audiobookshelf/Abs Web Client
Translate-URL: https://hosted.weblate.org/projects/audiobookshelf/abs-web-client/de/
2024-08-27 21:56:20 +00:00
Vito0912
840304ee04 Translated using Weblate (German)
Currently translated at 99.8% (871 of 872 strings)

Translation: Audiobookshelf/Abs Web Client
Translate-URL: https://hosted.weblate.org/projects/audiobookshelf/abs-web-client/de/
2024-08-27 21:56:20 +00:00
Mario
41bd9a9358 Translated using Weblate (German)
Currently translated at 99.8% (871 of 872 strings)

Translation: Audiobookshelf/Abs Web Client
Translate-URL: https://hosted.weblate.org/projects/audiobookshelf/abs-web-client/de/
2024-08-27 21:56:19 +00:00
Charlie
1e0a9918fd Translated using Weblate (French)
Currently translated at 99.8% (871 of 872 strings)

Translation: Audiobookshelf/Abs Web Client
Translate-URL: https://hosted.weblate.org/projects/audiobookshelf/abs-web-client/fr/
2024-08-27 21:56:19 +00:00
gallegonovato
799acf5db8 Translated using Weblate (Spanish)
Currently translated at 100.0% (872 of 872 strings)

Translation: Audiobookshelf/Abs Web Client
Translate-URL: https://hosted.weblate.org/projects/audiobookshelf/abs-web-client/es/
2024-08-27 21:56:18 +00:00
advplyr
1326d29fad Merge pull request #3332 from itzexor/memorystore-2
memorystore: simplify, refactor, re-enable
2024-08-27 16:56:07 -05:00
advplyr
9b35530956 Fix memorystore constructor validation 2024-08-27 16:53:18 -05:00
advplyr
0ae054c5d7 Update tools endpoint status codes 2024-08-26 17:02:29 -05:00
advplyr
c72eac9987 Fix:Check if book is already being merged before allowing to start #3331 2024-08-25 17:13:09 -05:00
advplyr
159ccd807f Updates to migrate off of old library model 2024-08-24 16:09:54 -05:00
advplyr
5d13faef33 Updates to LibraryController to use new Library model
- Additional validation on API endpoints
- Removed success toast when reorder libraries
2024-08-24 15:38:15 -05:00
advplyr
e0de59a4b6 Merge pull request #3329 from mikiher/embed-single-file
Fix embed and convert for single file library items
2024-08-24 13:27:43 -05:00
advplyr
519a1b0eaf Merge pull request #3328 from mikiher/aspect-ratio-card-width
Update series and collection width to account for book aspect ratio
2024-08-24 13:24:38 -05:00
mikiher
4d8e1b7cef Fix embed and convert for single file library items 2024-08-24 12:01:00 +03:00
mikiher
6d3e096e08 Update series and collection width to account for book aspect ratio 2024-08-24 08:49:40 +03:00
advplyr
38edcdca4b Updates to use new Library model 2024-08-23 16:59:51 -05:00
advplyr
8774e6be71 Update:Create library endpoint to create using new model, adding additional validation 2024-08-22 17:39:28 -05:00
James Ross
ec197b2e13 memorystore: simplify, refactor, re-enable
Removes a lot of unused (in ABS) functionality, refactors to ES6
style class, and re-enables this custom implementation with check
period and ttl of 1 day, and 1000 max entries.

The class now only implments the required (as per express-session docs)
methods and removes optional methods, except touch() which allows the
TTL of an entry to be refreshed without affecting its LRU recency.

There is no longer a way to stop the prune timer, but I don't belive
the function was ever being called beforehand. The session store's
lifetime is the same as the application's, and since it is unref()'d
should not cause any shutdown issues.
2024-08-22 03:55:51 +00:00
advplyr
1c0d6e9c67 Merge pull request #3313 from mikiher/author-image-path
Update AuthorController to handle invalid image paths and log a warning
2024-08-21 17:46:39 -05:00
advplyr
7d711da381 Merge pull request #3305 from nichwall/update_api_linting_workflow
Update api linting workflow
2024-08-21 17:43:05 -05:00
advplyr
f66cea9829 Merge pull request #3312 from nichwall/close_comics_during_scan
Close comics during scan
2024-08-21 17:41:06 -05:00
advplyr
5f572face5 Merge pull request #3311 from nichwall/backup_restore_clear_cache
Backup restore clear cache
2024-08-21 17:38:16 -05:00
advplyr
88a4cf9f12 Merge pull request #3319 from chancez/pr/chancez/ios_safari_content_type_workaround
Fix Content-Type header when browser user-agent is from an Apple mobile device
2024-08-21 17:20:14 -05:00
Chance Zibolski
0b860e0d40 Fix Content-Type header when browser user-agent is from an Apple mobile device
Fixes #3310

Signed-off-by: Chance Zibolski <chance.zibolski@gmail.com>
2024-08-20 19:01:14 -07:00
advplyr
149bb3e5b2 Fix:Audible book match not falling back to search after failed ASIN #3314 2024-08-20 17:04:48 -05:00
advplyr
7a7a779824 Update podcast audio file meta tag to use album-artist for author and fallback to artist tag #3315 2024-08-20 16:41:17 -05:00
mikiher
20a3657063 Update AuthorController to handle invalid image paths and log a warning 2024-08-20 10:51:24 +03:00
Nicholas Wallace
9c87c3a095 Free memory after extracting comic 2024-08-19 22:05:25 -07:00
Nicholas Wallace
4de65b4369 Autoformat parseComicMetadata 2024-08-19 21:00:16 -07:00
Nicholas Wallace
996c78d760 Add: clear metadata cache when restoring backup 2024-08-19 19:32:53 -07:00
Nicholas Wallace
ccdc3d60c4 Change: CacheManager use ensureDir 2024-08-19 19:25:01 -07:00
Nicholas Wallace
8be08882d8 Update formatting in CacheManager 2024-08-19 19:23:41 -07:00
advplyr
26d2c5a8f0 Remove oldUser object require 2024-08-19 17:35:00 -05:00
advplyr
bae39e3a2d Remove oldUser object require 2024-08-19 17:31:29 -05:00
advplyr
bb1a72269a Remove old User object with old MediaProgress & AudioBookmark 2024-08-19 17:26:17 -05:00
Nicholas Wallace
9674cfd258 Add: explicit permissions for OpenAPI linting workflow 2024-08-18 19:08:04 -07:00
Nicholas Wallace
627ddd2f70 Fix: OpenAPI lint workflow trigger 2024-08-18 19:07:18 -07:00
Nicholas W
27b3a44147 Add: Backup notification (#3225)
* Formatting updates

* Add: backup completion notification

* Fix: comment for backup

* Add: backup size units to notification

* Add: failed backup notification

* Add: calls to failed backup notification

* Update: notification OpenAPI spec

* Update notifications to first check if any are active for an event, update JS docs

---------

Co-authored-by: advplyr <advplyr@protonmail.com>
2024-08-18 14:32:05 -05:00
advplyr
5308fd8b46 Update:Create & update API endpoints to create with new data model 2024-08-17 17:18:40 -05:00
advplyr
1b914d5d4f Update:Log local auth login attempts for failed and successful #2533 #2579 2024-08-17 15:02:59 -05:00
advplyr
9e0f17f7c6 Merge pull request #3294 from mikiher/menu-keyboard-navigation-refactor
Refactor menu keyoboard navigation into mixin
2024-08-17 14:08:20 -05:00
advplyr
1320b6d785 Add:Next chapter button plays next item in queue #3299 2024-08-17 13:32:00 -05:00
mikiher
f1ddbeadaf Refactor menu keyoboard navigation into mixin 2024-08-17 06:08:32 +03:00
advplyr
f9f89e1e51 Update material symbols icon font
- only include Material Symbols Rounded
- Replace some ligatures with codepoint so loading isnt as ugly/shifting
2024-08-16 16:57:17 -05:00
advplyr
bbf214fa4c Update LibraryItem debug logs to show objects and display libraryFiles changes differently 2024-08-15 17:05:18 -05:00
advplyr
f1582177e1 Merge pull request #3271 from faush01/feature/nobreak_in_stats
no line breaks in size text
2024-08-15 16:14:02 -05:00
advplyr
d5712a564c Update client/pages/library/_library/stats.vue 2024-08-15 16:10:53 -05:00
advplyr
1c274862d8 Merge pull request #3288 from mikiher/fix-collapse-subseries
Fix: add back expand/collapse sub series in selected series page
2024-08-15 16:09:24 -05:00
advplyr
663c9e0fa9 Fix podcast filter user permissions query 2024-08-15 15:54:03 -05:00
mikiher
bcb0bc75c9 Fix: add back expand/collapse sub series in selected series page 2024-08-15 10:27:02 +03:00
advplyr
603823d6ea Merge pull request #3278 from mikiher/revert-to-ffbinaries
Go back to downloading binaries from ffbinaries.com
2024-08-14 16:43:01 -05:00
advplyr
20c04d3ed3 Simplify Logger source 2024-08-14 16:36:10 -05:00
mikiher
02e5d608d0 Go back to downloading binaries from ffbinaries.com 2024-08-13 09:25:39 +03:00
advplyr
e53ac6566b Update API JS docs 2024-08-11 17:01:25 -05:00
advplyr
2472b86284 Update:Express middleware sets req.user to new data model, openid permissions functions moved to new data model 2024-08-11 16:07:29 -05:00
advplyr
29a15858f4 Update ApiCacheManager unit test for userNew 2024-08-11 15:19:28 -05:00
advplyr
afc16358ca Update more API endpoints to use new user model 2024-08-11 15:15:34 -05:00
advplyr
9facf77ff1 Update remove old sync local sessions endpoint & update MeController routes to use new user model 2024-08-11 13:09:53 -05:00
advplyr
1923854202 Update bookmarks API endpoints to use new user model 2024-08-11 12:16:45 -05:00
advplyr
9cd92c7b7f Update API media progress endpoints to use new user model. Merge book & episode endpoints 2024-08-11 11:53:30 -05:00
Shaun
8e0b723207 no line breaks in size text 2024-08-11 21:51:38 +10:00
advplyr
68ef3a07a7 Update controllers to use new user model 2024-08-10 17:15:21 -05:00
advplyr
202ceb02b5 Update:Auth to use new user model
- Express requests include userNew to start migrating API controllers to new user model
2024-08-10 15:46:04 -05:00
advplyr
59370cae81 Update:Docker source skip binary manager check #3266 2024-08-10 12:37:41 -05:00
advplyr
52a3bc224a Version bump v2.12.3 2024-08-09 16:59:19 -05:00
advplyr
54d67e5216 Merge pull request #3245 from ic1415/LibraryItemController
Update LibraryItemController.js
2024-08-09 16:48:30 -05:00
advplyr
b55d8250cc Download log update 2024-08-09 16:48:21 -05:00
advplyr
3a1e9abd68 Revert unicode sqlite extension to fix db corruption #3241 2024-08-09 16:41:52 -05:00
advplyr
c5ba40a178 Merge pull request #3262 from Vito0912/lang/add-year-in-review
lang/localization of "year in review"
2024-08-09 16:26:12 -05:00
Vito0912
f0c6dccadb Added max width 2024-08-09 19:24:43 +02:00
Vito0912
e701d1ab6a now? please 2024-08-09 18:59:20 +02:00
Vito0912
e10c8093c9 localization of year in review 2024-08-09 18:48:29 +02:00
advplyr
e81b3461b2 Version bump v2.12.2 2024-08-08 17:17:22 -05:00
advplyr
9345cb3934 Update version check get changelogs 2024-08-08 17:04:50 -05:00
advplyr
eb36a0b3dd Merge pull request #3247 from weblate/weblate-audiobookshelf-abs-web-client
Translations update from Hosted Weblate
2024-08-08 16:58:06 -05:00
advplyr
7e442ecb3d Revert MemoryStore used in expressSession 2024-08-08 16:54:48 -05:00
tonttula
f07c5eb725 Translated using Weblate (Finnish)
Currently translated at 32.2% (275 of 853 strings)

Translation: Audiobookshelf/Abs Web Client
Translate-URL: https://hosted.weblate.org/projects/audiobookshelf/abs-web-client/fi/
2024-08-08 23:54:39 +02:00
SunSpring
a486be92cb Translated using Weblate (Chinese (Simplified))
Currently translated at 100.0% (853 of 853 strings)

Translation: Audiobookshelf/Abs Web Client
Translate-URL: https://hosted.weblate.org/projects/audiobookshelf/abs-web-client/zh_Hans/
2024-08-08 23:54:39 +02:00
tonttula
4d84060036 Translated using Weblate (Finnish)
Currently translated at 26.2% (224 of 853 strings)

Translation: Audiobookshelf/Abs Web Client
Translate-URL: https://hosted.weblate.org/projects/audiobookshelf/abs-web-client/fi/
2024-08-08 23:54:39 +02:00
tonttula
fc503691fe Translated using Weblate (Finnish)
Currently translated at 25.9% (221 of 853 strings)

Translation: Audiobookshelf/Abs Web Client
Translate-URL: https://hosted.weblate.org/projects/audiobookshelf/abs-web-client/fi/
2024-08-08 23:54:39 +02:00
Charlie
c80dd43a3e Translated using Weblate (French)
Currently translated at 99.7% (851 of 853 strings)

Translation: Audiobookshelf/Abs Web Client
Translate-URL: https://hosted.weblate.org/projects/audiobookshelf/abs-web-client/fr/
2024-08-08 23:54:39 +02:00
Charlie
a4a62e0c18 Translated using Weblate (French)
Currently translated at 99.7% (851 of 853 strings)

Translation: Audiobookshelf/Abs Web Client
Translate-URL: https://hosted.weblate.org/projects/audiobookshelf/abs-web-client/fr/
2024-08-08 23:54:39 +02:00
advplyr
2f98cb9b6d Update:Changelog shows all releases matching minor version and lower than current #3250 2024-08-07 17:56:55 -05:00
advplyr
91dc6eebb0 Merge pull request #3254 from mikiher/unc-path-support
Fix path normalization to support UNC paths
2024-08-07 15:27:53 -05:00
mikiher
d72e0a4418 Fix path normalization to support UNC paths 2024-08-07 21:18:53 +03:00
advplyr
2c8ebd43cc Update ApiCacheManager unit test 2024-08-06 17:26:36 -05:00
advplyr
9f561aa296 Update:Skip library api cache for random sort #3249 2024-08-06 17:20:53 -05:00
advplyr
930bacd45d Fix:Podcast episode download request failing due to user-agent string #3246 2024-08-05 16:59:37 -05:00
ic1415
ef2d736b20 Update LibraryItemController.js
standardizing log messages for all download cases
2024-08-05 16:33:56 -04:00
ic1415
f3a453be20 Update LibraryItemController.js
Additional logging for single file downloads coming from download function
2024-08-05 16:19:28 -04:00
advplyr
45c97a778d Merge branch 'master' of https://github.com/advplyr/audiobookshelf 2024-08-05 09:01:53 -05:00
advplyr
6ebc64f73b Version bump v2.12.1 2024-08-05 09:01:45 -05:00
advplyr
52807d0d49 Merge pull request #3233 from weblate/weblate-audiobookshelf-abs-web-client
Translations update from Hosted Weblate
2024-08-05 08:58:13 -05:00
SunSpring
a5e18e99bc Translated using Weblate (Chinese (Simplified))
Currently translated at 100.0% (853 of 853 strings)

Translation: Audiobookshelf/Abs Web Client
Translate-URL: https://hosted.weblate.org/projects/audiobookshelf/abs-web-client/zh_Hans/
2024-08-05 15:55:41 +02:00
Charlie
f545b3e745 Translated using Weblate (French)
Currently translated at 99.7% (851 of 853 strings)

Translation: Audiobookshelf/Abs Web Client
Translate-URL: https://hosted.weblate.org/projects/audiobookshelf/abs-web-client/fr/
2024-08-05 15:55:41 +02:00
Vito0912
e0877803e3 Translated using Weblate (German)
Currently translated at 100.0% (853 of 853 strings)

Translation: Audiobookshelf/Abs Web Client
Translate-URL: https://hosted.weblate.org/projects/audiobookshelf/abs-web-client/de/
2024-08-05 15:55:41 +02:00
advplyr
4916887c8d Merge pull request #3236 from devnoname120/arm64/fix-unicode-so-load
Fix unicode.so loading
2024-08-05 08:55:37 -05:00
Paul
20eb573897 Fix unicode.so loading
`unicode.so` is dynamically linked against glibc but Alpine only comes with musl. Installing the compatibility layer fixes it.

Fix https://github.com/advplyr/audiobookshelf/issues/3231
Fix https://github.com/advplyr/audiobookshelf/issues/3234
Close https://github.com/advplyr/audiobookshelf/pull/3235
2024-08-05 13:34:56 +02:00
advplyr
8ff7b6b6e6 Add server log for process.platform and process.arch #3231 2024-08-04 17:08:55 -05:00
advplyr
06eaee8909 Fix:Binary manager dylib file ext check #3231 2024-08-04 16:51:07 -05:00
advplyr
8f9487ba70 Version bump v2.12.0 2024-08-04 16:36:38 -05:00
advplyr
eca51457b7 Update jsdocs and auto-formatting 2024-08-04 16:13:40 -05:00
advplyr
15c6fce648 Remove duplicate dependency ms 2024-08-04 12:52:52 -05:00
advplyr
6c872263c6 Fix:Show changelog when clicking version on config side rail #3232 2024-08-04 12:30:18 -05:00
advplyr
4d3b3d1740 Update:Replace default express-session MemoryStore with stable MemoryStore #2538 2024-08-04 12:00:10 -05:00
advplyr
bba8920855 Merge pull request #3228 from weblate/weblate-audiobookshelf-abs-web-client
Translations update from Hosted Weblate
2024-08-04 10:10:02 -05:00
gallegonovato
f56b9487ff Translated using Weblate (Spanish)
Currently translated at 100.0% (853 of 853 strings)

Translation: Audiobookshelf/Abs Web Client
Translate-URL: https://hosted.weblate.org/projects/audiobookshelf/abs-web-client/es/
2024-08-04 00:09:30 +02:00
Mario
1946d8296b Translated using Weblate (German)
Currently translated at 100.0% (853 of 853 strings)

Translation: Audiobookshelf/Abs Web Client
Translate-URL: https://hosted.weblate.org/projects/audiobookshelf/abs-web-client/de/
2024-08-04 00:09:29 +02:00
burghy86
41e5d7f820 Translated using Weblate (Italian)
Currently translated at 100.0% (852 of 852 strings)

Translation: Audiobookshelf/Abs Web Client
Translate-URL: https://hosted.weblate.org/projects/audiobookshelf/abs-web-client/it/
2024-08-04 00:09:28 +02:00
weblate.user.1274
2507568103 Translated using Weblate (Norwegian Bokmål)
Currently translated at 83.4% (711 of 852 strings)

Translation: Audiobookshelf/Abs Web Client
Translate-URL: https://hosted.weblate.org/projects/audiobookshelf/abs-web-client/nb_NO/
2024-08-04 00:09:27 +02:00
gallegonovato
19733798fa Translated using Weblate (Spanish)
Currently translated at 100.0% (852 of 852 strings)

Translation: Audiobookshelf/Abs Web Client
Translate-URL: https://hosted.weblate.org/projects/audiobookshelf/abs-web-client/es/
2024-08-04 00:09:26 +02:00
Mario
427d6da360 Translated using Weblate (German)
Currently translated at 100.0% (852 of 852 strings)

Translation: Audiobookshelf/Abs Web Client
Translate-URL: https://hosted.weblate.org/projects/audiobookshelf/abs-web-client/de/
2024-08-04 00:09:25 +02:00
Mario
2b67d3d1c5 Translated using Weblate (German)
Currently translated at 100.0% (850 of 850 strings)

Translation: Audiobookshelf/Abs Web Client
Translate-URL: https://hosted.weblate.org/projects/audiobookshelf/abs-web-client/de/
2024-08-04 00:09:24 +02:00
gallegonovato
6926a40ad6 Translated using Weblate (Spanish)
Currently translated at 100.0% (850 of 850 strings)

Translation: Audiobookshelf/Abs Web Client
Translate-URL: https://hosted.weblate.org/projects/audiobookshelf/abs-web-client/es/
2024-08-04 00:09:24 +02:00
Illia Pyshniak
7a8da5bf3a Translated using Weblate (Ukrainian)
Currently translated at 100.0% (849 of 849 strings)

Translation: Audiobookshelf/Abs Web Client
Translate-URL: https://hosted.weblate.org/projects/audiobookshelf/abs-web-client/uk/
2024-08-04 00:09:23 +02:00
gallegonovato
fc8fa17c6f Translated using Weblate (Spanish)
Currently translated at 100.0% (849 of 849 strings)

Translation: Audiobookshelf/Abs Web Client
Translate-URL: https://hosted.weblate.org/projects/audiobookshelf/abs-web-client/es/
2024-08-04 00:09:22 +02:00
Mario
0a88659a9f Translated using Weblate (German)
Currently translated at 100.0% (849 of 849 strings)

Translation: Audiobookshelf/Abs Web Client
Translate-URL: https://hosted.weblate.org/projects/audiobookshelf/abs-web-client/de/
2024-08-04 00:09:21 +02:00
Illia Pyshniak
9967858c44 Translated using Weblate (Ukrainian)
Currently translated at 100.0% (849 of 849 strings)

Translation: Audiobookshelf/Abs Web Client
Translate-URL: https://hosted.weblate.org/projects/audiobookshelf/abs-web-client/uk/
2024-08-04 00:09:21 +02:00
Mario
e2ce388f90 Translated using Weblate (German)
Currently translated at 100.0% (849 of 849 strings)

Translation: Audiobookshelf/Abs Web Client
Translate-URL: https://hosted.weblate.org/projects/audiobookshelf/abs-web-client/de/
2024-08-04 00:09:20 +02:00
gallegonovato
f31649f1d2 Translated using Weblate (Spanish)
Currently translated at 100.0% (849 of 849 strings)

Translation: Audiobookshelf/Abs Web Client
Translate-URL: https://hosted.weblate.org/projects/audiobookshelf/abs-web-client/es/
2024-08-04 00:09:20 +02:00
advplyr
a55c167dde Fix:Cleanup media progress when deleting podcasts, remove usage of old user model 2024-08-03 17:09:17 -05:00
advplyr
642cf232ba Merge branch 'master' of https://github.com/advplyr/audiobookshelf 2024-08-03 15:08:45 -05:00
advplyr
164b4525c4 Replace function for cleaning up user seriesHideFromContinueListening to not use old model 2024-08-03 15:08:03 -05:00
advplyr
39c26d2bee Merge pull request #3217 from ic1415/LibraryItemController
Update LibraryItemController.js
2024-08-02 16:33:23 -05:00
advplyr
2a69955cc1 Update server/controllers/LibraryItemController.js 2024-08-02 16:30:21 -05:00
advplyr
4a5345dd5d Update:devcontainer dev.js default to not skip binaries check, fail gracefully if required binary env variables are not set when skipping 2024-08-01 14:25:57 -05:00
advplyr
1e6dd0e3e0 Add jsdocs for Ffmpeg and tools controller 2024-07-31 17:32:51 -05:00
advplyr
91cca2e358 Merge pull request #3214 from faush01/feature/persist_encoding_options
persist and show the advanced encoding options
2024-07-31 16:52:36 -05:00
advplyr
816a9be618 Merge pull request #3208 from nichwall/release_issue_workflow
Add: workflow to close issues on release
2024-07-31 16:07:24 -05:00
ic1415
9eb0ec76fe Update LibraryItemController.js
update library item controller to log downloaded ebooks to fix #3215
2024-07-31 10:48:41 -04:00
Shaun
49054d5239 persist the advanced encoding options, show the encoding options used with in progress encodes 2024-07-31 16:44:24 +10:00
advplyr
787c4e45a8 Merge pull request #3212 from mikiher/library-fetch
On item pages, fetch the item's library data to the store if it's not available
2024-07-30 16:52:51 -05:00
advplyr
34cb7a4d02 Remove unused loadLibraryFilterData func 2024-07-30 16:49:02 -05:00
advplyr
006241163b Replace setCurrentLibrary calls with fetch to ensure filterData matches library 2024-07-30 16:35:26 -05:00
advplyr
03818fadee Remove unnecessary setCurrentLibrary on mounted item page 2024-07-30 16:20:36 -05:00
mikiher
897c3ea625 on item pages, fetch item's library data if unavailable 2024-07-30 20:02:03 +03:00
Nicholas Wallace
73e4293f04 Fix: label has space in name 2024-07-29 18:08:40 -07:00
Nicholas Wallace
6f5ffcb1f8 Add: workflow to close issues on release 2024-07-29 18:06:05 -07:00
advplyr
ed70f3af83 Fix:Material symbols icon to use check instead of checkmark 2024-07-29 17:34:05 -05:00
advplyr
73196f9be8 Update:Match tab support clicking current value to set it #3200 2024-07-29 17:31:52 -05:00
advplyr
a77f4e9d77 Merge pull request #3204 from mikiher/embed-permissions
Fix permission issues in embed/merge
2024-07-29 16:49:12 -05:00
mikiher
294490f814 Fix permission issues in embed/merge 2024-07-29 20:19:58 +03:00
advplyr
6183001fca Merge pull request #3199 from mikiher/unaccent
Support accent-insensitive search using SQLean unicode sqlite3 extension
2024-07-28 17:21:32 -05:00
advplyr
3ac604c665 Remove ffmpeg binaries install step from debian preinst script 2024-07-28 16:55:45 -05:00
advplyr
e342b07cd0 Update:OPF metadata parser supports namespaces on creator and identifier tags #3201 2024-07-28 14:54:17 -05:00
advplyr
b524cbd1b3 Update:Parse epub cover image uses cover specified in opf meta #3201 2024-07-28 14:34:31 -05:00
advplyr
88693d73bd Fix:Shares not working with timeouts longer than 23 days #3164 2024-07-27 17:40:51 -05:00
mikiher
2c453a34ee Remove redundant console.log() message 2024-07-27 23:09:46 +03:00
mikiher
3d2b2e43b1 Set execution permission for downloaded binaries 2024-07-27 23:03:40 +03:00
mikiher
c3f3fca896 Remove dependency on libs/ffbinaries from BinaryManager test 2024-07-27 22:44:01 +03:00
mikiher
dedf6e5d4b Support accent-insensitive matching using the sqlean sqlite3 unicode extension 2024-07-27 21:56:07 +03:00
mikiher
6c379fc3a7 Add /unicode* to .gitignore 2024-07-27 21:52:33 +03:00
mikiher
329e9c9eb2 BinaryManager support for libraries and downloading from github release assets 2024-07-27 21:51:31 +03:00
advplyr
ee53086444 Update chapter modal colors for consistency 2024-07-26 17:36:39 -05:00
advplyr
43d6c6678f Add:Random library sorting option for libraries and series #3166
- Fixed author sort and match button not showing
2024-07-25 16:10:42 -05:00
advplyr
82f136ba79 Merge pull request #3195 from mikiher/remove-match-logic
Simplify ItemSearchCard component by removing matching logic
2024-07-25 15:46:12 -05:00
mikiher
e40d3dd64d Simplify ItemSearchCard component 2024-07-25 09:40:18 +03:00
advplyr
a5897fd64b Fix:Set series and collection RSS feed cover image using first item with cover #3193 2024-07-24 16:40:45 -05:00
advplyr
e786e3c057 Remove references to matrix server 2024-07-23 16:08:47 -05:00
advplyr
d347645475 Update:Format numbers on user stats page #3187 2024-07-22 17:43:42 -05:00
advplyr
215b78c162 Merge pull request #3186 from nichwall/author_image_restore
Ensure author and items folder is created before restoring backup
2024-07-22 16:41:55 -05:00
Nicholas Wallace
ee271519f9 Ensure author folder is created before extracting files 2024-07-21 18:04:46 +00:00
advplyr
b350277bbc Remove unused files 2024-07-21 11:12:17 -05:00
advplyr
604ae080ac Merge pull request #3185 from mikiher/genre-search
Adds genres to gloabl search
2024-07-21 11:07:43 -05:00
advplyr
a191dab359 Show numItems and numBooks on search cards 2024-07-21 11:07:54 -05:00
advplyr
3223011b13 Fix:Switching libraries on search page losing q query string 2024-07-21 10:52:23 -05:00
advplyr
f746e246e4 Merge pull request #3184 from brendans-bits/optionalDependencies
Moved cypress to `optionalDependencies`
2024-07-21 09:54:05 -05:00
mikiher
0476b68585 fix: Encode search query parameter in search.vue 2024-07-21 14:00:55 +03:00
mikiher
ec395bed72 fix: Encode search query parameter in GlobalSearch.vue 2024-07-21 13:53:10 +03:00
mikiher
bff56220c2 Adds genres to gloabl search 2024-07-21 11:10:05 +03:00
Brendan
3006405a52 Moved cypress to optionalDependencies 2024-07-21 11:30:50 +10:00
advplyr
9cd0ac80b1 Merge pull request #3182 from mikiher/ffmpeg-progress
Add progress and fixes to m4b and embed tools
2024-07-20 15:31:03 -05:00
mikiher
da51d38ba2 Improve documentation and arg names in TrackProgressMonitor.js 2024-07-20 21:42:58 +03:00
advplyr
5ba6459069 Merge pull request #3179 from nichwall/api_spec_opml
Update: podcast opml endpoints
2024-07-20 11:02:15 -05:00
mikiher
75899242fd put MessageEmbedFailed in akphabetic order. 2024-07-20 13:05:03 +03:00
mikiher
7faf42d892 Merge branch 'advplyr:master' into ffmpeg-progress 2024-07-20 12:28:47 +03:00
mikiher
10f5f331d7 Fixes + add progress to m4b and embed tools 2024-07-20 12:28:06 +03:00
Nicholas Wallace
b1414388e1 Add: podcast tags to opml endpoints 2024-07-20 02:42:41 +00:00
Nicholas Wallace
eb0f5b2e1b Update: podcast opml endpoints 2024-07-20 02:38:56 +00:00
advplyr
7af02ad2e2 Fix:Series bookshelf row padding when using ignore prefixes setting #3169 2024-07-19 17:12:12 -05:00
advplyr
8330dabc46 Merge branch 'master' of https://github.com/advplyr/audiobookshelf 2024-07-18 16:43:10 -05:00
advplyr
dbc7ad0b3b Fix:Podcast episode match not properly encoding search query #3177 2024-07-18 16:43:00 -05:00
advplyr
c0fd24770e Merge pull request #3170 from ajvgwu/nfo-metadata-language
Parse book language from NFO metadata source
2024-07-18 16:14:48 -05:00
advplyr
4289fe4990 Update Nfo scanner and auto-formatting 2024-07-18 16:09:40 -05:00
advplyr
e925e9b23f Update:Increase size of more menu icon on items page 2024-07-18 16:06:37 -05:00
Alex
71cd86fdd5 Merge branch 'advplyr:master' into nfo-metadata-language 2024-07-18 13:26:05 -04:00
advplyr
03be947ad6 Merge pull request #3163 from ajyey/feature/disable-max-backup-size
Adds support for allowing backups of unlimited size
2024-07-17 17:09:37 -05:00
advplyr
96f9084f2e Update:Disable axios progress indicator for sync requests 2024-07-17 17:11:57 -05:00
advplyr
bbccfcbd12 Merge pull request #3165 from weblate/weblate-audiobookshelf-abs-web-client
Translations update from Hosted Weblate
2024-07-17 16:57:52 -05:00
Alex
9a697f48db feat: parse language from NFO metadata source 2024-07-17 11:50:27 -04:00
advplyr
37ad1cced2 Fix:Large OPML import timeouts #3118
- Added OPML Api endpoints for /parse and /create, removed old
- Show task for OPML import and create failed tasks for failed feeds
2024-07-16 17:05:52 -05:00
Mario
26db20f63d Translated using Weblate (German)
Currently translated at 100.0% (848 of 848 strings)

Translation: Audiobookshelf/Abs Web Client
Translate-URL: https://hosted.weblate.org/projects/audiobookshelf/abs-web-client/de/
2024-07-16 16:47:20 +02:00
Andrew Leonard
ff788e3591 feat: adds better conditional for max backup size 2024-07-16 01:31:12 -04:00
Andrew Leonard
4b482488de feat: remember setting of 0 on server side 2024-07-16 01:30:00 -04:00
Andrew Leonard
e230b6640f feat: adds unlimited text to text label 2024-07-16 01:11:20 -04:00
Andrew Leonard
2bc949fae3 feat: adds support for allowing backups of unlimited size 2024-07-15 23:58:05 -04:00
advplyr
b1bc472205 Update:Incrase icon font size for more context menu and player loading 2024-07-15 17:38:13 -05:00
gallegonovato
5c7a38c292 Translated using Weblate (Spanish)
Currently translated at 100.0% (848 of 848 strings)

Translation: Audiobookshelf/Abs Web Client
Translate-URL: https://hosted.weblate.org/projects/audiobookshelf/abs-web-client/es/
2024-07-16 00:35:43 +02:00
Petteri Hjort
bbd6c51eb6 Translated using Weblate (Finnish)
Currently translated at 21.4% (182 of 848 strings)

Translation: Audiobookshelf/Abs Web Client
Translate-URL: https://hosted.weblate.org/projects/audiobookshelf/abs-web-client/fi/
2024-07-16 00:35:43 +02:00
Hosted Weblate
d17f9b0687 Update translation files
Updated by "Cleanup translation files" add-on in Weblate.

Translation: Audiobookshelf/Abs Web Client
Translate-URL: https://hosted.weblate.org/projects/audiobookshelf/abs-web-client/
2024-07-16 00:35:43 +02:00
Marcin Martela
4d2bdb6eee Translated using Weblate (Polish)
Currently translated at 90.2% (763 of 845 strings)

Translation: Audiobookshelf/Abs Web Client
Translate-URL: https://hosted.weblate.org/projects/audiobookshelf/abs-web-client/pl/
2024-07-16 00:35:43 +02:00
Vito0912
b6a1014c72 Translated using Weblate (German)
Currently translated at 100.0% (845 of 845 strings)

Translation: Audiobookshelf/Abs Web Client
Translate-URL: https://hosted.weblate.org/projects/audiobookshelf/abs-web-client/de/
2024-07-16 00:35:43 +02:00
gallegonovato
b99885c806 Translated using Weblate (Spanish)
Currently translated at 100.0% (845 of 845 strings)

Translation: Audiobookshelf/Abs Web Client
Translate-URL: https://hosted.weblate.org/projects/audiobookshelf/abs-web-client/es/
2024-07-16 00:35:43 +02:00
tonttula
f422c9b820 Translated using Weblate (Finnish)
Currently translated at 21.4% (181 of 844 strings)

Translation: Audiobookshelf/Abs Web Client
Translate-URL: https://hosted.weblate.org/projects/audiobookshelf/abs-web-client/fi/
2024-07-16 00:35:43 +02:00
Ori Z
0befe91360 Translated using Weblate (Hebrew)
Currently translated at 92.2% (779 of 844 strings)

Translation: Audiobookshelf/Abs Web Client
Translate-URL: https://hosted.weblate.org/projects/audiobookshelf/abs-web-client/he/
2024-07-16 00:35:43 +02:00
gallegonovato
da671e3fd5 Translated using Weblate (Spanish)
Currently translated at 100.0% (844 of 844 strings)

Translation: Audiobookshelf/Abs Web Client
Translate-URL: https://hosted.weblate.org/projects/audiobookshelf/abs-web-client/es/
2024-07-16 00:35:43 +02:00
Mario
fec94c18aa Translated using Weblate (German)
Currently translated at 100.0% (844 of 844 strings)

Translation: Audiobookshelf/Abs Web Client
Translate-URL: https://hosted.weblate.org/projects/audiobookshelf/abs-web-client/de/
2024-07-16 00:35:43 +02:00
gallegonovato
11c6fc7d90 Translated using Weblate (Spanish)
Currently translated at 100.0% (838 of 838 strings)

Translation: Audiobookshelf/Abs Web Client
Translate-URL: https://hosted.weblate.org/projects/audiobookshelf/abs-web-client/es/
2024-07-16 00:35:43 +02:00
Michał Pomarański
7ea5e7dc95 Translated using Weblate (Polish)
Currently translated at 90.4% (758 of 838 strings)

Translation: Audiobookshelf/Abs Web Client
Translate-URL: https://hosted.weblate.org/projects/audiobookshelf/abs-web-client/pl/
2024-07-16 00:35:43 +02:00
Mario
2a98e2c361 Translated using Weblate (German)
Currently translated at 100.0% (838 of 838 strings)

Translation: Audiobookshelf/Abs Web Client
Translate-URL: https://hosted.weblate.org/projects/audiobookshelf/abs-web-client/de/
2024-07-16 00:35:43 +02:00
Charlie
7fb499b301 Translated using Weblate (French)
Currently translated at 100.0% (832 of 832 strings)

Translation: Audiobookshelf/Abs Web Client
Translate-URL: https://hosted.weblate.org/projects/audiobookshelf/abs-web-client/fr/
2024-07-16 00:35:43 +02:00
Lode Smets
af9aee76cf Translated using Weblate (Dutch)
Currently translated at 86.7% (722 of 832 strings)

Translation: Audiobookshelf/Abs Web Client
Translate-URL: https://hosted.weblate.org/projects/audiobookshelf/abs-web-client/nl/
2024-07-16 00:35:43 +02:00
gallegonovato
075ec15f02 Translated using Weblate (Spanish)
Currently translated at 100.0% (832 of 832 strings)

Translation: Audiobookshelf/Abs Web Client
Translate-URL: https://hosted.weblate.org/projects/audiobookshelf/abs-web-client/es/
2024-07-16 00:35:43 +02:00
Valentin
1c650473f8 Translated using Weblate (German)
Currently translated at 100.0% (832 of 832 strings)

Translation: Audiobookshelf/Abs Web Client
Translate-URL: https://hosted.weblate.org/projects/audiobookshelf/abs-web-client/de/
2024-07-16 00:35:43 +02:00
Valentin
0efdf50821 Translated using Weblate (German)
Currently translated at 100.0% (832 of 832 strings)

Translation: Audiobookshelf/Abs Web Client
Translate-URL: https://hosted.weblate.org/projects/audiobookshelf/abs-web-client/de/
2024-07-16 00:35:43 +02:00
Mario
df65ef2191 Translated using Weblate (German)
Currently translated at 100.0% (832 of 832 strings)

Translation: Audiobookshelf/Abs Web Client
Translate-URL: https://hosted.weblate.org/projects/audiobookshelf/abs-web-client/de/
2024-07-16 00:35:43 +02:00
Ahetek
bc3b1d9565 Translated using Weblate (Polish)
Currently translated at 90.8% (756 of 832 strings)

Translation: Audiobookshelf/Abs Web Client
Translate-URL: https://hosted.weblate.org/projects/audiobookshelf/abs-web-client/pl/
2024-07-16 00:35:43 +02:00
advplyr
2998d3ba6a Merge pull request #3160 from glorenzen/home-page-ultrawide-support
Add ultrawide support for home page
2024-07-15 16:30:44 -05:00
Greg Lorenzen
ea11153032 Update responsive limit for displayed items on personalized shelves on home page 2024-07-15 15:15:06 +00:00
Greg Lorenzen
733f61075f WIP: Add "End of chapter" option for sleep timer (#3151)
* Add SleepTimerTypes for countdown and chapter

* Add functionality for 'end of chapter' sleep timer

* Fix custom time for sleep timer

* Include end of chapter string for sleep timer

* Increase chapter end tolerance to 0.75

* Show sleep time options in modal when timer is active

* Add SleepTimerTypes for countdown and chapter

* Add functionality for 'end of chapter' sleep timer

* Fix custom time for sleep timer

* Include end of chapter string for sleep timer

* Increase chapter end tolerance to 0.75

* Show sleep time options in modal when timer is active

* Sleep timer cleanup

* Localization for sleep timer modal, UI updates

---------

Co-authored-by: advplyr <advplyr@protonmail.com>
2024-07-14 13:56:48 -05:00
Greg Lorenzen
618e69775c Add responsive limit for displayed items on personalized shelves on home page 2024-07-13 22:32:44 -07:00
advplyr
eabfa90121 Update:Move library stats page to SideRail #3134 2024-07-13 15:26:07 -05:00
Greg Lorenzen
43b7ccd61a WIP: Add adjustable skip amount (#3113)
* Add playback settings string to en-us

* Add playback settings UI for jump forwards and jump backwards

* Remove jump forwards and jump backwards settings

* Remove jump forwards and jump backwards en-us strings

* Update player UI to include player settings button

* Add label view player settings string

* Add PlayerSettingsModal component

Includes a toggle switch for enabling/disabling the chapter track feature.

* Add player settings modal component to MediaPlayerContainer

* Handle useChapterTrack changes in PlayerUI

* Add jump forwards and jump backwards settings to user store

* Add jump forwards and jump backwards label strings

* Add jump forwards and jump backwards settings to PlayerSettingsModal

* Update jump forwards and jump backwards to handle user state values in PlayerHandler

* Update jump backwards icon in PlayerPlaybackControls

* Add playback settings string to en-us

* Add playback settings UI for jump forwards and jump backwards

* Remove jump forwards and jump backwards settings

* Remove jump forwards and jump backwards en-us strings

* Update player UI to include player settings button

* Add label view player settings string

* Add PlayerSettingsModal component

Includes a toggle switch for enabling/disabling the chapter track feature.

* Add player settings modal component to MediaPlayerContainer

* Handle useChapterTrack changes in PlayerUI

* Add jump forwards and jump backwards settings to user store

* Add jump forwards and jump backwards label strings

* Add jump forwards and jump backwards settings to PlayerSettingsModal

* Update jump forwards and jump backwards to handle user state values in PlayerHandler

* Update jump backwards icon in PlayerPlaybackControls

* Add jump amounts to playback controls tooltips

* Fix merge issues and add new Material Symbols to player ui

* Alphabetize strings in en-us.json

* Update dropdown component with SelectInput to support menu overflowing modal

* Update localization for player settings

* Update en-us strings order

---------

Co-authored-by: advplyr <advplyr@protonmail.com>
2024-07-12 17:52:48 -05:00
advplyr
b6875a44cf Merge pull request #3152 from mikiher/fix-opf-fetch-series
Fix OPF fetchSeries so it deduplicates found series
2024-07-12 07:59:10 -05:00
mikiher
c0004dd532 Fix fetchSeries so it deduplicates returned series 2024-07-12 12:42:42 +03:00
advplyr
0ee3b89760 Fix:Series and collection RSS feeds keeping correct order #3137 2024-07-11 17:49:05 -05:00
Nicholas W
c5e60d30e1 Podcast endpoints (#3140)
* Add: `AudioTrack.yaml`

* Fix: audiotrack example

* Initial: podcast schemas and endpoints

* Update schemas

* Add: podcasts tag

* Update bundled spec
2024-07-11 16:29:35 -05:00
advplyr
acaf1ac196 Merge pull request #3138 from glorenzen/update-material-icons
Update material icons
2024-07-10 16:41:21 -05:00
advplyr
8dc4538c95 Remove old material-icons files and classes 2024-07-10 16:41:42 -05:00
advplyr
e224fd2595 Update edit library folder icon to fill 2024-07-10 16:38:58 -05:00
advplyr
f0a1ea4d6d Merge pull request #3139 from nichwall/email_notification_linting_fixes
Email notification linting fixes
2024-07-09 16:25:31 -05:00
advplyr
10cb8ebf3b Merge pull request #3117 from mikiher/show-subtitles
New feature: Show Subtitles
2024-07-09 16:08:36 -05:00
advplyr
8c4afa1866 Fix typo 2024-07-09 16:09:41 -05:00
advplyr
eb5af47bbf Merge branch 'master' into show-subtitles 2024-07-09 15:58:34 -05:00
Nicholas Wallace
4fd93ce64c Update bundled spec 2024-07-09 01:55:55 +00:00
Nicholas Wallace
7ba4e9e66d Add: summary to Notification endpoints 2024-07-09 01:55:35 +00:00
Nicholas Wallace
e2e5449d25 Fix: schema reference in EmailController 2024-07-09 01:51:39 +00:00
Greg Lorenzen
abc76ca155 Replace Material Icons in LaxyBookCard and ShareModal components 2024-07-08 22:29:36 +00:00
Greg Lorenzen
0fc84a8684 Replace material-icons in YearInReview components 2024-07-08 22:29:20 +00:00
Greg Lorenzen
a76600e53b Update play_arrow icons with fill CSS class 2024-07-08 22:00:16 +00:00
Greg Lorenzen
e55cf30705 Add material-icons fill class to play icon in LazyBookCard.vue 2024-07-08 16:43:50 +00:00
Greg Lorenzen
2c65b8fd2b Replace material-icons class with material-symbols class in components 2024-07-08 09:39:00 -07:00
Greg Lorenzen
20b8e35132 Revert material-icons CSS classes
Add new material-symbols classes for Material Symbols fonts
2024-07-08 09:39:00 -07:00
Greg Lorenzen
8007225a41 Update font names and file paths for Material Symbols 2024-07-08 09:39:00 -07:00
Greg Lorenzen
63a6da6680 Add Material Symbols outlined and rounded woff files 2024-07-08 09:39:00 -07:00
advplyr
93114b2181 Version bump v2.11.0 2024-07-07 17:23:57 -05:00
advplyr
f6dd3de8e7 Merge branch 'master' of https://github.com/advplyr/audiobookshelf 2024-07-07 17:20:20 -05:00
advplyr
0918391636 Update home page shelves to handle share open/closed sockets 2024-07-07 17:19:44 -05:00
advplyr
972b4f7388 Merge pull request #3133 from weblate/weblate-audiobookshelf-abs-web-client
Translations update from Hosted Weblate
2024-07-07 17:10:58 -05:00
advplyr
af92ae4d51 Add link to media item shares guide in share modal 2024-07-07 16:09:32 -05:00
Ahetek
3bc6426cc7 Translated using Weblate (Polish)
Currently translated at 89.5% (745 of 832 strings)

Translation: Audiobookshelf/Abs Web Client
Translate-URL: https://hosted.weblate.org/projects/audiobookshelf/abs-web-client/pl/
2024-07-07 23:07:25 +02:00
Ahetek
acfbbd5aec Translated using Weblate (Polish)
Currently translated at 84.7% (705 of 832 strings)

Translation: Audiobookshelf/Abs Web Client
Translate-URL: https://hosted.weblate.org/projects/audiobookshelf/abs-web-client/pl/
2024-07-07 23:07:24 +02:00
Vito0912
9b677be12e Translated using Weblate (German)
Currently translated at 99.7% (830 of 832 strings)

Translation: Audiobookshelf/Abs Web Client
Translate-URL: https://hosted.weblate.org/projects/audiobookshelf/abs-web-client/de/
2024-07-07 23:07:24 +02:00
advplyr
2f2ec2ec1f Add book item more menu item for Share, restrict share to admin or up, add admin socket events for open/close shares 2024-07-07 15:51:50 -05:00
advplyr
e05ab14ad2 Merge pull request #3128 from weblate/weblate-audiobookshelf-abs-web-client
Translations update from Hosted Weblate
2024-07-06 16:28:54 -05:00
advplyr
9074e9ed88 Merge pull request #3125 from nichwall/changelog_link
Changelog Pub Date
2024-07-06 16:28:22 -05:00
advplyr
1e5cb09ada Update changelog version to link to release, pass versionData into changelog modal 2024-07-06 16:28:36 -05:00
advplyr
b0f1827e3c Merge branch 'master' into changelog_link 2024-07-06 16:06:51 -05:00
gallegonovato
ae7713bacc Translated using Weblate (Spanish)
Currently translated at 99.8% (831 of 832 strings)

Translation: Audiobookshelf/Abs Web Client
Translate-URL: https://hosted.weblate.org/projects/audiobookshelf/abs-web-client/es/
2024-07-06 21:03:28 +00:00
Jeldrik
b6c185eebe Translated using Weblate (German)
Currently translated at 99.2% (824 of 830 strings)

Translation: Audiobookshelf/Abs Web Client
Translate-URL: https://hosted.weblate.org/projects/audiobookshelf/abs-web-client/de/
2024-07-06 21:03:28 +00:00
gallegonovato
5114be0773 Translated using Weblate (Spanish)
Currently translated at 100.0% (830 of 830 strings)

Translation: Audiobookshelf/Abs Web Client
Translate-URL: https://hosted.weblate.org/projects/audiobookshelf/abs-web-client/es/
2024-07-06 21:03:27 +00:00
advplyr
9a4c5a16ef Merge pull request #3111 from mikiher/tone-replacement
Replace tone with ffmpeg for metadata and cover embedding
2024-07-06 16:03:17 -05:00
advplyr
e6b1acfb44 Remove tone scripts & references, rename tone-object endpoint, remove node-tone dependency, remove TONE_PATH env 2024-07-06 16:00:48 -05:00
advplyr
1e5787c60d Merge pull request #3126 from mikiher/pkg-replacement
Replace pkg with @yao-pkg/pkg and target node20
2024-07-06 14:33:52 -05:00
mikiher
928b080677 Replace pkg with @yao-pkg/pkg and target node20 2024-07-06 19:43:55 +03:00
Nicholas Wallace
3764ef14a9 Add: publish date of current version to modal 2024-07-06 16:21:06 +00:00
Nicholas Wallace
92aae736c4 Add: current pubdate 2024-07-06 15:57:20 +00:00
mikiher
31c8cb476a Make menu item localized where they're not 2024-07-06 00:58:25 +03:00
advplyr
3a2f786517 Merge pull request #3122 from nichwall/backup_field_prevent_edits_with_env
Prevent backup path edits when ENV is set
2024-07-05 16:10:14 -05:00
advplyr
7c0b4e35d7 Update backups config page to use backupPathEnvSet returned from endpoint, remove from ServerConfig 2024-07-05 16:10:07 -05:00
advplyr
0461b57e6c Merge pull request #3116 from nichwall/email_endpoints
Email endpoints
2024-07-05 15:35:37 -05:00
Nicholas Wallace
a1688488e5 Fix: name of backupPathEnvSet variable 2024-07-05 17:58:42 +00:00
Nicholas Wallace
4d24817ced Prevent editing backup path in web interface when env variable set 2024-07-05 17:44:49 +00:00
Nicholas Wallace
d46de541d6 Fix: bad variable name 2024-07-05 17:41:07 +00:00
Nicholas Wallace
37f62d22b6 Add: report whether backup path environment is set 2024-07-05 17:27:49 +00:00
mikiher
79bd6a25d9 Move Collapse Series option to the tollbar's context menu 2024-07-05 00:45:18 +03:00
mikiher
0042604e6d Merge branch 'advplyr:master' into show-subtitles 2024-07-05 00:17:01 +03:00
advplyr
b01ef1c691 Fix library shelf height on sorting by title with ignore prefixes and sorting by published year 2024-07-04 16:07:28 -05:00
advplyr
277ff8a5a5 Add:Book library filter for Share Open 2024-07-04 15:45:47 -05:00
advplyr
d5f991ae4a Fix media share player screen height on android browsers 2024-07-04 15:28:44 -05:00
mikiher
54f2bb1092 Add a Show Subtitles option 2024-07-04 20:35:58 +03:00
mikiher
6b6df619f5 Remove tailwind.compiled.css (auto-generated, added by mistake) 2024-07-04 20:30:29 +03:00
advplyr
fed5ff4863 Add:Daily cron that closes stale open playback sessions 2024-07-04 12:00:54 -05:00
advplyr
43217657d7 Update media item shares to close when changing shares on same device 2024-07-04 11:19:29 -05:00
Nicholas Wallace
fa1518cb1d Fix: wrong settings path 2024-07-04 03:51:54 +00:00
Nicholas Wallace
6d14ed8a72 Update: bundled spec 2024-07-04 03:48:22 +00:00
Nicholas Wallace
b8e17de8b4 Add: EmailController to root.yaml 2024-07-04 03:45:04 +00:00
Nicholas Wallace
e60a91379a Rename folder 2024-07-04 03:40:17 +00:00
Nicholas Wallace
046bf52d88 Initial EmailController paths 2024-07-04 03:36:01 +00:00
Nicholas Wallace
bfc3c7e7c9 Initial email settings schemas 2024-07-04 03:18:52 +00:00
advplyr
dd1d2b7c92 Fix media item share changing share, show error on failed to play 2024-07-03 17:08:30 -05:00
mikiher
8bdee51798 Add unit tests for new ffmpegHelpers functions 2024-07-03 23:50:42 +03:00
advplyr
5858b64fc6 Update:Share audio player page background color gradient using fast-average-color on cover image 2024-07-02 17:40:42 -05:00
advplyr
4baa89c8e1 Fix:Audio player chapter name text overflow 2024-07-02 16:54:39 -05:00
mikiher
1b015beba4 Remove windows restrictions from Tools.vuw 2024-07-02 19:00:03 +03:00
mikiher
ebaec23648 Replace tone with ffmpeg in AbMergeManager 2024-07-02 18:25:04 +03:00
advplyr
d5e00c8bbd Update:Get personalized home page shelves and get library items endpoint optional includes for media item shares, show public icon on shared book items 2024-07-01 17:26:13 -05:00
advplyr
4732ca8119 Embed track number 2024-07-01 16:57:14 -05:00
advplyr
134c2580c9 Update:Custom error page for nuxt 2024-06-30 16:46:54 -05:00
advplyr
8e286a6070 Open media item share sessions shown on listening sessions page, create device info for share sessions 2024-06-30 16:36:00 -05:00
advplyr
d7ace4d1dc Update:Media item share URL allows for sending starting time as query string #1768 2024-06-30 15:31:27 -05:00
mikiher
a21b1f3b16 Make required changes for mp3 embedding 2024-06-30 15:45:25 +03:00
advplyr
c309856f74 Update:Media item share modal UI/UX and localization #1768 2024-06-29 16:15:55 -05:00
advplyr
31146082f0 Update:Media item share endpoints and audio player #1768
- Add endpoints for getting tracks, getting cover image and updating progress
- Implement share session cookie and caching share playback session
- Audio player UI/UX
2024-06-29 15:05:35 -05:00
mikiher
6fbbc65edf Replace tone with ffmpeg for metadata and cover embedding 2024-06-29 20:04:23 +03:00
advplyr
c1349e586a Update share page to show player ui 2024-06-28 17:01:28 -05:00
advplyr
8985ebebe2 Merge pull request #3108 from weblate/weblate-audiobookshelf-abs-web-client
Translations update from Hosted Weblate
2024-06-28 15:50:00 -05:00
J. Lavoie
394a004ff5 Translated using Weblate (Finnish)
Currently translated at 19.2% (158 of 821 strings)

Translation: Audiobookshelf/Abs Web Client
Translate-URL: https://hosted.weblate.org/projects/audiobookshelf/abs-web-client/fi/
2024-06-27 23:36:51 +02:00
J. Lavoie
33e6ad4ad6 Translated using Weblate (Portuguese (Brazil))
Currently translated at 99.7% (819 of 821 strings)

Translation: Audiobookshelf/Abs Web Client
Translate-URL: https://hosted.weblate.org/projects/audiobookshelf/abs-web-client/pt_BR/
2024-06-27 23:36:51 +02:00
J. Lavoie
05a0793a9c Translated using Weblate (Norwegian Bokmål)
Currently translated at 85.3% (701 of 821 strings)

Translation: Audiobookshelf/Abs Web Client
Translate-URL: https://hosted.weblate.org/projects/audiobookshelf/abs-web-client/nb_NO/
2024-06-27 23:36:51 +02:00
J. Lavoie
3a5e9cd865 Translated using Weblate (Italian)
Currently translated at 97.8% (803 of 821 strings)

Translation: Audiobookshelf/Abs Web Client
Translate-URL: https://hosted.weblate.org/projects/audiobookshelf/abs-web-client/it/
2024-06-27 23:36:51 +02:00
J. Lavoie
a7cd79850d Translated using Weblate (French)
Currently translated at 100.0% (821 of 821 strings)

Translation: Audiobookshelf/Abs Web Client
Translate-URL: https://hosted.weblate.org/projects/audiobookshelf/abs-web-client/fr/
2024-06-27 23:36:51 +02:00
J. Lavoie
386edb0427 Translated using Weblate (Spanish)
Currently translated at 100.0% (821 of 821 strings)

Translation: Audiobookshelf/Abs Web Client
Translate-URL: https://hosted.weblate.org/projects/audiobookshelf/abs-web-client/es/
2024-06-27 23:36:51 +02:00
J. Lavoie
6c1e25e964 Translated using Weblate (German)
Currently translated at 100.0% (821 of 821 strings)

Translation: Audiobookshelf/Abs Web Client
Translate-URL: https://hosted.weblate.org/projects/audiobookshelf/abs-web-client/de/
2024-06-27 23:36:51 +02:00
J. Lavoie
a6a956fc28 Translated using Weblate (Italian)
Currently translated at 97.4% (800 of 821 strings)

Translation: Audiobookshelf/Abs Web Client
Translate-URL: https://hosted.weblate.org/projects/audiobookshelf/abs-web-client/it/
2024-06-27 23:36:51 +02:00
J. Lavoie
fb7d6807e2 Translated using Weblate (French)
Currently translated at 99.7% (819 of 821 strings)

Translation: Audiobookshelf/Abs Web Client
Translate-URL: https://hosted.weblate.org/projects/audiobookshelf/abs-web-client/fr/
2024-06-27 23:36:51 +02:00
J. Lavoie
e9f8ca1c14 Translated using Weblate (German)
Currently translated at 100.0% (821 of 821 strings)

Translation: Audiobookshelf/Abs Web Client
Translate-URL: https://hosted.weblate.org/projects/audiobookshelf/abs-web-client/de/
2024-06-27 23:36:51 +02:00
dex girl
c669ca5be1 Translated using Weblate (Croatian)
Currently translated at 62.3% (512 of 821 strings)

Translation: Audiobookshelf/Abs Web Client
Translate-URL: https://hosted.weblate.org/projects/audiobookshelf/abs-web-client/hr/
2024-06-27 23:36:51 +02:00
Allan Nordhøy
6dd0fb4225 Translated using Weblate (Norwegian Bokmål)
Currently translated at 85.3% (701 of 821 strings)

Translation: Audiobookshelf/Abs Web Client
Translate-URL: https://hosted.weblate.org/projects/audiobookshelf/abs-web-client/nb_NO/
2024-06-27 23:36:51 +02:00
Allan Nordhøy
709f9a65fa Translated using Weblate (Norwegian Bokmål)
Currently translated at 85.5% (702 of 821 strings)

Translation: Audiobookshelf/Abs Web Client
Translate-URL: https://hosted.weblate.org/projects/audiobookshelf/abs-web-client/nb_NO/
2024-06-27 23:36:51 +02:00
phewi
3c888d2876 Translated using Weblate (Finnish)
Currently translated at 18.3% (151 of 821 strings)

Translation: Audiobookshelf/Abs Web Client
Translate-URL: https://hosted.weblate.org/projects/audiobookshelf/abs-web-client/fi/
2024-06-27 23:36:51 +02:00
Allan Nordhøy
aca39011bb Translated using Weblate (Norwegian Bokmål)
Currently translated at 85.6% (703 of 821 strings)

Translation: Audiobookshelf/Abs Web Client
Translate-URL: https://hosted.weblate.org/projects/audiobookshelf/abs-web-client/nb_NO/
2024-06-27 23:36:51 +02:00
phewi
f6fc53d7d8 Translated using Weblate (Finnish)
Currently translated at 7.6% (63 of 821 strings)

Translation: Audiobookshelf/Abs Web Client
Translate-URL: https://hosted.weblate.org/projects/audiobookshelf/abs-web-client/fi/
2024-06-27 23:36:51 +02:00
advplyr
599623570b Merge pull request #3107 from taxilian/bug/oldLibraryItemNull
bug: if oldLibraryItem is null things crash
2024-06-27 16:36:29 -05:00
advplyr
67b47785a0 Update:Author endpoints to use faster db call to get number of books 2024-06-27 16:37:43 -05:00
advplyr
56c0124c13 Fix:Changing author name not updating library item metadata files #3060 2024-06-27 16:32:38 -05:00
Richard Bateman
f9e270e4be bug: if oldLibraryItem is null things crash 2024-06-27 14:32:14 -06:00
advplyr
8cadaa57f6 Update share endpoint to return playback session, add get share file endpoint 2024-06-26 17:03:12 -05:00
advplyr
042035051d Merge pull request #3037 from mikiher/bookshelf-refactor-2
Bookshelf and cards refactoring
2024-06-25 15:54:31 -05:00
advplyr
12ce3a6147 Merge pull request #3103 from weblate/weblate-audiobookshelf-abs-web-client
Translations update from Hosted Weblate
2024-06-25 08:31:25 -05:00
mikiher
9bf4bd9bfa Merge branch 'advplyr:master' into bookshelf-refactor-2 2024-06-25 10:03:18 +03:00
mikiher
2819317924 Remove now-unsued slider components 2024-06-25 09:14:33 +03:00
mikiher
e06ab594e1 Revert "feat: Add a Show Subtitles option"
This reverts commit 3ef189ed4a.
2024-06-25 08:57:09 +03:00
advplyr
04a65648a3 Merge pull request #3099 from mattbasta/patch-1
Add user agent string to feed requests
2024-06-24 17:19:27 -05:00
advplyr
2673742d8d Update User-Agent strings 2024-06-24 17:14:20 -05:00
advplyr
090c02079d Fix bookshelf view search page showing tags shelf, fix bookshelf row arrow overlay height 2024-06-24 16:44:29 -05:00
advplyr
514fb5f7da Fix ItemSlider component select 2024-06-24 16:37:02 -05:00
advplyr
f541bc2159 Fix ItemSlider component on search page 2024-06-24 16:24:30 -05:00
Mario
d70810364c Translated using Weblate (German)
Currently translated at 100.0% (821 of 821 strings)

Translation: Audiobookshelf/Abs Web Client
Translate-URL: https://hosted.weblate.org/projects/audiobookshelf/abs-web-client/de/
2024-06-24 17:13:23 +02:00
advplyr
09d7880779 Merge branch 'master' of https://github.com/advplyr/audiobookshelf 2024-06-23 14:14:56 -05:00
SunSpring
c69e6bff10 Translated using Weblate (Chinese (Simplified))
Currently translated at 100.0% (821 of 821 strings)

Translation: Audiobookshelf/Abs Web Client
Translate-URL: https://hosted.weblate.org/projects/audiobookshelf/abs-web-client/zh_Hans/
2024-06-23 19:13:54 +00:00
chris sollami
b49c2e7b82 Translated using Weblate (Italian)
Currently translated at 97.4% (800 of 821 strings)

Translation: Audiobookshelf/Abs Web Client
Translate-URL: https://hosted.weblate.org/projects/audiobookshelf/abs-web-client/it/
2024-06-23 19:13:53 +00:00
gallegonovato
d012b2107d Translated using Weblate (Spanish)
Currently translated at 100.0% (821 of 821 strings)

Translation: Audiobookshelf/Abs Web Client
Translate-URL: https://hosted.weblate.org/projects/audiobookshelf/abs-web-client/es/
2024-06-23 19:13:53 +00:00
Plazec
9294521632 Translated using Weblate (Czech)
Currently translated at 99.2% (813 of 819 strings)

Translation: Audiobookshelf/Abs Web Client
Translate-URL: https://hosted.weblate.org/projects/audiobookshelf/abs-web-client/cs/
2024-06-23 19:13:52 +00:00
Nicholas W
7d05317357 Notification endpoints (#3096)
* Initial notification schema

* Add: notification event and settings schema

* Add: NotificationController

* Update bundled spec

* Fix: `operationId` typos

* Fix: library response to be arroy of objects

* Fix: notification ID is not uuid

* Add: `nullable` notification creation parameters

* Nullable libraryId schema

* Remove: `id` from Notification requestBody

* Fix: `allOf` for `libraryItemSequence`

* Fix: required `id` in wrong body

* Fix: libraryItem typos

* Update: bundled spec
2024-06-23 14:12:10 -05:00
Matt Basta
2843a3b6d7 Add user agent string to feed requests 2024-06-23 12:35:37 -04:00
mikiher
635f22ddfe Reverted default spacing and font-sizing changes, and extended spaing with em-based variants 2024-06-23 19:15:39 +03:00
advplyr
903b685e1a Update jsdocs 2024-06-23 11:01:25 -05:00
advplyr
09bcc1191f Merge pull request #3095 from nichwall/parameter_changes
Change: `requestBody` to `parameter`, allow commas in queries
2024-06-23 07:41:21 -05:00
advplyr
d6eae9b43e Add:Create media item shares with expiration #1768 2024-06-22 16:42:13 -05:00
Nicholas Wallace
f95d9bd0e9 Change: requestBody to parameter, allow commas in queries 2024-06-21 22:25:22 +00:00
advplyr
e52b695f7e Update:Change chapters table End column to a Duration column #3093 2024-06-21 16:58:24 -05:00
advplyr
72c1407aa7 Fix:Automatic library scans using stale copy of library object resulting in reverting saved changes to it #3079 #2894 2024-06-20 17:08:18 -05:00
advplyr
2ec49cbdb1 Update en-us string order 2024-06-19 17:17:40 -05:00
advplyr
331d7a41ab Add:Ability to edit backup location path on backups page #2973
- Added api endpoint PATCH /api/backups/path
- Cleanup backup page UI for mobile screens
2024-06-19 17:14:37 -05:00
advplyr
8498cab842 Merge pull request #3086 from taxilian/bug/itemProgressNull
bug: If !itemProgress unhandled exception syncing user progress
2024-06-19 15:28:46 -05:00
advplyr
c170cb3132 Merge pull request #3089 from weblate/weblate-audiobookshelf-abs-web-client
Translations update from Hosted Weblate
2024-06-19 15:18:22 -05:00
Charlie
0c58c9060e Translated using Weblate (French)
Currently translated at 100.0% (819 of 819 strings)

Translation: Audiobookshelf/Abs Web Client
Translate-URL: https://hosted.weblate.org/projects/audiobookshelf/abs-web-client/fr/
2024-06-19 06:45:05 +02:00
Richard Bateman
e3c3903c71 bug: If !itemProgress unhandled exception syncing user progress 2024-06-18 18:52:37 -06:00
advplyr
7bc70effb0 Update:Add server setting for backupPath and allow overriding with BACKUP_PATH env variable #2973 2024-06-18 17:10:49 -05:00
kuci-JK
991da2870f Translated using Weblate (Czech)
Currently translated at 99.3% (814 of 819 strings)

Translation: Audiobookshelf/Abs Web Client
Translate-URL: https://hosted.weblate.org/projects/audiobookshelf/abs-web-client/cs/
2024-06-18 22:08:58 +00:00
JL
52b632d810 Translated using Weblate (Danish)
Currently translated at 86.0% (705 of 819 strings)

Translation: Audiobookshelf/Abs Web Client
Translate-URL: https://hosted.weblate.org/projects/audiobookshelf/abs-web-client/da/
2024-06-18 22:08:58 +00:00
Plazec
33531ff73b Translated using Weblate (Czech)
Currently translated at 99.2% (813 of 819 strings)

Translation: Audiobookshelf/Abs Web Client
Translate-URL: https://hosted.weblate.org/projects/audiobookshelf/abs-web-client/cs/
2024-06-18 22:08:57 +00:00
Plazec
391a777dde Translated using Weblate (Czech)
Currently translated at 92.1% (755 of 819 strings)

Translation: Audiobookshelf/Abs Web Client
Translate-URL: https://hosted.weblate.org/projects/audiobookshelf/abs-web-client/cs/
2024-06-18 22:08:57 +00:00
pmangro
85e7b63532 Translated using Weblate (Portuguese (Brazil))
Currently translated at 100.0% (819 of 819 strings)

Translation: Audiobookshelf/Abs Web Client
Translate-URL: https://hosted.weblate.org/projects/audiobookshelf/abs-web-client/pt_BR/
2024-06-18 22:08:56 +00:00
pmangro
b02429cf55 Translated using Weblate (Portuguese (Brazil))
Currently translated at 97.5% (799 of 819 strings)

Translation: Audiobookshelf/Abs Web Client
Translate-URL: https://hosted.weblate.org/projects/audiobookshelf/abs-web-client/pt_BR/
2024-06-18 22:08:56 +00:00
Smoukus
9e064e670a Translated using Weblate (Croatian)
Currently translated at 59.8% (490 of 819 strings)

Translation: Audiobookshelf/Abs Web Client
Translate-URL: https://hosted.weblate.org/projects/audiobookshelf/abs-web-client/hr/
2024-06-18 22:08:55 +00:00
Valentin
61b3785038 Translated using Weblate (German)
Currently translated at 100.0% (819 of 819 strings)

Translation: Audiobookshelf/Abs Web Client
Translate-URL: https://hosted.weblate.org/projects/audiobookshelf/abs-web-client/de/
2024-06-18 22:08:55 +00:00
advplyr
a75ad5d659 Add:Finnish language option 2024-06-16 09:42:40 -05:00
advplyr
516a3858c5 Merge pull request #3080 from weblate/weblate-audiobookshelf-abs-web-client
Translations update from Hosted Weblate
2024-06-16 08:52:14 -05:00
Petteri Hjort
364787db72 Translated using Weblate (Finnish)
Currently translated at 5.9% (49 of 819 strings)

Translation: Audiobookshelf/Abs Web Client
Translate-URL: https://hosted.weblate.org/projects/audiobookshelf/abs-web-client/fi/
2024-06-15 23:45:03 +02:00
Vito0912
b2562ede55 Translated using Weblate (German)
Currently translated at 100.0% (819 of 819 strings)

Translation: Audiobookshelf/Abs Web Client
Translate-URL: https://hosted.weblate.org/projects/audiobookshelf/abs-web-client/de/
2024-06-15 23:45:03 +02:00
Charlie
c441d83d39 Translated using Weblate (French)
Currently translated at 100.0% (819 of 819 strings)

Translation: Audiobookshelf/Abs Web Client
Translate-URL: https://hosted.weblate.org/projects/audiobookshelf/abs-web-client/fr/
2024-06-15 23:45:03 +02:00
Nicholas W
08c6cc674b Added translation using Weblate (Finnish) 2024-06-15 23:45:03 +02:00
Nicholas W
9c34e4bd14 Translated using Weblate (Chinese (Traditional))
Currently translated at 93.5% (766 of 819 strings)

Translation: Audiobookshelf/Abs Web Client
Translate-URL: https://hosted.weblate.org/projects/audiobookshelf/abs-web-client/zh_Hant/
2024-06-15 23:45:03 +02:00
Dmitry
9b159fc1e6 Translated using Weblate (Russian)
Currently translated at 100.0% (819 of 819 strings)

Translation: Audiobookshelf/Abs Web Client
Translate-URL: https://hosted.weblate.org/projects/audiobookshelf/abs-web-client/ru/
2024-06-15 23:45:03 +02:00
Nicholas W
bcc2fa409e Translated using Weblate (Portuguese (Brazil))
Currently translated at 96.5% (791 of 819 strings)

Translation: Audiobookshelf/Abs Web Client
Translate-URL: https://hosted.weblate.org/projects/audiobookshelf/abs-web-client/pt_BR/
2024-06-15 23:45:03 +02:00
Nicholas W
360d54847c Translated using Weblate (Italian)
Currently translated at 97.4% (798 of 819 strings)

Translation: Audiobookshelf/Abs Web Client
Translate-URL: https://hosted.weblate.org/projects/audiobookshelf/abs-web-client/it/
2024-06-15 23:45:03 +02:00
Nicholas W
b25314b4bd Translated using Weblate (Spanish)
Currently translated at 100.0% (819 of 819 strings)

Translation: Audiobookshelf/Abs Web Client
Translate-URL: https://hosted.weblate.org/projects/audiobookshelf/abs-web-client/es/
2024-06-15 23:45:03 +02:00
Nicholas W
c87f2a571e Translated using Weblate (Czech)
Currently translated at 88.0% (721 of 819 strings)

Translation: Audiobookshelf/Abs Web Client
Translate-URL: https://hosted.weblate.org/projects/audiobookshelf/abs-web-client/cs/
2024-06-15 23:45:03 +02:00
Dmitry
8be02303f9 Translated using Weblate (Russian)
Currently translated at 97.8% (801 of 819 strings)

Translation: Audiobookshelf/Abs Web Client
Translate-URL: https://hosted.weblate.org/projects/audiobookshelf/abs-web-client/ru/
2024-06-15 23:45:03 +02:00
SunSpring
c6b4694b22 Translated using Weblate (Chinese (Simplified))
Currently translated at 100.0% (819 of 819 strings)

Translation: Audiobookshelf/Abs Web Client
Translate-URL: https://hosted.weblate.org/projects/audiobookshelf/abs-web-client/zh_Hans/
2024-06-15 23:45:03 +02:00
Illia Pyshniak
4762cdb7d8 Translated using Weblate (Ukrainian)
Currently translated at 100.0% (819 of 819 strings)

Translation: Audiobookshelf/Abs Web Client
Translate-URL: https://hosted.weblate.org/projects/audiobookshelf/abs-web-client/uk/
2024-06-15 23:45:03 +02:00
Petras Šukys
fe2a07bf4b Translated using Weblate (Lithuanian)
Currently translated at 86.0% (705 of 819 strings)

Translation: Audiobookshelf/Abs Web Client
Translate-URL: https://hosted.weblate.org/projects/audiobookshelf/abs-web-client/lt/
2024-06-15 23:45:03 +02:00
DiamondtipDR
9f80900717 Translated using Weblate (Spanish)
Currently translated at 100.0% (819 of 819 strings)

Translation: Audiobookshelf/Abs Web Client
Translate-URL: https://hosted.weblate.org/projects/audiobookshelf/abs-web-client/es/
2024-06-15 23:45:03 +02:00
gallegonovato
6b001ad7a1 Translated using Weblate (Spanish)
Currently translated at 100.0% (819 of 819 strings)

Translation: Audiobookshelf/Abs Web Client
Translate-URL: https://hosted.weblate.org/projects/audiobookshelf/abs-web-client/es/
2024-06-15 23:45:02 +02:00
Mario
4241544aaf Translated using Weblate (German)
Currently translated at 100.0% (819 of 819 strings)

Translation: Audiobookshelf/Abs Web Client
Translate-URL: https://hosted.weblate.org/projects/audiobookshelf/abs-web-client/de/
2024-06-15 23:45:02 +02:00
A L
80bcc71c72 Translated using Weblate (Bulgarian)
Currently translated at 96.0% (787 of 819 strings)

Translation: Audiobookshelf/Abs Web Client
Translate-URL: https://hosted.weblate.org/projects/audiobookshelf/abs-web-client/bg/
2024-06-15 23:45:02 +02:00
SunSpring
253095dcd6 Translated using Weblate (Chinese (Simplified))
Currently translated at 100.0% (819 of 819 strings)

Translation: Audiobookshelf/Abs Web Client
Translate-URL: https://hosted.weblate.org/projects/audiobookshelf/abs-web-client/zh_Hans/
2024-06-15 23:45:02 +02:00
burghy86
0e4109a7c2 Translated using Weblate (Italian)
Currently translated at 97.3% (797 of 819 strings)

Translation: Audiobookshelf/Abs Web Client
Translate-URL: https://hosted.weblate.org/projects/audiobookshelf/abs-web-client/it/
2024-06-15 23:45:02 +02:00
Daniel Schosser
629741db92 Translated using Weblate (German)
Currently translated at 98.7% (809 of 819 strings)

Translation: Audiobookshelf/Abs Web Client
Translate-URL: https://hosted.weblate.org/projects/audiobookshelf/abs-web-client/de/
2024-06-15 23:45:02 +02:00
Mario
79236dd67d Translated using Weblate (German)
Currently translated at 98.7% (809 of 819 strings)

Translation: Audiobookshelf/Abs Web Client
Translate-URL: https://hosted.weblate.org/projects/audiobookshelf/abs-web-client/de/
2024-06-15 23:45:02 +02:00
SunSpring
bdfb7b9af3 Translated using Weblate (Chinese (Simplified))
Currently translated at 97.5% (799 of 819 strings)

Translation: Audiobookshelf/Abs Web Client
Translate-URL: https://hosted.weblate.org/projects/audiobookshelf/abs-web-client/zh_Hans/
2024-06-15 23:45:02 +02:00
Vito0912
665244f1b2 Translated using Weblate (German)
Currently translated at 97.9% (802 of 819 strings)

Translation: Audiobookshelf/Abs Web Client
Translate-URL: https://hosted.weblate.org/projects/audiobookshelf/abs-web-client/de/
2024-06-15 23:45:02 +02:00
gallegonovato
b74f13bbd7 Translated using Weblate (Spanish)
Currently translated at 97.1% (796 of 819 strings)

Translation: Audiobookshelf/Abs Web Client
Translate-URL: https://hosted.weblate.org/projects/audiobookshelf/abs-web-client/es/
2024-06-15 23:45:02 +02:00
gallegonovato
d1ee3af2d9 Translated using Weblate (Spanish)
Currently translated at 95.4% (782 of 819 strings)

Translation: Audiobookshelf/Abs Web Client
Translate-URL: https://hosted.weblate.org/projects/audiobookshelf/abs-web-client/es/
2024-06-15 23:45:02 +02:00
advplyr
38fa4d4169 Merge pull request #3078 from nichwall/time_parsing_fix
Time parsing fix
2024-06-15 16:44:57 -05:00
advplyr
56d3ed5a8e Merge pull request #3054 from nichwall/translation_readme_update
Translation readme update
2024-06-15 16:43:16 -05:00
Nicholas Wallace
cadef9b023 Misnamed variable and cumulative length 2024-06-15 01:20:22 +00:00
Nicholas Wallace
34b340f179 Fix: overdrive mediamarkers parse hours 2024-06-15 01:17:07 +00:00
advplyr
b89bbd2187 Update:Watcher pending delay to 10s. Increase file mtime check interval to 3s and timeout to 600s. Remove file from pending scan if it times out. 2024-06-14 16:50:09 -05:00
advplyr
d6438590d7 Update library series endpoint openapi spec to use query parameters instead of request body 2024-06-13 17:13:55 -05:00
Nicholas W
baf5f7fbc3 Initial library endpoints (#3012)
* Fix: extra type in `Author.yaml`

* Fix: formatting

* Initial library schema

* Additional debugging

* Fix: spec relative paths

* Add: ebook file spec

* Fix: response type should be string

* Linting updates

* Add: missing librarySettings

* Temporary fix: Library cron can be null or false

* Author controller updates

* Add: `/api/libraries/{id}` endpoint

* Update library responses

* Add: descriptions

* Fix: queries should be in body

* Fix: `body` should be `requestBody`

* Move: `libraryController` paths, clean up `requestBody`

* Clean up libraryController parameters

* Move: author endpoints to controller

* Add `get` for author images

* Simplify author schema with items

* Remove: unused response type

* Update: formatting

* Update json

* Update requestBody on LibraryController

* LibrarySettings update

* Replace: generic parameter with path specific parameter

* Fix: requestBody descriptions

* Fix: match post operation

* Temporary: nullable Author schemas

* LibraryController items endpoint

* Add: delete library items with issues

* Massive cleanup and violation fixing

* Update bundled spec

* Add: remove library items with issues

* Add: library items endpoint

* Fix: errors

* Fix: base schemas

* Add: series schemas

* Add: library series endpoint

* Fix: oneOf and array issues

* Add: author search region for matching

* Add: series endpoints

* Fix: series issues

* Add library series endpoint and update deprecation

* Fix: series endpoint deprecation

* Fix: `name` in `sortDesc` schema

* Add: workflow for linting spec

* Update OpenAPI readme
2024-06-13 17:09:02 -05:00
advplyr
e6a2555f05 Merge pull request #3071 from JBlond/master
Follow-up translation
2024-06-12 16:56:52 -05:00
JBlond
36425e1fab Follow-up translation for
f682a7a283
  a6c5732693
  e9453d4f6c
  11d8669426
  5da4861716
  800cdc129d
2024-06-12 16:05:39 +02:00
advplyr
18efd95759 Merge pull request #3057 from Machou/master
Update fr.json
2024-06-11 17:07:27 -05:00
advplyr
f682a7a283 Merge pull request #3063 from nichwall/localization_workflow_update
Update i18n workflow to `1.3.0`
2024-06-11 17:02:31 -05:00
Nicholas Wallace
cb968ef4ca Update i18n workflow to 1.3.0 2024-06-10 19:22:03 -07:00
advplyr
a6c5732693 Merge pull request #3053 from weblate/weblate-audiobookshelf-test-abs-web-client
Translations update from Hosted Weblate
2024-06-10 16:07:52 -05:00
Machou
7bbdc945d5 Update fr.json 2024-06-09 23:52:39 +02:00
Nicholas Wallace
b37431dfaa Update translation guide 2024-06-09 20:10:15 +00:00
Nicholas Wallace
a333ebe5b0 Readme formatting update 2024-06-09 20:09:47 +00:00
Nicholas W
4affcd0d89 Translated using Weblate (Chinese (Traditional))
Currently translated at 93.4% (765 of 819 strings)

Translation: Audiobookshelf/ABS Web Client
Translate-URL: https://hosted.weblate.org/projects/audiobookshelf-test/abs-web-client/zh_Hant/
2024-06-09 21:37:57 +02:00
Nicholas W
9d5e6351a4 Translated using Weblate (Vietnamese)
Currently translated at 87.7% (719 of 819 strings)

Translation: Audiobookshelf/ABS Web Client
Translate-URL: https://hosted.weblate.org/projects/audiobookshelf-test/abs-web-client/vi/
2024-06-09 21:37:57 +02:00
Nicholas W
91c25918f1 Translated using Weblate (Ukrainian)
Currently translated at 97.4% (798 of 819 strings)

Translation: Audiobookshelf/ABS Web Client
Translate-URL: https://hosted.weblate.org/projects/audiobookshelf-test/abs-web-client/uk/
2024-06-09 21:37:57 +02:00
Nicholas W
bb88b5d861 Translated using Weblate (Swedish)
Currently translated at 88.8% (728 of 819 strings)

Translation: Audiobookshelf/ABS Web Client
Translate-URL: https://hosted.weblate.org/projects/audiobookshelf-test/abs-web-client/sv/
2024-06-09 21:37:57 +02:00
Nicholas W
11818a3576 Translated using Weblate (Russian)
Currently translated at 90.7% (743 of 819 strings)

Translation: Audiobookshelf/ABS Web Client
Translate-URL: https://hosted.weblate.org/projects/audiobookshelf-test/abs-web-client/ru/
2024-06-09 21:37:56 +02:00
Nicholas W
f3de134980 Translated using Weblate (Portuguese (Brazil))
Currently translated at 96.3% (789 of 819 strings)

Translation: Audiobookshelf/ABS Web Client
Translate-URL: https://hosted.weblate.org/projects/audiobookshelf-test/abs-web-client/pt_BR/
2024-06-09 21:37:56 +02:00
Nicholas W
9fa5db6976 Translated using Weblate (Polish)
Currently translated at 72.4% (593 of 819 strings)

Translation: Audiobookshelf/ABS Web Client
Translate-URL: https://hosted.weblate.org/projects/audiobookshelf-test/abs-web-client/pl/
2024-06-09 21:37:56 +02:00
Nicholas W
5e9043e5fa Translated using Weblate (Norwegian Bokmål)
Currently translated at 85.4% (700 of 819 strings)

Translation: Audiobookshelf/ABS Web Client
Translate-URL: https://hosted.weblate.org/projects/audiobookshelf-test/abs-web-client/nb_NO/
2024-06-09 21:37:55 +02:00
Nicholas W
84e275174c Translated using Weblate (Dutch)
Currently translated at 84.3% (691 of 819 strings)

Translation: Audiobookshelf/ABS Web Client
Translate-URL: https://hosted.weblate.org/projects/audiobookshelf-test/abs-web-client/nl/
2024-06-09 21:37:55 +02:00
Nicholas W
ae90dd358e Translated using Weblate (Lithuanian)
Currently translated at 85.5% (701 of 819 strings)

Translation: Audiobookshelf/ABS Web Client
Translate-URL: https://hosted.weblate.org/projects/audiobookshelf-test/abs-web-client/lt/
2024-06-09 21:37:55 +02:00
Nicholas W
0cfd153694 Translated using Weblate (Italian)
Currently translated at 89.6% (734 of 819 strings)

Translation: Audiobookshelf/ABS Web Client
Translate-URL: https://hosted.weblate.org/projects/audiobookshelf-test/abs-web-client/it/
2024-06-09 21:37:54 +02:00
Nicholas W
bf99d3d506 Translated using Weblate (Hungarian)
Currently translated at 91.8% (752 of 819 strings)

Translation: Audiobookshelf/ABS Web Client
Translate-URL: https://hosted.weblate.org/projects/audiobookshelf-test/abs-web-client/hu/
2024-06-09 21:37:54 +02:00
Nicholas W
9e055831fe Translated using Weblate (Croatian)
Currently translated at 59.2% (485 of 819 strings)

Translation: Audiobookshelf/ABS Web Client
Translate-URL: https://hosted.weblate.org/projects/audiobookshelf-test/abs-web-client/hr/
2024-06-09 21:37:52 +02:00
Nicholas W
a349784da9 Translated using Weblate (Hindi)
Currently translated at 22.5% (185 of 819 strings)

Translation: Audiobookshelf/ABS Web Client
Translate-URL: https://hosted.weblate.org/projects/audiobookshelf-test/abs-web-client/hi/
2024-06-09 21:37:51 +02:00
Nicholas W
40f9e0f669 Translated using Weblate (Hebrew)
Currently translated at 94.3% (773 of 819 strings)

Translation: Audiobookshelf/ABS Web Client
Translate-URL: https://hosted.weblate.org/projects/audiobookshelf-test/abs-web-client/he/
2024-06-09 21:37:47 +02:00
Nicholas W
c253a95127 Translated using Weblate (Gujarati)
Currently translated at 28.6% (235 of 819 strings)

Translation: Audiobookshelf/ABS Web Client
Translate-URL: https://hosted.weblate.org/projects/audiobookshelf-test/abs-web-client/gu/
2024-06-09 21:37:47 +02:00
Nicholas W
d70d49b9da Translated using Weblate (French)
Currently translated at 94.0% (770 of 819 strings)

Translation: Audiobookshelf/ABS Web Client
Translate-URL: https://hosted.weblate.org/projects/audiobookshelf-test/abs-web-client/fr/
2024-06-09 21:37:38 +02:00
Nicholas W
16c5e4a398 Translated using Weblate (Estonian)
Currently translated at 93.4% (765 of 819 strings)

Translation: Audiobookshelf/ABS Web Client
Translate-URL: https://hosted.weblate.org/projects/audiobookshelf-test/abs-web-client/et/
2024-06-09 21:37:37 +02:00
Nicholas W
d53d16c551 Translated using Weblate (Danish)
Currently translated at 85.9% (704 of 819 strings)

Translation: Audiobookshelf/ABS Web Client
Translate-URL: https://hosted.weblate.org/projects/audiobookshelf-test/abs-web-client/da/
2024-06-09 21:37:37 +02:00
Nicholas W
312be0f639 Translated using Weblate (Czech)
Currently translated at 87.9% (720 of 819 strings)

Translation: Audiobookshelf/ABS Web Client
Translate-URL: https://hosted.weblate.org/projects/audiobookshelf-test/abs-web-client/cs/
2024-06-09 21:37:37 +02:00
Nicholas W
0246dcc10d Translated using Weblate (Bengali)
Currently translated at 95.2% (780 of 819 strings)

Translation: Audiobookshelf/ABS Web Client
Translate-URL: https://hosted.weblate.org/projects/audiobookshelf-test/abs-web-client/bn/
2024-06-09 21:37:36 +02:00
Nicholas W
5aa1b14695 Translated using Weblate (Bulgarian)
Currently translated at 92.4% (757 of 819 strings)

Translation: Audiobookshelf/ABS Web Client
Translate-URL: https://hosted.weblate.org/projects/audiobookshelf-test/abs-web-client/bg/
2024-06-09 21:37:36 +02:00
Nicholas W
2ee24c1ded Translated using Weblate (English)
Currently translated at 24.6% (202 of 819 strings)

Translation: Audiobookshelf/ABS Web Client
Translate-URL: https://hosted.weblate.org/projects/audiobookshelf-test/abs-web-client/en/
2024-06-09 21:37:36 +02:00
Nicholas W
700afeacf0 Translated using Weblate (Chinese (Simplified))
Currently translated at 94.6% (775 of 819 strings)

Translation: Audiobookshelf/ABS Web Client
Translate-URL: https://hosted.weblate.org/projects/audiobookshelf-test/abs-web-client/zh_Hans/
2024-06-09 21:29:44 +02:00
Nicholas W
e9453d4f6c Translated using Weblate (German)
Currently translated at 96.7% (792 of 819 strings)

Translation: Audiobookshelf/ABS Web Client
Translate-URL: https://hosted.weblate.org/projects/audiobookshelf-test/abs-web-client/de/
2024-06-09 21:23:08 +02:00
gallegonovato
661db2af26 Translated using Weblate (Spanish)
Currently translated at 100.0% (819 of 819 strings)

Translation: Audiobookshelf/ABS Web Client
Translate-URL: https://hosted.weblate.org/projects/audiobookshelf-test/abs-web-client/es/
2024-06-09 19:17:33 +00:00
Nicholas W
efd205716b Translated using Weblate (Italian)
Currently translated at 100.0% (819 of 819 strings)

Translation: Audiobookshelf/ABS Web Client
Translate-URL: https://hosted.weblate.org/projects/audiobookshelf-test/abs-web-client/it/
2024-06-09 19:17:32 +00:00
Nicholas W
84144bb32a Translated using Weblate (Bengali)
Currently translated at 95.3% (781 of 819 strings)

Translation: Audiobookshelf/ABS Web Client
Translate-URL: https://hosted.weblate.org/projects/audiobookshelf-test/abs-web-client/bn/
2024-06-09 19:17:32 +00:00
Anonymous
74a094c6df Translated using Weblate (Chinese (Traditional))
Currently translated at 100.0% (819 of 819 strings)

Translation: Audiobookshelf-test/ABS Web Client
Translate-URL: https://hosted.weblate.org/projects/audiobookshelf-test/abs-web-client/zh_Hant/
2024-06-09 19:17:31 +00:00
Anonymous
aa89aca632 Translated using Weblate (Chinese (Simplified))
Currently translated at 100.0% (819 of 819 strings)

Translation: Audiobookshelf-test/ABS Web Client
Translate-URL: https://hosted.weblate.org/projects/audiobookshelf-test/abs-web-client/zh_Hans/
2024-06-09 19:17:31 +00:00
Anonymous
8ac9a0d7c0 Translated using Weblate (Vietnamese)
Currently translated at 100.0% (819 of 819 strings)

Translation: Audiobookshelf-test/ABS Web Client
Translate-URL: https://hosted.weblate.org/projects/audiobookshelf-test/abs-web-client/vi/
2024-06-09 19:17:30 +00:00
Anonymous
0119d7fcff Translated using Weblate (Ukrainian)
Currently translated at 100.0% (819 of 819 strings)

Translation: Audiobookshelf-test/ABS Web Client
Translate-URL: https://hosted.weblate.org/projects/audiobookshelf-test/abs-web-client/uk/
2024-06-09 19:17:30 +00:00
Anonymous
be513fde4f Translated using Weblate (Swedish)
Currently translated at 100.0% (819 of 819 strings)

Translation: Audiobookshelf-test/ABS Web Client
Translate-URL: https://hosted.weblate.org/projects/audiobookshelf-test/abs-web-client/sv/
2024-06-09 19:17:29 +00:00
Anonymous
715199d88b Translated using Weblate (Russian)
Currently translated at 100.0% (819 of 819 strings)

Translation: Audiobookshelf-test/ABS Web Client
Translate-URL: https://hosted.weblate.org/projects/audiobookshelf-test/abs-web-client/ru/
2024-06-09 19:17:29 +00:00
Anonymous
34942a3857 Translated using Weblate (Portuguese (Brazil))
Currently translated at 100.0% (819 of 819 strings)

Translation: Audiobookshelf-test/ABS Web Client
Translate-URL: https://hosted.weblate.org/projects/audiobookshelf-test/abs-web-client/pt_BR/
2024-06-09 19:17:28 +00:00
Anonymous
d67e916c66 Translated using Weblate (Polish)
Currently translated at 100.0% (819 of 819 strings)

Translation: Audiobookshelf-test/ABS Web Client
Translate-URL: https://hosted.weblate.org/projects/audiobookshelf-test/abs-web-client/pl/
2024-06-09 19:17:28 +00:00
Anonymous
e3e2d4ff99 Translated using Weblate (Norwegian Bokmål)
Currently translated at 100.0% (819 of 819 strings)

Translation: Audiobookshelf-test/ABS Web Client
Translate-URL: https://hosted.weblate.org/projects/audiobookshelf-test/abs-web-client/nb_NO/
2024-06-09 19:17:27 +00:00
Anonymous
699615f2f3 Translated using Weblate (Dutch)
Currently translated at 100.0% (819 of 819 strings)

Translation: Audiobookshelf-test/ABS Web Client
Translate-URL: https://hosted.weblate.org/projects/audiobookshelf-test/abs-web-client/nl/
2024-06-09 19:17:27 +00:00
Anonymous
6d267cac0d Translated using Weblate (Lithuanian)
Currently translated at 100.0% (819 of 819 strings)

Translation: Audiobookshelf-test/ABS Web Client
Translate-URL: https://hosted.weblate.org/projects/audiobookshelf-test/abs-web-client/lt/
2024-06-09 19:17:26 +00:00
Anonymous
7d719d94ba Translated using Weblate (Italian)
Currently translated at 100.0% (819 of 819 strings)

Translation: Audiobookshelf-test/ABS Web Client
Translate-URL: https://hosted.weblate.org/projects/audiobookshelf-test/abs-web-client/it/
2024-06-09 19:17:26 +00:00
Anonymous
4bf410fd3e Translated using Weblate (Hungarian)
Currently translated at 100.0% (819 of 819 strings)

Translation: Audiobookshelf-test/ABS Web Client
Translate-URL: https://hosted.weblate.org/projects/audiobookshelf-test/abs-web-client/hu/
2024-06-09 19:17:25 +00:00
Anonymous
16cd05e187 Translated using Weblate (Croatian)
Currently translated at 100.0% (819 of 819 strings)

Translation: Audiobookshelf-test/ABS Web Client
Translate-URL: https://hosted.weblate.org/projects/audiobookshelf-test/abs-web-client/hr/
2024-06-09 19:17:25 +00:00
Anonymous
c7dcaa0316 Translated using Weblate (Hindi)
Currently translated at 100.0% (819 of 819 strings)

Translation: Audiobookshelf-test/ABS Web Client
Translate-URL: https://hosted.weblate.org/projects/audiobookshelf-test/abs-web-client/hi/
2024-06-09 19:17:24 +00:00
Anonymous
09cf502e70 Translated using Weblate (Hebrew)
Currently translated at 100.0% (819 of 819 strings)

Translation: Audiobookshelf-test/ABS Web Client
Translate-URL: https://hosted.weblate.org/projects/audiobookshelf-test/abs-web-client/he/
2024-06-09 19:17:24 +00:00
Anonymous
78ac7c2a28 Translated using Weblate (Gujarati)
Currently translated at 100.0% (819 of 819 strings)

Translation: Audiobookshelf-test/ABS Web Client
Translate-URL: https://hosted.weblate.org/projects/audiobookshelf-test/abs-web-client/gu/
2024-06-09 19:17:23 +00:00
Anonymous
57acda5592 Translated using Weblate (French)
Currently translated at 100.0% (819 of 819 strings)

Translation: Audiobookshelf-test/ABS Web Client
Translate-URL: https://hosted.weblate.org/projects/audiobookshelf-test/abs-web-client/fr/
2024-06-09 19:17:23 +00:00
Anonymous
d52a168582 Translated using Weblate (Estonian)
Currently translated at 100.0% (819 of 819 strings)

Translation: Audiobookshelf-test/ABS Web Client
Translate-URL: https://hosted.weblate.org/projects/audiobookshelf-test/abs-web-client/et/
2024-06-09 19:17:22 +00:00
Anonymous
97a9782f31 Translated using Weblate (Spanish)
Currently translated at 100.0% (819 of 819 strings)

Translation: Audiobookshelf-test/ABS Web Client
Translate-URL: https://hosted.weblate.org/projects/audiobookshelf-test/abs-web-client/es/
2024-06-09 19:17:22 +00:00
Anonymous
11d8669426 Translated using Weblate (German)
Currently translated at 100.0% (819 of 819 strings)

Translation: Audiobookshelf-test/ABS Web Client
Translate-URL: https://hosted.weblate.org/projects/audiobookshelf-test/abs-web-client/de/
2024-06-09 19:17:21 +00:00
Anonymous
2bceb6654a Translated using Weblate (Danish)
Currently translated at 100.0% (819 of 819 strings)

Translation: Audiobookshelf-test/ABS Web Client
Translate-URL: https://hosted.weblate.org/projects/audiobookshelf-test/abs-web-client/da/
2024-06-09 19:17:21 +00:00
Anonymous
6feea6a1b0 Translated using Weblate (Czech)
Currently translated at 100.0% (819 of 819 strings)

Translation: Audiobookshelf-test/ABS Web Client
Translate-URL: https://hosted.weblate.org/projects/audiobookshelf-test/abs-web-client/cs/
2024-06-09 19:17:20 +00:00
Anonymous
139919ab20 Translated using Weblate (Bengali)
Currently translated at 100.0% (819 of 819 strings)

Translation: Audiobookshelf-test/ABS Web Client
Translate-URL: https://hosted.weblate.org/projects/audiobookshelf-test/abs-web-client/bn/
2024-06-09 19:17:20 +00:00
Anonymous
234234cc5c Translated using Weblate (Bulgarian)
Currently translated at 100.0% (819 of 819 strings)

Translation: Audiobookshelf-test/ABS Web Client
Translate-URL: https://hosted.weblate.org/projects/audiobookshelf-test/abs-web-client/bg/
2024-06-09 19:17:19 +00:00
advplyr
fcd74ae17b Merge pull request #2188 from jfrazx/fix/match-authors-429
fix: HTTP/429 when requesting authors information, resolves #1570
2024-06-09 13:56:55 -05:00
advplyr
c2897f819d Update:findEpisode API endpoint validate title search param is a string 2024-06-09 13:55:53 -05:00
advplyr
a018374d26 Update:Validate ASIN for author, chapter and match requests 2024-06-09 13:43:03 -05:00
advplyr
ee501f70ed Auto-formatting 2024-06-09 12:51:28 -05:00
jfrazx
e9e9a8ba75 chore: merge and resolve 2024-06-09 09:18:42 -07:00
advplyr
5da4861716 Merge pull request #3040 from BimBimSalaBim/master
E-Reader Font Boldness Slider #3020
2024-06-07 17:05:29 -05:00
advplyr
9c7569fa7a Map localStorage ereaderSettings onto defaults 2024-06-07 16:43:12 -05:00
advplyr
c8892c3725 Fix:Truncate author in player #3038 2024-06-06 16:56:57 -05:00
advplyr
ef05e37a04 Fix:Casting for podcast episodes #3044 2024-06-05 17:02:03 -05:00
advplyr
065aae9a7e Merge pull request #3043 from dbrain/fix-feedurl-copy-paste
Fix ssrfFilter url
2024-06-05 08:26:12 -05:00
Daniel Brain
06202811b4 Fix ssrfFilter url 2024-06-05 20:32:52 +10:00
mikiher
3ef189ed4a feat: Add a Show Subtitles option 2024-06-04 20:07:36 +03:00
mikiher
5f8066e601 Add default line heights converted to em units to tailwind config 2024-06-04 18:11:56 +03:00
Faizan Zafar
ace490712e Merge branch 'master' of https://github.com/BimBimSalaBim/audiobookshelf 2024-06-03 18:47:47 -07:00
Faizan Zafar
265cd75691 Update font settings in EpubReader and Reader components to include a "Font Boldness" slider which applies a stroke to the text #3020 2024-06-03 18:47:29 -07:00
Faizan Zafar
f43969e429 Update font settings in EpubReader and Reader components to include a "Font Boldness" slider which applies a stroke to the text 2024-06-03 18:44:41 -07:00
Faizan Zafar
9adfdda7da Update font settings in EpubReader and Reader components to include a "Font Boldness" slider which applies a stroke to the text 2024-06-03 18:37:25 -07:00
Faizan Zafar
0715de8147 Update font settings in EpubReader and Reader components to include a "Font Boldness" slider which applies a stroke to the text 2024-06-03 18:32:10 -07:00
advplyr
9c33446449 Update:Support for ENV variables to disable SSRF request filter (DISABLE_SSRF_REQUEST_FILTER=1) #2549 2024-06-03 17:21:18 -05:00
mikiher
651601adf6 Add podcast to supported shelf types 2024-06-03 21:57:40 +03:00
mikiher
2186603039 Major bookshelf refactor 2024-06-03 09:04:03 +03:00
advplyr
2b5c7fb519 Merge pull request #3035 from Machou/master
fr update
2024-06-01 15:10:10 -05:00
Machou
82dcd2d6fb Update fr.json 2024-06-01 21:11:08 +02:00
Machou
3f2925029c Update fr.json 2024-06-01 19:17:29 +02:00
advplyr
4da4cf2885 Fix:Fluent ffmpeg not detecting formats in ffmpegv7 #3029 2024-06-01 11:19:43 -05:00
advplyr
ae412f2a57 Fix:PDF reader flickering & disable request progress indicator for ebook progress update events #2279 2024-05-31 16:32:38 -05:00
advplyr
95506bc638 Update:Add more translation strings, remove unused string #3027 2024-05-30 17:03:33 -05:00
advplyr
4b7b10a901 Add translation string for no results for query 2024-05-30 16:28:46 -05:00
advplyr
800cdc129d Merge pull request #2646 from Teekeks/payer-translation
feat(i18n): Added missing translation string in player UI
2024-05-30 16:24:25 -05:00
advplyr
fb86b4fc84 Fix chapter marker string, map translations 2024-05-30 16:23:27 -05:00
advplyr
941f3248d8 Add:SMTP email setting to disable certificate verification #3030 2024-05-29 16:59:43 -05:00
advplyr
6edbab863a Update:nodemailer version bump to 6.9.13 2024-05-29 16:23:47 -05:00
advplyr
a9a317a378 Merge pull request #3028 from Machou/master
Update fr.json
2024-05-28 17:24:03 -05:00
advplyr
3fd290c518 Remove unused functions, jsdoc updates, auto-formatting 2024-05-28 17:24:02 -05:00
Machou
b0924e4ce8 Update fr.json 2024-05-28 03:55:20 +02:00
Machou
24adc8f66f Update fr.json 2024-05-28 03:27:01 +02:00
advplyr
964ef910b6 Version bump v2.10.1 2024-05-27 16:09:32 -05:00
advplyr
ba6a88a5bf Fix:Edit author modal resetting form inputs on image change #2965 2024-05-27 16:04:36 -05:00
advplyr
1576164218 Update:Get all user playlists for library API endpoint performance improvement #2852 2024-05-27 15:37:02 -05:00
advplyr
94400f7794 Merge pull request #3023 from Dalabad/patch-1
Update de.json
2024-05-27 13:39:46 -05:00
advplyr
41e1b02f3a Merge branch 'master' of https://github.com/advplyr/audiobookshelf 2024-05-27 13:22:29 -05:00
advplyr
1337c60cde Fix:Debian pkg crash due to using toSorted that is only available in Node20+ #3024 2024-05-27 13:22:20 -05:00
Daniel Schosser
e9b4e07bd8 Update de.json
Revert Apprise string change
2024-05-27 19:14:10 +02:00
advplyr
607fdffc18 Merge pull request #3022 from JBlond/master
Update de strings.
2024-05-27 11:47:24 -05:00
Daniel Schosser
216139119b Update de.json 2024-05-27 15:02:00 +02:00
JBlond
19cbd1f8de Update de strings.
Follow-up for: ce7f891b9b and 6fad4521d4
2024-05-27 11:35:54 +02:00
advplyr
bf893a56c9 Version bump v2.10.0 for client 2024-05-26 17:20:48 -05:00
advplyr
3a2f680a51 Version bump v2.10.0 2024-05-26 17:06:22 -05:00
advplyr
ce7f891b9b Update:Disable epubs from running scripts by default, add library setting to enable it GHSA-7j99-76cj-q9pg 2024-05-26 16:01:08 -05:00
advplyr
8ec9da143f Merge pull request #3014 from BrianCArnold/UpdateMatchImportTagsAndNarrators
Change Tags and Narrators to work the same as Genres on the Match page.
2024-05-26 14:39:03 -05:00
advplyr
7f28fbb330 Update:Prevent MultiSelect input from adding items that are whitespace & trim whitespace before adding items 2024-05-26 14:37:07 -05:00
advplyr
3111d1860a Merge pull request #3017 from nichwall/playlist_user_permissions
Users can edit playlist in UI
2024-05-26 14:15:25 -05:00
Nicholas Wallace
bd3dce26d9 Playlist row can always be deleted 2024-05-26 17:22:47 +00:00
Nicholas Wallace
db9ee301e3 Playlist always shows edit/delete key 2024-05-26 17:22:21 +00:00
Brian C. Arnold
7d8fb3bb10 Change Tags and Narrators to work the same as Genres on the Match Import page. 2024-05-26 08:08:07 -04:00
advplyr
6fa49e0aab Fix:Add timeout to provider matching default to 30s #3000 2024-05-25 16:32:02 -05:00
advplyr
30d3e41542 Merge pull request #3009 from nichwall/timePicker_cleanup
Time picker cleanup
2024-05-25 15:03:18 -05:00
advplyr
c58d613949 Update client/components/ui/TimePicker.vue 2024-05-25 14:58:46 -05:00
advplyr
38ba7fbec2 Merge pull request #3010 from nichwall/ereader_settings_update
Ereader settings update
2024-05-25 14:45:43 -05:00
advplyr
6fad4521d4 Map translations 2024-05-25 14:44:46 -05:00
advplyr
2f72300636 Update email page to only load users when needed 2024-05-25 14:44:34 -05:00
Nicholas Wallace
b9cb54db71 Onscreen keyboard to appear with TimePicker 2024-05-25 19:27:23 +00:00
Nicholas Wallace
aaaa314761 Add: information to whitelist email 2024-05-25 18:19:47 +00:00
Nicholas Wallace
4e40dbc3a5 Add: user column to ereaders 2024-05-25 18:12:18 +00:00
Nicholas Wallace
ba6a4f1224 Add: TimePicker focusable by tab 2024-05-24 23:49:07 +00:00
Nicholas Wallace
524ed9b677 Tab removes focus from TimePicker 2024-05-24 23:47:38 +00:00
advplyr
5bbcb9cac3 Fix:Embedded chapters sort order #3007 2024-05-24 16:49:39 -05:00
advplyr
ff169f3fd0 Merge pull request #3002 from diamondtipdr/patch-1
Update es.json
2024-05-24 11:22:43 -05:00
DiamondtipDR
cf7b08c993 Update es.json
Updating spanish translation
2024-05-23 23:44:08 -04:00
advplyr
d99a77837b Merge pull request #2920 from rasmuslos/master
Add item sessions endpoint
2024-05-23 16:39:40 -05:00
advplyr
23dcf684d9 Item listening sessions endpoint returns 404 on not found media item 2024-05-23 16:35:36 -05:00
advplyr
9c2ed279df Fix mediaId reference, add JS docs, autoformatting 2024-05-23 16:32:34 -05:00
advplyr
700d7fe68e Fix:Ebook item context menu position on mouseover #2980 2024-05-22 16:51:11 -05:00
advplyr
69833db819 Add:Env variable setting to allow CORS 2024-05-19 14:40:46 -05:00
advplyr
ab2026ecea Merge pull request #2988 from Machou/patch-1
Update fr.json
2024-05-18 17:49:04 -05:00
Machou
811fd9018a Update fr.json 2024-05-18 22:15:46 +02:00
advplyr
6d89721371 Fix:Podcast download new episode check to compare both GUID and enclosure URL for existing episodes #2986 2024-05-18 09:33:48 -05:00
advplyr
ab3a137db9 Merge pull request #2982 from cor-bee/patch-1
Update uk.json
2024-05-17 16:50:32 -05:00
advplyr
a11cf7a90e Fix:Book library author name sort order with multi-author books #2859 2024-05-16 14:56:19 -05:00
advplyr
c995816076 Merge pull request #2981 from pmangro/PT-BR]-Updated-strings
[Pt-BR] updated strings
2024-05-16 14:53:33 -05:00
pmangro
94e7fc6434 [PT-BR] String fix 2024-05-16 16:43:36 -03:00
pmangro
3916bfe833 [PT-BR] Updated strings 2024-05-16 16:28:20 -03:00
Illia Pyshniak
3080ada35f Update uk.json 2024-05-16 18:31:48 +03:00
advplyr
4cddc597c1 Fix:Book library collapse series with no-series filter #2976 2024-05-14 17:24:39 -05:00
advplyr
ec07bfa940 Merge pull request #2974 from JBlond/master
Update de.json
2024-05-14 08:09:54 -05:00
JBlond
d20d4bf8c1 Update de.json 2024-05-14 14:21:25 +02:00
Rasmus Krämer
09e26a9e56 Use new database models, fix function name and use optional path parameter 2024-05-14 10:51:50 +02:00
Rasmus Krämer
ef74919f12 Merge branch 'advplyr:master' into master 2024-05-14 10:40:21 +02:00
advplyr
6462a50713 Add more translation strings 2024-05-13 17:25:01 -05:00
advplyr
8c6c43657c Add translation strings for toasts, update data load toasts to use generic failed to load data message 2024-05-13 16:58:41 -05:00
advplyr
b8ed56e91e Update:Using translations for scan buttons shown on empty library pages & add loading indicator 2024-05-13 16:31:30 -05:00
advplyr
dc0eaa32c9 Merge pull request #2954 from mikiher/series-progress-fixes
Fix series and collapsed series progress to be consistent and show average of book series progress
2024-05-12 13:37:10 -05:00
advplyr
60fc4e20e6 Cleanup inconsistencies with ExplicitIndicator component by removing prop 2024-05-12 13:35:03 -05:00
advplyr
6f43b32214 Merge pull request #2966 from lembata/master
Bulgarian Translation
2024-05-11 18:11:13 -05:00
advplyr
5e8ae79d71 Map translations 2024-05-11 18:09:51 -05:00
noiro
34718aa95d Update pl.json 2024-05-11 21:36:40 +02:00
Alexander Lemberg
d731ad1bd7 Removed duplicated key and trailing comma 2024-05-11 18:57:54 +03:00
Alexander Lemberg
e7fa698645 Add bg to language list 2024-05-11 16:44:42 +03:00
Alexander Lemberg
851d298916 Update bg.json 2024-05-11 16:19:18 +03:00
Alexander Lemberg
1a27e2bef7 Added Bulgarian Translation 2024-05-11 16:06:19 +03:00
advplyr
d64860001b Update devcontainer dev.js file to skip binaries check 2024-05-10 17:58:01 -05:00
advplyr
b82ac3d536 Update:Uploader shows item title on success/failure message #2958 2024-05-10 17:32:57 -05:00
advplyr
91be9eb0fc Merge pull request #2963 from JBlond/master
Update de strings
2024-05-10 17:00:10 -05:00
JBlond
d61bb0bea0 Update de strings 2024-05-10 17:11:49 +02:00
mikiher
911d72971e Removed incorrect stylesheet reference 2024-05-10 12:44:58 +03:00
mikiher
b244cc8d41 Add LazyBookCard tests 2024-05-10 12:43:33 +03:00
mikiher
8cc3bfa95e Add test identifiers to LazyBookCard 2024-05-10 12:39:53 +03:00
advplyr
ba3d59c645 Merge pull request #2955 from nichwall/feature_request
Add: new feature request form
2024-05-09 20:05:00 -05:00
Nicholas Wallace
e416958b01 Add: new feature request form 2024-05-09 06:10:52 +00:00
mikiher
05c1ced65c Update LazyBookCard progress calculation to handle finished items 2024-05-09 07:42:57 +03:00
mikiher
057bc1a0c0 Fix series progress to show sum of series book progresses 2024-05-09 07:31:00 +03:00
advplyr
32fc224600 Merge pull request #2933 from nichwall/logs_doc_update
Add: logs documentation
2024-05-08 16:32:55 -05:00
advplyr
fcecd415c8 Map translation strings 2024-05-08 16:33:58 -05:00
mikiher
e384527b67 Simplify progress bar and show correct collapsed series progress 2024-05-08 08:44:55 +03:00
advplyr
672672dd2a JSDoc formatting updates 2024-05-07 17:39:10 -05:00
advplyr
fd22a6f51d Merge pull request #2896 from CoffeeKnyte/master
Split the author call in the library stats page to 2 lighter functions
2024-05-07 17:36:57 -05:00
mikiher
c674042319 Add libraryItemIds to collapsedSeries objects 2024-05-07 18:19:55 +03:00
mikiher
a668921e29 Prettier-only formatting changes 2024-05-07 18:16:32 +03:00
advplyr
04ed4810fd Merge branch 'master' of https://github.com/advplyr/audiobookshelf 2024-05-06 17:17:41 -05:00
advplyr
941c798d78 Fix:Update author updatedAt when downloading new image, fixes author image refresh #2934 2024-05-06 17:17:35 -05:00
Nicholas Wallace
7f12c71eca Add: logs documentation 2024-05-06 01:47:57 +00:00
advplyr
f62d10746d Merge pull request #2930 from nichwall/book_matching_update
Book match tab update
2024-05-05 17:03:42 -05:00
advplyr
13afa12456 Map Select All translations 2024-05-05 17:04:41 -05:00
advplyr
4e1406f612 Merge pull request #2929 from nichwall/email_guide_link
Add: link to guide for email settings
2024-05-05 16:55:48 -05:00
advplyr
ce98bcc989 Merge pull request #2927 from nichwall/issue_templates
Issue Bug template updates
2024-05-05 16:54:17 -05:00
advplyr
ff5cbae059 Update .github/ISSUE_TEMPLATE/bug.yaml 2024-05-05 16:51:55 -05:00
advplyr
04a7f24bac Update .github/ISSUE_TEMPLATE/bug.yaml 2024-05-05 16:51:17 -05:00
advplyr
68bfcb2e6e Update .github/ISSUE_TEMPLATE/bug.yaml 2024-05-05 16:51:12 -05:00
advplyr
4bd7e21a51 Merge branch 'master' of https://github.com/advplyr/audiobookshelf 2024-05-05 16:39:54 -05:00
advplyr
37932f664a Auto formatting for Server.js 2024-05-05 16:39:38 -05:00
Nicholas Wallace
0081525ed3 Add: space between covers on match tab 2024-05-05 17:31:12 +00:00
Nicholas Wallace
7e13cb6ecf Add: Select All for match tab 2024-05-05 17:27:45 +00:00
Nicholas Wallace
721dd14c1f Add: link to guide for email settings 2024-05-05 17:07:35 +00:00
Nicholas Wallace
047c8ec017 Formatting updates 2024-05-05 16:44:00 +00:00
Nicholas Wallace
fa5d2b2020 Fix: label tabbing 2024-05-05 16:37:48 +00:00
Nicholas Wallace
dfe6505af0 Fix: label placement 2024-05-05 16:37:11 +00:00
Nicholas Wallace
b0e33970b8 Add more fields to bug report template 2024-05-05 16:35:26 +00:00
Rasmus Krämer
d9f828c717 Added item sessions endpoint 2024-05-05 13:14:30 +02:00
advplyr
15ca3307bd Merge pull request #2916 from Myticktack/patch-1
Update de.json, add translation for #2914
2024-05-04 12:21:33 -05:00
Daniel Drews
fa3b7e2f60 Update de.json, add translation for #2914
Add Translation for "Read more" & "Read less" added by Issue #2914
2024-05-04 17:27:38 +02:00
advplyr
a6de76a983 Update:Close edit modal when pressing chapter edit button and already on chapter page #2915 2024-05-03 17:25:30 -05:00
advplyr
724e06e9d2 Update:i18n translation strings for Read more/less #2914 2024-05-03 17:12:49 -05:00
advplyr
bf3db1dae0 Fix:Fullscreen cover image modal not updating when changing covers #2900 2024-05-02 17:48:50 -05:00
advplyr
410801347c Fix:Switching library on series item page not redirecting #2902 2024-05-01 17:23:49 -05:00
CoffeeKnyte
5041f80cb0 Added limit variable to getAuthorsWithCount()
- Clarified and updated the comments
- added parameter "limit" to getAuthorsWithCount()
- the limit is set to 10 when called from LibraryController.js
- as per Nichwall's comments
2024-05-01 07:24:42 -04:00
CoffeeKnyte
7229cfce84 Added limit 10 to getAuthorsWithCount() call
As per nichwall's request
2024-05-01 07:20:48 -04:00
advplyr
cb1ebd4a17 Update editor config formatting options
Co-authored-by: Arran Hobson Sayers <ahobsonsayers@gmail.com>
2024-04-30 17:45:55 -05:00
advplyr
7929f3dc42 Merge pull request #2853 from mikiher/nuxt-unit-tests
Add client component testing framework and tests
2024-04-30 17:32:24 -05:00
CoffeeKnyte
95cdb23efb split getAuthorsWithCount to 2 lighter functions
getAuthorsWithCount - now only gets the top 10 authors (in that library) by number of books
getAuthorsTotalCount - new function to only get total number of authors (in that library)
2024-04-30 11:14:55 -04:00
CoffeeKnyte
182527bfa8 Update LibraryController.js
used a lighter function to find total author count
2024-04-30 11:09:06 -04:00
mikiher
2eb19d46d5 Move test files to a separate directory 2024-04-30 11:30:00 +03:00
advplyr
10e7f142ec Update:Cover resolution to use unicode multiplication sign instead of x #2888 2024-04-29 17:22:14 -05:00
advplyr
c55988102d Merge pull request #2891 from ahobsonsayers/master
Tweaks to custom metadata provider schema
2024-04-29 17:13:04 -05:00
Arran Hobson Sayers
d488b17869 Update custom metadata provider schema 2024-04-29 22:50:42 +01:00
advplyr
ff27c0b58b Auto formatting 2024-04-29 16:30:30 -05:00
mikiher
2bd532eb9a Put book_placholder.jpg in browser cache 2024-04-29 11:16:49 +03:00
mikiher
e5fe31fe26 replace id attribute (which has to be unique across a document) with cy-id (which doesn't) 2024-04-29 08:30:14 +03:00
mikiher
ec83eb0a27 Add support for cy.get("&id") (translates to [cy-id="id"]) 2024-04-29 08:03:10 +03:00
mikiher
6236f53b4f Change a couple of element ids to camelCase 2024-04-29 07:59:38 +03:00
advplyr
1b2cf50633 Fix:Catch error with transcodes writing concat file & do not fallback to AAC encode if error message is a failure to find include file 2024-04-27 16:41:57 -05:00
advplyr
3ab638ed61 Fix:Trim whitespace from username when creating new, remove trim from password to allow whitespace #2882 2024-04-26 17:07:19 -05:00
advplyr
bd1309b680 Fix:nodemailer transport object only use secure: true when port is 465 #2765 2024-04-25 18:04:02 -05:00
advplyr
00bc50c02d Merge pull request #2877 from v3DJG6GL/v3DJG6GL-itunes-podcast-regions
add iTunes podcast regions for all ABS supported languages
2024-04-24 17:36:21 -05:00
advplyr
e8bb92826a UI/UX update podcast search region dropdown max width and height 2024-04-24 17:37:04 -05:00
advplyr
a0cc42b385 Fix:UI/UX: Users table show red rows for disabled accounts #2876 2024-04-24 10:01:32 -05:00
v3DJG6GL
7edc7ce861 remove-region-bangladesh 2024-04-24 16:13:19 +02:00
v3DJG6GL
0302ed986e fix sorting 2024-04-24 13:57:51 +02:00
v3DJG6GL
babfb6978a add-itunes-podcast-regions
This PR adds iTunes podcast regions for all languages that ABS currently supports.
2024-04-24 13:51:38 +02:00
advplyr
2cb53fafd7 Fix:Audio player cover art aspect ratio changes with library #2870 2024-04-23 17:12:13 -05:00
mikiher
8dbe35e5aa Use absolute positioning for the card element 2024-04-23 19:14:47 +03:00
advplyr
bd06b6c716 Update:Decrease breakpoint for hiding volume button on audio player #2868 2024-04-22 17:53:29 -05:00
advplyr
8b27c726d5 Version bump 2.9.0 2024-04-21 16:45:13 -05:00
advplyr
68418c1d3b Merge pull request #2820 from apocer/openid_signing_algorithm
Add option to set Signing Algorithm for OpenID Authentification
2024-04-21 16:07:30 -05:00
advplyr
a8af6db3d6 Format update of authentication page for supported algorithms 2024-04-21 16:05:41 -05:00
advplyr
af856ce1ec Merge branch 'master' into openid_signing_algorithm 2024-04-21 15:38:33 -05:00
advplyr
aae8e7535a Fix:Home page always showing horizontal scrollbar 2024-04-21 15:36:01 -05:00
advplyr
359a2752d8 Fix:Server crash when scanning in invalid epub #2856 2024-04-21 15:07:53 -05:00
advplyr
9102a0045f Merge pull request #2803 from nichwall/vacuum_bundling
OpenAPI Spec, try 2
2024-04-20 14:57:32 -05:00
advplyr
b124d61826 Update yaml docs to include BearerAuth 2024-04-20 14:57:38 -05:00
advplyr
8e6ead59ce Update yaml keys to camelCase 2024-04-20 14:55:57 -05:00
advplyr
f74d741821 Fix:Server crash when updating media with external cover url that fails to download #2857 2024-04-20 11:34:21 -05:00
mikiher
0498d8cb83 Get book placeholder image from fixture rather than from server 2024-04-19 09:49:19 +03:00
advplyr
15f83986e7 Update library stats previewicons padding 2024-04-18 17:45:47 -05:00
advplyr
a57fe42dff Update:Library stats to format numbers using selected language #2861, clean up UI for library stats preview icons 2024-04-18 17:30:06 -05:00
advplyr
b03198abd9 Add comments/jsdocs to i18n.js 2024-04-18 17:06:12 -05:00
advplyr
ad30977781 Fix:Custom metadata provider including extra curly bracket in query string #2860 2024-04-18 16:16:59 -05:00
mikiher
129da51f76 Add tailwind.compiled.css to .gitignore 2024-04-18 07:47:10 +03:00
advplyr
dbe10382fd Update:Podcast episode downloader only takes audio streams #2858 2024-04-17 17:09:36 -05:00
mikiher
e5bababeae Add tests for LazySeriesCard.vue 2024-04-17 23:27:31 +03:00
mikiher
9b332f0e66 make $constants, $strings, and utility functions avaiable to Cypress mounted componenets 2024-04-17 23:25:44 +03:00
mikiher
a49c5afa46 Fix a couple of stub assertions in AuthorCard.cy.js 2024-04-17 23:22:21 +03:00
advplyr
f0caf1a933 Update:Book matches support lowercase letters in audible ASIN #2849 2024-04-16 16:39:57 -05:00
mikiher
9e1c907591 Add NarratorCard and AuthorCard component tests 2024-04-16 00:00:35 +03:00
mikiher
d638a328d8 Add cypress npm scripts 2024-04-15 23:58:13 +03:00
mikiher
f597798839 Add Cypress config and support files 2024-04-15 23:57:21 +03:00
mikiher
303ef6b7c5 Add Cypress to dev dependencies 2024-04-15 23:54:56 +03:00
advplyr
0f7c99d989 Fix:Retry transcode forcing AAC to handle the bad audible m4bs #2720 2024-04-15 15:14:30 -05:00
advplyr
60c65008dc Fix:Match all books only matching first 100 #2096 2024-04-14 17:19:21 -05:00
advplyr
c4fd4ff9de Fix:Update metadata.json when using item metadata utils #2837 2024-04-12 17:34:10 -05:00
advplyr
29fc503503 Merge branch 'master' of https://github.com/advplyr/audiobookshelf 2024-04-11 17:29:31 -05:00
advplyr
bca49616e1 Update:Podcast episode audio file ID3 tags use comment and description tag for description instead of subtitle #2843 2024-04-11 17:29:23 -05:00
advplyr
cb49c17fc5 Merge pull request #2841 from nichwall/i18n-integration-fix
I18n integration fix
2024-04-11 17:17:13 -05:00
Nicholas Wallace
9e1686232b Example: es.json is fixed 2024-04-11 01:34:32 +00:00
Nicholas Wallace
f702358bbd Example: missing key in es.json 2024-04-11 01:33:38 +00:00
Nicholas Wallace
9a0b8de354 Example: bad key in es.json 2024-04-11 01:31:22 +00:00
Nicholas Wallace
6ed6fff6bd Update i18n workflow to 1.2.0 2024-04-11 01:29:00 +00:00
Nicholas Wallace
75007bb371 Fix: i18n-integration not running on PRs 2024-04-11 01:28:40 +00:00
advplyr
df9da095ef Map i18n strings to uk.json 2024-04-10 17:27:38 -05:00
advplyr
64c98722c3 Merge pull request #2840 from soaibsafi/master
Add Bengali translation
2024-04-10 17:25:59 -05:00
advplyr
36c1a8b2df Fix bn i18n string keys 2024-04-10 17:23:12 -05:00
soaibsafi
710d6af4b3 Adds Bengali translation 2024-04-10 19:46:39 +02:00
advplyr
cd7ecb9933 Update:User permission tags accessible to user are alphabetized #2667 2024-04-09 17:54:09 -05:00
apocer
f75f0b8cc8 show dropdown if issuer has list of algorithms 2024-04-09 22:29:06 +02:00
advplyr
e60d2a9858 Add:Podcast library filter for languages and show language on podcast item page 2024-04-08 15:48:41 -05:00
advplyr
04993dd63d Update:Show language on book item page w/ link to filter #2834 2024-04-08 15:38:34 -05:00
advplyr
41af913280 Update:Edit item cover tab UI for small screen sizes #2832 2024-04-07 16:24:23 -05:00
advplyr
8dc0f2c67c Fix:Duplicate keys error when the same library item is shown twice in continue series 2024-04-06 17:48:40 -05:00
advplyr
fc196180b3 Merge pull request #2805 from rasmuslos/master
Add client name to possible device info lines
2024-04-05 16:50:26 -05:00
advplyr
4a127d35b9 Update:Add client name and version to sessions table and session modal 2024-04-05 16:50:15 -05:00
advplyr
1525fdf4f6 Merge pull request #2821 from lkiesow/series-separator
Separator between multiple series
2024-04-04 17:55:53 -05:00
advplyr
8a29c998da Update item page series comma separated list to not include comma in link 2024-04-04 17:54:43 -05:00
Lars Kiesow
f56d9f128f Separator between multiple series
If a book is part of multiple series, this patch adds a separator
between the series on the library item details page. With no separator,
it is not immediately clear that they are separate series.
2024-04-04 21:55:52 +02:00
advplyr
c5785e9c20 Update:Increase breakpoint for player to change buttons to two lines #2799 2024-04-03 18:41:41 -05:00
advplyr
0ca91ecfff Merge pull request #2817 from springsunx/patch-2
Update zh-cn.json
2024-04-03 18:15:29 -05:00
basti
304d0f6d43 id_token_signed_respo... should be in new Client 2024-04-03 22:52:49 +02:00
basti
6c9a811472 Add ui and settings for OpenID Signing Algorithm 2024-04-03 16:18:13 +02:00
SunX
116a7fb994 Update zh-cn.json 2024-04-03 09:55:33 +08:00
advplyr
8e46181ba0 Update:Adding tooltips to player controls forward/backward and next/prev #2800 2024-04-02 18:05:44 -05:00
advplyr
a336686e42 Merge pull request #2802 from pmangro/master
[PT-BR] OpenID permission strings
2024-04-01 17:14:16 -05:00
Rasmus Krämer
c8957fe373 Add client name to possible device info lines 2024-04-01 16:20:09 +02:00
Nicholas Wallace
ca7eaf9750 OpenAPI spec readme 2024-04-01 00:44:51 +00:00
Nicholas Wallace
74dd24febf Bundled spec 2024-04-01 00:26:55 +00:00
Nicholas Wallace
7b856474af Rename base document 2024-03-31 22:48:58 +00:00
Nicholas Wallace
c7ac12a67a Split schema to sub files 2024-03-31 22:47:14 +00:00
pmangro
3264359771 [PT-BR] OpenID permission strings 2024-03-31 19:44:53 -03:00
advplyr
c7cc994532 Fix:Handle enabling/disabling library watchers #2775 2024-03-31 14:57:55 -05:00
Nicholas Wallace
afe40be957 Initial large file 2024-03-30 23:47:13 +00:00
advplyr
a9c9c447f1 Merge pull request #2769 from Sapd/openid-permissions
OpenID: Integrate permissions (Fixes #2523)
2024-03-30 14:38:32 -05:00
advplyr
aa1aeacc09 Map new translation strings 2024-03-30 14:26:55 -05:00
advplyr
fc595bd799 Updates to authentication page for mobile screen sizes 2024-03-30 14:25:38 -05:00
advplyr
a5d7a81519 Clean up formatting of advanced group/permission claims on authentication page 2024-03-30 14:17:34 -05:00
advplyr
7e8fd91fc5 Update OIDC advanced permissions check to only perform an update on changes
- Update permissions example to use UUIDv4 strings for allowedLibraries
- More validation on advanced permission JSON to ensure arrays are array of strings
- Only set allowedTags and allowedLibraries if the corresponding access all permission is false
2024-03-30 14:04:02 -05:00
advplyr
c2ed0b7d3d Merge branch 'master' of https://github.com/advplyr/audiobookshelf 2024-03-30 11:40:43 -05:00
advplyr
aefda8bd51 Fix:Local sessions set date and dayOfWeek using the updatedAt timestamp passed in from the client #2795 2024-03-30 11:40:35 -05:00
advplyr
93bec282d2 Merge pull request #1888 from jorgectf/jorgectf/add-codeql-workflow
Add CodeQL workflow
2024-03-29 16:47:07 -05:00
advplyr
1396a432a4 Merge pull request #2797 from mikiher/rtl-fixes
Add dir="auto" attribute where RTL display is needed
2024-03-29 16:08:50 -05:00
Denis Arnst
90e1283058 OpenID: Allow email_verified null and also check username
Only disallow when email_verified explicitly false
Also check username besides preferred_username, even when its not included in OIDC checks (synology uses username)
2024-03-29 15:11:56 +01:00
Denis Arnst
8cd50d5684 OpenID: Don't downgrade root 2024-03-29 14:51:34 +01:00
advplyr
50bd2648aa Fix:Server crash on matching book with an author name ending in comma #2796 2024-03-28 17:00:07 -05:00
mikiher
33254654d5 Add dir="auto" attribute where it makes sense 2024-03-28 23:56:59 +02:00
Denis Arnst
617b8f4487 OpenID: Rename tags switch 2024-03-28 16:16:26 +01:00
advplyr
f9b95bb003 Merge branch 'master' of https://github.com/advplyr/audiobookshelf 2024-03-27 16:11:57 -05:00
advplyr
740640884f Update:Support for comic files with webp images #2792 2024-03-27 16:11:47 -05:00
advplyr
86fea5c667 Merge pull request #2791 from mikiher/abs-windows-dispatch
Workflow to dispatch an ABS windows event on server release
2024-03-27 15:37:52 -05:00
mikiher
33e4b51aee Revert "add dummy pull_request event for the workflow to appear in the list"
This reverts commit 1cf0bd0f01.
2024-03-27 13:38:17 +02:00
mikiher
1cf0bd0f01 add dummy pull_request event for the workflow to appear in the list 2024-03-27 13:30:00 +02:00
mikiher
8ce5a5cdbd Add workflow to dispatch an abs-windows event 2024-03-27 13:18:02 +02:00
advplyr
fc26b7af0a Merge pull request #2789 from justcallmelarry/bugfix/corretly-working-limit-for-continue-series-toggle
Fix book limit for the Continue Series shelf (with skip earlier books toggle active)
2024-03-25 15:45:45 -05:00
Lauri Vuorela
2d68fa2c27 fix book limit for the contiue series shelf 2024-03-25 16:32:29 +01:00
advplyr
f241cb2280 Merge pull request #2787 from nichwall/translation_faq_link
Translation guide link added to readme
2024-03-24 05:07:08 -05:00
Nicholas Wallace
125346bb5c Translation guide link added to readme 2024-03-24 03:11:00 +00:00
advplyr
b60f62cebf Merge pull request #2784 from rasmuslos/patch-1
Fix custom metadata provider crash
2024-03-23 15:27:51 -05:00
advplyr
51ff62356d Merge pull request #2773 from mikiher/fix-library-files-inconsistencies
Fix library files inconsistencies
2024-03-23 15:25:33 -05:00
advplyr
f827aa97f8 Update library scanner findLibraryItemByItemToFileInoMatch query to iterate through json objects comparing inodes 2024-03-23 14:56:32 -05:00
mikiher
68276fe30b Fix handling of file moves from root folder to sub folder and back 2024-03-23 18:31:52 +02:00
Rasmus Krämer
961533765f Fix custom metadata provider crash 2024-03-23 14:54:34 +01:00
advplyr
c1bbec22f0 Merge pull request #2780 from nichwall/i18n_integration_check
Add i18n integration workflow
2024-03-22 15:21:14 -05:00
Nicholas Wallace
7d0eb215d6 Add integration workflow 2024-03-22 01:28:50 +00:00
advplyr
ff5226fa93 Update:Remove unused missing/invalid audiobook parts logic and keys 2024-03-21 14:38:52 -05:00
advplyr
8d7530254c Update:Re-order chapters table infront of audio tracks table on book item page #2778 2024-03-21 14:23:49 -05:00
advplyr
6957b4baf6 Merge pull request #2777 from burghy86/patch-14
Update it.json
2024-03-21 08:41:34 -05:00
burghy86
01c8d42291 Update it.json
month fix string
2024-03-21 12:52:49 +01:00
advplyr
1e21847852 Merge pull request #2772 from cor-bee/master
Add Ukrainian Translation and Podcast Region
2024-03-20 16:39:30 -05:00
mikiher
1bee082720 Update libraryFolderID correctly in scanFolderUpdates 2024-03-20 11:40:50 +02:00
Illia Pyshniak
b0a9bed15a Update i18n.js
Add Ukrainian language and podcast region
2024-03-19 23:18:34 +02:00
Illia Pyshniak
1d7434cbbb Create uk.json 2024-03-19 23:12:29 +02:00
Denis Arnst
1646f0ebc2 OpenID: Ignore admin for advanced permissions
Also removed some semicolons
2024-03-19 19:35:34 +01:00
Denis Arnst
50330b0a60 Auth: Add translations 2024-03-19 19:18:47 +01:00
Denis Arnst
f661e0835c Auth: Simplify Code 2024-03-19 19:18:38 +01:00
mikiher
9511122bae Fix LibraryItem and Media file update logic for library scans 2024-03-19 19:28:26 +02:00
Denis Arnst
56f1bfef50 Auth/OpenID: Implement Permissions via OpenID
* Ability to set group
* Ability to set more advanced permissions
* Modified TextInputWithLabel to provide an ability to specify a different placeholder then the name
2024-03-19 17:57:24 +01:00
advplyr
8e5b7504ae Merge pull request #2760 from pmangro/Continue-series
[PT-BR] Continue Series
2024-03-17 19:22:58 -05:00
advplyr
0a0006f949 Merge pull request #2756 from arcmagedr/master
Add Hebrew translation json and Hebrew to i18n.js
2024-03-17 19:22:21 -05:00
advplyr
5b836dfa28 Remove duplicate he language code 2024-03-17 19:19:52 -05:00
advplyr
8396900178 Merge pull request #2757 from mikiher/fix-unit-tests-flow
Change unit tests workflow to include conditional checkout steps
2024-03-17 19:14:41 -05:00
pmangro
8f80948211 [PT-BR] Continue Series 2024-03-17 19:09:16 -03:00
arcmagedr
4ad09ec3d8 Merge branch 'advplyr:master' into master 2024-03-17 22:26:49 +02:00
dor
be4eb28b21 finished proofing Hebrew translation 2024-03-17 22:25:49 +02:00
mikiher
f938fca2c7 Fix bug in workflow_dispatch checkout step 2024-03-17 07:57:28 +02:00
mikiher
d562f6a69f Change unit-tests.yml workflow to include conditional checkout step 2024-03-17 07:36:13 +02:00
advplyr
166454ef43 Version bump v2.8.1 2024-03-16 17:15:33 -05:00
advplyr
d5c854d606 Update:Add robots.txt and noindex meta tag 2024-03-16 16:35:05 -05:00
advplyr
eace46bf55 Merge pull request #2688 from mfcar/mf/loginPage
Update Login Page with Logo and Input Form Styling
2024-03-16 15:57:45 -05:00
advplyr
b9ffce166e Login page add overflow scroll for mobile landscape, update z index for logo 2024-03-16 15:55:13 -05:00
advplyr
9713e94aed Reformat login page with logo in top left 2024-03-16 15:41:35 -05:00
advplyr
d71bc89c9d Merge branch 'master' into mf/loginPage 2024-03-16 15:24:22 -05:00
advplyr
a2b2a2d060 Fix:Applying backup not properly overwriting existing sqlite file
- Fixed resetting api cache on backup
- Added loading indicator in backups table
- Fixed apply backup api not responding with 200 http status code
- Added additional logging and failsafes
2024-03-16 15:12:33 -05:00
dor
752268effb mid point proofing 2024-03-16 21:00:44 +02:00
dor
9e3b3f3e12 add Hebrew translation json and Hebrew to i18n.js 2024-03-16 19:26:22 +02:00
advplyr
88f9533b37 Fix:HLS.js retry fragments #2748 #2720 2024-03-15 17:10:43 -05:00
advplyr
630ece82ad Fix:Chapter modal scroll to current chapter 2024-03-15 14:35:09 -05:00
advplyr
5777184cae Merge pull request #2745 from mikiher/unit-tests-flow
Unit tests flow
2024-03-15 14:20:57 -05:00
mfcar
a76da14fb0 Merge branch 'refs/heads/master' into mf/loginPage 2024-03-15 08:14:45 +00:00
mikiher
0c612b4836 Update unit test workflow to include push event 2024-03-15 09:51:40 +02:00
mikiher
a1af672c7c Add unit test workflow 2024-03-15 08:50:51 +02:00
advplyr
5fcd23409a Update:dev.js in devcontainer to include the SkipBinariesCheck flag #2741 2024-03-14 16:32:23 -05:00
advplyr
99f0799a11 Update:Adding support for skipping check for ffmpeg/ffprobe binaries with environment variable SKIP_BINARIES_CHECK
- Set SKIP_BINARIES_CHECK=1 env variable to skip
- Or set SkipBinariesCheck: true in dev.js #2741
2024-03-14 16:29:01 -05:00
advplyr
316aeba1b0 Merge pull request #2740 from Schiriki123/master
Add name labels to login form
2024-03-14 15:40:45 -05:00
advplyr
bfd4a378f3 Merge pull request #2737 from justcallmelarry/feature/add-toggle-for-skipping-earlier-instalments-in-continue-series
Add library toggle setting for skipping earlier instalments in Continue Series
2024-03-14 14:40:20 -05:00
advplyr
521db90ae0 Update JSDocs, remove unused libraryId replacement 2024-03-14 14:37:24 -05:00
advplyr
d02fc2debe Update continue series skip earlier books query attribute to look for finished books, update wording on help text, map translations 2024-03-14 14:27:33 -05:00
advplyr
e6c21c5be1 Merge pull request #2742 from mikiher/broken-binary-manager-test
Fix broken BinaryManager.isBinaryGood test
2024-03-14 13:13:59 -05:00
advplyr
91248b496e Merge pull request #2734 from mikiher/fix-sequence-cleanup
Make series sequence cleanup slighlty less aggressive
2024-03-14 13:12:04 -05:00
mikiher
f7ae7783bd Fix broken BinaryManager.isBinaryGood test 2024-03-14 19:58:42 +02:00
mikiher
ae395497a5 Add tests for cleanSeriesSequence 2024-03-14 19:37:51 +02:00
mikiher
8826d3af62 fix cleanSeriesSequence method to extract first numeric value 2024-03-14 19:36:51 +02:00
Lauri Vuorela
65153fae9d var => let 2024-03-14 09:42:50 +01:00
Lauri Vuorela
d4c1bc5dfc use already fetched library settings, only fetch maxSequence if setting is turned on 2024-03-14 09:41:48 +01:00
Schiriki
d6f13513ae Add name labels to login form 2024-03-13 23:46:56 +01:00
advplyr
2584c3b432 Merge pull request #2733 from kaldigo/master
Added isbn to CustomProviderAdapter
2024-03-13 17:21:51 -05:00
advplyr
b54421412d Merge pull request #2738 from Sapd/auth-fix
Auth: Fix crash on missing logout URL
2024-03-13 17:18:35 -05:00
advplyr
e2451a3281 Merge pull request #2732 from den13501/i18n-add-zhTW
Add traditional Chinese(zh-TW) to i18n
2024-03-12 17:47:04 -05:00
advplyr
dbf4bd5c3d Merge pull request #2691 from lkiesow/hash-in-filename
Fix file names with URL control characters
2024-03-12 17:40:37 -05:00
Denis Arnst
2a722ab163 Auth: Fix crash on missing logout URL
When using OpenID
Also added debug information on openid errors
2024-03-12 18:07:13 +01:00
Lauri Vuorela
c83399c7b5 use the toggle to not show earlier works than the ones already read 2024-03-12 17:04:26 +01:00
Lauri Vuorela
a814e45150 add a toggle for the new continue series setting 2024-03-12 17:00:21 +01:00
mikiher
29e9216bb1 Make series sequence cleanup slighlty less aggressive 2024-03-12 13:17:52 +02:00
Kaldigo
94d1732b0d Added isbn to CustomProviderAdapter 2024-03-12 08:18:52 +00:00
-Shiken-
7610084627 Update zh-tw.json
fix
2024-03-12 15:39:01 +08:00
-Shiken-
d840905a97 Create zh-tw.json
add traditional Chinese translation text.
2024-03-12 15:36:42 +08:00
-Shiken-
7b1b448795 Add traditional Chinese translation
for traditional user.
2024-03-12 11:29:24 +08:00
advplyr
77559d29bb Merge pull request #2724 from mikiher/fix-library-filter-data-access
Fix library filter data direct access
2024-03-11 17:08:41 -05:00
advplyr
c14f9accaf Update functions for #2724 and add jsdocs 2024-03-11 17:07:03 -05:00
advplyr
76a1f48c62 Remove UID/GID from Server constructor 2024-03-11 11:11:13 -05:00
mikiher
ae0a9bcf86 Merge branch 'advplyr:master' into fix-library-filter-data-access 2024-03-11 08:33:47 +02:00
advplyr
9e44fe5524 Merge pull request #2721 from mikiher/keyboard-navigation-2
Add keyboard navigation to multi-select components
2024-03-10 09:45:16 -05:00
advplyr
727dad7e19 Update multi select highlight color to yellow, remove console logs 2024-03-10 09:43:24 -05:00
advplyr
0c2de91097 Merge pull request #2726 from nichwall/vietnamese_translations
Vietnamese translations
2024-03-09 20:09:09 -06:00
advplyr
450fa45360 Update Vietnamese datefns locale code 2024-03-09 20:09:08 -06:00
Nicholas Wallace
e0dddae2c2 Added missing keys 2024-03-09 23:13:20 +00:00
Nicholas Wallace
daa9fccc14 Add: Vietnamese translations 2024-03-09 23:00:01 +00:00
mikiher
ad45dadc15 Remove redundant space 2024-03-09 12:07:08 +02:00
mikiher
0e8148001e Fix direct access to Database.libraryFilterData 2024-03-09 11:59:50 +02:00
advplyr
fa71f9db2e Merge master 2024-03-08 12:22:29 -06:00
advplyr
0d9d2fa4be Merge pull request #2714 from mikiher/keyboard-navigation
Fix input width in MultiSelect UI components  - replacement solution
2024-03-08 12:20:46 -06:00
advplyr
c34e9cde05 Merge branch 'master' into keyboard-navigation 2024-03-08 12:15:07 -06:00
advplyr
b934a755b5 Merge branch 'master' into keyboard-navigation-2 2024-03-08 12:04:13 -06:00
mikiher
a5772f6b66 Add keyboard navigation to multi-select components 2024-03-08 08:51:05 +02:00
advplyr
153f149d58 Merge pull request #2580 from KeyboardHammer/authorSort
Add sorting to author page
2024-03-07 12:28:55 -06:00
advplyr
e50b06183e Merge branch 'master' into authorSort 2024-03-07 12:26:07 -06:00
advplyr
305689d513 Update authors sort 2024-03-07 12:26:04 -06:00
advplyr
4dd140585d Add:Abridged checkbox to batch edit overwrite map details #2695 2024-03-06 15:29:10 -06:00
mikiher
cd60d0219f Bring back setInputWidth 2024-03-06 14:02:15 +02:00
mikiher
8ec18e8d7b Merge branch 'keyboard-navigation' of https://github.com/mikiher/audiobookshelf into keyboard-navigation 2024-03-06 13:53:49 +02:00
mikiher
15545654ea Alternative input width fix in MultiSelect components 2024-03-06 13:41:54 +02:00
advplyr
8a0fab2b20 Fix:Resizing page update chapter ticks and track bar #2707 2024-03-05 14:30:39 -06:00
advplyr
6e8c6aa740 Merge pull request #2701 from mikiher/keyboard-navigation
Fix input width in MultiSelect UI components
2024-03-05 13:13:52 -06:00
mikiher
5005aabe5e Fix input width in MultiSelect components 2024-03-03 23:40:47 +02:00
advplyr
abc2d28617 Merge pull request #2699 from pmangro/master
[PT-BR] enhance-ebook-filter strings translation
2024-03-03 11:42:56 -06:00
pmangro
7569a14510 Merge pull request #1 from pmangro/enhance-ebook-filter-strings
[PT-BR] enhance-ebook-filter strings translation
2024-03-03 13:33:01 -03:00
pmangro
b52341dbcf [PT-BR] enhance-ebook-filter strings translation 2024-03-03 13:32:01 -03:00
advplyr
b4eed3bad2 Merge pull request #2694 from mikiher/client-image-caching
Client side cover image caching
2024-03-01 17:48:08 -06:00
mikiher
4fe672f09d Update cover image URLs with timestamp where available 2024-03-01 11:55:53 +02:00
advplyr
49af7eb7b0 Merge pull request #2692 from lkiesow/log-src
Fix log source in log file
2024-02-29 17:01:59 -06:00
advplyr
c93c863d82 Merge pull request #2677 from Teekeks/enhance-ebook-filter
feat: Expanded filter to include "has no ebook" and "has no supplementary ebooks" options
2024-02-29 14:00:50 -06:00
advplyr
763bb1b829 Map ebook filter translations 2024-02-29 13:59:00 -06:00
Lars Kiesow
79d32274aa Fix log source in log file
The logger should include a source containing the location where the
logger was called. This works well for logging to `stdout`. Unfortunately,
the file logs contain the locations where the file logging is called
inside of the logger. This is not helpful:

```
{"timestamp":"2023-11-19 16:35:43","source":"Logger.js:114","message":"[oldDbFiles] Processed db data file with 1 entities","levelName":"INFO","level":2}
{"timestamp":"2023-11-19 16:35:43","source":"Logger.js:114","message":"[oldDbFiles] Finished loading db data with 2 entities","levelName":"INFO","level":2}
{"timestamp":"2023-11-19 16:35:43","source":"Logger.js:114","message":"[oldDbFiles] 2 settings loaded","levelName":"INFO","level":2}
```

This patch fixes the issue, ensureing that the actual source location
will be logged:

```
{"timestamp":"2024-02-29 18:12:59.832","source":"DailyLog.js:132","message":"[DailyLog] 2024-02-29: Loaded 20 Logs","levelName":"DEBUG","level":1}
{"timestamp":"2024-02-29 18:12:59.638","source":"Server.js:172","message":"=== Starting Server ===","levelName":"INFO","level":2}
{"timestamp":"2024-02-29 18:12:59.638","source":"Server.js:103","message":"[Server] Init v2.8.0","levelName":"INFO","level":2}
```
2024-02-29 18:16:29 +01:00
Lars Kiesow
987842ed04 Fix file names with URL control characters
This patch ensures that files names like `series #3 xy.jpg` are actually
handled correctly instead of the part after `#` being interpreted as
fragment and being discarded.

I noticed that in a few rare cases the App wouldn't properly display
cover images. It turns out that due the file names containing a `#`, the
file path got corrupted, causing Audiobookshelf to return a 403.
2024-02-29 17:56:55 +01:00
advplyr
d2b006b909 Update:Windows binary manager to install ffmpeg/ffprobe 5.1 #1098 2024-02-28 16:16:44 -06:00
mfcar
f4a19e48ad Update login page 2024-02-28 19:21:11 +00:00
advplyr
38f12f4795 Fix:Podcast schedule max new episodes to download setting to 0 and fix input blurs #2680 2024-02-27 17:17:33 -06:00
advplyr
7a4f4b1586 Merge pull request #2676 from pmangro/2.8.0.a-PT-BT
[PT-BR] Updated strings
2024-02-27 15:59:09 -06:00
pmangro
20ec54e085 [PT-BR] Updated strings 2024-02-27 14:31:21 -03:00
Teekeks
655bebfec4 feat: Expanded filter to include "has no ebook" and "has no supplementary ebooks" options 2024-02-27 18:30:05 +01:00
advplyr
71e1abd263 Merge pull request #2673 from Machou/master
Update fr.json
2024-02-27 08:56:03 -06:00
Machou
72172dcb33 Update fr.json 2024-02-27 08:56:31 +01:00
advplyr
def2988e12 Update:Passport openid-client request timeout set to 10s (default was 3.5s) #2669 2024-02-26 17:20:11 -06:00
mikiher
b47793c365 Add cache control header for timestamped cover image requests 2024-02-26 14:00:25 +02:00
advplyr
3a99cc56b7 Update:Debian packager script to use xz compression instead of zstd 2024-02-25 12:56:04 -06:00
advplyr
24c35dede5 Merge pull request #2659 from mikiher/quick-match-dup-authors
Fix dup author addition logic
2024-02-25 08:12:05 -06:00
advplyr
8c4400dff1 Merge pull request #2657 from JBlond/master
Update de strings
2024-02-25 08:08:17 -06:00
advplyr
af8dffaa33 Merge pull request #2573 from mikiher/fix-match-update
Merge cover and media update in Match.vue into a single /media API call
2024-02-25 08:07:51 -06:00
advplyr
4a36a3c8e6 Merge branch 'master' into fix-match-update 2024-02-25 08:00:29 -06:00
mikiher
e6735e042e Fix dup author addition logic 2024-02-25 09:01:26 +02:00
JBlond
c799379a54 Update de strings 2024-02-24 20:23:51 +01:00
advplyr
d8b9f08e5a Merge pull request #2641 from Teekeks/year-review-translation
feat(i18n): made "Year in Review" UI elements translatable and added german translation for those
2024-02-23 17:01:43 -06:00
advplyr
608b25de45 Map en-us translations 2024-02-23 16:59:46 -06:00
advplyr
2db8869908 Merge branch 'master' into year-review-translation 2024-02-23 16:56:53 -06:00
advplyr
9500737bbe Merge pull request #2644 from RasmusKoit/estonian-translations
Adds estonian translation
2024-02-23 16:55:41 -06:00
advplyr
def2b6425b Update:Username and password inputs on login page trim whitespace #2628 2024-02-22 16:30:41 -06:00
jfrazx
5e8f247e84 chore: merge master 2024-02-22 10:50:49 -08:00
Lena During
761a2ff0bf Merge branch 'advplyr:master' into payer-translation 2024-02-22 19:25:04 +01:00
Teekeks
e368ffe29f feat(i18n): added missing translatable string in player ui 2024-02-22 19:20:49 +01:00
mikiher
0f4b11494e Merge branch 'advplyr:master' into fix-match-update 2024-02-22 12:20:49 +02:00
rasmuskoit
46448ce1e9 Adds estonian translation 2024-02-22 09:46:09 +02:00
advplyr
fbe12b393f Merge pull request #2639 from pmangro/2.8.0.a-PT-BT
[PT-BR] Terminology adjustments and typo fixes
2024-02-21 18:29:29 -06:00
advplyr
ccf59b2c1a Merge pull request #2638 from DownloadableFox/master
Updated and fixed Spanish translation
2024-02-21 18:28:28 -06:00
Lena During
d7af3b7788 Merge branch 'master' into year-review-translation 2024-02-22 00:39:49 +01:00
Teekeks
682aca0b2a feat(i18n): made "Year in Review" UI elements translatable and added german translation for those 2024-02-22 00:36:43 +01:00
advplyr
3328ffe1b9 Merge pull request #2636 from megamegax/master
feat(i18n): add Hungarian translation
2024-02-21 16:07:36 -06:00
DownloadableFox
c07b7840e2 Updated and fixed Spanish translation 2024-02-20 21:14:55 -05:00
pmangro
9f848b2c64 Update pt-br.json - adjust terminology and typo 2024-02-20 17:28:37 -03:00
pmangro
3d66ec0761 PT-BR size adjustment 2024-02-20 11:42:48 -03:00
advplyr
f50920be69 Merge pull request #2635 from lonezel/lonezel-updated-german-translation
Update de.json
2024-02-20 08:37:48 -06:00
advplyr
d31add9d5a Merge pull request #2634 from pmangro/2.8.0-PT-BR
Updated pt-br string
2024-02-20 08:36:33 -06:00
Hunyady Mihály
a4dcb4f92e feat(i18n): add Hungarian translation 2024-02-20 12:25:43 +01:00
lonezel
2c589c1dbd Update de.json
Translated new strings to german - this is my first ever commit on Github, if I need to change something in my workflow let me know!
2024-02-20 10:19:48 +01:00
pmangro
60ea386c6d Updated pt-br string 2024-02-20 05:57:52 -03:00
advplyr
24be1a0ec5 Merge pull request #2629 from burghy86/patch-13
Update it.json
2024-02-19 08:56:39 -06:00
burghy86
e71a14756b Update it.json
new string update
2024-02-19 15:53:12 +01:00
advplyr
85fecbd1b9 Version bump v2.8.0 2024-02-18 16:43:16 -06:00
advplyr
335d39f317 Update guide link for custom metadata providers 2024-02-18 16:22:10 -06:00
advplyr
973a18d346 Update:Added button to user edit modal for unlinking user from openid #2587 2024-02-18 15:38:45 -06:00
advplyr
a43b93d796 Fix:Clear library filter data cache when library item is updated #2597 2024-02-18 14:58:46 -06:00
advplyr
acf75abdf1 Update:Match author use closest name match by levenshtein distance #2624 2024-02-18 13:06:51 -06:00
advplyr
58598bfcf2 Update:Clamp author description to 4 lines and add more button #2614 2024-02-18 11:32:24 -06:00
advplyr
7a570439db Update:Clamp item descriptions to 4 lines and show more button #2614 2024-02-18 11:24:36 -06:00
advplyr
6e769d1c20 Merge pull request #2554 from mikiher/ffmpeg-latest
Modify BinaryManager to download version 6.1
2024-02-17 17:42:24 -06:00
advplyr
d9e7f5d133 Update BinaryManager JSDocs, move validVersions to required binary objects 2024-02-17 17:40:33 -06:00
advplyr
a119b05d85 Merge branch 'master' into ffmpeg-latest 2024-02-17 17:05:51 -06:00
advplyr
7bf7b6bcf9 Merge pull request #2553 from Sapd/sso
OpenID: Implement Logout + Fix state + Fix URL Regex
2024-02-17 17:03:12 -06:00
advplyr
e47ea98cdd Fix:Disconnect from socket on logout, remove unnecessary logout function 2024-02-17 16:58:49 -06:00
advplyr
bf66e13377 Update jsdocs 2024-02-17 16:06:25 -06:00
advplyr
d7aba5629e Remove old login rate limiter 2024-02-17 15:29:06 -06:00
advplyr
a5c200ac79 Merge branch 'master' into sso 2024-02-17 14:15:41 -06:00
advplyr
fdc1fc1b2a Merge pull request #2491 from liaochuan/liaocl
Add Podcast Search Region
2024-02-17 13:32:23 -06:00
advplyr
42a4b762bd Fix translations sort order and pt-br translations 2024-02-17 13:30:30 -06:00
advplyr
180c328ed1 Update jsdocs for search podcasts 2024-02-17 13:24:49 -06:00
advplyr
2ec52a7a45 Merge branch 'master' into liaocl 2024-02-17 12:56:05 -06:00
advplyr
aacf37e32b Fix:Year in Review crashing when listening session has a null genre #2623 2024-02-16 16:16:55 -06:00
advplyr
52323b7eb5 Update:Podcast episode download show ffmpeg progress and print full debug log dump on error 2024-02-16 16:05:02 -06:00
advplyr
5b5613a762 Merge pull request #2617 from ipcintron/pwa
Update pwa icon to use iOS icon
2024-02-16 13:44:57 -06:00
ipcintron
de6df0c029 Forgot to modify nuxt.config.js 2024-02-16 12:29:52 -06:00
ipcintron
e180b3c171 Renamed the icon to make it clear it is being used for iOS 2024-02-16 12:27:10 -06:00
ipcintron
1364b79cbf Put the icon in the link array for iOS only 2024-02-16 10:10:09 -06:00
ipcintron
ef96f3102f Merge branch 'advplyr:master' into pwa 2024-02-16 09:51:10 -06:00
advplyr
06ce3b08f7 Merge pull request #2619 from ipcintron/theme
PWA (iOS) theme color fix
2024-02-16 09:31:11 -06:00
advplyr
a13217dddf Fix:Initial language code setting eventBus not yet defined 2024-02-16 09:12:47 -06:00
advplyr
ce528d4012 Merge pull request #2620 from pmangro/pmangro-patch-1
PT-BR Strings
2024-02-16 09:05:31 -06:00
pmangro
89207b6d2a Update i18n.js
Added PT-BR
2024-02-16 11:57:54 -03:00
pmangro
e9591caf81 Spelling 2024-02-16 11:56:31 -03:00
pmangro
24f1aae6b6 Update pt-br.json
Strings 541-766
2024-02-16 11:44:25 -03:00
ipcintron
04fbc9a22b change theme color 2024-02-16 02:15:27 -06:00
ipcintron
14e31d5690 update pwa icon 2024-02-16 01:32:04 -06:00
advplyr
a9e9808183 Fix:trim whitespace from asin for chapter lookup #2605 2024-02-15 17:05:48 -06:00
advplyr
af7cb2432b Update:Log uncaught exceptions to crash_logs.txt #706 & cleanup logger 2024-02-15 16:46:19 -06:00
pmangro
e0c1364916 Create pt-br.json
540 linhas iniciais
2024-02-15 19:08:05 -03:00
advplyr
04d16fc535 Fix:Audio player buttons to use button el and add aria-label translations #2599 2024-02-14 18:28:19 -06:00
advplyr
44135b3fed Rename StreamContainer to MediaPlayerContainer 2024-02-14 18:12:35 -06:00
advplyr
6111e8f0da Fix:Global search menu for mobile 2024-02-13 18:45:01 -06:00
advplyr
4e3e7b10ce Update:Custom metadata provider adapter sends mediaType as a query param 2024-02-12 17:12:49 -06:00
advplyr
ce7f81d676 Merge pull request #2486 from FlyinPancake/dewyer/add-custom-metadata-provider
[Feature] Add support for custom metadata providers through a REST API
2024-02-11 17:04:55 -06:00
advplyr
0cf2f8885e Add custom metadata provider controller, update model, move to item metadata utils 2024-02-11 16:48:16 -06:00
advplyr
ddf4b2646c Merge branch 'master' into dewyer/add-custom-metadata-provider 2024-02-11 09:10:29 -06:00
advplyr
fe1e0749a2 Update:Listening sessions table rows per page text wrapping 2024-02-08 19:12:59 -06:00
advplyr
2093468c92 Fix:Local playback sessions not persisting the last updatedAt value 2024-02-08 19:12:35 -06:00
mikiher
19af7454f2 Force Update LibraryItem model updatedAt refresh (fixes #2593) 2024-02-07 20:57:50 +02:00
KeyboardHammer
d24427aad8 fix property 2024-02-03 22:04:40 -06:00
KeyboardHammer
e2bb0cfb7c add sorting to author page 2024-02-03 21:48:35 -06:00
mikiher
2ebdb44826 Merge cover and media update in Match.vue into a single /media API call 2024-02-01 12:03:12 +02:00
advplyr
432e25565e Merge branch 'master' of https://github.com/advplyr/audiobookshelf 2024-01-31 17:23:20 -06:00
advplyr
ebe511404a Remove updateMedia endpoint cover cache purge 2024-01-31 17:23:16 -06:00
advplyr
e0a79fb86c Merge pull request #2570 from Weldawadyathink/main
Return png from AudiobookCovers.com
2024-01-30 17:02:14 -06:00
Spenser Bushey
295ca3d9a2 Return png from AudiobookCovers.com
Changes AudiobookCovers.com provider to return the full size png file from the server. The original file url has the incorrect content-type header set, which caused issues downloading new cover images.
2024-01-30 09:15:50 -08:00
advplyr
dbad8bdb96 Merge pull request #2567 from ipcintron/lockscreen_cover
added raw cover on lockscreen for iOS
2024-01-29 15:21:44 -06:00
ipcintron
8c703859a0 added raw cover 2024-01-29 13:18:58 -06:00
advplyr
bedb260b00 Update:Epub ereader allow scripted content 2024-01-28 16:02:02 -06:00
advplyr
b49592301f Merge branch 'master' of https://github.com/advplyr/audiobookshelf 2024-01-28 16:00:32 -06:00
advplyr
c6c67078b8 Update:PWA manifest icon to include PNG #2520 2024-01-28 16:00:26 -06:00
advplyr
9e45ad10f1 Merge pull request #2564 from Teekeks/patch-1
Update de.json
2024-01-28 10:10:13 -06:00
Lena During
24da859975 Update de.json 2024-01-28 16:06:07 +01:00
advplyr
0b6a8a9641 Update:Remove 64x64 app icon from PWA manifest so that only the SVG is available #2520 2024-01-27 10:50:44 -06:00
advplyr
e43c4f082e Fix:Rich text editor labels and add translations 2024-01-26 17:22:37 -06:00
advplyr
0b334cf957 Add:Authentication setting to show a custom message on login #2552 2024-01-26 17:08:23 -06:00
advplyr
ae387ab397 Merge pull request #2559 from bloodscript/master
German localization optimization
2024-01-26 16:28:02 -06:00
bloodscript
056e62dce8 added plural to metadata order hint 2024-01-26 23:15:27 +01:00
bloodscript
47999214bd corrected misspelling of adress 2024-01-26 23:13:11 +01:00
bloodscript
68473ee345 added missing metadata translation 2024-01-26 23:11:38 +01:00
bloodscript
455f27d443 mainly changed usage formular wording for you to the more commonly used, also corrected some misspellings 2024-01-26 23:09:54 +01:00
bloodscript
ba996c3b55 translation wasnt accurate verschluesselung means encryption 2024-01-26 20:12:37 +01:00
mikiher
d43a1109c8 Modify BinaryManager to download version 6.1 and remove old dowloaded versions 2024-01-25 17:51:06 +02:00
Denis Arnst
c3ba7daa16 Auth: Remove is_rest cookie 2024-01-25 16:05:41 +01:00
Denis Arnst
82048cd4f3 SSO: Also save openid_id_token longer 2024-01-25 15:13:56 +01:00
Denis Arnst
71b0a5cc81 SSO Settings: Fix Redirect URL Regex
Forgot to include subpaths
2024-01-25 11:49:10 +01:00
Denis Arnst
edb5ff1e33 SSO: Remove pick function 2024-01-25 11:44:20 +01:00
Denis Arnst
d4ed6348ee Auth: Store auth_method longer
Its not unrealistic that someone keeps being logged into the app for more than a year
if not stored longer logout process might not work anymore
2024-01-25 11:20:44 +01:00
Denis Arnst
f12ac685e8 /auth/openid: Restructure
- Distingush more explictly between mobile and web flow and simplify logic
- Allow state parameter to be passed in mobile flow
- Additional checks for correct parameters
- Remove unused id_token code
- Enforce S256 and don't allow plain PKCE
2024-01-25 11:13:34 +01:00
advplyr
b9ec4068ee Merge pull request #2510 from Torstein-Eide/patch-1
README, add HAproxy example
2024-01-24 16:55:32 -06:00
advplyr
02aabb8f97 Update readme.md 2024-01-24 16:55:21 -06:00
advplyr
dcec2154c0 Update readme.md 2024-01-24 16:55:17 -06:00
advplyr
bbc1d20396 Update readme.md 2024-01-24 16:55:12 -06:00
advplyr
e682213681 Update readme.md 2024-01-24 16:55:06 -06:00
advplyr
0153c0faae Update readme.md 2024-01-24 16:54:54 -06:00
Denis Arnst
87ebf4722b OpenID/SSO: Implement Logout functionality 2024-01-24 22:47:50 +01:00
advplyr
3906dca04e Update:RSS feeds only use chapter titles for episode titles if all audio tracks match chapter times #2543 2024-01-23 17:51:34 -06:00
advplyr
399ba314a3 Update github issue template to include Windows Tray App 2024-01-23 15:48:58 -06:00
jfrazx
70827727aa feat(429): retry 429 request errors 2024-01-22 22:19:05 -08:00
jfrazx
73c21242b4 feat: utilize p-throttle instad of limiter 2024-01-22 20:36:20 -08:00
advplyr
19e1803633 Remove unused import 2024-01-22 17:56:41 -06:00
jfrazx
06391b9b37 chore: merge master 2024-01-21 19:15:52 -08:00
advplyr
71048c7ff0 Remove support for Docker armv7 builds 2024-01-20 16:39:43 -06:00
advplyr
7f350279fa Update to node20
- updates many dependencies
- removes @nuxtjs/tailwindcss and postcss8
- pkg targets are using node18 until node20 targets are available
2024-01-19 17:54:41 -06:00
jfrazx
4c9b2ad08b chore: merge master 2024-01-16 18:31:29 -08:00
advplyr
90f4833c9e Version bump v2.7.2 2024-01-16 17:26:50 -06:00
advplyr
c0cb3a176f Update:Hide audiobook tools for windows install, remove debian folder picker alert 2024-01-16 17:19:45 -06:00
advplyr
7b0fa48e2e Update jsdocs for expanded library items 2024-01-16 16:31:16 -06:00
advplyr
b51853b3df Update:Use raw cover art for media session #2514 2024-01-15 08:34:12 -06:00
advplyr
f5545cd3f4 Add:Scanner extracts cover from comic files #1837 and ComicInfo.xml parser 2024-01-14 17:51:26 -06:00
advplyr
e76af3bfc2 Fix comic page menu dropdown highlight correct page 2024-01-13 16:41:13 -06:00
jfrazx
79c34d0638 chore: merge master 2024-01-13 11:46:38 -08:00
FlyinPancake
6ef4944d89 Merge branch 'advplyr:master' into dewyer/add-custom-metadata-provider 2024-01-13 01:08:23 +01:00
advplyr
850397e4c1 Add:Playlist button to podcast episodes on latest page #2455 2024-01-12 17:58:07 -06:00
FlyinPancake
3b531144cf implemented suggestions, extended CMPs with series 2024-01-12 21:45:03 +01:00
Torstein Eide
6ca684603c Fix typos 2024-01-12 14:35:30 +01:00
Torstein Eide
cf85d66b2f Add example for HAproxy 2024-01-12 14:26:32 +01:00
advplyr
e8fa029df7 Fix:Specific podcast rss feed cannot be fetched due to accept header #2446 2024-01-10 08:12:26 -06:00
advplyr
1a361c91f1 Merge pull request #2506 from FreedomBen/remove-dev-logs
Change `Logger.dev` calls to `Logger.debug`
2024-01-09 16:47:21 -06:00
Benjamin Porter
4a76059608 Change Logger.dev calls to Logger.debug
Logger.dev is kind of in a weird spot where it doesn't fit into the
standard log level.  It is called directly by some code and it only
checks whether a property is set (which comes from an env var) before
deciding to print out.

This standardizes on `debug` by changing the dev calls to debug. Also
removes the now unused code.
2024-01-09 15:24:23 -07:00
advplyr
da25eff5c1 Fix:Parse series sequence from OPF in cases where series_index is not directly underneath series meta #2505 2024-01-08 18:21:15 -06:00
advplyr
69e23ef9f2 Add:Epub metadata parser and cover extractor #1479 2024-01-07 17:51:07 -06:00
advplyr
48a08e9659 Merge pull request #2503 from Machou/master
Little Missed Update fr.json
2024-01-07 12:42:09 -06:00
Machou
4608f91ec6 Update fr.json 2024-01-07 02:41:16 +01:00
advplyr
e88c1fa329 Update:Show tooltip for library item card titles that are truncated #2451
- Refactored tooltip so that they dont overflow the window
2024-01-06 15:54:48 -06:00
advplyr
935e545caa Update readme for iOS beta full 2024-01-06 14:13:39 -06:00
advplyr
a426da534c Fix:Export OPML not escaping characters #2487 2024-01-05 14:45:25 -06:00
advplyr
eaf6bf29cc Fix:Improve performance for podcast rss feed episodes modal for large rss feeds 2024-01-05 14:39:25 -06:00
advplyr
a0eb6bd3dc Fix:Refresh podcast episode table when new episodes are downloaded 2024-01-05 14:38:29 -06:00
advplyr
fbe228a4f8 Merge pull request #2485 from Machou/patch-1
Update fr.json
2024-01-05 10:40:29 -06:00
advplyr
578a59063f Update discord invite link 2024-01-05 09:24:18 -06:00
mozhu
81020ff34d 播客搜索地区配置增加默认参数 2024-01-05 15:50:20 +08:00
mozhu
fea78898a5 移动播客搜索地区配置到媒体库配置 2024-01-05 14:45:35 +08:00
Machou
ffa7cc0d22 Update fr.json 2024-01-05 07:19:07 +01:00
advplyr
4f9969cd9b Merge pull request #2488 from FreedomBen/add-init-system-to-docker
Add tini as PID 1 handler in container image
2024-01-04 13:50:54 -06:00
mozhu
1be34564f2 数据绑定错误修改 2024-01-04 15:00:40 +08:00
mozhu
56eff7a236 增加播客搜索地区配置 2024-01-04 11:52:45 +08:00
advplyr
9f909b0d85 Update:Library folder browser to also work for debian and windows 2024-01-03 16:23:17 -06:00
Benjamin Porter
baa65b8155 Add tini as PID 1 handler in container image
This PR adds `tini` to the container image and uses it as PID 1 when
starting the container.  This ensures that proper PID 1 signal-handling
is implemented and passed to the underlying node.js process, thereby
ensuring that the ABS process has a chance to receive and handle signals
other than `SIGKILL`, such as the important `SIGINT`.

This is somewhat related to #2445 . Without this, the signal handled by
2445 won't be received when running in a container.

Some background:

In linux, PID 1 has special duties involving signal handling that are
different than other processes.  Node doesn't properly handle these
signals, which can lead to a number of problems ranging from annoying to
disruptive.  PID 1 also has reaping duties that can lead to resource
exhaustion if not properly handled.

For example, the container ignores `SIGINT` (Ctrl+C) as well as `docker stop`,
which can be annoying in development as you have to kill or wait for
the timeout to be reached.  In a production environment (such as Kubernetes)
this can lead to signal escalation and unnecessarily adds delays to
deployments and restarts as K8s has to wait for the timeout to be reached
before sending `SIGKILL`.

At best this is annoying and unnecessarily adds
delays.  At worst this can lead to file/data corruption as the process
doesn't get a chance to clean anything up when it is sent `SIGKILL`.
Without a proper PID 1 to forward signals, only SIGKILL can be used to
terminate the running process.
2024-01-03 13:55:43 -07:00
Barnabas Ratki
12c6a1baa0 Fix log messages 2024-01-03 20:42:35 +01:00
Barnabas Ratki
5ea423072b Small fixes 2024-01-03 20:40:36 +01:00
Barnabas Ratki
08a41e37b4 Add specification 2024-01-03 20:27:42 +01:00
Barnabas Ratki
8027c4a06f Added support for custom metadata providers
WiP but already open to feedback
2024-01-03 20:25:34 +01:00
Machou
a1e321b153 Update fr.json 2024-01-03 20:16:21 +01:00
advplyr
8c6a2ac5dd Merge pull request #2391 from mikiher/binary-manager
Add a binary manager that finds ffmpeg and ffprobe and installs them if not found
2024-01-02 14:25:56 -06:00
advplyr
b489bf9236 Restrict binary manager to Windows or development 2024-01-02 14:24:59 -06:00
advplyr
aa63aa6cf3 Merge branch 'master' into binary-manager 2024-01-02 14:16:27 -06:00
advplyr
9a2b93fb37 Version bump v2.7.1 2023-12-31 15:37:23 -06:00
advplyr
e8ea7efc98 Merge branch 'master' of https://github.com/advplyr/audiobookshelf 2023-12-31 15:36:37 -06:00
advplyr
81a76593da Fix:Merging chapters from multiple audio files with the same chapter titles #2461 2023-12-31 15:35:17 -06:00
advplyr
5336864f7d Merge pull request #2465 from thevoltagesource/getFileMtimeMs_Unhandled_Exception
Add try/catch to fileUtils.getFileMtimeMs
2023-12-31 15:34:43 -06:00
advplyr
d38058e1d2 Fix:Podcast episode time remaining shown on button showing 0 seconds after toggling mark as finished 2023-12-31 15:32:44 -06:00
advplyr
fececd4651 Fix:Playlists navigation button not showing on mobile screen #2469 2023-12-31 15:09:35 -06:00
advplyr
021adf3104 Update:Podcast episode table is lazy loaded #1549 2023-12-31 14:51:01 -06:00
advplyr
160c83df4a Update:podcastEpisodes table index added for createdAt column #2073 #2075 2023-12-30 16:14:14 -06:00
advplyr
456bb87a00 Update:Find one library item endpoint sequelize query split into two queries to improve performance #2073 #2075 2023-12-30 12:12:48 -06:00
advplyr
707451309c Merge branch 'master' of https://github.com/advplyr/audiobookshelf 2023-12-29 17:05:40 -06:00
advplyr
269676e8a5 Update:CORS for /cover API endpoint for use in canvas in the mobile apps 2023-12-29 17:05:35 -06:00
Jacob Southard
e4effebc19 Add try/catch to fileutils.getFileMtimeMs 2023-12-29 10:04:59 -06:00
advplyr
fbbceddba8 Merge pull request #2454 from mikiher/socket-authority-close
Add SocketAuthority.close()
2023-12-28 16:32:40 -06:00
advplyr
9a634e0de5 Add JS docs for server stop 2023-12-28 16:32:21 -06:00
mikiher
21d0d43edc Add SocketAuthority.close() 2023-12-27 15:33:33 +02:00
mikiher
3051b963ef Merge branch 'advplyr:master' into binary-manager 2023-12-27 06:44:22 +02:00
advplyr
0d0bdce337 Fix:Fetch RSS feed request accept header #2446 2023-12-25 13:15:55 -06:00
advplyr
bdb5dc8c28 Merge pull request #2445 from mikiher/sigint-handler
Add a SIGINT handler for proper server shutdown
2023-12-25 12:51:22 -06:00
mikiher
209847d98a Add a SIGINT handler for proper server shutdown 2023-12-25 09:25:04 +02:00
advplyr
14f42e15d1 Fix:Book scanner update book series sequence if changed 2023-12-24 11:53:57 -06:00
advplyr
7402e4811d Merge pull request #2444 from jedrus2000/opf-multiple-series-support
Add: OPF file supports multiple series as sequence of : calibre:series and calibre:series_index; including tests
2023-12-24 11:42:06 -06:00
advplyr
6de0465b86 Update opf parser to ignore series with empty content and add tests 2023-12-24 11:41:27 -06:00
Andrzej Bargański
cd7c4baaaf Add: OPF file supports multiple series as sequence of : calibre:series and calibre:series_index; including tests 2023-12-24 00:43:42 +01:00
advplyr
a2db81bf7d Fix share button for year in review short card 2023-12-23 17:13:44 -06:00
advplyr
b376f89ce5 Version bump v2.7.0 2023-12-23 17:05:44 -06:00
advplyr
5633113f25 Update share buttons to not show an error on abort 2023-12-23 16:39:56 -06:00
advplyr
669415cfbf Merge pull request #2431 from pablojimenezmateo/comic-zoom
Add zoom controls to comic reader
2023-12-23 16:18:23 -06:00
advplyr
9f366863a9 Update comic reader buttons for mobile screens, add left scrollBy 2023-12-23 16:16:24 -06:00
advplyr
0d644fe0c9 Add:Year in review banner for user stats page #2373 2023-12-23 15:29:34 -06:00
advplyr
72fa6b8200 Fix:Show cover size widget when audio player is open #2443 2023-12-23 10:50:04 -06:00
advplyr
6d3f1d263a Merge pull request #2442 from JBlond/master
Follow up Translations for 76119445a3
2023-12-23 08:55:28 -06:00
JBlond
47bf9f7836 Follow up Translations for 76119445a3
* Update:Listening sessions table for multi-select, sorting and rows per page
- Updated get all sessions API endpoint to include sorting
- Added sessions API endpoint for batch deleting
2023-12-23 14:42:56 +01:00
advplyr
2738402aac Add:Year in review card for server stats #2373 2023-12-22 17:01:07 -06:00
advplyr
68d36522b1 Update:Listening sessions table UI for mobile 2023-12-21 14:36:51 -06:00
advplyr
24a587b944 Update:Remove playback sessions that are 3s or less on startup 2023-12-21 14:29:36 -06:00
advplyr
76119445a3 Update:Listening sessions table for multi-select, sorting and rows per page
- Updated get all sessions API endpoint to include sorting
- Added sessions API endpoint for batch deleting
2023-12-21 13:52:42 -06:00
advplyr
46ec59c74e Update:Year in review card prevent text overflow for narrator, author and genre #2373 2023-12-21 09:44:37 -06:00
advplyr
2b7122c744 Update:Year stats API endpoint & generate year in review image #2373 2023-12-20 17:18:21 -06:00
Pablo
52f0a5432b feat: enable zoom through the arrow buttons 2023-12-20 11:45:21 +01:00
advplyr
7391b4d0ec Add:User stats API for year stats 2023-12-19 17:19:33 -06:00
Pablo
aa7ee3e8ff fix: zoom buttons were showing when loading the image 2023-12-19 18:45:11 +01:00
Pablo
bef0f3709f feat: add basic zoom functionality to comic reader 2023-12-19 18:39:02 +01:00
advplyr
f33b011847 Merge pull request #2420 from treyg/synology-reverse-proxy-docs
docs: update synology reverse proxy to use the latest DSM settings
2023-12-17 16:42:43 -06:00
Trey Gordon
2d8d11d4da docs: update synology reverse proxy to use the latest DSM settings 2023-12-17 15:56:14 -05:00
advplyr
10b1784f6d Fix:Library search API endpoint /libraries/:id/search to check that query param q is a valid string 2023-12-17 12:23:55 -06:00
advplyr
f2f2ea161c Update:API endpoint /podcasts/feed validates rssFeed URL and uses SSRF req filter 2023-12-17 12:00:11 -06:00
advplyr
dc67a52000 Update:API endpoint /search/podcast throw 400 error if term query param is not supplied 2023-12-17 11:18:21 -06:00
advplyr
05820aa820 Update:API endpoints /podcasts/feed and /podcasts/opml restricted to admin users 2023-12-17 11:17:35 -06:00
advplyr
8966dbbcd1 Fix:Restrict podcast search page to admins 2023-12-17 11:06:03 -06:00
advplyr
cf32819c01 Merge branch 'master' of https://github.com/advplyr/audiobookshelf 2023-12-17 10:41:55 -06:00
advplyr
728496010c Update:/auth/openid/config API endpoint to require admin user and validate issuer URL 2023-12-17 10:41:39 -06:00
advplyr
0a08f47942 Merge pull request #2417 from springsunx/master
Update zh-cn.json
2023-12-15 12:14:15 -06:00
SunX
39ceb02500 Update zh-cn.json 2023-12-15 19:04:56 +08:00
advplyr
4336714248 Merge pull request #2415 from nichwall/docker_compose_update
Added comments to the Docker Compose file
2023-12-15 04:14:16 -06:00
nichwall
1d41904fc3 Added comments to the Docker Compose file 2023-12-14 21:04:37 -07:00
advplyr
fae383a045 Fix:RSS feeds for collections not updating #2414 2023-12-14 15:45:34 -06:00
mikiher
8f7a420cca Fix directory writable check (fs.access not working on Windows) 2023-12-14 09:47:18 +02:00
advplyr
9720ba3eed Merge pull request #2413 from JBlond/master
More german translations
2023-12-13 13:40:36 -06:00
JBlond
d3256d59d5 - Translate more strings
- Add missing least empty line
2023-12-13 20:12:25 +01:00
advplyr
fa5f7ab7a5 Merge pull request #2411 from Nab0y/master
Update Russian localization
2023-12-12 16:57:35 -06:00
Dmitry Naboychenko
6f26fd7238 Update Russian localization 2023-12-12 22:56:05 +03:00
advplyr
6abc0819d9 Merge pull request #2400 from mikiher/bookfinder-improvements
A few BookFinder improvements (including a fix for #2238)
2023-12-10 10:36:21 -06:00
advplyr
b580a23e7e BookFinder formatting update 2023-12-10 10:35:21 -06:00
advplyr
f659c3f11c Fix:Podcast RSS feed request header to include application/rss+xml #2401 2023-12-09 13:51:28 -06:00
mikiher
0282a0521b Sort audible match results by duration difference 2023-12-09 00:33:06 +02:00
advplyr
75637e4b94 Merge pull request #2397 from JBlond/master
Follow up for sso-redirecturi and #2305 and #2333
2023-12-08 15:42:28 -06:00
mikiher
b6c789dee6 Merge branch 'advplyr:master' into bookfinder-improvements 2023-12-08 14:07:25 +02:00
JBlond
8d3d636329 Follow up for sso-redirecturi and #2305 #2333
8f4c65ec8c / 7c9c278cc4 sso-redirecturi
2f6756eddf #2333
2e5822b7c8 #2305
2023-12-08 09:39:04 +01:00
advplyr
6f6395bad7 Only log update binary env path if it was updated 2023-12-07 17:32:06 -06:00
advplyr
b8c8d2a02e Merge pull request #2386 from Sapd/sso-redirecturi
SSO/OpenID: Use a mobile-redirect route (Fixes #2379 and #2381)
2023-12-07 17:12:36 -06:00
advplyr
98104a3c03 Map new translations to other files 2023-12-07 17:05:52 -06:00
advplyr
8f4c65ec8c Merge branch 'master' into sso-redirecturi 2023-12-07 17:04:59 -06:00
advplyr
341a0452da Update auth settings endpoint to return updated flag and show whether updates were made in client toast 2023-12-07 17:01:33 -06:00
mikiher
6afb8de3dd Remove ffbinaries local cache 2023-12-08 00:53:53 +02:00
mikiher
0e62ccc7aa Merge branch 'binary-manager' of https://github.com/mikiher/audiobookshelf into binary-manager 2023-12-07 23:51:33 +02:00
mikiher
09282a9a62 Remove all callbacks and refactor spaghetti code in downloadUrls 2023-12-07 23:49:46 +02:00
advplyr
18b3ab5610 Revert package-lock updates 2023-12-07 15:12:49 -06:00
mikiher
699a658df9 Remove debug printing from libs/ffbinaries 2023-12-07 08:50:45 +02:00
advplyr
b5e255a384 Update:Clean series sequence response from audible provider #2380
- Removes Book prefix
- Splits on spaces and takes first, removes trailing comma
2023-12-06 17:31:36 -06:00
mikiher
67ccd2c1fb Fix test after switching to libs/ffbinaries 2023-12-06 13:45:28 +02:00
mikiher
898b072e68 Merge branch 'advplyr:master' into binary-manager 2023-12-06 09:27:17 +02:00
advplyr
34156af403 Fix:Updating media progress not clearing cache #2392 2023-12-05 17:58:54 -06:00
advplyr
61a0126278 Remove ffbinaries dependency 2023-12-05 17:35:57 -06:00
advplyr
1ce1904c89 Add ffbinaries lib 2023-12-05 17:35:15 -06:00
advplyr
7c9c278cc4 Merge pull request #2293 from Dr-Blank/gujarati-translation
More Gujarati translations
2023-12-05 15:42:05 -06:00
advplyr
450507a812 Map english translations and merge with gu 2023-12-05 15:41:12 -06:00
mikiher
c074c835d4 Remove semicolons from test 2023-12-05 22:18:37 +02:00
mikiher
2e989fbe83 Add BinaryManager 2023-12-05 21:19:17 +02:00
mikiher
b1b325d00b Add ffbinaries dependency 2023-12-05 21:18:30 +02:00
Denis Arnst
cf00650c6d SSO/OpenID: Also fix possible race condition
- We need to define redirect_uri in the callback again, because the global params of passport can change between calls to the first route (ie. if multiple users log in at same time)
- Removed is_rest parameter as requirement for mobile flow (to maximise compatibility with possible oauth libraries)
- Also renamed some variables for clarity
2023-12-05 09:43:06 +01:00
Denis Arnst
e6ab28365f SSO/OpenID: Remove modifying redirect_uri in the callback
The redirect URI will be now correctly set to either /callback or /mobile-redirect in the /auth/openid route
2023-12-05 00:18:58 +01:00
Denis Arnst
80fd2a1a18 SSO/OpenID: Use a mobile-redirect route (Fixes #2379 and #2381)
- Implement /auth/openid/mobile-redirect this will redirect to an app-link like audiobookshelf://oauth
- An app must provide an `redirect_uri` parameter with the app-link in the authorization request to /auth/openid
- The user will have to whitelist possible URLs, or explicitly allow all
- Also modified MultiSelect to allow to hide the menu/popup
2023-12-04 22:36:34 +01:00
advplyr
84160b2f07 Fix:Server crash when user without a password attempts to login with a password #2378 2023-12-02 16:17:52 -06:00
advplyr
fbc2c2b481 Merge pull request #2333 from kieraneglin/ke/feature/upload-auto-fetch-data
Add ability to fetch book data on upload
2023-12-02 15:56:39 -06:00
Kieran Eglin
57a5005197 Addressed feedback changes 2023-12-01 21:42:54 -08:00
Kieran Eglin
9350c5513e Removed unneeded mixin 2023-12-01 15:19:50 -08:00
advplyr
f59516cc6e Fix:Hide change password form when password auth is disabled #2367 2023-12-01 17:10:33 -06:00
advplyr
88078ff813 Fix undefined series string when match has no series, minor ui updates 2023-12-01 16:44:04 -06:00
mikiher
281de48ed4 Fix "et al" cleanup 2023-11-30 21:49:24 +02:00
mikiher
3c6d6bf688 Merge branch 'advplyr:master' into bookfinder-improvements 2023-11-30 21:37:01 +02:00
mikiher
8ac0ce399f Remove "et al[.]" in author cleanup 2023-11-30 21:17:13 +02:00
mikiher
80458e24bd "[un]abridged" in title candidate generation 2023-11-30 21:15:25 +02:00
advplyr
6ab966ee2f Merge pull request #2365 from Sapd/sso-errorhandling
SSO/OpenID: Provide error messages to logs
2023-11-28 16:39:01 -06:00
advplyr
166477ae27 Fix:Narrators page 404 on reload #2359 2023-11-28 16:39:52 -06:00
advplyr
a719065b8d Auto formatting 2023-11-28 16:37:19 -06:00
Denis Arnst
36599a2984 SSO/OpenID: Rename probably misleading message 2023-11-28 21:16:39 +01:00
Kieran Eglin
d9c9289d65 Added error handling; Made querystring helper 2023-11-28 12:11:14 -08:00
Kieran Eglin
e5579b2c33 Improved UI; Added tooltips; Fixed unrelated layout issues 2023-11-28 11:45:44 -08:00
Denis Arnst
618028503b SSO/OpenID: Also Log token header 2023-11-28 20:07:49 +01:00
Kieran Eglin
2f6756eddf Merged parent 2023-11-28 10:21:41 -08:00
Denis Arnst
ad53894ea1 SSO/OpenID: Provide detailed error messages 2023-11-28 17:29:22 +01:00
advplyr
086954fb9c Version bump v2.6.0 2023-11-27 17:41:47 -06:00
advplyr
f243ad14e0 Add help link to oidc guide 2023-11-27 17:10:31 -06:00
advplyr
2e5822b7c8 Merge pull request #2305 from mikiher/nfo-metadata
Add NFO metadata source
2023-11-26 14:49:04 -06:00
advplyr
3d468339b3 Update parse nfo metadata test for description 2023-11-26 14:41:19 -06:00
advplyr
b4c14fc78d Parse NFO comma separated strings remove empty strings 2023-11-26 14:38:25 -06:00
advplyr
d9584174ff Parse NFO trim final parsed description 2023-11-26 14:33:35 -06:00
advplyr
36e00e8d6a Merge master 2023-11-26 13:54:06 -06:00
advplyr
5e69b54eb0 Reverse order of metadata precedence in UI, add translations 2023-11-26 13:45:43 -06:00
advplyr
5a8c60a8bc Merge pull request #2343 from mikiher/caching
Simple API Caching for /libraries* requests
2023-11-26 12:33:54 -06:00
mikiher
3ff41f2b43 Cache HTTP headers and status 2023-11-25 23:49:56 +02:00
advplyr
17cab0d3a8 Merge pull request #2351 from JBlond/master
de translation follow up
2023-11-25 12:18:08 -06:00
JBlond
0fac9e367d de translation follow up
for 2e06ae01a1
2023-11-25 19:10:26 +01:00
advplyr
bf0bcf8967 Merge pull request #2336 from JBlond/master
de language translation follow up
2023-11-25 11:31:59 -06:00
advplyr
2e06ae01a1 Merge pull request #2326 from lkiesow/hide-dev-logs
Allow enabling dev logs
2023-11-25 10:36:50 -06:00
mikiher
288a32cc1e Merge branch 'caching' of https://github.com/mikiher/audiobookshelf into caching 2023-11-25 08:14:54 +02:00
mikiher
26fc3a1966 Remove currently unused time measurement utils 2023-11-25 08:14:45 +02:00
advplyr
9d257ebecd Update:Home page shelf bulk items added socket event only adds new items to the recently added shelf instead of refreshing all shelves #2323 2023-11-24 15:36:42 -06:00
advplyr
1a046a9bcb Merge branch 'master' into caching 2023-11-24 14:38:27 -06:00
advplyr
7a9c869ac5 Ignore sequelize hooks when updating user lastSeen on socket authentication 2023-11-24 14:27:32 -06:00
advplyr
572fb0993c Rename ApiCacheManager to add .js file extension 2023-11-24 14:20:14 -06:00
advplyr
9beee3ed65 Fix:Change password api endpoint 2023-11-23 15:14:49 -06:00
mikiher
ab19e25586 Remove unnecessary timing measurements 2023-11-23 09:56:37 +02:00
mikiher
07d7d16418 Use a single router.get for API cache middleware 2023-11-23 09:55:55 +02:00
mikiher
5e1e748c71 Add ApiCacheManager unit test 2023-11-23 09:53:52 +02:00
advplyr
6651ad0d45 Update:Added translation strings for OIDC auth 2023-11-22 12:55:01 -06:00
advplyr
288beae874 Update:OIDC auth auto launch setting description to include manual override path 2023-11-22 12:38:11 -06:00
advplyr
32ce771911 Allow cors while in development 2023-11-22 12:37:18 -06:00
mikiher
d944ecaa21 Merge branch 'caching' of https://github.com/mikiher/audiobookshelf into caching 2023-11-22 19:10:29 +02:00
mikiher
5aeb6ade72 Merge branch 'caching' of https://github.com/mikiher/audiobookshelf into caching 2023-11-22 19:00:11 +02:00
mikiher
107b4b83c1 Add cache middleware to most /libraries get requests 2023-11-22 18:40:42 +02:00
JBlond
0d61e29ecf de language translation follow up for 27497451d9 2023-11-21 20:30:48 +01:00
mikiher
781d4f570f Add test for parseNfoMetadata 2023-11-21 09:12:37 +02:00
mikiher
a4d4f1bc2e Merge branch 'advplyr:master' into nfo-metadata 2023-11-21 09:09:12 +02:00
advplyr
048e27f03f Update:Openid auth endpoint sets the mobile flag on session to be used in the callback
Co-authored-by: Denis Arnst <git@sapd.eu>
2023-11-20 15:41:38 -06:00
Kieran Eglin
8c434703fb Added computed metadata check to UI dropdown 2023-11-20 09:18:50 -08:00
Kieran Eglin
3cc900ffbf Adds fetching book data on upload 2023-11-20 08:51:00 -08:00
Lars Kiesow
7b6aa3ba5a Allow enabling dev logs
This patch allows users to enable dev logs on production systems by
setting the `HIDE_DEV_LOGS` environment variable.

Before, you could only use this on a non-production environment. On
production, the logs would be disabled. This patch changes the behavior
and uses the `NODE_ENV` only as default. On production they are disabled
if `HIDE_DEV_LOGS` is undefined but can be enabled by setting
`HIDE_DEV_LOGS=0` on dev, they are enabled if undefined, but can be
disabled by setting `HIDE_DEV_LOGS=1`.
2023-11-19 21:00:54 +01:00
advplyr
aa933df525 Update oidc redirect_uri to check x-forwarded-proto header for proxies 2023-11-19 14:00:39 -06:00
advplyr
a0f137936d Merge pull request #2325 from lkiesow/milliseconds
Add milliseconds to logging
2023-11-19 13:41:10 -06:00
advplyr
dcbfc963c1 Update protocol for redirect_uri in openid strategy to work for reverse proxies 2023-11-19 13:38:09 -06:00
Lars Kiesow
91fa78d740 Add milliseconds to logging
This patch adds milliseconds to the time string used for logging. This
helps when debugging some timing issues and should have no real negative
side effect.
2023-11-19 20:36:04 +01:00
advplyr
89eb857c14 Fix initialize openid auth strategy 2023-11-19 12:57:17 -06:00
advplyr
e07d17c472 Merge pull request #1636 from lukeIam/auth_passportjs
Integrate passportjs for muti-strategy authentication and SSO
2023-11-19 11:46:52 -06:00
advplyr
4c2c320b9d Remove global CORS for api endpoints and setup temp CORS check for ebook endpoint 2023-11-19 11:32:48 -06:00
advplyr
56c574c928 Update package-lock 2023-11-19 08:29:58 -06:00
advplyr
d2aea86957 Merge pull request #2300 from mikiher/bookfinder-testing-mocha
Bookfinder.js unit testing with mocha
2023-11-18 13:55:18 -06:00
advplyr
80e061115f Add remove semicolons to .vscode settings, update BookFinder.test formatting 2023-11-18 13:41:08 -06:00
mikiher
4299627f5f Add lru-cache dependency 2023-11-17 08:54:16 +02:00
mikiher
6a722102c5 Use ApiCacheManager & timing middleware 2023-11-17 08:49:40 +02:00
mikiher
f22f3361d5 Add timing utils 2023-11-17 08:48:09 +02:00
mikiher
4dec8c265d Add ApiCacheManager 2023-11-17 08:47:40 +02:00
mikiher
d990e5b909 Add NFO metadata source 2023-11-12 13:30:23 +00:00
advplyr
fb48636510 Openid auth failures redirect to login page with error message.
Remove remaining google oauth server settings
2023-11-11 13:10:24 -06:00
advplyr
1ad6722e6d Remove google-oauth passport strategy 2023-11-11 11:29:59 -06:00
advplyr
557ef2ef79 Update /auth/openid endpoints for correct PKCE handling
- Provide error handling for /auth/openid
- Add session.mobile inside /auth/openid
- Proper PKCE handling for /auth/openid/callback
- redirect_uri handling for the token url in /auth/openid/callback

Co-authored-by: Denis Arnst <git@sapd.eu>
2023-11-11 10:52:05 -06:00
advplyr
cff2caa07a Update:Rename podcast search page to add #2301 2023-11-10 16:32:14 -06:00
advplyr
237fe84c54 Add new API endpoint for updating auth-settings and update passport auth strategies 2023-11-10 16:11:51 -06:00
advplyr
078cb0855f Merge branch 'master' into auth_passportjs 2023-11-10 07:26:07 -06:00
mikiher
ecba67da6d Add Istanbul coverage (nyc) 2023-11-10 10:02:02 +00:00
mikiher
ea05e1f559 Remove test/ from .gitigore (now contains unit tests) 2023-11-10 09:58:30 +00:00
advplyr
d3a55c8b1a Merge branch 'master' of https://github.com/advplyr/audiobookshelf 2023-11-09 16:36:37 -06:00
advplyr
d6b17678ec Update:Persist soft/hard delete checkbox option #1689 2023-11-09 16:36:28 -06:00
advplyr
33e287a543 Update:Persist show full path option for tables #2285 2023-11-09 16:26:49 -06:00
advplyr
08f045a02b Merge pull request #2299 from burghy86/patch-11
Update it.json
2023-11-09 16:15:16 -06:00
mikiher
e8c14dbb58 Test BookFinder.js using mocha 2023-11-09 19:58:51 +00:00
burghy86
bf48eee705 Update it.json
arrange the additional lines.
how the hell did we get to over 700 lines in less than two months?
2023-11-09 15:46:25 +01:00
advplyr
8f4c75ff2b Update:Author card books translation string #2284 2023-11-08 16:28:05 -06:00
advplyr
ee75d672e6 Matching user by openid sub, email or username based on server settings. Auto register user. Persist sub on User records 2023-11-08 16:14:57 -06:00
advplyr
e140897313 Add match existing user by and auto register settings and UI 2023-11-08 14:45:29 -06:00
mikiher
d1671f0ddc Cleanup commented out tests 2023-11-08 16:37:12 +00:00
mikiher
2730486ba5 Add tests for AuthorCandidates and search() in BookFinder 2023-11-08 16:24:08 +00:00
mikiher
49e4515785 Add stripRedudantSpaces 2023-11-08 16:21:20 +00:00
mikiher
819c524f51 Pass audnexus to AuthorCandidates constructor directly 2023-11-08 16:19:24 +00:00
Dr-Blank
6d968f9044 Update gu.json 2023-11-06 18:16:03 -05:00
Dr-Blank
23fa9e8d7f Update gu.json 2023-11-06 18:15:18 -05:00
Dr-Blank
59a428d549 more gu translations 2023-11-06 18:10:57 -05:00
advplyr
70c213ad22 Merge pull request #2291 from brianjaustin/fix/collection-duration
Hide collection duration if 0
2023-11-06 16:21:25 -06:00
advplyr
aad6402fdb Update client/components/tables/collection/BookTableRow.vue 2023-11-06 16:18:35 -06:00
advplyr
5ce1cda2d0 Merge pull request #2283 from mikiher/watcher-single-file-update
Fix handling of single media file updates
2023-11-06 16:08:23 -06:00
mikiher
ba60fc7581 Add tests for TitleCanidates 2023-11-06 05:33:06 +00:00
Brian Austin
0344e8cf1b Hide collection duration if 0 2023-11-05 19:13:26 -05:00
advplyr
f840aa80f8 Add button to populate openid URLs using the issuer URL 2023-11-05 14:11:37 -06:00
advplyr
c17540e191 Add app and serverVersion properties to response from /status 2023-11-05 13:06:26 -06:00
advplyr
309ef807ab Update /auth/openid endpoint to work with PKCE from mobile
Co-authored-by: Denis Arnst <git@sapd.eu>
2023-11-05 13:05:16 -06:00
advplyr
61e05e92a8 Add Swedish language option 2023-11-05 13:05:16 -06:00
Gustav Almstrom
1e5d6a5d52 Added swedish translation of strings 2023-11-05 13:05:15 -06:00
advplyr
ff831678e8 Merge pull request #2288 from ScuttleSE/master
Added swedish translation of strings
2023-11-05 10:18:45 -06:00
advplyr
910be21e93 Add Swedish language option 2023-11-05 10:16:40 -06:00
mikiher
89055f8655 Remove unnecessary includesAuthorDiff from sorting 2023-11-05 16:14:26 +00:00
Gustav Almstrom
b9ccc28baa Added swedish translation of strings 2023-11-05 16:51:45 +01:00
mikiher
5a3d450482 Refactor diff declarations in title candidate sorting 2023-11-05 15:13:42 +00:00
mikiher
047e7a72f2 Make position an internal property of titleCandidates 2023-11-05 14:56:20 +00:00
mikiher
3a9d09ea63 Add jest to dev dependencies 2023-11-05 14:33:56 +00:00
mikiher
ee3d3808ef Refactor removing author from title candidate 2023-11-05 14:31:36 +00:00
mikiher
8f5a6b7c95 Move utility functions to module scope 2023-11-05 14:17:26 +00:00
advplyr
840811b464 Replace passport openidconnect plugin with openid-client, add JWKS and logout URL server settings, use email and email_verified instead of username 2023-11-04 15:36:43 -05:00
mikiher
567e1c46db Fix handling of single mefia file updates 2023-11-04 11:06:54 +00:00
advplyr
cfe0c2a986 Merge branch 'master' into auth_passportjs 2023-11-03 08:29:05 -05:00
advplyr
68546acf2a Merge branch 'master' of https://github.com/advplyr/audiobookshelf 2023-11-03 07:08:04 -05:00
advplyr
5220361151 Fix:Podcast episode cron not adding/removing library items correctly #2277 2023-11-03 07:07:58 -05:00
advplyr
076e01dbfe Merge pull request #2276 from Plazec/patch-1
Update cs.json
2023-11-02 14:19:45 -05:00
advplyr
f15ed08b6a Merge branch 'master' into auth_passportjs 2023-11-02 13:55:16 -05:00
advplyr
828b96b2d9 Add server settings for changing openid button text and auto launching openid 2023-11-02 13:55:01 -05:00
Plazec
3100437651 Update cs.json
Corrected some translation errors and made the translation more consistent.
2023-11-02 19:44:32 +01:00
advplyr
20880a6bf6 Merge pull request #2274 from radekmuhlfeit2/patch-1
Create cs-CZ.json
2023-11-01 15:48:05 -05:00
advplyr
2eff69fe9f Add czech translation to dropdown 2023-11-01 15:42:54 -05:00
advplyr
5f035db0a9 Rename cs-CZ.json to cs.json 2023-11-01 15:29:58 -05:00
radekmuhlfeit2
e4a7e9d6b5 Create cs-CZ.json
Czech strings.
2023-11-01 20:21:45 +01:00
advplyr
ab14b561f5 Merge master 2023-11-01 08:58:48 -05:00
advplyr
5ce4734a70 Merge pull request #2272 from clement-dufour/master
Add support for the old Apple Podcasts iOS app
2023-11-01 07:43:48 -05:00
clement.dufour
1ae2089253 Update:Add cover file extension in RSS feeds 2023-11-01 12:11:24 +01:00
clement.dufour
3c21e9d413 Update:Simpler content URL in RSS feeds 2023-11-01 12:10:44 +01:00
advplyr
9616d99640 Fix:Crash when matching with author names ending in ??? by escaping regex strings #2265 2023-10-30 16:35:41 -05:00
advplyr
2ef11e5ad0 Version bump v2.5.0 2023-10-29 12:58:00 -05:00
advplyr
27497451d9 Add:Ereader device setting to set users that have access #1982 2023-10-29 11:28:34 -05:00
advplyr
94fd3841aa Update:Notification widget shows green dot indicating unseen completed tasks 2023-10-29 09:20:50 -05:00
advplyr
225dcdeafd Fix:RSS feed parser for episode metadata tags that have attributes #1996 2023-10-28 16:11:15 -05:00
advplyr
2c9f2e0d68 Fix podcast episode rss feed search showing all episodes are downloaded 2023-10-28 15:54:19 -05:00
advplyr
a9f74ace5a Merge pull request #2255 from MxMarx/added-search-epubs
Search epub text
2023-10-28 14:35:40 -05:00
advplyr
6dc5b58d8e Update TOC to not close when clicking on it 2023-10-28 14:32:11 -05:00
advplyr
88c794e710 Fix:Open RSS feed for series & collections respect prevent indexing option #2047 2023-10-28 13:45:06 -05:00
advplyr
61f2fb28e0 Add:Help icon buttons for libraries, rss feeds and users config pages, table add new buttons updated 2023-10-28 13:27:53 -05:00
advplyr
1df4dca4bb Merge pull request #2246 from MxMarx/expand-cover-images
show a modal with cover images when clicked
2023-10-27 16:55:53 -05:00
advplyr
6278bb8665 Move raw cover preview to a separate global component, fix item page cover overlay show on hover 2023-10-27 16:51:44 -05:00
MxMarx
4229cb7fb6 Added a method to unwrap the chapter list 2023-10-27 00:35:28 -07:00
MxMarx
5778200c8f Make epubs searchable 2023-10-27 00:14:46 -07:00
advplyr
5c1c511718 Merge pull request #2249 from mikiher/watcher-update-api
Add API to update a path on a watched library folder
2023-10-26 16:45:26 -05:00
advplyr
f9c4dd2457 Update watcher function calls, add js docs 2023-10-26 16:41:54 -05:00
advplyr
3bccd52196 Merge branch 'master' into watcher-update-api 2023-10-26 16:33:48 -05:00
advplyr
0c23da7b02 Add missing translations 2023-10-26 16:31:47 -05:00
advplyr
d577cae393 Merge pull request #2253 from MxMarx/add-epub-font-selector
Option to change the font family in epub viewer
2023-10-26 16:30:29 -05:00
MxMarx
24228b4424 Option to change the font family in epub viewer 2023-10-26 02:15:08 -07:00
advplyr
8dc4490169 Fix:Watcher waits for files to finish transferring before scanning #1362 #2248 2023-10-25 16:53:53 -05:00
advplyr
ef1cdf6ad2 Fix:Only show authors with books for users #2250 2023-10-24 17:04:54 -05:00
mikiher
e054b9a54c Add API to update a path on a watched library folder 2023-10-24 13:35:43 +00:00
MxMarx
32616aa441 show a modal with cover images when clicked 2023-10-23 20:37:51 -07:00
advplyr
0ee6336b02 Merge pull request #2245 from mikiher/watcher-fixes
Fix incorrect subpath checks in server/watcher.js
2023-10-23 17:28:44 -05:00
advplyr
9a477a9270 Add jsdocs 2023-10-23 17:28:59 -05:00
mikiher
976ae502bb Fix incorrect subpath checks 2023-10-23 21:48:34 +00:00
advplyr
c4c12836a4 Fix:Version in bottom left of siderail overlapping buttons #2195 2023-10-22 17:04:45 -05:00
advplyr
5a70c0d7be Fix:Authors page books hide radio button on hover 2023-10-22 16:40:12 -05:00
advplyr
60a80a2996 Update:Remove support for metadata.abs, added script to create metadata.json files if they dont exist 2023-10-22 15:53:05 -05:00
advplyr
ce88c6ccc3 Scanner metadata order of precedence description label, link to guide, add translations 2023-10-22 12:58:05 -05:00
advplyr
b42edfe7a7 Book duration shown on match page compares minutes #1803 2023-10-22 07:10:52 -05:00
advplyr
0cbcfbd273 Merge pull request #2233 from Hallo951/master
Update de.json
2023-10-21 17:04:04 -05:00
Hallo951
8ecec93e67 Update de.json 2023-10-21 22:33:31 +02:00
advplyr
49403771c9 Update:Quick match all for library to use task instead of toast, remove scan socket events 2023-10-21 13:53:00 -05:00
advplyr
50215dab9a Hide library modal tools tab for new libraries 2023-10-21 13:00:41 -05:00
advplyr
58b9a42c84 Add:Scan button on libraries table 2023-10-21 12:56:35 -05:00
advplyr
d7264f8c22 Update watcher scanner to show task notification 2023-10-21 12:25:45 -05:00
advplyr
bef6549805 Update:Replace library scan toast with task manager #1279 2023-10-20 17:46:18 -05:00
advplyr
6f65350269 Update:JSDocs for task manager 2023-10-20 16:39:32 -05:00
advplyr
5644a40a03 Update:Add missing tranlations #2217 2023-10-20 16:08:57 -05:00
advplyr
920ddf43d7 Remove unused old model functions 2023-10-19 17:20:12 -05:00
advplyr
4a5f534a65 Update:Description width of item page to match width of tables 2023-10-19 16:30:33 -05:00
advplyr
24031f12db Merge pull request #2229 from JBlond/master
Translate new string for DE language.
2023-10-19 16:17:09 -05:00
JBlond
22361d785d Translate new string for DE language. 2023-10-19 15:22:00 +02:00
advplyr
8c5ce6149f Fix:Aspect ratio of authors image on authors landing page #2227 2023-10-18 17:10:53 -05:00
advplyr
516b0b4464 Fix:Book scanner set item as missing if no media files are found #2226 2023-10-18 17:02:15 -05:00
advplyr
d22052c612 Update UI for library tools tab 2023-10-18 16:47:56 -05:00
advplyr
b4ce5342c0 Add:Tools tab on library modal, api endpoint to remove all metadata files from library item folders 2023-10-17 17:46:43 -05:00
advplyr
0d5792405f Fix:Podcast episodes store RSS feed guid so they can be matched if the RSS feed changes the episode URL #2207 2023-10-16 17:47:44 -05:00
advplyr
48a590df4a Fix:Authors page description shows line breaks #2218 2023-10-16 17:08:50 -05:00
advplyr
c264332994 Fix:Scanner detecting library item folder renames #1161 2023-10-15 12:55:22 -05:00
advplyr
cdd740015c Add:Danish translations 2023-10-15 08:23:22 -05:00
advplyr
07ad81969c Update:Scanner recognizes asin in book folder names #1852 2023-10-14 15:04:16 -05:00
advplyr
dcdd4bb20b Update:HLS router request validation, smooth out transcode reset logic 2023-10-14 12:50:48 -05:00
advplyr
c98fac30b6 Update:Validate image URI content-type before writing image file 2023-10-14 10:52:56 -05:00
advplyr
1f8372f5e5 Merge pull request #2215 from springsunx/master
Update zh-cn.json
2023-10-14 09:37:49 -05:00
SunX
616ecf77b0 Update zh-cn.json
Update zh-cn.json
2023-10-14 20:30:27 +08:00
advplyr
656c81a1fa Update:Remove image path input from author modal, add API endpoints for uploading and removing author image 2023-10-13 17:37:37 -05:00
advplyr
290a377ef9 Update:Remove local cover path input & replace with url from web input, include SSRF request filter 2023-10-13 16:33:47 -05:00
advplyr
05731c9f72 Remove unused css parser lib 2023-10-13 14:10:54 -05:00
advplyr
3108bc5ccc Fix:Server crash when removing last item from a playlist #2211 2023-10-13 13:33:15 -05:00
advplyr
e687a3403e Fix:Cleaning up orphan streams on server init #2209 2023-10-11 17:05:56 -05:00
advplyr
753ae3d7dc Fix:Server crash when downloading single file library items #2199 2023-10-10 17:51:52 -05:00
advplyr
c9a2fdcb29 Library scanner saves last scan info including metadata precedence. Remove force re-scan 2023-10-09 17:48:21 -05:00
advplyr
f84634e978 Fix OPF file scanner series sequence, book scanner check for mismatched audio file found lengths 2023-10-09 17:09:36 -05:00
advplyr
89821b91b0 Podcast scanner refactor/cleanup 2023-10-09 16:41:43 -05:00
advplyr
347b49f564 Update:Remove scanner settings, add library scanner settings tab, add order of precedence 2023-10-08 17:10:43 -05:00
advplyr
5ad9f507ba Merge pull request #2186 from mikiher/Fuzzy-Matching-Continued
Fuzzy matching continued
2023-10-08 10:39:11 -05:00
mikiher
f8f555b4b6 Remove some unused code in AuthorCandidates.add 2023-10-07 21:30:37 +00:00
advplyr
786df450e5 Merge branch 'master' into Fuzzy-Matching-Continued 2023-10-07 11:52:04 -05:00
advplyr
db9d5c9d43 Add:Support for pasting semicolon separated strings in multi select inputs #1198 2023-10-06 16:52:12 -05:00
advplyr
b447cf5c1c Fix:Handle non-ascii characters in global search by not lowercasing in query #2187 2023-10-05 17:00:40 -05:00
jfrazx
4e6b75d650 fix; HTTP/429 when requesting authors information, resolves #1570 2023-10-05 13:48:55 -07:00
mikiher
f44b7ed1d0 [enhancement] If no valid authors, use clean author field 2023-10-05 18:41:18 +00:00
mikiher
b0b7a0a618 [enhancement] Reduce spurious matches in validateAuthor 2023-10-05 18:27:52 +00:00
mikiher
bf9f3895db [enhancement] Treat underscores as title part separators 2023-10-05 17:53:54 +00:00
mikiher
f3555a12ce [enhancement] Handle initials in author normalization 2023-10-05 14:50:16 +00:00
mikiher
b2acdadcea [enhancement] Added a couple title transformers 2023-10-05 14:29:40 +00:00
mikiher
9eff471afa [enhancement] AuthorCandidates, author validation 2023-10-05 12:05:30 +00:00
mikiher
8979586404 [enhancement] Improve candidate sorting 2023-10-05 10:28:55 +00:00
advplyr
bfe514b7d4 Add:Email inputs for users 2023-10-04 17:05:12 -05:00
mikiher
752bfffb11 [enhamcement] Only add title candidate before and after all transforms 2023-10-04 14:53:12 +00:00
mikiher
10f5bc8cbe [cleanup] Make original title/author check with more readable 2023-10-04 05:26:16 +00:00
advplyr
565ff36d4e Merge pull request #2175 from MarshDeer/master
Completed Spanish translation and corrected typo in an English string
2023-10-03 17:18:07 -05:00
advplyr
401bd91204 Add:Show current book duration on match page as compared with book listed #1803 2023-10-03 17:16:49 -05:00
mikiher
5d7c197c89 [fix] Add back toLowerCase to cleanAuthor/Title (required by other uses) 2023-10-03 19:43:37 +00:00
MarshDeer
8e97be8ef4 Spanish translation completed 2023-10-02 19:42:42 -03:00
MarshDeer
733ad52684 Typo correction 2023-10-02 19:42:25 -03:00
advplyr
5ccf0df308 Merge branch 'master' of https://github.com/advplyr/audiobookshelf 2023-10-02 17:09:19 -05:00
advplyr
a3a8937ba3 Fix:Crash when searching for cover without an author #2174 2023-10-02 17:09:12 -05:00
advplyr
2662e8f715 Merge branch 'master' into auth_passportjs 2023-10-02 16:21:47 -05:00
advplyr
28b2005068 Merge pull request #2171 from Alistair1231/master
make force transcode apply to all "ffmpeg error 1"
2023-10-02 08:35:23 -05:00
advplyr
7c9631c1b0 Update server/objects/Stream.js 2023-10-02 08:34:56 -05:00
Alistair1231
4352989242 update comment to include second issue that is adressed by change 2023-10-02 09:30:57 +02:00
Alistair Bahr
73bb73a04a make force transcode apply to all "ffmpeg error 1" 2023-10-02 09:25:34 +02:00
advplyr
20a1d40d99 Fix:Set date properly on local playback sessions #2168 2023-10-01 12:44:52 -05:00
advplyr
e10b178565 Fix:Crash on failed scanner find covers #2164 2023-10-01 09:03:01 -05:00
mikiher
46b0b3a6ef [cleanup] Refactor candidates logic to separate class 2023-10-01 08:42:47 +00:00
advplyr
f2aed08d51 Version bump v2.4.4 2023-09-30 16:04:06 -05:00
advplyr
c2c8cf919e Fix:Bad backup causing other backup files to not be displayed #1961 2023-09-30 16:01:10 -05:00
advplyr
9ebe23e91b Merge pull request #2162 from 92Kev/master
Update nl.json
2023-09-30 15:25:36 -05:00
advplyr
3d96749d38 Fix:Downloading podcasts with watcher causing duplicate episodes #2122 2023-09-30 15:12:37 -05:00
advplyr
1dc369180c Fix:Home page recent series shelf respect hide single book series library setting #2134 2023-09-30 14:32:40 -05:00
advplyr
8d3a326216 Fix:Newest episodes home page shelf #2119 2023-09-30 14:19:10 -05:00
mikiher
1d3ad38187 [cleanup] refactor OpenLib sort into getOpenLibResult 2023-09-30 18:08:03 +00:00
advplyr
1b22205f74 Update:Add libraryItems table index to improve performance #2073 2023-09-30 12:39:16 -05:00
92Kev
826fee4590 Update nl.json
Added Dutch translations where they were missing
2023-09-30 16:42:28 +02:00
advplyr
f0929729a3 Fix:Adding new podcast with auto download episodes not setting the schedule #2160 2023-09-29 14:52:04 -05:00
advplyr
98ed2e01cc Fix:Scanner overwriting metadata when metadata file is not stored with items #2155 2023-09-28 17:23:52 -05:00
advplyr
ed82a5aa19 Update:Library folder path editable in library edit modal until submit #2150 2023-09-27 17:50:32 -05:00
advplyr
d7b2476473 Merge pull request #2146 from JBlond/master
Update German translation strings.
2023-09-26 17:18:18 -05:00
JBlond
ee162f468a Adjust Message according to https://de.wikipedia.org/wiki/Durchkopplung#Fremdsprachliche_Begriffe
as suggested by https://github.com/vanto
2023-09-26 09:51:34 +02:00
advplyr
0d5a30b214 Update JWT auth extractors, add state in openid redirect, add back cors for api router 2023-09-25 17:05:58 -05:00
JBlond
cb6678fa71 Update German translation strings. 2023-09-25 11:16:57 +02:00
advplyr
10011d3886 Add:Remove option for authors & show authors with 0 books on authors page #2124 2023-09-24 17:06:32 -05:00
advplyr
0367d9ec2a Fix:OPF files creating empty tags and genres #2142 2023-09-24 16:15:42 -05:00
advplyr
26f520ca4a Fix:Listening sessions page showing filtered user for open listening sessions #2136 2023-09-24 15:59:29 -05:00
advplyr
e282142d3f Add authentication page in config, add /auth-settings GET endpoint, remove authOpenIDCallbackURL server setting 2023-09-24 15:36:35 -05:00
advplyr
7ba10db7d4 Update login button openid and google urls 2023-09-24 12:39:38 -05:00
advplyr
f6de373388 Update /status endpoint to return available auth methods, fix socket auth, update openid to use username instead of email 2023-09-24 12:36:36 -05:00
advplyr
8683fc9fe4 Fix:Show series name when collapsing sub-series #2140 2023-09-23 14:38:30 -05:00
advplyr
fd0920c808 Fix:Updating RSS feeds with new episodes #2139 2023-09-23 14:27:13 -05:00
advplyr
9922294507 Fix setting tokenSecret on init 2023-09-23 13:42:28 -05:00
advplyr
f42ab45e1b Update passwordless root user check to user user.type instead of user.id 2023-09-23 13:30:28 -05:00
lukeIam
7a131880e5 show/hide of login buttons 2023-09-23 17:02:27 +01:00
advplyr
a446fc0f20 Merge pull request #2138 from husjon/add-norwegian_translation
Added Norwegian translation
2023-09-23 09:37:55 -05:00
Jon Erling Hustadnes
202c26acf5 translation progress 2023-09-23 16:31:23 +02:00
Jon Erling Hustadnes
f0b2acb4c7 added 'no' to languageCodeMap i18n.js 2023-09-23 16:29:54 +02:00
advplyr
102c90c4e8 Merge pull request #2133 from mfcar/mf/backup
Add more information to the backup page
2023-09-22 16:56:12 -05:00
advplyr
7c484d8e96 Remove commented out backup location 2023-09-22 16:53:59 -05:00
advplyr
e9f0f7d1bc Add LabelBackupLocation translation strings 2023-09-22 16:51:35 -05:00
advplyr
f37ab53eff Update get all backups api endpoint to return backupLocation, display location above backup settings 2023-09-22 16:49:01 -05:00
advplyr
97b0b98605 Merge pull request #2102 from selfhost-alt/sqlite-query-logging
Add ability to enable DEV logs of Sqlite queries
2023-09-22 16:17:32 -05:00
advplyr
1ab34fa77f Update server/Database.js 2023-09-22 16:14:12 -05:00
advplyr
b64ecc7c6f Update server/Database.js 2023-09-22 16:14:00 -05:00
advplyr
a11fc214e9 Merge pull request #2099 from mikiher/Fuzzy-Matching
Fuzzy Matching V1
2023-09-22 16:07:17 -05:00
advplyr
61c48602e8 Add jsdocs to BookFinder search functions 2023-09-22 16:03:41 -05:00
Jon Erling Hustadnes
452d59dcf6 copy of en-us.json to no.json 2023-09-22 12:31:38 +02:00
advplyr
5e976c08af Update cover API endpoint to only load necessary data from DB #2073 2023-09-21 16:57:48 -05:00
advplyr
f1cce76e2c Merge pull request #2121 from Hallo951/master
Update de.json
2023-09-20 17:32:03 -05:00
advplyr
872fba1103 Merge pull request #2129 from mikiher/URI-encoding-2
Use encodeURIComponent for text inputs in Match.vue
2023-09-20 16:49:18 -05:00
mfcar
944f5950ca File missing 2023-09-20 22:36:30 +01:00
mfcar
bfa87a2131 Add a way to see the backup location 2023-09-20 22:33:58 +01:00
lukeIam
0e75c80627 prepare show/hide of login buttons 2023-09-20 19:45:32 +01:00
lukeIam
2c25f64652 Add /auth_methods route 2023-09-20 19:16:08 +01:00
lukeIam
45cf00bd04 fix openid + jwt auth 2023-09-20 19:06:16 +01:00
lukeIam
f6113e85c7 cookie lifetime 2023-09-20 18:48:57 +01:00
lukeIam
2c90bba774 small refactorings 2023-09-20 18:37:55 +01:00
lukeIam
51b0750a3f Merge remote-tracking branch 'origin/master' into auth_passportjs 2023-09-20 17:34:29 +01:00
mikiher
6eab985b1e Use encodeURIComponent for text inputs 2023-09-20 11:25:21 +00:00
mikiher
81a9b8d158 Merge branch 'advplyr:master' into Fuzzy-Matching 2023-09-20 13:12:18 +03:00
mfcar
9519f6418d Now, whenever someone requests a backup file, it will automatically suggest a default file name for the downloaded file. 2023-09-19 22:37:57 +01:00
advplyr
9967a5dc66 Fix:Set ebookFormat on scans #2126 2023-09-19 15:42:38 -05:00
Hallo951
9382055bf2 Update de.json 2023-09-19 09:39:46 +02:00
advplyr
604f52762b Merge pull request #2120 from itzexor/x-accel-encode
[server] x-accel: encode all paths to URIs
2023-09-18 17:51:53 -05:00
advplyr
ae88a4d20a Fix:Matching a library with no items not removing library scan #2118 2023-09-18 17:38:45 -05:00
advplyr
b5a27226cc Fix:Misleading log on cover manager 2023-09-18 16:45:30 -05:00
advplyr
2c71324381 Fix:Book re-scan properly checking if existing coverPath exists #2110 2023-09-18 16:43:43 -05:00
James Ross
207ba7ec8e x-accel: encode all paths to URIs
updates util function  encodeUriPath to use node:url with a file:// path
prefix, and updates all instances x-accel redirection to use this helper
util instead of sending unencoded paths into the header.
2023-09-18 13:08:19 -07:00
advplyr
e56b8edc0a Version bump 2.4.3 2023-09-17 16:13:19 -05:00
advplyr
8ab0a0a14d Update personalized shelves logs to dev logs 2023-09-17 16:09:21 -05:00
advplyr
4e01722ba6 Merge pull request #2103 from selfhost-alt/faster-scan-for-empty-series
Scan for empty book series more efficiently
2023-09-17 15:54:33 -05:00
advplyr
87eaacea22 Fix empty podcast and empty book queries when cleaning db on init 2023-09-17 15:53:25 -05:00
advplyr
3ad4f05449 Merge branch 'master' into faster-scan-for-empty-series 2023-09-17 15:47:06 -05:00
advplyr
817be40959 Merge pull request #2101 from selfhost-alt/fix-parse-full-name-typo
Fix typo in fixParsedNameCase
2023-09-17 15:43:25 -05:00
advplyr
d18592eaeb Fix:Duplicate series and authors being added on matches and scans #2106 2023-09-17 15:29:39 -05:00
advplyr
0aae672e19 Fix:Scanner purge cover cache when extracting from audio file 2023-09-17 14:53:25 -05:00
lukeIam
0a6cd89090 Allow rest mode login (?isRest=true) 2023-09-17 18:42:42 +01:00
advplyr
cfd9a01da7 Fix:Server crash when removing item from playlist #2115 2023-09-17 12:40:13 -05:00
lukeIam
942aa93f57 Fix: local login not possible 2023-09-16 19:45:04 +00:00
lukeIam
763c0f4a3d add missing await 2023-09-16 18:51:29 +00:00
lukeIam
7af3033f8d Fix: ci error - no token sercret 2023-09-16 18:42:48 +00:00
lukeIam
91d8451ab3 Remove log messages 2023-09-16 18:22:23 +00:00
lukeIam
6aaf3f0f02 Fix bug with undefined property 2023-09-16 18:22:11 +00:00
lukeIam
226a774ab9 Merge remote-tracking branch 'origin/master' into auth_passportjs 2023-09-16 18:02:51 +00:00
Selfhost Alt
19cf3bfb9f Fix query to actually return empty series 2023-09-15 13:32:21 -07:00
mikiher
67bbe21513 Make quick-match more conservative 2023-09-15 09:24:19 +00:00
Selfhost Alt
b668c6e37a Remove stray quote 2023-09-14 23:04:47 -07:00
Selfhost Alt
71762ef837 Newline before printing query 2023-09-14 23:01:40 -07:00
Selfhost Alt
b1524d245e Add ability to enable DEV logs of Sqlite queries 2023-09-14 22:52:43 -07:00
Selfhost Alt
8b39b01269 Scan for empty book series more efficiently 2023-09-14 22:35:33 -07:00
Selfhost Alt
f7849d2956 Fix typo in fixParsedNameCase 2023-09-14 22:12:22 -07:00
mikiher
ac746f199b Fuzzy Matching V1 2023-09-14 21:32:20 +00:00
lukeIam
af4c35069b Use a short-time cookie to remember where to callback to 2023-09-14 18:49:19 +01:00
advplyr
fea28351f9 Version bump 2.4.2 2023-09-13 16:48:28 -05:00
advplyr
bb124d3274 Merge pull request #2089 from Lionfox2/patch-2
Update de.json
2023-09-13 16:46:39 -05:00
advplyr
6cd1b82ada Merge pull request #2083 from Machou/patch-1
Update fr.json
2023-09-13 16:46:23 -05:00
advplyr
c701617fbb Merge pull request #2079 from Nab0y/master
Update Russian localization
2023-09-13 16:46:02 -05:00
lukeIam
405c954b65 Updated + first rough implementation 2023-09-13 16:35:39 +00:00
Lionfox2
5d84c426fe Update de.json
The more suitable translation for "discover" is "Entdecken", same wording as in other media apps like Spotify
2023-09-12 23:30:58 +02:00
advplyr
083ba2fe19 Fix:Podcast download queue page available on refresh #2088 2023-09-12 15:35:14 -05:00
advplyr
1024bc5a75 Fix:Podcast library stat for total size #2072 2023-09-12 13:43:28 -05:00
advplyr
9553c19b33 Fix:Authors dropdown to use filter data instead of API endpoint #2077 2023-09-12 12:33:41 -05:00
Machou
2cbc9a07cb Update fr.json 2023-09-12 18:11:38 +02:00
advplyr
ab97a9d613 Fix:Crash when updating book author or series that includes an apostrophe #2070 2023-09-12 10:41:39 -05:00
advplyr
f1a7fd0d50 Fix:Podcast library include number of incomplete episodes in home page shelf api request #2081 2023-09-11 17:51:39 -05:00
Dmitry Naboychenko
e9d7efbc5c Update russian localization 2023-09-11 21:35:36 +03:00
lukeIam
f0f03efe17 Merge remote-tracking branch 'origin/master' into auth_passportjs 2023-09-10 13:11:35 +00:00
advplyr
6e5d334874 Version bump 2.4.1 2023-09-09 15:47:17 -05:00
advplyr
6822628994 Fix:Missing narrators library filter 2023-09-09 15:46:33 -05:00
advplyr
98d9fd8c32 Fix:Get all items api endpoint support providing no limit #2067 2023-09-09 15:01:58 -05:00
advplyr
e2cca60853 Fix:Crash on podcast library page sort by title #2069 2023-09-09 14:56:36 -05:00
advplyr
e80b313a7b Fix:Server crash when quick match with find covers setting enabled #2068 2023-09-09 08:57:59 -05:00
advplyr
b09b95ef24 Fix:Browse folders when adding new library folder crashing server #2065 2023-09-09 07:47:17 -05:00
advplyr
aec45d04f7 Version bump 2.4.0 2023-09-08 17:26:21 -05:00
advplyr
87d037cb0a Fix clean database method and version bump 2.3.5 2023-09-08 17:20:39 -05:00
advplyr
f6baf06164 Version bump 2.3.4 2023-09-08 16:20:03 -05:00
advplyr
7e75845851 Ignore podcast directory from watcher for additional 5s after downloading new episode 2023-09-08 15:12:39 -05:00
advplyr
2a11932822 Scanner ignore .part files #2063 2023-09-08 14:50:59 -05:00
advplyr
80fee92037 Update "disable watcher" server setting to display as "enable watcher" #2055 2023-09-08 14:28:21 -05:00
advplyr
d0c02a801a Update open rss feed prevent indexing - dont include block tags when not preventing indexing 2023-09-08 14:03:12 -05:00
advplyr
9e13c64408 Handle sorting when collapsing by series and filtering by series on library page 2023-09-08 13:42:19 -05:00
advplyr
826963bf00 Add api route for changing sorting prefixes, update default sorting prefixes to include a 2023-09-08 12:32:30 -05:00
advplyr
39b6ede1e9 Add support for hide from continue listening 2023-09-08 11:20:22 -05:00
advplyr
066d853156 Add support for hide from continue listening on new home page shelves route 2023-09-07 17:49:35 -05:00
advplyr
efae529fac Add cover finder to new book scanner 2023-09-06 17:48:50 -05:00
advplyr
934c0b9093 Fix watcher scanner detecting existing items 2023-09-06 15:43:59 -05:00
advplyr
f02992dd4d Remove the setting of file permissions #2057 2023-09-06 07:12:11 -05:00
advplyr
10011bd6a3 Add startup function to remove invalid records from DB 2023-09-05 17:58:13 -05:00
advplyr
a44ee913c4 Fix crash on get recent series home page shelf endpoint 2023-09-05 16:10:46 -05:00
advplyr
adccccbd7a Remove index creation from migration file 2023-09-05 15:36:19 -05:00
advplyr
05b1b2be36 Merge branch 'master' of https://github.com/advplyr/audiobookshelf 2023-09-05 15:07:49 -05:00
advplyr
7cc35a2cbe Remove indexes for columns that didnt exist in 2.3.3 2023-09-05 15:07:41 -05:00
advplyr
8d479b6e34 Merge pull request #2054 from JBlond/master
Follow-up update for German strings
2023-09-05 08:23:04 -05:00
JBlond
74d300f048 Follow up update for German string to 469167df66.
Plus addtional string translation
2023-09-05 11:36:41 +02:00
advplyr
1dd1fe8994 Update match all books to load items from DB, remove library items loading to memory on init 2023-09-04 16:33:55 -05:00
advplyr
03115e5e53 Replace old items filter/sort api endpoint with new, handle collapse sub-series 2023-09-04 15:26:07 -05:00
advplyr
b1c07834be Remove force re-scan and old scanner logic 2023-09-04 13:59:37 -05:00
advplyr
b9da3fa30e Add new podcast scanner and remove old scanner 2023-09-04 11:50:55 -05:00
advplyr
42ff3d8314 Add new library item scanner 2023-09-03 17:51:58 -05:00
advplyr
e63aab95d8 Update new library scanner to handle metadata file changes 2023-09-03 15:14:58 -05:00
advplyr
9123dcb365 Remove series search api endpoint, update authors and series to load from db 2023-09-03 10:49:02 -05:00
advplyr
7567e91878 Update get library item api endpoint to remove unnecessary authors include query param 2023-09-03 10:04:14 -05:00
advplyr
1b1bdea3c8 Remove authors search api endpoint 2023-09-03 09:54:23 -05:00
advplyr
2df95c1712 Updates for new book scanner 2023-09-02 17:49:28 -05:00
advplyr
4ad1cd2968 Fix:Batch API endpoints crash on reset library filter data 2023-09-02 10:46:47 -05:00
advplyr
0ecfdab463 Update new library scanner for scanning in new books 2023-09-01 18:01:17 -05:00
advplyr
75276f5a44 Fix:Server crash when updating cover to a directory #2007 2023-08-30 18:05:52 -05:00
advplyr
4585d2816b Fix:Comic reader not detecting file sort order when number is more than 5-digit #2036 2023-08-29 15:47:34 -05:00
advplyr
f8f94f2a6d Update new library scanner to check for cover images and ebooks 2023-08-28 17:50:21 -05:00
advplyr
2c8448d147 Updates to new library scanner and adding jsdoc types 2023-08-27 17:19:57 -05:00
advplyr
ea1d051cfb Merge branch 'master' of https://github.com/advplyr/audiobookshelf 2023-08-26 16:33:34 -05:00
advplyr
a38e43213d Fix:Server crash when deleting library item #2031 2023-08-26 16:33:27 -05:00
advplyr
6cac8fcd6e Merge pull request #2030 from bluecmd/debian-tar-owner
Fix system file ownership for Debian package
2023-08-25 16:19:26 -05:00
Christian Svensson
8e65c78869 Fix system file ownership for Debian package
Extract ffmpeg and tone but ignore the ownership information from the tar archive. This will ensure that those files are owned by root, not the UIDs/GIDs the tar files happen to contain.
2023-08-25 18:14:17 +02:00
advplyr
a3899b68e1 Merge branch 'master' of https://github.com/advplyr/audiobookshelf 2023-08-24 17:55:35 -05:00
advplyr
1187f91063 Update jsdoc defs for models 2023-08-24 17:55:29 -05:00
advplyr
7c288a5ff9 Merge pull request #2027 from realmain/docker-compose
Added podcasts volume to docker compose
2023-08-24 15:22:24 -05:00
advplyr
e0dae44c7d Update:Show published year on library page when sorting books by published year #2017 2023-08-23 18:01:58 -05:00
realmain
754498958d Added podcasts volume 2023-08-23 15:55:07 -07:00
advplyr
ec15978e26 Merge pull request #2026 from shawnphoffman/shawn/rss-feeds
Add config page for all RSS feeds
2023-08-22 16:42:31 -05:00
advplyr
469167df66 Update get all feeds route to be admin-only, map translation strings 2023-08-22 16:37:22 -05:00
advplyr
e7c43a3f32 Merge pull request #2009 from JBlond/master
Update German strings
2023-08-22 16:08:47 -05:00
Shawn Hoffman
24989e73ae Merge branch 'master' into shawn/rss-feeds 2023-08-22 10:30:16 -07:00
Shawn Hoffman
13427b9f70 Add RSS feeds config page 2023-08-22 10:11:10 -07:00
Mario
adafefecd4 Merge branch 'advplyr:master' into master 2023-08-22 11:39:13 +02:00
advplyr
6f96b069b5 Fix search query 2023-08-21 16:33:16 -05:00
advplyr
6c1b4e3a36 Update db model references 2023-08-20 13:34:03 -05:00
advplyr
21343ffbd1 Update numIssues on filter data, fix watcher scanning in new items 2023-08-20 13:16:53 -05:00
advplyr
4f94deefa0 Fix remove items with issues API route & remove old endpoints 2023-08-19 17:12:24 -05:00
advplyr
332078e6c1 Update library stats API route to load from db 2023-08-19 16:53:33 -05:00
advplyr
ff0d6326d3 Update OPML api route to load podcasts from db 2023-08-19 15:19:27 -05:00
advplyr
8d451217a3 Update recent-episodes API route to load from db 2023-08-19 14:49:06 -05:00
advplyr
f21d69339f Update search query to use user permissions 2023-08-19 14:11:34 -05:00
advplyr
c77cead9ae Update search endpoints to search db directly 2023-08-19 13:59:22 -05:00
advplyr
b334d40998 Update library routes to middlewareNew 2023-08-18 17:12:15 -05:00
advplyr
4e4a976050 Update get library series api endpoint to load from db 2023-08-18 17:08:34 -05:00
advplyr
9d7d4c6902 Update filterData for authors/series when added/removed 2023-08-18 14:40:36 -05:00
advplyr
7222171c5b Update checking empty series to load from Db 2023-08-17 17:58:57 -05:00
advplyr
361732a463 Update get User API endpoint to load media progress from db 2023-08-17 17:26:12 -05:00
advplyr
1ebe8a6f4c Update scanner to load library items from db 2023-08-16 18:08:00 -05:00
advplyr
a98942a361 Add jsdoc types to remaining models 2023-08-16 16:38:48 -05:00
advplyr
0bc89cd40f Fix collapse series and sort by title without ignore prefix 2023-08-16 15:24:56 -05:00
advplyr
2ae86ab5bb Fix Library undefined sequelize 2023-08-16 14:49:06 -05:00
advplyr
c707bcf0f6 Add jsdoc types for models 2023-08-15 18:03:43 -05:00
JBlond
10040ba9fa Update German strings as follow up of 09eefae808 2023-08-15 15:38:27 +02:00
advplyr
7afda1295b Update Author model to define types 2023-08-14 18:22:38 -05:00
advplyr
6d6e8613cf Update library API endpoints to load library items from db 2023-08-13 17:45:53 -05:00
advplyr
3651fffbee Update library filter data to load from db and cache, update rss feed routes to load library items from db 2023-08-13 15:10:26 -05:00
advplyr
8d03b23f46 Update MiscController api routes to load library items from db 2023-08-13 13:10:34 -05:00
advplyr
fc44c801f2 Update playlist API endpoints to load library items from DB 2023-08-13 11:22:38 -05:00
advplyr
6056c14926 Update podcast controller to load library items from db 2023-08-12 17:29:08 -05:00
advplyr
f465193b9c Update User.toJSONForPublic to remove mostRecent key and session key only includes the PlaybackSession 2023-08-12 16:11:58 -05:00
advplyr
09c9c28028 Remove test API endpoint for albums 2023-08-12 15:54:59 -05:00
advplyr
f1130eb63a Update MeController api endpoints to load library items from DB 2023-08-12 15:52:09 -05:00
advplyr
db80cec168 Update collection API routes to load libraryItems from DB 2023-08-12 15:01:27 -05:00
lukeIam
dd9a3858d7 Merge remote-tracking branch 'origin/master' into auth_passportjs 2023-08-12 16:44:44 +02:00
advplyr
38029d1202 Update library collections api endpoint to use libraryItems from db 2023-08-11 17:49:06 -05:00
advplyr
aac2879652 Fix library query sort by title, add indexes for books and libraryItems 2023-08-10 17:46:27 -05:00
advplyr
8c9fc3ddb5 Fix discover home page shelf query, add indexes for libraryItems and mediaProgresses table 2023-08-09 17:48:31 -05:00
advplyr
33e04d0cbb Update home page queries merging listen/read shelves, limit recent shelves to 60 days out 2023-08-07 17:59:04 -05:00
advplyr
fbb5fd41fb Merge pull request #1994 from NiclasHaderer/fix-backup-server-crash
Fix: server crash when uploading invalid backup file
2023-08-07 17:17:57 -05:00
advplyr
43a5296dd7 Update server/managers/BackupManager.js 2023-08-07 17:14:47 -05:00
advplyr
345ff1aa66 Update author API endpoints to load library items from DB 2023-08-06 15:06:45 -05:00
advplyr
56e3449db6 Remove media progress for podcast episodes when episode is removed 2023-08-06 14:18:51 -05:00
advplyr
1372c24535 Update queries to account for user permissions 2023-08-06 13:36:58 -05:00
Niclas Haderer
409c5f7b75 fix: the server does not crash any more when an invalid backup file is uploaded 2023-08-06 10:05:53 +02:00
advplyr
83d0db0607 Fix getting default library id for user 2023-08-05 16:37:26 -05:00
advplyr
91b6c4412d Add remaining personalized shelf queries for podcasts 2023-08-05 15:28:16 -05:00
advplyr
09eefae808 Add remaining personalized shelf queries, update book libraries home page to use new API endpoint 2023-08-05 14:01:16 -05:00
advplyr
80b3bfea51 Add recent series home page shelf query 2023-08-04 18:07:55 -05:00
advplyr
516298b5b2 Update continue series shelf to include rss feed 2023-08-04 17:26:43 -05:00
advplyr
8edab98163 Update continue series shelf queries 2023-08-04 17:24:06 -05:00
advplyr
58da095bcf Update query for continue series shelf 2023-08-03 18:14:25 -05:00
advplyr
b9633691f4 Add new personalized home page shelves API endpoint 2023-08-02 18:29:28 -05:00
advplyr
7ec1d8ee5f Fix socket authority check for valid client 2023-08-01 16:34:01 -05:00
advplyr
83a1374e79 Merge pull request #1985 from tomazed/translation-fr
fr translation update
2023-08-01 16:23:28 -05:00
Tomazed
5ef00bac92 fr translation update 2023-08-01 11:51:15 +02:00
advplyr
95c4b3862b Include library item podcast queries 2023-07-31 17:59:51 -05:00
advplyr
eeaf012cdc Update new library item API endpoint to handle collapse series 2023-07-30 17:51:44 -05:00
advplyr
11120a3765 Update version specific db migration check 2023-07-30 11:09:49 -05:00
advplyr
4d0acb30ba Update bookSeries & bookAuthors table to include createdAt timestamp 2023-07-29 17:25:11 -05:00
advplyr
4dbe8d29d9 Update db migration for duration, size, lastFirst, and ignore prefix columns 2023-07-28 18:03:31 -05:00
advplyr
0ca4ff4fca Update readme 2023-07-27 18:17:29 -05:00
advplyr
8be1651c6b Fix:Sync local media progress when library item not found #1971 2023-07-26 18:08:55 -05:00
advplyr
af2db86d1a Merge pull request #1965 from petras-sukys/l10n/lt
Added Lithuanian translation
2023-07-25 15:03:07 -05:00
Petras Šukys
57c834f88d Added EOL at EOF 2023-07-25 22:20:13 +03:00
Petras Šukys
65fdebde20 Added language entry for Lithuanian (Lietuvių) 2023-07-25 22:16:13 +03:00
Petras Šukys
b58e42ebf3 Lithuanian translation, part 5 2023-07-25 22:11:12 +03:00
Petras Šukys
b2d45f598b Merge branch 'master' into l10n/lt 2023-07-25 08:03:19 +03:00
Petras Šukys
09c4e690c6 Lithuanian translation, part 4 2023-07-25 07:57:51 +03:00
Petras Šukys
67ba481dca Lithuanian translation, part 3 2023-07-24 22:49:44 +03:00
advplyr
710a62c2af Update:Load playlists only when needed & remove podcast episode from playlist when deleted 2023-07-23 09:42:57 -05:00
advplyr
5a9eed0a5a Update:Only load collections when needed 2023-07-22 16:18:55 -05:00
advplyr
354e16e462 Update:Only load Users when needed 2023-07-22 15:32:20 -05:00
advplyr
1d974375a0 Update:Only load libraries from db when needed 2023-07-22 14:25:20 -05:00
advplyr
1c40af3eef Update:Sequelize transactionType to IMMEDIATE to fix SQLITE_BUSY #1910 2023-07-22 11:30:29 -05:00
advplyr
daa8c4cd67 Update:Remove sort index from podcast episodes 2023-07-22 09:24:46 -05:00
advplyr
d5da4441cd Fix:Set podcast episode audio file index to 1 on scans 2023-07-22 09:05:43 -05:00
advplyr
80aea0c82d Fix:Save metadata files when updating library items #1952 2023-07-22 07:50:47 -05:00
Petras Šukys
14836eeb0d Lithuanian translation, part 2 2023-07-22 12:00:29 +03:00
Petras Šukys
85e9883d3e Lithuanian translation, part 1 2023-07-22 11:53:37 +03:00
Petras Šukys
80ca73e491 Copied en-us language file to lt 2023-07-22 10:52:06 +03:00
advplyr
22323f606d Fix:RSS feed covers #1948 2023-07-21 16:59:00 -05:00
advplyr
01b65eb678 Fix:Initialize Feed entityUpdatedAt to prevent updated RSS feed every request 2023-07-21 16:18:58 -05:00
advplyr
d1d94c37a7 Update:Remove --max-old-space-size from Dockerfile 2023-07-20 17:00:08 -05:00
advplyr
838a24c8a5 Update:Remove healthcheck from Dockerfile 2023-07-20 16:59:46 -05:00
advplyr
3f380b0839 Fix:Parsing authors from meta tags removes duplicates #1932 2023-07-20 16:55:49 -05:00
advplyr
7fdf1a1d7f Merge pull request #1946 from rasmuslos/master
Fix byte conversion
2023-07-20 15:55:16 -05:00
Rasmus Krämer
38596d017f Fix byte conversion 2023-07-19 23:59:00 +02:00
Jorge
679bdf36b1 Add CodeQL workflow 2023-07-03 09:15:04 +02:00
lukeIam
95e6fef3d1 Merge remote-tracking branch 'origin/master' into auth_passportjs 2023-05-27 10:56:05 +02:00
advplyr
4359ca28df Fix XAccel issue 2023-04-29 16:05:05 -05:00
advplyr
8b685436de Merge 2023-04-29 15:49:04 -05:00
advplyr
8d0064763c Merge branch 'master' into auth_passportjs 2023-04-16 10:08:17 -05:00
advplyr
7010a13648 Fixes for passport local and allow empty password 2023-04-16 10:08:13 -05:00
lukeIam
812395b21b Merge remote-tracking branch 'origin/master' into auth_passportjs 2023-04-14 20:27:43 +02:00
lukeIam
62b0940766 Added passport-openidconnect implementation 2023-04-14 20:26:29 +02:00
lukeIam
08676a675a Fix: small problem with this context in Auth.js 2023-03-24 18:31:58 +01:00
lukeIam
be53b31712 Merge remote-tracking branch 'origin/master' into auth_passportjs 2023-03-24 18:23:08 +01:00
lukeIam
e1ddb95250 Inital passportjs integration 2023-03-24 18:21:25 +01:00
524 changed files with 72662 additions and 43677 deletions

View File

@@ -1,5 +1,5 @@
# [Choice] Node.js version (use -bullseye variants on local arm64/Apple Silicon): 18, 16, 14, 18-bullseye, 16-bullseye, 14-bullseye, 18-buster, 16-buster, 14-buster
ARG VARIANT=16
ARG VARIANT=20
FROM mcr.microsoft.com/devcontainers/javascript-node:0-${VARIANT} as base
# Setup the node environment
@@ -10,6 +10,3 @@ RUN apt-get update && \
DEBIAN_FRONTEND=noninteractive apt-get -y install --no-install-recommends \
curl tzdata ffmpeg && \
rm -rf /var/lib/apt/lists/*
# Move tone executable to appropriate directory
COPY --from=sandreas/tone:v0.1.5 /usr/local/bin/tone /usr/local/bin/

View File

@@ -5,5 +5,6 @@ module.exports.config = {
ConfigPath: Path.resolve('config'),
MetadataPath: Path.resolve('metadata'),
FFmpegPath: '/usr/bin/ffmpeg',
FFProbePath: '/usr/bin/ffprobe'
}
FFProbePath: '/usr/bin/ffprobe',
SkipBinariesCheck: false
}

View File

@@ -8,7 +8,7 @@
// Append -bullseye or -buster to pin to an OS version.
// Use -bullseye variants on local arm64/Apple Silicon.
"args": {
"VARIANT": "16"
"VARIANT": "20"
}
},
"mounts": [

8
.editorconfig Normal file
View File

@@ -0,0 +1,8 @@
root = true
[*]
indent_style = space
indent_size = 2
charset = utf-8
insert_final_newline = true
trim_trailing_whitespace = true

View File

@@ -1,40 +1,50 @@
name: 🐞 Bug Report
description: File a bug/issue
title: "[Bug]: "
labels: ["bug", "triage"]
description: File a bug/issue and help us improve Audiobookshelf
title: '[Bug]: '
labels: ['bug', 'triage']
body:
- type: markdown
attributes:
value: "### Please first search for your issue and check the [docs](https://audiobookshelf.org/docs)."
value: 'Thank you for filing a bug report! 🐛'
- type: markdown
attributes:
value: "### Mobile app issues report [here](https://github.com/advplyr/audiobookshelf-app/issues/new/choose)."
value: 'Please first search for your issue and check the [docs](https://audiobookshelf.org/docs).'
- type: markdown
attributes:
value: "### Join the [discord server](https://discord.gg/pJsjuNCKRq) for questions or if you are not sure about a bug."
value: 'Report issues with the mobile app [here](https://github.com/advplyr/audiobookshelf-app/issues/new/choose).'
- type: markdown
attributes:
value: "## Be as descriptive as you can. Include screenshots, error logs, browser, file types, everything you can think of that might be relevant."
value: 'Join the [discord server](https://discord.gg/HQgCbd6E75) for questions or if you are not sure about a bug.'
- type: textarea
id: what-happened
attributes:
label: Describe the issue
description: What happened & what did you expect to happen
label: What happened?
placeholder: Tell us what you see!
validations:
required: true
- type: textarea
id: what-was-expected
attributes:
label: What did you expect to happen?
placeholder: Tell us what you expected to see! Be as descriptive as you can and include screenshots if applicable.
validations:
required: true
- type: textarea
id: steps-to-reproduce
attributes:
label: Steps to reproduce the issue
value: "1. "
value: '1. '
validations:
required: true
- type: markdown
attributes:
value: '## Install Environment'
- type: input
id: version
attributes:
label: Audiobookshelf version
description: Do not put 'Latest version', please put the actual version here
placeholder: "e.g. v1.6.60"
placeholder: 'e.g. v1.6.60'
validations:
required: true
- type: dropdown
@@ -44,7 +54,45 @@ body:
options:
- Docker
- Debian/PPA
- Windows Tray App
- Built from source
- Other
- Other (list in "Additional Notes" box)
validations:
required: true
required: true
- type: dropdown
id: server-os
attributes:
label: What OS is your Audiobookshelf server hosted from?
options:
- Windows
- macOS
- Linux
- Other (list in "Additional Notes" box)
validations:
required: true
- type: dropdown
id: desktop-browsers
attributes:
label: If the issue is being seen in the UI, what browsers are you seeing the problem on?
options:
- Chrome
- Firefox
- Safari
- Edge
- Firefox for Android
- Chrome for Android
- Safari on iOS
- Other (list in "Additional Notes" box)
- type: textarea
id: logs
attributes:
label: Logs
description: Please include any relevant logs here. This field is automatically formatted into code, so you do not need to include any backticks.
placeholder: Paste logs here
render: shell
- type: textarea
id: additional-notes
attributes:
label: Additional Notes
description: Anything else you want to add?
placeholder: 'e.g. I have tried X, Y, and Z.'

View File

@@ -1,8 +1,5 @@
blank_issues_enabled: false
contact_links:
- name: Discord
url: https://discord.gg/pJsjuNCKRq
about: Ask questions, get help troubleshooting, and join the Abs community here.
- name: Matrix
url: https://matrix.to/#/#audiobookshelf:matrix.org
url: https://discord.gg/HQgCbd6E75
about: Ask questions, get help troubleshooting, and join the Abs community here.

View File

@@ -1,17 +1,63 @@
name: 🚀 Feature Request
description: Request a feature/enhancement
title: "[Enhancement]: "
labels: ["enhancement"]
title: '[Enhancement]: '
labels: ['enhancement']
body:
- type: markdown
attributes:
value: "### Please first search in both issues & discussions for your enhancement."
value: '#### *Mobile app features should be [requested here](https://github.com/advplyr/audiobookshelf-app/issues/new/choose)*.'
- type: markdown
attributes:
value: "### Mobile app features should be requested [here](https://github.com/advplyr/audiobookshelf-app/issues/new/choose)."
value: '## Web/Server Feature Request Description'
- type: markdown
attributes:
value: 'Please first search in both issues & discussions for your enhancement.'
- type: dropdown
id: enhancment-type
attributes:
label: Type of Enhancement
options:
- Server Backend
- Web Interface/Frontend
- Documentation
- type: textarea
id: describe
attributes:
label: Describe the feature/enhancement
label: Describe the Feature/Enhancement
description: Please help us understand what you want.
placeholder: What is your vision?
validations:
required: true
- type: textarea
id: the-why
attributes:
label: Why would this be helpful?
description: Please help us understand why this would enhance your experience.
placeholder: Explain the "why" or "use case".
validations:
required: true
- type: textarea
id: image
attributes:
label: Future Implementation (Screenshot)
description: Please help us visualize by including a doodle or screenshot.
placeholder: How could this look?
validations:
required: true
- type: markdown
attributes:
value: '## Web/Server Current Implementation'
- type: input
id: version
attributes:
label: Audiobookshelf Server Version
description: Do not put 'Latest version', please put your current version number here
placeholder: 'e.g. v1.6.60'
validations:
required: true
- type: textarea
id: current-image
attributes:
label: Current Implementation (Screenshot)
description: What page were you looking at when you thought of this enhancement?
placeholder: If an image is not applicable, please explain why.

55
.github/workflows/apply_comments.yaml vendored Normal file
View File

@@ -0,0 +1,55 @@
name: Add issue comments by label
on:
issues:
types:
- labeled
jobs:
help-wanted:
if: github.event.label.name == 'help wanted'
runs-on: ubuntu-latest
permissions:
issues: write
steps:
- name: Help wanted comment
run: gh issue comment "$NUMBER" --body "$BODY"
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
GH_REPO: ${{ github.repository }}
NUMBER: ${{ github.event.issue.number }}
BODY: >
This issue is not able to be completed due to limited bandwidth or access to the required test hardware.
This issue is available for anyone to work on.
config-issue:
if: github.event.label.name == 'config-issue'
runs-on: ubuntu-latest
permissions:
issues: write
steps:
- name: Config issue comment
run: gh issue close "$NUMBER" --reason "not planned" --comment "$BODY"
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
GH_REPO: ${{ github.repository }}
NUMBER: ${{ github.event.issue.number }}
BODY: >
After reviewing this issue, this appears to be a problem with your setup and not Audiobookshelf. This issue is being closed to keep the issue tracker focused on Audiobookshelf itself. Please reach out on the Audiobookshelf Discord for community support.
Some common search terms to help you find the solution to your problem:
- Reverse proxy
- Enabling websockets
- SSL (https vs http)
- Configuring a static IP
- `localhost` versus IP address
- hairpin NAT
- VPN
- firewall ports
- public versus private network
- bridge versus host mode
- Docker networking
- DNS (such as EAI_AGAIN errors)
After you have followed these steps, please post the solution or steps you followed to fix the problem to help others in the future, or show that it is a problem with Audiobookshelf so we can reopen the issue.

View File

@@ -0,0 +1,20 @@
name: Close fixed issues on release.
on:
release:
types: [published]
permissions:
contents: read
issues: write
jobs:
comment:
runs-on: ubuntu-latest
steps:
- name: Close issues marked as fixed upon a release.
uses: gcampbell-msft/fixed-pending-release@7fa1b75a0c04bcd4b375110522878e5f6100cff5
with:
label: 'awaiting release'
removeLabel: true
applyToAll: true
message: Fixed in [${releaseTag}](${releaseUrl}).

65
.github/workflows/codeql.yml vendored Normal file
View File

@@ -0,0 +1,65 @@
name: "CodeQL"
on:
push:
branches: [ 'master' ]
pull_request:
# The branches below must be a subset of the branches above
branches: [ 'master' ]
schedule:
- cron: '16 5 * * 4'
jobs:
analyze:
name: Analyze
runs-on: ubuntu-latest
permissions:
actions: read
contents: read
security-events: write
strategy:
fail-fast: false
matrix:
language: [ 'javascript' ]
# CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python', 'ruby' ]
# Use only 'java' to analyze code written in Java, Kotlin or both
# Use only 'javascript' to analyze code written in JavaScript, TypeScript or both
# Learn more about CodeQL language support at https://aka.ms/codeql-docs/language-support
steps:
- name: Checkout repository
uses: actions/checkout@v3
# Initializes the CodeQL tools for scanning.
- name: Initialize CodeQL
uses: github/codeql-action/init@v2
with:
languages: ${{ matrix.language }}
# If you wish to specify custom queries, you can do so here or in a config file.
# By default, queries listed here will override any specified in a config file.
# Prefix the list here with "+" to use these queries and those in the config file.
# For more details on CodeQL's query packs, refer to: https://docs.github.com/en/code-security/code-scanning/automatically-scanning-your-code-for-vulnerabilities-and-errors/configuring-code-scanning#using-queries-in-ql-packs
# queries: security-extended,security-and-quality
# Autobuild attempts to build any compiled languages (C/C++, C#, Go, Java, or Swift).
# If this step fails, then you should remove it and run the build manually (see below)
- name: Autobuild
uses: github/codeql-action/autobuild@v2
# Command-line programs to run using the OS shell.
# 📚 See https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idstepsrun
# If the Autobuild fails above, remove it and uncomment the following three lines.
# modify them (or add more) to build your code if your project, please refer to the EXAMPLE below for guidance.
# - run: |
# echo "Run, Build Application using script"
# ./location_of_script_within_repo/buildscript.sh
- name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@v2
with:
category: "/language:${{matrix.language}}"

View File

@@ -71,7 +71,7 @@ jobs:
with:
tags: ${{ github.event.inputs.tags || steps.meta.outputs.tags }}
context: .
platforms: linux/amd64,linux/arm64,linux/arm/v7
platforms: linux/amd64,linux/arm64
push: true
cache-from: type=local,src=/tmp/.buildx-cache
cache-to: type=local,dest=/tmp/.buildx-cache-new,mode=max

30
.github/workflows/i18n-integration.yml vendored Normal file
View File

@@ -0,0 +1,30 @@
name: Verify all i18n files are alphabetized
on:
pull_request:
paths:
- client/strings/** # Should only check if any strings changed
push:
paths:
- client/strings/** # Should only check if any strings changed
jobs:
update_translations:
runs-on: ubuntu-latest
steps:
# Check out the repository
- name: Checkout repository
uses: actions/checkout@v4
# Set up node to run the javascript
- name: Set up node
uses: actions/setup-node@v4
with:
node-version: '20'
# The only argument is the `directory`, which is where the i18n files are
# stored.
- name: Run Update JSON Files action
uses: audiobookshelf/audiobookshelf-i18n-updater@v1.3.0
with:
directory: 'client/strings/' # Adjust the directory path as needed

View File

@@ -4,7 +4,7 @@ on:
pull_request:
push:
branches-ignore:
- 'dependabot/**' # Don't run dependabot branches, as they are already covered by pull requests
- 'dependabot/**' # Don't run dependabot branches, as they are already covered by pull requests
jobs:
build:
@@ -16,10 +16,10 @@ jobs:
- name: setup nade
uses: actions/setup-node@v3
with:
node-version: 16
node-version: 20
- name: install pkg
run: npm install -g pkg
- name: install pkg (using yao-pkg fork for targetting node20)
run: npm install -g @yao-pkg/pkg
- name: get client dependencies
working-directory: client
@@ -33,7 +33,7 @@ jobs:
run: npm ci --only=production
- name: build binary
run: pkg -t node18-linux-x64 -o audiobookshelf .
run: pkg -t node20-linux-x64 -o audiobookshelf .
- name: run audiobookshelf
run: |

32
.github/workflows/lint-openapi.yml vendored Normal file
View File

@@ -0,0 +1,32 @@
name: API linting
# Run on pull requests or pushes when there is a change to any OpenAPI files in docs/
on:
pull_request:
push:
paths:
- 'docs/**'
# This action only needs read permissions
permissions:
contents: read
jobs:
build:
runs-on: ubuntu-latest
steps:
# Check out the repository
- name: Checkout
uses: actions/checkout@v4
# Set up node to run the javascript
- name: Set up node
uses: actions/setup-node@v4
# Install Redocly CLI
- name: Install Redocly CLI
run: npm install -g @redocly/cli@latest
# Perform linting for exploded spec
- name: Run linting for exploded spec
run: redocly lint docs/root.yaml --format=github-actions
# Perform linting for bundled spec
- name: Run linting for bundled spec
run: redocly lint docs/openapi.json --format=github-actions

View File

@@ -0,0 +1,17 @@
name: Dispatch an abs-windows event
on:
release:
types: [published]
workflow_dispatch:
jobs:
abs-windows-dispatch:
runs-on: ubuntu-latest
steps:
- name: Send a remote repository dispatch event
uses: peter-evans/repository-dispatch@v3
with:
token: ${{ secrets.ABS_WINDOWS_PAT }}
repository: mikiher/audiobookshelf-windows
event-type: build-windows

37
.github/workflows/unit-tests.yml vendored Normal file
View File

@@ -0,0 +1,37 @@
name: Run Unit Tests
on:
workflow_dispatch:
inputs:
ref:
description: 'Branch/Tag/SHA to test'
required: true
pull_request:
push:
jobs:
run-unit-tests:
name: Run Unit Tests
runs-on: ubuntu-latest
steps:
- name: Checkout (push/pull request)
uses: actions/checkout@v4
if: github.event_name != 'workflow_dispatch'
- name: Checkout (workflow_dispatch)
uses: actions/checkout@v4
with:
ref: ${{ inputs.ref }}
if: github.event_name == 'workflow_dispatch'
- name: Set up Node.js
uses: actions/setup-node@v4
with:
node-version: 20
- name: Install dependencies
run: npm ci
- name: Run tests
run: npm test

8
.gitignore vendored
View File

@@ -7,11 +7,17 @@
/podcasts/
/media/
/metadata/
test/
/client/.nuxt/
/client/dist/
/dist/
/deploy/
/coverage/
/.nyc_output/
/ffmpeg*
/ffprobe*
/unicode*
sw.*
.DS_STORE
.idea/*
tailwind.compiled.css

17
.prettierrc Normal file
View File

@@ -0,0 +1,17 @@
{
"semi": false,
"singleQuote": true,
"printWidth": 400,
"proseWrap": "never",
"trailingComma": "none",
"overrides": [
{
"files": ["*.html"],
"options": {
"singleQuote": false,
"wrapAttributes": false,
"sortAttributes": false
}
}
]
}

7
.vscode/extensions.json vendored Normal file
View File

@@ -0,0 +1,7 @@
{
"recommendations": [
"EditorConfig.EditorConfig",
"esbenp.prettier-vscode",
"octref.vetur"
]
}

View File

@@ -16,5 +16,12 @@
},
"editor.formatOnSave": true,
"editor.detectIndentation": true,
"editor.tabSize": 2
"editor.tabSize": 2,
"javascript.format.semicolons": "remove",
"[javascript][json][jsonc]": {
"editor.defaultFormatter": "esbenp.prettier-vscode"
},
"[vue]": {
"editor.defaultFormatter": "octref.vetur"
}
}

View File

@@ -1,13 +1,12 @@
### STAGE 0: Build client ###
FROM node:16-alpine AS build
FROM node:20-alpine AS build
WORKDIR /client
COPY /client /client
RUN npm ci && npm cache clean --force
RUN npm run generate
### STAGE 1: Build server ###
FROM sandreas/tone:v0.1.5 AS tone
FROM node:16-alpine
FROM node:20-alpine
ENV NODE_ENV=production
@@ -17,10 +16,11 @@ RUN apk update && \
tzdata \
ffmpeg \
make \
gcompat \
python3 \
g++
g++ \
tini
COPY --from=tone /usr/local/bin/tone /usr/local/bin/
COPY --from=build /client/dist /client/dist
COPY index.js package* /
COPY server server
@@ -29,12 +29,7 @@ RUN npm ci --only=production
RUN apk del make python3 g++
ENV NODE_OPTIONS=--max-old-space-size=4096
EXPOSE 80
HEALTHCHECK \
--interval=30s \
--timeout=3s \
--start-period=10s \
CMD curl -f http://127.0.0.1/healthcheck || exit 1
ENTRYPOINT ["tini", "--"]
CMD ["node", "index.js"]

View File

@@ -2,7 +2,6 @@
set -e
set -o pipefail
FFMPEG_INSTALL_DIR="/usr/lib/audiobookshelf-ffmpeg"
DEFAULT_DATA_DIR="/usr/share/audiobookshelf"
CONFIG_PATH="/etc/default/audiobookshelf"
DEFAULT_PORT=13378
@@ -46,43 +45,11 @@ add_group() {
fi
}
install_ffmpeg() {
echo "Starting FFMPEG Install"
WGET="wget https://johnvansickle.com/ffmpeg/builds/ffmpeg-git-amd64-static.tar.xz --output-document=ffmpeg-git-amd64-static.tar.xz"
WGET_TONE="wget https://github.com/sandreas/tone/releases/download/v0.1.5/tone-0.1.5-linux-x64.tar.gz --output-document=tone-0.1.5-linux-x64.tar.gz"
if ! cd "$FFMPEG_INSTALL_DIR"; then
echo "Creating ffmpeg install dir at $FFMPEG_INSTALL_DIR"
mkdir "$FFMPEG_INSTALL_DIR"
chown -R 'audiobookshelf:audiobookshelf' "$FFMPEG_INSTALL_DIR"
cd "$FFMPEG_INSTALL_DIR"
fi
$WGET
tar xvf ffmpeg-git-amd64-static.tar.xz --strip-components=1
rm ffmpeg-git-amd64-static.tar.xz
# Temp downloading tone library to the ffmpeg dir
echo "Getting tone.."
$WGET_TONE
tar xvf tone-0.1.5-linux-x64.tar.gz --strip-components=1
rm tone-0.1.5-linux-x64.tar.gz
echo "Good to go on Ffmpeg (& tone)... hopefully"
}
setup_config() {
if [ -f "$CONFIG_PATH" ]; then
echo "Existing config found."
cat $CONFIG_PATH
# TONE_PATH variable added in 2.1.6, if it doesnt exist then add it
if ! grep -q "TONE_PATH" "$CONFIG_PATH"; then
echo "Adding TONE_PATH to existing config"
echo "TONE_PATH=$FFMPEG_INSTALL_DIR/tone" >> "$CONFIG_PATH"
fi
else
if [ ! -d "$DEFAULT_DATA_DIR" ]; then
@@ -96,9 +63,6 @@ setup_config() {
config_text="METADATA_PATH=$DEFAULT_DATA_DIR/metadata
CONFIG_PATH=$DEFAULT_DATA_DIR/config
FFMPEG_PATH=$FFMPEG_INSTALL_DIR/ffmpeg
FFPROBE_PATH=$FFMPEG_INSTALL_DIR/ffprobe
TONE_PATH=$FFMPEG_INSTALL_DIR/tone
PORT=$DEFAULT_PORT
HOST=$DEFAULT_HOST"
@@ -115,5 +79,3 @@ add_group 'audiobookshelf' ''
add_user 'audiobookshelf' '' 'audiobookshelf' 'audiobookshelf user-daemon' '/bin/false'
setup_config
install_ffmpeg

View File

@@ -48,11 +48,10 @@ Description: $DESCRIPTION"
echo "$controlfile" > dist/debian/DEBIAN/control;
# Package debian
pkg -t node16-linux-x64 -o dist/debian/usr/share/audiobookshelf/audiobookshelf .
pkg -t node20-linux-x64 -o dist/debian/usr/share/audiobookshelf/audiobookshelf .
fakeroot dpkg-deb --build dist/debian
fakeroot dpkg-deb -Zxz --build dist/debian
mv dist/debian.deb "dist/$OUTPUT_FILE"
chmod +x "dist/$OUTPUT_FILE"
echo "Finished! Filename: $OUTPUT_FILE"

View File

@@ -30,8 +30,7 @@
}
.bookshelf-row {
/* Sidebar width + scrollbar width */
width: calc(100vw - 88px);
width: calc(100vw - (100vw - 100%));
}
@media (max-width: 768px) {
@@ -217,36 +216,6 @@ Bookshelf Label
filter: blur(20px);
}
.episode-subtitle {
word-break: break-word;
overflow: hidden;
text-overflow: ellipsis;
display: -webkit-box;
line-height: 16px;
/* fallback */
max-height: 32px;
/* fallback */
-webkit-line-clamp: 2;
/* number of lines to show */
-webkit-box-orient: vertical;
}
.episode-subtitle-long {
word-break: break-word;
overflow: hidden;
text-overflow: ellipsis;
display: -webkit-box;
line-height: 16px;
/* fallback */
max-height: 72px;
/* fallback */
-webkit-line-clamp: 6;
/* number of lines to show */
-webkit-box-orient: vertical;
}
/* Padding for toastification toasts in the top right to not cover appbar/toolbar */
.app-bar-and-toolbar .Vue-Toastification__container.top-right {
padding-top: 104px;
@@ -258,4 +227,24 @@ Bookshelf Label
.no-bars .Vue-Toastification__container.top-right {
padding-top: 8px;
}
.abs-btn::before {
content: '';
position: absolute;
border-radius: 6px;
top: 0;
left: 0;
width: 100%;
height: 100%;
background-color: rgba(255, 255, 255, 0);
transition: all 0.1s ease-in-out;
}
.abs-btn:hover:not(:disabled)::before {
background-color: rgba(255, 255, 255, 0.1);
}
.abs-btn:disabled::before {
background-color: rgba(0, 0, 0, 0.2);
}

View File

@@ -1,19 +1,12 @@
@font-face {
font-family: 'Material Icons';
font-family: 'Material Symbols Rounded';
font-style: normal;
font-weight: 400;
src: url(~static/fonts/MaterialIcons.woff2) format('woff2');
src: url(~static/fonts/MaterialSymbolsRounded.woff2) format('woff2');
}
@font-face {
font-family: 'Material Icons Outlined';
font-style: normal;
font-weight: 400;
src: url(~static/fonts/MaterialIconsOutlined.woff2) format('woff2');
}
.material-icons {
font-family: 'Material Icons';
.material-symbols {
font-family: 'Material Symbols Rounded';
font-weight: normal;
font-style: normal;
line-height: 1;
@@ -24,28 +17,12 @@
word-wrap: normal;
direction: ltr;
-webkit-font-smoothing: antialiased;
vertical-align: top;
}
.material-icons:not([class*="text-"]) {
font-size: 1.5rem;
}
.material-icons-outlined {
font-family: 'Material Icons Outlined';
font-weight: normal;
font-style: normal;
line-height: 1;
letter-spacing: normal;
text-transform: none;
display: inline-block;
white-space: nowrap;
word-wrap: normal;
direction: ltr;
-webkit-font-smoothing: antialiased;
}
.material-icons-outlined:not([class*="text-"]) {
font-size: 1.5rem;
.material-symbols.fill {
font-variation-settings:
'FILL' 1
}
/* cyrillic-ext */
@@ -316,4 +293,4 @@
font-display: swap;
src: url(~static/fonts/Ubuntu_Mono/UbuntuMono-Regular.ttf) format('truetype');
unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
}

View File

@@ -0,0 +1,3 @@
@tailwind base;
@tailwind components;
@tailwind utilities;

View File

@@ -16,7 +16,7 @@
<div class="flex-grow" />
<ui-tooltip v-if="isChromecastInitialized && !isHttps" direction="bottom" text="Casting requires a secure connection" class="flex items-center">
<span class="material-icons-outlined text-2xl text-warning text-opacity-50"> cast </span>
<span class="material-symbols text-2xl text-warning text-opacity-50"> cast </span>
</ui-tooltip>
<div v-if="isChromecastInitialized" class="w-6 min-w-6 h-6 ml-2 mr-1 sm:mx-2 cursor-pointer">
<google-cast-launcher></google-cast-launcher>
@@ -26,19 +26,19 @@
<nuxt-link v-if="currentLibrary" to="/config/stats" class="hover:text-gray-200 cursor-pointer w-8 h-8 hidden sm:flex items-center justify-center mx-1">
<ui-tooltip :text="$strings.HeaderYourStats" direction="bottom" class="flex items-center">
<span class="material-icons text-2xl" aria-label="User Stats" role="button">equalizer</span>
<span class="material-symbols text-2xl" aria-label="User Stats" role="button">&#xe01d;</span>
</ui-tooltip>
</nuxt-link>
<nuxt-link v-if="userCanUpload && currentLibrary" to="/upload" class="hover:text-gray-200 cursor-pointer w-8 h-8 flex items-center justify-center mx-1">
<ui-tooltip :text="$strings.ButtonUpload" direction="bottom" class="flex items-center">
<span class="material-icons text-2xl" aria-label="Upload Media" role="button">upload</span>
<span class="material-symbols text-2xl" aria-label="Upload Media" role="button">&#xf09b;</span>
</ui-tooltip>
</nuxt-link>
<nuxt-link v-if="userIsAdminOrUp" to="/config" class="hover:text-gray-200 cursor-pointer w-8 h-8 flex items-center justify-center mx-1">
<ui-tooltip :text="$strings.HeaderSettings" direction="bottom" class="flex items-center">
<span class="material-icons text-2xl" aria-label="System Settings" role="button">settings</span>
<span class="material-symbols text-2xl" aria-label="System Settings" role="button">&#xe8b8;</span>
</ui-tooltip>
</nuxt-link>
@@ -47,7 +47,7 @@
<span class="block truncate">{{ username }}</span>
</span>
<span class="h-full md:ml-3 md:absolute inset-y-0 md:right-0 flex items-center justify-center md:pr-2 pointer-events-none">
<span class="material-icons text-xl text-gray-100">person</span>
<span class="material-symbols text-xl text-gray-100">&#xe7fd;</span>
</span>
</nuxt-link>
</div>
@@ -55,7 +55,7 @@
<h1 class="text-lg md:text-2xl px-4">{{ $getString('MessageItemsSelected', [numMediaItemsSelected]) }}</h1>
<div class="flex-grow" />
<ui-btn v-if="!isPodcastLibrary && selectedMediaItemsArePlayable" color="success" :padding-x="4" small class="flex items-center h-9 mr-2" @click="playSelectedItems">
<span class="material-icons text-2xl -ml-2 pr-1 text-white">play_arrow</span>
<span class="material-symbols fill text-2xl -ml-2 pr-1 text-white">play_arrow</span>
{{ $strings.ButtonPlay }}
</ui-btn>
<ui-tooltip v-if="isBookLibrary" :text="selectedIsFinished ? $strings.MessageMarkAsNotFinished : $strings.MessageMarkAsFinished" direction="bottom">
@@ -76,7 +76,7 @@
<ui-context-menu-dropdown v-if="contextMenuItems.length && !processingBatch" :items="contextMenuItems" class="ml-1" @action="contextMenuAction" />
<ui-tooltip :text="$strings.LabelDeselectAll" direction="bottom" class="flex items-center">
<span class="material-icons text-3xl px-4 hover:text-gray-100 cursor-pointer" :class="processingBatch ? 'text-gray-400' : ''" @click="cancelSelectionMode">close</span>
<span class="material-symbols text-3xl px-4 hover:text-gray-100 cursor-pointer" :class="processingBatch ? 'text-gray-400' : ''" @click="cancelSelectionMode">close</span>
</ui-tooltip>
</div>
</div>
@@ -170,13 +170,13 @@ export default {
if (!this.isPodcastLibrary && this.selectedMediaItemsArePlayable) {
options.push({
text: 'Quick Embed Metadata',
text: this.$strings.ButtonQuickEmbedMetadata,
action: 'quick-embed'
})
}
options.push({
text: 'Re-Scan',
text: this.$strings.ButtonReScan,
action: 'rescan'
})
@@ -186,7 +186,7 @@ export default {
methods: {
requestBatchQuickEmbed() {
const payload = {
message: 'Warning! Quick embed will not backup your audio files. Make sure that you have a backup of your audio files. <br><br>Would you like to continue?',
message: this.$strings.MessageConfirmQuickEmbed,
callback: (confirmed) => {
if (confirmed) {
this.$axios
@@ -219,7 +219,7 @@ export default {
},
async batchRescan() {
const payload = {
message: `Are you sure you want to re-scan ${this.selectedMediaItems.length} items?`,
message: this.$getString('MessageConfirmReScanLibraryItems', [this.selectedMediaItems.length]),
callback: (confirmed) => {
if (confirmed) {
this.$axios
@@ -316,13 +316,15 @@ export default {
},
batchDeleteClick() {
const payload = {
message: `This will delete ${this.numMediaItemsSelected} library items from the database and your file system. Are you sure?`,
checkboxLabel: 'Delete from file system. Uncheck to only remove from database.',
message: this.$getString('MessageConfirmDeleteLibraryItems', [this.numMediaItemsSelected]),
checkboxLabel: this.$strings.LabelDeleteFromFileSystemCheckbox,
yesButtonText: this.$strings.ButtonDelete,
yesButtonColor: 'error',
checkboxDefaultValue: true,
checkboxDefaultValue: !Number(localStorage.getItem('softDeleteDefault') || 0),
callback: (confirmed, hardDelete) => {
if (confirmed) {
localStorage.setItem('softDeleteDefault', hardDelete ? 0 : 1)
this.$store.commit('setProcessingBatch', true)
this.$axios
@@ -330,13 +332,13 @@ export default {
libraryItemIds: this.selectedMediaItems.map((i) => i.id)
})
.then(() => {
this.$toast.success('Batch delete success')
this.$toast.success(this.$strings.ToastBatchDeleteSuccess)
this.$store.commit('globals/resetSelectedMediaItems', [])
this.$eventBus.$emit('bookshelf_clear_selection')
})
.catch((error) => {
console.error('Batch delete failed', error)
this.$toast.error('Batch delete failed')
this.$toast.error(this.$strings.ToastBatchDeleteFailed)
})
.finally(() => {
this.$store.commit('setProcessingBatch', false)

View File

@@ -1,41 +1,29 @@
<template>
<div id="bookshelf" ref="wrapper" class="w-full max-w-full h-full overflow-y-scroll relative">
<div id="bookshelf" ref="wrapper" class="w-full max-w-full h-full overflow-y-scroll relative" :style="{ fontSize: sizeMultiplier + 'rem' }">
<!-- Cover size widget -->
<widgets-cover-size-widget class="fixed bottom-4 right-4 z-50" />
<widgets-cover-size-widget class="fixed right-4 z-50" :style="{ bottom: streamLibraryItem ? '181px' : '16px' }" />
<div v-if="loaded && !shelves.length && !search" class="w-full flex flex-col items-center justify-center py-12">
<p class="text-center text-2xl mb-4 py-4">{{ libraryName }} Library is empty!</p>
<p class="text-center text-2xl mb-4 py-4">{{ $getString('MessageXLibraryIsEmpty', [libraryName]) }}</p>
<div v-if="userIsAdminOrUp" class="flex">
<ui-btn to="/config" color="primary" class="w-52 mr-2">Configure Scanner</ui-btn>
<ui-btn color="success" class="w-52" @click="scan">Scan Library</ui-btn>
<ui-btn to="/config" color="primary" class="w-52 mr-2">{{ $strings.ButtonConfigureScanner }}</ui-btn>
<ui-btn color="success" class="w-52" :loading="isScanningLibrary || tempIsScanning" @click="scan">{{ $strings.ButtonScanLibrary }}</ui-btn>
</div>
</div>
<div v-else-if="loaded && !shelves.length && search" class="w-full h-40 flex items-center justify-center">
<p class="text-center text-xl py-4">No results for query</p>
<p class="text-center text-xl py-4">{{ $strings.MessageBookshelfNoResultsForQuery }}</p>
</div>
<!-- Alternate plain view -->
<div v-else-if="isAlternativeBookshelfView" class="w-full mb-24">
<template v-for="(shelf, index) in shelves">
<widgets-item-slider v-if="shelf.type === 'book' || shelf.type === 'podcast'" :shelf-id="shelf.id" :key="index + '.'" :items="shelf.entities" :continue-listening-shelf="shelf.id === 'continue-listening' || shelf.id === 'continue-reading'" :height="232 * sizeMultiplier" class="bookshelf-row pl-8 my-6" @selectEntity="(payload) => selectEntity(payload, index)">
<p class="font-semibold text-gray-100" :style="{ fontSize: sizeMultiplier + 'rem' }">{{ $strings[shelf.labelStringKey] }}</p>
<div v-else-if="isAlternativeBookshelfView" class="w-full mb-24e">
<template v-for="(shelf, index) in supportedShelves">
<widgets-item-slider :shelf-id="shelf.id" :key="index + '.'" :items="shelf.entities" :continue-listening-shelf="shelf.id === 'continue-listening' || shelf.id === 'continue-reading'" :type="shelf.type" class="bookshelf-row pl-8e my-6e" @selectEntity="(payload) => selectEntity(payload, index)">
<p class="font-semibold text-gray-100">{{ $strings[shelf.labelStringKey] }}</p>
</widgets-item-slider>
<widgets-episode-slider v-else-if="shelf.type === 'episode'" :key="index + '.'" :items="shelf.entities" :continue-listening-shelf="shelf.id === 'continue-listening'" :height="232 * sizeMultiplier" class="bookshelf-row pl-8 my-6" @selectEntity="(payload) => selectEntity(payload, index)">
<p class="font-semibold text-gray-100" :style="{ fontSize: sizeMultiplier + 'rem' }">{{ $strings[shelf.labelStringKey] }}</p>
</widgets-episode-slider>
<widgets-series-slider v-else-if="shelf.type === 'series'" :key="index + '.'" :items="shelf.entities" :height="232 * sizeMultiplier" class="bookshelf-row pl-8 my-6">
<p class="font-semibold text-gray-100" :style="{ fontSize: sizeMultiplier + 'rem' }">{{ $strings[shelf.labelStringKey] }}</p>
</widgets-series-slider>
<widgets-authors-slider v-else-if="shelf.type === 'authors'" :key="index + '.'" :items="shelf.entities" :height="192 * sizeMultiplier" class="bookshelf-row pl-8 my-6">
<p class="font-semibold text-gray-100" :style="{ fontSize: sizeMultiplier + 'rem' }">{{ $strings[shelf.labelStringKey] }}</p>
</widgets-authors-slider>
<widgets-narrators-slider v-else-if="shelf.type === 'narrators'" :key="index + '.'" :items="shelf.entities" :height="100 * sizeMultiplier" class="bookshelf-row pl-8 my-6">
<p class="font-semibold text-gray-100" :style="{ fontSize: sizeMultiplier + 'rem' }">{{ $strings[shelf.labelStringKey] }}</p>
</widgets-narrators-slider>
</template>
</div>
<!-- Regular bookshelf view -->
<div v-else class="w-full">
<template v-for="(shelf, index) in shelves">
<template v-for="(shelf, index) in supportedShelves">
<app-book-shelf-row :key="index" :index="index" :shelf="shelf" :size-multiplier="sizeMultiplier" :book-cover-width="bookCoverWidth" :book-cover-aspect-ratio="coverAspectRatio" :continue-listening-shelf="shelf.id === 'continue-listening' || shelf.id === 'continue-reading'" @selectEntity="(payload) => selectEntity(payload, index)" />
</template>
</div>
@@ -58,16 +46,23 @@ export default {
scannerParseSubtitle: false,
wrapperClientWidth: 0,
shelves: [],
lastItemIndexSelected: -1
lastItemIndexSelected: -1,
tempIsScanning: false
}
},
computed: {
supportedShelves() {
return this.shelves.filter((shelf) => ['book', 'podcast', 'episode', 'series', 'authors', 'narrators'].includes(shelf.type))
},
userIsAdminOrUp() {
return this.$store.getters['user/getIsAdminOrUp']
},
currentLibraryId() {
return this.$store.state.libraries.currentLibraryId
},
currentLibraryMediaType() {
return this.$store.getters['libraries/getCurrentLibraryMediaType']
},
libraryName() {
return this.$store.getters['libraries/getCurrentLibraryName']
},
@@ -86,11 +81,16 @@ export default {
return this.coverAspectRatio == 1
},
sizeMultiplier() {
var baseSize = this.isCoverSquareAspectRatio ? 192 : 120
return this.bookCoverWidth / baseSize
return this.$store.getters['user/getSizeMultiplier']
},
selectedMediaItems() {
return this.$store.state.globals.selectedMediaItems || []
},
streamLibraryItem() {
return this.$store.state.streamLibraryItem
},
isScanningLibrary() {
return !!this.$store.getters['tasks/getRunningLibraryScanTask'](this.currentLibraryId)
}
},
methods: {
@@ -167,8 +167,19 @@ export default {
this.loaded = true
},
async fetchCategories() {
// Sets the limit for the number of items to be displayed based on the viewport width.
const viewportWidth = window.innerWidth
let limit
if (viewportWidth >= 3240) {
limit = 15
} else if (viewportWidth >= 2880 && viewportWidth < 3240) {
limit = 12
}
const limitQuery = limit ? `&limit=${limit}` : ''
const categories = await this.$axios
.$get(`/api/libraries/${this.currentLibraryId}/personalized?include=rssfeed,numEpisodesIncomplete`)
.$get(`/api/libraries/${this.currentLibraryId}/personalized?include=rssfeed,numEpisodesIncomplete,share${limitQuery}`)
.then((data) => {
return data
})
@@ -267,14 +278,15 @@ export default {
this.shelves = shelves
},
scan() {
this.tempIsScanning = true
this.$store
.dispatch('libraries/requestLibraryScan', { libraryId: this.$store.state.libraries.currentLibraryId })
.then(() => {
this.$toast.success('Library scan started')
})
.catch((error) => {
console.error('Failed to start scan', error)
this.$toast.error('Failed to start scan')
this.$toast.error(this.$strings.ToastLibraryScanFailedToStart)
})
.finally(() => {
this.tempIsScanning = false
})
},
userUpdated(user) {
@@ -335,9 +347,15 @@ export default {
libraryItemsAdded(libraryItems) {
console.log('libraryItems added', libraryItems)
const isThisLibrary = !libraryItems.some((li) => li.libraryId !== this.currentLibraryId)
if (!this.search && isThisLibrary) {
this.fetchCategories()
const recentlyAddedShelf = this.shelves.find((shelf) => shelf.id === 'recently-added')
if (!recentlyAddedShelf) return
// Add new library item to the recently added shelf
for (const libraryItem of libraryItems) {
if (libraryItem.libraryId === this.currentLibraryId && !recentlyAddedShelf.entities.some((ent) => ent.id === libraryItem.id)) {
// Add to front of array
recentlyAddedShelf.entities.unshift(libraryItem)
}
}
},
libraryItemsUpdated(items) {
@@ -346,8 +364,6 @@ export default {
})
},
episodeAdded(episodeWithLibraryItem) {
console.log('Podcast episode added', episodeWithLibraryItem)
const isThisLibrary = episodeWithLibraryItem.libraryItem?.libraryId === this.currentLibraryId
if (!this.search && isThisLibrary) {
this.fetchCategories()
@@ -403,6 +419,36 @@ export default {
}
})
},
shareOpen(mediaItemShare) {
this.shelves.forEach((shelf) => {
if (shelf.type == 'book') {
shelf.entities = shelf.entities.map((ent) => {
if (ent.media.id === mediaItemShare.mediaItemId) {
return {
...ent,
mediaItemShare
}
}
return ent
})
}
})
},
shareClosed(mediaItemShare) {
this.shelves.forEach((shelf) => {
if (shelf.type == 'book') {
shelf.entities = shelf.entities.map((ent) => {
if (ent.media.id === mediaItemShare.mediaItemId) {
return {
...ent,
mediaItemShare: null
}
}
return ent
})
}
})
},
initListeners() {
if (this.$root.socket) {
this.$root.socket.on('user_updated', this.userUpdated)
@@ -414,6 +460,8 @@ export default {
this.$root.socket.on('items_updated', this.libraryItemsUpdated)
this.$root.socket.on('items_added', this.libraryItemsAdded)
this.$root.socket.on('episode_added', this.episodeAdded)
this.$root.socket.on('share_open', this.shareOpen)
this.$root.socket.on('share_closed', this.shareClosed)
} else {
console.error('Error socket not initialized')
}
@@ -429,6 +477,8 @@ export default {
this.$root.socket.off('items_updated', this.libraryItemsUpdated)
this.$root.socket.off('items_added', this.libraryItemsAdded)
this.$root.socket.off('episode_added', this.episodeAdded)
this.$root.socket.off('share_open', this.shareOpen)
this.$root.socket.off('share_closed', this.shareClosed)
} else {
console.error('Error socket not initialized')
}

View File

@@ -1,67 +1,53 @@
<template>
<div class="relative">
<div ref="shelf" class="w-full max-w-full bookshelf-row categorizedBookshelfRow relative overflow-x-scroll overflow-y-hidden z-10" :style="{ paddingLeft: paddingLeft * sizeMultiplier + 'rem', height: shelfHeight + 'px' }" @scroll="scrolled">
<div class="w-full h-full pt-6">
<div ref="shelf" class="w-full max-w-full bookshelf-row categorizedBookshelfRow relative overflow-x-scroll no-scroll overflow-y-hidden z-10" :style="{ paddingLeft: paddingLeft + 'em' }" @scroll="scrolled">
<div class="w-full h-full pt-6e">
<div v-if="shelf.type === 'book' || shelf.type === 'podcast'" class="flex items-center">
<template v-for="(entity, index) in shelf.entities">
<cards-lazy-book-card :key="entity.id" :ref="`shelf-book-${entity.id}`" :index="index" :width="bookCoverWidth" :height="bookCoverHeight" :book-cover-aspect-ratio="bookCoverAspectRatio" :book-mount="entity" :continue-listening-shelf="continueListeningShelf" class="relative mx-2" @hook:updated="updatedBookCard" @select="selectItem" @edit="editItem" />
<cards-lazy-book-card :key="entity.id" :ref="`shelf-book-${entity.id}`" :index="index" :book-mount="entity" :continue-listening-shelf="continueListeningShelf" class="relative mx-2e" @hook:updated="updatedBookCard" @select="selectItem" @edit="editItem" />
</template>
</div>
<div v-if="shelf.type === 'episode'" class="flex items-center">
<template v-for="(entity, index) in shelf.entities">
<cards-lazy-book-card
:key="entity.recentEpisode.id"
:ref="`shelf-episode-${entity.recentEpisode.id}`"
:index="index"
:width="bookCoverWidth"
:height="bookCoverHeight"
:book-cover-aspect-ratio="bookCoverAspectRatio"
:book-mount="entity"
:continue-listening-shelf="continueListeningShelf"
class="relative mx-2"
@hook:updated="updatedBookCard"
@select="selectItem"
@editPodcast="editItem"
@edit="editEpisode"
/>
<cards-lazy-book-card :key="entity.recentEpisode.id" :ref="`shelf-episode-${entity.recentEpisode.id}`" :index="index" :book-mount="entity" :continue-listening-shelf="continueListeningShelf" class="relative mx-2e" @hook:updated="updatedBookCard" @select="selectItem" @editPodcast="editItem" @edit="editEpisode" />
</template>
</div>
<div v-if="shelf.type === 'series'" class="flex items-center">
<template v-for="entity in shelf.entities">
<cards-lazy-series-card :key="entity.name" :series-mount="entity" :height="bookCoverHeight" :width="bookCoverWidth * 2" :book-cover-aspect-ratio="bookCoverAspectRatio" class="relative mx-2" @hook:updated="updatedBookCard" />
<cards-lazy-series-card :key="entity.name" :series-mount="entity" class="relative mx-2e" @hook:updated="updatedBookCard" />
</template>
</div>
<div v-if="shelf.type === 'tags'" class="flex items-center">
<template v-for="entity in shelf.entities">
<cards-group-card :key="entity.name" :group="entity" :height="bookCoverHeight" :width="bookCoverWidth * 2" :book-cover-aspect-ratio="bookCoverAspectRatio" class="relative mx-2" @hook:updated="updatedBookCard" />
<cards-group-card :key="entity.name" :group="entity" class="relative mx-2e" @hook:updated="updatedBookCard" />
</template>
</div>
<div v-if="shelf.type === 'authors'" class="flex items-center">
<template v-for="entity in shelf.entities">
<cards-author-card :key="entity.id" :width="bookCoverWidth / 1.25" :height="bookCoverWidth" :author="entity" :size-multiplier="sizeMultiplier" @hook:updated="updatedBookCard" class="pb-6 mx-2" @edit="editAuthor" />
<cards-author-card :key="entity.id" :author="entity" @hook:updated="updatedBookCard" class="mx-2e" @edit="editAuthor" />
</template>
</div>
<div v-if="shelf.type === 'narrators'" class="flex items-center">
<template v-for="entity in shelf.entities">
<cards-narrator-card :key="entity.name" :width="150" :height="100" :narrator="entity" :size-multiplier="sizeMultiplier" @hook:updated="updatedBookCard" class="pb-6 mx-2" />
<cards-narrator-card :key="entity.name" :narrator="entity" @hook:updated="updatedBookCard" class="mx-2e" />
</template>
</div>
</div>
</div>
<div class="absolute text-center categoryPlacard transform z-30 bottom-px left-4 md:left-8 w-44 rounded-md" style="height: 22px">
<div class="w-full h-full shinyBlack flex items-center justify-center rounded-sm border">
<p class="transform text-sm">{{ $strings[shelf.labelStringKey] }}</p>
<div class="relative">
<div class="relative text-center categoryPlacard transform z-30 top-0 left-4e md:left-8e w-44e rounded-md">
<div class="w-full h-full shinyBlack flex items-center justify-center rounded-sm border" :style="{ padding: `0em 0.5em` }">
<p :style="{ fontSize: 0.9 + 'em' }">{{ $strings[shelf.labelStringKey] }}</p>
</div>
</div>
</div>
<div class="bookshelfDividerCategorized h-6 w-full absolute bottom-0 left-0 right-0 z-20"></div>
<div v-show="canScrollLeft && !isScrolling" class="hidden sm:flex absolute top-0 left-0 w-32 pr-8 bg-black book-shelf-arrow-left items-center justify-center cursor-pointer opacity-0 hover:opacity-100 z-30" @click="scrollLeft">
<span class="material-icons text-6xl text-white">chevron_left</span>
<div class="bookshelfDividerCategorized h-6e w-full absolute top-0 left-0 right-0 z-20"></div>
</div>
<div v-show="canScrollRight && !isScrolling" class="hidden sm:flex absolute top-0 right-0 w-32 pl-8 bg-black book-shelf-arrow-right items-center justify-center cursor-pointer opacity-0 hover:opacity-100 z-30" @click="scrollRight">
<span class="material-icons text-6xl text-white">chevron_right</span>
<div v-show="canScrollLeft && !isScrolling" class="hidden sm:flex absolute top-0 left-0 w-32 pr-8 bg-black book-shelf-arrow-left items-center justify-center cursor-pointer opacity-0 hover:opacity-100 z-40" @click="scrollLeft">
<span class="material-symbols text-white" :style="{ fontSize: 3.75 + 'em' }">chevron_left</span>
</div>
<div v-show="canScrollRight && !isScrolling" class="hidden sm:flex absolute top-0 right-0 w-32 pl-8 bg-black book-shelf-arrow-right items-center justify-center cursor-pointer opacity-0 hover:opacity-100 z-40" @click="scrollRight">
<span class="material-symbols text-white" :style="{ fontSize: 3.75 + 'em' }">chevron_right</span>
</div>
</div>
</template>
@@ -74,9 +60,6 @@ export default {
type: Object,
default: () => {}
},
sizeMultiplier: Number,
bookCoverWidth: Number,
bookCoverAspectRatio: Number,
continueListeningShelf: Boolean
},
data() {
@@ -89,12 +72,8 @@ export default {
}
},
computed: {
bookCoverHeight() {
return this.bookCoverWidth * this.bookCoverAspectRatio
},
shelfHeight() {
if (this.shelf.type === 'narrators') return 148
return this.bookCoverHeight + 48
sizeMultiplier() {
return this.$store.getters['user/getSizeMultiplier']
},
paddingLeft() {
if (window.innerWidth < 768) return 1
@@ -218,13 +197,13 @@ export default {
}
.book-shelf-arrow-right {
height: calc(100% - 24px);
height: calc(100% - 1.5em);
background: rgb(48, 48, 48);
background: linear-gradient(90deg, rgba(48, 48, 48, 0) 0%, rgba(25, 25, 25, 0.25) 8%, rgba(17, 17, 17, 0.4) 28%, rgba(17, 17, 17, 0.6) 71%, rgba(10, 10, 10, 0.6) 86%, rgba(0, 0, 0, 0.7) 100%);
}
.book-shelf-arrow-left {
height: calc(100% - 24px);
height: calc(100% - 1.5em);
background: rgb(48, 48, 48);
background: linear-gradient(-90deg, rgba(48, 48, 48, 0) 0%, rgba(25, 25, 25, 0.25) 8%, rgba(17, 17, 17, 0.4) 28%, rgba(17, 17, 17, 0.6) 71%, rgba(10, 10, 10, 0.6) 86%, rgba(0, 0, 0, 0.7) 100%);
}
</style>
</style>

View File

@@ -22,9 +22,13 @@
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 17V7m0 10a2 2 0 01-2 2H5a2 2 0 01-2-2V7a2 2 0 012-2h2a2 2 0 012 2m0 10a2 2 0 002 2h2a2 2 0 002-2M9 7a2 2 0 012-2h2a2 2 0 012 2m0 10V7m0 10a2 2 0 002 2h2a2 2 0 002-2V7a2 2 0 00-2-2h-2a2 2 0 00-2 2" />
</svg>
</nuxt-link>
<nuxt-link v-if="showPlaylists" :to="`/library/${currentLibraryId}/bookshelf/playlists`" class="flex-grow h-full flex justify-center items-center" :class="isPlaylistsPage ? 'bg-primary bg-opacity-80' : 'bg-primary bg-opacity-40'">
<p v-if="isPlaylistsPage || isPodcastLibrary" class="text-sm">{{ $strings.ButtonPlaylists }}</p>
<span v-else class="material-symbols text-lg">&#xe03d;</span>
</nuxt-link>
<nuxt-link v-if="isBookLibrary" :to="`/library/${currentLibraryId}/bookshelf/collections`" class="flex-grow h-full flex justify-center items-center" :class="isCollectionsPage ? 'bg-primary bg-opacity-80' : 'bg-primary bg-opacity-40'">
<p v-if="isCollectionsPage" class="text-sm">{{ $strings.ButtonCollections }}</p>
<span v-else class="material-icons-outlined text-lg">collections_bookmark</span>
<span v-else class="material-symbols text-lg">&#xe431;</span>
</nuxt-link>
<nuxt-link v-if="isBookLibrary" :to="`/library/${currentLibraryId}/authors`" class="flex-grow h-full flex justify-center items-center" :class="isAuthorsPage ? 'bg-primary bg-opacity-80' : 'bg-primary bg-opacity-40'">
<p v-if="isAuthorsPage" class="text-sm">{{ $strings.ButtonAuthors }}</p>
@@ -36,7 +40,7 @@
</svg>
</nuxt-link>
<nuxt-link v-if="isPodcastLibrary && userIsAdminOrUp" :to="`/library/${currentLibraryId}/podcast/search`" class="flex-grow h-full flex justify-center items-center" :class="isPodcastSearchPage ? 'bg-primary bg-opacity-80' : 'bg-primary bg-opacity-40'">
<p class="text-sm">{{ $strings.ButtonSearch }}</p>
<p class="text-sm">{{ $strings.ButtonAdd }}</p>
</nuxt-link>
</div>
<div id="toolbar" class="absolute top-10 md:top-0 left-0 w-full h-10 md:h-full z-40 flex items-center justify-end md:justify-start px-2 md:px-8">
@@ -49,7 +53,6 @@
<span class="font-mono">{{ numShowing }}</span>
</div>
<div class="flex-grow" />
<ui-checkbox v-if="!isBatchSelecting" v-model="settings.collapseBookSeries" :label="$strings.LabelCollapseSeries" checkbox-bg="bg" check-color="white" small class="mr-2" @input="updateCollapseBookSeries" />
<!-- RSS feed -->
<ui-tooltip v-if="seriesRssFeed" :text="$strings.LabelOpenRSSFeed" direction="top">
@@ -64,9 +67,6 @@
<div class="flex-grow hidden sm:inline-block" />
<!-- collapse series checkbox -->
<ui-checkbox v-if="isLibraryPage && isBookLibrary && !isBatchSelecting" v-model="settings.collapseSeries" :label="$strings.LabelCollapseSeries" checkbox-bg="bg" check-color="white" small class="mr-2" @input="updateCollapseSeries" />
<!-- library filter select -->
<controls-library-filter-select v-if="isLibraryPage && !isBatchSelecting" v-model="settings.filterBy" class="w-36 sm:w-44 md:w-48 h-7.5 ml-1 sm:ml-4" @change="updateFilter" />
@@ -89,11 +89,20 @@
<div class="flex-grow" />
<p>{{ $strings.MessageSearchResultsFor }} "{{ searchQuery }}"</p>
<div class="flex-grow" />
<ui-context-menu-dropdown v-if="contextMenuItems.length" :items="contextMenuItems" :menu-width="110" class="ml-2" @action="contextMenuAction" />
</template>
<!-- authors page -->
<template v-else-if="page === 'authors'">
<div class="flex-grow" />
<ui-btn v-if="userCanUpdate && authors && authors.length && !isBatchSelecting" :loading="processingAuthors" color="primary" small @click="matchAllAuthors">{{ $strings.ButtonMatchAllAuthors }}</ui-btn>
<ui-btn v-if="userCanUpdate && authors?.length && !isBatchSelecting" :loading="processingAuthors" color="primary" small @click="matchAllAuthors">{{ $strings.ButtonMatchAllAuthors }}</ui-btn>
<!-- author sort select -->
<controls-sort-select v-if="authors?.length" v-model="settings.authorSortBy" :descending.sync="settings.authorSortDesc" :items="authorSortItems" class="w-36 sm:w-44 md:w-48 h-7.5 ml-1 sm:ml-4" @change="updateAuthorSort" />
</template>
<!-- home page -->
<template v-else-if="isHome">
<div class="flex-grow" />
<ui-context-menu-dropdown v-if="contextMenuItems.length" :items="contextMenuItems" :menu-width="110" class="ml-2" @action="contextMenuAction" />
</template>
</div>
</div>
@@ -144,11 +153,14 @@ export default {
if (this.isSeriesRemovedFromContinueListening) {
items.push({
text: 'Re-Add Series to Continue Listening',
text: this.$strings.LabelReAddSeriesToContinueListening,
action: 're-add-to-continue-listening'
})
}
this.addSubtitlesMenuItem(items)
this.addCollapseSubSeriesMenuItem(items)
return items
},
seriesSortItems() {
@@ -176,6 +188,34 @@ export default {
{
text: this.$strings.LabelTotalDuration,
value: 'totalDuration'
},
{
text: this.$strings.LabelRandomly,
value: 'random'
}
]
},
authorSortItems() {
return [
{
text: this.$strings.LabelAuthorFirstLast,
value: 'name'
},
{
text: this.$strings.LabelAuthorLastFirst,
value: 'lastFirst'
},
{
text: this.$strings.LabelNumberOfBooks,
value: 'numBooks'
},
{
text: this.$strings.LabelAddedAt,
value: 'addedAt'
},
{
text: this.$strings.LabelUpdatedAt,
value: 'updatedAt'
}
]
},
@@ -287,18 +327,113 @@ export default {
if (this.isPodcastLibrary && this.isLibraryPage && this.userCanDownload) {
items.push({
text: 'Export OPML',
text: this.$strings.LabelExportOPML,
action: 'export-opml'
})
}
this.addSubtitlesMenuItem(items)
this.addCollapseSeriesMenuItem(items)
return items
},
showPlaylists() {
return this.$store.state.libraries.numUserPlaylists > 0
}
},
methods: {
addSubtitlesMenuItem(items) {
if (this.isBookLibrary && (!this.page || this.page === 'search')) {
if (this.settings.showSubtitles) {
items.push({
text: this.$strings.LabelHideSubtitles,
action: 'hide-subtitles'
})
} else {
items.push({
text: this.$strings.LabelShowSubtitles,
action: 'show-subtitles'
})
}
}
},
addCollapseSeriesMenuItem(items) {
if (this.isLibraryPage && this.isBookLibrary && !this.isBatchSelecting) {
if (this.settings.collapseSeries) {
items.push({
text: this.$strings.LabelExpandSeries,
action: 'expand-series'
})
} else {
items.push({
text: this.$strings.LabelCollapseSeries,
action: 'collapse-series'
})
}
}
},
addCollapseSubSeriesMenuItem(items) {
if (this.selectedSeries && this.isBookLibrary && !this.isBatchSelecting) {
if (this.settings.collapseBookSeries) {
items.push({
text: this.$strings.LabelExpandSubSeries,
action: 'expand-sub-series'
})
} else {
items.push({
text: this.$strings.LabelCollapseSubSeries,
action: 'collapse-sub-series'
})
}
}
},
handleSubtitlesAction(action) {
if (action === 'show-subtitles') {
this.settings.showSubtitles = true
this.updateShowSubtitles()
return true
}
if (action === 'hide-subtitles') {
this.settings.showSubtitles = false
this.updateShowSubtitles()
return true
}
return false
},
handleCollapseSeriesAction(action) {
if (action === 'collapse-series') {
this.settings.collapseSeries = true
this.updateCollapseSeries()
return true
}
if (action === 'expand-series') {
this.settings.collapseSeries = false
this.updateCollapseSeries()
return true
}
return false
},
handleCollapseSubSeriesAction(action) {
if (action === 'collapse-sub-series') {
this.settings.collapseBookSeries = true
this.updateCollapseSubSeries()
return true
}
if (action === 'expand-sub-series') {
this.settings.collapseBookSeries = false
this.updateCollapseSubSeries()
return true
}
return false
},
contextMenuAction({ action }) {
if (action === 'export-opml') {
this.exportOPML()
return
} else if (this.handleSubtitlesAction(action)) {
return
} else if (this.handleCollapseSeriesAction(action)) {
return
}
},
exportOPML() {
@@ -319,6 +454,10 @@ export default {
return
}
this.markSeriesFinished()
} else if (this.handleSubtitlesAction(action)) {
return
} else if (this.handleCollapseSubSeriesAction(action)) {
return
}
},
showOpenSeriesRSSFeed() {
@@ -334,11 +473,11 @@ export default {
this.$axios
.$get(`/api/me/series/${this.seriesId}/readd-to-continue-listening`)
.then(() => {
this.$toast.success('Series re-added to continue listening')
this.$toast.success(this.$strings.ToastItemUpdateSuccess)
})
.catch((error) => {
console.error('Failed to re-add series to continue listening', error)
this.$toast.error('Failed to re-add series to continue listening')
this.$toast.error(this.$strings.ToastItemUpdateFailed)
})
.finally(() => {
this.processingSeries = false
@@ -365,7 +504,7 @@ export default {
})
if (!response) {
console.error(`Author ${author.name} not found`)
this.$toast.error(`Author ${author.name} not found`)
this.$toast.error(this.$getString('ToastAuthorNotFound', [author.name]))
} else if (response.updated) {
if (response.author.imagePath) console.log(`Author ${response.author.name} was updated`)
else console.log(`Author ${response.author.name} was updated (no image found)`)
@@ -383,13 +522,13 @@ export default {
this.$axios
.$delete(`/api/libraries/${this.currentLibraryId}/issues`)
.then(() => {
this.$toast.success('Removed library items with issues')
this.$toast.success(this.$strings.ToastRemoveItemsWithIssuesSuccess)
this.$router.push(`/library/${this.currentLibraryId}/bookshelf`)
this.$store.dispatch('libraries/fetch', this.currentLibraryId)
})
.catch((error) => {
console.error('Failed to remove library items with issues', error)
this.$toast.error('Failed to remove library items with issues')
this.$toast.error(this.$strings.ToastRemoveItemsWithIssuesFailed)
})
.finally(() => {
this.processingIssues = false
@@ -445,7 +584,13 @@ export default {
updateCollapseSeries() {
this.saveSettings()
},
updateCollapseBookSeries() {
updateCollapseSubSeries() {
this.saveSettings()
},
updateShowSubtitles() {
this.saveSettings()
},
updateAuthorSort() {
this.saveSettings()
},
saveSettings() {

View File

@@ -2,7 +2,7 @@
<div>
<div class="w-44 fixed left-0 top-16 bg-bg bg-opacity-100 md:bg-opacity-70 shadow-lg border-r border-white border-opacity-5 py-3 transform transition-transform mb-12 overflow-y-auto" :class="wrapperClass + ' ' + (streamLibraryItem ? 'h-[calc(100%-270px)]' : 'h-[calc(100%-110px)]')" v-click-outside="clickOutside">
<div v-show="isMobilePortrait" class="flex items-center justify-end pb-2 px-4 mb-1" @click="closeDrawer">
<span class="material-icons text-2xl">arrow_back</span>
<span class="material-symbols text-2xl">arrow_back</span>
</div>
<nuxt-link v-for="route in configRoutes" :key="route.id" :to="route.path" class="w-full px-3 h-12 border-b border-primary border-opacity-30 flex items-center cursor-pointer relative" :class="routeName === route.id ? 'bg-primary bg-opacity-70' : 'hover:bg-primary hover:bg-opacity-30'">
@@ -10,16 +10,16 @@
<div v-show="routeName === route.iod" class="h-full w-0.5 bg-yellow-400 absolute top-0 left-0" />
</nuxt-link>
<modals-changelog-view-modal v-model="showChangelogModal" :changelog="currentVersionChangelog" :currentVersion="$config.version" />
<modals-changelog-view-modal v-model="showChangelogModal" :versionData="versionData" />
</div>
<div class="w-44 h-12 px-4 border-t bg-bg border-black border-opacity-20 fixed left-0 flex flex-col justify-center" :class="wrapperClass" :style="{ bottom: streamLibraryItem ? '160px' : '0px' }">
<div class="flex justify-between">
<p class="underline font-mono text-sm" @click="clickChangelog">v{{ $config.version }}</p>
<div class="flex items-center justify-between">
<button type="button" class="underline font-mono text-sm" @click="clickChangelog">v{{ $config.version }}</button>
<p class="font-mono text-xs text-gray-300 italic">{{ Source }}</p>
<p class="text-xs text-gray-300 italic">{{ Source }}</p>
</div>
<a v-if="hasUpdate" :href="githubTagUrl" target="_blank" class="text-warning text-xs">Latest: {{ latestVersion }}</a>
<a v-if="hasUpdate" :href="githubTagUrl" target="_blank" class="text-warning text-xs">Latest: {{ $config.version }}</a>
</div>
</div>
</template>
@@ -99,14 +99,24 @@ export default {
id: 'config-item-metadata-utils',
title: this.$strings.HeaderItemMetadataUtils,
path: '/config/item-metadata-utils'
},
{
id: 'config-rss-feeds',
title: this.$strings.HeaderRSSFeeds,
path: '/config/rss-feeds'
},
{
id: 'config-authentication',
title: this.$strings.HeaderAuthentication,
path: '/config/authentication'
}
]
if (this.currentLibraryId) {
configRoutes.push({
id: 'config-library-stats',
id: 'library-stats',
title: this.$strings.HeaderLibraryStats,
path: '/config/library-stats'
path: `/library/${this.currentLibraryId}/stats`
})
configRoutes.push({
id: 'config-stats',
@@ -146,15 +156,9 @@ export default {
hasUpdate() {
return !!this.versionData.hasUpdate
},
latestVersion() {
return this.versionData.latestVersion
},
githubTagUrl() {
return this.versionData.githubTagUrl
},
currentVersionChangelog() {
return this.versionData.currentVersionChangelog || 'No Changelog Available'
},
streamLibraryItem() {
return this.$store.state.streamLibraryItem
}
@@ -172,4 +176,4 @@ export default {
}
}
}
</script>
</script>

View File

@@ -1,8 +1,8 @@
<template>
<div id="bookshelf" class="w-full overflow-y-auto">
<div id="bookshelf" ref="bookshelf" class="w-full overflow-y-auto" :style="{ fontSize: sizeMultiplier + 'rem' }">
<template v-for="shelf in totalShelves">
<div :key="shelf" :id="`shelf-${shelf - 1}`" class="w-full px-4 sm:px-8 relative" :class="{ bookshelfRow: !isAlternativeBookshelfView }" :style="{ height: shelfHeight + 'px' }">
<div v-if="!isAlternativeBookshelfView" class="bookshelfDivider w-full absolute bottom-0 left-0 right-0 z-20" :class="`h-${shelfDividerHeightIndex}`" />
<div :key="shelf" :id="`shelf-${shelf - 1}`" class="w-full px-4e sm:px-8e relative" :class="{ bookshelfRow: !isAlternativeBookshelfView }" :style="{ height: shelfHeight + 'px' }">
<div v-if="!isAlternativeBookshelfView" class="bookshelfDivider w-full absolute bottom-0 left-0 right-0 z-20 h-6e" />
</div>
</template>
@@ -10,7 +10,7 @@
<p class="text-center text-2xl mb-4 py-4">{{ $getString('MessageXLibraryIsEmpty', [libraryName]) }}</p>
<div v-if="userIsAdminOrUp" class="flex">
<ui-btn to="/config" color="primary" class="w-52 mr-2">{{ $strings.ButtonConfigureScanner }}</ui-btn>
<ui-btn color="success" class="w-52" @click="scan">{{ $strings.ButtonScanLibrary }}</ui-btn>
<ui-btn color="success" class="w-52" :loading="isScanningLibrary || tempIsScanning" @click="scan">{{ $strings.ButtonScanLibrary }}</ui-btn>
</div>
</div>
<div v-else-if="!totalShelves && initialized" class="w-full py-16">
@@ -21,7 +21,7 @@
</div>
</div>
<widgets-cover-size-widget class="fixed bottom-4 right-4 z-50" />
<widgets-cover-size-widget class="fixed right-4 z-50" :style="{ bottom: streamLibraryItem ? '181px' : '16px' }" />
</div>
</template>
@@ -49,10 +49,9 @@ export default {
entityIndexesMounted: [],
entityComponentRefs: {},
currentBookWidth: 0,
pageLoadQueue: [],
isFetchingEntities: false,
scrollTimeout: null,
booksPerFetch: 100,
booksPerFetch: 0,
totalShelves: 0,
bookshelfMarginLeft: 0,
isSelectionMode: false,
@@ -62,7 +61,11 @@ export default {
currScrollTop: 0,
resizeTimeout: null,
mountWindowWidth: 0,
lastItemIndexSelected: -1
lastItemIndexSelected: -1,
tempIsScanning: false,
cardWidth: 0,
cardHeight: 0,
resizeObserver: null
}
},
watch: {
@@ -159,52 +162,46 @@ export default {
return this.$store.getters['libraries/getCurrentLibraryName']
},
bookWidth() {
const coverSize = this.$store.getters['user/getUserSetting']('bookshelfCoverSize')
if (this.isCoverSquareAspectRatio || this.entityName === 'playlists') return coverSize * 1.6
return coverSize
return this.cardWidth
},
bookHeight() {
if (this.isCoverSquareAspectRatio || this.entityName === 'playlists') return this.bookWidth
return this.bookWidth * 1.6
return this.cardHeight
},
shelfPadding() {
if (this.bookshelfWidth < 640) return 32
return 64
if (this.bookshelfWidth < 640) return 32 * this.sizeMultiplier
return 64 * this.sizeMultiplier
},
totalPadding() {
return this.shelfPadding * 2
},
entityWidth() {
if (this.entityName === 'series' || this.entityName === 'collections') {
if (this.bookWidth * 2 > this.bookshelfWidth - this.shelfPadding) return this.bookWidth * 1.6
return this.bookWidth * 2
}
return this.bookWidth
return this.cardWidth
},
entityHeight() {
return this.bookHeight
return this.cardHeight
},
shelfDividerHeightIndex() {
return 6
shelfPaddingHeight() {
return 16
},
shelfHeight() {
if (this.isAlternativeBookshelfView) {
const isItemEntity = this.entityName === 'series-books' || this.entityName === 'items'
const extraTitleSpace = isItemEntity ? 80 : this.entityName === 'albums' ? 60 : 40
return this.entityHeight + extraTitleSpace * this.sizeMultiplier
}
return this.entityHeight + 40
const dividerHeight = this.isAlternativeBookshelfView ? 0 : 24 // h-6
return this.cardHeight + (this.shelfPaddingHeight + dividerHeight) * this.sizeMultiplier
},
totalEntityCardWidth() {
// Includes margin
return this.entityWidth + 24
return this.entityWidth + 24 * this.sizeMultiplier
},
selectedMediaItems() {
return this.$store.state.globals.selectedMediaItems || []
},
sizeMultiplier() {
const baseSize = this.isCoverSquareAspectRatio ? 192 : 120
return this.entityWidth / baseSize
return this.$store.getters['user/getSizeMultiplier']
},
streamLibraryItem() {
return this.$store.state.streamLibraryItem
},
isScanningLibrary() {
return !!this.$store.getters['tasks/getRunningLibraryScanTask'](this.currentLibraryId)
}
},
methods: {
@@ -313,9 +310,9 @@ export default {
this.currentSFQueryString = this.buildSearchParams()
}
const entityPath = this.entityName === 'series-books' ? 'items' : this.entityName
let entityPath = this.entityName === 'series-books' ? 'items' : this.entityName
const sfQueryString = this.currentSFQueryString ? this.currentSFQueryString + '&' : ''
const fullQueryString = `?${sfQueryString}limit=${this.booksPerFetch}&page=${page}&minified=1&include=rssfeed,numEpisodesIncomplete`
const fullQueryString = `?${sfQueryString}limit=${this.booksPerFetch}&page=${page}&minified=1&include=rssfeed,numEpisodesIncomplete,share`
const payload = await this.$axios.$get(`/api/libraries/${this.currentLibraryId}/${entityPath}${fullQueryString}`).catch((error) => {
console.error('failed to fetch items', error)
@@ -429,10 +426,14 @@ export default {
rebuild() {
this.initSizeData()
var lastBookIndex = Math.min(this.totalEntities, this.shelvesPerPage * this.entitiesPerShelf)
var lastBookIndex = Math.min(this.totalEntities, this.booksPerFetch)
this.entityIndexesMounted = []
for (let i = 0; i < lastBookIndex; i++) {
this.entityIndexesMounted.push(i)
if (!this.entities[i]) {
const page = Math.floor(i / this.booksPerFetch)
this.loadPage(page)
}
}
var bookshelfEl = document.getElementById('bookshelf')
if (bookshelfEl) {
@@ -494,7 +495,8 @@ export default {
this.resetEntities()
}
},
settingsUpdated(settings) {
async settingsUpdated(settings) {
await this.cardsHelpers.setCardSize()
const wasUpdated = this.checkUpdateSearchParams()
if (wasUpdated) {
this.resetEntities()
@@ -599,6 +601,44 @@ export default {
this.executeRebuild()
}
},
shareOpen(mediaItemShare) {
if (this.entityName === 'items' || this.entityName === 'series-books') {
var indexOf = this.entities.findIndex((ent) => ent?.media?.id === mediaItemShare.mediaItemId)
if (indexOf >= 0) {
if (this.entityComponentRefs[indexOf]) {
const libraryItem = { ...this.entityComponentRefs[indexOf].libraryItem }
libraryItem.mediaItemShare = mediaItemShare
this.entityComponentRefs[indexOf].setEntity?.(libraryItem)
}
}
}
},
shareClosed(mediaItemShare) {
if (this.entityName === 'items' || this.entityName === 'series-books') {
var indexOf = this.entities.findIndex((ent) => ent?.media?.id === mediaItemShare.mediaItemId)
if (indexOf >= 0) {
if (this.entityComponentRefs[indexOf]) {
const libraryItem = { ...this.entityComponentRefs[indexOf].libraryItem }
libraryItem.mediaItemShare = null
this.entityComponentRefs[indexOf].setEntity?.(libraryItem)
}
}
}
},
updatePagesLoaded() {
let numPages = Math.ceil(this.totalEntities / this.booksPerFetch)
for (let page = 0; page < numPages; page++) {
let numEntities = Math.min(this.totalEntities - page * this.booksPerFetch, this.booksPerFetch)
this.pagesLoaded[page] = true
for (let i = 0; i < numEntities; i++) {
const index = page * this.booksPerFetch + i
if (!this.entities[index]) {
this.pagesLoaded[page] = false
break
}
}
}
},
initSizeData(_bookshelf) {
var bookshelf = _bookshelf || document.getElementById('bookshelf')
if (!bookshelf) {
@@ -615,6 +655,13 @@ export default {
this.entitiesPerShelf = Math.max(1, Math.floor((this.bookshelfWidth - this.shelfPadding) / this.totalEntityCardWidth))
this.shelvesPerPage = Math.ceil(this.bookshelfHeight / this.shelfHeight) + 2
this.bookshelfMarginLeft = (this.bookshelfWidth - this.entitiesPerShelf * this.totalEntityCardWidth) / 2
const booksPerFetch = this.entitiesPerShelf * this.shelvesPerPage
if (booksPerFetch !== this.booksPerFetch) {
this.booksPerFetch = booksPerFetch
if (this.totalEntities) {
this.updatePagesLoaded()
}
}
this.currentBookWidth = this.bookWidth
if (this.totalEntities) {
@@ -623,8 +670,8 @@ export default {
return entitiesPerShelfBefore < this.entitiesPerShelf // Books per shelf has changed
},
async init(bookshelf) {
this.checkUpdateSearchParams()
this.initSizeData(bookshelf)
this.checkUpdateSearchParams()
this.pagesLoaded[0] = true
await this.fetchEntites(0)
@@ -680,6 +727,8 @@ export default {
this.$root.socket.on('playlist_added', this.playlistAdded)
this.$root.socket.on('playlist_updated', this.playlistUpdated)
this.$root.socket.on('playlist_removed', this.playlistRemoved)
this.$root.socket.on('share_open', this.shareOpen)
this.$root.socket.on('share_closed', this.shareClosed)
} else {
console.error('Bookshelf - Socket not initialized')
}
@@ -707,6 +756,8 @@ export default {
this.$root.socket.off('playlist_added', this.playlistAdded)
this.$root.socket.off('playlist_updated', this.playlistUpdated)
this.$root.socket.off('playlist_removed', this.playlistRemoved)
this.$root.socket.off('share_open', this.shareOpen)
this.$root.socket.off('share_closed', this.shareClosed)
} else {
console.error('Bookshelf - Socket not initialized')
}
@@ -719,18 +770,20 @@ export default {
}
},
scan() {
this.tempIsScanning = true
this.$store
.dispatch('libraries/requestLibraryScan', { libraryId: this.currentLibraryId })
.then(() => {
this.$toast.success('Library scan started')
})
.catch((error) => {
console.error('Failed to start scan', error)
this.$toast.error('Failed to start scan')
this.$toast.error(this.$strings.ToastLibraryScanFailedToStart)
})
.finally(() => {
this.tempIsScanning = false
})
}
},
mounted() {
async mounted() {
await this.cardsHelpers.setCardSize()
this.initListeners()
this.routeFullPath = window.location.pathname + (window.location.search || '')
@@ -765,6 +818,6 @@ export default {
.bookshelfDivider {
background: rgb(149, 119, 90);
background: var(--bookshelf-divider-bg);
box-shadow: 2px 14px 8px #111111aa;
box-shadow: 0.125em 0.875em 0.5em #111111aa;
}
</style>
</style>

View File

@@ -1,63 +1,70 @@
<template>
<div v-if="streamLibraryItem" id="streamContainer" class="w-full fixed bottom-0 left-0 right-0 h-48 md:h-40 z-50 bg-primary px-2 md:px-4 pb-1 md:pb-4 pt-2">
<div v-if="streamLibraryItem" id="mediaPlayerContainer" class="w-full fixed bottom-0 left-0 right-0 h-48 lg:h-40 z-50 bg-primary px-2 lg:px-4 pb-1 lg:pb-4 pt-2">
<div id="videoDock" />
<nuxt-link v-if="!playerHandler.isVideo" :to="`/item/${streamLibraryItem.id}`" class="absolute left-2 top-2 md:left-4 cursor-pointer">
<covers-book-cover :library-item="streamLibraryItem" :width="bookCoverWidth" :book-cover-aspect-ratio="coverAspectRatio" />
</nuxt-link>
<div class="flex items-start mb-6 md:mb-0" :class="playerHandler.isVideo ? 'ml-4 pl-96' : isSquareCover ? 'pl-18 sm:pl-24' : 'pl-12 sm:pl-16'">
<div class="min-w-0">
<nuxt-link :to="`/item/${streamLibraryItem.id}`" class="hover:underline cursor-pointer text-sm sm:text-lg block truncate">
{{ title }}
</nuxt-link>
<div v-if="!playerHandler.isVideo" class="text-gray-400 flex items-center">
<span class="material-icons text-sm">person</span>
<div class="flex items-center">
<div v-if="podcastAuthor" class="pl-1 sm:pl-1.5 text-xs sm:text-base">{{ podcastAuthor }}</div>
<div v-else-if="musicArtists" class="pl-1 sm:pl-1.5 text-xs sm:text-base">{{ musicArtists }}</div>
<div v-else-if="authors.length" class="pl-1 sm:pl-1.5 text-xs sm:text-base">
<nuxt-link v-for="(author, index) in authors" :key="index" :to="`/author/${author.id}?library=${libraryId}`" class="hover:underline">{{ author.name }}<span v-if="index < authors.length - 1">,&nbsp;</span></nuxt-link>
</div>
<div v-else class="text-xs sm:text-base cursor-pointer pl-1 sm:pl-1.5">{{ $strings.LabelUnknown }}</div>
<widgets-explicit-indicator :explicit="isExplicit"></widgets-explicit-indicator>
<div class="absolute left-2 top-2 lg:left-4 cursor-pointer">
<covers-book-cover expand-on-click :library-item="streamLibraryItem" :width="bookCoverWidth" :book-cover-aspect-ratio="coverAspectRatio" />
</div>
<div class="flex items-start mb-6 lg:mb-0" :class="playerHandler.isVideo ? 'ml-4 pl-96' : isSquareCover ? 'pl-18 sm:pl-24' : 'pl-12 sm:pl-16'">
<div class="min-w-0 w-full">
<div class="flex items-center">
<nuxt-link :to="`/item/${streamLibraryItem.id}`" class="hover:underline cursor-pointer text-sm sm:text-lg block truncate">
{{ title }}
</nuxt-link>
<widgets-explicit-indicator v-if="isExplicit" />
</div>
<div v-if="!playerHandler.isVideo" class="text-gray-400 flex items-center w-1/2 sm:w-4/5 lg:w-2/5">
<span class="material-symbols text-sm">person</span>
<div v-if="podcastAuthor" class="pl-1 sm:pl-1.5 text-xs sm:text-base">{{ podcastAuthor }}</div>
<div v-else-if="musicArtists" class="pl-1 sm:pl-1.5 text-xs sm:text-base">{{ musicArtists }}</div>
<div v-else-if="authors.length" class="pl-1 sm:pl-1.5 text-xs sm:text-base truncate">
<nuxt-link v-for="(author, index) in authors" :key="index" :to="`/author/${author.id}`" class="hover:underline">{{ author.name }}<span v-if="index < authors.length - 1">,&nbsp;</span></nuxt-link>
</div>
<div v-else class="text-xs sm:text-base cursor-pointer pl-1 sm:pl-1.5">{{ $strings.LabelUnknown }}</div>
</div>
<div class="text-gray-400 flex items-center">
<span class="material-icons text-xs">schedule</span>
<span class="material-symbols text-xs">schedule</span>
<p class="font-mono text-xs sm:text-sm pl-1 sm:pl-1.5 pb-px">{{ totalDurationPretty }}</p>
</div>
</div>
<div class="flex-grow" />
<ui-tooltip direction="top" :text="$strings.LabelClosePlayer">
<span class="material-icons sm:px-2 py-1 md:p-4 cursor-pointer text-xl sm:text-2xl" @click="closePlayer">close</span>
<button :aria-label="$strings.LabelClosePlayer" class="material-symbols sm:px-2 py-1 lg:p-4 cursor-pointer text-xl sm:text-2xl" @click="closePlayer">close</button>
</ui-tooltip>
</div>
<player-ui
ref="audioPlayer"
:chapters="chapters"
:current-chapter="currentChapter"
:paused="!isPlaying"
:loading="playerLoading"
:bookmarks="bookmarks"
:sleep-timer-set="sleepTimerSet"
:sleep-timer-remaining="sleepTimerRemaining"
:sleep-timer-type="sleepTimerType"
:is-podcast="isPodcast"
:hasNextItemInQueue="hasNextItemInQueue"
@playPause="playPause"
@jumpForward="jumpForward"
@jumpBackward="jumpBackward"
@setVolume="setVolume"
@setPlaybackRate="setPlaybackRate"
@seek="seek"
@nextItemInQueue="playNextItemInQueue"
@close="closePlayer"
@showBookmarks="showBookmarks"
@showSleepTimer="showSleepTimerModal = true"
@showPlayerQueueItems="showPlayerQueueItemsModal = true"
@showPlayerSettings="showPlayerSettingsModal = true"
/>
<modals-bookmarks-modal v-model="showBookmarksModal" :bookmarks="bookmarks" :current-time="bookmarkCurrentTime" :library-item-id="libraryItemId" @select="selectBookmark" />
<modals-sleep-timer-modal v-model="showSleepTimerModal" :timer-set="sleepTimerSet" :timer-time="sleepTimerTime" :remaining="sleepTimerRemaining" @set="setSleepTimer" @cancel="cancelSleepTimer" @increment="incrementSleepTimer" @decrement="decrementSleepTimer" />
<modals-sleep-timer-modal v-model="showSleepTimerModal" :timer-set="sleepTimerSet" :timer-type="sleepTimerType" :remaining="sleepTimerRemaining" :has-chapters="!!chapters.length" @set="setSleepTimer" @cancel="cancelSleepTimer" @increment="incrementSleepTimer" @decrement="decrementSleepTimer" />
<modals-player-queue-items-modal v-model="showPlayerQueueItemsModal" :library-item-id="libraryItemId" />
<modals-player-queue-items-modal v-model="showPlayerQueueItemsModal" />
<modals-player-settings-modal v-model="showPlayerSettingsModal" />
</div>
</template>
@@ -76,19 +83,18 @@ export default {
currentTime: 0,
showSleepTimerModal: false,
showPlayerQueueItemsModal: false,
showPlayerSettingsModal: false,
sleepTimerSet: false,
sleepTimerTime: 0,
sleepTimerRemaining: 0,
sleepTimerType: null,
sleepTimer: null,
displayTitle: null,
currentPlaybackRate: 1,
syncFailedToast: null
syncFailedToast: null,
coverAspectRatio: 1
}
},
computed: {
coverAspectRatio() {
return this.$store.getters['libraries/getBookCoverAspectRatio']
},
isSquareCover() {
return this.coverAspectRatio === 1
},
@@ -138,7 +144,7 @@ export default {
return this.streamLibraryItem?.mediaType === 'music'
},
isExplicit() {
return this.mediaMetadata.explicit || false
return !!this.mediaMetadata.explicit
},
mediaMetadata() {
return this.media.metadata || {}
@@ -147,6 +153,9 @@ export default {
if (this.streamEpisode) return this.streamEpisode.chapters || []
return this.media.chapters || []
},
currentChapter() {
return this.chapters.find((chapter) => chapter.start <= this.currentTime && this.currentTime < chapter.end)
},
title() {
if (this.playerHandler.displayTitle) return this.playerHandler.displayTitle
return this.mediaMetadata.title || 'No Title'
@@ -169,6 +178,16 @@ export default {
if (!this.isMusic) return null
return this.mediaMetadata.artists.join(', ')
},
hasNextItemInQueue() {
return this.currentPlayerQueueIndex < this.playerQueueItems.length - 1
},
currentPlayerQueueIndex() {
if (!this.libraryItemId) return -1
return this.playerQueueItems.findIndex((i) => {
if (this.streamEpisode?.id) return i.episodeId === this.streamEpisode.id
return i.libraryItemId === this.libraryItemId
})
},
playerQueueItems() {
return this.$store.state.playerQueueItems || []
}
@@ -206,14 +225,18 @@ export default {
this.$store.commit('setIsPlaying', isPlaying)
this.updateMediaSessionPlaybackState()
},
setSleepTimer(seconds) {
setSleepTimer(time) {
this.sleepTimerSet = true
this.sleepTimerTime = seconds
this.sleepTimerRemaining = seconds
this.runSleepTimer()
this.showSleepTimerModal = false
this.sleepTimerType = time.timerType
if (this.sleepTimerType === this.$constants.SleepTimerTypes.COUNTDOWN) {
this.runSleepTimer(time)
}
},
runSleepTimer() {
runSleepTimer(time) {
this.sleepTimerRemaining = time.seconds
var lastTick = Date.now()
clearInterval(this.sleepTimer)
this.sleepTimer = setInterval(() => {
@@ -222,12 +245,23 @@ export default {
this.sleepTimerRemaining -= elapsed / 1000
if (this.sleepTimerRemaining <= 0) {
this.clearSleepTimer()
this.playerHandler.pause()
this.$toast.info('Sleep Timer Done.. zZzzZz')
this.sleepTimerEnd()
}
}, 1000)
},
checkChapterEnd(time) {
if (!this.currentChapter) return
const chapterEndTime = this.currentChapter.end
const tolerance = 0.75
if (time >= chapterEndTime - tolerance) {
this.sleepTimerEnd()
}
},
sleepTimerEnd() {
this.clearSleepTimer()
this.playerHandler.pause()
this.$toast.info('Sleep Timer Done.. zZzzZz')
},
cancelSleepTimer() {
this.showSleepTimerModal = false
this.clearSleepTimer()
@@ -237,6 +271,7 @@ export default {
this.sleepTimerRemaining = 0
this.sleepTimer = null
this.sleepTimerSet = false
this.sleepTimerType = null
},
incrementSleepTimer(amount) {
if (!this.sleepTimerSet) return
@@ -277,6 +312,10 @@ export default {
if (this.$refs.audioPlayer) {
this.$refs.audioPlayer.setCurrentTime(time)
}
if (this.sleepTimerType === this.$constants.SleepTimerTypes.CHAPTER && this.sleepTimerSet) {
this.checkChapterEnd(time)
}
},
setDuration(duration) {
this.totalDuration = duration
@@ -349,7 +388,7 @@ export default {
}
if ('mediaSession' in navigator) {
var coverImageSrc = this.$store.getters['globals/getLibraryItemCoverSrc'](this.streamLibraryItem, '/Logo.png')
var coverImageSrc = this.$store.getters['globals/getLibraryItemCoverSrc'](this.streamLibraryItem, '/Logo.png', true)
const artwork = [
{
src: coverImageSrc
@@ -380,7 +419,7 @@ export default {
if (this.playerHandler.isPlayingLocalItem && this.playerHandler.currentStreamId === data.stream) {
if (!data.numSegments) return
var chunks = data.chunks
console.log(`[StreamContainer] Stream Progress ${data.percent}`)
console.log(`[MediaPlayerContainer] Stream Progress ${data.percent}`)
if (this.$refs.audioPlayer) {
this.$refs.audioPlayer.setChunksReady(chunks, data.numSegments)
} else {
@@ -397,17 +436,17 @@ export default {
this.playerHandler.prepareOpenSession(session, this.currentPlaybackRate)
},
streamOpen(session) {
console.log(`[StreamContainer] Stream session open`, session)
console.log(`[MediaPlayerContainer] Stream session open`, session)
},
streamClosed(streamId) {
// Stream was closed from the server
if (this.playerHandler.isPlayingLocalItem && this.playerHandler.currentStreamId === streamId) {
console.warn('[StreamContainer] Closing stream due to request from server')
console.warn('[MediaPlayerContainer] Closing stream due to request from server')
this.playerHandler.closePlayer()
}
},
streamReady() {
console.log(`[StreamContainer] Stream Ready`)
console.log(`[MediaPlayerContainer] Stream Ready`)
if (this.$refs.audioPlayer) {
this.$refs.audioPlayer.setStreamReady()
} else {
@@ -417,7 +456,7 @@ export default {
streamError(streamId) {
// Stream had critical error from the server
if (this.playerHandler.isPlayingLocalItem && this.playerHandler.currentStreamId === streamId) {
console.warn('[StreamContainer] Closing stream due to stream error from server')
console.warn('[MediaPlayerContainer] Closing stream due to stream error from server')
this.playerHandler.closePlayer()
}
},
@@ -433,6 +472,30 @@ export default {
this.playerHandler.switchPlayer()
}
},
playNextItemInQueue() {
if (this.hasNextItemInQueue) {
this.playQueueItem({ index: this.currentPlayerQueueIndex + 1 })
}
},
/**
* @param {{ index: number }} payload
*/
playQueueItem(payload) {
if (payload?.index === undefined) {
console.error('playQueueItem: No index provided')
return
}
if (!this.playerQueueItems[payload.index]) {
console.error('playQueueItem: No item found at index', payload.index)
return
}
const item = this.playerQueueItems[payload.index]
this.playLibraryItem({
libraryItemId: item.libraryItemId,
episodeId: item.episodeId || null,
queueItems: this.playerQueueItems
})
},
async playLibraryItem(payload) {
const libraryItemId = payload.libraryItemId
const episodeId = payload.episodeId || null
@@ -457,6 +520,9 @@ export default {
episodeId,
queueItems: payload.queueItems || []
})
// Set cover aspect ratio for this item's library since the library may change
this.coverAspectRatio = this.$store.getters['libraries/getBookCoverAspectRatio']
this.$nextTick(() => {
if (this.$refs.audioPlayer) this.$refs.audioPlayer.checkUpdateChapterTrack()
})
@@ -482,6 +548,7 @@ export default {
this.$eventBus.$on('cast-session-active', this.castSessionActive)
this.$eventBus.$on('playback-seek', this.seek)
this.$eventBus.$on('playback-time-update', this.playbackTimeUpdate)
this.$eventBus.$on('play-queue-item', this.playQueueItem)
this.$eventBus.$on('play-item', this.playLibraryItem)
this.$eventBus.$on('pause-item', this.pauseItem)
},
@@ -489,6 +556,7 @@ export default {
this.$eventBus.$off('cast-session-active', this.castSessionActive)
this.$eventBus.$off('playback-seek', this.seek)
this.$eventBus.$off('playback-time-update', this.playbackTimeUpdate)
this.$eventBus.$off('play-queue-item', this.playQueueItem)
this.$eventBus.$off('play-item', this.playLibraryItem)
this.$eventBus.$off('pause-item', this.pauseItem)
}
@@ -496,7 +564,7 @@ export default {
</script>
<style>
#streamContainer {
#mediaPlayerContainer {
box-shadow: 0px -6px 8px #1111113f;
}
</style>

View File

@@ -1,11 +1,10 @@
<template>
<div class="bg-bg rounded-md shadow-lg border border-white border-opacity-5 p-4 mb-8">
<div class="bg-bg rounded-md shadow-lg border border-white border-opacity-5 p-2 sm:p-4 mb-8">
<div class="flex items-center mb-2">
<slot name="header-prefix"></slot>
<h1 class="text-xl">{{ headerText }}</h1>
<div v-if="showAddButton" class="mx-2 w-7 h-7 flex items-center justify-center rounded-full cursor-pointer hover:bg-white hover:bg-opacity-10 text-center" @click="clicked">
<button type="button" class="material-icons" :aria-label="$strings.ButtonAdd + ': ' + headerText" style="font-size: 1.4rem">add</button>
</div>
<slot name="header-items"></slot>
</div>
<p v-if="description" id="settings-description" class="mb-6 text-gray-200" v-html="description" />
@@ -19,14 +18,9 @@ export default {
props: {
headerText: String,
description: String,
note: String,
showAddButton: Boolean
note: String
},
methods: {
clicked() {
this.$emit('clicked')
}
}
methods: {}
}
</script>

View File

@@ -3,123 +3,133 @@
<!-- ugly little workaround to cover up the shadow overlapping the bookshelf toolbar -->
<div v-if="isShowingBookshelfToolbar" class="absolute top-0 -right-4 w-4 bg-bg h-10 pointer-events-none" />
<nuxt-link :to="`/library/${currentLibraryId}`" class="w-full h-20 flex flex-col items-center justify-center text-white border-b border-primary border-opacity-70 hover:bg-primary cursor-pointer relative" :class="homePage ? 'bg-primary bg-opacity-80' : 'bg-bg bg-opacity-60'">
<svg xmlns="http://www.w3.org/2000/svg" class="h-6 w-6" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M3 12l2-2m0 0l7-7 7 7M5 10v10a1 1 0 001 1h3m10-11l2 2m-2-2v10a1 1 0 01-1 1h-3m-6 0a1 1 0 001-1v-4a1 1 0 011-1h2a1 1 0 011 1v4a1 1 0 001 1m-6 0h6" />
</svg>
<div id="siderail-buttons-container" :class="{ 'player-open': streamLibraryItem }" class="w-full overflow-y-auto overflow-x-hidden">
<nuxt-link :to="`/library/${currentLibraryId}`" class="w-full h-20 flex flex-col items-center justify-center text-white border-b border-primary border-opacity-70 hover:bg-primary cursor-pointer relative" :class="homePage ? 'bg-primary bg-opacity-80' : 'bg-bg bg-opacity-60'">
<svg xmlns="http://www.w3.org/2000/svg" class="h-6 w-6" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M3 12l2-2m0 0l7-7 7 7M5 10v10a1 1 0 001 1h3m10-11l2 2m-2-2v10a1 1 0 01-1 1h-3m-6 0a1 1 0 001-1v-4a1 1 0 011-1h2a1 1 0 011 1v4a1 1 0 001 1m-6 0h6" />
</svg>
<p class="pt-1.5 text-center leading-4" style="font-size: 0.9rem">{{ $strings.ButtonHome }}</p>
<p class="pt-1.5 text-center leading-4" style="font-size: 0.9rem">{{ $strings.ButtonHome }}</p>
<div v-show="homePage" class="h-full w-0.5 bg-yellow-400 absolute top-0 left-0" />
</nuxt-link>
<div v-show="homePage" class="h-full w-0.5 bg-yellow-400 absolute top-0 left-0" />
</nuxt-link>
<nuxt-link v-if="isPodcastLibrary" :to="`/library/${currentLibraryId}/podcast/latest`" class="w-full h-20 flex flex-col items-center justify-center text-white border-b border-primary border-opacity-70 hover:bg-primary cursor-pointer relative" :class="isPodcastLatestPage ? 'bg-primary bg-opacity-80' : 'bg-bg bg-opacity-60'">
<span class="material-icons text-2xl">format_list_bulleted</span>
<nuxt-link v-if="isPodcastLibrary" :to="`/library/${currentLibraryId}/podcast/latest`" class="w-full h-20 flex flex-col items-center justify-center text-white border-b border-primary border-opacity-70 hover:bg-primary cursor-pointer relative" :class="isPodcastLatestPage ? 'bg-primary bg-opacity-80' : 'bg-bg bg-opacity-60'">
<span class="material-symbols text-2xl">&#xe241;</span>
<p class="pt-1 text-center leading-4" style="font-size: 0.9rem">{{ $strings.ButtonLatest }}</p>
<p class="pt-1 text-center leading-4" style="font-size: 0.9rem">{{ $strings.ButtonLatest }}</p>
<div v-show="isPodcastLatestPage" class="h-full w-0.5 bg-yellow-400 absolute top-0 left-0" />
</nuxt-link>
<div v-show="isPodcastLatestPage" class="h-full w-0.5 bg-yellow-400 absolute top-0 left-0" />
</nuxt-link>
<nuxt-link :to="`/library/${currentLibraryId}/bookshelf`" class="w-full h-20 flex flex-col items-center justify-center text-white border-b border-primary border-opacity-70 hover:bg-primary cursor-pointer relative" :class="showLibrary ? 'bg-primary bg-opacity-80' : 'bg-bg bg-opacity-60'">
<svg xmlns="http://www.w3.org/2000/svg" class="h-6 w-6" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 6.253v13m0-13C10.832 5.477 9.246 5 7.5 5S4.168 5.477 3 6.253v13C4.168 18.477 5.754 18 7.5 18s3.332.477 4.5 1.253m0-13C13.168 5.477 14.754 5 16.5 5c1.747 0 3.332.477 4.5 1.253v13C19.832 18.477 18.247 18 16.5 18c-1.746 0-3.332.477-4.5 1.253" />
</svg>
<nuxt-link :to="`/library/${currentLibraryId}/bookshelf`" class="w-full h-20 flex flex-col items-center justify-center text-white border-b border-primary border-opacity-70 hover:bg-primary cursor-pointer relative" :class="showLibrary ? 'bg-primary bg-opacity-80' : 'bg-bg bg-opacity-60'">
<svg xmlns="http://www.w3.org/2000/svg" class="h-6 w-6" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 6.253v13m0-13C10.832 5.477 9.246 5 7.5 5S4.168 5.477 3 6.253v13C4.168 18.477 5.754 18 7.5 18s3.332.477 4.5 1.253m0-13C13.168 5.477 14.754 5 16.5 5c1.747 0 3.332.477 4.5 1.253v13C19.832 18.477 18.247 18 16.5 18c-1.746 0-3.332.477-4.5 1.253" />
</svg>
<p class="pt-1.5 text-center leading-4" style="font-size: 0.9rem">{{ $strings.ButtonLibrary }}</p>
<p class="pt-1.5 text-center leading-4" style="font-size: 0.9rem">{{ $strings.ButtonLibrary }}</p>
<div v-show="showLibrary" class="h-full w-0.5 bg-yellow-400 absolute top-0 left-0" />
</nuxt-link>
<div v-show="showLibrary" class="h-full w-0.5 bg-yellow-400 absolute top-0 left-0" />
</nuxt-link>
<nuxt-link v-if="isBookLibrary" :to="`/library/${currentLibraryId}/bookshelf/series`" class="w-full h-20 flex flex-col items-center justify-center text-white text-opacity-80 border-b border-primary border-opacity-70 hover:bg-primary cursor-pointer relative" :class="isSeriesPage ? 'bg-primary bg-opacity-80' : 'bg-bg bg-opacity-60'">
<svg xmlns="http://www.w3.org/2000/svg" class="h-6 w-6" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 17V7m0 10a2 2 0 01-2 2H5a2 2 0 01-2-2V7a2 2 0 012-2h2a2 2 0 012 2m0 10a2 2 0 002 2h2a2 2 0 002-2M9 7a2 2 0 012-2h2a2 2 0 012 2m0 10V7m0 10a2 2 0 002 2h2a2 2 0 002-2V7a2 2 0 00-2-2h-2a2 2 0 00-2 2" />
</svg>
<nuxt-link v-if="isBookLibrary" :to="`/library/${currentLibraryId}/bookshelf/series`" class="w-full h-20 flex flex-col items-center justify-center text-white text-opacity-80 border-b border-primary border-opacity-70 hover:bg-primary cursor-pointer relative" :class="isSeriesPage ? 'bg-primary bg-opacity-80' : 'bg-bg bg-opacity-60'">
<svg xmlns="http://www.w3.org/2000/svg" class="h-6 w-6" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 17V7m0 10a2 2 0 01-2 2H5a2 2 0 01-2-2V7a2 2 0 012-2h2a2 2 0 012 2m0 10a2 2 0 002 2h2a2 2 0 002-2M9 7a2 2 0 012-2h2a2 2 0 012 2m0 10V7m0 10a2 2 0 002 2h2a2 2 0 002-2V7a2 2 0 00-2-2h-2a2 2 0 00-2 2" />
</svg>
<p class="pt-1.5 text-center leading-4" style="font-size: 0.9rem">{{ $strings.ButtonSeries }}</p>
<p class="pt-1.5 text-center leading-4" style="font-size: 0.9rem">{{ $strings.ButtonSeries }}</p>
<div v-show="isSeriesPage" class="h-full w-0.5 bg-yellow-400 absolute top-0 left-0" />
</nuxt-link>
<div v-show="isSeriesPage" class="h-full w-0.5 bg-yellow-400 absolute top-0 left-0" />
</nuxt-link>
<nuxt-link v-if="isBookLibrary" :to="`/library/${currentLibraryId}/bookshelf/collections`" class="w-full h-20 flex flex-col items-center justify-center text-white text-opacity-80 border-b border-primary border-opacity-70 hover:bg-primary cursor-pointer relative" :class="paramId === 'collections' ? 'bg-primary bg-opacity-80' : 'bg-bg bg-opacity-60'">
<span class="material-icons-outlined text-2xl">collections_bookmark</span>
<nuxt-link v-if="isBookLibrary" :to="`/library/${currentLibraryId}/bookshelf/collections`" class="w-full h-20 flex flex-col items-center justify-center text-white text-opacity-80 border-b border-primary border-opacity-70 hover:bg-primary cursor-pointer relative" :class="paramId === 'collections' ? 'bg-primary bg-opacity-80' : 'bg-bg bg-opacity-60'">
<span class="material-symbols text-2xl">&#xe431;</span>
<p class="pt-1.5 text-center leading-4" style="font-size: 0.9rem">{{ $strings.ButtonCollections }}</p>
<p class="pt-1.5 text-center leading-4" style="font-size: 0.9rem">{{ $strings.ButtonCollections }}</p>
<div v-show="paramId === 'collections'" class="h-full w-0.5 bg-yellow-400 absolute top-0 left-0" />
</nuxt-link>
<div v-show="paramId === 'collections'" class="h-full w-0.5 bg-yellow-400 absolute top-0 left-0" />
</nuxt-link>
<nuxt-link v-if="showPlaylists" :to="`/library/${currentLibraryId}/bookshelf/playlists`" class="w-full h-20 flex flex-col items-center justify-center text-white text-opacity-80 border-b border-primary border-opacity-70 hover:bg-primary cursor-pointer relative" :class="isPlaylistsPage ? 'bg-primary bg-opacity-80' : 'bg-bg bg-opacity-60'">
<span class="material-icons text-2.5xl">queue_music</span>
<nuxt-link v-if="showPlaylists" :to="`/library/${currentLibraryId}/bookshelf/playlists`" class="w-full h-20 flex flex-col items-center justify-center text-white text-opacity-80 border-b border-primary border-opacity-70 hover:bg-primary cursor-pointer relative" :class="isPlaylistsPage ? 'bg-primary bg-opacity-80' : 'bg-bg bg-opacity-60'">
<span class="material-symbols text-2.5xl">&#xe03d;</span>
<p class="pt-0.5 text-center leading-4" style="font-size: 0.9rem">{{ $strings.ButtonPlaylists }}</p>
<p class="pt-0.5 text-center leading-4" style="font-size: 0.9rem">{{ $strings.ButtonPlaylists }}</p>
<div v-show="isPlaylistsPage" class="h-full w-0.5 bg-yellow-400 absolute top-0 left-0" />
</nuxt-link>
<div v-show="isPlaylistsPage" class="h-full w-0.5 bg-yellow-400 absolute top-0 left-0" />
</nuxt-link>
<nuxt-link v-if="isBookLibrary" :to="`/library/${currentLibraryId}/authors`" class="w-full h-20 flex flex-col items-center justify-center text-white text-opacity-80 border-b border-primary border-opacity-70 hover:bg-primary cursor-pointer relative" :class="isAuthorsPage ? 'bg-primary bg-opacity-80' : 'bg-bg bg-opacity-60'">
<svg class="w-6 h-6" viewBox="0 0 24 24">
<path
fill="currentColor"
d="M12,5.5A3.5,3.5 0 0,1 15.5,9A3.5,3.5 0 0,1 12,12.5A3.5,3.5 0 0,1 8.5,9A3.5,3.5 0 0,1 12,5.5M5,8C5.56,8 6.08,8.15 6.53,8.42C6.38,9.85 6.8,11.27 7.66,12.38C7.16,13.34 6.16,14 5,14A3,3 0 0,1 2,11A3,3 0 0,1 5,8M19,8A3,3 0 0,1 22,11A3,3 0 0,1 19,14C17.84,14 16.84,13.34 16.34,12.38C17.2,11.27 17.62,9.85 17.47,8.42C17.92,8.15 18.44,8 19,8M5.5,18.25C5.5,16.18 8.41,14.5 12,14.5C15.59,14.5 18.5,16.18 18.5,18.25V20H5.5V18.25M0,20V18.5C0,17.11 1.89,15.94 4.45,15.6C3.86,16.28 3.5,17.22 3.5,18.25V20H0M24,20H20.5V18.25C20.5,17.22 20.14,16.28 19.55,15.6C22.11,15.94 24,17.11 24,18.5V20Z"
/>
</svg>
<nuxt-link v-if="isBookLibrary" :to="`/library/${currentLibraryId}/authors`" class="w-full h-20 flex flex-col items-center justify-center text-white text-opacity-80 border-b border-primary border-opacity-70 hover:bg-primary cursor-pointer relative" :class="isAuthorsPage ? 'bg-primary bg-opacity-80' : 'bg-bg bg-opacity-60'">
<svg class="w-6 h-6" viewBox="0 0 24 24">
<path
fill="currentColor"
d="M12,5.5A3.5,3.5 0 0,1 15.5,9A3.5,3.5 0 0,1 12,12.5A3.5,3.5 0 0,1 8.5,9A3.5,3.5 0 0,1 12,5.5M5,8C5.56,8 6.08,8.15 6.53,8.42C6.38,9.85 6.8,11.27 7.66,12.38C7.16,13.34 6.16,14 5,14A3,3 0 0,1 2,11A3,3 0 0,1 5,8M19,8A3,3 0 0,1 22,11A3,3 0 0,1 19,14C17.84,14 16.84,13.34 16.34,12.38C17.2,11.27 17.62,9.85 17.47,8.42C17.92,8.15 18.44,8 19,8M5.5,18.25C5.5,16.18 8.41,14.5 12,14.5C15.59,14.5 18.5,16.18 18.5,18.25V20H5.5V18.25M0,20V18.5C0,17.11 1.89,15.94 4.45,15.6C3.86,16.28 3.5,17.22 3.5,18.25V20H0M24,20H20.5V18.25C20.5,17.22 20.14,16.28 19.55,15.6C22.11,15.94 24,17.11 24,18.5V20Z"
/>
</svg>
<p class="pt-1 text-center leading-4" style="font-size: 0.9rem">{{ $strings.ButtonAuthors }}</p>
<p class="pt-1 text-center leading-4" style="font-size: 0.9rem">{{ $strings.ButtonAuthors }}</p>
<div v-show="isAuthorsPage" class="h-full w-0.5 bg-yellow-400 absolute top-0 left-0" />
</nuxt-link>
<div v-show="isAuthorsPage" class="h-full w-0.5 bg-yellow-400 absolute top-0 left-0" />
</nuxt-link>
<nuxt-link v-if="isBookLibrary" :to="`/library/${currentLibraryId}/narrators`" class="w-full h-20 flex flex-col items-center justify-center text-white text-opacity-80 border-b border-primary border-opacity-70 hover:bg-primary cursor-pointer relative" :class="isNarratorsPage ? 'bg-primary bg-opacity-80' : 'bg-bg bg-opacity-60'">
<span class="material-icons text-2xl">record_voice_over</span>
<nuxt-link v-if="isBookLibrary" :to="`/library/${currentLibraryId}/narrators`" class="w-full h-20 flex flex-col items-center justify-center text-white text-opacity-80 border-b border-primary border-opacity-70 hover:bg-primary cursor-pointer relative" :class="isNarratorsPage ? 'bg-primary bg-opacity-80' : 'bg-bg bg-opacity-60'">
<span class="material-symbols text-2xl">&#xe91f;</span>
<p class="pt-1 text-center leading-4" style="font-size: 0.9rem">{{ $strings.LabelNarrators }}</p>
<p class="pt-1 text-center leading-4" style="font-size: 0.9rem">{{ $strings.LabelNarrators }}</p>
<div v-show="isNarratorsPage" class="h-full w-0.5 bg-yellow-400 absolute top-0 left-0" />
</nuxt-link>
<div v-show="isNarratorsPage" class="h-full w-0.5 bg-yellow-400 absolute top-0 left-0" />
</nuxt-link>
<nuxt-link v-if="isPodcastLibrary && userIsAdminOrUp" :to="`/library/${currentLibraryId}/podcast/search`" class="w-full h-20 flex flex-col items-center justify-center text-white text-opacity-80 border-b border-primary border-opacity-70 hover:bg-primary cursor-pointer relative" :class="isPodcastSearchPage ? 'bg-primary bg-opacity-80' : 'bg-bg bg-opacity-60'">
<span class="abs-icons icon-podcast text-xl"></span>
<nuxt-link v-if="isBookLibrary && userIsAdminOrUp" :to="`/library/${currentLibraryId}/stats`" class="w-full h-20 flex flex-col items-center justify-center text-white text-opacity-80 border-b border-primary border-opacity-70 hover:bg-primary cursor-pointer relative" :class="isStatsPage ? 'bg-primary bg-opacity-80' : 'bg-bg bg-opacity-60'">
<span class="material-symbols text-2xl">&#xf190;</span>
<p class="pt-1.5 text-center leading-4" style="font-size: 0.9rem">{{ $strings.ButtonSearch }}</p>
<p class="pt-1 text-center leading-4" style="font-size: 0.9rem">{{ $strings.ButtonStats }}</p>
<div v-show="isPodcastSearchPage" class="h-full w-0.5 bg-yellow-400 absolute top-0 left-0" />
</nuxt-link>
<div v-show="isStatsPage" class="h-full w-0.5 bg-yellow-400 absolute top-0 left-0" />
</nuxt-link>
<nuxt-link v-if="isMusicLibrary" :to="`/library/${currentLibraryId}/bookshelf/albums`" class="w-full h-20 flex flex-col items-center justify-center text-white text-opacity-80 border-b border-primary border-opacity-70 hover:bg-primary cursor-pointer relative" :class="isMusicAlbumsPage ? 'bg-primary bg-opacity-80' : 'bg-bg bg-opacity-60'">
<span class="material-icons-outlined text-xl">album</span>
<nuxt-link v-if="isPodcastLibrary && userIsAdminOrUp" :to="`/library/${currentLibraryId}/podcast/search`" class="w-full h-20 flex flex-col items-center justify-center text-white text-opacity-80 border-b border-primary border-opacity-70 hover:bg-primary cursor-pointer relative" :class="isPodcastSearchPage ? 'bg-primary bg-opacity-80' : 'bg-bg bg-opacity-60'">
<span class="abs-icons icon-podcast text-xl"></span>
<p class="pt-1.5 text-center leading-4" style="font-size: 0.9rem">Albums</p>
<p class="pt-1.5 text-center leading-4" style="font-size: 0.9rem">{{ $strings.ButtonAdd }}</p>
<div v-show="isMusicAlbumsPage" class="h-full w-0.5 bg-yellow-400 absolute top-0 left-0" />
</nuxt-link>
<div v-show="isPodcastSearchPage" class="h-full w-0.5 bg-yellow-400 absolute top-0 left-0" />
</nuxt-link>
<nuxt-link v-if="isPodcastLibrary && userIsAdminOrUp" :to="`/library/${currentLibraryId}/podcast/download-queue`" class="w-full h-20 flex flex-col items-center justify-center text-white text-opacity-80 border-b border-primary border-opacity-70 hover:bg-primary cursor-pointer relative" :class="isPodcastDownloadQueuePage ? 'bg-primary bg-opacity-80' : 'bg-bg bg-opacity-60'">
<span class="material-icons text-2xl">file_download</span>
<nuxt-link v-if="isMusicLibrary" :to="`/library/${currentLibraryId}/bookshelf/albums`" class="w-full h-20 flex flex-col items-center justify-center text-white text-opacity-80 border-b border-primary border-opacity-70 hover:bg-primary cursor-pointer relative" :class="isMusicAlbumsPage ? 'bg-primary bg-opacity-80' : 'bg-bg bg-opacity-60'">
<span class="material-symbols text-xl">album</span>
<p class="pt-1.5 text-center leading-4" style="font-size: 0.9rem">{{ $strings.ButtonDownloadQueue }}</p>
<p class="pt-1.5 text-center leading-4" style="font-size: 0.9rem">Albums</p>
<div v-show="isPodcastDownloadQueuePage" class="h-full w-0.5 bg-yellow-400 absolute top-0 left-0" />
</nuxt-link>
<div v-show="isMusicAlbumsPage" class="h-full w-0.5 bg-yellow-400 absolute top-0 left-0" />
</nuxt-link>
<nuxt-link v-if="numIssues" :to="`/library/${currentLibraryId}/bookshelf?filter=issues`" class="w-full h-20 flex flex-col items-center justify-center text-white text-opacity-80 border-b border-primary border-opacity-70 hover:bg-opacity-40 cursor-pointer relative" :class="showingIssues ? 'bg-error bg-opacity-40' : ' bg-error bg-opacity-20'">
<span class="material-icons text-2xl">warning</span>
<nuxt-link v-if="isPodcastLibrary && userIsAdminOrUp" :to="`/library/${currentLibraryId}/podcast/download-queue`" class="w-full h-20 flex flex-col items-center justify-center text-white text-opacity-80 border-b border-primary border-opacity-70 hover:bg-primary cursor-pointer relative" :class="isPodcastDownloadQueuePage ? 'bg-primary bg-opacity-80' : 'bg-bg bg-opacity-60'">
<span class="material-symbols text-2xl">&#xf090;</span>
<p class="pt-1.5 text-center leading-4" style="font-size: 1rem">{{ $strings.ButtonIssues }}</p>
<p class="pt-1.5 text-center leading-4" style="font-size: 0.9rem">{{ $strings.ButtonDownloadQueue }}</p>
<div v-show="showingIssues" class="h-full w-0.5 bg-yellow-400 absolute top-0 left-0" />
<div class="absolute top-1 right-1 w-4 h-4 rounded-full bg-white bg-opacity-30 flex items-center justify-center">
<p class="text-xs font-mono pb-0.5">{{ numIssues }}</p>
</div>
</nuxt-link>
<div v-show="isPodcastDownloadQueuePage" class="h-full w-0.5 bg-yellow-400 absolute top-0 left-0" />
</nuxt-link>
<div class="w-full h-12 px-1 py-2 border-t border-black border-opacity-20 absolute left-0" :style="{ bottom: streamLibraryItem ? '240px' : '65px' }">
<nuxt-link v-if="numIssues" :to="`/library/${currentLibraryId}/bookshelf?filter=issues`" class="w-full h-20 flex flex-col items-center justify-center text-white text-opacity-80 border-b border-primary border-opacity-70 hover:bg-opacity-40 cursor-pointer relative" :class="showingIssues ? 'bg-error bg-opacity-40' : 'bg-error bg-opacity-20'">
<span class="material-symbols text-2xl">warning</span>
<p class="pt-1.5 text-center leading-4" style="font-size: 1rem">{{ $strings.ButtonIssues }}</p>
<div v-show="showingIssues" class="h-full w-0.5 bg-yellow-400 absolute top-0 left-0" />
<div class="absolute top-1 right-1 w-4 h-4 rounded-full bg-white bg-opacity-30 flex items-center justify-center">
<p class="text-xs font-mono pb-0.5">{{ numIssues }}</p>
</div>
</nuxt-link>
</div>
<div class="w-full h-12 px-1 py-2 border-t border-black/20 bg-bg absolute left-0" :style="{ bottom: streamLibraryItem ? '224px' : '65px' }">
<p class="underline font-mono text-xs text-center text-gray-300 leading-3 mb-1" @click="clickChangelog">v{{ $config.version }}</p>
<a v-if="hasUpdate" :href="githubTagUrl" target="_blank" class="text-warning text-xxs text-center block leading-3">Update</a>
<p v-else class="text-xxs text-gray-400 leading-3 text-center italic">{{ Source }}</p>
</div>
<modals-changelog-view-modal v-model="showChangelogModal" :changelog="currentVersionChangelog" :currentVersion="$config.version" />
<modals-changelog-view-modal v-model="showChangelogModal" :versionData="versionData" />
</div>
</template>
@@ -192,6 +202,9 @@ export default {
isPlaylistsPage() {
return this.paramId === 'playlists'
},
isStatsPage() {
return this.$route.name === 'library-library-stats'
},
libraryBookshelfPage() {
return this.$route.name === 'library-library-bookshelf-id'
},
@@ -217,9 +230,6 @@ export default {
githubTagUrl() {
return this.versionData.githubTagUrl
},
currentVersionChangelog() {
return this.versionData.currentVersionChangelog || 'No Changelog Available'
},
streamLibraryItem() {
return this.$store.state.streamLibraryItem
},
@@ -235,3 +245,12 @@ export default {
mounted() {}
}
</script>
<style>
#siderail-buttons-container {
max-height: calc(100vh - 64px - 48px);
}
#siderail-buttons-container.player-open {
max-height: calc(100vh - 64px - 48px - 160px);
}
</style>

View File

@@ -1,38 +1,40 @@
<template>
<nuxt-link :to="`/author/${author.id}?library=${currentLibraryId}`">
<div @mouseover="mouseover" @mouseleave="mouseleave">
<div :style="{ width: width + 'px', height: height + 'px' }" class="bg-primary box-shadow-book rounded-md relative overflow-hidden">
<!-- Image or placeholder -->
<covers-author-image :author="author" />
<div :style="{ minWidth: cardWidth + 'px', maxWidth: cardWidth + 'px' }">
<nuxt-link :to="`/author/${author.id}`">
<div cy-id="card" @mouseover="mouseover" @mouseleave="mouseleave">
<div cy-id="imageArea" :style="{ height: cardHeight + 'px' }" class="bg-primary box-shadow-book rounded-md relative overflow-hidden">
<!-- Image or placeholder -->
<covers-author-image :author="author" />
<!-- Author name & num books overlay -->
<div v-show="!searching && !nameBelow" class="absolute bottom-0 left-0 w-full py-1 bg-black bg-opacity-60 px-2">
<p class="text-center font-semibold truncate" :style="{ fontSize: sizeMultiplier * 0.75 + 'rem' }">{{ name }}</p>
<p class="text-center text-gray-200" :style="{ fontSize: sizeMultiplier * 0.65 + 'rem' }">{{ numBooks }} Book{{ numBooks === 1 ? '' : 's' }}</p>
</div>
<!-- Author name & num books overlay -->
<div cy-id="textInline" v-show="!searching && !nameBelow" class="absolute bottom-0 left-0 w-full py-1e bg-black bg-opacity-60 px-2e">
<p class="text-center font-semibold truncate" :style="{ fontSize: 0.75 + 'em' }">{{ name }}</p>
<p class="text-center text-gray-200" :style="{ fontSize: 0.65 + 'em' }">{{ numBooks }} {{ $strings.LabelBooks }}</p>
</div>
<!-- Search icon btn -->
<div v-show="!searching && isHovering && userCanUpdate" class="absolute top-0 left-0 p-2 cursor-pointer hover:text-white text-gray-200 transform hover:scale-125 duration-150" @click.prevent.stop="searchAuthor">
<ui-tooltip :text="$strings.ButtonQuickMatch" direction="bottom">
<span class="material-icons text-lg">search</span>
</ui-tooltip>
</div>
<div v-show="isHovering && !searching && userCanUpdate" class="absolute top-0 right-0 p-2 cursor-pointer hover:text-white text-gray-200 transform hover:scale-125 duration-150" @click.prevent.stop="$emit('edit', author)">
<ui-tooltip :text="$strings.LabelEdit" direction="bottom">
<span class="material-icons text-lg">edit</span>
</ui-tooltip>
</div>
<!-- Search icon btn -->
<div cy-id="match" v-show="!searching && isHovering && userCanUpdate" class="absolute top-0 left-0 p-2e cursor-pointer hover:text-white text-gray-200 transform hover:scale-125 duration-150" @click.prevent.stop="searchAuthor">
<ui-tooltip :text="$strings.ButtonQuickMatch" direction="bottom">
<span class="material-symbols" :style="{ fontSize: 1.125 + 'em' }">search</span>
</ui-tooltip>
</div>
<div cy-id="edit" v-show="isHovering && !searching && userCanUpdate" class="absolute top-0 right-0 p-2e cursor-pointer hover:text-white text-gray-200 transform hover:scale-125 duration-150" @click.prevent.stop="$emit('edit', author)">
<ui-tooltip :text="$strings.LabelEdit" direction="bottom">
<span class="material-symbols" :style="{ fontSize: 1.125 + 'em' }">edit</span>
</ui-tooltip>
</div>
<!-- Loading spinner -->
<div v-show="searching" class="absolute top-0 left-0 z-10 w-full h-full bg-black bg-opacity-50 flex items-center justify-center">
<widgets-loading-spinner size="" />
<!-- Loading spinner -->
<div cy-id="spinner" v-show="searching" class="absolute top-0 left-0 z-10 w-full h-full bg-black bg-opacity-50 flex items-center justify-center">
<widgets-loading-spinner size="" />
</div>
</div>
<div cy-id="nameBelow" v-show="nameBelow" class="w-full py-1e px-2e">
<p class="text-center font-semibold truncate text-gray-200" :style="{ fontSize: 0.75 + 'em' }">{{ name }}</p>
</div>
</div>
<div v-show="nameBelow" class="w-full py-1 px-2">
<p class="text-center font-semibold truncate text-gray-200" :style="{ fontSize: sizeMultiplier * 0.75 + 'rem' }">{{ name }}</p>
</div>
</div>
</nuxt-link>
</nuxt-link>
</div>
</template>
<script>
@@ -43,12 +45,14 @@ export default {
default: () => {}
},
width: Number,
height: Number,
sizeMultiplier: {
height: {
type: Number,
default: 1
default: 192
},
nameBelow: Boolean
nameBelow: {
type: Boolean,
default: false
}
},
data() {
return {
@@ -57,6 +61,12 @@ export default {
}
},
computed: {
cardWidth() {
return this.width || this.cardHeight * 0.8
},
cardHeight() {
return this.height * this.sizeMultiplier
},
userToken() {
return this.$store.getters['user/getToken']
},
@@ -83,6 +93,9 @@ export default {
},
libraryProvider() {
return this.$store.getters['libraries/getLibraryProvider'](this.currentLibraryId) || 'google'
},
sizeMultiplier() {
return this.$store.getters['user/getSizeMultiplier']
}
},
methods: {
@@ -128,4 +141,4 @@ export default {
this.$eventBus.$off(`searching-author-${this.authorId}`, this.setSearching)
}
}
</script>
</script>

View File

@@ -5,6 +5,7 @@
</div>
<div class="flex-grow px-2 authorSearchCardContent h-full">
<p class="truncate text-sm">{{ name }}</p>
<p class="text-xs text-gray-400">{{ $getString('LabelXBooks', [numBooks]) }}</p>
</div>
</div>
</template>
@@ -23,6 +24,9 @@ export default {
computed: {
name() {
return this.author.name
},
numBooks() {
return this.author.numBooks
}
},
methods: {},
@@ -33,9 +37,9 @@ export default {
<style>
.authorSearchCardContent {
width: calc(100% - 80px);
height: 40px;
height: 44px;
display: flex;
flex-direction: column;
justify-content: center;
}
</style>
</style>

View File

@@ -1,254 +0,0 @@
<template>
<div ref="wrapper" class="relative pointer-events-none" :style="{ width: standardWidth * 0.8 * 1.1 * scale + 'px', height: standardHeight * 1.1 * scale + 'px', marginBottom: 20 + 'px', marginTop: 15 + 'px' }">
<div ref="card" class="wrap absolute origin-center transform duration-200" :style="{ transform: `scale(${scale * scaleMultiplier}) translateY(${hover2 ? '-40%' : '-50%'})` }">
<div class="perspective">
<div class="book-wrap transform duration-100 pointer-events-auto" :class="hover2 ? 'z-80' : 'rotate'" @mouseover="hover = true" @mouseout="hover = false">
<div class="book book-1 box-shadow-book3d" ref="front"></div>
<div class="title book-1 pointer-events-none" ref="left"></div>
<div class="bottom book-1 pointer-events-none" ref="bottom"></div>
<div class="book-back book-1 pointer-events-none">
<div class="text pointer-events-none">
<h3 class="mb-4">Book Back</h3>
<p>
<span>Lorem ipsum dolor sit amet, consectetur adipisicing elit. Sunt earum doloremque aliquam culpa dolor nostrum consequatur quas dicta? Molestias repellendus minima pariatur libero vel, reiciendis optio magnam rerum, labore corporis.</span>
</p>
</div>
</div>
</div>
</div>
</div>
</div>
</template>
<script>
export default {
props: {
src: String,
width: {
type: Number,
default: 200
}
},
data() {
return {
hover: false,
hover2: false,
standardWidth: 200,
standardHeight: 320,
isAttached: true,
pageX: 0,
pageY: 0
}
},
watch: {
src(newVal) {
this.setCover()
},
width(newVal) {
this.init()
},
hover(newVal) {
if (newVal) {
this.unattach()
} else {
this.attach()
}
setTimeout(() => {
this.hover2 = newVal
}, 100)
}
},
computed: {
scaleMultiplier() {
return this.hover2 ? 1.25 : 1
},
scale() {
var scale = this.width / this.standardWidth
return scale
}
},
methods: {
unattach() {
if (this.$refs.card && this.isAttached) {
var bookshelf = document.getElementById('bookshelf')
if (bookshelf) {
var pos = this.$refs.wrapper.getBoundingClientRect()
this.pageX = pos.x
this.pageY = pos.y
document.body.appendChild(this.$refs.card)
this.$refs.card.style.left = this.pageX + 'px'
this.$refs.card.style.top = this.pageY + 'px'
this.$refs.card.style.zIndex = 50
this.isAttached = false
} else if (bookshelf) {
console.log(this.pageX, this.pageY)
this.isAttached = false
}
}
},
attach() {
if (this.$refs.card && !this.isAttached) {
if (this.$refs.wrapper) {
this.isAttached = true
this.$refs.wrapper.appendChild(this.$refs.card)
this.$refs.card.style.left = '0px'
this.$refs.card.style.top = '0px'
}
} else {
console.log('Is attached already', this.isAttached)
}
},
init() {
var standardWidth = this.standardWidth
document.documentElement.style.setProperty('--book-w', standardWidth + 'px')
document.documentElement.style.setProperty('--book-wx', standardWidth + 1 + 'px')
document.documentElement.style.setProperty('--book-h', standardWidth * 1.6 + 'px')
document.documentElement.style.setProperty('--book-d', 40 + 'px')
},
setElBg(el) {
el.style.backgroundImage = `url("${this.src}")`
el.style.backgroundSize = 'cover'
el.style.backgroundPosition = 'center center'
el.style.backgroundRepeat = 'no-repeat'
},
setCover() {
if (this.$refs.front) {
this.setElBg(this.$refs.front)
}
if (this.$refs.bottom) {
this.setElBg(this.$refs.bottom)
this.$refs.bottom.style.backgroundSize = '2000%'
this.$refs.bottom.style.filter = 'blur(1px)'
}
if (this.$refs.left) {
this.setElBg(this.$refs.left)
this.$refs.left.style.backgroundSize = '2000%'
this.$refs.left.style.filter = 'blur(1px)'
}
}
},
mounted() {
this.setCover()
this.init()
}
}
</script>
<style>
/* :root {
--book-w: 200px;
--book-h: 320px;
--book-d: 30px;
--book-wx: 201px;
} */
/*
.wrap {
width: calc(1.1 * var(--book-w));
height: calc(1.1 * var(--book-h));
margin: 0 auto;
}
.perspective {
position: relative;
width: 100%;
height: 100%;
perspective: 600px;
transform-style: preserve-3d;
overflow: hidden;
}
.book-wrap {
height: 100%;
width: 100%;
transform-style: preserve-3d;
transition: 'all ease-out 0.6s';
}
.book {
width: var(--book-w);
height: var(--book-h);
background: url(https://covers.openlibrary.org/b/id/8303020-L.jpg) no-repeat center center;
background-size: cover;
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
margin: auto;
cursor: pointer;
}
.title {
content: '';
height: var(--book-h);
width: var(--book-d);
position: absolute;
right: 0;
left: calc(var(--book-wx) * -1);
top: 0;
bottom: 0;
margin: auto;
background: #444;
transform: rotateY(-80deg) translateX(-14px);
background: url(https://covers.openlibrary.org/b/id/8303020-L.jpg) no-repeat center center;
background-size: 5000%;
filter: blur(1px);
}
.bottom {
content: '';
height: var(--book-d);
width: var(--book-w);
position: absolute;
right: 0;
bottom: var(--book-h);
top: 0;
left: 0;
margin: auto;
background: #444;
transform: rotateY(0deg) rotateX(90deg) translateY(-15px) translateX(-2.5px) skewX(10deg);
background: url(https://covers.openlibrary.org/b/id/8303020-L.jpg) no-repeat center center;
background-size: 5000%;
filter: blur(1px);
}
.book-back {
width: var(--book-w);
height: var(--book-h);
background-color: #444;
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
margin: auto;
cursor: pointer;
transform: rotate(180deg) translateZ(-30px) translateX(5px);
}
.book-back .text {
transform: rotateX(180deg);
position: absolute;
bottom: 0px;
padding: 20px;
text-align: left;
font-size: 12px;
}
.book-back .text h3 {
color: #fff;
}
.book-back .text span {
display: block;
margin-bottom: 20px;
color: #fff;
}
.book-wrap.rotate {
transform: rotateY(30deg) rotateX(0deg);
}
.book-wrap.flip {
transform: rotateY(180deg);
} */
</style>

View File

@@ -13,10 +13,10 @@
<div class="flex-grow" />
<p class="text-sm md:text-base">{{ book.publishedYear }}</p>
</div>
<p v-if="book.author" class="text-gray-300 text-xs md:text-sm">by {{ book.author }}</p>
<p v-if="book.narrator" class="text-gray-400 text-xs">Narrated by {{ book.narrator }}</p>
<p v-if="book.duration" class="text-gray-400 text-xs">Runtime: {{ $elapsedPrettyExtended(book.duration * 60) }}</p>
<div v-if="book.series && book.series.length" class="flex py-1 -mx-1">
<p v-if="book.author" class="text-gray-300 text-xs md:text-sm">{{ $getString('LabelByAuthor', [book.author]) }}</p>
<p v-if="book.narrator" class="text-gray-400 text-xs">{{ $strings.LabelNarrators }}: {{ book.narrator }}</p>
<p v-if="book.duration" class="text-gray-400 text-xs">{{ $strings.LabelDuration }}: {{ $elapsedPrettyExtended(bookDuration, false) }} {{ bookDurationComparison }}</p>
<div v-if="book.series?.length" class="flex py-1 -mx-1">
<div v-for="(series, index) in book.series" :key="index" class="bg-white bg-opacity-10 rounded-full px-1 py-0.5 mx-1">
<p class="leading-3 text-xs text-gray-400">
{{ series.series }}<span v-if="series.sequence">&nbsp;#{{ series.sequence }}</span>
@@ -29,11 +29,9 @@
</div>
<div v-else class="px-4 flex-grow">
<h1>
<div class="flex items-center">
{{ book.title }}<widgets-explicit-indicator :explicit="book.explicit" />
</div>
<div class="flex items-center">{{ book.title }}<widgets-explicit-indicator v-if="book.explicit" /></div>
</h1>
<p class="text-base text-gray-300 whitespace-nowrap truncate">by {{ book.author }}</p>
<p class="text-base text-gray-300 whitespace-nowrap truncate">{{ $getString('LabelByAuthor', [book.author]) }}</p>
<p v-if="book.genres" class="text-xs text-gray-400 leading-5">{{ book.genres.join(', ') }}</p>
<p class="text-xs text-gray-400 leading-5">{{ book.trackCount }} Episodes</p>
</div>
@@ -56,7 +54,8 @@ export default {
default: () => {}
},
isPodcast: Boolean,
bookCoverAspectRatio: Number
bookCoverAspectRatio: Number,
currentBookDuration: Number
},
data() {
return {
@@ -65,12 +64,27 @@ export default {
},
computed: {
bookCovers() {
return this.book.covers ? this.book.covers || [] : []
return this.book.covers || []
},
bookDuration() {
return (this.book.duration || 0) * 60
},
bookDurationComparison() {
if (!this.book.duration || !this.currentBookDuration) return ''
const currentBookDurationMinutes = Math.floor(this.currentBookDuration / 60)
let differenceInMinutes = currentBookDurationMinutes - this.book.duration
if (differenceInMinutes < 0) {
differenceInMinutes = Math.abs(differenceInMinutes)
return this.$getString('LabelDurationComparisonLonger', [this.$elapsedPrettyExtended(differenceInMinutes * 60, false, false)])
} else if (differenceInMinutes > 0) {
return this.$getString('LabelDurationComparisonShorter', [this.$elapsedPrettyExtended(differenceInMinutes * 60, false, false)])
}
return this.$strings.LabelDurationComparisonExactMatch
}
},
methods: {
selectMatch() {
var book = { ...this.book }
const book = { ...this.book }
book.cover = this.selectedCover
this.$emit('select', book)
},

View File

@@ -0,0 +1,36 @@
<template>
<div class="flex h-full px-1 overflow-hidden">
<div class="w-10 h-10 flex items-center justify-center">
<span class="material-symbols text-2xl text-gray-200">category</span>
</div>
<div class="flex-grow px-2 tagSearchCardContent h-full">
<p class="truncate text-sm">{{ genre }}</p>
<p class="text-xs text-gray-400">{{ $getString('LabelXItems', [numItems]) }}</p>
</div>
</div>
</template>
<script>
export default {
props: {
genre: String,
numItems: Number
},
data() {
return {}
},
computed: {},
methods: {},
mounted() {}
}
</script>
<style>
.tagSearchCardContent {
width: calc(100% - 40px);
height: 44px;
display: flex;
flex-direction: column;
justify-content: center;
}
</style>

View File

@@ -1,15 +1,15 @@
<template>
<div class="relative">
<div class="rounded-sm h-full relative" :style="{ width: width + 'px', height: height + 'px' }" @mouseover="mouseoverCard" @mouseleave="mouseleaveCard" @click="clickCard">
<div class="rounded-sm h-full relative" :style="{ width: cardWidth + 'px', height: cardHeight + 'px' }" @mouseover="mouseoverCard" @mouseleave="mouseleaveCard" @click="clickCard">
<nuxt-link :to="groupTo" class="cursor-pointer">
<div class="w-full h-full relative" :class="isHovering ? 'bg-black-400' : 'bg-primary'">
<covers-group-cover ref="groupcover" :id="groupEncode" :name="groupName" :type="groupType" :book-items="bookItems" :width="width" :height="height" :book-cover-aspect-ratio="bookCoverAspectRatio" />
<covers-group-cover ref="groupcover" :id="groupEncode" :name="groupName" :type="groupType" :book-items="bookItems" :width="cardWidth" :height="cardHeight" :book-cover-aspect-ratio="bookCoverAspectRatio" />
<div v-if="hasValidCovers" class="bg-black bg-opacity-60 absolute top-0 left-0 w-full h-full flex items-center justify-center text-center transition-opacity z-30" :class="isHovering ? '' : 'opacity-0'" :style="{ padding: `${sizeMultiplier}rem` }">
<p :style="{ fontSize: 1.2 * sizeMultiplier + 'rem' }">{{ groupName }}</p>
</div>
<div class="absolute z-10 top-1.5 right-1.5 rounded-md leading-3 text-sm p-1 font-semibold text-white flex items-center justify-center" style="background-color: #cd9d49dd">{{ bookItems.length }}</div>
<div class="absolute z-10 top-1.5e right-1.5e rounded-md leading-3e p-1e font-semibold text-white flex items-center justify-center" :style="{ fontSize: 0.8 + 'em' }" style="background-color: #cd9d49dd">{{ bookItems.length }}</div>
</div>
</nuxt-link>
</div>
@@ -24,8 +24,10 @@ export default {
default: () => null
},
width: Number,
height: Number,
bookCoverAspectRatio: Number
height: {
type: Number,
default: 192
}
},
data() {
return {
@@ -33,6 +35,15 @@ export default {
}
},
computed: {
bookCoverAspectRatio() {
return this.$store.getters['libraries/getBookCoverAspectRatio']
},
cardWidth() {
return this.width || this.cardHeight * 2
},
cardHeight() {
return this.height * this.sizeMultiplier
},
currentLibraryId() {
return this.$store.state.libraries.currentLibraryId
},
@@ -46,8 +57,7 @@ export default {
return `/library/${this.currentLibraryId}/bookshelf?filter=${this.filter}`
},
sizeMultiplier() {
if (this.bookCoverAspectRatio === 1) return this.width / (120 * 1.6 * 2)
return this.width / 240
return this.$store.getters['user/getSizeMultiplier']
},
bookItems() {
return this._group.books || []
@@ -78,4 +88,4 @@ export default {
}
}
}
</script>
</script>

View File

@@ -2,15 +2,9 @@
<div class="flex items-center h-full px-1 overflow-hidden">
<covers-book-cover :library-item="libraryItem" :width="coverWidth" :book-cover-aspect-ratio="bookCoverAspectRatio" />
<div class="flex-grow px-2 audiobookSearchCardContent">
<p v-if="matchKey !== 'title'" class="truncate text-sm">{{ title }}</p>
<p v-else class="truncate text-sm" v-html="matchHtml" />
<p v-if="matchKey === 'subtitle'" class="truncate text-xs text-gray-300" v-html="matchHtml" />
<p v-if="matchKey !== 'authors'" class="text-xs text-gray-200 truncate">by {{ authorName }}</p>
<p v-else class="truncate text-xs text-gray-200" v-html="matchHtml" />
<div v-if="matchKey === 'series' || matchKey === 'tags' || matchKey === 'isbn' || matchKey === 'asin' || matchKey === 'episode' || matchKey === 'narrators'" class="m-0 p-0 truncate text-xs" v-html="matchHtml" />
<p class="truncate text-sm">{{ title }}</p>
<p v-if="subtitle" class="truncate text-xs text-gray-300">{{ subtitle }}</p>
<p class="text-xs text-gray-200 truncate">{{ $getString('LabelByAuthor', [authorName]) }}</p>
</div>
</div>
</template>
@@ -21,10 +15,7 @@ export default {
libraryItem: {
type: Object,
default: () => {}
},
search: String,
matchKey: String,
matchText: String
}
},
data() {
return {}
@@ -58,23 +49,6 @@ export default {
authorName() {
if (this.isPodcast) return this.mediaMetadata.author || 'Unknown'
return this.mediaMetadata.authorName || 'Unknown'
},
matchHtml() {
if (!this.matchText || !this.search) return ''
// This used to highlight the part of the search found
// but with removing commas periods etc this is no longer plausible
const html = this.matchText
if (this.matchKey === 'episode') return `<p class="truncate">${this.$strings.LabelEpisode}: ${html}</p>`
if (this.matchKey === 'tags') return `<p class="truncate">${this.$strings.LabelTags}: ${html}</p>`
if (this.matchKey === 'subtitle') return `<p class="truncate">${html}</p>`
if (this.matchKey === 'authors') return `by ${html}`
if (this.matchKey === 'isbn') return `<p class="truncate">ISBN: ${html}</p>`
if (this.matchKey === 'asin') return `<p class="truncate">ASIN: ${html}</p>`
if (this.matchKey === 'series') return `<p class="truncate">${this.$strings.LabelSeries}: ${html}</p>`
if (this.matchKey === 'narrators') return `<p class="truncate">${this.$strings.LabelNarrator}: ${html}</p>`
return `${html}`
}
},
methods: {},
@@ -90,4 +64,4 @@ export default {
flex-direction: column;
justify-content: center;
}
</style>
</style>

View File

@@ -1,10 +1,8 @@
<template>
<div class="flex items-center px-1 overflow-hidden">
<div class="w-8 flex items-center justify-center">
<!-- <div class="text-lg"> -->
<span v-if="isFinished" :class="taskIconStatus" class="material-icons text-base">{{ actionIcon }}</span>
<span v-if="isFinished" :class="taskIconStatus" class="material-symbols text-base">{{ actionIcon }}</span>
<widgets-loading-spinner v-else />
<!-- </div> -->
</div>
<div class="flex-grow px-2 taskRunningCardContent">
<p class="truncate text-sm">{{ title }}</p>
@@ -12,7 +10,9 @@
<p class="truncate text-xs text-gray-300">{{ description }}</p>
<p v-if="isFailed && failedMessage" class="text-xs truncate text-red-500">{{ failedMessage }}</p>
<p v-else-if="!isFinished && cancelingScan" class="text-xs truncate">Canceling...</p>
</div>
<ui-btn v-if="userIsAdminOrUp && !isFinished && isLibraryScan && !cancelingScan" color="primary" :padding-y="1" :padding-x="1" class="text-xs w-16 max-w-16 truncate mr-1" @click.stop="cancelScan">{{ this.$strings.ButtonCancel }}</ui-btn>
</div>
</template>
@@ -25,9 +25,14 @@ export default {
}
},
data() {
return {}
return {
cancelingScan: false
}
},
computed: {
userIsAdminOrUp() {
return this.$store.getters['user/getIsAdminOrUp']
},
title() {
return this.task.title || 'No Title'
},
@@ -76,9 +81,22 @@ export default {
}
return ''
},
isLibraryScan() {
return this.action === 'library-scan' || this.action === 'library-match-all'
}
},
methods: {
cancelScan() {
const libraryId = this.task?.data?.libraryId
if (!libraryId) {
console.error('No library id in library-scan task', this.task)
return
}
this.cancelingScan = true
this.$root.socket.emit('cancel_scan', libraryId)
}
},
methods: {},
mounted() {}
}
</script>

View File

@@ -5,7 +5,7 @@
</div>
<div v-if="!processing && !uploadFailed && !uploadSuccess" class="absolute -top-3 -right-3 w-8 h-8 bg-bg border border-white border-opacity-10 flex items-center justify-center rounded-full hover:bg-error cursor-pointer" @click="$emit('remove')">
<span class="text-base text-white text-opacity-80 font-mono material-icons">close</span>
<span class="text-base text-white text-opacity-80 font-mono material-symbols">close</span>
</div>
<template v-if="!uploadSuccess && !uploadFailed">
@@ -15,24 +15,37 @@
<div class="flex my-2 -mx-2">
<div class="w-1/2 px-2">
<ui-text-input-with-label v-model="itemData.title" :disabled="processing" :label="$strings.LabelTitle" @input="titleUpdated" />
<ui-text-input-with-label v-model.trim="itemData.title" :disabled="processing" :label="$strings.LabelTitle" @input="titleUpdated" />
</div>
<div class="w-1/2 px-2">
<ui-text-input-with-label v-if="!isPodcast" v-model="itemData.author" :disabled="processing" :label="$strings.LabelAuthor" />
<div v-if="!isPodcast" class="flex items-end">
<ui-text-input-with-label v-model.trim="itemData.author" :disabled="processing" :label="$strings.LabelAuthor" />
<ui-tooltip :text="$strings.LabelUploaderItemFetchMetadataHelp">
<div class="ml-2 mb-1 w-8 h-8 bg-bg border border-white border-opacity-10 flex items-center justify-center rounded-full hover:bg-primary cursor-pointer" @click="fetchMetadata">
<span class="text-base text-white text-opacity-80 font-mono material-symbols">sync</span>
</div>
</ui-tooltip>
</div>
<div v-else class="w-full">
<p class="px-1 text-sm font-semibold">{{ $strings.LabelDirectory }} <em class="font-normal text-xs pl-2">(auto)</em></p>
<ui-text-input :value="directory" disabled class="w-full font-mono text-xs" style="height: 38px" />
<p class="px-1 text-sm font-semibold">
{{ $strings.LabelDirectory }}
<em class="font-normal text-xs pl-2">(auto)</em>
</p>
<ui-text-input :value="directory" disabled class="w-full font-mono text-xs" />
</div>
</div>
</div>
<div v-if="!isPodcast" class="flex my-2 -mx-2">
<div class="w-1/2 px-2">
<ui-text-input-with-label v-model="itemData.series" :disabled="processing" :label="$strings.LabelSeries" note="(optional)" />
<ui-text-input-with-label v-model.trim="itemData.series" :disabled="processing" :label="$strings.LabelSeries" note="(optional)" inputClass="h-10" />
</div>
<div class="w-1/2 px-2">
<div class="w-full">
<p class="px-1 text-sm font-semibold">{{ $strings.LabelDirectory }} <em class="font-normal text-xs pl-2">(auto)</em></p>
<ui-text-input :value="directory" disabled class="w-full font-mono text-xs" style="height: 38px" />
<label class="px-1 text-sm font-semibold">
{{ $strings.LabelDirectory }}
<em class="font-normal text-xs pl-2">(auto)</em>
</label>
<ui-text-input :value="directory" disabled class="w-full font-mono text-xs h-10" />
</div>
</div>
</div>
@@ -42,14 +55,14 @@
<tables-uploaded-files-table v-if="item.ignoredFiles.length" :title="$strings.HeaderIgnoredFiles" :files="item.ignoredFiles" />
</template>
<widgets-alert v-if="uploadSuccess" type="success">
<p class="text-base">{{ $strings.MessageUploaderItemSuccess }}</p>
<p class="text-base">"{{ itemData.title }}" {{ $strings.MessageUploaderItemSuccess }}</p>
</widgets-alert>
<widgets-alert v-if="uploadFailed" type="error">
<p class="text-base">{{ $strings.MessageUploaderItemFailed }}</p>
<p class="text-base">"{{ itemData.title }}" {{ $strings.MessageUploaderItemFailed }}</p>
</widgets-alert>
<div v-if="isUploading" class="absolute top-0 left-0 w-full h-full bg-black bg-opacity-50 flex items-center justify-center z-20">
<ui-loading-indicator :text="$strings.MessageUploading" />
<div v-if="isNonInteractable" class="absolute top-0 left-0 w-full h-full bg-black bg-opacity-50 flex items-center justify-center z-20">
<ui-loading-indicator :text="nonInteractionLabel" />
</div>
</div>
</template>
@@ -64,7 +77,8 @@ export default {
default: () => {}
},
mediaType: String,
processing: Boolean
processing: Boolean,
provider: String
},
data() {
return {
@@ -76,7 +90,8 @@ export default {
error: '',
isUploading: false,
uploadFailed: false,
uploadSuccess: false
uploadSuccess: false,
isFetchingMetadata: false
}
},
computed: {
@@ -87,12 +102,19 @@ export default {
if (!this.itemData.title) return ''
if (this.isPodcast) return this.itemData.title
if (this.itemData.series && this.itemData.author) {
return Path.join(this.itemData.author, this.itemData.series, this.itemData.title)
} else if (this.itemData.author) {
return Path.join(this.itemData.author, this.itemData.title)
} else {
return this.itemData.title
const outputPathParts = [this.itemData.author, this.itemData.series, this.itemData.title]
const cleanedOutputPathParts = outputPathParts.filter(Boolean).map((part) => this.$sanitizeFilename(part))
return Path.join(...cleanedOutputPathParts)
},
isNonInteractable() {
return this.isUploading || this.isFetchingMetadata
},
nonInteractionLabel() {
if (this.isUploading) {
return this.$strings.MessageUploading
} else if (this.isFetchingMetadata) {
return this.$strings.LabelFetchingMetadata
}
}
},
@@ -105,9 +127,42 @@ export default {
titleUpdated() {
this.error = ''
},
async fetchMetadata() {
if (!this.itemData.title.trim().length) {
return
}
this.isFetchingMetadata = true
this.error = ''
try {
const searchQueryString = new URLSearchParams({
title: this.itemData.title,
author: this.itemData.author,
provider: this.provider
})
const [bestCandidate, ..._rest] = await this.$axios.$get(`/api/search/books?${searchQueryString}`)
if (bestCandidate) {
this.itemData = {
...this.itemData,
title: bestCandidate.title,
author: bestCandidate.author,
series: (bestCandidate.series || [])[0]?.series
}
} else {
this.error = this.$strings.ErrorUploadFetchMetadataNoResults
}
} catch (e) {
console.error('Failed', e)
this.error = this.$strings.ErrorUploadFetchMetadataAPI
} finally {
this.isFetchingMetadata = false
}
},
getData() {
if (!this.itemData.title) {
this.error = 'Must have a title'
this.error = this.$strings.ErrorUploadLacksTitle
return null
}
this.error = ''
@@ -128,4 +183,4 @@ export default {
}
}
}
</script>
</script>

View File

@@ -1,18 +1,22 @@
<template>
<div ref="card" :id="`album-card-${index}`" :style="{ width: width + 'px', height: height + 'px' }" class="absolute top-0 left-0 rounded-sm z-30 cursor-pointer" @mousedown.prevent @mouseup.prevent @mousemove.prevent @mouseover="mouseover" @mouseleave="mouseleave" @click="clickCard">
<div class="absolute top-0 left-0 w-full box-shadow-book shadow-height" />
<div class="w-full h-full bg-primary relative rounded overflow-hidden">
<covers-preview-cover ref="cover" :src="coverSrc" :width="width" :book-cover-aspect-ratio="bookCoverAspectRatio" />
</div>
<div v-if="!isAlternativeBookshelfView" class="categoryPlacard absolute z-30 left-0 right-0 mx-auto -bottom-6 h-6 rounded-md text-center" :style="{ width: Math.min(200, width) + 'px' }">
<div class="w-full h-full shinyBlack flex items-center justify-center rounded-sm border" :style="{ padding: `0rem ${0.5 * sizeMultiplier}rem` }">
<p class="truncate" :style="{ fontSize: labelFontSize + 'rem' }">{{ title }}</p>
<div ref="card" :id="`album-card-${index}`" :style="{ width: cardWidth + 'px' }" class="absolute top-0 left-0 rounded-sm z-30 cursor-pointer" @mousedown.prevent @mouseup.prevent @mousemove.prevent @mouseover="mouseover" @mouseleave="mouseleave" @click="clickCard">
<div class="relative" :style="{ height: coverHeight + 'px' }">
<div class="absolute top-0 left-0 w-full box-shadow-book shadow-height" />
<div class="w-full h-full bg-primary relative rounded overflow-hidden">
<covers-preview-cover ref="cover" :src="coverSrc" :width="cardWidth" :book-cover-aspect-ratio="bookCoverAspectRatio" />
</div>
</div>
<div v-else class="absolute z-30 left-0 right-0 mx-auto -bottom-8 h-8 py-1 rounded-md text-center">
<p class="truncate" :style="{ fontSize: labelFontSize + 'rem' }">{{ title }}</p>
<p class="truncate text-gray-400" :style="{ fontSize: 0.8 * sizeMultiplier + 'rem' }">{{ artist || '&nbsp;' }}</p>
<div class="relative w-full">
<div v-if="!isAlternativeBookshelfView" class="categoryPlacard absolute z-30 left-0 right-0 mx-auto -bottom-6e h-6e rounded-md text-center" :style="{ width: Math.min(200, cardWidth) + 'px' }">
<div class="w-full h-full shinyBlack flex items-center justify-center rounded-sm border" :style="{ padding: `0em ${0.5}em` }">
<p class="truncate" :style="{ fontSize: labelFontSize + 'em' }">{{ title }}</p>
</div>
</div>
<div v-else class="absolute z-30 left-0 right-0 mx-auto -bottom-8e h-8e py-1e rounded-md text-center">
<p class="truncate" :style="{ fontSize: labelFontSize + 'em' }">{{ title }}</p>
<p class="truncate text-gray-400" :style="{ fontSize: 0.8 + 'em' }">{{ artist || '&nbsp;' }}</p>
</div>
</div>
</div>
</template>
@@ -22,8 +26,10 @@ export default {
props: {
index: Number,
width: Number,
height: Number,
bookCoverAspectRatio: Number,
height: {
type: Number,
default: 192
},
bookshelfView: {
type: Number,
default: 0
@@ -42,6 +48,29 @@ export default {
}
},
computed: {
bookCoverAspectRatio() {
return this.store.getters['libraries/getBookCoverAspectRatio']
},
cardWidth() {
return this.width || this.coverHeight
},
coverHeight() {
return this.height * this.sizeMultiplier
},
/*
cardHeight() {
return this.coverHeight + this.bottomTextHeight
},
bottomTextHeight() {
if (!this.isAlternativeBookshelfView) return 0
const lineHeight = 1.5
const remSize = 16
const baseHeight = this.sizeMultiplier * lineHeight * remSize
const titleHeight = this.labelFontSize * baseHeight
const paddingHeight = 4 * 2 * this.sizeMultiplier // py-1
return titleHeight + paddingHeight
},
*/
coverSrc() {
const config = this.$config || this.$nuxt.$config
if (!this.album || !this.album.libraryItemId) return `${config.routerBasePath}/book_placeholder.jpg`
@@ -49,11 +78,10 @@ export default {
},
labelFontSize() {
if (this.width < 160) return 0.75
return 0.875
return 0.9
},
sizeMultiplier() {
const baseSize = this.bookCoverAspectRatio === 1 ? 192 : 120
return this.width / baseSize
return this.store.getters['user/getSizeMultiplier']
},
title() {
return this.album ? this.album.title : ''
@@ -111,4 +139,4 @@ export default {
}
}
}
</script>
</script>

View File

@@ -1,128 +1,142 @@
<template>
<div ref="card" :id="`book-card-${index}`" :style="{ minWidth: width + 'px', maxWidth: width + 'px', height: height + 'px' }" class="rounded-sm z-10 bg-primary cursor-pointer box-shadow-book" @mousedown.prevent @mouseup.prevent @mousemove.prevent @mouseover="mouseover" @mouseleave="mouseleave" @click="clickCard">
<!-- When cover image does not fill -->
<div v-show="showCoverBg" class="absolute top-0 left-0 w-full h-full overflow-hidden rounded-sm bg-primary">
<div class="absolute cover-bg" ref="coverBg" />
<div ref="card" :id="`book-card-${index}`" :style="{ minWidth: coverWidth + 'px', maxWidth: coverWidth + 'px' }" class="absolute rounded-sm z-10 cursor-pointer" @mousedown.prevent @mouseup.prevent @mousemove.prevent @mouseover="mouseover" @mouseleave="mouseleave" @click="clickCard">
<div :id="`cover-area-${index}`" class="relative w-full top-0 left-0 rounded overflow-hidden z-10 bg-primary box-shadow-book" :style="{ height: coverHeight + 'px ' }">
<!-- When cover image does not fill -->
<div cy-id="coverBg" v-show="showCoverBg" class="absolute top-0 left-0 w-full h-full overflow-hidden rounded-sm bg-primary">
<div class="absolute cover-bg" ref="coverBg" />
</div>
<div cy-id="seriesSequenceList" v-if="seriesSequenceList" class="absolute rounded-lg bg-black bg-opacity-90 box-shadow-md z-20 text-right" :style="{ top: 0.375 + 'em', right: 0.375 + 'em', padding: `0.1em 0.25em` }" style="background-color: #78350f">
<p :style="{ fontSize: 0.8 + 'em' }">#{{ seriesSequenceList }}</p>
</div>
<div cy-id="booksInSeries" v-else-if="booksInSeries" class="absolute rounded-lg bg-black bg-opacity-90 box-shadow-md z-20" :style="{ top: 0.375 + 'em', right: 0.375 + 'em', padding: `0.1em 0.25em` }" style="background-color: #cd9d49dd">
<p :style="{ fontSize: 0.8 + 'em' }">{{ booksInSeries }}</p>
</div>
<div class="w-full h-full absolute top-0 left-0 rounded overflow-hidden z-10">
<div cy-id="titleImageNotReady" v-show="libraryItem && !imageReady" class="absolute top-0 left-0 w-full h-full flex items-center justify-center" :style="{ padding: 0.5 + 'em' }">
<p :style="{ fontSize: 0.8 + 'em' }" class="text-gray-300 text-center">{{ title }}</p>
</div>
<!-- Cover Image -->
<img cy-id="coverImage" v-show="libraryItem" ref="cover" :src="bookCoverSrc" class="relative w-full h-full transition-opacity duration-300" :class="showCoverBg ? 'object-contain' : 'object-fill'" @load="imageLoaded" :style="{ opacity: imageReady ? 1 : 0 }" />
<!-- Placeholder Cover Title & Author -->
<div cy-id="placeholderTitle" v-if="!hasCover" class="absolute top-0 left-0 right-0 bottom-0 w-full h-full flex items-center justify-center" :style="{ padding: placeholderCoverPadding + 'em' }">
<div>
<p cy-id="placeholderTitleText" class="text-center" style="color: rgb(247 223 187)" :style="{ fontSize: titleFontSize + 'em' }">{{ titleCleaned }}</p>
</div>
</div>
<div cy-id="placeholderAuthor" v-if="!hasCover" class="absolute left-0 right-0 w-full flex items-center justify-center" :style="{ padding: placeholderCoverPadding + 'em', bottom: authorBottom + 'em' }">
<p cy-id="placeholderAuthorText" class="text-center" style="color: rgb(247 223 187); opacity: 0.75" :style="{ fontSize: authorFontSize + 'em' }">{{ authorCleaned }}</p>
</div>
<div v-if="seriesSequenceList" class="absolute rounded-lg bg-black bg-opacity-90 box-shadow-md z-20 text-right" :style="{ top: 0.375 + 'em', right: 0.375 + 'em', padding: `${0.1}em ${0.25}em` }" style="background-color: #78350f">
<p :style="{ fontSize: 0.8 + 'em' }">#{{ seriesSequenceList }}</p>
</div>
<div v-else-if="booksInSeries" class="absolute rounded-lg bg-black bg-opacity-90 box-shadow-md z-20" :style="{ top: 0.375 + 'em', right: 0.375 + 'em', padding: `${0.1}em ${0.25}em` }" style="background-color: #cd9d49dd">
<p :style="{ fontSize: 0.8 + 'em' }">{{ booksInSeries }}</p>
</div>
<!-- No progress shown for podcasts (unless showing podcast episode) -->
<div cy-id="progressBar" v-if="!isPodcast || episodeProgress" class="absolute bottom-0 left-0 h-1e shadow-sm max-w-full z-10 rounded-b" :class="itemIsFinished ? 'bg-success' : 'bg-yellow-400'" :style="{ width: coverWidth * userProgressPercent + 'px' }"></div>
<!-- Overlay is not shown if collapsing series in library -->
<div cy-id="overlay" v-show="!booksInSeries && libraryItem && (isHovering || isSelectionMode || isMoreMenuOpen) && !processing" class="w-full h-full absolute top-0 left-0 z-10 bg-black rounded md:block" :class="overlayWrapperClasslist">
<div cy-id="playButton" v-show="showPlayButton" class="h-full flex items-center justify-center pointer-events-none">
<div class="hover:text-white text-gray-200 hover:scale-110 transform duration-200 pointer-events-auto" @click.stop.prevent="play">
<span class="material-symbols fill" :style="{ fontSize: playIconFontSize + 'em' }">play_arrow</span>
</div>
</div>
<div cy-id="readButton" v-show="showReadButton" class="h-full flex items-center justify-center pointer-events-none">
<div class="hover:text-white text-gray-200 hover:scale-110 transform duration-200 pointer-events-auto" @click.stop.prevent="clickReadEBook">
<span class="material-symbols" :style="{ fontSize: playIconFontSize + 'em' }">auto_stories</span>
</div>
</div>
<div cy-id="editButton" v-if="userCanUpdate" v-show="!isSelectionMode" class="absolute cursor-pointer hover:text-yellow-300 hover:scale-125 transform duration-150 top-0 right-0" :style="{ padding: 0.375 + 'em' }" @click.stop.prevent="editClick">
<span class="material-symbols" :style="{ fontSize: 1 + 'em' }">edit</span>
</div>
<!-- Radio button -->
<div cy-id="selectedRadioButton" v-if="!isAuthorBookshelfView" class="absolute cursor-pointer hover:text-yellow-300 hover:scale-125 transform duration-100" :style="{ top: 0.375 + 'em', left: 0.375 + 'em' }" @click.stop.prevent="selectBtnClick">
<span class="material-symbols" :class="selected ? 'text-yellow-400' : ''" :style="{ fontSize: 1.25 + 'em' }">{{ selected ? 'radio_button_checked' : 'radio_button_unchecked' }}</span>
</div>
<!-- More Menu Icon -->
<div cy-id="moreButton" ref="moreIcon" v-show="!isSelectionMode && moreMenuItems.length" class="md:block absolute cursor-pointer hover:text-yellow-300 300 hover:scale-125 transform duration-150" :style="{ bottom: 0.375 + 'em', right: 0.375 + 'em' }" @click.stop.prevent="clickShowMore">
<span class="material-symbols" :style="{ fontSize: 1.2 + 'em' }">more_vert</span>
</div>
<div cy-id="ebookFormat" v-if="ebookFormat" class="absolute" :style="{ bottom: 0.375 + 'em', left: 0.375 + 'em' }">
<span class="text-white/80" :style="{ fontSize: 0.8 + 'em' }">{{ ebookFormat }}</span>
</div>
</div>
<!-- Processing/loading spinner overlay -->
<div cy-id="loadingSpinner" v-if="processing" class="w-full h-full absolute top-0 left-0 z-10 bg-black bg-opacity-40 rounded flex items-center justify-center">
<widgets-loading-spinner size="la-lg" />
</div>
<!-- Series name overlay -->
<div cy-id="seriesNameOverlay" v-if="booksInSeries && libraryItem && isHovering" class="w-full h-full absolute top-0 left-0 z-10 bg-black bg-opacity-60 rounded flex items-center justify-center" :style="{ padding: 1 + 'em' }">
<p v-if="seriesName" class="text-gray-200 text-center" :style="{ fontSize: 1.1 + 'em' }">{{ seriesName }}</p>
</div>
<!-- Error widget -->
<ui-tooltip cy-id="ErrorTooltip" v-if="showError" :text="errorText" class="absolute bottom-4e left-0 z-10">
<div :style="{ height: 1.5 + 'em', width: 2.5 + 'em' }" class="bg-error rounded-r-full shadow-md flex items-center justify-end border-r border-b border-red-300">
<span class="material-symbols text-red-100 pr-1e" :style="{ fontSize: 0.875 + 'em' }">priority_high</span>
</div>
</ui-tooltip>
<!-- rss feed icon -->
<div cy-id="rssFeed" v-if="rssFeed && !isSelectionMode && !isHovering" class="absolute text-success top-0 left-0 z-10" :style="{ padding: 0.375 + 'em' }">
<span class="material-symbols" :style="{ fontSize: 1.5 + 'em' }">rss_feed</span>
</div>
<!-- media item shared icon -->
<div cy-id="mediaItemShare" v-if="mediaItemShare && !isSelectionMode && !isHovering" class="absolute text-success left-0 z-10" :style="{ padding: 0.375 + 'em', top: rssFeed ? '2em' : '0px' }">
<span class="material-symbols" :style="{ fontSize: 1.5 + 'em' }">public</span>
</div>
<!-- Series sequence -->
<div cy-id="seriesSequence" v-if="seriesSequence && !isHovering && !isSelectionMode" class="absolute rounded-lg bg-black bg-opacity-90 box-shadow-md z-10" :style="{ top: 0.375 + 'em', right: 0.375 + 'em', padding: `${0.1}em ${0.25}em` }">
<p :style="{ fontSize: 0.8 + 'em' }">#{{ seriesSequence }}</p>
</div>
<!-- Podcast Episode # -->
<div cy-id="podcastEpisodeNumber" v-if="recentEpisodeNumber !== null && !isHovering && !isSelectionMode && !processing" class="absolute rounded-lg bg-black bg-opacity-90 box-shadow-md z-10" :style="{ top: 0.375 + 'em', right: 0.375 + 'em', padding: `${0.1}em ${0.25}em` }">
<p :style="{ fontSize: 0.8 + 'em' }">
Episode<span v-if="recentEpisodeNumber"> #{{ recentEpisodeNumber }}</span>
</p>
</div>
<!-- Podcast Num Episodes -->
<div cy-id="numEpisodes" v-else-if="!numEpisodesIncomplete && numEpisodes && !isHovering && !isSelectionMode" class="absolute rounded-full bg-black bg-opacity-90 box-shadow-md z-10 flex items-center justify-center" :style="{ top: 0.375 + 'em', right: 0.375 + 'em', width: 1.25 + 'em', height: 1.25 + 'em' }">
<p :style="{ fontSize: 0.8 + 'em' }">{{ numEpisodes }}</p>
</div>
<!-- Podcast Num Episodes -->
<div cy-id="numEpisodesIncomplete" v-else-if="numEpisodesIncomplete && !isHovering && !isSelectionMode" class="absolute rounded-full bg-yellow-400 text-black font-semibold box-shadow-md z-10 flex items-center justify-center" :style="{ top: 0.375 + 'em', right: 0.375 + 'em', width: 1.25 + 'em', height: 1.25 + 'em' }">
<p :style="{ fontSize: 0.8 + 'em' }">{{ numEpisodesIncomplete }}</p>
</div>
</div>
</div>
<!-- Alternative bookshelf title/author/sort -->
<div v-if="isAlternativeBookshelfView || isAuthorBookshelfView" class="absolute left-0 z-50 w-full" :style="{ bottom: `-${titleDisplayBottomOffset}rem` }">
<div :style="{ fontSize: 0.9 * sizeMultiplier + 'rem' }">
<div class="flex items-center">
<span class="truncate">{{ displayTitle }}</span>
<widgets-explicit-indicator :explicit="isExplicit" />
</div>
<div cy-id="detailBottom" :id="`description-area-${index}`" v-if="isAlternativeBookshelfView || isAuthorBookshelfView" dir="auto" class="relative mt-2e mb-2e left-0 z-50 w-full">
<div :style="{ fontSize: 0.9 + 'em' }">
<ui-tooltip v-if="displayTitle" :text="displayTitle" :disabled="!displayTitleTruncated" direction="bottom" :delayOnShow="500" class="flex items-center">
<p cy-id="title" ref="displayTitle" class="truncate">{{ displayTitle }}</p>
<widgets-explicit-indicator cy-id="explicitIndicator" v-if="isExplicit" />
</ui-tooltip>
</div>
<p class="truncate text-gray-400" :style="{ fontSize: 0.8 * sizeMultiplier + 'rem' }">{{ displayLineTwo || '&nbsp;' }}</p>
<p v-if="displaySortLine" class="truncate text-gray-400" :style="{ fontSize: 0.8 * sizeMultiplier + 'rem' }">{{ displaySortLine }}</p>
</div>
<div v-if="seriesSequenceList" class="absolute rounded-lg bg-black bg-opacity-90 box-shadow-md z-20 text-right" :style="{ top: 0.375 * sizeMultiplier + 'rem', right: 0.375 * sizeMultiplier + 'rem', padding: `${0.1 * sizeMultiplier}rem ${0.25 * sizeMultiplier}rem` }" style="background-color: #78350f">
<p :style="{ fontSize: sizeMultiplier * 0.8 + 'rem' }">#{{ seriesSequenceList }}</p>
</div>
<div v-else-if="booksInSeries" class="absolute rounded-lg bg-black bg-opacity-90 box-shadow-md z-20" :style="{ top: 0.375 * sizeMultiplier + 'rem', right: 0.375 * sizeMultiplier + 'rem', padding: `${0.1 * sizeMultiplier}rem ${0.25 * sizeMultiplier}rem` }" style="background-color: #cd9d49dd">
<p :style="{ fontSize: sizeMultiplier * 0.8 + 'rem' }">{{ booksInSeries }}</p>
</div>
<div class="w-full h-full absolute top-0 left-0 rounded overflow-hidden z-10">
<div v-show="libraryItem && !imageReady" class="absolute top-0 left-0 w-full h-full flex items-center justify-center" :style="{ padding: sizeMultiplier * 0.5 + 'rem' }">
<p :style="{ fontSize: sizeMultiplier * 0.8 + 'rem' }" class="text-gray-300 text-center">{{ title }}</p>
</div>
<!-- Cover Image -->
<img v-show="libraryItem" ref="cover" :src="bookCoverSrc" class="w-full h-full transition-opacity duration-300" :class="showCoverBg ? 'object-contain' : 'object-fill'" @load="imageLoaded" :style="{ opacity: imageReady ? 1 : 0 }" />
<!-- Placeholder Cover Title & Author -->
<div v-if="!hasCover" class="absolute top-0 left-0 right-0 bottom-0 w-full h-full flex items-center justify-center" :style="{ padding: placeholderCoverPadding + 'rem' }">
<div>
<p class="text-center" style="color: rgb(247 223 187)" :style="{ fontSize: titleFontSize + 'rem' }">
{{ titleCleaned }}
</p>
</div>
</div>
<div v-if="!hasCover" class="absolute left-0 right-0 w-full flex items-center justify-center" :style="{ padding: placeholderCoverPadding + 'rem', bottom: authorBottom + 'rem' }">
<p class="text-center" style="color: rgb(247 223 187); opacity: 0.75" :style="{ fontSize: authorFontSize + 'rem' }">{{ authorCleaned }}</p>
</div>
</div>
<!-- No progress shown for collapsed series in library and podcasts (unless showing podcast episode) -->
<div v-if="!booksInSeries && (!isPodcast || episodeProgress)" class="absolute bottom-0 left-0 h-1 shadow-sm max-w-full z-10 rounded-b" :class="itemIsFinished ? 'bg-success' : 'bg-yellow-400'" :style="{ width: width * userProgressPercent + 'px' }"></div>
<!-- Finished progress bar for collapsed series -->
<div v-else-if="booksInSeries && seriesIsFinished" class="absolute bottom-0 left-0 h-1 shadow-sm max-w-full z-10 rounded-b bg-success" :style="{ width: width * userProgressPercent + 'px' }"></div>
<!-- Overlay is not shown if collapsing series in library -->
<div v-show="!booksInSeries && libraryItem && (isHovering || isSelectionMode || isMoreMenuOpen) && !processing" class="w-full h-full absolute top-0 left-0 z-10 bg-black rounded hidden md:block" :class="overlayWrapperClasslist">
<div v-show="showPlayButton" class="h-full flex items-center justify-center pointer-events-none">
<div class="hover:text-white text-gray-200 hover:scale-110 transform duration-200 pointer-events-auto" @click.stop.prevent="play">
<span class="material-icons" :style="{ fontSize: playIconFontSize + 'rem' }">play_circle_filled</span>
</div>
</div>
<div v-show="showReadButton" class="h-full flex items-center justify-center pointer-events-none">
<div class="hover:text-white text-gray-200 hover:scale-110 transform duration-200 pointer-events-auto" @click.stop.prevent="clickReadEBook">
<span class="material-icons" :style="{ fontSize: playIconFontSize + 'rem' }">auto_stories</span>
</div>
</div>
<div v-if="userCanUpdate" v-show="!isSelectionMode" class="absolute cursor-pointer hover:text-yellow-300 hover:scale-125 transform duration-150 top-0 right-0" :style="{ padding: 0.375 * sizeMultiplier + 'rem' }" @click.stop.prevent="editClick">
<span class="material-icons" :style="{ fontSize: sizeMultiplier + 'rem' }">edit</span>
</div>
<div class="absolute cursor-pointer hover:text-yellow-300 hover:scale-125 transform duration-100" :style="{ top: 0.375 * sizeMultiplier + 'rem', left: 0.375 * sizeMultiplier + 'rem' }" @click.stop.prevent="selectBtnClick">
<span class="material-icons" :class="selected ? 'text-yellow-400' : ''" :style="{ fontSize: 1.25 * sizeMultiplier + 'rem' }">{{ selected ? 'radio_button_checked' : 'radio_button_unchecked' }}</span>
</div>
<!-- More Menu Icon -->
<div ref="moreIcon" v-show="!isSelectionMode && moreMenuItems.length" class="hidden md:block absolute cursor-pointer hover:text-yellow-300 300 hover:scale-125 transform duration-150" :style="{ bottom: 0.375 * sizeMultiplier + 'rem', right: 0.375 * sizeMultiplier + 'rem' }" @click.stop.prevent="clickShowMore">
<span class="material-icons" :style="{ fontSize: 1.2 * sizeMultiplier + 'rem' }">more_vert</span>
</div>
<div v-if="ebookFormat" class="absolute" :style="{ bottom: 0.375 * sizeMultiplier + 'rem', left: 0.375 * sizeMultiplier + 'rem' }">
<span class="text-white/80" :style="{ fontSize: 0.8 * sizeMultiplier + 'rem' }">{{ ebookFormat }}</span>
</div>
</div>
<!-- Processing/loading spinner overlay -->
<div v-if="processing" class="w-full h-full absolute top-0 left-0 z-10 bg-black bg-opacity-40 rounded flex items-center justify-center">
<widgets-loading-spinner size="la-lg" />
</div>
<!-- Series name overlay -->
<div v-if="booksInSeries && libraryItem && isHovering" class="w-full h-full absolute top-0 left-0 z-10 bg-black bg-opacity-60 rounded flex items-center justify-center" :style="{ padding: sizeMultiplier + 'rem' }">
<p class="text-gray-200 text-center" :style="{ fontSize: 1.1 * sizeMultiplier + 'rem' }">{{ series }}</p>
</div>
<!-- Error widget -->
<ui-tooltip v-if="showError" :text="errorText" class="absolute bottom-4 left-0 z-10">
<div :style="{ height: 1.5 * sizeMultiplier + 'rem', width: 2.5 * sizeMultiplier + 'rem' }" class="bg-error rounded-r-full shadow-md flex items-center justify-end border-r border-b border-red-300">
<span class="material-icons text-red-100 pr-1" :style="{ fontSize: 0.875 * sizeMultiplier + 'rem' }">priority_high</span>
</div>
</ui-tooltip>
<div v-if="rssFeed && !isSelectionMode && !isHovering" class="absolute text-success top-0 left-0 z-10" :style="{ padding: 0.375 * sizeMultiplier + 'rem' }">
<span class="material-icons" :style="{ fontSize: sizeMultiplier * 1.5 + 'rem' }">rss_feed</span>
</div>
<!-- Series sequence -->
<div v-if="seriesSequence && !isHovering && !isSelectionMode" class="absolute rounded-lg bg-black bg-opacity-90 box-shadow-md z-10" :style="{ top: 0.375 * sizeMultiplier + 'rem', right: 0.375 * sizeMultiplier + 'rem', padding: `${0.1 * sizeMultiplier}rem ${0.25 * sizeMultiplier}rem` }">
<p :style="{ fontSize: sizeMultiplier * 0.8 + 'rem' }">#{{ seriesSequence }}</p>
</div>
<!-- Podcast Episode # -->
<div v-if="recentEpisodeNumber !== null && !isHovering && !isSelectionMode && !processing" class="absolute rounded-lg bg-black bg-opacity-90 box-shadow-md z-10" :style="{ top: 0.375 * sizeMultiplier + 'rem', right: 0.375 * sizeMultiplier + 'rem', padding: `${0.1 * sizeMultiplier}rem ${0.25 * sizeMultiplier}rem` }">
<p :style="{ fontSize: sizeMultiplier * 0.8 + 'rem' }">
Episode<span v-if="recentEpisodeNumber"> #{{ recentEpisodeNumber }}</span>
</p>
</div>
<!-- Podcast Num Episodes -->
<div v-else-if="!numEpisodesIncomplete && numEpisodes && !isHovering && !isSelectionMode" class="absolute rounded-full bg-black bg-opacity-90 box-shadow-md z-10 flex items-center justify-center" :style="{ top: 0.375 * sizeMultiplier + 'rem', right: 0.375 * sizeMultiplier + 'rem', width: 1.25 * sizeMultiplier + 'rem', height: 1.25 * sizeMultiplier + 'rem' }">
<p :style="{ fontSize: sizeMultiplier * 0.8 + 'rem' }">{{ numEpisodes }}</p>
</div>
<!-- Podcast Num Episodes -->
<div v-else-if="numEpisodesIncomplete && !isHovering && !isSelectionMode" class="absolute rounded-full bg-yellow-400 text-black font-semibold box-shadow-md z-10 flex items-center justify-center" :style="{ top: 0.375 * sizeMultiplier + 'rem', right: 0.375 * sizeMultiplier + 'rem', width: 1.25 * sizeMultiplier + 'rem', height: 1.25 * sizeMultiplier + 'rem' }">
<p :style="{ fontSize: sizeMultiplier * 0.8 + 'rem' }">{{ numEpisodesIncomplete }}</p>
<ui-tooltip v-if="showSubtitles" :text="displaySubtitle" :disabled="!displaySubtitleTruncated" direction="bottom" :delayOnShow="500" class="flex items-center">
<p cy-id="subtitle" class="truncate" ref="displaySubtitle" :style="{ fontSize: 0.6 + 'em' }">{{ displaySubtitle }}</p>
</ui-tooltip>
<p cy-id="line2" class="truncate text-gray-400" :style="{ fontSize: 0.8 + 'em' }">{{ displayLineTwo || '&nbsp;' }}</p>
<p cy-id="line3" v-if="displaySortLine" class="truncate text-gray-400" :style="{ fontSize: 0.8 + 'em' }">{{ displaySortLine }}</p>
</div>
</div>
</template>
@@ -134,15 +148,11 @@ import MoreMenu from '@/components/widgets/MoreMenu'
export default {
props: {
index: Number,
width: {
type: Number,
default: 120
},
width: Number,
height: {
type: Number,
default: 192
},
bookCoverAspectRatio: Number,
bookshelfView: Number,
bookMount: {
// Book can be passed as prop or set with setEntity()
@@ -163,6 +173,8 @@ export default {
imageReady: false,
selected: false,
isSelectionMode: false,
displayTitleTruncated: false,
displaySubtitleTruncated: false,
showCoverBg: false
}
},
@@ -176,6 +188,22 @@ export default {
}
},
computed: {
bookCoverAspectRatio() {
return this.store.getters['libraries/getBookCoverAspectRatio']
},
coverWidth() {
return this.width || this.coverHeight / this.bookCoverAspectRatio
},
coverHeight() {
return this.height * this.sizeMultiplier
},
cardWidth() {
// This method returns immediately without waiting for the DOM to update
return this.coverWidth
},
sizeMultiplier() {
return this.store.getters['user/getSizeMultiplier']
},
dateFormat() {
return this.store.state.serverSettings.dateFormat
},
@@ -196,7 +224,7 @@ export default {
return this._libraryItem.mediaType
},
isPodcast() {
return this.mediaType === 'podcast'
return this.mediaType === 'podcast' || this.store.getters['libraries/getCurrentLibraryMediaType'] === 'podcast'
},
isMusic() {
return this.mediaType === 'music'
@@ -218,8 +246,11 @@ export default {
// Only included when filtering by series or collapse series or Continue Series shelf on home page
return this.mediaMetadata.series
},
seriesName() {
return this.series?.name || null
},
seriesSequence() {
return this.series ? this.series.sequence : null
return this.series?.sequence || null
},
libraryId() {
return this._libraryItem.libraryId
@@ -272,10 +303,6 @@ export default {
squareAspectRatio() {
return this.bookCoverAspectRatio === 1
},
sizeMultiplier() {
const baseSize = this.squareAspectRatio ? 192 : 120
return this.width / baseSize
},
title() {
return this.mediaMetadata.title || ''
},
@@ -297,7 +324,14 @@ export default {
if (this.recentEpisode) return this.recentEpisode.title
const ignorePrefix = this.orderBy === 'media.metadata.title' && this.sortingIgnorePrefix
if (this.collapsedSeries) return ignorePrefix ? this.collapsedSeries.nameIgnorePrefix : this.collapsedSeries.name
return ignorePrefix ? this.mediaMetadata.titleIgnorePrefix : this.title
return ignorePrefix ? this.mediaMetadata.titleIgnorePrefix || '\u00A0' : this.title || '\u00A0'
},
displaySubtitle() {
if (!this.libraryItem) return '\u00A0'
if (this.collapsedSeries) return this.collapsedSeries.numBooks === 1 ? '1 book' : `${this.collapsedSeries.numBooks} books`
if (this.mediaMetadata.subtitle) return this.mediaMetadata.subtitle
if (this.mediaMetadata.seriesName) return this.mediaMetadata.seriesName
return ''
},
displayLineTwo() {
if (this.recentEpisode) return this.title
@@ -312,12 +346,16 @@ export default {
},
displaySortLine() {
if (this.collapsedSeries) return null
if (this.orderBy === 'mtimeMs') return 'Modified ' + this.$formatDate(this._libraryItem.mtimeMs, this.dateFormat)
if (this.orderBy === 'birthtimeMs') return 'Born ' + this.$formatDate(this._libraryItem.birthtimeMs, this.dateFormat)
if (this.orderBy === 'addedAt') return 'Added ' + this.$formatDate(this._libraryItem.addedAt, this.dateFormat)
if (this.orderBy === 'media.duration') return 'Duration: ' + this.$elapsedPrettyExtended(this.media.duration, false)
if (this.orderBy === 'size') return 'Size: ' + this.$bytesPretty(this._libraryItem.size)
if (this.orderBy === 'media.numTracks') return `${this.numEpisodes} Episodes`
if (this.orderBy === 'mtimeMs') return this.$getString('LabelFileModifiedDate', [this.$formatDate(this._libraryItem.mtimeMs, this.dateFormat)])
if (this.orderBy === 'birthtimeMs') return this.$getString('LabelFileBornDate', [this.$formatDate(this._libraryItem.birthtimeMs, this.dateFormat)])
if (this.orderBy === 'addedAt') return this.$getString('LabelAddedDate', [this.$formatDate(this._libraryItem.addedAt, this.dateFormat)])
if (this.orderBy === 'media.duration') return this.$strings.LabelDuration + ': ' + this.$elapsedPrettyExtended(this.media.duration, false)
if (this.orderBy === 'size') return this.$strings.LabelSize + ': ' + this.$bytesPretty(this._libraryItem.size)
if (this.orderBy === 'media.numTracks') return `${this.numEpisodes} ` + this.$strings.LabelEpisodes
if (this.orderBy === 'media.metadata.publishedYear') {
if (this.mediaMetadata.publishedYear) return this.$getString('LabelPublishedDate', [this.mediaMetadata.publishedYear])
return '\u00A0'
}
return null
},
episodeProgress() {
@@ -337,11 +375,22 @@ export default {
if (!this.userProgress || this.userProgress.progress) return false
return this.userProgress.ebookProgress > 0
},
seriesProgressPercent() {
if (!this.libraryItemIdsInSeries.length) return 0
let progressPercent = 0
const useEBookProgress = this.useEBookProgress
this.libraryItemIdsInSeries.forEach((lid) => {
const progress = this.store.getters['user/getUserMediaProgress'](lid)
if (progress) progressPercent += progress.isFinished ? 1 : useEBookProgress ? progress.ebookProgress || 0 : progress.progress || 0
})
return progressPercent / this.libraryItemIdsInSeries.length
},
userProgressPercent() {
if (this.useEBookProgress) return Math.max(Math.min(1, this.userProgress.ebookProgress), 0)
return this.userProgress ? Math.max(Math.min(1, this.userProgress.progress), 0) || 0 : 0
let progressPercent = this.itemIsFinished ? 1 : this.booksInSeries ? this.seriesProgressPercent : this.useEBookProgress ? this.userProgress?.ebookProgress || 0 : this.userProgress?.progress || 0
return Math.max(Math.min(1, progressPercent), 0)
},
itemIsFinished() {
if (this.booksInSeries) return this.seriesIsFinished
return this.userProgress ? !!this.userProgress.isFinished : false
},
seriesIsFinished() {
@@ -352,7 +401,7 @@ export default {
},
showError() {
if (this.recentEpisode) return false // Dont show podcast error on episode card
return this.numInvalidAudioFiles || this.numMissingParts || this.isMissing || this.isInvalid
return this.isMissing || this.isInvalid
},
libraryItemIdStreaming() {
return this.store.getters['getLibraryItemIdStreaming']
@@ -382,29 +431,13 @@ export default {
isInvalid() {
return this._libraryItem.isInvalid
},
numMissingParts() {
if (this.isPodcast) return 0
return this.media.numMissingParts
},
numInvalidAudioFiles() {
if (this.isPodcast) return 0
return this.media.numInvalidAudioFiles
},
errorText() {
if (this.isMissing) return 'Item directory is missing!'
else if (this.isInvalid) {
if (this.isPodcast) return 'Podcast has no episodes'
return 'Item has no audio tracks & ebook'
}
let txt = ''
if (this.numMissingParts) {
txt += `${this.numMissingParts} missing parts.`
}
if (this.numInvalidAudioFiles) {
if (txt) txt += ' '
txt += `${this.numInvalidAudioFiles} invalid audio files.`
}
return txt || 'Unknown Error'
return 'Unknown Error'
},
overlayWrapperClasslist() {
const classes = []
@@ -489,6 +522,12 @@ export default {
func: 'openPlaylists',
text: this.$strings.LabelAddToPlaylist
})
if (this.userIsAdminOrUp) {
items.push({
func: 'openShare',
text: this.$strings.LabelShare
})
}
}
if (this.ebookFormat && this.store.state.libraries.ereaderDevices?.length) {
items.push({
@@ -560,16 +599,16 @@ export default {
return this.$root.socket || this.$nuxt.$root.socket
},
titleFontSize() {
return 0.75 * this.sizeMultiplier
return 0.75
},
authorFontSize() {
return 0.6 * this.sizeMultiplier
return 0.6
},
placeholderCoverPadding() {
return 0.8 * this.sizeMultiplier
return 0.8
},
authorBottom() {
return 0.75 * this.sizeMultiplier
return 0.75
},
titleCleaned() {
if (!this.title) return ''
@@ -593,14 +632,15 @@ export default {
const constants = this.$constants || this.$nuxt.$constants
return this.bookshelfView === constants.BookshelfView.AUTHOR
},
titleDisplayBottomOffset() {
if (!this.isAlternativeBookshelfView && !this.isAuthorBookshelfView) return 0
else if (!this.displaySortLine) return 3 * this.sizeMultiplier
return 4.25 * this.sizeMultiplier
},
rssFeed() {
if (this.booksInSeries) return null
return this._libraryItem.rssFeed || null
},
mediaItemShare() {
return this._libraryItem.mediaItemShare || null
},
showSubtitles() {
return !this.isPodcast && this.store.getters['user/getUserSetting']('showSubtitles')
}
},
methods: {
@@ -637,6 +677,15 @@ export default {
}
this.libraryItem = libraryItem
this.$nextTick(() => {
if (this.$refs.displayTitle) {
this.displayTitleTruncated = this.$refs.displayTitle.scrollWidth > this.$refs.displayTitle.clientWidth
}
if (this.$refs.displaySubtitle) {
this.displaySubtitleTruncated = this.$refs.displaySubtitle.scrollWidth > this.$refs.displaySubtitle.clientWidth
}
})
},
clickCard(e) {
if (this.processing) return
@@ -661,7 +710,7 @@ export default {
toggleFinished(confirmed = false) {
if (!this.itemIsFinished && this.userProgressPercent > 0 && !confirmed) {
const payload = {
message: `Are you sure you want to mark "${this.displayTitle}" as finished?`,
message: this.$getString('MessageConfirmMarkItemFinished', [this.displayTitle]),
callback: (confirmed) => {
if (confirmed) {
this.toggleFinished(true)
@@ -706,18 +755,18 @@ export default {
.then((data) => {
var result = data.result
if (!result) {
this.$toast.error(`Re-Scan Failed for "${this.title}"`)
this.$toast.error(this.$getString('ToastRescanFailed', [this.displayTitle]))
} else if (result === 'UPDATED') {
this.$toast.success(`Re-Scan complete item was updated`)
this.$toast.success(this.$strings.ToastRescanUpdated)
} else if (result === 'UPTODATE') {
this.$toast.success(`Re-Scan complete item was up to date`)
this.$toast.success(this.$strings.ToastRescanUpToDate)
} else if (result === 'REMOVED') {
this.$toast.error(`Re-Scan complete item was removed`)
this.$toast.error(this.$strings.ToastRescanRemoved)
}
})
.catch((error) => {
console.error('Failed to scan library item', error)
this.$toast.error('Failed to scan library item')
this.$toast.error(this.$strings.ToastScanFailed)
})
.finally(() => {
this.processing = false
@@ -774,7 +823,7 @@ export default {
})
.catch((error) => {
console.error('Failed to remove series from home', error)
this.$toast.error('Failed to update user')
this.$toast.error(this.$strings.ToastFailedToUpdateUser)
})
.finally(() => {
this.processing = false
@@ -792,7 +841,7 @@ export default {
})
.catch((error) => {
console.error('Failed to hide item from home', error)
this.$toast.error('Failed to update user')
this.$toast.error(this.$strings.ToastFailedToUpdateUser)
})
.finally(() => {
this.processing = false
@@ -807,7 +856,7 @@ export default {
episodeId: this.recentEpisode.id,
title: this.recentEpisode.title,
subtitle: this.mediaMetadata.title,
caption: this.recentEpisode.publishedAt ? `Published ${this.$formatDate(this.recentEpisode.publishedAt, this.dateFormat)}` : 'Unknown publish date',
caption: this.recentEpisode.publishedAt ? this.$getString('LabelPublishedDate', [this.$formatDate(this.recentEpisode.publishedAt, this.dateFormat)]) : this.$strings.LabelUnknownPublishDate,
duration: this.recentEpisode.audioFile.duration || null,
coverPath: this.media.coverPath || null
}
@@ -837,25 +886,31 @@ export default {
this.store.commit('globals/setSelectedPlaylistItems', [{ libraryItem: this.libraryItem, episode: this.recentEpisode }])
this.store.commit('globals/setShowPlaylistsModal', true)
},
openShare() {
this.store.commit('setSelectedLibraryItem', this.libraryItem)
this.store.commit('globals/setShareModal', this.mediaItemShare)
},
deleteLibraryItem() {
const payload = {
message: 'This will delete the library item from the database and your file system. Are you sure?',
checkboxLabel: 'Delete from file system. Uncheck to only remove from database.',
message: this.$strings.MessageConfirmDeleteLibraryItem,
checkboxLabel: this.$strings.LabelDeleteFromFileSystemCheckbox,
yesButtonText: this.$strings.ButtonDelete,
yesButtonColor: 'error',
checkboxDefaultValue: true,
checkboxDefaultValue: !Number(localStorage.getItem('softDeleteDefault') || 0),
callback: (confirmed, hardDelete) => {
if (confirmed) {
localStorage.setItem('softDeleteDefault', hardDelete ? 0 : 1)
this.processing = true
const axios = this.$axios || this.$nuxt.$axios
axios
.$delete(`/api/items/${this.libraryItemId}?hard=${hardDelete ? 1 : 0}`)
.then(() => {
this.$toast.success('Item deleted')
this.$toast.success(this.$strings.ToastItemDeletedSuccess)
})
.catch((error) => {
console.error('Failed to delete item', error)
this.$toast.error('Failed to delete item')
this.$toast.error(this.$strings.ToastItemDeletedFailed)
})
.finally(() => {
this.processing = false
@@ -961,7 +1016,7 @@ export default {
episodeId: episode.id,
title: episode.title,
subtitle: this.mediaMetadata.title,
caption: episode.publishedAt ? `Published ${this.$formatDate(episode.publishedAt, this.dateFormat)}` : 'Unknown publish date',
caption: episode.publishedAt ? this.$getString('LabelPublishedDate', [this.$formatDate(episode.publishedAt, this.dateFormat)]) : this.$strings.LabelUnknownPublishDate,
duration: episode.audioFile.duration || null,
coverPath: this.media.coverPath || null
})

View File

@@ -1,24 +1,26 @@
<template>
<div ref="card" :id="`collection-card-${index}`" :style="{ width: width + 'px', height: height + 'px' }" class="absolute top-0 left-0 rounded-sm z-30 cursor-pointer" @mousedown.prevent @mouseup.prevent @mousemove.prevent @mouseover="mouseover" @mouseleave="mouseleave" @click="clickCard">
<div class="absolute top-0 left-0 w-full box-shadow-book shadow-height" />
<div class="w-full h-full bg-primary relative rounded overflow-hidden">
<covers-collection-cover ref="cover" :book-items="books" :width="width" :height="height" :book-cover-aspect-ratio="bookCoverAspectRatio" />
</div>
<div v-show="isHovering && userCanUpdate" class="w-full h-full absolute top-0 left-0 z-10 bg-black bg-opacity-40 pointer-events-none">
<div class="absolute pointer-events-auto" :style="{ top: 0.5 * sizeMultiplier + 'rem', right: 0.5 * sizeMultiplier + 'rem' }" @click.stop.prevent="clickEdit">
<span class="material-icons text-xl text-white text-opacity-75 hover:text-opacity-100">edit</span>
<div ref="card" :id="`collection-card-${index}`" :style="{ width: cardWidth + 'px' }" class="absolute top-0 left-0 rounded-sm z-30 cursor-pointer" @mousedown.prevent @mouseup.prevent @mousemove.prevent @mouseover="mouseover" @mouseleave="mouseleave" @click="clickCard">
<div class="relative" :style="{ height: coverHeight + 'px' }">
<div class="absolute top-0 left-0 w-full box-shadow-book shadow-height" />
<div class="w-full h-full bg-primary relative rounded overflow-hidden">
<covers-collection-cover ref="cover" :book-items="books" :width="cardWidth" :height="coverHeight" :book-cover-aspect-ratio="bookCoverAspectRatio" />
</div>
<div v-show="isHovering && userCanUpdate" class="w-full h-full absolute top-0 left-0 z-10 bg-black bg-opacity-40 pointer-events-none">
<div class="absolute pointer-events-auto" :style="{ top: 0.5 + 'em', right: 0.5 + 'em' }" @click.stop.prevent="clickEdit">
<span class="material-symbols text-white text-opacity-75 hover:text-opacity-100" :style="{ fontSize: 1.25 + 'em' }">edit</span>
</div>
</div>
<span v-if="!isHovering && rssFeed" class="absolute z-10 material-symbols text-success" :style="{ top: 0.5 + 'em', left: 0.5 + 'em', fontSize: 1.5 + 'em' }">rss_feed</span>
</div>
<span v-if="!isHovering && rssFeed" class="absolute z-10 material-icons text-success" :style="{ top: 0.5 * sizeMultiplier + 'rem', left: 0.5 * sizeMultiplier + 'rem', fontSize: 1.5 * sizeMultiplier + 'rem' }">rss_feed</span>
<div v-if="!isAlternativeBookshelfView" class="categoryPlacard absolute z-30 left-0 right-0 mx-auto -bottom-6 h-6 rounded-md text-center" :style="{ width: Math.min(200, width) + 'px' }">
<div class="w-full h-full shinyBlack flex items-center justify-center rounded-sm border" :style="{ padding: `0rem ${0.5 * sizeMultiplier}rem` }">
<p class="truncate" :style="{ fontSize: labelFontSize + 'rem' }">{{ title }}</p>
<div v-if="!isAlternativeBookshelfView" class="categoryPlacard absolute z-30 left-0 right-0 mx-auto -bottom-6e h-6e rounded-md text-center" :style="{ width: Math.min(200, cardWidth) + 'px' }">
<div class="w-full h-full shinyBlack flex items-center justify-center rounded-sm border" :style="{ padding: `0em ${0.5}em` }">
<p class="truncate" :style="{ fontSize: labelFontSize + 'em' }">{{ title }}</p>
</div>
</div>
<div v-else class="absolute z-30 left-0 right-0 mx-auto -bottom-8 h-8 py-1 rounded-md text-center">
<p class="truncate" :style="{ fontSize: labelFontSize * sizeMultiplier + 'rem' }">{{ title }}</p>
<div v-else class="relative z-30 left-0 right-0 mx-auto h-8e py-1e rounded-md text-center">
<p class="truncate" :style="{ fontSize: labelFontSize + 'em' }">{{ title }}</p>
</div>
</div>
</template>
@@ -28,8 +30,10 @@ export default {
props: {
index: Number,
width: Number,
height: Number,
bookCoverAspectRatio: Number,
height: {
type: Number,
default: 192
},
bookshelfView: {
type: Number,
default: 0
@@ -49,13 +53,21 @@ export default {
}
},
computed: {
bookCoverAspectRatio() {
return this.store.getters['libraries/getBookCoverAspectRatio']
},
cardWidth() {
return this.width || (this.coverHeight / this.bookCoverAspectRatio) * 2
},
coverHeight() {
return this.height * this.sizeMultiplier
},
labelFontSize() {
if (this.width < 160) return 0.75
return 0.875
return 0.9
},
sizeMultiplier() {
if (this.bookCoverAspectRatio === 1) return this.width / (120 * 1.6 * 2)
return this.width / 240
return this.store.getters['user/getSizeMultiplier']
},
title() {
return this.collection ? this.collection.name : ''
@@ -119,4 +131,4 @@ export default {
}
}
}
</script>
</script>

View File

@@ -1,21 +1,24 @@
<template>
<div ref="card" :id="`playlist-card-${index}`" :style="{ width: width + 'px', height: height + 'px' }" class="absolute top-0 left-0 rounded-sm z-30 cursor-pointer" @mousedown.prevent @mouseup.prevent @mousemove.prevent @mouseover="mouseover" @mouseleave="mouseleave" @click="clickCard">
<div class="absolute top-0 left-0 w-full box-shadow-book shadow-height" />
<div class="w-full h-full bg-primary relative rounded overflow-hidden">
<covers-playlist-cover ref="cover" :items="items" :width="width" :height="height" />
</div>
<div v-show="isHovering && userCanUpdate" class="w-full h-full absolute top-0 left-0 z-10 bg-black bg-opacity-40 pointer-events-none">
<div class="absolute pointer-events-auto" :style="{ top: 0.5 * sizeMultiplier + 'rem', right: 0.5 * sizeMultiplier + 'rem' }" @click.stop.prevent="clickEdit">
<span class="material-icons text-xl text-white text-opacity-75 hover:text-opacity-100">edit</span>
<div ref="card" :id="`playlist-card-${index}`" :style="{ width: cardWidth + 'px', fontSize: sizeMultiplier + 'rem' }" class="absolute top-0 left-0 rounded-sm z-30 cursor-pointer" @mousedown.prevent @mouseup.prevent @mousemove.prevent @mouseover="mouseover" @mouseleave="mouseleave" @click="clickCard">
<div class="relative" :style="{ height: coverHeight + 'px' }">
<div class="absolute top-0 left-0 w-full box-shadow-book shadow-height" />
<div class="w-full h-full bg-primary relative rounded overflow-hidden">
<covers-playlist-cover ref="cover" :items="items" :width="cardWidth" :height="coverHeight" />
</div>
<div v-show="isHovering && userCanUpdate" class="w-full h-full absolute top-0 left-0 z-10 bg-black bg-opacity-40 pointer-events-none">
<div class="absolute pointer-events-auto" :style="{ top: 0.5 + 'em', right: 0.5 + 'em' }" @click.stop.prevent="clickEdit">
<span class="material-symbols text-white text-opacity-75 hover:text-opacity-100" :style="{ fontSize: 1.25 + 'em' }">edit</span>
</div>
</div>
</div>
<div v-if="!isAlternativeBookshelfView" class="categoryPlacard absolute z-30 left-0 right-0 mx-auto -bottom-6 h-6 rounded-md text-center" :style="{ width: Math.min(200, width) + 'px' }">
<div class="w-full h-full shinyBlack flex items-center justify-center rounded-sm border" :style="{ padding: `0rem ${0.5 * sizeMultiplier}rem` }">
<p class="truncate" :style="{ fontSize: labelFontSize + 'rem' }">{{ title }}</p>
<div v-if="!isAlternativeBookshelfView" class="categoryPlacard absolute z-30 -bottom-6e left-0 right-0 mx-auto h-6e rounded-md text-center" :style="{ width: Math.min(200, width) + 'px' }">
<div class="w-full h-full shinyBlack flex items-center justify-center rounded-sm border" :style="{ padding: `0em ${0.5}em` }">
<p class="truncate" :style="{ fontSize: labelFontSize + 'em' }">{{ title }}</p>
</div>
</div>
<div v-else class="absolute z-30 left-0 right-0 mx-auto -bottom-8 h-8 py-1 rounded-md text-center">
<p class="truncate" :style="{ fontSize: labelFontSize * sizeMultiplier + 'rem' }">{{ title }}</p>
<div v-else class="relative z-30 left-0 right-0 mx-auto h-8e py-1e rounded-md text-center">
<p class="truncate" :style="{ fontSize: labelFontSize + 'em' }">{{ title }}</p>
</div>
</div>
</template>
@@ -25,8 +28,10 @@ export default {
props: {
index: Number,
width: Number,
height: Number,
bookCoverAspectRatio: Number,
height: {
type: Number,
default: 192
},
bookshelfView: {
type: Number,
default: 0
@@ -45,13 +50,21 @@ export default {
}
},
computed: {
bookCoverAspectRatio() {
return this.store.getters['libraries/getBookCoverAspectRatio']
},
cardWidth() {
return this.width || this.coverHeight
},
coverHeight() {
return this.height * this.sizeMultiplier
},
labelFontSize() {
if (this.width < 160) return 0.75
return 0.875
return 0.9
},
sizeMultiplier() {
if (this.bookCoverAspectRatio === 1) return this.width / (120 * 1.6)
return this.width / 120
return this.store.getters['user/getSizeMultiplier']
},
title() {
return this.playlist ? this.playlist.name : ''
@@ -112,4 +125,4 @@ export default {
}
}
}
</script>
</script>

View File

@@ -1,28 +1,32 @@
<template>
<div ref="card" :id="`series-card-${index}`" :style="{ width: width + 'px', height: height + 'px' }" class="rounded-sm z-30 cursor-pointer" @mousedown.prevent @mouseup.prevent @mousemove.prevent @mouseover="mouseover" @mouseleave="mouseleave" @click="clickCard">
<div class="absolute top-0 left-0 w-full box-shadow-book shadow-height" />
<div class="w-full h-full bg-primary relative rounded overflow-hidden z-0">
<covers-group-cover v-if="series" ref="cover" :id="seriesId" :name="displayTitle" :book-items="books" :width="width" :height="height" :book-cover-aspect-ratio="bookCoverAspectRatio" />
<div cy-id="card" ref="card" :id="`series-card-${index}`" :style="{ width: cardWidth + 'px' }" class="absolute rounded-sm z-30 cursor-pointer" @mousedown.prevent @mouseup.prevent @mousemove.prevent @mouseover="mouseover" @mouseleave="mouseleave" @click="clickCard">
<div cy-id="covers-area" class="relative" :style="{ height: coverHeight + 'px' }">
<div class="absolute top-0 left-0 w-full box-shadow-book shadow-height" />
<div class="w-full h-full bg-primary relative rounded overflow-hidden z-0">
<covers-group-cover v-if="series" ref="cover" :id="seriesId" :name="displayTitle" :book-items="books" :width="cardWidth" :height="coverHeight" :book-cover-aspect-ratio="bookCoverAspectRatio" />
</div>
<div cy-id="seriesLengthMarker" class="absolute rounded-lg bg-black bg-opacity-90 box-shadow-md z-20" :style="{ top: 0.375 + 'em', right: 0.375 + 'em', padding: `0.1em 0.25em` }" style="background-color: #cd9d49dd">
<p :style="{ fontSize: 0.8 + 'em' }">{{ books.length }}</p>
</div>
<div cy-id="seriesProgressBar" v-if="seriesPercentInProgress > 0" class="absolute bottom-0 left-0 h-1e shadow-sm max-w-full z-10 rounded-b w-full" :class="isSeriesFinished ? 'bg-success' : 'bg-yellow-400'" :style="{ width: seriesPercentInProgress * 100 + '%' }" />
<div cy-id="hoveringDisplayTitle" v-if="hasValidCovers" class="bg-black bg-opacity-60 absolute top-0 left-0 w-full h-full flex items-center justify-center text-center transition-opacity" :class="isHovering ? '' : 'opacity-0'" :style="{ padding: '1em' }">
<p :style="{ fontSize: 1.2 + 'em' }">{{ displayTitle }}</p>
</div>
<span cy-id="rssFeedMarker" v-if="!isHovering && rssFeed" class="absolute z-10 material-symbols text-success" :style="{ top: 0.5 + 'em', left: 0.5 + 'em', fontSize: 1.5 + 'em' }">rss_feed</span>
</div>
<div class="absolute z-10 top-1.5 right-1.5 rounded-md leading-3 text-sm p-1 font-semibold text-white flex items-center justify-center" style="background-color: #cd9d49dd">{{ books.length }}</div>
<div v-if="seriesPercentInProgress > 0" class="absolute bottom-0 left-0 h-1 shadow-sm max-w-full z-10 rounded-b w-full" :class="isSeriesFinished ? 'bg-success' : 'bg-yellow-400'" :style="{ width: seriesPercentInProgress * 100 + '%' }" />
<div v-if="hasValidCovers" class="bg-black bg-opacity-60 absolute top-0 left-0 w-full h-full flex items-center justify-center text-center transition-opacity" :class="isHovering ? '' : 'opacity-0'" :style="{ padding: `${sizeMultiplier}rem` }">
<p :style="{ fontSize: 1.2 * sizeMultiplier + 'rem' }">{{ displayTitle }}</p>
</div>
<span v-if="!isHovering && rssFeed" class="absolute z-10 material-icons text-success" :style="{ top: 0.5 * sizeMultiplier + 'rem', left: 0.5 * sizeMultiplier + 'rem', fontSize: 1.5 * sizeMultiplier + 'rem' }">rss_feed</span>
<div v-if="!isAlternativeBookshelfView" class="categoryPlacard absolute z-10 left-0 right-0 mx-auto -bottom-6 h-6 rounded-md text-center" :style="{ width: Math.min(200, width) + 'px' }">
<div class="w-full h-full shinyBlack flex items-center justify-center rounded-sm border" :style="{ padding: `0rem ${0.5 * sizeMultiplier}rem` }">
<p class="truncate" :style="{ fontSize: labelFontSize + 'rem' }">{{ displayTitle }}</p>
<div cy-id="standardBottomText" v-if="!isAlternativeBookshelfView" class="categoryPlacard absolute z-10 left-0 right-0 mx-auto -bottom-6e h-6e rounded-md text-center" :style="{ width: Math.min(200, cardWidth) + 'px' }">
<div class="w-full h-full shinyBlack flex items-center justify-center rounded-sm border" :style="{ padding: `0em 0.5em` }">
<p cy-id="standardBottomDisplayTitle" class="truncate" :style="{ fontSize: labelFontSize + 'em' }">{{ displayTitle }}</p>
</div>
</div>
<div v-else class="absolute z-30 left-0 right-0 mx-auto -bottom-8 h-8 py-1 rounded-md text-center">
<p class="truncate" :style="{ fontSize: labelFontSize * sizeMultiplier + 'rem' }">{{ displayTitle }}</p>
<p v-if="displaySortLine" class="truncate text-gray-400" :style="{ fontSize: 0.8 * sizeMultiplier + 'rem' }">{{ displaySortLine }}</p>
<div cy-id="detailBottomText" v-else class="relative z-30 left-0 right-0 mx-auto py-1e rounded-md text-center">
<p cy-id="detailBottomDisplayTitle" class="truncate" :style="{ fontSize: labelFontSize + 'em' }">{{ displayTitle }}</p>
<p cy-id="detailBottomSortLine" v-if="displaySortLine" class="truncate text-gray-400" :style="{ fontSize: 0.8 + 'em' }">{{ displaySortLine }}</p>
</div>
</div>
</template>
@@ -32,13 +36,14 @@ export default {
props: {
index: Number,
width: Number,
height: Number,
bookCoverAspectRatio: Number,
height: {
type: Number,
default: 192
},
bookshelfView: {
type: Number,
default: 0
},
isCategorized: Boolean,
seriesMount: {
type: Object,
default: () => null
@@ -56,34 +61,42 @@ export default {
}
},
computed: {
bookCoverAspectRatio() {
return this.store.getters['libraries/getBookCoverAspectRatio']
},
cardWidth() {
return this.width || (this.coverHeight / this.bookCoverAspectRatio) * 2
},
coverHeight() {
return this.height * this.sizeMultiplier
},
dateFormat() {
return this.store.state.serverSettings.dateFormat
},
labelFontSize() {
if (this.width < 160) return 0.75
return 0.875
return 0.9
},
sizeMultiplier() {
if (this.bookCoverAspectRatio === 1) return this.width / (120 * 1.6 * 2)
return this.width / 240
return this.store.getters['user/getSizeMultiplier']
},
seriesId() {
return this.series ? this.series.id : ''
return this.series?.id || ''
},
title() {
return this.series ? this.series.name : ''
return this.series?.name || ''
},
nameIgnorePrefix() {
return this.series ? this.series.nameIgnorePrefix : ''
return this.series?.nameIgnorePrefix || ''
},
displayTitle() {
if (this.sortingIgnorePrefix) return this.nameIgnorePrefix || this.title
return this.title
if (this.sortingIgnorePrefix) return this.nameIgnorePrefix || this.title || '\u00A0'
return this.title || '\u00A0'
},
displaySortLine() {
switch (this.orderBy) {
case 'addedAt':
return `${this.$strings.LabelAdded} ${this.$formatDate(this.addedAt, this.dateFormat)}`
return this.$getString('LabelAddedDate', [this.$formatDate(this.addedAt, this.dateFormat)])
case 'totalDuration':
return `${this.$strings.LabelDuration} ${this.$elapsedPrettyExtended(this.totalDuration, false)}`
case 'lastBookUpdated':
@@ -97,13 +110,13 @@ export default {
}
},
books() {
return this.series ? this.series.books || [] : []
return this.series?.books || []
},
addedAt() {
return this.series ? this.series.addedAt : 0
return this.series?.addedAt || 0
},
totalDuration() {
return this.series ? this.series.totalDuration : 0
return this.series?.totalDuration || 0
},
seriesBookProgress() {
return this.books
@@ -119,9 +132,13 @@ export default {
return this.seriesBookProgress.some((p) => !p.isFinished && p.progress > 0)
},
seriesPercentInProgress() {
let totalFinishedAndInProgress = this.seriesBooksFinished.length
if (this.hasSeriesBookInProgress) totalFinishedAndInProgress += 1
return Math.min(1, Math.max(0, totalFinishedAndInProgress / this.books.length))
if (!this.books.length) return 0
let progressPercent = 0
this.seriesBookProgress.forEach((progress) => {
progressPercent += progress.isFinished ? 1 : progress.progress || 0
})
progressPercent /= this.books.length
return Math.min(1, Math.max(0, progressPercent))
},
isSeriesFinished() {
return this.books.length === this.seriesBooksFinished.length
@@ -144,7 +161,7 @@ export default {
return this.bookshelfView == constants.BookshelfView.DETAIL
},
rssFeed() {
return this.series ? this.series.rssFeed : null
return this.series?.rssFeed
}
},
methods: {

View File

@@ -1,17 +1,19 @@
<template>
<nuxt-link :to="`/library/${currentLibraryId}/bookshelf?filter=narrators.${$encode(narrator.name)}`">
<div :style="{ width: width + 'px', height: height + 'px' }" class="bg-primary box-shadow-book rounded-md relative overflow-hidden">
<div class="absolute inset-0 w-full h-full flex items-center justify-center pointer-events-none opacity-40">
<span class="material-icons-outlined text-[10rem]">record_voice_over</span>
</div>
<div>
<nuxt-link :to="`/library/${currentLibraryId}/bookshelf?filter=narrators.${$encode(name)}`">
<div cy-id="card" :style="{ width: cardWidth + 'px', height: cardHeight + 'px', fontSize: sizeMultiplier + 'rem' }" class="bg-primary box-shadow-book rounded-md relative overflow-hidden">
<div class="absolute inset-0 w-full h-full flex items-center justify-center pointer-events-none opacity-40">
<span class="material-symbols text-[10em]">&#xe91f;</span>
</div>
<!-- Narrator name & num books overlay -->
<div class="absolute bottom-0 left-0 w-full py-1 bg-black bg-opacity-60 px-2">
<p class="text-center font-semibold truncate" :style="{ fontSize: sizeMultiplier * 0.75 + 'rem' }">{{ name }}</p>
<p class="text-center text-gray-200" :style="{ fontSize: sizeMultiplier * 0.65 + 'rem' }">{{ numBooks }} Book{{ numBooks === 1 ? '' : 's' }}</p>
<!-- Narrator name & num books overlay -->
<div class="absolute bottom-0 left-0 w-full py-1e bg-black bg-opacity-60 px-2e">
<p cy-id="name" class="text-center font-semibold truncate text-gray-200" :style="{ fontSize: 0.75 + 'em' }">{{ name }}</p>
<p cy-id="numBooks" class="text-center text-gray-200" :style="{ fontSize: 0.65 + 'em' }">{{ numBooks }} Book{{ numBooks === 1 ? '' : 's' }}</p>
</div>
</div>
</div>
</nuxt-link>
</nuxt-link>
</div>
</template>
<script>
@@ -22,29 +24,37 @@ export default {
default: () => {}
},
width: Number,
height: Number,
sizeMultiplier: {
height: {
type: Number,
default: 1
default: 100
}
},
data() {
return {}
},
computed: {
cardWidth() {
return this.cardHeight * 1.5
},
cardHeight() {
return this.height * this.sizeMultiplier
},
name() {
return this.narrator?.name || ''
},
numBooks() {
return this.narrator?.books?.length || 0
return this.narrator?.numBooks || this.narrator?.books?.length || 0
},
userCanUpdate() {
return this.$store.getters['user/getUserCanUpdate']
},
currentLibraryId() {
return this.$store.state.libraries.currentLibraryId
},
sizeMultiplier() {
return this.$store.getters['user/getSizeMultiplier']
}
},
methods: {}
}
</script>
</script>

View File

@@ -1,10 +1,11 @@
<template>
<div class="flex h-full px-1 overflow-hidden">
<div class="w-10 h-10 flex items-center justify-center">
<span class="material-icons text-2xl text-gray-200">record_voice_over</span>
<span class="material-symbols text-2xl text-gray-200">&#xe91f;</span>
</div>
<div class="flex-grow px-2 narratorSearchCardContent h-full">
<p class="truncate text-sm">{{ narrator }}</p>
<p class="text-xs text-gray-400">{{ $getString('LabelXBooks', [numBooks]) }}</p>
</div>
</div>
</template>
@@ -12,7 +13,8 @@
<script>
export default {
props: {
narrator: String
narrator: String,
numBooks: Number
},
data() {
return {}
@@ -26,9 +28,9 @@ export default {
<style scoped>
.narratorSearchCardContent {
width: calc(100% - 40px);
height: 40px;
height: 44px;
display: flex;
flex-direction: column;
justify-content: center;
}
</style>
</style>

View File

@@ -4,11 +4,11 @@
<p class="text-base md:text-lg font-semibold pr-4">{{ eventName }}</p>
<div class="flex-grow" />
<ui-btn v-if="eventName === 'onTest' && notification.enabled" :loading="testing" small class="mr-2" @click.stop="fireTestEventAndSucceed">Fire onTest Event</ui-btn>
<ui-btn v-if="eventName === 'onTest' && notification.enabled" :loading="testing" small class="mr-2" color="red-600" @click.stop="fireTestEventAndFail">Fire & Fail</ui-btn>
<ui-btn v-if="eventName === 'onTest' && notification.enabled" :loading="testing" small class="mr-2" @click.stop="fireTestEventAndSucceed">{{ this.$strings.ButtonFireOnTest }}</ui-btn>
<ui-btn v-if="eventName === 'onTest' && notification.enabled" :loading="testing" small class="mr-2" color="red-600" @click.stop="fireTestEventAndFail">{{ this.$strings.ButtonFireAndFail }}</ui-btn>
<!-- <ui-btn v-if="eventName === 'onTest' && notification.enabled" :loading="testing" small class="mr-2" @click.stop="rapidFireTestEvents">Rapid Fire</ui-btn> -->
<ui-btn v-else-if="notification.enabled" :loading="sendingTest" small class="mr-2" @click.stop="sendTestClick">Test</ui-btn>
<ui-btn v-else :loading="enabling" small color="success" class="mr-2" @click="enableNotification">Enable</ui-btn>
<ui-btn v-else-if="notification.enabled" :loading="sendingTest" small class="mr-2" @click.stop="sendTestClick">{{ this.$strings.ButtonTest }}</ui-btn>
<ui-btn v-else :loading="enabling" small color="success" class="mr-2" @click="enableNotification">{{ this.$strings.ButtonEnable }}</ui-btn>
<ui-icon-btn :size="7" icon-font-size="1.1rem" icon="edit" class="mr-2" @click="editNotification" />
<ui-icon-btn bg-color="error" :size="7" icon-font-size="1.2rem" icon="delete" @click="deleteNotificationClick" />
@@ -65,12 +65,12 @@ export default {
this.$axios
.$get(`/api/notifications/test?fail=${intentionallyFail ? 1 : 0}`)
.then(() => {
this.$toast.success('Triggered onTest Event')
this.$toast.success(this.$strings.ToastNotificationTestTriggerSuccess)
})
.catch((error) => {
console.error('Failed', error)
const errorMsg = error.response ? error.response.data : null
this.$toast.error(`Failed: ${errorMsg}` || 'Failed to trigger onTest event')
this.$toast.error(`Failed: ${errorMsg}` || this.$strings.ToastNotificationTestTriggerFailed)
})
.finally(() => {
this.testing = false
@@ -91,7 +91,7 @@ export default {
// End testing functions
sendTestClick() {
const payload = {
message: `Trigger this notification with test data?`,
message: this.$strings.MessageConfirmNotificationTestTrigger,
callback: (confirmed) => {
if (confirmed) {
this.sendTest()
@@ -106,12 +106,12 @@ export default {
this.$axios
.$get(`/api/notifications/${this.notification.id}/test`)
.then(() => {
this.$toast.success('Triggered test notification')
this.$toast.success(this.$strings.ToastNotificationTestTriggerSuccess)
})
.catch((error) => {
console.error('Failed', error)
const errorMsg = error.response ? error.response.data : null
this.$toast.error(`Failed: ${errorMsg}` || 'Failed to trigger test notification')
this.$toast.error(`Failed: ${errorMsg}` || this.$strings.ToastNotificationTestTriggerFailed)
})
.finally(() => {
this.sendingTest = false
@@ -127,11 +127,10 @@ export default {
.$patch(`/api/notifications/${this.notification.id}`, payload)
.then((updatedSettings) => {
this.$emit('update', updatedSettings)
this.$toast.success('Notification enabled')
})
.catch((error) => {
console.error('Failed to update notification', error)
this.$toast.error('Failed to update notification')
this.$toast.error(this.$strings.ToastNotificationUpdateFailed)
})
.finally(() => {
this.enabling = false
@@ -139,7 +138,7 @@ export default {
},
deleteNotificationClick() {
const payload = {
message: `Are you sure you want to delete this notification?`,
message: this.$strings.MessageConfirmDeleteNotification,
callback: (confirmed) => {
if (confirmed) {
this.deleteNotification()
@@ -155,11 +154,10 @@ export default {
.$delete(`/api/notifications/${this.notification.id}`)
.then((updatedSettings) => {
this.$emit('update', updatedSettings)
this.$toast.success('Deleted notification')
})
.catch((error) => {
console.error('Failed', error)
this.$toast.error('Failed to delete notification')
this.$toast.error(this.$strings.ToastNotificationDeleteFailed)
})
.finally(() => {
this.deleting = false
@@ -171,4 +169,4 @@ export default {
},
mounted() {}
}
</script>
</script>

View File

@@ -1,10 +1,11 @@
<template>
<div class="flex h-full px-1 overflow-hidden">
<div class="w-10 h-10 flex items-center justify-center">
<span class="material-icons text-2xl text-gray-200">local_offer</span>
<span class="material-symbols text-2xl text-gray-200">local_offer</span>
</div>
<div class="flex-grow px-2 tagSearchCardContent h-full">
<p class="truncate text-sm">{{ tag }}</p>
<p class="text-xs text-gray-400">{{ $getString('LabelXItems', [numItems]) }}</p>
</div>
</div>
</template>
@@ -12,7 +13,8 @@
<script>
export default {
props: {
tag: String
tag: String,
numItems: Number
},
data() {
return {}
@@ -26,9 +28,9 @@ export default {
<style>
.tagSearchCardContent {
width: calc(100% - 40px);
height: 40px;
height: 44px;
display: flex;
flex-direction: column;
justify-content: center;
}
</style>
</style>

View File

@@ -89,6 +89,14 @@
</template>
</div>
</div>
<div v-if="language" class="flex py-0.5">
<div class="w-24 min-w-24 sm:w-32 sm:min-w-32">
<span class="text-white text-opacity-60 uppercase text-sm">{{ $strings.LabelLanguage }}</span>
</div>
<div>
<nuxt-link :to="`/library/${libraryId}/bookshelf?filter=languages.${$encode(language)}`" class="hover:underline">{{ language }}</nuxt-link>
</div>
</div>
<div v-if="tracks.length || audioFile || (isPodcast && totalPodcastDuration)" class="flex py-0.5">
<div class="w-24 min-w-24 sm:w-32 sm:min-w-32">
<span class="text-white text-opacity-60 uppercase text-sm">{{ $strings.LabelDuration }}</span>
@@ -182,6 +190,9 @@ export default {
narrators() {
return this.mediaMetadata.narrators || []
},
language() {
return this.mediaMetadata.language || null
},
durationPretty() {
if (this.isPodcast) return this.$elapsedPrettyExtended(this.totalPodcastDuration)

View File

@@ -10,7 +10,7 @@
</svg>
</span>
<div v-else class="ml-3 absolute inset-y-0 right-0 flex items-center pr-2 cursor-pointer text-gray-400 hover:text-gray-300" @mousedown.stop @mouseup.stop @click.stop.prevent="clearSelected">
<span class="material-icons" style="font-size: 1.1rem">close</span>
<span class="material-symbols" style="font-size: 1.1rem">close</span>
</div>
</button>
@@ -24,7 +24,7 @@
<!-- selected checkmark icon -->
<div v-if="item.value === selected" class="absolute inset-y-0 right-2 h-full flex items-center pointer-events-none">
<span class="material-icons text-base text-yellow-400">check</span>
<span class="material-symbols text-base text-yellow-400">check</span>
</div>
</li>
</template>

View File

@@ -1,13 +1,15 @@
<template>
<div class="sm:w-80 w-full relative">
<form @submit.prevent="submitSearch">
<ui-text-input ref="input" v-model="search" :placeholder="$strings.PlaceholderSearch" @input="inputUpdate" @focus="focussed" @blur="blurred" class="w-full h-8 text-sm" />
</form>
<div class="absolute top-0 right-0 bottom-0 h-full flex items-center px-2 text-gray-400 cursor-pointer" @click="clickClear">
<span v-if="!search" class="material-icons" style="font-size: 1.2rem">search</span>
<span v-else class="material-icons" style="font-size: 1.2rem">close</span>
<div class="">
<div class="w-full relative sm:w-80">
<form @submit.prevent="submitSearch">
<ui-text-input ref="input" v-model="search" :placeholder="$strings.PlaceholderSearch" @input="inputUpdate" @focus="focussed" @blur="blurred" class="w-full h-8 text-sm" />
</form>
<div class="absolute top-0 right-0 bottom-0 h-full flex items-center px-2 text-gray-400 cursor-pointer" @click="clickClear">
<span v-if="!search" class="material-symbols" style="font-size: 1.2rem">&#xe8b6;</span>
<span v-else class="material-symbols" style="font-size: 1.2rem">close</span>
</div>
</div>
<div v-show="showMenu && (lastSearch || isTyping)" class="absolute z-40 -mt-px w-40 sm:w-full bg-bg border border-black-200 shadow-lg rounded-md py-1 px-2 text-base ring-1 ring-black ring-opacity-5 overflow-auto focus:outline-none sm:text-sm globalSearchMenu">
<div v-show="showMenu && (lastSearch || isTyping)" class="absolute z-40 -mt-px w-full max-w-64 sm:max-w-80 sm:w-80 bg-bg border border-black-200 shadow-lg rounded-md py-1 px-2 text-base ring-1 ring-black ring-opacity-5 overflow-auto focus:outline-none sm:text-sm globalSearchMenu">
<ul class="h-full w-full" role="listbox" aria-labelledby="listbox-label">
<li v-if="isTyping" class="py-2 px-2">
<p>{{ $strings.MessageThinking }}</p>
@@ -23,7 +25,7 @@
<template v-for="item in bookResults">
<li :key="item.libraryItem.id" class="text-gray-50 select-none relative cursor-pointer hover:bg-black-400 py-1" role="option" @click="clickOption">
<nuxt-link :to="`/item/${item.libraryItem.id}`">
<cards-item-search-card :library-item="item.libraryItem" :match-key="item.matchKey" :match-text="item.matchText" :search="lastSearch" />
<cards-item-search-card :library-item="item.libraryItem" />
</nuxt-link>
</li>
</template>
@@ -32,7 +34,7 @@
<template v-for="item in podcastResults">
<li :key="item.libraryItem.id" class="text-gray-50 select-none relative cursor-pointer hover:bg-black-400 py-1" role="option" @click="clickOption">
<nuxt-link :to="`/item/${item.libraryItem.id}`">
<cards-item-search-card :library-item="item.libraryItem" :match-key="item.matchKey" :match-text="item.matchText" :search="lastSearch" />
<cards-item-search-card :library-item="item.libraryItem" />
</nuxt-link>
</li>
</template>
@@ -40,7 +42,7 @@
<p v-if="authorResults.length" class="uppercase text-xs text-gray-400 mb-1 mt-3 px-1 font-semibold">{{ $strings.LabelAuthors }}</p>
<template v-for="item in authorResults">
<li :key="item.id" class="text-gray-50 select-none relative cursor-pointer hover:bg-black-400 py-1" role="option" @click="clickOption">
<nuxt-link :to="`/library/${currentLibraryId}/bookshelf?filter=authors.${$encode(item.id)}`">
<nuxt-link :to="`/author/${item.id}`">
<cards-author-search-card :author="item" />
</nuxt-link>
</li>
@@ -57,9 +59,18 @@
<p v-if="tagResults.length" class="uppercase text-xs text-gray-400 mb-1 mt-3 px-1 font-semibold">{{ $strings.LabelTags }}</p>
<template v-for="item in tagResults">
<li :key="item.name" class="text-gray-50 select-none relative cursor-pointer hover:bg-black-400 py-1" role="option" @click="clickOption">
<li :key="`tag.${item.name}`" class="text-gray-50 select-none relative cursor-pointer hover:bg-black-400 py-1" role="option" @click="clickOption">
<nuxt-link :to="`/library/${currentLibraryId}/bookshelf?filter=tags.${$encode(item.name)}`">
<cards-tag-search-card :tag="item.name" />
<cards-tag-search-card :tag="item.name" :num-items="item.numItems" />
</nuxt-link>
</li>
</template>
<p v-if="genreResults.length" class="uppercase text-xs text-gray-400 mb-1 mt-3 px-1 font-semibold">{{ $strings.LabelGenres }}</p>
<template v-for="item in genreResults">
<li :key="`genre.${item.name}`" class="text-gray-50 select-none relative cursor-pointer hover:bg-black-400 py-1" role="option" @click="clickOption">
<nuxt-link :to="`/library/${currentLibraryId}/bookshelf?filter=genres.${$encode(item.name)}`">
<cards-genre-search-card :genre="item.name" :num-items="item.numItems" />
</nuxt-link>
</li>
</template>
@@ -68,7 +79,7 @@
<template v-for="narrator in narratorResults">
<li :key="narrator.name" class="text-gray-50 select-none relative cursor-pointer hover:bg-black-400 py-1" role="option" @click="clickOption">
<nuxt-link :to="`/library/${currentLibraryId}/bookshelf?filter=narrators.${$encode(narrator.name)}`">
<cards-narrator-search-card :narrator="narrator.name" />
<cards-narrator-search-card :narrator="narrator.name" :num-books="narrator.numBooks" />
</nuxt-link>
</li>
</template>
@@ -93,6 +104,7 @@ export default {
authorResults: [],
seriesResults: [],
tagResults: [],
genreResults: [],
narratorResults: [],
searchTimeout: null,
lastSearch: null
@@ -103,7 +115,7 @@ export default {
return this.$store.state.libraries.currentLibraryId
},
totalResults() {
return this.bookResults.length + this.seriesResults.length + this.authorResults.length + this.tagResults.length + this.podcastResults.length
return this.bookResults.length + this.seriesResults.length + this.authorResults.length + this.tagResults.length + this.genreResults.length + this.podcastResults.length + this.narratorResults.length
}
},
methods: {
@@ -114,7 +126,7 @@ export default {
if (!this.search) return
var search = this.search
this.clearResults()
this.$router.push(`/library/${this.currentLibraryId}/search?q=${search}`)
this.$router.push(`/library/${this.currentLibraryId}/search?q=${encodeURIComponent(search)}`)
},
clearResults() {
this.search = null
@@ -124,6 +136,7 @@ export default {
this.authorResults = []
this.seriesResults = []
this.tagResults = []
this.genreResults = []
this.narratorResults = []
this.showMenu = false
this.isFetching = false
@@ -153,7 +166,7 @@ export default {
}
this.isFetching = true
const searchResults = await this.$axios.$get(`/api/libraries/${this.currentLibraryId}/search?q=${value}&limit=3`).catch((error) => {
const searchResults = await this.$axios.$get(`/api/libraries/${this.currentLibraryId}/search?q=${encodeURIComponent(value)}&limit=3`).catch((error) => {
console.error('Search error', error)
return []
})
@@ -166,6 +179,7 @@ export default {
this.authorResults = searchResults.authors || []
this.seriesResults = searchResults.series || []
this.tagResults = searchResults.tags || []
this.genreResults = searchResults.genres || []
this.narratorResults = searchResults.narrators || []
this.isFetching = false
@@ -201,4 +215,4 @@ export default {
.globalSearchMenu {
max-height: calc(100vh - 75px);
}
</style>
</style>

View File

@@ -10,7 +10,7 @@
</svg>
</span>
<div v-else class="ml-3 absolute inset-y-0 right-0 flex items-center pr-2 cursor-pointer text-gray-400 hover:text-gray-200" @mousedown.stop @mouseup.stop @click.stop.prevent="clearSelected">
<span class="material-icons" style="font-size: 1.1rem">close</span>
<span class="material-symbols" style="font-size: 1.1rem">close</span>
</div>
</button>
@@ -22,11 +22,11 @@
<span class="font-normal ml-3 block truncate text-sm">{{ item.text }}</span>
</div>
<div v-if="item.sublist" class="absolute right-1 top-0 bottom-0 h-full flex items-center">
<span class="material-icons text-2xl">arrow_right</span>
<span class="material-symbols text-2xl">arrow_right</span>
</div>
<!-- selected checkmark icon -->
<div v-if="item.value === selected" class="absolute inset-y-0 right-2 h-full flex items-center pointer-events-none">
<span class="material-icons text-base text-yellow-400">check</span>
<span class="material-symbols text-base text-yellow-400">check</span>
</div>
</li>
</template>
@@ -34,15 +34,15 @@
<ul v-show="sublist" class="h-full w-full" role="listbox" aria-labelledby="listbox-label">
<li class="text-gray-50 select-none relative py-2 pl-9 cursor-pointer hover:bg-white/5" role="option" @click="sublist = null">
<div class="absolute left-1 top-0 bottom-0 h-full flex items-center">
<span class="material-icons text-2xl">arrow_left</span>
<span class="material-symbols text-2xl">arrow_left</span>
</div>
<div class="flex items-center justify-between">
<span class="font-normal block truncate">Back</span>
<span class="font-normal block truncate">{{ $strings.ButtonBack }}</span>
</div>
</li>
<li v-if="!sublistItems.length" class="text-gray-400 select-none relative px-2" role="option">
<div class="flex items-center justify-center">
<span class="font-normal block truncate py-2">No {{ sublist }}</span>
<span class="font-normal block truncate py-2">{{ $getString('LabelLibraryFilterSublistEmpty', [selectedSublistText]) }}</span>
</div>
</li>
<template v-for="item in sublistItems">
@@ -52,7 +52,7 @@
</div>
<!-- selected checkmark icon -->
<div v-if="`${sublist}.${item.value}` === selected" class="absolute inset-y-0 right-2 h-full flex items-center pointer-events-none">
<span class="material-icons text-base text-yellow-400">check</span>
<span class="material-symbols text-base text-yellow-400">check</span>
</div>
</li>
</template>
@@ -89,6 +89,9 @@ export default {
this.$emit('input', val)
}
},
userIsAdminOrUp() {
return this.$store.getters['user/getIsAdminOrUp']
},
libraryMediaType() {
return this.$store.getters['libraries/getCurrentLibraryMediaType']
},
@@ -106,31 +109,37 @@ export default {
},
{
text: this.$strings.LabelGenre,
textPlural: this.$strings.LabelGenres,
value: 'genres',
sublist: true
},
{
text: this.$strings.LabelTag,
textPlural: this.$strings.LabelTags,
value: 'tags',
sublist: true
},
{
text: this.$strings.LabelAuthor,
textPlural: this.$strings.LabelAuthors,
value: 'authors',
sublist: true
},
{
text: this.$strings.LabelNarrator,
textPlural: this.$strings.LabelNarrators,
value: 'narrators',
sublist: true
},
{
text: this.$strings.LabelPublisher,
textPlural: this.$strings.LabelPublishers,
value: 'publishers',
sublist: true
},
{
text: this.$strings.LabelLanguage,
textPlural: this.$strings.LabelLanguages,
value: 'languages',
sublist: true
},
@@ -142,43 +151,50 @@ export default {
]
},
bookItems() {
return [
const items = [
{
text: this.$strings.LabelAll,
value: 'all'
},
{
text: this.$strings.LabelGenre,
textPlural: this.$strings.LabelGenres,
value: 'genres',
sublist: true
},
{
text: this.$strings.LabelTag,
textPlural: this.$strings.LabelTags,
value: 'tags',
sublist: true
},
{
text: this.$strings.LabelSeries,
textPlural: this.$strings.LabelSeries,
value: 'series',
sublist: true
},
{
text: this.$strings.LabelAuthor,
textPlural: this.$strings.LabelAuthors,
value: 'authors',
sublist: true
},
{
text: this.$strings.LabelNarrator,
textPlural: this.$strings.LabelNarrators,
value: 'narrators',
sublist: true
},
{
text: this.$strings.LabelPublisher,
textPlural: this.$strings.LabelPublishers,
value: 'publishers',
sublist: true
},
{
text: this.$strings.LabelLanguage,
textPlural: this.$strings.LabelLanguages,
value: 'languages',
sublist: true
},
@@ -218,6 +234,14 @@ export default {
sublist: false
}
]
if (this.userIsAdminOrUp) {
items.push({
text: this.$strings.LabelShareOpen,
value: 'share-open',
sublist: false
})
}
return items
},
podcastItems() {
return [
@@ -227,14 +251,22 @@ export default {
},
{
text: this.$strings.LabelGenre,
textPlural: this.$strings.LabelGenres,
value: 'genres',
sublist: true
},
{
text: this.$strings.LabelTag,
textPlural: this.$strings.LabelTags,
value: 'tags',
sublist: true
},
{
text: this.$strings.LabelLanguage,
textPlural: this.$strings.LabelLanguages,
value: 'languages',
sublist: true
},
{
text: this.$strings.ButtonIssues,
value: 'issues',
@@ -250,11 +282,13 @@ export default {
},
{
text: this.$strings.LabelGenre,
textPlural: this.$strings.LabelGenres,
value: 'genres',
sublist: true
},
{
text: this.$strings.LabelTag,
textPlural: this.$strings.LabelTags,
value: 'tags',
sublist: true
},
@@ -274,6 +308,13 @@ export default {
selectedItemSublist() {
return this.selected?.includes('.') ? this.selected.split('.')[0] : null
},
selectedSublistText() {
if (!this.sublist) {
return ''
}
const sublistItem = this.selectItems.find((i) => i.value === this.sublist)
return sublistItem?.textPlural || sublistItem?.text || ''
},
selectedText() {
if (!this.selected) return ''
const parts = this.selected.split('.')
@@ -348,6 +389,10 @@ export default {
},
tracks() {
return [
{
id: 'none',
name: this.$strings.LabelTracksNone
},
{
id: 'single',
name: this.$strings.LabelTracksSingleTrack
@@ -364,9 +409,17 @@ export default {
id: 'ebook',
name: this.$strings.LabelHasEbook
},
{
id: 'no-ebook',
name: this.$strings.LabelMissingEbook
},
{
id: 'supplementary',
name: this.$strings.LabelHasSupplementaryEbook
},
{
id: 'no-supplementary',
name: this.$strings.LabelMissingSupplementaryEbook
}
]
},
@@ -488,4 +541,4 @@ export default {
.libraryFilterMenu {
max-height: calc(100vh - 125px);
}
</style>
</style>

View File

@@ -3,7 +3,7 @@
<button type="button" class="relative w-full h-full bg-fg border border-gray-500 hover:border-gray-400 rounded shadow-sm pl-3 pr-3 py-0 text-left focus:outline-none sm:text-sm cursor-pointer" aria-haspopup="listbox" aria-expanded="true" aria-labelledby="listbox-label" @click.prevent="showMenu = !showMenu">
<span class="flex items-center justify-between">
<span class="block truncate text-xs" :class="!selectedText ? 'text-gray-300' : ''">{{ selectedText }}</span>
<span class="material-icons text-lg text-yellow-400">{{ descending ? 'expand_more' : 'expand_less' }}</span>
<span class="material-symbols text-lg text-yellow-400">{{ descending ? 'expand_more' : 'expand_less' }}</span>
</span>
</button>
@@ -14,7 +14,7 @@
<span class="font-normal ml-3 block truncate">{{ item.text }}</span>
</div>
<span v-if="item.value === selected" class="text-yellow-400 absolute inset-y-0 right-0 flex items-center pr-4">
<span class="material-icons text-xl">{{ descending ? 'expand_more' : 'expand_less' }}</span>
<span class="material-symbols text-xl">{{ descending ? 'expand_more' : 'expand_less' }}</span>
</span>
</li>
</template>
@@ -88,6 +88,10 @@ export default {
{
text: this.$strings.LabelFileModified,
value: 'mtimeMs'
},
{
text: this.$strings.LabelRandomly,
value: 'random'
}
]
},
@@ -128,6 +132,10 @@ export default {
{
text: this.$strings.LabelFileModified,
value: 'mtimeMs'
},
{
text: this.$strings.LabelRandomly,
value: 'random'
}
]
},
@@ -215,4 +223,4 @@ export default {
}
}
}
</script>
</script>

View File

@@ -3,7 +3,7 @@
<button type="button" class="relative w-full h-full border border-gray-500 hover:border-gray-400 rounded shadow-sm pl-3 pr-3 py-0 text-left focus:outline-none cursor-pointer" aria-haspopup="listbox" aria-expanded="true" aria-labelledby="listbox-label" @click.prevent="showMenu = !showMenu">
<span class="flex items-center justify-between">
<span class="block truncate text-xs" :class="!selectedText ? 'text-gray-300' : ''">{{ selectedText }}</span>
<span class="material-icons text-lg text-yellow-400">{{ descending ? 'expand_more' : 'expand_less' }}</span>
<span class="material-symbols text-lg text-yellow-400">{{ descending ? 'expand_more' : 'expand_less' }}</span>
</span>
</button>
@@ -14,7 +14,7 @@
<span class="font-normal ml-3 block truncate">{{ item.text }}</span>
</div>
<span v-if="item.value === selected" class="text-yellow-400 absolute inset-y-0 right-0 flex items-center pr-4">
<span class="material-icons text-xl">{{ descending ? 'expand_more' : 'expand_less' }}</span>
<span class="material-symbols text-xl">{{ descending ? 'expand_more' : 'expand_less' }}</span>
</span>
</li>
</template>

View File

@@ -1,8 +1,8 @@
<template>
<div class="relative" v-click-outside="clickOutside" @mouseover="mouseover" @mouseleave="mouseleave">
<div class="cursor-pointer text-gray-300 hover:text-white" @mousedown.prevent @mouseup.prevent @click="clickVolumeIcon">
<span class="material-icons text-2xl sm:text-3xl">{{ volumeIcon }}</span>
</div>
<button :aria-label="$strings.LabelVolume" class="text-gray-300 hover:text-white" @mousedown.prevent @mouseup.prevent @click="clickVolumeIcon">
<span class="material-symbols text-2xl sm:text-3xl">{{ volumeIcon }}</span>
</button>
<transition name="menux">
<div v-show="isOpen" class="volumeMenu h-6 absolute bottom-2 w-28 px-2 bg-bg shadow-sm rounded-lg" style="left: -116px">
<div ref="volumeTrack" class="h-1 w-full bg-gray-500 my-2.5 relative cursor-pointer rounded-full" @mousedown="mousedownTrack" @click="clickVolumeTrack">
@@ -38,8 +38,8 @@ export default {
},
set(val) {
try {
localStorage.setItem("volume", val);
} catch(error) {
localStorage.setItem('volume', val)
} catch (error) {
console.error('Failed to store volume', err)
}
this.$emit('input', val)
@@ -146,7 +146,7 @@ export default {
if (this.value === 0) {
this.isMute = true
}
const storageVolume = localStorage.getItem("volume")
const storageVolume = localStorage.getItem('volume')
if (storageVolume) {
this.volume = parseFloat(storageVolume)
}

View File

@@ -84,4 +84,4 @@ export default {
},
mounted() {}
}
</script>
</script>

View File

@@ -5,7 +5,8 @@
<div class="absolute cover-bg" ref="coverBg" />
</div>
<img v-if="libraryItem" ref="cover" :src="fullCoverUrl" loading="lazy" @error="imageError" @load="imageLoaded" class="w-full h-full absolute top-0 left-0 z-10 duration-300 transition-opacity" :style="{ opacity: imageReady ? '1' : '0' }" :class="showCoverBg ? 'object-contain' : 'object-fill'" />
<img v-if="libraryItem" ref="cover" :src="fullCoverUrl" loading="lazy" draggable="false" @error="imageError" @load="imageLoaded" class="w-full h-full absolute top-0 left-0 z-10 duration-300 transition-opacity" :style="{ opacity: imageReady ? '1' : '0' }" :class="showCoverBg ? 'object-contain' : 'object-fill'" @click="clickCover" />
<div v-show="loading && libraryItem" class="absolute top-0 left-0 h-full w-full flex items-center justify-center">
<p class="text-center" :style="{ fontSize: 0.75 * sizeMultiplier + 'rem' }">{{ title }}</p>
<div class="absolute top-2 right-2">
@@ -43,6 +44,7 @@ export default {
type: Number,
default: 120
},
expandOnClick: Boolean,
bookCoverAspectRatio: Number
},
data() {
@@ -99,9 +101,14 @@ export default {
},
fullCoverUrl() {
if (!this.libraryItem) return null
var store = this.$store || this.$nuxt.$store
const store = this.$store || this.$nuxt.$store
return store.getters['globals/getLibraryItemCoverSrc'](this.libraryItem, this.placeholderUrl)
},
rawCoverUrl() {
if (!this.libraryItem) return null
const store = this.$store || this.$nuxt.$store
return store.getters['globals/getLibraryItemCoverSrc'](this.libraryItem, this.placeholderUrl, true)
},
cover() {
return this.media.coverPath || this.placeholderUrl
},
@@ -124,14 +131,16 @@ export default {
authorBottom() {
return 0.75 * this.sizeMultiplier
},
userToken() {
return this.$store.getters['user/getToken']
},
resolution() {
return `${this.naturalWidth}x${this.naturalHeight}px`
}
},
methods: {
clickCover() {
if (this.expandOnClick && this.libraryItem) {
this.$store.commit('globals/setRawCoverPreviewModal', this.rawCoverUrl)
}
},
setCoverBg() {
if (this.$refs.coverBg) {
this.$refs.coverBg.style.backgroundImage = `url("${this.fullCoverUrl}")`

View File

@@ -7,14 +7,14 @@
<img ref="cover" :src="cover" @error="imageError" @load="imageLoaded" class="w-full h-full absolute top-0 left-0" :class="showCoverBg ? 'object-contain' : 'object-fill'" />
<a v-if="!imageFailed && showOpenNewTab && isHovering" :href="cover" @click.stop target="_blank" class="absolute bg-primary flex items-center justify-center shadow-sm rounded-full hover:scale-110 transform duration-100" :style="{ top: sizeMultiplier * 0.5 + 'rem', right: sizeMultiplier * 0.5 + 'rem', width: 2.5 * sizeMultiplier + 'rem', height: 2.5 * sizeMultiplier + 'rem' }">
<span class="material-icons" :style="{ fontSize: sizeMultiplier * 1.75 + 'rem' }">open_in_new</span>
<span class="material-symbols" :style="{ fontSize: sizeMultiplier * 1.75 + 'rem' }">open_in_new</span>
</a>
</div>
<div v-if="imageFailed" class="absolute top-0 left-0 right-0 bottom-0 w-full h-full bg-red-100" :style="{ padding: placeholderCoverPadding + 'rem' }">
<div class="w-full h-full border-2 border-error flex flex-col items-center justify-center">
<img src="/Logo.png" class="mb-2" :style="{ height: 64 * sizeMultiplier + 'px' }" />
<p class="text-center text-error" :style="{ fontSize: sizeMultiplier + 'rem' }">Invalid Cover</p>
<img v-if="width > 100" src="/Logo.png" class="mb-2" :style="{ height: 40 * sizeMultiplier + 'px' }" />
<p class="text-center text-error" :style="{ fontSize: invalidCoverFontSize + 'rem' }">Invalid Cover</p>
</div>
</div>
@@ -58,11 +58,14 @@ export default {
sizeMultiplier() {
return this.width / 120
},
invalidCoverFontSize() {
return Math.max(this.sizeMultiplier * 0.8, 0.5)
},
placeholderCoverPadding() {
return 0.8 * this.sizeMultiplier
},
resolution() {
return `${this.naturalWidth}x${this.naturalHeight}px`
return `${this.naturalWidth}×${this.naturalHeight}px`
},
placeholderUrl() {
const config = this.$config || this.$nuxt.$config

View File

@@ -10,17 +10,21 @@
<div class="w-full p-8">
<div class="flex py-2">
<div class="w-1/2 px-2">
<ui-text-input-with-label v-model="newUser.username" :label="$strings.LabelUsername" />
<ui-text-input-with-label v-model.trim="newUser.username" :label="$strings.LabelUsername" />
</div>
<div class="w-1/2 px-2">
<ui-text-input-with-label v-if="!isEditingRoot" v-model="newUser.password" :label="isNew ? $strings.LabelPassword : $strings.LabelChangePassword" type="password" />
<ui-text-input-with-label v-else v-model.trim="newUser.email" :label="$strings.LabelEmail" />
</div>
</div>
<div v-show="!isEditingRoot" class="flex py-2">
<div class="px-2 w-52">
<ui-dropdown v-model="newUser.type" :label="$strings.LabelAccountType" :disabled="isEditingRoot" :items="accountTypes" @input="userTypeUpdated" />
<div class="w-1/2 px-2">
<ui-text-input-with-label v-model.trim="newUser.email" :label="$strings.LabelEmail" />
</div>
<div class="flex-grow" />
<div class="px-2 w-52">
<ui-dropdown v-model="newUser.type" :label="$strings.LabelAccountType" :disabled="isEditingRoot" :items="accountTypes" small @input="userTypeUpdated" />
</div>
<div class="flex items-center pt-4 px-2">
<p class="px-3 font-semibold" id="user-enabled-toggle" :class="isEditingRoot ? 'text-gray-300' : ''">{{ $strings.LabelEnable }}</p>
<ui-toggle-switch labeledBy="user-enabled-toggle" v-model="newUser.isActive" :disabled="isEditingRoot" />
@@ -107,7 +111,8 @@
</div>
<div class="flex pt-4 px-2">
<ui-btn v-if="isEditingRoot" to="/account">{{ $strings.ButtonChangeRootPassword }}</ui-btn>
<ui-btn v-if="hasOpenIDLink" small :loading="unlinkingFromOpenID" color="primary" type="button" class="mr-2" @click.stop="unlinkOpenID">{{ $strings.ButtonUnlinkOpenId }}</ui-btn>
<ui-btn v-if="isEditingRoot" small class="flex items-center" to="/account">{{ $strings.ButtonChangeRootPassword }}</ui-btn>
<div class="flex-grow" />
<ui-btn color="success" type="submit">{{ $strings.ButtonSubmit }}</ui-btn>
</div>
@@ -132,7 +137,8 @@ export default {
newUser: {},
isNew: true,
tags: [],
loadingTags: false
loadingTags: false,
unlinkingFromOpenID: false
}
},
watch: {
@@ -176,7 +182,7 @@ export default {
return this.isNew ? this.$strings.HeaderNewAccount : this.$strings.HeaderUpdateAccount
},
isEditingRoot() {
return this.account && this.account.type === 'root'
return this.account?.type === 'root'
},
libraries() {
return this.$store.state.libraries.libraries
@@ -194,6 +200,9 @@ export default {
},
tagsSelectionText() {
return this.newUser.permissions.selectedTagsNotAccessible ? this.$strings.LabelTagsNotAccessibleToUser : this.$strings.LabelTagsAccessibleToUser
},
hasOpenIDLink() {
return !!this.account?.hasOpenIDLink
}
},
methods: {
@@ -201,6 +210,31 @@ export default {
// Force close when navigating - used in UsersTable
if (this.$refs.modal) this.$refs.modal.setHide()
},
unlinkOpenID() {
const payload = {
message: this.$strings.MessageConfirmUnlinkOpenId,
callback: (confirmed) => {
if (confirmed) {
this.unlinkingFromOpenID = true
this.$axios
.$patch(`/api/users/${this.account.id}/openid-unlink`)
.then(() => {
this.$toast.success(this.$strings.ToastUnlinkOpenIdSuccess)
this.show = false
})
.catch((error) => {
console.error('Failed to unlink user from OpenID', error)
this.$toast.error(this.$strings.ToastUnlinkOpenIdFailed)
})
.finally(() => {
this.unlinkingFromOpenID = false
})
}
},
type: 'yesNo'
}
this.$store.commit('globals/setConfirmPrompt', payload)
},
accessAllTagsToggled(val) {
if (val) {
if (this.newUser.itemTagsSelected?.length) {
@@ -231,15 +265,15 @@ export default {
},
submitForm() {
if (!this.newUser.username) {
this.$toast.error('Enter a username')
this.$toast.error(this.$strings.ToastNewUserUsernameError)
return
}
if (!this.newUser.permissions.accessAllLibraries && !this.newUser.librariesAccessible.length) {
this.$toast.error('Must select at least one library')
this.$toast.error(this.$strings.ToastNewUserLibraryError)
return
}
if (!this.newUser.permissions.accessAllTags && !this.newUser.itemTagsSelected.length) {
this.$toast.error('Must select at least one tag')
this.$toast.error(this.$strings.ToastNewUserTagError)
return
}
@@ -257,7 +291,6 @@ export default {
if (account.type === 'root' && !account.isActive) return
this.processing = true
console.log('Calling update', account)
this.$axios
.$patch(`/api/users/${this.account.id}`, account)
.then((data) => {
@@ -280,12 +313,12 @@ export default {
this.processing = false
console.error('Failed to update account', error)
var errMsg = error.response ? error.response.data || '' : ''
this.$toast.error(errMsg || 'Failed to update account')
this.$toast.error(errMsg || this.$strings.ToastFailedToUpdateAccount)
})
},
submitCreateAccount() {
if (!this.newUser.password) {
this.$toast.error('Must have a password, only root user can have an empty password')
this.$toast.error(this.$strings.ToastNewUserPasswordError)
return
}
@@ -296,9 +329,9 @@ export default {
.then((data) => {
this.processing = false
if (data.error) {
this.$toast.error(`Failed to create account: ${data.error}`)
this.$toast.error(this.$strings.ToastNewUserCreatedFailed + ': ' + data.error)
} else {
this.$toast.success('New account created')
this.$toast.success(this.$strings.ToastNewUserCreatedSuccess)
this.show = false
}
})
@@ -318,6 +351,7 @@ export default {
update: type === 'admin',
delete: type === 'admin',
upload: type === 'admin',
accessExplicitContent: true,
accessAllLibraries: true,
accessAllTags: true,
selectedTagsNotAccessible: false
@@ -326,9 +360,11 @@ export default {
init() {
this.fetchAllTags()
this.isNew = !this.account
if (this.account) {
this.newUser = {
username: this.account.username,
email: this.account.email,
password: this.account.password,
type: this.account.type,
isActive: this.account.isActive,
@@ -337,9 +373,9 @@ export default {
itemTagsSelected: [...(this.account.itemTagsSelected || [])]
}
} else {
this.fetchAllTags()
this.newUser = {
username: null,
email: null,
password: null,
type: 'user',
isActive: true,
@@ -350,6 +386,7 @@ export default {
upload: false,
accessAllLibraries: true,
accessAllTags: true,
accessExplicitContent: true,
selectedTagsNotAccessible: false
},
librariesAccessible: [],

View File

@@ -0,0 +1,105 @@
<template>
<modals-modal ref="modal" v-model="show" name="custom-metadata-provider" :width="600" :height="'unset'" :processing="processing">
<template #outer>
<div class="absolute top-0 left-0 p-5 w-2/3 overflow-hidden">
<p class="text-3xl text-white truncate">{{ $strings.HeaderAddCustomMetadataProvider }}</p>
</div>
</template>
<form @submit.prevent="submitForm">
<div class="px-4 w-full flex items-center text-sm py-6 rounded-lg bg-bg shadow-lg border border-black-300 overflow-y-auto overflow-x-hidden" style="min-height: 400px; max-height: 80vh">
<div class="w-full p-8">
<div class="flex mb-2">
<div class="w-3/4 p-1">
<ui-text-input-with-label v-model="newName" :label="$strings.LabelName" />
</div>
<div class="w-1/4 p-1">
<ui-text-input-with-label value="Book" readonly :label="$strings.LabelMediaType" />
</div>
</div>
<div class="w-full mb-2 p-1">
<ui-text-input-with-label v-model="newUrl" label="URL" />
</div>
<div class="w-full mb-2 p-1">
<ui-text-input-with-label v-model="newAuthHeaderValue" :label="$strings.LabelProviderAuthorizationValue" type="password" />
</div>
<div class="flex px-1 pt-4">
<div class="flex-grow" />
<ui-btn color="success" type="submit">{{ $strings.ButtonAdd }}</ui-btn>
</div>
</div>
</div>
</form>
</modals-modal>
</template>
<script>
export default {
props: {
value: Boolean
},
data() {
return {
processing: false,
newName: '',
newUrl: '',
newAuthHeaderValue: ''
}
},
watch: {
show: {
handler(newVal) {
if (newVal) {
this.init()
}
}
}
},
computed: {
show: {
get() {
return this.value
},
set(val) {
this.$emit('input', val)
}
}
},
methods: {
submitForm() {
if (!this.newName || !this.newUrl) {
this.$toast.error(this.$strings.ToastProviderNameAndUrlRequired)
return
}
this.processing = true
this.$axios
.$post('/api/custom-metadata-providers', {
name: this.newName,
url: this.newUrl,
mediaType: 'book', // Currently only supporting book mediaType
authHeaderValue: this.newAuthHeaderValue
})
.then((data) => {
this.$emit('added', data.provider)
this.$toast.success(this.$strings.ToastProviderCreatedSuccess)
this.show = false
})
.catch((error) => {
const errorMsg = error.response?.data || 'Unknown error'
console.error('Failed to add provider', error)
this.$toast.error(this.$strings.ToastProviderCreatedFailed + ': ' + errorMsg)
})
.finally(() => {
this.processing = false
})
},
init() {
this.processing = false
this.newName = ''
this.newUrl = ''
this.newAuthHeaderValue = ''
}
},
mounted() {}
}
</script>

View File

@@ -4,7 +4,7 @@
<div class="flex items-center justify-between">
<p class="text-base text-gray-200 truncate">{{ metadata.filename }}</p>
<ui-btn v-if="ffprobeData" small class="ml-2" @click="ffprobeData = null">{{ $strings.ButtonReset }}</ui-btn>
<ui-btn v-else-if="userIsAdminOrUp" small :loading="probingFile" class="ml-2" @click="getFFProbeData">Probe Audio File</ui-btn>
<ui-btn v-else-if="userIsAdminOrUp" small :loading="probingFile" class="ml-2" @click="getFFProbeData">{{ $strings.ButtonProbeAudioFile }}</ui-btn>
</div>
<div class="w-full h-px bg-white bg-opacity-10 my-4" />
@@ -91,7 +91,7 @@
<ui-textarea-with-label :value="prettyFfprobeData" readonly :rows="30" class="text-xs" />
<button class="absolute top-4 right-4" :class="copiedToClipboard ? 'text-success' : 'text-white/50 hover:text-white/80'" @click.stop="copyFfprobeData">
<span class="material-icons">{{ copiedToClipboard ? 'check' : 'content_copy' }}</span>
<span class="material-symbols">{{ copiedToClipboard ? 'check' : 'content_copy' }}</span>
</button>
</div>
</div>
@@ -159,7 +159,7 @@ export default {
})
.catch((error) => {
console.error('Failed to get ffprobe data', error)
this.$toast.error('FFProbe failed')
this.$toast.error(this.$strings.ToastFailedToLoadData)
})
.finally(() => {
this.probingFile = false

View File

@@ -9,7 +9,7 @@
<widgets-cron-expression-builder ref="expressionBuilder" v-model="newCronExpression" @input="expressionUpdated" />
<div class="flex items-center justify-end">
<ui-btn :disabled="!isUpdated" @click="submit">{{ isUpdated ? $strings.ButtonSave : $strings.MessageNoUpdateNecessary }}</ui-btn>
<ui-btn :disabled="!isUpdated" @click="submit">{{ isUpdated ? $strings.ButtonSave : $strings.MessageNoUpdatesWereNecessary }}</ui-btn>
</div>
</div>
</modals-modal>

View File

@@ -19,7 +19,7 @@
<ui-tooltip :text="$strings.LabelUpdateCoverHelp">
<p class="pl-4">
{{ $strings.LabelUpdateCover }}
<span class="material-icons icon-text">info_outlined</span>
<span class="material-symbols icon-text">info</span>
</p>
</ui-tooltip>
</div>
@@ -28,7 +28,7 @@
<ui-tooltip :text="$strings.LabelUpdateDetailsHelp">
<p class="pl-4">
{{ $strings.LabelUpdateDetails }}
<span class="material-icons icon-text">info_outlined</span>
<span class="material-symbols icon-text">info</span>
</p>
</ui-tooltip>
</div>

View File

@@ -24,7 +24,7 @@
<div class="flex-grow px-2">
<ui-text-input v-model="newBookmarkTitle" placeholder="Note" class="w-full" />
</div>
<ui-btn type="submit" color="success" :padding-x="4" class="h-10"><span class="material-icons text-2xl -mt-px">add</span></ui-btn>
<ui-btn type="submit" color="success" :padding-x="4" class="h-10"><span class="material-symbols text-2xl -mt-px">add</span></ui-btn>
</div>
</form>
</div>
@@ -94,7 +94,7 @@ export default {
this.$toast.success(this.$strings.ToastBookmarkRemoveSuccess)
})
.catch((error) => {
this.$toast.error(this.$strings.ToastBookmarkRemoveFailed)
this.$toast.error(this.$strings.ToastRemoveFailed)
console.error(error)
})
this.show = false

View File

@@ -1,8 +1,8 @@
<template>
<modals-modal v-model="show" name="chapters" :width="600" :height="'unset'">
<div id="chapter-modal-wrapper" ref="container" class="w-full rounded-lg bg-primary box-shadow-md overflow-y-auto overflow-x-hidden" style="max-height: 80vh">
<div id="chapter-modal-wrapper" ref="container" class="w-full rounded-lg bg-bg box-shadow-md overflow-y-auto overflow-x-hidden" style="max-height: 80vh">
<template v-for="chap in chapters">
<div :key="chap.id" :id="`chapter-row-${chap.id}`" class="flex items-center px-6 py-3 justify-start cursor-pointer hover:bg-bg relative" :class="chap.id === currentChapterId ? 'bg-yellow-400 bg-opacity-10' : chap.end / _playbackRate <= currentChapterStart ? 'bg-success bg-opacity-5' : 'bg-opacity-20'" @click="clickChapter(chap)">
<div :key="chap.id" :id="`chapter-row-${chap.id}`" class="flex items-center px-6 py-3 justify-start cursor-pointer relative" :class="chap.id === currentChapterId ? 'bg-yellow-400/20 hover:bg-yellow-400/10' : chap.end / _playbackRate <= currentChapterStart ? 'bg-success/10 hover:bg-success/5' : 'hover:bg-primary/10'" @click="clickChapter(chap)">
<p class="chapter-title truncate text-sm md:text-base">
{{ chap.title }}
</p>
@@ -34,11 +34,6 @@ export default {
data() {
return {}
},
watch: {
value(newVal) {
this.$nextTick(this.scrollToChapter)
}
},
computed: {
show: {
get() {
@@ -53,7 +48,7 @@ export default {
return this.playbackRate
},
currentChapterId() {
return this.currentChapter ? this.currentChapter.id : null
return this.currentChapter?.id || null
},
currentChapterStart() {
return (this.currentChapter?.start || 0) / this._playbackRate
@@ -74,6 +69,11 @@ export default {
}
}
}
},
updated() {
if (this.value) {
this.$nextTick(this.scrollToChapter)
}
}
}
</script>
@@ -87,4 +87,4 @@ export default {
max-width: calc(100% - 150px);
}
}
</style>
</style>

View File

@@ -1,14 +1,14 @@
<template>
<div ref="wrapper" class="hidden absolute top-0 left-0 w-full h-full bg-black bg-opacity-50 rounded-lg items-center justify-center" style="z-index: 61" @click="clickClose">
<div class="absolute top-3 right-3 md:top-5 md:right-5 h-8 w-8 md:h-12 md:w-12 flex items-center justify-center cursor-pointer text-white hover:text-gray-300">
<span class="material-icons text-2xl md:text-4xl">close</span>
<span class="material-symbols text-2xl md:text-4xl">close</span>
</div>
<div ref="content" class="text-white">
<form v-if="selectedSeries" @submit.prevent="submitSeriesForm">
<div class="bg-bg rounded-lg px-2 py-6 sm:p-6 md:p-8" @click.stop>
<div class="flex">
<div class="flex-grow p-1 min-w-48 sm:min-w-64 md:min-w-80">
<ui-input-dropdown ref="newSeriesSelect" v-model="selectedSeries.name" :items="existingSeriesNames" :disabled="!isNewSeries" :label="$strings.LabelSeriesName" />
<ui-input-dropdown ref="newSeriesSelect" v-model="selectedSeries.name" :items="existingSeriesNames" :disabled="!isNewSeries" :label="$strings.LabelSeriesName" @input="seriesNameInputHandler" />
</div>
<div class="w-24 sm:w-28 md:w-40 p-1">
<ui-text-input-with-label ref="sequenceInput" v-model="selectedSeries.sequence" :label="$strings.LabelSequence" />
@@ -66,6 +66,11 @@ export default {
}
},
methods: {
seriesNameInputHandler() {
if (this.$refs.sequenceInput) {
this.$refs.sequenceInput.setFocus()
}
},
setInputFocus() {
if (this.isNewSeries) {
// Focus on series input if new series
@@ -134,4 +139,4 @@ export default {
},
mounted() {}
}
</script>
</script>

View File

@@ -8,7 +8,7 @@
<div ref="container" class="w-full rounded-lg bg-bg box-shadow-md overflow-y-auto overflow-x-hidden p-6" style="max-height: 80vh">
<div class="flex items-center">
<p class="text-base text-gray-200">{{ _session.displayTitle }}</p>
<p v-if="_session.displayAuthor" class="text-xs text-gray-400 px-4">by {{ _session.displayAuthor }}</p>
<p v-if="_session.displayAuthor" class="text-xs text-gray-400 px-4">{{ $getString('LabelByAuthor', [_session.displayAuthor]) }}</p>
</div>
<div class="w-full h-px bg-white bg-opacity-10 my-4" />
@@ -80,26 +80,27 @@
</div>
</div>
<div class="w-full md:w-1/3">
<p class="font-semibold uppercase text-xs text-gray-400 tracking-wide mb-2 mt-6 md:mt-0">{{ $strings.LabelUser }}</p>
<p class="mb-1 text-xs">{{ _session.userId }}</p>
<p v-if="!isMediaItemShareSession" class="font-semibold uppercase text-xs text-gray-400 tracking-wide mb-2 mt-6 md:mt-0">{{ $strings.LabelUser }}</p>
<p v-if="!isMediaItemShareSession" class="mb-1 text-xs">{{ _session.userId }}</p>
<p class="font-semibold uppercase text-xs text-gray-400 tracking-wide mt-6 mb-2">{{ $strings.LabelMediaPlayer }}</p>
<p class="mb-1">{{ playMethodName }}</p>
<p class="mb-1">{{ _session.mediaPlayer }}</p>
<p v-if="hasDeviceInfo" class="font-semibold uppercase text-xs text-gray-400 tracking-wide mt-6 mb-2">{{ $strings.LabelDevice }}</p>
<p v-if="clientDisplayName" class="mb-1">{{ clientDisplayName }}</p>
<p v-if="deviceInfo.ipAddress" class="mb-1">{{ deviceInfo.ipAddress }}</p>
<p v-if="osDisplayName" class="mb-1">{{ osDisplayName }}</p>
<p v-if="deviceInfo.browserName" class="mb-1">{{ deviceInfo.browserName }}</p>
<p v-if="clientDisplayName" class="mb-1">{{ clientDisplayName }}</p>
<p v-if="deviceDisplayName" class="mb-1">{{ deviceDisplayName }}</p>
<p v-if="deviceInfo.sdkVersion" class="mb-1">SDK {{ $strings.LabelVersion }}: {{ deviceInfo.sdkVersion }}</p>
<p v-if="deviceInfo.deviceType" class="mb-1">{{ $strings.LabelType }}: {{ deviceInfo.deviceType }}</p>
</div>
</div>
<div class="flex items-center">
<ui-btn v-if="!isOpenSession" small color="error" @click.stop="deleteSessionClick">{{ $strings.ButtonDelete }}</ui-btn>
<ui-btn v-else small color="error" @click.stop="closeSessionClick">Close Open Session</ui-btn>
<ui-btn v-if="!isOpenSession && !isMediaItemShareSession" small color="error" @click.stop="deleteSessionClick">{{ $strings.ButtonDelete }}</ui-btn>
<ui-btn v-else-if="!isMediaItemShareSession" small color="error" @click.stop="closeSessionClick">{{ $strings.ButtonCloseSession }}</ui-btn>
</div>
</div>
</modals-modal>
@@ -141,10 +142,14 @@ export default {
if (!this.deviceInfo.osName) return null
return `${this.deviceInfo.osName} ${this.deviceInfo.osVersion}`
},
clientDisplayName() {
deviceDisplayName() {
if (!this.deviceInfo.manufacturer || !this.deviceInfo.model) return null
return `${this.deviceInfo.manufacturer} ${this.deviceInfo.model}`
},
clientDisplayName() {
if (!this.deviceInfo.clientName) return null
return `${this.deviceInfo.clientName} ${this.deviceInfo.clientVersion || ''}`
},
playMethodName() {
const playMethod = this._session.playMethod
if (playMethod === this.$constants.PlayMethod.DIRECTPLAY) return 'Direct Play'
@@ -161,6 +166,9 @@ export default {
},
isOpenSession() {
return !!this._session.open
},
isMediaItemShareSession() {
return this._session.mediaPlayer === 'web-share'
}
},
methods: {
@@ -198,14 +206,13 @@ export default {
this.$axios
.$post(`/api/session/${this._session.id}/close`)
.then(() => {
this.$toast.success('Session closed')
this.show = false
this.$emit('closedSession')
})
.catch((error) => {
console.error('Failed to close session', error)
const errMsg = error.response?.data || ''
this.$toast.error(errMsg || 'Failed to close open session')
this.$toast.error(errMsg || this.$strings.ToastSessionCloseFailed)
})
.finally(() => {
this.processing = false

View File

@@ -3,7 +3,7 @@
<div class="absolute top-0 left-0 right-0 w-full h-36 bg-gradient-to-t from-transparent via-black-500 to-black-700 opacity-90 pointer-events-none" />
<button class="absolute top-4 right-4 landscape:top-4 landscape:right-4 md:portrait:top-5 md:portrait:right-5 lg:top-5 lg:right-5 inline-flex text-gray-200 hover:text-white" aria-label="Close modal" @click="clickClose">
<span class="material-icons text-2xl landscape:text-2xl md:portrait:text-4xl lg:text-4xl">close</span>
<span class="material-symbols text-2xl landscape:text-2xl md:portrait:text-4xl lg:text-4xl">close</span>
</button>
<slot name="outer" />
<div ref="content" style="min-width: 380px; min-height: 200px; max-width: 100vw" class="relative text-white" aria-modal="true" :style="{ height: modalHeight, width: modalWidth, marginTop: contentMarginTop + 'px' }" @mousedown="mousedownModal" @mouseup="mouseupModal" v-click-outside="clickBg">

View File

@@ -0,0 +1,70 @@
<template>
<modals-modal v-model="show" name="player-settings" :width="500" :height="'unset'">
<div ref="container" class="w-full rounded-lg bg-bg box-shadow-md overflow-y-auto overflow-x-hidden p-4" style="max-height: 80vh; min-height: 40vh">
<h3 class="text-xl font-semibold mb-8">{{ $strings.HeaderPlayerSettings }}</h3>
<div class="flex items-center mb-4">
<ui-toggle-switch v-model="useChapterTrack" @input="setUseChapterTrack" />
<div class="pl-4">
<span>{{ $strings.LabelUseChapterTrack }}</span>
</div>
</div>
<div class="flex items-center mb-4">
<ui-select-input v-model="jumpForwardAmount" :label="$strings.LabelJumpForwardAmount" menuMaxHeight="250px" :items="jumpValues" @input="setJumpForwardAmount" />
</div>
<div class="flex items-center">
<ui-select-input v-model="jumpBackwardAmount" :label="$strings.LabelJumpBackwardAmount" menuMaxHeight="250px" :items="jumpValues" @input="setJumpBackwardAmount" />
</div>
</div>
</modals-modal>
</template>
<script>
export default {
props: {
value: Boolean
},
data() {
return {
useChapterTrack: false,
jumpValues: [
{ text: this.$getString('LabelTimeDurationXSeconds', ['10']), value: 10 },
{ text: this.$getString('LabelTimeDurationXSeconds', ['15']), value: 15 },
{ text: this.$getString('LabelTimeDurationXSeconds', ['30']), value: 30 },
{ text: this.$getString('LabelTimeDurationXSeconds', ['60']), value: 60 },
{ text: this.$getString('LabelTimeDurationXMinutes', ['2']), value: 120 },
{ text: this.$getString('LabelTimeDurationXMinutes', ['5']), value: 300 }
],
jumpForwardAmount: 10,
jumpBackwardAmount: 10
}
},
computed: {
show: {
get() {
return this.value
},
set(val) {
this.$emit('input', val)
}
}
},
methods: {
setUseChapterTrack() {
this.$store.dispatch('user/updateUserSettings', { useChapterTrack: this.useChapterTrack })
},
setJumpForwardAmount(val) {
this.jumpForwardAmount = val
this.$store.dispatch('user/updateUserSettings', { jumpForwardAmount: val })
},
setJumpBackwardAmount(val) {
this.jumpBackwardAmount = val
this.$store.dispatch('user/updateUserSettings', { jumpBackwardAmount: val })
}
},
mounted() {
this.useChapterTrack = this.$store.getters['user/getUserSetting']('useChapterTrack')
this.jumpForwardAmount = this.$store.getters['user/getUserSetting']('jumpForwardAmount')
this.jumpBackwardAmount = this.$store.getters['user/getUserSetting']('jumpBackwardAmount')
}
}
</script>

View File

@@ -0,0 +1,30 @@
<template>
<modals-modal v-model="show" name="cover" :width="'90%'" :height="'90%'" :contentMarginTop="0">
<div class="w-full h-full" @click="show = false">
<img loading="lazy" :src="rawCoverUrl" class="w-full h-full z-10 object-scale-down" />
</div>
</modals-modal>
</template>
<script>
export default {
data() {
return {}
},
computed: {
show: {
get() {
return this.$store.state.globals.showRawCoverPreviewModal
},
set(val) {
this.$store.commit('globals/setShowRawCoverPreviewModal', val)
}
},
rawCoverUrl() {
return this.$store.state.globals.selectedRawCoverUrl
}
},
methods: {},
mounted() {}
}
</script>

View File

@@ -0,0 +1,204 @@
<template>
<modals-modal ref="modal" v-model="show" name="share" :width="600" :height="'unset'" :processing="processing">
<template #outer>
<div class="absolute top-0 left-0 p-5 w-2/3 overflow-hidden">
<p class="text-3xl text-white truncate">{{ $strings.LabelShare }}</p>
</div>
</template>
<div class="px-6 py-8 w-full text-sm rounded-lg bg-bg shadow-lg border border-black-300 overflow-y-auto overflow-x-hidden" style="max-height: 80vh">
<div class="absolute top-0 right-0 p-4">
<ui-tooltip :text="$strings.LabelClickForMoreInfo" class="inline-flex ml-2">
<a href="https://www.audiobookshelf.org/guides/media-item-shares" target="_blank" class="inline-flex">
<span class="material-symbols text-xl w-5 text-gray-200">help_outline</span>
</a>
</ui-tooltip>
</div>
<template v-if="currentShare">
<div class="w-full py-2">
<label class="px-1 text-sm font-semibold block">{{ $strings.LabelShareURL }}</label>
<ui-text-input v-model="currentShareUrl" show-copy readonly class="text-base h-10" />
</div>
<div class="w-full py-2 px-1">
<p v-if="currentShare.expiresAt" class="text-base">{{ $getString('MessageShareExpiresIn', [currentShareTimeRemaining]) }}</p>
<p v-else>{{ $strings.LabelPermanent }}</p>
</div>
</template>
<template v-else>
<div class="flex flex-col sm:flex-row items-center justify-between space-y-4 sm:space-y-0 sm:space-x-4 mb-4">
<div class="w-full sm:w-48">
<label class="px-1 text-sm font-semibold block">{{ $strings.LabelSlug }}</label>
<ui-text-input v-model="newShareSlug" class="text-base h-10" />
</div>
<div class="flex-grow" />
<div class="w-full sm:w-80">
<label class="px-1 text-sm font-semibold block">{{ $strings.LabelDuration }}</label>
<div class="inline-flex items-center space-x-2">
<div>
<ui-icon-btn icon="remove" :size="10" @click="clickMinus" />
</div>
<ui-text-input v-model="newShareDuration" type="number" text-center no-spinner class="text-center max-w-12 min-w-12 h-10 text-base" />
<div>
<ui-icon-btn icon="add" :size="10" @click="clickPlus" />
</div>
<div class="w-28">
<ui-dropdown v-model="shareDurationUnit" :items="durationUnits" />
</div>
</div>
</div>
</div>
<p class="text-sm text-gray-300 py-1 px-1" v-html="$getString('MessageShareURLWillBe', [demoShareUrl])" />
<p class="text-sm text-gray-300 py-1 px-1" v-html="$getString('MessageShareExpirationWillBe', [expirationDateString])" />
</template>
<div class="flex items-center pt-6">
<div class="flex-grow" />
<ui-btn v-if="currentShare" color="error" small @click="deleteShare">{{ $strings.ButtonDelete }}</ui-btn>
<ui-btn v-if="!currentShare" color="success" small @click="openShare">{{ $strings.ButtonShare }}</ui-btn>
</div>
</div>
</modals-modal>
</template>
<script>
export default {
props: {},
data() {
return {
processing: false,
newShareSlug: '',
newShareDuration: 0,
currentShare: null,
shareDurationUnit: 'minutes',
durationUnits: [
{
text: this.$strings.LabelMinutes,
value: 'minutes'
},
{
text: this.$strings.LabelHours,
value: 'hours'
},
{
text: this.$strings.LabelDays,
value: 'days'
}
]
}
},
watch: {
show: {
handler(newVal) {
if (newVal) {
this.init()
}
}
}
},
computed: {
show: {
get() {
return this.$store.state.globals.showShareModal
},
set(val) {
this.$store.commit('globals/setShowShareModal', val)
}
},
mediaItemShare() {
return this.$store.state.globals.selectedMediaItemShare
},
libraryItem() {
return this.$store.state.selectedLibraryItem
},
user() {
return this.$store.state.user.user
},
demoShareUrl() {
return `${window.origin}/share/${this.newShareSlug}`
},
currentShareUrl() {
if (!this.currentShare) return ''
return `${window.origin}/share/${this.currentShare.slug}`
},
currentShareTimeRemaining() {
if (!this.currentShare) return 'Error'
if (!this.currentShare.expiresAt) return this.$strings.LabelPermanent
const msRemaining = new Date(this.currentShare.expiresAt).valueOf() - Date.now()
if (msRemaining <= 0) return 'Expired'
return this.$elapsedPrettyExtended(msRemaining / 1000, true, false)
},
expireDurationSeconds() {
let shareDuration = Number(this.newShareDuration)
if (!shareDuration || isNaN(shareDuration)) return 0
return this.newShareDuration * (this.shareDurationUnit === 'minutes' ? 60 : this.shareDurationUnit === 'hours' ? 3600 : 86400)
},
expirationDateString() {
if (!this.expireDurationSeconds) return this.$strings.LabelPermanent
const dateMs = Date.now() + this.expireDurationSeconds * 1000
return this.$formatDatetime(dateMs, this.$store.state.serverSettings.dateFormat, this.$store.state.serverSettings.timeFormat)
}
},
methods: {
clickPlus() {
this.newShareDuration++
},
clickMinus() {
if (this.newShareDuration > 0) {
this.newShareDuration--
}
},
deleteShare() {
if (!this.currentShare) return
this.processing = true
this.$axios
.$delete(`/api/share/mediaitem/${this.currentShare.id}`)
.then(() => {
this.currentShare = null
this.$emit('removed')
})
.catch((error) => {
console.error('deleteShare', error)
let errorMsg = error.response?.data || 'Failed to delete share'
this.$toast.error(errorMsg)
})
.finally(() => {
this.processing = false
})
},
openShare() {
if (!this.newShareSlug) {
this.$toast.error(this.$strings.ToastSlugRequired)
return
}
const payload = {
slug: this.newShareSlug,
mediaItemType: 'book',
mediaItemId: this.libraryItem.media.id,
expiresAt: this.expireDurationSeconds ? Date.now() + this.expireDurationSeconds * 1000 : 0
}
this.processing = true
this.$axios
.$post(`/api/share/mediaitem`, payload)
.then((data) => {
this.currentShare = data
this.$emit('opened', data)
})
.catch((error) => {
console.error('openShare', error)
let errorMsg = error.response?.data || 'Failed to share item'
this.$toast.error(errorMsg)
})
.finally(() => {
this.processing = false
})
},
init() {
this.newShareSlug = this.$randomId(10)
if (this.mediaItemShare) {
this.currentShare = { ...this.mediaItemShare }
} else {
this.currentShare = null
}
}
},
mounted() {}
}
</script>

View File

@@ -6,34 +6,36 @@
</div>
</template>
<div ref="container" class="w-full rounded-lg bg-primary box-shadow-md overflow-y-auto overflow-x-hidden" style="max-height: 80vh">
<div v-if="!timerSet" class="w-full">
<div ref="container" class="w-full rounded-lg bg-bg box-shadow-md overflow-y-auto overflow-x-hidden" style="max-height: 80vh">
<div class="w-full">
<template v-for="time in sleepTimes">
<div :key="time.text" class="flex items-center px-6 py-3 justify-center cursor-pointer hover:bg-bg relative" @click="setTime(time.seconds)">
<p class="text-xl text-center">{{ time.text }}</p>
<div :key="time.text" class="flex items-center px-6 py-3 justify-center cursor-pointer hover:bg-primary/25 relative" @click="setTime(time)">
<p class="text-lg text-center">{{ time.text }}</p>
</div>
</template>
<form class="flex items-center justify-center px-6 py-3" @submit.prevent="submitCustomTime">
<ui-text-input v-model="customTime" type="number" step="any" min="0.1" placeholder="Time in minutes" class="w-48" />
<ui-btn color="success" type="submit" :padding-x="0" class="h-9 w-12 flex items-center justify-center ml-1">Set</ui-btn>
<ui-text-input v-model="customTime" type="number" step="any" min="0.1" :placeholder="$strings.LabelTimeInMinutes" class="w-48" />
<ui-btn color="success" type="submit" :padding-x="0" class="h-9 w-18 flex items-center justify-center ml-1">{{ $strings.ButtonSubmit }}</ui-btn>
</form>
</div>
<div v-else class="w-full p-4">
<div class="mb-4 flex items-center justify-center">
<ui-btn :padding-x="2" small :disabled="remaining < 30 * 60" class="flex items-center mr-4" @click="decrement(30 * 60)">
<span class="material-icons text-lg">remove</span>
<span class="pl-1 text-base font-mono">30m</span>
<div v-if="timerSet" class="w-full p-4">
<div class="mb-4 h-px w-full bg-white/10" />
<div v-if="timerType === $constants.SleepTimerTypes.COUNTDOWN" class="mb-4 flex items-center justify-center space-x-4">
<ui-btn :padding-x="2" small :disabled="remaining < 30 * 60" class="flex items-center h-9" @click="decrement(30 * 60)">
<span class="material-symbols text-lg">remove</span>
<span class="pl-1 text-sm">30m</span>
</ui-btn>
<ui-icon-btn icon="remove" @click="decrement(60 * 5)" />
<ui-icon-btn icon="remove" class="min-w-9" @click="decrement(60 * 5)" />
<p class="mx-6 text-2xl font-mono">{{ $secondsToTimestamp(remaining) }}</p>
<p class="text-2xl font-mono">{{ $secondsToTimestamp(remaining) }}</p>
<ui-icon-btn icon="add" @click="increment(60 * 5)" />
<ui-icon-btn icon="add" class="min-w-9" @click="increment(60 * 5)" />
<ui-btn :padding-x="2" small class="flex items-center ml-4" @click="increment(30 * 60)">
<span class="material-icons text-lg">add</span>
<span class="pl-1 text-base font-mono">30m</span>
<ui-btn :padding-x="2" small class="flex items-center h-9" @click="increment(30 * 60)">
<span class="material-symbols text-lg">add</span>
<span class="pl-1 text-sm">30m</span>
</ui-btn>
</div>
<ui-btn class="w-full" @click="$emit('cancel')">{{ $strings.ButtonCancel }}</ui-btn>
@@ -47,52 +49,13 @@ export default {
props: {
value: Boolean,
timerSet: Boolean,
timerTime: Number,
remaining: Number
timerType: String,
remaining: Number,
hasChapters: Boolean
},
data() {
return {
customTime: null,
sleepTimes: [
{
seconds: 60 * 5,
text: '5 minutes'
},
{
seconds: 60 * 15,
text: '15 minutes'
},
{
seconds: 60 * 20,
text: '20 minutes'
},
{
seconds: 60 * 30,
text: '30 minutes'
},
{
seconds: 60 * 45,
text: '45 minutes'
},
{
seconds: 60 * 60,
text: '60 minutes'
},
{
seconds: 60 * 90,
text: '90 minutes'
},
{
seconds: 60 * 120,
text: '2 hours'
}
]
}
},
watch: {
show(newVal) {
if (newVal) {
}
customTime: null
}
},
computed: {
@@ -103,6 +66,54 @@ export default {
set(val) {
this.$emit('input', val)
}
},
sleepTimes() {
const times = [
{
seconds: 60 * 5,
text: this.$getString('LabelTimeDurationXMinutes', ['5']),
timerType: this.$constants.SleepTimerTypes.COUNTDOWN
},
{
seconds: 60 * 15,
text: this.$getString('LabelTimeDurationXMinutes', ['15']),
timerType: this.$constants.SleepTimerTypes.COUNTDOWN
},
{
seconds: 60 * 20,
text: this.$getString('LabelTimeDurationXMinutes', ['20']),
timerType: this.$constants.SleepTimerTypes.COUNTDOWN
},
{
seconds: 60 * 30,
text: this.$getString('LabelTimeDurationXMinutes', ['30']),
timerType: this.$constants.SleepTimerTypes.COUNTDOWN
},
{
seconds: 60 * 45,
text: this.$getString('LabelTimeDurationXMinutes', ['45']),
timerType: this.$constants.SleepTimerTypes.COUNTDOWN
},
{
seconds: 60 * 60,
text: this.$getString('LabelTimeDurationXMinutes', ['60']),
timerType: this.$constants.SleepTimerTypes.COUNTDOWN
},
{
seconds: 60 * 90,
text: this.$getString('LabelTimeDurationXMinutes', ['90']),
timerType: this.$constants.SleepTimerTypes.COUNTDOWN
},
{
seconds: 60 * 120,
text: this.$getString('LabelTimeDurationXHours', ['2']),
timerType: this.$constants.SleepTimerTypes.COUNTDOWN
}
]
if (this.hasChapters) {
times.push({ seconds: -1, text: this.$strings.LabelEndOfChapter, timerType: this.$constants.SleepTimerTypes.CHAPTER })
}
return times
}
},
methods: {
@@ -113,10 +124,14 @@ export default {
}
const timeInSeconds = Math.round(Number(this.customTime) * 60)
this.setTime(timeInSeconds)
const time = {
seconds: timeInSeconds,
timerType: this.$constants.SleepTimerTypes.COUNTDOWN
}
this.setTime(time)
},
setTime(seconds) {
this.$emit('set', seconds)
setTime(time) {
this.$emit('set', time)
},
increment(amount) {
this.$emit('increment', amount)
@@ -130,4 +145,4 @@ export default {
}
}
}
</script>
</script>

View File

@@ -12,7 +12,7 @@
</div>
<div v-if="previewUpload" class="absolute top-0 left-0 w-full h-full z-10 bg-bg p-8">
<p class="text-lg">Preview Cover</p>
<span class="absolute top-4 right-4 material-icons text-2xl cursor-pointer" @click="resetCoverPreview">close</span>
<span class="absolute top-4 right-4 material-symbols text-2xl cursor-pointer" @click="resetCoverPreview">close</span>
<div class="flex justify-center py-4">
<covers-preview-cover :src="previewUpload" :width="240" />
</div>
@@ -78,14 +78,13 @@ export default {
if (data.error) {
this.$toast.error(data.error)
} else {
this.$toast.success('Cover Uploaded')
this.resetCoverPreview()
}
this.processingUpload = false
})
.catch((error) => {
console.error('Failed', error)
var errorMsg = error.response && error.response.data ? error.response.data : 'Unknown Error'
var errorMsg = error.response && error.response.data ? error.response.data : this.$strings.ToastUnknownError
this.$toast.error(errorMsg)
this.processingUpload = false
})
@@ -95,7 +94,7 @@ export default {
var success = await this.$axios.$post(`/api/${this.entity}/${this.entityId}/cover`, { url: this.imageUrl }).catch((error) => {
console.error('Failed to download cover from url', error)
var errorMsg = error.response && error.response.data ? error.response.data : 'Unknown Error'
var errorMsg = error.response && error.response.data ? error.response.data : this.$strings.ToastUnknownError
this.$toast.error(errorMsg)
return false
})
@@ -104,4 +103,4 @@ export default {
}
}
}
</script>
</script>

View File

@@ -5,18 +5,23 @@
<p class="text-3xl text-white truncate">{{ title }}</p>
</div>
</template>
<div class="p-4 w-full text-sm py-6 rounded-lg bg-bg shadow-lg border border-black-300 relative overflow-hidden" style="min-height: 400px; max-height: 80vh">
<form v-if="author" @submit.prevent="submitForm">
<div class="flex">
<div class="w-40 p-2">
<div class="w-full h-45 relative">
<covers-author-image :author="author" />
<div v-show="!processing && author.imagePath" class="absolute top-0 left-0 w-full h-full opacity-0 hover:opacity-100">
<span class="absolute top-2 right-2 material-icons text-error transform hover:scale-125 transition-transform cursor-pointer text-lg" @click="removeCover">delete</span>
</div>
<div v-if="author" class="p-4 w-full text-sm py-6 rounded-lg bg-bg shadow-lg border border-black-300 relative overflow-hidden" style="min-height: 400px; max-height: 80vh">
<div class="flex">
<div class="w-40 p-2">
<div class="w-full h-45 relative">
<covers-author-image :author="authorCopy" />
<div v-if="userCanDelete && !processing && author.imagePath" class="absolute top-0 left-0 w-full h-full opacity-0 hover:opacity-100">
<span class="absolute top-2 right-2 material-symbols text-error transform hover:scale-125 transition-transform cursor-pointer text-lg" @click="removeCover">delete</span>
</div>
</div>
<div class="flex-grow">
</div>
<div class="flex-grow">
<form @submit.prevent="submitUploadCover" class="flex flex-grow mb-2 p-2">
<ui-text-input v-model="imageUrl" :placeholder="$strings.LabelImageURLFromTheWeb" class="h-9 w-full" />
<ui-btn color="success" type="submit" :padding-x="4" :disabled="!imageUrl" class="ml-2 sm:ml-3 w-24 h-9">{{ $strings.ButtonSubmit }}</ui-btn>
</form>
<form v-if="author" @submit.prevent="submitForm">
<div class="flex">
<div class="w-3/4 p-2">
<ui-text-input-with-label v-model="authorCopy.name" :disabled="processing" :label="$strings.LabelName" />
@@ -25,21 +30,20 @@
<ui-text-input-with-label v-model="authorCopy.asin" :disabled="processing" label="ASIN" />
</div>
</div>
<div class="p-2">
<ui-text-input-with-label v-model="authorCopy.imagePath" :disabled="processing" :label="$strings.LabelPhotoPathURL" />
</div>
<div class="p-2">
<ui-textarea-with-label v-model="authorCopy.description" :disabled="processing" :label="$strings.LabelDescription" :rows="8" />
</div>
<div class="flex pt-2 px-2">
<ui-btn type="button" @click="searchAuthor">{{ $strings.ButtonQuickMatch }}</ui-btn>
<ui-btn v-if="userCanDelete" small color="error" type="button" @click.stop="removeClick">{{ $strings.ButtonRemove }}</ui-btn>
<div class="flex-grow" />
<ui-btn type="button" class="mx-2" @click="searchAuthor">{{ $strings.ButtonQuickMatch }}</ui-btn>
<ui-btn type="submit">{{ $strings.ButtonSave }}</ui-btn>
</div>
</div>
</form>
</div>
</form>
</div>
</div>
</modals-modal>
</template>
@@ -51,9 +55,9 @@ export default {
authorCopy: {
name: '',
asin: '',
description: '',
imagePath: ''
description: ''
},
imageUrl: '',
processing: false
}
},
@@ -91,17 +95,45 @@ export default {
},
libraryProvider() {
return this.$store.getters['libraries/getLibraryProvider'](this.currentLibraryId) || 'google'
},
userCanDelete() {
return this.$store.getters['user/getUserCanDelete']
}
},
methods: {
init() {
this.authorCopy.name = this.author.name
this.authorCopy.asin = this.author.asin
this.authorCopy.description = this.author.description
this.authorCopy.imagePath = this.author.imagePath
this.imageUrl = ''
this.authorCopy = {
...this.author
}
},
removeClick() {
const payload = {
message: this.$getString('MessageConfirmRemoveAuthor', [this.author.name]),
callback: (confirmed) => {
if (confirmed) {
this.processing = true
this.$axios
.$delete(`/api/authors/${this.authorId}`)
.then(() => {
this.$toast.success(this.$strings.ToastAuthorRemoveSuccess)
this.show = false
})
.catch((error) => {
console.error('Failed to remove author', error)
this.$toast.error(this.$strings.ToastRemoveFailed)
})
.finally(() => {
this.processing = false
})
}
},
type: 'yesNo'
}
this.$store.commit('globals/setConfirmPrompt', payload)
},
async submitForm() {
var keysToCheck = ['name', 'asin', 'description', 'imagePath']
var keysToCheck = ['name', 'asin', 'description']
var updatePayload = {}
keysToCheck.forEach((key) => {
if (this.authorCopy[key] !== this.author[key]) {
@@ -109,7 +141,7 @@ export default {
}
})
if (!Object.keys(updatePayload).length) {
this.$toast.info(this.$strings.MessageNoUpdateNecessary)
this.$toast.info(this.$strings.ToastNoUpdatesNecessary)
return
}
this.processing = true
@@ -126,29 +158,58 @@ export default {
} else if (result.merged) {
this.$toast.success(this.$strings.ToastAuthorUpdateMerged)
this.show = false
} else this.$toast.info(this.$strings.MessageNoUpdatesWereNecessary)
} else this.$toast.info(this.$strings.ToastNoUpdatesNecessary)
}
this.processing = false
},
async removeCover() {
var updatePayload = {
imagePath: null
}
removeCover() {
this.processing = true
var result = await this.$axios.$patch(`/api/authors/${this.authorId}`, updatePayload).catch((error) => {
console.error('Failed', error)
this.$toast.error(this.$strings.ToastAuthorImageRemoveFailed)
return null
})
if (result && result.updated) {
this.$toast.success(this.$strings.ToastAuthorImageRemoveSuccess)
this.$store.commit('globals/showEditAuthorModal', result.author)
this.$axios
.$delete(`/api/authors/${this.authorId}/image`)
.then((data) => {
this.$toast.success(this.$strings.ToastAuthorImageRemoveSuccess)
this.authorCopy.updatedAt = data.author.updatedAt
this.authorCopy.imagePath = data.author.imagePath
})
.catch((error) => {
console.error('Failed', error)
this.$toast.error(this.$strings.ToastRemoveFailed)
})
.finally(() => {
this.processing = false
})
},
submitUploadCover() {
if (!this.imageUrl?.startsWith('http:') && !this.imageUrl?.startsWith('https:')) {
this.$toast.error(this.$strings.ToastInvalidImageUrl)
return
}
this.processing = false
this.processing = true
const updatePayload = {
url: this.imageUrl
}
this.$axios
.$post(`/api/authors/${this.authorId}/image`, updatePayload)
.then((data) => {
this.imageUrl = ''
this.$toast.success(this.$strings.ToastAuthorUpdateSuccess)
this.authorCopy.updatedAt = data.author.updatedAt
this.authorCopy.imagePath = data.author.imagePath
})
.catch((error) => {
console.error('Failed', error)
this.$toast.error(error.response.data || this.$strings.ToastRemoveFailed)
})
.finally(() => {
this.processing = false
})
},
async searchAuthor() {
if (!this.authorCopy.name && !this.authorCopy.asin) {
this.$toast.error('Must enter an author name')
this.$toast.error(this.$strings.ToastNameRequired)
return
}
this.processing = true
@@ -167,14 +228,19 @@ export default {
return null
})
if (!response) {
this.$toast.error('Author not found')
this.$toast.error(this.$strings.ToastAuthorSearchNotFound)
} else if (response.updated) {
if (response.author.imagePath) {
this.$toast.success(this.$strings.ToastAuthorUpdateSuccess)
this.$store.commit('globals/showEditAuthorModal', response.author)
} else this.$toast.success(this.$strings.ToastAuthorUpdateSuccessNoImageFound)
} else {
this.$toast.success(this.$strings.ToastAuthorUpdateSuccessNoImageFound)
}
this.authorCopy = {
...response.author
}
} else {
this.$toast.info('No updates were made for Author')
this.$toast.info(this.$strings.ToastNoUpdatesNecessary)
}
this.processing = false
}
@@ -182,4 +248,4 @@ export default {
mounted() {},
beforeDestroy() {}
}
</script>
</script>

View File

@@ -12,9 +12,9 @@
<div class="flex-grow pr-2">
<ui-text-input v-model="newBookmarkTitle" placeholder="Note" class="w-full" />
</div>
<ui-btn type="submit" color="success" :padding-x="4" class="h-10"><span class="material-icons text-2xl -mt-px">forward</span></ui-btn>
<ui-btn type="submit" color="success" :padding-x="4" class="h-10"><span class="material-symbols text-2xl -mt-px">forward</span></ui-btn>
<div class="pl-2 flex items-center">
<span class="material-icons text-3xl text-white text-opacity-70 hover:text-opacity-95 cursor-pointer" @click.stop.prevent="cancelEditing">close</span>
<span class="material-symbols text-3xl text-white text-opacity-70 hover:text-opacity-95 cursor-pointer" @click.stop.prevent="cancelEditing">close</span>
</div>
</div>
</form>
@@ -22,8 +22,8 @@
<p v-else class="pl-2 pr-2 truncate">{{ bookmark.title }}</p>
</div>
<div v-if="!isEditing" class="h-full flex items-center justify-end transform" :class="isHovering ? 'transition-transform translate-0 w-16' : 'translate-x-40 w-0'">
<span class="material-icons text-xl mr-2 text-gray-200 hover:text-yellow-400" @click.stop="editClick">edit</span>
<span class="material-icons text-xl text-gray-200 hover:text-error cursor-pointer" @click.stop="deleteClick">delete</span>
<span class="material-symbols text-xl mr-2 text-gray-200 hover:text-yellow-400" @click.stop="editClick">edit</span>
<span class="material-symbols text-xl text-gray-200 hover:text-error cursor-pointer" @click.stop="deleteClick">delete</span>
</div>
</div>
</template>

View File

@@ -6,8 +6,15 @@
</div>
</template>
<div class="px-8 py-6 w-full rounded-lg bg-bg shadow-lg border border-black-300 relative overflow-y-scroll" style="max-height: 80vh">
<p class="text-xl font-bold pb-4">Changelog v{{ currentVersionNumber }}</p>
<div class="custom-text" v-html="compiledMarkedown" />
<template v-for="release in releasesToShow">
<div :key="release.name">
<p class="text-xl font-bold pb-4">
Changelog <a :href="`https://github.com/advplyr/audiobookshelf/releases/tag/${release.name}`" target="_blank" class="hover:underline">{{ release.name }}</a> ({{ $formatDate(release.pubdate, dateFormat) }})
</p>
<div class="custom-text" v-html="getChangelog(release)" />
</div>
<div v-if="release !== releasesToShow[releasesToShow.length - 1]" class="border-b border-black-300 my-8" />
</template>
</div>
</modals-modal>
</template>
@@ -18,17 +25,9 @@ import { marked } from '@/static/libs/marked/index.js'
export default {
props: {
value: Boolean,
changelog: String,
currentVersion: String
},
watch: {
show: {
immediate: true,
handler(newVal) {
if (newVal) {
this.init()
}
}
versionData: {
type: Object,
default: () => {}
}
},
computed: {
@@ -40,15 +39,17 @@ export default {
this.$emit('input', val)
}
},
compiledMarkedown() {
return marked.parse(this.changelog, { gfm: true, breaks: true })
dateFormat() {
return this.$store.state.serverSettings.dateFormat
},
currentVersionNumber() {
return this.currentVersion
releasesToShow() {
return this.versionData?.releasesToShow || []
}
},
methods: {
init() {}
getChangelog(release) {
return marked.parse(release.changelog || 'No Changelog Available', { gfm: true, breaks: true })
}
},
mounted() {}
}
@@ -57,7 +58,7 @@ export default {
<style scoped>
/*
1. we need to manually define styles to apply to the parsed markdown elements,
since we don't have access to the actual elements in this component
since we don't have access to the actual elements in this component
2. v-deep allows these to take effect on the content passed in to the v-html in the div above
*/
@@ -70,4 +71,4 @@ since we don't have access to the actual elements in this component
.custom-text ::v-deep > ul {
@apply list-disc list-inside pb-4;
}
</style>
</style>

View File

@@ -122,7 +122,7 @@ export default {
})
.catch((error) => {
console.error('Failed to get collections', error)
this.$toast.error('Failed to load collections')
this.$toast.error(this.$strings.ToastFailedToLoadData)
})
.finally(() => {
this.processing = false
@@ -143,7 +143,7 @@ export default {
})
.catch((error) => {
console.error('Failed to remove books from collection', error)
this.$toast.error(this.$strings.ToastCollectionItemsRemoveFailed)
this.$toast.error(this.$strings.ToastRemoveFailed)
this.processing = false
})
} else {
@@ -157,7 +157,7 @@ export default {
})
.catch((error) => {
console.error('Failed to remove book from collection', error)
this.$toast.error(this.$strings.ToastCollectionItemsRemoveFailed)
this.$toast.error(this.$strings.ToastRemoveFailed)
this.processing = false
})
}
@@ -172,12 +172,12 @@ export default {
.$post(`/api/collections/${collection.id}/batch/add`, { books: this.selectedBookIds })
.then((updatedCollection) => {
console.log(`Books added to collection`, updatedCollection)
this.$toast.success('Books added to collection')
this.$toast.success(this.$strings.ToastCollectionItemsAddSuccess)
this.processing = false
})
.catch((error) => {
console.error('Failed to add books to collection', error)
this.$toast.error('Failed to add books to collection')
this.$toast.error(this.$strings.ToastCollectionItemsAddFailed)
this.processing = false
})
} else {
@@ -187,12 +187,12 @@ export default {
.$post(`/api/collections/${collection.id}/book`, { id: this.selectedLibraryItemId })
.then((updatedCollection) => {
console.log(`Book added to collection`, updatedCollection)
this.$toast.success('Book added to collection')
this.$toast.success(this.$strings.ToastCollectionItemsAddSuccess)
this.processing = false
})
.catch((error) => {
console.error('Failed to add book to collection', error)
this.$toast.error('Failed to add book to collection')
this.$toast.error(this.$strings.ToastCollectionItemsAddFailed)
this.processing = false
})
}
@@ -221,7 +221,7 @@ export default {
.catch((error) => {
console.error('Failed to create collection', error)
var errMsg = error.response ? error.response.data || '' : ''
this.$toast.error(`Failed to create collection: ${errMsg}`)
this.$toast.error(this.$strings.ToastCollectionCreateFailed + ': ' + errMsg)
this.processing = false
})
}

View File

@@ -8,8 +8,8 @@
<nuxt-link :to="`/collection/${collection.id}`" class="pl-2 pr-2 truncate hover:underline cursor-pointer" @click.native="clickNuxtLink">{{ collection.name }}</nuxt-link>
</div>
<div class="h-full flex items-center justify-end transform" :class="isHovering ? 'transition-transform translate-0 w-16' : 'translate-x-40 w-0'">
<ui-btn v-if="!isBookIncluded" color="success" :padding-x="3" small class="h-9" @click.stop="clickAdd"><span class="material-icons text-2xl pt-px">add</span></ui-btn>
<ui-btn v-else color="error" :padding-x="3" class="h-9" small @click.stop="clickRem"><span class="material-icons text-2xl pt-px">remove</span></ui-btn>
<ui-btn v-if="!isBookIncluded" color="success" :padding-x="3" small class="h-9" @click.stop="clickAdd"><span class="material-symbols text-2xl pt-px">add</span></ui-btn>
<ui-btn v-else color="error" :padding-x="3" class="h-9" small @click.stop="clickRem"><span class="material-symbols text-2xl pt-px">remove</span></ui-btn>
</div>
</div>
</template>

View File

@@ -28,7 +28,7 @@
<template v-else>
<div class="flex items-center mb-3">
<div class="hover:bg-white hover:bg-opacity-10 cursor-pointer h-11 w-11 flex items-center justify-center rounded-full" @click="showImageUploader = false">
<span class="material-icons text-4xl">arrow_back</span>
<span class="material-symbols text-4xl">arrow_back</span>
</div>
<p class="ml-2 text-xl mb-1">Collection Cover Image</p>
</div>
@@ -106,7 +106,7 @@ export default {
.catch((error) => {
console.error('Failed to remove collection', error)
this.processing = false
this.$toast.error(this.$strings.ToastCollectionRemoveFailed)
this.$toast.error(this.$strings.ToastRemoveFailed)
})
}
},
@@ -115,7 +115,7 @@ export default {
return
}
if (!this.newCollectionName) {
return this.$toast.error('Collection must have a name')
return this.$toast.error(this.$strings.ToastNameRequired)
}
this.processing = true

View File

@@ -8,7 +8,7 @@
<form @submit.prevent="submitForm">
<div class="w-full text-sm rounded-lg bg-bg shadow-lg border border-black-300">
<div class="w-full px-3 py-5 md:p-12">
<div class="flex items-center -mx-1 mb-2">
<div class="flex items-center -mx-1 mb-4">
<div class="w-full md:w-1/2 px-1">
<ui-text-input-with-label ref="ereaderNameInput" v-model="newDevice.name" :disabled="processing" :label="$strings.LabelName" />
</div>
@@ -16,6 +16,14 @@
<ui-text-input-with-label ref="ereaderEmailInput" v-model="newDevice.email" :disabled="processing" :label="$strings.LabelEmail" />
</div>
</div>
<div class="flex items-center -mx-1 mb-4">
<div class="w-full md:w-1/2 px-1">
<ui-dropdown v-model="newDevice.availabilityOption" :label="$strings.LabelDeviceIsAvailableTo" :items="userAvailabilityOptions" @input="availabilityOptionChanged" />
</div>
<div class="w-full md:w-1/2 px-1">
<ui-multi-select-dropdown v-if="newDevice.availabilityOption === 'specificUsers'" v-model="newDevice.users" :label="$strings.HeaderUsers" :items="userOptions" />
</div>
</div>
<div class="flex items-center pt-4">
<div class="flex-grow" />
@@ -38,14 +46,21 @@ export default {
ereaderDevice: {
type: Object,
default: () => null
}
},
users: {
type: Array,
default: () => []
},
loadUsers: Function
},
data() {
return {
processing: false,
newDevice: {
name: '',
email: ''
email: '',
availabilityOption: 'adminAndUp',
users: []
}
}
},
@@ -68,32 +83,73 @@ export default {
}
},
title() {
return this.ereaderDevice ? 'Create Device' : 'Update Device'
return !this.ereaderDevice ? 'Create Device' : 'Update Device'
},
userAvailabilityOptions() {
return [
{
text: this.$strings.LabelAdminUsersOnly,
value: 'adminOrUp'
},
{
text: this.$strings.LabelAllUsersExcludingGuests,
value: 'userOrUp'
},
{
text: this.$strings.LabelAllUsersIncludingGuests,
value: 'guestOrUp'
},
{
text: this.$strings.LabelSelectUsers,
value: 'specificUsers'
}
]
},
userOptions() {
return this.users.map((u) => ({ text: u.username, value: u.id }))
}
},
methods: {
availabilityOptionChanged(option) {
if (option === 'specificUsers' && !this.users.length) {
this.callLoadUsers()
}
},
async callLoadUsers() {
this.processing = true
await this.loadUsers()
this.processing = false
},
submitForm() {
this.$refs.ereaderNameInput.blur()
this.$refs.ereaderEmailInput.blur()
if (!this.newDevice.name?.trim() || !this.newDevice.email?.trim()) {
this.$toast.error('Name and email required')
this.$toast.error(this.$strings.ToastNameEmailRequired)
return
}
if (this.newDevice.availabilityOption === 'specificUsers' && !this.newDevice.users.length) {
this.$toast.error(this.$strings.ToastSelectAtLeastOneUser)
return
}
if (this.newDevice.availabilityOption !== 'specificUsers') {
this.newDevice.users = []
}
this.newDevice.name = this.newDevice.name.trim()
this.newDevice.email = this.newDevice.email.trim()
if (!this.ereaderDevice) {
if (this.existingDevices.some((d) => d.name === this.newDevice.name)) {
this.$toast.error('EReader device with that name already exists')
this.$toast.error(this.$strings.ToastDeviceNameAlreadyExists)
return
}
this.submitCreate()
} else {
if (this.ereaderDevice.name !== this.newDevice.name && this.existingDevices.some((d) => d.name === this.newDevice.name)) {
this.$toast.error('EReader device with that name already exists')
this.$toast.error(this.$strings.ToastDeviceNameAlreadyExists)
return
}
@@ -118,12 +174,11 @@ export default {
.$post(`/api/emails/ereader-devices`, payload)
.then((data) => {
this.$emit('update', data.ereaderDevices)
this.$toast.success('Device updated')
this.show = false
})
.catch((error) => {
console.error('Failed to update device', error)
this.$toast.error('Failed to update device')
this.$toast.error(this.$strings.ToastDeviceUpdateFailed)
})
.finally(() => {
this.processing = false
@@ -145,12 +200,11 @@ export default {
.$post('/api/emails/ereader-devices', payload)
.then((data) => {
this.$emit('update', data.ereaderDevices || [])
this.$toast.success('Device added')
this.show = false
})
.catch((error) => {
console.error('Failed to add device', error)
this.$toast.error('Failed to add device')
this.$toast.error(this.$strings.ToastDeviceAddFailed)
})
.finally(() => {
this.processing = false
@@ -160,9 +214,13 @@ export default {
if (this.ereaderDevice) {
this.newDevice.name = this.ereaderDevice.name
this.newDevice.email = this.ereaderDevice.email
this.newDevice.availabilityOption = this.ereaderDevice.availabilityOption || 'adminOrUp'
this.newDevice.users = this.ereaderDevice.users || []
} else {
this.newDevice.name = ''
this.newDevice.email = ''
this.newDevice.availabilityOption = 'adminOrUp'
this.newDevice.users = []
}
}
},

View File

@@ -12,10 +12,10 @@
</div>
<div v-show="canGoPrev" class="absolute -left-24 top-0 bottom-0 h-full pointer-events-none flex items-center px-6">
<div class="material-icons text-5xl text-white text-opacity-50 hover:text-opacity-90 cursor-pointer pointer-events-auto" @click.stop.prevent="goPrevBook" @mousedown.prevent>arrow_back_ios</div>
<div class="material-symbols text-5xl text-white text-opacity-50 hover:text-opacity-90 cursor-pointer pointer-events-auto" @click.stop.prevent="goPrevBook" @mousedown.prevent>arrow_back_ios</div>
</div>
<div v-show="canGoNext" class="absolute -right-24 top-0 bottom-0 h-full pointer-events-none flex items-center px-6">
<div class="material-icons text-5xl text-white text-opacity-50 hover:text-opacity-90 cursor-pointer pointer-events-auto" @click.stop.prevent="goNextBook" @mousedown.prevent>arrow_forward_ios</div>
<div class="material-symbols text-5xl text-white text-opacity-50 hover:text-opacity-90 cursor-pointer pointer-events-auto" @click.stop.prevent="goNextBook" @mousedown.prevent>arrow_forward_ios</div>
</div>
<div class="w-full h-full max-h-full text-sm rounded-b-lg rounded-tr-lg bg-bg shadow-lg border border-black-300 relative">

View File

@@ -1,10 +1,10 @@
<template>
<div class="w-full h-full overflow-y-auto overflow-x-hidden px-4 py-6">
<div class="w-full mb-4">
<tables-chapters-table v-if="chapters.length" :library-item="libraryItem" keep-open />
<tables-chapters-table v-if="chapters.length" :library-item="libraryItem" keep-open @close="closeModal" />
<div v-if="!chapters.length" class="py-4 text-center">
<p class="mb-8 text-xl">{{ $strings.MessageNoChapters }}</p>
<ui-btn v-if="userCanUpdate" :to="`/audiobook/${libraryItem.id}/chapters`">{{ $strings.ButtonAddChapters }}</ui-btn>
<ui-btn v-if="userCanUpdate" :to="`/audiobook/${libraryItem.id}/chapters`" @click="clickAddChapters">{{ $strings.ButtonAddChapters }}</ui-btn>
</div>
</div>
</div>
@@ -23,7 +23,7 @@ export default {
},
computed: {
media() {
return this.libraryItem ? this.libraryItem.media || {} : {}
return this.libraryItem?.media || {}
},
chapters() {
return this.media.chapters || []
@@ -32,6 +32,15 @@ export default {
return this.$store.getters['user/getUserCanUpdate']
}
},
methods: {}
methods: {
closeModal() {
this.$emit('close')
},
clickAddChapters() {
if (this.$route.name === 'audiobook-id-chapters' && this.$route.params?.id === this.libraryItem?.id) {
this.closeModal()
}
}
}
}
</script>
</script>

View File

@@ -1,30 +1,31 @@
<template>
<div class="w-full h-full overflow-hidden overflow-y-auto px-2 sm:px-4 py-6 relative">
<div class="flex flex-wrap mb-4">
<div class="relative">
<div class="flex flex-col sm:flex-row mb-4">
<div class="relative self-center">
<covers-preview-cover :src="$store.getters['globals/getLibraryItemCoverSrcById'](libraryItemId, libraryItemUpdatedAt, true)" :width="120" :book-cover-aspect-ratio="bookCoverAspectRatio" />
<!-- book cover overlay -->
<div v-if="media.coverPath" class="absolute top-0 left-0 w-full h-full z-10 opacity-0 hover:opacity-100 transition-opacity duration-100">
<div class="absolute top-0 left-0 w-full h-16 bg-gradient-to-b from-black-600 to-transparent" />
<div class="p-1 absolute top-1 right-1 text-red-500 rounded-full w-8 h-8 cursor-pointer hover:text-red-400 shadow-sm" @click="removeCover">
<div v-if="userCanDelete" class="p-1 absolute top-1 right-1 text-red-500 rounded-full w-8 h-8 cursor-pointer hover:text-red-400 shadow-sm" @click="removeCover">
<ui-tooltip direction="top" :text="$strings.LabelRemoveCover">
<span class="material-icons text-2xl">delete</span>
<span class="material-symbols text-2xl">delete</span>
</ui-tooltip>
</div>
</div>
</div>
<div class="flex-grow sm:pl-2 md:pl-6 sm:pr-2 mt-2 md:mt-0">
<div class="flex-grow sm:pl-2 md:pl-6 sm:pr-2 mt-6 md:mt-0">
<div class="flex items-center">
<div v-if="userCanUpload" class="w-10 md:w-40 pr-2 pt-4 md:min-w-32">
<div v-if="userCanUpload" class="w-10 md:w-40 pr-2 md:min-w-32">
<ui-file-input ref="fileInput" @change="fileUploadSelected">
<span class="hidden md:inline-block">{{ $strings.ButtonUploadCover }}</span>
<span class="material-icons text-2xl inline-block md:!hidden">upload</span>
<span class="material-symbols text-2xl inline-block md:!hidden">upload</span>
</ui-file-input>
</div>
<form @submit.prevent="submitForm" class="flex flex-grow">
<ui-text-input-with-label v-model="imageUrl" :label="$strings.LabelCoverImageURL" />
<ui-btn color="success" type="submit" :padding-x="4" class="mt-5 ml-2 sm:ml-3 w-24">{{ $strings.ButtonSave }}</ui-btn>
<ui-text-input v-model="imageUrl" :placeholder="$strings.LabelImageURLFromTheWeb" class="h-9 w-full" />
<ui-btn color="success" type="submit" :padding-x="4" :disabled="!imageUrl" class="ml-2 sm:ml-3 w-24 h-9">{{ $strings.ButtonSubmit }}</ui-btn>
</form>
</div>
@@ -48,23 +49,23 @@
</div>
</div>
<form @submit.prevent="submitSearchForm">
<div class="flex items-center justify-start -mx-1 h-20">
<div class="w-48 px-1">
<div class="flex flex-wrap sm:flex-nowrap items-center justify-start -mx-1">
<div class="w-48 flex-grow p-1">
<ui-dropdown v-model="provider" :items="providers" :label="$strings.LabelProvider" small />
</div>
<div class="w-72 px-1">
<div class="w-72 flex-grow p-1">
<ui-text-input-with-label v-model="searchTitle" :label="searchTitleLabel" :placeholder="$strings.PlaceholderSearch" />
</div>
<div v-show="provider != 'itunes' && provider != 'audiobookcovers'" class="w-72 px-1">
<div v-show="provider != 'itunes' && provider != 'audiobookcovers'" class="w-72 flex-grow p-1">
<ui-text-input-with-label v-model="searchAuthor" :label="$strings.LabelAuthor" />
</div>
<ui-btn class="mt-5 ml-1" type="submit">{{ $strings.ButtonSearch }}</ui-btn>
<ui-btn class="mt-5 ml-1 md:min-w-24" :padding-x="4" type="submit">{{ $strings.ButtonSearch }}</ui-btn>
</div>
</form>
<div v-if="hasSearched" class="flex items-center flex-wrap justify-center max-h-80 overflow-y-scroll mt-2 max-w-full">
<div v-if="hasSearched" class="flex items-center flex-wrap justify-center sm:max-h-80 sm:overflow-y-scroll mt-2 max-w-full">
<p v-if="!coversFound.length">{{ $strings.MessageNoCoversFound }}</p>
<template v-for="cover in coversFound">
<div :key="cover" class="m-0.5 mb-5 border-2 border-transparent hover:border-yellow-300 cursor-pointer" :class="cover === imageUrl ? 'border-yellow-300' : ''" @click="updateCover(cover)">
<div :key="cover" class="m-0.5 mb-5 border-2 border-transparent hover:border-yellow-300 cursor-pointer" :class="cover === coverPath ? 'border-yellow-300' : ''" @click="updateCover(cover)">
<covers-preview-cover :src="cover" :width="80" show-open-new-tab :book-cover-aspect-ratio="bookCoverAspectRatio" />
</div>
</template>
@@ -72,7 +73,7 @@
<div v-if="previewUpload" class="absolute top-0 left-0 w-full h-full z-10 bg-bg p-8">
<p class="text-lg">{{ $strings.HeaderPreviewCover }}</p>
<span class="absolute top-4 right-4 material-icons text-2xl cursor-pointer" @click="resetCoverPreview">close</span>
<span class="absolute top-4 right-4 material-symbols text-2xl cursor-pointer" @click="resetCoverPreview">close</span>
<div class="flex justify-center py-4">
<covers-preview-cover :src="previewUpload" :width="240" :book-cover-aspect-ratio="bookCoverAspectRatio" />
</div>
@@ -165,6 +166,9 @@ export default {
userCanUpload() {
return this.$store.getters['user/getUserCanUpload']
},
userCanDelete() {
return this.$store.getters['user/getUserCanDelete']
},
userToken() {
return this.$store.getters['user/getToken']
},
@@ -190,7 +194,6 @@ export default {
if (data.error) {
this.$toast.error(data.error)
} else {
this.$toast.success('Cover Uploaded')
this.resetCoverPreview()
}
this.processingUpload = false
@@ -200,7 +203,7 @@ export default {
if (error.response && error.response.data) {
this.$toast.error(error.response.data)
} else {
this.$toast.error('Oops, something went wrong...')
this.$toast.error(this.$strings.ToastUnknownError)
}
this.processingUpload = false
})
@@ -222,72 +225,52 @@ export default {
this.coversFound = []
this.hasSearched = false
}
this.imageUrl = this.media.coverPath || ''
this.imageUrl = ''
this.searchTitle = this.mediaMetadata.title || ''
this.searchAuthor = this.mediaMetadata.authorName || ''
if (this.isPodcast) this.provider = 'itunes'
else this.provider = localStorage.getItem('book-cover-provider') || localStorage.getItem('book-provider') || 'google'
},
removeCover() {
if (!this.media.coverPath) {
this.imageUrl = ''
if (!this.coverPath) {
return
}
this.updateCover('')
this.isProcessing = true
this.$axios
.$delete(`/api/items/${this.libraryItemId}/cover`)
.then(() => {})
.catch((error) => {
console.error('Failed to remove cover', error)
if (error.response?.data) {
this.$toast.error(error.response.data)
}
})
.finally(() => {
this.isProcessing = false
})
},
submitForm() {
this.updateCover(this.imageUrl)
},
async updateCover(cover) {
if (cover === this.coverPath) {
console.warn('Cover has not changed..', cover)
if (!cover.startsWith('http:') && !cover.startsWith('https:')) {
this.$toast.error(this.$strings.ToastInvalidUrl)
return
}
this.isProcessing = true
var success = false
if (!cover) {
// Remove cover
success = await this.$axios
.$delete(`/api/items/${this.libraryItemId}/cover`)
.then(() => true)
.catch((error) => {
console.error('Failed to remove cover', error)
if (error.response && error.response.data) {
this.$toast.error(error.response.data)
}
return false
})
} else if (cover.startsWith('http:') || cover.startsWith('https:')) {
// Download cover from url and use
success = await this.$axios.$post(`/api/items/${this.libraryItemId}/cover`, { url: cover }).catch((error) => {
console.error('Failed to download cover from url', error)
if (error.response && error.response.data) {
this.$toast.error(error.response.data)
}
return false
this.$axios
.$post(`/api/items/${this.libraryItemId}/cover`, { url: cover })
.then(() => {
this.imageUrl = ''
})
} else {
// Update local cover url
const updatePayload = {
cover
}
success = await this.$axios.$patch(`/api/items/${this.libraryItemId}/cover`, updatePayload).catch((error) => {
console.error('Failed to update', error)
if (error.response && error.response.data) {
this.$toast.error(error.response.data)
}
return false
.catch((error) => {
console.error('Failed to update cover', error)
this.$toast.error(error.response?.data || this.$strings.ToastCoverUpdateFailed)
})
.finally(() => {
this.isProcessing = false
})
}
if (success) {
this.$toast.success('Update Successful')
// this.$emit('close')
} else {
this.imageUrl = this.media.coverPath || ''
}
this.isProcessing = false
},
getSearchQuery() {
var searchQuery = `provider=${this.provider}&title=${this.searchTitle}`
@@ -320,8 +303,17 @@ export default {
this.hasSearched = true
},
setCover(coverFile) {
this.updateCover(coverFile.metadata.path)
this.isProcessing = true
this.$axios
.$patch(`/api/items/${this.libraryItemId}/cover`, { cover: coverFile.metadata.path })
.catch((error) => {
console.error('Failed to set local cover', error)
this.$toast.error(error.response?.data || this.$strings.ToastCoverUpdateFailed)
})
.finally(() => {
this.isProcessing = false
})
}
}
}
</script>
</script>

View File

@@ -11,8 +11,8 @@
<ui-btn v-if="userIsAdminOrUp" :loading="quickMatching" color="bg" type="button" class="h-full" small @click.stop.prevent="quickMatch">{{ $strings.ButtonQuickMatch }}</ui-btn>
</ui-tooltip>
<ui-tooltip :disabled="!!libraryScan" text="Rescan library item including metadata" direction="bottom" class="mr-2 md:mr-4">
<ui-btn v-if="userIsAdminOrUp && !isFile" :loading="rescanning" :disabled="!!libraryScan" color="bg" type="button" class="h-full" small @click.stop.prevent="rescan">{{ $strings.ButtonReScan }}</ui-btn>
<ui-tooltip :disabled="isLibraryScanning" text="Rescan library item including metadata" direction="bottom" class="mr-2 md:mr-4">
<ui-btn v-if="userIsAdminOrUp && !isFile" :loading="rescanning" :disabled="isLibraryScanning" color="bg" type="button" class="h-full" small @click.stop.prevent="rescan">{{ $strings.ButtonReScan }}</ui-btn>
</ui-tooltip>
<div class="flex-grow" />
@@ -80,9 +80,9 @@ export default {
libraryProvider() {
return this.$store.getters['libraries/getLibraryProvider'](this.libraryId) || 'google'
},
libraryScan() {
isLibraryScanning() {
if (!this.libraryId) return null
return this.$store.getters['scanners/getLibraryScan'](this.libraryId)
return !!this.$store.getters['tasks/getRunningLibraryScanTask'](this.libraryId)
}
},
methods: {
@@ -92,7 +92,7 @@ export default {
var { title, author } = this.$refs.itemDetailsEdit.getTitleAndAuthorName()
if (!title) {
this.$toast.error('Must have a title for quick match')
this.$toast.error(this.$strings.ToastTitleRequired)
return
}
this.quickMatching = true
@@ -108,9 +108,9 @@ export default {
if (res.warning) {
this.$toast.warning(res.warning)
} else if (res.updated) {
this.$toast.success('Item details updated')
this.$toast.success(this.$strings.ToastItemDetailsUpdateSuccess)
} else {
this.$toast.info('No updates were made')
this.$toast.info(this.$strings.ToastNoUpdatesNecessary)
}
})
.catch((error) => {
@@ -128,18 +128,18 @@ export default {
this.rescanning = false
var result = data.result
if (!result) {
this.$toast.error(`Re-Scan Failed for "${this.title}"`)
this.$toast.error(this.$getString('ToastRescanFailed', [this.title]))
} else if (result === 'UPDATED') {
this.$toast.success(`Re-Scan complete item was updated`)
this.$toast.success(this.$strings.ToastRescanUpdated)
} else if (result === 'UPTODATE') {
this.$toast.success(`Re-Scan complete item was up to date`)
this.$toast.success(this.$strings.ToastRescanUpToDate)
} else if (result === 'REMOVED') {
this.$toast.error(`Re-Scan complete item was removed`)
this.$toast.error(this.$strings.ToastRescanRemoved)
}
})
.catch((error) => {
console.error('Failed to scan library item', error)
this.$toast.error('Failed to scan library item')
this.$toast.error(this.$strings.ToastScanFailed)
this.rescanning = false
})
},
@@ -156,7 +156,7 @@ export default {
}
var updatedDetails = this.$refs.itemDetailsEdit.getDetails()
if (!updatedDetails.hasChanges) {
this.$toast.info('No changes were made')
this.$toast.info(this.$strings.MessageNoUpdatesWereNecessary)
return false
}
return this.updateDetails(updatedDetails)
@@ -170,7 +170,7 @@ export default {
this.isProcessing = false
if (updateResult) {
if (updateResult.updated) {
this.$toast.success('Item details updated')
this.$toast.success(this.$strings.ToastItemDetailsUpdateSuccess)
return true
} else {
this.$toast.info(this.$strings.MessageNoUpdatesWereNecessary)
@@ -217,4 +217,4 @@ export default {
height: calc(100% - 80px);
max-height: calc(100% - 80px);
}
</style>
</style>

View File

@@ -7,7 +7,7 @@
<div class="flex -mb-0.5">
<p class="px-1 text-sm font-semibold" :class="{ 'text-gray-400': checkingNewEpisodes }">{{ $strings.LabelLimit }}</p>
<ui-tooltip direction="top" text="Max # of episodes to download. Use 0 for unlimited.">
<span class="material-icons text-base">info_outlined</span>
<span class="material-symbols text-base">info</span>
</ui-tooltip>
</div>
</ui-text-input-with-label>
@@ -20,20 +20,16 @@
<div v-if="!episodes.length" class="flex my-4 text-center justify-center text-xl">{{ $strings.MessageNoEpisodes }}</div>
<table v-else class="text-sm tracksTable">
<tr>
<th class="text-left">Sort #</th>
<th class="text-left whitespace-nowrap">{{ $strings.LabelEpisode }}</th>
<th class="text-left">{{ $strings.EpisodeTitle }}</th>
<th class="text-center w-28">{{ $strings.EpisodeDuration }}</th>
<th class="text-center w-28">{{ $strings.EpisodeSize }}</th>
<th class="text-center w-20 min-w-20">{{ $strings.LabelEpisode }}</th>
<th class="text-left">{{ $strings.LabelEpisodeTitle }}</th>
<th class="text-center w-28">{{ $strings.LabelEpisodeDuration }}</th>
<th class="text-center w-28">{{ $strings.LabelEpisodeSize }}</th>
</tr>
<tr v-for="episode in episodes" :key="episode.id">
<td class="text-left">
<p class="px-4">{{ episode.index }}</p>
<td class="text-center w-20 min-w-20">
<p>{{ episode.episode }}</p>
</td>
<td class="text-left">
<p class="px-4">{{ episode.episode }}</p>
</td>
<td>
<td dir="auto">
{{ episode.title }}
</td>
<td class="font-mono text-center">

View File

@@ -14,8 +14,7 @@ export default {
},
data() {
return {
tracks: [],
showFullPath: false
tracks: []
}
},
watch: {

View File

@@ -22,17 +22,17 @@
</div>
<div v-show="!processing" class="w-full max-h-full overflow-y-auto overflow-x-hidden matchListWrapper mt-4">
<template v-for="(res, index) in searchResults">
<cards-book-match-card :key="index" :book="res" :is-podcast="isPodcast" :book-cover-aspect-ratio="bookCoverAspectRatio" @select="selectMatch" />
<cards-book-match-card :key="index" :book="res" :current-book-duration="currentBookDuration" :is-podcast="isPodcast" :book-cover-aspect-ratio="bookCoverAspectRatio" @select="selectMatch" />
</template>
</div>
<div v-if="selectedMatchOrig" class="absolute top-0 left-0 w-full bg-bg h-full px-2 py-6 md:p-8 max-h-full overflow-y-auto overflow-x-hidden">
<div class="flex mb-4">
<div class="w-8 h-8 rounded-full hover:bg-white hover:bg-opacity-10 flex items-center justify-center cursor-pointer" @click="clearSelectedMatch">
<span class="material-icons text-3xl">arrow_back</span>
<span class="material-symbols text-3xl">arrow_back</span>
</div>
<p class="text-xl pl-3">{{ $strings.HeaderUpdateDetails }}</p>
</div>
<ui-checkbox v-model="selectAll" checkbox-bg="bg" @input="selectAllToggled" />
<ui-checkbox v-model="selectAll" :label="$strings.LabelSelectAll" checkbox-bg="bg" @input="selectAllToggled" />
<form @submit.prevent="submitMatchUpdate">
<div v-if="selectedMatchOrig.cover" class="flex flex-wrap md:flex-nowrap items-center justify-center">
<div class="flex flex-grow items-center py-2">
@@ -42,15 +42,15 @@
<div class="flex py-2">
<div>
<p class="text-center text-gray-200">New</p>
<p class="text-center text-gray-200">{{ $strings.LabelNew }}</p>
<a :href="selectedMatch.cover" target="_blank" class="bg-primary">
<covers-preview-cover :src="selectedMatch.cover" :width="100" :book-cover-aspect-ratio="bookCoverAspectRatio" />
</a>
</div>
<div v-if="media.coverPath">
<p class="text-center text-gray-200">Current</p>
<a :href="$store.getters['globals/getLibraryItemCoverSrcById'](libraryItemId, null, true)" target="_blank" class="bg-primary">
<covers-preview-cover :src="$store.getters['globals/getLibraryItemCoverSrcById'](libraryItemId, null, true)" :width="100" :book-cover-aspect-ratio="bookCoverAspectRatio" />
<div v-if="media.coverPath" class="ml-0.5">
<p class="text-center text-gray-200">{{ $strings.LabelCurrent }}</p>
<a :href="$store.getters['globals/getLibraryItemCoverSrc'](libraryItem, null, true)" target="_blank" class="bg-primary">
<covers-preview-cover :src="$store.getters['globals/getLibraryItemCoverSrc'](libraryItem, null, true)" :width="100" :book-cover-aspect-ratio="bookCoverAspectRatio" />
</a>
</div>
</div>
@@ -59,49 +59,63 @@
<ui-checkbox v-model="selectedMatchUsage.title" checkbox-bg="bg" @input="checkboxToggled" />
<div class="flex-grow ml-4">
<ui-text-input-with-label v-model="selectedMatch.title" :disabled="!selectedMatchUsage.title" :label="$strings.LabelTitle" />
<p v-if="mediaMetadata.title" class="text-xs ml-1 text-white text-opacity-60">{{ $strings.LabelCurrently }} {{ mediaMetadata.title || '' }}</p>
<p v-if="mediaMetadata.title" class="text-xs ml-1 text-white text-opacity-60">
{{ $strings.LabelCurrently }} <a title="Click to use current value" class="cursor-pointer hover:underline" @click.stop="setMatchFieldValue('title', mediaMetadata.title)">{{ mediaMetadata.title || '' }}</a>
</p>
</div>
</div>
<div v-if="selectedMatchOrig.subtitle" class="flex items-center py-2">
<ui-checkbox v-model="selectedMatchUsage.subtitle" checkbox-bg="bg" @input="checkboxToggled" />
<div class="flex-grow ml-4">
<ui-text-input-with-label v-model="selectedMatch.subtitle" :disabled="!selectedMatchUsage.subtitle" :label="$strings.LabelSubtitle" />
<p v-if="mediaMetadata.subtitle" class="text-xs ml-1 text-white text-opacity-60">{{ $strings.LabelCurrently }} {{ mediaMetadata.subtitle || '' }}</p>
<p v-if="mediaMetadata.subtitle" class="text-xs ml-1 text-white text-opacity-60">
{{ $strings.LabelCurrently }} <a title="Click to use current value" class="cursor-pointer hover:underline" @click.stop="setMatchFieldValue('subtitle', mediaMetadata.subtitle)">{{ mediaMetadata.subtitle }}</a>
</p>
</div>
</div>
<div v-if="selectedMatchOrig.author" class="flex items-center py-2">
<ui-checkbox v-model="selectedMatchUsage.author" checkbox-bg="bg" @input="checkboxToggled" />
<div class="flex-grow ml-4">
<ui-text-input-with-label v-model="selectedMatch.author" :disabled="!selectedMatchUsage.author" :label="$strings.LabelAuthor" />
<p v-if="mediaMetadata.authorName" class="text-xs ml-1 text-white text-opacity-60">{{ $strings.LabelCurrently }} {{ mediaMetadata.authorName || '' }}</p>
<p v-if="mediaMetadata.authorName" class="text-xs ml-1 text-white text-opacity-60">
{{ $strings.LabelCurrently }} <a title="Click to use current value" class="cursor-pointer hover:underline" @click.stop="setMatchFieldValue('author', mediaMetadata.authorName)">{{ mediaMetadata.authorName }}</a>
</p>
</div>
</div>
<div v-if="selectedMatchOrig.narrator" class="flex items-center py-2">
<ui-checkbox v-model="selectedMatchUsage.narrator" checkbox-bg="bg" @input="checkboxToggled" />
<div class="flex-grow ml-4">
<ui-text-input-with-label v-model="selectedMatch.narrator" :disabled="!selectedMatchUsage.narrator" :label="$strings.LabelNarrators" />
<p v-if="mediaMetadata.narratorName" class="text-xs ml-1 text-white text-opacity-60">{{ $strings.LabelCurrently }} {{ mediaMetadata.narratorName || '' }}</p>
<ui-multi-select v-model="selectedMatch.narrator" :items="narrators" :disabled="!selectedMatchUsage.narrator" :label="$strings.LabelNarrators" />
<p v-if="mediaMetadata.narratorName" class="text-xs ml-1 text-white text-opacity-60">
{{ $strings.LabelCurrently }} <a title="Click to use current value" class="cursor-pointer hover:underline" @click.stop="setMatchFieldValue('narrator', mediaMetadata.narrators)">{{ mediaMetadata.narratorName }}</a>
</p>
</div>
</div>
<div v-if="selectedMatchOrig.description" class="flex items-center py-2">
<ui-checkbox v-model="selectedMatchUsage.description" checkbox-bg="bg" @input="checkboxToggled" />
<div class="flex-grow ml-4">
<ui-textarea-with-label v-model="selectedMatch.description" :rows="3" :disabled="!selectedMatchUsage.description" :label="$strings.LabelDescription" />
<p v-if="mediaMetadata.description" class="text-xs ml-1 text-white text-opacity-60">{{ $strings.LabelCurrently }} {{ mediaMetadata.description.substr(0, 100) + (mediaMetadata.description.length > 100 ? '...' : '') }}</p>
<p v-if="mediaMetadata.description" class="text-xs ml-1 text-white text-opacity-60">
{{ $strings.LabelCurrently }} <a title="Click to use current value" class="cursor-pointer hover:underline" @click.stop="setMatchFieldValue('description', mediaMetadata.description)">{{ mediaMetadata.description.substr(0, 100) + (mediaMetadata.description.length > 100 ? '...' : '') }}</a>
</p>
</div>
</div>
<div v-if="selectedMatchOrig.publisher" class="flex items-center py-2">
<ui-checkbox v-model="selectedMatchUsage.publisher" checkbox-bg="bg" @input="checkboxToggled" />
<div class="flex-grow ml-4">
<ui-text-input-with-label v-model="selectedMatch.publisher" :disabled="!selectedMatchUsage.publisher" :label="$strings.LabelPublisher" />
<p v-if="mediaMetadata.publisher" class="text-xs ml-1 text-white text-opacity-60">{{ $strings.LabelCurrently }} {{ mediaMetadata.publisher || '' }}</p>
<p v-if="mediaMetadata.publisher" class="text-xs ml-1 text-white text-opacity-60">
{{ $strings.LabelCurrently }} <a title="Click to use current value" class="cursor-pointer hover:underline" @click.stop="setMatchFieldValue('publisher', mediaMetadata.publisher)">{{ mediaMetadata.publisher }}</a>
</p>
</div>
</div>
<div v-if="selectedMatchOrig.publishedYear" class="flex items-center py-2">
<ui-checkbox v-model="selectedMatchUsage.publishedYear" checkbox-bg="bg" @input="checkboxToggled" />
<div class="flex-grow ml-4">
<ui-text-input-with-label v-model="selectedMatch.publishedYear" :disabled="!selectedMatchUsage.publishedYear" :label="$strings.LabelPublishYear" />
<p v-if="mediaMetadata.publishedYear" class="text-xs ml-1 text-white text-opacity-60">{{ $strings.LabelCurrently }} {{ mediaMetadata.publishedYear || '' }}</p>
<p v-if="mediaMetadata.publishedYear" class="text-xs ml-1 text-white text-opacity-60">
{{ $strings.LabelCurrently }} <a title="Click to use current value" class="cursor-pointer hover:underline" @click.stop="setMatchFieldValue('publishedYear', mediaMetadata.publishedYear)">{{ mediaMetadata.publishedYear }}</a>
</p>
</div>
</div>
@@ -109,42 +123,54 @@
<ui-checkbox v-model="selectedMatchUsage.series" checkbox-bg="bg" @input="checkboxToggled" />
<div class="flex-grow ml-4">
<widgets-series-input-widget v-model="selectedMatch.series" :disabled="!selectedMatchUsage.series" />
<p v-if="mediaMetadata.seriesName" class="text-xs ml-1 text-white text-opacity-60">{{ $strings.LabelCurrently }} {{ mediaMetadata.seriesName || '' }}</p>
<p v-if="mediaMetadata.seriesName" class="text-xs ml-1 text-white text-opacity-60">
{{ $strings.LabelCurrently }} <a title="Click to use current value" class="cursor-pointer hover:underline" @click.stop="setMatchFieldValue('series', mediaMetadata.series)">{{ mediaMetadata.seriesName }}</a>
</p>
</div>
</div>
<div v-if="selectedMatchOrig.genres && selectedMatchOrig.genres.length" class="flex items-center py-2">
<div v-if="selectedMatchOrig.genres?.length" class="flex items-center py-2">
<ui-checkbox v-model="selectedMatchUsage.genres" checkbox-bg="bg" @input="checkboxToggled" />
<div class="flex-grow ml-4">
<ui-multi-select v-model="selectedMatch.genres" :items="genres" :disabled="!selectedMatchUsage.genres" :label="$strings.LabelGenres" />
<p v-if="mediaMetadata.genres" class="text-xs ml-1 text-white text-opacity-60">{{ $strings.LabelCurrently }} {{ mediaMetadata.genres.join(', ') }}</p>
<p v-if="mediaMetadata.genres?.length" class="text-xs ml-1 text-white text-opacity-60">
{{ $strings.LabelCurrently }} <a title="Click to use current value" class="cursor-pointer hover:underline" @click.stop="setMatchFieldValue('genres', mediaMetadata.genres)">{{ mediaMetadata.genres.join(', ') }}</a>
</p>
</div>
</div>
<div v-if="selectedMatchOrig.tags" class="flex items-center py-2">
<ui-checkbox v-model="selectedMatchUsage.tags" checkbox-bg="bg" @input="checkboxToggled" />
<div class="flex-grow ml-4">
<ui-text-input-with-label v-model="selectedMatch.tags" :disabled="!selectedMatchUsage.tags" :label="$strings.LabelTags" />
<p v-if="media.tags" class="text-xs ml-1 text-white text-opacity-60">{{ $strings.LabelCurrently }} {{ media.tags.join(', ') }}</p>
<ui-multi-select v-model="selectedMatch.tags" :items="tags" :disabled="!selectedMatchUsage.tags" :label="$strings.LabelTags" />
<p v-if="media.tags?.length" class="text-xs ml-1 text-white text-opacity-60">
{{ $strings.LabelCurrently }} <a title="Click to use current value" class="cursor-pointer hover:underline" @click.stop="setMatchFieldValue('tags', media.tags)">{{ media.tags.join(', ') }}</a>
</p>
</div>
</div>
<div v-if="selectedMatchOrig.language" class="flex items-center py-2">
<ui-checkbox v-model="selectedMatchUsage.language" checkbox-bg="bg" @input="checkboxToggled" />
<div class="flex-grow ml-4">
<ui-text-input-with-label v-model="selectedMatch.language" :disabled="!selectedMatchUsage.language" :label="$strings.LabelLanguage" />
<p v-if="mediaMetadata.language" class="text-xs ml-1 text-white text-opacity-60">{{ $strings.LabelCurrently }} {{ mediaMetadata.language || '' }}</p>
<p v-if="mediaMetadata.language" class="text-xs ml-1 text-white text-opacity-60">
{{ $strings.LabelCurrently }} <a title="Click to use current value" class="cursor-pointer hover:underline" @click.stop="setMatchFieldValue('language', mediaMetadata.language)">{{ mediaMetadata.language }}</a>
</p>
</div>
</div>
<div v-if="selectedMatchOrig.isbn" class="flex items-center py-2">
<ui-checkbox v-model="selectedMatchUsage.isbn" checkbox-bg="bg" @input="checkboxToggled" />
<div class="flex-grow ml-4">
<ui-text-input-with-label v-model="selectedMatch.isbn" :disabled="!selectedMatchUsage.isbn" label="ISBN" />
<p v-if="mediaMetadata.isbn" class="text-xs ml-1 text-white text-opacity-60">{{ $strings.LabelCurrently }} {{ mediaMetadata.isbn || '' }}</p>
<p v-if="mediaMetadata.isbn" class="text-xs ml-1 text-white text-opacity-60">
{{ $strings.LabelCurrently }} <a title="Click to use current value" class="cursor-pointer hover:underline" @click.stop="setMatchFieldValue('isbn', mediaMetadata.isbn)">{{ mediaMetadata.isbn }}</a>
</p>
</div>
</div>
<div v-if="selectedMatchOrig.asin" class="flex items-center py-2">
<ui-checkbox v-model="selectedMatchUsage.asin" checkbox-bg="bg" @input="checkboxToggled" />
<div class="flex-grow ml-4">
<ui-text-input-with-label v-model="selectedMatch.asin" :disabled="!selectedMatchUsage.asin" label="ASIN" />
<p v-if="mediaMetadata.asin" class="text-xs ml-1 text-white text-opacity-60">{{ $strings.LabelCurrently }} {{ mediaMetadata.asin || '' }}</p>
<p v-if="mediaMetadata.asin" class="text-xs ml-1 text-white text-opacity-60">
{{ $strings.LabelCurrently }} <a title="Click to use current value" class="cursor-pointer hover:underline" @click.stop="setMatchFieldValue('asin', mediaMetadata.asin)">{{ mediaMetadata.asin }}</a>
</p>
</div>
</div>
@@ -152,42 +178,50 @@
<ui-checkbox v-model="selectedMatchUsage.itunesId" checkbox-bg="bg" @input="checkboxToggled" />
<div class="flex-grow ml-4">
<ui-text-input-with-label v-model="selectedMatch.itunesId" type="number" :disabled="!selectedMatchUsage.itunesId" label="iTunes ID" />
<p v-if="mediaMetadata.itunesId" class="text-xs ml-1 text-white text-opacity-60">{{ $strings.LabelCurrently }} {{ mediaMetadata.itunesId || '' }}</p>
<p v-if="mediaMetadata.itunesId" class="text-xs ml-1 text-white text-opacity-60">
{{ $strings.LabelCurrently }} <a title="Click to use current value" class="cursor-pointer hover:underline" @click.stop="setMatchFieldValue('itunesId', mediaMetadata.itunesId)">{{ mediaMetadata.itunesId }}</a>
</p>
</div>
</div>
<div v-if="selectedMatchOrig.feedUrl" class="flex items-center py-2">
<ui-checkbox v-model="selectedMatchUsage.feedUrl" checkbox-bg="bg" @input="checkboxToggled" />
<div class="flex-grow ml-4">
<ui-text-input-with-label v-model="selectedMatch.feedUrl" :disabled="!selectedMatchUsage.feedUrl" label="RSS Feed URL" />
<p v-if="mediaMetadata.feedUrl" class="text-xs ml-1 text-white text-opacity-60">{{ $strings.LabelCurrently }} {{ mediaMetadata.feedUrl || '' }}</p>
<p v-if="mediaMetadata.feedUrl" class="text-xs ml-1 text-white text-opacity-60">
{{ $strings.LabelCurrently }} <a title="Click to use current value" class="cursor-pointer hover:underline" @click.stop="setMatchFieldValue('feedUrl', mediaMetadata.feedUrl)">{{ mediaMetadata.feedUrl }}</a>
</p>
</div>
</div>
<div v-if="selectedMatchOrig.itunesPageUrl" class="flex items-center py-2">
<ui-checkbox v-model="selectedMatchUsage.itunesPageUrl" checkbox-bg="bg" @input="checkboxToggled" />
<div class="flex-grow ml-4">
<ui-text-input-with-label v-model="selectedMatch.itunesPageUrl" :disabled="!selectedMatchUsage.itunesPageUrl" label="iTunes Page URL" />
<p v-if="mediaMetadata.itunesPageUrl" class="text-xs ml-1 text-white text-opacity-60">{{ $strings.LabelCurrently }} {{ mediaMetadata.itunesPageUrl || '' }}</p>
<p v-if="mediaMetadata.itunesPageUrl" class="text-xs ml-1 text-white text-opacity-60">
{{ $strings.LabelCurrently }} <a title="Click to use current value" class="cursor-pointer hover:underline" @click.stop="setMatchFieldValue('itunesPageUrl', mediaMetadata.itunesPageUrl)">{{ mediaMetadata.itunesPageUrl }}</a>
</p>
</div>
</div>
<div v-if="selectedMatchOrig.releaseDate" class="flex items-center py-2">
<ui-checkbox v-model="selectedMatchUsage.releaseDate" checkbox-bg="bg" @input="checkboxToggled" />
<div class="flex-grow ml-4">
<ui-text-input-with-label v-model="selectedMatch.releaseDate" :disabled="!selectedMatchUsage.releaseDate" :label="$strings.LabelReleaseDate" />
<p v-if="mediaMetadata.releaseDate" class="text-xs ml-1 text-white text-opacity-60">{{ $strings.LabelCurrently }} {{ mediaMetadata.releaseDate || '' }}</p>
<p v-if="mediaMetadata.releaseDate" class="text-xs ml-1 text-white text-opacity-60">
{{ $strings.LabelCurrently }} <a title="Click to use current value" class="cursor-pointer hover:underline" @click.stop="setMatchFieldValue('releaseDate', mediaMetadata.releaseDate)">{{ mediaMetadata.releaseDate }}</a>
</p>
</div>
</div>
<div v-if="selectedMatchOrig.explicit != null" class="flex items-center pb-2" :class="{ 'pt-2': mediaMetadata.explicit == null }">
<ui-checkbox v-model="selectedMatchUsage.explicit" checkbox-bg="bg" @input="checkboxToggled" />
<div class="flex-grow ml-4" :class="{ 'pt-4': mediaMetadata.explicit != null }">
<ui-checkbox v-model="selectedMatch.explicit" :label="$strings.LabelExplicit" :disabled="!selectedMatchUsage.explicit" :checkbox-bg="!selectedMatchUsage.explicit ? 'bg' : 'primary'" border-color="gray-600" label-class="pl-2 text-base font-semibold" />
<p v-if="mediaMetadata.explicit != null" class="text-xs ml-1 text-white text-opacity-60">{{ $strings.LabelCurrently }} {{ mediaMetadata.explicit ? 'Explicit (checked)' : 'Not Explicit (unchecked)' }}</p>
<p v-if="mediaMetadata.explicit != null" class="text-xs ml-1 text-white text-opacity-60">{{ $strings.LabelCurrently }} {{ mediaMetadata.explicit ? $strings.LabelExplicitChecked : $strings.LabelExplicitUnchecked }}</p>
</div>
</div>
<div v-if="selectedMatchOrig.abridged != null" class="flex items-center pb-2" :class="{ 'pt-2': mediaMetadata.abridged == null }">
<ui-checkbox v-model="selectedMatchUsage.abridged" checkbox-bg="bg" @input="checkboxToggled" />
<div class="flex-grow ml-4" :class="{ 'pt-4': mediaMetadata.abridged != null }">
<ui-checkbox v-model="selectedMatch.abridged" :label="$strings.LabelAbridged" :disabled="!selectedMatchUsage.abridged" :checkbox-bg="!selectedMatchUsage.abridged ? 'bg' : 'primary'" border-color="gray-600" label-class="pl-2 text-base font-semibold" />
<p v-if="mediaMetadata.abridged != null" class="text-xs ml-1 text-white text-opacity-60">{{ $strings.LabelCurrently }} {{ mediaMetadata.abridged ? 'Abridged (checked)' : 'Unabridged (unchecked)' }}</p>
<p v-if="mediaMetadata.abridged != null" class="text-xs ml-1 text-white text-opacity-60">{{ $strings.LabelCurrently }} {{ mediaMetadata.abridged ? $strings.LabelAbridgedChecked : $strings.LabelAbridgedUnchecked }}</p>
</div>
</div>
@@ -280,6 +314,9 @@ export default {
bookCoverAspectRatio() {
return this.$store.getters['libraries/getBookCoverAspectRatio']
},
filterData() {
return this.$store.state.libraries.filterData || {}
},
providers() {
if (this.isPodcast) return this.$store.state.scanners.podcastProviders
return this.$store.state.scanners.providers
@@ -290,25 +327,41 @@ export default {
return this.$strings.LabelSearchTitle
},
media() {
return this.libraryItem ? this.libraryItem.media || {} : {}
return this.libraryItem?.media || {}
},
mediaMetadata() {
return this.media.metadata || {}
},
currentBookDuration() {
if (this.isPodcast) return 0
return this.media.duration || 0
},
mediaType() {
return this.libraryItem ? this.libraryItem.mediaType : null
return this.libraryItem?.mediaType || null
},
isPodcast() {
return this.mediaType == 'podcast'
},
narrators() {
return this.filterData.narrators || []
},
genres() {
const filterData = this.$store.state.libraries.filterData || {}
const currentGenres = filterData.genres || []
const currentGenres = this.filterData.genres || []
const selectedMatchGenres = this.selectedMatch.genres || []
return [...new Set([...currentGenres ,...selectedMatchGenres])]
return [...new Set([...currentGenres, ...selectedMatchGenres])]
},
tags() {
return this.filterData.tags || []
}
},
methods: {
setMatchFieldValue(field, value) {
if (Array.isArray(value)) {
this.selectedMatch[field] = [...value]
} else {
this.selectedMatch[field] = value
}
},
selectAllToggled(val) {
for (const key in this.selectedMatchUsage) {
this.selectedMatchUsage[key] = val
@@ -324,15 +377,27 @@ export default {
console.error('PersistProvider', error)
}
},
getDefaultBookProvider() {
let provider = localStorage.getItem('book-provider')
if (!provider) return 'google'
// Validate book provider
if (!this.$store.getters['scanners/checkBookProviderExists'](provider)) {
console.error('Stored book provider does not exist', provider)
localStorage.removeItem('book-provider')
return 'google'
}
return provider
},
getSearchQuery() {
if (this.isPodcast) return `term=${this.searchTitle}`
var searchQuery = `provider=${this.provider}&fallbackTitleOnly=1&title=${this.searchTitle}`
if (this.searchAuthor) searchQuery += `&author=${this.searchAuthor}`
if (this.isPodcast) return `term=${encodeURIComponent(this.searchTitle)}`
var searchQuery = `provider=${this.provider}&fallbackTitleOnly=1&title=${encodeURIComponent(this.searchTitle)}`
if (this.searchAuthor) searchQuery += `&author=${encodeURIComponent(this.searchAuthor)}`
if (this.libraryItemId) searchQuery += `&id=${this.libraryItemId}`
return searchQuery
},
submitSearch() {
if (!this.searchTitle) {
this.$toast.warning('Search title is required')
this.$toast.warning(this.$strings.ToastTitleRequired)
return
}
this.persistProvider()
@@ -429,7 +494,9 @@ export default {
this.searchTitle = this.libraryItem.media.metadata.title
this.searchAuthor = this.libraryItem.media.metadata.authorName || ''
if (this.isPodcast) this.provider = 'itunes'
else this.provider = localStorage.getItem('book-provider') || 'google'
else {
this.provider = this.getDefaultBookProvider()
}
// Prefer using ASIN if set and using audible provider
if (this.provider.startsWith('audible') && this.libraryItem.media.metadata.asin) {
@@ -461,6 +528,12 @@ export default {
// match.genres = match.genres.join(',')
match.genres = match.genres.split(',').map((g) => g.trim())
}
if (match.tags && !Array.isArray(match.tags)) {
match.tags = match.tags.split(',').map((g) => g.trim())
}
if (match.narrator && !Array.isArray(match.narrator)) {
match.narrator = match.narrator.split(',').map((g) => g.trim())
}
}
console.log('Select Match', match)
@@ -490,7 +563,10 @@ export default {
} else if (key === 'author' && !this.isPodcast) {
var authors = this.selectedMatch[key]
if (!Array.isArray(authors)) {
authors = authors.split(',').map((au) => au.trim())
authors = authors
.split(',')
.map((au) => au.trim())
.filter((au) => !!au)
}
var authorPayload = []
authors.forEach((authorName) =>
@@ -501,11 +577,11 @@ export default {
)
updatePayload.metadata.authors = authorPayload
} else if (key === 'narrator') {
updatePayload.metadata.narrators = this.selectedMatch[key].split(',').map((v) => v.trim())
updatePayload.metadata.narrators = this.selectedMatch[key]
} else if (key === 'genres') {
updatePayload.metadata.genres = [...this.selectedMatch[key]]
} else if (key === 'tags') {
updatePayload.tags = this.selectedMatch[key].split(',').map((v) => v.trim())
updatePayload.tags = this.selectedMatch[key]
} else if (key === 'itunesId') {
updatePayload.metadata.itunesId = Number(this.selectedMatch[key])
} else {
@@ -528,24 +604,11 @@ export default {
// Persist in local storage
localStorage.setItem('selectedMatchUsage', JSON.stringify(this.selectedMatchUsage))
if (updatePayload.metadata.cover) {
const coverPayload = {
url: updatePayload.metadata.cover
}
const success = await this.$axios.$post(`/api/items/${this.libraryItemId}/cover`, coverPayload).catch((error) => {
console.error('Failed to update', error)
return false
})
if (success) {
this.$toast.success(this.$strings.ToastItemCoverUpdateSuccess)
} else {
this.$toast.error(this.$strings.ToastItemCoverUpdateFailed)
}
console.log('Updated cover')
delete updatePayload.metadata.cover
}
if (Object.keys(updatePayload).length) {
if (updatePayload.metadata.cover) {
updatePayload.url = updatePayload.metadata.cover
delete updatePayload.metadata.cover
}
const mediaUpdatePayload = updatePayload
const updateResult = await this.$axios.$patch(`/api/items/${this.libraryItemId}/media`, mediaUpdatePayload).catch((error) => {
console.error('Failed to update', error)
@@ -555,7 +618,7 @@ export default {
if (updateResult.updated) {
this.$toast.success(this.$strings.ToastItemDetailsUpdateSuccess)
} else {
this.$toast.info(this.$strings.ToastItemDetailsUpdateUnneeded)
this.$toast.info(this.$strings.ToastNoUpdatesNecessary)
}
this.clearSelectedMatch()
this.$emit('selectTab', 'details')
@@ -580,6 +643,7 @@ export default {
.matchListWrapper {
height: calc(100% - 124px);
}
@media (min-width: 768px) {
.matchListWrapper {
height: calc(100% - 80px);

View File

@@ -15,16 +15,16 @@
<ui-tooltip text="Value of 0 sets no max limit. After a new episode is auto-downloaded this will delete the oldest episode if you have more than X episodes. <br>This will only delete 1 episode per new download.">
<p class="pl-4 text-base">
Max episodes to keep
<span class="material-icons icon-text">info_outlined</span>
<span class="material-symbols icon-text">info</span>
</p>
</ui-tooltip>
</div>
<div v-if="enableAutoDownloadEpisodes" class="flex items-center py-2">
<ui-text-input ref="maxEpisodesInput" type="number" v-model="newMaxNewEpisodesToDownload" no-spinner :padding-x="1" text-center class="w-10 text-base" @change="updateMaxNewEpisodesToDownload" />
<ui-text-input ref="maxEpisodesToDownloadInput" type="number" v-model="newMaxNewEpisodesToDownload" no-spinner :padding-x="1" text-center class="w-10 text-base" @change="updateMaxNewEpisodesToDownload" />
<ui-tooltip text="Value of 0 sets no max limit. When checking for new episodes this is the max number of episodes that will be downloaded.">
<p class="pl-4 text-base">
Max new episodes to download per check
<span class="material-icons icon-text">info_outlined</span>
<span class="material-symbols icon-text">info</span>
</p>
</ui-tooltip>
</div>
@@ -129,9 +129,12 @@ export default {
return
}
}
if (this.$refs.maxEpisodesInput && this.$refs.maxEpisodesInput.isFocused) {
if (this.$refs.maxEpisodesInput?.isFocused) {
this.$refs.maxEpisodesInput.blur()
return
}
if (this.$refs.maxEpisodesToDownloadInput?.isFocused) {
this.$refs.maxEpisodesToDownloadInput.blur()
}
const updatePayload = {
@@ -140,9 +143,11 @@ export default {
if (this.enableAutoDownloadEpisodes) {
updatePayload.autoDownloadSchedule = this.cronExpression
}
this.newMaxEpisodesToKeep = Number(this.newMaxEpisodesToKeep)
if (this.newMaxEpisodesToKeep !== this.maxEpisodesToKeep) {
updatePayload.maxEpisodesToKeep = this.newMaxEpisodesToKeep
}
this.newMaxNewEpisodesToDownload = Number(this.newMaxNewEpisodesToDownload)
if (this.newMaxNewEpisodesToDownload !== this.maxNewEpisodesToDownload) {
updatePayload.maxNewEpisodesToDownload = this.newMaxNewEpisodesToDownload
}
@@ -158,7 +163,7 @@ export default {
this.isProcessing = false
if (updateResult) {
if (updateResult.updated) {
this.$toast.success('Item details updated')
this.$toast.success(this.$strings.ToastItemDetailsUpdateSuccess)
return true
} else {
this.$toast.info(this.$strings.MessageNoUpdatesWereNecessary)

View File

@@ -13,26 +13,12 @@
<div>
<ui-btn :to="`/audiobook/${libraryItemId}/manage?tool=m4b`" class="flex items-center"
>{{ $strings.ButtonOpenManager }}
<span class="material-icons text-lg ml-2">launch</span>
<span class="material-symbols text-lg ml-2">launch</span>
</ui-btn>
</div>
</div>
</div>
<!-- Split to mp3 -->
<!-- <div v-if="showMp3Split" class="w-full border border-black-200 p-4 my-8">
<div class="flex items-center">
<div>
<p class="text-lg">{{ $strings.LabelToolsSplitM4b }}</p>
<p class="max-w-sm text-sm pt-2 text-gray-300">{{ $strings.LabelToolsSplitM4bDescription }}</p>
</div>
<div class="flex-grow" />
<div>
<ui-btn :disabled="true">{{ $strings.MessageNotYetImplemented }}</ui-btn>
</div>
</div>
</div> -->
<!-- Embed Metadata -->
<div v-if="mediaTracks.length" class="w-full border border-black-200 p-4 my-8">
<div class="flex items-center">
@@ -44,7 +30,7 @@
<div>
<ui-btn :to="`/audiobook/${libraryItemId}/manage?tool=embed`" class="flex items-center"
>{{ $strings.ButtonOpenManager }}
<span class="material-icons text-lg ml-2">launch</span>
<span class="material-symbols text-lg ml-2">launch</span>
</ui-btn>
<ui-btn v-if="!isMetadataEmbedQueued && !isEmbedTaskRunning" class="w-full mt-4" small @click.stop="quickEmbed">Quick Embed</ui-btn>
@@ -146,4 +132,4 @@ export default {
}
}
}
</script>
</script>

View File

@@ -19,19 +19,19 @@
<div class="folders-container overflow-y-auto w-full py-2 mb-2">
<p class="px-1 text-sm font-semibold">{{ $strings.LabelFolders }}</p>
<div v-for="(folder, index) in folders" :key="index" class="w-full flex items-center py-1 px-2">
<span class="material-icons bg-opacity-50 mr-2 text-yellow-200" style="font-size: 1.2rem">folder</span>
<ui-editable-text ref="folderInput" v-model="folder.fullPath" readonly type="text" class="w-full" />
<span v-show="folders.length > 1" class="material-icons text-2xl ml-2 cursor-pointer hover:text-error" @click="removeFolder(folder)">close</span>
<span class="material-symbols fill bg-opacity-50 mr-2 text-yellow-200" style="font-size: 1.2rem">folder</span>
<ui-editable-text ref="folderInput" v-model="folder.fullPath" :readonly="!!folder.id" type="text" class="w-full" @blur="existingFolderInputBlurred(folder)" />
<span v-show="folders.length > 1" class="material-symbols text-2xl ml-2 cursor-pointer hover:text-error" @click="removeFolder(folder)">close</span>
</div>
<div class="flex py-1 px-2 items-center w-full">
<span class="material-icons bg-opacity-50 mr-2 text-yellow-200" style="font-size: 1.2rem">folder</span>
<span class="material-symbols fill bg-opacity-50 mr-2 text-yellow-200" style="font-size: 1.2rem">folder</span>
<ui-editable-text ref="newFolderInput" v-model="newFolderPath" :placeholder="$strings.PlaceholderNewFolderPath" type="text" class="w-full" @blur="newFolderInputBlurred" />
</div>
<ui-btn class="w-full mt-2" color="primary" @click="browseForFolder">{{ $strings.ButtonBrowseForFolder }}</ui-btn>
</div>
</div>
<modals-libraries-folder-chooser v-else :paths="folderPaths" @back="showDirectoryPicker = false" @select="selectFolder" />
<modals-libraries-lazy-folder-chooser v-else :paths="folderPaths" @back="showDirectoryPicker = false" @select="selectFolder" />
</div>
</template>
@@ -67,10 +67,6 @@ export default {
value: 'podcast',
text: this.$strings.LabelPodcasts
}
// {
// value: 'music',
// text: 'Music'
// }
]
},
folderPaths() {
@@ -110,6 +106,11 @@ export default {
formUpdated() {
this.$emit('update', this.getLibraryData())
},
existingFolderInputBlurred(folder) {
if (!folder.fullPath) {
this.removeFolder(folder)
}
},
newFolderInputBlurred() {
if (this.newFolderPath) {
this.folders.push({ fullPath: this.newFolderPath })
@@ -149,6 +150,7 @@ export default {
this.folders = this.library ? this.library.folders.map((p) => ({ ...p })) : []
this.icon = this.library ? this.library.icon : 'default'
this.mediaType = this.library ? this.library.mediaType : 'book'
this.showDirectoryPicker = false
}
},
@@ -167,4 +169,4 @@ export default {
max-height: calc(80vh - 292px);
}
}
</style>
</style>

View File

@@ -1,5 +1,5 @@
<template>
<modals-modal v-model="show" name="edit-library" :width="700" :height="'unset'" :processing="processing">
<modals-modal v-model="show" name="edit-library" :width="800" :height="'unset'" :processing="processing">
<template #outer>
<div class="absolute top-0 left-0 p-5 w-2/3 overflow-hidden">
<p class="text-xl md:text-3xl text-white truncate">{{ title }}</p>
@@ -12,9 +12,9 @@
</div>
<div class="px-2 md:px-4 w-full text-sm pt-2 md:pt-6 pb-20 rounded-b-lg rounded-tr-lg bg-bg shadow-lg border border-black-300 relative overflow-hidden" style="min-height: 400px; max-height: 80vh">
<component v-if="libraryCopy && show" ref="tabComponent" :is="tabName" :is-new="!library" :library="libraryCopy" :processing.sync="processing" @update="updateLibrary" @close="show = false" />
<component v-if="libraryCopy && show" ref="tabComponent" :is="tabName" :is-new="!library" :library="libraryCopy" :library-id="libraryId" :processing.sync="processing" @update="updateLibrary" @close="show = false" />
<div class="absolute bottom-0 left-0 w-full px-4 py-4 border-t border-white border-opacity-10">
<div v-show="selectedTab !== 'tools'" class="absolute bottom-0 left-0 w-full px-4 py-4 border-t border-white border-opacity-10">
<div class="flex justify-end">
<ui-btn @click="submit">{{ buttonText }}</ui-btn>
</div>
@@ -54,6 +54,12 @@ export default {
buttonText() {
return this.library ? this.$strings.ButtonSave : this.$strings.ButtonCreate
},
mediaType() {
return this.libraryCopy?.mediaType
},
libraryId() {
return this.library?.id
},
tabs() {
return [
{
@@ -66,12 +72,26 @@ export default {
title: this.$strings.HeaderSettings,
component: 'modals-libraries-library-settings'
},
{
id: 'scanner',
title: this.$strings.HeaderSettingsScanner,
component: 'modals-libraries-library-scanner-settings'
},
{
id: 'schedule',
title: this.$strings.HeaderSchedule,
component: 'modals-libraries-schedule-scan'
},
{
id: 'tools',
title: this.$strings.HeaderTools,
component: 'modals-libraries-library-tools'
}
]
].filter((tab) => {
// Do not show tools tab for new libraries
if (tab.id === 'tools' && !this.library) return false
return tab.id !== 'scanner' || this.mediaType === 'book'
})
},
tabName() {
var _tab = this.tabs.find((t) => t.id === this.selectedTab)
@@ -105,7 +125,10 @@ export default {
disableWatcher: false,
skipMatchingMediaWithAsin: false,
skipMatchingMediaWithIsbn: false,
autoScanCronExpression: null
autoScanCronExpression: null,
hideSingleBookSeries: false,
onlyShowLaterBooksInContinueSeries: false,
metadataPrecedence: ['folderStructure', 'audioMetatags', 'nfoFile', 'txtFiles', 'opfFile', 'absMetadata']
}
}
},
@@ -120,7 +143,7 @@ export default {
for (const key in this.libraryCopy) {
if (library[key] !== undefined) {
if (key === 'folders') {
this.libraryCopy.folders = library.folders.map((f) => ({ ...f }))
this.libraryCopy.folders = library.folders.map((f) => ({ ...f })).filter((f) => !!f.fullPath?.trim())
} else if (key === 'settings') {
for (const settingKey in library.settings) {
this.libraryCopy.settings[settingKey] = library.settings[settingKey]
@@ -133,7 +156,7 @@ export default {
},
validate() {
if (!this.libraryCopy.name) {
this.$toast.error('Library must have a name')
this.$toast.error(this.$strings.ToastNameRequired)
return false
}
if (!this.libraryCopy.folders.length) {
@@ -182,7 +205,7 @@ export default {
submitUpdateLibrary() {
var newLibraryPayload = this.getLibraryUpdatePayload()
if (!Object.keys(newLibraryPayload).length) {
this.$toast.info('No updates are necessary')
this.$toast.info(this.$strings.ToastNoUpdatesNecessary)
return
}
@@ -241,4 +264,4 @@ export default {
.tab.tab-selected {
height: 41px;
}
</style>
</style>

View File

@@ -1,38 +1,40 @@
<template>
<div class="w-full h-full bg-bg absolute top-0 left-0 px-4 py-4 z-10">
<div class="flex items-center py-1 mb-2">
<span class="material-icons text-3xl cursor-pointer hover:text-gray-300" @click="$emit('back')">arrow_back</span>
<span class="material-symbols text-3xl cursor-pointer hover:text-gray-300" @click="$emit('back')">arrow_back</span>
<p class="px-4 text-xl">{{ $strings.HeaderChooseAFolder }}</p>
</div>
<div v-if="allFolders.length" class="w-full bg-primary bg-opacity-70 py-1 px-4 mb-2">
<p class="font-mono truncate">{{ selectedPath || '\\' }}</p>
<div v-if="rootDirs.length" class="w-full bg-primary bg-opacity-70 py-1 px-4 mb-2">
<p class="font-mono truncate">{{ selectedPath || '/' }}</p>
</div>
<div v-if="allFolders.length" class="flex bg-primary bg-opacity-50 p-4 folder-container">
<div v-if="rootDirs.length" class="relative flex bg-primary bg-opacity-50 p-4 folder-container">
<div class="w-1/2 border-r border-bg h-full overflow-y-auto">
<div v-if="level > 0" class="w-full p-1 cursor-pointer flex items-center" @click="goBack">
<span class="material-icons bg-opacity-50 text-yellow-200" style="font-size: 1.2rem">folder</span>
<div v-if="level > 0" class="w-full p-1 cursor-pointer flex items-center hover:bg-white/10" @click="goBack">
<span class="material-symbols fill bg-opacity-50 text-yellow-200" style="font-size: 1.2rem">folder</span>
<p class="text-base font-mono px-2">..</p>
</div>
<div v-for="dir in _directories" :key="dir.path" class="dir-item w-full p-1 cursor-pointer flex items-center hover:text-white text-gray-200" :class="dir.className" @click="selectDir(dir)">
<span class="material-icons bg-opacity-50 text-yellow-200" style="font-size: 1.2rem">folder</span>
<div v-for="dir in _directories" :key="dir.path" class="dir-item w-full p-1 cursor-pointer flex items-center hover:text-white text-gray-200 hover:bg-white/10" :class="dir.className" @click="selectDir(dir)">
<span class="material-symbols fill bg-opacity-50 text-yellow-200" style="font-size: 1.2rem">folder</span>
<p class="text-base font-mono px-2 truncate">{{ dir.dirname }}</p>
<span v-if="dir.dirs && dir.dirs.length && dir.path === selectedPath" class="material-icons" style="font-size: 1.1rem">arrow_right</span>
<span v-if="dir.path === selectedPath" class="material-symbols" style="font-size: 1.1rem">arrow_right</span>
</div>
</div>
<div class="w-1/2 h-full overflow-y-auto">
<div v-for="dir in _subdirs" :key="dir.path" :class="dir.className" class="dir-item w-full p-1 cursor-pointer flex items-center hover:text-white text-gray-200" @click="selectSubDir(dir)">
<span class="material-icons bg-opacity-50 text-yellow-200" style="font-size: 1.2rem">folder</span>
<div v-for="dir in _subdirs" :key="dir.path" :class="dir.className" class="dir-item w-full p-1 cursor-pointer flex items-center hover:text-white text-gray-200 hover:bg-white/10" @click="selectSubDir(dir)">
<span class="material-symbols fill bg-opacity-50 text-yellow-200" style="font-size: 1.2rem">folder</span>
<p class="text-base font-mono px-2 truncate">{{ dir.dirname }}</p>
</div>
</div>
<div v-if="loadingDirs" class="absolute inset-0 w-full h-full flex items-center justify-center bg-black/10">
<ui-loading-indicator />
</div>
</div>
<div v-else-if="loadingFolders" class="py-12 text-center">
<div v-else-if="initialLoad" class="py-12 text-center">
<p>{{ $strings.MessageLoadingFolders }}</p>
</div>
<div v-else class="py-12 text-center max-w-sm mx-auto">
<p class="text-lg mb-2">{{ $strings.MessageNoFoldersAvailable }}</p>
<p class="text-gray-300 mb-2">{{ $strings.NoteFolderPicker }}</p>
<p v-if="isDebian" class="text-red-400">{{ $strings.NoteFolderPickerDebian }}</p>
</div>
<div class="w-full py-2">
@@ -51,11 +53,12 @@ export default {
},
data() {
return {
loadingFolders: false,
allFolders: [],
initialLoad: false,
loadingDirs: false,
isPosix: true,
rootDirs: [],
directories: [],
selectedPath: '',
selectedFullPath: '',
subdirs: [],
level: 0,
currentDir: null,
@@ -89,68 +92,91 @@ export default {
...d
}
})
},
isDebian() {
return this.Source == 'debian'
},
Source() {
return this.$store.state.Source
}
},
methods: {
goBack() {
var splitPaths = this.selectedPath.split('\\').slice(1)
var prev = splitPaths.slice(0, -1).join('\\')
async goBack() {
let selPath = this.selectedPath.replace(/^\//, '')
var splitPaths = selPath.split('/')
var currDirs = this.allFolders
for (let i = 0; i < splitPaths.length; i++) {
var _dir = currDirs.find((dir) => dir.dirname === splitPaths[i])
if (_dir && _dir.path.slice(1) === prev) {
this.directories = currDirs
this.selectDir(_dir)
return
} else if (_dir) {
currDirs = _dir.dirs
}
let previousPath = ''
let lookupPath = ''
if (splitPaths.length > 2) {
lookupPath = splitPaths.slice(0, -2).join('/')
}
previousPath = splitPaths.slice(0, -1).join('/')
if (!this.isPosix) {
// For windows drives add a trailing slash. e.g. C:/
if (!this.isPosix && lookupPath.endsWith(':')) {
lookupPath += '/'
}
if (!this.isPosix && previousPath.endsWith(':')) {
previousPath += '/'
}
} else {
// Add leading slash
if (previousPath) previousPath = '/' + previousPath
if (lookupPath) lookupPath = '/' + lookupPath
}
this.level--
this.subdirs = this.directories
this.selectedPath = previousPath
this.directories = await this.fetchDirs(lookupPath, this.level)
},
selectDir(dir) {
async selectDir(dir) {
if (dir.isUsed) return
this.selectedPath = dir.path
this.selectedFullPath = dir.fullPath
this.level = dir.level
this.subdirs = dir.dirs
this.subdirs = await this.fetchDirs(dir.path, dir.level + 1)
},
selectSubDir(dir) {
async selectSubDir(dir) {
if (dir.isUsed) return
this.selectedPath = dir.path
this.selectedFullPath = dir.fullPath
this.level = dir.level
this.directories = this.subdirs
this.subdirs = dir.dirs
this.subdirs = await this.fetchDirs(dir.path, dir.level + 1)
},
selectFolder() {
if (!this.selectedPath) {
console.error('No Selected path')
return
}
if (this.paths.find((p) => p.startsWith(this.selectedFullPath))) {
if (this.paths.find((p) => p.startsWith(this.selectedPath))) {
this.$toast.error(`Oops, you cannot add a parent directory of a folder already added`)
return
}
this.$emit('select', this.selectedFullPath)
this.$emit('select', this.selectedPath)
this.selectedPath = ''
this.selectedFullPath = ''
},
fetchDirs(path, level) {
this.loadingDirs = true
return this.$axios
.$get(`/api/filesystem?path=${path}&level=${level}`)
.then((data) => {
console.log('Fetched directories', data.directories)
this.isPosix = !!data.posix
return data.directories
})
.catch((error) => {
console.error('Failed to get filesystem paths', error)
this.$toast.error(this.$strings.ToastFailedToLoadData)
return []
})
.finally(() => {
this.loadingDirs = false
})
},
async init() {
this.loadingFolders = true
this.allFolders = await this.$store.dispatch('libraries/loadFolders')
this.loadingFolders = false
this.initialLoad = true
this.rootDirs = await this.fetchDirs('', 0)
this.initialLoad = false
this.directories = this.allFolders
this.directories = this.rootDirs
this.subdirs = []
this.selectedPath = ''
this.selectedFullPath = ''
}
},
mounted() {
@@ -173,4 +199,4 @@ export default {
height: calc(100% - 130px);
min-height: calc(100% - 130px);
}
</style>
</style>

View File

@@ -0,0 +1,160 @@
<template>
<div class="w-full h-full px-1 md:px-4 py-1 mb-4">
<div class="flex items-center justify-between mb-2">
<h2 class="text-base md:text-lg text-gray-200">{{ $strings.HeaderMetadataOrderOfPrecedence }}</h2>
<ui-btn small @click="resetToDefault">{{ $strings.ButtonResetToDefault }}</ui-btn>
</div>
<div class="flex items-center justify-between md:justify-start mb-4">
<p class="text-sm text-gray-300 pr-2">{{ $strings.LabelMetadataOrderOfPrecedenceDescription }}</p>
<ui-tooltip :text="$strings.LabelClickForMoreInfo" class="inline-flex">
<a href="https://www.audiobookshelf.org/guides/book-scanner" target="_blank" class="inline-flex">
<span class="material-symbols text-xl w-5">help_outline</span>
</a>
</ui-tooltip>
</div>
<draggable v-model="metadataSourceMapped" v-bind="dragOptions" class="list-group" draggable=".item" handle=".drag-handle" tag="ul" @start="drag = true" @end="drag = false" @update="draggableUpdate">
<transition-group type="transition" :name="!drag ? 'flip-list' : null">
<li v-for="(source, index) in metadataSourceMapped" :key="source.id" :class="source.include ? 'item' : 'opacity-50'" class="w-full px-2 flex items-center relative border border-white/10">
<span class="material-symbols drag-handle text-xl text-gray-400 hover:text-gray-50 mr-2 md:mr-4">reorder</span>
<div class="text-center py-1 w-8 min-w-8">
{{ source.include ? getSourceIndex(source.id) : '' }}
</div>
<div class="flex-grow inline-flex justify-between px-4 py-3">
{{ source.name }} <span v-if="source.include && (index === firstActiveSourceIndex || index === lastActiveSourceIndex)" class="px-2 italic font-semibold text-xs text-gray-400">{{ index === firstActiveSourceIndex ? $strings.LabelHighestPriority : $strings.LabelLowestPriority }}</span>
</div>
<div class="px-2 opacity-100">
<ui-toggle-switch v-model="source.include" :off-color="'error'" @input="includeToggled(source)" />
</div>
</li>
</transition-group>
</draggable>
</div>
</template>
<script>
import draggable from 'vuedraggable'
export default {
components: {
draggable
},
props: {
library: {
type: Object,
default: () => null
},
processing: Boolean
},
data() {
return {
drag: false,
dragOptions: {
animation: 200,
group: 'description',
ghostClass: 'ghost'
},
metadataSourceData: {
folderStructure: {
id: 'folderStructure',
name: 'Folder structure',
include: true
},
audioMetatags: {
id: 'audioMetatags',
name: 'Audio file meta tags OR ebook metadata',
include: true
},
nfoFile: {
id: 'nfoFile',
name: 'NFO file',
include: true
},
txtFiles: {
id: 'txtFiles',
name: 'desc.txt & reader.txt files',
include: true
},
opfFile: {
id: 'opfFile',
name: 'OPF file',
include: true
},
absMetadata: {
id: 'absMetadata',
name: 'Audiobookshelf metadata file',
include: true
}
},
metadataSourceMapped: []
}
},
computed: {
librarySettings() {
return this.library.settings || {}
},
mediaType() {
return this.library.mediaType
},
isBookLibrary() {
return this.mediaType === 'book'
},
firstActiveSourceIndex() {
return this.metadataSourceMapped.findIndex((source) => source.include)
},
lastActiveSourceIndex() {
return this.metadataSourceMapped.findLastIndex((source) => source.include)
}
},
methods: {
getSourceIndex(source) {
const activeSources = (this.librarySettings.metadataPrecedence || []).map((s) => s).reverse()
return activeSources.findIndex((s) => s === source) + 1
},
resetToDefault() {
this.metadataSourceMapped = []
for (const key in this.metadataSourceData) {
this.metadataSourceMapped.push({ ...this.metadataSourceData[key] })
}
this.metadataSourceMapped.reverse()
this.$emit('update', this.getLibraryData())
},
getLibraryData() {
const metadataSourceIds = this.metadataSourceMapped.map((source) => (source.include ? source.id : null)).filter((s) => s)
metadataSourceIds.reverse()
return {
settings: {
metadataPrecedence: metadataSourceIds
}
}
},
includeToggled(source) {
this.updated()
},
draggableUpdate() {
this.updated()
},
updated() {
this.$emit('update', this.getLibraryData())
},
init() {
const metadataPrecedence = this.librarySettings.metadataPrecedence || []
this.metadataSourceMapped = metadataPrecedence.map((source) => this.metadataSourceData[source]).filter((s) => s)
for (const sourceKey in this.metadataSourceData) {
if (!metadataPrecedence.includes(sourceKey)) {
const unusedSourceData = { ...this.metadataSourceData[sourceKey], include: false }
this.metadataSourceMapped.unshift(unusedSourceData)
}
}
this.metadataSourceMapped.reverse()
}
},
mounted() {
this.init()
}
}
</script>

View File

@@ -5,15 +5,15 @@
<ui-tooltip :text="$strings.LabelSettingsSquareBookCoversHelp">
<p class="pl-4 text-base">
{{ $strings.LabelSettingsSquareBookCovers }}
<span class="material-icons icon-text text-sm">info_outlined</span>
<span class="material-symbols icon-text text-sm">info</span>
</p>
</ui-tooltip>
</div>
<div class="py-3">
<div class="flex items-center">
<ui-toggle-switch v-if="!globalWatcherDisabled" v-model="disableWatcher" @input="formUpdated" />
<ui-toggle-switch v-if="!globalWatcherDisabled" v-model="enableWatcher" @input="formUpdated" />
<ui-toggle-switch v-else disabled :value="false" />
<p class="pl-4 text-base">{{ $strings.LabelSettingsDisableWatcherForLibrary }}</p>
<p class="pl-4 text-base">{{ $strings.LabelSettingsEnableWatcherForLibrary }}</p>
</div>
<p v-if="globalWatcherDisabled" class="text-xs text-warning">*{{ $strings.MessageWatcherIsDisabledGlobally }}</p>
</div>
@@ -22,7 +22,7 @@
<ui-tooltip :text="$strings.LabelSettingsAudiobooksOnlyHelp">
<p class="pl-4 text-base">
{{ $strings.LabelSettingsAudiobooksOnly }}
<span class="material-icons icon-text text-sm">info_outlined</span>
<span class="material-symbols icon-text text-sm">info</span>
</p>
</ui-tooltip>
</div>
@@ -44,11 +44,36 @@
<ui-tooltip :text="$strings.LabelSettingsHideSingleBookSeriesHelp">
<p class="pl-4 text-base">
{{ $strings.LabelSettingsHideSingleBookSeries }}
<span class="material-icons icon-text text-sm">info_outlined</span>
<span class="material-symbols icon-text text-sm">info</span>
</p>
</ui-tooltip>
</div>
</div>
<div v-if="isBookLibrary" class="py-3">
<div class="flex items-center">
<ui-toggle-switch v-model="onlyShowLaterBooksInContinueSeries" @input="formUpdated" />
<ui-tooltip :text="$strings.LabelSettingsOnlyShowLaterBooksInContinueSeriesHelp">
<p class="pl-4 text-base">
{{ $strings.LabelSettingsOnlyShowLaterBooksInContinueSeries }}
<span class="material-symbols icon-text text-sm">info</span>
</p>
</ui-tooltip>
</div>
</div>
<div v-if="isBookLibrary" class="py-3">
<div class="flex items-center">
<ui-toggle-switch v-model="epubsAllowScriptedContent" @input="formUpdated" />
<ui-tooltip :text="$strings.LabelSettingsEpubsAllowScriptedContentHelp">
<p class="pl-4 text-base">
{{ $strings.LabelSettingsEpubsAllowScriptedContent }}
<span class="material-symbols icon-text text-sm">info</span>
</p>
</ui-tooltip>
</div>
</div>
<div v-if="isPodcastLibrary" class="py-3">
<ui-dropdown :label="$strings.LabelPodcastSearchRegion" v-model="podcastSearchRegion" :items="$podcastSearchRegionOptions" small class="max-w-72" menu-max-height="200px" @input="formUpdated" />
</div>
</div>
</template>
@@ -65,11 +90,14 @@ export default {
return {
provider: null,
useSquareBookCovers: false,
disableWatcher: false,
enableWatcher: false,
skipMatchingMediaWithAsin: false,
skipMatchingMediaWithIsbn: false,
audiobooksOnly: false,
hideSingleBookSeries: false
epubsAllowScriptedContent: false,
hideSingleBookSeries: false,
onlyShowLaterBooksInContinueSeries: false,
podcastSearchRegion: 'us'
}
},
computed: {
@@ -85,6 +113,9 @@ export default {
isBookLibrary() {
return this.mediaType === 'book'
},
isPodcastLibrary() {
return this.mediaType === 'podcast'
},
providers() {
if (this.mediaType === 'podcast') return this.$store.state.scanners.podcastProviders
return this.$store.state.scanners.providers
@@ -95,11 +126,14 @@ export default {
return {
settings: {
coverAspectRatio: this.useSquareBookCovers ? this.$constants.BookCoverAspectRatio.SQUARE : this.$constants.BookCoverAspectRatio.STANDARD,
disableWatcher: !!this.disableWatcher,
disableWatcher: !this.enableWatcher,
skipMatchingMediaWithAsin: !!this.skipMatchingMediaWithAsin,
skipMatchingMediaWithIsbn: !!this.skipMatchingMediaWithIsbn,
audiobooksOnly: !!this.audiobooksOnly,
hideSingleBookSeries: !!this.hideSingleBookSeries
epubsAllowScriptedContent: !!this.epubsAllowScriptedContent,
hideSingleBookSeries: !!this.hideSingleBookSeries,
onlyShowLaterBooksInContinueSeries: !!this.onlyShowLaterBooksInContinueSeries,
podcastSearchRegion: this.podcastSearchRegion
}
}
},
@@ -108,15 +142,18 @@ export default {
},
init() {
this.useSquareBookCovers = this.librarySettings.coverAspectRatio === this.$constants.BookCoverAspectRatio.SQUARE
this.disableWatcher = !!this.librarySettings.disableWatcher
this.enableWatcher = !this.librarySettings.disableWatcher
this.skipMatchingMediaWithAsin = !!this.librarySettings.skipMatchingMediaWithAsin
this.skipMatchingMediaWithIsbn = !!this.librarySettings.skipMatchingMediaWithIsbn
this.audiobooksOnly = !!this.librarySettings.audiobooksOnly
this.epubsAllowScriptedContent = !!this.librarySettings.epubsAllowScriptedContent
this.hideSingleBookSeries = !!this.librarySettings.hideSingleBookSeries
this.onlyShowLaterBooksInContinueSeries = !!this.librarySettings.onlyShowLaterBooksInContinueSeries
this.podcastSearchRegion = this.librarySettings.podcastSearchRegion || 'us'
}
},
mounted() {
this.init()
}
}
</script>
</script>

View File

@@ -0,0 +1,81 @@
<template>
<div class="w-full h-full px-1 md:px-2 py-1 mb-4">
<div class="w-full border border-black-200 p-4 my-8">
<div class="flex flex-wrap items-center">
<div>
<p class="text-lg">Remove metadata files in library item folders</p>
<p class="max-w-sm text-sm pt-2 text-gray-300">Remove all metadata.json or metadata.abs files in your {{ mediaType }} folders</p>
</div>
<div class="flex-grow" />
<div>
<ui-btn class="mb-4 block" @click.stop="removeAllMetadataClick('json')">Remove all metadata.json</ui-btn>
<ui-btn @click.stop="removeAllMetadataClick('abs')">Remove all metadata.abs</ui-btn>
</div>
</div>
</div>
</div>
</template>
<script>
export default {
props: {
library: {
type: Object,
default: () => null
},
libraryId: String,
processing: Boolean
},
data() {
return {}
},
computed: {
librarySettings() {
return this.library.settings || {}
},
mediaType() {
return this.library.mediaType
},
isBookLibrary() {
return this.mediaType === 'book'
}
},
methods: {
removeAllMetadataClick(ext) {
const payload = {
message: `Are you sure you want to remove all metadata.${ext} files in your library item folders?`,
persistent: true,
callback: (confirmed) => {
if (confirmed) {
this.removeAllMetadataInLibrary(ext)
}
},
type: 'yesNo'
}
this.$store.commit('globals/setConfirmPrompt', payload)
},
removeAllMetadataInLibrary(ext) {
this.$emit('update:processing', true)
this.$axios
.$post(`/api/libraries/${this.libraryId}/remove-metadata?ext=${ext}`)
.then((data) => {
if (!data.found) {
this.$toast.info(`No metadata.${ext} files were found in library`)
} else if (!data.removed) {
this.$toast.success(`No metadata.${ext} files removed`)
} else {
this.$toast.success(`Successfully removed ${data.removed} metadata.${ext} files`)
}
})
.catch((error) => {
console.error('Failed to remove metadata files', error)
this.$toast.error('Failed to remove metadata files')
})
.finally(() => {
this.$emit('update:processing', false)
})
}
},
mounted() {}
}
</script>

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