Skip to content

Feature/docsearch v3 #16545

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 2 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions data/versions.yml
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,11 @@ main: "1.26"

# The version of Istio currently documented in preliminary.istio.io
preliminary: "1.27"

supported_versions:
- version: "latest"
- version: "v1.25"
- version: "v1.24"
- version: "v1.23"
- version: "v1.22"
- version: "v1.21"
98 changes: 40 additions & 58 deletions layouts/_default/search.html
Original file line number Diff line number Diff line change
@@ -1,59 +1,41 @@
{{ define "main" }}

{{ partial "primary_top.html" . }}

<div class="search-results">
<script>
(function() {
let cx;
{{ if .Site.Data.args.preliminary }}
cx = '{{ .Site.Data.args.preliminary_search_engine_id }}';
{{ else if .Site.Data.args.archive }}
cx = '{{ .Site.Data.args.archive_search_engine_id }}';
{{ else }}
cx = '{{ .Site.Data.args.main_search_engine_id }}';
{{ end }}
if(window.location.href.includes('&site=')){
cx = '{{ .Site.Data.args.docs_search_engine_id }}';
}
const gcse = document.createElement('script');
gcse.type = 'text/javascript';
gcse.async = true;
gcse.src = 'https://cse.google.com/cse.js?cx=' + cx;
const s = document.getElementsByTagName('script')[0];
s.parentNode.insertBefore(gcse, s);
})();
</script>

{{ if .Site.Data.args.archive }}
<gcse:searchresults-only defaultToRefinement="{{ .Site.Data.args.archive_search_refinement }}"></gcse:searchresults-only>
{{ else }}
<gcse:searchresults-only></gcse:searchresults-only>
{{ end }}
</div>

<script>
function getParameterByName(name, url) {
if (!url) {
url = window.location.href;
}
name = name.replace(/[\[\]]/g, "\\$&");
const regex = new RegExp("[?&]" + name + "(=([^&#]*)|&|#|$)"),
results = regex.exec(url);
if (!results) {
return null;
}
if (!results[2]) {
return '';
}
return decodeURIComponent(results[2].replace(/\+/g, " "));
}
const q = getParameterByName('q', window.location.href);
document.getElementsByName('q')[0].value = q;
</script>

{{ .Content }}

{{ partial "primary_bottom.html" . }}

{{ define "main" }}

{{ partial "primary_top.html" . }}

<div class="search-results">
<div id="search-controls">
{{ $languages := .Site.Data.args.supported_languages }}
{{ $versions := .Site.Data.versions.supported_versions }}

<label for="lang-filter">Language:</label>
<select id="lang-filter">
{{ range $languages }}
<option value="{{ .code }}">{{ .name }}</option>
{{ end }}
</select>

<label for="version-filter">Version:</label>
<select id="version-filter">
{{ range $versions }}
<option value="{{ .version }}">{{ .version }}</option>
{{ end }}
</select>
</div>


<div id="search-query-header"></div>
<div id="search-results">Loading results...</div>
<div id="pagination-controls">
<div><button id="prev-page" class="btn" disabled>Previous</button></div>
<span id="page-info"></span>
<div><button id="next-page" class="btn" disabled>Next</button></div>
</div>
</div>

<script src="https://cdn.jsdelivr.net/npm/algoliasearch@4/dist/algoliasearch-lite.umd.js"></script>

{{ .Content }}

{{ partial "primary_bottom.html" . }}

{{ end }}
42 changes: 29 additions & 13 deletions layouts/partials/header.html
Original file line number Diff line number Diff line change
Expand Up @@ -55,19 +55,35 @@

</div>

<form id="search-form" class="search" name="cse" role="search">
{{ if .Site.Data.args.preliminary }}
<input type="hidden" name="cx" value="{{ .Site.Data.args.preliminary_search_engine_id }}" />
{{ else if or .Site.Data.args.archive .Site.Data.args.archive_landing }}
<input type="hidden" name="cx" value="{{ .Site.Data.args.archive_search_engine_id }}" />
{{ else }}
<input type="hidden" name="cx" value="{{ .Site.Data.args.main_search_engine_id }}" />
{{ end }}
<input type="hidden" name="ie" value="utf-8" />
<input type="hidden" name="hl" value="{{ .Page.Lang }}" />
<input type="hidden" id="search-page-url" value="{{ "/search" | relLangURL }}" />
<input id="search-textbox" class="search-textbox form-control" name="q" type="search" aria-label='{{ i18n "search" }}' placeholder='{{ i18n "search_label" }}' />
<button id="search-close" title='{{ i18n "search_cancel" }}' type="reset" aria-label='{{ i18n "search_cancel" }}'>{{ partial "icon.html" "menu-close" }}</button>
<form id="search-form" class="search" role="search" onsubmit="return redirectToSearch(event)">
<input id="search-textbox" class="search-textbox form-control" name="q" type="search" placeholder="Search..." />
<button id="search-close" type="reset">{{ partial "icon.html" "menu-close" }}</button>
</form>

