From 87b1dd405bd9ceb89a880ca6b139e7bde4c6c465 Mon Sep 17 00:00:00 2001 From: gabalafou Date: Fri, 4 Apr 2025 18:10:14 -0500 Subject: [PATCH 1/7] On page load, highlight TOC entry if there's a hash in the URL --- .../assets/scripts/pydata-sphinx-theme.js | 21 ++++++++++++++++--- 1 file changed, 18 insertions(+), 3 deletions(-) diff --git a/src/pydata_sphinx_theme/assets/scripts/pydata-sphinx-theme.js b/src/pydata_sphinx_theme/assets/scripts/pydata-sphinx-theme.js index 58c5affb7..0b2f11379 100644 --- a/src/pydata_sphinx_theme/assets/scripts/pydata-sphinx-theme.js +++ b/src/pydata_sphinx_theme/assets/scripts/pydata-sphinx-theme.js @@ -1018,17 +1018,32 @@ function setupArticleTocSyncing() { // callback would otherwise highlight, so we turn off the observer and turn it // back on later. let disableObserver = false; - pageToc.addEventListener("click", (event) => { + const timeoutObserver = () => { disableObserver = true; - const clickedTocLink = tocLinks.find((el) => el.contains(event.target)); - activate(clickedTocLink); setTimeout(() => { // Give the page ample time to finish scrolling, then re-enable the // intersection observer. disableObserver = false; }, 1000); + }; + pageToc.addEventListener("click", (event) => { + timeoutObserver(); + const clickedTocLink = tocLinks.find((el) => el.contains(event.target)); + activate(clickedTocLink); }); + // When the page loads, if the user has followed a URL with a hash, + // activate the corresponding TOC entry (if it exists) and wait for + // the page to scroll to the heading. + const { hash: pageHash } = window.location; + if (pageHash.length > 1) { + const matchingTocLink = tocLinks.find((link) => link.hash === pageHash); + if (matchingTocLink) { + timeoutObserver(); + activate(matchingTocLink); + } + } + /** * Activate an element and its chain of ancestor TOC entries; deactivate * everything else in the TOC. Together with the theme CSS, this unfolds From c7e4f9b873d8bbcef3ecc8b8244d231a0216b648 Mon Sep 17 00:00:00 2001 From: gabalafou Date: Fri, 4 Apr 2025 19:35:55 -0500 Subject: [PATCH 2/7] fix Safari --- src/pydata_sphinx_theme/assets/scripts/pydata-sphinx-theme.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pydata_sphinx_theme/assets/scripts/pydata-sphinx-theme.js b/src/pydata_sphinx_theme/assets/scripts/pydata-sphinx-theme.js index 0b2f11379..d6df3820c 100644 --- a/src/pydata_sphinx_theme/assets/scripts/pydata-sphinx-theme.js +++ b/src/pydata_sphinx_theme/assets/scripts/pydata-sphinx-theme.js @@ -1142,7 +1142,7 @@ function setupArticleTocSyncing() { } observer = new IntersectionObserver(callback, options); - headingsToTocLinks.keys().forEach((heading) => { + Array.from(headingsToTocLinks.keys()).forEach((heading) => { observer.observe(heading); }); } From 3ffb1b5cadade059a5603837149be46d33bdf3d8 Mon Sep 17 00:00:00 2001 From: gabalafou Date: Sun, 6 Apr 2025 22:08:50 -0500 Subject: [PATCH 3/7] use the same function for link click and page load --- .../assets/scripts/pydata-sphinx-theme.js | 67 ++++++++++++------- 1 file changed, 41 insertions(+), 26 deletions(-) diff --git a/src/pydata_sphinx_theme/assets/scripts/pydata-sphinx-theme.js b/src/pydata_sphinx_theme/assets/scripts/pydata-sphinx-theme.js index d6df3820c..b4fe984c7 100644 --- a/src/pydata_sphinx_theme/assets/scripts/pydata-sphinx-theme.js +++ b/src/pydata_sphinx_theme/assets/scripts/pydata-sphinx-theme.js @@ -1013,37 +1013,52 @@ function setupArticleTocSyncing() { return; } - // When the website visitor clicks a link in the TOC, we want that link to be - // highlighted/activated, NOT whichever TOC link the intersection observer - // callback would otherwise highlight, so we turn off the observer and turn it - // back on later. + // Create a boolean variable that allows us to turn off the intersection + // observer (and then later back on). When the website visitor clicks an + // in-page link, we want the entry in the TOC to be highlighted/activated, NOT + // whichever TOC link the intersection observer callback would otherwise + // highlight. let disableObserver = false; - const timeoutObserver = () => { - disableObserver = true; - setTimeout(() => { - // Give the page ample time to finish scrolling, then re-enable the - // intersection observer. - disableObserver = false; - }, 1000); - }; - pageToc.addEventListener("click", (event) => { - timeoutObserver(); - const clickedTocLink = tocLinks.find((el) => el.contains(event.target)); - activate(clickedTocLink); - }); - // When the page loads, if the user has followed a URL with a hash, - // activate the corresponding TOC entry (if it exists) and wait for - // the page to scroll to the heading. - const { hash: pageHash } = window.location; - if (pageHash.length > 1) { - const matchingTocLink = tocLinks.find((link) => link.hash === pageHash); - if (matchingTocLink) { - timeoutObserver(); - activate(matchingTocLink); + /** + * Check the hash portion of the page URL. If it matches an entry in the page + * table of contents, highlight that entry and temporarily disable the + * intersection observer while the page scrolls to the corresponding heading. + */ + function syncTocHash() { + const { hash: pageHash } = window.location; + if (pageHash.length > 1) { + const matchingTocLink = tocLinks.find((link) => link.hash === pageHash); + if (matchingTocLink) { + disableObserver = true; + setTimeout(() => { + // Give the page ample time to finish scrolling, then re-enable the + // intersection observer. + disableObserver = false; + }, 1000); + activate(matchingTocLink); + } } } + // When the page loads and when the user clicks an in-page link, + // sync the page's table of contents. + syncTocHash(); + // Note we cannot use the "hashchange" event because if the user clicks a hash + // link, scrolls away, then clicks the same hash link again, it will not fire + // the change event (because it's the same hash), but we still want to re-sync + // the table of contents. + window.addEventListener("click", (event) => { + if (event.target.closest("a")) { + // Defer the sync operation because window.location.hash does not change + // until after the default action (i.e., the link click) for the event has + // happened. + setTimeout(() => { + syncTocHash(); + }, 0); + } + }); + /** * Activate an element and its chain of ancestor TOC entries; deactivate * everything else in the TOC. Together with the theme CSS, this unfolds From 6dddb360c1805277e21787049dfa8018d71415ec Mon Sep 17 00:00:00 2001 From: gabalafou Date: Sun, 6 Apr 2025 22:14:30 -0500 Subject: [PATCH 4/7] comment --- src/pydata_sphinx_theme/assets/scripts/pydata-sphinx-theme.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/pydata_sphinx_theme/assets/scripts/pydata-sphinx-theme.js b/src/pydata_sphinx_theme/assets/scripts/pydata-sphinx-theme.js index b4fe984c7..7b029922a 100644 --- a/src/pydata_sphinx_theme/assets/scripts/pydata-sphinx-theme.js +++ b/src/pydata_sphinx_theme/assets/scripts/pydata-sphinx-theme.js @@ -1015,8 +1015,8 @@ function setupArticleTocSyncing() { // Create a boolean variable that allows us to turn off the intersection // observer (and then later back on). When the website visitor clicks an - // in-page link, we want the entry in the TOC to be highlighted/activated, NOT - // whichever TOC link the intersection observer callback would otherwise + // in-page link, we want that entry in the TOC to be highlighted/activated, + // NOT whichever TOC link the intersection observer callback would otherwise // highlight. let disableObserver = false; From 122486b635cdd13f137d1bc50b284dc04b7e4f1f Mon Sep 17 00:00:00 2001 From: gabalafou Date: Sun, 6 Apr 2025 22:16:42 -0500 Subject: [PATCH 5/7] comment --- src/pydata_sphinx_theme/assets/scripts/pydata-sphinx-theme.js | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/pydata_sphinx_theme/assets/scripts/pydata-sphinx-theme.js b/src/pydata_sphinx_theme/assets/scripts/pydata-sphinx-theme.js index 7b029922a..bf2aea2a2 100644 --- a/src/pydata_sphinx_theme/assets/scripts/pydata-sphinx-theme.js +++ b/src/pydata_sphinx_theme/assets/scripts/pydata-sphinx-theme.js @@ -1049,6 +1049,10 @@ function setupArticleTocSyncing() { // the change event (because it's the same hash), but we still want to re-sync // the table of contents. window.addEventListener("click", (event) => { + // Match any link because an in-page ("hash link") can occur anywhere on the + // page (e.g., one section of the page linking to another section of the + // page, also each of the headings contains a link to itself), not just in + // the side table of contents. if (event.target.closest("a")) { // Defer the sync operation because window.location.hash does not change // until after the default action (i.e., the link click) for the event has From caba869c2a6aa5eb23985b8446fdd58e92bd0ca9 Mon Sep 17 00:00:00 2001 From: gabalafou Date: Sun, 6 Apr 2025 22:17:30 -0500 Subject: [PATCH 6/7] comment --- .../assets/scripts/pydata-sphinx-theme.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/pydata_sphinx_theme/assets/scripts/pydata-sphinx-theme.js b/src/pydata_sphinx_theme/assets/scripts/pydata-sphinx-theme.js index bf2aea2a2..a57468c05 100644 --- a/src/pydata_sphinx_theme/assets/scripts/pydata-sphinx-theme.js +++ b/src/pydata_sphinx_theme/assets/scripts/pydata-sphinx-theme.js @@ -1050,9 +1050,9 @@ function setupArticleTocSyncing() { // the table of contents. window.addEventListener("click", (event) => { // Match any link because an in-page ("hash link") can occur anywhere on the - // page (e.g., one section of the page linking to another section of the - // page, also each of the headings contains a link to itself), not just in - // the side table of contents. + // page, not just in the side table of contents (e.g., one section of the + // page linking to another section of the page, also each of the headings + // contains a link to itself). if (event.target.closest("a")) { // Defer the sync operation because window.location.hash does not change // until after the default action (i.e., the link click) for the event has From 59eae1b07b60dbb0bf4e50aa0cc0346ad2887997 Mon Sep 17 00:00:00 2001 From: gabalafou Date: Sun, 6 Apr 2025 22:18:13 -0500 Subject: [PATCH 7/7] comment --- src/pydata_sphinx_theme/assets/scripts/pydata-sphinx-theme.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/pydata_sphinx_theme/assets/scripts/pydata-sphinx-theme.js b/src/pydata_sphinx_theme/assets/scripts/pydata-sphinx-theme.js index a57468c05..98a8eaffd 100644 --- a/src/pydata_sphinx_theme/assets/scripts/pydata-sphinx-theme.js +++ b/src/pydata_sphinx_theme/assets/scripts/pydata-sphinx-theme.js @@ -1055,8 +1055,8 @@ function setupArticleTocSyncing() { // contains a link to itself). if (event.target.closest("a")) { // Defer the sync operation because window.location.hash does not change - // until after the default action (i.e., the link click) for the event has - // happened. + // until after the default action for the event has happened (i.e., the + // link click). setTimeout(() => { syncTocHash(); }, 0);