diff --git a/src/components/MeetupsSection.astro b/src/components/MeetupsSection.astro index 090773f..e2d3b74 100644 --- a/src/components/MeetupsSection.astro +++ b/src/components/MeetupsSection.astro @@ -67,9 +67,9 @@ const statusConfig = {
{meetup.data.description}
@@ -223,9 +223,25 @@ const statusConfig = { flex-wrap: wrap; gap: var(--s2); } - .meetup-location { color: var(--pink); } + .meetup-location { + min-width: 0; + max-width: 100%; + overflow: hidden; + flex: 1 1 auto; + color: var(--pink); + text-align: right; + text-overflow: ellipsis; + white-space: nowrap; + } - .meetup-name { font-size: var(--text-xl); line-height: var(--leading-snug); } + .meetup-name { + min-width: 0; + overflow: hidden; + font-size: var(--text-xl); + line-height: var(--leading-snug); + text-overflow: ellipsis; + white-space: nowrap; + } .meetup-name a:hover { color: var(--pink); } .meetup-desc { diff --git a/src/lib/meetups.ts b/src/lib/meetups.ts index 1b0af66..10ed83e 100644 --- a/src/lib/meetups.ts +++ b/src/lib/meetups.ts @@ -351,7 +351,10 @@ function mapPublicMeetup(meetup: PublicMeetup, origin: URL): WebsiteMeetup { } function resolveRemoteWebsiteUrl(value: string, origin: URL): string { - return value.startsWith('/') ? new URL(value, origin).toString() : value; + const resolved = value.startsWith('/') ? new URL(value, origin) : new URL(value); + return isLoopbackHostname(resolved.hostname) + ? new URL(`${resolved.pathname}${resolved.search}${resolved.hash}`, origin).toString() + : resolved.toString(); } function resolveOptionalRemoteWebsiteUrl(value: string | null, origin: URL): string | null { diff --git a/src/pages/events/index.astro b/src/pages/events/index.astro index de5d19f..7e139ca 100644 --- a/src/pages/events/index.astro +++ b/src/pages/events/index.astro @@ -9,7 +9,8 @@ const events = sortEventsBySoonest(await getEvents()).map((event) => ({ })); const calendarFeedUrl = 'https://devcongress.org/events/calendar.ics'; -const googleCalendarUrl = `https://calendar.google.com/calendar/u/0/r?cid=${encodeURIComponent(calendarFeedUrl)}`; +const googleCalendarFeedUrl = `webcal://${new URL(calendarFeedUrl).host}${new URL(calendarFeedUrl).pathname}`; +const googleCalendarUrl = `https://calendar.google.com/calendar/r?cid=${encodeURIComponent(googleCalendarFeedUrl)}`; const hasSubscribableEvents = upcomingCalendarEvents(events).length > 0; function getEventStatus(event: WebsiteEvent): 'upcoming' | 'live' | 'past' { @@ -132,9 +133,18 @@ const calendarEvents = events.map((event) => { Add to Google Calendar )} + {hasSubscribableEvents && ( + + Download .ics file + + )}{hasSubscribableEvents - ? 'Current and upcoming published events. Updates automatically.' + ? 'Current and upcoming published events. Updates automatically. Download the .ics file if needed.' : 'No upcoming events yet. Subscriptions open when the next event is published.'}
{meetup.data.description}
@@ -164,9 +164,15 @@ const statusConfig = { letter-spacing: 0.04em; text-transform: uppercase; flex-wrap: wrap; gap: var(--s2); } - .meetup-location { color: var(--pink); } + .meetup-location { + min-width: 0; max-width: 100%; overflow: hidden; flex: 1 1 auto; + color: var(--pink); text-align: right; text-overflow: ellipsis; white-space: nowrap; + } - .meetup-name { font-size: var(--text-xl); line-height: var(--leading-snug); } + .meetup-name { + min-width: 0; overflow: hidden; font-size: var(--text-xl); + line-height: var(--leading-snug); text-overflow: ellipsis; white-space: nowrap; + } .meetup-name a:hover { color: var(--pink); } .meetup-desc {