Layout template generalization #25

Merged
jsheunis merged 1 commit from generalize-templates into main 2026-05-28 10:24:29 +00:00
18 changed files with 368 additions and 484 deletions

View file

@ -31,3 +31,40 @@ Making node/edge styling changes currently need to be done in the JS sources.
The JS code reads the graph from `static/graph.json`. This file is generated by The JS code reads the graph from `static/graph.json`. This file is generated by
`code/pool2graph.py`. Choice of node and edge types to consider for the navigation `code/pool2graph.py`. Choice of node and edge types to consider for the navigation
graph is done at the top of this file. graph is done at the top of this file.
### Layout templates
Taxonomy list pages can be customized to present terms in different formats by specifying
configuration properties in the taxonomy-specific `_index.md` page front matter. Technically,
this is done by using the `layouts/taxonomy.html` template as the main entrypoint for a taxonomy
list page rendering, which in turn renders specific partials driven by configuration.
Any given taxonomy list page can be customized to do the following:
- show all terms vs only terms that have a metadata-generated `_index.md` page
- list items in a grid (with a depiction if available) vs list items vertically
- include vs exclude filtering functionality (inclusion assumes the vertical list layout)
- specify which filter fields to use (e.g. `kind`, `topic`, `year`), if filtering functionality is active
- specify which fields to include in the text search functionality, if filtering functionality is active
An example configuration for the `publications` taxonomy list (including defaults),
specified in `content/publications/_index.md`, is:
```yaml
---
title: Publications
params:
list_variant: vertical # grid (default) | vertical (if filter == true, list_variant is set to vertical)
items: generated # generated (default) | all
filter: true # false (default) | true
filter_fields: # no default
- kind
- topic
- year
search_fields: # default: kind, topic, year, author, title
- kind
- topic
- year
- author
- title
---
```

View file

@ -1,5 +1,9 @@
--- ---
title: Dataset title: Dataset
params:
filter: true
filter_fields:
- topic
--- ---
Browse datasets that originate in activities of the Psychoinformatics project, or those that have seen contributions by such activities. Browse datasets that originate in activities of the Psychoinformatics project, or those that have seen contributions by such activities.

View file

@ -0,0 +1,8 @@
---
title: Instruments
params:
filter: true
filter_fields:
- kind
- topic
---

View file

@ -1,5 +1,7 @@
--- ---
title: People title: People
params:
items: generated
--- ---
The following persons currently have or previously had a relationship with the Psychoniformatics project. The following persons currently have or previously had a relationship with the Psychoniformatics project.

View file

@ -0,0 +1,17 @@
---
title: Publications
params:
list_variant: vertical
items: all
filter: true
filter_fields:
- kind
- topic
- year
search_fields:
- kind
- topic
- year
- author
- title
---

View file

