mirror of
https://github.com/advplyr/audiobookshelf.git
synced 2026-06-12 21:44:24 +02:00
Compare commits
379 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 25b7f005c6 | |||
| 777c59458d | |||
| 9785bc02ea | |||
| 6780ef9b37 | |||
| 88a0e75576 | |||
| 476933a144 | |||
| 2464aac2bf | |||
| b6b786e3a6 | |||
| bacb8aeac7 | |||
| ba9277cc44 | |||
| 3cc5fae586 | |||
| da7d9c10ad | |||
| aa82439125 | |||
| 2e0156d9fa | |||
| 20e0172fa3 | |||
| 6928f6eeb6 | |||
| 4cdc2a8c28 | |||
| e0c674d9a9 | |||
| 727310ab75 | |||
| f46b5a533c | |||
| f3e9cfbe45 | |||
| 4d8501c347 | |||
| b4e8f16174 | |||
| 7073f17cca | |||
| e1c41e4e58 | |||
| 13f73cc79d | |||
| d811ec3806 | |||
| e8505cb637 | |||
| 94fdd99ab5 | |||
| 331c7c011c | |||
| 5fa263023f | |||
| 7eb315a371 | |||
| 780c0dcb99 | |||
| 004210ee02 | |||
| 921880445a | |||
| 0099ae633a | |||
| 91d99deba1 | |||
| e21cbc9ff4 | |||
| 600c1e4668 | |||
| aea2951b89 | |||
| 71b943f434 | |||
| ed0484a8e1 | |||
| 5302f3225b | |||
| a94a7b7940 | |||
| 4318f64d60 | |||
| 26a6618e8f | |||
| c242e9d3d6 | |||
| 4ecb22f70d | |||
| 547a49e95b | |||
| b6875af148 | |||
| c652b5bf74 | |||
| eb0b92a547 | |||
| b56bcbb802 | |||
| 3b8af95211 | |||
| a3332f0478 | |||
| 46421d5f2c | |||
| 7db28d0e98 | |||
| 31d26929af | |||
| 086da5f6a1 | |||
| 09421a44e2 | |||
| fde51da479 | |||
| f3536dc3a3 | |||
| a0c93e5dec | |||
| 63aa6aa950 | |||
| 680099cab4 | |||
| 66f3f3eddf | |||
| a400c149a6 | |||
| 244b5ab36d | |||
| f26747627e | |||
| f57a07c483 | |||
| 080b879d8a | |||
| 63b3f22504 | |||
| 91f17efd5f | |||
| 858d697d0f | |||
| ba55413e63 | |||
| 6cef1e3f12 | |||
| b39268ccb0 | |||
| de8a9304d2 | |||
| f8fbd3ac8c | |||
| 369c05936b | |||
| 837a180dc1 | |||
| 302b651e7b | |||
| 4c68ad46f4 | |||
| e50bd93958 | |||
| d576625cb7 | |||
| ca2327aba3 | |||
| 9bd1f9e3d5 | |||
| c4610e6102 | |||
| 329bbea043 | |||
| e616b53877 | |||
| eab86f90a8 | |||
| f97389cb2b | |||
| c5c3aab130 | |||
| 4610e58337 | |||
| 190a1000d9 | |||
| 455b96d1ab | |||
| 8aaf62f243 | |||
| e6d754113e | |||
| 5f72e30e63 | |||
| 57906540fe | |||
| 726adbb3bf | |||
| f7b7b85673 | |||
| 5646466aa3 | |||
| b38ce41731 | |||
| a8ab8badd5 | |||
| 61729881cb | |||
| 5eca43082e | |||
| 6fa11934be | |||
| ff7edc32a1 | |||
| 9b8e059efe | |||
| 7486d6345d | |||
| 835490a9fc | |||
| 3b4a5b8785 | |||
| 9a1c773b7a | |||
| 890b0b949e | |||
| b19e360bbb | |||
| 1ff7952074 | |||
| 259d93d882 | |||
| 14f60a593b | |||
| 7334580c8c | |||
| f467c44543 | |||
| 867354e59d | |||
| 67952cc577 | |||
| 079a15541c | |||
| 658ac04268 | |||
| cbee6d8f5e | |||
| 68413ae2f6 | |||
| 252a233282 | |||
| c35185fff7 | |||
| 9774b2cfa5 | |||
| 344890fb45 | |||
| 5fa0897ad7 | |||
| 95c80a5b18 | |||
| 0f1b64b883 | |||
| 615ed26f0f | |||
| 84803cef82 | |||
| 605bd73c11 | |||
| cc89db059b | |||
| a03146e09c | |||
| 33aa4f1952 | |||
| c03f18b90a | |||
| 0dedb09a07 | |||
| 2b5484243b | |||
| c496db7c95 | |||
| ea4d5ff665 | |||
| 468a547864 | |||
| cd9999d192 | |||
| 31e302ea59 | |||
| 1ff1ba66fd | |||
| a5457d7e22 | |||
| ddcbfd4500 | |||
| 293e530297 | |||
| 7278ad4ee7 | |||
| 0449fb5ef9 | |||
| d2c28fc69c | |||
| 60ba0163af | |||
| 02ca926d88 | |||
| 4b52f31d58 | |||
| 9917f2d358 | |||
| 8c3ba67583 | |||
| 6d8720b404 | |||
| 843dd0b1b2 | |||
| 70f466d03c | |||
| ef82e8b0d0 | |||
| c643d4cec8 | |||
| 718d8b5999 | |||
| 2ba0f9157d | |||
| 53fdb5273c | |||
| fabdfd5517 | |||
| 950993f652 | |||
| 5a968b002a | |||
| 3acd29fab3 | |||
| 315b21db00 | |||
| f9aaeb3a34 | |||
| d19bb909b3 | |||
| f850db23fe | |||
| 5f81010f6a | |||
| daf2493f50 | |||
| 57222f3611 | |||
| 62b185979e | |||
| ebcc85acc4 | |||
| 33a7ba4acd | |||
| 1d4e6993fc | |||
| 784b761629 | |||
| 268fb2ce9a | |||
| fc5f35b388 | |||
| ff026a06bb | |||
| b148a57c98 | |||
| ee6e2d2983 | |||
| ea3a6fd75e | |||
| 22f85d3af9 | |||
| 75f4c2ee99 | |||
| dd3467efa2 | |||
| 4adb15c11b | |||
| a5e38d1473 | |||
| 778256ca16 | |||
| 2b0ba7d1e2 | |||
| 772f3fedb3 | |||
| fe25d1dccd | |||
| 10a7cd0987 | |||
| 6786df6965 | |||
| 4cfd18c81a | |||
| d25a21cd32 | |||
| b5f0a6f4a6 | |||
| cf19dd23cf | |||
| 3e6a2d670e | |||
| 26ef33a4b6 | |||
| 9940f1d6db | |||
| 75eef8d722 | |||
| 46a3c3de33 | |||
| 2b7e3f0efe | |||
| d5fbc1d455 | |||
| bbe59499ad | |||
| 4c88e9c8d2 | |||
| 5ccf5d7150 | |||
| 27c9381e1d | |||
| 45f8b06d56 | |||
| 2a62992c75 | |||
| 997afc1b2f | |||
| f941ea6500 | |||
| 92d083164f | |||
| 2dd30c7a26 | |||
| 3f0347253e | |||
| bb6377fb22 | |||
| 12c2071358 | |||
| ec4c4a4d5a | |||
| 876fcf3296 | |||
| 023ceed286 | |||
| cc42aa32ef | |||
| 7cbb1c60a2 | |||
| 4ad130a11a | |||
| 9bf46b6367 | |||
| 4be2909b24 | |||
| f161158d83 | |||
| 3a5f6ab6f1 | |||
| c1b626da14 | |||
| 48e0a3c450 | |||
| 8626fa3e00 | |||
| b50d7f0927 | |||
| 0d54b57151 | |||
| 5a2bdc58da | |||
| 01446c02aa | |||
| a382482173 | |||
| 2e970cbb39 | |||
| 161a3f4da9 | |||
| 713bdcbc41 | |||
| 1fa67535f9 | |||
| e8d8b67c0a | |||
| e57d4cc544 | |||
| 435b7fda7e | |||
| d7e810fc2f | |||
| 850ed48955 | |||
| a5ebd89817 | |||
| a8ec07cfc9 | |||
| 41fe5373a7 | |||
| 0812e189f7 | |||
| 588def6d33 | |||
| 0c244cbf95 | |||
| 7ef14aabed | |||
| 978c2b05f2 | |||
| 68fd1d67cb | |||
| bf8407274e | |||
| 3bc2941445 | |||
| 654b1d6b34 | |||
| 7a49681dd2 | |||
| 7a1623e6a1 | |||
| c25acb41fa | |||
| 4224b8a486 | |||
| 9e990d7927 | |||
| 431ae97593 | |||
| 633ff810cf | |||
| f3d2b781ab | |||
| a0b3960ee4 | |||
| e55db0afdc | |||
| ae9efe6359 | |||
| 32105665c1 | |||
| 2b18efdfdc | |||
| e0c66ea6df | |||
| 667c7361d7 | |||
| 63fdf0d18e | |||
| e05cb0ef4d | |||
| 925c7f7dc7 | |||
| c69e97ea24 | |||
| 5e2aebc724 | |||
| 6eba467b91 | |||
| 524cf5ec5b | |||
| 50fd659749 | |||
| 8169afb59b | |||
| d40086fea1 | |||
| 399c40debd | |||
| d986673dfd | |||
| f83f4d41f1 | |||
| 7ed711730e | |||
| 94e2ea9df3 | |||
| 8c8c4a15c3 | |||
| 2a9159f106 | |||
| 8f113d17c2 | |||
| 9084055b95 | |||
| fba9cce82e | |||
| 92cfb46c14 | |||
| 449dc1a0e2 | |||
| d9c345b0f3 | |||
| 69a639f76c | |||
| d576efe759 | |||
| 9ba2ecbc21 | |||
| 84003cd67e | |||
| be8c447216 | |||
| e534daf5d4 | |||
| 1fefc1af92 | |||
| e76c4ed2a4 | |||
| e1caf13233 | |||
| a7a2fbbca8 | |||
| 28d93d9160 | |||
| 4e90f90c28 | |||
| 2243fdddd3 | |||
| 39be3a2ef9 | |||
| ecc30b85bc | |||
| 6905b288d2 | |||
| 0782146682 | |||
| 91aea4f754 | |||
| 6ca277a21d | |||
| c47c75aefe | |||
| 9896e4381b | |||
| 953ffe889e | |||
| 72e59e77a7 | |||
| 35e2681ea9 | |||
| 84012d9090 | |||
| e8a1ea3b54 | |||
| ea6882d9ab | |||
| 1fa80e31d1 | |||
| d80752cc9d | |||
| b764e848c7 | |||
| b037c4e8a3 | |||
| 6ba2360790 | |||
| ca4eb507f0 | |||
| 965b094470 | |||
| 0fe313ecfd | |||
| 35a2f8d44f | |||
| 50797879d5 | |||
| 9327331ee9 | |||
| 1c15007e32 | |||
| 2151ffa114 | |||
| 49ed208a54 | |||
| d668462529 | |||
| f2102a0a23 | |||
| 5efc6b82c1 | |||
| 1e4e9768da | |||
| cc5109c305 | |||
| e858d6a1d5 | |||
| b4cd5d2862 | |||
| 0633a44cfb | |||
| 5748126b83 | |||
| 06375743a3 | |||
| 2a41c186aa | |||
| af51b7254c | |||
| f63dfd769f | |||
| a1512f3174 | |||
| 245751e2ce | |||
| 37001d9425 | |||
| 9d1f51c6ba | |||
| cb234fe1fc | |||
| cb85e0255b | |||
| 61b4cfdab7 | |||
| d2c405c126 | |||
| cbca560f92 | |||
| 2d7b63b4cf | |||
| 217038b085 | |||
| 13dd4edd6a | |||
| a7288b4fbf | |||
| 3020e8104e | |||
| 8fdeeaaf38 | |||
| 42616b59de | |||
| bf16681bea | |||
| 027190b5a4 | |||
| 241c02be30 | |||
| dd87268848 | |||
| f2ac24e623 | |||
| 99ffd3050c | |||
| 69dd82d329 |
@@ -0,0 +1,33 @@
|
|||||||
|
<!--
|
||||||
|
For Work In Progress Pull Requests, please use the Draft PR feature,
|
||||||
|
see https://github.blog/2019-02-14-introducing-draft-pull-requests/ for further details.
|
||||||
|
|
||||||
|
If you do not follow this template, the PR may be closed without review.
|
||||||
|
|
||||||
|
Please ensure all checks pass.
|
||||||
|
If you are a new contributor, the workflows will need to be manually approved before they run.
|
||||||
|
-->
|
||||||
|
|
||||||
|
## Brief summary
|
||||||
|
|
||||||
|
<!-- Please provide a brief summary of what your PR attempts to achieve. -->
|
||||||
|
|
||||||
|
## Which issue is fixed?
|
||||||
|
|
||||||
|
<!-- Which issue number does this PR fix? Ex: "Fixes #1234" -->
|
||||||
|
|
||||||
|
## In-depth Description
|
||||||
|
|
||||||
|
<!--
|
||||||
|
Describe your solution in more depth.
|
||||||
|
How does it work? Why is this the best solution?
|
||||||
|
Does it solve a problem that affects multiple users or is this an edge case for your setup?
|
||||||
|
-->
|
||||||
|
|
||||||
|
## How have you tested this?
|
||||||
|
|
||||||
|
<!-- Please describe in detail with reproducible steps how you tested your changes. -->
|
||||||
|
|
||||||
|
## Screenshots
|
||||||
|
|
||||||
|
<!-- If your PR includes any changes to the web client, please include screenshots or a short video from before and after your changes. -->
|
||||||
@@ -1,11 +1,25 @@
|
|||||||
name: "CodeQL"
|
name: 'CodeQL'
|
||||||
|
|
||||||
on:
|
on:
|
||||||
push:
|
push:
|
||||||
branches: [ 'master' ]
|
branches: ['master']
|
||||||
|
# Only build when files in these directories have been changed
|
||||||
|
paths:
|
||||||
|
- client/**
|
||||||
|
- server/**
|
||||||
|
- test/**
|
||||||
|
- index.js
|
||||||
|
- package.json
|
||||||
pull_request:
|
pull_request:
|
||||||
# The branches below must be a subset of the branches above
|
# The branches below must be a subset of the branches above
|
||||||
branches: [ 'master' ]
|
branches: ['master']
|
||||||
|
# Only build when files in these directories have been changed
|
||||||
|
paths:
|
||||||
|
- client/**
|
||||||
|
- server/**
|
||||||
|
- test/**
|
||||||
|
- index.js
|
||||||
|
- package.json
|
||||||
schedule:
|
schedule:
|
||||||
- cron: '16 5 * * 4'
|
- cron: '16 5 * * 4'
|
||||||
|
|
||||||
@@ -21,7 +35,7 @@ jobs:
|
|||||||
strategy:
|
strategy:
|
||||||
fail-fast: false
|
fail-fast: false
|
||||||
matrix:
|
matrix:
|
||||||
language: [ 'javascript' ]
|
language: ['javascript']
|
||||||
# CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python', 'ruby' ]
|
# CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python', 'ruby' ]
|
||||||
# Use only 'java' to analyze code written in Java, Kotlin or both
|
# Use only 'java' to analyze code written in Java, Kotlin or both
|
||||||
# Use only 'javascript' to analyze code written in JavaScript, TypeScript or both
|
# Use only 'javascript' to analyze code written in JavaScript, TypeScript or both
|
||||||
@@ -43,7 +57,6 @@ jobs:
|
|||||||
# For more details on CodeQL's query packs, refer to: https://docs.github.com/en/code-security/code-scanning/automatically-scanning-your-code-for-vulnerabilities-and-errors/configuring-code-scanning#using-queries-in-ql-packs
|
# For more details on CodeQL's query packs, refer to: https://docs.github.com/en/code-security/code-scanning/automatically-scanning-your-code-for-vulnerabilities-and-errors/configuring-code-scanning#using-queries-in-ql-packs
|
||||||
# queries: security-extended,security-and-quality
|
# queries: security-extended,security-and-quality
|
||||||
|
|
||||||
|
|
||||||
# Autobuild attempts to build any compiled languages (C/C++, C#, Go, Java, or Swift).
|
# Autobuild attempts to build any compiled languages (C/C++, C#, Go, Java, or Swift).
|
||||||
# If this step fails, then you should remove it and run the build manually (see below)
|
# If this step fails, then you should remove it and run the build manually (see below)
|
||||||
- name: Autobuild
|
- name: Autobuild
|
||||||
@@ -62,4 +75,4 @@ jobs:
|
|||||||
- name: Perform CodeQL Analysis
|
- name: Perform CodeQL Analysis
|
||||||
uses: github/codeql-action/analyze@v2
|
uses: github/codeql-action/analyze@v2
|
||||||
with:
|
with:
|
||||||
category: "/language:${{matrix.language}}"
|
category: '/language:${{matrix.language}}'
|
||||||
|
|||||||
@@ -5,6 +5,13 @@ on:
|
|||||||
push:
|
push:
|
||||||
branches-ignore:
|
branches-ignore:
|
||||||
- 'dependabot/**' # Don't run dependabot branches, as they are already covered by pull requests
|
- 'dependabot/**' # Don't run dependabot branches, as they are already covered by pull requests
|
||||||
|
# Only build when files in these directories have been changed
|
||||||
|
paths:
|
||||||
|
- client/**
|
||||||
|
- server/**
|
||||||
|
- test/**
|
||||||
|
- index.js
|
||||||
|
- package.json
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
build:
|
build:
|
||||||
|
|||||||
@@ -7,6 +7,7 @@
|
|||||||
/podcasts/
|
/podcasts/
|
||||||
/media/
|
/media/
|
||||||
/metadata/
|
/metadata/
|
||||||
|
/plugins/
|
||||||
/client/.nuxt/
|
/client/.nuxt/
|
||||||
/client/dist/
|
/client/dist/
|
||||||
/dist/
|
/dist/
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="w-full h-16 bg-primary relative">
|
<div class="w-full h-16 bg-primary relative">
|
||||||
<div id="appbar" class="absolute top-0 bottom-0 left-0 w-full h-full px-2 md:px-6 py-1 z-60">
|
<div id="appbar" role="toolbar" aria-label="Appbar" class="absolute top-0 bottom-0 left-0 w-full h-full px-2 md:px-6 py-1 z-60">
|
||||||
<div class="flex h-full items-center">
|
<div class="flex h-full items-center">
|
||||||
<nuxt-link to="/">
|
<nuxt-link to="/">
|
||||||
<img src="~static/icon.svg" :alt="$strings.ButtonHome" class="w-8 min-w-8 h-8 mr-2 sm:w-10 sm:min-w-10 sm:h-10 sm:mr-4" />
|
<img src="~static/icon.svg" :alt="$strings.ButtonHome" class="w-8 min-w-8 h-8 mr-2 sm:w-10 sm:min-w-10 sm:h-10 sm:mr-4" />
|
||||||
|
|||||||
@@ -17,7 +17,7 @@
|
|||||||
<div v-else-if="isAlternativeBookshelfView" class="w-full mb-24e">
|
<div v-else-if="isAlternativeBookshelfView" class="w-full mb-24e">
|
||||||
<template v-for="(shelf, index) in supportedShelves">
|
<template v-for="(shelf, index) in supportedShelves">
|
||||||
<widgets-item-slider :shelf-id="shelf.id" :key="index + '.'" :items="shelf.entities" :continue-listening-shelf="shelf.id === 'continue-listening' || shelf.id === 'continue-reading'" :type="shelf.type" class="bookshelf-row pl-8e my-6e" @selectEntity="(payload) => selectEntity(payload, index)">
|
<widgets-item-slider :shelf-id="shelf.id" :key="index + '.'" :items="shelf.entities" :continue-listening-shelf="shelf.id === 'continue-listening' || shelf.id === 'continue-reading'" :type="shelf.type" class="bookshelf-row pl-8e my-6e" @selectEntity="(payload) => selectEntity(payload, index)">
|
||||||
<p class="font-semibold text-gray-100">{{ $strings[shelf.labelStringKey] }}</p>
|
<h2 class="font-semibold text-gray-100">{{ $strings[shelf.labelStringKey] }}</h2>
|
||||||
</widgets-item-slider>
|
</widgets-item-slider>
|
||||||
</template>
|
</template>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -37,18 +37,18 @@
|
|||||||
<div class="relative">
|
<div class="relative">
|
||||||
<div class="relative text-center categoryPlacard transform z-30 top-0 left-4e md:left-8e w-44e rounded-md">
|
<div class="relative text-center categoryPlacard transform z-30 top-0 left-4e md:left-8e w-44e rounded-md">
|
||||||
<div class="w-full h-full shinyBlack flex items-center justify-center rounded-sm border" :style="{ padding: `0em 0.5em` }">
|
<div class="w-full h-full shinyBlack flex items-center justify-center rounded-sm border" :style="{ padding: `0em 0.5em` }">
|
||||||
<p :style="{ fontSize: 0.9 + 'em' }">{{ $strings[shelf.labelStringKey] }}</p>
|
<h2 :style="{ fontSize: 0.9 + 'em' }">{{ $strings[shelf.labelStringKey] }}</h2>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="bookshelfDividerCategorized h-6e w-full absolute top-0 left-0 right-0 z-20"></div>
|
<div class="bookshelfDividerCategorized h-6e w-full absolute top-0 left-0 right-0 z-20"></div>
|
||||||
</div>
|
</div>
|
||||||
<div v-show="canScrollLeft && !isScrolling" class="hidden sm:flex absolute top-0 left-0 w-32 pr-8 bg-black book-shelf-arrow-left items-center justify-center cursor-pointer opacity-0 hover:opacity-100 z-40" @click="scrollLeft">
|
<button v-show="canScrollLeft && !isScrolling" :aria-label="$strings.ButtonScrollLeft" class="hidden sm:flex absolute top-0 left-0 w-32 pr-8 bg-black book-shelf-arrow-left items-center justify-center cursor-pointer opacity-0 hover:opacity-100 z-40" @click="scrollLeft">
|
||||||
<span class="material-symbols text-white" :style="{ fontSize: 3.75 + 'em' }">chevron_left</span>
|
<span class="material-symbols text-white" :style="{ fontSize: 3.75 + 'em' }">chevron_left</span>
|
||||||
</div>
|
</button>
|
||||||
<div v-show="canScrollRight && !isScrolling" class="hidden sm:flex absolute top-0 right-0 w-32 pl-8 bg-black book-shelf-arrow-right items-center justify-center cursor-pointer opacity-0 hover:opacity-100 z-40" @click="scrollRight">
|
<button v-show="canScrollRight && !isScrolling" :aria-label="$strings.ButtonScrollRight" class="hidden sm:flex absolute top-0 right-0 w-32 pl-8 bg-black book-shelf-arrow-right items-center justify-center cursor-pointer opacity-0 hover:opacity-100 z-40" @click="scrollRight">
|
||||||
<span class="material-symbols text-white" :style="{ fontSize: 3.75 + 'em' }">chevron_right</span>
|
<span class="material-symbols text-white" :style="{ fontSize: 3.75 + 'em' }">chevron_right</span>
|
||||||
</div>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
|||||||
@@ -42,8 +42,11 @@
|
|||||||
<nuxt-link v-if="isPodcastLibrary && userIsAdminOrUp" :to="`/library/${currentLibraryId}/podcast/search`" class="flex-grow h-full flex justify-center items-center" :class="isPodcastSearchPage ? 'bg-primary bg-opacity-80' : 'bg-primary bg-opacity-40'">
|
<nuxt-link v-if="isPodcastLibrary && userIsAdminOrUp" :to="`/library/${currentLibraryId}/podcast/search`" class="flex-grow h-full flex justify-center items-center" :class="isPodcastSearchPage ? 'bg-primary bg-opacity-80' : 'bg-primary bg-opacity-40'">
|
||||||
<p class="text-sm">{{ $strings.ButtonAdd }}</p>
|
<p class="text-sm">{{ $strings.ButtonAdd }}</p>
|
||||||
</nuxt-link>
|
</nuxt-link>
|
||||||
|
<nuxt-link v-if="isPodcastLibrary && userIsAdminOrUp" :to="`/library/${currentLibraryId}/podcast/download-queue`" class="flex-grow h-full flex justify-center items-center" :class="isPodcastDownloadQueuePage ? 'bg-primary bg-opacity-80' : 'bg-primary bg-opacity-40'">
|
||||||
|
<p class="text-sm">{{ $strings.ButtonDownloadQueue }}</p>
|
||||||
|
</nuxt-link>
|
||||||
</div>
|
</div>
|
||||||
<div id="toolbar" class="absolute top-10 md:top-0 left-0 w-full h-10 md:h-full z-40 flex items-center justify-end md:justify-start px-2 md:px-8">
|
<div id="toolbar" role="toolbar" aria-label="Library Toolbar" class="absolute top-10 md:top-0 left-0 w-full h-10 md:h-full z-40 flex items-center justify-end md:justify-start px-2 md:px-8">
|
||||||
<!-- Series books page -->
|
<!-- Series books page -->
|
||||||
<template v-if="selectedSeries">
|
<template v-if="selectedSeries">
|
||||||
<p class="pl-2 text-base md:text-lg">
|
<p class="pl-2 text-base md:text-lg">
|
||||||
@@ -265,6 +268,9 @@ export default {
|
|||||||
isPodcastLatestPage() {
|
isPodcastLatestPage() {
|
||||||
return this.$route.name === 'library-library-podcast-latest'
|
return this.$route.name === 'library-library-podcast-latest'
|
||||||
},
|
},
|
||||||
|
isPodcastDownloadQueuePage() {
|
||||||
|
return this.$route.name === 'library-library-podcast-download-queue'
|
||||||
|
},
|
||||||
isAuthorsPage() {
|
isAuthorsPage() {
|
||||||
return this.page === 'authors'
|
return this.page === 'authors'
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
<template>
|
<template>
|
||||||
<div>
|
<div role="toolbar" aria-orientation="vertical" aria-label="Config Sidebar">
|
||||||
<div class="w-44 fixed left-0 top-16 bg-bg bg-opacity-100 md:bg-opacity-70 shadow-lg border-r border-white border-opacity-5 py-3 transform transition-transform mb-12 overflow-y-auto" :class="wrapperClass + ' ' + (streamLibraryItem ? 'h-[calc(100%-270px)]' : 'h-[calc(100%-110px)]')" v-click-outside="clickOutside">
|
<div role="navigation" aria-label="Config Navigation" class="w-44 fixed left-0 top-16 bg-bg bg-opacity-100 md:bg-opacity-70 shadow-lg border-r border-white border-opacity-5 py-3 transform transition-transform mb-12 overflow-y-auto" :class="wrapperClass + ' ' + (streamLibraryItem ? 'h-[calc(100%-270px)]' : 'h-[calc(100%-110px)]')" v-click-outside="clickOutside">
|
||||||
<div v-show="isMobilePortrait" class="flex items-center justify-end pb-2 px-4 mb-1" @click="closeDrawer">
|
<div v-show="isMobilePortrait" class="flex items-center justify-end pb-2 px-4 mb-1" @click="closeDrawer">
|
||||||
<span class="material-symbols text-2xl">arrow_back</span>
|
<span class="material-symbols text-2xl">arrow_back</span>
|
||||||
</div>
|
</div>
|
||||||
@@ -19,7 +19,7 @@
|
|||||||
|
|
||||||
<p class="text-xs text-gray-300 italic">{{ Source }}</p>
|
<p class="text-xs text-gray-300 italic">{{ Source }}</p>
|
||||||
</div>
|
</div>
|
||||||
<a v-if="hasUpdate" :href="githubTagUrl" target="_blank" class="text-warning text-xs">Latest: {{ $config.version }}</a>
|
<a v-if="hasUpdate" :href="githubTagUrl" target="_blank" class="text-warning text-xs">Latest: {{ versionData.latestVersion }}</a>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
@@ -2,6 +2,10 @@
|
|||||||
<div id="bookshelf" ref="bookshelf" class="w-full overflow-y-auto" :style="{ fontSize: sizeMultiplier + 'rem' }">
|
<div id="bookshelf" ref="bookshelf" class="w-full overflow-y-auto" :style="{ fontSize: sizeMultiplier + 'rem' }">
|
||||||
<template v-for="shelf in totalShelves">
|
<template v-for="shelf in totalShelves">
|
||||||
<div :key="shelf" :id="`shelf-${shelf - 1}`" class="w-full px-4e sm:px-8e relative" :class="{ bookshelfRow: !isAlternativeBookshelfView }" :style="{ height: shelfHeight + 'px' }">
|
<div :key="shelf" :id="`shelf-${shelf - 1}`" class="w-full px-4e sm:px-8e relative" :class="{ bookshelfRow: !isAlternativeBookshelfView }" :style="{ height: shelfHeight + 'px' }">
|
||||||
|
<!-- Card skeletons -->
|
||||||
|
<template v-for="entityIndex in entitiesInShelf(shelf)">
|
||||||
|
<div :key="entityIndex" class="w-full h-full absolute rounded z-5 top-0 left-0 bg-primary box-shadow-book" :style="{ transform: entityTransform(entityIndex), width: cardWidth + 'px', height: coverHeight + 'px' }" />
|
||||||
|
</template>
|
||||||
<div v-if="!isAlternativeBookshelfView" class="bookshelfDivider w-full absolute bottom-0 left-0 right-0 z-20 h-6e" />
|
<div v-if="!isAlternativeBookshelfView" class="bookshelfDivider w-full absolute bottom-0 left-0 right-0 z-20 h-6e" />
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
@@ -65,7 +69,13 @@ export default {
|
|||||||
tempIsScanning: false,
|
tempIsScanning: false,
|
||||||
cardWidth: 0,
|
cardWidth: 0,
|
||||||
cardHeight: 0,
|
cardHeight: 0,
|
||||||
resizeObserver: null
|
coverHeight: 0,
|
||||||
|
resizeObserver: null,
|
||||||
|
lastScrollTop: 0,
|
||||||
|
lastTimestamp: 0,
|
||||||
|
postScrollTimeout: null,
|
||||||
|
currFirstEntityIndex: -1,
|
||||||
|
currLastEntityIndex: -1
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
watch: {
|
watch: {
|
||||||
@@ -171,9 +181,6 @@ export default {
|
|||||||
bookWidth() {
|
bookWidth() {
|
||||||
return this.cardWidth
|
return this.cardWidth
|
||||||
},
|
},
|
||||||
bookHeight() {
|
|
||||||
return this.cardHeight
|
|
||||||
},
|
|
||||||
shelfPadding() {
|
shelfPadding() {
|
||||||
if (this.bookshelfWidth < 640) return 32 * this.sizeMultiplier
|
if (this.bookshelfWidth < 640) return 32 * this.sizeMultiplier
|
||||||
return 64 * this.sizeMultiplier
|
return 64 * this.sizeMultiplier
|
||||||
@@ -184,9 +191,6 @@ export default {
|
|||||||
entityWidth() {
|
entityWidth() {
|
||||||
return this.cardWidth
|
return this.cardWidth
|
||||||
},
|
},
|
||||||
entityHeight() {
|
|
||||||
return this.cardHeight
|
|
||||||
},
|
|
||||||
shelfPaddingHeight() {
|
shelfPaddingHeight() {
|
||||||
return 16
|
return 16
|
||||||
},
|
},
|
||||||
@@ -354,50 +358,53 @@ export default {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
loadPage(page) {
|
loadPage(page) {
|
||||||
this.pagesLoaded[page] = true
|
if (!this.pagesLoaded[page]) this.pagesLoaded[page] = this.fetchEntites(page)
|
||||||
this.fetchEntites(page)
|
return this.pagesLoaded[page]
|
||||||
},
|
},
|
||||||
showHideBookPlaceholder(index, show) {
|
showHideBookPlaceholder(index, show) {
|
||||||
var el = document.getElementById(`book-${index}-placeholder`)
|
var el = document.getElementById(`book-${index}-placeholder`)
|
||||||
if (el) el.style.display = show ? 'flex' : 'none'
|
if (el) el.style.display = show ? 'flex' : 'none'
|
||||||
},
|
},
|
||||||
mountEntites(fromIndex, toIndex) {
|
mountEntities(fromIndex, toIndex) {
|
||||||
for (let i = fromIndex; i < toIndex; i++) {
|
for (let i = fromIndex; i < toIndex; i++) {
|
||||||
if (!this.entityIndexesMounted.includes(i)) {
|
if (!this.entityIndexesMounted.includes(i)) {
|
||||||
this.cardsHelpers.mountEntityCard(i)
|
this.cardsHelpers.mountEntityCard(i)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
handleScroll(scrollTop) {
|
getVisibleIndices(scrollTop) {
|
||||||
this.currScrollTop = scrollTop
|
const firstShelfIndex = Math.floor(scrollTop / this.shelfHeight)
|
||||||
var firstShelfIndex = Math.floor(scrollTop / this.shelfHeight)
|
const lastShelfIndex = Math.min(Math.ceil((scrollTop + this.bookshelfHeight) / this.shelfHeight), this.totalShelves - 1)
|
||||||
var lastShelfIndex = Math.ceil((scrollTop + this.bookshelfHeight) / this.shelfHeight)
|
const firstEntityIndex = firstShelfIndex * this.entitiesPerShelf
|
||||||
lastShelfIndex = Math.min(this.totalShelves - 1, lastShelfIndex)
|
const lastEntityIndex = Math.min(lastShelfIndex * this.entitiesPerShelf + this.entitiesPerShelf, this.totalEntities)
|
||||||
|
return { firstEntityIndex, lastEntityIndex }
|
||||||
var firstBookIndex = firstShelfIndex * this.entitiesPerShelf
|
},
|
||||||
var lastBookIndex = lastShelfIndex * this.entitiesPerShelf + this.entitiesPerShelf
|
postScroll() {
|
||||||
lastBookIndex = Math.min(this.totalEntities, lastBookIndex)
|
const { firstEntityIndex, lastEntityIndex } = this.getVisibleIndices(this.currScrollTop)
|
||||||
|
|
||||||
var firstBookPage = Math.floor(firstBookIndex / this.booksPerFetch)
|
|
||||||
var lastBookPage = Math.floor(lastBookIndex / this.booksPerFetch)
|
|
||||||
if (!this.pagesLoaded[firstBookPage]) {
|
|
||||||
// console.log('Must load next batch', firstBookPage, 'book index', firstBookIndex)
|
|
||||||
this.loadPage(firstBookPage)
|
|
||||||
}
|
|
||||||
if (!this.pagesLoaded[lastBookPage]) {
|
|
||||||
// console.log('Must load last next batch', lastBookPage, 'book index', lastBookIndex)
|
|
||||||
this.loadPage(lastBookPage)
|
|
||||||
}
|
|
||||||
|
|
||||||
this.entityIndexesMounted = this.entityIndexesMounted.filter((_index) => {
|
this.entityIndexesMounted = this.entityIndexesMounted.filter((_index) => {
|
||||||
if (_index < firstBookIndex || _index >= lastBookIndex) {
|
if (_index < firstEntityIndex || _index >= lastEntityIndex) {
|
||||||
var el = document.getElementById(`book-card-${_index}`)
|
var el = this.entityComponentRefs[_index]
|
||||||
if (el) el.remove()
|
if (el && el.$el) el.$el.remove()
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
return true
|
return true
|
||||||
})
|
})
|
||||||
this.mountEntites(firstBookIndex, lastBookIndex)
|
},
|
||||||
|
handleScroll(scrollTop) {
|
||||||
|
this.currScrollTop = scrollTop
|
||||||
|
const { firstEntityIndex, lastEntityIndex } = this.getVisibleIndices(scrollTop)
|
||||||
|
if (firstEntityIndex === this.currFirstEntityIndex && lastEntityIndex === this.currLastEntityIndex) return
|
||||||
|
this.currFirstEntityIndex = firstEntityIndex
|
||||||
|
this.currLastEntityIndex = lastEntityIndex
|
||||||
|
|
||||||
|
clearTimeout(this.postScrollTimeout)
|
||||||
|
const firstPage = Math.floor(firstEntityIndex / this.booksPerFetch)
|
||||||
|
const lastPage = Math.floor(lastEntityIndex / this.booksPerFetch)
|
||||||
|
Promise.all([this.loadPage(firstPage), this.loadPage(lastPage)])
|
||||||
|
.then(() => this.mountEntities(firstEntityIndex, lastEntityIndex))
|
||||||
|
.catch((error) => console.error('Failed to load page', error))
|
||||||
|
|
||||||
|
this.postScrollTimeout = setTimeout(this.postScroll, 500)
|
||||||
},
|
},
|
||||||
async resetEntities() {
|
async resetEntities() {
|
||||||
if (this.isFetchingEntities) {
|
if (this.isFetchingEntities) {
|
||||||
@@ -405,8 +412,6 @@ export default {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
this.destroyEntityComponents()
|
this.destroyEntityComponents()
|
||||||
this.entityIndexesMounted = []
|
|
||||||
this.entityComponentRefs = {}
|
|
||||||
this.pagesLoaded = {}
|
this.pagesLoaded = {}
|
||||||
this.entities = []
|
this.entities = []
|
||||||
this.totalShelves = 0
|
this.totalShelves = 0
|
||||||
@@ -416,40 +421,21 @@ export default {
|
|||||||
this.initialized = false
|
this.initialized = false
|
||||||
|
|
||||||
this.initSizeData()
|
this.initSizeData()
|
||||||
this.pagesLoaded[0] = true
|
await this.loadPage(0)
|
||||||
await this.fetchEntites(0)
|
|
||||||
var lastBookIndex = Math.min(this.totalEntities, this.shelvesPerPage * this.entitiesPerShelf)
|
var lastBookIndex = Math.min(this.totalEntities, this.shelvesPerPage * this.entitiesPerShelf)
|
||||||
this.mountEntites(0, lastBookIndex)
|
this.mountEntities(0, lastBookIndex)
|
||||||
},
|
},
|
||||||
remountEntities() {
|
async rebuild() {
|
||||||
for (const key in this.entityComponentRefs) {
|
|
||||||
if (this.entityComponentRefs[key]) {
|
|
||||||
this.entityComponentRefs[key].destroy()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
this.entityComponentRefs = {}
|
|
||||||
this.entityIndexesMounted.forEach((i) => {
|
|
||||||
this.cardsHelpers.mountEntityCard(i)
|
|
||||||
})
|
|
||||||
},
|
|
||||||
rebuild() {
|
|
||||||
this.initSizeData()
|
this.initSizeData()
|
||||||
|
|
||||||
var lastBookIndex = Math.min(this.totalEntities, this.booksPerFetch)
|
var lastBookIndex = Math.min(this.totalEntities, this.booksPerFetch)
|
||||||
this.entityIndexesMounted = []
|
this.destroyEntityComponents()
|
||||||
for (let i = 0; i < lastBookIndex; i++) {
|
await this.loadPage(0)
|
||||||
this.entityIndexesMounted.push(i)
|
|
||||||
if (!this.entities[i]) {
|
|
||||||
const page = Math.floor(i / this.booksPerFetch)
|
|
||||||
this.loadPage(page)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
var bookshelfEl = document.getElementById('bookshelf')
|
var bookshelfEl = document.getElementById('bookshelf')
|
||||||
if (bookshelfEl) {
|
if (bookshelfEl) {
|
||||||
bookshelfEl.scrollTop = 0
|
bookshelfEl.scrollTop = 0
|
||||||
}
|
}
|
||||||
|
this.mountEntities(0, lastBookIndex)
|
||||||
this.$nextTick(this.remountEntities)
|
|
||||||
},
|
},
|
||||||
buildSearchParams() {
|
buildSearchParams() {
|
||||||
if (this.page === 'search' || this.page === 'collections') {
|
if (this.page === 'search' || this.page === 'collections') {
|
||||||
@@ -513,12 +499,29 @@ export default {
|
|||||||
if (wasUpdated) {
|
if (wasUpdated) {
|
||||||
this.resetEntities()
|
this.resetEntities()
|
||||||
} else if (settings.bookshelfCoverSize !== this.currentBookWidth) {
|
} else if (settings.bookshelfCoverSize !== this.currentBookWidth) {
|
||||||
this.executeRebuild()
|
this.rebuild()
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
getScrollRate() {
|
||||||
|
const currentTimestamp = Date.now()
|
||||||
|
const timeDelta = currentTimestamp - this.lastTimestamp
|
||||||
|
const scrollDelta = this.currScrollTop - this.lastScrollTop
|
||||||
|
const scrollRate = Math.abs(scrollDelta) / (timeDelta || 1)
|
||||||
|
this.lastScrollTop = this.currScrollTop
|
||||||
|
this.lastTimestamp = currentTimestamp
|
||||||
|
return scrollRate
|
||||||
|
},
|
||||||
scroll(e) {
|
scroll(e) {
|
||||||
if (!e || !e.target) return
|
if (!e || !e.target) return
|
||||||
var { scrollTop } = e.target
|
clearTimeout(this.scrollTimeout)
|
||||||
|
const { scrollTop } = e.target
|
||||||
|
const scrollRate = this.getScrollRate()
|
||||||
|
if (scrollRate > 5) {
|
||||||
|
this.scrollTimeout = setTimeout(() => {
|
||||||
|
this.handleScroll(scrollTop)
|
||||||
|
}, 25)
|
||||||
|
return
|
||||||
|
}
|
||||||
this.handleScroll(scrollTop)
|
this.handleScroll(scrollTop)
|
||||||
},
|
},
|
||||||
libraryItemAdded(libraryItem) {
|
libraryItemAdded(libraryItem) {
|
||||||
@@ -667,13 +670,14 @@ export default {
|
|||||||
},
|
},
|
||||||
updatePagesLoaded() {
|
updatePagesLoaded() {
|
||||||
let numPages = Math.ceil(this.totalEntities / this.booksPerFetch)
|
let numPages = Math.ceil(this.totalEntities / this.booksPerFetch)
|
||||||
|
this.pagesLoaded = {}
|
||||||
for (let page = 0; page < numPages; page++) {
|
for (let page = 0; page < numPages; page++) {
|
||||||
let numEntities = Math.min(this.totalEntities - page * this.booksPerFetch, this.booksPerFetch)
|
let numEntities = Math.min(this.totalEntities - page * this.booksPerFetch, this.booksPerFetch)
|
||||||
this.pagesLoaded[page] = true
|
this.pagesLoaded[page] = Promise.resolve()
|
||||||
for (let i = 0; i < numEntities; i++) {
|
for (let i = 0; i < numEntities; i++) {
|
||||||
const index = page * this.booksPerFetch + i
|
const index = page * this.booksPerFetch + i
|
||||||
if (!this.entities[index]) {
|
if (!this.entities[index]) {
|
||||||
this.pagesLoaded[page] = false
|
if (this.pagesLoaded[page]) delete this.pagesLoaded[page]
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -688,7 +692,6 @@ export default {
|
|||||||
var entitiesPerShelfBefore = this.entitiesPerShelf
|
var entitiesPerShelfBefore = this.entitiesPerShelf
|
||||||
|
|
||||||
var { clientHeight, clientWidth } = bookshelf
|
var { clientHeight, clientWidth } = bookshelf
|
||||||
// console.log('Init bookshelf width', clientWidth, 'window width', window.innerWidth)
|
|
||||||
this.mountWindowWidth = window.innerWidth
|
this.mountWindowWidth = window.innerWidth
|
||||||
this.bookshelfHeight = clientHeight
|
this.bookshelfHeight = clientHeight
|
||||||
this.bookshelfWidth = clientWidth
|
this.bookshelfWidth = clientWidth
|
||||||
@@ -713,10 +716,9 @@ export default {
|
|||||||
this.initSizeData(bookshelf)
|
this.initSizeData(bookshelf)
|
||||||
this.checkUpdateSearchParams()
|
this.checkUpdateSearchParams()
|
||||||
|
|
||||||
this.pagesLoaded[0] = true
|
await this.loadPage(0)
|
||||||
await this.fetchEntites(0)
|
|
||||||
var lastBookIndex = Math.min(this.totalEntities, this.shelvesPerPage * this.entitiesPerShelf)
|
var lastBookIndex = Math.min(this.totalEntities, this.shelvesPerPage * this.entitiesPerShelf)
|
||||||
this.mountEntites(0, lastBookIndex)
|
this.mountEntities(0, lastBookIndex)
|
||||||
|
|
||||||
// Set last scroll position for this bookshelf page
|
// Set last scroll position for this bookshelf page
|
||||||
if (this.$store.state.lastBookshelfScrollData[this.page] && window.bookshelf) {
|
if (this.$store.state.lastBookshelfScrollData[this.page] && window.bookshelf) {
|
||||||
@@ -747,7 +749,7 @@ export default {
|
|||||||
var bookshelf = document.getElementById('bookshelf')
|
var bookshelf = document.getElementById('bookshelf')
|
||||||
if (bookshelf) {
|
if (bookshelf) {
|
||||||
this.init(bookshelf)
|
this.init(bookshelf)
|
||||||
bookshelf.addEventListener('scroll', this.scroll)
|
bookshelf.addEventListener('scroll', this.scroll, { passive: true })
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -810,10 +812,14 @@ export default {
|
|||||||
},
|
},
|
||||||
destroyEntityComponents() {
|
destroyEntityComponents() {
|
||||||
for (const key in this.entityComponentRefs) {
|
for (const key in this.entityComponentRefs) {
|
||||||
if (this.entityComponentRefs[key] && this.entityComponentRefs[key].destroy) {
|
const ref = this.entityComponentRefs[key]
|
||||||
this.entityComponentRefs[key].destroy()
|
if (ref && ref.destroy) {
|
||||||
|
if (ref.$el) ref.$el.remove()
|
||||||
|
ref.destroy()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
this.entityComponentRefs = {}
|
||||||
|
this.entityIndexesMounted = []
|
||||||
},
|
},
|
||||||
scan() {
|
scan() {
|
||||||
this.tempIsScanning = true
|
this.tempIsScanning = true
|
||||||
@@ -826,6 +832,14 @@ export default {
|
|||||||
.finally(() => {
|
.finally(() => {
|
||||||
this.tempIsScanning = false
|
this.tempIsScanning = false
|
||||||
})
|
})
|
||||||
|
},
|
||||||
|
entitiesInShelf(shelf) {
|
||||||
|
return shelf == this.totalShelves ? this.totalEntities % this.entitiesPerShelf || this.entitiesPerShelf : this.entitiesPerShelf
|
||||||
|
},
|
||||||
|
entityTransform(entityIndex) {
|
||||||
|
const shelfOffsetY = this.shelfPaddingHeight * this.sizeMultiplier
|
||||||
|
const shelfOffsetX = (entityIndex - 1) * this.totalEntityCardWidth + this.bookshelfMarginLeft
|
||||||
|
return `translate3d(${shelfOffsetX}px, ${shelfOffsetY}px, 0px)`
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
async mounted() {
|
async mounted() {
|
||||||
|
|||||||
@@ -53,7 +53,6 @@
|
|||||||
@showBookmarks="showBookmarks"
|
@showBookmarks="showBookmarks"
|
||||||
@showSleepTimer="showSleepTimerModal = true"
|
@showSleepTimer="showSleepTimerModal = true"
|
||||||
@showPlayerQueueItems="showPlayerQueueItemsModal = true"
|
@showPlayerQueueItems="showPlayerQueueItemsModal = true"
|
||||||
@showPlayerSettings="showPlayerSettingsModal = true"
|
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<modals-bookmarks-modal v-model="showBookmarksModal" :bookmarks="bookmarks" :current-time="bookmarkCurrentTime" :library-item-id="libraryItemId" @select="selectBookmark" />
|
<modals-bookmarks-modal v-model="showBookmarksModal" :bookmarks="bookmarks" :current-time="bookmarkCurrentTime" :library-item-id="libraryItemId" @select="selectBookmark" />
|
||||||
@@ -61,8 +60,6 @@
|
|||||||
<modals-sleep-timer-modal v-model="showSleepTimerModal" :timer-set="sleepTimerSet" :timer-type="sleepTimerType" :remaining="sleepTimerRemaining" :has-chapters="!!chapters.length" @set="setSleepTimer" @cancel="cancelSleepTimer" @increment="incrementSleepTimer" @decrement="decrementSleepTimer" />
|
<modals-sleep-timer-modal v-model="showSleepTimerModal" :timer-set="sleepTimerSet" :timer-type="sleepTimerType" :remaining="sleepTimerRemaining" :has-chapters="!!chapters.length" @set="setSleepTimer" @cancel="cancelSleepTimer" @increment="incrementSleepTimer" @decrement="decrementSleepTimer" />
|
||||||
|
|
||||||
<modals-player-queue-items-modal v-model="showPlayerQueueItemsModal" />
|
<modals-player-queue-items-modal v-model="showPlayerQueueItemsModal" />
|
||||||
|
|
||||||
<modals-player-settings-modal v-model="showPlayerSettingsModal" />
|
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
@@ -81,7 +78,6 @@ export default {
|
|||||||
currentTime: 0,
|
currentTime: 0,
|
||||||
showSleepTimerModal: false,
|
showSleepTimerModal: false,
|
||||||
showPlayerQueueItemsModal: false,
|
showPlayerQueueItemsModal: false,
|
||||||
showPlayerSettingsModal: false,
|
|
||||||
sleepTimerSet: false,
|
sleepTimerSet: false,
|
||||||
sleepTimerRemaining: 0,
|
sleepTimerRemaining: 0,
|
||||||
sleepTimerType: null,
|
sleepTimerType: null,
|
||||||
@@ -167,7 +163,7 @@ export default {
|
|||||||
},
|
},
|
||||||
podcastAuthor() {
|
podcastAuthor() {
|
||||||
if (!this.isPodcast) return null
|
if (!this.isPodcast) return null
|
||||||
return this.mediaMetadata.author || 'Unknown'
|
return this.mediaMetadata.author || this.$strings.LabelUnknown
|
||||||
},
|
},
|
||||||
hasNextItemInQueue() {
|
hasNextItemInQueue() {
|
||||||
return this.currentPlayerQueueIndex < this.playerQueueItems.length - 1
|
return this.currentPlayerQueueIndex < this.playerQueueItems.length - 1
|
||||||
@@ -251,7 +247,7 @@ export default {
|
|||||||
sleepTimerEnd() {
|
sleepTimerEnd() {
|
||||||
this.clearSleepTimer()
|
this.clearSleepTimer()
|
||||||
this.playerHandler.pause()
|
this.playerHandler.pause()
|
||||||
this.$toast.info('Sleep Timer Done.. zZzzZz')
|
this.$toast.info(this.$strings.ToastSleepTimerDone)
|
||||||
},
|
},
|
||||||
cancelSleepTimer() {
|
cancelSleepTimer() {
|
||||||
this.showSleepTimerModal = false
|
this.showSleepTimerModal = false
|
||||||
@@ -525,7 +521,7 @@ export default {
|
|||||||
},
|
},
|
||||||
showFailedProgressSyncs() {
|
showFailedProgressSyncs() {
|
||||||
if (!isNaN(this.syncFailedToast)) this.$toast.dismiss(this.syncFailedToast)
|
if (!isNaN(this.syncFailedToast)) this.$toast.dismiss(this.syncFailedToast)
|
||||||
this.syncFailedToast = this.$toast('Progress is not being synced. Restart playback', { timeout: false, type: 'error' })
|
this.syncFailedToast = this.$toast(this.$strings.ToastProgressIsNotBeingSynced, { timeout: false, type: 'error' })
|
||||||
},
|
},
|
||||||
sessionClosedEvent(sessionId) {
|
sessionClosedEvent(sessionId) {
|
||||||
if (this.playerHandler.currentSessionId === sessionId) {
|
if (this.playerHandler.currentSessionId === sessionId) {
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="w-20 bg-bg h-full fixed left-0 box-shadow-side z-50" style="min-width: 80px" :style="{ top: offsetTop + 'px' }">
|
<div role="toolbar" aria-orientation="vertical" aria-label="Library Sidebar" class="w-20 bg-bg h-full fixed left-0 box-shadow-side z-50" style="min-width: 80px" :style="{ top: offsetTop + 'px' }">
|
||||||
<!-- ugly little workaround to cover up the shadow overlapping the bookshelf toolbar -->
|
<!-- ugly little workaround to cover up the shadow overlapping the bookshelf toolbar -->
|
||||||
<div v-if="isShowingBookshelfToolbar" class="absolute top-0 -right-4 w-4 bg-bg h-10 pointer-events-none" />
|
<div v-if="isShowingBookshelfToolbar" class="absolute top-0 -right-4 w-4 bg-bg h-10 pointer-events-none" />
|
||||||
|
|
||||||
<div id="siderail-buttons-container" :class="{ 'player-open': streamLibraryItem }" class="w-full overflow-y-auto overflow-x-hidden">
|
<div id="siderail-buttons-container" role="navigation" aria-label="Library Navigation" :class="{ 'player-open': streamLibraryItem }" class="w-full overflow-y-auto overflow-x-hidden">
|
||||||
<nuxt-link :to="`/library/${currentLibraryId}`" class="w-full h-20 flex flex-col items-center justify-center text-white border-b border-primary border-opacity-70 hover:bg-primary cursor-pointer relative" :class="homePage ? 'bg-primary bg-opacity-80' : 'bg-bg bg-opacity-60'">
|
<nuxt-link :to="`/library/${currentLibraryId}`" class="w-full h-20 flex flex-col items-center justify-center text-white border-b border-primary border-opacity-70 hover:bg-primary cursor-pointer relative" :class="homePage ? 'bg-primary bg-opacity-80' : 'bg-bg bg-opacity-60'">
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" class="h-6 w-6" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
<svg xmlns="http://www.w3.org/2000/svg" class="h-6 w-6" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M3 12l2-2m0 0l7-7 7 7M5 10v10a1 1 0 001 1h3m10-11l2 2m-2-2v10a1 1 0 01-1 1h-3m-6 0a1 1 0 001-1v-4a1 1 0 011-1h2a1 1 0 011 1v4a1 1 0 001 1m-6 0h6" />
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M3 12l2-2m0 0l7-7 7 7M5 10v10a1 1 0 001 1h3m10-11l2 2m-2-2v10a1 1 0 01-1 1h-3m-6 0a1 1 0 001-1v-4a1 1 0 011-1h2a1 1 0 011 1v4a1 1 0 001 1m-6 0h6" />
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="pb-3e" :style="{ minWidth: cardWidth + 'px', maxWidth: cardWidth + 'px' }">
|
<article class="pb-3e" :style="{ minWidth: cardWidth + 'px', maxWidth: cardWidth + 'px' }">
|
||||||
<nuxt-link :to="`/author/${author?.id}`">
|
<nuxt-link :to="`/author/${author?.id}`">
|
||||||
<div cy-id="card" @mouseover="mouseover" @mouseleave="mouseleave">
|
<div cy-id="card" @mouseover="mouseover" @mouseleave="mouseleave">
|
||||||
<div cy-id="imageArea" :style="{ height: cardHeight + 'px' }" class="bg-primary box-shadow-book rounded-md relative overflow-hidden">
|
<div cy-id="imageArea" :style="{ height: cardHeight + 'px' }" class="bg-primary box-shadow-book rounded-md relative overflow-hidden">
|
||||||
@@ -34,7 +34,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</nuxt-link>
|
</nuxt-link>
|
||||||
</div>
|
</article>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
@@ -68,6 +68,9 @@ export default {
|
|||||||
cardHeight() {
|
cardHeight() {
|
||||||
return this.height * this.sizeMultiplier
|
return this.height * this.sizeMultiplier
|
||||||
},
|
},
|
||||||
|
coverHeight() {
|
||||||
|
return this.cardHeight
|
||||||
|
},
|
||||||
userToken() {
|
userToken() {
|
||||||
return this.store.getters['user/getToken']
|
return this.store.getters['user/getToken']
|
||||||
},
|
},
|
||||||
@@ -125,12 +128,15 @@ export default {
|
|||||||
return null
|
return null
|
||||||
})
|
})
|
||||||
if (!response) {
|
if (!response) {
|
||||||
this.$toast.error(`Author ${this.name} not found`)
|
this.$toast.error(this.$getString('ToastAuthorNotFound', [this.name]))
|
||||||
} else if (response.updated) {
|
} else if (response.updated) {
|
||||||
if (response.author.imagePath) this.$toast.success(`Author ${response.author.name} was updated`)
|
if (response.author.imagePath) {
|
||||||
else this.$toast.success(`Author ${response.author.name} was updated (no image found)`)
|
this.$toast.success(this.$strings.ToastAuthorUpdateSuccess)
|
||||||
} else {
|
} else {
|
||||||
this.$toast.info(`No updates were made for Author ${response.author.name}`)
|
this.$toast.success(this.$strings.ToastAuthorUpdateSuccessNoImageFound)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
this.$toast.info(this.$strings.ToastNoUpdatesNecessary)
|
||||||
}
|
}
|
||||||
this.searching = false
|
this.searching = false
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
<template>
|
<template>
|
||||||
<div ref="card" :id="`book-card-${index}`" :style="{ minWidth: coverWidth + 'px', maxWidth: coverWidth + 'px' }" class="absolute rounded-sm z-10 cursor-pointer" @mousedown.prevent @mouseup.prevent @mousemove.prevent @mouseover="mouseover" @mouseleave="mouseleave" @click="clickCard">
|
<article ref="card" :id="`book-card-${index}`" tabindex="0" :aria-label="displayTitle" :style="{ minWidth: coverWidth + 'px', maxWidth: coverWidth + 'px' }" class="absolute rounded-sm z-10 cursor-pointer" @mousedown.prevent @mouseup.prevent @mousemove.prevent @mouseover="mouseover" @mouseleave="mouseleave" @click="clickCard">
|
||||||
<div :id="`cover-area-${index}`" class="relative w-full top-0 left-0 rounded overflow-hidden z-10 bg-primary box-shadow-book" :style="{ height: coverHeight + 'px ' }">
|
<div :id="`cover-area-${index}`" class="relative w-full top-0 left-0 rounded overflow-hidden z-10 bg-primary box-shadow-book" :style="{ height: coverHeight + 'px ' }">
|
||||||
<!-- When cover image does not fill -->
|
<!-- When cover image does not fill -->
|
||||||
<div cy-id="coverBg" v-show="showCoverBg" class="absolute top-0 left-0 w-full h-full overflow-hidden rounded-sm bg-primary">
|
<div cy-id="coverBg" v-show="showCoverBg" class="absolute top-0 left-0 w-full h-full overflow-hidden rounded-sm bg-primary">
|
||||||
@@ -14,21 +14,21 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="w-full h-full absolute top-0 left-0 rounded overflow-hidden z-10">
|
<div class="w-full h-full absolute top-0 left-0 rounded overflow-hidden z-10">
|
||||||
<div cy-id="titleImageNotReady" v-show="libraryItem && !imageReady" class="absolute top-0 left-0 w-full h-full flex items-center justify-center" :style="{ padding: 0.5 + 'em' }">
|
<div cy-id="titleImageNotReady" v-show="libraryItem && !imageReady" aria-hidden="true" class="absolute top-0 left-0 w-full h-full flex items-center justify-center" :style="{ padding: 0.5 + 'em' }">
|
||||||
<p :style="{ fontSize: 0.8 + 'em' }" class="text-gray-300 text-center">{{ title }}</p>
|
<p :style="{ fontSize: 0.8 + 'em' }" class="text-gray-300 text-center">{{ title }}</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Cover Image -->
|
<!-- Cover Image -->
|
||||||
<img cy-id="coverImage" v-show="libraryItem" ref="cover" :src="bookCoverSrc" class="relative w-full h-full transition-opacity duration-300" :class="showCoverBg ? 'object-contain' : 'object-fill'" @load="imageLoaded" :style="{ opacity: imageReady ? 1 : 0 }" />
|
<img cy-id="coverImage" v-if="libraryItem" :alt="`${displayTitle}, ${$strings.LabelCover}`" ref="cover" aria-hidden="true" :src="bookCoverSrc" class="relative w-full h-full transition-opacity duration-300" :class="showCoverBg ? 'object-contain' : 'object-fill'" @load="imageLoaded" :style="{ opacity: imageReady ? 1 : 0 }" />
|
||||||
|
|
||||||
<!-- Placeholder Cover Title & Author -->
|
<!-- Placeholder Cover Title & Author -->
|
||||||
<div cy-id="placeholderTitle" v-if="!hasCover" class="absolute top-0 left-0 right-0 bottom-0 w-full h-full flex items-center justify-center" :style="{ padding: placeholderCoverPadding + 'em' }">
|
<div cy-id="placeholderTitle" v-if="!hasCover" class="absolute top-0 left-0 right-0 bottom-0 w-full h-full flex items-center justify-center" :style="{ padding: placeholderCoverPadding + 'em' }">
|
||||||
<div>
|
<div>
|
||||||
<p cy-id="placeholderTitleText" class="text-center" style="color: rgb(247 223 187)" :style="{ fontSize: titleFontSize + 'em' }">{{ titleCleaned }}</p>
|
<p cy-id="placeholderTitleText" aria-hidden="true" class="text-center" style="color: rgb(247 223 187)" :style="{ fontSize: titleFontSize + 'em' }">{{ titleCleaned }}</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div cy-id="placeholderAuthor" v-if="!hasCover" class="absolute left-0 right-0 w-full flex items-center justify-center" :style="{ padding: placeholderCoverPadding + 'em', bottom: authorBottom + 'em' }">
|
<div cy-id="placeholderAuthor" v-if="!hasCover" class="absolute left-0 right-0 w-full flex items-center justify-center" :style="{ padding: placeholderCoverPadding + 'em', bottom: authorBottom + 'em' }">
|
||||||
<p cy-id="placeholderAuthorText" class="text-center" style="color: rgb(247 223 187); opacity: 0.75" :style="{ fontSize: authorFontSize + 'em' }">{{ authorCleaned }}</p>
|
<p cy-id="placeholderAuthorText" aria-hidden="true" class="text-center" style="color: rgb(247 223 187); opacity: 0.75" :style="{ fontSize: authorFontSize + 'em' }">{{ authorCleaned }}</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div v-if="seriesSequenceList" class="absolute rounded-lg bg-black bg-opacity-90 box-shadow-md z-20 text-right" :style="{ top: 0.375 + 'em', right: 0.375 + 'em', padding: `${0.1}em ${0.25}em` }" style="background-color: #78350f">
|
<div v-if="seriesSequenceList" class="absolute rounded-lg bg-black bg-opacity-90 box-shadow-md z-20 text-right" :style="{ top: 0.375 + 'em', right: 0.375 + 'em', padding: `${0.1}em ${0.25}em` }" style="background-color: #78350f">
|
||||||
@@ -93,11 +93,11 @@
|
|||||||
|
|
||||||
<!-- rss feed icon -->
|
<!-- rss feed icon -->
|
||||||
<div cy-id="rssFeed" v-if="rssFeed && !isSelectionMode && !isHovering" class="absolute text-success top-0 left-0 z-10" :style="{ padding: 0.375 + 'em' }">
|
<div cy-id="rssFeed" v-if="rssFeed && !isSelectionMode && !isHovering" class="absolute text-success top-0 left-0 z-10" :style="{ padding: 0.375 + 'em' }">
|
||||||
<span class="material-symbols" :style="{ fontSize: 1.5 + 'em' }">rss_feed</span>
|
<span class="material-symbols" aria-hidden="true" :style="{ fontSize: 1.5 + 'em' }">rss_feed</span>
|
||||||
</div>
|
</div>
|
||||||
<!-- media item shared icon -->
|
<!-- media item shared icon -->
|
||||||
<div cy-id="mediaItemShare" v-if="mediaItemShare && !isSelectionMode && !isHovering" class="absolute text-success left-0 z-10" :style="{ padding: 0.375 + 'em', top: rssFeed ? '2em' : '0px' }">
|
<div cy-id="mediaItemShare" v-if="mediaItemShare && !isSelectionMode && !isHovering" class="absolute text-success left-0 z-10" :style="{ padding: 0.375 + 'em', top: rssFeed ? '2em' : '0px' }">
|
||||||
<span class="material-symbols" :style="{ fontSize: 1.5 + 'em' }">public</span>
|
<span class="material-symbols" aria-hidden="true" :style="{ fontSize: 1.5 + 'em' }">public</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Series sequence -->
|
<!-- Series sequence -->
|
||||||
@@ -114,7 +114,7 @@
|
|||||||
|
|
||||||
<!-- Podcast Num Episodes -->
|
<!-- Podcast Num Episodes -->
|
||||||
<div cy-id="numEpisodes" v-else-if="!numEpisodesIncomplete && numEpisodes && !isHovering && !isSelectionMode" class="absolute rounded-full bg-black bg-opacity-90 box-shadow-md z-10 flex items-center justify-center" :style="{ top: 0.375 + 'em', right: 0.375 + 'em', width: 1.25 + 'em', height: 1.25 + 'em' }">
|
<div cy-id="numEpisodes" v-else-if="!numEpisodesIncomplete && numEpisodes && !isHovering && !isSelectionMode" class="absolute rounded-full bg-black bg-opacity-90 box-shadow-md z-10 flex items-center justify-center" :style="{ top: 0.375 + 'em', right: 0.375 + 'em', width: 1.25 + 'em', height: 1.25 + 'em' }">
|
||||||
<p :style="{ fontSize: 0.8 + 'em' }">{{ numEpisodes }}</p>
|
<p :style="{ fontSize: 0.8 + 'em' }" role="status" :aria-label="$strings.LabelNumberOfEpisodes">{{ numEpisodes }}</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Podcast Num Episodes -->
|
<!-- Podcast Num Episodes -->
|
||||||
@@ -128,7 +128,7 @@
|
|||||||
<div cy-id="detailBottom" :id="`description-area-${index}`" v-if="isAlternativeBookshelfView || isAuthorBookshelfView" dir="auto" class="relative mt-2e mb-2e left-0 z-50 w-full">
|
<div cy-id="detailBottom" :id="`description-area-${index}`" v-if="isAlternativeBookshelfView || isAuthorBookshelfView" dir="auto" class="relative mt-2e mb-2e left-0 z-50 w-full">
|
||||||
<div :style="{ fontSize: 0.9 + 'em' }">
|
<div :style="{ fontSize: 0.9 + 'em' }">
|
||||||
<ui-tooltip v-if="displayTitle" :text="displayTitle" :disabled="!displayTitleTruncated" direction="bottom" :delayOnShow="500" class="flex items-center">
|
<ui-tooltip v-if="displayTitle" :text="displayTitle" :disabled="!displayTitleTruncated" direction="bottom" :delayOnShow="500" class="flex items-center">
|
||||||
<p cy-id="title" ref="displayTitle" class="truncate">{{ displayTitle }}</p>
|
<p cy-id="title" ref="displayTitle" aria-hidden="true" class="truncate">{{ displayTitle }}</p>
|
||||||
<widgets-explicit-indicator cy-id="explicitIndicator" v-if="isExplicit" />
|
<widgets-explicit-indicator cy-id="explicitIndicator" v-if="isExplicit" />
|
||||||
</ui-tooltip>
|
</ui-tooltip>
|
||||||
</div>
|
</div>
|
||||||
@@ -138,7 +138,7 @@
|
|||||||
<p cy-id="line2" class="truncate text-gray-400" :style="{ fontSize: 0.8 + 'em' }">{{ displayLineTwo || ' ' }}</p>
|
<p cy-id="line2" class="truncate text-gray-400" :style="{ fontSize: 0.8 + 'em' }">{{ displayLineTwo || ' ' }}</p>
|
||||||
<p cy-id="line3" v-if="displaySortLine" class="truncate text-gray-400" :style="{ fontSize: 0.8 + 'em' }">{{ displaySortLine }}</p>
|
<p cy-id="line3" v-if="displaySortLine" class="truncate text-gray-400" :style="{ fontSize: 0.8 + 'em' }">{{ displaySortLine }}</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</article>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
<template>
|
<template>
|
||||||
<div ref="card" :id="`collection-card-${index}`" :style="{ width: cardWidth + 'px' }" class="absolute top-0 left-0 rounded-sm z-30 cursor-pointer" @mousedown.prevent @mouseup.prevent @mousemove.prevent @mouseover="mouseover" @mouseleave="mouseleave" @click="clickCard">
|
<div ref="card" :id="`collection-card-${index}`" role="button" :style="{ width: cardWidth + 'px' }" class="absolute top-0 left-0 rounded-sm z-30 cursor-pointer" @mousedown.prevent @mouseup.prevent @mousemove.prevent @mouseover="mouseover" @mouseleave="mouseleave" @click="clickCard">
|
||||||
<div class="relative" :style="{ height: coverHeight + 'px' }">
|
<div class="relative" :style="{ height: coverHeight + 'px' }">
|
||||||
<div class="absolute top-0 left-0 w-full box-shadow-book shadow-height" />
|
<div class="absolute top-0 left-0 w-full box-shadow-book shadow-height" />
|
||||||
<div class="w-full h-full bg-primary relative rounded overflow-hidden">
|
<div class="w-full h-full bg-primary relative rounded overflow-hidden">
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
<template>
|
<template>
|
||||||
<div ref="card" :id="`playlist-card-${index}`" :style="{ width: cardWidth + 'px', fontSize: sizeMultiplier + 'rem' }" class="absolute top-0 left-0 rounded-sm z-30 cursor-pointer" @mousedown.prevent @mouseup.prevent @mousemove.prevent @mouseover="mouseover" @mouseleave="mouseleave" @click="clickCard">
|
<div ref="card" :id="`playlist-card-${index}`" role="button" :style="{ width: cardWidth + 'px', fontSize: sizeMultiplier + 'rem' }" class="absolute top-0 left-0 rounded-sm z-30 cursor-pointer" @mousedown.prevent @mouseup.prevent @mousemove.prevent @mouseover="mouseover" @mouseleave="mouseleave" @click="clickCard">
|
||||||
<div class="relative" :style="{ height: coverHeight + 'px' }">
|
<div class="relative" :style="{ height: coverHeight + 'px' }">
|
||||||
<div class="absolute top-0 left-0 w-full box-shadow-book shadow-height" />
|
<div class="absolute top-0 left-0 w-full box-shadow-book shadow-height" />
|
||||||
<div class="w-full h-full bg-primary relative rounded overflow-hidden">
|
<div class="w-full h-full bg-primary relative rounded overflow-hidden">
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
<template>
|
<template>
|
||||||
<div cy-id="card" ref="card" :id="`series-card-${index}`" :style="{ width: cardWidth + 'px' }" class="absolute rounded-sm z-30 cursor-pointer" @mousedown.prevent @mouseup.prevent @mousemove.prevent @mouseover="mouseover" @mouseleave="mouseleave" @click="clickCard">
|
<article cy-id="card" ref="card" :id="`series-card-${index}`" tabindex="0" :aria-label="displayTitle" :style="{ width: cardWidth + 'px' }" class="absolute rounded-sm z-30 cursor-pointer" @mousedown.prevent @mouseup.prevent @mousemove.prevent @mouseover="mouseover" @mouseleave="mouseleave" @click="clickCard">
|
||||||
<div cy-id="covers-area" class="relative" :style="{ height: coverHeight + 'px' }">
|
<div cy-id="covers-area" class="relative" :style="{ height: coverHeight + 'px' }">
|
||||||
<div class="absolute top-0 left-0 w-full box-shadow-book shadow-height" />
|
<div class="absolute top-0 left-0 w-full box-shadow-book shadow-height" />
|
||||||
<div class="w-full h-full bg-primary relative rounded overflow-hidden z-0">
|
<div class="w-full h-full bg-primary relative rounded overflow-hidden z-0">
|
||||||
@@ -7,12 +7,12 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div cy-id="seriesLengthMarker" class="absolute rounded-lg bg-black bg-opacity-90 box-shadow-md z-20" :style="{ top: 0.375 + 'em', right: 0.375 + 'em', padding: `0.1em 0.25em` }" style="background-color: #cd9d49dd">
|
<div cy-id="seriesLengthMarker" class="absolute rounded-lg bg-black bg-opacity-90 box-shadow-md z-20" :style="{ top: 0.375 + 'em', right: 0.375 + 'em', padding: `0.1em 0.25em` }" style="background-color: #cd9d49dd">
|
||||||
<p :style="{ fontSize: 0.8 + 'em' }">{{ books.length }}</p>
|
<p :style="{ fontSize: 0.8 + 'em' }" role="status" :aria-label="$strings.LabelNumberOfBooks">{{ books.length }}</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div cy-id="seriesProgressBar" v-if="seriesPercentInProgress > 0" class="absolute bottom-0 left-0 h-1e shadow-sm max-w-full z-10 rounded-b w-full" :class="isSeriesFinished ? 'bg-success' : 'bg-yellow-400'" :style="{ width: seriesPercentInProgress * 100 + '%' }" />
|
<div cy-id="seriesProgressBar" v-if="seriesPercentInProgress > 0" class="absolute bottom-0 left-0 h-1e shadow-sm max-w-full z-10 rounded-b w-full" :class="isSeriesFinished ? 'bg-success' : 'bg-yellow-400'" :style="{ width: seriesPercentInProgress * 100 + '%' }" />
|
||||||
|
|
||||||
<div cy-id="hoveringDisplayTitle" v-if="hasValidCovers" class="bg-black bg-opacity-60 absolute top-0 left-0 w-full h-full flex items-center justify-center text-center transition-opacity" :class="isHovering ? '' : 'opacity-0'" :style="{ padding: '1em' }">
|
<div cy-id="hoveringDisplayTitle" v-if="hasValidCovers" aria-hidden="true" class="bg-black bg-opacity-60 absolute top-0 left-0 w-full h-full flex items-center justify-center text-center transition-opacity" :class="isHovering ? '' : 'opacity-0'" :style="{ padding: '1em' }">
|
||||||
<p :style="{ fontSize: 1.2 + 'em' }">{{ displayTitle }}</p>
|
<p :style="{ fontSize: 1.2 + 'em' }">{{ displayTitle }}</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -21,14 +21,14 @@
|
|||||||
|
|
||||||
<div cy-id="standardBottomText" v-if="!isAlternativeBookshelfView" class="categoryPlacard absolute z-10 left-0 right-0 mx-auto -bottom-6e h-6e rounded-md text-center" :style="{ width: Math.min(200, cardWidth) + 'px' }">
|
<div cy-id="standardBottomText" v-if="!isAlternativeBookshelfView" class="categoryPlacard absolute z-10 left-0 right-0 mx-auto -bottom-6e h-6e rounded-md text-center" :style="{ width: Math.min(200, cardWidth) + 'px' }">
|
||||||
<div class="w-full h-full shinyBlack flex items-center justify-center rounded-sm border" :style="{ padding: `0em 0.5em` }">
|
<div class="w-full h-full shinyBlack flex items-center justify-center rounded-sm border" :style="{ padding: `0em 0.5em` }">
|
||||||
<p cy-id="standardBottomDisplayTitle" class="truncate" :style="{ fontSize: labelFontSize + 'em' }">{{ displayTitle }}</p>
|
<p cy-id="standardBottomDisplayTitle" class="truncate" aria-hidden="true" :style="{ fontSize: labelFontSize + 'em' }">{{ displayTitle }}</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div cy-id="detailBottomText" v-else class="relative z-30 left-0 right-0 mx-auto py-1e rounded-md text-center">
|
<div cy-id="detailBottomText" v-else class="relative z-30 left-0 right-0 mx-auto py-1e rounded-md text-center">
|
||||||
<p cy-id="detailBottomDisplayTitle" class="truncate" :style="{ fontSize: labelFontSize + 'em' }">{{ displayTitle }}</p>
|
<p cy-id="detailBottomDisplayTitle" class="truncate" aria-hidden="true" :style="{ fontSize: labelFontSize + 'em' }">{{ displayTitle }}</p>
|
||||||
<p cy-id="detailBottomSortLine" v-if="displaySortLine" class="truncate text-gray-400" :style="{ fontSize: 0.8 + 'em' }">{{ displaySortLine }}</p>
|
<p cy-id="detailBottomSortLine" v-if="displaySortLine" class="truncate text-gray-400" :style="{ fontSize: 0.8 + 'em' }">{{ displaySortLine }}</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</article>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
|
|||||||
@@ -1,15 +1,15 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="">
|
<div class="">
|
||||||
<div class="w-full relative sm:w-80">
|
<div class="w-full relative sm:w-80">
|
||||||
<form @submit.prevent="submitSearch">
|
<form role="search" @submit.prevent="submitSearch">
|
||||||
<ui-text-input ref="input" v-model="search" :placeholder="$strings.PlaceholderSearch" @input="inputUpdate" @focus="focussed" @blur="blurred" class="w-full h-8 text-sm" />
|
<ui-text-input ref="input" v-model="search" :placeholder="$strings.PlaceholderSearch" @input="inputUpdate" @focus="focussed" @blur="blurred" class="w-full h-8 text-sm" />
|
||||||
</form>
|
</form>
|
||||||
<div class="absolute top-0 right-0 bottom-0 h-full flex items-center px-2 text-gray-400 cursor-pointer" @click="clickClear">
|
<button :aria-hidden="!search" class="absolute top-0 right-0 bottom-0 h-full flex items-center px-2 text-gray-400 cursor-pointer" @click="clickClear">
|
||||||
<span v-if="!search" class="material-symbols" style="font-size: 1.2rem"></span>
|
<span v-if="!search" class="material-symbols" style="font-size: 1.2rem"></span>
|
||||||
<span v-else class="material-symbols" style="font-size: 1.2rem">close</span>
|
<span v-else class="material-symbols" style="font-size: 1.2rem">close</span>
|
||||||
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
<div v-show="showMenu && (lastSearch || isTyping)" class="absolute z-40 -mt-px w-full max-w-64 sm:max-w-80 sm:w-80 bg-bg border border-black-200 shadow-lg rounded-md py-1 px-2 text-base ring-1 ring-black ring-opacity-5 overflow-auto focus:outline-none sm:text-sm globalSearchMenu" @mousedown.stop.prevent>
|
||||||
<div v-show="showMenu && (lastSearch || isTyping)" class="absolute z-40 -mt-px w-full max-w-64 sm:max-w-80 sm:w-80 bg-bg border border-black-200 shadow-lg rounded-md py-1 px-2 text-base ring-1 ring-black ring-opacity-5 overflow-auto focus:outline-none sm:text-sm globalSearchMenu">
|
|
||||||
<ul class="h-full w-full" role="listbox" aria-labelledby="listbox-label">
|
<ul class="h-full w-full" role="listbox" aria-labelledby="listbox-label">
|
||||||
<li v-if="isTyping" class="py-2 px-2">
|
<li v-if="isTyping" class="py-2 px-2">
|
||||||
<p>{{ $strings.MessageThinking }}</p>
|
<p>{{ $strings.MessageThinking }}</p>
|
||||||
@@ -157,7 +157,7 @@ export default {
|
|||||||
clearTimeout(this.focusTimeout)
|
clearTimeout(this.focusTimeout)
|
||||||
this.focusTimeout = setTimeout(() => {
|
this.focusTimeout = setTimeout(() => {
|
||||||
this.showMenu = false
|
this.showMenu = false
|
||||||
}, 200)
|
}, 100)
|
||||||
},
|
},
|
||||||
async runSearch(value) {
|
async runSearch(value) {
|
||||||
this.lastSearch = value
|
this.lastSearch = value
|
||||||
|
|||||||
@@ -1,28 +1,30 @@
|
|||||||
<template>
|
<template>
|
||||||
<div ref="wrapper" class="relative" v-click-outside="clickOutside">
|
<div ref="wrapper" class="relative" v-click-outside="clickOutside">
|
||||||
<button type="button" class="relative w-full h-full bg-bg border border-gray-500 hover:border-gray-400 rounded shadow-sm pl-3 pr-3 py-0 text-left focus:outline-none sm:text-sm cursor-pointer" aria-haspopup="listbox" aria-expanded="true" aria-labelledby="listbox-label" @click.prevent="showMenu = !showMenu">
|
<div class="relative h-7">
|
||||||
|
<button type="button" class="relative w-full h-full bg-bg border border-gray-500 hover:border-gray-400 rounded shadow-sm pl-3 pr-3 py-0 text-left focus:outline-none sm:text-sm cursor-pointer" aria-haspopup="menu" :aria-expanded="showMenu" @click.prevent="showMenu = !showMenu">
|
||||||
<span class="flex items-center justify-between">
|
<span class="flex items-center justify-between">
|
||||||
<span class="block truncate text-xs" :class="!selectedText ? 'text-gray-300' : ''">{{ selectedText }}</span>
|
<span class="block truncate text-xs" :class="!selectedText ? 'text-gray-300' : ''">{{ selectedText }}</span>
|
||||||
</span>
|
</span>
|
||||||
|
</button>
|
||||||
<span v-if="selected === 'all'" class="ml-3 absolute inset-y-0 right-0 flex items-center pr-2 pointer-events-none">
|
<span v-if="selected === 'all'" class="ml-3 absolute inset-y-0 right-0 flex items-center pr-2 pointer-events-none">
|
||||||
<svg class="h-5 w-5 text-gray-400" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true">
|
<svg class="h-5 w-5 text-gray-400" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true">
|
||||||
<path fill-rule="evenodd" d="M10 3a1 1 0 01.707.293l3 3a1 1 0 01-1.414 1.414L10 5.414 7.707 7.707a1 1 0 01-1.414-1.414l3-3A1 1 0 0110 3zm-3.707 9.293a1 1 0 011.414 0L10 14.586l2.293-2.293a1 1 0 011.414 1.414l-3 3a1 1 0 01-1.414 0l-3-3a1 1 0 010-1.414z" clip-rule="evenodd" />
|
<path fill-rule="evenodd" d="M10 3a1 1 0 01.707.293l3 3a1 1 0 01-1.414 1.414L10 5.414 7.707 7.707a1 1 0 01-1.414-1.414l3-3A1 1 0 0110 3zm-3.707 9.293a1 1 0 011.414 0L10 14.586l2.293-2.293a1 1 0 011.414 1.414l-3 3a1 1 0 01-1.414 0l-3-3a1 1 0 010-1.414z" clip-rule="evenodd" />
|
||||||
</svg>
|
</svg>
|
||||||
</span>
|
</span>
|
||||||
<div v-else class="ml-3 absolute inset-y-0 right-0 flex items-center pr-2 cursor-pointer text-gray-400 hover:text-gray-200" @mousedown.stop @mouseup.stop @click.stop.prevent="clearSelected">
|
<button v-else :aria-label="$strings.ButtonClearFilter" class="ml-3 absolute inset-y-0 right-0 flex items-center pr-2 cursor-pointer text-gray-400 hover:text-gray-200" @mousedown.stop @mouseup.stop @click.stop.prevent="clearSelected">
|
||||||
<span class="material-symbols" style="font-size: 1.1rem">close</span>
|
<span class="material-symbols" style="font-size: 1.1rem">close</span>
|
||||||
</div>
|
|
||||||
</button>
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div v-show="showMenu" class="absolute z-10 mt-1 w-full bg-bg border border-black-200 shadow-lg rounded-md py-1 ring-1 ring-black ring-opacity-5 overflow-auto focus:outline-none text-sm libraryFilterMenu">
|
<div v-show="showMenu" class="absolute z-10 mt-1 w-full bg-bg border border-black-200 shadow-lg rounded-md py-1 ring-1 ring-black ring-opacity-5 overflow-auto focus:outline-none text-sm libraryFilterMenu">
|
||||||
<ul v-show="!sublist" class="h-full w-full" role="listbox" aria-labelledby="listbox-label">
|
<ul v-show="!sublist" class="h-full w-full" role="menu">
|
||||||
<template v-for="item in selectItems">
|
<template v-for="item in selectItems">
|
||||||
<li :key="item.value" class="select-none relative py-2 pr-9 cursor-pointer hover:bg-white/5" :class="item.value === selected ? 'bg-white/5 text-yellow-400' : 'text-gray-200 hover:text-white'" role="option" @click="clickedOption(item)">
|
<li :key="item.value" class="select-none relative py-2 pr-9 cursor-pointer hover:bg-white/5" :class="item.value === selected ? 'bg-white/5 text-yellow-400' : 'text-gray-200 hover:text-white'" role="menuitem" :aria-haspopup="item.sublist ? '' : 'menu'" @click="clickedOption(item)">
|
||||||
<div class="flex items-center justify-between">
|
<div class="flex items-center justify-between">
|
||||||
<span class="font-normal ml-3 block truncate text-sm">{{ item.text }}</span>
|
<span class="font-normal ml-3 block truncate text-sm">{{ item.text }}</span>
|
||||||
</div>
|
</div>
|
||||||
<div v-if="item.sublist" class="absolute right-1 top-0 bottom-0 h-full flex items-center">
|
<div v-if="item.sublist" class="absolute right-1 top-0 bottom-0 h-full flex items-center">
|
||||||
<span class="material-symbols text-2xl">arrow_right</span>
|
<span class="material-symbols text-2xl" :aria-label="$strings.LabelMore">arrow_right</span>
|
||||||
</div>
|
</div>
|
||||||
<!-- selected checkmark icon -->
|
<!-- selected checkmark icon -->
|
||||||
<div v-if="item.value === selected" class="absolute inset-y-0 right-2 h-full flex items-center pointer-events-none">
|
<div v-if="item.value === selected" class="absolute inset-y-0 right-2 h-full flex items-center pointer-events-none">
|
||||||
@@ -31,8 +33,8 @@
|
|||||||
</li>
|
</li>
|
||||||
</template>
|
</template>
|
||||||
</ul>
|
</ul>
|
||||||
<ul v-show="sublist" class="h-full w-full" role="listbox" aria-labelledby="listbox-label">
|
<ul v-show="sublist" class="h-full w-full" role="menu">
|
||||||
<li class="text-gray-50 select-none relative py-2 pl-9 cursor-pointer hover:bg-white/5" role="option" @click="sublist = null">
|
<li class="text-gray-50 select-none relative py-2 pl-9 cursor-pointer hover:bg-white/5" role="menuitem" @click="sublist = null">
|
||||||
<div class="absolute left-1 top-0 bottom-0 h-full flex items-center">
|
<div class="absolute left-1 top-0 bottom-0 h-full flex items-center">
|
||||||
<span class="material-symbols text-2xl">arrow_left</span>
|
<span class="material-symbols text-2xl">arrow_left</span>
|
||||||
</div>
|
</div>
|
||||||
@@ -40,13 +42,13 @@
|
|||||||
<span class="font-normal block truncate">{{ $strings.ButtonBack }}</span>
|
<span class="font-normal block truncate">{{ $strings.ButtonBack }}</span>
|
||||||
</div>
|
</div>
|
||||||
</li>
|
</li>
|
||||||
<li v-if="!sublistItems.length" class="text-gray-400 select-none relative px-2" role="option">
|
<li v-if="!sublistItems.length" class="text-gray-400 select-none relative px-2" role="menuitem">
|
||||||
<div class="flex items-center justify-center">
|
<div class="flex items-center justify-center">
|
||||||
<span class="font-normal block truncate py-2">{{ $getString('LabelLibraryFilterSublistEmpty', [selectedSublistText]) }}</span>
|
<span class="font-normal block truncate py-2">{{ $getString('LabelLibraryFilterSublistEmpty', [selectedSublistText]) }}</span>
|
||||||
</div>
|
</div>
|
||||||
</li>
|
</li>
|
||||||
<template v-for="item in sublistItems">
|
<template v-for="item in sublistItems">
|
||||||
<li :key="item.value" class="select-none relative px-2 cursor-pointer hover:bg-white/5" :class="`${sublist}.${item.value}` === selected ? 'bg-white/5 text-yellow-400' : 'text-gray-200 hover:text-white'" role="option" @click="clickedSublistOption(item.value)">
|
<li :key="item.value" class="select-none relative px-2 cursor-pointer hover:bg-white/5" :class="`${sublist}.${item.value}` === selected ? 'bg-white/5 text-yellow-400' : 'text-gray-200 hover:text-white'" role="menuitem" @click="clickedSublistOption(item.value)">
|
||||||
<div class="flex items-center">
|
<div class="flex items-center">
|
||||||
<span class="font-normal truncate py-2 text-xs">{{ item.text }}</span>
|
<span class="font-normal truncate py-2 text-xs">{{ item.text }}</span>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,20 +1,20 @@
|
|||||||
<template>
|
<template>
|
||||||
<div ref="wrapper" class="relative" v-click-outside="clickOutside">
|
<div ref="wrapper" class="relative" v-click-outside="clickOutside">
|
||||||
<button type="button" class="relative w-full h-full bg-fg border border-gray-500 hover:border-gray-400 rounded shadow-sm pl-3 pr-3 py-0 text-left focus:outline-none sm:text-sm cursor-pointer" aria-haspopup="listbox" aria-expanded="true" aria-labelledby="listbox-label" @click.prevent="showMenu = !showMenu">
|
<button type="button" class="relative w-full h-full bg-fg border border-gray-500 hover:border-gray-400 rounded shadow-sm pl-3 pr-3 py-0 text-left focus:outline-none sm:text-sm cursor-pointer" aria-haspopup="menu" :aria-expanded="showMenu" @click.prevent="showMenu = !showMenu">
|
||||||
<span class="flex items-center justify-between">
|
<span class="flex items-center justify-between">
|
||||||
<span class="block truncate text-xs" :class="!selectedText ? 'text-gray-300' : ''">{{ selectedText }}</span>
|
<span class="block truncate text-xs" :class="!selectedText ? 'text-gray-300' : ''">{{ selectedText }}</span>
|
||||||
<span class="material-symbols text-lg text-yellow-400">{{ descending ? 'expand_more' : 'expand_less' }}</span>
|
<span class="material-symbols text-lg text-yellow-400" :aria-label="descending ? $strings.LabelSortDescending : $strings.LabelSortAscending">{{ descending ? 'expand_more' : 'expand_less' }}</span>
|
||||||
</span>
|
</span>
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
<ul v-show="showMenu" class="absolute z-10 mt-1 w-full bg-bg border border-black-200 shadow-lg max-h-96 rounded-md py-1 ring-1 ring-black ring-opacity-5 overflow-auto focus:outline-none text-sm" role="listbox" aria-labelledby="listbox-label">
|
<ul v-show="showMenu" class="absolute z-10 mt-1 w-full bg-bg border border-black-200 shadow-lg max-h-96 rounded-md py-1 ring-1 ring-black ring-opacity-5 overflow-auto focus:outline-none text-sm" role="menu">
|
||||||
<template v-for="item in selectItems">
|
<template v-for="item in selectItems">
|
||||||
<li :key="item.value" class="select-none relative py-2 pr-9 cursor-pointer hover:bg-white/5" :class="item.value === selected ? 'bg-white/5 text-yellow-400' : 'text-gray-200 hover:text-white'" role="option" @click="clickedOption(item.value)">
|
<li :key="item.value" class="select-none relative py-2 pr-9 cursor-pointer hover:bg-white/5" :class="item.value === selected ? 'bg-white/5 text-yellow-400' : 'text-gray-200 hover:text-white'" role="menuitem" @click="clickedOption(item.value)">
|
||||||
<div class="flex items-center">
|
<div class="flex items-center">
|
||||||
<span class="font-normal ml-3 block truncate">{{ item.text }}</span>
|
<span class="font-normal ml-3 block truncate">{{ item.text }}</span>
|
||||||
</div>
|
</div>
|
||||||
<span v-if="item.value === selected" class="text-yellow-400 absolute inset-y-0 right-0 flex items-center pr-4">
|
<span v-if="item.value === selected" class="text-yellow-400 absolute inset-y-0 right-0 flex items-center pr-4">
|
||||||
<span class="material-symbols text-xl">{{ descending ? 'expand_more' : 'expand_less' }}</span>
|
<span class="material-symbols text-xl" :aria-label="descending ? $strings.LabelSortDescending : $strings.LabelSortAscending">{{ descending ? 'expand_more' : 'expand_less' }}</span>
|
||||||
</span>
|
</span>
|
||||||
</li>
|
</li>
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
@@ -1,20 +1,20 @@
|
|||||||
<template>
|
<template>
|
||||||
<div ref="wrapper" class="relative" v-click-outside="clickOutside">
|
<div ref="wrapper" class="relative" v-click-outside="clickOutside">
|
||||||
<button type="button" class="relative w-full h-full border border-gray-500 hover:border-gray-400 rounded shadow-sm pl-3 pr-3 py-0 text-left focus:outline-none cursor-pointer" aria-haspopup="listbox" aria-expanded="true" aria-labelledby="listbox-label" @click.prevent="showMenu = !showMenu">
|
<button type="button" class="relative w-full h-full border border-gray-500 hover:border-gray-400 rounded shadow-sm pl-3 pr-3 py-0 text-left focus:outline-none cursor-pointer" aria-haspopup="menu" :aria-expanded="showMenu" @click.prevent="showMenu = !showMenu">
|
||||||
<span class="flex items-center justify-between">
|
<span class="flex items-center justify-between">
|
||||||
<span class="block truncate text-xs" :class="!selectedText ? 'text-gray-300' : ''">{{ selectedText }}</span>
|
<span class="block truncate text-xs" :class="!selectedText ? 'text-gray-300' : ''">{{ selectedText }}</span>
|
||||||
<span class="material-symbols text-lg text-yellow-400">{{ descending ? 'expand_more' : 'expand_less' }}</span>
|
<span class="material-symbols text-lg text-yellow-400" :aria-label="descending ? $strings.LabelSortDescending : $strings.LabelSortAscending">{{ descending ? 'expand_more' : 'expand_less' }}</span>
|
||||||
</span>
|
</span>
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
<ul v-show="showMenu" class="absolute z-10 mt-1 w-full bg-bg border border-black-200 shadow-lg max-h-80 rounded-md py-1 ring-1 ring-black ring-opacity-5 overflow-auto focus:outline-none text-sm" role="listbox" aria-labelledby="listbox-label">
|
<ul v-show="showMenu" class="absolute z-10 mt-1 w-full bg-bg border border-black-200 shadow-lg max-h-80 rounded-md py-1 ring-1 ring-black ring-opacity-5 overflow-auto focus:outline-none text-sm" role="menu">
|
||||||
<template v-for="item in items">
|
<template v-for="item in items">
|
||||||
<li :key="item.value" class="select-none relative py-2 pr-9 cursor-pointer hover:bg-white/5" :class="item.value === selected ? 'bg-white/5 text-yellow-400' : 'text-gray-200 hover:text-white'" role="option" @click="clickedOption(item.value)">
|
<li :key="item.value" class="select-none relative py-2 pr-9 cursor-pointer hover:bg-white/5" :class="item.value === selected ? 'bg-white/5 text-yellow-400' : 'text-gray-200 hover:text-white'" role="menuitem" @click="clickedOption(item.value)">
|
||||||
<div class="flex items-center">
|
<div class="flex items-center">
|
||||||
<span class="font-normal ml-3 block truncate">{{ item.text }}</span>
|
<span class="font-normal ml-3 block truncate">{{ item.text }}</span>
|
||||||
</div>
|
</div>
|
||||||
<span v-if="item.value === selected" class="text-yellow-400 absolute inset-y-0 right-0 flex items-center pr-4">
|
<span v-if="item.value === selected" class="text-yellow-400 absolute inset-y-0 right-0 flex items-center pr-4">
|
||||||
<span class="material-symbols text-xl">{{ descending ? 'expand_more' : 'expand_less' }}</span>
|
<span class="material-symbols text-xl" :aria-label="descending ? $strings.LabelSortDescending : $strings.LabelSortAscending">{{ descending ? 'expand_more' : 'expand_less' }}</span>
|
||||||
</span>
|
</span>
|
||||||
</li>
|
</li>
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
@@ -56,11 +56,7 @@ export default {
|
|||||||
},
|
},
|
||||||
imgSrc() {
|
imgSrc() {
|
||||||
if (!this.imagePath) return null
|
if (!this.imagePath) return null
|
||||||
if (process.env.NODE_ENV !== 'production') {
|
return `${this.$config.routerBasePath}/api/authors/${this.authorId}/image?ts=${this.updatedAt}`
|
||||||
// Testing
|
|
||||||
return `http://localhost:3333${this.$config.routerBasePath}/api/authors/${this.authorId}/image?token=${this.userToken}&ts=${this.updatedAt}`
|
|
||||||
}
|
|
||||||
return `/api/authors/${this.authorId}/image?token=${this.userToken}&ts=${this.updatedAt}`
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
|
|||||||
@@ -121,6 +121,8 @@ export default {
|
|||||||
|
|
||||||
var img = document.createElement('img')
|
var img = document.createElement('img')
|
||||||
img.src = src
|
img.src = src
|
||||||
|
img.alt = `${this.name}, ${this.$strings.LabelCover}`
|
||||||
|
img.ariaHidden = true
|
||||||
img.className = 'absolute top-0 left-0 w-full h-full'
|
img.className = 'absolute top-0 left-0 w-full h-full'
|
||||||
img.style.objectFit = showCoverBg ? 'contain' : 'cover'
|
img.style.objectFit = showCoverBg ? 'contain' : 'cover'
|
||||||
|
|
||||||
|
|||||||
@@ -69,6 +69,15 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div class="flex items-center my-2 max-w-md">
|
||||||
|
<div class="w-1/2">
|
||||||
|
<p id="ereader-permissions-toggle">{{ $strings.LabelPermissionsCreateEreader }}</p>
|
||||||
|
</div>
|
||||||
|
<div class="w-1/2">
|
||||||
|
<ui-toggle-switch labeledBy="ereader-permissions-toggle" v-model="newUser.permissions.createEreader" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div class="flex items-center my-2 max-w-md">
|
<div class="flex items-center my-2 max-w-md">
|
||||||
<div class="w-1/2">
|
<div class="w-1/2">
|
||||||
<p id="explicit-content-permissions-toggle">{{ $strings.LabelPermissionsAccessExplicitContent }}</p>
|
<p id="explicit-content-permissions-toggle">{{ $strings.LabelPermissionsAccessExplicitContent }}</p>
|
||||||
@@ -354,7 +363,8 @@ export default {
|
|||||||
accessExplicitContent: type === 'admin',
|
accessExplicitContent: type === 'admin',
|
||||||
accessAllLibraries: true,
|
accessAllLibraries: true,
|
||||||
accessAllTags: true,
|
accessAllTags: true,
|
||||||
selectedTagsNotAccessible: false
|
selectedTagsNotAccessible: false,
|
||||||
|
createEreader: type === 'admin'
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
init() {
|
init() {
|
||||||
@@ -387,7 +397,8 @@ export default {
|
|||||||
accessAllLibraries: true,
|
accessAllLibraries: true,
|
||||||
accessAllTags: true,
|
accessAllTags: true,
|
||||||
accessExplicitContent: false,
|
accessExplicitContent: false,
|
||||||
selectedTagsNotAccessible: false
|
selectedTagsNotAccessible: false,
|
||||||
|
createEreader: false
|
||||||
},
|
},
|
||||||
librariesAccessible: [],
|
librariesAccessible: [],
|
||||||
itemTagsSelected: []
|
itemTagsSelected: []
|
||||||
|
|||||||
@@ -54,8 +54,7 @@ export default {
|
|||||||
options: {
|
options: {
|
||||||
provider: undefined,
|
provider: undefined,
|
||||||
overrideDetails: true,
|
overrideDetails: true,
|
||||||
overrideCover: true,
|
overrideCover: true
|
||||||
overrideDefaults: true
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -99,8 +98,8 @@ export default {
|
|||||||
init() {
|
init() {
|
||||||
// If we don't have a set provider (first open of dialog) or we've switched library, set
|
// If we don't have a set provider (first open of dialog) or we've switched library, set
|
||||||
// the selected provider to the current library default provider
|
// the selected provider to the current library default provider
|
||||||
if (!this.options.provider || this.options.lastUsedLibrary != this.currentLibraryId) {
|
if (!this.options.provider || this.lastUsedLibrary != this.currentLibraryId) {
|
||||||
this.options.lastUsedLibrary = this.currentLibraryId
|
this.lastUsedLibrary = this.currentLibraryId
|
||||||
this.options.provider = this.libraryProvider
|
this.options.provider = this.libraryProvider
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -116,10 +115,10 @@ export default {
|
|||||||
libraryItemIds: this.selectedBookIds
|
libraryItemIds: this.selectedBookIds
|
||||||
})
|
})
|
||||||
.then(() => {
|
.then(() => {
|
||||||
this.$toast.info('Batch quick match of ' + this.selectedBookIds.length + ' books started!')
|
this.$toast.info(this.$getString('ToastBatchQuickMatchStarted', [this.selectedBookIds.length]))
|
||||||
})
|
})
|
||||||
.catch((error) => {
|
.catch((error) => {
|
||||||
this.$toast.error('Batch quick match failed')
|
this.$toast.error(this.$strings.ToastBatchQuickMatchFailed)
|
||||||
console.error('Failed to batch quick match', error)
|
console.error('Failed to batch quick match', error)
|
||||||
})
|
})
|
||||||
.finally(() => {
|
.finally(() => {
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
<template>
|
<template>
|
||||||
<div ref="wrapper" class="hidden absolute top-0 left-0 w-full h-full bg-black bg-opacity-50 rounded-lg items-center justify-center" style="z-index: 61" @click="clickClose">
|
<div ref="wrapper" role="dialog" aria-modal="true" class="hidden absolute top-0 left-0 w-full h-full bg-black bg-opacity-50 rounded-lg items-center justify-center" style="z-index: 61" @click="clickClose">
|
||||||
<div class="absolute top-3 right-3 md:top-5 md:right-5 h-8 w-8 md:h-12 md:w-12 flex items-center justify-center cursor-pointer text-white hover:text-gray-300">
|
<button type="button" class="absolute top-3 right-3 md:top-5 md:right-5 h-8 w-8 md:h-12 md:w-12 flex items-center justify-center cursor-pointer text-white hover:text-gray-300" aria-label="Close modal">
|
||||||
<span class="material-symbols text-2xl md:text-4xl">close</span>
|
<span class="material-symbols text-2xl md:text-4xl">close</span>
|
||||||
</div>
|
</button>
|
||||||
<div ref="content" class="text-white">
|
<div ref="content" class="text-white">
|
||||||
<form v-if="selectedSeries" @submit.prevent="submitSeriesForm">
|
<form v-if="selectedSeries" @submit.prevent="submitSeriesForm">
|
||||||
<div class="bg-bg rounded-lg px-2 py-6 sm:p-6 md:p-8" @click.stop>
|
<div class="bg-bg rounded-lg px-2 py-6 sm:p-6 md:p-8" @click.stop>
|
||||||
|
|||||||
@@ -1,12 +1,12 @@
|
|||||||
<template>
|
<template>
|
||||||
<div ref="wrapper" class="modal modal-bg w-full h-full fixed top-0 left-0 bg-primary items-center justify-center opacity-0 hidden" :class="`z-${zIndex} bg-opacity-${bgOpacity}`">
|
<div ref="wrapper" role="dialog" aria-modal="true" class="modal modal-bg w-full h-full fixed top-0 left-0 bg-primary items-center justify-center opacity-0 hidden" :class="`z-${zIndex} bg-opacity-${bgOpacity}`">
|
||||||
<div class="absolute top-0 left-0 right-0 w-full h-36 bg-gradient-to-t from-transparent via-black-500 to-black-700 opacity-90 pointer-events-none" />
|
<div class="absolute top-0 left-0 right-0 w-full h-36 bg-gradient-to-t from-transparent via-black-500 to-black-700 opacity-90 pointer-events-none" />
|
||||||
|
|
||||||
<button class="absolute top-4 right-4 landscape:top-4 landscape:right-4 md:portrait:top-5 md:portrait:right-5 lg:top-5 lg:right-5 inline-flex text-gray-200 hover:text-white" aria-label="Close modal" @click="clickClose">
|
<button class="absolute top-4 right-4 landscape:top-4 landscape:right-4 md:portrait:top-5 md:portrait:right-5 lg:top-5 lg:right-5 inline-flex text-gray-200 hover:text-white" aria-label="Close modal" @click="clickClose">
|
||||||
<span class="material-symbols text-2xl landscape:text-2xl md:portrait:text-4xl lg:text-4xl">close</span>
|
<span class="material-symbols text-2xl landscape:text-2xl md:portrait:text-4xl lg:text-4xl">close</span>
|
||||||
</button>
|
</button>
|
||||||
<slot name="outer" />
|
<slot name="outer" />
|
||||||
<div ref="content" style="min-width: 380px; min-height: 200px; max-width: 100vw" class="relative text-white" aria-modal="true" :style="{ height: modalHeight, width: modalWidth, marginTop: contentMarginTop + 'px' }" @mousedown="mousedownModal" @mouseup="mouseupModal" v-click-outside="clickBg">
|
<div ref="content" tabindex="0" style="min-width: 380px; min-height: 200px; max-width: 100vw" class="relative text-white outline-none" :style="{ height: modalHeight, width: modalWidth, marginTop: contentMarginTop + 'px' }" @mousedown="mousedownModal" @mouseup="mouseupModal" v-click-outside="clickBg">
|
||||||
<slot />
|
<slot />
|
||||||
<div v-if="processing" class="absolute top-0 left-0 right-0 bottom-0 w-full h-full bg-black bg-opacity-60 rounded-lg flex items-center justify-center">
|
<div v-if="processing" class="absolute top-0 left-0 right-0 bottom-0 w-full h-full bg-black bg-opacity-60 rounded-lg flex items-center justify-center">
|
||||||
<ui-loading-indicator />
|
<ui-loading-indicator />
|
||||||
@@ -126,6 +126,9 @@ export default {
|
|||||||
|
|
||||||
this.$eventBus.$on('modal-hotkey', this.hotkey)
|
this.$eventBus.$on('modal-hotkey', this.hotkey)
|
||||||
this.$store.commit('setOpenModal', this.name)
|
this.$store.commit('setOpenModal', this.name)
|
||||||
|
|
||||||
|
// Set focus to the modal content
|
||||||
|
this.content.focus()
|
||||||
},
|
},
|
||||||
setHide() {
|
setHide() {
|
||||||
if (this.content) this.content.style.transform = 'scale(0)'
|
if (this.content) this.content.style.transform = 'scale(0)'
|
||||||
|
|||||||
@@ -59,12 +59,19 @@ export default {
|
|||||||
setJumpBackwardAmount(val) {
|
setJumpBackwardAmount(val) {
|
||||||
this.jumpBackwardAmount = val
|
this.jumpBackwardAmount = val
|
||||||
this.$store.dispatch('user/updateUserSettings', { jumpBackwardAmount: val })
|
this.$store.dispatch('user/updateUserSettings', { jumpBackwardAmount: val })
|
||||||
}
|
|
||||||
},
|
},
|
||||||
mounted() {
|
settingsUpdated() {
|
||||||
this.useChapterTrack = this.$store.getters['user/getUserSetting']('useChapterTrack')
|
this.useChapterTrack = this.$store.getters['user/getUserSetting']('useChapterTrack')
|
||||||
this.jumpForwardAmount = this.$store.getters['user/getUserSetting']('jumpForwardAmount')
|
this.jumpForwardAmount = this.$store.getters['user/getUserSetting']('jumpForwardAmount')
|
||||||
this.jumpBackwardAmount = this.$store.getters['user/getUserSetting']('jumpBackwardAmount')
|
this.jumpBackwardAmount = this.$store.getters['user/getUserSetting']('jumpBackwardAmount')
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
mounted() {
|
||||||
|
this.settingsUpdated()
|
||||||
|
this.$eventBus.$on('user-settings', this.settingsUpdated)
|
||||||
|
},
|
||||||
|
beforeDestroy() {
|
||||||
|
this.$eventBus.$off('user-settings', this.settingsUpdated)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -19,12 +19,13 @@
|
|||||||
<ui-text-input v-model="currentShareUrl" show-copy readonly class="text-base h-10" />
|
<ui-text-input v-model="currentShareUrl" show-copy readonly class="text-base h-10" />
|
||||||
</div>
|
</div>
|
||||||
<div class="w-full py-2 px-1">
|
<div class="w-full py-2 px-1">
|
||||||
<p v-if="currentShare.expiresAt" class="text-base">{{ $getString('MessageShareExpiresIn', [currentShareTimeRemaining]) }}</p>
|
<p v-if="currentShare.isDownloadable" class="text-sm mb-2">{{ $strings.LabelDownloadable }}</p>
|
||||||
|
<p v-if="currentShare.expiresAt">{{ $getString('MessageShareExpiresIn', [currentShareTimeRemaining]) }}</p>
|
||||||
<p v-else>{{ $strings.LabelPermanent }}</p>
|
<p v-else>{{ $strings.LabelPermanent }}</p>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
<template v-else>
|
<template v-else>
|
||||||
<div class="flex flex-col sm:flex-row items-center justify-between space-y-4 sm:space-y-0 sm:space-x-4 mb-4">
|
<div class="flex flex-col sm:flex-row items-center justify-between space-y-4 sm:space-y-0 sm:space-x-4 mb-2">
|
||||||
<div class="w-full sm:w-48">
|
<div class="w-full sm:w-48">
|
||||||
<label class="px-1 text-sm font-semibold block">{{ $strings.LabelSlug }}</label>
|
<label class="px-1 text-sm font-semibold block">{{ $strings.LabelSlug }}</label>
|
||||||
<ui-text-input v-model="newShareSlug" class="text-base h-10" />
|
<ui-text-input v-model="newShareSlug" class="text-base h-10" />
|
||||||
@@ -46,6 +47,15 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="flex items-center w-full md:w-1/2 mb-4">
|
||||||
|
<p class="text-sm text-gray-300 py-1 px-1">{{ $strings.LabelDownloadable }}</p>
|
||||||
|
<ui-toggle-switch size="sm" v-model="isDownloadable" />
|
||||||
|
<ui-tooltip :text="$strings.LabelShareDownloadableHelp">
|
||||||
|
<p class="pl-4 text-sm">
|
||||||
|
<span class="material-symbols icon-text text-sm">info</span>
|
||||||
|
</p>
|
||||||
|
</ui-tooltip>
|
||||||
|
</div>
|
||||||
<p class="text-sm text-gray-300 py-1 px-1" v-html="$getString('MessageShareURLWillBe', [demoShareUrl])" />
|
<p class="text-sm text-gray-300 py-1 px-1" v-html="$getString('MessageShareURLWillBe', [demoShareUrl])" />
|
||||||
<p class="text-sm text-gray-300 py-1 px-1" v-html="$getString('MessageShareExpirationWillBe', [expirationDateString])" />
|
<p class="text-sm text-gray-300 py-1 px-1" v-html="$getString('MessageShareExpirationWillBe', [expirationDateString])" />
|
||||||
</template>
|
</template>
|
||||||
@@ -81,7 +91,8 @@ export default {
|
|||||||
text: this.$strings.LabelDays,
|
text: this.$strings.LabelDays,
|
||||||
value: 'days'
|
value: 'days'
|
||||||
}
|
}
|
||||||
]
|
],
|
||||||
|
isDownloadable: false
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
watch: {
|
watch: {
|
||||||
@@ -112,11 +123,11 @@ export default {
|
|||||||
return this.$store.state.user.user
|
return this.$store.state.user.user
|
||||||
},
|
},
|
||||||
demoShareUrl() {
|
demoShareUrl() {
|
||||||
return `${window.origin}/share/${this.newShareSlug}`
|
return `${window.origin}${this.$config.routerBasePath}/share/${this.newShareSlug}`
|
||||||
},
|
},
|
||||||
currentShareUrl() {
|
currentShareUrl() {
|
||||||
if (!this.currentShare) return ''
|
if (!this.currentShare) return ''
|
||||||
return `${window.origin}/share/${this.currentShare.slug}`
|
return `${window.origin}${this.$config.routerBasePath}/share/${this.currentShare.slug}`
|
||||||
},
|
},
|
||||||
currentShareTimeRemaining() {
|
currentShareTimeRemaining() {
|
||||||
if (!this.currentShare) return 'Error'
|
if (!this.currentShare) return 'Error'
|
||||||
@@ -172,7 +183,8 @@ export default {
|
|||||||
slug: this.newShareSlug,
|
slug: this.newShareSlug,
|
||||||
mediaItemType: 'book',
|
mediaItemType: 'book',
|
||||||
mediaItemId: this.libraryItem.media.id,
|
mediaItemId: this.libraryItem.media.id,
|
||||||
expiresAt: this.expireDurationSeconds ? Date.now() + this.expireDurationSeconds * 1000 : 0
|
expiresAt: this.expireDurationSeconds ? Date.now() + this.expireDurationSeconds * 1000 : 0,
|
||||||
|
isDownloadable: this.isDownloadable
|
||||||
}
|
}
|
||||||
this.processing = true
|
this.processing = true
|
||||||
this.$axios
|
this.$axios
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
<modals-modal v-model="show" name="changelog" :width="800" :height="'unset'">
|
<modals-modal v-model="show" name="changelog" :width="800" :height="'unset'">
|
||||||
<template #outer>
|
<template #outer>
|
||||||
<div class="absolute top-0 left-0 p-5 w-2/3 overflow-hidden">
|
<div class="absolute top-0 left-0 p-5 w-2/3 overflow-hidden">
|
||||||
<p class="text-3xl text-white truncate">Changelog</p>
|
<h1 class="text-3xl text-white truncate">Changelog</h1>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
<div class="px-8 py-6 w-full rounded-lg bg-bg shadow-lg border border-black-300 relative overflow-y-scroll" style="max-height: 80vh">
|
<div class="px-8 py-6 w-full rounded-lg bg-bg shadow-lg border border-black-300 relative overflow-y-scroll" style="max-height: 80vh">
|
||||||
@@ -13,7 +13,7 @@
|
|||||||
</p>
|
</p>
|
||||||
<div class="custom-text" v-html="getChangelog(release)" />
|
<div class="custom-text" v-html="getChangelog(release)" />
|
||||||
</div>
|
</div>
|
||||||
<div v-if="release !== releasesToShow[releasesToShow.length - 1]" class="border-b border-black-300 my-8" />
|
<div v-if="release !== releasesToShow[releasesToShow.length - 1]" :key="`${release.name}-divider`" class="border-b border-black-300 my-8" />
|
||||||
</template>
|
</template>
|
||||||
</div>
|
</div>
|
||||||
</modals-modal>
|
</modals-modal>
|
||||||
|
|||||||
@@ -138,7 +138,6 @@ export default {
|
|||||||
.$post(`/api/collections/${collection.id}/batch/remove`, { books: this.selectedBookIds })
|
.$post(`/api/collections/${collection.id}/batch/remove`, { books: this.selectedBookIds })
|
||||||
.then((updatedCollection) => {
|
.then((updatedCollection) => {
|
||||||
console.log(`Books removed from collection`, updatedCollection)
|
console.log(`Books removed from collection`, updatedCollection)
|
||||||
this.$toast.success(this.$strings.ToastCollectionItemsRemoveSuccess)
|
|
||||||
this.processing = false
|
this.processing = false
|
||||||
})
|
})
|
||||||
.catch((error) => {
|
.catch((error) => {
|
||||||
@@ -152,7 +151,6 @@ export default {
|
|||||||
.$delete(`/api/collections/${collection.id}/book/${this.selectedLibraryItemId}`)
|
.$delete(`/api/collections/${collection.id}/book/${this.selectedLibraryItemId}`)
|
||||||
.then((updatedCollection) => {
|
.then((updatedCollection) => {
|
||||||
console.log(`Book removed from collection`, updatedCollection)
|
console.log(`Book removed from collection`, updatedCollection)
|
||||||
this.$toast.success(this.$strings.ToastCollectionItemsRemoveSuccess)
|
|
||||||
this.processing = false
|
this.processing = false
|
||||||
})
|
})
|
||||||
.catch((error) => {
|
.catch((error) => {
|
||||||
@@ -167,12 +165,11 @@ export default {
|
|||||||
this.processing = true
|
this.processing = true
|
||||||
|
|
||||||
if (this.showBatchCollectionModal) {
|
if (this.showBatchCollectionModal) {
|
||||||
// BATCH Remove books
|
// BATCH Add books
|
||||||
this.$axios
|
this.$axios
|
||||||
.$post(`/api/collections/${collection.id}/batch/add`, { books: this.selectedBookIds })
|
.$post(`/api/collections/${collection.id}/batch/add`, { books: this.selectedBookIds })
|
||||||
.then((updatedCollection) => {
|
.then((updatedCollection) => {
|
||||||
console.log(`Books added to collection`, updatedCollection)
|
console.log(`Books added to collection`, updatedCollection)
|
||||||
this.$toast.success(this.$strings.ToastCollectionItemsAddSuccess)
|
|
||||||
this.processing = false
|
this.processing = false
|
||||||
})
|
})
|
||||||
.catch((error) => {
|
.catch((error) => {
|
||||||
@@ -187,7 +184,6 @@ export default {
|
|||||||
.$post(`/api/collections/${collection.id}/book`, { id: this.selectedLibraryItemId })
|
.$post(`/api/collections/${collection.id}/book`, { id: this.selectedLibraryItemId })
|
||||||
.then((updatedCollection) => {
|
.then((updatedCollection) => {
|
||||||
console.log(`Book added to collection`, updatedCollection)
|
console.log(`Book added to collection`, updatedCollection)
|
||||||
this.$toast.success(this.$strings.ToastCollectionItemsAddSuccess)
|
|
||||||
this.processing = false
|
this.processing = false
|
||||||
})
|
})
|
||||||
.catch((error) => {
|
.catch((error) => {
|
||||||
@@ -214,7 +210,6 @@ export default {
|
|||||||
.$post('/api/collections', newCollection)
|
.$post('/api/collections', newCollection)
|
||||||
.then((data) => {
|
.then((data) => {
|
||||||
console.log('New Collection Created', data)
|
console.log('New Collection Created', data)
|
||||||
this.$toast.success(`Collection "${data.name}" created`)
|
|
||||||
this.processing = false
|
this.processing = false
|
||||||
this.newCollectionName = ''
|
this.newCollectionName = ''
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -0,0 +1,188 @@
|
|||||||
|
<template>
|
||||||
|
<modals-modal ref="modal" v-model="show" name="ereader-device-edit" :width="800" :height="'unset'" :processing="processing">
|
||||||
|
<template #outer>
|
||||||
|
<div class="absolute top-0 left-0 p-5 w-2/3 overflow-hidden">
|
||||||
|
<p class="text-3xl text-white truncate">{{ title }}</p>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<form @submit.prevent="submitForm">
|
||||||
|
<div class="w-full text-sm rounded-lg bg-bg shadow-lg border border-black-300">
|
||||||
|
<div class="w-full px-3 py-5 md:p-12">
|
||||||
|
<div class="flex items-center -mx-1 mb-4">
|
||||||
|
<div class="w-full md:w-1/2 px-1">
|
||||||
|
<ui-text-input-with-label ref="ereaderNameInput" v-model="newDevice.name" :disabled="processing" :label="$strings.LabelName" />
|
||||||
|
</div>
|
||||||
|
<div class="w-full md:w-1/2 px-1">
|
||||||
|
<ui-text-input-with-label ref="ereaderEmailInput" v-model="newDevice.email" :disabled="processing" :label="$strings.LabelEmail" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="flex items-center pt-4">
|
||||||
|
<div class="flex-grow" />
|
||||||
|
<ui-btn color="success" type="submit">{{ $strings.ButtonSubmit }}</ui-btn>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</modals-modal>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
export default {
|
||||||
|
props: {
|
||||||
|
value: Boolean,
|
||||||
|
existingDevices: {
|
||||||
|
type: Array,
|
||||||
|
default: () => []
|
||||||
|
},
|
||||||
|
ereaderDevice: {
|
||||||
|
type: Object,
|
||||||
|
default: () => null
|
||||||
|
}
|
||||||
|
},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
processing: false,
|
||||||
|
newDevice: {
|
||||||
|
name: '',
|
||||||
|
email: '',
|
||||||
|
availabilityOption: 'adminAndUp',
|
||||||
|
users: []
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
watch: {
|
||||||
|
show: {
|
||||||
|
handler(newVal) {
|
||||||
|
if (newVal) {
|
||||||
|
this.init()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
show: {
|
||||||
|
get() {
|
||||||
|
return this.value
|
||||||
|
},
|
||||||
|
set(val) {
|
||||||
|
this.$emit('input', val)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
user() {
|
||||||
|
return this.$store.state.user.user
|
||||||
|
},
|
||||||
|
title() {
|
||||||
|
return !this.ereaderDevice ? 'Create Device' : 'Update Device'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
submitForm() {
|
||||||
|
this.$refs.ereaderNameInput.blur()
|
||||||
|
this.$refs.ereaderEmailInput.blur()
|
||||||
|
|
||||||
|
if (!this.newDevice.name?.trim() || !this.newDevice.email?.trim()) {
|
||||||
|
this.$toast.error(this.$strings.ToastNameEmailRequired)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
this.newDevice.name = this.newDevice.name.trim()
|
||||||
|
this.newDevice.email = this.newDevice.email.trim()
|
||||||
|
|
||||||
|
// Only catches duplicate names for the current user
|
||||||
|
// Duplicates with other users caught on server side
|
||||||
|
if (!this.ereaderDevice) {
|
||||||
|
if (this.existingDevices.some((d) => d.name === this.newDevice.name)) {
|
||||||
|
this.$toast.error(this.$strings.ToastDeviceNameAlreadyExists)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
this.submitCreate()
|
||||||
|
} else {
|
||||||
|
if (this.ereaderDevice.name !== this.newDevice.name && this.existingDevices.some((d) => d.name === this.newDevice.name)) {
|
||||||
|
this.$toast.error(this.$strings.ToastDeviceNameAlreadyExists)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
this.submitUpdate()
|
||||||
|
}
|
||||||
|
},
|
||||||
|
submitUpdate() {
|
||||||
|
this.processing = true
|
||||||
|
|
||||||
|
const existingDevicesWithoutThisOne = this.existingDevices.filter((d) => d.name !== this.ereaderDevice.name)
|
||||||
|
|
||||||
|
const payload = {
|
||||||
|
ereaderDevices: [
|
||||||
|
...existingDevicesWithoutThisOne,
|
||||||
|
{
|
||||||
|
...this.newDevice
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
this.$axios
|
||||||
|
.$post(`/api/me/ereader-devices`, payload)
|
||||||
|
.then((data) => {
|
||||||
|
this.$emit('update', data.ereaderDevices)
|
||||||
|
this.show = false
|
||||||
|
})
|
||||||
|
.catch((error) => {
|
||||||
|
console.error('Failed to update device', error)
|
||||||
|
if (error.response?.data?.toLowerCase().includes('duplicate')) {
|
||||||
|
this.$toast.error(this.$strings.ToastDeviceNameAlreadyExists)
|
||||||
|
} else {
|
||||||
|
this.$toast.error(this.$strings.ToastDeviceAddFailed)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.finally(() => {
|
||||||
|
this.processing = false
|
||||||
|
})
|
||||||
|
},
|
||||||
|
submitCreate() {
|
||||||
|
this.processing = true
|
||||||
|
|
||||||
|
const payload = {
|
||||||
|
ereaderDevices: [
|
||||||
|
...this.existingDevices,
|
||||||
|
{
|
||||||
|
...this.newDevice
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
this.$axios
|
||||||
|
.$post('/api/me/ereader-devices', payload)
|
||||||
|
.then((data) => {
|
||||||
|
this.$emit('update', data.ereaderDevices || [])
|
||||||
|
this.show = false
|
||||||
|
})
|
||||||
|
.catch((error) => {
|
||||||
|
console.error('Failed to add device', error)
|
||||||
|
if (error.response?.data?.toLowerCase().includes('duplicate')) {
|
||||||
|
this.$toast.error(this.$strings.ToastDeviceNameAlreadyExists)
|
||||||
|
} else {
|
||||||
|
this.$toast.error(this.$strings.ToastDeviceAddFailed)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.finally(() => {
|
||||||
|
this.processing = false
|
||||||
|
})
|
||||||
|
},
|
||||||
|
init() {
|
||||||
|
if (this.ereaderDevice) {
|
||||||
|
this.newDevice.name = this.ereaderDevice.name
|
||||||
|
this.newDevice.email = this.ereaderDevice.email
|
||||||
|
this.newDevice.availabilityOption = this.ereaderDevice.availabilityOption || 'specificUsers'
|
||||||
|
this.newDevice.users = this.ereaderDevice.users || [this.user.id]
|
||||||
|
} else {
|
||||||
|
this.newDevice.name = ''
|
||||||
|
this.newDevice.email = ''
|
||||||
|
this.newDevice.availabilityOption = 'specificUsers'
|
||||||
|
this.newDevice.users = [this.user.id]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
mounted() {}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
@@ -2,24 +2,24 @@
|
|||||||
<modals-modal v-model="show" name="edit-book" :width="800" :height="height" :processing="processing" :content-margin-top="marginTop">
|
<modals-modal v-model="show" name="edit-book" :width="800" :height="height" :processing="processing" :content-margin-top="marginTop">
|
||||||
<template #outer>
|
<template #outer>
|
||||||
<div class="absolute top-0 left-0 p-4 landscape:px-4 landscape:py-2 md:portrait:p-5 lg:p-5 w-2/3 overflow-hidden pointer-events-none">
|
<div class="absolute top-0 left-0 p-4 landscape:px-4 landscape:py-2 md:portrait:p-5 lg:p-5 w-2/3 overflow-hidden pointer-events-none">
|
||||||
<p class="text-xl md:portrait:text-3xl md:landscape:text-lg lg:text-3xl text-white truncate pointer-events-none">{{ title }}</p>
|
<h1 class="text-xl md:portrait:text-3xl md:landscape:text-lg lg:text-3xl text-white truncate pointer-events-none">{{ title }}</h1>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
<div class="absolute -top-10 left-0 z-10 w-full flex">
|
<div role="tablist" class="absolute -top-10 left-0 z-10 w-full flex">
|
||||||
<template v-for="tab in availableTabs">
|
<template v-for="tab in availableTabs">
|
||||||
<div :key="tab.id" class="w-28 rounded-t-lg flex items-center justify-center mr-0.5 sm:mr-1 cursor-pointer hover:bg-bg border-t border-l border-r border-black-300 tab text-xs sm:text-base" :class="selectedTab === tab.id ? 'tab-selected bg-bg pb-px' : 'bg-primary text-gray-400'" @click="selectTab(tab.id)">{{ tab.title }}</div>
|
<button :key="tab.id" role="tab" class="w-28 rounded-t-lg flex items-center justify-center mr-0.5 sm:mr-1 cursor-pointer hover:bg-bg border-t border-l border-r border-black-300 tab text-xs sm:text-base" :class="selectedTab === tab.id ? 'tab-selected bg-bg pb-px' : 'bg-primary text-gray-400'" @click="selectTab(tab.id)">{{ tab.title }}</button>
|
||||||
</template>
|
</template>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div role="tabpanel" class="w-full h-full max-h-full text-sm rounded-b-lg rounded-tr-lg bg-bg shadow-lg border border-black-300 relative">
|
||||||
|
<component v-if="libraryItem && show" :is="tabName" :library-item="libraryItem" :processing.sync="processing" @close="show = false" @selectTab="selectTab" />
|
||||||
|
</div>
|
||||||
|
|
||||||
<div v-show="canGoPrev" class="absolute -left-24 top-0 bottom-0 h-full pointer-events-none flex items-center px-6">
|
<div v-show="canGoPrev" class="absolute -left-24 top-0 bottom-0 h-full pointer-events-none flex items-center px-6">
|
||||||
<div class="material-symbols text-5xl text-white text-opacity-50 hover:text-opacity-90 cursor-pointer pointer-events-auto" @click.stop.prevent="goPrevBook" @mousedown.prevent>arrow_back_ios</div>
|
<button class="material-symbols text-5xl text-white text-opacity-50 hover:text-opacity-90 cursor-pointer pointer-events-auto" :aria-label="$strings.ButtonNext" @click.stop.prevent="goPrevBook" @mousedown.prevent>arrow_back_ios</button>
|
||||||
</div>
|
</div>
|
||||||
<div v-show="canGoNext" class="absolute -right-24 top-0 bottom-0 h-full pointer-events-none flex items-center px-6">
|
<div v-show="canGoNext" class="absolute -right-24 top-0 bottom-0 h-full pointer-events-none flex items-center px-6">
|
||||||
<div class="material-symbols text-5xl text-white text-opacity-50 hover:text-opacity-90 cursor-pointer pointer-events-auto" @click.stop.prevent="goNextBook" @mousedown.prevent>arrow_forward_ios</div>
|
<button class="material-symbols text-5xl text-white text-opacity-50 hover:text-opacity-90 cursor-pointer pointer-events-auto" :aria-label="$strings.ButtonPrevious" @click.stop.prevent="goNextBook" @mousedown.prevent>arrow_forward_ios</button>
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="w-full h-full max-h-full text-sm rounded-b-lg rounded-tr-lg bg-bg shadow-lg border border-black-300 relative">
|
|
||||||
<component v-if="libraryItem && show" :is="tabName" :library-item="libraryItem" :processing.sync="processing" @close="show = false" @selectTab="selectTab" />
|
|
||||||
</div>
|
</div>
|
||||||
</modals-modal>
|
</modals-modal>
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
@@ -6,7 +6,7 @@
|
|||||||
<ui-text-input-with-label ref="maxEpisodesInput" v-model="maxEpisodesToDownload" :disabled="checkingNewEpisodes" type="number" :label="$strings.LabelLimit" class="w-16 mr-2" input-class="h-10">
|
<ui-text-input-with-label ref="maxEpisodesInput" v-model="maxEpisodesToDownload" :disabled="checkingNewEpisodes" type="number" :label="$strings.LabelLimit" class="w-16 mr-2" input-class="h-10">
|
||||||
<div class="flex -mb-0.5">
|
<div class="flex -mb-0.5">
|
||||||
<p class="px-1 text-sm font-semibold" :class="{ 'text-gray-400': checkingNewEpisodes }">{{ $strings.LabelLimit }}</p>
|
<p class="px-1 text-sm font-semibold" :class="{ 'text-gray-400': checkingNewEpisodes }">{{ $strings.LabelLimit }}</p>
|
||||||
<ui-tooltip direction="top" text="Max # of episodes to download. Use 0 for unlimited.">
|
<ui-tooltip direction="top" :text="$strings.LabelMaxEpisodesToDownload">
|
||||||
<span class="material-symbols text-base">info</span>
|
<span class="material-symbols text-base">info</span>
|
||||||
</ui-tooltip>
|
</ui-tooltip>
|
||||||
</div>
|
</div>
|
||||||
@@ -99,7 +99,7 @@ export default {
|
|||||||
|
|
||||||
if (this.maxEpisodesToDownload < 0) {
|
if (this.maxEpisodesToDownload < 0) {
|
||||||
this.maxEpisodesToDownload = 3
|
this.maxEpisodesToDownload = 3
|
||||||
this.$toast.error('Invalid max episodes to download')
|
this.$toast.error(this.$strings.ToastInvalidMaxEpisodesToDownload)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -120,9 +120,9 @@ export default {
|
|||||||
.then((response) => {
|
.then((response) => {
|
||||||
if (response.episodes && response.episodes.length) {
|
if (response.episodes && response.episodes.length) {
|
||||||
console.log('New episodes', response.episodes.length)
|
console.log('New episodes', response.episodes.length)
|
||||||
this.$toast.success(`${response.episodes.length} new episodes found!`)
|
this.$toast.success(this.$getString('ToastNewEpisodesFound', [response.episodes.length]))
|
||||||
} else {
|
} else {
|
||||||
this.$toast.info('No new episodes found')
|
this.$toast.info(this.$strings.ToastNoNewEpisodesFound)
|
||||||
}
|
}
|
||||||
this.checkingNewEpisodes = false
|
this.checkingNewEpisodes = false
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -60,7 +60,7 @@
|
|||||||
<div class="flex-grow ml-4">
|
<div class="flex-grow ml-4">
|
||||||
<ui-text-input-with-label v-model="selectedMatch.title" :disabled="!selectedMatchUsage.title" :label="$strings.LabelTitle" />
|
<ui-text-input-with-label v-model="selectedMatch.title" :disabled="!selectedMatchUsage.title" :label="$strings.LabelTitle" />
|
||||||
<p v-if="mediaMetadata.title" class="text-xs ml-1 text-white text-opacity-60">
|
<p v-if="mediaMetadata.title" class="text-xs ml-1 text-white text-opacity-60">
|
||||||
{{ $strings.LabelCurrently }} <a title="Click to use current value" class="cursor-pointer hover:underline" @click.stop="setMatchFieldValue('title', mediaMetadata.title)">{{ mediaMetadata.title || '' }}</a>
|
{{ $strings.LabelCurrently }} <a :title="$strings.LabelClickToUseCurrentValue" class="cursor-pointer hover:underline" @click.stop="setMatchFieldValue('title', mediaMetadata.title)">{{ mediaMetadata.title || '' }}</a>
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -69,7 +69,7 @@
|
|||||||
<div class="flex-grow ml-4">
|
<div class="flex-grow ml-4">
|
||||||
<ui-text-input-with-label v-model="selectedMatch.subtitle" :disabled="!selectedMatchUsage.subtitle" :label="$strings.LabelSubtitle" />
|
<ui-text-input-with-label v-model="selectedMatch.subtitle" :disabled="!selectedMatchUsage.subtitle" :label="$strings.LabelSubtitle" />
|
||||||
<p v-if="mediaMetadata.subtitle" class="text-xs ml-1 text-white text-opacity-60">
|
<p v-if="mediaMetadata.subtitle" class="text-xs ml-1 text-white text-opacity-60">
|
||||||
{{ $strings.LabelCurrently }} <a title="Click to use current value" class="cursor-pointer hover:underline" @click.stop="setMatchFieldValue('subtitle', mediaMetadata.subtitle)">{{ mediaMetadata.subtitle }}</a>
|
{{ $strings.LabelCurrently }} <a :title="$strings.LabelClickToUseCurrentValue" class="cursor-pointer hover:underline" @click.stop="setMatchFieldValue('subtitle', mediaMetadata.subtitle)">{{ mediaMetadata.subtitle }}</a>
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -78,7 +78,7 @@
|
|||||||
<div class="flex-grow ml-4">
|
<div class="flex-grow ml-4">
|
||||||
<ui-text-input-with-label v-model="selectedMatch.author" :disabled="!selectedMatchUsage.author" :label="$strings.LabelAuthor" />
|
<ui-text-input-with-label v-model="selectedMatch.author" :disabled="!selectedMatchUsage.author" :label="$strings.LabelAuthor" />
|
||||||
<p v-if="mediaMetadata.authorName" class="text-xs ml-1 text-white text-opacity-60">
|
<p v-if="mediaMetadata.authorName" class="text-xs ml-1 text-white text-opacity-60">
|
||||||
{{ $strings.LabelCurrently }} <a title="Click to use current value" class="cursor-pointer hover:underline" @click.stop="setMatchFieldValue('author', mediaMetadata.authorName)">{{ mediaMetadata.authorName }}</a>
|
{{ $strings.LabelCurrently }} <a title="$strings.LabelClickToUseCurrentValue" class="cursor-pointer hover:underline" @click.stop="setMatchFieldValue('author', mediaMetadata.authorName)">{{ mediaMetadata.authorName }}</a>
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -87,7 +87,7 @@
|
|||||||
<div class="flex-grow ml-4">
|
<div class="flex-grow ml-4">
|
||||||
<ui-multi-select v-model="selectedMatch.narrator" :items="narrators" :disabled="!selectedMatchUsage.narrator" :label="$strings.LabelNarrators" />
|
<ui-multi-select v-model="selectedMatch.narrator" :items="narrators" :disabled="!selectedMatchUsage.narrator" :label="$strings.LabelNarrators" />
|
||||||
<p v-if="mediaMetadata.narratorName" class="text-xs ml-1 text-white text-opacity-60">
|
<p v-if="mediaMetadata.narratorName" class="text-xs ml-1 text-white text-opacity-60">
|
||||||
{{ $strings.LabelCurrently }} <a title="Click to use current value" class="cursor-pointer hover:underline" @click.stop="setMatchFieldValue('narrator', mediaMetadata.narrators)">{{ mediaMetadata.narratorName }}</a>
|
{{ $strings.LabelCurrently }} <a title="$strings.LabelClickToUseCurrentValue" class="cursor-pointer hover:underline" @click.stop="setMatchFieldValue('narrator', mediaMetadata.narrators)">{{ mediaMetadata.narratorName }}</a>
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -96,7 +96,7 @@
|
|||||||
<div class="flex-grow ml-4">
|
<div class="flex-grow ml-4">
|
||||||
<ui-textarea-with-label v-model="selectedMatch.description" :rows="3" :disabled="!selectedMatchUsage.description" :label="$strings.LabelDescription" />
|
<ui-textarea-with-label v-model="selectedMatch.description" :rows="3" :disabled="!selectedMatchUsage.description" :label="$strings.LabelDescription" />
|
||||||
<p v-if="mediaMetadata.description" class="text-xs ml-1 text-white text-opacity-60">
|
<p v-if="mediaMetadata.description" class="text-xs ml-1 text-white text-opacity-60">
|
||||||
{{ $strings.LabelCurrently }} <a title="Click to use current value" class="cursor-pointer hover:underline" @click.stop="setMatchFieldValue('description', mediaMetadata.description)">{{ mediaMetadata.description.substr(0, 100) + (mediaMetadata.description.length > 100 ? '...' : '') }}</a>
|
{{ $strings.LabelCurrently }} <a title="$strings.LabelClickToUseCurrentValue" class="cursor-pointer hover:underline" @click.stop="setMatchFieldValue('description', mediaMetadata.description)">{{ mediaMetadata.description.substr(0, 100) + (mediaMetadata.description.length > 100 ? '...' : '') }}</a>
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -105,7 +105,7 @@
|
|||||||
<div class="flex-grow ml-4">
|
<div class="flex-grow ml-4">
|
||||||
<ui-text-input-with-label v-model="selectedMatch.publisher" :disabled="!selectedMatchUsage.publisher" :label="$strings.LabelPublisher" />
|
<ui-text-input-with-label v-model="selectedMatch.publisher" :disabled="!selectedMatchUsage.publisher" :label="$strings.LabelPublisher" />
|
||||||
<p v-if="mediaMetadata.publisher" class="text-xs ml-1 text-white text-opacity-60">
|
<p v-if="mediaMetadata.publisher" class="text-xs ml-1 text-white text-opacity-60">
|
||||||
{{ $strings.LabelCurrently }} <a title="Click to use current value" class="cursor-pointer hover:underline" @click.stop="setMatchFieldValue('publisher', mediaMetadata.publisher)">{{ mediaMetadata.publisher }}</a>
|
{{ $strings.LabelCurrently }} <a title="$strings.LabelClickToUseCurrentValue" class="cursor-pointer hover:underline" @click.stop="setMatchFieldValue('publisher', mediaMetadata.publisher)">{{ mediaMetadata.publisher }}</a>
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -114,7 +114,7 @@
|
|||||||
<div class="flex-grow ml-4">
|
<div class="flex-grow ml-4">
|
||||||
<ui-text-input-with-label v-model="selectedMatch.publishedYear" :disabled="!selectedMatchUsage.publishedYear" :label="$strings.LabelPublishYear" />
|
<ui-text-input-with-label v-model="selectedMatch.publishedYear" :disabled="!selectedMatchUsage.publishedYear" :label="$strings.LabelPublishYear" />
|
||||||
<p v-if="mediaMetadata.publishedYear" class="text-xs ml-1 text-white text-opacity-60">
|
<p v-if="mediaMetadata.publishedYear" class="text-xs ml-1 text-white text-opacity-60">
|
||||||
{{ $strings.LabelCurrently }} <a title="Click to use current value" class="cursor-pointer hover:underline" @click.stop="setMatchFieldValue('publishedYear', mediaMetadata.publishedYear)">{{ mediaMetadata.publishedYear }}</a>
|
{{ $strings.LabelCurrently }} <a title="$strings.LabelClickToUseCurrentValue" class="cursor-pointer hover:underline" @click.stop="setMatchFieldValue('publishedYear', mediaMetadata.publishedYear)">{{ mediaMetadata.publishedYear }}</a>
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -124,7 +124,7 @@
|
|||||||
<div class="flex-grow ml-4">
|
<div class="flex-grow ml-4">
|
||||||
<widgets-series-input-widget v-model="selectedMatch.series" :disabled="!selectedMatchUsage.series" />
|
<widgets-series-input-widget v-model="selectedMatch.series" :disabled="!selectedMatchUsage.series" />
|
||||||
<p v-if="mediaMetadata.seriesName" class="text-xs ml-1 text-white text-opacity-60">
|
<p v-if="mediaMetadata.seriesName" class="text-xs ml-1 text-white text-opacity-60">
|
||||||
{{ $strings.LabelCurrently }} <a title="Click to use current value" class="cursor-pointer hover:underline" @click.stop="setMatchFieldValue('series', mediaMetadata.series)">{{ mediaMetadata.seriesName }}</a>
|
{{ $strings.LabelCurrently }} <a :title="$strings.LabelClickToUseCurrentValue" class="cursor-pointer hover:underline" @click.stop="setMatchFieldValue('series', mediaMetadata.series)">{{ mediaMetadata.seriesName }}</a>
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -133,7 +133,7 @@
|
|||||||
<div class="flex-grow ml-4">
|
<div class="flex-grow ml-4">
|
||||||
<ui-multi-select v-model="selectedMatch.genres" :items="genres" :disabled="!selectedMatchUsage.genres" :label="$strings.LabelGenres" />
|
<ui-multi-select v-model="selectedMatch.genres" :items="genres" :disabled="!selectedMatchUsage.genres" :label="$strings.LabelGenres" />
|
||||||
<p v-if="mediaMetadata.genres?.length" class="text-xs ml-1 text-white text-opacity-60">
|
<p v-if="mediaMetadata.genres?.length" class="text-xs ml-1 text-white text-opacity-60">
|
||||||
{{ $strings.LabelCurrently }} <a title="Click to use current value" class="cursor-pointer hover:underline" @click.stop="setMatchFieldValue('genres', mediaMetadata.genres)">{{ mediaMetadata.genres.join(', ') }}</a>
|
{{ $strings.LabelCurrently }} <a :title="$strings.LabelClickToUseCurrentValue" class="cursor-pointer hover:underline" @click.stop="setMatchFieldValue('genres', mediaMetadata.genres)">{{ mediaMetadata.genres.join(', ') }}</a>
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -142,7 +142,7 @@
|
|||||||
<div class="flex-grow ml-4">
|
<div class="flex-grow ml-4">
|
||||||
<ui-multi-select v-model="selectedMatch.tags" :items="tags" :disabled="!selectedMatchUsage.tags" :label="$strings.LabelTags" />
|
<ui-multi-select v-model="selectedMatch.tags" :items="tags" :disabled="!selectedMatchUsage.tags" :label="$strings.LabelTags" />
|
||||||
<p v-if="media.tags?.length" class="text-xs ml-1 text-white text-opacity-60">
|
<p v-if="media.tags?.length" class="text-xs ml-1 text-white text-opacity-60">
|
||||||
{{ $strings.LabelCurrently }} <a title="Click to use current value" class="cursor-pointer hover:underline" @click.stop="setMatchFieldValue('tags', media.tags)">{{ media.tags.join(', ') }}</a>
|
{{ $strings.LabelCurrently }} <a :title="$strings.LabelClickToUseCurrentValue" class="cursor-pointer hover:underline" @click.stop="setMatchFieldValue('tags', media.tags)">{{ media.tags.join(', ') }}</a>
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -151,7 +151,7 @@
|
|||||||
<div class="flex-grow ml-4">
|
<div class="flex-grow ml-4">
|
||||||
<ui-text-input-with-label v-model="selectedMatch.language" :disabled="!selectedMatchUsage.language" :label="$strings.LabelLanguage" />
|
<ui-text-input-with-label v-model="selectedMatch.language" :disabled="!selectedMatchUsage.language" :label="$strings.LabelLanguage" />
|
||||||
<p v-if="mediaMetadata.language" class="text-xs ml-1 text-white text-opacity-60">
|
<p v-if="mediaMetadata.language" class="text-xs ml-1 text-white text-opacity-60">
|
||||||
{{ $strings.LabelCurrently }} <a title="Click to use current value" class="cursor-pointer hover:underline" @click.stop="setMatchFieldValue('language', mediaMetadata.language)">{{ mediaMetadata.language }}</a>
|
{{ $strings.LabelCurrently }} <a :title="$strings.LabelClickToUseCurrentValue" class="cursor-pointer hover:underline" @click.stop="setMatchFieldValue('language', mediaMetadata.language)">{{ mediaMetadata.language }}</a>
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -160,7 +160,7 @@
|
|||||||
<div class="flex-grow ml-4">
|
<div class="flex-grow ml-4">
|
||||||
<ui-text-input-with-label v-model="selectedMatch.isbn" :disabled="!selectedMatchUsage.isbn" label="ISBN" />
|
<ui-text-input-with-label v-model="selectedMatch.isbn" :disabled="!selectedMatchUsage.isbn" label="ISBN" />
|
||||||
<p v-if="mediaMetadata.isbn" class="text-xs ml-1 text-white text-opacity-60">
|
<p v-if="mediaMetadata.isbn" class="text-xs ml-1 text-white text-opacity-60">
|
||||||
{{ $strings.LabelCurrently }} <a title="Click to use current value" class="cursor-pointer hover:underline" @click.stop="setMatchFieldValue('isbn', mediaMetadata.isbn)">{{ mediaMetadata.isbn }}</a>
|
{{ $strings.LabelCurrently }} <a :title="$strings.LabelClickToUseCurrentValue" class="cursor-pointer hover:underline" @click.stop="setMatchFieldValue('isbn', mediaMetadata.isbn)">{{ mediaMetadata.isbn }}</a>
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -169,7 +169,7 @@
|
|||||||
<div class="flex-grow ml-4">
|
<div class="flex-grow ml-4">
|
||||||
<ui-text-input-with-label v-model="selectedMatch.asin" :disabled="!selectedMatchUsage.asin" label="ASIN" />
|
<ui-text-input-with-label v-model="selectedMatch.asin" :disabled="!selectedMatchUsage.asin" label="ASIN" />
|
||||||
<p v-if="mediaMetadata.asin" class="text-xs ml-1 text-white text-opacity-60">
|
<p v-if="mediaMetadata.asin" class="text-xs ml-1 text-white text-opacity-60">
|
||||||
{{ $strings.LabelCurrently }} <a title="Click to use current value" class="cursor-pointer hover:underline" @click.stop="setMatchFieldValue('asin', mediaMetadata.asin)">{{ mediaMetadata.asin }}</a>
|
{{ $strings.LabelCurrently }} <a :title="$strings.LabelClickToUseCurrentValue" class="cursor-pointer hover:underline" @click.stop="setMatchFieldValue('asin', mediaMetadata.asin)">{{ mediaMetadata.asin }}</a>
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -179,7 +179,7 @@
|
|||||||
<div class="flex-grow ml-4">
|
<div class="flex-grow ml-4">
|
||||||
<ui-text-input-with-label v-model="selectedMatch.itunesId" type="number" :disabled="!selectedMatchUsage.itunesId" label="iTunes ID" />
|
<ui-text-input-with-label v-model="selectedMatch.itunesId" type="number" :disabled="!selectedMatchUsage.itunesId" label="iTunes ID" />
|
||||||
<p v-if="mediaMetadata.itunesId" class="text-xs ml-1 text-white text-opacity-60">
|
<p v-if="mediaMetadata.itunesId" class="text-xs ml-1 text-white text-opacity-60">
|
||||||
{{ $strings.LabelCurrently }} <a title="Click to use current value" class="cursor-pointer hover:underline" @click.stop="setMatchFieldValue('itunesId', mediaMetadata.itunesId)">{{ mediaMetadata.itunesId }}</a>
|
{{ $strings.LabelCurrently }} <a :title="$strings.LabelClickToUseCurrentValue" class="cursor-pointer hover:underline" @click.stop="setMatchFieldValue('itunesId', mediaMetadata.itunesId)">{{ mediaMetadata.itunesId }}</a>
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -188,7 +188,7 @@
|
|||||||
<div class="flex-grow ml-4">
|
<div class="flex-grow ml-4">
|
||||||
<ui-text-input-with-label v-model="selectedMatch.feedUrl" :disabled="!selectedMatchUsage.feedUrl" label="RSS Feed URL" />
|
<ui-text-input-with-label v-model="selectedMatch.feedUrl" :disabled="!selectedMatchUsage.feedUrl" label="RSS Feed URL" />
|
||||||
<p v-if="mediaMetadata.feedUrl" class="text-xs ml-1 text-white text-opacity-60">
|
<p v-if="mediaMetadata.feedUrl" class="text-xs ml-1 text-white text-opacity-60">
|
||||||
{{ $strings.LabelCurrently }} <a title="Click to use current value" class="cursor-pointer hover:underline" @click.stop="setMatchFieldValue('feedUrl', mediaMetadata.feedUrl)">{{ mediaMetadata.feedUrl }}</a>
|
{{ $strings.LabelCurrently }} <a :title="$strings.LabelClickToUseCurrentValue" class="cursor-pointer hover:underline" @click.stop="setMatchFieldValue('feedUrl', mediaMetadata.feedUrl)">{{ mediaMetadata.feedUrl }}</a>
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -197,7 +197,7 @@
|
|||||||
<div class="flex-grow ml-4">
|
<div class="flex-grow ml-4">
|
||||||
<ui-text-input-with-label v-model="selectedMatch.itunesPageUrl" :disabled="!selectedMatchUsage.itunesPageUrl" label="iTunes Page URL" />
|
<ui-text-input-with-label v-model="selectedMatch.itunesPageUrl" :disabled="!selectedMatchUsage.itunesPageUrl" label="iTunes Page URL" />
|
||||||
<p v-if="mediaMetadata.itunesPageUrl" class="text-xs ml-1 text-white text-opacity-60">
|
<p v-if="mediaMetadata.itunesPageUrl" class="text-xs ml-1 text-white text-opacity-60">
|
||||||
{{ $strings.LabelCurrently }} <a title="Click to use current value" class="cursor-pointer hover:underline" @click.stop="setMatchFieldValue('itunesPageUrl', mediaMetadata.itunesPageUrl)">{{ mediaMetadata.itunesPageUrl }}</a>
|
{{ $strings.LabelCurrently }} <a :title="$strings.LabelClickToUseCurrentValue" class="cursor-pointer hover:underline" @click.stop="setMatchFieldValue('itunesPageUrl', mediaMetadata.itunesPageUrl)">{{ mediaMetadata.itunesPageUrl }}</a>
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -206,7 +206,7 @@
|
|||||||
<div class="flex-grow ml-4">
|
<div class="flex-grow ml-4">
|
||||||
<ui-text-input-with-label v-model="selectedMatch.releaseDate" :disabled="!selectedMatchUsage.releaseDate" :label="$strings.LabelReleaseDate" />
|
<ui-text-input-with-label v-model="selectedMatch.releaseDate" :disabled="!selectedMatchUsage.releaseDate" :label="$strings.LabelReleaseDate" />
|
||||||
<p v-if="mediaMetadata.releaseDate" class="text-xs ml-1 text-white text-opacity-60">
|
<p v-if="mediaMetadata.releaseDate" class="text-xs ml-1 text-white text-opacity-60">
|
||||||
{{ $strings.LabelCurrently }} <a title="Click to use current value" class="cursor-pointer hover:underline" @click.stop="setMatchFieldValue('releaseDate', mediaMetadata.releaseDate)">{{ mediaMetadata.releaseDate }}</a>
|
{{ $strings.LabelCurrently }} <a :title="$strings.LabelClickToUseCurrentValue" class="cursor-pointer hover:underline" @click.stop="setMatchFieldValue('releaseDate', mediaMetadata.releaseDate)">{{ mediaMetadata.releaseDate }}</a>
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -2,28 +2,28 @@
|
|||||||
<div class="w-full h-full relative">
|
<div class="w-full h-full relative">
|
||||||
<div id="scheduleWrapper" class="w-full overflow-y-auto px-2 py-4 md:px-6 md:py-6">
|
<div id="scheduleWrapper" class="w-full overflow-y-auto px-2 py-4 md:px-6 md:py-6">
|
||||||
<template v-if="!feedUrl">
|
<template v-if="!feedUrl">
|
||||||
<widgets-alert type="warning" class="text-base mb-4">No RSS feed URL is set for this podcast</widgets-alert>
|
<widgets-alert type="warning" class="text-base mb-4">{{ $strings.ToastPodcastNoRssFeed }}</widgets-alert>
|
||||||
</template>
|
</template>
|
||||||
<template v-if="feedUrl || autoDownloadEpisodes">
|
<template v-if="feedUrl || autoDownloadEpisodes">
|
||||||
<div class="flex items-center justify-between mb-4">
|
<div class="flex items-center justify-between mb-4">
|
||||||
<p class="text-base md:text-xl font-semibold">Schedule Automatic Episode Downloads</p>
|
<p class="text-base md:text-xl font-semibold">{{ $strings.HeaderScheduleEpisodeDownloads }}</p>
|
||||||
<ui-checkbox v-model="enableAutoDownloadEpisodes" label="Enable" medium checkbox-bg="bg" label-class="pl-2 text-base md:text-lg" />
|
<ui-checkbox v-model="enableAutoDownloadEpisodes" :label="$strings.LabelEnable" medium checkbox-bg="bg" label-class="pl-2 text-base md:text-lg" />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div v-if="enableAutoDownloadEpisodes" class="flex items-center py-2">
|
<div v-if="enableAutoDownloadEpisodes" class="flex items-center py-2">
|
||||||
<ui-text-input ref="maxEpisodesInput" type="number" v-model="newMaxEpisodesToKeep" no-spinner :padding-x="1" text-center class="w-10 text-base" @change="updatedMaxEpisodesToKeep" />
|
<ui-text-input ref="maxEpisodesInput" type="number" v-model="newMaxEpisodesToKeep" no-spinner :padding-x="1" text-center class="w-10 text-base" @change="updatedMaxEpisodesToKeep" />
|
||||||
<ui-tooltip text="Value of 0 sets no max limit. After a new episode is auto-downloaded this will delete the oldest episode if you have more than X episodes. <br>This will only delete 1 episode per new download.">
|
<ui-tooltip :text="$strings.LabelMaxEpisodesToKeepHelp">
|
||||||
<p class="pl-4 text-base">
|
<p class="pl-4 text-base">
|
||||||
Max episodes to keep
|
{{ $strings.LabelMaxEpisodesToKeep }}
|
||||||
<span class="material-symbols icon-text">info</span>
|
<span class="material-symbols icon-text">info</span>
|
||||||
</p>
|
</p>
|
||||||
</ui-tooltip>
|
</ui-tooltip>
|
||||||
</div>
|
</div>
|
||||||
<div v-if="enableAutoDownloadEpisodes" class="flex items-center py-2">
|
<div v-if="enableAutoDownloadEpisodes" class="flex items-center py-2">
|
||||||
<ui-text-input ref="maxEpisodesToDownloadInput" type="number" v-model="newMaxNewEpisodesToDownload" no-spinner :padding-x="1" text-center class="w-10 text-base" @change="updateMaxNewEpisodesToDownload" />
|
<ui-text-input ref="maxEpisodesToDownloadInput" type="number" v-model="newMaxNewEpisodesToDownload" no-spinner :padding-x="1" text-center class="w-10 text-base" @change="updateMaxNewEpisodesToDownload" />
|
||||||
<ui-tooltip text="Value of 0 sets no max limit. When checking for new episodes this is the max number of episodes that will be downloaded.">
|
<ui-tooltip :text="$strings.LabelUseZeroForUnlimited">
|
||||||
<p class="pl-4 text-base">
|
<p class="pl-4 text-base">
|
||||||
Max new episodes to download per check
|
{{ $strings.LabelMaxEpisodesToDownloadPerCheck }}
|
||||||
<span class="material-symbols icon-text">info</span>
|
<span class="material-symbols icon-text">info</span>
|
||||||
</p>
|
</p>
|
||||||
</ui-tooltip>
|
</ui-tooltip>
|
||||||
@@ -36,7 +36,7 @@
|
|||||||
<div v-if="feedUrl || autoDownloadEpisodes" class="absolute bottom-0 left-0 w-full py-2 md:py-4 bg-bg border-t border-white border-opacity-5">
|
<div v-if="feedUrl || autoDownloadEpisodes" class="absolute bottom-0 left-0 w-full py-2 md:py-4 bg-bg border-t border-white border-opacity-5">
|
||||||
<div class="flex items-center px-2 md:px-4">
|
<div class="flex items-center px-2 md:px-4">
|
||||||
<div class="flex-grow" />
|
<div class="flex-grow" />
|
||||||
<ui-btn @click="save" :disabled="!isUpdated" :color="isUpdated ? 'success' : 'primary'" class="mx-2">{{ isUpdated ? 'Save' : 'No update necessary' }}</ui-btn>
|
<ui-btn @click="save" :disabled="!isUpdated" :color="isUpdated ? 'success' : 'primary'" class="mx-2">{{ isUpdated ? $strings.ButtonSave : $strings.MessageNoUpdatesWereNecessary }}</ui-btn>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -111,7 +111,6 @@ export default {
|
|||||||
},
|
},
|
||||||
updateLibrary(library) {
|
updateLibrary(library) {
|
||||||
this.mapLibraryToCopy(library)
|
this.mapLibraryToCopy(library)
|
||||||
console.log('Updated library', this.libraryCopy)
|
|
||||||
},
|
},
|
||||||
getNewLibraryData() {
|
getNewLibraryData() {
|
||||||
return {
|
return {
|
||||||
@@ -128,7 +127,9 @@ export default {
|
|||||||
autoScanCronExpression: null,
|
autoScanCronExpression: null,
|
||||||
hideSingleBookSeries: false,
|
hideSingleBookSeries: false,
|
||||||
onlyShowLaterBooksInContinueSeries: false,
|
onlyShowLaterBooksInContinueSeries: false,
|
||||||
metadataPrecedence: ['folderStructure', 'audioMetatags', 'nfoFile', 'txtFiles', 'opfFile', 'absMetadata']
|
metadataPrecedence: ['folderStructure', 'audioMetatags', 'nfoFile', 'txtFiles', 'opfFile', 'absMetadata'],
|
||||||
|
markAsFinishedPercentComplete: null,
|
||||||
|
markAsFinishedTimeRemaining: 10
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -160,7 +161,7 @@ export default {
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
if (!this.libraryCopy.folders.length) {
|
if (!this.libraryCopy.folders.length) {
|
||||||
this.$toast.error('Library must have at least 1 path')
|
this.$toast.error(this.$strings.ToastMustHaveAtLeastOnePath)
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -236,7 +237,6 @@ export default {
|
|||||||
this.show = false
|
this.show = false
|
||||||
this.$toast.success(this.$getString('ToastLibraryCreateSuccess', [res.name]))
|
this.$toast.success(this.$getString('ToastLibraryCreateSuccess', [res.name]))
|
||||||
if (!this.$store.state.libraries.currentLibraryId) {
|
if (!this.$store.state.libraries.currentLibraryId) {
|
||||||
console.log('Setting initially library id', res.id)
|
|
||||||
// First library added
|
// First library added
|
||||||
this.$store.dispatch('libraries/fetch', res.id)
|
this.$store.dispatch('libraries/fetch', res.id)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,79 +1,95 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="w-full h-full px-1 md:px-4 py-1 mb-4">
|
<div class="w-full h-full px-1 md:px-4 py-1 mb-4">
|
||||||
<div class="flex items-center py-3">
|
<div class="flex flex-wrap">
|
||||||
<ui-toggle-switch v-model="useSquareBookCovers" @input="formUpdated" />
|
<div class="flex items-center p-2 w-full md:w-1/2">
|
||||||
|
<ui-toggle-switch v-model="useSquareBookCovers" size="sm" @input="formUpdated" />
|
||||||
<ui-tooltip :text="$strings.LabelSettingsSquareBookCoversHelp">
|
<ui-tooltip :text="$strings.LabelSettingsSquareBookCoversHelp">
|
||||||
<p class="pl-4 text-base">
|
<p class="pl-4 text-sm">
|
||||||
{{ $strings.LabelSettingsSquareBookCovers }}
|
{{ $strings.LabelSettingsSquareBookCovers }}
|
||||||
<span class="material-symbols icon-text text-sm">info</span>
|
<span class="material-symbols icon-text text-sm">info</span>
|
||||||
</p>
|
</p>
|
||||||
</ui-tooltip>
|
</ui-tooltip>
|
||||||
</div>
|
</div>
|
||||||
<div class="py-3">
|
<div class="p-2 w-full md:w-1/2">
|
||||||
<div class="flex items-center">
|
<div class="flex items-center">
|
||||||
<ui-toggle-switch v-if="!globalWatcherDisabled" v-model="enableWatcher" @input="formUpdated" />
|
<ui-toggle-switch v-if="!globalWatcherDisabled" v-model="enableWatcher" size="sm" @input="formUpdated" />
|
||||||
<ui-toggle-switch v-else disabled :value="false" />
|
<ui-toggle-switch v-else disabled size="sm" :value="false" />
|
||||||
<p class="pl-4 text-base">{{ $strings.LabelSettingsEnableWatcherForLibrary }}</p>
|
<p class="pl-4 text-sm">{{ $strings.LabelSettingsEnableWatcherForLibrary }}</p>
|
||||||
</div>
|
</div>
|
||||||
<p v-if="globalWatcherDisabled" class="text-xs text-warning">*{{ $strings.MessageWatcherIsDisabledGlobally }}</p>
|
<p v-if="globalWatcherDisabled" class="text-xs text-warning">*{{ $strings.MessageWatcherIsDisabledGlobally }}</p>
|
||||||
</div>
|
</div>
|
||||||
<div v-if="isBookLibrary" class="flex items-center py-3">
|
<div v-if="isBookLibrary" class="flex items-center p-2 w-full md:w-1/2">
|
||||||
<ui-toggle-switch v-model="audiobooksOnly" @input="formUpdated" />
|
<ui-toggle-switch v-model="audiobooksOnly" size="sm" @input="formUpdated" />
|
||||||
<ui-tooltip :text="$strings.LabelSettingsAudiobooksOnlyHelp">
|
<ui-tooltip :text="$strings.LabelSettingsAudiobooksOnlyHelp">
|
||||||
<p class="pl-4 text-base">
|
<p class="pl-4 text-sm">
|
||||||
{{ $strings.LabelSettingsAudiobooksOnly }}
|
{{ $strings.LabelSettingsAudiobooksOnly }}
|
||||||
<span class="material-symbols icon-text text-sm">info</span>
|
<span class="material-symbols icon-text text-sm">info</span>
|
||||||
</p>
|
</p>
|
||||||
</ui-tooltip>
|
</ui-tooltip>
|
||||||
</div>
|
</div>
|
||||||
<div v-if="isBookLibrary" class="py-3">
|
<div v-if="isBookLibrary" class="p-2 w-full md:w-1/2">
|
||||||
<div class="flex items-center">
|
<div class="flex items-center">
|
||||||
<ui-toggle-switch v-model="skipMatchingMediaWithAsin" @input="formUpdated" />
|
<ui-toggle-switch v-model="skipMatchingMediaWithAsin" size="sm" @input="formUpdated" />
|
||||||
<p class="pl-4 text-base">{{ $strings.LabelSettingsSkipMatchingBooksWithASIN }}</p>
|
<p class="pl-4 text-sm">{{ $strings.LabelSettingsSkipMatchingBooksWithASIN }}</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div v-if="isBookLibrary" class="py-3">
|
<div v-if="isBookLibrary" class="p-2 w-full md:w-1/2">
|
||||||
<div class="flex items-center">
|
<div class="flex items-center">
|
||||||
<ui-toggle-switch v-model="skipMatchingMediaWithIsbn" @input="formUpdated" />
|
<ui-toggle-switch v-model="skipMatchingMediaWithIsbn" size="sm" @input="formUpdated" />
|
||||||
<p class="pl-4 text-base">{{ $strings.LabelSettingsSkipMatchingBooksWithISBN }}</p>
|
<p class="pl-4 text-sm">{{ $strings.LabelSettingsSkipMatchingBooksWithISBN }}</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div v-if="isBookLibrary" class="py-3">
|
<div v-if="isBookLibrary" class="p-2 w-full md:w-1/2">
|
||||||
<div class="flex items-center">
|
<div class="flex items-center">
|
||||||
<ui-toggle-switch v-model="hideSingleBookSeries" @input="formUpdated" />
|
<ui-toggle-switch v-model="hideSingleBookSeries" size="sm" @input="formUpdated" />
|
||||||
<ui-tooltip :text="$strings.LabelSettingsHideSingleBookSeriesHelp">
|
<ui-tooltip :text="$strings.LabelSettingsHideSingleBookSeriesHelp">
|
||||||
<p class="pl-4 text-base">
|
<p class="pl-4 text-sm">
|
||||||
{{ $strings.LabelSettingsHideSingleBookSeries }}
|
{{ $strings.LabelSettingsHideSingleBookSeries }}
|
||||||
<span class="material-symbols icon-text text-sm">info</span>
|
<span class="material-symbols icon-text text-sm">info</span>
|
||||||
</p>
|
</p>
|
||||||
</ui-tooltip>
|
</ui-tooltip>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div v-if="isBookLibrary" class="py-3">
|
<div v-if="isBookLibrary" class="p-2 w-full md:w-1/2">
|
||||||
<div class="flex items-center">
|
<div class="flex items-center">
|
||||||
<ui-toggle-switch v-model="onlyShowLaterBooksInContinueSeries" @input="formUpdated" />
|
<ui-toggle-switch v-model="onlyShowLaterBooksInContinueSeries" size="sm" @input="formUpdated" />
|
||||||
<ui-tooltip :text="$strings.LabelSettingsOnlyShowLaterBooksInContinueSeriesHelp">
|
<ui-tooltip :text="$strings.LabelSettingsOnlyShowLaterBooksInContinueSeriesHelp">
|
||||||
<p class="pl-4 text-base">
|
<p class="pl-4 text-sm">
|
||||||
{{ $strings.LabelSettingsOnlyShowLaterBooksInContinueSeries }}
|
{{ $strings.LabelSettingsOnlyShowLaterBooksInContinueSeries }}
|
||||||
<span class="material-symbols icon-text text-sm">info</span>
|
<span class="material-symbols icon-text text-sm">info</span>
|
||||||
</p>
|
</p>
|
||||||
</ui-tooltip>
|
</ui-tooltip>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div v-if="isBookLibrary" class="py-3">
|
<div v-if="isBookLibrary" class="p-2 w-full md:w-1/2">
|
||||||
<div class="flex items-center">
|
<div class="flex items-center">
|
||||||
<ui-toggle-switch v-model="epubsAllowScriptedContent" @input="formUpdated" />
|
<ui-toggle-switch v-model="epubsAllowScriptedContent" size="sm" @input="formUpdated" />
|
||||||
<ui-tooltip :text="$strings.LabelSettingsEpubsAllowScriptedContentHelp">
|
<ui-tooltip :text="$strings.LabelSettingsEpubsAllowScriptedContentHelp">
|
||||||
<p class="pl-4 text-base">
|
<p class="pl-4 text-sm">
|
||||||
{{ $strings.LabelSettingsEpubsAllowScriptedContent }}
|
{{ $strings.LabelSettingsEpubsAllowScriptedContent }}
|
||||||
<span class="material-symbols icon-text text-sm">info</span>
|
<span class="material-symbols icon-text text-sm">info</span>
|
||||||
</p>
|
</p>
|
||||||
</ui-tooltip>
|
</ui-tooltip>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div v-if="isPodcastLibrary" class="py-3">
|
<div v-if="isPodcastLibrary" class="p-2 w-full md:w-1/2">
|
||||||
<ui-dropdown :label="$strings.LabelPodcastSearchRegion" v-model="podcastSearchRegion" :items="$podcastSearchRegionOptions" small class="max-w-72" menu-max-height="200px" @input="formUpdated" />
|
<ui-dropdown :label="$strings.LabelPodcastSearchRegion" v-model="podcastSearchRegion" :items="$podcastSearchRegionOptions" small class="max-w-72" menu-max-height="200px" @input="formUpdated" />
|
||||||
</div>
|
</div>
|
||||||
|
<div class="p-2 w-full flex items-center space-x-2 flex-wrap">
|
||||||
|
<div>
|
||||||
|
<ui-dropdown v-model="markAsFinishedWhen" :items="maskAsFinishedWhenItems" :label="$strings.LabelSettingsLibraryMarkAsFinishedWhen" small class="w-72 min-w-72 text-sm" menu-max-height="200px" @input="markAsFinishedWhenChanged" />
|
||||||
|
</div>
|
||||||
|
<div class="w-16">
|
||||||
|
<div>
|
||||||
|
<label class="px-1 text-sm font-semibold"></label>
|
||||||
|
<div class="relative">
|
||||||
|
<ui-text-input v-model="markAsFinishedValue" type="number" label="" no-spinner custom-input-class="pr-5" @input="markAsFinishedChanged" />
|
||||||
|
<div class="absolute top-0 bottom-0 right-4 flex items-center">{{ markAsFinishedWhen === 'timeRemaining' ? '' : '%' }}</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
@@ -97,7 +113,9 @@ export default {
|
|||||||
epubsAllowScriptedContent: false,
|
epubsAllowScriptedContent: false,
|
||||||
hideSingleBookSeries: false,
|
hideSingleBookSeries: false,
|
||||||
onlyShowLaterBooksInContinueSeries: false,
|
onlyShowLaterBooksInContinueSeries: false,
|
||||||
podcastSearchRegion: 'us'
|
podcastSearchRegion: 'us',
|
||||||
|
markAsFinishedWhen: 'timeRemaining',
|
||||||
|
markAsFinishedValue: 10
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
@@ -119,10 +137,34 @@ export default {
|
|||||||
providers() {
|
providers() {
|
||||||
if (this.mediaType === 'podcast') return this.$store.state.scanners.podcastProviders
|
if (this.mediaType === 'podcast') return this.$store.state.scanners.podcastProviders
|
||||||
return this.$store.state.scanners.providers
|
return this.$store.state.scanners.providers
|
||||||
|
},
|
||||||
|
maskAsFinishedWhenItems() {
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
text: this.$strings.LabelSettingsLibraryMarkAsFinishedTimeRemaining,
|
||||||
|
value: 'timeRemaining'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
text: this.$strings.LabelSettingsLibraryMarkAsFinishedPercentComplete,
|
||||||
|
value: 'percentComplete'
|
||||||
|
}
|
||||||
|
]
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
|
markAsFinishedWhenChanged(val) {
|
||||||
|
if (val === 'percentComplete' && this.markAsFinishedValue > 100) {
|
||||||
|
this.markAsFinishedValue = 100
|
||||||
|
}
|
||||||
|
this.formUpdated()
|
||||||
|
},
|
||||||
|
markAsFinishedChanged(val) {
|
||||||
|
this.formUpdated()
|
||||||
|
},
|
||||||
getLibraryData() {
|
getLibraryData() {
|
||||||
|
let markAsFinishedTimeRemaining = this.markAsFinishedWhen === 'timeRemaining' ? Number(this.markAsFinishedValue) : null
|
||||||
|
let markAsFinishedPercentComplete = this.markAsFinishedWhen === 'percentComplete' ? Number(this.markAsFinishedValue) : null
|
||||||
|
|
||||||
return {
|
return {
|
||||||
settings: {
|
settings: {
|
||||||
coverAspectRatio: this.useSquareBookCovers ? this.$constants.BookCoverAspectRatio.SQUARE : this.$constants.BookCoverAspectRatio.STANDARD,
|
coverAspectRatio: this.useSquareBookCovers ? this.$constants.BookCoverAspectRatio.SQUARE : this.$constants.BookCoverAspectRatio.STANDARD,
|
||||||
@@ -133,7 +175,9 @@ export default {
|
|||||||
epubsAllowScriptedContent: !!this.epubsAllowScriptedContent,
|
epubsAllowScriptedContent: !!this.epubsAllowScriptedContent,
|
||||||
hideSingleBookSeries: !!this.hideSingleBookSeries,
|
hideSingleBookSeries: !!this.hideSingleBookSeries,
|
||||||
onlyShowLaterBooksInContinueSeries: !!this.onlyShowLaterBooksInContinueSeries,
|
onlyShowLaterBooksInContinueSeries: !!this.onlyShowLaterBooksInContinueSeries,
|
||||||
podcastSearchRegion: this.podcastSearchRegion
|
podcastSearchRegion: this.podcastSearchRegion,
|
||||||
|
markAsFinishedTimeRemaining: markAsFinishedTimeRemaining,
|
||||||
|
markAsFinishedPercentComplete: markAsFinishedPercentComplete
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -150,6 +194,11 @@ export default {
|
|||||||
this.hideSingleBookSeries = !!this.librarySettings.hideSingleBookSeries
|
this.hideSingleBookSeries = !!this.librarySettings.hideSingleBookSeries
|
||||||
this.onlyShowLaterBooksInContinueSeries = !!this.librarySettings.onlyShowLaterBooksInContinueSeries
|
this.onlyShowLaterBooksInContinueSeries = !!this.librarySettings.onlyShowLaterBooksInContinueSeries
|
||||||
this.podcastSearchRegion = this.librarySettings.podcastSearchRegion || 'us'
|
this.podcastSearchRegion = this.librarySettings.podcastSearchRegion || 'us'
|
||||||
|
this.markAsFinishedWhen = this.librarySettings.markAsFinishedTimeRemaining ? 'timeRemaining' : 'percentComplete'
|
||||||
|
if (!this.librarySettings.markAsFinishedTimeRemaining && !this.librarySettings.markAsFinishedPercentComplete) {
|
||||||
|
this.markAsFinishedWhen = 'timeRemaining'
|
||||||
|
}
|
||||||
|
this.markAsFinishedValue = this.librarySettings.markAsFinishedTimeRemaining || this.librarySettings.markAsFinishedPercentComplete || 10
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
mounted() {
|
mounted() {
|
||||||
|
|||||||
@@ -3,13 +3,13 @@
|
|||||||
<div class="w-full border border-black-200 p-4 my-8">
|
<div class="w-full border border-black-200 p-4 my-8">
|
||||||
<div class="flex flex-wrap items-center">
|
<div class="flex flex-wrap items-center">
|
||||||
<div>
|
<div>
|
||||||
<p class="text-lg">Remove metadata files in library item folders</p>
|
<p class="text-lg">{{ $strings.LabelRemoveMetadataFile }}</p>
|
||||||
<p class="max-w-sm text-sm pt-2 text-gray-300">Remove all metadata.json or metadata.abs files in your {{ mediaType }} folders</p>
|
<p class="max-w-sm text-sm pt-2 text-gray-300">{{ $getString('LabelRemoveMetadataFileHelp', [mediaType]) }}</p>
|
||||||
</div>
|
</div>
|
||||||
<div class="flex-grow" />
|
<div class="flex-grow" />
|
||||||
<div>
|
<div>
|
||||||
<ui-btn class="mb-4 block" @click.stop="removeAllMetadataClick('json')">Remove all metadata.json</ui-btn>
|
<ui-btn class="mb-4 block" @click.stop="removeAllMetadataClick('json')">{{ $strings.LabelRemoveAllMetadataJson }}</ui-btn>
|
||||||
<ui-btn @click.stop="removeAllMetadataClick('abs')">Remove all metadata.abs</ui-btn>
|
<ui-btn @click.stop="removeAllMetadataClick('abs')">{{ $strings.LabelRemoveAllMetadataAbs }}</ui-btn>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -43,7 +43,7 @@ export default {
|
|||||||
methods: {
|
methods: {
|
||||||
removeAllMetadataClick(ext) {
|
removeAllMetadataClick(ext) {
|
||||||
const payload = {
|
const payload = {
|
||||||
message: `Are you sure you want to remove all metadata.${ext} files in your library item folders?`,
|
message: this.$getString('MessageConfirmRemoveMetadataFiles', [ext]),
|
||||||
persistent: true,
|
persistent: true,
|
||||||
callback: (confirmed) => {
|
callback: (confirmed) => {
|
||||||
if (confirmed) {
|
if (confirmed) {
|
||||||
@@ -60,16 +60,16 @@ export default {
|
|||||||
.$post(`/api/libraries/${this.libraryId}/remove-metadata?ext=${ext}`)
|
.$post(`/api/libraries/${this.libraryId}/remove-metadata?ext=${ext}`)
|
||||||
.then((data) => {
|
.then((data) => {
|
||||||
if (!data.found) {
|
if (!data.found) {
|
||||||
this.$toast.info(`No metadata.${ext} files were found in library`)
|
this.$toast.info(this.$getString('ToastMetadataFilesRemovedNoneFound', [ext]))
|
||||||
} else if (!data.removed) {
|
} else if (!data.removed) {
|
||||||
this.$toast.success(`No metadata.${ext} files removed`)
|
this.$toast.success(this.$getString('ToastMetadataFilesRemovedNoneRemoved', [ext]))
|
||||||
} else {
|
} else {
|
||||||
this.$toast.success(`Successfully removed ${data.removed} metadata.${ext} files`)
|
this.$toast.success(this.$getString('ToastMetadataFilesRemovedSuccess', [data.removed, ext]))
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.catch((error) => {
|
.catch((error) => {
|
||||||
console.error('Failed to remove metadata files', error)
|
console.error('Failed to remove metadata files', error)
|
||||||
this.$toast.error('Failed to remove metadata files')
|
this.$toast.error(this.$getString('ToastMetadataFilesRemovedError', [ext]))
|
||||||
})
|
})
|
||||||
.finally(() => {
|
.finally(() => {
|
||||||
this.$emit('update:processing', false)
|
this.$emit('update:processing', false)
|
||||||
|
|||||||
@@ -130,7 +130,6 @@ export default {
|
|||||||
.$post(`/api/playlists/${playlist.id}/batch/remove`, { items: itemObjects })
|
.$post(`/api/playlists/${playlist.id}/batch/remove`, { items: itemObjects })
|
||||||
.then((updatedPlaylist) => {
|
.then((updatedPlaylist) => {
|
||||||
console.log(`Items removed from playlist`, updatedPlaylist)
|
console.log(`Items removed from playlist`, updatedPlaylist)
|
||||||
this.$toast.success(this.$strings.ToastPlaylistUpdateSuccess)
|
|
||||||
this.processing = false
|
this.processing = false
|
||||||
})
|
})
|
||||||
.catch((error) => {
|
.catch((error) => {
|
||||||
@@ -148,7 +147,6 @@ export default {
|
|||||||
.$post(`/api/playlists/${playlist.id}/batch/add`, { items: itemObjects })
|
.$post(`/api/playlists/${playlist.id}/batch/add`, { items: itemObjects })
|
||||||
.then((updatedPlaylist) => {
|
.then((updatedPlaylist) => {
|
||||||
console.log(`Items added to playlist`, updatedPlaylist)
|
console.log(`Items added to playlist`, updatedPlaylist)
|
||||||
this.$toast.success(this.$strings.ToastPlaylistUpdateSuccess)
|
|
||||||
this.processing = false
|
this.processing = false
|
||||||
})
|
})
|
||||||
.catch((error) => {
|
.catch((error) => {
|
||||||
@@ -174,7 +172,6 @@ export default {
|
|||||||
.$post('/api/playlists', newPlaylist)
|
.$post('/api/playlists', newPlaylist)
|
||||||
.then((data) => {
|
.then((data) => {
|
||||||
console.log('New playlist created', data)
|
console.log('New playlist created', data)
|
||||||
this.$toast.success(this.$strings.ToastPlaylistCreateSuccess + ': ' + data.name)
|
|
||||||
this.processing = false
|
this.processing = false
|
||||||
this.newPlaylistName = ''
|
this.newPlaylistName = ''
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -156,7 +156,12 @@ export default {
|
|||||||
return this.selectedFolder.fullPath
|
return this.selectedFolder.fullPath
|
||||||
},
|
},
|
||||||
podcastTypes() {
|
podcastTypes() {
|
||||||
return this.$store.state.globals.podcastTypes || []
|
return this.$store.state.globals.podcastTypes.map((e) => {
|
||||||
|
return {
|
||||||
|
text: this.$strings[e.descriptionKey] || e.text,
|
||||||
|
value: e.value
|
||||||
|
}
|
||||||
|
})
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
|
|||||||
@@ -18,6 +18,23 @@
|
|||||||
<p dir="auto" class="text-lg font-semibold mb-6">{{ title }}</p>
|
<p dir="auto" class="text-lg font-semibold mb-6">{{ title }}</p>
|
||||||
<div v-if="description" dir="auto" class="default-style" v-html="description" />
|
<div v-if="description" dir="auto" class="default-style" v-html="description" />
|
||||||
<p v-else class="mb-2">{{ $strings.MessageNoDescription }}</p>
|
<p v-else class="mb-2">{{ $strings.MessageNoDescription }}</p>
|
||||||
|
|
||||||
|
<div class="w-full h-px bg-white/5 my-4" />
|
||||||
|
|
||||||
|
<div class="flex items-center">
|
||||||
|
<div class="flex-grow">
|
||||||
|
<p class="font-semibold text-xs mb-1">{{ $strings.LabelFilename }}</p>
|
||||||
|
<p class="mb-2 text-xs">
|
||||||
|
{{ audioFileFilename }}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<div class="flex-grow">
|
||||||
|
<p class="font-semibold text-xs mb-1">{{ $strings.LabelSize }}</p>
|
||||||
|
<p class="mb-2 text-xs">
|
||||||
|
{{ audioFileSize }}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</modals-modal>
|
</modals-modal>
|
||||||
</template>
|
</template>
|
||||||
@@ -54,7 +71,7 @@ export default {
|
|||||||
return this.episode.description || ''
|
return this.episode.description || ''
|
||||||
},
|
},
|
||||||
media() {
|
media() {
|
||||||
return this.libraryItem ? this.libraryItem.media || {} : {}
|
return this.libraryItem?.media || {}
|
||||||
},
|
},
|
||||||
mediaMetadata() {
|
mediaMetadata() {
|
||||||
return this.media.metadata || {}
|
return this.media.metadata || {}
|
||||||
@@ -65,6 +82,14 @@ export default {
|
|||||||
podcastAuthor() {
|
podcastAuthor() {
|
||||||
return this.mediaMetadata.author
|
return this.mediaMetadata.author
|
||||||
},
|
},
|
||||||
|
audioFileFilename() {
|
||||||
|
return this.episode.audioFile?.metadata?.filename || ''
|
||||||
|
},
|
||||||
|
audioFileSize() {
|
||||||
|
const size = this.episode.audioFile?.metadata?.size || 0
|
||||||
|
|
||||||
|
return this.$bytesPretty(size)
|
||||||
|
},
|
||||||
bookCoverAspectRatio() {
|
bookCoverAspectRatio() {
|
||||||
return this.$store.getters['libraries/getBookCoverAspectRatio']
|
return this.$store.getters['libraries/getBookCoverAspectRatio']
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -33,11 +33,11 @@
|
|||||||
</div>
|
</div>
|
||||||
<div v-if="enclosureUrl" class="pb-4 pt-6">
|
<div v-if="enclosureUrl" class="pb-4 pt-6">
|
||||||
<ui-text-input-with-label :value="enclosureUrl" readonly class="text-xs">
|
<ui-text-input-with-label :value="enclosureUrl" readonly class="text-xs">
|
||||||
<label class="px-1 text-xs text-gray-200 font-semibold">Episode URL from RSS feed</label>
|
<label class="px-1 text-xs text-gray-200 font-semibold">{{ $strings.LabelEpisodeUrlFromRssFeed }}</label>
|
||||||
</ui-text-input-with-label>
|
</ui-text-input-with-label>
|
||||||
</div>
|
</div>
|
||||||
<div v-else class="py-4">
|
<div v-else class="py-4">
|
||||||
<p class="text-xs text-gray-300 font-semibold">Episode not linked to RSS feed episode</p>
|
<p class="text-xs text-gray-300 font-semibold">{{ $strings.LabelEpisodeNotLinkedToRssFeed }}</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
@@ -97,7 +97,12 @@ export default {
|
|||||||
return this.enclosure.url
|
return this.enclosure.url
|
||||||
},
|
},
|
||||||
episodeTypes() {
|
episodeTypes() {
|
||||||
return this.$store.state.globals.episodeTypes || []
|
return this.$store.state.globals.episodeTypes.map((e) => {
|
||||||
|
return {
|
||||||
|
text: this.$strings[e.descriptionKey] || e.text,
|
||||||
|
value: e.value
|
||||||
|
}
|
||||||
|
})
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
@@ -152,14 +157,14 @@ export default {
|
|||||||
const updateResult = await this.$axios.$patch(`/api/podcasts/${this.libraryItem.id}/episode/${this.episodeId}`, updatedDetails).catch((error) => {
|
const updateResult = await this.$axios.$patch(`/api/podcasts/${this.libraryItem.id}/episode/${this.episodeId}`, updatedDetails).catch((error) => {
|
||||||
console.error('Failed update episode', error)
|
console.error('Failed update episode', error)
|
||||||
this.isProcessing = false
|
this.isProcessing = false
|
||||||
this.$toast.error(error?.response?.data || 'Failed to update episode')
|
this.$toast.error(error?.response?.data || this.$strings.ToastFailedToUpdate)
|
||||||
return false
|
return false
|
||||||
})
|
})
|
||||||
|
|
||||||
this.isProcessing = false
|
this.isProcessing = false
|
||||||
if (updateResult) {
|
if (updateResult) {
|
||||||
if (updateResult) {
|
if (updateResult) {
|
||||||
this.$toast.success('Podcast episode updated')
|
this.$toast.success(this.$strings.ToastItemUpdateSuccess)
|
||||||
return true
|
return true
|
||||||
} else {
|
} else {
|
||||||
this.$toast.info(this.$strings.MessageNoUpdatesWereNecessary)
|
this.$toast.info(this.$strings.MessageNoUpdatesWereNecessary)
|
||||||
|
|||||||
@@ -10,9 +10,9 @@
|
|||||||
<p class="text-lg font-semibold mb-4">{{ $strings.HeaderRSSFeedIsOpen }}</p>
|
<p class="text-lg font-semibold mb-4">{{ $strings.HeaderRSSFeedIsOpen }}</p>
|
||||||
|
|
||||||
<div class="w-full relative">
|
<div class="w-full relative">
|
||||||
<ui-text-input v-model="currentFeed.feedUrl" readonly />
|
<ui-text-input :value="feedUrl" readonly />
|
||||||
|
|
||||||
<span class="material-symbols absolute right-2 bottom-2 p-0.5 text-base transition-transform duration-100 text-gray-300 hover:text-white transform hover:scale-125 cursor-pointer" @click="copyToClipboard(currentFeed.feedUrl)">content_copy</span>
|
<span class="material-symbols absolute right-2 bottom-2 p-0.5 text-base transition-transform duration-100 text-gray-300 hover:text-white transform hover:scale-125 cursor-pointer" @click="copyToClipboard(feedUrl)">content_copy</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div v-if="currentFeed.meta" class="mt-5">
|
<div v-if="currentFeed.meta" class="mt-5">
|
||||||
@@ -111,8 +111,11 @@ export default {
|
|||||||
userIsAdminOrUp() {
|
userIsAdminOrUp() {
|
||||||
return this.$store.getters['user/getIsAdminOrUp']
|
return this.$store.getters['user/getIsAdminOrUp']
|
||||||
},
|
},
|
||||||
|
feedUrl() {
|
||||||
|
return this.currentFeed ? `${window.origin}${this.$config.routerBasePath}${this.currentFeed.feedUrl}` : ''
|
||||||
|
},
|
||||||
demoFeedUrl() {
|
demoFeedUrl() {
|
||||||
return `${window.origin}/feed/${this.newFeedSlug}`
|
return `${window.origin}${this.$config.routerBasePath}/feed/${this.newFeedSlug}`
|
||||||
},
|
},
|
||||||
isHttp() {
|
isHttp() {
|
||||||
return window.origin.startsWith('http://')
|
return window.origin.startsWith('http://')
|
||||||
@@ -139,7 +142,7 @@ export default {
|
|||||||
slug: this.newFeedSlug,
|
slug: this.newFeedSlug,
|
||||||
metadataDetails: this.metadataDetails
|
metadataDetails: this.metadataDetails
|
||||||
}
|
}
|
||||||
if (this.$isDev) payload.serverAddress = `http://localhost:3333${this.$config.routerBasePath}`
|
if (this.$isDev) payload.serverAddress = process.env.serverUrl
|
||||||
|
|
||||||
console.log('Payload', payload)
|
console.log('Payload', payload)
|
||||||
this.$axios
|
this.$axios
|
||||||
|
|||||||
@@ -5,8 +5,8 @@
|
|||||||
<p class="text-lg font-semibold mb-4">{{ $strings.HeaderRSSFeedGeneral }}</p>
|
<p class="text-lg font-semibold mb-4">{{ $strings.HeaderRSSFeedGeneral }}</p>
|
||||||
|
|
||||||
<div class="w-full relative">
|
<div class="w-full relative">
|
||||||
<ui-text-input v-model="feed.feedUrl" readonly />
|
<ui-text-input :value="feedUrl" readonly />
|
||||||
<span class="material-symbols absolute right-2 bottom-2 p-0.5 text-base transition-transform duration-100 text-gray-300 hover:text-white transform hover:scale-125 cursor-pointer" @click="copyToClipboard(feed.feedUrl)">content_copy</span>
|
<span class="material-symbols absolute right-2 bottom-2 p-0.5 text-base transition-transform duration-100 text-gray-300 hover:text-white transform hover:scale-125 cursor-pointer" @click="copyToClipboard(feedUrl)">content_copy</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div v-if="feed.meta" class="mt-5">
|
<div v-if="feed.meta" class="mt-5">
|
||||||
@@ -70,6 +70,9 @@ export default {
|
|||||||
},
|
},
|
||||||
_feed() {
|
_feed() {
|
||||||
return this.feed || {}
|
return this.feed || {}
|
||||||
|
},
|
||||||
|
feedUrl() {
|
||||||
|
return this.feed ? `${window.origin}${this.$config.routerBasePath}${this.feed.feedUrl}` : ''
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
|
|||||||
@@ -37,7 +37,7 @@
|
|||||||
</ui-tooltip>
|
</ui-tooltip>
|
||||||
|
|
||||||
<ui-tooltip direction="top" :text="$strings.LabelViewPlayerSettings">
|
<ui-tooltip direction="top" :text="$strings.LabelViewPlayerSettings">
|
||||||
<button :aria-label="$strings.LabelViewPlayerSettings" class="outline-none text-gray-300 mx-1 lg:mx-2 hover:text-white" @mousedown.prevent @mouseup.prevent @click.stop="$emit('showPlayerSettings')">
|
<button :aria-label="$strings.LabelViewPlayerSettings" class="outline-none text-gray-300 mx-1 lg:mx-2 hover:text-white" @mousedown.prevent @mouseup.prevent @click.stop="showPlayerSettings">
|
||||||
<span class="material-symbols text-2xl sm:text-2.5xl">settings_slow_motion</span>
|
<span class="material-symbols text-2xl sm:text-2.5xl">settings_slow_motion</span>
|
||||||
</button>
|
</button>
|
||||||
</ui-tooltip>
|
</ui-tooltip>
|
||||||
@@ -64,6 +64,8 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<modals-chapters-modal v-model="showChaptersModal" :current-chapter="currentChapter" :playback-rate="playbackRate" :chapters="chapters" @select="selectChapter" />
|
<modals-chapters-modal v-model="showChaptersModal" :current-chapter="currentChapter" :playback-rate="playbackRate" :chapters="chapters" @select="selectChapter" />
|
||||||
|
|
||||||
|
<modals-player-settings-modal v-model="showPlayerSettingsModal" />
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
@@ -96,6 +98,7 @@ export default {
|
|||||||
audioEl: null,
|
audioEl: null,
|
||||||
seekLoading: false,
|
seekLoading: false,
|
||||||
showChaptersModal: false,
|
showChaptersModal: false,
|
||||||
|
showPlayerSettingsModal: false,
|
||||||
currentTime: 0,
|
currentTime: 0,
|
||||||
duration: 0
|
duration: 0
|
||||||
}
|
}
|
||||||
@@ -315,6 +318,9 @@ export default {
|
|||||||
if (!this.chapters.length) return
|
if (!this.chapters.length) return
|
||||||
this.showChaptersModal = !this.showChaptersModal
|
this.showChaptersModal = !this.showChaptersModal
|
||||||
},
|
},
|
||||||
|
showPlayerSettings() {
|
||||||
|
this.showPlayerSettingsModal = !this.showPlayerSettingsModal
|
||||||
|
},
|
||||||
init() {
|
init() {
|
||||||
this.playbackRate = this.$store.getters['user/getUserSetting']('playbackRate') || 1
|
this.playbackRate = this.$store.getters['user/getUserSetting']('playbackRate') || 1
|
||||||
|
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
<template>
|
<template>
|
||||||
<div>
|
<div>
|
||||||
<div v-if="processing" class="max-w-[800px] h-80 md:h-[800px] mx-auto flex items-center justify-center">
|
<div v-if="processing" role="img" :aria-label="$strings.MessageLoading" class="max-w-[800px] h-80 md:h-[800px] mx-auto flex items-center justify-center">
|
||||||
<widgets-loading-spinner />
|
<widgets-loading-spinner />
|
||||||
</div>
|
</div>
|
||||||
<img v-else-if="dataUrl" :src="dataUrl" class="mx-auto" />
|
<img v-else-if="dataUrl" :src="dataUrl" class="mx-auto" :aria-label="$getString('LabelPersonalYearReview', [variant + 1])" />
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
|||||||
@@ -7,7 +7,7 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="flex items-center">
|
<div class="flex items-center">
|
||||||
<p class="hidden md:block text-xl font-semibold">{{ $getString('HeaderYearReview', [yearInReviewYear]) }}</p>
|
<h1 class="hidden md:block text-xl font-semibold">{{ $getString('HeaderYearReview', [yearInReviewYear]) }}</h1>
|
||||||
<div class="hidden md:block flex-grow" />
|
<div class="hidden md:block flex-grow" />
|
||||||
<ui-btn class="w-full md:w-auto" @click.stop="clickShowYearInReview">{{ showYearInReview ? $strings.LabelYearReviewHide : $strings.LabelYearReviewShow }}</ui-btn>
|
<ui-btn class="w-full md:w-auto" @click.stop="clickShowYearInReview">{{ showYearInReview ? $strings.LabelYearReviewHide : $strings.LabelYearReviewShow }}</ui-btn>
|
||||||
</div>
|
</div>
|
||||||
@@ -16,17 +16,22 @@
|
|||||||
<div v-if="showYearInReview">
|
<div v-if="showYearInReview">
|
||||||
<div class="w-full h-px bg-slate-200/10 my-4" />
|
<div class="w-full h-px bg-slate-200/10 my-4" />
|
||||||
|
|
||||||
<div class="flex items-center justify-center mb-2 max-w-[800px] mx-auto">
|
<div v-if="availableYears.length > 1" class="mb-2 py-2 max-w-[800px] mx-auto">
|
||||||
|
<!-- year selector -->
|
||||||
|
<ui-dropdown v-model="yearInReviewYear" small :items="availableYears" :disabled="processingYearInReview" class="max-w-24" @input="yearInReviewYearChanged" />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div role="toolbar" class="flex items-center justify-center mb-2 max-w-[800px] mx-auto">
|
||||||
<!-- previous button -->
|
<!-- previous button -->
|
||||||
<ui-btn small :disabled="!yearInReviewVariant || processingYearInReview" class="inline-flex items-center font-semibold" @click="yearInReviewVariant--">
|
<ui-btn small :disabled="!yearInReviewVariant || processingYearInReview" :aria-label="$strings.ButtonPrevious" class="inline-flex items-center font-semibold" @click="yearInReviewVariant--">
|
||||||
<span class="material-symbols text-lg sm:pr-1 py-px sm:py-0">chevron_left</span>
|
<span class="material-symbols text-lg sm:pr-1 py-px sm:py-0">chevron_left</span>
|
||||||
<span class="hidden sm:inline-block pr-2">{{ $strings.ButtonPrevious }}</span>
|
<span class="hidden sm:inline-block pr-2">{{ $strings.ButtonPrevious }}</span>
|
||||||
</ui-btn>
|
</ui-btn>
|
||||||
<!-- share button -->
|
<!-- share button -->
|
||||||
<ui-btn v-if="showShareButton" small :disabled="processingYearInReview" class="inline-flex sm:hidden items-center font-semibold ml-1 sm:ml-2" @click="shareYearInReview">{{ $strings.ButtonShare }} </ui-btn>
|
<ui-btn v-if="showShareButton" small :disabled="processingYearInReview" class="inline-flex items-center font-semibold ml-1 sm:ml-2" @click="shareYearInReview">{{ $strings.ButtonShare }} </ui-btn>
|
||||||
|
|
||||||
<div class="flex-grow" />
|
<div class="flex-grow" />
|
||||||
<p class="hidden sm:block text-lg font-semibold">{{ $getString('LabelPersonalYearReview', [yearInReviewVariant + 1]) }}</p>
|
<h2 class="hidden sm:block text-lg font-semibold">{{ $getString('LabelPersonalYearReview', [yearInReviewVariant + 1]) }}</h2>
|
||||||
<p class="block sm:hidden text-lg font-semibold">{{ yearInReviewVariant + 1 }}</p>
|
<p class="block sm:hidden text-lg font-semibold">{{ yearInReviewVariant + 1 }}</p>
|
||||||
<div class="flex-grow" />
|
<div class="flex-grow" />
|
||||||
|
|
||||||
@@ -36,7 +41,7 @@
|
|||||||
<span class="material-symbols sm:!hidden text-lg py-px">refresh</span>
|
<span class="material-symbols sm:!hidden text-lg py-px">refresh</span>
|
||||||
</ui-btn>
|
</ui-btn>
|
||||||
<!-- next button -->
|
<!-- next button -->
|
||||||
<ui-btn small :disabled="yearInReviewVariant >= 2 || processingYearInReview" class="inline-flex items-center font-semibold" @click="yearInReviewVariant++">
|
<ui-btn small :disabled="yearInReviewVariant >= 2 || processingYearInReview" :aria-label="$strings.ButtonNext" class="inline-flex items-center font-semibold" @click="yearInReviewVariant++">
|
||||||
<span class="hidden sm:inline-block pl-2">{{ $strings.ButtonNext }}</span>
|
<span class="hidden sm:inline-block pl-2">{{ $strings.ButtonNext }}</span>
|
||||||
<span class="material-symbols text-lg sm:pl-1 py-px sm:py-0">chevron_right</span>
|
<span class="material-symbols text-lg sm:pl-1 py-px sm:py-0">chevron_right</span>
|
||||||
</ui-btn>
|
</ui-btn>
|
||||||
@@ -46,23 +51,23 @@
|
|||||||
<!-- your year in review short -->
|
<!-- your year in review short -->
|
||||||
<div class="w-full max-w-[800px] mx-auto my-4">
|
<div class="w-full max-w-[800px] mx-auto my-4">
|
||||||
<!-- share button -->
|
<!-- share button -->
|
||||||
<ui-btn v-if="showShareButton" small :disabled="processingYearInReviewShort" class="inline-flex sm:hidden items-center font-semibold mb-1" @click="shareYearInReviewShort">{{ $strings.ButtonShare }}</ui-btn>
|
<ui-btn v-if="showShareButton" small :disabled="processingYearInReviewShort" class="inline-flex items-center font-semibold mb-1" @click="shareYearInReviewShort">{{ $strings.ButtonShare }}</ui-btn>
|
||||||
<stats-year-in-review-short ref="yearInReviewShort" :year="yearInReviewYear" :processing.sync="processingYearInReviewShort" />
|
<stats-year-in-review-short ref="yearInReviewShort" :year="yearInReviewYear" :processing.sync="processingYearInReviewShort" />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- your server in review -->
|
<!-- your server in review -->
|
||||||
<div v-if="isAdminOrUp" class="w-full max-w-[800px] mx-auto mb-2 mt-4 border-t pt-4 border-white/10">
|
<div v-if="isAdminOrUp" role="toolbar" class="w-full max-w-[800px] mx-auto mb-2 mt-4 border-t pt-4 border-white/10">
|
||||||
<div class="flex items-center justify-center mb-2">
|
<div class="flex items-center justify-center mb-2">
|
||||||
<!-- previous button -->
|
<!-- previous button -->
|
||||||
<ui-btn small :disabled="!yearInReviewServerVariant || processingYearInReviewServer" class="inline-flex items-center font-semibold" @click="yearInReviewServerVariant--">
|
<ui-btn small :disabled="!yearInReviewServerVariant || processingYearInReviewServer" :aria-label="$strings.ButtonPrevious" class="inline-flex items-center font-semibold" @click="yearInReviewServerVariant--">
|
||||||
<span class="material-symbols text-lg sm:pr-1 py-px sm:py-0">chevron_left</span>
|
<span class="material-symbols text-lg sm:pr-1 py-px sm:py-0">chevron_left</span>
|
||||||
<span class="hidden sm:inline-block pr-2">{{ $strings.ButtonPrevious }}</span>
|
<span class="hidden sm:inline-block pr-2">{{ $strings.ButtonPrevious }}</span>
|
||||||
</ui-btn>
|
</ui-btn>
|
||||||
<!-- share button -->
|
<!-- share button -->
|
||||||
<ui-btn v-if="showShareButton" small :disabled="processingYearInReviewServer" class="inline-flex sm:hidden items-center font-semibold ml-1 sm:ml-2" @click="shareYearInReviewServer">{{ $strings.ButtonShare }} </ui-btn>
|
<ui-btn v-if="showShareButton" small :disabled="processingYearInReviewServer" class="inline-flex items-center font-semibold ml-1 sm:ml-2" @click="shareYearInReviewServer">{{ $strings.ButtonShare }} </ui-btn>
|
||||||
|
|
||||||
<div class="flex-grow" />
|
<div class="flex-grow" />
|
||||||
<p class="hidden sm:block text-lg font-semibold">{{ $getString('LabelServerYearReview', [yearInReviewServerVariant + 1]) }}</p>
|
<h2 class="hidden sm:block text-lg font-semibold">{{ $getString('LabelServerYearReview', [yearInReviewServerVariant + 1]) }}</h2>
|
||||||
<p class="block sm:hidden text-lg font-semibold">{{ yearInReviewServerVariant + 1 }}</p>
|
<p class="block sm:hidden text-lg font-semibold">{{ yearInReviewServerVariant + 1 }}</p>
|
||||||
<div class="flex-grow" />
|
<div class="flex-grow" />
|
||||||
|
|
||||||
@@ -72,7 +77,7 @@
|
|||||||
<span class="material-symbols sm:!hidden text-lg py-px">refresh</span>
|
<span class="material-symbols sm:!hidden text-lg py-px">refresh</span>
|
||||||
</ui-btn>
|
</ui-btn>
|
||||||
<!-- next button -->
|
<!-- next button -->
|
||||||
<ui-btn small :disabled="yearInReviewServerVariant >= 2 || processingYearInReviewServer" class="inline-flex items-center font-semibold" @click="yearInReviewServerVariant++">
|
<ui-btn small :disabled="yearInReviewServerVariant >= 2 || processingYearInReviewServer" :aria-label="$strings.ButtonNext" class="inline-flex items-center font-semibold" @click="yearInReviewServerVariant++">
|
||||||
<span class="hidden sm:inline-block pl-2">{{ $strings.ButtonNext }}</span>
|
<span class="hidden sm:inline-block pl-2">{{ $strings.ButtonNext }}</span>
|
||||||
<span class="material-symbols text-lg sm:pl-1 py-px sm:py-0">chevron_right</span>
|
<span class="material-symbols text-lg sm:pl-1 py-px sm:py-0">chevron_right</span>
|
||||||
</ui-btn>
|
</ui-btn>
|
||||||
@@ -88,6 +93,7 @@ export default {
|
|||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
showYearInReview: false,
|
showYearInReview: false,
|
||||||
|
availableYears: [],
|
||||||
yearInReviewYear: 0,
|
yearInReviewYear: 0,
|
||||||
yearInReviewVariant: 0,
|
yearInReviewVariant: 0,
|
||||||
yearInReviewServerVariant: 0,
|
yearInReviewServerVariant: 0,
|
||||||
@@ -100,6 +106,9 @@ export default {
|
|||||||
computed: {
|
computed: {
|
||||||
isAdminOrUp() {
|
isAdminOrUp() {
|
||||||
return this.$store.getters['user/getIsAdminOrUp']
|
return this.$store.getters['user/getIsAdminOrUp']
|
||||||
|
},
|
||||||
|
user() {
|
||||||
|
return this.$store.state.user.user
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
@@ -112,25 +121,57 @@ export default {
|
|||||||
shareYearInReviewShort() {
|
shareYearInReviewShort() {
|
||||||
this.$refs.yearInReviewShort.share()
|
this.$refs.yearInReviewShort.share()
|
||||||
},
|
},
|
||||||
|
yearInReviewYearChanged() {
|
||||||
|
this.$nextTick(() => {
|
||||||
|
this.refreshYearInReview()
|
||||||
|
this.refreshYearInReviewServer()
|
||||||
|
})
|
||||||
|
},
|
||||||
refreshYearInReviewServer() {
|
refreshYearInReviewServer() {
|
||||||
|
if (this.$refs.yearInReviewServer != null) {
|
||||||
this.$refs.yearInReviewServer.refresh()
|
this.$refs.yearInReviewServer.refresh()
|
||||||
|
}
|
||||||
},
|
},
|
||||||
refreshYearInReview() {
|
refreshYearInReview() {
|
||||||
|
if (this.$refs.yearInReview != null && this.$refs.yearInReviewShort != null) {
|
||||||
this.$refs.yearInReview.refresh()
|
this.$refs.yearInReview.refresh()
|
||||||
this.$refs.yearInReviewShort.refresh()
|
this.$refs.yearInReviewShort.refresh()
|
||||||
|
}
|
||||||
},
|
},
|
||||||
clickShowYearInReview() {
|
clickShowYearInReview() {
|
||||||
this.showYearInReview = !this.showYearInReview
|
this.showYearInReview = !this.showYearInReview
|
||||||
|
},
|
||||||
|
getAvailableYears() {
|
||||||
|
if (this.user) {
|
||||||
|
const oldestDate = this.user.createdAt
|
||||||
|
if (oldestDate) {
|
||||||
|
const date = new Date(oldestDate)
|
||||||
|
const oldestYear = date.getFullYear()
|
||||||
|
const currentYear = new Date().getFullYear()
|
||||||
|
|
||||||
|
const years = []
|
||||||
|
for (let year = currentYear; year >= oldestYear; year--) {
|
||||||
|
years.push({ value: year, text: year.toString() })
|
||||||
|
}
|
||||||
|
|
||||||
|
return years
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Fallback on error
|
||||||
|
return [{ value: this.yearInReviewYear, text: this.yearInReviewYear.toString() }]
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
beforeMount() {
|
beforeMount() {
|
||||||
this.yearInReviewYear = new Date().getFullYear()
|
this.yearInReviewYear = new Date().getFullYear()
|
||||||
|
|
||||||
// When not December show previous year
|
// When not December show previous year
|
||||||
if (new Date().getMonth() < 11) {
|
if (new Date().getMonth() < 11) {
|
||||||
this.yearInReviewYear--
|
this.yearInReviewYear--
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
mounted() {
|
mounted() {
|
||||||
|
this.availableYears = this.getAvailableYears()
|
||||||
|
|
||||||
if (typeof navigator.share !== 'undefined' && navigator.share) {
|
if (typeof navigator.share !== 'undefined' && navigator.share) {
|
||||||
this.showShareButton = true
|
this.showShareButton = true
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
<template>
|
<template>
|
||||||
<div>
|
<div>
|
||||||
<div v-if="processing" class="max-w-[800px] h-80 md:h-[800px] mx-auto flex items-center justify-center">
|
<div v-if="processing" role="img" :aria-label="$strings.MessageLoading" class="max-w-[800px] h-80 md:h-[800px] mx-auto flex items-center justify-center">
|
||||||
<widgets-loading-spinner />
|
<widgets-loading-spinner />
|
||||||
</div>
|
</div>
|
||||||
<img v-else-if="dataUrl" :src="dataUrl" class="mx-auto" />
|
<img v-else-if="dataUrl" :src="dataUrl" class="mx-auto" :aria-label="$getString('LabelServerYearReview', [variant + 1])" />
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
|||||||
@@ -120,6 +120,7 @@ export default {
|
|||||||
this.users = res.users.sort((a, b) => {
|
this.users = res.users.sort((a, b) => {
|
||||||
return a.createdAt - b.createdAt
|
return a.createdAt - b.createdAt
|
||||||
})
|
})
|
||||||
|
this.$emit('numUsers', this.users.length)
|
||||||
})
|
})
|
||||||
.catch((error) => {
|
.catch((error) => {
|
||||||
console.error('Failed', error)
|
console.error('Failed', error)
|
||||||
|
|||||||
@@ -218,7 +218,6 @@ export default {
|
|||||||
this.$toast.success(this.$strings.ToastPlaylistRemoveSuccess)
|
this.$toast.success(this.$strings.ToastPlaylistRemoveSuccess)
|
||||||
} else {
|
} else {
|
||||||
console.log(`Item removed from playlist`, updatedPlaylist)
|
console.log(`Item removed from playlist`, updatedPlaylist)
|
||||||
this.$toast.success(this.$strings.ToastPlaylistUpdateSuccess)
|
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.catch((error) => {
|
.catch((error) => {
|
||||||
|
|||||||
@@ -12,10 +12,10 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="h-8 flex items-center">
|
<div class="h-8 flex items-center">
|
||||||
<div class="w-full inline-flex justify-between max-w-xl">
|
<div class="w-full inline-flex justify-between max-w-xl">
|
||||||
<p v-if="episode?.season" class="text-sm text-gray-300">Season #{{ episode.season }}</p>
|
<p v-if="episode?.season" class="text-sm text-gray-300">{{ $getString('LabelSeasonNumber', [episode.season]) }}</p>
|
||||||
<p v-if="episode?.episode" class="text-sm text-gray-300">Episode #{{ episode.episode }}</p>
|
<p v-if="episode?.episode" class="text-sm text-gray-300">{{ $getString('LabelEpisodeNumber', [episode.episode]) }}</p>
|
||||||
<p v-if="episode?.chapters?.length" class="text-sm text-gray-300">{{ episode.chapters.length }} Chapters</p>
|
<p v-if="episode?.chapters?.length" class="text-sm text-gray-300">{{ $getString('LabelChapterCount', [episode.chapters.length]) }}</p>
|
||||||
<p v-if="publishedAt" class="text-sm text-gray-300">Published {{ $formatDate(publishedAt, dateFormat) }}</p>
|
<p v-if="publishedAt" class="text-sm text-gray-300">{{ $getString('LabelPublishedDate', [$formatDate(publishedAt, dateFormat)]) }}</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -132,13 +132,13 @@ export default {
|
|||||||
return this.store.state.streamIsPlaying && this.isStreaming
|
return this.store.state.streamIsPlaying && this.isStreaming
|
||||||
},
|
},
|
||||||
timeRemaining() {
|
timeRemaining() {
|
||||||
if (this.streamIsPlaying) return 'Playing'
|
if (this.streamIsPlaying) return this.$strings.ButtonPlaying
|
||||||
if (!this.itemProgress) return this.$elapsedPretty(this.episode?.duration || 0)
|
if (!this.itemProgress) return this.$elapsedPretty(this.episode?.duration || 0)
|
||||||
if (this.userIsFinished) return 'Finished'
|
if (this.userIsFinished) return this.$strings.LabelFinished
|
||||||
|
|
||||||
const duration = this.itemProgress.duration || this.episode?.duration || 0
|
const duration = this.itemProgress.duration || this.episode?.duration || 0
|
||||||
const remaining = Math.floor(duration - this.itemProgress.currentTime)
|
const remaining = Math.floor(duration - this.itemProgress.currentTime)
|
||||||
return `${this.$elapsedPretty(remaining)} left`
|
return this.$getString('LabelTimeLeft', [this.$elapsedPretty(remaining)])
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
@@ -182,7 +182,7 @@ export default {
|
|||||||
toggleFinished(confirmed = false) {
|
toggleFinished(confirmed = false) {
|
||||||
if (!this.userIsFinished && this.itemProgressPercent > 0 && !confirmed) {
|
if (!this.userIsFinished && this.itemProgressPercent > 0 && !confirmed) {
|
||||||
const payload = {
|
const payload = {
|
||||||
message: `Are you sure you want to mark "${this.episodeTitle}" as finished?`,
|
message: this.$getString('MessageConfirmMarkItemFinished', [this.episodeTitle]),
|
||||||
callback: (confirmed) => {
|
callback: (confirmed) => {
|
||||||
if (confirmed) {
|
if (confirmed) {
|
||||||
this.toggleFinished(true)
|
this.toggleFinished(true)
|
||||||
|
|||||||
@@ -25,7 +25,6 @@
|
|||||||
</template>
|
</template>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<!-- <p v-if="!episodes.length" class="py-4 text-center text-lg">{{ $strings.MessageNoEpisodes }}</p> -->
|
|
||||||
<div v-if="episodes.length" class="w-full py-3 mx-auto flex">
|
<div v-if="episodes.length" class="w-full py-3 mx-auto flex">
|
||||||
<form @submit.prevent="submit" class="flex flex-grow">
|
<form @submit.prevent="submit" class="flex flex-grow">
|
||||||
<ui-text-input v-model="search" @input="inputUpdate" type="search" :placeholder="$strings.PlaceholderSearchEpisode" class="flex-grow mr-2 text-sm md:text-base" />
|
<ui-text-input v-model="search" @input="inputUpdate" type="search" :placeholder="$strings.PlaceholderSearchEpisode" class="flex-grow mr-2 text-sm md:text-base" />
|
||||||
@@ -96,7 +95,7 @@ export default {
|
|||||||
const menuItems = []
|
const menuItems = []
|
||||||
if (this.userIsAdminOrUp) {
|
if (this.userIsAdminOrUp) {
|
||||||
menuItems.push({
|
menuItems.push({
|
||||||
text: 'Quick match all episodes',
|
text: this.$strings.MessageQuickMatchAllEpisodes,
|
||||||
action: 'quick-match-episodes'
|
action: 'quick-match-episodes'
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@@ -262,21 +261,21 @@ export default {
|
|||||||
this.processing = true
|
this.processing = true
|
||||||
|
|
||||||
const payload = {
|
const payload = {
|
||||||
message: 'Quick matching episodes will overwrite details if a match is found. Only unmatched episodes will be updated. Are you sure?',
|
message: this.$strings.MessageConfirmQuickMatchEpisodes,
|
||||||
callback: (confirmed) => {
|
callback: (confirmed) => {
|
||||||
if (confirmed) {
|
if (confirmed) {
|
||||||
this.$axios
|
this.$axios
|
||||||
.$post(`/api/podcasts/${this.libraryItem.id}/match-episodes?override=1`)
|
.$post(`/api/podcasts/${this.libraryItem.id}/match-episodes?override=1`)
|
||||||
.then((data) => {
|
.then((data) => {
|
||||||
if (data.numEpisodesUpdated) {
|
if (data.numEpisodesUpdated) {
|
||||||
this.$toast.success(`${data.numEpisodesUpdated} episodes updated`)
|
this.$toast.success(this.$getString('ToastEpisodeUpdateSuccess', [data.numEpisodesUpdated]))
|
||||||
} else {
|
} else {
|
||||||
this.$toast.info(this.$strings.ToastNoUpdatesNecessary)
|
this.$toast.info(this.$strings.ToastNoUpdatesNecessary)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.catch((error) => {
|
.catch((error) => {
|
||||||
console.error('Failed to request match episodes', error)
|
console.error('Failed to request match episodes', error)
|
||||||
this.$toast.error('Failed to match episodes')
|
this.$toast.error(this.$strings.ToastFailedToMatch)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
this.processing = false
|
this.processing = false
|
||||||
@@ -515,6 +514,10 @@ export default {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
filterSortChanged() {
|
filterSortChanged() {
|
||||||
|
// Save filterKey and sortKey to local storage
|
||||||
|
localStorage.setItem('podcastEpisodesFilter', this.filterKey)
|
||||||
|
localStorage.setItem('podcastEpisodesSortBy', this.sortKey + (this.sortDesc ? '-desc' : ''))
|
||||||
|
|
||||||
this.init()
|
this.init()
|
||||||
},
|
},
|
||||||
refresh() {
|
refresh() {
|
||||||
@@ -537,6 +540,11 @@ export default {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
mounted() {
|
mounted() {
|
||||||
|
this.filterKey = localStorage.getItem('podcastEpisodesFilter') || 'incomplete'
|
||||||
|
const sortBy = localStorage.getItem('podcastEpisodesSortBy') || 'publishedAt-desc'
|
||||||
|
this.sortKey = sortBy.split('-')[0]
|
||||||
|
this.sortDesc = sortBy.split('-')[1] === 'desc'
|
||||||
|
|
||||||
this.episodesCopy = this.episodes.map((ep) => ({ ...ep }))
|
this.episodesCopy = this.episodes.map((ep) => ({ ...ep }))
|
||||||
this.initListeners()
|
this.initListeners()
|
||||||
this.init()
|
this.init()
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="relative h-9 w-9" v-click-outside="clickOutsideObj">
|
<div class="relative h-9 w-9" v-click-outside="clickOutsideObj">
|
||||||
<slot :disabled="disabled" :showMenu="showMenu" :clickShowMenu="clickShowMenu" :processing="processing">
|
<slot :disabled="disabled" :showMenu="showMenu" :clickShowMenu="clickShowMenu" :processing="processing">
|
||||||
<button v-if="!processing" type="button" :disabled="disabled" class="relative h-full w-full flex items-center justify-center shadow-sm pl-3 pr-3 text-left focus:outline-none cursor-pointer text-gray-100 hover:text-gray-200 rounded-full hover:bg-white/5" aria-haspopup="listbox" :aria-expanded="showMenu" @click.stop.prevent="clickShowMenu">
|
<button v-if="!processing" type="button" :disabled="disabled" class="relative h-full w-full flex items-center justify-center shadow-sm pl-3 pr-3 text-left focus:outline-none cursor-pointer text-gray-100 hover:text-gray-200 rounded-full hover:bg-white/5" :aria-label="$strings.LabelMore" aria-haspopup="menu" :aria-expanded="showMenu" @click.stop.prevent="clickShowMenu">
|
||||||
<span class="material-symbols text-2xl" :class="iconClass"></span>
|
<span class="material-symbols text-2xl" :class="iconClass"></span>
|
||||||
</button>
|
</button>
|
||||||
<div v-else class="h-full w-full flex items-center justify-center">
|
<div v-else class="h-full w-full flex items-center justify-center">
|
||||||
@@ -10,12 +10,12 @@
|
|||||||
</slot>
|
</slot>
|
||||||
|
|
||||||
<transition name="menu">
|
<transition name="menu">
|
||||||
<div v-show="showMenu" ref="menuWrapper" class="absolute right-0 mt-1 z-10 bg-bg border border-black-200 shadow-lg rounded-md py-1 focus:outline-none sm:text-sm" :style="{ width: menuWidth + 'px' }">
|
<div v-show="showMenu" ref="menuWrapper" role="menu" class="absolute right-0 mt-1 z-10 bg-bg border border-black-200 shadow-lg rounded-md py-1 focus:outline-none sm:text-sm" :style="{ width: menuWidth + 'px' }">
|
||||||
<template v-for="(item, index) in items">
|
<template v-for="(item, index) in items">
|
||||||
<template v-if="item.subitems">
|
<template v-if="item.subitems">
|
||||||
<div :key="index" class="flex items-center px-2 py-1.5 hover:bg-white/5 text-white text-xs cursor-default" :class="{ 'bg-white/5': mouseoverItemIndex == index }" @mouseover="mouseoverItem(index)" @mouseleave="mouseleaveItem(index)" @click.stop>
|
<button :key="index" role="menuitem" aria-haspopup="menu" class="flex items-center px-2 py-1.5 hover:bg-white/5 text-white text-xs cursor-default w-full" :class="{ 'bg-white/5': mouseoverItemIndex == index }" @mouseover="mouseoverItem(index)" @mouseleave="mouseleaveItem(index)" @click.stop>
|
||||||
<p>{{ item.text }}</p>
|
<p>{{ item.text }}</p>
|
||||||
</div>
|
</button>
|
||||||
<div
|
<div
|
||||||
v-if="mouseoverItemIndex === index"
|
v-if="mouseoverItemIndex === index"
|
||||||
:key="`subitems-${index}`"
|
:key="`subitems-${index}`"
|
||||||
@@ -25,14 +25,14 @@
|
|||||||
:class="openSubMenuLeft ? 'rounded-l-md' : 'rounded-r-md'"
|
:class="openSubMenuLeft ? 'rounded-l-md' : 'rounded-r-md'"
|
||||||
:style="{ left: submenuLeftPos + 'px', top: index * 28 + 'px', width: submenuWidth + 'px' }"
|
:style="{ left: submenuLeftPos + 'px', top: index * 28 + 'px', width: submenuWidth + 'px' }"
|
||||||
>
|
>
|
||||||
<div v-for="(subitem, subitemindex) in item.subitems" :key="`subitem-${subitemindex}`" class="flex items-center px-2 py-1.5 hover:bg-white/5 text-white text-xs cursor-pointer" @click.stop="clickAction(subitem.action, subitem.data)">
|
<button v-for="(subitem, subitemindex) in item.subitems" :key="`subitem-${subitemindex}`" role="menuitem" class="flex items-center px-2 py-1.5 hover:bg-white/5 text-white text-xs cursor-pointer w-full" @click.stop="clickAction(subitem.action, subitem.data)">
|
||||||
<p>{{ subitem.text }}</p>
|
<p>{{ subitem.text }}</p>
|
||||||
</div>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
<div v-else :key="index" class="flex items-center px-2 py-1.5 hover:bg-white/5 text-white text-xs cursor-pointer" @click.stop="clickAction(item.action)">
|
<button v-else :key="index" role="menuitem" class="flex items-center px-2 py-1.5 hover:bg-white/5 text-white text-xs cursor-pointer w-full" @click.stop="clickAction(item.action)">
|
||||||
<p class="text-left">{{ item.text }}</p>
|
<p class="text-left">{{ item.text }}</p>
|
||||||
</div>
|
</button>
|
||||||
</template>
|
</template>
|
||||||
</div>
|
</div>
|
||||||
</transition>
|
</transition>
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="relative w-full" v-click-outside="clickOutsideObj">
|
<div class="relative w-full" v-click-outside="clickOutsideObj">
|
||||||
<p v-if="label" class="text-sm font-semibold px-1" :class="disabled ? 'text-gray-300' : ''">{{ label }}</p>
|
<p v-if="label" class="text-sm font-semibold px-1" :class="disabled ? 'text-gray-300' : ''">{{ label }}</p>
|
||||||
<button type="button" :aria-label="longLabel" :disabled="disabled" class="relative w-full border rounded shadow-sm pl-3 pr-8 py-2 text-left sm:text-sm" :class="buttonClass" aria-haspopup="listbox" aria-expanded="true" @click.stop.prevent="clickShowMenu">
|
<button type="button" :aria-label="longLabel" :disabled="disabled" class="relative w-full border rounded shadow-sm pl-3 pr-8 py-2 text-left sm:text-sm" :class="buttonClass" aria-haspopup="menu" :aria-expanded="showMenu" @click.stop.prevent="clickShowMenu">
|
||||||
<span class="flex items-center">
|
<span class="flex items-center">
|
||||||
<span class="block truncate font-sans" :class="{ 'font-semibold': selectedSubtext, 'text-sm': small }">{{ selectedText }}</span>
|
<span class="block truncate font-sans" :class="{ 'font-semibold': selectedSubtext, 'text-sm': small }">{{ selectedText }}</span>
|
||||||
<span v-if="selectedSubtext">: </span>
|
<span v-if="selectedSubtext">: </span>
|
||||||
@@ -13,9 +13,9 @@
|
|||||||
</button>
|
</button>
|
||||||
|
|
||||||
<transition name="menu">
|
<transition name="menu">
|
||||||
<ul v-show="showMenu" class="absolute z-10 -mt-px w-full bg-primary border border-black-200 shadow-lg rounded-md py-1 ring-1 ring-black ring-opacity-5 overflow-auto sm:text-sm" tabindex="-1" role="listbox" :style="{ maxHeight: menuMaxHeight }">
|
<ul v-show="showMenu" class="absolute z-10 -mt-px w-full bg-primary border border-black-200 shadow-lg rounded-md py-1 ring-1 ring-black ring-opacity-5 overflow-auto sm:text-sm" tabindex="-1" role="menu" :style="{ maxHeight: menuMaxHeight }">
|
||||||
<template v-for="item in itemsToShow">
|
<template v-for="item in itemsToShow">
|
||||||
<li :key="item.value" class="text-gray-100 relative py-2 cursor-pointer hover:bg-black-400" :id="'listbox-option-' + item.value" role="option" tabindex="0" @keyup.enter="clickedOption(item.value)" @click="clickedOption(item.value)">
|
<li :key="item.value" class="text-gray-100 relative py-2 cursor-pointer hover:bg-black-400" role="menuitem" tabindex="0" @keyup.enter="clickedOption(item.value)" @click="clickedOption(item.value)">
|
||||||
<div class="flex items-center">
|
<div class="flex items-center">
|
||||||
<span class="ml-3 block truncate font-sans text-sm" :class="{ 'font-semibold': item.subtext }">{{ item.text }}</span>
|
<span class="ml-3 block truncate font-sans text-sm" :class="{ 'font-semibold': item.subtext }">{{ item.text }}</span>
|
||||||
<span v-if="item.subtext">: </span>
|
<span v-if="item.subtext">: </span>
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
<template>
|
<template>
|
||||||
<button class="icon-btn rounded-md flex items-center justify-center relative" @mousedown.prevent :disabled="disabled || loading" :class="className" @click="clickBtn">
|
<button :aria-label="ariaLabel" class="icon-btn rounded-md flex items-center justify-center relative" @mousedown.prevent :disabled="disabled || loading" :class="className" @click="clickBtn">
|
||||||
<div v-if="loading" class="text-white absolute top-0 left-0 w-full h-full flex items-center justify-center text-opacity-100">
|
<div v-if="loading" class="text-white absolute top-0 left-0 w-full h-full flex items-center justify-center text-opacity-100">
|
||||||
<svg class="animate-spin" style="width: 24px; height: 24px" viewBox="0 0 24 24">
|
<svg class="animate-spin" style="width: 24px; height: 24px" viewBox="0 0 24 24">
|
||||||
<path fill="currentColor" d="M12,4V2A10,10 0 0,0 2,12H4A8,8 0 0,1 12,4Z" />
|
<path fill="currentColor" d="M12,4V2A10,10 0 0,0 2,12H4A8,8 0 0,1 12,4Z" />
|
||||||
@@ -28,7 +28,8 @@ export default {
|
|||||||
size: {
|
size: {
|
||||||
type: Number,
|
type: Number,
|
||||||
default: 9
|
default: 9
|
||||||
}
|
},
|
||||||
|
ariaLabel: String
|
||||||
},
|
},
|
||||||
data() {
|
data() {
|
||||||
return {}
|
return {}
|
||||||
|
|||||||
@@ -4,7 +4,7 @@
|
|||||||
type="button"
|
type="button"
|
||||||
:disabled="disabled"
|
:disabled="disabled"
|
||||||
class="w-10 sm:w-full relative h-full border border-white border-opacity-10 hover:border-opacity-20 rounded shadow-sm px-2 text-left text-sm cursor-pointer bg-black bg-opacity-20 text-gray-400 hover:text-gray-200"
|
class="w-10 sm:w-full relative h-full border border-white border-opacity-10 hover:border-opacity-20 rounded shadow-sm px-2 text-left text-sm cursor-pointer bg-black bg-opacity-20 text-gray-400 hover:text-gray-200"
|
||||||
aria-haspopup="listbox"
|
aria-haspopup="menu"
|
||||||
:aria-expanded="showMenu"
|
:aria-expanded="showMenu"
|
||||||
:aria-label="$strings.ButtonLibrary + ': ' + currentLibrary.name"
|
:aria-label="$strings.ButtonLibrary + ': ' + currentLibrary.name"
|
||||||
@click.stop.prevent="clickShowMenu"
|
@click.stop.prevent="clickShowMenu"
|
||||||
@@ -16,9 +16,9 @@
|
|||||||
</button>
|
</button>
|
||||||
|
|
||||||
<transition name="menu">
|
<transition name="menu">
|
||||||
<ul v-show="showMenu" class="absolute z-10 -mt-px w-full min-w-48 bg-primary border border-black-200 shadow-lg rounded-b-md py-1 overflow-auto focus:outline-none sm:text-sm librariesDropdownMenu" tabindex="-1" role="listbox">
|
<ul v-show="showMenu" class="absolute z-10 -mt-px w-full min-w-48 bg-primary border border-black-200 shadow-lg rounded-b-md py-1 overflow-auto focus:outline-none sm:text-sm librariesDropdownMenu" tabindex="-1" role="menu">
|
||||||
<template v-for="library in librariesFiltered">
|
<template v-for="library in librariesFiltered">
|
||||||
<li :key="library.id" class="text-gray-400 hover:text-white relative py-2 cursor-pointer hover:bg-black-400" role="option" tabindex="0" @keydown.enter="selectLibrary(library)" @click="selectLibrary(library)">
|
<li :key="library.id" class="text-gray-400 hover:text-white relative py-2 cursor-pointer hover:bg-black-400" role="menuitem" tabindex="0" @keydown.enter="selectLibrary(library)" @click="selectLibrary(library)">
|
||||||
<div class="flex items-center px-2">
|
<div class="flex items-center px-2">
|
||||||
<ui-library-icon :icon="library.icon" class="mr-1.5" />
|
<ui-library-icon :icon="library.icon" class="mr-1.5" />
|
||||||
<span class="font-normal block truncate font-sans text-sm">{{ library.name }}</span>
|
<span class="font-normal block truncate font-sans text-sm">{{ library.name }}</span>
|
||||||
|
|||||||
@@ -1,17 +1,17 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="w-full">
|
<div class="w-full">
|
||||||
<p class="px-1 text-sm font-semibold" :class="disabled ? 'text-gray-400' : ''">{{ label }}</p>
|
<label :for="identifier" class="px-1 text-sm font-semibold" :class="disabled ? 'text-gray-400' : ''">{{ label }}</label>
|
||||||
<div ref="wrapper" class="relative">
|
<div ref="wrapper" class="relative">
|
||||||
<form @submit.prevent="submitForm">
|
<form @submit.prevent="submitForm">
|
||||||
<div ref="inputWrapper" style="min-height: 36px" class="flex-wrap relative w-full shadow-sm flex items-center border border-gray-600 rounded px-2 py-1" :class="wrapperClass" @click.stop.prevent="clickWrapper" @mouseup.stop.prevent @mousedown.prevent>
|
<div ref="inputWrapper" role="list" style="min-height: 36px" class="flex-wrap relative w-full shadow-sm flex items-center border border-gray-600 rounded px-2 py-1" :class="wrapperClass" @click.stop.prevent="clickWrapper" @mouseup.stop.prevent @mousedown.prevent>
|
||||||
<div v-for="item in selected" :key="item" class="rounded-full px-2 py-1 mx-0.5 my-0.5 text-xs bg-bg flex flex-nowrap break-all items-center relative">
|
<div v-for="item in selected" :key="item" role="listitem" class="rounded-full px-2 py-1 mx-0.5 my-0.5 text-xs bg-bg flex flex-nowrap break-all items-center relative">
|
||||||
<div v-if="!disabled" class="w-full h-full rounded-full absolute top-0 left-0 px-1 bg-bg bg-opacity-75 flex items-center justify-end opacity-0 hover:opacity-100">
|
<div v-if="!disabled" class="w-full h-full rounded-full absolute top-0 left-0 px-1 bg-bg bg-opacity-75 flex items-center justify-end opacity-0 hover:opacity-100" :class="{ 'opacity-100': inputFocused }">
|
||||||
<span v-if="showEdit" class="material-symbols text-white hover:text-warning cursor-pointer" style="font-size: 1.1rem" @click.stop="editItem(item)">edit</span>
|
<button v-if="showEdit" type="button" :aria-label="$strings.ButtonEdit" class="material-symbols text-white hover:text-warning cursor-pointer" style="font-size: 1.1rem" @click.stop="editItem(item)">edit</button>
|
||||||
<span class="material-symbols text-white hover:text-error cursor-pointer" style="font-size: 1.1rem" @click.stop="removeItem(item)">close</span>
|
<button type="button" :aria-label="$strings.ButtonRemove" class="material-symbols text-white hover:text-error focus:text-error cursor-pointer" style="font-size: 1.1rem" @click.stop="removeItem(item)" @keydown.enter.stop.prevent="removeItem(item)" @focus="setInputFocused(true)" @blur="setInputFocused(false)" tabindex="0">close</button>
|
||||||
</div>
|
</div>
|
||||||
{{ item }}
|
{{ item }}
|
||||||
</div>
|
</div>
|
||||||
<input v-show="!readonly" ref="input" v-model="textInput" :disabled="disabled" class="h-full bg-primary focus:outline-none px-1 w-6" @keydown="keydownInput" @focus="inputFocus" @blur="inputBlur" @paste="inputPaste" />
|
<input v-show="!readonly" v-model="textInput" ref="input" :id="identifier" :disabled="disabled" class="h-full bg-primary focus:outline-none px-1 w-6" @keydown="keydownInput" @focus="inputFocus" @blur="inputBlur" @paste="inputPaste" />
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
|
|
||||||
@@ -66,7 +66,8 @@ export default {
|
|||||||
typingTimeout: null,
|
typingTimeout: null,
|
||||||
isFocused: false,
|
isFocused: false,
|
||||||
menu: null,
|
menu: null,
|
||||||
filteredItems: null
|
filteredItems: null,
|
||||||
|
inputFocused: false
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
watch: {
|
watch: {
|
||||||
@@ -100,6 +101,9 @@ export default {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return this.filteredItems
|
return this.filteredItems
|
||||||
|
},
|
||||||
|
identifier() {
|
||||||
|
return Math.random().toString(36).substring(2)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
@@ -129,6 +133,9 @@ export default {
|
|||||||
}, 100)
|
}, 100)
|
||||||
this.setInputWidth()
|
this.setInputWidth()
|
||||||
},
|
},
|
||||||
|
setInputFocused(focused) {
|
||||||
|
this.inputFocused = focused
|
||||||
|
},
|
||||||
setInputWidth() {
|
setInputWidth() {
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
var value = this.$refs.input.value
|
var value = this.$refs.input.value
|
||||||
|
|||||||
@@ -1,20 +1,20 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="w-full">
|
<div class="w-full">
|
||||||
<p class="px-1 text-sm font-semibold" :class="disabled ? 'text-gray-400' : ''">{{ label }}</p>
|
<label :for="identifier" class="px-1 text-sm font-semibold" :class="disabled ? 'text-gray-400' : ''">{{ label }}</label>
|
||||||
<div ref="wrapper" class="relative">
|
<div ref="wrapper" class="relative">
|
||||||
<form @submit.prevent="submitForm">
|
<form @submit.prevent="submitForm">
|
||||||
<div ref="inputWrapper" style="min-height: 36px" class="flex-wrap relative w-full shadow-sm flex items-center border border-gray-600 rounded px-2 py-0.5" :class="wrapperClass" @click.stop.prevent="clickWrapper" @mouseup.stop.prevent @mousedown.prevent>
|
<div ref="inputWrapper" role="list" style="min-height: 36px" class="flex-wrap relative w-full shadow-sm flex items-center border border-gray-600 rounded px-2 py-0.5" :class="wrapperClass" @click.stop.prevent="clickWrapper" @mouseup.stop.prevent @mousedown.prevent>
|
||||||
<div v-for="item in selected" :key="item.id" class="rounded-full px-2 py-0.5 m-0.5 text-xs bg-bg flex flex-nowrap whitespace-nowrap items-center justify-center relative min-w-12">
|
<div v-for="item in selected" :key="item.id" role="listitem" class="rounded-full px-2 py-0.5 m-0.5 text-xs bg-bg flex flex-nowrap whitespace-nowrap items-center justify-center relative min-w-12">
|
||||||
<div v-if="!disabled" class="w-full h-full rounded-full absolute top-0 left-0 opacity-0 hover:opacity-100 px-1 bg-bg bg-opacity-75 flex items-center justify-end cursor-pointer">
|
<div v-if="!disabled" class="w-full h-full rounded-full absolute top-0 left-0 opacity-0 hover:opacity-100 px-1 bg-bg bg-opacity-75 flex items-center justify-end cursor-pointer" :class="{ 'opacity-100': inputFocused }">
|
||||||
<span v-if="showEdit" class="material-symbols text-base text-white hover:text-warning mr-1" @click.stop="editItem(item)">edit</span>
|
<button v-if="showEdit" type="button" :aria-label="$strings.ButtonEdit" class="material-symbols text-base text-white hover:text-warning focus:text-warning mr-1" @click.stop="editItem(item)" @keydown.enter.stop.prevent="editItem(item)" @focus="setInputFocused(true)" @blur="setInputFocused(false)" tabindex="0">edit</button>
|
||||||
<span class="material-symbols text-white hover:text-error" style="font-size: 1.1rem" @click.stop="removeItem(item.id)">close</span>
|
<button type="button" :aria-label="$strings.ButtonRemove" class="material-symbols text-white hover:text-error focus:text-error" style="font-size: 1.1rem" @click.stop="removeItem(item.id)" @keydown.enter.stop="removeItem(item.id)" @focus="setInputFocused(true)" @blur="setInputFocused(false)" tabindex="0">close</button>
|
||||||
</div>
|
</div>
|
||||||
{{ item[textKey] }}
|
{{ item[textKey] }}
|
||||||
</div>
|
</div>
|
||||||
<div v-if="showEdit && !disabled" class="rounded-full cursor-pointer w-6 h-6 mx-0.5 bg-bg flex items-center justify-center">
|
<div v-if="showEdit && !disabled" class="rounded-full cursor-pointer w-6 h-6 mx-0.5 bg-bg flex items-center justify-center">
|
||||||
<span class="material-symbols text-white hover:text-success pt-px pr-px" style="font-size: 1.1rem" @click.stop="addItem">add</span>
|
<button type="button" :aria-label="$strings.ButtonAdd" class="material-symbols text-white hover:text-success focus:text-success pt-px pr-px" style="font-size: 1.1rem" @click.stop="addItem" @keydown.enter.stop="addItem" tabindex="0">add</button>
|
||||||
</div>
|
</div>
|
||||||
<input v-show="!readonly" ref="input" v-model="textInput" :disabled="disabled" class="h-full bg-primary focus:outline-none px-1 w-6" @keydown="keydownInput" @focus="inputFocus" @blur="inputBlur" @paste="inputPaste" />
|
<input v-show="!readonly" v-model="textInput" ref="input" :id="identifier" :disabled="disabled" class="h-full bg-primary focus:outline-none px-1 w-6" @keydown="keydownInput" @focus="inputFocus" @blur="inputBlur" @paste="inputPaste" />
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
|
|
||||||
@@ -65,6 +65,7 @@ export default {
|
|||||||
currentSearch: null,
|
currentSearch: null,
|
||||||
typingTimeout: null,
|
typingTimeout: null,
|
||||||
isFocused: false,
|
isFocused: false,
|
||||||
|
inputFocused: false,
|
||||||
menu: null,
|
menu: null,
|
||||||
items: []
|
items: []
|
||||||
}
|
}
|
||||||
@@ -102,6 +103,9 @@ export default {
|
|||||||
},
|
},
|
||||||
filterData() {
|
filterData() {
|
||||||
return this.$store.state.libraries.filterData || {}
|
return this.$store.state.libraries.filterData || {}
|
||||||
|
},
|
||||||
|
identifier() {
|
||||||
|
return Math.random().toString(36).substring(2)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
@@ -114,6 +118,9 @@ export default {
|
|||||||
getIsSelected(itemValue) {
|
getIsSelected(itemValue) {
|
||||||
return !!this.selected.find((i) => i.id === itemValue)
|
return !!this.selected.find((i) => i.id === itemValue)
|
||||||
},
|
},
|
||||||
|
setInputFocused(focused) {
|
||||||
|
this.inputFocused = focused
|
||||||
|
},
|
||||||
search() {
|
search() {
|
||||||
if (!this.textInput) return
|
if (!this.textInput) return
|
||||||
this.currentSearch = this.textInput
|
this.currentSearch = this.textInput
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
<template>
|
<template>
|
||||||
<button class="icon-btn rounded-md flex items-center justify-center h-9 w-9 relative" :class="borderless ? '' : 'bg-primary border border-gray-600'" @click="clickBtn">
|
<button :aria-label="isRead ? $strings.MessageMarkAsNotFinished : $strings.MessageMarkAsFinished" class="icon-btn rounded-md flex items-center justify-center h-9 w-9 relative" :class="borderless ? '' : 'bg-primary border border-gray-600'" @click="clickBtn">
|
||||||
<div class="w-5 h-5 text-white relative">
|
<div class="w-5 h-5 text-white relative">
|
||||||
<svg v-if="isRead" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="rgb(63, 181, 68)">
|
<svg v-if="isRead" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="rgb(63, 181, 68)">
|
||||||
<path d="M19 1H5c-1.1 0-1.99.9-1.99 2L3 15.93c0 .69.35 1.3.88 1.66L12 23l8.11-5.41c.53-.36.88-.97.88-1.66L21 3c0-1.1-.9-2-2-2zm-9 15l-5-5 1.41-1.41L10 13.17l7.59-7.59L19 7l-9 9z" />
|
<path d="M19 1H5c-1.1 0-1.99.9-1.99 2L3 15.93c0 .69.35 1.3.88 1.66L12 23l8.11-5.41c.53-.36.88-.97.88-1.66L21 3c0-1.1-.9-2-2-2zm-9 15l-5-5 1.41-1.41L10 13.17l7.59-7.59L19 7l-9 9z" />
|
||||||
|
|||||||
@@ -57,7 +57,8 @@ export default {
|
|||||||
inputName: String,
|
inputName: String,
|
||||||
showCopy: Boolean,
|
showCopy: Boolean,
|
||||||
step: [String, Number],
|
step: [String, Number],
|
||||||
min: [String, Number]
|
min: [String, Number],
|
||||||
|
customInputClass: String
|
||||||
},
|
},
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
@@ -82,6 +83,7 @@ export default {
|
|||||||
_list.push(`py-${this.paddingY}`)
|
_list.push(`py-${this.paddingY}`)
|
||||||
if (this.noSpinner) _list.push('no-spinner')
|
if (this.noSpinner) _list.push('no-spinner')
|
||||||
if (this.textCenter) _list.push('text-center')
|
if (this.textCenter) _list.push('text-center')
|
||||||
|
if (this.customInputClass) _list.push(this.customInputClass)
|
||||||
return _list.join(' ')
|
return _list.join(' ')
|
||||||
},
|
},
|
||||||
actualType() {
|
actualType() {
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
<template>
|
<template>
|
||||||
<div>
|
<div>
|
||||||
<button :aria-labelledby="labeledBy" role="checkbox" type="button" class="border rounded-full border-black-100 flex items-center cursor-pointer w-10 justify-start" :aria-checked="toggleValue" :class="className" @click="clickToggle">
|
<button :aria-labelledby="labeledBy" :aria-label="label" role="checkbox" type="button" class="border rounded-full border-black-100 flex items-center cursor-pointer justify-start" :style="{ width: buttonWidth + 'px' }" :aria-checked="toggleValue" :class="className" @click="clickToggle">
|
||||||
<span class="rounded-full border w-5 h-5 border-black-50 shadow transform transition-transform duration-100" :class="switchClassName"></span>
|
<span class="rounded-full border border-black-50 shadow transform transition-transform duration-100" :style="{ width: cursorHeightWidth + 'px', height: cursorHeightWidth + 'px' }" :class="switchClassName"></span>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
@@ -19,7 +19,12 @@ export default {
|
|||||||
default: 'primary'
|
default: 'primary'
|
||||||
},
|
},
|
||||||
disabled: Boolean,
|
disabled: Boolean,
|
||||||
labeledBy: String
|
labeledBy: String,
|
||||||
|
label: String,
|
||||||
|
size: {
|
||||||
|
type: String,
|
||||||
|
default: 'md'
|
||||||
|
}
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
toggleValue: {
|
toggleValue: {
|
||||||
@@ -37,6 +42,13 @@ export default {
|
|||||||
switchClassName() {
|
switchClassName() {
|
||||||
var bgColor = this.disabled ? 'bg-gray-300' : 'bg-white'
|
var bgColor = this.disabled ? 'bg-gray-300' : 'bg-white'
|
||||||
return this.toggleValue ? 'translate-x-5 ' + bgColor : bgColor
|
return this.toggleValue ? 'translate-x-5 ' + bgColor : bgColor
|
||||||
|
},
|
||||||
|
cursorHeightWidth() {
|
||||||
|
if (this.size === 'sm') return 16
|
||||||
|
return 20
|
||||||
|
},
|
||||||
|
buttonWidth() {
|
||||||
|
return this.cursorHeightWidth * 2
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
<template>
|
<template>
|
||||||
<div>
|
<div>
|
||||||
<div class="rounded-full py-1 bg-primary px-2 border border-black-100 text-center flex items-center box-shadow-md" @mousedown.prevent @mouseup.prevent>
|
<div aria-hidden="true" class="rounded-full py-1 bg-primary px-2 border border-black-100 text-center flex items-center box-shadow-md" @mousedown.prevent @mouseup.prevent>
|
||||||
<span class="material-symbols" :class="selectedSizeIndex === 0 ? 'text-gray-400' : 'hover:text-yellow-300 cursor-pointer'" style="font-size: 0.9rem" @mousedown.prevent @click="decreaseSize" aria-label="Decrease Cover Size" role="button"></span>
|
<span class="material-symbols" :class="selectedSizeIndex === 0 ? 'text-gray-400' : 'hover:text-yellow-300 cursor-pointer'" style="font-size: 0.9rem" @mousedown.prevent @click="decreaseSize" aria-label="Decrease Cover Size" role="button"></span>
|
||||||
<p class="px-2 font-mono" style="font-size: 1rem">{{ bookCoverWidth }}</p>
|
<p class="px-2 font-mono" style="font-size: 1rem">{{ bookCoverWidth }}</p>
|
||||||
<span class="material-symbols" :class="selectedSizeIndex === availableSizes.length - 1 ? 'text-gray-400' : 'hover:text-yellow-300 cursor-pointer'" style="font-size: 0.9rem" @mousedown.prevent @click="increaseSize" aria-label="Increase Cover Size" role="button"></span>
|
<span class="material-symbols" :class="selectedSizeIndex === availableSizes.length - 1 ? 'text-gray-400' : 'hover:text-yellow-300 cursor-pointer'" style="font-size: 0.9rem" @mousedown.prevent @click="increaseSize" aria-label="Increase Cover Size" role="button"></span>
|
||||||
|
|||||||
@@ -3,10 +3,10 @@
|
|||||||
<div class="flex items-center py-3e">
|
<div class="flex items-center py-3e">
|
||||||
<slot />
|
<slot />
|
||||||
<div class="flex-grow" />
|
<div class="flex-grow" />
|
||||||
<button cy-id="leftScrollButton" v-if="isScrollable" class="w-8e h-8e mx-1e flex items-center justify-center rounded-full" :class="canScrollLeft ? 'hover:bg-white hover:bg-opacity-5 text-gray-300 hover:text-white' : 'text-white text-opacity-40 cursor-text'" @click="scrollLeft">
|
<button cy-id="leftScrollButton" v-if="isScrollable" :aria-label="$strings.ButtonScrollLeft" class="w-8e h-8e mx-1e flex items-center justify-center rounded-full" :class="canScrollLeft ? 'hover:bg-white hover:bg-opacity-5 text-gray-300 hover:text-white' : 'text-white text-opacity-40 cursor-text'" @click="scrollLeft">
|
||||||
<span class="material-symbols" :style="{ fontSize: 1.5 + 'em' }">chevron_left</span>
|
<span class="material-symbols" :style="{ fontSize: 1.5 + 'em' }">chevron_left</span>
|
||||||
</button>
|
</button>
|
||||||
<button cy-id="rightScrollButton" v-if="isScrollable" class="w-8e h-8e mx-1e flex items-center justify-center rounded-full" :class="canScrollRight ? 'hover:bg-white hover:bg-opacity-5 text-gray-300 hover:text-white' : 'text-white text-opacity-40 cursor-text'" @click="scrollRight">
|
<button cy-id="rightScrollButton" v-if="isScrollable" :aria-label="$strings.ButtonScrollRight" class="w-8e h-8e mx-1e flex items-center justify-center rounded-full" :class="canScrollRight ? 'hover:bg-white hover:bg-opacity-5 text-gray-300 hover:text-white' : 'text-white text-opacity-40 cursor-text'" @click="scrollRight">
|
||||||
<span class="material-symbols" :style="{ fontSize: 1.5 + 'em' }">chevron_right</span>
|
<span class="material-symbols" :style="{ fontSize: 1.5 + 'em' }">chevron_right</span>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -101,7 +101,12 @@ export default {
|
|||||||
return this.$store.state.libraries.filterData || {}
|
return this.$store.state.libraries.filterData || {}
|
||||||
},
|
},
|
||||||
podcastTypes() {
|
podcastTypes() {
|
||||||
return this.$store.state.globals.podcastTypes || []
|
return this.$store.state.globals.podcastTypes.map((e) => {
|
||||||
|
return {
|
||||||
|
text: this.$strings[e.descriptionKey] || e.text,
|
||||||
|
value: e.value
|
||||||
|
}
|
||||||
|
})
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
|
|||||||
@@ -71,8 +71,6 @@ export default {
|
|||||||
this.showSeriesForm = true
|
this.showSeriesForm = true
|
||||||
},
|
},
|
||||||
submitSeriesForm() {
|
submitSeriesForm() {
|
||||||
console.log('submit series form', this.value, this.selectedSeries)
|
|
||||||
|
|
||||||
if (!this.selectedSeries.name) {
|
if (!this.selectedSeries.name) {
|
||||||
this.$toast.error('Must enter a series')
|
this.$toast.error('Must enter a series')
|
||||||
return
|
return
|
||||||
|
|||||||
@@ -357,7 +357,8 @@ export default {
|
|||||||
teardown: false,
|
teardown: false,
|
||||||
transports: ['websocket'],
|
transports: ['websocket'],
|
||||||
upgrade: false,
|
upgrade: false,
|
||||||
reconnection: true
|
reconnection: true,
|
||||||
|
path: `${this.$config.routerBasePath}/socket.io`
|
||||||
})
|
})
|
||||||
this.$root.socket = this.socket
|
this.$root.socket = this.socket
|
||||||
console.log('Socket initialized')
|
console.log('Socket initialized')
|
||||||
|
|||||||
@@ -57,9 +57,10 @@ export default {
|
|||||||
for (let entry of entries) {
|
for (let entry of entries) {
|
||||||
this.cardWidth = entry.borderBoxSize[0].inlineSize
|
this.cardWidth = entry.borderBoxSize[0].inlineSize
|
||||||
this.cardHeight = entry.borderBoxSize[0].blockSize
|
this.cardHeight = entry.borderBoxSize[0].blockSize
|
||||||
|
}
|
||||||
|
this.coverHeight = instance.coverHeight
|
||||||
this.resizeObserver.disconnect()
|
this.resizeObserver.disconnect()
|
||||||
this.$refs.bookshelf.removeChild(instance.$el)
|
this.$refs.bookshelf.removeChild(instance.$el)
|
||||||
}
|
|
||||||
})
|
})
|
||||||
instance.$el.style.visibility = 'hidden'
|
instance.$el.style.visibility = 'hidden'
|
||||||
instance.$el.style.position = 'absolute'
|
instance.$el.style.position = 'absolute'
|
||||||
@@ -131,10 +132,7 @@ export default {
|
|||||||
this.entityComponentRefs[index] = instance
|
this.entityComponentRefs[index] = instance
|
||||||
|
|
||||||
instance.$mount()
|
instance.$mount()
|
||||||
const shelfOffsetY = this.shelfPaddingHeight * this.sizeMultiplier
|
instance.$el.style.transform = this.entityTransform((index % this.entitiesPerShelf) + 1)
|
||||||
const row = index % this.entitiesPerShelf
|
|
||||||
const shelfOffsetX = row * this.totalEntityCardWidth + this.bookshelfMarginLeft
|
|
||||||
instance.$el.style.transform = `translate3d(${shelfOffsetX}px, ${shelfOffsetY}px, 0px)`
|
|
||||||
instance.$el.classList.add('absolute', 'top-0', 'left-0')
|
instance.$el.classList.add('absolute', 'top-0', 'left-0')
|
||||||
shelfEl.appendChild(instance.$el)
|
shelfEl.appendChild(instance.$el)
|
||||||
|
|
||||||
|
|||||||
@@ -28,10 +28,8 @@ export default {
|
|||||||
var validOtherFiles = []
|
var validOtherFiles = []
|
||||||
var ignoredFiles = []
|
var ignoredFiles = []
|
||||||
files.forEach((file) => {
|
files.forEach((file) => {
|
||||||
// var filetype = this.checkFileType(file.name)
|
|
||||||
if (!file.filetype) ignoredFiles.push(file)
|
if (!file.filetype) ignoredFiles.push(file)
|
||||||
else {
|
else {
|
||||||
// file.filetype = filetype
|
|
||||||
if (file.filetype === 'audio' || (file.filetype === 'ebook' && mediaType === 'book')) validItemFiles.push(file)
|
if (file.filetype === 'audio' || (file.filetype === 'ebook' && mediaType === 'book')) validItemFiles.push(file)
|
||||||
else validOtherFiles.push(file)
|
else validOtherFiles.push(file)
|
||||||
}
|
}
|
||||||
@@ -165,7 +163,7 @@ export default {
|
|||||||
|
|
||||||
var firstBookPath = Path.dirname(firstBookFile.filepath)
|
var firstBookPath = Path.dirname(firstBookFile.filepath)
|
||||||
|
|
||||||
var dirs = firstBookPath.split('/').filter(d => !!d && d !== '.')
|
var dirs = firstBookPath.split('/').filter((d) => !!d && d !== '.')
|
||||||
if (dirs.length) {
|
if (dirs.length) {
|
||||||
audiobook.title = dirs.pop()
|
audiobook.title = dirs.pop()
|
||||||
if (dirs.length > 1) {
|
if (dirs.length > 1) {
|
||||||
@@ -189,7 +187,7 @@ export default {
|
|||||||
var firstAudioFile = podcast.itemFiles[0]
|
var firstAudioFile = podcast.itemFiles[0]
|
||||||
if (!firstAudioFile.filepath) return podcast // No path
|
if (!firstAudioFile.filepath) return podcast // No path
|
||||||
var firstPath = Path.dirname(firstAudioFile.filepath)
|
var firstPath = Path.dirname(firstAudioFile.filepath)
|
||||||
var dirs = firstPath.split('/').filter(d => !!d && d !== '.')
|
var dirs = firstPath.split('/').filter((d) => !!d && d !== '.')
|
||||||
if (dirs.length) {
|
if (dirs.length) {
|
||||||
podcast.title = dirs.length > 1 ? dirs[1] : dirs[0]
|
podcast.title = dirs.length > 1 ? dirs[1] : dirs[0]
|
||||||
} else {
|
} else {
|
||||||
@@ -212,13 +210,15 @@ export default {
|
|||||||
}
|
}
|
||||||
var ignoredFiles = itemData.ignoredFiles
|
var ignoredFiles = itemData.ignoredFiles
|
||||||
var index = 1
|
var index = 1
|
||||||
var items = itemData.items.filter((ab) => {
|
var items = itemData.items
|
||||||
|
.filter((ab) => {
|
||||||
if (!ab.itemFiles.length) {
|
if (!ab.itemFiles.length) {
|
||||||
if (ab.otherFiles.length) ignoredFiles = ignoredFiles.concat(ab.otherFiles)
|
if (ab.otherFiles.length) ignoredFiles = ignoredFiles.concat(ab.otherFiles)
|
||||||
if (ab.ignoredFiles.length) ignoredFiles = ignoredFiles.concat(ab.ignoredFiles)
|
if (ab.ignoredFiles.length) ignoredFiles = ignoredFiles.concat(ab.ignoredFiles)
|
||||||
}
|
}
|
||||||
return ab.itemFiles.length
|
return ab.itemFiles.length
|
||||||
}).map(ab => this.cleanItem(ab, mediaType, index++))
|
})
|
||||||
|
.map((ab) => this.cleanItem(ab, mediaType, index++))
|
||||||
return {
|
return {
|
||||||
items,
|
items,
|
||||||
ignoredFiles
|
ignoredFiles
|
||||||
@@ -259,7 +259,7 @@ export default {
|
|||||||
|
|
||||||
otherFiles.forEach((file) => {
|
otherFiles.forEach((file) => {
|
||||||
var dir = Path.dirname(file.filepath)
|
var dir = Path.dirname(file.filepath)
|
||||||
var findItem = Object.values(itemMap).find(b => dir.startsWith(b.path))
|
var findItem = Object.values(itemMap).find((b) => dir.startsWith(b.path))
|
||||||
if (findItem) {
|
if (findItem) {
|
||||||
findItem.otherFiles.push(file)
|
findItem.otherFiles.push(file)
|
||||||
} else {
|
} else {
|
||||||
@@ -270,18 +270,18 @@ export default {
|
|||||||
var items = []
|
var items = []
|
||||||
var index = 1
|
var index = 1
|
||||||
// If book media type and all files are audio files then treat each one as an audiobook
|
// If book media type and all files are audio files then treat each one as an audiobook
|
||||||
if (itemMap[''] && !otherFiles.length && mediaType === 'book' && !itemMap[''].itemFiles.some(f => f.filetype !== 'audio')) {
|
if (itemMap[''] && !otherFiles.length && mediaType === 'book' && !itemMap[''].itemFiles.some((f) => f.filetype !== 'audio')) {
|
||||||
items = itemMap[''].itemFiles.map((audioFile) => {
|
items = itemMap[''].itemFiles.map((audioFile) => {
|
||||||
return this.cleanItem({ itemFiles: [audioFile], otherFiles: [], ignoredFiles: [] }, mediaType, index++)
|
return this.cleanItem({ itemFiles: [audioFile], otherFiles: [], ignoredFiles: [] }, mediaType, index++)
|
||||||
})
|
})
|
||||||
} else {
|
} else {
|
||||||
items = Object.values(itemMap).map(i => this.cleanItem(i, mediaType, index++))
|
items = Object.values(itemMap).map((i) => this.cleanItem(i, mediaType, index++))
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
items,
|
items,
|
||||||
ignoredFiles: ignoredFiles
|
ignoredFiles: ignoredFiles
|
||||||
}
|
}
|
||||||
},
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
+25
-40
@@ -1,19 +1,24 @@
|
|||||||
const pkg = require('./package.json')
|
const pkg = require('./package.json')
|
||||||
|
|
||||||
|
const routerBasePath = process.env.ROUTER_BASE_PATH || ''
|
||||||
|
const serverHostUrl = process.env.NODE_ENV === 'production' ? '' : 'http://localhost:3333'
|
||||||
|
const serverPaths = ['api/', 'public/', 'hls/', 'auth/', 'feed/', 'status', 'login', 'logout', 'init']
|
||||||
|
const proxy = Object.fromEntries(serverPaths.map((path) => [`${routerBasePath}/${path}`, { target: process.env.NODE_ENV !== 'production' ? serverHostUrl : '/' }]))
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
// Disable server-side rendering: https://go.nuxtjs.dev/ssr-mode
|
// Disable server-side rendering: https://go.nuxtjs.dev/ssr-mode
|
||||||
ssr: false,
|
ssr: false,
|
||||||
target: 'static',
|
target: 'static',
|
||||||
dev: process.env.NODE_ENV !== 'production',
|
dev: process.env.NODE_ENV !== 'production',
|
||||||
env: {
|
env: {
|
||||||
serverUrl: process.env.NODE_ENV === 'production' ? process.env.ROUTER_BASE_PATH || '' : 'http://localhost:3333',
|
serverUrl: serverHostUrl + routerBasePath,
|
||||||
chromecastReceiver: 'FD1F76C5'
|
chromecastReceiver: 'FD1F76C5'
|
||||||
},
|
},
|
||||||
telemetry: false,
|
telemetry: false,
|
||||||
|
|
||||||
publicRuntimeConfig: {
|
publicRuntimeConfig: {
|
||||||
version: pkg.version,
|
version: pkg.version,
|
||||||
routerBasePath: process.env.ROUTER_BASE_PATH || ''
|
routerBasePath
|
||||||
},
|
},
|
||||||
|
|
||||||
// Global page headers: https://go.nuxtjs.dev/config-head
|
// Global page headers: https://go.nuxtjs.dev/config-head
|
||||||
@@ -22,38 +27,23 @@ module.exports = {
|
|||||||
htmlAttrs: {
|
htmlAttrs: {
|
||||||
lang: 'en'
|
lang: 'en'
|
||||||
},
|
},
|
||||||
meta: [
|
meta: [{ charset: 'utf-8' }, { name: 'viewport', content: 'width=device-width, initial-scale=1' }, { hid: 'description', name: 'description', content: '' }, { hid: 'robots', name: 'robots', content: 'noindex' }],
|
||||||
{ charset: 'utf-8' },
|
|
||||||
{ name: 'viewport', content: 'width=device-width, initial-scale=1' },
|
|
||||||
{ hid: 'description', name: 'description', content: '' },
|
|
||||||
{ hid: 'robots', name: 'robots', content: 'noindex' }
|
|
||||||
],
|
|
||||||
script: [],
|
script: [],
|
||||||
link: [
|
link: [
|
||||||
{ rel: 'icon', type: 'image/x-icon', href: (process.env.ROUTER_BASE_PATH || '') + '/favicon.ico' },
|
{ rel: 'icon', type: 'image/x-icon', href: routerBasePath + '/favicon.ico' },
|
||||||
{ rel: 'apple-touch-icon', href: (process.env.ROUTER_BASE_PATH || '') + '/ios_icon.png' }
|
{ rel: 'apple-touch-icon', href: routerBasePath + '/ios_icon.png' }
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
|
||||||
router: {
|
router: {
|
||||||
base: process.env.ROUTER_BASE_PATH || ''
|
base: routerBasePath
|
||||||
},
|
},
|
||||||
|
|
||||||
// Global CSS: https://go.nuxtjs.dev/config-css
|
// Global CSS: https://go.nuxtjs.dev/config-css
|
||||||
css: [
|
css: ['@/assets/tailwind.css', '@/assets/app.css'],
|
||||||
'@/assets/tailwind.css',
|
|
||||||
'@/assets/app.css'
|
|
||||||
],
|
|
||||||
|
|
||||||
// Plugins to run before rendering page: https://go.nuxtjs.dev/config-plugins
|
// Plugins to run before rendering page: https://go.nuxtjs.dev/config-plugins
|
||||||
plugins: [
|
plugins: ['@/plugins/constants.js', '@/plugins/init.client.js', '@/plugins/axios.js', '@/plugins/toast.js', '@/plugins/utils.js', '@/plugins/i18n.js'],
|
||||||
'@/plugins/constants.js',
|
|
||||||
'@/plugins/init.client.js',
|
|
||||||
'@/plugins/axios.js',
|
|
||||||
'@/plugins/toast.js',
|
|
||||||
'@/plugins/utils.js',
|
|
||||||
'@/plugins/i18n.js'
|
|
||||||
],
|
|
||||||
|
|
||||||
// Auto import components: https://go.nuxtjs.dev/config-components
|
// Auto import components: https://go.nuxtjs.dev/config-components
|
||||||
components: true,
|
components: true,
|
||||||
@@ -65,30 +55,25 @@ module.exports = {
|
|||||||
],
|
],
|
||||||
|
|
||||||
// Modules: https://go.nuxtjs.dev/config-modules
|
// Modules: https://go.nuxtjs.dev/config-modules
|
||||||
modules: [
|
modules: ['nuxt-socket-io', '@nuxtjs/axios', '@nuxtjs/proxy'],
|
||||||
'nuxt-socket-io',
|
|
||||||
'@nuxtjs/axios',
|
|
||||||
'@nuxtjs/proxy'
|
|
||||||
],
|
|
||||||
|
|
||||||
proxy: {
|
proxy,
|
||||||
'/api/': { target: process.env.NODE_ENV !== 'production' ? 'http://localhost:3333' : '/' },
|
|
||||||
'/dev/': { target: 'http://localhost:3333', pathRewrite: { '^/dev/': '' } }
|
|
||||||
},
|
|
||||||
|
|
||||||
io: {
|
io: {
|
||||||
sockets: [{
|
sockets: [
|
||||||
|
{
|
||||||
name: 'dev',
|
name: 'dev',
|
||||||
url: 'http://localhost:3333'
|
url: serverHostUrl
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'prod'
|
name: 'prod'
|
||||||
}]
|
}
|
||||||
|
]
|
||||||
},
|
},
|
||||||
|
|
||||||
// Axios module configuration: https://go.nuxtjs.dev/config-axios
|
// Axios module configuration: https://go.nuxtjs.dev/config-axios
|
||||||
axios: {
|
axios: {
|
||||||
baseURL: process.env.ROUTER_BASE_PATH || ''
|
baseURL: routerBasePath
|
||||||
},
|
},
|
||||||
|
|
||||||
// nuxt/pwa https://pwa.nuxtjs.org
|
// nuxt/pwa https://pwa.nuxtjs.org
|
||||||
@@ -108,11 +93,11 @@ module.exports = {
|
|||||||
background_color: '#232323',
|
background_color: '#232323',
|
||||||
icons: [
|
icons: [
|
||||||
{
|
{
|
||||||
src: (process.env.ROUTER_BASE_PATH || '') + '/icon.svg',
|
src: routerBasePath + '/icon.svg',
|
||||||
sizes: 'any'
|
sizes: 'any'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
src: (process.env.ROUTER_BASE_PATH || '') + '/icon192.png',
|
src: routerBasePath + '/icon192.png',
|
||||||
type: 'image/png',
|
type: 'image/png',
|
||||||
sizes: 'any'
|
sizes: 'any'
|
||||||
}
|
}
|
||||||
@@ -132,7 +117,7 @@ module.exports = {
|
|||||||
postcssOptions: {
|
postcssOptions: {
|
||||||
plugins: {
|
plugins: {
|
||||||
tailwindcss: {},
|
tailwindcss: {},
|
||||||
autoprefixer: {},
|
autoprefixer: {}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -156,5 +141,5 @@ module.exports = {
|
|||||||
*/
|
*/
|
||||||
devServerHandlers: [],
|
devServerHandlers: [],
|
||||||
|
|
||||||
ignore: ["**/*.test.*", "**/*.cy.*"]
|
ignore: ['**/*.test.*', '**/*.cy.*']
|
||||||
}
|
}
|
||||||
|
|||||||
Generated
+2
-2
@@ -1,12 +1,12 @@
|
|||||||
{
|
{
|
||||||
"name": "audiobookshelf-client",
|
"name": "audiobookshelf-client",
|
||||||
"version": "2.15.0",
|
"version": "2.17.6",
|
||||||
"lockfileVersion": 3,
|
"lockfileVersion": 3,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"packages": {
|
"packages": {
|
||||||
"": {
|
"": {
|
||||||
"name": "audiobookshelf-client",
|
"name": "audiobookshelf-client",
|
||||||
"version": "2.15.0",
|
"version": "2.17.6",
|
||||||
"license": "ISC",
|
"license": "ISC",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@nuxtjs/axios": "^5.13.6",
|
"@nuxtjs/axios": "^5.13.6",
|
||||||
|
|||||||
+1
-1
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "audiobookshelf-client",
|
"name": "audiobookshelf-client",
|
||||||
"version": "2.15.0",
|
"version": "2.17.6",
|
||||||
"buildNumber": 1,
|
"buildNumber": 1,
|
||||||
"description": "Self-hosted audiobook and podcast client",
|
"description": "Self-hosted audiobook and podcast client",
|
||||||
"main": "index.js",
|
"main": "index.js",
|
||||||
|
|||||||
@@ -32,9 +32,48 @@
|
|||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div v-if="showEreaderTable">
|
||||||
|
<div class="w-full h-px bg-white/10 my-4" />
|
||||||
|
|
||||||
|
<app-settings-content :header-text="$strings.HeaderEreaderDevices">
|
||||||
|
<template #header-items>
|
||||||
|
<div class="flex-grow" />
|
||||||
|
|
||||||
|
<ui-btn color="primary" small @click="addNewDeviceClick">{{ $strings.ButtonAddDevice }}</ui-btn>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<table v-if="ereaderDevices.length" class="tracksTable mt-4">
|
||||||
|
<tr>
|
||||||
|
<th class="text-left">{{ $strings.LabelName }}</th>
|
||||||
|
<th class="text-left">{{ $strings.LabelEmail }}</th>
|
||||||
|
<th class="w-40"></th>
|
||||||
|
</tr>
|
||||||
|
<tr v-for="device in ereaderDevices" :key="device.name">
|
||||||
|
<td>
|
||||||
|
<p class="text-sm md:text-base text-gray-100">{{ device.name }}</p>
|
||||||
|
</td>
|
||||||
|
<td class="text-left">
|
||||||
|
<p class="text-sm md:text-base text-gray-100">{{ device.email }}</p>
|
||||||
|
</td>
|
||||||
|
<td class="w-40">
|
||||||
|
<div class="flex justify-end items-center h-10">
|
||||||
|
<ui-icon-btn icon="edit" borderless :size="8" icon-font-size="1.1rem" :disabled="deletingDeviceName === device.name || device.users?.length !== 1" class="mx-1" @click="editDeviceClick(device)" />
|
||||||
|
<ui-icon-btn icon="delete" borderless :size="8" icon-font-size="1.1rem" :disabled="deletingDeviceName === device.name || device.users?.length !== 1" @click="deleteDeviceClick(device)" />
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
<div v-else-if="!loading" class="text-center py-4">
|
||||||
|
<p class="text-lg text-gray-100">{{ $strings.MessageNoDevices }}</p>
|
||||||
|
</div>
|
||||||
|
</app-settings-content>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div class="py-4 mt-8 flex">
|
<div class="py-4 mt-8 flex">
|
||||||
<ui-btn color="primary flex items-center text-lg" @click="logout"><span class="material-symbols mr-4 icon-text">logout</span>{{ $strings.ButtonLogout }}</ui-btn>
|
<ui-btn color="primary flex items-center text-lg" @click="logout"><span class="material-symbols mr-4 icon-text">logout</span>{{ $strings.ButtonLogout }}</ui-btn>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<modals-emails-user-e-reader-device-modal v-model="showEReaderDeviceModal" :existing-devices="revisedEreaderDevices" :ereader-device="selectedEReaderDevice" @update="ereaderDevicesUpdated" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
@@ -43,11 +82,20 @@
|
|||||||
export default {
|
export default {
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
|
loading: false,
|
||||||
password: null,
|
password: null,
|
||||||
newPassword: null,
|
newPassword: null,
|
||||||
confirmPassword: null,
|
confirmPassword: null,
|
||||||
changingPassword: false,
|
changingPassword: false,
|
||||||
selectedLanguage: ''
|
selectedLanguage: '',
|
||||||
|
newEReaderDevice: {
|
||||||
|
name: '',
|
||||||
|
email: ''
|
||||||
|
},
|
||||||
|
ereaderDevices: [],
|
||||||
|
deletingDeviceName: null,
|
||||||
|
selectedEReaderDevice: null,
|
||||||
|
showEReaderDeviceModal: false
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
@@ -75,6 +123,12 @@ export default {
|
|||||||
},
|
},
|
||||||
showChangePasswordForm() {
|
showChangePasswordForm() {
|
||||||
return !this.isGuest && this.isPasswordAuthEnabled
|
return !this.isGuest && this.isPasswordAuthEnabled
|
||||||
|
},
|
||||||
|
showEreaderTable() {
|
||||||
|
return this.usertype !== 'root' && this.usertype !== 'admin' && this.user.permissions?.createEreader
|
||||||
|
},
|
||||||
|
revisedEreaderDevices() {
|
||||||
|
return this.ereaderDevices.filter((device) => device.users?.length === 1)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
@@ -142,10 +196,52 @@ export default {
|
|||||||
this.$toast.error(this.$strings.ToastUnknownError)
|
this.$toast.error(this.$strings.ToastUnknownError)
|
||||||
this.changingPassword = false
|
this.changingPassword = false
|
||||||
})
|
})
|
||||||
|
},
|
||||||
|
addNewDeviceClick() {
|
||||||
|
this.selectedEReaderDevice = null
|
||||||
|
this.showEReaderDeviceModal = true
|
||||||
|
},
|
||||||
|
editDeviceClick(device) {
|
||||||
|
this.selectedEReaderDevice = device
|
||||||
|
this.showEReaderDeviceModal = true
|
||||||
|
},
|
||||||
|
deleteDeviceClick(device) {
|
||||||
|
const payload = {
|
||||||
|
message: this.$getString('MessageConfirmDeleteDevice', [device.name]),
|
||||||
|
callback: (confirmed) => {
|
||||||
|
if (confirmed) {
|
||||||
|
this.deleteDevice(device)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
type: 'yesNo'
|
||||||
|
}
|
||||||
|
this.$store.commit('globals/setConfirmPrompt', payload)
|
||||||
|
},
|
||||||
|
deleteDevice(device) {
|
||||||
|
const payload = {
|
||||||
|
ereaderDevices: this.revisedEreaderDevices.filter((d) => d.name !== device.name)
|
||||||
|
}
|
||||||
|
this.deletingDeviceName = device.name
|
||||||
|
this.$axios
|
||||||
|
.$post(`/api/me/ereader-devices`, payload)
|
||||||
|
.then((data) => {
|
||||||
|
this.ereaderDevicesUpdated(data.ereaderDevices)
|
||||||
|
})
|
||||||
|
.catch((error) => {
|
||||||
|
console.error('Failed to delete device', error)
|
||||||
|
this.$toast.error(this.$strings.ToastRemoveFailed)
|
||||||
|
})
|
||||||
|
.finally(() => {
|
||||||
|
this.deletingDeviceName = null
|
||||||
|
})
|
||||||
|
},
|
||||||
|
ereaderDevicesUpdated(ereaderDevices) {
|
||||||
|
this.ereaderDevices = ereaderDevices
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
mounted() {
|
mounted() {
|
||||||
this.selectedLanguage = this.$languageCodes.current
|
this.selectedLanguage = this.$languageCodes.current
|
||||||
|
this.ereaderDevices = this.$store.state.libraries.ereaderDevices || []
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -415,7 +415,7 @@ export default {
|
|||||||
const audioEl = this.audioEl || document.createElement('audio')
|
const audioEl = this.audioEl || document.createElement('audio')
|
||||||
var src = audioTrack.contentUrl + `?token=${this.userToken}`
|
var src = audioTrack.contentUrl + `?token=${this.userToken}`
|
||||||
if (this.$isDev) {
|
if (this.$isDev) {
|
||||||
src = `http://localhost:3333${this.$config.routerBasePath}${src}`
|
src = `${process.env.serverUrl}${src}`
|
||||||
}
|
}
|
||||||
|
|
||||||
audioEl.src = src
|
audioEl.src = src
|
||||||
@@ -486,7 +486,7 @@ export default {
|
|||||||
.then((data) => {
|
.then((data) => {
|
||||||
this.saving = false
|
this.saving = false
|
||||||
if (data.updated) {
|
if (data.updated) {
|
||||||
this.$toast.success('Chapters updated')
|
this.$toast.success(this.$strings.ToastChaptersUpdated)
|
||||||
if (this.previousRoute) {
|
if (this.previousRoute) {
|
||||||
this.$router.push(this.previousRoute)
|
this.$router.push(this.previousRoute)
|
||||||
} else {
|
} else {
|
||||||
@@ -533,7 +533,7 @@ export default {
|
|||||||
},
|
},
|
||||||
findChapters() {
|
findChapters() {
|
||||||
if (!this.asinInput) {
|
if (!this.asinInput) {
|
||||||
this.$toast.error('Must input an ASIN')
|
this.$toast.error(this.$strings.ToastAsinRequired)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -64,6 +64,20 @@
|
|||||||
<ui-multi-select ref="redirectUris" v-model="newAuthSettings.authOpenIDMobileRedirectURIs" :items="newAuthSettings.authOpenIDMobileRedirectURIs" :label="$strings.LabelMobileRedirectURIs" class="mb-2" :menuDisabled="true" :disabled="savingSettings" />
|
<ui-multi-select ref="redirectUris" v-model="newAuthSettings.authOpenIDMobileRedirectURIs" :items="newAuthSettings.authOpenIDMobileRedirectURIs" :label="$strings.LabelMobileRedirectURIs" class="mb-2" :menuDisabled="true" :disabled="savingSettings" />
|
||||||
<p class="sm:pl-4 text-sm text-gray-300 mb-2" v-html="$strings.LabelMobileRedirectURIsDescription" />
|
<p class="sm:pl-4 text-sm text-gray-300 mb-2" v-html="$strings.LabelMobileRedirectURIsDescription" />
|
||||||
|
|
||||||
|
<div class="flex sm:items-center flex-col sm:flex-row pt-1 mb-2">
|
||||||
|
<div class="w-44">
|
||||||
|
<ui-dropdown v-model="newAuthSettings.authOpenIDSubfolderForRedirectURLs" small :items="subfolderOptions" :label="$strings.LabelWebRedirectURLsSubfolder" :disabled="savingSettings" />
|
||||||
|
</div>
|
||||||
|
<div class="mt-2 sm:mt-5">
|
||||||
|
<p class="sm:pl-4 text-sm text-gray-300">{{ $strings.LabelWebRedirectURLsDescription }}</p>
|
||||||
|
<p class="sm:pl-4 text-sm text-gray-300 mb-2">
|
||||||
|
<code>{{ webCallbackURL }}</code>
|
||||||
|
<br />
|
||||||
|
<code>{{ mobileAppCallbackURL }}</code>
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<ui-text-input-with-label ref="buttonTextInput" v-model="newAuthSettings.authOpenIDButtonText" :disabled="savingSettings" :label="$strings.LabelButtonText" class="mb-2" />
|
<ui-text-input-with-label ref="buttonTextInput" v-model="newAuthSettings.authOpenIDButtonText" :disabled="savingSettings" :label="$strings.LabelButtonText" class="mb-2" />
|
||||||
|
|
||||||
<div class="flex sm:items-center flex-col sm:flex-row pt-1 mb-2">
|
<div class="flex sm:items-center flex-col sm:flex-row pt-1 mb-2">
|
||||||
@@ -164,6 +178,27 @@ export default {
|
|||||||
value: 'username'
|
value: 'username'
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
},
|
||||||
|
subfolderOptions() {
|
||||||
|
const options = [
|
||||||
|
{
|
||||||
|
text: 'None',
|
||||||
|
value: ''
|
||||||
|
}
|
||||||
|
]
|
||||||
|
if (this.$config.routerBasePath) {
|
||||||
|
options.push({
|
||||||
|
text: this.$config.routerBasePath,
|
||||||
|
value: this.$config.routerBasePath
|
||||||
|
})
|
||||||
|
}
|
||||||
|
return options
|
||||||
|
},
|
||||||
|
webCallbackURL() {
|
||||||
|
return `https://<your.server.com>${this.newAuthSettings.authOpenIDSubfolderForRedirectURLs ? this.newAuthSettings.authOpenIDSubfolderForRedirectURLs : ''}/auth/openid/callback`
|
||||||
|
},
|
||||||
|
mobileAppCallbackURL() {
|
||||||
|
return `https://<your.server.com>${this.newAuthSettings.authOpenIDSubfolderForRedirectURLs ? this.newAuthSettings.authOpenIDSubfolderForRedirectURLs : ''}/auth/openid/mobile-redirect`
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
@@ -325,7 +360,8 @@ export default {
|
|||||||
},
|
},
|
||||||
init() {
|
init() {
|
||||||
this.newAuthSettings = {
|
this.newAuthSettings = {
|
||||||
...this.authSettings
|
...this.authSettings,
|
||||||
|
authOpenIDSubfolderForRedirectURLs: this.authSettings.authOpenIDSubfolderForRedirectURLs === undefined ? this.$config.routerBasePath : this.authSettings.authOpenIDSubfolderForRedirectURLs
|
||||||
}
|
}
|
||||||
this.enableLocalAuth = this.authMethods.includes('local')
|
this.enableLocalAuth = this.authMethods.includes('local')
|
||||||
this.enableOpenIDAuth = this.authMethods.includes('openid')
|
this.enableOpenIDAuth = this.authMethods.includes('openid')
|
||||||
|
|||||||
@@ -6,9 +6,9 @@
|
|||||||
<div class="pt-4">
|
<div class="pt-4">
|
||||||
<h2 class="font-semibold">{{ $strings.HeaderSettingsGeneral }}</h2>
|
<h2 class="font-semibold">{{ $strings.HeaderSettingsGeneral }}</h2>
|
||||||
</div>
|
</div>
|
||||||
<div class="flex items-end py-2">
|
<div role="article" :aria-label="$strings.LabelSettingsStoreCoversWithItemHelp" class="flex items-end py-2">
|
||||||
<ui-toggle-switch labeledBy="settings-store-cover-with-items" v-model="newServerSettings.storeCoverWithItem" :disabled="updatingServerSettings" @input="(val) => updateSettingsKey('storeCoverWithItem', val)" />
|
<ui-toggle-switch :label="$strings.LabelSettingsStoreCoversWithItem" v-model="newServerSettings.storeCoverWithItem" :disabled="updatingServerSettings" @input="(val) => updateSettingsKey('storeCoverWithItem', val)" />
|
||||||
<ui-tooltip :text="$strings.LabelSettingsStoreCoversWithItemHelp">
|
<ui-tooltip aria-hidden="true" :text="$strings.LabelSettingsStoreCoversWithItemHelp">
|
||||||
<p class="pl-4">
|
<p class="pl-4">
|
||||||
<span id="settings-store-cover-with-items">{{ $strings.LabelSettingsStoreCoversWithItem }}</span>
|
<span id="settings-store-cover-with-items">{{ $strings.LabelSettingsStoreCoversWithItem }}</span>
|
||||||
<span class="material-symbols icon-text">info</span>
|
<span class="material-symbols icon-text">info</span>
|
||||||
@@ -16,9 +16,9 @@
|
|||||||
</ui-tooltip>
|
</ui-tooltip>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="flex items-center py-2">
|
<div role="article" :aria-label="$strings.LabelSettingsStoreMetadataWithItemHelp" class="flex items-center py-2">
|
||||||
<ui-toggle-switch labeledBy="settings-store-metadata-with-items" v-model="newServerSettings.storeMetadataWithItem" :disabled="updatingServerSettings" @input="(val) => updateSettingsKey('storeMetadataWithItem', val)" />
|
<ui-toggle-switch :label="$strings.LabelSettingsStoreMetadataWithItem" v-model="newServerSettings.storeMetadataWithItem" :disabled="updatingServerSettings" @input="(val) => updateSettingsKey('storeMetadataWithItem', val)" />
|
||||||
<ui-tooltip :text="$strings.LabelSettingsStoreMetadataWithItemHelp">
|
<ui-tooltip aria-hidden="true" :text="$strings.LabelSettingsStoreMetadataWithItemHelp">
|
||||||
<p class="pl-4">
|
<p class="pl-4">
|
||||||
<span id="settings-store-metadata-with-items">{{ $strings.LabelSettingsStoreMetadataWithItem }}</span>
|
<span id="settings-store-metadata-with-items">{{ $strings.LabelSettingsStoreMetadataWithItem }}</span>
|
||||||
<span class="material-symbols icon-text">info</span>
|
<span class="material-symbols icon-text">info</span>
|
||||||
@@ -26,9 +26,9 @@
|
|||||||
</ui-tooltip>
|
</ui-tooltip>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="flex items-center py-2">
|
<div role="article" :aria-label="$strings.LabelSettingsSortingIgnorePrefixesHelp" class="flex items-center py-2">
|
||||||
<ui-toggle-switch labeledBy="settings-sorting-ignore-prefixes" v-model="newServerSettings.sortingIgnorePrefix" :disabled="updatingServerSettings" @input="(val) => updateSettingsKey('sortingIgnorePrefix', val)" />
|
<ui-toggle-switch :label="$strings.LabelSettingsSortingIgnorePrefixes" v-model="newServerSettings.sortingIgnorePrefix" :disabled="updatingServerSettings" @input="(val) => updateSettingsKey('sortingIgnorePrefix', val)" />
|
||||||
<ui-tooltip :text="$strings.LabelSettingsSortingIgnorePrefixesHelp">
|
<ui-tooltip aria-hidden="true" :text="$strings.LabelSettingsSortingIgnorePrefixesHelp">
|
||||||
<p class="pl-4">
|
<p class="pl-4">
|
||||||
<span id="settings-sorting-ignore-prefixes">{{ $strings.LabelSettingsSortingIgnorePrefixes }}</span>
|
<span id="settings-sorting-ignore-prefixes">{{ $strings.LabelSettingsSortingIgnorePrefixes }}</span>
|
||||||
<span class="material-symbols icon-text">info</span>
|
<span class="material-symbols icon-text">info</span>
|
||||||
@@ -42,18 +42,13 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="flex items-center py-2 mb-2">
|
|
||||||
<ui-toggle-switch labeledBy="settings-chromecast-support" v-model="newServerSettings.chromecastEnabled" :disabled="updatingServerSettings" @input="(val) => updateSettingsKey('chromecastEnabled', val)" />
|
|
||||||
<p class="pl-4" id="settings-chromecast-support">{{ $strings.LabelSettingsChromecastSupport }}</p>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="pt-4">
|
<div class="pt-4">
|
||||||
<h2 class="font-semibold">{{ $strings.HeaderSettingsScanner }}</h2>
|
<h2 class="font-semibold">{{ $strings.HeaderSettingsScanner }}</h2>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="flex items-center py-2">
|
<div role="article" :aria-label="$strings.LabelSettingsParseSubtitlesHelp" class="flex items-center py-2">
|
||||||
<ui-toggle-switch labeledBy="settings-parse-subtitles" v-model="newServerSettings.scannerParseSubtitle" :disabled="updatingServerSettings" @input="(val) => updateSettingsKey('scannerParseSubtitle', val)" />
|
<ui-toggle-switch :label="$strings.LabelSettingsParseSubtitles" v-model="newServerSettings.scannerParseSubtitle" :disabled="updatingServerSettings" @input="(val) => updateSettingsKey('scannerParseSubtitle', val)" />
|
||||||
<ui-tooltip :text="$strings.LabelSettingsParseSubtitlesHelp">
|
<ui-tooltip aria-hidden="true" :text="$strings.LabelSettingsParseSubtitlesHelp">
|
||||||
<p class="pl-4">
|
<p class="pl-4">
|
||||||
<span id="settings-parse-subtitles">{{ $strings.LabelSettingsParseSubtitles }}</span>
|
<span id="settings-parse-subtitles">{{ $strings.LabelSettingsParseSubtitles }}</span>
|
||||||
<span class="material-symbols icon-text">info</span>
|
<span class="material-symbols icon-text">info</span>
|
||||||
@@ -61,9 +56,9 @@
|
|||||||
</ui-tooltip>
|
</ui-tooltip>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="flex items-center py-2">
|
<div role="article" :aria-label="$strings.LabelSettingsFindCoversHelp" class="flex items-center py-2">
|
||||||
<ui-toggle-switch labeledBy="settings-find-covers" v-model="newServerSettings.scannerFindCovers" :disabled="updatingServerSettings" @input="(val) => updateSettingsKey('scannerFindCovers', val)" />
|
<ui-toggle-switch :label="$strings.LabelSettingsFindCovers" v-model="newServerSettings.scannerFindCovers" :disabled="updatingServerSettings" @input="(val) => updateSettingsKey('scannerFindCovers', val)" />
|
||||||
<ui-tooltip :text="$strings.LabelSettingsFindCoversHelp">
|
<ui-tooltip aria-hidden="true" :text="$strings.LabelSettingsFindCoversHelp">
|
||||||
<p class="pl-4">
|
<p class="pl-4">
|
||||||
<span id="settings-find-covers">{{ $strings.LabelSettingsFindCovers }}</span>
|
<span id="settings-find-covers">{{ $strings.LabelSettingsFindCovers }}</span>
|
||||||
<span class="material-symbols icon-text">info</span>
|
<span class="material-symbols icon-text">info</span>
|
||||||
@@ -75,9 +70,9 @@
|
|||||||
<ui-dropdown v-model="newServerSettings.scannerCoverProvider" small :items="providers" label="Cover Provider" @input="updateScannerCoverProvider" :disabled="updatingServerSettings" />
|
<ui-dropdown v-model="newServerSettings.scannerCoverProvider" small :items="providers" label="Cover Provider" @input="updateScannerCoverProvider" :disabled="updatingServerSettings" />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="flex items-center py-2">
|
<div role="article" :aria-label="$strings.LabelSettingsPreferMatchedMetadataHelp" class="flex items-center py-2">
|
||||||
<ui-toggle-switch labeledBy="settings-prefer-matched-metadata" v-model="newServerSettings.scannerPreferMatchedMetadata" :disabled="updatingServerSettings" @input="(val) => updateSettingsKey('scannerPreferMatchedMetadata', val)" />
|
<ui-toggle-switch :label="$strings.LabelSettingsPreferMatchedMetadata" v-model="newServerSettings.scannerPreferMatchedMetadata" :disabled="updatingServerSettings" @input="(val) => updateSettingsKey('scannerPreferMatchedMetadata', val)" />
|
||||||
<ui-tooltip :text="$strings.LabelSettingsPreferMatchedMetadataHelp">
|
<ui-tooltip aria-hidden="true" :text="$strings.LabelSettingsPreferMatchedMetadataHelp">
|
||||||
<p class="pl-4">
|
<p class="pl-4">
|
||||||
<span id="settings-prefer-matched-metadata">{{ $strings.LabelSettingsPreferMatchedMetadata }}</span>
|
<span id="settings-prefer-matched-metadata">{{ $strings.LabelSettingsPreferMatchedMetadata }}</span>
|
||||||
<span class="material-symbols icon-text">info</span>
|
<span class="material-symbols icon-text">info</span>
|
||||||
@@ -85,15 +80,29 @@
|
|||||||
</ui-tooltip>
|
</ui-tooltip>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="flex items-center py-2">
|
<div role="article" :aria-label="$strings.LabelSettingsEnableWatcherHelp" class="flex items-center py-2">
|
||||||
<ui-toggle-switch labeledBy="settings-disable-watcher" v-model="scannerEnableWatcher" :disabled="updatingServerSettings" @input="(val) => updateSettingsKey('scannerDisableWatcher', !val)" />
|
<ui-toggle-switch :label="$strings.LabelSettingsEnableWatcher" v-model="scannerEnableWatcher" :disabled="updatingServerSettings" @input="(val) => updateSettingsKey('scannerDisableWatcher', !val)" />
|
||||||
<ui-tooltip :text="$strings.LabelSettingsEnableWatcherHelp">
|
<ui-tooltip aria-hidden="true" :text="$strings.LabelSettingsEnableWatcherHelp">
|
||||||
<p class="pl-4">
|
<p class="pl-4">
|
||||||
<span id="settings-disable-watcher">{{ $strings.LabelSettingsEnableWatcher }}</span>
|
<span id="settings-disable-watcher">{{ $strings.LabelSettingsEnableWatcher }}</span>
|
||||||
<span class="material-symbols icon-text">info</span>
|
<span class="material-symbols icon-text">info</span>
|
||||||
</p>
|
</p>
|
||||||
</ui-tooltip>
|
</ui-tooltip>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div class="pt-4">
|
||||||
|
<h2 class="font-semibold">{{ $strings.HeaderSettingsWebClient }}</h2>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="flex items-center py-2">
|
||||||
|
<ui-toggle-switch v-model="newServerSettings.chromecastEnabled" :label="$strings.LabelSettingsChromecastSupport" :disabled="updatingServerSettings" @input="(val) => updateSettingsKey('chromecastEnabled', val)" />
|
||||||
|
<p aria-hidden="true" class="pl-4">{{ $strings.LabelSettingsChromecastSupport }}</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="flex items-center py-2 mb-2">
|
||||||
|
<ui-toggle-switch v-model="newServerSettings.allowIframe" :label="$strings.LabelSettingsAllowIframe" :disabled="updatingServerSettings" @input="(val) => updateSettingsKey('allowIframe', val)" />
|
||||||
|
<p aria-hidden="true" class="pl-4">{{ $strings.LabelSettingsAllowIframe }}</p>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="flex-1">
|
<div class="flex-1">
|
||||||
@@ -324,21 +333,21 @@ export default {
|
|||||||
},
|
},
|
||||||
updateServerSettings(payload) {
|
updateServerSettings(payload) {
|
||||||
this.updatingServerSettings = true
|
this.updatingServerSettings = true
|
||||||
this.$store
|
this.$store.dispatch('updateServerSettings', payload).then((response) => {
|
||||||
.dispatch('updateServerSettings', payload)
|
|
||||||
.then(() => {
|
|
||||||
this.updatingServerSettings = false
|
this.updatingServerSettings = false
|
||||||
|
|
||||||
|
if (response.error) {
|
||||||
|
console.error('Failed to update server settins', response.error)
|
||||||
|
this.$toast.error(response.error)
|
||||||
|
this.initServerSettings()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
if (payload.language) {
|
if (payload.language) {
|
||||||
// Updating language after save allows for re-rendering
|
// Updating language after save allows for re-rendering
|
||||||
this.$setLanguageCode(payload.language)
|
this.$setLanguageCode(payload.language)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.catch((error) => {
|
|
||||||
console.error('Failed to update server settings', error)
|
|
||||||
this.updatingServerSettings = false
|
|
||||||
this.$toast.error(this.$strings.ToastFailedToUpdate)
|
|
||||||
})
|
|
||||||
},
|
},
|
||||||
initServerSettings() {
|
initServerSettings() {
|
||||||
this.newServerSettings = this.serverSettings ? { ...this.serverSettings } : {}
|
this.newServerSettings = this.serverSettings ? { ...this.serverSettings } : {}
|
||||||
|
|||||||
@@ -25,7 +25,7 @@
|
|||||||
<tr v-for="feed in feeds" :key="feed.id" class="cursor-pointer h-12" @click="showFeed(feed)">
|
<tr v-for="feed in feeds" :key="feed.id" class="cursor-pointer h-12" @click="showFeed(feed)">
|
||||||
<!-- -->
|
<!-- -->
|
||||||
<td>
|
<td>
|
||||||
<img :src="coverUrl(feed)" class="h-full w-full" />
|
<img :src="coverUrl(feed)" class="h-auto w-full" />
|
||||||
</td>
|
</td>
|
||||||
<!-- -->
|
<!-- -->
|
||||||
<td class="w-48 max-w-64 min-w-24 text-left truncate">
|
<td class="w-48 max-w-64 min-w-24 text-left truncate">
|
||||||
@@ -126,7 +126,7 @@ export default {
|
|||||||
},
|
},
|
||||||
coverUrl(feed) {
|
coverUrl(feed) {
|
||||||
if (!feed.coverPath) return `${this.$config.routerBasePath}/Logo.png`
|
if (!feed.coverPath) return `${this.$config.routerBasePath}/Logo.png`
|
||||||
return `${feed.feedUrl}/cover`
|
return `${this.$config.routerBasePath}${feed.feedUrl}/cover`
|
||||||
},
|
},
|
||||||
async loadFeeds() {
|
async loadFeeds() {
|
||||||
const data = await this.$axios.$get(`/api/feeds`).catch((err) => {
|
const data = await this.$axios.$get(`/api/feeds`).catch((err) => {
|
||||||
|
|||||||
@@ -88,7 +88,7 @@
|
|||||||
<ui-dropdown v-model="itemsPerPage" :items="itemsPerPageOptions" small class="w-24 mx-2" @input="updatedItemsPerPage" />
|
<ui-dropdown v-model="itemsPerPage" :items="itemsPerPageOptions" small class="w-24 mx-2" @input="updatedItemsPerPage" />
|
||||||
</div>
|
</div>
|
||||||
<div class="inline-flex items-center">
|
<div class="inline-flex items-center">
|
||||||
<p class="text-sm mx-2">Page {{ currentPage + 1 }} of {{ numPages }}</p>
|
<p class="text-sm mx-2">{{ $getString('LabelPaginationPageXOfY', [currentPage + 1, numPages]) }}</p>
|
||||||
<ui-icon-btn icon="arrow_back_ios_new" :size="9" icon-font-size="1rem" class="mx-1" :disabled="currentPage === 0" @click="prevPage" />
|
<ui-icon-btn icon="arrow_back_ios_new" :size="9" icon-font-size="1rem" class="mx-1" :disabled="currentPage === 0" @click="prevPage" />
|
||||||
<ui-icon-btn icon="arrow_forward_ios" :size="9" icon-font-size="1rem" class="mx-1" :disabled="currentPage >= numPages - 1" @click="nextPage" />
|
<ui-icon-btn icon="arrow_forward_ios" :size="9" icon-font-size="1rem" class="mx-1" :disabled="currentPage >= numPages - 1" @click="nextPage" />
|
||||||
</div>
|
</div>
|
||||||
@@ -103,7 +103,7 @@
|
|||||||
<div v-if="openListeningSessions.length" class="w-full my-8 h-px bg-white/10" />
|
<div v-if="openListeningSessions.length" class="w-full my-8 h-px bg-white/10" />
|
||||||
|
|
||||||
<!-- open listening sessions table -->
|
<!-- open listening sessions table -->
|
||||||
<p v-if="openListeningSessions.length" class="text-lg my-4">Open Listening Sessions</p>
|
<p v-if="openListeningSessions.length" class="text-lg my-4">{{ $strings.HeaderOpenListeningSessions }}</p>
|
||||||
<div v-if="openListeningSessions.length" class="block max-w-full">
|
<div v-if="openListeningSessions.length" class="block max-w-full">
|
||||||
<table class="userSessionsTable">
|
<table class="userSessionsTable">
|
||||||
<tr class="bg-primary bg-opacity-40">
|
<tr class="bg-primary bg-opacity-40">
|
||||||
|
|||||||
@@ -14,7 +14,7 @@
|
|||||||
<h1 class="text-xl pl-2">{{ username }}</h1>
|
<h1 class="text-xl pl-2">{{ username }}</h1>
|
||||||
</div>
|
</div>
|
||||||
<div v-if="userToken" class="flex text-xs mt-4">
|
<div v-if="userToken" class="flex text-xs mt-4">
|
||||||
<ui-text-input-with-label label="API Token" :value="userToken" readonly />
|
<ui-text-input-with-label :label="$strings.LabelApiToken" :value="userToken" readonly />
|
||||||
|
|
||||||
<div class="px-1 mt-8 cursor-pointer" @click="copyToClipboard(userToken)">
|
<div class="px-1 mt-8 cursor-pointer" @click="copyToClipboard(userToken)">
|
||||||
<span class="material-symbols pl-2 text-base">content_copy</span>
|
<span class="material-symbols pl-2 text-base">content_copy</span>
|
||||||
|
|||||||
@@ -54,7 +54,7 @@
|
|||||||
</table>
|
</table>
|
||||||
<div class="flex items-center justify-end py-1">
|
<div class="flex items-center justify-end py-1">
|
||||||
<ui-icon-btn icon="arrow_back_ios_new" :size="7" icon-font-size="1rem" class="mx-1" :disabled="currentPage === 0" @click="prevPage" />
|
<ui-icon-btn icon="arrow_back_ios_new" :size="7" icon-font-size="1rem" class="mx-1" :disabled="currentPage === 0" @click="prevPage" />
|
||||||
<p class="text-sm mx-1">Page {{ currentPage + 1 }} of {{ numPages }}</p>
|
<p class="text-sm mx-1">{{ $getString('LabelPaginationPageXOfY', [currentPage + 1, numPages]) }}</p>
|
||||||
<ui-icon-btn icon="arrow_forward_ios" :size="7" icon-font-size="1rem" class="mx-1" :disabled="currentPage >= numPages - 1" @click="nextPage" />
|
<ui-icon-btn icon="arrow_forward_ios" :size="7" icon-font-size="1rem" class="mx-1" :disabled="currentPage >= numPages - 1" @click="nextPage" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -2,6 +2,10 @@
|
|||||||
<div>
|
<div>
|
||||||
<app-settings-content :header-text="$strings.HeaderUsers">
|
<app-settings-content :header-text="$strings.HeaderUsers">
|
||||||
<template #header-items>
|
<template #header-items>
|
||||||
|
<div v-if="numUsers" class="mx-2 px-1.5 rounded-lg bg-primary/50 text-gray-300/90 text-sm inline-flex items-center justify-center">
|
||||||
|
<span>{{ numUsers }}</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
<ui-tooltip :text="$strings.LabelClickForMoreInfo" class="inline-flex ml-2">
|
<ui-tooltip :text="$strings.LabelClickForMoreInfo" class="inline-flex ml-2">
|
||||||
<a href="https://www.audiobookshelf.org/guides/users" target="_blank" class="inline-flex">
|
<a href="https://www.audiobookshelf.org/guides/users" target="_blank" class="inline-flex">
|
||||||
<span class="material-symbols text-xl w-5 text-gray-200">help_outline</span>
|
<span class="material-symbols text-xl w-5 text-gray-200">help_outline</span>
|
||||||
@@ -13,7 +17,7 @@
|
|||||||
<ui-btn color="primary" small @click="setShowUserModal()">{{ $strings.ButtonAddUser }}</ui-btn>
|
<ui-btn color="primary" small @click="setShowUserModal()">{{ $strings.ButtonAddUser }}</ui-btn>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<tables-users-table class="pt-2" @edit="setShowUserModal" />
|
<tables-users-table class="pt-2" @edit="setShowUserModal" @numUsers="(count) => (numUsers = count)" />
|
||||||
</app-settings-content>
|
</app-settings-content>
|
||||||
<modals-account-modal ref="accountModal" v-model="showAccountModal" :account="selectedAccount" />
|
<modals-account-modal ref="accountModal" v-model="showAccountModal" :account="selectedAccount" />
|
||||||
</div>
|
</div>
|
||||||
@@ -29,7 +33,8 @@ export default {
|
|||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
selectedAccount: null,
|
selectedAccount: null,
|
||||||
showAccountModal: false
|
showAccountModal: false,
|
||||||
|
numUsers: 0
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
computed: {},
|
computed: {},
|
||||||
|
|||||||
@@ -12,12 +12,12 @@
|
|||||||
<!-- Item Cover Overlay -->
|
<!-- Item Cover Overlay -->
|
||||||
<div class="absolute top-0 left-0 w-full h-full z-10 opacity-0 group-hover:opacity-100 pointer-events-none">
|
<div class="absolute top-0 left-0 w-full h-full z-10 opacity-0 group-hover:opacity-100 pointer-events-none">
|
||||||
<div v-show="showPlayButton && !isStreaming" class="h-full flex items-center justify-center pointer-events-none">
|
<div v-show="showPlayButton && !isStreaming" class="h-full flex items-center justify-center pointer-events-none">
|
||||||
<div class="hover:text-white text-gray-200 hover:scale-110 transform duration-200 pointer-events-auto cursor-pointer" @click.stop.prevent="playItem">
|
<button class="hover:text-white text-gray-200 hover:scale-110 transform duration-200 pointer-events-auto cursor-pointer" :aria-label="$strings.ButtonPlay" @click.stop.prevent="playItem">
|
||||||
<span class="material-symbols fill text-4xl">play_arrow</span>
|
<span class="material-symbols fill text-4xl">play_arrow</span>
|
||||||
</div>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<span class="absolute bottom-2.5 right-2.5 z-10 material-symbols text-lg cursor-pointer text-white text-opacity-75 hover:text-opacity-100 hover:scale-110 transform duration-200 pointer-events-auto" @click="showEditCover">edit</span>
|
<button class="absolute bottom-2.5 right-2.5 z-10 material-symbols text-lg cursor-pointer text-white text-opacity-75 hover:text-opacity-100 hover:scale-110 transform duration-200 pointer-events-auto" :aria-label="$strings.ButtonEdit" @click="showEditCover">edit</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -87,7 +87,7 @@
|
|||||||
</ui-btn>
|
</ui-btn>
|
||||||
|
|
||||||
<ui-btn v-else-if="isMissing || isInvalid" color="error" :padding-x="4" small class="flex items-center h-9 mr-2">
|
<ui-btn v-else-if="isMissing || isInvalid" color="error" :padding-x="4" small class="flex items-center h-9 mr-2">
|
||||||
<span v-show="!isStreaming" class="material-symbols text-2xl -ml-2 pr-1 text-white">error</span>
|
<span class="material-symbols text-2xl -ml-2 pr-1 text-white">error</span>
|
||||||
{{ isMissing ? $strings.LabelMissing : $strings.LabelIncomplete }}
|
{{ isMissing ? $strings.LabelMissing : $strings.LabelIncomplete }}
|
||||||
</ui-btn>
|
</ui-btn>
|
||||||
|
|
||||||
@@ -96,12 +96,12 @@
|
|||||||
</ui-tooltip>
|
</ui-tooltip>
|
||||||
|
|
||||||
<ui-btn v-if="showReadButton" color="info" :padding-x="4" small class="flex items-center h-9 mr-2" @click="openEbook">
|
<ui-btn v-if="showReadButton" color="info" :padding-x="4" small class="flex items-center h-9 mr-2" @click="openEbook">
|
||||||
<span class="material-symbols text-2xl -ml-2 pr-2 text-white">auto_stories</span>
|
<span class="material-symbols text-2xl -ml-2 pr-2 text-white" aria-hidden="true">auto_stories</span>
|
||||||
{{ $strings.ButtonRead }}
|
{{ $strings.ButtonRead }}
|
||||||
</ui-btn>
|
</ui-btn>
|
||||||
|
|
||||||
<ui-tooltip v-if="userCanUpdate" :text="$strings.LabelEdit" direction="top">
|
<ui-tooltip v-if="userCanUpdate" :text="$strings.LabelEdit" direction="top">
|
||||||
<ui-icon-btn icon="" outlined class="mx-0.5" @click="editClick" />
|
<ui-icon-btn icon="" outlined class="mx-0.5" :aria-label="$strings.LabelEdit" @click="editClick" />
|
||||||
</ui-tooltip>
|
</ui-tooltip>
|
||||||
|
|
||||||
<ui-tooltip v-if="!isPodcast" :text="userIsFinished ? $strings.MessageMarkAsNotFinished : $strings.MessageMarkAsFinished" direction="top">
|
<ui-tooltip v-if="!isPodcast" :text="userIsFinished ? $strings.MessageMarkAsNotFinished : $strings.MessageMarkAsFinished" direction="top">
|
||||||
@@ -110,12 +110,12 @@
|
|||||||
|
|
||||||
<!-- Only admin or root user can download new episodes -->
|
<!-- Only admin or root user can download new episodes -->
|
||||||
<ui-tooltip v-if="isPodcast && userIsAdminOrUp" :text="$strings.LabelFindEpisodes" direction="top">
|
<ui-tooltip v-if="isPodcast && userIsAdminOrUp" :text="$strings.LabelFindEpisodes" direction="top">
|
||||||
<ui-icon-btn icon="search" class="mx-0.5" :loading="fetchingRSSFeed" outlined @click="findEpisodesClick" />
|
<ui-icon-btn icon="search" class="mx-0.5" :aria-label="$strings.LabelFindEpisodes" :loading="fetchingRSSFeed" outlined @click="findEpisodesClick" />
|
||||||
</ui-tooltip>
|
</ui-tooltip>
|
||||||
|
|
||||||
<ui-context-menu-dropdown v-if="contextMenuItems.length" :items="contextMenuItems" :menu-width="148" @action="contextMenuAction">
|
<ui-context-menu-dropdown v-if="contextMenuItems.length" :items="contextMenuItems" :menu-width="148" @action="contextMenuAction">
|
||||||
<template #default="{ showMenu, clickShowMenu, disabled }">
|
<template #default="{ showMenu, clickShowMenu, disabled }">
|
||||||
<button type="button" :disabled="disabled" class="mx-0.5 icon-btn bg-primary border border-gray-600 w-9 h-9 rounded-md flex items-center justify-center relative" aria-haspopup="listbox" :aria-expanded="showMenu" @click.stop.prevent="clickShowMenu">
|
<button type="button" :disabled="disabled" class="mx-0.5 icon-btn bg-primary border border-gray-600 w-9 h-9 rounded-md flex items-center justify-center relative" aria-haspopup="listbox" :aria-expanded="showMenu" :aria-label="$strings.LabelMore" @click.stop.prevent="clickShowMenu">
|
||||||
<span class="material-symbols text-2xl"></span>
|
<span class="material-symbols text-2xl"></span>
|
||||||
</button>
|
</button>
|
||||||
</template>
|
</template>
|
||||||
@@ -638,6 +638,11 @@ export default {
|
|||||||
this.episodesDownloading = this.episodesDownloading.filter((d) => d.id !== episodeDownload.id)
|
this.episodesDownloading = this.episodesDownloading.filter((d) => d.id !== episodeDownload.id)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
episodeDownloadQueueCleared(libraryItemId) {
|
||||||
|
if (libraryItemId === this.libraryItemId) {
|
||||||
|
this.episodeDownloadsQueued = []
|
||||||
|
}
|
||||||
|
},
|
||||||
rssFeedOpen(data) {
|
rssFeedOpen(data) {
|
||||||
if (data.entityId === this.libraryItemId) {
|
if (data.entityId === this.libraryItemId) {
|
||||||
console.log('RSS Feed Opened', data)
|
console.log('RSS Feed Opened', data)
|
||||||
@@ -776,6 +781,7 @@ export default {
|
|||||||
this.$root.socket.on('episode_download_queued', this.episodeDownloadQueued)
|
this.$root.socket.on('episode_download_queued', this.episodeDownloadQueued)
|
||||||
this.$root.socket.on('episode_download_started', this.episodeDownloadStarted)
|
this.$root.socket.on('episode_download_started', this.episodeDownloadStarted)
|
||||||
this.$root.socket.on('episode_download_finished', this.episodeDownloadFinished)
|
this.$root.socket.on('episode_download_finished', this.episodeDownloadFinished)
|
||||||
|
this.$root.socket.on('episode_download_queue_cleared', this.episodeDownloadQueueCleared)
|
||||||
},
|
},
|
||||||
beforeDestroy() {
|
beforeDestroy() {
|
||||||
this.$eventBus.$off(`${this.libraryItem.id}_updated`, this.libraryItemUpdated)
|
this.$eventBus.$off(`${this.libraryItem.id}_updated`, this.libraryItemUpdated)
|
||||||
@@ -787,6 +793,7 @@ export default {
|
|||||||
this.$root.socket.off('episode_download_queued', this.episodeDownloadQueued)
|
this.$root.socket.off('episode_download_queued', this.episodeDownloadQueued)
|
||||||
this.$root.socket.off('episode_download_started', this.episodeDownloadStarted)
|
this.$root.socket.off('episode_download_started', this.episodeDownloadStarted)
|
||||||
this.$root.socket.off('episode_download_finished', this.episodeDownloadFinished)
|
this.$root.socket.off('episode_download_finished', this.episodeDownloadFinished)
|
||||||
|
this.$root.socket.off('episode_download_queue_cleared', this.episodeDownloadQueueCleared)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -120,7 +120,7 @@ export default {
|
|||||||
})
|
})
|
||||||
.catch((error) => {
|
.catch((error) => {
|
||||||
console.error('Failed to updated narrator', error)
|
console.error('Failed to updated narrator', error)
|
||||||
this.$toast.error('Failed to update narrator')
|
this.$toast.error(this.$strings.ToastFailedToUpdate)
|
||||||
this.loading = false
|
this.loading = false
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -104,9 +104,6 @@ export default {
|
|||||||
this.episodesDownloading = this.episodesDownloading.filter((d) => d.id !== episodeDownload.id)
|
this.episodesDownloading = this.episodesDownloading.filter((d) => d.id !== episodeDownload.id)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
episodeDownloadQueueUpdated(downloadQueueDetails) {
|
|
||||||
this.episodeDownloadsQueued = downloadQueueDetails.queue.filter((q) => q.libraryId == this.libraryId)
|
|
||||||
},
|
|
||||||
async loadInitialDownloadQueue() {
|
async loadInitialDownloadQueue() {
|
||||||
this.processing = true
|
this.processing = true
|
||||||
const queuePayload = await this.$axios.$get(`/api/libraries/${this.libraryId}/episode-downloads`).catch((error) => {
|
const queuePayload = await this.$axios.$get(`/api/libraries/${this.libraryId}/episode-downloads`).catch((error) => {
|
||||||
@@ -128,7 +125,6 @@ export default {
|
|||||||
this.$root.socket.on('episode_download_queued', this.episodeDownloadQueued)
|
this.$root.socket.on('episode_download_queued', this.episodeDownloadQueued)
|
||||||
this.$root.socket.on('episode_download_started', this.episodeDownloadStarted)
|
this.$root.socket.on('episode_download_started', this.episodeDownloadStarted)
|
||||||
this.$root.socket.on('episode_download_finished', this.episodeDownloadFinished)
|
this.$root.socket.on('episode_download_finished', this.episodeDownloadFinished)
|
||||||
this.$root.socket.on('episode_download_queue_updated', this.episodeDownloadQueueUpdated)
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
mounted() {
|
mounted() {
|
||||||
@@ -138,7 +134,6 @@ export default {
|
|||||||
this.$root.socket.off('episode_download_queued', this.episodeDownloadQueued)
|
this.$root.socket.off('episode_download_queued', this.episodeDownloadQueued)
|
||||||
this.$root.socket.off('episode_download_started', this.episodeDownloadStarted)
|
this.$root.socket.off('episode_download_started', this.episodeDownloadStarted)
|
||||||
this.$root.socket.off('episode_download_finished', this.episodeDownloadFinished)
|
this.$root.socket.off('episode_download_finished', this.episodeDownloadFinished)
|
||||||
this.$root.socket.off('episode_download_queue_updated', this.episodeDownloadQueueUpdated)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -10,8 +10,12 @@
|
|||||||
<p v-if="mediaItemShare.playbackSession.displayAuthor" class="text-lg lg:text-xl text-slate-400 font-semibold text-center mb-1 truncate">{{ mediaItemShare.playbackSession.displayAuthor }}</p>
|
<p v-if="mediaItemShare.playbackSession.displayAuthor" class="text-lg lg:text-xl text-slate-400 font-semibold text-center mb-1 truncate">{{ mediaItemShare.playbackSession.displayAuthor }}</p>
|
||||||
|
|
||||||
<div class="w-full pt-16">
|
<div class="w-full pt-16">
|
||||||
<player-ui ref="audioPlayer" :chapters="chapters" :paused="isPaused" :loading="!hasLoaded" :is-podcast="false" hide-bookmarks hide-sleep-timer @playPause="playPause" @jumpForward="jumpForward" @jumpBackward="jumpBackward" @setVolume="setVolume" @setPlaybackRate="setPlaybackRate" @seek="seek" />
|
<player-ui ref="audioPlayer" :chapters="chapters" :current-chapter="currentChapter" :paused="isPaused" :loading="!hasLoaded" :is-podcast="false" hide-bookmarks hide-sleep-timer @playPause="playPause" @jumpForward="jumpForward" @jumpBackward="jumpBackward" @setVolume="setVolume" @setPlaybackRate="setPlaybackRate" @seek="seek" />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<ui-tooltip v-if="mediaItemShare.isDownloadable" direction="bottom" :text="$strings.LabelDownload" class="absolute top-0 left-0 m-4">
|
||||||
|
<button aria-label="Download" class="text-gray-300 hover:text-white" @click="downloadShareItem"><span class="material-symbols text-2xl sm:text-3xl">download</span></button>
|
||||||
|
</ui-tooltip>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -51,7 +55,8 @@ export default {
|
|||||||
windowHeight: 0,
|
windowHeight: 0,
|
||||||
listeningTimeSinceSync: 0,
|
listeningTimeSinceSync: 0,
|
||||||
coverRgb: null,
|
coverRgb: null,
|
||||||
coverBgIsLight: false
|
coverBgIsLight: false,
|
||||||
|
currentTime: 0
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
@@ -60,16 +65,13 @@ export default {
|
|||||||
},
|
},
|
||||||
coverUrl() {
|
coverUrl() {
|
||||||
if (!this.playbackSession.coverPath) return `${this.$config.routerBasePath}/book_placeholder.jpg`
|
if (!this.playbackSession.coverPath) return `${this.$config.routerBasePath}/book_placeholder.jpg`
|
||||||
if (process.env.NODE_ENV === 'development') {
|
return `${this.$config.routerBasePath}/public/share/${this.mediaItemShare.slug}/cover`
|
||||||
return `http://localhost:3333/public/share/${this.mediaItemShare.slug}/cover`
|
},
|
||||||
}
|
downloadUrl() {
|
||||||
return `/public/share/${this.mediaItemShare.slug}/cover`
|
return `${process.env.serverUrl}/public/share/${this.mediaItemShare.slug}/download`
|
||||||
},
|
},
|
||||||
audioTracks() {
|
audioTracks() {
|
||||||
return (this.playbackSession.audioTracks || []).map((track) => {
|
return (this.playbackSession.audioTracks || []).map((track) => {
|
||||||
if (process.env.NODE_ENV === 'development') {
|
|
||||||
track.contentUrl = `${process.env.serverUrl}${track.contentUrl}`
|
|
||||||
}
|
|
||||||
track.relativeContentUrl = track.contentUrl
|
track.relativeContentUrl = track.contentUrl
|
||||||
return track
|
return track
|
||||||
})
|
})
|
||||||
@@ -83,6 +85,9 @@ export default {
|
|||||||
chapters() {
|
chapters() {
|
||||||
return this.playbackSession.chapters || []
|
return this.playbackSession.chapters || []
|
||||||
},
|
},
|
||||||
|
currentChapter() {
|
||||||
|
return this.chapters.find((chapter) => chapter.start <= this.currentTime && this.currentTime < chapter.end)
|
||||||
|
},
|
||||||
coverAspectRatio() {
|
coverAspectRatio() {
|
||||||
const coverAspectRatio = this.playbackSession.coverAspectRatio
|
const coverAspectRatio = this.playbackSession.coverAspectRatio
|
||||||
return coverAspectRatio === this.$constants.BookCoverAspectRatio.STANDARD ? 1.6 : 1
|
return coverAspectRatio === this.$constants.BookCoverAspectRatio.STANDARD ? 1.6 : 1
|
||||||
@@ -128,12 +133,14 @@ export default {
|
|||||||
if (!this.localAudioPlayer || !this.hasLoaded) return
|
if (!this.localAudioPlayer || !this.hasLoaded) return
|
||||||
const currentTime = this.localAudioPlayer.getCurrentTime()
|
const currentTime = this.localAudioPlayer.getCurrentTime()
|
||||||
const duration = this.localAudioPlayer.getDuration()
|
const duration = this.localAudioPlayer.getDuration()
|
||||||
this.seek(Math.min(currentTime + 10, duration))
|
const jumpForwardAmount = this.$store.getters['user/getUserSetting']('jumpForwardAmount') || 10
|
||||||
|
this.seek(Math.min(currentTime + jumpForwardAmount, duration))
|
||||||
},
|
},
|
||||||
jumpBackward() {
|
jumpBackward() {
|
||||||
if (!this.localAudioPlayer || !this.hasLoaded) return
|
if (!this.localAudioPlayer || !this.hasLoaded) return
|
||||||
const currentTime = this.localAudioPlayer.getCurrentTime()
|
const currentTime = this.localAudioPlayer.getCurrentTime()
|
||||||
this.seek(Math.max(currentTime - 10, 0))
|
const jumpBackwardAmount = this.$store.getters['user/getUserSetting']('jumpBackwardAmount') || 10
|
||||||
|
this.seek(Math.max(currentTime - jumpBackwardAmount, 0))
|
||||||
},
|
},
|
||||||
setVolume(volume) {
|
setVolume(volume) {
|
||||||
if (!this.localAudioPlayer || !this.hasLoaded) return
|
if (!this.localAudioPlayer || !this.hasLoaded) return
|
||||||
@@ -154,6 +161,7 @@ export default {
|
|||||||
|
|
||||||
// Update UI
|
// Update UI
|
||||||
this.$refs.audioPlayer.setCurrentTime(time)
|
this.$refs.audioPlayer.setCurrentTime(time)
|
||||||
|
this.currentTime = time
|
||||||
},
|
},
|
||||||
setDuration() {
|
setDuration() {
|
||||||
if (!this.localAudioPlayer) return
|
if (!this.localAudioPlayer) return
|
||||||
@@ -246,9 +254,14 @@ export default {
|
|||||||
},
|
},
|
||||||
playerFinished() {
|
playerFinished() {
|
||||||
console.log('Player finished')
|
console.log('Player finished')
|
||||||
|
},
|
||||||
|
downloadShareItem() {
|
||||||
|
this.$downloadFile(this.downloadUrl)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
mounted() {
|
mounted() {
|
||||||
|
this.$store.dispatch('user/loadUserSettings')
|
||||||
|
|
||||||
this.resize()
|
this.resize()
|
||||||
window.addEventListener('resize', this.resize)
|
window.addEventListener('resize', this.resize)
|
||||||
window.addEventListener('keydown', this.keyDown)
|
window.addEventListener('keydown', this.keyDown)
|
||||||
|
|||||||
@@ -1,20 +1,20 @@
|
|||||||
<template>
|
<template>
|
||||||
<div id="page-wrapper" class="page p-0 sm:p-6 overflow-y-auto" :class="streamLibraryItem ? 'streaming' : ''">
|
<div id="page-wrapper" class="page p-1 sm:p-6 overflow-y-auto" :class="streamLibraryItem ? 'streaming' : ''">
|
||||||
<div class="w-full max-w-6xl mx-auto">
|
<div class="w-full max-w-6xl mx-auto">
|
||||||
<!-- Library & folder picker -->
|
<!-- Library & folder picker -->
|
||||||
<div class="flex my-6 -mx-2">
|
<div class="flex flex-wrap my-6 md:-mx-2">
|
||||||
<div class="w-1/5 px-2">
|
<div class="w-full md:w-1/5 px-2">
|
||||||
<ui-dropdown v-model="selectedLibraryId" :items="libraryItems" :label="$strings.LabelLibrary" :disabled="!!items.length" @input="libraryChanged" />
|
<ui-dropdown v-model="selectedLibraryId" :items="libraryItems" :label="$strings.LabelLibrary" :disabled="!!items.length" @input="libraryChanged" />
|
||||||
</div>
|
</div>
|
||||||
<div class="w-3/5 px-2">
|
<div class="w-full md:w-3/5 px-2">
|
||||||
<ui-dropdown v-model="selectedFolderId" :items="folderItems" :disabled="!selectedLibraryId || !!items.length" :label="$strings.LabelFolder" />
|
<ui-dropdown v-model="selectedFolderId" :items="folderItems" :disabled="!selectedLibraryId || !!items.length" :label="$strings.LabelFolder" />
|
||||||
</div>
|
</div>
|
||||||
<div class="w-1/5 px-2">
|
<div class="w-full md:w-1/5 px-2">
|
||||||
<ui-text-input-with-label :value="selectedLibraryMediaType" readonly :label="$strings.LabelMediaType" />
|
<ui-text-input-with-label :value="selectedLibraryMediaType" readonly :label="$strings.LabelMediaType" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div v-if="!selectedLibraryIsPodcast" class="flex items-center mb-6">
|
<div v-if="!selectedLibraryIsPodcast" class="flex items-center mb-6 px-2 md:px-0">
|
||||||
<label class="flex cursor-pointer pt-4">
|
<label class="flex cursor-pointer pt-4">
|
||||||
<ui-toggle-switch v-model="fetchMetadata.enabled" class="inline-flex" />
|
<ui-toggle-switch v-model="fetchMetadata.enabled" class="inline-flex" />
|
||||||
<span class="pl-2 text-base">{{ $strings.LabelAutoFetchMetadata }}</span>
|
<span class="pl-2 text-base">{{ $strings.LabelAutoFetchMetadata }}</span>
|
||||||
@@ -33,13 +33,13 @@
|
|||||||
</widgets-alert>
|
</widgets-alert>
|
||||||
|
|
||||||
<!-- Picker display -->
|
<!-- Picker display -->
|
||||||
<div v-if="!items.length && !ignoredFiles.length" class="w-full mx-auto border border-white border-opacity-20 px-12 pt-12 pb-4 my-12 relative" :class="isDragging ? 'bg-primary bg-opacity-40' : 'border-dashed'">
|
<div v-if="!items.length && !ignoredFiles.length" class="w-full mx-auto border border-white border-opacity-20 px-4 md:px-12 pt-12 pb-4 my-12 relative" :class="isDragging ? 'bg-primary bg-opacity-40' : 'border-dashed'">
|
||||||
<p class="text-2xl text-center">{{ isDragging ? $strings.LabelUploaderDropFiles : $strings.LabelUploaderDragAndDrop }}</p>
|
<p class="text-2xl text-center">{{ isDragging ? $strings.LabelUploaderDropFiles : isIOS ? $strings.LabelUploaderDragAndDropFilesOnly : $strings.LabelUploaderDragAndDrop }}</p>
|
||||||
<p class="text-center text-sm my-5">{{ $strings.MessageOr }}</p>
|
<p class="text-center text-sm my-5">{{ $strings.MessageOr }}</p>
|
||||||
<div class="w-full max-w-xl mx-auto">
|
<div class="w-full max-w-xl mx-auto">
|
||||||
<div class="flex">
|
<div class="flex">
|
||||||
<ui-btn class="w-full mx-1" @click="openFilePicker">{{ $strings.ButtonChooseFiles }}</ui-btn>
|
<ui-btn class="w-full mx-1" @click="openFilePicker">{{ $strings.ButtonChooseFiles }}</ui-btn>
|
||||||
<ui-btn class="w-full mx-1" @click="openFolderPicker">{{ $strings.ButtonChooseAFolder }}</ui-btn>
|
<ui-btn v-if="!isIOS" class="w-full mx-1" @click="openFolderPicker">{{ $strings.ButtonChooseAFolder }} </ui-btn>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="pt-8 text-center">
|
<div class="pt-8 text-center">
|
||||||
@@ -48,7 +48,7 @@
|
|||||||
</p>
|
</p>
|
||||||
|
|
||||||
<p class="text-sm text-white text-opacity-70">
|
<p class="text-sm text-white text-opacity-70">
|
||||||
{{ $strings.NoteUploaderFoldersWithMediaFiles }} <span v-if="selectedLibraryMediaType === 'book'">{{ $strings.NoteUploaderOnlyAudioFiles }}</span>
|
<span v-if="!isIOS">{{ $strings.NoteUploaderFoldersWithMediaFiles }}</span> <span v-if="selectedLibraryMediaType === 'book'">{{ $strings.NoteUploaderOnlyAudioFiles }}</span>
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -84,8 +84,8 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<input ref="fileInput" type="file" multiple :accept="inputAccept" class="hidden" @change="inputChanged" />
|
<input ref="fileInput" type="file" multiple :accept="isIOS ? '' : inputAccept" class="hidden" @change="inputChanged" />
|
||||||
<input ref="fileFolderInput" type="file" webkitdirectory multiple :accept="inputAccept" class="hidden" @change="inputChanged" />
|
<input ref="fileFolderInput" type="file" webkitdirectory multiple :accept="inputAccept" class="hidden" @change="inputChanged" v-if="!isIOS" />
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
@@ -127,6 +127,10 @@ export default {
|
|||||||
})
|
})
|
||||||
return extensions
|
return extensions
|
||||||
},
|
},
|
||||||
|
isIOS() {
|
||||||
|
const ua = window.navigator.userAgent
|
||||||
|
return /iPad|iPhone|iPod/.test(ua) && !window.MSStream
|
||||||
|
},
|
||||||
streamLibraryItem() {
|
streamLibraryItem() {
|
||||||
return this.$store.state.streamLibraryItem
|
return this.$store.state.streamLibraryItem
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -23,10 +23,6 @@ export default class AudioTrack {
|
|||||||
get relativeContentUrl() {
|
get relativeContentUrl() {
|
||||||
if (!this.contentUrl || this.contentUrl.startsWith('http')) return this.contentUrl
|
if (!this.contentUrl || this.contentUrl.startsWith('http')) return this.contentUrl
|
||||||
|
|
||||||
if (process.env.NODE_ENV === 'development') {
|
|
||||||
return `${process.env.serverUrl}${this.contentUrl}?token=${this.userToken}`
|
|
||||||
}
|
|
||||||
|
|
||||||
return this.contentUrl + `?token=${this.userToken}`
|
return this.contentUrl + `?token=${this.userToken}`
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -147,7 +147,7 @@ export default class LocalAudioPlayer extends EventEmitter {
|
|||||||
timeoutRetry: {
|
timeoutRetry: {
|
||||||
maxNumRetry: 4,
|
maxNumRetry: 4,
|
||||||
retryDelayMs: 0,
|
retryDelayMs: 0,
|
||||||
maxRetryDelayMs: 0,
|
maxRetryDelayMs: 0
|
||||||
},
|
},
|
||||||
errorRetry: {
|
errorRetry: {
|
||||||
maxNumRetry: 8,
|
maxNumRetry: 8,
|
||||||
@@ -160,7 +160,7 @@ export default class LocalAudioPlayer extends EventEmitter {
|
|||||||
}
|
}
|
||||||
return retry
|
return retry
|
||||||
}
|
}
|
||||||
},
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -194,7 +194,7 @@ export default class LocalAudioPlayer extends EventEmitter {
|
|||||||
|
|
||||||
setDirectPlay() {
|
setDirectPlay() {
|
||||||
// Set initial track and track time offset
|
// Set initial track and track time offset
|
||||||
var trackIndex = this.audioTracks.findIndex(t => this.startTime >= t.startOffset && this.startTime < (t.startOffset + t.duration))
|
var trackIndex = this.audioTracks.findIndex((t) => this.startTime >= t.startOffset && this.startTime < t.startOffset + t.duration)
|
||||||
this.currentTrackIndex = trackIndex >= 0 ? trackIndex : 0
|
this.currentTrackIndex = trackIndex >= 0 ? trackIndex : 0
|
||||||
|
|
||||||
this.loadCurrentTrack()
|
this.loadCurrentTrack()
|
||||||
@@ -270,7 +270,7 @@ export default class LocalAudioPlayer extends EventEmitter {
|
|||||||
// Seeking Direct play
|
// Seeking Direct play
|
||||||
if (time < this.currentTrack.startOffset || time > this.currentTrack.startOffset + this.currentTrack.duration) {
|
if (time < this.currentTrack.startOffset || time > this.currentTrack.startOffset + this.currentTrack.duration) {
|
||||||
// Change Track
|
// Change Track
|
||||||
var trackIndex = this.audioTracks.findIndex(t => time >= t.startOffset && time < (t.startOffset + t.duration))
|
var trackIndex = this.audioTracks.findIndex((t) => time >= t.startOffset && time < t.startOffset + t.duration)
|
||||||
if (trackIndex >= 0) {
|
if (trackIndex >= 0) {
|
||||||
this.startTime = time
|
this.startTime = time
|
||||||
this.currentTrackIndex = trackIndex
|
this.currentTrackIndex = trackIndex
|
||||||
@@ -293,7 +293,6 @@ export default class LocalAudioPlayer extends EventEmitter {
|
|||||||
this.player.volume = volume
|
this.player.volume = volume
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// Utils
|
// Utils
|
||||||
isValidDuration(duration) {
|
isValidDuration(duration) {
|
||||||
if (duration && !isNaN(duration) && duration !== Number.POSITIVE_INFINITY && duration !== Number.NEGATIVE_INFINITY) {
|
if (duration && !isNaN(duration) && duration !== Number.POSITIVE_INFINITY && duration !== Number.NEGATIVE_INFINITY) {
|
||||||
|
|||||||
@@ -297,7 +297,6 @@ export default class PlayerHandler {
|
|||||||
if (listeningTimeToAdd > 20) {
|
if (listeningTimeToAdd > 20) {
|
||||||
syncData = {
|
syncData = {
|
||||||
timeListened: listeningTimeToAdd,
|
timeListened: listeningTimeToAdd,
|
||||||
duration: this.getDuration(),
|
|
||||||
currentTime: this.getCurrentTime()
|
currentTime: this.getCurrentTime()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -317,7 +316,6 @@ export default class PlayerHandler {
|
|||||||
const listeningTimeToAdd = Math.max(0, Math.floor(this.listeningTimeSinceSync))
|
const listeningTimeToAdd = Math.max(0, Math.floor(this.listeningTimeSinceSync))
|
||||||
const syncData = {
|
const syncData = {
|
||||||
timeListened: listeningTimeToAdd,
|
timeListened: listeningTimeToAdd,
|
||||||
duration: this.getDuration(),
|
|
||||||
currentTime
|
currentTime
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
export default function ({ $axios, store, $config }) {
|
export default function ({ $axios, store, $config }) {
|
||||||
$axios.onRequest(config => {
|
$axios.onRequest((config) => {
|
||||||
if (!config.url) {
|
if (!config.url) {
|
||||||
console.error('Axios request invalid config', config)
|
console.error('Axios request invalid config', config)
|
||||||
return
|
return
|
||||||
@@ -13,12 +13,11 @@ export default function ({ $axios, store, $config }) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (process.env.NODE_ENV === 'development') {
|
if (process.env.NODE_ENV === 'development') {
|
||||||
config.url = `/dev${config.url}`
|
|
||||||
console.log('Making request to ' + config.url)
|
console.log('Making request to ' + config.url)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
$axios.onError(error => {
|
$axios.onError((error) => {
|
||||||
const code = parseInt(error.response && error.response.status)
|
const code = parseInt(error.response && error.response.status)
|
||||||
const message = error.response ? error.response.data || 'Unknown Error' : 'Unknown Error'
|
const message = error.response ? error.response.data || 'Unknown Error' : 'Unknown Error'
|
||||||
console.error('Axios error', code, message)
|
console.error('Axios error', code, message)
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
const SupportedFileTypes = {
|
const SupportedFileTypes = {
|
||||||
image: ['png', 'jpg', 'jpeg', 'webp'],
|
image: ['png', 'jpg', 'jpeg', 'webp'],
|
||||||
audio: ['m4b', 'mp3', 'm4a', 'flac', 'opus', 'ogg', 'oga', 'mp4', 'aac', 'wma', 'aiff', 'wav', 'webm', 'webma', 'mka', 'awb', 'caf'],
|
audio: ['m4b', 'mp3', 'm4a', 'flac', 'opus', 'ogg', 'oga', 'mp4', 'aac', 'wma', 'aiff', 'wav', 'webm', 'webma', 'mka', 'awb', 'caf', 'mpeg', 'mpg'],
|
||||||
ebook: ['epub', 'pdf', 'mobi', 'azw3', 'cbr', 'cbz'],
|
ebook: ['epub', 'pdf', 'mobi', 'azw3', 'cbr', 'cbz'],
|
||||||
info: ['nfo'],
|
info: ['nfo'],
|
||||||
text: ['txt'],
|
text: ['txt'],
|
||||||
@@ -81,9 +81,7 @@ const Hotkeys = {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export {
|
export { Constants }
|
||||||
Constants
|
|
||||||
}
|
|
||||||
export default ({ app }, inject) => {
|
export default ({ app }, inject) => {
|
||||||
inject('constants', Constants)
|
inject('constants', Constants)
|
||||||
inject('keynames', KeyNames)
|
inject('keynames', KeyNames)
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ const defaultCode = 'en-us'
|
|||||||
const languageCodeMap = {
|
const languageCodeMap = {
|
||||||
bg: { label: 'ĐŅĐģĐŗĐ°ŅŅĐēи', dateFnsLocale: 'bg' },
|
bg: { label: 'ĐŅĐģĐŗĐ°ŅŅĐēи', dateFnsLocale: 'bg' },
|
||||||
bn: { label: 'āĻŦāĻžāĻāϞāĻž', dateFnsLocale: 'bn' },
|
bn: { label: 'āĻŦāĻžāĻāϞāĻž', dateFnsLocale: 'bn' },
|
||||||
|
ca: { label: 'Català ', dateFnsLocale: 'ca' },
|
||||||
cs: { label: 'ÄeÅĄtina', dateFnsLocale: 'cs' },
|
cs: { label: 'ÄeÅĄtina', dateFnsLocale: 'cs' },
|
||||||
da: { label: 'Dansk', dateFnsLocale: 'da' },
|
da: { label: 'Dansk', dateFnsLocale: 'da' },
|
||||||
de: { label: 'Deutsch', dateFnsLocale: 'de' },
|
de: { label: 'Deutsch', dateFnsLocale: 'de' },
|
||||||
@@ -41,6 +42,7 @@ Vue.prototype.$languageCodeOptions = Object.keys(languageCodeMap).map((code) =>
|
|||||||
|
|
||||||
// iTunes search API uses ISO 3166 country codes: https://en.wikipedia.org/wiki/ISO_3166-1_alpha-2
|
// iTunes search API uses ISO 3166 country codes: https://en.wikipedia.org/wiki/ISO_3166-1_alpha-2
|
||||||
const podcastSearchRegionMap = {
|
const podcastSearchRegionMap = {
|
||||||
|
au: { label: 'Australia' },
|
||||||
br: { label: 'Brasil' },
|
br: { label: 'Brasil' },
|
||||||
be: { label: 'BelgiÃĢ / Belgique / Belgien' },
|
be: { label: 'BelgiÃĢ / Belgique / Belgien' },
|
||||||
cz: { label: 'Äesko' },
|
cz: { label: 'Äesko' },
|
||||||
@@ -56,6 +58,7 @@ const podcastSearchRegionMap = {
|
|||||||
hu: { label: 'MagyarorszÃĄg' },
|
hu: { label: 'MagyarorszÃĄg' },
|
||||||
nl: { label: 'Nederland' },
|
nl: { label: 'Nederland' },
|
||||||
no: { label: 'Norge' },
|
no: { label: 'Norge' },
|
||||||
|
nz: { label: 'New Zealand' },
|
||||||
at: { label: 'Ãsterreich' },
|
at: { label: 'Ãsterreich' },
|
||||||
pl: { label: 'Polska' },
|
pl: { label: 'Polska' },
|
||||||
pt: { label: 'Portugal' },
|
pt: { label: 'Portugal' },
|
||||||
|
|||||||
+7
-17
@@ -72,13 +72,13 @@ export const state = () => ({
|
|||||||
}
|
}
|
||||||
],
|
],
|
||||||
podcastTypes: [
|
podcastTypes: [
|
||||||
{ text: 'Episodic', value: 'episodic' },
|
{ text: 'Episodic', value: 'episodic', descriptionKey: 'LabelEpisodic' },
|
||||||
{ text: 'Serial', value: 'serial' }
|
{ text: 'Serial', value: 'serial', descriptionKey: 'LabelSerial' }
|
||||||
],
|
],
|
||||||
episodeTypes: [
|
episodeTypes: [
|
||||||
{ text: 'Full', value: 'full' },
|
{ text: 'Full', value: 'full', descriptionKey: 'LabelFull' },
|
||||||
{ text: 'Trailer', value: 'trailer' },
|
{ text: 'Trailer', value: 'trailer', descriptionKey: 'LabelTrailer' },
|
||||||
{ text: 'Bonus', value: 'bonus' }
|
{ text: 'Bonus', value: 'bonus', descriptionKey: 'LabelBonus' }
|
||||||
],
|
],
|
||||||
libraryIcons: ['database', 'audiobookshelf', 'books-1', 'books-2', 'book-1', 'microphone-1', 'microphone-3', 'radio', 'podcast', 'rss', 'headphones', 'music', 'file-picture', 'rocket', 'power', 'star', 'heart']
|
libraryIcons: ['database', 'audiobookshelf', 'books-1', 'books-2', 'book-1', 'microphone-1', 'microphone-3', 'radio', 'podcast', 'rss', 'headphones', 'music', 'file-picture', 'rocket', 'power', 'star', 'heart']
|
||||||
})
|
})
|
||||||
@@ -98,13 +98,7 @@ export const getters = {
|
|||||||
const userToken = rootGetters['user/getToken']
|
const userToken = rootGetters['user/getToken']
|
||||||
const lastUpdate = libraryItem.updatedAt || Date.now()
|
const lastUpdate = libraryItem.updatedAt || Date.now()
|
||||||
const libraryItemId = libraryItem.libraryItemId || libraryItem.id // Workaround for /users/:id page showing media progress covers
|
const libraryItemId = libraryItem.libraryItemId || libraryItem.id // Workaround for /users/:id page showing media progress covers
|
||||||
|
return `${rootState.routerBasePath}/api/items/${libraryItemId}/cover?ts=${lastUpdate}${raw ? '&raw=1' : ''}`
|
||||||
if (process.env.NODE_ENV !== 'production') {
|
|
||||||
// Testing
|
|
||||||
return `http://localhost:3333${rootState.routerBasePath}/api/items/${libraryItemId}/cover?token=${userToken}&ts=${lastUpdate}${raw ? '&raw=1' : ''}`
|
|
||||||
}
|
|
||||||
|
|
||||||
return `${rootState.routerBasePath}/api/items/${libraryItemId}/cover?token=${userToken}&ts=${lastUpdate}${raw ? '&raw=1' : ''}`
|
|
||||||
},
|
},
|
||||||
getLibraryItemCoverSrcById:
|
getLibraryItemCoverSrcById:
|
||||||
(state, getters, rootState, rootGetters) =>
|
(state, getters, rootState, rootGetters) =>
|
||||||
@@ -112,11 +106,7 @@ export const getters = {
|
|||||||
const placeholder = `${rootState.routerBasePath}/book_placeholder.jpg`
|
const placeholder = `${rootState.routerBasePath}/book_placeholder.jpg`
|
||||||
if (!libraryItemId) return placeholder
|
if (!libraryItemId) return placeholder
|
||||||
const userToken = rootGetters['user/getToken']
|
const userToken = rootGetters['user/getToken']
|
||||||
if (process.env.NODE_ENV !== 'production') {
|
return `${rootState.routerBasePath}/api/items/${libraryItemId}/cover?${raw ? '&raw=1' : ''}${timestamp ? `&ts=${timestamp}` : ''}`
|
||||||
// Testing
|
|
||||||
return `http://localhost:3333${rootState.routerBasePath}/api/items/${libraryItemId}/cover?token=${userToken}${raw ? '&raw=1' : ''}${timestamp ? `&ts=${timestamp}` : ''}`
|
|
||||||
}
|
|
||||||
return `${rootState.routerBasePath}/api/items/${libraryItemId}/cover?token=${userToken}${raw ? '&raw=1' : ''}${timestamp ? `&ts=${timestamp}` : ''}`
|
|
||||||
},
|
},
|
||||||
getIsBatchSelectingMediaItems: (state) => {
|
getIsBatchSelectingMediaItems: (state) => {
|
||||||
return state.selectedMediaItems.length
|
return state.selectedMediaItems.length
|
||||||
|
|||||||
@@ -72,16 +72,17 @@ export const actions = {
|
|||||||
return this.$axios
|
return this.$axios
|
||||||
.$patch('/api/settings', updatePayload)
|
.$patch('/api/settings', updatePayload)
|
||||||
.then((result) => {
|
.then((result) => {
|
||||||
if (result.success) {
|
if (result.serverSettings) {
|
||||||
commit('setServerSettings', result.serverSettings)
|
commit('setServerSettings', result.serverSettings)
|
||||||
return true
|
|
||||||
} else {
|
|
||||||
return false
|
|
||||||
}
|
}
|
||||||
|
return result
|
||||||
})
|
})
|
||||||
.catch((error) => {
|
.catch((error) => {
|
||||||
console.error('Failed to update server settings', error)
|
console.error('Failed to update server settings', error)
|
||||||
return false
|
const errorMsg = error.response?.data || 'Unknown error'
|
||||||
|
return {
|
||||||
|
error: errorMsg
|
||||||
|
}
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
checkForUpdate({ commit }) {
|
checkForUpdate({ commit }) {
|
||||||
|
|||||||
@@ -309,9 +309,9 @@ export const mutations = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Add publishedDecades
|
// Add publishedDecades
|
||||||
if (mediaMetadata.publishedYear) {
|
if (mediaMetadata.publishedYear && !isNaN(mediaMetadata.publishedYear)) {
|
||||||
const publishedYear = parseInt(mediaMetadata.publishedYear, 10)
|
const publishedYear = parseInt(mediaMetadata.publishedYear, 10)
|
||||||
const decade = Math.floor(publishedYear / 10) * 10
|
const decade = (Math.floor(publishedYear / 10) * 10).toString()
|
||||||
if (!state.filterData.publishedDecades.includes(decade)) {
|
if (!state.filterData.publishedDecades.includes(decade)) {
|
||||||
state.filterData.publishedDecades.push(decade)
|
state.filterData.publishedDecades.push(decade)
|
||||||
state.filterData.publishedDecades.sort((a, b) => a - b)
|
state.filterData.publishedDecades.sort((a, b) => a - b)
|
||||||
|
|||||||
@@ -0,0 +1,156 @@
|
|||||||
|
{
|
||||||
|
"ButtonAdd": "ØĨØļØ§ŲØŠ",
|
||||||
|
"ButtonAddChapters": "ØĨØļØ§ŲØŠ Ø§ŲŲØĩŲŲ",
|
||||||
|
"ButtonAddDevice": "ØĨØļØ§ŲØŠ ØŦŲØ§Ø˛",
|
||||||
|
"ButtonAddLibrary": "ØĨØļØ§ŲØŠ Ų
ŲØĒØ¨ØŠ",
|
||||||
|
"ButtonAddPodcasts": "ØĨØļØ§ŲØŠ Ø¨ŲØ¯ŲØ§ØŗØĒ",
|
||||||
|
"ButtonAddUser": "ØĨØļØ§ŲØŠ Ų
ØŗØĒ؎دŲ
",
|
||||||
|
"ButtonAddYourFirstLibrary": "ØŖØļŲ Ų
ŲØĒØ¨ØĒŲ Ø§ŲØŖŲŲŲ",
|
||||||
|
"ButtonApply": "ØŲظ",
|
||||||
|
"ButtonApplyChapters": "ØŲظ اŲŲØĩŲŲ",
|
||||||
|
"ButtonAuthors": "اŲŲ
ؤŲŲŲŲ",
|
||||||
|
"ButtonBack": "Ø§ŲØąØŦŲØš",
|
||||||
|
"ButtonBrowseForFolder": "Ø§ŲØ¨ØØĢ ØšŲ Ø§ŲŲ
ØŦŲØ¯",
|
||||||
|
"ButtonCancel": "ØĨŲØēØ§ØĄ",
|
||||||
|
"ButtonCancelEncode": "ØĨŲØēØ§ØĄ Ø§ŲØĒØąŲ
ŲØ˛",
|
||||||
|
"ButtonChangeRootPassword": "ØĒØēŲŲØą ŲŲŲ
ØŠ اŲŲ
ØąŲØą Ø§ŲØąØĻŲØŗŲØŠ",
|
||||||
|
"ButtonCheckAndDownloadNewEpisodes": "Ø§ŲØĒØŲŲ Ų
Ų Ø§ŲØŲŲØ§ØĒ Ø§ŲØŦØ¯ŲØ¯ØŠ ŲØĒŲØ˛ŲŲŲØ§",
|
||||||
|
"ButtonChooseAFolder": "ا؎ØĒØą اŲŲ
ØŦŲØ¯",
|
||||||
|
"ButtonChooseFiles": "ا؎ØĒØą اŲŲ
ŲŲØ§ØĒ",
|
||||||
|
"ButtonClearFilter": "ØĒØĩŲŲØŠ Ø§Ųبਞ",
|
||||||
|
"ButtonCloseFeed": "ØĨØēŲØ§Ų",
|
||||||
|
"ButtonCloseSession": "ØĨØēŲØ§Ų Ø§ŲØŦŲØŗØŠ اŲŲ
ŲØĒŲØØŠ",
|
||||||
|
"ButtonCollections": "اŲŲ
ØŦŲ
ب𨧨Ē",
|
||||||
|
"ButtonConfigureScanner": "ØĨؚداداØĒ اŲŲ
Ø§ØŗØ Ø§ŲØļŲØĻŲ",
|
||||||
|
"ButtonCreate": "ØĨŲØ´Ø§ØĄ",
|
||||||
|
"ButtonCreateBackup": "ØĨŲØ´Ø§ØĄ ŲØŗØŽØŠ Ø§ØØĒŲØ§ØˇŲØŠ",
|
||||||
|
"ButtonDelete": "ØØ°Ų",
|
||||||
|
"ButtonDownloadQueue": "ŲØ§ØĻŲ
ØŠ",
|
||||||
|
"ButtonEdit": "ØĒؚدŲŲ",
|
||||||
|
"ButtonEditChapters": "ØĒؚدŲŲ Ø§ŲŲØĩŲŲ",
|
||||||
|
"ButtonEditPodcast": "ØĒؚدŲŲ Ø§ŲØ¨ŲØ¯ŲØ§ØŗØĒ",
|
||||||
|
"ButtonEnable": "ØĒŲØšŲŲ",
|
||||||
|
"ButtonFireAndFail": "اŲŲØ§Øą ŲØ§ŲŲØ´Ų",
|
||||||
|
"ButtonFireOnTest": "ØØ§Ø¯ØĢØŠ ØĨØˇŲØ§Ų اŲŲØ§Øą",
|
||||||
|
"ButtonForceReScan": "ŲØąØļ ØĨؚاد؊ اŲŲ
ØŗØ",
|
||||||
|
"ButtonFullPath": "اŲŲ
ØŗØ§Øą اŲŲØ§Ų
Ų",
|
||||||
|
"ButtonHide": "ØĨØŽŲØ§ØĄ",
|
||||||
|
"ButtonHome": "Ø§ŲØąØĻŲØŗŲØŠ",
|
||||||
|
"ButtonIssues": "Ų
شاŲŲ",
|
||||||
|
"ButtonJumpBackward": "اŲŲØ˛ ŲŲØŽŲŲ",
|
||||||
|
"ButtonJumpForward": "اŲŲØ˛ ŲŲØŖŲ
اŲ
",
|
||||||
|
"ButtonLatest": "ØŖØØ¯ØĢ",
|
||||||
|
"ButtonLibrary": "اŲŲ
ŲØĒØ¨ØŠ",
|
||||||
|
"ButtonLogout": "ØĒØŗØŦŲŲ Ø§ŲØŽØąŲØŦ",
|
||||||
|
"ButtonLookup": "Ø§ŲØ¨ØØĢ",
|
||||||
|
"ButtonManageTracks": "ØĨØ¯Ø§ØąØŠ اŲŲ
ŲØ§ØˇØš",
|
||||||
|
"ButtonMapChapterTitles": "Ų
ØˇØ§Ø¨ŲØŠ ØšŲØ§ŲŲŲ Ø§ŲŲØĩŲŲ",
|
||||||
|
"ButtonMatchAllAuthors": "Ų
ØˇØ§Ø¨ŲØŠ ŲŲ Ø§ŲŲ
ؤŲŲŲŲ",
|
||||||
|
"ButtonMatchBooks": "Ų
ØˇØ§Ø¨ŲØŠ Ø§ŲŲØĒØ¨",
|
||||||
|
"ButtonNevermind": "ŲØ§ ØĒŲØĒŲ
",
|
||||||
|
"ButtonNext": "Ø§ŲØĒØ§ŲŲ",
|
||||||
|
"ButtonNextChapter": "اŲŲØĩŲ Ø§ŲØĒØ§ŲŲ",
|
||||||
|
"ButtonNextItemInQueue": "Ø§ŲØšŲØĩØą Ø§ŲØĒØ§ŲŲ ŲŲ ŲØ§ØĻŲ
ØŠ Ø§ŲØ§ŲØĒØ¸Ø§Øą",
|
||||||
|
"ButtonOk": "ŲØšŲ
",
|
||||||
|
"ButtonOpenFeed": "ŲØĒØ Ø§ŲØĒØēØ°ŲØŠ",
|
||||||
|
"ButtonOpenManager": "ŲØĒØ Ø§ŲØĨØ¯Ø§ØąØŠ",
|
||||||
|
"ButtonPause": "ØĒŲŲŲŲŲŲŲ",
|
||||||
|
"ButtonPlay": "ØĒØ´ØēŲŲ",
|
||||||
|
"ButtonPlayAll": "ØĒØ´ØēŲŲ Ø§ŲŲŲ",
|
||||||
|
"ButtonPlaying": "Ų
Ø´ØēŲ Ø§ŲØĸŲ",
|
||||||
|
"ButtonPlaylists": "ŲŲØ§ØĻŲ
Ø§ŲØĒØ´ØēŲŲ",
|
||||||
|
"ButtonPrevious": "ØŗØ§Ø¨ŲŲ",
|
||||||
|
"ButtonPreviousChapter": "اŲŲØĩŲ Ø§ŲØŗØ§Ø¨Ų",
|
||||||
|
"ButtonProbeAudioFile": "ŲØØĩ Ų
ŲŲ Ø§ŲØĩŲØĒ",
|
||||||
|
"ButtonPurgeAllCache": "Ų
ØŗØ ŲØ§ŲØŠ Ø°Ø§ŲØąØŠ Ø§ŲØĒØŽØ˛ŲŲ Ø§ŲŲ
Ø¤ŲØĒØŠ",
|
||||||
|
"ButtonPurgeItemsCache": "Ų
ØŗØ Ø°Ø§ŲØąØŠ Ø§ŲØĒØŽØ˛ŲŲ Ø§ŲŲ
Ø¤ŲØĒØŠ ŲŲØšŲاØĩØą",
|
||||||
|
"ButtonQueueAddItem": "ØŖØļŲ ØĨŲŲ ŲØ§ØĻŲ
ØŠ Ø§ŲØ§ŲØĒØ¸Ø§Øą",
|
||||||
|
"ButtonQueueRemoveItem": "ØĨØ˛Ø§ŲØŠ Ų
Ų ŲØ§ØĻŲ
ØŠ Ø§ŲØ§ŲØĒØ¸Ø§Øą",
|
||||||
|
"ButtonQuickEmbed": "Ø§ŲØĒØļŲ
ŲŲ Ø§ŲØŗØąŲØš",
|
||||||
|
"ButtonQuickEmbedMetadata": "ØĨØ¯ØąØ§ØŦ ØŗØąŲØš ŲŲØ¨ŲØ§ŲØ§ØĒ اŲŲØĩŲŲØŠ",
|
||||||
|
"ButtonQuickMatch": "Ų
ØˇØ§Ø¨ŲØŠ ØŗØąŲØšØŠ",
|
||||||
|
"ButtonReScan": "ØĨؚاد؊ Ø§ŲØ¨ØØĢ",
|
||||||
|
"ButtonRead": "Ø§ŲØąØŖ",
|
||||||
|
"ButtonReadLess": "ŲŲØĩ",
|
||||||
|
"ButtonReadMore": "اŲŲ
Ø˛ŲØ¯",
|
||||||
|
"ButtonRefresh": "ØĒØØ¯ŲØĢ",
|
||||||
|
"ButtonRemove": "ØĨØ˛Ø§ŲØŠ",
|
||||||
|
"ButtonRemoveAll": "ØĨØ˛Ø§ŲØŠ Ø§ŲŲŲ",
|
||||||
|
"ButtonRemoveAllLibraryItems": "ØĨØ˛Ø§ŲØŠ ŲØ§ŲØŠ ØšŲØ§ØĩØą اŲŲ
ŲØĒØ¨ØŠ",
|
||||||
|
"ButtonRemoveFromContinueListening": "ØĨØ˛Ø§ŲØŠ Ų
Ų Ų
ØĒابؚ؊ Ø§ŲØ§ØŗØĒŲ
اؚ",
|
||||||
|
"ButtonRemoveFromContinueReading": "ØĨØ˛Ø§ŲØŠ Ų
Ų Ų
ØĒابؚ؊ اŲŲØąØ§ØĄØŠ",
|
||||||
|
"ButtonRemoveSeriesFromContinueSeries": "ØĨØ˛Ø§ŲØŠ Ø§ŲØŗŲØŗŲØŠ Ų
Ų Ø§ØŗØĒŲ
ØąØ§Øą Ø§ŲØŗŲØŗŲØŠ",
|
||||||
|
"ButtonReset": "ØĨؚاد؊ ØļØ¨Øˇ",
|
||||||
|
"ButtonResetToDefault": "ØĨؚاد؊ ØļØ¨Øˇ ØĨŲŲ Ø§ŲŲØļØš Ø§ŲØ§ŲØĒØąØ§ØļŲ",
|
||||||
|
"ButtonRestore": "ØĨØŗØĒب𨧨¯ØŠ",
|
||||||
|
"ButtonSave": "ØŲظ",
|
||||||
|
"ButtonSaveAndClose": "ØŲظ Ų ØĨØēŲØ§Ų",
|
||||||
|
"ButtonSaveTracklist": "ØŲظ ŲØ§ØĻŲ
ØŠ Ø§ŲØĒØ´ØēŲŲ",
|
||||||
|
"ButtonScan": "ØĒŲØŲŲŲŲ",
|
||||||
|
"ButtonScanLibrary": "ØĒŲØŲŲŲŲ Ų
Ų Ø§ŲŲ
ŲØĒØ¨ØŠ",
|
||||||
|
"ButtonSearch": "Ø¨ØØĢ",
|
||||||
|
"ButtonSelectFolderPath": "ØØ¯Ø¯ Ų
ØŗØ§Øą اŲŲ
ØŦŲØ¯",
|
||||||
|
"ButtonSeries": "ØŗŲØŗŲØŠ",
|
||||||
|
"ButtonSetChaptersFromTracks": "ØĒØšŲŲŲ Ø§ŲŲØĩŲŲ Ų
Ų Ø§ŲŲ
ŲŲØ§ØĒ",
|
||||||
|
"ButtonShare": "ŲØ´Øą",
|
||||||
|
"ButtonShiftTimes": "ØŖŲŲØ§ØĒ Ø§ŲØšŲ
Ų",
|
||||||
|
"ButtonShow": "ØšØąØļ",
|
||||||
|
"ButtonStartM4BEncode": "Ø§Ø¨Ø¯ØŖ ØĒØąŲ
ŲØ˛ M4B",
|
||||||
|
"ButtonStartMetadataEmbed": "Ø§Ø¨Ø¯ØŖ ØĒØļŲ
ŲŲ Ø§ŲØ¨ŲØ§ŲØ§ØĒ اŲŲØĩŲŲØŠ",
|
||||||
|
"ButtonStats": "Ø§ŲØĨØØĩاØĻŲØ§ØĒ",
|
||||||
|
"ButtonSubmit": "ØĒŲØ¯ŲŲ
",
|
||||||
|
"ButtonTest": "ا؎ØĒØ¨Ø§Øą",
|
||||||
|
"ButtonUnlinkOpenId": "ØĨŲØēØ§ØĄ ØąØ¨Øˇ اŲŲ
ØšØąŲ",
|
||||||
|
"ButtonUpload": "ØąŲØš",
|
||||||
|
"ButtonUploadBackup": "ØĒØŲ
ŲŲ Ø§ŲŲØŗØŽØŠ Ø§ŲØ§ØØĒŲØ§ØˇŲØŠ",
|
||||||
|
"ButtonUploadCover": "Ø§ØąŲŲ Ø§ŲØēŲØ§Ų",
|
||||||
|
"ButtonUploadOPMLFile": "ØąŲØš Ų
ŲŲ OPML",
|
||||||
|
"ButtonUserDelete": "ØØ°Ų اŲŲ
ØŗØĒ؎دŲ
{0}",
|
||||||
|
"ButtonUserEdit": "ØĒؚدŲŲ Ø§ŲŲ
ØŗØĒ؎دŲ
{0}",
|
||||||
|
"ButtonViewAll": "ØšØąØļ اŲŲŲ",
|
||||||
|
"ButtonYes": "ŲØšŲ
",
|
||||||
|
"ErrorUploadFetchMetadataAPI": "ØŽØˇØŖ ŲŲ ØŦŲØ¨ Ø§ŲØ¨ŲØ§ŲØ§ØĒ اŲŲØĩŲŲØŠ",
|
||||||
|
"ErrorUploadFetchMetadataNoResults": "ŲŲ
ŲØĒŲ
Ø§ŲØšØĢŲØą ØšŲŲ Ø§ŲØ¨ŲØ§ŲØ§ØĒ اŲŲØĩŲŲØŠ - ØØ§ŲŲ ØĒØØ¯ŲØĢ Ø§ŲØšŲŲØ§Ų Ų/ØŖŲ Ø§ŲŲ
ؤŲŲ",
|
||||||
|
"ErrorUploadLacksTitle": "ŲØŦب ØŖŲ ŲŲŲŲ ŲŲ ØšŲŲØ§Ų",
|
||||||
|
"HeaderAccount": "Ø§ŲØØŗØ§Ø¨",
|
||||||
|
"HeaderAddCustomMetadataProvider": "ØĨØļØ§ŲØŠ Ų
ŲŲØą Ø¨ŲØ§ŲØ§ØĒ ØĒØšØąŲŲŲØŠ Ų
ØŽØĩØĩ",
|
||||||
|
"HeaderAdvanced": "Ų
ØĒŲØ¯Ų
",
|
||||||
|
"HeaderAppriseNotificationSettings": "ØĨؚداداØĒ Ø§ŲØĨØ´ØšØ§ØąØ§ØĒ",
|
||||||
|
"HeaderAudioTracks": "اŲŲ
ØŗØ§ØąØ§ØĒ Ø§ŲØĩŲØĒŲØŠ",
|
||||||
|
"HeaderAudiobookTools": "ØŖØ¯ŲØ§ØĒ ØĨØ¯Ø§ØąØŠ Ų
ŲŲØ§ØĒ اŲŲØĒØ¨ Ø§ŲØĩŲØĒŲØŠ",
|
||||||
|
"HeaderAuthentication": "اŲŲ
ØĩØ§Ø¯ŲØŠ",
|
||||||
|
"HeaderBackups": "اŲŲØŗØŽ Ø§ŲØ§ØØĒŲØ§ØˇŲØŠ",
|
||||||
|
"HeaderChangePassword": "ØĒØēŲŲØą ŲŲŲ
ØŠ اŲŲ
ØąŲØą",
|
||||||
|
"HeaderChapters": "اŲŲØĩŲŲ",
|
||||||
|
"HeaderChooseAFolder": "ا؎ØĒŲØ§Øą اŲŲ
ØŦŲØ¯",
|
||||||
|
"HeaderCollection": "Ų
ØŦŲ
ŲØšØŠ",
|
||||||
|
"HeaderCollectionItems": "ØšŲØ§ØĩØą اŲŲ
ØŦŲ
ŲØšØŠ",
|
||||||
|
"HeaderCover": "Ø§ŲØēŲØ§Ų",
|
||||||
|
"HeaderCurrentDownloads": "Ø§ŲØĒŲØ˛ŲŲØ§ØĒ Ø§ŲØŦØ§ØąŲØŠ",
|
||||||
|
"HeaderCustomMessageOnLogin": "ØąØŗØ§ŲØŠ Ų
ØŽØĩØĩØŠ ØšŲØ¯ ØĒØŗØŦŲŲ Ø§ŲØ¯ØŽŲŲ",
|
||||||
|
"HeaderCustomMetadataProviders": "Ų
ŲØ¯Ų
Ų Ø§ŲØ¨ŲØ§ŲØ§ØĒ اŲŲØĩŲŲØŠ Ø§ŲŲ
ØŽØĩØĩØŠ",
|
||||||
|
"HeaderDetails": "Ø§ŲØĒŲØ§ØĩŲŲ",
|
||||||
|
"HeaderDownloadQueue": "ØĒŲØ˛ŲŲ ŲØ§ØĻŲ
ØŠ Ø§ŲØ§ŲØĒØ¸Ø§Øą",
|
||||||
|
"HeaderEbookFiles": "Ų
ŲŲØ§ØĒ اŲŲØĒØ¨ Ø§ŲØĨŲŲØĒØąŲŲŲØŠ",
|
||||||
|
"HeaderEmail": "Ø§ŲØ¨ØąŲد Ø§ŲØĨŲŲØĒØąŲŲŲ",
|
||||||
|
"HeaderEmailSettings": "ØĨؚداداØĒ Ø§ŲØ¨ØąŲد Ø§ŲØĨŲŲØĒØąŲŲŲ",
|
||||||
|
"HeaderEpisodes": "Ø§ŲØŲŲØ§ØĒ",
|
||||||
|
"HeaderEreaderDevices": "ØŖØŦŲØ˛ØŠ ŲØąØ§ØĄØŠ اŲŲØĒØ¨ Ø§ŲØĨŲŲØĒØąŲŲŲØŠ",
|
||||||
|
"HeaderEreaderSettings": "ØĨؚداداØĒ اŲŲØ§ØąØĻ Ø§ŲØĨŲŲØĒØąŲŲŲ",
|
||||||
|
"HeaderFiles": "Ų
ŲŲØ§ØĒ",
|
||||||
|
"HeaderFindChapters": "Ø§ŲØ¨ØØĢ ØšŲ Ø§ŲŲØĩŲŲ",
|
||||||
|
"HeaderIgnoredFiles": "اŲŲ
ŲŲØ§ØĒ اŲŲ
ØĒØŦاŲŲØŠ",
|
||||||
|
"HeaderItemFiles": "Ų
ŲŲØ§ØĒ Ø§ŲØšŲØĩØą",
|
||||||
|
"HeaderItemMetadataUtils": "Ø¨ŲØ§ŲاØĒ ØĒØšØąŲŲ Ø§ŲØšŲØĩØą",
|
||||||
|
"HeaderLastListeningSession": "ØĸØŽØą ØŦŲØŗØŠ Ø§ØŗØĒŲ
اؚ",
|
||||||
|
"HeaderLatestEpisodes": "ØŖØØ¯ØĢ Ø§ŲØŲŲØ§ØĒ",
|
||||||
|
"HeaderLibraries": "اŲŲ
ŲØĒØ¨Ø§ØĒ",
|
||||||
|
"HeaderLibraryFiles": "Ų
ŲŲØ§ØĒ اŲŲ
ŲØĒØ¨ØŠ",
|
||||||
|
"HeaderLibraryStats": "ØĨØØĩاØĻŲØ§ØĒ اŲŲ
ŲØĒØ¨ØŠ",
|
||||||
|
"HeaderListeningSessions": "ØŦŲØŗØ§ØĒ Ø§ŲØ§ØŗØĒŲ
اؚ",
|
||||||
|
"HeaderListeningStats": "ØŦŲØŗØ§ØĒ Ø§ŲØ§ØŗØĒŲ
اؚ",
|
||||||
|
"HeaderLogin": "ØĒØŗØŦŲŲ Ø§ŲØ¯ØŽŲŲ",
|
||||||
|
"HeaderLogs": "Ø§ŲØŗØŦŲØ§ØĒ",
|
||||||
|
"HeaderManageGenres": "ØĨØ¯Ø§ØąØŠ Ø§ŲØ§ŲŲØ§Øš",
|
||||||
|
"HeaderManageTags": "ØĨØ¯Ø§ØąØŠ Ø§ŲØšŲاŲ
اØĒ"
|
||||||
|
}
|
||||||
@@ -729,7 +729,6 @@
|
|||||||
"ToastBookmarkUpdateSuccess": "ĐŅĐŧĐĩŅĐēаŅа Đĩ ОйĐŊОвĐĩĐŊа",
|
"ToastBookmarkUpdateSuccess": "ĐŅĐŧĐĩŅĐēаŅа Đĩ ОйĐŊОвĐĩĐŊа",
|
||||||
"ToastChaptersHaveErrors": "ĐĐģавиŅĐĩ иĐŧĐ°Ņ ĐŗŅĐĩŅĐēи",
|
"ToastChaptersHaveErrors": "ĐĐģавиŅĐĩ иĐŧĐ°Ņ ĐŗŅĐĩŅĐēи",
|
||||||
"ToastChaptersMustHaveTitles": "ĐĐģавиŅĐĩ ŅŅŅйва да иĐŧĐ°Ņ ĐˇĐ°ĐŗĐģавиŅ",
|
"ToastChaptersMustHaveTitles": "ĐĐģавиŅĐĩ ŅŅŅйва да иĐŧĐ°Ņ ĐˇĐ°ĐŗĐģавиŅ",
|
||||||
"ToastCollectionItemsRemoveSuccess": "ĐĐģĐĩĐŧĐĩĐŊŅ(и) ĐŋŅĐĩĐŧаŅ
ĐŊаŅи ĐžŅ ĐēĐžĐģĐĩĐēŅиŅ",
|
|
||||||
"ToastCollectionRemoveSuccess": "ĐĐžĐģĐĩĐēŅиŅŅа Đĩ ĐŋŅĐĩĐŧаŅ
ĐŊаŅа",
|
"ToastCollectionRemoveSuccess": "ĐĐžĐģĐĩĐēŅиŅŅа Đĩ ĐŋŅĐĩĐŧаŅ
ĐŊаŅа",
|
||||||
"ToastCollectionUpdateSuccess": "ĐĐžĐģĐĩĐēŅиŅŅа Đĩ ОйĐŊОвĐĩĐŊа",
|
"ToastCollectionUpdateSuccess": "ĐĐžĐģĐĩĐēŅиŅŅа Đĩ ОйĐŊОвĐĩĐŊа",
|
||||||
"ToastItemCoverUpdateSuccess": "ĐĐžŅиŅаŅа ĐŊа ĐĩĐģĐĩĐŧĐĩĐŊŅа Đĩ ОйĐŊОвĐĩĐŊа",
|
"ToastItemCoverUpdateSuccess": "ĐĐžŅиŅаŅа ĐŊа ĐĩĐģĐĩĐŧĐĩĐŊŅа Đĩ ОйĐŊОвĐĩĐŊа",
|
||||||
|
|||||||
+82
-2
@@ -66,6 +66,7 @@
|
|||||||
"ButtonPurgeItemsCache": "āĻāĻāĻā§āĻŽ āĻā§āϝāĻžāĻļā§ āĻĒāϰāĻŋāώā§āĻāĻžāϰ āĻāϰā§āύ",
|
"ButtonPurgeItemsCache": "āĻāĻāĻā§āĻŽ āĻā§āϝāĻžāĻļā§ āĻĒāϰāĻŋāώā§āĻāĻžāϰ āĻāϰā§āύ",
|
||||||
"ButtonQueueAddItem": "āϏāĻžāϰāĻŋāϤ⧠āϝā§āĻ āĻāϰā§āύ",
|
"ButtonQueueAddItem": "āϏāĻžāϰāĻŋāϤ⧠āϝā§āĻ āĻāϰā§āύ",
|
||||||
"ButtonQueueRemoveItem": "āϏāĻžāϰāĻŋ āĻĨā§āĻā§ āĻŽā§āĻā§ āĻĢā§āϞā§āύ",
|
"ButtonQueueRemoveItem": "āϏāĻžāϰāĻŋ āĻĨā§āĻā§ āĻŽā§āĻā§ āĻĢā§āϞā§āύ",
|
||||||
|
"ButtonQuickEmbed": "āĻĻā§āϰā§āϤ āĻāĻŽā§āĻŦā§āĻĄ āĻāϰā§āύ",
|
||||||
"ButtonQuickEmbedMetadata": "āĻŽā§āĻāĻžāĻĄā§āĻāĻž āĻĻā§āϰā§āϤ āĻāĻŽā§āĻŦā§āĻĄ āĻāϰā§āύ",
|
"ButtonQuickEmbedMetadata": "āĻŽā§āĻāĻžāĻĄā§āĻāĻž āĻĻā§āϰā§āϤ āĻāĻŽā§āĻŦā§āĻĄ āĻāϰā§āύ",
|
||||||
"ButtonQuickMatch": "āĻĻā§āϰā§āϤ āĻŽā§āϝāĻžāĻ",
|
"ButtonQuickMatch": "āĻĻā§āϰā§āϤ āĻŽā§āϝāĻžāĻ",
|
||||||
"ButtonReScan": "āĻĒā§āύāϰāĻžāϝāĻŧ āϏā§āĻā§āϝāĻžāύ",
|
"ButtonReScan": "āĻĒā§āύāϰāĻžāϝāĻŧ āϏā§āĻā§āϝāĻžāύ",
|
||||||
@@ -162,6 +163,7 @@
|
|||||||
"HeaderNotificationUpdate": "āĻŦāĻŋāĻā§āĻāĻĒā§āϤāĻŋ āĻāĻĒāĻĄā§āĻ āĻāϰā§āύ",
|
"HeaderNotificationUpdate": "āĻŦāĻŋāĻā§āĻāĻĒā§āϤāĻŋ āĻāĻĒāĻĄā§āĻ āĻāϰā§āύ",
|
||||||
"HeaderNotifications": "āĻŦāĻŋāĻā§āĻāĻĒā§āϤāĻŋ",
|
"HeaderNotifications": "āĻŦāĻŋāĻā§āĻāĻĒā§āϤāĻŋ",
|
||||||
"HeaderOpenIDConnectAuthentication": "āĻāĻĒā§āύāĻāĻāĻĄāĻŋ āϏāĻāϝā§āĻ āĻĒā§āϰāĻŽāĻžāĻŖā§āĻāϰāĻŖ",
|
"HeaderOpenIDConnectAuthentication": "āĻāĻĒā§āύāĻāĻāĻĄāĻŋ āϏāĻāϝā§āĻ āĻĒā§āϰāĻŽāĻžāĻŖā§āĻāϰāĻŖ",
|
||||||
|
"HeaderOpenListeningSessions": "āĻļā§āύāĻžāϰ āϏā§āĻļāύ āĻā§āϞā§āύ",
|
||||||
"HeaderOpenRSSFeed": "āĻāϰāĻāϏāĻāϏ āĻĢāĻŋāĻĄ āĻā§āϞā§āύ",
|
"HeaderOpenRSSFeed": "āĻāϰāĻāϏāĻāϏ āĻĢāĻŋāĻĄ āĻā§āϞā§āύ",
|
||||||
"HeaderOtherFiles": "āĻ
āύā§āϝāĻžāύā§āϝ āĻĢāĻžāĻāϞ",
|
"HeaderOtherFiles": "āĻ
āύā§āϝāĻžāύā§āϝ āĻĢāĻžāĻāϞ",
|
||||||
"HeaderPasswordAuthentication": "āĻĒāĻžāϏāĻāϝāĻŧāĻžāϰā§āĻĄ āĻĒā§āϰāĻŽāĻžāĻŖā§āĻāϰāĻŖ",
|
"HeaderPasswordAuthentication": "āĻĒāĻžāϏāĻāϝāĻŧāĻžāϰā§āĻĄ āĻĒā§āϰāĻŽāĻžāĻŖā§āĻāϰāĻŖ",
|
||||||
@@ -179,6 +181,7 @@
|
|||||||
"HeaderRemoveEpisodes": "{0}āĻāĻŋ āĻĒāϰā§āĻŦ āϏāϰāĻžāύ",
|
"HeaderRemoveEpisodes": "{0}āĻāĻŋ āĻĒāϰā§āĻŦ āϏāϰāĻžāύ",
|
||||||
"HeaderSavedMediaProgress": "āĻŽāĻŋāĻĄāĻŋāϝāĻŧāĻž āϏāĻāϰāĻā§āώāĻŖā§āϰ āĻ
āĻā§āϰāĻāϤāĻŋ",
|
"HeaderSavedMediaProgress": "āĻŽāĻŋāĻĄāĻŋāϝāĻŧāĻž āϏāĻāϰāĻā§āώāĻŖā§āϰ āĻ
āĻā§āϰāĻāϤāĻŋ",
|
||||||
"HeaderSchedule": "āϏāĻŽāϝāĻŧāϏā§āĻā§",
|
"HeaderSchedule": "āϏāĻŽāϝāĻŧāϏā§āĻā§",
|
||||||
|
"HeaderScheduleEpisodeDownloads": "āϏā§āĻŦāϝāĻŧāĻāĻā§āϰāĻŋāϝāĻŧ āĻĒāϰā§āĻŦ āĻĄāĻžāĻāύāϞā§āĻĄā§āϰ āϏāĻŽāϝāĻŧāϏā§āĻā§ āύāĻŋāϰā§āϧāĻžāϰāύ āĻāϰā§āύ",
|
||||||
"HeaderScheduleLibraryScans": "āϏā§āĻŦāϝāĻŧāĻāĻā§āϰāĻŋāϝāĻŧ āϞāĻžāĻāĻŦā§āϰā§āϰāĻŋ āϏā§āĻā§āϝāĻžāύā§āϰ āϏāĻŽāϝāĻŧāϏā§āĻā§",
|
"HeaderScheduleLibraryScans": "āϏā§āĻŦāϝāĻŧāĻāĻā§āϰāĻŋāϝāĻŧ āϞāĻžāĻāĻŦā§āϰā§āϰāĻŋ āϏā§āĻā§āϝāĻžāύā§āϰ āϏāĻŽāϝāĻŧāϏā§āĻā§",
|
||||||
"HeaderSession": "āϏā§āĻļāύ",
|
"HeaderSession": "āϏā§āĻļāύ",
|
||||||
"HeaderSetBackupSchedule": "āĻŦā§āϝāĻžāĻāĻāĻĒ āϏāĻŽāϝāĻŧāϏā§āĻā§ āϏā§āĻ āĻāϰā§āύ",
|
"HeaderSetBackupSchedule": "āĻŦā§āϝāĻžāĻāĻāĻĒ āϏāĻŽāϝāĻŧāϏā§āĻā§ āϏā§āĻ āĻāϰā§āύ",
|
||||||
@@ -224,7 +227,11 @@
|
|||||||
"LabelAllUsersExcludingGuests": "āĻ
āϤāĻŋāĻĨāĻŋ āĻŦā§āϝāϤā§āϤ āϏāĻāϞ āĻŦā§āϝāĻŦāĻšāĻžāϰāĻāĻžāϰā§",
|
"LabelAllUsersExcludingGuests": "āĻ
āϤāĻŋāĻĨāĻŋ āĻŦā§āϝāϤā§āϤ āϏāĻāϞ āĻŦā§āϝāĻŦāĻšāĻžāϰāĻāĻžāϰā§",
|
||||||
"LabelAllUsersIncludingGuests": "āĻ
āϤāĻŋāĻĨāĻŋ āϏāĻš āϏāĻāϞ āĻŦā§āϝāĻŦāĻšāĻžāϰāĻāĻžāϰā§",
|
"LabelAllUsersIncludingGuests": "āĻ
āϤāĻŋāĻĨāĻŋ āϏāĻš āϏāĻāϞ āĻŦā§āϝāĻŦāĻšāĻžāϰāĻāĻžāϰā§",
|
||||||
"LabelAlreadyInYourLibrary": "āĻāϤāĻŋāĻŽāϧā§āϝā§āĻ āĻāĻĒāύāĻžāϰ āϞāĻžāĻāĻŦā§āϰā§āϰāĻŋāϤ⧠āϰā§ā§āĻā§",
|
"LabelAlreadyInYourLibrary": "āĻāϤāĻŋāĻŽāϧā§āϝā§āĻ āĻāĻĒāύāĻžāϰ āϞāĻžāĻāĻŦā§āϰā§āϰāĻŋāϤ⧠āϰā§ā§āĻā§",
|
||||||
|
"LabelApiToken": "API āĻā§āĻā§āύ",
|
||||||
"LabelAppend": "āϏāĻāϝā§āĻāύ",
|
"LabelAppend": "āϏāĻāϝā§āĻāύ",
|
||||||
|
"LabelAudioBitrate": "āĻ
āĻĄāĻŋāĻ āĻŦāĻŋāĻāϰā§āĻ (āϝā§āĻŽāύ- 128k)",
|
||||||
|
"LabelAudioChannels": "āĻ
āĻĄāĻŋāĻ āĻā§āϝāĻžāύā§āϞ (ā§§ āĻŦāĻž ⧍)",
|
||||||
|
"LabelAudioCodec": "āĻ
āĻĄāĻŋāĻ āĻā§āĻĄā§āĻ",
|
||||||
"LabelAuthor": "āϞā§āĻāĻ",
|
"LabelAuthor": "āϞā§āĻāĻ",
|
||||||
"LabelAuthorFirstLast": "āϞā§āĻāĻ (āĻĒā§āϰāĻĨāĻŽ āĻļā§āώ)",
|
"LabelAuthorFirstLast": "āϞā§āĻāĻ (āĻĒā§āϰāĻĨāĻŽ āĻļā§āώ)",
|
||||||
"LabelAuthorLastFirst": "āϞā§āĻāĻ (āĻļā§āώ, āĻĒā§āϰāĻĨāĻŽ)",
|
"LabelAuthorLastFirst": "āϞā§āĻāĻ (āĻļā§āώ, āĻĒā§āϰāĻĨāĻŽ)",
|
||||||
@@ -237,6 +244,7 @@
|
|||||||
"LabelAutoRegister": "āϏā§āĻŦā§āĻāĻā§āϰāĻŋā§ āύāĻŋāĻŦāύā§āϧāύ",
|
"LabelAutoRegister": "āϏā§āĻŦā§āĻāĻā§āϰāĻŋā§ āύāĻŋāĻŦāύā§āϧāύ",
|
||||||
"LabelAutoRegisterDescription": "āϞāĻ āĻāύ āĻāϰāĻžāϰ āĻĒāϰ āϏā§āĻŦāϝāĻŧāĻāĻā§āϰāĻŋāϝāĻŧāĻāĻžāĻŦā§ āύāϤā§āύ āĻŦā§āϝāĻŦāĻšāĻžāϰāĻāĻžāϰ⧠āϤā§āϰāĻŋ āĻāϰā§āύ",
|
"LabelAutoRegisterDescription": "āϞāĻ āĻāύ āĻāϰāĻžāϰ āĻĒāϰ āϏā§āĻŦāϝāĻŧāĻāĻā§āϰāĻŋāϝāĻŧāĻāĻžāĻŦā§ āύāϤā§āύ āĻŦā§āϝāĻŦāĻšāĻžāϰāĻāĻžāϰ⧠āϤā§āϰāĻŋ āĻāϰā§āύ",
|
||||||
"LabelBackToUser": "āĻŦā§āϝāĻŦāĻšāĻžāϰāĻāĻžāϰā§āϰ āĻāĻžāĻā§ āĻĢāĻŋāϰ⧠āϝāĻžāύ",
|
"LabelBackToUser": "āĻŦā§āϝāĻŦāĻšāĻžāϰāĻāĻžāϰā§āϰ āĻāĻžāĻā§ āĻĢāĻŋāϰ⧠āϝāĻžāύ",
|
||||||
|
"LabelBackupAudioFiles": "āĻ
āĻĄāĻŋāĻ āĻĢāĻžāĻāϞāĻā§āϞ⧠āĻŦā§āϝāĻžāĻāĻāĻĒ",
|
||||||
"LabelBackupLocation": "āĻŦā§āϝāĻžāĻāĻāĻĒ āĻ
āĻŦāϏā§āĻĨāĻžāύ",
|
"LabelBackupLocation": "āĻŦā§āϝāĻžāĻāĻāĻĒ āĻ
āĻŦāϏā§āĻĨāĻžāύ",
|
||||||
"LabelBackupsEnableAutomaticBackups": "āϏā§āĻŦāϝāĻŧāĻāĻā§āϰāĻŋāϝāĻŧ āĻŦā§āϝāĻžāĻāĻāĻĒ āϏāĻā§āώāĻŽ āĻāϰā§āύ",
|
"LabelBackupsEnableAutomaticBackups": "āϏā§āĻŦāϝāĻŧāĻāĻā§āϰāĻŋāϝāĻŧ āĻŦā§āϝāĻžāĻāĻāĻĒ āϏāĻā§āώāĻŽ āĻāϰā§āύ",
|
||||||
"LabelBackupsEnableAutomaticBackupsHelp": "āĻŦā§āϝāĻžāĻāĻāĻĒāĻā§āϞāĻŋ /āĻŽā§āĻāĻžāĻĄāĻžāĻāĻž/āĻŦā§āϝāĻžāĻāĻāĻĒā§ āϏāĻāϰāĻā§āώāĻŋāϤ",
|
"LabelBackupsEnableAutomaticBackupsHelp": "āĻŦā§āϝāĻžāĻāĻāĻĒāĻā§āϞāĻŋ /āĻŽā§āĻāĻžāĻĄāĻžāĻāĻž/āĻŦā§āϝāĻžāĻāĻāĻĒā§ āϏāĻāϰāĻā§āώāĻŋāϤ",
|
||||||
@@ -245,15 +253,18 @@
|
|||||||
"LabelBackupsNumberToKeep": "āĻŦā§āϝāĻžāĻāĻāĻĒā§āϰ āϏāĻāĻā§āϝāĻž āϰāĻžāĻā§āύ",
|
"LabelBackupsNumberToKeep": "āĻŦā§āϝāĻžāĻāĻāĻĒā§āϰ āϏāĻāĻā§āϝāĻž āϰāĻžāĻā§āύ",
|
||||||
"LabelBackupsNumberToKeepHelp": "āĻāĻ āϏāĻŽāϝāĻŧā§ āĻļā§āϧā§āĻŽāĻžāϤā§āϰ ā§§ āĻāĻŋ āĻŦā§āϝāĻžāĻāĻāĻĒ āϏāϰāĻžāύ⧠āĻšāĻŦā§ āϤāĻžāĻ āϝāĻĻāĻŋ āĻāĻĒāύāĻžāϰ āĻāĻžāĻā§ āĻāϤāĻŋāĻŽāϧā§āϝ⧠āĻāϰ āĻā§āϝāĻŧā§ āĻŦā§āĻļāĻŋ āĻŦā§āϝāĻžāĻāĻāĻĒ āĻĨāĻžāĻā§ āϤāĻžāĻšāϞ⧠āĻāĻĒāύāĻžāĻā§ āĻŽā§āϝāĻžāύā§āϝāĻŧāĻžāϞāĻŋ āϏā§āĻā§āϞāĻŋ āϏāϰāĻŋāϝāĻŧā§ āĻĢā§āϞāϤ⧠āĻšāĻŦā§āĨ¤",
|
"LabelBackupsNumberToKeepHelp": "āĻāĻ āϏāĻŽāϝāĻŧā§ āĻļā§āϧā§āĻŽāĻžāϤā§āϰ ā§§ āĻāĻŋ āĻŦā§āϝāĻžāĻāĻāĻĒ āϏāϰāĻžāύ⧠āĻšāĻŦā§ āϤāĻžāĻ āϝāĻĻāĻŋ āĻāĻĒāύāĻžāϰ āĻāĻžāĻā§ āĻāϤāĻŋāĻŽāϧā§āϝ⧠āĻāϰ āĻā§āϝāĻŧā§ āĻŦā§āĻļāĻŋ āĻŦā§āϝāĻžāĻāĻāĻĒ āĻĨāĻžāĻā§ āϤāĻžāĻšāϞ⧠āĻāĻĒāύāĻžāĻā§ āĻŽā§āϝāĻžāύā§āϝāĻŧāĻžāϞāĻŋ āϏā§āĻā§āϞāĻŋ āϏāϰāĻŋāϝāĻŧā§ āĻĢā§āϞāϤ⧠āĻšāĻŦā§āĨ¤",
|
||||||
"LabelBitrate": "āĻŦāĻŋāĻāϰā§āĻ",
|
"LabelBitrate": "āĻŦāĻŋāĻāϰā§āĻ",
|
||||||
|
"LabelBonus": "āĻāĻĒāϰāĻŋāϞāĻžāĻ",
|
||||||
"LabelBooks": "āĻŦāĻāĻā§āϞā§",
|
"LabelBooks": "āĻŦāĻāĻā§āϞā§",
|
||||||
"LabelButtonText": "āĻāϰ āĻĒāĻžāĻ ā§āϝ",
|
"LabelButtonText": "āĻāϰ āĻĒāĻžāĻ ā§āϝ",
|
||||||
"LabelByAuthor": "āĻĻā§āĻŦāĻžāϰāĻž {0}",
|
"LabelByAuthor": "āĻĻā§āĻŦāĻžāϰāĻž {0}",
|
||||||
"LabelChangePassword": "āĻĒāĻžāϏāĻāϝāĻŧāĻžāϰā§āĻĄ āĻĒāϰāĻŋāĻŦāϰā§āϤāύ āĻāϰā§āύ",
|
"LabelChangePassword": "āĻĒāĻžāϏāĻāϝāĻŧāĻžāϰā§āĻĄ āĻĒāϰāĻŋāĻŦāϰā§āϤāύ āĻāϰā§āύ",
|
||||||
"LabelChannels": "āĻā§āϝāĻžāύā§āϞ",
|
"LabelChannels": "āĻā§āϝāĻžāύā§āϞ",
|
||||||
|
"LabelChapterCount": "{0} āĻ
āϧā§āϝāĻžāϝāĻŧ",
|
||||||
"LabelChapterTitle": "āĻ
āϧā§āϝāĻžāϝāĻŧā§āϰ āĻļāĻŋāϰā§āύāĻžāĻŽ",
|
"LabelChapterTitle": "āĻ
āϧā§āϝāĻžāϝāĻŧā§āϰ āĻļāĻŋāϰā§āύāĻžāĻŽ",
|
||||||
"LabelChapters": "āĻ
āϧā§āϝāĻžāϝāĻŧ",
|
"LabelChapters": "āĻ
āϧā§āϝāĻžāϝāĻŧ",
|
||||||
"LabelChaptersFound": "āĻ
āϧā§āϝāĻžāϝāĻŧ āĻĒāĻžāĻāϝāĻŧāĻž āĻā§āĻā§",
|
"LabelChaptersFound": "āĻ
āϧā§āϝāĻžāϝāĻŧ āĻĒāĻžāĻāϝāĻŧāĻž āĻā§āĻā§",
|
||||||
"LabelClickForMoreInfo": "āĻāϰ⧠āϤāĻĨā§āϝā§āϰ āĻāύā§āϝ āĻā§āϞāĻŋāĻ āĻāϰā§āύ",
|
"LabelClickForMoreInfo": "āĻāϰ⧠āϤāĻĨā§āϝā§āϰ āĻāύā§āϝ āĻā§āϞāĻŋāĻ āĻāϰā§āύ",
|
||||||
|
"LabelClickToUseCurrentValue": "āĻŦāϰā§āϤāĻŽāĻžāύ āĻŽāĻžāύ āĻŦā§āϝāĻŦāĻšāĻžāϰ āĻāϰāϤ⧠āĻā§āϞāĻŋāĻ āĻāϰā§āύ",
|
||||||
"LabelClosePlayer": "āĻĒā§āϞā§āϝāĻŧāĻžāϰ āĻŦāύā§āϧ āĻāϰā§āύ",
|
"LabelClosePlayer": "āĻĒā§āϞā§āϝāĻŧāĻžāϰ āĻŦāύā§āϧ āĻāϰā§āύ",
|
||||||
"LabelCodec": "āĻā§āĻĄā§āĻ",
|
"LabelCodec": "āĻā§āĻĄā§āĻ",
|
||||||
"LabelCollapseSeries": "āϏāĻŋāϰāĻŋāĻ āϏāĻā§āĻā§āĻāĻŋāϤ āĻāϰā§āύ",
|
"LabelCollapseSeries": "āϏāĻŋāϰāĻŋāĻ āϏāĻā§āĻā§āĻāĻŋāϤ āĻāϰā§āύ",
|
||||||
@@ -303,12 +314,25 @@
|
|||||||
"LabelEmailSettingsTestAddress": "āĻĒāϰā§āĻā§āώāĻžāϰ āĻ āĻŋāĻāĻžāύāĻž",
|
"LabelEmailSettingsTestAddress": "āĻĒāϰā§āĻā§āώāĻžāϰ āĻ āĻŋāĻāĻžāύāĻž",
|
||||||
"LabelEmbeddedCover": "āĻāĻŽā§āĻŦā§āĻĄā§āĻĄ āĻāĻāĻžāϰ",
|
"LabelEmbeddedCover": "āĻāĻŽā§āĻŦā§āĻĄā§āĻĄ āĻāĻāĻžāϰ",
|
||||||
"LabelEnable": "āϏāĻā§āώāĻŽ āĻāϰā§āύ",
|
"LabelEnable": "āϏāĻā§āώāĻŽ āĻāϰā§āύ",
|
||||||
|
"LabelEncodingBackupLocation": "āĻāĻĒāύāĻžāϰ āĻāϏāϞ āĻ
āĻĄāĻŋāĻ āĻĢāĻžāĻāϞāĻā§āϞā§āϰ āĻāĻāĻāĻŋ āĻŦā§āϝāĻžāĻāĻāĻĒ āĻāĻāĻžāύ⧠āϏāĻāϰāĻā§āώāĻŖ āĻāϰāĻž āĻšāĻŦā§:",
|
||||||
|
"LabelEncodingChaptersNotEmbedded": "āĻŽāĻžāϞā§āĻāĻŋ-āĻā§āϰā§āϝāĻžāĻ āĻ
āĻĄāĻŋāĻāĻŦā§āĻāĻā§āϞā§āϤ⧠āĻ
āϧā§āϝāĻžāϝāĻŧ āĻāĻŽā§āĻŦā§āĻĄ āĻāϰāĻž āĻšāϝāĻŧ āύāĻžāĨ¤",
|
||||||
|
"LabelEncodingClearItemCache": "āĻĒāϰā§āϝāĻžāϝāĻŧāĻā§āϰāĻŽā§ āĻāĻāĻā§āĻŽ āĻā§āϝāĻžāĻļā§ āĻĒāϰāĻŋāώā§āĻāĻžāϰ āĻāϰāϤ⧠āĻā§āϞāĻŦā§āύ āύāĻžāĨ¤",
|
||||||
|
"LabelEncodingFinishedM4B": "āϏāĻŽāĻžāĻĒā§āϤ āĻšāĻā§āĻž M4B-āĻā§āϞ⧠āĻāĻĒāύāĻžāϰ āĻ
āĻĄāĻŋāĻāĻŦā§āĻ āĻĢā§āϞā§āĻĄāĻžāϰ⧠āĻāĻāĻžāύ⧠āϰāĻžāĻāĻž āĻšāĻŦā§:",
|
||||||
|
"LabelEncodingInfoEmbedded": "āĻāĻĒāύāĻžāϰ āĻ
āĻĄāĻŋāĻāĻŦā§āĻ āĻĢā§āϞā§āĻĄāĻžāϰā§āϰ āĻāĻŋāϤāϰ⧠āĻ
āĻĄāĻŋāĻ āĻā§āϰā§āϝāĻžāĻāĻā§āϞā§āϤ⧠āĻŽā§āĻāĻžāĻĄā§āĻāĻž āĻāĻŽāĻŦā§āĻĄ āĻāϰāĻž āĻšāĻŦā§āĨ¤",
|
||||||
|
"LabelEncodingStartedNavigation": "āĻāĻāĻŦāĻžāϰ āĻāĻžāϏā§āĻ āĻļā§āϰ⧠āĻšāϞ⧠āĻāĻĒāύāĻŋ āĻāĻ āĻĒā§āώā§āĻ āĻž āĻĨā§āĻā§ āĻ
āύā§āϝāϤā§āϰ āϝā§āϤ⧠āĻĒāĻžāϰā§āύāĨ¤",
|
||||||
|
"LabelEncodingTimeWarning": "āĻāύāĻā§āĻĄāĻŋāĻ ā§Šā§Ļ āĻŽāĻŋāύāĻŋāĻ āĻĒāϰā§āϝāύā§āϤ āϏāĻŽāϝāĻŧ āύāĻŋāϤ⧠āĻĒāĻžāϰā§āĨ¤",
|
||||||
|
"LabelEncodingWarningAdvancedSettings": "āϏāϤāϰā§āĻāϤāĻž: āĻāĻ āϏā§āĻāĻŋāĻāϏ āĻāĻĒāĻĄā§āĻ āĻāϰāĻŦā§āύ āύāĻž, āϝāĻĻāĻŋ āύāĻž āĻāĻĒāύāĻŋ ffmpeg āĻāύāĻā§āĻĄāĻŋāĻ āĻŦāĻŋāĻāϞā§āĻĒāĻā§āϞā§āϰ āϏāĻžāĻĨā§ āĻĒāϰāĻŋāĻāĻŋāϤ āĻšāύāĨ¤",
|
||||||
|
"LabelEncodingWatcherDisabled": "āĻāĻĒāύāĻžāϰ āϝāĻĻāĻŋ āĻĒāϰā§āϝāĻŦā§āĻā§āώāĻ āĻ
āĻā§āώāĻŽ āĻĨāĻžāĻā§ āϤāĻŦā§ āĻāĻĒāύāĻžāĻā§ āĻĒāϰ⧠āĻāĻ āĻ
āĻĄāĻŋāĻāĻŦā§āĻāĻāĻŋ āĻĒā§āύāϰāĻžāϝāĻŧ āϏā§āĻā§āϝāĻžāύ āĻāϰāϤ⧠āĻšāĻŦā§āĨ¤",
|
||||||
"LabelEnd": "āϏāĻŽāĻžāĻĒā§āϤ",
|
"LabelEnd": "āϏāĻŽāĻžāĻĒā§āϤ",
|
||||||
"LabelEndOfChapter": "āĻ
āϧā§āϝāĻžāϝāĻŧā§āϰ āϏāĻŽāĻžāĻĒā§āϤāĻŋ",
|
"LabelEndOfChapter": "āĻ
āϧā§āϝāĻžāϝāĻŧā§āϰ āϏāĻŽāĻžāĻĒā§āϤāĻŋ",
|
||||||
"LabelEpisode": "āĻĒāϰā§āĻŦ",
|
"LabelEpisode": "āĻĒāϰā§āĻŦ",
|
||||||
|
"LabelEpisodeNotLinkedToRssFeed": "āĻĒāϰā§āĻŦāĻāĻŋ āĻāϰāĻāϏāĻāϏ āĻĢāĻŋāĻĄā§āϰ āϏāĻžāĻĨā§ āϏāĻāϝā§āĻā§āϤ āĻāϰāĻž āĻšāϝāĻŧāύāĻŋ",
|
||||||
|
"LabelEpisodeNumber": "āĻĒāϰā§āĻŦ #{0}",
|
||||||
"LabelEpisodeTitle": "āĻĒāϰā§āĻŦā§āϰ āĻļāĻŋāϰā§āύāĻžāĻŽ",
|
"LabelEpisodeTitle": "āĻĒāϰā§āĻŦā§āϰ āĻļāĻŋāϰā§āύāĻžāĻŽ",
|
||||||
"LabelEpisodeType": "āĻĒāϰā§āĻŦā§āϰ āϧāϰāύ",
|
"LabelEpisodeType": "āĻĒāϰā§āĻŦā§āϰ āϧāϰāύ",
|
||||||
|
"LabelEpisodeUrlFromRssFeed": "āĻāϰāĻāϏāĻāϏ āĻĢāĻŋāĻĄ āĻĨā§āĻā§ āĻĒāϰā§āĻŦ URL",
|
||||||
"LabelEpisodes": "āĻĒāϰā§āĻŦāĻā§āϞā§",
|
"LabelEpisodes": "āĻĒāϰā§āĻŦāĻā§āϞā§",
|
||||||
|
"LabelEpisodic": "āĻĒā§āϰāĻžāϏāĻā§āĻāĻŋāĻ",
|
||||||
"LabelExample": "āĻāĻĻāĻžāĻšāϰāĻŖ",
|
"LabelExample": "āĻāĻĻāĻžāĻšāϰāĻŖ",
|
||||||
"LabelExpandSeries": "āϏāĻŋāϰāĻŋāĻ āĻĒā§āϰāϏāĻžāϰāĻŋāϤ āĻāϰā§āύ",
|
"LabelExpandSeries": "āϏāĻŋāϰāĻŋāĻ āĻĒā§āϰāϏāĻžāϰāĻŋāϤ āĻāϰā§āύ",
|
||||||
"LabelExpandSubSeries": "āϏāĻžāĻŦ āϏāĻŋāϰāĻŋāĻ āĻĒā§āϰāϏāĻžāϰāĻŋāϤ āĻāϰā§āύ",
|
"LabelExpandSubSeries": "āϏāĻžāĻŦ āϏāĻŋāϰāĻŋāĻ āĻĒā§āϰāϏāĻžāϰāĻŋāϤ āĻāϰā§āύ",
|
||||||
@@ -336,6 +360,7 @@
|
|||||||
"LabelFontScale": "āĻĢāύā§āĻ āϏā§āĻā§āϞ",
|
"LabelFontScale": "āĻĢāύā§āĻ āϏā§āĻā§āϞ",
|
||||||
"LabelFontStrikethrough": "āĻ
āĻŦāĻā§āĻā§āĻĻāύ āϰā§āĻāĻž",
|
"LabelFontStrikethrough": "āĻ
āĻŦāĻā§āĻā§āĻĻāύ āϰā§āĻāĻž",
|
||||||
"LabelFormat": "āĻĢāϰāĻŽā§āϝāĻžāĻ",
|
"LabelFormat": "āĻĢāϰāĻŽā§āϝāĻžāĻ",
|
||||||
|
"LabelFull": "āĻĒā§āϰā§āĻŖ",
|
||||||
"LabelGenre": "āĻāϰāĻžāύāĻž",
|
"LabelGenre": "āĻāϰāĻžāύāĻž",
|
||||||
"LabelGenres": "āĻāϰāĻžāύāĻžāĻā§āϞā§",
|
"LabelGenres": "āĻāϰāĻžāύāĻžāĻā§āϞā§",
|
||||||
"LabelHardDeleteFile": "āĻā§āϰāĻĒā§āϰā§āĻŦāĻ āĻĢāĻžāĻāϞ āĻŽā§āĻā§ āĻĢā§āϞā§āύ",
|
"LabelHardDeleteFile": "āĻā§āϰāĻĒā§āϰā§āĻŦāĻ āĻĢāĻžāĻāϞ āĻŽā§āĻā§ āĻĢā§āϞā§āύ",
|
||||||
@@ -391,6 +416,10 @@
|
|||||||
"LabelLowestPriority": "āϏāϰā§āĻŦāύāĻŋāĻŽā§āύ āĻ
āĻā§āϰāĻžāϧāĻŋāĻāĻžāϰ",
|
"LabelLowestPriority": "āϏāϰā§āĻŦāύāĻŋāĻŽā§āύ āĻ
āĻā§āϰāĻžāϧāĻŋāĻāĻžāϰ",
|
||||||
"LabelMatchExistingUsersBy": "āĻŦāĻŋāĻĻā§āϝāĻŽāĻžāύ āĻŦā§āϝāĻŦāĻšāĻžāϰāĻāĻžāϰā§āĻĻā§āϰ āĻĻā§āĻŦāĻžāϰāĻž āĻŽāĻŋāϞāĻŋāϤ āĻāϰā§āύ",
|
"LabelMatchExistingUsersBy": "āĻŦāĻŋāĻĻā§āϝāĻŽāĻžāύ āĻŦā§āϝāĻŦāĻšāĻžāϰāĻāĻžāϰā§āĻĻā§āϰ āĻĻā§āĻŦāĻžāϰāĻž āĻŽāĻŋāϞāĻŋāϤ āĻāϰā§āύ",
|
||||||
"LabelMatchExistingUsersByDescription": "āĻŦāĻŋāĻĻā§āϝāĻŽāĻžāύ āĻŦā§āϝāĻŦāĻšāĻžāϰāĻāĻžāϰā§āĻĻā§āϰ āϏāĻāϝā§āĻ āĻāϰāĻžāϰ āĻāύā§āϝ āĻŦā§āϝāĻŦāĻšā§āϤ āĻšāϝāĻŧāĨ¤ āĻāĻāĻŦāĻžāϰ āϏāĻāϝā§āĻā§āϤ āĻšāϞā§, āĻŦā§āϝāĻŦāĻšāĻžāϰāĻāĻžāϰā§āĻĻā§āϰ āĻāĻĒāύāĻžāϰ SSO āĻĒā§āϰāĻĻāĻžāύāĻāĻžāϰā§āϰ āĻĨā§āĻā§ āĻāĻāĻāĻŋ āĻ
āύāύā§āϝ āĻāĻāĻĄāĻŋ āĻĻā§āĻŦāĻžāϰāĻž āĻŽāĻŋāϞāĻŋāϤ āĻšāĻŦā§",
|
"LabelMatchExistingUsersByDescription": "āĻŦāĻŋāĻĻā§āϝāĻŽāĻžāύ āĻŦā§āϝāĻŦāĻšāĻžāϰāĻāĻžāϰā§āĻĻā§āϰ āϏāĻāϝā§āĻ āĻāϰāĻžāϰ āĻāύā§āϝ āĻŦā§āϝāĻŦāĻšā§āϤ āĻšāϝāĻŧāĨ¤ āĻāĻāĻŦāĻžāϰ āϏāĻāϝā§āĻā§āϤ āĻšāϞā§, āĻŦā§āϝāĻŦāĻšāĻžāϰāĻāĻžāϰā§āĻĻā§āϰ āĻāĻĒāύāĻžāϰ SSO āĻĒā§āϰāĻĻāĻžāύāĻāĻžāϰā§āϰ āĻĨā§āĻā§ āĻāĻāĻāĻŋ āĻ
āύāύā§āϝ āĻāĻāĻĄāĻŋ āĻĻā§āĻŦāĻžāϰāĻž āĻŽāĻŋāϞāĻŋāϤ āĻšāĻŦā§",
|
||||||
|
"LabelMaxEpisodesToDownload": "āϏāϰā§āĻŦāĻžāϧāĻŋāĻ # āĻāĻŋ āĻĒāϰā§āĻŦ āĻĄāĻžāĻāύāϞā§āĻĄ āĻāϰāĻž āĻšāĻŦā§āĨ¤ āĻ
āϏā§āĻŽā§āϰ āĻāύā§āϝ 0 āĻŦā§āϝāĻŦāĻšāĻžāϰ āĻāϰā§āύāĨ¤",
|
||||||
|
"LabelMaxEpisodesToDownloadPerCheck": "āĻĒā§āϰāϤāĻŋ āĻāĻŋāϏā§āϤāĻŋāϤ⧠āϏāϰā§āĻŦāĻžāϧāĻŋāĻ # āĻāĻŋ āύāϤā§āύ āĻĒāϰā§āĻŦ āĻĄāĻžāĻāύāϞā§āĻĄ āĻāϰāĻž āĻšāĻŦā§",
|
||||||
|
"LabelMaxEpisodesToKeep": "āϏāϰā§āĻŦā§āĻā§āĻ # āĻāĻŋ āĻĒāϰā§āĻŦ āϰāĻžāĻāĻž āĻšāĻŦā§",
|
||||||
|
"LabelMaxEpisodesToKeepHelp": "ā§Ļ āĻā§āύ āϏāϰā§āĻŦā§āĻā§āĻ āϏā§āĻŽāĻž āϏā§āĻ āĻāϰ⧠āύāĻžāĨ¤ āĻāĻāĻāĻŋ āύāϤā§āύ āĻĒāϰā§āĻŦ āϏā§āĻŦāϝāĻŧāĻāĻā§āϰāĻŋāϝāĻŧ-āĻĄāĻžāĻāύāϞā§āĻĄ āĻšāĻāϝāĻŧāĻžāϰ āĻĒāϰ⧠āĻāĻĒāύāĻžāϰ āϝāĻĻāĻŋ X-āĻāϰ āĻŦā§āĻļāĻŋ āĻĒāϰā§āĻŦ āĻĨāĻžāĻā§ āϤāĻŦā§ āĻāĻāĻŋ āϏāĻŦāĻā§āϝāĻŧā§ āĻĒā§āϰāĻžāύ⧠āĻĒāϰā§āĻŦāĻāĻŋ āĻŽā§āĻā§ āĻĢā§āϞāĻŦā§āĨ¤ āĻāĻāĻŋ āĻĒā§āϰāϤāĻŋ āύāϤā§āύ āĻĄāĻžāĻāύāϞā§āĻĄā§āϰ āĻāύā§āϝ āĻļā§āϧā§āĻŽāĻžāϤā§āϰ ā§§ āĻāĻŋ āĻĒāϰā§āĻŦ āĻŽā§āĻā§ āĻĢā§āϞāĻŦā§āĨ¤",
|
||||||
"LabelMediaPlayer": "āĻŽāĻŋāĻĄāĻŋāϝāĻŧāĻž āĻĒā§āϞā§āϝāĻŧāĻžāϰ",
|
"LabelMediaPlayer": "āĻŽāĻŋāĻĄāĻŋāϝāĻŧāĻž āĻĒā§āϞā§āϝāĻŧāĻžāϰ",
|
||||||
"LabelMediaType": "āĻŽāĻŋāĻĄāĻŋāϝāĻŧāĻžāϰ āϧāϰāύ",
|
"LabelMediaType": "āĻŽāĻŋāĻĄāĻŋāϝāĻŧāĻžāϰ āϧāϰāύ",
|
||||||
"LabelMetaTag": "āĻŽā§āĻāĻž āĻā§āϝāĻžāĻ",
|
"LabelMetaTag": "āĻŽā§āĻāĻž āĻā§āϝāĻžāĻ",
|
||||||
@@ -436,12 +465,14 @@
|
|||||||
"LabelOpenIDGroupClaimDescription": "āĻāĻĒā§āύāĻāĻāĻĄāĻŋ āĻĻāĻžāĻŦāĻŋāϰ āύāĻžāĻŽ āϝāĻžāϤ⧠āĻŦā§āϝāĻŦāĻšāĻžāϰāĻāĻžāϰā§āϰ āĻā§āώā§āĻ ā§āϰ āĻāĻāĻāĻŋ āϤāĻžāϞāĻŋāĻāĻž āĻĨāĻžāĻā§āĨ¤ āϏāĻžāϧāĻžāϰāĻŖāϤ <code>āĻā§āϰā§āĻĒ</code> āĻšāĻŋāϏāĻžāĻŦā§ āĻāϞā§āϞā§āĻ āĻāϰāĻž āĻšāϝāĻŧāĨ¤ <b>āĻāύāĻĢāĻŋāĻāĻžāϰ āĻāϰāĻž āĻĨāĻžāĻāϞā§</b>, āĻ
ā§āϝāĻžāĻĒā§āϞāĻŋāĻā§āĻļāύāĻāĻŋ āϏā§āĻŦāϝāĻŧāĻāĻā§āϰāĻŋāϝāĻŧāĻāĻžāĻŦā§ āĻāϰ āĻāĻĒāϰ āĻāĻŋāϤā§āϤāĻŋ āĻāϰ⧠āĻŦā§āϝāĻŦāĻšāĻžāϰāĻāĻžāϰā§āϰ āĻā§āώā§āĻ ā§āϰ āϏāĻĻāϏā§āϝāĻĒāĻĻ āύāĻŋāϰā§āϧāĻžāϰāĻŖ āĻāϰāĻŦā§, āĻļāϰā§āϤ āĻāĻ āϝ⧠āĻāĻ āĻā§āώā§āĻ ā§āĻā§āϞāĻŋ āĻā§āϏ-āĻ
āϏāĻāĻŦā§āĻĻāύāĻļā§āϞāĻāĻžāĻŦā§ āĻĻāĻžāĻŦāĻŋāϤ⧠'āĻ
ā§āϝāĻžāĻĄāĻŽāĻŋāύ', 'āĻŦā§āϝāĻŦāĻšāĻžāϰāĻāĻžāϰā§' āĻŦāĻž 'āĻ
āϤāĻŋāĻĨāĻŋ' āύāĻžāĻŽ āĻĻā§āĻāϝāĻŧāĻž āĻšāϝāĻŧ⧎ āĻĻāĻžāĻŦāĻŋāϤ⧠āĻāĻāĻāĻŋ āϤāĻžāϞāĻŋāĻāĻž āĻĨāĻžāĻāĻž āĻāĻāĻŋāϤ āĻāĻŦāĻ āϝāĻĻāĻŋ āĻāĻāĻāύ āĻŦā§āϝāĻŦāĻšāĻžāϰāĻāĻžāϰ⧠āĻāĻāĻžāϧāĻŋāĻ āĻā§āώā§āĻ ā§āϰ āĻ
āύā§āϤāϰā§āĻāϤ āĻšāϝāĻŧ āϤāĻŦā§ āĻ
ā§āϝāĻžāĻĒā§āϞāĻŋāĻā§āĻļāύāĻāĻŋ āĻŦāϰāĻžāĻĻā§āĻĻ āĻāϰāĻŦā§ āϏāϰā§āĻŦā§āĻā§āĻ āϏā§āϤāϰā§āϰ āĻ
ā§āϝāĻžāĻā§āϏā§āϏā§āϰ āϏāĻžāĻĨā§ āϏāĻā§āĻāϤāĻŋāĻĒā§āϰā§āĻŖ āĻā§āĻŽāĻŋāĻāĻžā§ˇ āϝāĻĻāĻŋ āĻā§āύāĻ āĻā§āώā§āĻ ā§āϰ āϏāĻžāĻĨā§ āĻŽā§āϞ⧠āύāĻž, āϤāĻŦā§ āĻ
ā§āϝāĻžāĻā§āϏā§āϏ āĻ
āϏā§āĻŦā§āĻāĻžāϰ āĻāϰāĻž āĻšāĻŦā§āĨ¤",
|
"LabelOpenIDGroupClaimDescription": "āĻāĻĒā§āύāĻāĻāĻĄāĻŋ āĻĻāĻžāĻŦāĻŋāϰ āύāĻžāĻŽ āϝāĻžāϤ⧠āĻŦā§āϝāĻŦāĻšāĻžāϰāĻāĻžāϰā§āϰ āĻā§āώā§āĻ ā§āϰ āĻāĻāĻāĻŋ āϤāĻžāϞāĻŋāĻāĻž āĻĨāĻžāĻā§āĨ¤ āϏāĻžāϧāĻžāϰāĻŖāϤ <code>āĻā§āϰā§āĻĒ</code> āĻšāĻŋāϏāĻžāĻŦā§ āĻāϞā§āϞā§āĻ āĻāϰāĻž āĻšāϝāĻŧāĨ¤ <b>āĻāύāĻĢāĻŋāĻāĻžāϰ āĻāϰāĻž āĻĨāĻžāĻāϞā§</b>, āĻ
ā§āϝāĻžāĻĒā§āϞāĻŋāĻā§āĻļāύāĻāĻŋ āϏā§āĻŦāϝāĻŧāĻāĻā§āϰāĻŋāϝāĻŧāĻāĻžāĻŦā§ āĻāϰ āĻāĻĒāϰ āĻāĻŋāϤā§āϤāĻŋ āĻāϰ⧠āĻŦā§āϝāĻŦāĻšāĻžāϰāĻāĻžāϰā§āϰ āĻā§āώā§āĻ ā§āϰ āϏāĻĻāϏā§āϝāĻĒāĻĻ āύāĻŋāϰā§āϧāĻžāϰāĻŖ āĻāϰāĻŦā§, āĻļāϰā§āϤ āĻāĻ āϝ⧠āĻāĻ āĻā§āώā§āĻ ā§āĻā§āϞāĻŋ āĻā§āϏ-āĻ
āϏāĻāĻŦā§āĻĻāύāĻļā§āϞāĻāĻžāĻŦā§ āĻĻāĻžāĻŦāĻŋāϤ⧠'āĻ
ā§āϝāĻžāĻĄāĻŽāĻŋāύ', 'āĻŦā§āϝāĻŦāĻšāĻžāϰāĻāĻžāϰā§' āĻŦāĻž 'āĻ
āϤāĻŋāĻĨāĻŋ' āύāĻžāĻŽ āĻĻā§āĻāϝāĻŧāĻž āĻšāϝāĻŧ⧎ āĻĻāĻžāĻŦāĻŋāϤ⧠āĻāĻāĻāĻŋ āϤāĻžāϞāĻŋāĻāĻž āĻĨāĻžāĻāĻž āĻāĻāĻŋāϤ āĻāĻŦāĻ āϝāĻĻāĻŋ āĻāĻāĻāύ āĻŦā§āϝāĻŦāĻšāĻžāϰāĻāĻžāϰ⧠āĻāĻāĻžāϧāĻŋāĻ āĻā§āώā§āĻ ā§āϰ āĻ
āύā§āϤāϰā§āĻāϤ āĻšāϝāĻŧ āϤāĻŦā§ āĻ
ā§āϝāĻžāĻĒā§āϞāĻŋāĻā§āĻļāύāĻāĻŋ āĻŦāϰāĻžāĻĻā§āĻĻ āĻāϰāĻŦā§ āϏāϰā§āĻŦā§āĻā§āĻ āϏā§āϤāϰā§āϰ āĻ
ā§āϝāĻžāĻā§āϏā§āϏā§āϰ āϏāĻžāĻĨā§ āϏāĻā§āĻāϤāĻŋāĻĒā§āϰā§āĻŖ āĻā§āĻŽāĻŋāĻāĻžā§ˇ āϝāĻĻāĻŋ āĻā§āύāĻ āĻā§āώā§āĻ ā§āϰ āϏāĻžāĻĨā§ āĻŽā§āϞ⧠āύāĻž, āϤāĻŦā§ āĻ
ā§āϝāĻžāĻā§āϏā§āϏ āĻ
āϏā§āĻŦā§āĻāĻžāϰ āĻāϰāĻž āĻšāĻŦā§āĨ¤",
|
||||||
"LabelOpenRSSFeed": "āĻāϰāĻāϏāĻāϏ āĻĢāĻŋāĻĄ āĻā§āϞā§āύ",
|
"LabelOpenRSSFeed": "āĻāϰāĻāϏāĻāϏ āĻĢāĻŋāĻĄ āĻā§āϞā§āύ",
|
||||||
"LabelOverwrite": "āĻĒā§āύāĻāϞāĻŋāĻāĻŋāϤ",
|
"LabelOverwrite": "āĻĒā§āύāĻāϞāĻŋāĻāĻŋāϤ",
|
||||||
|
"LabelPaginationPageXOfY": "{1} āĻāĻŋāϰ āĻŽāϧā§āϝ⧠{0} āĻĒā§āώā§āĻ āĻž",
|
||||||
"LabelPassword": "āĻĒāĻžāϏāĻāϝāĻŧāĻžāϰā§āĻĄ",
|
"LabelPassword": "āĻĒāĻžāϏāĻāϝāĻŧāĻžāϰā§āĻĄ",
|
||||||
"LabelPath": "āĻĒāĻĨ",
|
"LabelPath": "āĻĒāĻĨ",
|
||||||
"LabelPermanent": "āϏā§āĻĨāĻžāϝāĻŧā§",
|
"LabelPermanent": "āϏā§āĻĨāĻžāϝāĻŧā§",
|
||||||
"LabelPermissionsAccessAllLibraries": "āϏāĻŽāϏā§āϤ āϞāĻžāĻāĻŦā§āϰā§āϰāĻŋ āĻ
ā§āϝāĻžāĻā§āϏā§āϏ āĻāϰāϤ⧠āĻĒāĻžāϰāĻŦā§",
|
"LabelPermissionsAccessAllLibraries": "āϏāĻŽāϏā§āϤ āϞāĻžāĻāĻŦā§āϰā§āϰāĻŋ āĻ
ā§āϝāĻžāĻā§āϏā§āϏ āĻāϰāϤ⧠āĻĒāĻžāϰāĻŦā§",
|
||||||
"LabelPermissionsAccessAllTags": "āϏāĻŽāϏā§āϤ āĻā§āϝāĻžāĻ āĻ
ā§āϝāĻžāĻā§āϏā§āϏ āĻāϰāϤ⧠āĻĒāĻžāϰāĻŦā§",
|
"LabelPermissionsAccessAllTags": "āϏāĻŽāϏā§āϤ āĻā§āϝāĻžāĻ āĻ
ā§āϝāĻžāĻā§āϏā§āϏ āĻāϰāϤ⧠āĻĒāĻžāϰāĻŦā§",
|
||||||
"LabelPermissionsAccessExplicitContent": "āϏā§āĻĒāώā§āĻ āĻŦāĻŋāώāϝāĻŧāĻŦāϏā§āϤ⧠āĻ
ā§āϝāĻžāĻā§āϏā§āϏ āĻāϰāϤ⧠āĻĒāĻžāϰā§",
|
"LabelPermissionsAccessExplicitContent": "āϏā§āĻĒāώā§āĻ āĻŦāĻŋāώāϝāĻŧāĻŦāϏā§āϤ⧠āĻ
ā§āϝāĻžāĻā§āϏā§āϏ āĻāϰāϤ⧠āĻĒāĻžāϰā§",
|
||||||
|
"LabelPermissionsCreateEreader": "āĻāϰāĻŋāĻĄāĻžāϰ āϤā§āϰāĻŋ āĻāϰāϤ⧠āĻĒāĻžāϰā§āύ",
|
||||||
"LabelPermissionsDelete": "āĻŽā§āĻā§ āĻĻāĻŋāϤ⧠āĻĒāĻžāϰāĻŦā§",
|
"LabelPermissionsDelete": "āĻŽā§āĻā§ āĻĻāĻŋāϤ⧠āĻĒāĻžāϰāĻŦā§",
|
||||||
"LabelPermissionsDownload": "āĻĄāĻžāĻāύāϞā§āĻĄ āĻāϰāϤ⧠āĻĒāĻžāϰāĻŦā§",
|
"LabelPermissionsDownload": "āĻĄāĻžāĻāύāϞā§āĻĄ āĻāϰāϤ⧠āĻĒāĻžāϰāĻŦā§",
|
||||||
"LabelPermissionsUpdate": "āĻāĻĒāĻĄā§āĻ āĻāϰāϤ⧠āĻĒāĻžāϰāĻŦā§",
|
"LabelPermissionsUpdate": "āĻāĻĒāĻĄā§āĻ āĻāϰāϤ⧠āĻĒāĻžāϰāĻŦā§",
|
||||||
@@ -465,6 +496,8 @@
|
|||||||
"LabelPubDate": "āĻĒā§āϰāĻāĻžāĻļā§āϰ āϤāĻžāϰāĻŋāĻ",
|
"LabelPubDate": "āĻĒā§āϰāĻāĻžāĻļā§āϰ āϤāĻžāϰāĻŋāĻ",
|
||||||
"LabelPublishYear": "āĻĒā§āϰāĻāĻžāĻļā§āϰ āĻŦāĻāϰ",
|
"LabelPublishYear": "āĻĒā§āϰāĻāĻžāĻļā§āϰ āĻŦāĻāϰ",
|
||||||
"LabelPublishedDate": "āĻĒā§āϰāĻāĻžāĻļāĻŋāϤ {0}",
|
"LabelPublishedDate": "āĻĒā§āϰāĻāĻžāĻļāĻŋāϤ {0}",
|
||||||
|
"LabelPublishedDecade": "āĻĒā§āϰāĻāĻžāĻļāύāĻžāϰ āĻĻāĻļāĻ",
|
||||||
|
"LabelPublishedDecades": "āĻĒā§āϰāĻāĻžāĻļāύāĻžāϰ āĻĻāĻļāĻāĻā§āϞā§",
|
||||||
"LabelPublisher": "āĻĒā§āϰāĻāĻžāĻļāĻ",
|
"LabelPublisher": "āĻĒā§āϰāĻāĻžāĻļāĻ",
|
||||||
"LabelPublishers": "āĻĒā§āϰāĻāĻžāĻļāĻāϰāĻž",
|
"LabelPublishers": "āĻĒā§āϰāĻāĻžāĻļāĻāϰāĻž",
|
||||||
"LabelRSSFeedCustomOwnerEmail": "āĻāĻžāϏā§āĻāĻŽ āĻŽāĻžāϞāĻŋāĻā§āϰ āĻāĻŽā§āĻāϞ",
|
"LabelRSSFeedCustomOwnerEmail": "āĻāĻžāϏā§āĻāĻŽ āĻŽāĻžāϞāĻŋāĻā§āϰ āĻāĻŽā§āĻāϞ",
|
||||||
@@ -484,21 +517,28 @@
|
|||||||
"LabelRedo": "āĻĒā§āύāϰāĻžāϝāĻŧ āĻāϰā§āύ",
|
"LabelRedo": "āĻĒā§āύāϰāĻžāϝāĻŧ āĻāϰā§āύ",
|
||||||
"LabelRegion": "āĻ
āĻā§āĻāϞ",
|
"LabelRegion": "āĻ
āĻā§āĻāϞ",
|
||||||
"LabelReleaseDate": "āĻāύā§āĻŽā§āĻžāĻāύā§āϰ āϤāĻžāϰāĻŋāĻ",
|
"LabelReleaseDate": "āĻāύā§āĻŽā§āĻžāĻāύā§āϰ āϤāĻžāϰāĻŋāĻ",
|
||||||
|
"LabelRemoveAllMetadataAbs": "āϏāĻŽāϏā§āϤ metadata.abs āĻĢāĻžāĻāϞ āϏāϰāĻžāύ",
|
||||||
|
"LabelRemoveAllMetadataJson": "āϏāĻŽāϏā§āϤ metadata.json āĻĢāĻžāĻāϞ āϏāϰāĻžāύ",
|
||||||
"LabelRemoveCover": "āĻāĻāĻžāϰ āϏāϰāĻžāύ",
|
"LabelRemoveCover": "āĻāĻāĻžāϰ āϏāϰāĻžāύ",
|
||||||
|
"LabelRemoveMetadataFile": "āϞāĻžāĻāĻŦā§āϰā§āϰāĻŋ āĻāĻāĻā§āĻŽ āĻĢā§āϞā§āĻĄāĻžāϰ⧠āĻŽā§āĻāĻžāĻĄā§āĻāĻž āĻĢāĻžāĻāϞ āϏāϰāĻžāύ",
|
||||||
|
"LabelRemoveMetadataFileHelp": "āĻāĻĒāύāĻžāϰ {0} āĻĢā§āϞā§āĻĄāĻžāϰā§āϰ āϏāĻŽāϏā§āϤ metadata.json āĻāĻŦāĻ metadata.abs āĻĢāĻžāĻāϞāĻā§āϞāĻŋ āϏāϰāĻžāύāĨ¤",
|
||||||
"LabelRowsPerPage": "āĻĒā§āϰāϤāĻŋ āĻĒā§āώā§āĻ āĻžāϝāĻŧ āϏāĻžāϰāĻŋ",
|
"LabelRowsPerPage": "āĻĒā§āϰāϤāĻŋ āĻĒā§āώā§āĻ āĻžāϝāĻŧ āϏāĻžāϰāĻŋ",
|
||||||
"LabelSearchTerm": "āĻ
āύā§āϏāύā§āϧāĻžāύ āĻļāĻŦā§āĻĻ",
|
"LabelSearchTerm": "āĻ
āύā§āϏāύā§āϧāĻžāύ āĻļāĻŦā§āĻĻ",
|
||||||
"LabelSearchTitle": "āĻ
āύā§āϏāύā§āϧāĻžāύ āĻļāĻŋāϰā§āύāĻžāĻŽ",
|
"LabelSearchTitle": "āĻ
āύā§āϏāύā§āϧāĻžāύ āĻļāĻŋāϰā§āύāĻžāĻŽ",
|
||||||
"LabelSearchTitleOrASIN": "āĻ
āύā§āϏāύā§āϧāĻžāύ āĻļāĻŋāϰā§āύāĻžāĻŽ āĻŦāĻž ASIN",
|
"LabelSearchTitleOrASIN": "āĻ
āύā§āϏāύā§āϧāĻžāύ āĻļāĻŋāϰā§āύāĻžāĻŽ āĻŦāĻž ASIN",
|
||||||
"LabelSeason": "āϏā§āĻļāύ",
|
"LabelSeason": "āϏā§āĻļāύ",
|
||||||
|
"LabelSeasonNumber": "āĻŽāϰāϏā§āĻŽ #{0}",
|
||||||
"LabelSelectAll": "āϏāĻŦ āύāĻŋāϰā§āĻŦāĻžāĻāύ āĻāϰā§āύ",
|
"LabelSelectAll": "āϏāĻŦ āύāĻŋāϰā§āĻŦāĻžāĻāύ āĻāϰā§āύ",
|
||||||
"LabelSelectAllEpisodes": "āϏāĻŽāϏā§āϤ āĻĒāϰā§āĻŦ āύāĻŋāϰā§āĻŦāĻžāĻāύ āĻāϰā§āύ",
|
"LabelSelectAllEpisodes": "āϏāĻŽāϏā§āϤ āĻĒāϰā§āĻŦ āύāĻŋāϰā§āĻŦāĻžāĻāύ āĻāϰā§āύ",
|
||||||
"LabelSelectEpisodesShowing": "āĻĻā§āĻāĻžāύ⧠{0}āĻāĻŋ āĻĒāϰā§āĻŦ āύāĻŋāϰā§āĻŦāĻžāĻāύ āĻāϰā§āύ",
|
"LabelSelectEpisodesShowing": "āĻĻā§āĻāĻžāύ⧠{0}āĻāĻŋ āĻĒāϰā§āĻŦ āύāĻŋāϰā§āĻŦāĻžāĻāύ āĻāϰā§āύ",
|
||||||
"LabelSelectUsers": "āĻŦā§āϝāĻŦāĻšāĻžāϰāĻāĻžāϰ⧠āύāĻŋāϰā§āĻŦāĻžāĻāύ āĻāϰā§āύ",
|
"LabelSelectUsers": "āĻŦā§āϝāĻŦāĻšāĻžāϰāĻāĻžāϰ⧠āύāĻŋāϰā§āĻŦāĻžāĻāύ āĻāϰā§āύ",
|
||||||
"LabelSendEbookToDevice": "āĻ-āĻŦāĻ āĻĒāĻžāĻ āĻžāύ...",
|
"LabelSendEbookToDevice": "āĻ-āĻŦāĻ āĻĒāĻžāĻ āĻžāύ...",
|
||||||
"LabelSequence": "āĻā§āϰāĻŽ",
|
"LabelSequence": "āĻā§āϰāĻŽ",
|
||||||
|
"LabelSerial": "āϧāĻžāϰāĻžāĻŦāĻžāĻšāĻŋāĻ",
|
||||||
"LabelSeries": "āϏāĻŋāϰāĻŋāĻ",
|
"LabelSeries": "āϏāĻŋāϰāĻŋāĻ",
|
||||||
"LabelSeriesName": "āϏāĻŋāϰāĻŋāĻā§āϰ āύāĻžāĻŽ",
|
"LabelSeriesName": "āϏāĻŋāϰāĻŋāĻā§āϰ āύāĻžāĻŽ",
|
||||||
"LabelSeriesProgress": "āϏāĻŋāϰāĻŋāĻā§āϰ āĻ
āĻā§āϰāĻāϤāĻŋ",
|
"LabelSeriesProgress": "āϏāĻŋāϰāĻŋāĻā§āϰ āĻ
āĻā§āϰāĻāϤāĻŋ",
|
||||||
|
"LabelServerLogLevel": "āϏāĻžāϰā§āĻāĻžāϰ āϞāĻ āϞā§āĻā§āϞ",
|
||||||
"LabelServerYearReview": "āϏāĻžāϰā§āĻāĻžāϰā§āϰ āĻŦāĻžā§āϏāϰāĻŋāĻ āĻĒāϰā§āϝāĻžāϞā§āĻāύāĻž ({0})",
|
"LabelServerYearReview": "āϏāĻžāϰā§āĻāĻžāϰā§āϰ āĻŦāĻžā§āϏāϰāĻŋāĻ āĻĒāϰā§āϝāĻžāϞā§āĻāύāĻž ({0})",
|
||||||
"LabelSetEbookAsPrimary": "āĻĒā§āϰāĻžāĻĨāĻŽāĻŋāĻ āĻšāĻŋāϏāĻžāĻŦā§ āϏā§āĻ āĻāϰā§āύ",
|
"LabelSetEbookAsPrimary": "āĻĒā§āϰāĻžāĻĨāĻŽāĻŋāĻ āĻšāĻŋāϏāĻžāĻŦā§ āϏā§āĻ āĻāϰā§āύ",
|
||||||
"LabelSetEbookAsSupplementary": "āĻĒāϰāĻŋāĻĒā§āϰāĻ āĻšāĻŋāϏā§āĻŦā§ āϏā§āĻ āĻāϰā§āύ",
|
"LabelSetEbookAsSupplementary": "āĻĒāϰāĻŋāĻĒā§āϰāĻ āĻšāĻŋāϏā§āĻŦā§ āϏā§āĻ āĻāϰā§āύ",
|
||||||
@@ -523,6 +563,9 @@
|
|||||||
"LabelSettingsHideSingleBookSeriesHelp": "āϝ⧠āϏāĻŋāϰāĻŋāĻāĻā§āϞā§āϤ⧠āĻāĻāĻāĻŋ āĻŦāĻ āĻāĻā§ āϏā§āĻā§āϞ⧠āϏāĻŋāϰāĻŋāĻā§āϰ āĻĒāĻžāϤāĻž āĻāĻŦāĻ āύā§ā§ āĻĒā§āĻā§āϰ āϤāĻžāĻ āĻĨā§āĻā§ āϞā§āĻāĻŋāϝāĻŧā§ āϰāĻžāĻāĻž āĻšāĻŦā§āĨ¤",
|
"LabelSettingsHideSingleBookSeriesHelp": "āϝ⧠āϏāĻŋāϰāĻŋāĻāĻā§āϞā§āϤ⧠āĻāĻāĻāĻŋ āĻŦāĻ āĻāĻā§ āϏā§āĻā§āϞ⧠āϏāĻŋāϰāĻŋāĻā§āϰ āĻĒāĻžāϤāĻž āĻāĻŦāĻ āύā§ā§ āĻĒā§āĻā§āϰ āϤāĻžāĻ āĻĨā§āĻā§ āϞā§āĻāĻŋāϝāĻŧā§ āϰāĻžāĻāĻž āĻšāĻŦā§āĨ¤",
|
||||||
"LabelSettingsHomePageBookshelfView": "āύā§ā§ āĻĒā§āĻā§ āĻŦā§āĻāĻļā§āϞāĻĢ āĻāĻŋāĻ āĻŦā§āϝāĻŦāĻšāĻžāϰ āĻāϰā§āύ",
|
"LabelSettingsHomePageBookshelfView": "āύā§ā§ āĻĒā§āĻā§ āĻŦā§āĻāĻļā§āϞāĻĢ āĻāĻŋāĻ āĻŦā§āϝāĻŦāĻšāĻžāϰ āĻāϰā§āύ",
|
||||||
"LabelSettingsLibraryBookshelfView": "āϞāĻžāĻāĻŦā§āϰā§āϰāĻŋ āĻŦā§āĻāĻļā§āϞāĻĢ āĻāĻŋāĻ āĻŦā§āϝāĻŦāĻšāĻžāϰ āĻāϰā§āύ",
|
"LabelSettingsLibraryBookshelfView": "āϞāĻžāĻāĻŦā§āϰā§āϰāĻŋ āĻŦā§āĻāĻļā§āϞāĻĢ āĻāĻŋāĻ āĻŦā§āϝāĻŦāĻšāĻžāϰ āĻāϰā§āύ",
|
||||||
|
"LabelSettingsLibraryMarkAsFinishedPercentComplete": "āĻļāϤāĻāϰāĻž āϏāĻŽā§āĻĒā§āϰā§āĻŖ āĻāϰ āĻā§āϝāĻŧā§ āĻŦā§āĻļāĻŋ",
|
||||||
|
"LabelSettingsLibraryMarkAsFinishedTimeRemaining": "āĻŦāĻžāĻāĻŋ āϏāĻŽāϝāĻŧ (āϏā§āĻā§āύā§āĻĄ) āĻāϰ āĻā§āϝāĻŧā§ āĻāĻŽ",
|
||||||
|
"LabelSettingsLibraryMarkAsFinishedWhen": "āĻŽāĻŋāĻĄāĻŋāϝāĻŧāĻž āĻāĻāĻā§āĻŽāĻā§ āϏāĻŽāĻžāĻĒā§āϤ āĻšāĻŋāϏāĻžāĻŦā§ āĻāĻŋāĻšā§āύāĻŋāϤ āĻāϰā§āύ āϝāĻāύ",
|
||||||
"LabelSettingsOnlyShowLaterBooksInContinueSeries": "āĻāύā§āĻāĻŋāύāĻŋāĻ āϏāĻŋāϰāĻŋāĻā§ āĻāĻā§āϰ āĻŦāĻāĻā§āϞ⧠āĻāĻĄāĻŧāĻŋāϝāĻŧā§ āϝāĻžāύ",
|
"LabelSettingsOnlyShowLaterBooksInContinueSeries": "āĻāύā§āĻāĻŋāύāĻŋāĻ āϏāĻŋāϰāĻŋāĻā§ āĻāĻā§āϰ āĻŦāĻāĻā§āϞ⧠āĻāĻĄāĻŧāĻŋāϝāĻŧā§ āϝāĻžāύ",
|
||||||
"LabelSettingsOnlyShowLaterBooksInContinueSeriesHelp": "āĻāύā§āĻāĻŋāύāĻŋāĻ āϏāĻŋāϰāĻŋāĻā§āϰ āύā§ā§ āĻĒā§āĻ āĻļā§āϞā§āĻĢ āĻĻā§āĻāĻžāϝāĻŧ āϝ⧠āϏāĻŋāϰāĻŋāĻā§ āĻļā§āϰ⧠āĻšāϝāĻŧāύāĻŋ āĻāĻŽāύ āĻĒā§āϰāĻĨāĻŽ āĻŦāĻ āϝāĻžāϰ āĻ
āύā§āϤāϤ āĻāĻāĻāĻŋ āĻŦāĻ āĻļā§āώ āĻšāϝāĻŧā§āĻā§ āĻāĻŦāĻ āĻā§āύ⧠āĻŦāĻ āĻāϞāĻā§ āύāĻžāĨ¤ āĻāĻ āϏā§āĻāĻŋāĻāĻāĻŋ āϏāĻā§āώāĻŽ āĻāϰāϞ⧠āĻļā§āϰ⧠āύāĻž āĻšāĻāϝāĻŧāĻž āĻĒā§āϰāĻĨāĻŽ āĻŦāĻāĻāĻŋāϰ āĻĒāϰāĻŋāĻŦāϰā§āϤ⧠āϏāĻŦāĻā§āϝāĻŧā§ āĻĻā§āϰā§āϰ āϏāĻŽā§āĻĒā§āϰā§āĻŖ āĻŦāĻ āĻĨā§āĻā§ āϏāĻŋāϰāĻŋāĻ āĻāϞāϤ⧠āĻĨāĻžāĻāĻŦā§āĨ¤",
|
"LabelSettingsOnlyShowLaterBooksInContinueSeriesHelp": "āĻāύā§āĻāĻŋāύāĻŋāĻ āϏāĻŋāϰāĻŋāĻā§āϰ āύā§ā§ āĻĒā§āĻ āĻļā§āϞā§āĻĢ āĻĻā§āĻāĻžāϝāĻŧ āϝ⧠āϏāĻŋāϰāĻŋāĻā§ āĻļā§āϰ⧠āĻšāϝāĻŧāύāĻŋ āĻāĻŽāύ āĻĒā§āϰāĻĨāĻŽ āĻŦāĻ āϝāĻžāϰ āĻ
āύā§āϤāϤ āĻāĻāĻāĻŋ āĻŦāĻ āĻļā§āώ āĻšāϝāĻŧā§āĻā§ āĻāĻŦāĻ āĻā§āύ⧠āĻŦāĻ āĻāϞāĻā§ āύāĻžāĨ¤ āĻāĻ āϏā§āĻāĻŋāĻāĻāĻŋ āϏāĻā§āώāĻŽ āĻāϰāϞ⧠āĻļā§āϰ⧠āύāĻž āĻšāĻāϝāĻŧāĻž āĻĒā§āϰāĻĨāĻŽ āĻŦāĻāĻāĻŋāϰ āĻĒāϰāĻŋāĻŦāϰā§āϤ⧠āϏāĻŦāĻā§āϝāĻŧā§ āĻĻā§āϰā§āϰ āϏāĻŽā§āĻĒā§āϰā§āĻŖ āĻŦāĻ āĻĨā§āĻā§ āϏāĻŋāϰāĻŋāĻ āĻāϞāϤ⧠āĻĨāĻžāĻāĻŦā§āĨ¤",
|
||||||
"LabelSettingsParseSubtitles": "āϏāĻžāĻŦāĻāĻžāĻāĻā§āϞ āĻĒāĻžāϰā§āϏ āĻāϰā§āύ",
|
"LabelSettingsParseSubtitles": "āϏāĻžāĻŦāĻāĻžāĻāĻā§āϞ āĻĒāĻžāϰā§āϏ āĻāϰā§āύ",
|
||||||
@@ -587,6 +630,7 @@
|
|||||||
"LabelTimeDurationXMinutes": "{0} āĻŽāĻŋāύāĻŋāĻ",
|
"LabelTimeDurationXMinutes": "{0} āĻŽāĻŋāύāĻŋāĻ",
|
||||||
"LabelTimeDurationXSeconds": "{0} āϏā§āĻā§āύā§āĻĄ",
|
"LabelTimeDurationXSeconds": "{0} āϏā§āĻā§āύā§āĻĄ",
|
||||||
"LabelTimeInMinutes": "āĻŽāĻŋāύāĻŋāĻā§ āϏāĻŽāϝāĻŧ",
|
"LabelTimeInMinutes": "āĻŽāĻŋāύāĻŋāĻā§ āϏāĻŽāϝāĻŧ",
|
||||||
|
"LabelTimeLeft": "{0} āĻŦāĻžāĻāĻŋ",
|
||||||
"LabelTimeListened": "āϏāĻŽāϝāĻŧ āĻļā§āύāĻž āĻšāϝāĻŧā§āĻā§",
|
"LabelTimeListened": "āϏāĻŽāϝāĻŧ āĻļā§āύāĻž āĻšāϝāĻŧā§āĻā§",
|
||||||
"LabelTimeListenedToday": "āĻāĻ āĻļā§āύāĻžāϰ āϏāĻŽāϝāĻŧ",
|
"LabelTimeListenedToday": "āĻāĻ āĻļā§āύāĻžāϰ āϏāĻŽāϝāĻŧ",
|
||||||
"LabelTimeRemaining": "{0}āĻāĻŋ āĻ
āĻŦāĻļāĻŋāώā§āĻ",
|
"LabelTimeRemaining": "{0}āĻāĻŋ āĻ
āĻŦāĻļāĻŋāώā§āĻ",
|
||||||
@@ -594,6 +638,7 @@
|
|||||||
"LabelTitle": "āĻļāĻŋāϰā§āύāĻžāĻŽ",
|
"LabelTitle": "āĻļāĻŋāϰā§āύāĻžāĻŽ",
|
||||||
"LabelToolsEmbedMetadata": "āĻŽā§āĻāĻžāĻĄā§āĻāĻž āĻāĻŽā§āĻŦā§āĻĄ āĻāϰā§āύ",
|
"LabelToolsEmbedMetadata": "āĻŽā§āĻāĻžāĻĄā§āĻāĻž āĻāĻŽā§āĻŦā§āĻĄ āĻāϰā§āύ",
|
||||||
"LabelToolsEmbedMetadataDescription": "āĻāĻāĻžāϰ āĻāĻŽā§āĻ āĻāĻŦāĻ āĻ
āϧā§āϝāĻžāϝāĻŧ āϏāĻš āĻ
āĻĄāĻŋāĻ āĻĢāĻžāĻāϞāĻā§āϞāĻŋāϤ⧠āĻŽā§āĻāĻžāĻĄā§āĻāĻž āĻāĻŽā§āĻŦā§āĻĄ āĻāϰā§āύāĨ¤",
|
"LabelToolsEmbedMetadataDescription": "āĻāĻāĻžāϰ āĻāĻŽā§āĻ āĻāĻŦāĻ āĻ
āϧā§āϝāĻžāϝāĻŧ āϏāĻš āĻ
āĻĄāĻŋāĻ āĻĢāĻžāĻāϞāĻā§āϞāĻŋāϤ⧠āĻŽā§āĻāĻžāĻĄā§āĻāĻž āĻāĻŽā§āĻŦā§āĻĄ āĻāϰā§āύāĨ¤",
|
||||||
|
"LabelToolsM4bEncoder": "M4B āĻāύāĻā§āĻĄāĻžāϰ",
|
||||||
"LabelToolsMakeM4b": "M4B āĻ
āĻĄāĻŋāĻāĻŦā§āĻ āĻĢāĻžāĻāϞ āϤā§āϰāĻŋ āĻāϰā§āύ",
|
"LabelToolsMakeM4b": "M4B āĻ
āĻĄāĻŋāĻāĻŦā§āĻ āĻĢāĻžāĻāϞ āϤā§āϰāĻŋ āĻāϰā§āύ",
|
||||||
"LabelToolsMakeM4bDescription": "āĻāĻŽāĻŦā§āĻĄā§āĻĄ āĻŽā§āĻāĻžāĻĄā§āĻāĻž, āĻāĻāĻžāϰ āĻāĻŽā§āĻ āĻāĻŦāĻ āĻ
āϧā§āϝāĻžāϝāĻŧ āϏāĻš āĻāĻāĻāĻŋ .M4B āĻ
āĻĄāĻŋāĻāĻŦā§āĻ āĻĢāĻžāĻāϞ āϤā§āϰāĻŋ āĻāϰā§āύāĨ¤",
|
"LabelToolsMakeM4bDescription": "āĻāĻŽāĻŦā§āĻĄā§āĻĄ āĻŽā§āĻāĻžāĻĄā§āĻāĻž, āĻāĻāĻžāϰ āĻāĻŽā§āĻ āĻāĻŦāĻ āĻ
āϧā§āϝāĻžāϝāĻŧ āϏāĻš āĻāĻāĻāĻŋ .M4B āĻ
āĻĄāĻŋāĻāĻŦā§āĻ āĻĢāĻžāĻāϞ āϤā§āϰāĻŋ āĻāϰā§āύāĨ¤",
|
||||||
"LabelToolsSplitM4b": "M4B āĻā§ MP3 āϤ⧠āĻŦāĻŋāĻāĻā§āϤ āĻāϰā§āύ",
|
"LabelToolsSplitM4b": "M4B āĻā§ MP3 āϤ⧠āĻŦāĻŋāĻāĻā§āϤ āĻāϰā§āύ",
|
||||||
@@ -606,6 +651,7 @@
|
|||||||
"LabelTracksMultiTrack": "āĻŽāĻžāϞā§āĻāĻŋ-āĻā§āϰā§āϝāĻžāĻ",
|
"LabelTracksMultiTrack": "āĻŽāĻžāϞā§āĻāĻŋ-āĻā§āϰā§āϝāĻžāĻ",
|
||||||
"LabelTracksNone": "āĻā§āύ āĻā§āϰā§āϝāĻžāĻ āύā§āĻ",
|
"LabelTracksNone": "āĻā§āύ āĻā§āϰā§āϝāĻžāĻ āύā§āĻ",
|
||||||
"LabelTracksSingleTrack": "āĻāĻāĻ-āĻā§āϰā§āϝāĻžāĻ",
|
"LabelTracksSingleTrack": "āĻāĻāĻ-āĻā§āϰā§āϝāĻžāĻ",
|
||||||
|
"LabelTrailer": "āĻāύā§āĻāĻŽāĻŋāĻ",
|
||||||
"LabelType": "āĻāĻžāĻāĻĒ",
|
"LabelType": "āĻāĻžāĻāĻĒ",
|
||||||
"LabelUnabridged": "āĻ
āϏāĻāϞāĻā§āύ",
|
"LabelUnabridged": "āĻ
āϏāĻāϞāĻā§āύ",
|
||||||
"LabelUndo": "āĻĒā§āϰā§āĻŦāĻžāĻŦāϏā§āĻĨāĻž",
|
"LabelUndo": "āĻĒā§āϰā§āĻŦāĻžāĻŦāϏā§āĻĨāĻž",
|
||||||
@@ -617,10 +663,13 @@
|
|||||||
"LabelUpdateDetailsHelp": "āĻāĻāĻāĻŋ āĻŽāĻŋāϞ āĻĨāĻžāĻāĻž āĻ
āĻŦāϏā§āĻĨāĻžāϝāĻŧ āύāĻŋāϰā§āĻŦāĻžāĻāĻŋāϤ āĻŦāĻāĻā§āϞāĻŋāϰ āĻŦāĻŋāĻĻā§āϝāĻŽāĻžāύ āĻŦāĻŋāĻŦāϰāĻŖ āĻāĻāĻžāϰāϰāĻžāĻāĻ āĻāϰāĻžāϰ āĻ
āύā§āĻŽāϤāĻŋ āĻĻāĻŋāύ",
|
"LabelUpdateDetailsHelp": "āĻāĻāĻāĻŋ āĻŽāĻŋāϞ āĻĨāĻžāĻāĻž āĻ
āĻŦāϏā§āĻĨāĻžāϝāĻŧ āύāĻŋāϰā§āĻŦāĻžāĻāĻŋāϤ āĻŦāĻāĻā§āϞāĻŋāϰ āĻŦāĻŋāĻĻā§āϝāĻŽāĻžāύ āĻŦāĻŋāĻŦāϰāĻŖ āĻāĻāĻžāϰāϰāĻžāĻāĻ āĻāϰāĻžāϰ āĻ
āύā§āĻŽāϤāĻŋ āĻĻāĻŋāύ",
|
||||||
"LabelUpdatedAt": "āĻāĻĒāĻĄā§āĻ āĻāϰāĻž āĻšāϝāĻŧā§āĻā§",
|
"LabelUpdatedAt": "āĻāĻĒāĻĄā§āĻ āĻāϰāĻž āĻšāϝāĻŧā§āĻā§",
|
||||||
"LabelUploaderDragAndDrop": "āĻĢāĻžāĻāϞ āĻŦāĻž āĻĢā§āϞā§āĻĄāĻžāϰ āĻā§āύ⧠āĻāύā§āύ āĻāĻŦāĻ āĻĢā§āϞ⧠āĻĻāĻŋāύ",
|
"LabelUploaderDragAndDrop": "āĻĢāĻžāĻāϞ āĻŦāĻž āĻĢā§āϞā§āĻĄāĻžāϰ āĻā§āύ⧠āĻāύā§āύ āĻāĻŦāĻ āĻĢā§āϞ⧠āĻĻāĻŋāύ",
|
||||||
|
"LabelUploaderDragAndDropFilesOnly": "āĻĢāĻžāĻāϞ āĻā§āύ⧠āĻāύā§āύ",
|
||||||
"LabelUploaderDropFiles": "āĻĢāĻžāĻāϞāĻā§āϞ⧠āĻĢā§āϞ⧠āĻĻāĻŋāύ",
|
"LabelUploaderDropFiles": "āĻĢāĻžāĻāϞāĻā§āϞ⧠āĻĢā§āϞ⧠āĻĻāĻŋāύ",
|
||||||
"LabelUploaderItemFetchMetadataHelp": "āϏā§āĻŦāϝāĻŧāĻāĻā§āϰāĻŋāϝāĻŧāĻāĻžāĻŦā§ āĻļāĻŋāϰā§āύāĻžāĻŽ, āϞā§āĻāĻ āĻāĻŦāĻ āϏāĻŋāϰāĻŋāĻ āĻāύā§āύ",
|
"LabelUploaderItemFetchMetadataHelp": "āϏā§āĻŦāϝāĻŧāĻāĻā§āϰāĻŋāϝāĻŧāĻāĻžāĻŦā§ āĻļāĻŋāϰā§āύāĻžāĻŽ, āϞā§āĻāĻ āĻāĻŦāĻ āϏāĻŋāϰāĻŋāĻ āĻāύā§āύ",
|
||||||
|
"LabelUseAdvancedOptions": "āĻāύā§āύāϤ āĻŦāĻŋāĻāϞā§āĻĒ āĻŦā§āϝāĻŦāĻšāĻžāϰ āĻāϰā§āύ",
|
||||||
"LabelUseChapterTrack": "āĻ
āϧā§āϝāĻžāϝāĻŧ āĻā§āϰā§āϝāĻžāĻ āĻŦā§āϝāĻŦāĻšāĻžāϰ āĻāϰā§āύ",
|
"LabelUseChapterTrack": "āĻ
āϧā§āϝāĻžāϝāĻŧ āĻā§āϰā§āϝāĻžāĻ āĻŦā§āϝāĻŦāĻšāĻžāϰ āĻāϰā§āύ",
|
||||||
"LabelUseFullTrack": "āϏāĻŽā§āĻĒā§āϰā§āĻŖ āĻā§āϰā§āϝāĻžāĻ āĻŦā§āϝāĻŦāĻšāĻžāϰ āĻāϰā§āύ",
|
"LabelUseFullTrack": "āϏāĻŽā§āĻĒā§āϰā§āĻŖ āĻā§āϰā§āϝāĻžāĻ āĻŦā§āϝāĻŦāĻšāĻžāϰ āĻāϰā§āύ",
|
||||||
|
"LabelUseZeroForUnlimited": "āĻ
āϏā§āĻŽā§āϰ āĻāύā§āϝ 0 āĻŦā§āϝāĻŦāĻšāĻžāϰ āĻāϰā§āύ",
|
||||||
"LabelUser": "āĻŦā§āϝāĻŦāĻšāĻžāϰāĻāĻžāϰā§",
|
"LabelUser": "āĻŦā§āϝāĻŦāĻšāĻžāϰāĻāĻžāϰā§",
|
||||||
"LabelUsername": "āĻŦā§āϝāĻŦāĻšāĻžāϰāĻāĻžāϰā§āϰ āύāĻžāĻŽ",
|
"LabelUsername": "āĻŦā§āϝāĻŦāĻšāĻžāϰāĻāĻžāϰā§āϰ āύāĻžāĻŽ",
|
||||||
"LabelValue": "āĻŽāĻžāύ",
|
"LabelValue": "āĻŽāĻžāύ",
|
||||||
@@ -667,6 +716,7 @@
|
|||||||
"MessageConfirmDeleteMetadataProvider": "āĻāĻĒāύāĻŋ āĻāĻŋ āύāĻŋāĻļā§āĻāĻŋāϤāĻāĻžāĻŦā§ āĻāĻžāϏā§āĻāĻŽ āĻŽā§āĻāĻžāĻĄā§āĻāĻž āĻĒā§āϰāĻĻāĻžāύāĻāĻžāϰ⧠\"{0}\" āĻŽā§āĻāϤ⧠āĻāĻžāύ?",
|
"MessageConfirmDeleteMetadataProvider": "āĻāĻĒāύāĻŋ āĻāĻŋ āύāĻŋāĻļā§āĻāĻŋāϤāĻāĻžāĻŦā§ āĻāĻžāϏā§āĻāĻŽ āĻŽā§āĻāĻžāĻĄā§āĻāĻž āĻĒā§āϰāĻĻāĻžāύāĻāĻžāϰ⧠\"{0}\" āĻŽā§āĻāϤ⧠āĻāĻžāύ?",
|
||||||
"MessageConfirmDeleteNotification": "āĻāĻĒāύāĻŋ āĻāĻŋ āύāĻŋāĻļā§āĻāĻŋāϤāĻāĻžāĻŦā§ āĻāĻ āĻŦāĻŋāĻā§āĻāĻĒā§āϤāĻŋāĻāĻŋ āĻŽā§āĻāϤ⧠āĻāĻžāύ?",
|
"MessageConfirmDeleteNotification": "āĻāĻĒāύāĻŋ āĻāĻŋ āύāĻŋāĻļā§āĻāĻŋāϤāĻāĻžāĻŦā§ āĻāĻ āĻŦāĻŋāĻā§āĻāĻĒā§āϤāĻŋāĻāĻŋ āĻŽā§āĻāϤ⧠āĻāĻžāύ?",
|
||||||
"MessageConfirmDeleteSession": "āĻāĻĒāύāĻŋ āĻāĻŋ āύāĻŋāĻļā§āĻāĻŋāϤ āĻāĻĒāύāĻŋ āĻāĻ āĻ
āϧāĻŋāĻŦā§āĻļāύ āĻŽā§āĻā§ āĻĻāĻŋāϤ⧠āĻāĻžāύ?",
|
"MessageConfirmDeleteSession": "āĻāĻĒāύāĻŋ āĻāĻŋ āύāĻŋāĻļā§āĻāĻŋāϤ āĻāĻĒāύāĻŋ āĻāĻ āĻ
āϧāĻŋāĻŦā§āĻļāύ āĻŽā§āĻā§ āĻĻāĻŋāϤ⧠āĻāĻžāύ?",
|
||||||
|
"MessageConfirmEmbedMetadataInAudioFiles": "āĻāĻĒāύāĻŋ āĻāĻŋ {0}āĻāĻŋ āĻ
āĻĄāĻŋāĻ āĻĢāĻžāĻāϞ⧠āĻŽā§āĻāĻžāĻĄā§āĻāĻž āĻāĻŽā§āĻŦā§āĻĄ āĻāϰāĻžāϰ āĻŦāĻŋāώāϝāĻŧā§ āύāĻŋāĻļā§āĻāĻŋāϤ?",
|
||||||
"MessageConfirmForceReScan": "āĻāĻĒāύāĻŋ āĻāĻŋ āύāĻŋāĻļā§āĻāĻŋāϤ āϝ⧠āĻāĻĒāύāĻŋ āĻā§āϰ āĻāϰ⧠āĻĒā§āύāϰāĻžāϝāĻŧ āϏā§āĻā§āϝāĻžāύ āĻāϰāϤ⧠āĻāĻžāύ?",
|
"MessageConfirmForceReScan": "āĻāĻĒāύāĻŋ āĻāĻŋ āύāĻŋāĻļā§āĻāĻŋāϤ āϝ⧠āĻāĻĒāύāĻŋ āĻā§āϰ āĻāϰ⧠āĻĒā§āύāϰāĻžāϝāĻŧ āϏā§āĻā§āϝāĻžāύ āĻāϰāϤ⧠āĻāĻžāύ?",
|
||||||
"MessageConfirmMarkAllEpisodesFinished": "āĻāĻĒāύāĻŋ āĻāĻŋ āύāĻŋāĻļā§āĻāĻŋāϤ āϝ⧠āĻāĻĒāύāĻŋ āϏāĻŽāϏā§āϤ āĻĒāϰā§āĻŦ āϏāĻŽāĻžāĻĒā§āϤ āĻšāĻŋāϏāĻžāĻŦā§ āĻāĻŋāĻšā§āύāĻŋāϤ āĻāϰāϤ⧠āĻāĻžāύ?",
|
"MessageConfirmMarkAllEpisodesFinished": "āĻāĻĒāύāĻŋ āĻāĻŋ āύāĻŋāĻļā§āĻāĻŋāϤ āϝ⧠āĻāĻĒāύāĻŋ āϏāĻŽāϏā§āϤ āĻĒāϰā§āĻŦ āϏāĻŽāĻžāĻĒā§āϤ āĻšāĻŋāϏāĻžāĻŦā§ āĻāĻŋāĻšā§āύāĻŋāϤ āĻāϰāϤ⧠āĻāĻžāύ?",
|
||||||
"MessageConfirmMarkAllEpisodesNotFinished": "āĻāĻĒāύāĻŋ āĻāĻŋ āύāĻŋāĻļā§āĻāĻŋāϤ āϝ⧠āĻāĻĒāύāĻŋ āϏāĻŽāϏā§āϤ āĻĒāϰā§āĻŦāĻā§ āĻļā§āώ āĻšāϝāĻŧāύāĻŋ āĻŦāϞ⧠āĻāĻŋāĻšā§āύāĻŋāϤ āĻāϰāϤ⧠āĻāĻžāύ?",
|
"MessageConfirmMarkAllEpisodesNotFinished": "āĻāĻĒāύāĻŋ āĻāĻŋ āύāĻŋāĻļā§āĻāĻŋāϤ āϝ⧠āĻāĻĒāύāĻŋ āϏāĻŽāϏā§āϤ āĻĒāϰā§āĻŦāĻā§ āĻļā§āώ āĻšāϝāĻŧāύāĻŋ āĻŦāϞ⧠āĻāĻŋāĻšā§āύāĻŋāϤ āĻāϰāϤ⧠āĻāĻžāύ?",
|
||||||
@@ -678,6 +728,7 @@
|
|||||||
"MessageConfirmPurgeCache": "āĻā§āϝāĻžāĻļā§ āĻĒāϰāĻŋāώā§āĻāĻžāϰāĻ <code>/metadata/cache</code>-āĻ āϏāĻŽā§āĻĒā§āϰā§āĻŖ āĻĄāĻŋāϰā§āĻā§āĻāϰāĻŋ āĻŽā§āĻā§ āĻĢā§āϞāĻŦā§āĨ¤ <br /><br />āĻāĻĒāύāĻŋ āĻāĻŋ āύāĻŋāĻļā§āĻāĻŋāϤ āĻāĻĒāύāĻŋ āĻā§āϝāĻžāĻļā§ āĻĄāĻŋāϰā§āĻā§āĻāϰāĻŋ āϏāϰāĻžāϤ⧠āĻāĻžāύ?",
|
"MessageConfirmPurgeCache": "āĻā§āϝāĻžāĻļā§ āĻĒāϰāĻŋāώā§āĻāĻžāϰāĻ <code>/metadata/cache</code>-āĻ āϏāĻŽā§āĻĒā§āϰā§āĻŖ āĻĄāĻŋāϰā§āĻā§āĻāϰāĻŋ āĻŽā§āĻā§ āĻĢā§āϞāĻŦā§āĨ¤ <br /><br />āĻāĻĒāύāĻŋ āĻāĻŋ āύāĻŋāĻļā§āĻāĻŋāϤ āĻāĻĒāύāĻŋ āĻā§āϝāĻžāĻļā§ āĻĄāĻŋāϰā§āĻā§āĻāϰāĻŋ āϏāϰāĻžāϤ⧠āĻāĻžāύ?",
|
||||||
"MessageConfirmPurgeItemsCache": "āĻāĻāĻā§āĻŽ āĻā§āϝāĻžāĻļā§ āĻĒāϰāĻŋāώā§āĻāĻžāϰāĻ <code>/metadata/cache/items</code>-āĻ āϏāĻŽā§āĻĒā§āϰā§āĻŖ āĻĄāĻŋāϰā§āĻā§āĻāϰāĻŋ āĻŽā§āĻā§ āĻĢā§āϞāĻŦā§āĨ¤<br />āĻāĻĒāύāĻŋ āĻāĻŋ āύāĻŋāĻļā§āĻāĻŋāϤ?",
|
"MessageConfirmPurgeItemsCache": "āĻāĻāĻā§āĻŽ āĻā§āϝāĻžāĻļā§ āĻĒāϰāĻŋāώā§āĻāĻžāϰāĻ <code>/metadata/cache/items</code>-āĻ āϏāĻŽā§āĻĒā§āϰā§āĻŖ āĻĄāĻŋāϰā§āĻā§āĻāϰāĻŋ āĻŽā§āĻā§ āĻĢā§āϞāĻŦā§āĨ¤<br />āĻāĻĒāύāĻŋ āĻāĻŋ āύāĻŋāĻļā§āĻāĻŋāϤ?",
|
||||||
"MessageConfirmQuickEmbed": "āϏāϤāϰā§āĻāϤāĻž! āĻĻā§āϰā§āϤ āĻāĻŽā§āĻŦā§āĻĄ āĻāĻĒāύāĻžāϰ āĻ
āĻĄāĻŋāĻ āĻĢāĻžāĻāϞā§āϰ āĻŦā§āϝāĻžāĻāĻāĻĒ āĻāϰāĻŦā§ āύāĻžāĨ¤ āύāĻŋāĻļā§āĻāĻŋāϤ āĻāϰā§āύ āϝ⧠āĻāĻĒāύāĻžāϰ āĻ
āĻĄāĻŋāĻ āĻĢāĻžāĻāϞāĻā§āϞāĻŋāϰ āĻāĻāĻāĻŋ āĻŦā§āϝāĻžāĻāĻāĻĒ āĻāĻā§āĨ¤ <br><br>āĻāĻĒāύāĻŋ āĻāĻŋ āĻāĻžāϞāĻŋāϝāĻŧā§ āϝā§āϤ⧠āĻāĻžāύ?",
|
"MessageConfirmQuickEmbed": "āϏāϤāϰā§āĻāϤāĻž! āĻĻā§āϰā§āϤ āĻāĻŽā§āĻŦā§āĻĄ āĻāĻĒāύāĻžāϰ āĻ
āĻĄāĻŋāĻ āĻĢāĻžāĻāϞā§āϰ āĻŦā§āϝāĻžāĻāĻāĻĒ āĻāϰāĻŦā§ āύāĻžāĨ¤ āύāĻŋāĻļā§āĻāĻŋāϤ āĻāϰā§āύ āϝ⧠āĻāĻĒāύāĻžāϰ āĻ
āĻĄāĻŋāĻ āĻĢāĻžāĻāϞāĻā§āϞāĻŋāϰ āĻāĻāĻāĻŋ āĻŦā§āϝāĻžāĻāĻāĻĒ āĻāĻā§āĨ¤ <br><br>āĻāĻĒāύāĻŋ āĻāĻŋ āĻāĻžāϞāĻŋāϝāĻŧā§ āϝā§āϤ⧠āĻāĻžāύ?",
|
||||||
|
"MessageConfirmQuickMatchEpisodes": "āĻāĻāĻāĻŋ āĻŽāĻŋāϞ āĻĒāĻžāĻāϝāĻŧāĻž āĻā§āϞ⧠āĻĻā§āϰā§āϤ āĻŽā§āϝāĻžāĻāĻŋāĻ āĻĒāϰā§āĻŦāĻā§āϞāĻŋ āĻŦāĻŋāϏā§āϤāĻžāϰāĻŋāϤ āĻāĻāĻžāϰāϰāĻžāĻāĻ āĻāϰāĻŦā§āĨ¤ āĻļā§āϧā§āĻŽāĻžāϤā§āϰ āĻ
āϤā§āϞāύā§āϝāĻŧ āĻĒāϰā§āĻŦ āĻāĻĒāĻĄā§āĻ āĻāϰāĻž āĻšāĻŦā§āĨ¤ āĻāĻĒāύāĻŋ āĻāĻŋ āύāĻŋāĻļā§āĻāĻŋāϤ?",
|
||||||
"MessageConfirmReScanLibraryItems": "āĻāĻĒāύāĻŋ āĻāĻŋ āύāĻŋāĻļā§āĻāĻŋāϤ āϝ⧠āĻāĻĒāύāĻŋ {0}āĻāĻŋ āĻāĻāĻā§āĻŽ āĻĒā§āύāϰāĻžāϝāĻŧ āϏā§āĻā§āϝāĻžāύ āĻāϰāϤ⧠āĻāĻžāύ?",
|
"MessageConfirmReScanLibraryItems": "āĻāĻĒāύāĻŋ āĻāĻŋ āύāĻŋāĻļā§āĻāĻŋāϤ āϝ⧠āĻāĻĒāύāĻŋ {0}āĻāĻŋ āĻāĻāĻā§āĻŽ āĻĒā§āύāϰāĻžāϝāĻŧ āϏā§āĻā§āϝāĻžāύ āĻāϰāϤ⧠āĻāĻžāύ?",
|
||||||
"MessageConfirmRemoveAllChapters": "āĻāĻĒāύāĻŋ āĻāĻŋ āύāĻŋāĻļā§āĻāĻŋāϤ āϝ⧠āĻāĻĒāύāĻŋ āϏāĻŽāϏā§āϤ āĻ
āϧā§āϝāĻžāϝāĻŧ āϏāϰāĻžāϤ⧠āĻāĻžāύ?",
|
"MessageConfirmRemoveAllChapters": "āĻāĻĒāύāĻŋ āĻāĻŋ āύāĻŋāĻļā§āĻāĻŋāϤ āϝ⧠āĻāĻĒāύāĻŋ āϏāĻŽāϏā§āϤ āĻ
āϧā§āϝāĻžāϝāĻŧ āϏāϰāĻžāϤ⧠āĻāĻžāύ?",
|
||||||
"MessageConfirmRemoveAuthor": "āĻāĻĒāύāĻŋ āĻāĻŋ āύāĻŋāĻļā§āĻāĻŋāϤ āϝ⧠āĻāĻĒāύāĻŋ āϞā§āĻāĻ \"{0}\" āĻ
āĻĒāϏāĻžāϰāĻŖ āĻāϰāϤ⧠āĻāĻžāύ?",
|
"MessageConfirmRemoveAuthor": "āĻāĻĒāύāĻŋ āĻāĻŋ āύāĻŋāĻļā§āĻāĻŋāϤ āϝ⧠āĻāĻĒāύāĻŋ āϞā§āĻāĻ \"{0}\" āĻ
āĻĒāϏāĻžāϰāĻŖ āĻāϰāϤ⧠āĻāĻžāύ?",
|
||||||
@@ -685,6 +736,7 @@
|
|||||||
"MessageConfirmRemoveEpisode": "āĻāĻĒāύāĻŋ āĻāĻŋ āύāĻŋāĻļā§āĻāĻŋāϤ āĻāĻĒāύāĻŋ \"{0}\" āĻĒāϰā§āĻŦāĻāĻŋ āϏāϰāĻžāϤ⧠āĻāĻžāύ?",
|
"MessageConfirmRemoveEpisode": "āĻāĻĒāύāĻŋ āĻāĻŋ āύāĻŋāĻļā§āĻāĻŋāϤ āĻāĻĒāύāĻŋ \"{0}\" āĻĒāϰā§āĻŦāĻāĻŋ āϏāϰāĻžāϤ⧠āĻāĻžāύ?",
|
||||||
"MessageConfirmRemoveEpisodes": "āĻāĻĒāύāĻŋ āĻāĻŋ āύāĻŋāĻļā§āĻāĻŋāϤ āϝ⧠āĻāĻĒāύāĻŋ {0}āĻāĻŋ āĻĒāϰā§āĻŦ āϏāϰāĻžāϤ⧠āĻāĻžāύ?",
|
"MessageConfirmRemoveEpisodes": "āĻāĻĒāύāĻŋ āĻāĻŋ āύāĻŋāĻļā§āĻāĻŋāϤ āϝ⧠āĻāĻĒāύāĻŋ {0}āĻāĻŋ āĻĒāϰā§āĻŦ āϏāϰāĻžāϤ⧠āĻāĻžāύ?",
|
||||||
"MessageConfirmRemoveListeningSessions": "āĻāĻĒāύāĻŋ āĻāĻŋ āύāĻŋāĻļā§āĻāĻŋāϤ āϝ⧠āĻāĻĒāύāĻŋ {0}āĻāĻŋ āĻļā§āύāĻžāϰ āϏā§āĻļāύ āϏāϰāĻžāϤ⧠āĻāĻžāύ?",
|
"MessageConfirmRemoveListeningSessions": "āĻāĻĒāύāĻŋ āĻāĻŋ āύāĻŋāĻļā§āĻāĻŋāϤ āϝ⧠āĻāĻĒāύāĻŋ {0}āĻāĻŋ āĻļā§āύāĻžāϰ āϏā§āĻļāύ āϏāϰāĻžāϤ⧠āĻāĻžāύ?",
|
||||||
|
"MessageConfirmRemoveMetadataFiles": "āĻāĻĒāύāĻŋ āĻāĻŋ āĻāĻĒāύāĻžāϰ āϞāĻžāĻāĻŦā§āϰā§āϰāĻŋ āĻāĻāĻā§āĻŽ āĻĢā§āϞā§āĻĄāĻžāϰ⧠āĻĨāĻžāĻāĻž āϏāĻŽāϏā§āϤ āĻŽā§āĻāĻžāĻĄā§āĻāĻž {0} āĻĢāĻžāĻāϞ āĻŽā§āĻā§ āĻĢā§āϞāĻžāϰ āĻŦāĻŋāώāϝāĻŧā§ āύāĻŋāĻļā§āĻāĻŋāϤ?",
|
||||||
"MessageConfirmRemoveNarrator": "āĻāĻĒāύāĻŋ āĻāĻŋ \"{0}\" āĻŦāϰā§āĻŖāύāĻžāĻāĻžāϰā§āĻā§ āϏāϰāĻžāύā§āϰ āĻŦāĻŋāώāϝāĻŧā§ āύāĻŋāĻļā§āĻāĻŋāϤ?",
|
"MessageConfirmRemoveNarrator": "āĻāĻĒāύāĻŋ āĻāĻŋ \"{0}\" āĻŦāϰā§āĻŖāύāĻžāĻāĻžāϰā§āĻā§ āϏāϰāĻžāύā§āϰ āĻŦāĻŋāώāϝāĻŧā§ āύāĻŋāĻļā§āĻāĻŋāϤ?",
|
||||||
"MessageConfirmRemovePlaylist": "āĻāĻĒāύāĻŋ āĻāĻŋ āύāĻŋāĻļā§āĻāĻŋāϤ āϝ⧠āĻāĻĒāύāĻŋ āĻāĻĒāύāĻžāϰ āĻĒā§āϞā§āϞāĻŋāϏā§āĻ \"{0}\" āϏāϰāĻžāϤ⧠āĻāĻžāύ?",
|
"MessageConfirmRemovePlaylist": "āĻāĻĒāύāĻŋ āĻāĻŋ āύāĻŋāĻļā§āĻāĻŋāϤ āϝ⧠āĻāĻĒāύāĻŋ āĻāĻĒāύāĻžāϰ āĻĒā§āϞā§āϞāĻŋāϏā§āĻ \"{0}\" āϏāϰāĻžāϤ⧠āĻāĻžāύ?",
|
||||||
"MessageConfirmRenameGenre": "āĻāĻĒāύāĻŋ āĻāĻŋ āύāĻŋāĻļā§āĻāĻŋāϤ āϝ⧠āĻāĻĒāύāĻŋ āϏāĻŽāϏā§āϤ āĻāĻāĻā§āĻŽā§āϰ āĻāύā§āϝ \"{0}\" āϧāĻžāϰāĻžāϰ āύāĻžāĻŽ āĻĒāϰāĻŋāĻŦāϰā§āϤāύ āĻāϰ⧠\"{1}\" āĻāϰāϤ⧠āĻāĻžāύ?",
|
"MessageConfirmRenameGenre": "āĻāĻĒāύāĻŋ āĻāĻŋ āύāĻŋāĻļā§āĻāĻŋāϤ āϝ⧠āĻāĻĒāύāĻŋ āϏāĻŽāϏā§āϤ āĻāĻāĻā§āĻŽā§āϰ āĻāύā§āϝ \"{0}\" āϧāĻžāϰāĻžāϰ āύāĻžāĻŽ āĻĒāϰāĻŋāĻŦāϰā§āϤāύ āĻāϰ⧠\"{1}\" āĻāϰāϤ⧠āĻāĻžāύ?",
|
||||||
@@ -700,6 +752,7 @@
|
|||||||
"MessageDragFilesIntoTrackOrder": "āϏāĻ āĻŋāĻ āĻā§āϰā§āϝāĻžāĻ āĻ
āϰā§āĻĄāĻžāϰ⧠āĻĢāĻžāĻāϞ āĻā§āύ⧠āĻāύā§āύ",
|
"MessageDragFilesIntoTrackOrder": "āϏāĻ āĻŋāĻ āĻā§āϰā§āϝāĻžāĻ āĻ
āϰā§āĻĄāĻžāϰ⧠āĻĢāĻžāĻāϞ āĻā§āύ⧠āĻāύā§āύ",
|
||||||
"MessageEmbedFailed": "āĻāĻŽā§āĻŦā§āĻĄ āĻŦā§āϝāϰā§āĻĨ āĻšāϝāĻŧā§āĻā§!",
|
"MessageEmbedFailed": "āĻāĻŽā§āĻŦā§āĻĄ āĻŦā§āϝāϰā§āĻĨ āĻšāϝāĻŧā§āĻā§!",
|
||||||
"MessageEmbedFinished": "āĻāĻŽā§āĻŦā§āĻĄ āĻāϰāĻž āĻļā§āώ!",
|
"MessageEmbedFinished": "āĻāĻŽā§āĻŦā§āĻĄ āĻāϰāĻž āĻļā§āώ!",
|
||||||
|
"MessageEmbedQueue": "āĻŽā§āĻāĻžāĻĄā§āĻāĻž āĻāĻŽā§āĻŦā§āĻĄā§āϰ āĻāύā§āϝ āϏāĻžāϰāĻŋāĻŦāĻĻā§āϧ ({0} āϏāĻžāϰāĻŋāϤā§)",
|
||||||
"MessageEpisodesQueuedForDownload": "{0} āĻĒāϰā§āĻŦ(āĻā§āϞāĻŋ) āĻĄāĻžāĻāύāϞā§āĻĄā§āϰ āĻāύā§āϝ āϏāĻžāϰāĻŋāĻŦāĻĻā§āϧ",
|
"MessageEpisodesQueuedForDownload": "{0} āĻĒāϰā§āĻŦ(āĻā§āϞāĻŋ) āĻĄāĻžāĻāύāϞā§āĻĄā§āϰ āĻāύā§āϝ āϏāĻžāϰāĻŋāĻŦāĻĻā§āϧ",
|
||||||
"MessageEreaderDevices": "āĻ-āĻŦā§āĻ āϏāϰāĻŦāϰāĻžāĻš āύāĻŋāĻļā§āĻāĻŋāϤ āĻāϰāϤā§, āĻāĻĒāύāĻžāĻā§ āύā§āĻā§ āϤāĻžāϞāĻŋāĻāĻžāĻā§āĻā§āϤ āĻĒā§āϰāϤāĻŋāĻāĻŋ āĻĄāĻŋāĻāĻžāĻāϏā§āϰ āĻāύā§āϝ āĻāĻāĻāĻŋ āĻŦā§āϧ āĻĒā§āϰā§āϰāĻ āĻšāĻŋāϏāĻžāĻŦā§ āĻāĻĒāϰā§āϰ āĻāĻŽā§āϞ āĻ āĻŋāĻāĻžāύāĻžāĻāĻŋ āϝā§āĻā§āϤ āĻāϰāϤ⧠āĻšāϤ⧠āĻĒāĻžāϰā§āĨ¤",
|
"MessageEreaderDevices": "āĻ-āĻŦā§āĻ āϏāϰāĻŦāϰāĻžāĻš āύāĻŋāĻļā§āĻāĻŋāϤ āĻāϰāϤā§, āĻāĻĒāύāĻžāĻā§ āύā§āĻā§ āϤāĻžāϞāĻŋāĻāĻžāĻā§āĻā§āϤ āĻĒā§āϰāϤāĻŋāĻāĻŋ āĻĄāĻŋāĻāĻžāĻāϏā§āϰ āĻāύā§āϝ āĻāĻāĻāĻŋ āĻŦā§āϧ āĻĒā§āϰā§āϰāĻ āĻšāĻŋāϏāĻžāĻŦā§ āĻāĻĒāϰā§āϰ āĻāĻŽā§āϞ āĻ āĻŋāĻāĻžāύāĻžāĻāĻŋ āϝā§āĻā§āϤ āĻāϰāϤ⧠āĻšāϤ⧠āĻĒāĻžāϰā§āĨ¤",
|
||||||
"MessageFeedURLWillBe": "āĻĢāĻŋāĻĄ URL āĻšāĻŦā§ {0}",
|
"MessageFeedURLWillBe": "āĻĢāĻŋāĻĄ URL āĻšāĻŦā§ {0}",
|
||||||
@@ -744,6 +797,7 @@
|
|||||||
"MessageNoLogs": "āĻā§āύāĻ āϞāĻ āύā§āĻ",
|
"MessageNoLogs": "āĻā§āύāĻ āϞāĻ āύā§āĻ",
|
||||||
"MessageNoMediaProgress": "āĻŽāĻŋāĻĄāĻŋāϝāĻŧāĻž āĻ
āĻā§āϰāĻāϤāĻŋ āύā§āĻ",
|
"MessageNoMediaProgress": "āĻŽāĻŋāĻĄāĻŋāϝāĻŧāĻž āĻ
āĻā§āϰāĻāϤāĻŋ āύā§āĻ",
|
||||||
"MessageNoNotifications": "āĻā§āύ⧠āĻŦāĻŋāĻā§āĻāĻĒā§āϤāĻŋ āύā§āĻ",
|
"MessageNoNotifications": "āĻā§āύ⧠āĻŦāĻŋāĻā§āĻāĻĒā§āϤāĻŋ āύā§āĻ",
|
||||||
|
"MessageNoPodcastFeed": "āĻ
āĻŦā§āϧ āĻĒāĻĄāĻāĻžāϏā§āĻ: āĻā§āύ⧠āĻĢāĻŋāĻĄ āύā§āĻ",
|
||||||
"MessageNoPodcastsFound": "āĻā§āύ āĻĒāĻĄāĻāĻžāϏā§āĻ āĻĒāĻžāĻāϝāĻŧāĻž āϝāĻžāϝāĻŧāύāĻŋ",
|
"MessageNoPodcastsFound": "āĻā§āύ āĻĒāĻĄāĻāĻžāϏā§āĻ āĻĒāĻžāĻāϝāĻŧāĻž āϝāĻžāϝāĻŧāύāĻŋ",
|
||||||
"MessageNoResults": "āĻā§āύ āĻĢāϞāĻžāĻĢāϞ āύā§āĻ",
|
"MessageNoResults": "āĻā§āύ āĻĢāϞāĻžāĻĢāϞ āύā§āĻ",
|
||||||
"MessageNoSearchResultsFor": "\"{0}\" āĻāϰ āĻāύā§āϝ āĻā§āύ āĻ
āύā§āϏāύā§āϧāĻžāύ āĻĢāϞāĻžāĻĢāϞ āύā§āĻ",
|
"MessageNoSearchResultsFor": "\"{0}\" āĻāϰ āĻāύā§āϝ āĻā§āύ āĻ
āύā§āϏāύā§āϧāĻžāύ āĻĢāϞāĻžāĻĢāϞ āύā§āĻ",
|
||||||
@@ -760,6 +814,10 @@
|
|||||||
"MessagePlaylistCreateFromCollection": "āϏāĻāĻā§āϰāĻš āĻĨā§āĻā§ āĻĒā§āϞā§āϞāĻŋāϏā§āĻ āϤā§āϰāĻŋ āĻāϰā§āύ",
|
"MessagePlaylistCreateFromCollection": "āϏāĻāĻā§āϰāĻš āĻĨā§āĻā§ āĻĒā§āϞā§āϞāĻŋāϏā§āĻ āϤā§āϰāĻŋ āĻāϰā§āύ",
|
||||||
"MessagePleaseWait": "āĻ
āύā§āĻā§āϰāĻš āĻāϰ⧠āĻ
āĻĒā§āĻā§āώāĻž āĻāϰā§āύ..āĨ¤",
|
"MessagePleaseWait": "āĻ
āύā§āĻā§āϰāĻš āĻāϰ⧠āĻ
āĻĒā§āĻā§āώāĻž āĻāϰā§āύ..āĨ¤",
|
||||||
"MessagePodcastHasNoRSSFeedForMatching": "āĻĒāĻĄāĻāĻžāϏā§āĻā§āϰ āϏāĻžāĻĨā§ āĻŽāĻŋāϞā§āϰ āĻāύā§āϝ āĻŦā§āϝāĻŦāĻšāĻžāϰ āĻāϰāĻžāϰ āĻāύā§āϝ āĻā§āύ RSS āĻĢāĻŋāĻĄ āĻāĻāĻāϰāĻāϞ āύā§āĻ",
|
"MessagePodcastHasNoRSSFeedForMatching": "āĻĒāĻĄāĻāĻžāϏā§āĻā§āϰ āϏāĻžāĻĨā§ āĻŽāĻŋāϞā§āϰ āĻāύā§āϝ āĻŦā§āϝāĻŦāĻšāĻžāϰ āĻāϰāĻžāϰ āĻāύā§āϝ āĻā§āύ RSS āĻĢāĻŋāĻĄ āĻāĻāĻāϰāĻāϞ āύā§āĻ",
|
||||||
|
"MessagePodcastSearchField": "āĻ
āύā§āϏāύā§āϧāĻžāύ āĻļāĻŦā§āĻĻ āĻŦāĻž RSS āĻĢāĻŋāĻĄ URL āϞāĻŋāĻā§āύ",
|
||||||
|
"MessageQuickEmbedInProgress": "āĻĻā§āϰā§āϤ āĻāĻŽā§āĻŦā§āĻĄ āĻāϰāĻž āĻšāĻā§āĻā§",
|
||||||
|
"MessageQuickEmbedQueue": "āĻĻā§āϰā§āϤ āĻāĻŽā§āĻŦā§āĻĄ āĻāϰāĻžāϰ āĻāύā§āϝ āϏāĻžāϰāĻŋāĻŦāĻĻā§āϧ ({0} āϏāĻžāϰāĻŋāϤā§)",
|
||||||
|
"MessageQuickMatchAllEpisodes": "āĻĻā§āϰā§āϤ āĻŽā§āϝāĻžāĻ āϏāĻŦ āĻĒāϰā§āĻŦ",
|
||||||
"MessageQuickMatchDescription": "āĻāĻžāϞāĻŋ āĻāĻāĻā§āĻŽā§āϰ āĻŦāĻŋāĻļāĻĻ āĻŦāĻŋāĻŦāϰāĻŖ āĻāĻŦāĻ '{0}' āĻĨā§āĻā§ āĻĒā§āϰāĻĨāĻŽ āĻŽā§āϝāĻžāĻā§āϰ āĻĢāϞāĻžāĻĢāϞā§āϰ āϏāĻžāĻĨā§ āĻāĻāĻžāϰ āĻāϰā§āύāĨ¤ āϏāĻžāϰā§āĻāĻžāϰ āϏā§āĻāĻŋāĻ āϏāĻā§āώāĻŽ āύāĻž āĻĨāĻžāĻāϞ⧠āĻŦāĻŋāĻļāĻĻ āĻāĻāĻžāϰāϰāĻžāĻāĻ āĻāϰ⧠āύāĻžāĨ¤",
|
"MessageQuickMatchDescription": "āĻāĻžāϞāĻŋ āĻāĻāĻā§āĻŽā§āϰ āĻŦāĻŋāĻļāĻĻ āĻŦāĻŋāĻŦāϰāĻŖ āĻāĻŦāĻ '{0}' āĻĨā§āĻā§ āĻĒā§āϰāĻĨāĻŽ āĻŽā§āϝāĻžāĻā§āϰ āĻĢāϞāĻžāĻĢāϞā§āϰ āϏāĻžāĻĨā§ āĻāĻāĻžāϰ āĻāϰā§āύāĨ¤ āϏāĻžāϰā§āĻāĻžāϰ āϏā§āĻāĻŋāĻ āϏāĻā§āώāĻŽ āύāĻž āĻĨāĻžāĻāϞ⧠āĻŦāĻŋāĻļāĻĻ āĻāĻāĻžāϰāϰāĻžāĻāĻ āĻāϰ⧠āύāĻžāĨ¤",
|
||||||
"MessageRemoveChapter": "āĻ
āϧā§āϝāĻžāϝāĻŧ āϏāϰāĻžāύ",
|
"MessageRemoveChapter": "āĻ
āϧā§āϝāĻžāϝāĻŧ āϏāϰāĻžāύ",
|
||||||
"MessageRemoveEpisodes": "{0}āĻāĻŋ āĻĒāϰā§āĻŦ(āĻā§āϞāĻŋ) āϏāϰāĻžāύ",
|
"MessageRemoveEpisodes": "{0}āĻāĻŋ āĻĒāϰā§āĻŦ(āĻā§āϞāĻŋ) āϏāϰāĻžāύ",
|
||||||
@@ -802,6 +860,9 @@
|
|||||||
"MessageTaskOpmlImportFeedPodcastExists": "āĻĒāĻĄāĻāĻžāϏā§āĻ āĻāĻā§ āĻĨā§āĻā§āĻ āĻĒāĻžāĻĨā§ āĻŦāĻŋāĻĻā§āϝāĻŽāĻžāύ",
|
"MessageTaskOpmlImportFeedPodcastExists": "āĻĒāĻĄāĻāĻžāϏā§āĻ āĻāĻā§ āĻĨā§āĻā§āĻ āĻĒāĻžāĻĨā§ āĻŦāĻŋāĻĻā§āϝāĻŽāĻžāύ",
|
||||||
"MessageTaskOpmlImportFeedPodcastFailed": "āĻĒāĻĄāĻāĻžāϏā§āĻ āϤā§āϰāĻŋ āĻāϰāϤ⧠āĻŦā§āϝāϰā§āĻĨ",
|
"MessageTaskOpmlImportFeedPodcastFailed": "āĻĒāĻĄāĻāĻžāϏā§āĻ āϤā§āϰāĻŋ āĻāϰāϤ⧠āĻŦā§āϝāϰā§āĻĨ",
|
||||||
"MessageTaskOpmlImportFinished": "{0}āĻāĻŋ āĻĒāĻĄāĻāĻžāϏā§āĻ āϝā§āĻ āĻāϰāĻž āĻšāϝāĻŧā§āĻā§",
|
"MessageTaskOpmlImportFinished": "{0}āĻāĻŋ āĻĒāĻĄāĻāĻžāϏā§āĻ āϝā§āĻ āĻāϰāĻž āĻšāϝāĻŧā§āĻā§",
|
||||||
|
"MessageTaskOpmlParseFailed": "OPML āĻĢāĻžāĻāϞ āĻĒāĻžāϰā§āϏ āĻāϰāϤ⧠āĻŦā§āϝāϰā§āĻĨ āĻšāϝāĻŧā§āĻā§",
|
||||||
|
"MessageTaskOpmlParseFastFail": "āĻ
āĻŦā§āϧ OPML āĻĢāĻžāĻāϞ <opml> āĻā§āϝāĻžāĻ āĻĒāĻžāĻāϝāĻŧāĻž āϝāĻžāϝāĻŧāύāĻŋ āĻŦāĻž āĻāĻāĻāĻŋ <outline> āĻā§āϝāĻžāĻ āĻĒāĻžāĻāϝāĻŧāĻž āϝāĻžāϝāĻŧāύāĻŋ",
|
||||||
|
"MessageTaskOpmlParseNoneFound": "OPML āĻĢāĻžāĻāϞ⧠āĻā§āύ⧠āĻĢāĻŋāĻĄ āĻĒāĻžāĻāϝāĻŧāĻž āϝāĻžāϝāĻŧāύāĻŋ",
|
||||||
"MessageTaskScanItemsAdded": "{0}āĻāĻŋ āĻāϰāĻž āĻšāϝāĻŧā§āĻā§",
|
"MessageTaskScanItemsAdded": "{0}āĻāĻŋ āĻāϰāĻž āĻšāϝāĻŧā§āĻā§",
|
||||||
"MessageTaskScanItemsMissing": "{0}āĻāĻŋ āĻ
āύā§āĻĒāϏā§āĻĨāĻŋāϤ",
|
"MessageTaskScanItemsMissing": "{0}āĻāĻŋ āĻ
āύā§āĻĒāϏā§āĻĨāĻŋāϤ",
|
||||||
"MessageTaskScanItemsUpdated": "{0} āĻāĻŋ āĻāĻĒāĻĄā§āĻ āĻāϰāĻž āĻšāϝāĻŧā§āĻā§",
|
"MessageTaskScanItemsUpdated": "{0} āĻāĻŋ āĻāĻĒāĻĄā§āĻ āĻāϰāĻž āĻšāϝāĻŧā§āĻā§",
|
||||||
@@ -826,6 +887,10 @@
|
|||||||
"NoteUploaderFoldersWithMediaFiles": "āĻŽāĻŋāĻĄāĻŋāϝāĻŧāĻž āĻĢāĻžāĻāϞ āϏāĻš āĻĢā§āϞā§āĻĄāĻžāϰāĻā§āϞāĻŋ āĻāϞāĻžāĻĻāĻž āϞāĻžāĻāĻŦā§āϰā§āϰāĻŋ āĻāĻāĻā§āĻŽ āĻšāĻŋāϏāĻžāĻŦā§ āĻĒāϰāĻŋāĻāĻžāϞāύāĻž āĻāϰāĻž āĻšāĻŦā§āĨ¤",
|
"NoteUploaderFoldersWithMediaFiles": "āĻŽāĻŋāĻĄāĻŋāϝāĻŧāĻž āĻĢāĻžāĻāϞ āϏāĻš āĻĢā§āϞā§āĻĄāĻžāϰāĻā§āϞāĻŋ āĻāϞāĻžāĻĻāĻž āϞāĻžāĻāĻŦā§āϰā§āϰāĻŋ āĻāĻāĻā§āĻŽ āĻšāĻŋāϏāĻžāĻŦā§ āĻĒāϰāĻŋāĻāĻžāϞāύāĻž āĻāϰāĻž āĻšāĻŦā§āĨ¤",
|
||||||
"NoteUploaderOnlyAudioFiles": "āϝāĻĻāĻŋ āĻļā§āϧā§āĻŽāĻžāϤā§āϰ āĻ
āĻĄāĻŋāĻ āĻĢāĻžāĻāϞ āĻāĻĒāϞā§āĻĄ āĻāϰāĻž āĻšāϝāĻŧ āϤāĻŦā§ āĻĒā§āϰāϤāĻŋāĻāĻŋ āĻ
āĻĄāĻŋāĻ āĻĢāĻžāĻāϞ āĻāĻāĻāĻŋ āĻĒā§āĻĨāĻ āĻ
āĻĄāĻŋāĻāĻŦā§āĻ āĻšāĻŋāϏāĻžāĻŦā§ āĻĒāϰāĻŋāĻāĻžāϞāύāĻž āĻāϰāĻž āĻšāĻŦā§āĨ¤",
|
"NoteUploaderOnlyAudioFiles": "āϝāĻĻāĻŋ āĻļā§āϧā§āĻŽāĻžāϤā§āϰ āĻ
āĻĄāĻŋāĻ āĻĢāĻžāĻāϞ āĻāĻĒāϞā§āĻĄ āĻāϰāĻž āĻšāϝāĻŧ āϤāĻŦā§ āĻĒā§āϰāϤāĻŋāĻāĻŋ āĻ
āĻĄāĻŋāĻ āĻĢāĻžāĻāϞ āĻāĻāĻāĻŋ āĻĒā§āĻĨāĻ āĻ
āĻĄāĻŋāĻāĻŦā§āĻ āĻšāĻŋāϏāĻžāĻŦā§ āĻĒāϰāĻŋāĻāĻžāϞāύāĻž āĻāϰāĻž āĻšāĻŦā§āĨ¤",
|
||||||
"NoteUploaderUnsupportedFiles": "āĻ
āϏāĻŽāϰā§āĻĨāĻŋāϤ āĻĢāĻžāĻāϞāĻā§āϞāĻŋ āĻāĻĒā§āĻā§āώāĻž āĻāϰāĻž āĻšāϝāĻŧāĨ¤ āĻāĻāĻāĻŋ āĻĢā§āϞā§āĻĄāĻžāϰ āĻŦā§āĻā§ āύā§āĻāϝāĻŧāĻž āĻŦāĻž āĻĢā§āϞ⧠āĻĻā§āĻāϝāĻŧāĻžāϰ āϏāĻŽāϝāĻŧ, āĻāĻāĻā§āĻŽ āĻĢā§āϞā§āĻĄāĻžāϰ⧠āύā§āĻ āĻāĻŽāύ āĻ
āύā§āϝāĻžāύā§āϝ āĻĢāĻžāĻāϞāĻā§āϞāĻŋ āĻāĻĒā§āĻā§āώāĻž āĻāϰāĻž āĻšāϝāĻŧāĨ¤",
|
"NoteUploaderUnsupportedFiles": "āĻ
āϏāĻŽāϰā§āĻĨāĻŋāϤ āĻĢāĻžāĻāϞāĻā§āϞāĻŋ āĻāĻĒā§āĻā§āώāĻž āĻāϰāĻž āĻšāϝāĻŧāĨ¤ āĻāĻāĻāĻŋ āĻĢā§āϞā§āĻĄāĻžāϰ āĻŦā§āĻā§ āύā§āĻāϝāĻŧāĻž āĻŦāĻž āĻĢā§āϞ⧠āĻĻā§āĻāϝāĻŧāĻžāϰ āϏāĻŽāϝāĻŧ, āĻāĻāĻā§āĻŽ āĻĢā§āϞā§āĻĄāĻžāϰ⧠āύā§āĻ āĻāĻŽāύ āĻ
āύā§āϝāĻžāύā§āϝ āĻĢāĻžāĻāϞāĻā§āϞāĻŋ āĻāĻĒā§āĻā§āώāĻž āĻāϰāĻž āĻšāϝāĻŧāĨ¤",
|
||||||
|
"NotificationOnBackupCompletedDescription": "āĻŦā§āϝāĻžāĻāĻāĻĒ āϏāĻŽā§āĻĒā§āϰā§āĻŖ āĻšāϞ⧠āĻā§āϰāĻŋāĻāĻžāϰ āĻšāĻŦā§",
|
||||||
|
"NotificationOnBackupFailedDescription": "āĻŦā§āϝāĻžāĻāĻāĻĒ āĻŦā§āϝāϰā§āĻĨ āĻšāϞ⧠āĻā§āϰāĻŋāĻāĻžāϰ āĻšāĻŦā§",
|
||||||
|
"NotificationOnEpisodeDownloadedDescription": "āĻāĻāĻāĻŋ āĻĒāĻĄāĻāĻžāϏā§āĻ āĻĒāϰā§āĻŦ āϏā§āĻŦāϝāĻŧāĻāĻā§āϰāĻŋāϝāĻŧāĻāĻžāĻŦā§ āĻĄāĻžāĻāύāϞā§āĻĄ āĻšāϞ⧠āĻā§āϰāĻŋāĻāĻžāϰ āĻšāĻŦā§",
|
||||||
|
"NotificationOnTestDescription": "āĻŦāĻŋāĻā§āĻāĻĒā§āϤāĻŋ āϏāĻŋāϏā§āĻā§āĻŽ āĻĒāϰā§āĻā§āώāĻžāϰ āĻāύā§āϝ āĻāĻā§āύā§āĻ",
|
||||||
"PlaceholderNewCollection": "āύāϤā§āύ āϏāĻāĻā§āϰāĻšā§āϰ āύāĻžāĻŽ",
|
"PlaceholderNewCollection": "āύāϤā§āύ āϏāĻāĻā§āϰāĻšā§āϰ āύāĻžāĻŽ",
|
||||||
"PlaceholderNewFolderPath": "āύāϤā§āύ āĻĢā§āϞā§āĻĄāĻžāϰ āĻĒāĻĨ",
|
"PlaceholderNewFolderPath": "āύāϤā§āύ āĻĢā§āϞā§āĻĄāĻžāϰ āĻĒāĻĨ",
|
||||||
"PlaceholderNewPlaylist": "āύāϤā§āύ āĻĒā§āϞā§āϞāĻŋāϏā§āĻā§āϰ āύāĻžāĻŽ",
|
"PlaceholderNewPlaylist": "āύāϤā§āύ āĻĒā§āϞā§āϞāĻŋāϏā§āĻā§āϰ āύāĻžāĻŽ",
|
||||||
@@ -851,6 +916,7 @@
|
|||||||
"StatsYearInReview": "āĻŦāĻžā§āϏāϰāĻŋāĻ āĻĒāϰā§āϝāĻžāϞā§āĻāύāĻž",
|
"StatsYearInReview": "āĻŦāĻžā§āϏāϰāĻŋāĻ āĻĒāϰā§āϝāĻžāϞā§āĻāύāĻž",
|
||||||
"ToastAccountUpdateSuccess": "āĻ
ā§āϝāĻžāĻāĻžāĻāύā§āĻ āĻāĻĒāĻĄā§āĻ āĻāϰāĻž āĻšāϝāĻŧā§āĻā§",
|
"ToastAccountUpdateSuccess": "āĻ
ā§āϝāĻžāĻāĻžāĻāύā§āĻ āĻāĻĒāĻĄā§āĻ āĻāϰāĻž āĻšāϝāĻŧā§āĻā§",
|
||||||
"ToastAppriseUrlRequired": "āĻāĻāĻāĻŋ Apprise āĻāĻāĻāϰāĻāϞ āϞāĻŋāĻāϤ⧠āĻšāĻŦā§",
|
"ToastAppriseUrlRequired": "āĻāĻāĻāĻŋ Apprise āĻāĻāĻāϰāĻāϞ āϞāĻŋāĻāϤ⧠āĻšāĻŦā§",
|
||||||
|
"ToastAsinRequired": "ASIN āĻĒā§āϰāϝāĻŧā§āĻāύ",
|
||||||
"ToastAuthorImageRemoveSuccess": "āϞā§āĻāĻā§āϰ āĻāĻŦāĻŋ āϏāϰāĻžāύ⧠āĻšāϝāĻŧā§āĻā§",
|
"ToastAuthorImageRemoveSuccess": "āϞā§āĻāĻā§āϰ āĻāĻŦāĻŋ āϏāϰāĻžāύ⧠āĻšāϝāĻŧā§āĻā§",
|
||||||
"ToastAuthorNotFound": "āϞā§āĻāĻ \"{0}\" āĻā§āĻāĻā§ āĻĒāĻžāĻāϝāĻŧāĻž āϝāĻžāϝāĻŧāύāĻŋ",
|
"ToastAuthorNotFound": "āϞā§āĻāĻ \"{0}\" āĻā§āĻāĻā§ āĻĒāĻžāĻāϝāĻŧāĻž āϝāĻžāϝāĻŧāύāĻŋ",
|
||||||
"ToastAuthorRemoveSuccess": "āϞā§āĻāĻ āϏāϰāĻžāύ⧠āĻšāϝāĻŧā§āĻā§",
|
"ToastAuthorRemoveSuccess": "āϞā§āĻāĻ āϏāϰāĻžāύ⧠āĻšāϝāĻŧā§āĻā§",
|
||||||
@@ -870,6 +936,8 @@
|
|||||||
"ToastBackupUploadSuccess": "āĻŦā§āϝāĻžāĻāĻāĻĒ āĻāĻĒāϞā§āĻĄ āĻšāϝāĻŧā§āĻā§",
|
"ToastBackupUploadSuccess": "āĻŦā§āϝāĻžāĻāĻāĻĒ āĻāĻĒāϞā§āĻĄ āĻšāϝāĻŧā§āĻā§",
|
||||||
"ToastBatchDeleteFailed": "āĻŦā§āϝāĻžāĻ āĻŽā§āĻā§ āĻĢā§āϞāϤ⧠āĻŦā§āϝāϰā§āĻĨ āĻšāϝāĻŧā§āĻā§",
|
"ToastBatchDeleteFailed": "āĻŦā§āϝāĻžāĻ āĻŽā§āĻā§ āĻĢā§āϞāϤ⧠āĻŦā§āϝāϰā§āĻĨ āĻšāϝāĻŧā§āĻā§",
|
||||||
"ToastBatchDeleteSuccess": "āĻŦā§āϝāĻžāĻ āĻŽā§āĻā§ āĻĢā§āϞāĻž āϏāĻĢāϞ āĻšā§ā§āĻā§",
|
"ToastBatchDeleteSuccess": "āĻŦā§āϝāĻžāĻ āĻŽā§āĻā§ āĻĢā§āϞāĻž āϏāĻĢāϞ āĻšā§ā§āĻā§",
|
||||||
|
"ToastBatchQuickMatchFailed": "āĻŦā§āϝāĻžāĻ āĻā§āĻāĻ āĻŽā§āϝāĻžāĻ āĻŦā§āϝāϰā§āĻĨ!",
|
||||||
|
"ToastBatchQuickMatchStarted": "{0}āĻāĻŋ āĻŦāĻāϝāĻŧā§āϰ āĻŦā§āϝāĻžāĻ āĻā§āĻāĻ āĻŽā§āϝāĻžāĻ āĻļā§āϰ⧠āĻšāϝāĻŧā§āĻā§!",
|
||||||
"ToastBatchUpdateFailed": "āĻŦā§āϝāĻžāĻ āĻāĻĒāĻĄā§āĻ āĻŦā§āϝāϰā§āĻĨ āĻšāϝāĻŧā§āĻā§",
|
"ToastBatchUpdateFailed": "āĻŦā§āϝāĻžāĻ āĻāĻĒāĻĄā§āĻ āĻŦā§āϝāϰā§āĻĨ āĻšāϝāĻŧā§āĻā§",
|
||||||
"ToastBatchUpdateSuccess": "āĻŦā§āϝāĻžāĻ āĻāĻĒāĻĄā§āĻ āϏāĻžāĻĢāϞā§āϝ",
|
"ToastBatchUpdateSuccess": "āĻŦā§āϝāĻžāĻ āĻāĻĒāĻĄā§āĻ āϏāĻžāĻĢāϞā§āϝ",
|
||||||
"ToastBookmarkCreateFailed": "āĻŦā§āĻāĻŽāĻžāϰā§āĻ āϤā§āϰāĻŋ āĻāϰāϤ⧠āĻŦā§āϝāϰā§āĻĨ",
|
"ToastBookmarkCreateFailed": "āĻŦā§āĻāĻŽāĻžāϰā§āĻ āϤā§āϰāĻŋ āĻāϰāϤ⧠āĻŦā§āϝāϰā§āĻĨ",
|
||||||
@@ -881,9 +949,8 @@
|
|||||||
"ToastChaptersHaveErrors": "āĻ
āϧā§āϝāĻžāϝāĻŧā§ āϤā§āϰā§āĻāĻŋ āĻāĻā§",
|
"ToastChaptersHaveErrors": "āĻ
āϧā§āϝāĻžāϝāĻŧā§ āϤā§āϰā§āĻāĻŋ āĻāĻā§",
|
||||||
"ToastChaptersMustHaveTitles": "āĻ
āϧā§āϝāĻžāϝāĻŧā§āϰ āĻļāĻŋāϰā§āύāĻžāĻŽ āĻĨāĻžāĻāϤ⧠āĻšāĻŦā§",
|
"ToastChaptersMustHaveTitles": "āĻ
āϧā§āϝāĻžāϝāĻŧā§āϰ āĻļāĻŋāϰā§āύāĻžāĻŽ āĻĨāĻžāĻāϤ⧠āĻšāĻŦā§",
|
||||||
"ToastChaptersRemoved": "āĻ
āϧā§āϝāĻžāϝāĻŧāĻā§āϞ⧠āĻŽā§āĻā§ āĻĢā§āϞāĻž āĻšāϝāĻŧā§āĻā§",
|
"ToastChaptersRemoved": "āĻ
āϧā§āϝāĻžāϝāĻŧāĻā§āϞ⧠āĻŽā§āĻā§ āĻĢā§āϞāĻž āĻšāϝāĻŧā§āĻā§",
|
||||||
|
"ToastChaptersUpdated": "āĻ
āϧā§āϝāĻžāϝāĻŧ āĻāĻĒāĻĄā§āĻ āĻāϰāĻž āĻšāϝāĻŧā§āĻā§",
|
||||||
"ToastCollectionItemsAddFailed": "āĻāĻāĻā§āĻŽ(āĻā§āϞāĻŋ) āϏāĻāĻā§āϰāĻšā§ āϝā§āĻ āĻāϰāĻž āĻŦā§āϝāϰā§āĻĨ āĻšāϝāĻŧā§āĻā§",
|
"ToastCollectionItemsAddFailed": "āĻāĻāĻā§āĻŽ(āĻā§āϞāĻŋ) āϏāĻāĻā§āϰāĻšā§ āϝā§āĻ āĻāϰāĻž āĻŦā§āϝāϰā§āĻĨ āĻšāϝāĻŧā§āĻā§",
|
||||||
"ToastCollectionItemsAddSuccess": "āĻāĻāĻā§āĻŽ(āĻā§āϞāĻŋ) āϏāĻāĻā§āϰāĻšā§ āϝā§āĻ āĻāϰāĻž āϏāĻĢāϞ āĻšāϝāĻŧā§āĻā§",
|
|
||||||
"ToastCollectionItemsRemoveSuccess": "āĻāĻāĻā§āĻŽ(āĻā§āϞāĻŋ) āϏāĻāĻā§āϰāĻš āĻĨā§āĻā§ āϏāϰāĻžāύ⧠āĻšāϝāĻŧā§āĻā§",
|
|
||||||
"ToastCollectionRemoveSuccess": "āϏāĻāĻā§āϰāĻš āϏāϰāĻžāύ⧠āĻšāϝāĻŧā§āĻā§",
|
"ToastCollectionRemoveSuccess": "āϏāĻāĻā§āϰāĻš āϏāϰāĻžāύ⧠āĻšāϝāĻŧā§āĻā§",
|
||||||
"ToastCollectionUpdateSuccess": "āϏāĻāĻā§āϰāĻš āĻāĻĒāĻĄā§āĻ āĻāϰāĻž āĻšāϝāĻŧā§āĻā§",
|
"ToastCollectionUpdateSuccess": "āϏāĻāĻā§āϰāĻš āĻāĻĒāĻĄā§āĻ āĻāϰāĻž āĻšāϝāĻŧā§āĻā§",
|
||||||
"ToastCoverUpdateFailed": "āĻāĻāĻžāϰ āĻāĻĒāĻĄā§āĻ āĻŦā§āϝāϰā§āĻĨ āĻšāϝāĻŧā§āĻā§",
|
"ToastCoverUpdateFailed": "āĻāĻāĻžāϰ āĻāĻĒāĻĄā§āĻ āĻŦā§āϝāϰā§āĻĨ āĻšāϝāĻŧā§āĻā§",
|
||||||
@@ -898,11 +965,14 @@
|
|||||||
"ToastEncodeCancelSucces": "āĻāύāĻā§āĻĄ āĻŦāĻžāϤāĻŋāϞ āĻāϰāĻž āĻšāϝāĻŧā§āĻā§",
|
"ToastEncodeCancelSucces": "āĻāύāĻā§āĻĄ āĻŦāĻžāϤāĻŋāϞ āĻāϰāĻž āĻšāϝāĻŧā§āĻā§",
|
||||||
"ToastEpisodeDownloadQueueClearFailed": "āϏāĻžāϰāĻŋ āϏāĻžāĻĢ āĻāϰāϤ⧠āĻŦā§āϝāϰā§āĻĨ āĻšāϝāĻŧā§āĻā§",
|
"ToastEpisodeDownloadQueueClearFailed": "āϏāĻžāϰāĻŋ āϏāĻžāĻĢ āĻāϰāϤ⧠āĻŦā§āϝāϰā§āĻĨ āĻšāϝāĻŧā§āĻā§",
|
||||||
"ToastEpisodeDownloadQueueClearSuccess": "āĻĒāϰā§āĻŦ āĻĄāĻžāĻāύāϞā§āĻĄ āϏāĻžāϰāĻŋ āĻĒāϰāĻŋāώā§āĻāĻžāϰ āĻāϰāĻž āĻšāϝāĻŧā§āĻā§",
|
"ToastEpisodeDownloadQueueClearSuccess": "āĻĒāϰā§āĻŦ āĻĄāĻžāĻāύāϞā§āĻĄ āϏāĻžāϰāĻŋ āĻĒāϰāĻŋāώā§āĻāĻžāϰ āĻāϰāĻž āĻšāϝāĻŧā§āĻā§",
|
||||||
|
"ToastEpisodeUpdateSuccess": "{0}āĻāĻŋ āĻĒāϰā§āĻŦ āĻāĻĒāĻĄā§āĻ āĻāϰāĻž āĻšāϝāĻŧā§āĻā§",
|
||||||
"ToastErrorCannotShare": "āĻāĻ āĻĄāĻŋāĻāĻžāĻāϏ⧠āϏā§āĻĨāĻžāύā§āϝāĻŧāĻāĻžāĻŦā§ āĻļā§āϝāĻŧāĻžāϰ āĻāϰāĻž āϝāĻžāĻŦā§ āύāĻž",
|
"ToastErrorCannotShare": "āĻāĻ āĻĄāĻŋāĻāĻžāĻāϏ⧠āϏā§āĻĨāĻžāύā§āϝāĻŧāĻāĻžāĻŦā§ āĻļā§āϝāĻŧāĻžāϰ āĻāϰāĻž āϝāĻžāĻŦā§ āύāĻž",
|
||||||
"ToastFailedToLoadData": "āĻĄā§āĻāĻž āϞā§āĻĄ āĻāϰāĻž āϝāĻžāϝāĻŧāύāĻŋ",
|
"ToastFailedToLoadData": "āĻĄā§āĻāĻž āϞā§āĻĄ āĻāϰāĻž āϝāĻžāϝāĻŧāύāĻŋ",
|
||||||
|
"ToastFailedToMatch": "āĻŽā§āϞāĻžāϤ⧠āĻŦā§āϝāϰā§āĻĨ āĻšā§ā§āĻā§",
|
||||||
"ToastFailedToShare": "āĻļā§āϝāĻŧāĻžāϰ āĻāϰāϤ⧠āĻŦā§āϝāϰā§āĻĨ",
|
"ToastFailedToShare": "āĻļā§āϝāĻŧāĻžāϰ āĻāϰāϤ⧠āĻŦā§āϝāϰā§āĻĨ",
|
||||||
"ToastFailedToUpdate": "āĻāĻĒāĻĄā§āĻ āĻāϰāϤ⧠āĻŦā§āϝāϰā§āĻĨ āĻšāϝāĻŧā§āĻā§",
|
"ToastFailedToUpdate": "āĻāĻĒāĻĄā§āĻ āĻāϰāϤ⧠āĻŦā§āϝāϰā§āĻĨ āĻšāϝāĻŧā§āĻā§",
|
||||||
"ToastInvalidImageUrl": "āĻ
āĻāĻžāϰā§āϝāĻāϰ āĻāĻŦāĻŋāϰ āĻāĻāĻāϰāĻāϞ",
|
"ToastInvalidImageUrl": "āĻ
āĻāĻžāϰā§āϝāĻāϰ āĻāĻŦāĻŋāϰ āĻāĻāĻāϰāĻāϞ",
|
||||||
|
"ToastInvalidMaxEpisodesToDownload": "āĻĄāĻžāĻāύāϞā§āĻĄ āĻāϰāĻžāϰ āĻāύā§āϝ āĻ
āĻŦā§āϧ āϏāϰā§āĻŦā§āĻā§āĻ āĻĒāϰā§āĻŦ",
|
||||||
"ToastInvalidUrl": "āĻ
āĻāĻžāϰā§āϝāĻāϰ āĻāĻāĻāϰāĻāϞ",
|
"ToastInvalidUrl": "āĻ
āĻāĻžāϰā§āϝāĻāϰ āĻāĻāĻāϰāĻāϞ",
|
||||||
"ToastItemCoverUpdateSuccess": "āĻāĻāĻā§āĻŽ āĻāĻāĻžāϰ āĻāĻĒāĻĄā§āĻ āĻāϰāĻž āĻšāϝāĻŧā§āĻā§",
|
"ToastItemCoverUpdateSuccess": "āĻāĻāĻā§āĻŽ āĻāĻāĻžāϰ āĻāĻĒāĻĄā§āĻ āĻāϰāĻž āĻšāϝāĻŧā§āĻā§",
|
||||||
"ToastItemDeletedFailed": "āĻāĻāĻā§āĻŽ āĻŽā§āĻā§ āĻĢā§āϞāϤ⧠āĻŦā§āϝāϰā§āĻĨ",
|
"ToastItemDeletedFailed": "āĻāĻāĻā§āĻŽ āĻŽā§āĻā§ āĻĢā§āϞāϤ⧠āĻŦā§āϝāϰā§āĻĨ",
|
||||||
@@ -920,14 +990,22 @@
|
|||||||
"ToastLibraryScanFailedToStart": "āϏā§āĻā§āϝāĻžāύ āĻļā§āϰ⧠āĻāϰāϤ⧠āĻŦā§āϝāϰā§āĻĨ",
|
"ToastLibraryScanFailedToStart": "āϏā§āĻā§āϝāĻžāύ āĻļā§āϰ⧠āĻāϰāϤ⧠āĻŦā§āϝāϰā§āĻĨ",
|
||||||
"ToastLibraryScanStarted": "āϞāĻžāĻāĻŦā§āϰā§āϰāĻŋ āϏā§āĻā§āϝāĻžāύ āĻļā§āϰ⧠āĻšāϝāĻŧā§āĻā§",
|
"ToastLibraryScanStarted": "āϞāĻžāĻāĻŦā§āϰā§āϰāĻŋ āϏā§āĻā§āϝāĻžāύ āĻļā§āϰ⧠āĻšāϝāĻŧā§āĻā§",
|
||||||
"ToastLibraryUpdateSuccess": "āϞāĻžāĻāĻŦā§āϰā§āϰāĻŋ \"{0}\" āĻāĻĒāĻĄā§āĻ āĻāϰāĻž āĻšāϝāĻŧā§āĻā§",
|
"ToastLibraryUpdateSuccess": "āϞāĻžāĻāĻŦā§āϰā§āϰāĻŋ \"{0}\" āĻāĻĒāĻĄā§āĻ āĻāϰāĻž āĻšāϝāĻŧā§āĻā§",
|
||||||
|
"ToastMatchAllAuthorsFailed": "āϏāĻŽāϏā§āϤ āϞā§āĻāĻā§āϰ āϏāĻžāĻĨā§ āĻŽāĻŋāϞāϤ⧠āĻŦā§āϝāϰā§āĻĨ āĻšāϝāĻŧā§āĻā§",
|
||||||
|
"ToastMetadataFilesRemovedError": "āĻŽā§āĻāĻžāĻĄā§āĻāĻž āϏāϰāĻžāύā§āϰ āϏāĻŽāϝāĻŧ āϤā§āϰā§āĻāĻŋ {0} āĻĢāĻžāĻāϞ",
|
||||||
|
"ToastMetadataFilesRemovedNoneFound": "āĻā§āύ⧠āĻŽā§āĻāĻžāĻĄā§āĻāĻž āύā§āĻāĨ¤āϞāĻžāĻāĻŦā§āϰā§āϰāĻŋāϤ⧠{0} āĻĢāĻžāĻāϞ āĻĒāĻžāĻāϝāĻŧāĻž āĻā§āĻā§",
|
||||||
|
"ToastMetadataFilesRemovedNoneRemoved": "āĻā§āύ⧠āĻŽā§āĻāĻžāĻĄā§āĻāĻž āύā§āĻāĨ¤{0} āĻĢāĻžāĻāϞ āϏāϰāĻžāύ⧠āĻšāϝāĻŧā§āĻā§",
|
||||||
|
"ToastMetadataFilesRemovedSuccess": "{0} āĻŽā§āĻāĻžāĻĄā§āĻāĻžā§ˇ{1} āĻĢāĻžāĻāϞ āϏāϰāĻžāύ⧠āĻšāϝāĻŧā§āĻā§",
|
||||||
|
"ToastMustHaveAtLeastOnePath": "āĻ
āύā§āϤāϤ āĻāĻāĻāĻŋ āĻĒāĻĨ āĻĨāĻžāĻāϤ⧠āĻšāĻŦā§",
|
||||||
"ToastNameEmailRequired": "āύāĻžāĻŽ āĻāĻŦāĻ āĻāĻŽā§āĻāϞ āĻāĻŦāĻļā§āϝāĻ",
|
"ToastNameEmailRequired": "āύāĻžāĻŽ āĻāĻŦāĻ āĻāĻŽā§āĻāϞ āĻāĻŦāĻļā§āϝāĻ",
|
||||||
"ToastNameRequired": "āύāĻžāĻŽ āĻāĻŦāĻļā§āϝāĻ",
|
"ToastNameRequired": "āύāĻžāĻŽ āĻāĻŦāĻļā§āϝāĻ",
|
||||||
|
"ToastNewEpisodesFound": "{0}āĻāĻŋ āύāϤā§āύ āĻĒāϰā§āĻŦ āĻĒāĻžāĻāϝāĻŧāĻž āĻā§āĻā§",
|
||||||
"ToastNewUserCreatedFailed": "āĻ
ā§āϝāĻžāĻāĻžāĻāύā§āĻ āϤā§āϰāĻŋ āĻāϰāϤ⧠āĻŦā§āϝāϰā§āĻĨ: \"{0}\"",
|
"ToastNewUserCreatedFailed": "āĻ
ā§āϝāĻžāĻāĻžāĻāύā§āĻ āϤā§āϰāĻŋ āĻāϰāϤ⧠āĻŦā§āϝāϰā§āĻĨ: \"{0}\"",
|
||||||
"ToastNewUserCreatedSuccess": "āύāϤā§āύ āĻāĻāĻžāĻāύā§āĻ āϤā§āϰāĻŋ āĻšāϝāĻŧā§āĻā§",
|
"ToastNewUserCreatedSuccess": "āύāϤā§āύ āĻāĻāĻžāĻāύā§āĻ āϤā§āϰāĻŋ āĻšāϝāĻŧā§āĻā§",
|
||||||
"ToastNewUserLibraryError": "āĻ
āύā§āϤāϤ āĻāĻāĻāĻŋ āϞāĻžāĻāĻŦā§āϰā§āϰāĻŋ āύāĻŋāϰā§āĻŦāĻžāĻāύ āĻāϰāϤ⧠āĻšāĻŦā§",
|
"ToastNewUserLibraryError": "āĻ
āύā§āϤāϤ āĻāĻāĻāĻŋ āϞāĻžāĻāĻŦā§āϰā§āϰāĻŋ āύāĻŋāϰā§āĻŦāĻžāĻāύ āĻāϰāϤ⧠āĻšāĻŦā§",
|
||||||
"ToastNewUserPasswordError": "āĻ
āύā§āϤāϤ āĻāĻāĻāĻŋ āĻĒāĻžāϏāĻāϝāĻŧāĻžāϰā§āĻĄ āĻĨāĻžāĻāϤ⧠āĻšāĻŦā§, āĻļā§āϧā§āĻŽāĻžāϤā§āϰ āϰā§āĻ āĻŦā§āϝāĻŦāĻšāĻžāϰāĻāĻžāϰā§āϰ āĻāĻāĻāĻŋ āĻāĻžāϞāĻŋ āĻĒāĻžāϏāĻāϝāĻŧāĻžāϰā§āĻĄ āĻĨāĻžāĻāϤ⧠āĻĒāĻžāϰā§",
|
"ToastNewUserPasswordError": "āĻ
āύā§āϤāϤ āĻāĻāĻāĻŋ āĻĒāĻžāϏāĻāϝāĻŧāĻžāϰā§āĻĄ āĻĨāĻžāĻāϤ⧠āĻšāĻŦā§, āĻļā§āϧā§āĻŽāĻžāϤā§āϰ āϰā§āĻ āĻŦā§āϝāĻŦāĻšāĻžāϰāĻāĻžāϰā§āϰ āĻāĻāĻāĻŋ āĻāĻžāϞāĻŋ āĻĒāĻžāϏāĻāϝāĻŧāĻžāϰā§āĻĄ āĻĨāĻžāĻāϤ⧠āĻĒāĻžāϰā§",
|
||||||
"ToastNewUserTagError": "āĻ
āύā§āϤāϤ āĻāĻāĻāĻŋ āĻā§āϝāĻžāĻ āύāĻŋāϰā§āĻŦāĻžāĻāύ āĻāϰāϤ⧠āĻšāĻŦā§",
|
"ToastNewUserTagError": "āĻ
āύā§āϤāϤ āĻāĻāĻāĻŋ āĻā§āϝāĻžāĻ āύāĻŋāϰā§āĻŦāĻžāĻāύ āĻāϰāϤ⧠āĻšāĻŦā§",
|
||||||
"ToastNewUserUsernameError": "āĻāĻāĻāĻŋ āĻŦā§āϝāĻŦāĻšāĻžāϰāĻāĻžāϰā§āϰ āύāĻžāĻŽ āϞāĻŋāĻā§āύ",
|
"ToastNewUserUsernameError": "āĻāĻāĻāĻŋ āĻŦā§āϝāĻŦāĻšāĻžāϰāĻāĻžāϰā§āϰ āύāĻžāĻŽ āϞāĻŋāĻā§āύ",
|
||||||
|
"ToastNoNewEpisodesFound": "āĻā§āύ āύāϤā§āύ āĻĒāϰā§āĻŦ āĻĒāĻžāĻāϝāĻŧāĻž āϝāĻžāϝāĻŧāύāĻŋ",
|
||||||
"ToastNoUpdatesNecessary": "āĻā§āύ āĻāĻĒāĻĄā§āĻā§āϰ āĻĒā§āϰāϝāĻŧā§āĻāύ āύā§āĻ",
|
"ToastNoUpdatesNecessary": "āĻā§āύ āĻāĻĒāĻĄā§āĻā§āϰ āĻĒā§āϰāϝāĻŧā§āĻāύ āύā§āĻ",
|
||||||
"ToastNotificationCreateFailed": "āĻŦāĻŋāĻā§āĻāĻĒā§āϤāĻŋ āϤā§āϰāĻŋ āĻāϰāϤ⧠āĻŦā§āϝāϰā§āĻĨ",
|
"ToastNotificationCreateFailed": "āĻŦāĻŋāĻā§āĻāĻĒā§āϤāĻŋ āϤā§āϰāĻŋ āĻāϰāϤ⧠āĻŦā§āϝāϰā§āĻĨ",
|
||||||
"ToastNotificationDeleteFailed": "āĻŦāĻŋāĻā§āĻāĻĒā§āϤāĻŋ āĻŽā§āĻā§ āĻĢā§āϞāϤ⧠āĻŦā§āϝāϰā§āĻĨ",
|
"ToastNotificationDeleteFailed": "āĻŦāĻŋāĻā§āĻāĻĒā§āϤāĻŋ āĻŽā§āĻā§ āĻĢā§āϞāϤ⧠āĻŦā§āϝāϰā§āĻĨ",
|
||||||
@@ -946,6 +1024,7 @@
|
|||||||
"ToastPodcastGetFeedFailed": "āĻĒāĻĄāĻāĻžāϏā§āĻ āĻĢāĻŋāĻĄ āĻĒā§āϤ⧠āĻŦā§āϝāϰā§āĻĨ āĻšāϝāĻŧā§āĻā§",
|
"ToastPodcastGetFeedFailed": "āĻĒāĻĄāĻāĻžāϏā§āĻ āĻĢāĻŋāĻĄ āĻĒā§āϤ⧠āĻŦā§āϝāϰā§āĻĨ āĻšāϝāĻŧā§āĻā§",
|
||||||
"ToastPodcastNoEpisodesInFeed": "āĻāϰāĻāϏāĻāϏ āĻĢāĻŋāĻĄā§ āĻā§āύ⧠āĻĒāϰā§āĻŦ āĻĒāĻžāĻāϝāĻŧāĻž āϝāĻžāϝāĻŧāύāĻŋ",
|
"ToastPodcastNoEpisodesInFeed": "āĻāϰāĻāϏāĻāϏ āĻĢāĻŋāĻĄā§ āĻā§āύ⧠āĻĒāϰā§āĻŦ āĻĒāĻžāĻāϝāĻŧāĻž āϝāĻžāϝāĻŧāύāĻŋ",
|
||||||
"ToastPodcastNoRssFeed": "āĻĒāĻĄāĻāĻžāϏā§āĻā§āϰ āĻā§āύ āĻāϰāĻāϏāĻāϏ āĻĢāĻŋāĻĄ āύā§āĻ",
|
"ToastPodcastNoRssFeed": "āĻĒāĻĄāĻāĻžāϏā§āĻā§āϰ āĻā§āύ āĻāϰāĻāϏāĻāϏ āĻĢāĻŋāĻĄ āύā§āĻ",
|
||||||
|
"ToastProgressIsNotBeingSynced": "āĻ
āĻā§āϰāĻāϤāĻŋ āϏāĻŋāĻā§āĻ āĻšāĻā§āĻā§ āύāĻž, āĻĒā§āϞā§āĻŦā§āϝāĻžāĻ āĻĒā§āύāϰāĻžāϝāĻŧ āĻāĻžāϞ⧠āĻāϰā§āύ",
|
||||||
"ToastProviderCreatedFailed": "āĻĒā§āϰāĻĻāĻžāύāĻāĻžāϰ⧠āϝā§āĻ āĻāϰāϤ⧠āĻŦā§āϝāϰā§āĻĨ āĻšāϝāĻŧā§āĻā§",
|
"ToastProviderCreatedFailed": "āĻĒā§āϰāĻĻāĻžāύāĻāĻžāϰ⧠āϝā§āĻ āĻāϰāϤ⧠āĻŦā§āϝāϰā§āĻĨ āĻšāϝāĻŧā§āĻā§",
|
||||||
"ToastProviderCreatedSuccess": "āύāϤā§āύ āĻĒā§āϰāĻĻāĻžāύāĻāĻžāϰ⧠āϝā§āĻ āĻāϰāĻž āĻšāϝāĻŧā§āĻā§",
|
"ToastProviderCreatedSuccess": "āύāϤā§āύ āĻĒā§āϰāĻĻāĻžāύāĻāĻžāϰ⧠āϝā§āĻ āĻāϰāĻž āĻšāϝāĻŧā§āĻā§",
|
||||||
"ToastProviderNameAndUrlRequired": "āύāĻžāĻŽ āĻāĻŦāĻ āĻāĻāĻāϰāĻāϞ āĻāĻŦāĻļā§āϝāĻ",
|
"ToastProviderNameAndUrlRequired": "āύāĻžāĻŽ āĻāĻŦāĻ āĻāĻāĻāϰāĻāϞ āĻāĻŦāĻļā§āϝāĻ",
|
||||||
@@ -972,6 +1051,7 @@
|
|||||||
"ToastSessionCloseFailed": "āĻ
āϧāĻŋāĻŦā§āĻļāύ āĻŦāύā§āϧ āĻāϰāϤ⧠āĻŦā§āϝāϰā§āĻĨ āĻšāϝāĻŧā§āĻā§",
|
"ToastSessionCloseFailed": "āĻ
āϧāĻŋāĻŦā§āĻļāύ āĻŦāύā§āϧ āĻāϰāϤ⧠āĻŦā§āϝāϰā§āĻĨ āĻšāϝāĻŧā§āĻā§",
|
||||||
"ToastSessionDeleteFailed": "āϏā§āĻļāύ āĻŽā§āĻā§ āĻĢā§āϞāϤ⧠āĻŦā§āϝāϰā§āĻĨ",
|
"ToastSessionDeleteFailed": "āϏā§āĻļāύ āĻŽā§āĻā§ āĻĢā§āϞāϤ⧠āĻŦā§āϝāϰā§āĻĨ",
|
||||||
"ToastSessionDeleteSuccess": "āϏā§āĻļāύ āĻŽā§āĻā§ āĻĢā§āϞāĻž āĻšāϝāĻŧā§āĻā§",
|
"ToastSessionDeleteSuccess": "āϏā§āĻļāύ āĻŽā§āĻā§ āĻĢā§āϞāĻž āĻšāϝāĻŧā§āĻā§",
|
||||||
|
"ToastSleepTimerDone": "āϏā§āϞāĻŋāĻĒ āĻāĻžāĻāĻŽāĻžāϰ āĻšāϝāĻŧā§ āĻā§āĻā§... zZzzZz",
|
||||||
"ToastSlugMustChange": "āϏā§āϞāĻžāĻā§ āĻ
āĻŦā§āϧ āĻ
āĻā§āώāϰ āϰāϝāĻŧā§āĻā§",
|
"ToastSlugMustChange": "āϏā§āϞāĻžāĻā§ āĻ
āĻŦā§āϧ āĻ
āĻā§āώāϰ āϰāϝāĻŧā§āĻā§",
|
||||||
"ToastSlugRequired": "āϏā§āϞāĻžāĻ āĻāĻŦāĻļā§āϝāĻ",
|
"ToastSlugRequired": "āϏā§āϞāĻžāĻ āĻāĻŦāĻļā§āϝāĻ",
|
||||||
"ToastSocketConnected": "āϏāĻā§āĻ āϏāĻāϝā§āĻā§āϤ",
|
"ToastSocketConnected": "āϏāĻā§āĻ āϏāĻāϝā§āĻā§āϤ",
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user