feat: rework the scripts to make it closer to docsy

feat: implement custom partial in search--input.html

chore: add some comments on how things can be refactored later

feat: bring the search.html closer to docsy by only including the search-input partial and using the other things from baseof.html

fix: first line comments need to be in the {{/* */}} block, see https://github.com/gohugoio/hugo/issues/7243

Apply suggestions from code review

fix: apply review suggestions

fix: search bar should be removed in page find results and should be present in the sidebar

Co-authored-by: Tim Bannister <tim@scalefactory.com>
This commit is contained in:
Sayak Mukhopadhyay 2025-02-12 13:15:51 +05:30
parent 74343d4d3b
commit 8af973175c
No known key found for this signature in database
GPG Key ID: 89EEFF3FB23D3FFD
8 changed files with 241 additions and 246 deletions

139
assets/js/custom-search.js Normal file
View File

@ -0,0 +1,139 @@
document.querySelector('html').classList.add('search');
document.addEventListener('DOMContentLoaded', function() {
let searchTerm = new URLSearchParams(window.location.search).get('q');
let fetchingElem = document.getElementById('bing-results-container');
if (!searchTerm) {
if (fetchingElem) fetchingElem.style.display = 'none';
}
});
window.renderGoogleSearchResults = () => {
const cx = '013288817511911618469:elfqqbqldzg';
const gcse = document.createElement('script');
gcse.type = 'text/javascript';
gcse.async = true;
gcse.src = (document.location.protocol === 'https:' ? 'https:' : 'http:') + '//cse.google.com/cse.js?cx=' + cx;
const s = document.getElementsByTagName('script')[0];
s.parentNode.insertBefore(gcse, s);
}
window.renderPageFindSearchResults = () => {
let urlParams = new URLSearchParams(window.location.search);
let searchTerm = urlParams.get("q") || "";
let sidebarSearch = document.querySelector('#search-results-search');
if (sidebarSearch) {
sidebarSearch.remove();
}
document.getElementById('search').style.display = 'block';
let pagefind = new PagefindUI({ element: "#search", showImages: false });
if (searchTerm) {
pagefind.triggerSearch(searchTerm);
}
document.querySelector("#search input").addEventListener("input", function() {
const inputValue = this.value;
const queryStringVar = "q";
updateQueryString(queryStringVar, inputValue);
});
}
function updateQueryString(key, value) {
const baseUrl = window.location.href.split("?")[0];
const queryString = window.location.search.slice(1);
const urlParams = new URLSearchParams(queryString);
if (urlParams.has(key)) {
urlParams.set(key, value);
} else {
urlParams.append(key, value);
}
const newUrl = baseUrl + "?" + urlParams.toString();
// Update the browser history (optional)
history.replaceState(null, '', newUrl);
}
// China Verification.
const path = "path=/;";
let d = new Date()
d.setTime(d.getTime() + (7 * 24 * 60 * 60 * 1000))
let expires = "expires=" + d.toUTCString()
function getCookie(name) {
const value = "; " + document.cookie;
const parts = value.split("; " + name + "=");
if (parts.length === 2) return parts.pop().split(";").shift();
else return "";
}
async function checkBlockedSite(url) {
const controller = new AbortController();
const timeout = setTimeout(() => {
controller.abort();
}, 5000); // Timeout set to 5000ms (5 seconds)
try {
const response = await fetch(url, { method: 'HEAD', mode: 'no-cors', signal: controller.signal });
// If we reach this point, the site is accessible (since mode: 'no-cors' doesn't allow us to check response.ok)
clearTimeout(timeout);
return false;
} catch (error) {
// If an error occurs, it's likely the site is blocked
return true;
}
}
async function loadSearch() {
if (getCookie("can_google") === "") {
const isGoogleBlocked = await checkBlockedSite("https://www.google.com/favicon.ico");
if ( isGoogleBlocked ) {
// Google is blocked.
document.cookie = "can_google=false;" + path + expires
window.renderPageFindSearchResults()
} else {
// Google is not blocked.
document.cookie = "can_google=true;" + path + expires
window.renderGoogleSearchResults()
}
} else if (getCookie("can_google") === "false") {
window.renderPageFindSearchResults()
} else {
window.renderGoogleSearchResults()
}
}
(function ($) {
"use strict";
const Search = {
init: function () {
$(document).ready(function () {
// Fill the search input form with the current search keywords
const searchKeywords = new URLSearchParams(location.search).get('q');
if (searchKeywords !== null && searchKeywords !== '') {
const searchInput = document.querySelector('.td-search-input');
searchInput.focus();
searchInput.value = searchKeywords;
}
// Set a keydown event
$(document).on("keypress", ".td-search-input", function (e) {
if (e.keyCode !== 13) {
return;
}
const query = $(this).val();
document.location = $(this).data('search-page') + "?q=" + query;
return false;
});
});
},
};
Search.init();
})(jQuery);
window.onload = loadSearch;

