diff --git a/.forgejo/workflows/update_publications_data.yaml b/.forgejo/workflows/update_publication_pages.yaml similarity index 52% rename from .forgejo/workflows/update_publications_data.yaml rename to .forgejo/workflows/update_publication_pages.yaml index c6ef4c8..d8720c5 100644 --- a/.forgejo/workflows/update_publications_data.yaml +++ b/.forgejo/workflows/update_publication_pages.yaml @@ -16,19 +16,11 @@ jobs: uses: actions/checkout@v4 - name: Prepare environment uses: ./.forgejo/actions/prep-metadata-query - - name: Install jq - run: | - apt-get update - apt-get install -y jq - - name: Update pages + - name: Update publication pages run: | dtc get-records ${{ env.DUMPTHINGS_APIURL }} public -C XYZPublication \ | qri inline-records -p about -p attributed_to -c public \ - | jq -sc '{pid: "xyzri:this-is-not-important", publications: .}' \ - | qri render-record page_templates/publications.json.j2 'data/publications.json' + | qri render-record page_templates/publication.md.j2 \ + 'content/{__pid_curie_reference}/_index.md' - name: Deposit changes - run: | - git add data - git diff --quiet --cached \ - && echo "Already up-to-date" \ - || ( git commit -m "chore: auto-generate content from metadata" && git push origin ) + uses: ./.forgejo/actions/deposit-changes diff --git a/content/publications/_index.md b/content/publications/_index.md deleted file mode 100644 index 7c38345..0000000 --- a/content/publications/_index.md +++ /dev/null @@ -1,3 +0,0 @@ ---- -title: Publications ---- \ No newline at end of file diff --git a/layouts/_partials/publication-item.html b/layouts/_partials/publication-item.html new file mode 100644 index 0000000..d4fe990 --- /dev/null +++ b/layouts/_partials/publication-item.html @@ -0,0 +1,67 @@ +
+ + +

+ + {{ .Title }} + + {{ if .Params.date }} + {{ $year := substr .Params.date 0 4 }} + + ({{ $year }}) + + {{ end }} + {{ with .Params.doi }} + + + DOI: {{ . }} + + + {{ end }} + {{ with .Params.kind }} + + {{ . | replaceRE "^.*:" "" }} + + {{ end }} +

