mirror of
https://github.com/wahyd4/links.git
synced 2026-08-09 05:06:16 +10:00
Add search
This commit is contained in:
BIN
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -0,0 +1,6 @@
|
||||
from django.urls import path
|
||||
from . import views
|
||||
|
||||
urlpatterns = [
|
||||
path('', views.search_aliases, name='search_aliases'),
|
||||
]
|
||||
+1
-1
@@ -7,6 +7,6 @@ urlpatterns = [
|
||||
path('update/<int:pk>/', views.LinkUpdateView.as_view(), name='link_update'),
|
||||
path('delete/<int:pk>/', views.LinkDeleteView.as_view(), name='link_delete'),
|
||||
path('delete-selected/', views.delete_selected, name='delete_selected'),
|
||||
path('<str:alias>/', views.redirect_to_original, name='redirect_to_original'),
|
||||
path('detail/<int:pk>/', views.LinkDetailView.as_view(), name='link_detail'),
|
||||
path('<str:alias>/', views.redirect_to_original, name='redirect_to_original'),
|
||||
]
|
||||
|
||||
+32
-2
@@ -1,7 +1,7 @@
|
||||
from django.shortcuts import render, redirect, get_object_or_404
|
||||
from django.views.generic import ListView, CreateView, UpdateView, DeleteView, DetailView
|
||||
from django.urls import reverse_lazy
|
||||
from django.db.models import F, Count
|
||||
from django.urls import reverse_lazy, reverse # Add 'reverse' here
|
||||
from django.db.models import F, Count, Q
|
||||
from django.db.models.functions import TruncDate
|
||||
from .models import Link, ClickLog
|
||||
from .forms import LinkForm
|
||||
@@ -11,6 +11,7 @@ from django.contrib import messages
|
||||
from django.db import IntegrityError
|
||||
from django.utils.translation import gettext as _
|
||||
from django.core.exceptions import ValidationError
|
||||
from django.http import JsonResponse
|
||||
|
||||
class LinkListView(ListView):
|
||||
model = Link
|
||||
@@ -79,3 +80,32 @@ class LinkDetailView(DetailView):
|
||||
|
||||
context['click_stats'] = json.dumps(click_stats_list, cls=DjangoJSONEncoder)
|
||||
return context
|
||||
|
||||
def search_aliases(request):
|
||||
query = request.GET.get('q', '').strip()
|
||||
print(f"Search query: {query}") # Debug print
|
||||
|
||||
if not query:
|
||||
return JsonResponse([], safe=False)
|
||||
|
||||
if len(query) == 1:
|
||||
# For single-letter queries, only match the start of the alias
|
||||
links = Link.objects.filter(alias__istartswith=query)[:10]
|
||||
else:
|
||||
# For longer queries, keep the current behavior
|
||||
links = Link.objects.filter(Q(alias__icontains=query) | Q(original_url__icontains=query))[:10]
|
||||
|
||||
print(f"Found {links.count()} links") # Debug print
|
||||
|
||||
results = []
|
||||
for link in links:
|
||||
try:
|
||||
url = request.build_absolute_uri(reverse('redirect_to_original', args=[link.alias]))
|
||||
results.append({'alias': link.alias, 'url': url})
|
||||
print(f"Added result: {link.alias}") # Debug print
|
||||
except Exception as e:
|
||||
print(f"Error creating URL for alias {link.alias}: {str(e)}") # Debug print
|
||||
continue
|
||||
|
||||
print(f"Search results: {results}") # Debug print
|
||||
return JsonResponse(results, safe=False)
|
||||
|
||||
Binary file not shown.
@@ -93,6 +93,9 @@ msgstr "该别名已存在。请选择一个不同的别名。"
|
||||
msgid "Edit Link"
|
||||
msgstr "编辑链接"
|
||||
|
||||
msgid "Search aliases..."
|
||||
msgstr "搜索别名..."
|
||||
|
||||
#: 3.12/lib/python3.12/site-packages/django/forms/fields.py:95
|
||||
msgid "This field is required."
|
||||
msgstr ""
|
||||
|
||||
+133
-1
@@ -32,13 +32,36 @@
|
||||
border-color: #f5c6cb;
|
||||
color: #721c24;
|
||||
}
|
||||
.search-results {
|
||||
position: absolute;
|
||||
top: 100%;
|
||||
left: 0;
|
||||
right: 0;
|
||||
background-color: white;
|
||||
border: 1px solid #ddd;
|
||||
border-top: none;
|
||||
max-height: 300px;
|
||||
overflow-y: auto;
|
||||
display: none;
|
||||
}
|
||||
.search-result-item {
|
||||
padding: 10px;
|
||||
cursor: pointer;
|
||||
}
|
||||
.search-result-item:hover, .search-result-item.active {
|
||||
background-color: #f8f9fa;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<nav class="navbar navbar-expand-lg navbar-dark bg-primary fixed-top">
|
||||
<div class="container">
|
||||
<a class="navbar-brand" href="{% url 'link_list' %}">{% trans "URL Manager" %}</a>
|
||||
<div class="ml-auto">
|
||||
<div class="d-flex">
|
||||
<div class="position-relative me-2">
|
||||
<input type="text" id="search-input" class="form-control" placeholder="{% trans 'Search aliases...' %}">
|
||||
<div id="search-results" class="search-results"></div>
|
||||
</div>
|
||||
<form action="{% url 'set_language' %}" method="post" class="form-inline">
|
||||
{% csrf_token %}
|
||||
<input name="next" type="hidden" value="{{ request.path }}">
|
||||
@@ -86,6 +109,115 @@
|
||||
}, 5000);
|
||||
});
|
||||
});
|
||||
|
||||
// Search functionality
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
const searchInput = document.getElementById('search-input');
|
||||
const searchResults = document.getElementById('search-results');
|
||||
let currentFocus = -1;
|
||||
|
||||
if (!searchInput || !searchResults) {
|
||||
console.error('Search input or results container not found');
|
||||
return;
|
||||
}
|
||||
|
||||
searchInput.addEventListener('input', debounce(function(e) {
|
||||
const query = e.target.value.trim();
|
||||
console.log('Search query:', query); // Debug log
|
||||
|
||||
if (query.length === 0) {
|
||||
searchResults.style.display = 'none';
|
||||
return;
|
||||
}
|
||||
|
||||
const url = `/search/?q=${encodeURIComponent(query)}`;
|
||||
console.log('Fetching from URL:', url); // Debug log
|
||||
|
||||
fetch(url)
|
||||
.then(response => {
|
||||
if (!response.ok) {
|
||||
throw new Error(`HTTP error! status: ${response.status}`);
|
||||
}
|
||||
return response.json();
|
||||
})
|
||||
.then(data => {
|
||||
console.log('Search results:', data); // Debug log
|
||||
searchResults.innerHTML = '';
|
||||
if (Array.isArray(data) && data.length > 0) {
|
||||
data.forEach(item => {
|
||||
if (item && typeof item === 'object' && 'alias' in item && 'url' in item) {
|
||||
const div = document.createElement('div');
|
||||
div.textContent = item.alias;
|
||||
div.classList.add('search-result-item');
|
||||
div.addEventListener('click', function() {
|
||||
window.location.href = item.url;
|
||||
});
|
||||
searchResults.appendChild(div);
|
||||
} else {
|
||||
console.warn('Invalid item in search results:', item);
|
||||
}
|
||||
});
|
||||
searchResults.style.display = 'block';
|
||||
} else {
|
||||
searchResults.innerHTML = '<div class="search-result-item">{% trans "No results found" %}</div>';
|
||||
searchResults.style.display = 'block';
|
||||
}
|
||||
})
|
||||
.catch(error => {
|
||||
console.error('Error:', error);
|
||||
searchResults.style.display = 'none';
|
||||
});
|
||||
}, 300));
|
||||
|
||||
searchInput.addEventListener('keydown', function(e) {
|
||||
const items = searchResults.getElementsByClassName('search-result-item');
|
||||
if (e.keyCode === 40) { // Down arrow
|
||||
currentFocus++;
|
||||
addActive(items);
|
||||
} else if (e.keyCode === 38) { // Up arrow
|
||||
currentFocus--;
|
||||
addActive(items);
|
||||
} else if (e.keyCode === 13) { // Enter
|
||||
e.preventDefault();
|
||||
if (currentFocus > -1) {
|
||||
if (items) items[currentFocus].click();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
function addActive(items) {
|
||||
if (!items) return false;
|
||||
removeActive(items);
|
||||
if (currentFocus >= items.length) currentFocus = 0;
|
||||
if (currentFocus < 0) currentFocus = (items.length - 1);
|
||||
items[currentFocus].classList.add('active');
|
||||
}
|
||||
|
||||
function removeActive(items) {
|
||||
for (let i = 0; i < items.length; i++) {
|
||||
items[i].classList.remove('active');
|
||||
}
|
||||
}
|
||||
|
||||
function debounce(func, wait) {
|
||||
let timeout;
|
||||
return function executedFunction(...args) {
|
||||
const context = this;
|
||||
const later = () => {
|
||||
clearTimeout(timeout);
|
||||
func.apply(context, args);
|
||||
};
|
||||
clearTimeout(timeout);
|
||||
timeout = setTimeout(later, wait);
|
||||
};
|
||||
}
|
||||
|
||||
document.addEventListener('click', function(e) {
|
||||
if (e.target !== searchInput && e.target !== searchResults) {
|
||||
searchResults.style.display = 'none';
|
||||
}
|
||||
});
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
Binary file not shown.
+5
-1
@@ -2,7 +2,11 @@ from django.contrib import admin
|
||||
from django.urls import path, include
|
||||
from django.conf.urls.i18n import i18n_patterns
|
||||
|
||||
urlpatterns = i18n_patterns(
|
||||
urlpatterns = [
|
||||
path('search/', include('links.search_urls')), # Add this line
|
||||
]
|
||||
|
||||
urlpatterns += i18n_patterns(
|
||||
path('admin/', admin.site.urls),
|
||||
path('', include('links.urls')),
|
||||
path('i18n/', include('django.conf.urls.i18n')),
|
||||
|
||||
Reference in New Issue
Block a user