Skip to content

Commit 32bce43

Browse files
authored
Improve search url canonicalization and mark all links nofollow/noindex (composer#1705)
1 parent fdfa109 commit 32bce43

1 file changed

Lines changed: 37 additions & 10 deletions

File tree

js/search.js

Lines changed: 37 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import algoliasearch from 'algoliasearch/lite';
22
import instantsearch from 'instantsearch.js';
3+
import historyRouter from 'instantsearch.js/es/lib/routers/history';
34
import { connectSearchBox, connectCurrentRefinements } from 'instantsearch.js/es/connectors';
45
import { hits, pagination, clearRefinements, menu, refinementList, configure, panel } from 'instantsearch.js/es/widgets';
56

@@ -34,6 +35,8 @@ if (decodeURI(location.search).match(/[<>]/)) {
3435
location.replace(location.origin + location.pathname);
3536
}
3637

38+
var isSearchPage = location.pathname === '/search/';
39+
var originalPathname = location.pathname;
3740
var searchClient = algoliasearch(algoliaConfig.app_id, algoliaConfig.search_key);
3841
var indexName = algoliaConfig.index_name;
3942
var searchThrottle = null;
@@ -59,7 +62,12 @@ var customSearchClient = {
5962

6063
// Show search container on initial load if URL has search params
6164
var urlParams = new URLSearchParams(window.location.search);
62-
if (urlParams.get('query') || urlParams.get('q') || urlParams.get('type') || urlParams.get('tags')) {
65+
var hasQuery = (urlParams.get('query') || '').trim() !== '' || (urlParams.get('q') || '').trim() !== '';
66+
var hasFilters = urlParams.get('type') || urlParams.get('tags');
67+
if (!isSearchPage && !hasQuery && hasFilters) {
68+
// Redirect to canonical /search/ URL with the filter params
69+
location.replace('/search/' + location.search);
70+
} else if (hasQuery || (isSearchPage && hasFilters)) {
6371
document.querySelector('#search-container').classList.remove('hidden');
6472
}
6573

@@ -70,9 +78,10 @@ var opts = {
7078
var indexState = uiState[indexName] || {};
7179
var searchResults = document.querySelector('#search-container');
7280

73-
var hasSearch = (indexState.query && indexState.query !== '')
74-
|| (indexState.menu && indexState.menu.type)
81+
var hasQuery = indexState.query && indexState.query.trim() !== '';
82+
var hasFilters = (indexState.menu && indexState.menu.type)
7583
|| (indexState.refinementList && indexState.refinementList.tags && indexState.refinementList.tags.length > 0);
84+
var hasSearch = hasQuery || (isSearchPage && hasFilters);
7685

7786
if (!hasSearch) {
7887
searchResults.classList.add('hidden');
@@ -98,6 +107,17 @@ var opts = {
98107
setUiState(uiState);
99108
},
100109
routing: {
110+
router: historyRouter({
111+
createURL: function ({ qsModule, routeState, location }) {
112+
var queryString = qsModule.stringify(routeState);
113+
var protocol = location.protocol;
114+
var hostname = location.hostname;
115+
var port = location.port ? ':' + location.port : '';
116+
// Use /search/ as base path when there are search params, otherwise restore original path
117+
var pathname = queryString ? '/search/' : originalPathname;
118+
return protocol + '//' + hostname + port + pathname + (queryString ? '?' + queryString : '') + location.hash;
119+
},
120+
}),
101121
stateMapping: {
102122
stateToRoute: function (uiState) {
103123
var indexUiState = uiState[indexName] || {};
@@ -113,11 +133,10 @@ var opts = {
113133
routeState.query = routeState.q;
114134
}
115135

116-
if (
117-
(!routeState.query || routeState.query === '')
118-
&& (!routeState.type || routeState.type === '')
119-
&& (!routeState.tags || routeState.tags === '')
120-
) {
136+
var hasQuery = routeState.query && routeState.query.trim() !== '';
137+
var hasFilters = (routeState.type && routeState.type !== '')
138+
|| (routeState.tags && routeState.tags !== '');
139+
if (!hasQuery && !(isSearchPage && hasFilters)) {
121140
return { [indexName]: {} };
122141
}
123142

@@ -251,7 +270,7 @@ search.addWidgets([
251270
if (hit.abandoned) {
252271
var replacementHtml = '';
253272
if (hit.replacementPackage) {
254-
replacementHtml = ` See <a href="${hit.replacementPackageUrl}">${hit.replacementPackage}</a>`;
273+
replacementHtml = ` See <a href="${hit.replacementPackageUrl}" rel="nofollow noindex">${hit.replacementPackage}</a>`;
255274
}
256275
abandonedHtml = `<p class="abandoned"><i class="glyphicon glyphicon-exclamation-sign"></i> Abandoned!${replacementHtml}</p>`;
257276
}
@@ -275,7 +294,7 @@ search.addWidgets([
275294
<div class="col-sm-9 col-lg-10">
276295
<p class="pull-right language">${hit.language || ''}</p>
277296
<h4 class="font-bold">
278-
<a href="${hit.url}" tabindex="2">${nameHighlight}</a>${extensionHtml}
297+
<a href="${hit.url}" tabindex="2" rel="nofollow noindex">${nameHighlight}</a>${extensionHtml}
279298
${virtualHtml}
280299
</h4>
281300
<p>${descHighlight}</p>
@@ -330,4 +349,12 @@ if (location.href.match(/\/extensions/)) {
330349
search.addWidgets([configure({ filters: 'extension = 1' })]);
331350
}
332351

352+
search.on('render', function () {
353+
document.querySelectorAll('#search-container a[href]').forEach(function (link) {
354+
if (!link.getAttribute('rel')) {
355+
link.setAttribute('rel', 'nofollow noindex');
356+
}
357+
});
358+
});
359+
333360
search.start();

0 commit comments

Comments
 (0)