+ + + {{ with .Params.author }} +
+ {{ range $i, $a := . }} + + {{ $a.given_name }} {{ $a.family_name }} + + {{ end }} +
+ {{ end }} + + + {{ with .Params.topic }} +
+ {{ range . }} + + {{ .display_label }} +   + {{ end }} +
+ {{ end }} + +
\ No newline at end of file diff --git a/layouts/publications/list.html b/layouts/publications/taxonomy.html similarity index 68% rename from layouts/publications/list.html rename to layouts/publications/taxonomy.html index 4dea629..9199550 100644 --- a/layouts/publications/list.html +++ b/layouts/publications/taxonomy.html @@ -9,6 +9,9 @@
- -
+ + + {{ $pubPages := where .Site.Pages "Section" "publications" }} + {{ $pubPagesTerms := where $pubPages "Kind" "term" }} +
+ {{ range $i, $p := $pubPagesTerms }} + {{ partial "publication-item.html" $p }} + {{ end }} +
- - diff --git a/page_templates/publication.md.j2 b/page_templates/publication.md.j2 new file mode 100644 index 0000000..cbfb886 --- /dev/null +++ b/page_templates/publication.md.j2 @@ -0,0 +1,59 @@ +{% macro taxonomy_terms(taxonomy, terms_list) -%} +{% if terms_list -%} +{{ taxonomy }}: +{% for el in terms_list -%} +{% if el.pid -%} + - {{ el.pid.split('/')[-1] }} +{% endif -%} +{% endfor -%} +{% endif -%} +{%- endmacro -%} + +{%- set doi = ( + __rec.identifiers + | selectattr('schema_type', 'equalto', 'dlthings:DOI') + | list + | first +) if __rec.identifiers is defined and __rec.identifiers is sequence else none -%} + +{%- set generation = ( + __rec.generated_by + | selectattr('object', 'equalto', 'obo:IAO_0000444') + | list + | first +) if __rec.generated_by is defined and __rec.generated_by is sequence else none -%} + +{%- set authors = [] -%} +{%- for auth in __rec.attributed_to -%} + {%- set _ = authors.append({ + "pid": auth.object.pid | default(none), + "given_name": auth.object.given_name | default(none), + "family_name": auth.object.family_name | default(none) + }) -%} +{%- endfor -%} + +{%- set topics = [] -%} +{%- for top in __rec.about -%} + {%- set _ = topics.append({ + "pid": top.pid | default(none), + "display_label": top.display_label | default(none) + }) -%} +{%- endfor -%} + +{%- set publication = { + "pid": __rec.pid | default(none), + "doi": doi.notation | default(none) if doi else none, + "date": generation.at_time | default(none) if generation else none, + "title": __rec.title | default(none), + "kind": __rec.kind | default(none), + "author": authors | default(none), + "topic": topics | default(none) +} -%} +--- +title: {{ publication.title | toyaml | replace('...\n','') | trim }} +{{ taxonomy_terms('persons', authors) -}} +{{ taxonomy_terms('topics', topics) -}} +params: + graphRootNodePID: {{ pid }} + {{ publication | toyaml | indent(2) | trim }} +--- diff --git a/static/publications.css b/static/publications.css index 58e1cd5..8c6ef31 100644 --- a/static/publications.css +++ b/static/publications.css @@ -18,6 +18,12 @@ max-height: calc(100vh - 4rem); overflow-y: auto; scroll-behavior: smooth; + display: flex; + flex-direction: column; +} + +.clear-button { + margin-left: auto; } .results-panel { @@ -49,4 +55,11 @@ color: #292524; padding-left: 0.5em; border-radius: 3px; + margin-bottom: 1em; + border: 1px solid grey; +} + +.topic-chip.active { + background-color: rgb(59 130 246 / 0.15); + color: rgb(59 130 246); } \ No newline at end of file diff --git a/static/publications.js b/static/publications.js index 9056ef8..8bbb1ce 100644 --- a/static/publications.js +++ b/static/publications.js @@ -1,13 +1,5 @@ -// Load data -const rawData = JSON.parse(document.getElementById("pub-data").textContent); - -// Normalize data -const publications = rawData.map(p => ({ - ...p, - year: p.date ? new Date(p.date).getFullYear() : 'unknown', - topics: (p.topic || []).map(t => t.display_label), - authors: (p.author || []).map(a => `${a.given_name} ${a.family_name}`) -})); +// Load data from all pre-rendered publication item divs +const publications = Array.from(document.querySelectorAll(".pub")); // State let state = { @@ -20,15 +12,13 @@ let state = { // Build filter options dynamically function getUniqueValues(field) { const values = new Set(); - - publications.forEach(p => { - if (Array.isArray(p[field])) { - p[field].forEach(v => values.add(v)); - } else if (p[field]) { - values.add(p[field]); + publications.forEach(el => { + if (field === "topic") { + getTopics(el).forEach(v => values.add(v)); + } else { + values.add(el.dataset[field]); } }); - return Array.from(values).sort(); } @@ -42,7 +32,6 @@ function formatValue(field, value) { return value } -// Render checkboxes for filters function renderFilter(containerId, field, values) { const container = document.getElementById(containerId); values.forEach(value => { @@ -52,6 +41,8 @@ function renderFilter(containerId, field, values) { const checkbox = document.createElement("input"); checkbox.type = "checkbox"; checkbox.value = value; + checkbox.dataset.field = field; + checkbox.id = id; checkbox.addEventListener("change", () => { if (checkbox.checked) { state[field].add(value); @@ -67,67 +58,118 @@ function renderFilter(containerId, field, values) { } // Filtering logic -function matchesFilters(p) { - // Kind - if (state.kind.size && !state.kind.has(p.kind)) return false; - // Year - if (state.year.size && !state.year.has(String(p.year))) return false; - // Topic (multi-value) +function matchesFilters(el) { + const kind = el.dataset.kind; + const year = el.dataset.year; + const topics = getTopics(el); + + // kind filter + if (state.kind.size && !state.kind.has(kind)) { + return false; + } + // year filter + if (state.year.size && !state.year.has(year)) { + return false; + } + // topic filter (multi-match) if (state.topic.size) { - const match = p.topics.some(t => state.topic.has(t)); + const match = topics.some(t => state.topic.has(t)); if (!match) return false; } return true; } -// Search -function matchesSearch(p) { +// Helper to get topics from an element +function getTopics(el) { + try { + return JSON.parse(el.dataset.topics || "[]").map(t => t.display_label); + } catch { + return []; + } +} + +// Search logic +function matchesSearch(el) { if (!state.search) return true; const text = ( - p.title + - " " + - p.kind + - " " + - p.topics.join(" ") + - " " + - p.authors.join(" ") + el.dataset.title + " " + + el.dataset.kind + " " + + el.dataset.year + " " + + el.dataset.topics + " " + + el.dataset.authors ).toLowerCase(); + return text.includes(state.search); } -// Render results +// Change display of divs based on filtering/searching function render() { - const container = document.getElementById("results"); - container.innerHTML = ""; - const filtered = publications.filter(p => - matchesFilters(p) && matchesSearch(p) - ); - if (filtered.length === 0) { - container.innerHTML = "

No results

"; - return; - } - filtered.forEach(p => { - const div = document.createElement("div"); - div.className = "pub"; - div.innerHTML = ` -

${p.title} ${p.year ? ' ('+p.year+')' : ''}

-

Authors: ${p.authors.join(", ")}

-

Kind: ${formatValue("kind", p.kind)}

-

Topics: ${p.topics.join(", ")}

- `; - container.appendChild(div); + let count = 0; + publications.forEach(el => { + const visible = + matchesFilters(el) && + matchesSearch(el); + if (visible) count+=1; + renderCount(count) + el.style.display = visible ? "" : "none"; }); } +// Count of searched+filtered publications +function renderCount(count) { + const countEl = document.getElementById('pub-count'); + countEl.innerHTML = `${count}` ; +} + +// Set checkbox if user clicks on topic pill +function selectFilter(field, value) { + const checkbox = document.querySelector( + `input[type="checkbox"][data-field="${field}"][value="${CSS.escape(value)}"]` + ); + if (!checkbox) return; + if (!checkbox.checked) { + checkbox.checked = true; + state[field].add(value); + render(); + } +} + +// Clear all +function clearAllFilters() { + // Reset state + state.kind.clear(); + state.topic.clear(); + state.year.clear(); + state.search = ""; + // Uncheck all checkboxes + document.querySelectorAll('input[type="checkbox"]').forEach(cb => { + cb.checked = false; + }); + // Clear search input + const searchInput = document.getElementById("search"); + if (searchInput) { + searchInput.value = ""; + } + // Re-render results + render(); +} + // On startup: -// Build filters +// 1) Build filters renderFilter("filter-kind", "kind", getUniqueValues("kind")); -renderFilter("filter-topic", "topic", getUniqueValues("topics")); +renderFilter("filter-topic", "topic", getUniqueValues("topic")); renderFilter("filter-year", "year", getUniqueValues("year").map(String)); -// Search input +// 2) Register search input document.getElementById("search").addEventListener("input", e => { state.search = e.target.value.toLowerCase(); render(); }); -// Initial render +// 3) Add topic click handlers +document.querySelectorAll(".topic-chip").forEach(el => { + el.addEventListener("click", () => { + const topic = el.dataset.topic; + selectFilter("topic", topic); + }); +}); +// 4) Initial render render(); \ No newline at end of file