@ -1,95 +0,0 @@
<article
class="dataset mb-6 border border-neutral-200 dark:border-neutral-700"
data-kind='{{ if .Params.kind}}{{ .Params.kind }}{{ else }}unknown{{ end }}'
data-year="{{ if .Params.date_created }}{{ substr .Params.date_created 0 4 }}{{ else }}unknown{{ end }}"
data-topic='{{ if .Params.topic}}{{ .Params.topic | jsonify }}{{ else }}unknown{{ end }}'
data-author='{{ .Params.author | jsonify }}'
data-title="{{ .Title | lower }}"
style="border-radius: 5px; padding: 1em;"
>
<!-- Title, including year and DOI and publication kind -->
<h3 class="text-xl font-semibold text-neutral-900 dark:text-neutral-100" style="margin-top: 0;">
<a href="{{ .RelPermalink }}" style="text-decoration: unset;">
{{ .Title }}
</a>
{{ if .Params.date_created }}
{{ $year := substr .Params.date_created 0 4 }}
<span class="text-neutral-500 text-base font-normal">
({{ $year }})
</span>
{{ end }}
{{ with .Params.doi }}
<span class="mt-3 text-sm" style="border-left: 1px solid grey; padding-left: 0.5em;">
<a
href="https://doi.org/{{ . }}"
target="_blank"
class="text-primary-600"
style="text-decoration: unset;"
>
DOI: {{ . }}
</a>
</span>
{{ end }}
{{ with .Params.kind }}
<span class="mt-3 text-sm" style="font-style: italic; border-left: 1px solid grey; padding-left: 0.5em; margin-left: 0.5em;">
{{ . | replaceRE "^.*:" "" }}
</span>
{{ end }}
{{ if or .Params.source_code_url .Params.documentation_url}}
<span class="mt-3 text-xs" style="font-style: italic; border-left: 1px solid grey; padding-left: 0.5em; margin-left: 0.5em;"></span>
{{ with .Params.source_code_url }}
<a style="text-decoration: none; cursor:pointer;" target="_blank" href="{{ . }}" title="Source code">{{ partial "icon.html" "mdi-code-not-equal-variant"}}</a>
{{ end }}
{{ with .Params.documentation_url }}
<a style="text-decoration: none; cursor:pointer;" target="_blank" href="{{ . }}" title="Documentation">{{ partial "icon.html" "mdi-file-document"}}</a>
{{ end }}
{{ end }}
</h3>
<!-- Authors -->
{{ with .Params.author }}
<div class="mt-2 mb-2 flex flex-wrap gap-2">
{{ range $i, $a := . }}
<span class="text-sm px-2 py-1 rounded-full border border-neutral-200 dark:border-neutral-700 whitespace-nowrap">
{{ $a.given_name }} {{ $a.family_name }}
</span>
{{ end }}
</div>
{{ end }}
<!-- Topics -->
{{ with .Params.topic }}
<div class="mb-2 flex flex-wrap gap-2">
<span class="ms-1 border-primary-400 px-1 py-[1px] text-xs font-normal dark:border-primary-600">
<em>Topics:</em>
</span>
{{ range . }}
<span
class="topic-chip ms-1 rounded-md border border-primary-400 px-1 py-[1px] text-xs font-normal text-secondary-700 dark:border-primary-600 dark:text-secondary-400"
style="cursor: pointer;"
data-topic="{{ .display_label }}"
>
{{ .display_label }}
</span>
{{ end }}
</div>
{{ end }}
<!-- Licenses -->
{{ with .Params.license }}
<div class="mb-2 flex flex-wrap gap-2">
<span class="ms-1 border-primary-400 px-1 py-[1px] text-xs font-normal dark:border-primary-600">
<em>License:</em>
</span>
{{ range . }}
<span class="ms-1 rounded-md border border-primary-400 px-1 py-[1px] text-xs font-normal text-secondary-700 dark:border-primary-600 dark:text-secondary-400">
<a style="text-decoration: none;" target="_blank" href="{{ .url }}">{{ .label }}</a>
</span>
{{ end }}
</div>
{{ end }}
</article>

View file

