@@ -2,30 +2,27 @@ import { type ISelectionParams, type ISynctexBlock, isSameBlock } from "@fluffyl
22import { type ReactNode , createContext , useCallback , useContext , useEffect , useMemo , useState } from "react" ;
33import { deserializeLegacyLocation } from "../../utils/deserializeLegacyLocation" ;
44import { type IMetadataContext , MetadataContext } from "../MetadataProvider/MetadataProvider" ;
5+ import type { ILocationParams , SearchParams } from "./types" ;
6+ import {
7+ BASE64_VALIDATION_REGEX ,
8+ SEGMENT_SEPARATOR ,
9+ SELECTION_DECOMPOSE_PATTERN ,
10+ SELECTION_SEGMENT_INDEX ,
11+ VERSION_SEGMENT_INDEX ,
12+ } from "./utils/constants" ;
13+ import { locationParamsToHash } from "./utils/locationParamsToHash" ;
514
615export interface ILocationContext {
716 locationParams : ILocationParams ;
817 setLocationParams : ( newParams : ILocationParams ) => void ;
918 synctexBlocksToSelectionParams : ( blocks : ISynctexBlock [ ] ) => ISelectionParams ;
10- }
11-
12- interface ILocationParams extends Partial < ISelectionParams > {
13- version : string ;
14- search ?: string ;
15- section ?: string ;
19+ getHashFromLocationParams : ( params : ILocationParams ) => string ;
1620}
1721
1822interface ILocationProviderProps {
1923 children : ReactNode ;
2024}
2125
22- const VERSION_SEGMENT_INDEX = 0 ;
23- const SELECTION_SEGMENT_INDEX = 1 ;
24- const SEGMENT_SEPARATOR = "/" ;
25- const SELECTION_DECOMPOSE_PATTERN = / [ 0 - 9 A - F ] { 6 } / gi;
26- const SHORT_COMMIT_HASH_LENGTH = 7 ; // as many as git uses for `git rev-parse --short`
27- const BASE64_VALIDATION_REGEX = / ^ # [ - A - Z a - z 0 - 9 + / ] * = { 0 , 3 } $ / ;
28-
2926export const LocationContext = createContext < ILocationContext | null > ( null ) ;
3027
3128export const useLocationContext = ( ) => {
@@ -53,30 +50,17 @@ export function LocationProvider({ children }: ILocationProviderProps) {
5350
5451 const handleSetLocationParams = useCallback (
5552 ( newParams ?: ILocationParams ) => {
56- const fullVersion = newParams ?. version ;
57- const version = fullVersion
58- ? fullVersion . substring ( 0 , SHORT_COMMIT_HASH_LENGTH )
59- : metadata . versions [ metadata . latest ] ?. hash . substring ( 0 , SHORT_COMMIT_HASH_LENGTH ) ;
60- const versionName = fullVersion ? metadata . versions [ fullVersion ] ?. name : metadata . versions [ metadata . latest ] ?. name ;
61-
62- const stringifiedParams = [ ] ;
63-
64- stringifiedParams [ VERSION_SEGMENT_INDEX ] = version ;
65-
66- if ( newParams ?. selectionStart && newParams ?. selectionEnd ) {
67- stringifiedParams [ SELECTION_SEGMENT_INDEX ] = [
68- encodePageNumberAndIndex ( newParams . selectionStart . pageNumber , newParams . selectionStart . index ) ,
69- encodePageNumberAndIndex ( newParams . selectionEnd . pageNumber , newParams . selectionEnd . index ) ,
70- ] . join ( "" ) ;
71- }
53+ if ( ! newParams ) return ;
54+ const hash = locationParamsToHash ( newParams , metadata ) ;
55+ window . location . hash = hash ;
56+ } ,
57+ [ metadata ] ,
58+ ) ;
7259
73- // we never put search/section to the URL,
74- // yet we keep them in `locationParams`.
75- const params : SearchParams = {
76- v : versionName ,
77- rest : `${ SEGMENT_SEPARATOR } ${ stringifiedParams . join ( SEGMENT_SEPARATOR ) } ` ,
78- } ;
79- window . location . hash = serializeSearchParams ( params ) ;
60+ const getHashFromLocationParams = useCallback (
61+ ( params : ILocationParams ) => {
62+ const hash = locationParamsToHash ( params , metadata ) ;
63+ return hash ;
8064 } ,
8165 [ metadata ] ,
8266 ) ;
@@ -191,8 +175,9 @@ export function LocationProvider({ children }: ILocationProviderProps) {
191175 locationParams,
192176 setLocationParams : handleSetLocationParams ,
193177 synctexBlocksToSelectionParams,
178+ getHashFromLocationParams,
194179 } ;
195- } , [ locationParams , handleSetLocationParams , synctexBlocksToSelectionParams ] ) ;
180+ } , [ locationParams , handleSetLocationParams , synctexBlocksToSelectionParams , getHashFromLocationParams ] ) ;
196181
197182 if ( ! context ) {
198183 return null ;
@@ -201,11 +186,6 @@ export function LocationProvider({ children }: ILocationProviderProps) {
201186 return < LocationContext . Provider value = { context } > { children } </ LocationContext . Provider > ;
202187}
203188
204- function encodePageNumberAndIndex ( pageNumber : number , index : number ) {
205- const asHexByte = ( num : number ) => ( num & 0xff ) . toString ( 16 ) . padStart ( 2 , "0" ) ;
206- return `${ asHexByte ( pageNumber ) } ${ asHexByte ( index ) } ${ asHexByte ( index >> 8 ) } ` ;
207- }
208-
209189function decodePageNumberAndIndex ( s : string ) {
210190 if ( s . length > 6 ) throw new Error ( "Pass exactly 6 hex characters" ) ;
211191 const fromHex = ( s : string ) => Number ( `0x${ s } ` ) ;
@@ -215,13 +195,6 @@ function decodePageNumberAndIndex(s: string) {
215195 return { pageNumber, index } ;
216196}
217197
218- type SearchParams = {
219- rest : string ;
220- v ?: string ;
221- search ?: string ;
222- section ?: string ;
223- } ;
224-
225198function extractSearchParams ( hash : string ) : SearchParams {
226199 // skip the leading '/'
227200 const [ rest , searchParams ] = hash . substring ( 1 ) . split ( "?" ) ;
@@ -246,13 +219,3 @@ function extractSearchParams(hash: string): SearchParams {
246219
247220 return result ;
248221}
249- function serializeSearchParams ( { rest, ...searchParams } : SearchParams ) {
250- const search = [ ] ;
251- for ( const key of Object . keys ( searchParams ) ) {
252- const val = searchParams [ key as keyof typeof searchParams ] ;
253- if ( val ) {
254- search . push ( `${ key } =${ encodeURIComponent ( val ) } ` ) ;
255- }
256- }
257- return `${ rest } ${ search . length > 0 ? `?${ search . join ( "&" ) } ` : "" } ` ;
258- }
0 commit comments