From f2768ea6625ebb3bca41a626b9496357ac03d696 Mon Sep 17 00:00:00 2001 From: peer35 Date: Tue, 16 Jun 2026 12:20:02 +0200 Subject: [PATCH] glossary terms from folder --- _extensions/glossary/_extension.yml | 6 ++ _extensions/glossary/glossary.lua | 97 +++++++++++++++++++++++++ _quarto.yml | 3 + custom.scss | 39 +++++++++- glossary/mfa.qmd | 5 ++ glossary/persistent-identifier.qmd | 20 ++++++ glossary/rsm.qmd | 5 ++ glossary/surf.qmd | 4 ++ guides/onboarding.qmd | 2 +- guides/process-and-analyse.qmd | 2 +- js/glossary-popup.js | 108 ++++++++++++++++++++++++++++ 11 files changed, 288 insertions(+), 3 deletions(-) create mode 100644 _extensions/glossary/_extension.yml create mode 100644 _extensions/glossary/glossary.lua create mode 100644 glossary/mfa.qmd create mode 100644 glossary/persistent-identifier.qmd create mode 100644 glossary/rsm.qmd create mode 100644 glossary/surf.qmd create mode 100644 js/glossary-popup.js diff --git a/_extensions/glossary/_extension.yml b/_extensions/glossary/_extension.yml new file mode 100644 index 000000000..686c84468 --- /dev/null +++ b/_extensions/glossary/_extension.yml @@ -0,0 +1,6 @@ +title: Glossary +author: VU Amsterdam +version: 1.0.0 +contributes: + shortcodes: + - glossary.lua diff --git a/_extensions/glossary/glossary.lua b/_extensions/glossary/glossary.lua new file mode 100644 index 000000000..1e099211a --- /dev/null +++ b/_extensions/glossary/glossary.lua @@ -0,0 +1,97 @@ +-- glossary.lua – `{{< glossary term-id >}}` shortcode +-- +-- Renders a link to /glossary/.html with the term's title and +-- description attached as data attributes, which js/glossary-popup.js uses +-- to show a hover/click popup. Term pages live in glossary/.qmd: the +-- page title is the term, an optional `aliases` metadata field lists +-- alternative spellings, and the page body is the description. +-- +-- Usage: For logging in {{< glossary mfa >}} is mandatory. +-- +-- Peter Vos, 2026-06-16 + +local stringify = pandoc.utils.stringify + +--- Resolve the glossary/ directory relative to the project root so the +--- shortcode works the same regardless of which subdirectory the current +--- file lives in. +local function glossary_dir() + if quarto.project.directory then + return quarto.project.directory .. '/glossary' + end + return 'glossary' +end + +--- Read every glossary/*.qmd file once and cache id -> { id, title, description }. +local terms = nil + +local function load_glossary() + local index = {} + local dir = glossary_dir() + local ok, files = pcall(pandoc.system.list_directory, dir) + if not ok or not files then + return index + end + + for _, file in ipairs(files) do + local id = file:match '^(.*)%.qmd$' or file:match '^(.*)%.md$' + if id then + local fh = io.open(dir .. '/' .. file, 'r') + if fh then + local text = fh:read '*a' + fh:close() + + local doc = pandoc.read(text, 'markdown') + local title = doc.meta.title and stringify(doc.meta.title) or id + + local parts = {} + for _, block in ipairs(doc.blocks) do + local part = stringify(block) + if part ~= '' then + table.insert(parts, part) + end + end + + local term = { id = id, title = title, description = table.concat(parts, ' ') } + index[id:lower()] = term + + if doc.meta.aliases then + for _, alias in ipairs(doc.meta.aliases) do + local alias_id = stringify(alias):lower() + if not index[alias_id] then + index[alias_id] = term + end + end + end + end + end + end + + return index +end + +return { + ['glossary'] = function(args, kwargs, meta) + if terms == nil then + terms = load_glossary() + end + + if #args == 0 then + return quarto.shortcode.error_output('glossary', 'missing term id', 'inline') + end + + local id = stringify(args[1]):lower() + local term = terms[id] + if not term then + quarto.log.warning("glossary shortcode: unknown term '" .. id .. "'") + return quarto.shortcode.error_output('glossary', "unknown term '" .. id .. "'", 'inline') + end + + local link = pandoc.Link({ pandoc.Str(term.title) }, '/glossary/' .. term.id .. '.html') + link.classes = { 'glossary-link' } + link.attributes['data-glossary-title'] = term.title + link.attributes['data-glossary-description'] = term.description + link.attributes['tabindex'] = '0' + return link + end +} diff --git a/_quarto.yml b/_quarto.yml index eaf7d06df..bf347aae5 100644 --- a/_quarto.yml +++ b/_quarto.yml @@ -57,6 +57,7 @@ language: resources: - "public/*" + - "js/*" - CNAME format: html: @@ -72,6 +73,8 @@ format:
Research Support Handbook
include-in-header: text: + include-after-body: + text: # from: "markdown+emoji" filters: - utils/include-files.lua diff --git a/custom.scss b/custom.scss index e5afda389..896db111f 100644 --- a/custom.scss +++ b/custom.scss @@ -277,7 +277,44 @@ footer.footer .nav-footer { .listing-image { display: flex; align-items: center; - justify-content: center; + justify-content: center; } } /* tools, topics and guides listing and accent color */ + +/* glossary links and popup */ +a.glossary-link { + text-decoration: underline dotted; + text-decoration-color: #0282c9; + text-underline-offset: 2px; + cursor: help; +} + +.glossary-tooltip { + position: absolute; + z-index: 1060; + max-width: 320px; + background-color: #1c1c1c; + color: white; + border-radius: .25rem; + padding: .6em .8em; + font-size: .85rem; + line-height: 1.4; + box-shadow: 0 .25rem .75rem rgba(0, 0, 0, .25); + visibility: hidden; + opacity: 0; + transition: opacity .1s ease-in-out; + pointer-events: none; +} + +.glossary-tooltip.visible { + visibility: visible; + opacity: 1; +} + +.glossary-tooltip-title { + display: block; + margin-bottom: .25em; + color: #6fc2f4; +} +/* glossary links and popup */ diff --git a/glossary/mfa.qmd b/glossary/mfa.qmd new file mode 100644 index 000000000..19b6712cd --- /dev/null +++ b/glossary/mfa.qmd @@ -0,0 +1,5 @@ +--- +title: "MFA" +aliases: ["Multi-Factor Authentication"] +--- +A security method that requires users to verify their identitiy using two or more factors, such as an authentication app or security key. Many tools, including Yoda, Research Drive, GitHub, and Microsoft apps, require MFA for secure access to their virtual environments. Instructions for settting up MFA can be found on the [VU Service Portal](https://vu.service-now.com/kb?id=kb_article_view&sysparm_article=KB0012310). \ No newline at end of file diff --git a/glossary/persistent-identifier.qmd b/glossary/persistent-identifier.qmd new file mode 100644 index 000000000..af7d52f44 --- /dev/null +++ b/glossary/persistent-identifier.qmd @@ -0,0 +1,20 @@ +--- +title: Persistent Identifier +aliases: ["PID"] +--- +In short, and in the current context, a Persistent Identifier (PID) is essentially a URL that will never break. There are multiple PID systems, each with its own particular properties. Examples of widely used PIDs in the research domain include: + + +### DOI: + +A Digital Object Identifier can be used to refer to research data and research software. DOIs can be assigned to datasets and software upon their deposit in a repository. + +### ORCiD: + +An Open Researcher and Contributor ID is used to create a researcher profile with a unique identification number. Researchers can request an ORCiD themselves, with which they can identify their research output as their work. + +### ROR: + +The Research Organization Registry is a global register with persistent identifiers for research institutes. Researchers can use the ROR for VU Amsterdam when filling metadata forms for their research output to show that their work has been created within their employment at VU Amsterdam. + +See the Persistent Identifier guide of Netwerk Digitaal Erfgoed for a more elaborate overview. Apart from widely used domain-agnostic PIDs, there is a wide range of domain-specific unique identifiers that can be used. diff --git a/glossary/rsm.qmd b/glossary/rsm.qmd new file mode 100644 index 000000000..9ee8b9931 --- /dev/null +++ b/glossary/rsm.qmd @@ -0,0 +1,5 @@ +--- +title: Research Software Management +aliases: ["RSM"] +--- +Research software management (RSM) is a structured and strategic approach to handling the creation, utilisation, and preservation of software in the research process. \ No newline at end of file diff --git a/glossary/surf.qmd b/glossary/surf.qmd new file mode 100644 index 000000000..13843fd0d --- /dev/null +++ b/glossary/surf.qmd @@ -0,0 +1,4 @@ +--- +title: SURF +--- +SURF is the collaborative organisation for ICT in Dutch education and research. SURF provides a wide range of services, including high-speed internet, cloud services, and data storage solutions, to support the needs of the Dutch research community. VU Amsterdam is a member of SURF, which allows researchers at VU to access these services and benefit from the collaborative efforts of the Dutch research community. \ No newline at end of file diff --git a/guides/onboarding.qmd b/guides/onboarding.qmd index a52af985c..cd71b66d4 100644 --- a/guides/onboarding.qmd +++ b/guides/onboarding.qmd @@ -156,7 +156,7 @@ See [Data Collection](../topics/data-collection.qmd) #### Security Measures - Determine the necessary security measures given the [**privacy risk**](https://vu.nl/en/research/dataclassification) of the data. -- Does the recipient need MFA? Full-disk encryption? +- Does the recipient need {{< glossary mfa >}}? Full-disk encryption? *Contact your [faculty privacy champion](https://vu.nl/en/employee/information-security/privacy-champions-information) before drawing up any contracts* diff --git a/guides/process-and-analyse.qmd b/guides/process-and-analyse.qmd index 8ed3ab47f..275f792c8 100644 --- a/guides/process-and-analyse.qmd +++ b/guides/process-and-analyse.qmd @@ -116,7 +116,7 @@ Roughly speaking, you should try to get access to the HPC when you need to stick #### SURF Snellius Compute Cluster -Snellius is the Dutch National supercomputer hosted at SURF. The system facilitates scientific research carried out in many Universities, independent research institutes, governmental organizations, and private companies in the Netherlands. +Snellius is the Dutch National supercomputer hosted at {{< glossary surf >}}. The system facilitates scientific research carried out in many Universities, independent research institutes, governmental organizations, and private companies in the Netherlands. It’s a service comprising a wide range of resources, compilers and, such as R statistics and MATLAB, and libraries. SURF continually adjusts the service to the needs of the user community. For example, Snellius Compute Cluster includes accelerators (very fast processors),high memory nodes and GPU nodes. diff --git a/js/glossary-popup.js b/js/glossary-popup.js new file mode 100644 index 000000000..49c1f6cac --- /dev/null +++ b/js/glossary-popup.js @@ -0,0 +1,108 @@ +// glossary-popup.js – show a glossary term's definition in a small popup +// when a reader hovers, focuses, or clicks a `.glossary-link` (created by +// utils/glossary.lua from `{{ glossary term-id }}` shortcodes). +// +// Clicking/tapping a link toggles a "pinned" popup instead of navigating, +// so touch users (who have no hover) can still read the definition; the +// link still has a real href to the glossary page for anyone who wants the +// full entry (e.g. ctrl/cmd/middle-click, or a screen reader following it). + +document.addEventListener('DOMContentLoaded', function () { + const tooltip = document.createElement('div'); + tooltip.className = 'glossary-tooltip'; + tooltip.setAttribute('role', 'tooltip'); + + const title = document.createElement('strong'); + title.className = 'glossary-tooltip-title'; + const description = document.createElement('div'); + description.className = 'glossary-tooltip-description'; + tooltip.append(title, description); + document.body.appendChild(tooltip); + + let activeLink = null; + let pinned = false; + + function positionTooltip(link) { + const rect = link.getBoundingClientRect(); + const margin = 10; + const tooltipRect = tooltip.getBoundingClientRect(); + let top = window.scrollY + rect.bottom + margin; + let left = window.scrollX + rect.left; + + if (left + tooltipRect.width > window.scrollX + window.innerWidth - margin) { + left = window.scrollX + window.innerWidth - tooltipRect.width - margin; + } + if (top + tooltipRect.height > window.scrollY + window.innerHeight - margin) { + top = window.scrollY + rect.top - tooltipRect.height - margin; + } + top = Math.max(top, window.scrollY + margin); + left = Math.max(left, window.scrollX + margin); + + tooltip.style.top = top + 'px'; + tooltip.style.left = left + 'px'; + } + + function showTooltip(link) { + if (!link) return; + activeLink = link; + title.textContent = link.dataset.glossaryTitle || link.textContent; + description.textContent = link.dataset.glossaryDescription || ''; + tooltip.classList.add('visible'); + positionTooltip(link); + } + + function hideTooltip() { + tooltip.classList.remove('visible'); + activeLink = null; + pinned = false; + } + + function isGlossaryLink(el) { + return el && el.closest && el.closest('a.glossary-link'); + } + + document.querySelectorAll('a.glossary-link').forEach(function (link) { + link.addEventListener('mouseenter', function () { + if (!pinned) showTooltip(link); + }); + link.addEventListener('focus', function () { + showTooltip(link); + }); + link.addEventListener('mouseleave', function () { + if (!pinned) hideTooltip(); + }); + link.addEventListener('blur', function () { + if (!pinned) hideTooltip(); + }); + link.addEventListener('click', function (event) { + if (event.ctrlKey || event.metaKey || event.shiftKey || event.altKey) { + return; // let the browser handle "open in new tab" etc. normally + } + event.preventDefault(); + if (pinned && activeLink === link) { + hideTooltip(); + } else { + pinned = true; + showTooltip(link); + } + }); + }); + + document.addEventListener('click', function (event) { + if (!isGlossaryLink(event.target)) { + hideTooltip(); + } + }); + + document.addEventListener('keydown', function (event) { + if (event.key === 'Escape') { + hideTooltip(); + } + }); + + window.addEventListener('scroll', function () { + if (activeLink && tooltip.classList.contains('visible')) { + positionTooltip(activeLink); + } + }); +});