Skip to content

Commit c2f4887

Browse files
committed
fix: edit URL failover, VCS icons (1.0.2)
- site-dark-mode: applyVcsIcons + getRepoUrl uses ftn-edit-inline links - optional partials/edit-breadcrumb.hbs (editUrl before fileUri) - docs: vcs-repo-logo guide and Files Involved table Made-with: Cursor
1 parent 29b8b40 commit c2f4887

5 files changed

Lines changed: 114 additions & 36 deletions

File tree

CHANGELOG.adoc

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,11 @@ and this project adheres to https://semver.org/spec/v2.0.0.html[Semantic Version
88

99
Entries below are in **chronological order** by date: prior release, then the **2026-03-04** org creation and repository move, then the next release.
1010

11+
== [1.0.2] - 2026-04-26
12+
13+
* `site-dark-mode.js`: after theme/Download setup, `applyVcsIcons()` sets `img/vcs/*.svg` from link URLs for `a.ftn-edit-inline-link` (and header/repo widgets), so GitHub (and other hosts) are not left on the generic `code.svg` placeholder when the edit link is `https`. `getRepoUrl()` can infer the repo from `a.ftn-edit-inline-link` (with a path) when meta is absent.
14+
* New optional partial `partials/edit-breadcrumb.hbs`: when included from custom breadcrumbs, **prefers** `page.editUrl` over `page.fileUri` (aligns with Antora `edit-this-page` default); `file://` is used only when there is no web edit URL.
15+
1116
== [1.0.1] - 2026-04-26
1217

1318
* `site-extra.css` (supplemental): on wide viewports, the left column uses a flex column layout so the nav area fills the height beside `main` while remaining in document flow. `.nav-panel-menu` and `.nav-panel-explore .components` use `overflow-y: auto` so a scrollbar appears only when content exceeds the available space.

docs/modules/guide/pages/vcs-repo-logo.adoc

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -72,14 +72,21 @@ To point to a different URL (e.g. a docs subfolder or a different repo), you wou
7272
=== Logo not showing (e.g. local build)
7373

7474
The logo is injected by JavaScript when the page loads.
75-
If the default Antora "Download" button is missing (e.g. custom UI or local build), the script now *appends* the logo to the navbar so it still appears.
75+
`replaceDownloadWithVcsLogo()` only runs when a default Antora Download button is present; if your layout removes that button, add your own nav link or extend the script.
7676
The script resolves the asset path from `data-ui-root-path` on the script tag, or from the script’s own URL so local or non-root builds can load `img/vcs/*.svg`.
7777
If the logo still doesn’t appear, ensure `.navbar .navbar-end` exists and that `site-dark-mode.js` runs (no script errors in the console).
7878

79+
== Breadcrumb “Edit” link (optional partial)
80+
81+
The theme ships `supplemental-ui/partials/edit-breadcrumb.hbs` for sites that add that partial to a custom `breadcrumbs` (for example, `+{{> edit-breadcrumb}}+` next to the rest of the trail).
82+
It **prefers** `page.editUrl` (https) over `page.fileUri` (file) when the usual public-edit conditions hold, and falls back to the file URL for local work when there is no web edit URL (same ordering as a well-formed `edit-this-page` override).
83+
`site-dark-mode.js` runs `applyVcsIcons()` to set the correct `img/vcs/<provider>.svg` from each link’s URL (GitHub, GitLab, and others), instead of a generic `code.svg` when the `href` is an `https` edit link.
84+
7985
== Files Involved
8086

