mirror of
https://github.com/wahyd4/links.git
synced 2026-08-09 05:06:16 +10:00
Add search
This commit is contained in:
Binary file not shown.
@@ -98,22 +98,23 @@
|
||||
<div class="main-content-grid">
|
||||
<!-- Left Column - Main Content -->
|
||||
<div class="left-column">
|
||||
<!-- Popular Links Section -->
|
||||
<div>
|
||||
<h2 class="section-title">{% trans "Popular Links" %}</h2>
|
||||
<div class="popular-links-desktop">
|
||||
{% for item in popular_links %}
|
||||
<div class="rounded-lg shadow-md overflow-hidden" style="background-color: {{ item.color }};">
|
||||
<div class="p-4">
|
||||
<a href="{% url 'redirect_to_original' item.link.alias %}" class="text-white hover:text-gray-200 font-semibold text-lg block truncate">
|
||||
{{ item.link.alias }}
|
||||
</a>
|
||||
<p class="text-white text-sm mt-2 truncate">{% trans "Clicks" %}: {{ item.link.click_count }}</p>
|
||||
</div>
|
||||
</div>
|
||||
{% empty %}
|
||||
<p class="text-gray-600 col-span-full">{% trans "No popular links available" %}</p>
|
||||
{% endfor %}
|
||||
<!-- Quick Search Section -->
|
||||
<div class="bg-white rounded-lg shadow-md p-6 mb-6">
|
||||
<div class="relative">
|
||||
<input
|
||||
type="text"
|
||||
id="main-search-input"
|
||||
placeholder="{% trans 'Search aliases...' %}"
|
||||
class="w-full text-lg px-4 py-3 pr-12 rounded-lg border-2 border-gray-300 focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-blue-500"
|
||||
autocomplete="off"
|
||||
autofocus
|
||||
>
|
||||
<div class="absolute inset-y-0 right-0 flex items-center pr-4 pointer-events-none">
|
||||
<svg class="h-6 w-6 text-gray-400" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor">
|
||||
<path fill-rule="evenodd" d="M8 4a4 4 0 100 8 4 4 0 000-8zM2 8a6 6 0 1110.89 3.476l4.817 4.817a1 1 0 01-1.414 1.414l-4.816-4.816A6 6 0 012 8z" clip-rule="evenodd" />
|
||||
</svg>
|
||||
</div>
|
||||
<div id="main-search-results" class="absolute top-full left-0 right-0 bg-white border border-gray-300 rounded-b-lg mt-1 hidden z-50 shadow-lg max-h-96 overflow-y-auto"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -216,22 +217,24 @@
|
||||
|
||||
<!-- Mobile/Tablet Layout (below 1024px) - Original Layout -->
|
||||
<div class="mobile-layout">
|
||||
<!-- Popular Links Section -->
|
||||
<div class="mb-6">
|
||||
<h2 class="text-lg font-semibold text-gray-700 mb-3 uppercase tracking-wide text-sm">{% trans "Popular Links" %}</h2>
|
||||
<div class="flex flex-row gap-2 sm:grid sm:grid-cols-3 sm:gap-6">
|
||||
{% for item in popular_links %}
|
||||
<div class="flex-1 sm:flex-none rounded-lg overflow-hidden shadow-md min-w-0" style="background-color: {{ item.color }};">
|
||||
<div class="p-2 sm:p-6">
|
||||
<a href="{% url 'redirect_to_original' item.link.alias %}" class="text-white hover:text-gray-200 font-semibold text-xs sm:text-2xl block truncate leading-tight sm:leading-normal">
|
||||
{{ item.link.alias }}
|
||||
</a>
|
||||
<p class="text-white text-xs sm:text-lg mt-1 sm:mt-3 truncate">{% trans "Clicks" %}: {{ item.link.click_count }}</p>
|
||||
</div>
|
||||
<!-- Quick Search Section -->
|
||||
<div class="mb-4">
|
||||
<div class="bg-white rounded-lg shadow-md p-3">
|
||||
<div class="relative">
|
||||
<input
|
||||
type="text"
|
||||
id="main-search-input-mobile"
|
||||
placeholder="{% trans 'Search aliases...' %}"
|
||||
class="w-full text-base px-3 py-2 pr-10 rounded-lg border-2 border-gray-300 focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-blue-500"
|
||||
autocomplete="off"
|
||||
>
|
||||
<div class="absolute inset-y-0 right-0 flex items-center pr-3 pointer-events-none">
|
||||
<svg class="h-5 w-5 text-gray-400" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor">
|
||||
<path fill-rule="evenodd" d="M8 4a4 4 0 100 8 4 4 0 000-8zM2 8a6 6 0 1110.89 3.476l4.817 4.817a1 1 0 01-1.414 1.414l-4.816-4.816A6 6 0 012 8z" clip-rule="evenodd" />
|
||||
</svg>
|
||||
</div>
|
||||
{% empty %}
|
||||
<p class="text-gray-600 w-full">{% trans "No popular links available" %}</p>
|
||||
{% endfor %}
|
||||
<div id="main-search-results-mobile" class="absolute top-full left-0 right-0 bg-white border border-gray-300 rounded-b-lg mt-1 hidden z-50 shadow-lg max-h-96 overflow-y-auto"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -538,35 +541,156 @@
|
||||
const localTime = utcTime.toLocaleString();
|
||||
element.textContent = localTime;
|
||||
});
|
||||
});
|
||||
|
||||
// Random image refresh functionality
|
||||
function refreshRandomImage(img) {
|
||||
const box = img.getAttribute('data-box');
|
||||
const timestamp = new Date().getTime();
|
||||
const width = img.offsetWidth || 500;
|
||||
const height = img.offsetHeight || 500;
|
||||
const newUrl = `/api/images/random/${width}/${height}/?fit=crop&v=${timestamp}&box=${box}`;
|
||||
// Main search functionality - Desktop
|
||||
setupMainSearch('main-search-input', 'main-search-results');
|
||||
|
||||
// Main search functionality - Mobile
|
||||
setupMainSearch('main-search-input-mobile', 'main-search-results-mobile');
|
||||
|
||||
// Create new image to preload
|
||||
const newImg = new Image();
|
||||
newImg.onload = function() {
|
||||
img.style.opacity = '0.5';
|
||||
setTimeout(() => {
|
||||
img.src = newUrl;
|
||||
function setupMainSearch(inputId, resultsId) {
|
||||
const searchInput = document.getElementById(inputId);
|
||||
const searchResults = document.getElementById(resultsId);
|
||||
if (!searchInput || !searchResults) return;
|
||||
|
||||
let currentFocus = -1;
|
||||
|
||||
function debounce(func, wait) {
|
||||
let timeout;
|
||||
return function executedFunction(...args) {
|
||||
const later = () => {
|
||||
clearTimeout(timeout);
|
||||
func(...args);
|
||||
};
|
||||
clearTimeout(timeout);
|
||||
timeout = setTimeout(later, wait);
|
||||
};
|
||||
}
|
||||
|
||||
function highlightMatch(text, query) {
|
||||
if (!query) return text;
|
||||
const regex = new RegExp(`(${query})`, 'gi');
|
||||
return text.replace(regex, '<strong class="text-blue-600 bg-blue-100">$1</strong>');
|
||||
}
|
||||
|
||||
searchInput.addEventListener('input', debounce(function(e) {
|
||||
const query = e.target.value.trim();
|
||||
|
||||
if (query.length === 0) {
|
||||
searchResults.style.display = 'none';
|
||||
searchResults.innerHTML = '';
|
||||
return;
|
||||
}
|
||||
|
||||
const url = `/search/aliases/?q=${encodeURIComponent(query)}`;
|
||||
|
||||
fetch(url)
|
||||
.then(response => response.json())
|
||||
.then(data => {
|
||||
searchResults.innerHTML = '';
|
||||
currentFocus = -1;
|
||||
|
||||
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.className = 'search-result-item p-3 hover:bg-gray-100 cursor-pointer border-b border-gray-100 last:border-b-0';
|
||||
|
||||
const aliasLink = document.createElement('a');
|
||||
aliasLink.href = item.url;
|
||||
aliasLink.innerHTML = highlightMatch(item.alias, query);
|
||||
aliasLink.className = 'text-blue-600 hover:underline block text-lg';
|
||||
|
||||
div.appendChild(aliasLink);
|
||||
searchResults.appendChild(div);
|
||||
}
|
||||
});
|
||||
searchResults.style.display = 'block';
|
||||
} else {
|
||||
searchResults.innerHTML = '<div class="p-3 text-gray-500">{% 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) {
|
||||
let items = searchResults.getElementsByClassName("search-result-item");
|
||||
|
||||
if (e.keyCode == 40) { // Down arrow
|
||||
e.preventDefault();
|
||||
currentFocus++;
|
||||
addActive(items);
|
||||
} else if (e.keyCode == 38) { // Up arrow
|
||||
e.preventDefault();
|
||||
currentFocus--;
|
||||
addActive(items);
|
||||
} else if (e.keyCode == 13) { // Enter
|
||||
e.preventDefault();
|
||||
if (currentFocus > -1 && items[currentFocus]) {
|
||||
items[currentFocus].querySelector('a').click();
|
||||
} else if (items.length > 0) {
|
||||
// Go to first result if no selection
|
||||
items[0].querySelector('a').click();
|
||||
}
|
||||
} else if (e.keyCode == 27) { // Escape
|
||||
searchResults.style.display = 'none';
|
||||
searchResults.innerHTML = '';
|
||||
searchInput.value = '';
|
||||
}
|
||||
});
|
||||
|
||||
function addActive(items) {
|
||||
if (!items || items.length === 0) return false;
|
||||
removeActive(items);
|
||||
if (currentFocus >= items.length) currentFocus = 0;
|
||||
if (currentFocus < 0) currentFocus = (items.length - 1);
|
||||
items[currentFocus].classList.add("bg-blue-50");
|
||||
}
|
||||
|
||||
function removeActive(items) {
|
||||
for (let i = 0; i < items.length; i++) {
|
||||
items[i].classList.remove("bg-blue-50");
|
||||
}
|
||||
}
|
||||
|
||||
// Close dropdown when clicking outside
|
||||
document.addEventListener('click', function(e) {
|
||||
if (e.target !== searchInput && !searchResults.contains(e.target)) {
|
||||
searchResults.style.display = 'none';
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Random image refresh functionality
|
||||
window.refreshRandomImage = function(img) {
|
||||
const box = img.getAttribute('data-box');
|
||||
const timestamp = new Date().getTime();
|
||||
const width = img.offsetWidth || 500;
|
||||
const height = img.offsetHeight || 500;
|
||||
const newUrl = `/api/images/random/${width}/${height}/?fit=crop&v=${timestamp}&box=${box}`;
|
||||
|
||||
const newImg = new Image();
|
||||
newImg.onload = function() {
|
||||
img.style.opacity = '0.5';
|
||||
setTimeout(() => {
|
||||
img.src = newUrl;
|
||||
img.style.opacity = '1';
|
||||
}, 150);
|
||||
};
|
||||
newImg.onerror = function() {
|
||||
console.error('Failed to load new random image');
|
||||
const currentUrl = new URL(img.src);
|
||||
currentUrl.searchParams.set('v', timestamp);
|
||||
img.src = currentUrl.toString();
|
||||
img.style.opacity = '1';
|
||||
}, 150);
|
||||
};
|
||||
newImg.src = newUrl;
|
||||
};
|
||||
newImg.onerror = function() {
|
||||
console.error('Failed to load new random image');
|
||||
// Fallback - just update timestamp on current URL
|
||||
const currentUrl = new URL(img.src);
|
||||
currentUrl.searchParams.set('v', timestamp);
|
||||
img.src = currentUrl.toString();
|
||||
img.style.opacity = '1';
|
||||
};
|
||||
newImg.src = newUrl;
|
||||
}
|
||||
});
|
||||
|
||||
// Select all functionality
|
||||
document.getElementById('select-all').addEventListener('change', function() {
|
||||
|
||||
@@ -82,13 +82,6 @@ class LinkListView(ListView):
|
||||
'#1abc9c', '#d35400', '#34495e', '#16a085', '#27ae60'
|
||||
]
|
||||
|
||||
# Add top 3 popular links to the context with random colors
|
||||
popular_links = Link.objects.order_by('-click_count')[:3]
|
||||
context['popular_links'] = [
|
||||
{'link': link, 'color': random.choice(colors)}
|
||||
for link in popular_links
|
||||
]
|
||||
|
||||
# Add latest 3 posts to the context (most recent first)
|
||||
context['latest_posts'] = Post.objects.all().order_by('-created_at')[:3]
|
||||
|
||||
|
||||
Reference in New Issue
Block a user