chore: merge upstream (#1404)
* feat(pushover): attach image to pushover notification payload (#3701) * fix: api language query parameter (#3720) * docs: add j0srisk as a contributor for code (#3745) [skip ci] * docs: update README.md * docs: update .all-contributorsrc --------- Co-authored-by: allcontributors[bot] <46447321+allcontributors[bot]@users.noreply.github.com> * feat(tooltip): add tooltip to display exact time on date hover (#3773) Co-authored-by: Loetwiek <lodommerholtcm@gmail.com> * docs: add Loetwiek as a contributor for code (#3776) [skip ci] * docs: update README.md * docs: update .all-contributorsrc --------- Co-authored-by: allcontributors[bot] <46447321+allcontributors[bot]@users.noreply.github.com> * fix(ui): ensure title fits into the `view collection` box (#3696) * fix(docs): correct openapi docs minor issues (#3648) * docs: add Fuochi as a contributor for doc (#3826) Co-authored-by: allcontributors[bot] <46447321+allcontributors[bot]@users.noreply.github.com> * feat: translations update from Hosted Weblate (#3597) * feat(lang): translated using Weblate (Portuguese (Brazil)) Currently translated at 100.0% (1234 of 1234 strings) feat(lang): translated using Weblate (Portuguese (Brazil)) Currently translated at 99.8% (1232 of 1234 strings) Co-authored-by: Cleiton Carvalho <cleitonsilvacarvalho@gmail.com> Co-authored-by: Hosted Weblate <hosted@weblate.org> Translate-URL: https://hosted.weblate.org/projects/overseerr/overseerr-frontend/pt_BR/ Translation: Overseerr/Overseerr Frontend * feat(lang): translated using Weblate (German) Currently translated at 100.0% (1240 of 1240 strings) feat(lang): translated using Weblate (German) Currently translated at 100.0% (1234 of 1234 strings) Co-authored-by: Hosted Weblate <hosted@weblate.org> Co-authored-by: Nandor Rusz <nandor.rusz@vodafone.de> Co-authored-by: Thomas Schöneberg <ta.schoeneberg@gmail.com> Translate-URL: https://hosted.weblate.org/projects/overseerr/overseerr-frontend/de/ Translation: Overseerr/Overseerr Frontend * feat(lang): translated using Weblate (Danish) Currently translated at 100.0% (1240 of 1240 strings) feat(lang): translated using Weblate (Danish) Currently translated at 100.0% (1236 of 1236 strings) feat(lang): translated using Weblate (Danish) Currently translated at 100.0% (1234 of 1234 strings) Co-authored-by: Anders Ecklon <aecklon@gmail.com> Co-authored-by: Hosted Weblate <hosted@weblate.org> Co-authored-by: Kenneth Hansen <erathor@live.dk> Translate-URL: https://hosted.weblate.org/projects/overseerr/overseerr-frontend/da/ Translation: Overseerr/Overseerr Frontend * feat(lang): translated using Weblate (Greek) Currently translated at 100.0% (1240 of 1240 strings) feat(lang): translated using Weblate (Greek) Currently translated at 100.0% (1236 of 1236 strings) Co-authored-by: BeardedWatermelon <BeardedWatermelon@users.noreply.hosted.weblate.org> Co-authored-by: Hosted Weblate <hosted@weblate.org> Translate-URL: https://hosted.weblate.org/projects/overseerr/overseerr-frontend/el/ Translation: Overseerr/Overseerr Frontend * feat(lang): translated using Weblate (Russian) Currently translated at 100.0% (1240 of 1240 strings) feat(lang): translated using Weblate (Russian) Currently translated at 100.0% (1240 of 1240 strings) feat(lang): translated using Weblate (Russian) Currently translated at 99.5% (1234 of 1240 strings) feat(lang): translated using Weblate (Russian) Currently translated at 100.0% (1234 of 1234 strings) feat(lang): translated using Weblate (Russian) Currently translated at 100.0% (1234 of 1234 strings) feat(lang): translated using Weblate (Russian) Currently translated at 100.0% (1234 of 1234 strings) Co-authored-by: Hosted Weblate <hosted@weblate.org> Co-authored-by: SoundwaveUwU <SoundwaveUwU@users.noreply.hosted.weblate.org> Co-authored-by: SoundwaveUwU <noreply@1000-7.space> Co-authored-by: Димитър Мазнеков (Topper) <d.maznekov@gmail.com> Co-authored-by: Кирилл Тюрин <1337soundwave1337@gmail.com> Translate-URL: https://hosted.weblate.org/projects/overseerr/overseerr-frontend/ru/ Translation: Overseerr/Overseerr Frontend * feat(lang): translated using Weblate (Romanian) Currently translated at 37.1% (461 of 1240 strings) feat(lang): translated using Weblate (Romanian) Currently translated at 37.0% (459 of 1240 strings) feat(lang): translated using Weblate (Romanian) Currently translated at 34.8% (432 of 1240 strings) Co-authored-by: Don Cezar <goldie.czr@gmail.com> Co-authored-by: Dragos <themsk@yahoo.com> Co-authored-by: Eduard Oancea <uberfly@420blaze.it> Co-authored-by: Hosted Weblate <hosted@weblate.org> Translate-URL: https://hosted.weblate.org/projects/overseerr/overseerr-frontend/ro/ Translation: Overseerr/Overseerr Frontend * feat(lang): translated using Weblate (Bulgarian) Currently translated at 100.0% (1240 of 1240 strings) feat(lang): translated using Weblate (Bulgarian) Currently translated at 100.0% (1240 of 1240 strings) feat(lang): translated using Weblate (Bulgarian) Currently translated at 57.4% (712 of 1240 strings) feat(lang): translated using Weblate (Bulgarian) Currently translated at 13.2% (164 of 1240 strings) feat(lang): translated using Weblate (Bulgarian) Currently translated at 4.8% (60 of 1240 strings) feat(lang): added translation using Weblate (Bulgarian) Co-authored-by: Hosted Weblate <hosted@weblate.org> Co-authored-by: sct <sctsnipe@gmail.com> Co-authored-by: Димитър Мазнеков (Topper) <d.maznekov@gmail.com> Translate-URL: https://hosted.weblate.org/projects/overseerr/overseerr-frontend/bg/ Translation: Overseerr/Overseerr Frontend * feat(lang): translated using Weblate (Ukrainian) Currently translated at 100.0% (1240 of 1240 strings) feat(lang): translated using Weblate (Ukrainian) Currently translated at 100.0% (1240 of 1240 strings) feat(lang): translated using Weblate (Ukrainian) Currently translated at 100.0% (1240 of 1240 strings) feat(lang): translated using Weblate (Ukrainian) Currently translated at 100.0% (1240 of 1240 strings) feat(lang): translated using Weblate (Ukrainian) Currently translated at 100.0% (1240 of 1240 strings) feat(lang): translated using Weblate (Ukrainian) Currently translated at 100.0% (1240 of 1240 strings) feat(lang): translated using Weblate (Ukrainian) Currently translated at 100.0% (1240 of 1240 strings) feat(lang): translated using Weblate (Ukrainian) Currently translated at 100.0% (1240 of 1240 strings) feat(lang): translated using Weblate (Ukrainian) Currently translated at 100.0% (1240 of 1240 strings) feat(lang): translated using Weblate (Ukrainian) Currently translated at 100.0% (1240 of 1240 strings) feat(lang): translated using Weblate (Ukrainian) Currently translated at 99.1% (1230 of 1240 strings) feat(lang): translated using Weblate (Ukrainian) Currently translated at 99.1% (1230 of 1240 strings) feat(lang): translated using Weblate (Ukrainian) Currently translated at 99.1% (1230 of 1240 strings) feat(lang): translated using Weblate (Ukrainian) Currently translated at 97.9% (1215 of 1240 strings) feat(lang): translated using Weblate (Ukrainian) Currently translated at 82.0% (1017 of 1240 strings) feat(lang): translated using Weblate (Ukrainian) Currently translated at 72.9% (905 of 1240 strings) feat(lang): translated using Weblate (Ukrainian) Currently translated at 72.9% (905 of 1240 strings) feat(lang): translated using Weblate (Ukrainian) Currently translated at 71.3% (885 of 1240 strings) feat(lang): translated using Weblate (Ukrainian) Currently translated at 64.9% (805 of 1240 strings) feat(lang): translated using Weblate (Ukrainian) Currently translated at 64.4% (799 of 1240 strings) feat(lang): translated using Weblate (Ukrainian) Currently translated at 63.8% (792 of 1240 strings) feat(lang): translated using Weblate (Ukrainian) Currently translated at 63.7% (791 of 1240 strings) feat(lang): translated using Weblate (Ukrainian) Currently translated at 57.5% (714 of 1240 strings) feat(lang): translated using Weblate (Ukrainian) Currently translated at 49.9% (619 of 1240 strings) feat(lang): translated using Weblate (Ukrainian) Currently translated at 35.9% (446 of 1240 strings) feat(lang): translated using Weblate (Ukrainian) Currently translated at 35.9% (446 of 1240 strings) feat(lang): translated using Weblate (Ukrainian) Currently translated at 32.1% (399 of 1240 strings) feat(lang): translated using Weblate (Ukrainian) Currently translated at 24.6% (306 of 1240 strings) feat(lang): translated using Weblate (Ukrainian) Currently translated at 18.9% (235 of 1240 strings) feat(lang): translated using Weblate (Ukrainian) Currently translated at 17.5% (217 of 1240 strings) feat(lang): translated using Weblate (Ukrainian) Currently translated at 17.3% (215 of 1240 strings) feat(lang): translated using Weblate (Ukrainian) Currently translated at 8.0% (100 of 1240 strings) feat(lang): translated using Weblate (Ukrainian) Currently translated at 3.3% (41 of 1240 strings) feat(lang): added translation using Weblate (Ukrainian) Co-authored-by: Hosted Weblate <hosted@weblate.org> Co-authored-by: Michael Michael <michaelvelosk@gmail.com> Co-authored-by: sct <sctsnipe@gmail.com> Co-authored-by: Сергій <sergiy.goncharuk.1@gmail.com> Translate-URL: https://hosted.weblate.org/projects/overseerr/overseerr-frontend/uk/ Translation: Overseerr/Overseerr Frontend * feat(lang): translated using Weblate (Catalan) Currently translated at 100.0% (1240 of 1240 strings) feat(lang): translated using Weblate (Catalan) Currently translated at 100.0% (1240 of 1240 strings) Co-authored-by: Hosted Weblate <hosted@weblate.org> Co-authored-by: dtalens <databio@gmail.com> Translate-URL: https://hosted.weblate.org/projects/overseerr/overseerr-frontend/ca/ Translation: Overseerr/Overseerr Frontend * feat(lang): translated using Weblate (Czech) Currently translated at 100.0% (1240 of 1240 strings) feat(lang): translated using Weblate (Czech) Currently translated at 99.6% (1236 of 1240 strings) Co-authored-by: Hosted Weblate <hosted@weblate.org> Co-authored-by: Karel Krýda <karel.kryda@gmail.com> Co-authored-by: Smexhy <roman.bartik@icloud.com> Translate-URL: https://hosted.weblate.org/projects/overseerr/overseerr-frontend/cs/ Translation: Overseerr/Overseerr Frontend * feat(lang): translated using Weblate (Croatian) Currently translated at 100.0% (1240 of 1240 strings) feat(lang): translated using Weblate (Croatian) Currently translated at 99.8% (1238 of 1240 strings) feat(lang): translated using Weblate (Croatian) Currently translated at 99.8% (1238 of 1240 strings) feat(lang): translated using Weblate (Croatian) Currently translated at 99.6% (1236 of 1240 strings) feat(lang): translated using Weblate (Croatian) Currently translated at 99.5% (1235 of 1240 strings) feat(lang): translated using Weblate (Croatian) Currently translated at 99.5% (1235 of 1240 strings) feat(lang): translated using Weblate (Croatian) Currently translated at 99.1% (1230 of 1240 strings) feat(lang): translated using Weblate (Croatian) Currently translated at 97.5% (1210 of 1240 strings) feat(lang): translated using Weblate (Croatian) Currently translated at 95.5% (1185 of 1240 strings) feat(lang): translated using Weblate (Croatian) Currently translated at 95.6% (1182 of 1236 strings) feat(lang): translated using Weblate (Croatian) Currently translated at 95.6% (1182 of 1236 strings) feat(lang): translated using Weblate (Croatian) Currently translated at 95.2% (1177 of 1236 strings) feat(lang): translated using Weblate (Croatian) Currently translated at 95.2% (1177 of 1236 strings) feat(lang): translated using Weblate (Croatian) Currently translated at 94.3% (1166 of 1236 strings) feat(lang): translated using Weblate (Croatian) Currently translated at 91.7% (1134 of 1236 strings) feat(lang): translated using Weblate (Croatian) Currently translated at 91.7% (1134 of 1236 strings) Co-authored-by: Bruno Ševčenko <bs3vcenk@gmail.com> Co-authored-by: Hosted Weblate <hosted@weblate.org> Co-authored-by: Milo Ivir <mail@milotype.de> Co-authored-by: Stjepan <stjepstjepanovic@gmail.com> Co-authored-by: lpispek <lpispek@gmail.com> Translate-URL: https://hosted.weblate.org/projects/overseerr/overseerr-frontend/hr/ Translation: Overseerr/Overseerr Frontend * feat(lang): translated using Weblate (Hungarian) Currently translated at 91.3% (1133 of 1240 strings) feat(lang): translated using Weblate (Hungarian) Currently translated at 89.3% (1108 of 1240 strings) Co-authored-by: Hosted Weblate <hosted@weblate.org> Co-authored-by: Levente Szajkó <leviko112@gmail.com> Co-authored-by: Nandor Rusz <nandor.rusz@vodafone.de> Translate-URL: https://hosted.weblate.org/projects/overseerr/overseerr-frontend/hu/ Translation: Overseerr/Overseerr Frontend * feat(lang): translated using Weblate (Hebrew) Currently translated at 13.9% (172 of 1236 strings) Co-authored-by: Hosted Weblate <hosted@weblate.org> Co-authored-by: osh <osh@osh.cc> Translate-URL: https://hosted.weblate.org/projects/overseerr/overseerr-frontend/he/ Translation: Overseerr/Overseerr Frontend * feat(lang): translated using Weblate (Polish) Currently translated at 99.1% (1225 of 1236 strings) Co-authored-by: Eryk Michalak <gnu.ewm@protonmail.com> Co-authored-by: Hosted Weblate <hosted@weblate.org> Translate-URL: https://hosted.weblate.org/projects/overseerr/overseerr-frontend/pl/ Translation: Overseerr/Overseerr Frontend * feat(lang): translated using Weblate (Italian) Currently translated at 92.8% (1148 of 1236 strings) Co-authored-by: Francesco <francy.ammirati@hotmail.com> Co-authored-by: Hosted Weblate <hosted@weblate.org> Translate-URL: https://hosted.weblate.org/projects/overseerr/overseerr-frontend/it/ Translation: Overseerr/Overseerr Frontend * feat(lang): translated using Weblate (Arabic) Currently translated at 100.0% (1240 of 1240 strings) feat(lang): translated using Weblate (Arabic) Currently translated at 100.0% (1240 of 1240 strings) feat(lang): translated using Weblate (Arabic) Currently translated at 100.0% (1234 of 1234 strings) Co-authored-by: Fhd-pro <juve.11@msn.com> Co-authored-by: Hosted Weblate <hosted@weblate.org> Translate-URL: https://hosted.weblate.org/projects/overseerr/overseerr-frontend/ar/ Translation: Overseerr/Overseerr Frontend * feat(lang): translated using Weblate (Dutch) Currently translated at 100.0% (1240 of 1240 strings) feat(lang): translated using Weblate (Dutch) Currently translated at 100.0% (1234 of 1234 strings) Co-authored-by: Hosted Weblate <hosted@weblate.org> Co-authored-by: Kobe <kobaubarr@gmail.com> Translate-URL: https://hosted.weblate.org/projects/overseerr/overseerr-frontend/nl/ Translation: Overseerr/Overseerr Frontend * feat(lang): translated using Weblate (Spanish) Currently translated at 100.0% (1240 of 1240 strings) feat(lang): translated using Weblate (Spanish) Currently translated at 100.0% (1236 of 1236 strings) Co-authored-by: Hosted Weblate <hosted@weblate.org> Co-authored-by: gallegonovato <fran-carro@hotmail.es> Translate-URL: https://hosted.weblate.org/projects/overseerr/overseerr-frontend/es/ Translation: Overseerr/Overseerr Frontend * feat(lang): translated using Weblate (French) Currently translated at 100.0% (1240 of 1240 strings) feat(lang): translated using Weblate (French) Currently translated at 100.0% (1240 of 1240 strings) feat(lang): translated using Weblate (French) Currently translated at 100.0% (1236 of 1236 strings) feat(lang): translated using Weblate (French) Currently translated at 99.9% (1235 of 1236 strings) feat(lang): translated using Weblate (French) Currently translated at 99.9% (1235 of 1236 strings) Co-authored-by: Baptiste <baptiste.nee@me.com> Co-authored-by: Dimitri <dimitridroeck@gmail.com> Co-authored-by: Hosted Weblate <hosted@weblate.org> Co-authored-by: Maxime Lafarie <maxime.lafarie@gmail.com> Co-authored-by: Miguel <mig.mllr@gmail.com> Co-authored-by: asurare <jonathan.biteau16@gmail.com> Translate-URL: https://hosted.weblate.org/projects/overseerr/overseerr-frontend/fr/ Translation: Overseerr/Overseerr Frontend * feat(lang): translated using Weblate (Swedish) Currently translated at 100.0% (1240 of 1240 strings) feat(lang): translated using Weblate (Swedish) Currently translated at 100.0% (1240 of 1240 strings) feat(lang): translated using Weblate (Swedish) Currently translated at 100.0% (1240 of 1240 strings) feat(lang): translated using Weblate (Swedish) Currently translated at 100.0% (1240 of 1240 strings) feat(lang): translated using Weblate (Swedish) Currently translated at 100.0% (1240 of 1240 strings) feat(lang): translated using Weblate (Swedish) Currently translated at 100.0% (1240 of 1240 strings) feat(lang): translated using Weblate (Swedish) Currently translated at 100.0% (1240 of 1240 strings) feat(lang): translated using Weblate (Swedish) Currently translated at 100.0% (1236 of 1236 strings) Co-authored-by: Hosted Weblate <hosted@weblate.org> Co-authored-by: Per Erik <urbanlolface@gmail.com> Co-authored-by: Shjosan <shjosan@kakmix.co> Co-authored-by: bittin1ddc447d824349b2 <bittin@reimu.nl> Translate-URL: https://hosted.weblate.org/projects/overseerr/overseerr-frontend/sv/ Translation: Overseerr/Overseerr Frontend * feat(lang): translated using Weblate (Finnish) Currently translated at 2.6% (33 of 1240 strings) feat(lang): added translation using Weblate (Finnish) Co-authored-by: Eero Konttaniemi <eero.konttaniemi@gmail.com> Co-authored-by: Hosted Weblate <hosted@weblate.org> Co-authored-by: sct <sctsnipe@gmail.com> Translate-URL: https://hosted.weblate.org/projects/overseerr/overseerr-frontend/fi/ Translation: Overseerr/Overseerr Frontend * feat(lang): translated using Weblate (Serbian) Currently translated at 50.8% (630 of 1240 strings) Co-authored-by: Hosted Weblate <hosted@weblate.org> Co-authored-by: Milan Smudja <smudja@gmail.com> Translate-URL: https://hosted.weblate.org/projects/overseerr/overseerr-frontend/sr/ Translation: Overseerr/Overseerr Frontend * feat(lang): translated using Weblate (Korean) Currently translated at 100.0% (1234 of 1234 strings) Co-authored-by: Developer J <jshsakura@gmail.com> Co-authored-by: Hosted Weblate <hosted@weblate.org> Translate-URL: https://hosted.weblate.org/projects/overseerr/overseerr-frontend/ko/ Translation: Overseerr/Overseerr Frontend * feat(lang): translated using Weblate (Chinese (Simplified)) Currently translated at 100.0% (1240 of 1240 strings) feat(lang): translated using Weblate (Chinese (Simplified)) Currently translated at 100.0% (1240 of 1240 strings) feat(lang): translated using Weblate (Chinese (Simplified)) Currently translated at 100.0% (1234 of 1234 strings) Co-authored-by: Haohao Zhang <hyacz@foxmail.com> Co-authored-by: Hosted Weblate <hosted@weblate.org> Co-authored-by: lkw123 <lkw20010211@gmail.com> Translate-URL: https://hosted.weblate.org/projects/overseerr/overseerr-frontend/zh_Hans/ Translation: Overseerr/Overseerr Frontend --------- Co-authored-by: Cleiton Carvalho <cleitonsilvacarvalho@gmail.com> Co-authored-by: Nandor Rusz <nandor.rusz@vodafone.de> Co-authored-by: Thomas Schöneberg <ta.schoeneberg@gmail.com> Co-authored-by: Anders Ecklon <aecklon@gmail.com> Co-authored-by: Kenneth Hansen <erathor@live.dk> Co-authored-by: BeardedWatermelon <BeardedWatermelon@users.noreply.hosted.weblate.org> Co-authored-by: SoundwaveUwU <SoundwaveUwU@users.noreply.hosted.weblate.org> Co-authored-by: SoundwaveUwU <noreply@1000-7.space> Co-authored-by: Димитър Мазнеков (Topper) <d.maznekov@gmail.com> Co-authored-by: Кирилл Тюрин <1337soundwave1337@gmail.com> Co-authored-by: Don Cezar <goldie.czr@gmail.com> Co-authored-by: Dragos <themsk@yahoo.com> Co-authored-by: Eduard Oancea <uberfly@420blaze.it> Co-authored-by: sct <sctsnipe@gmail.com> Co-authored-by: Michael Michael <michaelvelosk@gmail.com> Co-authored-by: Сергій <sergiy.goncharuk.1@gmail.com> Co-authored-by: dtalens <databio@gmail.com> Co-authored-by: Karel Krýda <karel.kryda@gmail.com> Co-authored-by: Smexhy <roman.bartik@icloud.com> Co-authored-by: Bruno Ševčenko <bs3vcenk@gmail.com> Co-authored-by: Milo Ivir <mail@milotype.de> Co-authored-by: Stjepan <stjepstjepanovic@gmail.com> Co-authored-by: lpispek <lpispek@gmail.com> Co-authored-by: Levente Szajkó <leviko112@gmail.com> Co-authored-by: osh <osh@osh.cc> Co-authored-by: Eryk Michalak <gnu.ewm@protonmail.com> Co-authored-by: Francesco <francy.ammirati@hotmail.com> Co-authored-by: Fhd-pro <juve.11@msn.com> Co-authored-by: Kobe <kobaubarr@gmail.com> Co-authored-by: gallegonovato <fran-carro@hotmail.es> Co-authored-by: Baptiste <baptiste.nee@me.com> Co-authored-by: Dimitri <dimitridroeck@gmail.com> Co-authored-by: Maxime Lafarie <maxime.lafarie@gmail.com> Co-authored-by: Miguel <mig.mllr@gmail.com> Co-authored-by: asurare <jonathan.biteau16@gmail.com> Co-authored-by: Per Erik <urbanlolface@gmail.com> Co-authored-by: Shjosan <shjosan@kakmix.co> Co-authored-by: bittin1ddc447d824349b2 <bittin@reimu.nl> Co-authored-by: Eero Konttaniemi <eero.konttaniemi@gmail.com> Co-authored-by: Milan Smudja <smudja@gmail.com> Co-authored-by: Developer J <jshsakura@gmail.com> Co-authored-by: Haohao Zhang <hyacz@foxmail.com> Co-authored-by: lkw123 <lkw20010211@gmail.com> * feat(lang): add lang config for Bulgarian, Finnish, Ukrainian, Indonesian, Slovak, Turkish and Maori (#3834) * fix: correct deeplinks on iPad (#3883) * feat(studios): add a24 to studios list (#3902) * docs: add demrich as a contributor for code (#3906) [skip ci] * docs: update README.md * docs: update .all-contributorsrc --------- Co-authored-by: allcontributors[bot] <46447321+allcontributors[bot]@users.noreply.github.com> * feat(watchlist): Cache watchlist requests with matching E-Tags (#3901) * perf(watchlist): add E-Tag caching to Plex watchlist requests * refactor(watchlist): increase frequency of watchlist requests * fix: sync watchlist every 3 min instead of 3 sec * docs: add maxnatamo as a contributor for code (#3907) [skip ci] * docs: update README.md * docs: update .all-contributorsrc --------- Co-authored-by: allcontributors[bot] <46447321+allcontributors[bot]@users.noreply.github.com> * feat(plex): refresh token schedule (#3875) * feat: refresh token schedule fix #3861 * fix(i18n): add i18n message * refactor(plextv): use randomUUID crypto instead custom function * docs: add DamsDev1 as a contributor for code (#3924) [skip ci] * docs: update README.md * docs: update .all-contributorsrc --------- Co-authored-by: allcontributors[bot] <46447321+allcontributors[bot]@users.noreply.github.com> * fix: correct icon showing on certain phones when not pulled (#3939) * feat: add support for requesting "Specials" for TV Shows (#3724) * feat: add support for requesting "Specials" for TV Shows This commit is responsible for adding support in Overseerr for requesting "Special" episodes for TV Shows. This request has become especially pertinent when you consider shows like "Doctor Who". These shows have Specials that are critical to understanding the plot of a TV show. fix #779 * chore(yarn.lock): undo inappropriate changes to yarn.lock I was informed by @sct in a comment on the #3724 PR that it was not appropriate to commit the changes that ended up being made to the yarn.lock file. This commit is responsible, then, for undoing the changes to the yarn.lock file that ended up being submitted. * refactor: change loose equality to strict equality I received a comment from OwsleyJr pointing out that we are using loose equality when we could alternatively just be using strict equality to increase the robustness of our code. This commit does exactly that by squashing out previous usages of loose equality in my commits and replacing them with strict equality * refactor: move 'Specials' string to a global message Owsley pointed out that we are redefining the 'Specials' string multiple times throughout this PR. Instead, we can just move it as a global message. This commit does exactly that. It squashes out and previous declarations of the 'Specials' string inside the src files, and moves it directly to the global messages file. * docs: add AhmedNSidd as a contributor for code (#3964) [skip ci] * docs: update README.md * docs: update .all-contributorsrc --------- Co-authored-by: allcontributors[bot] <46447321+allcontributors[bot]@users.noreply.github.com> * feat(lang): Translations update from Hosted Weblate (#3835) * Update translation files Updated by "Cleanup translation files" hook in Weblate. Co-authored-by: Hosted Weblate <hosted@weblate.org> Translate-URL: https://hosted.weblate.org/projects/overseerr/overseerr-frontend/ Translation: Overseerr/Overseerr Frontend * Update translation files Updated by "Cleanup translation files" hook in Weblate. Co-authored-by: Hosted Weblate <hosted@weblate.org> Translate-URL: https://hosted.weblate.org/projects/overseerr/overseerr-frontend/ Translation: Overseerr/Overseerr Frontend * Update translation files Updated by "Cleanup translation files" hook in Weblate. Co-authored-by: Hosted Weblate <hosted@weblate.org> Translate-URL: https://hosted.weblate.org/projects/overseerr/overseerr-frontend/ Translation: Overseerr/Overseerr Frontend * Update translation files Updated by "Cleanup translation files" hook in Weblate. Co-authored-by: Hosted Weblate <hosted@weblate.org> Translate-URL: https://hosted.weblate.org/projects/overseerr/overseerr-frontend/ Translation: Overseerr/Overseerr Frontend * Update translation files Updated by "Cleanup translation files" hook in Weblate. Co-authored-by: Hosted Weblate <hosted@weblate.org> Translate-URL: https://hosted.weblate.org/projects/overseerr/overseerr-frontend/ Translation: Overseerr/Overseerr Frontend * Update translation files Updated by "Cleanup translation files" hook in Weblate. Co-authored-by: Hosted Weblate <hosted@weblate.org> Translate-URL: https://hosted.weblate.org/projects/overseerr/overseerr-frontend/ Translation: Overseerr/Overseerr Frontend * feat(lang): translated using Weblate (Bulgarian) Currently translated at 100.0% (1240 of 1240 strings) Update translation files Updated by "Cleanup translation files" hook in Weblate. Co-authored-by: Hosted Weblate <hosted@weblate.org> Co-authored-by: Димитър Мазнеков (Topper) <d.maznekov@gmail.com> Translate-URL: https://hosted.weblate.org/projects/overseerr/overseerr-frontend/ Translate-URL: https://hosted.weblate.org/projects/overseerr/overseerr-frontend/bg/ Translation: Overseerr/Overseerr Frontend * Update translation files Updated by "Cleanup translation files" hook in Weblate. feat(lang): translated using Weblate (Ukrainian) Currently translated at 100.0% (1240 of 1240 strings) feat(lang): translated using Weblate (Ukrainian) Currently translated at 100.0% (1240 of 1240 strings) Co-authored-by: Hosted Weblate <hosted@weblate.org> Co-authored-by: Michael Michael <michaelvelosk@gmail.com> Translate-URL: https://hosted.weblate.org/projects/overseerr/overseerr-frontend/ Translate-URL: https://hosted.weblate.org/projects/overseerr/overseerr-frontend/uk/ Translation: Overseerr/Overseerr Frontend * Update translation files Updated by "Cleanup translation files" hook in Weblate. Co-authored-by: Hosted Weblate <hosted@weblate.org> Translate-URL: https://hosted.weblate.org/projects/overseerr/overseerr-frontend/ Translation: Overseerr/Overseerr Frontend * Update translation files Updated by "Cleanup translation files" hook in Weblate. feat(lang): translated using Weblate (Catalan) Currently translated at 100.0% (1241 of 1241 strings) Co-authored-by: Hosted Weblate <hosted@weblate.org> Co-authored-by: dtalens <databio@gmail.com> Translate-URL: https://hosted.weblate.org/projects/overseerr/overseerr-frontend/ Translate-URL: https://hosted.weblate.org/projects/overseerr/overseerr-frontend/ca/ Translation: Overseerr/Overseerr Frontend * Update translation files Updated by "Cleanup translation files" hook in Weblate. Co-authored-by: Hosted Weblate <hosted@weblate.org> Translate-URL: https://hosted.weblate.org/projects/overseerr/overseerr-frontend/ Translation: Overseerr/Overseerr Frontend * Update translation files Updated by "Cleanup translation files" hook in Weblate. Co-authored-by: Hosted Weblate <hosted@weblate.org> Translate-URL: https://hosted.weblate.org/projects/overseerr/overseerr-frontend/ Translation: Overseerr/Overseerr Frontend * Update translation files Updated by "Cleanup translation files" hook in Weblate. feat(lang): translated using Weblate (Hungarian) Currently translated at 99.2% (1231 of 1240 strings) Co-authored-by: Dargo <fuszi88@gmail.com> Co-authored-by: Hosted Weblate <hosted@weblate.org> Translate-URL: https://hosted.weblate.org/projects/overseerr/overseerr-frontend/ Translate-URL: https://hosted.weblate.org/projects/overseerr/overseerr-frontend/hu/ Translation: Overseerr/Overseerr Frontend * Update translation files Updated by "Cleanup translation files" hook in Weblate. feat(lang): translated using Weblate (Polish) Currently translated at 98.8% (1227 of 1241 strings) Co-authored-by: Hosted Weblate <hosted@weblate.org> Co-authored-by: senza <senza@users.noreply.hosted.weblate.org> Translate-URL: https://hosted.weblate.org/projects/overseerr/overseerr-frontend/ Translate-URL: https://hosted.weblate.org/projects/overseerr/overseerr-frontend/pl/ Translation: Overseerr/Overseerr Frontend * Update translation files Updated by "Cleanup translation files" hook in Weblate. Co-authored-by: Hosted Weblate <hosted@weblate.org> Translate-URL: https://hosted.weblate.org/projects/overseerr/overseerr-frontend/ Translation: Overseerr/Overseerr Frontend * Update translation files Updated by "Cleanup translation files" hook in Weblate. Co-authored-by: Hosted Weblate <hosted@weblate.org> Translate-URL: https://hosted.weblate.org/projects/overseerr/overseerr-frontend/ Translation: Overseerr/Overseerr Frontend * Update translation files Updated by "Cleanup translation files" hook in Weblate. feat(lang): translated using Weblate (Dutch) Currently translated at 100.0% (1241 of 1241 strings) Co-authored-by: Hosted Weblate <hosted@weblate.org> Co-authored-by: Robin Van de Vyvere <irazoxgames@gmail.com> Translate-URL: https://hosted.weblate.org/projects/overseerr/overseerr-frontend/ Translate-URL: https://hosted.weblate.org/projects/overseerr/overseerr-frontend/nl/ Translation: Overseerr/Overseerr Frontend * Update translation files Updated by "Cleanup translation files" hook in Weblate. feat(lang): translated using Weblate (Spanish) Currently translated at 100.0% (1241 of 1241 strings) feat(lang): translated using Weblate (Spanish) Currently translated at 100.0% (1241 of 1241 strings) Co-authored-by: Frostar <dasangra@hotmail.com> Co-authored-by: Hosted Weblate <hosted@weblate.org> Co-authored-by: gallegonovato <fran-carro@hotmail.es> Translate-URL: https://hosted.weblate.org/projects/overseerr/overseerr-frontend/ Translate-URL: https://hosted.weblate.org/projects/overseerr/overseerr-frontend/es/ Translation: Overseerr/Overseerr Frontend * feat(lang): translated using Weblate (French) Currently translated at 100.0% (1240 of 1240 strings) Update translation files Updated by "Cleanup translation files" hook in Weblate. feat(lang): translated using Weblate (French) Currently translated at 100.0% (1241 of 1241 strings) feat(lang): translated using Weblate (French) Currently translated at 100.0% (1240 of 1240 strings) Co-authored-by: Hosted Weblate <hosted@weblate.org> Co-authored-by: Nackophilz <zrv4flra@anonaddy.me> Co-authored-by: TayZ3r <artimmo@hotmail.fr> Translate-URL: https://hosted.weblate.org/projects/overseerr/overseerr-frontend/ Translate-URL: https://hosted.weblate.org/projects/overseerr/overseerr-frontend/fr/ Translation: Overseerr/Overseerr Frontend * Update translation files Updated by "Cleanup translation files" hook in Weblate. feat(lang): translated using Weblate (Swedish) Currently translated at 100.0% (1241 of 1241 strings) feat(lang): translated using Weblate (Swedish) Currently translated at 100.0% (1240 of 1240 strings) feat(lang): translated using Weblate (Swedish) Currently translated at 100.0% (1240 of 1240 strings) Co-authored-by: Hosted Weblate <hosted@weblate.org> Co-authored-by: Per Erik <urbanlolface@gmail.com> Translate-URL: https://hosted.weblate.org/projects/overseerr/overseerr-frontend/ Translate-URL: https://hosted.weblate.org/projects/overseerr/overseerr-frontend/sv/ Translation: Overseerr/Overseerr Frontend * feat(lang): translated using Weblate (Finnish) Currently translated at 2.9% (36 of 1240 strings) Co-authored-by: Oskari Lavinto <olavinto@protonmail.com> Translate-URL: https://hosted.weblate.org/projects/overseerr/overseerr-frontend/fi/ Translation: Overseerr/Overseerr Frontend * Update translation files Updated by "Cleanup translation files" hook in Weblate. Co-authored-by: Hosted Weblate <hosted@weblate.org> Translate-URL: https://hosted.weblate.org/projects/overseerr/overseerr-frontend/ Translation: Overseerr/Overseerr Frontend * Update translation files Updated by "Cleanup translation files" hook in Weblate. feat(lang): translated using Weblate (Albanian) Currently translated at 95.8% (1189 of 1240 strings) Co-authored-by: Hosted Weblate <hosted@weblate.org> Co-authored-by: W L <wl@mailhole.de> Translate-URL: https://hosted.weblate.org/projects/overseerr/overseerr-frontend/ Translate-URL: https://hosted.weblate.org/projects/overseerr/overseerr-frontend/sq/ Translation: Overseerr/Overseerr Frontend * Update translation files Updated by "Cleanup translation files" hook in Weblate. feat(lang): translated using Weblate (Korean) Currently translated at 100.0% (1241 of 1241 strings) feat(lang): translated using Weblate (Korean) Currently translated at 100.0% (1240 of 1240 strings) Co-authored-by: Hosted Weblate <hosted@weblate.org> Co-authored-by: Hyun Lee <hyun@yahoo.com> Co-authored-by: cutiekeek <cutiekeek@gmail.com> Translate-URL: https://hosted.weblate.org/projects/overseerr/overseerr-frontend/ Translate-URL: https://hosted.weblate.org/projects/overseerr/overseerr-frontend/ko/ Translation: Overseerr/Overseerr Frontend * Update translation files Updated by "Cleanup translation files" hook in Weblate. feat(lang): translated using Weblate (Portuguese (Portugal)) Currently translated at 98.4% (1221 of 1240 strings) Co-authored-by: Hosted Weblate <hosted@weblate.org> Co-authored-by: Rafael Souto <git@rafaelsouto.com> Translate-URL: https://hosted.weblate.org/projects/overseerr/overseerr-frontend/ Translate-URL: https://hosted.weblate.org/projects/overseerr/overseerr-frontend/pt_PT/ Translation: Overseerr/Overseerr Frontend * feat(lang): translated using Weblate (Chinese (Traditional Han script)) Currently translated at 99.9% (1239 of 1240 strings) Update translation files Updated by "Cleanup translation files" hook in Weblate. feat(lang): translated using Weblate (Chinese (Traditional Han script)) Currently translated at 98.2% (1219 of 1241 strings) Co-authored-by: Hosted Weblate <hosted@weblate.org> Co-authored-by: Marc Lerno <mlerno1192@student.carlalbert.edu> Co-authored-by: dtalens <databio@gmail.com> Translate-URL: https://hosted.weblate.org/projects/overseerr/overseerr-frontend/ Translate-URL: https://hosted.weblate.org/projects/overseerr/overseerr-frontend/zh_Hant/ Translation: Overseerr/Overseerr Frontend * Update translation files Updated by "Cleanup translation files" hook in Weblate. Co-authored-by: Hosted Weblate <hosted@weblate.org> Translate-URL: https://hosted.weblate.org/projects/overseerr/overseerr-frontend/ Translation: Overseerr/Overseerr Frontend * Update translation files Updated by "Cleanup translation files" hook in Weblate. feat(lang): translated using Weblate (Norwegian Bokmål) Currently translated at 89.9% (1115 of 1240 strings) Co-authored-by: Hosted Weblate <hosted@weblate.org> Co-authored-by: exentler <gurandsrud@gmail.com> Translate-URL: https://hosted.weblate.org/projects/overseerr/overseerr-frontend/ Translate-URL: https://hosted.weblate.org/projects/overseerr/overseerr-frontend/nb_NO/ Translation: Overseerr/Overseerr Frontend --------- Co-authored-by: Димитър Мазнеков (Topper) <d.maznekov@gmail.com> Co-authored-by: Michael Michael <michaelvelosk@gmail.com> Co-authored-by: dtalens <databio@gmail.com> Co-authored-by: Dargo <fuszi88@gmail.com> Co-authored-by: senza <senza@users.noreply.hosted.weblate.org> Co-authored-by: Robin Van de Vyvere <irazoxgames@gmail.com> Co-authored-by: Frostar <dasangra@hotmail.com> Co-authored-by: gallegonovato <fran-carro@hotmail.es> Co-authored-by: Nackophilz <zrv4flra@anonaddy.me> Co-authored-by: TayZ3r <artimmo@hotmail.fr> Co-authored-by: Per Erik <urbanlolface@gmail.com> Co-authored-by: Oskari Lavinto <olavinto@protonmail.com> Co-authored-by: W L <wl@mailhole.de> Co-authored-by: Hyun Lee <hyun@yahoo.com> Co-authored-by: cutiekeek <cutiekeek@gmail.com> Co-authored-by: Rafael Souto <git@rafaelsouto.com> Co-authored-by: Marc Lerno <mlerno1192@student.carlalbert.edu> Co-authored-by: exentler <gurandsrud@gmail.com> * feat(ui): prevent password manager interference & improve service links (#3989) * docs: add s0up4200 as a contributor for code (#4047) [skip ci] * docs: update README.md * docs: update .all-contributorsrc --------- Co-authored-by: allcontributors[bot] <46447321+allcontributors[bot]@users.noreply.github.com> * fix(ui): update Plex Logo (#3955) * docs: add JackW6809 as a contributor for code (#4048) [skip ci] * docs: update README.md * docs: update .all-contributorsrc --------- Co-authored-by: allcontributors[bot] <46447321+allcontributors[bot]@users.noreply.github.com> * feat: requests/issues menu count (#3470) * feat: request and issue count added to sidebar/mobile menu * fix: added permission check for count visibility * refactor: modified badge design for count * fix: properly update issue and request counts in certain scenarios (#4051) * fix: center count badge on sidebar and mobile menu (#4052) * fix: request english trailers as a fallback when using other languages (#4009) Co-authored-by: Stancu Florin <florin@stancu.me> * docs: add StancuFlorin as a contributor for code (#4053) [skip ci] * docs: update README.md * docs: update .all-contributorsrc --------- Co-authored-by: allcontributors[bot] <46447321+allcontributors[bot]@users.noreply.github.com> * feat: added the PWA badge indicator for requests pending (#3411) refactor: removed unnecessary code when sending web push notification fix: moved all notify user logic into webpush refactor: n refactor: remove all unnecessary prettier changes fix: n fix: n fix: n fix: n fix: increment sw version fix: n --------- Co-authored-by: Isaac M <masesisaac@gmail.com> Co-authored-by: Joseph Risk <j0srisk@gmail.com> Co-authored-by: allcontributors[bot] <46447321+allcontributors[bot]@users.noreply.github.com> Co-authored-by: Loetwiek <79059734+Loetwiek@users.noreply.github.com> Co-authored-by: Loetwiek <lodommerholtcm@gmail.com> Co-authored-by: Fallenbagel <98979876+Fallenbagel@users.noreply.github.com> Co-authored-by: Fuochi <ffuochi@hotmail.com> Co-authored-by: Weblate (bot) <hosted@weblate.org> Co-authored-by: Cleiton Carvalho <cleitonsilvacarvalho@gmail.com> Co-authored-by: Nandor Rusz <nandor.rusz@vodafone.de> Co-authored-by: Thomas Schöneberg <ta.schoeneberg@gmail.com> Co-authored-by: Anders Ecklon <aecklon@gmail.com> Co-authored-by: Kenneth Hansen <erathor@live.dk> Co-authored-by: BeardedWatermelon <BeardedWatermelon@users.noreply.hosted.weblate.org> Co-authored-by: SoundwaveUwU <SoundwaveUwU@users.noreply.hosted.weblate.org> Co-authored-by: SoundwaveUwU <noreply@1000-7.space> Co-authored-by: Димитър Мазнеков (Topper) <d.maznekov@gmail.com> Co-authored-by: Кирилл Тюрин <1337soundwave1337@gmail.com> Co-authored-by: Don Cezar <goldie.czr@gmail.com> Co-authored-by: Dragos <themsk@yahoo.com> Co-authored-by: Eduard Oancea <uberfly@420blaze.it> Co-authored-by: sct <sctsnipe@gmail.com> Co-authored-by: Michael Michael <michaelvelosk@gmail.com> Co-authored-by: Сергій <sergiy.goncharuk.1@gmail.com> Co-authored-by: dtalens <databio@gmail.com> Co-authored-by: Karel Krýda <karel.kryda@gmail.com> Co-authored-by: Smexhy <roman.bartik@icloud.com> Co-authored-by: Bruno Ševčenko <bs3vcenk@gmail.com> Co-authored-by: Milo Ivir <mail@milotype.de> Co-authored-by: Stjepan <stjepstjepanovic@gmail.com> Co-authored-by: lpispek <lpispek@gmail.com> Co-authored-by: Levente Szajkó <leviko112@gmail.com> Co-authored-by: osh <osh@osh.cc> Co-authored-by: Eryk Michalak <gnu.ewm@protonmail.com> Co-authored-by: Francesco <francy.ammirati@hotmail.com> Co-authored-by: Fhd-pro <juve.11@msn.com> Co-authored-by: Kobe <kobaubarr@gmail.com> Co-authored-by: gallegonovato <fran-carro@hotmail.es> Co-authored-by: Baptiste <baptiste.nee@me.com> Co-authored-by: Dimitri <dimitridroeck@gmail.com> Co-authored-by: Maxime Lafarie <maxime.lafarie@gmail.com> Co-authored-by: Miguel <mig.mllr@gmail.com> Co-authored-by: asurare <jonathan.biteau16@gmail.com> Co-authored-by: Per Erik <urbanlolface@gmail.com> Co-authored-by: Shjosan <shjosan@kakmix.co> Co-authored-by: bittin1ddc447d824349b2 <bittin@reimu.nl> Co-authored-by: Eero Konttaniemi <eero.konttaniemi@gmail.com> Co-authored-by: Milan Smudja <smudja@gmail.com> Co-authored-by: Developer J <jshsakura@gmail.com> Co-authored-by: Haohao Zhang <hyacz@foxmail.com> Co-authored-by: lkw123 <lkw20010211@gmail.com> Co-authored-by: Jordan Jones <me@jjones.tech> Co-authored-by: Brandon Cohen <brandon@z3hn.dev> Co-authored-by: David Emrich <demrich@me.com> Co-authored-by: Max T. Kristiansen <me@maxtrier.dk> Co-authored-by: Damien Fajole <60252259+DamsDev1@users.noreply.github.com> Co-authored-by: Ahmed Siddiqui <36286128+AhmedNSidd@users.noreply.github.com> Co-authored-by: Dargo <fuszi88@gmail.com> Co-authored-by: senza <senza@users.noreply.hosted.weblate.org> Co-authored-by: Robin Van de Vyvere <irazoxgames@gmail.com> Co-authored-by: Frostar <dasangra@hotmail.com> Co-authored-by: Nackophilz <zrv4flra@anonaddy.me> Co-authored-by: TayZ3r <artimmo@hotmail.fr> Co-authored-by: Oskari Lavinto <olavinto@protonmail.com> Co-authored-by: W L <wl@mailhole.de> Co-authored-by: Hyun Lee <hyun@yahoo.com> Co-authored-by: cutiekeek <cutiekeek@gmail.com> Co-authored-by: Rafael Souto <git@rafaelsouto.com> Co-authored-by: Marc Lerno <mlerno1192@student.carlalbert.edu> Co-authored-by: exentler <gurandsrud@gmail.com> Co-authored-by: soup <s0up4200@pm.me> Co-authored-by: JackOXI <53652452+JackW6809@users.noreply.github.com> Co-authored-by: Stancu Florin <StancuFlorin@users.noreply.github.com> Co-authored-by: Stancu Florin <florin@stancu.me> Co-authored-by: Brandon Cohen <cohbrandon@gmail.com>
This commit is contained in:
@@ -94,7 +94,8 @@
|
||||
"avatar_url": "https://avatars.githubusercontent.com/u/345752?v=4",
|
||||
"profile": "https://github.com/jab416171",
|
||||
"contributions": [
|
||||
"doc"
|
||||
"doc",
|
||||
"code"
|
||||
]
|
||||
},
|
||||
{
|
||||
@@ -620,6 +621,87 @@
|
||||
"contributions": [
|
||||
"code"
|
||||
]
|
||||
},
|
||||
{
|
||||
"login": "j0srisk",
|
||||
"name": "Joseph Risk",
|
||||
"avatar_url": "https://avatars.githubusercontent.com/u/18372584?v=4",
|
||||
"profile": "http://josephrisk.com",
|
||||
"contributions": [
|
||||
"code"
|
||||
]
|
||||
},
|
||||
{
|
||||
"login": "Loetwiek",
|
||||
"name": "Loetwiek",
|
||||
"avatar_url": "https://avatars.githubusercontent.com/u/79059734?v=4",
|
||||
"profile": "https://github.com/Loetwiek",
|
||||
"contributions": [
|
||||
"code"
|
||||
]
|
||||
},
|
||||
{
|
||||
"login": "Fuochi",
|
||||
"name": "Fuochi",
|
||||
"avatar_url": "https://avatars.githubusercontent.com/u/4720478?v=4",
|
||||
"profile": "https://github.com/Fuochi",
|
||||
"contributions": [
|
||||
"doc"
|
||||
]
|
||||
},
|
||||
{
|
||||
"login": "demrich",
|
||||
"name": "David Emrich",
|
||||
"avatar_url": "https://avatars.githubusercontent.com/u/30092389?v=4",
|
||||
"profile": "https://github.com/demrich",
|
||||
"contributions": [
|
||||
"code"
|
||||
]
|
||||
},
|
||||
{
|
||||
"login": "maxnatamo",
|
||||
"name": "Max T. Kristiansen",
|
||||
"avatar_url": "https://avatars.githubusercontent.com/u/5898152?v=4",
|
||||
"profile": "https://maxtrier.dk",
|
||||
"contributions": [
|
||||
"code"
|
||||
]
|
||||
},
|
||||
{
|
||||
"login": "DamsDev1",
|
||||
"name": "Damien Fajole",
|
||||
"avatar_url": "https://avatars.githubusercontent.com/u/60252259?v=4",
|
||||
"profile": "https://damsdev.me",
|
||||
"contributions": [
|
||||
"code"
|
||||
]
|
||||
},
|
||||
{
|
||||
"login": "AhmedNSidd",
|
||||
"name": "Ahmed Siddiqui",
|
||||
"avatar_url": "https://avatars.githubusercontent.com/u/36286128?v=4",
|
||||
"profile": "https://github.com/AhmedNSidd",
|
||||
"contributions": [
|
||||
"code"
|
||||
]
|
||||
},
|
||||
{
|
||||
"login": "JackW6809",
|
||||
"name": "JackOXI",
|
||||
"avatar_url": "https://avatars.githubusercontent.com/u/53652452?v=4",
|
||||
"profile": "https://github.com/JackW6809",
|
||||
"contributions": [
|
||||
"code"
|
||||
]
|
||||
},
|
||||
{
|
||||
"login": "StancuFlorin",
|
||||
"name": "Stancu Florin",
|
||||
"avatar_url": "https://avatars.githubusercontent.com/u/1199404?v=4",
|
||||
"profile": "http://indicus.ro",
|
||||
"contributions": [
|
||||
"code"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
@@ -288,7 +288,7 @@ Thanks goes to these wonderful people from Overseerr ([emoji key](https://allcon
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://github.com/byakurau"><img src="https://avatars.githubusercontent.com/u/1811683?v=4?s=100" width="100px;" alt="byakurau"/><br /><sub><b>byakurau</b></sub></a><br /><a href="#translation-byakurau" title="Translation">🌍</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://github.com/miknii"><img src="https://avatars.githubusercontent.com/u/109232569?v=4?s=100" width="100px;" alt="miknii"/><br /><sub><b>miknii</b></sub></a><br /><a href="#translation-miknii" title="Translation">🌍</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://github.com/Eclipseop"><img src="https://avatars.githubusercontent.com/u/5846213?v=4?s=100" width="100px;" alt="Mackenzie"/><br /><sub><b>Mackenzie</b></sub></a><br /><a href="https://github.com/sct/overseerr/commits?author=Eclipseop" title="Code">💻</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://github.com/s0up4200"><img src="https://avatars.githubusercontent.com/u/18177310?v=4?s=100" width="100px;" alt="soup"/><br /><sub><b>soup</b></sub></a><br /><a href="https://github.com/sct/overseerr/commits?author=s0up4200" title="Documentation">📖</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://github.com/s0up4200"><img src="https://avatars.githubusercontent.com/u/18177310?v=4?s=100" width="100px;" alt="soup"/><br /><sub><b>soup</b></sub></a><br /><a href="https://github.com/sct/overseerr/commits?author=s0up4200" title="Documentation">📖</a> <a href="https://github.com/sct/overseerr/commits?author=s0up4200" title="Code">💻</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://github.com/ceptonit"><img src="https://avatars.githubusercontent.com/u/12678743?v=4?s=100" width="100px;" alt="ceptonit"/><br /><sub><b>ceptonit</b></sub></a><br /><a href="https://github.com/sct/overseerr/commits?author=ceptonit" title="Documentation">📖</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://github.com/aedelbro"><img src="https://avatars.githubusercontent.com/u/36162221?v=4?s=100" width="100px;" alt="aedelbro"/><br /><sub><b>aedelbro</b></sub></a><br /><a href="https://github.com/sct/overseerr/commits?author=aedelbro" title="Code">💻</a></td>
|
||||
</tr>
|
||||
@@ -321,6 +321,8 @@ Thanks goes to these wonderful people from Overseerr ([emoji key](https://allcon
|
||||
</tr>
|
||||
<tr>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://github.com/AhmedNSidd"><img src="https://avatars.githubusercontent.com/u/36286128?v=4?s=100" width="100px;" alt="Ahmed Siddiqui"/><br /><sub><b>Ahmed Siddiqui</b></sub></a><br /><a href="https://github.com/sct/overseerr/commits?author=AhmedNSidd" title="Code">💻</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://github.com/JackW6809"><img src="https://avatars.githubusercontent.com/u/53652452?v=4?s=100" width="100px;" alt="JackOXI"/><br /><sub><b>JackOXI</b></sub></a><br /><a href="https://github.com/sct/overseerr/commits?author=JackW6809" title="Code">💻</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="http://indicus.ro"><img src="https://avatars.githubusercontent.com/u/1199404?v=4?s=100" width="100px;" alt="Stancu Florin"/><br /><sub><b>Stancu Florin</b></sub></a><br /><a href="https://github.com/sct/overseerr/commits?author=StancuFlorin" title="Code">💻</a></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
21
public/sw.js
21
public/sw.js
@@ -3,7 +3,7 @@
|
||||
// previously cached resources to be updated from the network.
|
||||
// This variable is intentionally declared and unused.
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
const OFFLINE_VERSION = 3;
|
||||
const OFFLINE_VERSION = 4;
|
||||
const CACHE_NAME = 'offline';
|
||||
// Customize this with a different URL if needed.
|
||||
const OFFLINE_URL = '/offline.html';
|
||||
@@ -107,6 +107,25 @@ self.addEventListener('push', (event) => {
|
||||
);
|
||||
}
|
||||
|
||||
// Set the badge with the amount of pending requests
|
||||
// Only update the badge if the payload confirms they are the admin
|
||||
if (
|
||||
(payload.notificationType === 'MEDIA_APPROVED' ||
|
||||
payload.notificationType === 'MEDIA_DECLINED') &&
|
||||
payload.isAdmin
|
||||
) {
|
||||
if ('setAppBadge' in navigator) {
|
||||
navigator.setAppBadge(payload.pendingRequestsCount);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (payload.notificationType === 'MEDIA_PENDING') {
|
||||
if ('setAppBadge' in navigator) {
|
||||
navigator.setAppBadge(payload.pendingRequestsCount);
|
||||
}
|
||||
}
|
||||
|
||||
event.waitUntil(self.registration.showNotification(payload.subject, options));
|
||||
});
|
||||
|
||||
|
||||
@@ -256,6 +256,7 @@ class TheMovieDb extends ExternalAPI {
|
||||
language,
|
||||
append_to_response:
|
||||
'credits,external_ids,videos,keywords,release_dates,watch/providers',
|
||||
include_video_language: language + ', en',
|
||||
},
|
||||
43200
|
||||
);
|
||||
@@ -280,6 +281,7 @@ class TheMovieDb extends ExternalAPI {
|
||||
language,
|
||||
append_to_response:
|
||||
'aggregate_credits,credits,external_ids,keywords,videos,content_ratings,watch/providers',
|
||||
include_video_language: language + ', en',
|
||||
},
|
||||
43200
|
||||
);
|
||||
|
||||
@@ -19,6 +19,8 @@ export interface NotificationPayload {
|
||||
request?: MediaRequest;
|
||||
issue?: Issue;
|
||||
comment?: IssueComment;
|
||||
pendingRequestsCount?: number;
|
||||
isAdmin?: boolean;
|
||||
}
|
||||
|
||||
export abstract class BaseAgent<T extends NotificationAgentConfig> {
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { IssueType, IssueTypeName } from '@server/constants/issue';
|
||||
import { MediaType } from '@server/constants/media';
|
||||
import { MediaRequestStatus, MediaType } from '@server/constants/media';
|
||||
import { getRepository } from '@server/datasource';
|
||||
import MediaRequest from '@server/entity/MediaRequest';
|
||||
import { User } from '@server/entity/User';
|
||||
import { UserPushSubscription } from '@server/entity/UserPushSubscription';
|
||||
import type { NotificationAgentConfig } from '@server/lib/settings';
|
||||
@@ -19,6 +20,8 @@ interface PushNotificationPayload {
|
||||
actionUrl?: string;
|
||||
actionUrlTitle?: string;
|
||||
requestId?: number;
|
||||
pendingRequestsCount?: number;
|
||||
isAdmin?: boolean;
|
||||
}
|
||||
|
||||
class WebPushAgent
|
||||
@@ -129,6 +132,8 @@ class WebPushAgent
|
||||
requestId: payload.request?.id,
|
||||
actionUrl,
|
||||
actionUrlTitle,
|
||||
pendingRequestsCount: payload.pendingRequestsCount,
|
||||
isAdmin: payload.isAdmin,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -152,6 +157,51 @@ class WebPushAgent
|
||||
|
||||
const mainUser = await userRepository.findOne({ where: { id: 1 } });
|
||||
|
||||
const requestRepository = getRepository(MediaRequest);
|
||||
|
||||
const pendingRequests = await requestRepository.find({
|
||||
where: { status: MediaRequestStatus.PENDING },
|
||||
});
|
||||
|
||||
const webPushNotification = async (
|
||||
pushSub: UserPushSubscription,
|
||||
notificationPayload: Buffer
|
||||
) => {
|
||||
logger.debug('Sending web push notification', {
|
||||
label: 'Notifications',
|
||||
recipient: pushSub.user.displayName,
|
||||
type: Notification[type],
|
||||
subject: payload.subject,
|
||||
});
|
||||
|
||||
try {
|
||||
await webpush.sendNotification(
|
||||
{
|
||||
endpoint: pushSub.endpoint,
|
||||
keys: {
|
||||
auth: pushSub.auth,
|
||||
p256dh: pushSub.p256dh,
|
||||
},
|
||||
},
|
||||
notificationPayload
|
||||
);
|
||||
} catch (e) {
|
||||
logger.error(
|
||||
'Error sending web push notification; removing subscription',
|
||||
{
|
||||
label: 'Notifications',
|
||||
recipient: pushSub.user.displayName,
|
||||
type: Notification[type],
|
||||
subject: payload.subject,
|
||||
errorMessage: e.message,
|
||||
}
|
||||
);
|
||||
|
||||
// Failed to send notification so we need to remove the subscription
|
||||
userPushSubRepository.remove(pushSub);
|
||||
}
|
||||
};
|
||||
|
||||
if (
|
||||
payload.notifyUser &&
|
||||
// Check if user has webpush notifications enabled and fallback to true if undefined
|
||||
@@ -169,7 +219,11 @@ class WebPushAgent
|
||||
pushSubs.push(...notifySubs);
|
||||
}
|
||||
|
||||
if (payload.notifyAdmin) {
|
||||
if (
|
||||
payload.notifyAdmin ||
|
||||
type === Notification.MEDIA_APPROVED ||
|
||||
type === Notification.MEDIA_DECLINED
|
||||
) {
|
||||
const users = await userRepository.find();
|
||||
|
||||
const manageUsers = users.filter(
|
||||
@@ -192,7 +246,42 @@ class WebPushAgent
|
||||
})
|
||||
.getMany();
|
||||
|
||||
pushSubs.push(...allSubs);
|
||||
// We only want to send the custom notification when type is approved or declined
|
||||
// Otherwise, default to the normal notification
|
||||
if (
|
||||
type === Notification.MEDIA_APPROVED ||
|
||||
type === Notification.MEDIA_DECLINED
|
||||
) {
|
||||
if (mainUser && allSubs.length > 0) {
|
||||
webpush.setVapidDetails(
|
||||
`mailto:${mainUser.email}`,
|
||||
settings.vapidPublic,
|
||||
settings.vapidPrivate
|
||||
);
|
||||
|
||||
// Custom payload only for updating the app badge
|
||||
const notificationBadgePayload = Buffer.from(
|
||||
JSON.stringify(
|
||||
this.getNotificationPayload(type, {
|
||||
subject: payload.subject,
|
||||
notifySystem: false,
|
||||
notifyAdmin: true,
|
||||
isAdmin: true,
|
||||
pendingRequestsCount: pendingRequests.length,
|
||||
})
|
||||
),
|
||||
'utf-8'
|
||||
);
|
||||
|
||||
await Promise.all(
|
||||
allSubs.map(async (sub) => {
|
||||
webPushNotification(sub, notificationBadgePayload);
|
||||
})
|
||||
);
|
||||
}
|
||||
} else {
|
||||
pushSubs.push(...allSubs);
|
||||
}
|
||||
}
|
||||
|
||||
if (mainUser && pushSubs.length > 0) {
|
||||
@@ -202,6 +291,10 @@ class WebPushAgent
|
||||
settings.vapidPrivate
|
||||
);
|
||||
|
||||
if (type === Notification.MEDIA_PENDING) {
|
||||
payload = { ...payload, pendingRequestsCount: pendingRequests.length };
|
||||
}
|
||||
|
||||
const notificationPayload = Buffer.from(
|
||||
JSON.stringify(this.getNotificationPayload(type, payload)),
|
||||
'utf-8'
|
||||
@@ -209,39 +302,7 @@ class WebPushAgent
|
||||
|
||||
await Promise.all(
|
||||
pushSubs.map(async (sub) => {
|
||||
logger.debug('Sending web push notification', {
|
||||
label: 'Notifications',
|
||||
recipient: sub.user.displayName,
|
||||
type: Notification[type],
|
||||
subject: payload.subject,
|
||||
});
|
||||
|
||||
try {
|
||||
await webpush.sendNotification(
|
||||
{
|
||||
endpoint: sub.endpoint,
|
||||
keys: {
|
||||
auth: sub.auth,
|
||||
p256dh: sub.p256dh,
|
||||
},
|
||||
},
|
||||
notificationPayload
|
||||
);
|
||||
} catch (e) {
|
||||
logger.error(
|
||||
'Error sending web push notification; removing subscription',
|
||||
{
|
||||
label: 'Notifications',
|
||||
recipient: sub.user.displayName,
|
||||
type: Notification[type],
|
||||
subject: payload.subject,
|
||||
errorMessage: e.message,
|
||||
}
|
||||
);
|
||||
|
||||
// Failed to send notification so we need to remove the subscription
|
||||
userPushSubRepository.remove(sub);
|
||||
}
|
||||
webPushNotification(sub, notificationPayload);
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,85 +1,43 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<!-- Generator: Adobe Illustrator 26.2.1, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" version="1.1" viewBox="0 0 361 157">
|
||||
<defs>
|
||||
<style>
|
||||
.cls-1 {
|
||||
fill: #fff;
|
||||
}
|
||||
|
||||
<svg
|
||||
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
||||
xmlns:cc="http://creativecommons.org/ns#"
|
||||
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||
xmlns:svg="http://www.w3.org/2000/svg"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
version="1.1"
|
||||
id="plex-logo"
|
||||
x="0px"
|
||||
y="0px"
|
||||
viewBox="0 0 1000 460.89727"
|
||||
xml:space="preserve"
|
||||
sodipodi:docname="plex-logo.svg"
|
||||
inkscape:version="0.92.4 (5da689c313, 2019-01-14)"><metadata
|
||||
id="metadata25"><rdf:RDF><cc:Work
|
||||
rdf:about=""><dc:format>image/svg+xml</dc:format><dc:type
|
||||
rdf:resource="http://purl.org/dc/dcmitype/StillImage" /><dc:title></dc:title></cc:Work></rdf:RDF></metadata><defs
|
||||
id="defs23">
|
||||
</defs><sodipodi:namedview
|
||||
pagecolor="#ffffff"
|
||||
bordercolor="#111111"
|
||||
borderopacity="1"
|
||||
objecttolerance="10"
|
||||
gridtolerance="10"
|
||||
guidetolerance="10"
|
||||
inkscape:pageopacity="0"
|
||||
inkscape:pageshadow="2"
|
||||
inkscape:window-width="1920"
|
||||
inkscape:window-height="1017"
|
||||
id="namedview21"
|
||||
showgrid="false"
|
||||
fit-margin-top="0"
|
||||
fit-margin-left="0"
|
||||
fit-margin-right="0"
|
||||
fit-margin-bottom="0"
|
||||
inkscape:zoom="0.27956081"
|
||||
inkscape:cx="783.06912"
|
||||
inkscape:cy="-132.85701"
|
||||
inkscape:window-x="1912"
|
||||
inkscape:window-y="-8"
|
||||
inkscape:window-maximized="1"
|
||||
inkscape:current-layer="plex-logo" />
|
||||
<style
|
||||
type="text/css"
|
||||
id="style2">
|
||||
.st0{fill:#FFFFFF;}
|
||||
.st1{fill:#EBAF00;}
|
||||
</style>
|
||||
<path
|
||||
class="st0"
|
||||
d="m 164.18919,82.43243 c -39.86487,0 -65.540543,11.48648 -87.162163,38.51351 V 91.21621 H 0 v 366.21621 c 0,0 1.3513514,0.67567 5.4054053,1.35135 5.4054057,1.35135 33.7837827,7.43243 54.7297287,-10.13514 18.243244,-15.54054 22.297295,-33.78378 22.297295,-54.05405 v -52.7027 c 22.297301,23.64864 47.297301,33.78378 82.432431,33.78378 75.67567,0 133.78378,-61.48648 133.78378,-143.24323 0,-88.51352 -56.08108,-150 -134.45945,-150 z m -14.86487,223.64864 c -42.56756,0 -76.351351,-35.13513 -76.351351,-77.7027 0,-41.89189 39.864871,-75.67567 76.351351,-75.67567 43.24324,0 76.35135,33.1081 76.35135,76.35135 0,43.24324 -33.78378,77.02702 -76.35135,77.02702 z"
|
||||
id="path4"
|
||||
inkscape:connector-curvature="0"
|
||||
style="fill:#ffffff;stroke-width:6.75675678" /><path
|
||||
class="st0"
|
||||
d="m 408.1081,223.64864 c 0,31.75676 3.37838,70.27027 34.45946,112.16216 0.67567,0.67567 2.02702,2.7027 2.02702,2.7027 -12.83783,21.62162 -28.37837,36.48648 -49.32432,36.48648 -16.21622,0 -32.43243,-8.78378 -45.94595,-23.64864 -14.18918,-16.21622 -20.94594,-37.16216 -20.94594,-59.45946 V 0 h 79.05405 z"
|
||||
id="path6"
|
||||
inkscape:connector-curvature="0"
|
||||
style="fill:#ffffff;stroke-width:6.75675678" /><polygon
|
||||
class="st1"
|
||||
points="117.9,33.9 104.1,13.5 118.3,13.5 132,33.9 118.3,54.2 104.1,54.2 "
|
||||
id="polygon8"
|
||||
style="fill:#ebaf00"
|
||||
transform="scale(6.7567568)" /><polygon
|
||||
class="st0"
|
||||
points="135.7,31.6 148,13.5 133.8,13.5 128.7,21 "
|
||||
id="polygon10"
|
||||
style="fill:#ffffff"
|
||||
transform="scale(6.7567568)" /><path
|
||||
class="st0"
|
||||
d="m 869.59458,316.2162 c 0,0 16.2162,22.2973 16.2162,22.2973 15.54058,24.32432 35.8108,36.48648 59.45949,36.48648 25,-0.67567 42.56752,-22.29729 49.3243,-30.4054 0,0 -12.16218,-10.81081 -27.7027,-29.05405 -20.94598,-24.32432 -48.64868,-68.91892 -49.3243,-70.94594 z"
|
||||
id="path12"
|
||||
inkscape:connector-curvature="0"
|
||||
style="fill:#ffffff;stroke-width:6.75675678" /><path
|
||||
style="fill:#ffffff;stroke-width:6.75675678"
|
||||
inkscape:connector-curvature="0"
|
||||
id="path16"
|
||||
d="m 632.43242,287.16215 c -16.21622,14.86486 -27.02703,22.97297 -49.32432,22.97297 -39.86487,0 -62.83784,-28.37837 -66.21622,-59.45945 h 211.4865 c 1.35131,-4.05406 2.027,-9.45946 2.027,-18.24324 0,-85.81082 -62.83783,-150 -145.27026,-150 -78.37837,0 -142.56756,65.54054 -142.56756,147.29729 0,81.08108 64.18919,145.27026 144.59459,145.27026 56.08108,0 104.72973,-31.75675 131.08105,-87.83783 z M 585.8108,147.29729 c 35.13513,0 61.48648,22.97297 67.56756,53.37838 H 519.59458 c 6.75676,-31.75676 31.75676,-53.37838 66.21622,-53.37838 z"
|
||||
class="st0" />
|
||||
.cls-2 {
|
||||
fill: #eaaf20;
|
||||
}
|
||||
</style>
|
||||
</defs>
|
||||
<!-- Generator: Adobe Illustrator 28.7.1, SVG Export Plug-In . SVG Version: 1.2.0 Build 142) -->
|
||||
<g>
|
||||
<g id="Layer_1">
|
||||
<path id="path4" class="cls-1"
|
||||
d="M60.6,28.8c-14.3,0-23.5,3.9-31.3,13v-10H1.6v123.7s.5.2,1.9.5c1.9.5,12.1,2.5,19.6-3.4,6.5-5.3,8-11.4,8-18.3v-17.8c8,8,17,11.4,29.6,11.4,27.2,0,48-20.8,48-48.4s-20.1-50.7-48.3-50.7h0ZM55.2,104.3c-15.3,0-27.4-11.9-27.4-26.3s14.3-25.6,27.4-25.6,27.4,11.2,27.4,25.8-12.1,26-27.4,26Z" />
|
||||
<path id="path6" class="cls-1"
|
||||
d="M148.1,76.5c0,10.7,1.2,23.7,12.4,37.9.2.2.7.9.7.9-4.6,7.3-10.2,12.3-17.7,12.3s-11.6-3-16.5-8c-5.1-5.5-7.5-12.6-7.5-20.1V.9h28.4l.2,75.6Z" />
|
||||
<polygon id="polygon8" class="cls-2"
|
||||
points="287.6 78.3 254.1 31.7 288.6 31.7 321.8 78.3 288.6 124.6 254.1 124.6 287.6 78.3" />
|
||||
<polygon id="polygon10" class="cls-1"
|
||||
points="330.8 73 360.6 31.7 326.2 31.7 313.8 48.9 330.8 73" />
|
||||
<path id="path12" class="cls-1"
|
||||
d="M313.8,107.7l5.8,7.5c5.6,8.2,12.9,12.3,21.3,12.3,9-.2,15.3-7.5,17.7-10.3,0,0-4.4-3.7-9.9-9.8-7.5-8.2-17.5-23.3-17.7-24l-17.2,24.2Z" />
|
||||
<path id="path16" class="cls-1"
|
||||
d="M228.7,97.9c-5.8,5-9.7,7.8-17.7,7.8-14.3,0-22.6-9.6-23.8-20.1h75.9c.5-1.4.7-3.2.7-6.2,0-29-22.6-50.7-52.2-50.7s-51.2,22.1-51.2,49.8,23,49.1,51.9,49.1,37.6-10.7,47.1-29.7h-30.8ZM211.9,50.7c12.6,0,22.1,7.8,24.3,18h-48c2.4-10.7,11.4-18,23.8-18h0Z" />
|
||||
<path id="path4-2" data-name="path4" class="cls-1"
|
||||
d="M59.3,28.2c-14.3,0-23.5,3.9-31.3,13v-10H.4v123.7s.5.2,1.9.5c1.9.5,12.1,2.5,19.6-3.4,6.5-5.3,8-11.4,8-18.3v-17.8c8,8,17,11.4,29.6,11.4,27.2,0,48-20.8,48-48.4s-20.1-50.7-48.3-50.7h0ZM54,103.8c-15.3,0-27.4-11.9-27.4-26.3s14.3-25.6,27.4-25.6,27.4,11.2,27.4,25.8-12.1,26-27.4,26Z" />
|
||||
<path id="path6-2" data-name="path6" class="cls-1"
|
||||
d="M146.9,75.9c0,10.7,1.2,23.7,12.4,37.9.2.2.7.9.7.9-4.6,7.3-10.2,12.3-17.7,12.3s-11.6-3-16.5-8c-5.1-5.5-7.5-12.6-7.5-20.1V.4h28.4l.2,75.6Z" />
|
||||
<polygon id="polygon8-2" data-name="polygon8" class="cls-2"
|
||||
points="286.4 77.8 252.9 31.2 287.3 31.2 320.6 77.8 287.3 124.1 252.9 124.1 286.4 77.8" />
|
||||
<polygon id="polygon10-2" data-name="polygon10" class="cls-1"
|
||||
points="329.5 72.5 359.4 31.2 324.9 31.2 312.6 48.3 329.5 72.5" />
|
||||
<path id="path12-2" data-name="path12" class="cls-1"
|
||||
d="M312.6,107.2l5.8,7.5c5.6,8.2,12.9,12.3,21.3,12.3,9-.2,15.3-7.5,17.7-10.3,0,0-4.4-3.7-9.9-9.8-7.5-8.2-17.5-23.3-17.7-24l-17.2,24.2Z" />
|
||||
<path id="path16-2" data-name="path16" class="cls-1"
|
||||
d="M227.4,97.4c-5.8,5-9.7,7.8-17.7,7.8-14.3,0-22.6-9.6-23.8-20.1h75.9c.5-1.4.7-3.2.7-6.2,0-29-22.6-50.7-52.2-50.7s-51.2,22.1-51.2,49.8,23,49.1,51.9,49.1,37.6-10.7,47.1-29.7h-30.8ZM210.7,50.1c12.6,0,22.1,7.8,24.3,18h-48c2.4-10.7,11.4-18,23.8-18h0Z" />
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 4.2 KiB After Width: | Height: | Size: 3.0 KiB |
@@ -25,6 +25,10 @@ const SensitiveInput = ({ as = 'input', ...props }: SensitiveInputProps) => {
|
||||
return (
|
||||
<>
|
||||
<Component
|
||||
autoComplete="off"
|
||||
data-1pignore="true"
|
||||
data-lpignore="true"
|
||||
data-bwignore="true"
|
||||
{...componentProps}
|
||||
className={`rounded-l-only ${componentProps.className ?? ''}`}
|
||||
type={
|
||||
|
||||
@@ -33,7 +33,7 @@ import { useRouter } from 'next/router';
|
||||
import { useState } from 'react';
|
||||
import { FormattedRelativeTime, useIntl } from 'react-intl';
|
||||
import { useToasts } from 'react-toast-notifications';
|
||||
import useSWR from 'swr';
|
||||
import useSWR, { mutate } from 'swr';
|
||||
import * as Yup from 'yup';
|
||||
|
||||
const messages = defineMessages('components.IssueDetails', {
|
||||
@@ -155,6 +155,7 @@ const IssueDetails = () => {
|
||||
autoDismiss: true,
|
||||
});
|
||||
revalidateIssue();
|
||||
mutate('/api/v1/issue/count');
|
||||
} catch (e) {
|
||||
addToast(intl.formatMessage(messages.toaststatusupdatefailed), {
|
||||
appearance: 'error',
|
||||
@@ -169,6 +170,7 @@ const IssueDetails = () => {
|
||||
method: 'DELETE',
|
||||
});
|
||||
if (!res.ok) throw new Error();
|
||||
mutate('/api/v1/issue/count');
|
||||
|
||||
addToast(intl.formatMessage(messages.toastissuedeleted), {
|
||||
appearance: 'success',
|
||||
|
||||
@@ -15,7 +15,7 @@ import { Field, Formik } from 'formik';
|
||||
import Link from 'next/link';
|
||||
import { useIntl } from 'react-intl';
|
||||
import { useToasts } from 'react-toast-notifications';
|
||||
import useSWR from 'swr';
|
||||
import useSWR, { mutate } from 'swr';
|
||||
import * as Yup from 'yup';
|
||||
|
||||
const messages = defineMessages('components.IssueModal.CreateIssueModal', {
|
||||
@@ -138,6 +138,8 @@ const CreateIssueModal = ({
|
||||
autoDismiss: true,
|
||||
}
|
||||
);
|
||||
|
||||
mutate('/api/v1/issue/count');
|
||||
}
|
||||
|
||||
if (onCancel) {
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import Badge from '@app/components/Common/Badge';
|
||||
import { menuMessages } from '@app/components/Layout/Sidebar';
|
||||
import useClickOutside from '@app/hooks/useClickOutside';
|
||||
import { Permission, useUser } from '@app/hooks/useUser';
|
||||
@@ -26,9 +27,16 @@ import {
|
||||
} from '@heroicons/react/24/solid';
|
||||
import Link from 'next/link';
|
||||
import { useRouter } from 'next/router';
|
||||
import { cloneElement, useRef, useState } from 'react';
|
||||
import { cloneElement, useEffect, useRef, useState } from 'react';
|
||||
import { useIntl } from 'react-intl';
|
||||
|
||||
interface MobileMenuProps {
|
||||
pendingRequestsCount: number;
|
||||
openIssuesCount: number;
|
||||
revalidateIssueCount: () => void;
|
||||
revalidateRequestsCount: () => void;
|
||||
}
|
||||
|
||||
interface MenuLink {
|
||||
href: string;
|
||||
svgIcon: JSX.Element;
|
||||
@@ -41,7 +49,12 @@ interface MenuLink {
|
||||
dataTestId?: string;
|
||||
}
|
||||
|
||||
const MobileMenu = () => {
|
||||
const MobileMenu = ({
|
||||
pendingRequestsCount,
|
||||
openIssuesCount,
|
||||
revalidateIssueCount,
|
||||
revalidateRequestsCount,
|
||||
}: MobileMenuProps) => {
|
||||
const ref = useRef<HTMLDivElement>(null);
|
||||
const intl = useIntl();
|
||||
const [isOpen, setIsOpen] = useState(false);
|
||||
@@ -139,6 +152,21 @@ const MobileMenu = () => {
|
||||
})
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
if (openIssuesCount) {
|
||||
revalidateIssueCount();
|
||||
}
|
||||
|
||||
if (pendingRequestsCount) {
|
||||
revalidateRequestsCount();
|
||||
}
|
||||
}, [
|
||||
revalidateIssueCount,
|
||||
revalidateRequestsCount,
|
||||
pendingRequestsCount,
|
||||
openIssuesCount,
|
||||
]);
|
||||
|
||||
return (
|
||||
<div className="fixed bottom-0 left-0 right-0 z-50">
|
||||
<Transition
|
||||
@@ -159,7 +187,7 @@ const MobileMenu = () => {
|
||||
<Link
|
||||
key={`mobile-menu-link-${link.href}`}
|
||||
href={link.href}
|
||||
className={`flex items-center space-x-2 ${
|
||||
className={`flex items-center ${
|
||||
isActive ? 'text-indigo-500' : ''
|
||||
}`}
|
||||
onKeyDown={(e) => {
|
||||
@@ -174,7 +202,25 @@ const MobileMenu = () => {
|
||||
{cloneElement(isActive ? link.svgIconSelected : link.svgIcon, {
|
||||
className: 'h-5 w-5',
|
||||
})}
|
||||
<span>{link.content}</span>
|
||||
<span className="ml-2">{link.content}</span>
|
||||
{link.href === '/requests' &&
|
||||
pendingRequestsCount > 0 &&
|
||||
hasPermission(Permission.MANAGE_REQUESTS) && (
|
||||
<div className="ml-auto flex">
|
||||
<Badge className="rounded-md border-indigo-500 bg-gradient-to-br from-indigo-600 to-purple-600">
|
||||
{pendingRequestsCount}
|
||||
</Badge>
|
||||
</div>
|
||||
)}
|
||||
{link.href === '/issues' &&
|
||||
openIssuesCount > 0 &&
|
||||
hasPermission(Permission.MANAGE_ISSUES) && (
|
||||
<div className="ml-auto flex">
|
||||
<Badge className="rounded-md border-indigo-500 bg-gradient-to-br from-indigo-600 to-purple-600">
|
||||
{openIssuesCount}
|
||||
</Badge>
|
||||
</div>
|
||||
)}
|
||||
</Link>
|
||||
);
|
||||
})}
|
||||
@@ -190,7 +236,7 @@ const MobileMenu = () => {
|
||||
<Link
|
||||
key={`mobile-menu-link-${link.href}`}
|
||||
href={link.href}
|
||||
className={`flex flex-col items-center space-y-1 ${
|
||||
className={`relative flex flex-col items-center space-y-1 ${
|
||||
isActive ? 'text-indigo-500' : ''
|
||||
}`}
|
||||
>
|
||||
@@ -200,6 +246,23 @@ const MobileMenu = () => {
|
||||
className: 'h-6 w-6',
|
||||
}
|
||||
)}
|
||||
{link.href === '/requests' &&
|
||||
pendingRequestsCount > 0 &&
|
||||
hasPermission(Permission.MANAGE_REQUESTS) && (
|
||||
<div className="absolute left-3 bottom-3">
|
||||
<Badge
|
||||
className={`bg-gradient-to-br ${
|
||||
router.pathname.match(link.activeRegExp)
|
||||
? 'border-indigo-600 from-indigo-700 to-purple-700'
|
||||
: 'border-indigo-500 from-indigo-600 to-purple-600'
|
||||
} flex h-4 w-4 items-center justify-center !px-[9px] !py-[9px] text-[9px]`}
|
||||
>
|
||||
{pendingRequestsCount > 99
|
||||
? '99+'
|
||||
: pendingRequestsCount}
|
||||
</Badge>
|
||||
</div>
|
||||
)}
|
||||
</Link>
|
||||
);
|
||||
})}
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import Badge from '@app/components/Common/Badge';
|
||||
import UserWarnings from '@app/components/Layout/UserWarnings';
|
||||
import VersionStatus from '@app/components/Layout/VersionStatus';
|
||||
import useClickOutside from '@app/hooks/useClickOutside';
|
||||
@@ -18,7 +19,7 @@ import {
|
||||
import Image from 'next/image';
|
||||
import Link from 'next/link';
|
||||
import { useRouter } from 'next/router';
|
||||
import { Fragment, useRef } from 'react';
|
||||
import { Fragment, useEffect, useRef } from 'react';
|
||||
import { useIntl } from 'react-intl';
|
||||
|
||||
export const menuMessages = defineMessages('components.Layout.Sidebar', {
|
||||
@@ -35,6 +36,10 @@ export const menuMessages = defineMessages('components.Layout.Sidebar', {
|
||||
interface SidebarProps {
|
||||
open?: boolean;
|
||||
setClosed: () => void;
|
||||
pendingRequestsCount: number;
|
||||
openIssuesCount: number;
|
||||
revalidateIssueCount: () => void;
|
||||
revalidateRequestsCount: () => void;
|
||||
}
|
||||
|
||||
interface SidebarLinkProps {
|
||||
@@ -114,13 +119,35 @@ const SidebarLinks: SidebarLinkProps[] = [
|
||||
},
|
||||
];
|
||||
|
||||
const Sidebar = ({ open, setClosed }: SidebarProps) => {
|
||||
const Sidebar = ({
|
||||
open,
|
||||
setClosed,
|
||||
pendingRequestsCount,
|
||||
openIssuesCount,
|
||||
revalidateIssueCount,
|
||||
revalidateRequestsCount,
|
||||
}: SidebarProps) => {
|
||||
const navRef = useRef<HTMLDivElement>(null);
|
||||
const router = useRouter();
|
||||
const intl = useIntl();
|
||||
const { hasPermission } = useUser();
|
||||
useClickOutside(navRef, () => setClosed());
|
||||
|
||||
useEffect(() => {
|
||||
if (openIssuesCount) {
|
||||
revalidateIssueCount();
|
||||
}
|
||||
|
||||
if (pendingRequestsCount) {
|
||||
revalidateRequestsCount();
|
||||
}
|
||||
}, [
|
||||
revalidateIssueCount,
|
||||
revalidateRequestsCount,
|
||||
pendingRequestsCount,
|
||||
openIssuesCount,
|
||||
]);
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className="lg:hidden">
|
||||
@@ -253,18 +280,48 @@ const Sidebar = ({ open, setClosed }: SidebarProps) => {
|
||||
href={sidebarLink.href}
|
||||
as={sidebarLink.as}
|
||||
className={`group flex items-center rounded-md px-2 py-2 text-lg font-medium leading-6 text-white transition duration-150 ease-in-out focus:outline-none
|
||||
${
|
||||
router.pathname.match(sidebarLink.activeRegExp)
|
||||
? 'bg-gradient-to-br from-indigo-600 to-purple-600 hover:from-indigo-500 hover:to-purple-500'
|
||||
: 'hover:bg-gray-700 focus:bg-gray-700'
|
||||
}
|
||||
`}
|
||||
${
|
||||
router.pathname.match(sidebarLink.activeRegExp)
|
||||
? 'bg-gradient-to-br from-indigo-600 to-purple-600 hover:from-indigo-500 hover:to-purple-500'
|
||||
: 'hover:bg-gray-700 focus:bg-gray-700'
|
||||
}
|
||||
`}
|
||||
data-testid={sidebarLink.dataTestId}
|
||||
>
|
||||
{sidebarLink.svgIcon}
|
||||
{intl.formatMessage(
|
||||
menuMessages[sidebarLink.messagesKey]
|
||||
)}
|
||||
{sidebarLink.messagesKey === 'requests' &&
|
||||
pendingRequestsCount > 0 &&
|
||||
hasPermission(Permission.MANAGE_REQUESTS) && (
|
||||
<div className="ml-auto flex">
|
||||
<Badge
|
||||
className={`rounded-md bg-gradient-to-br ${
|
||||
router.pathname.match(sidebarLink.activeRegExp)
|
||||
? 'border-indigo-600 from-indigo-700 to-purple-700'
|
||||
: 'border-indigo-500 from-indigo-600 to-purple-600'
|
||||
}`}
|
||||
>
|
||||
{pendingRequestsCount}
|
||||
</Badge>
|
||||
</div>
|
||||
)}
|
||||
{sidebarLink.messagesKey === 'issues' &&
|
||||
openIssuesCount > 0 &&
|
||||
hasPermission(Permission.MANAGE_ISSUES) && (
|
||||
<div className="ml-auto flex">
|
||||
<Badge
|
||||
className={`rounded-md bg-gradient-to-br ${
|
||||
router.pathname.match(sidebarLink.activeRegExp)
|
||||
? 'border-indigo-600 from-indigo-700 to-purple-700'
|
||||
: 'border-indigo-500 from-indigo-600 to-purple-600'
|
||||
}`}
|
||||
>
|
||||
{openIssuesCount}
|
||||
</Badge>
|
||||
</div>
|
||||
)}
|
||||
</Link>
|
||||
);
|
||||
})}
|
||||
|
||||
@@ -10,6 +10,7 @@ import { useUser } from '@app/hooks/useUser';
|
||||
import { ArrowLeftIcon, Bars3BottomLeftIcon } from '@heroicons/react/24/solid';
|
||||
import { useRouter } from 'next/router';
|
||||
import { useEffect, useState } from 'react';
|
||||
import useSWR from 'swr';
|
||||
|
||||
type LayoutProps = {
|
||||
children: React.ReactNode;
|
||||
@@ -22,6 +23,18 @@ const Layout = ({ children }: LayoutProps) => {
|
||||
const router = useRouter();
|
||||
const { currentSettings } = useSettings();
|
||||
const { setLocale } = useLocale();
|
||||
const { data: requestResponse, mutate: revalidateRequestsCount } = useSWR(
|
||||
'/api/v1/request/count',
|
||||
{
|
||||
revalidateOnMount: true,
|
||||
}
|
||||
);
|
||||
const { data: issueResponse, mutate: revalidateIssueCount } = useSWR(
|
||||
'/api/v1/issue/count',
|
||||
{
|
||||
revalidateOnMount: true,
|
||||
}
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
if (setLocale && user) {
|
||||
@@ -55,10 +68,21 @@ const Layout = ({ children }: LayoutProps) => {
|
||||
<div className="absolute top-0 h-64 w-full bg-gradient-to-bl from-gray-800 to-gray-900">
|
||||
<div className="relative inset-0 h-full w-full bg-gradient-to-t from-gray-900 to-transparent" />
|
||||
</div>
|
||||
|
||||
<Sidebar open={isSidebarOpen} setClosed={() => setSidebarOpen(false)} />
|
||||
<Sidebar
|
||||
open={isSidebarOpen}
|
||||
setClosed={() => setSidebarOpen(false)}
|
||||
pendingRequestsCount={requestResponse?.pending ?? 0}
|
||||
openIssuesCount={issueResponse?.open ?? 0}
|
||||
revalidateIssueCount={() => revalidateIssueCount()}
|
||||
revalidateRequestsCount={() => revalidateRequestsCount()}
|
||||
/>
|
||||
<div className="sm:hidden">
|
||||
<MobileMenu />
|
||||
<MobileMenu
|
||||
pendingRequestsCount={requestResponse?.pending ?? 0}
|
||||
openIssuesCount={issueResponse?.open ?? 0}
|
||||
revalidateIssueCount={() => revalidateIssueCount()}
|
||||
revalidateRequestsCount={() => revalidateRequestsCount()}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="relative mb-16 flex w-0 min-w-0 flex-1 flex-col lg:ml-64">
|
||||
|
||||
@@ -114,6 +114,9 @@ const LocalLogin = ({ revalidate }: LocalLoginProps) => {
|
||||
autoComplete="current-password"
|
||||
data-testid="password"
|
||||
className="!bg-gray-700/80 placeholder:text-gray-400"
|
||||
data-1pignore="false"
|
||||
data-lpignore="false"
|
||||
data-bwignore="false"
|
||||
/>
|
||||
</div>
|
||||
<div className="flex">
|
||||
|
||||
@@ -20,6 +20,7 @@ import type { MediaRequest } from '@server/entity/MediaRequest';
|
||||
import Link from 'next/link';
|
||||
import { useState } from 'react';
|
||||
import { useIntl } from 'react-intl';
|
||||
import { mutate } from 'swr';
|
||||
|
||||
const messages = defineMessages('components.RequestBlock', {
|
||||
seasons: '{seasonCount, plural, one {Season} other {Seasons}}',
|
||||
@@ -59,6 +60,7 @@ const RequestBlock = ({ request, onUpdate }: RequestBlockProps) => {
|
||||
|
||||
if (onUpdate) {
|
||||
onUpdate();
|
||||
mutate('/api/v1/request/count');
|
||||
}
|
||||
setIsUpdating(false);
|
||||
};
|
||||
@@ -72,6 +74,7 @@ const RequestBlock = ({ request, onUpdate }: RequestBlockProps) => {
|
||||
|
||||
if (onUpdate) {
|
||||
onUpdate();
|
||||
mutate('/api/v1/request/count');
|
||||
}
|
||||
|
||||
setIsUpdating(false);
|
||||
|
||||
@@ -15,6 +15,7 @@ import type Media from '@server/entity/Media';
|
||||
import type { MediaRequest } from '@server/entity/MediaRequest';
|
||||
import { useMemo, useState } from 'react';
|
||||
import { useIntl } from 'react-intl';
|
||||
import { mutate } from 'swr';
|
||||
|
||||
const messages = defineMessages('components.RequestButton', {
|
||||
viewrequest: 'View Request',
|
||||
@@ -101,6 +102,7 @@ const RequestButton = ({
|
||||
|
||||
if (data) {
|
||||
onUpdate();
|
||||
mutate('/api/v1/request/count');
|
||||
}
|
||||
};
|
||||
|
||||
@@ -123,6 +125,7 @@ const RequestButton = ({
|
||||
);
|
||||
|
||||
onUpdate();
|
||||
mutate('/api/v1/request/count');
|
||||
};
|
||||
|
||||
const buttons: ButtonOption[] = [];
|
||||
|
||||
@@ -80,6 +80,7 @@ const RequestCardError = ({ requestData }: RequestCardErrorProps) => {
|
||||
if (!res.ok) throw new Error();
|
||||
mutate('/api/v1/media?filter=allavailable&take=20&sort=mediaAdded');
|
||||
mutate('/api/v1/request?filter=all&take=10&sort=modified&skip=0');
|
||||
mutate('/api/v1/request/count');
|
||||
};
|
||||
|
||||
return (
|
||||
@@ -271,6 +272,7 @@ const RequestCard = ({ request, onTitleData }: RequestCardProps) => {
|
||||
|
||||
if (data) {
|
||||
revalidate();
|
||||
mutate('/api/v1/request/count');
|
||||
}
|
||||
};
|
||||
|
||||
@@ -280,6 +282,7 @@ const RequestCard = ({ request, onTitleData }: RequestCardProps) => {
|
||||
});
|
||||
if (!res.ok) throw new Error();
|
||||
mutate('/api/v1/request?filter=all&take=10&sort=modified&skip=0');
|
||||
mutate('/api/v1/request/count');
|
||||
};
|
||||
|
||||
const retryRequest = async () => {
|
||||
|
||||
@@ -27,7 +27,7 @@ import { useState } from 'react';
|
||||
import { useInView } from 'react-intersection-observer';
|
||||
import { FormattedRelativeTime, useIntl } from 'react-intl';
|
||||
import { useToasts } from 'react-toast-notifications';
|
||||
import useSWR from 'swr';
|
||||
import useSWR, { mutate } from 'swr';
|
||||
|
||||
const messages = defineMessages('components.RequestList.RequestItem', {
|
||||
seasons: '{seasonCount, plural, one {Season} other {Seasons}}',
|
||||
@@ -69,6 +69,7 @@ const RequestItemError = ({
|
||||
});
|
||||
if (!res.ok) throw new Error();
|
||||
revalidateList();
|
||||
mutate('/api/v1/request/count');
|
||||
};
|
||||
|
||||
const { mediaUrl: plexUrl, mediaUrl4k: plexUrl4k } = useDeepLinks({
|
||||
@@ -334,6 +335,7 @@ const RequestItem = ({ request, revalidateList }: RequestItemProps) => {
|
||||
|
||||
if (data) {
|
||||
revalidate();
|
||||
mutate('/api/v1/request/count');
|
||||
}
|
||||
};
|
||||
|
||||
@@ -344,6 +346,7 @@ const RequestItem = ({ request, revalidateList }: RequestItemProps) => {
|
||||
if (!res.ok) throw new Error();
|
||||
|
||||
revalidateList();
|
||||
mutate('/api/v1/request/count');
|
||||
};
|
||||
|
||||
const deleteMediaFile = async () => {
|
||||
|
||||
@@ -16,7 +16,7 @@ import type { Collection } from '@server/models/Collection';
|
||||
import { useCallback, useEffect, useState } from 'react';
|
||||
import { useIntl } from 'react-intl';
|
||||
import { useToasts } from 'react-toast-notifications';
|
||||
import useSWR from 'swr';
|
||||
import useSWR, { mutate } from 'swr';
|
||||
|
||||
const messages = defineMessages('components.RequestModal', {
|
||||
requestadmin: 'This request will be approved automatically.',
|
||||
@@ -220,6 +220,7 @@ const CollectionRequestModal = ({
|
||||
? MediaStatus.UNKNOWN
|
||||
: MediaStatus.PARTIALLY_AVAILABLE
|
||||
);
|
||||
mutate('/api/v1/request/count');
|
||||
}
|
||||
|
||||
addToast(
|
||||
@@ -239,7 +240,16 @@ const CollectionRequestModal = ({
|
||||
} finally {
|
||||
setIsUpdating(false);
|
||||
}
|
||||
}, [requestOverrides, data, onComplete, addToast, intl, selectedParts, is4k]);
|
||||
}, [
|
||||
requestOverrides,
|
||||
data?.parts,
|
||||
data?.name,
|
||||
onComplete,
|
||||
addToast,
|
||||
intl,
|
||||
selectedParts,
|
||||
is4k,
|
||||
]);
|
||||
|
||||
const hasAutoApprove = hasPermission(
|
||||
[
|
||||
|
||||
@@ -104,6 +104,7 @@ const MovieRequestModal = ({
|
||||
if (!res.ok) throw new Error();
|
||||
const mediaRequest: MediaRequest = await res.json();
|
||||
mutate('/api/v1/request?filter=all&take=10&sort=modified&skip=0');
|
||||
mutate('/api/v1/request/count');
|
||||
|
||||
if (mediaRequest) {
|
||||
if (onComplete) {
|
||||
@@ -138,7 +139,16 @@ const MovieRequestModal = ({
|
||||
} finally {
|
||||
setIsUpdating(false);
|
||||
}
|
||||
}, [data, onComplete, addToast, requestOverrides, hasPermission, intl, is4k]);
|
||||
}, [
|
||||
requestOverrides,
|
||||
data?.id,
|
||||
data?.title,
|
||||
is4k,
|
||||
onComplete,
|
||||
addToast,
|
||||
intl,
|
||||
hasPermission,
|
||||
]);
|
||||
|
||||
const cancelRequest = async () => {
|
||||
setIsUpdating(true);
|
||||
@@ -150,6 +160,7 @@ const MovieRequestModal = ({
|
||||
if (!res.ok) throw new Error();
|
||||
|
||||
mutate('/api/v1/request?filter=all&take=10&sort=modified&skip=0');
|
||||
mutate('/api/v1/request/count');
|
||||
|
||||
if (res.status === 204) {
|
||||
if (onComplete) {
|
||||
@@ -197,6 +208,7 @@ const MovieRequestModal = ({
|
||||
if (!res.ok) throw new Error();
|
||||
}
|
||||
mutate('/api/v1/request?filter=all&take=10&sort=modified&skip=0');
|
||||
mutate('/api/v1/request/count');
|
||||
|
||||
addToast(
|
||||
<span>
|
||||
|
||||
@@ -106,6 +106,7 @@ const TvRequestModal = ({
|
||||
|
||||
if (onUpdating) {
|
||||
onUpdating(true);
|
||||
mutate('/api/v1/request/count');
|
||||
}
|
||||
|
||||
try {
|
||||
@@ -141,6 +142,7 @@ const TvRequestModal = ({
|
||||
if (!res.ok) throw new Error();
|
||||
}
|
||||
mutate('/api/v1/request?filter=all&take=10&sort=modified&skip=0');
|
||||
mutate('/api/v1/request/count');
|
||||
|
||||
addToast(
|
||||
<span>
|
||||
@@ -189,6 +191,7 @@ const TvRequestModal = ({
|
||||
|
||||
if (onUpdating) {
|
||||
onUpdating(true);
|
||||
mutate('/api/v1/request/count');
|
||||
}
|
||||
|
||||
try {
|
||||
|
||||
@@ -238,6 +238,10 @@ const NotificationsDiscord = () => {
|
||||
name="botUsername"
|
||||
type="text"
|
||||
placeholder={settings.currentSettings.applicationTitle}
|
||||
autoComplete="off"
|
||||
data-1pignore="true"
|
||||
data-lpignore="true"
|
||||
data-bwignore="true"
|
||||
/>
|
||||
</div>
|
||||
{errors.botUsername &&
|
||||
|
||||
@@ -104,7 +104,7 @@ const NotificationsEmail = () => {
|
||||
otherwise: Yup.string().nullable(),
|
||||
})
|
||||
.matches(
|
||||
/-----BEGIN PGP PRIVATE KEY BLOCK-----.+-----END PGP PRIVATE KEY BLOCK-----/s,
|
||||
/-----BEGIN PGP PRIVATE KEY BLOCK-----.+-----END PGP PRIVATE KEY BLOCK-----/,
|
||||
intl.formatMessage(messages.validationPgpPrivateKey)
|
||||
),
|
||||
pgpPassword: Yup.string().when('pgpPrivateKey', {
|
||||
@@ -295,6 +295,10 @@ const NotificationsEmail = () => {
|
||||
name="emailFrom"
|
||||
type="text"
|
||||
inputMode="email"
|
||||
autoComplete="off"
|
||||
data-1pignore="true"
|
||||
data-lpignore="true"
|
||||
data-bwignore="true"
|
||||
/>
|
||||
</div>
|
||||
{errors.emailFrom &&
|
||||
@@ -316,6 +320,10 @@ const NotificationsEmail = () => {
|
||||
name="smtpHost"
|
||||
type="text"
|
||||
inputMode="url"
|
||||
autoComplete="off"
|
||||
data-1pignore="true"
|
||||
data-lpignore="true"
|
||||
data-bwignore="true"
|
||||
/>
|
||||
</div>
|
||||
{errors.smtpHost &&
|
||||
@@ -337,6 +345,10 @@ const NotificationsEmail = () => {
|
||||
type="text"
|
||||
inputMode="numeric"
|
||||
className="short"
|
||||
autoComplete="off"
|
||||
data-1pignore="true"
|
||||
data-lpignore="true"
|
||||
data-bwignore="true"
|
||||
/>
|
||||
{errors.smtpPort &&
|
||||
touched.smtpPort &&
|
||||
@@ -390,7 +402,15 @@ const NotificationsEmail = () => {
|
||||
</label>
|
||||
<div className="form-input-area">
|
||||
<div className="form-input-field">
|
||||
<Field id="authUser" name="authUser" type="text" />
|
||||
<Field
|
||||
id="authUser"
|
||||
name="authUser"
|
||||
type="text"
|
||||
autoComplete="off"
|
||||
data-1pignore="true"
|
||||
data-lpignore="true"
|
||||
data-bwignore="true"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -400,12 +420,7 @@ const NotificationsEmail = () => {
|
||||
</label>
|
||||
<div className="form-input-area">
|
||||
<div className="form-input-field">
|
||||
<SensitiveInput
|
||||
as="field"
|
||||
id="authPass"
|
||||
name="authPass"
|
||||
autoComplete="one-time-code"
|
||||
/>
|
||||
<SensitiveInput as="field" id="authPass" name="authPass" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -430,6 +445,10 @@ const NotificationsEmail = () => {
|
||||
type="textarea"
|
||||
rows="10"
|
||||
className="font-mono text-xs"
|
||||
autoComplete="off"
|
||||
data-1pignore="true"
|
||||
data-lpignore="true"
|
||||
data-bwignore="true"
|
||||
/>
|
||||
</div>
|
||||
{errors.pgpPrivateKey &&
|
||||
@@ -457,7 +476,10 @@ const NotificationsEmail = () => {
|
||||
as="field"
|
||||
id="pgpPassword"
|
||||
name="pgpPassword"
|
||||
autoComplete="one-time-code"
|
||||
autoComplete="off"
|
||||
data-1pignore="true"
|
||||
data-lpignore="true"
|
||||
data-bwignore="true"
|
||||
/>
|
||||
</div>
|
||||
{errors.pgpPassword &&
|
||||
|
||||
@@ -245,7 +245,7 @@ const NotificationsTelegram = () => {
|
||||
as="field"
|
||||
id="botAPI"
|
||||
name="botAPI"
|
||||
autoComplete="one-time-code"
|
||||
type="text"
|
||||
/>
|
||||
</div>
|
||||
{errors.botAPI &&
|
||||
@@ -264,7 +264,15 @@ const NotificationsTelegram = () => {
|
||||
</label>
|
||||
<div className="form-input-area">
|
||||
<div className="form-input-field">
|
||||
<Field id="botUsername" name="botUsername" type="text" />
|
||||
<Field
|
||||
id="botUsername"
|
||||
name="botUsername"
|
||||
type="text"
|
||||
autoComplete="off"
|
||||
data-1pignore="true"
|
||||
data-lpignore="true"
|
||||
data-bwignore="true"
|
||||
/>
|
||||
</div>
|
||||
{errors.botUsername &&
|
||||
touched.botUsername &&
|
||||
@@ -294,7 +302,15 @@ const NotificationsTelegram = () => {
|
||||
</label>
|
||||
<div className="form-input-area">
|
||||
<div className="form-input-field">
|
||||
<Field id="chatId" name="chatId" type="text" />
|
||||
<Field
|
||||
id="chatId"
|
||||
name="chatId"
|
||||
type="text"
|
||||
autoComplete="off"
|
||||
data-1pignore="true"
|
||||
data-lpignore="true"
|
||||
data-bwignore="true"
|
||||
/>
|
||||
</div>
|
||||
{errors.chatId &&
|
||||
touched.chatId &&
|
||||
|
||||
@@ -382,6 +382,10 @@ const RadarrModal = ({ onClose, radarr, onSave }: RadarrModalProps) => {
|
||||
id="name"
|
||||
name="name"
|
||||
type="text"
|
||||
autoComplete="off"
|
||||
data-1pignore="true"
|
||||
data-lpignore="true"
|
||||
data-bwignore="true"
|
||||
onChange={(e: React.ChangeEvent<HTMLInputElement>) => {
|
||||
setIsValidated(false);
|
||||
setFieldValue('name', e.target.value);
|
||||
@@ -475,7 +479,6 @@ const RadarrModal = ({ onClose, radarr, onSave }: RadarrModalProps) => {
|
||||
as="field"
|
||||
id="apiKey"
|
||||
name="apiKey"
|
||||
autoComplete="one-time-code"
|
||||
onChange={(e: React.ChangeEvent<HTMLInputElement>) => {
|
||||
setIsValidated(false);
|
||||
setFieldValue('apiKey', e.target.value);
|
||||
|
||||
@@ -872,6 +872,10 @@ const SettingsPlex = ({ onComplete }: SettingsPlexProps) => {
|
||||
id="tautulliPort"
|
||||
name="tautulliPort"
|
||||
className="short"
|
||||
autoComplete="off"
|
||||
data-1pignore="true"
|
||||
data-lpignore="true"
|
||||
data-bwignore="true"
|
||||
/>
|
||||
{errors.tautulliPort &&
|
||||
touched.tautulliPort &&
|
||||
@@ -909,6 +913,10 @@ const SettingsPlex = ({ onComplete }: SettingsPlexProps) => {
|
||||
inputMode="url"
|
||||
id="tautulliUrlBase"
|
||||
name="tautulliUrlBase"
|
||||
autoComplete="off"
|
||||
data-1pignore="true"
|
||||
data-lpignore="true"
|
||||
data-bwignore="true"
|
||||
/>
|
||||
</div>
|
||||
{errors.tautulliUrlBase &&
|
||||
@@ -929,7 +937,6 @@ const SettingsPlex = ({ onComplete }: SettingsPlexProps) => {
|
||||
as="field"
|
||||
id="tautulliApiKey"
|
||||
name="tautulliApiKey"
|
||||
autoComplete="one-time-code"
|
||||
/>
|
||||
</div>
|
||||
{errors.tautulliApiKey &&
|
||||
@@ -950,6 +957,10 @@ const SettingsPlex = ({ onComplete }: SettingsPlexProps) => {
|
||||
inputMode="url"
|
||||
id="tautulliExternalUrl"
|
||||
name="tautulliExternalUrl"
|
||||
autoComplete="off"
|
||||
data-1pignore="true"
|
||||
data-lpignore="true"
|
||||
data-bwignore="true"
|
||||
/>
|
||||
</div>
|
||||
{errors.tautulliExternalUrl &&
|
||||
|
||||
@@ -119,6 +119,8 @@ const ServerInstance = ({
|
||||
<h3 className="truncate font-medium leading-5 text-white">
|
||||
<a
|
||||
href={serviceUrl}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className="transition duration-300 hover:text-white hover:underline"
|
||||
>
|
||||
{name}
|
||||
@@ -147,6 +149,8 @@ const ServerInstance = ({
|
||||
</span>
|
||||
<a
|
||||
href={internalUrl}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className="transition duration-300 hover:text-white hover:underline"
|
||||
>
|
||||
{internalUrl}
|
||||
@@ -159,7 +163,12 @@ const ServerInstance = ({
|
||||
{profileName}
|
||||
</p>
|
||||
</div>
|
||||
<a href={serviceUrl} className="opacity-50 hover:opacity-100">
|
||||
<a
|
||||
href={serviceUrl}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className="opacity-50 hover:opacity-100"
|
||||
>
|
||||
{isSonarr ? (
|
||||
<SonarrLogo className="h-10 w-10 flex-shrink-0" />
|
||||
) : (
|
||||
|
||||
@@ -415,6 +415,10 @@ const SonarrModal = ({ onClose, sonarr, onSave }: SonarrModalProps) => {
|
||||
id="name"
|
||||
name="name"
|
||||
type="text"
|
||||
autoComplete="off"
|
||||
data-1pignore="true"
|
||||
data-lpignore="true"
|
||||
data-bwignore="true"
|
||||
onChange={(e: React.ChangeEvent<HTMLInputElement>) => {
|
||||
setIsValidated(false);
|
||||
setFieldValue('name', e.target.value);
|
||||
@@ -508,7 +512,6 @@ const SonarrModal = ({ onClose, sonarr, onSave }: SonarrModalProps) => {
|
||||
as="field"
|
||||
id="apiKey"
|
||||
name="apiKey"
|
||||
autoComplete="one-time-code"
|
||||
onChange={(e: React.ChangeEvent<HTMLInputElement>) => {
|
||||
setIsValidated(false);
|
||||
setFieldValue('apiKey', e.target.value);
|
||||
|
||||
@@ -399,6 +399,10 @@ const UserList = () => {
|
||||
name="email"
|
||||
type="text"
|
||||
inputMode="email"
|
||||
autoComplete="off"
|
||||
data-1pignore="true"
|
||||
data-lpignore="true"
|
||||
data-bwignore="true"
|
||||
/>
|
||||
</div>
|
||||
{errors.email &&
|
||||
|
||||
@@ -11,6 +11,7 @@ import { LanguageContext } from '@app/context/LanguageContext';
|
||||
import { SettingsProvider } from '@app/context/SettingsContext';
|
||||
import { UserContext } from '@app/context/UserContext';
|
||||
import type { User } from '@app/hooks/useUser';
|
||||
import { Permission, useUser } from '@app/hooks/useUser';
|
||||
import '@app/styles/globals.css';
|
||||
import '@app/utils/fetchOverride';
|
||||
import { polyfillIntl } from '@app/utils/polyfillIntl';
|
||||
@@ -128,6 +129,35 @@ const CoreApp: Omit<NextAppComponentType, 'origGetInitialProps'> = ({
|
||||
loadLocaleData(currentLocale).then(setMessages);
|
||||
}, [currentLocale]);
|
||||
|
||||
const { hasPermission } = useUser();
|
||||
|
||||
useEffect(() => {
|
||||
const requestsCount = async () => {
|
||||
const response = await fetch('/api/v1/request/count');
|
||||
return await response.json();
|
||||
};
|
||||
|
||||
// Cast navigator to a type that includes setAppBadge and clearAppBadge
|
||||
// to avoid TypeScript errors while ensuring these methods exist before calling them.
|
||||
const newNavigator = navigator as unknown as {
|
||||
setAppBadge?: (count: number) => Promise<void>;
|
||||
clearAppBadge?: () => Promise<void>;
|
||||
};
|
||||
|
||||
if ('setAppBadge' in navigator) {
|
||||
if (
|
||||
!router.pathname.match(/(login|setup|resetpassword)/) &&
|
||||
hasPermission(Permission.ADMIN)
|
||||
) {
|
||||
requestsCount().then((data) =>
|
||||
newNavigator?.setAppBadge?.(data.pending)
|
||||
);
|
||||
} else {
|
||||
newNavigator?.clearAppBadge?.();
|
||||
}
|
||||
}
|
||||
}, [hasPermission, router.pathname]);
|
||||
|
||||
if (router.pathname.match(/(login|setup|resetpassword)/)) {
|
||||
component = <Component {...pageProps} />;
|
||||
} else {
|
||||
|
||||
Reference in New Issue
Block a user