Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion assets/css/tailwind.css

Large diffs are not rendered by default.

3 changes: 1 addition & 2 deletions content/calendar/ohio-powershell-user-group.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,5 +8,4 @@ virtual: true
---
The Ohio PowerShell User Group puts common PowerShell wisdom to the test —
benchmarking competing approaches and exploring performance, the pipeline,
parallel execution, and output behavior. See the
[group page](/user-groups/ohio-powershell/) for more.
parallel execution, and output behavior.
Comment thread
HeyItsGilbert marked this conversation as resolved.
Resolved
41 changes: 41 additions & 0 deletions docs/adr/0006-calendar-user-group-linking.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
# Calendar entries link back to their user group by matching the Meetup URL host at render time, not an explicit stored field

Calendar entries (`content/calendar/meetup-*.md`) are generated by
`.github/scripts/sync-meetup-events.mjs` from each user group's
`links[].url` on `meetup.com`; manual entries like
`ohio-powershell-user-group.md` also happen to carry a `meetup.com`
`externalUrl`. To show a group's logo/name badge on the calendar page and
link it back to `/user-groups/<slug>/`, the template
(`themes/powershell-community/layouts/_default/calendar.html`) matches each
entry's `externalUrl` host against every user group's `links[].url` host at
render time, rather than the sync script stamping a `userGroup: <slug>`
field into generated frontmatter.

## Considered options

- **Explicit `userGroup` field, written by the sync script** — rejected (for
now). More "obviously correct" in the abstract, but the script currently
discards the per-group URL once it flattens all groups' Meetup slugs into
one set (`groups()` in `sync-meetup-events.mjs`), so this would mean
threading that mapping through, resyncing/backfilling the 13 existing
generated entries, and still leaving manual entries unmatched unless an
author remembers to set the field by hand.
- **Runtime match on Meetup URL host** — chosen. The join key (the Meetup
group slug embedded in both the calendar entry's `externalUrl` and the
user group's `links[].url`) is already 100% reliable across every existing
entry, synced or manual, with no script change and no backfill. It also
means Ohio's hand-authored entry matches for free, since its
`externalUrl` happens to be a `meetup.com` link too.

## Consequences

- Linking is **implicitly scoped to Meetup-sourced events.** A future
non-Meetup user-group event (e.g. Eventbrite) will render with no group
badge unless this matching logic is extended to other hosts, or unless we
revisit this decision in favor of an explicit field.
- The match is **deliberately on URL host, not on a link's declared `icon` or
`name`.** A user group that mislabels its Meetup link's icon still matches
correctly, because the template never looks at it.
- The calendar template now has to enumerate `/user-groups/` pages per
rendered event to find a match. This is a cheap, build-time cost (list
sizes are small), not a runtime one — Hugo is a static site generator.
3 changes: 3 additions & 0 deletions purgecss.config.cjs
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,9 @@ module.exports = {
// These never appear in committed markup, so pin the whole status palette.
'bg-blue-500', 'bg-green-500', 'bg-purple-500',
'bg-orange-500', 'bg-red-500', 'bg-yellow-500',
// Only rendered inside the calendar's upcoming-events loop, so a CI
// build with zero upcoming events would otherwise purge it.
'group-hover:text-purple-600',
],
},
output: 'assets/css/tailwind.css',
Expand Down
27 changes: 20 additions & 7 deletions themes/powershell-community/layouts/_default/calendar.html
Original file line number Diff line number Diff line change
Expand Up @@ -52,8 +52,8 @@ <h2 class="text-3xl font-bold text-gray-900">Upcoming Events</h2>
{{ if $upcoming }}
<div class="space-y-5 mb-10">
{{ range $upcoming }}
<a href="{{ .Params.externalUrl }}" target="_blank" rel="noopener"
class="block bg-gray-50 rounded-xl border border-gray-200 hover:border-purple-400 hover:shadow-md transition-all duration-200 overflow-hidden">
{{ $matchedGroup := partial "calendar-matched-user-group.html" (dict "externalUrl" .Params.externalUrl "site" $.Site) }}
<div class="bg-gray-50 rounded-xl border border-gray-200 hover:border-purple-400 hover:shadow-md transition-all duration-200 overflow-hidden">
<div class="md:flex">
<!-- Date Block -->
<div class="md:w-36 bg-purple-600 text-white p-6 flex flex-col items-center justify-center text-center flex-shrink-0">
Expand All @@ -64,8 +64,10 @@ <h2 class="text-3xl font-bold text-gray-900">Upcoming Events</h2>
</div>
<!-- Event Details -->
<div class="p-6 flex-1">
<h3 class="text-xl font-bold text-gray-900 mb-2">{{ .Title }}</h3>
<p class="text-gray-600 mb-3">{{ .Summary | plainify | truncate 240 }}</p>
<a href="{{ .Params.externalUrl }}" target="_blank" rel="noopener" class="group block">
<h3 class="text-xl font-bold text-gray-900 mb-2 underline group-hover:text-purple-600">{{ .Title }}</h3>
<p class="text-gray-600 mb-3">{{ .Summary | plainify | truncate 240 }}</p>
</a>
<div class="flex flex-wrap items-center gap-4 text-sm text-gray-500">
<div class="flex items-center">
<i class="fas fa-map-marker-alt text-purple-600 mr-2"></i>
Expand All @@ -87,14 +89,21 @@ <h3 class="text-xl font-bold text-gray-900 mb-2">{{ .Title }}</h3>
Virtual
</div>
{{ end }}
{{ with $matchedGroup }}
<div class="flex items-center">
{{ partial "calendar-user-group-badge.html" . }}
</div>
{{ end }}
</div>
</div>
<!-- Arrow -->
<div class="hidden md:flex items-center pr-6">
<i class="fas fa-external-link-alt text-gray-400"></i>
<a href="{{ .Params.externalUrl }}" target="_blank" rel="noopener" aria-label="View details for {{ .Title }}">
<i class="fas fa-external-link-alt text-gray-400"></i>
</a>
</div>
</div>
</a>
</div>
{{ end }}
</div>
{{ else }}
Expand All @@ -115,7 +124,8 @@ <h2 class="text-3xl font-bold text-gray-900">Past Events</h2>
</div>
<div class="space-y-4 mb-10">
{{ range (sort $past "Params.startDate" "desc") }}
<div class="bg-gray-50 rounded-lg border border-gray-200 p-4 opacity-75">
{{ $matchedGroup := partial "calendar-matched-user-group.html" (dict "externalUrl" .Params.externalUrl "site" $.Site) }}
<div class="bg-gray-50 rounded-lg border border-gray-200 p-4">
<div class="flex items-center justify-between">
<div>
<h3 class="font-semibold text-gray-700">{{ .Title }}</h3>
Expand All @@ -125,6 +135,9 @@ <h3 class="font-semibold text-gray-700">{{ .Title }}</h3>
{{ $startDate := time .Params.startDate }}
{{ $startDate.Format "January 2, 2006" }}
</span>
{{ with $matchedGroup }}
<span>{{ partial "calendar-user-group-badge.html" . }}</span>
Comment thread
Copilot marked this conversation as resolved.
Resolved
{{ end }}
</div>
</div>
{{ if .Params.externalUrl }}
Expand Down
6 changes: 6 additions & 0 deletions themes/powershell-community/layouts/_default/single.html
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,12 @@

