diff --git a/.env.example b/.env.example new file mode 100644 index 00000000..8a923ae3 --- /dev/null +++ b/.env.example @@ -0,0 +1,47 @@ +# 🌐 Frontend +PUBLIC_SERVER_URL=http://server:8000 # PLEASE DON'T CHANGE :) - Should be the service name of the backend with port 8000, even if you change the port in the backend service. Only change if you are using a custom more complex setup. +ORIGIN=http://localhost:8015 +BODY_SIZE_LIMIT=Infinity +FRONTEND_PORT=8015 + +# 🐘 PostgreSQL Database +PGHOST=db +POSTGRES_DB=database +POSTGRES_USER=adventure +POSTGRES_PASSWORD=changeme123 + +# 🔒 Django Backend +SECRET_KEY=changeme123 +DJANGO_ADMIN_USERNAME=admin +DJANGO_ADMIN_PASSWORD=admin +DJANGO_ADMIN_EMAIL=admin@example.com +PUBLIC_URL=http://localhost:8016 # Match the outward port, used for the creation of image urls +CSRF_TRUSTED_ORIGINS=http://localhost:8016,http://localhost:8015 +DEBUG=False +FRONTEND_URL=http://localhost:8015 # Used for email generation. This should be the url of the frontend +BACKEND_PORT=8016 + +# Optional: use Google Maps integration +# https://adventurelog.app/docs/configuration/google_maps_integration.html +# GOOGLE_MAPS_API_KEY=your_google_maps_api_key + +# Optional: disable registration +# https://adventurelog.app/docs/configuration/disable_registration.html +DISABLE_REGISTRATION=False +# DISABLE_REGISTRATION_MESSAGE=Registration is disabled for this instance of AdventureLog. + +# Optional: Use email +# https://adventurelog.app/docs/configuration/email.html +# EMAIL_BACKEND=email +# EMAIL_HOST=smtp.gmail.com +# EMAIL_USE_TLS=True +# EMAIL_PORT=587 +# EMAIL_USE_SSL=False +# EMAIL_HOST_USER=user +# EMAIL_HOST_PASSWORD=password +# DEFAULT_FROM_EMAIL=user@example.com + +# Optional: Use Umami for analytics +# https://adventurelog.app/docs/configuration/analytics.html +# PUBLIC_UMAMI_SRC=https://cloud.umami.is/script.js # If you are using the hosted version of Umami +# PUBLIC_UMAMI_WEBSITE_ID= \ No newline at end of file diff --git a/.github/.docker-compose-database.yml b/.github/.docker-compose-database.yml new file mode 100644 index 00000000..aa187bd6 --- /dev/null +++ b/.github/.docker-compose-database.yml @@ -0,0 +1,16 @@ +services: + db: + image: postgis/postgis:15-3.3 + container_name: adventurelog-db + restart: unless-stopped + ports: + - "127.0.0.1:5432:5432" + environment: + POSTGRES_DB: database + POSTGRES_USER: adventure + POSTGRES_PASSWORD: changeme123 + volumes: + - postgres_data:/var/lib/postgresql/data/ + +volumes: + postgres_data: diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml index 569c95d5..80c427d1 100644 --- a/.github/FUNDING.yml +++ b/.github/FUNDING.yml @@ -1 +1,2 @@ +github: seanmorley15 buy_me_a_coffee: seanmorley15 diff --git a/.github/workflows/backend-test.yml b/.github/workflows/backend-test.yml new file mode 100644 index 00000000..0ce9d7c3 --- /dev/null +++ b/.github/workflows/backend-test.yml @@ -0,0 +1,64 @@ +name: Test Backend + +permissions: + contents: read + +on: + pull_request: + paths: + - 'backend/server/**' + - '.github/workflows/backend-test.yml' + push: + paths: + - 'backend/server/**' + - '.github/workflows/backend-test.yml' + +jobs: + build: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - name: set up python 3.12 + uses: actions/setup-python@v5 + with: + python-version: '3.12' + + - name: install dependencies + run: | + sudo apt update -q + sudo apt install -y -q \ + python3-gdal + + - name: start database + run: | + docker compose -f .github/.docker-compose-database.yml up -d + + - name: install python libreries + working-directory: backend/server + run: | + pip install -r requirements.txt + + - name: run server + working-directory: backend/server + env: + PGHOST: "127.0.0.1" + PGDATABASE: "database" + PGUSER: "adventure" + PGPASSWORD: "changeme123" + SECRET_KEY: "changeme123" + DJANGO_ADMIN_USERNAME: "admin" + DJANGO_ADMIN_PASSWORD: "admin" + DJANGO_ADMIN_EMAIL: "admin@example.com" + PUBLIC_URL: "http://localhost:8000" + CSRF_TRUSTED_ORIGINS: "http://localhost:5173,http://localhost:8000" + DEBUG: "True" + FRONTEND_URL: "http://localhost:5173" + run: | + python manage.py migrate + python manage.py runserver & + + - name: wait for backend to boot + run: > + curl -fisS --retry 60 --retry-delay 1 --retry-all-errors + http://localhost:8000/ diff --git a/.github/workflows/frontend-test.yml b/.github/workflows/frontend-test.yml new file mode 100644 index 00000000..73d9b24d --- /dev/null +++ b/.github/workflows/frontend-test.yml @@ -0,0 +1,32 @@ +name: Test Frontend + +permissions: + contents: read + +on: + pull_request: + paths: + - "frontend/**" + - ".github/workflows/frontend-test.yml" + push: + paths: + - "frontend/**" + - ".github/workflows/frontend-test.yml" + +jobs: + build: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - uses: actions/setup-node@v4 + with: + node-version: 20 + + - name: install dependencies + working-directory: frontend + run: npm i + + - name: build frontend + working-directory: frontend + run: npm run build diff --git a/.gitignore b/.gitignore index 314a123f..090b6813 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,5 @@ # Ignore everything in the .venv folder .venv/ .vscode/settings.json -.pnpm-store/ \ No newline at end of file +.pnpm-store/ +.env diff --git a/backend/Dockerfile b/backend/Dockerfile index aa0f9f41..b3f41b77 100644 --- a/backend/Dockerfile +++ b/backend/Dockerfile @@ -1,18 +1,30 @@ # Use the official Python slim image as the base image -FROM python:3.10-slim - -LABEL Developers="Sean Morley" +FROM python:3.13-slim + +# Metadata labels for the AdventureLog image +LABEL maintainer="Sean Morley" \ + version="v0.10.0" \ + description="AdventureLog — the ultimate self-hosted travel companion." \ + org.opencontainers.image.title="AdventureLog" \ + org.opencontainers.image.description="AdventureLog is a self-hosted travel companion that helps you plan, track, and share your adventures." \ + org.opencontainers.image.version="v0.10.0" \ + org.opencontainers.image.authors="Sean Morley" \ + org.opencontainers.image.url="https://raw.githubusercontent.com/seanmorley15/AdventureLog/refs/heads/main/brand/banner.png" \ + org.opencontainers.image.source="https://github.com/seanmorley15/AdventureLog" \ + org.opencontainers.image.vendor="Sean Morley" \ + org.opencontainers.image.created="$(date -u +'%Y-%m-%dT%H:%M:%SZ')" \ + org.opencontainers.image.licenses="GPL-3.0" # Set environment variables -ENV PYTHONDONTWRITEBYTECODE 1 -ENV PYTHONUNBUFFERED 1 +ENV PYTHONDONTWRITEBYTECODE=1 +ENV PYTHONUNBUFFERED=1 # Set the working directory WORKDIR /code # Install system dependencies (Nginx included) RUN apt-get update \ - && apt-get install -y git postgresql-client gdal-bin libgdal-dev nginx \ + && apt-get install -y git postgresql-client gdal-bin libgdal-dev nginx supervisor \ && apt-get clean \ && rm -rf /var/lib/apt/lists/* @@ -31,6 +43,9 @@ COPY ./server /code/ # Copy Nginx configuration COPY ./nginx.conf /etc/nginx/nginx.conf +# Copy Supervisor configuration +COPY ./supervisord.conf /etc/supervisor/conf.d/supervisord.conf + # Collect static files RUN python3 manage.py collectstatic --noinput --verbosity 2 @@ -41,5 +56,5 @@ RUN chmod +x /code/entrypoint.sh # Expose ports for NGINX and Gunicorn EXPOSE 80 8000 -# Command to start Nginx and Gunicorn -CMD ["bash", "-c", "service nginx start && /code/entrypoint.sh"] \ No newline at end of file +# Command to start Supervisor (which starts Nginx and Gunicorn) +CMD ["supervisord", "-c", "/etc/supervisor/conf.d/supervisord.conf"] diff --git a/backend/entrypoint.sh b/backend/entrypoint.sh index 9ca926cc..1031cb10 100644 --- a/backend/entrypoint.sh +++ b/backend/entrypoint.sh @@ -1,10 +1,32 @@ #!/bin/bash # Function to check PostgreSQL availability +# Helper to get the first non-empty environment variable +get_env() { + for var in "$@"; do + value="${!var}" + if [ -n "$value" ]; then + echo "$value" + return + fi + done +} + check_postgres() { - PGPASSWORD=$PGPASSWORD psql -h "$PGHOST" -U "$PGUSER" -d "$PGDATABASE" -c '\q' >/dev/null 2>&1 + local db_host + local db_user + local db_name + local db_pass + + db_host=$(get_env PGHOST) + db_user=$(get_env PGUSER POSTGRES_USER) + db_name=$(get_env PGDATABASE POSTGRES_DB) + db_pass=$(get_env PGPASSWORD POSTGRES_PASSWORD) + + PGPASSWORD="$db_pass" psql -h "$db_host" -U "$db_user" -d "$db_name" -c '\q' >/dev/null 2>&1 } + # Wait for PostgreSQL to become available until check_postgres; do >&2 echo "PostgreSQL is unavailable - sleeping" @@ -62,5 +84,8 @@ fi cat /code/adventurelog.txt -# Start gunicorn -gunicorn main.wsgi:application --bind [::]:8000 --timeout 120 --workers 2 \ No newline at end of file +# Start Gunicorn in foreground +exec gunicorn main.wsgi:application \ + --bind [::]:8000 \ + --workers 2 \ + --timeout 120 diff --git a/backend/nginx.conf b/backend/nginx.conf index 8074aa69..23dba446 100644 --- a/backend/nginx.conf +++ b/backend/nginx.conf @@ -1,27 +1,20 @@ worker_processes 1; - events { worker_connections 1024; } - http { include /etc/nginx/mime.types; default_type application/octet-stream; - sendfile on; keepalive_timeout 65; - client_max_body_size 100M; - # The backend is running in the same container, so reference localhost upstream django { server 127.0.0.1:8000; # Use localhost to point to Gunicorn running internally } - server { listen 80; server_name localhost; - location / { proxy_pass http://django; # Forward to the upstream block proxy_set_header Host $host; @@ -29,17 +22,21 @@ http { proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto $scheme; } - location /static/ { alias /code/staticfiles/; # Serve static files directly } - # Serve protected media files with X-Accel-Redirect location /protectedMedia/ { internal; # Only internal requests are allowed alias /code/media/; # This should match Django MEDIA_ROOT try_files $uri =404; # Return a 404 if the file doesn't exist + + # Security headers for all protected files + add_header Content-Security-Policy "default-src 'self'; script-src 'none'; object-src 'none'; base-uri 'none'" always; + add_header X-Content-Type-Options nosniff always; + add_header X-Frame-Options SAMEORIGIN always; + add_header X-XSS-Protection "1; mode=block" always; + add_header Referrer-Policy "strict-origin-when-cross-origin" always; } - } -} +} \ No newline at end of file diff --git a/backend/server/.env.example b/backend/server/.env.example index 598aeb73..2c932085 100644 --- a/backend/server/.env.example +++ b/backend/server/.env.example @@ -22,6 +22,8 @@ EMAIL_BACKEND='console' # EMAIL_HOST_PASSWORD='password' # DEFAULT_FROM_EMAIL='user@example.com' +# GOOGLE_MAPS_API_KEY='key' + # ------------------- # # For Developers to start a Demo Database diff --git a/backend/server/adventures/admin.py b/backend/server/adventures/admin.py index 3210f7a8..a8938343 100644 --- a/backend/server/adventures/admin.py +++ b/backend/server/adventures/admin.py @@ -8,10 +8,25 @@ admin.autodiscover() admin.site.login = secure_admin_login(admin.site.login) +@admin.action(description="Trigger geocoding") +def trigger_geocoding(modeladmin, request, queryset): + count = 0 + for adventure in queryset: + try: + adventure.save() # Triggers geocoding logic in your model + count += 1 + except Exception as e: + modeladmin.message_user(request, f"Error geocoding {adventure}: {e}", level='error') + modeladmin.message_user(request, f"Geocoding triggered for {count} adventures.", level='success') + + + class AdventureAdmin(admin.ModelAdmin): list_display = ('name', 'get_category', 'get_visit_count', 'user_id', 'is_public') list_filter = ( 'user_id', 'is_public') search_fields = ('name',) + readonly_fields = ('city', 'region', 'country') + actions = [trigger_geocoding] def get_category(self, obj): if obj.category and obj.category.display_name and obj.category.icon: diff --git a/backend/server/adventures/geocoding.py b/backend/server/adventures/geocoding.py new file mode 100644 index 00000000..aaf34592 --- /dev/null +++ b/backend/server/adventures/geocoding.py @@ -0,0 +1,240 @@ +import requests +import time +import socket +from worldtravel.models import Region, City, VisitedRegion, VisitedCity +from django.conf import settings + +# ----------------- +# SEARCHING +# ----------------- +def search_google(query): + api_key = settings.GOOGLE_MAPS_API_KEY + url = "https://maps.googleapis.com/maps/api/place/textsearch/json" + params = {'query': query, 'key': api_key} + response = requests.get(url, params=params) + data = response.json() + + results = [] + for r in data.get("results", []): + location = r.get("geometry", {}).get("location", {}) + types = r.get("types", []) + + # First type is often most specific (e.g., 'restaurant', 'locality') + primary_type = types[0] if types else None + category = _extract_google_category(types) + addresstype = _infer_addresstype(primary_type) + + importance = None + if r.get("user_ratings_total") and r.get("rating"): + # Simple importance heuristic based on popularity and quality + importance = round(float(r["rating"]) * r["user_ratings_total"] / 100, 2) + + results.append({ + "lat": location.get("lat"), + "lon": location.get("lng"), + "name": r.get("name"), + "display_name": r.get("formatted_address"), + "type": primary_type, + "category": category, + "importance": importance, + "addresstype": addresstype, + "powered_by": "google", + }) + + # order by importance if available + if results and any("importance" in r for r in results): + results.sort(key=lambda x: x.get("importance", 0), reverse=True) + + return results + + +def _extract_google_category(types): + # Basic category inference based on common place types + if not types: + return None + if "restaurant" in types: + return "food" + if "lodging" in types: + return "accommodation" + if "park" in types or "natural_feature" in types: + return "nature" + if "museum" in types or "tourist_attraction" in types: + return "attraction" + if "locality" in types or "administrative_area_level_1" in types: + return "region" + return types[0] # fallback to first type + + +def _infer_addresstype(type_): + # Rough mapping of Google place types to OSM-style addresstypes + mapping = { + "locality": "city", + "sublocality": "neighborhood", + "administrative_area_level_1": "region", + "administrative_area_level_2": "county", + "country": "country", + "premise": "building", + "point_of_interest": "poi", + "route": "road", + "street_address": "address", + } + return mapping.get(type_, None) + + +def search_osm(query): + url = f"https://nominatim.openstreetmap.org/search?q={query}&format=jsonv2" + headers = {'User-Agent': 'AdventureLog Server'} + response = requests.get(url, headers=headers) + data = response.json() + + return [{ + "lat": item.get("lat"), + "lon": item.get("lon"), + "name": item.get("name"), + "display_name": item.get("display_name"), + "type": item.get("type"), + "category": item.get("category"), + "importance": item.get("importance"), + "addresstype": item.get("addresstype"), + "powered_by": "nominatim", + } for item in data] + +# ----------------- +# REVERSE GEOCODING +# ----------------- + +def extractIsoCode(user, data): + """ + Extract the ISO code from the response data. + Returns a dictionary containing the region name, country name, and ISO code if found. + """ + iso_code = None + town_city_or_county = None + display_name = None + country_code = None + city = None + visited_city = None + location_name = None + + # town = None + # city = None + # county = None + + if 'name' in data.keys(): + location_name = data['name'] + + if 'address' in data.keys(): + keys = data['address'].keys() + for key in keys: + if key.find("ISO") != -1: + iso_code = data['address'][key] + if 'town' in keys: + town_city_or_county = data['address']['town'] + if 'county' in keys: + town_city_or_county = data['address']['county'] + if 'city' in keys: + town_city_or_county = data['address']['city'] + if not iso_code: + return {"error": "No region found"} + + region = Region.objects.filter(id=iso_code).first() + visited_region = VisitedRegion.objects.filter(region=region, user_id=user).first() + + region_visited = False + city_visited = False + country_code = iso_code[:2] + + if region: + if town_city_or_county: + display_name = f"{town_city_or_county}, {region.name}, {country_code}" + city = City.objects.filter(name__contains=town_city_or_county, region=region).first() + visited_city = VisitedCity.objects.filter(city=city, user_id=user).first() + + if visited_region: + region_visited = True + if visited_city: + city_visited = True + if region: + return {"region_id": iso_code, "region": region.name, "country": region.country.name, "country_id": region.country.country_code, "region_visited": region_visited, "display_name": display_name, "city": city.name if city else None, "city_id": city.id if city else None, "city_visited": city_visited, 'location_name': location_name} + return {"error": "No region found"} +def is_host_resolvable(hostname: str) -> bool: + try: + socket.gethostbyname(hostname) + return True + except socket.error: + return False + +def reverse_geocode(lat, lon, user): + if getattr(settings, 'GOOGLE_MAPS_API_KEY', None): + return reverse_geocode_google(lat, lon, user) + return reverse_geocode_osm(lat, lon, user) + +def reverse_geocode_osm(lat, lon, user): + url = f"https://nominatim.openstreetmap.org/reverse?format=jsonv2&lat={lat}&lon={lon}" + headers = {'User-Agent': 'AdventureLog Server'} + connect_timeout = 1 + read_timeout = 5 + + if not is_host_resolvable("nominatim.openstreetmap.org"): + return {"error": "DNS resolution failed"} + + try: + response = requests.get(url, headers=headers, timeout=(connect_timeout, read_timeout)) + response.raise_for_status() + data = response.json() + return extractIsoCode(user, data) + except Exception: + return {"error": "An internal error occurred while processing the request"} + +def reverse_geocode_google(lat, lon, user): + api_key = settings.GOOGLE_MAPS_API_KEY + url = "https://maps.googleapis.com/maps/api/geocode/json" + params = {"latlng": f"{lat},{lon}", "key": api_key} + + try: + response = requests.get(url, params=params) + response.raise_for_status() + data = response.json() + + if data.get("status") != "OK": + return {"error": "Geocoding failed"} + + # Convert Google schema to Nominatim-style for extractIsoCode + first_result = data.get("results", [])[0] + result_data = { + "name": first_result.get("formatted_address"), + "address": _parse_google_address_components(first_result.get("address_components", [])) + } + return extractIsoCode(user, result_data) + except Exception: + return {"error": "An internal error occurred while processing the request"} + +def _parse_google_address_components(components): + parsed = {} + country_code = None + state_code = None + + for comp in components: + types = comp.get("types", []) + long_name = comp.get("long_name") + short_name = comp.get("short_name") + + if "country" in types: + parsed["country"] = long_name + country_code = short_name + parsed["ISO3166-1"] = short_name + if "administrative_area_level_1" in types: + parsed["state"] = long_name + state_code = short_name + if "administrative_area_level_2" in types: + parsed["county"] = long_name + if "locality" in types: + parsed["city"] = long_name + if "sublocality" in types: + parsed["town"] = long_name + + # Build composite ISO 3166-2 code like US-ME + if country_code and state_code: + parsed["ISO3166-2-lvl1"] = f"{country_code}-{state_code}" + + return parsed diff --git a/backend/server/adventures/middleware.py b/backend/server/adventures/middleware.py index 10050b0f..fae6b0b6 100644 --- a/backend/server/adventures/middleware.py +++ b/backend/server/adventures/middleware.py @@ -29,4 +29,12 @@ def process_request(self, request): class DisableCSRFForSessionTokenMiddleware(MiddlewareMixin): def process_request(self, request): if 'X-Session-Token' in request.headers: - setattr(request, '_dont_enforce_csrf_checks', True) \ No newline at end of file + setattr(request, '_dont_enforce_csrf_checks', True) + +class DisableCSRFForMobileLoginSignup(MiddlewareMixin): + def process_request(self, request): + is_mobile = request.headers.get('X-Is-Mobile', '').lower() == 'true' + is_login_or_signup = request.path in ['/auth/browser/v1/auth/login', '/auth/browser/v1/auth/signup'] + if is_mobile and is_login_or_signup: + setattr(request, '_dont_enforce_csrf_checks', True) + \ No newline at end of file diff --git a/backend/server/adventures/migrations/0025_alter_visit_end_date_alter_visit_start_date.py b/backend/server/adventures/migrations/0025_alter_visit_end_date_alter_visit_start_date.py new file mode 100644 index 00000000..668e9681 --- /dev/null +++ b/backend/server/adventures/migrations/0025_alter_visit_end_date_alter_visit_start_date.py @@ -0,0 +1,23 @@ +# Generated by Django 5.0.8 on 2025-03-17 21:41 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('adventures', '0024_alter_attachment_file'), + ] + + operations = [ + migrations.AlterField( + model_name='visit', + name='end_date', + field=models.DateTimeField(blank=True, null=True), + ), + migrations.AlterField( + model_name='visit', + name='start_date', + field=models.DateTimeField(blank=True, null=True), + ), + ] diff --git a/backend/server/adventures/migrations/0026_visit_timezone.py b/backend/server/adventures/migrations/0026_visit_timezone.py new file mode 100644 index 00000000..f0642dd7 --- /dev/null +++ b/backend/server/adventures/migrations/0026_visit_timezone.py @@ -0,0 +1,18 @@ +# Generated by Django 5.0.11 on 2025-05-10 14:53 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('adventures', '0025_alter_visit_end_date_alter_visit_start_date'), + ] + + operations = [ + migrations.AddField( + model_name='visit', + name='timezone', + field=models.CharField(blank=True, choices=[('Africa/Abidjan', 'Africa/Abidjan'), ('Africa/Accra', 'Africa/Accra'), ('Africa/Addis_Ababa', 'Africa/Addis_Ababa'), ('Africa/Algiers', 'Africa/Algiers'), ('Africa/Asmera', 'Africa/Asmera'), ('Africa/Bamako', 'Africa/Bamako'), ('Africa/Bangui', 'Africa/Bangui'), ('Africa/Banjul', 'Africa/Banjul'), ('Africa/Bissau', 'Africa/Bissau'), ('Africa/Blantyre', 'Africa/Blantyre'), ('Africa/Brazzaville', 'Africa/Brazzaville'), ('Africa/Bujumbura', 'Africa/Bujumbura'), ('Africa/Cairo', 'Africa/Cairo'), ('Africa/Casablanca', 'Africa/Casablanca'), ('Africa/Ceuta', 'Africa/Ceuta'), ('Africa/Conakry', 'Africa/Conakry'), ('Africa/Dakar', 'Africa/Dakar'), ('Africa/Dar_es_Salaam', 'Africa/Dar_es_Salaam'), ('Africa/Djibouti', 'Africa/Djibouti'), ('Africa/Douala', 'Africa/Douala'), ('Africa/El_Aaiun', 'Africa/El_Aaiun'), ('Africa/Freetown', 'Africa/Freetown'), ('Africa/Gaborone', 'Africa/Gaborone'), ('Africa/Harare', 'Africa/Harare'), ('Africa/Johannesburg', 'Africa/Johannesburg'), ('Africa/Juba', 'Africa/Juba'), ('Africa/Kampala', 'Africa/Kampala'), ('Africa/Khartoum', 'Africa/Khartoum'), ('Africa/Kigali', 'Africa/Kigali'), ('Africa/Kinshasa', 'Africa/Kinshasa'), ('Africa/Lagos', 'Africa/Lagos'), ('Africa/Libreville', 'Africa/Libreville'), ('Africa/Lome', 'Africa/Lome'), ('Africa/Luanda', 'Africa/Luanda'), ('Africa/Lubumbashi', 'Africa/Lubumbashi'), ('Africa/Lusaka', 'Africa/Lusaka'), ('Africa/Malabo', 'Africa/Malabo'), ('Africa/Maputo', 'Africa/Maputo'), ('Africa/Maseru', 'Africa/Maseru'), ('Africa/Mbabane', 'Africa/Mbabane'), ('Africa/Mogadishu', 'Africa/Mogadishu'), ('Africa/Monrovia', 'Africa/Monrovia'), ('Africa/Nairobi', 'Africa/Nairobi'), ('Africa/Ndjamena', 'Africa/Ndjamena'), ('Africa/Niamey', 'Africa/Niamey'), ('Africa/Nouakchott', 'Africa/Nouakchott'), ('Africa/Ouagadougou', 'Africa/Ouagadougou'), ('Africa/Porto-Novo', 'Africa/Porto-Novo'), ('Africa/Sao_Tome', 'Africa/Sao_Tome'), ('Africa/Tripoli', 'Africa/Tripoli'), ('Africa/Tunis', 'Africa/Tunis'), ('Africa/Windhoek', 'Africa/Windhoek'), ('America/Adak', 'America/Adak'), ('America/Anchorage', 'America/Anchorage'), ('America/Anguilla', 'America/Anguilla'), ('America/Antigua', 'America/Antigua'), ('America/Araguaina', 'America/Araguaina'), ('America/Argentina/La_Rioja', 'America/Argentina/La_Rioja'), ('America/Argentina/Rio_Gallegos', 'America/Argentina/Rio_Gallegos'), ('America/Argentina/Salta', 'America/Argentina/Salta'), ('America/Argentina/San_Juan', 'America/Argentina/San_Juan'), ('America/Argentina/San_Luis', 'America/Argentina/San_Luis'), ('America/Argentina/Tucuman', 'America/Argentina/Tucuman'), ('America/Argentina/Ushuaia', 'America/Argentina/Ushuaia'), ('America/Aruba', 'America/Aruba'), ('America/Asuncion', 'America/Asuncion'), ('America/Bahia', 'America/Bahia'), ('America/Bahia_Banderas', 'America/Bahia_Banderas'), ('America/Barbados', 'America/Barbados'), ('America/Belem', 'America/Belem'), ('America/Belize', 'America/Belize'), ('America/Blanc-Sablon', 'America/Blanc-Sablon'), ('America/Boa_Vista', 'America/Boa_Vista'), ('America/Bogota', 'America/Bogota'), ('America/Boise', 'America/Boise'), ('America/Buenos_Aires', 'America/Buenos_Aires'), ('America/Cambridge_Bay', 'America/Cambridge_Bay'), ('America/Campo_Grande', 'America/Campo_Grande'), ('America/Cancun', 'America/Cancun'), ('America/Caracas', 'America/Caracas'), ('America/Catamarca', 'America/Catamarca'), ('America/Cayenne', 'America/Cayenne'), ('America/Cayman', 'America/Cayman'), ('America/Chicago', 'America/Chicago'), ('America/Chihuahua', 'America/Chihuahua'), ('America/Ciudad_Juarez', 'America/Ciudad_Juarez'), ('America/Coral_Harbour', 'America/Coral_Harbour'), ('America/Cordoba', 'America/Cordoba'), ('America/Costa_Rica', 'America/Costa_Rica'), ('America/Creston', 'America/Creston'), ('America/Cuiaba', 'America/Cuiaba'), ('America/Curacao', 'America/Curacao'), ('America/Danmarkshavn', 'America/Danmarkshavn'), ('America/Dawson', 'America/Dawson'), ('America/Dawson_Creek', 'America/Dawson_Creek'), ('America/Denver', 'America/Denver'), ('America/Detroit', 'America/Detroit'), ('America/Dominica', 'America/Dominica'), ('America/Edmonton', 'America/Edmonton'), ('America/Eirunepe', 'America/Eirunepe'), ('America/El_Salvador', 'America/El_Salvador'), ('America/Fort_Nelson', 'America/Fort_Nelson'), ('America/Fortaleza', 'America/Fortaleza'), ('America/Glace_Bay', 'America/Glace_Bay'), ('America/Godthab', 'America/Godthab'), ('America/Goose_Bay', 'America/Goose_Bay'), ('America/Grand_Turk', 'America/Grand_Turk'), ('America/Grenada', 'America/Grenada'), ('America/Guadeloupe', 'America/Guadeloupe'), ('America/Guatemala', 'America/Guatemala'), ('America/Guayaquil', 'America/Guayaquil'), ('America/Guyana', 'America/Guyana'), ('America/Halifax', 'America/Halifax'), ('America/Havana', 'America/Havana'), ('America/Hermosillo', 'America/Hermosillo'), ('America/Indiana/Knox', 'America/Indiana/Knox'), ('America/Indiana/Marengo', 'America/Indiana/Marengo'), ('America/Indiana/Petersburg', 'America/Indiana/Petersburg'), ('America/Indiana/Tell_City', 'America/Indiana/Tell_City'), ('America/Indiana/Vevay', 'America/Indiana/Vevay'), ('America/Indiana/Vincennes', 'America/Indiana/Vincennes'), ('America/Indiana/Winamac', 'America/Indiana/Winamac'), ('America/Indianapolis', 'America/Indianapolis'), ('America/Inuvik', 'America/Inuvik'), ('America/Iqaluit', 'America/Iqaluit'), ('America/Jamaica', 'America/Jamaica'), ('America/Jujuy', 'America/Jujuy'), ('America/Juneau', 'America/Juneau'), ('America/Kentucky/Monticello', 'America/Kentucky/Monticello'), ('America/Kralendijk', 'America/Kralendijk'), ('America/La_Paz', 'America/La_Paz'), ('America/Lima', 'America/Lima'), ('America/Los_Angeles', 'America/Los_Angeles'), ('America/Louisville', 'America/Louisville'), ('America/Lower_Princes', 'America/Lower_Princes'), ('America/Maceio', 'America/Maceio'), ('America/Managua', 'America/Managua'), ('America/Manaus', 'America/Manaus'), ('America/Marigot', 'America/Marigot'), ('America/Martinique', 'America/Martinique'), ('America/Matamoros', 'America/Matamoros'), ('America/Mazatlan', 'America/Mazatlan'), ('America/Mendoza', 'America/Mendoza'), ('America/Menominee', 'America/Menominee'), ('America/Merida', 'America/Merida'), ('America/Metlakatla', 'America/Metlakatla'), ('America/Mexico_City', 'America/Mexico_City'), ('America/Miquelon', 'America/Miquelon'), ('America/Moncton', 'America/Moncton'), ('America/Monterrey', 'America/Monterrey'), ('America/Montevideo', 'America/Montevideo'), ('America/Montserrat', 'America/Montserrat'), ('America/Nassau', 'America/Nassau'), ('America/New_York', 'America/New_York'), ('America/Nome', 'America/Nome'), ('America/Noronha', 'America/Noronha'), ('America/North_Dakota/Beulah', 'America/North_Dakota/Beulah'), ('America/North_Dakota/Center', 'America/North_Dakota/Center'), ('America/North_Dakota/New_Salem', 'America/North_Dakota/New_Salem'), ('America/Ojinaga', 'America/Ojinaga'), ('America/Panama', 'America/Panama'), ('America/Paramaribo', 'America/Paramaribo'), ('America/Phoenix', 'America/Phoenix'), ('America/Port-au-Prince', 'America/Port-au-Prince'), ('America/Port_of_Spain', 'America/Port_of_Spain'), ('America/Porto_Velho', 'America/Porto_Velho'), ('America/Puerto_Rico', 'America/Puerto_Rico'), ('America/Punta_Arenas', 'America/Punta_Arenas'), ('America/Rankin_Inlet', 'America/Rankin_Inlet'), ('America/Recife', 'America/Recife'), ('America/Regina', 'America/Regina'), ('America/Resolute', 'America/Resolute'), ('America/Rio_Branco', 'America/Rio_Branco'), ('America/Santarem', 'America/Santarem'), ('America/Santiago', 'America/Santiago'), ('America/Santo_Domingo', 'America/Santo_Domingo'), ('America/Sao_Paulo', 'America/Sao_Paulo'), ('America/Scoresbysund', 'America/Scoresbysund'), ('America/Sitka', 'America/Sitka'), ('America/St_Barthelemy', 'America/St_Barthelemy'), ('America/St_Johns', 'America/St_Johns'), ('America/St_Kitts', 'America/St_Kitts'), ('America/St_Lucia', 'America/St_Lucia'), ('America/St_Thomas', 'America/St_Thomas'), ('America/St_Vincent', 'America/St_Vincent'), ('America/Swift_Current', 'America/Swift_Current'), ('America/Tegucigalpa', 'America/Tegucigalpa'), ('America/Thule', 'America/Thule'), ('America/Tijuana', 'America/Tijuana'), ('America/Toronto', 'America/Toronto'), ('America/Tortola', 'America/Tortola'), ('America/Vancouver', 'America/Vancouver'), ('America/Whitehorse', 'America/Whitehorse'), ('America/Winnipeg', 'America/Winnipeg'), ('America/Yakutat', 'America/Yakutat'), ('Antarctica/Casey', 'Antarctica/Casey'), ('Antarctica/Davis', 'Antarctica/Davis'), ('Antarctica/DumontDUrville', 'Antarctica/DumontDUrville'), ('Antarctica/Macquarie', 'Antarctica/Macquarie'), ('Antarctica/Mawson', 'Antarctica/Mawson'), ('Antarctica/McMurdo', 'Antarctica/McMurdo'), ('Antarctica/Palmer', 'Antarctica/Palmer'), ('Antarctica/Rothera', 'Antarctica/Rothera'), ('Antarctica/Syowa', 'Antarctica/Syowa'), ('Antarctica/Troll', 'Antarctica/Troll'), ('Antarctica/Vostok', 'Antarctica/Vostok'), ('Arctic/Longyearbyen', 'Arctic/Longyearbyen'), ('Asia/Aden', 'Asia/Aden'), ('Asia/Almaty', 'Asia/Almaty'), ('Asia/Amman', 'Asia/Amman'), ('Asia/Anadyr', 'Asia/Anadyr'), ('Asia/Aqtau', 'Asia/Aqtau'), ('Asia/Aqtobe', 'Asia/Aqtobe'), ('Asia/Ashgabat', 'Asia/Ashgabat'), ('Asia/Atyrau', 'Asia/Atyrau'), ('Asia/Baghdad', 'Asia/Baghdad'), ('Asia/Bahrain', 'Asia/Bahrain'), ('Asia/Baku', 'Asia/Baku'), ('Asia/Bangkok', 'Asia/Bangkok'), ('Asia/Barnaul', 'Asia/Barnaul'), ('Asia/Beirut', 'Asia/Beirut'), ('Asia/Bishkek', 'Asia/Bishkek'), ('Asia/Brunei', 'Asia/Brunei'), ('Asia/Calcutta', 'Asia/Calcutta'), ('Asia/Chita', 'Asia/Chita'), ('Asia/Colombo', 'Asia/Colombo'), ('Asia/Damascus', 'Asia/Damascus'), ('Asia/Dhaka', 'Asia/Dhaka'), ('Asia/Dili', 'Asia/Dili'), ('Asia/Dubai', 'Asia/Dubai'), ('Asia/Dushanbe', 'Asia/Dushanbe'), ('Asia/Famagusta', 'Asia/Famagusta'), ('Asia/Gaza', 'Asia/Gaza'), ('Asia/Hebron', 'Asia/Hebron'), ('Asia/Hong_Kong', 'Asia/Hong_Kong'), ('Asia/Hovd', 'Asia/Hovd'), ('Asia/Irkutsk', 'Asia/Irkutsk'), ('Asia/Jakarta', 'Asia/Jakarta'), ('Asia/Jayapura', 'Asia/Jayapura'), ('Asia/Jerusalem', 'Asia/Jerusalem'), ('Asia/Kabul', 'Asia/Kabul'), ('Asia/Kamchatka', 'Asia/Kamchatka'), ('Asia/Karachi', 'Asia/Karachi'), ('Asia/Katmandu', 'Asia/Katmandu'), ('Asia/Khandyga', 'Asia/Khandyga'), ('Asia/Krasnoyarsk', 'Asia/Krasnoyarsk'), ('Asia/Kuala_Lumpur', 'Asia/Kuala_Lumpur'), ('Asia/Kuching', 'Asia/Kuching'), ('Asia/Kuwait', 'Asia/Kuwait'), ('Asia/Macau', 'Asia/Macau'), ('Asia/Magadan', 'Asia/Magadan'), ('Asia/Makassar', 'Asia/Makassar'), ('Asia/Manila', 'Asia/Manila'), ('Asia/Muscat', 'Asia/Muscat'), ('Asia/Nicosia', 'Asia/Nicosia'), ('Asia/Novokuznetsk', 'Asia/Novokuznetsk'), ('Asia/Novosibirsk', 'Asia/Novosibirsk'), ('Asia/Omsk', 'Asia/Omsk'), ('Asia/Oral', 'Asia/Oral'), ('Asia/Phnom_Penh', 'Asia/Phnom_Penh'), ('Asia/Pontianak', 'Asia/Pontianak'), ('Asia/Pyongyang', 'Asia/Pyongyang'), ('Asia/Qatar', 'Asia/Qatar'), ('Asia/Qostanay', 'Asia/Qostanay'), ('Asia/Qyzylorda', 'Asia/Qyzylorda'), ('Asia/Rangoon', 'Asia/Rangoon'), ('Asia/Riyadh', 'Asia/Riyadh'), ('Asia/Saigon', 'Asia/Saigon'), ('Asia/Sakhalin', 'Asia/Sakhalin'), ('Asia/Samarkand', 'Asia/Samarkand'), ('Asia/Seoul', 'Asia/Seoul'), ('Asia/Shanghai', 'Asia/Shanghai'), ('Asia/Singapore', 'Asia/Singapore'), ('Asia/Srednekolymsk', 'Asia/Srednekolymsk'), ('Asia/Taipei', 'Asia/Taipei'), ('Asia/Tashkent', 'Asia/Tashkent'), ('Asia/Tbilisi', 'Asia/Tbilisi'), ('Asia/Tehran', 'Asia/Tehran'), ('Asia/Thimphu', 'Asia/Thimphu'), ('Asia/Tokyo', 'Asia/Tokyo'), ('Asia/Tomsk', 'Asia/Tomsk'), ('Asia/Ulaanbaatar', 'Asia/Ulaanbaatar'), ('Asia/Urumqi', 'Asia/Urumqi'), ('Asia/Ust-Nera', 'Asia/Ust-Nera'), ('Asia/Vientiane', 'Asia/Vientiane'), ('Asia/Vladivostok', 'Asia/Vladivostok'), ('Asia/Yakutsk', 'Asia/Yakutsk'), ('Asia/Yekaterinburg', 'Asia/Yekaterinburg'), ('Asia/Yerevan', 'Asia/Yerevan'), ('Atlantic/Azores', 'Atlantic/Azores'), ('Atlantic/Bermuda', 'Atlantic/Bermuda'), ('Atlantic/Canary', 'Atlantic/Canary'), ('Atlantic/Cape_Verde', 'Atlantic/Cape_Verde'), ('Atlantic/Faeroe', 'Atlantic/Faeroe'), ('Atlantic/Madeira', 'Atlantic/Madeira'), ('Atlantic/Reykjavik', 'Atlantic/Reykjavik'), ('Atlantic/South_Georgia', 'Atlantic/South_Georgia'), ('Atlantic/St_Helena', 'Atlantic/St_Helena'), ('Atlantic/Stanley', 'Atlantic/Stanley'), ('Australia/Adelaide', 'Australia/Adelaide'), ('Australia/Brisbane', 'Australia/Brisbane'), ('Australia/Broken_Hill', 'Australia/Broken_Hill'), ('Australia/Darwin', 'Australia/Darwin'), ('Australia/Eucla', 'Australia/Eucla'), ('Australia/Hobart', 'Australia/Hobart'), ('Australia/Lindeman', 'Australia/Lindeman'), ('Australia/Lord_Howe', 'Australia/Lord_Howe'), ('Australia/Melbourne', 'Australia/Melbourne'), ('Australia/Perth', 'Australia/Perth'), ('Australia/Sydney', 'Australia/Sydney'), ('Europe/Amsterdam', 'Europe/Amsterdam'), ('Europe/Andorra', 'Europe/Andorra'), ('Europe/Astrakhan', 'Europe/Astrakhan'), ('Europe/Athens', 'Europe/Athens'), ('Europe/Belgrade', 'Europe/Belgrade'), ('Europe/Berlin', 'Europe/Berlin'), ('Europe/Bratislava', 'Europe/Bratislava'), ('Europe/Brussels', 'Europe/Brussels'), ('Europe/Bucharest', 'Europe/Bucharest'), ('Europe/Budapest', 'Europe/Budapest'), ('Europe/Busingen', 'Europe/Busingen'), ('Europe/Chisinau', 'Europe/Chisinau'), ('Europe/Copenhagen', 'Europe/Copenhagen'), ('Europe/Dublin', 'Europe/Dublin'), ('Europe/Gibraltar', 'Europe/Gibraltar'), ('Europe/Guernsey', 'Europe/Guernsey'), ('Europe/Helsinki', 'Europe/Helsinki'), ('Europe/Isle_of_Man', 'Europe/Isle_of_Man'), ('Europe/Istanbul', 'Europe/Istanbul'), ('Europe/Jersey', 'Europe/Jersey'), ('Europe/Kaliningrad', 'Europe/Kaliningrad'), ('Europe/Kiev', 'Europe/Kiev'), ('Europe/Kirov', 'Europe/Kirov'), ('Europe/Lisbon', 'Europe/Lisbon'), ('Europe/Ljubljana', 'Europe/Ljubljana'), ('Europe/London', 'Europe/London'), ('Europe/Luxembourg', 'Europe/Luxembourg'), ('Europe/Madrid', 'Europe/Madrid'), ('Europe/Malta', 'Europe/Malta'), ('Europe/Mariehamn', 'Europe/Mariehamn'), ('Europe/Minsk', 'Europe/Minsk'), ('Europe/Monaco', 'Europe/Monaco'), ('Europe/Moscow', 'Europe/Moscow'), ('Europe/Oslo', 'Europe/Oslo'), ('Europe/Paris', 'Europe/Paris'), ('Europe/Podgorica', 'Europe/Podgorica'), ('Europe/Prague', 'Europe/Prague'), ('Europe/Riga', 'Europe/Riga'), ('Europe/Rome', 'Europe/Rome'), ('Europe/Samara', 'Europe/Samara'), ('Europe/San_Marino', 'Europe/San_Marino'), ('Europe/Sarajevo', 'Europe/Sarajevo'), ('Europe/Saratov', 'Europe/Saratov'), ('Europe/Simferopol', 'Europe/Simferopol'), ('Europe/Skopje', 'Europe/Skopje'), ('Europe/Sofia', 'Europe/Sofia'), ('Europe/Stockholm', 'Europe/Stockholm'), ('Europe/Tallinn', 'Europe/Tallinn'), ('Europe/Tirane', 'Europe/Tirane'), ('Europe/Ulyanovsk', 'Europe/Ulyanovsk'), ('Europe/Vaduz', 'Europe/Vaduz'), ('Europe/Vatican', 'Europe/Vatican'), ('Europe/Vienna', 'Europe/Vienna'), ('Europe/Vilnius', 'Europe/Vilnius'), ('Europe/Volgograd', 'Europe/Volgograd'), ('Europe/Warsaw', 'Europe/Warsaw'), ('Europe/Zagreb', 'Europe/Zagreb'), ('Europe/Zurich', 'Europe/Zurich'), ('Indian/Antananarivo', 'Indian/Antananarivo'), ('Indian/Chagos', 'Indian/Chagos'), ('Indian/Christmas', 'Indian/Christmas'), ('Indian/Cocos', 'Indian/Cocos'), ('Indian/Comoro', 'Indian/Comoro'), ('Indian/Kerguelen', 'Indian/Kerguelen'), ('Indian/Mahe', 'Indian/Mahe'), ('Indian/Maldives', 'Indian/Maldives'), ('Indian/Mauritius', 'Indian/Mauritius'), ('Indian/Mayotte', 'Indian/Mayotte'), ('Indian/Reunion', 'Indian/Reunion'), ('Pacific/Apia', 'Pacific/Apia'), ('Pacific/Auckland', 'Pacific/Auckland'), ('Pacific/Bougainville', 'Pacific/Bougainville'), ('Pacific/Chatham', 'Pacific/Chatham'), ('Pacific/Easter', 'Pacific/Easter'), ('Pacific/Efate', 'Pacific/Efate'), ('Pacific/Enderbury', 'Pacific/Enderbury'), ('Pacific/Fakaofo', 'Pacific/Fakaofo'), ('Pacific/Fiji', 'Pacific/Fiji'), ('Pacific/Funafuti', 'Pacific/Funafuti'), ('Pacific/Galapagos', 'Pacific/Galapagos'), ('Pacific/Gambier', 'Pacific/Gambier'), ('Pacific/Guadalcanal', 'Pacific/Guadalcanal'), ('Pacific/Guam', 'Pacific/Guam'), ('Pacific/Honolulu', 'Pacific/Honolulu'), ('Pacific/Kiritimati', 'Pacific/Kiritimati'), ('Pacific/Kosrae', 'Pacific/Kosrae'), ('Pacific/Kwajalein', 'Pacific/Kwajalein'), ('Pacific/Majuro', 'Pacific/Majuro'), ('Pacific/Marquesas', 'Pacific/Marquesas'), ('Pacific/Midway', 'Pacific/Midway'), ('Pacific/Nauru', 'Pacific/Nauru'), ('Pacific/Niue', 'Pacific/Niue'), ('Pacific/Norfolk', 'Pacific/Norfolk'), ('Pacific/Noumea', 'Pacific/Noumea'), ('Pacific/Pago_Pago', 'Pacific/Pago_Pago'), ('Pacific/Palau', 'Pacific/Palau'), ('Pacific/Pitcairn', 'Pacific/Pitcairn'), ('Pacific/Ponape', 'Pacific/Ponape'), ('Pacific/Port_Moresby', 'Pacific/Port_Moresby'), ('Pacific/Rarotonga', 'Pacific/Rarotonga'), ('Pacific/Saipan', 'Pacific/Saipan'), ('Pacific/Tahiti', 'Pacific/Tahiti'), ('Pacific/Tarawa', 'Pacific/Tarawa'), ('Pacific/Tongatapu', 'Pacific/Tongatapu'), ('Pacific/Truk', 'Pacific/Truk'), ('Pacific/Wake', 'Pacific/Wake'), ('Pacific/Wallis', 'Pacific/Wallis')], max_length=50, null=True), + ), + ] diff --git a/backend/server/adventures/migrations/0027_transportation_end_timezone_and_more.py b/backend/server/adventures/migrations/0027_transportation_end_timezone_and_more.py new file mode 100644 index 00000000..63312250 --- /dev/null +++ b/backend/server/adventures/migrations/0027_transportation_end_timezone_and_more.py @@ -0,0 +1,23 @@ +# Generated by Django 5.0.11 on 2025-05-10 15:40 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('adventures', '0026_visit_timezone'), + ] + + operations = [ + migrations.AddField( + model_name='transportation', + name='end_timezone', + field=models.CharField(blank=True, choices=[('Africa/Abidjan', 'Africa/Abidjan'), ('Africa/Accra', 'Africa/Accra'), ('Africa/Addis_Ababa', 'Africa/Addis_Ababa'), ('Africa/Algiers', 'Africa/Algiers'), ('Africa/Asmera', 'Africa/Asmera'), ('Africa/Bamako', 'Africa/Bamako'), ('Africa/Bangui', 'Africa/Bangui'), ('Africa/Banjul', 'Africa/Banjul'), ('Africa/Bissau', 'Africa/Bissau'), ('Africa/Blantyre', 'Africa/Blantyre'), ('Africa/Brazzaville', 'Africa/Brazzaville'), ('Africa/Bujumbura', 'Africa/Bujumbura'), ('Africa/Cairo', 'Africa/Cairo'), ('Africa/Casablanca', 'Africa/Casablanca'), ('Africa/Ceuta', 'Africa/Ceuta'), ('Africa/Conakry', 'Africa/Conakry'), ('Africa/Dakar', 'Africa/Dakar'), ('Africa/Dar_es_Salaam', 'Africa/Dar_es_Salaam'), ('Africa/Djibouti', 'Africa/Djibouti'), ('Africa/Douala', 'Africa/Douala'), ('Africa/El_Aaiun', 'Africa/El_Aaiun'), ('Africa/Freetown', 'Africa/Freetown'), ('Africa/Gaborone', 'Africa/Gaborone'), ('Africa/Harare', 'Africa/Harare'), ('Africa/Johannesburg', 'Africa/Johannesburg'), ('Africa/Juba', 'Africa/Juba'), ('Africa/Kampala', 'Africa/Kampala'), ('Africa/Khartoum', 'Africa/Khartoum'), ('Africa/Kigali', 'Africa/Kigali'), ('Africa/Kinshasa', 'Africa/Kinshasa'), ('Africa/Lagos', 'Africa/Lagos'), ('Africa/Libreville', 'Africa/Libreville'), ('Africa/Lome', 'Africa/Lome'), ('Africa/Luanda', 'Africa/Luanda'), ('Africa/Lubumbashi', 'Africa/Lubumbashi'), ('Africa/Lusaka', 'Africa/Lusaka'), ('Africa/Malabo', 'Africa/Malabo'), ('Africa/Maputo', 'Africa/Maputo'), ('Africa/Maseru', 'Africa/Maseru'), ('Africa/Mbabane', 'Africa/Mbabane'), ('Africa/Mogadishu', 'Africa/Mogadishu'), ('Africa/Monrovia', 'Africa/Monrovia'), ('Africa/Nairobi', 'Africa/Nairobi'), ('Africa/Ndjamena', 'Africa/Ndjamena'), ('Africa/Niamey', 'Africa/Niamey'), ('Africa/Nouakchott', 'Africa/Nouakchott'), ('Africa/Ouagadougou', 'Africa/Ouagadougou'), ('Africa/Porto-Novo', 'Africa/Porto-Novo'), ('Africa/Sao_Tome', 'Africa/Sao_Tome'), ('Africa/Tripoli', 'Africa/Tripoli'), ('Africa/Tunis', 'Africa/Tunis'), ('Africa/Windhoek', 'Africa/Windhoek'), ('America/Adak', 'America/Adak'), ('America/Anchorage', 'America/Anchorage'), ('America/Anguilla', 'America/Anguilla'), ('America/Antigua', 'America/Antigua'), ('America/Araguaina', 'America/Araguaina'), ('America/Argentina/La_Rioja', 'America/Argentina/La_Rioja'), ('America/Argentina/Rio_Gallegos', 'America/Argentina/Rio_Gallegos'), ('America/Argentina/Salta', 'America/Argentina/Salta'), ('America/Argentina/San_Juan', 'America/Argentina/San_Juan'), ('America/Argentina/San_Luis', 'America/Argentina/San_Luis'), ('America/Argentina/Tucuman', 'America/Argentina/Tucuman'), ('America/Argentina/Ushuaia', 'America/Argentina/Ushuaia'), ('America/Aruba', 'America/Aruba'), ('America/Asuncion', 'America/Asuncion'), ('America/Bahia', 'America/Bahia'), ('America/Bahia_Banderas', 'America/Bahia_Banderas'), ('America/Barbados', 'America/Barbados'), ('America/Belem', 'America/Belem'), ('America/Belize', 'America/Belize'), ('America/Blanc-Sablon', 'America/Blanc-Sablon'), ('America/Boa_Vista', 'America/Boa_Vista'), ('America/Bogota', 'America/Bogota'), ('America/Boise', 'America/Boise'), ('America/Buenos_Aires', 'America/Buenos_Aires'), ('America/Cambridge_Bay', 'America/Cambridge_Bay'), ('America/Campo_Grande', 'America/Campo_Grande'), ('America/Cancun', 'America/Cancun'), ('America/Caracas', 'America/Caracas'), ('America/Catamarca', 'America/Catamarca'), ('America/Cayenne', 'America/Cayenne'), ('America/Cayman', 'America/Cayman'), ('America/Chicago', 'America/Chicago'), ('America/Chihuahua', 'America/Chihuahua'), ('America/Ciudad_Juarez', 'America/Ciudad_Juarez'), ('America/Coral_Harbour', 'America/Coral_Harbour'), ('America/Cordoba', 'America/Cordoba'), ('America/Costa_Rica', 'America/Costa_Rica'), ('America/Creston', 'America/Creston'), ('America/Cuiaba', 'America/Cuiaba'), ('America/Curacao', 'America/Curacao'), ('America/Danmarkshavn', 'America/Danmarkshavn'), ('America/Dawson', 'America/Dawson'), ('America/Dawson_Creek', 'America/Dawson_Creek'), ('America/Denver', 'America/Denver'), ('America/Detroit', 'America/Detroit'), ('America/Dominica', 'America/Dominica'), ('America/Edmonton', 'America/Edmonton'), ('America/Eirunepe', 'America/Eirunepe'), ('America/El_Salvador', 'America/El_Salvador'), ('America/Fort_Nelson', 'America/Fort_Nelson'), ('America/Fortaleza', 'America/Fortaleza'), ('America/Glace_Bay', 'America/Glace_Bay'), ('America/Godthab', 'America/Godthab'), ('America/Goose_Bay', 'America/Goose_Bay'), ('America/Grand_Turk', 'America/Grand_Turk'), ('America/Grenada', 'America/Grenada'), ('America/Guadeloupe', 'America/Guadeloupe'), ('America/Guatemala', 'America/Guatemala'), ('America/Guayaquil', 'America/Guayaquil'), ('America/Guyana', 'America/Guyana'), ('America/Halifax', 'America/Halifax'), ('America/Havana', 'America/Havana'), ('America/Hermosillo', 'America/Hermosillo'), ('America/Indiana/Knox', 'America/Indiana/Knox'), ('America/Indiana/Marengo', 'America/Indiana/Marengo'), ('America/Indiana/Petersburg', 'America/Indiana/Petersburg'), ('America/Indiana/Tell_City', 'America/Indiana/Tell_City'), ('America/Indiana/Vevay', 'America/Indiana/Vevay'), ('America/Indiana/Vincennes', 'America/Indiana/Vincennes'), ('America/Indiana/Winamac', 'America/Indiana/Winamac'), ('America/Indianapolis', 'America/Indianapolis'), ('America/Inuvik', 'America/Inuvik'), ('America/Iqaluit', 'America/Iqaluit'), ('America/Jamaica', 'America/Jamaica'), ('America/Jujuy', 'America/Jujuy'), ('America/Juneau', 'America/Juneau'), ('America/Kentucky/Monticello', 'America/Kentucky/Monticello'), ('America/Kralendijk', 'America/Kralendijk'), ('America/La_Paz', 'America/La_Paz'), ('America/Lima', 'America/Lima'), ('America/Los_Angeles', 'America/Los_Angeles'), ('America/Louisville', 'America/Louisville'), ('America/Lower_Princes', 'America/Lower_Princes'), ('America/Maceio', 'America/Maceio'), ('America/Managua', 'America/Managua'), ('America/Manaus', 'America/Manaus'), ('America/Marigot', 'America/Marigot'), ('America/Martinique', 'America/Martinique'), ('America/Matamoros', 'America/Matamoros'), ('America/Mazatlan', 'America/Mazatlan'), ('America/Mendoza', 'America/Mendoza'), ('America/Menominee', 'America/Menominee'), ('America/Merida', 'America/Merida'), ('America/Metlakatla', 'America/Metlakatla'), ('America/Mexico_City', 'America/Mexico_City'), ('America/Miquelon', 'America/Miquelon'), ('America/Moncton', 'America/Moncton'), ('America/Monterrey', 'America/Monterrey'), ('America/Montevideo', 'America/Montevideo'), ('America/Montserrat', 'America/Montserrat'), ('America/Nassau', 'America/Nassau'), ('America/New_York', 'America/New_York'), ('America/Nome', 'America/Nome'), ('America/Noronha', 'America/Noronha'), ('America/North_Dakota/Beulah', 'America/North_Dakota/Beulah'), ('America/North_Dakota/Center', 'America/North_Dakota/Center'), ('America/North_Dakota/New_Salem', 'America/North_Dakota/New_Salem'), ('America/Ojinaga', 'America/Ojinaga'), ('America/Panama', 'America/Panama'), ('America/Paramaribo', 'America/Paramaribo'), ('America/Phoenix', 'America/Phoenix'), ('America/Port-au-Prince', 'America/Port-au-Prince'), ('America/Port_of_Spain', 'America/Port_of_Spain'), ('America/Porto_Velho', 'America/Porto_Velho'), ('America/Puerto_Rico', 'America/Puerto_Rico'), ('America/Punta_Arenas', 'America/Punta_Arenas'), ('America/Rankin_Inlet', 'America/Rankin_Inlet'), ('America/Recife', 'America/Recife'), ('America/Regina', 'America/Regina'), ('America/Resolute', 'America/Resolute'), ('America/Rio_Branco', 'America/Rio_Branco'), ('America/Santarem', 'America/Santarem'), ('America/Santiago', 'America/Santiago'), ('America/Santo_Domingo', 'America/Santo_Domingo'), ('America/Sao_Paulo', 'America/Sao_Paulo'), ('America/Scoresbysund', 'America/Scoresbysund'), ('America/Sitka', 'America/Sitka'), ('America/St_Barthelemy', 'America/St_Barthelemy'), ('America/St_Johns', 'America/St_Johns'), ('America/St_Kitts', 'America/St_Kitts'), ('America/St_Lucia', 'America/St_Lucia'), ('America/St_Thomas', 'America/St_Thomas'), ('America/St_Vincent', 'America/St_Vincent'), ('America/Swift_Current', 'America/Swift_Current'), ('America/Tegucigalpa', 'America/Tegucigalpa'), ('America/Thule', 'America/Thule'), ('America/Tijuana', 'America/Tijuana'), ('America/Toronto', 'America/Toronto'), ('America/Tortola', 'America/Tortola'), ('America/Vancouver', 'America/Vancouver'), ('America/Whitehorse', 'America/Whitehorse'), ('America/Winnipeg', 'America/Winnipeg'), ('America/Yakutat', 'America/Yakutat'), ('Antarctica/Casey', 'Antarctica/Casey'), ('Antarctica/Davis', 'Antarctica/Davis'), ('Antarctica/DumontDUrville', 'Antarctica/DumontDUrville'), ('Antarctica/Macquarie', 'Antarctica/Macquarie'), ('Antarctica/Mawson', 'Antarctica/Mawson'), ('Antarctica/McMurdo', 'Antarctica/McMurdo'), ('Antarctica/Palmer', 'Antarctica/Palmer'), ('Antarctica/Rothera', 'Antarctica/Rothera'), ('Antarctica/Syowa', 'Antarctica/Syowa'), ('Antarctica/Troll', 'Antarctica/Troll'), ('Antarctica/Vostok', 'Antarctica/Vostok'), ('Arctic/Longyearbyen', 'Arctic/Longyearbyen'), ('Asia/Aden', 'Asia/Aden'), ('Asia/Almaty', 'Asia/Almaty'), ('Asia/Amman', 'Asia/Amman'), ('Asia/Anadyr', 'Asia/Anadyr'), ('Asia/Aqtau', 'Asia/Aqtau'), ('Asia/Aqtobe', 'Asia/Aqtobe'), ('Asia/Ashgabat', 'Asia/Ashgabat'), ('Asia/Atyrau', 'Asia/Atyrau'), ('Asia/Baghdad', 'Asia/Baghdad'), ('Asia/Bahrain', 'Asia/Bahrain'), ('Asia/Baku', 'Asia/Baku'), ('Asia/Bangkok', 'Asia/Bangkok'), ('Asia/Barnaul', 'Asia/Barnaul'), ('Asia/Beirut', 'Asia/Beirut'), ('Asia/Bishkek', 'Asia/Bishkek'), ('Asia/Brunei', 'Asia/Brunei'), ('Asia/Calcutta', 'Asia/Calcutta'), ('Asia/Chita', 'Asia/Chita'), ('Asia/Colombo', 'Asia/Colombo'), ('Asia/Damascus', 'Asia/Damascus'), ('Asia/Dhaka', 'Asia/Dhaka'), ('Asia/Dili', 'Asia/Dili'), ('Asia/Dubai', 'Asia/Dubai'), ('Asia/Dushanbe', 'Asia/Dushanbe'), ('Asia/Famagusta', 'Asia/Famagusta'), ('Asia/Gaza', 'Asia/Gaza'), ('Asia/Hebron', 'Asia/Hebron'), ('Asia/Hong_Kong', 'Asia/Hong_Kong'), ('Asia/Hovd', 'Asia/Hovd'), ('Asia/Irkutsk', 'Asia/Irkutsk'), ('Asia/Jakarta', 'Asia/Jakarta'), ('Asia/Jayapura', 'Asia/Jayapura'), ('Asia/Jerusalem', 'Asia/Jerusalem'), ('Asia/Kabul', 'Asia/Kabul'), ('Asia/Kamchatka', 'Asia/Kamchatka'), ('Asia/Karachi', 'Asia/Karachi'), ('Asia/Katmandu', 'Asia/Katmandu'), ('Asia/Khandyga', 'Asia/Khandyga'), ('Asia/Krasnoyarsk', 'Asia/Krasnoyarsk'), ('Asia/Kuala_Lumpur', 'Asia/Kuala_Lumpur'), ('Asia/Kuching', 'Asia/Kuching'), ('Asia/Kuwait', 'Asia/Kuwait'), ('Asia/Macau', 'Asia/Macau'), ('Asia/Magadan', 'Asia/Magadan'), ('Asia/Makassar', 'Asia/Makassar'), ('Asia/Manila', 'Asia/Manila'), ('Asia/Muscat', 'Asia/Muscat'), ('Asia/Nicosia', 'Asia/Nicosia'), ('Asia/Novokuznetsk', 'Asia/Novokuznetsk'), ('Asia/Novosibirsk', 'Asia/Novosibirsk'), ('Asia/Omsk', 'Asia/Omsk'), ('Asia/Oral', 'Asia/Oral'), ('Asia/Phnom_Penh', 'Asia/Phnom_Penh'), ('Asia/Pontianak', 'Asia/Pontianak'), ('Asia/Pyongyang', 'Asia/Pyongyang'), ('Asia/Qatar', 'Asia/Qatar'), ('Asia/Qostanay', 'Asia/Qostanay'), ('Asia/Qyzylorda', 'Asia/Qyzylorda'), ('Asia/Rangoon', 'Asia/Rangoon'), ('Asia/Riyadh', 'Asia/Riyadh'), ('Asia/Saigon', 'Asia/Saigon'), ('Asia/Sakhalin', 'Asia/Sakhalin'), ('Asia/Samarkand', 'Asia/Samarkand'), ('Asia/Seoul', 'Asia/Seoul'), ('Asia/Shanghai', 'Asia/Shanghai'), ('Asia/Singapore', 'Asia/Singapore'), ('Asia/Srednekolymsk', 'Asia/Srednekolymsk'), ('Asia/Taipei', 'Asia/Taipei'), ('Asia/Tashkent', 'Asia/Tashkent'), ('Asia/Tbilisi', 'Asia/Tbilisi'), ('Asia/Tehran', 'Asia/Tehran'), ('Asia/Thimphu', 'Asia/Thimphu'), ('Asia/Tokyo', 'Asia/Tokyo'), ('Asia/Tomsk', 'Asia/Tomsk'), ('Asia/Ulaanbaatar', 'Asia/Ulaanbaatar'), ('Asia/Urumqi', 'Asia/Urumqi'), ('Asia/Ust-Nera', 'Asia/Ust-Nera'), ('Asia/Vientiane', 'Asia/Vientiane'), ('Asia/Vladivostok', 'Asia/Vladivostok'), ('Asia/Yakutsk', 'Asia/Yakutsk'), ('Asia/Yekaterinburg', 'Asia/Yekaterinburg'), ('Asia/Yerevan', 'Asia/Yerevan'), ('Atlantic/Azores', 'Atlantic/Azores'), ('Atlantic/Bermuda', 'Atlantic/Bermuda'), ('Atlantic/Canary', 'Atlantic/Canary'), ('Atlantic/Cape_Verde', 'Atlantic/Cape_Verde'), ('Atlantic/Faeroe', 'Atlantic/Faeroe'), ('Atlantic/Madeira', 'Atlantic/Madeira'), ('Atlantic/Reykjavik', 'Atlantic/Reykjavik'), ('Atlantic/South_Georgia', 'Atlantic/South_Georgia'), ('Atlantic/St_Helena', 'Atlantic/St_Helena'), ('Atlantic/Stanley', 'Atlantic/Stanley'), ('Australia/Adelaide', 'Australia/Adelaide'), ('Australia/Brisbane', 'Australia/Brisbane'), ('Australia/Broken_Hill', 'Australia/Broken_Hill'), ('Australia/Darwin', 'Australia/Darwin'), ('Australia/Eucla', 'Australia/Eucla'), ('Australia/Hobart', 'Australia/Hobart'), ('Australia/Lindeman', 'Australia/Lindeman'), ('Australia/Lord_Howe', 'Australia/Lord_Howe'), ('Australia/Melbourne', 'Australia/Melbourne'), ('Australia/Perth', 'Australia/Perth'), ('Australia/Sydney', 'Australia/Sydney'), ('Europe/Amsterdam', 'Europe/Amsterdam'), ('Europe/Andorra', 'Europe/Andorra'), ('Europe/Astrakhan', 'Europe/Astrakhan'), ('Europe/Athens', 'Europe/Athens'), ('Europe/Belgrade', 'Europe/Belgrade'), ('Europe/Berlin', 'Europe/Berlin'), ('Europe/Bratislava', 'Europe/Bratislava'), ('Europe/Brussels', 'Europe/Brussels'), ('Europe/Bucharest', 'Europe/Bucharest'), ('Europe/Budapest', 'Europe/Budapest'), ('Europe/Busingen', 'Europe/Busingen'), ('Europe/Chisinau', 'Europe/Chisinau'), ('Europe/Copenhagen', 'Europe/Copenhagen'), ('Europe/Dublin', 'Europe/Dublin'), ('Europe/Gibraltar', 'Europe/Gibraltar'), ('Europe/Guernsey', 'Europe/Guernsey'), ('Europe/Helsinki', 'Europe/Helsinki'), ('Europe/Isle_of_Man', 'Europe/Isle_of_Man'), ('Europe/Istanbul', 'Europe/Istanbul'), ('Europe/Jersey', 'Europe/Jersey'), ('Europe/Kaliningrad', 'Europe/Kaliningrad'), ('Europe/Kiev', 'Europe/Kiev'), ('Europe/Kirov', 'Europe/Kirov'), ('Europe/Lisbon', 'Europe/Lisbon'), ('Europe/Ljubljana', 'Europe/Ljubljana'), ('Europe/London', 'Europe/London'), ('Europe/Luxembourg', 'Europe/Luxembourg'), ('Europe/Madrid', 'Europe/Madrid'), ('Europe/Malta', 'Europe/Malta'), ('Europe/Mariehamn', 'Europe/Mariehamn'), ('Europe/Minsk', 'Europe/Minsk'), ('Europe/Monaco', 'Europe/Monaco'), ('Europe/Moscow', 'Europe/Moscow'), ('Europe/Oslo', 'Europe/Oslo'), ('Europe/Paris', 'Europe/Paris'), ('Europe/Podgorica', 'Europe/Podgorica'), ('Europe/Prague', 'Europe/Prague'), ('Europe/Riga', 'Europe/Riga'), ('Europe/Rome', 'Europe/Rome'), ('Europe/Samara', 'Europe/Samara'), ('Europe/San_Marino', 'Europe/San_Marino'), ('Europe/Sarajevo', 'Europe/Sarajevo'), ('Europe/Saratov', 'Europe/Saratov'), ('Europe/Simferopol', 'Europe/Simferopol'), ('Europe/Skopje', 'Europe/Skopje'), ('Europe/Sofia', 'Europe/Sofia'), ('Europe/Stockholm', 'Europe/Stockholm'), ('Europe/Tallinn', 'Europe/Tallinn'), ('Europe/Tirane', 'Europe/Tirane'), ('Europe/Ulyanovsk', 'Europe/Ulyanovsk'), ('Europe/Vaduz', 'Europe/Vaduz'), ('Europe/Vatican', 'Europe/Vatican'), ('Europe/Vienna', 'Europe/Vienna'), ('Europe/Vilnius', 'Europe/Vilnius'), ('Europe/Volgograd', 'Europe/Volgograd'), ('Europe/Warsaw', 'Europe/Warsaw'), ('Europe/Zagreb', 'Europe/Zagreb'), ('Europe/Zurich', 'Europe/Zurich'), ('Indian/Antananarivo', 'Indian/Antananarivo'), ('Indian/Chagos', 'Indian/Chagos'), ('Indian/Christmas', 'Indian/Christmas'), ('Indian/Cocos', 'Indian/Cocos'), ('Indian/Comoro', 'Indian/Comoro'), ('Indian/Kerguelen', 'Indian/Kerguelen'), ('Indian/Mahe', 'Indian/Mahe'), ('Indian/Maldives', 'Indian/Maldives'), ('Indian/Mauritius', 'Indian/Mauritius'), ('Indian/Mayotte', 'Indian/Mayotte'), ('Indian/Reunion', 'Indian/Reunion'), ('Pacific/Apia', 'Pacific/Apia'), ('Pacific/Auckland', 'Pacific/Auckland'), ('Pacific/Bougainville', 'Pacific/Bougainville'), ('Pacific/Chatham', 'Pacific/Chatham'), ('Pacific/Easter', 'Pacific/Easter'), ('Pacific/Efate', 'Pacific/Efate'), ('Pacific/Enderbury', 'Pacific/Enderbury'), ('Pacific/Fakaofo', 'Pacific/Fakaofo'), ('Pacific/Fiji', 'Pacific/Fiji'), ('Pacific/Funafuti', 'Pacific/Funafuti'), ('Pacific/Galapagos', 'Pacific/Galapagos'), ('Pacific/Gambier', 'Pacific/Gambier'), ('Pacific/Guadalcanal', 'Pacific/Guadalcanal'), ('Pacific/Guam', 'Pacific/Guam'), ('Pacific/Honolulu', 'Pacific/Honolulu'), ('Pacific/Kiritimati', 'Pacific/Kiritimati'), ('Pacific/Kosrae', 'Pacific/Kosrae'), ('Pacific/Kwajalein', 'Pacific/Kwajalein'), ('Pacific/Majuro', 'Pacific/Majuro'), ('Pacific/Marquesas', 'Pacific/Marquesas'), ('Pacific/Midway', 'Pacific/Midway'), ('Pacific/Nauru', 'Pacific/Nauru'), ('Pacific/Niue', 'Pacific/Niue'), ('Pacific/Norfolk', 'Pacific/Norfolk'), ('Pacific/Noumea', 'Pacific/Noumea'), ('Pacific/Pago_Pago', 'Pacific/Pago_Pago'), ('Pacific/Palau', 'Pacific/Palau'), ('Pacific/Pitcairn', 'Pacific/Pitcairn'), ('Pacific/Ponape', 'Pacific/Ponape'), ('Pacific/Port_Moresby', 'Pacific/Port_Moresby'), ('Pacific/Rarotonga', 'Pacific/Rarotonga'), ('Pacific/Saipan', 'Pacific/Saipan'), ('Pacific/Tahiti', 'Pacific/Tahiti'), ('Pacific/Tarawa', 'Pacific/Tarawa'), ('Pacific/Tongatapu', 'Pacific/Tongatapu'), ('Pacific/Truk', 'Pacific/Truk'), ('Pacific/Wake', 'Pacific/Wake'), ('Pacific/Wallis', 'Pacific/Wallis')], max_length=50, null=True), + ), + migrations.AddField( + model_name='transportation', + name='start_timezone', + field=models.CharField(blank=True, choices=[('Africa/Abidjan', 'Africa/Abidjan'), ('Africa/Accra', 'Africa/Accra'), ('Africa/Addis_Ababa', 'Africa/Addis_Ababa'), ('Africa/Algiers', 'Africa/Algiers'), ('Africa/Asmera', 'Africa/Asmera'), ('Africa/Bamako', 'Africa/Bamako'), ('Africa/Bangui', 'Africa/Bangui'), ('Africa/Banjul', 'Africa/Banjul'), ('Africa/Bissau', 'Africa/Bissau'), ('Africa/Blantyre', 'Africa/Blantyre'), ('Africa/Brazzaville', 'Africa/Brazzaville'), ('Africa/Bujumbura', 'Africa/Bujumbura'), ('Africa/Cairo', 'Africa/Cairo'), ('Africa/Casablanca', 'Africa/Casablanca'), ('Africa/Ceuta', 'Africa/Ceuta'), ('Africa/Conakry', 'Africa/Conakry'), ('Africa/Dakar', 'Africa/Dakar'), ('Africa/Dar_es_Salaam', 'Africa/Dar_es_Salaam'), ('Africa/Djibouti', 'Africa/Djibouti'), ('Africa/Douala', 'Africa/Douala'), ('Africa/El_Aaiun', 'Africa/El_Aaiun'), ('Africa/Freetown', 'Africa/Freetown'), ('Africa/Gaborone', 'Africa/Gaborone'), ('Africa/Harare', 'Africa/Harare'), ('Africa/Johannesburg', 'Africa/Johannesburg'), ('Africa/Juba', 'Africa/Juba'), ('Africa/Kampala', 'Africa/Kampala'), ('Africa/Khartoum', 'Africa/Khartoum'), ('Africa/Kigali', 'Africa/Kigali'), ('Africa/Kinshasa', 'Africa/Kinshasa'), ('Africa/Lagos', 'Africa/Lagos'), ('Africa/Libreville', 'Africa/Libreville'), ('Africa/Lome', 'Africa/Lome'), ('Africa/Luanda', 'Africa/Luanda'), ('Africa/Lubumbashi', 'Africa/Lubumbashi'), ('Africa/Lusaka', 'Africa/Lusaka'), ('Africa/Malabo', 'Africa/Malabo'), ('Africa/Maputo', 'Africa/Maputo'), ('Africa/Maseru', 'Africa/Maseru'), ('Africa/Mbabane', 'Africa/Mbabane'), ('Africa/Mogadishu', 'Africa/Mogadishu'), ('Africa/Monrovia', 'Africa/Monrovia'), ('Africa/Nairobi', 'Africa/Nairobi'), ('Africa/Ndjamena', 'Africa/Ndjamena'), ('Africa/Niamey', 'Africa/Niamey'), ('Africa/Nouakchott', 'Africa/Nouakchott'), ('Africa/Ouagadougou', 'Africa/Ouagadougou'), ('Africa/Porto-Novo', 'Africa/Porto-Novo'), ('Africa/Sao_Tome', 'Africa/Sao_Tome'), ('Africa/Tripoli', 'Africa/Tripoli'), ('Africa/Tunis', 'Africa/Tunis'), ('Africa/Windhoek', 'Africa/Windhoek'), ('America/Adak', 'America/Adak'), ('America/Anchorage', 'America/Anchorage'), ('America/Anguilla', 'America/Anguilla'), ('America/Antigua', 'America/Antigua'), ('America/Araguaina', 'America/Araguaina'), ('America/Argentina/La_Rioja', 'America/Argentina/La_Rioja'), ('America/Argentina/Rio_Gallegos', 'America/Argentina/Rio_Gallegos'), ('America/Argentina/Salta', 'America/Argentina/Salta'), ('America/Argentina/San_Juan', 'America/Argentina/San_Juan'), ('America/Argentina/San_Luis', 'America/Argentina/San_Luis'), ('America/Argentina/Tucuman', 'America/Argentina/Tucuman'), ('America/Argentina/Ushuaia', 'America/Argentina/Ushuaia'), ('America/Aruba', 'America/Aruba'), ('America/Asuncion', 'America/Asuncion'), ('America/Bahia', 'America/Bahia'), ('America/Bahia_Banderas', 'America/Bahia_Banderas'), ('America/Barbados', 'America/Barbados'), ('America/Belem', 'America/Belem'), ('America/Belize', 'America/Belize'), ('America/Blanc-Sablon', 'America/Blanc-Sablon'), ('America/Boa_Vista', 'America/Boa_Vista'), ('America/Bogota', 'America/Bogota'), ('America/Boise', 'America/Boise'), ('America/Buenos_Aires', 'America/Buenos_Aires'), ('America/Cambridge_Bay', 'America/Cambridge_Bay'), ('America/Campo_Grande', 'America/Campo_Grande'), ('America/Cancun', 'America/Cancun'), ('America/Caracas', 'America/Caracas'), ('America/Catamarca', 'America/Catamarca'), ('America/Cayenne', 'America/Cayenne'), ('America/Cayman', 'America/Cayman'), ('America/Chicago', 'America/Chicago'), ('America/Chihuahua', 'America/Chihuahua'), ('America/Ciudad_Juarez', 'America/Ciudad_Juarez'), ('America/Coral_Harbour', 'America/Coral_Harbour'), ('America/Cordoba', 'America/Cordoba'), ('America/Costa_Rica', 'America/Costa_Rica'), ('America/Creston', 'America/Creston'), ('America/Cuiaba', 'America/Cuiaba'), ('America/Curacao', 'America/Curacao'), ('America/Danmarkshavn', 'America/Danmarkshavn'), ('America/Dawson', 'America/Dawson'), ('America/Dawson_Creek', 'America/Dawson_Creek'), ('America/Denver', 'America/Denver'), ('America/Detroit', 'America/Detroit'), ('America/Dominica', 'America/Dominica'), ('America/Edmonton', 'America/Edmonton'), ('America/Eirunepe', 'America/Eirunepe'), ('America/El_Salvador', 'America/El_Salvador'), ('America/Fort_Nelson', 'America/Fort_Nelson'), ('America/Fortaleza', 'America/Fortaleza'), ('America/Glace_Bay', 'America/Glace_Bay'), ('America/Godthab', 'America/Godthab'), ('America/Goose_Bay', 'America/Goose_Bay'), ('America/Grand_Turk', 'America/Grand_Turk'), ('America/Grenada', 'America/Grenada'), ('America/Guadeloupe', 'America/Guadeloupe'), ('America/Guatemala', 'America/Guatemala'), ('America/Guayaquil', 'America/Guayaquil'), ('America/Guyana', 'America/Guyana'), ('America/Halifax', 'America/Halifax'), ('America/Havana', 'America/Havana'), ('America/Hermosillo', 'America/Hermosillo'), ('America/Indiana/Knox', 'America/Indiana/Knox'), ('America/Indiana/Marengo', 'America/Indiana/Marengo'), ('America/Indiana/Petersburg', 'America/Indiana/Petersburg'), ('America/Indiana/Tell_City', 'America/Indiana/Tell_City'), ('America/Indiana/Vevay', 'America/Indiana/Vevay'), ('America/Indiana/Vincennes', 'America/Indiana/Vincennes'), ('America/Indiana/Winamac', 'America/Indiana/Winamac'), ('America/Indianapolis', 'America/Indianapolis'), ('America/Inuvik', 'America/Inuvik'), ('America/Iqaluit', 'America/Iqaluit'), ('America/Jamaica', 'America/Jamaica'), ('America/Jujuy', 'America/Jujuy'), ('America/Juneau', 'America/Juneau'), ('America/Kentucky/Monticello', 'America/Kentucky/Monticello'), ('America/Kralendijk', 'America/Kralendijk'), ('America/La_Paz', 'America/La_Paz'), ('America/Lima', 'America/Lima'), ('America/Los_Angeles', 'America/Los_Angeles'), ('America/Louisville', 'America/Louisville'), ('America/Lower_Princes', 'America/Lower_Princes'), ('America/Maceio', 'America/Maceio'), ('America/Managua', 'America/Managua'), ('America/Manaus', 'America/Manaus'), ('America/Marigot', 'America/Marigot'), ('America/Martinique', 'America/Martinique'), ('America/Matamoros', 'America/Matamoros'), ('America/Mazatlan', 'America/Mazatlan'), ('America/Mendoza', 'America/Mendoza'), ('America/Menominee', 'America/Menominee'), ('America/Merida', 'America/Merida'), ('America/Metlakatla', 'America/Metlakatla'), ('America/Mexico_City', 'America/Mexico_City'), ('America/Miquelon', 'America/Miquelon'), ('America/Moncton', 'America/Moncton'), ('America/Monterrey', 'America/Monterrey'), ('America/Montevideo', 'America/Montevideo'), ('America/Montserrat', 'America/Montserrat'), ('America/Nassau', 'America/Nassau'), ('America/New_York', 'America/New_York'), ('America/Nome', 'America/Nome'), ('America/Noronha', 'America/Noronha'), ('America/North_Dakota/Beulah', 'America/North_Dakota/Beulah'), ('America/North_Dakota/Center', 'America/North_Dakota/Center'), ('America/North_Dakota/New_Salem', 'America/North_Dakota/New_Salem'), ('America/Ojinaga', 'America/Ojinaga'), ('America/Panama', 'America/Panama'), ('America/Paramaribo', 'America/Paramaribo'), ('America/Phoenix', 'America/Phoenix'), ('America/Port-au-Prince', 'America/Port-au-Prince'), ('America/Port_of_Spain', 'America/Port_of_Spain'), ('America/Porto_Velho', 'America/Porto_Velho'), ('America/Puerto_Rico', 'America/Puerto_Rico'), ('America/Punta_Arenas', 'America/Punta_Arenas'), ('America/Rankin_Inlet', 'America/Rankin_Inlet'), ('America/Recife', 'America/Recife'), ('America/Regina', 'America/Regina'), ('America/Resolute', 'America/Resolute'), ('America/Rio_Branco', 'America/Rio_Branco'), ('America/Santarem', 'America/Santarem'), ('America/Santiago', 'America/Santiago'), ('America/Santo_Domingo', 'America/Santo_Domingo'), ('America/Sao_Paulo', 'America/Sao_Paulo'), ('America/Scoresbysund', 'America/Scoresbysund'), ('America/Sitka', 'America/Sitka'), ('America/St_Barthelemy', 'America/St_Barthelemy'), ('America/St_Johns', 'America/St_Johns'), ('America/St_Kitts', 'America/St_Kitts'), ('America/St_Lucia', 'America/St_Lucia'), ('America/St_Thomas', 'America/St_Thomas'), ('America/St_Vincent', 'America/St_Vincent'), ('America/Swift_Current', 'America/Swift_Current'), ('America/Tegucigalpa', 'America/Tegucigalpa'), ('America/Thule', 'America/Thule'), ('America/Tijuana', 'America/Tijuana'), ('America/Toronto', 'America/Toronto'), ('America/Tortola', 'America/Tortola'), ('America/Vancouver', 'America/Vancouver'), ('America/Whitehorse', 'America/Whitehorse'), ('America/Winnipeg', 'America/Winnipeg'), ('America/Yakutat', 'America/Yakutat'), ('Antarctica/Casey', 'Antarctica/Casey'), ('Antarctica/Davis', 'Antarctica/Davis'), ('Antarctica/DumontDUrville', 'Antarctica/DumontDUrville'), ('Antarctica/Macquarie', 'Antarctica/Macquarie'), ('Antarctica/Mawson', 'Antarctica/Mawson'), ('Antarctica/McMurdo', 'Antarctica/McMurdo'), ('Antarctica/Palmer', 'Antarctica/Palmer'), ('Antarctica/Rothera', 'Antarctica/Rothera'), ('Antarctica/Syowa', 'Antarctica/Syowa'), ('Antarctica/Troll', 'Antarctica/Troll'), ('Antarctica/Vostok', 'Antarctica/Vostok'), ('Arctic/Longyearbyen', 'Arctic/Longyearbyen'), ('Asia/Aden', 'Asia/Aden'), ('Asia/Almaty', 'Asia/Almaty'), ('Asia/Amman', 'Asia/Amman'), ('Asia/Anadyr', 'Asia/Anadyr'), ('Asia/Aqtau', 'Asia/Aqtau'), ('Asia/Aqtobe', 'Asia/Aqtobe'), ('Asia/Ashgabat', 'Asia/Ashgabat'), ('Asia/Atyrau', 'Asia/Atyrau'), ('Asia/Baghdad', 'Asia/Baghdad'), ('Asia/Bahrain', 'Asia/Bahrain'), ('Asia/Baku', 'Asia/Baku'), ('Asia/Bangkok', 'Asia/Bangkok'), ('Asia/Barnaul', 'Asia/Barnaul'), ('Asia/Beirut', 'Asia/Beirut'), ('Asia/Bishkek', 'Asia/Bishkek'), ('Asia/Brunei', 'Asia/Brunei'), ('Asia/Calcutta', 'Asia/Calcutta'), ('Asia/Chita', 'Asia/Chita'), ('Asia/Colombo', 'Asia/Colombo'), ('Asia/Damascus', 'Asia/Damascus'), ('Asia/Dhaka', 'Asia/Dhaka'), ('Asia/Dili', 'Asia/Dili'), ('Asia/Dubai', 'Asia/Dubai'), ('Asia/Dushanbe', 'Asia/Dushanbe'), ('Asia/Famagusta', 'Asia/Famagusta'), ('Asia/Gaza', 'Asia/Gaza'), ('Asia/Hebron', 'Asia/Hebron'), ('Asia/Hong_Kong', 'Asia/Hong_Kong'), ('Asia/Hovd', 'Asia/Hovd'), ('Asia/Irkutsk', 'Asia/Irkutsk'), ('Asia/Jakarta', 'Asia/Jakarta'), ('Asia/Jayapura', 'Asia/Jayapura'), ('Asia/Jerusalem', 'Asia/Jerusalem'), ('Asia/Kabul', 'Asia/Kabul'), ('Asia/Kamchatka', 'Asia/Kamchatka'), ('Asia/Karachi', 'Asia/Karachi'), ('Asia/Katmandu', 'Asia/Katmandu'), ('Asia/Khandyga', 'Asia/Khandyga'), ('Asia/Krasnoyarsk', 'Asia/Krasnoyarsk'), ('Asia/Kuala_Lumpur', 'Asia/Kuala_Lumpur'), ('Asia/Kuching', 'Asia/Kuching'), ('Asia/Kuwait', 'Asia/Kuwait'), ('Asia/Macau', 'Asia/Macau'), ('Asia/Magadan', 'Asia/Magadan'), ('Asia/Makassar', 'Asia/Makassar'), ('Asia/Manila', 'Asia/Manila'), ('Asia/Muscat', 'Asia/Muscat'), ('Asia/Nicosia', 'Asia/Nicosia'), ('Asia/Novokuznetsk', 'Asia/Novokuznetsk'), ('Asia/Novosibirsk', 'Asia/Novosibirsk'), ('Asia/Omsk', 'Asia/Omsk'), ('Asia/Oral', 'Asia/Oral'), ('Asia/Phnom_Penh', 'Asia/Phnom_Penh'), ('Asia/Pontianak', 'Asia/Pontianak'), ('Asia/Pyongyang', 'Asia/Pyongyang'), ('Asia/Qatar', 'Asia/Qatar'), ('Asia/Qostanay', 'Asia/Qostanay'), ('Asia/Qyzylorda', 'Asia/Qyzylorda'), ('Asia/Rangoon', 'Asia/Rangoon'), ('Asia/Riyadh', 'Asia/Riyadh'), ('Asia/Saigon', 'Asia/Saigon'), ('Asia/Sakhalin', 'Asia/Sakhalin'), ('Asia/Samarkand', 'Asia/Samarkand'), ('Asia/Seoul', 'Asia/Seoul'), ('Asia/Shanghai', 'Asia/Shanghai'), ('Asia/Singapore', 'Asia/Singapore'), ('Asia/Srednekolymsk', 'Asia/Srednekolymsk'), ('Asia/Taipei', 'Asia/Taipei'), ('Asia/Tashkent', 'Asia/Tashkent'), ('Asia/Tbilisi', 'Asia/Tbilisi'), ('Asia/Tehran', 'Asia/Tehran'), ('Asia/Thimphu', 'Asia/Thimphu'), ('Asia/Tokyo', 'Asia/Tokyo'), ('Asia/Tomsk', 'Asia/Tomsk'), ('Asia/Ulaanbaatar', 'Asia/Ulaanbaatar'), ('Asia/Urumqi', 'Asia/Urumqi'), ('Asia/Ust-Nera', 'Asia/Ust-Nera'), ('Asia/Vientiane', 'Asia/Vientiane'), ('Asia/Vladivostok', 'Asia/Vladivostok'), ('Asia/Yakutsk', 'Asia/Yakutsk'), ('Asia/Yekaterinburg', 'Asia/Yekaterinburg'), ('Asia/Yerevan', 'Asia/Yerevan'), ('Atlantic/Azores', 'Atlantic/Azores'), ('Atlantic/Bermuda', 'Atlantic/Bermuda'), ('Atlantic/Canary', 'Atlantic/Canary'), ('Atlantic/Cape_Verde', 'Atlantic/Cape_Verde'), ('Atlantic/Faeroe', 'Atlantic/Faeroe'), ('Atlantic/Madeira', 'Atlantic/Madeira'), ('Atlantic/Reykjavik', 'Atlantic/Reykjavik'), ('Atlantic/South_Georgia', 'Atlantic/South_Georgia'), ('Atlantic/St_Helena', 'Atlantic/St_Helena'), ('Atlantic/Stanley', 'Atlantic/Stanley'), ('Australia/Adelaide', 'Australia/Adelaide'), ('Australia/Brisbane', 'Australia/Brisbane'), ('Australia/Broken_Hill', 'Australia/Broken_Hill'), ('Australia/Darwin', 'Australia/Darwin'), ('Australia/Eucla', 'Australia/Eucla'), ('Australia/Hobart', 'Australia/Hobart'), ('Australia/Lindeman', 'Australia/Lindeman'), ('Australia/Lord_Howe', 'Australia/Lord_Howe'), ('Australia/Melbourne', 'Australia/Melbourne'), ('Australia/Perth', 'Australia/Perth'), ('Australia/Sydney', 'Australia/Sydney'), ('Europe/Amsterdam', 'Europe/Amsterdam'), ('Europe/Andorra', 'Europe/Andorra'), ('Europe/Astrakhan', 'Europe/Astrakhan'), ('Europe/Athens', 'Europe/Athens'), ('Europe/Belgrade', 'Europe/Belgrade'), ('Europe/Berlin', 'Europe/Berlin'), ('Europe/Bratislava', 'Europe/Bratislava'), ('Europe/Brussels', 'Europe/Brussels'), ('Europe/Bucharest', 'Europe/Bucharest'), ('Europe/Budapest', 'Europe/Budapest'), ('Europe/Busingen', 'Europe/Busingen'), ('Europe/Chisinau', 'Europe/Chisinau'), ('Europe/Copenhagen', 'Europe/Copenhagen'), ('Europe/Dublin', 'Europe/Dublin'), ('Europe/Gibraltar', 'Europe/Gibraltar'), ('Europe/Guernsey', 'Europe/Guernsey'), ('Europe/Helsinki', 'Europe/Helsinki'), ('Europe/Isle_of_Man', 'Europe/Isle_of_Man'), ('Europe/Istanbul', 'Europe/Istanbul'), ('Europe/Jersey', 'Europe/Jersey'), ('Europe/Kaliningrad', 'Europe/Kaliningrad'), ('Europe/Kiev', 'Europe/Kiev'), ('Europe/Kirov', 'Europe/Kirov'), ('Europe/Lisbon', 'Europe/Lisbon'), ('Europe/Ljubljana', 'Europe/Ljubljana'), ('Europe/London', 'Europe/London'), ('Europe/Luxembourg', 'Europe/Luxembourg'), ('Europe/Madrid', 'Europe/Madrid'), ('Europe/Malta', 'Europe/Malta'), ('Europe/Mariehamn', 'Europe/Mariehamn'), ('Europe/Minsk', 'Europe/Minsk'), ('Europe/Monaco', 'Europe/Monaco'), ('Europe/Moscow', 'Europe/Moscow'), ('Europe/Oslo', 'Europe/Oslo'), ('Europe/Paris', 'Europe/Paris'), ('Europe/Podgorica', 'Europe/Podgorica'), ('Europe/Prague', 'Europe/Prague'), ('Europe/Riga', 'Europe/Riga'), ('Europe/Rome', 'Europe/Rome'), ('Europe/Samara', 'Europe/Samara'), ('Europe/San_Marino', 'Europe/San_Marino'), ('Europe/Sarajevo', 'Europe/Sarajevo'), ('Europe/Saratov', 'Europe/Saratov'), ('Europe/Simferopol', 'Europe/Simferopol'), ('Europe/Skopje', 'Europe/Skopje'), ('Europe/Sofia', 'Europe/Sofia'), ('Europe/Stockholm', 'Europe/Stockholm'), ('Europe/Tallinn', 'Europe/Tallinn'), ('Europe/Tirane', 'Europe/Tirane'), ('Europe/Ulyanovsk', 'Europe/Ulyanovsk'), ('Europe/Vaduz', 'Europe/Vaduz'), ('Europe/Vatican', 'Europe/Vatican'), ('Europe/Vienna', 'Europe/Vienna'), ('Europe/Vilnius', 'Europe/Vilnius'), ('Europe/Volgograd', 'Europe/Volgograd'), ('Europe/Warsaw', 'Europe/Warsaw'), ('Europe/Zagreb', 'Europe/Zagreb'), ('Europe/Zurich', 'Europe/Zurich'), ('Indian/Antananarivo', 'Indian/Antananarivo'), ('Indian/Chagos', 'Indian/Chagos'), ('Indian/Christmas', 'Indian/Christmas'), ('Indian/Cocos', 'Indian/Cocos'), ('Indian/Comoro', 'Indian/Comoro'), ('Indian/Kerguelen', 'Indian/Kerguelen'), ('Indian/Mahe', 'Indian/Mahe'), ('Indian/Maldives', 'Indian/Maldives'), ('Indian/Mauritius', 'Indian/Mauritius'), ('Indian/Mayotte', 'Indian/Mayotte'), ('Indian/Reunion', 'Indian/Reunion'), ('Pacific/Apia', 'Pacific/Apia'), ('Pacific/Auckland', 'Pacific/Auckland'), ('Pacific/Bougainville', 'Pacific/Bougainville'), ('Pacific/Chatham', 'Pacific/Chatham'), ('Pacific/Easter', 'Pacific/Easter'), ('Pacific/Efate', 'Pacific/Efate'), ('Pacific/Enderbury', 'Pacific/Enderbury'), ('Pacific/Fakaofo', 'Pacific/Fakaofo'), ('Pacific/Fiji', 'Pacific/Fiji'), ('Pacific/Funafuti', 'Pacific/Funafuti'), ('Pacific/Galapagos', 'Pacific/Galapagos'), ('Pacific/Gambier', 'Pacific/Gambier'), ('Pacific/Guadalcanal', 'Pacific/Guadalcanal'), ('Pacific/Guam', 'Pacific/Guam'), ('Pacific/Honolulu', 'Pacific/Honolulu'), ('Pacific/Kiritimati', 'Pacific/Kiritimati'), ('Pacific/Kosrae', 'Pacific/Kosrae'), ('Pacific/Kwajalein', 'Pacific/Kwajalein'), ('Pacific/Majuro', 'Pacific/Majuro'), ('Pacific/Marquesas', 'Pacific/Marquesas'), ('Pacific/Midway', 'Pacific/Midway'), ('Pacific/Nauru', 'Pacific/Nauru'), ('Pacific/Niue', 'Pacific/Niue'), ('Pacific/Norfolk', 'Pacific/Norfolk'), ('Pacific/Noumea', 'Pacific/Noumea'), ('Pacific/Pago_Pago', 'Pacific/Pago_Pago'), ('Pacific/Palau', 'Pacific/Palau'), ('Pacific/Pitcairn', 'Pacific/Pitcairn'), ('Pacific/Ponape', 'Pacific/Ponape'), ('Pacific/Port_Moresby', 'Pacific/Port_Moresby'), ('Pacific/Rarotonga', 'Pacific/Rarotonga'), ('Pacific/Saipan', 'Pacific/Saipan'), ('Pacific/Tahiti', 'Pacific/Tahiti'), ('Pacific/Tarawa', 'Pacific/Tarawa'), ('Pacific/Tongatapu', 'Pacific/Tongatapu'), ('Pacific/Truk', 'Pacific/Truk'), ('Pacific/Wake', 'Pacific/Wake'), ('Pacific/Wallis', 'Pacific/Wallis')], max_length=50, null=True), + ), + ] diff --git a/backend/server/adventures/migrations/0028_lodging_timezone.py b/backend/server/adventures/migrations/0028_lodging_timezone.py new file mode 100644 index 00000000..d9513f71 --- /dev/null +++ b/backend/server/adventures/migrations/0028_lodging_timezone.py @@ -0,0 +1,18 @@ +# Generated by Django 5.0.11 on 2025-05-10 15:54 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('adventures', '0027_transportation_end_timezone_and_more'), + ] + + operations = [ + migrations.AddField( + model_name='lodging', + name='timezone', + field=models.CharField(blank=True, choices=[('Africa/Abidjan', 'Africa/Abidjan'), ('Africa/Accra', 'Africa/Accra'), ('Africa/Addis_Ababa', 'Africa/Addis_Ababa'), ('Africa/Algiers', 'Africa/Algiers'), ('Africa/Asmera', 'Africa/Asmera'), ('Africa/Bamako', 'Africa/Bamako'), ('Africa/Bangui', 'Africa/Bangui'), ('Africa/Banjul', 'Africa/Banjul'), ('Africa/Bissau', 'Africa/Bissau'), ('Africa/Blantyre', 'Africa/Blantyre'), ('Africa/Brazzaville', 'Africa/Brazzaville'), ('Africa/Bujumbura', 'Africa/Bujumbura'), ('Africa/Cairo', 'Africa/Cairo'), ('Africa/Casablanca', 'Africa/Casablanca'), ('Africa/Ceuta', 'Africa/Ceuta'), ('Africa/Conakry', 'Africa/Conakry'), ('Africa/Dakar', 'Africa/Dakar'), ('Africa/Dar_es_Salaam', 'Africa/Dar_es_Salaam'), ('Africa/Djibouti', 'Africa/Djibouti'), ('Africa/Douala', 'Africa/Douala'), ('Africa/El_Aaiun', 'Africa/El_Aaiun'), ('Africa/Freetown', 'Africa/Freetown'), ('Africa/Gaborone', 'Africa/Gaborone'), ('Africa/Harare', 'Africa/Harare'), ('Africa/Johannesburg', 'Africa/Johannesburg'), ('Africa/Juba', 'Africa/Juba'), ('Africa/Kampala', 'Africa/Kampala'), ('Africa/Khartoum', 'Africa/Khartoum'), ('Africa/Kigali', 'Africa/Kigali'), ('Africa/Kinshasa', 'Africa/Kinshasa'), ('Africa/Lagos', 'Africa/Lagos'), ('Africa/Libreville', 'Africa/Libreville'), ('Africa/Lome', 'Africa/Lome'), ('Africa/Luanda', 'Africa/Luanda'), ('Africa/Lubumbashi', 'Africa/Lubumbashi'), ('Africa/Lusaka', 'Africa/Lusaka'), ('Africa/Malabo', 'Africa/Malabo'), ('Africa/Maputo', 'Africa/Maputo'), ('Africa/Maseru', 'Africa/Maseru'), ('Africa/Mbabane', 'Africa/Mbabane'), ('Africa/Mogadishu', 'Africa/Mogadishu'), ('Africa/Monrovia', 'Africa/Monrovia'), ('Africa/Nairobi', 'Africa/Nairobi'), ('Africa/Ndjamena', 'Africa/Ndjamena'), ('Africa/Niamey', 'Africa/Niamey'), ('Africa/Nouakchott', 'Africa/Nouakchott'), ('Africa/Ouagadougou', 'Africa/Ouagadougou'), ('Africa/Porto-Novo', 'Africa/Porto-Novo'), ('Africa/Sao_Tome', 'Africa/Sao_Tome'), ('Africa/Tripoli', 'Africa/Tripoli'), ('Africa/Tunis', 'Africa/Tunis'), ('Africa/Windhoek', 'Africa/Windhoek'), ('America/Adak', 'America/Adak'), ('America/Anchorage', 'America/Anchorage'), ('America/Anguilla', 'America/Anguilla'), ('America/Antigua', 'America/Antigua'), ('America/Araguaina', 'America/Araguaina'), ('America/Argentina/La_Rioja', 'America/Argentina/La_Rioja'), ('America/Argentina/Rio_Gallegos', 'America/Argentina/Rio_Gallegos'), ('America/Argentina/Salta', 'America/Argentina/Salta'), ('America/Argentina/San_Juan', 'America/Argentina/San_Juan'), ('America/Argentina/San_Luis', 'America/Argentina/San_Luis'), ('America/Argentina/Tucuman', 'America/Argentina/Tucuman'), ('America/Argentina/Ushuaia', 'America/Argentina/Ushuaia'), ('America/Aruba', 'America/Aruba'), ('America/Asuncion', 'America/Asuncion'), ('America/Bahia', 'America/Bahia'), ('America/Bahia_Banderas', 'America/Bahia_Banderas'), ('America/Barbados', 'America/Barbados'), ('America/Belem', 'America/Belem'), ('America/Belize', 'America/Belize'), ('America/Blanc-Sablon', 'America/Blanc-Sablon'), ('America/Boa_Vista', 'America/Boa_Vista'), ('America/Bogota', 'America/Bogota'), ('America/Boise', 'America/Boise'), ('America/Buenos_Aires', 'America/Buenos_Aires'), ('America/Cambridge_Bay', 'America/Cambridge_Bay'), ('America/Campo_Grande', 'America/Campo_Grande'), ('America/Cancun', 'America/Cancun'), ('America/Caracas', 'America/Caracas'), ('America/Catamarca', 'America/Catamarca'), ('America/Cayenne', 'America/Cayenne'), ('America/Cayman', 'America/Cayman'), ('America/Chicago', 'America/Chicago'), ('America/Chihuahua', 'America/Chihuahua'), ('America/Ciudad_Juarez', 'America/Ciudad_Juarez'), ('America/Coral_Harbour', 'America/Coral_Harbour'), ('America/Cordoba', 'America/Cordoba'), ('America/Costa_Rica', 'America/Costa_Rica'), ('America/Creston', 'America/Creston'), ('America/Cuiaba', 'America/Cuiaba'), ('America/Curacao', 'America/Curacao'), ('America/Danmarkshavn', 'America/Danmarkshavn'), ('America/Dawson', 'America/Dawson'), ('America/Dawson_Creek', 'America/Dawson_Creek'), ('America/Denver', 'America/Denver'), ('America/Detroit', 'America/Detroit'), ('America/Dominica', 'America/Dominica'), ('America/Edmonton', 'America/Edmonton'), ('America/Eirunepe', 'America/Eirunepe'), ('America/El_Salvador', 'America/El_Salvador'), ('America/Fort_Nelson', 'America/Fort_Nelson'), ('America/Fortaleza', 'America/Fortaleza'), ('America/Glace_Bay', 'America/Glace_Bay'), ('America/Godthab', 'America/Godthab'), ('America/Goose_Bay', 'America/Goose_Bay'), ('America/Grand_Turk', 'America/Grand_Turk'), ('America/Grenada', 'America/Grenada'), ('America/Guadeloupe', 'America/Guadeloupe'), ('America/Guatemala', 'America/Guatemala'), ('America/Guayaquil', 'America/Guayaquil'), ('America/Guyana', 'America/Guyana'), ('America/Halifax', 'America/Halifax'), ('America/Havana', 'America/Havana'), ('America/Hermosillo', 'America/Hermosillo'), ('America/Indiana/Knox', 'America/Indiana/Knox'), ('America/Indiana/Marengo', 'America/Indiana/Marengo'), ('America/Indiana/Petersburg', 'America/Indiana/Petersburg'), ('America/Indiana/Tell_City', 'America/Indiana/Tell_City'), ('America/Indiana/Vevay', 'America/Indiana/Vevay'), ('America/Indiana/Vincennes', 'America/Indiana/Vincennes'), ('America/Indiana/Winamac', 'America/Indiana/Winamac'), ('America/Indianapolis', 'America/Indianapolis'), ('America/Inuvik', 'America/Inuvik'), ('America/Iqaluit', 'America/Iqaluit'), ('America/Jamaica', 'America/Jamaica'), ('America/Jujuy', 'America/Jujuy'), ('America/Juneau', 'America/Juneau'), ('America/Kentucky/Monticello', 'America/Kentucky/Monticello'), ('America/Kralendijk', 'America/Kralendijk'), ('America/La_Paz', 'America/La_Paz'), ('America/Lima', 'America/Lima'), ('America/Los_Angeles', 'America/Los_Angeles'), ('America/Louisville', 'America/Louisville'), ('America/Lower_Princes', 'America/Lower_Princes'), ('America/Maceio', 'America/Maceio'), ('America/Managua', 'America/Managua'), ('America/Manaus', 'America/Manaus'), ('America/Marigot', 'America/Marigot'), ('America/Martinique', 'America/Martinique'), ('America/Matamoros', 'America/Matamoros'), ('America/Mazatlan', 'America/Mazatlan'), ('America/Mendoza', 'America/Mendoza'), ('America/Menominee', 'America/Menominee'), ('America/Merida', 'America/Merida'), ('America/Metlakatla', 'America/Metlakatla'), ('America/Mexico_City', 'America/Mexico_City'), ('America/Miquelon', 'America/Miquelon'), ('America/Moncton', 'America/Moncton'), ('America/Monterrey', 'America/Monterrey'), ('America/Montevideo', 'America/Montevideo'), ('America/Montserrat', 'America/Montserrat'), ('America/Nassau', 'America/Nassau'), ('America/New_York', 'America/New_York'), ('America/Nome', 'America/Nome'), ('America/Noronha', 'America/Noronha'), ('America/North_Dakota/Beulah', 'America/North_Dakota/Beulah'), ('America/North_Dakota/Center', 'America/North_Dakota/Center'), ('America/North_Dakota/New_Salem', 'America/North_Dakota/New_Salem'), ('America/Ojinaga', 'America/Ojinaga'), ('America/Panama', 'America/Panama'), ('America/Paramaribo', 'America/Paramaribo'), ('America/Phoenix', 'America/Phoenix'), ('America/Port-au-Prince', 'America/Port-au-Prince'), ('America/Port_of_Spain', 'America/Port_of_Spain'), ('America/Porto_Velho', 'America/Porto_Velho'), ('America/Puerto_Rico', 'America/Puerto_Rico'), ('America/Punta_Arenas', 'America/Punta_Arenas'), ('America/Rankin_Inlet', 'America/Rankin_Inlet'), ('America/Recife', 'America/Recife'), ('America/Regina', 'America/Regina'), ('America/Resolute', 'America/Resolute'), ('America/Rio_Branco', 'America/Rio_Branco'), ('America/Santarem', 'America/Santarem'), ('America/Santiago', 'America/Santiago'), ('America/Santo_Domingo', 'America/Santo_Domingo'), ('America/Sao_Paulo', 'America/Sao_Paulo'), ('America/Scoresbysund', 'America/Scoresbysund'), ('America/Sitka', 'America/Sitka'), ('America/St_Barthelemy', 'America/St_Barthelemy'), ('America/St_Johns', 'America/St_Johns'), ('America/St_Kitts', 'America/St_Kitts'), ('America/St_Lucia', 'America/St_Lucia'), ('America/St_Thomas', 'America/St_Thomas'), ('America/St_Vincent', 'America/St_Vincent'), ('America/Swift_Current', 'America/Swift_Current'), ('America/Tegucigalpa', 'America/Tegucigalpa'), ('America/Thule', 'America/Thule'), ('America/Tijuana', 'America/Tijuana'), ('America/Toronto', 'America/Toronto'), ('America/Tortola', 'America/Tortola'), ('America/Vancouver', 'America/Vancouver'), ('America/Whitehorse', 'America/Whitehorse'), ('America/Winnipeg', 'America/Winnipeg'), ('America/Yakutat', 'America/Yakutat'), ('Antarctica/Casey', 'Antarctica/Casey'), ('Antarctica/Davis', 'Antarctica/Davis'), ('Antarctica/DumontDUrville', 'Antarctica/DumontDUrville'), ('Antarctica/Macquarie', 'Antarctica/Macquarie'), ('Antarctica/Mawson', 'Antarctica/Mawson'), ('Antarctica/McMurdo', 'Antarctica/McMurdo'), ('Antarctica/Palmer', 'Antarctica/Palmer'), ('Antarctica/Rothera', 'Antarctica/Rothera'), ('Antarctica/Syowa', 'Antarctica/Syowa'), ('Antarctica/Troll', 'Antarctica/Troll'), ('Antarctica/Vostok', 'Antarctica/Vostok'), ('Arctic/Longyearbyen', 'Arctic/Longyearbyen'), ('Asia/Aden', 'Asia/Aden'), ('Asia/Almaty', 'Asia/Almaty'), ('Asia/Amman', 'Asia/Amman'), ('Asia/Anadyr', 'Asia/Anadyr'), ('Asia/Aqtau', 'Asia/Aqtau'), ('Asia/Aqtobe', 'Asia/Aqtobe'), ('Asia/Ashgabat', 'Asia/Ashgabat'), ('Asia/Atyrau', 'Asia/Atyrau'), ('Asia/Baghdad', 'Asia/Baghdad'), ('Asia/Bahrain', 'Asia/Bahrain'), ('Asia/Baku', 'Asia/Baku'), ('Asia/Bangkok', 'Asia/Bangkok'), ('Asia/Barnaul', 'Asia/Barnaul'), ('Asia/Beirut', 'Asia/Beirut'), ('Asia/Bishkek', 'Asia/Bishkek'), ('Asia/Brunei', 'Asia/Brunei'), ('Asia/Calcutta', 'Asia/Calcutta'), ('Asia/Chita', 'Asia/Chita'), ('Asia/Colombo', 'Asia/Colombo'), ('Asia/Damascus', 'Asia/Damascus'), ('Asia/Dhaka', 'Asia/Dhaka'), ('Asia/Dili', 'Asia/Dili'), ('Asia/Dubai', 'Asia/Dubai'), ('Asia/Dushanbe', 'Asia/Dushanbe'), ('Asia/Famagusta', 'Asia/Famagusta'), ('Asia/Gaza', 'Asia/Gaza'), ('Asia/Hebron', 'Asia/Hebron'), ('Asia/Hong_Kong', 'Asia/Hong_Kong'), ('Asia/Hovd', 'Asia/Hovd'), ('Asia/Irkutsk', 'Asia/Irkutsk'), ('Asia/Jakarta', 'Asia/Jakarta'), ('Asia/Jayapura', 'Asia/Jayapura'), ('Asia/Jerusalem', 'Asia/Jerusalem'), ('Asia/Kabul', 'Asia/Kabul'), ('Asia/Kamchatka', 'Asia/Kamchatka'), ('Asia/Karachi', 'Asia/Karachi'), ('Asia/Katmandu', 'Asia/Katmandu'), ('Asia/Khandyga', 'Asia/Khandyga'), ('Asia/Krasnoyarsk', 'Asia/Krasnoyarsk'), ('Asia/Kuala_Lumpur', 'Asia/Kuala_Lumpur'), ('Asia/Kuching', 'Asia/Kuching'), ('Asia/Kuwait', 'Asia/Kuwait'), ('Asia/Macau', 'Asia/Macau'), ('Asia/Magadan', 'Asia/Magadan'), ('Asia/Makassar', 'Asia/Makassar'), ('Asia/Manila', 'Asia/Manila'), ('Asia/Muscat', 'Asia/Muscat'), ('Asia/Nicosia', 'Asia/Nicosia'), ('Asia/Novokuznetsk', 'Asia/Novokuznetsk'), ('Asia/Novosibirsk', 'Asia/Novosibirsk'), ('Asia/Omsk', 'Asia/Omsk'), ('Asia/Oral', 'Asia/Oral'), ('Asia/Phnom_Penh', 'Asia/Phnom_Penh'), ('Asia/Pontianak', 'Asia/Pontianak'), ('Asia/Pyongyang', 'Asia/Pyongyang'), ('Asia/Qatar', 'Asia/Qatar'), ('Asia/Qostanay', 'Asia/Qostanay'), ('Asia/Qyzylorda', 'Asia/Qyzylorda'), ('Asia/Rangoon', 'Asia/Rangoon'), ('Asia/Riyadh', 'Asia/Riyadh'), ('Asia/Saigon', 'Asia/Saigon'), ('Asia/Sakhalin', 'Asia/Sakhalin'), ('Asia/Samarkand', 'Asia/Samarkand'), ('Asia/Seoul', 'Asia/Seoul'), ('Asia/Shanghai', 'Asia/Shanghai'), ('Asia/Singapore', 'Asia/Singapore'), ('Asia/Srednekolymsk', 'Asia/Srednekolymsk'), ('Asia/Taipei', 'Asia/Taipei'), ('Asia/Tashkent', 'Asia/Tashkent'), ('Asia/Tbilisi', 'Asia/Tbilisi'), ('Asia/Tehran', 'Asia/Tehran'), ('Asia/Thimphu', 'Asia/Thimphu'), ('Asia/Tokyo', 'Asia/Tokyo'), ('Asia/Tomsk', 'Asia/Tomsk'), ('Asia/Ulaanbaatar', 'Asia/Ulaanbaatar'), ('Asia/Urumqi', 'Asia/Urumqi'), ('Asia/Ust-Nera', 'Asia/Ust-Nera'), ('Asia/Vientiane', 'Asia/Vientiane'), ('Asia/Vladivostok', 'Asia/Vladivostok'), ('Asia/Yakutsk', 'Asia/Yakutsk'), ('Asia/Yekaterinburg', 'Asia/Yekaterinburg'), ('Asia/Yerevan', 'Asia/Yerevan'), ('Atlantic/Azores', 'Atlantic/Azores'), ('Atlantic/Bermuda', 'Atlantic/Bermuda'), ('Atlantic/Canary', 'Atlantic/Canary'), ('Atlantic/Cape_Verde', 'Atlantic/Cape_Verde'), ('Atlantic/Faeroe', 'Atlantic/Faeroe'), ('Atlantic/Madeira', 'Atlantic/Madeira'), ('Atlantic/Reykjavik', 'Atlantic/Reykjavik'), ('Atlantic/South_Georgia', 'Atlantic/South_Georgia'), ('Atlantic/St_Helena', 'Atlantic/St_Helena'), ('Atlantic/Stanley', 'Atlantic/Stanley'), ('Australia/Adelaide', 'Australia/Adelaide'), ('Australia/Brisbane', 'Australia/Brisbane'), ('Australia/Broken_Hill', 'Australia/Broken_Hill'), ('Australia/Darwin', 'Australia/Darwin'), ('Australia/Eucla', 'Australia/Eucla'), ('Australia/Hobart', 'Australia/Hobart'), ('Australia/Lindeman', 'Australia/Lindeman'), ('Australia/Lord_Howe', 'Australia/Lord_Howe'), ('Australia/Melbourne', 'Australia/Melbourne'), ('Australia/Perth', 'Australia/Perth'), ('Australia/Sydney', 'Australia/Sydney'), ('Europe/Amsterdam', 'Europe/Amsterdam'), ('Europe/Andorra', 'Europe/Andorra'), ('Europe/Astrakhan', 'Europe/Astrakhan'), ('Europe/Athens', 'Europe/Athens'), ('Europe/Belgrade', 'Europe/Belgrade'), ('Europe/Berlin', 'Europe/Berlin'), ('Europe/Bratislava', 'Europe/Bratislava'), ('Europe/Brussels', 'Europe/Brussels'), ('Europe/Bucharest', 'Europe/Bucharest'), ('Europe/Budapest', 'Europe/Budapest'), ('Europe/Busingen', 'Europe/Busingen'), ('Europe/Chisinau', 'Europe/Chisinau'), ('Europe/Copenhagen', 'Europe/Copenhagen'), ('Europe/Dublin', 'Europe/Dublin'), ('Europe/Gibraltar', 'Europe/Gibraltar'), ('Europe/Guernsey', 'Europe/Guernsey'), ('Europe/Helsinki', 'Europe/Helsinki'), ('Europe/Isle_of_Man', 'Europe/Isle_of_Man'), ('Europe/Istanbul', 'Europe/Istanbul'), ('Europe/Jersey', 'Europe/Jersey'), ('Europe/Kaliningrad', 'Europe/Kaliningrad'), ('Europe/Kiev', 'Europe/Kiev'), ('Europe/Kirov', 'Europe/Kirov'), ('Europe/Lisbon', 'Europe/Lisbon'), ('Europe/Ljubljana', 'Europe/Ljubljana'), ('Europe/London', 'Europe/London'), ('Europe/Luxembourg', 'Europe/Luxembourg'), ('Europe/Madrid', 'Europe/Madrid'), ('Europe/Malta', 'Europe/Malta'), ('Europe/Mariehamn', 'Europe/Mariehamn'), ('Europe/Minsk', 'Europe/Minsk'), ('Europe/Monaco', 'Europe/Monaco'), ('Europe/Moscow', 'Europe/Moscow'), ('Europe/Oslo', 'Europe/Oslo'), ('Europe/Paris', 'Europe/Paris'), ('Europe/Podgorica', 'Europe/Podgorica'), ('Europe/Prague', 'Europe/Prague'), ('Europe/Riga', 'Europe/Riga'), ('Europe/Rome', 'Europe/Rome'), ('Europe/Samara', 'Europe/Samara'), ('Europe/San_Marino', 'Europe/San_Marino'), ('Europe/Sarajevo', 'Europe/Sarajevo'), ('Europe/Saratov', 'Europe/Saratov'), ('Europe/Simferopol', 'Europe/Simferopol'), ('Europe/Skopje', 'Europe/Skopje'), ('Europe/Sofia', 'Europe/Sofia'), ('Europe/Stockholm', 'Europe/Stockholm'), ('Europe/Tallinn', 'Europe/Tallinn'), ('Europe/Tirane', 'Europe/Tirane'), ('Europe/Ulyanovsk', 'Europe/Ulyanovsk'), ('Europe/Vaduz', 'Europe/Vaduz'), ('Europe/Vatican', 'Europe/Vatican'), ('Europe/Vienna', 'Europe/Vienna'), ('Europe/Vilnius', 'Europe/Vilnius'), ('Europe/Volgograd', 'Europe/Volgograd'), ('Europe/Warsaw', 'Europe/Warsaw'), ('Europe/Zagreb', 'Europe/Zagreb'), ('Europe/Zurich', 'Europe/Zurich'), ('Indian/Antananarivo', 'Indian/Antananarivo'), ('Indian/Chagos', 'Indian/Chagos'), ('Indian/Christmas', 'Indian/Christmas'), ('Indian/Cocos', 'Indian/Cocos'), ('Indian/Comoro', 'Indian/Comoro'), ('Indian/Kerguelen', 'Indian/Kerguelen'), ('Indian/Mahe', 'Indian/Mahe'), ('Indian/Maldives', 'Indian/Maldives'), ('Indian/Mauritius', 'Indian/Mauritius'), ('Indian/Mayotte', 'Indian/Mayotte'), ('Indian/Reunion', 'Indian/Reunion'), ('Pacific/Apia', 'Pacific/Apia'), ('Pacific/Auckland', 'Pacific/Auckland'), ('Pacific/Bougainville', 'Pacific/Bougainville'), ('Pacific/Chatham', 'Pacific/Chatham'), ('Pacific/Easter', 'Pacific/Easter'), ('Pacific/Efate', 'Pacific/Efate'), ('Pacific/Enderbury', 'Pacific/Enderbury'), ('Pacific/Fakaofo', 'Pacific/Fakaofo'), ('Pacific/Fiji', 'Pacific/Fiji'), ('Pacific/Funafuti', 'Pacific/Funafuti'), ('Pacific/Galapagos', 'Pacific/Galapagos'), ('Pacific/Gambier', 'Pacific/Gambier'), ('Pacific/Guadalcanal', 'Pacific/Guadalcanal'), ('Pacific/Guam', 'Pacific/Guam'), ('Pacific/Honolulu', 'Pacific/Honolulu'), ('Pacific/Kiritimati', 'Pacific/Kiritimati'), ('Pacific/Kosrae', 'Pacific/Kosrae'), ('Pacific/Kwajalein', 'Pacific/Kwajalein'), ('Pacific/Majuro', 'Pacific/Majuro'), ('Pacific/Marquesas', 'Pacific/Marquesas'), ('Pacific/Midway', 'Pacific/Midway'), ('Pacific/Nauru', 'Pacific/Nauru'), ('Pacific/Niue', 'Pacific/Niue'), ('Pacific/Norfolk', 'Pacific/Norfolk'), ('Pacific/Noumea', 'Pacific/Noumea'), ('Pacific/Pago_Pago', 'Pacific/Pago_Pago'), ('Pacific/Palau', 'Pacific/Palau'), ('Pacific/Pitcairn', 'Pacific/Pitcairn'), ('Pacific/Ponape', 'Pacific/Ponape'), ('Pacific/Port_Moresby', 'Pacific/Port_Moresby'), ('Pacific/Rarotonga', 'Pacific/Rarotonga'), ('Pacific/Saipan', 'Pacific/Saipan'), ('Pacific/Tahiti', 'Pacific/Tahiti'), ('Pacific/Tarawa', 'Pacific/Tarawa'), ('Pacific/Tongatapu', 'Pacific/Tongatapu'), ('Pacific/Truk', 'Pacific/Truk'), ('Pacific/Wake', 'Pacific/Wake'), ('Pacific/Wallis', 'Pacific/Wallis')], max_length=50, null=True), + ), + ] diff --git a/backend/server/adventures/migrations/0029_adventure_city_adventure_country_adventure_region.py b/backend/server/adventures/migrations/0029_adventure_city_adventure_country_adventure_region.py new file mode 100644 index 00000000..3df78e09 --- /dev/null +++ b/backend/server/adventures/migrations/0029_adventure_city_adventure_country_adventure_region.py @@ -0,0 +1,30 @@ +# Generated by Django 5.0.11 on 2025-05-22 22:48 + +import django.db.models.deletion +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('adventures', '0028_lodging_timezone'), + ('worldtravel', '0015_city_insert_id_country_insert_id_region_insert_id'), + ] + + operations = [ + migrations.AddField( + model_name='adventure', + name='city', + field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to='worldtravel.city'), + ), + migrations.AddField( + model_name='adventure', + name='country', + field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to='worldtravel.country'), + ), + migrations.AddField( + model_name='adventure', + name='region', + field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to='worldtravel.region'), + ), + ] diff --git a/backend/server/adventures/migrations/0030_set_end_date_equal_start.py b/backend/server/adventures/migrations/0030_set_end_date_equal_start.py new file mode 100644 index 00000000..55d5f931 --- /dev/null +++ b/backend/server/adventures/migrations/0030_set_end_date_equal_start.py @@ -0,0 +1,18 @@ +from django.db import migrations + +def set_end_date_equal_to_start(apps, schema_editor): + Visit = apps.get_model('adventures', 'Visit') + for visit in Visit.objects.filter(end_date__isnull=True): + if visit.start_date: + visit.end_date = visit.start_date + visit.save() + +class Migration(migrations.Migration): + + dependencies = [ + ('adventures', '0029_adventure_city_adventure_country_adventure_region'), + ] + + operations = [ + migrations.RunPython(set_end_date_equal_to_start), + ] diff --git a/backend/server/adventures/migrations/0031_adventureimage_immich_id_alter_adventureimage_image_and_more.py b/backend/server/adventures/migrations/0031_adventureimage_immich_id_alter_adventureimage_image_and_more.py new file mode 100644 index 00000000..75ebb60a --- /dev/null +++ b/backend/server/adventures/migrations/0031_adventureimage_immich_id_alter_adventureimage_image_and_more.py @@ -0,0 +1,31 @@ +# Generated by Django 5.2.1 on 2025-06-01 16:57 + +import adventures.models +import django_resized.forms +from django.conf import settings +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('adventures', '0030_set_end_date_equal_start'), + migrations.swappable_dependency(settings.AUTH_USER_MODEL), + ] + + operations = [ + migrations.AddField( + model_name='adventureimage', + name='immich_id', + field=models.CharField(blank=True, max_length=200, null=True), + ), + migrations.AlterField( + model_name='adventureimage', + name='image', + field=django_resized.forms.ResizedImageField(blank=True, crop=None, force_format='WEBP', keep_meta=True, null=True, quality=75, scale=None, size=[1920, 1080], upload_to=adventures.models.PathAndRename('images/')), + ), + migrations.AddConstraint( + model_name='adventureimage', + constraint=models.CheckConstraint(condition=models.Q(models.Q(('image__isnull', False), ('immich_id__isnull', True)), models.Q(('image__isnull', True), ('immich_id__isnull', False)), _connector='OR'), name='image_xor_immich_id'), + ), + ] diff --git a/backend/server/adventures/migrations/0032_remove_adventureimage_image_xor_immich_id.py b/backend/server/adventures/migrations/0032_remove_adventureimage_image_xor_immich_id.py new file mode 100644 index 00000000..2ae80765 --- /dev/null +++ b/backend/server/adventures/migrations/0032_remove_adventureimage_image_xor_immich_id.py @@ -0,0 +1,17 @@ +# Generated by Django 5.2.1 on 2025-06-01 17:18 + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('adventures', '0031_adventureimage_immich_id_alter_adventureimage_image_and_more'), + ] + + operations = [ + migrations.RemoveConstraint( + model_name='adventureimage', + name='image_xor_immich_id', + ), + ] diff --git a/backend/server/adventures/migrations/0033_adventureimage_unique_immich_id_per_user.py b/backend/server/adventures/migrations/0033_adventureimage_unique_immich_id_per_user.py new file mode 100644 index 00000000..d3b1bb57 --- /dev/null +++ b/backend/server/adventures/migrations/0033_adventureimage_unique_immich_id_per_user.py @@ -0,0 +1,19 @@ +# Generated by Django 5.2.1 on 2025-06-02 02:31 + +from django.conf import settings +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('adventures', '0032_remove_adventureimage_image_xor_immich_id'), + migrations.swappable_dependency(settings.AUTH_USER_MODEL), + ] + + operations = [ + migrations.AddConstraint( + model_name='adventureimage', + constraint=models.UniqueConstraint(fields=('immich_id', 'user_id'), name='unique_immich_id_per_user'), + ), + ] diff --git a/backend/server/adventures/migrations/0034_remove_adventureimage_unique_immich_id_per_user.py b/backend/server/adventures/migrations/0034_remove_adventureimage_unique_immich_id_per_user.py new file mode 100644 index 00000000..52b3e521 --- /dev/null +++ b/backend/server/adventures/migrations/0034_remove_adventureimage_unique_immich_id_per_user.py @@ -0,0 +1,17 @@ +# Generated by Django 5.2.1 on 2025-06-02 02:44 + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('adventures', '0033_adventureimage_unique_immich_id_per_user'), + ] + + operations = [ + migrations.RemoveConstraint( + model_name='adventureimage', + name='unique_immich_id_per_user', + ), + ] diff --git a/backend/server/adventures/models.py b/backend/server/adventures/models.py index c7f78ca1..91be2d7d 100644 --- a/backend/server/adventures/models.py +++ b/backend/server/adventures/models.py @@ -5,16 +5,59 @@ from django.db import models from django.utils.deconstruct import deconstructible from adventures.managers import AdventureManager +import threading from django.contrib.auth import get_user_model from django.contrib.postgres.fields import ArrayField from django.forms import ValidationError from django_resized import ResizedImageField +from worldtravel.models import City, Country, Region, VisitedCity, VisitedRegion +from django.core.exceptions import ValidationError +from django.utils import timezone + +def background_geocode_and_assign(adventure_id: str): + try: + adventure = Adventure.objects.get(id=adventure_id) + if not (adventure.latitude and adventure.longitude): + return + + from adventures.geocoding import reverse_geocode # or wherever you defined it + is_visited = adventure.is_visited_status() + result = reverse_geocode(adventure.latitude, adventure.longitude, adventure.user_id) + + if 'region_id' in result: + region = Region.objects.filter(id=result['region_id']).first() + if region: + adventure.region = region + if is_visited: + VisitedRegion.objects.get_or_create(user_id=adventure.user_id, region=region) + + if 'city_id' in result: + city = City.objects.filter(id=result['city_id']).first() + if city: + adventure.city = city + if is_visited: + VisitedCity.objects.get_or_create(user_id=adventure.user_id, city=city) + + if 'country_id' in result: + country = Country.objects.filter(country_code=result['country_id']).first() + if country: + adventure.country = country + + # Save updated location info + # Save updated location info, skip geocode threading + adventure.save(update_fields=["region", "city", "country"], _skip_geocode=True) + + # print(f"[Adventure Geocode Thread] Successfully processed {adventure_id}: {adventure.name} - {adventure.latitude}, {adventure.longitude}") + + except Exception as e: + # Optional: log or print the error + print(f"[Adventure Geocode Thread] Error processing {adventure_id}: {e}") def validate_file_extension(value): import os from django.core.exceptions import ValidationError ext = os.path.splitext(value.name)[1] # [0] returns path+filename - valid_extensions = ['.pdf', '.doc', '.docx', '.xls', '.xlsx', '.ppt', '.pptx', '.txt', '.png', '.jpg', '.jpeg', '.gif', '.webp', '.mp4', '.mov', '.avi', '.mkv', '.mp3', '.wav', '.flac', '.ogg', '.m4a', '.wma', '.aac', '.opus', '.zip', '.rar', '.7z', '.tar', '.gz', '.bz2', '.xz', '.zst', '.lz4', '.lzma', '.lzo', '.z', '.tar.gz', '.tar.bz2', '.tar.xz', '.tar.zst', '.tar.lz4', '.tar.lzma', '.tar.lzo', '.tar.z', 'gpx', 'md', 'pdf'] + valid_extensions = ['.pdf', '.doc', '.docx', '.xls', '.xlsx', '.ppt', '.pptx', '.txt', '.png', '.jpg', '.jpeg', '.gif', '.webp', '.mp4', '.mov', '.avi', '.mkv', '.mp3', '.wav', '.flac', '.ogg', '.m4a', '.wma', '.aac', '.opus', '.zip', '.rar', '.7z', '.tar', '.gz', '.bz2', '.xz', '.zst', '.lz4', '.lzma', '.lzo', '.z', '.tar.gz', '.tar.bz2', '.tar.xz', '.tar.zst', '.tar.lz4', '.tar.lzma', '.tar.lzo', '.tar.z', '.gpx', '.md'] if not ext.lower() in valid_extensions: raise ValidationError('Unsupported file extension.') @@ -43,6 +86,426 @@ def validate_file_extension(value): ('other', 'Other') ] +TIMEZONES = [ + "Africa/Abidjan", + "Africa/Accra", + "Africa/Addis_Ababa", + "Africa/Algiers", + "Africa/Asmera", + "Africa/Bamako", + "Africa/Bangui", + "Africa/Banjul", + "Africa/Bissau", + "Africa/Blantyre", + "Africa/Brazzaville", + "Africa/Bujumbura", + "Africa/Cairo", + "Africa/Casablanca", + "Africa/Ceuta", + "Africa/Conakry", + "Africa/Dakar", + "Africa/Dar_es_Salaam", + "Africa/Djibouti", + "Africa/Douala", + "Africa/El_Aaiun", + "Africa/Freetown", + "Africa/Gaborone", + "Africa/Harare", + "Africa/Johannesburg", + "Africa/Juba", + "Africa/Kampala", + "Africa/Khartoum", + "Africa/Kigali", + "Africa/Kinshasa", + "Africa/Lagos", + "Africa/Libreville", + "Africa/Lome", + "Africa/Luanda", + "Africa/Lubumbashi", + "Africa/Lusaka", + "Africa/Malabo", + "Africa/Maputo", + "Africa/Maseru", + "Africa/Mbabane", + "Africa/Mogadishu", + "Africa/Monrovia", + "Africa/Nairobi", + "Africa/Ndjamena", + "Africa/Niamey", + "Africa/Nouakchott", + "Africa/Ouagadougou", + "Africa/Porto-Novo", + "Africa/Sao_Tome", + "Africa/Tripoli", + "Africa/Tunis", + "Africa/Windhoek", + "America/Adak", + "America/Anchorage", + "America/Anguilla", + "America/Antigua", + "America/Araguaina", + "America/Argentina/La_Rioja", + "America/Argentina/Rio_Gallegos", + "America/Argentina/Salta", + "America/Argentina/San_Juan", + "America/Argentina/San_Luis", + "America/Argentina/Tucuman", + "America/Argentina/Ushuaia", + "America/Aruba", + "America/Asuncion", + "America/Bahia", + "America/Bahia_Banderas", + "America/Barbados", + "America/Belem", + "America/Belize", + "America/Blanc-Sablon", + "America/Boa_Vista", + "America/Bogota", + "America/Boise", + "America/Buenos_Aires", + "America/Cambridge_Bay", + "America/Campo_Grande", + "America/Cancun", + "America/Caracas", + "America/Catamarca", + "America/Cayenne", + "America/Cayman", + "America/Chicago", + "America/Chihuahua", + "America/Ciudad_Juarez", + "America/Coral_Harbour", + "America/Cordoba", + "America/Costa_Rica", + "America/Creston", + "America/Cuiaba", + "America/Curacao", + "America/Danmarkshavn", + "America/Dawson", + "America/Dawson_Creek", + "America/Denver", + "America/Detroit", + "America/Dominica", + "America/Edmonton", + "America/Eirunepe", + "America/El_Salvador", + "America/Fort_Nelson", + "America/Fortaleza", + "America/Glace_Bay", + "America/Godthab", + "America/Goose_Bay", + "America/Grand_Turk", + "America/Grenada", + "America/Guadeloupe", + "America/Guatemala", + "America/Guayaquil", + "America/Guyana", + "America/Halifax", + "America/Havana", + "America/Hermosillo", + "America/Indiana/Knox", + "America/Indiana/Marengo", + "America/Indiana/Petersburg", + "America/Indiana/Tell_City", + "America/Indiana/Vevay", + "America/Indiana/Vincennes", + "America/Indiana/Winamac", + "America/Indianapolis", + "America/Inuvik", + "America/Iqaluit", + "America/Jamaica", + "America/Jujuy", + "America/Juneau", + "America/Kentucky/Monticello", + "America/Kralendijk", + "America/La_Paz", + "America/Lima", + "America/Los_Angeles", + "America/Louisville", + "America/Lower_Princes", + "America/Maceio", + "America/Managua", + "America/Manaus", + "America/Marigot", + "America/Martinique", + "America/Matamoros", + "America/Mazatlan", + "America/Mendoza", + "America/Menominee", + "America/Merida", + "America/Metlakatla", + "America/Mexico_City", + "America/Miquelon", + "America/Moncton", + "America/Monterrey", + "America/Montevideo", + "America/Montserrat", + "America/Nassau", + "America/New_York", + "America/Nome", + "America/Noronha", + "America/North_Dakota/Beulah", + "America/North_Dakota/Center", + "America/North_Dakota/New_Salem", + "America/Ojinaga", + "America/Panama", + "America/Paramaribo", + "America/Phoenix", + "America/Port-au-Prince", + "America/Port_of_Spain", + "America/Porto_Velho", + "America/Puerto_Rico", + "America/Punta_Arenas", + "America/Rankin_Inlet", + "America/Recife", + "America/Regina", + "America/Resolute", + "America/Rio_Branco", + "America/Santarem", + "America/Santiago", + "America/Santo_Domingo", + "America/Sao_Paulo", + "America/Scoresbysund", + "America/Sitka", + "America/St_Barthelemy", + "America/St_Johns", + "America/St_Kitts", + "America/St_Lucia", + "America/St_Thomas", + "America/St_Vincent", + "America/Swift_Current", + "America/Tegucigalpa", + "America/Thule", + "America/Tijuana", + "America/Toronto", + "America/Tortola", + "America/Vancouver", + "America/Whitehorse", + "America/Winnipeg", + "America/Yakutat", + "Antarctica/Casey", + "Antarctica/Davis", + "Antarctica/DumontDUrville", + "Antarctica/Macquarie", + "Antarctica/Mawson", + "Antarctica/McMurdo", + "Antarctica/Palmer", + "Antarctica/Rothera", + "Antarctica/Syowa", + "Antarctica/Troll", + "Antarctica/Vostok", + "Arctic/Longyearbyen", + "Asia/Aden", + "Asia/Almaty", + "Asia/Amman", + "Asia/Anadyr", + "Asia/Aqtau", + "Asia/Aqtobe", + "Asia/Ashgabat", + "Asia/Atyrau", + "Asia/Baghdad", + "Asia/Bahrain", + "Asia/Baku", + "Asia/Bangkok", + "Asia/Barnaul", + "Asia/Beirut", + "Asia/Bishkek", + "Asia/Brunei", + "Asia/Calcutta", + "Asia/Chita", + "Asia/Colombo", + "Asia/Damascus", + "Asia/Dhaka", + "Asia/Dili", + "Asia/Dubai", + "Asia/Dushanbe", + "Asia/Famagusta", + "Asia/Gaza", + "Asia/Hebron", + "Asia/Hong_Kong", + "Asia/Hovd", + "Asia/Irkutsk", + "Asia/Jakarta", + "Asia/Jayapura", + "Asia/Jerusalem", + "Asia/Kabul", + "Asia/Kamchatka", + "Asia/Karachi", + "Asia/Katmandu", + "Asia/Khandyga", + "Asia/Krasnoyarsk", + "Asia/Kuala_Lumpur", + "Asia/Kuching", + "Asia/Kuwait", + "Asia/Macau", + "Asia/Magadan", + "Asia/Makassar", + "Asia/Manila", + "Asia/Muscat", + "Asia/Nicosia", + "Asia/Novokuznetsk", + "Asia/Novosibirsk", + "Asia/Omsk", + "Asia/Oral", + "Asia/Phnom_Penh", + "Asia/Pontianak", + "Asia/Pyongyang", + "Asia/Qatar", + "Asia/Qostanay", + "Asia/Qyzylorda", + "Asia/Rangoon", + "Asia/Riyadh", + "Asia/Saigon", + "Asia/Sakhalin", + "Asia/Samarkand", + "Asia/Seoul", + "Asia/Shanghai", + "Asia/Singapore", + "Asia/Srednekolymsk", + "Asia/Taipei", + "Asia/Tashkent", + "Asia/Tbilisi", + "Asia/Tehran", + "Asia/Thimphu", + "Asia/Tokyo", + "Asia/Tomsk", + "Asia/Ulaanbaatar", + "Asia/Urumqi", + "Asia/Ust-Nera", + "Asia/Vientiane", + "Asia/Vladivostok", + "Asia/Yakutsk", + "Asia/Yekaterinburg", + "Asia/Yerevan", + "Atlantic/Azores", + "Atlantic/Bermuda", + "Atlantic/Canary", + "Atlantic/Cape_Verde", + "Atlantic/Faeroe", + "Atlantic/Madeira", + "Atlantic/Reykjavik", + "Atlantic/South_Georgia", + "Atlantic/St_Helena", + "Atlantic/Stanley", + "Australia/Adelaide", + "Australia/Brisbane", + "Australia/Broken_Hill", + "Australia/Darwin", + "Australia/Eucla", + "Australia/Hobart", + "Australia/Lindeman", + "Australia/Lord_Howe", + "Australia/Melbourne", + "Australia/Perth", + "Australia/Sydney", + "Europe/Amsterdam", + "Europe/Andorra", + "Europe/Astrakhan", + "Europe/Athens", + "Europe/Belgrade", + "Europe/Berlin", + "Europe/Bratislava", + "Europe/Brussels", + "Europe/Bucharest", + "Europe/Budapest", + "Europe/Busingen", + "Europe/Chisinau", + "Europe/Copenhagen", + "Europe/Dublin", + "Europe/Gibraltar", + "Europe/Guernsey", + "Europe/Helsinki", + "Europe/Isle_of_Man", + "Europe/Istanbul", + "Europe/Jersey", + "Europe/Kaliningrad", + "Europe/Kiev", + "Europe/Kirov", + "Europe/Lisbon", + "Europe/Ljubljana", + "Europe/London", + "Europe/Luxembourg", + "Europe/Madrid", + "Europe/Malta", + "Europe/Mariehamn", + "Europe/Minsk", + "Europe/Monaco", + "Europe/Moscow", + "Europe/Oslo", + "Europe/Paris", + "Europe/Podgorica", + "Europe/Prague", + "Europe/Riga", + "Europe/Rome", + "Europe/Samara", + "Europe/San_Marino", + "Europe/Sarajevo", + "Europe/Saratov", + "Europe/Simferopol", + "Europe/Skopje", + "Europe/Sofia", + "Europe/Stockholm", + "Europe/Tallinn", + "Europe/Tirane", + "Europe/Ulyanovsk", + "Europe/Vaduz", + "Europe/Vatican", + "Europe/Vienna", + "Europe/Vilnius", + "Europe/Volgograd", + "Europe/Warsaw", + "Europe/Zagreb", + "Europe/Zurich", + "Indian/Antananarivo", + "Indian/Chagos", + "Indian/Christmas", + "Indian/Cocos", + "Indian/Comoro", + "Indian/Kerguelen", + "Indian/Mahe", + "Indian/Maldives", + "Indian/Mauritius", + "Indian/Mayotte", + "Indian/Reunion", + "Pacific/Apia", + "Pacific/Auckland", + "Pacific/Bougainville", + "Pacific/Chatham", + "Pacific/Easter", + "Pacific/Efate", + "Pacific/Enderbury", + "Pacific/Fakaofo", + "Pacific/Fiji", + "Pacific/Funafuti", + "Pacific/Galapagos", + "Pacific/Gambier", + "Pacific/Guadalcanal", + "Pacific/Guam", + "Pacific/Honolulu", + "Pacific/Kiritimati", + "Pacific/Kosrae", + "Pacific/Kwajalein", + "Pacific/Majuro", + "Pacific/Marquesas", + "Pacific/Midway", + "Pacific/Nauru", + "Pacific/Niue", + "Pacific/Norfolk", + "Pacific/Noumea", + "Pacific/Pago_Pago", + "Pacific/Palau", + "Pacific/Pitcairn", + "Pacific/Ponape", + "Pacific/Port_Moresby", + "Pacific/Rarotonga", + "Pacific/Saipan", + "Pacific/Tahiti", + "Pacific/Tarawa", + "Pacific/Tongatapu", + "Pacific/Truk", + "Pacific/Wake", + "Pacific/Wallis" +] + LODGING_TYPES = [ ('hotel', 'Hotel'), ('hostel', 'Hostel'), @@ -76,8 +539,9 @@ def validate_file_extension(value): class Visit(models.Model): id = models.UUIDField(default=uuid.uuid4, editable=False, unique=True, primary_key=True) adventure = models.ForeignKey('Adventure', on_delete=models.CASCADE, related_name='visits') - start_date = models.DateField(null=True, blank=True) - end_date = models.DateField(null=True, blank=True) + start_date = models.DateTimeField(null=True, blank=True) + end_date = models.DateTimeField(null=True, blank=True) + timezone = models.CharField(max_length=50, choices=[(tz, tz) for tz in TIMEZONES], null=True, blank=True) notes = models.TextField(blank=True, null=True) created_at = models.DateTimeField(auto_now_add=True) updated_at = models.DateTimeField(auto_now=True) @@ -104,9 +568,16 @@ class Adventure(models.Model): rating = models.FloatField(blank=True, null=True) link = models.URLField(blank=True, null=True, max_length=2083) is_public = models.BooleanField(default=False) + longitude = models.DecimalField(max_digits=9, decimal_places=6, null=True, blank=True) latitude = models.DecimalField(max_digits=9, decimal_places=6, null=True, blank=True) + + city = models.ForeignKey(City, on_delete=models.SET_NULL, blank=True, null=True) + region = models.ForeignKey(Region, on_delete=models.SET_NULL, blank=True, null=True) + country = models.ForeignKey(Country, on_delete=models.SET_NULL, blank=True, null=True) + collection = models.ForeignKey('Collection', on_delete=models.CASCADE, blank=True, null=True) + created_at = models.DateTimeField(auto_now_add=True) updated_at = models.DateTimeField(auto_now=True) @@ -119,6 +590,17 @@ class Adventure(models.Model): # end_date = models.DateField(blank=True, null=True) # type = models.CharField(max_length=100, choices=ADVENTURE_TYPES, default='general') + def is_visited_status(self): + current_date = timezone.now().date() + for visit in self.visits.all(): + start_date = visit.start_date.date() if isinstance(visit.start_date, timezone.datetime) else visit.start_date + end_date = visit.end_date.date() if isinstance(visit.end_date, timezone.datetime) else visit.end_date + if start_date and end_date and (start_date <= current_date): + return True + elif start_date and not end_date and (start_date <= current_date): + return True + return False + def clean(self): if self.collection: if self.collection.is_public and not self.is_public: @@ -129,25 +611,31 @@ def clean(self): if self.user_id != self.category.user_id: raise ValidationError('Adventures must be associated with categories owned by the same user. Category owner: ' + self.category.user_id.username + ' Adventure owner: ' + self.user_id.username) - def save(self, force_insert: bool = False, force_update: bool = False, using: str | None = None, update_fields: Iterable[str] | None = None) -> None: - """ - Saves the current instance. If the instance is being inserted for the first time, it will be created in the database. - If it already exists, it will be updated. - """ + def save(self, force_insert=False, force_update=False, using=None, update_fields=None, _skip_geocode=False): if force_insert and force_update: raise ValueError("Cannot force both insert and updating in model saving.") + if not self.category: - category, created = Category.objects.get_or_create( - user_id=self.user_id, - name='general', - defaults={ - 'display_name': 'General', - 'icon': '🌍' - } - ) + category, _ = Category.objects.get_or_create( + user_id=self.user_id, + name='general', + defaults={'display_name': 'General', 'icon': '🌍'} + ) self.category = category - - return super().save(force_insert, force_update, using, update_fields) + + result = super().save(force_insert, force_update, using, update_fields) + + # ⛔ Skip threading if called from geocode background thread + if _skip_geocode: + return result + + if self.latitude and self.longitude: + thread = threading.Thread(target=background_geocode_and_assign, args=(str(self.id),)) + thread.daemon = True # Allows the thread to exit when the main program ends + thread.start() + + return result + def __str__(self): return self.name @@ -191,6 +679,8 @@ class Transportation(models.Model): link = models.URLField(blank=True, null=True) date = models.DateTimeField(blank=True, null=True) end_date = models.DateTimeField(blank=True, null=True) + start_timezone = models.CharField(max_length=50, choices=[(tz, tz) for tz in TIMEZONES], null=True, blank=True) + end_timezone = models.CharField(max_length=50, choices=[(tz, tz) for tz in TIMEZONES], null=True, blank=True) flight_number = models.CharField(max_length=100, blank=True, null=True) from_location = models.CharField(max_length=200, blank=True, null=True) origin_latitude = models.DecimalField(max_digits=9, decimal_places=6, null=True, blank=True) @@ -296,18 +786,41 @@ def __call__(self, instance, filename): class AdventureImage(models.Model): id = models.UUIDField(default=uuid.uuid4, editable=False, unique=True, primary_key=True) - user_id = models.ForeignKey( - User, on_delete=models.CASCADE, default=default_user_id) + user_id = models.ForeignKey(User, on_delete=models.CASCADE, default=default_user_id) image = ResizedImageField( force_format="WEBP", quality=75, - upload_to=PathAndRename('images/') # Use the callable class here + upload_to=PathAndRename('images/'), + blank=True, + null=True, ) + immich_id = models.CharField(max_length=200, null=True, blank=True) adventure = models.ForeignKey(Adventure, related_name='images', on_delete=models.CASCADE) is_primary = models.BooleanField(default=False) + def clean(self): + + # One of image or immich_id must be set, but not both + has_image = bool(self.image and str(self.image).strip()) + has_immich_id = bool(self.immich_id and str(self.immich_id).strip()) + + if has_image and has_immich_id: + raise ValidationError("Cannot have both image file and Immich ID. Please provide only one.") + if not has_image and not has_immich_id: + raise ValidationError("Must provide either an image file or an Immich ID.") + + def save(self, *args, **kwargs): + # Clean empty strings to None for proper database storage + if not self.image: + self.image = None + if not self.immich_id or not str(self.immich_id).strip(): + self.immich_id = None + + self.full_clean() # This calls clean() method + super().save(*args, **kwargs) + def __str__(self): - return self.image.url + return self.image.url if self.image else f"Immich ID: {self.immich_id or 'No image'}" class Attachment(models.Model): id = models.UUIDField(default=uuid.uuid4, editable=False, unique=True, primary_key=True) @@ -352,6 +865,7 @@ class Lodging(models.Model): link = models.URLField(blank=True, null=True, max_length=2083) check_in = models.DateTimeField(blank=True, null=True) check_out = models.DateTimeField(blank=True, null=True) + timezone = models.CharField(max_length=50, choices=[(tz, tz) for tz in TIMEZONES], null=True, blank=True) reservation_number = models.CharField(max_length=100, blank=True, null=True) price = models.DecimalField(max_digits=9, decimal_places=2, blank=True, null=True) latitude = models.DecimalField(max_digits=9, decimal_places=6, null=True, blank=True) @@ -363,8 +877,8 @@ class Lodging(models.Model): updated_at = models.DateTimeField(auto_now=True) def clean(self): - if self.date and self.end_date and self.date > self.end_date: - raise ValidationError('The start date must be before the end date. Start date: ' + str(self.date) + ' End date: ' + str(self.end_date)) + if self.check_in and self.check_out and self.check_in > self.check_out: + raise ValidationError('The start date must be before the end date. Start date: ' + str(self.check_in) + ' End date: ' + str(self.check_out)) if self.collection: if self.collection.is_public and not self.is_public: diff --git a/backend/server/adventures/serializers.py b/backend/server/adventures/serializers.py index 97dd6339..2708ab3e 100644 --- a/backend/server/adventures/serializers.py +++ b/backend/server/adventures/serializers.py @@ -4,22 +4,38 @@ from rest_framework import serializers from main.utils import CustomModelSerializer from users.serializers import CustomUserDetailsSerializer +from worldtravel.serializers import CountrySerializer, RegionSerializer, CitySerializer +from geopy.distance import geodesic +from integrations.models import ImmichIntegration class AdventureImageSerializer(CustomModelSerializer): class Meta: model = AdventureImage - fields = ['id', 'image', 'adventure', 'is_primary', 'user_id'] + fields = ['id', 'image', 'adventure', 'is_primary', 'user_id', 'immich_id'] read_only_fields = ['id', 'user_id'] def to_representation(self, instance): + # If immich_id is set, check for user integration once + integration = None + if instance.immich_id: + integration = ImmichIntegration.objects.filter(user=instance.user_id).first() + if not integration: + return None # Skip if Immich image but no integration + + # Base representation representation = super().to_representation(instance) - if instance.image: - public_url = os.environ.get('PUBLIC_URL', 'http://127.0.0.1:8000').rstrip('/') - #print(public_url) - # remove any ' from the url - public_url = public_url.replace("'", "") + + # Prepare public URL once + public_url = os.environ.get('PUBLIC_URL', 'http://127.0.0.1:8000').rstrip('/').replace("'", "") + + if instance.immich_id: + # Use Immich integration URL + representation['image'] = f"{public_url}/api/integrations/immich/{integration.id}/get/{instance.immich_id}" + elif instance.image: + # Use local image URL representation['image'] = f"{public_url}/media/{instance.image.name}" + return representation class AttachmentSerializer(CustomModelSerializer): @@ -72,26 +88,34 @@ class VisitSerializer(serializers.ModelSerializer): class Meta: model = Visit - fields = ['id', 'start_date', 'end_date', 'notes'] + fields = ['id', 'start_date', 'end_date', 'timezone', 'notes'] read_only_fields = ['id'] class AdventureSerializer(CustomModelSerializer): - images = AdventureImageSerializer(many=True, read_only=True) + images = serializers.SerializerMethodField() visits = VisitSerializer(many=True, read_only=False, required=False) attachments = AttachmentSerializer(many=True, read_only=True) category = CategorySerializer(read_only=False, required=False) is_visited = serializers.SerializerMethodField() user = serializers.SerializerMethodField() + country = CountrySerializer(read_only=True) + region = RegionSerializer(read_only=True) + city = CitySerializer(read_only=True) class Meta: model = Adventure fields = [ 'id', 'user_id', 'name', 'description', 'rating', 'activity_types', 'location', 'is_public', 'collection', 'created_at', 'updated_at', 'images', 'link', 'longitude', - 'latitude', 'visits', 'is_visited', 'category', 'attachments', 'user' + 'latitude', 'visits', 'is_visited', 'category', 'attachments', 'user', 'city', 'country', 'region' ] read_only_fields = ['id', 'created_at', 'updated_at', 'user_id', 'is_visited', 'user'] + def get_images(self, obj): + serializer = AdventureImageSerializer(obj.images.all(), many=True, context=self.context) + # Filter out None values from the serialized data + return [image for image in serializer.data if image is not None] + def validate_category(self, category_data): if isinstance(category_data, Category): return category_data @@ -103,7 +127,7 @@ def validate_category(self, category_data): return existing_category category_data['name'] = name return category_data - + def get_or_create_category(self, category_data): user = self.context['request'].user @@ -134,13 +158,7 @@ def get_user(self, obj): return CustomUserDetailsSerializer(user).data def get_is_visited(self, obj): - current_date = timezone.now().date() - for visit in obj.visits.all(): - if visit.start_date and visit.end_date and (visit.start_date <= current_date): - return True - elif visit.start_date and not visit.end_date and (visit.start_date <= current_date): - return True - return False + return obj.is_visited_status() def create(self, validated_data): visits_data = validated_data.pop('visits', None) @@ -153,7 +171,8 @@ def create(self, validated_data): if category_data: category = self.get_or_create_category(category_data) adventure.category = category - adventure.save() + + adventure.save() return adventure @@ -168,7 +187,6 @@ def update(self, instance, validated_data): if category_data: category = self.get_or_create_category(category_data) instance.category = category - instance.save() if has_visits: current_visits = instance.visits.all() @@ -190,18 +208,37 @@ def update(self, instance, validated_data): visits_to_delete = current_visit_ids - updated_visit_ids instance.visits.filter(id__in=visits_to_delete).delete() + # call save on the adventure to update the updated_at field and trigger any geocoding + instance.save() + return instance class TransportationSerializer(CustomModelSerializer): + distance = serializers.SerializerMethodField() class Meta: model = Transportation fields = [ 'id', 'user_id', 'type', 'name', 'description', 'rating', 'link', 'date', 'flight_number', 'from_location', 'to_location', - 'is_public', 'collection', 'created_at', 'updated_at', 'end_date', 'origin_latitude', 'origin_longitude', 'destination_latitude', 'destination_longitude' + 'is_public', 'collection', 'created_at', 'updated_at', 'end_date', + 'origin_latitude', 'origin_longitude', 'destination_latitude', 'destination_longitude', + 'start_timezone', 'end_timezone', 'distance' # ✅ Add distance here ] - read_only_fields = ['id', 'created_at', 'updated_at', 'user_id'] + read_only_fields = ['id', 'created_at', 'updated_at', 'user_id', 'distance'] + + def get_distance(self, obj): + if ( + obj.origin_latitude and obj.origin_longitude and + obj.destination_latitude and obj.destination_longitude + ): + try: + origin = (float(obj.origin_latitude), float(obj.origin_longitude)) + destination = (float(obj.destination_latitude), float(obj.destination_longitude)) + return round(geodesic(origin, destination).km, 2) + except ValueError: + return None + return None class LodgingSerializer(CustomModelSerializer): @@ -210,7 +247,7 @@ class Meta: fields = [ 'id', 'user_id', 'name', 'description', 'rating', 'link', 'check_in', 'check_out', 'reservation_number', 'price', 'latitude', 'longitude', 'location', 'is_public', - 'collection', 'created_at', 'updated_at', 'type' + 'collection', 'created_at', 'updated_at', 'type', 'timezone' ] read_only_fields = ['id', 'created_at', 'updated_at', 'user_id'] @@ -234,6 +271,7 @@ class Meta: class ChecklistSerializer(CustomModelSerializer): items = ChecklistItemSerializer(many=True, source='checklistitem_set') + class Meta: model = Checklist fields = [ @@ -244,8 +282,16 @@ class Meta: def create(self, validated_data): items_data = validated_data.pop('checklistitem_set') checklist = Checklist.objects.create(**validated_data) + for item_data in items_data: - ChecklistItem.objects.create(checklist=checklist, **item_data) + # Remove user_id from item_data to avoid constraint issues + item_data.pop('user_id', None) + # Set user_id from the parent checklist + ChecklistItem.objects.create( + checklist=checklist, + user_id=checklist.user_id, + **item_data + ) return checklist def update(self, instance, validated_data): @@ -263,6 +309,9 @@ def update(self, instance, validated_data): # Update or create items updated_item_ids = set() for item_data in items_data: + # Remove user_id from item_data to avoid constraint issues + item_data.pop('user_id', None) + item_id = item_data.get('id') if item_id: if item_id in current_item_ids: @@ -273,10 +322,18 @@ def update(self, instance, validated_data): updated_item_ids.add(item_id) else: # If ID is provided but doesn't exist, create new item - ChecklistItem.objects.create(checklist=instance, **item_data) + ChecklistItem.objects.create( + checklist=instance, + user_id=instance.user_id, + **item_data + ) else: # If no ID is provided, create new item - ChecklistItem.objects.create(checklist=instance, **item_data) + ChecklistItem.objects.create( + checklist=instance, + user_id=instance.user_id, + **item_data + ) # Delete items that are not in the updated data items_to_delete = current_item_ids - updated_item_ids @@ -292,7 +349,6 @@ def validate(self, data): raise serializers.ValidationError( 'Checklists associated with a public collection must be public.' ) - return data class CollectionSerializer(CustomModelSerializer): diff --git a/backend/server/adventures/urls.py b/backend/server/adventures/urls.py index 1a982737..d1bf6cbc 100644 --- a/backend/server/adventures/urls.py +++ b/backend/server/adventures/urls.py @@ -15,11 +15,10 @@ router.register(r'reverse-geocode', ReverseGeocodeViewSet, basename='reverse-geocode') router.register(r'categories', CategoryViewSet, basename='categories') router.register(r'ics-calendar', IcsCalendarGeneratorViewSet, basename='ics-calendar') -router.register(r'overpass', OverpassViewSet, basename='overpass') router.register(r'search', GlobalSearchView, basename='search') router.register(r'attachments', AttachmentViewSet, basename='attachments') router.register(r'lodging', LodgingViewSet, basename='lodging') - +router.register(r'recommendations', RecommendationsViewSet, basename='recommendations') urlpatterns = [ # Include the router under the 'api/' prefix diff --git a/backend/server/adventures/views/__init__.py b/backend/server/adventures/views/__init__.py index 8f531f78..c9aedb0b 100644 --- a/backend/server/adventures/views/__init__.py +++ b/backend/server/adventures/views/__init__.py @@ -7,10 +7,10 @@ from .generate_description_view import * from .ics_calendar_view import * from .note_view import * -from .overpass_view import * from .reverse_geocode_view import * from .stats_view import * from .transportation_view import * from .global_search_view import * from .attachment_view import * -from .lodging_view import * \ No newline at end of file +from .lodging_view import * +from .recommendations_view import * \ No newline at end of file diff --git a/backend/server/adventures/views/adventure_image_view.py b/backend/server/adventures/views/adventure_image_view.py index d76f6a56..52839ded 100644 --- a/backend/server/adventures/views/adventure_image_view.py +++ b/backend/server/adventures/views/adventure_image_view.py @@ -3,9 +3,12 @@ from rest_framework.permissions import IsAuthenticated from rest_framework.response import Response from django.db.models import Q +from django.core.files.base import ContentFile from adventures.models import Adventure, AdventureImage from adventures.serializers import AdventureImageSerializer +from integrations.models import ImmichIntegration import uuid +import requests class AdventureImageViewSet(viewsets.ModelViewSet): serializer_class = AdventureImageSerializer @@ -56,6 +59,78 @@ def create(self, request, *args, **kwargs): else: return Response({"error": "User does not own this adventure"}, status=status.HTTP_403_FORBIDDEN) + # Handle Immich ID for shared users by downloading the image + if (request.user != adventure.user_id and + 'immich_id' in request.data and + request.data.get('immich_id')): + + immich_id = request.data.get('immich_id') + + # Get the shared user's Immich integration + try: + user_integration = ImmichIntegration.objects.get(user_id=request.user) + except ImmichIntegration.DoesNotExist: + return Response({ + "error": "No Immich integration found for your account. Please set up Immich integration first.", + "code": "immich_integration_not_found" + }, status=status.HTTP_400_BAD_REQUEST) + + # Download the image from the shared user's Immich server + try: + immich_response = requests.get( + f'{user_integration.server_url}/assets/{immich_id}/thumbnail?size=preview', + headers={'x-api-key': user_integration.api_key}, + timeout=10 + ) + immich_response.raise_for_status() + + # Create a temporary file with the downloaded content + content_type = immich_response.headers.get('Content-Type', 'image/jpeg') + if not content_type.startswith('image/'): + return Response({ + "error": "Invalid content type returned from Immich server.", + "code": "invalid_content_type" + }, status=status.HTTP_400_BAD_REQUEST) + + # Determine file extension from content type + ext_map = { + 'image/jpeg': '.jpg', + 'image/png': '.png', + 'image/webp': '.webp', + 'image/gif': '.gif' + } + file_ext = ext_map.get(content_type, '.jpg') + filename = f"immich_{immich_id}{file_ext}" + + # Create a Django ContentFile from the downloaded image + image_file = ContentFile(immich_response.content, name=filename) + + # Modify request data to use the downloaded image instead of immich_id + request_data = request.data.copy() + request_data.pop('immich_id', None) # Remove immich_id + request_data['image'] = image_file # Add the image file + + # Create the serializer with the modified data + serializer = self.get_serializer(data=request_data) + serializer.is_valid(raise_exception=True) + + # Save with the downloaded image + adventure = serializer.validated_data['adventure'] + serializer.save(user_id=adventure.user_id, image=image_file) + + return Response(serializer.data, status=status.HTTP_201_CREATED) + + except requests.exceptions.RequestException: + return Response({ + "error": f"Failed to fetch image from Immich server", + "code": "immich_fetch_failed" + }, status=status.HTTP_502_BAD_GATEWAY) + except Exception: + return Response({ + "error": f"Unexpected error processing Immich image", + "code": "immich_processing_error" + }, status=status.HTTP_500_INTERNAL_SERVER_ERROR) + return super().create(request, *args, **kwargs) def update(self, request, *args, **kwargs): @@ -110,15 +185,25 @@ def adventure_images(self, request, adventure_id=None, *args, **kwargs): except ValueError: return Response({"error": "Invalid adventure ID"}, status=status.HTTP_400_BAD_REQUEST) + # Updated queryset to include images from adventures the user owns OR has shared access to queryset = AdventureImage.objects.filter( - Q(adventure__id=adventure_uuid) & Q(user_id=request.user) - ) + Q(adventure__id=adventure_uuid) & ( + Q(adventure__user_id=request.user) | # User owns the adventure + Q(adventure__collection__shared_with=request.user) # User has shared access via collection + ) + ).distinct() serializer = self.get_serializer(queryset, many=True, context={'request': request}) return Response(serializer.data) def get_queryset(self): - return AdventureImage.objects.filter(user_id=self.request.user) + # Updated to include images from adventures the user owns OR has shared access to + return AdventureImage.objects.filter( + Q(adventure__user_id=self.request.user) | # User owns the adventure + Q(adventure__collection__shared_with=self.request.user) # User has shared access via collection + ).distinct() def perform_create(self, serializer): - serializer.save(user_id=self.request.user) \ No newline at end of file + # Always set the image owner to the adventure owner, not the current user + adventure = serializer.validated_data['adventure'] + serializer.save(user_id=adventure.user_id) \ No newline at end of file diff --git a/backend/server/adventures/views/adventure_view.py b/backend/server/adventures/views/adventure_view.py index 2f7e1f17..a936f86b 100644 --- a/backend/server/adventures/views/adventure_view.py +++ b/backend/server/adventures/views/adventure_view.py @@ -10,6 +10,7 @@ from adventures.permissions import IsOwnerOrSharedWithFullAccess from adventures.serializers import AdventureSerializer, TransportationSerializer, LodgingSerializer from adventures.utils import pagination +import requests class AdventureViewSet(viewsets.ModelViewSet): serializer_class = AdventureSerializer @@ -59,14 +60,15 @@ def get_queryset(self): """ user = self.request.user + # Actions that allow public access (include 'retrieve' and your custom action) + public_allowed_actions = {'retrieve', 'additional_info'} + if not user.is_authenticated: - # Unauthenticated users can only access public adventures for retrieval - if self.action == 'retrieve': + if self.action in public_allowed_actions: return Adventure.objects.retrieve_adventures(user, include_public=True).order_by('-updated_at') return Adventure.objects.none() - # Authenticated users: Handle retrieval separately - include_public = self.action == 'retrieve' + include_public = self.action in public_allowed_actions return Adventure.objects.retrieve_adventures( user, include_public=include_public, @@ -74,6 +76,7 @@ def get_queryset(self): include_shared=True ).order_by('-updated_at') + def perform_update(self, serializer): adventure = serializer.save() if adventure.collection: @@ -170,48 +173,41 @@ def paginate_and_respond(self, queryset, request): serializer = self.get_serializer(queryset, many=True) return Response(serializer.data) - # @action(detail=True, methods=['post']) - # def convert(self, request, pk=None): - # """ - # Convert an Adventure instance into a Transportation or Lodging instance. - # Expects a JSON body with "target_type": "transportation" or "lodging". - # """ - # adventure = self.get_object() - # target_type = request.data.get("target_type", "").lower() - - # if target_type not in ["transportation", "lodging"]: - # return Response( - # {"error": "Invalid target type. Must be 'transportation' or 'lodging'."}, - # status=400 - # ) - # if not adventure.collection: - # return Response( - # {"error": "Adventure must be part of a collection to be converted."}, - # status=400 - # ) - - # # Define the overlapping fields that both the Adventure and target models share. - # overlapping_fields = ["name", "description", "is_public", 'collection'] - - # # Gather the overlapping data from the adventure instance. - # conversion_data = {} - # for field in overlapping_fields: - # if hasattr(adventure, field): - # conversion_data[field] = getattr(adventure, field) - - # # Make sure to include the user reference - # conversion_data["user_id"] = adventure.user_id - - # # Convert the adventure instance within an atomic transaction. - # with transaction.atomic(): - # if target_type == "transportation": - # new_instance = Transportation.objects.create(**conversion_data) - # serializer = TransportationSerializer(new_instance) - # else: # target_type == "lodging" - # new_instance = Lodging.objects.create(**conversion_data) - # serializer = LodgingSerializer(new_instance) - - # # Optionally, delete the original adventure to avoid duplicates. - # adventure.delete() - - # return Response(serializer.data) + @action(detail=True, methods=['get'], url_path='additional-info') + def additional_info(self, request, pk=None): + adventure = self.get_object() + + user = request.user + + # Allow if public + if not adventure.is_public: + # Only allow owner or shared collection members + if not user.is_authenticated or adventure.user_id != user: + if not (adventure.collection and adventure.collection.shared_with.filter(uuid=user.uuid).exists()): + return Response({"error": "User does not have permission to access this adventure"}, + status=status.HTTP_403_FORBIDDEN) + + serializer = self.get_serializer(adventure) + response_data = serializer.data + + visits = response_data.get('visits', []) + sun_times = [] + + for visit in visits: + date = visit.get('start_date') + if date and adventure.longitude and adventure.latitude: + api_url = f'https://api.sunrisesunset.io/json?lat={adventure.latitude}&lng={adventure.longitude}&date={date}' + res = requests.get(api_url) + if res.status_code == 200: + data = res.json() + results = data.get('results', {}) + if results.get('sunrise') and results.get('sunset'): + sun_times.append({ + "date": date, + "visit_id": visit.get('id'), + "sunrise": results.get('sunrise'), + "sunset": results.get('sunset') + }) + + response_data['sun_times'] = sun_times + return Response(response_data) \ No newline at end of file diff --git a/backend/server/adventures/views/collection_view.py b/backend/server/adventures/views/collection_view.py index f0529ee6..1765342d 100644 --- a/backend/server/adventures/views/collection_view.py +++ b/backend/server/adventures/views/collection_view.py @@ -22,7 +22,7 @@ def apply_sorting(self, queryset): order_by = self.request.query_params.get('order_by', 'name') order_direction = self.request.query_params.get('order_direction', 'asc') - valid_order_by = ['name', 'upated_at'] + valid_order_by = ['name', 'updated_at', 'start_date'] if order_by not in valid_order_by: order_by = 'updated_at' @@ -35,6 +35,12 @@ def apply_sorting(self, queryset): ordering = 'lower_name' if order_direction == 'desc': ordering = f'-{ordering}' + elif order_by == 'start_date': + ordering = 'start_date' + if order_direction == 'asc': + ordering = 'start_date' + else: + ordering = '-start_date' else: order_by == 'updated_at' ordering = 'updated_at' diff --git a/backend/server/adventures/views/overpass_view.py b/backend/server/adventures/views/overpass_view.py deleted file mode 100644 index a72b4a79..00000000 --- a/backend/server/adventures/views/overpass_view.py +++ /dev/null @@ -1,183 +0,0 @@ -from rest_framework import viewsets -from rest_framework.decorators import action -from rest_framework.permissions import IsAuthenticated -from rest_framework.response import Response -import requests - -class OverpassViewSet(viewsets.ViewSet): - permission_classes = [IsAuthenticated] - BASE_URL = "https://overpass-api.de/api/interpreter" - HEADERS = {'User-Agent': 'AdventureLog Server'} - - def make_overpass_query(self, query): - """ - Sends a query to the Overpass API and returns the response data. - Args: - query (str): The Overpass QL query string. - Returns: - dict: Parsed JSON response from the Overpass API. - Raises: - Response: DRF Response object with an error message in case of failure. - """ - url = f"{self.BASE_URL}?data={query}" - try: - response = requests.get(url, headers=self.HEADERS) - response.raise_for_status() # Raise an exception for HTTP errors - return response.json() - except requests.exceptions.RequestException: - return Response({"error": "Failed to connect to Overpass API"}, status=500) - except requests.exceptions.JSONDecodeError: - return Response({"error": "Invalid response from Overpass API"}, status=400) - - def parse_overpass_response(self, data, request): - """ - Parses the JSON response from the Overpass API and extracts relevant data, - turning it into an adventure-structured object. - - Args: - response (dict): The JSON response from the Overpass API. - - Returns: - list: A list of adventure objects with structured data. - """ - # Extract elements (nodes/ways/relations) from the response - nodes = data.get('elements', []) - adventures = [] - - # include all entries, even the ones that do not have lat long - all = request.query_params.get('all', False) - - for node in nodes: - # Ensure we are working with a "node" type (can also handle "way" or "relation" if needed) - if node.get('type') not in ['node', 'way', 'relation']: - continue - - # Extract tags and general data - tags = node.get('tags', {}) - adventure = { - "id": node.get('id'), # Include the unique OSM ID - "type": node.get('type'), # Type of element (node, way, relation) - "name": tags.get('name', tags.get('official_name', '')), # Fallback to 'official_name' - "description": tags.get('description', None), # Additional descriptive information - "latitude": node.get('lat', None), # Use None for consistency with missing values - "longitude": node.get('lon', None), - "address": { - "city": tags.get('addr:city', None), - "housenumber": tags.get('addr:housenumber', None), - "postcode": tags.get('addr:postcode', None), - "state": tags.get('addr:state', None), - "street": tags.get('addr:street', None), - "country": tags.get('addr:country', None), # Add 'country' if available - "suburb": tags.get('addr:suburb', None), # Add 'suburb' for more granularity - }, - "feature_id": tags.get('gnis:feature_id', None), - "tag": next((tags.get(key, None) for key in ['leisure', 'tourism', 'natural', 'historic', 'amenity'] if key in tags), None), - "contact": { - "phone": tags.get('phone', None), - "email": tags.get('contact:email', None), - "website": tags.get('website', None), - "facebook": tags.get('contact:facebook', None), # Social media links - "twitter": tags.get('contact:twitter', None), - }, - # "tags": tags, # Include all raw tags for future use - } - - # Filter out adventures with no name, latitude, or longitude - if (adventure["name"] and - adventure["latitude"] is not None and -90 <= adventure["latitude"] <= 90 and - adventure["longitude"] is not None and -180 <= adventure["longitude"] <= 180) or all: - adventures.append(adventure) - - return adventures - - - @action(detail=False, methods=['get']) - def query(self, request): - """ - Radius-based search for tourism-related locations around given coordinates. - """ - lat = request.query_params.get('lat') - lon = request.query_params.get('lon') - radius = request.query_params.get('radius', '1000') # Default radius: 1000 meters - - valid_categories = ['lodging', 'food', 'tourism'] - category = request.query_params.get('category', 'all') - if category not in valid_categories: - return Response({"error": f"Invalid category. Valid categories: {', '.join(valid_categories)}"}, status=400) - - if category == 'tourism': - query = f""" - [out:json]; - ( - node(around:{radius},{lat},{lon})["tourism"]; - node(around:{radius},{lat},{lon})["leisure"]; - node(around:{radius},{lat},{lon})["historic"]; - node(around:{radius},{lat},{lon})["sport"]; - node(around:{radius},{lat},{lon})["natural"]; - node(around:{radius},{lat},{lon})["attraction"]; - node(around:{radius},{lat},{lon})["museum"]; - node(around:{radius},{lat},{lon})["zoo"]; - node(around:{radius},{lat},{lon})["aquarium"]; - ); - out; - """ - if category == 'lodging': - query = f""" - [out:json]; - ( - node(around:{radius},{lat},{lon})["tourism"="hotel"]; - node(around:{radius},{lat},{lon})["tourism"="motel"]; - node(around:{radius},{lat},{lon})["tourism"="guest_house"]; - node(around:{radius},{lat},{lon})["tourism"="hostel"]; - node(around:{radius},{lat},{lon})["tourism"="camp_site"]; - node(around:{radius},{lat},{lon})["tourism"="caravan_site"]; - node(around:{radius},{lat},{lon})["tourism"="chalet"]; - node(around:{radius},{lat},{lon})["tourism"="alpine_hut"]; - node(around:{radius},{lat},{lon})["tourism"="apartment"]; - ); - out; - """ - if category == 'food': - query = f""" - [out:json]; - ( - node(around:{radius},{lat},{lon})["amenity"="restaurant"]; - node(around:{radius},{lat},{lon})["amenity"="cafe"]; - node(around:{radius},{lat},{lon})["amenity"="fast_food"]; - node(around:{radius},{lat},{lon})["amenity"="pub"]; - node(around:{radius},{lat},{lon})["amenity"="bar"]; - node(around:{radius},{lat},{lon})["amenity"="food_court"]; - node(around:{radius},{lat},{lon})["amenity"="ice_cream"]; - node(around:{radius},{lat},{lon})["amenity"="bakery"]; - node(around:{radius},{lat},{lon})["amenity"="confectionery"]; - ); - out; - """ - - # Validate required parameters - if not lat or not lon: - return Response( - {"error": "Latitude and longitude parameters are required."}, status=400 - ) - - data = self.make_overpass_query(query) - adventures = self.parse_overpass_response(data, request) - return Response(adventures) - - @action(detail=False, methods=['get'], url_path='search') - def search(self, request): - """ - Name-based search for nodes with the specified name. - """ - name = request.query_params.get('name') - - # Validate required parameter - if not name: - return Response({"error": "Name parameter is required."}, status=400) - - # Construct Overpass API query - query = f'[out:json];node["name"~"{name}",i];out;' - data = self.make_overpass_query(query) - - adventures = self.parse_overpass_response(data, request) - return Response(adventures) diff --git a/backend/server/adventures/views/recommendations_view.py b/backend/server/adventures/views/recommendations_view.py new file mode 100644 index 00000000..81f3facb --- /dev/null +++ b/backend/server/adventures/views/recommendations_view.py @@ -0,0 +1,232 @@ +from rest_framework import viewsets +from rest_framework.decorators import action +from rest_framework.permissions import IsAuthenticated +from rest_framework.response import Response +from django.conf import settings +import requests +from geopy.distance import geodesic +import time + + +class RecommendationsViewSet(viewsets.ViewSet): + permission_classes = [IsAuthenticated] + BASE_URL = "https://overpass-api.de/api/interpreter" + HEADERS = {'User-Agent': 'AdventureLog Server'} + + def parse_google_places(self, places, origin): + adventures = [] + + for place in places: + location = place.get('geometry', {}).get('location', {}) + types = place.get('types', []) + formatted_address = place.get("vicinity") or place.get("formatted_address") or place.get("name") + + lat = location.get('lat') + lon = location.get('lng') + + if not place.get("name") or not lat or not lon: + continue + + distance_km = geodesic(origin, (lat, lon)).km + + adventure = { + "id": place.get('place_id'), + "type": 'place', + "name": place.get('name', ''), + "description": place.get('business_status', None), + "latitude": lat, + "longitude": lon, + "address": formatted_address, + "tag": types[0] if types else None, + "distance_km": round(distance_km, 2), # Optional: include in response + } + + adventures.append(adventure) + + # Sort by distance ascending + adventures.sort(key=lambda x: x["distance_km"]) + + return adventures + + def parse_overpass_response(self, data, request): + nodes = data.get('elements', []) + adventures = [] + all = request.query_params.get('all', False) + + origin = None + try: + origin = ( + float(request.query_params.get('lat')), + float(request.query_params.get('lon')) + ) + except(ValueError, TypeError): + origin = None + + for node in nodes: + if node.get('type') not in ['node', 'way', 'relation']: + continue + + tags = node.get('tags', {}) + lat = node.get('lat') + lon = node.get('lon') + name = tags.get('name', tags.get('official_name', '')) + + if not name or lat is None or lon is None: + if not all: + continue + + # Flatten address + address_parts = [tags.get(f'addr:{k}') for k in ['housenumber', 'street', 'suburb', 'city', 'state', 'postcode', 'country']] + formatted_address = ", ".join(filter(None, address_parts)) or name + + # Calculate distance if possible + distance_km = None + if origin: + distance_km = round(geodesic(origin, (lat, lon)).km, 2) + + # Unified format + adventure = { + "id": f"osm:{node.get('id')}", + "type": "place", + "name": name, + "description": tags.get('description'), + "latitude": lat, + "longitude": lon, + "address": formatted_address, + "tag": next((tags.get(key) for key in ['leisure', 'tourism', 'natural', 'historic', 'amenity'] if key in tags), None), + "distance_km": distance_km, + "powered_by": "osm" + } + + adventures.append(adventure) + + # Sort by distance if available + if origin: + adventures.sort(key=lambda x: x.get("distance_km") or float("inf")) + + return adventures + + + def query_overpass(self, lat, lon, radius, category, request): + if category == 'tourism': + query = f""" + [out:json]; + ( + node(around:{radius},{lat},{lon})["tourism"]; + node(around:{radius},{lat},{lon})["leisure"]; + node(around:{radius},{lat},{lon})["historic"]; + node(around:{radius},{lat},{lon})["sport"]; + node(around:{radius},{lat},{lon})["natural"]; + node(around:{radius},{lat},{lon})["attraction"]; + node(around:{radius},{lat},{lon})["museum"]; + node(around:{radius},{lat},{lon})["zoo"]; + node(around:{radius},{lat},{lon})["aquarium"]; + ); + out; + """ + elif category == 'lodging': + query = f""" + [out:json]; + ( + node(around:{radius},{lat},{lon})["tourism"="hotel"]; + node(around:{radius},{lat},{lon})["tourism"="motel"]; + node(around:{radius},{lat},{lon})["tourism"="guest_house"]; + node(around:{radius},{lat},{lon})["tourism"="hostel"]; + node(around:{radius},{lat},{lon})["tourism"="camp_site"]; + node(around:{radius},{lat},{lon})["tourism"="caravan_site"]; + node(around:{radius},{lat},{lon})["tourism"="chalet"]; + node(around:{radius},{lat},{lon})["tourism"="alpine_hut"]; + node(around:{radius},{lat},{lon})["tourism"="apartment"]; + ); + out; + """ + elif category == 'food': + query = f""" + [out:json]; + ( + node(around:{radius},{lat},{lon})["amenity"="restaurant"]; + node(around:{radius},{lat},{lon})["amenity"="cafe"]; + node(around:{radius},{lat},{lon})["amenity"="fast_food"]; + node(around:{radius},{lat},{lon})["amenity"="pub"]; + node(around:{radius},{lat},{lon})["amenity"="bar"]; + node(around:{radius},{lat},{lon})["amenity"="food_court"]; + node(around:{radius},{lat},{lon})["amenity"="ice_cream"]; + node(around:{radius},{lat},{lon})["amenity"="bakery"]; + node(around:{radius},{lat},{lon})["amenity"="confectionery"]; + ); + out; + """ + else: + return Response({"error": "Invalid category."}, status=400) + + overpass_url = f"{self.BASE_URL}?data={query}" + try: + response = requests.get(overpass_url, headers=self.HEADERS) + response.raise_for_status() + data = response.json() + except Exception as e: + print("Overpass API error:", e) + return Response({"error": "Failed to retrieve data from Overpass API."}, status=500) + + adventures = self.parse_overpass_response(data, request) + return Response(adventures) + + + + @action(detail=False, methods=['get']) + def query(self, request): + lat = request.query_params.get('lat') + lon = request.query_params.get('lon') + radius = request.query_params.get('radius', '1000') + category = request.query_params.get('category', 'all') + + if not lat or not lon: + return Response({"error": "Latitude and longitude parameters are required."}, status=400) + + valid_categories = { + 'lodging': 'lodging', + 'food': 'restaurant', + 'tourism': 'tourist_attraction', + } + + if category not in valid_categories: + return Response({"error": f"Invalid category. Valid categories: {', '.join(valid_categories)}"}, status=400) + + api_key = getattr(settings, 'GOOGLE_MAPS_API_KEY', None) + + # Fallback to Overpass if no API key configured + if not api_key: + return self.query_overpass(lat, lon, radius, category, request) + + base_url = "https://maps.googleapis.com/maps/api/place/nearbysearch/json" + params = { + 'location': f"{lat},{lon}", + 'radius': radius, + 'type': valid_categories[category], + 'key': api_key + } + + all_places = [] + page_token = None + + try: + for _ in range(3): # Max 3 pages + if page_token: + params['pagetoken'] = page_token + time.sleep(2.5) + + response = requests.get(base_url, params=params) + response.raise_for_status() + data = response.json() + all_places.extend(data.get('results', [])) + page_token = data.get('next_page_token') + if not page_token: + break + + origin = (float(lat), float(lon)) + adventures = self.parse_google_places(all_places, origin) + return Response(adventures) + + except Exception as e: + print("Google Places API failed, falling back to Overpass:", e) + return self.query_overpass(lat, lon, radius, category, request) diff --git a/backend/server/adventures/views/reverse_geocode_view.py b/backend/server/adventures/views/reverse_geocode_view.py index 4dc1d6b8..df45f13e 100644 --- a/backend/server/adventures/views/reverse_geocode_view.py +++ b/backend/server/adventures/views/reverse_geocode_view.py @@ -6,77 +6,44 @@ from adventures.models import Adventure from adventures.serializers import AdventureSerializer import requests +from adventures.geocoding import reverse_geocode +from adventures.geocoding import extractIsoCode +from django.conf import settings +from adventures.geocoding import search_google, search_osm class ReverseGeocodeViewSet(viewsets.ViewSet): permission_classes = [IsAuthenticated] - def extractIsoCode(self, data): - """ - Extract the ISO code from the response data. - Returns a dictionary containing the region name, country name, and ISO code if found. - """ - iso_code = None - town_city_or_county = None - display_name = None - country_code = None - city = None - visited_city = None - location_name = None - - # town = None - # city = None - # county = None - - if 'name' in data.keys(): - location_name = data['name'] - - if 'address' in data.keys(): - keys = data['address'].keys() - for key in keys: - if key.find("ISO") != -1: - iso_code = data['address'][key] - if 'town' in keys: - town_city_or_county = data['address']['town'] - if 'county' in keys: - town_city_or_county = data['address']['county'] - if 'city' in keys: - town_city_or_county = data['address']['city'] - if not iso_code: - return {"error": "No region found"} - - region = Region.objects.filter(id=iso_code).first() - visited_region = VisitedRegion.objects.filter(region=region, user_id=self.request.user).first() - - region_visited = False - city_visited = False - country_code = iso_code[:2] - - if region: - if town_city_or_county: - display_name = f"{town_city_or_county}, {region.name}, {country_code}" - city = City.objects.filter(name__contains=town_city_or_county, region=region).first() - visited_city = VisitedCity.objects.filter(city=city, user_id=self.request.user).first() - - if visited_region: - region_visited = True - if visited_city: - city_visited = True - if region: - return {"region_id": iso_code, "region": region.name, "country": region.country.name, "region_visited": region_visited, "display_name": display_name, "city": city.name if city else None, "city_id": city.id if city else None, "city_visited": city_visited, 'location_name': location_name} - return {"error": "No region found"} - @action(detail=False, methods=['get']) def reverse_geocode(self, request): lat = request.query_params.get('lat', '') lon = request.query_params.get('lon', '') - url = f"https://nominatim.openstreetmap.org/reverse?format=jsonv2&lat={lat}&lon={lon}" - headers = {'User-Agent': 'AdventureLog Server'} - response = requests.get(url, headers=headers) + if not lat or not lon: + return Response({"error": "Latitude and longitude are required"}, status=400) + try: + lat = float(lat) + lon = float(lon) + except ValueError: + return Response({"error": "Invalid latitude or longitude"}, status=400) + data = reverse_geocode(lat, lon, self.request.user) + if 'error' in data: + return Response({"error": "An internal error occurred while processing the request"}, status=400) + return Response(data) + + @action(detail=False, methods=['get']) + def search(self, request): + query = request.query_params.get('query', '') + if not query: + return Response({"error": "Query parameter is required"}, status=400) + try: - data = response.json() - except requests.exceptions.JSONDecodeError: - return Response({"error": "Invalid response from geocoding service"}, status=400) - return Response(self.extractIsoCode(data)) + if getattr(settings, 'GOOGLE_MAPS_API_KEY', None): + results = search_google(query) + else: + results = search_osm(query) + return Response(results) + except Exception: + return Response({"error": "An internal error occurred while processing the request"}, status=500) @action(detail=False, methods=['post']) def mark_visited_region(self, request): @@ -93,16 +60,15 @@ def mark_visited_region(self, request): lon = adventure.longitude if not lat or not lon: continue - url = f"https://nominatim.openstreetmap.org/reverse?format=jsonv2&lat={lat}&lon={lon}" - headers = {'User-Agent': 'AdventureLog Server'} - response = requests.get(url, headers=headers) - try: - data = response.json() - except requests.exceptions.JSONDecodeError: - return Response({"error": "Invalid response from geocoding service"}, status=400) - extracted_region = self.extractIsoCode(data) - if 'error' not in extracted_region: - region = Region.objects.filter(id=extracted_region['region_id']).first() + + # Use the existing reverse_geocode function which handles both Google and OSM + data = reverse_geocode(lat, lon, self.request.user) + if 'error' in data: + continue + + # data already contains region_id and city_id + if 'region_id' in data and data['region_id'] is not None: + region = Region.objects.filter(id=data['region_id']).first() visited_region = VisitedRegion.objects.filter(region=region, user_id=self.request.user).first() if not visited_region: visited_region = VisitedRegion(region=region, user_id=self.request.user) @@ -110,12 +76,12 @@ def mark_visited_region(self, request): new_region_count += 1 new_regions[region.id] = region.name - if extracted_region['city_id'] is not None: - city = City.objects.filter(id=extracted_region['city_id']).first() - visited_city = VisitedCity.objects.filter(city=city, user_id=self.request.user).first() - if not visited_city: - visited_city = VisitedCity(city=city, user_id=self.request.user) - visited_city.save() - new_city_count += 1 - new_cities[city.id] = city.name + if 'city_id' in data and data['city_id'] is not None: + city = City.objects.filter(id=data['city_id']).first() + visited_city = VisitedCity.objects.filter(city=city, user_id=self.request.user).first() + if not visited_city: + visited_city = VisitedCity(city=city, user_id=self.request.user) + visited_city.save() + new_city_count += 1 + new_cities[city.id] = city.name return Response({"new_regions": new_region_count, "regions": new_regions, "new_cities": new_city_count, "cities": new_cities}) \ No newline at end of file diff --git a/backend/server/adventures/views/stats_view.py b/backend/server/adventures/views/stats_view.py index 4b3a524e..8b56f269 100644 --- a/backend/server/adventures/views/stats_view.py +++ b/backend/server/adventures/views/stats_view.py @@ -14,7 +14,7 @@ class StatsViewSet(viewsets.ViewSet): """ A simple ViewSet for listing the stats of a user. """ - @action(detail=False, methods=['get'], url_path='counts/(?P[\w.@+-]+)') + @action(detail=False, methods=['get'], url_path=r'counts/(?P[\w.@+-]+)') def counts(self, request, username): if request.user.username == username: user = get_object_or_404(User, username=username) diff --git a/backend/server/integrations/migrations/0002_immichintegration_copy_locally.py b/backend/server/integrations/migrations/0002_immichintegration_copy_locally.py new file mode 100644 index 00000000..cdd59cc9 --- /dev/null +++ b/backend/server/integrations/migrations/0002_immichintegration_copy_locally.py @@ -0,0 +1,18 @@ +# Generated by Django 5.2.1 on 2025-06-01 21:38 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('integrations', '0001_initial'), + ] + + operations = [ + migrations.AddField( + model_name='immichintegration', + name='copy_locally', + field=models.BooleanField(default=True, help_text='Copy image to local storage, instead of just linking to the remote URL.'), + ), + ] diff --git a/backend/server/integrations/models.py b/backend/server/integrations/models.py index 9db8a07c..7b0400ae 100644 --- a/backend/server/integrations/models.py +++ b/backend/server/integrations/models.py @@ -9,6 +9,7 @@ class ImmichIntegration(models.Model): api_key = models.CharField(max_length=255) user = models.ForeignKey( User, on_delete=models.CASCADE) + copy_locally = models.BooleanField(default=True, help_text="Copy image to local storage, instead of just linking to the remote URL.") id = models.UUIDField(default=uuid.uuid4, editable=False, unique=True, primary_key=True) def __str__(self): diff --git a/backend/server/integrations/views.py b/backend/server/integrations/views.py index dbf383ab..ea4071a8 100644 --- a/backend/server/integrations/views.py +++ b/backend/server/integrations/views.py @@ -1,13 +1,19 @@ import os from rest_framework.response import Response from rest_framework import viewsets, status - from .serializers import ImmichIntegrationSerializer from .models import ImmichIntegration from rest_framework.decorators import action from rest_framework.permissions import IsAuthenticated import requests from rest_framework.pagination import PageNumberPagination +from django.conf import settings +from adventures.models import AdventureImage +from django.http import HttpResponse +from django.shortcuts import get_object_or_404 +import logging + +logger = logging.getLogger(__name__) class IntegrationView(viewsets.ViewSet): permission_classes = [IsAuthenticated] @@ -16,15 +22,16 @@ def list(self, request): RESTful GET method for listing all integrations. """ immich_integrations = ImmichIntegration.objects.filter(user=request.user) + google_map_integration = settings.GOOGLE_MAPS_API_KEY != '' return Response( { - 'immich': immich_integrations.exists() + 'immich': immich_integrations.exists(), + 'google_maps': google_map_integration }, status=status.HTTP_200_OK ) - class StandardResultsSetPagination(PageNumberPagination): page_size = 25 page_size_query_param = 'page_size' @@ -33,13 +40,24 @@ class StandardResultsSetPagination(PageNumberPagination): class ImmichIntegrationView(viewsets.ViewSet): permission_classes = [IsAuthenticated] pagination_class = StandardResultsSetPagination + def check_integration(self, request): """ Checks if the user has an active Immich integration. Returns: - - None if the integration exists. + - The integration object if it exists. - A Response with an error message if the integration is missing. """ + if not request.user.is_authenticated: + return Response( + { + 'message': 'You need to be authenticated to use this feature.', + 'error': True, + 'code': 'immich.authentication_required' + }, + status=status.HTTP_403_FORBIDDEN + ) + user_integrations = ImmichIntegration.objects.filter(user=request.user) if not user_integrations.exists(): return Response( @@ -50,7 +68,8 @@ def check_integration(self, request): }, status=status.HTTP_403_FORBIDDEN ) - return ImmichIntegration.objects.first() + + return user_integrations.first() @action(detail=False, methods=['get'], url_path='search') def search(self, request): @@ -61,7 +80,7 @@ def search(self, request): integration = self.check_integration(request) if isinstance(integration, Response): return integration - + query = request.query_params.get('query', '') date = request.query_params.get('date', '') @@ -74,12 +93,30 @@ def search(self, request): }, status=status.HTTP_400_BAD_REQUEST ) - + arguments = {} if query: arguments['query'] = query if date: - arguments['takenBefore'] = date + # Create date range for the entire selected day + from datetime import datetime, timedelta + try: + # Parse the date and create start/end of day + selected_date = datetime.strptime(date, '%Y-%m-%d') + start_of_day = selected_date.strftime('%Y-%m-%d') + end_of_day = (selected_date + timedelta(days=1)).strftime('%Y-%m-%d') + + arguments['takenAfter'] = start_of_day + arguments['takenBefore'] = end_of_day + except ValueError: + return Response( + { + 'message': 'Invalid date format. Use YYYY-MM-DD.', + 'error': True, + 'code': 'immich.invalid_date_format' + }, + status=status.HTTP_400_BAD_REQUEST + ) # check so if the server is down, it does not tweak out like a madman and crash the server with a 500 error code try: @@ -99,14 +136,14 @@ def search(self, request): }, status=status.HTTP_503_SERVICE_UNAVAILABLE ) - + if 'assets' in res and 'items' in res['assets']: paginator = self.pagination_class() # for each item in the items, we need to add the image url to the item so we can display it in the frontend public_url = os.environ.get('PUBLIC_URL', 'http://127.0.0.1:8000').rstrip('/') public_url = public_url.replace("'", "") for item in res['assets']['items']: - item['image_url'] = f'{public_url}/api/integrations/immich/get/{item["id"]}' + item['image_url'] = f'{public_url}/api/integrations/immich/{integration.id}/get/{item["id"]}' result_page = paginator.paginate_queryset(res['assets']['items'], request) return paginator.get_paginated_response(result_page) else: @@ -118,44 +155,6 @@ def search(self, request): }, status=status.HTTP_404_NOT_FOUND ) - - @action(detail=False, methods=['get'], url_path='get/(?P[^/.]+)') - def get(self, request, imageid=None): - """ - RESTful GET method for retrieving a specific Immich image by ID. - """ - # Check for integration before proceeding - integration = self.check_integration(request) - if isinstance(integration, Response): - return integration - - if not imageid: - return Response( - { - 'message': 'Image ID is required.', - 'error': True, - 'code': 'immich.imageid_required' - }, - status=status.HTTP_400_BAD_REQUEST - ) - - # check so if the server is down, it does not tweak out like a madman and crash the server with a 500 error code - try: - immich_fetch = requests.get(f'{integration.server_url}/assets/{imageid}/thumbnail?size=preview', headers={ - 'x-api-key': integration.api_key - }) - # should return the image file - from django.http import HttpResponse - return HttpResponse(immich_fetch.content, content_type='image/jpeg', status=status.HTTP_200_OK) - except requests.exceptions.ConnectionError: - return Response( - { - 'message': 'The Immich server is currently down or unreachable.', - 'error': True, - 'code': 'immich.server_down' - }, - status=status.HTTP_503_SERVICE_UNAVAILABLE - ) @action(detail=False, methods=['get']) def albums(self, request): @@ -187,7 +186,7 @@ def albums(self, request): res, status=status.HTTP_200_OK ) - + @action(detail=False, methods=['get'], url_path='albums/(?P[^/.]+)') def album(self, request, albumid=None): """ @@ -195,6 +194,7 @@ def album(self, request, albumid=None): """ # Check for integration before proceeding integration = self.check_integration(request) + print(integration.user) if isinstance(integration, Response): return integration @@ -230,7 +230,7 @@ def album(self, request, albumid=None): public_url = os.environ.get('PUBLIC_URL', 'http://127.0.0.1:8000').rstrip('/') public_url = public_url.replace("'", "") for item in res['assets']: - item['image_url'] = f'{public_url}/api/integrations/immich/get/{item["id"]}' + item['image_url'] = f'{public_url}/api/integrations/immich/{integration.id}/get/{item["id"]}' result_page = paginator.paginate_queryset(res['assets'], request) return paginator.get_paginated_response(result_page) else: @@ -243,6 +243,119 @@ def album(self, request, albumid=None): status=status.HTTP_404_NOT_FOUND ) + @action( + detail=False, + methods=['get'], + url_path='(?P[^/.]+)/get/(?P[^/.]+)', + permission_classes=[] + ) + def get_by_integration(self, request, integration_id=None, imageid=None): + """ + GET an Immich image using the integration and asset ID. + Access levels (in order of priority): + 1. Public adventures: accessible by anyone + 2. Private adventures in public collections: accessible by anyone + 3. Private adventures in private collections shared with user: accessible by shared users + 4. Private adventures: accessible only to the owner + 5. No AdventureImage: owner can still view via integration + """ + if not imageid or not integration_id: + return Response({ + 'message': 'Image ID and Integration ID are required.', + 'error': True, + 'code': 'immich.missing_params' + }, status=status.HTTP_400_BAD_REQUEST) + + # Lookup integration and user + integration = get_object_or_404(ImmichIntegration, id=integration_id) + owner_id = integration.user_id + + # Try to find the image entry with collection and sharing information + image_entry = ( + AdventureImage.objects + .filter(immich_id=imageid, user_id=owner_id) + .select_related('adventure', 'adventure__collection') + .prefetch_related('adventure__collection__shared_with') + .order_by( + '-adventure__is_public', # Public adventures first + '-adventure__collection__is_public' # Then public collections + ) + .first() + ) + + # Access control + if image_entry: + adventure = image_entry.adventure + collection = adventure.collection + + # Determine access level + is_authorized = False + + # Level 1: Public adventure (highest priority) + if adventure.is_public: + is_authorized = True + + # Level 2: Private adventure in public collection + elif collection and collection.is_public: + is_authorized = True + + # Level 3: Owner access + elif request.user.is_authenticated and request.user.id == owner_id: + is_authorized = True + + # Level 4: Shared collection access + elif (request.user.is_authenticated and collection and + collection.shared_with.filter(id=request.user.id).exists()): + is_authorized = True + + if not is_authorized: + return Response({ + 'message': 'This image belongs to a private adventure and you are not authorized.', + 'error': True, + 'code': 'immich.permission_denied' + }, status=status.HTTP_403_FORBIDDEN) + else: + # No AdventureImage exists; allow only the integration owner + if not request.user.is_authenticated or request.user.id != owner_id: + return Response({ + 'message': 'Image is not linked to any adventure and you are not the owner.', + 'error': True, + 'code': 'immich.not_found' + }, status=status.HTTP_404_NOT_FOUND) + + # Fetch from Immich + try: + immich_response = requests.get( + f'{integration.server_url}/assets/{imageid}/thumbnail?size=preview', + headers={'x-api-key': integration.api_key}, + timeout=5 + ) + content_type = immich_response.headers.get('Content-Type', 'image/jpeg') + if not content_type.startswith('image/'): + return Response({ + 'message': 'Invalid content type returned from Immich.', + 'error': True, + 'code': 'immich.invalid_content' + }, status=status.HTTP_502_BAD_GATEWAY) + + response = HttpResponse(immich_response.content, content_type=content_type, status=200) + response['Cache-Control'] = 'public, max-age=86400, stale-while-revalidate=3600' + return response + + except requests.exceptions.ConnectionError: + return Response({ + 'message': 'The Immich server is unreachable.', + 'error': True, + 'code': 'immich.server_down' + }, status=status.HTTP_503_SERVICE_UNAVAILABLE) + + except requests.exceptions.Timeout: + return Response({ + 'message': 'The Immich server request timed out.', + 'error': True, + 'code': 'immich.timeout' + }, status=status.HTTP_504_GATEWAY_TIMEOUT) + class ImmichIntegrationViewSet(viewsets.ModelViewSet): permission_classes = [IsAuthenticated] serializer_class = ImmichIntegrationSerializer @@ -251,11 +364,78 @@ class ImmichIntegrationViewSet(viewsets.ModelViewSet): def get_queryset(self): return ImmichIntegration.objects.filter(user=self.request.user) + def _validate_immich_connection(self, server_url, api_key): + """ + Validate connection to Immich server before saving integration. + Returns tuple: (is_valid, corrected_server_url, error_message) + """ + if not server_url or not api_key: + return False, server_url, "Server URL and API key are required" + + # Ensure server_url has proper format + if not server_url.startswith(('http://', 'https://')): + server_url = f"https://{server_url}" + + # Remove trailing slash if present + original_server_url = server_url.rstrip('/') + + # Try both with and without /api prefix + test_configs = [ + (original_server_url, f"{original_server_url}/users/me"), + (f"{original_server_url}/api", f"{original_server_url}/api/users/me") + ] + + headers = { + 'X-API-Key': api_key, + 'Content-Type': 'application/json' + } + + for corrected_url, test_endpoint in test_configs: + try: + response = requests.get( + test_endpoint, + headers=headers, + timeout=10, # 10 second timeout + verify=True # SSL verification + ) + + if response.status_code == 200: + try: + json_response = response.json() + # Validate expected Immich user response structure + required_fields = ['id', 'email', 'name', 'isAdmin', 'createdAt'] + if all(field in json_response for field in required_fields): + return True, corrected_url, None + else: + continue # Try next endpoint + except (ValueError, KeyError): + continue # Try next endpoint + elif response.status_code == 401: + return False, original_server_url, "Invalid API key or unauthorized access" + elif response.status_code == 403: + return False, original_server_url, "Access forbidden - check API key permissions" + # Continue to next endpoint for 404 errors + + except requests.exceptions.ConnectTimeout: + return False, original_server_url, "Connection timeout - server may be unreachable" + except requests.exceptions.ConnectionError: + return False, original_server_url, "Cannot connect to server - check URL and network connectivity" + except requests.exceptions.SSLError: + return False, original_server_url, "SSL certificate error - check server certificate" + except requests.exceptions.RequestException as e: + logger.error(f"RequestException during Immich connection validation: {str(e)}") + return False, original_server_url, "Connection failed due to a network error." + except Exception as e: + logger.error(f"Unexpected error during Immich connection validation: {str(e)}") + return False, original_server_url, "An unexpected error occurred while validating the connection." + + # If we get here, none of the endpoints worked + return False, original_server_url, "Immich server endpoint not found - check server URL" + def create(self, request): """ RESTful POST method for creating a new Immich integration. """ - # Check if the user already has an integration user_integrations = ImmichIntegration.objects.filter(user=request.user) if user_integrations.exists(): @@ -270,11 +450,76 @@ def create(self, request): serializer = self.serializer_class(data=request.data) if serializer.is_valid(): - serializer.save(user=request.user) + # Validate Immich server connection before saving + server_url = serializer.validated_data.get('server_url') + api_key = serializer.validated_data.get('api_key') + + is_valid, corrected_server_url, error_message = self._validate_immich_connection(server_url, api_key) + + if not is_valid: + return Response( + { + 'message': f'Cannot connect to Immich server: {error_message}', + 'error': True, + 'code': 'immich.connection_failed', + 'details': error_message + }, + status=status.HTTP_400_BAD_REQUEST + ) + + # If validation passes, save the integration with the corrected URL + serializer.save(user=request.user, server_url=corrected_server_url) return Response( serializer.data, status=status.HTTP_201_CREATED ) + + return Response( + serializer.errors, + status=status.HTTP_400_BAD_REQUEST + ) + + def update(self, request, pk=None): + """ + RESTful PUT method for updating an existing Immich integration. + """ + integration = ImmichIntegration.objects.filter(user=request.user, id=pk).first() + if not integration: + return Response( + { + 'message': 'Integration not found.', + 'error': True, + 'code': 'immich.integration_not_found' + }, + status=status.HTTP_404_NOT_FOUND + ) + + serializer = self.serializer_class(integration, data=request.data, partial=True) + if serializer.is_valid(): + # Validate Immich server connection before updating + server_url = serializer.validated_data.get('server_url', integration.server_url) + api_key = serializer.validated_data.get('api_key', integration.api_key) + + is_valid, corrected_server_url, error_message = self._validate_immich_connection(server_url, api_key) + + if not is_valid: + return Response( + { + 'message': f'Cannot connect to Immich server: {error_message}', + 'error': True, + 'code': 'immich.connection_failed', + 'details': error_message + }, + status=status.HTTP_400_BAD_REQUEST + ) + + # If validation passes, save the integration with the corrected URL + serializer.save(server_url=corrected_server_url) + return Response( + serializer.data, + status=status.HTTP_200_OK + ) + return Response( serializer.errors, status=status.HTTP_400_BAD_REQUEST @@ -301,10 +546,9 @@ def destroy(self, request, pk=None): }, status=status.HTTP_200_OK ) - + def list(self, request, *args, **kwargs): # If the user has an integration, we only want to return that integration - user_integrations = ImmichIntegration.objects.filter(user=request.user) if user_integrations.exists(): integration = user_integrations.first() diff --git a/backend/server/main/settings.py b/backend/server/main/settings.py index 406e37a3..073fd778 100644 --- a/backend/server/main/settings.py +++ b/backend/server/main/settings.py @@ -27,7 +27,7 @@ SECRET_KEY = getenv('SECRET_KEY') # SECURITY WARNING: don't run with debug turned on in production! -DEBUG = getenv('DEBUG', 'True') == 'True' +DEBUG = getenv('DEBUG', 'true').lower() == 'true' # ALLOWED_HOSTS = [ # 'localhost', @@ -71,6 +71,7 @@ 'whitenoise.middleware.WhiteNoiseMiddleware', 'adventures.middleware.XSessionTokenMiddleware', 'adventures.middleware.DisableCSRFForSessionTokenMiddleware', + 'adventures.middleware.DisableCSRFForMobileLoginSignup', 'corsheaders.middleware.CorsMiddleware', 'django.contrib.sessions.middleware.SessionMiddleware', 'django.middleware.common.CommonMiddleware', @@ -101,22 +102,30 @@ # Database # https://docs.djangoproject.com/en/1.7/ref/settings/#databases +# Using legacy PG environment variables for compatibility with existing setups + +def env(*keys, default=None): + """Return the first non-empty environment variable from a list of keys.""" + for key in keys: + value = os.getenv(key) + if value: + return value + return default + DATABASES = { 'default': { 'ENGINE': 'django.contrib.gis.db.backends.postgis', - 'NAME': getenv('PGDATABASE'), - 'USER': getenv('PGUSER'), - 'PASSWORD': getenv('PGPASSWORD'), - 'HOST': getenv('PGHOST'), - 'PORT': getenv('PGPORT', 5432), + 'NAME': env('PGDATABASE', 'POSTGRES_DB'), + 'USER': env('PGUSER', 'POSTGRES_USER'), + 'PASSWORD': env('PGPASSWORD', 'POSTGRES_PASSWORD'), + 'HOST': env('PGHOST', default='localhost'), + 'PORT': int(env('PGPORT', default='5432')), 'OPTIONS': { 'sslmode': 'prefer', # Prefer SSL, but allow non-SSL connections }, } } - - # Internationalization # https://docs.djangoproject.com/en/1.7/topics/i18n/ @@ -138,6 +147,8 @@ SESSION_COOKIE_NAME = 'sessionid' SESSION_COOKIE_SECURE = FRONTEND_URL.startswith('https') +CSRF_COOKIE_SECURE = FRONTEND_URL.startswith('https') + hostname = urlparse(FRONTEND_URL).hostname is_ip_address = hostname.replace('.', '').isdigit() @@ -201,7 +212,7 @@ # Authentication settings -DISABLE_REGISTRATION = getenv('DISABLE_REGISTRATION', 'False') == 'True' +DISABLE_REGISTRATION = getenv('DISABLE_REGISTRATION', 'false').lower() == 'true' DISABLE_REGISTRATION_MESSAGE = getenv('DISABLE_REGISTRATION_MESSAGE', 'Registration is disabled. Please contact the administrator if you need an account.') AUTH_USER_MODEL = 'users.CustomUser' @@ -229,6 +240,8 @@ AUTHENTICATION_BACKENDS = [ 'users.backends.NoPasswordAuthBackend', + # 'allauth.account.auth_backends.AuthenticationBackend', + # 'django.contrib.auth.backends.ModelBackend', ] EMAIL_BACKEND = 'django.core.mail.backends.console.EmailBackend' @@ -242,9 +255,9 @@ else: EMAIL_BACKEND = 'django.core.mail.backends.smtp.EmailBackend' EMAIL_HOST = getenv('EMAIL_HOST') - EMAIL_USE_TLS = getenv('EMAIL_USE_TLS', 'True') == 'True' + EMAIL_USE_TLS = getenv('EMAIL_USE_TLS', 'true').lower() == 'true' EMAIL_PORT = getenv('EMAIL_PORT', 587) - EMAIL_USE_SSL = getenv('EMAIL_USE_SSL', 'False') == 'True' + EMAIL_USE_SSL = getenv('EMAIL_USE_SSL', 'false').lower() == 'true' EMAIL_HOST_USER = getenv('EMAIL_HOST_USER') EMAIL_HOST_PASSWORD = getenv('EMAIL_HOST_PASSWORD') DEFAULT_FROM_EMAIL = getenv('DEFAULT_FROM_EMAIL') @@ -312,4 +325,6 @@ # ADVENTURELOG_CDN_URL = getenv('ADVENTURELOG_CDN_URL', 'https://cdn.adventurelog.app') # https://github.com/dr5hn/countries-states-cities-database/tags -COUNTRY_REGION_JSON_VERSION = 'v2.5' \ No newline at end of file +COUNTRY_REGION_JSON_VERSION = 'v2.6' + +GOOGLE_MAPS_API_KEY = getenv('GOOGLE_MAPS_API_KEY', '') \ No newline at end of file diff --git a/backend/server/requirements.txt b/backend/server/requirements.txt index dcd01250..c679ecc0 100644 --- a/backend/server/requirements.txt +++ b/backend/server/requirements.txt @@ -1,16 +1,16 @@ -Django==5.0.11 +Django==5.2.1 djangorestframework>=3.15.2 django-allauth==0.63.3 drf-yasg==1.21.4 django-cors-headers==4.4.0 coreapi==2.3.3 -python-dotenv -psycopg2-binary -Pillow -whitenoise -django-resized -django-geojson -setuptools +python-dotenv==1.1.0 +psycopg2-binary==2.9.10 +pillow==11.2.1 +whitenoise==6.9.0 +django-resized==1.0.3 +django-geojson==4.2.0 +setuptools==79.0.1 gunicorn==23.0.0 qrcode==8.0 slippers==0.6.2 @@ -21,4 +21,6 @@ icalendar==6.1.0 ijson==3.3.0 tqdm==4.67.1 overpy==0.7 -publicsuffix2==2.20191221 \ No newline at end of file +publicsuffix2==2.20191221 +geopy==2.4.1 +psutil==6.1.1 \ No newline at end of file diff --git a/backend/server/users/backends.py b/backend/server/users/backends.py index a099f118..8c985ce7 100644 --- a/backend/server/users/backends.py +++ b/backend/server/users/backends.py @@ -1,16 +1,44 @@ from django.contrib.auth.backends import ModelBackend from allauth.socialaccount.models import SocialAccount +from allauth.account.auth_backends import AuthenticationBackend as AllauthBackend +from django.contrib.auth import get_user_model + +User = get_user_model() class NoPasswordAuthBackend(ModelBackend): def authenticate(self, request, username=None, password=None, **kwargs): - print("NoPasswordAuthBackend") - # First, attempt normal authentication - user = super().authenticate(request, username=username, password=password, **kwargs) - if user is None: + # Handle allauth-specific authentication (like email login) + allauth_backend = AllauthBackend() + allauth_user = allauth_backend.authenticate(request, username=username, password=password, **kwargs) + + # If allauth handled it, check our password disable logic + if allauth_user: + has_social_accounts = SocialAccount.objects.filter(user=allauth_user).exists() + if has_social_accounts and getattr(allauth_user, 'disable_password', False): + return None + if self.user_can_authenticate(allauth_user): + return allauth_user + return None + + # Fallback to regular username/password authentication + if username is None or password is None: return None - if SocialAccount.objects.filter(user=user).exists() and user.disable_password: - # If yes, disable login via password + try: + # Get the user first + user = User.objects.get(username=username) + except User.DoesNotExist: return None - return user + # Check if this user has social accounts and password is disabled + has_social_accounts = SocialAccount.objects.filter(user=user).exists() + + # If user has social accounts and disable_password is True, deny password login + if has_social_accounts and getattr(user, 'disable_password', False): + return None + + # Otherwise, proceed with normal password authentication + if user.check_password(password) and self.user_can_authenticate(user): + return user + + return None \ No newline at end of file diff --git a/backend/server/users/views.py b/backend/server/users/views.py index 7c763f55..d5a5a413 100644 --- a/backend/server/users/views.py +++ b/backend/server/users/views.py @@ -165,7 +165,7 @@ def get(self, request): providers = [] for provider in social_providers: if provider.provider == 'openid_connect': - new_provider = f'oidc/{provider.client_id}' + new_provider = f'oidc/{provider.provider_id}' else: new_provider = provider.provider providers.append({ @@ -204,4 +204,4 @@ def delete(self, request): user.disable_password = False user.save() return Response({"detail": "Password authentication enabled."}, status=status.HTTP_200_OK) - \ No newline at end of file + diff --git a/backend/server/worldtravel/management/commands/bulk-adventure-geocode.py b/backend/server/worldtravel/management/commands/bulk-adventure-geocode.py new file mode 100644 index 00000000..1a5d7a54 --- /dev/null +++ b/backend/server/worldtravel/management/commands/bulk-adventure-geocode.py @@ -0,0 +1,26 @@ +from django.core.management.base import BaseCommand +from adventures.models import Adventure +import time + +class Command(BaseCommand): + help = 'Bulk geocode all adventures by triggering save on each one' + + def handle(self, *args, **options): + adventures = Adventure.objects.all() + total = adventures.count() + + self.stdout.write(self.style.SUCCESS(f'Starting bulk geocoding of {total} adventures')) + + for i, adventure in enumerate(adventures): + try: + self.stdout.write(f'Processing adventure {i+1}/{total}: {adventure}') + adventure.save() # This should trigger any geocoding in the save method + self.stdout.write(self.style.SUCCESS(f'Successfully processed adventure {i+1}/{total}')) + except Exception as e: + self.stdout.write(self.style.ERROR(f'Error processing adventure {i+1}/{total}: {adventure} - {e}')) + + # Sleep for 2 seconds between each save + if i < total - 1: # Don't sleep after the last one + time.sleep(2) + + self.stdout.write(self.style.SUCCESS('Finished processing all adventures')) diff --git a/backend/server/worldtravel/management/commands/download-countries.py b/backend/server/worldtravel/management/commands/download-countries.py index f5c5702d..5b2f48ed 100644 --- a/backend/server/worldtravel/management/commands/download-countries.py +++ b/backend/server/worldtravel/management/commands/download-countries.py @@ -3,8 +3,11 @@ import requests from worldtravel.models import Country, Region, City from django.db import transaction -from tqdm import tqdm import ijson +import gc +import tempfile +import sqlite3 +from contextlib import contextmanager from django.conf import settings @@ -36,55 +39,112 @@ def saveCountryFlag(country_code): print(f'Error downloading flag for {country_code}') class Command(BaseCommand): - help = 'Imports the world travel data' + help = 'Imports the world travel data with minimal memory usage' def add_arguments(self, parser): parser.add_argument('--force', action='store_true', help='Force download the countries+regions+states.json file') + parser.add_argument('--batch-size', type=int, default=500, help='Batch size for database operations') + + @contextmanager + def _temp_db(self): + """Create a temporary SQLite database for intermediate storage""" + with tempfile.NamedTemporaryFile(suffix='.db', delete=False) as f: + temp_db_path = f.name + + try: + conn = sqlite3.connect(temp_db_path) + conn.execute('''CREATE TABLE temp_countries ( + country_code TEXT PRIMARY KEY, + name TEXT, + subregion TEXT, + capital TEXT, + longitude REAL, + latitude REAL + )''') + + conn.execute('''CREATE TABLE temp_regions ( + id TEXT PRIMARY KEY, + name TEXT, + country_code TEXT, + longitude REAL, + latitude REAL + )''') + + conn.execute('''CREATE TABLE temp_cities ( + id TEXT PRIMARY KEY, + name TEXT, + region_id TEXT, + longitude REAL, + latitude REAL + )''') + + conn.commit() + yield conn + finally: + conn.close() + try: + os.unlink(temp_db_path) + except OSError: + pass def handle(self, **options): force = options['force'] - batch_size = 100 + batch_size = options['batch_size'] countries_json_path = os.path.join(settings.MEDIA_ROOT, f'countries+regions+states-{COUNTRY_REGION_JSON_VERSION}.json') + + # Download or validate JSON file if not os.path.exists(countries_json_path) or force: + self.stdout.write('Downloading JSON file...') res = requests.get(f'https://raw.githubusercontent.com/dr5hn/countries-states-cities-database/{COUNTRY_REGION_JSON_VERSION}/json/countries%2Bstates%2Bcities.json') if res.status_code == 200: with open(countries_json_path, 'w') as f: f.write(res.text) - self.stdout.write(self.style.SUCCESS('countries+regions+states.json downloaded successfully')) + self.stdout.write(self.style.SUCCESS('JSON file downloaded successfully')) else: - self.stdout.write(self.style.ERROR('Error downloading countries+regions+states.json')) + self.stdout.write(self.style.ERROR('Error downloading JSON file')) return elif not os.path.isfile(countries_json_path): - self.stdout.write(self.style.ERROR('countries+regions+states.json is not a file')) + self.stdout.write(self.style.ERROR('JSON file is not a file')) return elif os.path.getsize(countries_json_path) == 0: - self.stdout.write(self.style.ERROR('countries+regions+states.json is empty')) + self.stdout.write(self.style.ERROR('JSON file is empty')) + return elif Country.objects.count() == 0 or Region.objects.count() == 0 or City.objects.count() == 0: - self.stdout.write(self.style.WARNING('Some region data is missing. Re-importing all data.')) + self.stdout.write(self.style.WARNING('Some data is missing. Re-importing all data.')) else: - self.stdout.write(self.style.SUCCESS('Latest country, region, and state data already downloaded.')) + self.stdout.write(self.style.SUCCESS('Latest data already imported.')) return - - with open(countries_json_path, 'r') as f: - f = open(countries_json_path, 'rb') - parser = ijson.items(f, 'item') - with transaction.atomic(): - existing_countries = {country.country_code: country for country in Country.objects.all()} - existing_regions = {region.id: region for region in Region.objects.all()} - existing_cities = {city.id: city for city in City.objects.all()} + self.stdout.write(self.style.SUCCESS('Starting ultra-memory-efficient import process...')) - countries_to_create = [] - regions_to_create = [] - countries_to_update = [] - regions_to_update = [] - cities_to_create = [] - cities_to_update = [] + # Use temporary SQLite database for intermediate storage + with self._temp_db() as temp_conn: + self.stdout.write('Step 1: Parsing JSON and storing in temporary database...') + self._parse_and_store_temp(countries_json_path, temp_conn) + + self.stdout.write('Step 2: Processing countries...') + self._process_countries_from_temp(temp_conn, batch_size) + + self.stdout.write('Step 3: Processing regions...') + self._process_regions_from_temp(temp_conn, batch_size) + + self.stdout.write('Step 4: Processing cities...') + self._process_cities_from_temp(temp_conn, batch_size) + + self.stdout.write('Step 5: Cleaning up obsolete records...') + self._cleanup_obsolete_records(temp_conn) - processed_country_codes = set() - processed_region_ids = set() - processed_city_ids = set() + self.stdout.write(self.style.SUCCESS('All data imported successfully with minimal memory usage')) + def _parse_and_store_temp(self, json_path, temp_conn): + """Parse JSON once and store in temporary SQLite database""" + country_count = 0 + region_count = 0 + city_count = 0 + + with open(json_path, 'rb') as f: + parser = ijson.items(f, 'item') + for country in parser: country_code = country['iso2'] country_name = country['name'] @@ -93,137 +153,365 @@ def handle(self, **options): longitude = round(float(country['longitude']), 6) if country['longitude'] else None latitude = round(float(country['latitude']), 6) if country['latitude'] else None - processed_country_codes.add(country_code) + # Store country + temp_conn.execute('''INSERT OR REPLACE INTO temp_countries + (country_code, name, subregion, capital, longitude, latitude) + VALUES (?, ?, ?, ?, ?, ?)''', + (country_code, country_name, country_subregion, country_capital, longitude, latitude)) + + country_count += 1 + + # Download flag (do this during parsing to avoid extra pass) + saveCountryFlag(country_code) + + # Process regions/states + if country['states']: + for state in country['states']: + state_id = f"{country_code}-{state['state_code']}" + state_name = state['name'] + state_lat = round(float(state['latitude']), 6) if state['latitude'] else None + state_lng = round(float(state['longitude']), 6) if state['longitude'] else None + + temp_conn.execute('''INSERT OR REPLACE INTO temp_regions + (id, name, country_code, longitude, latitude) + VALUES (?, ?, ?, ?, ?)''', + (state_id, state_name, country_code, state_lng, state_lat)) + + region_count += 1 + + # Process cities + if 'cities' in state and state['cities']: + for city in state['cities']: + city_id = f"{state_id}-{city['id']}" + city_name = city['name'] + city_lat = round(float(city['latitude']), 6) if city['latitude'] else None + city_lng = round(float(city['longitude']), 6) if city['longitude'] else None + + temp_conn.execute('''INSERT OR REPLACE INTO temp_cities + (id, name, region_id, longitude, latitude) + VALUES (?, ?, ?, ?, ?)''', + (city_id, city_name, state_id, city_lng, city_lat)) + + city_count += 1 + else: + # Country without states - create default region + state_id = f"{country_code}-00" + temp_conn.execute('''INSERT OR REPLACE INTO temp_regions + (id, name, country_code, longitude, latitude) + VALUES (?, ?, ?, ?, ?)''', + (state_id, country_name, country_code, None, None)) + region_count += 1 + + # Commit periodically to avoid memory buildup + if country_count % 100 == 0: + temp_conn.commit() + self.stdout.write(f' Parsed {country_count} countries, {region_count} regions, {city_count} cities...') + + temp_conn.commit() + self.stdout.write(f'✓ Parsing complete: {country_count} countries, {region_count} regions, {city_count} cities') + def _process_countries_from_temp(self, temp_conn, batch_size): + """Process countries from temporary database""" + cursor = temp_conn.execute('SELECT country_code, name, subregion, capital, longitude, latitude FROM temp_countries') + + countries_to_create = [] + countries_to_update = [] + processed = 0 + + while True: + rows = cursor.fetchmany(batch_size) + if not rows: + break + + # Batch check for existing countries + country_codes_in_batch = [row[0] for row in rows] + existing_countries = { + c.country_code: c for c in + Country.objects.filter(country_code__in=country_codes_in_batch) + .only('country_code', 'name', 'subregion', 'capital', 'longitude', 'latitude') + } + + for row in rows: + country_code, name, subregion, capital, longitude, latitude = row + if country_code in existing_countries: + # Update existing country_obj = existing_countries[country_code] - country_obj.name = country_name - country_obj.subregion = country_subregion - country_obj.capital = country_capital + country_obj.name = name + country_obj.subregion = subregion + country_obj.capital = capital country_obj.longitude = longitude country_obj.latitude = latitude countries_to_update.append(country_obj) else: - country_obj = Country( - name=country_name, + countries_to_create.append(Country( country_code=country_code, - subregion=country_subregion, - capital=country_capital, + name=name, + subregion=subregion, + capital=capital, longitude=longitude, latitude=latitude + )) + + processed += 1 + + # Flush batches + if countries_to_create: + with transaction.atomic(): + Country.objects.bulk_create(countries_to_create, batch_size=batch_size, ignore_conflicts=True) + countries_to_create.clear() + + if countries_to_update: + with transaction.atomic(): + Country.objects.bulk_update( + countries_to_update, + ['name', 'subregion', 'capital', 'longitude', 'latitude'], + batch_size=batch_size ) - countries_to_create.append(country_obj) - - saveCountryFlag(country_code) + countries_to_update.clear() + + if processed % 1000 == 0: + self.stdout.write(f' Processed {processed} countries...') + gc.collect() + + # Final flush + if countries_to_create: + with transaction.atomic(): + Country.objects.bulk_create(countries_to_create, batch_size=batch_size, ignore_conflicts=True) + if countries_to_update: + with transaction.atomic(): + Country.objects.bulk_update( + countries_to_update, + ['name', 'subregion', 'capital', 'longitude', 'latitude'], + batch_size=batch_size + ) + + self.stdout.write(f'✓ Countries complete: {processed} processed') - if country['states']: - for state in country['states']: - name = state['name'] - state_id = f"{country_code}-{state['state_code']}" - latitude = round(float(state['latitude']), 6) if state['latitude'] else None - longitude = round(float(state['longitude']), 6) if state['longitude'] else None - - # Check for duplicate regions - if state_id in processed_region_ids: - # self.stdout.write(self.style.ERROR(f'State {state_id} already processed')) - continue - - processed_region_ids.add(state_id) - - if state_id in existing_regions: - region_obj = existing_regions[state_id] - region_obj.name = name - region_obj.country = country_obj - region_obj.longitude = longitude - region_obj.latitude = latitude - regions_to_update.append(region_obj) - else: - region_obj = Region( - id=state_id, - name=name, - country=country_obj, - longitude=longitude, - latitude=latitude - ) - regions_to_create.append(region_obj) - # self.stdout.write(self.style.SUCCESS(f'State {state_id} prepared')) - - if 'cities' in state and len(state['cities']) > 0: - for city in state['cities']: - city_id = f"{state_id}-{city['id']}" - city_name = city['name'] - latitude = round(float(city['latitude']), 6) if city['latitude'] else None - longitude = round(float(city['longitude']), 6) if city['longitude'] else None - - # Check for duplicate cities - if city_id in processed_city_ids: - # self.stdout.write(self.style.ERROR(f'City {city_id} already processed')) - continue - - processed_city_ids.add(city_id) - - if city_id in existing_cities: - city_obj = existing_cities[city_id] - city_obj.name = city_name - city_obj.region = region_obj - city_obj.longitude = longitude - city_obj.latitude = latitude - cities_to_update.append(city_obj) - else: - city_obj = City( - id=city_id, - name=city_name, - region=region_obj, - longitude=longitude, - latitude=latitude - ) - cities_to_create.append(city_obj) - # self.stdout.write(self.style.SUCCESS(f'City {city_id} prepared')) + def _process_regions_from_temp(self, temp_conn, batch_size): + """Process regions from temporary database""" + # Get country mapping once + country_map = {c.country_code: c for c in Country.objects.only('id', 'country_code')} + + cursor = temp_conn.execute('SELECT id, name, country_code, longitude, latitude FROM temp_regions') + + regions_to_create = [] + regions_to_update = [] + processed = 0 + + while True: + rows = cursor.fetchmany(batch_size) + if not rows: + break + + # Batch check for existing regions + region_ids_in_batch = [row[0] for row in rows] + existing_regions = { + r.id: r for r in + Region.objects.filter(id__in=region_ids_in_batch) + .select_related('country') + .only('id', 'name', 'country', 'longitude', 'latitude') + } + + for row in rows: + region_id, name, country_code, longitude, latitude = row + country_obj = country_map.get(country_code) + + if not country_obj: + continue + + if region_id in existing_regions: + # Update existing + region_obj = existing_regions[region_id] + region_obj.name = name + region_obj.country = country_obj + region_obj.longitude = longitude + region_obj.latitude = latitude + regions_to_update.append(region_obj) + else: + regions_to_create.append(Region( + id=region_id, + name=name, + country=country_obj, + longitude=longitude, + latitude=latitude + )) + + processed += 1 + + # Flush batches + if regions_to_create: + with transaction.atomic(): + Region.objects.bulk_create(regions_to_create, batch_size=batch_size, ignore_conflicts=True) + regions_to_create.clear() + + if regions_to_update: + with transaction.atomic(): + Region.objects.bulk_update( + regions_to_update, + ['name', 'country', 'longitude', 'latitude'], + batch_size=batch_size + ) + regions_to_update.clear() + + if processed % 2000 == 0: + self.stdout.write(f' Processed {processed} regions...') + gc.collect() + + # Final flush + if regions_to_create: + with transaction.atomic(): + Region.objects.bulk_create(regions_to_create, batch_size=batch_size, ignore_conflicts=True) + if regions_to_update: + with transaction.atomic(): + Region.objects.bulk_update( + regions_to_update, + ['name', 'country', 'longitude', 'latitude'], + batch_size=batch_size + ) + + self.stdout.write(f'✓ Regions complete: {processed} processed') + def _process_cities_from_temp(self, temp_conn, batch_size): + """Process cities from temporary database with optimized existence checking""" + # Get region mapping once + region_map = {r.id: r for r in Region.objects.only('id')} + + cursor = temp_conn.execute('SELECT id, name, region_id, longitude, latitude FROM temp_cities') + + cities_to_create = [] + cities_to_update = [] + processed = 0 + + while True: + rows = cursor.fetchmany(batch_size) + if not rows: + break + + # Fast existence check - only get IDs, no objects + city_ids_in_batch = [row[0] for row in rows] + existing_city_ids = set( + City.objects.filter(id__in=city_ids_in_batch) + .values_list('id', flat=True) + ) + + for row in rows: + city_id, name, region_id, longitude, latitude = row + region_obj = region_map.get(region_id) + + if not region_obj: + continue + + if city_id in existing_city_ids: + # For updates, just store the data - we'll do bulk update by raw SQL + cities_to_update.append({ + 'id': city_id, + 'name': name, + 'region_id': region_obj.id, + 'longitude': longitude, + 'latitude': latitude + }) else: - state_id = f"{country_code}-00" - processed_region_ids.add(state_id) - if state_id in existing_regions: - region_obj = existing_regions[state_id] - region_obj.name = country_name - region_obj.country = country_obj - regions_to_update.append(region_obj) - else: - region_obj = Region( - id=state_id, - name=country_name, - country=country_obj - ) - regions_to_create.append(region_obj) - # self.stdout.write(self.style.SUCCESS(f'Region {state_id} prepared for {country_name}')) - for i in tqdm(range(0, len(countries_to_create), batch_size), desc="Processing countries"): - batch = countries_to_create[i:i + batch_size] - Country.objects.bulk_create(batch) - - for i in tqdm(range(0, len(regions_to_create), batch_size), desc="Processing regions"): - batch = regions_to_create[i:i + batch_size] - Region.objects.bulk_create(batch) - - for i in tqdm(range(0, len(cities_to_create), batch_size), desc="Processing cities"): - batch = cities_to_create[i:i + batch_size] - City.objects.bulk_create(batch) - - # Process updates in batches - for i in range(0, len(countries_to_update), batch_size): - batch = countries_to_update[i:i + batch_size] - for i in tqdm(range(0, len(countries_to_update), batch_size), desc="Updating countries"): - batch = countries_to_update[i:i + batch_size] - Country.objects.bulk_update(batch, ['name', 'subregion', 'capital', 'longitude', 'latitude']) - - for i in tqdm(range(0, len(regions_to_update), batch_size), desc="Updating regions"): - batch = regions_to_update[i:i + batch_size] - Region.objects.bulk_update(batch, ['name', 'country', 'longitude', 'latitude']) - - for i in tqdm(range(0, len(cities_to_update), batch_size), desc="Updating cities"): - batch = cities_to_update[i:i + batch_size] - City.objects.bulk_update(batch, ['name', 'region', 'longitude', 'latitude']) - Country.objects.exclude(country_code__in=processed_country_codes).delete() - Region.objects.exclude(id__in=processed_region_ids).delete() - City.objects.exclude(id__in=processed_city_ids).delete() - - self.stdout.write(self.style.SUCCESS('All data imported successfully')) \ No newline at end of file + cities_to_create.append(City( + id=city_id, + name=name, + region=region_obj, + longitude=longitude, + latitude=latitude + )) + + processed += 1 + + # Flush create batch (this is already fast) + if cities_to_create: + with transaction.atomic(): + City.objects.bulk_create(cities_to_create, batch_size=batch_size, ignore_conflicts=True) + cities_to_create.clear() + + # Flush update batch with raw SQL for speed + if cities_to_update: + self._bulk_update_cities_raw(cities_to_update) + cities_to_update.clear() + + if processed % 5000 == 0: + self.stdout.write(f' Processed {processed} cities...') + gc.collect() + + # Final flush + if cities_to_create: + with transaction.atomic(): + City.objects.bulk_create(cities_to_create, batch_size=batch_size, ignore_conflicts=True) + if cities_to_update: + self._bulk_update_cities_raw(cities_to_update) + + self.stdout.write(f'✓ Cities complete: {processed} processed') + + def _bulk_update_cities_raw(self, cities_data): + """Fast bulk update using raw SQL""" + if not cities_data: + return + + from django.db import connection + + with connection.cursor() as cursor: + # Build the SQL for bulk update + # Using CASE statements for efficient bulk updates + when_clauses_name = [] + when_clauses_region = [] + when_clauses_lng = [] + when_clauses_lat = [] + city_ids = [] + + for city in cities_data: + city_id = city['id'] + city_ids.append(city_id) + when_clauses_name.append(f"WHEN id = %s THEN %s") + when_clauses_region.append(f"WHEN id = %s THEN %s") + when_clauses_lng.append(f"WHEN id = %s THEN %s") + when_clauses_lat.append(f"WHEN id = %s THEN %s") + + # Build parameters list + params = [] + for city in cities_data: + params.extend([city['id'], city['name']]) # for name + for city in cities_data: + params.extend([city['id'], city['region_id']]) # for region_id + for city in cities_data: + params.extend([city['id'], city['longitude']]) # for longitude + for city in cities_data: + params.extend([city['id'], city['latitude']]) # for latitude + params.extend(city_ids) # for WHERE clause + + # Execute the bulk update + sql = f""" + UPDATE worldtravel_city + SET + name = CASE {' '.join(when_clauses_name)} END, + region_id = CASE {' '.join(when_clauses_region)} END, + longitude = CASE {' '.join(when_clauses_lng)} END, + latitude = CASE {' '.join(when_clauses_lat)} END + WHERE id IN ({','.join(['%s'] * len(city_ids))}) + """ + + cursor.execute(sql, params) + + def _cleanup_obsolete_records(self, temp_conn): + """Clean up obsolete records using temporary database""" + # Get IDs from temp database to avoid loading large lists into memory + temp_country_codes = {row[0] for row in temp_conn.execute('SELECT country_code FROM temp_countries')} + temp_region_ids = {row[0] for row in temp_conn.execute('SELECT id FROM temp_regions')} + temp_city_ids = {row[0] for row in temp_conn.execute('SELECT id FROM temp_cities')} + + with transaction.atomic(): + countries_deleted = Country.objects.exclude(country_code__in=temp_country_codes).count() + regions_deleted = Region.objects.exclude(id__in=temp_region_ids).count() + cities_deleted = City.objects.exclude(id__in=temp_city_ids).count() + + Country.objects.exclude(country_code__in=temp_country_codes).delete() + Region.objects.exclude(id__in=temp_region_ids).delete() + City.objects.exclude(id__in=temp_city_ids).delete() + + if countries_deleted > 0 or regions_deleted > 0 or cities_deleted > 0: + self.stdout.write(f'✓ Deleted {countries_deleted} obsolete countries, {regions_deleted} regions, {cities_deleted} cities') + else: + self.stdout.write('✓ No obsolete records found to delete') \ No newline at end of file diff --git a/backend/server/worldtravel/serializers.py b/backend/server/worldtravel/serializers.py index 64721251..962914bf 100644 --- a/backend/server/worldtravel/serializers.py +++ b/backend/server/worldtravel/serializers.py @@ -22,8 +22,11 @@ def get_num_regions(self, obj): def get_num_visits(self, obj): request = self.context.get('request') - if request and hasattr(request, 'user'): - return VisitedRegion.objects.filter(region__country=obj, user_id=request.user).count() + user = getattr(request, 'user', None) + + if user and user.is_authenticated: + return VisitedRegion.objects.filter(region__country=obj, user_id=user).count() + return 0 class Meta: diff --git a/backend/supervisord.conf b/backend/supervisord.conf new file mode 100644 index 00000000..e7adec76 --- /dev/null +++ b/backend/supervisord.conf @@ -0,0 +1,16 @@ +[supervisord] +nodaemon=true + +[program:nginx] +command=/usr/sbin/nginx -g "daemon off;" +autorestart=true +stdout_logfile=/dev/stdout +stderr_logfile=/dev/stderr + +[program:gunicorn] +command=/code/entrypoint.sh +autorestart=true +stdout_logfile=/dev/stdout +stderr_logfile=/dev/stderr +stdout_logfile_maxbytes = 0 +stderr_logfile_maxbytes = 0 diff --git a/brand/screenshots/adventures.png b/brand/screenshots/adventures.png index fd61d549..70265b1a 100644 Binary files a/brand/screenshots/adventures.png and b/brand/screenshots/adventures.png differ diff --git a/brand/screenshots/countries.png b/brand/screenshots/countries.png index 3b8e5346..a8bc1c2c 100644 Binary files a/brand/screenshots/countries.png and b/brand/screenshots/countries.png differ diff --git a/brand/screenshots/dashboard.png b/brand/screenshots/dashboard.png index af4d8bb6..a2482a43 100644 Binary files a/brand/screenshots/dashboard.png and b/brand/screenshots/dashboard.png differ diff --git a/brand/screenshots/details.png b/brand/screenshots/details.png index 6ae57eb9..83af4a5e 100644 Binary files a/brand/screenshots/details.png and b/brand/screenshots/details.png differ diff --git a/brand/screenshots/edit.png b/brand/screenshots/edit.png index 123160d7..0e2d08c0 100644 Binary files a/brand/screenshots/edit.png and b/brand/screenshots/edit.png differ diff --git a/brand/screenshots/itinerary.png b/brand/screenshots/itinerary.png index f1532637..fb4ad745 100644 Binary files a/brand/screenshots/itinerary.png and b/brand/screenshots/itinerary.png differ diff --git a/brand/screenshots/map.png b/brand/screenshots/map.png index 22b13b9d..35ca3bd0 100644 Binary files a/brand/screenshots/map.png and b/brand/screenshots/map.png differ diff --git a/brand/screenshots/regions.png b/brand/screenshots/regions.png index 6092dc63..59d6b7d4 100644 Binary files a/brand/screenshots/regions.png and b/brand/screenshots/regions.png differ diff --git a/docker-compose.yml b/docker-compose.yml index eca6a8cd..034ec065 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -4,23 +4,17 @@ services: image: ghcr.io/seanmorley15/adventurelog-frontend:latest container_name: adventurelog-frontend restart: unless-stopped - environment: - - PUBLIC_SERVER_URL=http://server:8000 # Should be the service name of the backend with port 8000, even if you change the port in the backend service - - ORIGIN=http://localhost:8015 - - BODY_SIZE_LIMIT=Infinity + env_file: .env ports: - - "8015:3000" + - "${FRONTEND_PORT:-8015}:3000" depends_on: - server db: - image: postgis/postgis:15-3.3 + image: postgis/postgis:16-3.5 container_name: adventurelog-db restart: unless-stopped - environment: - POSTGRES_DB: database - POSTGRES_USER: adventure - POSTGRES_PASSWORD: changeme123 + env_file: .env volumes: - postgres_data:/var/lib/postgresql/data/ @@ -29,21 +23,9 @@ services: image: ghcr.io/seanmorley15/adventurelog-backend:latest container_name: adventurelog-backend restart: unless-stopped - environment: - - PGHOST=db - - PGDATABASE=database - - PGUSER=adventure - - PGPASSWORD=changeme123 - - SECRET_KEY=changeme123 - - DJANGO_ADMIN_USERNAME=admin - - DJANGO_ADMIN_PASSWORD=admin - - DJANGO_ADMIN_EMAIL=admin@example.com - - PUBLIC_URL=http://localhost:8016 # Match the outward port, used for the creation of image urls - - CSRF_TRUSTED_ORIGINS=http://localhost:8016,http://localhost:8015 # Comma separated list of trusted origins for CSRF - - DEBUG=False - - FRONTEND_URL=http://localhost:8015 # Used for email generation. This should be the url of the frontend + env_file: .env ports: - - "8016:80" + - "${BACKEND_PORT:-8016}:80" depends_on: - db volumes: diff --git a/documentation/.vitepress/config.mts b/documentation/.vitepress/config.mts index dbd92993..212eedb6 100644 --- a/documentation/.vitepress/config.mts +++ b/documentation/.vitepress/config.mts @@ -1,7 +1,5 @@ import { defineConfig } from "vitepress"; -const inProd = process.env.NODE_ENV === "production"; - // https://vitepress.dev/reference/site-config export default defineConfig({ head: [ @@ -15,6 +13,14 @@ export default defineConfig({ "data-website-id": "a7552764-5a1d-4fe7-80c2-5331e1a53cb6", }, ], + + [ + "link", + { + rel: "me", + href: "https://mastodon.social/@adventurelog", + }, + ], ], ignoreDeadLinks: "localhostLinks", title: "AdventureLog", @@ -25,6 +31,67 @@ export default defineConfig({ hostname: "https://adventurelog.app", }, + transformPageData(pageData) { + if (pageData.relativePath === "index.md") { + const jsonLd = { + "@context": "https://schema.org", + "@type": "SoftwareApplication", + name: "AdventureLog", + url: "https://adventurelog.app", + applicationCategory: "TravelApplication", + operatingSystem: "Web, Docker, Linux", + description: + "AdventureLog is a self-hosted platform for tracking and planning travel experiences. Built for modern explorers, it offers trip planning, journaling, tracking and location mapping in one privacy-respecting package.", + creator: { + "@type": "Person", + name: "Sean Morley", + url: "https://seanmorley.com", + }, + offers: { + "@type": "Offer", + price: "0.00", + priceCurrency: "USD", + description: "Open-source version available for self-hosting.", + }, + softwareVersion: "v0.10.0", + license: + "https://github.com/seanmorley15/adventurelog/blob/main/LICENSE", + screenshot: + "https://raw.githubusercontent.com/seanmorley15/AdventureLog/refs/heads/main/brand/screenshots/adventures.png", + downloadUrl: "https://github.com/seanmorley15/adventurelog", + sameAs: ["https://github.com/seanmorley15/adventurelog"], + keywords: [ + "self-hosted travel log", + "open source trip planner", + "travel journaling app", + "docker travel diary", + "map-based travel tracker", + "privacy-focused travel app", + "adventure log software", + "travel experience tracker", + "self-hosted travel app", + "open source travel software", + "trip planning tool", + "travel itinerary manager", + "location-based travel app", + "travel experience sharing", + "travel log application", + ], + }; + + return { + frontmatter: { + ...pageData.frontmatter, + head: [ + ["script", { type: "application/ld+json" }, JSON.stringify(jsonLd)], + ], + }, + }; + } + + return {}; + }, + themeConfig: { // https://vitepress.dev/reference/default-theme-config nav: [ @@ -62,6 +129,7 @@ export default defineConfig({ collapsed: false, items: [ { text: "Getting Started", link: "/docs/install/getting_started" }, + { text: "Quick Start Script â˛ī¸", link: "/docs/install/quick_start" }, { text: "Docker 🐋", link: "/docs/install/docker" }, { text: "Proxmox LXC 🐧", link: "/docs/install/proxmox_lxc" }, { text: "Synology NAS â˜ī¸", link: "/docs/install/synology_nas" }, @@ -80,10 +148,21 @@ export default defineConfig({ link: "/docs/install/nginx_proxy_manager", }, { text: "Traefik", link: "/docs/install/traefik" }, + { text: "Caddy", link: "/docs/install/caddy" }, ], }, ], }, + { + text: "Usage", + collapsed: false, + items: [ + { + text: "How to use AdventureLog", + link: "/docs/usage/usage", + }, + ], + }, { text: "Configuration", collapsed: false, @@ -92,6 +171,10 @@ export default defineConfig({ text: "Immich Integration", link: "/docs/configuration/immich_integration", }, + { + text: "Google Maps Integration", + link: "/docs/configuration/google_maps_integration", + }, { text: "Social Auth and OIDC", link: "/docs/configuration/social_auth", @@ -108,6 +191,10 @@ export default defineConfig({ text: "GitHub", link: "/docs/configuration/social_auth/github", }, + { + text: "Authelia", + link: "https://www.authelia.com/integration/openid-connect/adventure-log/", + }, { text: "Open ID Connect", link: "/docs/configuration/social_auth/oidc", @@ -134,6 +221,10 @@ export default defineConfig({ text: "No Images Displaying", link: "/docs/troubleshooting/no_images", }, + { + text: "Login and Registration Unresponsive", + link: "/docs/troubleshooting/login_unresponsive", + }, { text: "Failed to Start Nginx", link: "/docs/troubleshooting/nginx_failed", @@ -158,6 +249,14 @@ export default defineConfig({ text: "Changelogs", collapsed: false, items: [ + { + text: "v0.10.0", + link: "/docs/changelogs/v0-10-0", + }, + { + text: "v0.9.0", + link: "/docs/changelogs/v0-9-0", + }, { text: "v0.8.0", link: "/docs/changelogs/v0-8-0", @@ -180,6 +279,7 @@ export default defineConfig({ { icon: "buymeacoffee", link: "https://buymeacoffee.com/seanmorley15" }, { icon: "x", link: "https://x.com/AdventureLogApp" }, { icon: "mastodon", link: "https://mastodon.social/@adventurelog" }, + { icon: "instagram", link: "https://www.instagram.com/adventurelogapp" }, ], }, }); diff --git a/documentation/docs/changelogs/v0-10-0.md b/documentation/docs/changelogs/v0-10-0.md new file mode 100644 index 00000000..8ecba829 --- /dev/null +++ b/documentation/docs/changelogs/v0-10-0.md @@ -0,0 +1,123 @@ +# AdventureLog v0.10.0 - Trip Maps, Google Maps Integration & Quick Deploy Script + +Released 06-10-2025 + +Hi everyone, + +I’m pleased to share **AdventureLog v0.10.0**, a focused update that brings timezone-aware planning, smoother maps, and simpler deployment. This release refines many of the core features you’ve been using and addresses feedback from the community. Thank you for your contributions, suggestions, and ongoing support! + +## 🧭 Time-Aware Travel Planning + +**Timezone-Aware Visits & Timeline Logic** + +- Exact start/end times with timezones for each visit, so your itinerary matches when and where events actually happen. +- Collections now auto-order by date, giving your timeline a clear, chronological flow. +- Lodging and transportation entries follow the same rules, making multi-city trips easier to visualize. +- A chronologically accurate map and timeline view shows your adventure in the right order. + +## đŸ—ēī¸ Smart Mapping & Location Tools + +**Google Maps Integration (Optional)** + +- Autocomplete-powered location search (via Google Maps) for faster, more accurate entries. +- Automatic geocoding ties new or updated adventures to the correct country, region, and city. +- Improved map performance and cleaner markers throughout the app. + +**Map Interaction Enhancements** + +- Open any adventure location in Apple Maps, Google Maps, or OpenStreetMap with one click. +- Full-width maps on mobile devices for better visibility. +- Tidied-up markers and updated category icons for clarity. + +## 🎨 UI & UX Refinements + +- Updated adventure forms with clearer labels and streamlined inputs. +- Smoother page transitions and consistent layouts on desktop and mobile. +- Design and spacing adjustments for a more balanced, polished appearance. +- Various bug fixes to address layout quirks and improve overall usability. + +## 🌍 Localization & Navigation + +- Expanded language support and updated locale files. +- Improved back/forward navigation so you don’t lose your place when browsing collections. +- Responsive collection cards that adapt to different screen sizes. +- Fixed minor layout issues related to collections and navigation. + +## 📷 Immich Integration Upgrades + +- Choose whether to copy Immich images into AdventureLog storage or reference them via URL to avoid duplicating files. +- Toggle “Copy Images” on or off to manage storage preferences. +- Updated logic to prevent duplicate image uploads when using Immich. + +## âš™ī¸ DevOps & Backend Enhancements + +- Switched to `supervisord` in Docker for reliable container startup and centralized logging. +- Restored IPv6 support for dual-stack deployments. +- Upgraded to Node.js v22 for better performance and compatibility. +- Added more tests, improved UTC-aware date validation, and refined ID generation. +- Optimized database migrations for smoother updates. + +## 📘 Documentation & Community Resources + +- New guide for deploying with Caddy web server, covering TLS setup and reverse proxy configuration. +- Updated instructions for Google Maps integration, including API key setup and troubleshooting. +- Follow our Mastodon profile at [@adventurelog@mastodon.social](https://mastodon.social/@adventurelog) for updates and discussion. +- Chat with other users on our [Discord server](https://discord.gg/wRbQ9Egr8C) to share feedback, ask questions, or swap travel tips. + +## ✨ NEW: Quick Deploy Script + +Based on community feedback, we’ve added a simple deployment script: + +1. Run: + + ```bash + curl -sSL https://get.adventurelog.app | bash + ``` + +2. Provide your domain/ip details when prompted. + The script handles Docker Compose, and environment configuration automatically. + +Self-hosting just got a bit easier—no more manual setup steps. + +## â„šī¸ Additional Notes + +- **Bulk Geocoding** + To geocode existing adventures in one go docker exec into the backend container and run: + + ``` + python manage.py bulk-adventure-geocode + ``` + + This will link all adventures to their correct country, region, and city. + +- **Timezone Migrations** + If you have older trips without explicit timezones, simply view a trip’s detail page and AdventureLog will auto-convert the dates. + +## đŸ‘Ĩ Thanks to Our Contributors + +Your pull requests, issue reports, and ongoing feedback have been instrumental. Special thanks to: + +- @ClumsyAdmin +- @eidsheim98 +- @andreatitolo +- @lesensei +- @theshaun +- @lkiesow +- @larsl-net +- @marcschumacher + +Every contribution helps make AdventureLog more reliable and user-friendly. + +## 💖 Support the Project + +If you find AdventureLog helpful, consider sponsoring me! Your support keeps this project going: + +[https://seanmorley.com/sponsor](https://seanmorley.com/sponsor) + +📖 [View the Full Changelog on GitHub](https://github.com/seanmorley15/AdventureLog/compare/v0.9.0...v0.10.0) + +Thanks for being part of the AdventureLog community. I appreciate your feedback and look forward to seeing where your next journey takes you! + +Happy travels, +**Sean Morley** (@seanmorley15) +Project Lead, AdventureLog diff --git a/documentation/docs/changelogs/v0-9-0.md b/documentation/docs/changelogs/v0-9-0.md new file mode 100644 index 00000000..18764ee0 --- /dev/null +++ b/documentation/docs/changelogs/v0-9-0.md @@ -0,0 +1,133 @@ +# AdventureLog v0.9.0 - Smart Recommendations, Attachments, and Maps + +Released 03-19-2025 + +Hi travelers! 🌍 +I’m excited to unveil **AdventureLog v0.9.0**, one of our most feature-packed updates yet! From Smart Recommendations to enhanced maps and a refreshed profile system, this release is all about improving your travel planning and adventure tracking experience. Let’s dive into what’s new! + +--- + +## What's New ✨ + +### 🧠 Smart Recommendations + +- **AdventureLog Smart Recommendations**: Get tailored suggestions for new adventures and activities based on your collection destinations. +- Leverages OpenStreetMap to recommend places and activities near your travel destinations. + +--- + +### đŸ—‚ī¸ Attachments, GPX Maps & Global Search + +- **Attachments System**: Attach files to your adventures to view key trip data like maps and tickets in AdventureLog! +- **GPX File Uploads & Maps**: Upload GPX tracks to adventures to visualize them directly on your maps. +- **Global Search**: A universal search bar to quickly find adventures, cities, countries, and more across your instance. + +--- + +### 🏨 Lodging & Itinerary + +- **Lodging Tracking**: Add and manage lodging accommodations as part of your collections, complete with check-in/check-out dates. +- **Improved Itinerary Views**: Better day-by-day itinerary display with clear UI enhancements. + +--- + +### đŸ—ēī¸ Maps & Locations + +- **Open Locations in Maps**: Directly open adventure locations and points of interest in your preferred mapping service. +- **Adventure Category Icons on Maps**: View custom category icons right on your adventure and collection maps. + +--- + +### đŸ—“ī¸ Calendar + +- **Collection Range View**: Improved calendar view showing the full date range of collections. + +--- + +### 🌐 Authentication & Security + +- **OIDC Authentication**: Added support for OpenID Connect (OIDC) for seamless integration with identity providers. +- **Secure Session Cookies**: Improved session cookie handling with dynamic domain detection and better security for IP addresses. +- **Disable Password Auth**: Option to disable password auth for users with connected OIDC/Social accounts. + +--- + +### đŸ–Ĩī¸ PWA Support + +- **Progressive Web App (PWA) Support**: Install AdventureLog as a PWA on your desktop or mobile device for a native app experience. + +--- + +### đŸ—ī¸ Infrastructure & DevOps + +- **Dual-Stack Backend**: IPv4 and IPv6 ready backend system (@larsl-net). +- **Kubernetes Configs** continue to be improved for scalable deployments. + +--- + +### 🌐 Localization + +- **Korean language support** (@seanmorley15). +- **Improved Dutch** (@ThomasDetemmerman), **Simplified Chinese** (@jyyyeung), **German** (@Cathnan and @marcschumacher) translations. +- **Polish and Swedish** translations improved in prior release! + +--- + +### 📝 Documentation + +- **New Unraid Installation Guide** with community-contributed updates (@ThunderLord956, @evertyang). +- Updated **OIDC** and **Immich integration** docs for clarity (@UndyingSoul, @motox986). +- General spell-check and documentation polish (@ThunderLord956, @mcguirepr89). + +--- + +### 🐛 Bug Fixes and Improvements + +- Fixed CSRF issues with admin tools. +- Backend ready for **dual-stack** environments. +- Improved itinerary element display and GPX file handling. +- Optimized session cookie handling for domain/IP setups. +- Various **small Python fixes** (@larsl-net). +- Fixed container relations (@bucherfa). +- Django updated to **5.0.11** for security and performance improvements. +- General **codebase clean-up** and UI polish. + +--- + +## 🌟 New Contributors + +A huge shoutout to our amazing new contributors! 🎉 + +- @larsl-net +- @bucherfa +- @UndyingSoul +- @ThunderLord956 +- @evertyang +- @Thiesjoo +- @motox986 +- @mcguirepr89 +- @ThomasDetemmerman +- @Cathnan +- @jyyyeung +- @marcschumacher + +Thank you for helping AdventureLog grow! 🙌 + +--- + +## Support My Work 💖 + +[![Buy Me A Coffee](https://www.buymeacoffee.com/assets/img/custom_images/orange_img.png)](https://www.buymeacoffee.com/seanmorley15) +If AdventureLog has made your travels more organized or your trip memories richer, consider supporting my work on **Buy Me A Coffee**. Your support directly helps shape the future of this project! ☕ + +--- + +Enjoy this update and keep sharing your journeys with us! đŸŒâœˆī¸ +As always, drop your feedback and ideas in the [official Discord](https://discord.gg/wRbQ9Egr8) or in the discussions! + +Happy travels, +**Sean Morley** (@seanmorley15) + +--- + +**[Full Changelog](https://github.com/seanmorley15/AdventureLog/compare/v0.8.0...v0.9.0)** diff --git a/documentation/docs/configuration/email.md b/documentation/docs/configuration/email.md index e83ac6c9..52a3a200 100644 --- a/documentation/docs/configuration/email.md +++ b/documentation/docs/configuration/email.md @@ -6,7 +6,7 @@ To change the email backend, you can set the following variable in your docker-c ```yaml environment: - - EMAIL_BACKEND='console' + - EMAIL_BACKEND=console ``` ## With SMTP diff --git a/documentation/docs/configuration/google_maps_integration.md b/documentation/docs/configuration/google_maps_integration.md new file mode 100644 index 00000000..2618d12a --- /dev/null +++ b/documentation/docs/configuration/google_maps_integration.md @@ -0,0 +1,36 @@ +# Google Maps Integration + +To enable Google Maps integration in AdventureLog, you'll need to create a Google Maps API key. This key allows AdventureLog to use Google Maps services such as geocoding and location search throughout the application. + +Follow the steps below to generate your own API key: + +## Google Cloud Console Setup + +1. Go to the [Google Cloud Console](https://console.cloud.google.com/). +2. Create an account if you don't have one in order to access the console. +3. Click on the project dropdown in the top bar. +4. Click **New Project**. +5. Name your project (e.g., `AdventureLog Maps`) and click **Create**. +6. Once the project is created, ensure it is selected in the project dropdown. +7. Click on the **Navigation menu** (three horizontal lines in the top left corner). +8. Navigate to **Google Maps Platform**. +9. Once in the Maps Platform, click on **Keys & Credentials** in the left sidebar. +10. Click on **Create credentials** and select **API key**. +11. A dialog will appear with your new API key. Copy this key for later use. + + + +## Configuration in AdventureLog + +Set the API key in your environment file or configuration under the backend service of AdventureLog. This is typically done in the `docker-compose.yml` file or directly in your environment variables `.env` file. + +```env +GOOGLE_MAPS_API_KEY=your_api_key_here +``` + +Once this is set, AdventureLog will be able to utilize Google Maps services for geocoding and location searches instead of relying on the default OpenStreetMap services. diff --git a/documentation/docs/configuration/social_auth.md b/documentation/docs/configuration/social_auth.md index 68214b98..9d4a2313 100644 --- a/documentation/docs/configuration/social_auth.md +++ b/documentation/docs/configuration/social_auth.md @@ -9,6 +9,7 @@ The steps for each service varies so please refer to the specific service's docu - [Authentik](social_auth/authentik.md) (self-hosted) - [GitHub](social_auth/github.md) - [Open ID Connect](social_auth/oidc.md) +- [Authelia](https://www.authelia.com/integration/openid-connect/adventure-log/) ## Linking Existing Accounts diff --git a/documentation/docs/install/caddy.md b/documentation/docs/install/caddy.md new file mode 100644 index 00000000..d9d088a7 --- /dev/null +++ b/documentation/docs/install/caddy.md @@ -0,0 +1,67 @@ +# Installation with Caddy + +Caddy is a modern HTTP reverse proxy. It automatically integrates with Let's Encrypt (or other certificate providers) to generate TLS certificates for your site. + +As an example, if you want to add Caddy to your Docker compose configuration, add the following service to your `docker-compose.yml`: + +```yaml +services: + caddy: + image: docker.io/library/caddy:2 + container_name: adventurelog-caddy + restart: unless-stopped + cap_add: + - NET_ADMIN + ports: + - "80:80" + - "443:443" + - "443:443/udp" + volumes: + - ./caddy:/etc/caddy + - caddy_data:/data + - caddy_config:/config + + web: ... + server: ... + db: ... + +volumes: + caddy_data: + caddy_config: +``` + +Since all ingress traffic to the AdventureLog containsers now travels through Caddy, we can also remove the external ports configuration from those containsers in the `docker-compose.yml`. Just delete this configuration: + +```yaml + web: + ports: + - "8016:80" +â€Ļ + server: + ports: + - "8015:3000" +``` + +That's it for the Docker compose changes. Of course, there are other methods to run Caddy which are equally valid. + +However, we also need to configure Caddy. For this, create a file `./caddy/Caddyfile` in which you configure the requests which are proxied to the frontend and backend respectively and what domain Caddy should request a certificate for: + +``` +adventurelog.example.com { + + @frontend { + not path /media* /admin* /static* /accounts* + } + reverse_proxy @frontend web:3000 + + reverse_proxy server:80 +} +``` + +Once configured, you can start up the containsers: + +```bash +docker compose up +``` + +Your AdventureLog should now be up and running. diff --git a/documentation/docs/install/docker.md b/documentation/docs/install/docker.md index 0f987d24..431e4bfe 100644 --- a/documentation/docs/install/docker.md +++ b/documentation/docs/install/docker.md @@ -1,7 +1,8 @@ # Docker 🐋 Docker is the preferred way to run AdventureLog on your local machine. It is a lightweight containerization technology that allows you to run applications in isolated environments called containers. -**Note**: This guide mainly focuses on installation with a linux based host machine, but the steps are similar for other operating systems. + +> **Note**: This guide mainly focuses on installation with a Linux-based host machine, but the steps are similar for other operating systems. ## Prerequisites @@ -9,7 +10,16 @@ Docker is the preferred way to run AdventureLog on your local machine. It is a l ## Getting Started -Get the `docker-compose.yml` file from the AdventureLog repository. You can download it from [here](https://github.com/seanmorley15/AdventureLog/blob/main/docker-compose.yml) or run this command to download it directly to your machine: +Get the `docker-compose.yml` and `.env.example` files from the AdventureLog repository. You can download them here: + +- [Docker Compose](https://github.com/seanmorley15/AdventureLog/blob/main/docker-compose.yml) +- [Environment Variables](https://github.com/seanmorley15/AdventureLog/blob/main/.env.example) + +```bash +wget https://raw.githubusercontent.com/seanmorley15/AdventureLog/main/docker-compose.yml +wget https://raw.githubusercontent.com/seanmorley15/AdventureLog/main/.env.example +cp .env.example .env +``` ::: tip @@ -17,46 +27,56 @@ If running on an ARM based machine, you will need to use a different PostGIS Ima ::: -```bash -wget https://raw.githubusercontent.com/seanmorley15/AdventureLog/main/docker-compose.yml -``` - ## Configuration -Here is a summary of the configuration options available in the `docker-compose.yml` file: +The `.env` file contains all the configuration settings for your AdventureLog instance. Here’s a breakdown of each section: + +### 🌐 Frontend (web) + +| Name | Required | Description | Default Value | +| ------------------- | --------- | ------------------------------------------------------------------------------------------------------------- | ----------------------- | +| `PUBLIC_SERVER_URL` | Yes | Used by the frontend SSR server to connect to the backend. Should match the internal container name and port. | `http://server:8000` | +| `ORIGIN` | Sometimes | Needed only if not using HTTPS. Set it to the domain or IP you'll use to access the frontend. | `http://localhost:8015` | +| `BODY_SIZE_LIMIT` | Yes | Maximum upload size in bytes. | `Infinity` | +| `FRONTEND_PORT` | Yes | Port that the frontend will run on inside Docker. | `8015` | + +### 🐘 PostgreSQL Database - +| Name | Required | Description | Default Value | +| ------------------- | -------- | --------------------- | ------------- | +| `PGHOST` | Yes | Internal DB hostname. | `db` | +| `POSTGRES_DB` | Yes | DB name. | `database` | +| `POSTGRES_USER` | Yes | DB user. | `adventure` | +| `POSTGRES_PASSWORD` | Yes | DB password. | `changeme123` | -### Frontend Container (web) +### 🔒 Backend (server) -| Name | Required | Description | Default Value | -| ------------------- | --------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------- | -| `PUBLIC_SERVER_URL` | Yes | What the frontend SSR server uses to connect to the backend. | ```http://server:8000``` | -| `ORIGIN` | Sometimes | Not needed if using HTTPS. If not, set it to the domain of what you will access the app from. | ```http://localhost:8015``` | -| `BODY_SIZE_LIMIT` | Yes | Used to set the maximum upload size to the server. Should be changed to prevent someone from uploading too much! Custom values must be set in **kilobytes**. | ```Infinity``` | +| Name | Required | Description | Default Value | +| ----------------------- | -------- | ---------------------------------------------------------------------------------- | --------------------------------------------- | +| `SECRET_KEY` | Yes | Django secret key. Change this in production! | `changeme123` | +| `DJANGO_ADMIN_USERNAME` | Yes | Default Django admin username. | `admin` | +| `DJANGO_ADMIN_PASSWORD` | Yes | Default Django admin password. | `admin` | +| `DJANGO_ADMIN_EMAIL` | Yes | Default admin email. | `admin@example.com` | +| `PUBLIC_URL` | Yes | Publicly accessible URL of the **backend**. Used for generating image URLs. | `http://localhost:8016` | +| `CSRF_TRUSTED_ORIGINS` | Yes | Comma-separated list of frontend/backend URLs that are allowed to submit requests. | `http://localhost:8016,http://localhost:8015` | +| `FRONTEND_URL` | Yes | URL to the **frontend**, used for email generation. | `http://localhost:8015` | +| `BACKEND_PORT` | Yes | Port that the backend will run on inside Docker. | `8016` | +| `DEBUG` | No | Should be `False` in production. | `False` | -### Backend Container (server) +## Optional Configuration -| Name | Required | Description | Default Value | -| ----------------------- | -------- | --------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------- | -| `PGHOST` | Yes | Database host. | ```db``` | -| `PGDATABASE` | Yes | Database. | ```database``` | -| `PGUSER` | Yes | Database user. | ```adventure``` | -| `PGPASSWORD` | Yes | Database password. | ```changeme123``` | -| `PGPORT` | No | Database port. | ```5432``` | -| `DJANGO_ADMIN_USERNAME` | Yes | Default username. | ```admin``` | -| `DJANGO_ADMIN_PASSWORD` | Yes | Default password, change after initial login. | ```admin``` | -| `DJANGO_ADMIN_EMAIL` | Yes | Default user's email. | ```admin@example.com``` | -| `PUBLIC_URL` | Yes | This needs to match the outward port of the server and be accessible from where the app is used. It is used for the creation of image urls. | ```http://localhost:8016``` | -| `CSRF_TRUSTED_ORIGINS` | Yes | Need to be changed to the origins where you use your backend server and frontend. These values are comma separated. | ```http://localhost:8016``` | -| `FRONTEND_URL` | Yes | This is the publicly accessible url to the **frontend** container. This link should be accessible for all users. Used for email generation. | ```http://localhost:8015``` | +- [Disable Registration](../configuration/disable_registration.md) +- [Google Maps](../configuration/google_maps_integration.md) +- [Email Configuration](../configuration/email.md) +- [Immich Integration](../configuration/immich_integration.md) +- [Umami Analytics](../configuration/analytics.md) ## Running the Containers -To start the containers, run the following command: +Once you've configured `.env`, you can start AdventureLog with: ```bash docker compose up -d ``` -Enjoy AdventureLog! 🎉 +Enjoy using AdventureLog! 🎉 diff --git a/documentation/docs/install/getting_started.md b/documentation/docs/install/getting_started.md index aa5187c6..87c5bee7 100644 --- a/documentation/docs/install/getting_started.md +++ b/documentation/docs/install/getting_started.md @@ -1,14 +1,26 @@ -# Install Options for AdventureLog +# 🚀 Install Options for AdventureLog -AdventureLog can be installed in a variety of ways. The following are the most common methods: +AdventureLog can be installed in a variety of ways, depending on your platform or preference. -- [Docker](docker.md) đŸŗ -- [Proxmox LXC](proxmox_lxc.md) 🐧 -- [Synology NAS](synology_nas.md) â˜ī¸ -- [Kubernetes and Kustomize](kustomize.md) 🌐 -- [Unraid](unraid.md) 🧡 +## đŸ“Ļ Docker Quick Start -### Other Options +::: tip Quick Start Script +**The fastest way to get started:** +[Install AdventureLog with a single command →](quick_start.md) +Perfect for and Docker beginners. +::: -- [Nginx Proxy Manager](nginx_proxy_manager.md) 🛡 -- [Traefik](traefik.md) 🚀 +## đŸŗ Popular Installation Methods + +- [Docker](docker.md) — Simple containerized setup +- [Proxmox LXC](proxmox_lxc.md) — Lightweight virtual environment +- [Synology NAS](synology_nas.md) — Self-host on your home NAS +- [Kubernetes + Kustomize](kustomize.md) — Advanced, scalable deployment +- [Unraid](unraid.md) — Easy integration for homelabbers +- [Umbrel](https://apps.umbrel.com/app/adventurelog) — Home server app store + +## âš™ī¸ Advanced & Alternative Setups + +- [Nginx Proxy Manager](nginx_proxy_manager.md) — Easy reverse proxy config +- [Traefik](traefik.md) — Dynamic reverse proxy with automation +- [Caddy](caddy.md) — Automatic HTTPS with a clean config diff --git a/documentation/docs/install/quick_start.md b/documentation/docs/install/quick_start.md new file mode 100644 index 00000000..64dbdceb --- /dev/null +++ b/documentation/docs/install/quick_start.md @@ -0,0 +1,45 @@ +# 🚀 Quick Start Install + +Install **AdventureLog** in seconds using our automated script. + +## đŸ§Ē One-Liner Install + +```bash +curl -sSL https://get.adventurelog.app | bash +``` + +This will: + +- Check dependencies (Docker, Docker Compose) +- Set up project directory +- Download required files +- Prompt for basic configuration (like domain name) +- Start AdventureLog with Docker Compose + +## ✅ Requirements + +- Docker + Docker Compose +- Linux server or VPS +- Optional: Domain name for HTTPS + +## 🔍 What It Does + +The script automatically: + +1. Verifies Docker is installed and running +2. Downloads `docker-compose.yml` and `.env` +3. Prompts you for domain and port settings +4. Waits for services to start +5. Prints success info with next steps + +## đŸ§ŧ Uninstall + +To remove everything: + +```bash +cd adventurelog +docker compose down -v +rm -rf adventurelog +``` + +Need more control? Explore other [install options](getting_started.md) like Docker, Proxmox, Synology NAS, and more. diff --git a/documentation/docs/intro/adventurelog_overview.md b/documentation/docs/intro/adventurelog_overview.md index 310237ff..cfb30f4c 100644 --- a/documentation/docs/intro/adventurelog_overview.md +++ b/documentation/docs/intro/adventurelog_overview.md @@ -27,4 +27,6 @@ AdventureLog is open-source software, licensed under the GPL-3.0 license. This m ## About the Maintainer -AdventureLog is created and maintained by [Sean Morley](https://seanmorley.com), a Computer Science student at the University of Connecticut. Sean is passionate about open-source software and building modern tools that help people solve real-world problems. +Hi, I'm [Sean Morley](https://seanmorley.com), the creator of AdventureLog. I'm a Computer Science student at the University of Connecticut, and I'm passionate about open-source software and building modern tools that help people solve real-world problems. I created AdventureLog to solve a problem: the lack of a modern, open-source, user-friendly travel companion. Many existing travel apps are either too complex, too expensive, or too closed-off to be useful for the average traveler. AdventureLog aims to be the opposite: simple, beautiful, and open to everyone. + +I hope you enjoy using AdventureLog as much as I enjoy creating it! If you have any questions, feedback, or suggestions, feel free to reach out to me via the email address listed on my website. I'm always happy to hear from users and help in any way I can. Thank you for using AdventureLog, and happy travels! 🌍 diff --git a/documentation/docs/troubleshooting/login_unresponsive.md b/documentation/docs/troubleshooting/login_unresponsive.md new file mode 100644 index 00000000..9abf6a02 --- /dev/null +++ b/documentation/docs/troubleshooting/login_unresponsive.md @@ -0,0 +1,18 @@ +# Troubleshooting: Login and Registration Unresponsive + +When you encounter issues with the login and registration pages being unresponsive in AdventureLog, it can be due to various reasons. This guide will help you troubleshoot and resolve the unresponsive login and registration pages in AdventureLog. + +1. Check to make sure the backend container is running and accessible. + + - Check the backend container logs to see if there are any errors or issues blocking the container from running. +2. Check the connection between the frontend and backend containers. + + - Attempt login with the browser console network tab open to see if there are any errors or issues with the connection between the frontend and backend containers. If there is a connection issue, the code will show an error like `Failed to load resource: net::ERR_CONNECTION_REFUSED`. If this is the case, check the `PUBLIC_SERVER_URL` in the frontend container and refer to the installation docs to ensure the correct URL is set. + - If the error is `403`, continue to the next step. + +3. The error most likely is due to a CSRF security config issue in either the backend or frontend. + + - Check that the `ORIGIN` variable in the frontend is set to the URL where the frontend is access and you are accessing the app from currently. + - Check that the `CSRF_TRUSTED_ORIGINS` variable in the backend is set to a comma separated list of the origins where you use your backend server and frontend. One of these values should match the `ORIGIN` variable in the frontend. + +4. If you are still experiencing issues, please refer to the [AdventureLog Discord Server](https://discord.gg/wRbQ9Egr8C) for further assistance, providing as much detail as possible about the issue you are experiencing! diff --git a/documentation/docs/usage/usage.md b/documentation/docs/usage/usage.md new file mode 100644 index 00000000..aa27cf32 --- /dev/null +++ b/documentation/docs/usage/usage.md @@ -0,0 +1,33 @@ +# How to use AdventureLog + +Welcome to AdventureLog! This guide will help you get started with AdventureLog and provide you with an overview of the features available to you. + +## Key Terms + +#### Adventures + +- **Adventure**: think of an adventure as a point on a map, a location you want to visit, or a place you want to explore. An adventure can be anything you want it to be, from a local park to a famous landmark. +- **Visit**: a visit is added to an adventure. It contains a date and notes about when the adventure was visited. If an adventure is visited multiple times, multiple visits can be added. If there are no visits on an adventure or the date of all visits is in the future, the adventure is considered planned. If the date of the visit is in the past, the adventure is considered completed. +- **Category**: a category is a way to group adventures together. For example, you could have a category for parks, a category for museums, and a category for restaurants. +- **Tag**: a tag is a way to add additional information to an adventure. For example, you could have a tag for the type of cuisine at a restaurant or the type of art at a museum. Multiple tags can be added to an adventure. +- **Image**: an image is a photo that is added to an adventure. Images can be added to an adventure to provide a visual representation of the location or to capture a memory of the visit. These can be uploaded from your device or with a service like [Immich](/docs/configuration/immich_integration) if the integration is enabled. +- **Attachment**: an attachment is a file that is added to an adventure. Attachments can be added to an adventure to provide additional information, such as a map of the location or a brochure from the visit. + +#### Collections + +- **Collection**: a collection is a way to group adventures together. Collections are flexible and can be used in many ways. When no start or end date is added to a collection, it acts like a folder to group adventures together. When a start and end date is added to a collection, it acts like a trip to group adventures together that were visited during that time period. With start and end dates, the collection is transformed into a full itinerary with a map showing the route taken between adventures. +- **Transportation**: a transportation is a collection exclusive feature that allows you to add transportation information to your trip. This can be used to show the route taken between locations and the mode of transportation used. It can also be used to track flight information, such as flight number and departure time. +- **Lodging**: a lodging is a collection exclusive feature that allows you to add lodging information to your trip. This can be used to plan where you will stay during your trip and add notes about the lodging location. It can also be used to track reservation information, such as reservation number and check-in time. +- **Note**: a note is a collection exclusive feature that allows you to add notes to your trip. This can be used to add additional information about your trip, such as a summary of the trip or a list of things to do. Notes can be assigned to a specific day of the trip to help organize the information. +- **Checklist**: a checklist is a collection exclusive feature that allows you to add a checklist to your trip. This can be used to create a list of things to do during your trip or for planning purposes like packing lists. Checklists can be assigned to a specific day of the trip to help organize the information. + +#### World Travel + +- **World Travel**: the world travel feature of AdventureLog allows you to track the countries, regions, and cities you have visited during your lifetime. You can add visits to countries, regions, and cities, and view statistics about your travels. The world travel feature is a fun way to visualize where you have been and where you want to go next. + - **Country**: a country is a geographical area that is recognized as an independent nation. You can add visits to countries to track where you have been. + - **Region**: a region is a geographical area that is part of a country. You can add visits to regions to track where you have been within a country. + - **City**: a city is a geographical area that is a populated urban center. You can add visits to cities to track where you have been within a region. + +## Tutorial Video + + diff --git a/documentation/index.md b/documentation/index.md index 87f26409..8103bc93 100644 --- a/documentation/index.md +++ b/documentation/index.md @@ -31,3 +31,255 @@ features: details: "Share your adventures with friends and family and collaborate on trips together." icon: 📸 --- + +## âšĄī¸ Quick Start + +Get AdventureLog running in under 60 seconds: + +```bash [One-Line Install] +curl -sSL https://get.adventurelog.app | bash +``` + +You can also explore our [full installation guide](/docs/install/getting_started) for plenty of options, including Docker, Proxmox, Synology NAS, and more. + +## 📸 See It In Action + +::: details đŸ—‚ī¸ **Adventure Overview & Management** +Manage your full list of adventures with ease. View upcoming and past trips, filter and sort by status, date, or category to find exactly what you want quickly. +Adventure Overview +::: + +::: details 📋 **Detailed Adventure Logs** +Capture rich details for every adventure: name, dates, precise locations, vivid descriptions, personal ratings, photos, and customizable categories. Your memories deserve to be more than just map pins — keep them alive with full, organized logs. +Detailed Adventure Logs +::: + +::: details đŸ—ēī¸ **Interactive World Map** +Track every destination you’ve visited or plan to visit with our beautifully detailed, interactive world map. Easily filter locations by visit status — visitedor planned — and add new adventures by simply clicking on the map. Watch your travel story unfold visually as your journey grows. +Interactive World Map +::: + +::: details âœˆī¸ **Comprehensive Trip Planning** +Organize your multi-day trips with detailed itineraries, including flight information, daily activities, collaborative notes, packing checklists, and handy resource links. Stay on top of your plans and ensure every adventure runs smoothly. +Comprehensive Trip Planning +::: + +::: details 📊 **Travel Statistics Dashboard** +Unlock insights into your travel habits and milestones through elegant, easy-to-understand analytics. Track total countries visited, regions explored, cities logged, and more. Visualize your world travels with ease and celebrate your achievements. +Travel Statistics Dashboard +::: + +::: details âœī¸ **Edit & Customize Adventures** +Make quick updates or deep customizations to any adventure using a clean and intuitive editing interface. Add photos, update notes, adjust dates, and more—keeping your records accurate and personal. +Edit Adventure Modal +::: + +::: details 🌍 **Countries & Regions Explorer** +Explore and manage the countries you’ve visited or plan to visit with an organized list, filtering by visit status. Dive deeper into each country’s regions, complete with interactive maps to help you visually select and track your regional travels. +Countries List +Regions Explorer +::: + +## đŸ’Ŧ What People Are Saying + +::: details âœˆī¸ **XDA Travel Week Reviews** + +> “I stumbled upon AdventureLog. It's an open-source, self-hosted travel planner that's completely free to use and has a bunch of cool features that make it a treat to plan, organize, and log your journey across the world. Safe to say, it's become a mainstay in Docker for me.” +> +> — _Sumukh Rao, Senior Author at XDA_ + +[Article Link](https://www.xda-developers.com/i-self-hosted-this-app-to-plan-itinerary-when-traveling/) + +::: + +::: details đŸ§ŗ **Rich Edmonds, XDA** + +**Overall Ranking: #1** + +> “The most important part of travelling in this socially connected world is to log everything and showcase all of your adventures. AdventureLog is aptly named, as it allows you to do just that. It just so happens to be one of the best apps for the job and can be fully self-hosted at home.” +> +> — _Rich Edmonds, Lead PC Hardware Editor at XDA_ + +[Article Link](https://www.xda-developers.com/these-self-hosted-apps-are-perfect-for-those-on-the-go/) + +::: + +## đŸ—ī¸ Built With Excellence + +
+ +
+ +### **Frontend Excellence** + +- 🎨 **SvelteKit** - Lightning-fast, modern web framework +- 💨 **TailwindCSS** - Utility-first styling for beautiful designs +- 🎭 **DaisyUI** - Beautiful, accessible component library +- đŸ—ēī¸ **MapLibre** - Interactive, customizable mapping + +
+ +
+ +### **Backend Power** + +- 🐍 **Django** - Robust, scalable web framework +- đŸ—ēī¸ **PostGIS** - Advanced geospatial database capabilities +- 🔌 **Django REST** - Modern API architecture +- 🔐 **AllAuth** - Comprehensive authentication system + +
+ +
+ +## 🌟 Join the Adventure + +
+ +
+ +### đŸŽ¯ **Active Development** + +Regular updates, new features, and community-driven improvements keep AdventureLog at the forefront of travel technology. + +
+ +
+ +### đŸ’Ŧ **Thriving Community** + +Join thousands of travelers sharing tips, contributing code, and building the future of travel documentation together. + +
+ +
+ +### 🚀 **Open Source Freedom** + +GPL 3.0 licensed, fully transparent, and built for the community. By travelers, for travelers. + +
+ +
+ +## 💖 Support the Project + +AdventureLog is lovingly maintained by passionate developers and supported by amazing users like you: + +- ⭐ [Star us on GitHub](https://github.com/seanmorley15/AdventureLog) +- đŸ’Ŧ [Join our Discord community](https://discord.gg/wRbQ9Egr8C) +- 💖 [Sponsor The Project](https://seanmorley.com/sponsor) to help us keep improving AdventureLog +- 🐛 [Report bugs & request features](https://github.com/seanmorley15/AdventureLog/issues) + +--- + + + + diff --git a/documentation/package.json b/documentation/package.json index c17d74f2..e666f604 100644 --- a/documentation/package.json +++ b/documentation/package.json @@ -1,6 +1,6 @@ { "devDependencies": { - "vitepress": "^1.5.0" + "vitepress": "^1.6.3" }, "scripts": { "docs:dev": "vitepress dev", diff --git a/documentation/pnpm-lock.yaml b/documentation/pnpm-lock.yaml index c5fa66d7..0d53d628 100644 --- a/documentation/pnpm-lock.yaml +++ b/documentation/pnpm-lock.yaml @@ -16,8 +16,8 @@ importers: version: 3.5.13 devDependencies: vitepress: - specifier: ^1.5.0 - version: 1.5.0(@algolia/client-search@5.15.0)(postcss@8.4.49)(search-insights@2.17.3) + specifier: ^1.6.3 + version: 1.6.3(@algolia/client-search@5.15.0)(postcss@8.4.49)(search-insights@2.17.3) packages: @@ -110,14 +110,14 @@ packages: resolution: {integrity: sha512-Z/yiTPj+lDVnF7lWeKCIJzaIkI0vYO87dMpZ4bg4TDrFe4XXLFWL1TbXU27gBP3QccxV9mZICCrnjnYlJjXHOA==} engines: {node: '>=6.9.0'} - '@docsearch/css@3.8.0': - resolution: {integrity: sha512-pieeipSOW4sQ0+bE5UFC51AOZp9NGxg89wAlZ1BAQFaiRAGK1IKUaPQ0UGZeNctJXyqZ1UvBtOQh2HH+U5GtmA==} + '@docsearch/css@3.8.2': + resolution: {integrity: sha512-y05ayQFyUmCXze79+56v/4HpycYF3uFqB78pLPrSV5ZKAlDuIAAJNhaRi8tTdRNXh05yxX/TyNnzD6LwSM89vQ==} - '@docsearch/js@3.8.0': - resolution: {integrity: sha512-PVuV629f5UcYRtBWqK7ID6vNL5647+2ADJypwTjfeBIrJfwPuHtzLy39hMGMfFK+0xgRyhTR0FZ83EkdEraBlg==} + '@docsearch/js@3.8.2': + resolution: {integrity: sha512-Q5wY66qHn0SwA7Taa0aDbHiJvaFJLOJyHmooQ7y8hlwwQLQ/5WwCcoX0g7ii04Qi2DJlHsd0XXzJ8Ypw9+9YmQ==} - '@docsearch/react@3.8.0': - resolution: {integrity: sha512-WnFK720+iwTVt94CxY3u+FgX6exb3BfN5kE9xUY6uuAH/9W/UFboBZFLlrw/zxFRHoHZCOXRtOylsXF+6LHI+Q==} + '@docsearch/react@3.8.2': + resolution: {integrity: sha512-xCRrJQlTt8N9GU0DG4ptwHRkfnSnD/YpdeaXe02iKfqs97TkZJv60yE+1eq/tjPcVnTW8dP5qLP7itifFVV5eg==} peerDependencies: '@types/react': '>= 16.8.0 < 19.0.0' react: '>= 16.8.0 < 19.0.0' @@ -271,8 +271,8 @@ packages: cpu: [x64] os: [win32] - '@iconify-json/simple-icons@1.2.12': - resolution: {integrity: sha512-lRNORrIdeLStShxAjN6FgXE1iMkaAgiAHZdP0P0GZecX91FVYW58uZnRSlXLlSx5cxMoELulkAAixybPA2g52g==} + '@iconify-json/simple-icons@1.2.37': + resolution: {integrity: sha512-jZwTBznpYVDYKWyAuRpepPpCiHScVrX6f8WRX8ReX6pdii99LYVHwJywKcH2excWQrWmBomC9nkxGlEKzXZ/wQ==} '@iconify/types@2.0.0': resolution: {integrity: sha512-+wluvCrRhXrhyOmRDJ3q8mux9JkKy5SJ/v8ol2tu4FVjyYvtEzkc/3pK15ET6RKg4b4w4BmTk1+gsCUhf21Ykg==} @@ -370,23 +370,29 @@ packages: cpu: [x64] os: [win32] - '@shikijs/core@1.23.1': - resolution: {integrity: sha512-NuOVgwcHgVC6jBVH5V7iblziw6iQbWWHrj5IlZI3Fqu2yx9awH7OIQkXIcsHsUmY19ckwSgUMgrqExEyP5A0TA==} + '@shikijs/core@2.5.0': + resolution: {integrity: sha512-uu/8RExTKtavlpH7XqnVYBrfBkUc20ngXiX9NSrBhOVZYv/7XQRKUyhtkeflY5QsxC0GbJThCerruZfsUaSldg==} - '@shikijs/engine-javascript@1.23.1': - resolution: {integrity: sha512-i/LdEwT5k3FVu07SiApRFwRcSJs5QM9+tod5vYCPig1Ywi8GR30zcujbxGQFJHwYD7A5BUqagi8o5KS+LEVgBg==} + '@shikijs/engine-javascript@2.5.0': + resolution: {integrity: sha512-VjnOpnQf8WuCEZtNUdjjwGUbtAVKuZkVQ/5cHy/tojVVRIRtlWMYVjyWhxOmIq05AlSOv72z7hRNRGVBgQOl0w==} - '@shikijs/engine-oniguruma@1.23.1': - resolution: {integrity: sha512-KQ+lgeJJ5m2ISbUZudLR1qHeH3MnSs2mjFg7bnencgs5jDVPeJ2NVDJ3N5ZHbcTsOIh0qIueyAJnwg7lg7kwXQ==} + '@shikijs/engine-oniguruma@2.5.0': + resolution: {integrity: sha512-pGd1wRATzbo/uatrCIILlAdFVKdxImWJGQ5rFiB5VZi2ve5xj3Ax9jny8QvkaV93btQEwR/rSz5ERFpC5mKNIw==} - '@shikijs/transformers@1.23.1': - resolution: {integrity: sha512-yQ2Cn0M9i46p30KwbyIzLvKDk+dQNU+lj88RGO0XEj54Hn4Cof1bZoDb9xBRWxFE4R8nmK63w7oHnJwvOtt0NQ==} + '@shikijs/langs@2.5.0': + resolution: {integrity: sha512-Qfrrt5OsNH5R+5tJ/3uYBBZv3SuGmnRPejV9IlIbFH3HTGLDlkqgHymAlzklVmKBjAaVmkPkyikAV/sQ1wSL+w==} - '@shikijs/types@1.23.1': - resolution: {integrity: sha512-98A5hGyEhzzAgQh2dAeHKrWW4HfCMeoFER2z16p5eJ+vmPeF6lZ/elEne6/UCU551F/WqkopqRsr1l2Yu6+A0g==} + '@shikijs/themes@2.5.0': + resolution: {integrity: sha512-wGrk+R8tJnO0VMzmUExHR+QdSaPUl/NKs+a4cQQRWyoc3YFbUzuLEi/KWK1hj+8BfHRKm2jNhhJck1dfstJpiw==} - '@shikijs/vscode-textmate@9.3.0': - resolution: {integrity: sha512-jn7/7ky30idSkd/O5yDBfAnVt+JJpepofP/POZ1iMOxK59cOfqIgg/Dj0eFsjOTMw+4ycJN0uhZH/Eb0bs/EUA==} + '@shikijs/transformers@2.5.0': + resolution: {integrity: sha512-SI494W5X60CaUwgi8u4q4m4s3YAFSxln3tzNjOSYqq54wlVgz0/NbbXEb3mdLbqMBztcmS7bVTaEd2w0qMmfeg==} + + '@shikijs/types@2.5.0': + resolution: {integrity: sha512-ygl5yhxki9ZLNuNpPitBWvcy9fsSKKaRuO4BAlMyagszQidxcpLAr0qiW/q43DtSIDxO6hEbtYLiFZNXO/hdGw==} + + '@shikijs/vscode-textmate@10.0.2': + resolution: {integrity: sha512-83yeghZ2xxin3Nj8z1NMd/NCuca+gsYXswywDy5bHvwlWL8tpTQmzGeUuHd9FC3E/SBEMvzJRwWEOz5gGes9Qg==} '@types/estree@1.0.6': resolution: {integrity: sha512-AYnb1nQyY49te+VRAVgmzfcgjYS91mY5P0TKUDCLEM+gNnA+3T6rWITXRLYCpahpqSQbN5cE+gHpnPyXjHWxcw==} @@ -409,17 +415,17 @@ packages: '@types/unist@3.0.3': resolution: {integrity: sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q==} - '@types/web-bluetooth@0.0.20': - resolution: {integrity: sha512-g9gZnnXVq7gM7v3tJCWV/qw7w+KeOlSHAhgF9RytFyifW6AF61hdT2ucrYhPq9hLs5JIryeupHV3qGk95dH9ow==} + '@types/web-bluetooth@0.0.21': + resolution: {integrity: sha512-oIQLCGWtcFZy2JW77j9k8nHzAOpqMHLQejDA48XXMWH6tjCQHz5RCFz1bzsmROyL6PUm+LLnUiI4BCn221inxA==} '@ungap/structured-clone@1.2.0': resolution: {integrity: sha512-zuVdFrMJiuCDQUMCzQaD6KL28MjnqqN8XnAqiEq9PNm/hCPTSGfrXCOfwj1ow4LFb/tNymJPwsNbVePc1xFqrQ==} - '@vitejs/plugin-vue@5.2.0': - resolution: {integrity: sha512-7n7KdUEtx/7Yl7I/WVAMZ1bEb0eVvXF3ummWTeLcs/9gvo9pJhuLdouSXGjdZ/MKD1acf1I272+X0RMua4/R3g==} + '@vitejs/plugin-vue@5.2.4': + resolution: {integrity: sha512-7Yx/SXSOcQq5HiiV3orevHUFn+pmMB4cgbEkDYgnkUWb0WfeQ/wa2yFv6D5ICiCQOVpjA7vYDXrC7AGO8yjDHA==} engines: {node: ^18.0.0 || >=20.0.0} peerDependencies: - vite: ^5.0.0 + vite: ^5.0.0 || ^6.0.0 vue: ^3.2.25 '@vue/compiler-core@3.5.13': @@ -434,14 +440,14 @@ packages: '@vue/compiler-ssr@3.5.13': resolution: {integrity: sha512-wMH6vrYHxQl/IybKJagqbquvxpWCuVYpoUJfCqFZwa/JY1GdATAQ+TgVtgrwwMZ0D07QhA99rs/EAAWfvG6KpA==} - '@vue/devtools-api@7.6.4': - resolution: {integrity: sha512-5AaJ5ELBIuevmFMZYYLuOO9HUuY/6OlkOELHE7oeDhy4XD/hSODIzktlsvBOsn+bto3aD0psj36LGzwVu5Ip8w==} + '@vue/devtools-api@7.7.6': + resolution: {integrity: sha512-b2Xx0KvXZObePpXPYHvBRRJLDQn5nhKjXh7vUhMEtWxz1AYNFOVIsh5+HLP8xDGL7sy+Q7hXeUxPHB/KgbtsPw==} - '@vue/devtools-kit@7.6.4': - resolution: {integrity: sha512-Zs86qIXXM9icU0PiGY09PQCle4TI750IPLmAJzW5Kf9n9t5HzSYf6Rz6fyzSwmfMPiR51SUKJh9sXVZu78h2QA==} + '@vue/devtools-kit@7.7.6': + resolution: {integrity: sha512-geu7ds7tem2Y7Wz+WgbnbZ6T5eadOvozHZ23Atk/8tksHMFOFylKi1xgGlQlVn0wlkEf4hu+vd5ctj1G4kFtwA==} - '@vue/devtools-shared@7.6.4': - resolution: {integrity: sha512-nD6CUvBEel+y7zpyorjiUocy0nh77DThZJ0k1GRnJeOmY3ATq2fWijEp7wk37gb023Cb0R396uYh5qMSBQ5WFg==} + '@vue/devtools-shared@7.7.6': + resolution: {integrity: sha512-yFEgJZ/WblEsojQQceuyK6FzpFDx4kqrz2ohInxNj5/DnhoX023upTv4OD6lNPLAA5LLkbwPVb10o/7b+Y4FVA==} '@vue/reactivity@3.5.13': resolution: {integrity: sha512-NaCwtw8o48B9I6L1zl2p41OHo/2Z4wqYGGIK1Khu5T7yxrn+ATOixn/Udn2m+6kZKB/J7cuT9DbWWhRxqixACg==} @@ -460,11 +466,11 @@ packages: '@vue/shared@3.5.13': resolution: {integrity: sha512-/hnE/qP5ZoGpol0a5mDi45bOd7t3tjYJBjsgCsivow7D48cJeV5l05RD82lPqi7gRiphZM37rnhW1l6ZoCNNnQ==} - '@vueuse/core@11.3.0': - resolution: {integrity: sha512-7OC4Rl1f9G8IT6rUfi9JrKiXy4bfmHhZ5x2Ceojy0jnd3mHNEvV4JaRygH362ror6/NZ+Nl+n13LPzGiPN8cKA==} + '@vueuse/core@12.8.2': + resolution: {integrity: sha512-HbvCmZdzAu3VGi/pWYm5Ut+Kd9mn1ZHnn4L5G8kOQTPs/IwIAmJoBrmYk2ckLArgMXZj0AW3n5CAejLUO+PhdQ==} - '@vueuse/integrations@11.3.0': - resolution: {integrity: sha512-5fzRl0apQWrDezmobchoiGTkGw238VWESxZHazfhP3RM7pDSiyXy18QbfYkILoYNTd23HPAfQTJpkUc5QbkwTw==} + '@vueuse/integrations@12.8.2': + resolution: {integrity: sha512-fbGYivgK5uBTRt7p5F3zy6VrETlV9RtZjBqd1/HxGdjdckBgBM4ugP8LHpjolqTj14TXTxSK1ZfgPbHYyGuH7g==} peerDependencies: async-validator: ^4 axios: ^1 @@ -504,18 +510,18 @@ packages: universal-cookie: optional: true - '@vueuse/metadata@11.3.0': - resolution: {integrity: sha512-pwDnDspTqtTo2HwfLw4Rp6yywuuBdYnPYDq+mO38ZYKGebCUQC/nVj/PXSiK9HX5otxLz8Fn7ECPbjiRz2CC3g==} + '@vueuse/metadata@12.8.2': + resolution: {integrity: sha512-rAyLGEuoBJ/Il5AmFHiziCPdQzRt88VxR+Y/A/QhJ1EWtWqPBBAxTAFaSkviwEuOEZNtW8pvkPgoCZQ+HxqW1A==} - '@vueuse/shared@11.3.0': - resolution: {integrity: sha512-P8gSSWQeucH5821ek2mn/ciCk+MS/zoRKqdQIM3bHq6p7GXDAJLmnRRKmF5F65sAVJIfzQlwR3aDzwCn10s8hA==} + '@vueuse/shared@12.8.2': + resolution: {integrity: sha512-dznP38YzxZoNloI0qpEfpkms8knDtaoQ6Y/sfS0L7Yki4zh40LFHEhur0odJC6xTHG5dxWVPiUWBXn+wCG2s5w==} algoliasearch@5.15.0: resolution: {integrity: sha512-Yf3Swz1s63hjvBVZ/9f2P1Uu48GjmjCN+Esxb6MAONMGtZB1fRX8/S1AhUTtsuTlcGovbYLxpHgc7wEzstDZBw==} engines: {node: '>= 14.0.0'} - birpc@0.2.19: - resolution: {integrity: sha512-5WeXXAvTmitV1RqJFppT5QtUiz2p1mRSYU000Jkft5ZUCLJIk4uQriYNO50HknxKwM6jd8utNc66K1qGIwwWBQ==} + birpc@2.3.0: + resolution: {integrity: sha512-ijbtkn/F3Pvzb6jHypHRyve2QApOCZDR25D/VnkY2G/lBNcXCTsnsCxgY4k4PkVB7zfwzYbY3O9Lcqe3xufS5g==} ccount@2.0.1: resolution: {integrity: sha512-eyrF0jiFpY+3drT6383f1qhkbGsLSifNAjA61IUjZjmLCWjItY6LB9ft9YhoDgwfmclB2zhu51Lc7+95b8NRAg==} @@ -558,16 +564,16 @@ packages: estree-walker@2.0.2: resolution: {integrity: sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==} - focus-trap@7.6.2: - resolution: {integrity: sha512-9FhUxK1hVju2+AiQIDJ5Dd//9R2n2RAfJ0qfhF4IHGHgcoEUTMpbTeG/zbEuwaiYXfuAH6XE0/aCyxDdRM+W5w==} + focus-trap@7.6.5: + resolution: {integrity: sha512-7Ke1jyybbbPZyZXFxEftUtxFGLMpE2n6A+z//m4CRDlj0hW+o3iYSmh8nFlYMurOiJVDmJRilUQtJr08KfIxlg==} fsevents@2.3.3: resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==} engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} os: [darwin] - hast-util-to-html@9.0.3: - resolution: {integrity: sha512-M17uBDzMJ9RPCqLMO92gNNUDuBSq10a25SDBI08iCCxmorf4Yy6sYHK57n9WAbRAAaU+DuR4W6GN9K4DFZesYg==} + hast-util-to-html@9.0.5: + resolution: {integrity: sha512-OguPdidb+fbHQSU4Q4ZiLKnzWo8Wwsf5bZfbvu7//a9oTYoqD/fWpe96NuHkoS9h0ccGOTe0C4NGXdtS0iObOw==} hast-util-whitespace@3.0.0: resolution: {integrity: sha512-88JUN06ipLwsnv+dVn+OIYOvAuvBMy/Qoi6O7mQHxdPXpjy+Cd6xRkWwux7DKO+4sYILtLBRIKgsdpS2gQc7qw==} @@ -617,8 +623,8 @@ packages: engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} hasBin: true - oniguruma-to-es@0.4.1: - resolution: {integrity: sha512-rNcEohFz095QKGRovP/yqPIKc+nP+Sjs4YTHMv33nMePGKrq/r2eu9Yh4646M5XluGJsUnmwoXuiXE69KDs+fQ==} + oniguruma-to-es@3.1.1: + resolution: {integrity: sha512-bUH8SDvPkH3ho3dvwJwfonjlQ4R80vjyvrU8YpxuROddv55vAEJrTuCuCVUhhsHbtlD9tGGbaNApGQckXhS8iQ==} perfect-debounce@1.0.0: resolution: {integrity: sha512-xCy9V055GLEqoFaHoC1SoLIaLmWctgCUaBaWxDZ7/Zx4CTyX7cJQLJOok/orfjZAh9kEYpjJa4d0KcJmCbctZA==} @@ -638,17 +644,17 @@ packages: engines: {node: '>=14'} hasBin: true - property-information@6.5.0: - resolution: {integrity: sha512-PgTgs/BlvHxOu8QuEN7wi5A0OmXaBcHpmCSTehcs6Uuu9IkDIEo13Hy7n898RHfrQ49vKCoGeWZSaAK01nwVig==} + property-information@7.1.0: + resolution: {integrity: sha512-TwEZ+X+yCJmYfL7TPUOcvBZ4QfoT5YenQiJuX//0th53DE6w0xxLEtfK3iyryQFddXuvkIk51EEgrJQ0WJkOmQ==} - regex-recursion@4.2.1: - resolution: {integrity: sha512-QHNZyZAeKdndD1G3bKAbBEKOSSK4KOHQrAJ01N1LJeb0SoH4DJIeFhp0uUpETgONifS4+P3sOgoA1dhzgrQvhA==} + regex-recursion@6.0.2: + resolution: {integrity: sha512-0YCaSCq2VRIebiaUviZNs0cBz1kg5kVS2UKUfNIx8YVs1cN3AV7NTctO5FOKBA+UT2BPJIWZauYHPqJODG50cg==} regex-utilities@2.3.0: resolution: {integrity: sha512-8VhliFJAWRaUiVvREIiW2NXXTmHs4vMNnSzuJVhscgmGav3g9VDxLrQndI3dZZVVdp0ZO/5v0xmX516/7M9cng==} - regex@5.0.2: - resolution: {integrity: sha512-/pczGbKIQgfTMRV0XjABvc5RzLqQmwqxLHdQao2RTXPk+pmTXB2P0IaUHYdYyk412YLwUIkaeMd5T+RzVgTqnQ==} + regex@6.0.1: + resolution: {integrity: sha512-uorlqlzAKjKQZ5P+kTJr3eeJGSVroLKoHmquUj4zHWuR+hEyNqlXsSKlYYF5F4NI6nl7tWCs0apKJ0lmfsXAPA==} rfdc@1.4.1: resolution: {integrity: sha512-q1b3N5QkRUWUl7iyylaaj3kOpIT0N2i9MqIEQXP73GVsN9cw3fdx8X63cEmWhJGi2PPCF23Ijp7ktmd39rawIA==} @@ -661,8 +667,8 @@ packages: search-insights@2.17.3: resolution: {integrity: sha512-RQPdCYTa8A68uM2jwxoY842xDhvx3E5LFL1LxvxCNMev4o5mLuokczhzjAgGwUZBAmOKZknArSxLKmXtIi2AxQ==} - shiki@1.23.1: - resolution: {integrity: sha512-8kxV9TH4pXgdKGxNOkrSMydn1Xf6It8lsle0fiqxf7a1149K1WGtdOu3Zb91T5r1JpvRPxqxU3C2XdZZXQnrig==} + shiki@2.5.0: + resolution: {integrity: sha512-mI//trrsaiCIPsja5CNfsyNOqgAZUb6VpJA+340toL42UpzQlXpwRV9nch69X6gaUxrr9kaOOa6e3y3uAkGFxQ==} source-map-js@1.2.1: resolution: {integrity: sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==} @@ -678,8 +684,8 @@ packages: stringify-entities@4.0.4: resolution: {integrity: sha512-IwfBptatlO+QCJUo19AqvrPNqlVMpW9YEL2LIVY+Rpv2qsjCGxaDLNRgeGsQWJhfItebuJhsGSLjaBbNSQ+ieg==} - superjson@2.2.1: - resolution: {integrity: sha512-8iGv75BYOa0xRJHK5vRLEjE2H/i4lulTjzpUXic3Eg8akftYjkmQDa8JARQ42rlczXyFR3IeRoeFCc7RxHsYZA==} + superjson@2.2.2: + resolution: {integrity: sha512-5JRxVqC8I8NuOUjzBbvVJAKNM8qoVuH0O77h4WInc/qC2q5IreqKxYwgkga3PfA22OayK2ikceb/B26dztPl+Q==} engines: {node: '>=16'} tabbable@6.2.0: @@ -709,8 +715,8 @@ packages: vfile@6.0.3: resolution: {integrity: sha512-KzIbH/9tXat2u30jf+smMwFCsno4wHVdNmzFyL+T/L3UGqqk6JKfVqOFOZEpZSHADH1k40ab6NUIXZq422ov3Q==} - vite@5.4.14: - resolution: {integrity: sha512-EK5cY7Q1D8JNhSaPKVK4pwBFvaTmZxEnoKXLG/U9gmdDcihQGNzFlgIvaxezFR4glP1LsuiedwMBqCXH3wZccA==} + vite@5.4.19: + resolution: {integrity: sha512-qO3aKv3HoQC8QKiNSTuUM1l9o/XX3+c+VTgLHbJWHZGeTPVAg2XwazI9UWzoxjIJCGCV2zU60uqMzjeLZuULqA==} engines: {node: ^18.0.0 || >=20.0.0} hasBin: true peerDependencies: @@ -740,8 +746,8 @@ packages: terser: optional: true - vitepress@1.5.0: - resolution: {integrity: sha512-q4Q/G2zjvynvizdB3/bupdYkCJe2umSAMv9Ju4d92E6/NXJ59z70xB0q5p/4lpRyAwflDsbwy1mLV9Q5+nlB+g==} + vitepress@1.6.3: + resolution: {integrity: sha512-fCkfdOk8yRZT8GD9BFqusW3+GggWYZ/rYncOfmgcDtP3ualNHCAg+Robxp2/6xfH1WwPHtGpPwv7mbA3qomtBw==} hasBin: true peerDependencies: markdown-it-mathjax3: ^4 @@ -752,17 +758,6 @@ packages: postcss: optional: true - vue-demi@0.14.10: - resolution: {integrity: sha512-nMZBOwuzabUO0nLgIcc6rycZEebF6eeUfaiQx9+WSk8e29IbLvPU9feI6tqW4kTo3hvoYAJkMh8n8D0fuISphg==} - engines: {node: '>=12'} - hasBin: true - peerDependencies: - '@vue/composition-api': ^1.0.0-rc.1 - vue: ^3.0.0-0 || ^2.6.0 - peerDependenciesMeta: - '@vue/composition-api': - optional: true - vue@3.5.13: resolution: {integrity: sha512-wmeiSMxkZCSc+PM2w2VRsOYAZC8GdipNFRTsLSfodVqI9mbejKeXEGr8SckuLnrQPGe3oJN5c3K0vpoU9q/wCQ==} peerDependencies: @@ -894,11 +889,11 @@ snapshots: '@babel/helper-string-parser': 7.25.9 '@babel/helper-validator-identifier': 7.25.9 - '@docsearch/css@3.8.0': {} + '@docsearch/css@3.8.2': {} - '@docsearch/js@3.8.0(@algolia/client-search@5.15.0)(search-insights@2.17.3)': + '@docsearch/js@3.8.2(@algolia/client-search@5.15.0)(search-insights@2.17.3)': dependencies: - '@docsearch/react': 3.8.0(@algolia/client-search@5.15.0)(search-insights@2.17.3) + '@docsearch/react': 3.8.2(@algolia/client-search@5.15.0)(search-insights@2.17.3) preact: 10.25.0 transitivePeerDependencies: - '@algolia/client-search' @@ -907,11 +902,11 @@ snapshots: - react-dom - search-insights - '@docsearch/react@3.8.0(@algolia/client-search@5.15.0)(search-insights@2.17.3)': + '@docsearch/react@3.8.2(@algolia/client-search@5.15.0)(search-insights@2.17.3)': dependencies: '@algolia/autocomplete-core': 1.17.7(@algolia/client-search@5.15.0)(algoliasearch@5.15.0)(search-insights@2.17.3) '@algolia/autocomplete-preset-algolia': 1.17.7(@algolia/client-search@5.15.0)(algoliasearch@5.15.0) - '@docsearch/css': 3.8.0 + '@docsearch/css': 3.8.2 algoliasearch: 5.15.0 optionalDependencies: search-insights: 2.17.3 @@ -987,7 +982,7 @@ snapshots: '@esbuild/win32-x64@0.21.5': optional: true - '@iconify-json/simple-icons@1.2.12': + '@iconify-json/simple-icons@1.2.37': dependencies: '@iconify/types': 2.0.0 @@ -1049,36 +1044,45 @@ snapshots: '@rollup/rollup-win32-x64-msvc@4.27.4': optional: true - '@shikijs/core@1.23.1': + '@shikijs/core@2.5.0': dependencies: - '@shikijs/engine-javascript': 1.23.1 - '@shikijs/engine-oniguruma': 1.23.1 - '@shikijs/types': 1.23.1 - '@shikijs/vscode-textmate': 9.3.0 + '@shikijs/engine-javascript': 2.5.0 + '@shikijs/engine-oniguruma': 2.5.0 + '@shikijs/types': 2.5.0 + '@shikijs/vscode-textmate': 10.0.2 '@types/hast': 3.0.4 - hast-util-to-html: 9.0.3 + hast-util-to-html: 9.0.5 - '@shikijs/engine-javascript@1.23.1': + '@shikijs/engine-javascript@2.5.0': dependencies: - '@shikijs/types': 1.23.1 - '@shikijs/vscode-textmate': 9.3.0 - oniguruma-to-es: 0.4.1 + '@shikijs/types': 2.5.0 + '@shikijs/vscode-textmate': 10.0.2 + oniguruma-to-es: 3.1.1 - '@shikijs/engine-oniguruma@1.23.1': + '@shikijs/engine-oniguruma@2.5.0': dependencies: - '@shikijs/types': 1.23.1 - '@shikijs/vscode-textmate': 9.3.0 + '@shikijs/types': 2.5.0 + '@shikijs/vscode-textmate': 10.0.2 - '@shikijs/transformers@1.23.1': + '@shikijs/langs@2.5.0': dependencies: - shiki: 1.23.1 + '@shikijs/types': 2.5.0 - '@shikijs/types@1.23.1': + '@shikijs/themes@2.5.0': dependencies: - '@shikijs/vscode-textmate': 9.3.0 + '@shikijs/types': 2.5.0 + + '@shikijs/transformers@2.5.0': + dependencies: + '@shikijs/core': 2.5.0 + '@shikijs/types': 2.5.0 + + '@shikijs/types@2.5.0': + dependencies: + '@shikijs/vscode-textmate': 10.0.2 '@types/hast': 3.0.4 - '@shikijs/vscode-textmate@9.3.0': {} + '@shikijs/vscode-textmate@10.0.2': {} '@types/estree@1.0.6': {} @@ -1101,13 +1105,13 @@ snapshots: '@types/unist@3.0.3': {} - '@types/web-bluetooth@0.0.20': {} + '@types/web-bluetooth@0.0.21': {} '@ungap/structured-clone@1.2.0': {} - '@vitejs/plugin-vue@5.2.0(vite@5.4.14)(vue@3.5.13)': + '@vitejs/plugin-vue@5.2.4(vite@5.4.19)(vue@3.5.13)': dependencies: - vite: 5.4.14 + vite: 5.4.19 vue: 3.5.13 '@vue/compiler-core@3.5.13': @@ -1140,21 +1144,21 @@ snapshots: '@vue/compiler-dom': 3.5.13 '@vue/shared': 3.5.13 - '@vue/devtools-api@7.6.4': + '@vue/devtools-api@7.7.6': dependencies: - '@vue/devtools-kit': 7.6.4 + '@vue/devtools-kit': 7.7.6 - '@vue/devtools-kit@7.6.4': + '@vue/devtools-kit@7.7.6': dependencies: - '@vue/devtools-shared': 7.6.4 - birpc: 0.2.19 + '@vue/devtools-shared': 7.7.6 + birpc: 2.3.0 hookable: 5.5.3 mitt: 3.0.1 perfect-debounce: 1.0.0 speakingurl: 14.0.1 - superjson: 2.2.1 + superjson: 2.2.2 - '@vue/devtools-shared@7.6.4': + '@vue/devtools-shared@7.7.6': dependencies: rfdc: 1.4.1 @@ -1182,35 +1186,32 @@ snapshots: '@vue/shared@3.5.13': {} - '@vueuse/core@11.3.0(vue@3.5.13)': + '@vueuse/core@12.8.2': dependencies: - '@types/web-bluetooth': 0.0.20 - '@vueuse/metadata': 11.3.0 - '@vueuse/shared': 11.3.0(vue@3.5.13) - vue-demi: 0.14.10(vue@3.5.13) + '@types/web-bluetooth': 0.0.21 + '@vueuse/metadata': 12.8.2 + '@vueuse/shared': 12.8.2 + vue: 3.5.13 transitivePeerDependencies: - - '@vue/composition-api' - - vue + - typescript - '@vueuse/integrations@11.3.0(focus-trap@7.6.2)(vue@3.5.13)': + '@vueuse/integrations@12.8.2(focus-trap@7.6.5)': dependencies: - '@vueuse/core': 11.3.0(vue@3.5.13) - '@vueuse/shared': 11.3.0(vue@3.5.13) - vue-demi: 0.14.10(vue@3.5.13) + '@vueuse/core': 12.8.2 + '@vueuse/shared': 12.8.2 + vue: 3.5.13 optionalDependencies: - focus-trap: 7.6.2 + focus-trap: 7.6.5 transitivePeerDependencies: - - '@vue/composition-api' - - vue + - typescript - '@vueuse/metadata@11.3.0': {} + '@vueuse/metadata@12.8.2': {} - '@vueuse/shared@11.3.0(vue@3.5.13)': + '@vueuse/shared@12.8.2': dependencies: - vue-demi: 0.14.10(vue@3.5.13) + vue: 3.5.13 transitivePeerDependencies: - - '@vue/composition-api' - - vue + - typescript algoliasearch@5.15.0: dependencies: @@ -1228,7 +1229,7 @@ snapshots: '@algolia/requester-fetch': 5.15.0 '@algolia/requester-node-http': 5.15.0 - birpc@0.2.19: {} + birpc@2.3.0: {} ccount@2.0.1: {} @@ -1282,14 +1283,14 @@ snapshots: estree-walker@2.0.2: {} - focus-trap@7.6.2: + focus-trap@7.6.5: dependencies: tabbable: 6.2.0 fsevents@2.3.3: optional: true - hast-util-to-html@9.0.3: + hast-util-to-html@9.0.5: dependencies: '@types/hast': 3.0.4 '@types/unist': 3.0.3 @@ -1298,7 +1299,7 @@ snapshots: hast-util-whitespace: 3.0.0 html-void-elements: 3.0.0 mdast-util-to-hast: 13.2.0 - property-information: 6.5.0 + property-information: 7.1.0 space-separated-tokens: 2.0.2 stringify-entities: 4.0.4 zwitch: 2.0.4 @@ -1354,11 +1355,11 @@ snapshots: nanoid@3.3.8: {} - oniguruma-to-es@0.4.1: + oniguruma-to-es@3.1.1: dependencies: emoji-regex-xs: 1.0.0 - regex: 5.0.2 - regex-recursion: 4.2.1 + regex: 6.0.1 + regex-recursion: 6.0.2 perfect-debounce@1.0.0: {} @@ -1374,15 +1375,15 @@ snapshots: prettier@3.3.3: {} - property-information@6.5.0: {} + property-information@7.1.0: {} - regex-recursion@4.2.1: + regex-recursion@6.0.2: dependencies: regex-utilities: 2.3.0 regex-utilities@2.3.0: {} - regex@5.0.2: + regex@6.0.1: dependencies: regex-utilities: 2.3.0 @@ -1414,13 +1415,15 @@ snapshots: search-insights@2.17.3: {} - shiki@1.23.1: + shiki@2.5.0: dependencies: - '@shikijs/core': 1.23.1 - '@shikijs/engine-javascript': 1.23.1 - '@shikijs/engine-oniguruma': 1.23.1 - '@shikijs/types': 1.23.1 - '@shikijs/vscode-textmate': 9.3.0 + '@shikijs/core': 2.5.0 + '@shikijs/engine-javascript': 2.5.0 + '@shikijs/engine-oniguruma': 2.5.0 + '@shikijs/langs': 2.5.0 + '@shikijs/themes': 2.5.0 + '@shikijs/types': 2.5.0 + '@shikijs/vscode-textmate': 10.0.2 '@types/hast': 3.0.4 source-map-js@1.2.1: {} @@ -1434,7 +1437,7 @@ snapshots: character-entities-html4: 2.1.0 character-entities-legacy: 3.0.0 - superjson@2.2.1: + superjson@2.2.2: dependencies: copy-anything: 3.0.5 @@ -1475,7 +1478,7 @@ snapshots: '@types/unist': 3.0.3 vfile-message: 4.0.2 - vite@5.4.14: + vite@5.4.19: dependencies: esbuild: 0.21.5 postcss: 8.4.49 @@ -1483,25 +1486,25 @@ snapshots: optionalDependencies: fsevents: 2.3.3 - vitepress@1.5.0(@algolia/client-search@5.15.0)(postcss@8.4.49)(search-insights@2.17.3): + vitepress@1.6.3(@algolia/client-search@5.15.0)(postcss@8.4.49)(search-insights@2.17.3): dependencies: - '@docsearch/css': 3.8.0 - '@docsearch/js': 3.8.0(@algolia/client-search@5.15.0)(search-insights@2.17.3) - '@iconify-json/simple-icons': 1.2.12 - '@shikijs/core': 1.23.1 - '@shikijs/transformers': 1.23.1 - '@shikijs/types': 1.23.1 + '@docsearch/css': 3.8.2 + '@docsearch/js': 3.8.2(@algolia/client-search@5.15.0)(search-insights@2.17.3) + '@iconify-json/simple-icons': 1.2.37 + '@shikijs/core': 2.5.0 + '@shikijs/transformers': 2.5.0 + '@shikijs/types': 2.5.0 '@types/markdown-it': 14.1.2 - '@vitejs/plugin-vue': 5.2.0(vite@5.4.14)(vue@3.5.13) - '@vue/devtools-api': 7.6.4 + '@vitejs/plugin-vue': 5.2.4(vite@5.4.19)(vue@3.5.13) + '@vue/devtools-api': 7.7.6 '@vue/shared': 3.5.13 - '@vueuse/core': 11.3.0(vue@3.5.13) - '@vueuse/integrations': 11.3.0(focus-trap@7.6.2)(vue@3.5.13) - focus-trap: 7.6.2 + '@vueuse/core': 12.8.2 + '@vueuse/integrations': 12.8.2(focus-trap@7.6.5) + focus-trap: 7.6.5 mark.js: 8.11.1 minisearch: 7.1.1 - shiki: 1.23.1 - vite: 5.4.14 + shiki: 2.5.0 + vite: 5.4.19 vue: 3.5.13 optionalDependencies: postcss: 8.4.49 @@ -1509,7 +1512,6 @@ snapshots: - '@algolia/client-search' - '@types/node' - '@types/react' - - '@vue/composition-api' - async-validator - axios - change-case @@ -1533,10 +1535,6 @@ snapshots: - typescript - universal-cookie - vue-demi@0.14.10(vue@3.5.13): - dependencies: - vue: 3.5.13 - vue@3.5.13: dependencies: '@vue/compiler-dom': 3.5.13 diff --git a/frontend/Dockerfile b/frontend/Dockerfile index 602960ef..6a8ceb37 100644 --- a/frontend/Dockerfile +++ b/frontend/Dockerfile @@ -1,35 +1,49 @@ # Use this image as the platform to build the app -FROM node:18-alpine AS external-website - -# A small line inside the image to show who made it -LABEL Developers="Sean Morley" +FROM node:22-alpine AS external-website + +# Metadata labels for the AdventureLog image +LABEL maintainer="Sean Morley" \ + version="v0.10.0" \ + description="AdventureLog — the ultimate self-hosted travel companion." \ + org.opencontainers.image.title="AdventureLog" \ + org.opencontainers.image.description="AdventureLog is a self-hosted travel companion that helps you plan, track, and share your adventures." \ + org.opencontainers.image.version="v0.10.0" \ + org.opencontainers.image.authors="Sean Morley" \ + org.opencontainers.image.url="https://raw.githubusercontent.com/seanmorley15/AdventureLog/refs/heads/main/brand/banner.png" \ + org.opencontainers.image.source="https://github.com/seanmorley15/AdventureLog" \ + org.opencontainers.image.vendor="Sean Morley" \ + org.opencontainers.image.created="$(date -u +'%Y-%m-%dT%H:%M:%SZ')" \ + org.opencontainers.image.licenses="GPL-3.0" # The WORKDIR instruction sets the working directory for everything that will happen next WORKDIR /app -# Copy all local files into the image +# Install pnpm globally first +RUN npm install -g pnpm + +# Copy package files first for better Docker layer caching +COPY package.json pnpm-lock.yaml* ./ + +# Clean install all node modules using pnpm with frozen lockfile +RUN pnpm install --frozen-lockfile + +# Copy the rest of the application files COPY . . # Remove the development .env file if present RUN rm -f .env -# Install pnpm -RUN npm install -g pnpm - -# Clean install all node modules using pnpm -RUN pnpm install - # Build SvelteKit app RUN pnpm run build -# Expose the port that the app is listening on -EXPOSE 3000 - -# Run the app +# Make startup script executable RUN chmod +x ./startup.sh -# The USER instruction sets the user name to use as the default user for the remainder of the current stage +# Change to non-root user for security USER node:node +# Expose the port that the app is listening on +EXPOSE 3000 + # Run startup.sh instead of the default command CMD ["./startup.sh"] \ No newline at end of file diff --git a/frontend/package.json b/frontend/package.json index a2ed8402..b0a09fb7 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -1,6 +1,6 @@ { "name": "adventurelog-frontend", - "version": "0.8.0", + "version": "0.10.0", "scripts": { "dev": "vite dev", "django": "cd .. && cd backend/server && python3 manage.py runserver", @@ -34,7 +34,7 @@ "tslib": "^2.6.3", "typescript": "^5.5.2", "unplugin-icons": "^0.19.0", - "vite": "^5.4.12" + "vite": "^5.4.19" }, "type": "module", "dependencies": { @@ -43,6 +43,7 @@ "dompurify": "^3.2.4", "emoji-picker-element": "^1.26.0", "gsap": "^3.12.7", + "luxon": "^3.6.1", "marked": "^15.0.4", "psl": "^1.15.0", "qrcode": "^1.5.4", diff --git a/frontend/pnpm-lock.yaml b/frontend/pnpm-lock.yaml index 318a11b7..22170d50 100644 --- a/frontend/pnpm-lock.yaml +++ b/frontend/pnpm-lock.yaml @@ -16,16 +16,19 @@ importers: version: 0.16.2 dompurify: specifier: ^3.2.4 - version: 3.2.4 + version: 3.2.5 emoji-picker-element: specifier: ^1.26.0 - version: 1.26.0 + version: 1.26.3 gsap: specifier: ^3.12.7 version: 3.12.7 + luxon: + specifier: ^3.6.1 + version: 3.6.1 marked: specifier: ^15.0.4 - version: 15.0.4 + version: 15.0.11 psl: specifier: ^1.15.0 version: 1.15.0 @@ -37,77 +40,77 @@ importers: version: 4.0.1(svelte@4.2.19) svelte-maplibre: specifier: ^0.9.8 - version: 0.9.8(svelte@4.2.19) + version: 0.9.14(svelte@4.2.19) devDependencies: '@event-calendar/core': specifier: ^3.7.1 - version: 3.7.1 + version: 3.12.0 '@event-calendar/day-grid': specifier: ^3.7.1 - version: 3.7.1 + version: 3.12.0 '@event-calendar/time-grid': specifier: ^3.7.1 - version: 3.7.1 + version: 3.12.0 '@iconify-json/mdi': specifier: ^1.1.67 - version: 1.1.67 + version: 1.2.3 '@sveltejs/adapter-node': specifier: ^5.2.0 - version: 5.2.0(@sveltejs/kit@2.8.3(@sveltejs/vite-plugin-svelte@3.1.1(svelte@4.2.19)(vite@5.4.12(@types/node@22.5.4)))(svelte@4.2.19)(vite@5.4.12(@types/node@22.5.4))) + version: 5.2.12(@sveltejs/kit@2.20.7(@sveltejs/vite-plugin-svelte@3.1.2(svelte@4.2.19)(vite@5.4.19(@types/node@22.15.2)))(svelte@4.2.19)(vite@5.4.19(@types/node@22.15.2))) '@sveltejs/adapter-vercel': specifier: ^5.4.1 - version: 5.4.1(@sveltejs/kit@2.8.3(@sveltejs/vite-plugin-svelte@3.1.1(svelte@4.2.19)(vite@5.4.12(@types/node@22.5.4)))(svelte@4.2.19)(vite@5.4.12(@types/node@22.5.4))) + version: 5.7.0(@sveltejs/kit@2.20.7(@sveltejs/vite-plugin-svelte@3.1.2(svelte@4.2.19)(vite@5.4.19(@types/node@22.15.2)))(svelte@4.2.19)(vite@5.4.19(@types/node@22.15.2)))(rollup@4.40.2) '@sveltejs/kit': specifier: ^2.8.3 - version: 2.8.3(@sveltejs/vite-plugin-svelte@3.1.1(svelte@4.2.19)(vite@5.4.12(@types/node@22.5.4)))(svelte@4.2.19)(vite@5.4.12(@types/node@22.5.4)) + version: 2.20.7(@sveltejs/vite-plugin-svelte@3.1.2(svelte@4.2.19)(vite@5.4.19(@types/node@22.15.2)))(svelte@4.2.19)(vite@5.4.19(@types/node@22.15.2)) '@sveltejs/vite-plugin-svelte': specifier: ^3.1.1 - version: 3.1.1(svelte@4.2.19)(vite@5.4.12(@types/node@22.5.4)) + version: 3.1.2(svelte@4.2.19)(vite@5.4.19(@types/node@22.15.2)) '@tailwindcss/typography': specifier: ^0.5.13 - version: 0.5.13(tailwindcss@3.4.4) + version: 0.5.16(tailwindcss@3.4.17) '@types/node': specifier: ^22.5.4 - version: 22.5.4 + version: 22.15.2 '@types/qrcode': specifier: ^1.5.5 version: 1.5.5 autoprefixer: specifier: ^10.4.19 - version: 10.4.19(postcss@8.4.38) + version: 10.4.21(postcss@8.5.3) daisyui: specifier: ^4.12.6 - version: 4.12.6(postcss@8.4.38) + version: 4.12.24(postcss@8.5.3) postcss: specifier: ^8.4.38 - version: 8.4.38 + version: 8.5.3 prettier: specifier: ^3.3.2 - version: 3.3.2 + version: 3.5.3 prettier-plugin-svelte: specifier: ^3.2.5 - version: 3.2.5(prettier@3.3.2)(svelte@4.2.19) + version: 3.3.3(prettier@3.5.3)(svelte@4.2.19) svelte: specifier: ^4.2.19 version: 4.2.19 svelte-check: specifier: ^3.8.1 - version: 3.8.1(postcss-load-config@4.0.2(postcss@8.4.38))(postcss@8.4.38)(svelte@4.2.19) + version: 3.8.6(postcss-load-config@4.0.2(postcss@8.5.3))(postcss@8.5.3)(svelte@4.2.19) tailwindcss: specifier: ^3.4.4 - version: 3.4.4 + version: 3.4.17 tslib: specifier: ^2.6.3 - version: 2.6.3 + version: 2.8.1 typescript: specifier: ^5.5.2 - version: 5.5.2 + version: 5.8.3 unplugin-icons: specifier: ^0.19.0 - version: 0.19.0 + version: 0.19.3 vite: - specifier: ^5.4.12 - version: 5.4.12(@types/node@22.5.4) + specifier: ^5.4.19 + version: 5.4.19(@types/node@22.15.2) packages: @@ -119,14 +122,17 @@ packages: resolution: {integrity: sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw==} engines: {node: '>=6.0.0'} - '@antfu/install-pkg@0.1.1': - resolution: {integrity: sha512-LyB/8+bSfa0DFGC06zpCEfs89/XoWZwws5ygEa5D+Xsm3OfI+aXQ86VgVG7Acyef+rSZ5HE7J8rrxzrQeM3PjQ==} + '@antfu/install-pkg@0.4.1': + resolution: {integrity: sha512-T7yB5QNG29afhWVkVq7XeIMBa5U/vs9mX69YqayXypPRmYzUmzwnYltplHmPtZ4HPCn+sQKeXW8I47wCbuBOjw==} - '@antfu/install-pkg@0.3.3': - resolution: {integrity: sha512-nHHsk3NXQ6xkCfiRRC8Nfrg8pU5kkr3P3Y9s9dKqiuRmBD0Yap7fymNDjGFKeWhZQHqqbCS5CfeMy9wtExM24w==} + '@antfu/install-pkg@1.0.0': + resolution: {integrity: sha512-xvX6P/lo1B3ej0OsaErAjqgFYzYVcJpamjLAFLYh9vRJngBrMoUG7aVnrGTeqM7yxbyTD5p3F2+0/QUEh8Vzhw==} - '@antfu/utils@0.7.8': - resolution: {integrity: sha512-rWQkqXRESdjXtc+7NRfK9lASQjpXJu1ayp7qi1d23zZorY+wBHVLHHoVcMsEnkqEBWTFqbztO7/QdJFzyEcLTg==} + '@antfu/utils@0.7.10': + resolution: {integrity: sha512-+562v9k4aI80m1+VuMHehNJWLOFjBnXn3tdOitzD0il5b7smkSBal4+a3oKiQTbrwMmN/TBUMDvbdoWDehgOww==} + + '@antfu/utils@8.1.1': + resolution: {integrity: sha512-Mex9nXf9vR6AhcXmMrlz/HVgYYZpVGJ6YlPgwl7UnaFpnshXs6EK/oa5Gpf3CzENMjkvEx2tQtntGnb7UtSTOQ==} '@esbuild/aix-ppc64@0.19.12': resolution: {integrity: sha512-bmoCYyWdEL3wDQIVbcyzRyeKLgk2WtWLTWz1ZIAZF/EGbNOwSA6ew3PftJ1PqMiOOGu0OyFMzG53L0zqIpPeNA==} @@ -140,6 +146,12 @@ packages: cpu: [ppc64] os: [aix] + '@esbuild/aix-ppc64@0.24.2': + resolution: {integrity: sha512-thpVCb/rhxE/BnMLQ7GReQLLN8q9qbHmI55F4489/ByVg2aQaQ6kbcLb6FHkocZzQhxc4gx0sCk0tJkKBFzDhA==} + engines: {node: '>=18'} + cpu: [ppc64] + os: [aix] + '@esbuild/android-arm64@0.19.12': resolution: {integrity: sha512-P0UVNGIienjZv3f5zq0DP3Nt2IE/3plFzuaS96vihvD0Hd6H/q4WXUGpCxD/E8YrSXfNyRPbpTq+T8ZQioSuPA==} engines: {node: '>=12'} @@ -152,6 +164,12 @@ packages: cpu: [arm64] os: [android] + '@esbuild/android-arm64@0.24.2': + resolution: {integrity: sha512-cNLgeqCqV8WxfcTIOeL4OAtSmL8JjcN6m09XIgro1Wi7cF4t/THaWEa7eL5CMoMBdjoHOTh/vwTO/o2TRXIyzg==} + engines: {node: '>=18'} + cpu: [arm64] + os: [android] + '@esbuild/android-arm@0.19.12': resolution: {integrity: sha512-qg/Lj1mu3CdQlDEEiWrlC4eaPZ1KztwGJ9B6J+/6G+/4ewxJg7gqj8eVYWvao1bXrqGiW2rsBZFSX3q2lcW05w==} engines: {node: '>=12'} @@ -164,6 +182,12 @@ packages: cpu: [arm] os: [android] + '@esbuild/android-arm@0.24.2': + resolution: {integrity: sha512-tmwl4hJkCfNHwFB3nBa8z1Uy3ypZpxqxfTQOcHX+xRByyYgunVbZ9MzUUfb0RxaHIMnbHagwAxuTL+tnNM+1/Q==} + engines: {node: '>=18'} + cpu: [arm] + os: [android] + '@esbuild/android-x64@0.19.12': resolution: {integrity: sha512-3k7ZoUW6Q6YqhdhIaq/WZ7HwBpnFBlW905Fa4s4qWJyiNOgT1dOqDiVAQFwBH7gBRZr17gLrlFCRzF6jFh7Kew==} engines: {node: '>=12'} @@ -176,6 +200,12 @@ packages: cpu: [x64] os: [android] + '@esbuild/android-x64@0.24.2': + resolution: {integrity: sha512-B6Q0YQDqMx9D7rvIcsXfmJfvUYLoP722bgfBlO5cGvNVb5V/+Y7nhBE3mHV9OpxBf4eAS2S68KZztiPaWq4XYw==} + engines: {node: '>=18'} + cpu: [x64] + os: [android] + '@esbuild/darwin-arm64@0.19.12': resolution: {integrity: sha512-B6IeSgZgtEzGC42jsI+YYu9Z3HKRxp8ZT3cqhvliEHovq8HSX2YX8lNocDn79gCKJXOSaEot9MVYky7AKjCs8g==} engines: {node: '>=12'} @@ -188,6 +218,12 @@ packages: cpu: [arm64] os: [darwin] + '@esbuild/darwin-arm64@0.24.2': + resolution: {integrity: sha512-kj3AnYWc+CekmZnS5IPu9D+HWtUI49hbnyqk0FLEJDbzCIQt7hg7ucF1SQAilhtYpIujfaHr6O0UHlzzSPdOeA==} + engines: {node: '>=18'} + cpu: [arm64] + os: [darwin] + '@esbuild/darwin-x64@0.19.12': resolution: {integrity: sha512-hKoVkKzFiToTgn+41qGhsUJXFlIjxI/jSYeZf3ugemDYZldIXIxhvwN6erJGlX4t5h417iFuheZ7l+YVn05N3A==} engines: {node: '>=12'} @@ -200,6 +236,12 @@ packages: cpu: [x64] os: [darwin] + '@esbuild/darwin-x64@0.24.2': + resolution: {integrity: sha512-WeSrmwwHaPkNR5H3yYfowhZcbriGqooyu3zI/3GGpF8AyUdsrrP0X6KumITGA9WOyiJavnGZUwPGvxvwfWPHIA==} + engines: {node: '>=18'} + cpu: [x64] + os: [darwin] + '@esbuild/freebsd-arm64@0.19.12': resolution: {integrity: sha512-4aRvFIXmwAcDBw9AueDQ2YnGmz5L6obe5kmPT8Vd+/+x/JMVKCgdcRwH6APrbpNXsPz+K653Qg8HB/oXvXVukA==} engines: {node: '>=12'} @@ -212,6 +254,12 @@ packages: cpu: [arm64] os: [freebsd] + '@esbuild/freebsd-arm64@0.24.2': + resolution: {integrity: sha512-UN8HXjtJ0k/Mj6a9+5u6+2eZ2ERD7Edt1Q9IZiB5UZAIdPnVKDoG7mdTVGhHJIeEml60JteamR3qhsr1r8gXvg==} + engines: {node: '>=18'} + cpu: [arm64] + os: [freebsd] + '@esbuild/freebsd-x64@0.19.12': resolution: {integrity: sha512-EYoXZ4d8xtBoVN7CEwWY2IN4ho76xjYXqSXMNccFSx2lgqOG/1TBPW0yPx1bJZk94qu3tX0fycJeeQsKovA8gg==} engines: {node: '>=12'} @@ -224,6 +272,12 @@ packages: cpu: [x64] os: [freebsd] + '@esbuild/freebsd-x64@0.24.2': + resolution: {integrity: sha512-TvW7wE/89PYW+IevEJXZ5sF6gJRDY/14hyIGFXdIucxCsbRmLUcjseQu1SyTko+2idmCw94TgyaEZi9HUSOe3Q==} + engines: {node: '>=18'} + cpu: [x64] + os: [freebsd] + '@esbuild/linux-arm64@0.19.12': resolution: {integrity: sha512-EoTjyYyLuVPfdPLsGVVVC8a0p1BFFvtpQDB/YLEhaXyf/5bczaGeN15QkR+O4S5LeJ92Tqotve7i1jn35qwvdA==} engines: {node: '>=12'} @@ -236,6 +290,12 @@ packages: cpu: [arm64] os: [linux] + '@esbuild/linux-arm64@0.24.2': + resolution: {integrity: sha512-7HnAD6074BW43YvvUmE/35Id9/NB7BeX5EoNkK9obndmZBUk8xmJJeU7DwmUeN7tkysslb2eSl6CTrYz6oEMQg==} + engines: {node: '>=18'} + cpu: [arm64] + os: [linux] + '@esbuild/linux-arm@0.19.12': resolution: {integrity: sha512-J5jPms//KhSNv+LO1S1TX1UWp1ucM6N6XuL6ITdKWElCu8wXP72l9MM0zDTzzeikVyqFE6U8YAV9/tFyj0ti+w==} engines: {node: '>=12'} @@ -248,6 +308,12 @@ packages: cpu: [arm] os: [linux] + '@esbuild/linux-arm@0.24.2': + resolution: {integrity: sha512-n0WRM/gWIdU29J57hJyUdIsk0WarGd6To0s+Y+LwvlC55wt+GT/OgkwoXCXvIue1i1sSNWblHEig00GBWiJgfA==} + engines: {node: '>=18'} + cpu: [arm] + os: [linux] + '@esbuild/linux-ia32@0.19.12': resolution: {integrity: sha512-Thsa42rrP1+UIGaWz47uydHSBOgTUnwBwNq59khgIwktK6x60Hivfbux9iNR0eHCHzOLjLMLfUMLCypBkZXMHA==} engines: {node: '>=12'} @@ -260,6 +326,12 @@ packages: cpu: [ia32] os: [linux] + '@esbuild/linux-ia32@0.24.2': + resolution: {integrity: sha512-sfv0tGPQhcZOgTKO3oBE9xpHuUqguHvSo4jl+wjnKwFpapx+vUDcawbwPNuBIAYdRAvIDBfZVvXprIj3HA+Ugw==} + engines: {node: '>=18'} + cpu: [ia32] + os: [linux] + '@esbuild/linux-loong64@0.19.12': resolution: {integrity: sha512-LiXdXA0s3IqRRjm6rV6XaWATScKAXjI4R4LoDlvO7+yQqFdlr1Bax62sRwkVvRIrwXxvtYEHHI4dm50jAXkuAA==} engines: {node: '>=12'} @@ -272,6 +344,12 @@ packages: cpu: [loong64] os: [linux] + '@esbuild/linux-loong64@0.24.2': + resolution: {integrity: sha512-CN9AZr8kEndGooS35ntToZLTQLHEjtVB5n7dl8ZcTZMonJ7CCfStrYhrzF97eAecqVbVJ7APOEe18RPI4KLhwQ==} + engines: {node: '>=18'} + cpu: [loong64] + os: [linux] + '@esbuild/linux-mips64el@0.19.12': resolution: {integrity: sha512-fEnAuj5VGTanfJ07ff0gOA6IPsvrVHLVb6Lyd1g2/ed67oU1eFzL0r9WL7ZzscD+/N6i3dWumGE1Un4f7Amf+w==} engines: {node: '>=12'} @@ -284,6 +362,12 @@ packages: cpu: [mips64el] os: [linux] + '@esbuild/linux-mips64el@0.24.2': + resolution: {integrity: sha512-iMkk7qr/wl3exJATwkISxI7kTcmHKE+BlymIAbHO8xanq/TjHaaVThFF6ipWzPHryoFsesNQJPE/3wFJw4+huw==} + engines: {node: '>=18'} + cpu: [mips64el] + os: [linux] + '@esbuild/linux-ppc64@0.19.12': resolution: {integrity: sha512-nYJA2/QPimDQOh1rKWedNOe3Gfc8PabU7HT3iXWtNUbRzXS9+vgB0Fjaqr//XNbd82mCxHzik2qotuI89cfixg==} engines: {node: '>=12'} @@ -296,6 +380,12 @@ packages: cpu: [ppc64] os: [linux] + '@esbuild/linux-ppc64@0.24.2': + resolution: {integrity: sha512-shsVrgCZ57Vr2L8mm39kO5PPIb+843FStGt7sGGoqiiWYconSxwTiuswC1VJZLCjNiMLAMh34jg4VSEQb+iEbw==} + engines: {node: '>=18'} + cpu: [ppc64] + os: [linux] + '@esbuild/linux-riscv64@0.19.12': resolution: {integrity: sha512-2MueBrlPQCw5dVJJpQdUYgeqIzDQgw3QtiAHUC4RBz9FXPrskyyU3VI1hw7C0BSKB9OduwSJ79FTCqtGMWqJHg==} engines: {node: '>=12'} @@ -308,6 +398,12 @@ packages: cpu: [riscv64] os: [linux] + '@esbuild/linux-riscv64@0.24.2': + resolution: {integrity: sha512-4eSFWnU9Hhd68fW16GD0TINewo1L6dRrB+oLNNbYyMUAeOD2yCK5KXGK1GH4qD/kT+bTEXjsyTCiJGHPZ3eM9Q==} + engines: {node: '>=18'} + cpu: [riscv64] + os: [linux] + '@esbuild/linux-s390x@0.19.12': resolution: {integrity: sha512-+Pil1Nv3Umes4m3AZKqA2anfhJiVmNCYkPchwFJNEJN5QxmTs1uzyy4TvmDrCRNT2ApwSari7ZIgrPeUx4UZDg==} engines: {node: '>=12'} @@ -320,6 +416,12 @@ packages: cpu: [s390x] os: [linux] + '@esbuild/linux-s390x@0.24.2': + resolution: {integrity: sha512-S0Bh0A53b0YHL2XEXC20bHLuGMOhFDO6GN4b3YjRLK//Ep3ql3erpNcPlEFed93hsQAjAQDNsvcK+hV90FubSw==} + engines: {node: '>=18'} + cpu: [s390x] + os: [linux] + '@esbuild/linux-x64@0.19.12': resolution: {integrity: sha512-B71g1QpxfwBvNrfyJdVDexenDIt1CiDN1TIXLbhOw0KhJzE78KIFGX6OJ9MrtC0oOqMWf+0xop4qEU8JrJTwCg==} engines: {node: '>=12'} @@ -332,6 +434,18 @@ packages: cpu: [x64] os: [linux] + '@esbuild/linux-x64@0.24.2': + resolution: {integrity: sha512-8Qi4nQcCTbLnK9WoMjdC9NiTG6/E38RNICU6sUNqK0QFxCYgoARqVqxdFmWkdonVsvGqWhmm7MO0jyTqLqwj0Q==} + engines: {node: '>=18'} + cpu: [x64] + os: [linux] + + '@esbuild/netbsd-arm64@0.24.2': + resolution: {integrity: sha512-wuLK/VztRRpMt9zyHSazyCVdCXlpHkKm34WUyinD2lzK07FAHTq0KQvZZlXikNWkDGoT6x3TD51jKQ7gMVpopw==} + engines: {node: '>=18'} + cpu: [arm64] + os: [netbsd] + '@esbuild/netbsd-x64@0.19.12': resolution: {integrity: sha512-3ltjQ7n1owJgFbuC61Oj++XhtzmymoCihNFgT84UAmJnxJfm4sYCiSLTXZtE00VWYpPMYc+ZQmB6xbSdVh0JWA==} engines: {node: '>=12'} @@ -344,6 +458,18 @@ packages: cpu: [x64] os: [netbsd] + '@esbuild/netbsd-x64@0.24.2': + resolution: {integrity: sha512-VefFaQUc4FMmJuAxmIHgUmfNiLXY438XrL4GDNV1Y1H/RW3qow68xTwjZKfj/+Plp9NANmzbH5R40Meudu8mmw==} + engines: {node: '>=18'} + cpu: [x64] + os: [netbsd] + + '@esbuild/openbsd-arm64@0.24.2': + resolution: {integrity: sha512-YQbi46SBct6iKnszhSvdluqDmxCJA+Pu280Av9WICNwQmMxV7nLRHZfjQzwbPs3jeWnuAhE9Jy0NrnJ12Oz+0A==} + engines: {node: '>=18'} + cpu: [arm64] + os: [openbsd] + '@esbuild/openbsd-x64@0.19.12': resolution: {integrity: sha512-RbrfTB9SWsr0kWmb9srfF+L933uMDdu9BIzdA7os2t0TXhCRjrQyCeOt6wVxr79CKD4c+p+YhCj31HBkYcXebw==} engines: {node: '>=12'} @@ -356,6 +482,12 @@ packages: cpu: [x64] os: [openbsd] + '@esbuild/openbsd-x64@0.24.2': + resolution: {integrity: sha512-+iDS6zpNM6EnJyWv0bMGLWSWeXGN/HTaF/LXHXHwejGsVi+ooqDfMCCTerNFxEkM3wYVcExkeGXNqshc9iMaOA==} + engines: {node: '>=18'} + cpu: [x64] + os: [openbsd] + '@esbuild/sunos-x64@0.19.12': resolution: {integrity: sha512-HKjJwRrW8uWtCQnQOz9qcU3mUZhTUQvi56Q8DPTLLB+DawoiQdjsYq+j+D3s9I8VFtDr+F9CjgXKKC4ss89IeA==} engines: {node: '>=12'} @@ -368,6 +500,12 @@ packages: cpu: [x64] os: [sunos] + '@esbuild/sunos-x64@0.24.2': + resolution: {integrity: sha512-hTdsW27jcktEvpwNHJU4ZwWFGkz2zRJUz8pvddmXPtXDzVKTTINmlmga3ZzwcuMpUvLw7JkLy9QLKyGpD2Yxig==} + engines: {node: '>=18'} + cpu: [x64] + os: [sunos] + '@esbuild/win32-arm64@0.19.12': resolution: {integrity: sha512-URgtR1dJnmGvX864pn1B2YUYNzjmXkuJOIqG2HdU62MVS4EHpU2946OZoTMnRUHklGtJdJZ33QfzdjGACXhn1A==} engines: {node: '>=12'} @@ -380,6 +518,12 @@ packages: cpu: [arm64] os: [win32] + '@esbuild/win32-arm64@0.24.2': + resolution: {integrity: sha512-LihEQ2BBKVFLOC9ZItT9iFprsE9tqjDjnbulhHoFxYQtQfai7qfluVODIYxt1PgdoyQkz23+01rzwNwYfutxUQ==} + engines: {node: '>=18'} + cpu: [arm64] + os: [win32] + '@esbuild/win32-ia32@0.19.12': resolution: {integrity: sha512-+ZOE6pUkMOJfmxmBZElNOx72NKpIa/HFOMGzu8fqzQJ5kgf6aTGrcJaFsNiVMH4JKpMipyK+7k0n2UXN7a8YKQ==} engines: {node: '>=12'} @@ -392,6 +536,12 @@ packages: cpu: [ia32] os: [win32] + '@esbuild/win32-ia32@0.24.2': + resolution: {integrity: sha512-q+iGUwfs8tncmFC9pcnD5IvRHAzmbwQ3GPS5/ceCyHdjXubwQWI12MKWSNSMYLJMq23/IUCvJMS76PDqXe1fxA==} + engines: {node: '>=18'} + cpu: [ia32] + os: [win32] + '@esbuild/win32-x64@0.19.12': resolution: {integrity: sha512-T1QyPSDCyMXaO3pzBkF96E8xMkiRYbUEZADd29SyPGabqxMViNoii+NcK7eWJAEoU6RZyEm5lVSIjTmcdoB9HA==} engines: {node: '>=12'} @@ -404,45 +554,55 @@ packages: cpu: [x64] os: [win32] - '@event-calendar/core@3.7.1': - resolution: {integrity: sha512-S5D4arG7b47uhXmcT/rC7FT3UO9+KB+QhDuhfOzDgKCpAFlEBU1wt1UoHmPTbGy3J+yVMR+rmcresYUvM44+pA==} + '@esbuild/win32-x64@0.24.2': + resolution: {integrity: sha512-7VTgWzgMGvup6aSqDPLiW5zHaxYJGTO4OokMjIlrCtf+VpEL+cXKtCvg723iguPYI5oaUNdS+/V7OU2gvXVWEg==} + engines: {node: '>=18'} + cpu: [x64] + os: [win32] - '@event-calendar/day-grid@3.7.1': - resolution: {integrity: sha512-kwmadkhUxtQDv+0azMkePrmilFp5dljWLHsluHl1uepfJa1yXlrvFy3GMFnYuPo2Gva0MV+HnU/GMqVG8vIcWw==} + '@event-calendar/core@3.12.0': + resolution: {integrity: sha512-aKtDwEKzWHOV2PLVdhR/f843ecW3C0w5G5VhGl1f0GBEgT8dZvoYreQ7QKTBWh7B7YEtEn2P0Dn36zFYj5XEXQ==} - '@event-calendar/time-grid@3.7.1': - resolution: {integrity: sha512-kPC4+XhFcSoNSnYG0TSQeGylpvrbFF1g+cTcFFIW6qH3wPIeBBCo0fRuD4Tr5/q4ewZQ5lNrCkZXOpZxHJxOfw==} + '@event-calendar/day-grid@3.12.0': + resolution: {integrity: sha512-gY6XvEIlwWI9uKWsXukyanDmrEWv1UDHdhikhchpe6iZP25p3+760qXIU2kdu91tXjb+hVbpFcn7sdNPPE4u7Q==} - '@formatjs/ecma402-abstract@2.2.1': - resolution: {integrity: sha512-O4ywpkdJybrjFc9zyL8qK5aklleIAi5O4nYhBVJaOFtCkNrnU+lKFeJOFC48zpsZQmR8Aok2V79hGpHnzbmFpg==} + '@event-calendar/time-grid@3.12.0': + resolution: {integrity: sha512-n/IoFSq/ym6ad2k+H9RL2A8GpfOJy1zpKKLb1Edp/QEusexpPg8LNdSbxhmKGz6ip5ud0Bi/xgUa8xUqut8ooQ==} - '@formatjs/fast-memoize@2.2.2': - resolution: {integrity: sha512-mzxZcS0g1pOzwZTslJOBTmLzDXseMLLvnh25ymRilCm8QLMObsQ7x/rj9GNrH0iUhZMlFisVOD6J1n6WQqpKPQ==} + '@formatjs/ecma402-abstract@2.3.4': + resolution: {integrity: sha512-qrycXDeaORzIqNhBOx0btnhpD1c+/qFIHAN9znofuMJX6QBwtbrmlpWfD4oiUUD2vJUOIYFA/gYtg2KAMGG7sA==} - '@formatjs/icu-messageformat-parser@2.9.1': - resolution: {integrity: sha512-7AYk4tjnLi5wBkxst2w7qFj38JLMJoqzj7BhdEl7oTlsWMlqwgx4p9oMmmvpXWTSDGNwOKBRc1SfwMh5MOHeNg==} + '@formatjs/fast-memoize@2.2.7': + resolution: {integrity: sha512-Yabmi9nSvyOMrlSeGGWDiH7rf3a7sIwplbvo/dlz9WCIjzIQAfy1RMf4S0X3yG724n5Ghu2GmEl5NJIV6O9sZQ==} - '@formatjs/icu-skeleton-parser@1.8.5': - resolution: {integrity: sha512-zRZ/e3B5qY2+JCLs7puTzWS1Jb+t/K+8Jur/gEZpA2EjWeLDE17nsx8thyo9P48Mno7UmafnPupV2NCJXX17Dg==} + '@formatjs/icu-messageformat-parser@2.11.2': + resolution: {integrity: sha512-AfiMi5NOSo2TQImsYAg8UYddsNJ/vUEv/HaNqiFjnI3ZFfWihUtD5QtuX6kHl8+H+d3qvnE/3HZrfzgdWpsLNA==} - '@formatjs/intl-localematcher@0.5.6': - resolution: {integrity: sha512-roz1+Ba5e23AHX6KUAWmLEyTRZegM5YDuxuvkHCyK3RJddf/UXB2f+s7pOMm9ktfPGla0g+mQXOn5vsuYirnaA==} + '@formatjs/icu-skeleton-parser@1.8.14': + resolution: {integrity: sha512-i4q4V4qslThK4Ig8SxyD76cp3+QJ3sAqr7f6q9VVfeGtxG9OhiAk3y9XF6Q41OymsKzsGQ6OQQoJNY4/lI8TcQ==} - '@iconify-json/mdi@1.1.67': - resolution: {integrity: sha512-00nllHES8hyACwIfgySlQgAE6MKgpr2wsKfpifMiZWZ9aXC5l4Jb0lR3lJSWwXgOW6kzAOdzC3T+2VOfBBZ13A==} + '@formatjs/intl-localematcher@0.6.1': + resolution: {integrity: sha512-ePEgLgVCqi2BBFnTMWPfIghu6FkbZnnBVhO2sSxvLfrdFw7wCHAHiDoM2h4NRgjbaY7+B7HgOLZGkK187pZTZg==} + + '@iconify-json/mdi@1.2.3': + resolution: {integrity: sha512-O3cLwbDOK7NNDf2ihaQOH5F9JglnulNDFV7WprU2dSoZu3h3cWH//h74uQAB87brHmvFVxIOkuBX2sZSzYhScg==} '@iconify/types@2.0.0': resolution: {integrity: sha512-+wluvCrRhXrhyOmRDJ3q8mux9JkKy5SJ/v8ol2tu4FVjyYvtEzkc/3pK15ET6RKg4b4w4BmTk1+gsCUhf21Ykg==} - '@iconify/utils@2.1.25': - resolution: {integrity: sha512-Y+iGko8uv/Fz5bQLLJyNSZGOdMW0G7cnlEX1CiNcKsRXX9cq/y/vwxrIAtLCZhKHr3m0VJmsjVPsvnM4uX8YLg==} + '@iconify/utils@2.3.0': + resolution: {integrity: sha512-GmQ78prtwYW6EtzXRU1rY+KwOKfz32PD7iJh6Iyqw68GiKuoZ2A6pRtzWONz5VQJbp50mEjXh/7NkumtrAgRKA==} '@isaacs/cliui@8.0.2': resolution: {integrity: sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==} engines: {node: '>=12'} - '@jridgewell/gen-mapping@0.3.5': - resolution: {integrity: sha512-IzL8ZoEDIBRWEzlCcRhOaCupYyN5gdIK+Q6fbFdPDg6HqX6jpkItn7DFIpW9LQzXG6Df9sA7+OKnq0qlz/GaQg==} + '@isaacs/fs-minipass@4.0.1': + resolution: {integrity: sha512-wgm9Ehl2jpeqP3zw/7mo3kRHFp5MEDhqAdwy1fTGkHAwnkGOVsgpvQhL8B5n1qlb01jV3n/bI0ZfZp5lWA1k4w==} + engines: {node: '>=18.0.0'} + + '@jridgewell/gen-mapping@0.3.8': + resolution: {integrity: sha512-imAbBGkb+ebQyxKgzv5Hu2nmROxoDOXHh80evxdoXNOrvAnVx7zimzc1Oo5h9RlfV4vPXaE2iM5pOFbvOCClWA==} engines: {node: '>=6.0.0'} '@jridgewell/resolve-uri@3.1.2': @@ -453,16 +613,12 @@ packages: resolution: {integrity: sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A==} engines: {node: '>=6.0.0'} - '@jridgewell/sourcemap-codec@1.4.15': - resolution: {integrity: sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg==} + '@jridgewell/sourcemap-codec@1.5.0': + resolution: {integrity: sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ==} '@jridgewell/trace-mapping@0.3.25': resolution: {integrity: sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==} - '@jsdevtools/ez-spawn@3.0.4': - resolution: {integrity: sha512-f5DRIOZf7wxogefH03RjMPMdBF7ADTWUMoOs9kaJo06EfwF+aFhMZMDZxHg/Xe12hptN9xoZjGso2fdjapBRIA==} - engines: {node: '>=10'} - '@lukulent/svelte-umami@0.0.3': resolution: {integrity: sha512-4pL0sJapfy14yDj6CyZgewbRDadRoBJtk/dLqCJh7/tQuX7HO4hviBzhrVa4Osxaq2kcGEKdpkhAKAoaNdlNSA==} peerDependencies: @@ -476,8 +632,9 @@ packages: resolution: {integrity: sha512-rY0o9A5ECsTQRVhv7tL/OyDpGAoUB4tTvLiW1DSzQGq4bvTPhNw1VpSNjDJc5GFZ2XuyOtSWSVN05qOtcD71qQ==} engines: {node: '>= 0.6'} - '@mapbox/node-pre-gyp@1.0.11': - resolution: {integrity: sha512-Yhlar6v9WQgUp/He7BdgzOz8lqMQ8sU+jkCq7Wx8Myc5YFJLbEe7lgui/V7G1qB1DJykHSGwreceSaD60Y0PUQ==} + '@mapbox/node-pre-gyp@2.0.0': + resolution: {integrity: sha512-llMXd39jtP0HpQLVI37Bf1m2ADlEb35GYSh1SDSLsBhR+5iCxiNGlT31yqbNtVHygHAtMy6dWFERpU2JgufhPg==} + engines: {node: '>=18'} hasBin: true '@mapbox/point-geometry@0.1.0': @@ -500,8 +657,8 @@ packages: resolution: {integrity: sha512-Es6WcD0nO5l+2BOQS4uLfNPYQaNDfbot3X1XUoloz+x0mPDS3eeORZJl06HXjwBG1fOGwCRnzK88LMdxKRrd6Q==} engines: {node: '>=6.0.0'} - '@maplibre/maplibre-gl-style-spec@20.3.0': - resolution: {integrity: sha512-eSiQ3E5LUSxAOY9ABXGyfNhout2iEa6mUxKeaQ9nJ8NL1NuaQYU7zKqzx/LEYcXe1neT4uYAgM1wYZj3fTSXtA==} + '@maplibre/maplibre-gl-style-spec@20.4.0': + resolution: {integrity: sha512-AzBy3095fTFPjDjmWpR2w6HVRAZJ6hQZUCwk5Plz6EyfnfuQW1odeW5i2Ai47Y6TBA2hQnC+azscjBSALpaWgw==} hasBin: true '@nodelib/fs.scandir@2.1.5': @@ -520,11 +677,11 @@ packages: resolution: {integrity: sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==} engines: {node: '>=14'} - '@polka/url@1.0.0-next.25': - resolution: {integrity: sha512-j7P6Rgr3mmtdkeDGTe0E/aYyWEWVtc5yFXtHCRHs28/jptDEWfaVOc5T7cblqy1XKPPfCxJc/8DwQ5YgLOZOVQ==} + '@polka/url@1.0.0-next.29': + resolution: {integrity: sha512-wwQAWhWSuHaag8c4q/KN/vCoeOJYshAIvMQwD4GpSb3OiZklFfvAgmj0VCBBImRpuF/aFgIRzllXlVX93Jevww==} - '@rollup/plugin-commonjs@26.0.1': - resolution: {integrity: sha512-UnsKoZK6/aGIH6AdkptXhNvhaqftcjq3zZdT+LY5Ftms6JR06nADcDsYp5hTU9E2lbJUEOhdlY5J4DNTneM+jQ==} + '@rollup/plugin-commonjs@28.0.3': + resolution: {integrity: sha512-pyltgilam1QPdn+Zd9gaCfOLcnjMEJ9gV+bTw6/r73INdvzf1ah9zLIJBm+kW7R6IUFIQ1YO+VqZtYxZNWFPEQ==} engines: {node: '>=16.0.0 || 14 >= 14.17'} peerDependencies: rollup: ^2.68.0||^3.0.0||^4.0.0 @@ -541,8 +698,8 @@ packages: rollup: optional: true - '@rollup/plugin-node-resolve@15.2.3': - resolution: {integrity: sha512-j/lym8nf5E21LwBT4Df1VD6hRO2L2iwUeUmP7litikRsVp1H6NWx20NEp0Y7su+7XGc476GnXXc4kFeZNGmaSQ==} + '@rollup/plugin-node-resolve@16.0.1': + resolution: {integrity: sha512-tk5YCxJWIG81umIvNkSod2qK5KyQW19qcBF/B78n1bjtOON6gzKoVeSzAE8yHCZEDmqkHKkxplExA8KzdJLJpA==} engines: {node: '>=14.0.0'} peerDependencies: rollup: ^2.78.0||^3.0.0||^4.0.0 @@ -550,12 +707,8 @@ packages: rollup: optional: true - '@rollup/pluginutils@4.2.1': - resolution: {integrity: sha512-iKnFXr7NkdZAIHiIWE+BX5ULi/ucVFYWD6TbAV+rZctiRTY2PL6tsIKhoIOaoskiWAkgu+VsbXgUVDNLHf+InQ==} - engines: {node: '>= 8.0.0'} - - '@rollup/pluginutils@5.1.0': - resolution: {integrity: sha512-XTIWOPPcpvyKI6L1NHo0lFlCyznUEyPmPY1mc3KpPVDYulHSTvyeLNVW00QTLIAFNhR3kYnJTQHeGqU4M3n09g==} + '@rollup/pluginutils@5.1.4': + resolution: {integrity: sha512-USm05zrsFxYLPdWWq+K3STlWiT/3ELn3RcV5hJMghpeAIhxfsUIg6mt12CBJBInWMV4VneoV7SfGv8xIwo2qNQ==} engines: {node: '>=14.0.0'} peerDependencies: rollup: ^1.20.0||^2.0.0||^3.0.0||^4.0.0 @@ -563,199 +716,124 @@ packages: rollup: optional: true - '@rollup/rollup-android-arm-eabi@4.24.0': - resolution: {integrity: sha512-Q6HJd7Y6xdB48x8ZNVDOqsbh2uByBhgK8PiQgPhwkIw/HC/YX5Ghq2mQY5sRMZWHb3VsFkWooUVOZHKr7DmDIA==} + '@rollup/rollup-android-arm-eabi@4.40.2': + resolution: {integrity: sha512-JkdNEq+DFxZfUwxvB58tHMHBHVgX23ew41g1OQinthJ+ryhdRk67O31S7sYw8u2lTjHUPFxwar07BBt1KHp/hg==} cpu: [arm] os: [android] - '@rollup/rollup-android-arm-eabi@4.31.0': - resolution: {integrity: sha512-9NrR4033uCbUBRgvLcBrJofa2KY9DzxL2UKZ1/4xA/mnTNyhZCWBuD8X3tPm1n4KxcgaraOYgrFKSgwjASfmlA==} - cpu: [arm] - os: [android] - - '@rollup/rollup-android-arm64@4.24.0': - resolution: {integrity: sha512-ijLnS1qFId8xhKjT81uBHuuJp2lU4x2yxa4ctFPtG+MqEE6+C5f/+X/bStmxapgmwLwiL3ih122xv8kVARNAZA==} + '@rollup/rollup-android-arm64@4.40.2': + resolution: {integrity: sha512-13unNoZ8NzUmnndhPTkWPWbX3vtHodYmy+I9kuLxN+F+l+x3LdVF7UCu8TWVMt1POHLh6oDHhnOA04n8oJZhBw==} cpu: [arm64] os: [android] - '@rollup/rollup-android-arm64@4.31.0': - resolution: {integrity: sha512-iBbODqT86YBFHajxxF8ebj2hwKm1k8PTBQSojSt3d1FFt1gN+xf4CowE47iN0vOSdnd+5ierMHBbu/rHc7nq5g==} - cpu: [arm64] - os: [android] - - '@rollup/rollup-darwin-arm64@4.24.0': - resolution: {integrity: sha512-bIv+X9xeSs1XCk6DVvkO+S/z8/2AMt/2lMqdQbMrmVpgFvXlmde9mLcbQpztXm1tajC3raFDqegsH18HQPMYtA==} - cpu: [arm64] - os: [darwin] - - '@rollup/rollup-darwin-arm64@4.31.0': - resolution: {integrity: sha512-WHIZfXgVBX30SWuTMhlHPXTyN20AXrLH4TEeH/D0Bolvx9PjgZnn4H677PlSGvU6MKNsjCQJYczkpvBbrBnG6g==} + '@rollup/rollup-darwin-arm64@4.40.2': + resolution: {integrity: sha512-Gzf1Hn2Aoe8VZzevHostPX23U7N5+4D36WJNHK88NZHCJr7aVMG4fadqkIf72eqVPGjGc0HJHNuUaUcxiR+N/w==} cpu: [arm64] os: [darwin] - '@rollup/rollup-darwin-x64@4.24.0': - resolution: {integrity: sha512-X6/nOwoFN7RT2svEQWUsW/5C/fYMBe4fnLK9DQk4SX4mgVBiTA9h64kjUYPvGQ0F/9xwJ5U5UfTbl6BEjaQdBQ==} + '@rollup/rollup-darwin-x64@4.40.2': + resolution: {integrity: sha512-47N4hxa01a4x6XnJoskMKTS8XZ0CZMd8YTbINbi+w03A2w4j1RTlnGHOz/P0+Bg1LaVL6ufZyNprSg+fW5nYQQ==} cpu: [x64] os: [darwin] - '@rollup/rollup-darwin-x64@4.31.0': - resolution: {integrity: sha512-hrWL7uQacTEF8gdrQAqcDy9xllQ0w0zuL1wk1HV8wKGSGbKPVjVUv/DEwT2+Asabf8Dh/As+IvfdU+H8hhzrQQ==} - cpu: [x64] - os: [darwin] - - '@rollup/rollup-freebsd-arm64@4.31.0': - resolution: {integrity: sha512-S2oCsZ4hJviG1QjPY1h6sVJLBI6ekBeAEssYKad1soRFv3SocsQCzX6cwnk6fID6UQQACTjeIMB+hyYrFacRew==} + '@rollup/rollup-freebsd-arm64@4.40.2': + resolution: {integrity: sha512-8t6aL4MD+rXSHHZUR1z19+9OFJ2rl1wGKvckN47XFRVO+QL/dUSpKA2SLRo4vMg7ELA8pzGpC+W9OEd1Z/ZqoQ==} cpu: [arm64] os: [freebsd] - '@rollup/rollup-freebsd-x64@4.31.0': - resolution: {integrity: sha512-pCANqpynRS4Jirn4IKZH4tnm2+2CqCNLKD7gAdEjzdLGbH1iO0zouHz4mxqg0uEMpO030ejJ0aA6e1PJo2xrPA==} + '@rollup/rollup-freebsd-x64@4.40.2': + resolution: {integrity: sha512-C+AyHBzfpsOEYRFjztcYUFsH4S7UsE9cDtHCtma5BK8+ydOZYgMmWg1d/4KBytQspJCld8ZIujFMAdKG1xyr4Q==} cpu: [x64] os: [freebsd] - '@rollup/rollup-linux-arm-gnueabihf@4.24.0': - resolution: {integrity: sha512-0KXvIJQMOImLCVCz9uvvdPgfyWo93aHHp8ui3FrtOP57svqrF/roSSR5pjqL2hcMp0ljeGlU4q9o/rQaAQ3AYA==} - cpu: [arm] - os: [linux] - - '@rollup/rollup-linux-arm-gnueabihf@4.31.0': - resolution: {integrity: sha512-0O8ViX+QcBd3ZmGlcFTnYXZKGbFu09EhgD27tgTdGnkcYXLat4KIsBBQeKLR2xZDCXdIBAlWLkiXE1+rJpCxFw==} - cpu: [arm] - os: [linux] - - '@rollup/rollup-linux-arm-musleabihf@4.24.0': - resolution: {integrity: sha512-it2BW6kKFVh8xk/BnHfakEeoLPv8STIISekpoF+nBgWM4d55CZKc7T4Dx1pEbTnYm/xEKMgy1MNtYuoA8RFIWw==} + '@rollup/rollup-linux-arm-gnueabihf@4.40.2': + resolution: {integrity: sha512-de6TFZYIvJwRNjmW3+gaXiZ2DaWL5D5yGmSYzkdzjBDS3W+B9JQ48oZEsmMvemqjtAFzE16DIBLqd6IQQRuG9Q==} cpu: [arm] os: [linux] - '@rollup/rollup-linux-arm-musleabihf@4.31.0': - resolution: {integrity: sha512-w5IzG0wTVv7B0/SwDnMYmbr2uERQp999q8FMkKG1I+j8hpPX2BYFjWe69xbhbP6J9h2gId/7ogesl9hwblFwwg==} + '@rollup/rollup-linux-arm-musleabihf@4.40.2': + resolution: {integrity: sha512-urjaEZubdIkacKc930hUDOfQPysezKla/O9qV+O89enqsqUmQm8Xj8O/vh0gHg4LYfv7Y7UsE3QjzLQzDYN1qg==} cpu: [arm] os: [linux] - '@rollup/rollup-linux-arm64-gnu@4.24.0': - resolution: {integrity: sha512-i0xTLXjqap2eRfulFVlSnM5dEbTVque/3Pi4g2y7cxrs7+a9De42z4XxKLYJ7+OhE3IgxvfQM7vQc43bwTgPwA==} + '@rollup/rollup-linux-arm64-gnu@4.40.2': + resolution: {integrity: sha512-KlE8IC0HFOC33taNt1zR8qNlBYHj31qGT1UqWqtvR/+NuCVhfufAq9fxO8BMFC22Wu0rxOwGVWxtCMvZVLmhQg==} cpu: [arm64] os: [linux] - '@rollup/rollup-linux-arm64-gnu@4.31.0': - resolution: {integrity: sha512-JyFFshbN5xwy6fulZ8B/8qOqENRmDdEkcIMF0Zz+RsfamEW+Zabl5jAb0IozP/8UKnJ7g2FtZZPEUIAlUSX8cA==} + '@rollup/rollup-linux-arm64-musl@4.40.2': + resolution: {integrity: sha512-j8CgxvfM0kbnhu4XgjnCWJQyyBOeBI1Zq91Z850aUddUmPeQvuAy6OiMdPS46gNFgy8gN1xkYyLgwLYZG3rBOg==} cpu: [arm64] os: [linux] - '@rollup/rollup-linux-arm64-musl@4.24.0': - resolution: {integrity: sha512-9E6MKUJhDuDh604Qco5yP/3qn3y7SLXYuiC0Rpr89aMScS2UAmK1wHP2b7KAa1nSjWJc/f/Lc0Wl1L47qjiyQw==} - cpu: [arm64] - os: [linux] - - '@rollup/rollup-linux-arm64-musl@4.31.0': - resolution: {integrity: sha512-kpQXQ0UPFeMPmPYksiBL9WS/BDiQEjRGMfklVIsA0Sng347H8W2iexch+IEwaR7OVSKtr2ZFxggt11zVIlZ25g==} - cpu: [arm64] - os: [linux] - - '@rollup/rollup-linux-loongarch64-gnu@4.31.0': - resolution: {integrity: sha512-pMlxLjt60iQTzt9iBb3jZphFIl55a70wexvo8p+vVFK+7ifTRookdoXX3bOsRdmfD+OKnMozKO6XM4zR0sHRrQ==} + '@rollup/rollup-linux-loongarch64-gnu@4.40.2': + resolution: {integrity: sha512-Ybc/1qUampKuRF4tQXc7G7QY9YRyeVSykfK36Y5Qc5dmrIxwFhrOzqaVTNoZygqZ1ZieSWTibfFhQ5qK8jpWxw==} cpu: [loong64] os: [linux] - '@rollup/rollup-linux-powerpc64le-gnu@4.24.0': - resolution: {integrity: sha512-2XFFPJ2XMEiF5Zi2EBf4h73oR1V/lycirxZxHZNc93SqDN/IWhYYSYj8I9381ikUFXZrz2v7r2tOVk2NBwxrWw==} - cpu: [ppc64] - os: [linux] - - '@rollup/rollup-linux-powerpc64le-gnu@4.31.0': - resolution: {integrity: sha512-D7TXT7I/uKEuWiRkEFbed1UUYZwcJDU4vZQdPTcepK7ecPhzKOYk4Er2YR4uHKme4qDeIh6N3XrLfpuM7vzRWQ==} + '@rollup/rollup-linux-powerpc64le-gnu@4.40.2': + resolution: {integrity: sha512-3FCIrnrt03CCsZqSYAOW/k9n625pjpuMzVfeI+ZBUSDT3MVIFDSPfSUgIl9FqUftxcUXInvFah79hE1c9abD+Q==} cpu: [ppc64] os: [linux] - '@rollup/rollup-linux-riscv64-gnu@4.24.0': - resolution: {integrity: sha512-M3Dg4hlwuntUCdzU7KjYqbbd+BLq3JMAOhCKdBE3TcMGMZbKkDdJ5ivNdehOssMCIokNHFOsv7DO4rlEOfyKpg==} + '@rollup/rollup-linux-riscv64-gnu@4.40.2': + resolution: {integrity: sha512-QNU7BFHEvHMp2ESSY3SozIkBPaPBDTsfVNGx3Xhv+TdvWXFGOSH2NJvhD1zKAT6AyuuErJgbdvaJhYVhVqrWTg==} cpu: [riscv64] os: [linux] - '@rollup/rollup-linux-riscv64-gnu@4.31.0': - resolution: {integrity: sha512-wal2Tc8O5lMBtoePLBYRKj2CImUCJ4UNGJlLwspx7QApYny7K1cUYlzQ/4IGQBLmm+y0RS7dwc3TDO/pmcneTw==} + '@rollup/rollup-linux-riscv64-musl@4.40.2': + resolution: {integrity: sha512-5W6vNYkhgfh7URiXTO1E9a0cy4fSgfE4+Hl5agb/U1sa0kjOLMLC1wObxwKxecE17j0URxuTrYZZME4/VH57Hg==} cpu: [riscv64] os: [linux] - '@rollup/rollup-linux-s390x-gnu@4.24.0': - resolution: {integrity: sha512-mjBaoo4ocxJppTorZVKWFpy1bfFj9FeCMJqzlMQGjpNPY9JwQi7OuS1axzNIk0nMX6jSgy6ZURDZ2w0QW6D56g==} + '@rollup/rollup-linux-s390x-gnu@4.40.2': + resolution: {integrity: sha512-B7LKIz+0+p348JoAL4X/YxGx9zOx3sR+o6Hj15Y3aaApNfAshK8+mWZEf759DXfRLeL2vg5LYJBB7DdcleYCoQ==} cpu: [s390x] os: [linux] - '@rollup/rollup-linux-s390x-gnu@4.31.0': - resolution: {integrity: sha512-O1o5EUI0+RRMkK9wiTVpk2tyzXdXefHtRTIjBbmFREmNMy7pFeYXCFGbhKFwISA3UOExlo5GGUuuj3oMKdK6JQ==} - cpu: [s390x] - os: [linux] - - '@rollup/rollup-linux-x64-gnu@4.24.0': - resolution: {integrity: sha512-ZXFk7M72R0YYFN5q13niV0B7G8/5dcQ9JDp8keJSfr3GoZeXEoMHP/HlvqROA3OMbMdfr19IjCeNAnPUG93b6A==} - cpu: [x64] - os: [linux] - - '@rollup/rollup-linux-x64-gnu@4.31.0': - resolution: {integrity: sha512-zSoHl356vKnNxwOWnLd60ixHNPRBglxpv2g7q0Cd3Pmr561gf0HiAcUBRL3S1vPqRC17Zo2CX/9cPkqTIiai1g==} - cpu: [x64] - os: [linux] - - '@rollup/rollup-linux-x64-musl@4.24.0': - resolution: {integrity: sha512-w1i+L7kAXZNdYl+vFvzSZy8Y1arS7vMgIy8wusXJzRrPyof5LAb02KGr1PD2EkRcl73kHulIID0M501lN+vobQ==} + '@rollup/rollup-linux-x64-gnu@4.40.2': + resolution: {integrity: sha512-lG7Xa+BmBNwpjmVUbmyKxdQJ3Q6whHjMjzQplOs5Z+Gj7mxPtWakGHqzMqNER68G67kmCX9qX57aRsW5V0VOng==} cpu: [x64] os: [linux] - '@rollup/rollup-linux-x64-musl@4.31.0': - resolution: {integrity: sha512-ypB/HMtcSGhKUQNiFwqgdclWNRrAYDH8iMYH4etw/ZlGwiTVxBz2tDrGRrPlfZu6QjXwtd+C3Zib5pFqID97ZA==} + '@rollup/rollup-linux-x64-musl@4.40.2': + resolution: {integrity: sha512-tD46wKHd+KJvsmije4bUskNuvWKFcTOIM9tZ/RrmIvcXnbi0YK/cKS9FzFtAm7Oxi2EhV5N2OpfFB348vSQRXA==} cpu: [x64] os: [linux] - '@rollup/rollup-win32-arm64-msvc@4.24.0': - resolution: {integrity: sha512-VXBrnPWgBpVDCVY6XF3LEW0pOU51KbaHhccHw6AS6vBWIC60eqsH19DAeeObl+g8nKAz04QFdl/Cefta0xQtUQ==} + '@rollup/rollup-win32-arm64-msvc@4.40.2': + resolution: {integrity: sha512-Bjv/HG8RRWLNkXwQQemdsWw4Mg+IJ29LK+bJPW2SCzPKOUaMmPEppQlu/Fqk1d7+DX3V7JbFdbkh/NMmurT6Pg==} cpu: [arm64] os: [win32] - '@rollup/rollup-win32-arm64-msvc@4.31.0': - resolution: {integrity: sha512-JuhN2xdI/m8Hr+aVO3vspO7OQfUFO6bKLIRTAy0U15vmWjnZDLrEgCZ2s6+scAYaQVpYSh9tZtRijApw9IXyMw==} - cpu: [arm64] - os: [win32] - - '@rollup/rollup-win32-ia32-msvc@4.24.0': - resolution: {integrity: sha512-xrNcGDU0OxVcPTH/8n/ShH4UevZxKIO6HJFK0e15XItZP2UcaiLFd5kiX7hJnqCbSztUF8Qot+JWBC/QXRPYWQ==} + '@rollup/rollup-win32-ia32-msvc@4.40.2': + resolution: {integrity: sha512-dt1llVSGEsGKvzeIO76HToiYPNPYPkmjhMHhP00T9S4rDern8P2ZWvWAQUEJ+R1UdMWJ/42i/QqJ2WV765GZcA==} cpu: [ia32] os: [win32] - '@rollup/rollup-win32-ia32-msvc@4.31.0': - resolution: {integrity: sha512-U1xZZXYkvdf5MIWmftU8wrM5PPXzyaY1nGCI4KI4BFfoZxHamsIe+BtnPLIvvPykvQWlVbqUXdLa4aJUuilwLQ==} - cpu: [ia32] - os: [win32] - - '@rollup/rollup-win32-x64-msvc@4.24.0': - resolution: {integrity: sha512-fbMkAF7fufku0N2dE5TBXcNlg0pt0cJue4xBRE2Qc5Vqikxr4VCgKj/ht6SMdFcOacVA9rqF70APJ8RN/4vMJw==} - cpu: [x64] - os: [win32] - - '@rollup/rollup-win32-x64-msvc@4.31.0': - resolution: {integrity: sha512-ul8rnCsUumNln5YWwz0ted2ZHFhzhRRnkpBZ+YRuHoRAlUji9KChpOUOndY7uykrPEPXVbHLlsdo6v5yXo/TXw==} + '@rollup/rollup-win32-x64-msvc@4.40.2': + resolution: {integrity: sha512-bwspbWB04XJpeElvsp+DCylKfF4trJDa2Y9Go8O6A7YLX2LIKGcNK/CYImJN6ZP4DcuOHB4Utl3iCbnR62DudA==} cpu: [x64] os: [win32] - '@sveltejs/adapter-node@5.2.0': - resolution: {integrity: sha512-HVZoei2078XSyPmvdTHE03VXDUD0ytTvMuMHMQP0j6zX4nPDpCcKrgvU7baEblMeCCMdM/shQvstFxOJPQKlUQ==} + '@sveltejs/adapter-node@5.2.12': + resolution: {integrity: sha512-0bp4Yb3jKIEcZWVcJC/L1xXp9zzJS4hDwfb4VITAkfT4OVdkspSHsx7YhqJDbb2hgLl6R9Vs7VQR+fqIVOxPUQ==} peerDependencies: '@sveltejs/kit': ^2.4.0 - '@sveltejs/adapter-vercel@5.4.1': - resolution: {integrity: sha512-JLcD1OgMnu9lQ8EssxVGxv7w0waWuyVzItTT1eqIH98Krufd9qfr1uC9zgo82z3dJ9v1AfPEbvIX5tonceg7XQ==} + '@sveltejs/adapter-vercel@5.7.0': + resolution: {integrity: sha512-Bd/loKugyr12I576NaktLzIHa0PinS638wuWgVq4ctPg/qmkeU459jurWjs3NiRN/pbBpXOlk8i8HXgQF+dsUg==} peerDependencies: '@sveltejs/kit': ^2.4.0 - '@sveltejs/kit@2.8.3': - resolution: {integrity: sha512-DVBVwugfzzn0SxKA+eAmKqcZ7aHZROCHxH7/pyrOi+HLtQ721eEsctGb9MkhEuqj6q/9S/OFYdn37vdxzFPdvw==} + '@sveltejs/kit@2.20.7': + resolution: {integrity: sha512-dVbLMubpJJSLI4OYB+yWYNHGAhgc2bVevWuBjDj8jFUXIJOAnLwYP3vsmtcgoxNGUXoq0rHS5f7MFCsryb6nzg==} engines: {node: '>=18.13'} hasBin: true peerDependencies: - '@sveltejs/vite-plugin-svelte': ^3.0.0 || ^4.0.0-next.1 + '@sveltejs/vite-plugin-svelte': ^3.0.0 || ^4.0.0-next.1 || ^5.0.0 svelte: ^4.0.0 || ^5.0.0-next.0 - vite: ^5.0.3 + vite: ^5.0.3 || ^6.0.0 '@sveltejs/vite-plugin-svelte-inspector@2.1.0': resolution: {integrity: sha512-9QX28IymvBlSCqsCll5t0kQVxipsfhFFL+L2t3nTWfXnddYwxBuAEtTtlaVQpRz9c37BhJjltSeY4AJSC03SSg==} @@ -765,35 +843,32 @@ packages: svelte: ^4.0.0 || ^5.0.0-next.0 vite: ^5.0.0 - '@sveltejs/vite-plugin-svelte@3.1.1': - resolution: {integrity: sha512-rimpFEAboBBHIlzISibg94iP09k/KYdHgVhJlcsTfn7KMBhc70jFX/GRWkRdFCc2fdnk+4+Bdfej23cMDnJS6A==} + '@sveltejs/vite-plugin-svelte@3.1.2': + resolution: {integrity: sha512-Txsm1tJvtiYeLUVRNqxZGKR/mI+CzuIQuc2gn+YCs9rMTowpNZ2Nqt53JdL8KF9bLhAf2ruR/dr9eZCwdTriRA==} engines: {node: ^18.0.0 || >=20} peerDependencies: svelte: ^4.0.0 || ^5.0.0-next.0 vite: ^5.0.0 - '@tailwindcss/typography@0.5.13': - resolution: {integrity: sha512-ADGcJ8dX21dVVHIwTRgzrcunY6YY9uSlAHHGVKvkA+vLc5qLwEszvKts40lx7z0qc4clpjclwLeK5rVCV2P/uw==} + '@tailwindcss/typography@0.5.16': + resolution: {integrity: sha512-0wDLwCVF5V3x3b1SGXPCDcdsbDHMBe+lkFzBRaHeLvNi+nrrnZ1lA18u+OTWO8iSWU2GxUOCvlXtDuqftc1oiA==} peerDependencies: - tailwindcss: '>=3.0.0 || insiders' + tailwindcss: '>=3.0.0 || insiders || >=4.0.0-alpha.20 || >=4.0.0-beta.1' '@types/cookie@0.6.0': resolution: {integrity: sha512-4Kh9a6B2bQciAhf7FSuMRRkUWecJgJu9nPnx3yzpsfXX/c50REIqpHY4C82bXP90qrLtXtkDxTZosYO3UpOwlA==} - '@types/estree@1.0.6': - resolution: {integrity: sha512-AYnb1nQyY49te+VRAVgmzfcgjYS91mY5P0TKUDCLEM+gNnA+3T6rWITXRLYCpahpqSQbN5cE+gHpnPyXjHWxcw==} + '@types/estree@1.0.7': + resolution: {integrity: sha512-w28IoSUCJpidD/TGviZwwMJckNESJZXFu7NBZ5YJ4mEUnNraUn9Pm8HSZm/jDF1pDWYKspWE7oVphigUPRakIQ==} '@types/geojson-vt@3.2.5': resolution: {integrity: sha512-qDO7wqtprzlpe8FfQ//ClPV9xiuoh2nkIgiouIptON9w5jvD/fA4szvP9GBlDVdJ5dldAl0kX/sy3URbWwLx0g==} - '@types/geojson@7946.0.14': - resolution: {integrity: sha512-WCfD5Ht3ZesJUsONdhvm84dmzWOiOzOAqOncN0++w0lBw1o8OuDNJF2McvvCef/yBqb/HYRahp1BYtODFQ8bRg==} - - '@types/junit-report-builder@3.0.2': - resolution: {integrity: sha512-R5M+SYhMbwBeQcNXYWNCZkl09vkVfAtcPIaCGdzIkkbeaTrVbGQ7HVgi4s+EmM/M1K4ZuWQH0jGcvMvNePfxYA==} + '@types/geojson@7946.0.16': + resolution: {integrity: sha512-6C8nqWur3j98U6+lXDfTUWIfgvZU+EumvpHKcYjujKH7woYyLj2sUmff0tRhrqM7BohUw7Pz3ZB1jj2gW9Fvmg==} - '@types/leaflet@1.9.12': - resolution: {integrity: sha512-BK7XS+NyRI291HIo0HCfE18Lp8oA30H1gpi1tf0mF3TgiCEzanQjOqNZ4x126SXzzi2oNSZhZ5axJp1k0iM6jg==} + '@types/leaflet@1.9.17': + resolution: {integrity: sha512-IJ4K6t7I3Fh5qXbQ1uwL3CFVbCi6haW9+53oLWgdKlLP7EaS21byWFJxxqOx9y8I0AP0actXSJLVMbyvxhkUTA==} '@types/mapbox__point-geometry@0.1.4': resolution: {integrity: sha512-mUWlSxAmYLfwnRBmgYV86tgYmMIICX4kza8YnE/eIlywGe2XoOxlpVnXWwir92xRLjwyarqwpu2EJKD2pk0IUA==} @@ -801,8 +876,8 @@ packages: '@types/mapbox__vector-tile@1.3.4': resolution: {integrity: sha512-bpd8dRn9pr6xKvuEBQup8pwQfD4VUyqO/2deGjfpe6AwC8YRlyEipvefyRJUSiCJTZuCb8Pl1ciVV5ekqJ96Bg==} - '@types/node@22.5.4': - resolution: {integrity: sha512-FDuKUJQm/ju9fT/SeX/6+gBzoPzlVCzfzmGkwKvRHQVxi4BntVbyIwf6a4Xn62mrvndLiml6z/UBXIdEVjQLXg==} + '@types/node@22.15.2': + resolution: {integrity: sha512-uKXqKN9beGoMdBfcaTY1ecwz6ctxuJAcUlwE55938g0ZJ8lRxwAZqRz2AJ4pzpt5dHdTPMB863UZ0ESiFUcP7A==} '@types/pbf@3.0.5': resolution: {integrity: sha512-j3pOPiEcWZ34R6a6mN07mUkM4o4Lwf6hPNt8eilOeZhTFbxFXmKhvXl9Y28jotFPaI1bpPDJsbCprUoNke6OrA==} @@ -822,38 +897,39 @@ packages: '@types/trusted-types@2.0.7': resolution: {integrity: sha512-ScaPdn1dQczgbl0QFTeTOmVHFULt394XJgOQNoyVhZ6r2vLnMLJfBPd53SB52T/3G36VI1/g2MZaX0cwDuXsfw==} - '@vercel/nft@0.27.2': - resolution: {integrity: sha512-7LeioS1yE5hwPpQfD3DdH04tuugKjo5KrJk3yK5kAI3Lh76iSsK/ezoFQfzuT08X3ZASQOd1y9ePjLNI9+TxTQ==} - engines: {node: '>=16'} + '@vercel/nft@0.29.2': + resolution: {integrity: sha512-A/Si4mrTkQqJ6EXJKv5EYCDQ3NL6nJXxG8VGXePsaiQigsomHYQC9xSpX8qGk7AEZk4b1ssbYIqJ0ISQQ7bfcA==} + engines: {node: '>=18'} hasBin: true '@xmldom/xmldom@0.8.10': resolution: {integrity: sha512-2WALfTl4xo2SkGCYRt6rDTFfk9R1czmBvUQy12gK2KuRKIpWEhcbbzy8EZXtz/jkRqHX8bFEc6FC1HjX4TUWYw==} engines: {node: '>=10.0.0'} - abbrev@1.1.1: - resolution: {integrity: sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==} + abbrev@3.0.1: + resolution: {integrity: sha512-AO2ac6pjRB3SJmGJo+v5/aK6Omggp6fsLrs6wN9bd35ulu4cCwaAU9+7ZhXjeqHVkaHThLuzH0nZr0YpCDhygg==} + engines: {node: ^18.17.0 || >=20.5.0} acorn-import-attributes@1.9.5: resolution: {integrity: sha512-n02Vykv5uA3eHGM/Z2dQrcD56kL8TyDb2p1+0P83PClMnC/nc+anbQRhIOWnSq4Ke/KvDPrY3C9hDtC/A3eHnQ==} peerDependencies: acorn: ^8 - acorn@8.12.0: - resolution: {integrity: sha512-RTvkC4w+KNXrM39/lWCUaG0IbRkWdCv7W/IOW9oU6SawyxulvkQy5HQPVTKxEjczcUvapcrw3cFx/60VN/NRNw==} + acorn@8.14.1: + resolution: {integrity: sha512-OvQ/2pUDKmgfCg++xsTX1wGxfTaszcHVcTctW4UJB4hibJx2HXxxO5UmVgyjMa+ZDsiaf5wWLXYpRWMmBI0QHg==} engines: {node: '>=0.4.0'} hasBin: true - agent-base@6.0.2: - resolution: {integrity: sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==} - engines: {node: '>= 6.0.0'} + agent-base@7.1.3: + resolution: {integrity: sha512-jRR5wdylq8CkOe6hei19GGZnxM6rBGwFl3Bg0YItGDimvjGtAvdZk4Pu6Cl4u4Igsws4a1fd1Vq3ezrhn4KmFw==} + engines: {node: '>= 14'} ansi-regex@5.0.1: resolution: {integrity: sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==} engines: {node: '>=8'} - ansi-regex@6.0.1: - resolution: {integrity: sha512-n5M855fKb2SsfMIiFFoVrABHJC8QtHwVx+mHWP3QcEqBHYienj5dHSgjbxtC0WEZXYt4wcD6zrQElDPhFuZgfA==} + ansi-regex@6.1.0: + resolution: {integrity: sha512-7HSX4QQb4CspciLpVFwyRe79O3xsIZDDLER21kERQ71oaPodF8jL725AgJMFAYbooIqolJoRLuM81SpeUkpkvA==} engines: {node: '>=12'} ansi-styles@4.3.0: @@ -871,40 +947,26 @@ packages: resolution: {integrity: sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==} engines: {node: '>= 8'} - aproba@2.0.0: - resolution: {integrity: sha512-lYe4Gx7QT+MKGbDsA+Z+he/Wtef0BiwDOlK/XkBrdfsh9J/jPPXbX0tE9x9cl27Tmu5gg3QUbUrQYa/y+KOHPQ==} - - are-we-there-yet@2.0.0: - resolution: {integrity: sha512-Ci/qENmwHnsYo9xKIcUJN5LeDKdJ6R1Z1j9V/J5wyq8nh/mYPEpIKJbBZXtZjG04HiK7zV/p6Vs9952MrMeUIw==} - engines: {node: '>=10'} - deprecated: This package is no longer supported. - arg@5.0.2: resolution: {integrity: sha512-PYjyFOLKQ9y57JvQ6QLo8dAgNqswh8M1RMJYdQduT6xbWSgK36P/Z/v+p888pM69jMMfS8Xd8F6I1kQ/I9HUGg==} - aria-query@5.3.0: - resolution: {integrity: sha512-b0P0sZPKtyu8HkeRAfCq0IfURZK+SuwMjY1UXGBU27wpAiTwQAIlq56IbIO+ytk/JjS1fMR14ee5WBBfKi5J6A==} - - arr-union@3.1.0: - resolution: {integrity: sha512-sKpyeERZ02v1FeCZT8lrfJq5u6goHCtpTAzPwJYe7c8SPFOboNjNg1vz2L4VTn9T4PQxEx13TbXLmYUcS6Ug7Q==} - engines: {node: '>=0.10.0'} - - assign-symbols@1.0.0: - resolution: {integrity: sha512-Q+JC7Whu8HhmTdBph/Tq59IoRtoy6KAm5zzPv00WdujX82lbAL8K7WVjne7vdCsAmbF4AYaDOPyO3k0kl8qIrw==} - engines: {node: '>=0.10.0'} + aria-query@5.3.2: + resolution: {integrity: sha512-COROpnaoap1E2F000S62r6A60uHZnmlvomhfyT2DlTcrY1OrBKn2UhH7qn5wTC9zMvD0AY7csdPSNwKP+7WiQw==} + engines: {node: '>= 0.4'} async-sema@3.1.1: resolution: {integrity: sha512-tLRNUXati5MFePdAk8dw7Qt7DpxPB60ofAgn8WRhW6a2rcimZnYBP9oxHiv0OHy+Wz7kPMG+t4LGdt31+4EmGg==} - autoprefixer@10.4.19: - resolution: {integrity: sha512-BaENR2+zBZ8xXhM4pUaKUxlVdxZ0EZhjvbopwnXmxRUfqDmwSpC2lAi/QXvx7NRdPCo1WKEcEF6mV64si1z4Ew==} + autoprefixer@10.4.21: + resolution: {integrity: sha512-O+A6LWV5LDHSJD3LjHYoNi4VLsj/Whi7k6zG12xTYaU4cQ8oxQGckXNX8cRHK5yOZ/ppVHe0ZBXGzSV9jXdVbQ==} engines: {node: ^10 || ^12 || >=14} hasBin: true peerDependencies: postcss: ^8.1.0 - axobject-query@4.0.0: - resolution: {integrity: sha512-+60uv1hiVFhHZeO+Lz0RYzsVHy5Wr1ayX0mwda9KPDVLNJgZ1T9Ny7VmFbLDzxsH0D87I86vgj3gFrjTJUYznw==} + axobject-query@4.1.0: + resolution: {integrity: sha512-qIj0G9wZbMGNLjLmg1PT6v2mE9AH2zlnADJD/2tC6E00hgmhUOfEB6greHPAfLRSufHqROIUTkw6E+M3lH0PTQ==} + engines: {node: '>= 0.4'} balanced-match@1.0.2: resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==} @@ -926,8 +988,8 @@ packages: resolution: {integrity: sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==} engines: {node: '>=8'} - browserslist@4.23.1: - resolution: {integrity: sha512-TUfofFo/KsK/bWZ9TWQ5O26tsWW4Uhmt8IYklbnUa70udB6P2wA7w7o4PY4muaEPBQaAX+CEnmmIA41NVHtPVw==} + browserslist@4.24.4: + resolution: {integrity: sha512-KDi1Ny1gSePi1vm0q4oxSF8b4DR44GF4BbmS2YdhPLOEqd8pDviZOGH/GsmRwoWJ2+5Lr085X7naowMwKHDG1A==} engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7} hasBin: true @@ -938,23 +1000,6 @@ packages: buffer-from@1.1.2: resolution: {integrity: sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==} - builtin-modules@3.3.0: - resolution: {integrity: sha512-zhaCDicdLuWN5UbN5IMnFqNMhNfo919sH85y2/ea+5Yg9TsTkeZxpL+JLbp6cgYFS4sRLp3YV4S6yDuqVWHYOw==} - engines: {node: '>=6'} - - bytewise-core@1.2.3: - resolution: {integrity: sha512-nZD//kc78OOxeYtRlVk8/zXqTB4gf/nlguL1ggWA8FuchMyOxcyHR4QPQZMUmA7czC+YnaBrPUCubqAWe50DaA==} - - bytewise@1.1.0: - resolution: {integrity: sha512-rHuuseJ9iQ0na6UDhnrRVDh8YnWVlU6xM3VH6q/+yHDeUH2zIhUzP+2/h3LIrhLDBtTqzWpE3p3tP/boefskKQ==} - - call-me-maybe@1.0.2: - resolution: {integrity: sha512-HpX65o1Hnr9HH25ojC1YGs7HCQLq0GCOibSaWER0eNpgJ/Z1MZv2mTc7+xh6WOPxbRVcmgbv4hGU+uSQ/2xFZQ==} - - callsites@3.1.0: - resolution: {integrity: sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==} - engines: {node: '>=6'} - camelcase-css@2.0.1: resolution: {integrity: sha512-QOSvevhslijgYwRx6Rv7zKdMF8lbRmx+uQGx2+vDc+KI/eBnsy9kit5aj23AgGu3pa4t9AgwbnXWqS+iOY+2aA==} engines: {node: '>= 6'} @@ -963,16 +1008,16 @@ packages: resolution: {integrity: sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==} engines: {node: '>=6'} - caniuse-lite@1.0.30001688: - resolution: {integrity: sha512-Nmqpru91cuABu/DTCXbM2NSRHzM2uVHfPnhJ/1zEAJx/ILBRVmz3pzH4N7DZqbdG0gWClsCC05Oj0mJ/1AWMbA==} + caniuse-lite@1.0.30001715: + resolution: {integrity: sha512-7ptkFGMm2OAOgvZpwgA4yjQ5SQbrNVGdRjzH0pBdy1Fasvcr+KAeECmbCAECzTuDuoX0FCY8KzUxjf9+9kfZEw==} chokidar@3.6.0: resolution: {integrity: sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==} engines: {node: '>= 8.10.0'} - chownr@2.0.0: - resolution: {integrity: sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ==} - engines: {node: '>=10'} + chownr@3.0.0: + resolution: {integrity: sha512-+IxzY9BZOQd/XuYPRmrvEVjF/nqj5kgT4kEq7VofrDoM1MxoRjEWkrCC3EtLi59TVawxTAn+orJwFQcrqEN1+g==} + engines: {node: '>=18'} cli-color@2.0.4: resolution: {integrity: sha512-zlnpg0jNcibNrO7GG9IeHH7maWFeCz+Ja1wx/7tZNU5ASSSSZ+/qZciM0/LHCYxSdqv5h2sdbQ/PXYdOuetXvA==} @@ -991,10 +1036,6 @@ packages: color-name@1.1.4: resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==} - color-support@1.1.3: - resolution: {integrity: sha512-qiBjkpbMLO/HL68y+lh4q0/O1MZFj2RX6X/KmMa3+gJD3z+WwI1ZzDHysvqHGS3mP6mznPckpXmw1nI9cJjyRg==} - hasBin: true - commander@4.1.1: resolution: {integrity: sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA==} engines: {node: '>= 6'} @@ -1009,11 +1050,15 @@ packages: resolution: {integrity: sha512-MWufYdFw53ccGjCA+Ol7XJYpAlW6/prSMzuPOTRnJGcGzuhLn4Scrz7qf6o8bROZ514ltazcIFJZevcfbo0x7A==} engines: {'0': node >= 6.0} - confbox@0.1.7: - resolution: {integrity: sha512-uJcB/FKZtBMCJpK8MQji6bJHgu1tixKPxRLeGkNzBoOZzpnZUJm0jm2/sBDWcuBx1dYgxV4JU+g5hmNxCyAmdA==} + confbox@0.1.8: + resolution: {integrity: sha512-RMtmw0iFkeR4YV+fUOSucriAQNb9g8zFR52MWCtl+cCZOFRNL6zeB395vPzFhEjjn4fMxXudmELnl/KF/WrK6w==} - console-control-strings@1.1.0: - resolution: {integrity: sha512-ty/fTekppD2fIwRvnZAVdeOiGd1c7YXEixbgJTNzqcxJWKQnjJ/V1bNEEE6hygpM3WjwHFUVK6HTjWSzV4a8sQ==} + confbox@0.2.2: + resolution: {integrity: sha512-1NB+BKqhtNipMsov4xI/NnhCKp9XG9NamYp5PVm9klAT0fsrNPjaFICsCFhNhwZJKNh7zB/3q8qXz0E9oaMNtQ==} + + consola@3.4.2: + resolution: {integrity: sha512-5IKcdX0nnYavi6G7TtOhwkYzyjfJlatbjMjuLSfE2kYT5pMDOilZ4OvMhi637CcDICTmz3wARPoyhqyX1Y+XvA==} + engines: {node: ^14.18.0 || >=16.10.0} cookie@0.6.0: resolution: {integrity: sha512-U71cyTamuh1CRNCfpGY6to28lxvNwPG4Guz/EVjgf3Jmzv0vlDp1atT9eS5dDjMYHucpHbWns6Lwf3BKz6svdw==} @@ -1051,12 +1096,12 @@ packages: resolution: {integrity: sha512-MOqHvMWF9/9MX6nza0KgvFH4HpMU0EF5uUDXqX/BtxtU8NfB0QzRtJ8Oe/6SuS4kbhyzVJwjd97EA4PKrzJ8bw==} engines: {node: '>=0.12'} - daisyui@4.12.6: - resolution: {integrity: sha512-Tz/rvi2ws7+7uh51JgGpsRqnASwI13t6Sz53ePaGkhLzhr4SQI4wwNxSypE8lj/d4gl/+lbHK1phIKUo+d2YNw==} + daisyui@4.12.24: + resolution: {integrity: sha512-JYg9fhQHOfXyLadrBrEqCDM6D5dWCSSiM6eTNCRrBRzx/VlOCrLS8eDfIw9RVvs64v2mJdLooKXY8EwQzoszAA==} engines: {node: '>=16.9.0'} - debug@4.3.5: - resolution: {integrity: sha512-pt0bNEmneDIvdL1Xsd9oDQ/wrQRkXDT4AUWlNZNPKvW5x/jyO9VFXkJUP07vQ2upmw5PlaITaPKc31jK13V+jg==} + debug@4.4.0: + resolution: {integrity: sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA==} engines: {node: '>=6.0'} peerDependencies: supports-color: '*' @@ -1068,13 +1113,13 @@ packages: resolution: {integrity: sha512-z2S+W9X73hAUUki+N+9Za2lBlun89zigOyGrsax+KUQ6wKW4ZoWpEYBkGhQjwAjjDCkWxhY0VKEhk8wzY7F5cA==} engines: {node: '>=0.10.0'} + decimal.js@10.5.0: + resolution: {integrity: sha512-8vDa8Qxvr/+d94hSh5P3IJwI5t8/c0KsMp+g8bNw9cY2icONa5aPfvKeieW1WlG0WQYwwhJ7mjui2xtiePQSXw==} + deepmerge@4.3.1: resolution: {integrity: sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==} engines: {node: '>=0.10.0'} - delegates@1.0.0: - resolution: {integrity: sha512-bd2L678uiWATM6m5Z1VzNCErI3jiGzt6HGY8OVICs40JQq/HALfbyNJmp0UDakEY4pMMaN0Ly5om/B1VI/+xfQ==} - dequal@2.0.3: resolution: {integrity: sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==} engines: {node: '>=6'} @@ -1083,8 +1128,8 @@ packages: resolution: {integrity: sha512-reYkTUJAZb9gUuZ2RvVCNhVHdg62RHnJ7WJl8ftMi4diZ6NWlciOzQN88pUhSELEwflJht4oQDv0F0BMlwaYtA==} engines: {node: '>=8'} - detect-libc@2.0.3: - resolution: {integrity: sha512-bwy0MGW55bG41VqxxypOsdSdGqLwXPI/focwgTYCFMbdUiBAxLg9CFzG08sz2aqzknwiX7Hkl0bQENjg8iLByw==} + detect-libc@2.0.4: + resolution: {integrity: sha512-3UDv+G9CsCKO1WKMGw9fwq/SWJYbI0c5Y7LU1AXYoDdbhE2AHQ6N6Nb34sG8Fj7T5APy8qXDCKuuIHd1BR0tVA==} engines: {node: '>=8'} devalue@5.1.1: @@ -1099,20 +1144,20 @@ packages: dlv@1.1.3: resolution: {integrity: sha512-+HlytyjlPKnIG8XuRG8WvmBP8xs8P71y+SKKS6ZXWoEgLuePxtDoUEiH7WkdePWrQ5JBpE6aoVqfZfJUQkjXwA==} - dompurify@3.2.4: - resolution: {integrity: sha512-ysFSFEDVduQpyhzAob/kkuJjf5zWkZD8/A9ywSp1byueyuCfHamrCBa14/Oc2iiB0e51B+NpxSl5gmzn+Ms/mg==} + dompurify@3.2.5: + resolution: {integrity: sha512-mLPd29uoRe9HpvwP2TxClGQBzGXeEC/we/q+bFlmPPmj2p2Ugl3r6ATu/UU1v77DXNcehiBg9zsr1dREyA/dJQ==} - earcut@2.2.4: - resolution: {integrity: sha512-/pjZsA1b4RPHbeWZQn66SWS8nZZWLQQ23oE3Eam7aroEFGEvwKAsJfZ9ytiEMycfzXWpca4FA9QIOehf7PocBQ==} + earcut@3.0.1: + resolution: {integrity: sha512-0l1/0gOjESMeQyYaK5IDiPNvFeu93Z/cO0TjZh9eZ1vyCtZnA7KMZ8rQggpsJHIbGSdrqYq9OhuveadOVHCshw==} eastasianwidth@0.2.0: resolution: {integrity: sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==} - electron-to-chromium@1.4.810: - resolution: {integrity: sha512-Kaxhu4T7SJGpRQx99tq216gCq2nMxJo+uuT6uzz9l8TVN2stL7M06MIIXAtr9jsrLs2Glflgf2vMQRepxawOdQ==} + electron-to-chromium@1.5.143: + resolution: {integrity: sha512-QqklJMOFBMqe46k8iIOwA9l2hz57V2OKMmP5eSWcUvwx+mASAsbU+wkF1pHjn9ZVSBPrsYWr4/W/95y5SwYg2g==} - emoji-picker-element@1.26.0: - resolution: {integrity: sha512-IcffFc+LNymYScmMuxOJooZulOCOACGc1Xvj+s7XeKqpc+0EoZfWrV9o4rBjEiuM7XjsgcEjD+m5DHg0aIfnnA==} + emoji-picker-element@1.26.3: + resolution: {integrity: sha512-fOMG44d/3OqTe1pPqlu5H4ZtWg7gK4Le6Bt24JTKtDyce5+EO3Mo8WA95cKHbPSsSsg7ehM12M1x3Y6U6fgvTQ==} emoji-regex@8.0.0: resolution: {integrity: sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==} @@ -1147,12 +1192,17 @@ packages: engines: {node: '>=12'} hasBin: true - escalade@3.1.2: - resolution: {integrity: sha512-ErCHMCae19vR8vQGe50xIsVomy19rg6gFu3+r3jkEO46suLMWBksvVyoGgQV+jOfl84ZSOSlmv6Gxa89PmTGmA==} + esbuild@0.24.2: + resolution: {integrity: sha512-+9egpBW8I3CD5XPe0n6BfT5fxLzxrlDzqydF3aviG+9ni1lDC/OvMHcxqEFV0+LANZG5R1bFMWfUrjVsdwxJvA==} + engines: {node: '>=18'} + hasBin: true + + escalade@3.2.0: + resolution: {integrity: sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==} engines: {node: '>=6'} - esm-env@1.0.0: - resolution: {integrity: sha512-Cf6VksWPsTuW01vU9Mk/3vRue91Zevka5SjyNf3nEpokFRuqt/KjUQoGAwq9qMmhpLTHmXzSIrFRw8zxWzmFBA==} + esm-env@1.2.2: + resolution: {integrity: sha512-Epxrv+Nr/CaL4ZcFGPJIYLWFom+YeV1DqMLHJoEd9SYRxNbaFruBwfEX/kkHUJf55j2+TUbmDcmuilbP1TmXHA==} esniff@2.0.1: resolution: {integrity: sha512-kTUIGKQ/mDPFoJ0oVfcmyJn4iBDRptjNVIzwIFR7tqWXdVI9xfA2RMwY/gbSpJG3lkdWNEjLap/NqVHZiJsdfg==} @@ -1167,30 +1217,29 @@ packages: event-emitter@0.3.5: resolution: {integrity: sha512-D9rRn9y7kLPnJ+hMq7S/nhvoKwwvVJahBi2BPmx3bvbsEdK3W9ii8cBSGjP+72/LnM4n6fo3+dkCX5FeTQruXA==} - execa@5.1.1: - resolution: {integrity: sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==} - engines: {node: '>=10'} + exsolve@1.0.5: + resolution: {integrity: sha512-pz5dvkYYKQ1AHVrgOzBKWeP4u4FRb3a6DNK2ucr0OoNwYIU4QWsJ+NM36LLzORT+z845MzKHHhpXiUF5nvQoJg==} ext@1.7.0: resolution: {integrity: sha512-6hxeJYaL110a9b5TEJSj0gojyHQAmA2ch5Os+ySCiA1QGdS697XWY1pzsrSjqA9LDEEgdB/KypIlR59RcLuHYw==} - extend-shallow@2.0.1: - resolution: {integrity: sha512-zCnTtlxNoAiDc3gqY2aYAWFx7XWWiasuF2K8Me5WbN8otHKTUKBwjPtNpRs/rbUZm7KxWAaNj7P1a/p52GbVug==} - engines: {node: '>=0.10.0'} - - extend-shallow@3.0.2: - resolution: {integrity: sha512-BwY5b5Ql4+qZoefgMj2NUmx+tehVTH/Kf4k1ZEtOHNFcm2wSxMRo992l6X3TIgni2eZVTZ85xMOjF31fwZAj6Q==} - engines: {node: '>=0.10.0'} - - fast-glob@3.3.2: - resolution: {integrity: sha512-oX2ruAFQwf/Orj8m737Y5adxDQO0LAB7/S5MnxCdTNDd4p6BsyIVsv9JQsATbTSq8KHRpLwIHbVlUNatxd+1Ow==} + fast-glob@3.3.3: + resolution: {integrity: sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==} engines: {node: '>=8.6.0'} fastparse@1.1.2: resolution: {integrity: sha512-483XLLxTVIwWK3QTrMGRqUfUpoOs/0hbQrl2oz4J0pAcm3A3bu84wxTFqGqkJzewCLdME38xJLJAxBABfQT8sQ==} - fastq@1.17.1: - resolution: {integrity: sha512-sRVD3lWVIXWg6By68ZN7vho9a1pQcN/WBFaAAsDDFzlJjvoGx0P8z7V1t72grFJfJhu3YPZBuu25f7Kaw2jN1w==} + fastq@1.19.1: + resolution: {integrity: sha512-GwLTyxkCXjXbxqIhTsMI2Nui8huMPtnxg7krajPJAjnEG/iiOS7i+zCtWGZR9G0NBKbXKh6X9m9UIsYX/N6vvQ==} + + fdir@6.4.4: + resolution: {integrity: sha512-1NZP+GK4GfuAv3PqKvxQRDMjdSRZjnkq7KfhlNrCNNlZ0ygQFpebfrnfnq/W7fpUnAv9aGWmY1zKx7FYL3gwhg==} + peerDependencies: + picomatch: ^3 || ^4 + peerDependenciesMeta: + picomatch: + optional: true fflate@0.8.2: resolution: {integrity: sha512-cPJU47OaAoCbg0pBvzsgpTPhmhqI5eJjh/JIu8tPj5q+T7iLvW/JAYUqmE7KOB4R1ZyEhzBaIQpQpardBF5z8A==} @@ -1206,21 +1255,13 @@ packages: resolution: {integrity: sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==} engines: {node: '>=8'} - find-up@5.0.0: - resolution: {integrity: sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==} - engines: {node: '>=10'} - - foreground-child@3.2.1: - resolution: {integrity: sha512-PXUUyLqrR2XCWICfv6ukppP96sdFwWbNEnfEMt7jNsISjMsvaLNinAHNDYyvkyU+SZG2BTSbT5NjG+vZslfGTA==} + foreground-child@3.3.1: + resolution: {integrity: sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw==} engines: {node: '>=14'} fraction.js@4.3.7: resolution: {integrity: sha512-ZsDfxO51wGAXREY55a7la9LScWpwv9RxIrYABrlvOFBlH/ShPnrtsXeuUIfXKKOVicNxQ+o8JTbJvjS4M89yew==} - fs-minipass@2.1.0: - resolution: {integrity: sha512-V/JgOLFCS+R6Vcq0slCuaeWEdNC3ouDlJMNIsacH2VtALiu9mV4LPrHc5cDl8k5aw6J8jwgWWpiTo5RYhmIzvg==} - engines: {node: '>= 8'} - fs.realpath@1.0.0: resolution: {integrity: sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==} @@ -1232,11 +1273,6 @@ packages: function-bind@1.1.2: resolution: {integrity: sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==} - gauge@3.0.2: - resolution: {integrity: sha512-+5J6MS/5XksCuXq++uFRsnUd7Ovu1XenbeuIuNRJxYWjgQbPuFhT14lAvsWfqfAmnwluf1OwMjz39HjfLPci0Q==} - engines: {node: '>=10'} - deprecated: This package is no longer supported. - geojson-vt@4.0.2: resolution: {integrity: sha512-AV9ROqlNqoZEIJGfm1ncNjEXfkz2hdFlZf0qkVfmkwdKa8vj7H16YUOT81rJw1rdFhyEDlN2Tds91p/glzbl5A==} @@ -1248,10 +1284,6 @@ packages: resolution: {integrity: sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==} engines: {node: '>=10'} - get-value@2.0.6: - resolution: {integrity: sha512-Ln0UQDlxH1BapMu3GPtf7CuYNwRZf2gwCuPqbyG6pB8WfmFpzqcy4xtAaAMUhnNqjMKTiCPZG2oMT3YSx8U2NA==} - engines: {node: '>=0.10.0'} - gl-matrix@3.4.3: resolution: {integrity: sha512-wcCp8vu8FT22BnvKVPjXa/ICBWRq/zjFfdofZy1WSpQZpphblv12/bOQLBC1rMM7SGOFS9ltVmKOHil5+Ml7gA==} @@ -1263,18 +1295,21 @@ packages: resolution: {integrity: sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==} engines: {node: '>=10.13.0'} - glob@10.4.2: - resolution: {integrity: sha512-GwMlUF6PkPo3Gk21UxkCohOv0PLcIXVtKyLlpEI28R/cO/4eNOdmLk3CMW1wROV/WR/EsZOWAfBbBOqYvs88/w==} - engines: {node: '>=16 || 14 >=14.18'} + glob@10.4.5: + resolution: {integrity: sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg==} hasBin: true glob@7.2.3: resolution: {integrity: sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==} deprecated: Glob versions prior to v9 are no longer supported - global-prefix@3.0.0: - resolution: {integrity: sha512-awConJSVCHVGND6x3tmMaKcQvwXLhjdkmomy2W+Goaui8YPgYgXJZewhg3fWC+DlfqqQuWg8AwqjGTD2nAPVWg==} - engines: {node: '>=6'} + global-prefix@4.0.0: + resolution: {integrity: sha512-w0Uf9Y9/nyHinEk5vMJKRie+wa4kR5hmDbEhGGds/kG1PwGLLHKRoNMeJOyCQjjBkANlnScqgzcFwGHgmgLkVA==} + engines: {node: '>=16'} + + globals@15.15.0: + resolution: {integrity: sha512-7ACyT3wmyp3I61S4fG682L0VA2RGD9otkqGJIwNUMF1SWUombIIk+af1unuDYgMm082aHYwD+mzJvv9Iu8dsgg==} + engines: {node: '>=18'} globalyzer@0.1.0: resolution: {integrity: sha512-40oNTM9UfG6aBmuKxk/giHn5nQ8RVz/SS4Ir6zgzOv9/qC3kKZ9v4etGTcJbEl/NyVQH7FGU7d+X1egr57Md2Q==} @@ -1288,28 +1323,17 @@ packages: gsap@3.12.7: resolution: {integrity: sha512-V4GsyVamhmKefvcAKaoy0h6si0xX7ogwBoBSs2CTJwt7luW0oZzC0LhdkyuKV8PJAXr7Yaj8pMjCKD4GJ+eEMg==} - has-unicode@2.0.1: - resolution: {integrity: sha512-8Rf9Y83NBReMnx0gFzA8JImQACstCYWUplepDa9xprwwtmgEZUF0h/i5xSA625zB/I37EtrswSST6OXxwaaIJQ==} - hasown@2.0.2: resolution: {integrity: sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==} engines: {node: '>= 0.4'} - https-proxy-agent@5.0.1: - resolution: {integrity: sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==} - engines: {node: '>= 6'} - - human-signals@2.1.0: - resolution: {integrity: sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==} - engines: {node: '>=10.17.0'} + https-proxy-agent@7.0.6: + resolution: {integrity: sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==} + engines: {node: '>= 14'} ieee754@1.2.1: resolution: {integrity: sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==} - import-fresh@3.3.0: - resolution: {integrity: sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==} - engines: {node: '>=6'} - import-meta-resolve@4.1.0: resolution: {integrity: sha512-I6fiaX09Xivtk+THaMfAwnA3MVA5Big1WHF1Dfx9hFuvNIWpXnorlkzhcQf6ehrqQiiZECRt1poOAkPmer3ruw==} @@ -1320,36 +1344,25 @@ packages: inherits@2.0.4: resolution: {integrity: sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==} - ini@1.3.8: - resolution: {integrity: sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==} + ini@4.1.3: + resolution: {integrity: sha512-X7rqawQBvfdjS10YU1y1YVreA3SsLrW9dX2CewP2EbBJM4ypVNLDkO5y04gejPwKIY9lR+7r9gn3rFPt/kmWFg==} + engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0} internmap@2.0.3: resolution: {integrity: sha512-5Hh7Y1wQbvY5ooGgPbDaL5iYLAPzMTUrjMulskHLH6wnv/A+1q5rgEaiuqEjB+oxGXIVZs1FF+R/KPN3ZSQYYg==} engines: {node: '>=12'} - intl-messageformat@10.7.3: - resolution: {integrity: sha512-AAo/3oyh7ROfPhDuh7DxTTydh97OC+lv7h1Eq5LuHWuLsUMKOhtzTYuyXlUReuwZ9vANDHo4CS1bGRrn7TZRtg==} + intl-messageformat@10.7.16: + resolution: {integrity: sha512-UmdmHUmp5CIKKjSoE10la5yfU+AYJAaiYLsodbjL4lji83JNvgOQUjGaGhGrpFCb0Uh7sl7qfP1IyILa8Z40ug==} is-binary-path@2.1.0: resolution: {integrity: sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==} engines: {node: '>=8'} - is-builtin-module@3.2.1: - resolution: {integrity: sha512-BSLE3HnV2syZ0FK0iMA/yUGplUeMmNz4AW5fnTunbCIqZi4vG3WjJT9FHMy5D69xmAYBHXQhJdALdpwVxV501A==} - engines: {node: '>=6'} - - is-core-module@2.14.0: - resolution: {integrity: sha512-a5dFJih5ZLYlRtDc0dZWP7RiKr6xIKzmn/oAYCDvdLThadVgyJwlaoQPmRtMSpz+rk0OGAgIu+TcM9HUF0fk1A==} + is-core-module@2.16.1: + resolution: {integrity: sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w==} engines: {node: '>= 0.4'} - is-extendable@0.1.1: - resolution: {integrity: sha512-5BMULNob1vgFX6EjQw5izWDxrecWK9AM72rugNr0TFldMOi0fj6Jk+zeKIt0xGj4cEfQIJth4w3OKWOJ4f+AFw==} - engines: {node: '>=0.10.0'} - - is-extendable@1.0.1: - resolution: {integrity: sha512-arnXMxT1hhoKo9k1LZdmlNyJdDDfy2v0fXjFlmok4+i8ul/6WlbVge9bhM74OpNPQPMGUToDtz+KXa1PneJxOA==} - engines: {node: '>=0.10.0'} - is-extglob@2.1.1: resolution: {integrity: sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==} engines: {node: '>=0.10.0'} @@ -1369,36 +1382,27 @@ packages: resolution: {integrity: sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==} engines: {node: '>=0.12.0'} - is-plain-object@2.0.4: - resolution: {integrity: sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og==} - engines: {node: '>=0.10.0'} - is-promise@2.2.2: resolution: {integrity: sha512-+lP4/6lKUBfQjZ2pdxThZvLUAafmZb8OAxFb8XXtiQmS35INgr85hdOGoEs124ez1FCnZJt6jau/T+alh58QFQ==} is-reference@1.2.1: resolution: {integrity: sha512-U82MsXXiFIrjCK4otLT+o2NA2Cd2g5MLoOVXUZjIOhLurrRxpEXzI8O0KZHr3IjLvlAH1kTPYSuqer5T9ZVBKQ==} - is-reference@3.0.2: - resolution: {integrity: sha512-v3rht/LgVcsdZa3O2Nqs+NMowLOxeOm7Ay9+/ARQ2F+qEoANRcqrjAZKGN0v8ymUetZGgkp26LTnGT7H0Qo9Pg==} - - is-stream@2.0.1: - resolution: {integrity: sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==} - engines: {node: '>=8'} + is-reference@3.0.3: + resolution: {integrity: sha512-ixkJoqQvAP88E6wLydLGGqCJsrFUnqoH6HnaczB8XmDH1oaWU+xxdptvikTgaEhtZ53Ky6YXiBuUI2WXLMCwjw==} isexe@2.0.0: resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==} - isobject@3.0.1: - resolution: {integrity: sha512-WhB9zCku7EGTj/HQQRz5aUQEUeoQZH2bWcltRErOpymJ4boYE6wL9Tbr23krRPSZ+C5zqNSrSw+Cc7sZZ4b7vg==} - engines: {node: '>=0.10.0'} + isexe@3.1.1: + resolution: {integrity: sha512-LpB/54B+/2J5hqQ7imZHfdU31OlgQqx7ZicVlkm9kzg9/w8GKLEcFfJl/t7DCEDueOyBAD6zCCwTO6Fzs0NoEQ==} + engines: {node: '>=16'} - jackspeak@3.4.0: - resolution: {integrity: sha512-JVYhQnN59LVPFCEcVa2C3CrEKYacvjRfqIQl+h8oi91aLYQVWRYbxjPcv1bUiUy/kLmQaANrYfNMCO3kuEDHfw==} - engines: {node: '>=14'} + jackspeak@3.4.3: + resolution: {integrity: sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==} - jiti@1.21.6: - resolution: {integrity: sha512-2yTgeWTWzMWkHu6Jp9NKgePDaYHbntiwvYuuJLbbN9vl7DC9DvXKOB2BC3ZZ92D3cvV/aflH0osDfwpHepQ53w==} + jiti@1.21.7: + resolution: {integrity: sha512-/imKNG4EbWNrVjoNC/1H5/9GFy+tqjGBHCaSsN+P2RnPqjsLmv6UD3Ej+Kj8nBWaRAwyk7kK5ZUc+OEatnTR3A==} hasBin: true json-stringify-pretty-compact@4.0.0: @@ -1424,19 +1428,19 @@ packages: kolorist@1.8.0: resolution: {integrity: sha512-Y+60/zizpJ3HRH8DCss+q95yr6145JXZo46OTpFvDZWLfRCE4qChOyk1b26nMaNpfHHgxagk9dXT5OP0Tfe+dQ==} - lilconfig@2.1.0: - resolution: {integrity: sha512-utWOt/GHzuUxnLKxB6dk81RoOeoNeHgbrXiuGk4yyF5qlRz+iIVWu56E2fqGHFrXz0QNUhLB/8nKqvRH66JKGQ==} - engines: {node: '>=10'} - - lilconfig@3.1.2: - resolution: {integrity: sha512-eop+wDAvpItUys0FWkHIKeC9ybYrTGbU41U5K7+bttZZeohvnY7M9dZ5kB21GNWiFT2q1OoPTvncPCgSOVO5ow==} + lilconfig@3.1.3: + resolution: {integrity: sha512-/vlFKAoH5Cgt3Ie+JLhRbwOsCQePABiU3tJ1egGvyQ+33R/vcwM2Zl2QR/LzjsBeItPt3oSVXapn+m4nQDvpzw==} engines: {node: '>=14'} lines-and-columns@1.2.4: resolution: {integrity: sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==} - local-pkg@0.5.0: - resolution: {integrity: sha512-ok6z3qlYyCDS4ZEU27HaU6x/xZa9Whf8jD4ptH5UZTQYZVYeb9bnZ3ojVhiJNLiXK1Hfc0GNbLXcmZ5plLDDBg==} + local-pkg@0.5.1: + resolution: {integrity: sha512-9rrA30MRRP3gBD3HTGnC6cDFpaE1kVDWxWgqWJUN0RvDNAo+Nz/9GxB+nHOH0ifbVFy0hSA1V6vFDvnx54lTEQ==} + engines: {node: '>=14'} + + local-pkg@1.1.1: + resolution: {integrity: sha512-WunYko2W1NcdfAFpuLUoucsgULmgDBRkdxHxWQ7mK0cQqwPiy8E1enjuRBrhLtZkB5iScJ1XIPdhVEFK8aOLSg==} engines: {node: '>=14'} locate-character@3.0.0: @@ -1446,10 +1450,6 @@ packages: resolution: {integrity: sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==} engines: {node: '>=8'} - locate-path@6.0.0: - resolution: {integrity: sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==} - engines: {node: '>=10'} - lodash.castarray@4.4.0: resolution: {integrity: sha512-aVx8ztPv7/2ULbArGJ2Y42bG1mEQ5mGjpdvrbJcJFU3TbYybe+QlLS4pst9zV52ymy2in1KpFPiZnAOATxD4+Q==} @@ -1459,26 +1459,25 @@ packages: lodash.merge@4.6.2: resolution: {integrity: sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==} - lru-cache@10.2.2: - resolution: {integrity: sha512-9hp3Vp2/hFQUiIwKo8XCeFVnrg8Pk3TYNPIR7tJADKi5YfcF7vEaK7avFHTlSy3kOKYaJQaalfEo6YuXdceBOQ==} - engines: {node: 14 || >=16.14} + lru-cache@10.4.3: + resolution: {integrity: sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==} lru-queue@0.1.0: resolution: {integrity: sha512-BpdYkt9EvGl8OfWHDQPISVpcl5xZthb+XPsbELj5AQXxIC8IriDZIQYjBJPEm5rS420sjZ0TLEzRcq5KdBhYrQ==} - magic-string@0.30.10: - resolution: {integrity: sha512-iIRwTIf0QKV3UAnYK4PU8uiEc4SRh5jX0mwpIwETPpHdhVM4f53RSwS/vXvN1JhGX+Cs7B8qIq3d6AH49O5fAQ==} + luxon@3.6.1: + resolution: {integrity: sha512-tJLxrKJhO2ukZ5z0gyjY1zPh3Rh88Ej9P7jNrZiHMUXHae1yvI2imgOZtL1TO8TW6biMMKfTtAOoEJANgtWBMQ==} + engines: {node: '>=12'} - make-dir@3.1.0: - resolution: {integrity: sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==} - engines: {node: '>=8'} + magic-string@0.30.17: + resolution: {integrity: sha512-sNPKHvyjVf7gyjwS4xGTaW/mCnF8wnjtifKBEhxfZ7E/S8tQ0rssrwGNn6q8JH/ohItJfSQp9mBtQYuTlH5QnA==} - maplibre-gl@4.5.0: - resolution: {integrity: sha512-qOS1hn4d/pn2i0uva4S5Oz+fACzTkgBKq+NpwT/Tqzi4MSyzcWNtDELzLUSgWqHfNIkGCl5CZ/w7dtis+t4RCw==} + maplibre-gl@4.7.1: + resolution: {integrity: sha512-lgL7XpIwsgICiL82ITplfS7IGwrB1OJIw/pCvprDp2dhmSSEBgmPzYRvwYYYvJGJD7fxUv1Tvpih4nZ6VrLuaA==} engines: {node: '>=16.14.0', npm: '>=8.1.0'} - marked@15.0.4: - resolution: {integrity: sha512-TCHvDqmb3ZJ4PWG7VEGVgtefA5/euFmsIhxtD0XsBxI39gUSKL81mIRFdt0AiNQozUahd4ke98ZdirExd/vSEw==} + marked@15.0.11: + resolution: {integrity: sha512-1BEXAU2euRCG3xwgLVT1y0xbJEld1XOrmRJpUwRCcy7rxhSCwMrmEu9LXoPhHSCJG41V7YcQ2mjKRr5BA3ITIA==} engines: {node: '>= 18'} hasBin: true @@ -1489,9 +1488,6 @@ packages: resolution: {integrity: sha512-DGqD7Hjpi/1or4F/aYAspXKNm5Yili0QDAFAY4QYvpqpgiY6+1jOfqpmByzjxbWd/T9mChbCArXAbDAsTm5oXA==} engines: {node: '>=0.12'} - merge-stream@2.0.0: - resolution: {integrity: sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==} - merge2@1.4.1: resolution: {integrity: sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==} engines: {node: '>= 8'} @@ -1500,10 +1496,6 @@ packages: resolution: {integrity: sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==} engines: {node: '>=8.6'} - mimic-fn@2.1.0: - resolution: {integrity: sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==} - engines: {node: '>=6'} - min-indent@1.0.1: resolution: {integrity: sha512-I9jwMn07Sy/IwOj3zVkVik2JTvgpaykDZEigL6Rx6N9LbMywwUSMtxET+7lVoDLLd3O3IXwJwvuuns8UB/HeAg==} engines: {node: '>=4'} @@ -1511,51 +1503,43 @@ packages: minimatch@3.1.2: resolution: {integrity: sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==} - minimatch@9.0.4: - resolution: {integrity: sha512-KqWh+VchfxcMNRAJjj2tnsSJdNbHsVgnkBhTNrW7AjVo6OvLtxw8zfT9oLw1JSohlFzJ8jCoTgaoXvJ+kHt6fw==} + minimatch@9.0.5: + resolution: {integrity: sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==} engines: {node: '>=16 || 14 >=14.17'} minimist@1.2.8: resolution: {integrity: sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==} - minipass@3.3.6: - resolution: {integrity: sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==} - engines: {node: '>=8'} - - minipass@5.0.0: - resolution: {integrity: sha512-3FnjYuehv9k6ovOEbyOswadCDPX1piCfhV8ncmYtHOjuPwylVWsghTLo7rabjC3Rx5xD4HDx8Wm1xnMF7S5qFQ==} - engines: {node: '>=8'} - minipass@7.1.2: resolution: {integrity: sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==} engines: {node: '>=16 || 14 >=14.17'} - minizlib@2.1.2: - resolution: {integrity: sha512-bAxsR8BVfj60DWXHE3u30oHzfl4G7khkSuPW+qvpd7jFRHm7dLxOjUk1EHACJ/hxLY8phGJ0YhYHZo7jil7Qdg==} - engines: {node: '>= 8'} + minizlib@3.0.2: + resolution: {integrity: sha512-oG62iEk+CYt5Xj2YqI5Xi9xWUeZhDI8jjQmC5oThVH5JGCTgIjr7ciJDzC7MBzYd//WvR1OTmP5Q38Q8ShQtVA==} + engines: {node: '>= 18'} mkdirp@0.5.6: resolution: {integrity: sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw==} hasBin: true - mkdirp@1.0.4: - resolution: {integrity: sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==} + mkdirp@3.0.1: + resolution: {integrity: sha512-+NsyUUAZDmo6YVHzL/stxSu3t9YS1iljliy3BSDrXJ/dkn1KYdmtZODGGjLcc9XLgVVpH4KshHB8XmZgMhaBXg==} engines: {node: '>=10'} hasBin: true - mlly@1.7.1: - resolution: {integrity: sha512-rrVRZRELyQzrIUAVMHxP97kv+G786pHmOKzuFII8zDYahFBS7qnHh2AlYSl1GAHhaMPCz6/oHjVMcfFYgFYHgA==} + mlly@1.7.4: + resolution: {integrity: sha512-qmdSIPC4bDJXgZTCR7XosJiNKySV7O215tsPtDN9iEO/7q/76b/ijtgRu/+epFXSJhijtTCCGp3DWS549P3xKw==} mri@1.2.0: resolution: {integrity: sha512-tzzskb3bG8LvYGFF/mDTpq3jpI6Q9wc3LEmBaghu+DdCssd1FakN7Bc0hVNmEyGq1bq3RgfkCb3cmQLpNPOroA==} engines: {node: '>=4'} - mrmime@2.0.0: - resolution: {integrity: sha512-eu38+hdgojoyq63s+yTpN4XMBdt5l8HhMhc4VKLO9KM5caLIBvUm4thi7fFaxyTmCKeNnXZ5pAlBwCUnhA09uw==} + mrmime@2.0.1: + resolution: {integrity: sha512-Y3wQdFg2Va6etvQ5I82yUhGdsKrcYox6p7FfL1LbK2J4V01F9TGlepTIhnK24t7koZibmg82KGglhA1XK5IsLQ==} engines: {node: '>=10'} - ms@2.1.2: - resolution: {integrity: sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==} + ms@2.1.3: + resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==} murmurhash-js@1.0.0: resolution: {integrity: sha512-TvmkNhkv8yct0SVBSy+o8wYzXjE4Zz3PCesbfs8HiCXXdcTuocApFv11UWlNFWKYsP2okqrhb7JNlSm9InBhIw==} @@ -1563,8 +1547,8 @@ packages: mz@2.7.0: resolution: {integrity: sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q==} - nanoid@3.3.8: - resolution: {integrity: sha512-WNLf5Sd8oZxOm+TzppcYk8gVOgP+l58xNy58D0nbUnOxOWRWvlcCV4kUF7ltmI6PsrLl/BgKEyS4mqsGChFN0w==} + nanoid@3.3.11: + resolution: {integrity: sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==} engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} hasBin: true @@ -1580,16 +1564,16 @@ packages: encoding: optional: true - node-gyp-build@4.8.1: - resolution: {integrity: sha512-OSs33Z9yWr148JZcbZd5WiAXhh/n9z8TxQcdMhIOlpN9AhWpLfvVFO73+m77bBABQMaY9XSvIa+qk0jlI7Gcaw==} + node-gyp-build@4.8.4: + resolution: {integrity: sha512-LA4ZjwlnUblHVgq0oBF3Jl/6h/Nvs5fzBLwdEF4nuxnFdsfajde4WfxtJr3CaiH+F6ewcIB/q4jQ4UzPyid+CQ==} hasBin: true - node-releases@2.0.14: - resolution: {integrity: sha512-y10wOWt8yZpqXmOgRo77WaHEmhYQYGNA6y421PKsKYWEK8aW+cqAphborZDhqfyKrbZEN92CN1X2KbafY2s7Yw==} + node-releases@2.0.19: + resolution: {integrity: sha512-xxOWJsBKtzAq7DY0J+DTzuz58K8e7sJbdgwkbMWQe8UYB6ekmsQ45q0M/tJDsGaZmbC+l7n57UV8Hl5tHxO9uw==} - nopt@5.0.0: - resolution: {integrity: sha512-Tbj67rffqceeLpcRXrT7vKAN8CwfPeIBgM7E6iBkmKLV7bEMwpGgYLGv0jACUsECaa/vuxP0IjEont6umdMgtQ==} - engines: {node: '>=6'} + nopt@8.1.0: + resolution: {integrity: sha512-ieGu42u/Qsa4TFktmaKEwM6MQH0pOWnaB3htzh0JRtx84+Mebc0cbZYN5bC+6WTZ4+77xrL9Pn5m7CV6VIkV7A==} + engines: {node: ^18.17.0 || >=20.5.0} hasBin: true normalize-path@3.0.0: @@ -1600,14 +1584,6 @@ packages: resolution: {integrity: sha512-bdok/XvKII3nUpklnV6P2hxtMNrCboOjAcyBuQnWEhO665FwrSNRxU+AqpsyvO6LgGYPspN+lu5CLtw4jPRKNA==} engines: {node: '>=0.10.0'} - npm-run-path@4.0.1: - resolution: {integrity: sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==} - engines: {node: '>=8'} - - npmlog@5.0.1: - resolution: {integrity: sha512-AqZtDUWOMKs1G/8lwylVjrdYgqA4d9nu8hc+0gzRxlDb1I10+FHBGMXs6aiQHFdCUUlqH99MUMuLfzWDNDtfxw==} - deprecated: This package is no longer supported. - object-assign@4.1.1: resolution: {integrity: sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==} engines: {node: '>=0.10.0'} @@ -1619,36 +1595,23 @@ packages: once@1.4.0: resolution: {integrity: sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==} - onetime@5.1.2: - resolution: {integrity: sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==} - engines: {node: '>=6'} - p-limit@2.3.0: resolution: {integrity: sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==} engines: {node: '>=6'} - p-limit@3.1.0: - resolution: {integrity: sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==} - engines: {node: '>=10'} - p-locate@4.1.0: resolution: {integrity: sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==} engines: {node: '>=8'} - p-locate@5.0.0: - resolution: {integrity: sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==} - engines: {node: '>=10'} - p-try@2.2.0: resolution: {integrity: sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==} engines: {node: '>=6'} - package-json-from-dist@1.0.0: - resolution: {integrity: sha512-dATvCeZN/8wQsGywez1mzHtTlP22H8OEfPrVMLNr4/eGa+ijtLn/6M5f0dY8UKNrC2O9UCU6SSoG3qRKnt7STw==} + package-json-from-dist@1.0.1: + resolution: {integrity: sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==} - parent-module@1.0.1: - resolution: {integrity: sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==} - engines: {node: '>=6'} + package-manager-detector@0.2.11: + resolution: {integrity: sha512-BEnLolu+yuz22S56CU1SUKq3XC3PkwD5wv4ikR4MfGvnRVcmzXR9DwSlW2fEamyTPyXHomBJRzgapeuBvRNzJQ==} path-exists@4.0.0: resolution: {integrity: sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==} @@ -1669,19 +1632,16 @@ packages: resolution: {integrity: sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==} engines: {node: '>=16 || 14 >=14.18'} - pathe@1.1.2: - resolution: {integrity: sha512-whLdWMYL2TwI08hn8/ZqAbrVemu0LNaNNJZX73O6qaIdCTfXutsLhMkjdENX0qhsQ9uIimo4/aQOmXkoon2nDQ==} + pathe@2.0.3: + resolution: {integrity: sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==} - pbf@3.2.1: - resolution: {integrity: sha512-ClrV7pNOn7rtmoQVF4TS1vyU0WhYRnP92fzbfF75jAIwpnzdJXf8iTd4CMEqO4yUenH6NDqLiwjqlh6QgZzgLQ==} + pbf@3.3.0: + resolution: {integrity: sha512-XDF38WCH3z5OV/OVa8GKUNtLAyneuzbCisx7QUCF8Q6Nutx0WnJrQe5O+kOtBlLfRNUws98Y58Lblp+NJG5T4Q==} hasBin: true periscopic@3.1.0: resolution: {integrity: sha512-vKiQ8RRtkl9P+r/+oefh25C3fhybptkHKCZSPlcXiJux2tJF55GnEj3BVn4A5gKfq9NWWXXrxkHBwVPUfH0opw==} - picocolors@1.0.1: - resolution: {integrity: sha512-anP1Z8qwhkbmu7MFP5iTt+wQKXgwzf7zTyGlcdzabySa9vd0Xt392U0rVmz9poOaBj0uHJKyyo9/upk0HrEQew==} - picocolors@1.1.1: resolution: {integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==} @@ -1689,19 +1649,26 @@ packages: resolution: {integrity: sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==} engines: {node: '>=8.6'} + picomatch@4.0.2: + resolution: {integrity: sha512-M7BAV6Rlcy5u+m6oPhAPFgJTzAioX/6B0DxyvDlo9l8+T3nLKbrczg2WLUyzd45L8RqfUMyGPzekbMvX2Ldkwg==} + engines: {node: '>=12'} + pify@2.3.0: resolution: {integrity: sha512-udgsAY+fTnvv7kI7aaxbqwWNb0AHiB0qBO89PZKPkoTmGOgdbrHDKD+0B2X4uTfJ/FT1R09r9gTsjUjNJotuog==} engines: {node: '>=0.10.0'} - pirates@4.0.6: - resolution: {integrity: sha512-saLsH7WeYYPiD25LDuLRRY/i+6HaPYr6G1OUlN39otzkSTxKnubR9RTxS3/Kk50s1g2JTgFwWQDQyplC5/SHZg==} + pirates@4.0.7: + resolution: {integrity: sha512-TfySrs/5nm8fQJDcBDuUng3VOUKsd7S+zqvbOTiGXHfxX4wK31ard+hoNuvkicM/2YFzlpDgABOevKSsB4G/FA==} engines: {node: '>= 6'} - pkg-types@1.1.1: - resolution: {integrity: sha512-ko14TjmDuQJ14zsotODv7dBlwxKhUKQEhuhmbqo1uCi9BB0Z2alo/wAXg6q1dTR5TyuqYyWhjtfe/Tsh+X28jQ==} + pkg-types@1.3.1: + resolution: {integrity: sha512-/Jm5M4RvtBFVkKWRu2BLUTNP8/M2a+UwuAX+ae4770q1qVGtfjG+WTCupoZixokjmHiry8uI+dlY8KXYV5HVVQ==} + + pkg-types@2.1.0: + resolution: {integrity: sha512-wmJwA+8ihJixSoHKxZJRBQG1oY8Yr9pGLzRmSsNms0iNWyHHAlZCa7mmKiFR10YPZuz/2k169JiS/inOjBCZ2A==} - pmtiles@3.0.6: - resolution: {integrity: sha512-IdeMETd5lBIDVTLul1HFl0Q7l4KLJjzdxgcp+sN7pYvbipaV7o/0u0HiV06kaFCD0IGEN8KtUHyFZpY30WMflw==} + pmtiles@3.2.1: + resolution: {integrity: sha512-3R4fBwwoli5mw7a6t1IGwOtfmcSAODq6Okz0zkXhS1zi9sz1ssjjIfslwPvcWw5TNhdjNBUg9fgfPLeqZlH6ng==} pngjs@5.0.0: resolution: {integrity: sha512-40QW5YalBNfQo5yRYmiw7Yz6TKKVr3h6970B2YE+3fQpsWcrbj1PzJgxeJ19DRQjhMbKPIuMY8rFaXc8moolVw==} @@ -1731,8 +1698,8 @@ packages: ts-node: optional: true - postcss-nested@6.0.1: - resolution: {integrity: sha512-mEp4xPMi5bSWiMbsgoPfcP74lsWLHkQbZc3sY+jWYd65CUwXrUaTp0fmNpa01ZcETKlIgUdFN/MpS2xZtqL9dQ==} + postcss-nested@6.2.0: + resolution: {integrity: sha512-HQbt28KulC5AJzG+cZtj9kvKB93CFCdLvog1WFLf1D+xmMvPGlBstkpTEZfK5+AN9hfJocyBFCNiqyS48bpgzQ==} engines: {node: '>=12.0'} peerDependencies: postcss: ^8.2.14 @@ -1741,32 +1708,28 @@ packages: resolution: {integrity: sha512-IQ7TZdoaqbT+LCpShg46jnZVlhWD2w6iQYAcYXfHARZ7X1t/UGhhceQDs5X0cGqKvYlHNOuv7Oa1xmb0oQuA3w==} engines: {node: '>=4'} - postcss-selector-parser@6.1.0: - resolution: {integrity: sha512-UMz42UD0UY0EApS0ZL9o1XnLhSTtvvvLe5Dc2H2O56fvRZi+KulDyf5ctDhhtYJBGKStV2FL1fy6253cmLgqVQ==} + postcss-selector-parser@6.1.2: + resolution: {integrity: sha512-Q8qQfPiZ+THO/3ZrOrO0cJJKfpYCagtMUkXbnEfmgUjwXg6z/WBeOyS9APBBPCTSiDV+s4SwQGu8yFsiMRIudg==} engines: {node: '>=4'} postcss-value-parser@4.2.0: resolution: {integrity: sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==} - postcss@8.4.38: - resolution: {integrity: sha512-Wglpdk03BSfXkHoQa3b/oulrotAkwrlLDRSOb9D0bN86FdRyE9lppSp33aHNPgBa0JKCoB+drFLZkQoRRYae5A==} - engines: {node: ^10 || ^12 || >=14} - - postcss@8.5.1: - resolution: {integrity: sha512-6oz2beyjc5VMn/KV1pPw8fliQkhBXrVn1Z3TVyqZxU8kZpzEKhBdmCFqI6ZbmGtamQvQGuU1sgPTk8ZrXDD7jQ==} + postcss@8.5.3: + resolution: {integrity: sha512-dle9A3yYxlBSrt8Fu+IpjGT8SY8hN0mlaA6GY8t0P5PjIOZemULz/E2Bnm/2dcUOena75OTNkHI76uZBNUUq3A==} engines: {node: ^10 || ^12 || >=14} potpack@2.0.0: resolution: {integrity: sha512-Q+/tYsFU9r7xoOJ+y/ZTtdVQwTWfzjbiXBDMM/JKUux3+QPP02iUuIoeBQ+Ot6oEDlC+/PGjB/5A3K7KKb7hcw==} - prettier-plugin-svelte@3.2.5: - resolution: {integrity: sha512-vP/M/Goc8z4iVIvrwXwbrYVjJgA0Hf8PO1G4LBh/ocSt6vUP6sLvyu9F3ABEGr+dbKyxZjEKLkeFsWy/yYl0HQ==} + prettier-plugin-svelte@3.3.3: + resolution: {integrity: sha512-yViK9zqQ+H2qZD1w/bH7W8i+bVfKrD8GIFjkFe4Thl6kCT9SlAsXVNmt3jCvQOCsnOhcvYgsoVlRV/Eu6x5nNw==} peerDependencies: prettier: ^3.0.0 svelte: ^3.2.0 || ^4.0.0-next.0 || ^5.0.0-next.0 - prettier@3.3.2: - resolution: {integrity: sha512-rAVeHYMcv8ATV5d508CFdn+8/pHPpXeIid1DdrPwXnaAdH7cqjVbpJaT5eq4yRAFU/lsbwYwSF/n5iNrdJHPQA==} + prettier@3.5.3: + resolution: {integrity: sha512-QQtaxnoDJeAkDvDKWCLiwIXkTgRhwYDEQCghU9Z6q03iyek/rxRh/2lC3HB7P8sWT2xC/y5JDctPLBIGzHKbhw==} engines: {node: '>=14'} hasBin: true @@ -1785,12 +1748,18 @@ packages: engines: {node: '>=10.13.0'} hasBin: true + quansync@0.2.10: + resolution: {integrity: sha512-t41VRkMYbkHyCYmOvx/6URnN80H7k4X0lLdBMGsz+maAwrJQYB1djpV6vHrQIBE0WBSGqhtEHrK9U3DWWH8v7A==} + queue-microtask@1.2.3: resolution: {integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==} quickselect@2.0.0: resolution: {integrity: sha512-RKJ22hX8mHe3Y6wH/N3wCM6BWtjaxIyyUIkpHOvfFnxdI4yD4tBXEBKSbriGujF6jnSVkJrffuo6vxACiSSxIw==} + quickselect@3.0.0: + resolution: {integrity: sha512-XdjUArbK4Bm5fLLvlm5KpTFOiOThgfWWI4axAZDWg4E/0mKdZyI9tNEfds27qCi1ze/vwTR16kvmmGhRra3c2g==} + read-cache@1.0.0: resolution: {integrity: sha512-Owdv/Ft7IjOgm/i0xvNDZ1LrRANRfew4b2prF3OWMQLxLfu3bS8FVhCsrSCMK4lR56Y9ya+AThoTpDCTxCmpRA==} @@ -1809,10 +1778,6 @@ packages: require-main-filename@2.0.0: resolution: {integrity: sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg==} - resolve-from@4.0.0: - resolution: {integrity: sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==} - engines: {node: '>=4'} - resolve-from@5.0.0: resolution: {integrity: sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==} engines: {node: '>=8'} @@ -1820,12 +1785,13 @@ packages: resolve-protobuf-schema@2.1.0: resolution: {integrity: sha512-kI5ffTiZWmJaS/huM8wZfEMer1eRd7oJQhDuxeCLe3t7N7mX3z94CN0xPxBQxFYQTSNz9T0i+v6inKqSdK8xrQ==} - resolve@1.22.8: - resolution: {integrity: sha512-oKWePCxqpd6FlLvGV1VU0x7bkPmmCNolxzjMf4NczoDnQcIWrAF+cPtZn5i6n+RfD2d9i0tzpKnG6Yk168yIyw==} + resolve@1.22.10: + resolution: {integrity: sha512-NPRy+/ncIMeDlTAsuqwKIiferiawhefFJtkNSW0qZJEqMEb+qBt/77B/jGeeek+F0uOeN05CDa6HXbbIgtVX4w==} + engines: {node: '>= 0.4'} hasBin: true - reusify@1.0.4: - resolution: {integrity: sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==} + reusify@1.1.0: + resolution: {integrity: sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==} engines: {iojs: '>=1.0.0', node: '>=0.10.0'} rimraf@2.7.1: @@ -1833,18 +1799,8 @@ packages: deprecated: Rimraf versions prior to v4 are no longer supported hasBin: true - rimraf@3.0.2: - resolution: {integrity: sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==} - deprecated: Rimraf versions prior to v4 are no longer supported - hasBin: true - - rollup@4.24.0: - resolution: {integrity: sha512-DOmrlGSXNk1DM0ljiQA+i+o0rSLhtii1je5wgk60j49d1jHT5YYttBv1iWOnYSTG+fZZESUOSNiAl89SIet+Cg==} - engines: {node: '>=18.0.0', npm: '>=8.0.0'} - hasBin: true - - rollup@4.31.0: - resolution: {integrity: sha512-9cCE8P4rZLx9+PjoyqHLs31V9a9Vpvfo4qNcs6JCiGWYhw2gijSetFbH6SSy1whnkgcefnUwr8sad7tgqsGvnw==} + rollup@4.40.2: + resolution: {integrity: sha512-tfUOg6DTP4rhQ3VjOO6B4wyrJnGOX85requAXvqYTHsOgb2TFJdZ3aWpT8W2kPoypSGP7dZUyzxJ9ee4buM5Fg==} engines: {node: '>=18.0.0', npm: '>=8.0.0'} hasBin: true @@ -1864,24 +1820,16 @@ packages: sander@0.5.1: resolution: {integrity: sha512-3lVqBir7WuKDHGrKRDn/1Ye3kwpXaDOMsiRP1wd6wpZW56gJhsbp5RqQpA6JG/P+pkXizygnr1dKR8vzWaVsfA==} - semver@6.3.1: - resolution: {integrity: sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==} - hasBin: true - - semver@7.6.2: - resolution: {integrity: sha512-FNAIBWCx9qcRhoHcgcJ0gvU7SN1lYU2ZXuSfl04bSC5OpvDHFyJCjdNHomPXxjQlCBU67YW64PzY7/VIEH7F2w==} + semver@7.7.1: + resolution: {integrity: sha512-hlq8tAfn0m/61p4BVRcPzIGr6LKiMwo4VM6dGi6pt4qcRkmNzTcWq6eCEjEh+qXjkMDvPlOFFSGwQjoEa6gyMA==} engines: {node: '>=10'} hasBin: true set-blocking@2.0.0: resolution: {integrity: sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw==} - set-cookie-parser@2.6.0: - resolution: {integrity: sha512-RVnVQxTXuerk653XfuliOxBP81Sf0+qfQE73LIYKcyMYHG94AuH0kgrQpRDuTZnSmjpysHmzxJXKNfa6PjFhyQ==} - - set-value@2.0.1: - resolution: {integrity: sha512-JxHc1weCN68wRY0fhCoXpyK55m/XPHafOmK4UWD7m2CI14GMcFypt4w/0+NV5f/ZMby2F6S2wwA7fgynh9gWSw==} - engines: {node: '>=0.10.0'} + set-cookie-parser@2.7.1: + resolution: {integrity: sha512-IOc8uWeOZgnb3ptbCURJWNjWUPcO3ZnTTdzsurqERrP6nPyv+paC55vJM0LpOlT2ne+Ix+9+CRG1MNLlyZ4GjQ==} shebang-command@2.0.0: resolution: {integrity: sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==} @@ -1891,49 +1839,22 @@ packages: resolution: {integrity: sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==} engines: {node: '>=8'} - signal-exit@3.0.7: - resolution: {integrity: sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==} - signal-exit@4.1.0: resolution: {integrity: sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==} engines: {node: '>=14'} - sirv@3.0.0: - resolution: {integrity: sha512-BPwJGUeDaDCHihkORDchNyyTvWFhcusy1XMmhEVTQTwGeybFbp8YEmB+njbPnth1FibULBSBVwCQni25XlCUDg==} + sirv@3.0.1: + resolution: {integrity: sha512-FoqMu0NCGBLCcAkS1qA+XJIQTR6/JHfQXl+uGteNCQ76T91DMUjPa9xfmeqMY3z80nLSg9yQmNjK0Px6RWsH/A==} engines: {node: '>=18'} sorcery@0.11.1: - resolution: {integrity: sha512-o7npfeJE6wi6J9l0/5LKshFzZ2rMatRiCDwYeDQaOzqdzRJwALhX7mk/A/ecg6wjMu7wdZbmXfD2S/vpOg0bdQ==} - hasBin: true - - sort-asc@0.2.0: - resolution: {integrity: sha512-umMGhjPeHAI6YjABoSTrFp2zaBtXBej1a0yKkuMUyjjqu6FJsTF+JYwCswWDg+zJfk/5npWUUbd33HH/WLzpaA==} - engines: {node: '>=0.10.0'} - - sort-desc@0.2.0: - resolution: {integrity: sha512-NqZqyvL4VPW+RAxxXnB8gvE1kyikh8+pR+T+CXLksVRN9eiQqkQlPwqWYU0mF9Jm7UnctShlxLyAt1CaBOTL1w==} - engines: {node: '>=0.10.0'} - - sort-object@3.0.3: - resolution: {integrity: sha512-nK7WOY8jik6zaG9CRwZTaD5O7ETWDLZYMM12pqY8htll+7dYeqGfEUPcUBHOpSJg2vJOrvFIY2Dl5cX2ih1hAQ==} - engines: {node: '>=0.10.0'} - - source-map-js@1.2.0: - resolution: {integrity: sha512-itJW8lvSA0TXEphiRoawsCksnlf8SyvmFzIhltqAHluXd88pkCd+cXJVHTDwdCr0IzwptSm035IHQktUu1QUMg==} - engines: {node: '>=0.10.0'} + resolution: {integrity: sha512-o7npfeJE6wi6J9l0/5LKshFzZ2rMatRiCDwYeDQaOzqdzRJwALhX7mk/A/ecg6wjMu7wdZbmXfD2S/vpOg0bdQ==} + hasBin: true source-map-js@1.2.1: resolution: {integrity: sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==} engines: {node: '>=0.10.0'} - split-string@3.1.0: - resolution: {integrity: sha512-NzNVhJDYpwceVVii8/Hu6DKfD2G+NrQHlS/V/qgv763EYudVwEcMQNxd2lh+0VrUByXN/oJkl5grOhYWvQUYiw==} - engines: {node: '>=0.10.0'} - - string-argv@0.3.2: - resolution: {integrity: sha512-aqD2Q0144Z+/RqG52NeHEkZauTAUWJO8c6yTftGJKO3Tja5tUgIfmIl6kExvhtxSDP7fXB6DvzkfMpCd/F3G+Q==} - engines: {node: '>=0.6.19'} - string-width@4.2.3: resolution: {integrity: sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==} engines: {node: '>=8'} @@ -1953,10 +1874,6 @@ packages: resolution: {integrity: sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==} engines: {node: '>=12'} - strip-final-newline@2.0.0: - resolution: {integrity: sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==} - engines: {node: '>=6'} - strip-indent@3.0.0: resolution: {integrity: sha512-laJTa3Jb+VQpaC6DseHhF7dXVqHTfJPCRDaEbid/drOhgitgYku/letMUqOXFoWV0zIIUbjpdH2t+tYj4bQMRQ==} engines: {node: '>=8'} @@ -1973,8 +1890,8 @@ packages: resolution: {integrity: sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==} engines: {node: '>= 0.4'} - svelte-check@3.8.1: - resolution: {integrity: sha512-KlQ0TRVe01mdvh49Ylkr9FQxO/UWbQOtaIrccl3gjgkvby1TxY41VkT7ijCl6i29FjaJPE4m6YGmhdqov0MfkA==} + svelte-check@3.8.6: + resolution: {integrity: sha512-ij0u4Lw/sOTREP13BdWZjiXD/BlHE6/e2e34XzmVmsp5IN4kVa3PWP65NM32JAgwjZlwBg/+JtiNV1MM8khu0Q==} hasBin: true peerDependencies: svelte: ^3.55.0 || ^4.0.0-next.0 || ^4.0.0 || ^5.0.0-next.0 @@ -1992,8 +1909,8 @@ packages: peerDependencies: svelte: ^3 || ^4 || ^5 - svelte-maplibre@0.9.8: - resolution: {integrity: sha512-z6YyJv1sT8AHJuzuzd+30M9PQMllFnGBpHvSJ5BlwFQF/yP4xdJY9+ynF9ziywJIzGMjuvTiCeEXZSY0fhsTAA==} + svelte-maplibre@0.9.14: + resolution: {integrity: sha512-5HBvibzU/Uf3g8eEz4Hty5XAwoBhW9Tp7NQEvb80U/glR/M1IHyzUKss6XMq8Zbci2wtsASeoPc6dA5R4+0e0w==} peerDependencies: '@deck.gl/core': ^8.8.0 '@deck.gl/layers': ^8.8.0 @@ -2048,14 +1965,14 @@ packages: resolution: {integrity: sha512-IY1rnGr6izd10B0A8LqsBfmlT5OILVuZ7XsI0vdGPEvuonFV7NYEUK4dAkm9Zg2q0Um92kYjTpS1CAP3Nh/KWw==} engines: {node: '>=16'} - tailwindcss@3.4.4: - resolution: {integrity: sha512-ZoyXOdJjISB7/BcLTR6SEsLgKtDStYyYZVLsUtWChO4Ps20CBad7lfJKVDiejocV4ME1hLmyY0WJE3hSDcmQ2A==} + tailwindcss@3.4.17: + resolution: {integrity: sha512-w33E2aCvSDP0tW9RZuNXadXlkHXqFzSkQew/aIa2i/Sj8fThxwovwlXHSPXTbAHwEIhBFXAedUhP2tueAKP8Og==} engines: {node: '>=14.0.0'} hasBin: true - tar@6.2.1: - resolution: {integrity: sha512-DZ4yORTwrbTj/7MZYq2w+/ZFdI6OZ/f9SFHR+71gIVUZhOQPHzVCLpvRnPgyaMpfWxxk/4ONva3GQSyNIKRv6A==} - engines: {node: '>=10'} + tar@7.4.3: + resolution: {integrity: sha512-5S7Va8hKfV7W5U6g3aYxXmlPoZVAwUMy9AOKyF2fVuZa2UD3qZjg578OrLRt8PcNN1PleVaL/5/yYATNL0ICUw==} + engines: {node: '>=18'} thenify-all@1.6.0: resolution: {integrity: sha512-RNxQH/qI8/t3thXJDwcstUO4zeqo64+Uy/+sNVRBx4Xn2OX+OZ9oP+iJnNFqplFra2ZUVeKCSa2oVWi3T4uVmA==} @@ -2071,8 +1988,11 @@ packages: tiny-glob@0.2.9: resolution: {integrity: sha512-g/55ssRPUjShh+xkfx9UPDXqhckHEsHr4Vd9zX55oSdGZc/MD0m3sferOkwWtp98bv+kcVfEHtRJgBVJzelrzg==} - tinyqueue@2.0.3: - resolution: {integrity: sha512-ppJZNDuKGgxzkHihX8v9v9G5f+18gzaTfrukGrq6ueg0lmH4nqVnA2IPG0AEH3jKEk2GRJCUhDoqpoiw3PHLBA==} + tinyexec@0.3.2: + resolution: {integrity: sha512-KQQR9yN7R5+OSwaK0XQoj22pwHoTlgYqmUscPYoknOoWCWfj/5/ABTMRi69FrKU5ffPVh5QcFikpWJI/P1ocHA==} + + tinyqueue@3.0.0: + resolution: {integrity: sha512-gRa9gwYU3ECmQYv3lslts5hxuIa90veaEcxDYuu3QGOIAEM2mOZkVHp48ANJuu1CURtRdHKUBY5Lm1tHV+sD4g==} to-regex-range@5.0.1: resolution: {integrity: sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==} @@ -2088,12 +2008,8 @@ packages: ts-interface-checker@0.1.13: resolution: {integrity: sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA==} - tslib@2.6.3: - resolution: {integrity: sha512-xNvxJEOUiWPGhUuUdQgAJPKOOJfGnIyKySOc09XkKsgdUV/3E2zvwZYdejjmRgPCgcym1juLH3226yA7sEFJKQ==} - - type-detect@4.0.8: - resolution: {integrity: sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==} - engines: {node: '>=4'} + tslib@2.8.1: + resolution: {integrity: sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==} type@2.7.3: resolution: {integrity: sha512-8j+1QmAbPvLZow5Qpi6NCaN8FB60p/6x8/vfNqOk/hC+HuvFZhL4+WfekuhQLiqFZXOgQdrs3B+XxEmCc6b3FQ==} @@ -2101,29 +2017,19 @@ packages: typedarray@0.0.6: resolution: {integrity: sha512-/aCDEGatGvZ2BIk+HmLf4ifCJFwvKFNb9/JeZPMulfgFracn9QFcAf5GO8B/mweUjSoblS5In0cWhqpfs/5PQA==} - typescript@5.5.2: - resolution: {integrity: sha512-NcRtPEOsPFFWjobJEtfihkLCZCXZt/os3zf8nTxjVH3RvTSxjrCamJpbExGvYOF+tFHc3pA65qpdwPbzjohhew==} + typescript@5.8.3: + resolution: {integrity: sha512-p1diW6TqL9L07nNxvRMM7hMMw4c5XOo/1ibL4aAIGmSAt9slTE1Xgw5KWuof2uTOvCg9BY7ZRi+GaF+7sfgPeQ==} engines: {node: '>=14.17'} hasBin: true - typewise-core@1.2.0: - resolution: {integrity: sha512-2SCC/WLzj2SbUwzFOzqMCkz5amXLlxtJqDKTICqg30x+2DZxcfZN2MvQZmGfXWKNWaKK9pBPsvkcwv8bF/gxKg==} + ufo@1.6.1: + resolution: {integrity: sha512-9a4/uxlTWJ4+a5i0ooc1rU7C7YOw3wT+UGqdeNNHWnOF9qcMBgLRS+4IYUqbczewFx4mLEig6gawh7X6mFlEkA==} - typewise@1.0.3: - resolution: {integrity: sha512-aXofE06xGhaQSPzt8hlTY+/YWQhm9P0jYUp1f2XtmW/3Bk0qzXcyFWAtPoo2uTGQj1ZwbDuSyuxicq+aDo8lCQ==} - - ufo@1.5.3: - resolution: {integrity: sha512-Y7HYmWaFwPUmkoQCUIAYpKqkOf+SbVj/2fJJZ4RJMCfZp0rTGwRbzQD+HghfnhKOjL9E01okqz+ncJskGYfBNw==} - - undici-types@6.19.8: - resolution: {integrity: sha512-ve2KP6f/JnbPBFyobGHuerC9g1FYGn/F8n1LWTwNxCEzd6IfqTwUQcNXgEtmmQ6DlRrC1hrSrBnCZPokRrDHjw==} - - union-value@1.0.1: - resolution: {integrity: sha512-tJfXmxMeWYnczCVs7XAEvIV7ieppALdyepWMkHkwciRpZraG/xwT+s2JN8+pr1+8jCRf80FFzvr+MpQeeoF4Xg==} - engines: {node: '>=0.10.0'} + undici-types@6.21.0: + resolution: {integrity: sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==} - unplugin-icons@0.19.0: - resolution: {integrity: sha512-u5g/gIZPZEj1wUGEQxe9nzftOSqmblhusc+sL3cawIRoIt/xWpE6XYcPOfAeFTYNjSbRrX/3QiX89PFiazgU1w==} + unplugin-icons@0.19.3: + resolution: {integrity: sha512-EUegRmsAI6+rrYr0vXjFlIP+lg4fSC4zb62zAZKx8FGXlWAGgEGBCa3JDe27aRAXhistObLPbBPhwa/0jYLFkQ==} peerDependencies: '@svgr/core': '>=7.0.0' '@svgx/core': ^1.0.1 @@ -2142,12 +2048,12 @@ packages: vue-template-es2015-compiler: optional: true - unplugin@1.10.1: - resolution: {integrity: sha512-d6Mhq8RJeGA8UfKCu54Um4lFA0eSaRa3XxdAJg8tIdxbu1ubW0hBCZUL7yI2uGyYCRndvbK8FLHzqy2XKfeMsg==} + unplugin@1.16.1: + resolution: {integrity: sha512-4/u/j4FrCKdi17jaxuJA0jClGxB1AvU2hw/IuayPc4ay1XGaJs/rbb4v5WKwAjNifjmXK9PIFyuPiaK8azyR9w==} engines: {node: '>=14.0.0'} - update-browserslist-db@1.0.16: - resolution: {integrity: sha512-KVbTxlBYlckhF5wgfyZXTWnMn7MMZjMu9XG8bPlliUOP9ThaF4QnhP8qrjrH7DRzHfSk0oQv1wToW+iA5GajEQ==} + update-browserslist-db@1.1.3: + resolution: {integrity: sha512-UxhIZQ+QInVdunkDAaiazvvT/+fXL5Osr0JZlJulepYu6Jd7qJtDZjlur0emRlT71EN3ScPoE7gvsuIKKNavKw==} hasBin: true peerDependencies: browserslist: '>= 4.21.0' @@ -2155,8 +2061,8 @@ packages: util-deprecate@1.0.2: resolution: {integrity: sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==} - vite@5.4.12: - resolution: {integrity: sha512-KwUaKB27TvWwDJr1GjjWthLMATbGEbeWYZIbGZ5qFIsgPP3vWzLu4cVooqhm5/Z2SPDUMjyPVjTztm5tYKwQxA==} + vite@5.4.19: + resolution: {integrity: sha512-qO3aKv3HoQC8QKiNSTuUM1l9o/XX3+c+VTgLHbJWHZGeTPVAg2XwazI9UWzoxjIJCGCV2zU60uqMzjeLZuULqA==} engines: {node: ^18.0.0 || >=20.0.0} hasBin: true peerDependencies: @@ -2200,10 +2106,6 @@ packages: webidl-conversions@3.0.1: resolution: {integrity: sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==} - webpack-sources@3.2.3: - resolution: {integrity: sha512-/DyMEOrDgLKKIG0fmvtz+4dUX/3Ghozwgm6iPp8KRhvn+eQf9+Q7GWxVNMk3+uCPWfdXYC4ExGBckIXdFEfH1w==} - engines: {node: '>=10.13.0'} - webpack-virtual-modules@0.6.2: resolution: {integrity: sha512-66/V2i5hQanC51vBQKPH4aI8NMAcBW59FVBs+rC7eGHupMyfn34q7rZIE+ETlJ+XTevqfUhVVBgSUNSW2flEUQ==} @@ -2213,17 +2115,15 @@ packages: which-module@2.0.1: resolution: {integrity: sha512-iBdZ57RDvnOR9AGBhML2vFZf7h8vmBjhoaZqODJBFWHVtKkDmKuHai3cx5PgVMrX5YDNp27AofYbAwctSS+vhQ==} - which@1.3.1: - resolution: {integrity: sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==} - hasBin: true - which@2.0.2: resolution: {integrity: sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==} engines: {node: '>= 8'} hasBin: true - wide-align@1.1.5: - resolution: {integrity: sha512-eDMORYaPNZ4sQIuuYPDHdQvf4gyCF9rEEV/yPxGfwPkRodwEgiMUUXTx/dex+Me0wxx53S+NgUHaP7y3MGlDmg==} + which@4.0.0: + resolution: {integrity: sha512-GlaYyEb07DPxYCKhKzplCWBJtvxZcZMrL+4UkrTSJHHPyZU4mYYTv3qaOe77H7EODLSSopAUFAc6W8U4yqvscg==} + engines: {node: ^16.13.0 || >=18.0.0} + hasBin: true wrap-ansi@6.2.0: resolution: {integrity: sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==} @@ -2243,11 +2143,12 @@ packages: y18n@4.0.3: resolution: {integrity: sha512-JKhqTOwSrqNA1NY5lSztJ1GrBiUodLMmIZuLiDaMRJ+itFd+ABVE8XBjOvIWL+rSqNDC74LCSFmlb/U4UZ4hJQ==} - yallist@4.0.0: - resolution: {integrity: sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==} + yallist@5.0.0: + resolution: {integrity: sha512-YgvUTfwqyc7UXVMrB+SImsVYSmTS8X/tSrtdNZMImM+n7+QTriRXyXim0mBrTXNeqzVF0KWGgHPeiyViFFrNDw==} + engines: {node: '>=18'} - yaml@2.4.5: - resolution: {integrity: sha512-aBx2bnqDzVOyNKfsysjA2ms5ZlnjSAW2eG3/L5G/CSujfjLJTJsEw1bGw8kCf04KodQWk1pxlGnZ56CRxiawmg==} + yaml@2.7.1: + resolution: {integrity: sha512-10ULxpnOCQXxJvBgxsn9ptjq6uviG/htZKk9veJGhlqn3w/DxQ631zFF+nlQXLwmImeS5amR2dl2U8sg6U9jsQ==} engines: {node: '>= 14'} hasBin: true @@ -2259,29 +2160,28 @@ packages: resolution: {integrity: sha512-aePbxDmcYW++PaqBsJ+HYUFwCdv4LVvdnhBy78E57PIor8/OVvhMrADFFEDh8DHDFRv/O9i3lPhsENjO7QX0+A==} engines: {node: '>=8'} - yocto-queue@0.1.0: - resolution: {integrity: sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==} - engines: {node: '>=10'} - snapshots: '@alloc/quick-lru@5.2.0': {} '@ampproject/remapping@2.3.0': dependencies: - '@jridgewell/gen-mapping': 0.3.5 + '@jridgewell/gen-mapping': 0.3.8 '@jridgewell/trace-mapping': 0.3.25 - '@antfu/install-pkg@0.1.1': + '@antfu/install-pkg@0.4.1': dependencies: - execa: 5.1.1 - find-up: 5.0.0 + package-manager-detector: 0.2.11 + tinyexec: 0.3.2 - '@antfu/install-pkg@0.3.3': + '@antfu/install-pkg@1.0.0': dependencies: - '@jsdevtools/ez-spawn': 3.0.4 + package-manager-detector: 0.2.11 + tinyexec: 0.3.2 - '@antfu/utils@0.7.8': {} + '@antfu/utils@0.7.10': {} + + '@antfu/utils@8.1.1': {} '@esbuild/aix-ppc64@0.19.12': optional: true @@ -2289,192 +2189,269 @@ snapshots: '@esbuild/aix-ppc64@0.21.5': optional: true + '@esbuild/aix-ppc64@0.24.2': + optional: true + '@esbuild/android-arm64@0.19.12': optional: true '@esbuild/android-arm64@0.21.5': optional: true + '@esbuild/android-arm64@0.24.2': + optional: true + '@esbuild/android-arm@0.19.12': optional: true '@esbuild/android-arm@0.21.5': optional: true + '@esbuild/android-arm@0.24.2': + optional: true + '@esbuild/android-x64@0.19.12': optional: true '@esbuild/android-x64@0.21.5': optional: true + '@esbuild/android-x64@0.24.2': + optional: true + '@esbuild/darwin-arm64@0.19.12': optional: true '@esbuild/darwin-arm64@0.21.5': optional: true + '@esbuild/darwin-arm64@0.24.2': + optional: true + '@esbuild/darwin-x64@0.19.12': optional: true '@esbuild/darwin-x64@0.21.5': optional: true + '@esbuild/darwin-x64@0.24.2': + optional: true + '@esbuild/freebsd-arm64@0.19.12': optional: true '@esbuild/freebsd-arm64@0.21.5': optional: true + '@esbuild/freebsd-arm64@0.24.2': + optional: true + '@esbuild/freebsd-x64@0.19.12': optional: true '@esbuild/freebsd-x64@0.21.5': optional: true + '@esbuild/freebsd-x64@0.24.2': + optional: true + '@esbuild/linux-arm64@0.19.12': optional: true '@esbuild/linux-arm64@0.21.5': optional: true + '@esbuild/linux-arm64@0.24.2': + optional: true + '@esbuild/linux-arm@0.19.12': optional: true '@esbuild/linux-arm@0.21.5': optional: true + '@esbuild/linux-arm@0.24.2': + optional: true + '@esbuild/linux-ia32@0.19.12': optional: true '@esbuild/linux-ia32@0.21.5': optional: true + '@esbuild/linux-ia32@0.24.2': + optional: true + '@esbuild/linux-loong64@0.19.12': optional: true '@esbuild/linux-loong64@0.21.5': optional: true + '@esbuild/linux-loong64@0.24.2': + optional: true + '@esbuild/linux-mips64el@0.19.12': optional: true '@esbuild/linux-mips64el@0.21.5': optional: true + '@esbuild/linux-mips64el@0.24.2': + optional: true + '@esbuild/linux-ppc64@0.19.12': optional: true '@esbuild/linux-ppc64@0.21.5': optional: true + '@esbuild/linux-ppc64@0.24.2': + optional: true + '@esbuild/linux-riscv64@0.19.12': optional: true '@esbuild/linux-riscv64@0.21.5': optional: true + '@esbuild/linux-riscv64@0.24.2': + optional: true + '@esbuild/linux-s390x@0.19.12': optional: true '@esbuild/linux-s390x@0.21.5': optional: true + '@esbuild/linux-s390x@0.24.2': + optional: true + '@esbuild/linux-x64@0.19.12': optional: true '@esbuild/linux-x64@0.21.5': optional: true + '@esbuild/linux-x64@0.24.2': + optional: true + + '@esbuild/netbsd-arm64@0.24.2': + optional: true + '@esbuild/netbsd-x64@0.19.12': optional: true '@esbuild/netbsd-x64@0.21.5': optional: true + '@esbuild/netbsd-x64@0.24.2': + optional: true + + '@esbuild/openbsd-arm64@0.24.2': + optional: true + '@esbuild/openbsd-x64@0.19.12': optional: true '@esbuild/openbsd-x64@0.21.5': optional: true + '@esbuild/openbsd-x64@0.24.2': + optional: true + '@esbuild/sunos-x64@0.19.12': optional: true '@esbuild/sunos-x64@0.21.5': optional: true + '@esbuild/sunos-x64@0.24.2': + optional: true + '@esbuild/win32-arm64@0.19.12': optional: true '@esbuild/win32-arm64@0.21.5': optional: true + '@esbuild/win32-arm64@0.24.2': + optional: true + '@esbuild/win32-ia32@0.19.12': optional: true '@esbuild/win32-ia32@0.21.5': optional: true + '@esbuild/win32-ia32@0.24.2': + optional: true + '@esbuild/win32-x64@0.19.12': optional: true '@esbuild/win32-x64@0.21.5': optional: true - '@event-calendar/core@3.7.1': + '@esbuild/win32-x64@0.24.2': + optional: true + + '@event-calendar/core@3.12.0': dependencies: svelte: 4.2.19 - '@event-calendar/day-grid@3.7.1': + '@event-calendar/day-grid@3.12.0': dependencies: - '@event-calendar/core': 3.7.1 + '@event-calendar/core': 3.12.0 svelte: 4.2.19 - '@event-calendar/time-grid@3.7.1': + '@event-calendar/time-grid@3.12.0': dependencies: - '@event-calendar/core': 3.7.1 + '@event-calendar/core': 3.12.0 svelte: 4.2.19 - '@formatjs/ecma402-abstract@2.2.1': + '@formatjs/ecma402-abstract@2.3.4': dependencies: - '@formatjs/fast-memoize': 2.2.2 - '@formatjs/intl-localematcher': 0.5.6 - tslib: 2.6.3 + '@formatjs/fast-memoize': 2.2.7 + '@formatjs/intl-localematcher': 0.6.1 + decimal.js: 10.5.0 + tslib: 2.8.1 - '@formatjs/fast-memoize@2.2.2': + '@formatjs/fast-memoize@2.2.7': dependencies: - tslib: 2.6.3 + tslib: 2.8.1 - '@formatjs/icu-messageformat-parser@2.9.1': + '@formatjs/icu-messageformat-parser@2.11.2': dependencies: - '@formatjs/ecma402-abstract': 2.2.1 - '@formatjs/icu-skeleton-parser': 1.8.5 - tslib: 2.6.3 + '@formatjs/ecma402-abstract': 2.3.4 + '@formatjs/icu-skeleton-parser': 1.8.14 + tslib: 2.8.1 - '@formatjs/icu-skeleton-parser@1.8.5': + '@formatjs/icu-skeleton-parser@1.8.14': dependencies: - '@formatjs/ecma402-abstract': 2.2.1 - tslib: 2.6.3 + '@formatjs/ecma402-abstract': 2.3.4 + tslib: 2.8.1 - '@formatjs/intl-localematcher@0.5.6': + '@formatjs/intl-localematcher@0.6.1': dependencies: - tslib: 2.6.3 + tslib: 2.8.1 - '@iconify-json/mdi@1.1.67': + '@iconify-json/mdi@1.2.3': dependencies: '@iconify/types': 2.0.0 '@iconify/types@2.0.0': {} - '@iconify/utils@2.1.25': + '@iconify/utils@2.3.0': dependencies: - '@antfu/install-pkg': 0.1.1 - '@antfu/utils': 0.7.8 + '@antfu/install-pkg': 1.0.0 + '@antfu/utils': 8.1.1 '@iconify/types': 2.0.0 - debug: 4.3.5 + debug: 4.4.0 + globals: 15.15.0 kolorist: 1.8.0 - local-pkg: 0.5.0 - mlly: 1.7.1 + local-pkg: 1.1.1 + mlly: 1.7.4 transitivePeerDependencies: - supports-color @@ -2487,29 +2464,26 @@ snapshots: wrap-ansi: 8.1.0 wrap-ansi-cjs: wrap-ansi@7.0.0 - '@jridgewell/gen-mapping@0.3.5': + '@isaacs/fs-minipass@4.0.1': + dependencies: + minipass: 7.1.2 + + '@jridgewell/gen-mapping@0.3.8': dependencies: '@jridgewell/set-array': 1.2.1 - '@jridgewell/sourcemap-codec': 1.4.15 + '@jridgewell/sourcemap-codec': 1.5.0 '@jridgewell/trace-mapping': 0.3.25 '@jridgewell/resolve-uri@3.1.2': {} '@jridgewell/set-array@1.2.1': {} - '@jridgewell/sourcemap-codec@1.4.15': {} + '@jridgewell/sourcemap-codec@1.5.0': {} '@jridgewell/trace-mapping@0.3.25': dependencies: '@jridgewell/resolve-uri': 3.1.2 - '@jridgewell/sourcemap-codec': 1.4.15 - - '@jsdevtools/ez-spawn@3.0.4': - dependencies: - call-me-maybe: 1.0.2 - cross-spawn: 7.0.6 - string-argv: 0.3.2 - type-detect: 4.0.8 + '@jridgewell/sourcemap-codec': 1.5.0 '@lukulent/svelte-umami@0.0.3(svelte@4.2.19)': dependencies: @@ -2522,17 +2496,15 @@ snapshots: '@mapbox/jsonlint-lines-primitives@2.0.2': {} - '@mapbox/node-pre-gyp@1.0.11': + '@mapbox/node-pre-gyp@2.0.0': dependencies: - detect-libc: 2.0.3 - https-proxy-agent: 5.0.1 - make-dir: 3.1.0 + consola: 3.4.2 + detect-libc: 2.0.4 + https-proxy-agent: 7.0.6 node-fetch: 2.7.0 - nopt: 5.0.0 - npmlog: 5.0.1 - rimraf: 3.0.2 - semver: 7.6.2 - tar: 6.2.1 + nopt: 8.1.0 + semver: 7.7.1 + tar: 7.4.3 transitivePeerDependencies: - encoding - supports-color @@ -2555,7 +2527,7 @@ snapshots: '@mapbox/whoots-js@3.1.0': {} - '@maplibre/maplibre-gl-style-spec@20.3.0': + '@maplibre/maplibre-gl-style-spec@20.4.0': dependencies: '@mapbox/jsonlint-lines-primitives': 2.0.2 '@mapbox/unitbezier': 0.0.1 @@ -2563,8 +2535,7 @@ snapshots: minimist: 1.2.8 quickselect: 2.0.0 rw: 1.3.3 - sort-object: 3.0.3 - tinyqueue: 2.0.3 + tinyqueue: 3.0.0 '@nodelib/fs.scandir@2.1.5': dependencies: @@ -2576,252 +2547,200 @@ snapshots: '@nodelib/fs.walk@1.2.8': dependencies: '@nodelib/fs.scandir': 2.1.5 - fastq: 1.17.1 + fastq: 1.19.1 '@pkgjs/parseargs@0.11.0': optional: true - '@polka/url@1.0.0-next.25': {} + '@polka/url@1.0.0-next.29': {} - '@rollup/plugin-commonjs@26.0.1(rollup@4.24.0)': + '@rollup/plugin-commonjs@28.0.3(rollup@4.40.2)': dependencies: - '@rollup/pluginutils': 5.1.0(rollup@4.24.0) + '@rollup/pluginutils': 5.1.4(rollup@4.40.2) commondir: 1.0.1 estree-walker: 2.0.2 - glob: 10.4.2 + fdir: 6.4.4(picomatch@4.0.2) is-reference: 1.2.1 - magic-string: 0.30.10 + magic-string: 0.30.17 + picomatch: 4.0.2 optionalDependencies: - rollup: 4.24.0 + rollup: 4.40.2 - '@rollup/plugin-json@6.1.0(rollup@4.24.0)': + '@rollup/plugin-json@6.1.0(rollup@4.40.2)': dependencies: - '@rollup/pluginutils': 5.1.0(rollup@4.24.0) + '@rollup/pluginutils': 5.1.4(rollup@4.40.2) optionalDependencies: - rollup: 4.24.0 + rollup: 4.40.2 - '@rollup/plugin-node-resolve@15.2.3(rollup@4.24.0)': + '@rollup/plugin-node-resolve@16.0.1(rollup@4.40.2)': dependencies: - '@rollup/pluginutils': 5.1.0(rollup@4.24.0) + '@rollup/pluginutils': 5.1.4(rollup@4.40.2) '@types/resolve': 1.20.2 deepmerge: 4.3.1 - is-builtin-module: 3.2.1 is-module: 1.0.0 - resolve: 1.22.8 + resolve: 1.22.10 optionalDependencies: - rollup: 4.24.0 - - '@rollup/pluginutils@4.2.1': - dependencies: - estree-walker: 2.0.2 - picomatch: 2.3.1 + rollup: 4.40.2 - '@rollup/pluginutils@5.1.0(rollup@4.24.0)': + '@rollup/pluginutils@5.1.4(rollup@4.40.2)': dependencies: - '@types/estree': 1.0.6 + '@types/estree': 1.0.7 estree-walker: 2.0.2 - picomatch: 2.3.1 + picomatch: 4.0.2 optionalDependencies: - rollup: 4.24.0 - - '@rollup/rollup-android-arm-eabi@4.24.0': - optional: true - - '@rollup/rollup-android-arm-eabi@4.31.0': - optional: true - - '@rollup/rollup-android-arm64@4.24.0': - optional: true - - '@rollup/rollup-android-arm64@4.31.0': - optional: true - - '@rollup/rollup-darwin-arm64@4.24.0': - optional: true + rollup: 4.40.2 - '@rollup/rollup-darwin-arm64@4.31.0': + '@rollup/rollup-android-arm-eabi@4.40.2': optional: true - '@rollup/rollup-darwin-x64@4.24.0': + '@rollup/rollup-android-arm64@4.40.2': optional: true - '@rollup/rollup-darwin-x64@4.31.0': + '@rollup/rollup-darwin-arm64@4.40.2': optional: true - '@rollup/rollup-freebsd-arm64@4.31.0': + '@rollup/rollup-darwin-x64@4.40.2': optional: true - '@rollup/rollup-freebsd-x64@4.31.0': + '@rollup/rollup-freebsd-arm64@4.40.2': optional: true - '@rollup/rollup-linux-arm-gnueabihf@4.24.0': + '@rollup/rollup-freebsd-x64@4.40.2': optional: true - '@rollup/rollup-linux-arm-gnueabihf@4.31.0': + '@rollup/rollup-linux-arm-gnueabihf@4.40.2': optional: true - '@rollup/rollup-linux-arm-musleabihf@4.24.0': + '@rollup/rollup-linux-arm-musleabihf@4.40.2': optional: true - '@rollup/rollup-linux-arm-musleabihf@4.31.0': + '@rollup/rollup-linux-arm64-gnu@4.40.2': optional: true - '@rollup/rollup-linux-arm64-gnu@4.24.0': + '@rollup/rollup-linux-arm64-musl@4.40.2': optional: true - '@rollup/rollup-linux-arm64-gnu@4.31.0': + '@rollup/rollup-linux-loongarch64-gnu@4.40.2': optional: true - '@rollup/rollup-linux-arm64-musl@4.24.0': + '@rollup/rollup-linux-powerpc64le-gnu@4.40.2': optional: true - '@rollup/rollup-linux-arm64-musl@4.31.0': + '@rollup/rollup-linux-riscv64-gnu@4.40.2': optional: true - '@rollup/rollup-linux-loongarch64-gnu@4.31.0': + '@rollup/rollup-linux-riscv64-musl@4.40.2': optional: true - '@rollup/rollup-linux-powerpc64le-gnu@4.24.0': + '@rollup/rollup-linux-s390x-gnu@4.40.2': optional: true - '@rollup/rollup-linux-powerpc64le-gnu@4.31.0': + '@rollup/rollup-linux-x64-gnu@4.40.2': optional: true - '@rollup/rollup-linux-riscv64-gnu@4.24.0': + '@rollup/rollup-linux-x64-musl@4.40.2': optional: true - '@rollup/rollup-linux-riscv64-gnu@4.31.0': + '@rollup/rollup-win32-arm64-msvc@4.40.2': optional: true - '@rollup/rollup-linux-s390x-gnu@4.24.0': + '@rollup/rollup-win32-ia32-msvc@4.40.2': optional: true - '@rollup/rollup-linux-s390x-gnu@4.31.0': + '@rollup/rollup-win32-x64-msvc@4.40.2': optional: true - '@rollup/rollup-linux-x64-gnu@4.24.0': - optional: true - - '@rollup/rollup-linux-x64-gnu@4.31.0': - optional: true - - '@rollup/rollup-linux-x64-musl@4.24.0': - optional: true - - '@rollup/rollup-linux-x64-musl@4.31.0': - optional: true - - '@rollup/rollup-win32-arm64-msvc@4.24.0': - optional: true - - '@rollup/rollup-win32-arm64-msvc@4.31.0': - optional: true - - '@rollup/rollup-win32-ia32-msvc@4.24.0': - optional: true - - '@rollup/rollup-win32-ia32-msvc@4.31.0': - optional: true - - '@rollup/rollup-win32-x64-msvc@4.24.0': - optional: true - - '@rollup/rollup-win32-x64-msvc@4.31.0': - optional: true - - '@sveltejs/adapter-node@5.2.0(@sveltejs/kit@2.8.3(@sveltejs/vite-plugin-svelte@3.1.1(svelte@4.2.19)(vite@5.4.12(@types/node@22.5.4)))(svelte@4.2.19)(vite@5.4.12(@types/node@22.5.4)))': + '@sveltejs/adapter-node@5.2.12(@sveltejs/kit@2.20.7(@sveltejs/vite-plugin-svelte@3.1.2(svelte@4.2.19)(vite@5.4.19(@types/node@22.15.2)))(svelte@4.2.19)(vite@5.4.19(@types/node@22.15.2)))': dependencies: - '@rollup/plugin-commonjs': 26.0.1(rollup@4.24.0) - '@rollup/plugin-json': 6.1.0(rollup@4.24.0) - '@rollup/plugin-node-resolve': 15.2.3(rollup@4.24.0) - '@sveltejs/kit': 2.8.3(@sveltejs/vite-plugin-svelte@3.1.1(svelte@4.2.19)(vite@5.4.12(@types/node@22.5.4)))(svelte@4.2.19)(vite@5.4.12(@types/node@22.5.4)) - rollup: 4.24.0 + '@rollup/plugin-commonjs': 28.0.3(rollup@4.40.2) + '@rollup/plugin-json': 6.1.0(rollup@4.40.2) + '@rollup/plugin-node-resolve': 16.0.1(rollup@4.40.2) + '@sveltejs/kit': 2.20.7(@sveltejs/vite-plugin-svelte@3.1.2(svelte@4.2.19)(vite@5.4.19(@types/node@22.15.2)))(svelte@4.2.19)(vite@5.4.19(@types/node@22.15.2)) + rollup: 4.40.2 - '@sveltejs/adapter-vercel@5.4.1(@sveltejs/kit@2.8.3(@sveltejs/vite-plugin-svelte@3.1.1(svelte@4.2.19)(vite@5.4.12(@types/node@22.5.4)))(svelte@4.2.19)(vite@5.4.12(@types/node@22.5.4)))': + '@sveltejs/adapter-vercel@5.7.0(@sveltejs/kit@2.20.7(@sveltejs/vite-plugin-svelte@3.1.2(svelte@4.2.19)(vite@5.4.19(@types/node@22.15.2)))(svelte@4.2.19)(vite@5.4.19(@types/node@22.15.2)))(rollup@4.40.2)': dependencies: - '@sveltejs/kit': 2.8.3(@sveltejs/vite-plugin-svelte@3.1.1(svelte@4.2.19)(vite@5.4.12(@types/node@22.5.4)))(svelte@4.2.19)(vite@5.4.12(@types/node@22.5.4)) - '@vercel/nft': 0.27.2 - esbuild: 0.21.5 + '@sveltejs/kit': 2.20.7(@sveltejs/vite-plugin-svelte@3.1.2(svelte@4.2.19)(vite@5.4.19(@types/node@22.15.2)))(svelte@4.2.19)(vite@5.4.19(@types/node@22.15.2)) + '@vercel/nft': 0.29.2(rollup@4.40.2) + esbuild: 0.24.2 transitivePeerDependencies: - encoding + - rollup - supports-color - '@sveltejs/kit@2.8.3(@sveltejs/vite-plugin-svelte@3.1.1(svelte@4.2.19)(vite@5.4.12(@types/node@22.5.4)))(svelte@4.2.19)(vite@5.4.12(@types/node@22.5.4))': + '@sveltejs/kit@2.20.7(@sveltejs/vite-plugin-svelte@3.1.2(svelte@4.2.19)(vite@5.4.19(@types/node@22.15.2)))(svelte@4.2.19)(vite@5.4.19(@types/node@22.15.2))': dependencies: - '@sveltejs/vite-plugin-svelte': 3.1.1(svelte@4.2.19)(vite@5.4.12(@types/node@22.5.4)) + '@sveltejs/vite-plugin-svelte': 3.1.2(svelte@4.2.19)(vite@5.4.19(@types/node@22.15.2)) '@types/cookie': 0.6.0 cookie: 0.6.0 devalue: 5.1.1 - esm-env: 1.0.0 + esm-env: 1.2.2 import-meta-resolve: 4.1.0 kleur: 4.1.5 - magic-string: 0.30.10 - mrmime: 2.0.0 + magic-string: 0.30.17 + mrmime: 2.0.1 sade: 1.8.1 - set-cookie-parser: 2.6.0 - sirv: 3.0.0 + set-cookie-parser: 2.7.1 + sirv: 3.0.1 svelte: 4.2.19 - tiny-glob: 0.2.9 - vite: 5.4.12(@types/node@22.5.4) + vite: 5.4.19(@types/node@22.15.2) - '@sveltejs/vite-plugin-svelte-inspector@2.1.0(@sveltejs/vite-plugin-svelte@3.1.1(svelte@4.2.19)(vite@5.4.12(@types/node@22.5.4)))(svelte@4.2.19)(vite@5.4.12(@types/node@22.5.4))': + '@sveltejs/vite-plugin-svelte-inspector@2.1.0(@sveltejs/vite-plugin-svelte@3.1.2(svelte@4.2.19)(vite@5.4.19(@types/node@22.15.2)))(svelte@4.2.19)(vite@5.4.19(@types/node@22.15.2))': dependencies: - '@sveltejs/vite-plugin-svelte': 3.1.1(svelte@4.2.19)(vite@5.4.12(@types/node@22.5.4)) - debug: 4.3.5 + '@sveltejs/vite-plugin-svelte': 3.1.2(svelte@4.2.19)(vite@5.4.19(@types/node@22.15.2)) + debug: 4.4.0 svelte: 4.2.19 - vite: 5.4.12(@types/node@22.5.4) + vite: 5.4.19(@types/node@22.15.2) transitivePeerDependencies: - supports-color - '@sveltejs/vite-plugin-svelte@3.1.1(svelte@4.2.19)(vite@5.4.12(@types/node@22.5.4))': + '@sveltejs/vite-plugin-svelte@3.1.2(svelte@4.2.19)(vite@5.4.19(@types/node@22.15.2))': dependencies: - '@sveltejs/vite-plugin-svelte-inspector': 2.1.0(@sveltejs/vite-plugin-svelte@3.1.1(svelte@4.2.19)(vite@5.4.12(@types/node@22.5.4)))(svelte@4.2.19)(vite@5.4.12(@types/node@22.5.4)) - debug: 4.3.5 + '@sveltejs/vite-plugin-svelte-inspector': 2.1.0(@sveltejs/vite-plugin-svelte@3.1.2(svelte@4.2.19)(vite@5.4.19(@types/node@22.15.2)))(svelte@4.2.19)(vite@5.4.19(@types/node@22.15.2)) + debug: 4.4.0 deepmerge: 4.3.1 kleur: 4.1.5 - magic-string: 0.30.10 + magic-string: 0.30.17 svelte: 4.2.19 svelte-hmr: 0.16.0(svelte@4.2.19) - vite: 5.4.12(@types/node@22.5.4) - vitefu: 0.2.5(vite@5.4.12(@types/node@22.5.4)) + vite: 5.4.19(@types/node@22.15.2) + vitefu: 0.2.5(vite@5.4.19(@types/node@22.15.2)) transitivePeerDependencies: - supports-color - '@tailwindcss/typography@0.5.13(tailwindcss@3.4.4)': + '@tailwindcss/typography@0.5.16(tailwindcss@3.4.17)': dependencies: lodash.castarray: 4.4.0 lodash.isplainobject: 4.0.6 lodash.merge: 4.6.2 postcss-selector-parser: 6.0.10 - tailwindcss: 3.4.4 + tailwindcss: 3.4.17 '@types/cookie@0.6.0': {} - '@types/estree@1.0.6': {} + '@types/estree@1.0.7': {} '@types/geojson-vt@3.2.5': dependencies: - '@types/geojson': 7946.0.14 + '@types/geojson': 7946.0.16 - '@types/geojson@7946.0.14': {} + '@types/geojson@7946.0.16': {} - '@types/junit-report-builder@3.0.2': {} - - '@types/leaflet@1.9.12': + '@types/leaflet@1.9.17': dependencies: - '@types/geojson': 7946.0.14 + '@types/geojson': 7946.0.16 '@types/mapbox__point-geometry@0.1.4': {} '@types/mapbox__vector-tile@1.3.4': dependencies: - '@types/geojson': 7946.0.14 + '@types/geojson': 7946.0.16 '@types/mapbox__point-geometry': 0.1.4 '@types/pbf': 3.0.5 - '@types/node@22.5.4': + '@types/node@22.15.2': dependencies: - undici-types: 6.19.8 + undici-types: 6.21.0 '@types/pbf@3.0.5': {} @@ -2829,54 +2748,51 @@ snapshots: '@types/qrcode@1.5.5': dependencies: - '@types/node': 22.5.4 + '@types/node': 22.15.2 '@types/resolve@1.20.2': {} '@types/supercluster@7.1.3': dependencies: - '@types/geojson': 7946.0.14 + '@types/geojson': 7946.0.16 '@types/trusted-types@2.0.7': optional: true - '@vercel/nft@0.27.2': + '@vercel/nft@0.29.2(rollup@4.40.2)': dependencies: - '@mapbox/node-pre-gyp': 1.0.11 - '@rollup/pluginutils': 4.2.1 - acorn: 8.12.0 - acorn-import-attributes: 1.9.5(acorn@8.12.0) + '@mapbox/node-pre-gyp': 2.0.0 + '@rollup/pluginutils': 5.1.4(rollup@4.40.2) + acorn: 8.14.1 + acorn-import-attributes: 1.9.5(acorn@8.14.1) async-sema: 3.1.1 bindings: 1.5.0 estree-walker: 2.0.2 - glob: 7.2.3 + glob: 10.4.5 graceful-fs: 4.2.11 - micromatch: 4.0.8 - node-gyp-build: 4.8.1 + node-gyp-build: 4.8.4 + picomatch: 4.0.2 resolve-from: 5.0.0 transitivePeerDependencies: - encoding + - rollup - supports-color '@xmldom/xmldom@0.8.10': {} - abbrev@1.1.1: {} + abbrev@3.0.1: {} - acorn-import-attributes@1.9.5(acorn@8.12.0): + acorn-import-attributes@1.9.5(acorn@8.14.1): dependencies: - acorn: 8.12.0 + acorn: 8.14.1 - acorn@8.12.0: {} + acorn@8.14.1: {} - agent-base@6.0.2: - dependencies: - debug: 4.3.5 - transitivePeerDependencies: - - supports-color + agent-base@7.1.3: {} ansi-regex@5.0.1: {} - ansi-regex@6.0.1: {} + ansi-regex@6.1.0: {} ansi-styles@4.3.0: dependencies: @@ -2891,38 +2807,23 @@ snapshots: normalize-path: 3.0.0 picomatch: 2.3.1 - aproba@2.0.0: {} - - are-we-there-yet@2.0.0: - dependencies: - delegates: 1.0.0 - readable-stream: 3.6.2 - arg@5.0.2: {} - aria-query@5.3.0: - dependencies: - dequal: 2.0.3 - - arr-union@3.1.0: {} - - assign-symbols@1.0.0: {} + aria-query@5.3.2: {} async-sema@3.1.1: {} - autoprefixer@10.4.19(postcss@8.4.38): + autoprefixer@10.4.21(postcss@8.5.3): dependencies: - browserslist: 4.23.1 - caniuse-lite: 1.0.30001688 + browserslist: 4.24.4 + caniuse-lite: 1.0.30001715 fraction.js: 4.3.7 normalize-range: 0.1.2 - picocolors: 1.0.1 - postcss: 8.4.38 + picocolors: 1.1.1 + postcss: 8.5.3 postcss-value-parser: 4.2.0 - axobject-query@4.0.0: - dependencies: - dequal: 2.0.3 + axobject-query@4.1.0: {} balanced-match@1.0.2: {} @@ -2945,37 +2846,22 @@ snapshots: dependencies: fill-range: 7.1.1 - browserslist@4.23.1: + browserslist@4.24.4: dependencies: - caniuse-lite: 1.0.30001688 - electron-to-chromium: 1.4.810 - node-releases: 2.0.14 - update-browserslist-db: 1.0.16(browserslist@4.23.1) + caniuse-lite: 1.0.30001715 + electron-to-chromium: 1.5.143 + node-releases: 2.0.19 + update-browserslist-db: 1.1.3(browserslist@4.24.4) buffer-crc32@1.0.0: {} buffer-from@1.1.2: {} - builtin-modules@3.3.0: {} - - bytewise-core@1.2.3: - dependencies: - typewise-core: 1.2.0 - - bytewise@1.1.0: - dependencies: - bytewise-core: 1.2.3 - typewise: 1.0.3 - - call-me-maybe@1.0.2: {} - - callsites@3.1.0: {} - camelcase-css@2.0.1: {} camelcase@5.3.1: {} - caniuse-lite@1.0.30001688: {} + caniuse-lite@1.0.30001715: {} chokidar@3.6.0: dependencies: @@ -2989,7 +2875,7 @@ snapshots: optionalDependencies: fsevents: 2.3.3 - chownr@2.0.0: {} + chownr@3.0.0: {} cli-color@2.0.4: dependencies: @@ -3007,9 +2893,9 @@ snapshots: code-red@1.0.4: dependencies: - '@jridgewell/sourcemap-codec': 1.4.15 - '@types/estree': 1.0.6 - acorn: 8.12.0 + '@jridgewell/sourcemap-codec': 1.5.0 + '@types/estree': 1.0.7 + acorn: 8.14.1 estree-walker: 3.0.3 periscopic: 3.1.0 @@ -3019,8 +2905,6 @@ snapshots: color-name@1.1.4: {} - color-support@1.1.3: {} - commander@4.1.1: {} commondir@1.0.1: {} @@ -3034,9 +2918,11 @@ snapshots: readable-stream: 3.6.2 typedarray: 0.0.6 - confbox@0.1.7: {} + confbox@0.1.8: {} + + confbox@0.2.2: {} - console-control-strings@1.1.0: {} + consola@3.4.2: {} cookie@0.6.0: {} @@ -3054,7 +2940,7 @@ snapshots: css-tree@2.3.1: dependencies: mdn-data: 2.0.30 - source-map-js: 1.2.0 + source-map-js: 1.2.1 cssesc@3.0.0: {} @@ -3073,30 +2959,30 @@ snapshots: es5-ext: 0.10.64 type: 2.7.3 - daisyui@4.12.6(postcss@8.4.38): + daisyui@4.12.24(postcss@8.5.3): dependencies: css-selector-tokenizer: 0.8.0 culori: 3.3.0 - picocolors: 1.0.1 - postcss-js: 4.0.1(postcss@8.4.38) + picocolors: 1.1.1 + postcss-js: 4.0.1(postcss@8.5.3) transitivePeerDependencies: - postcss - debug@4.3.5: + debug@4.4.0: dependencies: - ms: 2.1.2 + ms: 2.1.3 decamelize@1.2.0: {} - deepmerge@4.3.1: {} + decimal.js@10.5.0: {} - delegates@1.0.0: {} + deepmerge@4.3.1: {} dequal@2.0.3: {} detect-indent@6.1.0: {} - detect-libc@2.0.3: {} + detect-libc@2.0.4: {} devalue@5.1.1: {} @@ -3106,17 +2992,17 @@ snapshots: dlv@1.1.3: {} - dompurify@3.2.4: + dompurify@3.2.5: optionalDependencies: '@types/trusted-types': 2.0.7 - earcut@2.2.4: {} + earcut@3.0.1: {} eastasianwidth@0.2.0: {} - electron-to-chromium@1.4.810: {} + electron-to-chromium@1.5.143: {} - emoji-picker-element@1.26.0: {} + emoji-picker-element@1.26.3: {} emoji-regex@8.0.0: {} @@ -3201,9 +3087,37 @@ snapshots: '@esbuild/win32-ia32': 0.21.5 '@esbuild/win32-x64': 0.21.5 - escalade@3.1.2: {} - - esm-env@1.0.0: {} + esbuild@0.24.2: + optionalDependencies: + '@esbuild/aix-ppc64': 0.24.2 + '@esbuild/android-arm': 0.24.2 + '@esbuild/android-arm64': 0.24.2 + '@esbuild/android-x64': 0.24.2 + '@esbuild/darwin-arm64': 0.24.2 + '@esbuild/darwin-x64': 0.24.2 + '@esbuild/freebsd-arm64': 0.24.2 + '@esbuild/freebsd-x64': 0.24.2 + '@esbuild/linux-arm': 0.24.2 + '@esbuild/linux-arm64': 0.24.2 + '@esbuild/linux-ia32': 0.24.2 + '@esbuild/linux-loong64': 0.24.2 + '@esbuild/linux-mips64el': 0.24.2 + '@esbuild/linux-ppc64': 0.24.2 + '@esbuild/linux-riscv64': 0.24.2 + '@esbuild/linux-s390x': 0.24.2 + '@esbuild/linux-x64': 0.24.2 + '@esbuild/netbsd-arm64': 0.24.2 + '@esbuild/netbsd-x64': 0.24.2 + '@esbuild/openbsd-arm64': 0.24.2 + '@esbuild/openbsd-x64': 0.24.2 + '@esbuild/sunos-x64': 0.24.2 + '@esbuild/win32-arm64': 0.24.2 + '@esbuild/win32-ia32': 0.24.2 + '@esbuild/win32-x64': 0.24.2 + + escalade@3.2.0: {} + + esm-env@1.2.2: {} esniff@2.0.1: dependencies: @@ -3216,39 +3130,20 @@ snapshots: estree-walker@3.0.3: dependencies: - '@types/estree': 1.0.6 + '@types/estree': 1.0.7 event-emitter@0.3.5: dependencies: d: 1.0.2 es5-ext: 0.10.64 - execa@5.1.1: - dependencies: - cross-spawn: 7.0.6 - get-stream: 6.0.1 - human-signals: 2.1.0 - is-stream: 2.0.1 - merge-stream: 2.0.0 - npm-run-path: 4.0.1 - onetime: 5.1.2 - signal-exit: 3.0.7 - strip-final-newline: 2.0.0 + exsolve@1.0.5: {} ext@1.7.0: dependencies: type: 2.7.3 - extend-shallow@2.0.1: - dependencies: - is-extendable: 0.1.1 - - extend-shallow@3.0.2: - dependencies: - assign-symbols: 1.0.0 - is-extendable: 1.0.1 - - fast-glob@3.3.2: + fast-glob@3.3.3: dependencies: '@nodelib/fs.stat': 2.0.5 '@nodelib/fs.walk': 1.2.8 @@ -3258,9 +3153,13 @@ snapshots: fastparse@1.1.2: {} - fastq@1.17.1: + fastq@1.19.1: dependencies: - reusify: 1.0.4 + reusify: 1.1.0 + + fdir@6.4.4(picomatch@4.0.2): + optionalDependencies: + picomatch: 4.0.2 fflate@0.8.2: {} @@ -3275,22 +3174,13 @@ snapshots: locate-path: 5.0.0 path-exists: 4.0.0 - find-up@5.0.0: - dependencies: - locate-path: 6.0.0 - path-exists: 4.0.0 - - foreground-child@3.2.1: + foreground-child@3.3.1: dependencies: cross-spawn: 7.0.6 signal-exit: 4.1.0 fraction.js@4.3.7: {} - fs-minipass@2.1.0: - dependencies: - minipass: 3.3.6 - fs.realpath@1.0.0: {} fsevents@2.3.3: @@ -3298,26 +3188,12 @@ snapshots: function-bind@1.1.2: {} - gauge@3.0.2: - dependencies: - aproba: 2.0.0 - color-support: 1.1.3 - console-control-strings: 1.1.0 - has-unicode: 2.0.1 - object-assign: 4.1.1 - signal-exit: 3.0.7 - string-width: 4.2.3 - strip-ansi: 6.0.1 - wide-align: 1.1.5 - geojson-vt@4.0.2: {} get-caller-file@2.0.5: {} get-stream@6.0.1: {} - get-value@2.0.6: {} - gl-matrix@3.4.3: {} glob-parent@5.1.2: @@ -3328,13 +3204,13 @@ snapshots: dependencies: is-glob: 4.0.3 - glob@10.4.2: + glob@10.4.5: dependencies: - foreground-child: 3.2.1 - jackspeak: 3.4.0 - minimatch: 9.0.4 + foreground-child: 3.3.1 + jackspeak: 3.4.3 + minimatch: 9.0.5 minipass: 7.1.2 - package-json-from-dist: 1.0.0 + package-json-from-dist: 1.0.1 path-scurry: 1.11.1 glob@7.2.3: @@ -3346,11 +3222,13 @@ snapshots: once: 1.4.0 path-is-absolute: 1.0.1 - global-prefix@3.0.0: + global-prefix@4.0.0: dependencies: - ini: 1.3.8 + ini: 4.1.3 kind-of: 6.0.3 - which: 1.3.1 + which: 4.0.0 + + globals@15.15.0: {} globalyzer@0.1.0: {} @@ -3360,28 +3238,19 @@ snapshots: gsap@3.12.7: {} - has-unicode@2.0.1: {} - hasown@2.0.2: dependencies: function-bind: 1.1.2 - https-proxy-agent@5.0.1: + https-proxy-agent@7.0.6: dependencies: - agent-base: 6.0.2 - debug: 4.3.5 + agent-base: 7.1.3 + debug: 4.4.0 transitivePeerDependencies: - supports-color - human-signals@2.1.0: {} - ieee754@1.2.1: {} - import-fresh@3.3.0: - dependencies: - parent-module: 1.0.1 - resolve-from: 4.0.0 - import-meta-resolve@4.1.0: {} inflight@1.0.6: @@ -3391,35 +3260,25 @@ snapshots: inherits@2.0.4: {} - ini@1.3.8: {} + ini@4.1.3: {} internmap@2.0.3: {} - intl-messageformat@10.7.3: + intl-messageformat@10.7.16: dependencies: - '@formatjs/ecma402-abstract': 2.2.1 - '@formatjs/fast-memoize': 2.2.2 - '@formatjs/icu-messageformat-parser': 2.9.1 - tslib: 2.6.3 + '@formatjs/ecma402-abstract': 2.3.4 + '@formatjs/fast-memoize': 2.2.7 + '@formatjs/icu-messageformat-parser': 2.11.2 + tslib: 2.8.1 is-binary-path@2.1.0: dependencies: binary-extensions: 2.3.0 - is-builtin-module@3.2.1: - dependencies: - builtin-modules: 3.3.0 - - is-core-module@2.14.0: + is-core-module@2.16.1: dependencies: hasown: 2.0.2 - is-extendable@0.1.1: {} - - is-extendable@1.0.1: - dependencies: - is-plain-object: 2.0.4 - is-extglob@2.1.1: {} is-fullwidth-code-point@3.0.0: {} @@ -3432,33 +3291,27 @@ snapshots: is-number@7.0.0: {} - is-plain-object@2.0.4: - dependencies: - isobject: 3.0.1 - is-promise@2.2.2: {} is-reference@1.2.1: dependencies: - '@types/estree': 1.0.6 + '@types/estree': 1.0.7 - is-reference@3.0.2: + is-reference@3.0.3: dependencies: - '@types/estree': 1.0.6 - - is-stream@2.0.1: {} + '@types/estree': 1.0.7 isexe@2.0.0: {} - isobject@3.0.1: {} + isexe@3.1.1: {} - jackspeak@3.4.0: + jackspeak@3.4.3: dependencies: '@isaacs/cliui': 8.0.2 optionalDependencies: '@pkgjs/parseargs': 0.11.0 - jiti@1.21.6: {} + jiti@1.21.7: {} json-stringify-pretty-compact@4.0.0: {} @@ -3474,16 +3327,20 @@ snapshots: kolorist@1.8.0: {} - lilconfig@2.1.0: {} - - lilconfig@3.1.2: {} + lilconfig@3.1.3: {} lines-and-columns@1.2.4: {} - local-pkg@0.5.0: + local-pkg@0.5.1: dependencies: - mlly: 1.7.1 - pkg-types: 1.1.1 + mlly: 1.7.4 + pkg-types: 1.3.1 + + local-pkg@1.1.1: + dependencies: + mlly: 1.7.4 + pkg-types: 2.1.0 + quansync: 0.2.10 locate-character@3.0.0: {} @@ -3491,31 +3348,25 @@ snapshots: dependencies: p-locate: 4.1.0 - locate-path@6.0.0: - dependencies: - p-locate: 5.0.0 - lodash.castarray@4.4.0: {} lodash.isplainobject@4.0.6: {} lodash.merge@4.6.2: {} - lru-cache@10.2.2: {} + lru-cache@10.4.3: {} lru-queue@0.1.0: dependencies: es5-ext: 0.10.64 - magic-string@0.30.10: - dependencies: - '@jridgewell/sourcemap-codec': 1.4.15 + luxon@3.6.1: {} - make-dir@3.1.0: + magic-string@0.30.17: dependencies: - semver: 6.3.1 + '@jridgewell/sourcemap-codec': 1.5.0 - maplibre-gl@4.5.0: + maplibre-gl@4.7.1: dependencies: '@mapbox/geojson-rewind': 0.5.2 '@mapbox/jsonlint-lines-primitives': 2.0.2 @@ -3524,28 +3375,27 @@ snapshots: '@mapbox/unitbezier': 0.0.1 '@mapbox/vector-tile': 1.3.1 '@mapbox/whoots-js': 3.1.0 - '@maplibre/maplibre-gl-style-spec': 20.3.0 - '@types/geojson': 7946.0.14 + '@maplibre/maplibre-gl-style-spec': 20.4.0 + '@types/geojson': 7946.0.16 '@types/geojson-vt': 3.2.5 - '@types/junit-report-builder': 3.0.2 '@types/mapbox__point-geometry': 0.1.4 '@types/mapbox__vector-tile': 1.3.4 '@types/pbf': 3.0.5 '@types/supercluster': 7.1.3 - earcut: 2.2.4 + earcut: 3.0.1 geojson-vt: 4.0.2 gl-matrix: 3.4.3 - global-prefix: 3.0.0 + global-prefix: 4.0.0 kdbush: 4.0.2 murmurhash-js: 1.0.0 - pbf: 3.2.1 + pbf: 3.3.0 potpack: 2.0.0 - quickselect: 2.0.0 + quickselect: 3.0.0 supercluster: 8.0.1 - tinyqueue: 2.0.3 + tinyqueue: 3.0.0 vt-pbf: 3.1.3 - marked@15.0.4: {} + marked@15.0.11: {} mdn-data@2.0.30: {} @@ -3560,8 +3410,6 @@ snapshots: next-tick: 1.1.0 timers-ext: 0.1.8 - merge-stream@2.0.0: {} - merge2@1.4.1: {} micromatch@4.0.8: @@ -3569,51 +3417,42 @@ snapshots: braces: 3.0.3 picomatch: 2.3.1 - mimic-fn@2.1.0: {} - min-indent@1.0.1: {} minimatch@3.1.2: dependencies: brace-expansion: 1.1.11 - minimatch@9.0.4: + minimatch@9.0.5: dependencies: brace-expansion: 2.0.1 minimist@1.2.8: {} - minipass@3.3.6: - dependencies: - yallist: 4.0.0 - - minipass@5.0.0: {} - minipass@7.1.2: {} - minizlib@2.1.2: + minizlib@3.0.2: dependencies: - minipass: 3.3.6 - yallist: 4.0.0 + minipass: 7.1.2 mkdirp@0.5.6: dependencies: minimist: 1.2.8 - mkdirp@1.0.4: {} + mkdirp@3.0.1: {} - mlly@1.7.1: + mlly@1.7.4: dependencies: - acorn: 8.12.0 - pathe: 1.1.2 - pkg-types: 1.1.1 - ufo: 1.5.3 + acorn: 8.14.1 + pathe: 2.0.3 + pkg-types: 1.3.1 + ufo: 1.6.1 mri@1.2.0: {} - mrmime@2.0.0: {} + mrmime@2.0.1: {} - ms@2.1.2: {} + ms@2.1.3: {} murmurhash-js@1.0.0: {} @@ -3623,7 +3462,7 @@ snapshots: object-assign: 4.1.1 thenify-all: 1.6.0 - nanoid@3.3.8: {} + nanoid@3.3.11: {} next-tick@1.1.0: {} @@ -3631,29 +3470,18 @@ snapshots: dependencies: whatwg-url: 5.0.0 - node-gyp-build@4.8.1: {} + node-gyp-build@4.8.4: {} - node-releases@2.0.14: {} + node-releases@2.0.19: {} - nopt@5.0.0: + nopt@8.1.0: dependencies: - abbrev: 1.1.1 + abbrev: 3.0.1 normalize-path@3.0.0: {} normalize-range@0.1.2: {} - npm-run-path@4.0.1: - dependencies: - path-key: 3.1.1 - - npmlog@5.0.1: - dependencies: - are-we-there-yet: 2.0.0 - console-control-strings: 1.1.0 - gauge: 3.0.2 - set-blocking: 2.0.0 - object-assign@4.1.1: {} object-hash@3.0.0: {} @@ -3662,33 +3490,21 @@ snapshots: dependencies: wrappy: 1.0.2 - onetime@5.1.2: - dependencies: - mimic-fn: 2.1.0 - p-limit@2.3.0: dependencies: p-try: 2.2.0 - p-limit@3.1.0: - dependencies: - yocto-queue: 0.1.0 - p-locate@4.1.0: dependencies: p-limit: 2.3.0 - p-locate@5.0.0: - dependencies: - p-limit: 3.1.0 - p-try@2.2.0: {} - package-json-from-dist@1.0.0: {} + package-json-from-dist@1.0.1: {} - parent-module@1.0.1: + package-manager-detector@0.2.11: dependencies: - callsites: 3.1.0 + quansync: 0.2.10 path-exists@4.0.0: {} @@ -3700,101 +3516,101 @@ snapshots: path-scurry@1.11.1: dependencies: - lru-cache: 10.2.2 + lru-cache: 10.4.3 minipass: 7.1.2 - pathe@1.1.2: {} + pathe@2.0.3: {} - pbf@3.2.1: + pbf@3.3.0: dependencies: ieee754: 1.2.1 resolve-protobuf-schema: 2.1.0 periscopic@3.1.0: dependencies: - '@types/estree': 1.0.6 + '@types/estree': 1.0.7 estree-walker: 3.0.3 - is-reference: 3.0.2 - - picocolors@1.0.1: {} + is-reference: 3.0.3 picocolors@1.1.1: {} picomatch@2.3.1: {} + picomatch@4.0.2: {} + pify@2.3.0: {} - pirates@4.0.6: {} + pirates@4.0.7: {} + + pkg-types@1.3.1: + dependencies: + confbox: 0.1.8 + mlly: 1.7.4 + pathe: 2.0.3 - pkg-types@1.1.1: + pkg-types@2.1.0: dependencies: - confbox: 0.1.7 - mlly: 1.7.1 - pathe: 1.1.2 + confbox: 0.2.2 + exsolve: 1.0.5 + pathe: 2.0.3 - pmtiles@3.0.6: + pmtiles@3.2.1: dependencies: - '@types/leaflet': 1.9.12 + '@types/leaflet': 1.9.17 fflate: 0.8.2 pngjs@5.0.0: {} - postcss-import@15.1.0(postcss@8.4.38): + postcss-import@15.1.0(postcss@8.5.3): dependencies: - postcss: 8.4.38 + postcss: 8.5.3 postcss-value-parser: 4.2.0 read-cache: 1.0.0 - resolve: 1.22.8 + resolve: 1.22.10 - postcss-js@4.0.1(postcss@8.4.38): + postcss-js@4.0.1(postcss@8.5.3): dependencies: camelcase-css: 2.0.1 - postcss: 8.4.38 + postcss: 8.5.3 - postcss-load-config@4.0.2(postcss@8.4.38): + postcss-load-config@4.0.2(postcss@8.5.3): dependencies: - lilconfig: 3.1.2 - yaml: 2.4.5 + lilconfig: 3.1.3 + yaml: 2.7.1 optionalDependencies: - postcss: 8.4.38 + postcss: 8.5.3 - postcss-nested@6.0.1(postcss@8.4.38): + postcss-nested@6.2.0(postcss@8.5.3): dependencies: - postcss: 8.4.38 - postcss-selector-parser: 6.1.0 + postcss: 8.5.3 + postcss-selector-parser: 6.1.2 postcss-selector-parser@6.0.10: dependencies: cssesc: 3.0.0 util-deprecate: 1.0.2 - postcss-selector-parser@6.1.0: + postcss-selector-parser@6.1.2: dependencies: cssesc: 3.0.0 util-deprecate: 1.0.2 postcss-value-parser@4.2.0: {} - postcss@8.4.38: + postcss@8.5.3: dependencies: - nanoid: 3.3.8 - picocolors: 1.0.1 - source-map-js: 1.2.0 - - postcss@8.5.1: - dependencies: - nanoid: 3.3.8 + nanoid: 3.3.11 picocolors: 1.1.1 source-map-js: 1.2.1 potpack@2.0.0: {} - prettier-plugin-svelte@3.2.5(prettier@3.3.2)(svelte@4.2.19): + prettier-plugin-svelte@3.3.3(prettier@3.5.3)(svelte@4.2.19): dependencies: - prettier: 3.3.2 + prettier: 3.5.3 svelte: 4.2.19 - prettier@3.3.2: {} + prettier@3.5.3: {} protocol-buffers-schema@3.6.0: {} @@ -3810,10 +3626,14 @@ snapshots: pngjs: 5.0.0 yargs: 15.4.1 + quansync@0.2.10: {} + queue-microtask@1.2.3: {} quickselect@2.0.0: {} + quickselect@3.0.0: {} + read-cache@1.0.0: dependencies: pify: 2.3.0 @@ -3832,75 +3652,48 @@ snapshots: require-main-filename@2.0.0: {} - resolve-from@4.0.0: {} - resolve-from@5.0.0: {} resolve-protobuf-schema@2.1.0: dependencies: protocol-buffers-schema: 3.6.0 - resolve@1.22.8: + resolve@1.22.10: dependencies: - is-core-module: 2.14.0 + is-core-module: 2.16.1 path-parse: 1.0.7 supports-preserve-symlinks-flag: 1.0.0 - reusify@1.0.4: {} + reusify@1.1.0: {} rimraf@2.7.1: dependencies: glob: 7.2.3 - rimraf@3.0.2: - dependencies: - glob: 7.2.3 - - rollup@4.24.0: - dependencies: - '@types/estree': 1.0.6 - optionalDependencies: - '@rollup/rollup-android-arm-eabi': 4.24.0 - '@rollup/rollup-android-arm64': 4.24.0 - '@rollup/rollup-darwin-arm64': 4.24.0 - '@rollup/rollup-darwin-x64': 4.24.0 - '@rollup/rollup-linux-arm-gnueabihf': 4.24.0 - '@rollup/rollup-linux-arm-musleabihf': 4.24.0 - '@rollup/rollup-linux-arm64-gnu': 4.24.0 - '@rollup/rollup-linux-arm64-musl': 4.24.0 - '@rollup/rollup-linux-powerpc64le-gnu': 4.24.0 - '@rollup/rollup-linux-riscv64-gnu': 4.24.0 - '@rollup/rollup-linux-s390x-gnu': 4.24.0 - '@rollup/rollup-linux-x64-gnu': 4.24.0 - '@rollup/rollup-linux-x64-musl': 4.24.0 - '@rollup/rollup-win32-arm64-msvc': 4.24.0 - '@rollup/rollup-win32-ia32-msvc': 4.24.0 - '@rollup/rollup-win32-x64-msvc': 4.24.0 - fsevents: 2.3.3 - - rollup@4.31.0: + rollup@4.40.2: dependencies: - '@types/estree': 1.0.6 + '@types/estree': 1.0.7 optionalDependencies: - '@rollup/rollup-android-arm-eabi': 4.31.0 - '@rollup/rollup-android-arm64': 4.31.0 - '@rollup/rollup-darwin-arm64': 4.31.0 - '@rollup/rollup-darwin-x64': 4.31.0 - '@rollup/rollup-freebsd-arm64': 4.31.0 - '@rollup/rollup-freebsd-x64': 4.31.0 - '@rollup/rollup-linux-arm-gnueabihf': 4.31.0 - '@rollup/rollup-linux-arm-musleabihf': 4.31.0 - '@rollup/rollup-linux-arm64-gnu': 4.31.0 - '@rollup/rollup-linux-arm64-musl': 4.31.0 - '@rollup/rollup-linux-loongarch64-gnu': 4.31.0 - '@rollup/rollup-linux-powerpc64le-gnu': 4.31.0 - '@rollup/rollup-linux-riscv64-gnu': 4.31.0 - '@rollup/rollup-linux-s390x-gnu': 4.31.0 - '@rollup/rollup-linux-x64-gnu': 4.31.0 - '@rollup/rollup-linux-x64-musl': 4.31.0 - '@rollup/rollup-win32-arm64-msvc': 4.31.0 - '@rollup/rollup-win32-ia32-msvc': 4.31.0 - '@rollup/rollup-win32-x64-msvc': 4.31.0 + '@rollup/rollup-android-arm-eabi': 4.40.2 + '@rollup/rollup-android-arm64': 4.40.2 + '@rollup/rollup-darwin-arm64': 4.40.2 + '@rollup/rollup-darwin-x64': 4.40.2 + '@rollup/rollup-freebsd-arm64': 4.40.2 + '@rollup/rollup-freebsd-x64': 4.40.2 + '@rollup/rollup-linux-arm-gnueabihf': 4.40.2 + '@rollup/rollup-linux-arm-musleabihf': 4.40.2 + '@rollup/rollup-linux-arm64-gnu': 4.40.2 + '@rollup/rollup-linux-arm64-musl': 4.40.2 + '@rollup/rollup-linux-loongarch64-gnu': 4.40.2 + '@rollup/rollup-linux-powerpc64le-gnu': 4.40.2 + '@rollup/rollup-linux-riscv64-gnu': 4.40.2 + '@rollup/rollup-linux-riscv64-musl': 4.40.2 + '@rollup/rollup-linux-s390x-gnu': 4.40.2 + '@rollup/rollup-linux-x64-gnu': 4.40.2 + '@rollup/rollup-linux-x64-musl': 4.40.2 + '@rollup/rollup-win32-arm64-msvc': 4.40.2 + '@rollup/rollup-win32-ia32-msvc': 4.40.2 + '@rollup/rollup-win32-x64-msvc': 4.40.2 fsevents: 2.3.3 run-parallel@1.2.0: @@ -3922,20 +3715,11 @@ snapshots: mkdirp: 0.5.6 rimraf: 2.7.1 - semver@6.3.1: {} - - semver@7.6.2: {} + semver@7.7.1: {} set-blocking@2.0.0: {} - set-cookie-parser@2.6.0: {} - - set-value@2.0.1: - dependencies: - extend-shallow: 2.0.1 - is-extendable: 0.1.1 - is-plain-object: 2.0.4 - split-string: 3.1.0 + set-cookie-parser@2.7.1: {} shebang-command@2.0.0: dependencies: @@ -3943,46 +3727,23 @@ snapshots: shebang-regex@3.0.0: {} - signal-exit@3.0.7: {} - signal-exit@4.1.0: {} - sirv@3.0.0: + sirv@3.0.1: dependencies: - '@polka/url': 1.0.0-next.25 - mrmime: 2.0.0 + '@polka/url': 1.0.0-next.29 + mrmime: 2.0.1 totalist: 3.0.1 sorcery@0.11.1: dependencies: - '@jridgewell/sourcemap-codec': 1.4.15 + '@jridgewell/sourcemap-codec': 1.5.0 buffer-crc32: 1.0.0 minimist: 1.2.8 sander: 0.5.1 - sort-asc@0.2.0: {} - - sort-desc@0.2.0: {} - - sort-object@3.0.3: - dependencies: - bytewise: 1.1.0 - get-value: 2.0.6 - is-extendable: 0.1.1 - sort-asc: 0.2.0 - sort-desc: 0.2.0 - union-value: 1.0.1 - - source-map-js@1.2.0: {} - source-map-js@1.2.1: {} - split-string@3.1.0: - dependencies: - extend-shallow: 3.0.2 - - string-argv@0.3.2: {} - string-width@4.2.3: dependencies: emoji-regex: 8.0.0 @@ -4005,9 +3766,7 @@ snapshots: strip-ansi@7.1.0: dependencies: - ansi-regex: 6.0.1 - - strip-final-newline@2.0.0: {} + ansi-regex: 6.1.0 strip-indent@3.0.0: dependencies: @@ -4015,12 +3774,12 @@ snapshots: sucrase@3.35.0: dependencies: - '@jridgewell/gen-mapping': 0.3.5 + '@jridgewell/gen-mapping': 0.3.8 commander: 4.1.1 - glob: 10.4.2 + glob: 10.4.5 lines-and-columns: 1.2.4 mz: 2.7.0 - pirates: 4.0.6 + pirates: 4.0.7 ts-interface-checker: 0.1.13 supercluster@8.0.1: @@ -4029,17 +3788,15 @@ snapshots: supports-preserve-symlinks-flag@1.0.0: {} - svelte-check@3.8.1(postcss-load-config@4.0.2(postcss@8.4.38))(postcss@8.4.38)(svelte@4.2.19): + svelte-check@3.8.6(postcss-load-config@4.0.2(postcss@8.5.3))(postcss@8.5.3)(svelte@4.2.19): dependencies: '@jridgewell/trace-mapping': 0.3.25 chokidar: 3.6.0 - fast-glob: 3.3.2 - import-fresh: 3.3.0 - picocolors: 1.0.1 + picocolors: 1.1.1 sade: 1.8.1 svelte: 4.2.19 - svelte-preprocess: 5.1.4(postcss-load-config@4.0.2(postcss@8.4.38))(postcss@8.4.38)(svelte@4.2.19)(typescript@5.5.2) - typescript: 5.5.2 + svelte-preprocess: 5.1.4(postcss-load-config@4.0.2(postcss@8.5.3))(postcss@8.5.3)(svelte@4.2.19)(typescript@5.8.3) + typescript: 5.8.3 transitivePeerDependencies: - '@babel/core' - coffeescript @@ -4061,85 +3818,86 @@ snapshots: deepmerge: 4.3.1 esbuild: 0.19.12 estree-walker: 2.0.2 - intl-messageformat: 10.7.3 + intl-messageformat: 10.7.16 sade: 1.8.1 svelte: 4.2.19 tiny-glob: 0.2.9 - svelte-maplibre@0.9.8(svelte@4.2.19): + svelte-maplibre@0.9.14(svelte@4.2.19): dependencies: d3-geo: 3.1.1 + dequal: 2.0.3 just-compare: 2.3.0 just-flush: 2.3.0 - maplibre-gl: 4.5.0 - pmtiles: 3.0.6 + maplibre-gl: 4.7.1 + pmtiles: 3.2.1 svelte: 4.2.19 - svelte-preprocess@5.1.4(postcss-load-config@4.0.2(postcss@8.4.38))(postcss@8.4.38)(svelte@4.2.19)(typescript@5.5.2): + svelte-preprocess@5.1.4(postcss-load-config@4.0.2(postcss@8.5.3))(postcss@8.5.3)(svelte@4.2.19)(typescript@5.8.3): dependencies: '@types/pug': 2.0.10 detect-indent: 6.1.0 - magic-string: 0.30.10 + magic-string: 0.30.17 sorcery: 0.11.1 strip-indent: 3.0.0 svelte: 4.2.19 optionalDependencies: - postcss: 8.4.38 - postcss-load-config: 4.0.2(postcss@8.4.38) - typescript: 5.5.2 + postcss: 8.5.3 + postcss-load-config: 4.0.2(postcss@8.5.3) + typescript: 5.8.3 svelte@4.2.19: dependencies: '@ampproject/remapping': 2.3.0 - '@jridgewell/sourcemap-codec': 1.4.15 + '@jridgewell/sourcemap-codec': 1.5.0 '@jridgewell/trace-mapping': 0.3.25 - '@types/estree': 1.0.6 - acorn: 8.12.0 - aria-query: 5.3.0 - axobject-query: 4.0.0 + '@types/estree': 1.0.7 + acorn: 8.14.1 + aria-query: 5.3.2 + axobject-query: 4.1.0 code-red: 1.0.4 css-tree: 2.3.1 estree-walker: 3.0.3 - is-reference: 3.0.2 + is-reference: 3.0.3 locate-character: 3.0.0 - magic-string: 0.30.10 + magic-string: 0.30.17 periscopic: 3.1.0 - tailwindcss@3.4.4: + tailwindcss@3.4.17: dependencies: '@alloc/quick-lru': 5.2.0 arg: 5.0.2 chokidar: 3.6.0 didyoumean: 1.2.2 dlv: 1.1.3 - fast-glob: 3.3.2 + fast-glob: 3.3.3 glob-parent: 6.0.2 is-glob: 4.0.3 - jiti: 1.21.6 - lilconfig: 2.1.0 + jiti: 1.21.7 + lilconfig: 3.1.3 micromatch: 4.0.8 normalize-path: 3.0.0 object-hash: 3.0.0 - picocolors: 1.0.1 - postcss: 8.4.38 - postcss-import: 15.1.0(postcss@8.4.38) - postcss-js: 4.0.1(postcss@8.4.38) - postcss-load-config: 4.0.2(postcss@8.4.38) - postcss-nested: 6.0.1(postcss@8.4.38) - postcss-selector-parser: 6.1.0 - resolve: 1.22.8 + picocolors: 1.1.1 + postcss: 8.5.3 + postcss-import: 15.1.0(postcss@8.5.3) + postcss-js: 4.0.1(postcss@8.5.3) + postcss-load-config: 4.0.2(postcss@8.5.3) + postcss-nested: 6.2.0(postcss@8.5.3) + postcss-selector-parser: 6.1.2 + resolve: 1.22.10 sucrase: 3.35.0 transitivePeerDependencies: - ts-node - tar@6.2.1: + tar@7.4.3: dependencies: - chownr: 2.0.0 - fs-minipass: 2.1.0 - minipass: 5.0.0 - minizlib: 2.1.2 - mkdirp: 1.0.4 - yallist: 4.0.0 + '@isaacs/fs-minipass': 4.0.1 + chownr: 3.0.0 + minipass: 7.1.2 + minizlib: 3.0.2 + mkdirp: 3.0.1 + yallist: 5.0.0 thenify-all@1.6.0: dependencies: @@ -4159,7 +3917,9 @@ snapshots: globalyzer: 0.1.0 globrex: 0.1.2 - tinyqueue@2.0.3: {} + tinyexec@0.3.2: {} + + tinyqueue@3.0.0: {} to-regex-range@5.0.1: dependencies: @@ -4171,83 +3931,64 @@ snapshots: ts-interface-checker@0.1.13: {} - tslib@2.6.3: {} - - type-detect@4.0.8: {} + tslib@2.8.1: {} type@2.7.3: {} typedarray@0.0.6: {} - typescript@5.5.2: {} - - typewise-core@1.2.0: {} + typescript@5.8.3: {} - typewise@1.0.3: - dependencies: - typewise-core: 1.2.0 - - ufo@1.5.3: {} + ufo@1.6.1: {} - undici-types@6.19.8: {} - - union-value@1.0.1: - dependencies: - arr-union: 3.1.0 - get-value: 2.0.6 - is-extendable: 0.1.1 - set-value: 2.0.1 + undici-types@6.21.0: {} - unplugin-icons@0.19.0: + unplugin-icons@0.19.3: dependencies: - '@antfu/install-pkg': 0.3.3 - '@antfu/utils': 0.7.8 - '@iconify/utils': 2.1.25 - debug: 4.3.5 + '@antfu/install-pkg': 0.4.1 + '@antfu/utils': 0.7.10 + '@iconify/utils': 2.3.0 + debug: 4.4.0 kolorist: 1.8.0 - local-pkg: 0.5.0 - unplugin: 1.10.1 + local-pkg: 0.5.1 + unplugin: 1.16.1 transitivePeerDependencies: - supports-color - unplugin@1.10.1: + unplugin@1.16.1: dependencies: - acorn: 8.12.0 - chokidar: 3.6.0 - webpack-sources: 3.2.3 + acorn: 8.14.1 webpack-virtual-modules: 0.6.2 - update-browserslist-db@1.0.16(browserslist@4.23.1): + update-browserslist-db@1.1.3(browserslist@4.24.4): dependencies: - browserslist: 4.23.1 - escalade: 3.1.2 - picocolors: 1.0.1 + browserslist: 4.24.4 + escalade: 3.2.0 + picocolors: 1.1.1 util-deprecate@1.0.2: {} - vite@5.4.12(@types/node@22.5.4): + vite@5.4.19(@types/node@22.15.2): dependencies: esbuild: 0.21.5 - postcss: 8.5.1 - rollup: 4.31.0 + postcss: 8.5.3 + rollup: 4.40.2 optionalDependencies: - '@types/node': 22.5.4 + '@types/node': 22.15.2 fsevents: 2.3.3 - vitefu@0.2.5(vite@5.4.12(@types/node@22.5.4)): + vitefu@0.2.5(vite@5.4.19(@types/node@22.15.2)): optionalDependencies: - vite: 5.4.12(@types/node@22.5.4) + vite: 5.4.19(@types/node@22.15.2) vt-pbf@3.1.3: dependencies: '@mapbox/point-geometry': 0.1.0 '@mapbox/vector-tile': 1.3.1 - pbf: 3.2.1 + pbf: 3.3.0 webidl-conversions@3.0.1: {} - webpack-sources@3.2.3: {} - webpack-virtual-modules@0.6.2: {} whatwg-url@5.0.0: @@ -4257,17 +3998,13 @@ snapshots: which-module@2.0.1: {} - which@1.3.1: - dependencies: - isexe: 2.0.0 - which@2.0.2: dependencies: isexe: 2.0.0 - wide-align@1.1.5: + which@4.0.0: dependencies: - string-width: 4.2.3 + isexe: 3.1.1 wrap-ansi@6.2.0: dependencies: @@ -4291,9 +4028,9 @@ snapshots: y18n@4.0.3: {} - yallist@4.0.0: {} + yallist@5.0.0: {} - yaml@2.4.5: {} + yaml@2.7.1: {} yargs-parser@18.1.3: dependencies: @@ -4313,5 +4050,3 @@ snapshots: which-module: 2.0.1 y18n: 4.0.3 yargs-parser: 18.1.3 - - yocto-queue@0.1.0: {} diff --git a/frontend/src/lib/assets/google_maps.svg b/frontend/src/lib/assets/google_maps.svg new file mode 100644 index 00000000..9c0f945f --- /dev/null +++ b/frontend/src/lib/assets/google_maps.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/frontend/src/lib/assets/undraw_server_error.svg b/frontend/src/lib/assets/undraw_server_error.svg new file mode 100644 index 00000000..0daa1821 --- /dev/null +++ b/frontend/src/lib/assets/undraw_server_error.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/frontend/src/lib/components/AboutModal.svelte b/frontend/src/lib/components/AboutModal.svelte index 7b8fbf24..46ad5aac 100644 --- a/frontend/src/lib/components/AboutModal.svelte +++ b/frontend/src/lib/components/AboutModal.svelte @@ -7,11 +7,19 @@ const dispatch = createEventDispatcher(); let modal: HTMLDialogElement; - onMount(() => { + let integrations: Record | null = null; + + onMount(async () => { modal = document.getElementById('about_modal') as HTMLDialogElement; if (modal) { modal.showModal(); } + const response = await fetch('/api/integrations'); + if (response.ok) { + integrations = await response.json(); + } else { + integrations = null; + } }); function close() { @@ -90,18 +98,38 @@

{$t('about.oss_attributions')}

-

- {$t('about.nominatim_1')} - - OpenStreetMap - - . {$t('about.nominatim_2')} -

+ {#if integrations && integrations?.google_maps} +

+ {$t('about.nominatim_1')} + + Google Maps + + . +

+ {:else if integrations && !integrations?.google_maps} +

+ {$t('about.nominatim_1')} + + OpenStreetMap + + . {$t('about.nominatim_2')} +

+ {:else} +

+ {$t('about.generic_attributions')} +

+ {/if} +

{$t('about.other_attributions')}

diff --git a/frontend/src/lib/components/AdventureCard.svelte b/frontend/src/lib/components/AdventureCard.svelte index 2e9dd610..dbdf5e43 100644 --- a/frontend/src/lib/components/AdventureCard.svelte +++ b/frontend/src/lib/components/AdventureCard.svelte @@ -18,6 +18,10 @@ import DeleteWarning from './DeleteWarning.svelte'; import CardCarousel from './CardCarousel.svelte'; import { t } from 'svelte-i18n'; + import Star from '~icons/mdi/star'; + import StarOutline from '~icons/mdi/star-outline'; + import Eye from '~icons/mdi/eye'; + import EyeOff from '~icons/mdi/eye-off'; export let type: string | null = null; export let user: User | null; @@ -28,15 +32,18 @@ let isWarningModalOpen: boolean = false; export let adventure: Adventure; - let activityTypes: string[] = []; - // makes it reactivty to changes so it updates automatically + let displayActivityTypes: string[] = []; + let remainingCount = 0; + + // Process activity types for display $: { if (adventure.activity_types) { - activityTypes = adventure.activity_types; - if (activityTypes.length > 3) { - activityTypes = activityTypes.slice(0, 3); - let remaining = adventure.activity_types.length - 3; - activityTypes.push('+' + remaining); + if (adventure.activity_types.length <= 3) { + displayActivityTypes = adventure.activity_types; + remainingCount = 0; + } else { + displayActivityTypes = adventure.activity_types.slice(0, 3); + remainingCount = adventure.activity_types.length - 3; } } } @@ -47,18 +54,28 @@ $: { if (collection && collection?.start_date && collection.end_date) { unlinked = adventure.visits.every((visit) => { - // Check if visit dates exist - if (!visit.start_date || !visit.end_date) return true; // Consider "unlinked" for incomplete visit data - - // Check if collection dates are completely outside this visit's range + if (!visit.start_date || !visit.end_date) return true; const isBeforeVisit = collection.end_date && collection.end_date < visit.start_date; const isAfterVisit = collection.start_date && collection.start_date > visit.end_date; - return isBeforeVisit || isAfterVisit; }); } } + // Helper functions for display + function formatVisitCount() { + const count = adventure.visits.length; + return count > 1 ? `${count} ${$t('adventures.visits')}` : `${count} ${$t('adventures.visit')}`; + } + + function renderStars(rating: number) { + const stars = []; + for (let i = 1; i <= 5; i++) { + stars.push(i <= rating); + } + return stars; + } + async function deleteAdventure() { let res = await fetch(`/api/adventures/${adventure.id}`, { method: 'DELETE' @@ -131,119 +148,181 @@ {/if}
- + +
+ -
-
- -
-
-
- {adventure.category?.display_name + ' ' + adventure.category?.icon} -
-
{adventure.is_visited ? $t('adventures.visited') : $t('adventures.planned')}
-
- {adventure.is_public ? $t('adventures.public') : $t('adventures.private')} + {#if unlinked} +
{$t('adventures.out_of_range')}
+ {/if} +
+ + +
+
+
+ {#if adventure.is_public} + + {:else} + + {/if} +
- {#if unlinked} -
{$t('adventures.out_of_range')}
- {/if} - {#if adventure.location && adventure.location !== ''} -
- -

{adventure.location}

+ + + {#if adventure.category} +
+
+ {adventure.category.display_name} + {adventure.category.icon} +
{/if} +
+ + +
+ +
+ + + + {#if adventure.location} +
+ + {adventure.location} +
+ {/if} + + + {#if adventure.rating} +
+
+ {#each renderStars(adventure.rating) as filled} + {#if filled} + + {:else} + + {/if} + {/each} +
+ ({adventure.rating}/5) +
+ {/if} +
+ + {#if adventure.visits.length > 0} - -
- -

- {adventure.visits.length} - {adventure.visits.length > 1 ? $t('adventures.visits') : $t('adventures.visit')} -

+
+ + {formatVisitCount()}
{/if} - {#if adventure.activity_types && adventure.activity_types.length > 0} -
    - {#each activityTypes as activity} -
    - {activity} -
    - {/each} -
- {/if} - {#if !readOnly} -
- + + {#if !readOnly} +
{#if type != 'link'} - {#if adventure.user_id == user?.uuid || (collection && user && collection.shared_with?.includes(user.uuid))} - - {:else} +
- {/if} - {/if} - {#if type == 'link'} - + + View Details + + + {#if adventure.user_id == user?.uuid || (collection && user && collection.shared_with?.includes(user.uuid))} + + {/if} +
+ {:else} + {/if}
{/if}
+ + diff --git a/frontend/src/lib/components/AdventureModal.svelte b/frontend/src/lib/components/AdventureModal.svelte index c8b07431..e8b7d6ee 100644 --- a/frontend/src/lib/components/AdventureModal.svelte +++ b/frontend/src/lib/components/AdventureModal.svelte @@ -6,9 +6,23 @@ import { t } from 'svelte-i18n'; export let collection: Collection | null = null; + let fullStartDate: string = ''; + let fullEndDate: string = ''; + let fullStartDateOnly: string = ''; + let fullEndDateOnly: string = ''; + let allDay: boolean = true; + + // Set full start and end dates from collection + if (collection && collection.start_date && collection.end_date) { + fullStartDate = `${collection.start_date}T00:00`; + fullEndDate = `${collection.end_date}T23:59`; + fullStartDateOnly = collection.start_date; + fullEndDateOnly = collection.end_date; + } + const dispatch = createEventDispatcher(); - let images: { id: string; image: string; is_primary: boolean }[] = []; + let images: { id: string; image: string; is_primary: boolean; immich_id: string | null }[] = []; let warningMessage: string = ''; let constrainDates: boolean = false; @@ -60,25 +74,26 @@ '.tar.lzma', '.tar.lzo', '.tar.z', - 'gpx', - 'md', - 'pdf' + '.gpx', + '.md' ]; export let initialLatLng: { lat: number; lng: number } | null = null; // Used to pass the location from the map selection to the modal let fileInput: HTMLInputElement; let immichIntegration: boolean = false; + let copyImmichLocally: boolean = false; import ActivityComplete from './ActivityComplete.svelte'; import CategoryDropdown from './CategoryDropdown.svelte'; - import { findFirstValue } from '$lib'; + import { findFirstValue, isAllDay } from '$lib'; import MarkdownEditor from './MarkdownEditor.svelte'; import ImmichSelect from './ImmichSelect.svelte'; import Star from '~icons/mdi/star'; import Crown from '~icons/mdi/crown'; import AttachmentCard from './AttachmentCard.svelte'; import LocationDropdown from './LocationDropdown.svelte'; + import DateRangeCollapse from './DateRangeCollapse.svelte'; let modal: HTMLDialogElement; let wikiError: string = ''; @@ -147,13 +162,19 @@ addToast('error', $t('adventures.category_fetch_error')); } // Check for Immich Integration - let res = await fetch('/api/integrations'); - if (!res.ok) { + let res = await fetch('/api/integrations/immich/'); + // If the response is not ok, we assume Immich integration is not available + if (!res.ok && res.status !== 404) { addToast('error', $t('immich.integration_fetch_error')); } else { let data = await res.json(); - if (data.immich) { + if (data.error) { + immichIntegration = false; + } else if (data.id) { immichIntegration = true; + copyImmichLocally = data.copy_locally || false; + } else { + immichIntegration = false; } } }); @@ -163,6 +184,8 @@ let wikiImageError: string = ''; let triggerMarkVisted: boolean = false; + let isLoading: boolean = false; + images = adventure.images || []; $: { if (!adventure.rating) { @@ -314,7 +337,12 @@ }); if (res.ok) { let newData = deserialize(await res.text()) as { data: { id: string; image: string } }; - let newImage = { id: newData.data.id, image: newData.data.image, is_primary: false }; + let newImage = { + id: newData.data.id, + image: newData.data.image, + is_primary: false, + immich_id: null + }; images = [...images, newImage]; adventure.images = images; addToast('success', $t('adventures.image_upload_success')); @@ -365,7 +393,12 @@ }); if (res2.ok) { let newData = deserialize(await res2.text()) as { data: { id: string; image: string } }; - let newImage = { id: newData.data.id, image: newData.data.image, is_primary: false }; + let newImage = { + id: newData.data.id, + image: newData.data.image, + is_primary: false, + immich_id: null + }; images = [...images, newImage]; adventure.images = images; addToast('success', $t('adventures.image_upload_success')); @@ -376,35 +409,6 @@ } } - let new_start_date: string = ''; - let new_end_date: string = ''; - let new_notes: string = ''; - function addNewVisit() { - if (new_start_date && !new_end_date) { - new_end_date = new_start_date; - } - if (new_start_date > new_end_date) { - addToast('error', $t('adventures.start_before_end_error')); - return; - } - if (new_end_date && !new_start_date) { - addToast('error', $t('adventures.no_start_date')); - return; - } - adventure.visits = [ - ...adventure.visits, - { - start_date: new_start_date, - end_date: new_end_date, - notes: new_notes, - id: '' - } - ]; - new_start_date = ''; - new_end_date = ''; - new_notes = ''; - } - function close() { dispatch('close'); } @@ -429,6 +433,14 @@ async function handleSubmit(event: Event) { event.preventDefault(); triggerMarkVisted = true; + isLoading = true; + + // if category icon is empty, set it to the default icon + if (adventure.category?.icon == '' || adventure.category?.icon == null) { + if (adventure.category) { + adventure.category.icon = '🌍'; + } + } if (adventure.id === '') { if (adventure.category?.display_name == '') { @@ -446,6 +458,7 @@ }; } } + let res = await fetch('/api/adventures', { method: 'POST', headers: { @@ -484,6 +497,7 @@ } } imageSearch = adventure.name; + isLoading = false; } @@ -614,7 +628,7 @@

{wikiError}

- {#if !collection?.id} + {#if !adventure?.collection}
-
- -
- {$t('adventures.visits')} ({adventure.visits.length}) -
-
- -
- {#if !constrainDates} - { - if (e.key === 'Enter') { - e.preventDefault(); - addNewVisit(); - } - }} - /> - { - if (e.key === 'Enter') { - e.preventDefault(); - addNewVisit(); - } - }} - /> - {:else} - { - if (e.key === 'Enter') { - e.preventDefault(); - addNewVisit(); - } - }} - /> - { - if (e.key === 'Enter') { - e.preventDefault(); - addNewVisit(); - } - }} - /> - {/if} -
-
- - -
- -
- -
- {#if adventure.visits.length > 0} -

{$t('adventures.my_visits')}

- {#each adventure.visits as visit} -
-
-

- {new Date(visit.start_date).toLocaleDateString(undefined, { - timeZone: 'UTC' - })} -

- {#if visit.end_date && visit.end_date !== visit.start_date} -

- {new Date(visit.end_date).toLocaleDateString(undefined, { - timeZone: 'UTC' - })} -

- {/if} - -
- -
-
-

{visit.notes}

-
- {/each} - {/if} -
-
+
@@ -805,8 +689,17 @@ {$t('adventures.warning')}: {warningMessage}
{/if} - - +
+ {#if !isLoading} + + {:else} + + {/if} + +
@@ -849,6 +742,23 @@
+ + {#if attachmentToEdit}
{ @@ -941,6 +851,18 @@ url = e.detail; fetchImage(); }} + {copyImmichLocally} + on:remoteImmichSaved={(e) => { + const newImage = { + id: e.detail.id, + image: e.detail.image, + is_primary: e.detail.is_primary, + immich_id: e.detail.immich_id + }; + images = [...images, newImage]; + adventure.images = images; + addToast('success', $t('adventures.image_upload_success')); + }} /> {/if} diff --git a/frontend/src/lib/components/CardCarousel.svelte b/frontend/src/lib/components/CardCarousel.svelte index cb238d02..60f9b38b 100644 --- a/frontend/src/lib/components/CardCarousel.svelte +++ b/frontend/src/lib/components/CardCarousel.svelte @@ -97,12 +97,12 @@ {:else}
- +
{/if} diff --git a/frontend/src/lib/components/CategoryModal.svelte b/frontend/src/lib/components/CategoryModal.svelte index e28706fe..96cf18af 100644 --- a/frontend/src/lib/components/CategoryModal.svelte +++ b/frontend/src/lib/components/CategoryModal.svelte @@ -1,149 +1,353 @@ - - - -
diff --git a/frontend/src/lib/components/TimezoneSelector.svelte b/frontend/src/lib/components/TimezoneSelector.svelte new file mode 100644 index 00000000..5603555d --- /dev/null +++ b/frontend/src/lib/components/TimezoneSelector.svelte @@ -0,0 +1,126 @@ + + +
+ + + +
(dropdownOpen = !dropdownOpen)} + on:keydown={handleKeydown} + > + {selectedTimezone} + +
+ + + {#if dropdownOpen} +
+ +
+ +
+ + + {#if filteredTimezones.length > 0} + + {:else} +
No timezones found
+ {/if} +
+ {/if} +
diff --git a/frontend/src/lib/components/TransportationCard.svelte b/frontend/src/lib/components/TransportationCard.svelte index 8a08de3d..16dd996e 100644 --- a/frontend/src/lib/components/TransportationCard.svelte +++ b/frontend/src/lib/components/TransportationCard.svelte @@ -7,13 +7,24 @@ import { t } from 'svelte-i18n'; import DeleteWarning from './DeleteWarning.svelte'; // import ArrowDownThick from '~icons/mdi/arrow-down-thick'; + import { TRANSPORTATION_TYPES_ICONS } from '$lib'; + import { formatDateInTimezone } from '$lib/dateUtils'; + function getTransportationIcon(type: string) { + if (type in TRANSPORTATION_TYPES_ICONS) { + return TRANSPORTATION_TYPES_ICONS[type as keyof typeof TRANSPORTATION_TYPES_ICONS]; + } else { + return '🚗'; + } + } const dispatch = createEventDispatcher(); export let transportation: Transportation; export let user: User | null = null; export let collection: Collection | null = null; + const toMiles = (km: any) => (Number(km) * 0.621371).toFixed(1); + let isWarningModalOpen: boolean = false; function editTransportation() { @@ -98,74 +109,94 @@ {/if}
-
- -
-

{transportation.name}

-
+
+ +
+

{transportation.name}

+
{$t(`transportation.modes.${transportation.type}`)} + {getTransportationIcon(transportation.type)}
- {#if transportation.type == 'plane' && transportation.flight_number} -
{transportation.flight_number}
+ {#if transportation.type === 'plane' && transportation.flight_number} +
{transportation.flight_number}
+ {/if} + {#if unlinked} +
{$t('adventures.out_of_range')}
{/if}
- {#if unlinked} -
{$t('adventures.out_of_range')}
- {/if} - -
+ +
{#if transportation.from_location} -
- {$t('adventures.from')}: -

{transportation.from_location}

+
+ {$t('adventures.from')}: + {transportation.from_location}
{/if} - {#if transportation.date} -
- {$t('adventures.start')}: -

{new Date(transportation.date).toLocaleString(undefined, { timeZone: 'UTC' })}

+ + {#if transportation.to_location} +
+ {$t('adventures.to')}: + {transportation.to_location}
{/if} -
- -
- {#if transportation.to_location} - -
- {$t('adventures.to')}: + {#if transportation.distance && !isNaN(+transportation.distance)} +
+ {$t('adventures.distance')}: + + {(+transportation.distance).toFixed(1)} km / {toMiles(transportation.distance)} mi + +
+ {/if} +
-

{transportation.to_location}

+ +
+ {#if transportation.date} +
+ {$t('adventures.start')}: + + {formatDateInTimezone(transportation.date, transportation.start_timezone)} + {#if transportation.start_timezone} + ({transportation.start_timezone}) + {/if} +
{/if} + {#if transportation.end_date} -
- {$t('adventures.end')}: -

{new Date(transportation.end_date).toLocaleString(undefined, { timeZone: 'UTC' })}

+
+ {$t('adventures.end')}: + + {formatDateInTimezone(transportation.end_date, transportation.end_timezone)} + {#if transportation.end_timezone} + ({transportation.end_timezone}) + {/if} +
{/if}
- {#if transportation.user_id == user?.uuid || (collection && user && collection.shared_with && collection.shared_with.includes(user.uuid))} -
+ {#if transportation.user_id === user?.uuid || (collection && user && collection.shared_with?.includes(user.uuid))} +
-
- -
- {$t('adventures.date_information')} -
-
- -
- - {#if collection && collection.start_date && collection.end_date} - {/if} -
- -
-
- - {#if transportation.date} -
- -
- -
-
- {/if} -
-
+ -
@@ -549,11 +480,6 @@ class="relative aspect-[9/16] max-h-[70vh] w-full sm:aspect-video sm:max-h-full rounded-lg" standardControls > - - - {#if transportation.origin_latitude && transportation.origin_longitude} /> {/if} -
{#if transportation.from_location || transportation.to_location} {:else if shared_with && !shared_with.includes(user.uuid)} - + {:else} - + {/if}
diff --git a/frontend/src/lib/config.ts b/frontend/src/lib/config.ts index 8cff4391..73e6f924 100644 --- a/frontend/src/lib/config.ts +++ b/frontend/src/lib/config.ts @@ -1,4 +1,4 @@ -export let appVersion = 'v0.9.0'; -export let versionChangelog = 'https://github.com/seanmorley15/AdventureLog/releases/tag/v0.9.0'; +export let appVersion = 'v0.10.0'; +export let versionChangelog = 'https://github.com/seanmorley15/AdventureLog/releases/tag/v0.10.0'; export let appTitle = 'AdventureLog'; export let copyrightYear = '2023-2025'; diff --git a/frontend/src/lib/dateUtils.ts b/frontend/src/lib/dateUtils.ts new file mode 100644 index 00000000..38bba60d --- /dev/null +++ b/frontend/src/lib/dateUtils.ts @@ -0,0 +1,568 @@ +// @ts-ignore +import { DateTime } from 'luxon'; + +/** + * Convert a UTC ISO date to a datetime-local value in the specified timezone + * @param utcDate - UTC date in ISO format or null + * @param timezone - Target timezone (defaults to browser timezone) + * @returns Formatted local datetime string for input fields (YYYY-MM-DDTHH:MM) + */ +export function toLocalDatetime( + utcDate: string | null, + timezone: string = Intl.DateTimeFormat().resolvedOptions().timeZone +): string { + if (!utcDate) return ''; + + const dt = DateTime.fromISO(utcDate, { zone: 'UTC' }); + if (!dt.isValid) return ''; + + const isoString = dt.setZone(timezone).toISO({ + suppressSeconds: true, + includeOffset: false + }); + + return isoString ? isoString.slice(0, 16) : ''; +} + +/** + * Convert a local datetime to UTC + * @param localDate - Local datetime string in ISO format + * @param timezone - Source timezone (defaults to browser timezone) + * @returns UTC datetime in ISO format or null + */ +export function toUTCDatetime( + localDate: string, + timezone: string = Intl.DateTimeFormat().resolvedOptions().timeZone, + allDay: boolean = false +): string | null { + if (!localDate) return null; + + if (allDay) { + // Treat input as date-only, set UTC midnight manually + return DateTime.fromISO(localDate, { zone: 'UTC' }) + .startOf('day') + .toISO({ suppressMilliseconds: true }); + } + + // Normal timezone conversion for datetime-local input + return DateTime.fromISO(localDate, { zone: timezone }) + .toUTC() + .toISO({ suppressMilliseconds: true }); +} + +/** + * Updates local datetime values based on UTC date and timezone + * @param params Object containing UTC date and timezone + * @returns Object with updated local datetime string + */ +export function updateLocalDate({ + utcDate, + timezone +}: { + utcDate: string | null; + timezone: string; +}) { + return { + localDate: toLocalDatetime(utcDate, timezone) + }; +} + +/** + * Updates UTC datetime values based on local datetime and timezone + * @param params Object containing local date and timezone + * @returns Object with updated UTC datetime string + */ +export function updateUTCDate({ + localDate, + timezone, + allDay = false +}: { + localDate: string; + timezone: string; + allDay?: boolean; +}) { + return { + utcDate: toUTCDatetime(localDate, timezone, allDay) + }; +} + +/** + * Validate date ranges using UTC comparison + * @param startDate - Start date string in UTC (ISO format) + * @param endDate - End date string in UTC (ISO format) + * @returns Object with validation result and optional error message + */ +export function validateDateRange( + startDate: string, + endDate: string +): { valid: boolean; error?: string } { + if (endDate && !startDate) { + return { + valid: false, + error: 'Start date is required when end date is provided' + }; + } + + if ( + startDate && + endDate && + DateTime.fromISO(startDate, { zone: 'utc' }).toMillis() > + DateTime.fromISO(endDate, { zone: 'utc' }).toMillis() + ) { + return { + valid: false, + error: 'Start date must be before end date (based on UTC)' + }; + } + + return { valid: true }; +} + +export function formatDateInTimezone(utcDate: string, timezone: string | null): string { + if (!utcDate) return ''; + try { + return new Intl.DateTimeFormat(undefined, { + timeZone: timezone || undefined, + year: 'numeric', + month: 'short', + day: 'numeric', + hour: '2-digit', + minute: '2-digit', + hour12: true + }).format(new Date(utcDate)); + } catch { + return new Date(utcDate).toLocaleString(); + } +} + +/** + * Format UTC date for display + * @param utcDate - UTC date in ISO format + * @returns Formatted date string without seconds (YYYY-MM-DD HH:MM) + */ +export function formatUTCDate(utcDate: string | null): string { + if (!utcDate) return ''; + const dateTime = DateTime.fromISO(utcDate); + if (!dateTime.isValid) return ''; + return dateTime.toISO()?.slice(0, 16).replace('T', ' ') || ''; +} + +export const VALID_TIMEZONES = [ + 'Africa/Abidjan', + 'Africa/Accra', + 'Africa/Addis_Ababa', + 'Africa/Algiers', + 'Africa/Asmera', + 'Africa/Bamako', + 'Africa/Bangui', + 'Africa/Banjul', + 'Africa/Bissau', + 'Africa/Blantyre', + 'Africa/Brazzaville', + 'Africa/Bujumbura', + 'Africa/Cairo', + 'Africa/Casablanca', + 'Africa/Ceuta', + 'Africa/Conakry', + 'Africa/Dakar', + 'Africa/Dar_es_Salaam', + 'Africa/Djibouti', + 'Africa/Douala', + 'Africa/El_Aaiun', + 'Africa/Freetown', + 'Africa/Gaborone', + 'Africa/Harare', + 'Africa/Johannesburg', + 'Africa/Juba', + 'Africa/Kampala', + 'Africa/Khartoum', + 'Africa/Kigali', + 'Africa/Kinshasa', + 'Africa/Lagos', + 'Africa/Libreville', + 'Africa/Lome', + 'Africa/Luanda', + 'Africa/Lubumbashi', + 'Africa/Lusaka', + 'Africa/Malabo', + 'Africa/Maputo', + 'Africa/Maseru', + 'Africa/Mbabane', + 'Africa/Mogadishu', + 'Africa/Monrovia', + 'Africa/Nairobi', + 'Africa/Ndjamena', + 'Africa/Niamey', + 'Africa/Nouakchott', + 'Africa/Ouagadougou', + 'Africa/Porto-Novo', + 'Africa/Sao_Tome', + 'Africa/Tripoli', + 'Africa/Tunis', + 'Africa/Windhoek', + 'America/Adak', + 'America/Anchorage', + 'America/Anguilla', + 'America/Antigua', + 'America/Araguaina', + 'America/Argentina/La_Rioja', + 'America/Argentina/Rio_Gallegos', + 'America/Argentina/Salta', + 'America/Argentina/San_Juan', + 'America/Argentina/San_Luis', + 'America/Argentina/Tucuman', + 'America/Argentina/Ushuaia', + 'America/Aruba', + 'America/Asuncion', + 'America/Bahia', + 'America/Bahia_Banderas', + 'America/Barbados', + 'America/Belem', + 'America/Belize', + 'America/Blanc-Sablon', + 'America/Boa_Vista', + 'America/Bogota', + 'America/Boise', + 'America/Buenos_Aires', + 'America/Cambridge_Bay', + 'America/Campo_Grande', + 'America/Cancun', + 'America/Caracas', + 'America/Catamarca', + 'America/Cayenne', + 'America/Cayman', + 'America/Chicago', + 'America/Chihuahua', + 'America/Ciudad_Juarez', + 'America/Coral_Harbour', + 'America/Cordoba', + 'America/Costa_Rica', + 'America/Creston', + 'America/Cuiaba', + 'America/Curacao', + 'America/Danmarkshavn', + 'America/Dawson', + 'America/Dawson_Creek', + 'America/Denver', + 'America/Detroit', + 'America/Dominica', + 'America/Edmonton', + 'America/Eirunepe', + 'America/El_Salvador', + 'America/Fort_Nelson', + 'America/Fortaleza', + 'America/Glace_Bay', + 'America/Godthab', + 'America/Goose_Bay', + 'America/Grand_Turk', + 'America/Grenada', + 'America/Guadeloupe', + 'America/Guatemala', + 'America/Guayaquil', + 'America/Guyana', + 'America/Halifax', + 'America/Havana', + 'America/Hermosillo', + 'America/Indiana/Knox', + 'America/Indiana/Marengo', + 'America/Indiana/Petersburg', + 'America/Indiana/Tell_City', + 'America/Indiana/Vevay', + 'America/Indiana/Vincennes', + 'America/Indiana/Winamac', + 'America/Indianapolis', + 'America/Inuvik', + 'America/Iqaluit', + 'America/Jamaica', + 'America/Jujuy', + 'America/Juneau', + 'America/Kentucky/Monticello', + 'America/Kralendijk', + 'America/La_Paz', + 'America/Lima', + 'America/Los_Angeles', + 'America/Louisville', + 'America/Lower_Princes', + 'America/Maceio', + 'America/Managua', + 'America/Manaus', + 'America/Marigot', + 'America/Martinique', + 'America/Matamoros', + 'America/Mazatlan', + 'America/Mendoza', + 'America/Menominee', + 'America/Merida', + 'America/Metlakatla', + 'America/Mexico_City', + 'America/Miquelon', + 'America/Moncton', + 'America/Monterrey', + 'America/Montevideo', + 'America/Montserrat', + 'America/Nassau', + 'America/New_York', + 'America/Nome', + 'America/Noronha', + 'America/North_Dakota/Beulah', + 'America/North_Dakota/Center', + 'America/North_Dakota/New_Salem', + 'America/Ojinaga', + 'America/Panama', + 'America/Paramaribo', + 'America/Phoenix', + 'America/Port-au-Prince', + 'America/Port_of_Spain', + 'America/Porto_Velho', + 'America/Puerto_Rico', + 'America/Punta_Arenas', + 'America/Rankin_Inlet', + 'America/Recife', + 'America/Regina', + 'America/Resolute', + 'America/Rio_Branco', + 'America/Santarem', + 'America/Santiago', + 'America/Santo_Domingo', + 'America/Sao_Paulo', + 'America/Scoresbysund', + 'America/Sitka', + 'America/St_Barthelemy', + 'America/St_Johns', + 'America/St_Kitts', + 'America/St_Lucia', + 'America/St_Thomas', + 'America/St_Vincent', + 'America/Swift_Current', + 'America/Tegucigalpa', + 'America/Thule', + 'America/Tijuana', + 'America/Toronto', + 'America/Tortola', + 'America/Vancouver', + 'America/Whitehorse', + 'America/Winnipeg', + 'America/Yakutat', + 'Antarctica/Casey', + 'Antarctica/Davis', + 'Antarctica/DumontDUrville', + 'Antarctica/Macquarie', + 'Antarctica/Mawson', + 'Antarctica/McMurdo', + 'Antarctica/Palmer', + 'Antarctica/Rothera', + 'Antarctica/Syowa', + 'Antarctica/Troll', + 'Antarctica/Vostok', + 'Arctic/Longyearbyen', + 'Asia/Aden', + 'Asia/Almaty', + 'Asia/Amman', + 'Asia/Anadyr', + 'Asia/Aqtau', + 'Asia/Aqtobe', + 'Asia/Ashgabat', + 'Asia/Atyrau', + 'Asia/Baghdad', + 'Asia/Bahrain', + 'Asia/Baku', + 'Asia/Bangkok', + 'Asia/Barnaul', + 'Asia/Beirut', + 'Asia/Bishkek', + 'Asia/Brunei', + 'Asia/Calcutta', + 'Asia/Chita', + 'Asia/Colombo', + 'Asia/Damascus', + 'Asia/Dhaka', + 'Asia/Dili', + 'Asia/Dubai', + 'Asia/Dushanbe', + 'Asia/Famagusta', + 'Asia/Gaza', + 'Asia/Hebron', + 'Asia/Hong_Kong', + 'Asia/Hovd', + 'Asia/Irkutsk', + 'Asia/Jakarta', + 'Asia/Jayapura', + 'Asia/Jerusalem', + 'Asia/Kabul', + 'Asia/Kamchatka', + 'Asia/Karachi', + 'Asia/Katmandu', + 'Asia/Khandyga', + 'Asia/Krasnoyarsk', + 'Asia/Kuala_Lumpur', + 'Asia/Kuching', + 'Asia/Kuwait', + 'Asia/Macau', + 'Asia/Magadan', + 'Asia/Makassar', + 'Asia/Manila', + 'Asia/Muscat', + 'Asia/Nicosia', + 'Asia/Novokuznetsk', + 'Asia/Novosibirsk', + 'Asia/Omsk', + 'Asia/Oral', + 'Asia/Phnom_Penh', + 'Asia/Pontianak', + 'Asia/Pyongyang', + 'Asia/Qatar', + 'Asia/Qostanay', + 'Asia/Qyzylorda', + 'Asia/Rangoon', + 'Asia/Riyadh', + 'Asia/Saigon', + 'Asia/Sakhalin', + 'Asia/Samarkand', + 'Asia/Seoul', + 'Asia/Shanghai', + 'Asia/Singapore', + 'Asia/Srednekolymsk', + 'Asia/Taipei', + 'Asia/Tashkent', + 'Asia/Tbilisi', + 'Asia/Tehran', + 'Asia/Thimphu', + 'Asia/Tokyo', + 'Asia/Tomsk', + 'Asia/Ulaanbaatar', + 'Asia/Urumqi', + 'Asia/Ust-Nera', + 'Asia/Vientiane', + 'Asia/Vladivostok', + 'Asia/Yakutsk', + 'Asia/Yekaterinburg', + 'Asia/Yerevan', + 'Atlantic/Azores', + 'Atlantic/Bermuda', + 'Atlantic/Canary', + 'Atlantic/Cape_Verde', + 'Atlantic/Faeroe', + 'Atlantic/Madeira', + 'Atlantic/Reykjavik', + 'Atlantic/South_Georgia', + 'Atlantic/St_Helena', + 'Atlantic/Stanley', + 'Australia/Adelaide', + 'Australia/Brisbane', + 'Australia/Broken_Hill', + 'Australia/Darwin', + 'Australia/Eucla', + 'Australia/Hobart', + 'Australia/Lindeman', + 'Australia/Lord_Howe', + 'Australia/Melbourne', + 'Australia/Perth', + 'Australia/Sydney', + 'Europe/Amsterdam', + 'Europe/Andorra', + 'Europe/Astrakhan', + 'Europe/Athens', + 'Europe/Belgrade', + 'Europe/Berlin', + 'Europe/Bratislava', + 'Europe/Brussels', + 'Europe/Bucharest', + 'Europe/Budapest', + 'Europe/Busingen', + 'Europe/Chisinau', + 'Europe/Copenhagen', + 'Europe/Dublin', + 'Europe/Gibraltar', + 'Europe/Guernsey', + 'Europe/Helsinki', + 'Europe/Isle_of_Man', + 'Europe/Istanbul', + 'Europe/Jersey', + 'Europe/Kaliningrad', + 'Europe/Kiev', + 'Europe/Kirov', + 'Europe/Lisbon', + 'Europe/Ljubljana', + 'Europe/London', + 'Europe/Luxembourg', + 'Europe/Madrid', + 'Europe/Malta', + 'Europe/Mariehamn', + 'Europe/Minsk', + 'Europe/Monaco', + 'Europe/Moscow', + 'Europe/Oslo', + 'Europe/Paris', + 'Europe/Podgorica', + 'Europe/Prague', + 'Europe/Riga', + 'Europe/Rome', + 'Europe/Samara', + 'Europe/San_Marino', + 'Europe/Sarajevo', + 'Europe/Saratov', + 'Europe/Simferopol', + 'Europe/Skopje', + 'Europe/Sofia', + 'Europe/Stockholm', + 'Europe/Tallinn', + 'Europe/Tirane', + 'Europe/Ulyanovsk', + 'Europe/Vaduz', + 'Europe/Vatican', + 'Europe/Vienna', + 'Europe/Vilnius', + 'Europe/Volgograd', + 'Europe/Warsaw', + 'Europe/Zagreb', + 'Europe/Zurich', + 'Indian/Antananarivo', + 'Indian/Chagos', + 'Indian/Christmas', + 'Indian/Cocos', + 'Indian/Comoro', + 'Indian/Kerguelen', + 'Indian/Mahe', + 'Indian/Maldives', + 'Indian/Mauritius', + 'Indian/Mayotte', + 'Indian/Reunion', + 'Pacific/Apia', + 'Pacific/Auckland', + 'Pacific/Bougainville', + 'Pacific/Chatham', + 'Pacific/Easter', + 'Pacific/Efate', + 'Pacific/Enderbury', + 'Pacific/Fakaofo', + 'Pacific/Fiji', + 'Pacific/Funafuti', + 'Pacific/Galapagos', + 'Pacific/Gambier', + 'Pacific/Guadalcanal', + 'Pacific/Guam', + 'Pacific/Honolulu', + 'Pacific/Kiritimati', + 'Pacific/Kosrae', + 'Pacific/Kwajalein', + 'Pacific/Majuro', + 'Pacific/Marquesas', + 'Pacific/Midway', + 'Pacific/Nauru', + 'Pacific/Niue', + 'Pacific/Norfolk', + 'Pacific/Noumea', + 'Pacific/Pago_Pago', + 'Pacific/Palau', + 'Pacific/Pitcairn', + 'Pacific/Ponape', + 'Pacific/Port_Moresby', + 'Pacific/Rarotonga', + 'Pacific/Saipan', + 'Pacific/Tahiti', + 'Pacific/Tarawa', + 'Pacific/Tongatapu', + 'Pacific/Truk', + 'Pacific/Wake', + 'Pacific/Wallis' +]; diff --git a/frontend/src/lib/index.ts b/frontend/src/lib/index.ts index 0e143691..d66abec7 100644 --- a/frontend/src/lib/index.ts +++ b/frontend/src/lib/index.ts @@ -70,34 +70,65 @@ export function groupAdventuresByDate( // Initialize all days in the range for (let i = 0; i < numberOfDays; i++) { const currentDate = new Date(startDate); - currentDate.setUTCDate(startDate.getUTCDate() + i); - const dateString = currentDate.toISOString().split('T')[0]; + currentDate.setDate(startDate.getDate() + i); + const dateString = getLocalDateString(currentDate); groupedAdventures[dateString] = []; } adventures.forEach((adventure) => { adventure.visits.forEach((visit) => { if (visit.start_date) { - const adventureDate = new Date(visit.start_date).toISOString().split('T')[0]; - if (visit.end_date) { - const endDate = new Date(visit.end_date).toISOString().split('T')[0]; + // Check if this is an all-day event (both start and end at midnight) + const isAllDayEvent = + isAllDay(visit.start_date) && (visit.end_date ? isAllDay(visit.end_date) : false); - // Loop through all days and include adventure if it falls within the range + // For all-day events, we need to handle dates differently + if (isAllDayEvent && visit.end_date) { + // Extract just the date parts without time + const startDateStr = visit.start_date.split('T')[0]; + const endDateStr = visit.end_date.split('T')[0]; + + // Loop through all days in the range for (let i = 0; i < numberOfDays; i++) { const currentDate = new Date(startDate); - currentDate.setUTCDate(startDate.getUTCDate() + i); - const dateString = currentDate.toISOString().split('T')[0]; + currentDate.setDate(startDate.getDate() + i); + const currentDateStr = getLocalDateString(currentDate); // Include the current day if it falls within the adventure date range - if (dateString >= adventureDate && dateString <= endDate) { - if (groupedAdventures[dateString]) { - groupedAdventures[dateString].push(adventure); + if (currentDateStr >= startDateStr && currentDateStr <= endDateStr) { + if (groupedAdventures[currentDateStr]) { + groupedAdventures[currentDateStr].push(adventure); + } + } + } + } else { + // Handle regular events with time components + const adventureStartDate = new Date(visit.start_date); + const adventureDateStr = getLocalDateString(adventureStartDate); + + if (visit.end_date) { + const adventureEndDate = new Date(visit.end_date); + const endDateStr = getLocalDateString(adventureEndDate); + + // Loop through all days and include adventure if it falls within the range + for (let i = 0; i < numberOfDays; i++) { + const currentDate = new Date(startDate); + currentDate.setDate(startDate.getDate() + i); + const dateString = getLocalDateString(currentDate); + + // Include the current day if it falls within the adventure date range + if (dateString >= adventureDateStr && dateString <= endDateStr) { + if (groupedAdventures[dateString]) { + groupedAdventures[dateString].push(adventure); + } } } + } else { + // If there's no end date, add adventure to the start date only + if (groupedAdventures[adventureDateStr]) { + groupedAdventures[adventureDateStr].push(adventure); + } } - } else if (groupedAdventures[adventureDate]) { - // If there's no end date, add adventure to the start date only - groupedAdventures[adventureDate].push(adventure); } } }); @@ -106,6 +137,20 @@ export function groupAdventuresByDate( return groupedAdventures; } +function getLocalDateString(date: Date): string { + const year = date.getFullYear(); + const month = String(date.getMonth() + 1).padStart(2, '0'); // Months are 0-indexed + const day = String(date.getDate()).padStart(2, '0'); + return `${year}-${month}-${day}`; +} + +// Helper to check if a given date string represents midnight (all-day) +// Improved isAllDay function to handle different ISO date formats +export function isAllDay(dateStr: string): boolean { + // Check for various midnight formats in UTC + return dateStr.endsWith('T00:00:00Z') || dateStr.endsWith('T00:00:00.000Z'); +} + export function groupTransportationsByDate( transportations: Transportation[], startDate: Date, @@ -116,22 +161,22 @@ export function groupTransportationsByDate( // Initialize all days in the range for (let i = 0; i < numberOfDays; i++) { const currentDate = new Date(startDate); - currentDate.setUTCDate(startDate.getUTCDate() + i); - const dateString = currentDate.toISOString().split('T')[0]; + currentDate.setDate(startDate.getDate() + i); + const dateString = getLocalDateString(currentDate); groupedTransportations[dateString] = []; } transportations.forEach((transportation) => { if (transportation.date) { - const transportationDate = new Date(transportation.date).toISOString().split('T')[0]; + const transportationDate = getLocalDateString(new Date(transportation.date)); if (transportation.end_date) { const endDate = new Date(transportation.end_date).toISOString().split('T')[0]; // Loop through all days and include transportation if it falls within the range for (let i = 0; i < numberOfDays; i++) { const currentDate = new Date(startDate); - currentDate.setUTCDate(startDate.getUTCDate() + i); - const dateString = currentDate.toISOString().split('T')[0]; + currentDate.setDate(startDate.getDate() + i); + const dateString = getLocalDateString(currentDate); // Include the current day if it falls within the transportation date range if (dateString >= transportationDate && dateString <= endDate) { @@ -157,35 +202,32 @@ export function groupLodgingByDate( ): Record { const groupedTransportations: Record = {}; - // Initialize all days in the range + // Initialize all days in the range using local dates for (let i = 0; i < numberOfDays; i++) { const currentDate = new Date(startDate); - currentDate.setUTCDate(startDate.getUTCDate() + i); - const dateString = currentDate.toISOString().split('T')[0]; + currentDate.setDate(startDate.getDate() + i); + const dateString = getLocalDateString(currentDate); groupedTransportations[dateString] = []; } transportations.forEach((transportation) => { if (transportation.check_in) { - const transportationDate = new Date(transportation.check_in).toISOString().split('T')[0]; + // Use local date string conversion + const transportationDate = getLocalDateString(new Date(transportation.check_in)); if (transportation.check_out) { - const endDate = new Date(transportation.check_out).toISOString().split('T')[0]; + const endDate = getLocalDateString(new Date(transportation.check_out)); - // Loop through all days and include transportation if it falls within the range + // Loop through all days and include transportation if it falls within the transportation date range for (let i = 0; i < numberOfDays; i++) { const currentDate = new Date(startDate); - currentDate.setUTCDate(startDate.getUTCDate() + i); - const dateString = currentDate.toISOString().split('T')[0]; + currentDate.setDate(startDate.getDate() + i); + const dateString = getLocalDateString(currentDate); - // Include the current day if it falls within the transportation date range if (dateString >= transportationDate && dateString <= endDate) { - if (groupedTransportations[dateString]) { - groupedTransportations[dateString].push(transportation); - } + groupedTransportations[dateString].push(transportation); } } } else if (groupedTransportations[transportationDate]) { - // If there's no end date, add transportation to the start date only groupedTransportations[transportationDate].push(transportation); } } @@ -201,19 +243,18 @@ export function groupNotesByDate( ): Record { const groupedNotes: Record = {}; - // Initialize all days in the range + // Initialize all days in the range using local dates for (let i = 0; i < numberOfDays; i++) { const currentDate = new Date(startDate); - currentDate.setUTCDate(startDate.getUTCDate() + i); - const dateString = currentDate.toISOString().split('T')[0]; + currentDate.setDate(startDate.getDate() + i); + const dateString = getLocalDateString(currentDate); groupedNotes[dateString] = []; } notes.forEach((note) => { if (note.date) { - const noteDate = new Date(note.date).toISOString().split('T')[0]; - - // Add note to the appropriate date group if it exists + // Use the date string as is since it's already in "YYYY-MM-DD" format. + const noteDate = note.date; if (groupedNotes[noteDate]) { groupedNotes[noteDate].push(note); } @@ -230,19 +271,18 @@ export function groupChecklistsByDate( ): Record { const groupedChecklists: Record = {}; - // Initialize all days in the range + // Initialize all days in the range using local dates for (let i = 0; i < numberOfDays; i++) { const currentDate = new Date(startDate); - currentDate.setUTCDate(startDate.getUTCDate() + i); - const dateString = currentDate.toISOString().split('T')[0]; + currentDate.setDate(startDate.getDate() + i); + const dateString = getLocalDateString(currentDate); groupedChecklists[dateString] = []; } checklists.forEach((checklist) => { if (checklist.date) { - const checklistDate = new Date(checklist.date).toISOString().split('T')[0]; - - // Add checklist to the appropriate date group if it exists + // Use the date string as is since it's already in "YYYY-MM-DD" format. + const checklistDate = checklist.date; if (groupedChecklists[checklistDate]) { groupedChecklists[checklistDate].push(checklist); } @@ -338,6 +378,17 @@ export let LODGING_TYPES_ICONS = { other: '❓' }; +export let TRANSPORTATION_TYPES_ICONS = { + car: '🚗', + plane: 'âœˆī¸', + train: '🚆', + bus: '🚌', + boat: 'â›ĩ', + bike: '🚲', + walking: 'đŸšļ', + other: '❓' +}; + export function getAdventureTypeLabel(type: string) { // return the emoji ADVENTURE_TYPE_ICONS label for the given type if not found return ? emoji if (type in ADVENTURE_TYPE_ICONS) { diff --git a/frontend/src/lib/types.ts b/frontend/src/lib/types.ts index fd0eadbd..286a37d6 100644 --- a/frontend/src/lib/types.ts +++ b/frontend/src/lib/types.ts @@ -1,3 +1,5 @@ +import { VALID_TIMEZONES } from './dateUtils'; + export type User = { pk: number; username: string; @@ -25,11 +27,13 @@ export type Adventure = { id: string; image: string; is_primary: boolean; + immich_id: string | null; }[]; visits: { id: string; start_date: string; end_date: string; + timezone: string | null; notes: string; }[]; collection?: string | null; @@ -42,6 +46,18 @@ export type Adventure = { category: Category | null; attachments: Attachment[]; user?: User | null; + city?: City | null; + region?: Region | null; + country?: Country | null; +}; + +export type AdditionalAdventure = Adventure & { + sun_times: { + date: string; + visit_id: string; + sunrise: string; + sunset: string; + }[]; }; export type Country = { @@ -124,21 +140,15 @@ export type Collection = { link?: string | null; }; -export type OpenStreetMapPlace = { - place_id: number; - licence: string; - osm_type: string; - osm_id: number; - lat: string; - lon: string; - category: string; - type: string; - place_rank: number; - importance: number; - addresstype: string; - name: string; - display_name: string; - boundingbox: string[]; +export type GeocodeSearchResult = { + lat?: string; + lon?: string; + category?: string; + type?: string; + importance?: number; + addresstype?: string; + name?: string; + display_name?: string; }; export type Transportation = { @@ -151,6 +161,8 @@ export type Transportation = { link: string | null; date: string | null; // ISO 8601 date string end_date: string | null; // ISO 8601 date string + start_timezone: string | null; + end_timezone: string | null; flight_number: string | null; from_location: string | null; to_location: string | null; @@ -159,6 +171,7 @@ export type Transportation = { destination_latitude: number | null; destination_longitude: number | null; is_public: boolean; + distance: number | null; // in kilometers collection: Collection | null | string; created_at: string; // ISO 8601 date string updated_at: string; // ISO 8601 date string @@ -230,6 +243,7 @@ export type ImmichIntegration = { id: string; server_url: string; api_key: string; + copy_locally: boolean; }; export type ImmichAlbum = { @@ -279,6 +293,7 @@ export type Lodging = { link: string | null; check_in: string | null; // ISO 8601 date string check_out: string | null; // ISO 8601 date string + timezone: string | null; reservation_number: string | null; price: number | null; latitude: number | null; diff --git a/frontend/src/locales/de.json b/frontend/src/locales/de.json index 56c84dcb..8ff6f139 100644 --- a/frontend/src/locales/de.json +++ b/frontend/src/locales/de.json @@ -8,7 +8,8 @@ "nominatim_2": "Deren Daten sind unter der ODbL-Lizenz lizenziert.", "oss_attributions": "Open Source Quellenangaben", "other_attributions": "Weitere Hinweise finden Sie in der README-Datei.", - "source_code": "Quellcode" + "source_code": "Quellcode", + "generic_attributions": "Melden Sie sich bei Adventurelog an, um Zuschreibungen fÃŧr aktivierte Integrationen und Dienste anzuzeigen." }, "adventures": { "activities": { @@ -247,7 +248,32 @@ "price": "Preis", "reservation_number": "Reservierungsnummer", "welcome_map_info": "Frei zugängliche Abenteuer auf diesem Server", - "open_in_maps": "In Karten Ãļffnen" + "open_in_maps": "In Karten Ãļffnen", + "all_day": "Den ganzen Tag", + "collection_no_start_end_date": "Durch das HinzufÃŧgen eines Start- und Enddatums zur Sammlung werden Reiseroutenplanungsfunktionen auf der Sammlungsseite freigegeben.", + "date_itinerary": "Datumstrecke", + "no_ordered_items": "FÃŧgen Sie der Sammlung Elemente mit Daten hinzu, um sie hier zu sehen.", + "ordered_itinerary": "Reiseroute bestellt", + "additional_info": "Weitere Informationen", + "invalid_date_range": "UngÃŧltiger Datumsbereich", + "sunrise_sunset": "Sonnenaufgang", + "timezone": "Zeitzone", + "no_visits": "Keine Besuche", + "arrival_timezone": "Ankunftszeitzone", + "departure_timezone": "Abfahrtszeit", + "arrival_date": "Ankunftsdatum", + "departure_date": "Abflugdatum", + "coordinates": "Koordinaten", + "copy_coordinates": "Koordinaten kopieren", + "sun_times": "Sonnenzeiten", + "sunrise": "Sonnenaufgang", + "sunset": "Sonnenuntergang", + "timed": "Zeitlich abgestimmt", + "distance": "Distanz", + "all_linked_items": "Alle verknÃŧpften Elemente", + "itinerary": "Route", + "joined": "Verbunden", + "view_profile": "Profil anzeigen" }, "home": { "desc_1": "Entdecken, planen und erkunden Sie mÃŧhelos", @@ -436,7 +462,58 @@ "password_disabled": "Kennwortauthentifizierung deaktiviert", "password_disabled_error": "Fehler beim Deaktivieren der Kennwortauthentifizierung. \nStellen Sie sicher, dass ein sozialer oder OIDC -Anbieter mit Ihrem Konto verknÃŧpft ist.", "password_enabled": "Kennwortauthentifizierung aktiviert", - "password_enabled_error": "Fehler zur Kennwortauthentifizierung." + "password_enabled_error": "Fehler zur Kennwortauthentifizierung.", + "access_restricted": "Zugang eingeschränkt", + "access_restricted_desc": "Yadministrative Funktionen stehen den Mitarbeitern nur zur VerfÃŧgung.", + "add_new_email": "Neue E -Mail hinzufÃŧgen", + "add_new_email_address": "Neue E -Mail -Adresse hinzufÃŧgen", + "admin": "Administrator", + "admin_panel_desc": "Greifen Sie auf die vollständige Verwaltungsschnittstelle zu", + "administration": "Verwaltung", + "administration_desc": "Verwaltungswerkzeuge und Einstellungen", + "advanced": "Fortschrittlich", + "advanced_settings": "Erweiterte Einstellungen", + "advanced_settings_desc": "Erweiterte Konfigurations- und Entwicklungstools", + "all_rights_reserved": "Alle Rechte vorbehalten.", + "app_version": "App -Version", + "confirm_new_password_desc": "Neues Passwort bestätigen", + "connected": "Verbunden", + "debug_information": "Informationen debuggen", + "disabled": "Deaktiviert", + "disconnected": "Getrennt", + "email_management": "E -Mail -Management", + "email_management_desc": "Verwalten Sie Ihre E -Mail -Adressen und den ÜberprÃŧfungsstatus", + "emails": "E -Mails", + "enabled": "ErmÃļglicht", + "enter_current_password": "Geben Sie das aktuelle Passwort ein", + "enter_first_name": "Geben Sie Ihren Vornamen ein", + "enter_last_name": "Geben Sie Ihren Nachnamen ein", + "enter_new_email": "Geben Sie eine neue E -Mail -Adresse ein", + "enter_new_password": "Geben Sie ein neues Passwort ein", + "enter_username": "Geben Sie Ihren Benutzernamen ein", + "integrations": "Integrationen", + "integrations_desc": "Verbinden Sie externe Dienste, um Ihre Erfahrung zu verbessern", + "license": "Lizenz", + "mfa_desc": "FÃŧgen Sie Ihrem Konto eine zusätzliche Sicherheitsebene hinzu", + "mfa_is_enabled": "MFA ist aktiviert", + "pass_change_desc": "Aktualisieren Sie Ihr Kontokennwort fÃŧr eine bessere Sicherheit", + "password_auth": "Passwortauthentifizierung", + "password_login_disabled": "Passwort Login deaktiviert", + "password_login_enabled": "Passwort Login aktiviert", + "profile_info": "Profilinformationen", + "public_profile_desc": "Machen Sie Ihr Profil fÃŧr andere Benutzer sichtbar", + "quick_actions": "Schnelle Aktionen", + "region_updates": "Regionen Updates", + "region_updates_desc": "Update besuchte Regionen und Städte", + "regular_user": "Regulärer Benutzer", + "security": "Sicherheit", + "settings_menu": "EinstellungsmenÃŧ", + "social_auth": "Sozialauthentifizierung", + "social_auth_desc_1": "Verwalten Sie die Optionen fÃŧr soziale Anmeldungen und Kennworteinstellungen", + "social_auth_setup": "Social Authentication Setup", + "staff_status": "Personalstatus", + "staff_user": "Personalbenutzer", + "profile_info_desc": "Aktualisieren Sie Ihre persÃļnlichen Daten und Ihr Profilbild" }, "checklist": { "add_item": "Eintrag hinzufÃŧgen", @@ -553,7 +630,9 @@ "manage_categories": "Kategorien verwalten", "no_categories_found": "Keine Kategorien gefunden.", "select_category": "Kategorie wählen", - "update_after_refresh": "Die Abenteuerkarten werden aktualisiert, sobald Sie die Seite aktualisieren." + "update_after_refresh": "Die Abenteuerkarten werden aktualisiert, sobald Sie die Seite aktualisieren.", + "add_category": "Kategorie hinzufÃŧgen", + "add_new_category": "Neue Kategorie hinzufÃŧgen" }, "dashboard": { "add_some": "Warum nicht gleich Ihr nächstes Abenteuer planen? Sie kÃļnnen ein neues Abenteuer hinzufÃŧgen, indem Sie auf den Button unten klicken.", @@ -588,14 +667,31 @@ "update_integration": "Integration updaten", "immich_integration": "Immich-Integration", "documentation": "Dokumentation zur Immich-Integration", - "localhost_note": "Hinweis: localhost wird hÃļchstwahrscheinlich nicht funktionieren, es sei denn, Sie haben Docker-Netzwerke entsprechend eingerichtet. \nEs wird empfohlen, die IP-Adresse des Servers oder den Domänennamen zu verwenden." + "localhost_note": "Hinweis: localhost wird hÃļchstwahrscheinlich nicht funktionieren, es sei denn, Sie haben Docker-Netzwerke entsprechend eingerichtet. \nEs wird empfohlen, die IP-Adresse des Servers oder den Domänennamen zu verwenden.", + "api_key_placeholder": "Geben Sie Ihren Immich -API -SchlÃŧssel ein", + "enable_integration": "Integration aktivieren", + "immich_integration_desc": "Schließen Sie Ihren Immich -Photo -Management -Server an", + "need_help": "BenÃļtigen Sie Hilfe bei der Einrichtung? \nSchauen Sie sich das an", + "connection_error": "Fehler, die eine Verbindung zum Immich -Server herstellen", + "copy_locally": "Kopieren Sie Bilder lokal", + "copy_locally_desc": "Kopieren Sie Bilder auf den Server fÃŧr den Offline -Zugriff. \nNutzt mehr Speicherplatz.", + "error_saving_image": "Fehler speichern Bild", + "integration_already_exists": "Es gibt bereits eine Immichintegration. \nSie kÃļnnen jeweils nur eine Integration haben.", + "integration_not_found": "Immich -Integration nicht gefunden. \nBitte erstellen Sie eine neue Integration.", + "network_error": "Netzwerkfehler beim Verbindung mit dem Immich -Server. \nBitte ÃŧberprÃŧfen Sie Ihre Verbindung und versuchen Sie es erneut.", + "validation_error": "Bei der Validierung der Immichintegration trat ein Fehler auf. \nBitte ÃŧberprÃŧfen Sie Ihre Server -URL- und API -SchlÃŧssel." }, "recomendations": { "address": "Adresse", "contact": "Kontakt", "phone": "Telefon", "recommendation": "Empfehlung", - "website": "Webseite" + "website": "Webseite", + "recommendations": "Empfehlungen", + "adventure_recommendations": "Abenteuerempfehlungen", + "food": "Essen", + "miles": "Meilen", + "tourism": "Tourismus" }, "lodging": { "apartment": "Wohnung", @@ -626,5 +722,8 @@ "type": "Typ", "villa": "Villa", "current_timezone": "Aktuelle Zeitzone" + }, + "google_maps": { + "google_maps_integration_desc": "Verbinden Sie Ihr Google Maps-Konto, um hochwertige Suchergebnisse und Empfehlungen fÃŧr Standort zu erhalten." } } diff --git a/frontend/src/locales/en.json b/frontend/src/locales/en.json index 25a79de8..65b00cf3 100644 --- a/frontend/src/locales/en.json +++ b/frontend/src/locales/en.json @@ -42,6 +42,7 @@ "nominatim_1": "Location Search and Geocoding is provided by", "nominatim_2": "Their data is liscensed under the ODbL license.", "other_attributions": "Additional attributions can be found in the README file.", + "generic_attributions": "Login to AdventureLog to view attributions for enabled integrations and services.", "close": "Close" }, "home": { @@ -62,6 +63,13 @@ "collection_remove_success": "Adventure removed from collection successfully!", "collection_remove_error": "Error removing adventure from collection", "collection_link_success": "Adventure linked to collection successfully!", + "invalid_date_range": "Invalid date range", + "timezone": "Timezone", + "no_visits": "No visits", + "departure_timezone": "Departure Timezone", + "arrival_timezone": "Arrival Timezone", + "departure_date": "Departure Date", + "arrival_date": "Arrival Date", "no_image_found": "No image found", "collection_link_error": "Error linking adventure to collection", "adventure_delete_confirm": "Are you sure you want to delete this adventure? This action cannot be undone.", @@ -87,9 +95,14 @@ "longitude": "Longitude", "latitude": "Latitude", "visit": "Visit", + "timed": "Timed", + "coordinates": "Coordinates", + "copy_coordinates": "Copy Coordinates", "visits": "Visits", "create_new": "Create New...", "adventure": "Adventure", + "additional_info": "Additional Information", + "sunrise_sunset": "Sunrise & Sunset", "count_txt": "results matching your search", "sort": "Sort", "order_by": "Order By", @@ -108,9 +121,13 @@ "add_an_activity": "Add an activity", "show_region_labels": "Show Region Labels", "no_images": "No Images", + "distance": "Distance", "upload_images_here": "Upload images here", "share_adventure": "Share this Adventure!", "copy_link": "Copy Link", + "sun_times": "Sun Times", + "sunrise": "Sunrise", + "sunset": "Sunset", "image": "Image", "upload_image": "Upload Image", "open_in_maps": "Open in Maps", @@ -130,7 +147,8 @@ "location": "Location", "search_for_location": "Search for a location", "clear_map": "Clear map", - "search_results": "Searh results", + "search_results": "Search results", + "collection_no_start_end_date": "Adding a start and end date to the collection will unlock itinerary planning features in the collection page.", "no_results": "No results found", "wiki_desc": "Pulls excerpt from Wikipedia article matching the name of the adventure.", "attachments": "Attachments", @@ -240,6 +258,8 @@ "hide": "Hide", "clear_location": "Clear Location", "starting_airport": "Starting Airport", + "view_profile": "View Profile", + "joined": "Joined", "ending_airport": "Ending Airport", "no_location_found": "No location found", "from": "From", @@ -250,6 +270,12 @@ "show_map": "Show Map", "emoji_picker": "Emoji Picker", "download_calendar": "Download Calendar", + "all_day": "All Day", + "ordered_itinerary": "Ordered Itinerary", + "itinerary": "Itinerary", + "all_linked_items": "All Linked Items", + "date_itinerary": "Date Itinerary", + "no_ordered_items": "Add items with dates to the collection to see them here.", "date_information": "Date Information", "flight_information": "Flight Information", "out_of_range": "Not in itinerary date range", @@ -356,12 +382,22 @@ "account_settings": "User Account Settings", "update": "Update", "no_verified_email_warning": "You must have a verified email address to enable two-factor authentication.", + "social_auth": "Social Authentication", + "social_auth_desc_1": "Manage social login options and password settings", + "password_auth": "Password Authentication", + "password_login_enabled": "Password login enabled", + "password_login_disabled": "Password login disabled", "password_change": "Change Password", "new_password": "New Password", "confirm_new_password": "Confirm New Password", "email_change": "Change Email", "current_email": "Current Email", "no_email_set": "No email set", + "email_management": "Email Management", + "email_management_desc": "Manage your email addresses and verification status", + "add_new_email": "Add New Email", + "add_new_email_address": "Add New Email Address", + "enter_new_email": "Enter new email address", "new_email": "New Email", "change_password": "Change Password", "login_redir": "You will then be redirected to the login page.", @@ -397,10 +433,14 @@ "no_emai_set": "No email set", "error_change_password": "Error changing password. Please check your current password and try again.", "mfa_disabled": "Multi-factor authentication disabled successfully!", - "mfa_page_title": "Multi-factor Authentication", + "mfa_page_title": "Multi-Factor Authentication", + "mfa_desc": "Add an extra layer of security to your account", "enable_mfa": "Enable MFA", "disable_mfa": "Disable MFA", + "enabled": "Enabled", + "disabled": "Disabled", "mfa_not_enabled": "MFA is not enabled", + "mfa_is_enabled": "MFA is enabled", "mfa_enabled": "Multi-factor authentication enabled successfully!", "copy": "Copy", "recovery_codes": "Recovery Codes", @@ -422,6 +462,24 @@ "username_taken": "This username is already in use.", "administration_settings": "Administration Settings", "launch_administration_panel": "Launch Administration Panel", + "administration": "Administration", + "admin_panel_desc": "Access the full administration interface", + "region_updates": "Region Updates", + "debug_information": "Debug Information", + "staff_status": "Staff Status", + "staff_user": "Staff User", + "regular_user": "Regular User", + "app_version": "App Version", + "quick_actions": "Quick Actions", + "license": "License", + "all_rights_reserved": "All rights reserved.", + "region_updates_desc": "Update visited regions and cities", + "access_restricted": "Access Restricted", + "access_restricted_desc": "YAdministrative features are only available to staff members.", + "advanced_settings": "Advanced Settings", + "advanced_settings_desc": "Advanced configuration and development tools", + "social_auth_setup": "Social Authentication Setup", + "administration_desc": "Administrative tools and settings", "social_oidc_auth": "Social and OIDC Authentication", "social_auth_desc": "Enable or disable social and OIDC authentication providers for your account. These connections allow you to sign in with self hosted authentication identity providers like Authentik or 3rd party providers like GitHub.", "social_auth_desc_2": "These settings are managed in the AdventureLog server and must be manually enabled by the administrator.", @@ -436,7 +494,26 @@ "password_disabled": "Password authentication disabled", "password_disable_warning": "Currently, password authentication is disabled. Login via a social or OIDC provider is required.", "password_disabled_error": "Error disabling password authentication. Make sure a social or OIDC provider is linked to your account.", - "password_enabled_error": "Error enabling password authentication." + "password_enabled_error": "Error enabling password authentication.", + "settings_menu": "Settings Menu", + "security": "Security", + "emails": "Emails", + "integrations": "Integrations", + "integrations_desc": "Connect external services to enhance your experience", + "admin": "Admin", + "advanced": "Advanced", + "profile_info": "Profile Information", + "profile_info_desc": "Update your personal details and profile picture", + "public_profile_desc": "Make your profile visible to other users", + "pass_change_desc": "Update your account password for better security", + "enter_first_name": "Enter your first name", + "enter_last_name": "Enter your last name", + "enter_username": "Enter your username", + "enter_current_password": "Enter current password", + "enter_new_password": "Enter new password", + "connected": "Connected", + "disconnected": "Disconnected", + "confirm_new_password_desc": "Confirm new password" }, "collection": { "collection_created": "Collection created successfully!", @@ -583,7 +660,9 @@ "icon": "Icon", "update_after_refresh": "The adventure cards will be updated once you refresh the page.", "select_category": "Select Category", - "category_name": "Category Name" + "category_name": "Category Name", + "add_category": "Add Category", + "add_new_category": "Add New Category" }, "dashboard": { "welcome_back": "Welcome back", @@ -615,16 +694,36 @@ "api_note": "Note: this must be the URL to the Immich API server so it likely ends with /api unless you have a custom config.", "api_key": "Immich API Key", "enable_immich": "Enable Immich", + "enable_integration": "Enable Integration", "update_integration": "Update Integration", "immich_integration": "Immich Integration", + "immich_integration_desc": "Connect your Immich photo management server", "localhost_note": "Note: localhost will most likely not work unless you have setup docker networks accordingly. It is recommended to use the IP address of the server or the domain name.", - "documentation": "Immich Integration Documentation" + "documentation": "Immich Integration Documentation", + "api_key_placeholder": "Enter your Immich API key", + "need_help": "Need help setting this up? Check out the", + "copy_locally": "Copy Images Locally", + "copy_locally_desc": "Copy images to the server for offline access. Uses more disk space.", + "error_saving_image": "Error saving image", + "connection_error": "Error connecting to Immich server", + "integration_already_exists": "An Immich integration already exists. You can only have one integration at a time.", + "integration_not_found": "Immich integration not found. Please create a new integration.", + "validation_error": "An error occurred while validating the Immich integration. Please check your server URL and API key.", + "network_error": "Network error while connecting to the Immich server. Please check your connection and try again." + }, + "google_maps": { + "google_maps_integration_desc": "Connect your Google Maps account to get high-quality location search results and recommendations." }, "recomendations": { "address": "Address", "phone": "Phone", "contact": "Contact", "website": "Website", - "recommendation": "Recommendation" + "recommendation": "Recommendation", + "recommendations": "Recommendations", + "adventure_recommendations": "Adventure Recommendations", + "miles": "Miles", + "food": "Food", + "tourism": "Tourism" } } diff --git a/frontend/src/locales/es.json b/frontend/src/locales/es.json index 3814ec2d..fa01b4c4 100644 --- a/frontend/src/locales/es.json +++ b/frontend/src/locales/es.json @@ -42,7 +42,8 @@ "nominatim_1": "La bÃēsqueda de ubicaciones y geocodificaciÃŗn es proporcionada por", "nominatim_2": "Sus datos estÃĄn licenciados bajo la licencia ODbL.", "other_attributions": "Atribuciones adicionales se pueden encontrar en el archivo README.", - "close": "Cerrar" + "close": "Cerrar", + "generic_attributions": "Inicie sesiÃŗn en AdventurElog para ver las atribuciones para integraciones y servicios habilitados." }, "home": { "hero_1": "Descubre las Aventuras MÃĄs Emocionantes del Mundo", @@ -295,7 +296,32 @@ "region": "RegiÃŗn", "reservation_number": "NÃēmero de reserva", "welcome_map_info": "Aventuras pÃēblicas en este servidor", - "open_in_maps": "Abrir en mapas" + "open_in_maps": "Abrir en mapas", + "all_day": "Todo el día", + "collection_no_start_end_date": "Agregar una fecha de inicio y finalizaciÃŗn a la colecciÃŗn desbloquearÃĄ las funciones de planificaciÃŗn del itinerario en la pÃĄgina de colecciÃŗn.", + "date_itinerary": "Itinerario de fecha", + "no_ordered_items": "Agregue elementos con fechas a la colecciÃŗn para verlos aquí.", + "ordered_itinerary": "Itinerario ordenado", + "additional_info": "informaciÃŗn adicional", + "invalid_date_range": "Rango de fechas no vÃĄlido", + "sunrise_sunset": "Amanecer", + "timezone": "Zona horaria", + "no_visits": "No hay visitas", + "arrival_timezone": "Zona horaria de llegada", + "departure_timezone": "Zona horaria de salida", + "arrival_date": "Fecha de llegada", + "departure_date": "Fecha de salida", + "coordinates": "Coordenadas", + "copy_coordinates": "Coordenadas de copia", + "sun_times": "Sol Times", + "sunrise": "Amanecer", + "sunset": "Atardecer", + "timed": "Cronometrado", + "distance": "Distancia", + "all_linked_items": "Todos los artículos vinculados", + "itinerary": "Itinerario", + "joined": "Unido", + "view_profile": "Ver perfil" }, "worldtravel": { "all": "Todo", @@ -436,7 +462,58 @@ "password_disabled": "AutenticaciÃŗn de contraseÃąa deshabilitada", "password_disabled_error": "Error a deshabilitar la autenticaciÃŗn de contraseÃąa. \nAsegÃērese de que un proveedor social o OIDC estÊ vinculado a su cuenta.", "password_enabled": "AutenticaciÃŗn de contraseÃąa habilitada", - "password_enabled_error": "Error al habilitar la autenticaciÃŗn de contraseÃąa." + "password_enabled_error": "Error al habilitar la autenticaciÃŗn de contraseÃąa.", + "admin": "AdministraciÃŗn", + "advanced": "Avanzado", + "confirm_new_password_desc": "Confirmar nueva contraseÃąa", + "disabled": "Desactivado", + "emails": "Correos electrÃŗnicos", + "enabled": "Activado", + "enter_current_password": "Ingrese la contraseÃąa actual", + "enter_first_name": "Ingrese su primer nombre", + "enter_last_name": "Ingrese su apellido", + "enter_new_password": "Ingrese una nueva contraseÃąa", + "enter_username": "Ingrese su nombre de usuario", + "integrations": "IntegraciÃŗn", + "mfa_desc": "Agregue una capa adicional de seguridad a su cuenta", + "mfa_is_enabled": "MFA estÃĄ habilitado", + "pass_change_desc": "Actualice la contraseÃąa de su cuenta para una mejor seguridad", + "password_auth": "AutenticaciÃŗn de contraseÃąa", + "password_login_disabled": "Inicio de sesiÃŗn de contraseÃąa Deshabilitado", + "password_login_enabled": "Inicio de sesiÃŗn de contraseÃąa habilitado", + "profile_info": "InformaciÃŗn de perfil", + "profile_info_desc": "Actualice sus datos personales y su foto de perfil", + "public_profile_desc": "Haga que su perfil sea visible para otros usuarios", + "security": "Seguridad", + "settings_menu": "MenÃē ConfiguraciÃŗn", + "social_auth": "AutenticaciÃŗn social", + "social_auth_desc_1": "Administrar opciones de inicio de sesiÃŗn sociales y configuraciÃŗn de contraseÃąa", + "add_new_email": "Agregar nuevo correo electrÃŗnico", + "add_new_email_address": "Agregar nueva direcciÃŗn de correo electrÃŗnico", + "email_management": "AdministraciÃŗn de correo electrÃŗnico", + "email_management_desc": "Administre sus direcciones de correo electrÃŗnico y estado de verificaciÃŗn", + "enter_new_email": "Ingrese una nueva direcciÃŗn de correo electrÃŗnico", + "access_restricted": "Acceso restringido", + "access_restricted_desc": "Las características de YAdMinistratrating solo estÃĄn disponibles para los miembros del personal.", + "admin_panel_desc": "Acceder a la interfaz de administraciÃŗn completa", + "administration": "AdministraciÃŗn", + "administration_desc": "Herramientas y configuraciones administrativas", + "advanced_settings": "ConfiguraciÃŗn avanzada", + "advanced_settings_desc": "Herramientas avanzadas de configuraciÃŗn y desarrollo", + "app_version": "VersiÃŗn de la aplicaciÃŗn", + "connected": "Conectado", + "debug_information": "InformaciÃŗn de depuraciÃŗn", + "disconnected": "Desconectado", + "integrations_desc": "Conecte los servicios externos para mejorar su experiencia", + "quick_actions": "Acciones rÃĄpidas", + "region_updates": "Actualizaciones de regiÃŗn", + "region_updates_desc": "Actualizar regiones y ciudades visitadas", + "regular_user": "Usuario regular", + "social_auth_setup": "ConfiguraciÃŗn de autenticaciÃŗn social", + "staff_status": "Estado del personal", + "staff_user": "Usuario de personal", + "license": "Licencia", + "all_rights_reserved": "Reservados todos los derechos." }, "checklist": { "add_item": "Agregar artículo", @@ -553,7 +630,9 @@ "manage_categories": "Administrar categorías", "no_categories_found": "No se encontraron categorías.", "select_category": "Seleccionar categoría", - "update_after_refresh": "Las tarjetas de aventuras se actualizarÃĄn una vez que actualices la pÃĄgina." + "update_after_refresh": "Las tarjetas de aventuras se actualizarÃĄn una vez que actualices la pÃĄgina.", + "add_category": "Agregar categoría", + "add_new_category": "Agregar nueva categoría" }, "dashboard": { "add_some": "ÂŋPor quÊ no empezar a planificar tu prÃŗxima aventura? \nPuedes agregar una nueva aventura haciendo clic en el botÃŗn de abajo.", @@ -588,14 +667,31 @@ "update_integration": "IntegraciÃŗn de actualizaciÃŗn", "immich_integration": "IntegraciÃŗn Immich", "documentation": "DocumentaciÃŗn de integraciÃŗn de Immich", - "localhost_note": "Nota: lo mÃĄs probable es que localhost no funcione a menos que haya configurado las redes acoplables en consecuencia. \nSe recomienda utilizar la direcciÃŗn IP del servidor o el nombre de dominio." + "localhost_note": "Nota: lo mÃĄs probable es que localhost no funcione a menos que haya configurado las redes acoplables en consecuencia. \nSe recomienda utilizar la direcciÃŗn IP del servidor o el nombre de dominio.", + "api_key_placeholder": "Ingrese su clave de API IMICH", + "enable_integration": "Habilitar la integraciÃŗn", + "immich_integration_desc": "Conecte su servidor de administraciÃŗn de fotos de Immich", + "need_help": "ÂŋNecesita ayuda para configurar esto? \nMira el", + "connection_error": "Error conectarse al servidor Immich", + "copy_locally": "Copiar imÃĄgenes localmente", + "copy_locally_desc": "Copie imÃĄgenes al servidor para obtener acceso fuera de línea. \nUtiliza mÃĄs espacio en disco.", + "error_saving_image": "Imagen de ahorro de errores", + "integration_already_exists": "Ya existe una integraciÃŗn IMICH. \nSolo puedes tener una integraciÃŗn a la vez.", + "integration_not_found": "IntegraciÃŗn IMACH no encontrada. \nPor favor cree una nueva integraciÃŗn.", + "network_error": "Error de red mientras se conecta al servidor Immich. \nVerifique su conexiÃŗn y vuelva a intentarlo.", + "validation_error": "Se produjo un error al validar la integraciÃŗn de Immich. \nVerifique la URL y la tecla API de su servidor." }, "recomendations": { "address": "DIRECCIÓN", "contact": "Contacto", "phone": "TelÊfono", "recommendation": "RecomendaciÃŗn", - "website": "Sitio web" + "website": "Sitio web", + "recommendations": "Recomendaciones", + "adventure_recommendations": "Recomendaciones de aventura", + "food": "Alimento", + "miles": "Millas", + "tourism": "Turismo" }, "lodging": { "apartment": "Departamento", @@ -626,5 +722,8 @@ "villa": "Villa", "edit_lodging": "Editar alojamiento", "current_timezone": "Zona horaria" + }, + "google_maps": { + "google_maps_integration_desc": "Conecte su cuenta de Google Maps para obtener resultados y recomendaciones de bÃēsqueda de ubicaciÃŗn de alta calidad." } } diff --git a/frontend/src/locales/fr.json b/frontend/src/locales/fr.json index 3e6ec37d..d2a1aa59 100644 --- a/frontend/src/locales/fr.json +++ b/frontend/src/locales/fr.json @@ -8,7 +8,8 @@ "nominatim_2": "Leurs donnÊes sont sous licence ODbL.", "oss_attributions": "Attributions Open Source", "other_attributions": "Des attributions supplÊmentaires peuvent ÃĒtre trouvÊes dans le fichier README.", - "source_code": "Code source" + "source_code": "Code source", + "generic_attributions": "Connectez-vous à AdventureLog pour afficher les attributions pour les intÊgrations et services activÊs." }, "adventures": { "activities": { @@ -50,19 +51,19 @@ "adventure_delete_success": "Aventure supprimÊe avec succès !", "adventure_details": "DÊtails de l'aventure", "adventure_type": "Type d'aventure", - "archive": "Archive", - "archived": "ArchivÊ", + "archive": "Archiver", + "archived": "ArchivÊe", "archived_collection_message": "Collection archivÊe avec succès !", "archived_collections": "Collections archivÊes", "ascending": "Ascendant", "cancel": "Annuler", - "category_filter": "Filtre de catÊgorie", - "clear": "Clair", + "category_filter": "Filtres de catÊgorie", + "clear": "RÊinitialiser", "close_filters": "Fermer les filtres", "collection": "Collection", - "collection_adventures": "Inclure les aventures de collection", + "collection_adventures": "Inclure les aventures liÊes à une collection", "count_txt": "rÊsultats correspondant à votre recherche", - "create_new": "CrÊer un nouveau...", + "create_new": "CrÊer une nouvelle aventure...", "date": "Date", "dates": "Dates", "delete_adventure": "Supprimer l'aventure", @@ -72,7 +73,7 @@ "descending": "Descendant", "duration": "DurÊe", "edit_collection": "Modifier la collection", - "filter": "Filtre", + "filter": "Filtrer", "homepage": "Page d'accueil", "image_removed_error": "Erreur lors de la suppression de l'image", "image_removed_success": "Image supprimÊe avec succès !", @@ -84,12 +85,12 @@ "name": "Nom", "no_image_url": "Aucune image trouvÊe à cette URL.", "not_found": "Aventure introuvable", - "not_found_desc": "L'aventure que vous cherchiez est introuvable. \nVeuillez essayer une autre aventure ou revenez plus tard.", + "not_found_desc": "L'aventure que vous cherchez est introuvable. \nVeuillez essayer une autre aventure ou revenez plus tard.", "open_filters": "Ouvrir les filtres", - "order_by": "Commander par", - "order_direction": "Direction de la commande", - "planned": "PrÊvu", - "private": "PrivÊ", + "order_by": "Trier par", + "order_direction": "Direction du tri", + "planned": "PrÊvue", + "private": "PrivÊe", "public": "Publique", "rating": "Notation", "share": "Partager", @@ -98,12 +99,12 @@ "start_before_end_error": "La date de dÊbut doit ÃĒtre antÊrieure à la date de fin", "unarchive": "DÊsarchiver", "unarchived_collection_message": "Collection dÊsarchivÊe avec succès !", - "updated": "Mis à jour", + "updated": "Mise à jour", "visit": "Visite", - "visited": "VisitÊ", + "visited": "VisitÊe", "visits": "Visites", "wiki_image_error": "Erreur lors de la rÊcupÊration de l'image depuis WikipÊdia", - "actions": "Actes", + "actions": "Actions", "activity": "ActivitÊ", "activity_types": "Types d'activitÊs", "add": "Ajouter", @@ -117,7 +118,7 @@ "category": "CatÊgorie", "clear_map": "Effacer la carte", "copy_link": "Copier le lien", - "date_constrain": "Contraindre aux dates de collecte", + "date_constrain": "Limiter aux dates de la collection", "description": "Description", "end_date": "Date de fin", "fetch_image": "RÊcupÊrer une image", @@ -125,7 +126,7 @@ "image": "Image", "image_fetch_failed": "Échec de la rÊcupÊration de l'image", "link": "Lien", - "location": "Emplacement", + "location": "Lieu", "location_information": "Informations de localisation", "my_images": "Mes images", "my_visits": "Mes visites", @@ -138,7 +139,7 @@ "public_adventure": "Aventure publique", "remove": "Retirer", "save_next": "Sauvegarder", - "search_for_location": "Rechercher un emplacement", + "search_for_location": "Rechercher un lieu", "search_results": "RÊsultats de la recherche", "see_adventures": "Voir les aventures", "select_adventure_category": "SÊlectionnez la catÊgorie d'aventure", @@ -148,73 +149,73 @@ "upload_images_here": "TÊlÊchargez des images ici", "url": "URL", "warning": "Avertissement", - "wiki_desc": "Extrait un extrait de l'article WikipÊdia correspondant au nom de l'aventure.", + "wiki_desc": "Obtient un extrait de l'article WikipÊdia correspondant au nom de l'aventure.", "wikipedia": "WikipÊdia", - "adventure_not_found": "Il n'y a aucune aventure à afficher. \nAjoutez-en en utilisant le bouton plus en bas à droite ou essayez de changer les filtres !", + "adventure_not_found": "Il n'y a aucune aventure à afficher. \nAjoutez-en en utilisant le bouton '+' en bas à droite ou essayez de changer les filtres !", "all": "Tous", "error_updating_regions": "Erreur lors de la mise à jour des rÊgions", "mark_region_as_visited": "Marquer la rÊgion {region}, {country} comme visitÊe ?", - "mark_visited": "Mark a visitÊ", + "mark_visited": "Marquer comme visitÊ", "my_adventures": "Mes aventures", "no_adventures_found": "Aucune aventure trouvÊe", "no_collections_found": "Aucune collection trouvÊe pour ajouter cette aventure.", "no_linkable_adventures": "Aucune aventure trouvÊe pouvant ÃĒtre liÊe à cette collection.", - "not_visited": "Non visitÊ", + "not_visited": "Non visitÊe", "regions_updated": "rÊgions mises à jour", "update_visited_regions": "Mettre à jour les rÊgions visitÊes", "update_visited_regions_disclaimer": "Cela peut prendre un certain temps en fonction du nombre d'aventures que vous avez visitÊes.", "visited_region_check": "VÊrification de la rÊgion visitÊe", - "visited_region_check_desc": "En sÊlectionnant cette option, le serveur vÊrifiera toutes vos aventures visitÊes et marquera les rÊgions dans lesquelles elles se trouvent comme visitÊes lors des voyages dans le monde.", + "visited_region_check_desc": "En sÊlectionnant cette option, le serveur vÊrifiera toutes vos aventures visitÊes et marquera les rÊgions correspondantes comme visitÊes dans la section 'Voyage dans le monde'.", "add_new": "Ajouter un nouveau...", "checklists": "Listes de contrôle", "collection_archived": "Cette collection a ÊtÊ archivÊe.", "collection_completed": "Vous avez terminÊ cette collection !", - "collection_stats": "Statistiques de collecte", + "collection_stats": "Statistiques de la collection", "days": "jours", - "itineary_by_date": "ItinÊraire par date", + "itineary_by_date": "ItinÊraire triÊ par date", "keep_exploring": "Continuez à explorer !", - "link_new": "Lien Nouveau...", + "link_new": "Ajouter un lien vers...", "linked_adventures": "Aventures liÊes", - "links": "Links", + "links": "Liens", "no_end_date": "Veuillez saisir une date de fin", "note": "Note", - "notes": "Remarques", + "notes": "Notes", "nothing_planned": "Rien de prÊvu pour cette journÊe. \nBon voyage !", - "transportation": "Transport", - "transportations": "Transports", - "visit_link": "Visitez le lien", + "transportation": "DÊplacement", + "transportations": "DÊplacements", + "visit_link": "Visiter le lien", "checklist": "Liste de contrôle", "day": "Jour", "add_a_tag": "Ajouter une balise", "tags": "Balises", - "set_to_pin": "DÊfinir sur Épingler", + "set_to_pin": "Épingler", "category_fetch_error": "Erreur lors de la rÊcupÊration des catÊgories", "copied_to_clipboard": "CopiÊ dans le presse-papier !", "copy_failed": "Échec de la copie", - "adventure_calendar": "Calendrier d'aventure", + "adventure_calendar": "Calendrier des aventures", "emoji_picker": "SÊlecteur d'Êmoticônes", "hide": "Cacher", "show": "Montrer", "download_calendar": "TÊlÊcharger le calendrier", - "md_instructions": "Écrivez votre dÊmarque ici...", + "md_instructions": "Écrivez ici au format Markdown...", "preview": "Aperçu", "checklist_delete_confirm": "Êtes-vous sÃģr de vouloir supprimer cette liste de contrôle ? \nCette action ne peut pas ÃĒtre annulÊe.", - "clear_location": "Effacer l'emplacement", - "date_information": "Informations sur les dates", + "clear_location": "Effacer le lieu", + "date_information": "Dates", "delete_checklist": "Supprimer la liste de contrôle", "delete_note": "Supprimer la note", - "delete_transportation": "Supprimer le transport", - "end": "Fin", - "ending_airport": "AÊroport de fin", + "delete_transportation": "Supprimer le dÊplacement", + "end": "ArrivÊe", + "ending_airport": "AÊroport d'arrivÊe", "flight_information": "Informations sur le vol", "from": "Depuis", - "no_location_found": "Aucun emplacement trouvÊ", + "no_location_found": "Aucun lieu trouvÊ", "note_delete_confirm": "Êtes-vous sÃģr de vouloir supprimer cette note ? \nCette action ne peut pas ÃĒtre annulÊe.", "out_of_range": "Pas dans la plage de dates de l'itinÊraire", "show_region_labels": "Afficher les Êtiquettes de rÊgion", - "start": "Commencer", + "start": "DÊpart", "starting_airport": "AÊroport de dÊpart", - "to": "À", + "to": "Vers", "transportation_delete_confirm": "Etes-vous sÃģr de vouloir supprimer ce transport ? \nCette action ne peut pas ÃĒtre annulÊe.", "show_map": "Afficher la carte", "will_be_marked": "sera marquÊ comme visitÊ une fois l’aventure sauvegardÊe.", @@ -232,22 +233,47 @@ "attachments": "Pièces jointes", "gpx_tip": "TÊlÊchargez des fichiers GPX en pièces jointes pour les afficher sur la carte !", "images": "Images", - "primary": "Primaire", + "primary": "Principale", "upload": "TÊlÊcharger", "view_attachment": "Voir la pièce jointe", "of": "de", "city": "Ville", "delete_lodging": "Supprimer l'hÊbergement", "display_name": "Nom d'affichage", - "location_details": "DÊtails de l'emplacement", + "location_details": "DÊtails du lieu", "lodging": "HÊbergement", - "lodging_delete_confirm": "Êtes-vous sÃģr de vouloir supprimer cet emplacement d'hÊbergement? \nCette action ne peut pas ÃĒtre annulÊe.", + "lodging_delete_confirm": "Êtes-vous sÃģr de vouloir supprimer cet hÊbergement? \nCette action ne peut pas ÃĒtre annulÊe.", "lodging_information": "Informations sur l'hÊbergement", "price": "Prix", "region": "RÊgion", "reservation_number": "NumÊro de rÊservation", "welcome_map_info": "Aventures publiques sur ce serveur", - "open_in_maps": "Ouvert dans les cartes" + "open_in_maps": "Ouvert dans les cartes", + "all_day": "JournÊe complète", + "collection_no_start_end_date": "L'ajout d'une date de dÊbut et de fin à la collection dÊbloquera les fonctionnalitÊs de planification de l'itinÊraire dans la page de collection.", + "date_itinerary": "ItinÊraire triÊ par date", + "no_ordered_items": "Ajoutez des ÊlÊments avec des dates de visite à la collection pour les voir ici.", + "ordered_itinerary": "ItinÊraire triÊ par activitÊ", + "additional_info": "Informations ComplÊmentaires", + "invalid_date_range": "Plage de dates non valide", + "sunrise_sunset": "Lever du soleil", + "timezone": "Fuseau horaire", + "no_visits": "Pas de visites", + "arrival_timezone": "Fuseau horaire d'arrivÊe", + "departure_timezone": "Fuseau horaire de dÊpart", + "arrival_date": "Date d'arrivÊe", + "departure_date": "Date de dÊpart", + "coordinates": "CoordonnÊes", + "copy_coordinates": "CoordonnÊes de copie", + "sun_times": "Temps du soleil", + "sunrise": "Lever du soleil", + "sunset": "Coucher de soleil", + "timed": "ChronomÊtrÊ", + "distance": "Distance", + "all_linked_items": "Tous les ÊlÊments liÊs", + "itinerary": "ItinÊraire", + "joined": "Joint", + "view_profile": "Afficher le profil" }, "home": { "desc_1": "DÊcouvrez, planifiez et explorez en toute simplicitÊ", @@ -267,7 +293,7 @@ "about": "À propos de AdventureLog", "adventures": "Aventures", "collections": "Collections", - "discord": "Discorde", + "discord": "Discord", "documentation": "Documentation", "greeting": "Salut", "logout": "DÊconnexion", @@ -280,12 +306,12 @@ "theme_selection": "SÊlection de thèmes", "themes": { "forest": "ForÃĒt", - "light": "Lumière", + "light": "Clair", "night": "Nuit", "aqua": "Aqua", "dark": "Sombre", - "aestheticDark": "EsthÊtique sombre", - "aestheticLight": "Lumière esthÊtique", + "aestheticDark": "EsthÊtique (sombre)", + "aestheticLight": "EsthÊtique (clair)", "northernLights": "Aurores borÊales" }, "users": "Utilisateurs", @@ -298,7 +324,7 @@ "admin_panel": "Panneau d'administration" }, "auth": { - "confirm_password": "Confirmez le mot de passe", + "confirm_password": "Confirmer le mot de passe", "email": "E-mail", "first_name": "PrÊnom", "forgot_password": "Mot de passe oubliÊ ?", @@ -312,18 +338,18 @@ "profile_picture": "Photo de profil", "public_profile": "Profil public", "public_tooltip": "Avec un profil public, les utilisateurs peuvent partager des collections avec vous et afficher votre profil sur la page des utilisateurs.", - "email_required": "L'e-mail est requis", + "email_required": "Le courriel est requis", "both_passwords_required": "Les deux mots de passe sont requis", "new_password": "Nouveau mot de passe", "reset_failed": "Échec de la rÊinitialisation du mot de passe", "or_3rd_party": "Ou connectez-vous avec un service tiers", "no_public_adventures": "Aucune aventure publique trouvÊe", "no_public_collections": "Aucune collection publique trouvÊe", - "user_adventures": "Aventures utilisateur", - "user_collections": "Collections d'utilisateurs" + "user_adventures": "Aventures de l'utilisateur", + "user_collections": "Collections de l'utilisateur" }, "users": { - "no_users_found": "Aucun utilisateur trouvÊ avec des profils publics." + "no_users_found": "Aucun utilisateur trouvÊ avec un profil public." }, "worldtravel": { "all": "Tous", @@ -352,27 +378,27 @@ "settings": { "account_settings": "Paramètres du compte utilisateur", "confirm_new_password": "Confirmer le nouveau mot de passe", - "current_email": "Courriel actuel", - "email_change": "Changer l'e-mail", - "new_email": "Nouvel e-mail", + "current_email": "Adresse de courriel actuel", + "email_change": "Changer l'adresse de courriel", + "new_email": "Nouvelle adresse de courriel", "new_password": "Nouveau mot de passe", - "no_email_set": "Aucune adresse e-mail dÊfinie", + "no_email_set": "Aucune adresse de courriel dÊfinie", "password_change": "Changer le mot de passe", - "settings_page": "Page Paramètres", + "settings_page": "Page de paramÊtrage", "update": "Mise à jour", "update_error": "Erreur lors de la mise à jour des paramètres", "update_success": "Paramètres mis à jour avec succès !", "change_password": "Changer le mot de passe", "invalid_token": "Le jeton n'est pas valide ou a expirÊ", "login_redir": "Vous serez alors redirigÊ vers la page de connexion.", - "missing_email": "Veuillez entrer une adresse e-mail", + "missing_email": "Veuillez entrer une adresse de courriel", "password_does_not_match": "Les mots de passe ne correspondent pas", "password_is_required": "Le mot de passe est requis", - "possible_reset": "Si l'adresse e-mail que vous avez fournie est associÊe à un compte, vous recevrez un e-mail avec des instructions pour rÊinitialiser votre mot de passe !", + "possible_reset": "Si l'adresse de courriel que vous avez fournie est associÊe à un compte, vous recevrez un courriel avec des instructions pour rÊinitialiser votre mot de passe !", "reset_password": "RÊinitialiser le mot de passe", - "submit": "Soumettre", - "token_required": "Le jeton et l'UID sont requis pour la rÊinitialisation du mot de passe.", - "about_this_background": "À propos de ce contexte", + "submit": "Valider", + "token_required": "Le jeton et l'identifiant utilisateur sont requis pour la rÊinitialisation du mot de passe.", + "about_this_background": "À propos de cette photo", "join_discord": "Rejoignez le Discord", "join_discord_desc": "pour partager vos propres photos. \nPostez-les dans le", "photo_by": "Photo par", @@ -380,77 +406,128 @@ "current_password": "Mot de passe actuel", "password_change_lopout_warning": "Vous serez dÊconnectÊ après avoir modifiÊ votre mot de passe.", "authenticator_code": "Code d'authentification", - "copy": "Copie", - "disable_mfa": "DÊsactiver MFA", - "email_added": "E-mail ajoutÊ avec succès !", - "email_added_error": "Erreur lors de l'ajout de l'e-mail", - "email_removed": "E-mail supprimÊ avec succès !", - "email_removed_error": "Erreur lors de la suppression de l'e-mail", - "email_set_primary": "E-mail dÊfini comme principal avec succès !", - "email_set_primary_error": "Erreur lors de la dÊfinition de l'adresse e-mail comme adresse principale", - "email_verified": "E-mail vÊrifiÊ avec succès !", - "email_verified_erorr_desc": "Votre email n'a pas pu ÃĒtre vÊrifiÊ. \nVeuillez rÊessayer.", - "email_verified_error": "Erreur lors de la vÊrification de l'e-mail", - "email_verified_success": "Votre email a ÊtÊ vÊrifiÊ. \nVous pouvez maintenant vous connecter.", - "enable_mfa": "Activer l'authentification multifacteur", + "copy": "Copier", + "disable_mfa": "DÊsactiver l'authentification multi-facteurs", + "email_added": "Adresse de courriel ajoutÊe avec succès !", + "email_added_error": "Erreur lors de l'ajout de l'adresse de courriel", + "email_removed": "Adresse de courriel supprimÊe avec succès !", + "email_removed_error": "Erreur lors de la suppression de l'adresse de courriel", + "email_set_primary": "Adresse de courriel principale dÊfinie avec succès !", + "email_set_primary_error": "Erreur lors de la dÊfinition de l'adresse de courriel principale", + "email_verified": "Adresse de courriel vÊrifiÊe avec succès !", + "email_verified_erorr_desc": "Votre adresse de courriel n'a pas pu ÃĒtre vÊrifiÊe. \nVeuillez rÊessayer.", + "email_verified_error": "Erreur lors de la vÊrification de l'adresse de courriel", + "email_verified_success": "Votre adresse de courriel a ÊtÊ vÊrifiÊe. \nVous pouvez maintenant vous connecter.", + "enable_mfa": "Activer l'authentification multi-facteurs", "error_change_password": "Erreur lors du changement de mot de passe. \nVeuillez vÊrifier votre mot de passe actuel et rÊessayer.", "generic_error": "Une erreur s'est produite lors du traitement de votre demande.", - "invalid_code": "Code MFA invalide", + "invalid_code": "Code d'authentification multi-facteurs invalide", "invalid_credentials": "Nom d'utilisateur ou mot de passe invalide", - "make_primary": "Rendre primaire", - "mfa_disabled": "Authentification multifacteur dÊsactivÊe avec succès !", - "mfa_enabled": "Authentification multifacteur activÊe avec succès !", - "mfa_not_enabled": "MFA n'est pas activÊ", - "mfa_page_title": "Authentification multifacteur", - "mfa_required": "Une authentification multifacteur est requise", - "no_emai_set": "Aucune adresse e-mail dÊfinie", - "not_verified": "Non vÊrifiÊ", - "primary": "Primaire", + "make_primary": "DÊfinir comme adresse de courriel principale", + "mfa_disabled": "Authentification multi-facteurs dÊsactivÊe avec succès !", + "mfa_enabled": "Authentification multi-facteurs activÊe avec succès !", + "mfa_not_enabled": "L'authentification multi-facteurs n'est pas activÊe", + "mfa_page_title": "Authentification multi-facteurs", + "mfa_required": "Une authentification multi-facteurs est requise", + "no_emai_set": "Aucune adresse de courriel dÊfinie", + "not_verified": "Non vÊrifiÊe", + "primary": "Principale", "recovery_codes": "Codes de rÊcupÊration", - "recovery_codes_desc": "Ce sont vos codes de rÊcupÊration. \nGardez-les en sÊcuritÊ. \nVous ne pourrez plus les revoir.", + "recovery_codes_desc": "Ce sont vos codes de rÊcupÊration. \nGardez-les en sÊcuritÊ. \nIls ne pourront plus vous ÃĒtre affichÊs.", "reset_session_error": "Veuillez vous dÊconnecter, puis vous reconnecter pour actualiser votre session et rÊessayer.", - "verified": "VÊrifiÊ", + "verified": "VÊrifiÊe", "verify": "VÊrifier", - "verify_email_error": "Erreur lors de la vÊrification de l'e-mail. \nRÊessayez dans quelques minutes.", - "verify_email_success": "VÊrification par e-mail envoyÊe avec succès !", - "add_email_blocked": "Vous ne pouvez pas ajouter une adresse e-mail à un compte protÊgÊ par une authentification à deux facteurs.", + "verify_email_error": "Erreur lors de la vÊrification de l'adresse de courriel. \nRÊessayez dans quelques minutes.", + "verify_email_success": "VÊrification par courriel envoyÊe avec succès !", + "add_email_blocked": "Vous ne pouvez pas ajouter une adresse de courriel à un compte protÊgÊ par une authentification à deux facteurs.", "required": "Ce champ est obligatoire", "csrf_failed": "Échec de la rÊcupÊration du jeton CSRF", - "duplicate_email": "Cette adresse e-mail est dÊjà utilisÊe.", - "email_taken": "Cette adresse e-mail est dÊjà utilisÊe.", + "duplicate_email": "Cette adresse de courriel est dÊjà utilisÊe.", + "email_taken": "Cette adresse de courriel est dÊjà utilisÊe.", "username_taken": "Ce nom d'utilisateur est dÊjà utilisÊ.", "administration_settings": "Paramètres d'administration", "documentation_link": "Lien vers la documentation", "launch_account_connections": "Lancer les connexions au compte", "launch_administration_panel": "Lancer le panneau d'administration", - "no_verified_email_warning": "Vous devez disposer d'une adresse e-mail vÊrifiÊe pour activer l'authentification à deux facteurs.", - "social_auth_desc": "Activez ou dÊsactivez les fournisseurs d'authentification sociale et OIDC pour votre compte. \nCes connexions vous permettent de vous connecter avec des fournisseurs d'identitÊ d'authentification auto-hÊbergÊs comme Authentik ou des fournisseurs tiers comme GitHub.", + "no_verified_email_warning": "Vous devez disposer d'une adresse de courriel vÊrifiÊe pour activer l'authentification multi-facteurs.", + "social_auth_desc": "Activez ou dÊsactivez les fournisseurs d'authentification sociale et OIDC pour votre compte. \nCes connexions vous permettent de vous connecter avec des fournisseurs d'identitÊ auto-hÊbergÊs comme Authentik ou des fournisseurs tiers comme GitHub.", "social_auth_desc_2": "Ces paramètres sont gÊrÊs sur le serveur AdventureLog et doivent ÃĒtre activÊs manuellement par l'administrateur.", "social_oidc_auth": "Authentification sociale et OIDC", - "add_email": "Ajouter un e-mail", + "add_email": "Ajouter une adresse de courriel", "password_too_short": "Le mot de passe doit contenir au moins 6 caractères", "disable_password": "DÊsactiver le mot de passe", - "password_disable": "DÊsactiver l'authentification du mot de passe", - "password_disable_desc": "La dÊsactivation de l'authentification du mot de passe vous empÃĒchera de vous connecter avec un mot de passe. \nVous devrez utiliser un fournisseur social ou OIDC pour vous connecter. Si votre fournisseur social est non liÊ, l'authentification du mot de passe sera automatiquement rÊactivÊ mÃĒme si ce paramètre est dÊsactivÊ.", - "password_disable_warning": "Actuellement, l'authentification du mot de passe est dÊsactivÊe. \nLa connexion via un fournisseur social ou OIDC est requise.", - "password_disabled": "Authentification du mot de passe dÊsactivÊ", - "password_disabled_error": "Erreur de dÊsactivation de l'authentification du mot de passe. \nAssurez-vous qu'un fournisseur social ou OIDC est liÊ à votre compte.", - "password_enabled": "Authentification du mot de passe activÊ", - "password_enabled_error": "Erreur permettant l'authentification du mot de passe." + "password_disable": "DÊsactiver l'authentification par mot de passe", + "password_disable_desc": "La dÊsactivation de l'authentification par mot de passe vous empÃĒchera de vous connecter avec un mot de passe. \nVous devrez utiliser un fournisseur social ou OIDC pour vous connecter. Si votre fournisseur social est non liÊ, l'authentification par mot de passe sera automatiquement rÊactivÊe mÃĒme si ce paramètre est dÊsactivÊ.", + "password_disable_warning": "Actuellement, l'authentification par mot de passe est dÊsactivÊe. \nLa connexion via un fournisseur social ou OIDC est requise.", + "password_disabled": "Authentification par mot de passe dÊsactivÊe", + "password_disabled_error": "Erreur de dÊsactivation de l'authentification par mot de passe. \nAssurez-vous qu'un fournisseur social ou OIDC est liÊ à votre compte.", + "password_enabled": "Authentification par mot de passe activÊe", + "password_enabled_error": "Erreur permettant l'authentification par mot de passe.", + "admin_panel_desc": "AccÊder à l'interface d'administration complète", + "administration": "Administration", + "advanced_settings": "Paramètres avancÊs", + "app_version": "Version de l'application", + "confirm_new_password_desc": "Confirmer un nouveau mot de passe", + "connected": "ConnectÊ", + "email_management_desc": "GÊrez vos adresses e-mail et votre statut de vÊrification", + "emails": "E-mails", + "enabled": "ActivÊ", + "enter_current_password": "Entrez le mot de passe actuel", + "enter_first_name": "Entrez votre prÊnom", + "enter_new_email": "Entrez la nouvelle adresse e-mail", + "enter_new_password": "Entrez un nouveau mot de passe", + "enter_username": "Entrez votre nom d'utilisateur", + "integrations": "IntÊgrations", + "integrations_desc": "Connectez les services externes pour amÊliorer votre expÊrience", + "license": "Licence", + "mfa_desc": "Ajoutez une couche supplÊmentaire de sÊcuritÊ à votre compte", + "mfa_is_enabled": "MFA est activÊ", + "pass_change_desc": "Mettez à jour le mot de passe de votre compte pour une meilleure sÊcuritÊ", + "password_auth": "Authentification du mot de passe", + "password_login_disabled": "Login de mot de passe dÊsactivÊ", + "password_login_enabled": "Connexion du mot de passe activÊ", + "profile_info": "Informations sur le profil", + "profile_info_desc": "Mettez à jour vos coordonnÊes personnelles et votre photo de profil", + "public_profile_desc": "Rendre votre profil visible pour les autres utilisateurs", + "quick_actions": "Actions rapides", + "region_updates": "Mises à jour de la rÊgion", + "region_updates_desc": "Mettre à jour les rÊgions et les villes visitÊes", + "regular_user": "Utilisateur rÊgulier", + "security": "SÊcuritÊ", + "settings_menu": "Menu des paramètres", + "social_auth": "Authentification sociale", + "social_auth_desc_1": "GÊrer les options de connexion sociales et les paramètres de mot de passe", + "social_auth_setup": "Configuration d'authentification sociale", + "staff_status": "Statut du personnel", + "staff_user": "Utilisateur du personnel", + "access_restricted": "Accès restreint", + "access_restricted_desc": "Les fonctionnalitÊs yadministratives ne sont disponibles que pour les membres du personnel.", + "add_new_email": "Ajouter un nouvel e-mail", + "add_new_email_address": "Ajouter une nouvelle adresse e-mail", + "admin": "Administrer", + "administration_desc": "Outils et paramètres administratifs", + "advanced": "AvancÊ", + "advanced_settings_desc": "Outils avancÊs de configuration et de dÊveloppement", + "all_rights_reserved": "Tous droits rÊservÊs.", + "debug_information": "DÊbogage des informations", + "disabled": "DÊsactivÊ", + "disconnected": "DÊconnectÊ", + "email_management": "Gestion des e-mails", + "enter_last_name": "Entrez votre nom de famille" }, "checklist": { - "add_item": "Ajouter un article", + "add_item": "Ajouter un ÊlÊment", "checklist_delete_error": "Erreur lors de la suppression de la liste de contrôle", "checklist_deleted": "Liste de contrôle supprimÊe avec succès !", "checklist_editor": "Éditeur de liste de contrôle", "checklist_public": "Cette liste de contrôle est publique car elle fait partie d’une collection publique.", - "editing_checklist": "Liste de contrôle d'Êdition", + "editing_checklist": "Édition de la liste de contrôle", "failed_to_save": "Échec de l'enregistrement de la liste de contrôle", - "item": "Article", - "item_already_exists": "L'article existe dÊjà", + "item": "ÉlÊment", + "item_already_exists": "L'ÊlÊment existe dÊjà", "item_cannot_be_empty": "L'ÊlÊment ne peut pas ÃĒtre vide", - "items": "Articles", - "new_item": "Nouvel article", + "items": "ÉlÊments", + "new_item": "Nouvel ÊlÊment", "save": "Sauvegarder", "checklist_viewer": "Visionneuse de liste de contrôle", "new_checklist": "Nouvelle liste de contrôle" @@ -468,7 +545,7 @@ "notes": { "add_a_link": "Ajouter un lien", "content": "Contenu", - "editing_note": "Note d'Êdition", + "editing_note": "Modification de la note", "failed_to_save": "Échec de l'enregistrement de la note", "note_delete_error": "Erreur lors de la suppression de la note", "note_deleted": "Note supprimÊe avec succès !", @@ -480,13 +557,13 @@ "note_viewer": "Visionneuse de notes" }, "transportation": { - "date_time": "Date de dÊbut", + "date_time": "Date de dÊpart", "edit": "Modifier", - "edit_transportation": "Modifier le transport", - "end_date_time": "Date de fin", - "error_editing_transportation": "Erreur lors de la modification du transport", + "edit_transportation": "Modifier le dÊplacement", + "end_date_time": "Date d'arrivÊe", + "error_editing_transportation": "Erreur lors de la modification du dÊplacement", "flight_number": "NumÊro du vol", - "from_location": "De l'emplacement", + "from_location": "Du lieu", "modes": { "bike": "VÊlo", "boat": "Bateau", @@ -494,33 +571,33 @@ "car": "Voiture", "other": "Autre", "plane": "Avion", - "train": "Former", + "train": "Train", "walking": "Marche" }, - "new_transportation": "Nouveau transport", - "provide_start_date": "Veuillez fournir une date de dÊbut", + "new_transportation": "Nouveau dÊplacement", + "provide_start_date": "Veuillez fournir une date de dÊpart", "start": "Commencer", - "to_location": "Vers l'emplacement", + "to_location": "Vers le lieu", "transport_type": "Type de transport", - "type": "Taper", - "date_and_time": "Date", - "transportation_added": "Transport ajoutÊ avec succès !", - "transportation_delete_error": "Erreur lors de la suppression du transport", - "transportation_deleted": "Transport supprimÊ avec succès !", - "transportation_edit_success": "Transport modifiÊ avec succès !", - "ending_airport_desc": "Entrez la fin du code aÊroportuaire (par exemple, laxiste)", - "fetch_location_information": "RÊcupÊrer les informations de localisation", - "starting_airport_desc": "Entrez le code aÊroport de dÊmarrage (par exemple, JFK)" + "type": "Type", + "date_and_time": "Date et heure", + "transportation_added": "DÊplacement ajoutÊ avec succès !", + "transportation_delete_error": "Erreur lors de la suppression du dÊplacement", + "transportation_deleted": "DÊplacement supprimÊ avec succès !", + "transportation_edit_success": "DÊplacement modifiÊ avec succès !", + "ending_airport_desc": "Entrez le code de l'aÊroport de dÊpart (par exemple, CDG)", + "fetch_location_information": "RÊcupÊrer les informations sur les lieux", + "starting_airport_desc": "Entrez le code de l'aÊroport d'arrivÊe (par exemple, ORY)" }, "search": { - "adventurelog_results": "RÊsultats du journal d'aventure", + "adventurelog_results": "RÊsultats dans AdventureLog", "online_results": "RÊsultats en ligne", "public_adventures": "Aventures publiques" }, "map": { "add_adventure": "Ajouter une nouvelle aventure", "add_adventure_at_marker": "Ajouter une nouvelle aventure au marqueur", - "adventure_map": "Carte d'aventure", + "adventure_map": "Carte des aventures", "clear_marker": "Effacer le marqueur", "map_options": "Options de la carte", "show_visited_regions": "Afficher les rÊgions visitÊes", @@ -528,20 +605,20 @@ }, "languages": {}, "share": { - "no_users_shared": "Aucun utilisateur partagÊ avec", - "not_shared_with": "Non partagÊ avec", - "share_desc": "Partagez cette collection avec d'autres utilisateurs.", - "shared": "Commun", - "shared_with": "PartagÊ avec", - "unshared": "Non partagÊ", + "no_users_shared": "Aucun utilisateur", + "not_shared_with": "Pas encore partagÊ avec", + "share_desc": "Partager cette collection avec d'autres utilisateurs.", + "shared": "PartagÊ", + "shared_with": "DÊjà partagÊ avec", + "unshared": "Partage dÊsactivÊ pour", "with": "avec", "go_to_settings": "Allez dans les paramètres", - "no_shared_found": "Aucune collection trouvÊe partagÊe avec vous.", - "set_public": "Afin de permettre aux utilisateurs de partager avec vous, vous devez dÊfinir votre profil comme public." + "no_shared_found": "Aucune collection ne semble encore avoir ÊtÊ partagÊe avec vous.", + "set_public": "Afin de permettre aux utilisateurs de partager avec vous, vous devez rendre votre profil public." }, "profile": { "member_since": "Membre depuis", - "user_stats": "Statistiques des utilisateurs", + "user_stats": "Statistiques de l'utilisateur", "visited_countries": "Pays visitÊs", "visited_regions": "RÊgions visitÊes", "visited_cities": "Villes visitÊes" @@ -553,12 +630,14 @@ "manage_categories": "GÊrer les catÊgories", "no_categories_found": "Aucune catÊgorie trouvÊe.", "select_category": "SÊlectionnez une catÊgorie", - "update_after_refresh": "Les cartes d'aventure seront mises à jour une fois que vous aurez actualisÊ la page." + "update_after_refresh": "Les cartes d'aventure seront mises à jour une fois que vous aurez actualisÊ la page.", + "add_category": "Ajouter une catÊgorie", + "add_new_category": "Ajouter une nouvelle catÊgorie" }, "dashboard": { "add_some": "Pourquoi ne pas commencer à planifier votre prochaine aventure ? \nVous pouvez ajouter une nouvelle aventure en cliquant sur le bouton ci-dessous.", "countries_visited": "Pays visitÊs", - "no_recent_adventures": "Pas d'aventures rÊcentes ?", + "no_recent_adventures": "Pas d'aventure rÊcente ?", "recent_adventures": "Aventures rÊcentes", "total_adventures": "Aventures totales", "total_visited_regions": "Total des rÊgions visitÊes", @@ -566,8 +645,8 @@ "total_visited_cities": "Total des villes visitÊes" }, "immich": { - "api_key": "ClÊ API Immich", - "api_note": "Remarque : il doit s'agir de l'URL du serveur API Immich, elle se termine donc probablement par /api, sauf si vous disposez d'une configuration personnalisÊe.", + "api_key": "ClÊ d'API Immich", + "api_note": "Remarque : il doit s'agir de l'URL de base de l'API Immich, elle se termine donc gÊnÊralement par /api, sauf si vous disposez d'une configuration personnalisÊe.", "disable": "DÊsactiver", "enable_immich": "Activer Immich", "imageid_required": "L'identifiant de l'image est requis", @@ -587,44 +666,64 @@ "server_down": "Le serveur Immich est actuellement en panne ou inaccessible", "server_url": "URL du serveur Immich", "update_integration": "IntÊgration des mises à jour", - "documentation": "Documentation d'intÊgration Immich", - "localhost_note": "Remarque : localhost ne fonctionnera probablement pas à moins que vous n'ayez configurÊ les rÊseaux Docker en consÊquence. \nIl est recommandÊ d'utiliser l'adresse IP du serveur ou le nom de domaine." + "documentation": "Documentation de l'intÊgration Immich", + "localhost_note": "Remarque : localhost ne fonctionnera probablement pas à moins que vous n'ayez configurÊ les rÊseaux Docker en consÊquence. \nIl est recommandÊ d'utiliser l'adresse IP du serveur ou le nom de domaine.", + "api_key_placeholder": "Entrez votre clÊ API Immich", + "enable_integration": "Activer l'intÊgration", + "immich_integration_desc": "Connectez votre serveur de gestion de photos Immich", + "need_help": "Besoin d'aide pour la configurer? \nDÊcouvrez le", + "connection_error": "Erreur de connexion à Immich Server", + "copy_locally": "Copier les images localement", + "copy_locally_desc": "Copiez des images sur le serveur pour un accès hors ligne. \nUtilise plus d'espace disque.", + "error_saving_image": "Image d'enregistrement d'erreur", + "integration_already_exists": "Une intÊgration Immich existe dÊjà. \nVous ne pouvez avoir qu'une seule intÊgration à la fois.", + "integration_not_found": "L'intÊgration d'immich n'est pas trouvÊe. \nVeuillez crÊer une nouvelle intÊgration.", + "network_error": "Erreur rÊseau lors de la connexion au serveur Immich. \nVeuillez vÊrifier votre connexion et rÊessayer.", + "validation_error": "Une erreur s'est produite lors de la validation de l'intÊgration d'Immich. \nVeuillez vÊrifier l'URL et la clÊ API de votre serveur." }, "recomendations": { "address": "Adresse", "contact": "Contact", "phone": "TÊlÊphone", "recommendation": "Recommandation", - "website": "Site web" + "website": "Site web", + "recommendations": "Recommandations", + "adventure_recommendations": "Recommandations d'aventure", + "food": "Nourriture", + "miles": "Kilomètres", + "tourism": "Tourisme" }, "lodging": { "apartment": "Appartement", - "bnb": "Bed and petit-dÊjeuner", - "cabin": "Cabine", + "bnb": "Bed and Breakfast", + "cabin": "ChÃĸlet", "campground": "Camping", "check_in": "Enregistrement", - "check_out": "VÊrifier", - "date_and_time": "Date", + "check_out": "Checkout", + "date_and_time": "Date et heure", "edit": "Modifier", "edit_lodging": "Modifier l'hÊbergement", - "error_editing_lodging": "Édition d'erreurs HÊbergement", + "error_editing_lodging": "Erreur lors de la modification de l'hÊbergement", "hostel": "Auberge", "hotel": "Hôtel", "house": "Maison", - "lodging_added": "L'hÊbergement a ajoutÊ avec succès!", + "lodging_added": "HÊbergement ajoutÊ avec succès!", "lodging_delete_error": "Erreur de suppression de l'hÊbergement", - "lodging_deleted": "L'hÊbergement est supprimÊ avec succès!", - "lodging_edit_success": "L'hÊbergement ÊditÊ avec succès!", + "lodging_deleted": "HÊbergement supprimÊ avec succès!", + "lodging_edit_success": "HÊbergement modifiÊ avec succès!", "lodging_type": "Type d'hÊbergement", "motel": "Motel", - "new_lodging": "Nouveau logement", + "new_lodging": "Nouvel hÊbergement", "other": "Autre", "provide_start_date": "Veuillez fournir une date de dÊbut", "reservation_number": "NumÊro de rÊservation", - "resort": "Station balnÊaire", + "resort": "Complexe touristique", "start": "Commencer", - "type": "Taper", + "type": "Type", "villa": "Villa", "current_timezone": "Fuseau horaire actuel" + }, + "google_maps": { + "google_maps_integration_desc": "Connectez votre compte Google Maps pour obtenir des rÊsultats de recherche et recommandations de recherche de haute qualitÊ." } } diff --git a/frontend/src/locales/it.json b/frontend/src/locales/it.json index a68ce990..7e876c04 100644 --- a/frontend/src/locales/it.json +++ b/frontend/src/locales/it.json @@ -1,22 +1,23 @@ { "about": { "about": "Di", - "close": "Vicino", + "close": "Chiudi", "license": "Concesso in licenza con la licenza GPL-3.0.", "message": "Realizzato con â¤ī¸ negli Stati Uniti.", "nominatim_1": "La ricerca della posizione e la geocodifica sono fornite da", "nominatim_2": "I loro dati sono concessi in licenza con la licenza ODbL.", "oss_attributions": "Attribuzioni Open Source", "other_attributions": "Ulteriori attribuzioni possono essere trovate nel file README.", - "source_code": "Codice sorgente" + "source_code": "Codice sorgente", + "generic_attributions": "Accedi a AdventureLog per visualizzare le attribuzioni per integrazioni e servizi abilitati." }, "adventures": { "activities": { "activity": "Attività 🏄", - "art_museums": "Arte", + "art_museums": "Arte e Musei", "attraction": "Attrazione đŸŽĸ", "culture": "Cultura 🎭", - "dining": "Pranzo đŸŊī¸", + "dining": "Mangiare đŸŊī¸", "event": "Evento 🎉", "festivals": "Festival đŸŽĒ", "fitness": "Forma fisica đŸ‹ī¸", @@ -35,34 +36,34 @@ "water_sports": "Sport acquatici 🚤", "wildlife": "Fauna selvatica đŸĻ’" }, - "add_to_collection": "Aggiungi alla raccolta", + "add_to_collection": "Aggiungi alla collezione", "adventure": "Avventura", "adventure_delete_confirm": "Sei sicuro di voler eliminare questa avventura? \nQuesta azione non puÃ˛ essere annullata.", "adventure_details": "Dettagli dell'avventura", "adventure_type": "Tipo di avventura", "archive": "Archivio", "archived": "Archiviato", - "archived_collection_message": "Raccolta archiviata con successo!", + "archived_collection_message": "Collezione archiviata con successo!", "archived_collections": "Collezioni archiviate", "ascending": "Ascendente", "cancel": "Cancellare", "category_filter": "Filtro categoria", - "clear": "Chiaro", + "clear": "Rimuovere", "close_filters": "Chiudi filtri", "collection": "Collezione", - "collection_link_error": "Errore nel collegamento dell'avventura alla raccolta", - "collection_remove_error": "Errore durante la rimozione dell'avventura dalla raccolta", - "collection_remove_success": "Avventura rimossa con successo dalla raccolta!", + "collection_link_error": "Errore nel collegamento dell'avventura alla collezione", + "collection_remove_error": "Errore durante la rimozione dell'avventura dalla collezione", + "collection_remove_success": "Avventura rimossa con successo dalla collezione!", "count_txt": "risultati corrispondenti alla tua ricerca", "create_new": "Crea nuovo...", "date": "Data", "delete": "Eliminare", - "delete_collection": "Elimina raccolta", - "delete_collection_success": "Raccolta eliminata con successo!", - "delete_collection_warning": "Sei sicuro di voler eliminare questa raccolta? \nCiÃ˛ eliminerà anche tutte le avventure collegate. \nQuesta azione non puÃ˛ essere annullata.", + "delete_collection": "Elimina collezione", + "delete_collection_success": "Collezione eliminata con successo!", + "delete_collection_warning": "Sei sicuro di voler eliminare questa collezione? \nCiÃ˛ eliminerà anche tutte le avventure collegate. \nQuesta azione non puÃ˛ essere annullata.", "descending": "Discendente", "edit_adventure": "Modifica Avventura", - "edit_collection": "Modifica raccolta", + "edit_collection": "Modifica collezione", "filter": "Filtro", "homepage": "Home page", "latitude": "Latitudine", @@ -79,18 +80,18 @@ "private": "Privato", "public": "Pubblico", "rating": "Valutazione", - "remove_from_collection": "Rimuovi dalla raccolta", + "remove_from_collection": "Rimuovi dalla collezione", "share": "Condividere", "sort": "Ordinare", "sources": "Fonti", "unarchive": "Annulla l'archiviazione", - "unarchived_collection_message": "Raccolta annullata con successo!", + "unarchived_collection_message": "Collezione disarchiviata con successo!", "updated": "Aggiornato", "visit": "Visita", "visits": "Visite", "adventure_delete_success": "Avventura eliminata con successo!", - "collection_adventures": "Includi avventure di raccolta", - "collection_link_success": "Avventura collegata alla raccolta con successo!", + "collection_adventures": "Includi avventure dalle raccolte", + "collection_link_success": "Avventura collegata alla collezione con successo!", "dates": "Date", "delete_adventure": "Elimina avventura", "duration": "Durata", @@ -114,9 +115,9 @@ "adventure_updated": "Avventura aggiornata", "basic_information": "Informazioni di base", "category": "Categoria", - "clear_map": "Mappa chiara", + "clear_map": "Libera mappa", "copy_link": "Copia collegamento", - "date_constrain": "Vincolare alle date di raccolta", + "date_constrain": "Vincolare alle date di collezione", "description": "Descrizione", "end_date": "Data di fine", "fetch_image": "Recupera immagine", @@ -140,7 +141,7 @@ "search_for_location": "Cerca una posizione", "search_results": "Risultati della ricerca", "see_adventures": "Vedi Avventure", - "select_adventure_category": "Seleziona la categoria Avventura", + "select_adventure_category": "Seleziona la categoria per l'avventura", "share_adventure": "Condividi questa avventura!", "start_date": "Data di inizio", "upload_image": "Carica immagine", @@ -154,11 +155,11 @@ "all": "Tutto", "error_updating_regions": "Errore durante l'aggiornamento delle regioni", "mark_region_as_visited": "Contrassegnare la regione {regione}, {paese} come visitata?", - "mark_visited": "Marco ha visitato", + "mark_visited": "Segna come visitato", "my_adventures": "Le mie avventure", "no_adventures_found": "Nessuna avventura trovata", - "no_collections_found": "Nessuna raccolta trovata a cui aggiungere questa avventura.", - "no_linkable_adventures": "Non è stata trovata alcuna avventura che possa essere collegata a questa raccolta.", + "no_collections_found": "Nessuna collezione trovata a cui aggiungere questa avventura.", + "no_linkable_adventures": "Non è stata trovata alcuna avventura che possa essere collegata a questa collezione.", "not_visited": "Non visitato", "regions_updated": "regioni aggiornate", "update_visited_regions": "Aggiorna le regioni visitate", @@ -168,20 +169,20 @@ "add_new": "Aggiungi nuovo...", "checklist": "Lista di controllo", "checklists": "Liste di controllo", - "collection_archived": "Questa raccolta è stata archiviata.", - "collection_completed": "Hai completato questa raccolta!", - "collection_stats": "Statistiche della raccolta", + "collection_archived": "Questa collezione è stata archiviata.", + "collection_completed": "Hai completato questa collezione!", + "collection_stats": "Statistiche della collezione", "days": "giorni", "itineary_by_date": "Itinerario per data", "keep_exploring": "Continua a esplorare!", - "link_new": "Collegamento Nuovo...", + "link_new": "Collega Nuovo...", "linked_adventures": "Avventure collegate", "links": "Collegamenti", "no_end_date": "Inserisci una data di fine", "note": "Nota", "notes": "Note", "nothing_planned": "Niente in programma per questa giornata. \nBuon viaggio!", - "transportation": "Trasporti", + "transportation": "Trasporto", "transportations": "Trasporti", "visit_link": "Visita il collegamento", "day": "Giorno", @@ -194,9 +195,9 @@ "adventure_calendar": "Calendario delle avventure", "emoji_picker": "Selettore di emoji", "hide": "Nascondere", - "show": "Spettacolo", + "show": "Mostrare", "download_calendar": "Scarica Calendario", - "md_instructions": "Scrivi qui il tuo ribasso...", + "md_instructions": "Scrivi qui in markdown...", "preview": "Anteprima", "checklist_delete_confirm": "Sei sicuro di voler eliminare questa lista di controllo? \nQuesta azione non puÃ˛ essere annullata.", "clear_location": "Cancella posizione", @@ -205,7 +206,7 @@ "delete_note": "Elimina nota", "delete_transportation": "Elimina trasporto", "end": "FINE", - "ending_airport": "Fine dell'aeroporto", + "ending_airport": "Aeroporto di arrivo", "flight_information": "Informazioni sul volo", "from": "Da", "no_location_found": "Nessuna posizione trovata", @@ -213,7 +214,7 @@ "out_of_range": "Non nell'intervallo di date dell'itinerario", "show_region_labels": "Mostra etichette regione", "start": "Inizio", - "starting_airport": "Inizio aeroporto", + "starting_airport": "Aeroporto di partenza", "to": "A", "transportation_delete_confirm": "Sei sicuro di voler eliminare questo trasporto? \nQuesta azione non puÃ˛ essere annullata.", "show_map": "Mostra mappa", @@ -221,7 +222,7 @@ "cities_updated": "città aggiornate", "create_adventure": "Crea Avventura", "no_adventures_to_recommendations": "Nessuna avventura trovata. \nAggiungi almeno un'avventura per ricevere consigli.", - "finding_recommendations": "Alla scoperta di gemme nascoste per la tua prossima avventura", + "finding_recommendations": "Alla scoperta di tesori nascosti per la tua prossima avventura", "attachment": "Allegato", "attachment_delete_success": "Allegato eliminato con successo!", "attachment_name": "Nome dell'allegato", @@ -247,7 +248,32 @@ "region": "Regione", "welcome_map_info": "Avventure pubbliche su questo server", "reservation_number": "Numero di prenotazione", - "open_in_maps": "Aperto in mappe" + "open_in_maps": "Aprire in Mappe", + "all_day": "Tutto il giorno", + "collection_no_start_end_date": "L'aggiunta di una data di inizio e fine alla collezione sbloccherà le funzionalità di pianificazione dell'itinerario nella pagina della collezione.", + "date_itinerary": "Data dell'itinerario", + "no_ordered_items": "Aggiungi elementi con date alla collezione per vederli qui.", + "ordered_itinerary": "Itinerario ordinato", + "additional_info": "Ulteriori informazioni", + "invalid_date_range": "Intervallo di date non valido", + "sunrise_sunset": "Alba", + "timezone": "Fuso orario", + "no_visits": "Nessuna visita", + "arrival_timezone": "Fuso orario di arrivo", + "departure_timezone": "Fuso orario di partenza", + "arrival_date": "Data di arrivo", + "departure_date": "Data di partenza", + "coordinates": "Coordinate", + "copy_coordinates": "Copia coordinate", + "sun_times": "Sun Times", + "sunrise": "Alba", + "sunset": "Tramonto", + "timed": "A tempo", + "distance": "Distanza", + "all_linked_items": "Tutti gli elementi collegati", + "itinerary": "Itinerario", + "joined": "Partecipato", + "view_profile": "Visualizza il profilo" }, "home": { "desc_1": "Scopri, pianifica ed esplora con facilità", @@ -267,9 +293,9 @@ "about": "Informazioni su AdventureLog", "adventures": "Avventure", "collections": "Collezioni", - "discord": "Discordia", + "discord": "Discord", "documentation": "Documentazione", - "greeting": "CIAO", + "greeting": "Ciao", "logout": "Esci", "map": "Mappa", "my_adventures": "Le mie avventure", @@ -290,7 +316,7 @@ }, "users": "Utenti", "worldtravel": "Viaggio nel mondo", - "my_tags": "I miei tag", + "my_tags": "Le mie tag", "tag": "Etichetta", "language_selection": "Lingua", "support": "Supporto", @@ -314,7 +340,7 @@ "public_tooltip": "Con un profilo pubblico, gli utenti possono condividere raccolte con te e visualizzare il tuo profilo nella pagina degli utenti.", "email_required": "L'e-mail è obbligatoria", "both_passwords_required": "Sono necessarie entrambe le password", - "new_password": "Nuova parola d'ordine", + "new_password": "Nuova password", "reset_failed": "Impossibile reimpostare la password", "or_3rd_party": "Oppure accedi con un servizio di terze parti", "no_public_adventures": "Nessuna avventura pubblica trovata", @@ -344,7 +370,7 @@ "region_failed_visited": "Impossibile contrassegnare la regione come visitata", "region_stats": "Statistiche della regione", "regions_in": "Regioni dentro", - "removed": "RIMOSSO", + "removed": "Rimosso", "view_cities": "Visualizza città", "visit_remove_failed": "Impossibile rimuovere la visita", "visit_to": "Visita a" @@ -355,7 +381,7 @@ "current_email": "E-mail corrente", "email_change": "Cambia e-mail", "new_email": "Nuova e-mail", - "new_password": "Nuova parola d'ordine", + "new_password": "Nuova password", "no_email_set": "Nessuna e-mail impostata", "password_change": "Cambiare la password", "settings_page": "Pagina Impostazioni", @@ -373,7 +399,7 @@ "submit": "Invia", "token_required": "Token e UID sono necessari per la reimpostazione della password.", "about_this_background": "A proposito di questo contesto", - "join_discord": "Unisciti alla Discordia", + "join_discord": "Unisciti a Discord", "join_discord_desc": "per condividere le tue foto. \nPubblicateli in", "photo_by": "Foto di", "change_password_error": "Impossibile modificare la password. \nPassword attuale non valida o nuova password non valida.", @@ -407,7 +433,7 @@ "not_verified": "Non verificato", "primary": "Primario", "recovery_codes": "Codici di ripristino", - "recovery_codes_desc": "Questi sono i tuoi codici di ripristino. \nTeneteli al sicuro. \nNon potrai vederli piÚ.", + "recovery_codes_desc": "Questi sono i tuoi codici di ripristino. \nTienili al sicuro. \nNon potrai vederli piÚ.", "reset_session_error": "Esci, effettua nuovamente l'accesso per aggiornare la sessione e riprova.", "verified": "Verificato", "verify_email_success": "Verifica email inviata con successo!", @@ -426,7 +452,7 @@ "no_verified_email_warning": "È necessario disporre di un indirizzo e-mail verificato per abilitare l'autenticazione a due fattori.", "social_auth_desc": "Abilita o disabilita i provider di autenticazione social e OIDC per il tuo account. \nQueste connessioni ti consentono di accedere con provider di identità di autenticazione self-hosted come Authentik o provider di terze parti come GitHub.", "social_auth_desc_2": "Queste impostazioni sono gestite nel server AdventureLog e devono essere abilitate manualmente dall'amministratore.", - "social_oidc_auth": "Autenticazione sociale e OIDC", + "social_oidc_auth": "Autenticazione social e OIDC", "add_email": "Aggiungi e-mail", "password_too_short": "La password deve contenere almeno 6 caratteri", "disable_password": "Disabilita la password", @@ -436,43 +462,94 @@ "password_disabled": "Autenticazione password disabilitata", "password_disabled_error": "Errore di disabilitazione dell'autenticazione della password. \nAssicurati che un fornitore sociale o OIDC sia collegato al tuo account.", "password_enabled": "Autenticazione password abilitata", - "password_enabled_error": "Errore che abilita l'autenticazione della password." + "password_enabled_error": "Errore che abilita l'autenticazione della password.", + "access_restricted": "Accesso limitato", + "access_restricted_desc": "Le funzionalità YAdministrative sono disponibili solo per i membri del personale.", + "add_new_email": "Aggiungi nuova e -mail", + "add_new_email_address": "Aggiungi nuovo indirizzo email", + "admin": "Amministratore", + "admin_panel_desc": "Accedi all'interfaccia di amministrazione completa", + "administration": "Amministrazione", + "administration_desc": "Strumenti e impostazioni amministrative", + "advanced": "Avanzato", + "advanced_settings": "Impostazioni avanzate", + "advanced_settings_desc": "Strumenti avanzati di configurazione e sviluppo", + "all_rights_reserved": "Tutti i diritti riservati.", + "app_version": "Versione app", + "confirm_new_password_desc": "Conferma nuova password", + "connected": "Collegato", + "debug_information": "Informazioni sul debug", + "disabled": "Disabile", + "disconnected": "Disconnesso", + "email_management": "Gestione e -mail", + "email_management_desc": "Gestisci i tuoi indirizzi e -mail e lo stato di verifica", + "emails": "E -mail", + "enabled": "Abilitato", + "enter_current_password": "Immettere la password corrente", + "enter_first_name": "Inserisci il tuo nome", + "enter_last_name": "Inserisci il tuo cognome", + "enter_new_email": "Inserisci un nuovo indirizzo email", + "enter_new_password": "Immettere nuova password", + "enter_username": "Inserisci il tuo nome utente", + "integrations": "Integrazioni", + "integrations_desc": "Collega i servizi esterni per migliorare la tua esperienza", + "license": "Licenza", + "mfa_desc": "Aggiungi un ulteriore livello di sicurezza al tuo account", + "mfa_is_enabled": "MFA è abilitato", + "pass_change_desc": "Aggiorna la password del tuo account per una migliore sicurezza", + "password_login_disabled": "Accesso della password disabilitata", + "password_login_enabled": "Accesso alla password abilitata", + "profile_info": "Informazioni sul profilo", + "profile_info_desc": "Aggiorna i tuoi dettagli personali e l'immagine del profilo", + "public_profile_desc": "Rendi il tuo profilo visibile ad altri utenti", + "quick_actions": "Azioni rapide", + "region_updates": "Aggiornamenti della regione", + "region_updates_desc": "Aggiorna le regioni e le città visitate", + "regular_user": "Utente normale", + "security": "Sicurezza", + "settings_menu": "Menu Impostazioni", + "social_auth": "Autenticazione sociale", + "social_auth_desc_1": "Gestisci le opzioni di accesso social e le impostazioni della password", + "social_auth_setup": "Setup di autenticazione sociale", + "staff_status": "Stato del personale", + "staff_user": "Utente del personale", + "password_auth": "Autenticazione della password" }, "checklist": { - "add_item": "Aggiungi articolo", + "add_item": "Aggiungi elemento", "checklist_delete_error": "Errore durante l'eliminazione della lista di controllo", "checklist_deleted": "Lista di controllo eliminata con successo!", "checklist_editor": "Redattore della lista di controllo", - "checklist_public": "Questa lista di controllo è pubblica perchÊ è in una raccolta pubblica.", + "checklist_public": "Questa lista di controllo è pubblica perchÊ è in una collezione pubblica.", "editing_checklist": "Lista di controllo per la modifica", "failed_to_save": "Impossibile salvare la lista di controllo", - "item": "Articolo", - "item_already_exists": "L'articolo esiste già", - "item_cannot_be_empty": "L'articolo non puÃ˛ essere vuoto", + "item": "Elemento", + "item_already_exists": "L'elemento esiste già", + "item_cannot_be_empty": "L'elemento non puÃ˛ essere vuoto", "items": "Elementi", "save": "Salva", - "new_item": "Nuovo articolo", + "new_item": "Nuovo elemento", "checklist_viewer": "Visualizzatore della lista di controllo", "new_checklist": "Nuova lista di controllo" }, "collection": { - "edit_collection": "Modifica raccolta", - "error_creating_collection": "Errore durante la creazione della raccolta", - "error_editing_collection": "Errore durante la modifica della raccolta", + "edit_collection": "Modifica collezione", + "error_creating_collection": "Errore durante la creazione della collezione", + "error_editing_collection": "Errore durante la modifica della collezione", "new_collection": "Nuova collezione", "collection_created": "Collezione creata con successo!", - "collection_edit_success": "Raccolta modificata con successo!", + "collection_edit_success": "Collezione modificata con successo!", "create": "Creare", "public_collection": "Collezione pubblica" }, "notes": { "add_a_link": "Aggiungi un collegamento", "content": "Contenuto", - "editing_note": "Nota di modifica", + "editing_note": "Editor di modifica nota", "failed_to_save": "Impossibile salvare la nota", "note_delete_error": "Errore durante l'eliminazione della nota", "note_deleted": "Nota eliminata con successo!", - "note_editor": "Redattore della nota", + "note_editor": "Editor della nota", "note_public": "Questa nota è pubblica perchÊ è in una collezione pubblica.", "open": "Aprire", "save": "Salva", @@ -508,7 +585,7 @@ "transportation_deleted": "Trasporto eliminato con successo!", "transportation_edit_success": "Trasporti modificati con successo!", "type": "Tipo", - "ending_airport_desc": "Immettere il codice aeroportuale finale (ad es. LAX)", + "ending_airport_desc": "Immettere il codice dell'aroporto di arrivo (ad es. LAX)", "fetch_location_information": "Informazioni sulla posizione di recupero", "starting_airport_desc": "Immettere il codice dell'aeroporto di partenza (ad es. JFK)" }, @@ -530,17 +607,17 @@ "share": { "no_users_shared": "Nessun utente condiviso con", "not_shared_with": "Non condiviso con", - "share_desc": "Condividi questa raccolta con altri utenti.", + "share_desc": "Condividi questa collezione con altri utenti.", "shared": "Condiviso", "shared_with": "Condiviso con", "unshared": "Non condiviso", "with": "con", "go_to_settings": "Vai alle impostazioni", - "no_shared_found": "Nessuna raccolta trovata condivisa con te.", + "no_shared_found": "Nessuna collezione trovata condivisa con te.", "set_public": "Per consentire agli utenti di condividere con te, è necessario che il tuo profilo sia impostato su pubblico." }, "profile": { - "member_since": "Membro da allora", + "member_since": "Membro da", "user_stats": "Statistiche utente", "visited_countries": "Paesi visitati", "visited_regions": "Regioni visitate", @@ -553,7 +630,9 @@ "manage_categories": "Gestisci categorie", "no_categories_found": "Nessuna categoria trovata.", "select_category": "Seleziona Categoria", - "update_after_refresh": "Le carte avventura verranno aggiornate una volta aggiornata la pagina." + "update_after_refresh": "Le carte avventura verranno aggiornate una volta aggiornata la pagina.", + "add_category": "Aggiungi categoria", + "add_new_category": "Aggiungi nuova categoria" }, "dashboard": { "add_some": "PerchÊ non iniziare a pianificare la tua prossima avventura? \nPuoi aggiungere una nuova avventura facendo clic sul pulsante in basso.", @@ -588,26 +667,43 @@ "server_url": "URL del server Immich", "update_integration": "Aggiorna integrazione", "documentation": "Documentazione sull'integrazione di Immich", - "localhost_note": "Nota: molto probabilmente localhost non funzionerà a meno che tu non abbia configurato le reti docker di conseguenza. \nSi consiglia di utilizzare l'indirizzo IP del server o il nome del dominio." + "localhost_note": "Nota: molto probabilmente localhost non funzionerà a meno che tu non abbia configurato le reti docker di conseguenza. \nSi consiglia di utilizzare l'indirizzo IP del server o il nome del dominio.", + "api_key_placeholder": "Inserisci la tua chiave API immich", + "enable_integration": "Abilita l'integrazione", + "immich_integration_desc": "Collega il tuo server di gestione delle foto immich", + "need_help": "Hai bisogno di aiuto per impostare questo? \nDai un'occhiata al", + "connection_error": "Errore che si collega al server immich", + "copy_locally": "Copia immagini localmente", + "error_saving_image": "Errore salvare l'immagine", + "integration_already_exists": "Esiste già un'integrazione immich. \nPuoi avere solo un'integrazione alla volta.", + "integration_not_found": "Integrazione immich non trovata. \nSi prega di creare una nuova integrazione.", + "network_error": "Errore di rete durante la connessione al server immich. \nControlla la tua connessione e riprova.", + "validation_error": "Si è verificato un errore durante la convalida dell'integrazione immich. \nControlla l'URL e la chiave API del server.", + "copy_locally_desc": "Copia le immagini sul server per l'accesso offline. \nUtilizza piÚ spazio su disco." }, "recomendations": { "address": "Indirizzo", "contact": "Contatto", "phone": "Telefono", "recommendation": "Raccomandazione", - "website": "Sito web" + "website": "Sito web", + "recommendations": "Raccomandazioni", + "adventure_recommendations": "Consigli di avventura", + "food": "Cibo", + "miles": "Miglia", + "tourism": "Turismo" }, "lodging": { "apartment": "Appartamento", "bnb": "Bed and Breakfast", "cabin": "Cabina", "campground": "Campeggio", - "check_in": "Check -in", - "check_out": "Guardare", - "date_and_time": "Data", + "check_in": "Check-in", + "check_out": "Check-out", + "date_and_time": "Data e ora", "edit": "Modificare", "edit_lodging": "Modifica alloggio", - "error_editing_lodging": "Alloggio di modifica degli errori", + "error_editing_lodging": "Errore nella modifica dell'alloggio", "hostel": "Ostello", "hotel": "Hotel", "house": "Casa", @@ -616,15 +712,18 @@ "other": "Altro", "provide_start_date": "Si prega di fornire una data di inizio", "reservation_number": "Numero di prenotazione", - "resort": "Ricorrere", + "resort": "Resort", "start": "Inizio", "type": "Tipo", "villa": "Villa", - "lodging_delete_error": "Errore di eliminazione dell'alloggio", + "lodging_delete_error": "Errore nell'eliminazione dell'alloggio", "lodging_deleted": "Alloggio eliminato con successo!", "lodging_edit_success": "Alloggio modificato con successo!", "lodging_type": "Tipo di alloggio", "motel": "Motel", "current_timezone": "Fuso orario attuale" + }, + "google_maps": { + "google_maps_integration_desc": "Collega il tuo account Google Maps per ottenere risultati e consigli di ricerca sulla posizione di alta qualità." } } diff --git a/frontend/src/locales/ko.json b/frontend/src/locales/ko.json index bfe761b2..47bcf4cb 100644 --- a/frontend/src/locales/ko.json +++ b/frontend/src/locales/ko.json @@ -8,7 +8,8 @@ "nominatim_2": "ë°ė´í„°ëŠ” ODbL ëŧė´ė„ ėŠ¤ę°€ ė ėšŠëŠë‹ˆë‹¤.", "oss_attributions": "ė˜¤í”ˆ ė†ŒėŠ¤ ė†ė„ą", "other_attributions": "ėļ”ę°€ ė†ė„ąė€ README 파ėŧė—ė„œ ė°žė„ 눘 ėžˆėŠĩ니다.", - "source_code": "ė†ŒėŠ¤ ėŊ”드" + "source_code": "ė†ŒėŠ¤ ėŊ”드", + "generic_attributions": "Adventurelog뗐 ëĄœęˇ¸ė¸í•˜ė—Ŧ í™œė„ąí™” 된 í†ĩ합 및 ė„œëš„ėŠ¤ė— 대한 ė†ė„ąė„ëŗ´ė‹­ė‹œė˜¤." }, "adventures": { "actions": "행동", @@ -247,7 +248,32 @@ "region": "맀뗭", "reservation_number": "똈ė•Ŋ 번호", "welcome_map_info": "ė´ ė„œë˛„ė˜ ęŗĩ氜 ëĒ¨í—˜", - "open_in_maps": "ė§€ë„ė—ė„œ ė—´ëĻŊ니다" + "open_in_maps": "ė§€ë„ė—ė„œ ė—´ëĻŊ니다", + "all_day": "í•˜ëŖ¨ ėĸ…ėŧ", + "collection_no_start_end_date": "ėģŦë ‰ė…˜ė— ė‹œėž‘ 및 ėĸ…ëŖŒ ë‚ ė§œëĨŧ ėļ”ę°€í•˜ëŠ´ ėģŦë ‰ė…˜ íŽ˜ė´ė§€ė—ė„œ ė—Ŧė • ęŗ„íš 기ëŠĨė´ ėž ę¸ˆ í•´ė œëŠë‹ˆë‹¤.", + "date_itinerary": "ë‚ ė§œ ėŧė •", + "no_ordered_items": "ėģŦë ‰ė…˜ė— ë‚ ė§œę°€ėžˆëŠ” 항ëĒŠė„ ėļ”ę°€í•˜ė—Ŧ ė—Ŧę¸°ė—ė„œ í™•ė¸í•˜ė‹­ė‹œė˜¤.", + "ordered_itinerary": "ėŖŧëŦ¸í•œ ė—Ŧė •", + "additional_info": "ėļ”ę°€ ė •ëŗ´", + "invalid_date_range": "ėž˜ëĒģ된 ë‚ ė§œ ë˛”ėœ„", + "sunrise_sunset": "í•´ë‹ė´", + "timezone": "ė‹œę°„ëŒ€", + "no_visits": "ë°ŠëŦ¸ ė—†ėŒ", + "arrival_timezone": "ë„ė°Š ė‹œę°„ëŒ€", + "departure_timezone": "ėļœë°œ ė‹œę°„ëŒ€", + "arrival_date": "ë„ė°Š ë‚ ė§œ", + "departure_date": "ėļœë°œ ë‚ ė§œ", + "coordinates": "ėĸŒí‘œ", + "copy_coordinates": "ėĸŒí‘œëĨŧ ëŗĩė‚Ŧí•˜ė‹­ė‹œė˜¤", + "sun_times": "íƒœė–‘ ė‹œę°„", + "sunrise": "í•´ë‹ė´", + "sunset": "ėŧëǰ", + "timed": "ė‹œę°„ė´ ė •í•´ėĄŒėŠĩ니다", + "distance": "ęą°ëĻŦ", + "all_linked_items": "ëĒ¨ë“  링íŦ 된 항ëĒŠ", + "itinerary": "ė—Ŧė •", + "joined": "ę°€ėž…", + "view_profile": "í”„ëĄœí•„ė„ 봅니다" }, "auth": { "both_passwords_required": "두 ė•”í˜¸ ëĒ¨ë‘ í•„ėš”í•Šë‹ˆë‹¤", @@ -281,7 +307,9 @@ "manage_categories": "ėš´í…Œęŗ ëĻŦ 관ëĻŦ", "no_categories_found": "ėš´í…Œęŗ ëĻŦ가 ė—†ėŠĩ니다.", "select_category": "ėš´í…Œęŗ ëĻŦ ė„ íƒ", - "update_after_refresh": "íŽ˜ė´ė§€ëĨŧ ėƒˆëĄœęŗ ėš¨í•´ė•ŧ ëĒ¨í—˜ ėš´ë“œę°€ ė—…ë°ė´íŠ¸ëŠë‹ˆë‹¤." + "update_after_refresh": "íŽ˜ė´ė§€ëĨŧ ėƒˆëĄœęŗ ėš¨í•´ė•ŧ ëĒ¨í—˜ ėš´ë“œę°€ ė—…ë°ė´íŠ¸ëŠë‹ˆë‹¤.", + "add_category": "ėš´í…Œęŗ ëĻŦ ėļ”ę°€", + "add_new_category": "냈 ėš´í…Œęŗ ëĻŦëĨŧ ėļ”ę°€í•˜ė‹­ė‹œė˜¤" }, "checklist": { "add_item": "항ëĒŠ ėļ”ę°€", @@ -357,7 +385,19 @@ "query_required": "ėŋŧëĻŦ가 í•„ėš”í•Šë‹ˆë‹¤", "server_down": "현ėžŦ Immich ė„œë˛„ę°€ ë‹¤ėš´ë˜ė—ˆęą°ë‚˜ 도ë‹Ŧ할 눘 ė—†ėŠĩ니다", "server_url": "Immich ė„œë˛„ URL", - "update_integration": "í†ĩ합 ė—…ë°ė´íŠ¸" + "update_integration": "í†ĩ합 ė—…ë°ė´íŠ¸", + "api_key_placeholder": "Immich API 키ëĨŧ ėž…ë Ĩí•˜ė‹­ė‹œė˜¤", + "enable_integration": "í†ĩí•Šė„ í™œė„ąí™”í•Šë‹ˆë‹¤", + "immich_integration_desc": "Immich Photo Management ServerëĨŧ ė—°ę˛°í•˜ė‹­ė‹œė˜¤", + "need_help": "ė´ę˛ƒė„ ė„¤ė •í•˜ëŠ” 데 ë„ė›€ė´ í•„ėš”í•˜ė‹­ë‹ˆęšŒ? \ní™•ė¸í•˜ė‹­ė‹œė˜¤", + "connection_error": "Immich ė„œë˛„ė— ė—°ę˛°í•˜ëŠ” 똤ëĨ˜", + "copy_locally": "로ėģŦė—ė„œ ė´ë¯¸ė§€ëĨŧ ëŗĩė‚Ŧí•˜ė‹­ė‹œė˜¤", + "copy_locally_desc": "ė˜¤í”„ëŧė¸ ė•Ąė„¸ėŠ¤ëĨŧ ėœ„í•´ ė´ë¯¸ė§€ëĨŧ ė„œë˛„ė— ëŗĩė‚Ŧí•˜ė‹­ė‹œė˜¤. \n더 ë§Žė€ ë””ėŠ¤íŦ ęŗĩę°„ė„ ė‚ŦėšŠí•Šë‹ˆë‹¤.", + "error_saving_image": "똤ëĨ˜ ė €ėžĨ ė´ë¯¸ė§€", + "integration_already_exists": "ëŠ´ė—­ í†ĩí•Šė´ ė´ë¯¸ ėĄ´ėžŦ합니다. \n한 ë˛ˆė— 하나만 í†ĩ합 할 눘 ėžˆėŠĩ니다.", + "integration_not_found": "Immich í†ĩí•Šė„ ė°žė„ 눘 ė—†ėŠĩ니다. \nėƒˆëĄœėš´ í†ĩí•Šė„ ėž‘ė„ąí•˜ė‹­ė‹œė˜¤.", + "network_error": "Immich ė„œë˛„ė— ė—°ę˛°í•˜ëŠ” ë™ė•ˆ ë„¤íŠ¸ė›ŒíŦ 똤ëĨ˜. \nė—°ę˛°ė„ í™•ė¸í•˜ęŗ  ë‹¤ė‹œ ė‹œë„í•˜ė‹­ė‹œė˜¤.", + "validation_error": "ëŠ´ė—­ í†ĩí•Šė„ 검ėĻí•˜ëŠ” ë™ė•ˆ 똤ëĨ˜ę°€ ë°œėƒí–ˆėŠĩ니다. \nė„œë˛„ URL 및 API 키ëĨŧ í™•ė¸í•˜ė‹­ė‹œė˜¤." }, "map": { "add_adventure": "ėƒˆëĄœėš´ ëĒ¨í—˜ ėļ”ę°€", @@ -428,7 +468,12 @@ "contact": "ė—°ëŊ래", "phone": "핸드폰", "recommendation": "ėļ”ė˛œ", - "website": "ė›šė‚Ŧė´íŠ¸" + "website": "ė›šė‚Ŧė´íŠ¸", + "recommendations": "ęļŒėžĨ ė‚Ŧ항", + "adventure_recommendations": "ëĒ¨í—˜ ėļ”ė˛œ", + "food": "ėŒė‹", + "miles": "마ėŧ", + "tourism": "관광 ė—Ŧ행" }, "search": { "adventurelog_results": "Adventurelog 결ęŗŧ", @@ -522,7 +567,58 @@ "password_disabled": "비밀번호 ė¸ėĻ ëš„í™œė„ąí™”", "password_disabled_error": "비밀번호 ė¸ėĻ 똤ëĨ˜. \nė†Œė…œ 또는 OIDC 렜ęŗĩ 뗅랴氀 ęˇ€í•˜ė˜ 溄렕뗐 ė—°ę˛°ë˜ė–´ ėžˆëŠ”ė§€ í™•ė¸í•˜ė‹­ė‹œė˜¤.", "password_enabled": "비밀번호 ė¸ėĻ í™œė„ąí™”", - "password_enabled_error": "비밀번호 ė¸ėĻė„ í™œė„ąí™”í•˜ëŠ” 똤ëĨ˜." + "password_enabled_error": "비밀번호 ė¸ėĻė„ í™œė„ąí™”í•˜ëŠ” 똤ëĨ˜.", + "access_restricted": "ė•Ąė„¸ėŠ¤ ė œí•œ", + "access_restricted_desc": "Yadministrative 기ëŠĨė€ ė§ė›ė—ę˛Œë§Œ ė‚ŦėšŠí•  눘 ėžˆėŠĩ니다.", + "add_new_email": "냈 ė´ëŠ”ėŧė„ ėļ”ę°€í•˜ė‹­ė‹œė˜¤", + "add_new_email_address": "냈 ė´ëŠ”ėŧ ėŖŧė†ŒëĨŧ ėļ”ę°€í•˜ė‹­ė‹œė˜¤", + "admin": "관ëĻŦėž", + "admin_panel_desc": "렄랴 관ëĻŦ ė¸í„°íŽ˜ė´ėŠ¤ė— ė•Ąė„¸ėŠ¤í•˜ė‹­ė‹œė˜¤", + "administration": "관ëĻŦ", + "administration_desc": "관ëĻŦ 도ęĩŦ 및 네렕", + "advanced": "ęŗ ę¸‰ė˜", + "advanced_settings": "溠揉 네렕", + "advanced_settings_desc": "溠揉 ęĩŦė„ą 및 개발 도ęĩŦ", + "all_rights_reserved": "ëĒ¨ë“  ęļŒëĻŦ ëŗ´ėœ .", + "app_version": "ė•ą ë˛„ė „", + "confirm_new_password_desc": "냈 비밀번호ëĨŧ í™•ė¸í•˜ė‹­ė‹œė˜¤", + "connected": "뗰枰", + "debug_information": "디버그 ė •ëŗ´", + "disabled": "ėžĨė• ę°€ ėžˆëŠ”", + "disconnected": "ė—°ę˛°ė´ ëŠė–´ėĄŒėŠĩ니다", + "email_management": "ė´ëŠ”ėŧ 관ëĻŦ", + "email_management_desc": "ė´ëŠ”ėŧ ėŖŧė†Œ 및 í™•ė¸ ėƒíƒœëĨŧ 관ëĻŦí•˜ė‹­ė‹œė˜¤", + "emails": "ė´ëŠ”ėŧ", + "enabled": "í™œė„ąí™”", + "enter_current_password": "현ėžŦ 비밀번호ëĨŧ ėž…ë Ĩí•˜ė‹­ė‹œė˜¤", + "enter_first_name": "ė´ëĻ„ė„ ėž…ë Ĩí•˜ė‹­ė‹œė˜¤", + "enter_last_name": "ė„ąė„ ėž…ë Ĩí•˜ė‹­ė‹œė˜¤", + "enter_new_email": "냈 ė´ëŠ”ėŧ ėŖŧė†ŒëĨŧ ėž…ë Ĩí•˜ė‹­ė‹œė˜¤", + "enter_new_password": "냈 비밀번호ëĨŧ ėž…ë Ĩí•˜ė‹­ė‹œė˜¤", + "enter_username": "ė‚ŦėšŠėž ė´ëĻ„ė„ ėž…ë Ĩí•˜ė‹­ė‹œė˜¤", + "integrations": "í†ĩ합", + "integrations_desc": "ę˛Ŋí—˜ė„ í–Ĩėƒė‹œí‚¤ę¸° ėœ„í•´ 뙏ëļ€ ė„œëš„ėŠ¤ëĨŧ ė—°ę˛°í•˜ė‹­ė‹œė˜¤", + "license": "특허", + "mfa_desc": "溄렕뗐 ėļ”ę°€ ëŗ´ė•ˆ ęŗ„ė¸ĩė„ ėļ”ę°€í•˜ė‹­ė‹œė˜¤", + "mfa_is_enabled": "MFA가 í™œė„ąí™”ë˜ė—ˆėŠĩ니다", + "pass_change_desc": "더 ë‚˜ė€ ëŗ´ė•ˆė„ ėœ„í•´ ęŗ„ė • 비밀번호ëĨŧ ė—…ë°ė´íŠ¸í•˜ė‹­ė‹œė˜¤", + "password_auth": "비밀번호 ė¸ėĻ", + "password_login_disabled": "ė•”í˜¸ ëĄœęˇ¸ė¸ė´ ëš„í™œė„ąí™”ë˜ė—ˆėŠĩ니다", + "password_login_enabled": "ė•”í˜¸ ëĄœęˇ¸ė¸ė´ í™œė„ąí™”ë˜ė—ˆėŠĩ니다", + "profile_info": "프로필 ė •ëŗ´", + "profile_info_desc": "ę°œė¸ ė •ëŗ´ 및 프로필 ė‚Ŧė§„ė„ ė—…ë°ė´íŠ¸í•˜ė‹­ė‹œė˜¤", + "public_profile_desc": "í”„ëĄœí•„ė„ 다ëĨ¸ ė‚ŦėšŠėžė—ę˛Œ ëŗ´ė´ę˛Œí•˜ė‹­ė‹œė˜¤", + "quick_actions": "ëš ëĨ¸ 행동", + "region_updates": "맀뗭 ė—…ë°ė´íŠ¸", + "region_updates_desc": "ė—…ë°ė´íŠ¸ ë°ŠëŦ¸ 맀뗭 및 ë„ė‹œ", + "regular_user": "ėŧ반 ė‚ŦėšŠėž", + "security": "ëŗ´ė•ˆ", + "settings_menu": "네렕 메뉴", + "social_auth": "ė‚ŦíšŒė  ė¸ėĻ", + "social_auth_desc_1": "ė†Œė…œ ëĄœęˇ¸ė¸ ė˜ĩė…˜ 및 비밀번호 ė„¤ė •ė„ 관ëĻŦ합니다", + "social_auth_setup": "ė†Œė…œ ė¸ėĻ 네렕", + "staff_status": "링뛐 ėƒíƒœ", + "staff_user": "링뛐 ė‚ŦėšŠėž" }, "share": { "go_to_settings": "네렕ėœŧ로 ė´ë™", @@ -625,5 +721,8 @@ "type": "ėœ í˜•", "villa": "ëŗ„ėžĨ", "check_out": "랴íŦ ė•„ė›ƒ" + }, + "google_maps": { + "google_maps_integration_desc": "Googleė§€ë„ ęŗ„ė •ė„ ė—°ę˛°í•˜ė—Ŧ ęŗ í’ˆė§ˆ ėœ„ėš˜ ę˛€ėƒ‰ 결ęŗŧ 및 ęļŒėžĨ ė‚Ŧí•­ė„ ė–ģėœŧė‹­ė‹œė˜¤." } } diff --git a/frontend/src/locales/nl.json b/frontend/src/locales/nl.json index 4a783ec0..cf690ad3 100644 --- a/frontend/src/locales/nl.json +++ b/frontend/src/locales/nl.json @@ -8,7 +8,8 @@ "nominatim_2": "Hun gegevens zijn in licentie gegeven onder de ODbL-licentie.", "oss_attributions": "Open source gebruik", "other_attributions": "Aanvullende vermeldingen zijn te vinden in het README-bestand.", - "source_code": "Broncode" + "source_code": "Broncode", + "generic_attributions": "Log in op AdventUrelog om attributies te bekijken voor ingeschakelde integraties en services." }, "adventures": { "activities": { @@ -247,7 +248,32 @@ "lodging_information": "Informatie overliggen", "price": "Prijs", "region": "Regio", - "open_in_maps": "Open in kaarten" + "open_in_maps": "Open in kaarten", + "all_day": "De hele dag", + "collection_no_start_end_date": "Als u een start- en einddatum aan de collectie toevoegt, ontgrendelt u de functies van de planning van de route ontgrendelen in de verzamelpagina.", + "date_itinerary": "Datumroute", + "no_ordered_items": "Voeg items toe met datums aan de collectie om ze hier te zien.", + "ordered_itinerary": "Besteld reisschema", + "additional_info": "Aanvullende informatie", + "invalid_date_range": "Ongeldige datumbereik", + "sunrise_sunset": "Zonsopgang", + "timezone": "Tijdzone", + "no_visits": "Geen bezoeken", + "arrival_timezone": "Aankomsttijdzone", + "departure_timezone": "Vertrektijdzone", + "arrival_date": "Aankomstdatum", + "departure_date": "Vertrekdatum", + "coordinates": "CoÃļrdineert", + "copy_coordinates": "Kopieer coÃļrdinaten", + "sun_times": "Zonnetijden", + "sunrise": "Zonsopgang", + "sunset": "Zonsondergang", + "timed": "Getimed", + "distance": "Afstand", + "all_linked_items": "Alle gekoppelde items", + "itinerary": "Routebeschrijving", + "joined": "Samengevoegd", + "view_profile": "Bekijk profiel" }, "home": { "desc_1": "Ontdek, plan en verken met gemak", @@ -436,7 +462,58 @@ "password_disabled": "Wachtwoordverificatie uitgeschakeld", "password_disabled_error": "Fout het uitschakelen van wachtwoordverificatie. \nZorg ervoor dat een sociale of OIDC -provider is gekoppeld aan uw account.", "password_enabled": "Wachtwoordverificatie ingeschakeld", - "password_enabled_error": "Fout bij het inschakelen van wachtwoordverificatie." + "password_enabled_error": "Fout bij het inschakelen van wachtwoordverificatie.", + "access_restricted": "Toegang beperkt", + "access_restricted_desc": "Yadministratieve functies zijn alleen beschikbaar voor personeelsleden.", + "add_new_email": "Voeg nieuwe e -mail toe", + "add_new_email_address": "Voeg nieuw e -mailadres toe", + "admin": "Beheersing", + "admin_panel_desc": "Toegang tot de volledige beheerinterface", + "administration": "Administratie", + "administration_desc": "Administratieve tools en instellingen", + "advanced": "Geavanceerd", + "advanced_settings": "Geavanceerde instellingen", + "advanced_settings_desc": "Geavanceerde configuratie- en ontwikkelingstools", + "all_rights_reserved": "Alle rechten voorbehouden.", + "app_version": "App -versie", + "confirm_new_password_desc": "Bevestig nieuw wachtwoord", + "debug_information": "Debug -informatie", + "disabled": "Gehandicapt", + "disconnected": "Losgekoppeld", + "email_management": "E -mailbeheer", + "email_management_desc": "Beheer uw e -mailadressen en verificatiestatus", + "emails": "E -mails", + "enabled": "Ingeschakeld", + "enter_current_password": "Voer het huidige wachtwoord in", + "enter_first_name": "Voer uw voornaam in", + "enter_last_name": "Voer uw achternaam in", + "enter_new_email": "Voer een nieuw e -mailadres in", + "enter_new_password": "Voer een nieuw wachtwoord in", + "enter_username": "Voer uw gebruikersnaam in", + "integrations": "Integratie", + "integrations_desc": "Verbind externe services om uw ervaring te verbeteren", + "license": "Licentie", + "mfa_desc": "Voeg een extra beveiligingslaag toe aan uw account", + "mfa_is_enabled": "MFA is ingeschakeld", + "pass_change_desc": "Werk uw accountwachtwoord bij voor een betere beveiliging", + "password_auth": "Wachtwoordverificatie", + "password_login_disabled": "Wachtwoord Aanmelding Uitgeschakeld", + "password_login_enabled": "Wachtwoordaanmelding ingeschakeld", + "profile_info": "Profielinformatie", + "profile_info_desc": "Werk uw persoonlijke gegevens en profielfoto bij", + "public_profile_desc": "Maak uw profiel zichtbaar voor andere gebruikers", + "quick_actions": "Snelle acties", + "region_updates": "Regio -updates", + "region_updates_desc": "Update bezochte regio's en steden", + "regular_user": "Gewone gebruiker", + "security": "Beveiliging", + "settings_menu": "Instellingenmenu", + "social_auth": "Sociale authenticatie", + "social_auth_desc_1": "Beheer sociale inlogopties en wachtwoordinstellingen", + "social_auth_setup": "Sociale authenticatie -opstelling", + "staff_status": "Status", + "staff_user": "Personeelsgebruiker", + "connected": "Aangesloten" }, "checklist": { "add_item": "Artikel toevoegen", @@ -553,7 +630,9 @@ "manage_categories": "Beheer categorieÃĢn", "no_categories_found": "Geen categorieÃĢn gevonden.", "select_category": "Selecteer een categorie", - "update_after_refresh": "De avonturenkaarten worden bijgewerkt zodra u de pagina vernieuwt." + "update_after_refresh": "De avonturenkaarten worden bijgewerkt zodra u de pagina vernieuwt.", + "add_category": "Categorie toevoegen", + "add_new_category": "Voeg een nieuwe categorie toe" }, "dashboard": { "add_some": "Waarom begint u niet met het plannen van uw volgende avontuur? \nJe kunt een nieuw avontuur toevoegen door op de onderstaande knop te klikken.", @@ -588,14 +667,31 @@ "server_url": "Immich-server-URL", "update_integration": "Integratie bijwerken", "documentation": "Immich-integratiedocumentatie", - "localhost_note": "Opmerking: localhost zal hoogstwaarschijnlijk niet werken tenzij dit bewust zo geconfigureerd is in het docker-netwerk. \nHet is aanbevolen om het IP-adres van de server of de domeinnaam te gebruiken." + "localhost_note": "Opmerking: localhost zal hoogstwaarschijnlijk niet werken tenzij dit bewust zo geconfigureerd is in het docker-netwerk. \nHet is aanbevolen om het IP-adres van de server of de domeinnaam te gebruiken.", + "api_key_placeholder": "Voer uw Immich API -sleutel in", + "enable_integration": "Integratie inschakelen", + "immich_integration_desc": "Verbind uw Immich Photo Management Server", + "need_help": "Hulp nodig bij het opzetten van dit? \nBekijk de", + "connection_error": "Error verbinding maken met Immich Server", + "copy_locally": "Kopieer afbeeldingen lokaal", + "copy_locally_desc": "Kopieer afbeeldingen naar de server voor offline toegang. \nGebruikt meer schijfruimte.", + "error_saving_image": "Foutopslagafbeelding", + "integration_already_exists": "Er bestaat al een Immich -integratie. \nU kunt maar ÊÊn integratie tegelijk hebben.", + "integration_not_found": "Immich -integratie niet gevonden. \nMaak een nieuwe integratie.", + "network_error": "Netwerkfout terwijl u verbinding maakt met de Immich -server. \nControleer uw verbinding en probeer het opnieuw.", + "validation_error": "Er is een fout opgetreden bij het valideren van de Immich -integratie. \nControleer uw server -URL en API -toets." }, "recomendations": { "address": "Adres", "contact": "Contact", "phone": "Telefoon", "recommendation": "Aanbeveling", - "website": "Website" + "website": "Website", + "recommendations": "Aanbevelingen", + "adventure_recommendations": "Avontuuraanbevelingen", + "food": "Voedsel", + "miles": "Kilometers", + "tourism": "Toerisme" }, "lodging": { "apartment": "Appartement", @@ -626,5 +722,8 @@ "type": "Type", "villa": "Villa", "current_timezone": "Huidige tijdzone" + }, + "google_maps": { + "google_maps_integration_desc": "Sluit uw Google Maps-account aan om zoekresultaten en aanbevelingen van hoge kwaliteit te krijgen." } } diff --git a/frontend/src/locales/no.json b/frontend/src/locales/no.json new file mode 100644 index 00000000..08063219 --- /dev/null +++ b/frontend/src/locales/no.json @@ -0,0 +1,729 @@ +{ + "navbar": { + "adventures": "Eventyr", + "collections": "Samlinger", + "worldtravel": "Verdensreiser", + "map": "Kart", + "users": "Brukere", + "search": "Søk", + "profile": "Profil", + "greeting": "Hei", + "my_adventures": "Mine Eventyr", + "my_tags": "Mine Tags", + "tag": "Tag", + "shared_with_me": "Delt med meg", + "settings": "Innstillinger", + "logout": "Logg ut", + "about": "Om AdventureLog", + "documentation": "Dokumentasjon", + "discord": "Discord", + "language_selection": "SprÃĨk", + "support": "Støtte", + "calendar": "Kalender", + "theme_selection": "Tema-valg", + "admin_panel": "Admin Panel", + "themes": { + "light": "Lyst", + "dark": "Mørkt", + "night": "Natt", + "forest": "Skog", + "aestheticLight": "Estetisk Lyst", + "aestheticDark": "Estetisk Mørkt", + "aqua": "Aqua", + "northernLights": "Nordlys" + } + }, + "about": { + "about": "Om", + "license": "Lisensiert under GPL-3.0-lisensen.", + "source_code": "Kildekode", + "message": "Laget med â¤ī¸ i USA.", + "oss_attributions": "Open Source-attribusjoner", + "nominatim_1": "Stedsøk og geokoding leveres av", + "nominatim_2": "Deres data er lisensiert under ODbL-lisensen.", + "other_attributions": "Ytterligere attribusjoner finnes i README-filen.", + "close": "Lukk", + "generic_attributions": "Logg inn pÃĨ Adventurelog for ÃĨ se attribusjoner for aktiverte integrasjoner og tjenester." + }, + "home": { + "hero_1": "Oppdag verdens mest spennende eventyr", + "hero_2": "Oppdag og planlegg ditt neste eventyr med AdventureLog. Utforsk fantastiske destinasjoner, lag tilpassede reiseplaner, og hold kontakten pÃĨ farten.", + "go_to": "GÃĨ til AdventureLog", + "key_features": "Nøkkelfunksjoner", + "desc_1": "Oppdag, planlegg og utforsk med letthet", + "desc_2": "AdventureLog er designet for ÃĨ forenkle reisen din, og gir deg verktøy og ressurser til ÃĨ planlegge, pakke og navigere ditt neste uforglemmelige eventyr.", + "feature_1": "Reiselogg", + "feature_1_desc": "Før en personlig reiselogg for eventyrene dine og del opplevelsene dine med venner og familie.", + "feature_2": "Reiseplanlegging", + "feature_2_desc": "Lag enkelt tilpassede reiseplaner og fÃĨ en dag-for-dag oversikt over turen din.", + "feature_3": "Reisekart", + "feature_3_desc": "Se reisene dine over hele verden med et interaktivt kart og utforsk nye destinasjoner." + }, + "adventures": { + "collection_remove_success": "Eventyret ble fjernet fra samlingen!", + "collection_remove_error": "Feil ved fjerning av eventyr fra samling", + "collection_link_success": "Eventyret ble lagt til samlingen!", + "no_image_found": "Ingen bilde funnet", + "collection_link_error": "Feil ved lenking av eventyr til samling", + "adventure_delete_confirm": "Er du sikker pÃĨ at du vil slette dette eventyret? Denne handlingen kan ikke angres.", + "checklist_delete_confirm": "Er du sikker pÃĨ at du vil slette denne sjekklisten? Denne handlingen kan ikke angres.", + "note_delete_confirm": "Er du sikker pÃĨ at du vil slette dette notatet? Denne handlingen kan ikke angres.", + "transportation_delete_confirm": "Er du sikker pÃĨ at du vil slette dette transportmiddelet? Denne handlingen kan ikke angres.", + "lodging_delete_confirm": "Er du sikker pÃĨ at du vil slette dette overnattingsstedet? Denne handlingen kan ikke angres.", + "delete_checklist": "Slett sjekkliste", + "delete_note": "Slett notat", + "delete_transportation": "Slett transport", + "delete_lodging": "Slett overnatting", + "open_details": "Åpne detaljer", + "edit_adventure": "Rediger eventyr", + "remove_from_collection": "Fjern fra samling", + "add_to_collection": "Legg til i samling", + "delete": "Slett", + "not_found": "Fant ikke eventyret", + "not_found_desc": "Eventyret du leter etter, ble ikke funnet. Vennligst prøv et annet eventyr eller kom tilbake senere.", + "homepage": "Hjemmeside", + "adventure_details": "Eventyrdetaljer", + "collection": "Samling", + "adventure_type": "Eventyrtype", + "longitude": "Lengdegrad", + "latitude": "Breddegrad", + "visit": "Besøk", + "visits": "Besøk", + "create_new": "Opprett nytt...", + "adventure": "Eventyr", + "count_txt": "resultater som samsvarer med søket ditt", + "sort": "Sorter", + "order_by": "Sorter etter", + "order_direction": "Sorteringsretning", + "ascending": "Stigende", + "descending": "Synkende", + "updated": "Oppdatert", + "name": "Navn", + "date": "Dato", + "activity_types": "Aktivitetstyper", + "tags": "Tags", + "add_a_tag": "Legg til en tag", + "date_constrain": "Begrens til samlingsdatoer", + "rating": "Vurdering", + "my_images": "Mine bilder", + "add_an_activity": "Legg til en aktivitet", + "show_region_labels": "Vis regionetiketter", + "no_images": "Ingen bilder", + "upload_images_here": "Last opp bilder her", + "share_adventure": "Del dette eventyret!", + "copy_link": "Kopier lenke", + "image": "Bilde", + "upload_image": "Last opp bilde", + "open_in_maps": "Åpne i kart", + "url": "URL", + "fetch_image": "Hent bilde", + "wikipedia": "Wikipedia", + "add_notes": "Legg til notater", + "warning": "Advarsel", + "my_adventures": "Mine eventyr", + "no_linkable_adventures": "Ingen eventyr funnet som kan legges til denne samlingen.", + "add": "Legg til", + "save_next": "Lagre og fortsett", + "end_date": "Sluttdato", + "my_visits": "Mine besøk", + "start_date": "Startdato", + "remove": "Fjern", + "location": "Plassering", + "search_for_location": "Søk etter sted", + "clear_map": "Tøm kart", + "search_results": "Søkeresultater", + "no_results": "Ingen resultater funnet", + "wiki_desc": "Henter utdrag fra Wikipedia-artikkelen som samsvarer med navnet pÃĨ eventyret.", + "attachments": "Vedlegg", + "attachment": "Vedlegg", + "images": "Bilder", + "primary": "PrimÃĻr", + "view_attachment": "Vis vedlegg", + "generate_desc": "Generer beskrivelse", + "public_adventure": "Offentlig eventyr", + "location_information": "Plasseringsinformasjon", + "link": "Lenke", + "links": "Lenker", + "description": "Beskrivelse", + "sources": "Kilder", + "collection_adventures": "Inkluder eventyr i samlinger", + "filter": "Filter", + "category_filter": "Kategorifilter", + "category": "Kategori", + "select_adventure_category": "Velg eventyrkategori", + "clear": "Tøm", + "my_collections": "Mine samlinger", + "open_filters": "Åpne filtre", + "close_filters": "Lukk filtre", + "archived_collections": "Arkiverte samlinger", + "share": "Del", + "private": "Privat", + "public": "Offentlig", + "archived": "Arkivert", + "edit_collection": "Rediger samling", + "unarchive": "Fjern fra arkiv", + "archive": "Arkiver", + "no_collections_found": "Ingen samlinger funnet for ÃĨ legge dette eventyret til.", + "not_visited": "Ikke besøkt", + "archived_collection_message": "Samlingen ble arkivert!", + "unarchived_collection_message": "Samlingen ble fjernet fra arkivet!", + "delete_collection_success": "Samlingen ble slettet!", + "delete_collection_warning": "Er du sikker pÃĨ at du vil slette denne samlingen? Dette vil ogsÃĨ slette alle lenkede eventyr. Denne handlingen kan ikke angres.", + "cancel": "Avbryt", + "of": "av", + "delete_collection": "Slett samling", + "delete_adventure": "Slett eventyr", + "adventure_delete_success": "Eventyret ble slettet!", + "visited": "Besøkt", + "planned": "Planlagt", + "duration": "Varighet", + "all": "Alle", + "image_removed_success": "Bilde ble fjernet!", + "image_removed_error": "Feil ved fjerning av bilde", + "no_image_url": "Finner ikke bilde pÃĨ den oppgitte URL-en.", + "image_upload_success": "Bilde opplastet!", + "image_upload_error": "Feil ved opplasting av bilde", + "dates": "Datoer", + "wiki_image_error": "Feil ved henting av bilde fra Wikipedia", + "start_before_end_error": "Startdato mÃĨ vÃĻre før sluttdato", + "activity": "Aktivitet", + "actions": "Handlinger", + "no_end_date": "Vennligst angi en sluttdato", + "see_adventures": "Se eventyr", + "image_fetch_failed": "Kunne ikke hente bilde", + "no_location": "Vennligst angi et sted", + "no_start_date": "Vennligst angi en startdato", + "no_description_found": "Fant ingen beskrivelse", + "adventure_created": "Eventyr opprettet", + "adventure_create_error": "Kunne ikke opprette eventyr", + "lodging": "Overnatting", + "create_adventure": "Opprett eventyr", + "adventure_updated": "Eventyr oppdatert", + "adventure_update_error": "Kunne ikke oppdatere eventyr", + "set_to_pin": "Fest", + "category_fetch_error": "Feil ved henting av kategorier", + "new_adventure": "Nytt eventyr", + "basic_information": "Grunnleggende informasjon", + "no_adventures_to_recommendations": "Ingen eventyr funnet. Legg til minst ett eventyr for ÃĨ fÃĨ anbefalinger.", + "display_name": "Visningsnavn", + "adventure_not_found": "Det finnes ingen eventyr ÃĨ vise. Legg til noen ved ÃĨ trykke pÃĨ plusstegnet nederst til høyre, eller prøv ÃĨ endre filtre!", + "no_adventures_found": "Ingen eventyr funnet", + "mark_region_as_visited": "Merk regionen {region}, {country} som besøkt?", + "mark_visited": "Merk som besøkt", + "error_updating_regions": "Feil ved oppdatering av regioner", + "regions_updated": "regioner oppdatert", + "cities_updated": "byer oppdatert", + "visited_region_check": "Sjekk besøkte regioner", + "visited_region_check_desc": "Ved ÃĨ markere denne, vil serveren sjekke alle dine besøkte eventyr og markere regionene de befinner seg i som besøkt i verdensreiser.", + "update_visited_regions": "Oppdater besøkte regioner", + "update_visited_regions_disclaimer": "Dette kan ta litt tid avhengig av hvor mange eventyr du har besøkt.", + "link_new": "Lenk ny...", + "add_new": "Legg til ny...", + "transportation": "Transport", + "note": "Notat", + "checklist": "Sjekkliste", + "collection_archived": "Denne samlingen er arkivert.", + "visit_link": "Besøk lenke", + "collection_completed": "Du har fullført denne samlingen!", + "collection_stats": "Samlingsstatistikk", + "keep_exploring": "Fortsett ÃĨ utforske!", + "linked_adventures": "Lenkede eventyr", + "notes": "Notater", + "checklists": "Sjekklister", + "transportations": "Transportmidler", + "adventure_calendar": "Eventyrkalender", + "day": "Dag", + "itineary_by_date": "Reiseplan etter dato", + "nothing_planned": "Ingenting planlagt denne dagen. Nyt reisen!", + "copied_to_clipboard": "Kopiert til utklippstavlen!", + "copy_failed": "Kopiering mislyktes", + "show": "Vis", + "hide": "Skjul", + "clear_location": "Fjern sted", + "starting_airport": "Avreiseflyplass", + "ending_airport": "Ankomsflyplass", + "no_location_found": "Ingen sted funnet", + "from": "Fra", + "to": "Til", + "will_be_marked": "vil bli markert som besøkt nÃĨr eventyret er lagret.", + "start": "Start", + "end": "Slutt", + "show_map": "Vis kart", + "emoji_picker": "Emoji-velger", + "download_calendar": "Last ned kalender", + "date_information": "Dato-informasjon", + "flight_information": "Flyinformasjon", + "out_of_range": "Ikke i reiseplandatoer", + "preview": "ForhÃĨndsvisning", + "finding_recommendations": "Oppdager skjulte perler for ditt neste eventyr", + "location_details": "Stedsdetaljer", + "city": "By", + "region": "Region", + "md_instructions": "Skriv markdown her...", + "days": "dager", + "attachment_upload_success": "Vedlegg lastet opp!", + "attachment_upload_error": "Feil ved opplasting av vedlegg", + "upload": "Last opp", + "attachment_delete_success": "Vedlegg slettet!", + "attachment_update_success": "Vedlegg oppdatert!", + "attachment_name": "Vedleggsnavn", + "gpx_tip": "Last opp GPX-filer i vedlegg for ÃĨ se dem pÃĨ kartet!", + "welcome_map_info": "Offentlige eventyr pÃĨ denne serveren", + "attachment_update_error": "Feil ved oppdatering av vedlegg", + "activities": { + "general": "Generelt 🌍", + "outdoor": "Utendørs đŸžī¸", + "lodging": "Overnatting 🛌", + "dining": "Servering đŸŊī¸", + "activity": "Aktivitet 🏄", + "attraction": "Attraksjon đŸŽĸ", + "shopping": "Shopping đŸ›ī¸", + "nightlife": "Uteliv 🌃", + "event": "Arrangement 🎉", + "transportation": "Transport 🚗", + "culture": "Kultur 🎭", + "water_sports": "Vannsport 🚤", + "hiking": "Fotturer đŸĨž", + "wildlife": "Dyreliv đŸĻ’", + "historical_sites": "Historiske steder đŸ›ī¸", + "music_concerts": "Musikk og konserter đŸŽļ", + "fitness": "Trening đŸ‹ī¸", + "art_museums": "Kunst og museer 🎨", + "festivals": "Festivaler đŸŽĒ", + "spiritual_journeys": "Spirituelle reiser đŸ§˜â€â™€ī¸", + "volunteer_work": "Frivillig arbeid 🤝", + "other": "Annet" + }, + "lodging_information": "Overnattingsinformasjon", + "price": "Pris", + "reservation_number": "Reservasjonsnummer", + "additional_info": "Ytterligere informasjon", + "all_day": "Hele dagen", + "collection_no_start_end_date": "Å legge til en start- og sluttdato til samlingen vil lÃĨse opp reiseruteplanleggingsfunksjoner pÃĨ innsamlingssiden.", + "date_itinerary": "Dato reiserute", + "invalid_date_range": "Ugyldig datoomrÃĨde", + "no_ordered_items": "Legg til varer med datoer i samlingen for ÃĨ se dem her.", + "ordered_itinerary": "Bestilt reiserute", + "sunrise_sunset": "Soloppgang", + "timezone": "Tidssone", + "no_visits": "Ingen besøk", + "arrival_timezone": "Ankomst tidssone", + "departure_timezone": "Avgangstidssone", + "arrival_date": "Ankomstdato", + "departure_date": "Avgangsdato", + "coordinates": "Koordinater", + "copy_coordinates": "Kopier koordinater", + "sun_times": "Soltider", + "sunrise": "Soloppgang", + "sunset": "Solnedgang", + "timed": "Tidsbestemt", + "distance": "Avstand", + "all_linked_items": "Alle koblede varer", + "itinerary": "Reiserute", + "joined": "Ble med", + "view_profile": "Vis profil" + }, + "worldtravel": { + "country_list": "Liste over land", + "num_countries": "land funnet", + "all": "Alle", + "partially_visited": "Delvis besøkt", + "not_visited": "Ikke besøkt", + "completely_visited": "Fullstendig besøkt", + "all_subregions": "Alle underregioner", + "clear_search": "Tøm søk", + "no_countries_found": "Ingen land funnet", + "view_cities": "Vis byer", + "no_cities_found": "Ingen byer funnet", + "visit_to": "Besøk i", + "region_failed_visited": "Kunne ikke markere region som besøkt", + "failed_to_mark_visit": "Kunne ikke markere besøk i", + "visit_remove_failed": "Kunne ikke fjerne besøk", + "removed": "fjernet", + "failed_to_remove_visit": "Kunne ikke fjerne besøk i", + "marked_visited": "markert som besøkt", + "regions_in": "Regioner i", + "region_stats": "Regionstatistikk", + "all_visited": "Du har besøkt alle regionene i", + "cities": "byer" + }, + "auth": { + "username": "Brukernavn", + "password": "Passord", + "forgot_password": "Glemt passord?", + "signup": "Registrer deg", + "login_error": "Kan ikke logge inn med oppgitte legitimasjon.", + "login": "Logg inn", + "email": "E-post", + "first_name": "Fornavn", + "last_name": "Etternavn", + "confirm_password": "Bekreft passord", + "registration_disabled": "Registrering er for øyeblikket deaktivert.", + "profile_picture": "Profilbilde", + "public_profile": "Offentlig profil", + "public_tooltip": "Med en offentlig profil kan brukere dele samlinger med deg og se profilen din pÃĨ brukersiden.", + "email_required": "E-post kreves", + "new_password": "Nytt passord (6+ tegn)", + "both_passwords_required": "Begge passord er pÃĨkrevd", + "reset_failed": "Kunne ikke tilbakestille passord", + "or_3rd_party": "Eller logg inn med en tredjepartstjeneste", + "no_public_adventures": "Ingen offentlige eventyr funnet", + "no_public_collections": "Ingen offentlige samlinger funnet", + "user_adventures": "Brukerens eventyr", + "user_collections": "Brukerens samlinger" + }, + "users": { + "no_users_found": "Ingen brukere med offentlig profil funnet." + }, + "settings": { + "update_error": "Feil ved oppdatering av innstillinger", + "update_success": "Innstillinger oppdatert!", + "settings_page": "Innstillingsside", + "account_settings": "Brukerkontoinnstillinger", + "update": "Oppdater", + "no_verified_email_warning": "Du mÃĨ ha en verifisert e-postadresse for ÃĨ aktivere tofaktorautentisering.", + "password_change": "Bytt passord", + "new_password": "Nytt passord", + "confirm_new_password": "Bekreft nytt passord", + "email_change": "Bytt e-post", + "current_email": "NÃĨvÃĻrende e-post", + "no_email_set": "Ingen e-post angitt", + "new_email": "Ny e-post", + "change_password": "Bytt passord", + "login_redir": "Du blir da omdirigert til innloggingssiden.", + "token_required": "Token og UID kreves for tilbakestilling av passord.", + "reset_password": "Tilbakestill passord", + "possible_reset": "Hvis e-postadressen du oppga er knyttet til en konto, vil du motta en e-post med instruksjoner om ÃĨ tilbakestille passordet ditt!", + "missing_email": "Vennligst skriv inn en e-postadresse", + "submit": "Send inn", + "password_does_not_match": "Passordene samsvarer ikke", + "password_is_required": "Passord er pÃĨkrevd", + "invalid_token": "Token er ugyldig eller utløpt", + "about_this_background": "Om denne bakgrunnen", + "photo_by": "Foto av", + "join_discord": "Bli med pÃĨ Discord", + "join_discord_desc": "for ÃĨ dele dine egne bilder. Legg dem ut i #travel-share-kanalen.", + "current_password": "NÃĨvÃĻrende passord", + "change_password_error": "Kan ikke endre passord. Ugyldig nÃĨvÃĻrende passord eller ugyldig nytt passord.", + "password_change_lopout_warning": "Du vil bli logget ut etter ÃĨ ha endret passordet.", + "generic_error": "En feil oppsto under behandlingen av forespørselen din.", + "email_removed": "E-post fjernet!", + "email_removed_error": "Feil ved fjerning av e-post", + "verify_email_success": "E-postbekreftelse sendt!", + "verify_email_error": "Feil ved e-postbekreftelse. Prøv igjen om noen minutter.", + "email_added": "E-post lagt til!", + "email_added_error": "Feil ved legging til e-post", + "email_set_primary": "E-post satt som primÃĻr!", + "email_set_primary_error": "Feil ved innstilling av primÃĻr e-post", + "verified": "Verifisert", + "primary": "PrimÃĻr", + "not_verified": "Ikke verifisert", + "make_primary": "Gjør til primÃĻr", + "verify": "Verifiser", + "no_emai_set": "Ingen e-post angitt", + "error_change_password": "Feil ved endring av passord. Sjekk ditt nÃĨvÃĻrende passord og prøv igjen.", + "mfa_disabled": "Tofaktorautentisering er deaktivert!", + "mfa_page_title": "Tofaktorautentisering", + "enable_mfa": "Aktiver MFA", + "disable_mfa": "Deaktiver MFA", + "mfa_not_enabled": "MFA er ikke aktivert", + "mfa_enabled": "Tofaktorautentisering er aktivert!", + "copy": "Kopier", + "recovery_codes": "Gjenopprettingskoder", + "recovery_codes_desc": "Dette er dine gjenopprettingskoder. Oppbevar dem trygt. Du vil ikke kunne se dem igjen.", + "reset_session_error": "Logg ut og logg inn igjen for ÃĨ oppdatere økten din, og prøv igjen.", + "authenticator_code": "Autentiseringskode", + "email_verified": "E-post verifisert!", + "email_verified_success": "E-posten din er verifisert. Du kan nÃĨ logge inn.", + "email_verified_error": "Feil ved verifisering av e-post", + "email_verified_erorr_desc": "E-posten din kunne ikke verifiseres. Vennligst prøv igjen.", + "invalid_code": "Ugyldig MFA-kode", + "invalid_credentials": "Ugyldig brukernavn eller passord", + "mfa_required": "Tofaktorautentisering er pÃĨkrevd", + "required": "Dette feltet er pÃĨkrevd", + "add_email_blocked": "Du kan ikke legge til en e-postadresse pÃĨ en konto som er beskyttet av tofaktorautentisering.", + "duplicate_email": "Denne e-postadressen er allerede i bruk.", + "csrf_failed": "Kunne ikke hente CSRF-token", + "email_taken": "Denne e-postadressen er allerede i bruk.", + "username_taken": "Dette brukernavnet er allerede i bruk.", + "administration_settings": "Administrasjonsinnstillinger", + "launch_administration_panel": "Åpne administrasjonspanelet", + "social_oidc_auth": "Social og OIDC-autentisering", + "social_auth_desc": "Aktiver eller deaktiver sosiale og OIDC-autentiseringsleverandører for kontoen din. Disse koblingene lar deg logge inn med selvhostede autentiseringstjenester som Authentik eller tredjepartsleverandører som GitHub.", + "social_auth_desc_2": "Disse innstillingene administreres pÃĨ AdventureLog-serveren og mÃĨ aktiveres manuelt av administratoren.", + "documentation_link": "Dokumentasjonslenke", + "launch_account_connections": "Åpne kontotilkoblinger", + "password_too_short": "Passordet mÃĨ vÃĻre minst 6 tegn", + "add_email": "Legg til e-post", + "password_disable": "Deaktiver passordautentisering", + "password_disable_desc": "Å deaktivere passordautentisering vil hindre deg fra ÃĨ logge inn med et passord. Du mÃĨ bruke en sosial eller OIDC-leverandør for ÃĨ logge inn. Skulle leverandøren din fjernes, vil passordautentisering automatisk bli gjenaktivert, selv om denne innstillingen er deaktivert.", + "disable_password": "Deaktiver passord", + "password_enabled": "Passordautentisering er aktivert", + "password_disabled": "Passordautentisering er deaktivert", + "password_disable_warning": "Akkurat nÃĨ er passordautentisering deaktivert. Innlogging via en sosial eller OIDC-leverandør er pÃĨkrevd.", + "password_disabled_error": "Feil ved deaktivering av passordautentisering. Sørg for at en sosial eller OIDC-leverandør er koblet til kontoen din.", + "password_enabled_error": "Feil ved aktivering av passordautentisering.", + "access_restricted": "Tilgang begrenset", + "access_restricted_desc": "Yadministative funksjoner er bare tilgjengelige for ansatte.", + "add_new_email": "Legg til ny e -post", + "add_new_email_address": "Legg til ny e -postadresse", + "admin": "Admin", + "admin_panel_desc": "FÃĨ tilgang til hele administrasjonsgrensesnittet", + "administration": "Administrasjon", + "administration_desc": "Administrative verktøy og innstillinger", + "advanced": "Avansert", + "advanced_settings": "Avanserte innstillinger", + "advanced_settings_desc": "Avanserte konfigurasjons- og utviklingsverktøy", + "all_rights_reserved": "Alle rettigheter forbeholdt.", + "app_version": "Appversjon", + "confirm_new_password_desc": "Bekreft nytt passord", + "connected": "Tilkoblet", + "debug_information": "Feilsøkingsinformasjon", + "disabled": "Funksjonshemmet", + "disconnected": "Koblet fra", + "email_management": "E -postadministrasjon", + "email_management_desc": "Administrer e -postadressene og bekreftelsesstatusen", + "emails": "E -post", + "enabled": "Aktivert", + "enter_current_password": "Skriv inn gjeldende passord", + "enter_first_name": "Skriv inn fornavnet ditt", + "enter_last_name": "Skriv inn etternavnet ditt", + "enter_new_email": "Skriv inn ny e -postadresse", + "enter_new_password": "Skriv inn nytt passord", + "enter_username": "Skriv inn brukernavnet ditt", + "integrations": "Integrasjoner", + "integrations_desc": "Koble eksterne tjenester for ÃĨ forbedre opplevelsen din", + "license": "Tillatelse", + "mfa_desc": "Legg til et ekstra lag med sikkerhet til kontoen din", + "mfa_is_enabled": "MFA er aktivert", + "pass_change_desc": "Oppdater kontopassordet ditt for bedre sikkerhet", + "password_auth": "Passordautentisering", + "password_login_disabled": "Passordinnloggingsaktivert", + "password_login_enabled": "Passordinnlogging er aktivert", + "profile_info": "Profilinformasjon", + "profile_info_desc": "Oppdater dine personlige detaljer og profilbilde", + "public_profile_desc": "Gjør profilen din synlig for andre brukere", + "quick_actions": "Raske handlinger", + "region_updates": "Regionoppdateringer", + "region_updates_desc": "Oppdatering besøkte regioner og byer", + "regular_user": "Vanlig bruker", + "security": "Sikkerhet", + "settings_menu": "Innstillingsmenyen", + "social_auth": "Sosial godkjenning", + "social_auth_desc_1": "Administrer sosiale pÃĨloggingsalternativer og passordinnstillinger", + "social_auth_setup": "Sosial autentiseringsoppsett", + "staff_status": "Personalstatus", + "staff_user": "Personalbruker" + }, + "collection": { + "collection_created": "Samling opprettet!", + "error_creating_collection": "Feil ved oppretting av samling", + "new_collection": "Ny samling", + "create": "Opprett", + "collection_edit_success": "Samling redigert!", + "error_editing_collection": "Feil ved redigering av samling", + "edit_collection": "Rediger samling", + "public_collection": "Offentlig samling" + }, + "notes": { + "note_deleted": "Notat slettet!", + "note_delete_error": "Feil ved sletting av notat", + "open": "Åpne", + "failed_to_save": "Kunne ikke lagre notat", + "note_editor": "Notatredigerer", + "note_viewer": "Notatviser", + "editing_note": "Redigerer notat", + "content": "Innhold", + "save": "Lagre", + "note_public": "Dette notatet er offentlig fordi det er i en offentlig samling.", + "add_a_link": "Legg til en lenke", + "invalid_url": "Ugyldig URL" + }, + "checklist": { + "checklist_deleted": "Sjekkliste slettet!", + "checklist_delete_error": "Feil ved sletting av sjekkliste", + "failed_to_save": "Kunne ikke lagre sjekkliste", + "checklist_editor": "Sjekklisteredigerer", + "checklist_viewer": "Sjekklisteviser", + "editing_checklist": "Redigerer sjekkliste", + "new_checklist": "Ny sjekkliste", + "item": "Punkt", + "items": "Punkter", + "add_item": "Legg til punkt", + "new_item": "Nytt punkt", + "save": "Lagre", + "checklist_public": "Denne sjekklisten er offentlig fordi den er i en offentlig samling.", + "item_cannot_be_empty": "Punktet kan ikke vÃĻre tomt", + "item_already_exists": "Punktet finnes allerede" + }, + "transportation": { + "transportation_deleted": "Transport slettet!", + "transportation_delete_error": "Feil ved sletting av transport", + "provide_start_date": "Vennligst angi en startdato", + "transport_type": "Transporttype", + "type": "Type", + "transportation_added": "Transport lagt til!", + "error_editing_transportation": "Feil ved redigering av transport", + "new_transportation": "Ny transport", + "date_time": "Startdato og -tid", + "end_date_time": "Sluttdato og -tid", + "flight_number": "Flynummer", + "from_location": "Fra sted", + "to_location": "Til sted", + "fetch_location_information": "Hent stedsinformasjon", + "starting_airport_desc": "Skriv inn avreiseflyplasskode (f.eks. JFK)", + "ending_airport_desc": "Skriv inn ankomsflyplasskode (f.eks. LAX)", + "edit": "Rediger", + "modes": { + "car": "Bil", + "plane": "Fly", + "train": "Tog", + "bus": "Buss", + "boat": "BÃĨt", + "bike": "Sykkel", + "walking": "GÃĨr", + "other": "Annet" + }, + "transportation_edit_success": "Transport redigert!", + "edit_transportation": "Rediger transport", + "start": "Start", + "date_and_time": "Dato og tid" + }, + "lodging": { + "lodging_deleted": "Overnatting slettet!", + "lodging_delete_error": "Feil ved sletting av overnatting", + "provide_start_date": "Vennligst angi en startdato", + "lodging_type": "Overnattingstype", + "type": "Type", + "lodging_added": "Overnatting lagt til!", + "error_editing_lodging": "Feil ved redigering av overnatting", + "new_lodging": "Ny overnatting", + "check_in": "Innsjekking", + "check_out": "Utsjekking", + "edit": "Rediger", + "lodging_edit_success": "Overnatting redigert!", + "edit_lodging": "Rediger overnatting", + "start": "Start", + "date_and_time": "Dato og tid", + "hotel": "Hotell", + "hostel": "Hostell", + "resort": "Resort", + "bnb": "Bed & Breakfast", + "campground": "Campingplass", + "cabin": "Hytte", + "apartment": "Leilighet", + "house": "Hus", + "villa": "Villa", + "motel": "Motell", + "other": "Annet", + "reservation_number": "Reservasjonsnummer", + "current_timezone": "Gjeldende tidssone" + }, + "search": { + "adventurelog_results": "AdventureLog-resultater", + "public_adventures": "Offentlige eventyr", + "online_results": "Nettresultater" + }, + "map": { + "view_details": "Vis detaljer", + "adventure_map": "Eventyrkart", + "map_options": "Kartalternativer", + "show_visited_regions": "Vis besøkte regioner", + "add_adventure_at_marker": "Legg til nytt eventyr ved markøren", + "clear_marker": "Fjern markør", + "add_adventure": "Legg til nytt eventyr" + }, + "share": { + "shared": "Delt", + "with": "med", + "unshared": "Udelt", + "share_desc": "Del denne samlingen med andre brukere.", + "shared_with": "Delt med", + "no_users_shared": "Ingen brukere delt med", + "not_shared_with": "Ikke delt med", + "no_shared_found": "Ingen samlinger funnet som er delt med deg.", + "set_public": "For ÃĨ la brukere dele med deg, mÃĨ profilen din vÃĻre offentlig.", + "go_to_settings": "GÃĨ til innstillinger" + }, + "languages": {}, + "profile": { + "member_since": "Medlem siden", + "user_stats": "Brukerstatistikk", + "visited_countries": "Besøkte land", + "visited_regions": "Besøkte regioner", + "visited_cities": "Besøkte byer" + }, + "categories": { + "manage_categories": "Administrer kategorier", + "no_categories_found": "Ingen kategorier funnet.", + "edit_category": "Rediger kategori", + "icon": "Ikon", + "update_after_refresh": "Eventyrkortene vil oppdateres nÃĨr du oppdaterer siden.", + "select_category": "Velg kategori", + "category_name": "Kategorinavn", + "add_category": "Legg til kategori", + "add_new_category": "Legg til ny kategori" + }, + "dashboard": { + "welcome_back": "Velkommen tilbake", + "countries_visited": "Land besøkt", + "total_adventures": "Totalt antall eventyr", + "total_visited_regions": "Totalt antall besøkte regioner", + "total_visited_cities": "Totalt antall besøkte byer", + "recent_adventures": "Nylige eventyr", + "no_recent_adventures": "Ingen nylige eventyr?", + "add_some": "Hvorfor ikke begynne ÃĨ planlegge ditt neste eventyr? Du kan legge til et nytt eventyr ved ÃĨ klikke pÃĨ knappen nedenfor." + }, + "immich": { + "immich": "Immich", + "integration_fetch_error": "Feil ved henting av data fra Immich-integrasjonen", + "integration_missing": "Immich-integrasjonen mangler pÃĨ backend", + "query_required": "Forespørsel er pÃĨkrevd", + "server_down": "Immich-serveren er nede eller utilgjengelig", + "no_items_found": "Ingen elementer funnet", + "imageid_required": "Bilde-ID er pÃĨkrevd", + "load_more": "Last mer", + "immich_updated": "Immich-innstillinger oppdatert!", + "immich_enabled": "Immich-integrasjon aktivert!", + "immich_error": "Feil ved oppdatering av Immich-integrasjon", + "immich_disabled": "Immich-integrasjon deaktivert!", + "immich_desc": "Integrer Immich-kontoen din med AdventureLog for ÃĨ søke i bildebiblioteket ditt og importere bilder til eventyrene dine.", + "integration_enabled": "Integrasjon aktivert", + "disable": "Deaktiver", + "server_url": "Immich-server-URL", + "api_note": "Merk: dette mÃĨ vÃĻre URL-en til Immich API-serveren, sÃĨ den slutter sannsynligvis med /api, med mindre du har en tilpasset konfig.", + "api_key": "Immich API-nøkkel", + "enable_immich": "Aktiver Immich", + "update_integration": "Oppdater integrasjon", + "immich_integration": "Immich-integrasjon", + "localhost_note": "Merk: localhost vil sannsynligvis ikke fungere med mindre du har satt opp docker-nettverk. Det anbefales ÃĨ bruke serverens IP-adresse eller domenenavn.", + "documentation": "Immich-integrasjonsdokumentasjon", + "api_key_placeholder": "Skriv inn Immich API -tasten", + "enable_integration": "Aktiver integrasjon", + "immich_integration_desc": "Koble til Immich Photo Management Server", + "need_help": "Trenger du hjelp til ÃĨ sette opp dette? \nSjekk ut", + "connection_error": "Feil tilkobling til Immich Server", + "copy_locally": "Kopier bilder lokalt", + "copy_locally_desc": "Kopier bilder til serveren for offline tilgang. \nBruker mer diskplass.", + "error_saving_image": "Feil ÃĨ lagre bilde", + "integration_already_exists": "En Immich -integrasjon eksisterer allerede. \nDu kan bare ha en integrasjon om gangen.", + "integration_not_found": "Immich -integrasjon ikke funnet. \nOpprett en ny integrasjon.", + "network_error": "Nettverksfeil mens du kobler til Immich -serveren. \nVennligst sjekk tilkoblingen din og prøv igjen.", + "validation_error": "Det oppstod en feil under validering av Immich -integrasjonen. \nVennligst sjekk server -URL -en og API -tasten." + }, + "recomendations": { + "address": "Adresse", + "phone": "Telefon", + "contact": "Kontakt", + "website": "Nettsted", + "recommendation": "Anbefaling", + "recommendations": "Anbefalinger", + "adventure_recommendations": "Eventyranbefalinger", + "food": "Mat", + "miles": "Miles", + "tourism": "Turisme" + }, + "google_maps": { + "google_maps_integration_desc": "Koble til Google Maps-kontoen din for ÃĨ fÃĨ søkeresultater og anbefalinger av høy kvalitet." + } +} diff --git a/frontend/src/locales/pl.json b/frontend/src/locales/pl.json index 3925de13..d5e0eeb9 100644 --- a/frontend/src/locales/pl.json +++ b/frontend/src/locales/pl.json @@ -42,7 +42,8 @@ "nominatim_1": "Wyszukiwanie lokalizacji i geokodowanie zapewnia", "nominatim_2": "Ich dane są licencjonowane na licencji ODbL.", "other_attributions": "Dodatkowe atrybucje moÅŧna znaleÅēć w pliku README.", - "close": "Zamknij" + "close": "Zamknij", + "generic_attributions": "Zaloguj się do Adventurelog, aby wyświetlić atrybucje dla włączonych integracji i usług." }, "home": { "hero_1": "Odkryj najbardziej ekscytujące podrÃŗÅŧe na świecie", @@ -295,7 +296,32 @@ "region": "Region", "reservation_number": "Numer rezerwacji", "welcome_map_info": "Publiczne przygody na tym serwerze", - "open_in_maps": "Otwarte w mapach" + "open_in_maps": "Otwarte w mapach", + "all_day": "Cały dzień", + "collection_no_start_end_date": "Dodanie daty rozpoczęcia i końca do kolekcji odblokuje funkcje planowania planu podrÃŗÅŧy na stronie kolekcji.", + "date_itinerary": "Trasa daty", + "no_ordered_items": "Dodaj przedmioty z datami do kolekcji, aby je zobaczyć tutaj.", + "ordered_itinerary": "ZamÃŗwiono trasę", + "additional_info": "Dodatkowe informacje", + "invalid_date_range": "Niepoprawny zakres dat", + "sunrise_sunset": "WschÃŗd słońca", + "timezone": "Strefa czasowa", + "no_visits": "Brak wizyt", + "arrival_timezone": "Strefa czasowa przyjazdu", + "departure_timezone": "Strefa czasowa odlotu", + "arrival_date": "Data przyjazdu", + "departure_date": "Data wyjazdu", + "coordinates": "WspÃŗÅ‚rzędne", + "copy_coordinates": "Kopiuj wspÃŗÅ‚rzędne", + "sun_times": "Czasy słońca", + "sunrise": "WschÃŗd słońca", + "sunset": "ZachÃŗd słońca", + "timed": "Czas", + "distance": "Dystans", + "all_linked_items": "Wszystkie połączone elementy", + "itinerary": "Trasa", + "joined": "Dołączył", + "view_profile": "Zobacz profil" }, "worldtravel": { "country_list": "Lista krajÃŗw", @@ -436,7 +462,58 @@ "password_disabled": "Uwierzytelnianie hasła wyłączone", "password_disabled_error": "Błąd wyłączający uwierzytelnianie hasła. \nUpewnij się, Åŧe dostawca społecznościowy lub OIDC jest powiązany z Twoim kontem.", "password_enabled": "Włączone uwierzytelnianie hasła", - "password_enabled_error": "Błąd umoÅŧliwiający uwierzytelnianie hasła." + "password_enabled_error": "Błąd umoÅŧliwiający uwierzytelnianie hasła.", + "access_restricted": "Dostęp do ograniczenia", + "access_restricted_desc": "Funkcje jadminarne są dostępne tylko dla pracownikÃŗw.", + "add_new_email": "Dodaj nowy e -mail", + "add_new_email_address": "Dodaj nowy adres e -mail", + "admin": "Admin", + "admin_panel_desc": "Uzyskaj dostęp do pełnego interfejsu administracyjnego", + "administration": "Administracja", + "administration_desc": "Narzędzia administracyjne i ustawienia", + "advanced": "Zaawansowany", + "advanced_settings": "Zaawansowane ustawienia", + "advanced_settings_desc": "Zaawansowane narzędzia konfiguracyjne i programistyczne", + "all_rights_reserved": "Wszelkie prawa zastrzeÅŧone.", + "app_version": "Wersja aplikacji", + "confirm_new_password_desc": "PotwierdÅē nowe hasło", + "connected": "Połączony", + "debug_information": "Informacje o debugowaniu", + "disabled": "Wyłączony", + "disconnected": "Bezładny", + "email_management": "Zarządzanie e -mail", + "email_management_desc": "Zarządzaj adresami e -mail i statusem weryfikacji", + "emails": "E -maile", + "enabled": "Włączony", + "enter_current_password": "WprowadÅē bieÅŧące hasło", + "enter_first_name": "WprowadÅē swoje imię", + "enter_last_name": "WprowadÅē swoje nazwisko", + "enter_new_email": "WprowadÅē nowy adres e -mail", + "enter_new_password": "WprowadÅē nowe hasło", + "enter_username": "WprowadÅē swoją nazwę uÅŧytkownika", + "integrations": "Integracje", + "integrations_desc": "Połącz usługi zewnętrzne, aby ulepszyć swoje wraÅŧenia", + "license": "Licencja", + "mfa_desc": "Dodaj dodatkową warstwę bezpieczeństwa na swoje konto", + "mfa_is_enabled": "MFA jest włączona", + "pass_change_desc": "Zaktualizuj hasło konta, aby uzyskać lepsze bezpieczeństwo", + "password_auth": "Uwierzytelnianie hasła", + "password_login_disabled": "Wyłączanie logowania hasła", + "password_login_enabled": "Włączone logowanie hasła", + "profile_info": "Informacje o profilu", + "profile_info_desc": "Zaktualizuj swoje dane osobowe i zdjęcie profilowe", + "public_profile_desc": "Spraw, aby TwÃŗj profil był widoczny dla innych uÅŧytkownikÃŗw", + "quick_actions": "Szybkie działania", + "region_updates": "Aktualizacje regionu", + "region_updates_desc": "Aktualizacja odwiedzonych regionÃŗw i miast", + "regular_user": "Zwykły uÅŧytkownik", + "security": "Bezpieczeństwo", + "settings_menu": "Menu Ustawienia", + "social_auth": "Uwierzytelnianie społeczne", + "social_auth_desc_1": "Zarządzaj opcjami logowania społecznościowego i ustawieniami haseł", + "social_auth_setup": "Konfiguracja uwierzytelniania społecznego", + "staff_status": "Status personelu", + "staff_user": "UÅŧytkownik personelu" }, "collection": { "collection_created": "Kolekcja została pomyślnie utworzona!", @@ -553,7 +630,9 @@ "icon": "Ikona", "update_after_refresh": "Karty podrÃŗÅŧy zostaną zaktualizowane po odświeÅŧeniu strony.", "select_category": "Wybierz kategorię", - "category_name": "Nazwa kategorii" + "category_name": "Nazwa kategorii", + "add_category": "Dodaj kategorię", + "add_new_category": "Dodaj nową kategorię" }, "dashboard": { "add_some": "Dlaczego nie zacząć planować kolejnej przygody? \nMoÅŧesz dodać nową przygodę, klikając przycisk poniÅŧej.", @@ -588,14 +667,31 @@ "immich_desc": "Zintegruj swoje konto Immich z AdventureLog, aby mÃŗc przeszukiwać bibliotekę zdjęć i importować zdjęcia do swoich przygÃŗd.", "immich_disabled": "Integracja z Immich została pomyślnie wyłączona!", "documentation": "Dokumentacja integracji Immicha", - "localhost_note": "Uwaga: localhost najprawdopodobniej nie będzie działać, jeśli nie skonfigurujesz odpowiednio sieci dokerÃŗw. \nZalecane jest uÅŧycie adresu IP serwera lub nazwy domeny." + "localhost_note": "Uwaga: localhost najprawdopodobniej nie będzie działać, jeśli nie skonfigurujesz odpowiednio sieci dokerÃŗw. \nZalecane jest uÅŧycie adresu IP serwera lub nazwy domeny.", + "api_key_placeholder": "WprowadÅē swÃŗj klucz API IMMICH", + "enable_integration": "Włącz integrację", + "immich_integration_desc": "Połącz swÃŗj serwer zarządzania zdjęciami Immich", + "need_help": "Potrzebujesz pomocy w konfiguracji? \nSprawdÅē", + "connection_error": "Błąd łączący się z serwerem Immich", + "copy_locally": "Kopiuj obrazy lokalnie", + "copy_locally_desc": "Skopiuj obrazy na serwer, aby uzyskać dostęp offline. \nWykorzystuje więcej miejsca na dysku.", + "error_saving_image": "Obraz zapisujący błąd", + "integration_already_exists": "Immichu jest juÅŧ integracja i immich. \nMoÅŧesz mieć tylko jedną integrację na raz.", + "integration_not_found": "Nie znaleziono integracji imich. \nUtwÃŗrz nową integrację.", + "network_error": "Błąd sieci podczas łączenia się z serwerem IMMICH. \nSprawdÅē połączenie i sprÃŗbuj ponownie.", + "validation_error": "Wystąpił błąd podczas walidacji integracji immicha. \nSprawdÅē swÃŗj adres URL serwera i klawisz API." }, "recomendations": { "address": "Adres", "contact": "Kontakt", "phone": "Telefon", "recommendation": "Zalecenie", - "website": "Strona internetowa" + "website": "Strona internetowa", + "recommendations": "Zalecenia", + "adventure_recommendations": "Zalecenia przygodowe", + "food": "Åģywność", + "miles": "Mil", + "tourism": "Turystyka" }, "lodging": { "apartment": "Apartament", @@ -626,5 +722,8 @@ "reservation_number": "Numer rezerwacji", "resort": "Uciec", "current_timezone": "Obecna strefa czasowa" + }, + "google_maps": { + "google_maps_integration_desc": "Połącz swoje konto Google Maps, aby uzyskać wysokiej jakości wyniki wyszukiwania i zalecenia dotyczące lokalizacji." } } diff --git a/frontend/src/locales/sv.json b/frontend/src/locales/sv.json index c6fd2000..d3388e8c 100644 --- a/frontend/src/locales/sv.json +++ b/frontend/src/locales/sv.json @@ -8,7 +8,8 @@ "nominatim_2": "Deras data är licensierad under ODbL-licensen.", "oss_attributions": "Tillskrivningar med Ãļppen källkod", "other_attributions": "Ytterligare attributioner finns i README-filen.", - "source_code": "Källkod" + "source_code": "Källkod", + "generic_attributions": "Logga in pÃĨ AdventureLog fÃļr att visa attribut fÃļr aktiverade integrationer och tjänster." }, "adventures": { "activities": { @@ -247,7 +248,32 @@ "price": "Pris", "region": "OmrÃĨde", "reservation_number": "Bokningsnummer", - "open_in_maps": "Kappas in" + "open_in_maps": "Kappas in", + "all_day": "Hela dagen", + "collection_no_start_end_date": "Att lägga till ett start- och slutdatum till samlingen kommer att lÃĨsa upp planeringsfunktioner fÃļr resplan pÃĨ insamlingssidan.", + "date_itinerary": "Datum resplan", + "no_ordered_items": "Lägg till objekt med datum i samlingen fÃļr att se dem här.", + "ordered_itinerary": "Beställd resplan", + "additional_info": "Ytterligare information", + "invalid_date_range": "Ogiltigt datumintervall", + "sunrise_sunset": "SoluppgÃĨng", + "timezone": "Tidszon", + "no_visits": "Inga besÃļk", + "arrival_timezone": "Ankomsttidszon", + "departure_timezone": "AvgÃĨngstidszon", + "arrival_date": "Ankomstdatum", + "departure_date": "AvgÃĨngsdatum", + "coordinates": "Koordinater", + "copy_coordinates": "Kopieringskoordinater", + "sun_times": "Soltider", + "sunrise": "SoluppgÃĨng", + "sunset": "SolnedgÃĨng", + "timed": "Tidsinställd", + "distance": "AvstÃĨnd", + "all_linked_items": "Alla länkade objekt", + "itinerary": "Resväg", + "joined": "Gick med i", + "view_profile": "Visa profil" }, "home": { "desc_1": "Upptäck, planera och utforska med lätthet", @@ -436,7 +462,58 @@ "password_disabled": "LÃļsenordsautentisering inaktiverad", "password_disabled_error": "Fel Inaktivera lÃļsenordsautentisering. \nSe till att en social eller OIDC -leverantÃļr är länkad till ditt konto.", "password_enabled": "LÃļsenordsautentisering aktiverad", - "password_enabled_error": "Fel som aktiverar lÃļsenordsautentisering." + "password_enabled_error": "Fel som aktiverar lÃļsenordsautentisering.", + "access_restricted": "TillgÃĨng begränsad", + "access_restricted_desc": "Yadministrativa funktioner är endast tillgängliga fÃļr personal.", + "add_new_email": "Lägg till nytt e -postmeddelande", + "add_new_email_address": "Lägg till ny e -postadress", + "admin": "Administration", + "admin_panel_desc": "Åtkomst till hela administrationsgränssnittet", + "administration": "Administration", + "administration_desc": "Administrativa verktyg och inställningar", + "advanced": "Avancerad", + "advanced_settings": "Avancerade inställningar", + "advanced_settings_desc": "Avancerade konfigurations- och utvecklingsverktyg", + "all_rights_reserved": "Alla rättigheter reserverade.", + "app_version": "Appversion", + "confirm_new_password_desc": "Bekräfta nytt lÃļsenord", + "connected": "Ansluten", + "debug_information": "FelsÃļkningsinformation", + "disabled": "Funktionshindrad", + "disconnected": "Osammanhängande", + "email_management": "E -posthantering", + "email_management_desc": "Hantera dina e -postadresser och verifieringsstatus", + "emails": "E -postmeddelanden", + "enabled": "Aktiverad", + "enter_current_password": "Ange aktuellt lÃļsenord", + "enter_first_name": "Ange ditt fÃļrnamn", + "enter_last_name": "Ange ditt efternamn", + "enter_new_email": "Ange en ny e -postadress", + "enter_new_password": "Ange ett nytt lÃļsenord", + "enter_username": "Ange ditt användarnamn", + "integrations": "Integrationer", + "integrations_desc": "Anslut externa tjänster fÃļr att fÃļrbättra din upplevelse", + "license": "Licens", + "mfa_desc": "Lägg till ett extra lager av säkerhet till ditt konto", + "mfa_is_enabled": "MFA är aktiverad", + "pass_change_desc": "Uppdatera ditt kontolÃļsenord fÃļr bättre säkerhet", + "password_auth": "LÃļsenordsautentisering", + "password_login_disabled": "LÃļsenordsinloggning inaktiverad", + "password_login_enabled": "LÃļsenordsinloggning aktiverad", + "profile_info": "Profilinformation", + "profile_info_desc": "Uppdatera dina personliga uppgifter och profilbild", + "public_profile_desc": "GÃļr din profil synlig fÃļr andra användare", + "quick_actions": "SnabbÃĨtgärder", + "region_updates": "Regionuppdateringar", + "region_updates_desc": "Uppdatera besÃļkta regioner och städer", + "regular_user": "Vanlig användare", + "security": "Säkerhet", + "settings_menu": "Inställningsmeny", + "social_auth": "Social autentisering", + "social_auth_desc_1": "Hantera sociala inloggningsalternativ och lÃļsenordsinställningar", + "social_auth_setup": "Social autentiseringsinställning", + "staff_status": "Personalstatus", + "staff_user": "Personalanvändare" }, "checklist": { "add_item": "Lägg till objekt", @@ -553,7 +630,9 @@ "manage_categories": "Hantera kategorier", "no_categories_found": "Inga kategorier hittades.", "select_category": "Välj Kategori", - "update_after_refresh": "Äventyrskorten kommer att uppdateras när du uppdaterar sidan." + "update_after_refresh": "Äventyrskorten kommer att uppdateras när du uppdaterar sidan.", + "add_category": "Lägg till kategori", + "add_new_category": "Lägg till en ny kategori" }, "dashboard": { "add_some": "VarfÃļr inte bÃļrja planera ditt nästa äventyr? \nDu kan lägga till ett nytt äventyr genom att klicka pÃĨ knappen nedan.", @@ -588,14 +667,31 @@ "server_url": "Immich Server URL", "update_integration": "Uppdatera integration", "documentation": "Immich Integrationsdokumentation", - "localhost_note": "Obs: localhost kommer sannolikt inte att fungera om du inte har konfigurerat docker-nätverk i enlighet med detta. \nDet rekommenderas att använda serverns IP-adress eller domännamnet." + "localhost_note": "Obs: localhost kommer sannolikt inte att fungera om du inte har konfigurerat docker-nätverk i enlighet med detta. \nDet rekommenderas att använda serverns IP-adress eller domännamnet.", + "api_key_placeholder": "Ange din immich API -nyckel", + "enable_integration": "Aktivera integration", + "immich_integration_desc": "Anslut din immich fotohanteringsserver", + "need_help": "BehÃļver du hjälp med att ställa in detta? \nKolla in", + "connection_error": "Fel som ansluter till Imchich Server", + "copy_locally": "Kopiera bilder lokalt", + "copy_locally_desc": "Kopiera bilder till servern fÃļr offlineÃĨtkomst. \nAnvänder mer diskutrymme.", + "error_saving_image": "Felbesparande bild", + "integration_already_exists": "En immich integration finns redan. \nDu kan bara ha en integration ÃĨt gÃĨngen.", + "integration_not_found": "Imkik integration hittades inte. \nSkapa en ny integration.", + "network_error": "Nätverksfel vid anslutning till den immich servern. \nKontrollera din anslutning och fÃļrsÃļk igen.", + "validation_error": "Ett fel inträffade vid validering av den immich integrationen. \nKontrollera din server -URL- och API -nyckel." }, "recomendations": { "address": "Adress", "contact": "Kontakta", "phone": "Telefon", "recommendation": "Rekommendation", - "website": "Webbplats" + "website": "Webbplats", + "recommendations": "Rekommendationer", + "adventure_recommendations": "Äventyrsrekommendationer", + "food": "Mat", + "miles": "Miles", + "tourism": "Turism" }, "lodging": { "apartment": "Lägenhet", @@ -626,5 +722,8 @@ "edit": "Redigera", "edit_lodging": "Redigera logi", "current_timezone": "Nuvarande tidszon" + }, + "google_maps": { + "google_maps_integration_desc": "Anslut ditt Google Maps-konto fÃļr att fÃĨ sÃļkresultat och rekommendationer av hÃļg kvalitet." } } diff --git a/frontend/src/locales/zh.json b/frontend/src/locales/zh.json index 864d6993..8da865c5 100644 --- a/frontend/src/locales/zh.json +++ b/frontend/src/locales/zh.json @@ -42,7 +42,8 @@ "oss_attributions": "åŧ€æēåŖ°æ˜Ž", "other_attributions": "å…ļäģ–åŖ°æ˜Žå¯äģĨ在 README 文äģļ中扞到。", "source_code": "æēäģŖį ", - "close": "å…ŗé—­" + "close": "å…ŗé—­", + "generic_attributions": "į™ģåŊ•到AdventureLogäģĨæŸĨįœ‹å¯į”¨é›†æˆå’ŒæœåŠĄįš„åŊ’因。" }, "home": { "desc_1": "čŊģæžå‘įŽ°ã€č§„åˆ’å’ŒæŽĸį´ĸ", @@ -295,7 +296,32 @@ "lodging_information": "äŊåŽŋäŋĄæ¯", "price": "ä쎿 ŧ", "reservation_number": "éĸ„čŽĸåˇ", - "open_in_maps": "在地回上打åŧ€" + "open_in_maps": "在地回上打åŧ€", + "all_day": "整夊", + "collection_no_start_end_date": "在集合éĄĩéĸ中æˇģ加åŧ€å§‹æ—Ĩ期和į쓿Ÿæ—Ĩ期将在“æ”ļ集”éĄĩéĸä¸­č§Ŗé”čĄŒį¨‹čŽĄåˆ’åŠŸčƒŊ。", + "date_itinerary": "æ—ĨæœŸčĄŒį¨‹", + "no_ordered_items": "将å¸Ļ有æ—ĨæœŸįš„éĄšį›Žæˇģ加到集合中īŧŒäģĨäžŋ在此处æŸĨįœ‹åŽƒäģŦ。", + "ordered_itinerary": "čŽĸč´­äē†čĄŒį¨‹", + "additional_info": "附加äŋĄæ¯", + "invalid_date_range": "æ— æ•ˆįš„æ—ĨæœŸčŒƒå›´", + "sunrise_sunset": "æ—Ĩå‡ē", + "timezone": "æ—ļåŒē", + "no_visits": "æ˛Ąæœ‰čŽŋ问", + "arrival_timezone": "åˆ°čžžæ—ļåŒē", + "departure_timezone": "įĻģåŧ€æ—ļåŒē", + "arrival_date": "åˆ°čžžæ—Ĩ期", + "departure_date": "å‡ē发æ—Ĩ期", + "coordinates": "坐标", + "copy_coordinates": "复åˆļ坐标", + "sun_times": "é˜ŗå…‰æ—ļäģŖ", + "sunrise": "æ—Ĩå‡ē", + "sunset": "æ—ĨčŊ", + "timed": "æ—ļ间", + "distance": "距įĻģ", + "all_linked_items": "所有铞æŽĨįš„éĄšį›Ž", + "itinerary": "čĄŒį¨‹", + "joined": "加å…Ĩ", + "view_profile": "æŸĨįœ‹ä¸Ēäēēčĩ„æ–™" }, "auth": { "forgot_password": "åŋ˜čް坆᠁īŧŸ", @@ -436,7 +462,58 @@ "password_disabled": "坆᠁čēĢäģŊénj蝁įρᔍ", "password_disabled_error": "错蝝įρᔍ坆᠁čēĢäģŊéĒŒč¯ã€‚\nįĄŽäŋå°†į¤žä礿ˆ–OIDC提䞛商铞æŽĨåˆ°æ‚¨įš„å¸æˆˇã€‚", "password_enabled": "吝ᔍ坆᠁čēĢäģŊénj蝁", - "password_enabled_error": "吝ᔍ坆᠁čēĢäģŊéĒŒč¯įš„é”™č¯¯ã€‚" + "password_enabled_error": "吝ᔍ坆᠁čēĢäģŊéĒŒč¯įš„é”™č¯¯ã€‚", + "access_restricted": "čŽŋ闎受限åˆļ", + "access_restricted_desc": "Yadministrative功čƒŊäģ…适ᔍäēŽåˇĨäŊœäēē员。", + "add_new_email": "æˇģ加新į”ĩ子邎äģļ", + "add_new_email_address": "æˇģåŠ æ–°įš„į”ĩ子邎äģļ地址", + "admin": "行æ”ŋ", + "admin_panel_desc": "čŽŋé—ŽåŽŒæ•´įš„įŽĄį†æŽĨåŖ", + "administration": "行æ”ŋ", + "administration_desc": "įŽĄį†åˇĨå…ˇå’ŒčŽžįŊŽ", + "advanced": "先čŋ›įš„", + "advanced_settings": "é̘įē§čŽžįŊŽ", + "advanced_settings_desc": "é̘įē§é…įŊŽå’Œåŧ€å‘åˇĨå…ˇ", + "all_rights_reserved": "į‰ˆæƒæ‰€æœ‰ã€‚", + "app_version": "åē”į”¨į‰ˆæœŦ", + "confirm_new_password_desc": "įĄŽčŽ¤æ–°å¯†į ", + "connected": "čŋžæŽĨ", + "debug_information": "č°ƒč¯•äŋĄæ¯", + "disabled": "įρᔍ", + "disconnected": "断åŧ€čŋžæŽĨ", + "email_management": "į”ĩ子邎äģļįŽĄį†", + "email_management_desc": "įŽĄį†æ‚¨įš„į”ĩ子邎äģļ地址和énj蝁įŠļ态", + "emails": "į”ĩ子邎äģļ", + "enabled": "吝ᔍ", + "enter_current_password": "输å…ĨåŊ“å‰å¯†į ", + "enter_first_name": "输å…Ĩæ‚¨įš„åå­—", + "enter_last_name": "输å…Ĩæ‚¨įš„å§“æ°", + "enter_new_email": "输å…Ĩæ–°įš„į”ĩ子邎äģļ地址", + "enter_new_password": "输å…Ĩæ–°å¯†į ", + "enter_username": "输å…Ĩæ‚¨įš„į”¨æˆˇå", + "integrations": "集成", + "integrations_desc": "čŋžæŽĨå¤–éƒ¨æœåŠĄäģĨåĸžåŧēæ‚¨įš„äŊ“énj", + "license": "æ‰§į…§", + "mfa_desc": "åœ¨æ‚¨įš„å¸æˆˇä¸­æˇģ加éĸå¤–įš„åŽ‰å…¨æ€§", + "mfa_is_enabled": "MFA厞吝ᔍ", + "pass_change_desc": "æ›´æ–°æ‚¨įš„å¸æˆˇå¯†į äģĨčŽˇåž—æ›´åĨŊįš„åŽ‰å…¨æ€§", + "password_auth": "坆᠁čēĢäģŊénj蝁", + "password_login_disabled": "坆᠁į™ģåŊ•įρᔍ", + "password_login_enabled": "吝ᔍ坆᠁į™ģåŊ•", + "profile_info": "ä¸Ēäēēčĩ„æ–™äŋĄæ¯", + "profile_info_desc": "æ›´æ–°æ‚¨įš„ä¸Ēäēēč¯Ļįģ†äŋĄæ¯å’Œä¸Ēäēēčĩ„æ–™å›žį‰‡", + "public_profile_desc": "莊å…ļäģ–į”¨æˆˇå¯äģĨįœ‹åˆ°æ‚¨įš„ä¸Ēäēēčĩ„æ–™", + "regular_user": "å¸¸č§„į”¨æˆˇ", + "security": "厉全", + "settings_menu": "莞įŊŽčœå•", + "social_auth": "į¤žäŧščēĢäģŊénj蝁", + "social_auth_desc_1": "įŽĄį†į¤žäē¤į™ģåŊ•é€‰éĄšå’Œå¯†į čŽžįŊŽ", + "social_auth_setup": "į¤žäŧščēĢäģŊéĒŒč¯čŽžįŊŽ", + "staff_status": "员åˇĨčēĢäģŊ", + "staff_user": "员åˇĨį”¨æˆˇ", + "quick_actions": "åŋĢ速动äŊœ", + "region_updates": "åŒē域更新", + "region_updates_desc": "更新čŽŋ问äē†åœ°åŒē和城市" }, "checklist": { "add_item": "æˇģåŠ éĄšį›Ž", @@ -553,7 +630,9 @@ "manage_categories": "įŽĄį†įąģåˆĢ", "no_categories_found": "æœĒ扞到įąģåˆĢ。", "select_category": "选拊įąģåˆĢ", - "update_after_refresh": "åˆˇæ–°éĄĩéĸ后īŧŒå†’é™ŠåĄå°†æ›´æ–°ã€‚" + "update_after_refresh": "åˆˇæ–°éĄĩéĸ后īŧŒå†’é™ŠåĄå°†æ›´æ–°ã€‚", + "add_category": "æˇģ加įąģåˆĢ", + "add_new_category": "æˇģ加新įąģåˆĢ" }, "dashboard": { "add_some": "ä¸ēäģ€äšˆä¸åŧ€å§‹čŽĄåˆ’äŊ įš„下一æŦĄå†’陊å‘ĸīŧŸ\n您可äģĨ通čŋ‡å•å‡ģ下éĸįš„æŒ‰é’ŽæˇģåŠ æ–°įš„å†’é™Šã€‚", @@ -588,14 +667,31 @@ "server_url": "Immich æœåŠĄå™¨įŊ‘址", "update_integration": "更新集成", "documentation": "Immich é›†æˆæ–‡æĄŖ", - "localhost_note": "æŗ¨æ„īŧšé™¤éžæ‚¨į›¸åē”åœ°čŽžįŊŽäē† docker įŊ‘įģœīŧŒåĻ则 localhost 垈可čƒŊæ— æŗ•åˇĨäŊœã€‚\nåģē莎äŊŋį”¨æœåŠĄå™¨įš„IP地址或域名。" + "localhost_note": "æŗ¨æ„īŧšé™¤éžæ‚¨į›¸åē”åœ°čŽžįŊŽäē† docker įŊ‘įģœīŧŒåĻ则 localhost 垈可čƒŊæ— æŗ•åˇĨäŊœã€‚\nåģē莎äŊŋį”¨æœåŠĄå™¨įš„IP地址或域名。", + "api_key_placeholder": "输å…Ĩæ‚¨įš„Immich API密é’Ĩ", + "enable_integration": "å¯į”¨é›†æˆ", + "immich_integration_desc": "čŋžæŽĨæ‚¨įš„Immichį…§į‰‡įŽĄį†æœåŠĄå™¨", + "need_help": "需čĻå¸ŽåŠŠčŽžįŊŽå—īŧŸ\næŸĨįœ‹", + "connection_error": "čŋžæŽĨ到ImmichæœåŠĄå™¨įš„é”™č¯¯", + "copy_locally": "在æœŦ地复åˆļ回像", + "copy_locally_desc": "将回像复åˆļåˆ°æœåŠĄå™¨äģĨčŋ›čĄŒįĻģįēŋčŽŋ闎。\näŊŋį”¨æ›´å¤šįš„įŖį›˜įŠē间。", + "error_saving_image": "äŋå­˜é”™č¯¯įš„回像", + "integration_already_exists": "Immichæ•´åˆåˇ˛įģå­˜åœ¨ã€‚\n您一æŦĄåĒčƒŊčŋ›čĄŒä¸€ä¸Ē集成。", + "integration_not_found": "æ˛Ąæœ‰æ‰žåˆ°Immich整合。\nč¯ˇåˆ›åģē一ä¸Ēæ–°įš„é›†æˆã€‚", + "network_error": "čŋžæŽĨ到ImmichæœåŠĄå™¨æ—ļįš„įŊ‘įģœé”™č¯¯ã€‚\nč¯ˇæŖ€æŸĨæ‚¨įš„čŋžæŽĨīŧŒį„ļåŽé‡č¯•ã€‚", + "validation_error": "在énj蝁IMMICH集成æ—ļå‘į”Ÿäē†é”™č¯¯ã€‚\nč¯ˇæŖ€æŸĨæ‚¨įš„æœåŠĄå™¨URL和API锎。" }, "recomendations": { "address": "地址", "contact": "联įŗģæ–šåŧ", "phone": "į”ĩč¯", "recommendation": "æŽ¨č", - "website": "įŊ‘įĢ™" + "website": "įŊ‘įĢ™", + "recommendations": "åģē莎", + "adventure_recommendations": "冒陊åģē莎", + "food": "éŖŸį‰Š", + "miles": "英里", + "tourism": "旅游" }, "lodging": { "campground": "霞čĨ地", @@ -626,5 +722,8 @@ "reservation_number": "éĸ„čŽĸåˇ", "resort": "åēĻ假村", "current_timezone": "åŊ“前æ—ļåŒē" + }, + "google_maps": { + "google_maps_integration_desc": "čŋžæŽĨæ‚¨įš„Google Mapså¸æˆˇäģĨčŽˇå–éĢ˜č´¨é‡įš„äŊįŊŽæœį´ĸį쓿žœå’ŒåģēčŽŽã€‚" } } diff --git a/frontend/src/routes/+error.svelte b/frontend/src/routes/+error.svelte index 4cff1c82..20fa13e7 100644 --- a/frontend/src/routes/+error.svelte +++ b/frontend/src/routes/+error.svelte @@ -2,6 +2,7 @@ import { goto } from '$app/navigation'; import { page } from '$app/stores'; import Lost from '$lib/assets/undraw_lost.svg'; + import ServerError from '$lib/assets/undraw_server_error.svg'; {#if $page.status === 404} @@ -24,3 +25,52 @@
{/if} + +{#if $page.status === 500} +
+
+ Lost in the forest +

+ {$page.status}: {$page.error?.message} +

+

+ Oops, looks like something went wrong. +

+ +

+ AdventureLog server encountered an error while processing your request. +
+ Please check the server logs for more information. +

+ +
+

+ Administrators: Please check your setup using the + documentation. +

+
+ + + {#if $page.url.pathname === '/login' || $page.url.pathname === '/signup'} +
+

+ Hint: If you are an administrator, please check your PUBLIC_SERVER_URL + in the frontend config to make sure it can reach the backend. +
+

+
+ {/if} + +
+ +
+
+
+{/if} diff --git a/frontend/src/routes/+layout.svelte b/frontend/src/routes/+layout.svelte index dc904eaf..27dbca4e 100644 --- a/frontend/src/routes/+layout.svelte +++ b/frontend/src/routes/+layout.svelte @@ -15,8 +15,9 @@ register('sv', () => import('../locales/sv.json')); register('pl', () => import('../locales/pl.json')); register('ko', () => import('../locales/ko.json')); + register('no', () => import('../locales/no.json')); - let locales = ['en', 'es', 'fr', 'de', 'it', 'zh', 'nl', 'sv', 'pl', 'ko']; + let locales = ['en', 'es', 'fr', 'de', 'it', 'zh', 'nl', 'sv', 'pl', 'ko', 'no']; if (browser) { init({ diff --git a/frontend/src/routes/adventures/+page.svelte b/frontend/src/routes/adventures/+page.svelte index fa59fcc4..8bbb2605 100644 --- a/frontend/src/routes/adventures/+page.svelte +++ b/frontend/src/routes/adventures/+page.svelte @@ -209,7 +209,7 @@
-

{$t('navbar.my_adventures')}

+

{$t('navbar.my_adventures')}

{count} {$t('adventures.count_txt')}

{#if adventures.length === 0} diff --git a/frontend/src/routes/adventures/[id]/+page.server.ts b/frontend/src/routes/adventures/[id]/+page.server.ts index eed47ba5..140ca460 100644 --- a/frontend/src/routes/adventures/[id]/+page.server.ts +++ b/frontend/src/routes/adventures/[id]/+page.server.ts @@ -1,11 +1,11 @@ import type { PageServerLoad } from './$types'; const PUBLIC_SERVER_URL = process.env['PUBLIC_SERVER_URL']; -import type { Adventure, Collection } from '$lib/types'; +import type { AdditionalAdventure, Adventure, Collection } from '$lib/types'; const endpoint = PUBLIC_SERVER_URL || 'http://localhost:8000'; export const load = (async (event) => { const id = event.params as { id: string }; - let request = await fetch(`${endpoint}/api/adventures/${id.id}/`, { + let request = await fetch(`${endpoint}/api/adventures/${id.id}/additional-info/`, { headers: { Cookie: `sessionid=${event.cookies.get('sessionid')}` }, @@ -19,7 +19,7 @@ export const load = (async (event) => { } }; } else { - let adventure = (await request.json()) as Adventure; + let adventure = (await request.json()) as AdditionalAdventure; let collection: Collection | null = null; if (adventure.collection) { diff --git a/frontend/src/routes/adventures/[id]/+page.svelte b/frontend/src/routes/adventures/[id]/+page.svelte index 674a5f4e..6c01956f 100644 --- a/frontend/src/routes/adventures/[id]/+page.svelte +++ b/frontend/src/routes/adventures/[id]/+page.svelte @@ -1,17 +1,25 @@ {#if notFound} -
-
-
- Lost -
-

- {$t('adventures.not_found')} -

-

- {$t('adventures.not_found_desc')} -

-
- +
+
+
+ Lost +

{$t('adventures.not_found')}

+

{$t('adventures.not_found_desc')}

+
@@ -155,426 +142,615 @@ {/if} {#if !adventure && !notFound} -
- +
+
+ +
{/if} {#if adventure} {#if data.user && data.user.uuid == adventure.user_id} -
- +
{/if} -
-
-
-
- {#if adventure.images && adventure.images.length > 0} -
+
{/if} - {data.props.adventure && data.props.adventure.name + <title> + {data.props.adventure && data.props.adventure.name ? `${data.props.adventure.name}` - : 'Adventure'} + : 'Adventure'} + diff --git a/frontend/src/routes/collections/+page.server.ts b/frontend/src/routes/collections/+page.server.ts index 20e2c401..7c5812a0 100644 --- a/frontend/src/routes/collections/+page.server.ts +++ b/frontend/src/routes/collections/+page.server.ts @@ -46,290 +46,3 @@ export const load = (async (event) => { }; } }) satisfies PageServerLoad; - -export const actions: Actions = { - create: async (event) => { - const formData = await event.request.formData(); - - const name = formData.get('name') as string; - const description = formData.get('description') as string | null; - const start_date = formData.get('start_date') as string | null; - const end_date = formData.get('end_date') as string | null; - let link = formData.get('link') as string | null; - - if (link) { - link = checkLink(link); - } - - if (!name) { - return { - status: 400, - body: { error: 'Missing required fields' } - }; - } - - const formDataToSend = new FormData(); - formDataToSend.append('name', name); - formDataToSend.append('description', description || ''); - formDataToSend.append('start_date', start_date || ''); - formDataToSend.append('end_date', end_date || ''); - formDataToSend.append('link', link || ''); - let sessionid = event.cookies.get('sessionid'); - - if (!sessionid) { - return { - status: 401, - body: { message: 'Unauthorized' } - }; - } - - const csrfToken = await fetchCSRFToken(); - - if (!csrfToken) { - return { - status: 500, - body: { message: 'Failed to fetch CSRF token' } - }; - } - - const res = await fetch(`${serverEndpoint}/api/collections/`, { - method: 'POST', - headers: { - 'X-CSRFToken': csrfToken, - Referer: event.url.origin, // Include Referer header - Cookie: `sessionid=${sessionid}; csrftoken=${csrfToken}` - }, - body: formDataToSend - }); - - let new_id = await res.json(); - - if (!res.ok) { - const errorBody = await res.json(); - return { - status: res.status, - body: { error: errorBody } - }; - } - - let id = new_id.id; - let user_id = new_id.user_id; - - return { id, user_id }; - }, - edit: async (event) => { - const formData = await event.request.formData(); - - const collectionId = formData.get('adventureId') as string; - const name = formData.get('name') as string; - const description = formData.get('description') as string | null; - let is_public = formData.get('is_public') as string | null | boolean; - const start_date = formData.get('start_date') as string | null; - const end_date = formData.get('end_date') as string | null; - let link = formData.get('link') as string | null; - - if (is_public) { - is_public = true; - } else { - is_public = false; - } - - if (link) { - link = checkLink(link); - } - - if (!name) { - return { - status: 400, - body: { error: 'Missing name.' } - }; - } - - const formDataToSend = new FormData(); - formDataToSend.append('name', name); - formDataToSend.append('description', description || ''); - formDataToSend.append('is_public', is_public.toString()); - formDataToSend.append('start_date', start_date || ''); - formDataToSend.append('end_date', end_date || ''); - formDataToSend.append('link', link || ''); - - let sessionId = event.cookies.get('sessionid'); - - if (!sessionId) { - return { - status: 401, - body: { message: 'Unauthorized' } - }; - } - - const csrfToken = await fetchCSRFToken(); - - if (!csrfToken) { - return { - status: 500, - body: { message: 'Failed to fetch CSRF token' } - }; - } - - const res = await fetch(`${serverEndpoint}/api/collections/${collectionId}/`, { - method: 'PATCH', - headers: { - 'X-CSRFToken': csrfToken, - Cookie: `sessionid=${sessionId}; csrftoken=${csrfToken}`, - Referer: event.url.origin // Include Referer header - }, - body: formDataToSend, - - credentials: 'include' - }); - - if (!res.ok) { - const errorBody = await res.json(); - return { - status: res.status, - body: { error: errorBody } - }; - } - - return { - status: 200 - }; - }, - get: async (event) => { - if (!event.locals.user) { - return { - status: 401, - body: { message: 'Unauthorized' } - }; - } - - const formData = await event.request.formData(); - - const order_direction = formData.get('order_direction') as string; - const order_by = formData.get('order_by') as string; - - console.log(order_direction, order_by); - - let adventures: Adventure[] = []; - - if (!event.locals.user) { - return { - status: 401, - body: { message: 'Unauthorized' } - }; - } - - let next = null; - let previous = null; - let count = 0; - - let collectionsFetch = await fetch( - `${serverEndpoint}/api/collections/?order_by=${order_by}&order_direction=${order_direction}`, - { - headers: { - Cookie: `sessionid=${event.cookies.get('sessionid')}` - }, - credentials: 'include' - } - ); - if (!collectionsFetch.ok) { - console.error('Failed to fetch visited adventures'); - return redirect(302, '/login'); - } else { - let res = await collectionsFetch.json(); - let visited = res.results as Adventure[]; - next = res.next; - previous = res.previous; - count = res.count; - adventures = [...adventures, ...visited]; - console.log(next, previous, count); - } - - return { - adventures, - next, - previous, - count - }; - }, - changePage: async (event) => { - const formData = await event.request.formData(); - const next = formData.get('next') as string; - const previous = formData.get('previous') as string; - const page = formData.get('page') as string; - - if (!event.locals.user) { - return { - status: 401, - body: { message: 'Unauthorized' } - }; - } - - if (!page) { - return { - status: 400, - body: { error: 'Missing required fields' } - }; - } - - // Start with the provided URL or default to the filtered adventures endpoint - let url: string = next || previous || '/api/collections/'; - - // Extract the path starting from '/api/adventures' - const apiIndex = url.indexOf('/api/collections'); - if (apiIndex !== -1) { - url = url.slice(apiIndex); - } else { - url = '/api/collections/'; - } - - // Replace or add the page number in the URL - if (url.includes('page=')) { - url = url.replace(/page=\d+/, `page=${page}`); - } else { - // If 'page=' is not in the URL, add it - url += url.includes('?') ? '&' : '?'; - url += `page=${page}`; - } - - const fullUrl = `${serverEndpoint}${url}`; - - let sessionId = event.cookies.get('sessionid'); - - try { - const response = await fetch(fullUrl, { - headers: { - 'Content-Type': 'application/json', - Cookie: `sessionid=${sessionId}` - }, - credentials: 'include' - }); - - if (!response.ok) { - throw new Error(`HTTP error! status: ${response.status}`); - } - const data = await response.json(); - let adventures = data.results as Adventure[]; - let next = data.next; - let previous = data.previous; - let count = data.count; - - return { - status: 200, - body: { - adventures, - next, - previous, - count, - page - } - }; - } catch (error) { - console.error('Error fetching data:', error); - return { - status: 500, - body: { error: 'Failed to fetch data' } - }; - } - } -}; diff --git a/frontend/src/routes/collections/+page.svelte b/frontend/src/routes/collections/+page.svelte index 171a5d4b..6eddc70e 100644 --- a/frontend/src/routes/collections/+page.svelte +++ b/frontend/src/routes/collections/+page.svelte @@ -15,8 +15,6 @@ let collections: Collection[] = data.props.adventures || []; - let currentSort = { attribute: 'name', order: 'asc' }; - let newType: string = ''; let resultsPerPage: number = 25; @@ -235,17 +233,36 @@ aria-label={$t(`adventures.descending`)} />
+

{$t('adventures.order_by')}

+
+ + + +

- diff --git a/frontend/src/routes/collections/[id]/+page.svelte b/frontend/src/routes/collections/[id]/+page.svelte index a63dbe5a..7d5a1c3b 100644 --- a/frontend/src/routes/collections/[id]/+page.svelte +++ b/frontend/src/routes/collections/[id]/+page.svelte @@ -1,6 +1,6 @@