@ -1,95 +0,0 @@
<article
class="instrument mb-6 border border-neutral-200 dark:border-neutral-700"
data-kind='{{ if .Params.kind}}{{ .Params.kind }}{{ else }}unknown{{ end }}'
data-year="{{ if .Params.date_created }}{{ substr .Params.date_created 0 4 }}{{ else }}unknown{{ end }}"
data-topic='{{ if .Params.topic}}{{ .Params.topic | jsonify }}{{ else }}unknown{{ end }}'
data-author='{{ .Params.author | jsonify }}'
data-title="{{ .Title | lower }}"
style="border-radius: 5px; padding: 1em;"
>
<!-- Title, including year and DOI and publication kind -->
<h3 class="text-xl font-semibold text-neutral-900 dark:text-neutral-100" style="margin-top: 0;">
<a href="{{ .RelPermalink }}" style="text-decoration: unset;">
{{ .Title }}
</a>
{{ if .Params.date_created }}
{{ $year := substr .Params.date_created 0 4 }}
<span class="text-neutral-500 text-base font-normal">
({{ $year }})
</span>
{{ end }}
{{ with .Params.doi }}
<span class="mt-3 text-sm" style="border-left: 1px solid grey; padding-left: 0.5em;">
<a
href="https://doi.org/{{ . }}"
target="_blank"
class="text-primary-600"
style="text-decoration: unset;"
>
DOI: {{ . }}
</a>
</span>
{{ end }}
{{ with .Params.kind }}
<span class="mt-3 text-sm" style="font-style: italic; border-left: 1px solid grey; padding-left: 0.5em; margin-left: 0.5em;">
{{ . | replaceRE "^.*:" "" }}
</span>
{{ end }}
{{ if or .Params.source_code_url .Params.documentation_url}}
<span class="mt-3 text-xs" style="font-style: italic; border-left: 1px solid grey; padding-left: 0.5em; margin-left: 0.5em;"></span>
{{ with .Params.source_code_url }}
<a style="text-decoration: none; cursor:pointer;" target="_blank" href="{{ . }}" title="Source code">{{ partial "icon.html" "mdi-code-not-equal-variant"}}</a>
{{ end }}
{{ with .Params.documentation_url }}
<a style="text-decoration: none; cursor:pointer;" target="_blank" href="{{ . }}" title="Documentation">{{ partial "icon.html" "mdi-file-document"}}</a>
{{ end }}
{{ end }}
</h3>
<!-- Authors -->
{{ with .Params.author }}
<div class="mt-2 mb-2 flex flex-wrap gap-2">
{{ range $i, $a := . }}
<span class="text-sm px-2 py-1 rounded-full border border-neutral-200 dark:border-neutral-700 whitespace-nowrap">
{{ $a.given_name }} {{ $a.family_name }}
</span>
{{ end }}
</div>
{{ end }}
<!-- Topics -->
{{ with .Params.topic }}
<div class="mb-2 flex flex-wrap gap-2">
<span class="ms-1 border-primary-400 px-1 py-[1px] text-xs font-normal dark:border-primary-600">
<em>Topics:</em>
</span>
{{ range . }}
<span
class="topic-chip ms-1 rounded-md border border-primary-400 px-1 py-[1px] text-xs font-normal text-secondary-700 dark:border-primary-600 dark:text-secondary-400"
style="cursor: pointer;"
data-topic="{{ .display_label }}"
>
{{ .display_label }}
</span>
{{ end }}
</div>
{{ end }}
<!-- Licenses -->
{{ with .Params.license }}
<div class="mb-2 flex flex-wrap gap-2">
<span class="ms-1 border-primary-400 px-1 py-[1px] text-xs font-normal dark:border-primary-600">
<em>License:</em>
</span>
{{ range . }}
<span class="ms-1 rounded-md border border-primary-400 px-1 py-[1px] text-xs font-normal text-secondary-700 dark:border-primary-600 dark:text-secondary-400">
<a style="text-decoration: none;" target="_blank" href="{{ .url }}">{{ .label }}</a>
</span>
{{ end }}
</div>
{{ end }}
</article>

View file

