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
`code/pool2graph.py`. Choice of node and edge types to consider for the navigation
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
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.

View file

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

View file

@ -1,5 +1,7 @@
---
title: People
params:
items: generated
---
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>
</section>
{{ end }}
<section class="-mx-2 flex flex-wrap overflow-hidden">
{{ range .Data.Terms }}
{{ if .Page.Params.title }}
<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">
<h2 class="flex items-center">
<a
class="text-xl font-medium decoration-primary-500 hover:underline hover:underline-offset-2"
href="{{ .Page.Permalink }}"
>{{ .Page.Title }}</a
>
{{ 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 }}
</h2>
</article>
<article class="prose max-w-full dark:prose-invert">
{{ if and (isset .Params "filter") (eq .Params.filter true) }}
{{ partial "taxonomy-list-filter.html" . }}
{{ else }}
{{ if and (isset .Params "list_variant") (eq .Params.list_variant "vertical") }}
{{ partial "taxonomy-list-vertical.html" . }}
{{ else }}
{{ partial "taxonomy-list-grid.html" . }}
{{ end }}
{{ end }}
</section>
{{ end }}
</article>
{{ end }}

View file

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