Skip to content

Commit aa4e8dc

Browse files
authored
Fix zensical preview in site hierarchy (#2401)
* fixup glossary * add preview hook
1 parent de5c538 commit aa4e8dc

4 files changed

Lines changed: 114 additions & 8 deletions

File tree

Dockerfile

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,10 @@ RUN apk add --no-cache git && \
66
pip install uv && uv --version && \
77
uv pip install --break-system-packages --system zensical==0.0.29
88

9+
COPY zensical_hooks.py /tmp/zensical_hooks.py
10+
RUN python3 -c \
11+
"import site, shutil; shutil.copy('/tmp/zensical_hooks.py', site.getsitepackages()[0] + '/zensical_hooks.py')"
12+
913
EXPOSE 8080
1014

1115
ENTRYPOINT ["zensical"]

docs/installation/docker/site-template/site-template.md

Lines changed: 3 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,8 @@
22

33
## What is the ISLE Site Template?
44

5-
The [ISLE Site Template][ISLE Site Template] is a system for installing
6-
Islandora on Docker. As with ISLE-DC, it uses [Docker Compose][Docker Compose]
5+
The [ISLE Site Template] is a system for installing
6+
Islandora on Docker. As with ISLE-DC, it uses [Docker Compose]
77
to orchestrate the installation of all the different services (Docker
88
containers) that make up Islandora. Unlike ISLE-DC, in ISLE Site Template you
99
use Docker Compose commands directly, helping you to get familiar with the
@@ -15,7 +15,7 @@ kinds of commands that will be a key part of running and maintaining Islandora.
1515
* Click the green "Use this template" button on the [ISLE Site Template][ISLE Site Template]
1616
repository to create your own copy. This creates a new repository in your GitHub account
1717
with the same directory structure and files, including a pre-installed copy of the
18-
[Islandora Starter Site][Islandora Starter Site].
18+
[Islandora Starter Site].
1919

2020
2. **Clone your new repository**
2121
* After creating your repository from the template, clone it to your local machine or server.
@@ -35,7 +35,3 @@ kinds of commands that will be a key part of running and maintaining Islandora.
3535
configuration, service settings, Drupal modules, themes, and site configuration. Your
3636
custom Drupal codebase is part of the repository and gets built into a custom Docker
3737
image.
38-
39-
[ISLE Site Template]: https://github.com/Islandora-Devops/isle-site-template
40-
[Docker Compose]: https://docs.docker.com/compose/
41-
[Islandora Starter Site]: https://github.com/Islandora-Devops/islandora-starter-site

mkdocs.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ theme:
2929
logo: 'assets/Islandora_logo.png'
3030

3131
markdown_extensions:
32+
- zensical_hooks
3233
- abbr
3334
- admonition
3435
- attr_list
@@ -81,7 +82,6 @@ markdown_extensions:
8182
custom_checkbox: true
8283
- pymdownx.tilde
8384

84-
8585
extra_css:
8686
- css/custom.css
8787
extra_javascript:

zensical_hooks.py

Lines changed: 106 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,106 @@
1+
"""
2+
Python-Markdown extension: fix glossary link depth and inject data-preview.
3+
4+
Background
5+
----------
6+
Zensical's LinksProcessor always prepends exactly one extra "../" to non-index
7+
relative links when use_directory_urls=True. Because includes/abbreviations.md
8+
uses "../user-documentation/glossary.md#term" (one leading ".."), LinksProcessor
9+
always emits "../../user-documentation/glossary/#term" in HTML regardless of
10+
actual page depth:
11+
12+
depth-2 page (something/page/): ../../ -> root OK
13+
depth-4 page (a/b/c/page/): ../../ -> a/b/ WRONG
14+
15+
Separately, zensical.extensions.preview adds data-preview="" by resolving the
16+
original .md href relative to the current source file. For deeply nested pages
17+
the relative "../user-documentation/glossary.md" doesn't resolve to
18+
"user-documentation/glossary.md", so data-preview is never added.
19+
20+
Fix
21+
---
22+
Register a Treeprocessor at priority -1 (lower than the priority-0 processors
23+
PreviewProcessor and LinksProcessor). It runs after both, reads the current
24+
page's source path from Zensical's `zrelpath` processor, computes the correct
25+
depth, rewrites every glossary href, and adds data-preview="" where absent.
26+
"""
27+
28+
from __future__ import annotations
29+
30+
import re
31+
from typing import TYPE_CHECKING
32+
33+
from markdown import Extension
34+
from markdown.treeprocessors import Treeprocessor
35+
from zensical.extensions.links import LinksProcessor
36+
37+
if TYPE_CHECKING:
38+
from xml.etree.ElementTree import Element
39+
40+
# Matches the relative prefix (any number of ../) before the glossary path
41+
_GLOSSARY_RE = re.compile(r"^(?:\.\./)*user-documentation/glossary/")
42+
43+
44+
def _url_depth(src_path: str) -> int:
45+
"""Return URL depth with use_directory_urls=True.
46+
47+
- index.md / README.md at directory depth D -> D
48+
- any other .md at directory depth D -> D + 1
49+
"""
50+
parts = src_path.replace("\\", "/").split("/")
51+
is_index = parts[-1].lower() in ("index.md", "readme.md")
52+
return len(parts) - 1 if is_index else len(parts)
53+
54+
55+
def _find_page_path(md) -> str | None:
56+
"""Return the source path of the page being rendered.
57+
58+
This repo ships a Docker-pinned Zensical version that always registers the
59+
links treeprocessor as ``zrelpath``.
60+
"""
61+
try:
62+
idx = md.treeprocessors.get_index_for_name("zrelpath")
63+
proc = md.treeprocessors[idx]
64+
if isinstance(proc, LinksProcessor) and isinstance(proc.path, str):
65+
return proc.path
66+
except Exception: # noqa: BLE001
67+
pass
68+
69+
return None
70+
71+
72+
class GlossaryFixProcessor(Treeprocessor):
73+
"""Rewrite glossary hrefs to the correct depth and ensure data-preview."""
74+
75+
def run(self, root: Element) -> None:
76+
try:
77+
path = _find_page_path(self.md)
78+
if path is None:
79+
return
80+
depth = _url_depth(path)
81+
prefix = "../" * depth
82+
except Exception: # noqa: BLE001
83+
return
84+
85+
for el in root.iter("a"):
86+
href = el.get("href", "")
87+
if "user-documentation/glossary/" not in href:
88+
continue
89+
90+
new_href = _GLOSSARY_RE.sub(
91+
prefix + "user-documentation/glossary/", href
92+
)
93+
if new_href != href:
94+
el.set("href", new_href)
95+
96+
if "data-preview" not in el.attrib:
97+
el.set("data-preview", "")
98+
99+
100+
class GlossaryFixExtension(Extension):
101+
def extendMarkdown(self, md) -> None: # noqa: N802
102+
md.treeprocessors.register(GlossaryFixProcessor(md), "glossary_fix", -1)
103+
104+
105+
def makeExtension(**kwargs): # noqa: N802
106+
return GlossaryFixExtension(**kwargs)

0 commit comments

Comments
 (0)