Use a pill with explicit links for iCal and Google Calendar.
Some checks failed
/ build (pull_request) Successful in 27s
/ accessibility (pull_request) Failing after 1m41s

This commit is contained in:
Stefan Bethke 2026-10-17 02:13:21 +02:00
commit 443ae1e05d
9 changed files with 34 additions and 40 deletions

View file

@ -49,7 +49,7 @@ Without a data file, or without any tours in it, the shortcode renders nothing a
## Shortcode: Calendar entry ## Shortcode: Calendar entry
`{{< calendar >}}` adds a calendar icon after a tour date that downloads a calendar file (`.ics`) for that date. Hackertours-Admin puts it into the list of dates on each tour page: `{{< calendar >}}` adds a small pill after a tour date with a calendar icon and two links: "iCal download", a calendar file (`.ics`) for that date, and "Google calendar", which adds the date in Google Calendar. Hackertours-Admin puts it into the list of dates on each tour page:
```markdown ```markdown
* Tag 1 (27.12.) **17:30 Uhr** {{< calendar date="2026-12-27" time="17:30" duration="210" >}} * Tag 1 (27.12.) **17:30 Uhr** {{< calendar date="2026-12-27" time="17:30" duration="210" >}}
@ -62,7 +62,7 @@ Without a data file, or without any tours in it, the shortcode renders nothing a
| `duration` | no | Length in minutes. Without it, `duration` of the tour in `tours` in the schedule file is used (see "Map data in the schedule file"); without either, the calendar entry has no end time and the build logs a warning with the ID `warning-calendar-duration`. | | `duration` | no | Length in minutes. Without it, `duration` of the tour in `tours` in the schedule file is used (see "Map data in the schedule file"); without either, the calendar entry has no end time and the build logs a warning with the ID `warning-calendar-duration`. |
| `title` | no | Title of the calendar entry. Without it, the `title` of this date in the schedule file is used, then the page title. | | `title` | no | Title of the calendar entry. Without it, the `title` of this date in the schedule file is used, then the page title. |
The `.ics` file is in the iCalendar format (RFC 5545), which Apple Calendar, Outlook, Thunderbird and most other calendar apps open directly. The Google Calendar app on Android can't open downloaded `.ics` files, so on Android the link adds the date in Google Calendar instead ([assets/js/calendar.js](assets/js/calendar.js) switches it when the browser reports Android). That also applies to Android users with another calendar app, such as Samsung Calendar, which could have opened the file; without JavaScript, everyone gets the `.ics` file. The `.ics` file is in the iCalendar format (RFC 5545), which Apple Calendar, Outlook, Thunderbird and most other calendar apps open directly. The Google calendar link is there because the Google Calendar app on Android can't open downloaded `.ics` files.
The icon is `regular/calendar-plus` from [Font Awesome Free](https://fontawesome.com/search?ic=free), kept in [assets/vendor/fontawesome/](assets/vendor/fontawesome/). Other templates can use any of its icons with `{{ partial "fa-icon.html" "solid/ship" }}`: it inlines the SVG, decorative and in the text colour. The icons are CC BY 4.0; the attribution is the comment inside each SVG, which the partial keeps. The icon is `regular/calendar-plus` from [Font Awesome Free](https://fontawesome.com/search?ic=free), kept in [assets/vendor/fontawesome/](assets/vendor/fontawesome/). Other templates can use any of its icons with `{{ partial "fa-icon.html" "solid/ship" }}`: it inlines the SVG, decorative and in the text colour. The icons are CC BY 4.0; the attribution is the comment inside each SVG, which the partial keeps.

View file

@ -107,17 +107,30 @@ span.content-image {
color: var(--color-text-meta); color: var(--color-text-meta);
} }
/* Calendar link after a tour date, see layouts/_shortcodes/calendar.html. /* Calendar links after a tour date, see layouts/_shortcodes/calendar.html.
At least 24 by 24 pixels, so it is easy to hit (WCAG 2.5.8). */ The links are at least 24 pixels high, so they are easy to hit (WCAG 2.5.8). */
.tour-calendar { .tour-calendar {
display: inline-flex; display: inline-flex;
align-items: center; align-items: center;
justify-content: center; gap: 0.4em;
min-width: 24px; margin-inline-start: 0.4em;
min-height: 24px; padding: 0 0.6em;
margin-inline-start: 0.25em; border: 1px solid color-mix(in oklch, var(--color-text) 25%, transparent);
border-radius: 999px;
font-family: var(--ff-alt);
font-size: var(--fs-xs);
vertical-align: middle; vertical-align: middle;
text-decoration: none; white-space: nowrap;
a {
display: inline-flex;
align-items: center;
min-height: 24px;
}
}
.tour-calendar__separator {
color: var(--color-text-meta);
} }
.fa-icon { .fa-icon {

View file

@ -1,15 +0,0 @@
// Calendar shortcode: on Android, the calendar links add the date in Google Calendar instead of
// downloading the .ics file, because the Google Calendar app there can't open .ics files.
{
const isAndroid =
navigator.userAgentData?.platform === 'Android' || /\bAndroid\b/i.test(navigator.userAgent);
if (isAndroid) {
for (const link of document.querySelectorAll('a.tour-calendar[data-google-href]')) {
link.href = link.dataset.googleHref;
link.removeAttribute('download');
link.title = link.dataset.googleTitle;
link.querySelector('.visually-hidden').textContent = link.dataset.googleLabel;
}
}
}

View file

@ -6,5 +6,5 @@ skip_to_content: "Zum Inhalt springen"
logo_alt: "Hamburg Hackertours – Startseite" logo_alt: "Hamburg Hackertours – Startseite"
map_39c3_title: "Karte der 39C3-Touren mit Wegen vom CCH (uMap)" map_39c3_title: "Karte der 39C3-Touren mit Wegen vom CCH (uMap)"
calendar_meeting_point: "Hackertours-Desk" calendar_meeting_point: "Hackertours-Desk"
calendar_ics: "iCal-Kalenderdatei" calendar_ics: "iCal Download"
calendar_google: "In Google Kalender eintragen" calendar_google: "Google Calendar"

View file

@ -6,5 +6,5 @@ skip_to_content: "Skip to content"
logo_alt: "Hamburg Hackertours – home page" logo_alt: "Hamburg Hackertours – home page"
map_39c3_title: "Map of the 39C3 tours with routes from the CCH (uMap)" map_39c3_title: "Map of the 39C3 tours with routes from the CCH (uMap)"
calendar_meeting_point: "Hackertours Desk" calendar_meeting_point: "Hackertours Desk"
calendar_ics: "iCal calendar file" calendar_ics: "iCal download"
calendar_google: "Add to Google Calendar" calendar_google: "Google calendar"

View file

@ -1,4 +0,0 @@
{{- /* Script for the calendar shortcode, included by the base templates on pages that use it. */ -}}
{{- with resources.Get "js/calendar.js" | minify | fingerprint -}}
<script src="{{ .RelPermalink }}" integrity="{{ .Data.Integrity }}" crossorigin="anonymous" defer></script>
{{- end -}}

View file

@ -1,7 +1,7 @@
{{- /* {{- /*
Calendar link for one date of a tour, shown as a calendar icon: an .ics file (iCalendar), which Calendar links for one date of a tour, in a small pill with a calendar icon: an .ics file
Apple Calendar, Outlook, Thunderbird and most other calendar apps can open. On Android, (iCalendar), which Apple Calendar, Outlook, Thunderbird and most other calendar apps can open,
assets/js/calendar.js turns it into a link that adds the date in Google Calendar. and a link that adds the date in Google Calendar, whose Android app can't open .ics files.
Meant for the list of dates on a tour page: Meant for the list of dates on a tour page:
* Tag 1 (27.12.) **17:30 Uhr** {{< calendar date="2026-12-27" time="17:30" duration="210" >}} * Tag 1 (27.12.) **17:30 Uhr** {{< calendar date="2026-12-27" time="17:30" duration="210" >}}
@ -71,9 +71,8 @@ title of this date in the schedule data, else the page title).
{{- $file := printf "%s-%s-%s.ics" $slug $date (replace $time ":" "") -}} {{- $file := printf "%s-%s-%s.ics" $slug $date (replace $time ":" "") -}}
{{- $icsFile := resources.FromString (path.Join (strings.TrimPrefix "/" $page.RelPermalink) $file) $ics -}} {{- $icsFile := resources.FromString (path.Join (strings.TrimPrefix "/" $page.RelPermalink) $file) $ics -}}
{{- /* The icon is the same for every date, so the link name for screen readers includes the date. */ -}} {{- /* The link texts are the same for every date, so their names for screen readers include the date. */ -}}
{{- $when := printf "%s %s" ($start | time.Format "Mon 2.1.") $time -}} {{- $when := printf "%s %s" ($start | time.Format "Mon 2.1.") $time -}}
{{- /* Android's Google Calendar app can't open .ics files; there, calendar.js uses this link instead. */ -}}
{{- $google := printf "https://calendar.google.com/calendar/render?%s" (querify {{- $google := printf "https://calendar.google.com/calendar/render?%s" (querify
"action" "TEMPLATE" "action" "TEMPLATE"
"text" $title "text" $title
@ -82,5 +81,8 @@ title of this date in the schedule data, else the page title).
"location" $location "location" $location
"ctz" "Europe/Berlin" "ctz" "Europe/Berlin"
) -}} ) -}}
<a class="tour-calendar" href="{{ $icsFile.RelPermalink }}" download="{{ $file }}" title="{{ i18n "calendar_ics" }}" data-google-href="{{ $google }}" data-google-title="{{ i18n "calendar_google" }}" data-google-label="{{ i18n "calendar_google" }} {{ $when }}">{{ partial "fa-icon.html" "regular/calendar-plus" }}<span class="visually-hidden">{{ i18n "calendar_ics" }} {{ $when }}</span></a> <span class="tour-calendar">{{ partial "fa-icon.html" "regular/calendar-plus" -}}
<a href="{{ $icsFile.RelPermalink }}" download="{{ $file }}">{{ i18n "calendar_ics" }}<span class="visually-hidden"> {{ $when }}</span></a>
{{- /* */ -}}<span class="tour-calendar__separator" aria-hidden="true">|</span>
{{- /* */ -}}<a href="{{ $google }}">{{ i18n "calendar_google" }}<span class="visually-hidden"> {{ $when }}</span></a></span>
{{- /* chomp */ -}} {{- /* chomp */ -}}

View file

@ -15,7 +15,6 @@
{{ block "head" . }}{{ end }} {{ block "head" . }}{{ end }}
{{ if .HasShortcode "schedule" }}{{ partial "schedule-script.html" . }}{{ end }} {{ if .HasShortcode "schedule" }}{{ partial "schedule-script.html" . }}{{ end }}
{{ if .HasShortcode "tour-map" }}{{ partial "tour-map-script.html" . }}{{ end }} {{ if .HasShortcode "tour-map" }}{{ partial "tour-map-script.html" . }}{{ end }}
{{ if .HasShortcode "calendar" }}{{ partial "calendar-script.html" . }}{{ end }}
</head> </head>
<body class="{{ if .IsPage }}single-page{{ else }}list-page{{ end }}{{ if .IsHome }} front{{ end }}{{ with .Section }} section-page--{{ . | urlize }}{{ end }}{{ with $.Param "bodyclass" }} {{ . }}{{ end }}"> <body class="{{ if .IsPage }}single-page{{ else }}list-page{{ end }}{{ if .IsHome }} front{{ end }}{{ with .Section }} section-page--{{ . | urlize }}{{ end }}{{ with $.Param "bodyclass" }} {{ . }}{{ end }}">

View file

@ -15,7 +15,6 @@
{{ block "head" . }}{{ end }} {{ block "head" . }}{{ end }}
{{ if .HasShortcode "schedule" }}{{ partial "schedule-script.html" . }}{{ end }} {{ if .HasShortcode "schedule" }}{{ partial "schedule-script.html" . }}{{ end }}
{{ if .HasShortcode "tour-map" }}{{ partial "tour-map-script.html" . }}{{ end }} {{ if .HasShortcode "tour-map" }}{{ partial "tour-map-script.html" . }}{{ end }}
{{ if .HasShortcode "calendar" }}{{ partial "calendar-script.html" . }}{{ end }}
</head> </head>
<body class="{{ if .IsPage }}single-page{{ else }}list-page{{ end }}{{ if .IsHome }} front{{ end }}{{ with .Section }} section-page--{{ . | urlize }}{{ end }}{{ with $.Param "bodyclass" }} {{ . }}{{ end }}"> <body class="{{ if .IsPage }}single-page{{ else }}list-page{{ end }}{{ if .IsHome }} front{{ end }}{{ with .Section }} section-page--{{ . | urlize }}{{ end }}{{ with $.Param "bodyclass" }} {{ . }}{{ end }}">