@@ -39,7 +39,7 @@ import {
3939 HttpErrorContent ,
4040} from '@/components' ;
4141import { LoginToContinueModal , BadgeModal } from '@/components/Modal' ;
42- import { changeTheme , Storage } from '@/utils' ;
42+ import { changeTheme , Storage , scrollToElementTop } from '@/utils' ;
4343import { useQueryNotificationStatus } from '@/services' ;
4444import { useExternalToast } from '@/hooks' ;
4545import { EXTERNAL_CONTENT_DISPLAY_MODE } from '@/common/constants' ;
@@ -58,6 +58,74 @@ const Layout: FC = () => {
5858 const { show : showLoginToContinueModal } = loginToContinueStore ( ) ;
5959 const { data : notificationData } = useQueryNotificationStatus ( ) ;
6060
61+ useEffect ( ( ) => {
62+ // handle footnote links
63+ const fixFootnoteLinks = ( ) => {
64+ const footnoteLinks = document . querySelectorAll (
65+ 'a[href^="#"]:not([data-footnote-fixed])' ,
66+ ) ;
67+
68+ footnoteLinks . forEach ( ( link ) => {
69+ link . setAttribute ( 'data-footnote-fixed' , 'true' ) ;
70+ const href = link . getAttribute ( 'href' ) ;
71+ link . addEventListener ( 'click' , ( e ) => {
72+ e . preventDefault ( ) ;
73+ const targetId = href ?. substring ( 1 ) || '' ;
74+ const targetElement = document . getElementById ( targetId ) ;
75+
76+ if ( targetElement ) {
77+ window . history . pushState ( null , '' , `${ location . pathname } ${ href } ` ) ;
78+
79+ scrollToElementTop ( targetElement ) ;
80+ }
81+ } ) ;
82+ } ) ;
83+
84+ if ( window . location . hash ) {
85+ const { hash } = window . location ;
86+ const targetElement = document . getElementById ( hash . substring ( 1 ) ) ;
87+
88+ if ( targetElement ) {
89+ setTimeout ( ( ) => {
90+ scrollToElementTop ( targetElement ) ;
91+ } , 100 ) ;
92+ }
93+ }
94+ } ;
95+ fixFootnoteLinks ( ) ;
96+
97+ const observer = new MutationObserver ( ( ) => {
98+ fixFootnoteLinks ( ) ;
99+ } ) ;
100+
101+ observer . observe ( document . body , {
102+ childList : true ,
103+ subtree : true ,
104+ attributes : true ,
105+ attributeFilter : [ 'id' , 'href' ] ,
106+ } ) ;
107+
108+ const handleHashChange = ( ) => {
109+ if ( window . location . hash ) {
110+ const { hash } = window . location ;
111+ const targetElement = document . getElementById ( hash . substring ( 1 ) ) ;
112+
113+ if ( targetElement ) {
114+ setTimeout ( ( ) => {
115+ scrollToElementTop ( targetElement ) ;
116+ } , 100 ) ;
117+ }
118+ }
119+ } ;
120+
121+ window . addEventListener ( 'hashchange' , handleHashChange ) ;
122+
123+ return ( ) => {
124+ observer . disconnect ( ) ;
125+ window . removeEventListener ( 'hashchange' , handleHashChange ) ;
126+ } ;
127+ } , [ location . pathname ] ) ;
128+
61129 useEffect ( ( ) => {
62130 httpStatusReset ( ) ;
63131 } , [ location ] ) ;
0 commit comments