View File

@ -1,45 +0,0 @@
/*
Copyright 2018 Google LLC
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
https://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
(function ($) {
"use strict";
var Search = {
init: function () {
$(document).ready(function () {
// Fill the search input form with the current search keywords
const searchKeywords = new URLSearchParams(location.search).get('q');
if (searchKeywords !== null && searchKeywords !== '') {
const searchInput = document.querySelector('.td-search-input');
searchInput.focus();
searchInput.value = searchKeywords;
}
// Set a keydown event
$(document).on("keypress", ".td-search-input", function (e) {
if (e.keyCode !== 13) {
return;
}
var query = $(this).val();
var searchPage = $(this).data('search-page') + "?q=" + query;
document.location = searchPage;
return false;
});
});
},
};
Search.init();
})(jQuery);

View File

@ -151,7 +151,7 @@ githubWebsiteRaw = "raw.githubusercontent.com/kubernetes/website"
github_repo = "https://github.com/kubernetes/website"
# Searching
k8s_search = true
customSearch = true
# The following search parameters are specific to Docsy's implementation. Kubernetes implementes its own search-related partials and scripts.

View File

@ -1,54 +1,42 @@
<!doctype html>
<html lang="{{ .Site.Language.Lang }}" class="{{.Params.class}} no-js">
<head>
{{ partial "head.html" . }}
</head>
<body class="td-search {{- if ne (lower .Params.cid) "" -}}{{- printf " cid-%s" (lower .Params.cid) -}}{{- end -}}">
<header>
{{ partial "navbar.html" . }}
{{ block "announcement" . }}
{{ partial "announcement.html" . }}
{{ end }}
{{ block "hero" . }}
<section class="header-hero filler">
</section>
{{ block "hero-more" . }}{{ end }}
{{ end }}
</header>
<div class="td-outer">
<main role="main" class="td-main">
<section class="row td-search-result">
<div class="col-12 col-md-4 offset-md-2">
<form class="td-sidebar__search d-flex align-items-center">
{{ partial "search-input.html" . }}
</form>
</div>
<div class="col-12 col-md-8 offset-md-2">
{{ if .Site.Params.gcs_engine_id }}
<script>
(function() {
var cx = '{{ . }}';
var gcse = document.createElement('script');
gcse.type = 'text/javascript';
gcse.async = true;
gcse.src = 'https://cse.google.com/cse.js?cx=' + cx;
var s = document.getElementsByTagName('script')[0];
s.parentNode.insertBefore(gcse, s);
})();
</script>
<gcse:searchresults-only></gcse:searchresults-only>
{{ else if .Site.Params.k8s_search }}
<script src="{{ "js/search.js" | relURL }}"></script>
<script src="/pagefind/pagefind-ui.js"></script>
<gcse:searchresults-only linktarget="_parent">
<div id="search" style="display:none"></div>
</gcse:searchresults-only>
{{ end }}
</div>
</section>
</main>
</div>
{{ partial "footer.html" . }}
{{ partialCached "scripts.html" . }}
</body>
</html>
{{/*
Copied from Docsy with the addition of the search-input and the customSearch block.
Revisit this if / when either of https://github.com/google/docsy/issues/2194 and https://github.com/google/docsy/pull/1512 are closed
*/}}
{{ define "main" }}
{{/*
Do not use the `search-results-search` id elsewhere as it is used
delete this element for pagefind/China users
*/}}
{{/* From shortcodes/site-searchbar.html which is used in the home page */}}
<div id="search-results-search" class="col-sm-6 col-md-6 col-lg-6 mx-auto py-3">
{{partial "search-input" .}}
</div>
<section class="row td-search-result">
<div class="col-12 col-md-8 offset-md-2">
<h2 class="ml-4">{{ .Title }}</h2>
{{ with .Site.Params.gcs_engine_id }}
<script>
(function() {
var cx = '{{ . }}';
var gcse = document.createElement('script');
gcse.type = 'text/javascript';
gcse.async = true;
gcse.src = 'https://cse.google.com/cse.js?cx=' + cx;
var s = document.getElementsByTagName('script')[0];
s.parentNode.insertBefore(gcse, s);
})();
</script>
<gcse:searchresults-only></gcse:searchresults-only>
{{ end }}
{{ if .Site.Params.customSearch }}
<script src="/pagefind/pagefind-ui.js"></script>
<gcse:searchresults-only linktarget="_parent">
<div id="search" style="display:none"></div>
</gcse:searchresults-only>
{{ end }}
</div>
</section>
{{ end }}

