From d38bed5d14f0ab2ea3f092f1d95151dff6289533 Mon Sep 17 00:00:00 2001 From: ionous Date: Mon, 2 Jun 2025 19:42:23 -0700 Subject: [PATCH] spa: add export calendar links to events page also: remove export button add as a link to bottom of page change contactLink and webLink to computed properties instead of methods for consistency's sake --- cal/src/EventDetails.vue | 62 +++++++++++++++++++++++++++++++------ cal/src/calHelpers.js | 30 ++++++++++++++++++ cal/src/eventDetails.js | 1 - cal/src/siteConfig.js | 2 ++ cal/src/support/dataPool.js | 9 +++++- 5 files changed, 92 insertions(+), 12 deletions(-) diff --git a/cal/src/EventDetails.vue b/cal/src/EventDetails.vue index a1f8e873..b167d46b 100644 --- a/cal/src/EventDetails.vue +++ b/cal/src/EventDetails.vue @@ -63,6 +63,8 @@ export default { // done loading. const page = buildPage(evt, vm.calStart, to.fullPath); vm.evt = evt; + // override the server's shareable with the spa's current page. + evt.shareable = window.location; vm.$emit("pageLoaded", page); }); } @@ -92,6 +94,7 @@ export default { const { caldaily_id } = this.$route.params; return { // placeholder empty event data + // 'id' will become valid when loading is finished evt: { caldaily_id, }, @@ -111,15 +114,27 @@ export default { }, loopText() { return this.evt.loopride && 'Ride is a loop'; - } - }, - methods: { - webLink(evt) { - return helpers.getWebLink(evt.weburl); }, - contactLink(evt) { - return helpers.getContactLink(evt.contact); + shareableLink() { + return this.evt.shareable; + }, + exportLink() { + // FIX: this matches the calendar but should be a single day. + const { series_id } = this.$route.params; + return dataPool.getExportURL(series_id); + }, + addToGoogleLink() { + const { evt } = this; + return evt.id && helpers.getAddToGoogleLink(evt); }, + webLink() { + const { evt } = this; + return evt.id && helpers.getWebLink(evt.weburl); + }, + contactLink() { + const { evt } = this; + return evt.id && helpers.getContactLink(evt.contact); + } } } @@ -143,8 +158,8 @@ export default { - - {{evt.organizer}} + + {{evt.organizer}} {{ evt.organizer }} @@ -162,7 +177,7 @@ export default { - + {{evt.webname || evt.weburl}} @@ -170,6 +185,11 @@ export default {

{{evt.details}}

+ @@ -205,4 +225,26 @@ export default { margin-top: 1em; padding-top: 1em; } +.c-detail-links { + display: flex; + justify-content: center; + flex-direction: column; + flex-flow: row wrap; + list-style-type: none; + gap: 5px; + /* on safari empty tags collapse, on chrome they take up space. + this helps keep things consistent */ + margin: 1em 0; + padding-inline-start: 0px; + + /* copied from main.css eventlink */ + li { + border-right: 1px solid var(--page-text); + padding-right: 5px; + &:last-child { + border-right: none; + } + } +} + \ No newline at end of file diff --git a/cal/src/calHelpers.js b/cal/src/calHelpers.js index 222fb8e6..6abadde9 100644 --- a/cal/src/calHelpers.js +++ b/cal/src/calHelpers.js @@ -54,6 +54,36 @@ export default { return friendlyTime(time) + suffix; }, + // duplicated from calendars helpers + getAddToGoogleLink(event) { + const googleCalUrl = new URL('https://www.google.com/calendar/render'); + + const startDate = dayjs(`${event.date} ${event.time}`).toISOString(); + const duration = event.duration ?? 60; // Google requires a duration and defaults to 60 minutes anyway + const endDate = dayjs(startDate).add(dayjs.duration({ 'minute': duration })).toISOString(); + /** + * Matches anything that is not a word or whitespace + * @example + * "2025-05-21T16:30:00.000Z".replace(regex, '') // 20250521T163000000Z + */ + const regex = /[^\w\s]/gi; + + // Remove colons and periods for Google Calendar URL (2025-05-21T16:30:00.000Z => 20250521T163000000Z) + const calendarDates = `${startDate.replace(regex, '')}/${endDate.replace(regex, '')}`; + + googleCalUrl.search = new URLSearchParams({ + action: "TEMPLATE", + text: `shift2Bikes: ${event.title}`, + location: event.address, + details: `${event.details}\n\n${event.shareable}`, + dates: calendarDates, + sf: true, // ?? + output: 'xml' + }); + + return googleCalUrl.toString(); + }, + // https://jasonwatmore.com/vanilla-js-slugify-a-string-in-javascript // might want the server to do this; but fine for now. slugify(evt) { diff --git a/cal/src/eventDetails.js b/cal/src/eventDetails.js index 04018ca8..4b10a19e 100644 --- a/cal/src/eventDetails.js +++ b/cal/src/eventDetails.js @@ -50,7 +50,6 @@ function buildShortcuts(evt, fullPath) { }; }, addevent: "/addevent/", - export: `/api/ics.php?id=${evt.id}`, // hide share till its ready // see also the ShortcutButton // this might depend on platform or sussing out capabilities. diff --git a/cal/src/siteConfig.js b/cal/src/siteConfig.js index 9ab9300a..fc18acc9 100644 --- a/cal/src/siteConfig.js +++ b/cal/src/siteConfig.js @@ -7,9 +7,11 @@ import siteInfo from 'extras/siteInfo.json' import dayjs from 'dayjs' import advancedFormat from 'dayjs/plugin/advancedFormat' import customParseFormat from 'dayjs/plugin/customParseFormat' +import duration from 'dayjs/plugin/duration' dayjs.extend(advancedFormat); dayjs.extend(customParseFormat); +dayjs.extend(duration); // the calendar part of the menu has links to the pages we are already on // so those are unneeded. diff --git a/cal/src/support/dataPool.js b/cal/src/support/dataPool.js index 6013d2bf..de0e345c 100644 --- a/cal/src/support/dataPool.js +++ b/cal/src/support/dataPool.js @@ -9,6 +9,7 @@ const API_BASE_URL = window.location.origin; // const API_BASE_URL = "https://api.shift2bikes.org"; const API_EVENTS_URL = new URL(`/api/events.php`, API_BASE_URL); const API_SEARCH_URL = new URL(`/api/search.php`, API_BASE_URL); +const API_ICS_URL = new URL('/api/ics.php', API_BASE_URL); // cache the most recent range. // useful for front-end development where browser caching is disable. @@ -73,7 +74,13 @@ export default { const data = await resp.json(); // data => { events: [], pagination: {} } mungeEvents(data.events); return data; - } + }, + // fix: this isn't nice as a method of data pool + // make an endpoints file of some sort that dataPool uses. + // ( and then have callers of this use that file directly ) + getExportURL(id) { + return buildUrl(API_ICS_URL, {id}); + }, } // change dates into dayjs; and sort.