1
0
Fork 0
mirror of https://github.com/Findus23/RPGnotes.git synced 2024-09-19 15:43:45 +02:00

autocomplete

This commit is contained in:
Lukas Winkler 2022-04-11 23:52:19 +02:00
parent 24d6e6dd24
commit fb3878ad94
Signed by: lukas
GPG key ID: 54DE4D798D244853
11 changed files with 156 additions and 9 deletions

View file

@ -30,6 +30,7 @@
{% endblock %}
{% block extra_js %}
{{ super() }}
{% if "Safari/" not in request.META.get('HTTP_USER_AGENT', '') %}
<script src="{{ static("libs/easymde.min.js") }}"></script>
<script src="{{ static("libs/fontawesome-solid.min.js") }}"></script>

11
package-lock.json generated
View file

@ -7,6 +7,7 @@
"dependencies": {
"@fortawesome/fontawesome-free": "^5.15.4",
"@sentry/browser": "^6.12.0",
"@trevoreyre/autocomplete-js": "^2.2.0",
"bootstrap": "^5.1.0",
"easymde": "^2.15.0",
"luminous-lightbox": "^2.3.5"
@ -106,6 +107,11 @@
"node": ">=6"
}
},
"node_modules/@trevoreyre/autocomplete-js": {
"version": "2.2.0",
"resolved": "https://registry.npmjs.org/@trevoreyre/autocomplete-js/-/autocomplete-js-2.2.0.tgz",
"integrity": "sha512-emHJWZBPWdB5iDW9MrLSfq3lopyDlIhYXa8ttnCX9kQp1g+G0Lmfu/v6fW2aggjAfsZX8ksuZSG65o+EdwoN0g=="
},
"node_modules/@types/codemirror": {
"version": "5.60.5",
"resolved": "https://registry.npmjs.org/@types/codemirror/-/codemirror-5.60.5.tgz",
@ -265,6 +271,11 @@
"tslib": "^1.9.3"
}
},
"@trevoreyre/autocomplete-js": {
"version": "2.2.0",
"resolved": "https://registry.npmjs.org/@trevoreyre/autocomplete-js/-/autocomplete-js-2.2.0.tgz",
"integrity": "sha512-emHJWZBPWdB5iDW9MrLSfq3lopyDlIhYXa8ttnCX9kQp1g+G0Lmfu/v6fW2aggjAfsZX8ksuZSG65o+EdwoN0g=="
},
"@types/codemirror": {
"version": "5.60.5",
"resolved": "https://registry.npmjs.org/@types/codemirror/-/codemirror-5.60.5.tgz",

View file

@ -2,6 +2,7 @@
"dependencies": {
"@fortawesome/fontawesome-free": "^5.15.4",
"@sentry/browser": "^6.12.0",
"@trevoreyre/autocomplete-js": "^2.2.0",
"bootstrap": "^5.1.0",
"easymde": "^2.15.0",
"luminous-lightbox": "^2.3.5"

View file

@ -2,7 +2,8 @@ from django.urls import path
from search import views
urlpatterns=[
urlpatterns = [
path("", views.SearchResultsView.as_view(), name="search"),
path("autocomplete/", views.autocomplete, name="autocomplete"),
]

View file

@ -1,7 +1,8 @@
# Create your views here.
from itertools import chain
from django.contrib.postgres.search import SearchVector, SearchQuery, SearchRank, SearchHeadline, TrigramWordSimilarity
from django.contrib.postgres.search import SearchVector, SearchQuery, SearchRank, SearchHeadline, TrigramWordDistance
from django.http import JsonResponse
from django.views.generic import TemplateView
from campaigns.models import Campaign
@ -12,6 +13,8 @@ from locations.models import Location
from loot.models import Loot
from notes.models import Note
search_models = [Location, Character, Faction, IngameDay, Note, Loot]
class SearchResultsView(TemplateView):
template_name = "search/search_results.jinja"
@ -27,16 +30,15 @@ class SearchResultsView(TemplateView):
name_vector = SearchVector('name', weight="A", config=config)
description_vector = SearchVector("description_html", weight="B", config=config)
query = SearchQuery(query_string, search_type='websearch', config=config)
models = [Location, Character, Faction, IngameDay, Note, Loot]
all_results = []
all_similar = []
for m in models:
for m in search_models:
if m == IngameDay:
vector = description_vector
else:
vector = description_vector + name_vector
similar = m.objects.annotate(
distance=TrigramWordSimilarity(query_string, "name")
distance=TrigramWordDistance(query_string, "name")
# distance=TrigramDistance("name", query_string)
).filter(name__trigram_word_similar=query_string).order_by('distance')
all_similar.extend(list(similar))
@ -58,3 +60,29 @@ class SearchResultsView(TemplateView):
context["similars"] = all_similar
context["query"] = query_string
return context
def autocomplete(request):
if "q" not in request.GET:
return ""
query_string = request.GET['q']
all_similar = []
for m in search_models:
if m == IngameDay:
continue
similar = m.objects.annotate(
distance=TrigramWordDistance(query_string, "name")
# distance=TrigramDistance("name", query_string)
).order_by('distance')
similar = [s for s in similar if s.distance < 0.5]
all_similar.extend(list(similar))
all_similar.sort(key=lambda s: s.distance)
data = []
for s in all_similar:
data.append({
"url": s.get_absolute_url(),
"name": s.name,
"distance": s.distance
})
return JsonResponse(data, safe=False)

25
static/js/autocomplete.js Normal file
View file

@ -0,0 +1,25 @@
new Autocomplete('#autocomplete', {
search: input => {
const url = `/search/autocomplete/?q=${encodeURI(input)}`
return new Promise(resolve => {
if (input.length === 0) {
return resolve([])
}
fetch(url)
.then(response => response.json())
.then(data => {
resolve(data)
})
})
},
getResultValue: result => result.name,
onSubmit: result => {
if (!result) {
return
}
location.href = result.url
}
})

1
static/libs/autocomplete.min.js vendored Symbolic link
View file

@ -0,0 +1 @@
../../node_modules/@trevoreyre/autocomplete-js/dist/autocomplete.min.js

View file

@ -0,0 +1,71 @@
// based on https://github.com/trevoreyre/autocomplete/blob/4caf5f8107365c268a0543c652f6ad44a91fe488/packages/style.css
[data-position="below"] .autocomplete-input[aria-expanded="true"] {
border-bottom-color: transparent;
}
[data-position="above"] .autocomplete-input[aria-expanded="true"] {
border-top-color: transparent;
z-index: 2;
}
/* Loading spinner */
.autocomplete[data-loading="true"]::after {
content: "";
border: 3px solid rgba(0, 0, 0, 0.12);
border-right: 3px solid rgba(0, 0, 0, 0.48);
border-radius: 100%;
width: 20px;
height: 20px;
position: absolute;
right: 12px;
top: 50%;
transform: translateY(-50%);
animation: rotate 1s infinite linear;
}
.autocomplete-result-list {
margin: 0;
border: 1px solid rgba(0, 0, 0, 0.12);
padding: 0;
box-sizing: border-box;
max-height: 296px;
overflow-y: auto;
background: #fff;
list-style: none;
box-shadow: 0 2px 2px rgba(0, 0, 0, 0.16);
}
[data-position="below"] .autocomplete-result-list {
margin-top: -1px;
border-top-color: transparent;
border-radius: 0 0 8px 8px;
padding-bottom: 8px;
}
[data-position="above"] .autocomplete-result-list {
margin-bottom: -1px;
border-bottom-color: transparent;
border-radius: 8px 8px 0 0;
padding-top: 8px;
}
/* Single result item */
.autocomplete-result {
cursor: default;
padding: 12px;
}
.autocomplete-result:hover,
.autocomplete-result[aria-selected="true"] {
background-color: rgba(0, 0, 0, 0.06);
}
@keyframes rotate {
from {
transform: translateY(-50%) rotate(0deg);
}
to {
transform: translateY(-50%) rotate(359deg);
}
}

View file

@ -3,6 +3,7 @@
@import "node_modules/bootstrap/scss/bootstrap";
@import "node_modules/easymde/dist/easymde.min";
@import "node_modules/luminous-lightbox/dist/luminous-basic";
@import "autocomple";
@import "misc";
@import "avatar";

View file

@ -48,7 +48,6 @@
<script nonce="{{ request.csp_nonce }}">
document.addEventListener('DOMContentLoaded', function () {
const galleries = document.querySelectorAll("a.image-viewer");
console.info(galleries)
new LuminousGallery(galleries)
});
</script>

View file

@ -45,9 +45,12 @@
</ul>
<form class="d-flex" action="{{ url("search") }}">
<div class="input-group">
<input class="form-control" name="q" type="search"
<div id="autocomplete" class="autocomplete">
<input class="form-control autocomplete-input" name="q" type="search"
placeholder="{% trans %}Search{% endtrans %}"
aria-label="{% trans %}Search{% endtrans %}">
<ul class="autocomplete-result-list"></ul>
</div>
<button class="btn btn-outline-secondary" type="submit">
{% trans %}Go!{% endtrans %}
</button>
@ -83,3 +86,8 @@
{% block content %}
{% endblock %}
{% endblock %}
{% block extra_js %}
<script src="/static/libs/autocomplete.min.js"></script>
<script src="/static/js/autocomplete.js"></script>
{% endblock %}