diff --git a/Vue InstantSearch/routing-full-url/src/App.vue b/Vue InstantSearch/routing-full-url/src/App.vue index cc0e557557..b531ad0f4a 100644 --- a/Vue InstantSearch/routing-full-url/src/App.vue +++ b/Vue InstantSearch/routing-full-url/src/App.vue @@ -43,6 +43,27 @@ import algoliasearch from 'algoliasearch/lite'; import { history as historyRouter } from 'instantsearch.js/es/lib/routers'; import 'instantsearch.css/themes/algolia-min.css'; +// Returns a slug from the category name. +// Spaces are replaced by "+" to make +// the URL easier to read and other +// characters are encoded. +function getCategorySlug(name) { + return name + .split(' ') + .map(encodeURIComponent) + .join('+'); +} + +// Returns a name from the category slug. +// The "+" are replaced by spaces and other +// characters are decoded. +function getCategoryName(slug) { + return slug + .split('+') + .map(decodeURIComponent) + .join(' '); +} + export default { data() { return { @@ -52,65 +73,89 @@ export default { ), routing: { router: historyRouter({ - windowTitle(routeState) { - return `Website / Find ${routeState.q} in ${ - routeState.brands - } brands`; + windowTitle({ category, query }) { + const queryTitle = query ? `Results for "${query}"` : 'Search'; + + if (category) { + return `${category} – ${queryTitle}`; + } + + return queryTitle; }, - createURL({ routeState, location }) { - let baseUrl = location.href.split('/search/')[0]; - if ( - !routeState.q && - routeState.brands === 'all' && - routeState.p === 1 - ) - return baseUrl; - if (baseUrl[baseUrl.length - 1] !== '/') baseUrl += '/'; - let routeStateArray = [ - 'q', - encodeURIComponent(routeState.q), - 'brands', - encodeURIComponent(routeState.brands), - 'p', - routeState.p, - ]; - - return `${baseUrl}search/${routeStateArray.join('/')}`; + + createURL({ qsModule, routeState, location }) { + const urlParts = location.href.match(/^(.*?)\/search/); + const baseUrl = `${urlParts ? urlParts[1] : ''}/`; + + const categoryPath = routeState.category + ? `${getCategorySlug(routeState.category)}/` + : ''; + const queryParameters = {}; + + if (routeState.query) { + queryParameters.query = encodeURIComponent(routeState.query); + } + if (routeState.page !== 1) { + queryParameters.page = routeState.page; + } + if (routeState.brands) { + queryParameters.brands = routeState.brands.map( + encodeURIComponent + ); + } + + const queryString = qsModule.stringify(queryParameters, { + addQueryPrefix: true, + arrayFormat: 'repeat', + }); + + return `${baseUrl}search/${categoryPath}${queryString}`; }, - parseURL({ location }) { - let routeStateString = location.href.split('/search/')[1]; - if (routeStateString === undefined) return {}; - const routeStateValues = routeStateString.match( - /^q\/(.*?)\/brands\/(.*?)\/p\/(.*?)$/ + + parseURL({ qsModule, location }) { + const pathnameMatches = location.pathname.match( + /search\/(.*?)\/?$/ ); + const category = getCategoryName( + (pathnameMatches && pathnameMatches[1]) || '' + ); + const { query = '', page, brands = [] } = qsModule.parse( + location.search.slice(1) + ); + // `qs` does not return an array when there's a single value. + const allBrands = Array.isArray(brands) + ? brands + : [brands].filter(Boolean); + return { - q: decodeURIComponent(routeStateValues[1]), - brands: decodeURIComponent(routeStateValues[2]), - p: routeStateValues[3], + query: decodeURIComponent(query), + page, + brands: allBrands.map(decodeURIComponent), + category, }; }, }), + stateMapping: { stateToRoute(uiState) { return { - q: uiState.query || '', - brands: - (uiState.refinementList && - uiState.refinementList.brand && - uiState.refinementList.brand.join('~')) || - 'all', - p: uiState.page || 1, + query: uiState.query, + page: uiState.page, + brands: uiState.refinementList && uiState.refinementList.brand, + category: uiState.menu && uiState.menu.categories, }; }, - routeToState(routeState) { - if (routeState.brands === 'all') routeState.brands = undefined; + routeToState(routeState) { return { - query: routeState.q, + query: routeState.query, + page: routeState.page, + menu: { + categories: routeState.category, + }, refinementList: { - brand: routeState.brands && routeState.brands.split('~'), + brand: routeState.brands, }, - page: routeState.p, }; }, }, diff --git a/Vue InstantSearch/routing-state-mapping/src/App.vue b/Vue InstantSearch/routing-state-mapping/src/App.vue index 478f37b5ef..0ea94a6e50 100644 --- a/Vue InstantSearch/routing-state-mapping/src/App.vue +++ b/Vue InstantSearch/routing-state-mapping/src/App.vue @@ -41,6 +41,7 @@