|
87 | 87 | function ensureToggleButton() { |
88 | 88 | const existingToggle = document.getElementById("theme-toggle"); |
89 | 89 | if (existingToggle) { |
| 90 | + existingToggle.classList.add("ftn-header-icon-btn"); |
90 | 91 | existingToggle.addEventListener("click", toggleTheme); |
91 | 92 | updateToggleLabel(); |
92 | 93 | return; |
|
97 | 98 |
|
98 | 99 | const button = document.createElement("button"); |
99 | 100 | button.id = "theme-toggle"; |
100 | | - button.className = "navbar-item theme-toggle"; |
| 101 | + button.className = "navbar-item ftn-header-icon-btn theme-toggle"; |
101 | 102 | button.type = "button"; |
102 | 103 | button.addEventListener("click", toggleTheme); |
103 | 104 |
|
104 | 105 | navbarEnd.insertBefore(button, navbarEnd.firstChild); |
105 | 106 | updateToggleLabel(); |
106 | 107 | } |
107 | 108 |
|
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; |
110 | 115 | 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(); |
120 | 117 | } catch { |
121 | | - return null; |
| 118 | + return "code"; |
122 | 119 | } |
| 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 | + }); |
123 | 172 | } |
124 | 173 |
|
125 | 174 | function getRepoUrl() { |
126 | 175 | const meta = document.querySelector('meta[name="antora-repo-url"]'); |
127 | 176 | if (meta && meta.content) return meta.content; |
128 | 177 | 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*="/"]' |
130 | 179 | ); |
131 | 180 | if (editLink && editLink.href) { |
132 | 181 | try { |
|
165 | 214 | return "."; |
166 | 215 | } |
167 | 216 |
|
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"); |
171 | 219 | const wrapper = document.createElement("div"); |
172 | 220 | wrapper.className = "navbar-item vcs-repo-logo"; |
173 | 221 | const a = document.createElement("a"); |
|
186 | 234 | img.src = logoUrl; |
187 | 235 | img.onerror = function () { |
188 | 236 | 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; |
195 | 243 | } |
| 244 | + this.src = `${root}img/vcs/repo.svg`; |
196 | 245 | }; |
197 | 246 | logo.appendChild(img); |
198 | 247 | a.appendChild(logo); |
|
201 | 250 | } |
202 | 251 |
|
203 | 252 | 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; |
204 | 259 | const repoUrl = getRepoUrl(); |
205 | | - const provider = repoUrl ? detectVcsProvider(repoUrl) : null; |
| 260 | + const iconId = repoUrl ? vcsIconIdFromUrl(repoUrl) : "code"; |
206 | 261 | const navbarEnd = document.querySelector(".navbar .navbar-end"); |
207 | 262 | if (!navbarEnd) return; |
208 | 263 | 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); |
218 | 267 | } |
219 | 268 |
|
220 | 269 | function init() { |
221 | 270 | applyInitialTheme(); |
222 | 271 | ensureToggleButton(); |
223 | 272 | replaceDownloadWithVcsLogo(); |
| 273 | + applyVcsIcons(); |
224 | 274 | } |
225 | 275 |
|
226 | 276 | if (document.readyState === "loading") { |
|
0 commit comments