View File

@ -33,6 +33,9 @@
{{ $jsSearch := resources.Get "js/search.js" | resources.ExecuteAsTemplate "js/search.js" .Site.Home }}
{{ if .Site.Params.offlineSearch }}
{{ $jsSearch = resources.Get "js/offline-search.js" }}
{{/* Revisit this if / when either of https://github.com/google/docsy/issues/2194 and https://github.com/google/docsy/pull/1512 are closed */}}
{{ else if .Site.Params.customSearch }}
{{ $jsSearch = resources.Get "js/custom-search.js" }}
{{ end }}
{{ $js := (slice $jsBs $jsBase $jsAnchor $jsSearch) | resources.Concat "js/main.js" -}}
{{ if hugo.IsProduction -}}

View File

@ -0,0 +1,20 @@
{{/* Revisit this if / when either of https://github.com/google/docsy/issues/2194 and https://github.com/google/docsy/pull/1512 are closed */}}
{{ $lang := .Site.Language.Lang }}
{{ $searchFile := printf "content/%s/search.md" $lang }}
<div class="search-bar">
<i class="search-icon fa-solid fa-search"></i>
<input
type="search"
name="q"
{{ if fileExists $searchFile }}
data-search-page="{{ "search/" | relLangURL }}"
{{ else }}
data-search-page="{{ "search/" | relURL }}"
{{ end }}
class="search-input td-search-input"
placeholder="{{ T "ui_search_placeholder" }}"
aria-label="{{ T "ui_search_placeholder" }}"
autocomplete="off"
>
</div>

View File

@ -1,45 +1,43 @@
{{ if or .Site.Params.gcs_engine_id .Site.Params.algolia_docsearch }}
<div class="search-bar">
<i class="search-icon fa-solid fa-search"></i>
<input
type="search"
class="search-input td-search-input"
placeholder="{{ T "ui_search_placeholder" }}"
aria-label="{{ T "ui_search_placeholder" }}"
autocomplete="off"
>
{{/*
Copied from Docsy with the addition of the customSearch block.
This can be deleted once https://github.com/google/docsy/issues/2194 and https://github.com/google/docsy/pull/1512 are closed
and the site has been updated to incorporate the upstream change.
*/}}
{{ if .Site.Params.gcs_engine_id -}}
<div class="td-search">
<div class="td-search__icon"></div>
<input type="search" class="td-search__input form-control td-search-input" placeholder="{{ T "ui_search" }}" aria-label="{{ T "ui_search" }}" autocomplete="off">
</div>
{{ else if .Site.Params.offlineSearch }}
<div class="search-bar" id="search-nav-container">
<i class="search-icon fa-solid fa-search"></i>
<input
type="search"
class="search-input td-search-input"
id="search-input"
placeholder="{{ T "ui_search_placeholder" }}"
aria-label="{{ T "ui_search_placeholder" }}"
autocomplete="off"
>
</div>
{{ else if .Site.Params.k8s_search }}
{{ else if .Site.Params.algolia_docsearch -}}
<div id="docsearch"></div>
{{ else if .Site.Params.offlineSearch -}}
{{ $offlineSearchIndex := resources.Get "json/offline-search-index.json" | resources.ExecuteAsTemplate "offline-search-index.json" . -}}
{{ if hugo.IsProduction -}}
{{/* Use `md5` as finger print hash function to shorten file name to avoid `file name too long` error. */ -}}
{{ $offlineSearchIndex = $offlineSearchIndex | fingerprint "md5" -}}
{{ end -}}
{{ $offlineSearchLink := $offlineSearchIndex.RelPermalink -}}
{{ $lang := .Site.Language.Lang }}
{{ $searchFile := printf "content/%s/search.md" $lang }}
<div class="search-bar">
<i class="search-icon fa-solid fa-search"></i>
<div class="td-search td-search--offline">
<div class="td-search__icon"></div>
<input
type="search"
name="q"
{{ if fileExists $searchFile }}
data-search-page="{{ "search/" | relLangURL }}"
{{ else }}
data-search-page="{{ "search/" | relURL }}"
{{ end }}
class="search-input td-search-input"
placeholder="{{ T "ui_search_placeholder" }}"
aria-label="{{ T "ui_search_placeholder" }}"
class="td-search__input form-control"
placeholder="{{ T "ui_search" }}"
aria-label="{{ T "ui_search" }}"
autocomplete="off"
{{/*
The data attribute name of the json file URL must end with `src` since
Hugo's absurlreplacer requires `src`, `href`, `action` or `srcset` suffix for the attribute name.
If the absurlreplacer is not applied, the URL will start with `/`.
It causes the json file loading error when when relativeURLs is enabled.
https://github.com/google/docsy/issues/181
*/}}
data-offline-search-index-json-src="{{ $offlineSearchLink }}"
data-offline-search-base-href="/"
data-offline-search-max-results="{{ .Site.Params.offlineSearchMaxResults | default 10 }}"
>
</div>
{{ end }}
{{ else if .Site.Params.customSearch -}}
{{ partialCached "search-input-custom" . }}
{{ end -}}

