diff --git a/.github/actions/build/action.yml b/.github/actions/build/action.yml
index 59a6ec142..cd48feb2d 100644
--- a/.github/actions/build/action.yml
+++ b/.github/actions/build/action.yml
@@ -1,17 +1,13 @@
---
name: Build
-description: Setup to run and run mkdocs
+description: Setup to install dependencies and build docs with zensical
+inputs:
+ site_url:
+ description: 'Override site_url (e.g. for PR previews)'
+ required: false
+ default: "https://islandora.github.io/documentation/"
runs:
using: "composite"
steps:
- - name: Setup build environment
- uses: actions/setup-python@v4
- with:
- python-version: '3.x'
- cache: 'pip'
- - name: Install build requirements
+ - run: make build SITE_URL="${{ inputs.site_url }}"
shell: bash
- run: pip install -r requirements.txt
- - name: Build docs
- shell: bash
- run: mkdocs build
diff --git a/.github/workflows/docs-preview.yml b/.github/workflows/docs-preview.yml
index ac4123226..bc02bf30f 100644
--- a/.github/workflows/docs-preview.yml
+++ b/.github/workflows/docs-preview.yml
@@ -21,6 +21,8 @@ jobs:
uses: actions/checkout@v3
- name: Build docs
uses: ./.github/actions/build
+ with:
+ site_url: https://islandora.github.io/documentation/pr-preview/pr-${{ github.event.number }}/
- name: Deploy docs preview
# XXX: Avoid attempting to do preview things across fork boundaries, as it doesn't work.
# See: https://github.com/rossjrw/pr-preview-action/pull/6
diff --git a/Dockerfile b/Dockerfile
new file mode 100644
index 000000000..73f1eec8e
--- /dev/null
+++ b/Dockerfile
@@ -0,0 +1,12 @@
+FROM python:3.12-alpine3.22
+
+WORKDIR /work
+
+RUN apk add --no-cache git && \
+ pip install uv && uv --version && \
+ uv pip install --break-system-packages --system zensical==0.0.29
+
+EXPOSE 8080
+
+ENTRYPOINT ["zensical"]
+CMD ["serve", "--dev-addr", "0.0.0.0:8080"]
diff --git a/Makefile b/Makefile
new file mode 100644
index 000000000..691c44468
--- /dev/null
+++ b/Makefile
@@ -0,0 +1,39 @@
+.PHONY: docker-build build abbreviations help preview serve
+
+help: ## Show this help message
+ echo 'Usage: make [target]'
+ echo ''
+ echo 'Available targets:'
+ awk 'BEGIN {FS = ":.*?## "} /^[a-zA-Z_-]+:.*?## / {printf " \033[36m%s\033[0m\t%s\n", $$1, $$2}' $(MAKEFILE_LIST) | sort | column -t -s $$'\t'
+
+abbreviations: ## Parse ./docs/user-documentation/glossary.md and create Zensical Snippets other docs can reference
+ ./scripts/generate-abbreviations.sh
+
+docker-build: ## Build the Zensical python application
+ docker build -t islandora-docs .
+
+build: docker-build ## Run zensical build --clean
+ rm -rf site
+ docker run --rm \
+ $(if $(SITE_URL),-e SITE_URL=$(SITE_URL)) \
+ -v "$(CURDIR):/work" \
+ -w /work \
+ islandora-docs \
+ build --clean
+
+serve: docker-build ## Run zensical serve to make the docs available at http://localhost:8080. You can then make live updates to the docs.
+ docker run --rm -it \
+ -p 8080:8080 \
+ -v "$(CURDIR):/work" \
+ -w /work \
+ islandora-docs
+
+preview: ## Make the contents from zensical build --clean available at http://localhost:8080. Use for troubleshooting GitHuub Pages issues.
+ $(MAKE) build SITE_URL=http://localhost:8080
+ docker run --rm -it \
+ -p 8080:8080 \
+ -v "$(CURDIR)/site:/site" \
+ -w /site \
+ --entrypoint python3 \
+ islandora-docs \
+ -m http.server 8080
diff --git a/docs/contributing/docs-style-guide.md b/docs/contributing/docs-style-guide.md
index d4692a226..966815e1e 100644
--- a/docs/contributing/docs-style-guide.md
+++ b/docs/contributing/docs-style-guide.md
@@ -8,7 +8,7 @@
- Submit documentation formatted in [Markdown](https://en.wikipedia.org/wiki/Markdown) format.
- Include a top-level heading for the whole page (using `#`)
- Please add Markdown headings (`#` and `##`) to the content sections.
-
+- Wrap glossary terms used in Markdown with brackets `[]` when you want that term to leverage [Zensical's snippets/glossary](https://zensical.org/docs/authoring/tooltips/#add-a-glossary) feature. A snippet provides the definition of a term without needing to look it up in the glossary by simply mousing over the term.
- Use the "bold/emphasis" style in Markdown by enclosing text in double asterisks or underscores, `**bold text**` or `__bold text__`, for UI elements that users will interact with. For example, a button label for a button that must be pressed should be made bold in Markdown.
- Use the "italics" style in Markdown by enclosing text in single asterisks or underscores, `*italic text*` or `_italic text_`, for UI elements that have a label or title if you need to reference them in the documentation. For example, a title of a screen or page that will visit should be made italic in Markdown.
- Use `>>` and `**bold text**` to indicate clicking through nested menu items, and also include the direct path. _Example:_
diff --git a/docs/css/custom.css b/docs/css/custom.css
index e5b9c586a..4f793da96 100644
--- a/docs/css/custom.css
+++ b/docs/css/custom.css
@@ -1,4 +1,3 @@
-
.md-typeset .admonition.islandora,
.md-typeset details.islandora {
border-color: rgb(194, 19, 19);
@@ -22,3 +21,118 @@
--text: #f5fffa;
}
+/** Mermaid animation helpers for stepped flow diagrams. */
+@keyframes dash {
+ to {
+ stroke-dashoffset: -100;
+ }
+}
+
+/**
+ * Sequential flow animation for Mermaid diagrams.
+ *
+ * Author diagrams by assigning flow step classes to edges:
+ *
+ * class e1 flow0;
+ * class e2 flow1;
+ * class e3 flow2;
+ *
+ * The JavaScript runtime finds flow0..flow10-style classes, applies the dash
+ * styling, and animates those steps in sequence. Edges sharing the same flowN
+ * value animate in parallel. By default each step lasts 3 s, followed by a 2 s
+ * pause before the full sequence repeats.
+ */
+@keyframes dashFlash {
+ 0% { stroke-dashoffset: 900; }
+ 12.5% { stroke-dashoffset: -100; }
+ 12.6% { stroke-dashoffset: 900; }
+ 100% { stroke-dashoffset: 900; }
+}
+
+.mermaid-expandable {
+ cursor: zoom-in;
+}
+
+.mermaid-expandable:focus-visible {
+ outline: 2px solid var(--md-accent-fg-color);
+ outline-offset: 0.3rem;
+}
+
+.mermaid-lightbox {
+ background: transparent;
+ border: 0;
+ max-width: min(96vw, 1400px);
+ padding: 0;
+ width: 96vw;
+}
+
+.mermaid-lightbox::backdrop {
+ background: rgba(0, 0, 0, 0.72);
+}
+
+.mermaid-lightbox__frame {
+ background: var(--md-default-bg-color);
+ border-radius: 0.6rem;
+ box-shadow: var(--md-shadow-z3);
+ max-height: 90vh;
+ overflow: hidden;
+}
+
+.mermaid-lightbox__close {
+ appearance: none;
+ background: transparent;
+ border: 1px solid currentColor;
+ border-radius: 999px;
+ cursor: pointer;
+ display: block;
+ margin: 1rem 1rem 0 auto;
+ padding: 0.35rem 0.75rem;
+}
+
+.mermaid-lightbox__body {
+ overflow: auto;
+ padding: 1rem;
+}
+
+.mermaid-lightbox__body svg {
+ display: block;
+ margin: 0 auto;
+}
+
+/** Play / Pause / Restart controls injected below animated diagrams **/
+.mermaid-controls {
+ display: flex;
+ gap: 0.3rem;
+ justify-content: center;
+ margin-top: 0.5rem;
+}
+
+.mermaid-controls .mermaid-btn {
+ appearance: none;
+ background: transparent;
+ border: 1px solid currentColor;
+ border-radius: 3px;
+ cursor: pointer;
+ font-size: 0.8rem;
+ line-height: 1;
+ opacity: 0.35;
+ padding: 0.25rem 0.5rem;
+ transition: opacity 0.15s;
+}
+
+.mermaid-controls .mermaid-btn:hover,
+.mermaid-controls .mermaid-btn:focus-visible {
+ opacity: 0.85;
+}
+
+.mermaid-controls .mermaid-btn:disabled {
+ cursor: default;
+ opacity: 0.85;
+}
+
+@media (prefers-reduced-motion: reduce) {
+ .mermaid * {
+ animation: none !important;
+ transition: none !important;
+ }
+}
diff --git a/docs/installation/component-overview.md b/docs/installation/component-overview.md
index 7e9e421a1..6649e6037 100644
--- a/docs/installation/component-overview.md
+++ b/docs/installation/component-overview.md
@@ -9,59 +9,60 @@ This list includes four different kinds of components:
- Components that can't easily be swapped out but are not necessarily required (such as using Solr as the site's internal search engine)
- Components which do not have official alternatives and are not necessarily required, but will likely exist on the vast majority of Islandora installations (such as Alpaca and Crayfish)
-## The Webserver Stack - Apache, PHP, and MySQL/PostgreSQL
+## The Webserver Stack - Traefik, NGINX, PHP, and MariaDB (or PostgreSQL)
-Combined together, Apache, PHP, and MySQL/PostgreSQL comprise a LAMP or LAPP server used to provide end-user-facing components - namely, the website.
+Combined together, [Traefik], [NGINX], PHP, and [MariaDB] (or [PostgreSQL]) comprise a solution stack used to provide end-user-facing components - namely, the website.```
-**Apache** is the webserver that will serve up webpages to the public. It will also manage some internal functionality provided by Crayfish, and will expose Cantaloupe to the public. We’ll be making changes to the VirtualHost entry, enabling some modules, and modifying the ports configuration. The VirtualHost entry will eventually be modified when we need to expose other services like Cantaloupe to the public.
+**[Traefik]** acts as the Islandora stack's front-end proxy. It will terminate TLS and routes requests to services in the Islandora stack i.e. Drupal, Cantaloupe, and Fedora.
-**PHP** is the runtime interpreter for all the code Drupal and Crayfish need to be processed. By default, installing PHP 7.2 will give us a command-line interpreter, as well as an interpreter for Apache. We’re going to install several PHP modules required and/or useful for the components that make use of PHP.
-**MySQL** and **PostgreSQL** are database management systems that we will use to store information for many different components like Drupal and Fedora. By default, the Ansible playbook installs MySQL, though this can be switched to PostgreSQL. The manual installation guide recommends and walks through installing and using PostgreSQL.
+**[NGINX]** is the webserver that will serve up webpages to the public.
-## The Front-Facing CDM - Composer, Drush, and Drupal
+**PHP** is the runtime interpreter for all the code Drupal needs to be processed. By default, installing PHP 8.3 will give us a command-line interpreter, as well as an interpreter for NGINX.
-Composer will be used to install both Drupal and Drush simultaneously using Islandora's fork of the [drupal-project](https://github.com/Islandora/drupal-project) repository.
+**[MariaDB]** and **[PostgreSQL]** are database management systems that we will use to store information for many different components like Drupal and Fedora. By default, the [ISLE Site Template] installs MariaDB, though this can be switched to PostgreSQL.
-**Composer** is an installer and dependency manager for PHP projects. We're going to need it to install components for any PHP code we need to make use of, including Drupal and Crayfish.
+## Composer, Drush, and Drupal
-**Drush** and **Drupal** are installed simultaneously using [drupal-project](https://github.com/Islandora/drupal-project). Drupal will serve up webpages and manage Islandora content, and Drush will help us get some things done from the command-line.
+Composer will be used to install both Drupal and Drush simultaneously using [Islandora Starter Site]
+
+**[Composer]** is an installer and dependency manager for PHP projects. We're going to need it to install components for Drupal.
+
+**[Drush]** and **[Drupal]** are installed simultaneously using [Composer]. Drupal will serve up webpages and manage Islandora content, and Drush will help us get some things done from the command-line.
## The Web Application Server - Tomcat and Cantaloupe
-Several applets will be deployed via their `.war` files into Tomcat, including Fedora and Cantaloupe.
+Several applets will be deployed via their `.war` files into Tomcat, including [Fedora (Repository Software)] and [Cantaloupe].
-**Tomcat** serves up webpages and other kinds of content much like Apache, but is specifically designed to deploy Java applications as opposed to running PHP code.
+**[Tomcat]** serves up webpages and other kinds of content much like NGINX, but is specifically designed to deploy Java applications as opposed to running PHP code.
-**Cantaloupe** is an image tileserver that Islandora will connect to and use to serve up extremely large images in a way that doesn't have an adverse effect on the overall system.
+**[Cantaloupe]** is an image tileserver that Islandora will connect to and use to serve up extremely large images in a way that doesn't have an adverse effect on the overall system.
-## The Back-End File Management Repository - Fedora, Syn, and Blazegraph
+## The Back-End File Management Repository - Fedora and Blazegraph
Fedora will be installed in its own section, rather than as part of the Tomcat installation, as the installation process is rather involved and requires some authorization pieces to be set up in order to connect them back to Drupal and other components.
-**Fedora** is the default backend repository that Islandora content will be synchronized with and stored in. A great deal of configuration will be required to get it up and running, including ensuring a database is created and accessible.
-
-**Syn** is the authorization piece that allows Fedora to connect to other components.
+**[Fedora (Repository Software)]** is the default backend repository that Islandora content will be synchronized with and stored in. A great deal of configuration will be required to get it up and running, including ensuring a database is created and accessible.
-**Blazegraph** will store representative graph data about the repository that can be queried using SPARQL. Some configuration will also be required to link it back to Fedora, as well as to ensure it is being properly indexed.
+**[Blazegraph]** will store representative graph data about the repository that can be queried using SPARQL. Some configuration will also be required to link it back to Fedora, as well as to ensure it is being properly indexed.
## The Search Engine - Solr and search_api_solr
The installation of Solr itself is rather straightforward, but a configuration will have to be generated and applied from the Drupal side.
-**Solr** will be installed as a standalone application. Nothing of particular importance needs to happen here; the configuration will be applied when `search_api_solr` is installed.
+**[Solr]** will be installed as a standalone application. Nothing of particular importance needs to happen here; the configuration will be applied when `search_api_solr` is installed.
**search_api_solr** is the Drupal module that implements the Solr API for Drupal-side searches. After installing and configuring the module, the `drush solr-gsc` command will be used to generate Solr configs, and these configs will be moved to the Solr configuration location.
-## The Asynchronous Background Services - Crayfish
+## The Asynchronous Background Services - scyllaridae
-**Crayfish** is a series of microservices that perform different asynchronous tasks kicked off by Islandora. It contains a series of submodules that will be installed via Composer. Later, these configured components will be connected to Alpaca.
+**[scyllaridae]** is a series of microservices that perform different asynchronous tasks kicked off by Islandora. It contains a series of submodules that will be installed via Composer. Later, these configured components will be connected to Alpaca.
## The Broker Connecting Everything - Karaf and Alpaca
**Karaf**’s job is similar to Tomcat, except where Tomcat is a web-accessible endpoint for Java applets, Karaf is simply meant to be a container for system-level applets to communicate via its OSGI. Alpaca is one such applet; it will broker messages between Fedora and Drupal, and between Drupal and various derivative generation applications.
-**Alpaca** contains Karaf services to manage moving information between Islandora, Fedora, and Blazegraph as well as kicking off derivative services in Crayfish. These will be configured to broker between Drupal and Fedora using an ActiveMQ queue.
+**[Alpaca]** contains Karaf services to manage moving information between Islandora, Fedora, and Blazegraph as well as kicking off derivative services in Crayfish. These will be configured to broker between Drupal and Fedora using an ActiveMQ queue.
## Finalized Drupal Configurations
diff --git a/docs/js/diagram-animation.js b/docs/js/diagram-animation.js
new file mode 100644
index 000000000..fa0358e17
--- /dev/null
+++ b/docs/js/diagram-animation.js
@@ -0,0 +1,259 @@
+/**
+ * Sequential flow animation for Mermaid diagrams.
+ */
+(() => {
+ const STEP_DURATION = 1500;
+ const STEP_GAP = 150;
+ const EDGE_SELECTOR = "path";
+
+ const reduceMotion = window.matchMedia("(prefers-reduced-motion: reduce)");
+
+ const getStep = (element) => {
+ let el = element;
+ while (el) {
+ for (const cls of el.classList) {
+ const match = cls.match(/^flow(\d+)$/);
+ if (match) return parseInt(match[1], 10);
+ }
+ el = el.parentElement;
+ }
+ return null;
+ };
+
+ const getEdgeSelector = (edgeId) => {
+ const escaped = window.CSS && window.CSS.escape ? window.CSS.escape(edgeId) : edgeId;
+ return '[id="' + escaped + '"], [id$="-' + escaped + '"]';
+ };
+
+ const getSteppedEdges = (container) => {
+ const flowSteps = container.__islandoraFlowSteps || {};
+ const mappedEdges = Object.entries(flowSteps)
+ .map(([edgeId, step]) => {
+ const el = container.querySelector(getEdgeSelector(edgeId));
+ return el ? { el, step } : null;
+ })
+ .filter(Boolean);
+
+ if (mappedEdges.length > 0) {
+ return mappedEdges;
+ }
+
+ return Array.from(new Set(container.querySelectorAll(EDGE_SELECTOR)))
+ .map((el) => ({ el, step: getStep(el) }))
+ .filter(({ step }) => step !== null);
+ };
+
+ const ensureAnimatedPath = (edgePath) => {
+ if (edgePath.__islandoraAnimatedPath && edgePath.__islandoraAnimatedPath.isConnected) {
+ return edgePath.__islandoraAnimatedPath;
+ }
+
+ const animatedPath = edgePath.cloneNode(false);
+ const computed = window.getComputedStyle(edgePath);
+ const strokeWidth = parseFloat(computed.strokeWidth || edgePath.getAttribute("stroke-width") || "2") || 2;
+
+ animatedPath.removeAttribute("id");
+ animatedPath.removeAttribute("marker-start");
+ animatedPath.removeAttribute("marker-end");
+ animatedPath.setAttribute("aria-hidden", "true");
+ animatedPath.style.fill = "none";
+ animatedPath.style.opacity = "0";
+ animatedPath.style.pointerEvents = "none";
+ animatedPath.style.stroke = computed.stroke;
+ animatedPath.style.strokeWidth = String(Math.max(strokeWidth * 1.75, strokeWidth + 1));
+ animatedPath.style.strokeLinecap = "round";
+ animatedPath.style.strokeLinejoin = "round";
+ animatedPath.style.strokeDasharray = "0 1";
+ animatedPath.style.strokeDashoffset = "0";
+
+ edgePath.parentNode && edgePath.parentNode.appendChild(animatedPath);
+ edgePath.__islandoraAnimatedPath = animatedPath;
+ return animatedPath;
+ };
+
+ const attachAnimationListeners = (container, syncUI) => {
+ (container.__islandoraAnimations || []).forEach((animation) => {
+ if (animation.__islandoraSyncAttached) {
+ return;
+ }
+ animation.addEventListener("finish", syncUI);
+ animation.addEventListener("cancel", syncUI);
+ animation.__islandoraSyncAttached = true;
+ });
+ };
+
+ const animateContainer = (container) => {
+ const steppedEdges = getSteppedEdges(container);
+ if (steppedEdges.length === 0) return false;
+
+ const maxStep = Math.max(...steppedEdges.map(({ step }) => step));
+ const totalCycle = (maxStep + 1) * STEP_DURATION + maxStep * STEP_GAP;
+
+ container.__islandoraAnimations = steppedEdges.map(({ el, step }) => {
+ const animatedPath = ensureAnimatedPath(el);
+ const pathLength = Math.max(el.getTotalLength(), 1);
+ const segmentLength = Math.max(Math.min(pathLength * 0.35, 120), 18);
+ const hiddenOffset = pathLength + segmentLength;
+ const visibleDash = String(segmentLength) + ' ' + String(pathLength);
+ const stepDelay = step * (STEP_DURATION + STEP_GAP);
+ const startOffset = Math.min(stepDelay / totalCycle, 1);
+ const finishOffset = Math.min((stepDelay + STEP_DURATION) / totalCycle, 1);
+ const hideOffset = Math.min(finishOffset + 0.0005, 1);
+
+ animatedPath.__islandoraDashAnimation && animatedPath.__islandoraDashAnimation.cancel();
+ animatedPath.style.strokeDasharray = visibleDash;
+ animatedPath.style.strokeDashoffset = String(hiddenOffset);
+ animatedPath.style.opacity = '0';
+
+ const animation = animatedPath.animate(
+ [
+ { strokeDashoffset: hiddenOffset, opacity: 0, offset: 0 },
+ { strokeDashoffset: hiddenOffset, opacity: 0, offset: startOffset },
+ { strokeDashoffset: hiddenOffset, opacity: 1, offset: startOffset },
+ { strokeDashoffset: segmentLength * 2, opacity: 1, offset: finishOffset },
+ { strokeDashoffset: segmentLength * 2, opacity: 0, offset: hideOffset },
+ { strokeDashoffset: hiddenOffset, opacity: 0, offset: 1 },
+ ],
+ {
+ duration: totalCycle,
+ easing: 'linear',
+ fill: 'forwards',
+ iterations: Infinity,
+ }
+ );
+ animatedPath.__islandoraDashAnimation = animation;
+ return animation;
+ });
+
+ return true;
+ };
+
+ const stopContainer = (container) => {
+ container.querySelectorAll(EDGE_SELECTOR).forEach((el) => {
+ if (el.__islandoraAnimatedPath && el.__islandoraAnimatedPath.__islandoraDashAnimation) {
+ el.__islandoraAnimatedPath.__islandoraDashAnimation.cancel();
+ }
+ el.__islandoraAnimatedPath && el.__islandoraAnimatedPath.remove();
+ el.__islandoraAnimatedPath = null;
+ });
+ container.__islandoraAnimations = null;
+ delete container.dataset.islandoraAnimated;
+ };
+
+ const injectControls = (container) => {
+ container.querySelector('.mermaid-controls') && container.querySelector('.mermaid-controls').remove();
+
+ const controls = document.createElement('div');
+ controls.className = 'mermaid-controls';
+ controls.setAttribute('role', 'group');
+ controls.setAttribute('aria-label', 'Diagram animation controls');
+
+ const makeBtn = (text, label) => {
+ const btn = document.createElement('button');
+ btn.className = 'mermaid-btn';
+ btn.type = 'button';
+ btn.textContent = text;
+ btn.setAttribute('aria-label', label);
+ return btn;
+ };
+
+ const playBtn = makeBtn('▶', 'Play animation');
+ const pauseBtn = makeBtn('⏸', 'Pause animation');
+ const restartBtn = makeBtn('↺', 'Restart animation');
+ const enlargeBtn = makeBtn('+', 'Enlarge diagram');
+
+ const syncUI = () => {
+ const activeAnimations = container.__islandoraAnimations || [];
+ const running = activeAnimations.some((animation) => animation.playState === 'running');
+ playBtn.disabled = running || activeAnimations.length === 0;
+ pauseBtn.disabled = !running;
+ };
+
+ playBtn.addEventListener('click', () => {
+ (container.__islandoraAnimations || []).forEach((animation) => animation.play());
+ syncUI();
+ });
+
+ pauseBtn.addEventListener('click', () => {
+ (container.__islandoraAnimations || []).forEach((animation) => animation.pause());
+ syncUI();
+ });
+
+ restartBtn.addEventListener('click', () => {
+ animateContainer(container);
+ attachAnimationListeners(container, syncUI);
+ (container.__islandoraAnimations || []).forEach((animation) => {
+ animation.currentTime = 0;
+ animation.play();
+ });
+ syncUI();
+ });
+
+ enlargeBtn.addEventListener('click', () => {
+ container.__islandoraOpenLightbox && container.__islandoraOpenLightbox();
+ });
+
+ attachAnimationListeners(container, syncUI);
+ syncUI();
+ controls.append(playBtn, pauseBtn, restartBtn, enlargeBtn);
+ container.prepend(controls);
+ };
+
+ const activateContainer = (container) => {
+ if (reduceMotion.matches) return;
+ if (container.dataset.islandoraAnimated === 'true') return;
+
+ const hasSteps = animateContainer(container);
+ if (!hasSteps) return;
+
+ container.dataset.islandoraAnimated = 'true';
+ if (!container.querySelector('.mermaid-controls')) {
+ injectControls(container);
+ }
+ };
+
+ document.addEventListener('mermaid:rendered', ({ detail: container }) => {
+ activateContainer(container);
+ });
+
+ const onMotionChange = () => {
+ document.querySelectorAll('.mermaid').forEach((container) => {
+ if (reduceMotion.matches) {
+ stopContainer(container);
+ container.querySelector('.mermaid-controls') && container.querySelector('.mermaid-controls').remove();
+ } else {
+ activateContainer(container);
+ }
+ });
+ };
+
+ const observeMermaidContainers = (root) => {
+ if (!(root instanceof Element || root instanceof Document)) return;
+ if (root instanceof Element && root.matches('.mermaid')) {
+ activateContainer(root);
+ }
+ root.querySelectorAll && root.querySelectorAll('.mermaid').forEach((container) => {
+ activateContainer(container);
+ });
+ };
+
+ observeMermaidContainers(document);
+
+ const observer = new MutationObserver((records) => {
+ records.forEach((record) => {
+ record.addedNodes.forEach((node) => {
+ observeMermaidContainers(node);
+ });
+ });
+ });
+
+ if (document.body) {
+ observer.observe(document.body, { childList: true, subtree: true });
+ }
+
+ if (typeof reduceMotion.addEventListener === 'function') {
+ reduceMotion.addEventListener('change', onMotionChange);
+ } else if (typeof reduceMotion.addListener === 'function') {
+ reduceMotion.addListener(onMotionChange);
+ }
+})();
diff --git a/docs/js/mermaid-init.mjs b/docs/js/mermaid-init.mjs
new file mode 100644
index 000000000..019e0ebea
--- /dev/null
+++ b/docs/js/mermaid-init.mjs
@@ -0,0 +1,158 @@
+import mermaid from "https://unpkg.com/mermaid@11.13.0/dist/mermaid.esm.min.mjs";
+
+mermaid.initialize({
+ startOnLoad: false,
+ securityLevel: "loose",
+});
+
+const LIGHTBOX_ID = "mermaid-lightbox";
+
+const parseFlowSteps = (source) => {
+ const flowSteps = {};
+ const pattern = /^\s*class\s+([^;]+?)\s+flow(\d+)\s*;/gm;
+ let match;
+
+ while ((match = pattern.exec(source)) !== null) {
+ const ids = match[1].split(",").map((id) => id.trim()).filter(Boolean);
+ const step = parseInt(match[2], 10);
+ ids.forEach((id) => {
+ flowSteps[id] = step;
+ });
+ }
+
+ return flowSteps;
+};
+
+const ensureLightbox = () => {
+ let lightbox = document.getElementById(LIGHTBOX_ID);
+ if (lightbox) {
+ return lightbox;
+ }
+
+ lightbox = document.createElement("dialog");
+ lightbox.id = LIGHTBOX_ID;
+ lightbox.className = "mermaid-lightbox";
+ lightbox.setAttribute("aria-label", "Expanded Mermaid diagram");
+
+ const frame = document.createElement("div");
+ frame.className = "mermaid-lightbox__frame";
+
+ const closeButton = document.createElement("button");
+ closeButton.type = "button";
+ closeButton.className = "mermaid-lightbox__close";
+ closeButton.setAttribute("aria-label", "Close expanded diagram");
+ closeButton.textContent = "Close";
+ closeButton.addEventListener("click", () => {
+ lightbox.close();
+ });
+
+ const body = document.createElement("div");
+ body.className = "mermaid-lightbox__body";
+
+ frame.append(closeButton, body);
+ lightbox.append(frame);
+
+ lightbox.addEventListener("click", (event) => {
+ if (event.target === lightbox) {
+ lightbox.close();
+ }
+ });
+
+ document.body.append(lightbox);
+ return lightbox;
+};
+
+const openLightbox = (container) => {
+ const svg = container.querySelector("svg");
+ if (!svg) {
+ return;
+ }
+
+ const lightbox = ensureLightbox();
+ const body = lightbox.querySelector(".mermaid-lightbox__body");
+ const clone = svg.cloneNode(true);
+
+ clone.removeAttribute("width");
+ clone.removeAttribute("height");
+ clone.style.width = "100%";
+ clone.style.height = "auto";
+
+ body.replaceChildren(clone);
+
+ if (!lightbox.open) {
+ lightbox.showModal();
+ }
+};
+
+const attachExpandBehavior = (container) => {
+ if (container.dataset.mermaidExpandable === "true") {
+ return;
+ }
+ container.dataset.mermaidExpandable = "true";
+ container.classList.add("mermaid-expandable");
+ container.tabIndex = 0;
+ container.setAttribute("role", "button");
+ container.setAttribute("aria-label", "Open Mermaid diagram in a larger view");
+ container.title = "Click to enlarge diagram";
+ container.__islandoraOpenLightbox = () => {
+ openLightbox(container);
+ };
+
+ container.addEventListener("click", (event) => {
+ if (event.target.closest(".mermaid-controls, button, a")) {
+ return;
+ }
+ openLightbox(container);
+ });
+
+ container.addEventListener("keydown", (event) => {
+ if (event.key !== "Enter" && event.key !== " ") {
+ return;
+ }
+ event.preventDefault();
+ openLightbox(container);
+ });
+};
+
+const renderDiagram = async (container, index) => {
+ if (container.dataset.mermaidRendered === "true") {
+ return;
+ }
+ container.dataset.mermaidRendered = "true";
+
+ const code = container.querySelector("code");
+ const source = code ? code.textContent : container.textContent;
+ if (!source || !source.trim()) {
+ return;
+ }
+
+ const flowSteps = parseFlowSteps(source);
+ const renderTarget = document.createElement("div");
+ renderTarget.className = "mermaid";
+ renderTarget.__islandoraFlowSteps = flowSteps;
+
+ try {
+ const result = await mermaid.render(`islandora-mermaid-${index}`, source);
+ renderTarget.innerHTML = result.svg;
+ attachExpandBehavior(renderTarget);
+ container.replaceWith(renderTarget);
+ document.dispatchEvent(new CustomEvent("mermaid:rendered", { detail: renderTarget }));
+ } catch (error) {
+ console.error("Mermaid render failed", error);
+ }
+};
+
+const renderAll = () => {
+ document.querySelectorAll("pre.mermaid").forEach((container, index) => {
+ void renderDiagram(container, index);
+ });
+};
+
+if (document.readyState === "loading") {
+ document.addEventListener("DOMContentLoaded", renderAll, { once: true });
+} else {
+ renderAll();
+}
+
+// Required for Zensical/Material to find the mermaid instance.
+window.mermaid = mermaid;
diff --git a/docs/technical-documentation/adding-format-jsonld.md b/docs/technical-documentation/adding-format-jsonld.md
index 2dd5165e6..6eac2580b 100644
--- a/docs/technical-documentation/adding-format-jsonld.md
+++ b/docs/technical-documentation/adding-format-jsonld.md
@@ -3,16 +3,8 @@ Drupal requires the use of a `_format` query parameter to get alternate represen
By default, Islandora deploys with the [jsonld](https://github.com/Islandora/jsonld) module and the [Milliner](https://github.com/Islandora/Crayfish/tree/main/Milliner) microservice. These two components are configured to strip this `_format` query parameter off of the end of URIs.
This means that when your content is indexed in Fedora, the triplestore, etc... it's URI will
-be something like `http://localhost:8000/node/1` and not `http://localhost:8000/node/1?_format=jsonld`.
+be something like `http://islandora.traefik.me/node/1` and not `http://islandora.traefik.me/node/1?_format=jsonld`.
-## Pre-1.0 installations.
-
-If you are using a __very__ early version of Islandora "8" (pre-release), then you may have URIs with `_format=jsonld` at the end of them.
-
-If you update to newer code, you will need to ensure that your site is configured to add `?_format=jsonld`
-back to the URLs if you want to maintain consistency.
-
-If you **don't** do this, you can end up with two copies of your objects in your Fedora repository (one with and one without `?_format=jsonld`). You will also have two sets of triples in your triplestore.
## Adding ?_format=jsonld to your URIs
diff --git a/docs/technical-documentation/alpaca-tips.md b/docs/technical-documentation/alpaca-tips.md
index acf0fa15b..fb9bec185 100644
--- a/docs/technical-documentation/alpaca-tips.md
+++ b/docs/technical-documentation/alpaca-tips.md
@@ -1,8 +1,6 @@
# Alpaca Tips
-[Alpaca](https://github.com/Islandora/Alpaca) is event-driven middleware based on [Apache Camel](https://camel.apache.org/) for Islandora
-
-Currently, Alpaca ships with four event-driven components
+[Alpaca] ships with four event-driven components
- [islandora-connector-derivative](#islandora-connector-derivative)
- [islandora-http-client](#islandora-http-client)
@@ -10,7 +8,7 @@ Currently, Alpaca ships with four event-driven components
- [islandora-indexing-triplestore](#islandora-indexing-triplestore)
## islandora-connector-derivative
-This service receives requests from Drupal when it wants to create derivatives and passes that request along to a microservice in [Crayfish](https://github.com/Islandora/Crayfish). When it receives the derivative file back from the microservice, it passes the file back to Drupal.
+This service receives requests from Drupal when it wants to create derivatives and passes that request along to a microservice in [scyllaridae]. When it receives the derivative file back from the microservice, it passes the file back to Drupal.
## islandora-http-client
This service overrides the default http client with Islandora specific configuration.
diff --git a/docs/technical-documentation/diagram.md b/docs/technical-documentation/diagram.md
index 7ff38415e..67bb77a52 100644
--- a/docs/technical-documentation/diagram.md
+++ b/docs/technical-documentation/diagram.md
@@ -1,37 +1,204 @@
-# Islandora Architecture Diagram
+# Islandora Architecture
-
+At its core, Islandora is a [Drupal] website that leverages a suite of [Microservices]. To help illustrate this, different site operations are described below.
-Diagram prepared by [Bethany Seeger](https://github.com/bseeger) based on work done by [Gavin Morris](https://github.com/g7morris)
+- The diagram under [Islandora is a Drupal Website](#islandora-is-a-drupal-website) describes how a typical web page is displayed when a site visitor arrives at an Islandora repository on the internet. How most HTML responses are generated for Islandora sites are identical to any Drupal website that integrates with [Solr]. The exception being Islandora's [IIIF] integration, which is explained in a bit more detail in [IIIF Integration](#iiif-integration)
+- The diagrams under [Microservices](#microservices) explain how events are emitted from an Islandora Drupal website and processed by Islandora's event driven architecture
+- [Components](#components) is a list of software used in the Islandora tech stack
+
+## Islandora is a Drupal Website
+
+When a client visits an Islandora website, the request flow looks like a typical [Drupal] request.
+The request is received by [NGINX], which forwards the request to [Drupal].
+Drupal bootstraps and queries [MariaDB] to generate an HTML response.
+If the request was for a search page, Drupal may also query [Solr] to include search results in the HTML response.
+
+```mermaid
+flowchart TD
+ user([Client / Browser])
+ user e1@-->|HTTP request| nginx
+
+ subgraph webserver[Nginx Web Server]
+ nginx[Nginx] e2@-->|forward request| drupal[Drupal]
+ end
+
+ drupal e3@-->|query| mariadb[(MariaDB)]
+ drupal e4@-->|query| solr[(Solr)]
+
+ mariadb e6@-.->|data| drupal
+ solr e7@-.->|results| drupal
+ drupal e9@-.->|HTML response| nginx
+ nginx e10@-.->|HTML response| user
+
+ class e1 flow0;
+ class e2 flow1;
+ class e3 flow2;
+ class e4 flow2;
+ class e5 flow2;
+ class e6 flow3;
+ class e7 flow3;
+ class e8 flow3;
+ class e9 flow4;
+ class e10 flow5;
+
+```
+
+
+### IIIF Integration
+
+For some [Resource Nodes] in Islandora, the HTML response may include a link to a [IIIF Manifest] that is dynamically generated by Drupal for the given node. This typically happens when rendering [Resource Nodes] that have image [media]. For these types of requests, Islandora's [IIIF] module may also query the [Cantaloupe] IIIF server for metadata needed to generate the [IIIF Manifest]. The IIIF viewer configured on the Islandora site (e.g. [OpenSeadragon] or [Mirador]) will communicate with Cantaloupe to render the image(s) included in the IIIF Manifest.
+
+All this to say, in addition to [the typical drupal request flow](#islandora-is-a-drupal-website), Drupal may also query Cantaloupe for basic image metadata (e.g. height/width) which are needed to generate a valid [IIIF Manifest]. The client's web browser will then read that IIIF Manifest using Javascript and the IIIF viewer will `GET` the images referenced in the IIIF Manifest from [Cantaloupe].
+
+```mermaid
+flowchart TD
+ user([Client / Browser])
+ user e1@-->|GET IIIF Manifest| nginx
+
+ subgraph webserver[Nginx Web Server]
+ nginx[Nginx] e2@-->|forward request| drupal[Drupal]
+ end
+
+ drupal e3@-->|GET /info.json| cantaloupe[Cantaloupe IIIF Image Server]
+
+ cantaloupe e4@-->|info.json| drupal
+ drupal e5@-.->|HTML response| nginx
+ nginx e6@-.->|HTML response| user
+ user e7@-->|GET /image.png| cantaloupe
+ cantaloupe e8@-.->|image.png| user
+
+ class e1 flow0;
+ class e2 flow1;
+ class e3 flow2;
+ class e4 flow3;
+ class e5 flow4;
+ class e6 flow5;
+ class e7 flow6;
+ class e8 flow7;
+ class e9 flow8;
+
+```
+
+
+### Fedora Flysystem Adapter
+
+Islandora uses [Flysystem] and the [associated Drupal module](https://www.drupal.org/project/flysystem) to store Drupal managed files in [Fedora (Repository Software)].
+
+You can read more about this in [Islandora's Flysystem documentation](../user-documentation/flysystem/).
+
+## Microservices
+
+In addition to all the tools Drupal provides, Islandora extends the Drupal site's capabilities using an event-driven, distributed architecture of [Microservices]. When a repository manager creates, updates, or deletes Drupal [entities], the Islandora Drupal module generates an event message which is put on Islandora's [ActiveMQ] queue.
+
+There are two different types of events Islandora emits:
+
+- [Derivative] Events - these types of events create [derivatives] from files uploaded to Islandora
+- Index Events - these types of events create an indexed representation of a [Resource Node] in a system external to [Drupal]
+
+### Derivative Events
+
+Below is a full diagram of the different microservices Islandora provides. You can see as an animation in the diagram what happens when an Islandora repository manager uploads an image to their Islandora repository. First, Drupal emits an event to generate a thumbnail for that image. That event is put on the [ActiveMQ] event queue, [alpaca] reads the message from the queue, and forwards the event to the configured service. In the case of a thumbnail, [houdini] handles generating the thumbnail for the uploaded image. [Houdini] creates the thumbnail and alpaca saves the thumbnail in Drupal.
+
+```mermaid
+flowchart TD
+ drupal([Islandora Drupal Website])
+
+ drupal e1@-->|publishes drupal entity event| activemq
+
+ subgraph broker[Message Broker]
+ activemq[ActiveMQ]
+ alpaca[Alpaca]
+ activemq e2@-->|alpaca receives event| alpaca
+ end
+
+
+ subgraph microservices[scyllaridae microservices]
+ fits[FITS]
+ homarus[Homarus]
+ houdini[Houdini]
+ hypercube[Hypercube]
+ end
+
+ alpaca --> fits
+ alpaca --> homarus
+ alpaca e3@--> houdini
+ alpaca --> hypercube
+
+ fits -.->|derivative streamed back| alpaca
+ homarus -.->|derivative streamed back| alpaca
+ houdini e4@-.->|derivative streamed back| alpaca
+ hypercube -.->|derivative streamed back| alpaca
+ alpaca e5@-.->|alpaca saves the derivative| drupal
+
+ class e1 flow0;
+ class e2 flow1;
+ class e3 flow2;
+ class e4 flow3;
+ class e5 flow4;
+```
+
+### Index Events
+
+There are two systems that are populated using Islandora Index Events: [Blazegraph] and [Fedora (Repository Software)].
+
+- For [Blazegraph], [Alpaca] is fully implemented to be able to index content from Drupal directly into Blazegraph using [RDF].
+- For [Fedora (Repository Software)] an intermediate service is used called [Milliner] to store the metadata in Fedora.
+
+```mermaid
+flowchart TD
+ drupal([Islandora Drupal Website])
+
+ drupal e1@-->|publishes drupal entity event| activemq
+
+ subgraph broker[Message Broker]
+ activemq[ActiveMQ]
+ alpaca[Alpaca]
+ activemq e2@-->|alpaca receives event| alpaca
+ end
+
+
+ alpaca e3@--> milliner
+
+ fedora[(Fedora)]
+ milliner e4@-.->|syncs resource to| fedora
+ blazegraph[(blazegraph)]
+ alpaca -.->|sends RDF| blazegraph
+
+ class e1 flow0;
+ class e2 flow1;
+ class e3 flow2;
+ class e4 flow3;
+ class e5 flow4;
+```
## Components
### Islandora
-The following components are microservices developed and maintained by the Islandora community. They are bundled under [Islandora Crayfish](https://github.com/Islandora/Crayfish):
-
-* [FITS](https://github.com/roblib/CrayFits) - A Symfony 4 Microservice to generate FITS data and persist it as a Drupal media node. Works with [Islandora FITS](https://github.com/roblib/islandora_fits)
-* [Homarus](https://github.com/Islandora/Crayfish/tree/dev/Homarus) - Provides [FFmpeg](https://www.ffmpeg.org/) as a microservice for generating video and audio derivatives.
-* [Houdini](https://github.com/Islandora/Crayfish/tree/dev/Houdini) - [ImageMagick](https://www.imagemagick.org/script/index.php) as a microservice for generating image-based derivatives, including thumbnails.
-* [Hypercube](https://github.com/Islandora/Crayfish/tree/dev/Hypercube) - [Tesseract](https://github.com/tesseract-ocr) as a microservice for optical character recognition (OCR).
-* [Milliner](https://github.com/Islandora/Crayfish/tree/dev/Milliner) - A microservice that converts Drupal entities into Fedora resources.
-* [Recast](https://github.com/Islandora/Crayfish/tree/dev/Recast) - A microservice that remaps Drupal URIs to add Fedora-to-Fedora links based on associated Drupal URIs in RDF.
+The following components are microservices developed and maintained by the Islandora community. They are bundled under [scyllaridae] and [Islandora Crayfish](https://github.com/Islandora/Crayfish):
+* [scyllaridae]
+ * [Crayfits]
+ * [Homarus]
+ * [Houdini]
+ * [Hypercube]
+* [Milliner] (uses [Crayfish])
### Other Open Source
The following components are deployed with Islandora, but are developed and maintained by other open source projects:
-
-* [Apache](https://www.apache.org/) - The Apache HTTP Server, colloquially called Apache, is a free and open-source cross-platform web server software. Provides the environment in which Islandora and its components run.
- * [ActiveMQ](https://activemq.apache.org/) - Apache ActiveMQ is an open source message broker written in Java together with a full Java Message Service client.
- * [Karaf](https://karaf.apache.org/) - A modular open source OSGi runtime environment.
- * [Tomcat](http://tomcat.apache.org/) - an open-source implementation of the Java Servlet, JavaServer Pages, Java Expression Language and WebSocket technologies. Tomcat provides a "pure Java" HTTP web server environment in which Java code can run.
- * [Solr](https://lucene.apache.org/solr/) - An open-source enterprise-search platform. Solr is the default search and discover layer of Islandora, and a key component in some methods for [migration to Islandora from Islandora Legacy](https://github.com/Islandora-devops/migrate_7x_claw)
-* [Blazegraph](https://blazegraph.com/) - Blazegraph is a triplestore and graph database.
-* [Cantaloupe](https://cantaloupe-project.github.io/) - an open-source dynamic image server for on-demand generation of derivatives of high-resolution source images. Used in Islandora to support [IIIF](https://iiif.io/)
-* [Drupal](https://www.drupal.org/) - Drupal is an open source content management system, and the heart of Islandora. All user and site-building aspects of Islandora are experienced through Drupal as a graphical user interface.
-* [Fedora](https://wiki.lyrasis.org/display/FF/Fedora+Repository+Home) - A robust, modular, open source repository system for the management and dissemination of digital content. The default smart storage for Islandora.
-* [Matomo](https://matomo.org/) - Matomo, formerly Piwik, is a free and open source web analytics application. It provides usage statistics and a rich dashboard for Islandora.
-* [MySQL](https://www.mysql.com/) - MySQL is an open-source relational database management system. Used as a Drupal database in Islandora, it can be easily replaced with other database management systems such as [PostgreSQL](https://www.postgresql.org/)
-* Triplestore - See Blazegraph.
+* [Apache]
+ * [ActiveMQ]
+ * [Tomcat]
+ * [Solr]
+* [Blazegraph]
+* [Cantaloupe]
+* [Drupal]
+* [FITS]
+* [Fedora (Repository Software)]
+* [MariaDB]
+* [NGINX]
+* [PostgreSQL]
+* [Traefik]
+* Triplestore - See [Blazegraph]
diff --git a/docs/technical-documentation/docs-build.md b/docs/technical-documentation/docs-build.md
index a84e06621..5ef536d99 100644
--- a/docs/technical-documentation/docs-build.md
+++ b/docs/technical-documentation/docs-build.md
@@ -1,83 +1,67 @@
# Introduction
-This documentation is built using [MkDocs](http://www.mkdocs.org/), a static site generator that is geared towards building project documentation. The documentation is created in the [Markdown](http://en.wikipedia.org/wiki/Markdown) format, and it all resides in the [`docs`](https://github.com/Islandora/documentation/tree/main/docs) directory in the repository. The organization of the documentation is controlled by the [`mkdocs.yml`](https://github.com/Islandora/documentation/blob/main/mkdocs.yml) in the root of the repository.
-
-!!! Tip "Video version available"
- Some of the material in this tutorial is presented in our video, [How to Build Documentation](https://youtu.be/YgSXicNow5w).
-
+This documentation is built using [Zensical](https://zensical.org/docs/get-started/), a static site generator that is geared towards building project documentation. The documentation is created in the [Markdown](http://en.wikipedia.org/wiki/Markdown) format. All of the documentation resides in the [`docs`](https://github.com/Islandora/documentation/tree/main/docs) directory in the repository. The organization of the documentation is controlled by the [`mkdocs.yml`](https://github.com/Islandora/documentation/blob/main/mkdocs.yml) in the root of the repository.
## Prerequisites
-You will need to have `mkdocs` software installed locally, as well as a required plugin and the MkDocs _Material_ theme. Below we will show you how to install `mkdocs` using the Python language's `pip` tool. For more details on installing and using MkDocs visit the [MkDocs installation guide](https://www.mkdocs.org/#installation).
-
-- Open a terminal window.
-
-- Install `mkdocs`:
-
- Windows / Linux:
-
- `sudo -H pip install mkdocs`
-
- macOS:
-
- `pip3 install mkdocs`
-
-
-
-- Install plugin to enable display of the last revision date:
-
- Windows / Linux:
-
- `sudo -H pip install mkdocs-git-revision-date-localized-plugin`
-
- macOS:
-
- `pip3 install mkdocs-git-revision-date-localized-plugin`
-
-
-- Install plugin to enable redirects:
+On your local machine you will need to have
- Windows / Linux:
+- [Docker](https://docs.docker.com/engine/install/)
+- `git`
+- `make`
- `sudo -H pip install mkdocs-redirects`
+If you're an Islandora committer or have access to edit Islandora Documentation, you can checkout [https://github.com/Islandora/documentation](https://github.com/Islandora/documentation)
- macOS:
+```
+git clone git@github.com:Islandora/documentation
+cd documentation
+git checkout -B "$(whoami)-patch-1"
+```
- `pip3 install mkdocs-redirects`
+If you can not make edits to the documentation you will need to fork [https://github.com/Islandora/documentation](https://github.com/Islandora/documentation) and run a similar command as above using your fork repo instead of `git@github.com:Islandora/documentation`
+## Make edits to the documentation locally
-- Install Material theme:
+You can view the docs at [http://localhost:8080](http://localhost:8080) by running
- Windows / Linux:
+```
+make serve
+```
- `sudo -H pip install mkdocs-material`
+This will create a live preview of Islandora's docs. While `make serve` is running you can then make edits to the markdown files and your web browser will automatically reload when you save the file.
- macOS:
+To stop the `make serve` command just type the key combination "Control-c".
- `pip3 install mkdocs-material`
+### Glossary and Snippets
+Islandora's docs leverage [Zensical's glossary](https://zensical.org/docs/authoring/tooltips/#add-a-glossary) feature.
+> The Snippets extension can be used to implement a simple glossary by moving all abbreviations in a dedicated file, and auto-append this file to all pages.
-## Build and Deploy documentation
+Our docs are configured so any term in [Islandora's Glossary](https://islandora.github.io/documentation/user-documentation/glossary/) can be referenced in any markdown file by wrapping the term in brackets `[]`. Adding brackets to a term will add a rich snippet for the term when someone hovers their mouse over the word. A snippet provides the definition of a term without needing to look it up in the glossary.
-Make sure you have all the submodules:
+If you a create a new term in `glossary.md` you can run the following `make` helper to get the snippets to work properly. Namely, it populates `./includes/abbreviations.md` with the correct markdown to the glossary terms so docs can reference them. This also runs in GitHub Actions before deploying incase this step is forgotten by a docs editor.
-`git submodule update --init --recursive`
+```
+make abbreviations
+```
-Documentation is build by running to the following command in the root of the repository:
+Only add terms to the glossary if they will be referenced by other documentation pages. If you just want a couple snippets on a particular docs page and they aren't generally useful you can define snippets on individual markdown pages.
-`mkdocs build --clean`
+Once you're done with your edits, put up a PR and a preview site will be created to show your changes. Make sure the changes are the same as your local build was showing you. If something doesn't look right, you can debug with the steps below.
-This command will create a static `site` folder in the root of the repository.
+## Debug GitHub Pages Preview
-You can preview any changes you have made to the documentation by running the following command:
+Islandora's documentation is deployed to GitHub Pages using GitHub Actions. When you create a PR on [https://github.com/Islandora/documentation](https://github.com/Islandora/documentation) a preview site of your changes will be made available by an automated comment on the PR.
-`mkdocs serve`
+If something in GitHub Pages doesn't look the same as it did on your local machine, you can use the steps below to help troubleshoot:
-And then visiting http://localhost:8111 in your browser.
+```
+make preview
+```
-To deploy documentation to GitHub Pages, issue the following command:
+This command will create a static `site` folder in the root of the repository. That set of HTML files is what GitHub Pages shows. The HTML will be available at [http://localhost:8080](http://localhost:8080).
-`mkdocs gh-deploy --clean`
+`make preview` won't have live edits like `make serve` does but you can use this to more closely replicate GitHub Pages' infrastructure and troubleshoot bugs or subtle differences between zensical's `serve` and `build` commands that may be undocumented or unknown.
-To stop the `mkdocs serve` command just type the key combination "Control-c".
+To stop the `make preview` command just type the key combination "Control-c".
diff --git a/docs/technical-documentation/ppa-documentation.md b/docs/technical-documentation/ppa-documentation.md
deleted file mode 100644
index c6bd02675..000000000
--- a/docs/technical-documentation/ppa-documentation.md
+++ /dev/null
@@ -1,143 +0,0 @@
-# Updating a `deb` and adding it to LYRASIS PPA
-
-## This page is here for archival purposes.
-
-If you are running Ubuntu 20.04 or higher, installing a specially-compiled imagemagick is no longer necessary. We are leaving this page available in case in the future we have a need to package our own `deb` and add it to the Lyrasis PPA.
-
-## Background
-
-Ubuntu removed JPEG 2000 support from ImageMagick since Vivid Vervet's 8:6.8.9.9-5 version. The PPA that we have created adds JPEG 2000 support back into ImageMagick for Ubuntu 16.04 and 18.04. More information on why JPEG 2000 support was removed can be found [here](https://bugs.launchpad.net/ubuntu/+source/imagemagick/+bug/1447968), and more information on `openjpeg` in Ubuntu can be found [here](https://bugs.launchpad.net/ubuntu/+source/openjpeg2/+bug/711061).
-
-## Prerequisites
-
-Review the [Ubuntu Packaging Guide](http://packaging.ubuntu.com/html/). Key items needed are in the [Getting Set Up section](http://packaging.ubuntu.com/html/getting-set-up.html):
-
-- Basic packaging software:
- - `sudo apt install gnupg pbuilder ubuntu-dev-tools apt-file`
-- Make sure you have your GPG, if you do not, follow the instructions in the guide.
-- Setup `pbuilder`:
- - `pbuilder-dist bionic create`
-- Configure your shell with some exports for `debuild`:
- - export DEBFULLNAME="Bob Dobbs"
- - export DEBEMAIL="subgenius@example.com"
-- Contact [Jonathan Green](https://github.com/jonathangreen) to get setup on the [LYRASIS PPA team](https://launchpad.net/~lyrasis).
-
-## Patching source
-
-`imagemagick` is used an example. If you need to only change or patch the actual source code, then you will need to use `quilt`. More information on using `quilt` can be found in the [Patches to Packages section](http://packaging.ubuntu.com/html/patches-to-packages.html). If you are only change dependencies, or information in the `debian` directory of a package, `quilt` is not required, and [if used will cause build failures](https://stackoverflow.com/questions/29634868/adding-a-file-in-a-quilt-dquilt-patch-patch-applies-correctly-by-hand-but-brea).
-
-- Create a directory to work in:
- - `mkdir imagemagick-bionic-jp2`
- - `cd imagemagick-bionic-jp2`
-- Pull down the source:
- - `pull-lp-source imagemagick bionic`
- - `cd imagemagick-6.9.7.4+dfsg`
-- Edit files as needed
-- Document the fix:
- - `dch` (**Make sure to change `UNRELEASED` to the Ubuntu release name. For example: `bionic`**)
-- Build the package:
- - `debuild -S`
-
-## Example Patch
-
-```diff
-Index: imagemagick-6.9.7.4+dfsg/debian/control
-===================================================================
---- imagemagick-6.9.7.4+dfsg.orig/debian/control
-+++ imagemagick-6.9.7.4+dfsg/debian/control
-@@ -26,8 +26,7 @@ Build-Depends: debhelper (>= 10),
- libx11-dev, libxext-dev, libxt-dev,
- # for plugins
- ghostscript, libdjvulibre-dev, libexif-dev,
-- libjpeg-dev,
--# libopenjp2-7-dev, Needed for JPEG 2000 but not in main see MIR #711061
-+ libjpeg-dev, libopenjp2-7-dev,
- libopenexr-dev, libperl-dev, libpng-dev, libtiff-dev,
- libwmf-dev,
- # libgraphviz-dev, incompatible license against fftw
-@@ -273,8 +272,7 @@ Depends: libmagickcore-6-headers (= ${so
- libmagickcore-6.q16-3 (= ${binary:Version}),
- libmagickcore-6.q16-3-extra (= ${binary:Version}),
- libbz2-dev, libdjvulibre-dev,
-- libexif-dev, libfreetype6-dev, libjpeg-dev,
--# libopenjp2-7-dev, Needed for JPEG 2000 but not in main see MIR #711061
-+ libexif-dev, libfreetype6-dev, libjpeg-dev, libopenjp2-7-dev,
- liblcms2-dev, liblqr-1-0-dev, libltdl-dev, libopenexr-dev, libpng-dev,
- librsvg2-dev, libtiff-dev, libwmf-dev, libx11-dev, libxext-dev, libxml2-dev,
- libxt-dev, zlib1g-dev,
-@@ -483,8 +481,7 @@ Depends: libmagickcore-6-headers (= ${so
- libmagickcore-6.q16hdri-3 (= ${binary:Version}),
- libmagickcore-6.q16hdri-3-extra (= ${binary:Version}),
- libbz2-dev, libdjvulibre-dev,
-- libexif-dev, libfreetype6-dev, libjpeg-dev,
--# libopenjp2-7-dev, Needed for JPEG 2000 but not in main see MIR #711061
-+ libexif-dev, libfreetype6-dev, libjpeg-dev, libopenjp2-7-dev,
- liblcms2-dev, liblqr-1-0-dev, libltdl-dev, libopenexr-dev, libpng-dev,
- librsvg2-dev, libtiff-dev, libwmf-dev, libx11-dev, libxext-dev, libxml2-dev,
- libxt-dev, zlib1g-dev,
-Index: imagemagick-6.9.7.4+dfsg/debian/control.d/noquantum.in
-===================================================================
---- imagemagick-6.9.7.4+dfsg.orig/debian/control.d/noquantum.in
-+++ imagemagick-6.9.7.4+dfsg/debian/control.d/noquantum.in
-@@ -26,8 +26,7 @@ Build-Depends: debhelper (>= 10),
- libx11-dev, libxext-dev, libxt-dev,
- # for plugins
- ghostscript, libdjvulibre-dev, libexif-dev,
-- libjpeg-dev,
--# libopenjp2-7-dev, Needed for JPEG 2000 but not in main see MIR #711061
-+ libjpeg-dev, libopenjp2-7-dev,
- libopenexr-dev, libperl-dev, libpng-dev, libtiff-dev,
- libwmf-dev,
- # libgraphviz-dev, incompatible license against fftw
-Index: imagemagick-6.9.7.4+dfsg/debian/control.d/quantum.in
-===================================================================
---- imagemagick-6.9.7.4+dfsg.orig/debian/control.d/quantum.in
-+++ imagemagick-6.9.7.4+dfsg/debian/control.d/quantum.in
-@@ -78,8 +78,7 @@ Depends: libmagickcore-${IMVERSION}-head
- libmagickcore-${IMVERSION}.${QUANTUMDEPTH}-${CORESOVERSION} (= ${binary:Version}),
- libmagickcore-${IMVERSION}.${QUANTUMDEPTH}-${CORESOVERSION}-extra (= ${binary:Version}),
- libbz2-dev, libdjvulibre-dev,
-- libexif-dev, libfreetype6-dev, libjpeg-dev,
--# libopenjp2-7-dev, Needed for JPEG 2000 but not in main see MIR #711061
-+ libexif-dev, libfreetype6-dev, libjpeg-dev, libopenjp2-7-dev,
- liblcms2-dev, liblqr-1-0-dev, libltdl-dev, libopenexr-dev, libpng-dev,
- librsvg2-dev, libtiff-dev, libwmf-dev, libx11-dev, libxext-dev, libxml2-dev,
- libxt-dev, zlib1g-dev,
-Index: imagemagick-6.9.7.4+dfsg/debian/rules
-===================================================================
---- imagemagick-6.9.7.4+dfsg.orig/debian/rules
-+++ imagemagick-6.9.7.4+dfsg/debian/rules
-@@ -98,7 +98,7 @@ STATIC_CONFIGURE_OPTIONS := \
- --with-gs-font-dir=/usr/share/fonts/type1/gsfonts \
- --with-magick-plus-plus \
- --with-djvu \
-- --with-openjp2 \
-+ --with-openjp2 \
- --with-wmf \
- --without-gvc \
- --enable-shared \
-```
-
-## Uploading to LYRASIS PPA
-
-Once the package is built successfully, you'll upload the `changes` file to Launchpad. For example:
-
-- `dput ppa:lyrasis/imagemagick-jp2 imagemagick_6.9.7.4+dfsg-16ubuntu6.8_source.changes`
-
-After the package is successfully uploaded to the PPA, you'll receive an email from Launchpad. Something like this:
-
-```
-Accepted:
- OK: imagemagick_6.9.7.4+dfsg.orig.tar.xz
- OK: imagemagick_6.9.7.4+dfsg-16ubuntu6.8.debian.tar.xz
- OK: imagemagick_6.9.7.4+dfsg-16ubuntu6.8.dsc
- -> Component: main Section: graphics
-
-imagemagick (8:6.9.7.4+dfsg-16ubuntu6.8) bionic; urgency=medium
-
- * Add jp2 support.
-```
-
-And you will see it in the interface for the Lyrasis PPA:
-
-
diff --git a/docs/technical-documentation/resizing-vm.md b/docs/technical-documentation/resizing-vm.md
deleted file mode 100644
index ba7237c7b..000000000
--- a/docs/technical-documentation/resizing-vm.md
+++ /dev/null
@@ -1,30 +0,0 @@
-## Resize vagrant machine
-To expand virtual machine's hard drive for testing of larger files. Once the VM has started, you'll need to `halt` the VM, download and run the script, tell it what size (in MB) and then start the VM.
-The last step `vagrant ssh --command "sudo resize2fs /dev/sda1"` is a check. It should return there was nothing to do. If you already provisioned you VM you can skip the 2 steps with provisioning in them.
-
-```shell
-# Skip this if you VM is already provisioned.
-$ vagrant up --no-provision <-- Exclude if already running and provisioned.
-
-$ vagrant halt
-
-# Download and run. This will default to the correct name (just press enter) then give the size.
-# Example: `350000` is equal to 350GB
-
-$ wget https://gist.githubusercontent.com/DonRichards/6dc6c81ae9fc22cba8d7a57b90ab1509/raw/45017e07a3b93657f8822dfbbe4fc690169cdabc/expand_disk.py
-$ chmod +x expand_disk.py
-$ python expand_disk.py
-$ vagrant up --no-provision
-
-# This step isn't needed but acts as a check to verify it worked.
-$ vagrant ssh --command "sudo resize2fs /dev/sda1"
-
-# Skip this if you VM is already provisioned.
-$ vagrant provision <-- Exclude if already provisioned.
-```
-
-### Troubleshooting expand_disk.py
-You may need to remove the "resized" version. Assuming your VM location is `~/VirtualBox\ VMs`
-```shell
-$ rm -rf ~/VirtualBox\ VMs/Islandora\ CLAW\ Ansible_resized
-```
diff --git a/docs/user-documentation/flysystem.md b/docs/user-documentation/flysystem.md
index 5d4bd4b0c..efcef381b 100644
--- a/docs/user-documentation/flysystem.md
+++ b/docs/user-documentation/flysystem.md
@@ -1,6 +1,6 @@
# Flysystem
-Islandora uses [Flysystem](https://github.com/thephpleague/flysystem) and the [associated Drupal module](https://www.drupal.org/project/flysystem) to persist binary files to Fedora instead of keeping a copy in both Drupal and Fedora.
+Islandora uses [Flysystem] and the [associated Drupal module](https://www.drupal.org/project/flysystem) to persist binary files to Fedora instead of keeping a copy in both Drupal and [Fedora (Repository Software)].
## Background
diff --git a/docs/user-documentation/glossary.md b/docs/user-documentation/glossary.md
index 321460a27..86942e6e2 100644
--- a/docs/user-documentation/glossary.md
+++ b/docs/user-documentation/glossary.md
@@ -2,6 +2,9 @@
The following glossary of terms addresses an Islandora context. When comparing new Islandora and Fedora to older versions it may also be helpful to reference [the Islandora 7 Glossary](https://wiki.duraspace.org/display/ISLANDORA/APPENDIX+E+-+Glossary).
+## ActiveMQ
+Apache ActiveMQ is a JMS compliant Messaging Queue. Messaging client can make use of JMS to send messages.
+
## Alpaca
Islandora's event-driven middleware based on [Apache Camel](http://camel.apache.org/) that handles communication between various components of Islandora, for instance synchronizing [Drupal](#drupal) data with a [Fedora](#fedora-repository-software) repository and the [Blazegraph](#blazegraph) triple store.
@@ -80,10 +83,17 @@ The standard Drupal Content types are 'Article' and 'Basic page'. _Islandora Sta
## Context
An "if-this-then-that" configuration created using the Drupal [Context](https://www.drupal.org/project/context) contrib module. Islandora extends the capabilities of Context by adding custom Conditions, custom Reactions, and by evaluating context at specific times to allow Contexts to be used for derivatives, indexing, and display.
+## Composer
+Composer is a tool for dependency management in PHP. It allows you to declare the libraries your project depends on and it will manage (install/update) them for you.
+
+[https://getcomposer.org/](https://getcomposer.org/)
## Crayfish
A collection of Islandora [microservices](#microservice). Some microservices are built specifically for use with a [Fedora](#fedora-repository-software) repository, while others are just for general use within Islandora.
+## Crayfits
+Crayfits is a [microservice](#microservice) wrapper of [FITS](#fits) for identifying, validating and extracting of technical metadata for a wide range of file formats.
+
## Datastream
Deprecated terminology, refers to how [Fedora 3](#fedora-repository-software)/Islandora Legacy stored files as part of a resource ('object') in the [Fedora](#fedora-repository-software) repository. Replaced by [Drupal Media entities](https://www.drupal.org/docs/8/core/modules/media/overview), which 'wraps' [Files](https://www.drupal.org/docs/8/core/modules/file/overview) in an intermediate structure. This allows Fields to be attached to files, for instance for storing technical metadata.
@@ -93,6 +103,9 @@ A version of a file which is derived from an uploaded file. For example, a thumb
## Docker
[Docker](https://www.docker.com/) is a platform that use OS-level virtualization to deliver software in packages called containers. Islandora uses Docker as part of [ISLE](#isle), a suite of Docker containers that run the various components of Islandora.
+## Docker Compose
+[Docker Compose](https://docs.docker.com/compose/) is a tool for defining and running multi-container applications. It is the key to unlocking a streamlined and efficient development and deployment experience.
+
## Drupal
Drupal is an open source web content management system (CMS) written in PHP. Known for being extremely flexible and extensible, Drupal is supported by a community of over 630,000 users and developers. Drupal sites can be customized and themed in a wide variety of ways. Drupal sites must include [Drupal Core](#drupal-core) and usually involve additional, Contributed code.
@@ -102,6 +115,11 @@ The files, themes, profile, and modules included with the standard project softw
## Drupal Roles
Roles are a way of assigning specific permissions to a group of users. Any user assigned to a role will have the same permissions as all other users assigned to that role. This allows you to control which users have permission to view, edit, or delete content in [Drupal](#drupal). Islandora provides a special role called _fedoraAdmin_ that is required to have actions in Drupal reflected in [Fedora](#fedora-repository-software).
+## Drush
+Drush is a command line shell and Unix scripting interface for Drupal. Drush core ships with lots of [useful commands](https://www.drush.org/13.x/commands/all/) and [generators](https://www.drush.org/13.x/generators/all/). Similarly, it runs update.php, executes SQL queries, runs content migrations, and misc utilities like cron or cache rebuild. Drush can be extended by [3rd party commandfiles](https://www.drupal.org/project/project_module?f[2]=im_vid_3%3A4654).
+
+[https://www.drush.org/](https://www.drush.org/)
+
## Entity
A [Drupal](#drupal) term for an item of either content or configuration data. Examples include [Nodes](#node) (content items), Blocks, [Taxonomy terms](#taxonomy-term), and definitions of [content types](#content-type); the first three are [content entities](#content-entity), and the last is a [configuration entity](#configuration-entity). In common usage, the term often refers to Drupal content entities like [Nodes](#node) or [Taxonomy terms](#taxonomy-term).
@@ -174,21 +192,19 @@ Acronym for "Graphical User Interface". Often refers to taking actions through D
An installation without legacy constraints. Usually refers to a brand new system where users load new content, as opposed to migrating content from a previous system.
## Imagemagick
-[Imagemagick](https://imagemagick.org/index.php) is an open-source image processing library. In Islandora, Imagemagick is provided by the [Crayfish](#crayfish) [Microservice](#microservice), [Houdini](#houdini).
+[Imagemagick](https://imagemagick.org/index.php) is an open-source image processing library. In Islandora, Imagemagick is provided by the [scyllaridae](#scyllaridae) [Microservice](#microservice), [Houdini](#houdini).
## hOCR
[hOCR](https://kba.github.io/hocr-spec/1.2/) is an open standard for representing OCR (Optical Character Recognition) results, including text positioning, as HTML. hOCR can be produced by [Tesseract](#tesseract), and can be displayed as an overlay on an image by [Mirador](#mirador).
-
## Homarus
-Homarus is a [microservice](#microservice) wrapper for [FFMpeg](#ffmpeg). It is part of [Crayfish](#crayfish).
+Homarus is a [microservice](#microservice) wrapper of [FFMpeg](#ffmpeg) for generating video and audio derivatives. It is implemented using [scyllaridae](#scyllaridae).
## Houdini
-Houdini is a [microservice](#microservice) wrapper for [Imagemagick](#imagemagick). It is part of [Crayfish](#crayfish).
-
+Houdini is a [microservice](#microservice) wrapper of [Imagemagick](#imagemagick) for generating image-based derivatives, including thumbnails. It is implemented using [scyllaridae](#scyllaridae).
## Hypercube
-Hypercube is a [microservice](#microservice) wrapper for [Tesseract](#tesseract). It is part of [Crayfish](#crayfish).
+Hypercube is a [microservice](#microservice) wrapper of [Tesseract](#tesseract) for optical character recognition (OCR) and [hOCR](#hOCR). It is implemented using [scyllaridae](#scyllaridae).
## IIIF
The [International Image Interoperability Framework](https://iiif.io/). Generally pronounced "triple-eye-eff." A set of open standards and APIs that help archives, libraries, and museums make the most of their digitized collections with deep zoom, annotation capabilities, and more, and also the community of users and developers that support the framework.
@@ -239,6 +255,15 @@ A set of human-readable [YAML](#yaml) files, containing instructions for automat
## ISLE
ISLE, or ISLandora Enterprise, is a community initiative to ease the installation and maintenance of Islandora by using [Docker](#docker). ISLE is one of the installation methods currently supported by the Islandora community.
+## ISLE Site Template
+[ISLE Site Template](https://github.com/Islandora-Devops/isle-site-template) is a [Docker Compose] project provided in [a GitHub Template](https://docs.github.com/en/repositories/creating-and-managing-repositories/creating-a-repository-from-a-template). The project is used for building and customizing your institution's Islandora installation, for use as both a development and production environment.
+
+Creating a GitHub repository from a template is similar to [forking a git repository](https://docs.github.com/en/pull-requests/collaborating-with-pull-requests/working-with-forks/fork-a-repo), but there are important differences:
+
+- A new GitHub fork includes the entire commit history of the parent git repository, while a git repository created from a template starts with a single commit.
+- A GitHub fork can be a temporary way to contribute code to an existing project, while creating a git repository from a template starts a **new** project quickly.
+
+
## JSON-LD
[JSON-LD (JavaScript Object Notation for Linked Data)](https://json-ld.org/) is a method of encoding [linked data](#linked-data) using JSON.
@@ -248,6 +273,10 @@ In computing, linked data is structured data which is interlinked with other dat
## Manifest
See [IIIF Manifest](#iiif-manifest).
+## MariaDB
+MariaDB Server is one of the most popular database servers in the world. It’s made by the original developers of MySQL and guaranteed to stay open source. Notable users include Wikipedia, WordPress.com and Google.
+
+[https://mariadb.org/](https://mariadb.org/)
## Matomo
[Matomo](https://matomo.org/), formerly called Piwik, is a software for tracking visits to websites. It is an open source alternative to Google Analytics and allows the generation of website usage reports.
@@ -266,9 +295,18 @@ Protocol specification that allows a web client to request an earlier/historic s
## Microservice
A software development technique — a variant of the service-oriented architecture (SOA) structural style — that arranges an application as a collection of loosely coupled services. In a microservices' architecture, services are fine-grained and the protocols are lightweight.
+## Milliner
+
+A microservice that converts Drupal entities into Fedora resources.
+
## Module
Software (usually PHP, JavaScript, and/or CSS) that extends site features and adds functionality. [Drupal](#drupal) modules conform to a specific structure allowing them to integrate with the Drupal architecture.
+## NGINX
+nginx ("engine x") is an HTTP web server, reverse proxy, content cache, load balancer, TCP/UDP proxy server, and mail proxy server. Originally written by [Igor Sysoev](http://sysoev.ru/en/) and distributed under the [2-clause BSD License](https://nginx.org/LICENSE). Enterprise distributions, commercial support and training are [available from F5, Inc.](https://nginx.org/en/enterprise.html)
+
+[https://nginx.org/](https://nginx.org/)
+
## Node
Usually refers to a piece of Drupal [Content](#content-entity) of the type 'Node'. This includes actual pages, articles, and [Resource nodes](#resource-node). Nodes must belong to a specific node bundle, called a ["Content Type"](#content-type).
@@ -284,6 +322,11 @@ Open source describes a method of software development that promotes access to t
## OpenSeadragon
[OpenSeadragon](https://openseadragon.github.io/) is javascript-based zoomable image [Viewer](#viewer). It has the ability to do zooming and display multiple pages. To render an image through OpenSeadragon, it must be provided in a [IIIF Manifest](#iiif-manifest).
+## PostgreSQL
+PostgreSQL is a powerful, open source object-relational database system with over 35 years of active development that has earned it a strong reputation for reliability, feature robustness, and performance.
+
+[https://www.postgresql.org/](https://www.postgresql.org/)
+
## PR
See [Pull request](#pull-request)
@@ -307,6 +350,14 @@ The term 'Resource node' is specific to Islandora. Typically, Resource nodes in
For example, a video stored in Islandora will have a Resource node, with metadata stored in [Fields](#field). Attached to the Resource node is a [Media](#media) entity, which encapsulates the preservation-grade file. The Resource node may be linked to further [Media](#media), for instance for a thumbnail, web-friendly derivative, and technical metadata associated with the resource node. The Resource node may also belong to one or more collections.
+## Solr
+An open-source enterprise-search platform. Solr is the default search and discover layer of Islandora, and a key component in some methods for [migration to Islandora from Islandora Legacy](https://github.com/Islandora-devops/migrate_7x_claw)
+
+[https://lucene.apache.org/solr/](https://lucene.apache.org/solr/)
+
+## scyllaridae
+A framework for building Islandora [microservices](#microservice).
+
## Source Field
A Drupal term for the main file-[type](#field-type) [field](#field) on a [Media](#media). The names of these fields differ across Media Types, such as "Image" (`field_media_image`) on Image media, and "Video File" (`field_media_video_file`) on Video media. While it is possible to add other fields, including file fields, to a Media, the source field is the one configured during the creation of a Media Type. Islandora provides utility functions to get the source field from a Media ([MediaSourceService.php](https://github.com/Islandora/islandora/blob/2.x/src/MediaSource/MediaSourceService.php)).
@@ -316,10 +367,20 @@ A [Drupal](#drupal) [Content Entity](#content-entity) of the type 'taxonomy term
## Tesseract
[Tesseract](https://github.com/tesseract-ocr/tesseract) is an open-source OCR (Optical Character Recognition) software. It can perform OCR in multiple languages. It can produce OCR (plain text) and [hOCR](#hocr) (HTML, which includes positional data). In Islandora, Tesseract is provided by the [Crayfish](#crayfish) [Microservice](#microservice), [Hypercube](#hypercube).
-
## Theme
Software and asset files (images, CSS, PHP code, and/or templates) that determine the style and layout of the site. The [Drupal](#drupal) project distinguishes between core and contributed themes.
+## Tomcat
+An open-source implementation of the Java Servlet, JavaServer Pages, Java Expression Language and WebSocket technologies. Tomcat provides a "pure Java" HTTP web server environment in which Java code can run.
+
+[http://tomcat.apache.org/](http://tomcat.apache.org/)
+
+## Traefik
+
+Traefik is a leading modern open source reverse proxy and ingress controller that makes deploying services and APIs easy. Traefik integrates with your existing infrastructure components and configures itself automatically and dynamically.
+
+[https://traefik.io/traefik](https://traefik.io/traefik)
+
## Vagrant
[Vagrant](https://www.vagrantup.com/) is an open-source software product for building and maintaining portable virtual software development environments (virtual machines). The [Islandora Playbook](#islandora-playbook) includes a 'vagrantfile', a set of instructions that allows users to create a local virtual machine environment which will subsequently run [Ansible](#ansible) to execute the configuration and installation steps recorded in the [Islandora Playbook](#islandora-playbook).
diff --git a/includes/abbreviations.md b/includes/abbreviations.md
new file mode 100644
index 000000000..23c2cf7d1
--- /dev/null
+++ b/includes/abbreviations.md
@@ -0,0 +1,201 @@
+[ActiveMQ]: ../user-documentation/glossary.md#activemq
+[ActiveMQs]: ../user-documentation/glossary.md#activemq
+[Alpaca]: ../user-documentation/glossary.md#alpaca
+[Alpacas]: ../user-documentation/glossary.md#alpaca
+[Ansible]: ../user-documentation/glossary.md#ansible
+[Ansibles]: ../user-documentation/glossary.md#ansible
+[Apache]: ../user-documentation/glossary.md#apache
+[Apaches]: ../user-documentation/glossary.md#apache
+[API]: ../user-documentation/glossary.md#api
+[APIs]: ../user-documentation/glossary.md#api
+[Application Programming Interface]: ../user-documentation/glossary.md#application-programming-interface
+[Application Programming Interfaces]: ../user-documentation/glossary.md#application-programming-interface
+[Blazegraph]: ../user-documentation/glossary.md#blazegraph
+[Blazegraphs]: ../user-documentation/glossary.md#blazegraph
+[Bundle]: ../user-documentation/glossary.md#bundle
+[Bundles]: ../user-documentation/glossary.md#bundle
+[Cantaloupe]: ../user-documentation/glossary.md#cantaloupe
+[Cantaloupes]: ../user-documentation/glossary.md#cantaloupe
+[Checksum]: ../user-documentation/glossary.md#checksum
+[Checksums]: ../user-documentation/glossary.md#checksum
+[CLAW]: ../user-documentation/glossary.md#claw
+[CLAWs]: ../user-documentation/glossary.md#claw
+[Collection]: ../user-documentation/glossary.md#collection
+[Collections]: ../user-documentation/glossary.md#collection
+[Configuration]: ../user-documentation/glossary.md#configuration
+[Configurations]: ../user-documentation/glossary.md#configuration
+[Configuration entity]: ../user-documentation/glossary.md#configuration-entity
+[Configuration entities]: ../user-documentation/glossary.md#configuration-entity
+[Content]: ../user-documentation/glossary.md#content
+[Contents]: ../user-documentation/glossary.md#content
+[Content entity]: ../user-documentation/glossary.md#content-entity
+[Content entities]: ../user-documentation/glossary.md#content-entity
+[Content model]: ../user-documentation/glossary.md#content-model
+[Content models]: ../user-documentation/glossary.md#content-model
+[Content type]: ../user-documentation/glossary.md#content-type
+[Content types]: ../user-documentation/glossary.md#content-type
+[Context]: ../user-documentation/glossary.md#context
+[Contexts]: ../user-documentation/glossary.md#context
+[Composer]: ../user-documentation/glossary.md#composer
+[Composers]: ../user-documentation/glossary.md#composer
+[Crayfish]: ../user-documentation/glossary.md#crayfish
+[Crayfishs]: ../user-documentation/glossary.md#crayfish
+[Crayfits]: ../user-documentation/glossary.md#crayfits
+[Datastream]: ../user-documentation/glossary.md#datastream
+[Datastreams]: ../user-documentation/glossary.md#datastream
+[Derivative]: ../user-documentation/glossary.md#derivative
+[Derivatives]: ../user-documentation/glossary.md#derivative
+[Docker]: ../user-documentation/glossary.md#docker
+[Dockers]: ../user-documentation/glossary.md#docker
+[Docker Compose]: ../user-documentation/glossary.md#docker-compose
+[Docker Composes]: ../user-documentation/glossary.md#docker-compose
+[Drupal]: ../user-documentation/glossary.md#drupal
+[Drupals]: ../user-documentation/glossary.md#drupal
+[Drupal Core]: ../user-documentation/glossary.md#drupal-core
+[Drupal Cores]: ../user-documentation/glossary.md#drupal-core
+[Drupal Roles]: ../user-documentation/glossary.md#drupal-roles
+[Drush]: ../user-documentation/glossary.md#drush
+[Drushs]: ../user-documentation/glossary.md#drush
+[Entity]: ../user-documentation/glossary.md#entity
+[Entities]: ../user-documentation/glossary.md#entity
+[Fedora (Repository Software)]: ../user-documentation/glossary.md#fedora-repository-software
+[Fedora (Repository Software)s]: ../user-documentation/glossary.md#fedora-repository-software
+[FFmpeg]: ../user-documentation/glossary.md#ffmpeg
+[FFmpegs]: ../user-documentation/glossary.md#ffmpeg
+[Field]: ../user-documentation/glossary.md#field
+[Fields]: ../user-documentation/glossary.md#field
+[Field instance]: ../user-documentation/glossary.md#field-instance
+[Field instances]: ../user-documentation/glossary.md#field-instance
+[Field formatter]: ../user-documentation/glossary.md#field-formatter
+[Field formatters]: ../user-documentation/glossary.md#field-formatter
+[Field type]: ../user-documentation/glossary.md#field-type
+[Field types]: ../user-documentation/glossary.md#field-type
+[Field Storage]: ../user-documentation/glossary.md#field-storage
+[Field Storages]: ../user-documentation/glossary.md#field-storage
+[FITS]: ../user-documentation/glossary.md#fits
+[Fixity]: ../user-documentation/glossary.md#fixity
+[Fixities]: ../user-documentation/glossary.md#fixity
+[Flysystem]: ../user-documentation/glossary.md#flysystem
+[Flysystems]: ../user-documentation/glossary.md#flysystem
+[GLAM]: ../user-documentation/glossary.md#glam
+[GLAMs]: ../user-documentation/glossary.md#glam
+[GUI]: ../user-documentation/glossary.md#gui
+[GUIs]: ../user-documentation/glossary.md#gui
+[Greenfield]: ../user-documentation/glossary.md#greenfield
+[Greenfields]: ../user-documentation/glossary.md#greenfield
+[Imagemagick]: ../user-documentation/glossary.md#imagemagick
+[Imagemagicks]: ../user-documentation/glossary.md#imagemagick
+[hOCR]: ../user-documentation/glossary.md#hocr
+[hOCRs]: ../user-documentation/glossary.md#hocr
+[Homarus]: ../user-documentation/glossary.md#homarus
+[Houdini]: ../user-documentation/glossary.md#houdini
+[Houdinis]: ../user-documentation/glossary.md#houdini
+[Hypercube]: ../user-documentation/glossary.md#hypercube
+[Hypercubes]: ../user-documentation/glossary.md#hypercube
+[IIIF]: ../user-documentation/glossary.md#iiif
+[IIIFs]: ../user-documentation/glossary.md#iiif
+[IIIF Manifest]: ../user-documentation/glossary.md#iiif-manifest
+[IIIF Manifests]: ../user-documentation/glossary.md#iiif-manifest
+[Ingest]: ../user-documentation/glossary.md#ingest
+[Ingests]: ../user-documentation/glossary.md#ingest
+[Islandora 8 (8.x, 2.0)]: ../user-documentation/glossary.md#islandora-8-8x-20
+[Islandora 8 (8.x, 2.0)s]: ../user-documentation/glossary.md#islandora-8-8x-20
+[Islandora Install Profile]: ../user-documentation/glossary.md#islandora-install-profile
+[Islandora Install Profiles]: ../user-documentation/glossary.md#islandora-install-profile
+[Islandora Starter Site]: ../user-documentation/glossary.md#islandora-starter-site
+[Islandora Starter Sites]: ../user-documentation/glossary.md#islandora-starter-site
+[Islandora model]: ../user-documentation/glossary.md#islandora-model
+[Islandora models]: ../user-documentation/glossary.md#islandora-model
+[Islandora playbook]: ../user-documentation/glossary.md#islandora-playbook
+[Islandora playbooks]: ../user-documentation/glossary.md#islandora-playbook
+[ISLE]: ../user-documentation/glossary.md#isle
+[ISLEs]: ../user-documentation/glossary.md#isle
+[ISLE Site Template]: ../user-documentation/glossary.md#isle-site-emplate
+[ISLE Site Templates]: ../user-documentation/glossary.md#isle-site-emplate
+[JSON-LD]: ../user-documentation/glossary.md#json-ld
+[JSON-LDs]: ../user-documentation/glossary.md#json-ld
+[Linked data]: ../user-documentation/glossary.md#linked-data
+[Linked datas]: ../user-documentation/glossary.md#linked-data
+[Manifest]: ../user-documentation/glossary.md#manifest
+[Manifests]: ../user-documentation/glossary.md#manifest
+[MariaDB]: ../user-documentation/glossary.md#mariadb
+[MariaDBs]: ../user-documentation/glossary.md#mariadb
+[Matomo]: ../user-documentation/glossary.md#matomo
+[Matomos]: ../user-documentation/glossary.md#matomo
+[Media]: ../user-documentation/glossary.md#media
+[Medias]: ../user-documentation/glossary.md#media
+[Memento]: ../user-documentation/glossary.md#memento
+[Mementos]: ../user-documentation/glossary.md#memento
+[Mirador]: ../user-documentation/glossary.md#mirador
+[Miradors]: ../user-documentation/glossary.md#mirador
+[Microservice]: ../user-documentation/glossary.md#microservice
+[Microservices]: ../user-documentation/glossary.md#microservice
+[Milliner]: ../user-documentation/glossary.md#milliner
+[Milliners]: ../user-documentation/glossary.md#milliner
+[Module]: ../user-documentation/glossary.md#module
+[Modules]: ../user-documentation/glossary.md#module
+[NGINX]: ../user-documentation/glossary.md#nginx
+[NGINXs]: ../user-documentation/glossary.md#nginx
+[Node]: ../user-documentation/glossary.md#node
+[Nodes]: ../user-documentation/glossary.md#node
+[OAI-PMH]: ../user-documentation/glossary.md#oai-pmh
+[OAI-PMHs]: ../user-documentation/glossary.md#oai-pmh
+[Ontology]: ../user-documentation/glossary.md#ontology
+[Ontologies]: ../user-documentation/glossary.md#ontology
+[Open Source]: ../user-documentation/glossary.md#open-source
+[Open Sources]: ../user-documentation/glossary.md#open-source
+[OpenSeadragon]: ../user-documentation/glossary.md#openseadragon
+[OpenSeadragons]: ../user-documentation/glossary.md#openseadragon
+[PostgreSQL]: ../user-documentation/glossary.md#postgresql
+[PostgreSQLs]: ../user-documentation/glossary.md#postgresql
+[PR]: ../user-documentation/glossary.md#pr
+[PRs]: ../user-documentation/glossary.md#pr
+[Pull request]: ../user-documentation/glossary.md#pull-request
+[Pull requests]: ../user-documentation/glossary.md#pull-request
+[RDF]: ../user-documentation/glossary.md#rdf
+[RDFs]: ../user-documentation/glossary.md#rdf
+[Repository Item]: ../user-documentation/glossary.md#repository-item
+[Repository Items]: ../user-documentation/glossary.md#repository-item
+[Resource Description Framework]: ../user-documentation/glossary.md#resource-description-framework
+[Resource Description Frameworks]: ../user-documentation/glossary.md#resource-description-framework
+[Resource Node]: ../user-documentation/glossary.md#resource-node
+[Resource Nodes]: ../user-documentation/glossary.md#resource-node
+[Solr]: ../user-documentation/glossary.md#solr
+[Solrs]: ../user-documentation/glossary.md#solr
+[scyllaridae]: ../user-documentation/glossary.md#scyllaridae
+[scyllaridaes]: ../user-documentation/glossary.md#scyllaridae
+[Source Field]: ../user-documentation/glossary.md#source-field
+[Source Fields]: ../user-documentation/glossary.md#source-field
+[Taxonomy term]: ../user-documentation/glossary.md#taxonomy-term
+[Taxonomy terms]: ../user-documentation/glossary.md#taxonomy-term
+[Tesseract]: ../user-documentation/glossary.md#tesseract
+[Tesseracts]: ../user-documentation/glossary.md#tesseract
+[Theme]: ../user-documentation/glossary.md#theme
+[Themes]: ../user-documentation/glossary.md#theme
+[Tomcat]: ../user-documentation/glossary.md#tomcat
+[Tomcats]: ../user-documentation/glossary.md#tomcat
+[Traefik]: ../user-documentation/glossary.md#traefik
+[Traefiks]: ../user-documentation/glossary.md#traefik
+[Vagrant]: ../user-documentation/glossary.md#vagrant
+[Vagrants]: ../user-documentation/glossary.md#vagrant
+[VBO]: ../user-documentation/glossary.md#vbo
+[VBOs]: ../user-documentation/glossary.md#vbo
+[View]: ../user-documentation/glossary.md#view
+[Views]: ../user-documentation/glossary.md#view
+[View Mode]: ../user-documentation/glossary.md#view-mode
+[View Modes]: ../user-documentation/glossary.md#view-mode
+[Viewer]: ../user-documentation/glossary.md#viewer
+[Viewers]: ../user-documentation/glossary.md#viewer
+[Views Bulk Operations]: ../user-documentation/glossary.md#views-bulk-operations
+[Virtual Machine Image]: ../user-documentation/glossary.md#virtual-machine-image
+[Virtual Machine Images]: ../user-documentation/glossary.md#virtual-machine-image
+[Vocabulary]: ../user-documentation/glossary.md#vocabulary
+[Vocabularies]: ../user-documentation/glossary.md#vocabulary
+[Weight]: ../user-documentation/glossary.md#weight
+[Weights]: ../user-documentation/glossary.md#weight
+[White Screen of Death ]: ../user-documentation/glossary.md#white-screen-of-death
+[White Screen of Death s]: ../user-documentation/glossary.md#white-screen-of-death
+[WSoD]: ../user-documentation/glossary.md#wsod
+[WSoDs]: ../user-documentation/glossary.md#wsod
+[YAML]: ../user-documentation/glossary.md#yaml
+[YAMLs]: ../user-documentation/glossary.md#yaml
diff --git a/mkdocs.yml b/mkdocs.yml
index 8375bb2af..e247e4c88 100644
--- a/mkdocs.yml
+++ b/mkdocs.yml
@@ -1,44 +1,111 @@
site_name: Islandora
-site_dir: site
-docs_dir: docs
site_description: Documentation for Islandora
dev_addr: 'localhost:8111'
repo_url: https://github.com/Islandora/documentation
-site_url: https://islandora.github.io/documentation/
+site_url: !ENV [SITE_URL, 'https://islandora.github.io/documentation/']
edit_uri: 'edit/main/docs/'
theme:
- name: 'material'
- palette:
- primary: 'green'
- accent: 'blue'
- font:
- text: 'Roboto'
- code: 'Roboto Mono'
- logo: 'assets/Islandora_logo.png'
- language: 'en'
+ name: material
features:
+ - navigation.instant
+ - navigation.instant.prefetch
- content.action.edit
+ - content.action.view
+ - content.code.annotate
+ - content.code.copy
+ - content.code.select
+ - content.footnote.tooltips
+ - content.tabs.link
+ - content.tooltips
+ - search.highlight
+ palette:
+ - media: "(prefers-color-scheme)"
+ toggle:
+ icon: lucide/sun-moon
+ name: Switch to light mode
+ - media: "(prefers-color-scheme: light)"
+ scheme: default
+ primary: indigo
+ accent: indigo
+ toggle:
+ icon: lucide/sun
+ name: Switch to dark mode
+ - media: "(prefers-color-scheme: dark)"
+ scheme: slate
+ primary: indigo
+ accent: orange
+ toggle:
+ icon: lucide/moon-star
+ name: Switch to system preference
icon:
- edit: material/pencil
+ tag:
+ default: lucide/hash
+ logo: 'assets/Islandora_logo.png'
+
markdown_extensions:
+ - abbr
- admonition
+ - attr_list
+ - def_list
- footnotes
+ - md_in_html
- toc:
- permalink: True
+ permalink: true
+ - zensical.extensions.preview:
+ targets:
+ include:
+ - user-documentation/glossary.md
+ - pymdownx.arithmatex:
+ generic: true
+ - pymdownx.betterem:
+ smart_enable: all
+ - pymdownx.caret
- pymdownx.details
+ - pymdownx.emoji:
+ emoji_generator: !!python/name:zensical.extensions.emoji.to_svg
+ emoji_index: !!python/name:zensical.extensions.emoji.twemoji
- pymdownx.highlight:
anchor_linenums: true
line_spans: __span
pygments_lang_class: true
- pymdownx.inlinehilite
- - pymdownx.snippets
- - pymdownx.superfences
+ - pymdownx.keys
+ - pymdownx.magiclink:
+ normalize_issue_symbols: true
+ repo_url_shorthand: true
+ user: zensical
+ repo: zensical
+ - pymdownx.mark
+ - pymdownx.smartsymbols
+ - pymdownx.snippets:
+ auto_append:
+ - includes/abbreviations.md
+ - pymdownx.superfences:
+ custom_fences:
+ - name: mermaid
+ class: mermaid
+ format: !!python/name:pymdownx.superfences.fence_code_format
+ - pymdownx.tabbed:
+ alternate_style: true
+ combine_header_slug: true
+ slugify: !!python/object/apply:pymdownx.slugs.slugify
+ kwds:
+ case: lower
+ - pymdownx.tasklist:
+ custom_checkbox: true
+ - pymdownx.tilde
+
+
extra_css:
- css/custom.css
+extra_javascript:
+ - js/mermaid-init.mjs
+ - js/diagram-animation.js
plugins:
- search
+ - tags
- git-revision-date-localized
- redirects:
redirect_maps:
@@ -81,17 +148,19 @@ plugins:
extra:
- font:
- text: 'Roboto'
- code: 'Roboto Mono'
- author:
- twitter: 'islandora'
- palette:
- primary: 'red'
- accent: 'red'
+ scope: /
+ annotate:
+ json: [.s2]
+ status:
+ new: Recently added
+ deprecated: Deprecated
social:
- - icon: fontawesome/brands/twitter
- link: 'https://twitter.com/islandora'
+ - icon: fontawesome/brands/github
+ link: https://github.com/islandora
+ - icon: fontawesome/brands/bluesky
+ link: https://bsky.app/profile/islandora.bsky.social
+ tags:
+ default: default
copyright: This documentation is user-sourced! Suggestions and comments are welcome in our issue queue.
@@ -254,12 +323,7 @@ nav:
- 'Islandora 7': 'technical-documentation/migrate-7x.md'
- Developer Resources:
- 'Stack Overview': 'installation/component-overview.md'
- # moved from "Installation" section; alternatively duplicate a simplified version in the overview section
- # also see Architecture Diagram in Developer documentation
- # also see user-intro.md#architecture for the cheeseburger/bento box analogy
- # Conceptual/ reference, all user roles/ sys admins/ developers:
- # procedural information should be moved out installation guides
- - 'Architecture Diagram': 'technical-documentation/diagram.md'
+ - 'Islandora Architecture': 'technical-documentation/diagram.md'
- REST Documentation:
- 'Introduction': 'technical-documentation/using-rest-endpoints.md'
- 'Authorization': 'technical-documentation/rest-authorization.md'
@@ -271,10 +335,7 @@ nav:
- Alpaca:
- 'Alpaca Technical Stack': 'alpaca/alpaca-technical-stack.md'
- 'Alpaca Tips': 'technical-documentation/alpaca-tips.md'
- - Tests:
- - 'Testing Notes': 'technical-documentation/testing-notes.md'
- - 'Resizing a VM': 'technical-documentation/resizing-vm.md'
- - 'Updating a `deb` and adding it to Lyrasis PPA': 'technical-documentation/ppa-documentation.md'
+ - Tests: 'technical-documentation/testing-notes.md'
- 'Adding back ?_format=jsonld': 'technical-documentation/adding-format-jsonld.md'
- Contribute to Islandora:
- 'How to contribute': 'contributing/CONTRIBUTING.md'
diff --git a/requirements.txt b/requirements.txt
index 2c8fb6a71..d91c01a0b 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -1,34 +1 @@
-Babel==2.11.0
-certifi==2024.7.4
-charset-normalizer==3.0.1
-click==8.1.3
-colorama==0.4.6
-ghp-import==2.1.0
-gitdb==4.0.10
-GitPython==3.1.41
-idna==3.7
-Jinja2==3.1.6
-Markdown==3.10
-MarkupSafe==2.1.2
-mergedeep==1.3.4
-mkdocs==1.6.1
-mkdocs-get-deps==0.2.0
-mkdocs-git-revision-date-localized-plugin==1.1.0
-mkdocs-material==9.0.12
-mkdocs-material-extensions==1.1.1
-mkdocs-redirects==1.2.1
-packaging==23.0
-pathspec==1.0.1
-platformdirs==4.5.1
-Pygments==2.15.0
-pymdown-extensions==10.20
-python-dateutil==2.8.2
-pytz==2022.7.1
-PyYAML==6.0.1
-pyyaml_env_tag==0.1
-regex==2022.10.31
-requests==2.32.4
-six==1.16.0
-smmap==5.0.0
-urllib3==2.6.3
-watchdog==2.2.1
+zensical==0.0.29
diff --git a/scripts/generate-abbreviations.sh b/scripts/generate-abbreviations.sh
new file mode 100755
index 000000000..08f6687e9
--- /dev/null
+++ b/scripts/generate-abbreviations.sh
@@ -0,0 +1,68 @@
+#!/usr/bin/env bash
+
+set -euo pipefail
+
+ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
+GLOSSARY_FILE="${1:-$ROOT_DIR/docs/user-documentation/glossary.md}"
+OUTPUT_FILE="${2:-$ROOT_DIR/includes/abbreviations.md}"
+TARGET_PATH="../user-documentation/glossary.md"
+
+declare -A seen_labels=()
+
+if [[ ! -f "$GLOSSARY_FILE" ]]; then
+ printf 'Glossary file not found: %s\n' "$GLOSSARY_FILE" >&2
+ exit 1
+fi
+
+mkdir -p "$(dirname "$OUTPUT_FILE")"
+
+slugify() {
+ local value="$1"
+
+ value="$(printf '%s' "$value" | tr '[:upper:]' '[:lower:]')"
+ value="$(printf '%s' "$value" | sed -E 's/[^a-z0-9 _-]+//g')"
+ value="$(printf '%s' "$value" | sed -E 's/[[:space:]_-]+/-/g; s/^-+//; s/-+$//')"
+
+ printf '%s\n' "$value"
+}
+
+emit_label() {
+ local label="$1"
+ local slug="$2"
+
+ [[ -n $label ]] || return
+ if [[ -v "seen_labels[$label]" ]]; then
+ return
+ fi
+
+ printf '[%s]: %s#%s\n' "$label" "$TARGET_PATH" "$slug"
+ seen_labels["$label"]=1
+}
+
+pluralize() {
+ local value="$1"
+
+ if [[ $value =~ [sS]$ ]]; then
+ printf '%s\n' "$value"
+ return
+ fi
+
+ if [[ $value =~ [^aeiouAEIOU]y$ ]]; then
+ printf '%sies\n' "${value::-1}"
+ return
+ fi
+
+ printf '%ss\n' "$value"
+}
+
+while IFS= read -r line; do
+ [[ $line == '## '* ]] || continue
+
+ heading="${line#\#\# }"
+ slug="$(slugify "$heading")"
+
+ [[ -n $slug ]] || continue
+
+ emit_label "$heading" "$slug"
+ emit_label "$(pluralize "$heading")" "$slug"
+done < "$GLOSSARY_FILE" > "$OUTPUT_FILE"