From 8b3760a4b7fd5203ba291b94e67c10417131fb0f Mon Sep 17 00:00:00 2001 From: Lukas Heumos Date: Mon, 21 Sep 2026 13:55:24 +0200 Subject: [PATCH] Read language implementations from the registry and add a language filter --- assets/components/ecosystem.ts | 33 +++++++++++----- assets/main.scss | 4 ++ layouts/packages/list.html | 55 +++++++++++++++++++++++++-- layouts/partials/package-section.html | 3 ++ 4 files changed, 83 insertions(+), 12 deletions(-) diff --git a/assets/components/ecosystem.ts b/assets/components/ecosystem.ts index 608d9128..ca8a1a17 100644 --- a/assets/components/ecosystem.ts +++ b/assets/components/ecosystem.ts @@ -1,16 +1,18 @@ /** biome-ignore-all lint/style/noNonNullAssertion: easy to debug misspelling */ -// Ecosystem package registry: free-text search, one active category, one active tag. -// Categories and tags both come from the controlled vocabulary in the registry schema. +// Ecosystem package registry: free-text search, one active category, one active language, one active tag. +// Categories, languages and tags all come from the controlled vocabulary in the registry schema. export default function init(root: HTMLElement) { const input = root.querySelector("#eco-filter")! const chipRow = root.querySelector("#eco-chips")! + const languageRow = root.querySelector("#eco-languages")! const grid = root.querySelector("#eco-grid")! const counter = root.querySelector("#eco-count")! const empty = root.querySelector("#eco-empty")! const clearButton = root.querySelector("#eco-clear")! const cards = Array.from(grid.querySelectorAll(".eco-card")) let activeCategory: string | null = null + let activeLanguage: string | null = null let activeTag: string | null = null const apply = () => { @@ -20,21 +22,22 @@ export default function init(root: HTMLElement) { const visible = terms.every((term) => card.dataset.search!.includes(term)) && (!activeCategory || card.dataset.category === activeCategory) && + (!activeLanguage || card.dataset.languages!.includes(`|${activeLanguage}|`)) && (!activeTag || card.dataset.tags!.includes(`|${activeTag}|`)) card.hidden = !visible if (visible) shown += 1 } counter.textContent = String(shown) empty.hidden = shown !== 0 - clearButton.hidden = !terms.length && !activeCategory && !activeTag + clearButton.hidden = !terms.length && !activeCategory && !activeLanguage && !activeTag for (const tag of root.querySelectorAll(".eco-tag")) { tag.classList.toggle("is-active", tag.dataset.tag === activeTag) } } - const syncChips = () => { - for (const chip of chipRow.querySelectorAll(".eco-chip")) { - const isActive = chip.dataset.category === activeCategory + const syncChips = (row: HTMLElement, key: "category" | "language", active: string | null) => { + for (const chip of row.querySelectorAll(".eco-chip")) { + const isActive = (chip.dataset[key] || null) === active chip.classList.toggle("is-active", isActive) chip.setAttribute("aria-pressed", String(isActive)) } @@ -43,8 +46,10 @@ export default function init(root: HTMLElement) { const reset = () => { input.value = "" activeCategory = null + activeLanguage = null activeTag = null - syncChips() + syncChips(chipRow, "category", activeCategory) + syncChips(languageRow, "language", activeLanguage) apply() } @@ -61,8 +66,18 @@ export default function init(root: HTMLElement) { chipRow.addEventListener("click", (event) => { const chip = (event.target as HTMLElement).closest(".eco-chip") if (!chip) return - activeCategory = activeCategory === chip.dataset.category ? null : chip.dataset.category! - syncChips() + const category = chip.dataset.category || null + activeCategory = activeCategory === category ? null : category + syncChips(chipRow, "category", activeCategory) + apply() + }) + + languageRow.addEventListener("click", (event) => { + const chip = (event.target as HTMLElement).closest(".eco-chip") + if (!chip) return + const language = chip.dataset.language || null + activeLanguage = activeLanguage === language ? null : language + syncChips(languageRow, "language", activeLanguage) apply() }) diff --git a/assets/main.scss b/assets/main.scss index 90390dc5..e7ca0342 100644 --- a/assets/main.scss +++ b/assets/main.scss @@ -1220,6 +1220,10 @@ body { flex-wrap: wrap; gap: 0.4rem; margin: 1.25rem 0 2rem; + + &:has(+ .eco-chips) { + margin-bottom: 0.4rem; + } } .eco-chip { diff --git a/layouts/packages/list.html b/layouts/packages/list.html index ca5f2eb9..06d27cae 100644 --- a/layouts/packages/list.html +++ b/layouts/packages/list.html @@ -54,12 +54,20 @@

Ecosystem packages maintained by scverse community

{{ end }} {{/* Registry descriptions are plain text with the odd bit of markdown and hard line breaks. */}} {{ $desc := trim (.description | markdownify | plainify | htmlUnescape | replaceRE `\s+` " ") " " }} - {{ $search := lower (printf "%s %s %s %s %s" .name $desc (delimit $tags " ") $category .language) }} + {{ $langs := slice .language }} + {{ $bindingNames := slice }} + {{ range .bindings }} + {{ $langs = $langs | append .language }} + {{ $bindingNames = $bindingNames | append .name }} + {{ end }} + {{ $langs = uniq $langs }} + {{ $search := lower (printf "%s %s %s %s %s %s" .name $desc (delimit $tags " ") $category (delimit $langs " ") (delimit $bindingNames " ")) }} {{ $items = $items | append (merge . (dict "core" $isCore "normtags" $tags "category" $category "language" .language + "languages" $langs "desc" $desc "search" $search )) @@ -73,6 +81,23 @@

Ecosystem packages maintained by scverse community

{{ end }} {{ $categoryCounts = sort $categoryCounts "count" "desc" }} + {{/* Language row: the package's own language plus every language it has a binding for. */}} + {{ $languages := slice }} + {{ range $items }} + {{ range .languages }} + {{ if not (in $languages .) }} + {{ $languages = $languages | append . }} + {{ end }} + {{ end }} + {{ end }} + {{ $languageCounts := slice }} + {{ range $languages }} + {{ $language := . }} + {{ $count := len (where $items "languages" "intersect" (slice $language)) }} + {{ $languageCounts = $languageCounts | append (dict "name" $language "count" $count) }} + {{ end }} + {{ $languageCounts = sort $languageCounts "count" "desc" }} +

Ecosystem packages maintained by scverse community

-
+
{{ range $categoryCounts }}
+
+ + {{ range $languageCounts }} + + {{ end }} +
+
{{ range $i, $e := $items }} {{ $home := $e.project_home }} @@ -195,6 +237,12 @@

Ecosystem packages maintained by scverse community

) }} {{ end }} + {{ range $e.bindings }} + {{ $candidates = $candidates | append (dict + "icon" "eco-i-package" "label" .language "url" .url "title" .name + ) + }} + {{ end }} {{ with $e.publications }} {{ $doi := index . 0 }} {{ $url := cond (hasPrefix $doi "http") $doi (printf "https://doi.org/%s" $doi) }} @@ -218,6 +266,7 @@

Ecosystem packages maintained by scverse community

data-search="{{ $e.search }}" data-tags="|{{ delimit $e.normtags "|" }}|" data-category="{{ $e.category }}" + data-languages="|{{ delimit $e.languages "|" }}|" >
{{ with $e.logo }} @@ -259,7 +308,7 @@

{{ with $links }}