mirror of
https://github.com/moku-project/Moku.git
synced 2026-06-13 09:19:56 -05:00
Compare commits
13 Commits
31a19687ce
..
main
| Author | SHA1 | Date | |
|---|---|---|---|
| d6ea1fab67 | |||
| bf19ee02bc | |||
| 09d794da96 | |||
| baece20f46 | |||
| b170a151f0 | |||
| 6a84280db0 | |||
| be38d87bec | |||
| ab9305e6ab | |||
| ceb9ba12d7 | |||
| 2fa33bc928 | |||
| 5c703bdba5 | |||
| a041b182e5 | |||
| 9dad1fb329 |
@@ -1,11 +1,15 @@
|
|||||||
# Sourced by CI jobs that need versions from nix/versions.nix.
|
# Sourced by CI jobs that need versions from nix/versions.nix.
|
||||||
# Usage: source .github/read_versions.sh
|
# Usage: source .github/read_versions.sh
|
||||||
# Exports: MOKU_VERSION SUWA_VERSION SUWA_HASH_LINUX SUWA_HASH_MACOS_ARM64 SUWA_HASH_MACOS_X64 SUWA_HASH_WINDOWS
|
# Exports: MOKU_VERSION SUWA_VERSION SUWA_HASH_LINUX SUWA_HASH_MACOS_ARM64 SUWA_HASH_MACOS_X64 SUWA_HASH_WINDOWS
|
||||||
|
#
|
||||||
|
# Uses only POSIX -E grep (no -P) so this works on both GNU grep (Linux/Windows)
|
||||||
|
# and BSD grep (macOS), which does not support -P/PCRE.
|
||||||
|
|
||||||
_nix="$( cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd )/nix/versions.nix"
|
_nix="$( cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd )/nix/versions.nix"
|
||||||
_t=$(cat "$_nix")
|
_t=$(cat "$_nix")
|
||||||
|
|
||||||
_pick() { echo "$_t" | grep -oP "${1}\s*=\s*\"\K[^\"]+"; }
|
# Match `key = "value"` with -E, then strip the surrounding quotes.
|
||||||
|
_pick() { echo "$_t" | grep -oE "${1}"'[[:space:]]*=[[:space:]]*"[^"]+"' | grep -oE '"[^"]+"' | tr -d '"'; }
|
||||||
|
|
||||||
export MOKU_VERSION=$(_pick "moku")
|
export MOKU_VERSION=$(_pick "moku")
|
||||||
export SUWA_VERSION=$(_pick "version")
|
export SUWA_VERSION=$(_pick "version")
|
||||||
|
|||||||
@@ -0,0 +1,115 @@
|
|||||||
|
name: Build Flatpak
|
||||||
|
|
||||||
|
on:
|
||||||
|
workflow_dispatch:
|
||||||
|
inputs:
|
||||||
|
version:
|
||||||
|
description: "Version to build (e.g. 0.9.0)"
|
||||||
|
required: true
|
||||||
|
|
||||||
|
permissions:
|
||||||
|
contents: write
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
flatpak:
|
||||||
|
name: Build Flatpak bundle
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
|
||||||
|
- name: Free up disk space
|
||||||
|
run: |
|
||||||
|
sudo rm -rf /usr/local/lib/android /opt/ghc /usr/share/dotnet /opt/hostedtoolcache/CodeQL
|
||||||
|
sudo docker image prune -af || true
|
||||||
|
|
||||||
|
- uses: pnpm/action-setup@v4
|
||||||
|
with: { version: 10 }
|
||||||
|
- uses: actions/setup-node@v4
|
||||||
|
with:
|
||||||
|
node-version: 22
|
||||||
|
cache: pnpm
|
||||||
|
|
||||||
|
- name: Build frontend and pack tarball
|
||||||
|
run: |
|
||||||
|
pnpm install --frozen-lockfile
|
||||||
|
pnpm build:static
|
||||||
|
tar -czf packaging/frontend-dist.tar.gz -C dist .
|
||||||
|
|
||||||
|
- name: Compute frontend-dist sha256
|
||||||
|
run: |
|
||||||
|
SHA=$(sha256sum packaging/frontend-dist.tar.gz | awk '{print $1}')
|
||||||
|
echo "FRONTEND_SHA=$SHA" >> $GITHUB_ENV
|
||||||
|
echo "frontend-dist.tar.gz sha256: $SHA"
|
||||||
|
|
||||||
|
- name: Patch frontend-dist sha256 in flatpak manifest
|
||||||
|
run: |
|
||||||
|
python3 -c "
|
||||||
|
import re, pathlib, os
|
||||||
|
p = pathlib.Path('io.github.moku_project.Moku.yml')
|
||||||
|
content = p.read_text()
|
||||||
|
# Replace the sha256 line that follows the frontend-dist.tar.gz source entry
|
||||||
|
content = re.sub(
|
||||||
|
r'(path: packaging/frontend-dist\.tar\.gz\n\s+sha256: )[0-9a-f]{64}',
|
||||||
|
r'\g<1>' + os.environ['FRONTEND_SHA'],
|
||||||
|
content
|
||||||
|
)
|
||||||
|
p.write_text(content)
|
||||||
|
"
|
||||||
|
|
||||||
|
- name: Install flatpak tooling
|
||||||
|
run: |
|
||||||
|
sudo apt-get update
|
||||||
|
sudo apt-get install -y flatpak flatpak-builder
|
||||||
|
flatpak --user remote-add --if-not-exists flathub https://flathub.org/repo/flathub.flatpakrepo
|
||||||
|
|
||||||
|
- name: Cache flatpak runtimes/SDKs
|
||||||
|
uses: actions/cache@v4
|
||||||
|
with:
|
||||||
|
path: ~/.local/share/flatpak
|
||||||
|
key: flatpak-runtimes-gnome48-rust-stable
|
||||||
|
|
||||||
|
- name: Install runtime and SDK
|
||||||
|
run: |
|
||||||
|
flatpak --user install -y --noninteractive flathub \
|
||||||
|
org.gnome.Platform//48 \
|
||||||
|
org.gnome.Sdk//48
|
||||||
|
|
||||||
|
- name: Build flatpak
|
||||||
|
run: |
|
||||||
|
rm -rf build-dir repo
|
||||||
|
flatpak-builder \
|
||||||
|
--user \
|
||||||
|
--install-deps-from=flathub \
|
||||||
|
--repo=repo \
|
||||||
|
--force-clean \
|
||||||
|
build-dir \
|
||||||
|
io.github.moku_project.Moku.yml
|
||||||
|
|
||||||
|
- name: Bundle flatpak
|
||||||
|
run: |
|
||||||
|
flatpak build-bundle \
|
||||||
|
--runtime-repo=https://flathub.org/repo/flathub.flatpakrepo \
|
||||||
|
repo \
|
||||||
|
moku.flatpak \
|
||||||
|
io.github.moku_project.Moku
|
||||||
|
|
||||||
|
- name: Upload Flatpak artifact to release
|
||||||
|
env:
|
||||||
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
run: |
|
||||||
|
# Poll for up to 10 minutes — the release is created by the Windows workflow
|
||||||
|
# which may still be building when the flatpak bundle finishes.
|
||||||
|
for i in $(seq 1 40); do
|
||||||
|
RELEASE_ID=$(curl -s -H "Authorization: Bearer $GITHUB_TOKEN" \
|
||||||
|
"https://api.github.com/repos/moku-project/Moku/releases" \
|
||||||
|
| jq -r '.[] | select(.tag_name == "v${{ github.event.inputs.version }}") | .id' | head -1)
|
||||||
|
[ -n "$RELEASE_ID" ] && break
|
||||||
|
echo "Waiting for release... attempt $i/40"; sleep 15
|
||||||
|
done
|
||||||
|
[ -z "$RELEASE_ID" ] && { echo "ERROR: release not found after polling"; exit 1; }
|
||||||
|
|
||||||
|
curl -s -X POST \
|
||||||
|
-H "Authorization: Bearer $GITHUB_TOKEN" \
|
||||||
|
-H "Content-Type: application/octet-stream" \
|
||||||
|
--data-binary @"moku.flatpak" \
|
||||||
|
"https://uploads.github.com/repos/moku-project/Moku/releases/$RELEASE_ID/assets?name=moku.flatpak"
|
||||||
@@ -17,7 +17,7 @@ jobs:
|
|||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v4
|
||||||
- uses: pnpm/action-setup@v4
|
- uses: pnpm/action-setup@v4
|
||||||
with: { version: latest }
|
with: { version: 10 }
|
||||||
- uses: actions/setup-node@v4
|
- uses: actions/setup-node@v4
|
||||||
with:
|
with:
|
||||||
node-version: 22
|
node-version: 22
|
||||||
@@ -52,7 +52,7 @@ jobs:
|
|||||||
with: { workspaces: src-tauri }
|
with: { workspaces: src-tauri }
|
||||||
|
|
||||||
- uses: pnpm/action-setup@v4
|
- uses: pnpm/action-setup@v4
|
||||||
with: { version: latest }
|
with: { version: 10 }
|
||||||
- uses: actions/setup-node@v4
|
- uses: actions/setup-node@v4
|
||||||
with:
|
with:
|
||||||
node-version: 22
|
node-version: 22
|
||||||
@@ -91,7 +91,13 @@ jobs:
|
|||||||
|
|
||||||
- name: Patch tauri.conf.json for CI
|
- name: Patch tauri.conf.json for CI
|
||||||
run: |
|
run: |
|
||||||
sed -i '' 's/"beforeBuildCommand": "pnpm build"/"beforeBuildCommand": ""/' src-tauri/tauri.conf.json
|
python3 -c "
|
||||||
|
import json, pathlib
|
||||||
|
p = pathlib.Path('src-tauri/tauri.conf.json')
|
||||||
|
c = json.loads(p.read_text())
|
||||||
|
c.setdefault('build', {})['beforeBuildCommand'] = ''
|
||||||
|
p.write_text(json.dumps(c, indent=2))
|
||||||
|
"
|
||||||
|
|
||||||
- name: Build Tauri app (aarch64)
|
- name: Build Tauri app (aarch64)
|
||||||
run: |
|
run: |
|
||||||
|
|||||||
@@ -0,0 +1,51 @@
|
|||||||
|
name: Build Static WebUI
|
||||||
|
|
||||||
|
on:
|
||||||
|
workflow_dispatch:
|
||||||
|
inputs:
|
||||||
|
version:
|
||||||
|
description: "Version to build (e.g. 0.9.0)"
|
||||||
|
required: true
|
||||||
|
|
||||||
|
permissions:
|
||||||
|
contents: write
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
build:
|
||||||
|
name: Build static frontend
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
- uses: pnpm/action-setup@v4
|
||||||
|
with: { version: 10 }
|
||||||
|
- uses: actions/setup-node@v4
|
||||||
|
with:
|
||||||
|
node-version: 22
|
||||||
|
cache: pnpm
|
||||||
|
|
||||||
|
- run: pnpm install --frozen-lockfile
|
||||||
|
- run: pnpm build:static
|
||||||
|
|
||||||
|
- name: Zip static build
|
||||||
|
run: |
|
||||||
|
cd dist
|
||||||
|
zip -r "../moku-webui-${{ github.event.inputs.version }}.zip" .
|
||||||
|
|
||||||
|
- name: Upload WebUI artifact to release
|
||||||
|
env:
|
||||||
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
run: |
|
||||||
|
for i in $(seq 1 12); do
|
||||||
|
RELEASE_ID=$(curl -s -H "Authorization: Bearer $GITHUB_TOKEN" \
|
||||||
|
"https://api.github.com/repos/moku-project/Moku/releases" \
|
||||||
|
| jq -r '.[] | select(.tag_name == "v${{ github.event.inputs.version }}") | .id' | head -1)
|
||||||
|
[ -n "$RELEASE_ID" ] && break
|
||||||
|
echo "Waiting for release... attempt $i"; sleep 15
|
||||||
|
done
|
||||||
|
[ -z "$RELEASE_ID" ] && { echo "ERROR: release not found"; exit 1; }
|
||||||
|
|
||||||
|
curl -s -X POST \
|
||||||
|
-H "Authorization: Bearer $GITHUB_TOKEN" \
|
||||||
|
-H "Content-Type: application/zip" \
|
||||||
|
--data-binary @"moku-webui-${{ github.event.inputs.version }}.zip" \
|
||||||
|
"https://uploads.github.com/repos/moku-project/Moku/releases/$RELEASE_ID/assets?name=moku-webui-${{ github.event.inputs.version }}.zip"
|
||||||
@@ -17,7 +17,7 @@ jobs:
|
|||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v4
|
||||||
- uses: pnpm/action-setup@v4
|
- uses: pnpm/action-setup@v4
|
||||||
with: { version: latest }
|
with: { version: 10 }
|
||||||
- uses: actions/setup-node@v4
|
- uses: actions/setup-node@v4
|
||||||
with:
|
with:
|
||||||
node-version: 22
|
node-version: 22
|
||||||
@@ -51,7 +51,7 @@ jobs:
|
|||||||
with: { workspaces: src-tauri }
|
with: { workspaces: src-tauri }
|
||||||
|
|
||||||
- uses: pnpm/action-setup@v4
|
- uses: pnpm/action-setup@v4
|
||||||
with: { version: latest }
|
with: { version: 10 }
|
||||||
- uses: actions/setup-node@v4
|
- uses: actions/setup-node@v4
|
||||||
with:
|
with:
|
||||||
node-version: 22
|
node-version: 22
|
||||||
@@ -88,7 +88,13 @@ jobs:
|
|||||||
- name: Patch tauri.conf.json for CI
|
- name: Patch tauri.conf.json for CI
|
||||||
shell: bash
|
shell: bash
|
||||||
run: |
|
run: |
|
||||||
sed -i 's/"beforeBuildCommand": "pnpm build"/"beforeBuildCommand": ""/' src-tauri/tauri.conf.json
|
python3 -c "
|
||||||
|
import json, pathlib
|
||||||
|
p = pathlib.Path('src-tauri/tauri.conf.json')
|
||||||
|
c = json.loads(p.read_text())
|
||||||
|
c.setdefault('build', {})['beforeBuildCommand'] = ''
|
||||||
|
p.write_text(json.dumps(c, indent=2))
|
||||||
|
"
|
||||||
|
|
||||||
- name: Delete existing draft release
|
- name: Delete existing draft release
|
||||||
shell: bash
|
shell: bash
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
pkgname=moku
|
pkgname=moku
|
||||||
pkgver=0.9.4
|
pkgver=0.10.0
|
||||||
pkgrel=1
|
pkgrel=1
|
||||||
pkgdesc="Native Linux manga reader frontend for Suwayomi-Server"
|
pkgdesc="Native Linux manga reader frontend for Suwayomi-Server"
|
||||||
arch=('x86_64')
|
arch=('x86_64')
|
||||||
@@ -25,7 +25,7 @@ source=(
|
|||||||
)
|
)
|
||||||
noextract=("Suwayomi-Server-v2.1.2087.jar")
|
noextract=("Suwayomi-Server-v2.1.2087.jar")
|
||||||
sha256sums=(
|
sha256sums=(
|
||||||
'fc1c8268b812e70e56460c8930ca8ae83bcd30eea5903ddfef4e30a3a9a5c1cc'
|
'589b389b356a48d54ad4022e68eaac165a1de654d0b98edec79ebbab2c4a1275'
|
||||||
'f589a422674252394c13b289a9c8be691905bf583efb7f4d5f1501ae5e91e6b3'
|
'f589a422674252394c13b289a9c8be691905bf583efb7f4d5f1501ae5e91e6b3'
|
||||||
)
|
)
|
||||||
b2sums=(
|
b2sums=(
|
||||||
|
|||||||
@@ -80,6 +80,9 @@ modules:
|
|||||||
|
|
||||||
- name: libayatana-indicator
|
- name: libayatana-indicator
|
||||||
buildsystem: cmake-ninja
|
buildsystem: cmake-ninja
|
||||||
|
build-options:
|
||||||
|
env:
|
||||||
|
PKG_CONFIG_PATH: /app/lib/pkgconfig:/app/share/pkgconfig
|
||||||
config-opts:
|
config-opts:
|
||||||
- -DENABLE_TESTS=OFF
|
- -DENABLE_TESTS=OFF
|
||||||
- -DGSETTINGS_COMPILE=OFF
|
- -DGSETTINGS_COMPILE=OFF
|
||||||
@@ -90,6 +93,9 @@ modules:
|
|||||||
|
|
||||||
- name: libayatana-appindicator
|
- name: libayatana-appindicator
|
||||||
buildsystem: cmake-ninja
|
buildsystem: cmake-ninja
|
||||||
|
build-options:
|
||||||
|
env:
|
||||||
|
PKG_CONFIG_PATH: /app/lib/pkgconfig:/app/share/pkgconfig
|
||||||
config-opts:
|
config-opts:
|
||||||
- -DENABLE_TESTS=OFF
|
- -DENABLE_TESTS=OFF
|
||||||
- -DENABLE_BINDINGS_MONO=OFF
|
- -DENABLE_BINDINGS_MONO=OFF
|
||||||
@@ -232,6 +238,7 @@ modules:
|
|||||||
XDG_DATA_HOME: /run/build/moku/xdg-data
|
XDG_DATA_HOME: /run/build/moku/xdg-data
|
||||||
TAURI_SKIP_DEVSERVER_CHECK: 'true'
|
TAURI_SKIP_DEVSERVER_CHECK: 'true'
|
||||||
PKG_CONFIG_PATH: /usr/lib/pkgconfig:/usr/share/pkgconfig:/app/lib/pkgconfig
|
PKG_CONFIG_PATH: /usr/lib/pkgconfig:/usr/share/pkgconfig:/app/lib/pkgconfig
|
||||||
|
TAURI_CONFIG: '{"build":{"devUrl":null,"frontendDist":"../dist"},"app":{"windows":[{"devtools":false}]},"bundle":{"externalBin":[]}}'
|
||||||
build-commands:
|
build-commands:
|
||||||
- tar -xzf frontend-dist.tar.gz
|
- tar -xzf frontend-dist.tar.gz
|
||||||
- . /usr/lib/sdk/rust-stable/enable.sh && PKG_CONFIG_PATH=/usr/lib/pkgconfig:/usr/share/pkgconfig:/app/lib/pkgconfig cargo build --release --manifest-path src-tauri/Cargo.toml
|
- . /usr/lib/sdk/rust-stable/enable.sh && PKG_CONFIG_PATH=/usr/lib/pkgconfig:/usr/share/pkgconfig:/app/lib/pkgconfig cargo build --release --manifest-path src-tauri/Cargo.toml
|
||||||
@@ -244,11 +251,10 @@ modules:
|
|||||||
sources:
|
sources:
|
||||||
- type: git
|
- type: git
|
||||||
url: https://github.com/moku-project/Moku.git
|
url: https://github.com/moku-project/Moku.git
|
||||||
tag: v0.9.4
|
commit: baece20f467d2c7d4cebaa9ea8892980aa93aa10
|
||||||
commit: 239960683b6c7f1347e1798b0e179a8a46628728
|
|
||||||
- type: file
|
- type: file
|
||||||
path: packaging/frontend-dist.tar.gz
|
path: packaging/frontend-dist.tar.gz
|
||||||
sha256: 7db288b4b54277aa82b6ec5b21fc31a1e71f8246c50a74777500083b806c1fa5
|
sha256: 676ec2273ffd9a69248849c5d51dc4d59a5d5b68fbba7a4fe7e7b572a5f25f14
|
||||||
- packaging/cargo-sources.json
|
- packaging/cargo-sources.json
|
||||||
- type: inline
|
- type: inline
|
||||||
dest: src-tauri/.cargo
|
dest: src-tauri/.cargo
|
||||||
|
|||||||
+16
-15
@@ -7,6 +7,7 @@
|
|||||||
gnused
|
gnused
|
||||||
coreutils
|
coreutils
|
||||||
git
|
git
|
||||||
|
xxd
|
||||||
rustToolchain
|
rustToolchain
|
||||||
nodejs_22
|
nodejs_22
|
||||||
pnpm
|
pnpm
|
||||||
@@ -85,30 +86,30 @@ PYEOF
|
|||||||
|
|
||||||
if [[ $# -ge 2 ]]; then
|
if [[ $# -ge 2 ]]; then
|
||||||
SUWA_VER="$2"
|
SUWA_VER="$2"
|
||||||
BASE="https://github.com/Suwayomi/Suwayomi-Server-preview/releases/download/v${SUWA_VER}"
|
BASE="https://github.com/Suwayomi/Suwayomi-Server-preview/releases/download/v''${SUWA_VER}"
|
||||||
|
|
||||||
echo "Fetching Suwayomi v${SUWA_VER} hashes (5 downloads)..."
|
echo "Fetching Suwayomi v''${SUWA_VER} hashes (5 downloads)..."
|
||||||
|
|
||||||
sha_of() { curl -fsSL "$1" | sha256sum | awk '{print $1}'; }
|
sha_of() { curl -fsSL "$1" | sha256sum | awk '{print $1}'; }
|
||||||
to_sri() { echo "$1" | xxd -r -p | base64 -w0 | sed 's/^/sha256-/'; }
|
to_sri() { echo "$1" | xxd -r -p | base64 -w0 | sed 's/^/sha256-/'; }
|
||||||
|
|
||||||
JAR_SHA=$(sha_of "${BASE}/Suwayomi-Server-v${SUWA_VER}.jar")
|
JAR_SHA=$(sha_of "''${BASE}/Suwayomi-Server-v''${SUWA_VER}.jar")
|
||||||
WIN_SHA=$(sha_of "${BASE}/Suwayomi-Server-v${SUWA_VER}-windows-x64.zip")
|
WIN_SHA=$(sha_of "''${BASE}/Suwayomi-Server-v''${SUWA_VER}-windows-x64.zip")
|
||||||
LINUX_SHA=$(sha_of "${BASE}/Suwayomi-Server-v${SUWA_VER}-linux-x64.tar.gz")
|
LINUX_SHA=$(sha_of "''${BASE}/Suwayomi-Server-v''${SUWA_VER}-linux-x64.tar.gz")
|
||||||
ARM64_SHA=$(sha_of "${BASE}/Suwayomi-Server-v${SUWA_VER}-macOS-arm64.tar.gz")
|
ARM64_SHA=$(sha_of "''${BASE}/Suwayomi-Server-v''${SUWA_VER}-macOS-arm64.tar.gz")
|
||||||
X64_SHA=$(sha_of "${BASE}/Suwayomi-Server-v${SUWA_VER}-macOS-x64.tar.gz")
|
X64_SHA=$(sha_of "''${BASE}/Suwayomi-Server-v''${SUWA_VER}-macOS-x64.tar.gz")
|
||||||
|
|
||||||
JAR_SRI=$(to_sri "$JAR_SHA")
|
JAR_SRI=$(to_sri "$JAR_SHA")
|
||||||
|
|
||||||
sed -i "s/version = \"[^\"]*\"/version = \"${SUWA_VER}\"/" "$VERSIONS"
|
sed -i "s/version = \"[^\"]*\"/version = \"''${SUWA_VER}\"/" "$VERSIONS"
|
||||||
sed -i "s|hash = \"sha256-[^\"]*\"|hash = \"${JAR_SRI}\"|" "$VERSIONS"
|
sed -i "s|hash = \"sha256-[^\"]*\"|hash = \"''${JAR_SRI}\"|" "$VERSIONS"
|
||||||
sed -i "s|windowsHash = \"[^\"]*\"|windowsHash = \"${WIN_SHA}\"|" "$VERSIONS"
|
sed -i "s|windowsHash = \"[^\"]*\"|windowsHash = \"''${WIN_SHA}\"|" "$VERSIONS"
|
||||||
sed -i "s|linuxHash = \"[^\"]*\"|linuxHash = \"${LINUX_SHA}\"|" "$VERSIONS"
|
sed -i "s|linuxHash = \"[^\"]*\"|linuxHash = \"''${LINUX_SHA}\"|" "$VERSIONS"
|
||||||
sed -i "s|macosArm64Hash = \"[^\"]*\"|macosArm64Hash = \"${ARM64_SHA}\"|" "$VERSIONS"
|
sed -i "s|macosArm64Hash = \"[^\"]*\"|macosArm64Hash = \"''${ARM64_SHA}\"|" "$VERSIONS"
|
||||||
sed -i "s|macosX64Hash = \"[^\"]*\"|macosX64Hash = \"${X64_SHA}\"|" "$VERSIONS"
|
sed -i "s|macosX64Hash = \"[^\"]*\"|macosX64Hash = \"''${X64_SHA}\"|" "$VERSIONS"
|
||||||
|
|
||||||
sed -i "s|Suwayomi-Server-preview/releases/download/v[^/]*/|Suwayomi-Server-preview/releases/download/v${SUWA_VER}/|" "$MANIFEST"
|
sed -i "s|Suwayomi-Server-preview/releases/download/v[^/]*/|Suwayomi-Server-preview/releases/download/v''${SUWA_VER}/|" "$MANIFEST"
|
||||||
sed -i "s|Suwayomi-Server-v[0-9.]*\.jar|Suwayomi-Server-v${SUWA_VER}.jar|g" "$MANIFEST"
|
sed -i "s|Suwayomi-Server-v[0-9.]*\.jar|Suwayomi-Server-v''${SUWA_VER}.jar|g" "$MANIFEST"
|
||||||
python3 - "$MANIFEST" "$JAR_SHA" <<'PYEOF'
|
python3 - "$MANIFEST" "$JAR_SHA" <<'PYEOF'
|
||||||
import re, sys
|
import re, sys
|
||||||
path, sha = sys.argv[1], sys.argv[2]
|
path, sha = sys.argv[1], sys.argv[2]
|
||||||
|
|||||||
+4
-4
@@ -1,5 +1,5 @@
|
|||||||
{
|
{
|
||||||
moku = "0.9.4";
|
moku = "0.10.0";
|
||||||
|
|
||||||
suwayomi = {
|
suwayomi = {
|
||||||
version = "2.2.2196";
|
version = "2.2.2196";
|
||||||
@@ -11,9 +11,9 @@
|
|||||||
};
|
};
|
||||||
|
|
||||||
frontend = {
|
frontend = {
|
||||||
pnpmHash = "sha256-18twdFhprV9v9hzvqxuVDHD6Tm4zHNDJs7s6l/7ClBo=";
|
pnpmHash = "sha256-fBkNpQXEeGZNbrpx7+0xVYYtQ6dGvpgRflCGPoxvnVY=";
|
||||||
distHash = "7db288b4b54277aa82b6ec5b21fc31a1e71f8246c50a74777500083b806c1fa5";
|
distHash = "7db288b4b54277aa82b6ec5b21fc31a1e71f8246c50a74777500083b806c1fa5";
|
||||||
distHashSri = "sha256-fbiiu0tCd6qCtu+SIfw+aR8Yj2bFCnR3dQAIO4BvwfM=";
|
distHashSri = "sha256-Z27CJz/9mmkkiEnF1R3E1ZpdW2j7unpP5+e1cqXyXxQ=";
|
||||||
};
|
};
|
||||||
|
|
||||||
gitDeps = {
|
gitDeps = {
|
||||||
@@ -21,5 +21,5 @@
|
|||||||
};
|
};
|
||||||
|
|
||||||
gitCommit = "239960683b6c7f1347e1798b0e179a8a46628728";
|
gitCommit = "239960683b6c7f1347e1798b0e179a8a46628728";
|
||||||
tarballHash = "";
|
tarballHash = "589b389b356a48d54ad4022e68eaac165a1de654d0b98edec79ebbab2c4a1275";
|
||||||
}
|
}
|
||||||
|
|||||||
+240
-240
@@ -125,14 +125,14 @@
|
|||||||
{
|
{
|
||||||
"type": "archive",
|
"type": "archive",
|
||||||
"archive-type": "tar-gzip",
|
"archive-type": "tar-gzip",
|
||||||
"url": "https://static.crates.io/crates/autocfg/autocfg-1.5.0.crate",
|
"url": "https://static.crates.io/crates/autocfg/autocfg-1.5.1.crate",
|
||||||
"sha256": "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8",
|
"sha256": "f2032f911046de80f0a198e0901378627c33f59ea0ac00e363d481118bd70a53",
|
||||||
"dest": "cargo/vendor/autocfg-1.5.0"
|
"dest": "cargo/vendor/autocfg-1.5.1"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "inline",
|
"type": "inline",
|
||||||
"contents": "{\"package\": \"c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8\", \"files\": {}}",
|
"contents": "{\"package\": \"f2032f911046de80f0a198e0901378627c33f59ea0ac00e363d481118bd70a53\", \"files\": {}}",
|
||||||
"dest": "cargo/vendor/autocfg-1.5.0",
|
"dest": "cargo/vendor/autocfg-1.5.1",
|
||||||
"dest-filename": ".cargo-checksum.json"
|
"dest-filename": ".cargo-checksum.json"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -203,14 +203,14 @@
|
|||||||
{
|
{
|
||||||
"type": "archive",
|
"type": "archive",
|
||||||
"archive-type": "tar-gzip",
|
"archive-type": "tar-gzip",
|
||||||
"url": "https://static.crates.io/crates/bitflags/bitflags-2.11.1.crate",
|
"url": "https://static.crates.io/crates/bitflags/bitflags-2.13.0.crate",
|
||||||
"sha256": "c4512299f36f043ab09a583e57bceb5a5aab7a73db1805848e8fef3c9e8c78b3",
|
"sha256": "b4388bee8683e3d04af747c73422af53102d2bd24d9eadb6cbc100baef4b43f8",
|
||||||
"dest": "cargo/vendor/bitflags-2.11.1"
|
"dest": "cargo/vendor/bitflags-2.13.0"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "inline",
|
"type": "inline",
|
||||||
"contents": "{\"package\": \"c4512299f36f043ab09a583e57bceb5a5aab7a73db1805848e8fef3c9e8c78b3\", \"files\": {}}",
|
"contents": "{\"package\": \"b4388bee8683e3d04af747c73422af53102d2bd24d9eadb6cbc100baef4b43f8\", \"files\": {}}",
|
||||||
"dest": "cargo/vendor/bitflags-2.11.1",
|
"dest": "cargo/vendor/bitflags-2.13.0",
|
||||||
"dest-filename": ".cargo-checksum.json"
|
"dest-filename": ".cargo-checksum.json"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -242,27 +242,27 @@
|
|||||||
{
|
{
|
||||||
"type": "archive",
|
"type": "archive",
|
||||||
"archive-type": "tar-gzip",
|
"archive-type": "tar-gzip",
|
||||||
"url": "https://static.crates.io/crates/brotli/brotli-8.0.2.crate",
|
"url": "https://static.crates.io/crates/brotli/brotli-8.0.3.crate",
|
||||||
"sha256": "4bd8b9603c7aa97359dbd97ecf258968c95f3adddd6db2f7e7a5bef101c84560",
|
"sha256": "8119e4516436f5708bbc474a9d395bf12f1b5395e93a92a56e647ac3388c8610",
|
||||||
"dest": "cargo/vendor/brotli-8.0.2"
|
"dest": "cargo/vendor/brotli-8.0.3"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "inline",
|
"type": "inline",
|
||||||
"contents": "{\"package\": \"4bd8b9603c7aa97359dbd97ecf258968c95f3adddd6db2f7e7a5bef101c84560\", \"files\": {}}",
|
"contents": "{\"package\": \"8119e4516436f5708bbc474a9d395bf12f1b5395e93a92a56e647ac3388c8610\", \"files\": {}}",
|
||||||
"dest": "cargo/vendor/brotli-8.0.2",
|
"dest": "cargo/vendor/brotli-8.0.3",
|
||||||
"dest-filename": ".cargo-checksum.json"
|
"dest-filename": ".cargo-checksum.json"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "archive",
|
"type": "archive",
|
||||||
"archive-type": "tar-gzip",
|
"archive-type": "tar-gzip",
|
||||||
"url": "https://static.crates.io/crates/brotli-decompressor/brotli-decompressor-5.0.0.crate",
|
"url": "https://static.crates.io/crates/brotli-decompressor/brotli-decompressor-5.0.1.crate",
|
||||||
"sha256": "874bb8112abecc98cbd6d81ea4fa7e94fb9449648c93cc89aa40c81c24d7de03",
|
"sha256": "5962523e1b92ce1b5e793d9169b9943eece10d39f62550bc04bb605d75b94924",
|
||||||
"dest": "cargo/vendor/brotli-decompressor-5.0.0"
|
"dest": "cargo/vendor/brotli-decompressor-5.0.1"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "inline",
|
"type": "inline",
|
||||||
"contents": "{\"package\": \"874bb8112abecc98cbd6d81ea4fa7e94fb9449648c93cc89aa40c81c24d7de03\", \"files\": {}}",
|
"contents": "{\"package\": \"5962523e1b92ce1b5e793d9169b9943eece10d39f62550bc04bb605d75b94924\", \"files\": {}}",
|
||||||
"dest": "cargo/vendor/brotli-decompressor-5.0.0",
|
"dest": "cargo/vendor/brotli-decompressor-5.0.1",
|
||||||
"dest-filename": ".cargo-checksum.json"
|
"dest-filename": ".cargo-checksum.json"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -281,14 +281,14 @@
|
|||||||
{
|
{
|
||||||
"type": "archive",
|
"type": "archive",
|
||||||
"archive-type": "tar-gzip",
|
"archive-type": "tar-gzip",
|
||||||
"url": "https://static.crates.io/crates/bumpalo/bumpalo-3.20.2.crate",
|
"url": "https://static.crates.io/crates/bumpalo/bumpalo-3.20.3.crate",
|
||||||
"sha256": "5d20789868f4b01b2f2caec9f5c4e0213b41e3e5702a50157d699ae31ced2fcb",
|
"sha256": "72f5acc6cb2ba439de613abc23857ec3d78374d8ed5ac84e9d11336e87da8649",
|
||||||
"dest": "cargo/vendor/bumpalo-3.20.2"
|
"dest": "cargo/vendor/bumpalo-3.20.3"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "inline",
|
"type": "inline",
|
||||||
"contents": "{\"package\": \"5d20789868f4b01b2f2caec9f5c4e0213b41e3e5702a50157d699ae31ced2fcb\", \"files\": {}}",
|
"contents": "{\"package\": \"72f5acc6cb2ba439de613abc23857ec3d78374d8ed5ac84e9d11336e87da8649\", \"files\": {}}",
|
||||||
"dest": "cargo/vendor/bumpalo-3.20.2",
|
"dest": "cargo/vendor/bumpalo-3.20.3",
|
||||||
"dest-filename": ".cargo-checksum.json"
|
"dest-filename": ".cargo-checksum.json"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -411,14 +411,14 @@
|
|||||||
{
|
{
|
||||||
"type": "archive",
|
"type": "archive",
|
||||||
"archive-type": "tar-gzip",
|
"archive-type": "tar-gzip",
|
||||||
"url": "https://static.crates.io/crates/cc/cc-1.2.62.crate",
|
"url": "https://static.crates.io/crates/cc/cc-1.2.64.crate",
|
||||||
"sha256": "a1dce859f0832a7d088c4f1119888ab94ef4b5d6795d1ce05afb7fe159d79f98",
|
"sha256": "dad887fd958be91b5098c0248def011f4523ab786cd411be668777e55063501f",
|
||||||
"dest": "cargo/vendor/cc-1.2.62"
|
"dest": "cargo/vendor/cc-1.2.64"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "inline",
|
"type": "inline",
|
||||||
"contents": "{\"package\": \"a1dce859f0832a7d088c4f1119888ab94ef4b5d6795d1ce05afb7fe159d79f98\", \"files\": {}}",
|
"contents": "{\"package\": \"dad887fd958be91b5098c0248def011f4523ab786cd411be668777e55063501f\", \"files\": {}}",
|
||||||
"dest": "cargo/vendor/cc-1.2.62",
|
"dest": "cargo/vendor/cc-1.2.64",
|
||||||
"dest-filename": ".cargo-checksum.json"
|
"dest-filename": ".cargo-checksum.json"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -489,14 +489,14 @@
|
|||||||
{
|
{
|
||||||
"type": "archive",
|
"type": "archive",
|
||||||
"archive-type": "tar-gzip",
|
"archive-type": "tar-gzip",
|
||||||
"url": "https://static.crates.io/crates/chrono/chrono-0.4.44.crate",
|
"url": "https://static.crates.io/crates/chrono/chrono-0.4.45.crate",
|
||||||
"sha256": "c673075a2e0e5f4a1dde27ce9dee1ea4558c7ffe648f576438a20ca1d2acc4b0",
|
"sha256": "1aa79e62e7697b8e29b513a68abacf485adcd1fe8284a4316c5ae868e6633327",
|
||||||
"dest": "cargo/vendor/chrono-0.4.44"
|
"dest": "cargo/vendor/chrono-0.4.45"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "inline",
|
"type": "inline",
|
||||||
"contents": "{\"package\": \"c673075a2e0e5f4a1dde27ce9dee1ea4558c7ffe648f576438a20ca1d2acc4b0\", \"files\": {}}",
|
"contents": "{\"package\": \"1aa79e62e7697b8e29b513a68abacf485adcd1fe8284a4316c5ae868e6633327\", \"files\": {}}",
|
||||||
"dest": "cargo/vendor/chrono-0.4.44",
|
"dest": "cargo/vendor/chrono-0.4.45",
|
||||||
"dest-filename": ".cargo-checksum.json"
|
"dest-filename": ".cargo-checksum.json"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -944,14 +944,14 @@
|
|||||||
{
|
{
|
||||||
"type": "archive",
|
"type": "archive",
|
||||||
"archive-type": "tar-gzip",
|
"archive-type": "tar-gzip",
|
||||||
"url": "https://static.crates.io/crates/displaydoc/displaydoc-0.2.5.crate",
|
"url": "https://static.crates.io/crates/displaydoc/displaydoc-0.2.6.crate",
|
||||||
"sha256": "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0",
|
"sha256": "1ac70aa55017e108007fbaf5aa0f54b021c98f92ff8af59d42eda9da96e3dd4f",
|
||||||
"dest": "cargo/vendor/displaydoc-0.2.5"
|
"dest": "cargo/vendor/displaydoc-0.2.6"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "inline",
|
"type": "inline",
|
||||||
"contents": "{\"package\": \"97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0\", \"files\": {}}",
|
"contents": "{\"package\": \"1ac70aa55017e108007fbaf5aa0f54b021c98f92ff8af59d42eda9da96e3dd4f\", \"files\": {}}",
|
||||||
"dest": "cargo/vendor/displaydoc-0.2.5",
|
"dest": "cargo/vendor/displaydoc-0.2.6",
|
||||||
"dest-filename": ".cargo-checksum.json"
|
"dest-filename": ".cargo-checksum.json"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -1100,14 +1100,14 @@
|
|||||||
{
|
{
|
||||||
"type": "archive",
|
"type": "archive",
|
||||||
"archive-type": "tar-gzip",
|
"archive-type": "tar-gzip",
|
||||||
"url": "https://static.crates.io/crates/either/either-1.15.0.crate",
|
"url": "https://static.crates.io/crates/either/either-1.16.0.crate",
|
||||||
"sha256": "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719",
|
"sha256": "91622ff5e7162018101f2fea40d6ebf4a78bbe5a49736a2020649edf9693679e",
|
||||||
"dest": "cargo/vendor/either-1.15.0"
|
"dest": "cargo/vendor/either-1.16.0"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "inline",
|
"type": "inline",
|
||||||
"contents": "{\"package\": \"48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719\", \"files\": {}}",
|
"contents": "{\"package\": \"91622ff5e7162018101f2fea40d6ebf4a78bbe5a49736a2020649edf9693679e\", \"files\": {}}",
|
||||||
"dest": "cargo/vendor/either-1.15.0",
|
"dest": "cargo/vendor/either-1.16.0",
|
||||||
"dest-filename": ".cargo-checksum.json"
|
"dest-filename": ".cargo-checksum.json"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -1867,14 +1867,14 @@
|
|||||||
{
|
{
|
||||||
"type": "archive",
|
"type": "archive",
|
||||||
"archive-type": "tar-gzip",
|
"archive-type": "tar-gzip",
|
||||||
"url": "https://static.crates.io/crates/http/http-1.4.0.crate",
|
"url": "https://static.crates.io/crates/http/http-1.4.2.crate",
|
||||||
"sha256": "e3ba2a386d7f85a81f119ad7498ebe444d2e22c2af0b86b069416ace48b3311a",
|
"sha256": "6970f50e31d6fc17d3fa27329444bfa74e196cf62e95052a3f6fee181dba6425",
|
||||||
"dest": "cargo/vendor/http-1.4.0"
|
"dest": "cargo/vendor/http-1.4.2"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "inline",
|
"type": "inline",
|
||||||
"contents": "{\"package\": \"e3ba2a386d7f85a81f119ad7498ebe444d2e22c2af0b86b069416ace48b3311a\", \"files\": {}}",
|
"contents": "{\"package\": \"6970f50e31d6fc17d3fa27329444bfa74e196cf62e95052a3f6fee181dba6425\", \"files\": {}}",
|
||||||
"dest": "cargo/vendor/http-1.4.0",
|
"dest": "cargo/vendor/http-1.4.2",
|
||||||
"dest-filename": ".cargo-checksum.json"
|
"dest-filename": ".cargo-checksum.json"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -1919,14 +1919,14 @@
|
|||||||
{
|
{
|
||||||
"type": "archive",
|
"type": "archive",
|
||||||
"archive-type": "tar-gzip",
|
"archive-type": "tar-gzip",
|
||||||
"url": "https://static.crates.io/crates/hyper/hyper-1.9.0.crate",
|
"url": "https://static.crates.io/crates/hyper/hyper-1.10.1.crate",
|
||||||
"sha256": "6299f016b246a94207e63da54dbe807655bf9e00044f73ded42c3ac5305fbcca",
|
"sha256": "55281c53a1894c864990125767da440a4e630446785086f52523b20033b74498",
|
||||||
"dest": "cargo/vendor/hyper-1.9.0"
|
"dest": "cargo/vendor/hyper-1.10.1"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "inline",
|
"type": "inline",
|
||||||
"contents": "{\"package\": \"6299f016b246a94207e63da54dbe807655bf9e00044f73ded42c3ac5305fbcca\", \"files\": {}}",
|
"contents": "{\"package\": \"55281c53a1894c864990125767da440a4e630446785086f52523b20033b74498\", \"files\": {}}",
|
||||||
"dest": "cargo/vendor/hyper-1.9.0",
|
"dest": "cargo/vendor/hyper-1.10.1",
|
||||||
"dest-filename": ".cargo-checksum.json"
|
"dest-filename": ".cargo-checksum.json"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -2322,14 +2322,14 @@
|
|||||||
{
|
{
|
||||||
"type": "archive",
|
"type": "archive",
|
||||||
"archive-type": "tar-gzip",
|
"archive-type": "tar-gzip",
|
||||||
"url": "https://static.crates.io/crates/js-sys/js-sys-0.3.98.crate",
|
"url": "https://static.crates.io/crates/js-sys/js-sys-0.3.102.crate",
|
||||||
"sha256": "67df7112613f8bfd9150013a0314e196f4800d3201ae742489d999db2f979f08",
|
"sha256": "03d04c30968dffe80775bd4d7fb676131cd04a1fb46d2686dbffbaec2d9dfd31",
|
||||||
"dest": "cargo/vendor/js-sys-0.3.98"
|
"dest": "cargo/vendor/js-sys-0.3.102"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "inline",
|
"type": "inline",
|
||||||
"contents": "{\"package\": \"67df7112613f8bfd9150013a0314e196f4800d3201ae742489d999db2f979f08\", \"files\": {}}",
|
"contents": "{\"package\": \"03d04c30968dffe80775bd4d7fb676131cd04a1fb46d2686dbffbaec2d9dfd31\", \"files\": {}}",
|
||||||
"dest": "cargo/vendor/js-sys-0.3.98",
|
"dest": "cargo/vendor/js-sys-0.3.102",
|
||||||
"dest-filename": ".cargo-checksum.json"
|
"dest-filename": ".cargo-checksum.json"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -2452,14 +2452,14 @@
|
|||||||
{
|
{
|
||||||
"type": "archive",
|
"type": "archive",
|
||||||
"archive-type": "tar-gzip",
|
"archive-type": "tar-gzip",
|
||||||
"url": "https://static.crates.io/crates/libredox/libredox-0.1.16.crate",
|
"url": "https://static.crates.io/crates/libredox/libredox-0.1.17.crate",
|
||||||
"sha256": "e02f3bb43d335493c96bf3fd3a321600bf6bd07ed34bc64118e9293bdffea46c",
|
"sha256": "f02ab6bace2054fb888a3c16f990117b579d14a3088e472d63c6011fa185c9d3",
|
||||||
"dest": "cargo/vendor/libredox-0.1.16"
|
"dest": "cargo/vendor/libredox-0.1.17"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "inline",
|
"type": "inline",
|
||||||
"contents": "{\"package\": \"e02f3bb43d335493c96bf3fd3a321600bf6bd07ed34bc64118e9293bdffea46c\", \"files\": {}}",
|
"contents": "{\"package\": \"f02ab6bace2054fb888a3c16f990117b579d14a3088e472d63c6011fa185c9d3\", \"files\": {}}",
|
||||||
"dest": "cargo/vendor/libredox-0.1.16",
|
"dest": "cargo/vendor/libredox-0.1.17",
|
||||||
"dest-filename": ".cargo-checksum.json"
|
"dest-filename": ".cargo-checksum.json"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -2517,14 +2517,14 @@
|
|||||||
{
|
{
|
||||||
"type": "archive",
|
"type": "archive",
|
||||||
"archive-type": "tar-gzip",
|
"archive-type": "tar-gzip",
|
||||||
"url": "https://static.crates.io/crates/log/log-0.4.29.crate",
|
"url": "https://static.crates.io/crates/log/log-0.4.32.crate",
|
||||||
"sha256": "5e5032e24019045c762d3c0f28f5b6b8bbf38563a65908389bf7978758920897",
|
"sha256": "953f07c43838f8e6f9758cab68bf5bed85465e7587ebe0b823f1bcd81978ad3a",
|
||||||
"dest": "cargo/vendor/log-0.4.29"
|
"dest": "cargo/vendor/log-0.4.32"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "inline",
|
"type": "inline",
|
||||||
"contents": "{\"package\": \"5e5032e24019045c762d3c0f28f5b6b8bbf38563a65908389bf7978758920897\", \"files\": {}}",
|
"contents": "{\"package\": \"953f07c43838f8e6f9758cab68bf5bed85465e7587ebe0b823f1bcd81978ad3a\", \"files\": {}}",
|
||||||
"dest": "cargo/vendor/log-0.4.29",
|
"dest": "cargo/vendor/log-0.4.32",
|
||||||
"dest-filename": ".cargo-checksum.json"
|
"dest-filename": ".cargo-checksum.json"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -2556,14 +2556,14 @@
|
|||||||
{
|
{
|
||||||
"type": "archive",
|
"type": "archive",
|
||||||
"archive-type": "tar-gzip",
|
"archive-type": "tar-gzip",
|
||||||
"url": "https://static.crates.io/crates/memchr/memchr-2.8.0.crate",
|
"url": "https://static.crates.io/crates/memchr/memchr-2.8.2.crate",
|
||||||
"sha256": "f8ca58f447f06ed17d5fc4043ce1b10dd205e060fb3ce5b979b8ed8e59ff3f79",
|
"sha256": "88904434abc2901f197fe8cc55f0445e7ded921dba5911dad2e2b39b48e663c4",
|
||||||
"dest": "cargo/vendor/memchr-2.8.0"
|
"dest": "cargo/vendor/memchr-2.8.2"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "inline",
|
"type": "inline",
|
||||||
"contents": "{\"package\": \"f8ca58f447f06ed17d5fc4043ce1b10dd205e060fb3ce5b979b8ed8e59ff3f79\", \"files\": {}}",
|
"contents": "{\"package\": \"88904434abc2901f197fe8cc55f0445e7ded921dba5911dad2e2b39b48e663c4\", \"files\": {}}",
|
||||||
"dest": "cargo/vendor/memchr-2.8.0",
|
"dest": "cargo/vendor/memchr-2.8.2",
|
||||||
"dest-filename": ".cargo-checksum.json"
|
"dest-filename": ".cargo-checksum.json"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -2608,27 +2608,27 @@
|
|||||||
{
|
{
|
||||||
"type": "archive",
|
"type": "archive",
|
||||||
"archive-type": "tar-gzip",
|
"archive-type": "tar-gzip",
|
||||||
"url": "https://static.crates.io/crates/mio/mio-1.2.0.crate",
|
"url": "https://static.crates.io/crates/mio/mio-1.2.1.crate",
|
||||||
"sha256": "50b7e5b27aa02a74bac8c3f23f448f8d87ff11f92d3aac1a6ed369ee08cc56c1",
|
"sha256": "02bd0af71c67b473010cbbc60715ee815645a4dc942899111f494b4b737d6fda",
|
||||||
"dest": "cargo/vendor/mio-1.2.0"
|
"dest": "cargo/vendor/mio-1.2.1"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "inline",
|
"type": "inline",
|
||||||
"contents": "{\"package\": \"50b7e5b27aa02a74bac8c3f23f448f8d87ff11f92d3aac1a6ed369ee08cc56c1\", \"files\": {}}",
|
"contents": "{\"package\": \"02bd0af71c67b473010cbbc60715ee815645a4dc942899111f494b4b737d6fda\", \"files\": {}}",
|
||||||
"dest": "cargo/vendor/mio-1.2.0",
|
"dest": "cargo/vendor/mio-1.2.1",
|
||||||
"dest-filename": ".cargo-checksum.json"
|
"dest-filename": ".cargo-checksum.json"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "archive",
|
"type": "archive",
|
||||||
"archive-type": "tar-gzip",
|
"archive-type": "tar-gzip",
|
||||||
"url": "https://static.crates.io/crates/muda/muda-0.19.1.crate",
|
"url": "https://static.crates.io/crates/muda/muda-0.19.2.crate",
|
||||||
"sha256": "0ae8844f63b5b118e334e205585b8c5c17b984121dbdb179d44aeb087ffad3cb",
|
"sha256": "47a2e3dff89cd322c66647942668faee0a2b1f88ea6cbb4d374b4a8d7e92528c",
|
||||||
"dest": "cargo/vendor/muda-0.19.1"
|
"dest": "cargo/vendor/muda-0.19.2"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "inline",
|
"type": "inline",
|
||||||
"contents": "{\"package\": \"0ae8844f63b5b118e334e205585b8c5c17b984121dbdb179d44aeb087ffad3cb\", \"files\": {}}",
|
"contents": "{\"package\": \"47a2e3dff89cd322c66647942668faee0a2b1f88ea6cbb4d374b4a8d7e92528c\", \"files\": {}}",
|
||||||
"dest": "cargo/vendor/muda-0.19.1",
|
"dest": "cargo/vendor/muda-0.19.2",
|
||||||
"dest-filename": ".cargo-checksum.json"
|
"dest-filename": ".cargo-checksum.json"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -2686,14 +2686,14 @@
|
|||||||
{
|
{
|
||||||
"type": "archive",
|
"type": "archive",
|
||||||
"archive-type": "tar-gzip",
|
"archive-type": "tar-gzip",
|
||||||
"url": "https://static.crates.io/crates/nix/nix-0.30.1.crate",
|
"url": "https://static.crates.io/crates/nix/nix-0.31.3.crate",
|
||||||
"sha256": "74523f3a35e05aba87a1d978330aef40f67b0304ac79c1c00b294c9830543db6",
|
"sha256": "cf20d2fde8ff38632c426f1165ed7436270b44f199fc55284c38276f9db47c3d",
|
||||||
"dest": "cargo/vendor/nix-0.30.1"
|
"dest": "cargo/vendor/nix-0.31.3"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "inline",
|
"type": "inline",
|
||||||
"contents": "{\"package\": \"74523f3a35e05aba87a1d978330aef40f67b0304ac79c1c00b294c9830543db6\", \"files\": {}}",
|
"contents": "{\"package\": \"cf20d2fde8ff38632c426f1165ed7436270b44f199fc55284c38276f9db47c3d\", \"files\": {}}",
|
||||||
"dest": "cargo/vendor/nix-0.30.1",
|
"dest": "cargo/vendor/nix-0.31.3",
|
||||||
"dest-filename": ".cargo-checksum.json"
|
"dest-filename": ".cargo-checksum.json"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -2712,14 +2712,14 @@
|
|||||||
{
|
{
|
||||||
"type": "archive",
|
"type": "archive",
|
||||||
"archive-type": "tar-gzip",
|
"archive-type": "tar-gzip",
|
||||||
"url": "https://static.crates.io/crates/num-conv/num-conv-0.2.1.crate",
|
"url": "https://static.crates.io/crates/num-conv/num-conv-0.2.2.crate",
|
||||||
"sha256": "c6673768db2d862beb9b39a78fdcb1a69439615d5794a1be50caa9bc92c81967",
|
"sha256": "521739c6d2bac4aa25192232afe6841231376b2b26d4d9fae5ecf8ca5772e441",
|
||||||
"dest": "cargo/vendor/num-conv-0.2.1"
|
"dest": "cargo/vendor/num-conv-0.2.2"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "inline",
|
"type": "inline",
|
||||||
"contents": "{\"package\": \"c6673768db2d862beb9b39a78fdcb1a69439615d5794a1be50caa9bc92c81967\", \"files\": {}}",
|
"contents": "{\"package\": \"521739c6d2bac4aa25192232afe6841231376b2b26d4d9fae5ecf8ca5772e441\", \"files\": {}}",
|
||||||
"dest": "cargo/vendor/num-conv-0.2.1",
|
"dest": "cargo/vendor/num-conv-0.2.2",
|
||||||
"dest-filename": ".cargo-checksum.json"
|
"dest-filename": ".cargo-checksum.json"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -3024,14 +3024,14 @@
|
|||||||
{
|
{
|
||||||
"type": "archive",
|
"type": "archive",
|
||||||
"archive-type": "tar-gzip",
|
"archive-type": "tar-gzip",
|
||||||
"url": "https://static.crates.io/crates/openssl/openssl-0.10.80.crate",
|
"url": "https://static.crates.io/crates/openssl/openssl-0.10.81.crate",
|
||||||
"sha256": "a45fa2aa886c42762255da344f0a0d313e254066c46aad76f300c3d3da62d967",
|
"sha256": "77823a27f0babb03091cb9ed9ef80af3b39dbc82f97e8fa530374b7dafd87a45",
|
||||||
"dest": "cargo/vendor/openssl-0.10.80"
|
"dest": "cargo/vendor/openssl-0.10.81"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "inline",
|
"type": "inline",
|
||||||
"contents": "{\"package\": \"a45fa2aa886c42762255da344f0a0d313e254066c46aad76f300c3d3da62d967\", \"files\": {}}",
|
"contents": "{\"package\": \"77823a27f0babb03091cb9ed9ef80af3b39dbc82f97e8fa530374b7dafd87a45\", \"files\": {}}",
|
||||||
"dest": "cargo/vendor/openssl-0.10.80",
|
"dest": "cargo/vendor/openssl-0.10.81",
|
||||||
"dest-filename": ".cargo-checksum.json"
|
"dest-filename": ".cargo-checksum.json"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -3063,14 +3063,14 @@
|
|||||||
{
|
{
|
||||||
"type": "archive",
|
"type": "archive",
|
||||||
"archive-type": "tar-gzip",
|
"archive-type": "tar-gzip",
|
||||||
"url": "https://static.crates.io/crates/openssl-sys/openssl-sys-0.9.116.crate",
|
"url": "https://static.crates.io/crates/openssl-sys/openssl-sys-0.9.117.crate",
|
||||||
"sha256": "f28a22dc7140cda5f096e5e7724a6962ca81a7f8bfd2979f9b18c11af56318c4",
|
"sha256": "b47e7e6bb2c38cd930d25a23b40fa52e068c10e85f3e03a7f5ba5aaca5713695",
|
||||||
"dest": "cargo/vendor/openssl-sys-0.9.116"
|
"dest": "cargo/vendor/openssl-sys-0.9.117"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "inline",
|
"type": "inline",
|
||||||
"contents": "{\"package\": \"f28a22dc7140cda5f096e5e7724a6962ca81a7f8bfd2979f9b18c11af56318c4\", \"files\": {}}",
|
"contents": "{\"package\": \"b47e7e6bb2c38cd930d25a23b40fa52e068c10e85f3e03a7f5ba5aaca5713695\", \"files\": {}}",
|
||||||
"dest": "cargo/vendor/openssl-sys-0.9.116",
|
"dest": "cargo/vendor/openssl-sys-0.9.117",
|
||||||
"dest-filename": ".cargo-checksum.json"
|
"dest-filename": ".cargo-checksum.json"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -3089,14 +3089,14 @@
|
|||||||
{
|
{
|
||||||
"type": "archive",
|
"type": "archive",
|
||||||
"archive-type": "tar-gzip",
|
"archive-type": "tar-gzip",
|
||||||
"url": "https://static.crates.io/crates/os_info/os_info-3.14.0.crate",
|
"url": "https://static.crates.io/crates/os_info/os_info-3.15.0.crate",
|
||||||
"sha256": "e4022a17595a00d6a369236fdae483f0de7f0a339960a53118b818238e132224",
|
"sha256": "9cf20a545b305cf1da722b236b5155c9bb35f1d5ceb28c048bd96ca842f41b5b",
|
||||||
"dest": "cargo/vendor/os_info-3.14.0"
|
"dest": "cargo/vendor/os_info-3.15.0"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "inline",
|
"type": "inline",
|
||||||
"contents": "{\"package\": \"e4022a17595a00d6a369236fdae483f0de7f0a339960a53118b818238e132224\", \"files\": {}}",
|
"contents": "{\"package\": \"9cf20a545b305cf1da722b236b5155c9bb35f1d5ceb28c048bd96ca842f41b5b\", \"files\": {}}",
|
||||||
"dest": "cargo/vendor/os_info-3.14.0",
|
"dest": "cargo/vendor/os_info-3.15.0",
|
||||||
"dest-filename": ".cargo-checksum.json"
|
"dest-filename": ".cargo-checksum.json"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -3726,14 +3726,14 @@
|
|||||||
{
|
{
|
||||||
"type": "archive",
|
"type": "archive",
|
||||||
"archive-type": "tar-gzip",
|
"archive-type": "tar-gzip",
|
||||||
"url": "https://static.crates.io/crates/regex/regex-1.12.3.crate",
|
"url": "https://static.crates.io/crates/regex/regex-1.12.4.crate",
|
||||||
"sha256": "e10754a14b9137dd7b1e3e5b0493cc9171fdd105e0ab477f51b72e7f3ac0e276",
|
"sha256": "f1292b7759ae1cb9ec195452d1390a074f0cd8541ab7a5a8c31cd6db45d4a6ba",
|
||||||
"dest": "cargo/vendor/regex-1.12.3"
|
"dest": "cargo/vendor/regex-1.12.4"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "inline",
|
"type": "inline",
|
||||||
"contents": "{\"package\": \"e10754a14b9137dd7b1e3e5b0493cc9171fdd105e0ab477f51b72e7f3ac0e276\", \"files\": {}}",
|
"contents": "{\"package\": \"f1292b7759ae1cb9ec195452d1390a074f0cd8541ab7a5a8c31cd6db45d4a6ba\", \"files\": {}}",
|
||||||
"dest": "cargo/vendor/regex-1.12.3",
|
"dest": "cargo/vendor/regex-1.12.4",
|
||||||
"dest-filename": ".cargo-checksum.json"
|
"dest-filename": ".cargo-checksum.json"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -3752,14 +3752,14 @@
|
|||||||
{
|
{
|
||||||
"type": "archive",
|
"type": "archive",
|
||||||
"archive-type": "tar-gzip",
|
"archive-type": "tar-gzip",
|
||||||
"url": "https://static.crates.io/crates/regex-syntax/regex-syntax-0.8.10.crate",
|
"url": "https://static.crates.io/crates/regex-syntax/regex-syntax-0.8.11.crate",
|
||||||
"sha256": "dc897dd8d9e8bd1ed8cdad82b5966c3e0ecae09fb1907d58efaa013543185d0a",
|
"sha256": "d6f6ff9a378485b298a5286656da665ba74413d36db0979633275d2e708145d4",
|
||||||
"dest": "cargo/vendor/regex-syntax-0.8.10"
|
"dest": "cargo/vendor/regex-syntax-0.8.11"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "inline",
|
"type": "inline",
|
||||||
"contents": "{\"package\": \"dc897dd8d9e8bd1ed8cdad82b5966c3e0ecae09fb1907d58efaa013543185d0a\", \"files\": {}}",
|
"contents": "{\"package\": \"d6f6ff9a378485b298a5286656da665ba74413d36db0979633275d2e708145d4\", \"files\": {}}",
|
||||||
"dest": "cargo/vendor/regex-syntax-0.8.10",
|
"dest": "cargo/vendor/regex-syntax-0.8.11",
|
||||||
"dest-filename": ".cargo-checksum.json"
|
"dest-filename": ".cargo-checksum.json"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -3778,14 +3778,14 @@
|
|||||||
{
|
{
|
||||||
"type": "archive",
|
"type": "archive",
|
||||||
"archive-type": "tar-gzip",
|
"archive-type": "tar-gzip",
|
||||||
"url": "https://static.crates.io/crates/reqwest/reqwest-0.13.3.crate",
|
"url": "https://static.crates.io/crates/reqwest/reqwest-0.13.4.crate",
|
||||||
"sha256": "62e0021ea2c22aed41653bc7e1419abb2c97e038ff2c33d0e1309e49a97deec0",
|
"sha256": "219c5811de6525e5416c7d5d53bb656d3afdbc6c5af816e0802bcfa42dbdc1c3",
|
||||||
"dest": "cargo/vendor/reqwest-0.13.3"
|
"dest": "cargo/vendor/reqwest-0.13.4"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "inline",
|
"type": "inline",
|
||||||
"contents": "{\"package\": \"62e0021ea2c22aed41653bc7e1419abb2c97e038ff2c33d0e1309e49a97deec0\", \"files\": {}}",
|
"contents": "{\"package\": \"219c5811de6525e5416c7d5d53bb656d3afdbc6c5af816e0802bcfa42dbdc1c3\", \"files\": {}}",
|
||||||
"dest": "cargo/vendor/reqwest-0.13.3",
|
"dest": "cargo/vendor/reqwest-0.13.4",
|
||||||
"dest-filename": ".cargo-checksum.json"
|
"dest-filename": ".cargo-checksum.json"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -4129,14 +4129,14 @@
|
|||||||
{
|
{
|
||||||
"type": "archive",
|
"type": "archive",
|
||||||
"archive-type": "tar-gzip",
|
"archive-type": "tar-gzip",
|
||||||
"url": "https://static.crates.io/crates/serde_json/serde_json-1.0.149.crate",
|
"url": "https://static.crates.io/crates/serde_json/serde_json-1.0.150.crate",
|
||||||
"sha256": "83fc039473c5595ace860d8c4fafa220ff474b3fc6bfdb4293327f1a37e94d86",
|
"sha256": "e8014e44b4736ed0538adeecded0fce2a272f22dc9578a7eb6b2d9993c74cfb9",
|
||||||
"dest": "cargo/vendor/serde_json-1.0.149"
|
"dest": "cargo/vendor/serde_json-1.0.150"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "inline",
|
"type": "inline",
|
||||||
"contents": "{\"package\": \"83fc039473c5595ace860d8c4fafa220ff474b3fc6bfdb4293327f1a37e94d86\", \"files\": {}}",
|
"contents": "{\"package\": \"e8014e44b4736ed0538adeecded0fce2a272f22dc9578a7eb6b2d9993c74cfb9\", \"files\": {}}",
|
||||||
"dest": "cargo/vendor/serde_json-1.0.149",
|
"dest": "cargo/vendor/serde_json-1.0.150",
|
||||||
"dest-filename": ".cargo-checksum.json"
|
"dest-filename": ".cargo-checksum.json"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -4194,27 +4194,27 @@
|
|||||||
{
|
{
|
||||||
"type": "archive",
|
"type": "archive",
|
||||||
"archive-type": "tar-gzip",
|
"archive-type": "tar-gzip",
|
||||||
"url": "https://static.crates.io/crates/serde_with/serde_with-3.20.0.crate",
|
"url": "https://static.crates.io/crates/serde_with/serde_with-3.21.0.crate",
|
||||||
"sha256": "e72c1c2cb7b223fafb600a619537a871c2818583d619401b785e7c0b746ccde2",
|
"sha256": "76a5c54c7310e7b8b9577c286d7e399ddd876c3e12b3ed917a8aabc4b96e9e8c",
|
||||||
"dest": "cargo/vendor/serde_with-3.20.0"
|
"dest": "cargo/vendor/serde_with-3.21.0"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "inline",
|
"type": "inline",
|
||||||
"contents": "{\"package\": \"e72c1c2cb7b223fafb600a619537a871c2818583d619401b785e7c0b746ccde2\", \"files\": {}}",
|
"contents": "{\"package\": \"76a5c54c7310e7b8b9577c286d7e399ddd876c3e12b3ed917a8aabc4b96e9e8c\", \"files\": {}}",
|
||||||
"dest": "cargo/vendor/serde_with-3.20.0",
|
"dest": "cargo/vendor/serde_with-3.21.0",
|
||||||
"dest-filename": ".cargo-checksum.json"
|
"dest-filename": ".cargo-checksum.json"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "archive",
|
"type": "archive",
|
||||||
"archive-type": "tar-gzip",
|
"archive-type": "tar-gzip",
|
||||||
"url": "https://static.crates.io/crates/serde_with_macros/serde_with_macros-3.20.0.crate",
|
"url": "https://static.crates.io/crates/serde_with_macros/serde_with_macros-3.21.0.crate",
|
||||||
"sha256": "b90c488738ecb4fb0262f41f43bc40efc5868d9fb744319ddf5f5317f417bfac",
|
"sha256": "84d57bc0c8b9a17920c178daa6bb924850d54a9c97ab45194bb8c17ad66bb660",
|
||||||
"dest": "cargo/vendor/serde_with_macros-3.20.0"
|
"dest": "cargo/vendor/serde_with_macros-3.21.0"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "inline",
|
"type": "inline",
|
||||||
"contents": "{\"package\": \"b90c488738ecb4fb0262f41f43bc40efc5868d9fb744319ddf5f5317f417bfac\", \"files\": {}}",
|
"contents": "{\"package\": \"84d57bc0c8b9a17920c178daa6bb924850d54a9c97ab45194bb8c17ad66bb660\", \"files\": {}}",
|
||||||
"dest": "cargo/vendor/serde_with_macros-3.20.0",
|
"dest": "cargo/vendor/serde_with_macros-3.21.0",
|
||||||
"dest-filename": ".cargo-checksum.json"
|
"dest-filename": ".cargo-checksum.json"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -4285,14 +4285,14 @@
|
|||||||
{
|
{
|
||||||
"type": "archive",
|
"type": "archive",
|
||||||
"archive-type": "tar-gzip",
|
"archive-type": "tar-gzip",
|
||||||
"url": "https://static.crates.io/crates/shlex/shlex-1.3.0.crate",
|
"url": "https://static.crates.io/crates/shlex/shlex-2.0.1.crate",
|
||||||
"sha256": "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64",
|
"sha256": "f8fadd59c855ef2080decdef8ff161eb6661b86933c9d82e5ba29dc602a55aba",
|
||||||
"dest": "cargo/vendor/shlex-1.3.0"
|
"dest": "cargo/vendor/shlex-2.0.1"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "inline",
|
"type": "inline",
|
||||||
"contents": "{\"package\": \"0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64\", \"files\": {}}",
|
"contents": "{\"package\": \"f8fadd59c855ef2080decdef8ff161eb6661b86933c9d82e5ba29dc602a55aba\", \"files\": {}}",
|
||||||
"dest": "cargo/vendor/shlex-1.3.0",
|
"dest": "cargo/vendor/shlex-2.0.1",
|
||||||
"dest-filename": ".cargo-checksum.json"
|
"dest-filename": ".cargo-checksum.json"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -4376,27 +4376,27 @@
|
|||||||
{
|
{
|
||||||
"type": "archive",
|
"type": "archive",
|
||||||
"archive-type": "tar-gzip",
|
"archive-type": "tar-gzip",
|
||||||
"url": "https://static.crates.io/crates/smallvec/smallvec-1.15.1.crate",
|
"url": "https://static.crates.io/crates/smallvec/smallvec-1.15.2.crate",
|
||||||
"sha256": "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03",
|
"sha256": "8ed6a63f02c8539c91a8685a86f4099661ba3da017932f6ebbea6de3f0fa7c90",
|
||||||
"dest": "cargo/vendor/smallvec-1.15.1"
|
"dest": "cargo/vendor/smallvec-1.15.2"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "inline",
|
"type": "inline",
|
||||||
"contents": "{\"package\": \"67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03\", \"files\": {}}",
|
"contents": "{\"package\": \"8ed6a63f02c8539c91a8685a86f4099661ba3da017932f6ebbea6de3f0fa7c90\", \"files\": {}}",
|
||||||
"dest": "cargo/vendor/smallvec-1.15.1",
|
"dest": "cargo/vendor/smallvec-1.15.2",
|
||||||
"dest-filename": ".cargo-checksum.json"
|
"dest-filename": ".cargo-checksum.json"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "archive",
|
"type": "archive",
|
||||||
"archive-type": "tar-gzip",
|
"archive-type": "tar-gzip",
|
||||||
"url": "https://static.crates.io/crates/socket2/socket2-0.6.3.crate",
|
"url": "https://static.crates.io/crates/socket2/socket2-0.6.4.crate",
|
||||||
"sha256": "3a766e1110788c36f4fa1c2b71b387a7815aa65f88ce0229841826633d93723e",
|
"sha256": "52d1cfed4120b4d927bf7c0f86d2087a4a7d6027c906d9f9d525a80573b9be51",
|
||||||
"dest": "cargo/vendor/socket2-0.6.3"
|
"dest": "cargo/vendor/socket2-0.6.4"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "inline",
|
"type": "inline",
|
||||||
"contents": "{\"package\": \"3a766e1110788c36f4fa1c2b71b387a7815aa65f88ce0229841826633d93723e\", \"files\": {}}",
|
"contents": "{\"package\": \"52d1cfed4120b4d927bf7c0f86d2087a4a7d6027c906d9f9d525a80573b9be51\", \"files\": {}}",
|
||||||
"dest": "cargo/vendor/socket2-0.6.3",
|
"dest": "cargo/vendor/socket2-0.6.4",
|
||||||
"dest-filename": ".cargo-checksum.json"
|
"dest-filename": ".cargo-checksum.json"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -4649,14 +4649,14 @@
|
|||||||
{
|
{
|
||||||
"type": "archive",
|
"type": "archive",
|
||||||
"archive-type": "tar-gzip",
|
"archive-type": "tar-gzip",
|
||||||
"url": "https://static.crates.io/crates/tao/tao-0.35.2.crate",
|
"url": "https://static.crates.io/crates/tao/tao-0.35.3.crate",
|
||||||
"sha256": "a33f7f9e486ade65fcf1e45c440f9236c904f5c1002cdc7fc6ae582777345ce4",
|
"sha256": "d1c93047acf68669466a34690ac58cca7010bd1b201e1ec86f1fd0a75d3dd4a9",
|
||||||
"dest": "cargo/vendor/tao-0.35.2"
|
"dest": "cargo/vendor/tao-0.35.3"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "inline",
|
"type": "inline",
|
||||||
"contents": "{\"package\": \"a33f7f9e486ade65fcf1e45c440f9236c904f5c1002cdc7fc6ae582777345ce4\", \"files\": {}}",
|
"contents": "{\"package\": \"d1c93047acf68669466a34690ac58cca7010bd1b201e1ec86f1fd0a75d3dd4a9\", \"files\": {}}",
|
||||||
"dest": "cargo/vendor/tao-0.35.2",
|
"dest": "cargo/vendor/tao-0.35.3",
|
||||||
"dest-filename": ".cargo-checksum.json"
|
"dest-filename": ".cargo-checksum.json"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -5239,14 +5239,14 @@
|
|||||||
{
|
{
|
||||||
"type": "archive",
|
"type": "archive",
|
||||||
"archive-type": "tar-gzip",
|
"archive-type": "tar-gzip",
|
||||||
"url": "https://static.crates.io/crates/toml_edit/toml_edit-0.25.11+spec-1.1.0.crate",
|
"url": "https://static.crates.io/crates/toml_edit/toml_edit-0.25.12+spec-1.1.0.crate",
|
||||||
"sha256": "0b59c4d22ed448339746c59b905d24568fcbb3ab65a500494f7b8c3e97739f2b",
|
"sha256": "d2153edc6955a6c354fad8f5efd38b6a8769bdccf9fe50f8e1329f81b0baa5d7",
|
||||||
"dest": "cargo/vendor/toml_edit-0.25.11+spec-1.1.0"
|
"dest": "cargo/vendor/toml_edit-0.25.12+spec-1.1.0"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "inline",
|
"type": "inline",
|
||||||
"contents": "{\"package\": \"0b59c4d22ed448339746c59b905d24568fcbb3ab65a500494f7b8c3e97739f2b\", \"files\": {}}",
|
"contents": "{\"package\": \"d2153edc6955a6c354fad8f5efd38b6a8769bdccf9fe50f8e1329f81b0baa5d7\", \"files\": {}}",
|
||||||
"dest": "cargo/vendor/toml_edit-0.25.11+spec-1.1.0",
|
"dest": "cargo/vendor/toml_edit-0.25.12+spec-1.1.0",
|
||||||
"dest-filename": ".cargo-checksum.json"
|
"dest-filename": ".cargo-checksum.json"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -5291,14 +5291,14 @@
|
|||||||
{
|
{
|
||||||
"type": "archive",
|
"type": "archive",
|
||||||
"archive-type": "tar-gzip",
|
"archive-type": "tar-gzip",
|
||||||
"url": "https://static.crates.io/crates/tower-http/tower-http-0.6.10.crate",
|
"url": "https://static.crates.io/crates/tower-http/tower-http-0.6.11.crate",
|
||||||
"sha256": "68d6fdd9f81c2819c9a8b0e0cd91660e7746a8e6ea2ba7c6b2b057985f6bcb51",
|
"sha256": "4cfcf7e2740e6fc6d4d688b4ef00650406bb94adf4731e43c096c3a19fe40840",
|
||||||
"dest": "cargo/vendor/tower-http-0.6.10"
|
"dest": "cargo/vendor/tower-http-0.6.11"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "inline",
|
"type": "inline",
|
||||||
"contents": "{\"package\": \"68d6fdd9f81c2819c9a8b0e0cd91660e7746a8e6ea2ba7c6b2b057985f6bcb51\", \"files\": {}}",
|
"contents": "{\"package\": \"4cfcf7e2740e6fc6d4d688b4ef00650406bb94adf4731e43c096c3a19fe40840\", \"files\": {}}",
|
||||||
"dest": "cargo/vendor/tower-http-0.6.10",
|
"dest": "cargo/vendor/tower-http-0.6.11",
|
||||||
"dest-filename": ".cargo-checksum.json"
|
"dest-filename": ".cargo-checksum.json"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -5408,14 +5408,14 @@
|
|||||||
{
|
{
|
||||||
"type": "archive",
|
"type": "archive",
|
||||||
"archive-type": "tar-gzip",
|
"archive-type": "tar-gzip",
|
||||||
"url": "https://static.crates.io/crates/typenum/typenum-1.20.0.crate",
|
"url": "https://static.crates.io/crates/typenum/typenum-1.20.1.crate",
|
||||||
"sha256": "40ce102ab67701b8526c123c1bab5cbe42d7040ccfd0f64af1a385808d2f43de",
|
"sha256": "b6f5e870be6c3b371b77fe0ee0bafb859fa4964b4404c27de1d380043c4dda20",
|
||||||
"dest": "cargo/vendor/typenum-1.20.0"
|
"dest": "cargo/vendor/typenum-1.20.1"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "inline",
|
"type": "inline",
|
||||||
"contents": "{\"package\": \"40ce102ab67701b8526c123c1bab5cbe42d7040ccfd0f64af1a385808d2f43de\", \"files\": {}}",
|
"contents": "{\"package\": \"b6f5e870be6c3b371b77fe0ee0bafb859fa4964b4404c27de1d380043c4dda20\", \"files\": {}}",
|
||||||
"dest": "cargo/vendor/typenum-1.20.0",
|
"dest": "cargo/vendor/typenum-1.20.1",
|
||||||
"dest-filename": ".cargo-checksum.json"
|
"dest-filename": ".cargo-checksum.json"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -5499,14 +5499,14 @@
|
|||||||
{
|
{
|
||||||
"type": "archive",
|
"type": "archive",
|
||||||
"archive-type": "tar-gzip",
|
"archive-type": "tar-gzip",
|
||||||
"url": "https://static.crates.io/crates/unicode-segmentation/unicode-segmentation-1.13.2.crate",
|
"url": "https://static.crates.io/crates/unicode-segmentation/unicode-segmentation-1.13.3.crate",
|
||||||
"sha256": "9629274872b2bfaf8d66f5f15725007f635594914870f65218920345aa11aa8c",
|
"sha256": "c6f5d3c3b1bf09027a88a6bc961fc00497d651009560b5463668dc81b0fa87a8",
|
||||||
"dest": "cargo/vendor/unicode-segmentation-1.13.2"
|
"dest": "cargo/vendor/unicode-segmentation-1.13.3"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "inline",
|
"type": "inline",
|
||||||
"contents": "{\"package\": \"9629274872b2bfaf8d66f5f15725007f635594914870f65218920345aa11aa8c\", \"files\": {}}",
|
"contents": "{\"package\": \"c6f5d3c3b1bf09027a88a6bc961fc00497d651009560b5463668dc81b0fa87a8\", \"files\": {}}",
|
||||||
"dest": "cargo/vendor/unicode-segmentation-1.13.2",
|
"dest": "cargo/vendor/unicode-segmentation-1.13.3",
|
||||||
"dest-filename": ".cargo-checksum.json"
|
"dest-filename": ".cargo-checksum.json"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -5616,14 +5616,14 @@
|
|||||||
{
|
{
|
||||||
"type": "archive",
|
"type": "archive",
|
||||||
"archive-type": "tar-gzip",
|
"archive-type": "tar-gzip",
|
||||||
"url": "https://static.crates.io/crates/uuid/uuid-1.23.1.crate",
|
"url": "https://static.crates.io/crates/uuid/uuid-1.23.3.crate",
|
||||||
"sha256": "ddd74a9687298c6858e9b88ec8935ec45d22e8fd5e6394fa1bd4e99a87789c76",
|
"sha256": "144d6b123cef80b301b8f72a9e2ca4370ddec21950d0a103dd22c437006d2db7",
|
||||||
"dest": "cargo/vendor/uuid-1.23.1"
|
"dest": "cargo/vendor/uuid-1.23.3"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "inline",
|
"type": "inline",
|
||||||
"contents": "{\"package\": \"ddd74a9687298c6858e9b88ec8935ec45d22e8fd5e6394fa1bd4e99a87789c76\", \"files\": {}}",
|
"contents": "{\"package\": \"144d6b123cef80b301b8f72a9e2ca4370ddec21950d0a103dd22c437006d2db7\", \"files\": {}}",
|
||||||
"dest": "cargo/vendor/uuid-1.23.1",
|
"dest": "cargo/vendor/uuid-1.23.3",
|
||||||
"dest-filename": ".cargo-checksum.json"
|
"dest-filename": ".cargo-checksum.json"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -5733,14 +5733,14 @@
|
|||||||
{
|
{
|
||||||
"type": "archive",
|
"type": "archive",
|
||||||
"archive-type": "tar-gzip",
|
"archive-type": "tar-gzip",
|
||||||
"url": "https://static.crates.io/crates/wasip2/wasip2-1.0.3+wasi-0.2.9.crate",
|
"url": "https://static.crates.io/crates/wasip2/wasip2-1.0.4+wasi-0.2.12.crate",
|
||||||
"sha256": "20064672db26d7cdc89c7798c48a0fdfac8213434a1186e5ef29fd560ae223d6",
|
"sha256": "b67efb37e106e55ce722a510d6b5f9c17f083e5fc79afc2badeb12cc313d9487",
|
||||||
"dest": "cargo/vendor/wasip2-1.0.3+wasi-0.2.9"
|
"dest": "cargo/vendor/wasip2-1.0.4+wasi-0.2.12"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "inline",
|
"type": "inline",
|
||||||
"contents": "{\"package\": \"20064672db26d7cdc89c7798c48a0fdfac8213434a1186e5ef29fd560ae223d6\", \"files\": {}}",
|
"contents": "{\"package\": \"b67efb37e106e55ce722a510d6b5f9c17f083e5fc79afc2badeb12cc313d9487\", \"files\": {}}",
|
||||||
"dest": "cargo/vendor/wasip2-1.0.3+wasi-0.2.9",
|
"dest": "cargo/vendor/wasip2-1.0.4+wasi-0.2.12",
|
||||||
"dest-filename": ".cargo-checksum.json"
|
"dest-filename": ".cargo-checksum.json"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -5759,66 +5759,66 @@
|
|||||||
{
|
{
|
||||||
"type": "archive",
|
"type": "archive",
|
||||||
"archive-type": "tar-gzip",
|
"archive-type": "tar-gzip",
|
||||||
"url": "https://static.crates.io/crates/wasm-bindgen/wasm-bindgen-0.2.121.crate",
|
"url": "https://static.crates.io/crates/wasm-bindgen/wasm-bindgen-0.2.125.crate",
|
||||||
"sha256": "49ace1d07c165b0864824eee619580c4689389afa9dc9ed3a4c75040d82e6790",
|
"sha256": "8ddb3f79143bced6de84270411622a2699cee572fc0875aeaf1e7867cf9fca1a",
|
||||||
"dest": "cargo/vendor/wasm-bindgen-0.2.121"
|
"dest": "cargo/vendor/wasm-bindgen-0.2.125"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "inline",
|
"type": "inline",
|
||||||
"contents": "{\"package\": \"49ace1d07c165b0864824eee619580c4689389afa9dc9ed3a4c75040d82e6790\", \"files\": {}}",
|
"contents": "{\"package\": \"8ddb3f79143bced6de84270411622a2699cee572fc0875aeaf1e7867cf9fca1a\", \"files\": {}}",
|
||||||
"dest": "cargo/vendor/wasm-bindgen-0.2.121",
|
"dest": "cargo/vendor/wasm-bindgen-0.2.125",
|
||||||
"dest-filename": ".cargo-checksum.json"
|
"dest-filename": ".cargo-checksum.json"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "archive",
|
"type": "archive",
|
||||||
"archive-type": "tar-gzip",
|
"archive-type": "tar-gzip",
|
||||||
"url": "https://static.crates.io/crates/wasm-bindgen-futures/wasm-bindgen-futures-0.4.71.crate",
|
"url": "https://static.crates.io/crates/wasm-bindgen-futures/wasm-bindgen-futures-0.4.75.crate",
|
||||||
"sha256": "96492d0d3ffba25305a7dc88720d250b1401d7edca02cc3bcd50633b424673b8",
|
"sha256": "503b14d284f2c8dac03b819967e155ea753f573586193b2b2c95990cb5d69280",
|
||||||
"dest": "cargo/vendor/wasm-bindgen-futures-0.4.71"
|
"dest": "cargo/vendor/wasm-bindgen-futures-0.4.75"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "inline",
|
"type": "inline",
|
||||||
"contents": "{\"package\": \"96492d0d3ffba25305a7dc88720d250b1401d7edca02cc3bcd50633b424673b8\", \"files\": {}}",
|
"contents": "{\"package\": \"503b14d284f2c8dac03b819967e155ea753f573586193b2b2c95990cb5d69280\", \"files\": {}}",
|
||||||
"dest": "cargo/vendor/wasm-bindgen-futures-0.4.71",
|
"dest": "cargo/vendor/wasm-bindgen-futures-0.4.75",
|
||||||
"dest-filename": ".cargo-checksum.json"
|
"dest-filename": ".cargo-checksum.json"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "archive",
|
"type": "archive",
|
||||||
"archive-type": "tar-gzip",
|
"archive-type": "tar-gzip",
|
||||||
"url": "https://static.crates.io/crates/wasm-bindgen-macro/wasm-bindgen-macro-0.2.121.crate",
|
"url": "https://static.crates.io/crates/wasm-bindgen-macro/wasm-bindgen-macro-0.2.125.crate",
|
||||||
"sha256": "8e68e6f4afd367a562002c05637acb8578ff2dea1943df76afb9e83d177c8578",
|
"sha256": "4e21a184b13fb19e157296e2c46056aec9092264fab83e4ba59e68c61b323c3d",
|
||||||
"dest": "cargo/vendor/wasm-bindgen-macro-0.2.121"
|
"dest": "cargo/vendor/wasm-bindgen-macro-0.2.125"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "inline",
|
"type": "inline",
|
||||||
"contents": "{\"package\": \"8e68e6f4afd367a562002c05637acb8578ff2dea1943df76afb9e83d177c8578\", \"files\": {}}",
|
"contents": "{\"package\": \"4e21a184b13fb19e157296e2c46056aec9092264fab83e4ba59e68c61b323c3d\", \"files\": {}}",
|
||||||
"dest": "cargo/vendor/wasm-bindgen-macro-0.2.121",
|
"dest": "cargo/vendor/wasm-bindgen-macro-0.2.125",
|
||||||
"dest-filename": ".cargo-checksum.json"
|
"dest-filename": ".cargo-checksum.json"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "archive",
|
"type": "archive",
|
||||||
"archive-type": "tar-gzip",
|
"archive-type": "tar-gzip",
|
||||||
"url": "https://static.crates.io/crates/wasm-bindgen-macro-support/wasm-bindgen-macro-support-0.2.121.crate",
|
"url": "https://static.crates.io/crates/wasm-bindgen-macro-support/wasm-bindgen-macro-support-0.2.125.crate",
|
||||||
"sha256": "d95a9ec35c64b2a7cb35d3fead40c4238d0940c86d107136999567a4703259f2",
|
"sha256": "fecefd9c35bd935a20fc3fc344b5f29138961e4f47fb03297d88f2587afb5ebd",
|
||||||
"dest": "cargo/vendor/wasm-bindgen-macro-support-0.2.121"
|
"dest": "cargo/vendor/wasm-bindgen-macro-support-0.2.125"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "inline",
|
"type": "inline",
|
||||||
"contents": "{\"package\": \"d95a9ec35c64b2a7cb35d3fead40c4238d0940c86d107136999567a4703259f2\", \"files\": {}}",
|
"contents": "{\"package\": \"fecefd9c35bd935a20fc3fc344b5f29138961e4f47fb03297d88f2587afb5ebd\", \"files\": {}}",
|
||||||
"dest": "cargo/vendor/wasm-bindgen-macro-support-0.2.121",
|
"dest": "cargo/vendor/wasm-bindgen-macro-support-0.2.125",
|
||||||
"dest-filename": ".cargo-checksum.json"
|
"dest-filename": ".cargo-checksum.json"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "archive",
|
"type": "archive",
|
||||||
"archive-type": "tar-gzip",
|
"archive-type": "tar-gzip",
|
||||||
"url": "https://static.crates.io/crates/wasm-bindgen-shared/wasm-bindgen-shared-0.2.121.crate",
|
"url": "https://static.crates.io/crates/wasm-bindgen-shared/wasm-bindgen-shared-0.2.125.crate",
|
||||||
"sha256": "c4e0100b01e9f0d03189a92b96772a1fb998639d981193d7dbab487302513441",
|
"sha256": "23939e44bb9a5d7576fa2b563dc2e136628f1224e88a8deed09e04858b77871f",
|
||||||
"dest": "cargo/vendor/wasm-bindgen-shared-0.2.121"
|
"dest": "cargo/vendor/wasm-bindgen-shared-0.2.125"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "inline",
|
"type": "inline",
|
||||||
"contents": "{\"package\": \"c4e0100b01e9f0d03189a92b96772a1fb998639d981193d7dbab487302513441\", \"files\": {}}",
|
"contents": "{\"package\": \"23939e44bb9a5d7576fa2b563dc2e136628f1224e88a8deed09e04858b77871f\", \"files\": {}}",
|
||||||
"dest": "cargo/vendor/wasm-bindgen-shared-0.2.121",
|
"dest": "cargo/vendor/wasm-bindgen-shared-0.2.125",
|
||||||
"dest-filename": ".cargo-checksum.json"
|
"dest-filename": ".cargo-checksum.json"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -5876,14 +5876,14 @@
|
|||||||
{
|
{
|
||||||
"type": "archive",
|
"type": "archive",
|
||||||
"archive-type": "tar-gzip",
|
"archive-type": "tar-gzip",
|
||||||
"url": "https://static.crates.io/crates/web-sys/web-sys-0.3.98.crate",
|
"url": "https://static.crates.io/crates/web-sys/web-sys-0.3.102.crate",
|
||||||
"sha256": "4b572dff8bcf38bad0fa19729c89bb5748b2b9b1d8be70cf90df697e3a8f32aa",
|
"sha256": "a6430a72df5eb332242960fe84b3002a241163998241eb596d4f739b9757061d",
|
||||||
"dest": "cargo/vendor/web-sys-0.3.98"
|
"dest": "cargo/vendor/web-sys-0.3.102"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "inline",
|
"type": "inline",
|
||||||
"contents": "{\"package\": \"4b572dff8bcf38bad0fa19729c89bb5748b2b9b1d8be70cf90df697e3a8f32aa\", \"files\": {}}",
|
"contents": "{\"package\": \"a6430a72df5eb332242960fe84b3002a241163998241eb596d4f739b9757061d\", \"files\": {}}",
|
||||||
"dest": "cargo/vendor/web-sys-0.3.98",
|
"dest": "cargo/vendor/web-sys-0.3.102",
|
||||||
"dest-filename": ".cargo-checksum.json"
|
"dest-filename": ".cargo-checksum.json"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -7137,14 +7137,14 @@
|
|||||||
{
|
{
|
||||||
"type": "archive",
|
"type": "archive",
|
||||||
"archive-type": "tar-gzip",
|
"archive-type": "tar-gzip",
|
||||||
"url": "https://static.crates.io/crates/yoke/yoke-0.8.2.crate",
|
"url": "https://static.crates.io/crates/yoke/yoke-0.8.3.crate",
|
||||||
"sha256": "abe8c5fda708d9ca3df187cae8bfb9ceda00dd96231bed36e445a1a48e66f9ca",
|
"sha256": "709fe23a0424b6a435d82152b1bd3fdfb0833487d5fa90d05d42762a9891fef5",
|
||||||
"dest": "cargo/vendor/yoke-0.8.2"
|
"dest": "cargo/vendor/yoke-0.8.3"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "inline",
|
"type": "inline",
|
||||||
"contents": "{\"package\": \"abe8c5fda708d9ca3df187cae8bfb9ceda00dd96231bed36e445a1a48e66f9ca\", \"files\": {}}",
|
"contents": "{\"package\": \"709fe23a0424b6a435d82152b1bd3fdfb0833487d5fa90d05d42762a9891fef5\", \"files\": {}}",
|
||||||
"dest": "cargo/vendor/yoke-0.8.2",
|
"dest": "cargo/vendor/yoke-0.8.3",
|
||||||
"dest-filename": ".cargo-checksum.json"
|
"dest-filename": ".cargo-checksum.json"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -7163,27 +7163,27 @@
|
|||||||
{
|
{
|
||||||
"type": "archive",
|
"type": "archive",
|
||||||
"archive-type": "tar-gzip",
|
"archive-type": "tar-gzip",
|
||||||
"url": "https://static.crates.io/crates/zerocopy/zerocopy-0.8.48.crate",
|
"url": "https://static.crates.io/crates/zerocopy/zerocopy-0.8.52.crate",
|
||||||
"sha256": "eed437bf9d6692032087e337407a86f04cd8d6a16a37199ed57949d415bd68e9",
|
"sha256": "ce1022995ff5ff5d841ad7d994facc23098cd40152f2c1d11cd607c6f530653f",
|
||||||
"dest": "cargo/vendor/zerocopy-0.8.48"
|
"dest": "cargo/vendor/zerocopy-0.8.52"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "inline",
|
"type": "inline",
|
||||||
"contents": "{\"package\": \"eed437bf9d6692032087e337407a86f04cd8d6a16a37199ed57949d415bd68e9\", \"files\": {}}",
|
"contents": "{\"package\": \"ce1022995ff5ff5d841ad7d994facc23098cd40152f2c1d11cd607c6f530653f\", \"files\": {}}",
|
||||||
"dest": "cargo/vendor/zerocopy-0.8.48",
|
"dest": "cargo/vendor/zerocopy-0.8.52",
|
||||||
"dest-filename": ".cargo-checksum.json"
|
"dest-filename": ".cargo-checksum.json"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "archive",
|
"type": "archive",
|
||||||
"archive-type": "tar-gzip",
|
"archive-type": "tar-gzip",
|
||||||
"url": "https://static.crates.io/crates/zerocopy-derive/zerocopy-derive-0.8.48.crate",
|
"url": "https://static.crates.io/crates/zerocopy-derive/zerocopy-derive-0.8.52.crate",
|
||||||
"sha256": "70e3cd084b1788766f53af483dd21f93881ff30d7320490ec3ef7526d203bad4",
|
"sha256": "1ae7f38b72ec2a254e2b87ef277cf2cd4fb97cbebf944faa6f33354da0867930",
|
||||||
"dest": "cargo/vendor/zerocopy-derive-0.8.48"
|
"dest": "cargo/vendor/zerocopy-derive-0.8.52"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "inline",
|
"type": "inline",
|
||||||
"contents": "{\"package\": \"70e3cd084b1788766f53af483dd21f93881ff30d7320490ec3ef7526d203bad4\", \"files\": {}}",
|
"contents": "{\"package\": \"1ae7f38b72ec2a254e2b87ef277cf2cd4fb97cbebf944faa6f33354da0867930\", \"files\": {}}",
|
||||||
"dest": "cargo/vendor/zerocopy-derive-0.8.48",
|
"dest": "cargo/vendor/zerocopy-derive-0.8.52",
|
||||||
"dest-filename": ".cargo-checksum.json"
|
"dest-filename": ".cargo-checksum.json"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -7215,14 +7215,14 @@
|
|||||||
{
|
{
|
||||||
"type": "archive",
|
"type": "archive",
|
||||||
"archive-type": "tar-gzip",
|
"archive-type": "tar-gzip",
|
||||||
"url": "https://static.crates.io/crates/zeroize/zeroize-1.8.2.crate",
|
"url": "https://static.crates.io/crates/zeroize/zeroize-1.9.0.crate",
|
||||||
"sha256": "b97154e67e32c85465826e8bcc1c59429aaaf107c1e4a9e53c8d8ccd5eff88d0",
|
"sha256": "e13c156562582aa81c60cb29407084cdb54c4164760106ab78e6c5b0858cf64e",
|
||||||
"dest": "cargo/vendor/zeroize-1.8.2"
|
"dest": "cargo/vendor/zeroize-1.9.0"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "inline",
|
"type": "inline",
|
||||||
"contents": "{\"package\": \"b97154e67e32c85465826e8bcc1c59429aaaf107c1e4a9e53c8d8ccd5eff88d0\", \"files\": {}}",
|
"contents": "{\"package\": \"e13c156562582aa81c60cb29407084cdb54c4164760106ab78e6c5b0858cf64e\", \"files\": {}}",
|
||||||
"dest": "cargo/vendor/zeroize-1.8.2",
|
"dest": "cargo/vendor/zeroize-1.9.0",
|
||||||
"dest-filename": ".cargo-checksum.json"
|
"dest-filename": ".cargo-checksum.json"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|||||||
Generated
+84
-85
@@ -117,9 +117,9 @@ checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "bitflags"
|
name = "bitflags"
|
||||||
version = "2.12.1"
|
version = "2.13.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "84d7ced0ae9557296835c32bf1b1e02b44c746701f898460fb000d7eaa84f00a"
|
checksum = "b4388bee8683e3d04af747c73422af53102d2bd24d9eadb6cbc100baef4b43f8"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"serde_core",
|
"serde_core",
|
||||||
]
|
]
|
||||||
@@ -205,7 +205,7 @@ version = "0.18.5"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "8ca26ef0159422fb77631dc9d17b102f253b876fe1586b03b803e63a309b4ee2"
|
checksum = "8ca26ef0159422fb77631dc9d17b102f253b876fe1586b03b803e63a309b4ee2"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bitflags 2.12.1",
|
"bitflags 2.13.0",
|
||||||
"cairo-sys-rs",
|
"cairo-sys-rs",
|
||||||
"glib",
|
"glib",
|
||||||
"libc",
|
"libc",
|
||||||
@@ -268,9 +268,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "cc"
|
name = "cc"
|
||||||
version = "1.2.63"
|
version = "1.2.64"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "556e016178bb5662a08681bbe0f00f8e17631781a4dfc8c45e466e4b185ec27f"
|
checksum = "dad887fd958be91b5098c0248def011f4523ab786cd411be668777e55063501f"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"find-msvc-tools",
|
"find-msvc-tools",
|
||||||
"shlex",
|
"shlex",
|
||||||
@@ -290,7 +290,7 @@ checksum = "d38f2da7a0a2c4ccf0065be06397cc26a81f4e528be095826eee9d4adbb8c60f"
|
|||||||
dependencies = [
|
dependencies = [
|
||||||
"byteorder",
|
"byteorder",
|
||||||
"fnv",
|
"fnv",
|
||||||
"uuid 1.23.2",
|
"uuid 1.23.3",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -317,9 +317,9 @@ checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "chrono"
|
name = "chrono"
|
||||||
version = "0.4.44"
|
version = "0.4.45"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "c673075a2e0e5f4a1dde27ce9dee1ea4558c7ffe648f576438a20ca1d2acc4b0"
|
checksum = "1aa79e62e7697b8e29b513a68abacf485adcd1fe8284a4316c5ae868e6633327"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"iana-time-zone",
|
"iana-time-zone",
|
||||||
"num-traits",
|
"num-traits",
|
||||||
@@ -398,7 +398,7 @@ version = "0.25.0"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "064badf302c3194842cf2c5d61f56cc88e54a759313879cdf03abdd27d0c3b97"
|
checksum = "064badf302c3194842cf2c5d61f56cc88e54a759313879cdf03abdd27d0c3b97"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bitflags 2.12.1",
|
"bitflags 2.13.0",
|
||||||
"core-foundation 0.10.1",
|
"core-foundation 0.10.1",
|
||||||
"core-graphics-types",
|
"core-graphics-types",
|
||||||
"foreign-types 0.5.0",
|
"foreign-types 0.5.0",
|
||||||
@@ -411,7 +411,7 @@ version = "0.2.0"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "3d44a101f213f6c4cdc1853d4b78aef6db6bdfa3468798cc1d9912f4735013eb"
|
checksum = "3d44a101f213f6c4cdc1853d4b78aef6db6bdfa3468798cc1d9912f4735013eb"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bitflags 2.12.1",
|
"bitflags 2.13.0",
|
||||||
"core-foundation 0.10.1",
|
"core-foundation 0.10.1",
|
||||||
"libc",
|
"libc",
|
||||||
]
|
]
|
||||||
@@ -672,7 +672,7 @@ version = "0.3.1"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "1e0e367e4e7da84520dedcac1901e4da967309406d1e51017ae1abfb97adbd38"
|
checksum = "1e0e367e4e7da84520dedcac1901e4da967309406d1e51017ae1abfb97adbd38"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bitflags 2.12.1",
|
"bitflags 2.13.0",
|
||||||
"block2",
|
"block2",
|
||||||
"libc",
|
"libc",
|
||||||
"objc2",
|
"objc2",
|
||||||
@@ -1228,7 +1228,7 @@ version = "0.18.5"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "233daaf6e83ae6a12a52055f568f9d7cf4671dabb78ff9560ab6da230ce00ee5"
|
checksum = "233daaf6e83ae6a12a52055f568f9d7cf4671dabb78ff9560ab6da230ce00ee5"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bitflags 2.12.1",
|
"bitflags 2.13.0",
|
||||||
"futures-channel",
|
"futures-channel",
|
||||||
"futures-core",
|
"futures-core",
|
||||||
"futures-executor",
|
"futures-executor",
|
||||||
@@ -1408,9 +1408,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "http"
|
name = "http"
|
||||||
version = "1.4.1"
|
version = "1.4.2"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "8be7462df143984c4598a256ef469b251d7d7f9e271135073e78fc535414f3d0"
|
checksum = "6970f50e31d6fc17d3fa27329444bfa74e196cf62e95052a3f6fee181dba6425"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bytes",
|
"bytes",
|
||||||
"itoa",
|
"itoa",
|
||||||
@@ -1804,13 +1804,12 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "js-sys"
|
name = "js-sys"
|
||||||
version = "0.3.99"
|
version = "0.3.102"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "142bc4740e452c1e57ade0cbc129f139c9093e354346f0872ef985f4f5cf5f11"
|
checksum = "03d04c30968dffe80775bd4d7fb676131cd04a1fb46d2686dbffbaec2d9dfd31"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"cfg-if",
|
"cfg-if",
|
||||||
"futures-util",
|
"futures-util",
|
||||||
"once_cell",
|
|
||||||
"wasm-bindgen",
|
"wasm-bindgen",
|
||||||
]
|
]
|
||||||
|
|
||||||
@@ -1842,7 +1841,7 @@ version = "0.7.0"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "b750dcadc39a09dbadd74e118f6dd6598df77fa01df0cfcdc52c28dece74528a"
|
checksum = "b750dcadc39a09dbadd74e118f6dd6598df77fa01df0cfcdc52c28dece74528a"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bitflags 2.12.1",
|
"bitflags 2.13.0",
|
||||||
"serde",
|
"serde",
|
||||||
"unicode-segmentation",
|
"unicode-segmentation",
|
||||||
]
|
]
|
||||||
@@ -1940,9 +1939,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "log"
|
name = "log"
|
||||||
version = "0.4.31"
|
version = "0.4.32"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "113b30b4cd05f7c06868fdb2854f66a7b9fece9a48425351cd532e810d74024f"
|
checksum = "953f07c43838f8e6f9758cab68bf5bed85465e7587ebe0b823f1bcd81978ad3a"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "lru-slab"
|
name = "lru-slab"
|
||||||
@@ -1963,9 +1962,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "memchr"
|
name = "memchr"
|
||||||
version = "2.8.1"
|
version = "2.8.2"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "6b947ae49db0d222b1dbc6b113ce7248a3fc3a6ca21b696717bfc000ba4484d8"
|
checksum = "88904434abc2901f197fe8cc55f0445e7ded921dba5911dad2e2b39b48e663c4"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "memoffset"
|
name = "memoffset"
|
||||||
@@ -2005,7 +2004,7 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "moku"
|
name = "moku"
|
||||||
version = "0.9.4"
|
version = "0.10.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"dirs 5.0.1",
|
"dirs 5.0.1",
|
||||||
"reqwest 0.12.28",
|
"reqwest 0.12.28",
|
||||||
@@ -2071,7 +2070,7 @@ version = "0.9.0"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "c3f42e7bbe13d351b6bead8286a43aac9534b82bd3cc43e47037f012ebfd62d4"
|
checksum = "c3f42e7bbe13d351b6bead8286a43aac9534b82bd3cc43e47037f012ebfd62d4"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bitflags 2.12.1",
|
"bitflags 2.13.0",
|
||||||
"jni-sys 0.3.1",
|
"jni-sys 0.3.1",
|
||||||
"log",
|
"log",
|
||||||
"ndk-sys",
|
"ndk-sys",
|
||||||
@@ -2101,7 +2100,7 @@ version = "0.31.3"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "cf20d2fde8ff38632c426f1165ed7436270b44f199fc55284c38276f9db47c3d"
|
checksum = "cf20d2fde8ff38632c426f1165ed7436270b44f199fc55284c38276f9db47c3d"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bitflags 2.12.1",
|
"bitflags 2.13.0",
|
||||||
"cfg-if",
|
"cfg-if",
|
||||||
"cfg_aliases",
|
"cfg_aliases",
|
||||||
"libc",
|
"libc",
|
||||||
@@ -2169,7 +2168,7 @@ version = "0.3.2"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "d49e936b501e5c5bf01fda3a9452ff86dc3ea98ad5f283e1455153142d97518c"
|
checksum = "d49e936b501e5c5bf01fda3a9452ff86dc3ea98ad5f283e1455153142d97518c"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bitflags 2.12.1",
|
"bitflags 2.13.0",
|
||||||
"block2",
|
"block2",
|
||||||
"objc2",
|
"objc2",
|
||||||
"objc2-core-foundation",
|
"objc2-core-foundation",
|
||||||
@@ -2182,7 +2181,7 @@ version = "0.3.2"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "73ad74d880bb43877038da939b7427bba67e9dd42004a18b809ba7d87cee241c"
|
checksum = "73ad74d880bb43877038da939b7427bba67e9dd42004a18b809ba7d87cee241c"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bitflags 2.12.1",
|
"bitflags 2.13.0",
|
||||||
"objc2",
|
"objc2",
|
||||||
"objc2-foundation",
|
"objc2-foundation",
|
||||||
]
|
]
|
||||||
@@ -2203,7 +2202,7 @@ version = "0.3.2"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "2a180dd8642fa45cdb7dd721cd4c11b1cadd4929ce112ebd8b9f5803cc79d536"
|
checksum = "2a180dd8642fa45cdb7dd721cd4c11b1cadd4929ce112ebd8b9f5803cc79d536"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bitflags 2.12.1",
|
"bitflags 2.13.0",
|
||||||
"dispatch2",
|
"dispatch2",
|
||||||
"objc2",
|
"objc2",
|
||||||
]
|
]
|
||||||
@@ -2214,7 +2213,7 @@ version = "0.3.2"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "e022c9d066895efa1345f8e33e584b9f958da2fd4cd116792e15e07e4720a807"
|
checksum = "e022c9d066895efa1345f8e33e584b9f958da2fd4cd116792e15e07e4720a807"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bitflags 2.12.1",
|
"bitflags 2.13.0",
|
||||||
"dispatch2",
|
"dispatch2",
|
||||||
"objc2",
|
"objc2",
|
||||||
"objc2-core-foundation",
|
"objc2-core-foundation",
|
||||||
@@ -2247,7 +2246,7 @@ version = "0.3.2"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "0cde0dfb48d25d2b4862161a4d5fcc0e3c24367869ad306b0c9ec0073bfed92d"
|
checksum = "0cde0dfb48d25d2b4862161a4d5fcc0e3c24367869ad306b0c9ec0073bfed92d"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bitflags 2.12.1",
|
"bitflags 2.13.0",
|
||||||
"objc2",
|
"objc2",
|
||||||
"objc2-core-foundation",
|
"objc2-core-foundation",
|
||||||
"objc2-core-graphics",
|
"objc2-core-graphics",
|
||||||
@@ -2274,7 +2273,7 @@ version = "0.3.2"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "e3e0adef53c21f888deb4fa59fc59f7eb17404926ee8a6f59f5df0fd7f9f3272"
|
checksum = "e3e0adef53c21f888deb4fa59fc59f7eb17404926ee8a6f59f5df0fd7f9f3272"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bitflags 2.12.1",
|
"bitflags 2.13.0",
|
||||||
"block2",
|
"block2",
|
||||||
"libc",
|
"libc",
|
||||||
"objc2",
|
"objc2",
|
||||||
@@ -2297,7 +2296,7 @@ version = "0.3.2"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "180788110936d59bab6bd83b6060ffdfffb3b922ba1396b312ae795e1de9d81d"
|
checksum = "180788110936d59bab6bd83b6060ffdfffb3b922ba1396b312ae795e1de9d81d"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bitflags 2.12.1",
|
"bitflags 2.13.0",
|
||||||
"objc2",
|
"objc2",
|
||||||
"objc2-core-foundation",
|
"objc2-core-foundation",
|
||||||
]
|
]
|
||||||
@@ -2308,7 +2307,7 @@ version = "0.3.2"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "96c1358452b371bf9f104e21ec536d37a650eb10f7ee379fff67d2e08d537f1f"
|
checksum = "96c1358452b371bf9f104e21ec536d37a650eb10f7ee379fff67d2e08d537f1f"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bitflags 2.12.1",
|
"bitflags 2.13.0",
|
||||||
"objc2",
|
"objc2",
|
||||||
"objc2-core-foundation",
|
"objc2-core-foundation",
|
||||||
"objc2-foundation",
|
"objc2-foundation",
|
||||||
@@ -2320,7 +2319,7 @@ version = "0.3.2"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "d87d638e33c06f577498cbcc50491496a3ed4246998a7fbba7ccb98b1e7eab22"
|
checksum = "d87d638e33c06f577498cbcc50491496a3ed4246998a7fbba7ccb98b1e7eab22"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bitflags 2.12.1",
|
"bitflags 2.13.0",
|
||||||
"block2",
|
"block2",
|
||||||
"objc2",
|
"objc2",
|
||||||
"objc2-cloud-kit",
|
"objc2-cloud-kit",
|
||||||
@@ -2351,7 +2350,7 @@ version = "0.3.2"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "b2e5aaab980c433cf470df9d7af96a7b46a9d892d521a2cbbb2f8a4c16751e7f"
|
checksum = "b2e5aaab980c433cf470df9d7af96a7b46a9d892d521a2cbbb2f8a4c16751e7f"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bitflags 2.12.1",
|
"bitflags 2.13.0",
|
||||||
"block2",
|
"block2",
|
||||||
"objc2",
|
"objc2",
|
||||||
"objc2-app-kit",
|
"objc2-app-kit",
|
||||||
@@ -2379,11 +2378,11 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "openssl"
|
name = "openssl"
|
||||||
version = "0.10.80"
|
version = "0.10.81"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "a45fa2aa886c42762255da344f0a0d313e254066c46aad76f300c3d3da62d967"
|
checksum = "77823a27f0babb03091cb9ed9ef80af3b39dbc82f97e8fa530374b7dafd87a45"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bitflags 2.12.1",
|
"bitflags 2.13.0",
|
||||||
"cfg-if",
|
"cfg-if",
|
||||||
"foreign-types 0.3.2",
|
"foreign-types 0.3.2",
|
||||||
"libc",
|
"libc",
|
||||||
@@ -2410,9 +2409,9 @@ checksum = "7c87def4c32ab89d880effc9e097653c8da5d6ef28e6b539d313baaacfbafcbe"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "openssl-sys"
|
name = "openssl-sys"
|
||||||
version = "0.9.116"
|
version = "0.9.117"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "f28a22dc7140cda5f096e5e7724a6962ca81a7f8bfd2979f9b18c11af56318c4"
|
checksum = "b47e7e6bb2c38cd930d25a23b40fa52e068c10e85f3e03a7f5ba5aaca5713695"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"cc",
|
"cc",
|
||||||
"libc",
|
"libc",
|
||||||
@@ -2609,7 +2608,7 @@ version = "0.18.1"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "60769b8b31b2a9f263dae2776c37b1b28ae246943cf719eb6946a1db05128a61"
|
checksum = "60769b8b31b2a9f263dae2776c37b1b28ae246943cf719eb6946a1db05128a61"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bitflags 2.12.1",
|
"bitflags 2.13.0",
|
||||||
"crc32fast",
|
"crc32fast",
|
||||||
"fdeflate",
|
"fdeflate",
|
||||||
"flate2",
|
"flate2",
|
||||||
@@ -2880,7 +2879,7 @@ version = "0.5.18"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "ed2bf2547551a7053d6fdfafda3f938979645c44812fbfcda098faae3f1a362d"
|
checksum = "ed2bf2547551a7053d6fdfafda3f938979645c44812fbfcda098faae3f1a362d"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bitflags 2.12.1",
|
"bitflags 2.13.0",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -2927,9 +2926,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "regex"
|
name = "regex"
|
||||||
version = "1.12.3"
|
version = "1.12.4"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "e10754a14b9137dd7b1e3e5b0493cc9171fdd105e0ab477f51b72e7f3ac0e276"
|
checksum = "f1292b7759ae1cb9ec195452d1390a074f0cd8541ab7a5a8c31cd6db45d4a6ba"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"aho-corasick",
|
"aho-corasick",
|
||||||
"memchr",
|
"memchr",
|
||||||
@@ -2950,9 +2949,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "regex-syntax"
|
name = "regex-syntax"
|
||||||
version = "0.8.10"
|
version = "0.8.11"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "dc897dd8d9e8bd1ed8cdad82b5966c3e0ecae09fb1907d58efaa013543185d0a"
|
checksum = "d6f6ff9a378485b298a5286656da665ba74413d36db0979633275d2e708145d4"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "reqwest"
|
name = "reqwest"
|
||||||
@@ -3095,7 +3094,7 @@ version = "1.1.4"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "b6fe4565b9518b83ef4f91bb47ce29620ca828bd32cb7e408f0062e9930ba190"
|
checksum = "b6fe4565b9518b83ef4f91bb47ce29620ca828bd32cb7e408f0062e9930ba190"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bitflags 2.12.1",
|
"bitflags 2.13.0",
|
||||||
"errno",
|
"errno",
|
||||||
"libc",
|
"libc",
|
||||||
"linux-raw-sys",
|
"linux-raw-sys",
|
||||||
@@ -3179,7 +3178,7 @@ dependencies = [
|
|||||||
"serde",
|
"serde",
|
||||||
"serde_json",
|
"serde_json",
|
||||||
"url",
|
"url",
|
||||||
"uuid 1.23.2",
|
"uuid 1.23.3",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -3230,7 +3229,7 @@ version = "3.7.0"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "b7f4bc775c73d9a02cde8bf7b2ec4c9d12743edf609006c7facc23998404cd1d"
|
checksum = "b7f4bc775c73d9a02cde8bf7b2ec4c9d12743edf609006c7facc23998404cd1d"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bitflags 2.12.1",
|
"bitflags 2.13.0",
|
||||||
"core-foundation 0.10.1",
|
"core-foundation 0.10.1",
|
||||||
"core-foundation-sys",
|
"core-foundation-sys",
|
||||||
"libc",
|
"libc",
|
||||||
@@ -3253,7 +3252,7 @@ version = "0.36.1"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "c5d9c0c92a92d33f08817311cf3f2c29a3538a8240e94a6a3c622ce652d7e00c"
|
checksum = "c5d9c0c92a92d33f08817311cf3f2c29a3538a8240e94a6a3c622ce652d7e00c"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bitflags 2.12.1",
|
"bitflags 2.13.0",
|
||||||
"cssparser",
|
"cssparser",
|
||||||
"derive_more",
|
"derive_more",
|
||||||
"log",
|
"log",
|
||||||
@@ -3385,9 +3384,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "serde_with"
|
name = "serde_with"
|
||||||
version = "3.20.0"
|
version = "3.21.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "e72c1c2cb7b223fafb600a619537a871c2818583d619401b785e7c0b746ccde2"
|
checksum = "76a5c54c7310e7b8b9577c286d7e399ddd876c3e12b3ed917a8aabc4b96e9e8c"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"base64 0.22.1",
|
"base64 0.22.1",
|
||||||
"bs58",
|
"bs58",
|
||||||
@@ -3405,9 +3404,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "serde_with_macros"
|
name = "serde_with_macros"
|
||||||
version = "3.20.0"
|
version = "3.21.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "b90c488738ecb4fb0262f41f43bc40efc5868d9fb744319ddf5f5317f417bfac"
|
checksum = "84d57bc0c8b9a17920c178daa6bb924850d54a9c97ab45194bb8c17ad66bb660"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"darling",
|
"darling",
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
@@ -3525,9 +3524,9 @@ checksum = "0c790de23124f9ab44544d7ac05d60440adc586479ce501c1d6d7da3cd8c9cf5"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "smallvec"
|
name = "smallvec"
|
||||||
version = "1.15.1"
|
version = "1.15.2"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03"
|
checksum = "8ed6a63f02c8539c91a8685a86f4099661ba3da017932f6ebbea6de3f0fa7c90"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "socket2"
|
name = "socket2"
|
||||||
@@ -3724,7 +3723,7 @@ version = "0.7.0"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "a13f3d0daba03132c0aa9767f98351b3488edc2c100cda2d2ec2b04f3d8d3c8b"
|
checksum = "a13f3d0daba03132c0aa9767f98351b3488edc2c100cda2d2ec2b04f3d8d3c8b"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bitflags 2.12.1",
|
"bitflags 2.13.0",
|
||||||
"core-foundation 0.9.4",
|
"core-foundation 0.9.4",
|
||||||
"system-configuration-sys",
|
"system-configuration-sys",
|
||||||
]
|
]
|
||||||
@@ -3758,7 +3757,7 @@ version = "0.35.3"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "d1c93047acf68669466a34690ac58cca7010bd1b201e1ec86f1fd0a75d3dd4a9"
|
checksum = "d1c93047acf68669466a34690ac58cca7010bd1b201e1ec86f1fd0a75d3dd4a9"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bitflags 2.12.1",
|
"bitflags 2.13.0",
|
||||||
"block2",
|
"block2",
|
||||||
"core-foundation 0.10.1",
|
"core-foundation 0.10.1",
|
||||||
"core-graphics",
|
"core-graphics",
|
||||||
@@ -3904,7 +3903,7 @@ dependencies = [
|
|||||||
"thiserror 2.0.18",
|
"thiserror 2.0.18",
|
||||||
"time",
|
"time",
|
||||||
"url",
|
"url",
|
||||||
"uuid 1.23.2",
|
"uuid 1.23.3",
|
||||||
"walkdir",
|
"walkdir",
|
||||||
]
|
]
|
||||||
|
|
||||||
@@ -4171,7 +4170,7 @@ dependencies = [
|
|||||||
"toml 1.1.2+spec-1.1.0",
|
"toml 1.1.2+spec-1.1.0",
|
||||||
"url",
|
"url",
|
||||||
"urlpattern",
|
"urlpattern",
|
||||||
"uuid 1.23.2",
|
"uuid 1.23.3",
|
||||||
"walkdir",
|
"walkdir",
|
||||||
]
|
]
|
||||||
|
|
||||||
@@ -4505,7 +4504,7 @@ version = "0.6.11"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "4cfcf7e2740e6fc6d4d688b4ef00650406bb94adf4731e43c096c3a19fe40840"
|
checksum = "4cfcf7e2740e6fc6d4d688b4ef00650406bb94adf4731e43c096c3a19fe40840"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bitflags 2.12.1",
|
"bitflags 2.13.0",
|
||||||
"bytes",
|
"bytes",
|
||||||
"futures-util",
|
"futures-util",
|
||||||
"http",
|
"http",
|
||||||
@@ -4719,9 +4718,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "uuid"
|
name = "uuid"
|
||||||
version = "1.23.2"
|
version = "1.23.3"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "d258b83ceec21034727ecee8c382cfa6c3e133699b0742c64571814fb420c9f7"
|
checksum = "144d6b123cef80b301b8f72a9e2ca4370ddec21950d0a103dd22c437006d2db7"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"getrandom 0.4.2",
|
"getrandom 0.4.2",
|
||||||
"js-sys",
|
"js-sys",
|
||||||
@@ -4794,9 +4793,9 @@ checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "wasip2"
|
name = "wasip2"
|
||||||
version = "1.0.3+wasi-0.2.9"
|
version = "1.0.4+wasi-0.2.12"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "20064672db26d7cdc89c7798c48a0fdfac8213434a1186e5ef29fd560ae223d6"
|
checksum = "b67efb37e106e55ce722a510d6b5f9c17f083e5fc79afc2badeb12cc313d9487"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"wit-bindgen 0.57.1",
|
"wit-bindgen 0.57.1",
|
||||||
]
|
]
|
||||||
@@ -4812,9 +4811,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "wasm-bindgen"
|
name = "wasm-bindgen"
|
||||||
version = "0.2.122"
|
version = "0.2.125"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "3ed04576f974d2b2fba0f38c51dbc5518011e38c36bf1143164be765528fd409"
|
checksum = "8ddb3f79143bced6de84270411622a2699cee572fc0875aeaf1e7867cf9fca1a"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"cfg-if",
|
"cfg-if",
|
||||||
"once_cell",
|
"once_cell",
|
||||||
@@ -4825,9 +4824,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "wasm-bindgen-futures"
|
name = "wasm-bindgen-futures"
|
||||||
version = "0.4.72"
|
version = "0.4.75"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "9473dbd2991ae90b6291c3c32c30c6187ac49aa32f9905d1cce280ec1e110b0f"
|
checksum = "503b14d284f2c8dac03b819967e155ea753f573586193b2b2c95990cb5d69280"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"js-sys",
|
"js-sys",
|
||||||
"wasm-bindgen",
|
"wasm-bindgen",
|
||||||
@@ -4835,9 +4834,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "wasm-bindgen-macro"
|
name = "wasm-bindgen-macro"
|
||||||
version = "0.2.122"
|
version = "0.2.125"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "916151b09da36bd82f6615cbf3a419e2f0ba23a03c6160e8e92eb6bd4aa1dec6"
|
checksum = "4e21a184b13fb19e157296e2c46056aec9092264fab83e4ba59e68c61b323c3d"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"quote",
|
"quote",
|
||||||
"wasm-bindgen-macro-support",
|
"wasm-bindgen-macro-support",
|
||||||
@@ -4845,9 +4844,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "wasm-bindgen-macro-support"
|
name = "wasm-bindgen-macro-support"
|
||||||
version = "0.2.122"
|
version = "0.2.125"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "299047362ccbfce148b67ab7e73349f77748e00c8296f9542adfad2ad82c5c5e"
|
checksum = "fecefd9c35bd935a20fc3fc344b5f29138961e4f47fb03297d88f2587afb5ebd"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bumpalo",
|
"bumpalo",
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
@@ -4858,9 +4857,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "wasm-bindgen-shared"
|
name = "wasm-bindgen-shared"
|
||||||
version = "0.2.122"
|
version = "0.2.125"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "9a929b2c61f11ba3e9bc35b50c1f25cb38e0e892c0c231ae2b8cf78d5dad4437"
|
checksum = "23939e44bb9a5d7576fa2b563dc2e136628f1224e88a8deed09e04858b77871f"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"unicode-ident",
|
"unicode-ident",
|
||||||
]
|
]
|
||||||
@@ -4906,7 +4905,7 @@ version = "0.244.0"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "47b807c72e1bac69382b3a6fb3dbe8ea4c0ed87ff5629b8685ae6b9a611028fe"
|
checksum = "47b807c72e1bac69382b3a6fb3dbe8ea4c0ed87ff5629b8685ae6b9a611028fe"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bitflags 2.12.1",
|
"bitflags 2.13.0",
|
||||||
"hashbrown 0.15.5",
|
"hashbrown 0.15.5",
|
||||||
"indexmap 2.14.0",
|
"indexmap 2.14.0",
|
||||||
"semver",
|
"semver",
|
||||||
@@ -4914,9 +4913,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "web-sys"
|
name = "web-sys"
|
||||||
version = "0.3.99"
|
version = "0.3.102"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "6d621441cfc37b84979402712047321980c178f299193a3589d05b99e8763436"
|
checksum = "a6430a72df5eb332242960fe84b3002a241163998241eb596d4f739b9757061d"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"js-sys",
|
"js-sys",
|
||||||
"wasm-bindgen",
|
"wasm-bindgen",
|
||||||
@@ -5759,7 +5758,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||||||
checksum = "9d66ea20e9553b30172b5e831994e35fbde2d165325bec84fc43dbf6f4eb9cb2"
|
checksum = "9d66ea20e9553b30172b5e831994e35fbde2d165325bec84fc43dbf6f4eb9cb2"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"bitflags 2.12.1",
|
"bitflags 2.13.0",
|
||||||
"indexmap 2.14.0",
|
"indexmap 2.14.0",
|
||||||
"log",
|
"log",
|
||||||
"serde",
|
"serde",
|
||||||
@@ -5885,18 +5884,18 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "zerocopy"
|
name = "zerocopy"
|
||||||
version = "0.8.50"
|
version = "0.8.52"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "3b065d4f0e55f82fae73202e189638116a87c55ab6b8e6c2721e13dd9d854ad1"
|
checksum = "ce1022995ff5ff5d841ad7d994facc23098cd40152f2c1d11cd607c6f530653f"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"zerocopy-derive",
|
"zerocopy-derive",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "zerocopy-derive"
|
name = "zerocopy-derive"
|
||||||
version = "0.8.50"
|
version = "0.8.52"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "0b631b19d36a892ab55420c92dbc83ccd79274f25be714855d3074aa71cab639"
|
checksum = "1ae7f38b72ec2a254e2b87ef277cf2cd4fb97cbebf944faa6f33354da0867930"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
@@ -5926,9 +5925,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "zeroize"
|
name = "zeroize"
|
||||||
version = "1.8.2"
|
version = "1.9.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "b97154e67e32c85465826e8bcc1c59429aaaf107c1e4a9e53c8d8ccd5eff88d0"
|
checksum = "e13c156562582aa81c60cb29407084cdb54c4164760106ab78e6c5b0858cf64e"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "zerotrie"
|
name = "zerotrie"
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "moku"
|
name = "moku"
|
||||||
version = "0.9.4"
|
version = "0.10.0"
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
|
|
||||||
[lib]
|
[lib]
|
||||||
|
|||||||
@@ -1,10 +1,9 @@
|
|||||||
{
|
{
|
||||||
"$schema": "https://schema.tauri.app/config/2",
|
"$schema": "https://schema.tauri.app/config/2",
|
||||||
"productName": "Moku",
|
"productName": "Moku",
|
||||||
"version": "0.9.4",
|
"version": "0.10.0",
|
||||||
"identifier": "io.github.MokuProject.Moku",
|
"identifier": "io.github.MokuProject.Moku",
|
||||||
"build": {
|
"build": {
|
||||||
"devUrl": "http://localhost:1420",
|
|
||||||
"frontendDist": "../dist",
|
"frontendDist": "../dist",
|
||||||
"beforeBuildCommand": "pnpm build:static"
|
"beforeBuildCommand": "pnpm build:static"
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
{
|
{
|
||||||
"build": {
|
"build": {
|
||||||
|
"devUrl": "http://localhost:1420",
|
||||||
"beforeDevCommand": "pnpm dev"
|
"beforeDevCommand": "pnpm dev"
|
||||||
},
|
},
|
||||||
"app": {
|
"app": {
|
||||||
|
|||||||
+20
-30
@@ -1,21 +1,13 @@
|
|||||||
import { detectAdapter } from '$lib/platform-adapters'
|
import { detectAdapter } from '$lib/platform-adapters'
|
||||||
import { initPlatformService } from '$lib/platform-service'
|
import { initPlatformService } from '$lib/platform-service'
|
||||||
import { initRequestManager } from '$lib/request-manager'
|
import { initRequestManager } from '$lib/request-manager'
|
||||||
import { appState } from '$lib/state/app.svelte'
|
import { appState } from '$lib/state/app.svelte'
|
||||||
import { configureAuth, probeServer } from '$lib/core/auth'
|
import { configureAuth, probeServer } from '$lib/core/auth'
|
||||||
import { loadSettings, loadLibrary, loadUpdates } from '$lib/core/persistence/persist'
|
import { loadSettings, loadLibrary } from '$lib/core/persistence/persist'
|
||||||
import { loadSettingsIntoState } from '$lib/state/settings.svelte'
|
import { loadSettingsIntoState, settingsState } from '$lib/state/settings.svelte'
|
||||||
import { historyState } from '$lib/state/history.svelte'
|
import { historyState } from '$lib/state/history.svelte'
|
||||||
import { readerState } from '$lib/state/reader.svelte'
|
import { readerState } from '$lib/state/reader.svelte'
|
||||||
|
import { seriesState } from '$lib/state/series.svelte'
|
||||||
const KEY_URL = 'moku_server_url'
|
|
||||||
const KEY_AUTH = 'moku_auth_config'
|
|
||||||
|
|
||||||
interface SavedAuth {
|
|
||||||
mode: 'NONE' | 'BASIC_AUTH' | 'UI_LOGIN'
|
|
||||||
user?: string
|
|
||||||
pass?: string
|
|
||||||
}
|
|
||||||
|
|
||||||
async function boot() {
|
async function boot() {
|
||||||
try {
|
try {
|
||||||
@@ -34,36 +26,34 @@ async function boot() {
|
|||||||
const [settingsData, libraryData] = await Promise.all([
|
const [settingsData, libraryData] = await Promise.all([
|
||||||
loadSettings(),
|
loadSettings(),
|
||||||
loadLibrary(),
|
loadLibrary(),
|
||||||
loadUpdates(),
|
|
||||||
])
|
])
|
||||||
|
|
||||||
await loadSettingsIntoState(settingsData.settings)
|
await loadSettingsIntoState(settingsData.settings)
|
||||||
|
|
||||||
readerState.bookmarks = libraryData.bookmarks
|
seriesState.bookmarks = libraryData.bookmarks
|
||||||
readerState.markers = libraryData.markers
|
readerState.markers = libraryData.markers
|
||||||
historyState.load(libraryData.sessions, libraryData.dailyReadCounts)
|
historyState.load(libraryData.sessions, libraryData.dailyReadCounts)
|
||||||
|
|
||||||
const savedUrl = (await platformAdapter.getCredential(KEY_URL)) ?? 'http://127.0.0.1:4567'
|
const savedUrl = settingsState.settings.serverUrl ?? 'http://127.0.0.1:4567'
|
||||||
const savedAuthRaw = await platformAdapter.getCredential(KEY_AUTH)
|
const authMode = settingsState.settings.serverAuthMode ?? 'NONE'
|
||||||
const savedAuth: SavedAuth = savedAuthRaw ? JSON.parse(savedAuthRaw) : { mode: 'NONE' }
|
const authUser = settingsState.settings.serverAuthUser || undefined
|
||||||
|
const authPass = settingsState.settings.serverAuthPass || undefined
|
||||||
|
|
||||||
appState.serverUrl = savedUrl
|
appState.serverUrl = savedUrl
|
||||||
appState.authMode = savedAuth.mode
|
appState.authMode = authMode === 'SIMPLE_LOGIN' ? 'UI_LOGIN' : authMode
|
||||||
appState.authUser = savedAuth.user ?? ''
|
|
||||||
appState.authPass = savedAuth.pass ?? ''
|
|
||||||
|
|
||||||
configureAuth(savedUrl, savedAuth.mode, savedAuth.user, savedAuth.pass)
|
configureAuth(savedUrl, authMode, authUser, authPass)
|
||||||
|
|
||||||
await serverAdapter.connect({
|
await serverAdapter.connect({
|
||||||
baseUrl: savedUrl,
|
baseUrl: savedUrl,
|
||||||
credentials:
|
credentials:
|
||||||
savedAuth.mode === 'BASIC_AUTH' && savedAuth.user && savedAuth.pass
|
authMode === 'BASIC_AUTH' && authUser && authPass
|
||||||
? { username: savedAuth.user, password: savedAuth.pass }
|
? { username: authUser, password: authPass }
|
||||||
: undefined,
|
: undefined,
|
||||||
})
|
})
|
||||||
|
|
||||||
const isTauri = platformAdapter.platform === 'tauri'
|
const isTauri = platformAdapter.platform === 'tauri'
|
||||||
const autoStartServer = settingsData.settings.autoStartServer ?? false
|
const autoStartServer = settingsState.settings.autoStartServer
|
||||||
|
|
||||||
if (isTauri && autoStartServer) {
|
if (isTauri && autoStartServer) {
|
||||||
appState.status = 'booting'
|
appState.status = 'booting'
|
||||||
|
|||||||
@@ -170,7 +170,7 @@
|
|||||||
label: "New folder & add",
|
label: "New folder & add",
|
||||||
icon: FolderSimplePlusIcon,
|
icon: FolderSimplePlusIcon,
|
||||||
onClick: async () => {
|
onClick: async () => {
|
||||||
const name = prompt("FolderIcon name:");
|
const name = prompt("Folder name:");
|
||||||
if (!name?.trim()) return;
|
if (!name?.trim()) return;
|
||||||
const cat = await getAdapter().createCategory(name.trim()).catch(console.error);
|
const cat = await getAdapter().createCategory(name.trim()).catch(console.error);
|
||||||
if (cat) {
|
if (cat) {
|
||||||
|
|||||||
@@ -43,20 +43,20 @@
|
|||||||
const isDev = import.meta.env.DEV
|
const isDev = import.meta.env.DEV
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
mode?: 'loading' | 'idle' | 'locked'
|
mode?: 'loading' | 'idle' | 'locked'
|
||||||
ringFull?: boolean
|
ringFull?: boolean
|
||||||
failed?: boolean
|
failed?: boolean
|
||||||
notConfigured?: boolean
|
notConfigured?: boolean
|
||||||
showCards?: boolean
|
showCards?: boolean
|
||||||
showFps?: boolean
|
showFps?: boolean
|
||||||
showDevOverlay?: boolean
|
showDevOverlay?: boolean
|
||||||
pinLen?: number
|
pinLen?: number
|
||||||
pinCorrect?: string
|
pinCorrect?: string
|
||||||
onReady?: () => void
|
onReady?: () => void
|
||||||
onUnlock?: () => void
|
onUnlock?: () => void
|
||||||
onRetry?: () => void
|
onRetry?: () => void
|
||||||
onBypass?: () => void
|
onBypass?: () => void
|
||||||
onDismiss?: () => void
|
onDismiss?: () => void
|
||||||
}
|
}
|
||||||
|
|
||||||
let {
|
let {
|
||||||
@@ -91,13 +91,14 @@
|
|||||||
const PHASE2_MS = 10000
|
const PHASE2_MS = 10000
|
||||||
|
|
||||||
function triggerExit(cb?: () => void) {
|
function triggerExit(cb?: () => void) {
|
||||||
if (exitLock) return
|
console.log('[splash] triggerExit called — exitLock:', exitLock, 'mode:', mode, 'cb:', cb?.name ?? String(cb))
|
||||||
|
if (exitLock) { console.log('[splash] triggerExit blocked by exitLock'); return }
|
||||||
exitLock = true
|
exitLock = true
|
||||||
exiting = true
|
exiting = true
|
||||||
setTimeout(() => cb?.(), EXIT_MS)
|
setTimeout(() => { console.log('[splash] triggerExit timeout — calling cb'); cb?.() }, EXIT_MS)
|
||||||
}
|
}
|
||||||
|
|
||||||
let animFrame: number
|
let animFrame = 0
|
||||||
let animStart: number | null = null
|
let animStart: number | null = null
|
||||||
let animPhase = 1
|
let animPhase = 1
|
||||||
|
|
||||||
@@ -117,20 +118,21 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
$effect(() => {
|
$effect(() => {
|
||||||
if (mode === 'loading' && !failed && !notConfigured && !ringFull) {
|
if (mode !== 'loading' || failed || notConfigured || ringFull || !isTauri) return
|
||||||
if (!isTauri) return
|
animStart = null
|
||||||
animStart = null
|
animPhase = 1
|
||||||
animPhase = 1
|
animFrame = requestAnimationFrame(animateRing)
|
||||||
animFrame = requestAnimationFrame(animateRing)
|
return () => cancelAnimationFrame(animFrame)
|
||||||
return () => cancelAnimationFrame(animFrame)
|
|
||||||
}
|
|
||||||
})
|
})
|
||||||
|
|
||||||
$effect(() => {
|
$effect(() => {
|
||||||
|
console.log('[splash] ringFull effect — ringFull:', ringFull, 'mode:', mode, 'exitLock:', exitLock)
|
||||||
if (!ringFull || mode === 'locked') { exitLock = false; exiting = false; return }
|
if (!ringFull || mode === 'locked') { exitLock = false; exiting = false; return }
|
||||||
cancelAnimationFrame(animFrame)
|
cancelAnimationFrame(animFrame)
|
||||||
ringProg = 1
|
animFrame = 0
|
||||||
setTimeout(() => triggerExit(onReady), 650)
|
ringProg = 1
|
||||||
|
const t = setTimeout(() => { console.log('[splash] ringFull timeout firing — calling triggerExit(onReady)'); triggerExit(onReady) }, 650)
|
||||||
|
return () => { console.log('[splash] ringFull effect cleanup — cancelling timeout'); clearTimeout(t) }
|
||||||
})
|
})
|
||||||
|
|
||||||
function submitPin() {
|
function submitPin() {
|
||||||
@@ -179,6 +181,7 @@
|
|||||||
window.removeEventListener('touchstart', handler)
|
window.removeEventListener('touchstart', handler)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return () => clearInterval(iv)
|
return () => clearInterval(iv)
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -212,7 +215,7 @@
|
|||||||
const speed = cfg.speedMin + hash(seed + 5) * (cfg.speedMax - cfg.speedMin)
|
const speed = cfg.speedMin + hash(seed + 5) * (cfg.speedMax - cfg.speedMin)
|
||||||
const travel = vh + h + BUF
|
const travel = vh + h + BUF
|
||||||
cards.push({
|
cards.push({
|
||||||
cx: (col + 0.5) * laneW + (hash(seed + 2) * 2 - 1) * Math.max(0, (laneW - w) / 2 - 2),
|
cx: (col + 0.5) * laneW + (hash(seed + 2) * 2 - 1) * Math.max(0, (laneW - w) / 2 - 2),
|
||||||
w, h,
|
w, h,
|
||||||
lines: 1 + Math.floor(hash(seed + 7) * 3),
|
lines: 1 + Math.floor(hash(seed + 7) * 3),
|
||||||
alpha: cfg.alpha,
|
alpha: cfg.alpha,
|
||||||
@@ -307,11 +310,12 @@
|
|||||||
ctx.drawImage(vignette, 0, 0, cw, ch)
|
ctx.drawImage(vignette, 0, 0, cw, ch)
|
||||||
}
|
}
|
||||||
|
|
||||||
let fps = 0, fpsFrames = 0, fpsLast = 0
|
let fpsFrames = 0, fpsLast = -1
|
||||||
function tickFps(now: number) {
|
function tickFps(now: number) {
|
||||||
|
if (fpsLast < 0) { fpsLast = now; return }
|
||||||
fpsFrames++
|
fpsFrames++
|
||||||
if (now - fpsLast >= 500) {
|
if (now - fpsLast >= 500) {
|
||||||
fps = Math.round(fpsFrames / ((now - fpsLast) / 1000))
|
const fps = Math.round(fpsFrames / ((now - fpsLast) / 1000))
|
||||||
fpsFrames = 0
|
fpsFrames = 0
|
||||||
fpsLast = now
|
fpsLast = now
|
||||||
if (fpsEl) fpsEl.textContent = `${fps} fps`
|
if (fpsEl) fpsEl.textContent = `${fps} fps`
|
||||||
@@ -370,7 +374,7 @@
|
|||||||
function cleanup() {
|
function cleanup() {
|
||||||
if (live) {
|
if (live) {
|
||||||
live.stamps.forEach(c => { c.width = 0; c.height = 0 })
|
live.stamps.forEach(c => { c.width = 0; c.height = 0 })
|
||||||
live.vignette.width = 0
|
live.vignette.width = 0
|
||||||
live.vignette.height = 0
|
live.vignette.height = 0
|
||||||
live = null
|
live = null
|
||||||
}
|
}
|
||||||
@@ -444,14 +448,17 @@
|
|||||||
|
|
||||||
document.addEventListener('visibilitychange', onVis)
|
document.addEventListener('visibilitychange', onVis)
|
||||||
raf = requestAnimationFrame(frame)
|
raf = requestAnimationFrame(frame)
|
||||||
return () => {
|
|
||||||
cancelAnimationFrame(raf)
|
return {
|
||||||
cleanup()
|
destroy() {
|
||||||
extraCleanup?.()
|
cancelAnimationFrame(raf)
|
||||||
document.removeEventListener('visibilitychange', onVis)
|
cleanup()
|
||||||
if (isDev && mode === 'idle') {
|
extraCleanup?.()
|
||||||
splashDevUnregister(el)
|
document.removeEventListener('visibilitychange', onVis)
|
||||||
devLiveCount = splashDevLiveCount()
|
if (isDev && mode === 'idle') {
|
||||||
|
splashDevUnregister(el)
|
||||||
|
devLiveCount = splashDevLiveCount()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -502,9 +509,9 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{:else}
|
{:else if isTauri || failed || notConfigured || ringFull}
|
||||||
<div style="position:relative;width:{ringSize}px;height:{ringSize}px;margin-bottom:20px;display:flex;align-items:center;justify-content:center">
|
<div style="position:relative;width:{ringSize}px;height:{ringSize}px;margin-bottom:20px;display:flex;align-items:center;justify-content:center">
|
||||||
{#if !failed && !notConfigured}
|
{#if !failed && !notConfigured && isTauri}
|
||||||
<svg width={ringSize} height={ringSize} class="loading-ring" style="position:absolute;top:0;left:0;pointer-events:none">
|
<svg width={ringSize} height={ringSize} class="loading-ring" style="position:absolute;top:0;left:0;pointer-events:none">
|
||||||
<circle cx={ringC} cy={ringC} r={ringR} fill="none" stroke="var(--border-base)" stroke-width="2" />
|
<circle cx={ringC} cy={ringC} r={ringR} fill="none" stroke="var(--border-base)" stroke-width="2" />
|
||||||
<circle cx={ringC} cy={ringC} r={ringR} fill="none" stroke="var(--accent)" stroke-width="2"
|
<circle cx={ringC} cy={ringC} r={ringR} fill="none" stroke="var(--accent)" stroke-width="2"
|
||||||
@@ -548,7 +555,7 @@
|
|||||||
|
|
||||||
.logo-wrap { position:relative; width:72px; height:72px; display:flex; align-items:center; justify-content:center; }
|
.logo-wrap { position:relative; width:72px; height:72px; display:flex; align-items:center; justify-content:center; }
|
||||||
|
|
||||||
.pin-card { z-index:1; width:min(280px,calc(100vw - 48px)); background:var(--bg-surface); border:1px solid var(--border-base); border-radius:var(--radius-xl); padding:var(--sp-6) var(--sp-5); display:flex; flex-direction:column; align-items:center; gap:var(--sp-3); box-shadow:0 32px 80px rgba(0,0,0,0.75); animation:cardIn 0.38s cubic-bezier(0.22,1,0.36,1) 0.06s both; }
|
.pin-card { z-index:1; width:min(280px,calc(100vw - 48px)); background:var(--bg-surface); border:1px solid var(--border-base); border-radius:var(--radius-xl); padding:var(--sp-6) var(--sp-5); display:flex; flex-direction:column; align-items:center; gap:var(--sp-3); box-shadow:0 32px 80px rgba(0,0,0,0.75); animation:cardIn 0.38s cubic-bezier(0.22,1,0.36,1) 0.06s both; }
|
||||||
.pin-card--leaving { animation:cardOut 0.28s cubic-bezier(0.4,0,1,1) both; }
|
.pin-card--leaving { animation:cardOut 0.28s cubic-bezier(0.4,0,1,1) both; }
|
||||||
|
|
||||||
.pin-label { font-family:var(--font-ui); font-size:var(--text-xs); color:var(--text-faint); letter-spacing:var(--tracking-wider); text-transform:uppercase; margin:0; }
|
.pin-label { font-family:var(--font-ui); font-size:var(--text-xs); color:var(--text-faint); letter-spacing:var(--tracking-wider); text-transform:uppercase; margin:0; }
|
||||||
|
|||||||
@@ -16,15 +16,22 @@
|
|||||||
let closeDialogOpen = $state(false)
|
let closeDialogOpen = $state(false)
|
||||||
let closeRemember = $state(false)
|
let closeRemember = $state(false)
|
||||||
|
|
||||||
onMount(async () => {
|
onMount(() => {
|
||||||
isFullscreen = await win.isFullscreen()
|
let unlistenResize: (() => void) | undefined
|
||||||
const unlistenResize = await win.onResized(async () => {
|
let unlistenClose: (() => void) | undefined
|
||||||
|
|
||||||
|
win.isFullscreen().then(v => { isFullscreen = v })
|
||||||
|
|
||||||
|
win.onResized(async () => {
|
||||||
isFullscreen = await win.isFullscreen()
|
isFullscreen = await win.isFullscreen()
|
||||||
})
|
}).then(u => { unlistenResize = u })
|
||||||
const unlistenClose = await win.listen('tauri://close-requested', handleCloseRequested)
|
|
||||||
|
win.listen('tauri://close-requested', handleCloseRequested)
|
||||||
|
.then(u => { unlistenClose = u })
|
||||||
|
|
||||||
return () => {
|
return () => {
|
||||||
unlistenResize()
|
unlistenResize?.()
|
||||||
unlistenClose()
|
unlistenClose?.()
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -56,6 +63,10 @@
|
|||||||
if (choice === 'tray') await doHide()
|
if (choice === 'tray') await doHide()
|
||||||
else await doQuit()
|
else await doQuit()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function onBackdropKey(e: KeyboardEvent) {
|
||||||
|
if (e.key === 'Escape') { closeDialogOpen = false; closeRemember = false }
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
{#if !isFullscreen}
|
{#if !isFullscreen}
|
||||||
@@ -99,8 +110,21 @@
|
|||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
{#if closeDialogOpen}
|
{#if closeDialogOpen}
|
||||||
<div class="close-backdrop" role="presentation" onclick={() => { closeDialogOpen = false; closeRemember = false; }}>
|
<div
|
||||||
<div class="close-dialog" role="dialog" aria-modal="true" onclick={(e) => e.stopPropagation()}>
|
class="close-backdrop"
|
||||||
|
role="presentation"
|
||||||
|
onclick={() => { closeDialogOpen = false; closeRemember = false }}
|
||||||
|
onkeydown={onBackdropKey}
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="close-dialog"
|
||||||
|
role="dialog"
|
||||||
|
aria-modal="true"
|
||||||
|
aria-label="Close Moku"
|
||||||
|
tabindex="-1"
|
||||||
|
onclick={(e) => e.stopPropagation()}
|
||||||
|
onkeydown={(e) => e.stopPropagation()}
|
||||||
|
>
|
||||||
<div class="close-header">
|
<div class="close-header">
|
||||||
<p class="close-title">Close Moku?</p>
|
<p class="close-title">Close Moku?</p>
|
||||||
<p class="close-sub">Choose how the app should exit.</p>
|
<p class="close-sub">Choose how the app should exit.</p>
|
||||||
@@ -169,6 +193,7 @@
|
|||||||
0 24px 64px rgba(0,0,0,0.7),
|
0 24px 64px rgba(0,0,0,0.7),
|
||||||
0 8px 24px rgba(0,0,0,0.4);
|
0 8px 24px rgba(0,0,0,0.4);
|
||||||
animation: cdPop 0.22s cubic-bezier(0.16,1,0.3,1) both;
|
animation: cdPop 0.22s cubic-bezier(0.16,1,0.3,1) both;
|
||||||
|
outline: none;
|
||||||
}
|
}
|
||||||
@keyframes cdPop { from { opacity: 0; transform: scale(0.96) translateY(6px) } to { opacity: 1; transform: none } }
|
@keyframes cdPop { from { opacity: 0; transform: scale(0.96) translateY(6px) } to { opacity: 1; transform: none } }
|
||||||
|
|
||||||
|
|||||||
@@ -1,171 +0,0 @@
|
|||||||
const CARD_COUNT = 18
|
|
||||||
const CARD_W = 52
|
|
||||||
const CARD_H = 72
|
|
||||||
const CARD_RADIUS = 6
|
|
||||||
const DRIFT_SPEED = 0.018
|
|
||||||
|
|
||||||
interface Card {
|
|
||||||
x: number
|
|
||||||
y: number
|
|
||||||
vx: number
|
|
||||||
vy: number
|
|
||||||
rot: number
|
|
||||||
vrot: number
|
|
||||||
opacity: number
|
|
||||||
scale: number
|
|
||||||
hue: number
|
|
||||||
}
|
|
||||||
|
|
||||||
function makeCard(w: number, h: number): Card {
|
|
||||||
const side = Math.floor(Math.random() * 4)
|
|
||||||
const margin = 80
|
|
||||||
let x = 0, y = 0
|
|
||||||
if (side === 0) { x = Math.random() * w; y = -margin }
|
|
||||||
if (side === 1) { x = w + margin; y = Math.random() * h }
|
|
||||||
if (side === 2) { x = Math.random() * w; y = h + margin }
|
|
||||||
if (side === 3) { x = -margin; y = Math.random() * h }
|
|
||||||
const cx = w / 2, cy = h / 2
|
|
||||||
const dx = cx - x, dy = cy - y
|
|
||||||
const len = Math.sqrt(dx * dx + dy * dy) || 1
|
|
||||||
const spd = 0.12 + Math.random() * 0.1
|
|
||||||
return {
|
|
||||||
x,
|
|
||||||
y,
|
|
||||||
vx: (dx / len) * spd * (0.3 + Math.random() * 0.4),
|
|
||||||
vy: (dy / len) * spd * (0.3 + Math.random() * 0.4),
|
|
||||||
rot: Math.random() * Math.PI * 2,
|
|
||||||
vrot: (Math.random() - 0.5) * 0.006,
|
|
||||||
opacity: 0.025 + Math.random() * 0.055,
|
|
||||||
scale: 0.7 + Math.random() * 0.7,
|
|
||||||
hue: 120 + Math.random() * 40,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function drawCard(ctx: CanvasRenderingContext2D, c: Card) {
|
|
||||||
ctx.save()
|
|
||||||
ctx.globalAlpha = c.opacity
|
|
||||||
ctx.translate(c.x, c.y)
|
|
||||||
ctx.rotate(c.rot)
|
|
||||||
ctx.scale(c.scale, c.scale)
|
|
||||||
|
|
||||||
const w = CARD_W, h = CARD_H, r = CARD_RADIUS
|
|
||||||
const x = -w / 2, y = -h / 2
|
|
||||||
|
|
||||||
ctx.beginPath()
|
|
||||||
ctx.moveTo(x + r, y)
|
|
||||||
ctx.lineTo(x + w - r, y)
|
|
||||||
ctx.quadraticCurveTo(x + w, y, x + w, y + r)
|
|
||||||
ctx.lineTo(x + w, y + h - r)
|
|
||||||
ctx.quadraticCurveTo(x + w, y + h, x + w - r, y + h)
|
|
||||||
ctx.lineTo(x + r, y + h)
|
|
||||||
ctx.quadraticCurveTo(x, y + h, x, y + h - r)
|
|
||||||
ctx.lineTo(x, y + r)
|
|
||||||
ctx.quadraticCurveTo(x, y, x + r, y)
|
|
||||||
ctx.closePath()
|
|
||||||
|
|
||||||
ctx.strokeStyle = `hsla(${c.hue}, 28%, 62%, 0.9)`
|
|
||||||
ctx.lineWidth = 1 / c.scale
|
|
||||||
ctx.stroke()
|
|
||||||
|
|
||||||
const grad = ctx.createLinearGradient(x, y, x, y + h)
|
|
||||||
grad.addColorStop(0, `hsla(${c.hue}, 20%, 40%, 0.18)`)
|
|
||||||
grad.addColorStop(1, `hsla(${c.hue}, 20%, 20%, 0.06)`)
|
|
||||||
ctx.fillStyle = grad
|
|
||||||
ctx.fill()
|
|
||||||
|
|
||||||
ctx.restore()
|
|
||||||
}
|
|
||||||
|
|
||||||
export function mountCardCanvas(canvas: HTMLCanvasElement) {
|
|
||||||
const ctx = canvas.getContext('2d')!
|
|
||||||
let cards = Array.from({ length: CARD_COUNT }, () => makeCard(canvas.offsetWidth || 800, canvas.offsetHeight || 600))
|
|
||||||
let raf = 0
|
|
||||||
let running = true
|
|
||||||
|
|
||||||
function resize() {
|
|
||||||
const dpr = window.devicePixelRatio || 1
|
|
||||||
canvas.width = canvas.offsetWidth * dpr
|
|
||||||
canvas.height = canvas.offsetHeight * dpr
|
|
||||||
ctx.scale(dpr, dpr)
|
|
||||||
cards = Array.from({ length: CARD_COUNT }, () => makeCard(canvas.offsetWidth, canvas.offsetHeight))
|
|
||||||
}
|
|
||||||
|
|
||||||
function tick() {
|
|
||||||
if (!running) return
|
|
||||||
const w = canvas.offsetWidth, h = canvas.offsetHeight
|
|
||||||
ctx.clearRect(0, 0, w, h)
|
|
||||||
for (const c of cards) {
|
|
||||||
c.x += c.vx
|
|
||||||
c.y += c.vy
|
|
||||||
c.rot += c.vrot
|
|
||||||
const pad = 120
|
|
||||||
if (c.x < -pad || c.x > w + pad || c.y < -pad || c.y > h + pad) {
|
|
||||||
Object.assign(c, makeCard(w, h))
|
|
||||||
}
|
|
||||||
drawCard(ctx, c)
|
|
||||||
}
|
|
||||||
raf = requestAnimationFrame(tick)
|
|
||||||
}
|
|
||||||
|
|
||||||
const ro = new ResizeObserver(resize)
|
|
||||||
ro.observe(canvas)
|
|
||||||
resize()
|
|
||||||
tick()
|
|
||||||
|
|
||||||
return {
|
|
||||||
destroy() {
|
|
||||||
running = false
|
|
||||||
cancelAnimationFrame(raf)
|
|
||||||
ro.disconnect()
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export function ringGeometry(r: number, pad: number) {
|
|
||||||
const size = (r + pad) * 2
|
|
||||||
const c = size / 2
|
|
||||||
const circ = 2 * Math.PI * r
|
|
||||||
return { size, c, circ }
|
|
||||||
}
|
|
||||||
|
|
||||||
const RING_STEPS = [
|
|
||||||
{ target: 0.15, duration: 400 },
|
|
||||||
{ target: 0.45, duration: 800 },
|
|
||||||
{ target: 0.72, duration: 600 },
|
|
||||||
{ target: 0.88, duration: 1000 },
|
|
||||||
{ target: 0.96, duration: 700 },
|
|
||||||
]
|
|
||||||
|
|
||||||
export function animateRingProgress(onProgress: (p: number) => void): () => void {
|
|
||||||
let current = 0.025
|
|
||||||
let stepIdx = 0
|
|
||||||
let start = performance.now()
|
|
||||||
let raf = 0
|
|
||||||
let stopped = false
|
|
||||||
|
|
||||||
function ease(t: number) {
|
|
||||||
return t < 0.5 ? 2 * t * t : -1 + (4 - 2 * t) * t
|
|
||||||
}
|
|
||||||
|
|
||||||
function tick(now: number) {
|
|
||||||
if (stopped) return
|
|
||||||
if (stepIdx >= RING_STEPS.length) return
|
|
||||||
|
|
||||||
const step = RING_STEPS[stepIdx]
|
|
||||||
const elapsed = now - start
|
|
||||||
const t = Math.min(elapsed / step.duration, 1)
|
|
||||||
const from = stepIdx === 0 ? 0.025 : RING_STEPS[stepIdx - 1].target
|
|
||||||
current = from + (step.target - from) * ease(t)
|
|
||||||
onProgress(current)
|
|
||||||
|
|
||||||
if (t >= 1) {
|
|
||||||
stepIdx++
|
|
||||||
start = now
|
|
||||||
}
|
|
||||||
|
|
||||||
raf = requestAnimationFrame(tick)
|
|
||||||
}
|
|
||||||
|
|
||||||
raf = requestAnimationFrame(tick)
|
|
||||||
return () => { stopped = true; cancelAnimationFrame(raf) }
|
|
||||||
}
|
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { CircleNotch, ArrowClockwise, X } from "phosphor-svelte";
|
import { CircleNotchIcon, ArrowClockwiseIcon, XIcon } from "phosphor-svelte";
|
||||||
import Thumbnail from "$lib/components/shared/manga/Thumbnail.svelte";
|
import Thumbnail from "$lib/components/shared/manga/Thumbnail.svelte";
|
||||||
import { longPress } from "$lib/core/ui/touchscreen";
|
import { longPress } from "$lib/core/ui/touchscreen";
|
||||||
import type { DownloadQueueItem } from "$lib/types/api";
|
import type { DownloadQueueItem } from "$lib/types/api";
|
||||||
@@ -78,12 +78,12 @@
|
|||||||
<div class="actions">
|
<div class="actions">
|
||||||
{#if isError}
|
{#if isError}
|
||||||
<button class="action-btn retry" onclick={(e) => { e.stopPropagation(); onRetry(item.chapter.id); }} disabled={isRemoving} title="Retry">
|
<button class="action-btn retry" onclick={(e) => { e.stopPropagation(); onRetry(item.chapter.id); }} disabled={isRemoving} title="Retry">
|
||||||
{#if isRemoving}<CircleNotch size={11} weight="light" class="anim-spin" />{:else}<ArrowClockwise size={11} weight="bold" />{/if}
|
{#if isRemoving}<CircleNotchIcon size={11} weight="light" class="anim-spin" />{:else}<ArrowClockwiseIcon size={11} weight="bold" />{/if}
|
||||||
</button>
|
</button>
|
||||||
{/if}
|
{/if}
|
||||||
{#if !isActive}
|
{#if !isActive}
|
||||||
<button class="action-btn remove" onclick={(e) => { e.stopPropagation(); onRemove(item.chapter.id); }} disabled={isRemoving} title="Remove">
|
<button class="action-btn remove" onclick={(e) => { e.stopPropagation(); onRemove(item.chapter.id); }} disabled={isRemoving} title="Remove">
|
||||||
{#if isRemoving}<CircleNotch size={11} weight="light" class="anim-spin" />{:else}<X size={12} weight="light" />{/if}
|
{#if isRemoving}<CircleNotchIcon size={11} weight="light" class="anim-spin" />{:else}<XIcon size={12} weight="light" />{/if}
|
||||||
</button>
|
</button>
|
||||||
{/if}
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { CircleNotch } from "phosphor-svelte";
|
import { CircleNotchIcon } from "phosphor-svelte";
|
||||||
import DownloadItem from "$lib/components/downloads/DownloadItem.svelte";
|
import DownloadItem from "$lib/components/downloads/DownloadItem.svelte";
|
||||||
import type { DownloadQueueItem } from "$lib/types/api";
|
import type { DownloadQueueItem } from "$lib/types/api";
|
||||||
|
|
||||||
@@ -9,16 +9,14 @@
|
|||||||
isRunning: boolean;
|
isRunning: boolean;
|
||||||
dequeueing: Set<number>;
|
dequeueing: Set<number>;
|
||||||
selected: Set<number>;
|
selected: Set<number>;
|
||||||
onRemove: (chapterId: number) => void;
|
onRemove: (chapterId: number) => void;
|
||||||
onRetry: (chapterId: number) => void;
|
onRetry: (chapterId: number) => void;
|
||||||
onReorder: (chapterId: number, dir: "up" | "down") => void;
|
onSelect: (chapterId: number, e: MouseEvent) => void;
|
||||||
onReorderEdge: (chapterId: number, edge: "top" | "bottom") => void;
|
|
||||||
onSelect: (chapterId: number, e: MouseEvent) => void;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const {
|
const {
|
||||||
queue, loading, isRunning, dequeueing, selected,
|
queue, loading, isRunning, dequeueing, selected,
|
||||||
onRemove, onRetry, onReorder, onReorderEdge, onSelect,
|
onRemove, onRetry, onSelect,
|
||||||
}: Props = $props();
|
}: Props = $props();
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
@@ -54,8 +52,6 @@
|
|||||||
isSelected={selected.has(item.chapter.id)}
|
isSelected={selected.has(item.chapter.id)}
|
||||||
{onRemove}
|
{onRemove}
|
||||||
{onRetry}
|
{onRetry}
|
||||||
{onReorder}
|
|
||||||
{onReorderEdge}
|
|
||||||
{onSelect}
|
{onSelect}
|
||||||
/>
|
/>
|
||||||
{/each}
|
{/each}
|
||||||
|
|||||||
@@ -165,10 +165,8 @@
|
|||||||
isRunning={downloadStore.isRunning}
|
isRunning={downloadStore.isRunning}
|
||||||
dequeueing={downloadStore.dequeueing}
|
dequeueing={downloadStore.dequeueing}
|
||||||
selected={downloadStore.selected}
|
selected={downloadStore.selected}
|
||||||
onRemove={(id) => downloadStore.dequeue(id)}
|
onRemove={(id: number) => downloadStore.dequeue(id)}
|
||||||
onRetry={(id) => downloadStore.retryOne(id)}
|
onRetry={(id: number) => downloadStore.retryOne(id)}
|
||||||
onReorder={(id, dir) => downloadStore.reorder(id, dir)}
|
|
||||||
onReorderEdge={(id, edge) => downloadStore.reorderToEdge(id, edge)}
|
|
||||||
onSelect={handleSelect}
|
onSelect={handleSelect}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
@@ -219,7 +217,7 @@
|
|||||||
.move-step { display: flex; align-items: center; border: 1px solid var(--border-dim); border-radius: var(--radius-sm); overflow: hidden; }
|
.move-step { display: flex; align-items: center; border: 1px solid var(--border-dim); border-radius: var(--radius-sm); overflow: hidden; }
|
||||||
.move-step .sel-action-btn { border: none; border-radius: 0; background: none; padding: 3px 6px; }
|
.move-step .sel-action-btn { border: none; border-radius: 0; background: none; padding: 3px 6px; }
|
||||||
.move-step .sel-action-btn:hover:not(:disabled) { background: var(--bg-overlay); border-color: transparent; }
|
.move-step .sel-action-btn:hover:not(:disabled) { background: var(--bg-overlay); border-color: transparent; }
|
||||||
.move-input { width: 28px; background: none; border: none; border-left: 1px solid var(--border-dim); border-right: 1px solid var(--border-dim); color: var(--text-muted); font-family: var(--font-ui); font-size: var(--text-xs); text-align: center; padding: 2px 0; outline: none; -moz-appearance: textfield; }
|
.move-input { width: 28px; background: none; border: none; border-left: 1px solid var(--border-dim); border-right: 1px solid var(--border-dim); color: var(--text-muted); font-family: var(--font-ui); font-size: var(--text-xs); text-align: center; padding: 2px 0; outline: none; -moz-appearance: textfield; appearance: textfield; }
|
||||||
.move-input::-webkit-outer-spin-button, .move-input::-webkit-inner-spin-button { -webkit-appearance: none; }
|
.move-input::-webkit-outer-spin-button, .move-input::-webkit-inner-spin-button { -webkit-appearance: none; }
|
||||||
.move-input:focus { color: var(--text-primary); }
|
.move-input:focus { color: var(--text-primary); }
|
||||||
|
|
||||||
|
|||||||
@@ -25,7 +25,7 @@
|
|||||||
|
|
||||||
let { pkgName, extensionName, iconUrl, cols, cropCovers, statsAlways, anims, sources, onBack, onSettings }: Props = $props();
|
let { pkgName, extensionName, iconUrl, cols, cropCovers, statsAlways, anims, sources, onBack, onSettings }: Props = $props();
|
||||||
|
|
||||||
const isLocal = pkgName === '__local__';
|
const isLocal = $derived(pkgName === '__local__');
|
||||||
|
|
||||||
let groups: SourceLibrary[] = $state([]);
|
let groups: SourceLibrary[] = $state([]);
|
||||||
let sourceNodes: SourceNode[] = $state([]);
|
let sourceNodes: SourceNode[] = $state([]);
|
||||||
@@ -409,7 +409,7 @@
|
|||||||
.badge-done { background: rgba(255,255,255,0.18); color: rgba(255,255,255,0.9); border: 1px solid rgba(255,255,255,0.25); }
|
.badge-done { background: rgba(255,255,255,0.18); color: rgba(255,255,255,0.9); border: 1px solid rgba(255,255,255,0.25); }
|
||||||
.badge-dl { background: rgba(0,0,0,0.55); color: rgba(255,255,255,0.8); border: 1px solid rgba(255,255,255,0.18); margin-left: auto; }
|
.badge-dl { background: rgba(0,0,0,0.55); color: rgba(255,255,255,0.8); border: 1px solid rgba(255,255,255,0.18); margin-left: auto; }
|
||||||
|
|
||||||
.card-title { margin-top: var(--sp-2); font-size: var(--text-sm); color: var(--text-secondary); line-height: var(--leading-snug); display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden; height: 2lh; }
|
.card-title { margin-top: var(--sp-2); font-size: var(--text-sm); color: var(--text-secondary); line-height: var(--leading-snug); display: -webkit-box; -webkit-line-clamp: 2; line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden; height: 2lh; }
|
||||||
.card.anims .card-title { transition: color var(--t-base); }
|
.card.anims .card-title { transition: color var(--t-base); }
|
||||||
|
|
||||||
.card-skeleton { padding: 0; }
|
.card-skeleton { padding: 0; }
|
||||||
|
|||||||
@@ -13,9 +13,9 @@
|
|||||||
import ExtensionLibrary from "$lib/components/extensions/ExtensionLibrary.svelte";
|
import ExtensionLibrary from "$lib/components/extensions/ExtensionLibrary.svelte";
|
||||||
|
|
||||||
const anims = $derived(settingsState.settings.qolAnimations ?? true);
|
const anims = $derived(settingsState.settings.qolAnimations ?? true);
|
||||||
const cols = $derived(settingsState.settings.libraryCols ?? 5);
|
const cols = $derived(settingsState.settings.libraryPageSize ?? 5);
|
||||||
const cropCovers = $derived(settingsState.settings.cropCovers ?? true);
|
const cropCovers = $derived(settingsState.settings.libraryCropCovers ?? true);
|
||||||
const statsAlways = $derived(settingsState.settings.statsAlways ?? false);
|
const statsAlways = $derived(settingsState.settings.libraryStatsAlways ?? false);
|
||||||
|
|
||||||
let tabsEl = $state<HTMLDivElement | undefined>(undefined);
|
let tabsEl = $state<HTMLDivElement | undefined>(undefined);
|
||||||
let tabIndicator = $state({ left: 0, width: 0 });
|
let tabIndicator = $state({ left: 0, width: 0 });
|
||||||
@@ -337,14 +337,14 @@
|
|||||||
{:else}
|
{:else}
|
||||||
<div class="list">
|
<div class="list">
|
||||||
{#if showLocal}
|
{#if showLocal}
|
||||||
<div class="local-row" style="cursor:pointer" onclick={() => libraryTarget = { pkgName: '__local__', extensionName: 'Local Source', iconUrl: '' }}>
|
<button type="button" class="local-row" onclick={() => libraryTarget = { pkgName: '__local__', extensionName: 'Local Source', iconUrl: '' }}>
|
||||||
<div class="local-icon"><HardDrives size={18} weight="bold" /></div>
|
<div class="local-icon"><HardDrives size={18} weight="bold" /></div>
|
||||||
<div class="info">
|
<div class="info">
|
||||||
<span class="name">Local Source</span>
|
<span class="name">Local Source</span>
|
||||||
<span class="meta">Built-in · {localMangaCount} {localMangaCount === 1 ? "manga" : "manga"}</span>
|
<span class="meta">Built-in · {localMangaCount} {localMangaCount === "1" ? "manga" : "mangas"}</span>
|
||||||
</div>
|
</div>
|
||||||
<span class="local-badge">Built-in</span>
|
<span class="local-badge">Built-in</span>
|
||||||
</div>
|
</button>
|
||||||
{/if}
|
{/if}
|
||||||
{#each groups as { base, primary, variants }}
|
{#each groups as { base, primary, variants }}
|
||||||
<ExtensionCard
|
<ExtensionCard
|
||||||
@@ -404,8 +404,7 @@
|
|||||||
.repo-remove { display: flex; align-items: center; justify-content: center; width: 20px; height: 20px; border-radius: var(--radius-sm); color: var(--text-faint); flex-shrink: 0; transition: color var(--t-base), background var(--t-base); }
|
.repo-remove { display: flex; align-items: center; justify-content: center; width: 20px; height: 20px; border-radius: var(--radius-sm); color: var(--text-faint); flex-shrink: 0; transition: color var(--t-base), background var(--t-base); }
|
||||||
.repo-remove:hover:not(:disabled) { color: var(--color-error); background: var(--bg-overlay); }
|
.repo-remove:hover:not(:disabled) { color: var(--color-error); background: var(--bg-overlay); }
|
||||||
@keyframes panelSlide { from { opacity: 0; transform: translateY(-6px); } to { opacity: 1; transform: translateY(0); } }
|
@keyframes panelSlide { from { opacity: 0; transform: translateY(-6px); } to { opacity: 1; transform: translateY(0); } }
|
||||||
.local-row { display: flex; align-items: center; gap: var(--sp-3); padding: 8px var(--sp-3); border-radius: var(--radius-md); border: 1px solid transparent; transition: background var(--t-fast), border-color var(--t-fast); margin-bottom: 1px; }
|
.local-row { display: flex; align-items: center; gap: var(--sp-3); padding: 8px var(--sp-3); border-radius: var(--radius-md); border: 1px solid transparent; transition: background var(--t-fast), border-color var(--t-fast); margin-bottom: 1px; width: 100%; text-align: left; background: none; font: inherit; cursor: pointer; }
|
||||||
.local-row:hover { background: var(--bg-raised); border-color: var(--border-dim); }
|
|
||||||
.local-icon { width: 32px; height: 32px; border-radius: var(--radius-md); background: var(--accent-muted); border: 1px solid var(--accent-dim); display: flex; align-items: center; justify-content: center; color: var(--accent-fg); flex-shrink: 0; }
|
.local-icon { width: 32px; height: 32px; border-radius: var(--radius-md); background: var(--accent-muted); border: 1px solid var(--accent-dim); display: flex; align-items: center; justify-content: center; color: var(--accent-fg); flex-shrink: 0; }
|
||||||
.info { flex: 1; display: flex; flex-direction: column; gap: 2px; overflow: hidden; }
|
.info { flex: 1; display: flex; flex-direction: column; gap: 2px; overflow: hidden; }
|
||||||
.name { font-size: var(--text-base); font-weight: var(--weight-medium); color: var(--text-secondary); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
|
.name { font-size: var(--text-base); font-weight: var(--weight-medium); color: var(--text-secondary); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ export interface LibraryManga {
|
|||||||
thumbnailUrl: string;
|
thumbnailUrl: string;
|
||||||
unreadCount: number;
|
unreadCount: number;
|
||||||
downloadCount: number;
|
downloadCount: number;
|
||||||
source: { id: string; displayName: string };
|
source: { id: string; displayName: string } | null;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface SourceLibrary {
|
export interface SourceLibrary {
|
||||||
@@ -17,7 +17,7 @@ export type SourceNode = {
|
|||||||
id: string;
|
id: string;
|
||||||
displayName: string;
|
displayName: string;
|
||||||
isConfigurable: boolean;
|
isConfigurable: boolean;
|
||||||
extension: { pkgName: string };
|
extension?: { pkgName: string };
|
||||||
};
|
};
|
||||||
|
|
||||||
export function libraryByExtension(
|
export function libraryByExtension(
|
||||||
@@ -31,7 +31,7 @@ export function libraryByExtension(
|
|||||||
const bySource = new Map<string, LibraryManga[]>();
|
const bySource = new Map<string, LibraryManga[]>();
|
||||||
for (const src of pkgSources) bySource.set(src.id, []);
|
for (const src of pkgSources) bySource.set(src.id, []);
|
||||||
for (const m of libraryManga) {
|
for (const m of libraryManga) {
|
||||||
if (sourceIds.has(m.source.id)) bySource.get(m.source.id)!.push(m);
|
if (m.source && sourceIds.has(m.source.id)) bySource.get(m.source.id)!.push(m);
|
||||||
}
|
}
|
||||||
|
|
||||||
return pkgSources
|
return pkgSources
|
||||||
@@ -49,6 +49,7 @@ export function libraryCountByPkg(
|
|||||||
}
|
}
|
||||||
const counts: Record<string, number> = {};
|
const counts: Record<string, number> = {};
|
||||||
for (const m of libraryManga) {
|
for (const m of libraryManga) {
|
||||||
|
if (!m.source) continue;
|
||||||
const pkg = sourceIdToPkg.get(m.source.id);
|
const pkg = sourceIdToPkg.get(m.source.id);
|
||||||
if (pkg) counts[pkg] = (counts[pkg] ?? 0) + 1;
|
if (pkg) counts[pkg] = (counts[pkg] ?? 0) + 1;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -112,12 +112,10 @@
|
|||||||
for (let i = 0; i < entries.length; i++) {
|
for (let i = 0; i < entries.length; i++) {
|
||||||
entries[i] = { ...entries[i], status: "searching" };
|
entries[i] = { ...entries[i], status: "searching" };
|
||||||
try {
|
try {
|
||||||
const d = await gql<{ fetchSourceManga: { mangas: Manga[] } }>(FETCH_SOURCE_MANGA, {
|
const mangas = await getAdapter().searchManga(entries[i].manga.title, target.id);
|
||||||
source: target.id, type: "SEARCH", page: 1, query: entries[i].manga.title,
|
const results = mangas
|
||||||
});
|
.map((m: Manga) => ({ manga: m, similarity: titleSimilarity(entries[i].manga.title, m.title) }))
|
||||||
const results = d.fetchSourceManga.mangas
|
.sort((a: { manga: Manga; similarity: number }, b: { manga: Manga; similarity: number }) => b.similarity - a.similarity);
|
||||||
.map(m => ({ manga: m, similarity: titleSimilarity(entries[i].manga.title, m.title) }))
|
|
||||||
.sort((a, b) => b.similarity - a.similarity);
|
|
||||||
|
|
||||||
if (results.length > 0 && results[0].similarity > 0.3) {
|
if (results.length > 0 && results[0].similarity > 0.3) {
|
||||||
entries[i] = { ...entries[i], match: results[0].manga, similarity: results[0].similarity, status: "found" };
|
entries[i] = { ...entries[i], match: results[0].manga, similarity: results[0].similarity, status: "found" };
|
||||||
@@ -147,17 +145,15 @@
|
|||||||
for (const entry of toMigrate) {
|
for (const entry of toMigrate) {
|
||||||
const idx = entries.indexOf(entry);
|
const idx = entries.indexOf(entry);
|
||||||
try {
|
try {
|
||||||
const d = await gql<{ fetchChapters: { chapters: Chapter[] } }>(FETCH_CHAPTERS, { mangaId: entry.match!.id });
|
const newChaps = await getAdapter().fetchChapters(String(entry.match!.id));
|
||||||
const newChaps = d.fetchChapters.chapters;
|
|
||||||
|
|
||||||
const toMarkRead: number[] = [];
|
const toMarkRead: number[] = [];
|
||||||
const toMarkBookmarked: number[] = [];
|
|
||||||
|
|
||||||
for (const nc of newChaps) {
|
// LibraryManga has no chapter detail — use unreadCount as a proxy:
|
||||||
const oldIdx = entries[idx].manga;
|
// if unreadCount < total fetched, the user had read some, so carry them all over.
|
||||||
if (oldIdx) {
|
const hadReads = entries[idx].manga.unreadCount < newChaps.length;
|
||||||
toMarkRead.push(nc.id);
|
if (hadReads) {
|
||||||
}
|
for (const nc of newChaps) toMarkRead.push(nc.id);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (toMarkRead.length)
|
if (toMarkRead.length)
|
||||||
@@ -183,7 +179,7 @@
|
|||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="overlay" onclick={(e) => { if (e.target === e.currentTarget && phase !== "migrating") onClose(); }}>
|
<div class="overlay" role="presentation" onclick={(e) => { if (e.target === e.currentTarget && phase !== "migrating") onClose(); }} onkeydown={(e) => { if (e.key === "Escape" && phase !== "migrating") onClose(); }}>
|
||||||
<div class="modal">
|
<div class="modal">
|
||||||
|
|
||||||
<div class="modal-header">
|
<div class="modal-header">
|
||||||
|
|||||||
@@ -19,7 +19,7 @@
|
|||||||
const entries = $derived(
|
const entries = $derived(
|
||||||
historyState.sessions
|
historyState.sessions
|
||||||
.filter((s, i, arr) => arr.findIndex(x => x.mangaId === s.mangaId) === i)
|
.filter((s, i, arr) => arr.findIndex(x => x.mangaId === s.mangaId) === i)
|
||||||
.slice(0, 10)
|
.slice(0, 6)
|
||||||
)
|
)
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|||||||
@@ -5,6 +5,7 @@
|
|||||||
import { getAdapter } from '$lib/request-manager'
|
import { getAdapter } from '$lib/request-manager'
|
||||||
import { libraryState } from '$lib/state/library.svelte'
|
import { libraryState } from '$lib/state/library.svelte'
|
||||||
import { homeState, setHeroSlot } from '$lib/state/home.svelte'
|
import { homeState, setHeroSlot } from '$lib/state/home.svelte'
|
||||||
|
import { openReaderForChapter } from '$lib/state/series.svelte'
|
||||||
import { historyState } from '$lib/state/history.svelte'
|
import { historyState } from '$lib/state/history.svelte'
|
||||||
import type { ReadSession } from '$lib/types/history'
|
import type { ReadSession } from '$lib/types/history'
|
||||||
import HeroStage from '$lib/components/home/HeroStage.svelte'
|
import HeroStage from '$lib/components/home/HeroStage.svelte'
|
||||||
@@ -107,7 +108,7 @@
|
|||||||
heroAllChapters = all
|
heroAllChapters = all
|
||||||
const lastReadIdx = heroEntry
|
const lastReadIdx = heroEntry
|
||||||
? all.findLastIndex(c => c.id === heroEntry!.endChapterId)
|
? all.findLastIndex(c => c.id === heroEntry!.endChapterId)
|
||||||
: all.findLastIndex(c => c.isRead)
|
: all.findLastIndex(c => c.read)
|
||||||
const startIdx = Math.max(0, lastReadIdx)
|
const startIdx = Math.max(0, lastReadIdx)
|
||||||
heroChapters = all.slice(startIdx, startIdx + 5)
|
heroChapters = all.slice(startIdx, startIdx + 5)
|
||||||
} catch {
|
} catch {
|
||||||
@@ -129,7 +130,7 @@
|
|||||||
if (!heroEntry && heroManga) { goto(`/series/${heroManga.id}`); return }
|
if (!heroEntry && heroManga) { goto(`/series/${heroManga.id}`); return }
|
||||||
if (!heroEntry) return
|
if (!heroEntry) return
|
||||||
const target = heroAllChapters.find(c => c.id === heroEntry!.endChapterId) ?? heroAllChapters[0]
|
const target = heroAllChapters.find(c => c.id === heroEntry!.endChapterId) ?? heroAllChapters[0]
|
||||||
if (target) openChapter(target)
|
if (target) openReaderForChapter(target, heroManga ?? null)
|
||||||
}
|
}
|
||||||
|
|
||||||
function cycleNext() { activeIdx = (activeIdx + 1) % TOTAL_SLOTS; heroChapters = []; heroAllChapters = [] }
|
function cycleNext() { activeIdx = (activeIdx + 1) % TOTAL_SLOTS; heroChapters = []; heroAllChapters = [] }
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { onMount, untrack, tick } from "svelte";
|
import { onMount, untrack, tick } from "svelte";
|
||||||
import { readerState, PAGE_STYLES } from "$lib/state/reader.svelte";
|
import { readerState, PAGE_STYLES } from "$lib/state/reader.svelte";
|
||||||
|
import type { PageStyle } from "$lib/state/reader.svelte";
|
||||||
import { settingsState, updateSettings } from "$lib/state/settings.svelte";
|
import { settingsState, updateSettings } from "$lib/state/settings.svelte";
|
||||||
import { app, appState } from "$lib/state/app.svelte";
|
import { app, appState } from "$lib/state/app.svelte";
|
||||||
import { DEFAULT_KEYBINDS } from "$lib/core/keybinds/defaultBinds";
|
import { DEFAULT_KEYBINDS } from "$lib/core/keybinds/defaultBinds";
|
||||||
@@ -11,7 +12,7 @@
|
|||||||
import { clampZoom, captureZoomAnchor, restoreZoomAnchor } from "$lib/components/reader/lib/zoomHelpers";
|
import { clampZoom, captureZoomAnchor, restoreZoomAnchor } from "$lib/components/reader/lib/zoomHelpers";
|
||||||
import { loadChapter, scheduleResumeDismiss } from "$lib/components/reader/lib/chapterLoader";
|
import { loadChapter, scheduleResumeDismiss } from "$lib/components/reader/lib/chapterLoader";
|
||||||
import { historyState } from "$lib/state/history.svelte";
|
import { historyState } from "$lib/state/history.svelte";
|
||||||
import { setPreviewManga } from "$lib/state/series.svelte";
|
import { setPreviewManga, seriesState } from "$lib/state/series.svelte";
|
||||||
import { getAdapter } from "$lib/request-manager";
|
import { getAdapter } from "$lib/request-manager";
|
||||||
import { setReading, clearReading } from "$lib/core/discord";
|
import { setReading, clearReading } from "$lib/core/discord";
|
||||||
import { revokeBlobUrl, cancelQueuedFetches, preloadBlobUrls } from "$lib/core/cache/imageCache";
|
import { revokeBlobUrl, cancelQueuedFetches, preloadBlobUrls } from "$lib/core/cache/imageCache";
|
||||||
@@ -55,9 +56,16 @@
|
|||||||
|
|
||||||
const currentBookmark = $derived(
|
const currentBookmark = $derived(
|
||||||
readerState.activeManga
|
readerState.activeManga
|
||||||
? readerState.bookmarks.find(b => b.mangaId === readerState.activeManga!.id)
|
? seriesState.bookmarks.find(b => b.mangaId === readerState.activeManga!.id)
|
||||||
: undefined
|
: undefined
|
||||||
);
|
);
|
||||||
|
const currentGroup = $derived.by(() => {
|
||||||
|
const group = style === "double" && readerState.pageGroups.length
|
||||||
|
? (readerState.pageGroups.find(g => g.includes(readerState.pageNumber)) ?? [readerState.pageNumber])
|
||||||
|
: [readerState.pageNumber];
|
||||||
|
return rtl ? [...group].reverse() : group;
|
||||||
|
});
|
||||||
|
|
||||||
const isBookmarked = $derived(
|
const isBookmarked = $derived(
|
||||||
!!currentBookmark &&
|
!!currentBookmark &&
|
||||||
currentBookmark.chapterId === displayChapter?.id &&
|
currentBookmark.chapterId === displayChapter?.id &&
|
||||||
@@ -102,13 +110,6 @@
|
|||||||
effectiveReaderSettings.optimizeContrast && "optimize-contrast",
|
effectiveReaderSettings.optimizeContrast && "optimize-contrast",
|
||||||
].filter(Boolean).join(" "));
|
].filter(Boolean).join(" "));
|
||||||
|
|
||||||
const currentGroup = $derived.by(() => {
|
|
||||||
const group = style === "double" && readerState.pageGroups.length
|
|
||||||
? (readerState.pageGroups.find(g => g.includes(readerState.pageNumber)) ?? [readerState.pageNumber])
|
|
||||||
: [readerState.pageNumber];
|
|
||||||
return rtl ? [...group].reverse() : group;
|
|
||||||
});
|
|
||||||
|
|
||||||
const sliderPage = $derived.by(() => {
|
const sliderPage = $derived.by(() => {
|
||||||
if (style === "double" && readerState.pageGroups.length)
|
if (style === "double" && readerState.pageGroups.length)
|
||||||
return readerState.pageGroups.findIndex(g => g.includes(readerState.pageNumber)) + 1;
|
return readerState.pageGroups.findIndex(g => g.includes(readerState.pageNumber)) + 1;
|
||||||
@@ -240,7 +241,7 @@
|
|||||||
lastPage: () => lastPage,
|
lastPage: () => lastPage,
|
||||||
adjustZoom: (d) => { captureZoomAnchor(containerEl, style, zoomAnchor); applySettings({ readerZoom: clampZoom(zoom + d) }); restoreZoomAnchor(containerEl, zoomAnchor); },
|
adjustZoom: (d) => { captureZoomAnchor(containerEl, style, zoomAnchor); applySettings({ readerZoom: clampZoom(zoom + d) }); restoreZoomAnchor(containerEl, zoomAnchor); },
|
||||||
resetZoom: () => { captureZoomAnchor(containerEl, style, zoomAnchor); applySettings({ readerZoom: 1.0 }); restoreZoomAnchor(containerEl, zoomAnchor); },
|
resetZoom: () => { captureZoomAnchor(containerEl, style, zoomAnchor); applySettings({ readerZoom: 1.0 }); restoreZoomAnchor(containerEl, zoomAnchor); },
|
||||||
cycleStyle: () => { const idx = PAGE_STYLES.indexOf(style); applySettings({ pageStyle: PAGE_STYLES[(idx + 1) % PAGE_STYLES.length] }); },
|
cycleStyle: () => { const idx = PAGE_STYLES.indexOf(style); applySettings({ pageStyle: PAGE_STYLES[(idx + 1) % PAGE_STYLES.length] as PageStyle }); },
|
||||||
toggleDirection: () => applySettings({ readingDirection: rtl ? "ltr" : "rtl" }),
|
toggleDirection: () => applySettings({ readingDirection: rtl ? "ltr" : "rtl" }),
|
||||||
openSettings: () => { app.setSettingsOpen(true); },
|
openSettings: () => { app.setSettingsOpen(true); },
|
||||||
toggleBookmark: () => toggleBookmark(displayChapter, readerState.pageNumber),
|
toggleBookmark: () => toggleBookmark(displayChapter, readerState.pageNumber),
|
||||||
@@ -255,11 +256,11 @@
|
|||||||
},
|
},
|
||||||
chapterNext: () => {
|
chapterNext: () => {
|
||||||
const ch = rtl ? adjacent.prev : adjacent.next;
|
const ch = rtl ? adjacent.prev : adjacent.next;
|
||||||
if (ch) { maybeMarkCurrentRead(); readerState.openReader(ch, readerState.activeChapterList); }
|
if (ch) { maybeMarkCurrentRead(); readerState.openReader(ch, readerState.activeManga); }
|
||||||
},
|
},
|
||||||
chapterPrev: () => {
|
chapterPrev: () => {
|
||||||
const ch = rtl ? adjacent.next : adjacent.prev;
|
const ch = rtl ? adjacent.next : adjacent.prev;
|
||||||
if (ch) readerState.openReader(ch, readerState.activeChapterList);
|
if (ch) readerState.openReader(ch, readerState.activeManga);
|
||||||
},
|
},
|
||||||
closePopovers: () => readerState.closeAllPopovers(),
|
closePopovers: () => readerState.closeAllPopovers(),
|
||||||
getKeybinds: () => settingsState.settings.keybinds ?? DEFAULT_KEYBINDS,
|
getKeybinds: () => settingsState.settings.keybinds ?? DEFAULT_KEYBINDS,
|
||||||
@@ -269,7 +270,7 @@
|
|||||||
|
|
||||||
function captureCurrentReaderSettings(): ReaderSettings {
|
function captureCurrentReaderSettings(): ReaderSettings {
|
||||||
return {
|
return {
|
||||||
pageStyle: style,
|
pageStyle: style as PageStyle,
|
||||||
fitMode: fit,
|
fitMode: fit,
|
||||||
readingDirection: (settingsState.settings.readingDirection ?? "ltr") as ReaderSettings["readingDirection"],
|
readingDirection: (settingsState.settings.readingDirection ?? "ltr") as ReaderSettings["readingDirection"],
|
||||||
readerZoom: zoom,
|
readerZoom: zoom,
|
||||||
@@ -463,9 +464,7 @@
|
|||||||
if (!hasNavigated) return;
|
if (!hasNavigated) return;
|
||||||
if (style === "longstrip" && visibleChapterId && chapterId !== visibleChapterId) return;
|
if (style === "longstrip" && visibleChapterId && chapterId !== visibleChapterId) return;
|
||||||
if (settingsState.settings.autoBookmark ?? true) {
|
if (settingsState.settings.autoBookmark ?? true) {
|
||||||
const existing = readerState.bookmarks.find(b => b.mangaId === mangaId && b.chapterId !== chapterId);
|
seriesState.setBookmark({ mangaId, mangaTitle, thumbnailUrl: thumb, chapterId, chapterName, pageNumber: pageNum });
|
||||||
if (existing) readerState.removeBookmark(existing.chapterId);
|
|
||||||
readerState.addBookmark({ mangaId, mangaTitle, thumbnailUrl: thumb, chapterId, chapterName, pageNumber: pageNum });
|
|
||||||
}
|
}
|
||||||
if (style !== "longstrip" && (settingsState.settings.autoMarkRead ?? true) && atLast) markChapterRead(chapterId, markedRead);
|
if (style !== "longstrip" && (settingsState.settings.autoMarkRead ?? true) && atLast) markChapterRead(chapterId, markedRead);
|
||||||
|
|
||||||
@@ -585,6 +584,7 @@
|
|||||||
resumePage={readerState.resumePage}
|
resumePage={readerState.resumePage}
|
||||||
resumeFading={readerState.resumeFading}
|
resumeFading={readerState.resumeFading}
|
||||||
{adjacent}
|
{adjacent}
|
||||||
|
{barPosition}
|
||||||
onDismissResume={() => { readerState.resumeVisible = false; readerState.resumeFading = false; }}
|
onDismissResume={() => { readerState.resumeVisible = false; readerState.resumeFading = false; }}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
@@ -673,4 +673,4 @@
|
|||||||
.root.bar-right :global(.viewer) { margin-right: 40px; }
|
.root.bar-right :global(.viewer) { margin-right: 40px; }
|
||||||
|
|
||||||
.root.pinch-active :global(.viewer) { touch-action: none; }
|
.root.pinch-active :global(.viewer) { touch-action: none; }
|
||||||
</style>
|
</style>
|
||||||
@@ -19,7 +19,7 @@ export function markChapterRead(id: number, markedRead: Set<number>) {
|
|||||||
const manga = readerState.activeManga;
|
const manga = readerState.activeManga;
|
||||||
|
|
||||||
if (manga && chapter) {
|
if (manga && chapter) {
|
||||||
readerState.addBookmark({
|
seriesState.setBookmark({
|
||||||
mangaId: manga.id,
|
mangaId: manga.id,
|
||||||
mangaTitle: manga.title,
|
mangaTitle: manga.title,
|
||||||
thumbnailUrl: manga.thumbnailUrl,
|
thumbnailUrl: manga.thumbnailUrl,
|
||||||
@@ -86,9 +86,7 @@ export function toggleBookmark(chapter: typeof readerState.activeChapter, pageNu
|
|||||||
if (existing) {
|
if (existing) {
|
||||||
seriesState.removeBookmark(chapter.id);
|
seriesState.removeBookmark(chapter.id);
|
||||||
} else {
|
} else {
|
||||||
const other = seriesState.bookmarks.find(b => b.mangaId === manga.id && b.chapterId !== chapter.id);
|
seriesState.setBookmark({
|
||||||
if (other) seriesState.removeBookmark(other.chapterId);
|
|
||||||
seriesState.addBookmark({
|
|
||||||
mangaId: manga.id,
|
mangaId: manga.id,
|
||||||
mangaTitle: manga.title,
|
mangaTitle: manga.title,
|
||||||
thumbnailUrl: manga.thumbnailUrl,
|
thumbnailUrl: manga.thumbnailUrl,
|
||||||
|
|||||||
@@ -316,8 +316,8 @@
|
|||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
{#each flatPages as page, gi (page.chapterId + ":" + page.localIndex)}
|
{#each flatPages as page, gi (page.chapterId + ":" + page.localIndex)}
|
||||||
{@const src = (_version, resolvedSrc.get(gi))}
|
{@const src = _version >= 0 ? resolvedSrc.get(gi) : undefined}
|
||||||
{@const isLoaded = (_version, loadedSet.has(gi))}
|
{@const isLoaded = _version >= 0 ? loadedSet.has(gi) : false}
|
||||||
<div class="strip-slot" data-local-page={page.localIndex + 1} data-chapter={page.chapterId} style={getCachedAspect(page.url) != null ? `--aspect:${getCachedAspect(page.url)}` : undefined}>
|
<div class="strip-slot" data-local-page={page.localIndex + 1} data-chapter={page.chapterId} style={getCachedAspect(page.url) != null ? `--aspect:${getCachedAspect(page.url)}` : undefined}>
|
||||||
{#if isLoaded && src}
|
{#if isLoaded && src}
|
||||||
<img
|
<img
|
||||||
|
|||||||
@@ -6,12 +6,13 @@
|
|||||||
import { historyState } from '$lib/state/history.svelte'
|
import { historyState } from '$lib/state/history.svelte'
|
||||||
import { setActiveManga, openReaderForChapter, setPreviewManga } from '$lib/state/series.svelte'
|
import { setActiveManga, openReaderForChapter, setPreviewManga } from '$lib/state/series.svelte'
|
||||||
import { addToast } from '$lib/state/notifications.svelte'
|
import { addToast } from '$lib/state/notifications.svelte'
|
||||||
|
import { downloadStore } from '$lib/state/downloads.svelte'
|
||||||
import { groupByDay } from './lib/recentHistory'
|
import { groupByDay } from './lib/recentHistory'
|
||||||
import { fetchedAtMs, parseServerTimestamp, groupUpdatesByDay } from './lib/recentUpdates'
|
import { fetchedAtMs, parseServerTimestamp, groupUpdatesByDay } from './lib/recentUpdates'
|
||||||
import RecentToolbar from './RecentToolbar.svelte'
|
import RecentToolbar from './RecentToolbar.svelte'
|
||||||
import UpdatesTab from './UpdatesTab.svelte'
|
import UpdatesTab from './UpdatesTab.svelte'
|
||||||
import HistoryTab from './HistoryTab.svelte'
|
import HistoryTab from './HistoryTab.svelte'
|
||||||
import type { Manga } from '$lib/types'
|
import type { Manga, Chapter } from '$lib/types'
|
||||||
import type { RecentUpdate, UpdateGroup } from './lib/recentUpdates'
|
import type { RecentUpdate, UpdateGroup } from './lib/recentUpdates'
|
||||||
import type { HistoryGroup } from './lib/recentHistory'
|
import type { HistoryGroup } from './lib/recentHistory'
|
||||||
|
|
||||||
@@ -27,6 +28,7 @@
|
|||||||
let updatesLoading: boolean = $state(true)
|
let updatesLoading: boolean = $state(true)
|
||||||
let updatesError: string | null = $state(null)
|
let updatesError: string | null = $state(null)
|
||||||
let openingId: number | null = $state(null)
|
let openingId: number | null = $state(null)
|
||||||
|
let enqueueing: Set<number> = $state(new Set())
|
||||||
let updaterRunning: boolean = $state(false)
|
let updaterRunning: boolean = $state(false)
|
||||||
let lastUpdatedTs: number | null = $state(null)
|
let lastUpdatedTs: number | null = $state(null)
|
||||||
let updaterFinishedJobs: number | null = $state(null)
|
let updaterFinishedJobs: number | null = $state(null)
|
||||||
@@ -120,9 +122,9 @@
|
|||||||
if (force) cache.clear(key)
|
if (force) cache.clear(key)
|
||||||
|
|
||||||
const [updatesRes, statusRes] = await Promise.all([
|
const [updatesRes, statusRes] = await Promise.all([
|
||||||
cache.get<RecentUpdate[]>(
|
cache.get<Chapter[]>(
|
||||||
key,
|
key,
|
||||||
() => getAdapter().getRecentlyUpdated(nextCtrl.signal),
|
() => getAdapter().getRecentlyUpdated(),
|
||||||
RECENT_UPDATES_TTL_MS,
|
RECENT_UPDATES_TTL_MS,
|
||||||
CACHE_GROUPS.LIBRARY,
|
CACHE_GROUPS.LIBRARY,
|
||||||
),
|
),
|
||||||
@@ -136,7 +138,7 @@
|
|||||||
if (nextCtrl.signal.aborted) return
|
if (nextCtrl.signal.aborted) return
|
||||||
|
|
||||||
updates = (updatesRes ?? [])
|
updates = (updatesRes ?? [])
|
||||||
.filter(item => item.manga?.inLibrary)
|
.map(item => ({ ...item, isRead: item.read }))
|
||||||
.sort((a, b) => fetchedAtMs(b) - fetchedAtMs(a))
|
.sort((a, b) => fetchedAtMs(b) - fetchedAtMs(a))
|
||||||
} catch (e: any) {
|
} catch (e: any) {
|
||||||
if (nextCtrl.signal.aborted) return
|
if (nextCtrl.signal.aborted) return
|
||||||
@@ -191,6 +193,42 @@
|
|||||||
clearHistory()
|
clearHistory()
|
||||||
historyConfirmClear = false
|
historyConfirmClear = false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function enqueueUpdate(item: RecentUpdate) {
|
||||||
|
if (enqueueing.has(item.id)) return
|
||||||
|
enqueueing = new Set(enqueueing).add(item.id)
|
||||||
|
try {
|
||||||
|
const allowed = await downloadStore.enqueue(item.id)
|
||||||
|
if (allowed) addToast({ kind: 'download', title: 'Download queued', body: item.name ?? 'Chapter' })
|
||||||
|
} catch {
|
||||||
|
addToast({ kind: 'error', title: 'Download failed', body: 'Could not queue chapter.' })
|
||||||
|
} finally {
|
||||||
|
enqueueing.delete(item.id)
|
||||||
|
enqueueing = new Set(enqueueing)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function deleteDownloaded(item: RecentUpdate) {
|
||||||
|
try {
|
||||||
|
await getAdapter().deleteDownloadedChapters([String(item.id)])
|
||||||
|
updates = updates.map(u => u.id === item.id ? { ...u, isDownloaded: false } : u)
|
||||||
|
} catch {
|
||||||
|
addToast({ kind: 'error', title: 'Delete failed', body: 'Could not delete download.' })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function toggleLibraryUpdate() {
|
||||||
|
try {
|
||||||
|
if (updaterRunning) {
|
||||||
|
await getAdapter().stopLibraryUpdate()
|
||||||
|
} else {
|
||||||
|
await getAdapter().startLibraryUpdate()
|
||||||
|
scheduleStatusPoll()
|
||||||
|
}
|
||||||
|
} catch (e: any) {
|
||||||
|
addToast({ kind: 'error', title: 'Update error', body: e?.message ?? 'Failed' })
|
||||||
|
}
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="root anim-fade-in">
|
<div class="root anim-fade-in">
|
||||||
@@ -215,13 +253,16 @@
|
|||||||
error={updatesError}
|
error={updatesError}
|
||||||
groups={updateGroups}
|
groups={updateGroups}
|
||||||
{updatesSearch}
|
{updatesSearch}
|
||||||
totalCount={updates.length}
|
totalCount={updates.filter(u => !u.isRead).length}
|
||||||
{openingId}
|
{openingId}
|
||||||
|
{enqueueing}
|
||||||
{updaterRunning}
|
{updaterRunning}
|
||||||
{lastUpdatedLabel}
|
{lastUpdatedLabel}
|
||||||
{updaterProgressLabel}
|
{updaterProgressLabel}
|
||||||
onOpenUpdate={openUpdate}
|
onOpenUpdate={openUpdate}
|
||||||
onOpenSeries={(item) => setActiveManga(mangaStub(item))}
|
onOpenSeries={(item) => setActiveManga(mangaStub(item))}
|
||||||
|
onEnqueue={enqueueUpdate}
|
||||||
|
onDeleteDownload={deleteDownloaded}
|
||||||
/>
|
/>
|
||||||
{:else}
|
{:else}
|
||||||
<HistoryTab
|
<HistoryTab
|
||||||
|
|||||||
@@ -19,8 +19,10 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
let {
|
let {
|
||||||
tab, historySearch, updatesSearch, historyConfirmClear, hasHistory, updatesLoading,
|
tab, historySearch, updatesSearch, historyConfirmClear, hasHistory,
|
||||||
onTabChange, onHistorySearchChange, onUpdatesSearchChange, onHistoryClear, onRefreshUpdates,
|
updatesLoading,
|
||||||
|
onTabChange, onHistorySearchChange, onUpdatesSearchChange,
|
||||||
|
onHistoryClear, onRefreshUpdates,
|
||||||
}: Props = $props()
|
}: Props = $props()
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
@@ -57,7 +59,7 @@
|
|||||||
class="icon-btn"
|
class="icon-btn"
|
||||||
onclick={onRefreshUpdates}
|
onclick={onRefreshUpdates}
|
||||||
disabled={updatesLoading}
|
disabled={updatesLoading}
|
||||||
title="Refresh updates"
|
title="Reload update list"
|
||||||
>
|
>
|
||||||
{#if updatesLoading}
|
{#if updatesLoading}
|
||||||
<CircleNotch size={14} weight="light" class="anim-spin" />
|
<CircleNotch size={14} weight="light" class="anim-spin" />
|
||||||
@@ -79,19 +81,6 @@
|
|||||||
{/if}
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<button
|
|
||||||
class="icon-btn"
|
|
||||||
onclick={onRefreshUpdates}
|
|
||||||
disabled={updatesLoading}
|
|
||||||
title="Refresh library"
|
|
||||||
>
|
|
||||||
{#if updatesLoading}
|
|
||||||
<CircleNotch size={14} weight="light" class="anim-spin" />
|
|
||||||
{:else}
|
|
||||||
<ArrowsClockwise size={14} weight="bold" />
|
|
||||||
{/if}
|
|
||||||
</button>
|
|
||||||
|
|
||||||
{#if hasHistory}
|
{#if hasHistory}
|
||||||
<button
|
<button
|
||||||
class="clear-btn"
|
class="clear-btn"
|
||||||
@@ -155,6 +144,8 @@
|
|||||||
}
|
}
|
||||||
.icon-btn:hover:not(:disabled) { color: var(--text-primary); border-color: var(--border-strong); }
|
.icon-btn:hover:not(:disabled) { color: var(--text-primary); border-color: var(--border-strong); }
|
||||||
.icon-btn:disabled { opacity: 0.45; cursor: default; }
|
.icon-btn:disabled { opacity: 0.45; cursor: default; }
|
||||||
|
.icon-btn.running { color: var(--color-error); border-color: color-mix(in srgb, var(--color-error) 30%, transparent); background: var(--color-error-bg); }
|
||||||
|
.icon-btn.running:hover { color: var(--color-error); border-color: var(--color-error); }
|
||||||
|
|
||||||
.search-wrap { position: relative; display: flex; align-items: center; }
|
.search-wrap { position: relative; display: flex; align-items: center; }
|
||||||
.search-wrap :global(.search-icon) { position: absolute; left: 8px; color: var(--text-faint); pointer-events: none; }
|
.search-wrap :global(.search-icon) { position: absolute; left: 8px; color: var(--text-faint); pointer-events: none; }
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { BookOpen, CircleNotch } from 'phosphor-svelte'
|
import { BookOpen, CircleNotch, Download, Trash } from 'phosphor-svelte'
|
||||||
import Thumbnail from '$lib/components/shared/manga/Thumbnail.svelte'
|
import Thumbnail from '$lib/components/shared/manga/Thumbnail.svelte'
|
||||||
import type { RecentUpdate, UpdateGroup } from './lib/recentUpdates'
|
import type { RecentUpdate, UpdateGroup } from './lib/recentUpdates'
|
||||||
|
|
||||||
@@ -10,17 +10,20 @@
|
|||||||
updatesSearch: string
|
updatesSearch: string
|
||||||
totalCount: number
|
totalCount: number
|
||||||
openingId: number | null
|
openingId: number | null
|
||||||
|
enqueueing: Set<number>
|
||||||
updaterRunning: boolean
|
updaterRunning: boolean
|
||||||
lastUpdatedLabel: string | null
|
lastUpdatedLabel: string | null
|
||||||
updaterProgressLabel: string | null
|
updaterProgressLabel: string | null
|
||||||
onOpenUpdate: (item: RecentUpdate) => void
|
onOpenUpdate: (item: RecentUpdate) => void
|
||||||
onOpenSeries: (item: RecentUpdate) => void
|
onOpenSeries: (item: RecentUpdate) => void
|
||||||
|
onEnqueue: (item: RecentUpdate) => void
|
||||||
|
onDeleteDownload: (item: RecentUpdate) => void
|
||||||
}
|
}
|
||||||
|
|
||||||
let {
|
let {
|
||||||
loading, error, groups, updatesSearch, totalCount, openingId,
|
loading, error, groups, updatesSearch, totalCount, openingId, enqueueing,
|
||||||
updaterRunning, lastUpdatedLabel, updaterProgressLabel,
|
updaterRunning, lastUpdatedLabel, updaterProgressLabel,
|
||||||
onOpenUpdate, onOpenSeries,
|
onOpenUpdate, onOpenSeries, onEnqueue, onDeleteDownload,
|
||||||
}: Props = $props()
|
}: Props = $props()
|
||||||
|
|
||||||
const filteredGroups = $derived(updatesSearch.trim()
|
const filteredGroups = $derived(updatesSearch.trim()
|
||||||
@@ -63,7 +66,7 @@
|
|||||||
<div class="bar-sep"></div>
|
<div class="bar-sep"></div>
|
||||||
{/if}
|
{/if}
|
||||||
{#if !loading && totalCount > 0}
|
{#if !loading && totalCount > 0}
|
||||||
<span class="status-count">{totalCount} chapter{totalCount === 1 ? '' : 's'}</span>
|
<span class="status-count">{totalCount} unread</span>
|
||||||
{/if}
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -138,7 +141,7 @@
|
|||||||
<div class="update-info">
|
<div class="update-info">
|
||||||
<div class="title-row">
|
<div class="title-row">
|
||||||
<span class="series-title">{item.manga?.title ?? 'Unknown series'}</span>
|
<span class="series-title">{item.manga?.title ?? 'Unknown series'}</span>
|
||||||
{#if !item.isRead}<span class="pill">Unread</span>{/if}
|
{#if !item.isRead}<span class="pill" title="Unread"></span>{/if}
|
||||||
</div>
|
</div>
|
||||||
<span class="chapter-title">{chapterLabel(item)}</span>
|
<span class="chapter-title">{chapterLabel(item)}</span>
|
||||||
{#if (item.lastPageRead ?? 0) > 0 && !item.isRead}
|
{#if (item.lastPageRead ?? 0) > 0 && !item.isRead}
|
||||||
@@ -146,6 +149,17 @@
|
|||||||
{/if}
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
<div class="row-end">
|
<div class="row-end">
|
||||||
|
{#if enqueueing.has(item.id)}
|
||||||
|
<CircleNotch size={14} weight="light" class="anim-spin" />
|
||||||
|
{:else if item.isDownloaded}
|
||||||
|
<button class="dl-btn dl-btn-delete" onclick={(e) => { e.stopPropagation(); onDeleteDownload(item) }} title="Delete download">
|
||||||
|
<Trash size={13} weight="light" />
|
||||||
|
</button>
|
||||||
|
{:else}
|
||||||
|
<button class="dl-btn" onclick={(e) => { e.stopPropagation(); onEnqueue(item) }} title="Download">
|
||||||
|
<Download size={13} weight="light" />
|
||||||
|
</button>
|
||||||
|
{/if}
|
||||||
{#if openingId === item.id}
|
{#if openingId === item.id}
|
||||||
<CircleNotch size={14} weight="light" class="anim-spin" />
|
<CircleNotch size={14} weight="light" class="anim-spin" />
|
||||||
{:else}
|
{:else}
|
||||||
@@ -258,12 +272,20 @@
|
|||||||
.chapter-title { font-family: var(--font-ui); font-size: var(--text-xs); color: var(--text-muted); }
|
.chapter-title { font-family: var(--font-ui); font-size: var(--text-xs); color: var(--text-muted); }
|
||||||
.meta-row { font-family: var(--font-ui); font-size: var(--text-2xs); color: var(--text-faint); letter-spacing: var(--tracking-wide); }
|
.meta-row { font-family: var(--font-ui); font-size: var(--text-2xs); color: var(--text-faint); letter-spacing: var(--tracking-wide); }
|
||||||
.pill {
|
.pill {
|
||||||
padding: 2px 6px; border-radius: var(--radius-full);
|
width: 6px; height: 6px; border-radius: 50%;
|
||||||
background: var(--accent-muted); color: var(--accent-fg);
|
background: var(--color-success, #22c55e); flex-shrink: 0;
|
||||||
font-family: var(--font-ui); font-size: var(--text-2xs);
|
|
||||||
letter-spacing: var(--tracking-wide); text-transform: uppercase; flex-shrink: 0;
|
|
||||||
}
|
}
|
||||||
.row-end { color: var(--text-faint); display: flex; align-items: center; justify-content: center; width: 24px; flex-shrink: 0; }
|
.row-end { color: var(--text-faint); display: flex; align-items: center; gap: var(--sp-1); justify-content: center; flex-shrink: 0; }
|
||||||
|
|
||||||
|
.dl-btn {
|
||||||
|
display: flex; align-items: center; justify-content: center;
|
||||||
|
width: 24px; height: 24px; border-radius: var(--radius-sm);
|
||||||
|
border: none; background: none; color: var(--text-faint); cursor: pointer;
|
||||||
|
transition: color var(--t-base), background var(--t-base);
|
||||||
|
}
|
||||||
|
.dl-btn:hover { color: var(--text-muted); background: var(--bg-overlay); }
|
||||||
|
.dl-btn-delete { color: var(--color-error); }
|
||||||
|
.dl-btn-delete:hover { background: var(--color-error-bg); }
|
||||||
|
|
||||||
.empty {
|
.empty {
|
||||||
flex: 1; display: flex; flex-direction: column; align-items: center;
|
flex: 1; display: flex; flex-direction: column; align-items: center;
|
||||||
|
|||||||
@@ -25,7 +25,10 @@ export interface UpdateStatus {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function fetchedAtMs(item: Pick<RecentUpdate, 'fetchedAt'>): number {
|
export function fetchedAtMs(item: Pick<RecentUpdate, 'fetchedAt'>): number {
|
||||||
const ts = item.fetchedAt ? new Date(item.fetchedAt).getTime() : Date.now()
|
if (!item.fetchedAt) return Date.now()
|
||||||
|
const numeric = Number(item.fetchedAt)
|
||||||
|
if (Number.isFinite(numeric)) return numeric * 1000
|
||||||
|
const ts = new Date(item.fetchedAt).getTime()
|
||||||
return Number.isFinite(ts) ? ts : Date.now()
|
return Number.isFinite(ts) ? ts : Date.now()
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -42,10 +45,18 @@ export function parseServerTimestamp(value: unknown): number | null {
|
|||||||
|
|
||||||
export function groupUpdatesByDay(updates: RecentUpdate[]): UpdateGroup[] {
|
export function groupUpdatesByDay(updates: RecentUpdate[]): UpdateGroup[] {
|
||||||
const grouped: Record<string, RecentUpdate[]> = {}
|
const grouped: Record<string, RecentUpdate[]> = {}
|
||||||
|
const order: Record<string, number> = {}
|
||||||
for (const item of updates) {
|
for (const item of updates) {
|
||||||
const label = dayLabel(fetchedAtMs(item))
|
const ts = fetchedAtMs(item)
|
||||||
if (!grouped[label]) grouped[label] = []
|
const label = dayLabel(ts)
|
||||||
|
if (!grouped[label]) {
|
||||||
|
grouped[label] = []
|
||||||
|
order[label] = ts
|
||||||
|
}
|
||||||
grouped[label].push(item)
|
grouped[label].push(item)
|
||||||
|
if (ts > order[label]) order[label] = ts
|
||||||
}
|
}
|
||||||
return Object.entries(grouped).map(([label, items]) => ({ label, items }))
|
return Object.entries(grouped)
|
||||||
|
.sort(([a], [b]) => order[b] - order[a])
|
||||||
|
.map(([label, items]) => ({ label, items }))
|
||||||
}
|
}
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { Download, CheckCircle, Circle, CircleNotch, Trash } from 'phosphor-svelte'
|
import { Download, CheckSquare, Square, CircleNotch, Trash } from 'phosphor-svelte'
|
||||||
import ContextMenu from '$lib/components/shared/ui/ContextMenu.svelte'
|
import ContextMenu from '$lib/components/shared/ui/ContextMenu.svelte'
|
||||||
import type { MenuEntry } from '$lib/components/shared/ui/ContextMenu.svelte'
|
import type { MenuEntry } from '$lib/components/shared/ui/ContextMenu.svelte'
|
||||||
import { longPress } from '$lib/core/ui/touchscreen'
|
import { longPress } from '$lib/core/ui/touchscreen'
|
||||||
@@ -14,27 +14,39 @@
|
|||||||
enqueueing: Set<number>
|
enqueueing: Set<number>
|
||||||
chapterPage: number
|
chapterPage: number
|
||||||
totalPages: number
|
totalPages: number
|
||||||
scrollEl?: HTMLDivElement | null
|
|
||||||
onOpen: (ch: Chapter, inProgress: boolean) => void
|
onOpen: (ch: Chapter, inProgress: boolean) => void
|
||||||
onToggleSelect: (id: number, e: MouseEvent | KeyboardEvent) => void
|
onToggleSelect: (id: number, e: MouseEvent | KeyboardEvent) => void
|
||||||
onEnqueue: (ch: Chapter, e: MouseEvent) => void
|
onEnqueue: (ch: Chapter, e: MouseEvent) => void
|
||||||
onDeleteDownload:(id: number) => void
|
onDeleteDownload:(id: number) => void
|
||||||
onPageChange: (page: number) => void
|
onPageChange: (page: number) => void
|
||||||
|
onPageSizeChange:(n: number) => void
|
||||||
buildCtxItems: (ch: Chapter, idx: number) => MenuEntry[]
|
buildCtxItems: (ch: Chapter, idx: number) => MenuEntry[]
|
||||||
}
|
}
|
||||||
|
|
||||||
let {
|
let {
|
||||||
pageChapters, sortedChapters, viewMode, loadingChapters,
|
pageChapters, sortedChapters, viewMode, loadingChapters,
|
||||||
selectedIds, enqueueing, chapterPage, totalPages,
|
selectedIds, enqueueing, chapterPage, totalPages,
|
||||||
scrollEl = $bindable(null),
|
|
||||||
onOpen, onToggleSelect, onEnqueue, onDeleteDownload,
|
onOpen, onToggleSelect, onEnqueue, onDeleteDownload,
|
||||||
onPageChange, buildCtxItems,
|
onPageChange, onPageSizeChange, buildCtxItems,
|
||||||
}: Props = $props()
|
}: Props = $props()
|
||||||
|
|
||||||
let ctx: { x: number; y: number; chapter: Chapter; idx: number } | null = $state(null)
|
let ctx: { x: number; y: number; chapter: Chapter; idx: number } | null = $state(null)
|
||||||
|
let listEl: HTMLDivElement | null = $state(null)
|
||||||
|
|
||||||
const hasSelection = $derived(selectedIds.size > 0)
|
const hasSelection = $derived(selectedIds.size > 0)
|
||||||
|
|
||||||
|
$effect(() => {
|
||||||
|
if (!listEl || viewMode !== 'list') return
|
||||||
|
const ro = new ResizeObserver(([entry]) => {
|
||||||
|
const firstRow = listEl!.querySelector('.ch-row') as HTMLElement | null
|
||||||
|
const rowH = firstRow ? firstRow.offsetHeight : 37
|
||||||
|
const n = Math.max(1, Math.floor(entry.contentRect.height / rowH))
|
||||||
|
onPageSizeChange(n)
|
||||||
|
})
|
||||||
|
ro.observe(listEl)
|
||||||
|
return () => ro.disconnect()
|
||||||
|
})
|
||||||
|
|
||||||
function chapterLongPress(node: HTMLElement, param: [Chapter, number]) {
|
function chapterLongPress(node: HTMLElement, param: [Chapter, number]) {
|
||||||
const [ch, idx] = param
|
const [ch, idx] = param
|
||||||
return longPress(node, {
|
return longPress(node, {
|
||||||
@@ -50,7 +62,7 @@
|
|||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class={viewMode === 'grid' ? 'ch-grid' : 'ch-list'} bind:this={scrollEl}>
|
<div class={viewMode === 'grid' ? 'ch-grid' : 'ch-list'} bind:this={listEl}>
|
||||||
{#if loadingChapters && sortedChapters.length === 0}
|
{#if loadingChapters && sortedChapters.length === 0}
|
||||||
{#if viewMode === 'grid'}
|
{#if viewMode === 'grid'}
|
||||||
{#each Array(24) as _}<div class="grid-cell-skeleton skeleton"></div>{/each}
|
{#each Array(24) as _}<div class="grid-cell-skeleton skeleton"></div>{/each}
|
||||||
@@ -100,7 +112,7 @@
|
|||||||
oncontextmenu={(e) => { e.preventDefault(); ctx = { x: e.clientX, y: e.clientY, chapter: ch, idx: idxInSorted } }}
|
oncontextmenu={(e) => { e.preventDefault(); ctx = { x: e.clientX, y: e.clientY, chapter: ch, idx: idxInSorted } }}
|
||||||
>
|
>
|
||||||
<button class="ch-check" class:ch-check-visible={hasSelection} onclick={(e) => onToggleSelect(ch.id, e)} title="Select">
|
<button class="ch-check" class:ch-check-visible={hasSelection} onclick={(e) => onToggleSelect(ch.id, e)} title="Select">
|
||||||
{#if isSelected}<CheckCircle size={15} weight="fill" />{:else}<Circle size={15} weight="light" />{/if}
|
{#if isSelected}<CheckSquare size={15} weight="fill" />{:else}<Square size={15} weight="light" />{/if}
|
||||||
</button>
|
</button>
|
||||||
<div class="ch-left">
|
<div class="ch-left">
|
||||||
<span class="ch-name">{ch.name}</span>
|
<span class="ch-name">{ch.name}</span>
|
||||||
@@ -111,7 +123,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="ch-right">
|
<div class="ch-right">
|
||||||
{#if ch.read}<CheckCircle size={14} weight="light" class="read-icon" />{/if}
|
{#if ch.read}<CheckSquare size={14} weight="light" class="read-icon" />{/if}
|
||||||
{#if ch.downloaded}
|
{#if ch.downloaded}
|
||||||
<div class="ch-dl-wrap">
|
<div class="ch-dl-wrap">
|
||||||
<Download size={13} weight="fill" class="ch-dl-icon" />
|
<Download size={13} weight="fill" class="ch-dl-icon" />
|
||||||
@@ -145,38 +157,42 @@
|
|||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
.ch-list { flex: 1; overflow-y: auto; }
|
.ch-list { flex: 1; overflow: hidden; }
|
||||||
.ch-grid { flex: 1; overflow-y: auto; display: grid; grid-template-columns: repeat(auto-fill, minmax(42px, 1fr)); gap: 4px; padding: var(--sp-3); align-content: start; }
|
.ch-grid { flex: 1; overflow: hidden; display: grid; grid-template-columns: repeat(auto-fill, minmax(42px, 1fr)); gap: 4px; padding: var(--sp-3); align-content: start; }
|
||||||
|
|
||||||
.ch-row { display: flex; align-items: center; padding: 10px var(--sp-4); border-bottom: 1px solid var(--border-dim); cursor: pointer; transition: background var(--t-fast); gap: var(--sp-3); }
|
.ch-row { display: flex; align-items: center; padding: 8px var(--sp-4); border-bottom: 1px solid var(--border-dim); cursor: pointer; transition: background var(--t-fast); gap: var(--sp-3); }
|
||||||
.ch-row:hover { background: var(--bg-raised); }
|
.ch-row:hover { background: var(--bg-raised); }
|
||||||
.ch-row.read { opacity: 0.45; }
|
.ch-row.read { opacity: 0.5; }
|
||||||
.ch-left { flex: 1; min-width: 0; display: flex; flex-direction: column; gap: 3px; }
|
.ch-left { flex: 1; min-width: 0; display: flex; flex-direction: column; gap: 2px; }
|
||||||
.ch-name { font-size: var(--text-sm); color: var(--text-secondary); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
|
.ch-name { font-size: var(--text-sm); color: var(--text-secondary); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
|
||||||
.ch-meta { display: flex; align-items: center; gap: var(--sp-2); flex-wrap: wrap; }
|
.ch-meta { display: flex; align-items: center; gap: var(--sp-2); }
|
||||||
.ch-meta-item { font-family: var(--font-ui); font-size: var(--text-2xs); color: var(--text-faint); letter-spacing: var(--tracking-wide); }
|
.ch-meta-item { font-family: var(--font-ui); font-size: var(--text-2xs); color: var(--text-faint); letter-spacing: var(--tracking-wide); }
|
||||||
.ch-right { display: flex; align-items: center; gap: var(--sp-1); flex-shrink: 0; }
|
.ch-right { display: flex; align-items: center; gap: var(--sp-1); flex-shrink: 0; }
|
||||||
:global(.read-icon) { color: var(--text-faint); }
|
:global(.read-icon) { color: var(--text-faint); }
|
||||||
:global(.enqueue-icon) { color: var(--text-faint); }
|
:global(.enqueue-icon) { color: var(--text-faint); }
|
||||||
|
|
||||||
.dl-btn { display: flex; align-items: center; justify-content: center; width: 24px; height: 24px; border-radius: var(--radius-sm); color: var(--text-faint); transition: color var(--t-base), background var(--t-base); opacity: 0; }
|
.dl-btn { display: flex; align-items: center; justify-content: center; width: 24px; height: 24px; border-radius: var(--radius-sm); color: var(--text-faint); transition: color var(--t-base), background var(--t-base); }
|
||||||
.ch-row:hover .dl-btn { opacity: 1; }
|
|
||||||
.dl-btn:hover { color: var(--text-muted); background: var(--bg-overlay); }
|
.dl-btn:hover { color: var(--text-muted); background: var(--bg-overlay); }
|
||||||
.dl-btn-delete { color: var(--color-error) !important; opacity: 0; }
|
.dl-btn-delete { color: var(--color-error) !important; }
|
||||||
.ch-row:hover .dl-btn-delete { opacity: 1; }
|
|
||||||
.dl-btn-delete:hover { background: var(--color-error-bg) !important; }
|
.dl-btn-delete:hover { background: var(--color-error-bg) !important; }
|
||||||
|
|
||||||
.ch-dl-wrap { position: relative; display: flex; align-items: center; justify-content: center; width: 24px; height: 24px; }
|
.ch-dl-wrap { display: flex; align-items: center; gap: var(--sp-1); }
|
||||||
:global(.ch-dl-icon) { color: var(--text-faint); transition: opacity var(--t-fast); }
|
:global(.ch-dl-icon) { color: var(--text-faint); }
|
||||||
.ch-row:hover .ch-dl-wrap :global(.ch-dl-icon) { opacity: 0; }
|
|
||||||
.ch-dl-wrap .dl-btn-delete { position: absolute; inset: 0; opacity: 0; }
|
|
||||||
.ch-row:hover .ch-dl-wrap .dl-btn-delete { opacity: 1; }
|
|
||||||
|
|
||||||
.ch-check { display: flex; align-items: center; justify-content: center; width: 20px; height: 20px; flex-shrink: 0; border-radius: var(--radius-sm); border: none; background: none; color: var(--text-faint); cursor: pointer; opacity: 0; transition: opacity var(--t-fast), color var(--t-fast); padding: 0; }
|
.ch-check {
|
||||||
.ch-row:hover .ch-check { opacity: 1; }
|
display: flex; align-items: center; justify-content: center;
|
||||||
.ch-check-visible { opacity: 1 !important; }
|
width: 20px; height: 20px; flex-shrink: 0;
|
||||||
|
border-radius: var(--radius-sm); border: none; background: none;
|
||||||
|
color: var(--text-faint); cursor: pointer; padding: 0;
|
||||||
|
opacity: 0;
|
||||||
|
transform: translateX(-6px);
|
||||||
|
transition: opacity var(--t-fast), transform var(--t-fast), color var(--t-fast);
|
||||||
|
margin-right: -20px;
|
||||||
|
}
|
||||||
|
.ch-row:hover .ch-check { opacity: 1; transform: translateX(0); margin-right: 0; }
|
||||||
|
.ch-check-visible { opacity: 1 !important; transform: translateX(0) !important; margin-right: 0 !important; }
|
||||||
|
.ch-selected .ch-check { color: var(--accent-fg); }
|
||||||
.ch-selected { background: color-mix(in srgb, var(--accent) 8%, transparent) !important; }
|
.ch-selected { background: color-mix(in srgb, var(--accent) 8%, transparent) !important; }
|
||||||
.ch-selected .ch-check { color: var(--accent-fg); opacity: 1; }
|
|
||||||
|
|
||||||
.row-skeleton { display: flex; flex-direction: column; gap: var(--sp-2); padding: 12px var(--sp-4); border-bottom: 1px solid var(--border-dim); }
|
.row-skeleton { display: flex; flex-direction: column; gap: var(--sp-2); padding: 12px var(--sp-4); border-bottom: 1px solid var(--border-dim); }
|
||||||
|
|
||||||
@@ -184,8 +200,8 @@
|
|||||||
.grid-cell:hover { background: var(--bg-overlay); border-color: var(--border-strong); }
|
.grid-cell:hover { background: var(--bg-overlay); border-color: var(--border-strong); }
|
||||||
.grid-cell.read { background: var(--color-read); color: var(--text-faint); border-color: transparent; }
|
.grid-cell.read { background: var(--color-read); color: var(--text-faint); border-color: transparent; }
|
||||||
.grid-cell-num { font-size: 10px; }
|
.grid-cell-num { font-size: 10px; }
|
||||||
.grid-cell-dot { position: absolute; bottom: 3px; right: 3px; width: 4px; height: 4px; border-radius: 50%; background: var(--text-faint); }
|
.grid-cell-dot { position: absolute; bottom: 3px; right: 3px; width: 4px; height: 4px; border-radius: var(--radius-sm); background: var(--text-faint); }
|
||||||
.grid-cell-dl { position: absolute; top: 3px; left: 3px; width: 4px; height: 4px; border-radius: 50%; background: var(--accent-fg); }
|
.grid-cell-dl { position: absolute; top: 3px; left: 3px; width: 4px; height: 4px; border-radius: var(--radius-sm); background: var(--accent-fg); }
|
||||||
.grid-cell-spinner { position: absolute; top: 2px; right: 2px; }
|
.grid-cell-spinner { position: absolute; top: 2px; right: 2px; }
|
||||||
.grid-cell-skeleton { aspect-ratio: 1; border-radius: var(--radius-sm); }
|
.grid-cell-skeleton { aspect-ratio: 1; border-radius: var(--radius-sm); }
|
||||||
.grid-selected { background: var(--accent-muted) !important; border-color: var(--accent-dim) !important; }
|
.grid-selected { background: var(--accent-muted) !important; border-color: var(--accent-dim) !important; }
|
||||||
|
|||||||
@@ -19,8 +19,6 @@
|
|||||||
sortMode: ChapterSortMode
|
sortMode: ChapterSortMode
|
||||||
sortDir: ChapterSortDir
|
sortDir: ChapterSortDir
|
||||||
viewMode: 'list' | 'grid'
|
viewMode: 'list' | 'grid'
|
||||||
chapterPage: number
|
|
||||||
totalPages: number
|
|
||||||
downloadedCount: number
|
downloadedCount: number
|
||||||
totalCount: number
|
totalCount: number
|
||||||
deletingAll: boolean
|
deletingAll: boolean
|
||||||
@@ -57,7 +55,7 @@
|
|||||||
|
|
||||||
let {
|
let {
|
||||||
chapters, sortedChapters, sortMode, sortDir, viewMode,
|
chapters, sortedChapters, sortMode, sortDir, viewMode,
|
||||||
chapterPage, totalPages, downloadedCount, totalCount, deletingAll,
|
downloadedCount, totalCount, deletingAll,
|
||||||
hasSelection, selectedCount, continueChapter,
|
hasSelection, selectedCount, continueChapter,
|
||||||
availableScanlators, scanlatorFilter, scanlatorBlacklist, scanlatorForce,
|
availableScanlators, scanlatorFilter, scanlatorBlacklist, scanlatorForce,
|
||||||
allCategories, mangaCategories, catsLoading, refreshing,
|
allCategories, mangaCategories, catsLoading, refreshing,
|
||||||
@@ -277,9 +275,11 @@
|
|||||||
<ArrowsClockwise size={14} weight="light" class={refreshing ? 'anim-spin' : ''} />
|
<ArrowsClockwise size={14} weight="light" class={refreshing ? 'anim-spin' : ''} />
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
<button class="icon-btn" onclick={onOpenFolder} title="Open manga folder">
|
{#if downloadedCount > 0}
|
||||||
<FolderOpen size={14} weight="light" />
|
<button class="icon-btn" onclick={onOpenFolder} title="Open manga folder">
|
||||||
</button>
|
<FolderOpen size={14} weight="light" />
|
||||||
|
</button>
|
||||||
|
{/if}
|
||||||
|
|
||||||
<div class="fp-wrap" bind:this={folderPickerRef}>
|
<div class="fp-wrap" bind:this={folderPickerRef}>
|
||||||
<button class="icon-btn" class:active={hasFolders} onclick={() => folderPickerOpen = !folderPickerOpen}>
|
<button class="icon-btn" class:active={hasFolders} onclick={() => folderPickerOpen = !folderPickerOpen}>
|
||||||
@@ -377,13 +377,6 @@
|
|||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
{#if totalPages > 1}
|
|
||||||
<div class="pagination">
|
|
||||||
<button class="page-btn" onclick={() => onPageChange(Math.max(1, chapterPage - 1))} disabled={chapterPage === 1}>←</button>
|
|
||||||
<span class="page-num">{chapterPage} / {totalPages}</span>
|
|
||||||
<button class="page-btn" onclick={() => onPageChange(Math.min(totalPages, chapterPage + 1))} disabled={chapterPage === totalPages}>→</button>
|
|
||||||
</div>
|
|
||||||
{/if}
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -572,17 +565,6 @@
|
|||||||
.dl-unified-btn.dl-has-count:hover { background: var(--accent-muted); border-color: var(--accent); opacity: 0.9; }
|
.dl-unified-btn.dl-has-count:hover { background: var(--accent-muted); border-color: var(--accent); opacity: 0.9; }
|
||||||
.dl-unified-btn.active { color: var(--accent-fg); border-color: var(--accent-dim); background: var(--accent-muted); }
|
.dl-unified-btn.active { color: var(--accent-fg); border-color: var(--accent-dim); background: var(--accent-muted); }
|
||||||
|
|
||||||
.pagination { display: flex; align-items: center; gap: var(--sp-2); }
|
|
||||||
.page-btn {
|
|
||||||
font-family: var(--font-ui); font-size: var(--text-xs); letter-spacing: var(--tracking-wide);
|
|
||||||
padding: 4px 10px; border-radius: var(--radius-sm); border: 1px solid var(--border-dim);
|
|
||||||
color: var(--text-faint); background: none; cursor: pointer;
|
|
||||||
transition: color var(--t-base), border-color var(--t-base);
|
|
||||||
}
|
|
||||||
.page-btn:hover:not(:disabled) { color: var(--text-muted); border-color: var(--border-strong); }
|
|
||||||
.page-btn:disabled { opacity: 0.3; cursor: default; }
|
|
||||||
.page-num { font-family: var(--font-ui); font-size: var(--text-xs); color: var(--text-faint); letter-spacing: var(--tracking-wide); }
|
|
||||||
|
|
||||||
.sel-count {
|
.sel-count {
|
||||||
font-family: var(--font-ui); font-size: var(--text-xs); color: var(--text-muted);
|
font-family: var(--font-ui); font-size: var(--text-xs); color: var(--text-muted);
|
||||||
letter-spacing: var(--tracking-wide); padding: 0 var(--sp-1);
|
letter-spacing: var(--tracking-wide); padding: 0 var(--sp-1);
|
||||||
|
|||||||
@@ -8,13 +8,17 @@
|
|||||||
CheckCircle, Circle, ArrowFatLinesUp, ArrowFatLinesDown,
|
CheckCircle, Circle, ArrowFatLinesUp, ArrowFatLinesDown,
|
||||||
ArrowFatLineUp, ArrowFatLineDown, Download, Trash, DownloadSimple, CheckSquare,
|
ArrowFatLineUp, ArrowFatLineDown, Download, Trash, DownloadSimple, CheckSquare,
|
||||||
} from 'phosphor-svelte'
|
} from 'phosphor-svelte'
|
||||||
import type { MenuEntry } from '$lib/components/shared/ui/ContextMenu.svelte'
|
|
||||||
|
type MenuSeparator = { separator: true }
|
||||||
|
type MenuItem = { label: string; icon?: any; onClick: () => void; danger?: boolean; disabled?: boolean; separator?: never; children?: MenuEntry[] }
|
||||||
|
type MenuEntry = MenuItem | MenuSeparator
|
||||||
import { getManga, getMangaList } from '$lib/request-manager/manga'
|
import { getManga, getMangaList } from '$lib/request-manager/manga'
|
||||||
import { markChapterRead, markChaptersRead, deleteDownloadedChapters, fetchChapters } from '$lib/request-manager/chapters'
|
import { markChapterRead, markChaptersRead, deleteDownloadedChapters, fetchChapters } from '$lib/request-manager/chapters'
|
||||||
import { downloadStore } from '$lib/state/downloads.svelte'
|
import { downloadStore } from '$lib/state/downloads.svelte'
|
||||||
import { getCategories, updateMangaCategories, createCategory as createCategoryReq, updateManga } from '$lib/request-manager/manga'
|
import { getCategories, updateMangaCategories, createCategory as createCategoryReq, updateManga } from '$lib/request-manager/manga'
|
||||||
import { saveScroll, getScroll } from '$lib/state/app.svelte'
|
import { saveScroll, getScroll } from '$lib/state/app.svelte'
|
||||||
import { seriesState, openReaderForChapter, acknowledgeUpdate, addBookmark, clearMarkersForManga } from '$lib/state/series.svelte'
|
import { seriesState, openReaderForChapter, acknowledgeUpdate, addBookmark, clearMarkersForManga } from '$lib/state/series.svelte'
|
||||||
|
import { updateSettings } from '$lib/state/settings.svelte'
|
||||||
import { DEFAULT_MANGA_PREFS } from '$lib/state/series.svelte'
|
import { DEFAULT_MANGA_PREFS } from '$lib/state/series.svelte'
|
||||||
import type { MangaPrefs } from '$lib/types/settings'
|
import type { MangaPrefs } from '$lib/types/settings'
|
||||||
import { addToast } from '$lib/state/notifications.svelte'
|
import { addToast } from '$lib/state/notifications.svelte'
|
||||||
@@ -33,8 +37,8 @@
|
|||||||
interface Props { mangaId: number }
|
interface Props { mangaId: number }
|
||||||
let { mangaId }: Props = $props()
|
let { mangaId }: Props = $props()
|
||||||
|
|
||||||
const CHAPTERS_PER_PAGE = 25
|
let chaptersPerPage: number = $state(25)
|
||||||
const MANGA_TTL_MS = 5 * 60 * 1000
|
const MANGA_TTL_MS = 5 * 60 * 1000
|
||||||
|
|
||||||
const mangaCache: Map<number, { data: Manga; fetchedAt: number }> = new Map()
|
const mangaCache: Map<number, { data: Manga; fetchedAt: number }> = new Map()
|
||||||
|
|
||||||
@@ -80,8 +84,8 @@
|
|||||||
const scanlatorBlacklist = $derived(get('scanlatorBlacklist') as string[])
|
const scanlatorBlacklist = $derived(get('scanlatorBlacklist') as string[])
|
||||||
const scanlatorForce = $derived(get('scanlatorForce') as boolean)
|
const scanlatorForce = $derived(get('scanlatorForce') as boolean)
|
||||||
|
|
||||||
const totalPages = $derived(Math.ceil(sortedChapters.length / CHAPTERS_PER_PAGE))
|
const totalPages = $derived(Math.ceil(sortedChapters.length / chaptersPerPage))
|
||||||
const pageChapters = $derived(sortedChapters.slice((chapterPage - 1) * CHAPTERS_PER_PAGE, chapterPage * CHAPTERS_PER_PAGE))
|
const pageChapters = $derived(sortedChapters.slice((chapterPage - 1) * chaptersPerPage, chapterPage * chaptersPerPage))
|
||||||
const readCount = $derived(sortedChapters.filter(c => c.read).length)
|
const readCount = $derived(sortedChapters.filter(c => c.read).length)
|
||||||
const totalCount = $derived(sortedChapters.length)
|
const totalCount = $derived(sortedChapters.length)
|
||||||
const progressPct = $derived(totalCount > 0 ? (readCount / totalCount) * 100 : 0)
|
const progressPct = $derived(totalCount > 0 ? (readCount / totalCount) * 100 : 0)
|
||||||
@@ -94,11 +98,11 @@
|
|||||||
const bookmark = seriesState.bookmarks.find(b => b.mangaId === mangaId)
|
const bookmark = seriesState.bookmarks.find(b => b.mangaId === mangaId)
|
||||||
const bookmarkedCh = bookmark ? asc.find(c => c.id === bookmark.chapterId) : null
|
const bookmarkedCh = bookmark ? asc.find(c => c.id === bookmark.chapterId) : null
|
||||||
if (bookmarkedCh && !bookmarkedCh.read)
|
if (bookmarkedCh && !bookmarkedCh.read)
|
||||||
return { chapter: bookmarkedCh, type: (anyRead ? 'continue' : 'start') as const, resumePage: bookmark!.pageNumber }
|
return { chapter: bookmarkedCh, type: (anyRead ? 'continue' : 'start') as 'continue' | 'start', resumePage: bookmark!.pageNumber }
|
||||||
const inProgress = asc.find(c => !c.read && (c.lastPageRead ?? 0) > 0)
|
const inProgress = asc.find(c => !c.read && (c.lastPageRead ?? 0) > 0)
|
||||||
const firstUnread = asc.find(c => !c.read)
|
const firstUnread = asc.find(c => !c.read)
|
||||||
const target = inProgress ?? firstUnread
|
const target = inProgress ?? firstUnread
|
||||||
if (target) return { chapter: target, type: (anyRead ? 'continue' : 'start') as const, resumePage: null }
|
if (target) return { chapter: target, type: (anyRead ? 'continue' : 'start') as 'continue' | 'start', resumePage: null }
|
||||||
return { chapter: asc[0], type: 'reread' as const, resumePage: null }
|
return { chapter: asc[0], type: 'reread' as const, resumePage: null }
|
||||||
})())
|
})())
|
||||||
|
|
||||||
@@ -140,8 +144,13 @@
|
|||||||
const completed = allCategories.find(c => c.name === 'Completed')
|
const completed = allCategories.find(c => c.name === 'Completed')
|
||||||
if (!completed) return
|
if (!completed) return
|
||||||
const inCompleted = mangaCategories.some(c => c.id === completed.id)
|
const inCompleted = mangaCategories.some(c => c.id === completed.id)
|
||||||
if (allRead && !inCompleted) mangaCategories = [...mangaCategories, completed]
|
if (allRead && !inCompleted) {
|
||||||
else if (!allRead && inCompleted) mangaCategories = mangaCategories.filter(c => c.id !== completed.id)
|
await updateMangaCategories(String(id), [completed.id], []).catch(console.error)
|
||||||
|
mangaCategories = [...mangaCategories, completed]
|
||||||
|
} else if (!allRead && inCompleted) {
|
||||||
|
await updateMangaCategories(String(id), [], [completed.id]).catch(console.error)
|
||||||
|
mangaCategories = mangaCategories.filter(c => c.id !== completed.id)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function loadMangaData(id: number) {
|
function loadMangaData(id: number) {
|
||||||
@@ -154,7 +163,6 @@
|
|||||||
loadingManga = false
|
loadingManga = false
|
||||||
seriesState.setActiveManga(cached.data)
|
seriesState.setActiveManga(cached.data)
|
||||||
if (Date.now() - cached.fetchedAt < MANGA_TTL_MS) return
|
if (Date.now() - cached.fetchedAt < MANGA_TTL_MS) return
|
||||||
// stale-while-revalidate: update cache + store in background
|
|
||||||
getManga(id, ctrl.signal)
|
getManga(id, ctrl.signal)
|
||||||
.then(m => {
|
.then(m => {
|
||||||
if (ctrl.signal.aborted) return
|
if (ctrl.signal.aborted) return
|
||||||
@@ -224,8 +232,8 @@
|
|||||||
const records = trackingState.recordsFor(id)
|
const records = trackingState.recordsFor(id)
|
||||||
if (!records.length) return
|
if (!records.length) return
|
||||||
const prefs = {
|
const prefs = {
|
||||||
sortMode: get('sortMode'),
|
sortMode: seriesState.settings.chapterSortMode,
|
||||||
sortDir: get('sortDir'),
|
sortDir: seriesState.settings.chapterSortDir,
|
||||||
preferredScanlator: get('preferredScanlator') as string,
|
preferredScanlator: get('preferredScanlator') as string,
|
||||||
scanlatorFilter: scanlatorFilter,
|
scanlatorFilter: scanlatorFilter,
|
||||||
scanlatorBlacklist: scanlatorBlacklist,
|
scanlatorBlacklist: scanlatorBlacklist,
|
||||||
@@ -278,7 +286,7 @@
|
|||||||
checkAndMarkCompleted(mangaId, seriesState.chaptersFor(mangaId))
|
checkAndMarkCompleted(mangaId, seriesState.chaptersFor(mangaId))
|
||||||
const ch = seriesState.chaptersFor(mangaId).find(c => c.id === chapterId)
|
const ch = seriesState.chaptersFor(mangaId).find(c => c.id === chapterId)
|
||||||
const currentPrefs = {
|
const currentPrefs = {
|
||||||
sortMode: get('sortMode'), sortDir: get('sortDir'),
|
sortMode: seriesState.settings.chapterSortMode, sortDir: seriesState.settings.chapterSortDir,
|
||||||
preferredScanlator: get('preferredScanlator') as string,
|
preferredScanlator: get('preferredScanlator') as string,
|
||||||
scanlatorFilter, scanlatorBlacklist, scanlatorForce,
|
scanlatorFilter, scanlatorBlacklist, scanlatorForce,
|
||||||
}
|
}
|
||||||
@@ -310,7 +318,7 @@
|
|||||||
seriesState.patchChapters(mangaId, chaps => chaps.map(c => idSet.has(c.id) ? { ...c, read: isRead } : c))
|
seriesState.patchChapters(mangaId, chaps => chaps.map(c => idSet.has(c.id) ? { ...c, read: isRead } : c))
|
||||||
checkAndMarkCompleted(mangaId, seriesState.chaptersFor(mangaId))
|
checkAndMarkCompleted(mangaId, seriesState.chaptersFor(mangaId))
|
||||||
const currentPrefs = {
|
const currentPrefs = {
|
||||||
sortMode: get('sortMode'), sortDir: get('sortDir'),
|
sortMode: seriesState.settings.chapterSortMode, sortDir: seriesState.settings.chapterSortDir,
|
||||||
preferredScanlator: get('preferredScanlator') as string,
|
preferredScanlator: get('preferredScanlator') as string,
|
||||||
scanlatorFilter, scanlatorBlacklist, scanlatorForce,
|
scanlatorFilter, scanlatorBlacklist, scanlatorForce,
|
||||||
}
|
}
|
||||||
@@ -431,21 +439,8 @@
|
|||||||
openReaderForChapter(ch, manga)
|
openReaderForChapter(ch, manga)
|
||||||
}
|
}
|
||||||
|
|
||||||
function handleContinue(cc: typeof continueChapter) {
|
interface ContinueChapter { chapter: Chapter; type: 'start' | 'continue' | 'reread'; resumePage: number | null }
|
||||||
if (!cc) return
|
function handleContinue(cc: ContinueChapter) {
|
||||||
if (cc.type === 'continue' && cc.resumePage && cc.resumePage > 1) {
|
|
||||||
const existing = seriesState.bookmarks.find(b => b.chapterId === cc.chapter.id)
|
|
||||||
if (!existing || existing.pageNumber < cc.resumePage) {
|
|
||||||
addBookmark({
|
|
||||||
mangaId,
|
|
||||||
mangaTitle: manga!.title,
|
|
||||||
thumbnailUrl: manga!.thumbnailUrl,
|
|
||||||
chapterId: cc.chapter.id,
|
|
||||||
chapterName: cc.chapter.name,
|
|
||||||
pageNumber: cc.resumePage,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
openReaderForChapter(cc.chapter, manga)
|
openReaderForChapter(cc.chapter, manga)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -472,7 +467,7 @@
|
|||||||
async function toggleCategory(cat: Category) {
|
async function toggleCategory(cat: Category) {
|
||||||
const inCat = mangaCategories.some(c => c.id === cat.id)
|
const inCat = mangaCategories.some(c => c.id === cat.id)
|
||||||
try {
|
try {
|
||||||
await updateMangaCategories(mangaId, inCat ? [] : [cat.id], inCat ? [cat.id] : [])
|
await updateMangaCategories(String(mangaId), inCat ? [] : [cat.id], inCat ? [cat.id] : [])
|
||||||
if (!inCat && !manga?.inLibrary) {
|
if (!inCat && !manga?.inLibrary) {
|
||||||
await updateManga(mangaId, { inLibrary: true }).catch(console.error)
|
await updateManga(mangaId, { inLibrary: true }).catch(console.error)
|
||||||
if (manga) { manga = { ...manga, inLibrary: true }; seriesState.setActiveManga(manga) }
|
if (manga) { manga = { ...manga, inLibrary: true }; seriesState.setActiveManga(manga) }
|
||||||
@@ -485,7 +480,7 @@
|
|||||||
if (!name) return
|
if (!name) return
|
||||||
try {
|
try {
|
||||||
const cat = await createCategoryReq(name)
|
const cat = await createCategoryReq(name)
|
||||||
await updateMangaCategories(mangaId, [cat.id], [])
|
await updateMangaCategories(String(mangaId), [cat.id], [])
|
||||||
if (!manga?.inLibrary) {
|
if (!manga?.inLibrary) {
|
||||||
await updateManga(mangaId, { inLibrary: true }).catch(console.error)
|
await updateManga(mangaId, { inLibrary: true }).catch(console.error)
|
||||||
if (manga) { manga = { ...manga, inLibrary: true }; seriesState.setActiveManga(manga) }
|
if (manga) { manga = { ...manga, inLibrary: true }; seriesState.setActiveManga(manga) }
|
||||||
@@ -514,7 +509,7 @@
|
|||||||
{loadingLinkList}
|
{loadingLinkList}
|
||||||
{mangaCategories}
|
{mangaCategories}
|
||||||
{togglingLibrary}
|
{togglingLibrary}
|
||||||
onRead={handleContinue}
|
onRead={(ch) => handleContinue(ch)}
|
||||||
onToggleLibrary={toggleLibrary}
|
onToggleLibrary={toggleLibrary}
|
||||||
onDeleteAll={deleteAllDownloads}
|
onDeleteAll={deleteAllDownloads}
|
||||||
onMigrateOpen={() => migrateOpen = true}
|
onMigrateOpen={() => migrateOpen = true}
|
||||||
@@ -526,15 +521,13 @@
|
|||||||
onGenreClick={(genre) => goto(`/browse?genre=${encodeURIComponent(genre)}`)}
|
onGenreClick={(genre) => goto(`/browse?genre=${encodeURIComponent(genre)}`)}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<div class="list-wrap">
|
<div class="list-wrap" bind:this={chapterListEl}>
|
||||||
<SeriesActions
|
<SeriesActions
|
||||||
{chapters}
|
{chapters}
|
||||||
{sortedChapters}
|
{sortedChapters}
|
||||||
sortMode={get('sortMode')}
|
sortMode={seriesState.settings.chapterSortMode}
|
||||||
sortDir={get('sortDir')}
|
sortDir={seriesState.settings.chapterSortDir}
|
||||||
{viewMode}
|
{viewMode}
|
||||||
{chapterPage}
|
|
||||||
{totalPages}
|
|
||||||
{downloadedCount}
|
{downloadedCount}
|
||||||
{totalCount}
|
{totalCount}
|
||||||
{deletingAll}
|
{deletingAll}
|
||||||
@@ -564,8 +557,8 @@
|
|||||||
onSetScanlatorFilter={(v) => set('scanlatorFilter', v)}
|
onSetScanlatorFilter={(v) => set('scanlatorFilter', v)}
|
||||||
onSetScanlatorBlacklist={(v) => set('scanlatorBlacklist', v)}
|
onSetScanlatorBlacklist={(v) => set('scanlatorBlacklist', v)}
|
||||||
onSetScanlatorForce={(v) => set('scanlatorForce', v)}
|
onSetScanlatorForce={(v) => set('scanlatorForce', v)}
|
||||||
onSortModeChange={(v) => set('sortMode', v)}
|
onSortModeChange={(v) => updateSettings({ chapterSortMode: v })}
|
||||||
onSortDirChange={(v) => set('sortDir', v)}
|
onSortDirChange={(v) => updateSettings({ chapterSortDir: v })}
|
||||||
onOpenFolder={() => manga && openMangaFolder(manga)}
|
onOpenFolder={() => manga && openMangaFolder(manga)}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
@@ -578,12 +571,12 @@
|
|||||||
{enqueueing}
|
{enqueueing}
|
||||||
{chapterPage}
|
{chapterPage}
|
||||||
{totalPages}
|
{totalPages}
|
||||||
bind:scrollEl={chapterListEl}
|
|
||||||
onOpen={openReaderWithAhead}
|
onOpen={openReaderWithAhead}
|
||||||
onToggleSelect={toggleSelect}
|
onToggleSelect={toggleSelect}
|
||||||
onEnqueue={enqueue}
|
onEnqueue={enqueue}
|
||||||
onDeleteDownload={deleteDownloaded}
|
onDeleteDownload={deleteDownloaded}
|
||||||
onPageChange={(p) => chapterPage = p}
|
onPageChange={(p) => chapterPage = p}
|
||||||
|
onPageSizeChange={(n) => { chaptersPerPage = n; chapterPage = Math.min(chapterPage, Math.ceil(sortedChapters.length / n) || 1) }}
|
||||||
{buildCtxItems}
|
{buildCtxItems}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -163,7 +163,7 @@
|
|||||||
<Play size={12} weight="fill" />
|
<Play size={12} weight="fill" />
|
||||||
{continueChapter.type === 'reread' ? 'Read again'
|
{continueChapter.type === 'reread' ? 'Read again'
|
||||||
: continueChapter.type === 'start' ? 'Start reading'
|
: continueChapter.type === 'start' ? 'Start reading'
|
||||||
: `Continue · Ch.${continueChapter.chapter.chapterNumber}${continueChapter.resumePage ? ` p.${continueChapter.resumePage}` : ''}`}
|
: `Continue · Ch.${continueChapter.chapter.chapterNumber}`}
|
||||||
</button>
|
</button>
|
||||||
{/if}
|
{/if}
|
||||||
<div class="actions">
|
<div class="actions">
|
||||||
|
|||||||
@@ -1,19 +0,0 @@
|
|||||||
import { settingsState, updateSettings } from '$lib/state/settings.svelte'
|
|
||||||
import { DEFAULT_MANGA_PREFS } from '$lib/types/settings'
|
|
||||||
import type { MangaPrefs } from '$lib/types/settings'
|
|
||||||
|
|
||||||
export { DEFAULT_MANGA_PREFS } from '$lib/types/settings'
|
|
||||||
|
|
||||||
export function getPref<K extends keyof MangaPrefs>(mangaId: number, key: K): MangaPrefs[K] {
|
|
||||||
const prefs = settingsState.settings.mangaPrefs?.[mangaId] ?? {}
|
|
||||||
return (prefs[key] ?? DEFAULT_MANGA_PREFS[key]) as MangaPrefs[K]
|
|
||||||
}
|
|
||||||
|
|
||||||
export function setPref<K extends keyof MangaPrefs>(mangaId: number, key: K, value: MangaPrefs[K]) {
|
|
||||||
updateSettings({
|
|
||||||
mangaPrefs: {
|
|
||||||
...settingsState.settings.mangaPrefs,
|
|
||||||
[mangaId]: { ...(settingsState.settings.mangaPrefs?.[mangaId] ?? {}), [key]: value },
|
|
||||||
},
|
|
||||||
})
|
|
||||||
}
|
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { X } from "phosphor-svelte";
|
import { X } from "phosphor-svelte";
|
||||||
import { getPref, setPref } from "$lib/components/series/lib/mangaPrefs";
|
import { getPref, setPref } from "$lib/state/series.svelte";
|
||||||
import { settingsState } from "$lib/state/settings.svelte";
|
import { settingsState } from "$lib/state/settings.svelte";
|
||||||
import { libraryState } from "$lib/state/library.svelte";
|
import { libraryState } from "$lib/state/library.svelte";
|
||||||
import { resolvedCover } from "$lib/core/cover/coverResolver";
|
import { resolvedCover } from "$lib/core/cover/coverResolver";
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { X, CaretLeft, CaretRight, CircleNotch } from "phosphor-svelte";
|
import { X, CaretLeft, CaretRight, CircleNotch } from "phosphor-svelte";
|
||||||
import { setPref } from "$lib/components/series/lib/mangaPrefs";
|
import { setPref } from "$lib/state/series.svelte";
|
||||||
import { coverCandidatesSync, dedupeByImage } from "$lib/core/cover/coverResolver";
|
import { coverCandidatesSync, dedupeByImage } from "$lib/core/cover/coverResolver";
|
||||||
import Thumbnail from "$lib/components/shared/manga/Thumbnail.svelte";
|
import Thumbnail from "$lib/components/shared/manga/Thumbnail.svelte";
|
||||||
import type { Manga } from "$lib/types";
|
import type { Manga } from "$lib/types";
|
||||||
|
|||||||
@@ -129,9 +129,10 @@ export function buildIssueUrl(
|
|||||||
): string {
|
): string {
|
||||||
const base = 'https://github.com/moku-project/Moku/issues/new'
|
const base = 'https://github.com/moku-project/Moku/issues/new'
|
||||||
|
|
||||||
|
const prefix = type === 'bug' ? '[Bug] ' : '[Feature Request] '
|
||||||
const common = {
|
const common = {
|
||||||
template: type === 'bug' ? 'bug_report.yml' : 'feature_request.yml',
|
template: type === 'bug' ? 'bug_report.yml' : 'feature_request.yml',
|
||||||
title,
|
title: title.startsWith(prefix) ? title : `${prefix}${title}`,
|
||||||
environment: buildEnvironmentBlock(serverVersion),
|
environment: buildEnvironmentBlock(serverVersion),
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -149,6 +150,10 @@ export function buildIssueUrl(
|
|||||||
alternatives: (fields as FeatureFields).alternatives,
|
alternatives: (fields as FeatureFields).alternatives,
|
||||||
}
|
}
|
||||||
|
|
||||||
const params = new URLSearchParams({ ...common, ...specific })
|
const merged: Record<string, string> = {}
|
||||||
|
for (const [k, v] of Object.entries({ ...common, ...specific })) {
|
||||||
|
if (v !== undefined) merged[k] = v
|
||||||
|
}
|
||||||
|
const params = new URLSearchParams(merged)
|
||||||
return `${base}?${params.toString()}`
|
return `${base}?${params.toString()}`
|
||||||
}
|
}
|
||||||
@@ -54,7 +54,7 @@
|
|||||||
const name = newFolderName.trim()
|
const name = newFolderName.trim()
|
||||||
if (!name) return
|
if (!name) return
|
||||||
try {
|
try {
|
||||||
const cat = await getAdapter().createCategory({ name })
|
const cat = await getAdapter().createCategory(name)
|
||||||
categories = [...categories, cat]
|
categories = [...categories, cat]
|
||||||
newFolderName = ''
|
newFolderName = ''
|
||||||
} catch (e: any) { catsError = e?.message ?? 'Failed to create folder' }
|
} catch (e: any) { catsError = e?.message ?? 'Failed to create folder' }
|
||||||
@@ -65,7 +65,7 @@
|
|||||||
async function commitEdit() {
|
async function commitEdit() {
|
||||||
if (editingId !== null && editingName.trim()) {
|
if (editingId !== null && editingName.trim()) {
|
||||||
try {
|
try {
|
||||||
await getAdapter().updateCategory({ id: editingId, name: editingName.trim() })
|
await (getAdapter() as any).updateCategory(editingId, { name: editingName.trim() })
|
||||||
categories = categories.map(c => c.id === editingId ? { ...c, name: editingName.trim() } : c)
|
categories = categories.map(c => c.id === editingId ? { ...c, name: editingName.trim() } : c)
|
||||||
} catch (e: any) { catsError = e?.message ?? 'Failed to rename' }
|
} catch (e: any) { catsError = e?.message ?? 'Failed to rename' }
|
||||||
}
|
}
|
||||||
@@ -74,7 +74,7 @@
|
|||||||
|
|
||||||
async function deleteFolder(id: number) {
|
async function deleteFolder(id: number) {
|
||||||
try {
|
try {
|
||||||
await getAdapter().deleteCategory({ id })
|
await getAdapter().deleteCategory(id)
|
||||||
categories = categories.filter(c => c.id !== id)
|
categories = categories.filter(c => c.id !== id)
|
||||||
} catch (e: any) { catsError = e?.message ?? 'Failed to delete folder' }
|
} catch (e: any) { catsError = e?.message ?? 'Failed to delete folder' }
|
||||||
}
|
}
|
||||||
@@ -85,7 +85,7 @@
|
|||||||
const next = !cat[flag]
|
const next = !cat[flag]
|
||||||
categories = categories.map(c => c.id === id ? { ...c, [flag]: next } : c)
|
categories = categories.map(c => c.id === id ? { ...c, [flag]: next } : c)
|
||||||
try {
|
try {
|
||||||
await getAdapter().updateCategories({ ids: [id], patch: { [flag]: next ? 'INCLUDE' : 'EXCLUDE' } })
|
await (getAdapter() as any).updateCategories([id], { [flag]: next ? 'INCLUDE' : 'EXCLUDE' })
|
||||||
} catch (e: any) {
|
} catch (e: any) {
|
||||||
categories = categories.map(c => c.id === id ? { ...c, [flag]: !next } : c)
|
categories = categories.map(c => c.id === id ? { ...c, [flag]: !next } : c)
|
||||||
catsError = e?.message ?? 'Failed to update folder'
|
catsError = e?.message ?? 'Failed to update folder'
|
||||||
@@ -117,7 +117,7 @@
|
|||||||
const optimistic = [...zeroCat, ...reordered.map((c, i) => ({ ...c, order: i + 1 }))]
|
const optimistic = [...zeroCat, ...reordered.map((c, i) => ({ ...c, order: i + 1 }))]
|
||||||
categories = optimistic
|
categories = optimistic
|
||||||
const serverPosition = sToIdx + 1
|
const serverPosition = sToIdx + 1
|
||||||
getAdapter().updateCategoryOrder({ id: fromNumId, position: serverPosition })
|
getAdapter().updateCategoryOrder(fromNumId, serverPosition)
|
||||||
.then((updated: Category[]) => {
|
.then((updated: Category[]) => {
|
||||||
categories = [
|
categories = [
|
||||||
...zeroCat,
|
...zeroCat,
|
||||||
@@ -189,6 +189,7 @@
|
|||||||
{#if isBuiltin || cat}
|
{#if isBuiltin || cat}
|
||||||
<div
|
<div
|
||||||
class="s-folder-row"
|
class="s-folder-row"
|
||||||
|
role="listitem"
|
||||||
class:dragging={dragStrId === id}
|
class:dragging={dragStrId === id}
|
||||||
class:drop-above={dragOverStrId === id && dragStrId !== id && dropPosition === 'above'}
|
class:drop-above={dragOverStrId === id && dragStrId !== id && dropPosition === 'above'}
|
||||||
class:drop-below={dragOverStrId === id && dragStrId !== id && dropPosition === 'below'}
|
class:drop-below={dragOverStrId === id && dragStrId !== id && dropPosition === 'below'}
|
||||||
@@ -205,7 +206,7 @@
|
|||||||
<DotsSixVertical size={14} weight="bold" />
|
<DotsSixVertical size={14} weight="bold" />
|
||||||
</span>
|
</span>
|
||||||
<span class="s-folder-name">{cat?.name ?? 'Completed'}</span>
|
<span class="s-folder-name">{cat?.name ?? 'Completed'}</span>
|
||||||
<span class="s-folder-count">{cat?.mangas?.nodes?.length ?? 0} manga</span>
|
<span class="s-folder-count">{cat?.mangas?.length ?? 0} manga</span>
|
||||||
<span class="s-folder-badge">built-in</span>
|
<span class="s-folder-badge">built-in</span>
|
||||||
<div class="s-folder-actions">
|
<div class="s-folder-actions">
|
||||||
<button class="s-btn-icon" class:muted={hidden} onclick={() => toggleHidden(id)} title={hidden ? 'Show tab in library' : 'Hide tab from library'}>
|
<button class="s-btn-icon" class:muted={hidden} onclick={() => toggleHidden(id)} title={hidden ? 'Show tab in library' : 'Hide tab from library'}>
|
||||||
@@ -235,16 +236,17 @@
|
|||||||
onblur={commitEdit} use:focusInput />
|
onblur={commitEdit} use:focusInput />
|
||||||
<button class="s-btn-icon" onclick={commitEdit} title="Save">✓</button>
|
<button class="s-btn-icon" onclick={commitEdit} title="Save">✓</button>
|
||||||
{:else}
|
{:else}
|
||||||
<div class="s-folder-identity" draggable="true"
|
<div class="s-folder-identity" role="button" tabindex="0" draggable="true"
|
||||||
ondragstart={(e) => onDragStart(e, id)}
|
ondragstart={(e) => onDragStart(e, id)}
|
||||||
ondragend={onDragEnd}>
|
ondragend={onDragEnd}
|
||||||
|
onkeydown={(e) => e.key === 'Enter' && startEdit(cat.id, cat.name)}>
|
||||||
<span class="s-folder-icon">
|
<span class="s-folder-icon">
|
||||||
<FolderSimple size={14} weight="light" />
|
<FolderSimple size={14} weight="light" />
|
||||||
<DotsSixVertical size={14} weight="bold" />
|
<DotsSixVertical size={14} weight="bold" />
|
||||||
</span>
|
</span>
|
||||||
<span class="s-folder-name" onclick={(e) => { e.stopPropagation(); startEdit(cat.id, cat.name) }} title="Click to rename">{cat.name}</span>
|
<button class="s-folder-name" onclick={(e) => { e.stopPropagation(); startEdit(cat.id, cat.name) }} title="Click to rename">{cat.name}</button>
|
||||||
</div>
|
</div>
|
||||||
<span class="s-folder-count">{cat.mangas?.nodes.length ?? 0} manga</span>
|
<span class="s-folder-count">{cat.mangas?.length ?? 0} manga</span>
|
||||||
<div class="s-folder-actions">
|
<div class="s-folder-actions">
|
||||||
<button class="s-btn-icon"
|
<button class="s-btn-icon"
|
||||||
class:active={(settingsState.settings.defaultLibraryCategoryId ?? null) === cat.id}
|
class:active={(settingsState.settings.defaultLibraryCategoryId ?? null) === cat.id}
|
||||||
@@ -332,6 +334,8 @@
|
|||||||
.s-folder-icon {
|
.s-folder-icon {
|
||||||
display: grid;
|
display: grid;
|
||||||
flex-shrink: 0;
|
flex-shrink: 0;
|
||||||
|
overflow: visible;
|
||||||
|
padding: 1px;
|
||||||
}
|
}
|
||||||
.s-folder-icon > :global(*) { grid-area: 1 / 1; transition: opacity 0.12s; }
|
.s-folder-icon > :global(*) { grid-area: 1 / 1; transition: opacity 0.12s; }
|
||||||
.s-folder-icon > :global(*:last-child) { opacity: 0; }
|
.s-folder-icon > :global(*:last-child) { opacity: 0; }
|
||||||
|
|||||||
@@ -17,8 +17,9 @@ interface StoredVault {
|
|||||||
data: string
|
data: string
|
||||||
}
|
}
|
||||||
|
|
||||||
function toB64(buf: ArrayBuffer): string {
|
function toB64(buf: ArrayBuffer | Uint8Array): string {
|
||||||
return btoa(String.fromCharCode(...new Uint8Array(buf)))
|
const bytes = buf instanceof Uint8Array ? buf : new Uint8Array(buf)
|
||||||
|
return btoa(String.fromCharCode(...bytes))
|
||||||
}
|
}
|
||||||
|
|
||||||
function fromB64(s: string): Uint8Array {
|
function fromB64(s: string): Uint8Array {
|
||||||
@@ -29,7 +30,7 @@ async function deriveKey(pin: string, salt: Uint8Array): Promise<CryptoKey> {
|
|||||||
const enc = new TextEncoder()
|
const enc = new TextEncoder()
|
||||||
const keyMat = await crypto.subtle.importKey('raw', enc.encode(pin), 'PBKDF2', false, ['deriveKey'])
|
const keyMat = await crypto.subtle.importKey('raw', enc.encode(pin), 'PBKDF2', false, ['deriveKey'])
|
||||||
return crypto.subtle.deriveKey(
|
return crypto.subtle.deriveKey(
|
||||||
{ name: 'PBKDF2', salt, iterations: SALT_ITERATIONS, hash: 'SHA-256' },
|
{ name: 'PBKDF2', salt: salt.slice(), iterations: SALT_ITERATIONS, hash: 'SHA-256' },
|
||||||
keyMat,
|
keyMat,
|
||||||
{ name: 'AES-GCM', length: 256 },
|
{ name: 'AES-GCM', length: 256 },
|
||||||
false,
|
false,
|
||||||
@@ -74,11 +75,11 @@ export async function unlockVault(pin: string): Promise<VaultPayload | null> {
|
|||||||
if (!stored) return null
|
if (!stored) return null
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const key = await deriveKey(pin, fromB64(stored.salt))
|
const key = await deriveKey(pin, fromB64(stored.salt).slice())
|
||||||
const plain = await crypto.subtle.decrypt(
|
const plain = await crypto.subtle.decrypt(
|
||||||
{ name: 'AES-GCM', iv: fromB64(stored.iv) },
|
{ name: 'AES-GCM', iv: fromB64(stored.iv).slice() },
|
||||||
key,
|
key,
|
||||||
fromB64(stored.data),
|
fromB64(stored.data).slice(),
|
||||||
)
|
)
|
||||||
return JSON.parse(new TextDecoder().decode(plain)) as VaultPayload
|
return JSON.parse(new TextDecoder().decode(plain)) as VaultPayload
|
||||||
} catch {
|
} catch {
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ import { libraryState } from "$lib/state/library.svelte";
|
|||||||
import { addToast } from "$lib/state/notifications.svelte";
|
import { addToast } from "$lib/state/notifications.svelte";
|
||||||
import { seriesState } from "$lib/state/series.svelte";
|
import { seriesState } from "$lib/state/series.svelte";
|
||||||
import type { MangaFilters, MangaMeta } from "$lib/server-adapters/types";
|
import type { MangaFilters, MangaMeta } from "$lib/server-adapters/types";
|
||||||
import type { Manga, Chapter, Category } from "$lib/types";
|
import type { Manga, Category } from "$lib/types";
|
||||||
|
|
||||||
export async function loadLibrary(filters: MangaFilters = { inLibrary: true }) {
|
export async function loadLibrary(filters: MangaFilters = { inLibrary: true }) {
|
||||||
libraryState.loading = true;
|
libraryState.loading = true;
|
||||||
@@ -36,28 +36,12 @@ export async function updateManga(id: number, patch: { inLibrary?: boolean }): P
|
|||||||
if (patch.inLibrary === false) await getAdapter().removeFromLibrary(String(id));
|
if (patch.inLibrary === false) await getAdapter().removeFromLibrary(String(id));
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function loadManga(id: string) {
|
export async function loadManga(id: string): Promise<Manga> {
|
||||||
seriesState.loading = true;
|
return getAdapter().getManga(id);
|
||||||
seriesState.error = null;
|
|
||||||
try {
|
|
||||||
seriesState.current = await getAdapter().getManga(id);
|
|
||||||
} catch (e) {
|
|
||||||
seriesState.error = String(e);
|
|
||||||
} finally {
|
|
||||||
seriesState.loading = false;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function fetchManga(id: string) {
|
export async function fetchManga(id: string): Promise<Manga> {
|
||||||
seriesState.loading = true;
|
return getAdapter().fetchManga(id);
|
||||||
seriesState.error = null;
|
|
||||||
try {
|
|
||||||
seriesState.current = await getAdapter().fetchManga(id);
|
|
||||||
} catch (e) {
|
|
||||||
seriesState.error = String(e);
|
|
||||||
} finally {
|
|
||||||
seriesState.loading = false;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function searchManga(query: string, sourceId?: string) {
|
export async function searchManga(query: string, sourceId?: string) {
|
||||||
@@ -84,12 +68,12 @@ export async function removeFromLibrary(mangaId: string) {
|
|||||||
|
|
||||||
export async function updateMangaMeta(id: string, meta: Partial<MangaMeta>) {
|
export async function updateMangaMeta(id: string, meta: Partial<MangaMeta>) {
|
||||||
await getAdapter().updateMangaMeta(id, meta);
|
await getAdapter().updateMangaMeta(id, meta);
|
||||||
if (String(seriesState.current?.id) === id) await loadManga(id);
|
if (String(seriesState.activeManga?.id) === id) seriesState.setActiveManga(await getAdapter().getManga(id));
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function deleteMangaMeta(id: string, key: string) {
|
export async function deleteMangaMeta(id: string, key: string) {
|
||||||
await getAdapter().deleteMangaMeta(id, key);
|
await getAdapter().deleteMangaMeta(id, key);
|
||||||
if (String(seriesState.current?.id) === id) await loadManga(id);
|
if (String(seriesState.activeManga?.id) === id) seriesState.setActiveManga(await getAdapter().getManga(id));
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function refreshLibrary() {
|
export async function refreshLibrary() {
|
||||||
|
|||||||
@@ -20,9 +20,9 @@ export const GET_CHAPTER = `
|
|||||||
|
|
||||||
export const GET_RECENTLY_UPDATED = `
|
export const GET_RECENTLY_UPDATED = `
|
||||||
query GetRecentlyUpdated {
|
query GetRecentlyUpdated {
|
||||||
chapters(orderBy: FETCHED_AT, orderByType: DESC, first: 300) {
|
chapters(orderBy: FETCHED_AT, orderByType: DESC, first: 300, filter: { inLibrary: { equalTo: true } }) {
|
||||||
nodes {
|
nodes {
|
||||||
id name chapterNumber sourceOrder isRead lastPageRead mangaId fetchedAt
|
id name chapterNumber sourceOrder isRead isDownloaded lastPageRead mangaId fetchedAt
|
||||||
manga { id title thumbnailUrl inLibrary }
|
manga { id title thumbnailUrl inLibrary }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -270,9 +270,9 @@ export class SuwayomiAdapter implements ServerAdapter {
|
|||||||
await this.gql(DELETE_MANGA_META, { mangaId: Number(id), key })
|
await this.gql(DELETE_MANGA_META, { mangaId: Number(id), key })
|
||||||
}
|
}
|
||||||
|
|
||||||
async getChapters(mangaId: string): Promise<Chapter[]> {
|
async getChapters(mangaId: string, signal?: AbortSignal): Promise<Chapter[]> {
|
||||||
const data = await this.gql<{ chapters: { nodes: Record<string, unknown>[] } }>(
|
const data = await this.gql<{ chapters: { nodes: Record<string, unknown>[] } }>(
|
||||||
GET_CHAPTERS, { mangaId: Number(mangaId) }
|
GET_CHAPTERS, { mangaId: Number(mangaId) }, signal
|
||||||
)
|
)
|
||||||
return data.chapters.nodes.map(mapChapter)
|
return data.chapters.nodes.map(mapChapter)
|
||||||
}
|
}
|
||||||
@@ -291,9 +291,9 @@ export class SuwayomiAdapter implements ServerAdapter {
|
|||||||
return data.fetchChapterPages.pages.map((url, index) => ({ index, url }))
|
return data.fetchChapterPages.pages.map((url, index) => ({ index, url }))
|
||||||
}
|
}
|
||||||
|
|
||||||
async fetchChapters(mangaId: string): Promise<Chapter[]> {
|
async fetchChapters(mangaId: string, signal?: AbortSignal): Promise<Chapter[]> {
|
||||||
const data = await this.gql<{ fetchChapters: { chapters: Record<string, unknown>[] } }>(
|
const data = await this.gql<{ fetchChapters: { chapters: Record<string, unknown>[] } }>(
|
||||||
FETCH_CHAPTERS, { mangaId: Number(mangaId) }
|
FETCH_CHAPTERS, { mangaId: Number(mangaId) }, signal
|
||||||
)
|
)
|
||||||
return data.fetchChapters.chapters.map(mapChapter)
|
return data.fetchChapters.chapters.map(mapChapter)
|
||||||
}
|
}
|
||||||
@@ -491,6 +491,21 @@ export class SuwayomiAdapter implements ServerAdapter {
|
|||||||
await this.gql(DELETE_CATEGORY, { id })
|
await this.gql(DELETE_CATEGORY, { id })
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async updateCategory(id: number, patch: { name?: string; includeInUpdate?: string; includeInDownload?: string }): Promise<Category> {
|
||||||
|
const data = await this.gql<{ updateCategory: { category: Record<string, unknown> } }>(
|
||||||
|
UPDATE_CATEGORY, { id, ...patch }
|
||||||
|
)
|
||||||
|
return mapCategory(data.updateCategory.category)
|
||||||
|
}
|
||||||
|
|
||||||
|
async updateCategories(
|
||||||
|
ids: number[],
|
||||||
|
patch: { includeInUpdate?: 'INCLUDE' | 'EXCLUDE'; includeInDownload?: 'INCLUDE' | 'EXCLUDE' },
|
||||||
|
): Promise<void> {
|
||||||
|
// Suwayomi has no bulk-category-patch mutation; fan out individually.
|
||||||
|
await Promise.all(ids.map(id => this.gql(UPDATE_CATEGORY, { id, ...patch })))
|
||||||
|
}
|
||||||
|
|
||||||
async updateCategoryOrder(id: number, position: number): Promise<Category[]> {
|
async updateCategoryOrder(id: number, position: number): Promise<Category[]> {
|
||||||
const data = await this.gql<{ updateCategoryOrder: { categories: Record<string, unknown>[] } }>(
|
const data = await this.gql<{ updateCategoryOrder: { categories: Record<string, unknown>[] } }>(
|
||||||
UPDATE_CATEGORY_ORDER, { id, position }
|
UPDATE_CATEGORY_ORDER, { id, position }
|
||||||
@@ -676,6 +691,10 @@ export class SuwayomiAdapter implements ServerAdapter {
|
|||||||
return []
|
return []
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async startLibraryUpdate(): Promise<void> {
|
||||||
|
await this.gql(UPDATE_LIBRARY)
|
||||||
|
}
|
||||||
|
|
||||||
async stopLibraryUpdate(): Promise<void> {
|
async stopLibraryUpdate(): Promise<void> {
|
||||||
await this.gql(UPDATE_STOP)
|
await this.gql(UPDATE_STOP)
|
||||||
}
|
}
|
||||||
@@ -685,9 +704,11 @@ export class SuwayomiAdapter implements ServerAdapter {
|
|||||||
libraryUpdateStatus: {
|
libraryUpdateStatus: {
|
||||||
jobsInfo: { isRunning: boolean; finishedJobs: number; totalJobs: number }
|
jobsInfo: { isRunning: boolean; finishedJobs: number; totalJobs: number }
|
||||||
}
|
}
|
||||||
|
lastUpdateTimestamp: { timestamp: string } | null
|
||||||
}>(LIBRARY_UPDATE_STATUS)
|
}>(LIBRARY_UPDATE_STATUS)
|
||||||
const { isRunning, finishedJobs, totalJobs } = data.libraryUpdateStatus.jobsInfo
|
const { isRunning, finishedJobs, totalJobs } = data.libraryUpdateStatus.jobsInfo
|
||||||
return { isRunning, finishedJobs, totalJobs }
|
const lastUpdated = data.lastUpdateTimestamp ? Number(data.lastUpdateTimestamp.timestamp) : undefined
|
||||||
|
return { isRunning, finishedJobs, totalJobs, lastUpdated }
|
||||||
}
|
}
|
||||||
|
|
||||||
clearPageCache(chapterId?: number): void {
|
clearPageCache(chapterId?: number): void {
|
||||||
|
|||||||
@@ -134,9 +134,9 @@ export const CREATE_CATEGORY = `
|
|||||||
`
|
`
|
||||||
|
|
||||||
export const UPDATE_CATEGORY = `
|
export const UPDATE_CATEGORY = `
|
||||||
mutation UpdateCategory($id: Int!, $name: String) {
|
mutation UpdateCategory($id: Int!, $name: String, $includeInUpdate: IncludeOrExclude, $includeInDownload: IncludeOrExclude) {
|
||||||
updateCategory(input: { id: $id, patch: { name: $name } }) {
|
updateCategory(input: { id: $id, patch: { name: $name, includeInUpdate: $includeInUpdate, includeInDownload: $includeInDownload } }) {
|
||||||
category { id name order }
|
category { id name order includeInUpdate includeInDownload }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
`
|
`
|
||||||
|
|||||||
@@ -1,126 +1,240 @@
|
|||||||
export type PlatformFeature =
|
import type { DownloadStatus } from '$lib/types/api'
|
||||||
| 'server-management'
|
import type { Manga, Chapter, Extension, Source, Tracker, TrackRecord, Category } from '$lib/types'
|
||||||
| 'biometric-auth'
|
|
||||||
| 'native-window'
|
|
||||||
| 'filesystem'
|
|
||||||
| 'app-updates'
|
|
||||||
| 'discord-rpc'
|
|
||||||
|
|
||||||
export type Platform = 'tauri' | 'capacitor' | 'web'
|
export interface ServerConfig {
|
||||||
|
baseUrl: string
|
||||||
export interface ServerLaunchConfig {
|
credentials?: { username: string; password: string }
|
||||||
binary?: string
|
|
||||||
binaryArgs?: string
|
|
||||||
webUiEnabled?: boolean
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface DiscordAssets {
|
export type ServerStatus = 'connected' | 'disconnected' | 'error'
|
||||||
largeImage?: string
|
|
||||||
largeText?: string
|
export interface MangaFilters {
|
||||||
smallImage?: string
|
inLibrary?: boolean
|
||||||
smallText?: string
|
status?: MangaStatus
|
||||||
|
tags?: string[]
|
||||||
|
unread?: boolean
|
||||||
|
sourceId?: string
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface DiscordButton {
|
export type MangaStatus =
|
||||||
label: string
|
| 'ONGOING'
|
||||||
url: string
|
| 'COMPLETED'
|
||||||
|
| 'LICENSED'
|
||||||
|
| 'PUBLISHING_FINISHED'
|
||||||
|
| 'CANCELLED'
|
||||||
|
| 'ON_HIATUS'
|
||||||
|
|
||||||
|
export interface PaginatedResult<T> {
|
||||||
|
items: T[]
|
||||||
|
hasNextPage: boolean
|
||||||
|
total?: number
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface DiscordPresence {
|
export interface MangaMeta {
|
||||||
state?: string
|
customTitle?: string
|
||||||
details?: string
|
customCover?: string
|
||||||
assets?: DiscordAssets
|
notes?: string
|
||||||
buttons?: DiscordButton[]
|
[key: string]: unknown
|
||||||
timestamps?: { start?: number; end?: number }
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface AppUpdateInfo {
|
export interface Page {
|
||||||
version: string
|
index: number
|
||||||
url: string
|
url: string
|
||||||
notes: string
|
imageData?: string
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface StorageInfo {
|
export interface AboutServer {
|
||||||
manga_bytes: number
|
name: string
|
||||||
total_bytes: number
|
version: string
|
||||||
free_bytes: number
|
buildType: string
|
||||||
path: string
|
buildTime: number
|
||||||
|
github: string
|
||||||
|
discord: string
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface MigrateProgress {
|
export interface AboutWebUI {
|
||||||
done: number
|
channel: string
|
||||||
total: number
|
tag: string
|
||||||
current: string
|
updateTimestamp: number
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface UpdateProgress {
|
export interface DownloadItem {
|
||||||
downloaded: number
|
chapterId: string
|
||||||
total: number | null
|
mangaId: string
|
||||||
|
chapterName: string
|
||||||
|
mangaTitle: string
|
||||||
|
thumbnailUrl?: string
|
||||||
|
progress: number
|
||||||
|
state: 'queued' | 'downloading' | 'finished' | 'error'
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ReleaseInfo {
|
export interface UpdateResult {
|
||||||
tag_name: string
|
mangaId: string
|
||||||
name: string
|
newChapters: number
|
||||||
body: string
|
|
||||||
published_at: string
|
|
||||||
html_url: string
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface PlatformAdapter {
|
export interface LibraryUpdateProgress {
|
||||||
readonly platform: Platform
|
isRunning: boolean
|
||||||
|
finishedJobs: number
|
||||||
|
totalJobs: number
|
||||||
|
lastUpdated?: number
|
||||||
|
}
|
||||||
|
|
||||||
init(): Promise<void>
|
export interface ServerSecurity {
|
||||||
destroy(): Promise<void>
|
authMode: string
|
||||||
isSupported(feature: PlatformFeature): boolean
|
authUsername: string
|
||||||
|
socksProxyEnabled: boolean
|
||||||
|
socksProxyHost: string
|
||||||
|
socksProxyPort: string
|
||||||
|
socksProxyVersion: number
|
||||||
|
socksProxyUsername: string
|
||||||
|
flareSolverrEnabled: boolean
|
||||||
|
flareSolverrUrl: string
|
||||||
|
flareSolverrTimeout: number
|
||||||
|
flareSolverrSessionName: string
|
||||||
|
flareSolverrSessionTtl: number
|
||||||
|
flareSolverrAsResponseFallback: boolean
|
||||||
|
}
|
||||||
|
|
||||||
getAppDir(): Promise<string>
|
export interface SetServerAuthInput {
|
||||||
|
authMode: string
|
||||||
|
authUsername: string
|
||||||
|
authPassword: string
|
||||||
|
}
|
||||||
|
|
||||||
loadStore(key: string): Promise<unknown>
|
export interface SetSocksProxyInput {
|
||||||
saveStore(key: string, value: unknown): Promise<void>
|
socksProxyEnabled: boolean
|
||||||
|
socksProxyHost: string
|
||||||
|
socksProxyPort: string
|
||||||
|
socksProxyVersion: number
|
||||||
|
socksProxyUsername: string
|
||||||
|
socksProxyPassword: string
|
||||||
|
}
|
||||||
|
|
||||||
storeCredential(key: string, value: string): Promise<void>
|
export interface SetFlareSolverrInput {
|
||||||
getCredential(key: string): Promise<string | null>
|
flareSolverrEnabled: boolean
|
||||||
authenticateBiometric(): Promise<boolean>
|
flareSolverrUrl: string
|
||||||
|
flareSolverrTimeout: number
|
||||||
|
flareSolverrSessionName: string
|
||||||
|
flareSolverrSessionTtl: number
|
||||||
|
flareSolverrAsResponseFallback: boolean
|
||||||
|
}
|
||||||
|
|
||||||
readFile(path: string): Promise<Uint8Array>
|
export interface TrackRecordPatch {
|
||||||
writeFile(path: string, data: Uint8Array): Promise<void>
|
status?: number
|
||||||
pickFolder(): Promise<string | null>
|
score?: number
|
||||||
checkPathExists(path: string): Promise<boolean>
|
lastChapterRead?: number
|
||||||
createDirectory(path: string): Promise<void>
|
startDate?: string
|
||||||
openPath(path: string): Promise<void>
|
finishDate?: string
|
||||||
getDefaultDownloadsPath(): Promise<string>
|
private?: boolean
|
||||||
getStorageInfo(downloadsPath: string): Promise<StorageInfo>
|
}
|
||||||
migrateDownloads(src: string, dst: string): Promise<void>
|
|
||||||
getAutoBackupDir(): Promise<string>
|
|
||||||
|
|
||||||
fetchImage(url: string, headers: Record<string, string>): Promise<Blob>
|
export interface RestoreStatus {
|
||||||
|
mangaProgress: number
|
||||||
|
state: string
|
||||||
|
totalManga: number
|
||||||
|
}
|
||||||
|
|
||||||
launchServer(config: ServerLaunchConfig): Promise<void>
|
export interface ValidateBackupResult {
|
||||||
stopServer(): Promise<void>
|
missingSources: { id: string; name: string }[]
|
||||||
getServerStatus(): Promise<'running' | 'stopped' | 'error'>
|
missingTrackers: { name: string }[]
|
||||||
|
}
|
||||||
|
|
||||||
setTitle(title: string): Promise<void>
|
export interface ServerAdapter {
|
||||||
minimize(): Promise<void>
|
connect(config: ServerConfig): Promise<void>
|
||||||
maximize(): Promise<void>
|
getStatus(): Promise<ServerStatus>
|
||||||
close(): Promise<void>
|
getServerUrl(): string
|
||||||
toggleFullscreen(): Promise<void>
|
|
||||||
|
|
||||||
setDiscordPresence(presence: DiscordPresence): Promise<void>
|
getManga(id: string, signal?: AbortSignal): Promise<Manga>
|
||||||
clearDiscordPresence(): Promise<void>
|
getMangaList(filters: MangaFilters): Promise<PaginatedResult<Manga>>
|
||||||
|
getMangasByGenre(filter: Record<string, unknown>, first: number, offset: number, signal?: AbortSignal): Promise<{ items: Manga[]; hasNextPage: boolean; totalCount: number }>
|
||||||
|
searchManga(query: string, sourceId?: string): Promise<Manga[]>
|
||||||
|
searchSource(sourceId: string, query: string, page?: number, signal?: AbortSignal): Promise<PaginatedResult<Manga>>
|
||||||
|
fetchManga(id: string): Promise<Manga>
|
||||||
|
addToLibrary(mangaId: string): Promise<void>
|
||||||
|
removeFromLibrary(mangaId: string): Promise<void>
|
||||||
|
updateMangas(ids: string[], patch: { inLibrary?: boolean }): Promise<void>
|
||||||
|
updateMangaMeta(id: string, meta: Partial<MangaMeta>): Promise<void>
|
||||||
|
deleteMangaMeta(id: string, key: string): Promise<void>
|
||||||
|
|
||||||
getVersion(): Promise<string>
|
getChapters(mangaId: string, signal?: AbortSignal): Promise<Chapter[]>
|
||||||
openExternal(url: string): Promise<void>
|
getChapter(id: string): Promise<Chapter>
|
||||||
checkForAppUpdate(): Promise<AppUpdateInfo | null>
|
getChapterPages(id: string, signal?: AbortSignal): Promise<Page[]>
|
||||||
installAppUpdate(tag: string): Promise<void>
|
fetchChapters(mangaId: string, signal?: AbortSignal): Promise<Chapter[]>
|
||||||
restartApp(): Promise<void>
|
getRecentlyUpdated(): Promise<Chapter[]>
|
||||||
exitApp(): Promise<void>
|
markChapterRead(id: string, read: boolean): Promise<void>
|
||||||
listReleases(): Promise<ReleaseInfo[]>
|
markChaptersRead(ids: string[], read: boolean): Promise<void>
|
||||||
|
updateChaptersProgress(ids: string[], patch: { isRead?: boolean; isBookmarked?: boolean; lastPageRead?: number }): Promise<void>
|
||||||
|
deleteDownloadedChapters(ids: string[]): Promise<void>
|
||||||
|
setChapterMeta(chapterId: string, key: string, value: string): Promise<void>
|
||||||
|
deleteChapterMeta(chapterId: string, key: string): Promise<void>
|
||||||
|
|
||||||
clearMokuCache(): Promise<void>
|
getAboutServer(): Promise<AboutServer>
|
||||||
clearSuwayomiCache(): Promise<void>
|
getAboutWebUI(): Promise<AboutWebUI>
|
||||||
resetSuwayomiData(): Promise<void>
|
|
||||||
|
|
||||||
onUpdateProgress(cb: (p: UpdateProgress) => void): Promise<() => void>
|
getDownloads(): Promise<DownloadItem[]>
|
||||||
onUpdateLaunching(cb: () => void): Promise<() => void>
|
getDownloadStatus(): Promise<DownloadStatus>
|
||||||
onMigrateProgress(cb: (p: MigrateProgress) => void): Promise<() => void>
|
enqueueDownload(chapterId: string): Promise<void>
|
||||||
|
enqueueDownloads(chapterIds: string[]): Promise<void>
|
||||||
|
dequeueDownload(chapterId: string): Promise<void>
|
||||||
|
dequeueDownloads(chapterIds: string[]): Promise<void>
|
||||||
|
reorderDownload(chapterId: string, to: number): Promise<DownloadStatus | null>
|
||||||
|
clearDownloads(): Promise<void>
|
||||||
|
startDownloader(): Promise<DownloadStatus | null>
|
||||||
|
stopDownloader(): Promise<DownloadStatus | null>
|
||||||
|
|
||||||
|
getExtensions(): Promise<Extension[]>
|
||||||
|
installExtension(id: string): Promise<void>
|
||||||
|
uninstallExtension(id: string): Promise<void>
|
||||||
|
updateExtension(id: string): Promise<void>
|
||||||
|
updateExtensions(ids: string[]): Promise<void>
|
||||||
|
installExternalExtension(url: string): Promise<void>
|
||||||
|
getExtensionRepos(): Promise<string[]>
|
||||||
|
setExtensionRepos(repos: string[]): Promise<string[]>
|
||||||
|
|
||||||
|
getSources(): Promise<Source[]>
|
||||||
|
browseSource(sourceId: string, page: number): Promise<PaginatedResult<Manga>>
|
||||||
|
getSourceSettings(sourceId: string): Promise<unknown[]>
|
||||||
|
updateSourcePreference(sourceId: string, position: number, changeType: string, value: unknown): Promise<unknown[]>
|
||||||
|
|
||||||
|
getCategories(): Promise<Category[]>
|
||||||
|
createCategory(name: string): Promise<Category>
|
||||||
|
deleteCategory(id: number): Promise<void>
|
||||||
|
updateCategoryOrder(id: number, position: number): Promise<Category[]>
|
||||||
|
updateMangaCategories(mangaId: string, addTo: number[], removeFrom: number[]): Promise<void>
|
||||||
|
updateMangasCategories(mangaIds: string[], addTo: number[], removeFrom: number[]): Promise<void>
|
||||||
|
updateCategoryManga(categoryId: number): Promise<void>
|
||||||
|
|
||||||
|
getTrackers(): Promise<Tracker[]>
|
||||||
|
getAllTrackerRecords(): Promise<unknown[]>
|
||||||
|
getMangaTrackRecords(mangaId: string): Promise<unknown[]>
|
||||||
|
searchTracker(trackerId: string, query: string): Promise<unknown[]>
|
||||||
|
linkTracker(mangaId: string, trackerId: string, remoteId: string): Promise<void>
|
||||||
|
unlinkTracker(recordId: string): Promise<void>
|
||||||
|
updateTrackRecord(recordId: string, patch: TrackRecordPatch): Promise<TrackRecord>
|
||||||
|
fetchTrackRecord(recordId: string): Promise<TrackRecord>
|
||||||
|
syncTracking(mangaId: string): Promise<void>
|
||||||
|
loginTrackerOAuth(trackerId: string, callbackUrl: string): Promise<void>
|
||||||
|
loginTrackerCredentials(trackerId: string, username: string, password: string): Promise<void>
|
||||||
|
logoutTracker(trackerId: string): Promise<void>
|
||||||
|
|
||||||
|
getServerSecurity(): Promise<ServerSecurity>
|
||||||
|
setServerAuth(input: SetServerAuthInput): Promise<void>
|
||||||
|
setSocksProxy(input: SetSocksProxyInput): Promise<void>
|
||||||
|
setFlareSolverr(input: SetFlareSolverrInput): Promise<void>
|
||||||
|
|
||||||
|
getDownloadsPath(): Promise<{ downloadsPath: string; localSourcePath: string }>
|
||||||
|
setDownloadsPath(path: string): Promise<void>
|
||||||
|
setLocalSourcePath(path: string): Promise<void>
|
||||||
|
createBackup(): Promise<{ url: string }>
|
||||||
|
restoreBackup(file: File): Promise<{ id: string; status: RestoreStatus }>
|
||||||
|
validateBackup(file: File): Promise<ValidateBackupResult>
|
||||||
|
pollRestoreStatus(id: string): Promise<RestoreStatus>
|
||||||
|
clearCachedImages(opts: { cachedPages: boolean; cachedThumbnails: boolean; downloadedThumbnails: boolean }): Promise<void>
|
||||||
|
|
||||||
|
checkForUpdates(mangaIds?: string[]): Promise<UpdateResult[]>
|
||||||
|
startLibraryUpdate(): Promise<void>
|
||||||
|
stopLibraryUpdate(): Promise<void>
|
||||||
|
getLibraryUpdateStatus(): Promise<LibraryUpdateProgress>
|
||||||
|
clearPageCache(chapterId?: number): void
|
||||||
}
|
}
|
||||||
@@ -35,6 +35,8 @@ export const appState = $state({
|
|||||||
history: [] as unknown[],
|
history: [] as unknown[],
|
||||||
toasts: [] as unknown[],
|
toasts: [] as unknown[],
|
||||||
appDir: '',
|
appDir: '',
|
||||||
|
authUser: '',
|
||||||
|
authPass: '',
|
||||||
idleSplash: false,
|
idleSplash: false,
|
||||||
devSplash: false,
|
devSplash: false,
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import { appState } from '$lib/state/app.svelte'
|
|||||||
import { settingsState } from '$lib/state/settings.svelte'
|
import { settingsState } from '$lib/state/settings.svelte'
|
||||||
|
|
||||||
const MAX_ATTEMPTS = 40
|
const MAX_ATTEMPTS = 40
|
||||||
|
const WEB_MAX_ATTEMPTS = 1
|
||||||
const BG_MAX_ATTEMPTS = 120
|
const BG_MAX_ATTEMPTS = 120
|
||||||
|
|
||||||
export const boot = $state({
|
export const boot = $state({
|
||||||
@@ -33,11 +34,8 @@ export async function initPlatform(): Promise<void> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function pinLockEnabled(): boolean {
|
function pinLockEnabled(): boolean {
|
||||||
return (
|
const pin = settingsState.settings.appLockPin
|
||||||
settingsState.settings.appLockEnabled === true &&
|
return typeof pin === 'string' && pin.length >= 4
|
||||||
typeof settingsState.settings.appLockPin === 'string' &&
|
|
||||||
settingsState.settings.appLockPin.length >= 4
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function handleProbeSuccess(gen: number) {
|
function handleProbeSuccess(gen: number) {
|
||||||
@@ -56,6 +54,7 @@ function handleAuthRequired(
|
|||||||
pass: string,
|
pass: string,
|
||||||
) {
|
) {
|
||||||
if (gen !== probeGeneration) return
|
if (gen !== probeGeneration) return
|
||||||
|
if (boot.skipped) return
|
||||||
boot.failed = false
|
boot.failed = false
|
||||||
appState.authMode = authMode
|
appState.authMode = authMode
|
||||||
|
|
||||||
@@ -93,13 +92,6 @@ export async function startProbe(
|
|||||||
const baseUrl = settingsState.settings.serverUrl ?? 'http://127.0.0.1:4567'
|
const baseUrl = settingsState.settings.serverUrl ?? 'http://127.0.0.1:4567'
|
||||||
configureAuth(baseUrl, authMode, user || undefined, pass || undefined)
|
configureAuth(baseUrl, authMode, user || undefined, pass || undefined)
|
||||||
|
|
||||||
if (appState.platform === 'web') {
|
|
||||||
boot.failed = true
|
|
||||||
appState.status = 'error'
|
|
||||||
startBackgroundProbe(gen, authMode, user, pass)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
let tries = 0
|
let tries = 0
|
||||||
|
|
||||||
async function probe() {
|
async function probe() {
|
||||||
@@ -110,7 +102,8 @@ export async function startProbe(
|
|||||||
|
|
||||||
if (result === 'ok') { handleProbeSuccess(gen); return }
|
if (result === 'ok') { handleProbeSuccess(gen); return }
|
||||||
if (result === 'auth_required') { handleAuthRequired(gen, authMode, user, pass); return }
|
if (result === 'auth_required') { handleAuthRequired(gen, authMode, user, pass); return }
|
||||||
if (tries >= MAX_ATTEMPTS) { boot.failed = true; appState.status = 'error'; startBackgroundProbe(gen, authMode, user, pass); return }
|
const maxAttempts = appState.platform === 'tauri' ? MAX_ATTEMPTS : WEB_MAX_ATTEMPTS
|
||||||
|
if (tries >= maxAttempts) { boot.failed = true; appState.status = 'error'; startBackgroundProbe(gen, authMode, user, pass); return }
|
||||||
|
|
||||||
setTimeout(probe, Math.min(500 + tries * 200, 2000))
|
setTimeout(probe, Math.min(500 + tries * 200, 2000))
|
||||||
}
|
}
|
||||||
@@ -191,10 +184,9 @@ export function bypassBoot(
|
|||||||
user = '',
|
user = '',
|
||||||
pass = '',
|
pass = '',
|
||||||
) {
|
) {
|
||||||
const gen = probeGeneration
|
|
||||||
boot.loginRequired = false
|
boot.loginRequired = false
|
||||||
boot.sessionExpired = false
|
boot.sessionExpired = false
|
||||||
boot.skipped = true
|
boot.skipped = true
|
||||||
appState.status = 'ready'
|
appState.status = 'ready'
|
||||||
startBackgroundProbe(gen, authMode, user, pass)
|
startBackgroundProbe(probeGeneration, authMode, user, pass)
|
||||||
}
|
}
|
||||||
@@ -188,7 +188,7 @@ class HistoryStore {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private async _persist() {
|
private async _persist() {
|
||||||
const bookmarks = (await import('$lib/state/reader.svelte')).readerState.bookmarks
|
const bookmarks = (await import('$lib/state/series.svelte')).seriesState.bookmarks
|
||||||
const markers = (await import('$lib/state/reader.svelte')).readerState.markers
|
const markers = (await import('$lib/state/reader.svelte')).readerState.markers
|
||||||
await saveLibrary({
|
await saveLibrary({
|
||||||
sessions: this.sessions,
|
sessions: this.sessions,
|
||||||
|
|||||||
@@ -149,7 +149,7 @@ class LibraryState {
|
|||||||
|
|
||||||
const f = this.tabFilters[tab] ?? {};
|
const f = this.tabFilters[tab] ?? {};
|
||||||
if (f.unread) items = items.filter(m => (m.unreadCount ?? 0) > 0);
|
if (f.unread) items = items.filter(m => (m.unreadCount ?? 0) > 0);
|
||||||
if (f.started) items = items.filter(m => (m.unreadCount ?? 0) > 0 && (m.totalChapters ?? 0) > (m.unreadCount ?? 0));
|
if (f.started) items = items.filter(m => (m.unreadCount ?? 0) > 0 && (m.chapters?.totalCount ?? 0) > (m.unreadCount ?? 0));
|
||||||
if (f.downloaded) items = items.filter(m => (m.downloadCount ?? 0) > 0);
|
if (f.downloaded) items = items.filter(m => (m.downloadCount ?? 0) > 0);
|
||||||
if (f.bookmarked) items = items.filter(m => (m.bookmarkCount ?? 0) > 0);
|
if (f.bookmarked) items = items.filter(m => (m.bookmarkCount ?? 0) > 0);
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import type { Manga, Chapter } from "$lib/types";
|
import type { Manga, Chapter } from "$lib/types";
|
||||||
import type { BookmarkEntry, MarkerEntry, MarkerColor } from "$lib/types/history";
|
import type { MarkerEntry, MarkerColor } from "$lib/types/history";
|
||||||
import type { MangaPrefs, ReaderSettings, ReaderPreset } from "$lib/types/settings";
|
import type { MangaPrefs, ReaderSettings, ReaderPreset } from "$lib/types/settings";
|
||||||
import { settingsState, updateSettings } from "$lib/state/settings.svelte";
|
import { settingsState, updateSettings } from "$lib/state/settings.svelte";
|
||||||
import { seriesState } from "$lib/state/series.svelte";
|
import { seriesState } from "$lib/state/series.svelte";
|
||||||
@@ -39,7 +39,6 @@ class ReaderState {
|
|||||||
|
|
||||||
pageUrls = $state<string[]>([]);
|
pageUrls = $state<string[]>([]);
|
||||||
pageNumber = $state(1);
|
pageNumber = $state(1);
|
||||||
bookmarks = $state<BookmarkEntry[]>([]);
|
|
||||||
markers = $state<MarkerEntry[]>([]);
|
markers = $state<MarkerEntry[]>([]);
|
||||||
|
|
||||||
loading = $state(true);
|
loading = $state(true);
|
||||||
@@ -147,17 +146,6 @@ class ReaderState {
|
|||||||
this.markerEditId = "";
|
this.markerEditId = "";
|
||||||
}
|
}
|
||||||
|
|
||||||
addBookmark(entry: Omit<BookmarkEntry, "savedAt">) {
|
|
||||||
this.bookmarks = [
|
|
||||||
{ ...entry, savedAt: Date.now() },
|
|
||||||
...this.bookmarks.filter(b => b.mangaId !== entry.mangaId),
|
|
||||||
].slice(0, 200);
|
|
||||||
}
|
|
||||||
|
|
||||||
removeBookmark(chapterId: number) {
|
|
||||||
this.bookmarks = this.bookmarks.filter(b => b.chapterId !== chapterId);
|
|
||||||
}
|
|
||||||
|
|
||||||
addMarker(entry: Omit<MarkerEntry, "id" | "createdAt">): string {
|
addMarker(entry: Omit<MarkerEntry, "id" | "createdAt">): string {
|
||||||
const id = Math.random().toString(36).slice(2);
|
const id = Math.random().toString(36).slice(2);
|
||||||
this.markers = [...this.markers, { ...entry, id, createdAt: Date.now() }];
|
this.markers = [...this.markers, { ...entry, id, createdAt: Date.now() }];
|
||||||
|
|||||||
@@ -10,20 +10,18 @@ export type { BookmarkEntry, MarkerEntry, MarkerColor } from '$lib/types/history
|
|||||||
export type { MangaPrefs } from '$lib/types/settings'
|
export type { MangaPrefs } from '$lib/types/settings'
|
||||||
|
|
||||||
export const DEFAULT_MANGA_PREFS: MangaPrefs = {
|
export const DEFAULT_MANGA_PREFS: MangaPrefs = {
|
||||||
sortMode: 'source',
|
autoDownload: false,
|
||||||
sortDir: 'asc',
|
downloadAhead: 0,
|
||||||
preferredScanlator: '',
|
deleteOnRead: false,
|
||||||
scanlatorFilter: [],
|
deleteDelayHours: 0,
|
||||||
scanlatorBlacklist: [],
|
maxKeepChapters: 0,
|
||||||
scanlatorForce: false,
|
pauseUpdates: false,
|
||||||
autoDownload: false,
|
refreshInterval: 'global',
|
||||||
downloadAhead: 0,
|
preferredScanlator: '',
|
||||||
maxKeepChapters: 0,
|
scanlatorFilter: [],
|
||||||
deleteOnRead: false,
|
scanlatorBlacklist: [],
|
||||||
deleteDelayHours: 0,
|
scanlatorForce: false,
|
||||||
pauseUpdates: false,
|
autoDownloadScanlators: [],
|
||||||
refreshInterval: 'global',
|
|
||||||
coverUrl: '',
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const CHAPTER_TTL_MS = 2 * 60 * 1000
|
const CHAPTER_TTL_MS = 2 * 60 * 1000
|
||||||
@@ -36,24 +34,25 @@ class SeriesStore {
|
|||||||
markers = $state<MarkerEntry[]>([])
|
markers = $state<MarkerEntry[]>([])
|
||||||
acknowledgedUpdates = $state<Set<number>>(new Set())
|
acknowledgedUpdates = $state<Set<number>>(new Set())
|
||||||
|
|
||||||
#rawChapters = $state<Map<number, Chapter[]>>(new Map())
|
#rawChapters = $state<Map<number, Chapter[]>>(new Map())
|
||||||
#fetchedAt = new Map<number, number>()
|
#fetchedAt = new Map<number, number>()
|
||||||
#abortCtrls = new Map<number, AbortController>()
|
#abortCtrls = new Map<number, AbortController>()
|
||||||
#loading = $state<Set<number>>(new Set())
|
#loading = $state<Set<number>>(new Set())
|
||||||
#errors = $state<Map<number, string>>(new Map())
|
#errors = $state<Map<number, string>>(new Map())
|
||||||
|
|
||||||
readonly activeChapterList = $derived.by(() => {
|
readonly activeChapterList = $derived.by(() => {
|
||||||
const id = this.activeManga?.id
|
const id = this.activeManga?.id
|
||||||
if (id == null) return []
|
if (id == null) return []
|
||||||
const raw = this.#rawChapters.get(id) ?? []
|
const raw = this.#rawChapters.get(id) ?? []
|
||||||
const prefs = settingsState.settings.mangaPrefs?.[id] ?? {}
|
const prefs = settingsState.settings.mangaPrefs?.[id] ?? {}
|
||||||
|
const globals = settingsState.settings
|
||||||
return buildChapterList(raw, {
|
return buildChapterList(raw, {
|
||||||
sortMode: (prefs.sortMode ?? DEFAULT_MANGA_PREFS.sortMode) as MangaPrefs['sortMode'],
|
sortMode: globals.chapterSortMode,
|
||||||
sortDir: (prefs.sortDir ?? DEFAULT_MANGA_PREFS.sortDir) as MangaPrefs['sortDir'],
|
sortDir: globals.chapterSortDir,
|
||||||
preferredScanlator: (prefs.preferredScanlator ?? DEFAULT_MANGA_PREFS.preferredScanlator) as string,
|
preferredScanlator: (prefs.preferredScanlator ?? DEFAULT_MANGA_PREFS.preferredScanlator),
|
||||||
scanlatorFilter: (prefs.scanlatorFilter ?? DEFAULT_MANGA_PREFS.scanlatorFilter) as string[],
|
scanlatorFilter: (prefs.scanlatorFilter ?? DEFAULT_MANGA_PREFS.scanlatorFilter),
|
||||||
scanlatorBlacklist: (prefs.scanlatorBlacklist ?? DEFAULT_MANGA_PREFS.scanlatorBlacklist) as string[],
|
scanlatorBlacklist: (prefs.scanlatorBlacklist ?? DEFAULT_MANGA_PREFS.scanlatorBlacklist),
|
||||||
scanlatorForce: (prefs.scanlatorForce ?? DEFAULT_MANGA_PREFS.scanlatorForce) as boolean,
|
scanlatorForce: (prefs.scanlatorForce ?? DEFAULT_MANGA_PREFS.scanlatorForce),
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -65,21 +64,21 @@ class SeriesStore {
|
|||||||
return buildChapterList(raw, {
|
return buildChapterList(raw, {
|
||||||
sortMode: 'source',
|
sortMode: 'source',
|
||||||
sortDir: 'asc',
|
sortDir: 'asc',
|
||||||
preferredScanlator: (prefs.preferredScanlator ?? DEFAULT_MANGA_PREFS.preferredScanlator) as string,
|
preferredScanlator: (prefs.preferredScanlator ?? DEFAULT_MANGA_PREFS.preferredScanlator),
|
||||||
scanlatorFilter: (prefs.scanlatorFilter ?? DEFAULT_MANGA_PREFS.scanlatorFilter) as string[],
|
scanlatorFilter: (prefs.scanlatorFilter ?? DEFAULT_MANGA_PREFS.scanlatorFilter),
|
||||||
scanlatorBlacklist: (prefs.scanlatorBlacklist ?? DEFAULT_MANGA_PREFS.scanlatorBlacklist) as string[],
|
scanlatorBlacklist: (prefs.scanlatorBlacklist ?? DEFAULT_MANGA_PREFS.scanlatorBlacklist),
|
||||||
scanlatorForce: (prefs.scanlatorForce ?? DEFAULT_MANGA_PREFS.scanlatorForce) as boolean,
|
scanlatorForce: (prefs.scanlatorForce ?? DEFAULT_MANGA_PREFS.scanlatorForce),
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
chaptersFor(mangaId: number): Chapter[] { return this.#rawChapters.get(mangaId) ?? [] }
|
chaptersFor(mangaId: number): Chapter[] { return this.#rawChapters.get(mangaId) ?? [] }
|
||||||
isLoadingChapters(mangaId: number) { return this.#loading.has(mangaId) }
|
isLoadingChapters(mangaId: number) { return this.#loading.has(mangaId) }
|
||||||
chapterError(mangaId: number) { return this.#errors.get(mangaId) ?? null }
|
chapterError(mangaId: number) { return this.#errors.get(mangaId) ?? null }
|
||||||
|
|
||||||
async loadChapters(mangaId: number, { force = false } = {}): Promise<void> {
|
async loadChapters(mangaId: number, { force = false } = {}): Promise<void> {
|
||||||
const now = Date.now()
|
const now = Date.now()
|
||||||
const stalest = this.#fetchedAt.get(mangaId) ?? 0
|
const stalest = this.#fetchedAt.get(mangaId) ?? 0
|
||||||
const fresh = !force && this.#rawChapters.has(mangaId) && now - stalest < CHAPTER_TTL_MS
|
const fresh = !force && this.#rawChapters.has(mangaId) && now - stalest < CHAPTER_TTL_MS
|
||||||
|
|
||||||
if (fresh) return
|
if (fresh) return
|
||||||
|
|
||||||
@@ -127,13 +126,8 @@ class SeriesStore {
|
|||||||
this.#rawChapters = new Map(this.#rawChapters).set(mangaId, updater(current))
|
this.#rawChapters = new Map(this.#rawChapters).set(mangaId, updater(current))
|
||||||
}
|
}
|
||||||
|
|
||||||
setActiveManga(manga: Manga | null) {
|
setActiveManga(manga: Manga | null) { this.activeManga = manga }
|
||||||
this.activeManga = manga
|
setPreviewManga(manga: Manga | null) { this.previewManga = manga }
|
||||||
}
|
|
||||||
|
|
||||||
setPreviewManga(manga: Manga | null) {
|
|
||||||
this.previewManga = manga
|
|
||||||
}
|
|
||||||
|
|
||||||
openReaderForChapter(chapter: Chapter, manga?: Manga | null) {
|
openReaderForChapter(chapter: Chapter, manga?: Manga | null) {
|
||||||
if (manga !== undefined) this.activeManga = manga
|
if (manga !== undefined) this.activeManga = manga
|
||||||
@@ -189,9 +183,17 @@ class SeriesStore {
|
|||||||
].slice(0, 200)
|
].slice(0, 200)
|
||||||
}
|
}
|
||||||
|
|
||||||
removeBookmark(chapterId: number) { this.bookmarks = this.bookmarks.filter(b => b.chapterId !== chapterId) }
|
/** Sets the single "resume" bookmark for a manga, replacing any bookmark
|
||||||
clearBookmarks() { this.bookmarks = [] }
|
* that exists for that manga in a different chapter. */
|
||||||
getBookmark(chapterId: number) { return this.bookmarks.find(b => b.chapterId === chapterId) }
|
setBookmark(entry: Omit<BookmarkEntry, 'savedAt'>, label?: string) {
|
||||||
|
const other = this.bookmarks.find(b => b.mangaId === entry.mangaId && b.chapterId !== entry.chapterId)
|
||||||
|
if (other) this.removeBookmark(other.chapterId)
|
||||||
|
this.addBookmark(entry, label)
|
||||||
|
}
|
||||||
|
|
||||||
|
removeBookmark(chapterId: number) { this.bookmarks = this.bookmarks.filter(b => b.chapterId !== chapterId) }
|
||||||
|
clearBookmarks() { this.bookmarks = [] }
|
||||||
|
getBookmark(chapterId: number) { return this.bookmarks.find(b => b.chapterId === chapterId) }
|
||||||
|
|
||||||
addMarker(entry: Omit<MarkerEntry, 'id' | 'createdAt'>): string {
|
addMarker(entry: Omit<MarkerEntry, 'id' | 'createdAt'>): string {
|
||||||
const id = Math.random().toString(36).slice(2)
|
const id = Math.random().toString(36).slice(2)
|
||||||
@@ -203,11 +205,11 @@ class SeriesStore {
|
|||||||
this.markers = this.markers.map(m => m.id === id ? { ...m, ...patch, updatedAt: Date.now() } : m)
|
this.markers = this.markers.map(m => m.id === id ? { ...m, ...patch, updatedAt: Date.now() } : m)
|
||||||
}
|
}
|
||||||
|
|
||||||
removeMarker(id: string) { this.markers = this.markers.filter(m => m.id !== id) }
|
removeMarker(id: string) { this.markers = this.markers.filter(m => m.id !== id) }
|
||||||
getMarkersForPage(chapterId: number, page: number) { return this.markers.filter(m => m.chapterId === chapterId && m.pageNumber === page) }
|
getMarkersForPage(chapterId: number, page: number) { return this.markers.filter(m => m.chapterId === chapterId && m.pageNumber === page) }
|
||||||
getMarkersForChapter(chapterId: number) { return this.markers.filter(m => m.chapterId === chapterId) }
|
getMarkersForChapter(chapterId: number) { return this.markers.filter(m => m.chapterId === chapterId) }
|
||||||
getMarkersForManga(mangaId: number) { return this.markers.filter(m => m.mangaId === mangaId) }
|
getMarkersForManga(mangaId: number) { return this.markers.filter(m => m.mangaId === mangaId) }
|
||||||
clearMarkersForManga(mangaId: number) { this.markers = this.markers.filter(m => m.mangaId !== mangaId) }
|
clearMarkersForManga(mangaId: number) { this.markers = this.markers.filter(m => m.mangaId !== mangaId) }
|
||||||
|
|
||||||
get settings() { return settingsState.settings }
|
get settings() { return settingsState.settings }
|
||||||
}
|
}
|
||||||
@@ -215,21 +217,22 @@ class SeriesStore {
|
|||||||
export const seriesState = new SeriesStore()
|
export const seriesState = new SeriesStore()
|
||||||
export const seriesStore = seriesState
|
export const seriesStore = seriesState
|
||||||
|
|
||||||
export function setActiveManga(next: Manga | null) { seriesState.setActiveManga(next) }
|
export function setActiveManga(next: Manga | null) { seriesState.setActiveManga(next) }
|
||||||
export function setPreviewManga(next: Manga | null) { seriesState.setPreviewManga(next) }
|
export function setPreviewManga(next: Manga | null) { seriesState.setPreviewManga(next) }
|
||||||
export function openReaderForChapter(ch: Chapter, manga?: Manga | null) { seriesState.openReaderForChapter(ch, manga) }
|
export function openReaderForChapter(ch: Chapter, manga?: Manga | null) { seriesState.openReaderForChapter(ch, manga) }
|
||||||
export function closeReader() { seriesState.closeReader() }
|
export function closeReader() { seriesState.closeReader() }
|
||||||
export function acknowledgeUpdate(mangaId: number) { seriesState.acknowledgeUpdate(mangaId) }
|
export function acknowledgeUpdate(mangaId: number) { seriesState.acknowledgeUpdate(mangaId) }
|
||||||
export function addBookmark(entry: Omit<BookmarkEntry, 'savedAt'>, label?: string) { seriesState.addBookmark(entry, label) }
|
export function addBookmark(entry: Omit<BookmarkEntry, 'savedAt'>, label?: string) { seriesState.addBookmark(entry, label) }
|
||||||
export function removeBookmark(chapterId: number) { seriesState.removeBookmark(chapterId) }
|
export function setBookmark(entry: Omit<BookmarkEntry, 'savedAt'>, label?: string) { seriesState.setBookmark(entry, label) }
|
||||||
export function clearBookmarks() { seriesState.clearBookmarks() }
|
export function removeBookmark(chapterId: number) { seriesState.removeBookmark(chapterId) }
|
||||||
export function getBookmark(chapterId: number) { return seriesState.getBookmark(chapterId) }
|
export function clearBookmarks() { seriesState.clearBookmarks() }
|
||||||
export function addMarker(entry: Omit<MarkerEntry, 'id' | 'createdAt'>): string { return seriesState.addMarker(entry) }
|
export function getBookmark(chapterId: number) { return seriesState.getBookmark(chapterId) }
|
||||||
export function updateMarker(id: string, patch: Partial<Pick<MarkerEntry, 'note' | 'color'>>) { seriesState.updateMarker(id, patch) }
|
export function addMarker(entry: Omit<MarkerEntry, 'id' | 'createdAt'>): string { return seriesState.addMarker(entry) }
|
||||||
export function removeMarker(id: string) { seriesState.removeMarker(id) }
|
export function updateMarker(id: string, patch: Partial<Pick<MarkerEntry, 'note' | 'color'>>) { seriesState.updateMarker(id, patch) }
|
||||||
export function getMarkersForPage(chapterId: number, page: number) { return seriesState.getMarkersForPage(chapterId, page) }
|
export function removeMarker(id: string) { seriesState.removeMarker(id) }
|
||||||
export function getMarkersForChapter(chapterId: number) { return seriesState.getMarkersForChapter(chapterId) }
|
export function getMarkersForPage(chapterId: number, page: number) { return seriesState.getMarkersForPage(chapterId, page) }
|
||||||
export function getMarkersForManga(mangaId: number) { return seriesState.getMarkersForManga(mangaId) }
|
export function getMarkersForChapter(chapterId: number) { return seriesState.getMarkersForChapter(chapterId) }
|
||||||
export function clearMarkersForManga(mangaId: number) { seriesState.clearMarkersForManga(mangaId) }
|
export function getMarkersForManga(mangaId: number) { return seriesState.getMarkersForManga(mangaId) }
|
||||||
export function getPref<K extends keyof MangaPrefs>(mangaId: number, key: K): MangaPrefs[K] { return seriesState.getPref(mangaId, key) }
|
export function clearMarkersForManga(mangaId: number) { seriesState.clearMarkersForManga(mangaId) }
|
||||||
export function setPref<K extends keyof MangaPrefs>(mangaId: number, key: K, v: MangaPrefs[K]) { seriesState.setPref(mangaId, key, v) }
|
export function getPref<K extends keyof MangaPrefs>(mangaId: number, key: K): MangaPrefs[K] { return seriesState.getPref(mangaId, key) }
|
||||||
|
export function setPref<K extends keyof MangaPrefs>(mangaId: number, key: K, v: MangaPrefs[K]) { seriesState.setPref(mangaId, key, v) }
|
||||||
@@ -7,6 +7,7 @@ export interface Source {
|
|||||||
isNsfw: boolean
|
isNsfw: boolean
|
||||||
isConfigurable: boolean
|
isConfigurable: boolean
|
||||||
supportsLatest: boolean
|
supportsLatest: boolean
|
||||||
|
extension?: { pkgName: string }
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface Extension {
|
export interface Extension {
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
export type { Manga, MangaDetail, Category, ChapterRef } from './manga'
|
export type { Manga, MangaDetail, Category, ChapterRef } from './manga'
|
||||||
export type { Chapter } from './chapter'
|
export type { Chapter } from './chapter'
|
||||||
export type { Extension, Source } from './extension'
|
export type { Extension, Source } from './extension'
|
||||||
export type { Tracker, TrackRecord, TrackerStatus } from './tracking'
|
export type { Tracker, TrackRecord, TrackerStatus } from './tracking'
|
||||||
|
export type { Settings, MangaPrefs, ContentLevel } from './settings'
|
||||||
@@ -49,5 +49,8 @@ export interface Manga {
|
|||||||
firstUnreadChapter?: ChapterRef | null
|
firstUnreadChapter?: ChapterRef | null
|
||||||
highestNumberedChapter?: ChapterRef | null
|
highestNumberedChapter?: ChapterRef | null
|
||||||
|
|
||||||
source?: { id: string; name: string; displayName: string } | null
|
source?: { id: string; name: string; displayName: string; isNsfw?: boolean } | null
|
||||||
}
|
chapters?: { totalCount: number }
|
||||||
|
}
|
||||||
|
|
||||||
|
export type MangaDetail = Manga
|
||||||
+58
-45
@@ -1,7 +1,7 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { onMount } from 'svelte'
|
import { onMount } from 'svelte'
|
||||||
import { page } from '$app/stores'
|
import { page } from '$app/stores'
|
||||||
import { appState, app } from '$lib/state/app.svelte'
|
import { appState, app, type AppStatus } from '$lib/state/app.svelte'
|
||||||
import { boot } from '$lib/state/boot.svelte'
|
import { boot } from '$lib/state/boot.svelte'
|
||||||
import { notifications } from '$lib/state/notifications.svelte'
|
import { notifications } from '$lib/state/notifications.svelte'
|
||||||
import { settingsState, loadSettingsIntoState, updateSettings } from '$lib/state/settings.svelte'
|
import { settingsState, loadSettingsIntoState, updateSettings } from '$lib/state/settings.svelte'
|
||||||
@@ -34,16 +34,23 @@
|
|||||||
|
|
||||||
const isTauri = typeof window !== 'undefined' && '__TAURI_INTERNALS__' in window
|
const isTauri = typeof window !== 'undefined' && '__TAURI_INTERNALS__' in window
|
||||||
|
|
||||||
let splashDismissed = $state(false)
|
appState.status = 'booting' as AppStatus
|
||||||
let themeEditorOpen = $state(false)
|
|
||||||
let themeEditorId = $state<string | null>(null)
|
let splashDismissed = $state(false)
|
||||||
|
let settingsLoaded = $state(false)
|
||||||
|
let themeEditorOpen = $state(false)
|
||||||
|
let themeEditorId = $state<string | null>(null)
|
||||||
|
|
||||||
const splashVisible = $derived(
|
const splashVisible = $derived(
|
||||||
!splashDismissed ||
|
|
||||||
appState.status === 'booting' ||
|
appState.status === 'booting' ||
|
||||||
appState.status === 'locked' ||
|
appState.status === 'locked' ||
|
||||||
appState.status === 'error' ||
|
appState.status === 'error' ||
|
||||||
appState.status === 'auth'
|
appState.status === 'auth' ||
|
||||||
|
(appState.status === 'ready' && !splashDismissed)
|
||||||
|
)
|
||||||
|
|
||||||
|
const splashMode = $derived(
|
||||||
|
appState.status === 'locked' && settingsLoaded ? 'locked' : 'loading'
|
||||||
)
|
)
|
||||||
|
|
||||||
const ringFull = $derived(appState.status === 'ready')
|
const ringFull = $derived(appState.status === 'ready')
|
||||||
@@ -62,51 +69,57 @@
|
|||||||
const readerContainerized = $derived(settingsState.settings.readerContainerized ?? false)
|
const readerContainerized = $derived(settingsState.settings.readerContainerized ?? false)
|
||||||
const strippedLayout = $derived(isReaderRoute && !readerContainerized)
|
const strippedLayout = $derived(isReaderRoute && !readerContainerized)
|
||||||
|
|
||||||
onMount(async () => {
|
onMount(() => {
|
||||||
const { detectAdapter } = await import('$lib/platform-adapters')
|
async function init() {
|
||||||
const { initPlatformService } = await import('$lib/platform-service')
|
const { detectAdapter } = await import('$lib/platform-adapters')
|
||||||
const { loadSettings } = await import('$lib/core/persistence/persist')
|
const { initPlatformService } = await import('$lib/platform-service')
|
||||||
const { startProbe } = await import('$lib/state/boot.svelte')
|
const { loadSettings } = await import('$lib/core/persistence/persist')
|
||||||
|
const { startProbe } = await import('$lib/state/boot.svelte')
|
||||||
|
|
||||||
const adapter = detectAdapter()
|
const adapter = detectAdapter()
|
||||||
initPlatformService(adapter)
|
initPlatformService(adapter)
|
||||||
await adapter.init()
|
await adapter.init()
|
||||||
appState.platform = adapter.platform
|
appState.platform = adapter.platform
|
||||||
appState.version = await platformService.getVersion().catch(() => '')
|
appState.version = await platformService.getVersion().catch(() => '')
|
||||||
appState.appDir = await platformService.getAppDir().catch(() => '')
|
appState.appDir = await platformService.getAppDir().catch(() => '')
|
||||||
|
|
||||||
const persisted = await loadSettings()
|
const persisted = await loadSettings()
|
||||||
const raw = persisted?.settings ?? persisted ?? null
|
const raw = persisted?.settings ?? persisted ?? null
|
||||||
await loadSettingsIntoState(raw)
|
await loadSettingsIntoState(raw)
|
||||||
|
|
||||||
const s = (raw ?? {}) as Record<string, unknown>
|
const s = (raw ?? {}) as Record<string, unknown>
|
||||||
appState.serverUrl = (s.serverUrl as string) ?? ''
|
appState.serverUrl = (s.serverUrl as string) ?? ''
|
||||||
appState.authMode = (s.serverAuthMode as 'NONE' | 'BASIC_AUTH' | 'UI_LOGIN') ?? 'NONE'
|
appState.authMode = (s.serverAuthMode as 'NONE' | 'BASIC_AUTH' | 'UI_LOGIN') ?? 'NONE'
|
||||||
appState.authUser = (s.serverAuthUser as string) ?? ''
|
appState.authUser = (s.serverAuthUser as string) ?? ''
|
||||||
appState.authPass = (s.serverAuthPass as string) ?? ''
|
appState.authPass = (s.serverAuthPass as string) ?? ''
|
||||||
|
|
||||||
applyTheme(
|
settingsLoaded = true
|
||||||
settingsState.settings.theme ?? 'dark',
|
|
||||||
settingsState.settings.customThemes ?? [],
|
|
||||||
)
|
|
||||||
|
|
||||||
if (isTauri && settingsState.settings.autoStartServer) {
|
applyTheme(
|
||||||
platformService.launchServer({
|
settingsState.settings.theme ?? 'dark',
|
||||||
binary: settingsState.settings.serverBinary,
|
settingsState.settings.customThemes ?? [],
|
||||||
binaryArgs: settingsState.settings.serverBinaryArgs,
|
)
|
||||||
webUiEnabled: settingsState.settings.suwayomiWebUI,
|
|
||||||
}).catch(() => {})
|
if (isTauri && settingsState.settings.autoStartServer) {
|
||||||
|
platformService.launchServer({
|
||||||
|
binary: settingsState.settings.serverBinary,
|
||||||
|
binaryArgs: settingsState.settings.serverBinaryArgs,
|
||||||
|
webUiEnabled: settingsState.settings.suwayomiWebUI,
|
||||||
|
}).catch(() => {})
|
||||||
|
}
|
||||||
|
|
||||||
|
startProbe(
|
||||||
|
appState.authMode ?? 'NONE',
|
||||||
|
appState.authUser ?? '',
|
||||||
|
appState.authPass ?? '',
|
||||||
|
isTauri && settingsState.settings.autoStartServer ? 2000 : 100,
|
||||||
|
)
|
||||||
|
|
||||||
|
polling = true
|
||||||
|
pollLoop()
|
||||||
}
|
}
|
||||||
|
|
||||||
startProbe(
|
init()
|
||||||
appState.authMode ?? 'NONE',
|
|
||||||
appState.authUser ?? '',
|
|
||||||
appState.authPass ?? '',
|
|
||||||
isTauri && settingsState.settings.autoStartServer ? 2000 : 100,
|
|
||||||
)
|
|
||||||
|
|
||||||
polling = true
|
|
||||||
pollLoop()
|
|
||||||
|
|
||||||
return () => {
|
return () => {
|
||||||
polling = false
|
polling = false
|
||||||
@@ -191,7 +204,7 @@
|
|||||||
|
|
||||||
{#if splashVisible}
|
{#if splashVisible}
|
||||||
<SplashScreen
|
<SplashScreen
|
||||||
mode={appState.status === 'locked' ? 'locked' : 'loading'}
|
mode={splashMode}
|
||||||
{ringFull}
|
{ringFull}
|
||||||
failed={appState.status === 'error'}
|
failed={appState.status === 'error'}
|
||||||
notConfigured={boot.notConfigured}
|
notConfigured={boot.notConfigured}
|
||||||
|
|||||||
Reference in New Issue
Block a user