From a899579a020a57cc5687ddb256e948ddf317f922 Mon Sep 17 00:00:00 2001 From: Nikolai Eidsheim Date: Thu, 6 Feb 2025 21:33:17 +0000 Subject: [PATCH 01/79] Started work on norwegian translations --- frontend/src/locales/no.json | 504 +++++++++++++++++++++++++++++++++++ 1 file changed, 504 insertions(+) create mode 100644 frontend/src/locales/no.json diff --git a/frontend/src/locales/no.json b/frontend/src/locales/no.json new file mode 100644 index 0000000..c5bcd5c --- /dev/null +++ b/frontend/src/locales/no.json @@ -0,0 +1,504 @@ +{ + "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 tagger", + "tag": "Tagg", + "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": "Temavalg", + "themes": { + "light": "Lys", + "dark": "Mørk", + "night": "Natt", + "forest": "Skog", + "aestheticLight": "Estetisk lys", + "aestheticDark": "Estetisk mørk", + "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-bidrag", + "nominatim_1": "Stedsøk og geokoding tilbys av", + "nominatim_2": "Deres data er lisensiert under ODbL-lisensen.", + "other_attributions": "Ytterligere bidrag finnes i README-filen.", + "close": "Lukk" + }, + "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øyene og ressursene du trenger for å planlegge, pakke og navigere ditt neste uforglemmelige eventyr.", + "feature_1": "Reiselogg", + "feature_1_desc": "Før logg over dine eventyr med en personlig reiselogg 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 dine reiser over hele verden med et interaktivt kart og utforsk nye destinasjoner." + }, + "adventures": { + "collection_remove_success": "Eventyr fjernet fra samlingen!", + "collection_remove_error": "Feil ved fjerning av eventyr fra samlingen", + "collection_link_success": "Eventyr lagt til samlingen!", + "no_image_found": "Ingen bilde funnet", + "collection_link_error": "Feil ved kobling av eventyr til samlingen", + "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 denne notatet? Denne handlingen kan ikke angres.", + "transportation_delete_confirm": "Er du sikker på at du vil slette denne transporten? Denne handlingen kan ikke angres.", + "delete_checklist": "Slett sjekkliste", + "delete_note": "Slett notat", + "delete_transportation": "Slett transport", + "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": "Eventyr ikke funnet", + "not_found_desc": "Eventyret du lette etter ble ikke funnet. Prøv et annet eventyr eller sjekk igjen 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 matcher ditt søk", + "sort": "Sorter", + "order_by": "Sorter etter", + "order_direction": "Sorteringsretning", + "ascending": "Stigende", + "descending": "Synkende", + "updated": "Oppdatert", + "name": "Navn", + "date": "Dato", + "activity_types": "Aktivitetstyper", + "tags": "Tagger", + "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", + "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 kobles til denne samlingen.", + "add": "Legg til", + "save_next": "Lagre og neste", + "end_date": "Sluttdato", + "my_visits": "Mine besøk", + "start_date": "Startdato", + "remove": "Fjern", + "location": "Sted", + "search_for_location": "Søk etter et sted", + "clear_map": "Tøm kart", + "search_results": "Søkeresultater", + "no_results": "Ingen resultater funnet", + "wiki_desc": "Henter utdrag fra Wikipedia-artikkelen som matcher eventyrets navn.", + "generate_desc": "Generer beskrivelse", + "public_adventure": "Offentlig eventyr", + "location_information": "Stedsinformasjon", + "link": "Lenke", + "links": "Lenker", + "description": "Beskrivelse", + "sources": "Kilder", + "collection_adventures": "Inkluder eventyr fra samlingen", + "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": "Avarkiver", + "archive": "Arkiver", + "no_collections_found": "Ingen samlinger funnet å legge dette eventyret til.", + "not_visited": "Ikke besøkt", + "archived_collection_message": "Samling arkivert!", + "unarchived_collection_message": "Samling avarkivert!", + "delete_collection_success": "Samling slettet!", + "delete_collection_warning": "Er du sikker på at du vil slette denne samlingen? Dette vil også slette alle tilknyttede eventyr. Denne handlingen kan ikke angres.", + "cancel": "Avbryt", + "delete_collection": "Slett samling", + "delete_adventure": "Slett eventyr", + "adventure_delete_success": "Eventyr slettet!", + "visited": "Besøkt", + "planned": "Planlagt", + "duration": "Varighet", + "all": "Alle", + "image_removed_success": "Bilde fjernet!", + "image_removed_error": "Feil ved fjerning av bilde", + "no_image_url": "Ingen bilde funnet på den URL-en.", + "image_upload_success": "Bilde lastet opp!", + "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 skriv inn en sluttdato", + "see_adventures": "Se eventyr", + "image_fetch_failed": "Kunne ikke hente bilde", + "no_location": "Vennligst skriv inn et sted", + "no_start_date": "Vennligst oppgi en startdato", + "no_description_found": "Ingen beskrivelse funnet", + "adventure_created": "Eventyr opprettet", + "adventure_create_error": "Kunne ikke opprette eventyr", + "adventure_updated": "Eventyr oppdatert", + "adventure_update_error": "Kunne ikke oppdatere eventyr", + "set_to_pin": "Sett til pin", + "category_fetch_error": "Feil ved henting av kategorier", + "new_adventure": "Nytt eventyr", + "basic_information": "Grunnleggende informasjon", + "adventure_not_found": "Det er ingen eventyr å vise. Legg til noen ved å bruke plussknappen nederst til høyre, eller prøv å endre filtrene!", + "no_adventures_found": "Ingen eventyr funnet", + "mark_region_as_visited": "Marker region {region}, {country} som besøkt?", + "mark_visited": "Marker som besøkt", + "error_updating_regions": "Feil ved oppdatering av regioner", + "regions_updated": "Regioner oppdatert", + "visited_region_check": "Sjekk besøkte regioner", + "visited_region_check_desc": "Ved å velge dette vil serveren sjekke alle dine besøkte eventyr og markere regionene de ligger i som besøkt i verdensreiser.", + "update_visited_regions": "Oppdater besøkte regioner", + "update_visited_regions_disclaimer": "Dette kan ta en stund, avhengig av hvor mange eventyr du har besøkt.", + "link_new": "Koble til ny...", + "add_new": "Legg til ny...", + "transportation": "Transport", + "note": "Notat", + "checklist": "Sjekkliste", + "collection_archived": "Denne samlingen har blitt arkivert.", + "visit_link": "Besøk lenke", + "collection_completed": "Du har fullført denne samlingen!", + "collection_stats": "Samlingstatistikk", + "keep_exploring": "Fortsett å utforske!", + "linked_adventures": "Koblede eventyr", + "notes": "Notater", + "checklists": "Sjekklister", + "transportations": "Transportmidler", + "adventure_calendar": "Eventyrkalender", + "day": "Dag", + "itineary_by_date": "Reiseplan etter dato", + "nothing_planned": "Ingenting planlagt for denne dagen. Nyt reisen!", + "copied_to_clipboard": "Kopiert til utklippstavlen!", + "copy_failed": "Kopiering mislyktes", + "show": "Vis", + "hide": "Skjul", + "clear_location": "Fjern plassering", + "starting_airport": "Startflyplass", + "ending_airport": "Sluttflyplass", + "no_location_found": "Ingen plassering funnet", + "from": "Fra", + "to": "Til", + "start": "Start", + "end": "Slutt", + "emoji_picker": "Emoji-velger", + "download_calendar": "Last ned kalender", + "date_information": "Dato informasjon", + "flight_information": "Flyinformasjon", + "out_of_range": "Ikke innenfor reiseplanens datoområde", + "preview": "Forhåndsvisning", + "md_instructions": "Skriv markdown her...", + "days": "dager", + "activities": { + "general": "Generelt 🌍", + "outdoor": "Utendørs 🏞️", + "lodging": "Overnatting 🛌", + "dining": "Spisesteder 🍽️", + "activity": "Aktivitet 🏄", + "attraction": "Attraksjon 🎢", + "shopping": "Shopping 🛍️", + "nightlife": "Natteliv 🌃", + "event": "Arrangement 🎉", + "transportation": "Transport 🚗", + "culture": "Kultur 🎭", + "water_sports": "Vannsport 🚤", + "hiking": "Fotturer 🥾", + "wildlife": "Dyreliv 🦒", + "historical_sites": "Historiske steder 🏛️", + "music_concerts": "Musikk & konserter 🎶", + "fitness": "Trening 🏋️", + "art_museums": "Kunst & museer 🎨", + "festivals": "Festivaler 🎪", + "spiritual_journeys": "Spirituelle reiser 🧘‍♀️", + "volunteer_work": "Frivillig arbeid 🤝", + "other": "Annet" + }, + "worldtravel": { + "country_list": "Landeliste", + "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": "Fjern søk", + "no_countries_found": "Ingen land funnet" + }, + "auth": { + "username": "Brukernavn", + "password": "Passord", + "forgot_password": "Glemt passord?", + "signup": "Lag ny bruker", + "login_error": "Kunne ikke logge inn med kombinasjonen av brukernavn og passord.", + "login": "Logg inn", + "email": "Email", + "first_name": "Fornavn", + "last_name": "Etternavn", + "confirm_password": "Gjenta passord", + "registration_disabled": "Registrering er deaktivert.", + "profile_picture": "Profilbilde", + "public_profile": "Offentlig Profil", + "public_tooltip": "Med en offentlig profil kan brukere dele samlinger med deg og se profilen din.", + "email_required": "Email er påkrevet", + "new_password": "Nytt Passord (6+ tegn)", + "both_passwords_required": "Begge passord må skrives inn", + "reset_failed": "Kunne ikke resette passord" + }, + "users": { + "no_users_found": "Ingen brukere med offentlig profil funnet." + }, + "settings": { + "update_error": "Kunne ikke oppdatere innstillinger", + "update_success": "Innstillinger oppdatert!", + "settings_page": "Innstillinger", + "account_settings": "Brukerinnstillinger", + "update": "Oppdater", + "password_change": "Bytt passord", + "new_password": "Nytt passord", + "confirm_new_password": "Bekreft nytt passord", + "email_change": "Bytt email", + "current_email": "Nåværende email", + "no_email_set": "Ingen email satt", + "new_email": "Ny email", + "change_password": "Bytt passord", + "login_redir": "You will then be redirected to the login page.", + "token_required": "Token and UID are required for password reset.", + "reset_password": "Reset Password", + "possible_reset": "If the email address you provided is associated with an account, you will receive an email with instructions to reset your password!", + "missing_email": "Please enter an email address", + "submit": "Submit", + "password_does_not_match": "Passwords do not match", + "password_is_required": "Password is required", + "invalid_token": "Token is invalid or has expired", + "about_this_background": "About this background", + "photo_by": "Photo by", + "join_discord": "Join the Discord", + "join_discord_desc": "to share your own photos. Post them in the #travel-share channel.", + "current_password": "Current Password", + "change_password_error": "Unable to change password. Invalid current password or invalid new password.", + "password_change_lopout_warning": "You will be logged out after changing your password.", + "generic_error": "An error occurred while processing your request.", + "email_removed": "Email removed successfully!", + "email_removed_error": "Error removing email", + "verify_email_success": "Email verification sent successfully!", + "verify_email_error": "Error verifying email. Try again in a few minutes.", + "email_added": "Email added successfully!", + "email_added_error": "Error adding email", + "email_set_primary": "Email set as primary successfully!", + "email_set_primary_error": "Error setting email as primary", + "verified": "Verified", + "primary": "Primary", + "not_verified": "Not Verified", + "make_primary": "Make Primary", + "verify": "Verify", + "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", + "enable_mfa": "Enable MFA", + "disable_mfa": "Disable MFA", + "mfa_not_enabled": "MFA is not enabled", + "mfa_enabled": "Multi-factor authentication enabled successfully!", + "copy": "Copy", + "recovery_codes": "Recovery Codes", + "recovery_codes_desc": "These are your recovery codes. Keep them safe. You will not be able to see them again.", + "reset_session_error": "Please logout and back in to refresh your session and try again.", + "authenticator_code": "Authenticator Code", + "email_verified": "Email verified successfully!", + "email_verified_success": "Your email has been verified. You can now log in.", + "email_verified_error": "Error verifying email", + "email_verified_erorr_desc": "Your email could not be verified. Please try again.", + "invalid_code": "Invalid MFA code", + "invalid_credentials": "Invalid username or password", + "mfa_required": "Multi-factor authentication is required", + "required": "This field is required", + "add_email_blocked": "You cannot add an email address to an account protected by two-factor authentication.", + "duplicate_email": "This email address is already in use.", + "csrf_failed": "Failed to fetch CSRF token", + "email_taken": "This email address is already in use.", + "username_taken": "This username is already in use." + }, + "collection": { + "collection_created": "Collection created successfully!", + "error_creating_collection": "Error creating collection", + "new_collection": "New Collection", + "create": "Create", + "collection_edit_success": "Collection edited successfully!", + "error_editing_collection": "Error editing collection", + "edit_collection": "Edit Collection", + "public_collection": "Public Collection" + }, + "notes": { + "note_deleted": "Note deleted successfully!", + "note_delete_error": "Error deleting note", + "open": "Open", + "failed_to_save": "Failed to save note", + "note_editor": "Note Editor", + "note_viewer": "Note Viewer", + "editing_note": "Editing note", + "content": "Content", + "save": "Save", + "note_public": "This note is public because it is in a public collection.", + "add_a_link": "Add a link", + "invalid_url": "Invalid URL" + }, + "checklist": { + "checklist_deleted": "Checklist deleted successfully!", + "checklist_delete_error": "Error deleting checklist", + "failed_to_save": "Failed to save checklist", + "checklist_editor": "Checklist Editor", + "checklist_viewer": "Checklist Viewer", + "editing_checklist": "Editing checklist", + "new_checklist": "New Checklist", + "item": "Item", + "items": "Items", + "add_item": "Add Item", + "new_item": "New Item", + "save": "Save", + "checklist_public": "This checklist is public because it is in a public collection.", + "item_cannot_be_empty": "Item cannot be empty", + "item_already_exists": "Item already exists" + }, + "transportation": { + "transportation_deleted": "Transportation deleted successfully!", + "transportation_delete_error": "Error deleting transportation", + "provide_start_date": "Please provide a start date", + "transport_type": "Transport Type", + "type": "Type", + "transportation_added": "Transportation added successfully!", + "error_editing_transportation": "Error editing transportation", + "new_transportation": "New Transportation", + "date_time": "Start Date & Time", + "end_date_time": "End Date & Time", + "flight_number": "Flight Number", + "from_location": "From Location", + "to_location": "To Location", + "edit": "Edit", + "modes": { + "car": "Car", + "plane": "Plane", + "train": "Train", + "bus": "Bus", + "boat": "Boat", + "bike": "Bike", + "walking": "Walking", + "other": "Other" + }, + "transportation_edit_success": "Transportation edited successfully!", + "edit_transportation": "Edit Transportation", + "start": "Start", + "date_and_time": "Date & Time" + }, + "search": { + "adventurelog_results": "AdventureLog Results", + "public_adventures": "Public Adventures", + "online_results": "Online Results" + }, + "map": { + "view_details": "View Details", + "adventure_map": "Adventure Map", + "map_options": "Map Options", + "show_visited_regions": "Show Visited Regions", + "add_adventure_at_marker": "Add New Adventure at Marker", + "clear_marker": "Clear Marker", + "add_adventure": "Add New Adventure" + }, + "share": { + "shared": "Shared", + "with": "with", + "unshared": "Unshared", + "share_desc": "Share this collection with other users.", + "shared_with": "Shared With", + "no_users_shared": "No users shared with", + "not_shared_with": "Not Shared With", + "no_shared_found": "No collections found that are shared with you.", + "set_public": "In order to allow users to share with you, you need your profile set to public.", + "go_to_settings": "Go to settings" + }, + "languages": { + "en": "English", + "de": "German", + "es": "Spanish", + "fr": "French", + "it": "Italian", + "nl": "Dutch", + "sv": "Swedish", + "zh": "Chinese", + "pl": "Polish" + }, + "profile": { + "member_since": "Member since", + "user_stats": "User Stats", + "visited_countries": "Visited Countries", + "visited_regions": "Visited Regions" + }, + "categories": { + "manage_categories": "Manage Categories", + "no_categories_found": "No categories found.", + "edit_category": "Edit Category", + "icon": "Icon", + "update_after_refresh": "The adventure cards will be updated once you refresh the page.", + "select_category": "Select Category", + "category_name": "Category Name" + }, + "dashboard": { + "welcome_back": "Welcome back", + "countries_visited": "Countries Visited", + "total_adventures": "Total Adventures", + "total_visited_regions": "Total Visited Regions", + "recent_adventures": "Recent Adventures", + "no_recent_adventures": "No recent adventures?", + "add_some": "Why not start planning your next adventure? You can add a new adventure by clicking the button below." + } + } + From 9fd2a142cbcb1325099b0f436952f17d1757c94f Mon Sep 17 00:00:00 2001 From: Sean Morley Date: Tue, 18 Mar 2025 14:04:31 -0400 Subject: [PATCH 02/79] feat: Update Visit model to use DateTimeField for start and end dates, and enhance AdventureModal with datetime-local inputs --- ...r_visit_end_date_alter_visit_start_date.py | 23 ++++ backend/server/adventures/models.py | 4 +- backend/server/adventures/serializers.py | 6 +- .../src/lib/components/AdventureModal.svelte | 107 +++++++++++++++--- frontend/src/lib/index.ts | 6 + .../src/routes/adventures/[id]/+page.svelte | 41 ++++--- 6 files changed, 152 insertions(+), 35 deletions(-) create mode 100644 backend/server/adventures/migrations/0025_alter_visit_end_date_alter_visit_start_date.py 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 0000000..668e968 --- /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/models.py b/backend/server/adventures/models.py index c7f78ca..0d53bc9 100644 --- a/backend/server/adventures/models.py +++ b/backend/server/adventures/models.py @@ -76,8 +76,8 @@ User = get_user_model() 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) notes = models.TextField(blank=True, null=True) created_at = models.DateTimeField(auto_now_add=True) updated_at = models.DateTimeField(auto_now=True) diff --git a/backend/server/adventures/serializers.py b/backend/server/adventures/serializers.py index 97dd633..d69466d 100644 --- a/backend/server/adventures/serializers.py +++ b/backend/server/adventures/serializers.py @@ -136,9 +136,11 @@ class AdventureSerializer(CustomModelSerializer): 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): + 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 visit.start_date and not visit.end_date and (visit.start_date <= current_date): + elif start_date and not end_date and (start_date <= current_date): return True return False diff --git a/frontend/src/lib/components/AdventureModal.svelte b/frontend/src/lib/components/AdventureModal.svelte index c8b0743..b9daa0f 100644 --- a/frontend/src/lib/components/AdventureModal.svelte +++ b/frontend/src/lib/components/AdventureModal.svelte @@ -6,6 +6,16 @@ import { t } from 'svelte-i18n'; export let collection: Collection | null = null; + let fullStartDate: string = ''; + let fullEndDate: string = ''; + let allDay: boolean = false; + + // 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`; + } + const dispatch = createEventDispatcher(); let images: { id: string; image: string; is_primary: boolean }[] = []; @@ -72,7 +82,7 @@ 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'; @@ -379,7 +389,10 @@ let new_start_date: string = ''; let new_end_date: string = ''; let new_notes: string = ''; + + // Function to add a new visit. function addNewVisit() { + // If an end date isn’t provided, assume it’s the same as start. if (new_start_date && !new_end_date) { new_end_date = new_start_date; } @@ -391,15 +404,31 @@ addToast('error', $t('adventures.no_start_date')); return; } + // Convert input to UTC if not already. + if (new_start_date && !new_start_date.includes('Z')) { + new_start_date = new Date(new_start_date).toISOString(); + } + if (new_end_date && !new_end_date.includes('Z')) { + new_end_date = new Date(new_end_date).toISOString(); + } + + // If the visit is all day, force the times to midnight. + if (allDay) { + new_start_date = new_start_date.split('T')[0] + 'T00:00:00.000Z'; + new_end_date = new_end_date.split('T')[0] + 'T00:00:00.000Z'; + } + adventure.visits = [ ...adventure.visits, { start_date: new_start_date, end_date: new_end_date, notes: new_notes, - id: '' + id: '' // or generate an id as needed } ]; + + // Clear the input fields. new_start_date = ''; new_end_date = ''; new_notes = ''; @@ -669,13 +698,23 @@ on:change={() => (constrainDates = !constrainDates)} /> {/if} + All Day + (allDay = !allDay)} + />
- {#if !constrainDates} + {#if !allDay} { if (e.key === 'Enter') { @@ -685,10 +724,12 @@ }} /> { if (e.key === 'Enter') { e.preventDefault(); @@ -701,8 +742,8 @@ type="date" class="input input-bordered w-full" placeholder={$t('adventures.start_date')} - min={collection?.start_date} - max={collection?.end_date} + min={constrainDates ? fullStartDate : ''} + max={constrainDates ? fullEndDate : ''} bind:value={new_start_date} on:keydown={(e) => { if (e.key === 'Enter') { @@ -716,8 +757,8 @@ class="input input-bordered w-full" placeholder={$t('adventures.end_date')} bind:value={new_end_date} - min={collection?.start_date} - max={collection?.end_date} + min={constrainDates ? fullStartDate : ''} + max={constrainDates ? fullEndDate : ''} on:keydown={(e) => { if (e.key === 'Enter') { e.preventDefault(); @@ -742,6 +783,30 @@ >
+ +
{#if adventure.visits.length > 0} -

{$t('adventures.my_visits')}

+

{$t('adventures.my_visits')}

{#each adventure.visits as visit}
-
+

- {new Date(visit.start_date).toLocaleDateString(undefined, { - timeZone: 'UTC' - })} + {#if isAllDay(visit.start_date)} + + {new Date(visit.start_date).toLocaleDateString(undefined, { + timeZone: 'UTC' + })} + {:else} + + {new Date(visit.start_date).toLocaleDateString()} ({new Date( + visit.start_date + ).toLocaleTimeString()}) + {/if}

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

{new Date(visit.end_date).toLocaleDateString(undefined, { timeZone: 'UTC' })} + {#if !isAllDay(visit.end_date)} + ({new Date(visit.end_date).toLocaleTimeString()}) + {/if}

{/if} -
diff --git a/frontend/src/lib/components/TransportationModal.svelte b/frontend/src/lib/components/TransportationModal.svelte index 9012dd9..1d4a516 100644 --- a/frontend/src/lib/components/TransportationModal.svelte +++ b/frontend/src/lib/components/TransportationModal.svelte @@ -16,10 +16,15 @@ let constrainDates: boolean = false; + // Format date as local datetime + // Convert an ISO date to a datetime-local value in local time. function toLocalDatetime(value: string | null): string { if (!value) return ''; const date = new Date(value); - return date.toISOString().slice(0, 16); // Format: YYYY-MM-DDTHH:mm + // Adjust the time by subtracting the timezone offset. + date.setMinutes(date.getMinutes() - date.getTimezoneOffset()); + // Return format YYYY-MM-DDTHH:mm + return date.toISOString().slice(0, 16); } let transportation: Transportation = { @@ -185,6 +190,14 @@ return; } + // Convert local dates to UTC + if (transportation.date && !transportation.date.includes('Z')) { + transportation.date = new Date(transportation.date).toISOString(); + } + if (transportation.end_date && !transportation.end_date.includes('Z')) { + transportation.end_date = new Date(transportation.end_date).toISOString(); + } + if (transportation.type != 'plane') { transportation.flight_number = ''; } @@ -422,6 +435,29 @@
{/if} +
diff --git a/frontend/src/lib/index.ts b/frontend/src/lib/index.ts index fd21597..70aef59 100644 --- a/frontend/src/lib/index.ts +++ b/frontend/src/lib/index.ts @@ -338,6 +338,17 @@ export let LODGING_TYPES_ICONS = { other: '❓' }; +export let TRANSPORTATION_TYPES_ICONS = { + car: '🚗', + plane: '✈️', + train: '🚆', + bus: '🚌', + boat: '⛵', + bike: '🚲', + walking: '🚶', + other: '❓' +}; + // Helper to check if a given date string represents midnight (all-day) export function isAllDay(dateStr: string | string[]) { // Checks for the pattern "T00:00:00.000Z" diff --git a/frontend/src/locales/de.json b/frontend/src/locales/de.json index 56c84dc..c0d939e 100644 --- a/frontend/src/locales/de.json +++ b/frontend/src/locales/de.json @@ -247,7 +247,8 @@ "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" }, "home": { "desc_1": "Entdecken, planen und erkunden Sie mühelos", diff --git a/frontend/src/locales/en.json b/frontend/src/locales/en.json index 25a79de..4e32577 100644 --- a/frontend/src/locales/en.json +++ b/frontend/src/locales/en.json @@ -250,6 +250,7 @@ "show_map": "Show Map", "emoji_picker": "Emoji Picker", "download_calendar": "Download Calendar", + "all_day": "All Day", "date_information": "Date Information", "flight_information": "Flight Information", "out_of_range": "Not in itinerary date range", diff --git a/frontend/src/locales/es.json b/frontend/src/locales/es.json index 3814ec2..bd9b6a4 100644 --- a/frontend/src/locales/es.json +++ b/frontend/src/locales/es.json @@ -295,7 +295,8 @@ "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" }, "worldtravel": { "all": "Todo", diff --git a/frontend/src/locales/fr.json b/frontend/src/locales/fr.json index 3e6ec37..2523fe5 100644 --- a/frontend/src/locales/fr.json +++ b/frontend/src/locales/fr.json @@ -247,7 +247,8 @@ "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": "Toute la journée" }, "home": { "desc_1": "Découvrez, planifiez et explorez en toute simplicité", diff --git a/frontend/src/locales/it.json b/frontend/src/locales/it.json index a68ce99..216d121 100644 --- a/frontend/src/locales/it.json +++ b/frontend/src/locales/it.json @@ -247,7 +247,8 @@ "region": "Regione", "welcome_map_info": "Avventure pubbliche su questo server", "reservation_number": "Numero di prenotazione", - "open_in_maps": "Aperto in mappe" + "open_in_maps": "Aperto in mappe", + "all_day": "Tutto il giorno" }, "home": { "desc_1": "Scopri, pianifica ed esplora con facilità", diff --git a/frontend/src/locales/ko.json b/frontend/src/locales/ko.json index bfe761b..2099c0c 100644 --- a/frontend/src/locales/ko.json +++ b/frontend/src/locales/ko.json @@ -247,7 +247,8 @@ "region": "지역", "reservation_number": "예약 번호", "welcome_map_info": "이 서버의 공개 모험", - "open_in_maps": "지도에서 열립니다" + "open_in_maps": "지도에서 열립니다", + "all_day": "하루 종일" }, "auth": { "both_passwords_required": "두 암호 모두 필요합니다", diff --git a/frontend/src/locales/nl.json b/frontend/src/locales/nl.json index 4a783ec..6dbf2d7 100644 --- a/frontend/src/locales/nl.json +++ b/frontend/src/locales/nl.json @@ -247,7 +247,8 @@ "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" }, "home": { "desc_1": "Ontdek, plan en verken met gemak", diff --git a/frontend/src/locales/pl.json b/frontend/src/locales/pl.json index 3925de1..3eb73ac 100644 --- a/frontend/src/locales/pl.json +++ b/frontend/src/locales/pl.json @@ -295,7 +295,8 @@ "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ń" }, "worldtravel": { "country_list": "Lista krajów", diff --git a/frontend/src/locales/sv.json b/frontend/src/locales/sv.json index c6fd200..0baff34 100644 --- a/frontend/src/locales/sv.json +++ b/frontend/src/locales/sv.json @@ -247,7 +247,8 @@ "price": "Pris", "region": "Område", "reservation_number": "Bokningsnummer", - "open_in_maps": "Kappas in" + "open_in_maps": "Kappas in", + "all_day": "Hela dagen" }, "home": { "desc_1": "Upptäck, planera och utforska med lätthet", diff --git a/frontend/src/locales/zh.json b/frontend/src/locales/zh.json index 864d699..dfd5e8e 100644 --- a/frontend/src/locales/zh.json +++ b/frontend/src/locales/zh.json @@ -295,7 +295,8 @@ "lodging_information": "住宿信息", "price": "价格", "reservation_number": "预订号", - "open_in_maps": "在地图上打开" + "open_in_maps": "在地图上打开", + "all_day": "整天" }, "auth": { "forgot_password": "忘记密码?", diff --git a/frontend/src/routes/adventures/[id]/+page.svelte b/frontend/src/routes/adventures/[id]/+page.svelte index 17f1ae3..6d88e0e 100644 --- a/frontend/src/routes/adventures/[id]/+page.svelte +++ b/frontend/src/routes/adventures/[id]/+page.svelte @@ -456,12 +456,14 @@ {/if} - {$t('adventures.open_in_maps')} + {#if adventure.longitude && adventure.latitude} + {$t('adventures.open_in_maps')} + {/if} Date: Tue, 18 Mar 2025 18:16:25 -0400 Subject: [PATCH 04/79] feat: Implement chronological itinerary path visualization with GeoJSON for adventures, transportation, and lodging --- .../src/routes/collections/[id]/+page.svelte | 195 +++++++++++++++--- 1 file changed, 167 insertions(+), 28 deletions(-) diff --git a/frontend/src/routes/collections/[id]/+page.svelte b/frontend/src/routes/collections/[id]/+page.svelte index a63dbe5..b6ffac3 100644 --- a/frontend/src/routes/collections/[id]/+page.svelte +++ b/frontend/src/routes/collections/[id]/+page.svelte @@ -136,6 +136,66 @@ let adventures: Adventure[] = []; + // Add this after your existing MapLibre markers + + // Add this after your existing MapLibre markers + + // Create line data from orderedItems + $: lineData = createLineData(orderedItems); + + // Function to create GeoJSON line data from ordered items + function createLineData( + items: Array<{ + item: Adventure | Transportation | Lodging | Note | Checklist; + start: string; + end: string; + }> + ) { + if (items.length < 2) return null; + + const coordinates: [number, number][] = []; + + // Extract coordinates from each item + for (const orderItem of items) { + const item = orderItem.item; + + if ( + 'origin_longitude' in item && + 'origin_latitude' in item && + 'destination_longitude' in item && + 'destination_latitude' in item && + item.origin_longitude && + item.origin_latitude && + item.destination_longitude && + item.destination_latitude + ) { + // For Transportation, add both origin and destination points + coordinates.push([item.origin_longitude, item.origin_latitude]); + coordinates.push([item.destination_longitude, item.destination_latitude]); + } else if ('longitude' in item && 'latitude' in item && item.longitude && item.latitude) { + // Handle Adventure and Lodging types + coordinates.push([item.longitude, item.latitude]); + } + } + + // Only create line data if we have at least 2 coordinates + if (coordinates.length >= 2) { + return { + type: 'Feature' as const, + properties: { + name: 'Itinerary Path', + description: 'Path connecting chronological items' + }, + geometry: { + type: 'LineString' as const, + coordinates: coordinates + } + }; + } + + return null; + } + let numVisited: number = 0; let numAdventures: number = 0; @@ -169,6 +229,63 @@ } } + let orderedItems: Array<{ + item: Adventure | Transportation | Lodging; + type: 'adventure' | 'transportation' | 'lodging'; + start: string; // ISO date string + end: string; // ISO date string + }> = []; + + $: { + // Reset ordered items + orderedItems = []; + + // Add Adventures (using visit dates) + adventures.forEach((adventure) => { + adventure.visits.forEach((visit) => { + orderedItems.push({ + item: adventure, + start: visit.start_date, + end: visit.end_date, + type: 'adventure' + }); + }); + }); + + // Add Transportation + transportations.forEach((transport) => { + if (transport.date) { + // Only add if date exists + orderedItems.push({ + item: transport, + start: transport.date, + end: transport.end_date || transport.date, // Use end_date if available, otherwise use date, + type: 'transportation' + }); + } + }); + + // Add Lodging + lodging.forEach((lodging) => { + if (lodging.check_in) { + // Only add if check_in exists + orderedItems.push({ + item: lodging, + start: lodging.check_in, + end: lodging.check_out || lodging.check_in, // Use check_out if available, otherwise use check_in, + type: 'lodging' + }); + } + }); + + // Sort all items chronologically by start date + orderedItems.sort((a, b) => { + const dateA = new Date(a.start).getTime(); + const dateB = new Date(b.start).getTime(); + return dateA - dateB; + }); + } + $: { numAdventures = adventures.length; numVisited = adventures.filter((adventure) => adventure.is_visited).length; @@ -994,6 +1111,19 @@ {/if} {/each} + {#if lineData} + + + + {/if} {#each transportations as transportation} {#if transportation.origin_latitude && transportation.origin_longitude && transportation.destination_latitude && transportation.destination_longitude} @@ -1035,34 +1165,6 @@

- - - - - {/if} {/each} @@ -1286,6 +1388,43 @@ {/if} {/if} +{#each orderedItems as orderedItem} +

{orderedItem.type}

+ {#if orderedItem.type === 'adventure'} + {#if orderedItem.item && 'images' in orderedItem.item} + + {/if} + {/if} + {#if orderedItem.type === 'transportation' && orderedItem.item && 'origin_latitude' in orderedItem.item} + { + transportations = transportations.filter((t) => t.id != event.detail); + }} + on:edit={editTransportation} + {collection} + /> + {/if} + {#if orderedItem.type === 'lodging' && orderedItem.item && 'reservation_number' in orderedItem.item} + { + lodging = lodging.filter((t) => t.id != event.detail); + }} + on:edit={editLodging} + {collection} + /> + {/if} +{/each} + {data.props.adventure && data.props.adventure.name From f554bb8777281772ea7fc8cec40e71ae8d59023e Mon Sep 17 00:00:00 2001 From: Sean Morley <zipsm15@gmail.com> Date: Tue, 18 Mar 2025 21:07:34 -0400 Subject: [PATCH 05/79] feat: Enhance date handling in AdventureModal and related components for improved localization and all-day event support --- .../src/lib/components/AdventureModal.svelte | 15 ++-- frontend/src/lib/index.ts | 72 ++++++++++--------- .../src/routes/collections/[id]/+page.svelte | 13 ++-- 3 files changed, 54 insertions(+), 46 deletions(-) diff --git a/frontend/src/lib/components/AdventureModal.svelte b/frontend/src/lib/components/AdventureModal.svelte index 1321db0..3054687 100644 --- a/frontend/src/lib/components/AdventureModal.svelte +++ b/frontend/src/lib/components/AdventureModal.svelte @@ -834,11 +834,16 @@ </p> {#if visit.end_date && visit.end_date !== visit.start_date} <p> - {new Date(visit.end_date).toLocaleDateString(undefined, { - timeZone: 'UTC' - })} - {#if !isAllDay(visit.end_date)} - ({new Date(visit.end_date).toLocaleTimeString()}) + {#if isAllDay(visit.end_date)} + <!-- For all-day events, show just the date --> + {new Date(visit.end_date).toLocaleDateString(undefined, { + timeZone: 'UTC' + })} + {:else} + <!-- For timed events, show date and time --> + {new Date(visit.end_date).toLocaleDateString()} ({new Date( + visit.end_date + ).toLocaleTimeString()}) {/if} </p> {/if} diff --git a/frontend/src/lib/index.ts b/frontend/src/lib/index.ts index 70aef59..af68de2 100644 --- a/frontend/src/lib/index.ts +++ b/frontend/src/lib/index.ts @@ -70,23 +70,23 @@ 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]; + const adventureDate = getLocalDateString(new Date(visit.start_date)); if (visit.end_date) { const endDate = new Date(visit.end_date).toISOString().split('T')[0]; // 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.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 adventure date range if (dateString >= adventureDate && dateString <= endDate) { @@ -116,22 +116,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) { @@ -150,6 +150,13 @@ export function groupTransportationsByDate( return groupedTransportations; } +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}`; +} + export function groupLodgingByDate( transportations: Lodging[], startDate: Date, @@ -157,35 +164,32 @@ export function groupLodgingByDate( ): Record<string, Lodging[]> { const groupedTransportations: Record<string, Lodging[]> = {}; - // 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 +205,18 @@ export function groupNotesByDate( ): Record<string, Note[]> { const groupedNotes: Record<string, Note[]> = {}; - // 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 +233,18 @@ export function groupChecklistsByDate( ): Record<string, Checklist[]> { const groupedChecklists: Record<string, Checklist[]> = {}; - // 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); } diff --git a/frontend/src/routes/collections/[id]/+page.svelte b/frontend/src/routes/collections/[id]/+page.svelte index b6ffac3..1a69fdd 100644 --- a/frontend/src/routes/collections/[id]/+page.svelte +++ b/frontend/src/routes/collections/[id]/+page.svelte @@ -935,24 +935,25 @@ {@const dateString = adjustedDate.toISOString().split('T')[0]} {@const dayAdventures = - groupAdventuresByDate(adventures, new Date(collection.start_date), numberOfDays)[ + groupAdventuresByDate(adventures, new Date(collection.start_date), numberOfDays + 1)[ dateString ] || []} {@const dayTransportations = groupTransportationsByDate( transportations, new Date(collection.start_date), - numberOfDays + numberOfDays + 1 )[dateString] || []} {@const dayLodging = - groupLodgingByDate(lodging, new Date(collection.start_date), numberOfDays)[ + groupLodgingByDate(lodging, new Date(collection.start_date), numberOfDays + 1)[ dateString ] || []} {@const dayNotes = - groupNotesByDate(notes, new Date(collection.start_date), numberOfDays)[dateString] || - []} + groupNotesByDate(notes, new Date(collection.start_date), numberOfDays + 1)[ + dateString + ] || []} {@const dayChecklists = - groupChecklistsByDate(checklists, new Date(collection.start_date), numberOfDays)[ + groupChecklistsByDate(checklists, new Date(collection.start_date), numberOfDays + 1)[ dateString ] || []} From 771579ef3d83d158f6acaf53b2b6ccc7b8a5e996 Mon Sep 17 00:00:00 2001 From: Sean Morley <zipsm15@gmail.com> Date: Thu, 20 Mar 2025 10:26:41 -0400 Subject: [PATCH 06/79] feat: Improve adventure date grouping to handle all-day events and enhance date formatting --- frontend/src/lib/index.ts | 80 +++++++++++++++------- frontend/src/routes/signup/+page.server.ts | 2 - 2 files changed, 56 insertions(+), 26 deletions(-) diff --git a/frontend/src/lib/index.ts b/frontend/src/lib/index.ts index af68de2..d66abec 100644 --- a/frontend/src/lib/index.ts +++ b/frontend/src/lib/index.ts @@ -78,26 +78,57 @@ export function groupAdventuresByDate( adventures.forEach((adventure) => { adventure.visits.forEach((visit) => { if (visit.start_date) { - const adventureDate = getLocalDateString(new Date(visit.start_date)); - 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.setDate(startDate.getDate() + i); - const dateString = getLocalDateString(currentDate); + 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 if (groupedAdventures[adventureDate]) { - // If there's no end date, add adventure to the start date only - groupedAdventures[adventureDate].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); + } + } } } }); @@ -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, @@ -150,13 +195,6 @@ export function groupTransportationsByDate( return groupedTransportations; } -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}`; -} - export function groupLodgingByDate( transportations: Lodging[], startDate: Date, @@ -351,12 +389,6 @@ export let TRANSPORTATION_TYPES_ICONS = { other: '❓' }; -// Helper to check if a given date string represents midnight (all-day) -export function isAllDay(dateStr: string | string[]) { - // Checks for the pattern "T00:00:00.000Z" - return dateStr.includes('T00:00:00Z') || dateStr.includes('T00:00:00.000Z'); -} - 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/routes/signup/+page.server.ts b/frontend/src/routes/signup/+page.server.ts index 1e39414..61f0852 100644 --- a/frontend/src/routes/signup/+page.server.ts +++ b/frontend/src/routes/signup/+page.server.ts @@ -74,8 +74,6 @@ export const actions: Actions = { } else { const setCookieHeader = loginFetch.headers.get('Set-Cookie'); - console.log('setCookieHeader:', setCookieHeader); - if (setCookieHeader) { // Regular expression to match sessionid cookie and its expiry const sessionIdRegex = /sessionid=([^;]+).*?expires=([^;]+)/; From 1042a3edcc93ff184d11b4b9addff7c54f9b3756 Mon Sep 17 00:00:00 2001 From: Sean Morley <zipsm15@gmail.com> Date: Thu, 20 Mar 2025 22:08:22 -0400 Subject: [PATCH 07/79] refactor: Remove debug print statement from NoPasswordAuthBackend authentication method --- backend/server/users/backends.py | 1 - .../src/routes/collections/[id]/+page.svelte | 376 +++++++++++------- 2 files changed, 224 insertions(+), 153 deletions(-) diff --git a/backend/server/users/backends.py b/backend/server/users/backends.py index a099f11..f9291f0 100644 --- a/backend/server/users/backends.py +++ b/backend/server/users/backends.py @@ -3,7 +3,6 @@ from allauth.socialaccount.models import SocialAccount 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: diff --git a/frontend/src/routes/collections/[id]/+page.svelte b/frontend/src/routes/collections/[id]/+page.svelte index 1a69fdd..df8ad0a 100644 --- a/frontend/src/routes/collections/[id]/+page.svelte +++ b/frontend/src/routes/collections/[id]/+page.svelte @@ -133,6 +133,7 @@ } let currentView: string = 'itinerary'; + let currentItineraryView: string = 'date'; let adventures: Adventure[] = []; @@ -303,6 +304,7 @@ } else { notFound = true; } + if (collection.start_date && collection.end_date) { numberOfDays = Math.floor( @@ -923,129 +925,233 @@ })}</span > </p> + <div class="join mt-2"> + <input + class="join-item btn btn-neutral" + type="radio" + name="options" + aria-label="Date Itinerary" + checked={currentItineraryView == 'date'} + on:change={() => (currentItineraryView = 'date')} + /> + <input + class="join-item btn btn-neutral" + type="radio" + name="options" + aria-label="Ordered Itinerary" + checked={currentItineraryView == 'ordered'} + on:change={() => (currentItineraryView = 'ordered')} + /> + </div> </div> </div> </div> - <div class="container mx-auto px-4"> - {#each Array(numberOfDays) as _, i} - {@const startDate = new Date(collection.start_date)} - {@const tempDate = new Date(startDate.getTime())} - {@const adjustedDate = new Date(tempDate.setUTCDate(tempDate.getUTCDate() + i))} - {@const dateString = adjustedDate.toISOString().split('T')[0]} + {#if currentItineraryView == 'date'} + <div class="container mx-auto px-4"> + {#each Array(numberOfDays) as _, i} + {@const startDate = new Date(collection.start_date)} + {@const tempDate = new Date(startDate.getTime())} + {@const adjustedDate = new Date(tempDate.setUTCDate(tempDate.getUTCDate() + i))} + {@const dateString = adjustedDate.toISOString().split('T')[0]} - {@const dayAdventures = - groupAdventuresByDate(adventures, new Date(collection.start_date), numberOfDays + 1)[ - dateString - ] || []} - {@const dayTransportations = - groupTransportationsByDate( - transportations, - new Date(collection.start_date), - numberOfDays + 1 - )[dateString] || []} - {@const dayLodging = - groupLodgingByDate(lodging, new Date(collection.start_date), numberOfDays + 1)[ - dateString - ] || []} - {@const dayNotes = - groupNotesByDate(notes, new Date(collection.start_date), numberOfDays + 1)[ - dateString - ] || []} - {@const dayChecklists = - groupChecklistsByDate(checklists, new Date(collection.start_date), numberOfDays + 1)[ - dateString - ] || []} + {@const dayAdventures = + groupAdventuresByDate(adventures, new Date(collection.start_date), numberOfDays + 1)[ + dateString + ] || []} + {@const dayTransportations = + groupTransportationsByDate( + transportations, + new Date(collection.start_date), + numberOfDays + 1 + )[dateString] || []} + {@const dayLodging = + groupLodgingByDate(lodging, new Date(collection.start_date), numberOfDays + 1)[ + dateString + ] || []} + {@const dayNotes = + groupNotesByDate(notes, new Date(collection.start_date), numberOfDays + 1)[ + dateString + ] || []} + {@const dayChecklists = + groupChecklistsByDate(checklists, new Date(collection.start_date), numberOfDays + 1)[ + dateString + ] || []} - <div class="card bg-base-100 shadow-xl my-8"> - <div class="card-body bg-base-200"> - <h2 class="card-title text-3xl justify-center g"> - {$t('adventures.day')} - {i + 1} - <div class="badge badge-lg"> - {adjustedDate.toLocaleDateString(undefined, { timeZone: 'UTC' })} + <div class="card bg-base-100 shadow-xl my-8"> + <div class="card-body bg-base-200"> + <h2 class="card-title text-3xl justify-center g"> + {$t('adventures.day')} + {i + 1} + <div class="badge badge-lg"> + {adjustedDate.toLocaleDateString(undefined, { timeZone: 'UTC' })} + </div> + </h2> + + <div class="divider"></div> + + <div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4"> + {#if dayAdventures.length > 0} + {#each dayAdventures as adventure} + <AdventureCard + user={data.user} + on:edit={editAdventure} + on:delete={deleteAdventure} + {adventure} + /> + {/each} + {/if} + {#if dayTransportations.length > 0} + {#each dayTransportations as transportation} + <TransportationCard + {transportation} + user={data?.user} + on:delete={(event) => { + transportations = transportations.filter((t) => t.id != event.detail); + }} + on:edit={(event) => { + transportationToEdit = event.detail; + isShowingTransportationModal = true; + }} + /> + {/each} + {/if} + {#if dayNotes.length > 0} + {#each dayNotes as note} + <NoteCard + {note} + user={data.user || null} + on:edit={(event) => { + noteToEdit = event.detail; + isNoteModalOpen = true; + }} + on:delete={(event) => { + notes = notes.filter((n) => n.id != event.detail); + }} + /> + {/each} + {/if} + {#if dayLodging.length > 0} + {#each dayLodging as hotel} + <LodgingCard + lodging={hotel} + user={data?.user} + on:delete={(event) => { + lodging = lodging.filter((t) => t.id != event.detail); + }} + on:edit={editLodging} + /> + {/each} + {/if} + {#if dayChecklists.length > 0} + {#each dayChecklists as checklist} + <ChecklistCard + {checklist} + user={data.user || null} + on:delete={(event) => { + notes = notes.filter((n) => n.id != event.detail); + }} + on:edit={(event) => { + checklistToEdit = event.detail; + isShowingChecklistModal = true; + }} + /> + {/each} + {/if} </div> - </h2> - <div class="divider"></div> - - <div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4"> - {#if dayAdventures.length > 0} - {#each dayAdventures as adventure} - <AdventureCard - user={data.user} - on:edit={editAdventure} - on:delete={deleteAdventure} - {adventure} - /> - {/each} - {/if} - {#if dayTransportations.length > 0} - {#each dayTransportations as transportation} - <TransportationCard - {transportation} - user={data?.user} - on:delete={(event) => { - transportations = transportations.filter((t) => t.id != event.detail); - }} - on:edit={(event) => { - transportationToEdit = event.detail; - isShowingTransportationModal = true; - }} - /> - {/each} - {/if} - {#if dayNotes.length > 0} - {#each dayNotes as note} - <NoteCard - {note} - user={data.user || null} - on:edit={(event) => { - noteToEdit = event.detail; - isNoteModalOpen = true; - }} - on:delete={(event) => { - notes = notes.filter((n) => n.id != event.detail); - }} - /> - {/each} - {/if} - {#if dayLodging.length > 0} - {#each dayLodging as hotel} - <LodgingCard - lodging={hotel} - user={data?.user} - on:delete={(event) => { - lodging = lodging.filter((t) => t.id != event.detail); - }} - on:edit={editLodging} - /> - {/each} - {/if} - {#if dayChecklists.length > 0} - {#each dayChecklists as checklist} - <ChecklistCard - {checklist} - user={data.user || null} - on:delete={(event) => { - notes = notes.filter((n) => n.id != event.detail); - }} - on:edit={(event) => { - checklistToEdit = event.detail; - isShowingChecklistModal = true; - }} - /> - {/each} + {#if dayAdventures.length == 0 && dayTransportations.length == 0 && dayNotes.length == 0 && dayChecklists.length == 0 && dayLodging.length == 0} + <p class="text-center text-lg mt-2 italic">{$t('adventures.nothing_planned')}</p> {/if} </div> - - {#if dayAdventures.length == 0 && dayTransportations.length == 0 && dayNotes.length == 0 && dayChecklists.length == 0 && dayLodging.length == 0} - <p class="text-center text-lg mt-2 italic">{$t('adventures.nothing_planned')}</p> + </div> + {/each} + </div> + {:else} + <div class="container mx-auto px-4 py-8"> + <div class="flex flex-col items-center"> + <div class="w-full max-w-4xl relative"> + <!-- Vertical timeline line that spans the entire height --> + <div class="absolute left-8 top-0 bottom-0 w-1 bg-primary"></div> + <ul class="relative"> + {#each orderedItems as orderedItem, index} + <li class="relative pl-20 mb-8"> + <!-- Timeline Icon --> + <div + class="absolute left-0 top-0 flex items-center justify-center w-16 h-16 bg-base-200 rounded-full border-2 border-primary" + > + {#if orderedItem.type === 'adventure' && orderedItem.item && 'category' in orderedItem.item && orderedItem.item.category && 'icon' in orderedItem.item.category} + <span class="text-2xl">{orderedItem.item.category.icon}</span> + {:else if orderedItem.type === 'transportation' && orderedItem.item && 'origin_latitude' in orderedItem.item} + <span class="text-2xl">{getTransportationEmoji(orderedItem.item.type)}</span + > + {:else if orderedItem.type === 'lodging' && orderedItem.item && 'reservation_number' in orderedItem.item} + <span class="text-2xl">{getLodgingIcon(orderedItem.item.type)}</span> + {/if} + </div> + <!-- Card Content --> + <div class="bg-base-200 p-6 rounded-lg shadow-lg"> + <div class="flex justify-between items-center mb-4"> + <span class="badge badge-lg">{orderedItem.type}</span> + <div class="text-sm opacity-80 text-right"> + {new Date(orderedItem.start).toLocaleDateString(undefined, { + month: 'short', + day: 'numeric' + })} + {#if orderedItem.start !== orderedItem.end} + <div> + {new Date(orderedItem.start).toLocaleTimeString(undefined, { + hour: '2-digit', + minute: '2-digit' + })} + </div> + {:else} + <p>{$t('adventures.all_day')} ⏱️</p> + {/if} + </div> + </div> + {#if orderedItem.type === 'adventure' && orderedItem.item && 'images' in orderedItem.item} + <AdventureCard + user={data.user} + on:edit={editAdventure} + on:delete={deleteAdventure} + adventure={orderedItem.item} + {collection} + /> + {:else if orderedItem.type === 'transportation' && orderedItem.item && 'origin_latitude' in orderedItem.item} + <TransportationCard + transportation={orderedItem.item} + user={data?.user} + on:delete={(event) => { + transportations = transportations.filter((t) => t.id != event.detail); + }} + on:edit={editTransportation} + {collection} + /> + {:else if orderedItem.type === 'lodging' && orderedItem.item && 'reservation_number' in orderedItem.item} + <LodgingCard + lodging={orderedItem.item} + user={data?.user} + on:delete={(event) => { + lodging = lodging.filter((t) => t.id != event.detail); + }} + on:edit={editLodging} + {collection} + /> + {/if} + </div> + </li> + {/each} + </ul> + {#if orderedItems.length === 0} + <div class="alert alert-info"> + <p class="text-center text-lg">{$t('adventures.nothing_planned')}</p> + </div> {/if} </div> </div> - {/each} - </div> + </div> + {/if} {/if} {/if} @@ -1331,13 +1437,16 @@ {recomendation.name || $t('recomendations.recommendation')} </h2> <div class="badge badge-primary">{recomendation.tag}</div> - {#if recomendation.address} + {#if recomendation.address && (recomendation.address.housenumber || recomendation.address.street || recomendation.address.city || recomendation.address.state || recomendation.address.postcode)} <p class="text-md"> <strong>{$t('recomendations.address')}:</strong> - {recomendation.address.housenumber} - {recomendation.address.street}, {recomendation.address.city}, {recomendation - .address.state} - {recomendation.address.postcode} + {#if recomendation.address.housenumber}{recomendation.address + .housenumber}{/if} + {#if recomendation.address.street} + {recomendation.address.street}{/if} + {#if recomendation.address.city}, {recomendation.address.city}{/if} + {#if recomendation.address.state}, {recomendation.address.state}{/if} + {#if recomendation.address.postcode}, {recomendation.address.postcode}{/if} </p> {/if} {#if recomendation.contact} @@ -1389,43 +1498,6 @@ {/if} {/if} -{#each orderedItems as orderedItem} - <p>{orderedItem.type}</p> - {#if orderedItem.type === 'adventure'} - {#if orderedItem.item && 'images' in orderedItem.item} - <AdventureCard - user={data.user} - on:edit={editAdventure} - on:delete={deleteAdventure} - adventure={orderedItem.item} - {collection} - /> - {/if} - {/if} - {#if orderedItem.type === 'transportation' && orderedItem.item && 'origin_latitude' in orderedItem.item} - <TransportationCard - transportation={orderedItem.item} - user={data?.user} - on:delete={(event) => { - transportations = transportations.filter((t) => t.id != event.detail); - }} - on:edit={editTransportation} - {collection} - /> - {/if} - {#if orderedItem.type === 'lodging' && orderedItem.item && 'reservation_number' in orderedItem.item} - <LodgingCard - lodging={orderedItem.item} - user={data?.user} - on:delete={(event) => { - lodging = lodging.filter((t) => t.id != event.detail); - }} - on:edit={editLodging} - {collection} - /> - {/if} -{/each} - <svelte:head> <title >{data.props.adventure && data.props.adventure.name From f79b06f6b3f35a4e8ceb1d77edcdae536be2e44c Mon Sep 17 00:00:00 2001 From: Sean Morley <zipsm15@gmail.com> Date: Thu, 20 Mar 2025 22:28:23 -0400 Subject: [PATCH 08/79] feat: Add troubleshooting guide for unresponsive login and registration, enhance collection modal alerts, and improve localization for itinerary features --- documentation/.vitepress/config.mts | 4 +++ .../troubleshooting/login_unresponsive.md | 19 ++++++++++++++ .../src/lib/components/CollectionModal.svelte | 25 +++++++++++++++++-- frontend/src/locales/en.json | 4 +++ .../src/routes/collections/[id]/+page.svelte | 10 +++++--- frontend/src/routes/worldtravel/+page.svelte | 8 ++++++ 6 files changed, 64 insertions(+), 6 deletions(-) create mode 100644 documentation/docs/troubleshooting/login_unresponsive.md diff --git a/documentation/.vitepress/config.mts b/documentation/.vitepress/config.mts index dbd9299..4263aca 100644 --- a/documentation/.vitepress/config.mts +++ b/documentation/.vitepress/config.mts @@ -134,6 +134,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", diff --git a/documentation/docs/troubleshooting/login_unresponsive.md b/documentation/docs/troubleshooting/login_unresponsive.md new file mode 100644 index 0000000..aecf431 --- /dev/null +++ b/documentation/docs/troubleshooting/login_unresponsive.md @@ -0,0 +1,19 @@ +# 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 contianer 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/frontend/src/lib/components/CollectionModal.svelte b/frontend/src/lib/components/CollectionModal.svelte index e5cb294..93ec776 100644 --- a/frontend/src/lib/components/CollectionModal.svelte +++ b/frontend/src/lib/components/CollectionModal.svelte @@ -189,10 +189,31 @@ </div> </div> </div> - <!-- Form Actions --> + + {#if !collection.start_date && !collection.end_date} + <div class="mt-4"> + <div role="alert" class="alert alert-neutral"> + <svg + xmlns="http://www.w3.org/2000/svg" + fill="none" + viewBox="0 0 24 24" + class="h-6 w-6 shrink-0 stroke-current" + > + <path + stroke-linecap="round" + stroke-linejoin="round" + stroke-width="2" + d="M13 16h-1v-4h-1m1-4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z" + ></path> + </svg> + <span>{$t('adventures.collection_no_start_end_date')}</span> + </div> + </div> + {/if} + <div class="mt-4"> <button type="submit" class="btn btn-primary"> - {$t('adventures.save_next')} + {$t('notes.save')} </button> <button type="button" class="btn" on:click={close}> {$t('about.close')} diff --git a/frontend/src/locales/en.json b/frontend/src/locales/en.json index 4e32577..72d1883 100644 --- a/frontend/src/locales/en.json +++ b/frontend/src/locales/en.json @@ -131,6 +131,7 @@ "search_for_location": "Search for a location", "clear_map": "Clear map", "search_results": "Searh 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", @@ -251,6 +252,9 @@ "emoji_picker": "Emoji Picker", "download_calendar": "Download Calendar", "all_day": "All Day", + "ordered_itinerary": "Ordered Itinerary", + "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", diff --git a/frontend/src/routes/collections/[id]/+page.svelte b/frontend/src/routes/collections/[id]/+page.svelte index df8ad0a..4bdba31 100644 --- a/frontend/src/routes/collections/[id]/+page.svelte +++ b/frontend/src/routes/collections/[id]/+page.svelte @@ -930,7 +930,7 @@ class="join-item btn btn-neutral" type="radio" name="options" - aria-label="Date Itinerary" + aria-label={$t('adventures.date_itinerary')} checked={currentItineraryView == 'date'} on:change={() => (currentItineraryView = 'date')} /> @@ -938,7 +938,7 @@ class="join-item btn btn-neutral" type="radio" name="options" - aria-label="Ordered Itinerary" + aria-label={$t('adventures.ordered_itinerary')} checked={currentItineraryView == 'ordered'} on:change={() => (currentItineraryView = 'ordered')} /> @@ -1072,7 +1072,9 @@ <div class="flex flex-col items-center"> <div class="w-full max-w-4xl relative"> <!-- Vertical timeline line that spans the entire height --> - <div class="absolute left-8 top-0 bottom-0 w-1 bg-primary"></div> + {#if orderedItems.length > 0} + <div class="absolute left-8 top-0 bottom-0 w-1 bg-primary"></div> + {/if} <ul class="relative"> {#each orderedItems as orderedItem, index} <li class="relative pl-20 mb-8"> @@ -1145,7 +1147,7 @@ </ul> {#if orderedItems.length === 0} <div class="alert alert-info"> - <p class="text-center text-lg">{$t('adventures.nothing_planned')}</p> + <p class="text-center text-lg">{$t('adventures.no_ordered_items')}</p> </div> {/if} </div> diff --git a/frontend/src/routes/worldtravel/+page.svelte b/frontend/src/routes/worldtravel/+page.svelte index dad43a1..aeabc14 100644 --- a/frontend/src/routes/worldtravel/+page.svelte +++ b/frontend/src/routes/worldtravel/+page.svelte @@ -164,6 +164,14 @@ {#if filteredCountries.length === 0} <p class="text-center font-bold text-2xl mt-12">{$t('worldtravel.no_countries_found')}</p> + + <div class="text-center mt-4"> + <a + class="link link-primary" + href="https://adventurelog.app/docs/configuration/updating.html#updating-the-region-data" + target="_blank">{$t('settings.documentation_link')}</a + > + </div> {/if} <svelte:head> From 4ccfa6e42ce39389cdff5528b9212f747899b710 Mon Sep 17 00:00:00 2001 From: ClumsyAdmin <59402569+ClumsyAdmin@users.noreply.github.com> Date: Fri, 21 Mar 2025 12:02:23 -0400 Subject: [PATCH 09/79] refactor docker startup to use supervisord --- backend/Dockerfile | 9 ++++++--- backend/entrypoint.sh | 7 +++++-- backend/supervisord.conf | 10 ++++++++++ frontend/startup.sh | 2 +- 4 files changed, 22 insertions(+), 6 deletions(-) create mode 100644 backend/supervisord.conf diff --git a/backend/Dockerfile b/backend/Dockerfile index aa0f9f4..fae48fb 100644 --- a/backend/Dockerfile +++ b/backend/Dockerfile @@ -12,7 +12,7 @@ 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 +31,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 +44,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 9ca926c..780a182 100644 --- a/backend/entrypoint.sh +++ b/backend/entrypoint.sh @@ -62,5 +62,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 0.0.0.0:8000 \ + --workers 4 \ + --timeout 120 diff --git a/backend/supervisord.conf b/backend/supervisord.conf new file mode 100644 index 0000000..7657f7f --- /dev/null +++ b/backend/supervisord.conf @@ -0,0 +1,10 @@ +[supervisord] +nodaemon=true + +[program:nginx] +command=/usr/sbin/nginx -g "daemon off;" +autorestart=true + +[program:gunicorn] +command=/code/entrypoint.sh +autorestart=true diff --git a/frontend/startup.sh b/frontend/startup.sh index 1b611e3..67dbb1e 100644 --- a/frontend/startup.sh +++ b/frontend/startup.sh @@ -2,4 +2,4 @@ echo "The origin to be set is: $ORIGIN" # Start the application -ORIGIN=$ORIGIN node build \ No newline at end of file +ORIGIN=$ORIGIN exec node build From db63b6e7d8b65f22b89ac021b6ad708eb6373d5c Mon Sep 17 00:00:00 2001 From: Sean Morley <zipsm15@gmail.com> Date: Fri, 21 Mar 2025 13:35:29 -0400 Subject: [PATCH 10/79] feat: Add usage guide for AdventureLog and enhance overview with personal message from the maintainer --- documentation/.vitepress/config.mts | 10 +++++++ .../docs/intro/adventurelog_overview.md | 4 ++- documentation/docs/usage/usage.md | 29 +++++++++++++++++++ 3 files changed, 42 insertions(+), 1 deletion(-) create mode 100644 documentation/docs/usage/usage.md diff --git a/documentation/.vitepress/config.mts b/documentation/.vitepress/config.mts index 4263aca..4877f62 100644 --- a/documentation/.vitepress/config.mts +++ b/documentation/.vitepress/config.mts @@ -84,6 +84,16 @@ export default defineConfig({ }, ], }, + { + text: "Usage", + collapsed: false, + items: [ + { + text: "How to use AdventureLog", + link: "/docs/usage/usage", + }, + ], + }, { text: "Configuration", collapsed: false, diff --git a/documentation/docs/intro/adventurelog_overview.md b/documentation/docs/intro/adventurelog_overview.md index 310237f..cfb30f4 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/usage/usage.md b/documentation/docs/usage/usage.md new file mode 100644 index 0000000..8cf0129 --- /dev/null +++ b/documentation/docs/usage/usage.md @@ -0,0 +1,29 @@ +# 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 uploded 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. From 794df82ec62f7b99ab0262e880bdfeb1a1cd80a8 Mon Sep 17 00:00:00 2001 From: Sean Morley <zipsm15@gmail.com> Date: Fri, 21 Mar 2025 16:30:03 -0400 Subject: [PATCH 11/79] feat: Enhance AdventureModal date handling for all-day events and improve localization in collections page --- .../src/lib/components/AdventureModal.svelte | 59 +++++++++++++++++-- .../src/routes/collections/[id]/+page.svelte | 32 +++++++++- 2 files changed, 86 insertions(+), 5 deletions(-) diff --git a/frontend/src/lib/components/AdventureModal.svelte b/frontend/src/lib/components/AdventureModal.svelte index 3054687..85db0a4 100644 --- a/frontend/src/lib/components/AdventureModal.svelte +++ b/frontend/src/lib/components/AdventureModal.svelte @@ -8,12 +8,16 @@ 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(); @@ -742,8 +746,8 @@ type="date" class="input input-bordered w-full" placeholder={$t('adventures.start_date')} - min={constrainDates ? fullStartDate : ''} - max={constrainDates ? fullEndDate : ''} + min={constrainDates ? fullStartDateOnly : ''} + max={constrainDates ? fullEndDateOnly : ''} bind:value={new_start_date} on:keydown={(e) => { if (e.key === 'Enter') { @@ -757,8 +761,8 @@ class="input input-bordered w-full" placeholder={$t('adventures.end_date')} bind:value={new_end_date} - min={constrainDates ? fullStartDate : ''} - max={constrainDates ? fullEndDate : ''} + min={constrainDates ? fullStartDateOnly : ''} + max={constrainDates ? fullEndDateOnly : ''} on:keydown={(e) => { if (e.key === 'Enter') { e.preventDefault(); @@ -848,6 +852,53 @@ </p> {/if} <div> + <button + type="button" + class="btn btn-sm btn-neutral" + on:click={() => { + // Determine if this is an all-day event + const isAllDayEvent = isAllDay(visit.start_date); + allDay = isAllDayEvent; + + if (isAllDayEvent) { + // For all-day events, use date only + new_start_date = visit.start_date.split('T')[0]; + new_end_date = visit.end_date.split('T')[0]; + } else { + // For timed events, format properly for datetime-local input + const startDate = new Date(visit.start_date); + const endDate = new Date(visit.end_date); + + // Format as yyyy-MM-ddThh:mm + new_start_date = + startDate.getFullYear() + + '-' + + String(startDate.getMonth() + 1).padStart(2, '0') + + '-' + + String(startDate.getDate()).padStart(2, '0') + + 'T' + + String(startDate.getHours()).padStart(2, '0') + + ':' + + String(startDate.getMinutes()).padStart(2, '0'); + + new_end_date = + endDate.getFullYear() + + '-' + + String(endDate.getMonth() + 1).padStart(2, '0') + + '-' + + String(endDate.getDate()).padStart(2, '0') + + 'T' + + String(endDate.getHours()).padStart(2, '0') + + ':' + + String(endDate.getMinutes()).padStart(2, '0'); + } + + new_notes = visit.notes; + adventure.visits = adventure.visits.filter((v) => v !== visit); + }} + > + {$t('lodging.edit')} + </button> <button type="button" class="btn btn-sm btn-error" diff --git a/frontend/src/routes/collections/[id]/+page.svelte b/frontend/src/routes/collections/[id]/+page.svelte index 4bdba31..2578384 100644 --- a/frontend/src/routes/collections/[id]/+page.svelte +++ b/frontend/src/routes/collections/[id]/+page.svelte @@ -1094,7 +1094,7 @@ <!-- Card Content --> <div class="bg-base-200 p-6 rounded-lg shadow-lg"> <div class="flex justify-between items-center mb-4"> - <span class="badge badge-lg">{orderedItem.type}</span> + <span class="badge badge-lg">{$t(`adventures.${orderedItem.type}`)}</span> <div class="text-sm opacity-80 text-right"> {new Date(orderedItem.start).toLocaleDateString(undefined, { month: 'short', @@ -1106,6 +1106,36 @@ hour: '2-digit', minute: '2-digit' })} + - + {new Date(orderedItem.end).toLocaleTimeString(undefined, { + hour: '2-digit', + minute: '2-digit' + })} + </div> + <div> + <!-- Duration --> + {Math.round( + (new Date(orderedItem.end).getTime() - + new Date(orderedItem.start).getTime()) / + 1000 / + 60 / + 60 + )}h + {Math.round( + ((new Date(orderedItem.end).getTime() - + new Date(orderedItem.start).getTime()) / + 1000 / + 60 / + 60 - + Math.floor( + (new Date(orderedItem.end).getTime() - + new Date(orderedItem.start).getTime()) / + 1000 / + 60 / + 60 + )) * + 60 + )}m </div> {:else} <p>{$t('adventures.all_day')} ⏱️</p> From fe25f8e2c8574fed89a009d631b8d5b2d0b9b932 Mon Sep 17 00:00:00 2001 From: Sean Morley <zipsm15@gmail.com> Date: Fri, 21 Mar 2025 17:31:33 -0400 Subject: [PATCH 12/79] feat: Add start_date to collection ordering and enhance localization for itinerary features --- .../adventures/views/collection_view.py | 8 +++- frontend/src/locales/de.json | 6 ++- frontend/src/locales/es.json | 6 ++- frontend/src/locales/fr.json | 6 ++- frontend/src/locales/it.json | 6 ++- frontend/src/locales/ko.json | 6 ++- frontend/src/locales/nl.json | 6 ++- frontend/src/locales/pl.json | 6 ++- frontend/src/locales/sv.json | 6 ++- frontend/src/locales/zh.json | 6 ++- .../src/routes/collections/+page.server.ts | 4 +- frontend/src/routes/collections/+page.svelte | 39 +++++++++++++------ 12 files changed, 82 insertions(+), 23 deletions(-) diff --git a/backend/server/adventures/views/collection_view.py b/backend/server/adventures/views/collection_view.py index f0529ee..2c46dc5 100644 --- a/backend/server/adventures/views/collection_view.py +++ b/backend/server/adventures/views/collection_view.py @@ -22,7 +22,7 @@ class CollectionViewSet(viewsets.ModelViewSet): 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', 'upated_at', 'start_date'] if order_by not in valid_order_by: order_by = 'updated_at' @@ -35,6 +35,12 @@ class CollectionViewSet(viewsets.ModelViewSet): 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/frontend/src/locales/de.json b/frontend/src/locales/de.json index c0d939e..13a02ad 100644 --- a/frontend/src/locales/de.json +++ b/frontend/src/locales/de.json @@ -248,7 +248,11 @@ "reservation_number": "Reservierungsnummer", "welcome_map_info": "Frei zugängliche Abenteuer auf diesem Server", "open_in_maps": "In Karten öffnen", - "all_day": "Den ganzen Tag" + "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" }, "home": { "desc_1": "Entdecken, planen und erkunden Sie mühelos", diff --git a/frontend/src/locales/es.json b/frontend/src/locales/es.json index bd9b6a4..c82df3f 100644 --- a/frontend/src/locales/es.json +++ b/frontend/src/locales/es.json @@ -296,7 +296,11 @@ "reservation_number": "Número de reserva", "welcome_map_info": "Aventuras públicas en este servidor", "open_in_maps": "Abrir en mapas", - "all_day": "Todo el día" + "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" }, "worldtravel": { "all": "Todo", diff --git a/frontend/src/locales/fr.json b/frontend/src/locales/fr.json index 2523fe5..249243d 100644 --- a/frontend/src/locales/fr.json +++ b/frontend/src/locales/fr.json @@ -248,7 +248,11 @@ "reservation_number": "Numéro de réservation", "welcome_map_info": "Aventures publiques sur ce serveur", "open_in_maps": "Ouvert dans les cartes", - "all_day": "Toute la journée" + "all_day": "Toute la journée", + "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 de date", + "no_ordered_items": "Ajoutez des articles avec des dates à la collection pour les voir ici.", + "ordered_itinerary": "Itinéraire ordonné" }, "home": { "desc_1": "Découvrez, planifiez et explorez en toute simplicité", diff --git a/frontend/src/locales/it.json b/frontend/src/locales/it.json index 216d121..87c963f 100644 --- a/frontend/src/locales/it.json +++ b/frontend/src/locales/it.json @@ -248,7 +248,11 @@ "welcome_map_info": "Avventure pubbliche su questo server", "reservation_number": "Numero di prenotazione", "open_in_maps": "Aperto in mappe", - "all_day": "Tutto il giorno" + "all_day": "Tutto il giorno", + "collection_no_start_end_date": "L'aggiunta di una data di inizio e fine alla raccolta sbloccherà le funzionalità di pianificazione dell'itinerario nella pagina di raccolta.", + "date_itinerary": "Itinerario della data", + "no_ordered_items": "Aggiungi articoli con date alla collezione per vederli qui.", + "ordered_itinerary": "Itinerario ordinato" }, "home": { "desc_1": "Scopri, pianifica ed esplora con facilità", diff --git a/frontend/src/locales/ko.json b/frontend/src/locales/ko.json index 2099c0c..dda6962 100644 --- a/frontend/src/locales/ko.json +++ b/frontend/src/locales/ko.json @@ -248,7 +248,11 @@ "reservation_number": "예약 번호", "welcome_map_info": "이 서버의 공개 모험", "open_in_maps": "지도에서 열립니다", - "all_day": "하루 종일" + "all_day": "하루 종일", + "collection_no_start_end_date": "컬렉션에 시작 및 종료 날짜를 추가하면 컬렉션 페이지에서 여정 계획 기능이 잠금 해제됩니다.", + "date_itinerary": "날짜 일정", + "no_ordered_items": "컬렉션에 날짜가있는 항목을 추가하여 여기에서 확인하십시오.", + "ordered_itinerary": "주문한 여정" }, "auth": { "both_passwords_required": "두 암호 모두 필요합니다", diff --git a/frontend/src/locales/nl.json b/frontend/src/locales/nl.json index 6dbf2d7..7cb51dc 100644 --- a/frontend/src/locales/nl.json +++ b/frontend/src/locales/nl.json @@ -248,7 +248,11 @@ "price": "Prijs", "region": "Regio", "open_in_maps": "Open in kaarten", - "all_day": "De hele dag" + "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" }, "home": { "desc_1": "Ontdek, plan en verken met gemak", diff --git a/frontend/src/locales/pl.json b/frontend/src/locales/pl.json index 3eb73ac..c4cd914 100644 --- a/frontend/src/locales/pl.json +++ b/frontend/src/locales/pl.json @@ -296,7 +296,11 @@ "reservation_number": "Numer rezerwacji", "welcome_map_info": "Publiczne przygody na tym serwerze", "open_in_maps": "Otwarte w mapach", - "all_day": "Cały dzień" + "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ę" }, "worldtravel": { "country_list": "Lista krajów", diff --git a/frontend/src/locales/sv.json b/frontend/src/locales/sv.json index 0baff34..5efbf63 100644 --- a/frontend/src/locales/sv.json +++ b/frontend/src/locales/sv.json @@ -248,7 +248,11 @@ "region": "Område", "reservation_number": "Bokningsnummer", "open_in_maps": "Kappas in", - "all_day": "Hela dagen" + "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" }, "home": { "desc_1": "Upptäck, planera och utforska med lätthet", diff --git a/frontend/src/locales/zh.json b/frontend/src/locales/zh.json index dfd5e8e..84caea8 100644 --- a/frontend/src/locales/zh.json +++ b/frontend/src/locales/zh.json @@ -296,7 +296,11 @@ "price": "价格", "reservation_number": "预订号", "open_in_maps": "在地图上打开", - "all_day": "整天" + "all_day": "整天", + "collection_no_start_end_date": "在集合页面中添加开始日期和结束日期将在“收集”页面中解锁行程计划功能。", + "date_itinerary": "日期行程", + "no_ordered_items": "将带有日期的项目添加到集合中,以便在此处查看它们。", + "ordered_itinerary": "订购了行程" }, "auth": { "forgot_password": "忘记密码?", diff --git a/frontend/src/routes/collections/+page.server.ts b/frontend/src/routes/collections/+page.server.ts index 20e2c40..0c9a24b 100644 --- a/frontend/src/routes/collections/+page.server.ts +++ b/frontend/src/routes/collections/+page.server.ts @@ -208,7 +208,7 @@ export const actions: Actions = { const order_direction = formData.get('order_direction') as string; const order_by = formData.get('order_by') as string; - console.log(order_direction, order_by); + // console.log(order_direction, order_by); let adventures: Adventure[] = []; @@ -242,7 +242,7 @@ export const actions: Actions = { previous = res.previous; count = res.count; adventures = [...adventures, ...visited]; - console.log(next, previous, count); + // console.log(next, previous, count); } return { diff --git a/frontend/src/routes/collections/+page.svelte b/frontend/src/routes/collections/+page.svelte index 171a5d4..6eddc70 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`)} /> </div> + <p class="text-lg font-semibold mt-2 mb-2">{$t('adventures.order_by')}</p> + <div class="join"> + <input + class="join-item btn btn-neutral" + type="radio" + name="order_by" + id="upated_at" + value="upated_at" + aria-label={$t('adventures.updated')} + checked + /> + <input + class="join-item btn btn-neutral" + type="radio" + name="order_by" + id="start_date" + value="start_date" + aria-label={$t('adventures.start_date')} + /> + <input + class="join-item btn btn-neutral" + type="radio" + name="order_by" + id="name" + value="name" + aria-label={$t('adventures.name')} + /> + </div> <br /> - <input - type="radio" - name="order_by" - id="name" - class="radio radio-primary" - checked - value="name" - hidden - /> <button type="submit" class="btn btn-success btn-primary mt-4" >{$t(`adventures.sort`)}</button > From a3cd94006534b965b14d2043392e96ba6e74db73 Mon Sep 17 00:00:00 2001 From: Sean Morley <zipsm15@gmail.com> Date: Fri, 21 Mar 2025 17:35:58 -0400 Subject: [PATCH 13/79] feat: Pass collection data to adventure, transportation, lodging, and checklist components --- frontend/src/routes/collections/[id]/+page.svelte | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/frontend/src/routes/collections/[id]/+page.svelte b/frontend/src/routes/collections/[id]/+page.svelte index 2578384..351aabd 100644 --- a/frontend/src/routes/collections/[id]/+page.svelte +++ b/frontend/src/routes/collections/[id]/+page.svelte @@ -998,6 +998,7 @@ on:edit={editAdventure} on:delete={deleteAdventure} {adventure} + {collection} /> {/each} {/if} @@ -1013,6 +1014,7 @@ transportationToEdit = event.detail; isShowingTransportationModal = true; }} + {collection} /> {/each} {/if} @@ -1028,6 +1030,7 @@ on:delete={(event) => { notes = notes.filter((n) => n.id != event.detail); }} + {collection} /> {/each} {/if} @@ -1040,6 +1043,7 @@ lodging = lodging.filter((t) => t.id != event.detail); }} on:edit={editLodging} + {collection} /> {/each} {/if} @@ -1055,6 +1059,7 @@ checklistToEdit = event.detail; isShowingChecklistModal = true; }} + {collection} /> {/each} {/if} From b71109fd090193aa7e339e161778205a9b406849 Mon Sep 17 00:00:00 2001 From: ClumsyAdmin <59402569+ClumsyAdmin@users.noreply.github.com> Date: Fri, 21 Mar 2025 20:25:25 -0400 Subject: [PATCH 14/79] potential fix: set Supervisor priorities to ensure Gunicorn starts before nginx to prevent 502 errors --- backend/supervisord.conf | 3 +++ 1 file changed, 3 insertions(+) diff --git a/backend/supervisord.conf b/backend/supervisord.conf index 7657f7f..4436a9b 100644 --- a/backend/supervisord.conf +++ b/backend/supervisord.conf @@ -4,7 +4,10 @@ nodaemon=true [program:nginx] command=/usr/sbin/nginx -g "daemon off;" autorestart=true +priority=20 [program:gunicorn] command=/code/entrypoint.sh autorestart=true +priority=10 +startsecs=5 From 13d3b24ec2ba5eb662dd0f4730cae4b6a5701aa2 Mon Sep 17 00:00:00 2001 From: Sean Morley <zipsm15@gmail.com> Date: Fri, 21 Mar 2025 20:27:46 -0400 Subject: [PATCH 15/79] chore: Reduce Gunicorn worker count from 4 to 2 for optimized resource usage --- backend/entrypoint.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/backend/entrypoint.sh b/backend/entrypoint.sh index 780a182..19444fb 100644 --- a/backend/entrypoint.sh +++ b/backend/entrypoint.sh @@ -65,5 +65,5 @@ cat /code/adventurelog.txt # Start Gunicorn in foreground exec gunicorn main.wsgi:application \ --bind 0.0.0.0:8000 \ - --workers 4 \ + --workers 2 \ --timeout 120 From ca4ef7983729a8881bfe8f46128d054c3a90e4cb Mon Sep 17 00:00:00 2001 From: ClumsyAdmin <59402569+ClumsyAdmin@users.noreply.github.com> Date: Fri, 21 Mar 2025 20:51:58 -0400 Subject: [PATCH 16/79] fix supervisor logging --- backend/supervisord.conf | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/backend/supervisord.conf b/backend/supervisord.conf index 4436a9b..e7adec7 100644 --- a/backend/supervisord.conf +++ b/backend/supervisord.conf @@ -4,10 +4,13 @@ nodaemon=true [program:nginx] command=/usr/sbin/nginx -g "daemon off;" autorestart=true -priority=20 +stdout_logfile=/dev/stdout +stderr_logfile=/dev/stderr [program:gunicorn] command=/code/entrypoint.sh autorestart=true -priority=10 -startsecs=5 +stdout_logfile=/dev/stdout +stderr_logfile=/dev/stderr +stdout_logfile_maxbytes = 0 +stderr_logfile_maxbytes = 0 From 16a77720038e25f3bc57cf417e6b1440fe6b0295 Mon Sep 17 00:00:00 2001 From: Sean Morley <zipsm15@gmail.com> Date: Sat, 22 Mar 2025 12:25:53 -0400 Subject: [PATCH 17/79] feat: Add additional adventure type and endpoint for sunrise/sunset information --- .../server/adventures/views/adventure_view.py | 71 ++++---- frontend/src/lib/types.ts | 9 ++ frontend/src/locales/en.json | 2 + .../routes/adventures/[id]/+page.server.ts | 6 +- .../src/routes/adventures/[id]/+page.svelte | 153 ++++++++++++------ 5 files changed, 146 insertions(+), 95 deletions(-) diff --git a/backend/server/adventures/views/adventure_view.py b/backend/server/adventures/views/adventure_view.py index 2f7e1f1..55beac3 100644 --- a/backend/server/adventures/views/adventure_view.py +++ b/backend/server/adventures/views/adventure_view.py @@ -10,6 +10,7 @@ from adventures.models import Adventure, Category, Transportation, Lodging 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 @@ -170,48 +171,38 @@ class AdventureViewSet(viewsets.ModelViewSet): 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() + @action(detail=True, methods=['get'], url_path='additional-info') + def additional_info(self, request, pk=None): + adventure = self.get_object() - # 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 - # ) + # Permission check: owner or shared collection member + if adventure.user_id != request.user: + if not (adventure.collection and adventure.collection.shared_with.filter(id=request.user.id).exists()): + return Response({"error": "User does not have permission to access this adventure"}, + status=status.HTTP_403_FORBIDDEN) - # # Define the overlapping fields that both the Adventure and target models share. - # overlapping_fields = ["name", "description", "is_public", 'collection'] + serializer = self.get_serializer(adventure) + response_data = serializer.data - # # 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) + visits = response_data.get('visits', []) + sun_times = [] - # # Make sure to include the user reference - # conversion_data["user_id"] = adventure.user_id + 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') + }) + - # # 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) + response_data['sun_times'] = sun_times + return Response(response_data) \ No newline at end of file diff --git a/frontend/src/lib/types.ts b/frontend/src/lib/types.ts index fd0eadb..69735a3 100644 --- a/frontend/src/lib/types.ts +++ b/frontend/src/lib/types.ts @@ -44,6 +44,15 @@ export type Adventure = { user?: User | null; }; +export type AdditionalAdventure = Adventure & { + sun_times: { + date: string; + visit_id: string; + sunrise: string; + sunset: string; + }[]; +}; + export type Country = { id: number; name: string; diff --git a/frontend/src/locales/en.json b/frontend/src/locales/en.json index 72d1883..65c7288 100644 --- a/frontend/src/locales/en.json +++ b/frontend/src/locales/en.json @@ -90,6 +90,8 @@ "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", diff --git a/frontend/src/routes/adventures/[id]/+page.server.ts b/frontend/src/routes/adventures/[id]/+page.server.ts index eed47ba..140ca46 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 6d88e0e..f11f450 100644 --- a/frontend/src/routes/adventures/[id]/+page.svelte +++ b/frontend/src/routes/adventures/[id]/+page.svelte @@ -1,5 +1,5 @@ <script lang="ts"> - import type { Adventure } from '$lib/types'; + import type { AdditionalAdventure, Adventure } from '$lib/types'; import { onMount } from 'svelte'; import type { PageData } from './$types'; import { goto } from '$app/navigation'; @@ -12,6 +12,7 @@ import toGeoJSON from '@mapbox/togeojson'; import LightbulbOn from '~icons/mdi/lightbulb-on'; + import WeatherSunset from '~icons/mdi/weather-sunset'; let geojson: any; @@ -75,7 +76,7 @@ export let data: PageData; console.log(data); - let adventure: Adventure; + let adventure: AdditionalAdventure; let currentSlide = 0; @@ -112,7 +113,7 @@ await getGpxFiles(); }); - async function saveEdit(event: CustomEvent<Adventure>) { + async function saveEdit(event: CustomEvent<AdditionalAdventure>) { adventure = event.detail; isEditModalOpen = false; geojson = null; @@ -522,60 +523,108 @@ </MapLibre> {/if} </div> - {#if adventure.attachments && adventure.attachments.length > 0} - <div> - <!-- attachments --> - <h2 class="text-2xl font-bold mt-4"> - {$t('adventures.attachments')} - <div class="tooltip z-10" data-tip={$t('adventures.gpx_tip')}> - <button class="btn btn-sm btn-circle btn-neutral"> - <LightbulbOn class="w-6 h-6" /> - </button> - </div> - </h2> - <div class="grid gap-4 mt-4"> - {#if adventure.attachments && adventure.attachments.length > 0} - <div class="grid gap-4 sm:grid-cols-2 lg:grid-cols-3"> - {#each adventure.attachments as attachment} - <AttachmentCard {attachment} /> - {/each} + <!-- Additional Info Display Section --> + + <div> + {#if adventure.sun_times && adventure.sun_times.length > 0} + <h2 class="text-2xl font-bold mt-4 mb-4">{$t('adventures.additional_info')}</h2> + {#if adventure.sun_times && adventure.sun_times.length > 0} + <div class="collapse collapse-plus bg-base-200 mb-2 overflow-visible"> + <input type="checkbox" /> + <div class="collapse-title text-xl font-medium"> + <span> + {$t('adventures.sunrise_sunset')} + <WeatherSunset class="w-6 h-6 inline-block ml-2 -mt-1" /> + </span> </div> - {/if} - </div> - </div> - {/if} - {#if adventure.images && adventure.images.length > 0} - <div> - <h2 class="text-2xl font-bold mt-4">{$t('adventures.images')}</h2> - <div class="grid gap-4 mt-4"> - {#if adventure.images && adventure.images.length > 0} - <div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4"> - {#each adventure.images as image} - <div class="relative"> - <!-- svelte-ignore a11y-no-static-element-interactions --> - <!-- svelte-ignore a11y-missing-attribute --> - <!-- svelte-ignore a11y-missing-content --> - <!-- svelte-ignore a11y-click-events-have-key-events --> - <div - class="w-full h-48 bg-cover bg-center rounded-lg" - style="background-image: url({image.image})" - on:click={() => (image_url = image.image)} - ></div> - {#if image.is_primary} - <div - class="absolute top-0 right-0 bg-primary text-white px-2 py-1 rounded-bl-lg" - > - {$t('adventures.primary')} + + <div class="collapse-content"> + <div class="grid gap-4 mt-4"> + <!-- Sunrise and Sunset times --> + {#each adventure.sun_times as sun_time} + <div class="grid md:grid-cols-3 gap-4"> + <div> + <p class="text-sm text-muted-foreground">Date</p> + <p class="text-base font-medium"> + {new Date(sun_time.date).toLocaleDateString()} + </p> </div> - {/if} - </div> - {/each} + <div> + <p class="text-sm text-muted-foreground">Sunrise</p> + <p class="text-base font-medium"> + {sun_time.sunrise} + </p> + </div> + <div> + <p class="text-sm text-muted-foreground">Sunset</p> + <p class="text-base font-medium"> + {sun_time.sunset} + </p> + </div> + </div> + {/each} + </div> </div> - {/if} + </div> + {/if} + {/if} + + {#if adventure.attachments && adventure.attachments.length > 0} + <div> + <!-- attachments --> + <h2 class="text-2xl font-bold mt-4"> + {$t('adventures.attachments')} + <div class="tooltip z-10" data-tip={$t('adventures.gpx_tip')}> + <button class="btn btn-sm btn-circle btn-neutral"> + <LightbulbOn class="w-6 h-6" /> + </button> + </div> + </h2> + + <div class="grid gap-4 mt-4"> + {#if adventure.attachments && adventure.attachments.length > 0} + <div class="grid gap-4 sm:grid-cols-2 lg:grid-cols-3"> + {#each adventure.attachments as attachment} + <AttachmentCard {attachment} /> + {/each} + </div> + {/if} + </div> </div> - </div> - {/if} + {/if} + {#if adventure.images && adventure.images.length > 0} + <div> + <h2 class="text-2xl font-bold mt-4">{$t('adventures.images')}</h2> + <div class="grid gap-4 mt-4"> + {#if adventure.images && adventure.images.length > 0} + <div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4"> + {#each adventure.images as image} + <div class="relative"> + <!-- svelte-ignore a11y-no-static-element-interactions --> + <!-- svelte-ignore a11y-missing-attribute --> + <!-- svelte-ignore a11y-missing-content --> + <!-- svelte-ignore a11y-click-events-have-key-events --> + <div + class="w-full h-48 bg-cover bg-center rounded-lg" + style="background-image: url({image.image})" + on:click={() => (image_url = image.image)} + ></div> + {#if image.is_primary} + <div + class="absolute top-0 right-0 bg-primary text-white px-2 py-1 rounded-bl-lg" + > + {$t('adventures.primary')} + </div> + {/if} + </div> + {/each} + </div> + {/if} + </div> + </div> + {/if} + </div> </div> </div> </div> From b4c5e22662f3ac3c1aa1cbbfed652f2d3d7a4cf8 Mon Sep 17 00:00:00 2001 From: Sean Morley <zipsm15@gmail.com> Date: Sun, 23 Mar 2025 16:40:08 -0400 Subject: [PATCH 18/79] fix: Correct file extension validation for gpx, md, and pdf formats --- backend/server/adventures/models.py | 2 +- frontend/src/lib/components/AdventureModal.svelte | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/backend/server/adventures/models.py b/backend/server/adventures/models.py index 0d53bc9..22af4e3 100644 --- a/backend/server/adventures/models.py +++ b/backend/server/adventures/models.py @@ -14,7 +14,7 @@ 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', '.pdf'] if not ext.lower() in valid_extensions: raise ValidationError('Unsupported file extension.') diff --git a/frontend/src/lib/components/AdventureModal.svelte b/frontend/src/lib/components/AdventureModal.svelte index 85db0a4..b41c00a 100644 --- a/frontend/src/lib/components/AdventureModal.svelte +++ b/frontend/src/lib/components/AdventureModal.svelte @@ -74,9 +74,9 @@ '.tar.lzma', '.tar.lzo', '.tar.z', - 'gpx', - 'md', - 'pdf' + '.gpx', + '.md', + '.pdf' ]; export let initialLatLng: { lat: number; lng: number } | null = null; // Used to pass the location from the map selection to the modal From 04c3402e14ec18dc722ded33b292e1e1bb9a7561 Mon Sep 17 00:00:00 2001 From: Sean Morley <zipsm15@gmail.com> Date: Sun, 23 Mar 2025 16:40:55 -0400 Subject: [PATCH 19/79] fix: Remove duplicate file extension validation for md and pdf formats --- backend/server/adventures/models.py | 2 +- frontend/src/lib/components/AdventureModal.svelte | 3 +-- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/backend/server/adventures/models.py b/backend/server/adventures/models.py index 22af4e3..6f5b3b7 100644 --- a/backend/server/adventures/models.py +++ b/backend/server/adventures/models.py @@ -14,7 +14,7 @@ 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.') diff --git a/frontend/src/lib/components/AdventureModal.svelte b/frontend/src/lib/components/AdventureModal.svelte index b41c00a..20dd39f 100644 --- a/frontend/src/lib/components/AdventureModal.svelte +++ b/frontend/src/lib/components/AdventureModal.svelte @@ -75,8 +75,7 @@ '.tar.lzo', '.tar.z', '.gpx', - '.md', - '.pdf' + '.md' ]; export let initialLatLng: { lat: number; lng: number } | null = null; // Used to pass the location from the map selection to the modal From 47c219affd4a66b26fe9536de149cbac6b8d9d5c Mon Sep 17 00:00:00 2001 From: Nikolai Eidsheim <nikolai.eidsheim@hiveautonomy.no> Date: Tue, 1 Apr 2025 22:35:03 +0200 Subject: [PATCH 20/79] Added Norwegian translation --- frontend/src/lib/components/Navbar.svelte | 3 +- frontend/src/locales/no.json | 1081 ++++++++++++--------- frontend/src/routes/+layout.svelte | 3 +- 3 files changed, 608 insertions(+), 479 deletions(-) diff --git a/frontend/src/lib/components/Navbar.svelte b/frontend/src/lib/components/Navbar.svelte index 0e5e4f2..5de5971 100644 --- a/frontend/src/lib/components/Navbar.svelte +++ b/frontend/src/lib/components/Navbar.svelte @@ -54,7 +54,8 @@ sv: 'Svenska', zh: '中文', pl: 'Polski', - ko: '한국어' + ko: '한국어', + no: "Norsk" }; let query: string = ''; diff --git a/frontend/src/locales/no.json b/frontend/src/locales/no.json index c5bcd5c..8b49218 100644 --- a/frontend/src/locales/no.json +++ b/frontend/src/locales/no.json @@ -1,253 +1,283 @@ { "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 tagger", - "tag": "Tagg", - "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": "Temavalg", - "themes": { - "light": "Lys", - "dark": "Mørk", - "night": "Natt", - "forest": "Skog", - "aestheticLight": "Estetisk lys", - "aestheticDark": "Estetisk mørk", - "aqua": "Aqua", - "northernLights": "Nordlys" - } + "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-bidrag", - "nominatim_1": "Stedsøk og geokoding tilbys av", - "nominatim_2": "Deres data er lisensiert under ODbL-lisensen.", - "other_attributions": "Ytterligere bidrag finnes i README-filen.", - "close": "Lukk" + "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" }, "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øyene og ressursene du trenger for å planlegge, pakke og navigere ditt neste uforglemmelige eventyr.", - "feature_1": "Reiselogg", - "feature_1_desc": "Før logg over dine eventyr med en personlig reiselogg 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 dine reiser over hele verden med et interaktivt kart og utforsk nye destinasjoner." + "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": "Eventyr fjernet fra samlingen!", - "collection_remove_error": "Feil ved fjerning av eventyr fra samlingen", - "collection_link_success": "Eventyr lagt til samlingen!", - "no_image_found": "Ingen bilde funnet", - "collection_link_error": "Feil ved kobling av eventyr til samlingen", - "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 denne notatet? Denne handlingen kan ikke angres.", - "transportation_delete_confirm": "Er du sikker på at du vil slette denne transporten? Denne handlingen kan ikke angres.", - "delete_checklist": "Slett sjekkliste", - "delete_note": "Slett notat", - "delete_transportation": "Slett transport", - "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": "Eventyr ikke funnet", - "not_found_desc": "Eventyret du lette etter ble ikke funnet. Prøv et annet eventyr eller sjekk igjen 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 matcher ditt søk", - "sort": "Sorter", - "order_by": "Sorter etter", - "order_direction": "Sorteringsretning", - "ascending": "Stigende", - "descending": "Synkende", - "updated": "Oppdatert", - "name": "Navn", - "date": "Dato", - "activity_types": "Aktivitetstyper", - "tags": "Tagger", - "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", - "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 kobles til denne samlingen.", - "add": "Legg til", - "save_next": "Lagre og neste", - "end_date": "Sluttdato", - "my_visits": "Mine besøk", - "start_date": "Startdato", - "remove": "Fjern", - "location": "Sted", - "search_for_location": "Søk etter et sted", - "clear_map": "Tøm kart", - "search_results": "Søkeresultater", - "no_results": "Ingen resultater funnet", - "wiki_desc": "Henter utdrag fra Wikipedia-artikkelen som matcher eventyrets navn.", - "generate_desc": "Generer beskrivelse", - "public_adventure": "Offentlig eventyr", - "location_information": "Stedsinformasjon", - "link": "Lenke", - "links": "Lenker", - "description": "Beskrivelse", - "sources": "Kilder", - "collection_adventures": "Inkluder eventyr fra samlingen", - "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": "Avarkiver", - "archive": "Arkiver", - "no_collections_found": "Ingen samlinger funnet å legge dette eventyret til.", - "not_visited": "Ikke besøkt", - "archived_collection_message": "Samling arkivert!", - "unarchived_collection_message": "Samling avarkivert!", - "delete_collection_success": "Samling slettet!", - "delete_collection_warning": "Er du sikker på at du vil slette denne samlingen? Dette vil også slette alle tilknyttede eventyr. Denne handlingen kan ikke angres.", - "cancel": "Avbryt", - "delete_collection": "Slett samling", - "delete_adventure": "Slett eventyr", - "adventure_delete_success": "Eventyr slettet!", - "visited": "Besøkt", - "planned": "Planlagt", - "duration": "Varighet", - "all": "Alle", - "image_removed_success": "Bilde fjernet!", - "image_removed_error": "Feil ved fjerning av bilde", - "no_image_url": "Ingen bilde funnet på den URL-en.", - "image_upload_success": "Bilde lastet opp!", - "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 skriv inn en sluttdato", - "see_adventures": "Se eventyr", - "image_fetch_failed": "Kunne ikke hente bilde", - "no_location": "Vennligst skriv inn et sted", - "no_start_date": "Vennligst oppgi en startdato", - "no_description_found": "Ingen beskrivelse funnet", - "adventure_created": "Eventyr opprettet", - "adventure_create_error": "Kunne ikke opprette eventyr", - "adventure_updated": "Eventyr oppdatert", - "adventure_update_error": "Kunne ikke oppdatere eventyr", - "set_to_pin": "Sett til pin", - "category_fetch_error": "Feil ved henting av kategorier", - "new_adventure": "Nytt eventyr", - "basic_information": "Grunnleggende informasjon", - "adventure_not_found": "Det er ingen eventyr å vise. Legg til noen ved å bruke plussknappen nederst til høyre, eller prøv å endre filtrene!", - "no_adventures_found": "Ingen eventyr funnet", - "mark_region_as_visited": "Marker region {region}, {country} som besøkt?", - "mark_visited": "Marker som besøkt", - "error_updating_regions": "Feil ved oppdatering av regioner", - "regions_updated": "Regioner oppdatert", - "visited_region_check": "Sjekk besøkte regioner", - "visited_region_check_desc": "Ved å velge dette vil serveren sjekke alle dine besøkte eventyr og markere regionene de ligger i som besøkt i verdensreiser.", - "update_visited_regions": "Oppdater besøkte regioner", - "update_visited_regions_disclaimer": "Dette kan ta en stund, avhengig av hvor mange eventyr du har besøkt.", - "link_new": "Koble til ny...", - "add_new": "Legg til ny...", - "transportation": "Transport", - "note": "Notat", - "checklist": "Sjekkliste", - "collection_archived": "Denne samlingen har blitt arkivert.", - "visit_link": "Besøk lenke", - "collection_completed": "Du har fullført denne samlingen!", - "collection_stats": "Samlingstatistikk", - "keep_exploring": "Fortsett å utforske!", - "linked_adventures": "Koblede eventyr", - "notes": "Notater", - "checklists": "Sjekklister", - "transportations": "Transportmidler", - "adventure_calendar": "Eventyrkalender", - "day": "Dag", - "itineary_by_date": "Reiseplan etter dato", - "nothing_planned": "Ingenting planlagt for denne dagen. Nyt reisen!", - "copied_to_clipboard": "Kopiert til utklippstavlen!", - "copy_failed": "Kopiering mislyktes", - "show": "Vis", - "hide": "Skjul", - "clear_location": "Fjern plassering", - "starting_airport": "Startflyplass", - "ending_airport": "Sluttflyplass", - "no_location_found": "Ingen plassering funnet", - "from": "Fra", - "to": "Til", - "start": "Start", - "end": "Slutt", - "emoji_picker": "Emoji-velger", - "download_calendar": "Last ned kalender", - "date_information": "Dato informasjon", - "flight_information": "Flyinformasjon", - "out_of_range": "Ikke innenfor reiseplanens datoområde", - "preview": "Forhåndsvisning", - "md_instructions": "Skriv markdown her...", - "days": "dager", - "activities": { + "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": "Spisesteder 🍽️", + "dining": "Servering 🍽️", "activity": "Aktivitet 🏄", "attraction": "Attraksjon 🎢", "shopping": "Shopping 🛍️", - "nightlife": "Natteliv 🌃", + "nightlife": "Uteliv 🌃", "event": "Arrangement 🎉", "transportation": "Transport 🚗", "culture": "Kultur 🎭", @@ -255,250 +285,347 @@ "hiking": "Fotturer 🥾", "wildlife": "Dyreliv 🦒", "historical_sites": "Historiske steder 🏛️", - "music_concerts": "Musikk & konserter 🎶", + "music_concerts": "Musikk og konserter 🎶", "fitness": "Trening 🏋️", - "art_museums": "Kunst & museer 🎨", + "art_museums": "Kunst og museer 🎨", "festivals": "Festivaler 🎪", "spiritual_journeys": "Spirituelle reiser 🧘‍♀️", "volunteer_work": "Frivillig arbeid 🤝", "other": "Annet" - }, - "worldtravel": { - "country_list": "Landeliste", - "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": "Fjern søk", - "no_countries_found": "Ingen land funnet" - }, - "auth": { - "username": "Brukernavn", - "password": "Passord", - "forgot_password": "Glemt passord?", - "signup": "Lag ny bruker", - "login_error": "Kunne ikke logge inn med kombinasjonen av brukernavn og passord.", - "login": "Logg inn", - "email": "Email", - "first_name": "Fornavn", - "last_name": "Etternavn", - "confirm_password": "Gjenta passord", - "registration_disabled": "Registrering er deaktivert.", - "profile_picture": "Profilbilde", - "public_profile": "Offentlig Profil", - "public_tooltip": "Med en offentlig profil kan brukere dele samlinger med deg og se profilen din.", - "email_required": "Email er påkrevet", - "new_password": "Nytt Passord (6+ tegn)", - "both_passwords_required": "Begge passord må skrives inn", - "reset_failed": "Kunne ikke resette passord" - }, - "users": { - "no_users_found": "Ingen brukere med offentlig profil funnet." - }, - "settings": { - "update_error": "Kunne ikke oppdatere innstillinger", - "update_success": "Innstillinger oppdatert!", - "settings_page": "Innstillinger", - "account_settings": "Brukerinnstillinger", - "update": "Oppdater", - "password_change": "Bytt passord", - "new_password": "Nytt passord", - "confirm_new_password": "Bekreft nytt passord", - "email_change": "Bytt email", - "current_email": "Nåværende email", - "no_email_set": "Ingen email satt", - "new_email": "Ny email", - "change_password": "Bytt passord", - "login_redir": "You will then be redirected to the login page.", - "token_required": "Token and UID are required for password reset.", - "reset_password": "Reset Password", - "possible_reset": "If the email address you provided is associated with an account, you will receive an email with instructions to reset your password!", - "missing_email": "Please enter an email address", - "submit": "Submit", - "password_does_not_match": "Passwords do not match", - "password_is_required": "Password is required", - "invalid_token": "Token is invalid or has expired", - "about_this_background": "About this background", - "photo_by": "Photo by", - "join_discord": "Join the Discord", - "join_discord_desc": "to share your own photos. Post them in the #travel-share channel.", - "current_password": "Current Password", - "change_password_error": "Unable to change password. Invalid current password or invalid new password.", - "password_change_lopout_warning": "You will be logged out after changing your password.", - "generic_error": "An error occurred while processing your request.", - "email_removed": "Email removed successfully!", - "email_removed_error": "Error removing email", - "verify_email_success": "Email verification sent successfully!", - "verify_email_error": "Error verifying email. Try again in a few minutes.", - "email_added": "Email added successfully!", - "email_added_error": "Error adding email", - "email_set_primary": "Email set as primary successfully!", - "email_set_primary_error": "Error setting email as primary", - "verified": "Verified", - "primary": "Primary", - "not_verified": "Not Verified", - "make_primary": "Make Primary", - "verify": "Verify", - "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", - "enable_mfa": "Enable MFA", - "disable_mfa": "Disable MFA", - "mfa_not_enabled": "MFA is not enabled", - "mfa_enabled": "Multi-factor authentication enabled successfully!", - "copy": "Copy", - "recovery_codes": "Recovery Codes", - "recovery_codes_desc": "These are your recovery codes. Keep them safe. You will not be able to see them again.", - "reset_session_error": "Please logout and back in to refresh your session and try again.", - "authenticator_code": "Authenticator Code", - "email_verified": "Email verified successfully!", - "email_verified_success": "Your email has been verified. You can now log in.", - "email_verified_error": "Error verifying email", - "email_verified_erorr_desc": "Your email could not be verified. Please try again.", - "invalid_code": "Invalid MFA code", - "invalid_credentials": "Invalid username or password", - "mfa_required": "Multi-factor authentication is required", - "required": "This field is required", - "add_email_blocked": "You cannot add an email address to an account protected by two-factor authentication.", - "duplicate_email": "This email address is already in use.", - "csrf_failed": "Failed to fetch CSRF token", - "email_taken": "This email address is already in use.", - "username_taken": "This username is already in use." - }, - "collection": { - "collection_created": "Collection created successfully!", - "error_creating_collection": "Error creating collection", - "new_collection": "New Collection", - "create": "Create", - "collection_edit_success": "Collection edited successfully!", - "error_editing_collection": "Error editing collection", - "edit_collection": "Edit Collection", - "public_collection": "Public Collection" - }, - "notes": { - "note_deleted": "Note deleted successfully!", - "note_delete_error": "Error deleting note", - "open": "Open", - "failed_to_save": "Failed to save note", - "note_editor": "Note Editor", - "note_viewer": "Note Viewer", - "editing_note": "Editing note", - "content": "Content", - "save": "Save", - "note_public": "This note is public because it is in a public collection.", - "add_a_link": "Add a link", - "invalid_url": "Invalid URL" - }, - "checklist": { - "checklist_deleted": "Checklist deleted successfully!", - "checklist_delete_error": "Error deleting checklist", - "failed_to_save": "Failed to save checklist", - "checklist_editor": "Checklist Editor", - "checklist_viewer": "Checklist Viewer", - "editing_checklist": "Editing checklist", - "new_checklist": "New Checklist", - "item": "Item", - "items": "Items", - "add_item": "Add Item", - "new_item": "New Item", - "save": "Save", - "checklist_public": "This checklist is public because it is in a public collection.", - "item_cannot_be_empty": "Item cannot be empty", - "item_already_exists": "Item already exists" - }, - "transportation": { - "transportation_deleted": "Transportation deleted successfully!", - "transportation_delete_error": "Error deleting transportation", - "provide_start_date": "Please provide a start date", - "transport_type": "Transport Type", - "type": "Type", - "transportation_added": "Transportation added successfully!", - "error_editing_transportation": "Error editing transportation", - "new_transportation": "New Transportation", - "date_time": "Start Date & Time", - "end_date_time": "End Date & Time", - "flight_number": "Flight Number", - "from_location": "From Location", - "to_location": "To Location", - "edit": "Edit", - "modes": { - "car": "Car", - "plane": "Plane", - "train": "Train", - "bus": "Bus", - "boat": "Boat", - "bike": "Bike", - "walking": "Walking", - "other": "Other" - }, - "transportation_edit_success": "Transportation edited successfully!", - "edit_transportation": "Edit Transportation", - "start": "Start", - "date_and_time": "Date & Time" - }, - "search": { - "adventurelog_results": "AdventureLog Results", - "public_adventures": "Public Adventures", - "online_results": "Online Results" - }, - "map": { - "view_details": "View Details", - "adventure_map": "Adventure Map", - "map_options": "Map Options", - "show_visited_regions": "Show Visited Regions", - "add_adventure_at_marker": "Add New Adventure at Marker", - "clear_marker": "Clear Marker", - "add_adventure": "Add New Adventure" - }, - "share": { - "shared": "Shared", - "with": "with", - "unshared": "Unshared", - "share_desc": "Share this collection with other users.", - "shared_with": "Shared With", - "no_users_shared": "No users shared with", - "not_shared_with": "Not Shared With", - "no_shared_found": "No collections found that are shared with you.", - "set_public": "In order to allow users to share with you, you need your profile set to public.", - "go_to_settings": "Go to settings" - }, - "languages": { - "en": "English", - "de": "German", - "es": "Spanish", - "fr": "French", - "it": "Italian", - "nl": "Dutch", - "sv": "Swedish", - "zh": "Chinese", - "pl": "Polish" - }, - "profile": { - "member_since": "Member since", - "user_stats": "User Stats", - "visited_countries": "Visited Countries", - "visited_regions": "Visited Regions" - }, - "categories": { - "manage_categories": "Manage Categories", - "no_categories_found": "No categories found.", - "edit_category": "Edit Category", - "icon": "Icon", - "update_after_refresh": "The adventure cards will be updated once you refresh the page.", - "select_category": "Select Category", - "category_name": "Category Name" - }, - "dashboard": { - "welcome_back": "Welcome back", - "countries_visited": "Countries Visited", - "total_adventures": "Total Adventures", - "total_visited_regions": "Total Visited Regions", - "recent_adventures": "Recent Adventures", - "no_recent_adventures": "No recent adventures?", - "add_some": "Why not start planning your next adventure? You can add a new adventure by clicking the button below." - } + }, + "lodging_information": "Overnattingsinformasjon", + "price": "Pris", + "reservation_number": "Reservasjonsnummer" + }, + "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." + }, + "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" + }, + "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" + }, + "recomendations": { + "address": "Adresse", + "phone": "Telefon", + "contact": "Kontakt", + "website": "Nettsted", + "recommendation": "Anbefaling" } - + } + \ No newline at end of file diff --git a/frontend/src/routes/+layout.svelte b/frontend/src/routes/+layout.svelte index dc904ea..27dbca4 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({ From 8041f67ba15dce0c5c01ea88165f1e4d9f1e79a8 Mon Sep 17 00:00:00 2001 From: Lars Kiesow <lkiesow@uos.de> Date: Sun, 6 Apr 2025 12:30:59 +0200 Subject: [PATCH 21/79] Document deployment using Caddy This patch adds documentation on how to run AdventureLog with Caddy as a reverse proxy. Signed-off-by: Lars Kiesow <lkiesow@uos.de> --- documentation/.vitepress/config.mts | 1 + documentation/docs/install/caddy.md | 67 +++++++++++++++++++ documentation/docs/install/getting_started.md | 1 + 3 files changed, 69 insertions(+) create mode 100644 documentation/docs/install/caddy.md diff --git a/documentation/.vitepress/config.mts b/documentation/.vitepress/config.mts index ef5cd99..529ecbd 100644 --- a/documentation/.vitepress/config.mts +++ b/documentation/.vitepress/config.mts @@ -80,6 +80,7 @@ export default defineConfig({ link: "/docs/install/nginx_proxy_manager", }, { text: "Traefik", link: "/docs/install/traefik" }, + { text: "Caddy", link: "/docs/install/caddy" }, ], }, ], diff --git a/documentation/docs/install/caddy.md b/documentation/docs/install/caddy.md new file mode 100644 index 0000000..2f99db8 --- /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-ymö`: + +```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/getting_started.md b/documentation/docs/install/getting_started.md index aa5187c..a74d6a6 100644 --- a/documentation/docs/install/getting_started.md +++ b/documentation/docs/install/getting_started.md @@ -12,3 +12,4 @@ AdventureLog can be installed in a variety of ways. The following are the most c - [Nginx Proxy Manager](nginx_proxy_manager.md) 🛡 - [Traefik](traefik.md) 🚀 +- [Caddy](caddy.md) 🔒 From af8a9acbae4ae6a08659350e4f299b2aa73e3fec Mon Sep 17 00:00:00 2001 From: Lars Kiesow <lkiesow@uos.de> Date: Sun, 6 Apr 2025 14:32:19 +0200 Subject: [PATCH 22/79] Prevent unnecessary redirect when requesting users Opening the share dialog, the frontend is requesting `/auth/users/` which is always redirected to `/auth/users`. That's an unnecessary extra step. This patch makes the front-end request `/auth/users` in the first place. --- frontend/src/lib/components/ShareModal.svelte | 2 +- frontend/src/routes/users/+page.server.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/frontend/src/lib/components/ShareModal.svelte b/frontend/src/lib/components/ShareModal.svelte index 0f4fcec..e3dbc6a 100644 --- a/frontend/src/lib/components/ShareModal.svelte +++ b/frontend/src/lib/components/ShareModal.svelte @@ -62,7 +62,7 @@ if (modal) { modal.showModal(); } - let res = await fetch(`/auth/users/`); + let res = await fetch(`/auth/users`); if (res.ok) { let data = await res.json(); allUsers = data; diff --git a/frontend/src/routes/users/+page.server.ts b/frontend/src/routes/users/+page.server.ts index 3fe2574..3ff1157 100644 --- a/frontend/src/routes/users/+page.server.ts +++ b/frontend/src/routes/users/+page.server.ts @@ -9,7 +9,7 @@ export const load = (async (event) => { return redirect(302, '/login'); } - const res = await fetch(`${serverEndpoint}/auth/users/`, { + const res = await fetch(`${serverEndpoint}/auth/users`, { headers: { Cookie: `sessionid=${sessionId}` } From 8531855f465c7f9eb8f2072c87bd40aa0a7caee7 Mon Sep 17 00:00:00 2001 From: Lars Kiesow <lkiesow@uos.de> Date: Sun, 6 Apr 2025 21:09:27 +0200 Subject: [PATCH 23/79] Enable Browser Navigation in Collections MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit If you are in a collection – e.g on “All Linked Items” – and go into an adventure, going back will cause you to end up on the itinerary. This is quite annoying if you have a number of options for a trip linked already but whenever you go back, you have to scroll up again, click on “All Linked Items”, and scroll down again to get to the next adventure in line. This patch makes AdventureLog remember the tab you were in and going back and forth in the browser history will actually work. --- .../src/routes/collections/[id]/+page.svelte | 50 +++++++++++-------- 1 file changed, 28 insertions(+), 22 deletions(-) diff --git a/frontend/src/routes/collections/[id]/+page.svelte b/frontend/src/routes/collections/[id]/+page.svelte index 56f613a..0e32acb 100644 --- a/frontend/src/routes/collections/[id]/+page.svelte +++ b/frontend/src/routes/collections/[id]/+page.svelte @@ -1,6 +1,6 @@ <script lang="ts"> import type { Adventure, Checklist, Collection, Lodging, Note, Transportation } from '$lib/types'; - import { onMount } from 'svelte'; + import { onMount, onDestroy } from 'svelte'; import type { PageData } from './$types'; import { marked } from 'marked'; // Import the markdown parser @@ -179,6 +179,17 @@ let isShowingTransportationModal: boolean = false; let isShowingChecklistModal: boolean = false; + function handleHashChange() { + const hash = window.location.hash.replace('#', ''); + if (hash) { + currentView = hash + } else if (!collection.start_date) { + currentView = 'all'; + } else { + currentView = 'itinerary'; + } + } + onMount(() => { if (data.props.adventure) { collection = data.props.adventure; @@ -209,11 +220,12 @@ if (collection.checklists) { checklists = collection.checklists; } - if (!collection.start_date) { - currentView = 'all'; - } else { - currentView = 'itinerary'; - } + window.addEventListener('hashchange', handleHashChange); + handleHashChange(); + }); + + onDestroy(() => { + window.removeEventListener('hashchange', handleHashChange); }); function deleteAdventure(event: CustomEvent<string>) { @@ -647,41 +659,35 @@ <!-- svelte-ignore a11y-missing-attribute --> {#if collection.start_date} <a + href="#itinerary" role="tab" class="tab {currentView === 'itinerary' ? 'tab-active' : ''}" - tabindex="0" - on:click={() => (currentView = 'itinerary')} - on:keydown={(e) => e.key === 'Enter' && (currentView = 'itinerary')}>Itinerary</a + tabindex="0">Itinerary</a > {/if} <a + href="#all" role="tab" class="tab {currentView === 'all' ? 'tab-active' : ''}" - tabindex="0" - on:click={() => (currentView = 'all')} - on:keydown={(e) => e.key === 'Enter' && (currentView = 'all')}>All Linked Items</a + tabindex="0">All Linked Items</a > <a + href="#calendar" role="tab" class="tab {currentView === 'calendar' ? 'tab-active' : ''}" - tabindex="0" - on:click={() => (currentView = 'calendar')} - on:keydown={(e) => e.key === 'Enter' && (currentView = 'calendar')}>Calendar</a + tabindex="0">Calendar</a > <a + href="#map" role="tab" class="tab {currentView === 'map' ? 'tab-active' : ''}" - tabindex="0" - on:click={() => (currentView = 'map')} - on:keydown={(e) => e.key === 'Enter' && (currentView = 'map')}>Map</a + tabindex="0">Map</a > <a + href="#recommendations" role="tab" class="tab {currentView === 'recommendations' ? 'tab-active' : ''}" - tabindex="0" - on:click={() => (currentView = 'recommendations')} - on:keydown={(e) => e.key === 'Enter' && (currentView = 'recommendations')} - >Recommendations</a + tabindex="0">Recommendations</a > </div> </div> From 847193a0aee81514c63a8686f69ba9138209f239 Mon Sep 17 00:00:00 2001 From: Lars Lehmann <lars@lars-lehmann.net> Date: Sun, 6 Apr 2025 22:55:13 +0200 Subject: [PATCH 24/79] fix: Reenable IPv6 for Backend --- backend/entrypoint.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/backend/entrypoint.sh b/backend/entrypoint.sh index 19444fb..06adc3c 100644 --- a/backend/entrypoint.sh +++ b/backend/entrypoint.sh @@ -64,6 +64,6 @@ cat /code/adventurelog.txt # Start Gunicorn in foreground exec gunicorn main.wsgi:application \ - --bind 0.0.0.0:8000 \ + --bind [::]:8000 \ --workers 2 \ --timeout 120 From 658764fb589e9e01623db7bc213ca9ab9e417ae0 Mon Sep 17 00:00:00 2001 From: Lars Kiesow <lkiesow@uos.de> Date: Mon, 7 Apr 2025 01:38:14 +0200 Subject: [PATCH 25/79] Prevent UI overlaps in lodging card This patch prevents overlapping UI eelements in the ledging card. It adjusts the UI to be more like the adventure cards when it comes to font size and element placing. This partly fixes #539 --- .../src/lib/components/LodgingCard.svelte | 19 +++++++------------ 1 file changed, 7 insertions(+), 12 deletions(-) diff --git a/frontend/src/lib/components/LodgingCard.svelte b/frontend/src/lib/components/LodgingCard.svelte index a7bd34c..bb7b6db 100644 --- a/frontend/src/lib/components/LodgingCard.svelte +++ b/frontend/src/lib/components/LodgingCard.svelte @@ -96,20 +96,15 @@ > <div class="card-body space-y-4"> <!-- Title and Type --> - <div class="flex items-center justify-between"> - <h2 class="card-title text-lg font-semibold truncate">{lodging.name}</h2> - <div class="flex items-center gap-2"> - <div class="badge badge-secondary"> - {$t(`lodging.${lodging.type}`) + ' ' + getLodgingIcon(lodging.type)} - </div> - <!-- {#if hotel.type == 'plane' && hotel.flight_number} - <div class="badge badge-neutral-200">{hotel.flight_number}</div> - {/if} --> + <h2 class="text-2xl font-semibold">{lodging.name}</h2> + <div> + <div class="badge badge-secondary"> + {$t(`lodging.${lodging.type}`) + ' ' + getLodgingIcon(lodging.type)} </div> + {#if unlinked} + <div class="badge badge-error">{$t('adventures.out_of_range')}</div> + {/if} </div> - {#if unlinked} - <div class="badge badge-error">{$t('adventures.out_of_range')}</div> - {/if} <!-- Location --> <div class="space-y-2"> From d2933854ffe366789a371bff12122e0f44e12211 Mon Sep 17 00:00:00 2001 From: Sean Morley <98704938+seanmorley15@users.noreply.github.com> Date: Mon, 7 Apr 2025 19:14:04 -0400 Subject: [PATCH 26/79] Update documentation/docs/usage/usage.md Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- documentation/docs/usage/usage.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/documentation/docs/usage/usage.md b/documentation/docs/usage/usage.md index 8cf0129..c286bc1 100644 --- a/documentation/docs/usage/usage.md +++ b/documentation/docs/usage/usage.md @@ -10,7 +10,7 @@ Welcome to AdventureLog! This guide will help you get started with AdventureLog - **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 uploded from your device or with a service like [Immich](/docs/configuration/immich_integration) if the integration is enabled. +- **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 From 3b5240dffed6b7fecc7afae72ebaebadf7ca14f8 Mon Sep 17 00:00:00 2001 From: Sean Morley <98704938+seanmorley15@users.noreply.github.com> Date: Mon, 7 Apr 2025 19:14:55 -0400 Subject: [PATCH 27/79] Update documentation/docs/troubleshooting/login_unresponsive.md Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- documentation/docs/troubleshooting/login_unresponsive.md | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/documentation/docs/troubleshooting/login_unresponsive.md b/documentation/docs/troubleshooting/login_unresponsive.md index aecf431..9abf6a0 100644 --- a/documentation/docs/troubleshooting/login_unresponsive.md +++ b/documentation/docs/troubleshooting/login_unresponsive.md @@ -4,8 +4,7 @@ When you encounter issues with the login and registration pages being unresponsi 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 contianer from running. - + - 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. From 43b8275fc18bdad09ed26bf6f8692dc294c814c7 Mon Sep 17 00:00:00 2001 From: Sean Morley <98704938+seanmorley15@users.noreply.github.com> Date: Mon, 7 Apr 2025 19:16:58 -0400 Subject: [PATCH 28/79] Update documentation/docs/install/caddy.md Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- documentation/docs/install/caddy.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/documentation/docs/install/caddy.md b/documentation/docs/install/caddy.md index 2f99db8..d9d088a 100644 --- a/documentation/docs/install/caddy.md +++ b/documentation/docs/install/caddy.md @@ -2,7 +2,7 @@ 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-ymö`: +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: From 937c3c6a68e41356fd3de2aba826563951be1f91 Mon Sep 17 00:00:00 2001 From: Sean Morley <98704938+seanmorley15@users.noreply.github.com> Date: Mon, 7 Apr 2025 19:17:26 -0400 Subject: [PATCH 29/79] Update backend/server/adventures/views/collection_view.py Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- backend/server/adventures/views/collection_view.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/backend/server/adventures/views/collection_view.py b/backend/server/adventures/views/collection_view.py index 2c46dc5..7c636a9 100644 --- a/backend/server/adventures/views/collection_view.py +++ b/backend/server/adventures/views/collection_view.py @@ -22,8 +22,7 @@ class CollectionViewSet(viewsets.ModelViewSet): 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', 'start_date'] - if order_by not in valid_order_by: + valid_order_by = ['name', 'updated_at', 'start_date'] order_by = 'updated_at' if order_direction not in ['asc', 'desc']: From a09b3f379fa3c23cd2e376dbaf2a94d574b84594 Mon Sep 17 00:00:00 2001 From: Andrea Titolo <mail@andreatitolo.com> Date: Tue, 8 Apr 2025 19:26:11 +0200 Subject: [PATCH 30/79] Update italian translation and terms --- frontend/src/locales/it.json | 90 ++++++++++++++++++------------------ 1 file changed, 45 insertions(+), 45 deletions(-) diff --git a/frontend/src/locales/it.json b/frontend/src/locales/it.json index 87c963f..70593e2 100644 --- a/frontend/src/locales/it.json +++ b/frontend/src/locales/it.json @@ -1,7 +1,7 @@ { "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", @@ -13,10 +13,10 @@ "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 🏋️", @@ -47,7 +47,7 @@ "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", @@ -84,12 +84,12 @@ "sort": "Ordinare", "sources": "Fonti", "unarchive": "Annulla l'archiviazione", - "unarchived_collection_message": "Raccolta annullata con successo!", + "unarchived_collection_message": "Raccolta disarchiviata con successo!", "updated": "Aggiornato", "visit": "Visita", "visits": "Visite", "adventure_delete_success": "Avventura eliminata con successo!", - "collection_adventures": "Includi avventure di raccolta", + "collection_adventures": "Includi avventure dalle raccolte", "collection_link_success": "Avventura collegata alla raccolta con successo!", "dates": "Date", "delete_adventure": "Elimina avventura", @@ -114,7 +114,7 @@ "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", "description": "Descrizione", @@ -140,7 +140,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,7 +154,7 @@ "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.", @@ -174,14 +174,14 @@ "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 +194,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 +205,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 +213,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 +221,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,11 +247,11 @@ "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 raccolta sbloccherà le funzionalità di pianificazione dell'itinerario nella pagina di raccolta.", - "date_itinerary": "Itinerario della data", - "no_ordered_items": "Aggiungi articoli con date alla collezione per vederli qui.", + "collection_no_start_end_date": "L'aggiunta di una data di inizio e fine alla raccolta sbloccherà le funzionalità di pianificazione dell'itinerario nella pagina della raccolta.", + "date_itinerary": "Data dell'itinerario", + "no_ordered_items": "Aggiungi elementi con date alla collezione per vederli qui.", "ordered_itinerary": "Itinerario ordinato" }, "home": { @@ -272,9 +272,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", @@ -295,7 +295,7 @@ }, "users": "Utenti", "worldtravel": "Viaggio nel mondo", - "my_tags": "I miei tag", + "my_tags": "Le mie tag", "tag": "Etichetta", "language_selection": "Lingua", "support": "Supporto", @@ -319,7 +319,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", @@ -349,7 +349,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" @@ -360,7 +360,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", @@ -378,7 +378,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.", @@ -412,7 +412,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!", @@ -431,7 +431,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", @@ -444,19 +444,19 @@ "password_enabled_error": "Errore che abilita l'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.", "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" }, @@ -473,11 +473,11 @@ "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", @@ -513,7 +513,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)" }, @@ -545,7 +545,7 @@ "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", @@ -607,12 +607,12 @@ "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", @@ -621,15 +621,15 @@ "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" } -} +} \ No newline at end of file From 6f489b27344f8175aecf422bbe4bd8d312f5887e Mon Sep 17 00:00:00 2001 From: Andrea Titolo <mail@andreatitolo.com> Date: Tue, 8 Apr 2025 20:49:49 +0200 Subject: [PATCH 31/79] Consistency in translating collections --- frontend/src/locales/it.json | 52 ++++++++++++++++++------------------ 1 file changed, 26 insertions(+), 26 deletions(-) diff --git a/frontend/src/locales/it.json b/frontend/src/locales/it.json index 70593e2..1a77839 100644 --- a/frontend/src/locales/it.json +++ b/frontend/src/locales/it.json @@ -35,14 +35,14 @@ "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", @@ -50,19 +50,19 @@ "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 +79,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 disarchiviata 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 dalle raccolte", - "collection_link_success": "Avventura collegata alla raccolta con successo!", + "collection_link_success": "Avventura collegata alla collezione con successo!", "dates": "Date", "delete_adventure": "Elimina avventura", "duration": "Durata", @@ -116,7 +116,7 @@ "category": "Categoria", "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", @@ -157,8 +157,8 @@ "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,9 +168,9 @@ "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!", @@ -249,7 +249,7 @@ "reservation_number": "Numero di prenotazione", "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 raccolta sbloccherà le funzionalità di pianificazione dell'itinerario nella pagina della raccolta.", + "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" @@ -448,7 +448,7 @@ "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": "Elemento", @@ -461,12 +461,12 @@ "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" }, @@ -535,13 +535,13 @@ "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": { From 23426012af3e0987f33b64c07f0cdae419575e52 Mon Sep 17 00:00:00 2001 From: Lars Kiesow <lkiesow@uos.de> Date: Fri, 11 Apr 2025 20:18:14 +0200 Subject: [PATCH 32/79] Fix Python Syntax Error Commit 937c3c6a68e41356fd3de2aba826563951be1f91 introduced a Python syntax error, breaking the server. This fixes the issue by restoring the probably accidental removal of one line of code. --- backend/server/adventures/views/collection_view.py | 1 + 1 file changed, 1 insertion(+) diff --git a/backend/server/adventures/views/collection_view.py b/backend/server/adventures/views/collection_view.py index 7c636a9..1765342 100644 --- a/backend/server/adventures/views/collection_view.py +++ b/backend/server/adventures/views/collection_view.py @@ -23,6 +23,7 @@ class CollectionViewSet(viewsets.ModelViewSet): order_direction = self.request.query_params.get('order_direction', 'asc') valid_order_by = ['name', 'updated_at', 'start_date'] + if order_by not in valid_order_by: order_by = 'updated_at' if order_direction not in ['asc', 'desc']: From 548d43b563320229ff94f5df30df9a83030f9c22 Mon Sep 17 00:00:00 2001 From: Lars Kiesow <lkiesow@uos.de> Date: Fri, 11 Apr 2025 21:10:52 +0200 Subject: [PATCH 33/79] Basic Integration Test for backend This patch implements a very basic test for commits and pull requests to be run on GitHub Actions. This does not yet check much, but would have caught something like the recent syntax error. --- .github/.docker-compose-database.yml | 16 ++++++++ .github/workflows/backend-test.yml | 61 ++++++++++++++++++++++++++++ 2 files changed, 77 insertions(+) create mode 100644 .github/.docker-compose-database.yml create mode 100644 .github/workflows/backend-test.yml diff --git a/.github/.docker-compose-database.yml b/.github/.docker-compose-database.yml new file mode 100644 index 0000000..aa187bd --- /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/workflows/backend-test.yml b/.github/workflows/backend-test.yml new file mode 100644 index 0000000..afe3fa8 --- /dev/null +++ b/.github/workflows/backend-test.yml @@ -0,0 +1,61 @@ +name: Test Backend + +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/ From dd01ada61ea00a0921292f492433183630b04aa5 Mon Sep 17 00:00:00 2001 From: Sean Morley <98704938+seanmorley15@users.noreply.github.com> Date: Mon, 14 Apr 2025 11:22:20 -0400 Subject: [PATCH 34/79] Update docker.md Fixes #560 --- documentation/docs/install/docker.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/documentation/docs/install/docker.md b/documentation/docs/install/docker.md index 0f987d2..8eed303 100644 --- a/documentation/docs/install/docker.md +++ b/documentation/docs/install/docker.md @@ -33,7 +33,7 @@ Here is a summary of the configuration options available in the `docker-compose. | ------------------- | --------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------- | | `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``` | +| `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 **bytes**. | ```Infinity``` | ### Backend Container (server) From 49f7bf27e8d4709af90117c58f287c0da6ab5cc6 Mon Sep 17 00:00:00 2001 From: Lars Kiesow <lkiesow@uos.de> Date: Mon, 14 Apr 2025 18:44:15 +0200 Subject: [PATCH 35/79] Fix rendering issue in adventure dropdown MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This is a very simple patch fixing the rendering issue of the “Remove from collection” option in the adventure dropdown.- Together with #552, this should fix #539. --- frontend/src/lib/components/AdventureCard.svelte | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frontend/src/lib/components/AdventureCard.svelte b/frontend/src/lib/components/AdventureCard.svelte index 2e9dd61..057137d 100644 --- a/frontend/src/lib/components/AdventureCard.svelte +++ b/frontend/src/lib/components/AdventureCard.svelte @@ -196,7 +196,7 @@ <!-- svelte-ignore a11y-no-noninteractive-tabindex --> <ul tabindex="0" - class="dropdown-content menu bg-base-100 rounded-box z-[1] w-52 p-2 shadow" + class="dropdown-content menu bg-base-100 rounded-box z-[1] w-64 p-2 shadow" > <button class="btn btn-neutral mb-2" From 44a260b5b64add3adce2ef6715daf230864ea7d0 Mon Sep 17 00:00:00 2001 From: Lars Kiesow <lkiesow@uos.de> Date: Tue, 15 Apr 2025 01:05:02 +0200 Subject: [PATCH 36/79] Show Note preview on Card Often, you end up having short notes or even just a simple link and it is somewhat tedious to go into the details to retrieve the additional information. This patch displays a preview of the note content and a maximum of three links on the notes card directly. This makes accessing information much faster. This fixes #562. --- frontend/src/lib/components/NoteCard.svelte | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/frontend/src/lib/components/NoteCard.svelte b/frontend/src/lib/components/NoteCard.svelte index 9b6ba62..c973084 100644 --- a/frontend/src/lib/components/NoteCard.svelte +++ b/frontend/src/lib/components/NoteCard.svelte @@ -71,11 +71,28 @@ {#if unlinked} <div class="badge badge-error">{$t('adventures.out_of_range')}</div> {/if} + {#if note.content && note.content.length > 0} + <p class="line-clamp-6"> + {note.content} + </p> + {/if} {#if note.links && note.links.length > 0} <p> {note.links.length} {note.links.length > 1 ? $t('adventures.links') : $t('adventures.link')} </p> + <ul class="list-disc pl-6"> + {#each note.links.slice(0, 3) as link} + <li> + <a class="link link-primary" href={link}> + {link.split('//')[1].split('/', 1)[0]} + </a> + </li> + {/each} + {#if note.links.length > 3} + <li>…</li> + {/if} + </ul> {/if} {#if note.date && note.date !== ''} <div class="inline-flex items-center"> From a11400fa98545e52c0d15a10e6182dbb2054f787 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 14 Apr 2025 23:21:27 +0000 Subject: [PATCH 37/79] chore(deps-dev): bump @sveltejs/kit Bumps the npm_and_yarn group with 1 update in the /frontend directory: [@sveltejs/kit](https://github.com/sveltejs/kit/tree/HEAD/packages/kit). Updates `@sveltejs/kit` from 2.8.3 to 2.20.6 - [Release notes](https://github.com/sveltejs/kit/releases) - [Changelog](https://github.com/sveltejs/kit/blob/main/packages/kit/CHANGELOG.md) - [Commits](https://github.com/sveltejs/kit/commits/@sveltejs/kit@2.20.6/packages/kit) --- updated-dependencies: - dependency-name: "@sveltejs/kit" dependency-version: 2.20.6 dependency-type: direct:development dependency-group: npm_and_yarn ... Signed-off-by: dependabot[bot] <support@github.com> --- frontend/package.json | 2 +- frontend/pnpm-lock.yaml | 91 +++++++++++++++++++++++------------------ 2 files changed, 52 insertions(+), 41 deletions(-) diff --git a/frontend/package.json b/frontend/package.json index a2ed840..918a869 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -18,7 +18,7 @@ "@iconify-json/mdi": "^1.1.67", "@sveltejs/adapter-node": "^5.2.0", "@sveltejs/adapter-vercel": "^5.4.1", - "@sveltejs/kit": "^2.8.3", + "@sveltejs/kit": "^2.20.6", "@sveltejs/vite-plugin-svelte": "^3.1.1", "@tailwindcss/typography": "^0.5.13", "@types/node": "^22.5.4", diff --git a/frontend/pnpm-lock.yaml b/frontend/pnpm-lock.yaml index 318a11b..f41d2f5 100644 --- a/frontend/pnpm-lock.yaml +++ b/frontend/pnpm-lock.yaml @@ -53,13 +53,13 @@ importers: version: 1.1.67 '@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.0(@sveltejs/kit@2.20.6(@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': 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.4.1(@sveltejs/kit@2.20.6(@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': - 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)) + specifier: ^2.20.6 + version: 2.20.6(@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': specifier: ^3.1.1 version: 3.1.1(svelte@4.2.19)(vite@5.4.12(@types/node@22.5.4)) @@ -456,6 +456,9 @@ packages: '@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==} @@ -520,8 +523,8 @@ 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==} @@ -748,14 +751,14 @@ packages: peerDependencies: '@sveltejs/kit': ^2.4.0 - '@sveltejs/kit@2.8.3': - resolution: {integrity: sha512-DVBVwugfzzn0SxKA+eAmKqcZ7aHZROCHxH7/pyrOi+HLtQ721eEsctGb9MkhEuqj6q/9S/OFYdn37vdxzFPdvw==} + '@sveltejs/kit@2.20.6': + resolution: {integrity: sha512-ImUkSQ//Xf4N9r0HHAe5vRA7RyQ7U1Ue1YUT235Ig+IiIqbsixEulHTHrP5LtBiC8xOkJoPZQ1VZ/nWHNOaGGw==} 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==} @@ -1151,8 +1154,8 @@ packages: resolution: {integrity: sha512-ErCHMCae19vR8vQGe50xIsVomy19rg6gFu3+r3jkEO46suLMWBksvVyoGgQV+jOfl84ZSOSlmv6Gxa89PmTGmA==} 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==} @@ -1469,6 +1472,9 @@ packages: magic-string@0.30.10: resolution: {integrity: sha512-iIRwTIf0QKV3UAnYK4PU8uiEc4SRh5jX0mwpIwETPpHdhVM4f53RSwS/vXvN1JhGX+Cs7B8qIq3d6AH49O5fAQ==} + magic-string@0.30.17: + resolution: {integrity: sha512-sNPKHvyjVf7gyjwS4xGTaW/mCnF8wnjtifKBEhxfZ7E/S8tQ0rssrwGNn6q8JH/ohItJfSQp9mBtQYuTlH5QnA==} + make-dir@3.1.0: resolution: {integrity: sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==} engines: {node: '>=8'} @@ -1550,8 +1556,8 @@ packages: 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: @@ -1876,8 +1882,8 @@ packages: set-blocking@2.0.0: resolution: {integrity: sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw==} - set-cookie-parser@2.6.0: - resolution: {integrity: sha512-RVnVQxTXuerk653XfuliOxBP81Sf0+qfQE73LIYKcyMYHG94AuH0kgrQpRDuTZnSmjpysHmzxJXKNfa6PjFhyQ==} + set-cookie-parser@2.7.1: + resolution: {integrity: sha512-IOc8uWeOZgnb3ptbCURJWNjWUPcO3ZnTTdzsurqERrP6nPyv+paC55vJM0LpOlT2ne+Ix+9+CRG1MNLlyZ4GjQ==} set-value@2.0.1: resolution: {integrity: sha512-JxHc1weCN68wRY0fhCoXpyK55m/XPHafOmK4UWD7m2CI14GMcFypt4w/0+NV5f/ZMby2F6S2wwA7fgynh9gWSw==} @@ -1898,8 +1904,8 @@ packages: 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: @@ -2490,7 +2496,7 @@ snapshots: '@jridgewell/gen-mapping@0.3.5': 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': {} @@ -2499,6 +2505,8 @@ snapshots: '@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 @@ -2581,7 +2589,7 @@ snapshots: '@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)': dependencies: @@ -2590,7 +2598,7 @@ snapshots: estree-walker: 2.0.2 glob: 10.4.2 is-reference: 1.2.1 - magic-string: 0.30.10 + magic-string: 0.30.17 optionalDependencies: rollup: 4.24.0 @@ -2729,39 +2737,38 @@ snapshots: '@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.0(@sveltejs/kit@2.20.6(@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)))': 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)) + '@sveltejs/kit': 2.20.6(@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 - '@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.4.1(@sveltejs/kit@2.20.6(@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)))': 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)) + '@sveltejs/kit': 2.20.6(@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 transitivePeerDependencies: - encoding - 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.6(@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))': dependencies: '@sveltejs/vite-plugin-svelte': 3.1.1(svelte@4.2.19)(vite@5.4.12(@types/node@22.5.4)) '@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) '@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))': @@ -3203,7 +3210,7 @@ snapshots: escalade@3.1.2: {} - esm-env@1.0.0: {} + esm-env@1.2.2: {} esniff@2.0.1: dependencies: @@ -3511,6 +3518,10 @@ snapshots: dependencies: '@jridgewell/sourcemap-codec': 1.4.15 + magic-string@0.30.17: + dependencies: + '@jridgewell/sourcemap-codec': 1.5.0 + make-dir@3.1.0: dependencies: semver: 6.3.1 @@ -3611,7 +3622,7 @@ snapshots: mri@1.2.0: {} - mrmime@2.0.0: {} + mrmime@2.0.1: {} ms@2.1.2: {} @@ -3928,7 +3939,7 @@ snapshots: set-blocking@2.0.0: {} - set-cookie-parser@2.6.0: {} + set-cookie-parser@2.7.1: {} set-value@2.0.1: dependencies: @@ -3947,15 +3958,15 @@ snapshots: 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 @@ -4079,7 +4090,7 @@ snapshots: 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 From 9a825e56e47818468d874d1ba608fdf32753cd34 Mon Sep 17 00:00:00 2001 From: Lars Kiesow <lkiesow@uos.de> Date: Wed, 16 Apr 2025 00:45:29 +0200 Subject: [PATCH 38/79] Harmonize language and theme picker interface This patch adjusts the theme picker to more look and feel like the language picker right next to it. --- frontend/src/lib/components/Navbar.svelte | 27 ++++++++++++++++------- 1 file changed, 19 insertions(+), 8 deletions(-) diff --git a/frontend/src/lib/components/Navbar.svelte b/frontend/src/lib/components/Navbar.svelte index 5de5971..9135584 100644 --- a/frontend/src/lib/components/Navbar.svelte +++ b/frontend/src/lib/components/Navbar.svelte @@ -20,6 +20,8 @@ import { onMount } from 'svelte'; let inputElement: HTMLInputElement | null = null; + let theme = ''; + // Event listener for focusing input function handleKeydown(event: KeyboardEvent) { // Ignore any keypresses in an input/textarea field, so we don't interfere with typing. @@ -38,6 +40,8 @@ // Attach event listener on component mount document.addEventListener('keydown', handleKeydown); + theme = document.documentElement.getAttribute('data-theme'); + // Cleanup event listener on component destruction return () => { document.removeEventListener('keydown', handleKeydown); @@ -69,9 +73,14 @@ locale.set(newLocale); window.location.reload(); }; + const submitThemeChange = (event: Event) => { + const theme = event.target.value; + const themeForm = event.target.parentNode; + themeForm.action = `/?/setTheme&theme=${theme}`; + themeForm.submit(); + }; const submitUpdateTheme: SubmitFunction = ({ action }) => { const theme = action.searchParams.get('theme'); - console.log('theme', theme); if (theme) { document.documentElement.setAttribute('data-theme', theme); } @@ -303,13 +312,15 @@ </form> <p class="font-bold m-4 text-lg text-center">{$t('navbar.theme_selection')}</p> <form method="POST" use:enhance={submitUpdateTheme}> - {#each themes as theme} - <li> - <button formaction="/?/setTheme&theme={theme.name}" - >{$t(`navbar.themes.${theme.name}`)} - </button> - </li> - {/each} + <select + class="select select-bordered w-full max-w-xs bg-base-100 text-base-content" + bind:value={theme} + on:change={submitThemeChange} + > + {#each themes as theme} + <option value={theme.name} class="text-base-content">{$t(`navbar.themes.${theme.name}`)}</option> + {/each} + </select> </form> </ul> </div> From ea85c5fc5ad0daba13b3509594439c0622759a73 Mon Sep 17 00:00:00 2001 From: Lars Kiesow <lkiesow@uos.de> Date: Fri, 18 Apr 2025 00:22:50 +0200 Subject: [PATCH 39/79] Prevent UI overlaps in transportation card Similar to #552 (658764f) the card title and badges of the transportation card can overlap. This patch adjusts the transportation card to list the badges below the title similar to the other cards. --- .../lib/components/TransportationCard.svelte | 26 +++++++++---------- 1 file changed, 12 insertions(+), 14 deletions(-) diff --git a/frontend/src/lib/components/TransportationCard.svelte b/frontend/src/lib/components/TransportationCard.svelte index 8b384e9..c97bd85 100644 --- a/frontend/src/lib/components/TransportationCard.svelte +++ b/frontend/src/lib/components/TransportationCard.svelte @@ -110,22 +110,20 @@ > <div class="card-body space-y-4"> <!-- Title and Type --> - <div class="flex items-center justify-between"> - <h2 class="card-title text-lg font-semibold truncate">{transportation.name}</h2> - <div class="flex items-center gap-2"> - <div class="badge badge-secondary"> - {$t(`transportation.modes.${transportation.type}`) + - ' ' + - getTransportationIcon(transportation.type)} - </div> - {#if transportation.type == 'plane' && transportation.flight_number} - <div class="badge badge-neutral-200">{transportation.flight_number}</div> - {/if} + <h2 class="card-title text-lg font-semibold truncate">{transportation.name}</h2> + <div> + <div class="badge badge-secondary"> + {$t(`transportation.modes.${transportation.type}`) + + ' ' + + getTransportationIcon(transportation.type)} </div> + {#if transportation.type == 'plane' && transportation.flight_number} + <div class="badge badge-neutral-200">{transportation.flight_number}</div> + {/if} + {#if unlinked} + <div class="badge badge-error">{$t('adventures.out_of_range')}</div> + {/if} </div> - {#if unlinked} - <div class="badge badge-error">{$t('adventures.out_of_range')}</div> - {/if} <!-- Locations --> <div class="space-y-2"> From 0f36f34bfb77516efe3921da831fb6981dcb7f61 Mon Sep 17 00:00:00 2001 From: Lars Kiesow <lkiesow@uos.de> Date: Fri, 18 Apr 2025 00:44:47 +0200 Subject: [PATCH 40/79] Simple Frontend Build Test This patch adds a simple test for checking if the frontend builds properly. This is similar to the recently added backend check and will be run automatically on pushes and pull requests. --- .github/workflows/frontend-test.yml | 29 +++++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) create mode 100644 .github/workflows/frontend-test.yml diff --git a/.github/workflows/frontend-test.yml b/.github/workflows/frontend-test.yml new file mode 100644 index 0000000..e5b58e6 --- /dev/null +++ b/.github/workflows/frontend-test.yml @@ -0,0 +1,29 @@ +name: Test ₣ontend + +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 From 983a0384207284a185dc8969ae8146a678a25e5d Mon Sep 17 00:00:00 2001 From: Sean Morley <98704938+seanmorley15@users.noreply.github.com> Date: Fri, 18 Apr 2025 11:50:33 -0400 Subject: [PATCH 41/79] Update frontend-test.yml --- .github/workflows/frontend-test.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/frontend-test.yml b/.github/workflows/frontend-test.yml index e5b58e6..04df55a 100644 --- a/.github/workflows/frontend-test.yml +++ b/.github/workflows/frontend-test.yml @@ -1,4 +1,4 @@ -name: Test ₣ontend +name: Test Frontend on: pull_request: From a8502884dcc726135399608f5e448492cee32238 Mon Sep 17 00:00:00 2001 From: Sean Morley <mail@seanmorley.com> Date: Fri, 18 Apr 2025 15:04:04 -0400 Subject: [PATCH 42/79] feat: Add tutorial video section to usage documentation --- documentation/docs/usage/usage.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/documentation/docs/usage/usage.md b/documentation/docs/usage/usage.md index 8cf0129..8a2bf99 100644 --- a/documentation/docs/usage/usage.md +++ b/documentation/docs/usage/usage.md @@ -27,3 +27,7 @@ Welcome to AdventureLog! This guide will help you get started with AdventureLog - **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 + +<iframe width="560" height="315" src="https://www.youtube.com/embed/4Y2LvxG3xn4" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe> From 7499722867dafaa576a63b100acb4ec9f50b54b4 Mon Sep 17 00:00:00 2001 From: Sean Morley <mail@seanmorley.com> Date: Fri, 18 Apr 2025 21:55:30 -0400 Subject: [PATCH 43/79] feat: Add server error handling and SVG asset for 500 error page --- .../src/lib/assets/undraw_server_error.svg | 1 + frontend/src/routes/+error.svelte | 50 +++++++++++++++++++ 2 files changed, 51 insertions(+) create mode 100644 frontend/src/lib/assets/undraw_server_error.svg 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 0000000..0daa182 --- /dev/null +++ b/frontend/src/lib/assets/undraw_server_error.svg @@ -0,0 +1 @@ +<svg xmlns="http://www.w3.org/2000/svg" width="799.031" height="618.112" viewBox="0 0 799.031 618.112" xmlns:xlink="http://www.w3.org/1999/xlink" role="img" artist="Katerina Limpitsouni" source="https://undraw.co/"><g transform="translate(-893 -197)"><path d="M15.18,488.763c0,.872.478,1.573,1.073,1.573h535.1c.6,0,1.073-.7,1.073-1.573s-.478-1.573-1.073-1.573H16.253C15.658,487.191,15.18,487.891,15.18,488.763Z" transform="translate(1007.711 324.776)" fill="#ccc"/><rect width="19.105" height="3.371" transform="translate(1198.162 808.354)" fill="#b6b3c5"/><rect width="19.105" height="3.371" transform="translate(1367.295 808.917)" fill="#b6b3c5"/><path d="M352.955,370.945a27.529,27.529,0,0,1-54.321,0H229.146V521.536h193.3V370.945Z" transform="translate(966.721 287.378)" fill="#d6d6e3"/><rect width="193.296" height="5.242" transform="translate(1196.43 796.983)" fill="#090814"/><path d="M788.255,487.17H10.776A10.788,10.788,0,0,1,0,476.394V32.688A10.788,10.788,0,0,1,10.776,21.911H788.255a10.789,10.789,0,0,1,10.776,10.776V476.394a10.789,10.789,0,0,1-10.776,10.776Z" transform="translate(893 175.089)" fill="#090814"/><rect width="760.822" height="429.297" transform="translate(911.104 213.968)" fill="#fff"/><g transform="translate(20.477 16.308)"><path d="M604.463,379.271H317.442a8.655,8.655,0,0,1-8.645-8.645V273.8a8.655,8.655,0,0,1,8.645-8.645H604.463a8.655,8.655,0,0,1,8.645,8.645v96.826a8.655,8.655,0,0,1-8.645,8.645Z" transform="translate(811.648 85.826)" fill="#6c63ff"/><rect width="76.078" height="8.645" rx="2" transform="translate(1165.4 380.374)" fill="#fff"/><ellipse cx="5.187" cy="5.187" rx="5.187" ry="5.187" transform="translate(1336.576 380.374)" fill="#090814"/><ellipse cx="5.187" cy="5.187" rx="5.187" ry="5.187" transform="translate(1353.865 380.374)" fill="#090814"/><ellipse cx="5.187" cy="5.187" rx="5.187" ry="5.187" transform="translate(1371.156 380.374)" fill="#090814"/></g><ellipse cx="40.952" cy="40.952" rx="40.952" ry="40.952" transform="translate(1404.281 440.452)" fill="#090814"/><path d="M10.863-57.7l-.524-29.6h8.246l-.554,29.6Zm3.613,14.307a4.7,4.7,0,0,1-3.409-1.3,4.368,4.368,0,0,1-1.34-3.278,4.39,4.39,0,0,1,1.34-3.322,4.732,4.732,0,0,1,3.409-1.282,4.732,4.732,0,0,1,3.409,1.282,4.39,4.39,0,0,1,1.34,3.322,4.368,4.368,0,0,1-1.34,3.278A4.7,4.7,0,0,1,14.476-43.394Z" transform="translate(1430.76 546.754)" fill="#fff"/></g></svg> \ No newline at end of file diff --git a/frontend/src/routes/+error.svelte b/frontend/src/routes/+error.svelte index 4cff1c8..20fa13e 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'; </script> {#if $page.status === 404} @@ -24,3 +25,52 @@ </div> </div> {/if} + +{#if $page.status === 500} + <div + class="flex min-h-[100dvh] flex-col items-center justify-center bg-background px-4 py-12 sm:px-6 lg:px-8" + > + <div class="mx-auto max-w-md text-center"> + <img src={ServerError} alt="Lost in the forest" /> + <h1 class="text-center text-5xl font-extrabold mt-2"> + {$page.status}: {$page.error?.message} + </h1> + <h1 class="mt-4 text-xl font-bold tracking-tight text-foreground"> + Oops, looks like something went wrong. + </h1> + + <p class="mt-4"> + AdventureLog server encountered an error while processing your request. + <br /> + Please check the server logs for more information. + </p> + + <div class="alert alert-warning mt-4"> + <p class="text-muted-foreground"> + <strong>Administrators:</strong> Please check your setup using the + <a class="link link-primary" target="_blank" href="https://adventurelog.app" + >documentation</a + >. + </p> + </div> + + <!-- If the route is /login give a hint as an alert --> + {#if $page.url.pathname === '/login' || $page.url.pathname === '/signup'} + <div class="alert alert-info mt-4"> + <p + class="text-muted + -foreground" + > + <strong>Hint:</strong> If you are an administrator, please check your PUBLIC_SERVER_URL + in the frontend config to make sure it can reach the backend. + <br /> + </p> + </div> + {/if} + + <div class="mt-6 flex flex-col items-center gap-4 sm:flex-row"> + <button class="btn btn-neutral" on:click={() => goto('/')}>Go to Homepage</button> + </div> + </div> + </div> +{/if} From 6942f5e1bb863faf8d88a36a2bf03e22f6c85f1b Mon Sep 17 00:00:00 2001 From: Sean Morley <mail@seanmorley.com> Date: Fri, 18 Apr 2025 23:06:36 -0400 Subject: [PATCH 44/79] feat: Add TimezoneSelector component and integrate Luxon for date handling --- frontend/package.json | 1 + frontend/pnpm-lock.yaml | 9 + .../lib/components/TimezoneSelector.svelte | 92 ++++++++++ .../lib/components/TransportationModal.svelte | 167 +++++++++++++----- 4 files changed, 221 insertions(+), 48 deletions(-) create mode 100644 frontend/src/lib/components/TimezoneSelector.svelte diff --git a/frontend/package.json b/frontend/package.json index 918a869..a486beb 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -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 f41d2f5..5480141 100644 --- a/frontend/pnpm-lock.yaml +++ b/frontend/pnpm-lock.yaml @@ -23,6 +23,9 @@ importers: 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 @@ -1469,6 +1472,10 @@ packages: lru-queue@0.1.0: resolution: {integrity: sha512-BpdYkt9EvGl8OfWHDQPISVpcl5xZthb+XPsbELj5AQXxIC8IriDZIQYjBJPEm5rS420sjZ0TLEzRcq5KdBhYrQ==} + luxon@3.6.1: + resolution: {integrity: sha512-tJLxrKJhO2ukZ5z0gyjY1zPh3Rh88Ej9P7jNrZiHMUXHae1yvI2imgOZtL1TO8TW6biMMKfTtAOoEJANgtWBMQ==} + engines: {node: '>=12'} + magic-string@0.30.10: resolution: {integrity: sha512-iIRwTIf0QKV3UAnYK4PU8uiEc4SRh5jX0mwpIwETPpHdhVM4f53RSwS/vXvN1JhGX+Cs7B8qIq3d6AH49O5fAQ==} @@ -3514,6 +3521,8 @@ snapshots: dependencies: es5-ext: 0.10.64 + luxon@3.6.1: {} + magic-string@0.30.10: dependencies: '@jridgewell/sourcemap-codec': 1.4.15 diff --git a/frontend/src/lib/components/TimezoneSelector.svelte b/frontend/src/lib/components/TimezoneSelector.svelte new file mode 100644 index 0000000..c0738b8 --- /dev/null +++ b/frontend/src/lib/components/TimezoneSelector.svelte @@ -0,0 +1,92 @@ +<script lang="ts"> + import { t } from 'svelte-i18n'; + import { onMount } from 'svelte'; + + export let selectedTimezone: string = Intl.DateTimeFormat().resolvedOptions().timeZone; + + let dropdownOpen = false; + let searchQuery = ''; + const timezones = Intl.supportedValuesOf('timeZone'); + + // Filter timezones based on search query + $: filteredTimezones = searchQuery + ? timezones.filter((tz) => tz.toLowerCase().includes(searchQuery.toLowerCase())) + : timezones; + + function selectTimezone(tz: string) { + selectedTimezone = tz; + dropdownOpen = false; + searchQuery = ''; + } + + // Close dropdown if clicked outside + onMount(() => { + const handleClickOutside = (e: MouseEvent) => { + const dropdown = document.getElementById('tz-selector'); + if (dropdown && !dropdown.contains(e.target as Node)) dropdownOpen = false; + }; + document.addEventListener('click', handleClickOutside); + return () => document.removeEventListener('click', handleClickOutside); + }); +</script> + +<div class="form-control w-full max-w-xs relative" id="tz-selector"> + <label class="label"> + <span class="label-text">Timezone</span> + </label> + + <!-- Trigger --> + <div + tabindex="0" + role="button" + class="input input-bordered flex justify-between items-center cursor-pointer" + on:click={() => (dropdownOpen = !dropdownOpen)} + > + <span class="truncate">{selectedTimezone}</span> + <svg + xmlns="http://www.w3.org/2000/svg" + class="w-4 h-4" + fill="none" + viewBox="0 0 24 24" + stroke="currentColor" + > + <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 9l-7 7-7-7" /> + </svg> + </div> + + <!-- Dropdown --> + {#if dropdownOpen} + <div + class="absolute mt-1 z-10 bg-base-100 shadow-lg rounded-box w-full max-h-60 overflow-y-auto" + > + <!-- Search --> + <div class="sticky top-0 bg-base-100 p-2 border-b"> + <input + type="text" + placeholder="Search timezone" + class="input input-sm input-bordered w-full" + bind:value={searchQuery} + autofocus + /> + </div> + + <!-- Timezone list --> + {#if filteredTimezones.length > 0} + <ul class="menu p-2 space-y-1"> + {#each filteredTimezones as tz} + <li> + <a + class={`truncate ${tz === selectedTimezone ? 'active font-bold' : ''}`} + on:click|preventDefault={() => selectTimezone(tz)} + > + {tz} + </a> + </li> + {/each} + </ul> + {:else} + <div class="p-2 text-sm text-center opacity-60">No timezones found</div> + {/if} + </div> + {/if} +</div> diff --git a/frontend/src/lib/components/TransportationModal.svelte b/frontend/src/lib/components/TransportationModal.svelte index 1d4a516..3bd32e7 100644 --- a/frontend/src/lib/components/TransportationModal.svelte +++ b/frontend/src/lib/components/TransportationModal.svelte @@ -6,36 +6,53 @@ import { addToast } from '$lib/toasts'; let modal: HTMLDialogElement; import { t } from 'svelte-i18n'; + // @ts-ignore + import { DateTime } from 'luxon'; + + // Initialize with browser's timezone + let selectedTimezone: string = Intl.DateTimeFormat().resolvedOptions().timeZone; + + // Store the UTC dates as source of truth + let utcStartDate: string | null = null; + let utcEndDate: string | null = null; + + // Local display values + let localStartDate: string = ''; + let localEndDate: string = ''; import MarkdownEditor from './MarkdownEditor.svelte'; import { appVersion } from '$lib/config'; import { DefaultMarker, MapLibre } from 'svelte-maplibre'; + import TimezoneSelector from './TimezoneSelector.svelte'; export let collection: Collection; export let transportationToEdit: Transportation | null = null; let constrainDates: boolean = false; - // Format date as local datetime - // Convert an ISO date to a datetime-local value in local time. - function toLocalDatetime(value: string | null): string { - if (!value) return ''; - const date = new Date(value); - // Adjust the time by subtracting the timezone offset. - date.setMinutes(date.getMinutes() - date.getTimezoneOffset()); - // Return format YYYY-MM-DDTHH:mm - return date.toISOString().slice(0, 16); + // Convert a UTC ISO date to a datetime-local value in the specified timezone + function toLocalDatetime(utcDate: string | null, timezone: string = selectedTimezone): string { + if (!utcDate) return ''; + return DateTime.fromISO(utcDate, { zone: 'UTC' }) + .setZone(timezone) + .toISO({ suppressSeconds: true, includeOffset: false }) + .slice(0, 16); } + // Convert a local datetime to UTC + function toUTCDatetime(localDate: string, timezone: string = selectedTimezone): string | null { + if (!localDate) return null; + return DateTime.fromISO(localDate, { zone: timezone }).toUTC().toISO(); + } + + // Initialize transportation object let transportation: Transportation = { id: transportationToEdit?.id || '', type: transportationToEdit?.type || '', name: transportationToEdit?.name || '', description: transportationToEdit?.description || '', - date: transportationToEdit?.date ? toLocalDatetime(transportationToEdit.date) : null, - end_date: transportationToEdit?.end_date - ? toLocalDatetime(transportationToEdit.end_date) - : null, + date: null, + end_date: null, rating: transportationToEdit?.rating || 0, link: transportationToEdit?.link || '', flight_number: transportationToEdit?.flight_number || '', @@ -69,13 +86,44 @@ } } - console.log(transportation); + // Update local display dates whenever timezone or UTC dates change + $: { + if (utcStartDate) { + localStartDate = toLocalDatetime(utcStartDate, selectedTimezone); + } + if (utcEndDate) { + localEndDate = toLocalDatetime(utcEndDate, selectedTimezone); + } + } + + // Explicitly watch for timezone changes to update displayed dates + $: { + // This will trigger whenever selectedTimezone changes + selectedTimezone; + if (utcStartDate) { + localStartDate = toLocalDatetime(utcStartDate); + } + if (utcEndDate) { + localEndDate = toLocalDatetime(utcEndDate); + } + } onMount(async () => { modal = document.getElementById('my_modal_1') as HTMLDialogElement; if (modal) { modal.showModal(); } + + // Initialize UTC dates from transportationToEdit if available + if (transportationToEdit?.date) { + utcStartDate = transportationToEdit.date; + localStartDate = toLocalDatetime(utcStartDate); + } + + if (transportationToEdit?.end_date) { + utcEndDate = transportationToEdit.end_date; + localEndDate = toLocalDatetime(utcEndDate); + } }); function close() { @@ -88,6 +136,12 @@ } } + // Update UTC dates when local dates change + function updateUTCDates() { + utcStartDate = localStartDate ? toUTCDatetime(localStartDate) : null; + utcEndDate = localEndDate ? toUTCDatetime(localEndDate) : null; + } + async function geocode(e: Event | null) { if (e) { e.preventDefault(); @@ -172,47 +226,56 @@ Math.round(transportation.destination_longitude * 1e6) / 1e6; } - if (transportation.end_date && !transportation.date) { - transportation.date = null; - transportation.end_date = null; + // Validate dates + if (localEndDate && !localStartDate) { + addToast('error', $t('adventures.start_date_required')); + return; } - if (transportation.date && !transportation.end_date) { - transportation.end_date = transportation.date; + if (localStartDate && !localEndDate) { + // If only start date is provided, set end date to the same value + localEndDate = localStartDate; + utcEndDate = utcStartDate; } if ( - transportation.date && - transportation.end_date && - transportation.date > transportation.end_date + localStartDate && + localEndDate && + DateTime.fromISO(localStartDate).toMillis() > DateTime.fromISO(localEndDate).toMillis() ) { addToast('error', $t('adventures.start_before_end_error')); return; } - // Convert local dates to UTC - if (transportation.date && !transportation.date.includes('Z')) { - transportation.date = new Date(transportation.date).toISOString(); - } - if (transportation.end_date && !transportation.end_date.includes('Z')) { - transportation.end_date = new Date(transportation.end_date).toISOString(); - } + // Use the stored UTC dates for submission + const submissionData = { + ...transportation, + date: utcStartDate, + end_date: utcEndDate + }; if (transportation.type != 'plane') { - transportation.flight_number = ''; + submissionData.flight_number = ''; } - if (transportation.id === '') { + if (submissionData.id === '') { let res = await fetch('/api/transportations', { method: 'POST', headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify(transportation) + body: JSON.stringify(submissionData) }); let data = await res.json(); if (data.id) { transportation = data as Transportation; + // Update the UTC dates with the values from the server + utcStartDate = data.date; + utcEndDate = data.end_date; + // Update displayed dates + localStartDate = toLocalDatetime(utcStartDate); + localEndDate = toLocalDatetime(utcEndDate); + addToast('success', $t('adventures.adventure_created')); dispatch('save', transportation); } else { @@ -225,11 +288,18 @@ headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify(transportation) + body: JSON.stringify(submissionData) }); let data = await res.json(); if (data.id) { transportation = data as Transportation; + // Update the UTC dates with the values from the server + utcStartDate = data.date; + utcEndDate = data.end_date; + // Update displayed dates + localStartDate = toLocalDatetime(utcStartDate); + localEndDate = toLocalDatetime(utcEndDate); + addToast('success', $t('adventures.adventure_updated')); dispatch('save', transportation); } else { @@ -385,6 +455,7 @@ {$t('adventures.date_information')} </div> <div class="collapse-content"> + <TimezoneSelector bind:selectedTimezone /> <!-- Start Date --> <div> <label for="date"> @@ -409,7 +480,8 @@ type="datetime-local" id="date" name="date" - bind:value={transportation.date} + bind:value={localStartDate} + on:change={updateUTCDates} min={constrainDates ? fullStartDate : ''} max={constrainDates ? fullEndDate : ''} class="input input-bordered w-full max-w-xs mt-1" @@ -417,7 +489,7 @@ </div> </div> <!-- End Date --> - {#if transportation.date} + {#if localStartDate} <div> <label for="end_date"> {$t('adventures.end_date')} @@ -427,9 +499,10 @@ type="datetime-local" id="end_date" name="end_date" - min={constrainDates ? transportation.date : ''} + min={constrainDates ? localStartDate : ''} max={constrainDates ? fullEndDate : ''} - bind:value={transportation.end_date} + bind:value={localEndDate} + on:change={updateUTCDates} class="input input-bordered w-full max-w-xs mt-1" /> </div> @@ -451,13 +524,17 @@ </svg> <span> {$t('lodging.current_timezone')}: - {(() => { - const tz = new Intl.DateTimeFormat().resolvedOptions().timeZone; - const [continent, city] = tz.split('/'); - return `${continent} (${city.replace('_', ' ')})`; - })()} + {selectedTimezone} </span> </div> + {#if utcStartDate} + <div class="text-sm mt-2"> + UTC Time: {DateTime.fromISO(utcStartDate).toISO().slice(0, 16).replace('T', ' ')} + {#if utcEndDate && utcEndDate !== utcStartDate} + to {DateTime.fromISO(utcEndDate).toISO().slice(0, 16).replace('T', ' ')} + {/if} + </div> + {/if} </div> </div> @@ -585,11 +662,6 @@ class="relative aspect-[9/16] max-h-[70vh] w-full sm:aspect-video sm:max-h-full rounded-lg" standardControls > - <!-- MapEvents gives you access to map events even from other components inside the map, -where you might not have access to the top-level `MapLibre` component. In this case -it would also work to just use on:click on the MapLibre component itself. --> - <!-- @ts-ignore --> - {#if transportation.origin_latitude && transportation.origin_longitude} <DefaultMarker lngLat={[transportation.origin_longitude, transportation.origin_latitude]} @@ -604,7 +676,6 @@ it would also work to just use on:click on the MapLibre component itself. --> /> {/if} </MapLibre> - <!-- button to clear to and from location --> </div> {#if transportation.from_location || transportation.to_location} <button From c12f94855d1744f90227a8d46fc466dcec6fbd6f Mon Sep 17 00:00:00 2001 From: Sean Morley <mail@seanmorley.com> Date: Sat, 19 Apr 2025 21:44:40 -0400 Subject: [PATCH 45/79] feat: Refactor date handling in TransportationModal and add utility functions for date conversion and validation --- .../lib/components/TransportationModal.svelte | 116 +++++++++--------- frontend/src/lib/dateUtils.ts | 114 +++++++++++++++++ 2 files changed, 173 insertions(+), 57 deletions(-) create mode 100644 frontend/src/lib/dateUtils.ts diff --git a/frontend/src/lib/components/TransportationModal.svelte b/frontend/src/lib/components/TransportationModal.svelte index 3bd32e7..944b8e2 100644 --- a/frontend/src/lib/components/TransportationModal.svelte +++ b/frontend/src/lib/components/TransportationModal.svelte @@ -6,8 +6,14 @@ import { addToast } from '$lib/toasts'; let modal: HTMLDialogElement; import { t } from 'svelte-i18n'; - // @ts-ignore - import { DateTime } from 'luxon'; + import { + toLocalDatetime, + toUTCDatetime, + updateLocalDates, + updateUTCDates, + validateDateRange, + formatUTCDate + } from '$lib/dateUtils'; // Initialize with browser's timezone let selectedTimezone: string = Intl.DateTimeFormat().resolvedOptions().timeZone; @@ -30,21 +36,6 @@ let constrainDates: boolean = false; - // Convert a UTC ISO date to a datetime-local value in the specified timezone - function toLocalDatetime(utcDate: string | null, timezone: string = selectedTimezone): string { - if (!utcDate) return ''; - return DateTime.fromISO(utcDate, { zone: 'UTC' }) - .setZone(timezone) - .toISO({ suppressSeconds: true, includeOffset: false }) - .slice(0, 16); - } - - // Convert a local datetime to UTC - function toUTCDatetime(localDate: string, timezone: string = selectedTimezone): string | null { - if (!localDate) return null; - return DateTime.fromISO(localDate, { zone: timezone }).toUTC().toISO(); - } - // Initialize transportation object let transportation: Transportation = { id: transportationToEdit?.id || '', @@ -88,24 +79,13 @@ // Update local display dates whenever timezone or UTC dates change $: { - if (utcStartDate) { - localStartDate = toLocalDatetime(utcStartDate, selectedTimezone); - } - if (utcEndDate) { - localEndDate = toLocalDatetime(utcEndDate, selectedTimezone); - } - } - - // Explicitly watch for timezone changes to update displayed dates - $: { - // This will trigger whenever selectedTimezone changes - selectedTimezone; - if (utcStartDate) { - localStartDate = toLocalDatetime(utcStartDate); - } - if (utcEndDate) { - localEndDate = toLocalDatetime(utcEndDate); - } + const updatedDates = updateLocalDates({ + utcStartDate, + utcEndDate, + timezone: selectedTimezone + }); + localStartDate = updatedDates.localStartDate; + localEndDate = updatedDates.localEndDate; } onMount(async () => { @@ -117,13 +97,20 @@ // Initialize UTC dates from transportationToEdit if available if (transportationToEdit?.date) { utcStartDate = transportationToEdit.date; - localStartDate = toLocalDatetime(utcStartDate); } if (transportationToEdit?.end_date) { utcEndDate = transportationToEdit.end_date; - localEndDate = toLocalDatetime(utcEndDate); } + + // Update local dates based on UTC dates + const updatedDates = updateLocalDates({ + utcStartDate, + utcEndDate, + timezone: selectedTimezone + }); + localStartDate = updatedDates.localStartDate; + localEndDate = updatedDates.localEndDate; }); function close() { @@ -137,12 +124,18 @@ } // Update UTC dates when local dates change - function updateUTCDates() { - utcStartDate = localStartDate ? toUTCDatetime(localStartDate) : null; - utcEndDate = localEndDate ? toUTCDatetime(localEndDate) : null; + function handleLocalDateChange() { + const updated = updateUTCDates({ + localStartDate, + localEndDate, + timezone: selectedTimezone + }); + utcStartDate = updated.utcStartDate; + utcEndDate = updated.utcEndDate; } async function geocode(e: Event | null) { + // Geocoding logic unchanged if (e) { e.preventDefault(); } @@ -226,7 +219,7 @@ Math.round(transportation.destination_longitude * 1e6) / 1e6; } - // Validate dates + // Validate dates using utility function if (localEndDate && !localStartDate) { addToast('error', $t('adventures.start_date_required')); return; @@ -238,11 +231,9 @@ utcEndDate = utcStartDate; } - if ( - localStartDate && - localEndDate && - DateTime.fromISO(localStartDate).toMillis() > DateTime.fromISO(localEndDate).toMillis() - ) { + // Validate date range + const validation = validateDateRange(localStartDate, localEndDate); + if (!validation.valid) { addToast('error', $t('adventures.start_before_end_error')); return; } @@ -272,9 +263,15 @@ // Update the UTC dates with the values from the server utcStartDate = data.date; utcEndDate = data.end_date; - // Update displayed dates - localStartDate = toLocalDatetime(utcStartDate); - localEndDate = toLocalDatetime(utcEndDate); + + // Update displayed dates using utility function + const updatedDates = updateLocalDates({ + utcStartDate, + utcEndDate, + timezone: selectedTimezone + }); + localStartDate = updatedDates.localStartDate; + localEndDate = updatedDates.localEndDate; addToast('success', $t('adventures.adventure_created')); dispatch('save', transportation); @@ -296,9 +293,15 @@ // Update the UTC dates with the values from the server utcStartDate = data.date; utcEndDate = data.end_date; - // Update displayed dates - localStartDate = toLocalDatetime(utcStartDate); - localEndDate = toLocalDatetime(utcEndDate); + + // Update displayed dates using utility function + const updatedDates = updateLocalDates({ + utcStartDate, + utcEndDate, + timezone: selectedTimezone + }); + localStartDate = updatedDates.localStartDate; + localEndDate = updatedDates.localEndDate; addToast('success', $t('adventures.adventure_updated')); dispatch('save', transportation); @@ -481,7 +484,7 @@ id="date" name="date" bind:value={localStartDate} - on:change={updateUTCDates} + on:change={handleLocalDateChange} min={constrainDates ? fullStartDate : ''} max={constrainDates ? fullEndDate : ''} class="input input-bordered w-full max-w-xs mt-1" @@ -502,7 +505,7 @@ min={constrainDates ? localStartDate : ''} max={constrainDates ? fullEndDate : ''} bind:value={localEndDate} - on:change={updateUTCDates} + on:change={handleLocalDateChange} class="input input-bordered w-full max-w-xs mt-1" /> </div> @@ -529,15 +532,14 @@ </div> {#if utcStartDate} <div class="text-sm mt-2"> - UTC Time: {DateTime.fromISO(utcStartDate).toISO().slice(0, 16).replace('T', ' ')} + UTC Time: {formatUTCDate(utcStartDate)} {#if utcEndDate && utcEndDate !== utcStartDate} - to {DateTime.fromISO(utcEndDate).toISO().slice(0, 16).replace('T', ' ')} + to {formatUTCDate(utcEndDate)} {/if} </div> {/if} </div> </div> - <!-- Flight Information --> <div class="collapse collapse-plus bg-base-200 mb-4"> diff --git a/frontend/src/lib/dateUtils.ts b/frontend/src/lib/dateUtils.ts new file mode 100644 index 0000000..fdf2c8c --- /dev/null +++ b/frontend/src/lib/dateUtils.ts @@ -0,0 +1,114 @@ +// @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 ''; + return DateTime.fromISO(utcDate, { zone: 'UTC' }) + .setZone(timezone) + .toISO({ suppressSeconds: true, includeOffset: false }) + .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 +): string | null { + if (!localDate) return null; + return DateTime.fromISO(localDate, { zone: timezone }).toUTC().toISO(); +} + +/** + * Updates local datetime display values based on UTC values and timezone + * @param params Object containing UTC dates and timezone + * @returns Object with updated local datetime strings + */ +export function updateLocalDates({ + utcStartDate, + utcEndDate, + timezone +}: { + utcStartDate: string | null; + utcEndDate: string | null; + timezone: string; +}) { + return { + localStartDate: toLocalDatetime(utcStartDate, timezone), + localEndDate: toLocalDatetime(utcEndDate, timezone) + }; +} + +/** + * Updates UTC datetime values based on local values and timezone + * @param params Object containing local dates and timezone + * @returns Object with updated UTC datetime strings + */ +export function updateUTCDates({ + localStartDate, + localEndDate, + timezone +}: { + localStartDate: string; + localEndDate: string; + timezone: string; +}) { + return { + utcStartDate: toUTCDatetime(localStartDate, timezone), + utcEndDate: toUTCDatetime(localEndDate, timezone) + }; +} + +/** + * Validate date ranges + * @param startDate - Start date string + * @param endDate - End date string + * @returns Object with validation result and 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).toMillis() > DateTime.fromISO(endDate).toMillis() + ) { + return { + valid: false, + error: 'Start date must be before end date' + }; + } + + return { valid: true }; +} + +/** + * 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 ''; + return DateTime.fromISO(utcDate).toISO().slice(0, 16).replace('T', ' '); +} From 85b4db87ec325e216350c0e37e9bc117836c8b39 Mon Sep 17 00:00:00 2001 From: Sean Morley <mail@seanmorley.com> Date: Sat, 19 Apr 2025 21:53:17 -0400 Subject: [PATCH 46/79] refactor: Simplify date handling by replacing updateLocalDates and updateUTCDates with updateLocalDate and updateUTCDate functions --- .../lib/components/TransportationModal.svelte | 77 +++++++++---------- frontend/src/lib/dateUtils.ts | 36 +++------ 2 files changed, 48 insertions(+), 65 deletions(-) diff --git a/frontend/src/lib/components/TransportationModal.svelte b/frontend/src/lib/components/TransportationModal.svelte index 944b8e2..342dbd5 100644 --- a/frontend/src/lib/components/TransportationModal.svelte +++ b/frontend/src/lib/components/TransportationModal.svelte @@ -6,14 +6,7 @@ import { addToast } from '$lib/toasts'; let modal: HTMLDialogElement; import { t } from 'svelte-i18n'; - import { - toLocalDatetime, - toUTCDatetime, - updateLocalDates, - updateUTCDates, - validateDateRange, - formatUTCDate - } from '$lib/dateUtils'; + import { updateLocalDate, updateUTCDate, validateDateRange, formatUTCDate } from '$lib/dateUtils'; // Initialize with browser's timezone let selectedTimezone: string = Intl.DateTimeFormat().resolvedOptions().timeZone; @@ -79,13 +72,14 @@ // Update local display dates whenever timezone or UTC dates change $: { - const updatedDates = updateLocalDates({ - utcStartDate, - utcEndDate, + localStartDate = updateLocalDate({ + utcDate: utcStartDate, timezone: selectedTimezone - }); - localStartDate = updatedDates.localStartDate; - localEndDate = updatedDates.localEndDate; + }).localDate; + localEndDate = updateLocalDate({ + utcDate: utcEndDate, + timezone: selectedTimezone + }).localDate; } onMount(async () => { @@ -103,14 +97,14 @@ utcEndDate = transportationToEdit.end_date; } - // Update local dates based on UTC dates - const updatedDates = updateLocalDates({ - utcStartDate, - utcEndDate, + localStartDate = updateLocalDate({ + utcDate: utcStartDate, timezone: selectedTimezone - }); - localStartDate = updatedDates.localStartDate; - localEndDate = updatedDates.localEndDate; + }).localDate; + localEndDate = updateLocalDate({ + utcDate: utcEndDate, + timezone: selectedTimezone + }).localDate; }); function close() { @@ -125,13 +119,14 @@ // Update UTC dates when local dates change function handleLocalDateChange() { - const updated = updateUTCDates({ - localStartDate, - localEndDate, + utcStartDate = updateUTCDate({ + localDate: localStartDate, timezone: selectedTimezone - }); - utcStartDate = updated.utcStartDate; - utcEndDate = updated.utcEndDate; + }).utcDate; + utcEndDate = updateUTCDate({ + localDate: localEndDate, + timezone: selectedTimezone + }).utcDate; } async function geocode(e: Event | null) { @@ -264,14 +259,14 @@ utcStartDate = data.date; utcEndDate = data.end_date; - // Update displayed dates using utility function - const updatedDates = updateLocalDates({ - utcStartDate, - utcEndDate, + localStartDate = updateLocalDate({ + utcDate: utcStartDate, timezone: selectedTimezone - }); - localStartDate = updatedDates.localStartDate; - localEndDate = updatedDates.localEndDate; + }).localDate; + localEndDate = updateLocalDate({ + utcDate: utcEndDate, + timezone: selectedTimezone + }).localDate; addToast('success', $t('adventures.adventure_created')); dispatch('save', transportation); @@ -294,14 +289,14 @@ utcStartDate = data.date; utcEndDate = data.end_date; - // Update displayed dates using utility function - const updatedDates = updateLocalDates({ - utcStartDate, - utcEndDate, + localStartDate = updateLocalDate({ + utcDate: utcStartDate, timezone: selectedTimezone - }); - localStartDate = updatedDates.localStartDate; - localEndDate = updatedDates.localEndDate; + }).localDate; + localEndDate = updateLocalDate({ + utcDate: utcEndDate, + timezone: selectedTimezone + }).localDate; addToast('success', $t('adventures.adventure_updated')); dispatch('save', transportation); diff --git a/frontend/src/lib/dateUtils.ts b/frontend/src/lib/dateUtils.ts index fdf2c8c..d47f119 100644 --- a/frontend/src/lib/dateUtils.ts +++ b/frontend/src/lib/dateUtils.ts @@ -33,42 +33,30 @@ export function toUTCDatetime( } /** - * Updates local datetime display values based on UTC values and timezone - * @param params Object containing UTC dates and timezone - * @returns Object with updated local datetime strings + * 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 updateLocalDates({ - utcStartDate, - utcEndDate, +export function updateLocalDate({ + utcDate, timezone }: { - utcStartDate: string | null; - utcEndDate: string | null; + utcDate: string | null; timezone: string; }) { return { - localStartDate: toLocalDatetime(utcStartDate, timezone), - localEndDate: toLocalDatetime(utcEndDate, timezone) + localDate: toLocalDatetime(utcDate, timezone) }; } /** - * Updates UTC datetime values based on local values and timezone - * @param params Object containing local dates and timezone - * @returns Object with updated UTC datetime strings + * 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 updateUTCDates({ - localStartDate, - localEndDate, - timezone -}: { - localStartDate: string; - localEndDate: string; - timezone: string; -}) { +export function updateUTCDate({ localDate, timezone }: { localDate: string; timezone: string }) { return { - utcStartDate: toUTCDatetime(localStartDate, timezone), - utcEndDate: toUTCDatetime(localEndDate, timezone) + utcDate: toUTCDatetime(localDate, timezone) }; } From be0e56728a0b6fd0fa6e2974674ae447f035a738 Mon Sep 17 00:00:00 2001 From: Lars Kiesow <lkiesow@uos.de> Date: Fri, 25 Apr 2025 14:19:07 +0200 Subject: [PATCH 47/79] Upgrade Node.js to Version 22 This patch updates the Dockerfile to use Node.js 22 for running the frontend. Given that the security support of Node.js 18 ends in 6 days (30 Apr 2025), upgrading definitely makes sense. Additionally, this also seems to fix the broken JSON generated by the recently upgrraded version of @sveltejs/kit, thus fixing #584. --- frontend/Dockerfile | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/frontend/Dockerfile b/frontend/Dockerfile index 602960e..15450d4 100644 --- a/frontend/Dockerfile +++ b/frontend/Dockerfile @@ -1,5 +1,5 @@ # Use this image as the platform to build the app -FROM node:18-alpine AS external-website +FROM node:22-alpine AS external-website # A small line inside the image to show who made it LABEL Developers="Sean Morley" @@ -32,4 +32,4 @@ RUN chmod +x ./startup.sh USER node:node # Run startup.sh instead of the default command -CMD ["./startup.sh"] \ No newline at end of file +CMD ["./startup.sh"] From b8aa96b5b364f8b131ec92d0615ba741f48261da Mon Sep 17 00:00:00 2001 From: Lars Kiesow <lkiesow@uos.de> Date: Fri, 18 Apr 2025 16:35:02 +0200 Subject: [PATCH 48/79] Improve overlapping on navbar on mobile devices This patch makes it less likely for elements of the navigation bar to overlap each other on mobile devices. It also makes spacing a bit more homogeneous. The patch basically just adjust some spacing as and hides the map icon on mobile devices. --- frontend/src/lib/components/Navbar.svelte | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/frontend/src/lib/components/Navbar.svelte b/frontend/src/lib/components/Navbar.svelte index 5de5971..61e65c1 100644 --- a/frontend/src/lib/components/Navbar.svelte +++ b/frontend/src/lib/components/Navbar.svelte @@ -179,8 +179,9 @@ {/if} </ul> </div> - <a class="btn btn-ghost text-2xl font-bold tracking-normal" href="/"> - AdventureLog <img src="/favicon.png" alt="Map Logo" class="w-10" /> + <a class="btn btn-ghost p-0 text-2xl font-bold tracking-normal" href="/"> + AdventureLog + <img src="/favicon.png" alt="Map Logo" class="w-10 md:inline hidden" /> </a> </div> <div class="navbar-center hidden lg:flex"> @@ -265,7 +266,7 @@ <Avatar user={data.user} /> {/if} <div class="dropdown dropdown-bottom dropdown-end"> - <div tabindex="0" role="button" class="btn m-1 ml-4"> + <div tabindex="0" role="button" class="btn m-1 p-2"> <DotsHorizontal class="w-6 h-6" /> </div> <!-- svelte-ignore a11y-no-noninteractive-tabindex --> From 3a8776c000d8145c833846bc22a8352fbb474581 Mon Sep 17 00:00:00 2001 From: Lars Kiesow <lkiesow@uos.de> Date: Fri, 25 Apr 2025 15:54:02 +0200 Subject: [PATCH 49/79] Show AdventureLog icon instead of text in mobile mode This patch makes AdventureLog hide the text but show the app icon in the navigation bar when in mobile mode. --- frontend/src/lib/components/Navbar.svelte | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/frontend/src/lib/components/Navbar.svelte b/frontend/src/lib/components/Navbar.svelte index 61e65c1..efa0fa6 100644 --- a/frontend/src/lib/components/Navbar.svelte +++ b/frontend/src/lib/components/Navbar.svelte @@ -180,8 +180,8 @@ </ul> </div> <a class="btn btn-ghost p-0 text-2xl font-bold tracking-normal" href="/"> - AdventureLog - <img src="/favicon.png" alt="Map Logo" class="w-10 md:inline hidden" /> + <span class="md:inline hidden">AdventureLog</span> + <img src="/favicon.png" alt="Map Logo" class="w-10" /> </a> </div> <div class="navbar-center hidden lg:flex"> From 56bbbb0ffb231d2861453985d870b18970cb8ef9 Mon Sep 17 00:00:00 2001 From: Sean Morley <mail@seanmorley.com> Date: Sat, 26 Apr 2025 11:22:17 -0400 Subject: [PATCH 50/79] Implement code changes to enhance functionality and improve performance --- frontend/package.json | 2 +- frontend/pnpm-lock.yaml | 2287 +++++++++++++++++---------------------- 2 files changed, 1002 insertions(+), 1287 deletions(-) diff --git a/frontend/package.json b/frontend/package.json index a486beb..96dd929 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -18,7 +18,7 @@ "@iconify-json/mdi": "^1.1.67", "@sveltejs/adapter-node": "^5.2.0", "@sveltejs/adapter-vercel": "^5.4.1", - "@sveltejs/kit": "^2.20.6", + "@sveltejs/kit": "^2.8.3", "@sveltejs/vite-plugin-svelte": "^3.1.1", "@tailwindcss/typography": "^0.5.13", "@types/node": "^22.5.4", diff --git a/frontend/pnpm-lock.yaml b/frontend/pnpm-lock.yaml index 5480141..f084d2a 100644 --- a/frontend/pnpm-lock.yaml +++ b/frontend/pnpm-lock.yaml @@ -16,10 +16,10 @@ 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 @@ -28,7 +28,7 @@ importers: 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 @@ -40,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.20.6(@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.18(@types/node@22.15.2)))(svelte@4.2.19)(vite@5.4.18(@types/node@22.15.2))) '@sveltejs/adapter-vercel': specifier: ^5.4.1 - version: 5.4.1(@sveltejs/kit@2.20.6(@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.18(@types/node@22.15.2)))(svelte@4.2.19)(vite@5.4.18(@types/node@22.15.2)))(rollup@4.40.0) '@sveltejs/kit': - specifier: ^2.20.6 - version: 2.20.6(@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)) + specifier: ^2.8.3 + version: 2.20.7(@sveltejs/vite-plugin-svelte@3.1.2(svelte@4.2.19)(vite@5.4.18(@types/node@22.15.2)))(svelte@4.2.19)(vite@5.4.18(@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.18(@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) + version: 5.4.18(@types/node@22.15.2) packages: @@ -122,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==} @@ -143,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'} @@ -155,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'} @@ -167,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'} @@ -179,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'} @@ -191,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'} @@ -203,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'} @@ -215,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'} @@ -227,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'} @@ -239,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'} @@ -251,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'} @@ -263,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'} @@ -275,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'} @@ -287,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'} @@ -299,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'} @@ -311,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'} @@ -323,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'} @@ -335,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'} @@ -347,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'} @@ -359,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'} @@ -371,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'} @@ -383,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'} @@ -395,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'} @@ -407,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': @@ -456,19 +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: @@ -482,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': @@ -506,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': @@ -529,8 +680,8 @@ packages: '@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 @@ -547,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 @@ -556,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 @@ -569,193 +716,118 @@ 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.0': + resolution: {integrity: sha512-+Fbls/diZ0RDerhE8kyC6hjADCXA1K4yVNlH0EYfd2XjyH0UGgzaQ8MlT0pCXAThfxv3QUAczHaL+qSv1E4/Cg==} 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.0': + resolution: {integrity: sha512-PPA6aEEsTPRz+/4xxAmaoWDqh67N7wFbgFUJGMnanCFs0TV99M0M8QhhaSCks+n6EbQoFvLQgYOGXxlMGQe/6w==} 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==} + '@rollup/rollup-darwin-arm64@4.40.0': + resolution: {integrity: sha512-GwYOcOakYHdfnjjKwqpTGgn5a6cUX7+Ra2HeNj/GdXvO2VJOOXCiYYlRFU4CubFM67EhbmzLOmACKEfvp3J1kQ==} cpu: [arm64] os: [darwin] - '@rollup/rollup-darwin-arm64@4.31.0': - resolution: {integrity: sha512-WHIZfXgVBX30SWuTMhlHPXTyN20AXrLH4TEeH/D0Bolvx9PjgZnn4H677PlSGvU6MKNsjCQJYczkpvBbrBnG6g==} - 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.0': + resolution: {integrity: sha512-CoLEGJ+2eheqD9KBSxmma6ld01czS52Iw0e2qMZNpPDlf7Z9mj8xmMemxEucinev4LgHalDPczMyxzbq+Q+EtA==} 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.0': + resolution: {integrity: sha512-r7yGiS4HN/kibvESzmrOB/PxKMhPTlz+FcGvoUIKYoTyGd5toHp48g1uZy1o1xQvybwwpqpe010JrcGG2s5nkg==} cpu: [arm64] os: [freebsd] - '@rollup/rollup-freebsd-x64@4.31.0': - resolution: {integrity: sha512-pCANqpynRS4Jirn4IKZH4tnm2+2CqCNLKD7gAdEjzdLGbH1iO0zouHz4mxqg0uEMpO030ejJ0aA6e1PJo2xrPA==} + '@rollup/rollup-freebsd-x64@4.40.0': + resolution: {integrity: sha512-mVDxzlf0oLzV3oZOr0SMJ0lSDd3xC4CmnWJ8Val8isp9jRGl5Dq//LLDSPFrasS7pSm6m5xAcKaw3sHXhBjoRw==} cpu: [x64] os: [freebsd] - '@rollup/rollup-linux-arm-gnueabihf@4.24.0': - resolution: {integrity: sha512-0KXvIJQMOImLCVCz9uvvdPgfyWo93aHHp8ui3FrtOP57svqrF/roSSR5pjqL2hcMp0ljeGlU4q9o/rQaAQ3AYA==} + '@rollup/rollup-linux-arm-gnueabihf@4.40.0': + resolution: {integrity: sha512-y/qUMOpJxBMy8xCXD++jeu8t7kzjlOCkoxxajL58G62PJGBZVl/Gwpm7JK9+YvlB701rcQTzjUZ1JgUoPTnoQA==} cpu: [arm] os: [linux] - '@rollup/rollup-linux-arm-gnueabihf@4.31.0': - resolution: {integrity: sha512-0O8ViX+QcBd3ZmGlcFTnYXZKGbFu09EhgD27tgTdGnkcYXLat4KIsBBQeKLR2xZDCXdIBAlWLkiXE1+rJpCxFw==} + '@rollup/rollup-linux-arm-musleabihf@4.40.0': + resolution: {integrity: sha512-GoCsPibtVdJFPv/BOIvBKO/XmwZLwaNWdyD8TKlXuqp0veo2sHE+A/vpMQ5iSArRUz/uaoj4h5S6Pn0+PdhRjg==} cpu: [arm] os: [linux] - '@rollup/rollup-linux-arm-musleabihf@4.24.0': - resolution: {integrity: sha512-it2BW6kKFVh8xk/BnHfakEeoLPv8STIISekpoF+nBgWM4d55CZKc7T4Dx1pEbTnYm/xEKMgy1MNtYuoA8RFIWw==} - cpu: [arm] - os: [linux] - - '@rollup/rollup-linux-arm-musleabihf@4.31.0': - resolution: {integrity: sha512-w5IzG0wTVv7B0/SwDnMYmbr2uERQp999q8FMkKG1I+j8hpPX2BYFjWe69xbhbP6J9h2gId/7ogesl9hwblFwwg==} - 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.0': + resolution: {integrity: sha512-L5ZLphTjjAD9leJzSLI7rr8fNqJMlGDKlazW2tX4IUF9P7R5TMQPElpH82Q7eNIDQnQlAyiNVfRPfP2vM5Avvg==} 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.0': + resolution: {integrity: sha512-ATZvCRGCDtv1Y4gpDIXsS+wfFeFuLwVxyUBSLawjgXK2tRE6fnsQEkE4csQQYWlBlsFztRzCnBvWVfcae/1qxQ==} 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.0': + resolution: {integrity: sha512-wG9e2XtIhd++QugU5MD9i7OnpaVb08ji3P1y/hNbxrQ3sYEelKJOq1UJ5dXczeo6Hj2rfDEL5GdtkMSVLa/AOg==} cpu: [loong64] os: [linux] - '@rollup/rollup-linux-powerpc64le-gnu@4.24.0': - resolution: {integrity: sha512-2XFFPJ2XMEiF5Zi2EBf4h73oR1V/lycirxZxHZNc93SqDN/IWhYYSYj8I9381ikUFXZrz2v7r2tOVk2NBwxrWw==} + '@rollup/rollup-linux-powerpc64le-gnu@4.40.0': + resolution: {integrity: sha512-vgXfWmj0f3jAUvC7TZSU/m/cOE558ILWDzS7jBhiCAFpY2WEBn5jqgbqvmzlMjtp8KlLcBlXVD2mkTSEQE6Ixw==} cpu: [ppc64] os: [linux] - '@rollup/rollup-linux-powerpc64le-gnu@4.31.0': - resolution: {integrity: sha512-D7TXT7I/uKEuWiRkEFbed1UUYZwcJDU4vZQdPTcepK7ecPhzKOYk4Er2YR4uHKme4qDeIh6N3XrLfpuM7vzRWQ==} - cpu: [ppc64] - os: [linux] - - '@rollup/rollup-linux-riscv64-gnu@4.24.0': - resolution: {integrity: sha512-M3Dg4hlwuntUCdzU7KjYqbbd+BLq3JMAOhCKdBE3TcMGMZbKkDdJ5ivNdehOssMCIokNHFOsv7DO4rlEOfyKpg==} + '@rollup/rollup-linux-riscv64-gnu@4.40.0': + resolution: {integrity: sha512-uJkYTugqtPZBS3Z136arevt/FsKTF/J9dEMTX/cwR7lsAW4bShzI2R0pJVw+hcBTWF4dxVckYh72Hk3/hWNKvA==} 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.0': + resolution: {integrity: sha512-rKmSj6EXQRnhSkE22+WvrqOqRtk733x3p5sWpZilhmjnkHkpeCgWsFFo0dGnUGeA+OZjRl3+VYq+HyCOEuwcxQ==} cpu: [riscv64] os: [linux] - '@rollup/rollup-linux-s390x-gnu@4.24.0': - resolution: {integrity: sha512-mjBaoo4ocxJppTorZVKWFpy1bfFj9FeCMJqzlMQGjpNPY9JwQi7OuS1axzNIk0nMX6jSgy6ZURDZ2w0QW6D56g==} + '@rollup/rollup-linux-s390x-gnu@4.40.0': + resolution: {integrity: sha512-SpnYlAfKPOoVsQqmTFJ0usx0z84bzGOS9anAC0AZ3rdSo3snecihbhFTlJZ8XMwzqAcodjFU4+/SM311dqE5Sw==} 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==} + '@rollup/rollup-linux-x64-gnu@4.40.0': + resolution: {integrity: sha512-RcDGMtqF9EFN8i2RYN2W+64CdHruJ5rPqrlYw+cgM3uOVPSsnAQps7cpjXe9be/yDp8UC7VLoCoKC8J3Kn2FkQ==} cpu: [x64] os: [linux] - '@rollup/rollup-linux-x64-gnu@4.31.0': - resolution: {integrity: sha512-zSoHl356vKnNxwOWnLd60ixHNPRBglxpv2g7q0Cd3Pmr561gf0HiAcUBRL3S1vPqRC17Zo2CX/9cPkqTIiai1g==} + '@rollup/rollup-linux-x64-musl@4.40.0': + resolution: {integrity: sha512-HZvjpiUmSNx5zFgwtQAV1GaGazT2RWvqeDi0hV+AtC8unqqDSsaFjPxfsO6qPtKRRg25SisACWnJ37Yio8ttaw==} cpu: [x64] os: [linux] - '@rollup/rollup-linux-x64-musl@4.24.0': - resolution: {integrity: sha512-w1i+L7kAXZNdYl+vFvzSZy8Y1arS7vMgIy8wusXJzRrPyof5LAb02KGr1PD2EkRcl73kHulIID0M501lN+vobQ==} - cpu: [x64] - os: [linux] - - '@rollup/rollup-linux-x64-musl@4.31.0': - resolution: {integrity: sha512-ypB/HMtcSGhKUQNiFwqgdclWNRrAYDH8iMYH4etw/ZlGwiTVxBz2tDrGRrPlfZu6QjXwtd+C3Zib5pFqID97ZA==} - 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.0': + resolution: {integrity: sha512-UtZQQI5k/b8d7d3i9AZmA/t+Q4tk3hOC0tMOMSq2GlMYOfxbesxG4mJSeDp0EHs30N9bsfwUvs3zF4v/RzOeTQ==} 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.0': + resolution: {integrity: sha512-+m03kvI2f5syIqHXCZLPVYplP8pQch9JHyXKZ3AGMKlg8dCyr2PKHjwRLiW53LTrN/Nc3EqHOKxUxzoSPdKddA==} 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==} + '@rollup/rollup-win32-x64-msvc@4.40.0': + resolution: {integrity: sha512-lpPE1cLfP5oPzVjKMx10pgBmKELQnFJXHgvtHCtuJWOv8MxqdEIMNtgHgBFf7Ea2/7EuVwa9fodWUfXAlXZLZQ==} cpu: [x64] os: [win32] - '@rollup/rollup-win32-x64-msvc@4.31.0': - resolution: {integrity: sha512-ul8rnCsUumNln5YWwz0ted2ZHFhzhRRnkpBZ+YRuHoRAlUji9KChpOUOndY7uykrPEPXVbHLlsdo6v5yXo/TXw==} - 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.20.6': - resolution: {integrity: sha512-ImUkSQ//Xf4N9r0HHAe5vRA7RyQ7U1Ue1YUT235Ig+IiIqbsixEulHTHrP5LtBiC8xOkJoPZQ1VZ/nWHNOaGGw==} + '@sveltejs/kit@2.20.7': + resolution: {integrity: sha512-dVbLMubpJJSLI4OYB+yWYNHGAhgc2bVevWuBjDj8jFUXIJOAnLwYP3vsmtcgoxNGUXoq0rHS5f7MFCsryb6nzg==} engines: {node: '>=18.13'} hasBin: true peerDependencies: @@ -771,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/geojson@7946.0.16': + resolution: {integrity: sha512-6C8nqWur3j98U6+lXDfTUWIfgvZU+EumvpHKcYjujKH7woYyLj2sUmff0tRhrqM7BohUw7Pz3ZB1jj2gW9Fvmg==} - '@types/junit-report-builder@3.0.2': - resolution: {integrity: sha512-R5M+SYhMbwBeQcNXYWNCZkl09vkVfAtcPIaCGdzIkkbeaTrVbGQ7HVgi4s+EmM/M1K4ZuWQH0jGcvMvNePfxYA==} - - '@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==} @@ -807,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==} @@ -828,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: @@ -877,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==} @@ -932,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 @@ -944,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'} @@ -969,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==} @@ -997,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'} @@ -1015,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==} @@ -1057,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: '*' @@ -1074,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'} @@ -1089,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: @@ -1105,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==} @@ -1153,8 +1192,13 @@ 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.2.2: @@ -1173,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==} @@ -1212,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==} @@ -1238,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==} @@ -1254,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==} @@ -1269,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==} @@ -1294,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==} @@ -1326,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'} @@ -1375,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: @@ -1430,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: @@ -1452,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==} @@ -1465,9 +1459,8 @@ 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==} @@ -1476,22 +1469,15 @@ packages: resolution: {integrity: sha512-tJLxrKJhO2ukZ5z0gyjY1zPh3Rh88Ej9P7jNrZiHMUXHae1yvI2imgOZtL1TO8TW6biMMKfTtAOoEJANgtWBMQ==} engines: {node: '>=12'} - magic-string@0.30.10: - resolution: {integrity: sha512-iIRwTIf0QKV3UAnYK4PU8uiEc4SRh5jX0mwpIwETPpHdhVM4f53RSwS/vXvN1JhGX+Cs7B8qIq3d6AH49O5fAQ==} - magic-string@0.30.17: resolution: {integrity: sha512-sNPKHvyjVf7gyjwS4xGTaW/mCnF8wnjtifKBEhxfZ7E/S8tQ0rssrwGNn6q8JH/ohItJfSQp9mBtQYuTlH5QnA==} - make-dir@3.1.0: - resolution: {integrity: sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==} - engines: {node: '>=8'} - - 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 @@ -1502,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'} @@ -1513,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'} @@ -1524,40 +1503,32 @@ 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==} @@ -1567,8 +1538,8 @@ packages: 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==} @@ -1576,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 @@ -1593,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: @@ -1613,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'} @@ -1632,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==} @@ -1682,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==} @@ -1702,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==} - pmtiles@3.0.6: - resolution: {integrity: sha512-IdeMETd5lBIDVTLul1HFl0Q7l4KLJjzdxgcp+sN7pYvbipaV7o/0u0HiV06kaFCD0IGEN8KtUHyFZpY30WMflw==} + pkg-types@2.1.0: + resolution: {integrity: sha512-wmJwA+8ihJixSoHKxZJRBQG1oY8Yr9pGLzRmSsNms0iNWyHHAlZCa7mmKiFR10YPZuz/2k169JiS/inOjBCZ2A==} + + pmtiles@3.2.1: + resolution: {integrity: sha512-3R4fBwwoli5mw7a6t1IGwOtfmcSAODq6Okz0zkXhS1zi9sz1ssjjIfslwPvcWw5TNhdjNBUg9fgfPLeqZlH6ng==} pngjs@5.0.0: resolution: {integrity: sha512-40QW5YalBNfQo5yRYmiw7Yz6TKKVr3h6970B2YE+3fQpsWcrbj1PzJgxeJ19DRQjhMbKPIuMY8rFaXc8moolVw==} @@ -1744,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 @@ -1754,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 @@ -1798,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==} @@ -1822,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'} @@ -1833,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: @@ -1846,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.0: + resolution: {integrity: sha512-Noe455xmA96nnqH5piFtLobsGbCij7Tu+tb3c1vYjNbTkfzGqXqQXG3wJaYXkRZuQ0vEYN4bhwg7QnIrqB5B+w==} engines: {node: '>=18.0.0', npm: '>=8.0.0'} hasBin: true @@ -1877,12 +1820,8 @@ 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 @@ -1892,10 +1831,6 @@ packages: set-cookie-parser@2.7.1: resolution: {integrity: sha512-IOc8uWeOZgnb3ptbCURJWNjWUPcO3ZnTTdzsurqERrP6nPyv+paC55vJM0LpOlT2ne+Ix+9+CRG1MNLlyZ4GjQ==} - set-value@2.0.1: - resolution: {integrity: sha512-JxHc1weCN68wRY0fhCoXpyK55m/XPHafOmK4UWD7m2CI14GMcFypt4w/0+NV5f/ZMby2F6S2wwA7fgynh9gWSw==} - engines: {node: '>=0.10.0'} - shebang-command@2.0.0: resolution: {integrity: sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==} engines: {node: '>=8'} @@ -1904,9 +1839,6 @@ 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'} @@ -1919,34 +1851,10 @@ packages: 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'} - 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'} @@ -1966,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'} @@ -1986,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 @@ -2005,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 @@ -2061,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==} @@ -2084,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==} @@ -2101,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==} @@ -2114,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==} + undici-types@6.21.0: + resolution: {integrity: sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==} - 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'} - - 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 @@ -2155,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' @@ -2168,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.18: + resolution: {integrity: sha512-1oDcnEp3lVyHCuQ2YFelM4Alm2o91xNoMncRm1U7S+JdYfYOvbiGZ3/CxGttrOu2M/KcGz7cRC2DoNUA6urmMA==} engines: {node: ^18.0.0 || >=20.0.0} hasBin: true peerDependencies: @@ -2213,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==} @@ -2226,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==} @@ -2256,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 @@ -2272,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 @@ -2302,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 @@ -2500,7 +2464,11 @@ 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.5.0 @@ -2510,21 +2478,12 @@ snapshots: '@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: @@ -2537,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 @@ -2570,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 @@ -2578,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: @@ -2591,179 +2547,130 @@ 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.29': {} - '@rollup/plugin-commonjs@26.0.1(rollup@4.24.0)': + '@rollup/plugin-commonjs@28.0.3(rollup@4.40.0)': dependencies: - '@rollup/pluginutils': 5.1.0(rollup@4.24.0) + '@rollup/pluginutils': 5.1.4(rollup@4.40.0) 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.17 + picomatch: 4.0.2 optionalDependencies: - rollup: 4.24.0 + rollup: 4.40.0 - '@rollup/plugin-json@6.1.0(rollup@4.24.0)': + '@rollup/plugin-json@6.1.0(rollup@4.40.0)': dependencies: - '@rollup/pluginutils': 5.1.0(rollup@4.24.0) + '@rollup/pluginutils': 5.1.4(rollup@4.40.0) optionalDependencies: - rollup: 4.24.0 + rollup: 4.40.0 - '@rollup/plugin-node-resolve@15.2.3(rollup@4.24.0)': + '@rollup/plugin-node-resolve@16.0.1(rollup@4.40.0)': dependencies: - '@rollup/pluginutils': 5.1.0(rollup@4.24.0) + '@rollup/pluginutils': 5.1.4(rollup@4.40.0) '@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: 4.40.0 - '@rollup/pluginutils@4.2.1': + '@rollup/pluginutils@5.1.4(rollup@4.40.0)': dependencies: + '@types/estree': 1.0.7 estree-walker: 2.0.2 - picomatch: 2.3.1 - - '@rollup/pluginutils@5.1.0(rollup@4.24.0)': - dependencies: - '@types/estree': 1.0.6 - estree-walker: 2.0.2 - picomatch: 2.3.1 + picomatch: 4.0.2 optionalDependencies: - rollup: 4.24.0 + rollup: 4.40.0 - '@rollup/rollup-android-arm-eabi@4.24.0': + '@rollup/rollup-android-arm-eabi@4.40.0': optional: true - '@rollup/rollup-android-arm-eabi@4.31.0': + '@rollup/rollup-android-arm64@4.40.0': optional: true - '@rollup/rollup-android-arm64@4.24.0': + '@rollup/rollup-darwin-arm64@4.40.0': optional: true - '@rollup/rollup-android-arm64@4.31.0': + '@rollup/rollup-darwin-x64@4.40.0': optional: true - '@rollup/rollup-darwin-arm64@4.24.0': + '@rollup/rollup-freebsd-arm64@4.40.0': optional: true - '@rollup/rollup-darwin-arm64@4.31.0': + '@rollup/rollup-freebsd-x64@4.40.0': optional: true - '@rollup/rollup-darwin-x64@4.24.0': + '@rollup/rollup-linux-arm-gnueabihf@4.40.0': optional: true - '@rollup/rollup-darwin-x64@4.31.0': + '@rollup/rollup-linux-arm-musleabihf@4.40.0': optional: true - '@rollup/rollup-freebsd-arm64@4.31.0': + '@rollup/rollup-linux-arm64-gnu@4.40.0': optional: true - '@rollup/rollup-freebsd-x64@4.31.0': + '@rollup/rollup-linux-arm64-musl@4.40.0': optional: true - '@rollup/rollup-linux-arm-gnueabihf@4.24.0': + '@rollup/rollup-linux-loongarch64-gnu@4.40.0': optional: true - '@rollup/rollup-linux-arm-gnueabihf@4.31.0': + '@rollup/rollup-linux-powerpc64le-gnu@4.40.0': optional: true - '@rollup/rollup-linux-arm-musleabihf@4.24.0': + '@rollup/rollup-linux-riscv64-gnu@4.40.0': optional: true - '@rollup/rollup-linux-arm-musleabihf@4.31.0': + '@rollup/rollup-linux-riscv64-musl@4.40.0': optional: true - '@rollup/rollup-linux-arm64-gnu@4.24.0': + '@rollup/rollup-linux-s390x-gnu@4.40.0': optional: true - '@rollup/rollup-linux-arm64-gnu@4.31.0': + '@rollup/rollup-linux-x64-gnu@4.40.0': optional: true - '@rollup/rollup-linux-arm64-musl@4.24.0': + '@rollup/rollup-linux-x64-musl@4.40.0': optional: true - '@rollup/rollup-linux-arm64-musl@4.31.0': + '@rollup/rollup-win32-arm64-msvc@4.40.0': optional: true - '@rollup/rollup-linux-loongarch64-gnu@4.31.0': + '@rollup/rollup-win32-ia32-msvc@4.40.0': optional: true - '@rollup/rollup-linux-powerpc64le-gnu@4.24.0': + '@rollup/rollup-win32-x64-msvc@4.40.0': optional: true - '@rollup/rollup-linux-powerpc64le-gnu@4.31.0': - optional: true - - '@rollup/rollup-linux-riscv64-gnu@4.24.0': - optional: true - - '@rollup/rollup-linux-riscv64-gnu@4.31.0': - optional: true - - '@rollup/rollup-linux-s390x-gnu@4.24.0': - optional: true - - '@rollup/rollup-linux-s390x-gnu@4.31.0': - 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.20.6(@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.18(@types/node@22.15.2)))(svelte@4.2.19)(vite@5.4.18(@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.20.6(@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.0) + '@rollup/plugin-json': 6.1.0(rollup@4.40.0) + '@rollup/plugin-node-resolve': 16.0.1(rollup@4.40.0) + '@sveltejs/kit': 2.20.7(@sveltejs/vite-plugin-svelte@3.1.2(svelte@4.2.19)(vite@5.4.18(@types/node@22.15.2)))(svelte@4.2.19)(vite@5.4.18(@types/node@22.15.2)) + rollup: 4.40.0 - '@sveltejs/adapter-vercel@5.4.1(@sveltejs/kit@2.20.6(@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.18(@types/node@22.15.2)))(svelte@4.2.19)(vite@5.4.18(@types/node@22.15.2)))(rollup@4.40.0)': dependencies: - '@sveltejs/kit': 2.20.6(@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.18(@types/node@22.15.2)))(svelte@4.2.19)(vite@5.4.18(@types/node@22.15.2)) + '@vercel/nft': 0.29.2(rollup@4.40.0) + esbuild: 0.24.2 transitivePeerDependencies: - encoding + - rollup - supports-color - '@sveltejs/kit@2.20.6(@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.18(@types/node@22.15.2)))(svelte@4.2.19)(vite@5.4.18(@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.18(@types/node@22.15.2)) '@types/cookie': 0.6.0 cookie: 0.6.0 devalue: 5.1.1 @@ -2776,66 +2683,64 @@ snapshots: set-cookie-parser: 2.7.1 sirv: 3.0.1 svelte: 4.2.19 - vite: 5.4.12(@types/node@22.5.4) + vite: 5.4.18(@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.18(@types/node@22.15.2)))(svelte@4.2.19)(vite@5.4.18(@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.18(@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.18(@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.18(@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.18(@types/node@22.15.2)))(svelte@4.2.19)(vite@5.4.18(@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.18(@types/node@22.15.2) + vitefu: 0.2.5(vite@5.4.18(@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': {} @@ -2843,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.0)': 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.0) + 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: @@ -2905,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: {} @@ -2959,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: @@ -3003,7 +2875,7 @@ snapshots: optionalDependencies: fsevents: 2.3.3 - chownr@2.0.0: {} + chownr@3.0.0: {} cli-color@2.0.4: dependencies: @@ -3021,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 @@ -3033,8 +2905,6 @@ snapshots: color-name@1.1.4: {} - color-support@1.1.3: {} - commander@4.1.1: {} commondir@1.0.1: {} @@ -3048,9 +2918,11 @@ snapshots: readable-stream: 3.6.2 typedarray: 0.0.6 - confbox@0.1.7: {} + confbox@0.1.8: {} - console-control-strings@1.1.0: {} + confbox@0.2.2: {} + + consola@3.4.2: {} cookie@0.6.0: {} @@ -3068,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: {} @@ -3087,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: {} @@ -3120,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: {} @@ -3215,7 +3087,35 @@ snapshots: '@esbuild/win32-ia32': 0.21.5 '@esbuild/win32-x64': 0.21.5 - escalade@3.1.2: {} + 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: {} @@ -3230,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 @@ -3272,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: {} @@ -3289,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: @@ -3312,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: @@ -3342,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: @@ -3360,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: {} @@ -3374,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: @@ -3405,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: {} @@ -3446,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: {} @@ -3488,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: {} @@ -3505,17 +3348,13 @@ 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: @@ -3523,19 +3362,11 @@ snapshots: luxon@3.6.1: {} - magic-string@0.30.10: - dependencies: - '@jridgewell/sourcemap-codec': 1.4.15 - magic-string@0.30.17: dependencies: '@jridgewell/sourcemap-codec': 1.5.0 - make-dir@3.1.0: - dependencies: - semver: 6.3.1 - - maplibre-gl@4.5.0: + maplibre-gl@4.7.1: dependencies: '@mapbox/geojson-rewind': 0.5.2 '@mapbox/jsonlint-lines-primitives': 2.0.2 @@ -3544,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: {} @@ -3580,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: @@ -3589,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.1: {} - ms@2.1.2: {} + ms@2.1.3: {} murmurhash-js@1.0.0: {} @@ -3643,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: {} @@ -3651,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: {} @@ -3682,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: {} @@ -3720,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.1.1: + pkg-types@1.3.1: dependencies: - confbox: 0.1.7 - mlly: 1.7.1 - pathe: 1.1.2 + confbox: 0.1.8 + mlly: 1.7.4 + pathe: 2.0.3 - pmtiles@3.0.6: + pkg-types@2.1.0: dependencies: - '@types/leaflet': 1.9.12 + confbox: 0.2.2 + exsolve: 1.0.5 + pathe: 2.0.3 + + pmtiles@3.2.1: + dependencies: + '@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: {} @@ -3830,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 @@ -3852,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: + rollup@4.40.0: dependencies: - glob: 7.2.3 - - rollup@4.24.0: - dependencies: - '@types/estree': 1.0.6 + '@types/estree': 1.0.7 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: - dependencies: - '@types/estree': 1.0.6 - 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.0 + '@rollup/rollup-android-arm64': 4.40.0 + '@rollup/rollup-darwin-arm64': 4.40.0 + '@rollup/rollup-darwin-x64': 4.40.0 + '@rollup/rollup-freebsd-arm64': 4.40.0 + '@rollup/rollup-freebsd-x64': 4.40.0 + '@rollup/rollup-linux-arm-gnueabihf': 4.40.0 + '@rollup/rollup-linux-arm-musleabihf': 4.40.0 + '@rollup/rollup-linux-arm64-gnu': 4.40.0 + '@rollup/rollup-linux-arm64-musl': 4.40.0 + '@rollup/rollup-linux-loongarch64-gnu': 4.40.0 + '@rollup/rollup-linux-powerpc64le-gnu': 4.40.0 + '@rollup/rollup-linux-riscv64-gnu': 4.40.0 + '@rollup/rollup-linux-riscv64-musl': 4.40.0 + '@rollup/rollup-linux-s390x-gnu': 4.40.0 + '@rollup/rollup-linux-x64-gnu': 4.40.0 + '@rollup/rollup-linux-x64-musl': 4.40.0 + '@rollup/rollup-win32-arm64-msvc': 4.40.0 + '@rollup/rollup-win32-ia32-msvc': 4.40.0 + '@rollup/rollup-win32-x64-msvc': 4.40.0 fsevents: 2.3.3 run-parallel@1.2.0: @@ -3942,29 +3715,18 @@ 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.7.1: {} - 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 - shebang-command@2.0.0: dependencies: shebang-regex: 3.0.0 shebang-regex@3.0.0: {} - signal-exit@3.0.7: {} - signal-exit@4.1.0: {} sirv@3.0.1: @@ -3980,29 +3742,8 @@ snapshots: 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 @@ -4025,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: @@ -4035,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: @@ -4049,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 @@ -4081,21 +3818,22 @@ 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 @@ -4104,62 +3842,62 @@ snapshots: 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: @@ -4179,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: @@ -4191,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: {} + typescript@5.8.3: {} - typewise-core@1.2.0: {} + ufo@1.6.1: {} - typewise@1.0.3: + undici-types@6.21.0: {} + + unplugin-icons@0.19.3: dependencies: - typewise-core: 1.2.0 - - ufo@1.5.3: {} - - 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 - - unplugin-icons@0.19.0: - 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.18(@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.0 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.18(@types/node@22.15.2)): optionalDependencies: - vite: 5.4.12(@types/node@22.5.4) + vite: 5.4.18(@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: @@ -4277,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: @@ -4311,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: @@ -4333,5 +4050,3 @@ snapshots: which-module: 2.0.1 y18n: 4.0.3 yargs-parser: 18.1.3 - - yocto-queue@0.1.0: {} From e40ea028d0d1abf121c91ebd26b34c248204f166 Mon Sep 17 00:00:00 2001 From: Lars Kiesow <lkiesow@uos.de> Date: Sat, 26 Apr 2025 22:53:45 +0200 Subject: [PATCH 51/79] Collection view selection on mobile devices The tab-based selection of views in a collection doesn't really work on mobile devices since it needs too much horizontal space. This leads to text overflowing buttons as well as half of the tab bar disappearing behind the right edge of the phone screen. This patch modifies the navigation by keeping the current tabs in desktop mode, but switching to a very compact dropdown on mobile devices. The functionality of both elements is identical. --- .../src/routes/collections/[id]/+page.svelte | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/frontend/src/routes/collections/[id]/+page.svelte b/frontend/src/routes/collections/[id]/+page.svelte index ab12175..b57af12 100644 --- a/frontend/src/routes/collections/[id]/+page.svelte +++ b/frontend/src/routes/collections/[id]/+page.svelte @@ -308,6 +308,10 @@ } } + function changeHash(event) { + window.location.hash = '#' + event.target.value; + } + onMount(() => { if (data.props.adventure) { collection = data.props.adventure; @@ -772,7 +776,17 @@ {/if} {#if collection.id} - <div class="flex justify-center mx-auto"> + <select class="select select-bordered border-primary md:hidden w-full" + value={currentView} + on:change={changeHash} + > + <option value="itinerary">📅 Itinerary</option> + <option value="all">🗒️ All Linked Items</option> + <option value="calendar">🗓️ Calendar</option> + <option value="map">🗺️ Map</option> + <option value="recommendations">👍️ Recommendations</option> + </select> + <div class="md:flex justify-center mx-auto hidden"> <!-- svelte-ignore a11y-missing-attribute --> <div role="tablist" class="tabs tabs-boxed tabs-lg max-w-full"> <!-- svelte-ignore a11y-missing-attribute --> From cd494fefeefe0f1ee2a5529c57b61993e5de7ab1 Mon Sep 17 00:00:00 2001 From: Lars Kiesow <lkiesow@uos.de> Date: Sun, 27 Apr 2025 15:30:12 +0200 Subject: [PATCH 52/79] Unify collection card width The width of collection cards can vary quite a bit. Additionally, the cards look different than the cards within a collection. It would be nice if the design would be more or leyy the same for all of them. This patch adjusts the collection card design by adapting the classes from the cards within the collection (I literally just copied the classes from ChecklistCard). This is another step in making the user interface more homogeneous. --- frontend/src/lib/components/CollectionCard.svelte | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frontend/src/lib/components/CollectionCard.svelte b/frontend/src/lib/components/CollectionCard.svelte index 78b511f..44691f3 100644 --- a/frontend/src/lib/components/CollectionCard.svelte +++ b/frontend/src/lib/components/CollectionCard.svelte @@ -87,7 +87,7 @@ {/if} <div - class="card min-w-max lg:w-96 md:w-80 sm:w-60 xs:w-40 bg-neutral text-neutral-content shadow-xl" + class="card w-full max-w-xs sm:max-w-sm md:max-w-md lg:max-w-md xl:max-w-md bg-neutral text-neutral-content shadow-xl overflow-hidden" > <CardCarousel {adventures} /> <div class="card-body"> From 932036bc8b807ed6e771ec9f72de3180481bc93f Mon Sep 17 00:00:00 2001 From: Lars Kiesow <lkiesow@uos.de> Date: Sun, 27 Apr 2025 15:41:43 +0200 Subject: [PATCH 53/79] Move hiding AdventureLog to first Tailwind breakpoint This is a slight improvement to pull request #576. I noticed that on a tablet, the AdventureLog in the navigation bar would not render, even though there would be enpugh free space. This patch moves the breakpoint for hiding the text one step further towards smaller devices. --- frontend/src/lib/components/Navbar.svelte | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frontend/src/lib/components/Navbar.svelte b/frontend/src/lib/components/Navbar.svelte index efa0fa6..72a01cf 100644 --- a/frontend/src/lib/components/Navbar.svelte +++ b/frontend/src/lib/components/Navbar.svelte @@ -180,7 +180,7 @@ </ul> </div> <a class="btn btn-ghost p-0 text-2xl font-bold tracking-normal" href="/"> - <span class="md:inline hidden">AdventureLog</span> + <span class="sm:inline hidden">AdventureLog</span> <img src="/favicon.png" alt="Map Logo" class="w-10" /> </a> </div> From 911ce67d9fcbe99c3024202052f18459f7d17b92 Mon Sep 17 00:00:00 2001 From: Lars Kiesow <lkiesow@uos.de> Date: Sun, 27 Apr 2025 16:03:16 +0200 Subject: [PATCH 54/79] Remove Invisible MapMarker This patch removes the map marker from the adventures list item of the main menu dropdown. It is not being rendered and given that all other elements do not have an icon, it is probably a remnant of old code and left by accident. --- frontend/src/lib/components/Navbar.svelte | 1 - 1 file changed, 1 deletion(-) diff --git a/frontend/src/lib/components/Navbar.svelte b/frontend/src/lib/components/Navbar.svelte index efa0fa6..66b0dd3 100644 --- a/frontend/src/lib/components/Navbar.svelte +++ b/frontend/src/lib/components/Navbar.svelte @@ -121,7 +121,6 @@ > {#if data.user} <li> - <MapMarker /> <button on:click={() => goto('/adventures')}>{$t('navbar.adventures')}</button> </li> <li> From 5f7bf5275860448151e1828560dccd1710f989b6 Mon Sep 17 00:00:00 2001 From: Lars Kiesow <lkiesow@uos.de> Date: Sun, 27 Apr 2025 16:08:03 +0200 Subject: [PATCH 55/79] Adjust main menu font size This patch slightly adjusts the font size of list items in the main menu. I probably wouldn't mind making it even a bit bigger, but that's probably worth a separate discussion. Reasons for this adjustment: 1. Don't use different font sizes in the same lement. While the buttons rendered at 14px, the search text rendered at 16px. They should have the same font-size. 2. The buttons were below the base text size controlled by Tailwind CSS. This means, it also puts the font size below the recommended font-size for mobile devices (Google/Apple guidelines). This patch puts them at the base level. --- frontend/src/lib/components/Navbar.svelte | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frontend/src/lib/components/Navbar.svelte b/frontend/src/lib/components/Navbar.svelte index 0e5e4f2..1d776df 100644 --- a/frontend/src/lib/components/Navbar.svelte +++ b/frontend/src/lib/components/Navbar.svelte @@ -116,7 +116,7 @@ <!-- svelte-ignore a11y-no-noninteractive-tabindex --> <ul tabindex="0" - class="menu dropdown-content mt-3 z-[1] p-2 shadow bg-neutral text-neutral-content rounded-box gap-2 w-96" + class="menu dropdown-content mt-3 z-[1] p-2 shadow bg-neutral text-base text-neutral-content rounded-box gap-2 w-96" > {#if data.user} <li> From f6097a2d60fb1220ab0ffe0a46d576f4ad1f050b Mon Sep 17 00:00:00 2001 From: Lars Kiesow <lkiesow@uos.de> Date: Sun, 27 Apr 2025 17:42:51 +0200 Subject: [PATCH 56/79] Open in Apple Maps or Google Maps This patch provides the additional option to open a location in Google maps as an alternative to Apple Maps. --- .../src/routes/adventures/[id]/+page.svelte | 21 +++++++++++++------ 1 file changed, 15 insertions(+), 6 deletions(-) diff --git a/frontend/src/routes/adventures/[id]/+page.svelte b/frontend/src/routes/adventures/[id]/+page.svelte index f11f450..b32a19e 100644 --- a/frontend/src/routes/adventures/[id]/+page.svelte +++ b/frontend/src/routes/adventures/[id]/+page.svelte @@ -458,12 +458,21 @@ </div> {/if} {#if adventure.longitude && adventure.latitude} - <a - class="btn btn-neutral btn-sm max-w-32" - href={`https://maps.apple.com/?q=${adventure.latitude},${adventure.longitude}`} - target="_blank" - rel="noopener noreferrer">{$t('adventures.open_in_maps')}</a - > + <div class="flex flex-wrap gap-2 items-center"> + <span>{$t('adventures.open_in_maps')}:</span> + <a + class="btn btn-neutral text-base btn-sm max-w-32" + href={`https://maps.apple.com/?q=${adventure.latitude},${adventure.longitude}`} + target="_blank" + rel="noopener noreferrer">Apple</a + > + <a + class="btn btn-neutral text-base btn-sm max-w-32" + href={`https://maps.google.com/?q=${adventure.latitude},${adventure.longitude}`} + target="_blank" + rel="noopener noreferrer">Google</a + > + </div> {/if} <MapLibre style="https://basemaps.cartocdn.com/gl/voyager-gl-style/style.json" From f31db982cee5e0604aefe19517a19262de1647f2 Mon Sep 17 00:00:00 2001 From: Lars Kiesow <lkiesow@uos.de> Date: Sun, 27 Apr 2025 17:49:31 +0200 Subject: [PATCH 57/79] Full width map on mobile This patch lets the map in the adventure details use the full screen width on mobile instead of having one sixth of the screen empty. --- frontend/src/routes/adventures/[id]/+page.svelte | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frontend/src/routes/adventures/[id]/+page.svelte b/frontend/src/routes/adventures/[id]/+page.svelte index f11f450..c76d905 100644 --- a/frontend/src/routes/adventures/[id]/+page.svelte +++ b/frontend/src/routes/adventures/[id]/+page.svelte @@ -467,7 +467,7 @@ {/if} <MapLibre style="https://basemaps.cartocdn.com/gl/voyager-gl-style/style.json" - class="flex items-center self-center justify-center aspect-[9/16] max-h-[70vh] sm:aspect-video sm:max-h-full w-10/12 rounded-lg" + class="flex items-center self-center justify-center aspect-[9/16] max-h-[70vh] sm:aspect-video sm:max-h-full w-full md:w-10/12 rounded-lg" standardControls center={{ lng: adventure.longitude || 0, lat: adventure.latitude || 0 }} zoom={adventure.longitude ? 12 : 1} From f1f1cda7991087d3afe2aa9f32fc6f879e74056d Mon Sep 17 00:00:00 2001 From: lesensei <1148387+lesensei@users.noreply.github.com> Date: Sun, 27 Apr 2025 18:05:27 +0200 Subject: [PATCH 58/79] Update fr.json --- frontend/src/locales/fr.json | 318 +++++++++++++++++------------------ 1 file changed, 159 insertions(+), 159 deletions(-) diff --git a/frontend/src/locales/fr.json b/frontend/src/locales/fr.json index 249243d..86703fc 100644 --- a/frontend/src/locales/fr.json +++ b/frontend/src/locales/fr.json @@ -50,19 +50,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 +72,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 +84,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 +98,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 +117,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 +125,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 +138,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 +148,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,27 +232,27 @@ "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", - "all_day": "Toute la journée", + "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 de date", - "no_ordered_items": "Ajoutez des articles avec des dates à la collection pour les voir ici.", - "ordered_itinerary": "Itinéraire ordonné" + "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é" }, "home": { "desc_1": "Découvrez, planifiez et explorez en toute simplicité", @@ -272,7 +272,7 @@ "about": "À propos de AdventureLog", "adventures": "Aventures", "collections": "Collections", - "discord": "Discorde", + "discord": "Discord", "documentation": "Documentation", "greeting": "Salut", "logout": "Déconnexion", @@ -285,12 +285,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", @@ -303,7 +303,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é ?", @@ -317,18 +317,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", @@ -357,27 +357,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", @@ -385,77 +385,77 @@ "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." }, "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" @@ -473,7 +473,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 !", @@ -485,13 +485,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", @@ -499,33 +499,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", @@ -533,20 +533,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" @@ -563,7 +563,7 @@ "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", @@ -571,8 +571,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", @@ -592,7 +592,7 @@ "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", + "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." }, "recomendations": { @@ -604,31 +604,31 @@ }, "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" } From 645cc9728b6642e7222e16eb0bb5c74aebf8ce98 Mon Sep 17 00:00:00 2001 From: Shaun Walker <shaun@theshaun.com> Date: Sat, 3 May 2025 20:50:06 +1000 Subject: [PATCH 59/79] Update en.json - Correct spelling of Search --- frontend/src/locales/en.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frontend/src/locales/en.json b/frontend/src/locales/en.json index 25a79de..7c4f452 100644 --- a/frontend/src/locales/en.json +++ b/frontend/src/locales/en.json @@ -130,7 +130,7 @@ "location": "Location", "search_for_location": "Search for a location", "clear_map": "Clear map", - "search_results": "Searh results", + "search_results": "Search results", "no_results": "No results found", "wiki_desc": "Pulls excerpt from Wikipedia article matching the name of the adventure.", "attachments": "Attachments", From 7442bd70cd9ee8d3f0d838ed24b449c430d9204c Mon Sep 17 00:00:00 2001 From: Sean Morley <mail@seanmorley.com> Date: Tue, 6 May 2025 14:38:31 -0400 Subject: [PATCH 60/79] Update version to 0.9.0, add DateRangeDropdown component, enhance LodgingModal with price step, and add invalid date range message --- frontend/package.json | 2 +- .../lib/components/DateRangeDropdown.svelte | 172 ++++++++++++++++++ .../src/lib/components/LodgingModal.svelte | 1 + frontend/src/locales/en.json | 1 + frontend/src/routes/datetest/+page.svelte | 13 ++ 5 files changed, 188 insertions(+), 1 deletion(-) create mode 100644 frontend/src/lib/components/DateRangeDropdown.svelte create mode 100644 frontend/src/routes/datetest/+page.svelte diff --git a/frontend/package.json b/frontend/package.json index 96dd929..2d3d3e0 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -1,6 +1,6 @@ { "name": "adventurelog-frontend", - "version": "0.8.0", + "version": "0.9.0", "scripts": { "dev": "vite dev", "django": "cd .. && cd backend/server && python3 manage.py runserver", diff --git a/frontend/src/lib/components/DateRangeDropdown.svelte b/frontend/src/lib/components/DateRangeDropdown.svelte new file mode 100644 index 0000000..608d8bd --- /dev/null +++ b/frontend/src/lib/components/DateRangeDropdown.svelte @@ -0,0 +1,172 @@ +<script lang="ts"> + import type { Collection } from '$lib/types'; + import TimezoneSelector from './TimezoneSelector.svelte'; + import { t } from 'svelte-i18n'; + export let collection: Collection | null = null; + import { updateLocalDate, updateUTCDate, validateDateRange, formatUTCDate } from '$lib/dateUtils'; + import { onMount } from 'svelte'; + + // Initialize with browser's timezone + let selectedTimezone: string = Intl.DateTimeFormat().resolvedOptions().timeZone; + + // Store the UTC dates as source of truth + export let utcStartDate: string | null = null; + export let utcEndDate: string | null = null; + + // Local display values + let localStartDate: string = ''; + let localEndDate: string = ''; + + let fullStartDate: string = ''; + let fullEndDate: string = ''; + + let constrainDates: boolean = false; + + onMount(async () => { + // Initialize UTC dates from transportationToEdit if available + localStartDate = updateLocalDate({ + utcDate: utcStartDate, + timezone: selectedTimezone + }).localDate; + localEndDate = updateLocalDate({ + utcDate: utcEndDate, + timezone: selectedTimezone + }).localDate; + }); + + if (collection && collection.start_date && collection.end_date) { + fullStartDate = `${collection.start_date}T00:00`; + fullEndDate = `${collection.end_date}T23:59`; + } + + // Update local display dates whenever timezone or UTC dates change + $: { + localStartDate = updateLocalDate({ + utcDate: utcStartDate, + timezone: selectedTimezone + }).localDate; + localEndDate = updateLocalDate({ + utcDate: utcEndDate, + timezone: selectedTimezone + }).localDate; + } + + // Update UTC dates when local dates change + function handleLocalDateChange() { + utcStartDate = updateUTCDate({ + localDate: localStartDate, + timezone: selectedTimezone + }).utcDate; + utcEndDate = updateUTCDate({ + localDate: localEndDate, + timezone: selectedTimezone + }).utcDate; + } +</script> + +<div class="collapse collapse-plus bg-base-200 mb-4 rounded-lg"> + <input type="checkbox" checked /> + <div class="collapse-title text-xl font-semibold"> + {$t('adventures.date_information')} + </div> + <div class="collapse-content space-y-6"> + <!-- Timezone Selector --> + <div class="flex flex-col sm:flex-row sm:items-center sm:justify-between gap-4"> + <TimezoneSelector bind:selectedTimezone /> + </div> + + <!-- Dates Input Section --> + <div class="grid grid-cols-1 md:grid-cols-2 gap-6"> + <!-- Start Date --> + <div class="flex flex-col space-y-2"> + <label for="date" class="font-medium"> + {$t('adventures.start_date')} + </label> + + <input + type="datetime-local" + id="date" + name="date" + bind:value={localStartDate} + on:change={handleLocalDateChange} + min={constrainDates ? fullStartDate : ''} + max={constrainDates ? fullEndDate : ''} + class="input input-bordered w-full" + /> + + {#if collection && collection.start_date && collection.end_date} + <label class="flex items-center gap-2 mt-2"> + <input + type="checkbox" + class="toggle toggle-primary" + id="constrain_dates" + name="constrain_dates" + on:change={() => (constrainDates = !constrainDates)} + /> + <span class="label-text">{$t('adventures.date_constrain')}</span> + </label> + {/if} + </div> + + <!-- End Date --> + {#if localStartDate} + <div class="flex flex-col space-y-2"> + <label for="end_date" class="font-medium"> + {$t('adventures.end_date')} + </label> + + <input + type="datetime-local" + id="end_date" + name="end_date" + bind:value={localEndDate} + on:change={handleLocalDateChange} + min={constrainDates ? localStartDate : ''} + max={constrainDates ? fullEndDate : ''} + class="input input-bordered w-full" + /> + </div> + {/if} + </div> + + <!-- Validation Message --> + {#if !validateDateRange(localStartDate, localEndDate).valid} + <div role="alert" class="alert alert-error"> + <svg + xmlns="http://www.w3.org/2000/svg" + class="h-6 w-6 shrink-0 stroke-current" + fill="none" + viewBox="0 0 24 24" + > + <path + stroke-linecap="round" + stroke-linejoin="round" + stroke-width="2" + d="M10 14l2-2m0 0l2-2m-2 2l-2-2m2 2l2 2m7-2a9 9 0 11-18 0 9 9 0 0118 0z" + /> + </svg> + <span>{$t('adventures.invalid_date_range')}</span> + </div> + {/if} + + <!-- + <div role="alert" class="alert shadow-lg bg-neutral text-neutral-content mt-6"> + <svg + xmlns="http://www.w3.org/2000/svg" + fill="none" + viewBox="0 0 24 24" + class="stroke-info h-6 w-6 shrink-0" + > + <path + stroke-linecap="round" + stroke-linejoin="round" + stroke-width="2" + d="M13 16h-1v-4h-1m1-4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z" + ></path> + </svg> + <span class="ml-2"> + {$t('lodging.current_timezone')}: {selectedTimezone} + </span> + </div> --> + </div> +</div> diff --git a/frontend/src/lib/components/LodgingModal.svelte b/frontend/src/lib/components/LodgingModal.svelte index d91155c..9a2975e 100644 --- a/frontend/src/lib/components/LodgingModal.svelte +++ b/frontend/src/lib/components/LodgingModal.svelte @@ -323,6 +323,7 @@ id="price" name="price" bind:value={lodging.price} + step="0.01" class="input input-bordered w-full max-w-xs mt-1" /> </div> diff --git a/frontend/src/locales/en.json b/frontend/src/locales/en.json index 65c7288..20e2e90 100644 --- a/frontend/src/locales/en.json +++ b/frontend/src/locales/en.json @@ -62,6 +62,7 @@ "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", "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.", diff --git a/frontend/src/routes/datetest/+page.svelte b/frontend/src/routes/datetest/+page.svelte new file mode 100644 index 0000000..b06ba1f --- /dev/null +++ b/frontend/src/routes/datetest/+page.svelte @@ -0,0 +1,13 @@ +<script> + import DateRangeDropdown from '$lib/components/DateRangeDropdown.svelte'; + + let utcStartDate = ''; + let utcEndDate = ''; +</script> + +<DateRangeDropdown bind:utcStartDate bind:utcEndDate /> + +<p>{new Date(utcStartDate).toLocaleString()} - {new Date(utcEndDate).toLocaleString()}</p> + +<p>UTC Start Date: {utcStartDate}</p> +<p>UTC End Date: {utcEndDate}</p> From bbad7b890ccf4fbe55a8b16910985d823b4351fc Mon Sep 17 00:00:00 2001 From: Lars Kiesow <lkiesow@uos.de> Date: Wed, 7 May 2025 21:10:38 +0200 Subject: [PATCH 61/79] Add support for OpenStreetMap This patch adds an option to open an item in OpenStreetMap as well as in Google Maps and Apple Maps. --- .../src/routes/adventures/[id]/+page.svelte | 36 +++++++++++-------- 1 file changed, 22 insertions(+), 14 deletions(-) diff --git a/frontend/src/routes/adventures/[id]/+page.svelte b/frontend/src/routes/adventures/[id]/+page.svelte index b32a19e..afbb3ae 100644 --- a/frontend/src/routes/adventures/[id]/+page.svelte +++ b/frontend/src/routes/adventures/[id]/+page.svelte @@ -458,20 +458,28 @@ </div> {/if} {#if adventure.longitude && adventure.latitude} - <div class="flex flex-wrap gap-2 items-center"> - <span>{$t('adventures.open_in_maps')}:</span> - <a - class="btn btn-neutral text-base btn-sm max-w-32" - href={`https://maps.apple.com/?q=${adventure.latitude},${adventure.longitude}`} - target="_blank" - rel="noopener noreferrer">Apple</a - > - <a - class="btn btn-neutral text-base btn-sm max-w-32" - href={`https://maps.google.com/?q=${adventure.latitude},${adventure.longitude}`} - target="_blank" - rel="noopener noreferrer">Google</a - > + <div> + {$t('adventures.open_in_maps')}: + <div class="flex flex-wrap gap-2"> + <a + class="btn btn-neutral text-base btn-sm max-w-32" + href={`https://maps.apple.com/?q=${adventure.latitude},${adventure.longitude}`} + target="_blank" + rel="noopener noreferrer">Apple</a + > + <a + class="btn btn-neutral text-base btn-sm max-w-32" + href={`https://maps.google.com/?q=${adventure.latitude},${adventure.longitude}`} + target="_blank" + rel="noopener noreferrer">Google</a + > + <a + class="btn btn-neutral text-base btn-sm max-w-32" + href={`https://www.openstreetmap.org/?mlat=${adventure.latitude}&mlon=${adventure.longitude}`} + target="_blank" + rel="noopener noreferrer">OSM</a + > + </div> </div> {/if} <MapLibre From 2c50ca0b1aec1974cdc7c0c4862614233a89bbb1 Mon Sep 17 00:00:00 2001 From: Sean Morley <mail@seanmorley.com> Date: Fri, 9 May 2025 10:24:29 -0400 Subject: [PATCH 62/79] Refactor date handling components: Replace DateRangeDropdown with DateRangeCollapse - Introduced DateRangeCollapse.svelte to manage date range selection with timezone support. - Removed DateRangeDropdown.svelte as it was redundant. - Updated LodgingModal and TransportationModal to utilize DateRangeCollapse for date selection. - Enhanced date conversion utilities to handle all-day events correctly. - Adjusted TimezoneSelector for improved accessibility and focus management. - Updated date handling logic in dateUtils.ts to support all-day events. - Modified test page to reflect changes in date range component usage. --- .../src/lib/components/AdventureModal.svelte | 232 +---------- .../lib/components/DateRangeCollapse.svelte | 387 ++++++++++++++++++ .../lib/components/DateRangeDropdown.svelte | 172 -------- .../src/lib/components/LodgingModal.svelte | 158 ++----- .../lib/components/TimezoneSelector.svelte | 41 +- .../lib/components/TransportationModal.svelte | 211 +--------- frontend/src/lib/dateUtils.ts | 28 +- frontend/src/routes/datetest/+page.svelte | 13 - 8 files changed, 484 insertions(+), 758 deletions(-) create mode 100644 frontend/src/lib/components/DateRangeCollapse.svelte delete mode 100644 frontend/src/lib/components/DateRangeDropdown.svelte delete mode 100644 frontend/src/routes/datetest/+page.svelte diff --git a/frontend/src/lib/components/AdventureModal.svelte b/frontend/src/lib/components/AdventureModal.svelte index 20dd39f..add0ef4 100644 --- a/frontend/src/lib/components/AdventureModal.svelte +++ b/frontend/src/lib/components/AdventureModal.svelte @@ -92,6 +92,7 @@ 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 = ''; @@ -684,237 +685,8 @@ <ActivityComplete bind:activities={adventure.activity_types} /> </div> </div> - <div class="collapse collapse-plus bg-base-200 mb-4"> - <input type="checkbox" /> - <div class="collapse-title text-xl font-medium"> - {$t('adventures.visits')} ({adventure.visits.length}) - </div> - <div class="collapse-content"> - <label class="label cursor-pointer flex items-start space-x-2"> - {#if adventure.collection && collection && collection.start_date && collection.end_date} - <span class="label-text">{$t('adventures.date_constrain')}</span> - <input - type="checkbox" - class="toggle toggle-primary" - id="constrain_dates" - name="constrain_dates" - on:change={() => (constrainDates = !constrainDates)} - /> - {/if} - <span class="label-text">{$t('adventures.all_day')}</span> - <input - type="checkbox" - class="toggle toggle-primary" - id="constrain_dates" - name="constrain_dates" - bind:checked={allDay} - /> - </label> - <div class="flex gap-2 mb-1"> - {#if !allDay} - <input - type="datetime-local" - class="input input-bordered w-full" - placeholder={$t('adventures.start_date')} - min={constrainDates ? fullStartDate : ''} - max={constrainDates ? fullEndDate : ''} - bind:value={new_start_date} - on:keydown={(e) => { - if (e.key === 'Enter') { - e.preventDefault(); - addNewVisit(); - } - }} - /> - <input - type="datetime-local" - class="input input-bordered w-full" - placeholder={$t('adventures.end_date')} - bind:value={new_end_date} - min={constrainDates ? fullStartDate : ''} - max={constrainDates ? fullEndDate : ''} - on:keydown={(e) => { - if (e.key === 'Enter') { - e.preventDefault(); - addNewVisit(); - } - }} - /> - {:else} - <input - type="date" - class="input input-bordered w-full" - placeholder={$t('adventures.start_date')} - min={constrainDates ? fullStartDateOnly : ''} - max={constrainDates ? fullEndDateOnly : ''} - bind:value={new_start_date} - on:keydown={(e) => { - if (e.key === 'Enter') { - e.preventDefault(); - addNewVisit(); - } - }} - /> - <input - type="date" - class="input input-bordered w-full" - placeholder={$t('adventures.end_date')} - bind:value={new_end_date} - min={constrainDates ? fullStartDateOnly : ''} - max={constrainDates ? fullEndDateOnly : ''} - on:keydown={(e) => { - if (e.key === 'Enter') { - e.preventDefault(); - addNewVisit(); - } - }} - /> - {/if} - </div> - <div class="flex gap-2 mb-1"> - <!-- textarea for notes --> - <textarea - class="textarea textarea-bordered w-full" - placeholder={$t('adventures.add_notes')} - bind:value={new_notes} - on:keydown={(e) => { - if (e.key === 'Enter') { - e.preventDefault(); - addNewVisit(); - } - }} - ></textarea> - </div> - {#if !allDay} - <div role="alert" class="alert shadow-lg bg-neutral mt-2 mb-2"> - <svg - xmlns="http://www.w3.org/2000/svg" - fill="none" - viewBox="0 0 24 24" - class="stroke-info h-6 w-6 shrink-0" - > - <path - stroke-linecap="round" - stroke-linejoin="round" - stroke-width="2" - d="M13 16h-1v-4h-1m1-4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z" - ></path> - </svg> - <span> - {$t('lodging.current_timezone')}: - {(() => { - const tz = new Intl.DateTimeFormat().resolvedOptions().timeZone; - const [continent, city] = tz.split('/'); - return `${continent} (${city.replace('_', ' ')})`; - })()} - </span> - </div> - {/if} - <div class="flex gap-2"> - <button type="button" class="btn btn-neutral" on:click={addNewVisit} - >{$t('adventures.add')}</button - > - </div> - - {#if adventure.visits.length > 0} - <h2 class="font-bold text-xl mt-2">{$t('adventures.my_visits')}</h2> - {#each adventure.visits as visit} - <div class="flex flex-col gap-2"> - <div class="flex gap-2 items-center"> - <p> - {#if isAllDay(visit.start_date)} - <!-- For all-day events, show just the date --> - {new Date(visit.start_date).toLocaleDateString(undefined, { - timeZone: 'UTC' - })} - {:else} - <!-- For timed events, show date and time --> - {new Date(visit.start_date).toLocaleDateString()} ({new Date( - visit.start_date - ).toLocaleTimeString()}) - {/if} - </p> - {#if visit.end_date && visit.end_date !== visit.start_date} - <p> - {#if isAllDay(visit.end_date)} - <!-- For all-day events, show just the date --> - {new Date(visit.end_date).toLocaleDateString(undefined, { - timeZone: 'UTC' - })} - {:else} - <!-- For timed events, show date and time --> - {new Date(visit.end_date).toLocaleDateString()} ({new Date( - visit.end_date - ).toLocaleTimeString()}) - {/if} - </p> - {/if} - <div> - <button - type="button" - class="btn btn-sm btn-neutral" - on:click={() => { - // Determine if this is an all-day event - const isAllDayEvent = isAllDay(visit.start_date); - allDay = isAllDayEvent; - - if (isAllDayEvent) { - // For all-day events, use date only - new_start_date = visit.start_date.split('T')[0]; - new_end_date = visit.end_date.split('T')[0]; - } else { - // For timed events, format properly for datetime-local input - const startDate = new Date(visit.start_date); - const endDate = new Date(visit.end_date); - - // Format as yyyy-MM-ddThh:mm - new_start_date = - startDate.getFullYear() + - '-' + - String(startDate.getMonth() + 1).padStart(2, '0') + - '-' + - String(startDate.getDate()).padStart(2, '0') + - 'T' + - String(startDate.getHours()).padStart(2, '0') + - ':' + - String(startDate.getMinutes()).padStart(2, '0'); - - new_end_date = - endDate.getFullYear() + - '-' + - String(endDate.getMonth() + 1).padStart(2, '0') + - '-' + - String(endDate.getDate()).padStart(2, '0') + - 'T' + - String(endDate.getHours()).padStart(2, '0') + - ':' + - String(endDate.getMinutes()).padStart(2, '0'); - } - - new_notes = visit.notes; - adventure.visits = adventure.visits.filter((v) => v !== visit); - }} - > - {$t('lodging.edit')} - </button> - <button - type="button" - class="btn btn-sm btn-error" - on:click={() => { - adventure.visits = adventure.visits.filter((v) => v !== visit); - }} - > - {$t('adventures.remove')} - </button> - </div> - </div> - <p class="whitespace-pre-wrap -mt-2 mb-2">{visit.notes}</p> - </div> - {/each} - {/if} - </div> - </div> + <DateRangeCollapse type="adventure" {collection} bind:visits={adventure.visits} /> <div> <div class="mt-4"> diff --git a/frontend/src/lib/components/DateRangeCollapse.svelte b/frontend/src/lib/components/DateRangeCollapse.svelte new file mode 100644 index 0000000..d8fc699 --- /dev/null +++ b/frontend/src/lib/components/DateRangeCollapse.svelte @@ -0,0 +1,387 @@ +<script lang="ts"> + import type { Collection } from '$lib/types'; + import TimezoneSelector from './TimezoneSelector.svelte'; + import { t } from 'svelte-i18n'; + export let collection: Collection | null = null; + import { updateLocalDate, updateUTCDate, validateDateRange, formatUTCDate } from '$lib/dateUtils'; + import { onMount } from 'svelte'; + import { isAllDay } from '$lib'; + + export let type: 'adventure' | 'transportation' | 'lodging' = 'adventure'; + + // Initialize with browser's timezone + let selectedTimezone: string = Intl.DateTimeFormat().resolvedOptions().timeZone; + + let allDay: boolean = false; + + // Store the UTC dates as source of truth + export let utcStartDate: string | null = null; + export let utcEndDate: string | null = null; + + console.log('UTC Start Date:', utcStartDate); + console.log('UTC End Date:', utcEndDate); + + export let note: string | null = null; + type Visit = { + id: string; + start_date: string; + end_date: string; + notes: string; + }; + export let visits: Visit[] | null = null; + + // Local display values + let localStartDate: string = ''; + let localEndDate: string = ''; + + let fullStartDate: string = ''; + let fullEndDate: string = ''; + + let constrainDates: boolean = false; + + let isEditing = false; // Disable reactivity when editing + + onMount(async () => { + console.log('Selected timezone:', selectedTimezone); + console.log('UTC Start Date:', utcStartDate); + console.log('UTC End Date:', utcEndDate); + // Initialize UTC dates from transportationToEdit if available + localStartDate = updateLocalDate({ + utcDate: utcStartDate, + timezone: selectedTimezone + }).localDate; + localEndDate = updateLocalDate({ + utcDate: utcEndDate, + timezone: selectedTimezone + }).localDate; + }); + + if (collection && collection.start_date && collection.end_date) { + fullStartDate = `${collection.start_date}T00:00`; + fullEndDate = `${collection.end_date}T23:59`; + } + + // Update local display dates whenever timezone or UTC dates change + $: if (!isEditing) { + if (allDay) { + localStartDate = utcStartDate?.substring(0, 10) ?? ''; + localEndDate = utcEndDate?.substring(0, 10) ?? ''; + } else { + const start = updateLocalDate({ + utcDate: utcStartDate, + timezone: selectedTimezone + }).localDate; + + const end = updateLocalDate({ + utcDate: utcEndDate, + timezone: selectedTimezone + }).localDate; + + localStartDate = start; + localEndDate = end; + } + } + + // Update UTC dates when local dates change + function handleLocalDateChange() { + utcStartDate = updateUTCDate({ + localDate: localStartDate, + timezone: selectedTimezone, + allDay + }).utcDate; + + utcEndDate = updateUTCDate({ + localDate: localEndDate, + timezone: selectedTimezone, + allDay + }).utcDate; + } +</script> + +<div class="collapse collapse-plus bg-base-200 mb-4 rounded-lg"> + <input type="checkbox" /> + <div class="collapse-title text-xl font-semibold"> + {$t('adventures.date_information')} + </div> + <div class="collapse-content space-y-6"> + <!-- Timezone Selector --> + <div class="flex flex-col sm:flex-row sm:items-center sm:justify-between gap-4"> + <TimezoneSelector bind:selectedTimezone /> + </div> + + <span class="label-text">{$t('adventures.all_day')}</span> + <input + type="checkbox" + class="toggle toggle-primary" + id="constrain_dates" + name="constrain_dates" + bind:checked={allDay} + on:change={() => { + // clear local dates when toggling all day + if (allDay) { + localStartDate = localStartDate.split('T')[0]; + localEndDate = localEndDate.split('T')[0]; + } else { + localStartDate = localStartDate + 'T00:00'; + localEndDate = localEndDate + 'T23:59'; + } + // Update UTC dates when toggling all day + utcStartDate = updateUTCDate({ + localDate: localStartDate, + timezone: selectedTimezone, + allDay + }).utcDate; + utcEndDate = updateUTCDate({ + localDate: localEndDate, + timezone: selectedTimezone, + allDay + }).utcDate; + // Update local dates when toggling all day + localStartDate = updateLocalDate({ + utcDate: utcStartDate, + timezone: selectedTimezone + }).localDate; + localEndDate = updateLocalDate({ + utcDate: utcEndDate, + timezone: selectedTimezone + }).localDate; + }} + /> + <!-- All Day Event Checkbox --> + + <!-- Dates Input Section --> + <div class="grid grid-cols-1 md:grid-cols-2 gap-6"> + <!-- Start Date --> + <div class="flex flex-col space-y-2"> + <label for="date" class="font-medium"> + {$t('adventures.start_date')} + </label> + + {#if allDay} + <input + type="date" + id="date" + name="date" + bind:value={localStartDate} + on:change={handleLocalDateChange} + min={constrainDates ? fullStartDate : ''} + max={constrainDates ? fullEndDate : ''} + class="input input-bordered w-full" + /> + {:else} + <input + type="datetime-local" + id="date" + name="date" + bind:value={localStartDate} + on:change={handleLocalDateChange} + min={constrainDates ? fullStartDate : ''} + max={constrainDates ? fullEndDate : ''} + class="input input-bordered w-full" + /> + {/if} + + {#if collection && collection.start_date && collection.end_date} + <label class="flex items-center gap-2 mt-2"> + <input + type="checkbox" + class="toggle toggle-primary" + id="constrain_dates" + name="constrain_dates" + on:change={() => (constrainDates = !constrainDates)} + /> + <span class="label-text">{$t('adventures.date_constrain')}</span> + </label> + {/if} + </div> + + <!-- End Date --> + {#if localStartDate} + <div class="flex flex-col space-y-2"> + <label for="end_date" class="font-medium"> + {$t('adventures.end_date')} + </label> + + {#if allDay} + <input + type="date" + id="end_date" + name="end_date" + bind:value={localEndDate} + on:change={handleLocalDateChange} + min={constrainDates ? localStartDate : ''} + max={constrainDates ? fullEndDate : ''} + class="input input-bordered w-full" + /> + {:else} + <input + type="datetime-local" + id="end_date" + name="end_date" + bind:value={localEndDate} + on:change={handleLocalDateChange} + min={constrainDates ? localStartDate : ''} + max={constrainDates ? fullEndDate : ''} + class="input input-bordered w-full" + /> + {/if} + </div> + {/if} + + <!-- Notes --> + {#if type === 'adventure'} + <div class="flex gap-2 mb-1"> + <!-- textarea for notes --> + <textarea + class="textarea textarea-bordered w-full" + placeholder={$t('adventures.add_notes')} + bind:value={note} + ></textarea> + </div> + {/if} + </div> + + <!-- Validation Message --> + {#if !validateDateRange(localStartDate, localEndDate).valid} + <div role="alert" class="alert alert-error"> + <svg + xmlns="http://www.w3.org/2000/svg" + class="h-6 w-6 shrink-0 stroke-current" + fill="none" + viewBox="0 0 24 24" + > + <path + stroke-linecap="round" + stroke-linejoin="round" + stroke-width="2" + d="M10 14l2-2m0 0l2-2m-2 2l-2-2m2 2l2 2m7-2a9 9 0 11-18 0 9 9 0 0118 0z" + /> + </svg> + <span>{$t('adventures.invalid_date_range')}</span> + </div> + {/if} + + {#if visits && visits.length > 0} + <div class="space-y-4"> + {#each visits as visit} + <div + class="p-4 border border-neutral rounded-lg bg-base-100 shadow-sm flex flex-col gap-2" + > + <p class="text-sm text-base-content font-medium"> + {#if isAllDay(visit.start_date)} + <span class="badge badge-outline mr-2">All Day</span> + {visit.start_date.split('T')[0]} – {visit.end_date.split('T')[0]} + {:else} + {new Date(visit.start_date).toLocaleString()} – {new Date( + visit.end_date + ).toLocaleString()} + {/if} + </p> + + <!-- If the selected timezone is not the current one show the timezone + the time converted there --> + + {#if visit.notes} + <p class="text-sm text-base-content opacity-70 italic"> + "{visit.notes}" + </p> + {/if} + + <div class="flex gap-2 mt-2"> + <button + class="btn btn-error btn-sm" + type="button" + on:click={() => { + if (visits) { + visits = visits.filter((v) => v.id !== visit.id); + } + }} + > + {$t('adventures.remove')} + </button> + + <button + class="btn btn-primary btn-sm" + type="button" + on:click={() => { + isEditing = true; + const isAllDayEvent = isAllDay(visit.start_date); + allDay = isAllDayEvent; + + if (isAllDayEvent) { + localStartDate = visit.start_date.split('T')[0]; + localEndDate = visit.end_date.split('T')[0]; + } else { + const startDate = new Date(visit.start_date); + const endDate = new Date(visit.end_date); + + localStartDate = `${startDate.getFullYear()}-${String( + startDate.getMonth() + 1 + ).padStart(2, '0')}-${String(startDate.getDate()).padStart(2, '0')}T${String( + startDate.getHours() + ).padStart(2, '0')}:${String(startDate.getMinutes()).padStart(2, '0')}`; + + localEndDate = `${endDate.getFullYear()}-${String( + endDate.getMonth() + 1 + ).padStart(2, '0')}-${String(endDate.getDate()).padStart(2, '0')}T${String( + endDate.getHours() + ).padStart(2, '0')}:${String(endDate.getMinutes()).padStart(2, '0')}`; + } + + // remove it from visits + if (visits) { + visits = visits.filter((v) => v.id !== visit.id); + } + + note = visit.notes; + constrainDates = true; + utcStartDate = visit.start_date; + utcEndDate = visit.end_date; + type = 'adventure'; + + setTimeout(() => { + isEditing = false; + }, 0); + }} + > + {$t('lodging.edit')} + </button> + </div> + </div> + {/each} + </div> + {/if} + <div class="flex gap-2 mb-1"> + <!-- add button --> + {#if type === 'adventure'} + <button + class="btn btn-primary" + type="button" + on:click={() => { + const newVisit = { + id: crypto.randomUUID(), + start_date: utcStartDate ?? '', + end_date: utcEndDate ?? utcStartDate ?? '', + notes: note ?? '' + }; + + // Ensure reactivity by assigning a *new* array + if (visits) { + visits = [...visits, newVisit]; + } else { + visits = [newVisit]; + } + + // Optionally clear the form + note = ''; + localStartDate = ''; + localEndDate = ''; + utcStartDate = null; + utcEndDate = null; + }} + > + {$t('adventures.add')} + </button> + {/if} + </div> + </div> +</div> diff --git a/frontend/src/lib/components/DateRangeDropdown.svelte b/frontend/src/lib/components/DateRangeDropdown.svelte deleted file mode 100644 index 608d8bd..0000000 --- a/frontend/src/lib/components/DateRangeDropdown.svelte +++ /dev/null @@ -1,172 +0,0 @@ -<script lang="ts"> - import type { Collection } from '$lib/types'; - import TimezoneSelector from './TimezoneSelector.svelte'; - import { t } from 'svelte-i18n'; - export let collection: Collection | null = null; - import { updateLocalDate, updateUTCDate, validateDateRange, formatUTCDate } from '$lib/dateUtils'; - import { onMount } from 'svelte'; - - // Initialize with browser's timezone - let selectedTimezone: string = Intl.DateTimeFormat().resolvedOptions().timeZone; - - // Store the UTC dates as source of truth - export let utcStartDate: string | null = null; - export let utcEndDate: string | null = null; - - // Local display values - let localStartDate: string = ''; - let localEndDate: string = ''; - - let fullStartDate: string = ''; - let fullEndDate: string = ''; - - let constrainDates: boolean = false; - - onMount(async () => { - // Initialize UTC dates from transportationToEdit if available - localStartDate = updateLocalDate({ - utcDate: utcStartDate, - timezone: selectedTimezone - }).localDate; - localEndDate = updateLocalDate({ - utcDate: utcEndDate, - timezone: selectedTimezone - }).localDate; - }); - - if (collection && collection.start_date && collection.end_date) { - fullStartDate = `${collection.start_date}T00:00`; - fullEndDate = `${collection.end_date}T23:59`; - } - - // Update local display dates whenever timezone or UTC dates change - $: { - localStartDate = updateLocalDate({ - utcDate: utcStartDate, - timezone: selectedTimezone - }).localDate; - localEndDate = updateLocalDate({ - utcDate: utcEndDate, - timezone: selectedTimezone - }).localDate; - } - - // Update UTC dates when local dates change - function handleLocalDateChange() { - utcStartDate = updateUTCDate({ - localDate: localStartDate, - timezone: selectedTimezone - }).utcDate; - utcEndDate = updateUTCDate({ - localDate: localEndDate, - timezone: selectedTimezone - }).utcDate; - } -</script> - -<div class="collapse collapse-plus bg-base-200 mb-4 rounded-lg"> - <input type="checkbox" checked /> - <div class="collapse-title text-xl font-semibold"> - {$t('adventures.date_information')} - </div> - <div class="collapse-content space-y-6"> - <!-- Timezone Selector --> - <div class="flex flex-col sm:flex-row sm:items-center sm:justify-between gap-4"> - <TimezoneSelector bind:selectedTimezone /> - </div> - - <!-- Dates Input Section --> - <div class="grid grid-cols-1 md:grid-cols-2 gap-6"> - <!-- Start Date --> - <div class="flex flex-col space-y-2"> - <label for="date" class="font-medium"> - {$t('adventures.start_date')} - </label> - - <input - type="datetime-local" - id="date" - name="date" - bind:value={localStartDate} - on:change={handleLocalDateChange} - min={constrainDates ? fullStartDate : ''} - max={constrainDates ? fullEndDate : ''} - class="input input-bordered w-full" - /> - - {#if collection && collection.start_date && collection.end_date} - <label class="flex items-center gap-2 mt-2"> - <input - type="checkbox" - class="toggle toggle-primary" - id="constrain_dates" - name="constrain_dates" - on:change={() => (constrainDates = !constrainDates)} - /> - <span class="label-text">{$t('adventures.date_constrain')}</span> - </label> - {/if} - </div> - - <!-- End Date --> - {#if localStartDate} - <div class="flex flex-col space-y-2"> - <label for="end_date" class="font-medium"> - {$t('adventures.end_date')} - </label> - - <input - type="datetime-local" - id="end_date" - name="end_date" - bind:value={localEndDate} - on:change={handleLocalDateChange} - min={constrainDates ? localStartDate : ''} - max={constrainDates ? fullEndDate : ''} - class="input input-bordered w-full" - /> - </div> - {/if} - </div> - - <!-- Validation Message --> - {#if !validateDateRange(localStartDate, localEndDate).valid} - <div role="alert" class="alert alert-error"> - <svg - xmlns="http://www.w3.org/2000/svg" - class="h-6 w-6 shrink-0 stroke-current" - fill="none" - viewBox="0 0 24 24" - > - <path - stroke-linecap="round" - stroke-linejoin="round" - stroke-width="2" - d="M10 14l2-2m0 0l2-2m-2 2l-2-2m2 2l2 2m7-2a9 9 0 11-18 0 9 9 0 0118 0z" - /> - </svg> - <span>{$t('adventures.invalid_date_range')}</span> - </div> - {/if} - - <!-- - <div role="alert" class="alert shadow-lg bg-neutral text-neutral-content mt-6"> - <svg - xmlns="http://www.w3.org/2000/svg" - fill="none" - viewBox="0 0 24 24" - class="stroke-info h-6 w-6 shrink-0" - > - <path - stroke-linecap="round" - stroke-linejoin="round" - stroke-width="2" - d="M13 16h-1v-4h-1m1-4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z" - ></path> - </svg> - <span class="ml-2"> - {$t('lodging.current_timezone')}: {selectedTimezone} - </span> - </div> --> - </div> -</div> diff --git a/frontend/src/lib/components/LodgingModal.svelte b/frontend/src/lib/components/LodgingModal.svelte index 9a2975e..cbe05c8 100644 --- a/frontend/src/lib/components/LodgingModal.svelte +++ b/frontend/src/lib/components/LodgingModal.svelte @@ -5,6 +5,7 @@ import MarkdownEditor from './MarkdownEditor.svelte'; import type { Collection, Lodging } from '$lib/types'; import LocationDropdown from './LocationDropdown.svelte'; + import DateRangeCollapse from './DateRangeCollapse.svelte'; const dispatch = createEventDispatcher(); @@ -12,22 +13,10 @@ export let lodgingToEdit: Lodging | null = null; let modal: HTMLDialogElement; - let constrainDates: boolean = false; let lodging: Lodging = { ...initializeLodging(lodgingToEdit) }; let fullStartDate: string = ''; let fullEndDate: string = ''; - // Format date as local datetime - // Convert an ISO date to a datetime-local value in local time. - function toLocalDatetime(value: string | null): string { - if (!value) return ''; - const date = new Date(value); - // Adjust the time by subtracting the timezone offset. - date.setMinutes(date.getMinutes() - date.getTimezoneOffset()); - // Return format YYYY-MM-DDTHH:mm - return date.toISOString().slice(0, 16); - } - type LodgingType = { value: string; label: string; @@ -47,27 +36,27 @@ { value: 'other', label: 'Other' } ]; - // Initialize hotel with values from hotelToEdit or default values - function initializeLodging(hotelToEdit: Lodging | null): Lodging { + // Initialize hotel with values from lodgingToEdit or default values + function initializeLodging(lodgingToEdit: Lodging | null): Lodging { return { - id: hotelToEdit?.id || '', - user_id: hotelToEdit?.user_id || '', - name: hotelToEdit?.name || '', - type: hotelToEdit?.type || 'other', - description: hotelToEdit?.description || '', - rating: hotelToEdit?.rating || NaN, - link: hotelToEdit?.link || '', - check_in: hotelToEdit?.check_in ? toLocalDatetime(hotelToEdit.check_in) : null, - check_out: hotelToEdit?.check_out ? toLocalDatetime(hotelToEdit.check_out) : null, - reservation_number: hotelToEdit?.reservation_number || '', - price: hotelToEdit?.price || null, - latitude: hotelToEdit?.latitude || null, - longitude: hotelToEdit?.longitude || null, - location: hotelToEdit?.location || '', - is_public: hotelToEdit?.is_public || false, - collection: hotelToEdit?.collection || collection.id, - created_at: hotelToEdit?.created_at || '', - updated_at: hotelToEdit?.updated_at || '' + id: lodgingToEdit?.id || '', + user_id: lodgingToEdit?.user_id || '', + name: lodgingToEdit?.name || '', + type: lodgingToEdit?.type || 'other', + description: lodgingToEdit?.description || '', + rating: lodgingToEdit?.rating || NaN, + link: lodgingToEdit?.link || '', + check_in: lodgingToEdit?.check_in || null, + check_out: lodgingToEdit?.check_out || null, + reservation_number: lodgingToEdit?.reservation_number || '', + price: lodgingToEdit?.price || null, + latitude: lodgingToEdit?.latitude || null, + longitude: lodgingToEdit?.longitude || null, + location: lodgingToEdit?.location || '', + is_public: lodgingToEdit?.is_public || false, + collection: lodgingToEdit?.collection || collection.id, + created_at: lodgingToEdit?.created_at || '', + updated_at: lodgingToEdit?.updated_at || '' }; } @@ -104,27 +93,6 @@ async function handleSubmit(event: Event) { event.preventDefault(); - if (lodging.check_in && !lodging.check_out) { - const checkInDate = new Date(lodging.check_in); - checkInDate.setDate(checkInDate.getDate() + 1); - lodging.check_out = checkInDate.toISOString(); - } - - if (lodging.check_in && lodging.check_out && lodging.check_in > lodging.check_out) { - addToast('error', $t('adventures.start_before_end_error')); - return; - } - - // Only convert to UTC if the time is still in local format. - if (lodging.check_in && !lodging.check_in.includes('Z')) { - // new Date(lodging.check_in) interprets the input as local time. - lodging.check_in = new Date(lodging.check_in).toISOString(); - } - if (lodging.check_out && !lodging.check_out.includes('Z')) { - lodging.check_out = new Date(lodging.check_out).toISOString(); - } - console.log(lodging.check_in, lodging.check_out); - // Create or update lodging... const url = lodging.id === '' ? '/api/lodging' : `/api/lodging/${lodging.id}`; const method = lodging.id === '' ? 'POST' : 'PATCH'; @@ -331,85 +299,11 @@ </div> </div> - <div class="collapse collapse-plus bg-base-200 mb-4"> - <input type="checkbox" checked /> - <div class="collapse-title text-xl font-medium"> - {$t('adventures.date_information')} - </div> - <div class="collapse-content"> - <!-- Check In --> - <div> - <label for="date"> - {$t('lodging.check_in')} - </label> - - {#if collection && collection.start_date && collection.end_date}<label - class="label cursor-pointer flex items-start space-x-2" - > - <span class="label-text">{$t('adventures.date_constrain')}</span> - <input - type="checkbox" - class="toggle toggle-primary" - id="constrain_dates" - name="constrain_dates" - on:change={() => (constrainDates = !constrainDates)} - /></label - > - {/if} - <div> - <input - type="datetime-local" - id="date" - name="date" - bind:value={lodging.check_in} - min={constrainDates ? fullStartDate : ''} - max={constrainDates ? fullEndDate : ''} - class="input input-bordered w-full max-w-xs mt-1" - /> - </div> - </div> - <!-- End Date --> - <div> - <label for="end_date"> - {$t('lodging.check_out')} - </label> - <div> - <input - type="datetime-local" - id="end_date" - name="end_date" - min={constrainDates ? lodging.check_in : ''} - max={constrainDates ? fullEndDate : ''} - bind:value={lodging.check_out} - class="input input-bordered w-full max-w-xs mt-1" - /> - </div> - </div> - <div role="alert" class="alert shadow-lg bg-neutral mt-4"> - <svg - xmlns="http://www.w3.org/2000/svg" - fill="none" - viewBox="0 0 24 24" - class="stroke-info h-6 w-6 shrink-0" - > - <path - stroke-linecap="round" - stroke-linejoin="round" - stroke-width="2" - d="M13 16h-1v-4h-1m1-4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z" - ></path> - </svg> - <span> - {$t('lodging.current_timezone')}: - {(() => { - const tz = new Intl.DateTimeFormat().resolvedOptions().timeZone; - const [continent, city] = tz.split('/'); - return `${continent} (${city.replace('_', ' ')})`; - })()} - </span> - </div> - </div> - </div> + <DateRangeCollapse + type="lodging" + bind:utcStartDate={lodging.check_in} + bind:utcEndDate={lodging.check_out} + /> <!-- Location Information --> <LocationDropdown bind:item={lodging} /> diff --git a/frontend/src/lib/components/TimezoneSelector.svelte b/frontend/src/lib/components/TimezoneSelector.svelte index c0738b8..fe3bf36 100644 --- a/frontend/src/lib/components/TimezoneSelector.svelte +++ b/frontend/src/lib/components/TimezoneSelector.svelte @@ -6,6 +6,7 @@ let dropdownOpen = false; let searchQuery = ''; + let searchInput: HTMLInputElement; const timezones = Intl.supportedValuesOf('timeZone'); // Filter timezones based on search query @@ -19,6 +20,23 @@ searchQuery = ''; } + // Focus search input when dropdown opens + $: if (dropdownOpen && searchInput) { + // Use setTimeout to delay focus until after the element is rendered + setTimeout(() => searchInput.focus(), 0); + } + + function handleKeydown(event: KeyboardEvent, tz?: string) { + if (event.key === 'Enter' || event.key === ' ') { + event.preventDefault(); + if (tz) selectTimezone(tz); + else dropdownOpen = !dropdownOpen; + } else if (event.key === 'Escape') { + event.preventDefault(); + dropdownOpen = false; + } + } + // Close dropdown if clicked outside onMount(() => { const handleClickOutside = (e: MouseEvent) => { @@ -31,16 +49,20 @@ </script> <div class="form-control w-full max-w-xs relative" id="tz-selector"> - <label class="label"> + <label class="label" for="timezone-display"> <span class="label-text">Timezone</span> </label> <!-- Trigger --> <div + id="timezone-display" tabindex="0" role="button" + aria-haspopup="listbox" + aria-expanded={dropdownOpen} class="input input-bordered flex justify-between items-center cursor-pointer" on:click={() => (dropdownOpen = !dropdownOpen)} + on:keydown={handleKeydown} > <span class="truncate">{selectedTimezone}</span> <svg @@ -49,6 +71,7 @@ fill="none" viewBox="0 0 24 24" stroke="currentColor" + aria-hidden="true" > <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 9l-7 7-7-7" /> </svg> @@ -58,6 +81,8 @@ {#if dropdownOpen} <div class="absolute mt-1 z-10 bg-base-100 shadow-lg rounded-box w-full max-h-60 overflow-y-auto" + role="listbox" + aria-labelledby="timezone-display" > <!-- Search --> <div class="sticky top-0 bg-base-100 p-2 border-b"> @@ -66,7 +91,7 @@ placeholder="Search timezone" class="input input-sm input-bordered w-full" bind:value={searchQuery} - autofocus + bind:this={searchInput} /> </div> @@ -75,12 +100,16 @@ <ul class="menu p-2 space-y-1"> {#each filteredTimezones as tz} <li> - <a - class={`truncate ${tz === selectedTimezone ? 'active font-bold' : ''}`} - on:click|preventDefault={() => selectTimezone(tz)} + <button + type="button" + class={`w-full text-left truncate ${tz === selectedTimezone ? 'active font-bold' : ''}`} + on:click={() => selectTimezone(tz)} + on:keydown={(e) => handleKeydown(e, tz)} + role="option" + aria-selected={tz === selectedTimezone} > {tz} - </a> + </button> </li> {/each} </ul> diff --git a/frontend/src/lib/components/TransportationModal.svelte b/frontend/src/lib/components/TransportationModal.svelte index 342dbd5..cbf4920 100644 --- a/frontend/src/lib/components/TransportationModal.svelte +++ b/frontend/src/lib/components/TransportationModal.svelte @@ -6,37 +6,23 @@ import { addToast } from '$lib/toasts'; let modal: HTMLDialogElement; import { t } from 'svelte-i18n'; - import { updateLocalDate, updateUTCDate, validateDateRange, formatUTCDate } from '$lib/dateUtils'; - - // Initialize with browser's timezone - let selectedTimezone: string = Intl.DateTimeFormat().resolvedOptions().timeZone; - - // Store the UTC dates as source of truth - let utcStartDate: string | null = null; - let utcEndDate: string | null = null; - - // Local display values - let localStartDate: string = ''; - let localEndDate: string = ''; import MarkdownEditor from './MarkdownEditor.svelte'; import { appVersion } from '$lib/config'; import { DefaultMarker, MapLibre } from 'svelte-maplibre'; - import TimezoneSelector from './TimezoneSelector.svelte'; + import DateRangeCollapse from './DateRangeCollapse.svelte'; export let collection: Collection; export let transportationToEdit: Transportation | null = null; - let constrainDates: boolean = false; - // Initialize transportation object let transportation: Transportation = { id: transportationToEdit?.id || '', type: transportationToEdit?.type || '', name: transportationToEdit?.name || '', description: transportationToEdit?.description || '', - date: null, - end_date: null, + date: transportationToEdit?.date || null, + end_date: transportationToEdit?.end_date || null, rating: transportationToEdit?.rating || 0, link: transportationToEdit?.link || '', flight_number: transportationToEdit?.flight_number || '', @@ -53,58 +39,20 @@ destination_longitude: transportationToEdit?.destination_longitude || NaN }; - let fullStartDate: string = ''; - let fullEndDate: string = ''; - let starting_airport: string = ''; let ending_airport: string = ''; - if (collection.start_date && collection.end_date) { - fullStartDate = `${collection.start_date}T00:00`; - fullEndDate = `${collection.end_date}T23:59`; - } - $: { if (!transportation.rating) { transportation.rating = NaN; } } - // Update local display dates whenever timezone or UTC dates change - $: { - localStartDate = updateLocalDate({ - utcDate: utcStartDate, - timezone: selectedTimezone - }).localDate; - localEndDate = updateLocalDate({ - utcDate: utcEndDate, - timezone: selectedTimezone - }).localDate; - } - onMount(async () => { modal = document.getElementById('my_modal_1') as HTMLDialogElement; if (modal) { modal.showModal(); } - - // Initialize UTC dates from transportationToEdit if available - if (transportationToEdit?.date) { - utcStartDate = transportationToEdit.date; - } - - if (transportationToEdit?.end_date) { - utcEndDate = transportationToEdit.end_date; - } - - localStartDate = updateLocalDate({ - utcDate: utcStartDate, - timezone: selectedTimezone - }).localDate; - localEndDate = updateLocalDate({ - utcDate: utcEndDate, - timezone: selectedTimezone - }).localDate; }); function close() { @@ -117,18 +65,6 @@ } } - // Update UTC dates when local dates change - function handleLocalDateChange() { - utcStartDate = updateUTCDate({ - localDate: localStartDate, - timezone: selectedTimezone - }).utcDate; - utcEndDate = updateUTCDate({ - localDate: localEndDate, - timezone: selectedTimezone - }).utcDate; - } - async function geocode(e: Event | null) { // Geocoding logic unchanged if (e) { @@ -214,30 +150,9 @@ Math.round(transportation.destination_longitude * 1e6) / 1e6; } - // Validate dates using utility function - if (localEndDate && !localStartDate) { - addToast('error', $t('adventures.start_date_required')); - return; - } - - if (localStartDate && !localEndDate) { - // If only start date is provided, set end date to the same value - localEndDate = localStartDate; - utcEndDate = utcStartDate; - } - - // Validate date range - const validation = validateDateRange(localStartDate, localEndDate); - if (!validation.valid) { - addToast('error', $t('adventures.start_before_end_error')); - return; - } - // Use the stored UTC dates for submission const submissionData = { - ...transportation, - date: utcStartDate, - end_date: utcEndDate + ...transportation }; if (transportation.type != 'plane') { @@ -255,18 +170,6 @@ let data = await res.json(); if (data.id) { transportation = data as Transportation; - // Update the UTC dates with the values from the server - utcStartDate = data.date; - utcEndDate = data.end_date; - - localStartDate = updateLocalDate({ - utcDate: utcStartDate, - timezone: selectedTimezone - }).localDate; - localEndDate = updateLocalDate({ - utcDate: utcEndDate, - timezone: selectedTimezone - }).localDate; addToast('success', $t('adventures.adventure_created')); dispatch('save', transportation); @@ -285,18 +188,6 @@ let data = await res.json(); if (data.id) { transportation = data as Transportation; - // Update the UTC dates with the values from the server - utcStartDate = data.date; - utcEndDate = data.end_date; - - localStartDate = updateLocalDate({ - utcDate: utcStartDate, - timezone: selectedTimezone - }).localDate; - localEndDate = updateLocalDate({ - utcDate: utcEndDate, - timezone: selectedTimezone - }).localDate; addToast('success', $t('adventures.adventure_updated')); dispatch('save', transportation); @@ -447,96 +338,14 @@ </div> </div> </div> - <div class="collapse collapse-plus bg-base-200 mb-4"> - <input type="checkbox" checked /> - <div class="collapse-title text-xl font-medium"> - {$t('adventures.date_information')} - </div> - <div class="collapse-content"> - <TimezoneSelector bind:selectedTimezone /> - <!-- Start Date --> - <div> - <label for="date"> - {$t('adventures.start_date')} - </label> - {#if collection && collection.start_date && collection.end_date}<label - class="label cursor-pointer flex items-start space-x-2" - > - <span class="label-text">{$t('adventures.date_constrain')}</span> - <input - type="checkbox" - class="toggle toggle-primary" - id="constrain_dates" - name="constrain_dates" - on:change={() => (constrainDates = !constrainDates)} - /></label - > - {/if} - <div> - <input - type="datetime-local" - id="date" - name="date" - bind:value={localStartDate} - on:change={handleLocalDateChange} - min={constrainDates ? fullStartDate : ''} - max={constrainDates ? fullEndDate : ''} - class="input input-bordered w-full max-w-xs mt-1" - /> - </div> - </div> - <!-- End Date --> - {#if localStartDate} - <div> - <label for="end_date"> - {$t('adventures.end_date')} - </label> - <div> - <input - type="datetime-local" - id="end_date" - name="end_date" - min={constrainDates ? localStartDate : ''} - max={constrainDates ? fullEndDate : ''} - bind:value={localEndDate} - on:change={handleLocalDateChange} - class="input input-bordered w-full max-w-xs mt-1" - /> - </div> - </div> - {/if} - <div role="alert" class="alert shadow-lg bg-neutral mt-4"> - <svg - xmlns="http://www.w3.org/2000/svg" - fill="none" - viewBox="0 0 24 24" - class="stroke-info h-6 w-6 shrink-0" - > - <path - stroke-linecap="round" - stroke-linejoin="round" - stroke-width="2" - d="M13 16h-1v-4h-1m1-4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z" - ></path> - </svg> - <span> - {$t('lodging.current_timezone')}: - {selectedTimezone} - </span> - </div> - {#if utcStartDate} - <div class="text-sm mt-2"> - UTC Time: {formatUTCDate(utcStartDate)} - {#if utcEndDate && utcEndDate !== utcStartDate} - to {formatUTCDate(utcEndDate)} - {/if} - </div> - {/if} - </div> - </div> + <DateRangeCollapse + type="transportation" + bind:utcStartDate={transportation.date} + bind:utcEndDate={transportation.end_date} + /> + <!-- Flight Information --> - <div class="collapse collapse-plus bg-base-200 mb-4"> <input type="checkbox" checked /> <div class="collapse-title text-xl font-medium"> diff --git a/frontend/src/lib/dateUtils.ts b/frontend/src/lib/dateUtils.ts index d47f119..3a4a18c 100644 --- a/frontend/src/lib/dateUtils.ts +++ b/frontend/src/lib/dateUtils.ts @@ -26,10 +26,22 @@ export function toLocalDatetime( */ export function toUTCDatetime( localDate: string, - timezone: string = Intl.DateTimeFormat().resolvedOptions().timeZone + timezone: string = Intl.DateTimeFormat().resolvedOptions().timeZone, + allDay: boolean = false ): string | null { if (!localDate) return null; - return DateTime.fromISO(localDate, { zone: timezone }).toUTC().toISO(); + + 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 }); } /** @@ -54,9 +66,17 @@ export function updateLocalDate({ * @param params Object containing local date and timezone * @returns Object with updated UTC datetime string */ -export function updateUTCDate({ localDate, timezone }: { localDate: string; timezone: string }) { +export function updateUTCDate({ + localDate, + timezone, + allDay = false +}: { + localDate: string; + timezone: string; + allDay?: boolean; +}) { return { - utcDate: toUTCDatetime(localDate, timezone) + utcDate: toUTCDatetime(localDate, timezone, allDay) }; } diff --git a/frontend/src/routes/datetest/+page.svelte b/frontend/src/routes/datetest/+page.svelte deleted file mode 100644 index b06ba1f..0000000 --- a/frontend/src/routes/datetest/+page.svelte +++ /dev/null @@ -1,13 +0,0 @@ -<script> - import DateRangeDropdown from '$lib/components/DateRangeDropdown.svelte'; - - let utcStartDate = ''; - let utcEndDate = ''; -</script> - -<DateRangeDropdown bind:utcStartDate bind:utcEndDate /> - -<p>{new Date(utcStartDate).toLocaleString()} - {new Date(utcEndDate).toLocaleString()}</p> - -<p>UTC Start Date: {utcStartDate}</p> -<p>UTC End Date: {utcEndDate}</p> From 311e2847cbfa7c758bf929c97d0a7692877cb369 Mon Sep 17 00:00:00 2001 From: Sean Morley <mail@seanmorley.com> Date: Fri, 9 May 2025 15:19:17 -0400 Subject: [PATCH 63/79] Enhance visit display: Improve layout and formatting of visit dates and notes --- .../src/routes/adventures/[id]/+page.svelte | 40 ++++++++++++------- 1 file changed, 25 insertions(+), 15 deletions(-) diff --git a/frontend/src/routes/adventures/[id]/+page.svelte b/frontend/src/routes/adventures/[id]/+page.svelte index f11f450..418b33b 100644 --- a/frontend/src/routes/adventures/[id]/+page.svelte +++ b/frontend/src/routes/adventures/[id]/+page.svelte @@ -498,22 +498,32 @@ {adventure.category?.display_name + ' ' + adventure.category?.icon} </p> {#if adventure.visits.length > 0} - <p class="text-black text-sm"> + <p> {#each adventure.visits as visit} - {visit.start_date - ? new Date(visit.start_date).toLocaleDateString(undefined, { - timeZone: 'UTC' - }) - : ''} - {visit.end_date && - visit.end_date !== '' && - visit.end_date !== visit.start_date - ? ' - ' + - new Date(visit.end_date).toLocaleDateString(undefined, { - timeZone: 'UTC' - }) - : ''} - <br /> + <div + class="p-4 border border-neutral rounded-lg bg-base-100 shadow-sm flex flex-col gap-2" + > + <p class="text-sm text-base-content font-medium"> + {#if isAllDay(visit.start_date)} + <span class="badge badge-outline mr-2">All Day</span> + {visit.start_date.split('T')[0]} – {visit.end_date.split( + 'T' + )[0]} + {:else} + {new Date(visit.start_date).toLocaleString()} – {new Date( + visit.end_date + ).toLocaleString()} + {/if} + </p> + + <!-- If the selected timezone is not the current one show the timezone + the time converted there --> + + {#if visit.notes} + <p class="text-sm text-base-content opacity-70 italic"> + "{visit.notes}" + </p> + {/if} + </div> {/each} </p> {/if} From 3caebd37ddefc4420ce982937dac6c26105c0488 Mon Sep 17 00:00:00 2001 From: Sean Morley <mail@seanmorley.com> Date: Fri, 9 May 2025 15:59:48 -0400 Subject: [PATCH 64/79] Add additional localization strings for itinerary features in Polish, Swedish, and Chinese - Added "additional_info", "invalid_date_range", "sunrise_sunset", and "timezone" keys to pl.json, sv.json, and zh.json. - Updated existing strings for consistency across languages. --- .../lib/components/DateRangeCollapse.svelte | 153 +- .../lib/components/TimezoneSelector.svelte | 2 +- frontend/src/locales/de.json | 6 +- frontend/src/locales/en.json | 1 + frontend/src/locales/es.json | 6 +- frontend/src/locales/fr.json | 6 +- frontend/src/locales/it.json | 8 +- frontend/src/locales/ko.json | 6 +- frontend/src/locales/nl.json | 6 +- frontend/src/locales/no.json | 1266 +++++++++-------- frontend/src/locales/pl.json | 6 +- frontend/src/locales/sv.json | 6 +- frontend/src/locales/zh.json | 6 +- 13 files changed, 771 insertions(+), 707 deletions(-) diff --git a/frontend/src/lib/components/DateRangeCollapse.svelte b/frontend/src/lib/components/DateRangeCollapse.svelte index d8fc699..5a4198a 100644 --- a/frontend/src/lib/components/DateRangeCollapse.svelte +++ b/frontend/src/lib/components/DateRangeCollapse.svelte @@ -56,11 +56,20 @@ }).localDate; }); - if (collection && collection.start_date && collection.end_date) { + // Set the full date range for constraining purposes + $: if (collection && collection.start_date && collection.end_date) { fullStartDate = `${collection.start_date}T00:00`; fullEndDate = `${collection.end_date}T23:59`; } + // Get constraint dates in the right format based on allDay setting + $: constraintStartDate = allDay + ? fullStartDate + ? fullStartDate.split('T')[0] + : '' + : fullStartDate; + $: constraintEndDate = allDay ? (fullEndDate ? fullEndDate.split('T')[0] : '') : fullEndDate; + // Update local display dates whenever timezone or UTC dates change $: if (!isEditing) { if (allDay) { @@ -109,51 +118,69 @@ <TimezoneSelector bind:selectedTimezone /> </div> - <span class="label-text">{$t('adventures.all_day')}</span> - <input - type="checkbox" - class="toggle toggle-primary" - id="constrain_dates" - name="constrain_dates" - bind:checked={allDay} - on:change={() => { - // clear local dates when toggling all day - if (allDay) { - localStartDate = localStartDate.split('T')[0]; - localEndDate = localEndDate.split('T')[0]; - } else { - localStartDate = localStartDate + 'T00:00'; - localEndDate = localEndDate + 'T23:59'; - } - // Update UTC dates when toggling all day - utcStartDate = updateUTCDate({ - localDate: localStartDate, - timezone: selectedTimezone, - allDay - }).utcDate; - utcEndDate = updateUTCDate({ - localDate: localEndDate, - timezone: selectedTimezone, - allDay - }).utcDate; - // Update local dates when toggling all day - localStartDate = updateLocalDate({ - utcDate: utcStartDate, - timezone: selectedTimezone - }).localDate; - localEndDate = updateLocalDate({ - utcDate: utcEndDate, - timezone: selectedTimezone - }).localDate; - }} - /> - <!-- All Day Event Checkbox --> + <div class="rounded-xl border border-base-300 bg-base-100 p-4 space-y-4 shadow-sm"> + <!-- Group Header --> + <h3 class="text-md font-semibold">{$t('navbar.settings')}</h3> + + <!-- All Day Toggle --> + <div class="flex justify-between items-center"> + <span class="text-sm">{$t('adventures.all_day')}</span> + <input + type="checkbox" + class="toggle toggle-primary" + id="all_day" + name="all_day" + bind:checked={allDay} + on:change={() => { + if (allDay) { + localStartDate = localStartDate.split('T')[0]; + localEndDate = localEndDate.split('T')[0]; + } else { + localStartDate = localStartDate + 'T00:00'; + localEndDate = localEndDate + 'T23:59'; + } + utcStartDate = updateUTCDate({ + localDate: localStartDate, + timezone: selectedTimezone, + allDay + }).utcDate; + utcEndDate = updateUTCDate({ + localDate: localEndDate, + timezone: selectedTimezone, + allDay + }).utcDate; + localStartDate = updateLocalDate({ + utcDate: utcStartDate, + timezone: selectedTimezone + }).localDate; + localEndDate = updateLocalDate({ + utcDate: utcEndDate, + timezone: selectedTimezone + }).localDate; + }} + /> + </div> + + <!-- Constrain Dates Toggle --> + {#if collection?.start_date && collection?.end_date} + <div class="flex justify-between items-center"> + <span class="text-sm">{$t('adventures.date_constrain')}</span> + <input + type="checkbox" + id="constrain_dates" + name="constrain_dates" + class="toggle toggle-primary" + on:change={() => (constrainDates = !constrainDates)} + /> + </div> + {/if} + </div> <!-- Dates Input Section --> <div class="grid grid-cols-1 md:grid-cols-2 gap-6"> <!-- Start Date --> - <div class="flex flex-col space-y-2"> - <label for="date" class="font-medium"> + <div class="space-y-2"> + <label for="date" class="text-sm font-medium"> {$t('adventures.start_date')} </label> @@ -164,8 +191,8 @@ name="date" bind:value={localStartDate} on:change={handleLocalDateChange} - min={constrainDates ? fullStartDate : ''} - max={constrainDates ? fullEndDate : ''} + min={constrainDates ? constraintStartDate : ''} + max={constrainDates ? constraintEndDate : ''} class="input input-bordered w-full" /> {:else} @@ -175,30 +202,17 @@ name="date" bind:value={localStartDate} on:change={handleLocalDateChange} - min={constrainDates ? fullStartDate : ''} - max={constrainDates ? fullEndDate : ''} + min={constrainDates ? constraintStartDate : ''} + max={constrainDates ? constraintEndDate : ''} class="input input-bordered w-full" /> {/if} - - {#if collection && collection.start_date && collection.end_date} - <label class="flex items-center gap-2 mt-2"> - <input - type="checkbox" - class="toggle toggle-primary" - id="constrain_dates" - name="constrain_dates" - on:change={() => (constrainDates = !constrainDates)} - /> - <span class="label-text">{$t('adventures.date_constrain')}</span> - </label> - {/if} </div> <!-- End Date --> {#if localStartDate} - <div class="flex flex-col space-y-2"> - <label for="end_date" class="font-medium"> + <div class="space-y-2"> + <label for="end_date" class="text-sm font-medium"> {$t('adventures.end_date')} </label> @@ -210,7 +224,7 @@ bind:value={localEndDate} on:change={handleLocalDateChange} min={constrainDates ? localStartDate : ''} - max={constrainDates ? fullEndDate : ''} + max={constrainDates ? constraintEndDate : ''} class="input input-bordered w-full" /> {:else} @@ -221,21 +235,26 @@ bind:value={localEndDate} on:change={handleLocalDateChange} min={constrainDates ? localStartDate : ''} - max={constrainDates ? fullEndDate : ''} + max={constrainDates ? constraintEndDate : ''} class="input input-bordered w-full" /> {/if} </div> {/if} - <!-- Notes --> + <!-- Notes (for adventures only) --> {#if type === 'adventure'} - <div class="flex gap-2 mb-1"> - <!-- textarea for notes --> + <div class="md:col-span-2"> + <label for="note" class="text-sm font-medium block mb-1"> + {$t('adventures.add_notes')} + </label> <textarea + id="note" + name="note" class="textarea textarea-bordered w-full" placeholder={$t('adventures.add_notes')} bind:value={note} + rows="4" ></textarea> </div> {/if} @@ -269,7 +288,7 @@ > <p class="text-sm text-base-content font-medium"> {#if isAllDay(visit.start_date)} - <span class="badge badge-outline mr-2">All Day</span> + <span class="badge badge-outline mr-2">{$t('adventures.all_day')}</span> {visit.start_date.split('T')[0]} – {visit.end_date.split('T')[0]} {:else} {new Date(visit.start_date).toLocaleString()} – {new Date( diff --git a/frontend/src/lib/components/TimezoneSelector.svelte b/frontend/src/lib/components/TimezoneSelector.svelte index fe3bf36..4e40d61 100644 --- a/frontend/src/lib/components/TimezoneSelector.svelte +++ b/frontend/src/lib/components/TimezoneSelector.svelte @@ -50,7 +50,7 @@ <div class="form-control w-full max-w-xs relative" id="tz-selector"> <label class="label" for="timezone-display"> - <span class="label-text">Timezone</span> + <span class="label-text">{$t('adventures.timezone')}</span> </label> <!-- Trigger --> diff --git a/frontend/src/locales/de.json b/frontend/src/locales/de.json index 13a02ad..18488ae 100644 --- a/frontend/src/locales/de.json +++ b/frontend/src/locales/de.json @@ -252,7 +252,11 @@ "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" + "ordered_itinerary": "Reiseroute bestellt", + "additional_info": "Weitere Informationen", + "invalid_date_range": "Ungültiger Datumsbereich", + "sunrise_sunset": "Sonnenaufgang", + "timezone": "Zeitzone" }, "home": { "desc_1": "Entdecken, planen und erkunden Sie mühelos", diff --git a/frontend/src/locales/en.json b/frontend/src/locales/en.json index 20e2e90..9ab91c0 100644 --- a/frontend/src/locales/en.json +++ b/frontend/src/locales/en.json @@ -63,6 +63,7 @@ "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_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.", diff --git a/frontend/src/locales/es.json b/frontend/src/locales/es.json index c82df3f..6070d2d 100644 --- a/frontend/src/locales/es.json +++ b/frontend/src/locales/es.json @@ -300,7 +300,11 @@ "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" + "ordered_itinerary": "Itinerario ordenado", + "additional_info": "información adicional", + "invalid_date_range": "Rango de fechas no válido", + "sunrise_sunset": "Amanecer", + "timezone": "Zona horaria" }, "worldtravel": { "all": "Todo", diff --git a/frontend/src/locales/fr.json b/frontend/src/locales/fr.json index 86703fc..2f9b4ad 100644 --- a/frontend/src/locales/fr.json +++ b/frontend/src/locales/fr.json @@ -252,7 +252,11 @@ "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é" + "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" }, "home": { "desc_1": "Découvrez, planifiez et explorez en toute simplicité", diff --git a/frontend/src/locales/it.json b/frontend/src/locales/it.json index 1a77839..ecaef2d 100644 --- a/frontend/src/locales/it.json +++ b/frontend/src/locales/it.json @@ -252,7 +252,11 @@ "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" + "ordered_itinerary": "Itinerario ordinato", + "additional_info": "Ulteriori informazioni", + "invalid_date_range": "Intervallo di date non valido", + "sunrise_sunset": "Alba", + "timezone": "Fuso orario" }, "home": { "desc_1": "Scopri, pianifica ed esplora con facilità", @@ -632,4 +636,4 @@ "motel": "Motel", "current_timezone": "Fuso orario attuale" } -} \ No newline at end of file +} diff --git a/frontend/src/locales/ko.json b/frontend/src/locales/ko.json index dda6962..fd47b52 100644 --- a/frontend/src/locales/ko.json +++ b/frontend/src/locales/ko.json @@ -252,7 +252,11 @@ "collection_no_start_end_date": "컬렉션에 시작 및 종료 날짜를 추가하면 컬렉션 페이지에서 여정 계획 기능이 잠금 해제됩니다.", "date_itinerary": "날짜 일정", "no_ordered_items": "컬렉션에 날짜가있는 항목을 추가하여 여기에서 확인하십시오.", - "ordered_itinerary": "주문한 여정" + "ordered_itinerary": "주문한 여정", + "additional_info": "추가 정보", + "invalid_date_range": "잘못된 날짜 범위", + "sunrise_sunset": "해돋이", + "timezone": "시간대" }, "auth": { "both_passwords_required": "두 암호 모두 필요합니다", diff --git a/frontend/src/locales/nl.json b/frontend/src/locales/nl.json index 7cb51dc..08f013d 100644 --- a/frontend/src/locales/nl.json +++ b/frontend/src/locales/nl.json @@ -252,7 +252,11 @@ "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" + "ordered_itinerary": "Besteld reisschema", + "additional_info": "Aanvullende informatie", + "invalid_date_range": "Ongeldige datumbereik", + "sunrise_sunset": "Zonsopgang", + "timezone": "Tijdzone" }, "home": { "desc_1": "Ontdek, plan en verken met gemak", diff --git a/frontend/src/locales/no.json b/frontend/src/locales/no.json index 8b49218..9aeb95e 100644 --- a/frontend/src/locales/no.json +++ b/frontend/src/locales/no.json @@ -1,631 +1,639 @@ { - "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" - }, - "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" - }, - "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." - }, - "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" - }, - "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" - }, - "recomendations": { - "address": "Adresse", - "phone": "Telefon", - "contact": "Kontakt", - "website": "Nettsted", - "recommendation": "Anbefaling" - } + "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" + }, + "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" + }, + "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." + }, + "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" + }, + "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" + }, + "recomendations": { + "address": "Adresse", + "phone": "Telefon", + "contact": "Kontakt", + "website": "Nettsted", + "recommendation": "Anbefaling" } - \ No newline at end of file +} diff --git a/frontend/src/locales/pl.json b/frontend/src/locales/pl.json index c4cd914..512fcac 100644 --- a/frontend/src/locales/pl.json +++ b/frontend/src/locales/pl.json @@ -300,7 +300,11 @@ "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ę" + "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" }, "worldtravel": { "country_list": "Lista krajów", diff --git a/frontend/src/locales/sv.json b/frontend/src/locales/sv.json index 5efbf63..b4e596d 100644 --- a/frontend/src/locales/sv.json +++ b/frontend/src/locales/sv.json @@ -252,7 +252,11 @@ "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" + "ordered_itinerary": "Beställd resplan", + "additional_info": "Ytterligare information", + "invalid_date_range": "Ogiltigt datumintervall", + "sunrise_sunset": "Soluppgång", + "timezone": "Tidszon" }, "home": { "desc_1": "Upptäck, planera och utforska med lätthet", diff --git a/frontend/src/locales/zh.json b/frontend/src/locales/zh.json index 84caea8..b8ea77c 100644 --- a/frontend/src/locales/zh.json +++ b/frontend/src/locales/zh.json @@ -300,7 +300,11 @@ "collection_no_start_end_date": "在集合页面中添加开始日期和结束日期将在“收集”页面中解锁行程计划功能。", "date_itinerary": "日期行程", "no_ordered_items": "将带有日期的项目添加到集合中,以便在此处查看它们。", - "ordered_itinerary": "订购了行程" + "ordered_itinerary": "订购了行程", + "additional_info": "附加信息", + "invalid_date_range": "无效的日期范围", + "sunrise_sunset": "日出", + "timezone": "时区" }, "auth": { "forgot_password": "忘记密码?", From 04f9227ae6f2ae9faa1ba05562c652fb115440df Mon Sep 17 00:00:00 2001 From: Sean Morley <mail@seanmorley.com> Date: Fri, 9 May 2025 21:17:11 -0400 Subject: [PATCH 65/79] Add default category icon and improve visit display: - Set default icon for empty category in AdventureModal - Enhance layout for visit buttons and validation messages in DateRangeCollapse - Update localization files to include "no visits" strings in multiple languages --- .../src/lib/components/AdventureModal.svelte | 14 +- .../lib/components/DateRangeCollapse.svelte | 237 +++++++++--------- frontend/src/locales/de.json | 3 +- frontend/src/locales/en.json | 1 + frontend/src/locales/es.json | 3 +- frontend/src/locales/fr.json | 3 +- frontend/src/locales/it.json | 3 +- frontend/src/locales/ko.json | 3 +- frontend/src/locales/nl.json | 3 +- frontend/src/locales/no.json | 3 +- frontend/src/locales/pl.json | 3 +- frontend/src/locales/sv.json | 3 +- frontend/src/locales/zh.json | 3 +- 13 files changed, 157 insertions(+), 125 deletions(-) diff --git a/frontend/src/lib/components/AdventureModal.svelte b/frontend/src/lib/components/AdventureModal.svelte index add0ef4..016e0f1 100644 --- a/frontend/src/lib/components/AdventureModal.svelte +++ b/frontend/src/lib/components/AdventureModal.svelte @@ -463,6 +463,13 @@ event.preventDefault(); triggerMarkVisted = 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 == '') { if (categories.some((category) => category.name === 'general')) { @@ -479,6 +486,7 @@ }; } } + let res = await fetch('/api/adventures', { method: 'POST', headers: { @@ -708,8 +716,10 @@ <span>{$t('adventures.warning')}: {warningMessage}</span> </div> {/if} - <button type="submit" class="btn btn-primary">{$t('adventures.save_next')}</button> - <button type="button" class="btn" on:click={close}>{$t('about.close')}</button> + <div class="flex flex-row gap-2"> + <button type="submit" class="btn btn-primary">{$t('adventures.save_next')}</button> + <button type="button" class="btn" on:click={close}>{$t('about.close')}</button> + </div> </div> </div> </form> diff --git a/frontend/src/lib/components/DateRangeCollapse.svelte b/frontend/src/lib/components/DateRangeCollapse.svelte index 5a4198a..33d0066 100644 --- a/frontend/src/lib/components/DateRangeCollapse.svelte +++ b/frontend/src/lib/components/DateRangeCollapse.svelte @@ -258,119 +258,6 @@ ></textarea> </div> {/if} - </div> - - <!-- Validation Message --> - {#if !validateDateRange(localStartDate, localEndDate).valid} - <div role="alert" class="alert alert-error"> - <svg - xmlns="http://www.w3.org/2000/svg" - class="h-6 w-6 shrink-0 stroke-current" - fill="none" - viewBox="0 0 24 24" - > - <path - stroke-linecap="round" - stroke-linejoin="round" - stroke-width="2" - d="M10 14l2-2m0 0l2-2m-2 2l-2-2m2 2l2 2m7-2a9 9 0 11-18 0 9 9 0 0118 0z" - /> - </svg> - <span>{$t('adventures.invalid_date_range')}</span> - </div> - {/if} - - {#if visits && visits.length > 0} - <div class="space-y-4"> - {#each visits as visit} - <div - class="p-4 border border-neutral rounded-lg bg-base-100 shadow-sm flex flex-col gap-2" - > - <p class="text-sm text-base-content font-medium"> - {#if isAllDay(visit.start_date)} - <span class="badge badge-outline mr-2">{$t('adventures.all_day')}</span> - {visit.start_date.split('T')[0]} – {visit.end_date.split('T')[0]} - {:else} - {new Date(visit.start_date).toLocaleString()} – {new Date( - visit.end_date - ).toLocaleString()} - {/if} - </p> - - <!-- If the selected timezone is not the current one show the timezone + the time converted there --> - - {#if visit.notes} - <p class="text-sm text-base-content opacity-70 italic"> - "{visit.notes}" - </p> - {/if} - - <div class="flex gap-2 mt-2"> - <button - class="btn btn-error btn-sm" - type="button" - on:click={() => { - if (visits) { - visits = visits.filter((v) => v.id !== visit.id); - } - }} - > - {$t('adventures.remove')} - </button> - - <button - class="btn btn-primary btn-sm" - type="button" - on:click={() => { - isEditing = true; - const isAllDayEvent = isAllDay(visit.start_date); - allDay = isAllDayEvent; - - if (isAllDayEvent) { - localStartDate = visit.start_date.split('T')[0]; - localEndDate = visit.end_date.split('T')[0]; - } else { - const startDate = new Date(visit.start_date); - const endDate = new Date(visit.end_date); - - localStartDate = `${startDate.getFullYear()}-${String( - startDate.getMonth() + 1 - ).padStart(2, '0')}-${String(startDate.getDate()).padStart(2, '0')}T${String( - startDate.getHours() - ).padStart(2, '0')}:${String(startDate.getMinutes()).padStart(2, '0')}`; - - localEndDate = `${endDate.getFullYear()}-${String( - endDate.getMonth() + 1 - ).padStart(2, '0')}-${String(endDate.getDate()).padStart(2, '0')}T${String( - endDate.getHours() - ).padStart(2, '0')}:${String(endDate.getMinutes()).padStart(2, '0')}`; - } - - // remove it from visits - if (visits) { - visits = visits.filter((v) => v.id !== visit.id); - } - - note = visit.notes; - constrainDates = true; - utcStartDate = visit.start_date; - utcEndDate = visit.end_date; - type = 'adventure'; - - setTimeout(() => { - isEditing = false; - }, 0); - }} - > - {$t('lodging.edit')} - </button> - </div> - </div> - {/each} - </div> - {/if} - <div class="flex gap-2 mb-1"> - <!-- add button --> {#if type === 'adventure'} <button class="btn btn-primary" @@ -402,5 +289,129 @@ </button> {/if} </div> + + <!-- Validation Message --> + {#if !validateDateRange(localStartDate, localEndDate).valid} + <div role="alert" class="alert alert-error"> + <svg + xmlns="http://www.w3.org/2000/svg" + class="h-6 w-6 shrink-0 stroke-current" + fill="none" + viewBox="0 0 24 24" + > + <path + stroke-linecap="round" + stroke-linejoin="round" + stroke-width="2" + d="M10 14l2-2m0 0l2-2m-2 2l-2-2m2 2l2 2m7-2a9 9 0 11-18 0 9 9 0 0118 0z" + /> + </svg> + <span>{$t('adventures.invalid_date_range')}</span> + </div> + {/if} + + {#if type === 'adventure'} + <div class="border-t border-neutral pt-4"> + <h3 class="text-xl font-semibold"> + {$t('adventures.visits')} + </h3> + + <!-- Visits List --> + {#if visits && visits.length === 0} + <p class="text-sm text-base-content opacity-70"> + {$t('adventures.no_visits')} + </p> + {/if} + </div> + + {#if visits && visits.length > 0} + <div class="space-y-4"> + {#each visits as visit} + <div + class="p-4 border border-neutral rounded-lg bg-base-100 shadow-sm flex flex-col gap-2" + > + <p class="text-sm text-base-content font-medium"> + {#if isAllDay(visit.start_date)} + <span class="badge badge-outline mr-2">{$t('adventures.all_day')}</span> + {visit.start_date.split('T')[0]} – {visit.end_date.split('T')[0]} + {:else} + {new Date(visit.start_date).toLocaleString()} – {new Date( + visit.end_date + ).toLocaleString()} + {/if} + </p> + + <!-- If the selected timezone is not the current one show the timezone + the time converted there --> + + {#if visit.notes} + <p class="text-sm text-base-content opacity-70 italic"> + "{visit.notes}" + </p> + {/if} + + <div class="flex gap-2 mt-2"> + <button + class="btn btn-primary btn-sm" + type="button" + on:click={() => { + isEditing = true; + const isAllDayEvent = isAllDay(visit.start_date); + allDay = isAllDayEvent; + + if (isAllDayEvent) { + localStartDate = visit.start_date.split('T')[0]; + localEndDate = visit.end_date.split('T')[0]; + } else { + const startDate = new Date(visit.start_date); + const endDate = new Date(visit.end_date); + + localStartDate = `${startDate.getFullYear()}-${String( + startDate.getMonth() + 1 + ).padStart(2, '0')}-${String(startDate.getDate()).padStart(2, '0')}T${String( + startDate.getHours() + ).padStart(2, '0')}:${String(startDate.getMinutes()).padStart(2, '0')}`; + + localEndDate = `${endDate.getFullYear()}-${String( + endDate.getMonth() + 1 + ).padStart(2, '0')}-${String(endDate.getDate()).padStart(2, '0')}T${String( + endDate.getHours() + ).padStart(2, '0')}:${String(endDate.getMinutes()).padStart(2, '0')}`; + } + + // remove it from visits + if (visits) { + visits = visits.filter((v) => v.id !== visit.id); + } + + note = visit.notes; + constrainDates = true; + utcStartDate = visit.start_date; + utcEndDate = visit.end_date; + type = 'adventure'; + + setTimeout(() => { + isEditing = false; + }, 0); + }} + > + {$t('lodging.edit')} + </button> + <button + class="btn btn-error btn-sm" + type="button" + on:click={() => { + if (visits) { + visits = visits.filter((v) => v.id !== visit.id); + } + }} + > + {$t('adventures.remove')} + </button> + </div> + </div> + {/each} + </div> + {/if} + {/if} </div> </div> diff --git a/frontend/src/locales/de.json b/frontend/src/locales/de.json index 18488ae..9808cd1 100644 --- a/frontend/src/locales/de.json +++ b/frontend/src/locales/de.json @@ -256,7 +256,8 @@ "additional_info": "Weitere Informationen", "invalid_date_range": "Ungültiger Datumsbereich", "sunrise_sunset": "Sonnenaufgang", - "timezone": "Zeitzone" + "timezone": "Zeitzone", + "no_visits": "Keine Besuche" }, "home": { "desc_1": "Entdecken, planen und erkunden Sie mühelos", diff --git a/frontend/src/locales/en.json b/frontend/src/locales/en.json index 9ab91c0..2a837de 100644 --- a/frontend/src/locales/en.json +++ b/frontend/src/locales/en.json @@ -64,6 +64,7 @@ "collection_link_success": "Adventure linked to collection successfully!", "invalid_date_range": "Invalid date range", "timezone": "Timezone", + "no_visits": "No visits", "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.", diff --git a/frontend/src/locales/es.json b/frontend/src/locales/es.json index 6070d2d..14f8ec3 100644 --- a/frontend/src/locales/es.json +++ b/frontend/src/locales/es.json @@ -304,7 +304,8 @@ "additional_info": "información adicional", "invalid_date_range": "Rango de fechas no válido", "sunrise_sunset": "Amanecer", - "timezone": "Zona horaria" + "timezone": "Zona horaria", + "no_visits": "No hay visitas" }, "worldtravel": { "all": "Todo", diff --git a/frontend/src/locales/fr.json b/frontend/src/locales/fr.json index 2f9b4ad..206bcf0 100644 --- a/frontend/src/locales/fr.json +++ b/frontend/src/locales/fr.json @@ -256,7 +256,8 @@ "additional_info": "Informations Complémentaires", "invalid_date_range": "Plage de dates non valide", "sunrise_sunset": "Lever du soleil", - "timezone": "Fuseau horaire" + "timezone": "Fuseau horaire", + "no_visits": "Pas de visites" }, "home": { "desc_1": "Découvrez, planifiez et explorez en toute simplicité", diff --git a/frontend/src/locales/it.json b/frontend/src/locales/it.json index ecaef2d..e34c06a 100644 --- a/frontend/src/locales/it.json +++ b/frontend/src/locales/it.json @@ -256,7 +256,8 @@ "additional_info": "Ulteriori informazioni", "invalid_date_range": "Intervallo di date non valido", "sunrise_sunset": "Alba", - "timezone": "Fuso orario" + "timezone": "Fuso orario", + "no_visits": "Nessuna visita" }, "home": { "desc_1": "Scopri, pianifica ed esplora con facilità", diff --git a/frontend/src/locales/ko.json b/frontend/src/locales/ko.json index fd47b52..8925ef2 100644 --- a/frontend/src/locales/ko.json +++ b/frontend/src/locales/ko.json @@ -256,7 +256,8 @@ "additional_info": "추가 정보", "invalid_date_range": "잘못된 날짜 범위", "sunrise_sunset": "해돋이", - "timezone": "시간대" + "timezone": "시간대", + "no_visits": "방문 없음" }, "auth": { "both_passwords_required": "두 암호 모두 필요합니다", diff --git a/frontend/src/locales/nl.json b/frontend/src/locales/nl.json index 08f013d..8226e11 100644 --- a/frontend/src/locales/nl.json +++ b/frontend/src/locales/nl.json @@ -256,7 +256,8 @@ "additional_info": "Aanvullende informatie", "invalid_date_range": "Ongeldige datumbereik", "sunrise_sunset": "Zonsopgang", - "timezone": "Tijdzone" + "timezone": "Tijdzone", + "no_visits": "Geen bezoeken" }, "home": { "desc_1": "Ontdek, plan en verken met gemak", diff --git a/frontend/src/locales/no.json b/frontend/src/locales/no.json index 9aeb95e..f43f284 100644 --- a/frontend/src/locales/no.json +++ b/frontend/src/locales/no.json @@ -304,7 +304,8 @@ "no_ordered_items": "Legg til varer med datoer i samlingen for å se dem her.", "ordered_itinerary": "Bestilt reiserute", "sunrise_sunset": "Soloppgang", - "timezone": "Tidssone" + "timezone": "Tidssone", + "no_visits": "Ingen besøk" }, "worldtravel": { "country_list": "Liste over land", diff --git a/frontend/src/locales/pl.json b/frontend/src/locales/pl.json index 512fcac..bb0554b 100644 --- a/frontend/src/locales/pl.json +++ b/frontend/src/locales/pl.json @@ -304,7 +304,8 @@ "additional_info": "Dodatkowe informacje", "invalid_date_range": "Niepoprawny zakres dat", "sunrise_sunset": "Wschód słońca", - "timezone": "Strefa czasowa" + "timezone": "Strefa czasowa", + "no_visits": "Brak wizyt" }, "worldtravel": { "country_list": "Lista krajów", diff --git a/frontend/src/locales/sv.json b/frontend/src/locales/sv.json index b4e596d..a9aefb9 100644 --- a/frontend/src/locales/sv.json +++ b/frontend/src/locales/sv.json @@ -256,7 +256,8 @@ "additional_info": "Ytterligare information", "invalid_date_range": "Ogiltigt datumintervall", "sunrise_sunset": "Soluppgång", - "timezone": "Tidszon" + "timezone": "Tidszon", + "no_visits": "Inga besök" }, "home": { "desc_1": "Upptäck, planera och utforska med lätthet", diff --git a/frontend/src/locales/zh.json b/frontend/src/locales/zh.json index b8ea77c..98993fa 100644 --- a/frontend/src/locales/zh.json +++ b/frontend/src/locales/zh.json @@ -304,7 +304,8 @@ "additional_info": "附加信息", "invalid_date_range": "无效的日期范围", "sunrise_sunset": "日出", - "timezone": "时区" + "timezone": "时区", + "no_visits": "没有访问" }, "auth": { "forgot_password": "忘记密码?", From 4ce7ed7045bda14535f7f4f873dee9bd783e75f9 Mon Sep 17 00:00:00 2001 From: Sean Morley <mail@seanmorley.com> Date: Fri, 9 May 2025 21:31:37 -0400 Subject: [PATCH 66/79] Improve layout by wrapping map link text in a paragraph element --- frontend/src/routes/adventures/[id]/+page.svelte | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frontend/src/routes/adventures/[id]/+page.svelte b/frontend/src/routes/adventures/[id]/+page.svelte index 3e7bb38..6f095ba 100644 --- a/frontend/src/routes/adventures/[id]/+page.svelte +++ b/frontend/src/routes/adventures/[id]/+page.svelte @@ -459,7 +459,7 @@ {/if} {#if adventure.longitude && adventure.latitude} <div> - {$t('adventures.open_in_maps')}: + <p class="mb-1">{$t('adventures.open_in_maps')}:</p> <div class="flex flex-wrap gap-2"> <a class="btn btn-neutral text-base btn-sm max-w-32" From b712d10d7e092318d81ae1b23af7e163d714a4c1 Mon Sep 17 00:00:00 2001 From: Sean Morley <98704938+seanmorley15@users.noreply.github.com> Date: Fri, 9 May 2025 21:35:16 -0400 Subject: [PATCH 67/79] Potential fix for code scanning alert no. 13: Workflow does not contain permissions Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> --- .github/workflows/backend-test.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.github/workflows/backend-test.yml b/.github/workflows/backend-test.yml index afe3fa8..0ce9d7c 100644 --- a/.github/workflows/backend-test.yml +++ b/.github/workflows/backend-test.yml @@ -1,5 +1,8 @@ name: Test Backend +permissions: + contents: read + on: pull_request: paths: From 07c0c36ab81fe5bbc1b01c47b8954a64c149db73 Mon Sep 17 00:00:00 2001 From: Sean Morley <98704938+seanmorley15@users.noreply.github.com> Date: Fri, 9 May 2025 21:35:33 -0400 Subject: [PATCH 68/79] Potential fix for code scanning alert no. 14: Workflow does not contain permissions Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> --- .github/workflows/frontend-test.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.github/workflows/frontend-test.yml b/.github/workflows/frontend-test.yml index 04df55a..73d9b24 100644 --- a/.github/workflows/frontend-test.yml +++ b/.github/workflows/frontend-test.yml @@ -1,5 +1,8 @@ name: Test Frontend +permissions: + contents: read + on: pull_request: paths: From c6177c5a050bf958d26d47b508bd9b9878a2cddf Mon Sep 17 00:00:00 2001 From: Sean Morley <mail@seanmorley.com> Date: Fri, 9 May 2025 23:27:53 -0400 Subject: [PATCH 69/79] Refactor DateRangeCollapse component layout and improve styling --- .../src/lib/components/DateRangeCollapse.svelte | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/frontend/src/lib/components/DateRangeCollapse.svelte b/frontend/src/lib/components/DateRangeCollapse.svelte index 33d0066..0c07d90 100644 --- a/frontend/src/lib/components/DateRangeCollapse.svelte +++ b/frontend/src/lib/components/DateRangeCollapse.svelte @@ -112,16 +112,16 @@ <div class="collapse-title text-xl font-semibold"> {$t('adventures.date_information')} </div> - <div class="collapse-content space-y-6"> + <div class="collapse-content"> <!-- Timezone Selector --> - <div class="flex flex-col sm:flex-row sm:items-center sm:justify-between gap-4"> - <TimezoneSelector bind:selectedTimezone /> - </div> + <div class="flex flex-col sm:flex-row sm:items-center sm:justify-between gap-4"></div> - <div class="rounded-xl border border-base-300 bg-base-100 p-4 space-y-4 shadow-sm"> + <div class="rounded-xl border border-base-300 bg-base-100 p-4 space-y-4 shadow-sm mb-4"> <!-- Group Header --> <h3 class="text-md font-semibold">{$t('navbar.settings')}</h3> + <TimezoneSelector bind:selectedTimezone /> + <!-- All Day Toggle --> <div class="flex justify-between items-center"> <span class="text-sm">{$t('adventures.all_day')}</span> @@ -260,7 +260,7 @@ {/if} {#if type === 'adventure'} <button - class="btn btn-primary" + class="btn btn-primary mb-2" type="button" on:click={() => { const newVisit = { @@ -311,7 +311,7 @@ {/if} {#if type === 'adventure'} - <div class="border-t border-neutral pt-4"> + <div class="border-t border-neutral pt-4 mb-2"> <h3 class="text-xl font-semibold"> {$t('adventures.visits')} </h3> From 89c4f1058a526baf926c60a159738a8598fe3a93 Mon Sep 17 00:00:00 2001 From: Sean Morley <mail@seanmorley.com> Date: Fri, 9 May 2025 23:33:58 -0400 Subject: [PATCH 70/79] Fix date formatting for constraint dates in DateRangeCollapse component --- frontend/src/lib/components/DateRangeCollapse.svelte | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/frontend/src/lib/components/DateRangeCollapse.svelte b/frontend/src/lib/components/DateRangeCollapse.svelte index 0c07d90..87fd957 100644 --- a/frontend/src/lib/components/DateRangeCollapse.svelte +++ b/frontend/src/lib/components/DateRangeCollapse.svelte @@ -64,11 +64,15 @@ // Get constraint dates in the right format based on allDay setting $: constraintStartDate = allDay - ? fullStartDate + ? fullStartDate && fullStartDate.includes('T') ? fullStartDate.split('T')[0] : '' - : fullStartDate; - $: constraintEndDate = allDay ? (fullEndDate ? fullEndDate.split('T')[0] : '') : fullEndDate; + : fullStartDate || ''; + $: constraintEndDate = allDay + ? fullEndDate && fullEndDate.includes('T') + ? fullEndDate.split('T')[0] + : '' + : fullEndDate || ''; // Update local display dates whenever timezone or UTC dates change $: if (!isEditing) { From b30d6df964f6b8984d4e2c952180883d13bc88e9 Mon Sep 17 00:00:00 2001 From: Sean Morley <mail@seanmorley.com> Date: Sat, 10 May 2025 10:47:00 -0400 Subject: [PATCH 71/79] Enhance timezone handling in AdventureModal and DateRangeCollapse components; add support for departure and arrival timezones in the TimezoneSelector and update localization for new timezone labels. --- .../src/lib/components/AdventureModal.svelte | 48 ------- .../lib/components/DateRangeCollapse.svelte | 125 +++++++++++------- .../lib/components/TimezoneSelector.svelte | 20 +-- frontend/src/locales/en.json | 2 + 4 files changed, 94 insertions(+), 101 deletions(-) diff --git a/frontend/src/lib/components/AdventureModal.svelte b/frontend/src/lib/components/AdventureModal.svelte index 016e0f1..37af876 100644 --- a/frontend/src/lib/components/AdventureModal.svelte +++ b/frontend/src/lib/components/AdventureModal.svelte @@ -390,54 +390,6 @@ } } - let new_start_date: string = ''; - let new_end_date: string = ''; - let new_notes: string = ''; - - // Function to add a new visit. - function addNewVisit() { - // If an end date isn’t provided, assume it’s the same as start. - 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; - } - // Convert input to UTC if not already. - if (new_start_date && !new_start_date.includes('Z')) { - new_start_date = new Date(new_start_date).toISOString(); - } - if (new_end_date && !new_end_date.includes('Z')) { - new_end_date = new Date(new_end_date).toISOString(); - } - - // If the visit is all day, force the times to midnight. - if (allDay) { - new_start_date = new_start_date.split('T')[0] + 'T00:00:00.000Z'; - new_end_date = new_end_date.split('T')[0] + 'T00:00:00.000Z'; - } - - adventure.visits = [ - ...adventure.visits, - { - start_date: new_start_date, - end_date: new_end_date, - notes: new_notes, - id: '' // or generate an id as needed - } - ]; - - // Clear the input fields. - new_start_date = ''; - new_end_date = ''; - new_notes = ''; - } - function close() { dispatch('close'); } diff --git a/frontend/src/lib/components/DateRangeCollapse.svelte b/frontend/src/lib/components/DateRangeCollapse.svelte index 87fd957..71b5834 100644 --- a/frontend/src/lib/components/DateRangeCollapse.svelte +++ b/frontend/src/lib/components/DateRangeCollapse.svelte @@ -10,7 +10,8 @@ export let type: 'adventure' | 'transportation' | 'lodging' = 'adventure'; // Initialize with browser's timezone - let selectedTimezone: string = Intl.DateTimeFormat().resolvedOptions().timeZone; + let selectedStartTimezone: string = Intl.DateTimeFormat().resolvedOptions().timeZone; + let selectedEndTimezone: string = Intl.DateTimeFormat().resolvedOptions().timeZone; let allDay: boolean = false; @@ -18,15 +19,14 @@ export let utcStartDate: string | null = null; export let utcEndDate: string | null = null; - console.log('UTC Start Date:', utcStartDate); - console.log('UTC End Date:', utcEndDate); - export let note: string | null = null; type Visit = { id: string; start_date: string; end_date: string; notes: string; + start_timezone?: string; + end_timezone?: string; }; export let visits: Visit[] | null = null; @@ -42,17 +42,15 @@ let isEditing = false; // Disable reactivity when editing onMount(async () => { - console.log('Selected timezone:', selectedTimezone); - console.log('UTC Start Date:', utcStartDate); - console.log('UTC End Date:', utcEndDate); - // Initialize UTC dates from transportationToEdit if available + // Initialize UTC dates localStartDate = updateLocalDate({ utcDate: utcStartDate, - timezone: selectedTimezone + timezone: selectedStartTimezone }).localDate; + localEndDate = updateLocalDate({ utcDate: utcEndDate, - timezone: selectedTimezone + timezone: type === 'transportation' ? selectedEndTimezone : selectedStartTimezone }).localDate; }); @@ -82,12 +80,12 @@ } else { const start = updateLocalDate({ utcDate: utcStartDate, - timezone: selectedTimezone + timezone: selectedStartTimezone }).localDate; const end = updateLocalDate({ utcDate: utcEndDate, - timezone: selectedTimezone + timezone: type === 'transportation' ? selectedEndTimezone : selectedStartTimezone }).localDate; localStartDate = start; @@ -99,16 +97,34 @@ function handleLocalDateChange() { utcStartDate = updateUTCDate({ localDate: localStartDate, - timezone: selectedTimezone, + timezone: selectedStartTimezone, allDay }).utcDate; utcEndDate = updateUTCDate({ localDate: localEndDate, - timezone: selectedTimezone, + timezone: type === 'transportation' ? selectedEndTimezone : selectedStartTimezone, allDay }).utcDate; } + + // Create a visit object with appropriate timezone information + function createVisitObject() { + const newVisit: Visit = { + id: crypto.randomUUID(), + start_date: utcStartDate ?? '', + end_date: utcEndDate ?? utcStartDate ?? '', + notes: note ?? '' + }; + + // For transportation, add timezone information + if (type === 'transportation') { + newVisit.start_timezone = selectedStartTimezone; + newVisit.end_timezone = selectedEndTimezone; + } + + return newVisit; + } </script> <div class="collapse collapse-plus bg-base-200 mb-4 rounded-lg"> @@ -117,14 +133,32 @@ {$t('adventures.date_information')} </div> <div class="collapse-content"> - <!-- Timezone Selector --> - <div class="flex flex-col sm:flex-row sm:items-center sm:justify-between gap-4"></div> - + <!-- Timezone Selector Section --> <div class="rounded-xl border border-base-300 bg-base-100 p-4 space-y-4 shadow-sm mb-4"> <!-- Group Header --> <h3 class="text-md font-semibold">{$t('navbar.settings')}</h3> - <TimezoneSelector bind:selectedTimezone /> + {#if type === 'transportation'} + <!-- Dual timezone selectors for transportation --> + <div class="space-y-4"> + <div> + <label class="text-sm font-medium block mb-1"> + {$t('adventures.departure_timezone')} + </label> + <TimezoneSelector bind:selectedTimezone={selectedStartTimezone} /> + </div> + + <div> + <label class="text-sm font-medium block mb-1"> + {$t('adventures.arrival_timezone')} + </label> + <TimezoneSelector bind:selectedTimezone={selectedEndTimezone} /> + </div> + </div> + {:else} + <!-- Single timezone selector for other types --> + <TimezoneSelector bind:selectedTimezone={selectedStartTimezone} /> + {/if} <!-- All Day Toggle --> <div class="flex justify-between items-center"> @@ -145,21 +179,21 @@ } utcStartDate = updateUTCDate({ localDate: localStartDate, - timezone: selectedTimezone, + timezone: selectedStartTimezone, allDay }).utcDate; utcEndDate = updateUTCDate({ localDate: localEndDate, - timezone: selectedTimezone, + timezone: type === 'transportation' ? selectedEndTimezone : selectedStartTimezone, allDay }).utcDate; localStartDate = updateLocalDate({ utcDate: utcStartDate, - timezone: selectedTimezone + timezone: selectedStartTimezone }).localDate; localEndDate = updateLocalDate({ utcDate: utcEndDate, - timezone: selectedTimezone + timezone: type === 'transportation' ? selectedEndTimezone : selectedStartTimezone }).localDate; }} /> @@ -185,7 +219,9 @@ <!-- Start Date --> <div class="space-y-2"> <label for="date" class="text-sm font-medium"> - {$t('adventures.start_date')} + {type === 'transportation' + ? $t('adventures.departure_date') + : $t('adventures.start_date')} </label> {#if allDay} @@ -217,7 +253,7 @@ {#if localStartDate} <div class="space-y-2"> <label for="end_date" class="text-sm font-medium"> - {$t('adventures.end_date')} + {type === 'transportation' ? $t('adventures.arrival_date') : $t('adventures.end_date')} </label> {#if allDay} @@ -267,12 +303,7 @@ class="btn btn-primary mb-2" type="button" on:click={() => { - const newVisit = { - id: crypto.randomUUID(), - start_date: utcStartDate ?? '', - end_date: utcEndDate ?? utcStartDate ?? '', - notes: note ?? '' - }; + const newVisit = createVisitObject(); // Ensure reactivity by assigning a *new* array if (visits) { @@ -345,7 +376,12 @@ {/if} </p> - <!-- If the selected timezone is not the current one show the timezone + the time converted there --> + <!-- Display timezone information for transportation visits --> + {#if visit.start_timezone && visit.end_timezone && visit.start_timezone !== visit.end_timezone} + <p class="text-xs text-base-content"> + {visit.start_timezone} → {visit.end_timezone} + </p> + {/if} {#if visit.notes} <p class="text-sm text-base-content opacity-70 italic"> @@ -362,24 +398,24 @@ const isAllDayEvent = isAllDay(visit.start_date); allDay = isAllDayEvent; + // Set timezone information if available + if (visit.start_timezone) selectedStartTimezone = visit.start_timezone; + if (visit.end_timezone) selectedEndTimezone = visit.end_timezone; + if (isAllDayEvent) { localStartDate = visit.start_date.split('T')[0]; localEndDate = visit.end_date.split('T')[0]; } else { - const startDate = new Date(visit.start_date); - const endDate = new Date(visit.end_date); + // Update with timezone awareness + localStartDate = updateLocalDate({ + utcDate: visit.start_date, + timezone: selectedStartTimezone + }).localDate; - localStartDate = `${startDate.getFullYear()}-${String( - startDate.getMonth() + 1 - ).padStart(2, '0')}-${String(startDate.getDate()).padStart(2, '0')}T${String( - startDate.getHours() - ).padStart(2, '0')}:${String(startDate.getMinutes()).padStart(2, '0')}`; - - localEndDate = `${endDate.getFullYear()}-${String( - endDate.getMonth() + 1 - ).padStart(2, '0')}-${String(endDate.getDate()).padStart(2, '0')}T${String( - endDate.getHours() - ).padStart(2, '0')}:${String(endDate.getMinutes()).padStart(2, '0')}`; + localEndDate = updateLocalDate({ + utcDate: visit.end_date, + timezone: visit.end_timezone || selectedStartTimezone + }).localDate; } // remove it from visits @@ -391,7 +427,6 @@ constrainDates = true; utcStartDate = visit.start_date; utcEndDate = visit.end_date; - type = 'adventure'; setTimeout(() => { isEditing = false; diff --git a/frontend/src/lib/components/TimezoneSelector.svelte b/frontend/src/lib/components/TimezoneSelector.svelte index 4e40d61..b0e52fd 100644 --- a/frontend/src/lib/components/TimezoneSelector.svelte +++ b/frontend/src/lib/components/TimezoneSelector.svelte @@ -3,10 +3,12 @@ import { onMount } from 'svelte'; export let selectedTimezone: string = Intl.DateTimeFormat().resolvedOptions().timeZone; + // Generate a unique ID for this component instance + const instanceId = `tz-selector-${crypto.randomUUID().substring(0, 8)}`; let dropdownOpen = false; let searchQuery = ''; - let searchInput: HTMLInputElement; + let searchInput: HTMLInputElement | null = null; const timezones = Intl.supportedValuesOf('timeZone'); // Filter timezones based on search query @@ -20,10 +22,12 @@ searchQuery = ''; } - // Focus search input when dropdown opens + // Focus search input when dropdown opens - with proper null check $: if (dropdownOpen && searchInput) { // Use setTimeout to delay focus until after the element is rendered - setTimeout(() => searchInput.focus(), 0); + setTimeout(() => { + if (searchInput) searchInput.focus(); + }, 0); } function handleKeydown(event: KeyboardEvent, tz?: string) { @@ -40,7 +44,7 @@ // Close dropdown if clicked outside onMount(() => { const handleClickOutside = (e: MouseEvent) => { - const dropdown = document.getElementById('tz-selector'); + const dropdown = document.getElementById(instanceId); if (dropdown && !dropdown.contains(e.target as Node)) dropdownOpen = false; }; document.addEventListener('click', handleClickOutside); @@ -48,14 +52,14 @@ }); </script> -<div class="form-control w-full max-w-xs relative" id="tz-selector"> - <label class="label" for="timezone-display"> +<div class="form-control w-full max-w-xs relative" id={instanceId}> + <label class="label" for={`timezone-display-${instanceId}`}> <span class="label-text">{$t('adventures.timezone')}</span> </label> <!-- Trigger --> <div - id="timezone-display" + id={`timezone-display-${instanceId}`} tabindex="0" role="button" aria-haspopup="listbox" @@ -82,7 +86,7 @@ <div class="absolute mt-1 z-10 bg-base-100 shadow-lg rounded-box w-full max-h-60 overflow-y-auto" role="listbox" - aria-labelledby="timezone-display" + aria-labelledby={`timezone-display-${instanceId}`} > <!-- Search --> <div class="sticky top-0 bg-base-100 p-2 border-b"> diff --git a/frontend/src/locales/en.json b/frontend/src/locales/en.json index 46d723e..7212fc0 100644 --- a/frontend/src/locales/en.json +++ b/frontend/src/locales/en.json @@ -65,6 +65,8 @@ "invalid_date_range": "Invalid date range", "timezone": "Timezone", "no_visits": "No visits", + "departure_timezone": "Departure Timezone", + "arrival_timezone": "Arrival Timezone", "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.", From 1323d91e3226e1b0cc676fe1d3aedc5dcbab0523 Mon Sep 17 00:00:00 2001 From: Sean Morley <mail@seanmorley.com> Date: Sat, 10 May 2025 11:59:56 -0400 Subject: [PATCH 72/79] Add timezone support for visits, transportation, and lodging - Introduced TIMEZONES constant in models.py to store valid timezone options. - Updated Visit, Transportation, and Lodging models to include timezone fields. - Modified serializers to include timezone fields in VisitSerializer, TransportationSerializer, and LodgingSerializer. - Enhanced DateRangeCollapse component to handle timezone selection and formatting. - Implemented timezone formatting functions in LodgingCard and TransportationCard components. - Updated LodgingModal and TransportationModal to bind timezone data. - Added VALID_TIMEZONES to dateUtils for consistent timezone management across the application. --- .../migrations/0026_visit_timezone.py | 18 + ...27_transportation_end_timezone_and_more.py | 23 + .../migrations/0028_lodging_timezone.py | 18 + backend/server/adventures/models.py | 424 ++++++++++++++++++ backend/server/adventures/serializers.py | 6 +- .../lib/components/DateRangeCollapse.svelte | 106 ++++- .../src/lib/components/LodgingCard.svelte | 35 +- .../src/lib/components/LodgingModal.svelte | 4 +- .../lib/components/TransportationCard.svelte | 34 +- .../lib/components/TransportationModal.svelte | 7 +- frontend/src/lib/dateUtils.ts | 420 +++++++++++++++++ frontend/src/lib/types.ts | 6 + 12 files changed, 1056 insertions(+), 45 deletions(-) create mode 100644 backend/server/adventures/migrations/0026_visit_timezone.py create mode 100644 backend/server/adventures/migrations/0027_transportation_end_timezone_and_more.py create mode 100644 backend/server/adventures/migrations/0028_lodging_timezone.py 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 0000000..f0642dd --- /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 0000000..6331225 --- /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 0000000..d9513f7 --- /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/models.py b/backend/server/adventures/models.py index 6f5b3b7..b09d603 100644 --- a/backend/server/adventures/models.py +++ b/backend/server/adventures/models.py @@ -43,6 +43,426 @@ ADVENTURE_TYPES = [ ('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'), @@ -78,6 +498,7 @@ class Visit(models.Model): adventure = models.ForeignKey('Adventure', on_delete=models.CASCADE, related_name='visits') 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) @@ -191,6 +612,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) @@ -352,6 +775,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) diff --git a/backend/server/adventures/serializers.py b/backend/server/adventures/serializers.py index d69466d..b352c65 100644 --- a/backend/server/adventures/serializers.py +++ b/backend/server/adventures/serializers.py @@ -72,7 +72,7 @@ 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): @@ -201,7 +201,7 @@ class TransportationSerializer(CustomModelSerializer): 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' ] read_only_fields = ['id', 'created_at', 'updated_at', 'user_id'] @@ -212,7 +212,7 @@ class LodgingSerializer(CustomModelSerializer): 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'] diff --git a/frontend/src/lib/components/DateRangeCollapse.svelte b/frontend/src/lib/components/DateRangeCollapse.svelte index 71b5834..bc65817 100644 --- a/frontend/src/lib/components/DateRangeCollapse.svelte +++ b/frontend/src/lib/components/DateRangeCollapse.svelte @@ -10,8 +10,8 @@ export let type: 'adventure' | 'transportation' | 'lodging' = 'adventure'; // Initialize with browser's timezone - let selectedStartTimezone: string = Intl.DateTimeFormat().resolvedOptions().timeZone; - let selectedEndTimezone: string = Intl.DateTimeFormat().resolvedOptions().timeZone; + export let selectedStartTimezone: string = Intl.DateTimeFormat().resolvedOptions().timeZone; + export let selectedEndTimezone: string = Intl.DateTimeFormat().resolvedOptions().timeZone; let allDay: boolean = false; @@ -25,10 +25,19 @@ start_date: string; end_date: string; notes: string; - start_timezone?: string; - end_timezone?: string; + timezone: string | null; }; - export let visits: Visit[] | null = null; + + type TransportationVisit = { + id: string; + start_date: string; + end_date: string; + notes: string; + start_timezone: string; + end_timezone: string; + }; + + export let visits: (Visit | TransportationVisit)[] | null = null; // Local display values let localStartDate: string = ''; @@ -52,6 +61,13 @@ utcDate: utcEndDate, timezone: type === 'transportation' ? selectedEndTimezone : selectedStartTimezone }).localDate; + + if (!selectedStartTimezone) { + selectedStartTimezone = Intl.DateTimeFormat().resolvedOptions().timeZone; + } + if (!selectedEndTimezone) { + selectedEndTimezone = Intl.DateTimeFormat().resolvedOptions().timeZone; + } }); // Set the full date range for constraining purposes @@ -60,6 +76,22 @@ fullEndDate = `${collection.end_date}T23:59`; } + function formatDateInTimezone(utcDate: string, timezone: string): string { + try { + return new Intl.DateTimeFormat(undefined, { + timeZone: timezone, + year: 'numeric', + month: 'short', + day: 'numeric', + hour: '2-digit', + minute: '2-digit', + hour12: true + }).format(new Date(utcDate)); + } catch { + return new Date(utcDate).toLocaleString(); // fallback + } + } + // Get constraint dates in the right format based on allDay setting $: constraintStartDate = allDay ? fullStartDate && fullStartDate.includes('T') @@ -108,22 +140,27 @@ }).utcDate; } - // Create a visit object with appropriate timezone information - function createVisitObject() { - const newVisit: Visit = { - id: crypto.randomUUID(), - start_date: utcStartDate ?? '', - end_date: utcEndDate ?? utcStartDate ?? '', - notes: note ?? '' - }; - - // For transportation, add timezone information + function createVisitObject(): Visit | TransportationVisit { if (type === 'transportation') { - newVisit.start_timezone = selectedStartTimezone; - newVisit.end_timezone = selectedEndTimezone; + const transportVisit: TransportationVisit = { + id: crypto.randomUUID(), + start_date: utcStartDate ?? '', + end_date: utcEndDate ?? utcStartDate ?? '', + notes: note ?? '', + start_timezone: selectedStartTimezone, + end_timezone: selectedEndTimezone + }; + return transportVisit; + } else { + const regularVisit: Visit = { + id: crypto.randomUUID(), + start_date: utcStartDate ?? '', + end_date: utcEndDate ?? utcStartDate ?? '', + notes: note ?? '', + timezone: selectedStartTimezone + }; + return regularVisit; } - - return newVisit; } </script> @@ -369,15 +406,31 @@ {#if isAllDay(visit.start_date)} <span class="badge badge-outline mr-2">{$t('adventures.all_day')}</span> {visit.start_date.split('T')[0]} – {visit.end_date.split('T')[0]} + {:else if 'start_timezone' in visit} + {formatDateInTimezone(visit.start_date, visit.start_timezone)} – {formatDateInTimezone( + visit.end_date, + visit.end_timezone + )} + {:else if visit.timezone} + {formatDateInTimezone(visit.start_date, visit.timezone)} – {formatDateInTimezone( + visit.end_date, + visit.timezone + )} {:else} {new Date(visit.start_date).toLocaleString()} – {new Date( visit.end_date ).toLocaleString()} + <!-- showe timezones badge --> + {/if} + {#if 'timezone' in visit && visit.timezone} + <span class="badge badge-outline ml-2">{visit.timezone}</span> {/if} </p> + <!-- --> + <!-- Display timezone information for transportation visits --> - {#if visit.start_timezone && visit.end_timezone && visit.start_timezone !== visit.end_timezone} + {#if 'start_timezone' in visit && 'end_timezone' in visit && visit.start_timezone !== visit.end_timezone} <p class="text-xs text-base-content"> {visit.start_timezone} → {visit.end_timezone} </p> @@ -399,8 +452,14 @@ allDay = isAllDayEvent; // Set timezone information if available - if (visit.start_timezone) selectedStartTimezone = visit.start_timezone; - if (visit.end_timezone) selectedEndTimezone = visit.end_timezone; + if ('start_timezone' in visit) { + // TransportationVisit + selectedStartTimezone = visit.start_timezone; + selectedEndTimezone = visit.end_timezone; + } else if (visit.timezone) { + // Visit + selectedStartTimezone = visit.timezone; + } if (isAllDayEvent) { localStartDate = visit.start_date.split('T')[0]; @@ -414,7 +473,8 @@ localEndDate = updateLocalDate({ utcDate: visit.end_date, - timezone: visit.end_timezone || selectedStartTimezone + timezone: + 'end_timezone' in visit ? visit.end_timezone : selectedStartTimezone }).localDate; } diff --git a/frontend/src/lib/components/LodgingCard.svelte b/frontend/src/lib/components/LodgingCard.svelte index 2fe8b0e..abb2cfd 100644 --- a/frontend/src/lib/components/LodgingCard.svelte +++ b/frontend/src/lib/components/LodgingCard.svelte @@ -18,6 +18,23 @@ } } + function formatDateInTimezone(utcDate: string, timezone?: string): string { + if (!utcDate) return ''; + try { + return new Intl.DateTimeFormat(undefined, { + timeZone: timezone, + year: 'numeric', + month: 'short', + day: 'numeric', + hour: '2-digit', + minute: '2-digit', + hour12: true + }).format(new Date(utcDate)); + } catch { + return new Date(utcDate).toLocaleString(); + } + } + export let lodging: Lodging; export let user: User | null = null; export let collection: Collection | null = null; @@ -119,21 +136,9 @@ <div class="flex items-center gap-2"> <span class="font-medium text-sm">{$t('adventures.dates')}:</span> <p> - {new Date(lodging.check_in).toLocaleString(undefined, { - month: 'short', - day: 'numeric', - year: 'numeric', - hour: 'numeric', - minute: 'numeric' - })} - - - {new Date(lodging.check_out).toLocaleString(undefined, { - month: 'short', - day: 'numeric', - year: 'numeric', - hour: 'numeric', - minute: 'numeric' - })} + {formatDateInTimezone(lodging.check_in ?? '', lodging.timezone ?? undefined)} – + {formatDateInTimezone(lodging.check_out ?? '', lodging.timezone ?? undefined)} + <span class="text-xs opacity-60 ml-1">({lodging.timezone})</span> </p> </div> {/if} diff --git a/frontend/src/lib/components/LodgingModal.svelte b/frontend/src/lib/components/LodgingModal.svelte index cbe05c8..239211b 100644 --- a/frontend/src/lib/components/LodgingModal.svelte +++ b/frontend/src/lib/components/LodgingModal.svelte @@ -56,7 +56,8 @@ is_public: lodgingToEdit?.is_public || false, collection: lodgingToEdit?.collection || collection.id, created_at: lodgingToEdit?.created_at || '', - updated_at: lodgingToEdit?.updated_at || '' + updated_at: lodgingToEdit?.updated_at || '', + timezone: lodgingToEdit?.timezone || '' }; } @@ -303,6 +304,7 @@ type="lodging" bind:utcStartDate={lodging.check_in} bind:utcEndDate={lodging.check_out} + bind:selectedStartTimezone={lodging.timezone} /> <!-- Location Information --> diff --git a/frontend/src/lib/components/TransportationCard.svelte b/frontend/src/lib/components/TransportationCard.svelte index c97bd85..9eea0a0 100644 --- a/frontend/src/lib/components/TransportationCard.svelte +++ b/frontend/src/lib/components/TransportationCard.svelte @@ -18,6 +18,23 @@ } const dispatch = createEventDispatcher(); + function formatDateInTimezone(utcDate: string, timezone?: string): string { + if (!utcDate) return ''; + try { + return new Intl.DateTimeFormat(undefined, { + timeZone: timezone, + year: 'numeric', + month: 'short', + day: 'numeric', + hour: '2-digit', + minute: '2-digit', + hour12: true + }).format(new Date(utcDate)); + } catch { + return new Date(utcDate).toLocaleString(); + } + } + export let transportation: Transportation; export let user: User | null = null; export let collection: Collection | null = null; @@ -136,7 +153,12 @@ {#if transportation.date} <div class="flex items-center gap-2"> <span class="font-medium text-sm">{$t('adventures.start')}:</span> - <p>{new Date(transportation.date).toLocaleString()}</p> + <p> + {formatDateInTimezone(transportation.date, transportation.start_timezone ?? undefined)} + {#if transportation.start_timezone} + <span class="text-xs opacity-60 ml-1">({transportation.start_timezone})</span> + {/if} + </p> </div> {/if} </div> @@ -154,7 +176,15 @@ {#if transportation.end_date} <div class="flex items-center gap-2"> <span class="font-medium text-sm">{$t('adventures.end')}:</span> - <p>{new Date(transportation.end_date).toLocaleString()}</p> + <p> + {formatDateInTimezone( + transportation.end_date, + transportation.end_timezone || undefined + )} + {#if transportation.end_timezone} + <span class="text-xs opacity-60 ml-1">({transportation.end_timezone})</span> + {/if} + </p> </div> {/if} </div> diff --git a/frontend/src/lib/components/TransportationModal.svelte b/frontend/src/lib/components/TransportationModal.svelte index cbf4920..b1bce1e 100644 --- a/frontend/src/lib/components/TransportationModal.svelte +++ b/frontend/src/lib/components/TransportationModal.svelte @@ -36,7 +36,9 @@ origin_latitude: transportationToEdit?.origin_latitude || NaN, origin_longitude: transportationToEdit?.origin_longitude || NaN, destination_latitude: transportationToEdit?.destination_latitude || NaN, - destination_longitude: transportationToEdit?.destination_longitude || NaN + destination_longitude: transportationToEdit?.destination_longitude || NaN, + start_timezone: transportationToEdit?.start_timezone || '', + end_timezone: transportationToEdit?.end_timezone || '' }; let starting_airport: string = ''; @@ -343,6 +345,9 @@ type="transportation" bind:utcStartDate={transportation.date} bind:utcEndDate={transportation.end_date} + bind:selectedStartTimezone={transportation.start_timezone} + bind:selectedEndTimezone={transportation.end_timezone} + {collection} /> <!-- Flight Information --> diff --git a/frontend/src/lib/dateUtils.ts b/frontend/src/lib/dateUtils.ts index 3a4a18c..e35144e 100644 --- a/frontend/src/lib/dateUtils.ts +++ b/frontend/src/lib/dateUtils.ts @@ -120,3 +120,423 @@ export function formatUTCDate(utcDate: string | null): string { if (!utcDate) return ''; return DateTime.fromISO(utcDate).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/types.ts b/frontend/src/lib/types.ts index 69735a3..ab7f01c 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; @@ -30,6 +32,7 @@ export type Adventure = { id: string; start_date: string; end_date: string; + timezone: string | null; notes: string; }[]; collection?: string | null; @@ -160,6 +163,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; @@ -288,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; From ab189c8aff5de8b2f1849781c88802954c51e9d4 Mon Sep 17 00:00:00 2001 From: Sean Morley <mail@seanmorley.com> Date: Sat, 10 May 2025 12:49:43 -0400 Subject: [PATCH 73/79] Improve UTC date formatting by adding validation for ISO date strings in formatUTCDate function --- frontend/src/lib/dateUtils.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/frontend/src/lib/dateUtils.ts b/frontend/src/lib/dateUtils.ts index e35144e..d949504 100644 --- a/frontend/src/lib/dateUtils.ts +++ b/frontend/src/lib/dateUtils.ts @@ -118,7 +118,9 @@ export function validateDateRange( */ export function formatUTCDate(utcDate: string | null): string { if (!utcDate) return ''; - return DateTime.fromISO(utcDate).toISO().slice(0, 16).replace('T', ' '); + const dateTime = DateTime.fromISO(utcDate); + if (!dateTime.isValid) return ''; + return dateTime.toISO()?.slice(0, 16).replace('T', ' ') || ''; } export const VALID_TIMEZONES = [ From c9fa1d55f7050bf6d5adfd526c0c9daebc25b6ef Mon Sep 17 00:00:00 2001 From: Sean Morley <mail@seanmorley.com> Date: Sat, 10 May 2025 13:02:06 -0400 Subject: [PATCH 74/79] Refactor toLocalDatetime function to improve ISO date handling and return format --- frontend/src/lib/dateUtils.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/frontend/src/lib/dateUtils.ts b/frontend/src/lib/dateUtils.ts index d949504..6d611ed 100644 --- a/frontend/src/lib/dateUtils.ts +++ b/frontend/src/lib/dateUtils.ts @@ -12,10 +12,10 @@ export function toLocalDatetime( timezone: string = Intl.DateTimeFormat().resolvedOptions().timeZone ): string { if (!utcDate) return ''; - return DateTime.fromISO(utcDate, { zone: 'UTC' }) + const isoString = DateTime.fromISO(utcDate, { zone: 'UTC' }) .setZone(timezone) - .toISO({ suppressSeconds: true, includeOffset: false }) - .slice(0, 16); + .toISO({ suppressSeconds: true, includeOffset: false }); + return isoString ? isoString.slice(0, 16) : ''; } /** From 8538aa0b7cfdbf7bd3539b66758b8868c900ef25 Mon Sep 17 00:00:00 2001 From: Sean Morley <mail@seanmorley.com> Date: Sat, 10 May 2025 13:02:44 -0400 Subject: [PATCH 75/79] Validate ISO date in toLocalDatetime function and return empty string for invalid dates --- frontend/src/lib/dateUtils.ts | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/frontend/src/lib/dateUtils.ts b/frontend/src/lib/dateUtils.ts index 6d611ed..166af3b 100644 --- a/frontend/src/lib/dateUtils.ts +++ b/frontend/src/lib/dateUtils.ts @@ -12,9 +12,15 @@ export function toLocalDatetime( timezone: string = Intl.DateTimeFormat().resolvedOptions().timeZone ): string { if (!utcDate) return ''; - const isoString = DateTime.fromISO(utcDate, { zone: 'UTC' }) - .setZone(timezone) - .toISO({ suppressSeconds: true, includeOffset: false }); + + 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) : ''; } From 330fabb3e07b5ce761c18f533d13f0e81a198c8a Mon Sep 17 00:00:00 2001 From: Sean Morley <mail@seanmorley.com> Date: Sat, 10 May 2025 13:17:01 -0400 Subject: [PATCH 76/79] Fix validation in Lodging model to check check-in and check-out dates; update LodgingCard to conditionally display timezone --- backend/server/adventures/models.py | 4 ++-- frontend/src/lib/components/LodgingCard.svelte | 4 +++- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/backend/server/adventures/models.py b/backend/server/adventures/models.py index b09d603..800677a 100644 --- a/backend/server/adventures/models.py +++ b/backend/server/adventures/models.py @@ -787,8 +787,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/frontend/src/lib/components/LodgingCard.svelte b/frontend/src/lib/components/LodgingCard.svelte index abb2cfd..7b7395c 100644 --- a/frontend/src/lib/components/LodgingCard.svelte +++ b/frontend/src/lib/components/LodgingCard.svelte @@ -138,7 +138,9 @@ <p> {formatDateInTimezone(lodging.check_in ?? '', lodging.timezone ?? undefined)} – {formatDateInTimezone(lodging.check_out ?? '', lodging.timezone ?? undefined)} - <span class="text-xs opacity-60 ml-1">({lodging.timezone})</span> + {#if lodging.timezone} + <span class="text-xs opacity-60 ml-1">({lodging.timezone})</span> + {/if} </p> </div> {/if} From abc2d86dcf5082b957f08598bb27f758cd749789 Mon Sep 17 00:00:00 2001 From: Sean Morley <mail@seanmorley.com> Date: Sat, 10 May 2025 21:40:18 -0400 Subject: [PATCH 77/79] Enhance visit date display with timezone support and improved formatting --- .../src/routes/adventures/[id]/+page.svelte | 82 ++++++++++++------- 1 file changed, 54 insertions(+), 28 deletions(-) diff --git a/frontend/src/routes/adventures/[id]/+page.svelte b/frontend/src/routes/adventures/[id]/+page.svelte index 6d54d85..4d5d335 100644 --- a/frontend/src/routes/adventures/[id]/+page.svelte +++ b/frontend/src/routes/adventures/[id]/+page.svelte @@ -10,6 +10,8 @@ import DOMPurify from 'dompurify'; // @ts-ignore import toGeoJSON from '@mapbox/togeojson'; + // @ts-ignore + import { DateTime } from 'luxon'; import LightbulbOn from '~icons/mdi/lightbulb-on'; import WeatherSunset from '~icons/mdi/weather-sunset'; @@ -412,33 +414,41 @@ </p> <!-- show each visit start and end date as well as notes --> {#each adventure.visits as visit} - <div class="flex flex-col gap-2"> - <div class="flex gap-2 items-center"> - <p> - {#if isAllDay(visit.start_date)} - <!-- For all-day events, show just the date --> - {new Date(visit.start_date).toLocaleDateString(undefined, { - timeZone: 'UTC' - })} + <div + class="p-4 border border-neutral rounded-lg bg-base-100 shadow-sm flex flex-col gap-2 mb-1" + > + {#if isAllDay(visit.start_date)} + <p class="text-sm text-base-content font-medium"> + <span class="badge badge-outline mr-2">All Day</span> + {visit.start_date.split('T')[0]} – {visit.end_date.split('T')[0]} + </p> + {:else} + <p class="text-sm text-base-content font-medium"> + {#if visit.timezone} + <!-- Use visit.timezone --> + 🕓 <strong>{visit.timezone}</strong><br /> + {DateTime.fromISO(visit.start_date, { zone: 'utc' }) + .setZone(visit.timezone) + .toLocaleString(DateTime.DATETIME_MED)} – + {DateTime.fromISO(visit.end_date, { zone: 'utc' }) + .setZone(visit.timezone) + .toLocaleString(DateTime.DATETIME_MED)} {:else} - <!-- For timed events, show date and time --> - {new Date(visit.start_date).toLocaleDateString()} ({new Date( - visit.start_date - ).toLocaleTimeString()}) + <!-- Fallback to local browser time --> + 🕓 <strong>Local Time</strong><br /> + {DateTime.fromISO(visit.start_date).toLocaleString( + DateTime.DATETIME_MED + )} – + {DateTime.fromISO(visit.end_date).toLocaleString( + DateTime.DATETIME_MED + )} {/if} </p> - {#if visit.end_date && visit.end_date !== visit.start_date} - <p> - - {new Date(visit.end_date).toLocaleDateString(undefined, { - timeZone: 'UTC' - })} - {#if !isAllDay(visit.end_date)} - ({new Date(visit.end_date).toLocaleTimeString()}) - {/if} - </p> - {/if} - </div> - <p class="whitespace-pre-wrap -mt-2 mb-2">{visit.notes}</p> + {/if} + + {#if visit.notes} + <p class="text-sm text-base-content opacity-70 italic">"{visit.notes}"</p> + {/if} </div> {/each} </div> @@ -527,13 +537,29 @@ 'T' )[0]} {:else} - {new Date(visit.start_date).toLocaleString()} – {new Date( - visit.end_date - ).toLocaleString()} + <span> + <strong>Local:</strong> + {DateTime.fromISO(visit.start_date).toLocaleString( + DateTime.DATETIME_MED + )} – + {DateTime.fromISO(visit.end_date).toLocaleString( + DateTime.DATETIME_MED + )} + </span> {/if} </p> - <!-- If the selected timezone is not the current one show the timezone + the time converted there --> + {#if !isAllDay(visit.start_date) && visit.timezone} + <p class="text-sm text-base-content opacity-80"> + <strong>{visit.timezone}:</strong> + {DateTime.fromISO(visit.start_date, { zone: 'utc' }) + .setZone(visit.timezone) + .toLocaleString(DateTime.DATETIME_MED)} – + {DateTime.fromISO(visit.end_date, { zone: 'utc' }) + .setZone(visit.timezone) + .toLocaleString(DateTime.DATETIME_MED)} + </p> + {/if} {#if visit.notes} <p class="text-sm text-base-content opacity-70 italic"> From d9d754a87c4a373de344673cbf3b5fdbc99a3786 Mon Sep 17 00:00:00 2001 From: Sean Morley <mail@seanmorley.com> Date: Sat, 10 May 2025 22:03:31 -0400 Subject: [PATCH 78/79] Fix date handling in DateRangeCollapse and TransportationModal; improve hash change handling in +page.svelte --- frontend/src/lib/components/DateRangeCollapse.svelte | 8 +++++--- .../src/lib/components/TransportationModal.svelte | 11 +++++++++-- frontend/src/routes/collections/[id]/+page.svelte | 7 ++++--- 3 files changed, 18 insertions(+), 8 deletions(-) diff --git a/frontend/src/lib/components/DateRangeCollapse.svelte b/frontend/src/lib/components/DateRangeCollapse.svelte index bc65817..4161ad3 100644 --- a/frontend/src/lib/components/DateRangeCollapse.svelte +++ b/frontend/src/lib/components/DateRangeCollapse.svelte @@ -208,8 +208,8 @@ bind:checked={allDay} on:change={() => { if (allDay) { - localStartDate = localStartDate.split('T')[0]; - localEndDate = localEndDate.split('T')[0]; + localStartDate = localStartDate ? localStartDate.split('T')[0] : ''; + localEndDate = localEndDate ? localEndDate.split('T')[0] : ''; } else { localStartDate = localStartDate + 'T00:00'; localEndDate = localEndDate + 'T23:59'; @@ -405,7 +405,9 @@ <p class="text-sm text-base-content font-medium"> {#if isAllDay(visit.start_date)} <span class="badge badge-outline mr-2">{$t('adventures.all_day')}</span> - {visit.start_date.split('T')[0]} – {visit.end_date.split('T')[0]} + {visit.start_date ? visit.start_date.split('T')[0] : ''} – {visit.end_date + ? visit.end_date.split('T')[0] + : ''} {:else if 'start_timezone' in visit} {formatDateInTimezone(visit.start_date, visit.start_timezone)} – {formatDateInTimezone( visit.end_date, diff --git a/frontend/src/lib/components/TransportationModal.svelte b/frontend/src/lib/components/TransportationModal.svelte index b1bce1e..38580da 100644 --- a/frontend/src/lib/components/TransportationModal.svelte +++ b/frontend/src/lib/components/TransportationModal.svelte @@ -41,6 +41,13 @@ end_timezone: transportationToEdit?.end_timezone || '' }; + let startTimezone: string | undefined = transportation.start_timezone ?? undefined; + let endTimezone: string | undefined = transportation.end_timezone ?? undefined; + + // Later, you should manually sync these back to `transportation` if needed + $: transportation.start_timezone = startTimezone ?? ''; + $: transportation.end_timezone = endTimezone ?? ''; + let starting_airport: string = ''; let ending_airport: string = ''; @@ -345,8 +352,8 @@ type="transportation" bind:utcStartDate={transportation.date} bind:utcEndDate={transportation.end_date} - bind:selectedStartTimezone={transportation.start_timezone} - bind:selectedEndTimezone={transportation.end_timezone} + bind:selectedStartTimezone={startTimezone} + bind:selectedEndTimezone={endTimezone} {collection} /> diff --git a/frontend/src/routes/collections/[id]/+page.svelte b/frontend/src/routes/collections/[id]/+page.svelte index b57af12..63ee0ce 100644 --- a/frontend/src/routes/collections/[id]/+page.svelte +++ b/frontend/src/routes/collections/[id]/+page.svelte @@ -300,7 +300,7 @@ function handleHashChange() { const hash = window.location.hash.replace('#', ''); if (hash) { - currentView = hash + currentView = hash; } else if (!collection.start_date) { currentView = 'all'; } else { @@ -308,7 +308,7 @@ } } - function changeHash(event) { + function changeHash(event: any) { window.location.hash = '#' + event.target.value; } @@ -776,7 +776,8 @@ {/if} {#if collection.id} - <select class="select select-bordered border-primary md:hidden w-full" + <select + class="select select-bordered border-primary md:hidden w-full" value={currentView} on:change={changeHash} > From 4af80eb58416c309c00eaa3b50ad8ab6a225c9e8 Mon Sep 17 00:00:00 2001 From: Sean Morley <mail@seanmorley.com> Date: Sat, 10 May 2025 22:10:55 -0400 Subject: [PATCH 79/79] Add arrival and departure timezone translations in multiple languages --- frontend/src/locales/de.json | 4 +++- frontend/src/locales/es.json | 4 +++- frontend/src/locales/fr.json | 4 +++- frontend/src/locales/it.json | 4 +++- frontend/src/locales/ko.json | 4 +++- frontend/src/locales/nl.json | 4 +++- frontend/src/locales/no.json | 4 +++- frontend/src/locales/pl.json | 4 +++- frontend/src/locales/sv.json | 4 +++- frontend/src/locales/zh.json | 4 +++- 10 files changed, 30 insertions(+), 10 deletions(-) diff --git a/frontend/src/locales/de.json b/frontend/src/locales/de.json index 9808cd1..7032096 100644 --- a/frontend/src/locales/de.json +++ b/frontend/src/locales/de.json @@ -257,7 +257,9 @@ "invalid_date_range": "Ungültiger Datumsbereich", "sunrise_sunset": "Sonnenaufgang", "timezone": "Zeitzone", - "no_visits": "Keine Besuche" + "no_visits": "Keine Besuche", + "arrival_timezone": "Ankunftszeitzone", + "departure_timezone": "Abfahrtszeit" }, "home": { "desc_1": "Entdecken, planen und erkunden Sie mühelos", diff --git a/frontend/src/locales/es.json b/frontend/src/locales/es.json index 14f8ec3..c6fd713 100644 --- a/frontend/src/locales/es.json +++ b/frontend/src/locales/es.json @@ -305,7 +305,9 @@ "invalid_date_range": "Rango de fechas no válido", "sunrise_sunset": "Amanecer", "timezone": "Zona horaria", - "no_visits": "No hay visitas" + "no_visits": "No hay visitas", + "arrival_timezone": "Zona horaria de llegada", + "departure_timezone": "Zona horaria de salida" }, "worldtravel": { "all": "Todo", diff --git a/frontend/src/locales/fr.json b/frontend/src/locales/fr.json index 206bcf0..1a9575f 100644 --- a/frontend/src/locales/fr.json +++ b/frontend/src/locales/fr.json @@ -257,7 +257,9 @@ "invalid_date_range": "Plage de dates non valide", "sunrise_sunset": "Lever du soleil", "timezone": "Fuseau horaire", - "no_visits": "Pas de visites" + "no_visits": "Pas de visites", + "arrival_timezone": "Fuseau horaire d'arrivée", + "departure_timezone": "Fuseau horaire de départ" }, "home": { "desc_1": "Découvrez, planifiez et explorez en toute simplicité", diff --git a/frontend/src/locales/it.json b/frontend/src/locales/it.json index e34c06a..a9e9159 100644 --- a/frontend/src/locales/it.json +++ b/frontend/src/locales/it.json @@ -257,7 +257,9 @@ "invalid_date_range": "Intervallo di date non valido", "sunrise_sunset": "Alba", "timezone": "Fuso orario", - "no_visits": "Nessuna visita" + "no_visits": "Nessuna visita", + "arrival_timezone": "Fuso orario di arrivo", + "departure_timezone": "Fuso orario di partenza" }, "home": { "desc_1": "Scopri, pianifica ed esplora con facilità", diff --git a/frontend/src/locales/ko.json b/frontend/src/locales/ko.json index 8925ef2..d74928e 100644 --- a/frontend/src/locales/ko.json +++ b/frontend/src/locales/ko.json @@ -257,7 +257,9 @@ "invalid_date_range": "잘못된 날짜 범위", "sunrise_sunset": "해돋이", "timezone": "시간대", - "no_visits": "방문 없음" + "no_visits": "방문 없음", + "arrival_timezone": "도착 시간대", + "departure_timezone": "출발 시간대" }, "auth": { "both_passwords_required": "두 암호 모두 필요합니다", diff --git a/frontend/src/locales/nl.json b/frontend/src/locales/nl.json index 8226e11..3434452 100644 --- a/frontend/src/locales/nl.json +++ b/frontend/src/locales/nl.json @@ -257,7 +257,9 @@ "invalid_date_range": "Ongeldige datumbereik", "sunrise_sunset": "Zonsopgang", "timezone": "Tijdzone", - "no_visits": "Geen bezoeken" + "no_visits": "Geen bezoeken", + "arrival_timezone": "Aankomsttijdzone", + "departure_timezone": "Vertrektijdzone" }, "home": { "desc_1": "Ontdek, plan en verken met gemak", diff --git a/frontend/src/locales/no.json b/frontend/src/locales/no.json index f43f284..3880f43 100644 --- a/frontend/src/locales/no.json +++ b/frontend/src/locales/no.json @@ -305,7 +305,9 @@ "ordered_itinerary": "Bestilt reiserute", "sunrise_sunset": "Soloppgang", "timezone": "Tidssone", - "no_visits": "Ingen besøk" + "no_visits": "Ingen besøk", + "arrival_timezone": "Ankomst tidssone", + "departure_timezone": "Avgangstidssone" }, "worldtravel": { "country_list": "Liste over land", diff --git a/frontend/src/locales/pl.json b/frontend/src/locales/pl.json index bb0554b..f91c20a 100644 --- a/frontend/src/locales/pl.json +++ b/frontend/src/locales/pl.json @@ -305,7 +305,9 @@ "invalid_date_range": "Niepoprawny zakres dat", "sunrise_sunset": "Wschód słońca", "timezone": "Strefa czasowa", - "no_visits": "Brak wizyt" + "no_visits": "Brak wizyt", + "arrival_timezone": "Strefa czasowa przyjazdu", + "departure_timezone": "Strefa czasowa odlotu" }, "worldtravel": { "country_list": "Lista krajów", diff --git a/frontend/src/locales/sv.json b/frontend/src/locales/sv.json index a9aefb9..dd8b1bc 100644 --- a/frontend/src/locales/sv.json +++ b/frontend/src/locales/sv.json @@ -257,7 +257,9 @@ "invalid_date_range": "Ogiltigt datumintervall", "sunrise_sunset": "Soluppgång", "timezone": "Tidszon", - "no_visits": "Inga besök" + "no_visits": "Inga besök", + "arrival_timezone": "Ankomsttidszon", + "departure_timezone": "Avgångstidszon" }, "home": { "desc_1": "Upptäck, planera och utforska med lätthet", diff --git a/frontend/src/locales/zh.json b/frontend/src/locales/zh.json index 98993fa..e4380b5 100644 --- a/frontend/src/locales/zh.json +++ b/frontend/src/locales/zh.json @@ -305,7 +305,9 @@ "invalid_date_range": "无效的日期范围", "sunrise_sunset": "日出", "timezone": "时区", - "no_visits": "没有访问" + "no_visits": "没有访问", + "arrival_timezone": "到达时区", + "departure_timezone": "离开时区" }, "auth": { "forgot_password": "忘记密码?",