@@ -52,11 +52,121 @@ function enableInstantSearch(config) {
5252 query : config . urlQuery ,
5353 } ;
5454
55+ // Custom URL routing: short, stable URL keys (q, category, brand, size, color, store, price, sort, page, etc.)
56+ // instead of the default `simple` mapping's index-prefixed keys, with `arrayFormat: 'repeat'
57+ // so `?brand=Apple&brand=Samsung` replaces `?brand[0]=Apple&brand[1]=Samsung`.
58+ const isMaster = algoliaData . recordModel === 'master-level' || algoliaData . recordModel === 'attribute-sliced' ;
59+ const sizeAttribute = isMaster ? 'variants.size' : 'size' ;
60+ const colorAttribute = isMaster ? 'variants.color' : 'color' ;
61+ const storeAttribute = isMaster ? 'variants.storeAvailability' : 'storeAvailability' ;
62+ const priceAttribute = isMaster ? 'variants.price.' + algoliaData . currencyCode : 'price.' + algoliaData . currencyCode ;
63+
64+ /**
65+ * Compresses a hierarchicalMenu uiState slot (which stores cumulative paths at
66+ * every level) into one segment per level so the URL reads as one value per level.
67+ * Assumes category display names do not contain ' > '.
68+ * @param {string[] } levels Cumulative paths, e.g. ['Womens', 'Womens > Clothing']
69+ * @returns {string[] } One segment per level, e.g. ['Womens', 'Clothing']
70+ */
71+ function compressBreadcrumb ( levels ) {
72+ return levels . map ( function ( level , i ) {
73+ return i === 0 ? level : level . split ( ' > ' ) . pop ( ) ;
74+ } ) ;
75+ }
76+
77+ /**
78+ * Rebuilds the cumulative-path form the hierarchicalMenu widget expects in uiState
79+ * from the per-level segments carried in the URL. Inverse of compressBreadcrumb.
80+ * @param {string[] } segments One segment per level, e.g. ['Womens', 'Clothing']
81+ * @returns {string[] } Cumulative paths, e.g. ['Womens', 'Womens > Clothing']
82+ */
83+ function expandBreadcrumb ( segments ) {
84+ return segments . map ( function ( _ , i ) {
85+ return segments . slice ( 0 , i + 1 ) . join ( ' > ' ) ;
86+ } ) ;
87+ }
88+
89+ // URL keys this mapping owns. Anything else already on the URL (lang, utm_*, ...) is
90+ // preserved across refinements so locale and marketing parameters are not stripped.
91+ const routeKeys = [ 'q' , 'category' , 'newArrivals' , 'newArrival' , 'brand' , 'color' , 'size' , 'store' , 'price' , 'sort' , 'page' ] ;
92+
93+ const router = instantsearch . routers . history ( {
94+ createURL : function ( params ) {
95+ const qsModule = params . qsModule ;
96+ const url = new URL ( params . location . href ) ;
97+ const current = qsModule . parse ( url . search . slice ( 1 ) ) ;
98+ routeKeys . forEach ( function ( key ) { delete current [ key ] ; } ) ;
99+ const merged = Object . assign ( { } , current , params . routeState ) ;
100+ url . search = qsModule . stringify ( merged , { arrayFormat : 'repeat' } ) ;
101+ return url . href ;
102+ }
103+ } ) ;
104+
105+ const stateMapping = {
106+ stateToRoute : function ( uiState ) {
107+ var indexUiState = uiState [ productsIndex ] || { } ;
108+ var route = { } ;
109+ if ( indexUiState . query ) route . q = indexUiState . query ;
110+ if ( indexUiState . page ) route . page = indexUiState . page ;
111+ if ( indexUiState . sortBy === productsIndex ) route . sort = 'best' ;
112+ if ( indexUiState . sortBy === productsIndexPriceAsc ) route . sort = 'price-asc' ;
113+ if ( indexUiState . sortBy === productsIndexPriceDesc ) route . sort = 'price-desc' ;
114+ if ( indexUiState . hierarchicalMenu && indexUiState . hierarchicalMenu [ '__primary_category.0' ] ) {
115+ route . category = compressBreadcrumb ( indexUiState . hierarchicalMenu [ '__primary_category.0' ] ) ;
116+ }
117+ if ( indexUiState . hierarchicalMenu && indexUiState . hierarchicalMenu [ 'newArrivalsCategory.0' ] ) {
118+ route . newArrivals = compressBreadcrumb ( indexUiState . hierarchicalMenu [ 'newArrivalsCategory.0' ] ) ;
119+ }
120+ if ( indexUiState . toggle && indexUiState . toggle . newArrival ) route . newArrival = '1' ;
121+ if ( indexUiState . refinementList ) {
122+ if ( indexUiState . refinementList . brand ) route . brand = indexUiState . refinementList . brand ;
123+ if ( indexUiState . refinementList [ sizeAttribute ] ) route . size = indexUiState . refinementList [ sizeAttribute ] ;
124+ if ( indexUiState . refinementList [ colorAttribute ] ) route . color = indexUiState . refinementList [ colorAttribute ] ;
125+ if ( indexUiState . refinementList [ storeAttribute ] ) route . store = indexUiState . refinementList [ storeAttribute ] ;
126+ }
127+ if ( indexUiState . range && indexUiState . range [ priceAttribute ] ) route . price = indexUiState . range [ priceAttribute ] ;
128+ return route ;
129+ } ,
130+ routeToState : function ( route ) {
131+ var indexUiState = { } ;
132+ if ( route . q ) indexUiState . query = route . q ;
133+ if ( route . page ) indexUiState . page = Number ( route . page ) ;
134+ if ( route . sort === 'best' ) indexUiState . sortBy = productsIndex ;
135+ if ( route . sort === 'price-asc' ) indexUiState . sortBy = productsIndexPriceAsc ;
136+ if ( route . sort === 'price-desc' ) indexUiState . sortBy = productsIndexPriceDesc ;
137+ if ( route . category ) {
138+ indexUiState . hierarchicalMenu = indexUiState . hierarchicalMenu || { } ;
139+ indexUiState . hierarchicalMenu [ '__primary_category.0' ] = expandBreadcrumb ( [ ] . concat ( route . category ) ) ;
140+ }
141+ if ( route . newArrivals ) {
142+ indexUiState . hierarchicalMenu = indexUiState . hierarchicalMenu || { } ;
143+ indexUiState . hierarchicalMenu [ 'newArrivalsCategory.0' ] = expandBreadcrumb ( [ ] . concat ( route . newArrivals ) ) ;
144+ }
145+ if ( route . newArrival === '1' ) indexUiState . toggle = { newArrival : true } ;
146+ var refinementList = { } ;
147+ if ( route . brand ) refinementList . brand = [ ] . concat ( route . brand ) ;
148+ if ( route . size ) refinementList [ sizeAttribute ] = [ ] . concat ( route . size ) ;
149+ if ( route . color ) refinementList [ colorAttribute ] = [ ] . concat ( route . color ) ;
150+ if ( route . store ) refinementList [ storeAttribute ] = [ ] . concat ( route . store ) ;
151+ if ( Object . keys ( refinementList ) . length ) indexUiState . refinementList = refinementList ;
152+ if ( route . price ) {
153+ indexUiState . range = { } ;
154+ indexUiState . range [ priceAttribute ] = route . price ;
155+ }
156+ var ui = { } ;
157+ ui [ productsIndex ] = indexUiState ;
158+ if ( route . q ) {
159+ ui [ contentsIndex ] = { query : route . q } ;
160+ }
161+ return ui ;
162+ }
163+ } ;
164+
55165 var search = instantsearch ( {
56166 indexName : productsIndex ,
57167 searchClient : config . searchClient ,
58168 initialUiState : initialUiState ,
59- routing : true ,
169+ routing : { router : router , stateMapping : stateMapping } ,
60170 } ) ;
61171
62172 if ( algoliaData . enableInsights ) {
0 commit comments