@ -1,67 +0,0 @@
<article
class="pub mb-6 border border-neutral-200 dark:border-neutral-700"
data-kind="{{ .Params.kind }}"
data-year="{{ if .Params.date }}{{ substr .Params.date 0 4 }}{{ else }}unknown{{ end }}"
data-topic='{{ .Params.topic | jsonify }}'
data-author='{{ .Params.author | jsonify }}'
data-title="{{ .Title | lower }}"
style="border-radius: 5px; padding: 1em;"
>
<!-- Title, including year and DOI and publication kind -->
<h3 class="text-xl font-semibold text-neutral-900 dark:text-neutral-100" style="margin-top: 0;">
<a href="{{ .RelPermalink }}" style="text-decoration: unset;">
{{ .Title }}
</a>
{{ if .Params.date }}
{{ $year := substr .Params.date 0 4 }}
<span class="text-neutral-500 text-base font-normal">
({{ $year }})
</span>
{{ end }}
{{ with .Params.doi }}
<span class="mt-3 text-sm" style="border-left: 1px solid grey; padding-left: 0.5em;">
<a
href="https://doi.org/{{ . }}"
target="_blank"
class="text-primary-600"
style="text-decoration: unset;"
>
DOI: {{ . }}
</a>
</span>
{{ end }}
{{ with .Params.kind }}
<span class="mt-3 text-xs" style="font-style: italic; border-left: 1px solid grey; padding-left: 0.5em; margin-left: 0.5em;">
{{ . | replaceRE "^.*:" "" }}
</span>
{{ end }}
</h3>
<!-- Authors -->
{{ with .Params.author }}
<div class="mt-2 flex flex-wrap gap-2">
{{ range $i, $a := . }}
<span class="text-sm px-2 py-1 rounded-full border border-neutral-200 dark:border-neutral-700 whitespace-nowrap">
{{ $a.given_name }} {{ $a.family_name }}
</span>
{{ end }}
</div>
{{ end }}
<!-- Topics -->
{{ with .Params.topic }}
<div class="flex flex-wrap gap-2" style="margin-top: 1em;">
{{ range . }}
<span
class="topic-chip text-xs bg-neutral-200 dark:bg-neutral-700 text-neutral-800 dark:text-neutral-200 cursor-pointer transition-colors duration-150 hover:bg-primary-100 dark:hover:bg-primary-900 hover:text-primary-700 dark:hover:text-primary-300"
style="border-radius: 4px; padding: 0.5em"
data-topic="{{ .display_label }}"
>
{{ .display_label }}
</span>&nbsp;
{{ end }}
</div>
{{ end }}
</article>

View file

@ -0,0 +1,47 @@
{{ $currentTaxonomy := .Data.Plural }}
{{ $singleTerm := .Data.Singular }}
{{ $filterFieldsDefault := (slice "kind" "year" "topic")}}
{{ $searchFieldsDefault := (slice "title" "kind" "year" "topic" "author")}}
{{ $filterFields := .Params.filter_fields | default $filterFieldsDefault }}
{{ $searchFields := .Params.search_fields | default $searchFieldsDefault }}
<div class="list-layout">
<!-- Sidebar -->
<aside class="filters-panel">
<button onclick="listApp.clearAllFilters()" class="clear-button mt-2 text-sm text-primary-600 hover:underline">
Clear all
</button>
{{ range $i, $ff := $filterFields }}
<div class="filter-block">
<strong>{{$ff | humanize}}</strong>
<div id="filter-{{$ff}}" class="filter-group"></div>
</div>
{{ end }}
</aside>
<!-- Main rendered content -->
<div class="results-panel">
<input type="text" id="search" placeholder="Search {{$currentTaxonomy}}..." class="search-bar" /> <span
id="{{$currentTaxonomy}}-count"></span>
<!-- Data -->
<div id="results">
{{ range .Pages }}
{{ $includeItems := .Params.items | default "generated" }}
{{ if or (ne $includeItems "generated") .Params.title }}
{{ partial "taxonomy-list-vertical-item.html" . }}
{{ end }}
{{ end }}
</div>
</div>
</div>
<!-- JS + CSS -->
<script src="/filter-list.js"></script>
<script>
const listApp = new FilterableList({
itemSelector: '.{{$singleTerm}}',
searchInputId: "search",
countId: "{{$currentTaxonomy}}-count",
filters: {{ $filterFields }},
searchFields: {{ $searchFields }},
})
</script>
<link rel="stylesheet" href="/filter-list.css">

View file

@ -0,0 +1,29 @@
{{ $includeItems := .Params.items | default "generated" }}
<section class="items-grid">
{{ range .Pages }}
{{ if or (ne $includeItems "generated") .Params.title }}
{{ $count := 0 }}
{{ $key := .Data.Term }}
{{ with index $.Data.Terms $key }}
{{ $count = .Count }}
{{ end }}
{{ $portrait := .Resources.GetMatch "*portrait*" }}
<a class="item-card"
href="{{ .Permalink }}">
<div class="item-depiction">
{{ with $portrait }}
<img src="{{ (.Fill "300x300 Top").RelPermalink }}" alt="{{ .Title }}">
{{ else }}
<div class="item-placeholder">
<span>{{ substr .Title 0 1 }}</span>
</div>
{{ end }}
</div>
<div class="item-title">
{{ .Title }} ({{ $count }})
</div>
</a>
{{ end }}
{{ end }}
</section>
<link rel="stylesheet" href="/grid-list.css">

