From 6aa7ed3c71948148d566d9f0b39c517f8f43b15c Mon Sep 17 00:00:00 2001 From: kisfenyo Date: Thu, 26 Feb 2026 08:55:43 +0100 Subject: [PATCH] Search dropdown: keyboard navigation, edit/delete actions; edit page delete MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Search dropdown now supports arrow up/down to highlight items, Enter navigates to edit page of highlighted recipe - Each dropdown item shows edit (pencil) and delete (trash) buttons - Edit page now has a "Recept törlése" button with confirmation modal - Successful delete on edit page redirects back to /recipes Co-Authored-By: Claude Opus 4.6 --- app/templates/recipe_edit.html | 53 ++++++++++++++++ app/templates/recipes.html | 113 ++++++++++++++++++++++++++++----- 2 files changed, 150 insertions(+), 16 deletions(-) diff --git a/app/templates/recipe_edit.html b/app/templates/recipe_edit.html index b1bb42f..84aa81a 100644 --- a/app/templates/recipe_edit.html +++ b/app/templates/recipe_edit.html @@ -274,9 +274,22 @@
Mégsem +
+ + + {% endblock %} {% block scripts %} @@ -559,6 +572,46 @@ async function saveRecipe() { } } +/* ===== Delete ===== */ +function confirmDelete() { + const title = document.getElementById('recipeTitle').value.trim() || 'ezt a receptet'; + document.getElementById('deleteModalText').textContent = + 'Biztosan törölni szeretnéd a következő receptet: „' + title + '"? Ez a művelet nem vonható vissza!'; + document.getElementById('deleteModal').style.display = 'flex'; +} + +function closeDeleteModal() { + document.getElementById('deleteModal').style.display = 'none'; +} + +async function executeDelete() { + const btn = document.getElementById('confirmDeleteBtn'); + btn.disabled = true; + btn.innerHTML = ' Törlés...'; + + try { + const resp = await fetch('/api/recipes/' + BACKEND + '/delete', { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ ids: [RECIPE_ID] }), + }); + const data = await resp.json(); + if (data.ok) { + window.location.href = '/recipes'; + } else { + closeDeleteModal(); + btn.disabled = false; + btn.textContent = 'Törlés'; + showToast(data.error || 'Hiba történt.', 'error'); + } + } catch (e) { + closeDeleteModal(); + btn.disabled = false; + btn.textContent = 'Törlés'; + showToast('Hiba: ' + e.message, 'error'); + } +} + /* ===== Toast ===== */ function showToast(msg, type) { const el = document.createElement('div'); diff --git a/app/templates/recipes.html b/app/templates/recipes.html index b279138..61de469 100644 --- a/app/templates/recipes.html +++ b/app/templates/recipes.html @@ -62,11 +62,40 @@ padding: 0.5rem 0.75rem; cursor: pointer; font-size: 0.9rem; + display: flex; + align-items: center; + gap: 0.5rem; + } + .search-dropdown-item:hover, .search-dropdown-item.highlighted { background: var(--accent-glow); } + .search-dropdown-item .sdi-name { + flex: 1; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; } - .search-dropdown-item:hover { background: var(--accent-glow); } + .search-dropdown-item .sdi-actions { + display: flex; + gap: 0.3rem; + flex-shrink: 0; + } + .search-dropdown-item .sdi-actions button { + background: none; + border: none; + cursor: pointer; + font-size: 0.85rem; + padding: 0.15rem 0.35rem; + border-radius: 4px; + color: var(--text-dim); + transition: background 0.1s, color 0.1s; + } + .search-dropdown-item .sdi-actions button:hover { + background: var(--surface); + color: var(--text); + } + .search-dropdown-item .sdi-actions .sdi-delete:hover { + background: rgba(218,54,51,0.2); + color: #f85149; + } .tag-filter-wrap { position: relative; } @@ -365,6 +394,8 @@ let pendingDeleteIds = []; // set by confirmDelete / confirmBulkDelete let backendTags = []; // [{name, id}, ...] let activeFilterTagIds = []; let activeFilterTagNames = {}; // id -> name, for chip display +let searchSuggestions = []; // [{id, name}, ...] from last suggestion fetch +let searchHighlightIdx = -1; // -1 = none highlighted /* ===== Escaping ===== */ function escHtml(s) { @@ -393,23 +424,52 @@ function switchBackend(b) { /* ===== Search ===== */ function onSearchInput() { clearTimeout(searchTimeout); + searchHighlightIdx = -1; searchTimeout = setTimeout(() => { loadSearchSuggestions(); }, 300); } function onSearchKeydown(e) { - if (e.key === 'Enter') { + const dropdown = document.getElementById('searchDropdown'); + const isOpen = dropdown.classList.contains('open'); + + if (e.key === 'ArrowDown' && isOpen) { e.preventDefault(); - document.getElementById('searchDropdown').classList.remove('open'); - executeSearch(); + searchHighlightIdx = Math.min(searchHighlightIdx + 1, searchSuggestions.length - 1); + updateSearchHighlight(); + } else if (e.key === 'ArrowUp' && isOpen) { + e.preventDefault(); + searchHighlightIdx = Math.max(searchHighlightIdx - 1, -1); + updateSearchHighlight(); + } else if (e.key === 'Enter') { + e.preventDefault(); + if (isOpen && searchHighlightIdx >= 0 && searchHighlightIdx < searchSuggestions.length) { + // Navigate to edit page of highlighted item + const item = searchSuggestions[searchHighlightIdx]; + dropdown.classList.remove('open'); + window.location.href = '/recipes/' + currentBackend + '/' + encodeURIComponent(item.id) + '/edit'; + } else { + dropdown.classList.remove('open'); + executeSearch(); + } } else if (e.key === 'Escape') { - document.getElementById('searchDropdown').classList.remove('open'); + dropdown.classList.remove('open'); + searchHighlightIdx = -1; + } +} + +function updateSearchHighlight() { + const items = document.querySelectorAll('#searchDropdown .search-dropdown-item'); + items.forEach((el, i) => el.classList.toggle('highlighted', i === searchHighlightIdx)); + if (searchHighlightIdx >= 0 && items[searchHighlightIdx]) { + items[searchHighlightIdx].scrollIntoView({ block: 'nearest' }); } } function executeSearch() { document.getElementById('searchDropdown').classList.remove('open'); + searchHighlightIdx = -1; currentPage = 1; selectedIds.clear(); loadRecipes(); @@ -418,7 +478,7 @@ function executeSearch() { async function loadSearchSuggestions() { const q = document.getElementById('recipeSearch').value.trim(); const dropdown = document.getElementById('searchDropdown'); - if (!q) { dropdown.classList.remove('open'); return; } + if (!q) { dropdown.classList.remove('open'); searchSuggestions = []; return; } const params = new URLSearchParams({ page: 1, per_page: 8, search: q }); if (activeFilterTagIds.length) params.set('tag_ids', activeFilterTagIds.join(',')); @@ -428,24 +488,45 @@ async function loadSearchSuggestions() { const data = await resp.json(); if (!data.ok || data.items.length === 0) { dropdown.classList.remove('open'); + searchSuggestions = []; return; } - let html = ''; - for (const r of data.items) { - html += '
' + escHtml(r.name) + '
'; - } - dropdown.innerHTML = html; + searchSuggestions = data.items; + searchHighlightIdx = -1; + renderSearchDropdown(); dropdown.classList.add('open'); } catch (e) { dropdown.classList.remove('open'); + searchSuggestions = []; } } -function selectSearchItem(name) { - document.getElementById('recipeSearch').value = name; - document.getElementById('searchDropdown').classList.remove('open'); - executeSearch(); +function renderSearchDropdown() { + const dropdown = document.getElementById('searchDropdown'); + let html = ''; + searchSuggestions.forEach((r, i) => { + const editUrl = '/recipes/' + currentBackend + '/' + encodeURIComponent(r.id) + '/edit'; + const cls = i === searchHighlightIdx ? ' highlighted' : ''; + html += '
' + + '' + escHtml(r.name) + '' + + '' + + '' + + '' + + '
'; + }); + dropdown.innerHTML = html; +} + +function navigateToEdit(idx) { + if (idx >= 0 && idx < searchSuggestions.length) { + const item = searchSuggestions[idx]; + document.getElementById('searchDropdown').classList.remove('open'); + window.location.href = '/recipes/' + currentBackend + '/' + encodeURIComponent(item.id) + '/edit'; + } } /* ===== Tag filter ===== */