forked from drowl87/hextra_mirror
feat: basic flexsearch implementation
This commit is contained in:
parent
b90c2e7737
commit
16a656947b
86
assets/js/flexsearch.js
Normal file
86
assets/js/flexsearch.js
Normal file
@ -0,0 +1,86 @@
|
||||
// {{ $searchDataFile := printf "%s.search-data.json" .Language.Lang }}
|
||||
// {{ $searchData := resources.Get "json/search-data.json" | resources.ExecuteAsTemplate $searchDataFile . | resources.Minify | resources.Fingerprint }}
|
||||
|
||||
(function () {
|
||||
const searchDataURL = '{{ $searchData.Permalink }}';
|
||||
console.log('searchDataURL', searchDataURL);
|
||||
|
||||
const indexConfig = {
|
||||
tokenize: "full",
|
||||
cache: 100,
|
||||
document: {
|
||||
id: 'id',
|
||||
store: ['title', 'href', 'section'],
|
||||
index: ["title", "content"]
|
||||
},
|
||||
context: {
|
||||
resolution: 9,
|
||||
depth: 2,
|
||||
bidirectional: true
|
||||
}
|
||||
}
|
||||
window.flexSearchIndex = new FlexSearch.Document(indexConfig);
|
||||
|
||||
const input = document.getElementById('search-input');
|
||||
const results = document.getElementById('search-results');
|
||||
|
||||
input.addEventListener('focus', init);
|
||||
input.addEventListener('keyup', search);
|
||||
|
||||
|
||||
/**
|
||||
* Initializes the search functionality by adding the necessary event listeners and fetching the search data.
|
||||
*/
|
||||
function init() {
|
||||
input.removeEventListener('focus', init); // init once
|
||||
|
||||
fetch(searchDataURL).then(resp => resp.json()).then(pages => {
|
||||
pages.forEach(page => {
|
||||
window.flexSearchIndex.add(page);
|
||||
});
|
||||
}).then(search);
|
||||
}
|
||||
|
||||
function search() {
|
||||
console.log('search', input.value);
|
||||
while (results.firstChild) {
|
||||
results.removeChild(results.firstChild);
|
||||
}
|
||||
|
||||
if (!input.value) {
|
||||
return;
|
||||
}
|
||||
|
||||
const searchHits = window.flexSearchIndex.search(input.value, { limit: 5, enrich: true });
|
||||
showResults(searchHits);
|
||||
}
|
||||
|
||||
function showResults(hits) {
|
||||
console.log('showResults', hits);
|
||||
const flatResults = new Map(); // keyed by href to dedupe hits
|
||||
for (const result of hits.flatMap(r => r.result)) {
|
||||
if (flatResults.has(result.doc.href)) continue;
|
||||
flatResults.set(result.doc.href, result.doc);
|
||||
}
|
||||
console.log('flatResults', flatResults);
|
||||
const create = (str) => {
|
||||
const div = document.createElement('div');
|
||||
div.innerHTML = str.trim();
|
||||
return div.firstChild;
|
||||
}
|
||||
const fragment = document.createDocumentFragment();
|
||||
|
||||
console.log(fragment)
|
||||
|
||||
for (const result of flatResults.values()) {
|
||||
const li = create(`
|
||||
<li class="mx-2.5 break-words rounded-md contrast-more:border text-gray-800 contrast-more:border-transparent dark:text-gray-300">
|
||||
<a class="block scroll-m-12 px-2.5 py-2" data-index="0" href="${result.href}">
|
||||
<div class="text-base font-semibold leading-5">${result.title}</div>
|
||||
</a>
|
||||
</li>`);
|
||||
fragment.appendChild(li);
|
||||
}
|
||||
results.appendChild(fragment);
|
||||
}
|
||||
})();
|
19
assets/json/search-data.json
Normal file
19
assets/json/search-data.json
Normal file
@ -0,0 +1,19 @@
|
||||
{{- $pages := where .Site.Pages "Kind" "in" (slice "page" "section") -}}
|
||||
{{- $pages = where $pages "Params.excludeSearch" "!=" true -}}
|
||||
{{- $pages = where $pages "Content" "!=" "" -}}
|
||||
|
||||
[
|
||||
{{ range $index, $page := $pages }}
|
||||
{{ $pageTitle := $page.LinkTitle | default $page.File.BaseFileName }}
|
||||
{{ $pageContent := $page.Plain }}
|
||||
{{ $pageSection := $page.Parent.LinkTitle }}
|
||||
|
||||
{{ if gt $index 0}},{{end}} {
|
||||
"id": {{ $index }},
|
||||
"href": "{{ $page.Permalink }}",
|
||||
"title": {{ $pageTitle | jsonify }},
|
||||
"section": {{ $pageSection | jsonify }},
|
||||
"content": {{ $page.Plain | jsonify }}
|
||||
}
|
||||
{{- end -}}
|
||||
]
|
@ -34,5 +34,5 @@ warning: <svg xmlns="http://www.w3.org/2000/svg" fill="none" stroke="currentColo
|
||||
one: <svg xmlns="http://www.w3.org/2000/svg" fill="currentColor" viewBox="-1 0 19 19"><path d="M16.417 9.6A7.917 7.917 0 1 1 8.5 1.683 7.917 7.917 0 0 1 16.417 9.6zM9.666 6.508H8.248L6.09 8.09l.806 1.103 1.222-.945v4.816h1.547z"></path></svg>
|
||||
cards: <svg xmlns="http://www.w3.org/2000/svg" fill="none" stroke="currentColor" stroke-width="1.5" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" d="M6 6.878V6a2.25 2.25 0 0 1 2.25-2.25h7.5A2.25 2.25 0 0 1 18 6v.878m-12 0c.235-.083.487-.128.75-.128h10.5c.263 0 .515.045.75.128m-12 0A2.25 2.25 0 0 0 4.5 9v.878m13.5-3A2.25 2.25 0 0 1 19.5 9v.878m0 0a2.246 2.246 0 0 0-.75-.128H5.25c-.263 0-.515.045-.75.128m15 0A2.25 2.25 0 0 1 21 12v6a2.25 2.25 0 0 1-2.25 2.25H5.25A2.25 2.25 0 0 1 3 18v-6c0-.98.626-1.813 1.5-2.122"></path></svg>
|
||||
|
||||
copy: <svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg" stroke="currentColor" class="nextra-copy-icon nx-pointer-events-none nx-h-4 nx-w-4"><rect x="9" y="9" width="13" height="13" rx="2" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"></rect><path d="M5 15H4C2.89543 15 2 14.1046 2 13V4C2 2.89543 2.89543 2 4 2H13C14.1046 2 15 2.89543 15 4V5" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"></path></svg>
|
||||
copy: <svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg" stroke="currentColor"><rect x="9" y="9" width="13" height="13" rx="2" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"></rect><path d="M5 15H4C2.89543 15 2 14.1046 2 13V4C2 2.89543 2.89543 2 4 2H13C14.1046 2 15 2.89543 15 4V5" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"></path></svg>
|
||||
check: <svg xmlns="http://www.w3.org/2000/svg" width="15" height="15" viewBox="0 0 15 15"><path fill="currentColor" fill-rule="evenodd" d="M11.467 3.727c.289.189.37.576.181.865l-4.25 6.5a.625.625 0 0 1-.944.12l-2.75-2.5a.625.625 0 0 1 .841-.925l2.208 2.007l3.849-5.886a.625.625 0 0 1 .865-.181Z" clip-rule="evenodd"/></svg>
|
||||
|
@ -63,6 +63,10 @@ defaultContentLanguage = 'en'
|
||||
name = 'About'
|
||||
pageRef = '/about'
|
||||
weight = 30
|
||||
[[menu.main]]
|
||||
name = 'Search'
|
||||
weight = 35
|
||||
params = { placeholder = 'Search documentation...' }
|
||||
[[menu.main]]
|
||||
name = 'GitHub'
|
||||
url = 'https://github.com'
|
||||
|
@ -3,17 +3,19 @@
|
||||
|
||||
<nav class="mx-auto flex items-center justify-end gap-2 h-16 px-6 max-w-[90rem]">
|
||||
<a class="flex items-center hover:opacity-75 ltr:mr-auto rtl:ml-auto" href="{{ .Site.BaseURL }}">
|
||||
{{ partial "utils/icon.html" (dict "context" . "name" "hugo" "attributes" "height=20") }}
|
||||
{{ partial "utils/icon.html" (dict "name" "hugo" "attributes" "height=20") }}
|
||||
<span class="mx-2 font-extrabold hidden md:inline select-none" title="{{ .Site.Title }}">
|
||||
{{ .Site.Title }}
|
||||
</span>
|
||||
</a>
|
||||
|
||||
{{ $currentPage := . }}
|
||||
{{ range .Site.Menus.main }}
|
||||
{{ if .Params.icon }}
|
||||
{{- $currentPage := . -}}
|
||||
{{- range .Site.Menus.main -}}
|
||||
{{- if eq .Name "Search" -}}
|
||||
{{ partial "search.html" (dict "params" .Params) }}
|
||||
{{- else if .Params.icon -}}
|
||||
<a class="p-2 text-current" target="_blank" rel="noreferer" href="{{ .URL }}">
|
||||
{{ partial "utils/icon.html" (dict "context" $currentPage "name" .Params.icon "attributes" "height=24") }}
|
||||
{{ partial "utils/icon.html" (dict "name" .Params.icon "attributes" "height=24") }}
|
||||
<span class="sr-only">{{ .Name }}</span>
|
||||
</a>
|
||||
{{ else }}
|
||||
|
@ -18,6 +18,11 @@
|
||||
<script src="{{ $tabsJS.RelPermalink }}"></script>
|
||||
{{ end }}
|
||||
|
||||
{{- $searchJSFile := printf "%s.search.js" .Language.Lang }}
|
||||
{{- $searchJS := resources.Get "js/flexsearch.js" | resources.ExecuteAsTemplate $searchJSFile . | resources.Minify | resources.Fingerprint -}}
|
||||
<script src="https://cdn.jsdelivr.net/npm/flexsearch@0.7.31/dist/flexsearch.bundle.min.js"></script>
|
||||
<script defer src="{{ $searchJS.RelPermalink }}"></script>
|
||||
|
||||
{{ if .Page.Params.math }}
|
||||
<!-- TODO: embed katex in the theme -->
|
||||
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/katex@0.16.8/dist/katex.min.css" integrity="sha384-GvrOXuhMATgEsSwCs4smul74iXGOixntILdUW9XmUC6+HX0sLNAK3q71HotJqlAn" crossorigin="anonymous" />
|
||||
|
17
layouts/partials/search.html
Normal file
17
layouts/partials/search.html
Normal file
@ -0,0 +1,17 @@
|
||||
{{- $placeholder := .params.placeholder | default "Search..." -}}
|
||||
|
||||
|
||||
<div id="search-wrapper" class="search-wrapper relative md:w-64 hidden md:inline-block mx-min-w-[200px]">
|
||||
<div class="relative flex items-center text-gray-900 contrast-more:text-gray-800 dark:text-gray-300 contrast-more:dark:text-gray-300">
|
||||
<input id="search-input" placeholder="{{ $placeholder }}" class="block w-full appearance-none rounded-lg px-3 py-2 transition-colors text-base leading-tight md:text-sm bg-black/[.05] dark:bg-gray-50/10 focus:bg-white dark:focus:bg-dark placeholder:text-gray-500 dark:placeholder:text-gray-400 contrast-more:border contrast-more:border-current" type="search" value="" spellcheck="false" />
|
||||
<kbd class="absolute my-1.5 select-none ltr:right-1.5 rtl:left-1.5 h-5 rounded bg-white px-1.5 font-mono text-[10px] font-medium text-gray-500 border dark:border-gray-100/20 dark:bg-dark/50 contrast-more:border-current contrast-more:text-current contrast-more:dark:border-current items-center gap-1 transition-opacity pointer-events-none hidden sm:flex"> <span class="text-xs">⌘</span>K </kbd>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<ul
|
||||
id="search-results"
|
||||
class="border border-gray-200 bg-white text-gray-100 dark:border-neutral-800 dark:bg-neutral-900 absolute top-full z-20 mt-2 overflow-auto overscroll-contain rounded-xl py-2.5 shadow-xl max-h-[min(calc(50vh-11rem-env(safe-area-inset-bottom)),400px)] md:max-h-[min(calc(100vh-5rem-env(safe-area-inset-bottom)),400px)] inset-x-0 ltr:md:left-auto rtl:md:right-auto contrast-more:border contrast-more:border-gray-900 contrast-more:dark:border-gray-50 w-screen min-h-[100px] max-w-[min(calc(100vw-2rem),calc(100%+20rem))]"
|
||||
style="transition: max-height 0.2s ease 0s;"
|
||||
></ul>
|
||||
</div>
|
||||
</div>
|
Loading…
x
Reference in New Issue
Block a user