View file

@ -0,0 +1,111 @@
<article
class="{{.Data.Singular }} px-3 mb-6 border rounded-md border-neutral-200 dark:border-neutral-700"
data-kind='{{ if .Params.kind}}{{ .Params.kind }}{{ else }}unknown{{ end }}'
data-year="{{ if .Params.date }}{{ substr .Params.date 0 4 }}{{ else }}unknown{{ end }}"
data-topic='{{ .Params.topic | jsonify }}'
data-author='{{ .Params.author | jsonify }}'
data-title="{{ .Title | lower }}"
>
{{ $depictions := .Resources.Match "depiction.*" }}
{{ $hasDepiction := gt (len $depictions) 0 }}
<table class="w-full m-0">
<tr>
<td class="align-middle">
<!-- Title, including year and DOI and publication kind -->
<h3 class="text-xl font-semibold text-neutral-900 dark:text-neutral-100" style="margin-top: 0;">
<a href="{{ .RelPermalink }}" style="text-decoration: unset;">
{{ .Title }}
</a>
{{ if .Params.date }}
{{ $year := substr .Params.date 0 4 }}
<span class="text-neutral-500 text-base font-normal">
({{ $year }})
</span>
{{ end }}
{{ with .Params.doi }}
<span class="mt-3 text-sm" style="border-left: 1px solid grey; padding-left: 0.5em;">
<a
href="https://doi.org/{{ . }}"
target="_blank"
class="text-primary-600"
style="text-decoration: unset;"
>
DOI: {{ . }}
</a>
</span>
{{ end }}
{{ with .Params.kind }}
<span class="mt-3 text-sm" style="font-style: italic; border-left: 1px solid grey; padding-left: 0.5em; margin-left: 0.5em;">
{{ . | replaceRE "^.*:" "" }}
</span>
{{ end }}
{{ if or .Params.source_code_url .Params.documentation_url}}
<span class="mt-3 text-xs" style="font-style: italic; border-left: 1px solid grey; padding-left: 0.5em; margin-left: 0.5em;"></span>
{{ with .Params.source_code_url }}
<a style="text-decoration: none; cursor:pointer;" target="_blank" href="{{ . }}" title="Source code">{{ partial "icon.html" "mdi-code-not-equal-variant"}}</a>
{{ end }}
{{ with .Params.documentation_url }}
<a style="text-decoration: none; cursor:pointer;" target="_blank" href="{{ . }}" title="Documentation">{{ partial "icon.html" "mdi-file-document"}}</a>
{{ end }}
{{ end }}
</h3>
<!-- Authors -->
{{ with .Params.author }}
<div class="mt-2 flex flex-wrap gap-1">
{{ range $i, $a := . }}
<span class="text-sm px-2 py-1 rounded-full border border-neutral-200 dark:border-neutral-700 whitespace-nowrap">
{{ $a.given_name }} {{ $a.family_name }}
</span>
{{ end }}
</div>
{{ end }}
<!-- Topics -->
{{ with .Params.topic }}
<div class="flex flex-wrap gap-1" style="margin-top: 1em;">
<span class="ms-1 border-primary-400 px-1 py-[1px] text-xs font-normal dark:border-primary-600">
<em>Topics:</em>
</span>
{{ range . }}
<span
class="topic-chip ms-1 rounded-md border border-primary-400 px-1 py-[1px] text-xs font-normal text-secondary-700 dark:border-primary-600 dark:text-secondary-400"
data-topic="{{ .display_label }}"
>
<a style="text-decoration: none;">{{ .display_label }}</a>
</span>&nbsp;
{{ end }}
</div>
{{ end }}
<!-- Licenses -->
{{ with .Params.license }}
<div class="mb-2 flex flex-wrap gap-1" style="margin-top: 1em;">
<span class="ms-1 border-primary-400 px-1 py-[1px] text-xs font-normal dark:border-primary-600">
<em>License:</em>
</span>
{{ range . }}
<span class="ms-1 rounded-md border border-primary-400 px-1 py-[1px] text-xs font-normal text-secondary-700 dark:border-primary-600 dark:text-secondary-400">
<a style="text-decoration: none;" target="_blank" href="{{ .url }}">{{ .label }}</a>
</span>
{{ end }}
</div>
{{ end }}
</td>
{{ if $hasDepiction }}
<td width="20%" class="align-middle text-center border-l-1 border-neutral-200 dark:border-neutral-700">
{{ with index $depictions 0 }}
{{ if eq .MediaType.SubType "svg" }}
<img src="{{ .RelPermalink }}" style="margin: 0;" alt="depiction of instrumet">
{{ else }}
<img src="{{ (.Fill "300x300 Center").RelPermalink }}" alt="depiction of instrumet">
{{ end }}
{{ end }}
</td>
{{ end }}
</tr>
</table>
</article>

