mirror of
https://github.com/iv-org/invidious.git
synced 2026-07-27 16:06:47 -05:00
Merge 6f9242d20e4892036f5580fbc22dd97c66dd365a into 59bb97cdc3011b37cbcd9e85f2f708f009978893
This commit is contained in:
commit
c57285f08a
@ -540,7 +540,8 @@ span > select {
|
||||
.light-theme .pure-button-primary:hover,
|
||||
.light-theme .pure-button-primary:focus,
|
||||
.light-theme .pure-button-secondary:hover,
|
||||
.light-theme .pure-button-secondary:focus {
|
||||
.light-theme .pure-button-secondary:focus,
|
||||
.light-theme #suggestions .active {
|
||||
color: #fff !important;
|
||||
border-color: rgba(0, 182, 240, 0.75) !important;
|
||||
background-color: rgba(0, 182, 240, 0.75) !important;
|
||||
@ -571,6 +572,10 @@ span > select {
|
||||
border: 1px solid black;
|
||||
}
|
||||
|
||||
.light-theme #suggestions {
|
||||
background: rgb(255, 255, 255);
|
||||
}
|
||||
|
||||
@media (prefers-color-scheme: light) {
|
||||
.no-theme a:hover,
|
||||
.no-theme a:active,
|
||||
@ -583,7 +588,8 @@ span > select {
|
||||
.no-theme .pure-button-primary:hover,
|
||||
.no-theme .pure-button-primary:focus,
|
||||
.no-theme .pure-button-secondary:hover,
|
||||
.no-theme .pure-button-secondary:focus {
|
||||
.no-theme .pure-button-secondary:focus,
|
||||
.no-theme #suggestions .active {
|
||||
color: #fff !important;
|
||||
border-color: rgba(0, 182, 240, 0.75) !important;
|
||||
background-color: rgba(0, 182, 240, 0.75) !important;
|
||||
@ -621,6 +627,10 @@ span > select {
|
||||
.no-theme .error-card {
|
||||
border: 1px solid black;
|
||||
}
|
||||
|
||||
.no-theme #suggestions {
|
||||
background: rgb(255, 255, 255);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -639,7 +649,8 @@ span > select {
|
||||
.dark-theme .pure-button-primary:hover,
|
||||
.dark-theme .pure-button-primary:focus,
|
||||
.dark-theme .pure-button-secondary:hover,
|
||||
.dark-theme .pure-button-secondary:focus {
|
||||
.dark-theme .pure-button-secondary:focus,
|
||||
.dark-theme #suggestions .active {
|
||||
color: #fff !important;
|
||||
border-color: rgb(0, 182, 240) !important;
|
||||
background-color: rgba(0, 182, 240, 1) !important;
|
||||
@ -687,6 +698,10 @@ body.dark-theme {
|
||||
border: 1px solid #5e5e5e;
|
||||
}
|
||||
|
||||
.dark-theme #suggestions {
|
||||
background: rgb(35, 35, 35);
|
||||
}
|
||||
|
||||
@media (prefers-color-scheme: dark) {
|
||||
.no-theme a:hover,
|
||||
.no-theme a:active,
|
||||
@ -697,7 +712,8 @@ body.dark-theme {
|
||||
.no-theme .pure-button-primary:hover,
|
||||
.no-theme .pure-button-primary:focus,
|
||||
.no-theme .pure-button-secondary:hover,
|
||||
.no-theme .pure-button-secondary:focus {
|
||||
.no-theme .pure-button-secondary:focus,
|
||||
.no-theme #suggestions .active {
|
||||
color: #fff !important;
|
||||
border-color: rgb(0, 182, 240) !important;
|
||||
background-color: rgba(0, 182, 240, 1) !important;
|
||||
@ -752,6 +768,10 @@ body.dark-theme {
|
||||
.no-theme .error-card {
|
||||
border: 1px solid #5e5e5e;
|
||||
}
|
||||
|
||||
.no-theme #suggestions {
|
||||
background: rgb(35, 35, 35);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -910,3 +930,33 @@ h1, h2, h3, h4, h5, p,
|
||||
display: inline-block;
|
||||
vertical-align: top;
|
||||
}
|
||||
|
||||
#search-container {
|
||||
position: relative;
|
||||
display: block !important;
|
||||
}
|
||||
|
||||
#suggestions {
|
||||
position: absolute;
|
||||
width: 100%;
|
||||
top: calc(100% + .25em);
|
||||
border: 1px solid #a0a0a0;
|
||||
z-index: 200;
|
||||
padding: .25em 0;
|
||||
margin: 0;
|
||||
display: none;
|
||||
}
|
||||
|
||||
#search-container:has(:active) #suggestions:has(*),
|
||||
#search-container:has(:focus) #suggestions:has(*) {
|
||||
display: block;
|
||||
}
|
||||
|
||||
#suggestions>* {
|
||||
display: block;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
padding: .25em .5em;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
123
assets/js/suggestions.js
Normal file
123
assets/js/suggestions.js
Normal file
@ -0,0 +1,123 @@
|
||||
"use strict";
|
||||
const searchbox = document.getElementById("searchbox");
|
||||
const searchContainer = document.getElementById("search-container");
|
||||
const suggestions = document.getElementById("suggestions");
|
||||
let selectedSuggestion = -1;
|
||||
let suggestionController;
|
||||
let queuedSuggestions = 0;
|
||||
|
||||
function resetSuggestions() {
|
||||
suggestions.textContent = "";
|
||||
selectedSuggestion = -1;
|
||||
}
|
||||
|
||||
searchbox?.addEventListener("input", function () {
|
||||
// If there is already a request in progress,
|
||||
// abort. The user has made another input,
|
||||
// invalidating the old query.
|
||||
if (suggestionController != undefined) {
|
||||
suggestionController.abort();
|
||||
}
|
||||
|
||||
// Only make a request for suggestions after
|
||||
// there is a delay of at least 150 milliseconds
|
||||
// between inputs.
|
||||
queuedSuggestions++;
|
||||
setTimeout(async function () {
|
||||
queuedSuggestions--;
|
||||
if (queuedSuggestions != 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Only continue for queries of more than one
|
||||
// character.
|
||||
if (searchbox.value.length < 2) {
|
||||
resetSuggestions();
|
||||
return;
|
||||
}
|
||||
|
||||
// Create the controller which will be
|
||||
// used to abort this request if another
|
||||
// is made before it finishes.
|
||||
suggestionController = new AbortController();
|
||||
const signal = suggestionController.signal;
|
||||
|
||||
// Make the request for suggestions.
|
||||
try {
|
||||
const resp = await fetch(
|
||||
`/api/v1/search/suggestions?q=${encodeURIComponent(searchbox.value)}`,
|
||||
{ signal },
|
||||
);
|
||||
const body = await resp.json();
|
||||
|
||||
// Put the results into DOM.
|
||||
resetSuggestions();
|
||||
const results = body.suggestions;
|
||||
for (let i = 0; i < results.length; i++) {
|
||||
const s = document.createElement("a");
|
||||
s.href = "/search";
|
||||
s.innerText = results[i];
|
||||
s.setAttribute("role", "option");
|
||||
s.setAttribute("aria-selected", "false");
|
||||
s.addEventListener("click", function (e) {
|
||||
searchbox.value = results[i];
|
||||
if (searchbox.form?.requestSubmit) {
|
||||
searchbox.form.requestSubmit();
|
||||
} else {
|
||||
searchbox.form?.submit();
|
||||
}
|
||||
e.preventDefault();
|
||||
});
|
||||
suggestions.appendChild(s);
|
||||
}
|
||||
} catch (err) {
|
||||
// Discard AbortError as it is expected.
|
||||
if (err.name !== "AbortError") {
|
||||
console.error(err);
|
||||
}
|
||||
}
|
||||
}, 150);
|
||||
});
|
||||
|
||||
searchbox?.addEventListener("keydown", function (e) {
|
||||
const currentSuggestions = suggestions.children;
|
||||
|
||||
// Navigate suggestions by key.
|
||||
switch (e.key) {
|
||||
case "ArrowDown":
|
||||
selectedSuggestion++;
|
||||
e.preventDefault();
|
||||
break;
|
||||
case "ArrowUp":
|
||||
selectedSuggestion--;
|
||||
e.preventDefault();
|
||||
break;
|
||||
case "Enter":
|
||||
if (selectedSuggestion != -1) {
|
||||
currentSuggestions[selectedSuggestion].click();
|
||||
e.preventDefault();
|
||||
return;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
// Loop around if you go beyond the end
|
||||
// or before -1 (the resting position).
|
||||
if (selectedSuggestion >= currentSuggestions.length) {
|
||||
selectedSuggestion = -1;
|
||||
} else if (selectedSuggestion < -1) {
|
||||
selectedSuggestion = currentSuggestions.length - 1;
|
||||
}
|
||||
|
||||
// Remove .active class from all elements
|
||||
// except the active one.
|
||||
for (let i = 0; i < currentSuggestions.length; i++) {
|
||||
if (i == selectedSuggestion) {
|
||||
currentSuggestions[i].classList.add("active");
|
||||
currentSuggestions[i].setAttribute("aria-selected", "true");
|
||||
} else if (currentSuggestions[i].classList.contains("active")) {
|
||||
currentSuggestions[i].classList.remove("active");
|
||||
currentSuggestions[i].setAttribute("aria-selected", "false");
|
||||
}
|
||||
}
|
||||
});
|
||||
@ -7,12 +7,14 @@
|
||||
<% else %>
|
||||
<form class="pure-form" action="/search" method="get">
|
||||
<% end %>
|
||||
<fieldset>
|
||||
<fieldset id="search-container">
|
||||
<input type="search" id="searchbox" autocorrect="off"
|
||||
aria-autocomplete="list" aria-controls="suggestions"
|
||||
autocapitalize="none" spellcheck="false" <% if autofocus %>autofocus<% end %>
|
||||
name="q" placeholder="<%= I18n.translate(locale, "search") %>"
|
||||
title="<%= I18n.translate(locale, "search") %>"
|
||||
value="<%= env.get?("search").try {|x| HTML.escape(x.as(String)) } %>">
|
||||
<ul id="suggestions"></ul>
|
||||
</fieldset>
|
||||
<button type="submit" id="searchbutton" aria-label="<%= I18n.translate(locale, "search") %>">
|
||||
<i class="icon ion-ios-search"></i>
|
||||
|
||||
@ -176,6 +176,7 @@
|
||||
</div>
|
||||
<script src="/js/handlers.js?v=<%= ASSET_COMMIT %>"></script>
|
||||
<script src="/js/themes.js?v=<%= ASSET_COMMIT %>"></script>
|
||||
<script src="/js/suggestions.js?v=<%= ASSET_COMMIT %>"></script>
|
||||
<% if env.get? "user" %>
|
||||
<script src="/js/sse.js?v=<%= ASSET_COMMIT %>"></script>
|
||||
<script id="notification_data" type="application/json">
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user