From 2b031f51ac7f50dc387a7b4f5f0eba55fb1d2a2e Mon Sep 17 00:00:00 2001 From: Jannis Martensen Date: Fri, 11 Apr 2025 12:35:05 +0200 Subject: [PATCH 01/52] fixed oicd not working on frontend login page --- backend/server/users/views.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/backend/server/users/views.py b/backend/server/users/views.py index 7c763f5..d5a5a41 100644 --- a/backend/server/users/views.py +++ b/backend/server/users/views.py @@ -165,7 +165,7 @@ class EnabledSocialProvidersView(APIView): providers = [] for provider in social_providers: if provider.provider == 'openid_connect': - new_provider = f'oidc/{provider.client_id}' + new_provider = f'oidc/{provider.provider_id}' else: new_provider = provider.provider providers.append({ @@ -204,4 +204,4 @@ class DisablePasswordAuthenticationView(APIView): user.disable_password = False user.save() return Response({"detail": "Password authentication enabled."}, status=status.HTTP_200_OK) - \ No newline at end of file + From 44a260b5b64add3adce2ef6715daf230864ea7d0 Mon Sep 17 00:00:00 2001 From: Lars Kiesow Date: Tue, 15 Apr 2025 01:05:02 +0200 Subject: [PATCH 02/52] 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}
{$t('adventures.out_of_range')}
{/if} + {#if note.content && note.content.length > 0} +

+ {note.content} +

+ {/if} {#if note.links && note.links.length > 0}

{note.links.length} {note.links.length > 1 ? $t('adventures.links') : $t('adventures.link')}

+ {/if} {#if note.date && note.date !== ''}
From 9a825e56e47818468d874d1ba608fdf32753cd34 Mon Sep 17 00:00:00 2001 From: Lars Kiesow Date: Wed, 16 Apr 2025 00:45:29 +0200 Subject: [PATCH 03/52] 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 @@

{$t('navbar.theme_selection')}

- {#each themes as theme} -
  • - -
  • - {/each} +
    From e40ea028d0d1abf121c91ebd26b34c248204f166 Mon Sep 17 00:00:00 2001 From: Lars Kiesow Date: Sat, 26 Apr 2025 22:53:45 +0200 Subject: [PATCH 04/52] 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} -
    + + 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 @@ + + + + +

    {new Date(utcStartDate).toLocaleString()} - {new Date(utcEndDate).toLocaleString()}

    + +

    UTC Start Date: {utcStartDate}

    +

    UTC End Date: {utcEndDate}

    From 77be046f194515a3de8a0c51c1a62c418225b751 Mon Sep 17 00:00:00 2001 From: Sean Morley Date: Tue, 6 May 2025 14:47:03 -0400 Subject: [PATCH 14/52] feat: Add JSON-LD structured data for homepage SEO enhancement --- documentation/.vitepress/config.mts | 60 +++++++++++++++++++++++++++++ 1 file changed, 60 insertions(+) diff --git a/documentation/.vitepress/config.mts b/documentation/.vitepress/config.mts index ef5cd99..46effbb 100644 --- a/documentation/.vitepress/config.mts +++ b/documentation/.vitepress/config.mts @@ -25,6 +25,66 @@ export default defineConfig({ hostname: "https://adventurelog.app", }, + transformPageData(pageData) { + if (pageData.relativePath === "/") { + const jsonLd = { + "@context": "https://schema.org", + "@type": "SoftwareApplication", + name: "AdventureLog", + url: "https://adventurelog.app", + applicationCategory: "TravelApplication", + operatingSystem: "Web, Docker, Linux", + description: + "AdventureLog is a self-hosted platform for tracking and planning travel experiences. Built for modern explorers, it offers trip planning, journaling, tracking and location mapping in one privacy-respecting package.", + creator: { + "@type": "Person", + name: "Sean Morley", + url: "https://seanmorley.com", + }, + offers: { + "@type": "Offer", + price: "0.00", + priceCurrency: "USD", + description: "Open-source version available for self-hosting.", + }, + softwareVersion: "v0.9.0", + license: + "https://github.com/seanmorley15/adventurelog/blob/main/LICENSE", + screenshot: "https://adventurelog.app/adventurelog.png", + downloadUrl: "https://github.com/seanmorley15/adventurelog", + sameAs: ["https://github.com/seanmorley15/adventurelog"], + keywords: [ + "self-hosted travel log", + "open source trip planner", + "travel journaling app", + "docker travel diary", + "map-based travel tracker", + "privacy-focused travel app", + "adventure log software", + "travel experience tracker", + "self-hosted travel app", + "open source travel software", + "trip planning tool", + "travel itinerary manager", + "location-based travel app", + "travel experience sharing", + "travel log application", + ], + }; + + return { + frontmatter: { + ...pageData.frontmatter, + head: [ + ["script", { type: "application/ld+json" }, JSON.stringify(jsonLd)], + ], + }, + }; + } + + return {}; + }, + themeConfig: { // https://vitepress.dev/reference/default-theme-config nav: [ From 8d0490fd814888cdcf144eba693fa82affff50cb Mon Sep 17 00:00:00 2001 From: Sean Morley Date: Tue, 6 May 2025 14:50:39 -0400 Subject: [PATCH 15/52] fix: Update pageData check to use 'index.md' for JSON-LD transformation --- documentation/.vitepress/config.mts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/documentation/.vitepress/config.mts b/documentation/.vitepress/config.mts index 46effbb..d3bbf49 100644 --- a/documentation/.vitepress/config.mts +++ b/documentation/.vitepress/config.mts @@ -26,7 +26,7 @@ export default defineConfig({ }, transformPageData(pageData) { - if (pageData.relativePath === "/") { + if (pageData.relativePath === "index.md") { const jsonLd = { "@context": "https://schema.org", "@type": "SoftwareApplication", From c93d4865cea702ff7b5e7f3b99eeec0bc4e6ba80 Mon Sep 17 00:00:00 2001 From: Sean Morley <98704938+seanmorley15@users.noreply.github.com> Date: Tue, 6 May 2025 16:57:32 -0400 Subject: [PATCH 16/52] Update config.mts --- documentation/.vitepress/config.mts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/documentation/.vitepress/config.mts b/documentation/.vitepress/config.mts index d3bbf49..b4562a9 100644 --- a/documentation/.vitepress/config.mts +++ b/documentation/.vitepress/config.mts @@ -50,7 +50,7 @@ export default defineConfig({ softwareVersion: "v0.9.0", license: "https://github.com/seanmorley15/adventurelog/blob/main/LICENSE", - screenshot: "https://adventurelog.app/adventurelog.png", + screenshot: "https://raw.githubusercontent.com/seanmorley15/AdventureLog/refs/heads/main/brand/screenshots/adventures.png", downloadUrl: "https://github.com/seanmorley15/adventurelog", sameAs: ["https://github.com/seanmorley15/adventurelog"], keywords: [ From 5c109bbbaf483d083e261d4914f76bd2d59a2607 Mon Sep 17 00:00:00 2001 From: Sean Morley <98704938+seanmorley15@users.noreply.github.com> Date: Tue, 6 May 2025 19:40:36 -0400 Subject: [PATCH 17/52] Update config.mts --- documentation/.vitepress/config.mts | 1 + 1 file changed, 1 insertion(+) diff --git a/documentation/.vitepress/config.mts b/documentation/.vitepress/config.mts index b4562a9..715e501 100644 --- a/documentation/.vitepress/config.mts +++ b/documentation/.vitepress/config.mts @@ -244,6 +244,7 @@ export default defineConfig({ { icon: "buymeacoffee", link: "https://buymeacoffee.com/seanmorley15" }, { icon: "x", link: "https://x.com/AdventureLogApp" }, { icon: "mastodon", link: "https://mastodon.social/@adventurelog" }, + { icon: "instagram", link: "https://www.instagram.com/adventurelogapp" }, ], }, }); From bbad7b890ccf4fbe55a8b16910985d823b4351fc Mon Sep 17 00:00:00 2001 From: Lars Kiesow Date: Wed, 7 May 2025 21:10:38 +0200 Subject: [PATCH 18/52] 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 @@
    {/if} {#if adventure.longitude && adventure.latitude} -
    - {$t('adventures.open_in_maps')}: - Apple - Google +
    + {$t('adventures.open_in_maps')}: +
    + Apple + Google + OSM +
    {/if} Date: Fri, 9 May 2025 10:24:29 -0400 Subject: [PATCH 19/52] 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 @@
    -
    - -
    - {$t('adventures.visits')} ({adventure.visits.length}) -
    -
    - -
    - {#if !allDay} - { - if (e.key === 'Enter') { - e.preventDefault(); - addNewVisit(); - } - }} - /> - { - if (e.key === 'Enter') { - e.preventDefault(); - addNewVisit(); - } - }} - /> - {:else} - { - if (e.key === 'Enter') { - e.preventDefault(); - addNewVisit(); - } - }} - /> - { - if (e.key === 'Enter') { - e.preventDefault(); - addNewVisit(); - } - }} - /> - {/if} -
    -
    - - -
    - {#if !allDay} - - {/if} -
    - -
    - - {#if adventure.visits.length > 0} -

    {$t('adventures.my_visits')}

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

    - {#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} -

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

    - {/if} -
    - - -
    -
    -

    {visit.notes}

    -
    - {/each} - {/if} -
    -
    +
    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 @@ + + +
    + +
    + {$t('adventures.date_information')} +
    +
    + +
    + +
    + + {$t('adventures.all_day')} + { + // 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; + }} + /> + + + +
    + +
    + + + {#if allDay} + + {:else} + + {/if} + + {#if collection && collection.start_date && collection.end_date} + + {/if} +
    + + + {#if localStartDate} +
    + + + {#if allDay} + + {:else} + + {/if} +
    + {/if} + + + {#if type === 'adventure'} +
    + + +
    + {/if} +
    + + + {#if !validateDateRange(localStartDate, localEndDate).valid} + + {/if} + + {#if visits && visits.length > 0} +
    + {#each visits as visit} +
    +

    + {#if isAllDay(visit.start_date)} + All Day + {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} +

    + + + + {#if visit.notes} +

    + "{visit.notes}" +

    + {/if} + +
    + + + +
    +
    + {/each} +
    + {/if} +
    + + {#if type === 'adventure'} + + {/if} +
    +
    +
    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 @@ - - -
    - -
    - {$t('adventures.date_information')} -
    -
    - -
    - -
    - - -
    - -
    - - - - - {#if collection && collection.start_date && collection.end_date} - - {/if} -
    - - - {#if localStartDate} -
    - - - -
    - {/if} -
    - - - {#if !validateDateRange(localStartDate, localEndDate).valid} - - {/if} - - -
    -
    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 @@
    -
    - -
    - {$t('adventures.date_information')} -
    -
    - -
    - - - {#if collection && collection.start_date && collection.end_date} - {/if} -
    - -
    -
    - -
    - -
    - -
    -
    - -
    -
    + 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 @@
    -
    -
    - -
    - {$t('adventures.date_information')} -
    -
    - - -
    - - {#if collection && collection.start_date && collection.end_date} - {/if} -
    - -
    -
    - - {#if localStartDate} -
    - -
    - -
    -
    - {/if} - - {#if utcStartDate} -
    - UTC Time: {formatUTCDate(utcStartDate)} - {#if utcEndDate && utcEndDate !== utcStartDate} - to {formatUTCDate(utcEndDate)} - {/if} -
    - {/if} -
    -
    + + -
    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 @@ - - - - -

    {new Date(utcStartDate).toLocaleString()} - {new Date(utcEndDate).toLocaleString()}

    - -

    UTC Start Date: {utcStartDate}

    -

    UTC End Date: {utcEndDate}

    From 311e2847cbfa7c758bf929c97d0a7692877cb369 Mon Sep 17 00:00:00 2001 From: Sean Morley Date: Fri, 9 May 2025 15:19:17 -0400 Subject: [PATCH 20/52] 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}

    {#if adventure.visits.length > 0} -

    +

    {#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' - }) - : ''} -
    +

    +

    + {#if isAllDay(visit.start_date)} + All Day + {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} +

    + + + + {#if visit.notes} +

    + "{visit.notes}" +

    + {/if} +
    {/each}

    {/if} From 3caebd37ddefc4420ce982937dac6c26105c0488 Mon Sep 17 00:00:00 2001 From: Sean Morley Date: Fri, 9 May 2025 15:59:48 -0400 Subject: [PATCH 21/52] 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 @@
    - {$t('adventures.all_day')} - { - // 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; - }} - /> - +
    + +

    {$t('navbar.settings')}

    + + +
    + {$t('adventures.all_day')} + { + 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; + }} + /> +
    + + + {#if collection?.start_date && collection?.end_date} +
    + {$t('adventures.date_constrain')} + (constrainDates = !constrainDates)} + /> +
    + {/if} +
    -
    -
    {/if} -
    - - - {#if !validateDateRange(localStartDate, localEndDate).valid} - - {/if} - - {#if visits && visits.length > 0} -
    - {#each visits as visit} -
    -

    - {#if isAllDay(visit.start_date)} - {$t('adventures.all_day')} - {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} -

    - - - - {#if visit.notes} -

    - "{visit.notes}" -

    - {/if} - -
    - - - -
    -
    - {/each} -
    - {/if} -
    - {#if type === 'adventure'}
    + + + {#if !validateDateRange(localStartDate, localEndDate).valid} + + {/if} + + {#if type === 'adventure'} +
    +

    + {$t('adventures.visits')} +

    + + + {#if visits && visits.length === 0} +

    + {$t('adventures.no_visits')} +

    + {/if} +
    + + {#if visits && visits.length > 0} +
    + {#each visits as visit} +
    +

    + {#if isAllDay(visit.start_date)} + {$t('adventures.all_day')} + {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} +

    + + + + {#if visit.notes} +

    + "{visit.notes}" +

    + {/if} + +
    + + +
    +
    + {/each} +
    + {/if} + {/if}
    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 Date: Fri, 9 May 2025 21:31:37 -0400 Subject: [PATCH 23/52] 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}
    - {$t('adventures.open_in_maps')}: +

    {$t('adventures.open_in_maps')}:

    Date: Fri, 9 May 2025 21:35:16 -0400 Subject: [PATCH 24/52] 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 25/52] 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 Date: Fri, 9 May 2025 23:27:53 -0400 Subject: [PATCH 26/52] 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 @@
    {$t('adventures.date_information')}
    -
    +
    -
    - -
    +
    -
    +

    {$t('navbar.settings')}

    + +
    {$t('adventures.all_day')} @@ -260,7 +260,7 @@ {/if} {#if type === 'adventure'}