Skip to content
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
13 changes: 13 additions & 0 deletions layouts/_default/search.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
{{- $index := slice -}}
{{- range .Site.RegularPages -}}
{{- $page := . -}}
{{- $entry := dict
"title" .Title
"content" .Plain
"permalink" .Permalink
"category" (or .Section "General")
"summary" .Summary
-}}
{{- $index = $index | append $entry -}}
{{- end -}}
{{- $index | jsonify -}}
19 changes: 19 additions & 0 deletions layouts/partials/navbar.html
Original file line number Diff line number Diff line change
Expand Up @@ -32,9 +32,28 @@
{{ end }}
</ul>
</div>
<div class="navbar-search-container">
{{ partial "search.html" . }}
</div>
<div class="d-none d-lg-block">
{{ with .Site.Params.links }} {{ with index . "developer"}} {{ template
"footer-links-block" . }} {{ end }} {{ end }}
</div>
<!-- <div class="navbar-nav d-none d-lg-block">{{ partial "search-input.html" . }}</div> -->
</nav>

<style>
.navbar-search-container {
flex: 1;
max-width: 350px;
margin: 0 20px;
margin-left: 300px;
}

@media (max-width: 768px) {
.navbar-search-container {
margin: 10px 0;
max-width: 100%;
}
}
</style>
257 changes: 257 additions & 0 deletions layouts/partials/search.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,257 @@
<!-- Search Component -->
<div class="search-container relative">
<div class="search-wrapper">
<input type="text" id="search-input" class="search-input" placeholder="Search..." aria-label="Search">
<button type="button" id="search-button" class="search-button">
<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<circle cx="11" cy="11" r="8"></circle>
<line x1="21" y1="21" x2="16.65" y2="16.65"></line>
</svg>
</button>
</div>
<div id="search-results" class="search-results hidden">
<!-- Results will be dynamically inserted here -->
</div>
</div>

<style>
.search-container {
position: relative;
width: 100%;
max-width: 600px;
margin: 0 auto;
}

.search-wrapper {
display: flex;
align-items: center;
background: #1e2124;
border-radius: 4px;
padding: 4px 12px;
border: 1px solid #444c56;
}

.search-input {
flex: 1;
border: none;
background: transparent;
padding: 8px;
font-size: 16px;
outline: none;
color: #adbac7;
}

.search-input::placeholder {
color: #768390;
}

.search-button {
background: none;
border: none;
padding: 4px;
cursor: pointer;
color: #768390;
}

.search-button:hover {
color: #adbac7;
}

.search-results {
position: absolute;
top: 100%;
left: 0;
right: 0;
background: #1e2124;
border-radius: 4px;
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.2);
margin-top: 8px;
max-height: 400px;
overflow-y: auto;
z-index: 1000;
border: 1px solid #444c56;
}

.search-results.hidden {
display: none;
}

.search-result-item {
padding: 12px 16px;
border-bottom: 1px solid #444c56;
cursor: pointer;
display: block;
text-decoration: none;
color: inherit;
transition: background-color 0.2s;
}

.search-result-item:last-child {
border-bottom: none;
}

.search-result-item:hover {
background: #2d333b;
}

.search-result-title {
font-weight: 500;
color: #adbac7;
margin-bottom: 4px;
}

.search-result-category {
font-size: 13px;
color: #768390;
margin-bottom: 4px;
}

.search-result-summary {
font-size: 13px;
color: #768390;
display: -webkit-box;
-webkit-line-clamp: 2;
-webkit-box-orient: vertical;
overflow: hidden;
}

mark {
background-color: rgba(187, 128, 9, 0.15);
color: #e3b341;
padding: 0 2px;
border-radius: 2px;
}
</style>

<script>
document.addEventListener('DOMContentLoaded', function() {
const searchInput = document.getElementById('search-input');
const searchResults = document.getElementById('search-results');
const searchButton = document.getElementById('search-button');

// Generate search index from Hugo's page data
const searchData = [
{{ range .Site.Pages }}
{{ if and .Title (not .Params.exclude_search) }}
{
title: {{ .Title | jsonify }},
category: {{ with .Section }}{{ . | title | jsonify }}{{ else }}"Documentation"{{ end }},
summary: {{ with .Description }}{{ . | jsonify }}{{ else }}{{ .Summary | plainify | jsonify }}{{ end }},
url: "{{ .RelPermalink }}"
},
{{ end }}
{{ end }}
];

function performSearch(query) {
query = query.toLowerCase();
searchResults.classList.remove('hidden');

const results = searchData.filter(item => {
const titleMatch = item.title.toLowerCase().includes(query);
const summaryMatch = item.summary.toLowerCase().includes(query);
const categoryMatch = item.category.toLowerCase().includes(query);

// Calculate relevance score
let score = 0;
if (titleMatch) score += 3;
if (categoryMatch) score += 2;
if (summaryMatch) score += 1;

item.score = score;

return titleMatch || summaryMatch || categoryMatch;
})
.sort((a, b) => b.score - a.score)
.slice(0, 10);

displayResults(results);
}

function displayResults(results) {
if (!results || results.length === 0) {
searchResults.innerHTML = '<div class="search-result-item">No results found</div>';
return;
}

const resultsHtml = results.map(result => {
let summary = result.summary;
const query = searchInput.value.toLowerCase();

if (query && summary) {
const regex = new RegExp(`(${query})`, 'gi');
summary = summary.replace(regex, '<mark>$1</mark>');
}

return `
<div class="search-result-item" data-url="${result.url}">
<div class="search-result-title">${result.title}</div>
<div class="search-result-category">${result.category}</div>
<div class="search-result-summary">${summary}</div>
</div>
`;
}).join('');

searchResults.innerHTML = resultsHtml;

// Add click event listeners to all search result items
document.querySelectorAll('.search-result-item').forEach(item => {
item.addEventListener('click', function() {
const url = this.getAttribute('data-url');
if (url) {
window.location.href = url;
}
});
});
}

// Handle search input with debounce
let debounceTimer;
searchInput.addEventListener('input', function(e) {
clearTimeout(debounceTimer);
const query = e.target.value.trim();

if (query.length === 0) {
searchResults.classList.add('hidden');
return;
}

debounceTimer = setTimeout(() => {
performSearch(query);
}, 100);
});

// Handle search button click
searchButton.addEventListener('click', function() {
const query = searchInput.value.trim();
if (query.length > 0) {
performSearch(query);
}
});

// Handle Enter key
searchInput.addEventListener('keypress', function(e) {
if (e.key === 'Enter') {
const query = e.target.value.trim();
if (query.length > 0) {
performSearch(query);
}
}
});

// Close search results when clicking outside
document.addEventListener('click', function(e) {
if (!searchResults.contains(e.target) && e.target !== searchInput && e.target !== searchButton) {
searchResults.classList.add('hidden');
}
});

// Focus search input when pressing '/'
document.addEventListener('keydown', function(e) {
if (e.key === '/' && document.activeElement !== searchInput) {
e.preventDefault();
searchInput.focus();
}
});
});
</script>
Loading
Loading