Skip to content

Commit a6e76a7

Browse files
garazdawiclaude
andcommitted
Let SWUP handle in-doc links that carry a fragment
a66397f restricted SWUP's linkSelector to `[href$=".html"]` so that links to non-HTML files (downloads such as `.mmd`) would no longer be routed through SWUP and break the page swap (elixir-lang#2182). But `$=` matches only hrefs that *end* in `.html`, which also excluded in-doc anchored links -- function references like `Foo.html#fun/1` and the sidebar's per-function entries -- dropping them back to a full page reload. Match `.html` whether or not a fragment follows, via `:is([href$=".html"], [href*=".html#"])`. Non-HTML files stay excluded, so this keeps elixir-lang#2182 fixed while restoring smooth navigation for anchored links. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
1 parent 09595e9 commit a6e76a7

3 files changed

Lines changed: 37 additions & 11 deletions

File tree

assets/js/swup.js

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,14 @@ const maybePreventSwup = (visit, {page}) => {
3838
}
3939
}
4040

41+
// Only let SWUP intercept links to local .html documentation pages; routing
42+
// downloads or other non-HTML files through it breaks the page swap (#2182).
43+
// The `.html#fragment` branch keeps in-doc anchored links — function references
44+
// like `Foo.html#fun/1`, sidebar entries — on the SWUP path, which a plain
45+
// `[href$=".html"]` (matching only hrefs that *end* in .html) would drop.
46+
export const LINK_SELECTOR =
47+
'a[href]:not([href^="/"]):not([href^="http"]):is([href$=".html"], [href*=".html#"])'
48+
4149
window.addEventListener('DOMContentLoaded', emitExdocLoaded)
4250

4351
if (!isEmbedded && window.location.protocol !== 'file:') {
@@ -50,7 +58,7 @@ if (!isEmbedded && window.location.protocol !== 'file:') {
5058
path === window.location.pathname + '.html' ||
5159
isCrossBuild(window.location.pathname, path)
5260
},
53-
linkSelector: 'a[href]:not([href^="/"]):not([href^="http"])[href$=".html"]',
61+
linkSelector: LINK_SELECTOR,
5462
hooks: {
5563
'page:load': maybePreventSwup,
5664
'page:view': emitExdocLoaded

assets/test/swup.spec.js

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,7 @@
1-
import { isCrossBuild } from '../js/swup'
1+
import { el } from '../js/helpers'
2+
import { isCrossBuild, LINK_SELECTOR } from '../js/swup'
3+
4+
const linkMatches = (href) => el('a', {href}).matches(LINK_SELECTOR)
25

36
// SWUP resolves every href against the current page before it reaches
47
// isCrossBuild, so the inputs here are always absolute pathnames -- a relative
@@ -21,4 +24,19 @@ describe('swup', () => {
2124
expect(isCrossBuild('/doc/apps/erts/atomics.html', '/doc/index.html')).toBe(true)
2225
})
2326
})
27+
28+
describe('LINK_SELECTOR', () => {
29+
it('matches in-doc .html pages, with or without a fragment', () => {
30+
expect(linkMatches('Foo.html')).toBe(true)
31+
expect(linkMatches('String.Chars.html#trim/1')).toBe(true)
32+
expect(linkMatches('module.html#content')).toBe(true)
33+
})
34+
35+
it('ignores non-HTML files (#2182) and non-relative links', () => {
36+
expect(linkMatches('ecto_erd.mmd')).toBe(false)
37+
expect(linkMatches('ecto_erd.mmd#anchor')).toBe(false)
38+
expect(linkMatches('/absolute.html')).toBe(false)
39+
expect(linkMatches('https://hexdocs.pm/ecto/Ecto.html')).toBe(false)
40+
})
41+
})
2442
})

0 commit comments

Comments
 (0)