<!-- Title -->
<h1 class="text-3xl lg:text-4xl font-bold text-gray-900 mb-4 leading-tight">{{ .Title }}</h1>
{{ if eq .Section "calendar" }}
{{ $matchedGroup := partial "calendar-matched-user-group.html" (dict "externalUrl" .Params.externalUrl "site" .Site) }}
{{ with $matchedGroup }}
<div class="mb-4">{{ partial "calendar-user-group-badge.html" . }}</div>
{{ end }}
{{ end }}

<!-- Author and Reading Time -->
<div class="flex items-center justify-between">
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
{{/*
Resolves a calendar entry's Meetup externalUrl to the user group that owns
it, by matching the Meetup group slug embedded in the calendar entry's URL
against the slug embedded in each user group's `links[].url`. See
docs/adr/0006-calendar-user-group-linking.md for why this is a runtime
match rather than a stored field.

@param .externalUrl string: the calendar entry's `externalUrl` param
@param .site Site: the site context, for GetPage
@returns Page|string: the matched user-group page, or "" if none
*/}}
{{- $url := .externalUrl -}}
{{- $site := .site -}}
{{- /* Case-insensitive on scheme+host only: a locale prefix is only
recognized in Meetup's own convention (lowercase language, UPPERCASE
region, e.g. "de-DE") so it isn't confused with a lowercase two-letter-
pair group slug like "ps-ug". */ -}}
{{- $slugPattern := `^(?i:https?://(?:www\.)?meetup\.com)/(?:[a-z]{2}-[A-Z]{2}/)?([^/?#]+).*$` -}}
{{- $match := "" -}}
{{- if $url -}}
{{- $eventSlug := replaceRE $slugPattern "$1" $url -}}
{{- if ne $eventSlug $url -}}
{{- $groupsPage := $site.GetPage "/user-groups" -}}
{{- if $groupsPage -}}
{{- range $groupsPage.Pages -}}
{{- $group := . -}}
{{- range .Params.links -}}
{{- $linkSlug := replaceRE $slugPattern "$1" .url -}}
{{- if and (ne $linkSlug .url) (eq (lower $linkSlug) (lower $eventSlug)) -}}
{{- $match = $group -}}
{{- end -}}
{{- end -}}
{{- end -}}
{{- end -}}
{{- end -}}
{{- end -}}
{{- return $match -}}
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
{{/*
Renders a small logo/icon + name badge linking to a user group's page.

@param . Page: the user-group page to render a badge for
*/}}
{{- $group := . -}}
<a href="{{ $group.RelPermalink }}" class="inline-flex items-center gap-1.5 text-blue-600 hover:text-blue-700 underline">
<span class="inline-flex items-center justify-center w-5 h-5 rounded-full bg-blue-100 overflow-hidden flex-shrink-0">
{{ if $group.Params.logo }}
<img src="{{ $group.Params.logo }}" alt="" width="20" height="20" class="w-full h-full object-contain">
{{ else }}
<i class="fas fa-people-group text-xs" aria-hidden="true"></i>
{{ end }}
</span>
{{ $group.Title }}
</a>
Loading