${Utils.esc(data.content)
.split("\n")
.map((p) => `
${p}
`)
.join("")}
${result.source.toUpperCase()}
${Utils.esc(result.title)}
${Utils.esc(result.url)}
${Utils.highlight(Utils.esc(result.snippet), this.state.query)}
${
result.meta
? `
${result.meta.stars !== undefined ? `★ ${Utils.fmt(result.meta.stars)}` : ""}
${result.meta.forks !== undefined ? `⑂ ${Utils.fmt(result.meta.forks)}` : ""}
${result.meta.score !== undefined ? `↑ ${Utils.fmt(result.meta.score)}` : ""}
${result.meta.comments !== undefined ? `◨ ${Utils.fmt(result.meta.comments)}` : ""}
${result.meta.words !== undefined ? `◎ ${Utils.fmt(result.meta.words)}W` : ""}
${result.meta.lang ? `${result.meta.lang.toUpperCase()}` : ""}
${result.meta.version ? `V${result.meta.version}` : ""}
`
: ""
}
`;
// Add click handler
resultEl.addEventListener("click", (e) => {
if (!e.target.closest(".result-meta")) {
window.open(result.url, "_blank");
}
});
fragment.appendChild(resultEl);
});
// Batch update
this.resultsEl.innerHTML = "";
this.resultsEl.appendChild(fragment);
}
/**
* Show loading state
*/
showLoading() {
this.resultsEl.innerHTML = Array(4)
.fill(
'
${now.toLocaleTimeString("en-GB", {
hour: "2-digit",
minute: "2-digit",
timeZone: zone.tz,
})}
${zone.label}
`,
)
.join("");
}
}
/**
* Setup event listeners
*/
setupListeners() {
// Debounced search on input
const debouncedSearch = Utils.debounce((value) => {
this.search(value);
this.updateSuggestions(value);
}, 300);
this.searchInput.addEventListener("input", (e) => {
debouncedSearch(e.target.value);
});
// Enter key for immediate search
this.searchInput.addEventListener("keydown", (e) => {
if (e.key === "Enter") {
this.search(this.searchInput.value);
this.hideSuggestions();
} else if (e.key === "Escape") {
this.goHome();
}
});
// Global keyboard shortcuts
document.addEventListener("keydown", (e) => {
if (
e.key === "/" &&
document.activeElement !== this.searchInput &&
document.activeElement !== document.getElementById("watchlist-input")
) {
e.preventDefault();
this.searchInput.focus();
}
if (e.key === "Escape") {
this.goHome();
}
});
// Clear button
const clearBtn = document.getElementById("clear-btn");
if (clearBtn) {
clearBtn.onclick = () => {
this.goHome();
this.searchInput.focus();
};
}
// Voice search
const voiceBtn = document.getElementById("voice-btn");
if (voiceBtn) {
voiceBtn.onclick = () => this.voiceSearch();
}
// Handle browser back/forward
window.addEventListener("popstate", () => {
const query = this.getQueryFromUrl();
if (query !== this.state.query) {
if (query.trim()) {
this.searchInput.value = query;
this.search(query);
} else {
this.goHome();
}
}
});
}
/**
* Setup keyboard navigation for results
*/
setupKeyboardNavigation() {
document.addEventListener("keydown", (e) => {
if (!this.state.hasSearched || this.state.results.length === 0) return;
const results = Array.from(document.querySelectorAll(".result"));
if (results.length === 0) return;
switch (e.key) {
case "ArrowDown":
e.preventDefault();
this.navigateResults(1);
break;
case "ArrowUp":
e.preventDefault();
this.navigateResults(-1);
break;
case "Enter":
if (this.state.selectedResultIndex >= 0) {
const result = results[this.state.selectedResultIndex];
if (result) {
const url = result.querySelector(".result-url").textContent;
if (url) window.open(url, "_blank");
}
}
break;
}
});
}
/**
* Navigate through results with arrow keys
* @param {number} direction - 1 for down, -1 for up
*/
navigateResults(direction) {
const results = Array.from(document.querySelectorAll(".result"));
if (results.length === 0) return;
// Remove previous selection
if (this.state.selectedResultIndex >= 0 && results[this.state.selectedResultIndex]) {
results[this.state.selectedResultIndex].classList.remove("selected");
}
// Calculate new index
let newIndex = this.state.selectedResultIndex + direction;
if (newIndex < 0) newIndex = results.length - 1;
if (newIndex >= results.length) newIndex = 0;
// Apply new selection
this.state.selectedResultIndex = newIndex;
results[newIndex].classList.add("selected");
results[newIndex].scrollIntoView({ behavior: "smooth", block: "nearest" });
}
/**
* Update search suggestions
* @param {string} query - Current query
*/
updateSuggestions(query) {
if (!query.trim()) {
this.hideSuggestions();
return;
}
// Get suggestions from history and bangs
this.suggestions = [
...this.state.history
.filter((item) => item.q.toLowerCase().includes(query.toLowerCase()))
.map((item) => item.q)
.slice(0, 3),
...Object.values(this.sources)
.map((source) => `${source.bang} `)
.filter((bang) => bang.includes(query.toLowerCase())),
];
this.showSuggestions();
}
/**
* Show suggestions dropdown
*/
showSuggestions() {
if (this.suggestions.length === 0) {
this.hideSuggestions();
return;
}
// Create or update suggestions container
let container = document.querySelector(".suggestions-container");
if (!container) {
container = document.createElement("div");
container.className = "suggestions-container";
this.searchInput.parentNode.appendChild(container);
}
container.innerHTML = this.suggestions
.map(
(suggestion) => `