View file

@ -0,0 +1,13 @@
{{ $includeItems := .Params.items | default "generated" }}
<div class="list-layout">
<div class="results-panel">
<div id="results">
{{ range .Pages }}
{{ if or (ne $includeItems "generated") .Params.title }}
{{ partial "taxonomy-list-vertical-item.html" . }}
{{ end }}
{{ end }}
</div>
</div>
</div>
<link rel="stylesheet" href="/filter-list.css">

View file

@ -1,67 +0,0 @@
{{ define "main" }}
<header>
{{ if .Params.showBreadcrumbs | default (.Site.Params.list.showBreadcrumbs | default false) }}
{{ partial "breadcrumbs.html" . }}
{{ end }}
<h1 class="mt-0 text-4xl font-extrabold text-neutral-900 dark:text-neutral">{{ .Title }}</h1>
</header>
{{ if .Content }}
<section class="prose mt-0 flex max-w-full flex-col dark:prose-invert lg:flex-row">
<div class="min-h-0 min-w-0 max-w-prose grow">
{{ .Content | emojify }}
</div>
</section>
{{ end }}
<article class="prose max-w-full dark:prose-invert">
<div class="list-layout">
<!-- Sidebar -->
<aside class="filters-panel">
<button onclick="listApp.clearAllFilters()" class="clear-button mt-2 text-sm text-primary-600 hover:underline">
Clear all
</button>
<div class="filter-block">
<strong>Kind</strong>
<div id="filter-kind" class="filter-group"></div>
</div>
<div class="filter-block">
<strong>Topic</strong>
<div id="filter-topic" class="filter-group"></div>
</div>
</aside>
<!-- Main rendered content -->
<div class="results-panel">
<input type="text" id="search" placeholder="Search datasets..." class="search-bar" /> <span id="dataset-count"></span>
<!-- Data -->
{{ $datasetPages := where .Site.Pages "Section" "datasets" }}
{{ $datasetPagesTerms := where $datasetPages "Kind" "term" }}
<div id="results">
{{ range $i, $p := $datasetPagesTerms }}
{{ partial "dataset-item.html" $p }}
{{ end }}
</div>
</div>
</div>
<!-- JS + CSS -->
<script src="/filter-list.js"></script>
<script>
const listApp = new FilterableList({
itemSelector: '.dataset',
searchInputId: "search",
countId: "dataset-count",
filters: [
'kind',
'topic',
],
searchFields: [
"title",
"kind",
"year",
"topic",
"author"
],
})
</script>
<link rel="stylesheet" href="/filter-list.css">
</article>
{{ end }}

View file