<script>
function redirectToSearch(event) {
event.preventDefault();

const query = document.getElementById('search-textbox').value.trim();
if (!query) return false;

const pathParts = window.location.pathname.split('/').filter(Boolean);
let version = 'latest';
let lang = '';

for (const part of pathParts) {
if (/^v[0-9.]+$/.test(part) || part === 'latest') {
version = part;
} else if (/^[a-z]{2}(-[a-z]{2})?$/.test(part)) {
lang = part;
}
}
const langSegment = (lang && lang !== 'en') ? `/${lang}` : '';
const searchUrl = `/${version}${langSegment}/search/?q=${encodeURIComponent(query)}`;

window.location.href = searchUrl;
return false;
}
</script>
</nav>
</header>
1 change: 1 addition & 0 deletions scripts/gen_site.sh
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ babel --source-maps --minified --no-comments --presets minify \
tmp/js/callToAction.js \
tmp/js/events.js \
tmp/js/faq.js \
tmp/js/search.js \
--out-file generated/js/all.min.js

babel --source-maps --minified --no-comments --presets minify \
Expand Down
116 changes: 116 additions & 0 deletions src/ts/search.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
const appId = '95IG3UJ1LV';
const apiKey = '23827432dd1f4c529eece856289a421c';
const indexName = 'istio';

const searchClient = algoliasearch(appId, apiKey);
const index = searchClient.initIndex(indexName);

function getParameterByName(name, url = window.location.href) {
name = name.replace(/[\[\]]/g, "\\$&");
const regex = new RegExp("[?&]" + name + "(=([^&#]*)|&|#|$)");
const results = regex.exec(url);
if (!results) return null;
if (!results[2]) return '';
return decodeURIComponent(results[2].replace(/\+/g, " "));
}

function extractVersionAndLangFromPath() {
const pathParts = window.location.pathname.split('/');
let version = 'latest';
let lang = 'en';

for (const part of pathParts) {
if (/^v[0-9.]+$/.test(part) || part === 'latest') {
version = part;
} else if (/^[a-z]{2}(-[a-z]{2})?$/.test(part)) {
lang = part;
}
}

return { version, lang };
}

function updateURLParam(param, value) {
const url = new URL(window.location.href);
url.searchParams.set(param, value);
window.history.pushState({}, '', url);
}

const query = getParameterByName('q') || '';
let page = parseInt(getParameterByName('page')) || 0;
const hitsPerPage = 10;

const { version, lang } = extractVersionAndLangFromPath();

// Set default dropdowns
window.addEventListener('DOMContentLoaded', () => {
document.getElementById('version-filter').value = version;
document.getElementById('lang-filter').value = lang;

document.getElementById('search-query-header').innerHTML =
`<h3>Search results for "<strong>${query}</strong>"</h3>`;

function runSearch() {
const selectedVersion = document.getElementById('version-filter').value;
const selectedLang = document.getElementById('lang-filter').value;

index.search(query, {
hitsPerPage,
page,
facetFilters: [
[`lang:${selectedLang}`],
[`version:${selectedVersion}`]
]
}).then(({ hits, nbPages }) => {
const container = document.getElementById('search-results');
if (hits.length === 0) {
container.innerHTML = `<p>No results found for "<strong>${query}</strong>".</p>`;
document.getElementById('pagination-controls').style.display = 'none';
return;
}

container.innerHTML = hits.map(hit => {
const title = hit.hierarchy?.lvl1 || '(No Title)';
const subtitle = hit.hierarchy?.lvl2 ? ` > ${hit.hierarchy.lvl2}` : '';
const fullUrl = hit.anchor ? `${hit.url_without_anchor}#${hit.anchor}` : hit.url;

return `
<div class="search-hit">
<a href="${fullUrl}">
<h2>${title}${subtitle}</h2>
<p>${hit.content?.substring(0, 160) || ''}</p>
</a>
<div class="hit-meta">
<span><strong>URL:</strong> <a href="${fullUrl}">${fullUrl}</a></span>
</div>
</div>
`;
}).join('');
document.getElementById('pagination-controls').style.display = 'flex';
document.getElementById('page-info').textContent = `Page ${page + 1} of ${nbPages}`;
document.getElementById('prev-page').disabled = page <= 0;
document.getElementById('next-page').disabled = page + 1 >= nbPages;
}).catch(err => {
document.getElementById('search-results').innerHTML = `<p>Error fetching results: ${err.message}</p>`;
});
}

document.getElementById('prev-page').addEventListener('click', () => {
if (page > 0) {
page--;
updateURLParam('page', page);
runSearch();
}
});

document.getElementById('next-page').addEventListener('click', () => {
page++;
updateURLParam('page', page);
runSearch();
});

document.getElementById('version-filter').addEventListener('change', runSearch);
document.getElementById('lang-filter').addEventListener('change', runSearch);

runSearch();
});