Cache crossed-out ingredients & steps in recipe detail view with reset buttons
This commit is contained in:
@@ -633,11 +633,41 @@
|
||||
margin: 0 0 12px 0;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: 8px;
|
||||
border-bottom: 1.5px solid var(--altrosa);
|
||||
padding-bottom: 4px;
|
||||
}
|
||||
|
||||
.cb-reset-checks-btn {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 4px;
|
||||
background: rgba(30, 77, 59, 0.05);
|
||||
border: 1px dashed rgba(30, 77, 59, 0.35);
|
||||
color: var(--tannengruen);
|
||||
font-family: var(--font-hand);
|
||||
font-size: 0.92rem;
|
||||
font-weight: 600;
|
||||
padding: 2px 9px;
|
||||
border-radius: 12px;
|
||||
cursor: pointer;
|
||||
transition: all 0.2s ease;
|
||||
line-height: 1;
|
||||
user-select: none;
|
||||
-webkit-tap-highlight-color: transparent;
|
||||
}
|
||||
|
||||
.cb-reset-checks-btn:hover {
|
||||
background: rgba(30, 77, 59, 0.12);
|
||||
border-color: var(--tannengruen);
|
||||
border-style: solid;
|
||||
}
|
||||
|
||||
.cb-reset-checks-btn:active {
|
||||
transform: scale(0.96);
|
||||
}
|
||||
|
||||
.ingredients-list {
|
||||
list-style: none;
|
||||
padding: 0;
|
||||
@@ -2572,6 +2602,21 @@ body.cook-mode-on .cookmode-text-box .cookmode-status-text {
|
||||
background: rgba(34, 34, 34, 0.04);
|
||||
}
|
||||
|
||||
.matrix-row {
|
||||
cursor: pointer;
|
||||
transition: background 0.15s ease;
|
||||
}
|
||||
|
||||
.matrix-row:hover {
|
||||
background: rgba(30, 77, 59, 0.05);
|
||||
}
|
||||
|
||||
.matrix-row.is-checked td {
|
||||
text-decoration: line-through;
|
||||
opacity: 0.45;
|
||||
color: #777;
|
||||
}
|
||||
|
||||
/* Interactive step completion in cook mode */
|
||||
.step-card {
|
||||
cursor: pointer;
|
||||
@@ -2740,6 +2785,7 @@ body.cook-mode-on .cookmode-text-box .cookmode-status-text {
|
||||
.cookmode-toggle-btn,
|
||||
.cb-nav-cookmode-btn,
|
||||
.cb-mobile-cookmode-item,
|
||||
.cb-reset-checks-btn,
|
||||
.recipe-cookmode-banner,
|
||||
.step-done-check,
|
||||
.no-print {
|
||||
|
||||
@@ -60,6 +60,9 @@
|
||||
</div>
|
||||
<span id="cookmode-mobile-badge" class="cb-badge-pill">Aus</span>
|
||||
</button>
|
||||
<button type="button" id="reset-all-menu-btn" onclick="resetAllChecks()" class="cb-mobile-item no-print" style="display: none;">
|
||||
<span>↺</span> <span>Abgehaktes zurücksetzen</span>
|
||||
</button>
|
||||
<hr class="cb-mobile-divider">
|
||||
{% if not is_shared %}
|
||||
<button type="button" onclick="openShareModal()" class="cb-mobile-item">
|
||||
@@ -189,7 +192,12 @@
|
||||
|
||||
<!-- Zutatenleiste ohne Portionsangabe (da schon in Kopfzeile) -->
|
||||
<div class="ingredients-box {% if recipe.has_portion_table %}has-portion-matrix{% endif %}">
|
||||
<h4 class="section-hand-title">Zutaten</h4>
|
||||
<h4 class="section-hand-title">
|
||||
<span>Zutaten</span>
|
||||
<button type="button" id="reset-ingredients-btn" class="cb-reset-checks-btn no-print" onclick="resetIngredientChecks()" title="Abgehakte Zutaten zurücksetzen" style="display: none;">
|
||||
<span>↺</span> <span>Zurücksetzen</span>
|
||||
</button>
|
||||
</h4>
|
||||
|
||||
{% if recipe.has_portion_table and recipe.portion_matrix %}
|
||||
<!-- Tabelle für 2/4/6/8 Portionen (z. B. Waffeln / Pancakes) -->
|
||||
@@ -210,7 +218,7 @@
|
||||
<th colspan="5">{{ row.text }}</th>
|
||||
</tr>
|
||||
{% else %}
|
||||
<tr class="matrix-row">
|
||||
<tr class="matrix-row" onclick="toggleIngredientCheck(this)" title="Tippen zum Abhaken">
|
||||
<td class="matrix-td-name">{{ row.name }}</td>
|
||||
<td class="matrix-td-amt">{{ row.amounts[2] }}</td>
|
||||
<td class="matrix-td-amt highlight">{{ row.amounts[4] }}</td>
|
||||
@@ -242,7 +250,12 @@
|
||||
<!-- Rechte Spalte: Zubereitung + Notizen + Quelle -->
|
||||
<section class="recipe-main">
|
||||
<div>
|
||||
<h4 class="section-hand-title">Zubereitung</h4>
|
||||
<h4 class="section-hand-title">
|
||||
<span>Zubereitung</span>
|
||||
<button type="button" id="reset-steps-btn" class="cb-reset-checks-btn no-print" onclick="resetStepChecks()" title="Erledigte Schritte zurücksetzen" style="display: none;">
|
||||
<span>↺</span> <span>Zurücksetzen</span>
|
||||
</button>
|
||||
</h4>
|
||||
<div class="instructions-steps">
|
||||
{% for step in recipe.instructions.split('\n') %}
|
||||
{% if step.strip() %}
|
||||
@@ -717,6 +730,7 @@
|
||||
|
||||
// Gespeicherten Zustand beim Laden prüfen
|
||||
window.addEventListener('DOMContentLoaded', () => {
|
||||
loadChecksFromStorage();
|
||||
try {
|
||||
const saved = localStorage.getItem('wtc_cookmode_active') === 'true';
|
||||
if (saved) {
|
||||
@@ -735,14 +749,142 @@
|
||||
} catch (e) {}
|
||||
});
|
||||
|
||||
// ==========================================
|
||||
// INTERAKTIVES ABHAKEN (ZUTATEN & SCHRITTE) MIT ZWISCHENSPEICHER
|
||||
// ==========================================
|
||||
const RECIPE_CHECK_STORAGE_KEY = 'wtc_recipe_checks_{{ recipe.id }}';
|
||||
|
||||
function getCheckableIngredients() {
|
||||
return Array.from(document.querySelectorAll('.ingredients-list .ingredient-item, .portion-matrix-table .matrix-row'));
|
||||
}
|
||||
|
||||
function getCheckableSteps() {
|
||||
return Array.from(document.querySelectorAll('.instructions-steps .step-card'));
|
||||
}
|
||||
|
||||
function updateResetButtonsVisibility(hasIngChecks, hasStepChecks) {
|
||||
const ingResetBtn = document.getElementById('reset-ingredients-btn');
|
||||
if (ingResetBtn) ingResetBtn.style.display = hasIngChecks ? 'inline-flex' : 'none';
|
||||
|
||||
const stepResetBtn = document.getElementById('reset-steps-btn');
|
||||
if (stepResetBtn) stepResetBtn.style.display = hasStepChecks ? 'inline-flex' : 'none';
|
||||
|
||||
const menuResetBtn = document.getElementById('reset-all-menu-btn');
|
||||
if (menuResetBtn) menuResetBtn.style.display = (hasIngChecks || hasStepChecks) ? 'flex' : 'none';
|
||||
}
|
||||
|
||||
function saveChecksToStorage() {
|
||||
try {
|
||||
const ingEls = getCheckableIngredients();
|
||||
const stepEls = getCheckableSteps();
|
||||
|
||||
const checkedIngredients = [];
|
||||
ingEls.forEach((el, idx) => {
|
||||
if (el.classList.contains('is-checked')) checkedIngredients.push(idx);
|
||||
});
|
||||
|
||||
const checkedSteps = [];
|
||||
stepEls.forEach((el, idx) => {
|
||||
if (el.classList.contains('is-done')) checkedSteps.push(idx);
|
||||
});
|
||||
|
||||
const data = {
|
||||
recipeId: {{ recipe.id }},
|
||||
ingredients: checkedIngredients,
|
||||
steps: checkedSteps,
|
||||
updatedAt: Date.now()
|
||||
};
|
||||
|
||||
const json = JSON.stringify(data);
|
||||
sessionStorage.setItem(RECIPE_CHECK_STORAGE_KEY, json);
|
||||
localStorage.setItem(RECIPE_CHECK_STORAGE_KEY, json);
|
||||
|
||||
updateResetButtonsVisibility(checkedIngredients.length > 0, checkedSteps.length > 0);
|
||||
} catch (e) {
|
||||
console.warn('Fehler beim Speichern des Abhake-Status:', e);
|
||||
}
|
||||
}
|
||||
|
||||
function loadChecksFromStorage() {
|
||||
try {
|
||||
let raw = sessionStorage.getItem(RECIPE_CHECK_STORAGE_KEY);
|
||||
if (!raw) {
|
||||
raw = localStorage.getItem(RECIPE_CHECK_STORAGE_KEY);
|
||||
}
|
||||
if (!raw) {
|
||||
updateResetButtonsVisibility(false, false);
|
||||
return;
|
||||
}
|
||||
|
||||
const data = JSON.parse(raw);
|
||||
// Nach 8 Stunden verfällt der Zwischenspeicher automatisch
|
||||
const EIGHT_HOURS = 8 * 60 * 60 * 1000;
|
||||
if (data.updatedAt && (Date.now() - data.updatedAt > EIGHT_HOURS)) {
|
||||
sessionStorage.removeItem(RECIPE_CHECK_STORAGE_KEY);
|
||||
localStorage.removeItem(RECIPE_CHECK_STORAGE_KEY);
|
||||
updateResetButtonsVisibility(false, false);
|
||||
return;
|
||||
}
|
||||
|
||||
const ingEls = getCheckableIngredients();
|
||||
if (Array.isArray(data.ingredients)) {
|
||||
data.ingredients.forEach(idx => {
|
||||
if (ingEls[idx]) ingEls[idx].classList.add('is-checked');
|
||||
});
|
||||
}
|
||||
|
||||
const stepEls = getCheckableSteps();
|
||||
if (Array.isArray(data.steps)) {
|
||||
data.steps.forEach(idx => {
|
||||
if (stepEls[idx]) stepEls[idx].classList.add('is-done');
|
||||
});
|
||||
}
|
||||
|
||||
const hasIngChecks = Array.isArray(data.ingredients) && data.ingredients.length > 0;
|
||||
const hasStepChecks = Array.isArray(data.steps) && data.steps.length > 0;
|
||||
updateResetButtonsVisibility(hasIngChecks, hasStepChecks);
|
||||
} catch (e) {
|
||||
console.warn('Fehler beim Laden des Abhake-Status:', e);
|
||||
}
|
||||
}
|
||||
|
||||
// Interaktive Zutaten-Checkliste (zum Abhaken beim Vorbereiten)
|
||||
function toggleIngredientCheck(el) {
|
||||
el.classList.toggle('is-checked');
|
||||
saveChecksToStorage();
|
||||
}
|
||||
|
||||
// Interaktiver Schritt-Tracker (zum Markieren erledigter Schritte)
|
||||
function toggleStepCheck(el) {
|
||||
el.classList.toggle('is-done');
|
||||
saveChecksToStorage();
|
||||
}
|
||||
|
||||
function resetIngredientChecks() {
|
||||
const ingEls = getCheckableIngredients();
|
||||
ingEls.forEach(el => el.classList.remove('is-checked'));
|
||||
saveChecksToStorage();
|
||||
showToast('Zutaten zurückgesetzt');
|
||||
}
|
||||
|
||||
function resetStepChecks() {
|
||||
const stepEls = getCheckableSteps();
|
||||
stepEls.forEach(el => el.classList.remove('is-done'));
|
||||
saveChecksToStorage();
|
||||
showToast('Schritte zurückgesetzt');
|
||||
}
|
||||
|
||||
function resetAllChecks() {
|
||||
const ingEls = getCheckableIngredients();
|
||||
ingEls.forEach(el => el.classList.remove('is-checked'));
|
||||
const stepEls = getCheckableSteps();
|
||||
stepEls.forEach(el => el.classList.remove('is-done'));
|
||||
try {
|
||||
sessionStorage.removeItem(RECIPE_CHECK_STORAGE_KEY);
|
||||
localStorage.removeItem(RECIPE_CHECK_STORAGE_KEY);
|
||||
} catch (e) {}
|
||||
updateResetButtonsVisibility(false, false);
|
||||
showToast('Alles zurückgesetzt');
|
||||
}
|
||||
|
||||
// Toast Benachrichtigung
|
||||
|
||||
Reference in New Issue
Block a user