@ -1,67 +0,0 @@
{{ define "main" }}
<header>
{{ if .Params.showBreadcrumbs | default (.Site.Params.list.showBreadcrumbs | default false) }}
{{ partial "breadcrumbs.html" . }}
{{ end }}
<h1 class="mt-0 text-4xl font-extrabold text-neutral-900 dark:text-neutral">{{ .Title }}</h1>
</header>
{{ if .Content }}
<section class="prose mt-0 flex max-w-full flex-col dark:prose-invert lg:flex-row">
<div class="min-h-0 min-w-0 max-w-prose grow">
{{ .Content | emojify }}
</div>
</section>
{{ end }}
<article class="prose max-w-full dark:prose-invert">
<div class="list-layout">
<!-- Sidebar -->
<aside class="filters-panel">
<button onclick="listApp.clearAllFilters()" class="clear-button mt-2 text-sm text-primary-600 hover:underline">
Clear all
</button>
<div class="filter-block">
<strong>Kind</strong>
<div id="filter-kind" class="filter-group"></div>
</div>
<div class="filter-block">
<strong>Topic</strong>
<div id="filter-topic" class="filter-group"></div>
</div>
</aside>
<!-- Main rendered content -->
<div class="results-panel">
<input type="text" id="search" placeholder="Search instruments..." class="search-bar" /> <span id="instrument-count"></span>
<!-- Data -->
{{ $instrumentPages := where .Site.Pages "Section" "instruments" }}
{{ $instrumentPagesTerms := where $instrumentPages "Kind" "term" }}
<div id="results">
{{ range $i, $p := $instrumentPagesTerms }}
{{ partial "instrument-item.html" $p }}
{{ end }}
</div>
</div>
</div>
<!-- JS + CSS -->
<script src="/filter-list.js"></script>
<script>
const listApp = new FilterableList({
itemSelector: '.instrument',
searchInputId: "search",
countId: "instrument-count",
filters: [
'kind',
'topic',
],
searchFields: [
"title",
"kind",
"year",
"topic",
"author"
],
})
</script>
<link rel="stylesheet" href="/filter-list.css">
</article>
{{ end }}

View file

@ -1,73 +0,0 @@
{{ define "main" }}
<header>
{{ if .Params.showBreadcrumbs | default (.Site.Params.list.showBreadcrumbs | default false) }}
{{ partial "breadcrumbs.html" . }}
{{ end }}
<h1 class="mt-0 text-4xl font-extrabold text-neutral-900 dark:text-neutral">{{ .Title }}</h1>
</header>
{{ if .Content }}
<section class="prose mt-0 flex max-w-full flex-col dark:prose-invert lg:flex-row">
<div class="min-h-0 min-w-0 max-w-prose grow">
{{ .Content | emojify }}
</div>
</section>
{{ end }}
<article class="prose max-w-full dark:prose-invert">
<div class="list-layout">
<!-- Sidebar -->
<aside class="filters-panel">
<button onclick="listApp.clearAllFilters()" class="clear-button mt-2 text-sm text-primary-600 hover:underline">
Clear all
</button>
<div class="filter-block">
<strong>Kind</strong>
<div id="filter-kind" class="filter-group"></div>
</div>
<div class="filter-block">
<strong>Topic</strong>
<div id="filter-topic" class="filter-group"></div>
</div>
<div class="filter-block">
<strong>Year</strong>
<div id="filter-year" class="filter-group"></div>
</div>
</aside>
<!-- Main rendered content -->
<div class="results-panel">
<input type="text" id="search" placeholder="Search publications..." class="search-bar" /> <span id="pub-count"></span>
<!-- Data -->
{{ $pubPages := where .Site.Pages "Section" "publications" }}
{{ $pubPagesTerms := where $pubPages "Kind" "term" }}
<div id="results">
{{ range $i, $p := $pubPagesTerms }}
{{ partial "publication-item.html" $p }}
{{ end }}
</div>
</div>
</div>
<!-- JS + CSS -->
<script src="/filter-list.js"></script>
<script>
const listApp = new FilterableList({
itemSelector: '.pub',
searchInputId: "search",
countId: "pub-count",
filters: [
'kind',
'topic',
'year'
],
searchFields: [
"title",
"kind",
"year",
"topic",
"author"
],
})
</script>
<link rel="stylesheet" href="/filter-list.css">
</article>
{{ end }}

