fix: tag UI — two-field design with active/inactive toggle

Scraped tags default to inactive (won't be added). Click a tag to move
it between active (green, will be imported) and inactive (muted, from
webpage). Manually added/searched tags go directly to active.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-02-24 12:49:53 +01:00
parent bbd0889471
commit f8772b20b7
+82 -41
View File
@@ -124,33 +124,41 @@
.add-btn:hover { border-color: var(--accent); color: var(--text); }
/* Tags */
.tag-section-label {
font-size: 0.8rem;
color: var(--text-dim);
font-weight: 600;
text-transform: uppercase;
letter-spacing: 0.03em;
margin-bottom: 0.3rem;
}
.tag-chips {
display: flex;
flex-wrap: wrap;
gap: 0.4rem;
min-height: 32px;
min-height: 28px;
}
.tag-chip {
display: inline-flex;
align-items: center;
gap: 0.3rem;
background: var(--accent);
color: #fff;
padding: 0.25rem 0.5rem;
padding: 0.25rem 0.6rem;
border-radius: 999px;
font-size: 0.85rem;
line-height: 1.2;
}
.tag-chip button {
background: none;
border: none;
color: rgba(255,255,255,0.7);
cursor: pointer;
font-size: 0.9rem;
padding: 0;
line-height: 1;
transition: opacity 0.15s;
user-select: none;
}
.tag-chip:hover { opacity: 0.8; }
.tag-chip.tag-active {
background: var(--success, #2ea043);
color: #fff;
}
.tag-chip.tag-inactive {
background: var(--surface2);
color: var(--text-dim);
border: 1px solid var(--border);
}
.tag-chip button:hover { color: #fff; }
.tag-search-wrap {
position: relative;
margin-top: 0.5rem;
@@ -245,12 +253,15 @@
<!-- Tags -->
<label>Címkék</label>
<div id="tagChips" class="tag-chips"></div>
<div class="tag-section-label">Hozzáadásra kerül:</div>
<div id="tagsActive" class="tag-chips mb-1"></div>
<div class="tag-search-wrap">
<input type="text" id="tagSearch" placeholder="Címke keresése / hozzáadása..."
autocomplete="off" oninput="onTagSearch()" onfocus="onTagSearch()" onkeydown="onTagKeydown(event)">
<div id="tagDropdown" class="tag-dropdown"></div>
</div>
<div class="tag-section-label" style="margin-top:0.6rem;">Weboldalról (kattints a hozzáadáshoz):</div>
<div id="tagsInactive" class="tag-chips"></div>
<div class="flex mt-2">
{% if has_mealie %}
@@ -352,9 +363,10 @@ function populatePreview(r) {
instList.innerHTML = '';
(r.instructions || []).forEach(t => addInstruction(t));
// Tags
document.getElementById('tagChips').innerHTML = '';
(r.tags || []).forEach(t => addTagChip(t));
// Tags — scraped tags go to inactive (user must opt-in)
document.getElementById('tagsActive').innerHTML = '';
document.getElementById('tagsInactive').innerHTML = '';
(r.tags || []).forEach(t => addTagChip(t, false));
}
function addIngredient(item) {
@@ -430,7 +442,7 @@ function gatherRecipe() {
});
const tags = [];
document.querySelectorAll('#tagChips .tag-chip').forEach(el => {
document.querySelectorAll('#tagsActive .tag-chip').forEach(el => {
tags.push(el.dataset.tag);
});
@@ -529,25 +541,54 @@ async function loadExistingTags() {
} catch (e) { /* ignore */ }
}
function addTagChip(name) {
name = name.trim();
if (!name) return;
// Avoid duplicates
const chips = document.getElementById('tagChips');
for (const c of chips.querySelectorAll('.tag-chip')) {
if (c.dataset.tag.toLowerCase() === name.toLowerCase()) return;
function tagExistsIn(name, containerId) {
const container = document.getElementById(containerId);
for (const c of container.querySelectorAll('.tag-chip')) {
if (c.dataset.tag.toLowerCase() === name.toLowerCase()) return true;
}
const chip = document.createElement('span');
chip.className = 'tag-chip';
chip.dataset.tag = name;
chip.innerHTML = escHtml(name) + ' <button onclick="this.parentElement.remove()">✕</button>';
chips.appendChild(chip);
return false;
}
function getActiveTags() {
const tags = [];
document.querySelectorAll('#tagChips .tag-chip').forEach(el => tags.push(el.dataset.tag.toLowerCase()));
return tags;
function addTagChip(name, active) {
name = name.trim();
if (!name) return;
// Avoid duplicates in both areas
if (tagExistsIn(name, 'tagsActive') || tagExistsIn(name, 'tagsInactive')) return;
const chip = document.createElement('span');
chip.dataset.tag = name;
chip.textContent = name;
chip.onclick = function() { toggleTag(this); };
if (active) {
chip.className = 'tag-chip tag-active';
document.getElementById('tagsActive').appendChild(chip);
} else {
chip.className = 'tag-chip tag-inactive';
document.getElementById('tagsInactive').appendChild(chip);
}
}
function toggleTag(chip) {
if (chip.classList.contains('tag-active')) {
// Move to inactive
chip.classList.remove('tag-active');
chip.classList.add('tag-inactive');
document.getElementById('tagsInactive').appendChild(chip);
} else {
// Move to active
chip.classList.remove('tag-inactive');
chip.classList.add('tag-active');
document.getElementById('tagsActive').appendChild(chip);
}
}
function getAllTagNames() {
const names = [];
document.querySelectorAll('#tagsActive .tag-chip, #tagsInactive .tag-chip').forEach(el => {
names.push(el.dataset.tag.toLowerCase());
});
return names;
}
function onTagSearch() {
@@ -557,7 +598,7 @@ function onTagSearch() {
if (!q) { dropdown.classList.remove('open'); return; }
const active = getActiveTags();
const allNames = getAllTagNames();
// Merge tags from both sources, track origin
const seen = {};
for (const t of existingTags.mealie || []) {
@@ -571,9 +612,9 @@ function onTagSearch() {
seen[k].sources.push('T');
}
// Filter by query, exclude already-added
// Filter by query, exclude already-present tags
const matches = Object.values(seen)
.filter(e => e.name.toLowerCase().includes(q) && !active.includes(e.name.toLowerCase()))
.filter(e => e.name.toLowerCase().includes(q) && !allNames.includes(e.name.toLowerCase()))
.slice(0, 10);
let html = '';
@@ -585,7 +626,7 @@ function onTagSearch() {
}
// "Add new" option if exact match not found
const exactExists = matches.some(m => m.name.toLowerCase() === q) || active.includes(q);
const exactExists = matches.some(m => m.name.toLowerCase() === q) || allNames.includes(q);
if (!exactExists && q) {
html += '<div class="tag-dropdown-item tag-add-new" onclick="selectTag(\'' + escHtml(input.value.trim()).replace(/'/g, "\\'") + '\')">'
+ '+ &quot;' + escHtml(input.value.trim()) + '&quot; hozzáadása</div>';
@@ -596,7 +637,7 @@ function onTagSearch() {
}
function selectTag(name) {
addTagChip(name);
addTagChip(name, true); // manually added → active
const input = document.getElementById('tagSearch');
input.value = '';
document.getElementById('tagDropdown').classList.remove('open');
@@ -608,7 +649,7 @@ function onTagKeydown(e) {
e.preventDefault();
const val = e.target.value.trim();
if (val) {
addTagChip(val);
addTagChip(val, true); // manually added → active
e.target.value = '';
document.getElementById('tagDropdown').classList.remove('open');
}