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" github_repo = "https://github.com/kubernetes/website"
# Searching # 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. # The following search parameters are specific to Docsy's implementation. Kubernetes implementes its own search-related partials and scripts.

View File

@ -1,30 +1,22 @@
<!doctype html> {{/*
<html lang="{{ .Site.Language.Lang }}" class="{{.Params.class}} no-js"> Copied from Docsy with the addition of the search-input and the customSearch block.
<head> Revisit this if / when either of https://github.com/google/docsy/issues/2194 and https://github.com/google/docsy/pull/1512 are closed
{{ partial "head.html" . }} */}}
</head> {{ define "main" }}
<body class="td-search {{- if ne (lower .Params.cid) "" -}}{{- printf " cid-%s" (lower .Params.cid) -}}{{- end -}}">
<header> {{/*
{{ partial "navbar.html" . }} Do not use the `search-results-search` id elsewhere as it is used
{{ block "announcement" . }} delete this element for pagefind/China users
{{ partial "announcement.html" . }} */}}
{{ end }}
{{ block "hero" . }} {{/* From shortcodes/site-searchbar.html which is used in the home page */}}
<section class="header-hero filler"> <div id="search-results-search" class="col-sm-6 col-md-6 col-lg-6 mx-auto py-3">
</section> {{partial "search-input" .}}
{{ 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>
<section class="row td-search-result">
<div class="col-12 col-md-8 offset-md-2"> <div class="col-12 col-md-8 offset-md-2">
{{ if .Site.Params.gcs_engine_id }} <h2 class="ml-4">{{ .Title }}</h2>
{{ with .Site.Params.gcs_engine_id }}
<script> <script>
(function() { (function() {
var cx = '{{ . }}'; var cx = '{{ . }}';
@ -37,8 +29,8 @@
})(); })();
</script> </script>
<gcse:searchresults-only></gcse:searchresults-only> <gcse:searchresults-only></gcse:searchresults-only>
{{ else if .Site.Params.k8s_search }} {{ end }}
<script src="{{ "js/search.js" | relURL }}"></script> {{ if .Site.Params.customSearch }}
<script src="/pagefind/pagefind-ui.js"></script> <script src="/pagefind/pagefind-ui.js"></script>
<gcse:searchresults-only linktarget="_parent"> <gcse:searchresults-only linktarget="_parent">
<div id="search" style="display:none"></div> <div id="search" style="display:none"></div>
@ -46,9 +38,5 @@
{{ end }} {{ end }}
</div> </div>
</section> </section>
</main>
</div> {{ end }}
{{ partial "footer.html" . }}
{{ partialCached "scripts.html" . }}
</body>
</html>

View File

@ -33,6 +33,9 @@
{{ $jsSearch := resources.Get "js/search.js" | resources.ExecuteAsTemplate "js/search.js" .Site.Home }} {{ $jsSearch := resources.Get "js/search.js" | resources.ExecuteAsTemplate "js/search.js" .Site.Home }}
{{ if .Site.Params.offlineSearch }} {{ if .Site.Params.offlineSearch }}
{{ $jsSearch = resources.Get "js/offline-search.js" }} {{ $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 }} {{ end }}
{{ $js := (slice $jsBs $jsBase $jsAnchor $jsSearch) | resources.Concat "js/main.js" -}} {{ $js := (slice $jsBs $jsBase $jsAnchor $jsSearch) | resources.Concat "js/main.js" -}}
{{ if hugo.IsProduction -}} {{ 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"> Copied from Docsy with the addition of the customSearch block.
<i class="search-icon fa-solid fa-search"></i> This can be deleted once https://github.com/google/docsy/issues/2194 and https://github.com/google/docsy/pull/1512 are closed
<input and the site has been updated to incorporate the upstream change.
type="search" */}}
class="search-input td-search-input" {{ if .Site.Params.gcs_engine_id -}}
placeholder="{{ T "ui_search_placeholder" }}" <div class="td-search">
aria-label="{{ T "ui_search_placeholder" }}" <div class="td-search__icon"></div>
autocomplete="off" <input type="search" class="td-search__input form-control td-search-input" placeholder="{{ T "ui_search" }}" aria-label="{{ T "ui_search" }}" autocomplete="off">
>
</div> </div>
{{ else if .Site.Params.offlineSearch }} {{ else if .Site.Params.algolia_docsearch -}}
<div class="search-bar" id="search-nav-container"> <div id="docsearch"></div>
<i class="search-icon fa-solid fa-search"></i> {{ else if .Site.Params.offlineSearch -}}
<input {{ $offlineSearchIndex := resources.Get "json/offline-search-index.json" | resources.ExecuteAsTemplate "offline-search-index.json" . -}}
type="search" {{ if hugo.IsProduction -}}
class="search-input td-search-input" {{/* Use `md5` as finger print hash function to shorten file name to avoid `file name too long` error. */ -}}
id="search-input" {{ $offlineSearchIndex = $offlineSearchIndex | fingerprint "md5" -}}
placeholder="{{ T "ui_search_placeholder" }}" {{ end -}}
aria-label="{{ T "ui_search_placeholder" }}" {{ $offlineSearchLink := $offlineSearchIndex.RelPermalink -}}
autocomplete="off"
>
</div>
{{ else if .Site.Params.k8s_search }}
{{ $lang := .Site.Language.Lang }} <div class="td-search td-search--offline">
{{ $searchFile := printf "content/%s/search.md" $lang }} <div class="td-search__icon"></div>
<div class="search-bar">
<i class="search-icon fa-solid fa-search"></i>
<input <input
type="search" type="search"
name="q" class="td-search__input form-control"
{{ if fileExists $searchFile }} placeholder="{{ T "ui_search" }}"
data-search-page="{{ "search/" | relLangURL }}" aria-label="{{ T "ui_search" }}"
{{ 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" 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> </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;