View File

@ -1,108 +0,0 @@
document.querySelector('html').classList.add('search');
document.addEventListener('DOMContentLoaded', function() {
let searchTerm = new URLSearchParams(window.location.search).get('q');
let fetchingElem = document.getElementById('bing-results-container');
if (!searchTerm) {
if (fetchingElem) fetchingElem.style.display = 'none';
}
});
window.renderGoogleSearchResults = () => {
var cx = '013288817511911618469:elfqqbqldzg';
var gcse = document.createElement('script');
gcse.type = 'text/javascript';
gcse.async = true;
gcse.src = (document.location.protocol == 'https:' ? 'https:' : 'http:') + '//cse.google.com/cse.js?cx=' + cx;
var s = document.getElementsByTagName('script')[0];
s.parentNode.insertBefore(gcse, s);
}
window.renderPageFindSearchResults = () => {
let urlParams = new URLSearchParams(window.location.search);
let searchTerm = urlParams.get("q") || "";
let sidebarSearch = document.querySelector('.td-sidebar__search');
if (sidebarSearch) {
sidebarSearch.remove();
}
document.getElementById('search').style.display = 'block';
pagefind = new PagefindUI({ element: "#search", showImages: false });
if (searchTerm) {
pagefind.triggerSearch(searchTerm);
}
document.querySelector("#search input").addEventListener("input", function() {
var inputValue = this.value;
var queryStringVar = "q";
updateQueryString(queryStringVar, inputValue);
});
}
function updateQueryString(key, value) {
var baseUrl = window.location.href.split("?")[0];
var queryString = window.location.search.slice(1);
var urlParams = new URLSearchParams(queryString);
if (urlParams.has(key)) {
urlParams.set(key, value);
} else {
urlParams.append(key, value);
}
var newUrl = baseUrl + "?" + urlParams.toString();
// Update the browser history (optional)
history.replaceState(null, '', newUrl);
}
// China Verification.
var path = "path=/;"
d = new Date()
d.setTime(d.getTime() + (7 * 24 * 60 * 60 * 1000))
expires = "expires=" + d.toUTCString()
function getCookie(name) {
var value = "; " + document.cookie;
var parts = value.split("; " + name + "=");
if (parts.length == 2) return parts.pop().split(";").shift();
else return "";
}
async function checkBlockedSite(url) {
const controller = new AbortController();
const timeout = setTimeout(() => {
controller.abort();
}, 5000); // Timeout set to 5000ms (5 seconds)
try {
const response = await fetch(url, { method: 'HEAD', mode: 'no-cors', signal: controller.signal });
// If we reach this point, the site is accessible (since mode: 'no-cors' doesn't allow us to check response.ok)
clearTimeout(timeout);
return false;
} catch (error) {
// If an error occurs, it's likely the site is blocked
return true;
}
}
async function loadSearch() {
if (getCookie("can_google") === "") {
const isGoogleBlocked = await checkBlockedSite("https://www.google.com/favicon.ico");
if ( isGoogleBlocked ) {
// Google is blocked.
document.cookie = "can_google=false;" + path + expires
window.renderPageFindSearchResults()
} else {
// Google is not blocked.
document.cookie = "can_google=true;" + path + expires
window.renderGoogleSearchResults()
}
} else if (getCookie("can_google") == "false") {
window.renderPageFindSearchResults()
} else {
window.renderGoogleSearchResults()
}
}
window.onload = loadSearch;