@@ -1013,20 +1013,79 @@ function setupArticleTocSyncing() {
1013
1013
return ;
1014
1014
}
1015
1015
1016
- // When the website visitor clicks a link in the TOC, we want that link to be
1017
- // highlighted/activated, NOT whichever TOC link the intersection observer
1018
- // callback would otherwise highlight, so we turn off the observer and turn it
1019
- // back on later.
1016
+ // Create a boolean variable that allows us to turn off the intersection
1017
+ // observer (and then later back on). When the website visitor clicks an
1018
+ // in-page link, we want that entry in the TOC to be highlighted/activated,
1019
+ // NOT whichever TOC link the intersection observer callback would otherwise
1020
+ // highlight.
1020
1021
let disableObserver = false ;
1021
- pageToc . addEventListener ( "click" , ( event ) => {
1022
+
1023
+ function temporarilyDisableObserver ( time ) {
1022
1024
disableObserver = true ;
1023
- const clickedTocLink = tocLinks . find ( ( el ) => el . contains ( event . target ) ) ;
1024
- activate ( clickedTocLink ) ;
1025
1025
setTimeout ( ( ) => {
1026
- // Give the page ample time to finish scrolling, then re-enable the
1027
- // intersection observer.
1028
1026
disableObserver = false ;
1029
- } , 1000 ) ;
1027
+ } , time ) ;
1028
+ }
1029
+
1030
+ /**
1031
+ * If the provided URL hash fragment (beginning with "#") matches an entry in
1032
+ * the page table of contents, highlight that entry and temporarily disable
1033
+ * the intersection observer while the page scrolls to the corresponding
1034
+ * heading.
1035
+ */
1036
+ function syncTocHash ( hash ) {
1037
+ if ( hash . length > 1 ) {
1038
+ const matchingTocLink = tocLinks . find ( ( tocLink ) => tocLink . hash === hash ) ;
1039
+ if ( matchingTocLink ) {
1040
+ // It's important to disable the intersection observer before
1041
+ // highlighting the TOC link and its corresponding article heading. This
1042
+ // is because the browser takes a little time to scroll to the article
1043
+ // heading, and while scrolling, it could trigger intersection events
1044
+ // that cause some other link in the table of contents to be
1045
+ // highlighted.
1046
+ temporarilyDisableObserver ( 1000 ) ;
1047
+ activate ( matchingTocLink ) ;
1048
+ }
1049
+ }
1050
+ }
1051
+
1052
+ // On page load...
1053
+ //
1054
+ // When the page loads, sync the page's table of contents.
1055
+ syncTocHash ( window . location . hash ) ;
1056
+
1057
+ // On navigation to another part of the page...
1058
+ //
1059
+ // When the user navigates to another part of the page, sync the page's table
1060
+ // of contents.
1061
+ window . addEventListener ( "hashchange" , ( ) => {
1062
+ // By the time this event is fired, window.location.hash has already been
1063
+ // updated with the new hash
1064
+ syncTocHash ( window . location . hash ) ;
1065
+ } ) ;
1066
+
1067
+ // On return to the same part of the page...
1068
+ //
1069
+ // The hashchange event will handle most cases where we need to sync the table
1070
+ // of contents with a hash link click. But there is one edge case it doesn't
1071
+ // handle, which is when the user clicks an internal page link whose hash is
1072
+ // already in the browser address bar. For example, the user loads the page at
1073
+ // #first-heading, scrolls to the bottom of the page, then clicks in the
1074
+ // sidebar table of contents to go back up to the first heading in the
1075
+ // article. In this case the "hashchange" event will not fire. Nonetheless, we
1076
+ // want to guarantee that the TOC entry for the first heading gets
1077
+ // highlighted. Note we cannot rely exclusively on the "click" event for all
1078
+ // internal page navigations because it will not fire in the edge case where
1079
+ // the user modifies the hash directly in the browser address bar.
1080
+ window . addEventListener ( "click" , ( event ) => {
1081
+ const link = event . target . closest ( "a" ) ;
1082
+ if (
1083
+ link &&
1084
+ link . hash === window . location . hash &&
1085
+ link . origin === window . location . origin
1086
+ ) {
1087
+ syncTocHash ( link . hash ) ;
1088
+ }
1030
1089
} ) ;
1031
1090
1032
1091
/**
@@ -1127,7 +1186,7 @@ function setupArticleTocSyncing() {
1127
1186
}
1128
1187
1129
1188
observer = new IntersectionObserver ( callback , options ) ;
1130
- headingsToTocLinks . keys ( ) . forEach ( ( heading ) => {
1189
+ Array . from ( headingsToTocLinks . keys ( ) ) . forEach ( ( heading ) => {
1131
1190
observer . observe ( heading ) ;
1132
1191
} ) ;
1133
1192
}
0 commit comments