View file

@ -12,25 +12,15 @@
</div> </div>
</section> </section>
{{ end }} {{ end }}
<section class="-mx-2 flex flex-wrap overflow-hidden"> <article class="prose max-w-full dark:prose-invert">
{{ range .Data.Terms }} {{ if and (isset .Params "filter") (eq .Params.filter true) }}
{{ if .Page.Params.title }} {{ partial "taxonomy-list-filter.html" . }}
<article class="my-3 w-full overflow-hidden px-2 sm:w-1/2 md:w-1/3 lg:w-1/4 xl:w-1/5"> {{ else }}
<h2 class="flex items-center"> {{ if and (isset .Params "list_variant") (eq .Params.list_variant "vertical") }}
<a {{ partial "taxonomy-list-vertical.html" . }}
class="text-xl font-medium decoration-primary-500 hover:underline hover:underline-offset-2" {{ else }}
href="{{ .Page.Permalink }}" {{ partial "taxonomy-list-grid.html" . }}
>{{ .Page.Title }}</a {{ end }}
>
{{ if $.Site.Params.taxonomy.showTermCount | default true }}
<span class="px-2 text-base text-primary-500">&middot;</span>
<span class="text-base text-neutral-400">
{{ .Count }}
</span>
{{ end }} {{ end }}
</h2>
</article> </article>
{{ end }} {{ end }}
{{ end }}
</section>
{{ end }}

View file

@ -59,6 +59,10 @@
border: 1px solid grey; border: 1px solid grey;
} }
.topic-chip {
cursor: pointer
}
.topic-chip.active { .topic-chip.active {
background-color: rgb(59 130 246 / 0.15); background-color: rgb(59 130 246 / 0.15);
color: rgb(59 130 246); color: rgb(59 130 246);

86
static/grid-list.css Normal file
View file

@ -0,0 +1,86 @@
/* Grid */
.items-grid {
display: grid;
grid-template-columns: repeat(4, 1fr);
gap: 1rem;
}
/* Card */
.item-card {
text-decoration: none;
color: inherit;
display: flex;
flex-direction: column;
border-radius: 10px;
overflow: hidden;
border: 1px solid rgba(0,0,0,0.07);
background: #fff;
transition: box-shadow 0.2s, transform 0.2s;
}
.item-card:hover {
box-shadow: 0 4px 16px rgba(0,0,0,0.1);
transform: translateY(-2px);
}
html.dark .item-card {
background: #242424;
border-color: rgba(255,255,255,0.07);
}
html.dark .item-card:hover {
box-shadow: 0 4px 16px rgba(0,0,0,0.3);
}
.item-depiction {
width: 100%;
aspect-ratio: 1;
overflow: hidden;
background: #e8e8e8;
}
html.dark .item-depiction { background: #333; }
.item-depiction img {
width: 100%;
height: 100%;
object-fit: cover;
display: block;
}
.item-placeholder {
width: 100%;
height: 100%;
display: flex;
align-items: center;
justify-content: center;
font-size: 2.5rem;
font-weight: 700;
color: #aaa;
background: repeating-linear-gradient(
45deg,
#ddd,
#ddd 10px,
#e8e8e8 10px,
#e8e8e8 20px
);
}
html.dark .item-placeholder {
background: repeating-linear-gradient(
45deg,
#2a2a2a,
#2a2a2a 10px,
#333 10px,
#333 20px
);
color: #555;
}
.item-title {
padding: 0.6rem 0.75rem;
font-size: 0.85rem;
font-weight: 600;
line-height: 1.3;
text-align: center;
}
@media (max-width: 900px) {
.items-grid { grid-template-columns: repeat(3, 1fr); }
}
@media (max-width: 600px) {
.items-grid { grid-template-columns: repeat(2, 1fr); }
}