site: implement search functionality

This commit is contained in:
Lucas Gabriel Vuotto 2025-04-27 15:20:49 +00:00
parent 3ae6b02672
commit 08be6b17f5
8 changed files with 257 additions and 2 deletions

View file

@ -52,9 +52,11 @@
}
dl,
form,
h1,
h2,
h3,
input,
ol,
p,
ul {
@ -72,6 +74,7 @@ body {
}
dl,
form,
h1,
h2,
h3,
@ -127,6 +130,65 @@ img {
height: auto;
}
input {
font: inherit;
border: var(--border-thin);
padding: 0 0.1875rem;
}
input[type=submit] {
background-color: var(--accent);
color: var(--bg);
font-weight: bold;
}
input[type=submit]:active {
background-color: var(--accent-hover);
border-color: var(--accent-hover);
}
form[name=search] {
gap: 1rem;
justify-content: center;
align-items: center;
padding-right: 0.5rem;
padding-left: 0.5rem;
}
form[name=search] input[type=submit] {
width: 7rem;
}
form[name=search] input[name=tags] {
width: 100%;
}
.position-relative {
position: relative;
}
#tags-suggestions {
position: absolute;
background-color: var(--bg);
width: 100%;
border: var(--border-thin);
box-shadow: rgba(0, 0, 0, 0.5) 0 0.25rem 0.5rem -0.25rem;
padding-left: 0;
list-style: none;
}
.suggestion-hover {
background-color: #eee;
}
#tags-suggestions li {
padding-left: 0.1875rem;
padding-right: 0.1875rem;
}
body > header {
border-bottom: var(--border-thin);
font-size: 1.25rem;
@ -168,10 +230,18 @@ main {
padding-bottom: var(--gap);
}
[x-cloak] {
display: none;
}
.text-center {
text-align: center;
}
.float-right {
float: right;
}
.layout-viewport {
margin-left: auto;
margin-right: auto;
@ -263,16 +333,23 @@ main {
line-height: 0;
}
.accent {
color: var(--accent);
}
span.tag-kind-a,
a.tag-kind-a:link,
a.tag-kind-a:visited {
color: var(--tag-kind-a);
}
span.tag-kind-c,
a.tag-kind-c:link,
a.tag-kind-c:visited {
color: var(--tag-kind-c);
}
span.tag-kind-r,
a.tag-kind-r:link,
a.tag-kind-r:visited {
color: var(--tag-kind-r);

File diff suppressed because one or more lines are too long

97
public/js/app.js Normal file
View file

@ -0,0 +1,97 @@
function getCurrentWord(s, cursor) {
return s.substring(s.lastIndexOf(' ', cursor - 1) + 1, cursor);
}
function mySplice(s, idx, del, n) {
return s.substring(0, idx) + n + s.substring(idx + del);
}
function suggestionPosition(el) {
// el.parentNode is ul#tags-suggestion, which include a template. That's why
// a -1 is needed here.
const idx = [...el.parentNode.children].indexOf(el);
return idx <= 0 ? null : idx - 1;
}
function currentSuggestionElement(idx) {
if (idx === null)
return null;
return document.querySelector("#tags-suggestions").children[idx + 1];
}
document.addEventListener('alpine:init', () => {
Alpine.data('tagsSuggestions', () => ({
suggestions: [],
currentSuggestion: null,
inputCursor: 0,
query: '',
reset() {
this.suggestions = [];
this.currentSuggestion = null;
this.inputCursor = 0;
this.query = '';
},
async fetchSuggestions(evt) {
this.inputCursor = evt.target.selectionStart;
this.query = getCurrentWord(evt.target.value, this.inputCursor);
if (!this.query) {
this.reset();
return;
}
const params = new URLSearchParams({q: this.query});
const response = await fetch(`${endpoints.search}?${params}`);
if (!response.ok) {
this.reset();
return;
}
this.suggestions = await response.json();
},
completeSuggestion(evt) {
if (this.currentSuggestion === null)
return;
const value = this.$refs.tags.value;
const pos = this.inputCursor - this.query.length;
const tag = this.suggestions[this.currentSuggestion].display;
this.$refs.tags.value = mySplice(value, pos, this.query.length,
tag + ' ');
this.reset();
this.$refs.tags.focus();
// Don't switch focus if we're completing after a Tab press.
if (evt.type === "keydown" && evt.keyCode === 9)
evt.preventDefault();
},
selectCurrentSuggestion(evt) {
this.currentSuggestion = suggestionPosition(evt.currentTarget);
this.completeSuggestion(evt);
},
setCurrentSuggestion(evt, amount = 0) {
currentSuggestionElement(this.currentSuggestion)
?.classList.remove("suggestion-hover");
if (evt === 'mouseenter')
this.currentSuggestion = suggestionPosition(evt.currentTarget);
else {
const len = this.suggestions.length;
let cur = this.currentSuggestion;
if (cur === null)
cur = amount > 0 ? -1 : amount < 0 ? len : 0;
this.currentSuggestion = (cur + amount + len) % len;
}
currentSuggestionElement(this.currentSuggestion)
?.classList.add("suggestion-hover");
}
}));
});