Skip to content

Commit b704100

Browse files
garazdawiclaude
andcommitted
Restrict swup transitions to same-directory links
Use directory comparison in ignoreVisit instead of CSS selector filtering. This correctly handles both absolute and relative links: - Same-directory links get smooth swup transitions (sidebar stays valid) - Cross-directory links trigger full page reloads (sidebar updates) - Same-page links with .html mismatch are recognized as self-links The broader linkSelector now accepts all non-external, non-anchor links, while ignoreVisit does the smart filtering with JS logic. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent 099b69f commit b704100

3 files changed

Lines changed: 84 additions & 17 deletions

File tree

assets/js/swup.js

Lines changed: 20 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -19,18 +19,32 @@ const maybeMetaRedirect = (visit, {page}) => {
1919
}
2020
}
2121

22+
/**
23+
* Checks if a navigation should be ignored by swup.
24+
* Returns true for same-page links and cross-directory links.
25+
* Same-directory links get smooth swup transitions.
26+
*/
27+
export function shouldIgnoreVisit (url, currentPathname) {
28+
const path = url.split('#')[0]
29+
// Ignore same-page links (with or without .html mismatch).
30+
if (path === currentPathname ||
31+
path === currentPathname + '.html' ||
32+
path + '.html' === currentPathname) { return true }
33+
// Only use swup for links within the same directory (same app).
34+
// Cross-directory links need a full reload to update the sidebar.
35+
const currentDir = currentPathname.substring(0, currentPathname.lastIndexOf('/') + 1)
36+
const targetDir = path.substring(0, path.lastIndexOf('/') + 1)
37+
return targetDir !== currentDir
38+
}
39+
2240
window.addEventListener('DOMContentLoaded', emitExdocLoaded)
2341

2442
if (!isEmbedded && window.location.protocol !== 'file:') {
2543
new Swup({
2644
animationSelector: false,
2745
containers: ['#main'],
28-
ignoreVisit: (url) => {
29-
const path = url.split('#')[0]
30-
return path === window.location.pathname ||
31-
path === window.location.pathname + '.html'
32-
},
33-
linkSelector: 'a[href]:not([href^="/"]):not([href^="http"])[href$=".html"]',
46+
ignoreVisit: (url) => shouldIgnoreVisit(url, window.location.pathname),
47+
linkSelector: 'a[href]:not([href^="http"]):not([href^="#"])',
3448
hooks: {
3549
'page:load': maybeMetaRedirect,
3650
'page:view': emitExdocLoaded

assets/test/swup.spec.js

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
import { shouldIgnoreVisit } from '../js/swup'
2+
3+
describe('shouldIgnoreVisit', () => {
4+
describe('same-page detection', () => {
5+
it('ignores exact same path', () => {
6+
expect(shouldIgnoreVisit('/doc/apps/stdlib/gen_server.html', '/doc/apps/stdlib/gen_server.html')).toBe(true)
7+
})
8+
9+
it('ignores same path with hash', () => {
10+
expect(shouldIgnoreVisit('/doc/apps/stdlib/gen_server.html#start_link/3', '/doc/apps/stdlib/gen_server.html')).toBe(true)
11+
})
12+
13+
it('ignores extensionless URL matching .html page', () => {
14+
expect(shouldIgnoreVisit('/doc/apps/stdlib/gen_server', '/doc/apps/stdlib/gen_server.html')).toBe(true)
15+
})
16+
17+
it('ignores .html URL matching extensionless page', () => {
18+
expect(shouldIgnoreVisit('/doc/apps/stdlib/gen_server.html', '/doc/apps/stdlib/gen_server')).toBe(true)
19+
})
20+
})
21+
22+
describe('same-directory links (use swup)', () => {
23+
it('allows .html links in same directory', () => {
24+
expect(shouldIgnoreVisit('/doc/apps/stdlib/lists.html', '/doc/apps/stdlib/gen_server.html')).toBe(false)
25+
})
26+
27+
it('allows extensionless links in same directory', () => {
28+
expect(shouldIgnoreVisit('/doc/apps/stdlib/lists', '/doc/apps/stdlib/gen_server')).toBe(false)
29+
})
30+
31+
it('allows extensionless link from .html page', () => {
32+
expect(shouldIgnoreVisit('/doc/apps/stdlib/lists', '/doc/apps/stdlib/gen_server.html')).toBe(false)
33+
})
34+
35+
it('allows .html link from extensionless page', () => {
36+
expect(shouldIgnoreVisit('/doc/apps/stdlib/lists.html', '/doc/apps/stdlib/gen_server')).toBe(false)
37+
})
38+
})
39+
40+
describe('cross-directory links (full reload)', () => {
41+
it('ignores cross-app links', () => {
42+
expect(shouldIgnoreVisit('/doc/apps/kernel/file.html', '/doc/apps/stdlib/gen_server.html')).toBe(true)
43+
})
44+
45+
it('ignores cross-app extensionless links', () => {
46+
expect(shouldIgnoreVisit('/doc/apps/kernel/file', '/doc/apps/stdlib/gen_server')).toBe(true)
47+
})
48+
49+
it('ignores links to system docs from app docs', () => {
50+
expect(shouldIgnoreVisit('/doc/system/design_principles', '/doc/apps/stdlib/gen_server')).toBe(true)
51+
})
52+
})
53+
})

0 commit comments

Comments
 (0)