Skip to content

Commit 6d47096

Browse files
committed
Full-reload SWUP navigations that cross ExDoc builds
We force a full reload if there is a anywhere in the path. Fix issue introduced in a66397f where anchors are not swupped.
1 parent a806765 commit 6d47096

3 files changed

Lines changed: 71 additions & 13 deletions

File tree

assets/js/swup.js

Lines changed: 19 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -21,16 +21,32 @@ const maybeMetaRedirect = (visit, {page}) => {
2121

2222
window.addEventListener('DOMContentLoaded', emitExdocLoaded)
2323

24+
// Match links to local .html documentation pages, with or without a fragment.
25+
// Routing non-HTML files (downloads such as .mmd) through SWUP breaks the page
26+
// swap (#2182); the `.html#` branch keeps in-doc anchored links (function
27+
// references like file.html#list_dir/1, sidebar entries) on the SWUP path.
28+
export const LINK_SELECTOR = 'a[href]:not([href^="/"]):not([href^="http"]):is([href$=".html"], [href*=".html#"])'
29+
30+
// SWUP only swaps `#main`, so it must stay within a single ExDoc build: the
31+
// sidebar (navigation + version) is built from per-build <head> scripts SWUP
32+
// does not re-run. ExDoc writes every page of a build as a flat file in one
33+
// folder, so an in-build link is a bare filename and a slash in its path means
34+
// another folder/build (the Erlang/OTP docs flatten one build per application).
35+
// We test the path -- the part before the fragment -- not the whole href, since
36+
// anchors carry slashes too (the arity in file.html#list_dir/1).
37+
export const isWithinBuild = (href) => !href.split('#')[0].includes('/')
38+
2439
if (!isEmbedded && window.location.protocol !== 'file:') {
2540
new Swup({
2641
animationSelector: false,
2742
containers: ['#main'],
28-
ignoreVisit: (url) => {
43+
ignoreVisit: (url, {el} = {}) => {
2944
const path = url.split('#')[0]
3045
return path === window.location.pathname ||
31-
path === window.location.pathname + '.html'
46+
path === window.location.pathname + '.html' ||
47+
!isWithinBuild(el?.getAttribute('href') ?? '')
3248
},
33-
linkSelector: 'a[href]:not([href^="/"]):not([href^="http"])[href$=".html"]',
49+
linkSelector: LINK_SELECTOR,
3450
hooks: {
3551
'page:load': maybeMetaRedirect,
3652
'page:view': emitExdocLoaded

assets/test/swup.spec.js

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
import { el } from '../js/helpers'
2+
import { LINK_SELECTOR, isWithinBuild } from '../js/swup'
3+
4+
const linkMatches = (href) => el('a', {href}).matches(LINK_SELECTOR)
5+
6+
describe('swup', () => {
7+
describe('LINK_SELECTOR', () => {
8+
it('matches local .html pages, with or without an anchor', () => {
9+
expect(linkMatches('Foo.html')).toBe(true)
10+
expect(linkMatches('Foo.Bar.html')).toBe(true)
11+
expect(linkMatches('file.html#section')).toBe(true)
12+
// Anchors carry slashes (name/arity); the selector must still match them.
13+
expect(linkMatches('file.html#list_dir/1')).toBe(true)
14+
})
15+
16+
it('ignores non-HTML files (#2182), absolute and external links', () => {
17+
expect(linkMatches('ecto_erd.mmd')).toBe(false)
18+
expect(linkMatches('image.png')).toBe(false)
19+
expect(linkMatches('/Foo.html')).toBe(false)
20+
expect(linkMatches('http://example.com/Foo.html')).toBe(false)
21+
expect(linkMatches('https://example.com/Foo.html')).toBe(false)
22+
})
23+
24+
it('does not match bare same-page anchors (scrolled natively, no SWUP)', () => {
25+
expect(linkMatches('#section')).toBe(false)
26+
})
27+
})
28+
29+
describe('isWithinBuild', () => {
30+
it('is true for a same-folder link (bare filename)', () => {
31+
expect(isWithinBuild('Foo.html')).toBe(true)
32+
expect(isWithinBuild('file.html#section')).toBe(true)
33+
// A slash in the fragment (name/arity) does not leave the build.
34+
expect(isWithinBuild('file.html#list_dir/1')).toBe(true)
35+
})
36+
37+
it('is false for a link into another folder/build', () => {
38+
expect(isWithinBuild('apps/kernel/file.html')).toBe(false)
39+
expect(isWithinBuild('../stdlib/lists.html#foo/1')).toBe(false)
40+
})
41+
})
42+
})

0 commit comments

Comments
 (0)