import { memo , useEffect , StrictMode } from 'react' ; import { useLocation } from '~/components/router/client' ; import { useDocumentTitle } from '~/hooks/useDocumentTitle' ; import { useMetaDescription } from '~/hooks/useMetaDescription' ; import { usePrevious } from '~/hooks/usePrevious' ; import { scrollSmooth } from '~/services/scrollSmooth' ; import type { Location } from 'history' ; interface ScrollPosition { x : number ; y : number ; } interface Props { title ? : string ; description ? : string ; } interface PropsMemo { location : Location ; title ? : string ; description ? : string ; } const MAX_SCROLL_ENTRIES = 50 ; const SCROLL_ENTRIES_SESSION_KEY = 'scrollEntries' ; const SCROLL_RESTORATION = 'scrollRestoration' in window . history ; const TERMINATION_EVENT = 'onpagehide' in self ? 'pagehide' : 'unload' ; let scrollEntries = new Map ( ) ; if ( SCROLL_RESTORATION ) { window . history . scrollRestoration = 'manual' ; const scrollEntriesSession = sessionStorage . getItem ( SCROLL_ENTRIES_SESSION_KEY ) ; if ( scrollEntriesSession != null ) { scrollEntries = new Map ( JSON . parse ( scrollEntriesSession ) ) ; } } function storeScroll ( key ) { scrollEntries . set ( key , { x : window . pageXOffset , y : window . pageYOffset } ) ; if ( scrollEntries . size > MAX_SCROLL_ENTRIES ) { scrollEntries . delete ( scrollEntries . keys ( ) . next ( ) . value ) ; } } function scrollToTop ( ) { if ( window . pageXOffset + window . pageYOffset > 0 ) { scroll ( 0 , 0 ) ; } } function arePropsEqual ( { location : prevLocation } , { location : nextLocation } ) { if ( ! FLAG_PRODUCTION ) { if ( prevLocation === nextLocation ) { return false ; } } return prevLocation . pathname === nextLocation . pathname && prevLocation . search === nextLocation . search ; } const PageViewWithLocation = ( { location , title , description , children } ) => { const { hash , key = 'default' } = location ; useDocumentTitle ( title ) ; useMetaDescription ( description ) ; useEffect ( ( ) => { if ( SCROLL_RESTORATION ) { const storeScrollEntriesInSession = ( ) => { storeScroll ( key ) ; sessionStorage . setItem ( SCROLL_ENTRIES_SESSION_KEY , JSON . stringify ( Array . from ( scrollEntries ) ) ) ; } ; const entry = scrollEntries . get ( key ) ; if ( entry !== undefined ) { scroll ( entry . x , entry . y ) ; } else if ( hash . length === 0 ) { scrollToTop ( ) ; } window . addEventListener ( TERMINATION_EVENT , storeScrollEntriesInSession ) ; return ( ) => { storeScroll ( key ) ; window . removeEventListener ( TERMINATION_EVENT , storeScrollEntriesInSession ) ; } ; } else if ( hash . length === 0 ) { scrollToTop ( ) ; } } , [ key , hash ] ) ; return children !== undefined ? < StrictMode > { children } < /StrictMode> : null; } ; const PageViewMemo = memo ( PageViewWithLocation , arePropsEqual ) ; let defaultScrollPos = [ 0 , 0 ] ; let scrolling = false ; function scrollEnd ( ) { scrolling = false ; } function scrollToTarget ( el ) { var rect = el . getBoundingClientRect ( ) ; scrollSmooth ( 0 , rect . top + window . pageYOffset ) ; window . setTimeout ( scrollEnd , 0 ) ; } export const PageView = ( props ) => { const location = useLocation ( ) ; const { hash } = location ; const prevHash = usePrevious ( hash , '' ) ; useEffect ( ( ) => { const targetEl = hash . length > 1 ? document . getElementById ( hash . slice ( 1 ) ) : null ; if ( prevHash === null ) { if ( targetEl !== null ) { scrollToTarget ( targetEl ) ; } } else if ( prevHash !== hash ) { if ( hash . length && targetEl !== null ) { if ( prevHash === '' ) { defaultScrollPos = [ window . pageXOffset , window . pageYOffset ] ; } scrolling = true ; scrollToTarget ( targetEl ) ; } else if ( ! scrolling ) { scrolling = true ; scrollSmooth ( defaultScrollPos [ 0 ] , defaultScrollPos [ 1 ] ) ; setTimeout ( scrollEnd , 0 ) ; } } } , [ hash , prevHash ] ) ; return < PageViewMemo location = { location } { ... props } />; } ;