@@ -11,6 +11,8 @@ export interface ILocationContext {
1111
1212interface ILocationParams extends Partial < ISelectionParams > {
1313 version : string ;
14+ search ?: string ;
15+ section ?: string ;
1416}
1517
1618interface ILocationProviderProps {
@@ -43,10 +45,11 @@ export function LocationProvider({ children }: ILocationProviderProps) {
4345
4446 const handleSetLocationParams = useCallback (
4547 ( newParams ?: ILocationParams ) => {
46- const version =
47- newParams ?. version . substring ( 0 , SHORT_COMMIT_HASH_LENGTH ) ||
48- metadata . versions [ metadata . latest ] ?. hash . substring ( 0 , SHORT_COMMIT_HASH_LENGTH ) ;
49- const versionName = newParams ? metadata . versions [ newParams . version ] ?. name : undefined ;
48+ const fullVersion = newParams ?. version ;
49+ const version = fullVersion
50+ ? fullVersion . substring ( 0 , SHORT_COMMIT_HASH_LENGTH )
51+ : metadata . versions [ metadata . latest ] ?. hash . substring ( 0 , SHORT_COMMIT_HASH_LENGTH ) ;
52+ const versionName = fullVersion ? metadata . versions [ fullVersion ] ?. name : metadata . versions [ metadata . latest ] ?. name ;
5053
5154 const stringifiedParams = [ ] ;
5255
@@ -59,54 +62,83 @@ export function LocationProvider({ children }: ILocationProviderProps) {
5962 ] . join ( "" ) ;
6063 }
6164
62- const newHash = `${ SEGMENT_SEPARATOR } ${ stringifiedParams . join ( SEGMENT_SEPARATOR ) } ` ;
63- window . location . hash = versionName ? `${ newHash } ?v=${ versionName } ` : newHash ;
65+ // we never put search/section to the URL,
66+ // yet we keep them in `locationParams`.
67+ const params : SearchParams = {
68+ v : versionName ,
69+ rest : `${ SEGMENT_SEPARATOR } ${ stringifiedParams . join ( SEGMENT_SEPARATOR ) } ` ,
70+ } ;
71+ window . location . hash = serializeSearchParams ( params ) ;
6472 } ,
6573 [ metadata ] ,
6674 ) ;
6775
6876 const handleHashChange = useCallback ( ( ) => {
69- const newHash = window . location . hash . substring ( 1 ) ;
70-
71- if ( ! newHash || ! newHash . startsWith ( SEGMENT_SEPARATOR ) ) {
72- handleSetLocationParams ( ) ;
77+ const { rest : newHash , search, section } = extractSearchParams ( window . location . hash ) ;
78+
79+ if ( ! newHash . startsWith ( SEGMENT_SEPARATOR ) ) {
80+ const version = metadata . latest ;
81+ setLocationParams ( ( params ) => ( {
82+ ...params ,
83+ version,
84+ search,
85+ section,
86+ } ) ) ;
87+ handleSetLocationParams ( { version, search, section } ) ;
7388 return ;
7489 }
7590
7691 const rawParams = newHash . split ( SEGMENT_SEPARATOR ) . slice ( 1 ) ;
92+ const selectedVersion = rawParams [ VERSION_SEGMENT_INDEX ] ;
7793
78- const fullVersion = Object . keys ( metadata . versions ) . find ( ( version ) =>
79- version . startsWith ( rawParams [ VERSION_SEGMENT_INDEX ] ) ,
80- ) ;
94+ const fullVersion =
95+ selectedVersion . length > 0
96+ ? Object . keys ( metadata . versions ) . find ( ( version ) => version . startsWith ( rawParams [ VERSION_SEGMENT_INDEX ] ) )
97+ : null ;
8198
8299 if ( ! fullVersion ) {
83- handleSetLocationParams ( ) ;
100+ const version = metadata . latest ;
101+ setLocationParams ( ( params ) => ( {
102+ ...params ,
103+ version,
104+ search,
105+ section,
106+ } ) ) ;
107+ handleSetLocationParams ( { version, search, section } ) ;
84108 return ;
85109 }
86110
87- const processedParams : ILocationParams = {
111+ const newLocationParams : ILocationParams = {
88112 version : fullVersion ,
113+ search,
114+ section,
89115 } ;
90116
91117 if ( rawParams [ SELECTION_SEGMENT_INDEX ] ) {
92118 const matchedHexSegments = [ ...rawParams [ SELECTION_SEGMENT_INDEX ] . matchAll ( SELECTION_DECOMPOSE_PATTERN ) ] ;
93119
94120 if ( matchedHexSegments . length === 2 ) {
95- processedParams . selectionStart = decodePageNumberAndIndex ( matchedHexSegments [ 0 ] [ 0 ] ) ;
96- processedParams . selectionEnd = decodePageNumberAndIndex ( matchedHexSegments [ 1 ] [ 0 ] ) ;
121+ newLocationParams . selectionStart = decodePageNumberAndIndex ( matchedHexSegments [ 0 ] [ 0 ] ) ;
122+ newLocationParams . selectionEnd = decodePageNumberAndIndex ( matchedHexSegments [ 1 ] [ 0 ] ) ;
97123 }
98124 }
99125
100126 // Update location but only if it has REALLY changed.
101127 setLocationParams ( ( params ) => {
102- if ( ! isSameBlock ( params ?. selectionStart , processedParams . selectionStart ) ) {
103- return processedParams ;
128+ if ( ! isSameBlock ( params ?. selectionStart , newLocationParams . selectionStart ) ) {
129+ return newLocationParams ;
130+ }
131+ if ( ! isSameBlock ( params ?. selectionEnd , newLocationParams . selectionEnd ) ) {
132+ return newLocationParams ;
104133 }
105- if ( ! isSameBlock ( params ?. selectionEnd , processedParams . selectionEnd ) ) {
106- return processedParams ;
134+ if ( params ?. version !== newLocationParams . version ) {
135+ return newLocationParams ;
107136 }
108- if ( params ?. version !== processedParams . version ) {
109- return processedParams ;
137+ if ( params ?. search !== newLocationParams . search ) {
138+ return newLocationParams ;
139+ }
140+ if ( params ?. section !== newLocationParams . section ) {
141+ return newLocationParams ;
110142 }
111143 return params ;
112144 } ) ;
@@ -174,3 +206,45 @@ function decodePageNumberAndIndex(s: string) {
174206 index += fromHex ( s . substring ( 4 , 6 ) ) << 8 ;
175207 return { pageNumber, index } ;
176208}
209+
210+ type SearchParams = {
211+ rest : string ;
212+ v ?: string ;
213+ search ?: string ;
214+ section ?: string ;
215+ } ;
216+
217+ function extractSearchParams ( hash : string ) : SearchParams {
218+ // skip the leading '/'
219+ const [ rest , searchParams ] = hash . substring ( 1 ) . split ( "?" ) ;
220+
221+ const result = {
222+ rest,
223+ v : undefined ,
224+ search : undefined ,
225+ section : undefined ,
226+ } ;
227+
228+ if ( ! searchParams ) {
229+ return result ;
230+ }
231+
232+ for ( const v of searchParams . split ( "&" ) ) {
233+ const [ key , val ] = v . split ( "=" ) ;
234+ if ( key in result ) {
235+ ( result as { [ key : string ] : string | undefined } ) [ key ] = decodeURIComponent ( val ) ;
236+ }
237+ }
238+
239+ return result ;
240+ }
241+ function serializeSearchParams ( { rest, ...searchParams } : SearchParams ) {
242+ const search = [ ] ;
243+ for ( const key of Object . keys ( searchParams ) ) {
244+ const val = searchParams [ key as keyof typeof searchParams ] ;
245+ if ( val ) {
246+ search . push ( `${ key } =${ encodeURIComponent ( val ) } ` ) ;
247+ }
248+ }
249+ return `${ rest } ${ search . length > 0 ? `?${ search . join ( "&" ) } ` : "" } ` ;
250+ }
0 commit comments