8187
| File | Role
8288
| `supplemental-ui/partials/head-meta.hbs` | Emits `<meta name="antora-repo-url" content="{{page.origin.webUrl}}">` when the page has an origin.
83-
| `supplemental-ui/js/site-dark-mode.js` | `replaceDownloadWithVcsLogo()` gets repo URL, detects provider, and either replaces the Download button with the logo or appends the logo to the navbar if the button is missing (e.g. local or custom builds). Asset base path falls back to the script’s URL when needed for local viewing.
89+
| `supplemental-ui/js/site-dark-mode.js` | `replaceDownloadWithVcsLogo()` replaces the default Download button when present; `applyVcsIcons()` maps edit/header/repo `img` tags to `img/vcs/*.svg` by URL. `getRepoUrl()` uses meta, then edit/blob links. Asset base path falls back to the script’s URL when needed for local viewing.
8490
| `supplemental-ui/img/vcs/*.svg` | One SVG per provider (and `repo.svg` fallback).
8591
| `supplemental-ui/css/site-extra.css` | Hides the default Download button until JS runs (avoids flash). `.vcs-repo-logo`, `.vcs-repo-link`, `.vcs-logo` (div proportions and mask) and hover.
92+
| `supplemental-ui/partials/edit-breadcrumb.hbs` (optional) | Breadcrumb “Edit” link; prefers `page.editUrl` over `fileUri`. Pair with your custom `breadcrumbs` partial.

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "antora-dark-theme",
3-
"version": "1.0.1",
3+
"version": "1.0.2",
44
"description": "Dark mode supplemental UI theme for Antora documentation sites",
55
"keywords": [
66
"antora",

supplemental-ui/js/site-dark-mode.js

Lines changed: 83 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,7 @@
8787
function ensureToggleButton() {
8888
const existingToggle = document.getElementById("theme-toggle");
8989
if (existingToggle) {
90+
existingToggle.classList.add("ftn-header-icon-btn");
9091
existingToggle.addEventListener("click", toggleTheme);
9192
updateToggleLabel();
9293
return;
@@ -97,36 +98,84 @@
9798

9899
const button = document.createElement("button");
99100
button.id = "theme-toggle";
100-
button.className = "navbar-item theme-toggle";
101+
button.className = "navbar-item ftn-header-icon-btn theme-toggle";
101102
button.type = "button";
102103
button.addEventListener("click", toggleTheme);
103104

104105
navbarEnd.insertBefore(button, navbarEnd.firstChild);
105106
updateToggleLabel();
106107
}
107108

108-
function detectVcsProvider(repoUrl) {
109-
if (!repoUrl) return null;
109+
/**
110+
* Icon basename under img/vcs/*.svg (white artwork; CSS may filter in header / mast).
111+
*/
112+
function vcsIconIdFromUrl(url) {
113+
if (!url) return "code";
114+
let host;
110115
try {
111-
const host = new URL(repoUrl).hostname.toLowerCase();
112-
if (host === "github.com") return "github";
113-
if (host.includes("gitlab")) return "gitlab";
114-
if (host === "bitbucket.org") return "bitbucket";
115-
if (host.includes("gitea")) return "gitea";
116-
if (host === "codeberg.org") return "codeberg";
117-
if (host.includes("forgejo")) return "forgejo";
118-
if (host.includes("sourcehut") || host.endsWith("sr.ht")) return "sourcehut";
119-
return "repo";
116+
host = new URL(url).hostname.toLowerCase();
120117
} catch {
121-
return null;
118+
return "code";
122119
}
120+
if (host === "github.com" || host === "raw.githubusercontent.com" || host === "github.dev" || host.endsWith(".github.com")) {
121+
return "github";
122+
}
123+
if (host === "bitbucket.org" || host.includes("bitbucket.")) return "bitbucket";
124+
if (host.includes("gitlab")) return "gitlab";
125+
if (host === "codeberg.org" || host.endsWith(".codeberg.page") || host.endsWith(".codeberg.org")) {
126+
return "codeberg";
127+
}
128+
if (host.includes("gitea")) return "gitea";
129+
if (host.includes("forgejo")) return "forgejo";
130+
if (host.includes("sourcehut") || host.endsWith("sr.ht") || host === "git.sr.ht") {
131+
return "sourcehut";
132+
}
133+
if (host === "dev.azure.com" || host === "dev.azure" || host.endsWith("visualstudio.com") || host.includes("vssps.visualstudio.com")) {
134+
return "code";
135+
}
136+
return "code";
137+
}
138+
139+
function vcsIconUrl(base, id) {
140+
const root = (base || ".").replace(/\/?$/, "/");
141+
return `${root}img/vcs/${id}.svg`;
142+
}
143+
144+
function applyVcsIcons() {
145+
const base = getUiBase();
146+
function setVcsImage(img, href) {
147+
if (!img || !href) return;
148+
const id = vcsIconIdFromUrl(href);
149+
const primary = vcsIconUrl(base, id);
150+
img.onerror = function ftnVcsOerr() {
151+
img.onerror = null;
152+
if (img.getAttribute("data-ftn-vcs-tried") === "1") return;
153+
img.setAttribute("data-ftn-vcs-tried", "1");
154+
if (!img.src || img.src.indexOf("code.svg") < 0) {
155+
img.src = vcsIconUrl(base, "code");
156+
}
157+
};
158+
img.src = primary;
159+
}
160+
document.querySelectorAll("a.ftn-edit-inline-link[href]").forEach((a) => {
161+
const img = a.querySelector("img.ftn-vcs-icon-img, img.ftn-edit-vcs-img");
162+
setVcsImage(img, a.href);
163+
});
164+
document.querySelectorAll("a.ftn-header-vcs[href] img.ftn-header-vcs-img").forEach((img) => {
165+
const a = img.closest("a");
166+
if (a) setVcsImage(img, a.href);
167+
});
168+
document.querySelectorAll("a.vcs-repo-link[href] img.vcs-logo-img").forEach((img) => {
169+
const a = img.closest("a");
170+
if (a) setVcsImage(img, a.href);
171+
});
123172
}
124173

125174
function getRepoUrl() {
126175
const meta = document.querySelector('meta[name="antora-repo-url"]');
127176
if (meta && meta.content) return meta.content;
128177
const editLink = document.querySelector(
129-
'.navbar-end a[href*="/edit/"], .navbar-end a[href*="/-/edit/"], .navbar-end a[href*="/blob/"]'
178+
'.navbar-end a[href*="/edit/"], .navbar-end a[href*="/-/edit/"], .navbar-end a[href*="/blob/"], a.ftn-edit-inline-link[href*="/"]'
130179
);
131180
if (editLink && editLink.href) {
132181
try {
@@ -165,9 +214,8 @@
165214
return ".";
166215
}
167216

168-
function buildVcsLogoWidget(repoUrl, provider, base) {
169-
const logoFile = provider ? `${provider}.svg` : "repo.svg";
170-
const logoUrl = `${base}/img/vcs/${logoFile}`;
217+
function buildVcsLogoWidget(repoUrl, id, base) {
218+
const logoUrl = vcsIconUrl(base, id || "code");
171219
const wrapper = document.createElement("div");
172220
wrapper.className = "navbar-item vcs-repo-logo";
173221
const a = document.createElement("a");
@@ -186,13 +234,14 @@
186234
img.src = logoUrl;
187235
img.onerror = function () {
188236
this.onerror = null;
189-
var dataRoot = document.querySelector("#site-script")?.dataset?.uiRootPath;
190-
var fallback = (dataRoot || ".") + "/img/vcs/" + (provider ? provider + ".svg" : "repo.svg");
191-
if (fallback !== logoUrl) {
192-
this.src = fallback;
193-
} else if (!dataRoot) {
194-
this.src = "img/vcs/repo.svg";
237+
const dataRoot = document.querySelector("#site-script")?.dataset?.uiRootPath;
238+
const root = (dataRoot || ".").replace(/\/?$/, "/");
239+
const tryCode = `${root}img/vcs/code.svg`;
240+
if (this.src && !this.src.includes("code.svg")) {
241+
this.src = tryCode;
242+
return;
195243
}
244+
this.src = `${root}img/vcs/repo.svg`;
196245
};
197246
logo.appendChild(img);
198247
a.appendChild(logo);
@@ -201,26 +250,27 @@
201250
}
202251

203252
function replaceDownloadWithVcsLogo() {
253+
const downloadLink = document.querySelector(
254+
'.navbar .navbar-end a.button[href="#"], .navbar .navbar-end a.button.is-primary'
255+
);
256+
if (!downloadLink) return;
257+
const isDownload = /Download/i.test(downloadLink.textContent || "");
258+
if (!isDownload) return;
204259
const repoUrl = getRepoUrl();
205-
const provider = repoUrl ? detectVcsProvider(repoUrl) : null;
260+
const iconId = repoUrl ? vcsIconIdFromUrl(repoUrl) : "code";
206261
const navbarEnd = document.querySelector(".navbar .navbar-end");
207262
if (!navbarEnd) return;
208263
const base = getUiBase();
209-
const widget = buildVcsLogoWidget(repoUrl, provider, base);
210-
const downloadLink = navbarEnd.querySelector('a.button[href="#"], a.button.is-primary');
211-
const isDownload = downloadLink && /Download/i.test(downloadLink.textContent || "");
212-
if (downloadLink && isDownload) {
213-
const toReplace = downloadLink.closest(".control") || downloadLink.closest(".navbar-item") || downloadLink;
214-
toReplace.parentNode.replaceChild(widget, toReplace);
215-
} else {
216-
navbarEnd.appendChild(widget);
217-
}
264+
const widget = buildVcsLogoWidget(repoUrl, iconId, base);
265+
const toReplace = downloadLink.closest(".control") || downloadLink.closest(".navbar-item") || downloadLink;
266+
toReplace.parentNode.replaceChild(widget, toReplace);
218267
}
219268

220269
function init() {
221270
applyInitialTheme();
222271
ensureToggleButton();
223272
replaceDownloadWithVcsLogo();
273+
applyVcsIcons();
224274
}
225275

226276
if (document.readyState === "loading") {
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
{{! Optional: include in breadcrumbs as {{> edit-breadcrumb}}. Prefer page.editUrl (https) so VCS icon logic matches; file: only when no edit URL. }}
2+
{{#if (and page.editUrl (or env.FORCE_SHOW_EDIT_PAGE_LINK (not page.origin.private)))}}
3+
<li class="ftn-breadcrumb-edit" role="presentation">
4+
<a class="ftn-edit-inline-link" href="{{page.editUrl}}" title="Edit this page" aria-label="Edit this page">
5+
<img class="ftn-vcs-icon-img ftn-edit-vcs-img" src="{{{uiRootPath}}}/img/vcs/github.svg" width="16" height="16" alt="" />
6+
<span class="ftn-edit-inline-text">Edit</span>
7+
</a>
8+
</li>
9+
{{else if (and page.fileUri (not env.CI))}}
10+
<li class="ftn-breadcrumb-edit" role="presentation">
11+
<a class="ftn-edit-inline-link" href="{{page.fileUri}}" title="Edit this page" aria-label="Edit this page">
12+
<img class="ftn-vcs-icon-img ftn-edit-vcs-img" src="{{{uiRootPath}}}/img/vcs/code.svg" width="16" height="16" alt="" />
13+
<span class="ftn-edit-inline-text">Edit</span>
14+
</a>
15+
</li>
16+
{{/if}}

0 commit comments

Comments
 (0)