mirror of
https://github.com/sbondCo/Watcharr.git
synced 2026-08-07 23:32:09 +00:00
Compare commits
6 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 1e7702b16b | |||
| fa2a4a40d0 | |||
| 203e4a31ef | |||
| 5f2c1977ee | |||
| 5860fd2783 | |||
| 3c2cbf55c6 |
@@ -0,0 +1,3 @@
|
||||
# Docker dev volume
|
||||
container_data/
|
||||
data/
|
||||
+1
-1
@@ -1,7 +1,7 @@
|
||||
#
|
||||
# Backend
|
||||
#
|
||||
FROM golang:1.24-alpine AS server
|
||||
FROM golang:1.22-alpine AS server
|
||||
|
||||
WORKDIR /server
|
||||
COPY server/*.go server/go.* ./
|
||||
|
||||
@@ -54,7 +54,7 @@ Feel free to abuse this demo instance (nicely), which runs on the latest `dev` b
|
||||
|
||||
# Set Up
|
||||
|
||||
[Checkout our documentation](https://watcharr.app/docs/category/installation) for an up to date guide on setup! If you hate manuals, but love docker, this [docker-compose.yml](./docker-compose.yml) file is your friend.
|
||||
[Checkout our documentation](https://watcharr.app/docs/category/installation) for an up to date guide on setup! If you hate manuals, but love docker, this [compose.yml](./compose.yml) file is your friend.
|
||||
|
||||
# Community Made Tools
|
||||
|
||||
|
||||
@@ -0,0 +1,24 @@
|
||||
# This compose file will replace watcharrs
|
||||
# built in UI with a custom one provided in
|
||||
# a named volume (for use with ui-build image).
|
||||
|
||||
services:
|
||||
watcharr:
|
||||
# We dont need to build here too for testing if
|
||||
# a custom ui works, so using latest prod image.
|
||||
image: ghcr.io/sbondco/watcharr:latest
|
||||
container_name: watcharr
|
||||
ports:
|
||||
- 3080:3080
|
||||
volumes:
|
||||
- ./container_data:/data
|
||||
- type: volume
|
||||
source: watcharr-ui
|
||||
target: /ui
|
||||
volume:
|
||||
nocopy: true
|
||||
subpath: build
|
||||
|
||||
volumes:
|
||||
watcharr-ui:
|
||||
external: true
|
||||
@@ -1,5 +1,5 @@
|
||||
---
|
||||
sidebar_position: 2
|
||||
sidebar_position: 1
|
||||
---
|
||||
|
||||
# MyAnimeList
|
||||
|
||||
@@ -1,30 +0,0 @@
|
||||
---
|
||||
sidebar_position: 1
|
||||
---
|
||||
|
||||
# Text File (.txt list)
|
||||
|
||||
:::info Backup?
|
||||
You may consider a backup of your server before starting any import. They are not easily reversible, though we do our best to ensure they are accurate and bug free!
|
||||
:::
|
||||
|
||||
The text file (.txt) import is of an arbitrary format (the one I used for years before creating Watcharr).
|
||||
|
||||
Hopefully it is useful for others with similar files or in scenarios where its the easiest to generate for an import (though if possible, when generating a backup from another service data manually, matching a Watcharr export would enable keeping more data).
|
||||
|
||||
## Format
|
||||
|
||||
Each line is a new entry. The name of the content (show/movie) must be provided, the year can be optionally provided surrounded by brackets. Doesn't support specifying if name is for a show or movie, the importer will only automatically match on full search matches, if there are multiple results, you will be asked to pick the correct one.
|
||||
|
||||
```
|
||||
<name> [(<year>)]
|
||||
```
|
||||
|
||||
## An example
|
||||
|
||||
```
|
||||
The Terminator (1984)
|
||||
Breaking Bad
|
||||
A Fistful of Dollars
|
||||
Reacher (2022)
|
||||
```
|
||||
@@ -1,16 +0,0 @@
|
||||
---
|
||||
sidebar_position: 3
|
||||
---
|
||||
|
||||
# Trakt
|
||||
|
||||
:::info Backup?
|
||||
You may consider a backup of your server before starting any import. They are not easily reversible, though we do our best to ensure they are accurate and bug free!
|
||||
:::
|
||||
|
||||
**Note:** Your Trakt profile must be public _during_ this process. You are free to private it again once it completes.
|
||||
|
||||
1. Provide your Trakt username in the text box.
|
||||
2. Press `Start Import`.
|
||||
|
||||
This will be a long process, possibly a couple hours depending on how large of a Trakt history you have. If you think it has frozen or isn't working, try checking your server logs to see if it is doing anything.
|
||||
@@ -1,16 +0,0 @@
|
||||
---
|
||||
sidebar_position: 0
|
||||
---
|
||||
|
||||
# Watcharr
|
||||
|
||||
:::info Backup?
|
||||
You may consider a backup of your server before starting any import. They are not easily reversible, though we do our best to ensure they are accurate and bug free!
|
||||
:::
|
||||
|
||||
:::warning May not be what you want
|
||||
Watcharr import/export is a feature intended for end users. If you are the server admin, it is recommended that you copy your server files directly to any new server you are creating (it'll be much faster and easier). [See backup guide](/docs/server_config/backup).
|
||||
:::
|
||||
|
||||
1. Provide the Watcharr export file (that you generated from your profile page).
|
||||
2. Review the import table, if everything looks good, start the import.
|
||||
@@ -0,0 +1,104 @@
|
||||
---
|
||||
sidebar_position: 2
|
||||
description: Install and setup with Docker Compose for access through a subpath.
|
||||
---
|
||||
|
||||
# Docker Compose (subpath)
|
||||
|
||||
Install and setup with Docker Compose for access through a subpath.
|
||||
|
||||
:::info Not a great experience at the moment!
|
||||
|
||||
Currently, hosting Watcharr via a subpath on your server is not very easy (unlike hosting under a subdomain). Hopefully this will change in the future, but as a temporary measure to at least allow hosting under a subpath, this method has been provided.
|
||||
|
||||
The following issue will continue to track this: https://github.com/sbondCo/Watcharr/issues/312
|
||||
|
||||
:::
|
||||
|
||||
## Installing
|
||||
|
||||
### Build UI
|
||||
|
||||
First we have to build the frontend with our subpath provided as an environment variable.
|
||||
|
||||
If you don't want to use `/watcharr` as your subpath, replace the value of `WATCHARR_BASE` before running the command.
|
||||
|
||||
```bash
|
||||
docker run -e WATCHARR_BASE=/watcharr -v watcharr-ui:/ui --rm ghcr.io/sbondco/watcharr-ui-build:latest
|
||||
```
|
||||
|
||||
Once the container has finished running the build script, it will exit and remove itself. The `watcharr-ui` volume will contain the built files.
|
||||
|
||||
### Install Watcharr
|
||||
|
||||
Now we can install Watcharr. You can copy the example below to get started:
|
||||
|
||||
```yaml title="compose.yml"
|
||||
services:
|
||||
watcharr:
|
||||
# The :latest tag is used for simplicity, it is recommended
|
||||
# to use an actual version, then when updating check the releases for changelogs.
|
||||
image: ghcr.io/sbondco/watcharr:latest
|
||||
container_name: watcharr
|
||||
ports:
|
||||
- 3080:3080
|
||||
volumes:
|
||||
# Contains all of watcharr data (database & cache)
|
||||
- ./data:/data
|
||||
# Use our volume containing built ui files
|
||||
# instead of default ui included in image.
|
||||
- type: volume
|
||||
source: watcharr-ui
|
||||
target: /ui
|
||||
volume:
|
||||
nocopy: true
|
||||
subpath: build
|
||||
restart: unless-stopped
|
||||
|
||||
volumes:
|
||||
watcharr-ui:
|
||||
external: true
|
||||
```
|
||||
|
||||
:::danger first account
|
||||
|
||||
When **first** running Watcharr, make sure only you have access. The first user created will become admin.
|
||||
|
||||
:::
|
||||
|
||||
You can now start `Watcharr` like so:
|
||||
|
||||
```bash
|
||||
docker compose up -d
|
||||
```
|
||||
|
||||
If you didn't change the ports in the example, the server will be available at [http://localhost:3080/](http://localhost:3080/).
|
||||
|
||||
## Updating
|
||||
|
||||
:::danger Take care
|
||||
|
||||
We try taking care as to not release breaking changes, however it is still recommended that
|
||||
you lookover changelogs before updating!
|
||||
|
||||
Breaking changes are marked at the top of releases: https://github.com/sbondCo/Watcharr/releases
|
||||
|
||||
:::
|
||||
|
||||
1. Update your built ui files by following the [Build UI](#build-ui) step again.
|
||||
|
||||
2. Update the `image` version in your `compose.yml` file.
|
||||
Skip this step if you are using the `latest` tag.
|
||||
|
||||
```yaml
|
||||
# eg. update v1.19.0 to v1.20.0 (or whatever version you are updating to)
|
||||
image: ghcr.io/sbondco/watcharr:v1.19.0
|
||||
```
|
||||
|
||||
3. Pull the new changes and re-create your container:
|
||||
|
||||
```bash
|
||||
docker compose pull && docker compose down && docker compose up -d
|
||||
```
|
||||
|
||||
And that is it!
|
||||
@@ -9,7 +9,7 @@ description: Install and setup with Docker Compose.
|
||||
|
||||
Installing Watcharr with a docker compose file is easy. You can copy the example below to get started:
|
||||
|
||||
```yaml title="docker-compose.yml"
|
||||
```yaml title="compose.yml"
|
||||
services:
|
||||
watcharr:
|
||||
# The :latest tag is used for simplicity, it is recommended
|
||||
@@ -51,7 +51,7 @@ Breaking changes are marked at the top of releases: https://github.com/sbondCo/W
|
||||
|
||||
Updating your server can be done in two steps:
|
||||
|
||||
1. Update the `image` version in your `docker-compose.yml` file.
|
||||
1. Update the `image` version in your `compose.yml` file.
|
||||
Skip this step if you are using the `latest` tag.
|
||||
|
||||
```yaml
|
||||
|
||||
@@ -80,9 +80,6 @@ const config = {
|
||||
position: "left",
|
||||
label: "Docs",
|
||||
},
|
||||
{
|
||||
type: "docsVersionDropdown",
|
||||
},
|
||||
{
|
||||
href: "https://beta.watcharr.app",
|
||||
label: "Demo",
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
This folder is for any _extra_ images that we produce.
|
||||
|
||||
The main watcharr Dockerfile remains in project root.
|
||||
@@ -0,0 +1,16 @@
|
||||
#
|
||||
# Frontend
|
||||
#
|
||||
FROM node:20-alpine AS ui
|
||||
|
||||
COPY ./docker/ui-build/entrypoint.sh /entrypoint.sh
|
||||
RUN ["chmod", "+x", "/entrypoint.sh"]
|
||||
|
||||
WORKDIR /app
|
||||
COPY package*.json vite.config.ts svelte.config.js tsconfig.json ./
|
||||
COPY ./src ./src
|
||||
COPY ./static ./static
|
||||
|
||||
VOLUME /ui
|
||||
|
||||
ENTRYPOINT ["/entrypoint.sh"]
|
||||
@@ -0,0 +1,7 @@
|
||||
Image for building watcharr ui.
|
||||
|
||||
Accepts environment variables:
|
||||
|
||||
- `WATCHARR_BASE`
|
||||
|
||||
https://watcharr.app/docs/installation/docker-compose-subpath
|
||||
@@ -0,0 +1,15 @@
|
||||
# To test prod
|
||||
services:
|
||||
watcharr-ui-build:
|
||||
build:
|
||||
context: ../../
|
||||
dockerfile: ./docker/ui-build/Dockerfile
|
||||
container_name: watcharr-ui-build
|
||||
volumes:
|
||||
- watcharr-ui:/ui
|
||||
environment:
|
||||
WATCHARR_BASE: "/sp"
|
||||
|
||||
volumes:
|
||||
watcharr-ui:
|
||||
name: "watcharr-ui"
|
||||
Executable
+25
@@ -0,0 +1,25 @@
|
||||
#!/bin/sh
|
||||
|
||||
# This is loosely based on the main containers Dockerfile commands.
|
||||
# Just everything needed to get ui built and ready and nothing else.
|
||||
|
||||
set -x
|
||||
|
||||
echo "wuib: Starting build"
|
||||
|
||||
# Install all deps
|
||||
/usr/local/bin/npm install
|
||||
|
||||
# Run build
|
||||
/usr/local/bin/npm run build
|
||||
|
||||
# Remove any existing build files from volume
|
||||
rm -rf /ui/build
|
||||
|
||||
# Move build folder to /ui
|
||||
mv /app/build /ui
|
||||
# Move package.json and lock file to /ui/build for final npm ci call
|
||||
mv /app/package.json /app/package-lock.json /ui/build
|
||||
|
||||
# cd to build files final destination and install dependencies for production
|
||||
cd /ui/build && /usr/local/bin/npm ci --omit=dev --ignore-scripts=true
|
||||
Generated
+311
-400
File diff suppressed because it is too large
Load Diff
+10
-10
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "watcharr",
|
||||
"version": "2.1.0",
|
||||
"version": "2.0.2",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"dev": "vite dev",
|
||||
@@ -15,27 +15,27 @@
|
||||
},
|
||||
"devDependencies": {
|
||||
"@sveltejs/adapter-node": "^5.2.12",
|
||||
"@sveltejs/kit": "^2.21.0",
|
||||
"@sveltejs/kit": "^2.16.0",
|
||||
"@types/papaparse": "^5.3.15",
|
||||
"@typescript-eslint/eslint-plugin": "^8.32.1",
|
||||
"@typescript-eslint/parser": "^8.32.1",
|
||||
"@typescript-eslint/eslint-plugin": "^8.18.2",
|
||||
"@typescript-eslint/parser": "^8.18.2",
|
||||
"@vite-pwa/sveltekit": "^0.6.6",
|
||||
"eslint": "^8.57.0",
|
||||
"eslint-config-prettier": "^10.1.2",
|
||||
"eslint-config-prettier": "^9.1.0",
|
||||
"eslint-plugin-svelte": "^2.45.1",
|
||||
"prettier": "^3.4.2",
|
||||
"prettier-plugin-svelte": "^3.4.0",
|
||||
"sass": "^1.89.0",
|
||||
"prettier-plugin-svelte": "^3.2.6",
|
||||
"sass": "^1.83.0",
|
||||
"svelte": "^5.17.3",
|
||||
"svelte-check": "^4.1.3",
|
||||
"svelte-eslint-parser": "^0.42.0",
|
||||
"svelte-preprocess": "^6.0.3",
|
||||
"typescript": "^5.8.3",
|
||||
"vite": "^6.3.5"
|
||||
"typescript": "^5.7.2",
|
||||
"vite": "^6.0.7"
|
||||
},
|
||||
"type": "module",
|
||||
"dependencies": {
|
||||
"axios": "^1.9.0",
|
||||
"axios": "^1.7.4",
|
||||
"blurhash": "^2.0.5",
|
||||
"papaparse": "^5.4.1"
|
||||
}
|
||||
|
||||
+21
-20
@@ -1,16 +1,16 @@
|
||||
module github.com/sbondCo/Watcharr
|
||||
|
||||
go 1.24
|
||||
go 1.22
|
||||
|
||||
require (
|
||||
github.com/buckket/go-blurhash v1.1.0
|
||||
github.com/gin-contrib/cache v1.3.1
|
||||
github.com/gin-contrib/cors v1.7.5
|
||||
github.com/gin-contrib/cors v1.7.3
|
||||
github.com/gin-gonic/gin v1.10.0
|
||||
github.com/go-co-op/gocron/v2 v2.16.2
|
||||
github.com/golang-jwt/jwt/v5 v5.2.2
|
||||
github.com/go-co-op/gocron/v2 v2.14.2
|
||||
github.com/golang-jwt/jwt/v5 v5.2.1
|
||||
github.com/joho/godotenv v1.5.1
|
||||
golang.org/x/crypto v0.38.0
|
||||
golang.org/x/crypto v0.32.0
|
||||
gopkg.in/natefinch/lumberjack.v2 v2.2.1
|
||||
gorm.io/driver/sqlite v1.5.7
|
||||
gorm.io/gorm v1.25.12
|
||||
@@ -18,23 +18,23 @@ require (
|
||||
|
||||
require (
|
||||
github.com/bradfitz/gomemcache v0.0.0-20230905024940-24af94b03874 // indirect
|
||||
github.com/bytedance/sonic v1.13.2 // indirect
|
||||
github.com/bytedance/sonic/loader v0.2.4 // indirect
|
||||
github.com/cloudwego/base64x v0.1.5 // indirect
|
||||
github.com/gabriel-vasile/mimetype v1.4.8 // indirect
|
||||
github.com/gin-contrib/sse v1.0.0 // indirect
|
||||
github.com/bytedance/sonic v1.12.6 // indirect
|
||||
github.com/bytedance/sonic/loader v0.2.1 // indirect
|
||||
github.com/cloudwego/base64x v0.1.4 // indirect
|
||||
github.com/cloudwego/iasm v0.2.0 // indirect
|
||||
github.com/gabriel-vasile/mimetype v1.4.7 // indirect
|
||||
github.com/gin-contrib/sse v0.1.0 // indirect
|
||||
github.com/go-playground/locales v0.14.1 // indirect
|
||||
github.com/go-playground/universal-translator v0.18.1 // indirect
|
||||
github.com/go-playground/validator/v10 v10.26.0 // indirect
|
||||
github.com/goccy/go-json v0.10.5 // indirect
|
||||
github.com/go-playground/validator/v10 v10.23.0 // indirect
|
||||
github.com/goccy/go-json v0.10.4 // indirect
|
||||
github.com/gomodule/redigo v1.9.2 // indirect
|
||||
github.com/google/go-cmp v0.6.0 // indirect
|
||||
github.com/google/uuid v1.6.0 // indirect
|
||||
github.com/jinzhu/inflection v1.0.0 // indirect
|
||||
github.com/jinzhu/now v1.1.5 // indirect
|
||||
github.com/jonboulle/clockwork v0.5.0 // indirect
|
||||
github.com/jonboulle/clockwork v0.4.0 // indirect
|
||||
github.com/json-iterator/go v1.1.12 // indirect
|
||||
github.com/klauspost/cpuid/v2 v2.2.10 // indirect
|
||||
github.com/klauspost/cpuid/v2 v2.2.9 // indirect
|
||||
github.com/leodido/go-urn v1.4.0 // indirect
|
||||
github.com/mattn/go-isatty v0.0.20 // indirect
|
||||
github.com/mattn/go-sqlite3 v1.14.22 // indirect
|
||||
@@ -46,10 +46,11 @@ require (
|
||||
github.com/robfig/go-cache v0.0.0-20130306151617-9fc39e0dbf62 // indirect
|
||||
github.com/twitchyliquid64/golang-asm v0.15.1 // indirect
|
||||
github.com/ugorji/go/codec v1.2.12 // indirect
|
||||
golang.org/x/arch v0.15.0 // indirect
|
||||
golang.org/x/net v0.38.0 // indirect
|
||||
golang.org/x/sys v0.33.0 // indirect
|
||||
golang.org/x/text v0.25.0 // indirect
|
||||
google.golang.org/protobuf v1.36.6 // indirect
|
||||
golang.org/x/arch v0.12.0 // indirect
|
||||
golang.org/x/exp v0.0.0-20240613232115-7f521ea00fb8 // indirect
|
||||
golang.org/x/net v0.33.0 // indirect
|
||||
golang.org/x/sys v0.29.0 // indirect
|
||||
golang.org/x/text v0.21.0 // indirect
|
||||
google.golang.org/protobuf v1.36.1 // indirect
|
||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||
)
|
||||
|
||||
+39
-38
@@ -2,41 +2,42 @@ github.com/bradfitz/gomemcache v0.0.0-20230905024940-24af94b03874 h1:N7oVaKyGp8b
|
||||
github.com/bradfitz/gomemcache v0.0.0-20230905024940-24af94b03874/go.mod h1:r5xuitiExdLAJ09PR7vBVENGvp4ZuTBeWTGtxuX3K+c=
|
||||
github.com/buckket/go-blurhash v1.1.0 h1:X5M6r0LIvwdvKiUtiNcRL2YlmOfMzYobI3VCKCZc9Do=
|
||||
github.com/buckket/go-blurhash v1.1.0/go.mod h1:aT2iqo5W9vu9GpyoLErKfTHwgODsZp3bQfXjXJUxNb8=
|
||||
github.com/bytedance/sonic v1.13.2 h1:8/H1FempDZqC4VqjptGo14QQlJx8VdZJegxs6wwfqpQ=
|
||||
github.com/bytedance/sonic v1.13.2/go.mod h1:o68xyaF9u2gvVBuGHPlUVCy+ZfmNNO5ETf1+KgkJhz4=
|
||||
github.com/bytedance/sonic v1.12.6 h1:/isNmCUF2x3Sh8RAp/4mh4ZGkcFAX/hLrzrK3AvpRzk=
|
||||
github.com/bytedance/sonic v1.12.6/go.mod h1:B8Gt/XvtZ3Fqj+iSKMypzymZxw/FVwgIGKzMzT9r/rk=
|
||||
github.com/bytedance/sonic/loader v0.1.1/go.mod h1:ncP89zfokxS5LZrJxl5z0UJcsk4M4yY2JpfqGeCtNLU=
|
||||
github.com/bytedance/sonic/loader v0.2.4 h1:ZWCw4stuXUsn1/+zQDqeE7JKP+QO47tz7QCNan80NzY=
|
||||
github.com/bytedance/sonic/loader v0.2.4/go.mod h1:N8A3vUdtUebEY2/VQC0MyhYeKUFosQU6FxH2JmUe6VI=
|
||||
github.com/cloudwego/base64x v0.1.5 h1:XPciSp1xaq2VCSt6lF0phncD4koWyULpl5bUxbfCyP4=
|
||||
github.com/cloudwego/base64x v0.1.5/go.mod h1:0zlkT4Wn5C6NdauXdJRhSKRlJvmclQ1hhJgA0rcu/8w=
|
||||
github.com/bytedance/sonic/loader v0.2.1 h1:1GgorWTqf12TA8mma4DDSbaQigE2wOgQo7iCjjJv3+E=
|
||||
github.com/bytedance/sonic/loader v0.2.1/go.mod h1:ncP89zfokxS5LZrJxl5z0UJcsk4M4yY2JpfqGeCtNLU=
|
||||
github.com/cloudwego/base64x v0.1.4 h1:jwCgWpFanWmN8xoIUHa2rtzmkd5J2plF/dnLS6Xd/0Y=
|
||||
github.com/cloudwego/base64x v0.1.4/go.mod h1:0zlkT4Wn5C6NdauXdJRhSKRlJvmclQ1hhJgA0rcu/8w=
|
||||
github.com/cloudwego/iasm v0.2.0 h1:1KNIy1I1H9hNNFEEH3DVnI4UujN+1zjpuk6gwHLTssg=
|
||||
github.com/cloudwego/iasm v0.2.0/go.mod h1:8rXZaNYT2n95jn+zTI1sDr+IgcD2GVs0nlbbQPiEFhY=
|
||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/gabriel-vasile/mimetype v1.4.8 h1:FfZ3gj38NjllZIeJAmMhr+qKL8Wu+nOoI3GqacKw1NM=
|
||||
github.com/gabriel-vasile/mimetype v1.4.8/go.mod h1:ByKUIKGjh1ODkGM1asKUbQZOLGrPjydw3hYPU2YU9t8=
|
||||
github.com/gabriel-vasile/mimetype v1.4.7 h1:SKFKl7kD0RiPdbht0s7hFtjl489WcQ1VyPW8ZzUMYCA=
|
||||
github.com/gabriel-vasile/mimetype v1.4.7/go.mod h1:GDlAgAyIRT27BhFl53XNAFtfjzOkLaF35JdEG0P7LtU=
|
||||
github.com/gin-contrib/cache v1.3.1 h1:EWjkOaLocs5fGt9feQaI7rt1GZbDyatFXEUh2/s3ZI8=
|
||||
github.com/gin-contrib/cache v1.3.1/go.mod h1:6Tme0p3QEF/Ck/KUcq7h/OAqZvUDjHRH1DtQbNgfIX0=
|
||||
github.com/gin-contrib/cors v1.7.5 h1:cXC9SmofOrRg0w9PigwGlHG3ztswH6bqq4vJVXnvYMk=
|
||||
github.com/gin-contrib/cors v1.7.5/go.mod h1:4q3yi7xBEDDWKapjT2o1V7mScKDDr8k+jZ0fSquGoy0=
|
||||
github.com/gin-contrib/sse v1.0.0 h1:y3bT1mUWUxDpW4JLQg/HnTqV4rozuW4tC9eFKTxYI9E=
|
||||
github.com/gin-contrib/sse v1.0.0/go.mod h1:zNuFdwarAygJBht0NTKiSi3jRf6RbqeILZ9Sp6Slhe0=
|
||||
github.com/gin-contrib/cors v1.7.3 h1:hV+a5xp8hwJoTw7OY+a70FsL8JkVVFTXw9EcfrYUdns=
|
||||
github.com/gin-contrib/cors v1.7.3/go.mod h1:M3bcKZhxzsvI+rlRSkkxHyljJt1ESd93COUvemZ79j4=
|
||||
github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE=
|
||||
github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI=
|
||||
github.com/gin-gonic/gin v1.10.0 h1:nTuyha1TYqgedzytsKYqna+DfLos46nTv2ygFy86HFU=
|
||||
github.com/gin-gonic/gin v1.10.0/go.mod h1:4PMNQiOhvDRa013RKVbsiNwoyezlm2rm0uX/T7kzp5Y=
|
||||
github.com/go-co-op/gocron/v2 v2.16.2 h1:r08P663ikXiulLT9XaabkLypL/W9MoCIbqgQoAutyX4=
|
||||
github.com/go-co-op/gocron/v2 v2.16.2/go.mod h1:4YTLGCCAH75A5RlQ6q+h+VacO7CgjkgP0EJ+BEOXRSI=
|
||||
github.com/go-co-op/gocron/v2 v2.14.2 h1:S6CbI7MVfD3S/aPJNLoSg2YcGyEqzEMwUopDejuT4Oc=
|
||||
github.com/go-co-op/gocron/v2 v2.14.2/go.mod h1:ZF70ZwEqz0OO4RBXE1sNxnANy/zvwLcattWEFsqpKig=
|
||||
github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s=
|
||||
github.com/go-playground/assert/v2 v2.2.0/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4=
|
||||
github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA=
|
||||
github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY=
|
||||
github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY=
|
||||
github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY=
|
||||
github.com/go-playground/validator/v10 v10.26.0 h1:SP05Nqhjcvz81uJaRfEV0YBSSSGMc/iMaVtFbr3Sw2k=
|
||||
github.com/go-playground/validator/v10 v10.26.0/go.mod h1:I5QpIEbmr8On7W0TktmJAumgzX4CA1XNl4ZmDuVHKKo=
|
||||
github.com/goccy/go-json v0.10.5 h1:Fq85nIqj+gXn/S5ahsiTlK3TmC85qgirsdTP/+DeaC4=
|
||||
github.com/goccy/go-json v0.10.5/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M=
|
||||
github.com/golang-jwt/jwt/v5 v5.2.2 h1:Rl4B7itRWVtYIHFrSNd7vhTiz9UpLdi6gZhZ3wEeDy8=
|
||||
github.com/golang-jwt/jwt/v5 v5.2.2/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk=
|
||||
github.com/go-playground/validator/v10 v10.23.0 h1:/PwmTwZhS0dPkav3cdK9kV1FsAmrL8sThn8IHr/sO+o=
|
||||
github.com/go-playground/validator/v10 v10.23.0/go.mod h1:dbuPbCMFw/DrkbEynArYaCwl3amGuJotoKCe95atGMM=
|
||||
github.com/goccy/go-json v0.10.4 h1:JSwxQzIqKfmFX1swYPpUThQZp/Ka4wzJdK0LWVytLPM=
|
||||
github.com/goccy/go-json v0.10.4/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M=
|
||||
github.com/golang-jwt/jwt/v5 v5.2.1 h1:OuVbFODueb089Lh128TAcimifWaLhJwVflnrgM17wHk=
|
||||
github.com/golang-jwt/jwt/v5 v5.2.1/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk=
|
||||
github.com/gomodule/redigo v1.9.2 h1:HrutZBLhSIU8abiSfW8pj8mPhOyMYjZT/wcA4/L9L9s=
|
||||
github.com/gomodule/redigo v1.9.2/go.mod h1:KsU3hiK/Ay8U42qpaJk+kuNa3C+spxapWpM+ywhcgtw=
|
||||
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
|
||||
@@ -50,13 +51,13 @@ github.com/jinzhu/now v1.1.5 h1:/o9tlHleP7gOFmsnYNz3RGnqzefHA47wQpKrrdTIwXQ=
|
||||
github.com/jinzhu/now v1.1.5/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8=
|
||||
github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0=
|
||||
github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4=
|
||||
github.com/jonboulle/clockwork v0.5.0 h1:Hyh9A8u51kptdkR+cqRpT1EebBwTn1oK9YfGYbdFz6I=
|
||||
github.com/jonboulle/clockwork v0.5.0/go.mod h1:3mZlmanh0g2NDKO5TWZVJAfofYk64M7XN3SzBPjZF60=
|
||||
github.com/jonboulle/clockwork v0.4.0 h1:p4Cf1aMWXnXAUh8lVfewRBx1zaTSYKrKMF2g3ST4RZ4=
|
||||
github.com/jonboulle/clockwork v0.4.0/go.mod h1:xgRqUGwRcjKCO1vbZUEtSLrqKoPSsUpK7fnezOII0kc=
|
||||
github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
|
||||
github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
|
||||
github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg=
|
||||
github.com/klauspost/cpuid/v2 v2.2.10 h1:tBs3QSyvjDyFTq3uoc/9xFpCuOsJQFNPiAhYdw2skhE=
|
||||
github.com/klauspost/cpuid/v2 v2.2.10/go.mod h1:hqwkgyIinND0mEev00jJYCxPNVRVXFQeu1XKlok6oO0=
|
||||
github.com/klauspost/cpuid/v2 v2.2.9 h1:66ze0taIn2H33fBvCkXuv9BmCwDfafmiIVpKV9kKGuY=
|
||||
github.com/klauspost/cpuid/v2 v2.2.9/go.mod h1:rqkxqrZ1EhYM9G+hXH7YdowN5R5RGN6NK4QwQ3WMXF8=
|
||||
github.com/knz/go-libedit v1.10.1/go.mod h1:MZTVkCWyz0oBc7JOWP3wNAzd002ZbM/5hgShxwh4x8M=
|
||||
github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0=
|
||||
github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk=
|
||||
@@ -88,13 +89,11 @@ github.com/rogpeppe/go-internal v1.8.0/go.mod h1:WmiCO8CzOY8rg0OYDC4/i/2WRWAB6po
|
||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
|
||||
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
|
||||
github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA=
|
||||
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
||||
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
|
||||
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
|
||||
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
|
||||
github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
|
||||
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
|
||||
github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI=
|
||||
@@ -103,19 +102,21 @@ github.com/ugorji/go/codec v1.2.12 h1:9LC83zGrHhuUA9l16C9AHXAqEV/2wBQ4nkvumAE65E
|
||||
github.com/ugorji/go/codec v1.2.12/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg=
|
||||
go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
|
||||
go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE=
|
||||
golang.org/x/arch v0.15.0 h1:QtOrQd0bTUnhNVNndMpLHNWrDmYzZ2KDqSrEymqInZw=
|
||||
golang.org/x/arch v0.15.0/go.mod h1:JmwW7aLIoRUKgaTzhkiEFxvcEiQGyOg9BMonBJUS7EE=
|
||||
golang.org/x/crypto v0.38.0 h1:jt+WWG8IZlBnVbomuhg2Mdq0+BBQaHbtqHEFEigjUV8=
|
||||
golang.org/x/crypto v0.38.0/go.mod h1:MvrbAqul58NNYPKnOra203SB9vpuZW0e+RRZV+Ggqjw=
|
||||
golang.org/x/net v0.38.0 h1:vRMAPTMaeGqVhG5QyLJHqNDwecKTomGeqbnfZyKlBI8=
|
||||
golang.org/x/net v0.38.0/go.mod h1:ivrbrMbzFq5J41QOQh0siUuly180yBYtLp+CKbEaFx8=
|
||||
golang.org/x/arch v0.12.0 h1:UsYJhbzPYGsT0HbEdmYcqtCv8UNGvnaL561NnIUvaKg=
|
||||
golang.org/x/arch v0.12.0/go.mod h1:FEVrYAQjsQXMVJ1nsMoVVXPZg6p2JE2mx8psSWTDQys=
|
||||
golang.org/x/crypto v0.32.0 h1:euUpcYgM8WcP71gNpTqQCn6rC2t6ULUPiOzfWaXVVfc=
|
||||
golang.org/x/crypto v0.32.0/go.mod h1:ZnnJkOaASj8g0AjIduWNlq2NRxL0PlBrbKVyZ6V/Ugc=
|
||||
golang.org/x/exp v0.0.0-20240613232115-7f521ea00fb8 h1:yixxcjnhBmY0nkL253HFVIm0JsFHwrHdT3Yh6szTnfY=
|
||||
golang.org/x/exp v0.0.0-20240613232115-7f521ea00fb8/go.mod h1:jj3sYF3dwk5D+ghuXyeI3r5MFf+NT2An6/9dOA95KSI=
|
||||
golang.org/x/net v0.33.0 h1:74SYHlV8BIgHIFC/LrYkOGIwL19eTYXQ5wc6TBuO36I=
|
||||
golang.org/x/net v0.33.0/go.mod h1:HXLR5J+9DxmrqMwG9qjGCxZ+zKXxBru04zlTvWlWuN4=
|
||||
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.33.0 h1:q3i8TbbEz+JRD9ywIRlyRAQbM0qF7hu24q3teo2hbuw=
|
||||
golang.org/x/sys v0.33.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
|
||||
golang.org/x/text v0.25.0 h1:qVyWApTSYLk/drJRO5mDlNYskwQznZmkpV2c8q9zls4=
|
||||
golang.org/x/text v0.25.0/go.mod h1:WEdwpYrmk1qmdHvhkSTNPm3app7v4rsT8F2UD6+VHIA=
|
||||
google.golang.org/protobuf v1.36.6 h1:z1NpPI8ku2WgiWnf+t9wTPsn6eP1L7ksHUlkfLvd9xY=
|
||||
google.golang.org/protobuf v1.36.6/go.mod h1:jduwjTPXsFjZGTmRluh+L6NjiWu7pchiJ2/5YcXBHnY=
|
||||
golang.org/x/sys v0.29.0 h1:TPYlXGxvx1MGTn2GiZDhnjPA9wZzZeGKHHmKhHYvgaU=
|
||||
golang.org/x/sys v0.29.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/text v0.21.0 h1:zyQAAkrwaneQ066sspRyJaG9VNi/YJ1NfzcGB3hZ/qo=
|
||||
golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ=
|
||||
google.golang.org/protobuf v1.36.1 h1:yBPeRvTftaleIgM3PZ/WBIZ7XM/eEYAaEyCwvyjq/gk=
|
||||
google.golang.org/protobuf v1.36.1/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
|
||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
<script lang="ts">
|
||||
import type { PublicUser } from "@/types";
|
||||
import { toBaseUrl } from "./util/url";
|
||||
|
||||
interface Props {
|
||||
users: PublicUser[];
|
||||
@@ -13,7 +14,7 @@
|
||||
<ul>
|
||||
{#each users as user}
|
||||
<li title={user.username}>
|
||||
<a href="/lists/{user.id}/{user.username}">
|
||||
<a href={toBaseUrl(`/lists/${user.id}/${user.username}`)}>
|
||||
<span>{user.username}</span>
|
||||
</a>
|
||||
</li>
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
import { getLatestWatchedInTv } from "./util/helpers";
|
||||
import { notify } from "./util/notify";
|
||||
import { untrack } from "svelte";
|
||||
import { toBaseUrl } from "./util/url";
|
||||
|
||||
interface Props {
|
||||
list: Watched[];
|
||||
@@ -296,7 +297,7 @@
|
||||
<h4 class="norm">
|
||||
Try searching for something you would like to add.
|
||||
</h4>
|
||||
<button onclick={() => goto("/import")}>Import</button>
|
||||
<button onclick={() => goto(toBaseUrl("/import"))}>Import</button>
|
||||
{/if}
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
import Spinner from "../Spinner.svelte";
|
||||
import { clearWatcharrData } from ".";
|
||||
import { goto } from "$app/navigation";
|
||||
import { toBaseUrl } from "../util/url";
|
||||
|
||||
interface Props {
|
||||
onClose: () => void;
|
||||
@@ -34,7 +35,7 @@
|
||||
|
||||
function logout() {
|
||||
clearWatcharrData();
|
||||
goto("/login?noAuto=1");
|
||||
goto(toBaseUrl("/login?noAuto=1"));
|
||||
}
|
||||
|
||||
function proxyLogout() {
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
import { clearWatcharrData } from "../logout";
|
||||
import { notify } from "../util/notify";
|
||||
import AboutModal from "./AboutModal.svelte";
|
||||
import { toBaseUrl } from "../util/url";
|
||||
|
||||
let user = $derived(store.userInfo);
|
||||
let proxyUserLogoutShown = $state(false);
|
||||
@@ -20,23 +21,23 @@
|
||||
return;
|
||||
}
|
||||
clearWatcharrData();
|
||||
goto("/login");
|
||||
goto(toBaseUrl("/login"));
|
||||
}
|
||||
|
||||
function profile() {
|
||||
goto("/profile");
|
||||
goto(toBaseUrl("/profile"));
|
||||
}
|
||||
|
||||
function serverSettings() {
|
||||
goto("/server");
|
||||
goto(toBaseUrl("/server"));
|
||||
}
|
||||
|
||||
function userManagement() {
|
||||
goto("/manage_users");
|
||||
goto(toBaseUrl("/manage_users"));
|
||||
}
|
||||
|
||||
function requestManagement() {
|
||||
goto("/arr_requests");
|
||||
goto(toBaseUrl("/arr_requests"));
|
||||
}
|
||||
|
||||
function shareWatchedList() {
|
||||
@@ -44,7 +45,9 @@
|
||||
const ud = parseTokenPayload();
|
||||
console.log(ud);
|
||||
if (ud?.userId && ud?.username) {
|
||||
const shareLink = `${window.location.origin}/lists/${ud.userId}/${ud.username}`;
|
||||
const shareLink = `${window.location.origin}${toBaseUrl(
|
||||
`/lists/${ud.userId}/${ud.username}`,
|
||||
)}`;
|
||||
navigator.clipboard
|
||||
.writeText(shareLink)
|
||||
.then(() => {
|
||||
@@ -69,7 +72,7 @@
|
||||
}
|
||||
</script>
|
||||
|
||||
<Menu conf={{ width: "140px", arrowRight: "10px" }}>
|
||||
<Menu conf={{ arrowRight: "10px" }}>
|
||||
{#if user?.username}
|
||||
<h5 title={user.username}>Hi {user.username}!</h5>
|
||||
{/if}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
<script lang="ts">
|
||||
import { store } from "@/store.svelte";
|
||||
import Menu from "../Menu.svelte";
|
||||
import { toBaseUrl } from "../util/url";
|
||||
|
||||
interface Props {
|
||||
close: () => {};
|
||||
@@ -15,7 +16,9 @@
|
||||
<div class="list">
|
||||
{#each store.follows as f}
|
||||
<a
|
||||
href="/lists/{f.followedUser.id}/{f.followedUser.username}"
|
||||
href={toBaseUrl(
|
||||
`/lists/${f.followedUser.id}/${f.followedUser.username}`,
|
||||
)}
|
||||
onclick={() => close()}
|
||||
>
|
||||
{f.followedUser.username}
|
||||
|
||||
@@ -18,6 +18,7 @@
|
||||
import PosterRating from "./PosterRating.svelte";
|
||||
import { decode } from "blurhash";
|
||||
import ExtraDetails from "./ExtraDetails.svelte";
|
||||
import { toBaseUrl } from "../util/url";
|
||||
|
||||
interface Props {
|
||||
id?: number | undefined; // Watched list id
|
||||
@@ -72,7 +73,7 @@
|
||||
const poster = media.poster?.path
|
||||
? `${baseURL}/${media.poster.path}`
|
||||
: `https://images.igdb.com/igdb/image/upload/t_cover_big/${media.coverId}.jpg`;
|
||||
const link = media.id ? `/game/${media.id}` : undefined;
|
||||
const link = media.id ? toBaseUrl(`/game/${media.id}`) : undefined;
|
||||
const dateStr = media.firstReleaseDate;
|
||||
const year = dateStr ? new Date(dateStr).getFullYear() : undefined;
|
||||
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
addClassToParent,
|
||||
calculateTransformOrigin,
|
||||
} from "@/lib/util/helpers";
|
||||
import { toBaseUrl } from "../util/url";
|
||||
|
||||
interface Props {
|
||||
id: number | undefined;
|
||||
@@ -24,7 +25,7 @@
|
||||
const poster = path
|
||||
? `https://image.tmdb.org/t/p/w300_and_h450_bestv2${path}`
|
||||
: undefined;
|
||||
const link = id ? `/person/${id}` : undefined;
|
||||
const link = id ? toBaseUrl(`/person/${id}`) : undefined;
|
||||
</script>
|
||||
|
||||
<!-- Quick fix to ignore error, should be fixed -->
|
||||
|
||||
@@ -13,6 +13,7 @@
|
||||
import PosterStatus from "./PosterStatus.svelte";
|
||||
import PosterRating from "./PosterRating.svelte";
|
||||
import ExtraDetails from "./ExtraDetails.svelte";
|
||||
import { toBaseUrl } from "../util/url";
|
||||
|
||||
interface Props {
|
||||
id?: number | undefined; // Watched list id
|
||||
@@ -84,7 +85,7 @@
|
||||
: `https://image.tmdb.org/t/p/w500${media.poster_path}`,
|
||||
);
|
||||
let link = $derived(
|
||||
media.id ? `/${media.media_type}/${media.id}` : undefined,
|
||||
media.id ? toBaseUrl(`/${media.media_type}/${media.id}`) : undefined,
|
||||
);
|
||||
let dateStr = $derived(media.release_date || media.first_air_date);
|
||||
let year = $derived(dateStr ? new Date(dateStr).getFullYear() : undefined);
|
||||
|
||||
+2
-1
@@ -16,6 +16,7 @@ import {
|
||||
import axios from "axios";
|
||||
import { notify, unNotify } from "./notify";
|
||||
import { browser } from "$app/environment";
|
||||
import { toBaseUrl } from "./url";
|
||||
const { MODE } = import.meta.env;
|
||||
|
||||
export const baseURL =
|
||||
@@ -23,7 +24,7 @@ export const baseURL =
|
||||
? browser
|
||||
? `${location.protocol}//${location.hostname}:3080/api`
|
||||
: "http://127.0.0.1:3080/api"
|
||||
: "/api";
|
||||
: toBaseUrl("/api");
|
||||
console.log("api: baseURL constructed:", baseURL);
|
||||
|
||||
/**
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
import { store } from "@/store.svelte";
|
||||
import {
|
||||
UserPermission,
|
||||
type Icon,
|
||||
type MediaType,
|
||||
type TMDBContentCreditsCrew,
|
||||
type Theme,
|
||||
type TokenClaims,
|
||||
type Watched,
|
||||
type WatchedStatus,
|
||||
@@ -292,6 +294,26 @@ export function getOrdinalSuffix(i: number) {
|
||||
return "th";
|
||||
}
|
||||
|
||||
/**
|
||||
* Toggle site wide theme.
|
||||
* @param theme The theme to switch to.
|
||||
* @param updateStore Should the store be updated to new theme?
|
||||
* **If set to `false`, state should be manually updated.**
|
||||
*/
|
||||
export function toggleTheme(theme: Theme, updateStore = true) {
|
||||
if (theme === "dark") {
|
||||
document.documentElement.classList.add("theme-dark");
|
||||
if (updateStore) {
|
||||
store.appTheme = "dark";
|
||||
}
|
||||
} else {
|
||||
document.documentElement.classList.remove("theme-dark");
|
||||
if (updateStore) {
|
||||
store.appTheme = "light";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export function parseTokenPayload(): TokenClaims | undefined {
|
||||
try {
|
||||
const token = localStorage.getItem("token");
|
||||
|
||||
@@ -1,66 +0,0 @@
|
||||
// App Theme logic.
|
||||
|
||||
import { browser } from "$app/environment";
|
||||
import { store } from "@/store.svelte";
|
||||
import type { Theme } from "@/types";
|
||||
|
||||
/**
|
||||
* Utility function to handle media queries for theme preferences.
|
||||
*/
|
||||
|
||||
const prefersDarkThemeQuery: MediaQueryList | undefined = browser
|
||||
? window.matchMedia("(prefers-color-scheme: dark)")
|
||||
: undefined;
|
||||
|
||||
/**
|
||||
* prefersDarkThemeQuery onChange handler.
|
||||
*/
|
||||
function prefersDarkThemeChanged(e: MediaQueryListEvent) {
|
||||
console.info(
|
||||
"prefersDarkThemeChanged: User preferred theme has changed. prefersDark:",
|
||||
e.matches,
|
||||
);
|
||||
document.documentElement.classList.toggle("theme-dark", e.matches);
|
||||
}
|
||||
|
||||
/**
|
||||
* Toggle site wide theme.
|
||||
* @param theme The theme to switch to.
|
||||
* @param updateStore Should the store be updated to new theme?
|
||||
* **If set to `false`, state should be manually updated.**
|
||||
*/
|
||||
export function toggleTheme(theme: Theme, updateStore = true) {
|
||||
if (updateStore) {
|
||||
store.appTheme = theme;
|
||||
}
|
||||
|
||||
switch (theme) {
|
||||
case "dark":
|
||||
document.documentElement.classList.add("theme-dark");
|
||||
break;
|
||||
case "light":
|
||||
document.documentElement.classList.remove("theme-dark");
|
||||
break;
|
||||
case "system":
|
||||
document.documentElement.classList.toggle(
|
||||
"theme-dark",
|
||||
prefersDarkThemeQuery?.matches,
|
||||
);
|
||||
break;
|
||||
}
|
||||
|
||||
if (prefersDarkThemeQuery) {
|
||||
// Always remove first before adding to avoid cases
|
||||
// where we add multiple events at same time.
|
||||
prefersDarkThemeQuery.removeEventListener(
|
||||
"change",
|
||||
prefersDarkThemeChanged,
|
||||
);
|
||||
// If using system theme, add change listener (since
|
||||
// system theme supports live updating when user
|
||||
// preference changes on os or wherever).
|
||||
if (theme === "system") {
|
||||
prefersDarkThemeQuery.addEventListener("change", prefersDarkThemeChanged);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
import { base } from "$app/paths";
|
||||
|
||||
/**
|
||||
* Takes in a `path` and prefixes it with `base`.
|
||||
* This enables support for using base paths to access
|
||||
* the frontend by fixing all absolute paths passed through.
|
||||
*/
|
||||
export function toBaseUrl(path: string) {
|
||||
return base + path;
|
||||
}
|
||||
@@ -1,5 +1,6 @@
|
||||
<script lang="ts">
|
||||
import { afterNavigate, goto } from "$app/navigation";
|
||||
import { base } from "$app/paths";
|
||||
import { page } from "$app/state";
|
||||
import Icon from "@/lib/Icon.svelte";
|
||||
import PageError from "@/lib/PageError.svelte";
|
||||
@@ -12,6 +13,7 @@
|
||||
import SortMenu from "@/lib/nav/SortMenu.svelte";
|
||||
import TagMenu from "@/lib/tag/TagMenu.svelte";
|
||||
import { isTouch } from "@/lib/util/helpers";
|
||||
import { toBaseUrl } from "@/lib/util/url";
|
||||
import { store, defaultSort } from "@/store.svelte";
|
||||
import axios from "axios";
|
||||
import { onMount } from "svelte";
|
||||
@@ -34,7 +36,7 @@
|
||||
|
||||
function handleProfileClick() {
|
||||
if (!localStorage.getItem("token")) {
|
||||
goto("/login");
|
||||
goto(toBaseUrl("/login"));
|
||||
} else {
|
||||
closeAllSubMenus("sub");
|
||||
subMenuShown = !subMenuShown;
|
||||
@@ -43,25 +45,13 @@
|
||||
|
||||
function handleSearch(ev: KeyboardEvent) {
|
||||
if (
|
||||
ev.key === "ContextMenu" ||
|
||||
ev.key === "Home" ||
|
||||
ev.key === "End" ||
|
||||
ev.key === "PageDown" ||
|
||||
ev.key === "PageUp" ||
|
||||
ev.key === "NumLock" ||
|
||||
ev.key === "Escape" ||
|
||||
ev.key === "Tab" ||
|
||||
ev.key === "CapsLock" ||
|
||||
ev.key === "OS" ||
|
||||
ev.key === "ArrowLeft" ||
|
||||
ev.key === "ArrowRight" ||
|
||||
ev.key === "ArrowUp" ||
|
||||
ev.key === "ArrowDown" ||
|
||||
ev.key === "Control" ||
|
||||
ev.key === "Alt" ||
|
||||
ev.key === "AltGraph" ||
|
||||
ev.key === "Shift" ||
|
||||
ev.key === "Meta"
|
||||
ev.key === "ArrowDown"
|
||||
)
|
||||
return;
|
||||
clearTimeout(searchTimeout);
|
||||
@@ -76,7 +66,7 @@
|
||||
// Using autofocus seems to work. Disables after goto runs.
|
||||
// https://github.com/sbondCo/Watcharr/issues/169
|
||||
target.autofocus = true;
|
||||
goto(`/search?q=${encodeURIComponent(query)}`).then(() => {
|
||||
goto(toBaseUrl(`/search?q=${encodeURIComponent(query)}`)).then(() => {
|
||||
// Use mainSearchEl if nav not split, otherwise use ev target.
|
||||
if (
|
||||
!document.body.classList.contains("split-nav") &&
|
||||
@@ -124,7 +114,7 @@
|
||||
store.tags = ts.data;
|
||||
}
|
||||
} else {
|
||||
goto("/login?again=1");
|
||||
goto(toBaseUrl("/login?again=1"));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -174,33 +164,6 @@
|
||||
scroll = window.scrollY;
|
||||
}
|
||||
|
||||
function focusSearch() {
|
||||
try {
|
||||
if (!mainSearchEl) {
|
||||
console.warn("focusSearch: mainSearchEl not defined!");
|
||||
return;
|
||||
}
|
||||
if (document.activeElement === mainSearchEl) {
|
||||
console.debug("focusSearch: mainSearchEl is already focused.");
|
||||
return;
|
||||
}
|
||||
mainSearchEl.focus();
|
||||
} catch (err) {
|
||||
console.error("focusSearch: Failed!", err);
|
||||
}
|
||||
}
|
||||
|
||||
function handleGlobalKeybind(ev: KeyboardEvent) {
|
||||
switch (ev.key.toLowerCase()) {
|
||||
case "s":
|
||||
if (ev.ctrlKey) {
|
||||
ev.preventDefault();
|
||||
focusSearch();
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
afterNavigate(() => {
|
||||
decideOnNavSplit();
|
||||
closeAllSubMenus();
|
||||
@@ -211,12 +174,10 @@
|
||||
decideOnNavSplit();
|
||||
window.addEventListener("resize", decideOnNavSplit);
|
||||
window.document.addEventListener("scroll", docOnScroll);
|
||||
window.document.addEventListener("keydown", handleGlobalKeybind);
|
||||
|
||||
return () => {
|
||||
window.removeEventListener("resize", decideOnNavSplit);
|
||||
window.document.removeEventListener("scroll", docOnScroll);
|
||||
window.document.removeEventListener("keydown", handleGlobalKeybind);
|
||||
};
|
||||
} else {
|
||||
console.error(
|
||||
@@ -228,7 +189,7 @@
|
||||
|
||||
<nav bind:this={navEl}>
|
||||
<div class="wrapper">
|
||||
<a href="/">
|
||||
<a href={toBaseUrl("/")}>
|
||||
<span class="large">Watcharr</span>
|
||||
<span class="small">W</span>
|
||||
</a>
|
||||
@@ -319,7 +280,7 @@
|
||||
{#if tagMenuShown}
|
||||
<TagMenu
|
||||
onTagClick={(tag) => {
|
||||
goto(`/tag/${tag.id}`);
|
||||
goto(toBaseUrl(`/tag/${tag.id}`));
|
||||
tagMenuShown = false;
|
||||
}}
|
||||
showManageBtn={true}
|
||||
@@ -327,7 +288,7 @@
|
||||
{/if}
|
||||
<button
|
||||
class="plain other discover"
|
||||
onclick={() => goto("/discover")}
|
||||
onclick={() => goto(toBaseUrl("/discover"))}
|
||||
use:tooltip={{ text: "Discover", pos: "bot" }}
|
||||
>
|
||||
<Icon i="compass" wh={26} />
|
||||
|
||||
@@ -7,6 +7,7 @@ import axios from "axios";
|
||||
import { baseURL } from "@/lib/util/api";
|
||||
import { notify } from "@/lib/util/notify";
|
||||
import { clearWatcharrData } from "@/lib/logout";
|
||||
import { toBaseUrl } from "@/lib/util/url";
|
||||
|
||||
axios.interceptors.request.use(
|
||||
(config) => {
|
||||
@@ -18,7 +19,7 @@ axios.interceptors.request.use(
|
||||
// Don't require token check if going to auth route (login/register)
|
||||
if (!token && !config.url?.includes("/auth")) {
|
||||
console.error("No token, going to login. Endpoint:", config.url);
|
||||
goto("/login?again=1");
|
||||
goto(toBaseUrl("/login?again=1"));
|
||||
throw new axios.Cancel("No auth token found");
|
||||
}
|
||||
config.headers.set("Authorization", token);
|
||||
@@ -40,7 +41,7 @@ axios.interceptors.response.use(
|
||||
console.error("Recieved 401 response, going to login.");
|
||||
notify({ text: "Request Authorization Failed!", type: "error" });
|
||||
clearWatcharrData();
|
||||
goto("/login?again=1");
|
||||
goto(toBaseUrl("/login?again=1"));
|
||||
}
|
||||
return Promise.reject(error);
|
||||
},
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
import { baseURL } from "@/lib/util/api";
|
||||
import { toRelativeDate } from "@/lib/util/helpers";
|
||||
import { notify } from "@/lib/util/notify";
|
||||
import { toBaseUrl } from "@/lib/util/url";
|
||||
import {
|
||||
type ArrRequestResponse,
|
||||
type TMDBMovieDetails,
|
||||
@@ -94,7 +95,7 @@
|
||||
<h2 class="norm">
|
||||
<a
|
||||
data-sveltekit-preload-data="tap"
|
||||
href={`/${r.content.type}/${r.content.tmdbId}`}
|
||||
href={toBaseUrl(`/${r.content.type}/${r.content.tmdbId}`)}
|
||||
class="plain"
|
||||
>
|
||||
{r.content.title}
|
||||
|
||||
@@ -26,6 +26,7 @@
|
||||
TodoMoviesMovie,
|
||||
} from "@/types";
|
||||
import Icon from "@/lib/Icon.svelte";
|
||||
import { toBaseUrl } from "@/lib/util/url";
|
||||
|
||||
let isDragOver = $state(false);
|
||||
let isLoading = $state(false);
|
||||
@@ -83,7 +84,7 @@
|
||||
data: r.result.toString(),
|
||||
type,
|
||||
};
|
||||
goto("/import/process");
|
||||
goto(toBaseUrl("/import/process"));
|
||||
}
|
||||
},
|
||||
false,
|
||||
@@ -255,7 +256,7 @@
|
||||
data: JSON.stringify(toImport),
|
||||
type: "movary",
|
||||
};
|
||||
goto("/import/process");
|
||||
goto(toBaseUrl("/import/process"));
|
||||
} catch (err) {
|
||||
isLoading = false;
|
||||
notify({ type: "error", text: "Failed to read files!" });
|
||||
@@ -331,7 +332,7 @@
|
||||
data: JSON.stringify(toImport),
|
||||
type: "watcharr",
|
||||
};
|
||||
goto("/import/process");
|
||||
goto(toBaseUrl("/import/process"));
|
||||
} catch (err) {
|
||||
isLoading = false;
|
||||
notify({ type: "error", text: "Failed to read file!" });
|
||||
@@ -380,7 +381,7 @@
|
||||
data: r.result.toString(),
|
||||
type: "myanimelist",
|
||||
};
|
||||
goto("/import/process");
|
||||
goto(toBaseUrl("/import/process"));
|
||||
}
|
||||
},
|
||||
false,
|
||||
@@ -430,7 +431,7 @@
|
||||
// Build toImport array
|
||||
const toImport: ImportedList[] = [];
|
||||
const fileText = await readFile(new FileReader(), file);
|
||||
const jsonData = JSON.parse(fileText)["metadata"] as any[];
|
||||
const jsonData = JSON.parse(fileText)["media"] as any[];
|
||||
for (const v of jsonData) {
|
||||
if (
|
||||
!v.source_id ||
|
||||
@@ -491,9 +492,11 @@
|
||||
? v.reviews[0].review?.text
|
||||
: "",
|
||||
|
||||
rating: v.reviews?.length
|
||||
? validifyRating(Number(v.reviews[0].rating))
|
||||
: undefined,
|
||||
// Ryot does not support overall rating for shows
|
||||
rating:
|
||||
v.lot === "movie" && v.reviews?.length
|
||||
? validifyRating(Number(v.reviews[0].rating))
|
||||
: undefined,
|
||||
|
||||
datesWatched:
|
||||
v.lot === "movie" && v.seen_history?.length
|
||||
@@ -537,7 +540,7 @@
|
||||
data: JSON.stringify(toImport),
|
||||
type: "ryot",
|
||||
};
|
||||
goto("/import/process");
|
||||
goto(toBaseUrl("/import/process"));
|
||||
} catch (err) {
|
||||
isLoading = false;
|
||||
notify({ type: "error", text: "Failed to read file!" });
|
||||
@@ -655,7 +658,7 @@
|
||||
type: "todomovies",
|
||||
};
|
||||
|
||||
goto("/import/process");
|
||||
goto(toBaseUrl("/import/process"));
|
||||
} catch (err) {
|
||||
isLoading = false;
|
||||
notify({ type: "error", text: "Failed to read files!" });
|
||||
@@ -665,7 +668,7 @@
|
||||
|
||||
onMount(() => {
|
||||
if (!localStorage.getItem("token")) {
|
||||
goto("/login");
|
||||
goto(toBaseUrl("/login"));
|
||||
}
|
||||
});
|
||||
</script>
|
||||
@@ -700,7 +703,7 @@
|
||||
filesSelected={(f) => processFiles(f, "tmdb")}
|
||||
/>
|
||||
|
||||
<button class="plain" onclick={() => goto("/import/trakt")}>
|
||||
<button class="plain" onclick={() => goto(toBaseUrl("/import/trakt"))}>
|
||||
<Icon i="trakt" wh="100%" />
|
||||
<h4 class="norm">Trakt Import</h4>
|
||||
</button>
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
<!--
|
||||
<!--
|
||||
/import/process is for processing the
|
||||
selected files data. Here it will be
|
||||
displayed and imported.
|
||||
@@ -29,6 +29,7 @@
|
||||
import { onDestroy } from "svelte";
|
||||
import papa from "papaparse";
|
||||
import Status from "@/lib/Status.svelte";
|
||||
import { toBaseUrl } from "@/lib/util/url";
|
||||
|
||||
interface ImportedListItemMultiProblem {
|
||||
original: ImportedList;
|
||||
@@ -58,7 +59,7 @@
|
||||
const list = store.importedList;
|
||||
if (!list) {
|
||||
console.log("import/process, no list, returning to /import");
|
||||
goto("/import");
|
||||
goto(toBaseUrl("/import"));
|
||||
return;
|
||||
}
|
||||
console.log("getList", list);
|
||||
@@ -166,33 +167,17 @@
|
||||
if (year) {
|
||||
l.year = String(year.getFullYear());
|
||||
}
|
||||
|
||||
switch (type) {
|
||||
case "movie":
|
||||
case "video":
|
||||
case "tv movie":
|
||||
case "short":
|
||||
l.type = "movie";
|
||||
break;
|
||||
case "tv series":
|
||||
case "tv mini series":
|
||||
case "tv special":
|
||||
case "tv short":
|
||||
l.type = "tv";
|
||||
break;
|
||||
case "tv episode":
|
||||
l.type = "tv_episode";
|
||||
break;
|
||||
default:
|
||||
console.warn(
|
||||
"Skipping item with invalid type",
|
||||
`(${type})`,
|
||||
el,
|
||||
);
|
||||
anySkipped = true;
|
||||
continue;
|
||||
if (type === "movie") {
|
||||
l.type = "movie";
|
||||
} else if (type === "tv series") {
|
||||
l.type = "tv";
|
||||
} else if (type === "tv episode") {
|
||||
l.type = "tv_episode";
|
||||
} else {
|
||||
console.warn("Skipping item with invalid type", `(${type})`, el);
|
||||
anySkipped = true;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (imdbId) {
|
||||
l.imdbId = imdbId;
|
||||
}
|
||||
@@ -447,14 +432,14 @@
|
||||
) {
|
||||
// Some items failed.. go to some-failed
|
||||
store.parsedImportedList = rList;
|
||||
goto("/import/some-failed");
|
||||
goto(toBaseUrl("/import/some-failed"));
|
||||
} else {
|
||||
notify({
|
||||
type: "success",
|
||||
text: "All content successfully imported! Try refreshing if you are missing data.",
|
||||
time: 15000,
|
||||
});
|
||||
goto("/");
|
||||
goto(toBaseUrl("/"));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -693,7 +678,8 @@
|
||||
</tbody>
|
||||
</table>
|
||||
<div class="btns">
|
||||
<button onclick={() => goto("/import")}><Icon i="arrow" />Back</button
|
||||
<button onclick={() => goto(toBaseUrl("/import"))}
|
||||
><Icon i="arrow" />Back</button
|
||||
>
|
||||
<button onclick={() => changeAllStatuses()} disabled={isImporting}>
|
||||
Change Statuses
|
||||
@@ -725,9 +711,7 @@
|
||||
{#if importMultiItem}
|
||||
<Modal
|
||||
title="Multiple Results Found"
|
||||
desc="Select the correct item for {importMultiItem.original
|
||||
.name} {importMultiItem.original.year &&
|
||||
'(' + importMultiItem.original.year + ')'}"
|
||||
desc="Select the correct item for {importMultiItem.original.name}"
|
||||
onClose={() => {
|
||||
importMultiItem?.callback("closed results modal");
|
||||
importMultiItem = undefined;
|
||||
|
||||
@@ -12,6 +12,7 @@
|
||||
|
||||
<script lang="ts">
|
||||
import { goto } from "$app/navigation";
|
||||
import { toBaseUrl } from "@/lib/util/url";
|
||||
import { store } from "@/store.svelte";
|
||||
import { ImportResponseType, type ImportedList } from "@/types";
|
||||
import { onMount } from "svelte";
|
||||
@@ -36,7 +37,7 @@
|
||||
}
|
||||
console.log("failedlen", failed.length);
|
||||
} else {
|
||||
goto("/import");
|
||||
goto(toBaseUrl("/import"));
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
@@ -7,7 +7,11 @@
|
||||
import Stat from "@/lib/stats/Stat.svelte";
|
||||
import Stats from "@/lib/stats/Stats.svelte";
|
||||
import { updateUserSetting } from "@/lib/util/api";
|
||||
import { getOrdinalSuffix, monthsShort } from "@/lib/util/helpers";
|
||||
import {
|
||||
getOrdinalSuffix,
|
||||
monthsShort,
|
||||
toggleTheme,
|
||||
} from "@/lib/util/helpers";
|
||||
import { store } from "@/store.svelte";
|
||||
import { UserType, type Image, type Profile } from "@/types";
|
||||
import axios from "axios";
|
||||
@@ -17,7 +21,7 @@
|
||||
import SyncModal from "./modals/SyncModal.svelte";
|
||||
import RegionDropDown from "@/lib/RegionDropDown.svelte";
|
||||
import RatingSetting from "@/lib/rating/RatingSetting.svelte";
|
||||
import { toggleTheme } from "@/lib/util/theme";
|
||||
import { toBaseUrl } from "@/lib/util/url";
|
||||
|
||||
let user = $derived(store.userInfo);
|
||||
let settings = $derived(store.userSettings);
|
||||
@@ -215,13 +219,6 @@
|
||||
<div class="theme">
|
||||
<h4 class="norm">Theme</h4>
|
||||
<div class="row">
|
||||
<button
|
||||
class={`plain${selectedTheme === "system" ? " selected" : ""}`}
|
||||
id="system"
|
||||
onclick={() => toggleTheme("system")}
|
||||
>
|
||||
<span>system</span>
|
||||
</button>
|
||||
<button
|
||||
class={`plain${selectedTheme === "light" ? " selected" : ""}`}
|
||||
id="light"
|
||||
@@ -349,7 +346,7 @@
|
||||
<RatingSetting />
|
||||
|
||||
<div class="row btns">
|
||||
<button onclick={() => goto("/import")}>Import</button>
|
||||
<button onclick={() => goto(toBaseUrl("/import"))}>Import</button>
|
||||
<button onclick={() => downloadWatchedList()} disabled={exportDisabled}
|
||||
>Export</button
|
||||
>
|
||||
@@ -508,20 +505,6 @@
|
||||
}
|
||||
}
|
||||
|
||||
&#system {
|
||||
background: linear-gradient(to right bottom, white 50%, black 50.3%);
|
||||
outline-color: black;
|
||||
|
||||
span {
|
||||
mix-blend-mode: difference;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
color: white;
|
||||
-webkit-text-stroke: 0.5px white;
|
||||
}
|
||||
}
|
||||
|
||||
&.selected {
|
||||
outline-color: gold !important;
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
<script lang="ts">
|
||||
import { goto } from "$app/navigation";
|
||||
import { notify } from "@/lib/util/notify";
|
||||
import { toBaseUrl } from "@/lib/util/url";
|
||||
import { store } from "@/store.svelte";
|
||||
import { UserPermission } from "@/types";
|
||||
import axios from "axios";
|
||||
@@ -32,7 +33,7 @@
|
||||
if (store.userInfo) {
|
||||
store.userInfo.permissions = UserPermission.PERM_ADMIN;
|
||||
}
|
||||
goto("/");
|
||||
goto(toBaseUrl("/"));
|
||||
})
|
||||
.catch((err) => {
|
||||
console.error(
|
||||
|
||||
@@ -29,6 +29,7 @@
|
||||
import Icon from "@/lib/Icon.svelte";
|
||||
import { afterNavigate, goto } from "$app/navigation";
|
||||
import { page } from "$app/state";
|
||||
import { toBaseUrl } from "@/lib/util/url.js";
|
||||
|
||||
type GameWithMediaType = GameSearch & { media_type: "game" };
|
||||
type CombinedResult =
|
||||
@@ -278,7 +279,7 @@
|
||||
data[0],
|
||||
);
|
||||
|
||||
goto(`/game/${data[0].id}`);
|
||||
goto(toBaseUrl(`/game/${data[0].id}`));
|
||||
return;
|
||||
}
|
||||
allSearchResults.push(...data);
|
||||
@@ -290,7 +291,7 @@
|
||||
// assuming that people paste the id in, this should work
|
||||
// without the debounce going to an incomplete id.
|
||||
// Flesh out if anyone has issues.
|
||||
goto(`/${extProvider.provider}/${extProvider.id}`);
|
||||
goto(toBaseUrl(`/${extProvider.provider}/${extProvider.id}`));
|
||||
return;
|
||||
} else {
|
||||
// Else call tmdb `external id` endpoint
|
||||
@@ -324,7 +325,11 @@
|
||||
mediaType,
|
||||
);
|
||||
} else {
|
||||
goto(`/${data.results[0].media_type}/${data.results[0].id}`);
|
||||
goto(
|
||||
toBaseUrl(
|
||||
`/${data.results[0].media_type}/${data.results[0].id}`,
|
||||
),
|
||||
);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -24,6 +24,7 @@
|
||||
import RegionDropDown from "@/lib/RegionDropDown.svelte";
|
||||
import TaskScheduleModal from "./modals/TaskScheduleModal.svelte";
|
||||
import TrustedHeaderAuthModal from "./modals/TrustedHeaderAuthModal.svelte";
|
||||
import { toBaseUrl } from "@/lib/util/url";
|
||||
|
||||
let serverConfig: ServerConfig | undefined = $state();
|
||||
let jellyfinOrEmby = $derived(serverConfig?.USE_EMBY ? "Emby" : "Jellyfin");
|
||||
@@ -108,7 +109,12 @@
|
||||
{#await getServerStats()}
|
||||
<Spinner />
|
||||
{:then stats}
|
||||
<Stat name="Users" value={stats.users} href="/manage_users" large />
|
||||
<Stat
|
||||
name="Users"
|
||||
value={stats.users}
|
||||
href={toBaseUrl("/manage_users")}
|
||||
large
|
||||
/>
|
||||
<Stat name="Private Users" value={stats.privateUsers} large />
|
||||
<Stat name="Watched Movies" value={stats.watchedMovies} large />
|
||||
<Stat name="Watched Shows" value={stats.watchedShows} large />
|
||||
@@ -118,14 +124,14 @@
|
||||
<Stat
|
||||
name="Most Watched Movie"
|
||||
value={stats.mostWatchedMovie.title}
|
||||
href="/movie/{stats.mostWatchedMovie.tmdbId}"
|
||||
href={toBaseUrl(`/movie/${stats.mostWatchedMovie.tmdbId}`)}
|
||||
/>
|
||||
{/if}
|
||||
{#if stats.mostWatchedShow?.title}
|
||||
<Stat
|
||||
name="Most Watched Show"
|
||||
value={stats.mostWatchedShow.title}
|
||||
href="/tv/{stats.mostWatchedShow.tmdbId}"
|
||||
href={toBaseUrl(`/tv/${stats.mostWatchedShow.tmdbId}`)}
|
||||
/>
|
||||
{/if}
|
||||
{:catch err}
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
import { noAuthAxios } from "@/lib/util/api";
|
||||
import { onMount } from "svelte";
|
||||
import { notify, unNotify } from "@/lib/util/notify";
|
||||
import { toBaseUrl } from "@/lib/util/url";
|
||||
|
||||
let error: string | undefined = $state();
|
||||
let login = $state(true);
|
||||
@@ -18,7 +19,7 @@
|
||||
|
||||
onMount(() => {
|
||||
if (localStorage.getItem("token")) {
|
||||
goto("/");
|
||||
goto(toBaseUrl("/"));
|
||||
}
|
||||
|
||||
if (!error && page.url.searchParams.get("again")) {
|
||||
@@ -35,7 +36,7 @@
|
||||
if (r?.data) {
|
||||
if (r.data.isInSetup) {
|
||||
console.log("Server is in setup.. navigating to web setup page.");
|
||||
goto("/setup");
|
||||
goto(toBaseUrl("/setup"));
|
||||
}
|
||||
availableProviders = r.data.available;
|
||||
apHeader = availableProviders?.includes("header");
|
||||
@@ -83,7 +84,7 @@
|
||||
} else {
|
||||
localStorage.removeItem("useEmby");
|
||||
}
|
||||
goto("/");
|
||||
goto(toBaseUrl("/"));
|
||||
notify({ id: nid, text: `Welcome ${user}!`, type: "success" });
|
||||
}
|
||||
})
|
||||
@@ -120,7 +121,7 @@
|
||||
if (resp.data?.token) {
|
||||
console.log("Received token... logging in.");
|
||||
localStorage.setItem("token", resp.data.token);
|
||||
goto("/");
|
||||
goto(toBaseUrl("/"));
|
||||
notify({ id: nid, text: `Welcome!`, type: "success" });
|
||||
}
|
||||
})
|
||||
@@ -148,7 +149,7 @@
|
||||
if (resp.data?.token) {
|
||||
console.log("Received token... logging in.");
|
||||
localStorage.setItem("token", resp.data.token);
|
||||
goto("/");
|
||||
goto(toBaseUrl("/"));
|
||||
notify({ id: nid, text: `Welcome!`, type: "success" });
|
||||
}
|
||||
})
|
||||
|
||||
@@ -1,30 +1,30 @@
|
||||
<script lang="ts">
|
||||
import { preventDefault } from "svelte/legacy";
|
||||
|
||||
import { goto } from "$app/navigation";
|
||||
import type { AvailableAuthProviders } from "@/types";
|
||||
import { noAuthAxios } from "@/lib/util/api";
|
||||
import { onMount } from "svelte";
|
||||
import { notify, unNotify } from "@/lib/util/notify";
|
||||
import { toBaseUrl } from "@/lib/util/url";
|
||||
|
||||
let error: string = $state();
|
||||
let error: string | undefined = $state();
|
||||
|
||||
onMount(() => {
|
||||
if (localStorage.getItem("token")) {
|
||||
goto("/");
|
||||
goto(toBaseUrl("/"));
|
||||
}
|
||||
|
||||
noAuthAxios.get<AvailableAuthProviders>("/auth/available").then((r) => {
|
||||
if (r?.data) {
|
||||
if (!r?.data?.isInSetup) {
|
||||
console.log("Server not in setup.. navigating to login page.");
|
||||
goto("/login");
|
||||
goto(toBaseUrl("/login"));
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
function handleLogin(ev: SubmitEvent) {
|
||||
ev.preventDefault();
|
||||
const fd = new FormData(ev.target! as HTMLFormElement);
|
||||
const user = fd.get("username");
|
||||
const pass = fd.get("password");
|
||||
@@ -44,7 +44,7 @@
|
||||
if (resp.data?.token) {
|
||||
console.log("Received token... logging in.");
|
||||
localStorage.setItem("token", resp.data.token);
|
||||
goto("/");
|
||||
goto(toBaseUrl("/"));
|
||||
notify({ id: nid, text: `Welcome ${user}!`, type: "success" });
|
||||
}
|
||||
})
|
||||
@@ -74,7 +74,7 @@
|
||||
<span class="error">{error}!</span>
|
||||
{/if}
|
||||
|
||||
<form onsubmit={preventDefault(handleLogin)}>
|
||||
<form onsubmit={handleLogin}>
|
||||
<label for="username">Username</label>
|
||||
<input type="text" name="username" placeholder="Username" />
|
||||
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
<script>
|
||||
import { goto } from "$app/navigation";
|
||||
import { page } from "$app/state";
|
||||
import { toBaseUrl } from "@/lib/util/url";
|
||||
</script>
|
||||
|
||||
<div>
|
||||
@@ -16,7 +17,7 @@
|
||||
<h4 class="norm">We couldn't load this page</h4>
|
||||
{/if}
|
||||
<div class="btns">
|
||||
<button onclick={() => goto("/")}>Home</button>
|
||||
<button onclick={() => goto(toBaseUrl("/"))}>Home</button>
|
||||
<button onclick={() => location.reload()}>Refresh</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
+7
-4
@@ -12,7 +12,7 @@ import type {
|
||||
} from "./types";
|
||||
import type { Notification } from "./lib/util/notify";
|
||||
import { browser } from "$app/environment";
|
||||
import { toggleTheme } from "./lib/util/theme";
|
||||
import { toggleTheme } from "./lib/util/helpers";
|
||||
|
||||
export const defaultSort = ["DATEADDED", "DOWN"];
|
||||
|
||||
@@ -54,7 +54,7 @@ const _store: Store = $state({
|
||||
notifications: [],
|
||||
activeSort: defaultSort,
|
||||
activeFilters: { type: [], status: [] },
|
||||
appTheme: "system",
|
||||
appTheme: "light",
|
||||
importedList: undefined,
|
||||
parsedImportedList: undefined,
|
||||
searchQuery: "",
|
||||
@@ -186,7 +186,7 @@ export const clearAllStores = () => {
|
||||
store.watchedList = [];
|
||||
store.notifications = [];
|
||||
store.activeSort = defaultSort;
|
||||
store.appTheme = "system";
|
||||
store.appTheme = "light";
|
||||
store.importedList = undefined;
|
||||
store.parsedImportedList = undefined;
|
||||
store.searchQuery = "";
|
||||
@@ -244,7 +244,10 @@ function rehydrateStore() {
|
||||
$state.snapshot(store.appTheme),
|
||||
);
|
||||
} else {
|
||||
let defTheme: Theme = "system";
|
||||
let defTheme: Theme = "light";
|
||||
if (window.matchMedia("(prefers-color-scheme: dark)").matches) {
|
||||
defTheme = "dark";
|
||||
}
|
||||
_store.appTheme = defTheme;
|
||||
toggleTheme(defTheme, false);
|
||||
console.debug(
|
||||
|
||||
+1
-1
@@ -63,7 +63,7 @@ export type Icon =
|
||||
| "tmdb"
|
||||
| "igdb";
|
||||
|
||||
export type Theme = "light" | "dark" | "system";
|
||||
export type Theme = "light" | "dark";
|
||||
|
||||
export type WLDetailedViewOption =
|
||||
| "statusRating"
|
||||
|
||||
@@ -2,6 +2,14 @@ import adapter from "@sveltejs/adapter-node";
|
||||
import { vitePreprocess } from "@sveltejs/vite-plugin-svelte";
|
||||
import { sveltePreprocess } from "svelte-preprocess";
|
||||
|
||||
if (
|
||||
process.env.WATCHARR_BASE &&
|
||||
(!process.env.WATCHARR_BASE.startsWith("/") ||
|
||||
process.env.WATCHARR_BASE.endsWith("/"))
|
||||
) {
|
||||
throw new Error("WATCHARR_BASE must start with, but not end with '/'");
|
||||
}
|
||||
|
||||
/** @type {import('@sveltejs/kit').Config} */
|
||||
const config = {
|
||||
// Consult https://kit.svelte.dev/docs/integrations#preprocessors
|
||||
@@ -21,6 +29,11 @@ const config = {
|
||||
alias: {
|
||||
"@": "src",
|
||||
},
|
||||
|
||||
paths: {
|
||||
base: process.env.WATCHARR_BASE,
|
||||
relative: true,
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
|
||||
Reference in New Issue
Block a user