mirror of
https://github.com/gotson/komga.git
synced 2026-05-09 05:10:19 +02:00
Compare commits
No commits in common. "master" and "1.11.2" have entirely different histories.
581 changed files with 22791 additions and 52757 deletions
2
.github/ISSUE_TEMPLATE/config.yml
vendored
2
.github/ISSUE_TEMPLATE/config.yml
vendored
|
|
@ -7,5 +7,5 @@ contact_links:
|
|||
url: https://komga.org/docs/faq
|
||||
about: Guides, troubleshooting, and answers to common questions
|
||||
- name: ⚠️ Mihon extension
|
||||
url: https://github.com/keiyoushi/extensions-source
|
||||
url: https://github.com/keiyoushi/extensions
|
||||
about: Issues and requests about the Mihon extension should be opened in the keiyoushi/extensions repository instead
|
||||
|
|
|
|||
4
.github/ISSUE_TEMPLATE/report_issue.yml
vendored
4
.github/ISSUE_TEMPLATE/report_issue.yml
vendored
|
|
@ -44,9 +44,7 @@ body:
|
|||
attributes:
|
||||
label: Logs
|
||||
description: |
|
||||
:warning: **Do not share logs with Kobo Sync information publicly !**
|
||||
|
||||
If applicable, add an excerpt of the log file (max 20 lines) _AND_ attach the complete log file or a link to a gist/pastebin containing the log file ([where to find the logs](https://komga.org/docs/faq#where-can-i-find-the-log-files)).
|
||||
If applicable, add an excerpt of the log file (max 20 lines) _AND_ attach the complete log file or a link to a gist/pastebin containing the log file ([where to find the logs](https://komga.org/faq/#where-can-i-find-the-log-files)).
|
||||
placeholder: |
|
||||
You can paste the logs in pure text or upload it as an attachment.
|
||||
|
||||
|
|
|
|||
13
.github/readme-images/jetbrains.svg
vendored
13
.github/readme-images/jetbrains.svg
vendored
|
|
@ -1,13 +0,0 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" width="298" height="64" fill="none" viewBox="0 0 298 64">
|
||||
<defs>
|
||||
<linearGradient id="a" x1=".850001" x2="62.62" y1="62.72" y2="1.81" gradientUnits="userSpaceOnUse">
|
||||
<stop stop-color="#FF9419"/>
|
||||
<stop offset=".43" stop-color="#FF021D"/>
|
||||
<stop offset=".99" stop-color="#E600FF"/>
|
||||
</linearGradient>
|
||||
</defs>
|
||||
<path fill="#000" d="M86.4844 40.5858c0 .8464-.1792 1.5933-.5377 2.2505-.3585.6573-.8564 1.1651-1.5137 1.5236-.6572.3585-1.3941.5378-2.2406.5378H78v6.1044h5.0787c1.912 0 3.6248-.4282 5.1484-1.2846 1.5236-.8564 2.7186-2.0415 3.585-3.5452.8663-1.5037 1.3045-3.1966 1.3045-5.0886V21.0178h-6.6322v19.568Zm17.8556-1.8224h13.891v-5.6065H104.34v-6.3633h15.355v-5.7758H97.8766v29.9743h22.2464v-5.7757H104.34v-6.453Zm17.865-11.8005h8.882v24.0193h6.633V26.9629h8.842v-5.9451h-24.367v5.9551l.01-.01Zm47.022 9.0022c-.517-.2788-1.085-.4879-1.673-.6472.449-.1295.877-.2888 1.275-.488 1.096-.5676 1.962-1.3643 2.579-2.39.618-1.0257.936-2.2007.936-3.5351 0-1.5237-.418-2.8879-1.244-4.0929-.827-1.195-1.992-2.131-3.486-2.8082-1.494-.6672-3.206-1.0058-5.118-1.0058h-13.315v29.9743h13.574c2.011 0 3.804-.3485 5.387-1.0556 1.573-.707 2.798-1.6829 3.675-2.9476.866-1.2547 1.304-2.6887 1.304-4.302 0-1.4837-.338-2.8082-1.026-3.9833-.687-1.175-1.633-2.0812-2.858-2.7285l-.01.0099Zm-13.603-9.9184h5.886c.816 0 1.533.1494 2.161.4382.627.2888 1.115.707 1.464 1.2547.348.5378.527 1.1751.527 1.9021 0 .7269-.179 1.414-.527 1.9817-.349.5676-.837.9958-1.464 1.3045-.628.3087-1.345.4581-2.161.4581h-5.886v-7.3492.0099Zm10.138 18.134c-.378.5676-.916 1.0058-1.603 1.3145-.697.3087-1.484.4581-2.39.4581h-6.145v-7.6878h6.145c.886 0 1.673.1693 2.37.4979.687.3286 1.235.7867 1.613 1.3842.378.5975.578 1.2747.578 2.0414 0 .7668-.19 1.4241-.568 1.9917Zm29.596-5.3077c1.663-.7967 2.947-1.922 3.864-3.3659.916-1.444 1.374-3.117 1.374-5.0289 0-1.912-.448-3.5253-1.344-4.9592-.897-1.434-2.171-2.5394-3.814-3.3261-1.644-.7867-3.546-1.1751-5.717-1.1751h-13.124v29.9743h6.642V40.0779h4.322l6.084 10.9142h7.578l-6.851-11.7208c.339-.1195.677-.249.996-.3983h-.01Zm-2.151-6.1244c-.369.6274-.896 1.1154-1.583 1.444-.688.3386-1.494.5079-2.42.5079h-5.975v-8.2953h5.975c.926 0 1.732.1693 2.42.4979.687.3287 1.214.8166 1.583 1.434.368.6174.558 1.3544.558 2.1908 0 .8365-.19 1.5734-.558 2.2008v.0199Zm20.594-11.7308-10.706 29.9743h6.742l2.121-6.6122h11.114l2.27 6.6122h6.612L220.99 21.0178h-7.189Zm-.339 18.3431 3.445-10.5756.409-1.922.408 1.922 3.685 10.5756h-7.947Zm20.693 11.6312h6.851V21.0178h-6.851v29.9743Zm31.02-9.6993-12.896-20.275h-6.463v29.9743h6.055V30.7172l12.826 20.2749h6.533V21.0178h-6.055v20.275Zm31.528-3.3559c-.647-1.2448-1.564-2.2904-2.729-3.1369-1.165-.8464-2.509-1.4041-4.023-1.6929l-5.098-1.0456c-.797-.1892-1.434-.5178-1.902-.9958-.469-.478-.708-1.0755-.708-1.7825 0-.6473.17-1.205.518-1.683.339-.478.827-.8464 1.444-1.1153.618-.2689 1.335-.3983 2.151-.3983.817 0 1.554.1394 2.181.4182.627.2788 1.115.6672 1.464 1.1751s.528 1.0755.528 1.7228h6.642c-.04-1.7427-.528-3.2863-1.444-4.6207-.916-1.3443-2.201-2.3899-3.834-3.1468-1.633-.7568-3.505-1.1352-5.597-1.1352-2.091 0-3.943.3884-5.566 1.1751-1.623.7867-2.898 1.8721-3.804 3.2663-.906 1.3941-1.364 2.9775-1.364 4.76 0 1.444.288 2.7485.876 3.9036.587 1.1652 1.414 2.1311 2.479 2.8979 1.076.7668 2.311 1.3045 3.725 1.6033l5.397 1.1153c.886.2091 1.584.5975 2.101 1.1551.518.5577.767 1.2448.767 2.0813 0 .6672-.189 1.2747-.567 1.8025-.379.5277-.907.936-1.584 1.2248-.677.2888-1.474.4282-2.39.4282-.916 0-1.782-.1593-2.529-.478-.747-.3186-1.325-.7767-1.733-1.3742-.418-.5875-.617-1.2747-.617-2.0414h-6.642c.029 1.8721.527 3.5152 1.513 4.9492.976 1.424 2.32 2.5394 4.033 3.336 1.713.7967 3.675 1.195 5.886 1.195 2.21 0 4.202-.4083 5.915-1.2249 1.723-.8165 3.057-1.9418 4.023-3.3758.966-1.434 1.444-3.0572 1.444-4.8696 0-1.4838-.329-2.848-.976-4.1028l.02.01Z"/>
|
||||
<path fill="url(#a)" d="M20.34 3.66 3.66 20.34C1.32 22.68 0 25.86 0 29.18V59c0 2.76 2.24 5 5 5h29.82c3.32 0 6.49-1.32 8.84-3.66l16.68-16.68c2.34-2.34 3.66-5.52 3.66-8.84V5c0-2.76-2.24-5-5-5H29.18c-3.32 0-6.49 1.32-8.84 3.66Z"/>
|
||||
<path fill="#000" d="M48 16H8v40h40V16Z"/>
|
||||
<path fill="#fff" d="M30 47H13v4h17v-4Z"/>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 4.1 KiB |
BIN
.github/readme-images/sponsors-jetbrains.png
vendored
Normal file
BIN
.github/readme-images/sponsors-jetbrains.png
vendored
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 24 KiB |
35
.github/workflows/browserlist-update.yml
vendored
35
.github/workflows/browserlist-update.yml
vendored
|
|
@ -1,35 +0,0 @@
|
|||
name: Update Browserslist database
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
schedule:
|
||||
- cron: '0 2 1 * *'
|
||||
|
||||
permissions:
|
||||
contents: write
|
||||
pull-requests: write
|
||||
|
||||
jobs:
|
||||
update-browserslist-database:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v6
|
||||
with:
|
||||
fetch-depth: 0
|
||||
- name: Configure git
|
||||
run: |
|
||||
# Setup for commiting using built-in token. See https://github.com/actions/checkout#push-a-commit-using-the-built-in-token
|
||||
git config user.name "github-actions[bot]"
|
||||
git config user.email "41898282+github-actions[bot]@users.noreply.github.com"
|
||||
- name: Update Browserslist database and create PR if applies
|
||||
uses: c2corg/browserslist-update-action@v2
|
||||
with:
|
||||
github_token: ${{ secrets.GITHUB_TOKEN }}
|
||||
branch: browserslist-update
|
||||
base_branch: master
|
||||
directory: ./komga-webui
|
||||
commit_message: 'build(webui): update Browserslist db'
|
||||
title: 'Browserslist database update'
|
||||
body: Auto-generated by [browserslist-update-action](https://github.com/c2corg/browserslist-update-action/)
|
||||
labels: 'github_actions'
|
||||
18
.github/workflows/dispatch.yml
vendored
18
.github/workflows/dispatch.yml
vendored
|
|
@ -1,18 +0,0 @@
|
|||
name: Dispatch events
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [ master ]
|
||||
paths:
|
||||
- '**/openapi.json'
|
||||
|
||||
jobs:
|
||||
dispatch:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Repository Dispatch
|
||||
uses: peter-evans/repository-dispatch@v4
|
||||
with:
|
||||
token: ${{ secrets.REPO_ACCESS_TOKEN }}
|
||||
repository: gotson/komga-website
|
||||
event-type: openapi
|
||||
2
.github/workflows/dockerhub_description.yml
vendored
2
.github/workflows/dockerhub_description.yml
vendored
|
|
@ -15,7 +15,7 @@ jobs:
|
|||
steps:
|
||||
- uses: actions/checkout@master
|
||||
- name: DockerHub Description
|
||||
uses: peter-evans/dockerhub-description@v5.0.0
|
||||
uses: peter-evans/dockerhub-description@v4.0.0
|
||||
env:
|
||||
DOCKERHUB_USERNAME: ${{ secrets.DOCKER_USERNAME }}
|
||||
DOCKERHUB_PASSWORD: ${{ secrets.DOCKER_PASSWORD }}
|
||||
|
|
|
|||
14
.github/workflows/gradle-wrapper-validation.yml
vendored
Normal file
14
.github/workflows/gradle-wrapper-validation.yml
vendored
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
name: "Validate Gradle Wrapper"
|
||||
on:
|
||||
pull_request:
|
||||
push:
|
||||
branches-ignore:
|
||||
- 'dependabot/**'
|
||||
|
||||
jobs:
|
||||
validation:
|
||||
name: "Validation"
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: gradle/actions/wrapper-validation@v3
|
||||
2
.github/workflows/lock.yml
vendored
2
.github/workflows/lock.yml
vendored
|
|
@ -12,7 +12,7 @@ jobs:
|
|||
lock:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: dessant/lock-threads@v6
|
||||
- uses: dessant/lock-threads@v5
|
||||
with:
|
||||
github-token: ${{ github.token }}
|
||||
issue-inactive-days: '30'
|
||||
|
|
|
|||
79
.github/workflows/release.yml
vendored
79
.github/workflows/release.yml
vendored
|
|
@ -43,24 +43,24 @@ jobs:
|
|||
version_next: ${{ steps.versions.outputs.version_next }}
|
||||
should_release: ${{ steps.versions.outputs.should_release }}
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
- name: Set up Homebrew
|
||||
id: set-up-homebrew
|
||||
uses: Homebrew/actions/setup-homebrew@main
|
||||
uses: Homebrew/actions/setup-homebrew@master
|
||||
- name: Install svu
|
||||
run: brew install --cask caarlos0/tap/svu
|
||||
run: brew install caarlos0/tap/svu
|
||||
- name: Compute next version for release
|
||||
run: |
|
||||
echo "VERSION_NEXT=`svu ${{ inputs.bump }}`" | tee -a $GITHUB_ENV
|
||||
echo "VERSION_NEXT_SUFFIX=`svu ${{ inputs.bump }}`" | tee -a $GITHUB_ENV
|
||||
echo "VERSION_NEXT=`svu --pattern="[0-9]*" --strip-prefix ${{ inputs.bump }}`" | tee -a $GITHUB_ENV
|
||||
echo "VERSION_NEXT_SUFFIX=`svu --pattern="[0-9]*" --strip-prefix ${{ inputs.bump }}`" | tee -a $GITHUB_ENV
|
||||
- name: Set Versions
|
||||
id: versions
|
||||
run: |
|
||||
echo "version_current=`svu current`" >> $GITHUB_OUTPUT
|
||||
echo "version_current=`svu --pattern="[0-9]*" --strip-prefix current`" >> $GITHUB_OUTPUT
|
||||
echo "version_next=${{ env.VERSION_NEXT_SUFFIX }}" >> $GITHUB_OUTPUT
|
||||
[[ `svu current` != ${{ env.VERSION_NEXT }} ]] && echo "should_release=true" >> $GITHUB_OUTPUT || echo
|
||||
[[ `svu --pattern="[0-9]*" --strip-prefix current` != ${{ env.VERSION_NEXT }} ]] && echo "should_release=true" >> $GITHUB_OUTPUT || echo
|
||||
|
||||
release:
|
||||
name: Release
|
||||
|
|
@ -72,7 +72,7 @@ jobs:
|
|||
sudo rm -rf /usr/share/dotnet
|
||||
sudo rm -rf "$AGENT_TOOLSDIRECTORY"
|
||||
|
||||
- uses: actions/checkout@v6
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
|
|
@ -84,40 +84,44 @@ jobs:
|
|||
if: needs.version.outputs.should_release #only redo if the version changed
|
||||
run: sed -i -e "s/version=.*/version=${{ needs.version.outputs.version_next }}/" gradle.properties
|
||||
|
||||
- uses: actions/setup-node@v6
|
||||
- uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version-file: '.nvmrc'
|
||||
cache: 'npm'
|
||||
cache-dependency-path: komga-webui/package-lock.json
|
||||
|
||||
- name: Setup Java 21
|
||||
uses: actions/setup-java@v5
|
||||
- uses: actions/setup-java@v4
|
||||
with:
|
||||
java-version: 21
|
||||
java-package: 'jdk'
|
||||
distribution: 'temurin'
|
||||
|
||||
- uses: actions/setup-java@v4
|
||||
with:
|
||||
java-version: 17
|
||||
java-package: 'jdk'
|
||||
distribution: 'temurin'
|
||||
|
||||
- name: Set up QEMU
|
||||
uses: docker/setup-qemu-action@v4
|
||||
uses: docker/setup-qemu-action@v3
|
||||
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v4
|
||||
uses: docker/setup-buildx-action@v3
|
||||
|
||||
- name: Login to Docker Hub
|
||||
uses: docker/login-action@v4
|
||||
uses: docker/login-action@v3
|
||||
with:
|
||||
username: ${{ secrets.DOCKER_USERNAME }}
|
||||
password: ${{ secrets.DOCKER_PASSWORD }}
|
||||
|
||||
- name: Login to Docker Hub
|
||||
uses: docker/login-action@v4
|
||||
uses: docker/login-action@v3
|
||||
with:
|
||||
registry: ghcr.io
|
||||
username: ${{ github.repository_owner }}
|
||||
password: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Setup Gradle
|
||||
uses: gradle/actions/setup-gradle@v6
|
||||
- uses: gradle/actions/setup-gradle@v3
|
||||
|
||||
- name: Build
|
||||
run: ./gradlew :komga:prepareThymeLeaf :komga:bootJar :komga-tray:jar
|
||||
|
|
@ -143,7 +147,7 @@ jobs:
|
|||
JRELEASER_GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
- name: JReleaser Changelog output
|
||||
if: always() && needs.version.outputs.should_release
|
||||
uses: actions/upload-artifact@v7
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: jreleaser-changelog
|
||||
path: |
|
||||
|
|
@ -151,7 +155,7 @@ jobs:
|
|||
build/jreleaser/output.properties
|
||||
|
||||
- name: Release commit and push
|
||||
uses: EndBug/add-and-commit@v10
|
||||
uses: EndBug/add-and-commit@v9
|
||||
if: needs.version.outputs.should_release #only redo if the version changed
|
||||
with:
|
||||
message: 'chore(release): ${{ needs.version.outputs.version_next }} [skip ci]'
|
||||
|
|
@ -167,7 +171,7 @@ jobs:
|
|||
echo $APPLE_PRIVATE_KEY | base64 --decode > ./secret/apple_private_key.p8
|
||||
|
||||
- name: Conveyor make copied-site
|
||||
uses: hydraulic-software/conveyor/actions/build@v22.0
|
||||
uses: hydraulic-software/conveyor/actions/build@v14.3
|
||||
if: inputs.conveyor-copied-site
|
||||
with:
|
||||
command: --cache-limit=2.0 -f conveyor.ci.conf make copied-site -o ./output/site
|
||||
|
|
@ -182,7 +186,7 @@ jobs:
|
|||
AWS_SECRET_ACCESS_KEY: ${{ secrets.B2_SECRET_ACCESS_KEY }}
|
||||
- name: Upload Conveyor log
|
||||
if: always() && inputs.conveyor-copied-site
|
||||
uses: actions/upload-artifact@v7
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: conveyor-make-copied-site
|
||||
path: ~/.cache/hydraulic/conveyor/logs/log.latest.txt
|
||||
|
|
@ -194,13 +198,27 @@ jobs:
|
|||
JRELEASER_GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
- name: JReleaser Release output
|
||||
if: always() && inputs.github_release
|
||||
uses: actions/upload-artifact@v7
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: jreleaser-release
|
||||
path: |
|
||||
build/jreleaser/trace.log
|
||||
build/jreleaser/output.properties
|
||||
|
||||
- name: JReleaser Announce
|
||||
if: inputs.github_release
|
||||
run: ./gradlew jreleaserAnnounce
|
||||
env:
|
||||
JRELEASER_GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
- name: JReleaser Announce output
|
||||
if: always() && inputs.github_release
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: jreleaser-announce
|
||||
path: |
|
||||
build/jreleaser/trace.log
|
||||
build/jreleaser/output.properties
|
||||
|
||||
# Sometimes the workflow will fail because it's out of disk space
|
||||
- name: Cleanup Conveyor output
|
||||
run: rm -fr ./output
|
||||
|
|
@ -212,7 +230,7 @@ jobs:
|
|||
JRELEASER_GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
- name: JReleaser Publish output
|
||||
if: always() && inputs.docker_release
|
||||
uses: actions/upload-artifact@v7
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: jreleaser-publish
|
||||
path: |
|
||||
|
|
@ -220,7 +238,7 @@ jobs:
|
|||
build/jreleaser/output.properties
|
||||
|
||||
- name: Conveyor - publish to Microsoft Store
|
||||
uses: hydraulic-software/conveyor/actions/build@v22.0
|
||||
uses: hydraulic-software/conveyor/actions/build@v14.3
|
||||
if: inputs.msstore_release
|
||||
with:
|
||||
command: --cache-limit=2.0 -f conveyor.msstore.ci.conf make ms-store-release -o ./output/msstore
|
||||
|
|
@ -236,18 +254,7 @@ jobs:
|
|||
AWS_SECRET_ACCESS_KEY: ${{ secrets.B2_SECRET_ACCESS_KEY }}
|
||||
- name: Upload Conveyor log
|
||||
if: always() && inputs.msstore_release
|
||||
uses: actions/upload-artifact@v7
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: conveyor-ms-store-release
|
||||
path: ~/.cache/hydraulic/conveyor/logs/log.latest.txt
|
||||
|
||||
dispatch:
|
||||
needs: release
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Repository Dispatch
|
||||
uses: peter-evans/repository-dispatch@v4
|
||||
with:
|
||||
token: ${{ secrets.REPO_ACCESS_TOKEN }}
|
||||
repository: gotson/komga-website
|
||||
event-type: komga-release
|
||||
|
|
|
|||
48
.github/workflows/tests.yml
vendored
48
.github/workflows/tests.yml
vendored
|
|
@ -12,51 +12,43 @@ on:
|
|||
|
||||
jobs:
|
||||
test:
|
||||
runs-on: ${{ matrix.os }}
|
||||
strategy:
|
||||
matrix:
|
||||
os: [ ubuntu-latest, macos-latest, windows-latest ]
|
||||
fail-fast: false
|
||||
name: Test server - ${{ matrix.os }}
|
||||
runs-on: ubuntu-latest
|
||||
name: Test server
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
- name: Setup Java 21
|
||||
uses: actions/setup-java@v5
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/setup-java@v4
|
||||
with:
|
||||
java-version: 21
|
||||
java-package: 'jdk'
|
||||
distribution: 'temurin'
|
||||
- uses: actions/setup-java@v4
|
||||
with:
|
||||
java-version: 17
|
||||
java-package: 'jdk'
|
||||
distribution: 'temurin'
|
||||
|
||||
- name: Setup Gradle
|
||||
uses: gradle/actions/setup-gradle@v6
|
||||
- uses: gradle/actions/setup-gradle@v3
|
||||
|
||||
- name: Build
|
||||
run: ./gradlew build :komga-tray:jar
|
||||
|
||||
- name: Upload Unit Test Results
|
||||
if: always()
|
||||
uses: actions/upload-artifact@v7
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: test-results-${{ matrix.os }}
|
||||
name: test-results
|
||||
path: komga/build/test-results/
|
||||
|
||||
- name: Upload Unit Test Reports
|
||||
if: always()
|
||||
uses: actions/upload-artifact@v7
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: test-reports-${{ matrix.os }}
|
||||
name: test-reports
|
||||
path: komga/build/reports/tests/
|
||||
|
||||
- name: Publish Test Report
|
||||
uses: mikepenz/action-junit-report@v6
|
||||
if: always()
|
||||
with:
|
||||
report_paths: '**/build/test-results/test/TEST-*.xml'
|
||||
check_name: 'JUnit Test Report: ${{ matrix.os }}'
|
||||
|
||||
- name: Conveyor - compute JDK module list
|
||||
if: github.event_name == 'push' && github.repository_owner == 'gotson' && contains(matrix.os, 'ubuntu')
|
||||
uses: hydraulic-software/conveyor/actions/build@v22.0
|
||||
if: github.event_name == 'push'
|
||||
uses: hydraulic-software/conveyor/actions/build@v14.3
|
||||
with:
|
||||
command: -f conveyor.detect.conf -Kapp.machines=mac.aarch64 make processed-jars
|
||||
signing_key: ${{ secrets.CONVEYOR_SIGNING_KEY }}
|
||||
|
|
@ -64,12 +56,12 @@ jobs:
|
|||
|
||||
- name: Compare JDK required modules
|
||||
id: conveyor_compare
|
||||
if: github.event_name == 'push' && github.repository_owner == 'gotson' && contains(matrix.os, 'ubuntu')
|
||||
if: github.event_name == 'push'
|
||||
run: diff --unified ./komga-tray/conveyor/required-jdk-modules.txt ./output/required-jdk-modules.txt
|
||||
|
||||
- name: Upload JDK required modules
|
||||
if: steps.conveyor_compare.outcome == 'failure'
|
||||
uses: actions/upload-artifact@v7
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: conveyor-required-jdk-modules
|
||||
path: ./output/required-jdk-modules.txt
|
||||
|
|
@ -78,8 +70,8 @@ jobs:
|
|||
runs-on: ubuntu-latest
|
||||
name: Test webui builds
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
- uses: actions/setup-node@v6
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version-file: '.nvmrc'
|
||||
cache: 'npm'
|
||||
|
|
|
|||
3
.gitignore
vendored
3
.gitignore
vendored
|
|
@ -2,9 +2,6 @@
|
|||
.gradle
|
||||
!gradle/wrapper/gradle-wrapper.jar
|
||||
|
||||
### Kotlin
|
||||
.kotlin
|
||||
|
||||
### NodeJS
|
||||
node_modules
|
||||
|
||||
|
|
|
|||
3
.svu.yml
3
.svu.yml
|
|
@ -1,3 +0,0 @@
|
|||
tag:
|
||||
prefix: ''
|
||||
pattern: '[0-9]*'
|
||||
1268
CHANGELOG.md
1268
CHANGELOG.md
File diff suppressed because it is too large
Load diff
|
|
@ -1,4 +1,4 @@
|
|||
1. **Before reporting a new issue, take a look at the [FAQ](https://komga.org/docs/faq/), the [changelog](https://github.com/gotson/komga/blob/master/CHANGELOG.md) and the already opened [issues](https://github.com/gotson/komga/issues).**
|
||||
1. **Before reporting a new issue, take a look at the [FAQ](https://komga.org/faq/), the [changelog](https://github.com/gotson/komga/blob/master/CHANGELOG.md) and the already opened [issues](https://github.com/gotson/komga/issues).**
|
||||
1. If you are unsure, ask here: [](https://discord.gg/TdRpkDu)
|
||||
1. **DO NOT** reply on existing issues to say _"+1"_ or _"I am interested in this"_.
|
||||
1. **DO** show your enthusiasm for an existing issue by adding a :+1: reaction on the first message in the discussion.
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@ Thanks a lot for contributing to Komga!
|
|||
|
||||
You will need:
|
||||
|
||||
- Java JDK version 21+
|
||||
- Java JDK version 17 & 21
|
||||
- Nodejs version 18+ (check the `.nvmrc` file)
|
||||
|
||||
## Setting up the project
|
||||
|
|
|
|||
|
|
@ -37,9 +37,3 @@
|
|||
| ERR_1031 | ComicRack CBL Book is missing series or number |
|
||||
| ERR_1032 | EPUB file has wrong media type |
|
||||
| ERR_1033 | Some entries are missing |
|
||||
| ERR_1034 | An API key with that comment already exists |
|
||||
| ERR_1035 | Error while getting EPUB TOC |
|
||||
| ERR_1036 | Error while getting EPUB Landmarks |
|
||||
| ERR_1037 | Error while getting EPUB page list |
|
||||
| ERR_1038 | Error while getting EPUB divina pages |
|
||||
| ERR_1039 | Error while getting EPUB positions |
|
||||
|
|
|
|||
13
README.md
13
README.md
|
|
@ -22,9 +22,6 @@ Komga is a media server for your comics, mangas, BDs, magazines and eBooks.
|
|||
- Webreader with multiple reading modes
|
||||
- Manage multiple users, with per-library access control, age restrictions, and labels restrictions
|
||||
- Offers a REST API, many community tools and scripts can interact with Komga
|
||||
- OPDS v1 and v2 support
|
||||
- Kobo Sync with your Kobo eReader
|
||||
- KOReader Sync
|
||||
- Download book files, whole series, or read lists
|
||||
- Duplicate files detection
|
||||
- Duplicate pages detection and removal
|
||||
|
|
@ -47,15 +44,9 @@ Check the [development guidelines](./DEVELOPING.md).
|
|||
|
||||
[](https://hosted.weblate.org/engage/komga/)
|
||||
|
||||
## Powered by
|
||||
## Sponsors
|
||||
|
||||
[](https://www.jetbrains.com/?from=Komga)
|
||||
|
||||
Thanks to [JetBrains](https://www.jetbrains.com/?from=Komga) for providing the development environment that helps us develop Komga.
|
||||
|
||||
[](https://www.chromatic.com)
|
||||
|
||||
Thanks to [Chromatic](https://www.chromatic.com/) for providing the visual testing platform that helps us review UI changes and catch visual regressions.
|
||||
[](https://www.jetbrains.com/?from=Komga)
|
||||
|
||||
## Credits
|
||||
|
||||
|
|
|
|||
|
|
@ -7,14 +7,14 @@ import kotlin.io.path.exists
|
|||
|
||||
plugins {
|
||||
run {
|
||||
val kotlinVersion = "2.2.0"
|
||||
val kotlinVersion = "1.9.21"
|
||||
kotlin("jvm") version kotlinVersion
|
||||
kotlin("plugin.spring") version kotlinVersion
|
||||
kotlin("kapt") version kotlinVersion
|
||||
}
|
||||
id("org.jlleitschuh.gradle.ktlint") version "13.0.0"
|
||||
id("com.github.ben-manes.versions") version "0.52.0"
|
||||
id("org.jreleaser") version "1.19.0"
|
||||
id("org.jlleitschuh.gradle.ktlint") version "12.1.0"
|
||||
id("com.github.ben-manes.versions") version "0.50.0"
|
||||
id("org.jreleaser") version "1.10.0"
|
||||
}
|
||||
|
||||
fun isNonStable(version: String): Boolean {
|
||||
|
|
@ -44,16 +44,12 @@ allprojects {
|
|||
}
|
||||
|
||||
configure<org.jlleitschuh.gradle.ktlint.KtlintExtension> {
|
||||
version = "1.7.1"
|
||||
filter {
|
||||
exclude("**/generated-src/**")
|
||||
exclude("**/generated/**")
|
||||
}
|
||||
version = "1.1.1"
|
||||
}
|
||||
}
|
||||
|
||||
tasks.wrapper {
|
||||
gradleVersion = "8.14.3"
|
||||
gradleVersion = "8.5"
|
||||
distributionType = Wrapper.DistributionType.ALL
|
||||
}
|
||||
|
||||
|
|
@ -164,7 +160,7 @@ jreleaser {
|
|||
packagers {
|
||||
docker {
|
||||
active = Active.RELEASE
|
||||
continueOnError = false
|
||||
continueOnError = true
|
||||
templateDirectory = rootDir.resolve("komga/docker")
|
||||
repository.active = Active.NEVER
|
||||
buildArgs = listOf("--cache-from", "gotson/komga:latest")
|
||||
|
|
|
|||
|
|
@ -1,5 +1,4 @@
|
|||
include "#!./gradlew -q :komga-tray:printConveyorConfig"
|
||||
include required("/stdlib/jdk/23/eclipse.conf")
|
||||
|
||||
app {
|
||||
display-name = Komga
|
||||
|
|
@ -10,14 +9,9 @@ app {
|
|||
license = MIT
|
||||
icons = "res/komga_text_as_path.svg"
|
||||
|
||||
machines = [
|
||||
windows.amd64,
|
||||
mac
|
||||
]
|
||||
|
||||
jvm {
|
||||
// for NightMonkeys & NightCompress
|
||||
options += "--enable-native-access=ALL-UNNAMED"
|
||||
// for NightMonkeys
|
||||
options += "--enable-preview"
|
||||
|
||||
mac.options += "-Dspring.profiles.include=mac"
|
||||
|
||||
|
|
@ -51,16 +45,12 @@ app {
|
|||
exe-installer-basename = "KomgaInstaller"
|
||||
manifests.msix.background-color = transparent
|
||||
inputs += ./komga-tray/lib/windows/x64/
|
||||
amd64.inputs += "https://github.com/pgaskin/kepubify/releases/latest/download/kepubify-windows-64bit.exe" -> kepubify.exe
|
||||
aarch64.inputs += "https://github.com/pgaskin/kepubify/releases/latest/download/kepubify-windows-arm64.exe" -> kepubify.exe
|
||||
}
|
||||
|
||||
mac {
|
||||
info-plist.LSMinimumSystemVersion = 13
|
||||
info-plist.LSMinimumSystemVersion = 12
|
||||
aarch64.inputs += ./komga-tray/lib/mac/aarch64/
|
||||
aarch64.inputs += "https://github.com/pgaskin/kepubify/releases/latest/download/kepubify-darwin-arm64" -> kepubify
|
||||
amd64.inputs += ./komga-tray/lib/mac/x64/
|
||||
amd64.inputs += "https://github.com/pgaskin/kepubify/releases/latest/download/kepubify-darwin-64bit" -> kepubify
|
||||
}
|
||||
|
||||
site {
|
||||
|
|
@ -68,4 +58,4 @@ app {
|
|||
}
|
||||
}
|
||||
|
||||
conveyor.compatibility-level = 18
|
||||
conveyor.compatibility-level = 13
|
||||
|
|
|
|||
|
|
@ -1,2 +1,2 @@
|
|||
version=1.24.4
|
||||
version=1.11.2
|
||||
org.gradle.jvmargs=-Xmx2G
|
||||
|
|
|
|||
|
|
@ -1,10 +0,0 @@
|
|||
[versions]
|
||||
sqliteJdbc = "3.50.2.0"
|
||||
nightmonkeys = "1.0.0"
|
||||
twelvemonkeys = "3.12.0"
|
||||
springboot = "3.5.14"
|
||||
lucene = "9.9.1" # v10 requires JDK 21
|
||||
jooq = "3.19.32" # should be aligned with the version provided by Spring Boot
|
||||
|
||||
[plugins]
|
||||
gradleGitProperties = {id = "com.gorylenko.gradle-git-properties", version = "2.5.7"}
|
||||
BIN
gradle/wrapper/gradle-wrapper.jar
vendored
BIN
gradle/wrapper/gradle-wrapper.jar
vendored
Binary file not shown.
2
gradle/wrapper/gradle-wrapper.properties
vendored
2
gradle/wrapper/gradle-wrapper.properties
vendored
|
|
@ -1,6 +1,6 @@
|
|||
distributionBase=GRADLE_USER_HOME
|
||||
distributionPath=wrapper/dists
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-8.14.3-all.zip
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-8.5-all.zip
|
||||
networkTimeout=10000
|
||||
validateDistributionUrl=true
|
||||
zipStoreBase=GRADLE_USER_HOME
|
||||
|
|
|
|||
12
gradlew
vendored
12
gradlew
vendored
|
|
@ -15,8 +15,6 @@
|
|||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
#
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
#
|
||||
|
||||
##############################################################################
|
||||
#
|
||||
|
|
@ -57,7 +55,7 @@
|
|||
# Darwin, MinGW, and NonStop.
|
||||
#
|
||||
# (3) This script is generated from the Groovy template
|
||||
# https://github.com/gradle/gradle/blob/HEAD/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
|
||||
# https://github.com/gradle/gradle/blob/HEAD/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
|
||||
# within the Gradle project.
|
||||
#
|
||||
# You can find Gradle at https://github.com/gradle/gradle/.
|
||||
|
|
@ -86,7 +84,7 @@ done
|
|||
# shellcheck disable=SC2034
|
||||
APP_BASE_NAME=${0##*/}
|
||||
# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036)
|
||||
APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s\n' "$PWD" ) || exit
|
||||
APP_HOME=$( cd "${APP_HOME:-./}" > /dev/null && pwd -P ) || exit
|
||||
|
||||
# Use the maximum available, or set MAX_FD != -1 to use that value.
|
||||
MAX_FD=maximum
|
||||
|
|
@ -114,7 +112,7 @@ case "$( uname )" in #(
|
|||
NONSTOP* ) nonstop=true ;;
|
||||
esac
|
||||
|
||||
CLASSPATH="\\\"\\\""
|
||||
CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
|
||||
|
||||
|
||||
# Determine the Java command to use to start the JVM.
|
||||
|
|
@ -205,7 +203,7 @@ fi
|
|||
DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
|
||||
|
||||
# Collect all arguments for the java command:
|
||||
# * DEFAULT_JVM_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments,
|
||||
# * DEFAULT_JVM_OPTS, JAVA_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments,
|
||||
# and any embedded shellness will be escaped.
|
||||
# * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be
|
||||
# treated as '${Hostname}' itself on the command line.
|
||||
|
|
@ -213,7 +211,7 @@ DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
|
|||
set -- \
|
||||
"-Dorg.gradle.appname=$APP_BASE_NAME" \
|
||||
-classpath "$CLASSPATH" \
|
||||
-jar "$APP_HOME/gradle/wrapper/gradle-wrapper.jar" \
|
||||
org.gradle.wrapper.GradleWrapperMain \
|
||||
"$@"
|
||||
|
||||
# Stop when "xargs" is not available.
|
||||
|
|
|
|||
26
gradlew.bat
vendored
26
gradlew.bat
vendored
|
|
@ -13,8 +13,6 @@
|
|||
@rem See the License for the specific language governing permissions and
|
||||
@rem limitations under the License.
|
||||
@rem
|
||||
@rem SPDX-License-Identifier: Apache-2.0
|
||||
@rem
|
||||
|
||||
@if "%DEBUG%"=="" @echo off
|
||||
@rem ##########################################################################
|
||||
|
|
@ -45,11 +43,11 @@ set JAVA_EXE=java.exe
|
|||
%JAVA_EXE% -version >NUL 2>&1
|
||||
if %ERRORLEVEL% equ 0 goto execute
|
||||
|
||||
echo. 1>&2
|
||||
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2
|
||||
echo. 1>&2
|
||||
echo Please set the JAVA_HOME variable in your environment to match the 1>&2
|
||||
echo location of your Java installation. 1>&2
|
||||
echo.
|
||||
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
|
||||
echo.
|
||||
echo Please set the JAVA_HOME variable in your environment to match the
|
||||
echo location of your Java installation.
|
||||
|
||||
goto fail
|
||||
|
||||
|
|
@ -59,22 +57,22 @@ set JAVA_EXE=%JAVA_HOME%/bin/java.exe
|
|||
|
||||
if exist "%JAVA_EXE%" goto execute
|
||||
|
||||
echo. 1>&2
|
||||
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2
|
||||
echo. 1>&2
|
||||
echo Please set the JAVA_HOME variable in your environment to match the 1>&2
|
||||
echo location of your Java installation. 1>&2
|
||||
echo.
|
||||
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
|
||||
echo.
|
||||
echo Please set the JAVA_HOME variable in your environment to match the
|
||||
echo location of your Java installation.
|
||||
|
||||
goto fail
|
||||
|
||||
:execute
|
||||
@rem Setup the command line
|
||||
|
||||
set CLASSPATH=
|
||||
set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
|
||||
|
||||
|
||||
@rem Execute Gradle
|
||||
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" -jar "%APP_HOME%\gradle\wrapper\gradle-wrapper.jar" %*
|
||||
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %*
|
||||
|
||||
:end
|
||||
@rem End local scope for the variables with windows NT shell
|
||||
|
|
|
|||
|
|
@ -1,28 +1,20 @@
|
|||
import org.jetbrains.kotlin.gradle.dsl.JvmTarget
|
||||
import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
|
||||
|
||||
plugins {
|
||||
run {
|
||||
kotlin("jvm")
|
||||
kotlin("plugin.spring")
|
||||
}
|
||||
alias(libs.plugins.gradleGitProperties)
|
||||
id("org.jetbrains.compose") version "1.8.2"
|
||||
id("org.jetbrains.kotlin.plugin.compose") version "2.2.0"
|
||||
id("dev.hydraulic.conveyor") version "1.12"
|
||||
id("com.gorylenko.gradle-git-properties") version "2.4.1"
|
||||
id("org.jetbrains.compose") version "1.5.11"
|
||||
id("dev.hydraulic.conveyor") version "1.8"
|
||||
application
|
||||
}
|
||||
|
||||
group = "org.gotson"
|
||||
|
||||
repositories {
|
||||
mavenCentral()
|
||||
google()
|
||||
}
|
||||
|
||||
kotlin {
|
||||
compilerOptions {
|
||||
jvmTarget = JvmTarget.JVM_17
|
||||
}
|
||||
jvmToolchain(21)
|
||||
}
|
||||
|
||||
tasks {
|
||||
|
|
@ -30,13 +22,17 @@ tasks {
|
|||
sourceCompatibility = "17"
|
||||
targetCompatibility = "17"
|
||||
}
|
||||
withType<KotlinCompile> {
|
||||
kotlinOptions {
|
||||
jvmTarget = "17"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation(project(":komga"))
|
||||
|
||||
implementation(compose.desktop.currentOs)
|
||||
implementation(compose.components.resources)
|
||||
|
||||
linuxAmd64(compose.desktop.linux_x64)
|
||||
macAmd64(compose.desktop.macos_x64)
|
||||
|
|
|
|||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
|
|
@ -16,6 +16,7 @@ import androidx.compose.ui.Alignment
|
|||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.platform.LocalClipboardManager
|
||||
import androidx.compose.ui.platform.LocalDensity
|
||||
import androidx.compose.ui.res.loadSvgPainter
|
||||
import androidx.compose.ui.text.AnnotatedString
|
||||
import androidx.compose.ui.unit.Dp
|
||||
import androidx.compose.ui.unit.DpSize
|
||||
|
|
@ -26,7 +27,6 @@ import androidx.compose.ui.window.WindowPosition
|
|||
import androidx.compose.ui.window.WindowState
|
||||
import androidx.compose.ui.window.application
|
||||
import org.gotson.komga.RB
|
||||
import org.jetbrains.compose.resources.decodeToSvgPainter
|
||||
import org.springframework.core.io.ClassPathResource
|
||||
|
||||
@Preview
|
||||
|
|
@ -50,7 +50,7 @@ fun showErrorDialog(
|
|||
Dp.Unspecified,
|
||||
),
|
||||
),
|
||||
icon = ClassPathResource("icons/komga-color.svg").inputStream.readAllBytes().decodeToSvgPainter(LocalDensity.current),
|
||||
icon = loadSvgPainter(ClassPathResource("icons/komga-color.svg").inputStream, LocalDensity.current),
|
||||
) {
|
||||
Column(
|
||||
modifier = Modifier.padding(16.dp),
|
||||
|
|
@ -60,7 +60,7 @@ fun showErrorDialog(
|
|||
modifier = Modifier.padding(bottom = 16.dp),
|
||||
) {
|
||||
Image(
|
||||
painter = ClassPathResource("icons/komga-color.svg").inputStream.readAllBytes().decodeToSvgPainter(LocalDensity.current),
|
||||
painter = loadSvgPainter(ClassPathResource("icons/komga-color.svg").inputStream, LocalDensity.current),
|
||||
contentDescription = "Komga logo",
|
||||
modifier =
|
||||
Modifier
|
||||
|
|
|
|||
|
|
@ -1,13 +1,13 @@
|
|||
package org.gotson.komga.application.gui
|
||||
|
||||
import androidx.compose.ui.platform.LocalDensity
|
||||
import androidx.compose.ui.res.loadSvgPainter
|
||||
import androidx.compose.ui.window.Tray
|
||||
import androidx.compose.ui.window.application
|
||||
import org.gotson.komga.RB
|
||||
import org.gotson.komga.infrastructure.web.WebServerEffectiveSettings
|
||||
import org.gotson.komga.openExplorer
|
||||
import org.gotson.komga.openUrl
|
||||
import org.jetbrains.compose.resources.decodeToSvgPainter
|
||||
import org.springframework.beans.factory.annotation.Value
|
||||
import org.springframework.boot.ApplicationArguments
|
||||
import org.springframework.boot.ApplicationRunner
|
||||
|
|
@ -21,8 +21,8 @@ import java.io.File
|
|||
@Profile("!test")
|
||||
@Component
|
||||
class TrayIconRunner(
|
||||
@param:Value("#{komgaProperties.configDir}") komgaConfigDir: String,
|
||||
@param:Value($$"${logging.file.name}") logFileName: String,
|
||||
@Value("#{komgaProperties.configDir}") komgaConfigDir: String,
|
||||
@Value("\${logging.file.name}") logFileName: String,
|
||||
serverSettings: WebServerEffectiveSettings,
|
||||
env: Environment,
|
||||
) : ApplicationRunner {
|
||||
|
|
@ -39,7 +39,7 @@ class TrayIconRunner(
|
|||
private fun runTray() {
|
||||
application {
|
||||
Tray(
|
||||
icon = ClassPathResource("icons/$iconFileName").inputStream.readAllBytes().decodeToSvgPainter(LocalDensity.current),
|
||||
icon = loadSvgPainter(ClassPathResource("icons/$iconFileName").inputStream, LocalDensity.current),
|
||||
menu = {
|
||||
Item(RB.getString("menu.open_komga"), onClick = { openUrl(komgaUrl) })
|
||||
Item(RB.getString("menu.show_log"), onClick = { openExplorer(logFile) })
|
||||
|
|
|
|||
|
|
@ -3,10 +3,3 @@ logging:
|
|||
name: ${user.home}/Library/Logs/Komga/komga.log
|
||||
komga:
|
||||
config-dir: ${user.home}/Library/Application Support/Komga
|
||||
kobo.kepubify-path: kepubify
|
||||
spring:
|
||||
config:
|
||||
import:
|
||||
- "optional:file:${komga.config-dir}/application.yml"
|
||||
- "optional:file:${komga.config-dir}/application.yaml"
|
||||
- "optional:file:${komga.config-dir}/application.properties"
|
||||
|
|
|
|||
|
|
@ -1,9 +1,2 @@
|
|||
komga:
|
||||
config-dir: ${LOCALAPPDATA}/Komga
|
||||
kobo.kepubify-path: kepubify.exe
|
||||
spring:
|
||||
config:
|
||||
import:
|
||||
- "optional:file:${komga.config-dir}/application.yml"
|
||||
- "optional:file:${komga.config-dir}/application.yaml"
|
||||
- "optional:file:${komga.config-dir}/application.properties"
|
||||
|
|
|
|||
|
|
@ -1,9 +0,0 @@
|
|||
dialog_error.close=إغلاق
|
||||
dialog_error.copy_clipboard=النسخ إلى الحافظة
|
||||
dialog_error.title=Komga لم يقدر على البدء
|
||||
error_message.port_in_use=الباب {} على قيد الاستعمال.\nربما Komga جارٍ بالفعل.\nتحقق أيقونة علبة النظام أو القائمة لأيقونة Komga.
|
||||
error_message.unexpected=حصل خطأ غير متوقع.
|
||||
menu.open_komga=فتح Komga
|
||||
menu.quit=إغلاق Komga
|
||||
menu.show_conf_dir=فتح موقع الإعدادات
|
||||
menu.show_log=إظهار ملف السجل
|
||||
|
|
@ -1,9 +0,0 @@
|
|||
dialog_error.close=বন্ধ কৰক
|
||||
dialog_error.copy_clipboard=ক্লিপব’ৰ্ডলৈ কপি কৰক
|
||||
dialog_error.title=কমগা আৰম্ভ কৰাত বিফল
|
||||
error_message.port_in_use={} প’ৰ্ট ইতিমধ্যে ব্যৱহৃত।\nকমগা সম্ভৱতঃ ইতিমধ্যে চলি আছে।\nকমগা আইকনৰ বাবে ট্ৰে আইকন বা মেনু বাৰ পৰীক্ষা কৰক।
|
||||
error_message.unexpected=এক অপ্ৰত্যাশিত ত্ৰুটি ঘটিল।
|
||||
menu.open_komga=কমগা খোলক
|
||||
menu.quit=কমগা বন্ধ কৰক
|
||||
menu.show_conf_dir=বিন্যাস ডাইৰেক্টৰি খোলক
|
||||
menu.show_log=লগ ফাইল দেখুৱাওক
|
||||
|
|
@ -1,9 +0,0 @@
|
|||
dialog_error.close=Затвори
|
||||
dialog_error.copy_clipboard=Копирай в клипборда
|
||||
dialog_error.title=Komga не успя да стартира
|
||||
error_message.port_in_use=Порт {} вече се използва.\nKomga сигурно вече работи.\nПровери tray иконите или menu лентата за иконата на Komga.
|
||||
error_message.unexpected=Възникна неочаквана грешка.
|
||||
menu.open_komga=Отвори Komga
|
||||
menu.quit=Изключи Komga
|
||||
menu.show_conf_dir=Отвори конфигурационната директория
|
||||
menu.show_log=Отвори файла с логовете
|
||||
|
|
@ -1,9 +1,9 @@
|
|||
dialog_error.close=Zavřít
|
||||
dialog_error.copy_clipboard=Zkopírovat do schránky
|
||||
dialog_error.title=Aplikace Komga se nepodařila spustit
|
||||
error_message.port_in_use=Port {} je aktuálně používán.\nAplikace Komga je pravděpodobně již spuštěna.\nZkontrolujte, zda ikona Komga není již v systémové liště nebo mezi schovanými ikonami.
|
||||
error_message.unexpected=Nastala neočekávaná chyba.
|
||||
menu.open_komga=Otevřít aplikaci Komga
|
||||
menu.quit=Ukončit aplikaci Komga
|
||||
dialog_error.title=Komga se nepodařila spustit
|
||||
error_message.port_in_use=Port {} je aktuálně používán.\nKomga je pravděpodobně již zapnutá.\nZkontrolujte, zda ikona Komga není již v systémové liště nebo mezi schovanými ikony.
|
||||
error_message.unexpected=Nastal neočekávaný error.
|
||||
menu.open_komga=Otevřít Komga
|
||||
menu.quit=Zavřít Komga
|
||||
menu.show_conf_dir=Otevřít konfigurační složku
|
||||
menu.show_log=Zobrazit logy
|
||||
|
|
|
|||
|
|
@ -1,9 +0,0 @@
|
|||
dialog_error.close=Luk
|
||||
dialog_error.copy_clipboard=Kopier til udklipsholder
|
||||
dialog_error.title=Komga kunne ikke starte
|
||||
error_message.port_in_use=Port {} er allerede i brug.\nKomga kører sandsynligvis allerede.\nTjek systembakken eller menulinjen for Komga-ikonet.
|
||||
error_message.unexpected=Der opstod en uventet fejl.
|
||||
menu.open_komga=Åbn Komga
|
||||
menu.quit=Afslut Komga
|
||||
menu.show_conf_dir=Åbn konfigurationsmappen
|
||||
menu.show_log=Vis logfil
|
||||
|
|
@ -1,9 +0,0 @@
|
|||
dialog_error.close=Pechar
|
||||
dialog_error.copy_clipboard=Copiar ao portapapeis
|
||||
dialog_error.title=Komga fallou ao iniciar
|
||||
error_message.port_in_use=O porto {} está en uso.\nÉ probable que Komga xa esté a executarse.\nBusca a icona de Komga na bandexa de sistema ou a barra de menú.
|
||||
error_message.unexpected=Aconteceu un erro imprevisto.
|
||||
menu.open_komga=Abrir Komga
|
||||
menu.quit=Saír de Komga
|
||||
menu.show_conf_dir=Abrir o cartafol de configuración
|
||||
menu.show_log=Amosar o ficheiro de rexistro
|
||||
|
|
@ -1,9 +0,0 @@
|
|||
dialog_error.close=Zatvori
|
||||
dialog_error.copy_clipboard=Kopiraj u međuspremnik
|
||||
dialog_error.title=Komga se nije mogao pokrenuti
|
||||
error_message.port_in_use=Priključak {} se već koristi.\nKomga vjerojatno već radi.\nPotraži Komga ikonu u traci ikona ili traci izbornika.
|
||||
error_message.unexpected=Dogodila se neočekivana greška.
|
||||
menu.open_komga=Otvori Komga
|
||||
menu.quit=Zatvori Komga
|
||||
menu.show_conf_dir=Otvori mapu konfiguracije
|
||||
menu.show_log=Prikaži log-datoteku
|
||||
|
|
@ -1,9 +0,0 @@
|
|||
dialog_error.close=Bezárás
|
||||
dialog_error.copy_clipboard=Másolás a vágólapra
|
||||
dialog_error.title=A Komga elindulása meghiúsult
|
||||
error_message.port_in_use=A Port {} már használatban van.\nValószínűleg már fut a Komga.\nKeresd a Komga ikont a tálcán, vagy a menüsávon.
|
||||
error_message.unexpected=Váratlan hiba történt.
|
||||
menu.open_komga=Komga megnyitása
|
||||
menu.quit=Komga bezárása
|
||||
menu.show_conf_dir=Konfigurációs könyvtár megnyitása
|
||||
menu.show_log=Eseménynapló fájl megjelenítése
|
||||
|
|
@ -1,9 +0,0 @@
|
|||
dialog_error.close=Tutup
|
||||
dialog_error.copy_clipboard=Salin ke papan klip
|
||||
dialog_error.title=Komga gagal dimuat
|
||||
error_message.port_in_use=Port {} telah digunakan.\nKomga telah berjalan.\nCari ikon Komga di baki ikon atau menu bar.
|
||||
error_message.unexpected=Terjadi galat tak terduga.
|
||||
menu.open_komga=Buka Komga
|
||||
menu.quit=Tutup komga
|
||||
menu.show_conf_dir=Buka direktori pengaturan
|
||||
menu.show_log=Buka berkas catatan
|
||||
|
|
@ -1,9 +0,0 @@
|
|||
dialog_error.close=Zamknij
|
||||
dialog_error.copy_clipboard=Skopiuj do schowka
|
||||
dialog_error.title=Nie udało się uruchomić Komgi
|
||||
error_message.port_in_use=Port {} jest już używany.\nNajprawdopodobniej Komga jest już uruchomiona.\nSprawdź czy w zasobniku jest ikona Komgi.
|
||||
error_message.unexpected=Napotkano nieoczekiwany błąd.
|
||||
menu.open_komga=Otwórz Komgę
|
||||
menu.quit=Wyjdź z Komgi
|
||||
menu.show_conf_dir=Otwórz katalog z konfiguracją
|
||||
menu.show_log=Pokaż dziennik
|
||||
|
|
@ -1,9 +0,0 @@
|
|||
dialog_error.close=Fechar
|
||||
dialog_error.copy_clipboard=Copiar para área de transferência
|
||||
dialog_error.title=Falha no arranque do Konga
|
||||
error_message.port_in_use=A porta {} já está em utilização.\nO Komga provavelmente já está em execução.\nVerifique o ícone do Komga na barra de tarefas/barra de menus.
|
||||
error_message.unexpected=Ocorreu um erro inesperado.
|
||||
menu.open_komga=Abrir Komga
|
||||
menu.quit=Fechar Komga
|
||||
menu.show_conf_dir=Abrir pasta de configuração
|
||||
menu.show_log=Mostrar ficheiro de historial
|
||||
|
|
@ -1,9 +0,0 @@
|
|||
dialog_error.close=Fechar
|
||||
dialog_error.copy_clipboard=Copiar para área de transferência
|
||||
dialog_error.title=Komga falhou ao inicializar
|
||||
error_message.port_in_use=A porta {} já está em uso.\nProvavelmente o Komga já está rodando.\nVerifique o ícone na barra de tarefas ou a barra de menu.
|
||||
error_message.unexpected=Ocorreu um erro inesperado.
|
||||
menu.open_komga=Abrir Komga
|
||||
menu.quit=Sair do Komga
|
||||
menu.show_conf_dir=Abrir configuração de diretório
|
||||
menu.show_log=Mostrar arquivo de logs
|
||||
|
|
@ -1,7 +1,7 @@
|
|||
dialog_error.close=Закрыть
|
||||
dialog_error.copy_clipboard=Скопировать в буфер обмена
|
||||
dialog_error.title=Komga не удалось запустить
|
||||
error_message.port_in_use=Порт {} уже используется.\nВероятно, Komga уже запущен.\nПроверьте область уведомлений или панель меню на наличие иконки Komga.
|
||||
dialog_error.title=Komga не смог запуститься
|
||||
error_message.port_in_use=Порт {] уже используется.\nKomga возможно уже работает.\nПроверьте панель задач или меню на наличие иконки Komga.
|
||||
error_message.unexpected=Произошла непредвиденная ошибка.
|
||||
menu.open_komga=Открыть Komga
|
||||
menu.quit=Закрыть Komga
|
||||
|
|
|
|||
|
|
@ -1,9 +0,0 @@
|
|||
dialog_error.close=Zavrieť
|
||||
dialog_error.copy_clipboard=Skopírovať do schránky
|
||||
dialog_error.title=Aplikáciu Komga sa nepodarilo spustiť
|
||||
error_message.port_in_use=Port {} sa aktuálne používa.\nAplikácia Komga je pravdepodobne už spustená.\nSkontrolujte, či ikona Komga už nie je v systémovej lište alebo medzi schovanými ikonami.
|
||||
error_message.unexpected=Nastala neočakávaná chyba.
|
||||
menu.open_komga=Otvoriť aplikáciu Komga
|
||||
menu.quit=Zavrieť aplikáciu Komga
|
||||
menu.show_conf_dir=Otvoriť konfiguračný priečinok
|
||||
menu.show_log=Zobraziť logy
|
||||
|
|
@ -1,9 +0,0 @@
|
|||
dialog_error.close=மூடு
|
||||
dialog_error.copy_clipboard=இடைநிலைப்பலகைக்கு நகலெடுக்கவும்
|
||||
dialog_error.title=கோம்கா தொடங்கத் தவறிவிட்டார்
|
||||
error_message.port_in_use=துறைமுகம் {} ஏற்கனவே பயன்பாட்டில் உள்ளது.\n கோம்கா ஏற்கனவே இயங்குகிறது.\n கோம்கா ஐகானுக்கான தட்டு படவுரு அல்லது பட்டியல் பட்டியை சரிபார்க்கவும்.
|
||||
error_message.unexpected=எதிர்பாராத பிழை ஏற்பட்டது.
|
||||
menu.open_komga=திறந்த கொம்கா
|
||||
menu.quit=கொங்காவை விட்டு வெளியேறவும்
|
||||
menu.show_conf_dir=திறந்த உள்ளமைவு அடைவு
|
||||
menu.show_log=பதிவு கோப்பைக் காட்டு
|
||||
|
|
@ -1,9 +0,0 @@
|
|||
dialog_error.close=ปิด
|
||||
dialog_error.copy_clipboard=คัดลอกไปยังคลิปบอร์ด
|
||||
dialog_error.title=Komga ไม่สามารถเริ่มต้นได้
|
||||
error_message.port_in_use=พอร์ต {} ถูกใช้งานอยู่แล้ว\nKomga อาจกำลังทำงานอยู่แล้ว\nตรวจสอบ ไอคอน Komga บนถาดไอคอนหรือแถบเมนู
|
||||
error_message.unexpected=เกิดข้อผิดพลาดที่ไม่คาดคิด
|
||||
menu.open_komga=เปิด Komga
|
||||
menu.quit=ออก Komga
|
||||
menu.show_conf_dir=เปิดไดเรกทอรีการกำหนดค่า
|
||||
menu.show_log=แสดงไฟล์บันทึก
|
||||
|
|
@ -1,9 +0,0 @@
|
|||
dialog_error.close=Закрити
|
||||
dialog_error.copy_clipboard=Копіювати в буфер обміну
|
||||
dialog_error.title=Помилка при запуску Komga
|
||||
error_message.port_in_use=Порт {} вже використовується.\nKomga напевно вже запущена.\nПеревірте панель запущених програм на наявність іконки Komga.
|
||||
error_message.unexpected=Сталася невідома помилка.
|
||||
menu.open_komga=Відкрити Komga
|
||||
menu.quit=Закрити Komga
|
||||
menu.show_conf_dir=Відкрити теку з налаштуваннями
|
||||
menu.show_log=Відкрити log журнал
|
||||
3664
komga-webui/package-lock.json
generated
3664
komga-webui/package-lock.json
generated
File diff suppressed because it is too large
Load diff
|
|
@ -10,17 +10,16 @@
|
|||
},
|
||||
"dependencies": {
|
||||
"@d-i-t-a/reader": "github:gotson/R2D2BC#fork",
|
||||
"@w0s/isbn-verify": "^3.1.2",
|
||||
"axios": "^1.15.0",
|
||||
"@saekitominaga/isbn-verify": "^2.0.1",
|
||||
"axios": "^1.6.0",
|
||||
"chart.js": "^2.9.4",
|
||||
"core-js": "^3.8.3",
|
||||
"date-fns": "^2.30.0",
|
||||
"filesize": "^10.0.12",
|
||||
"js-file-downloader": "^1.1.25",
|
||||
"language-tags": "^1.0.9",
|
||||
"lodash": "^4.18.1",
|
||||
"marked": "^15.0.4",
|
||||
"qs": "^6.14.2",
|
||||
"lodash": "^4.17.21",
|
||||
"qs": "^6.11.2",
|
||||
"screenfull": "^5.2.0",
|
||||
"vue": "^2.6.14",
|
||||
"vue-chartkick": "^0.6.1",
|
||||
|
|
|
|||
|
|
@ -1,182 +0,0 @@
|
|||
<template>
|
||||
<div style="position: relative">
|
||||
<div v-if="apiKeys">
|
||||
<div v-if="apiKeys.length > 0">
|
||||
<v-list elevation="3"
|
||||
three-line
|
||||
>
|
||||
<div v-for="(apiKey, index) in apiKeys" :key="apiKey.id">
|
||||
<v-list-item>
|
||||
<v-list-item-content>
|
||||
<v-list-item-title>{{ apiKey.comment }}</v-list-item-title>
|
||||
<v-list-item-subtitle>
|
||||
{{
|
||||
$t('account_settings.api_key.created_date', {
|
||||
date:
|
||||
new Intl.DateTimeFormat($i18n.locale, {
|
||||
dateStyle: 'medium',
|
||||
timeStyle: 'short'
|
||||
}).format(apiKey.createdDate)
|
||||
})
|
||||
}}
|
||||
</v-list-item-subtitle>
|
||||
<v-list-item-subtitle v-if="apiKeyLastActivity[apiKey.id] !== undefined">
|
||||
{{
|
||||
$t('settings_user.latest_activity', {
|
||||
date:
|
||||
new Intl.DateTimeFormat($i18n.locale, {
|
||||
dateStyle: 'medium',
|
||||
timeStyle: 'short'
|
||||
}).format(apiKeyLastActivity[apiKey.id])
|
||||
})
|
||||
}}
|
||||
</v-list-item-subtitle>
|
||||
<v-list-item-subtitle v-else>{{ $t('settings_user.no_recent_activity') }}</v-list-item-subtitle>
|
||||
</v-list-item-content>
|
||||
|
||||
<v-list-item-action>
|
||||
<v-tooltip bottom>
|
||||
<template v-slot:activator="{ on }">
|
||||
<v-btn icon @click="promptSyncPointDelete(apiKey)" v-on="on">
|
||||
<v-icon>mdi-book-refresh</v-icon>
|
||||
</v-btn>
|
||||
</template>
|
||||
<span>{{ $t('account_settings.api_key.force_kobo_sync') }}</span>
|
||||
</v-tooltip>
|
||||
</v-list-item-action>
|
||||
|
||||
<v-list-item-action>
|
||||
<v-btn icon @click="promptDeleteApiKey(apiKey)">
|
||||
<v-icon>mdi-delete</v-icon>
|
||||
</v-btn>
|
||||
</v-list-item-action>
|
||||
</v-list-item>
|
||||
|
||||
<v-divider v-if="index !== apiKeys.length-1"/>
|
||||
</div>
|
||||
</v-list>
|
||||
|
||||
<v-btn fab absolute bottom color="primary"
|
||||
:right="!$vuetify.rtl"
|
||||
:left="$vuetify.rtl"
|
||||
class="mx-6"
|
||||
small
|
||||
@click="generateApiKey"
|
||||
>
|
||||
<v-icon>mdi-plus</v-icon>
|
||||
</v-btn>
|
||||
</div>
|
||||
|
||||
<div v-else>
|
||||
<v-container fluid class="pa-0">
|
||||
<v-row>
|
||||
<v-col>{{ $t('account_settings.api_key.no_keys') }}</v-col>
|
||||
</v-row>
|
||||
<v-row>
|
||||
<v-col>
|
||||
<v-btn color="primary" @click="generateApiKey">{{
|
||||
$t('account_settings.api_key.generate_api_key')
|
||||
}}
|
||||
</v-btn>
|
||||
</v-col>
|
||||
|
||||
</v-row>
|
||||
|
||||
</v-container>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<confirmation-dialog
|
||||
v-model="modalDeleteSyncPoints"
|
||||
:title="$t('dialog.force_kobo_sync.dialog_title')"
|
||||
:body-html="$t('dialog.force_kobo_sync.warning_html')"
|
||||
:button-confirm="$t('common.i_understand')"
|
||||
button-confirm-color="warning"
|
||||
@confirm="deleteSyncPoint"
|
||||
/>
|
||||
|
||||
<confirmation-dialog
|
||||
v-model="modalDeleteApiKey"
|
||||
:title="$t('dialog.delete_apikey.dialog_title')"
|
||||
:body-html="$t('dialog.delete_apikey.warning_html')"
|
||||
:confirm-text=" $t('dialog.delete_apikey.confirm_delete', {name: apiKeyToDelete.comment})"
|
||||
:button-confirm="$t('dialog.delete_apikey.button_confirm')"
|
||||
button-confirm-color="error"
|
||||
@confirm="deleteApiKey"
|
||||
/>
|
||||
|
||||
<api-key-add-dialog
|
||||
v-model="modalGenerateApiKey"
|
||||
@generate="loadApiKeys"
|
||||
/>
|
||||
</div>
|
||||
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import Vue from 'vue'
|
||||
import {ApiKeyDto} from '@/types/komga-users'
|
||||
import {ERROR} from '@/types/events'
|
||||
import ConfirmationDialog from '@/components/dialogs/ConfirmationDialog.vue'
|
||||
import ApiKeyAddDialog from '@/components/dialogs/ApiKeyAddDialog.vue'
|
||||
|
||||
export default Vue.extend({
|
||||
name: 'ApiKeyTable',
|
||||
components: {ApiKeyAddDialog, ConfirmationDialog},
|
||||
data: () => {
|
||||
return {
|
||||
apiKeys: undefined as ApiKeyDto[],
|
||||
apiKeyToDelete: {} as ApiKeyDto,
|
||||
apiKeySyncPointsToDelete: {} as ApiKeyDto,
|
||||
modalDeleteApiKey: false,
|
||||
modalDeleteSyncPoints: false,
|
||||
modalGenerateApiKey: false,
|
||||
apiKeyLastActivity: {} as any,
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
this.loadApiKeys()
|
||||
},
|
||||
methods: {
|
||||
async loadApiKeys() {
|
||||
try {
|
||||
this.apiKeys = await this.$komgaUsers.getApiKeys()
|
||||
this.apiKeys.forEach((a: ApiKeyDto) => {
|
||||
this.$komgaUsers.getLatestAuthenticationActivityForUser(a.userId, a.id)
|
||||
.then(value => this.$set(this.apiKeyLastActivity, `${a.id}`, value.dateTime))
|
||||
.catch(e => {
|
||||
})
|
||||
})
|
||||
} catch (e) {
|
||||
this.$eventHub.$emit(ERROR, {message: e.message} as ErrorEvent)
|
||||
}
|
||||
},
|
||||
promptDeleteApiKey(apiKey: ApiKeyDto) {
|
||||
this.apiKeyToDelete = apiKey
|
||||
this.modalDeleteApiKey = true
|
||||
},
|
||||
promptSyncPointDelete(apiKey: ApiKeyDto) {
|
||||
this.apiKeySyncPointsToDelete = apiKey
|
||||
this.modalDeleteSyncPoints = true
|
||||
},
|
||||
async deleteSyncPoint() {
|
||||
try {
|
||||
await this.$komgaSyncPoints.deleteMySyncPointsByApiKey(this.apiKeySyncPointsToDelete.id)
|
||||
} catch (e) {
|
||||
this.$eventHub.$emit(ERROR, {message: e.message} as ErrorEvent)
|
||||
}
|
||||
},
|
||||
async deleteApiKey() {
|
||||
try {
|
||||
await this.$komgaUsers.deleteApiKey(this.apiKeyToDelete.id)
|
||||
await this.loadApiKeys()
|
||||
} catch (e) {
|
||||
this.$eventHub.$emit(ERROR, {message: e.message} as ErrorEvent)
|
||||
}
|
||||
},
|
||||
generateApiKey() {
|
||||
this.modalGenerateApiKey = true
|
||||
},
|
||||
},
|
||||
})
|
||||
</script>
|
||||
|
|
@ -33,7 +33,7 @@
|
|||
<script lang="ts">
|
||||
import Vue from 'vue'
|
||||
import {ERROR} from '@/types/events'
|
||||
import {AuthenticationActivityDto} from '@/types/komga-users'
|
||||
import { AuthenticationActivityDto } from '@/types/komga-users'
|
||||
|
||||
export default Vue.extend({
|
||||
name: 'AuthenticationActivityTable',
|
||||
|
|
@ -68,7 +68,6 @@ export default Vue.extend({
|
|||
{text: this.$t('authentication_activity.user_agent').toString(), value: 'userAgent'},
|
||||
{text: this.$t('authentication_activity.success').toString(), value: 'success'},
|
||||
{text: this.$t('authentication_activity.source').toString(), value: 'source'},
|
||||
{text: this.$t('authentication_activity.api_key').toString(), value: 'apiKeyComment'},
|
||||
{text: this.$t('authentication_activity.error').toString(), value: 'error'},
|
||||
{text: this.$t('authentication_activity.datetime').toString(), value: 'dateTime', groupable: false},
|
||||
)
|
||||
|
|
|
|||
|
|
@ -10,7 +10,6 @@
|
|||
@scroll-changed="(percent) => scrollChanged(collectionsLoaders[index], percent)"
|
||||
>
|
||||
<template v-slot:prepend>
|
||||
<slot name="prepend" v-bind:collection="c"/>
|
||||
<router-link class="text-overline"
|
||||
:to="{name: 'browse-collection', params: {collectionId: c.id}}"
|
||||
>{{ $t('collections_expansion_panel.manage_collection') }}
|
||||
|
|
|
|||
|
|
@ -31,19 +31,17 @@
|
|||
<template v-else>
|
||||
<div style="height: 2em" class="missing"></div>
|
||||
</template>
|
||||
<series-picker-dialog v-model="modalSeriesPicker" @update:series="pickedSeries" :include-oneshots="true"/>
|
||||
<series-picker-dialog v-model="modalSeriesPicker" @update:series="pickedSeries" :include-oneshots="false"/>
|
||||
</td>
|
||||
|
||||
<!-- Book number chooser -->
|
||||
<td>
|
||||
<v-text-field v-if="!selectedSeries?.oneshot"
|
||||
v-model.number="bookNumber"
|
||||
<v-text-field v-model.number="bookNumber"
|
||||
type="number"
|
||||
step="0.1"
|
||||
dense
|
||||
:disabled="!selectedSeries"
|
||||
/>
|
||||
<span v-else>{{ $t('common.oneshot') }}</span>
|
||||
</td>
|
||||
|
||||
<!-- Book details -->
|
||||
|
|
@ -131,8 +129,7 @@ import TransientBookViewerDialog from '@/components/dialogs/TransientBookViewerD
|
|||
import {bookPageUrl, transientBookPageUrl} from '@/functions/urls'
|
||||
import {convertErrorCodes} from '@/functions/error-codes'
|
||||
import FileNameChooserDialog from '@/components/dialogs/FileNameChooserDialog.vue'
|
||||
import {SeriesSelected} from '@/types/series-slim'
|
||||
import {BookSearch, SearchConditionSeriesId, SearchOperatorIs} from '@/types/komga-search'
|
||||
import {ReadListRequestBookMatchSeriesDto} from '@/types/komga-readlists'
|
||||
|
||||
export default Vue.extend({
|
||||
name: 'FileImportRow',
|
||||
|
|
@ -170,7 +167,6 @@ export default Vue.extend({
|
|||
if (val) this.selectedSeries = {
|
||||
seriesId: val.id,
|
||||
title: val.metadata.title,
|
||||
oneshot: val.oneshot,
|
||||
}
|
||||
},
|
||||
immediate: true,
|
||||
|
|
@ -193,7 +189,7 @@ export default Vue.extend({
|
|||
convertErrorCodes,
|
||||
innerSelect: false,
|
||||
bookAnalyzed: undefined as unknown as TransientBookDto,
|
||||
selectedSeries: undefined as SeriesSelected | undefined,
|
||||
selectedSeries: undefined as ReadListRequestBookMatchSeriesDto | undefined,
|
||||
seriesBooks: [] as BookDto[],
|
||||
bookToUpgrade: undefined as BookDto | undefined,
|
||||
bookToUpgradePages: [] as PageDto[],
|
||||
|
|
@ -257,18 +253,12 @@ export default Vue.extend({
|
|||
this.selectedSeries = {
|
||||
seriesId: seriesDto.id,
|
||||
title: seriesDto.metadata.title,
|
||||
oneshot: seriesDto.oneshot,
|
||||
}
|
||||
}
|
||||
},
|
||||
async getSeriesBooks(series: SeriesSelected) {
|
||||
async getSeriesBooks(series: ReadListRequestBookMatchSeriesDto) {
|
||||
if (series) {
|
||||
this.seriesBooks = (await this.$komgaBooks.getBooksList({
|
||||
condition: new SearchConditionSeriesId(new SearchOperatorIs(series.seriesId)),
|
||||
} as BookSearch, {unpaged: true, sort: 'metadata.numberSort'})).content
|
||||
if (series.oneshot) {
|
||||
this.bookNumber = this.seriesBooks[0].metadata.numberSort
|
||||
}
|
||||
this.seriesBooks = (await this.$komgaSeries.getBooks(series.seriesId, {unpaged: true})).content
|
||||
this.checkForUpgrade(this.bookNumber)
|
||||
}
|
||||
},
|
||||
|
|
@ -281,7 +271,6 @@ export default Vue.extend({
|
|||
this.selectedSeries = {
|
||||
seriesId: series.id,
|
||||
title: series.metadata.title,
|
||||
oneshot: series.oneshot,
|
||||
}
|
||||
},
|
||||
},
|
||||
|
|
|
|||
|
|
@ -5,14 +5,14 @@
|
|||
>
|
||||
<v-subheader v-if="f.name">{{ f.name }}</v-subheader>
|
||||
<v-list-item v-for="v in f.values"
|
||||
:key="JSON.stringify(v.value)"
|
||||
:key="v.value"
|
||||
@click.stop="click(key, v.value, v.nValue)"
|
||||
>
|
||||
<v-list-item-icon>
|
||||
<v-icon v-if="key in filtersActive && includes(filtersActive[key], v.nValue)" color="secondary">
|
||||
<v-icon v-if="key in filtersActive && filtersActive[key].includes(v.nValue)" color="secondary">
|
||||
mdi-minus-box
|
||||
</v-icon>
|
||||
<v-icon v-else-if="key in filtersActive && includes(filtersActive[key], v.value)" color="secondary">
|
||||
<v-icon v-else-if="key in filtersActive && filtersActive[key].includes(v.value)" color="secondary">
|
||||
mdi-checkbox-marked
|
||||
</v-icon>
|
||||
<v-icon v-else>
|
||||
|
|
@ -27,7 +27,6 @@
|
|||
|
||||
<script lang="ts">
|
||||
import Vue from 'vue'
|
||||
import {FiltersActive, FiltersOptions} from '@/types/filter'
|
||||
|
||||
export default Vue.extend({
|
||||
name: 'FilterList',
|
||||
|
|
@ -42,24 +41,16 @@ export default Vue.extend({
|
|||
},
|
||||
},
|
||||
methods: {
|
||||
includes(array: any[], value: any): boolean {
|
||||
return this.$_.isObject(value) ? this.$_.some(array, value) : this.$_.includes(array, value)
|
||||
},
|
||||
click(key: string, value: any, nValue?: any) {
|
||||
click(key: string, value: string, nValue?: string) {
|
||||
let r = this.$_.cloneDeep(this.filtersActive)
|
||||
if (!(key in r)) r[key] = []
|
||||
|
||||
const pull = this.$_.isObject(value) ? this.$_.remove : this.$_.pull
|
||||
const includes = this.$_.isObject(value) ? this.$_.some : this.$_.includes
|
||||
|
||||
if (nValue && includes(r[key], nValue))
|
||||
pull(r[key], nValue)
|
||||
else if (includes(r[key], value)) {
|
||||
pull(r[key], value)
|
||||
if (nValue && r[key].includes(nValue))
|
||||
this.$_.pull(r[key], (nValue))
|
||||
else if (r[key].includes(value)) {
|
||||
this.$_.pull(r[key], (value))
|
||||
if (nValue)
|
||||
r[key].push(nValue)
|
||||
} else
|
||||
r[key].push(value)
|
||||
} else r[key].push(value)
|
||||
|
||||
this.$emit('update:filtersActive', r)
|
||||
},
|
||||
|
|
|
|||
|
|
@ -5,18 +5,11 @@
|
|||
:key="key"
|
||||
:disabled="(f.values && f.values.length === 0) && !f.search"
|
||||
>
|
||||
<v-expansion-panel-header class="text-uppercase ps-1">
|
||||
<v-expansion-panel-header class="text-uppercase">
|
||||
<v-icon
|
||||
color="secondary"
|
||||
style="max-width: 24px"
|
||||
class="mx-0"
|
||||
@click.stop="clickFilterMode(key, false)"
|
||||
>{{ groupAllOfActive(key) ? 'mdi-filter-multiple' : '' }}
|
||||
</v-icon>
|
||||
<v-icon
|
||||
color="secondary"
|
||||
style="max-width: 24px"
|
||||
class="me-2"
|
||||
class="mx-2"
|
||||
@click.stop="clear(key)"
|
||||
>{{ groupActive(key) ? 'mdi-checkbox-marked' : '' }}
|
||||
</v-icon>
|
||||
|
|
@ -33,35 +26,11 @@
|
|||
</template>
|
||||
</search-box-base>
|
||||
|
||||
<div style="position: absolute; right: 0; z-index: 1">
|
||||
<v-btn-toggle v-if="f.anyAllSelector || groupAllOfActive(key)" mandatory class="semi-transparent"
|
||||
:value="filtersActiveMode[key]?.allOf">
|
||||
<v-tooltip bottom>
|
||||
<template v-slot:activator="{ on }">
|
||||
<v-btn small icon :value="false" v-on="on" @click.stop="clickFilterMode(key, false)">
|
||||
<v-icon small>mdi-filter-outline</v-icon>
|
||||
</v-btn>
|
||||
</template>
|
||||
<span>{{ $t('common.any_of') }}</span>
|
||||
</v-tooltip>
|
||||
|
||||
<v-tooltip bottom>
|
||||
<template v-slot:activator="{ on }">
|
||||
<v-btn small icon :value="true" v-on="on" @click.stop="clickFilterMode(key, true)">
|
||||
<v-icon small>mdi-filter-multiple-outline</v-icon>
|
||||
</v-btn>
|
||||
</template>
|
||||
<span>{{ $t('common.all_of') }}</span>
|
||||
</v-tooltip>
|
||||
</v-btn-toggle>
|
||||
</div>
|
||||
|
||||
<v-list
|
||||
v-if="f.search || f.values"
|
||||
v-if="f.search"
|
||||
dense
|
||||
>
|
||||
<!-- Dynamic content from search -->
|
||||
<v-list-item v-for="(v, i) in searchFiltersActive(key)"
|
||||
<v-list-item v-for="(v, i) in filtersActive[key]"
|
||||
:key="i"
|
||||
@click.stop="click(key, v)"
|
||||
>
|
||||
|
|
@ -70,17 +39,18 @@
|
|||
</v-list-item-icon>
|
||||
<v-list-item-title>{{ v }}</v-list-item-title>
|
||||
</v-list-item>
|
||||
</v-list>
|
||||
|
||||
<!-- Static content from filters options -->
|
||||
<v-list
|
||||
v-if="f.values"
|
||||
dense
|
||||
>
|
||||
<v-list-item v-for="v in f.values"
|
||||
:key="JSON.stringify(v.value)"
|
||||
@click.stop="click(key, v.value, v.nValue)"
|
||||
:key="v.value"
|
||||
@click.stop="click(key, v.value)"
|
||||
>
|
||||
<v-list-item-icon>
|
||||
<v-icon v-if="key in filtersActive && includes(filtersActive[key], v.nValue)" color="secondary">
|
||||
mdi-minus-box
|
||||
</v-icon>
|
||||
<v-icon v-else-if="key in filtersActive && includes(filtersActive[key], v.value)" color="secondary">
|
||||
<v-icon v-if="key in filtersActive && filtersActive[key].includes(v.value)" color="secondary">
|
||||
mdi-checkbox-marked
|
||||
</v-icon>
|
||||
<v-icon v-else>
|
||||
|
|
@ -98,7 +68,6 @@
|
|||
<script lang="ts">
|
||||
import Vue, {PropType} from 'vue'
|
||||
import SearchBoxBase from '@/components/SearchBoxBase.vue'
|
||||
import {FiltersActive, FiltersActiveMode, FiltersOptions} from '@/types/filter'
|
||||
|
||||
export default Vue.extend({
|
||||
name: 'FilterPanels',
|
||||
|
|
@ -112,25 +81,11 @@ export default Vue.extend({
|
|||
type: Object as PropType<FiltersActive>,
|
||||
required: true,
|
||||
},
|
||||
filtersActiveMode: {
|
||||
type: Object as PropType<FiltersActiveMode>,
|
||||
required: false,
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
// filtersActive, filtered to not show options that are in filtersOptions
|
||||
searchFiltersActive(key: string): FiltersActive[] {
|
||||
if (!(key in this.filtersActive)) return []
|
||||
const listedOptions = this.filtersOptions[key]?.values?.flatMap(x => [x.value, x.nValue]).map(x => JSON.stringify(x))
|
||||
return this.filtersActive[key].filter((x: string) => !this.$_.includes(listedOptions, JSON.stringify(x)))
|
||||
},
|
||||
includes(array: any[], value: any): boolean {
|
||||
return this.$_.isObject(value) ? this.$_.some(array, value) : this.$_.includes(array, value)
|
||||
},
|
||||
clear(key: string) {
|
||||
let r = this.$_.cloneDeep(this.filtersActive)
|
||||
r[key] = []
|
||||
if (!this.filtersOptions[key].anyAllSelector) this.clickFilterMode(key, false)
|
||||
|
||||
this.$emit('update:filtersActive', r)
|
||||
},
|
||||
|
|
@ -138,39 +93,14 @@ export default Vue.extend({
|
|||
if (!(key in this.filtersActive)) return false
|
||||
return this.filtersActive[key].length > 0
|
||||
},
|
||||
groupAllOfActive(key: string): boolean {
|
||||
if (!this.filtersActiveMode || !(key in this.filtersActiveMode)) return false
|
||||
return this.filtersActiveMode[key].allOf
|
||||
},
|
||||
click(key: string, value: any, nValue?: any) {
|
||||
click(key: string, value: string) {
|
||||
let r = this.$_.cloneDeep(this.filtersActive)
|
||||
if (!(key in r)) r[key] = []
|
||||
|
||||
const pull = this.$_.isObject(value) ? this.$_.remove : this.$_.pull
|
||||
const includes = this.$_.isObject(value) ? this.$_.some : this.$_.includes
|
||||
|
||||
if (nValue && includes(r[key], nValue))
|
||||
pull(r[key], nValue)
|
||||
else if (includes(r[key], value)) {
|
||||
pull(r[key], value)
|
||||
if (nValue) {
|
||||
r[key].push(nValue)
|
||||
this.clickFilterMode(key, true)
|
||||
}
|
||||
} else
|
||||
r[key].push(value)
|
||||
|
||||
if (!this.filtersOptions[key].anyAllSelector && r[key].length == 0) this.clickFilterMode(key, false)
|
||||
if (r[key].includes(value)) this.$_.pull(r[key], (value))
|
||||
else r[key].push(value)
|
||||
|
||||
this.$emit('update:filtersActive', r)
|
||||
},
|
||||
clickFilterMode(key: string, value: boolean) {
|
||||
if (!this.filtersActiveMode) return
|
||||
let r = this.$_.cloneDeep(this.filtersActiveMode)
|
||||
r[key] = {allOf: value}
|
||||
|
||||
this.$emit('update:filtersActiveMode', r)
|
||||
},
|
||||
},
|
||||
})
|
||||
</script>
|
||||
|
|
@ -179,12 +109,4 @@ export default Vue.extend({
|
|||
.no-padding .v-expansion-panel-content__wrap {
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.semi-transparent {
|
||||
opacity: 0.5;
|
||||
}
|
||||
|
||||
.semi-transparent:hover {
|
||||
opacity: 1;
|
||||
}
|
||||
</style>
|
||||
|
|
|
|||
|
|
@ -8,13 +8,9 @@
|
|||
:class="flexClass"
|
||||
handle=".handle"
|
||||
v-bind="dragOptions"
|
||||
:forceFallback="true"
|
||||
:scroll-sensitivity="200"
|
||||
@start="transitions = false"
|
||||
@end="transitions = true"
|
||||
>
|
||||
<transition-group type="transition"
|
||||
:name="transitions ? 'flip-list' : null"
|
||||
:name="!draggable ? 'flip-list' : null"
|
||||
:class="flexClass"
|
||||
>
|
||||
<v-item
|
||||
|
|
@ -37,24 +33,9 @@
|
|||
:preselect="shouldPreselect"
|
||||
:onEdit="(draggable || deletable) ? undefined : editFunction"
|
||||
:onSelected="(draggable || deletable) ? undefined : selectable ? (item, event) => handleSelectClick(toggle, item, event): undefined"
|
||||
:action-menu="(draggable || deletable) ? false : actionMenu"
|
||||
:disable-fab="draggable || deletable"
|
||||
:action-menu="actionMenu"
|
||||
></item-card>
|
||||
|
||||
<v-slide-y-reverse-transition>
|
||||
<v-text-field v-if="draggable"
|
||||
v-model="localItemsIndex[JSON.stringify(item)]"
|
||||
type="number"
|
||||
min="0"
|
||||
:max="localItems.length - 1"
|
||||
solo
|
||||
style="position: absolute; top: 0; left: 0;"
|
||||
ref=""
|
||||
@blur="updateIndex(item)"
|
||||
@keydown.enter="updateIndex(item)"
|
||||
/>
|
||||
</v-slide-y-reverse-transition>
|
||||
|
||||
<v-slide-y-reverse-transition>
|
||||
<v-icon v-if="draggable"
|
||||
class="handle"
|
||||
|
|
@ -148,12 +129,10 @@ export default Vue.extend({
|
|||
data: () => {
|
||||
return {
|
||||
selectedItems: [] as any[],
|
||||
localItems: [] as any[],
|
||||
localItemsIndex: {} as Record<string, any>,
|
||||
localItems: [],
|
||||
lastClickedNoShift: undefined as any,
|
||||
lastClickedShift: undefined as any,
|
||||
width: 150,
|
||||
transitions: true,
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
|
|
@ -172,10 +151,6 @@ export default Vue.extend({
|
|||
items: {
|
||||
handler() {
|
||||
this.localItems = this.items as []
|
||||
this.localItemsIndex = {}
|
||||
for (const [i, value] of this.localItems.entries()) {
|
||||
this.$set(this.localItemsIndex, JSON.stringify(value), i)
|
||||
}
|
||||
},
|
||||
immediate: true,
|
||||
},
|
||||
|
|
@ -249,12 +224,6 @@ export default Vue.extend({
|
|||
const index = this.localItems.findIndex((e: any) => e.id === item.id)
|
||||
this.localItems.splice(index, 1)
|
||||
},
|
||||
updateIndex(item: any) {
|
||||
const oldIndex = this.localItems.indexOf(item)
|
||||
const newIndex = Math.min(Math.max(this.localItemsIndex[JSON.stringify(item)], 0), this.localItems.length - 1)
|
||||
if (oldIndex != newIndex)
|
||||
this.localItems.splice(oldIndex, 1, this.localItems.splice(newIndex, 1, this.localItems[oldIndex])[0])
|
||||
},
|
||||
},
|
||||
})
|
||||
</script>
|
||||
|
|
|
|||
|
|
@ -12,9 +12,7 @@
|
|||
:src="thumbnailUrl"
|
||||
:lazy-src="thumbnailError ? coverBase64 : undefined"
|
||||
aspect-ratio="0.7071"
|
||||
:contain="!isStretch"
|
||||
:position="isStretch ? 'top' : undefined"
|
||||
:class="shouldBlurPoster ? 'blur' : undefined"
|
||||
contain
|
||||
@error="thumbnailError = true"
|
||||
@load="thumbnailError = false"
|
||||
>
|
||||
|
|
@ -50,7 +48,7 @@
|
|||
|
||||
<!-- FAB reading (center) -->
|
||||
<v-btn
|
||||
v-if="showFab"
|
||||
v-if="bookReady && !selected && !preselect && canReadPages"
|
||||
fab
|
||||
x-large
|
||||
color="accent"
|
||||
|
|
@ -177,7 +175,6 @@ import {
|
|||
import {coverBase64} from '@/types/image'
|
||||
import {ReadListDto} from '@/types/komga-readlists'
|
||||
import OneShotActionsMenu from '@/components/menus/OneshotActionsMenu.vue'
|
||||
import {CLIENT_SETTING} from '@/types/komga-clientsettings'
|
||||
|
||||
export default Vue.extend({
|
||||
name: 'ItemCard',
|
||||
|
|
@ -233,11 +230,6 @@ export default Vue.extend({
|
|||
type: Boolean,
|
||||
default: true,
|
||||
},
|
||||
// force disable fab
|
||||
disableFab: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
},
|
||||
data: () => {
|
||||
return {
|
||||
|
|
@ -275,20 +267,11 @@ export default Vue.extend({
|
|||
this.$eventHub.$off(THUMBNAILCOLLECTION_DELETED, this.thumbnailCollectionChanged)
|
||||
},
|
||||
computed: {
|
||||
isStretch(): boolean {
|
||||
return this.$store.getters.getClientSettings[CLIENT_SETTING.WEBUI_POSTER_STRETCH]?.value === 'true'
|
||||
},
|
||||
isBlurUnread(): boolean {
|
||||
return this.$store.getters.getClientSettings[CLIENT_SETTING.WEBUI_POSTER_BLUR_UNREAD]?.value === 'true'
|
||||
},
|
||||
shouldBlurPoster(): boolean | undefined {
|
||||
return (this.isUnread || this.allUnread) && this.isBlurUnread
|
||||
},
|
||||
canReadPages(): boolean {
|
||||
return this.$store.getters.mePageStreaming && this.computedItem.type() === ItemTypes.BOOK
|
||||
},
|
||||
overlay(): boolean {
|
||||
return this.onEdit !== undefined || this.onSelected !== undefined || this.showFab || this.actionMenu
|
||||
return this.onEdit !== undefined || this.onSelected !== undefined || this.bookReady || this.canReadPages || this.actionMenu
|
||||
},
|
||||
computedItem(): Item<BookDto | SeriesDto | CollectionDto | ReadListDto> {
|
||||
let item = this.item
|
||||
|
|
@ -324,10 +307,6 @@ export default Vue.extend({
|
|||
if (this.computedItem.type() === ItemTypes.SERIES) return (this.item as SeriesDto).booksUnreadCount + (this.item as SeriesDto).booksInProgressCount
|
||||
return undefined
|
||||
},
|
||||
allUnread(): boolean | undefined {
|
||||
if (this.computedItem.type() === ItemTypes.SERIES) return (this.item as SeriesDto).booksCount == (this.item as SeriesDto).booksUnreadCount
|
||||
return undefined
|
||||
},
|
||||
readProgressPercentage(): number {
|
||||
if (this.computedItem.type() === ItemTypes.BOOK) return getReadProgressPercentage(this.item as BookDto)
|
||||
return 0
|
||||
|
|
@ -338,9 +317,6 @@ export default Vue.extend({
|
|||
}
|
||||
return false
|
||||
},
|
||||
showFab(): boolean {
|
||||
return !this.disableFab && this.bookReady && !this.selected && !this.preselect && this.canReadPages
|
||||
},
|
||||
to(): RawLocation {
|
||||
return this.computedItem.to()
|
||||
},
|
||||
|
|
@ -396,10 +372,6 @@ export default Vue.extend({
|
|||
</script>
|
||||
|
||||
<style>
|
||||
.blur > .v-image__image {
|
||||
filter: blur(5px);
|
||||
}
|
||||
|
||||
.no-link {
|
||||
cursor: default;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
<template>
|
||||
<div>
|
||||
<v-bottom-navigation
|
||||
v-if="bottomNavigation"
|
||||
v-if="show && bottomNavigation"
|
||||
grow color="primary"
|
||||
:app="$vuetify.breakpoint.smAndUp"
|
||||
:fixed="bottomNavigation"
|
||||
|
|
@ -9,25 +9,20 @@
|
|||
<v-btn v-if="showRecommended"
|
||||
:to="{name: 'recommended-libraries', params: {libraryId: libraryId}}"
|
||||
>
|
||||
<span class="caption">{{ $t('library_navigation.recommended') }}</span>
|
||||
<span>{{ $t('library_navigation.recommended') }}</span>
|
||||
<v-icon>mdi-star</v-icon>
|
||||
</v-btn>
|
||||
|
||||
<v-btn :to="{name: 'browse-libraries', params: {libraryId: libraryId}}">
|
||||
<span class="caption">{{ $t('library_navigation.browse_series') }}</span>
|
||||
<span>{{ $t('library_navigation.browse') }}</span>
|
||||
<v-icon>mdi-bookshelf</v-icon>
|
||||
</v-btn>
|
||||
|
||||
<v-btn :to="{name: 'browse-books', params: {libraryId: libraryId}}">
|
||||
<span class="caption">{{ $t('library_navigation.browse_books') }}</span>
|
||||
<v-icon>mdi-book-multiple</v-icon>
|
||||
</v-btn>
|
||||
|
||||
<v-btn
|
||||
v-if="collectionsCount > 0"
|
||||
:to="{name: 'browse-collections', params: {libraryId: libraryId}}"
|
||||
>
|
||||
<span class="caption">{{ $t('library_navigation.collections') }}</span>
|
||||
<span>{{ $t('library_navigation.collections') }}</span>
|
||||
<v-icon>mdi-layers-triple</v-icon>
|
||||
</v-btn>
|
||||
|
||||
|
|
@ -35,14 +30,14 @@
|
|||
v-if="readListsCount > 0"
|
||||
:to="{name: 'browse-readlists', params: {libraryId: libraryId}}"
|
||||
>
|
||||
<span class="caption">{{ $t('library_navigation.readlists') }}</span>
|
||||
<v-icon>mdi-bookmark-multiple</v-icon>
|
||||
<span>{{ $t('library_navigation.readlists') }}</span>
|
||||
<v-icon>mdi-book-multiple</v-icon>
|
||||
</v-btn>
|
||||
|
||||
</v-bottom-navigation>
|
||||
|
||||
<template
|
||||
v-if="!bottomNavigation"
|
||||
v-if="show && !bottomNavigation"
|
||||
>
|
||||
<v-btn v-if="showRecommended"
|
||||
:to="{name: 'recommended-libraries', params: {libraryId: libraryId}}"
|
||||
|
|
@ -56,14 +51,7 @@
|
|||
text
|
||||
class="mx-1"
|
||||
>
|
||||
{{ $t('library_navigation.browse_series') }}
|
||||
</v-btn>
|
||||
|
||||
<v-btn :to="{name: 'browse-books', params: {libraryId: libraryId}}"
|
||||
text
|
||||
class="mx-1"
|
||||
>
|
||||
{{ $t('library_navigation.browse_books') }}
|
||||
{{ $t('library_navigation.browse') }}
|
||||
</v-btn>
|
||||
|
||||
<v-btn
|
||||
|
|
@ -92,7 +80,6 @@
|
|||
import Vue from 'vue'
|
||||
import {COLLECTION_ADDED, COLLECTION_DELETED, READLIST_ADDED, READLIST_DELETED} from '@/types/events'
|
||||
import {LIBRARIES_ALL} from '@/types/library'
|
||||
import {LibraryDto} from '@/types/komga-libraries'
|
||||
|
||||
export default Vue.extend({
|
||||
name: 'LibraryNavigation',
|
||||
|
|
@ -120,14 +107,6 @@ export default Vue.extend({
|
|||
},
|
||||
immediate: true,
|
||||
},
|
||||
'$store.getters.getLibrariesPinned': {
|
||||
handler(val) {
|
||||
if (this.libraryId === LIBRARIES_ALL) {
|
||||
this.loadCollectionCounts(this.libraryId)
|
||||
this.loadReadListCounts(this.libraryId)
|
||||
}
|
||||
},
|
||||
},
|
||||
},
|
||||
created() {
|
||||
this.$eventHub.$on(COLLECTION_ADDED, this.collectionAdded)
|
||||
|
|
@ -145,6 +124,9 @@ export default Vue.extend({
|
|||
showRecommended(): boolean {
|
||||
return this.libraryId !== LIBRARIES_ALL
|
||||
},
|
||||
show(): boolean {
|
||||
return this.collectionsCount > 0 || this.readListsCount > 0 || this.showRecommended
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
readListAdded() {
|
||||
|
|
@ -160,12 +142,12 @@ export default Vue.extend({
|
|||
if(this.collectionsCount === 1) this.loadCollectionCounts(this.libraryId)
|
||||
},
|
||||
async loadCollectionCounts(libraryId: string) {
|
||||
const lib = libraryId !== LIBRARIES_ALL ? [libraryId] : this.$store.getters.getLibrariesPinned.map((it: LibraryDto) => it.id)
|
||||
const lib = libraryId !== LIBRARIES_ALL ? [libraryId] : undefined
|
||||
this.$komgaCollections.getCollections(lib, {size: 0})
|
||||
.then(v => this.collectionsCount = v.totalElements)
|
||||
},
|
||||
async loadReadListCounts(libraryId: string) {
|
||||
const lib = libraryId !== LIBRARIES_ALL ? [libraryId] : this.$store.getters.getLibrariesPinned.map((it: LibraryDto) => it.id)
|
||||
const lib = libraryId !== LIBRARIES_ALL ? [libraryId] : undefined
|
||||
await this.$komgaReadLists.getReadLists(lib, {size: 0})
|
||||
.then(v => this.readListsCount = v.totalElements)
|
||||
},
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@
|
|||
<v-icon>mdi-view-grid-plus</v-icon>
|
||||
</v-btn>
|
||||
</template>
|
||||
<v-list :dark="dark">
|
||||
<v-list>
|
||||
<v-list-item-group v-model="selection">
|
||||
|
||||
<v-list-item v-for="(item, index) in items"
|
||||
|
|
@ -38,10 +38,6 @@ export default Vue.extend({
|
|||
type: Number,
|
||||
required: true,
|
||||
},
|
||||
dark: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
},
|
||||
watch: {
|
||||
value: {
|
||||
|
|
|
|||
|
|
@ -15,8 +15,7 @@
|
|||
year: 'numeric',
|
||||
timeZone: 'UTC'
|
||||
}).format(new Date(series.releaseDate))
|
||||
}}
|
||||
</div>
|
||||
}}</div>
|
||||
</template>
|
||||
<template v-else>
|
||||
<div style="height: 2em" class="missing"></div>
|
||||
|
|
@ -72,7 +71,6 @@ import {
|
|||
ReadListRequestBookMatchSeriesDto,
|
||||
} from '@/types/komga-readlists'
|
||||
import BookPickerDialog from '@/components/dialogs/BookPickerDialog.vue'
|
||||
import {BookSearch, SearchConditionSeriesId, SearchOperatorIs} from '@/types/komga-search'
|
||||
|
||||
export default Vue.extend({
|
||||
name: 'ReadListMatchRow',
|
||||
|
|
@ -97,10 +95,6 @@ export default Vue.extend({
|
|||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
error: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
},
|
||||
watch: {
|
||||
series: {
|
||||
|
|
@ -122,13 +116,16 @@ export default Vue.extend({
|
|||
existingFileNames(): string[] {
|
||||
return this.seriesBooks.map(x => x.name)
|
||||
},
|
||||
error(): string {
|
||||
if (!this.series) return this.$t('book_import.row.error_choose_series').toString()
|
||||
if (!this.book) return this.$t('readlist_import.row.error_choose_book').toString()
|
||||
return ''
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
openBookPicker() {
|
||||
if (!this.seriesBooksCached) {
|
||||
this.$komgaBooks.getBooksList({
|
||||
condition: new SearchConditionSeriesId(new SearchOperatorIs(this.series?.seriesId)),
|
||||
} as BookSearch, {unpaged: true, sort: 'metadata.numberSort'})
|
||||
this.$komgaSeries.getBooks(this.series?.seriesId, {unpaged: true})
|
||||
.then(r => {
|
||||
this.seriesBooks = r.content
|
||||
this.seriesBooksCached = true
|
||||
|
|
|
|||
|
|
@ -10,7 +10,6 @@
|
|||
@scroll-changed="(percent) => scrollChanged(readListsLoaders[index], percent)"
|
||||
>
|
||||
<template v-slot:prepend>
|
||||
<slot name="prepend" v-bind:readlist="r"/>
|
||||
<router-link class="text-overline"
|
||||
:to="{name: 'browse-readlist', params: {readListId: r.id}}"
|
||||
>{{ $t('readlists_expansion_panel.manage_readlist') }}
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
<template>
|
||||
<vue-read-more-smooth no-shadow :lines="4" :open.sync="open">
|
||||
<vue-read-more-smooth no-shadow :lines="4">
|
||||
<div style="white-space: pre-wrap" class="body-2">
|
||||
<slot/>
|
||||
</div>
|
||||
|
|
@ -19,25 +19,8 @@ import Vue from 'vue'
|
|||
|
||||
export default Vue.extend({
|
||||
name: 'ReadMore',
|
||||
components: {VueReadMoreSmooth},
|
||||
data: () => {
|
||||
return {
|
||||
open: false,
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
value(val) {
|
||||
this.open = val
|
||||
},
|
||||
open(val) {
|
||||
this.$emit('input', val)
|
||||
},
|
||||
},
|
||||
components: { VueReadMoreSmooth },
|
||||
props: {
|
||||
value: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
i18nMore: {
|
||||
type: String,
|
||||
default: 'read_more.more',
|
||||
|
|
|
|||
|
|
@ -1,126 +0,0 @@
|
|||
<template>
|
||||
<div>
|
||||
<v-list>
|
||||
<v-list-item class="contrast-1">
|
||||
<v-list-item-content>
|
||||
<v-list-item-title class="text-uppercase">{{ $t('common.reorder') }}</v-list-item-title>
|
||||
</v-list-item-content>
|
||||
<v-list-item-action class="ma-0">
|
||||
<v-btn icon @click.stop.capture.prevent="dismiss">
|
||||
<v-icon>mdi-close</v-icon>
|
||||
</v-btn>
|
||||
</v-list-item-action>
|
||||
</v-list-item>
|
||||
|
||||
<v-list-item class="text--disabled">
|
||||
<v-list-item-icon>
|
||||
<v-icon>mdi-home</v-icon>
|
||||
</v-list-item-icon>
|
||||
<v-list-item-content>
|
||||
<v-list-item-title>{{ $t('navigation.home') }}</v-list-item-title>
|
||||
</v-list-item-content>
|
||||
</v-list-item>
|
||||
|
||||
<draggable
|
||||
v-model="localItems"
|
||||
v-bind="dragOptions"
|
||||
handle=".handle"
|
||||
>
|
||||
<v-list-item v-for="l in localItems" :key="l.id">
|
||||
<v-list-item-icon>
|
||||
<v-icon class="handle">mdi-drag-horizontal-variant</v-icon>
|
||||
</v-list-item-icon>
|
||||
<v-list-item-content>
|
||||
<v-list-item-title class="handle">{{ l.name }}</v-list-item-title>
|
||||
</v-list-item-content>
|
||||
<v-list-item-icon>
|
||||
<v-btn icon v-if="!l.unpinned" @click.stop.capture.prevent="unpin(l.id)" x-small>
|
||||
<v-icon>mdi-pin</v-icon>
|
||||
</v-btn>
|
||||
<v-btn icon v-if="l.unpinned" @click.stop.capture.prevent="pin(l.id)" x-small>
|
||||
<v-icon>mdi-pin-off</v-icon>
|
||||
</v-btn>
|
||||
</v-list-item-icon>
|
||||
</v-list-item>
|
||||
</draggable>
|
||||
</v-list>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import Vue from 'vue'
|
||||
import draggable from 'vuedraggable'
|
||||
import {LibraryDto} from '@/types/komga-libraries'
|
||||
import {ClientSettingLibraryUpdate} from '@/types/komga-clientsettings'
|
||||
|
||||
export default Vue.extend({
|
||||
name: 'ReorderLibraries',
|
||||
components: {draggable},
|
||||
data: () => {
|
||||
return {
|
||||
localItems: [] as LibraryDto[],
|
||||
unwatch: false,
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
dragOptions(): any {
|
||||
return {
|
||||
animation: 200,
|
||||
ghostClass: 'ghost',
|
||||
}
|
||||
},
|
||||
},
|
||||
mounted() {
|
||||
this.localItems = this.$store.getters.getLibraries
|
||||
},
|
||||
watch: {
|
||||
localItems: {
|
||||
handler(val: LibraryDto[]) {
|
||||
const newSettings = val.map((it, index) => ({
|
||||
libraryId: it.id,
|
||||
patch: {
|
||||
order: index,
|
||||
},
|
||||
} as ClientSettingLibraryUpdate))
|
||||
|
||||
this.$store.dispatch('updateLibrariesSettings', newSettings)
|
||||
},
|
||||
immediate: true,
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
dismiss() {
|
||||
this.$emit('dismiss')
|
||||
},
|
||||
unpin(libraryId: string) {
|
||||
this.$store.dispatch('updateLibrarySetting', {
|
||||
libraryId: libraryId,
|
||||
patch: {
|
||||
unpinned: true,
|
||||
},
|
||||
} as ClientSettingLibraryUpdate)
|
||||
this.localItems.find(it => it.id == libraryId).unpinned = true
|
||||
},
|
||||
pin(libraryId: string) {
|
||||
this.$store.dispatch('updateLibrarySetting', {
|
||||
libraryId: libraryId,
|
||||
patch: {
|
||||
unpinned: false,
|
||||
},
|
||||
} as ClientSettingLibraryUpdate)
|
||||
this.localItems.find(it => it.id == libraryId).unpinned = false
|
||||
},
|
||||
},
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.handle {
|
||||
cursor: grab !important;
|
||||
}
|
||||
|
||||
.ghost {
|
||||
opacity: 0.5;
|
||||
background: #c8ebfb;
|
||||
}
|
||||
</style>
|
||||
|
|
@ -123,12 +123,6 @@ import {SeriesDto} from '@/types/komga-series'
|
|||
import {getReadProgress} from '@/functions/book-progress'
|
||||
import {ReadStatus} from '@/types/enum-books'
|
||||
import {ReadListDto} from '@/types/komga-readlists'
|
||||
import {
|
||||
BookSearch,
|
||||
SearchConditionOneShot,
|
||||
SearchOperatorIsFalse,
|
||||
SeriesSearch,
|
||||
} from '@/types/komga-search'
|
||||
|
||||
export default Vue.extend({
|
||||
name: 'SearchBox',
|
||||
|
|
@ -208,13 +202,8 @@ export default Vue.extend({
|
|||
searchItems: debounce(async function (this: any, query: string) {
|
||||
if (query) {
|
||||
this.loading = true
|
||||
this.series = (await this.$komgaSeries.getSeriesList({
|
||||
fullTextSearch: query,
|
||||
condition: new SearchConditionOneShot(new SearchOperatorIsFalse()),
|
||||
} as SeriesSearch, {size: this.pageSize})).content
|
||||
this.books = (await this.$komgaBooks.getBooksList({
|
||||
fullTextSearch: query,
|
||||
} as BookSearch, {size: this.pageSize})).content
|
||||
this.series = (await this.$komgaSeries.getSeries(undefined, {size: this.pageSize}, query, undefined, undefined, undefined, undefined, undefined, undefined, undefined, undefined, undefined, undefined, undefined, undefined, false)).content
|
||||
this.books = (await this.$komgaBooks.getBooks(undefined, {size: this.pageSize}, query)).content
|
||||
this.collections = (await this.$komgaCollections.getCollections(undefined, {size: this.pageSize}, query)).content
|
||||
this.readLists = (await this.$komgaReadLists.getReadLists(undefined, {size: this.pageSize}, query)).content
|
||||
this.showResults = true
|
||||
|
|
|
|||
|
|
@ -161,7 +161,7 @@ export default Vue.extend({
|
|||
watch: {
|
||||
users(val) {
|
||||
val.forEach((u: UserDto) => {
|
||||
this.$komgaUsers.getLatestAuthenticationActivityForUser(u.id)
|
||||
this.$komgaUsers.getLatestAuthenticationActivityForUser(u)
|
||||
.then(value => this.$set(this.usersLastActivity, `${u.id}`, value.dateTime))
|
||||
.catch(e => {
|
||||
})
|
||||
|
|
|
|||
|
|
@ -52,7 +52,7 @@
|
|||
</v-tooltip>
|
||||
</v-btn>
|
||||
|
||||
<v-btn icon @click="addToReadList" v-if="isAdmin && (kind === 'books' || kind === 'series')">
|
||||
<v-btn icon @click="addToReadList" v-if="isAdmin && (kind === 'books' || (kind === 'series' && oneshots))">
|
||||
<v-tooltip bottom>
|
||||
<template v-slot:activator="{ on }">
|
||||
<v-icon v-on="on">mdi-book-plus-multiple</v-icon>
|
||||
|
|
|
|||
|
|
@ -1,172 +0,0 @@
|
|||
<template>
|
||||
<v-dialog v-model="modal"
|
||||
max-width="600"
|
||||
>
|
||||
<v-card>
|
||||
<v-card-title>{{ $t('dialog.add_api_key.dialog_title') }}</v-card-title>
|
||||
<v-btn icon absolute top right @click="dialogClose">
|
||||
<v-icon>mdi-close</v-icon>
|
||||
</v-btn>
|
||||
|
||||
<v-card-text>
|
||||
<v-container fluid>
|
||||
<v-row>
|
||||
<v-col>{{ $t('dialog.add_api_key.context') }}</v-col>
|
||||
</v-row>
|
||||
<v-row v-if="!apiKey">
|
||||
<v-col>
|
||||
<v-text-field v-model.trim="form.comment"
|
||||
autofocus
|
||||
:label="$t('dialog.add_api_key.field_comment')"
|
||||
:hint="$t('dialog.add_api_key.field_comment_hint')"
|
||||
:error-messages="getErrors('comment')"
|
||||
@blur="$v.form.comment.$touch()"
|
||||
/>
|
||||
</v-col>
|
||||
</v-row>
|
||||
|
||||
<v-row v-if="apiKey">
|
||||
<v-col>
|
||||
<v-alert type="info" class="body-2">{{ $t('dialog.add_api_key.info_copy') }}</v-alert>
|
||||
</v-col>
|
||||
</v-row>
|
||||
|
||||
<v-row v-if="apiKey">
|
||||
<v-col>
|
||||
<v-icon color="success">mdi-check</v-icon>
|
||||
{{ apiKey.key }}
|
||||
|
||||
<v-tooltip top v-model="copied" v-if="isClipboardApiAvailable">
|
||||
<template v-slot:activator="on">
|
||||
<v-btn v-on="on"
|
||||
icon
|
||||
x-small
|
||||
class="align-content-end"
|
||||
@click="copyApiKeyToClipboard"
|
||||
>
|
||||
<v-icon v-if="copied" color="success">mdi-check</v-icon>
|
||||
<v-icon v-else>mdi-content-copy</v-icon>
|
||||
</v-btn>
|
||||
</template>
|
||||
<span>{{ $t('common.copied') }}</span>
|
||||
</v-tooltip>
|
||||
</v-col>
|
||||
</v-row>
|
||||
</v-container>
|
||||
|
||||
</v-card-text>
|
||||
|
||||
<v-card-actions>
|
||||
<v-spacer/>
|
||||
<v-btn text @click="dialogClose">{{ $t('common.close') }}</v-btn>
|
||||
<v-btn color="primary" @click="generateApiKey" :disabled="!!apiKey">{{ $t('dialog.add_api_key.button_confirm') }}</v-btn>
|
||||
</v-card-actions>
|
||||
</v-card>
|
||||
</v-dialog>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import {UserRoles} from '@/types/enum-users'
|
||||
import Vue from 'vue'
|
||||
import {required} from 'vuelidate/lib/validators'
|
||||
import {ERROR} from '@/types/events'
|
||||
import {ApiKeyDto, ApiKeyRequestDto} from '@/types/komga-users'
|
||||
|
||||
function validComment(value: string) {
|
||||
return !this.alreadyUsedComment.includes(value)
|
||||
}
|
||||
|
||||
export default Vue.extend({
|
||||
name: 'ApiKeyAddDialog',
|
||||
data: function () {
|
||||
return {
|
||||
UserRoles,
|
||||
modal: false,
|
||||
apiKey: undefined as ApiKeyDto,
|
||||
copied: false,
|
||||
alreadyUsedComment: [] as string[],
|
||||
form: {
|
||||
comment: '',
|
||||
},
|
||||
}
|
||||
},
|
||||
props: {
|
||||
value: Boolean,
|
||||
},
|
||||
watch: {
|
||||
value(val) {
|
||||
this.modal = val
|
||||
if (val) {
|
||||
this.clear()
|
||||
}
|
||||
},
|
||||
modal(val) {
|
||||
!val && this.dialogClose()
|
||||
},
|
||||
},
|
||||
validations: {
|
||||
form: {
|
||||
comment: {required, validComment},
|
||||
},
|
||||
},
|
||||
computed: {
|
||||
isClipboardApiAvailable(): boolean {
|
||||
return !!navigator.clipboard
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
clear() {
|
||||
this.apiKey = undefined
|
||||
this.alreadyUsedComment = []
|
||||
this.form.comment = ''
|
||||
this.$v.$reset()
|
||||
},
|
||||
dialogClose() {
|
||||
this.$emit('input', false)
|
||||
},
|
||||
getErrors(fieldName: string): string[] {
|
||||
const errors = [] as string[]
|
||||
|
||||
const field = this.$v.form!![fieldName] as any
|
||||
if (field && field.$invalid && field.$dirty) {
|
||||
if (!field.validComment) errors.push(this.$t('error_codes.ERR_1034').toString())
|
||||
if (!field.required) errors.push(this.$t('common.required').toString())
|
||||
}
|
||||
return errors
|
||||
},
|
||||
validateInput(): ApiKeyRequestDto {
|
||||
this.$v.$touch()
|
||||
|
||||
if (!this.$v.$invalid) {
|
||||
return {
|
||||
comment: this.form.comment,
|
||||
}
|
||||
}
|
||||
return undefined
|
||||
},
|
||||
async generateApiKey() {
|
||||
const apiKeyRequest = this.validateInput()
|
||||
if (apiKeyRequest) {
|
||||
try {
|
||||
this.apiKey = await this.$komgaUsers.createApiKey(apiKeyRequest)
|
||||
this.$emit('generate')
|
||||
} catch (e) {
|
||||
if (e.message.includes('ERR_1034'))
|
||||
this.alreadyUsedComment.push(this.form.comment)
|
||||
else
|
||||
this.$eventHub.$emit(ERROR, {message: e.message} as ErrorEvent)
|
||||
}
|
||||
}
|
||||
},
|
||||
copyApiKeyToClipboard() {
|
||||
navigator.clipboard.writeText(this.apiKey.key)
|
||||
this.copied = true
|
||||
setTimeout(() => this.copied = false, 3000)
|
||||
},
|
||||
},
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
|
||||
</style>
|
||||
|
|
@ -122,7 +122,7 @@
|
|||
|
||||
<!-- Sort Number -->
|
||||
<v-col cols="2">
|
||||
<v-text-field v-model.number="form[book.id].numberSort"
|
||||
<v-text-field v-model="form[book.id].numberSort"
|
||||
type="number"
|
||||
step="0.1"
|
||||
dense
|
||||
|
|
@ -200,7 +200,7 @@
|
|||
<script lang="ts">
|
||||
import Vue from 'vue'
|
||||
import {BookDto} from '@/types/komga-books'
|
||||
import IsbnVerify from '@w0s/isbn-verify'
|
||||
import IsbnVerify from '@saekitominaga/isbn-verify'
|
||||
import {isMatch} from 'date-fns'
|
||||
import {ERROR} from '@/types/events'
|
||||
|
||||
|
|
@ -283,7 +283,7 @@ export default Vue.extend({
|
|||
return value || value === 0 ? true : this.$t('common.required').toString()
|
||||
},
|
||||
validateReleaseDate(date: string): string | boolean {
|
||||
return date && (!isMatch(date, 'yyyy-MM-dd') || date.length !== 10) ? this.$t('dialog.edit_books.field_release_date_error').toString() : true
|
||||
return date && !isMatch(date, 'yyyy-MM-dd') ? this.$t('dialog.edit_books.field_release_date_error').toString() : true
|
||||
},
|
||||
bookDisplayName(book: BookDto): string {
|
||||
const parts = book.url.split('/')
|
||||
|
|
|
|||
|
|
@ -100,6 +100,7 @@
|
|||
import {UserRoles} from '@/types/enum-users'
|
||||
import Vue from 'vue'
|
||||
import {ERROR, ErrorEvent} from '@/types/events'
|
||||
import {LibraryDto} from '@/types/komga-libraries'
|
||||
import ThumbnailCard from '@/components/ThumbnailCard.vue'
|
||||
import DropZone from '@/components/DropZone.vue'
|
||||
|
||||
|
|
@ -151,6 +152,9 @@ export default Vue.extend({
|
|||
},
|
||||
},
|
||||
computed: {
|
||||
libraries(): LibraryDto[] {
|
||||
return this.$store.state.komgaLibraries.libraries
|
||||
},
|
||||
getErrorsName(): string {
|
||||
if (this.form.name === '') return this.$t('common.required').toString()
|
||||
if (this.form.name?.toLowerCase() !== this.collection.name?.toLowerCase() && this.collections.some(e => e.name.toLowerCase() === this.form.name.toLowerCase())) {
|
||||
|
|
|
|||
|
|
@ -430,15 +430,14 @@ import {authorRoles} from '@/types/author-roles'
|
|||
import Vue from 'vue'
|
||||
import {helpers, requiredIf} from 'vuelidate/lib/validators'
|
||||
import {BookDto, BookThumbnailDto} from '@/types/komga-books'
|
||||
import IsbnVerify from '@w0s/isbn-verify'
|
||||
import IsbnVerify from '@saekitominaga/isbn-verify'
|
||||
import {isMatch} from 'date-fns'
|
||||
import {debounce} from 'lodash'
|
||||
import {ERROR, ErrorEvent} from '@/types/events'
|
||||
import DropZone from '@/components/DropZone.vue'
|
||||
import ThumbnailCard from '@/components/ThumbnailCard.vue'
|
||||
import {NameValue} from '@/types/filter'
|
||||
|
||||
const validDate = (value: string) => !helpers.req(value) || isMatch(value, 'yyyy-MM-dd') && value.length == 10
|
||||
const validDate = (value: string) => !helpers.req(value) || isMatch(value, 'yyyy-MM-dd')
|
||||
const validIsbn = (value: string) => !helpers.req(value) || new IsbnVerify(value).isIsbn13({check_digit: true})
|
||||
|
||||
export default Vue.extend({
|
||||
|
|
|
|||
|
|
@ -570,7 +570,7 @@ import DropZone from '@/components/DropZone.vue'
|
|||
import ThumbnailCard from '@/components/ThumbnailCard.vue'
|
||||
import {BookDto, BookThumbnailDto} from '@/types/komga-books'
|
||||
import {isMatch} from 'date-fns'
|
||||
import IsbnVerify from '@w0s/isbn-verify'
|
||||
import IsbnVerify from '@saekitominaga/isbn-verify'
|
||||
import {debounce} from 'lodash'
|
||||
import {authorRoles} from '@/types/author-roles'
|
||||
import {groupAuthorsByRole} from '@/functions/authors'
|
||||
|
|
|
|||
|
|
@ -1,136 +0,0 @@
|
|||
<template>
|
||||
<v-dialog v-model="modal"
|
||||
max-width="450"
|
||||
:fullscreen="$vuetify.breakpoint.xsOnly"
|
||||
>
|
||||
<v-card>
|
||||
<v-card-title>{{ $t('dialog.edit_recommended.dialog_title') }}</v-card-title>
|
||||
<v-btn icon absolute top right @click="dialogClose">
|
||||
<v-icon>mdi-close</v-icon>
|
||||
</v-btn>
|
||||
|
||||
<v-card-text :class="$vuetify.breakpoint.xsOnly ? 'px-0' : undefined">
|
||||
<v-list>
|
||||
<draggable
|
||||
v-model="localItems"
|
||||
v-bind="dragOptions"
|
||||
handle=".handle"
|
||||
>
|
||||
<v-list-item v-for="(l, index) in localItems" :key="index">
|
||||
<v-list-item-icon>
|
||||
<v-icon class="handle">mdi-drag-horizontal-variant</v-icon>
|
||||
</v-list-item-icon>
|
||||
<v-list-item-content>
|
||||
<v-list-item-title class="handle">{{ $t(`dashboard.${l.section.toLowerCase()}`) }}</v-list-item-title>
|
||||
</v-list-item-content>
|
||||
<v-list-item-action>
|
||||
<v-switch v-model="enabled[l.section]"/>
|
||||
</v-list-item-action>
|
||||
</v-list-item>
|
||||
</draggable>
|
||||
</v-list>
|
||||
</v-card-text>
|
||||
|
||||
<v-card-actions>
|
||||
<v-spacer/>
|
||||
<v-btn v-if="$vuetify.breakpoint.smAndUp" text @click="dialogClose">{{ $t('common.cancel') }}</v-btn>
|
||||
<v-btn color="error" @click="resetToDefault">{{
|
||||
$t('dialog.edit_recommended.button_reset')
|
||||
}}
|
||||
</v-btn>
|
||||
<v-btn color="primary" @click="saveChanges">{{
|
||||
$t('dialog.edit_recommended.button_confirm')
|
||||
}}
|
||||
</v-btn>
|
||||
</v-card-actions>
|
||||
</v-card>
|
||||
</v-dialog>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import {UserRoles} from '@/types/enum-users'
|
||||
import draggable from 'vuedraggable'
|
||||
import Vue, {PropType} from 'vue'
|
||||
import {
|
||||
ClientSettingsRecommendedView,
|
||||
ClientSettingsRecommendedViewSection,
|
||||
RECOMMENDED_DEFAULT,
|
||||
} from '@/types/komga-clientsettings'
|
||||
|
||||
|
||||
export default Vue.extend({
|
||||
name: 'EditRecommendedDialog',
|
||||
components: {draggable},
|
||||
data: function () {
|
||||
return {
|
||||
UserRoles,
|
||||
modal: false,
|
||||
localItems: [] as ClientSettingsRecommendedViewSection[],
|
||||
enabled: {} as Record<string, boolean>,
|
||||
}
|
||||
},
|
||||
props: {
|
||||
value: Boolean,
|
||||
viewConfig: {
|
||||
type: Object as PropType<ClientSettingsRecommendedView>,
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
watch: {
|
||||
value(val) {
|
||||
this.modal = val
|
||||
if (val) {
|
||||
this.reset(this.viewConfig)
|
||||
}
|
||||
},
|
||||
modal(val) {
|
||||
!val && this.dialogClose()
|
||||
},
|
||||
},
|
||||
computed: {
|
||||
dragOptions(): any {
|
||||
return {
|
||||
animation: 200,
|
||||
ghostClass: 'ghost',
|
||||
}
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
reset(viewConfig: ClientSettingsRecommendedView) {
|
||||
this.localItems = viewConfig?.sections || []
|
||||
this.enabled = []
|
||||
this.localItems.forEach(it => this.enabled[it.section] = true)
|
||||
RECOMMENDED_DEFAULT.sections
|
||||
.filter(it => !viewConfig?.sections.some(s => s.section === it.section))
|
||||
.forEach(it => this.localItems.push(it))
|
||||
},
|
||||
dialogClose() {
|
||||
this.$emit('input', false)
|
||||
},
|
||||
resetToDefault() {
|
||||
this.$emit('reset-defaults')
|
||||
this.dialogClose()
|
||||
},
|
||||
saveChanges() {
|
||||
const sections = this.localItems.filter(it => this.enabled[it.section])
|
||||
const updated = {
|
||||
sections: sections,
|
||||
} as ClientSettingsRecommendedView
|
||||
|
||||
this.$emit('update:viewConfig', updated)
|
||||
this.dialogClose()
|
||||
},
|
||||
},
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.handle {
|
||||
cursor: grab !important;
|
||||
}
|
||||
|
||||
.ghost {
|
||||
opacity: 0.5;
|
||||
background: #c8ebfb;
|
||||
}
|
||||
</style>
|
||||
|
|
@ -17,7 +17,7 @@
|
|||
|
||||
<template v-if="directoryListing.hasOwnProperty('parent')">
|
||||
<v-list-item
|
||||
@click.prevent="selectParent(directoryListing.parent)"
|
||||
@click.prevent="select(directoryListing.parent)"
|
||||
>
|
||||
<v-list-item-icon>
|
||||
<v-icon>mdi-arrow-left</v-icon>
|
||||
|
|
@ -32,7 +32,7 @@
|
|||
|
||||
<div v-for="(d, index) in directoryListing.directories" :key="index">
|
||||
<v-list-item
|
||||
@click.prevent="select(d)"
|
||||
@click.prevent="select(d.path)"
|
||||
>
|
||||
<v-list-item-icon>
|
||||
<v-icon>{{ d.type === 'directory' ? 'mdi-folder' : 'mdi-file' }}</v-icon>
|
||||
|
|
@ -47,24 +47,6 @@
|
|||
|
||||
<v-divider v-if="index !== directoryListing.directories.length-1"/>
|
||||
</div>
|
||||
|
||||
<div v-for="(d, index) in directoryListing.files" :key="index">
|
||||
<v-list-item
|
||||
@click.prevent="select(d)"
|
||||
>
|
||||
<v-list-item-icon>
|
||||
<v-icon>{{ d.type === 'directory' ? 'mdi-folder' : 'mdi-file' }}</v-icon>
|
||||
</v-list-item-icon>
|
||||
|
||||
<v-list-item-content>
|
||||
<v-list-item-title>
|
||||
{{ d.name }}
|
||||
</v-list-item-title>
|
||||
</v-list-item-content>
|
||||
</v-list-item>
|
||||
|
||||
<v-divider v-if="index !== directoryListing.files.length-1"/>
|
||||
</div>
|
||||
</v-list>
|
||||
</v-card-text>
|
||||
|
||||
|
|
@ -109,10 +91,6 @@ export default Vue.extend({
|
|||
type: String,
|
||||
required: false,
|
||||
},
|
||||
showFiles: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
dialogTitle: {
|
||||
type: String,
|
||||
default: function (): string {
|
||||
|
|
@ -141,23 +119,18 @@ export default Vue.extend({
|
|||
dialogConfirm() {
|
||||
this.$emit('input', false)
|
||||
this.$emit('update:path', this.selectedPath)
|
||||
this.$emit('confirm')
|
||||
},
|
||||
async getDirs(path?: string) {
|
||||
try {
|
||||
this.directoryListing = await this.$komgaFileSystem.getDirectoryListing(path, this.showFiles)
|
||||
this.directoryListing = await this.$komgaFileSystem.getDirectoryListing(path)
|
||||
} catch (e) {
|
||||
this.$eventHub.$emit(ERROR, {message: e.message} as ErrorEvent)
|
||||
}
|
||||
},
|
||||
selectParent(path: string) {
|
||||
select(path: string) {
|
||||
this.selectedPath = path
|
||||
this.getDirs(path)
|
||||
},
|
||||
select(path: PathDto) {
|
||||
this.selectedPath = path.path
|
||||
if(path.type == 'directory') this.getDirs(path.path)
|
||||
},
|
||||
},
|
||||
})
|
||||
</script>
|
||||
|
|
|
|||
|
|
@ -108,9 +108,6 @@ export default Vue.extend({
|
|||
},
|
||||
immediate: true,
|
||||
},
|
||||
modal(val) {
|
||||
!val && this.dialogClose()
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
clear() {
|
||||
|
|
|
|||
|
|
@ -215,22 +215,6 @@
|
|||
</template>
|
||||
</v-checkbox>
|
||||
|
||||
<v-checkbox
|
||||
v-model="form.hashKoreader"
|
||||
:label="$t('dialog.edit_library.field_analysis_hash_koreader')"
|
||||
hide-details
|
||||
class="mx-4"
|
||||
>
|
||||
<template v-slot:append>
|
||||
<v-tooltip bottom>
|
||||
<template v-slot:activator="{ on }">
|
||||
<v-icon v-on="on" color="warning">mdi-alert-circle-outline</v-icon>
|
||||
</template>
|
||||
{{ $t('dialog.edit_library.tooltip_use_resources') }}
|
||||
</v-tooltip>
|
||||
</template>
|
||||
</v-checkbox>
|
||||
|
||||
<v-checkbox
|
||||
v-model="form.analyzeDimensions"
|
||||
:label="$t('dialog.edit_library.field_analysis_analyze_dimensions')"
|
||||
|
|
@ -481,7 +465,6 @@ export default Vue.extend({
|
|||
seriesCover: SeriesCoverDto.FIRST as SeriesCoverDto,
|
||||
hashFiles: true,
|
||||
hashPages: false,
|
||||
hashKoreader: false,
|
||||
analyzeDimensions: true,
|
||||
oneshotsDirectory: '',
|
||||
},
|
||||
|
|
@ -641,7 +624,6 @@ export default Vue.extend({
|
|||
this.form.seriesCover = library ? library.seriesCover : SeriesCoverDto.FIRST
|
||||
this.form.hashFiles = library ? library.hashFiles : true
|
||||
this.form.hashPages = library ? library.hashPages : false
|
||||
this.form.hashKoreader = library ? library.hashKoreader : false
|
||||
this.form.analyzeDimensions = library ? library.analyzeDimensions : true
|
||||
this.form.oneshotsDirectory = library ? library.oneshotsDirectory : ''
|
||||
this.$v.$reset()
|
||||
|
|
@ -676,7 +658,6 @@ export default Vue.extend({
|
|||
seriesCover: this.form.seriesCover,
|
||||
hashFiles: this.form.hashFiles,
|
||||
hashPages: this.form.hashPages,
|
||||
hashKoreader: this.form.hashKoreader,
|
||||
analyzeDimensions: this.form.analyzeDimensions,
|
||||
oneshotsDirectory: this.form.oneshotsDirectory,
|
||||
}
|
||||
|
|
|
|||
|
|
@ -113,6 +113,7 @@
|
|||
import {UserRoles} from '@/types/enum-users'
|
||||
import Vue from 'vue'
|
||||
import {ERROR, ErrorEvent} from '@/types/events'
|
||||
import {LibraryDto} from '@/types/komga-libraries'
|
||||
import DropZone from '@/components/DropZone.vue'
|
||||
import ThumbnailCard from '@/components/ThumbnailCard.vue'
|
||||
import {ReadListDto, ReadListThumbnailDto, ReadListUpdateDto} from '@/types/komga-readlists'
|
||||
|
|
@ -166,6 +167,9 @@ export default Vue.extend({
|
|||
},
|
||||
},
|
||||
computed: {
|
||||
libraries(): LibraryDto[] {
|
||||
return this.$store.state.komgaLibraries.libraries
|
||||
},
|
||||
getErrorsName(): string {
|
||||
if (this.form.name === '') return this.$t('common.required').toString()
|
||||
if (this.form.name?.toLowerCase() !== this.readList.name?.toLowerCase() && this.readLists.some(e => e.name.toLowerCase() === this.form.name.toLowerCase())) {
|
||||
|
|
|
|||
|
|
@ -82,7 +82,6 @@ import Vue, {PropType} from 'vue'
|
|||
import {SeriesDto} from '@/types/komga-series'
|
||||
import {debounce} from 'lodash'
|
||||
import {seriesThumbnailUrl} from '@/functions/urls'
|
||||
import {SearchConditionOneShot, SearchOperatorIsFalse, SeriesSearch} from '@/types/komga-search'
|
||||
|
||||
export default Vue.extend({
|
||||
name: 'SeriesPickerDialog',
|
||||
|
|
@ -124,10 +123,7 @@ export default Vue.extend({
|
|||
searchItems: debounce(async function (this: any, query: string) {
|
||||
if (query) {
|
||||
this.showResults = false
|
||||
this.results = (await this.$komgaSeries.getSeriesList({
|
||||
fullTextSearch: query,
|
||||
condition: this.includeOneshots ? undefined : new SearchConditionOneShot(new SearchOperatorIsFalse()),
|
||||
} as SeriesSearch, {unpaged: true})).content
|
||||
this.results = (await this.$komgaSeries.getSeries(undefined, {unpaged: true}, query, undefined, undefined, undefined, undefined, undefined, undefined, undefined, undefined, undefined, undefined, undefined, undefined, this.includeOneshots ? undefined : false)).content
|
||||
this.showResults = true
|
||||
} else {
|
||||
this.clear()
|
||||
|
|
|
|||
|
|
@ -9,12 +9,6 @@
|
|||
:total-visible="perPage"
|
||||
:length="Math.ceil(thumbnails.length/perPage)"
|
||||
></v-pagination>
|
||||
|
||||
<page-size-select
|
||||
v-model="perPage"
|
||||
dark
|
||||
:items="[10, 20, 50, 100]"
|
||||
/>
|
||||
</v-card-title>
|
||||
<v-card-text>
|
||||
<v-container fluid>
|
||||
|
|
@ -35,7 +29,7 @@
|
|||
@click="input = false; goTo(((page - 1 ) * perPage + i + 1))"
|
||||
style="cursor: pointer"
|
||||
/>
|
||||
<div class="white--text text-center font-weight-bold">{{ (page - 1) * perPage + i + 1 }}</div>
|
||||
<div class="white--text text-center font-weight-bold">{{ (page - 1 ) * perPage + i + 1 }}</div>
|
||||
</div>
|
||||
|
||||
</v-row>
|
||||
|
|
@ -48,11 +42,9 @@
|
|||
<script lang="ts">
|
||||
import Vue from 'vue'
|
||||
import {bookPageThumbnailUrl} from '@/functions/urls'
|
||||
import PageSizeSelect from '@/components/PageSizeSelect.vue'
|
||||
|
||||
export default Vue.extend({
|
||||
name: 'ThumbnailExplorerDialog',
|
||||
components: {PageSizeSelect},
|
||||
props: {
|
||||
pagesCount: {
|
||||
type: Number,
|
||||
|
|
@ -68,45 +60,39 @@ export default Vue.extend({
|
|||
return {
|
||||
input: '',
|
||||
page: 1,
|
||||
perPage: 10,
|
||||
perPage: 8,
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
value(val) {
|
||||
value (val) {
|
||||
this.input = val
|
||||
},
|
||||
input(val) {
|
||||
input (val) {
|
||||
!val && this.$emit('input', false)
|
||||
},
|
||||
perPage(val) {
|
||||
this.$store.commit('setThumbnailsPageSize', val)
|
||||
},
|
||||
},
|
||||
mounted() {
|
||||
this.perPage = this.$store.state.persistedState.thumbnailsPageSize || this.perPage
|
||||
},
|
||||
computed: {
|
||||
thumbnails(): string[] {
|
||||
thumbnails (): string[] {
|
||||
let thumbnails = []
|
||||
for (let p = 1; p <= this.pagesCount; p++) {
|
||||
thumbnails.push(this.getThumbnailUrl(p))
|
||||
}
|
||||
return thumbnails
|
||||
},
|
||||
visibleThumbnails(): String[] {
|
||||
visibleThumbnails (): String[] {
|
||||
let a: number = (this.page - 1) * this.perPage
|
||||
let b: number = this.page * this.perPage
|
||||
return this.thumbnails.slice(a, b)
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
updateInput() {
|
||||
updateInput () {
|
||||
this.$emit('input', this.input)
|
||||
},
|
||||
goTo(page: number) {
|
||||
goTo (page: number) {
|
||||
this.$emit('go', page)
|
||||
},
|
||||
getThumbnailUrl(page: number): string {
|
||||
getThumbnailUrl (page: number): string {
|
||||
return bookPageThumbnailUrl(this.bookId, page)
|
||||
},
|
||||
},
|
||||
|
|
|
|||
|
|
@ -39,7 +39,7 @@
|
|||
<tr>
|
||||
<td class="font-weight-medium">{{ $t('dialog.transient_book_details.label_format') }}</td>
|
||||
<td :class="rightBook ? 'diff' : ''">{{ getBookFormatFromMediaType(leftBook.mediaType).type }}</td>
|
||||
<td v-if="rightBook">{{ getBookFormatFromMedia(rightBook.media).type }}</td>
|
||||
<td v-if="rightBook">{{ getBookFormatFromMediaType(rightBook.media.mediaType).type }}</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
|
|
@ -69,7 +69,7 @@
|
|||
import Vue, {PropType} from 'vue'
|
||||
import {TransientBookDto} from '@/types/komga-transientbooks'
|
||||
import {BookDto, PageDto} from '@/types/komga-books'
|
||||
import {getBookFormatFromMedia, getBookFormatFromMediaType} from '@/functions/book-format'
|
||||
import {getBookFormatFromMediaType} from '@/functions/book-format'
|
||||
import PagesTable from '@/components/PagesTable.vue'
|
||||
|
||||
export default Vue.extend({
|
||||
|
|
@ -78,8 +78,7 @@ export default Vue.extend({
|
|||
data: () => {
|
||||
return {
|
||||
modal: false,
|
||||
getBookFormatFromMediaType: getBookFormatFromMediaType,
|
||||
getBookFormatFromMedia: getBookFormatFromMedia,
|
||||
getBookFormatFromMediaType,
|
||||
}
|
||||
},
|
||||
props: {
|
||||
|
|
|
|||
|
|
@ -50,12 +50,24 @@
|
|||
|
||||
<v-row>
|
||||
<v-col>
|
||||
<span>{{ $t('common.roles') }}</span>
|
||||
<v-checkbox v-for="role in userRoles" :key="role.value"
|
||||
v-model="form.roles"
|
||||
:label="role.text"
|
||||
:value="role.value"
|
||||
hide-details
|
||||
<span>{{ $t('dialog.add_user.label_roles') }}</span>
|
||||
<v-checkbox
|
||||
v-model="form.roles"
|
||||
:label="$t('dialog.add_user.field_role_administrator')"
|
||||
:value="UserRoles.ADMIN"
|
||||
hide-details
|
||||
/>
|
||||
<v-checkbox
|
||||
v-model="form.roles"
|
||||
:label="$t('dialog.add_user.field_role_page_streaming')"
|
||||
:value="UserRoles.PAGE_STREAMING"
|
||||
hide-details
|
||||
/>
|
||||
<v-checkbox
|
||||
v-model="form.roles"
|
||||
:label="$t('dialog.add_user.field_role_file_download')"
|
||||
:value="UserRoles.FILE_DOWNLOAD"
|
||||
hide-details
|
||||
/>
|
||||
</v-col>
|
||||
</v-row>
|
||||
|
|
@ -83,6 +95,7 @@ export default Vue.extend({
|
|||
name: 'UserAddDialog',
|
||||
data: function () {
|
||||
return {
|
||||
UserRoles,
|
||||
modalAddUser: true,
|
||||
showPassword: false,
|
||||
dialogTitle: this.$i18n.t('dialog.add_user.dialog_title').toString(),
|
||||
|
|
@ -105,14 +118,6 @@ export default Vue.extend({
|
|||
password: {required},
|
||||
},
|
||||
},
|
||||
computed: {
|
||||
userRoles(): any[] {
|
||||
return Object.keys(UserRoles).map(x => ({
|
||||
text: this.$t(`user_roles.${x}`),
|
||||
value: x,
|
||||
}))
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
getErrors(fieldName: string): string[] {
|
||||
const errors = [] as string[]
|
||||
|
|
|
|||
|
|
@ -15,11 +15,23 @@
|
|||
|
||||
<v-row>
|
||||
<v-col>
|
||||
<v-checkbox v-for="role in userRoles" :key="role.value"
|
||||
v-model="roles"
|
||||
:label="role.text"
|
||||
:value="role.value"
|
||||
hide-details
|
||||
<v-checkbox
|
||||
v-model="roles"
|
||||
:label="$t('dialog.add_user.field_role_administrator')"
|
||||
:value="UserRoles.ADMIN"
|
||||
hide-details
|
||||
/>
|
||||
<v-checkbox
|
||||
v-model="roles"
|
||||
:label="$t('dialog.add_user.field_role_page_streaming')"
|
||||
:value="UserRoles.PAGE_STREAMING"
|
||||
hide-details
|
||||
/>
|
||||
<v-checkbox
|
||||
v-model="roles"
|
||||
:label="$t('dialog.add_user.field_role_file_download')"
|
||||
:value="UserRoles.FILE_DOWNLOAD"
|
||||
hide-details
|
||||
/>
|
||||
</v-col>
|
||||
</v-row>
|
||||
|
|
@ -43,12 +55,14 @@
|
|||
import {UserRoles} from '@/types/enum-users'
|
||||
import Vue from 'vue'
|
||||
import {ERROR} from '@/types/events'
|
||||
import {LibraryDto} from '@/types/komga-libraries'
|
||||
import {UserDto, UserUpdateDto} from '@/types/komga-users'
|
||||
|
||||
export default Vue.extend({
|
||||
name: 'UserEditDialog',
|
||||
data: () => {
|
||||
return {
|
||||
UserRoles,
|
||||
modal: false,
|
||||
roles: [] as string[],
|
||||
}
|
||||
|
|
@ -72,11 +86,8 @@ export default Vue.extend({
|
|||
},
|
||||
},
|
||||
computed: {
|
||||
userRoles(): any[] {
|
||||
return Object.keys(UserRoles).map(x => ({
|
||||
text: this.$t(`user_roles.${x}`),
|
||||
value: x,
|
||||
}))
|
||||
libraries(): LibraryDto[] {
|
||||
return this.$store.state.komgaLibraries.libraries
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
|
|
|
|||
|
|
@ -1,70 +0,0 @@
|
|||
<template>
|
||||
<div>
|
||||
<v-menu offset-y>
|
||||
<template v-slot:activator="{ on }">
|
||||
<v-btn icon v-on="on" @click.prevent="">
|
||||
<v-icon>mdi-dots-vertical</v-icon>
|
||||
</v-btn>
|
||||
</template>
|
||||
<v-list dense>
|
||||
<v-list-item @click="reorder">
|
||||
<v-list-item-title>{{ $t('common.reorder') }}</v-list-item-title>
|
||||
</v-list-item>
|
||||
<v-list-item @click="scan(false)" v-if="isAdmin">
|
||||
<v-list-item-title>{{ $t('server.server_management.button_scan_libraries') }}</v-list-item-title>
|
||||
</v-list-item>
|
||||
<v-list-item @click="scan(true)" class="list-warning" v-if="isAdmin">
|
||||
<v-list-item-title>{{ $t('server.server_management.button_scan_libraries_deep') }}</v-list-item-title>
|
||||
</v-list-item>
|
||||
<v-list-item @click="confirmEmptyTrash = true" v-if="isAdmin">
|
||||
<v-list-item-title>{{ $t('server.server_management.button_empty_trash') }}</v-list-item-title>
|
||||
</v-list-item>
|
||||
</v-list>
|
||||
</v-menu>
|
||||
|
||||
<confirmation-dialog
|
||||
v-model="confirmEmptyTrash"
|
||||
:title="$t('dialog.empty_trash.title')"
|
||||
:body="$t('dialog.empty_trash.body')"
|
||||
:button-confirm="$t('dialog.empty_trash.button_confirm')"
|
||||
@confirm="emptyTrash"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
<script lang="ts">
|
||||
import Vue from 'vue'
|
||||
import ConfirmationDialog from '@/components/dialogs/ConfirmationDialog.vue'
|
||||
|
||||
export default Vue.extend({
|
||||
name: 'LibrariesActionsMenu',
|
||||
components: {ConfirmationDialog},
|
||||
data: () => {
|
||||
return {
|
||||
confirmEmptyTrash: false,
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
isAdmin(): boolean {
|
||||
return this.$store.getters.meAdmin
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
reorder() {
|
||||
this.$emit('reorder')
|
||||
},
|
||||
scan(scanDeep: boolean) {
|
||||
this.$store.state.komgaLibraries.libraries.forEach(library => {
|
||||
this.$komgaLibraries.scanLibrary(library, scanDeep)
|
||||
})
|
||||
},
|
||||
emptyTrash() {
|
||||
this.$store.state.komgaLibraries.libraries.forEach(library => {
|
||||
this.$komgaLibraries.emptyTrash(library)
|
||||
})
|
||||
},
|
||||
},
|
||||
})
|
||||
</script>
|
||||
<style scoped>
|
||||
@import "../../styles/list-warning.css";
|
||||
</style>
|
||||
|
|
@ -1,34 +1,32 @@
|
|||
<template>
|
||||
<div>
|
||||
<v-menu offset-y>
|
||||
<v-menu offset-y v-if="isAdmin">
|
||||
<template v-slot:activator="{ on }">
|
||||
<v-btn icon v-on="on" @click.prevent="">
|
||||
<v-icon>mdi-dots-vertical</v-icon>
|
||||
</v-btn>
|
||||
</template>
|
||||
<v-list dense>
|
||||
<v-list-item @click="scan(false)" v-if="isAdmin">
|
||||
<v-list-item @click="scan(false)">
|
||||
<v-list-item-title>{{ $t('menu.scan_library_files') }}</v-list-item-title>
|
||||
</v-list-item>
|
||||
<v-list-item @click="scan(true)" class="list-warning" v-if="isAdmin">
|
||||
<v-list-item @click="scan(true)" class="list-warning">
|
||||
<v-list-item-title>{{ $t('menu.scan_library_files_deep') }}</v-list-item-title>
|
||||
</v-list-item>
|
||||
<v-list-item @click="confirmAnalyzeModal = true" v-if="isAdmin">
|
||||
<v-list-item @click="confirmAnalyzeModal = true">
|
||||
<v-list-item-title>{{ $t('menu.analyze') }}</v-list-item-title>
|
||||
</v-list-item>
|
||||
<v-list-item @click="confirmRefreshMetadataModal = true" v-if="isAdmin">
|
||||
<v-list-item @click="confirmRefreshMetadataModal = true">
|
||||
<v-list-item-title>{{ $t('menu.refresh_metadata') }}</v-list-item-title>
|
||||
</v-list-item>
|
||||
<v-list-item @click="confirmEmptyTrash = true" v-if="isAdmin">
|
||||
<v-list-item @click="confirmEmptyTrash = true">
|
||||
<v-list-item-title>{{ $t('menu.empty_trash') }}</v-list-item-title>
|
||||
</v-list-item>
|
||||
<v-list-item @click="edit" v-if="isAdmin">
|
||||
<v-list-item @click="edit">
|
||||
<v-list-item-title>{{ $t('menu.edit') }}</v-list-item-title>
|
||||
</v-list-item>
|
||||
<v-list-item @click="promptDeleteLibrary"
|
||||
class="list-danger"
|
||||
v-if="isAdmin"
|
||||
>
|
||||
class="list-danger">
|
||||
<v-list-item-title>{{ $t('menu.delete') }}</v-list-item-title>
|
||||
</v-list-item>
|
||||
</v-list>
|
||||
|
|
|
|||
|
|
@ -38,7 +38,6 @@ import {ReadStatus} from '@/types/enum-books'
|
|||
import Vue from 'vue'
|
||||
import {BookDto} from '@/types/komga-books'
|
||||
import {SeriesDto} from '@/types/komga-series'
|
||||
import {BookSearch, SearchConditionSeriesId, SearchOperatorIs} from '@/types/komga-search'
|
||||
|
||||
export default Vue.extend({
|
||||
name: 'OneShotActionsMenu',
|
||||
|
|
@ -94,9 +93,7 @@ export default Vue.extend({
|
|||
this.$store.dispatch('dialogAddSeriesToCollection', [this.seriesId])
|
||||
},
|
||||
async addToReadList() {
|
||||
if (!this.book && !this.localBookId) this.localBookId = (await this.$komgaBooks.getBooksList({
|
||||
condition: new SearchConditionSeriesId(new SearchOperatorIs(this.seriesId)),
|
||||
} as BookSearch)).content[0].id
|
||||
if (!this.book && !this.localBookId) this.localBookId = (await this.$komgaSeries.getBooks(this.seriesId)).content[0].id
|
||||
this.$store.dispatch('dialogAddBooksToReadList', [this.book?.id || this.localBookId])
|
||||
},
|
||||
async markRead() {
|
||||
|
|
|
|||
|
|
@ -16,9 +16,6 @@
|
|||
<v-list-item @click="addToCollection" v-if="isAdmin">
|
||||
<v-list-item-title>{{ $t('menu.add_to_collection') }}</v-list-item-title>
|
||||
</v-list-item>
|
||||
<v-list-item @click="addToReadList" v-if="isAdmin">
|
||||
<v-list-item-title>{{ $t('menu.add_to_readlist') }}</v-list-item-title>
|
||||
</v-list-item>
|
||||
<v-list-item @click="markRead" v-if="!isRead">
|
||||
<v-list-item-title>{{ $t('menu.mark_read') }}</v-list-item-title>
|
||||
</v-list-item>
|
||||
|
|
@ -35,7 +32,6 @@
|
|||
<script lang="ts">
|
||||
import Vue from 'vue'
|
||||
import {SeriesDto} from '@/types/komga-series'
|
||||
import {BookSearch, SearchConditionSeriesId, SearchOperatorIs} from '@/types/komga-search'
|
||||
|
||||
export default Vue.extend({
|
||||
name: 'SeriesActionsMenu',
|
||||
|
|
@ -80,12 +76,6 @@ export default Vue.extend({
|
|||
addToCollection() {
|
||||
this.$store.dispatch('dialogAddSeriesToCollection', [this.series.id])
|
||||
},
|
||||
async addToReadList() {
|
||||
const books = await this.$komgaBooks.getBooksList({
|
||||
condition: new SearchConditionSeriesId(new SearchOperatorIs(this.series.id)),
|
||||
} as BookSearch, {unpaged: true, sort: ['metadata.numberSort']})
|
||||
this.$store.dispatch('dialogAddBooksToReadList', books.content.map(b => b.id))
|
||||
},
|
||||
async markRead() {
|
||||
await this.$komgaSeries.markAsRead(this.series.id)
|
||||
},
|
||||
|
|
|
|||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue