mirror of
https://github.com/advplyr/audiobookshelf.git
synced 2026-06-03 17:30:39 +02:00
Compare commits
395 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| ed17dd9b51 | |||
| eb505a0be7 | |||
| f3918a47e1 | |||
| c8a05920dd | |||
| e7f7d1a573 | |||
| 5201625d38 | |||
| 8c4d0c503b | |||
| d3bda898d4 | |||
| 86809dcc62 | |||
| 9fa00a1904 | |||
| 46247ecf78 | |||
| 0444829a9f | |||
| 754c121168 | |||
| 1c2ee09f18 | |||
| ee310d967e | |||
| 25b7f005c6 | |||
| 777c59458d | |||
| 9785bc02ea | |||
| 6780ef9b37 | |||
| 88a0e75576 | |||
| 476933a144 | |||
| 2464aac2bf | |||
| b6b786e3a6 | |||
| bacb8aeac7 | |||
| ba9277cc44 | |||
| 3cc5fae586 | |||
| da7d9c10ad | |||
| aa82439125 | |||
| 2e0156d9fa | |||
| 20e0172fa3 | |||
| 6928f6eeb6 | |||
| 4cdc2a8c28 | |||
| e0c674d9a9 | |||
| d7830f4bfc | |||
| 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,45 +35,44 @@ 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
|
||||||
# Learn more about CodeQL language support at https://aka.ms/codeql-docs/language-support
|
# Learn more about CodeQL language support at https://aka.ms/codeql-docs/language-support
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout repository
|
- name: Checkout repository
|
||||||
uses: actions/checkout@v3
|
uses: actions/checkout@v3
|
||||||
|
|
||||||
# Initializes the CodeQL tools for scanning.
|
# Initializes the CodeQL tools for scanning.
|
||||||
- name: Initialize CodeQL
|
- name: Initialize CodeQL
|
||||||
uses: github/codeql-action/init@v2
|
uses: github/codeql-action/init@v2
|
||||||
with:
|
with:
|
||||||
languages: ${{ matrix.language }}
|
languages: ${{ matrix.language }}
|
||||||
# If you wish to specify custom queries, you can do so here or in a config file.
|
# If you wish to specify custom queries, you can do so here or in a config file.
|
||||||
# By default, queries listed here will override any specified in a config file.
|
# By default, queries listed here will override any specified in a config file.
|
||||||
# Prefix the list here with "+" to use these queries and those in the config file.
|
# Prefix the list here with "+" to use these queries and those in the config file.
|
||||||
|
|
||||||
# For more details on CodeQL's query packs, refer to: https://docs.github.com/en/code-security/code-scanning/automatically-scanning-your-code-for-vulnerabilities-and-errors/configuring-code-scanning#using-queries-in-ql-packs
|
# 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).
|
||||||
|
# If this step fails, then you should remove it and run the build manually (see below)
|
||||||
|
- name: Autobuild
|
||||||
|
uses: github/codeql-action/autobuild@v2
|
||||||
|
|
||||||
# Autobuild attempts to build any compiled languages (C/C++, C#, Go, Java, or Swift).
|
# âšī¸ Command-line programs to run using the OS shell.
|
||||||
# If this step fails, then you should remove it and run the build manually (see below)
|
# đ See https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idstepsrun
|
||||||
- name: Autobuild
|
|
||||||
uses: github/codeql-action/autobuild@v2
|
|
||||||
|
|
||||||
# âšī¸ Command-line programs to run using the OS shell.
|
# If the Autobuild fails above, remove it and uncomment the following three lines.
|
||||||
# đ See https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idstepsrun
|
# modify them (or add more) to build your code if your project, please refer to the EXAMPLE below for guidance.
|
||||||
|
|
||||||
# If the Autobuild fails above, remove it and uncomment the following three lines.
|
# - run: |
|
||||||
# modify them (or add more) to build your code if your project, please refer to the EXAMPLE below for guidance.
|
# echo "Run, Build Application using script"
|
||||||
|
# ./location_of_script_within_repo/buildscript.sh
|
||||||
|
|
||||||
# - run: |
|
- name: Perform CodeQL Analysis
|
||||||
# echo "Run, Build Application using script"
|
uses: github/codeql-action/analyze@v2
|
||||||
# ./location_of_script_within_repo/buildscript.sh
|
with:
|
||||||
|
category: '/language:${{matrix.language}}'
|
||||||
- name: Perform CodeQL Analysis
|
|
||||||
uses: github/codeql-action/analyze@v2
|
|
||||||
with:
|
|
||||||
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
|
||||||
@@ -378,19 +374,27 @@ export default {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// https://developer.mozilla.org/en-US/docs/Web/API/Media_Session_API
|
||||||
if ('mediaSession' in navigator) {
|
if ('mediaSession' in navigator) {
|
||||||
var coverImageSrc = this.$store.getters['globals/getLibraryItemCoverSrc'](this.streamLibraryItem, '/Logo.png', true)
|
const chapterInfo = []
|
||||||
const artwork = [
|
if (this.chapters.length) {
|
||||||
{
|
this.chapters.forEach((chapter) => {
|
||||||
src: coverImageSrc
|
chapterInfo.push({
|
||||||
}
|
title: chapter.title,
|
||||||
]
|
startTime: chapter.start
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
navigator.mediaSession.metadata = new MediaMetadata({
|
navigator.mediaSession.metadata = new MediaMetadata({
|
||||||
title: this.title,
|
title: this.title,
|
||||||
artist: this.playerHandler.displayAuthor || this.mediaMetadata.authorName || 'Unknown',
|
artist: this.playerHandler.displayAuthor || this.mediaMetadata.authorName || 'Unknown',
|
||||||
album: this.mediaMetadata.seriesName || '',
|
album: this.mediaMetadata.seriesName || '',
|
||||||
artwork
|
artwork: [
|
||||||
|
{
|
||||||
|
src: this.$store.getters['globals/getLibraryItemCoverSrc'](this.streamLibraryItem, '/Logo.png', true)
|
||||||
|
}
|
||||||
|
]
|
||||||
})
|
})
|
||||||
console.log('Set media session metadata', navigator.mediaSession.metadata)
|
console.log('Set media session metadata', navigator.mediaSession.metadata)
|
||||||
|
|
||||||
@@ -525,7 +529,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 {
|
||||||
|
this.$toast.success(this.$strings.ToastAuthorUpdateSuccessNoImageFound)
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
this.$toast.info(`No updates were made for Author ${response.author.name}`)
|
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>
|
||||||
</div>
|
</button>
|
||||||
</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">
|
<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>
|
||||||
<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">
|
||||||
<span class="flex items-center justify-between">
|
<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="block truncate text-xs" :class="!selectedText ? 'text-gray-300' : ''">{{ selectedText }}</span>
|
<span class="flex items-center justify-between">
|
||||||
</span>
|
<span class="block truncate text-xs" :class="!selectedText ? 'text-gray-300' : ''">{{ selectedText }}</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 })
|
||||||
|
},
|
||||||
|
settingsUpdated() {
|
||||||
|
this.useChapterTrack = this.$store.getters['user/getUserSetting']('useChapterTrack')
|
||||||
|
this.jumpForwardAmount = this.$store.getters['user/getUserSetting']('jumpForwardAmount')
|
||||||
|
this.jumpBackwardAmount = this.$store.getters['user/getUserSetting']('jumpBackwardAmount')
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
mounted() {
|
mounted() {
|
||||||
this.useChapterTrack = this.$store.getters['user/getUserSetting']('useChapterTrack')
|
this.settingsUpdated()
|
||||||
this.jumpForwardAmount = this.$store.getters['user/getUserSetting']('jumpForwardAmount')
|
this.$eventBus.$on('user-settings', this.settingsUpdated)
|
||||||
this.jumpBackwardAmount = this.$store.getters['user/getUserSetting']('jumpBackwardAmount')
|
},
|
||||||
|
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 v-show="canGoPrev" class="absolute -left-24 top-0 bottom-0 h-full pointer-events-none flex items-center px-6">
|
<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">
|
||||||
<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>
|
<component v-if="libraryItem && show" :is="tabName" :library-item="libraryItem" :processing.sync="processing" @close="show = false" @selectTab="selectTab" />
|
||||||
</div>
|
|
||||||
<div v-show="canGoNext" class="absolute -right-24 top-0 bottom-0 h-full pointer-events-none flex items-center px-6">
|
|
||||||
<div class="material-symbols text-5xl text-white text-opacity-50 hover:text-opacity-90 cursor-pointer pointer-events-auto" @click.stop.prevent="goNextBook" @mousedown.prevent>arrow_forward_ios</div>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<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">
|
<div v-show="canGoPrev" class="absolute -left-24 top-0 bottom-0 h-full pointer-events-none flex items-center px-6">
|
||||||
<component v-if="libraryItem && show" :is="tabName" :library-item="libraryItem" :processing.sync="processing" @close="show = false" @selectTab="selectTab" />
|
<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 v-show="canGoNext" class="absolute -right-24 top-0 bottom-0 h-full pointer-events-none flex items-center px-6">
|
||||||
|
<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>
|
||||||
</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,78 +1,94 @@
|
|||||||
<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-tooltip :text="$strings.LabelSettingsSquareBookCoversHelp">
|
<ui-toggle-switch v-model="useSquareBookCovers" size="sm" @input="formUpdated" />
|
||||||
<p class="pl-4 text-base">
|
<ui-tooltip :text="$strings.LabelSettingsSquareBookCoversHelp">
|
||||||
{{ $strings.LabelSettingsSquareBookCovers }}
|
<p class="pl-4 text-sm">
|
||||||
<span class="material-symbols icon-text text-sm">info</span>
|
{{ $strings.LabelSettingsSquareBookCovers }}
|
||||||
</p>
|
|
||||||
</ui-tooltip>
|
|
||||||
</div>
|
|
||||||
<div class="py-3">
|
|
||||||
<div class="flex items-center">
|
|
||||||
<ui-toggle-switch v-if="!globalWatcherDisabled" v-model="enableWatcher" @input="formUpdated" />
|
|
||||||
<ui-toggle-switch v-else disabled :value="false" />
|
|
||||||
<p class="pl-4 text-base">{{ $strings.LabelSettingsEnableWatcherForLibrary }}</p>
|
|
||||||
</div>
|
|
||||||
<p v-if="globalWatcherDisabled" class="text-xs text-warning">*{{ $strings.MessageWatcherIsDisabledGlobally }}</p>
|
|
||||||
</div>
|
|
||||||
<div v-if="isBookLibrary" class="flex items-center py-3">
|
|
||||||
<ui-toggle-switch v-model="audiobooksOnly" @input="formUpdated" />
|
|
||||||
<ui-tooltip :text="$strings.LabelSettingsAudiobooksOnlyHelp">
|
|
||||||
<p class="pl-4 text-base">
|
|
||||||
{{ $strings.LabelSettingsAudiobooksOnly }}
|
|
||||||
<span class="material-symbols icon-text text-sm">info</span>
|
|
||||||
</p>
|
|
||||||
</ui-tooltip>
|
|
||||||
</div>
|
|
||||||
<div v-if="isBookLibrary" class="py-3">
|
|
||||||
<div class="flex items-center">
|
|
||||||
<ui-toggle-switch v-model="skipMatchingMediaWithAsin" @input="formUpdated" />
|
|
||||||
<p class="pl-4 text-base">{{ $strings.LabelSettingsSkipMatchingBooksWithASIN }}</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div v-if="isBookLibrary" class="py-3">
|
|
||||||
<div class="flex items-center">
|
|
||||||
<ui-toggle-switch v-model="skipMatchingMediaWithIsbn" @input="formUpdated" />
|
|
||||||
<p class="pl-4 text-base">{{ $strings.LabelSettingsSkipMatchingBooksWithISBN }}</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div v-if="isBookLibrary" class="py-3">
|
|
||||||
<div class="flex items-center">
|
|
||||||
<ui-toggle-switch v-model="hideSingleBookSeries" @input="formUpdated" />
|
|
||||||
<ui-tooltip :text="$strings.LabelSettingsHideSingleBookSeriesHelp">
|
|
||||||
<p class="pl-4 text-base">
|
|
||||||
{{ $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 class="p-2 w-full md:w-1/2">
|
||||||
<div v-if="isBookLibrary" class="py-3">
|
<div class="flex items-center">
|
||||||
<div class="flex items-center">
|
<ui-toggle-switch v-if="!globalWatcherDisabled" v-model="enableWatcher" size="sm" @input="formUpdated" />
|
||||||
<ui-toggle-switch v-model="onlyShowLaterBooksInContinueSeries" @input="formUpdated" />
|
<ui-toggle-switch v-else disabled size="sm" :value="false" />
|
||||||
<ui-tooltip :text="$strings.LabelSettingsOnlyShowLaterBooksInContinueSeriesHelp">
|
<p class="pl-4 text-sm">{{ $strings.LabelSettingsEnableWatcherForLibrary }}</p>
|
||||||
<p class="pl-4 text-base">
|
</div>
|
||||||
{{ $strings.LabelSettingsOnlyShowLaterBooksInContinueSeries }}
|
<p v-if="globalWatcherDisabled" class="text-xs text-warning">*{{ $strings.MessageWatcherIsDisabledGlobally }}</p>
|
||||||
|
</div>
|
||||||
|
<div v-if="isBookLibrary" class="flex items-center p-2 w-full md:w-1/2">
|
||||||
|
<ui-toggle-switch v-model="audiobooksOnly" size="sm" @input="formUpdated" />
|
||||||
|
<ui-tooltip :text="$strings.LabelSettingsAudiobooksOnlyHelp">
|
||||||
|
<p class="pl-4 text-sm">
|
||||||
|
{{ $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>
|
<div v-if="isBookLibrary" class="p-2 w-full md:w-1/2">
|
||||||
<div v-if="isBookLibrary" class="py-3">
|
<div class="flex items-center">
|
||||||
<div class="flex items-center">
|
<ui-toggle-switch v-model="skipMatchingMediaWithAsin" size="sm" @input="formUpdated" />
|
||||||
<ui-toggle-switch v-model="epubsAllowScriptedContent" @input="formUpdated" />
|
<p class="pl-4 text-sm">{{ $strings.LabelSettingsSkipMatchingBooksWithASIN }}</p>
|
||||||
<ui-tooltip :text="$strings.LabelSettingsEpubsAllowScriptedContentHelp">
|
</div>
|
||||||
<p class="pl-4 text-base">
|
</div>
|
||||||
{{ $strings.LabelSettingsEpubsAllowScriptedContent }}
|
<div v-if="isBookLibrary" class="p-2 w-full md:w-1/2">
|
||||||
<span class="material-symbols icon-text text-sm">info</span>
|
<div class="flex items-center">
|
||||||
</p>
|
<ui-toggle-switch v-model="skipMatchingMediaWithIsbn" size="sm" @input="formUpdated" />
|
||||||
</ui-tooltip>
|
<p class="pl-4 text-sm">{{ $strings.LabelSettingsSkipMatchingBooksWithISBN }}</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div v-if="isBookLibrary" class="p-2 w-full md:w-1/2">
|
||||||
|
<div class="flex items-center">
|
||||||
|
<ui-toggle-switch v-model="hideSingleBookSeries" size="sm" @input="formUpdated" />
|
||||||
|
<ui-tooltip :text="$strings.LabelSettingsHideSingleBookSeriesHelp">
|
||||||
|
<p class="pl-4 text-sm">
|
||||||
|
{{ $strings.LabelSettingsHideSingleBookSeries }}
|
||||||
|
<span class="material-symbols icon-text text-sm">info</span>
|
||||||
|
</p>
|
||||||
|
</ui-tooltip>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div v-if="isBookLibrary" class="p-2 w-full md:w-1/2">
|
||||||
|
<div class="flex items-center">
|
||||||
|
<ui-toggle-switch v-model="onlyShowLaterBooksInContinueSeries" size="sm" @input="formUpdated" />
|
||||||
|
<ui-tooltip :text="$strings.LabelSettingsOnlyShowLaterBooksInContinueSeriesHelp">
|
||||||
|
<p class="pl-4 text-sm">
|
||||||
|
{{ $strings.LabelSettingsOnlyShowLaterBooksInContinueSeries }}
|
||||||
|
<span class="material-symbols icon-text text-sm">info</span>
|
||||||
|
</p>
|
||||||
|
</ui-tooltip>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div v-if="isBookLibrary" class="p-2 w-full md:w-1/2">
|
||||||
|
<div class="flex items-center">
|
||||||
|
<ui-toggle-switch v-model="epubsAllowScriptedContent" size="sm" @input="formUpdated" />
|
||||||
|
<ui-tooltip :text="$strings.LabelSettingsEpubsAllowScriptedContentHelp">
|
||||||
|
<p class="pl-4 text-sm">
|
||||||
|
{{ $strings.LabelSettingsEpubsAllowScriptedContent }}
|
||||||
|
<span class="material-symbols icon-text text-sm">info</span>
|
||||||
|
</p>
|
||||||
|
</ui-tooltip>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<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" />
|
||||||
|
</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 v-if="isPodcastLibrary" class="py-3">
|
|
||||||
<ui-dropdown :label="$strings.LabelPodcastSearchRegion" v-model="podcastSearchRegion" :items="$podcastSearchRegionOptions" small class="max-w-72" menu-max-height="200px" @input="formUpdated" />
|
|
||||||
</div>
|
</div>
|
||||||
</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,7 +1,7 @@
|
|||||||
<template>
|
<template>
|
||||||
<div id="heatmap" class="w-full">
|
<div id="heatmap" class="w-full">
|
||||||
<div class="mx-auto" :style="{ height: innerHeight + 160 + 'px', width: innerWidth + 52 + 'px' }" style="background-color: rgba(13, 17, 23, 0)">
|
<div class="mx-auto" :style="{ height: innerHeight + 160 + 'px', width: innerWidth + 52 + 'px' }" style="background-color: rgba(13, 17, 23, 0)">
|
||||||
<p class="mb-2 px-1 text-sm text-gray-200">{{ $getString('MessageListeningSessionsInTheLastYear', [Object.values(daysListening).length]) }}</p>
|
<p class="mb-2 px-1 text-sm text-gray-200">{{ $getString('MessageDaysListenedInTheLastYear', [daysListenedInTheLastYear]) }}</p>
|
||||||
<div class="border border-white border-opacity-25 rounded py-2 w-full" style="background-color: #232323" :style="{ height: innerHeight + 80 + 'px' }">
|
<div class="border border-white border-opacity-25 rounded py-2 w-full" style="background-color: #232323" :style="{ height: innerHeight + 80 + 'px' }">
|
||||||
<div :style="{ width: innerWidth + 'px', height: innerHeight + 'px' }" class="ml-10 mt-5 absolute" @mouseover="mouseover" @mouseout="mouseout">
|
<div :style="{ width: innerWidth + 'px', height: innerHeight + 'px' }" class="ml-10 mt-5 absolute" @mouseover="mouseover" @mouseout="mouseout">
|
||||||
<div v-for="dayLabel in dayLabels" :key="dayLabel.label" :style="dayLabel.style" class="absolute top-0 left-0 text-gray-300">{{ dayLabel.label }}</div>
|
<div v-for="dayLabel in dayLabels" :key="dayLabel.label" :style="dayLabel.style" class="absolute top-0 left-0 text-gray-300">{{ dayLabel.label }}</div>
|
||||||
@@ -37,6 +37,7 @@ export default {
|
|||||||
innerHeight: 13 * 7,
|
innerHeight: 13 * 7,
|
||||||
blockWidth: 13,
|
blockWidth: 13,
|
||||||
data: [],
|
data: [],
|
||||||
|
daysListenedInTheLastYear: 0,
|
||||||
monthLabels: [],
|
monthLabels: [],
|
||||||
tooltipEl: null,
|
tooltipEl: null,
|
||||||
tooltipTextEl: null,
|
tooltipTextEl: null,
|
||||||
@@ -193,46 +194,47 @@ export default {
|
|||||||
buildData() {
|
buildData() {
|
||||||
this.data = []
|
this.data = []
|
||||||
|
|
||||||
var maxValue = 0
|
let maxValue = 0
|
||||||
var minValue = 0
|
let minValue = 0
|
||||||
Object.values(this.daysListening).forEach((val) => {
|
|
||||||
if (val > maxValue) maxValue = val
|
|
||||||
if (!minValue || val < minValue) minValue = val
|
|
||||||
})
|
|
||||||
const range = maxValue - minValue + 0.01
|
|
||||||
|
|
||||||
|
const dates = []
|
||||||
for (let i = 0; i < this.daysToShow + 1; i++) {
|
for (let i = 0; i < this.daysToShow + 1; i++) {
|
||||||
const col = Math.floor(i / 7)
|
|
||||||
const row = i % 7
|
|
||||||
|
|
||||||
const date = i === 0 ? this.firstWeekStart : this.$addDaysToDate(this.firstWeekStart, i)
|
const date = i === 0 ? this.firstWeekStart : this.$addDaysToDate(this.firstWeekStart, i)
|
||||||
const dateString = this.$formatJsDate(date, 'yyyy-MM-dd')
|
const dateString = this.$formatJsDate(date, 'yyyy-MM-dd')
|
||||||
const datePretty = this.$formatJsDate(date, 'MMM d, yyyy')
|
const dateObj = {
|
||||||
const monthString = this.$formatJsDate(date, 'MMM')
|
col: Math.floor(i / 7),
|
||||||
const value = this.daysListening[dateString] || 0
|
row: i % 7,
|
||||||
const x = col * 13
|
date,
|
||||||
const y = row * 13
|
dateString,
|
||||||
|
datePretty: this.$formatJsDate(date, 'MMM d, yyyy'),
|
||||||
|
monthString: this.$formatJsDate(date, 'MMM'),
|
||||||
|
dayOfMonth: Number(dateString.split('-').pop()),
|
||||||
|
yearString: dateString.split('-').shift(),
|
||||||
|
value: this.daysListening[dateString] || 0
|
||||||
|
}
|
||||||
|
dates.push(dateObj)
|
||||||
|
|
||||||
var bgColor = this.bgColors[0]
|
if (dateObj.value > 0) {
|
||||||
var outlineColor = this.outlineColors[0]
|
this.daysListenedInTheLastYear++
|
||||||
if (value) {
|
if (dateObj.value > maxValue) maxValue = dateObj.value
|
||||||
|
if (!minValue || dateObj.value < minValue) minValue = dateObj.value
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const range = maxValue - minValue + 0.01
|
||||||
|
|
||||||
|
for (const dateObj of dates) {
|
||||||
|
let bgColor = this.bgColors[0]
|
||||||
|
let outlineColor = this.outlineColors[0]
|
||||||
|
if (dateObj.value) {
|
||||||
outlineColor = this.outlineColors[1]
|
outlineColor = this.outlineColors[1]
|
||||||
var percentOfAvg = (value - minValue) / range
|
const percentOfAvg = (dateObj.value - minValue) / range
|
||||||
var bgIndex = Math.floor(percentOfAvg * 4) + 1
|
const bgIndex = Math.floor(percentOfAvg * 4) + 1
|
||||||
bgColor = this.bgColors[bgIndex] || 'red'
|
bgColor = this.bgColors[bgIndex] || 'red'
|
||||||
}
|
}
|
||||||
|
|
||||||
this.data.push({
|
this.data.push({
|
||||||
date,
|
...dateObj,
|
||||||
dateString,
|
style: `transform:translate(${dateObj.col * 13}px,${dateObj.row * 13}px);background-color:${bgColor};outline:1px solid ${outlineColor};outline-offset:-1px;`
|
||||||
datePretty,
|
|
||||||
monthString,
|
|
||||||
dayOfMonth: Number(dateString.split('-').pop()),
|
|
||||||
yearString: dateString.split('-').shift(),
|
|
||||||
value,
|
|
||||||
col,
|
|
||||||
row,
|
|
||||||
style: `transform:translate(${x}px,${y}px);background-color:${bgColor};outline:1px solid ${outlineColor};outline-offset:-1px;`
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -260,6 +262,7 @@ export default {
|
|||||||
const heatmapEl = document.getElementById('heatmap')
|
const heatmapEl = document.getElementById('heatmap')
|
||||||
this.contentWidth = heatmapEl.clientWidth
|
this.contentWidth = heatmapEl.clientWidth
|
||||||
this.maxInnerWidth = this.contentWidth - 52
|
this.maxInnerWidth = this.contentWidth - 52
|
||||||
|
this.daysListenedInTheLastYear = 0
|
||||||
this.buildData()
|
this.buildData()
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -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() {
|
||||||
this.$refs.yearInReviewServer.refresh()
|
if (this.$refs.yearInReviewServer != null) {
|
||||||
|
this.$refs.yearInReviewServer.refresh()
|
||||||
|
}
|
||||||
},
|
},
|
||||||
refreshYearInReview() {
|
refreshYearInReview() {
|
||||||
this.$refs.yearInReview.refresh()
|
if (this.$refs.yearInReview != null && this.$refs.yearInReviewShort != null) {
|
||||||
this.$refs.yearInReviewShort.refresh()
|
this.$refs.yearInReview.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.resizeObserver.disconnect()
|
|
||||||
this.$refs.bookshelf.removeChild(instance.$el)
|
|
||||||
}
|
}
|
||||||
|
this.coverHeight = instance.coverHeight
|
||||||
|
this.resizeObserver.disconnect()
|
||||||
|
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
|
||||||
if (!ab.itemFiles.length) {
|
.filter((ab) => {
|
||||||
if (ab.otherFiles.length) ignoredFiles = ignoredFiles.concat(ab.otherFiles)
|
if (!ab.itemFiles.length) {
|
||||||
if (ab.ignoredFiles.length) ignoredFiles = ignoredFiles.concat(ab.ignoredFiles)
|
if (ab.otherFiles.length) ignoredFiles = ignoredFiles.concat(ab.otherFiles)
|
||||||
}
|
if (ab.ignoredFiles.length) ignoredFiles = ignoredFiles.concat(ab.ignoredFiles)
|
||||||
return ab.itemFiles.length
|
}
|
||||||
}).map(ab => this.cleanItem(ab, mediaType, index++))
|
return ab.itemFiles.length
|
||||||
|
})
|
||||||
|
.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
|
||||||
}
|
}
|
||||||
},
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
+34
-49
@@ -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',
|
{
|
||||||
url: 'http://localhost:3333'
|
name: 'dev',
|
||||||
},
|
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: {}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -149,12 +134,12 @@ module.exports = {
|
|||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Temporary workaround for @nuxt-community/tailwindcss-module.
|
* Temporary workaround for @nuxt-community/tailwindcss-module.
|
||||||
*
|
*
|
||||||
* Reported: 2022-05-23
|
* Reported: 2022-05-23
|
||||||
* See: [Issue tracker](https://github.com/nuxt-community/tailwindcss-module/issues/480)
|
* See: [Issue tracker](https://github.com/nuxt-community/tailwindcss-module/issues/480)
|
||||||
*/
|
*/
|
||||||
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)
|
this.updatingServerSettings = false
|
||||||
.then(() => {
|
|
||||||
this.updatingServerSettings = false
|
|
||||||
|
|
||||||
if (payload.language) {
|
if (response.error) {
|
||||||
// Updating language after save allows for re-rendering
|
console.error('Failed to update server settins', response.error)
|
||||||
this.$setLanguageCode(payload.language)
|
this.$toast.error(response.error)
|
||||||
}
|
this.initServerSettings()
|
||||||
})
|
return
|
||||||
.catch((error) => {
|
}
|
||||||
console.error('Failed to update server settings', error)
|
|
||||||
this.updatingServerSettings = false
|
if (payload.language) {
|
||||||
this.$toast.error(this.$strings.ToastFailedToUpdate)
|
// Updating language after save allows for re-rendering
|
||||||
})
|
this.$setLanguageCode(payload.language)
|
||||||
|
}
|
||||||
|
})
|
||||||
},
|
},
|
||||||
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>
|
||||||
|
|||||||
+117
-12
@@ -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
|
||||||
@@ -105,6 +110,84 @@ export default {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
|
mediaSessionPlay() {
|
||||||
|
console.log('Media session play')
|
||||||
|
this.play()
|
||||||
|
},
|
||||||
|
mediaSessionPause() {
|
||||||
|
console.log('Media session pause')
|
||||||
|
this.pause()
|
||||||
|
},
|
||||||
|
mediaSessionStop() {
|
||||||
|
console.log('Media session stop')
|
||||||
|
this.pause()
|
||||||
|
},
|
||||||
|
mediaSessionSeekBackward() {
|
||||||
|
console.log('Media session seek backward')
|
||||||
|
this.jumpBackward()
|
||||||
|
},
|
||||||
|
mediaSessionSeekForward() {
|
||||||
|
console.log('Media session seek forward')
|
||||||
|
this.jumpForward()
|
||||||
|
},
|
||||||
|
mediaSessionSeekTo(e) {
|
||||||
|
console.log('Media session seek to', e)
|
||||||
|
if (e.seekTime !== null && !isNaN(e.seekTime)) {
|
||||||
|
this.seek(e.seekTime)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
mediaSessionPreviousTrack() {
|
||||||
|
if (this.$refs.audioPlayer) {
|
||||||
|
this.$refs.audioPlayer.prevChapter()
|
||||||
|
}
|
||||||
|
},
|
||||||
|
mediaSessionNextTrack() {
|
||||||
|
if (this.$refs.audioPlayer) {
|
||||||
|
this.$refs.audioPlayer.nextChapter()
|
||||||
|
}
|
||||||
|
},
|
||||||
|
updateMediaSessionPlaybackState() {
|
||||||
|
if ('mediaSession' in navigator) {
|
||||||
|
navigator.mediaSession.playbackState = this.isPlaying ? 'playing' : 'paused'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
setMediaSession() {
|
||||||
|
// https://developer.mozilla.org/en-US/docs/Web/API/Media_Session_API
|
||||||
|
if ('mediaSession' in navigator) {
|
||||||
|
const chapterInfo = []
|
||||||
|
if (this.chapters.length > 0) {
|
||||||
|
this.chapters.forEach((chapter) => {
|
||||||
|
chapterInfo.push({
|
||||||
|
title: chapter.title,
|
||||||
|
startTime: chapter.start
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
navigator.mediaSession.metadata = new MediaMetadata({
|
||||||
|
title: this.mediaItemShare.playbackSession.displayTitle || 'No title',
|
||||||
|
artist: this.mediaItemShare.playbackSession.displayAuthor || 'Unknown',
|
||||||
|
artwork: [
|
||||||
|
{
|
||||||
|
src: this.coverUrl
|
||||||
|
}
|
||||||
|
],
|
||||||
|
chapterInfo
|
||||||
|
})
|
||||||
|
console.log('Set media session metadata', navigator.mediaSession.metadata)
|
||||||
|
|
||||||
|
navigator.mediaSession.setActionHandler('play', this.mediaSessionPlay)
|
||||||
|
navigator.mediaSession.setActionHandler('pause', this.mediaSessionPause)
|
||||||
|
navigator.mediaSession.setActionHandler('stop', this.mediaSessionStop)
|
||||||
|
navigator.mediaSession.setActionHandler('seekbackward', this.mediaSessionSeekBackward)
|
||||||
|
navigator.mediaSession.setActionHandler('seekforward', this.mediaSessionSeekForward)
|
||||||
|
navigator.mediaSession.setActionHandler('seekto', this.mediaSessionSeekTo)
|
||||||
|
navigator.mediaSession.setActionHandler('previoustrack', this.mediaSessionSeekBackward)
|
||||||
|
navigator.mediaSession.setActionHandler('nexttrack', this.mediaSessionSeekForward)
|
||||||
|
} else {
|
||||||
|
console.warn('Media session not available')
|
||||||
|
}
|
||||||
|
},
|
||||||
async coverImageLoaded(e) {
|
async coverImageLoaded(e) {
|
||||||
if (!this.playbackSession.coverPath) return
|
if (!this.playbackSession.coverPath) return
|
||||||
const fac = new FastAverageColor()
|
const fac = new FastAverageColor()
|
||||||
@@ -121,19 +204,32 @@ export default {
|
|||||||
})
|
})
|
||||||
},
|
},
|
||||||
playPause() {
|
playPause() {
|
||||||
|
if (this.isPlaying) {
|
||||||
|
this.pause()
|
||||||
|
} else {
|
||||||
|
this.play()
|
||||||
|
}
|
||||||
|
},
|
||||||
|
play() {
|
||||||
if (!this.localAudioPlayer || !this.hasLoaded) return
|
if (!this.localAudioPlayer || !this.hasLoaded) return
|
||||||
this.localAudioPlayer.playPause()
|
this.localAudioPlayer.play()
|
||||||
|
},
|
||||||
|
pause() {
|
||||||
|
if (!this.localAudioPlayer || !this.hasLoaded) return
|
||||||
|
this.localAudioPlayer.pause()
|
||||||
},
|
},
|
||||||
jumpForward() {
|
jumpForward() {
|
||||||
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 +250,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
|
||||||
@@ -205,6 +302,7 @@ export default {
|
|||||||
} else {
|
} else {
|
||||||
this.stopPlayInterval()
|
this.stopPlayInterval()
|
||||||
}
|
}
|
||||||
|
this.updateMediaSessionPlaybackState()
|
||||||
},
|
},
|
||||||
playerTimeUpdate(time) {
|
playerTimeUpdate(time) {
|
||||||
this.setCurrentTime(time)
|
this.setCurrentTime(time)
|
||||||
@@ -246,9 +344,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)
|
||||||
@@ -263,6 +366,8 @@ export default {
|
|||||||
this.localAudioPlayer.on('timeupdate', this.playerTimeUpdate.bind(this))
|
this.localAudioPlayer.on('timeupdate', this.playerTimeUpdate.bind(this))
|
||||||
this.localAudioPlayer.on('error', this.playerError.bind(this))
|
this.localAudioPlayer.on('error', this.playerError.bind(this))
|
||||||
this.localAudioPlayer.on('finished', this.playerFinished.bind(this))
|
this.localAudioPlayer.on('finished', this.playerFinished.bind(this))
|
||||||
|
|
||||||
|
this.setMediaSession()
|
||||||
},
|
},
|
||||||
beforeDestroy() {
|
beforeDestroy() {
|
||||||
window.removeEventListener('resize', this.resize)
|
window.removeEventListener('resize', this.resize)
|
||||||
|
|||||||
@@ -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": "ØĨØ¯Ø§ØąØŠ Ø§ŲØšŲاŲ
اØĒ"
|
||||||
|
}
|
||||||
@@ -629,7 +629,6 @@
|
|||||||
"MessageItemsSelected": "{0} иСйŅаĐŊи",
|
"MessageItemsSelected": "{0} иСйŅаĐŊи",
|
||||||
"MessageItemsUpdated": "{0} ĐĩĐģĐĩĐŧĐĩĐŊŅа ОйĐŊОвĐĩĐŊи",
|
"MessageItemsUpdated": "{0} ĐĩĐģĐĩĐŧĐĩĐŊŅа ОйĐŊОвĐĩĐŊи",
|
||||||
"MessageJoinUsOn": "ĐŅиŅŅĐĩдиĐŊĐĩŅĐĩ ŅĐĩ ĐēŅĐŧ ĐŊаŅ",
|
"MessageJoinUsOn": "ĐŅиŅŅĐĩдиĐŊĐĩŅĐĩ ŅĐĩ ĐēŅĐŧ ĐŊаŅ",
|
||||||
"MessageListeningSessionsInTheLastYear": "{0} ŅĐģŅŅаŅĐĩĐģŅĐēи ŅĐĩŅии ĐŋŅĐĩС ĐŋĐžŅĐģĐĩĐ´ĐŊаŅа ĐŗĐžĐ´Đ¸ĐŊа",
|
|
||||||
"MessageLoading": "ĐаŅĐĩĐļдаĐŊĐĩ...",
|
"MessageLoading": "ĐаŅĐĩĐļдаĐŊĐĩ...",
|
||||||
"MessageLoadingFolders": "ĐаŅĐĩĐļдаĐŊĐĩ ĐŊа ĐаĐŋĐēи...",
|
"MessageLoadingFolders": "ĐаŅĐĩĐļдаĐŊĐĩ ĐŊа ĐаĐŋĐēи...",
|
||||||
"MessageM4BFailed": "M4B ĐŅОваĐģĐĩĐŊĐž!",
|
"MessageM4BFailed": "M4B ĐŅОваĐģĐĩĐŊĐž!",
|
||||||
@@ -729,7 +728,6 @@
|
|||||||
"ToastBookmarkUpdateSuccess": "ĐŅĐŧĐĩŅĐēаŅа Đĩ ОйĐŊОвĐĩĐŊа",
|
"ToastBookmarkUpdateSuccess": "ĐŅĐŧĐĩŅĐēаŅа Đĩ ОйĐŊОвĐĩĐŊа",
|
||||||
"ToastChaptersHaveErrors": "ĐĐģавиŅĐĩ иĐŧĐ°Ņ ĐŗŅĐĩŅĐēи",
|
"ToastChaptersHaveErrors": "ĐĐģавиŅĐĩ иĐŧĐ°Ņ ĐŗŅĐĩŅĐēи",
|
||||||
"ToastChaptersMustHaveTitles": "ĐĐģавиŅĐĩ ŅŅŅйва да иĐŧĐ°Ņ ĐˇĐ°ĐŗĐģавиŅ",
|
"ToastChaptersMustHaveTitles": "ĐĐģавиŅĐĩ ŅŅŅйва да иĐŧĐ°Ņ ĐˇĐ°ĐŗĐģавиŅ",
|
||||||
"ToastCollectionItemsRemoveSuccess": "ĐĐģĐĩĐŧĐĩĐŊŅ(и) ĐŋŅĐĩĐŧаŅ
ĐŊаŅи ĐžŅ ĐēĐžĐģĐĩĐēŅиŅ",
|
|
||||||
"ToastCollectionRemoveSuccess": "ĐĐžĐģĐĩĐēŅиŅŅа Đĩ ĐŋŅĐĩĐŧаŅ
ĐŊаŅа",
|
"ToastCollectionRemoveSuccess": "ĐĐžĐģĐĩĐēŅиŅŅа Đĩ ĐŋŅĐĩĐŧаŅ
ĐŊаŅа",
|
||||||
"ToastCollectionUpdateSuccess": "ĐĐžĐģĐĩĐēŅиŅŅа Đĩ ОйĐŊОвĐĩĐŊа",
|
"ToastCollectionUpdateSuccess": "ĐĐžĐģĐĩĐēŅиŅŅа Đĩ ОйĐŊОвĐĩĐŊа",
|
||||||
"ToastItemCoverUpdateSuccess": "ĐĐžŅиŅаŅа ĐŊа ĐĩĐģĐĩĐŧĐĩĐŊŅа Đĩ ОйĐŊОвĐĩĐŊа",
|
"ToastItemCoverUpdateSuccess": "ĐĐžŅиŅаŅа ĐŊа ĐĩĐģĐĩĐŧĐĩĐŊŅа Đĩ ОйĐŊОвĐĩĐŊа",
|
||||||
|
|||||||
+82
-3
@@ -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}",
|
||||||
@@ -710,7 +763,6 @@
|
|||||||
"MessageItemsSelected": "{0}āĻāĻŋ āĻāĻāĻā§āĻŽ āύāĻŋāϰā§āĻŦāĻžāĻāĻŋāϤ",
|
"MessageItemsSelected": "{0}āĻāĻŋ āĻāĻāĻā§āĻŽ āύāĻŋāϰā§āĻŦāĻžāĻāĻŋāϤ",
|
||||||
"MessageItemsUpdated": "{0}āĻāĻŋ āĻāĻāĻā§āĻŽ āĻāĻĒāĻĄā§āĻ āĻāϰāĻž āĻšāϝāĻŧā§āĻā§",
|
"MessageItemsUpdated": "{0}āĻāĻŋ āĻāĻāĻā§āĻŽ āĻāĻĒāĻĄā§āĻ āĻāϰāĻž āĻšāϝāĻŧā§āĻā§",
|
||||||
"MessageJoinUsOn": "āĻāĻŽāĻžāĻĻā§āϰ āϏāĻžāĻĨā§ āϝā§āĻ āĻĻāĻŋāύ",
|
"MessageJoinUsOn": "āĻāĻŽāĻžāĻĻā§āϰ āϏāĻžāĻĨā§ āϝā§āĻ āĻĻāĻŋāύ",
|
||||||
"MessageListeningSessionsInTheLastYear": "āĻāϤ āĻŦāĻāϰ⧠{0}āĻāĻŋ āĻļā§āύāĻžāϰ āϏā§āĻļāύ",
|
|
||||||
"MessageLoading": "āϞā§āĻĄ āĻšāĻā§āĻā§.āĨ¤",
|
"MessageLoading": "āϞā§āĻĄ āĻšāĻā§āĻā§.āĨ¤",
|
||||||
"MessageLoadingFolders": "āĻĢā§āϞā§āĻĄāĻžāϰ āϞā§āĻĄ āĻšāĻā§āĻā§...",
|
"MessageLoadingFolders": "āĻĢā§āϞā§āĻĄāĻžāϰ āϞā§āĻĄ āĻšāĻā§āĻā§...",
|
||||||
"MessageLogsDescription": "āϞāĻāĻā§āϞāĻŋ JSON āĻĢāĻžāĻāϞ āĻšāĻŋāϏāĻžāĻŦā§ <code>/metadata/logs</code>-āĻ āϏāĻāϰāĻā§āώāĻŖ āĻāϰāĻž āĻšāϝāĻŧāĨ¤ āĻā§āϰā§āϝāĻžāĻļ āϞāĻāĻā§āϞāĻŋ <code>/metadata/logs/crash_logs.txt</code>-āĻ āϏāĻāϰāĻā§āώāĻŖ āĻāϰāĻž āĻšāϝāĻŧāĨ¤",
|
"MessageLogsDescription": "āϞāĻāĻā§āϞāĻŋ JSON āĻĢāĻžāĻāϞ āĻšāĻŋāϏāĻžāĻŦā§ <code>/metadata/logs</code>-āĻ āϏāĻāϰāĻā§āώāĻŖ āĻāϰāĻž āĻšāϝāĻŧāĨ¤ āĻā§āϰā§āϝāĻžāĻļ āϞāĻāĻā§āϞāĻŋ <code>/metadata/logs/crash_logs.txt</code>-āĻ āϏāĻāϰāĻā§āώāĻŖ āĻāϰāĻž āĻšāϝāĻŧāĨ¤",
|
||||||
@@ -744,6 +796,7 @@
|
|||||||
"MessageNoLogs": "āĻā§āύāĻ āϞāĻ āύā§āĻ",
|
"MessageNoLogs": "āĻā§āύāĻ āϞāĻ āύā§āĻ",
|
||||||
"MessageNoMediaProgress": "āĻŽāĻŋāĻĄāĻŋāϝāĻŧāĻž āĻ
āĻā§āϰāĻāϤāĻŋ āύā§āĻ",
|
"MessageNoMediaProgress": "āĻŽāĻŋāĻĄāĻŋāϝāĻŧāĻž āĻ
āĻā§āϰāĻāϤāĻŋ āύā§āĻ",
|
||||||
"MessageNoNotifications": "āĻā§āύ⧠āĻŦāĻŋāĻā§āĻāĻĒā§āϤāĻŋ āύā§āĻ",
|
"MessageNoNotifications": "āĻā§āύ⧠āĻŦāĻŋāĻā§āĻāĻĒā§āϤāĻŋ āύā§āĻ",
|
||||||
|
"MessageNoPodcastFeed": "āĻ
āĻŦā§āϧ āĻĒāĻĄāĻāĻžāϏā§āĻ: āĻā§āύ⧠āĻĢāĻŋāĻĄ āύā§āĻ",
|
||||||
"MessageNoPodcastsFound": "āĻā§āύ āĻĒāĻĄāĻāĻžāϏā§āĻ āĻĒāĻžāĻāϝāĻŧāĻž āϝāĻžāϝāĻŧāύāĻŋ",
|
"MessageNoPodcastsFound": "āĻā§āύ āĻĒāĻĄāĻāĻžāϏā§āĻ āĻĒāĻžāĻāϝāĻŧāĻž āϝāĻžāϝāĻŧāύāĻŋ",
|
||||||
"MessageNoResults": "āĻā§āύ āĻĢāϞāĻžāĻĢāϞ āύā§āĻ",
|
"MessageNoResults": "āĻā§āύ āĻĢāϞāĻžāĻĢāϞ āύā§āĻ",
|
||||||
"MessageNoSearchResultsFor": "\"{0}\" āĻāϰ āĻāύā§āϝ āĻā§āύ āĻ
āύā§āϏāύā§āϧāĻžāύ āĻĢāϞāĻžāĻĢāϞ āύā§āĻ",
|
"MessageNoSearchResultsFor": "\"{0}\" āĻāϰ āĻāύā§āϝ āĻā§āύ āĻ
āύā§āϏāύā§āϧāĻžāύ āĻĢāϞāĻžāĻĢāϞ āύā§āĻ",
|
||||||
@@ -760,6 +813,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 +859,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 +886,10 @@
|
|||||||
"NoteUploaderFoldersWithMediaFiles": "āĻŽāĻŋāĻĄāĻŋāϝāĻŧāĻž āĻĢāĻžāĻāϞ āϏāĻš āĻĢā§āϞā§āĻĄāĻžāϰāĻā§āϞāĻŋ āĻāϞāĻžāĻĻāĻž āϞāĻžāĻāĻŦā§āϰā§āϰāĻŋ āĻāĻāĻā§āĻŽ āĻšāĻŋāϏāĻžāĻŦā§ āĻĒāϰāĻŋāĻāĻžāϞāύāĻž āĻāϰāĻž āĻšāĻŦā§āĨ¤",
|
"NoteUploaderFoldersWithMediaFiles": "āĻŽāĻŋāĻĄāĻŋāϝāĻŧāĻž āĻĢāĻžāĻāϞ āϏāĻš āĻĢā§āϞā§āĻĄāĻžāϰāĻā§āϞāĻŋ āĻāϞāĻžāĻĻāĻž āϞāĻžāĻāĻŦā§āϰā§āϰāĻŋ āĻāĻāĻā§āĻŽ āĻšāĻŋāϏāĻžāĻŦā§ āĻĒāϰāĻŋāĻāĻžāϞāύāĻž āĻāϰāĻž āĻšāĻŦā§āĨ¤",
|
||||||
"NoteUploaderOnlyAudioFiles": "āϝāĻĻāĻŋ āĻļā§āϧā§āĻŽāĻžāϤā§āϰ āĻ
āĻĄāĻŋāĻ āĻĢāĻžāĻāϞ āĻāĻĒāϞā§āĻĄ āĻāϰāĻž āĻšāϝāĻŧ āϤāĻŦā§ āĻĒā§āϰāϤāĻŋāĻāĻŋ āĻ
āĻĄāĻŋāĻ āĻĢāĻžāĻāϞ āĻāĻāĻāĻŋ āĻĒā§āĻĨāĻ āĻ
āĻĄāĻŋāĻāĻŦā§āĻ āĻšāĻŋāϏāĻžāĻŦā§ āĻĒāϰāĻŋāĻāĻžāϞāύāĻž āĻāϰāĻž āĻšāĻŦā§āĨ¤",
|
"NoteUploaderOnlyAudioFiles": "āϝāĻĻāĻŋ āĻļā§āϧā§āĻŽāĻžāϤā§āϰ āĻ
āĻĄāĻŋāĻ āĻĢāĻžāĻāϞ āĻāĻĒāϞā§āĻĄ āĻāϰāĻž āĻšāϝāĻŧ āϤāĻŦā§ āĻĒā§āϰāϤāĻŋāĻāĻŋ āĻ
āĻĄāĻŋāĻ āĻĢāĻžāĻāϞ āĻāĻāĻāĻŋ āĻĒā§āĻĨāĻ āĻ
āĻĄāĻŋāĻāĻŦā§āĻ āĻšāĻŋāϏāĻžāĻŦā§ āĻĒāϰāĻŋāĻāĻžāϞāύāĻž āĻāϰāĻž āĻšāĻŦā§āĨ¤",
|
||||||
"NoteUploaderUnsupportedFiles": "āĻ
āϏāĻŽāϰā§āĻĨāĻŋāϤ āĻĢāĻžāĻāϞāĻā§āϞāĻŋ āĻāĻĒā§āĻā§āώāĻž āĻāϰāĻž āĻšāϝāĻŧāĨ¤ āĻāĻāĻāĻŋ āĻĢā§āϞā§āĻĄāĻžāϰ āĻŦā§āĻā§ āύā§āĻāϝāĻŧāĻž āĻŦāĻž āĻĢā§āϞ⧠āĻĻā§āĻāϝāĻŧāĻžāϰ āϏāĻŽāϝāĻŧ, āĻāĻāĻā§āĻŽ āĻĢā§āϞā§āĻĄāĻžāϰ⧠āύā§āĻ āĻāĻŽāύ āĻ
āύā§āϝāĻžāύā§āϝ āĻĢāĻžāĻāϞāĻā§āϞāĻŋ āĻāĻĒā§āĻā§āώāĻž āĻāϰāĻž āĻšāϝāĻŧāĨ¤",
|
"NoteUploaderUnsupportedFiles": "āĻ
āϏāĻŽāϰā§āĻĨāĻŋāϤ āĻĢāĻžāĻāϞāĻā§āϞāĻŋ āĻāĻĒā§āĻā§āώāĻž āĻāϰāĻž āĻšāϝāĻŧāĨ¤ āĻāĻāĻāĻŋ āĻĢā§āϞā§āĻĄāĻžāϰ āĻŦā§āĻā§ āύā§āĻāϝāĻŧāĻž āĻŦāĻž āĻĢā§āϞ⧠āĻĻā§āĻāϝāĻŧāĻžāϰ āϏāĻŽāϝāĻŧ, āĻāĻāĻā§āĻŽ āĻĢā§āϞā§āĻĄāĻžāϰ⧠āύā§āĻ āĻāĻŽāύ āĻ
āύā§āϝāĻžāύā§āϝ āĻĢāĻžāĻāϞāĻā§āϞāĻŋ āĻāĻĒā§āĻā§āώāĻž āĻāϰāĻž āĻšāϝāĻŧāĨ¤",
|
||||||
|
"NotificationOnBackupCompletedDescription": "āĻŦā§āϝāĻžāĻāĻāĻĒ āϏāĻŽā§āĻĒā§āϰā§āĻŖ āĻšāϞ⧠āĻā§āϰāĻŋāĻāĻžāϰ āĻšāĻŦā§",
|
||||||
|
"NotificationOnBackupFailedDescription": "āĻŦā§āϝāĻžāĻāĻāĻĒ āĻŦā§āϝāϰā§āĻĨ āĻšāϞ⧠āĻā§āϰāĻŋāĻāĻžāϰ āĻšāĻŦā§",
|
||||||
|
"NotificationOnEpisodeDownloadedDescription": "āĻāĻāĻāĻŋ āĻĒāĻĄāĻāĻžāϏā§āĻ āĻĒāϰā§āĻŦ āϏā§āĻŦāϝāĻŧāĻāĻā§āϰāĻŋāϝāĻŧāĻāĻžāĻŦā§ āĻĄāĻžāĻāύāϞā§āĻĄ āĻšāϞ⧠āĻā§āϰāĻŋāĻāĻžāϰ āĻšāĻŦā§",
|
||||||
|
"NotificationOnTestDescription": "āĻŦāĻŋāĻā§āĻāĻĒā§āϤāĻŋ āϏāĻŋāϏā§āĻā§āĻŽ āĻĒāϰā§āĻā§āώāĻžāϰ āĻāύā§āϝ āĻāĻā§āύā§āĻ",
|
||||||
"PlaceholderNewCollection": "āύāϤā§āύ āϏāĻāĻā§āϰāĻšā§āϰ āύāĻžāĻŽ",
|
"PlaceholderNewCollection": "āύāϤā§āύ āϏāĻāĻā§āϰāĻšā§āϰ āύāĻžāĻŽ",
|
||||||
"PlaceholderNewFolderPath": "āύāϤā§āύ āĻĢā§āϞā§āĻĄāĻžāϰ āĻĒāĻĨ",
|
"PlaceholderNewFolderPath": "āύāϤā§āύ āĻĢā§āϞā§āĻĄāĻžāϰ āĻĒāĻĨ",
|
||||||
"PlaceholderNewPlaylist": "āύāϤā§āύ āĻĒā§āϞā§āϞāĻŋāϏā§āĻā§āϰ āύāĻžāĻŽ",
|
"PlaceholderNewPlaylist": "āύāϤā§āύ āĻĒā§āϞā§āϞāĻŋāϏā§āĻā§āϰ āύāĻžāĻŽ",
|
||||||
@@ -851,6 +915,7 @@
|
|||||||
"StatsYearInReview": "āĻŦāĻžā§āϏāϰāĻŋāĻ āĻĒāϰā§āϝāĻžāϞā§āĻāύāĻž",
|
"StatsYearInReview": "āĻŦāĻžā§āϏāϰāĻŋāĻ āĻĒāϰā§āϝāĻžāϞā§āĻāύāĻž",
|
||||||
"ToastAccountUpdateSuccess": "āĻ
ā§āϝāĻžāĻāĻžāĻāύā§āĻ āĻāĻĒāĻĄā§āĻ āĻāϰāĻž āĻšāϝāĻŧā§āĻā§",
|
"ToastAccountUpdateSuccess": "āĻ
ā§āϝāĻžāĻāĻžāĻāύā§āĻ āĻāĻĒāĻĄā§āĻ āĻāϰāĻž āĻšāϝāĻŧā§āĻā§",
|
||||||
"ToastAppriseUrlRequired": "āĻāĻāĻāĻŋ Apprise āĻāĻāĻāϰāĻāϞ āϞāĻŋāĻāϤ⧠āĻšāĻŦā§",
|
"ToastAppriseUrlRequired": "āĻāĻāĻāĻŋ Apprise āĻāĻāĻāϰāĻāϞ āϞāĻŋāĻāϤ⧠āĻšāĻŦā§",
|
||||||
|
"ToastAsinRequired": "ASIN āĻĒā§āϰāϝāĻŧā§āĻāύ",
|
||||||
"ToastAuthorImageRemoveSuccess": "āϞā§āĻāĻā§āϰ āĻāĻŦāĻŋ āϏāϰāĻžāύ⧠āĻšāϝāĻŧā§āĻā§",
|
"ToastAuthorImageRemoveSuccess": "āϞā§āĻāĻā§āϰ āĻāĻŦāĻŋ āϏāϰāĻžāύ⧠āĻšāϝāĻŧā§āĻā§",
|
||||||
"ToastAuthorNotFound": "āϞā§āĻāĻ \"{0}\" āĻā§āĻāĻā§ āĻĒāĻžāĻāϝāĻŧāĻž āϝāĻžāϝāĻŧāύāĻŋ",
|
"ToastAuthorNotFound": "āϞā§āĻāĻ \"{0}\" āĻā§āĻāĻā§ āĻĒāĻžāĻāϝāĻŧāĻž āϝāĻžāϝāĻŧāύāĻŋ",
|
||||||
"ToastAuthorRemoveSuccess": "āϞā§āĻāĻ āϏāϰāĻžāύ⧠āĻšāϝāĻŧā§āĻā§",
|
"ToastAuthorRemoveSuccess": "āϞā§āĻāĻ āϏāϰāĻžāύ⧠āĻšāϝāĻŧā§āĻā§",
|
||||||
@@ -870,6 +935,8 @@
|
|||||||
"ToastBackupUploadSuccess": "āĻŦā§āϝāĻžāĻāĻāĻĒ āĻāĻĒāϞā§āĻĄ āĻšāϝāĻŧā§āĻā§",
|
"ToastBackupUploadSuccess": "āĻŦā§āϝāĻžāĻāĻāĻĒ āĻāĻĒāϞā§āĻĄ āĻšāϝāĻŧā§āĻā§",
|
||||||
"ToastBatchDeleteFailed": "āĻŦā§āϝāĻžāĻ āĻŽā§āĻā§ āĻĢā§āϞāϤ⧠āĻŦā§āϝāϰā§āĻĨ āĻšāϝāĻŧā§āĻā§",
|
"ToastBatchDeleteFailed": "āĻŦā§āϝāĻžāĻ āĻŽā§āĻā§ āĻĢā§āϞāϤ⧠āĻŦā§āϝāϰā§āĻĨ āĻšāϝāĻŧā§āĻā§",
|
||||||
"ToastBatchDeleteSuccess": "āĻŦā§āϝāĻžāĻ āĻŽā§āĻā§ āĻĢā§āϞāĻž āϏāĻĢāϞ āĻšā§ā§āĻā§",
|
"ToastBatchDeleteSuccess": "āĻŦā§āϝāĻžāĻ āĻŽā§āĻā§ āĻĢā§āϞāĻž āϏāĻĢāϞ āĻšā§ā§āĻā§",
|
||||||
|
"ToastBatchQuickMatchFailed": "āĻŦā§āϝāĻžāĻ āĻā§āĻāĻ āĻŽā§āϝāĻžāĻ āĻŦā§āϝāϰā§āĻĨ!",
|
||||||
|
"ToastBatchQuickMatchStarted": "{0}āĻāĻŋ āĻŦāĻāϝāĻŧā§āϰ āĻŦā§āϝāĻžāĻ āĻā§āĻāĻ āĻŽā§āϝāĻžāĻ āĻļā§āϰ⧠āĻšāϝāĻŧā§āĻā§!",
|
||||||
"ToastBatchUpdateFailed": "āĻŦā§āϝāĻžāĻ āĻāĻĒāĻĄā§āĻ āĻŦā§āϝāϰā§āĻĨ āĻšāϝāĻŧā§āĻā§",
|
"ToastBatchUpdateFailed": "āĻŦā§āϝāĻžāĻ āĻāĻĒāĻĄā§āĻ āĻŦā§āϝāϰā§āĻĨ āĻšāϝāĻŧā§āĻā§",
|
||||||
"ToastBatchUpdateSuccess": "āĻŦā§āϝāĻžāĻ āĻāĻĒāĻĄā§āĻ āϏāĻžāĻĢāϞā§āϝ",
|
"ToastBatchUpdateSuccess": "āĻŦā§āϝāĻžāĻ āĻāĻĒāĻĄā§āĻ āϏāĻžāĻĢāϞā§āϝ",
|
||||||
"ToastBookmarkCreateFailed": "āĻŦā§āĻāĻŽāĻžāϰā§āĻ āϤā§āϰāĻŋ āĻāϰāϤ⧠āĻŦā§āϝāϰā§āĻĨ",
|
"ToastBookmarkCreateFailed": "āĻŦā§āĻāĻŽāĻžāϰā§āĻ āϤā§āϰāĻŋ āĻāϰāϤ⧠āĻŦā§āϝāϰā§āĻĨ",
|
||||||
@@ -881,9 +948,8 @@
|
|||||||
"ToastChaptersHaveErrors": "āĻ
āϧā§āϝāĻžāϝāĻŧā§ āϤā§āϰā§āĻāĻŋ āĻāĻā§",
|
"ToastChaptersHaveErrors": "āĻ
āϧā§āϝāĻžāϝāĻŧā§ āϤā§āϰā§āĻāĻŋ āĻāĻā§",
|
||||||
"ToastChaptersMustHaveTitles": "āĻ
āϧā§āϝāĻžāϝāĻŧā§āϰ āĻļāĻŋāϰā§āύāĻžāĻŽ āĻĨāĻžāĻāϤ⧠āĻšāĻŦā§",
|
"ToastChaptersMustHaveTitles": "āĻ
āϧā§āϝāĻžāϝāĻŧā§āϰ āĻļāĻŋāϰā§āύāĻžāĻŽ āĻĨāĻžāĻāϤ⧠āĻšāĻŦā§",
|
||||||
"ToastChaptersRemoved": "āĻ
āϧā§āϝāĻžāϝāĻŧāĻā§āϞ⧠āĻŽā§āĻā§ āĻĢā§āϞāĻž āĻšāϝāĻŧā§āĻā§",
|
"ToastChaptersRemoved": "āĻ
āϧā§āϝāĻžāϝāĻŧāĻā§āϞ⧠āĻŽā§āĻā§ āĻĢā§āϞāĻž āĻšāϝāĻŧā§āĻā§",
|
||||||
|
"ToastChaptersUpdated": "āĻ
āϧā§āϝāĻžāϝāĻŧ āĻāĻĒāĻĄā§āĻ āĻāϰāĻž āĻšāϝāĻŧā§āĻā§",
|
||||||
"ToastCollectionItemsAddFailed": "āĻāĻāĻā§āĻŽ(āĻā§āϞāĻŋ) āϏāĻāĻā§āϰāĻšā§ āϝā§āĻ āĻāϰāĻž āĻŦā§āϝāϰā§āĻĨ āĻšāϝāĻŧā§āĻā§",
|
"ToastCollectionItemsAddFailed": "āĻāĻāĻā§āĻŽ(āĻā§āϞāĻŋ) āϏāĻāĻā§āϰāĻšā§ āϝā§āĻ āĻāϰāĻž āĻŦā§āϝāϰā§āĻĨ āĻšāϝāĻŧā§āĻā§",
|
||||||
"ToastCollectionItemsAddSuccess": "āĻāĻāĻā§āĻŽ(āĻā§āϞāĻŋ) āϏāĻāĻā§āϰāĻšā§ āϝā§āĻ āĻāϰāĻž āϏāĻĢāϞ āĻšāϝāĻŧā§āĻā§",
|
|
||||||
"ToastCollectionItemsRemoveSuccess": "āĻāĻāĻā§āĻŽ(āĻā§āϞāĻŋ) āϏāĻāĻā§āϰāĻš āĻĨā§āĻā§ āϏāϰāĻžāύ⧠āĻšāϝāĻŧā§āĻā§",
|
|
||||||
"ToastCollectionRemoveSuccess": "āϏāĻāĻā§āϰāĻš āϏāϰāĻžāύ⧠āĻšāϝāĻŧā§āĻā§",
|
"ToastCollectionRemoveSuccess": "āϏāĻāĻā§āϰāĻš āϏāϰāĻžāύ⧠āĻšāϝāĻŧā§āĻā§",
|
||||||
"ToastCollectionUpdateSuccess": "āϏāĻāĻā§āϰāĻš āĻāĻĒāĻĄā§āĻ āĻāϰāĻž āĻšāϝāĻŧā§āĻā§",
|
"ToastCollectionUpdateSuccess": "āϏāĻāĻā§āϰāĻš āĻāĻĒāĻĄā§āĻ āĻāϰāĻž āĻšāϝāĻŧā§āĻā§",
|
||||||
"ToastCoverUpdateFailed": "āĻāĻāĻžāϰ āĻāĻĒāĻĄā§āĻ āĻŦā§āϝāϰā§āĻĨ āĻšāϝāĻŧā§āĻā§",
|
"ToastCoverUpdateFailed": "āĻāĻāĻžāϰ āĻāĻĒāĻĄā§āĻ āĻŦā§āϝāϰā§āĻĨ āĻšāϝāĻŧā§āĻā§",
|
||||||
@@ -898,11 +964,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 +989,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 +1023,7 @@
|
|||||||
"ToastPodcastGetFeedFailed": "āĻĒāĻĄāĻāĻžāϏā§āĻ āĻĢāĻŋāĻĄ āĻĒā§āϤ⧠āĻŦā§āϝāϰā§āĻĨ āĻšāϝāĻŧā§āĻā§",
|
"ToastPodcastGetFeedFailed": "āĻĒāĻĄāĻāĻžāϏā§āĻ āĻĢāĻŋāĻĄ āĻĒā§āϤ⧠āĻŦā§āϝāϰā§āĻĨ āĻšāϝāĻŧā§āĻā§",
|
||||||
"ToastPodcastNoEpisodesInFeed": "āĻāϰāĻāϏāĻāϏ āĻĢāĻŋāĻĄā§ āĻā§āύ⧠āĻĒāϰā§āĻŦ āĻĒāĻžāĻāϝāĻŧāĻž āϝāĻžāϝāĻŧāύāĻŋ",
|
"ToastPodcastNoEpisodesInFeed": "āĻāϰāĻāϏāĻāϏ āĻĢāĻŋāĻĄā§ āĻā§āύ⧠āĻĒāϰā§āĻŦ āĻĒāĻžāĻāϝāĻŧāĻž āϝāĻžāϝāĻŧāύāĻŋ",
|
||||||
"ToastPodcastNoRssFeed": "āĻĒāĻĄāĻāĻžāϏā§āĻā§āϰ āĻā§āύ āĻāϰāĻāϏāĻāϏ āĻĢāĻŋāĻĄ āύā§āĻ",
|
"ToastPodcastNoRssFeed": "āĻĒāĻĄāĻāĻžāϏā§āĻā§āϰ āĻā§āύ āĻāϰāĻāϏāĻāϏ āĻĢāĻŋāĻĄ āύā§āĻ",
|
||||||
|
"ToastProgressIsNotBeingSynced": "āĻ
āĻā§āϰāĻāϤāĻŋ āϏāĻŋāĻā§āĻ āĻšāĻā§āĻā§ āύāĻž, āĻĒā§āϞā§āĻŦā§āϝāĻžāĻ āĻĒā§āύāϰāĻžāϝāĻŧ āĻāĻžāϞ⧠āĻāϰā§āύ",
|
||||||
"ToastProviderCreatedFailed": "āĻĒā§āϰāĻĻāĻžāύāĻāĻžāϰ⧠āϝā§āĻ āĻāϰāϤ⧠āĻŦā§āϝāϰā§āĻĨ āĻšāϝāĻŧā§āĻā§",
|
"ToastProviderCreatedFailed": "āĻĒā§āϰāĻĻāĻžāύāĻāĻžāϰ⧠āϝā§āĻ āĻāϰāϤ⧠āĻŦā§āϝāϰā§āĻĨ āĻšāϝāĻŧā§āĻā§",
|
||||||
"ToastProviderCreatedSuccess": "āύāϤā§āύ āĻĒā§āϰāĻĻāĻžāύāĻāĻžāϰ⧠āϝā§āĻ āĻāϰāĻž āĻšāϝāĻŧā§āĻā§",
|
"ToastProviderCreatedSuccess": "āύāϤā§āύ āĻĒā§āϰāĻĻāĻžāύāĻāĻžāϰ⧠āϝā§āĻ āĻāϰāĻž āĻšāϝāĻŧā§āĻā§",
|
||||||
"ToastProviderNameAndUrlRequired": "āύāĻžāĻŽ āĻāĻŦāĻ āĻāĻāĻāϰāĻāϞ āĻāĻŦāĻļā§āϝāĻ",
|
"ToastProviderNameAndUrlRequired": "āύāĻžāĻŽ āĻāĻŦāĻ āĻāĻāĻāϰāĻāϞ āĻāĻŦāĻļā§āϝāĻ",
|
||||||
@@ -972,6 +1050,7 @@
|
|||||||
"ToastSessionCloseFailed": "āĻ
āϧāĻŋāĻŦā§āĻļāύ āĻŦāύā§āϧ āĻāϰāϤ⧠āĻŦā§āϝāϰā§āĻĨ āĻšāϝāĻŧā§āĻā§",
|
"ToastSessionCloseFailed": "āĻ
āϧāĻŋāĻŦā§āĻļāύ āĻŦāύā§āϧ āĻāϰāϤ⧠āĻŦā§āϝāϰā§āĻĨ āĻšāϝāĻŧā§āĻā§",
|
||||||
"ToastSessionDeleteFailed": "āϏā§āĻļāύ āĻŽā§āĻā§ āĻĢā§āϞāϤ⧠āĻŦā§āϝāϰā§āĻĨ",
|
"ToastSessionDeleteFailed": "āϏā§āĻļāύ āĻŽā§āĻā§ āĻĢā§āϞāϤ⧠āĻŦā§āϝāϰā§āĻĨ",
|
||||||
"ToastSessionDeleteSuccess": "āϏā§āĻļāύ āĻŽā§āĻā§ āĻĢā§āϞāĻž āĻšāϝāĻŧā§āĻā§",
|
"ToastSessionDeleteSuccess": "āϏā§āĻļāύ āĻŽā§āĻā§ āĻĢā§āϞāĻž āĻšāϝāĻŧā§āĻā§",
|
||||||
|
"ToastSleepTimerDone": "āϏā§āϞāĻŋāĻĒ āĻāĻžāĻāĻŽāĻžāϰ āĻšāϝāĻŧā§ āĻā§āĻā§... zZzzZz",
|
||||||
"ToastSlugMustChange": "āϏā§āϞāĻžāĻā§ āĻ
āĻŦā§āϧ āĻ
āĻā§āώāϰ āϰāϝāĻŧā§āĻā§",
|
"ToastSlugMustChange": "āϏā§āϞāĻžāĻā§ āĻ
āĻŦā§āϧ āĻ
āĻā§āώāϰ āϰāϝāĻŧā§āĻā§",
|
||||||
"ToastSlugRequired": "āϏā§āϞāĻžāĻ āĻāĻŦāĻļā§āϝāĻ",
|
"ToastSlugRequired": "āϏā§āϞāĻžāĻ āĻāĻŦāĻļā§āϝāĻ",
|
||||||
"ToastSocketConnected": "āϏāĻā§āĻ āϏāĻāϝā§āĻā§āϤ",
|
"ToastSocketConnected": "āϏāĻā§āĻ āϏāĻāϝā§āĻā§āϤ",
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user