Skip to content

Commit d38bed5

Browse files
committed
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
1 parent cf75390 commit d38bed5

File tree

5 files changed

+92
-12
lines changed

5 files changed

+92
-12
lines changed

cal/src/EventDetails.vue

Lines changed: 52 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,8 @@ export default {
6363
// done loading.
6464
const page = buildPage(evt, vm.calStart, to.fullPath);
6565
vm.evt = evt;
66+
// override the server's shareable with the spa's current page.
67+
evt.shareable = window.location;
6668
vm.$emit("pageLoaded", page);
6769
});
6870
}
@@ -92,6 +94,7 @@ export default {
9294
const { caldaily_id } = this.$route.params;
9395
return {
9496
// placeholder empty event data
97+
// 'id' will become valid when loading is finished
9598
evt: {
9699
caldaily_id,
97100
},
@@ -111,15 +114,27 @@ export default {
111114
},
112115
loopText() {
113116
return this.evt.loopride && 'Ride is a loop';
114-
}
115-
},
116-
methods: {
117-
webLink(evt) {
118-
return helpers.getWebLink(evt.weburl);
119117
},
120-
contactLink(evt) {
121-
return helpers.getContactLink(evt.contact);
118+
shareableLink() {
119+
return this.evt.shareable;
120+
},
121+
exportLink() {
122+
// FIX: this matches the calendar but should be a single day.
123+
const { series_id } = this.$route.params;
124+
return dataPool.getExportURL(series_id);
125+
},
126+
addToGoogleLink() {
127+
const { evt } = this;
128+
return evt.id && helpers.getAddToGoogleLink(evt);
122129
},
130+
webLink() {
131+
const { evt } = this;
132+
return evt.id && helpers.getWebLink(evt.weburl);
133+
},
134+
contactLink() {
135+
const { evt } = this;
136+
return evt.id && helpers.getContactLink(evt.contact);
137+
}
123138
}
124139
}
125140
</script>
@@ -143,8 +158,8 @@ export default {
143158
<LocationLink :evt="evt"></LocationLink>
144159
</Term>
145160
<Term id="organizer" label= "Organizer">
146-
<span class="c-organizer__name c-organizer__name--link" v-if="contactLink(evt)">
147-
<ExternalLink :href="contactLink(evt)">{{evt.organizer}}</ExternalLink>
161+
<span class="c-organizer__name c-organizer__name--link" v-if="contactLink">
162+
<ExternalLink :href="contactLink">{{evt.organizer}}</ExternalLink>
148163
</span>
149164
<span v-else class="c-organizer__name c-organizer__name--text">
150165
{{ evt.organizer }}
@@ -162,14 +177,19 @@ export default {
162177
<Term id="locend" label= "End Location" pretext="Ending at " :text="evt.locend"/>
163178
<Term id="loop" label= "Loop" :text="loopText"/>
164179
<Term v-if="evt.weburl" label="More Info">
165-
<ExternalLink :href="webLink(evt)">
180+
<ExternalLink :href="webLink">
166181
{{evt.webname || evt.weburl}}
167182
</ExternalLink>
168183
</Term>
169184
</dl>
170185
<p class="c-description">
171186
{{evt.details}}
172187
</p>
188+
<ul class="c-detail-links" v-if="evt.id">
189+
<li><a :href="shareableLink" class="c-links__share" rel="bookmark">Sharable link</a></li>
190+
<li><a :href="exportLink" class="c-links__export">Export to calendar</a></li>
191+
<li><a :href="addToGoogleLink" class="c-links__google" target="_blank">Add to Google Calendar</a></li>
192+
</ul>
173193
</article>
174194
</template>
175195

@@ -205,4 +225,26 @@ export default {
205225
margin-top: 1em;
206226
padding-top: 1em;
207227
}
228+
.c-detail-links {
229+
display: flex;
230+
justify-content: center;
231+
flex-direction: column;
232+
flex-flow: row wrap;
233+
list-style-type: none;
234+
gap: 5px;
235+
/* on safari empty tags collapse, on chrome they take up space.
236+
this helps keep things consistent */
237+
margin: 1em 0;
238+
padding-inline-start: 0px;
239+
240+
/* copied from main.css eventlink */
241+
li {
242+
border-right: 1px solid var(--page-text);
243+
padding-right: 5px;
244+
&:last-child {
245+
border-right: none;
246+
}
247+
}
248+
}
249+
208250
</style>

cal/src/calHelpers.js

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,36 @@ export default {
5454
return friendlyTime(time) + suffix;
5555
},
5656

57+
// duplicated from calendars helpers
58+
getAddToGoogleLink(event) {
59+
const googleCalUrl = new URL('https://www.google.com/calendar/render');
60+
61+
const startDate = dayjs(`${event.date} ${event.time}`).toISOString();
62+
const duration = event.duration ?? 60; // Google requires a duration and defaults to 60 minutes anyway
63+
const endDate = dayjs(startDate).add(dayjs.duration({ 'minute': duration })).toISOString();
64+
/**
65+
* Matches anything that is not a word or whitespace
66+
* @example
67+
* "2025-05-21T16:30:00.000Z".replace(regex, '') // 20250521T163000000Z
68+
*/
69+
const regex = /[^\w\s]/gi;
70+
71+
// Remove colons and periods for Google Calendar URL (2025-05-21T16:30:00.000Z => 20250521T163000000Z)
72+
const calendarDates = `${startDate.replace(regex, '')}/${endDate.replace(regex, '')}`;
73+
74+
googleCalUrl.search = new URLSearchParams({
75+
action: "TEMPLATE",
76+
text: `shift2Bikes: ${event.title}`,
77+
location: event.address,
78+
details: `${event.details}\n\n${event.shareable}`,
79+
dates: calendarDates,
80+
sf: true, // ??
81+
output: 'xml'
82+
});
83+
84+
return googleCalUrl.toString();
85+
},
86+
5787
// https://jasonwatmore.com/vanilla-js-slugify-a-string-in-javascript
5888
// might want the server to do this; but fine for now.
5989
slugify(evt) {

cal/src/eventDetails.js

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,6 @@ function buildShortcuts(evt, fullPath) {
5050
};
5151
},
5252
addevent: "/addevent/",
53-
export: `/api/ics.php?id=${evt.id}`,
5453
// hide share till its ready
5554
// see also the ShortcutButton
5655
// this might depend on platform or sussing out capabilities.

cal/src/siteConfig.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,9 +7,11 @@ import siteInfo from 'extras/siteInfo.json'
77
import dayjs from 'dayjs'
88
import advancedFormat from 'dayjs/plugin/advancedFormat'
99
import customParseFormat from 'dayjs/plugin/customParseFormat'
10+
import duration from 'dayjs/plugin/duration'
1011

1112
dayjs.extend(advancedFormat);
1213
dayjs.extend(customParseFormat);
14+
dayjs.extend(duration);
1315

1416
// the calendar part of the menu has links to the pages we are already on
1517
// so those are unneeded.

cal/src/support/dataPool.js

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ const API_BASE_URL = window.location.origin;
99
// const API_BASE_URL = "https://api.shift2bikes.org";
1010
const API_EVENTS_URL = new URL(`/api/events.php`, API_BASE_URL);
1111
const API_SEARCH_URL = new URL(`/api/search.php`, API_BASE_URL);
12+
const API_ICS_URL = new URL('/api/ics.php', API_BASE_URL);
1213

1314
// cache the most recent range.
1415
// useful for front-end development where browser caching is disable.
@@ -73,7 +74,13 @@ export default {
7374
const data = await resp.json(); // data => { events: [], pagination: {} }
7475
mungeEvents(data.events);
7576
return data;
76-
}
77+
},
78+
// fix: this isn't nice as a method of data pool
79+
// make an endpoints file of some sort that dataPool uses.
80+
// ( and then have callers of this use that file directly )
81+
getExportURL(id) {
82+
return buildUrl(API_ICS_URL, {id});
83+
},
7784
}
7885

7986
// change dates into dayjs; and sort.

0 commit comments

Comments
 (0)