Compare commits
1
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a465912b55 |
+56
-4
@@ -797,6 +797,21 @@
|
||||
.toast.info .toast-icon { color: var(--info); }
|
||||
.toast.warn .toast-icon { color: var(--warn); }
|
||||
.toast-text { flex: 1; min-width: 0; word-break: break-word; }
|
||||
.toast-action {
|
||||
flex: 0 0 auto;
|
||||
padding: 4px 10px;
|
||||
background: var(--surface-3);
|
||||
border: 1px solid var(--border-light);
|
||||
color: var(--fg);
|
||||
border-radius: 4px;
|
||||
font-size: 0.85em;
|
||||
font-family: inherit;
|
||||
cursor: pointer;
|
||||
pointer-events: auto; /* BUG-06: Klick auf Action-Button muss trotz Toast-Click-Handler gehen */
|
||||
}
|
||||
.toast-action:hover { background: var(--accent); color: white; border-color: var(--accent); }
|
||||
.toast.success .toast-action { border-color: var(--success); color: var(--success); }
|
||||
.toast.success .toast-action:hover { background: var(--success); color: var(--bg); }
|
||||
@keyframes toastIn {
|
||||
from { opacity: 0; transform: translateX(20px); }
|
||||
to { opacity: 1; transform: translateX(0); }
|
||||
@@ -1135,12 +1150,25 @@
|
||||
// ============ Toast-Helper ============
|
||||
// Aufruf: toast("Item gelöscht", "success" | "error" | "info" | "warn")
|
||||
// Optional: toast(text, type, duration_ms) — duration 0 = manuell wegklicken
|
||||
function toast(text, type = "info", duration = 3500) {
|
||||
// Optional: toast(text, type, duration_ms, action={label, onClick})
|
||||
// — Toast wird mit action-Button gerendert, Click auf den Button ruft onClick
|
||||
// auf und dismissed den Toast.
|
||||
function toast(text, type = "info", duration = 3500, action = null) {
|
||||
const c = document.getElementById('toastContainer');
|
||||
const el = document.createElement('div');
|
||||
el.className = 'toast ' + type;
|
||||
const iconChar = { success: '✓', error: '✕', info: 'ⓘ', warn: '⚠' }[type] || 'ⓘ';
|
||||
el.innerHTML = `<div class="toast-icon">${iconChar}</div><div class="toast-text"></div>`;
|
||||
if (action) {
|
||||
el.innerHTML = `<div class="toast-icon">${iconChar}</div>
|
||||
<div class="toast-text"></div>
|
||||
<button class="toast-action">${action.label}</button>`;
|
||||
el.querySelector('.toast-action').addEventListener('click', (e) => {
|
||||
e.stopPropagation();
|
||||
try { action.onClick(); } finally { remove(); }
|
||||
});
|
||||
} else {
|
||||
el.innerHTML = `<div class="toast-icon">${iconChar}</div><div class="toast-text"></div>`;
|
||||
}
|
||||
el.querySelector('.toast-text').textContent = text;
|
||||
let removed = false;
|
||||
const remove = () => {
|
||||
@@ -1631,17 +1659,41 @@
|
||||
const ok = await modalConfirm({
|
||||
icon: 'warn',
|
||||
title: 'Alle Items neu anordnen?',
|
||||
body: 'Auto-Pack sortiert alle Widgets nach Größe in den 4×4-Grid. Bestehende manuelle Positionen gehen verloren.',
|
||||
body: 'Auto-Pack sortiert alle Widgets nach Größe in den 4×4-Grid. Bestehende manuelle Positionen gehen verloren. Du kannst den Schritt 10 Sekunden lang rückgängig machen.',
|
||||
confirmText: 'Neu anordnen',
|
||||
});
|
||||
if (!ok) return;
|
||||
// BUG-06: Snapshot der aktuellen Items in sessionStorage für Undo.
|
||||
// server gibt neue Items zurück; wir können das alte Layout wiederherstellen.
|
||||
const snapshot = JSON.stringify(layoutItems);
|
||||
const r = await fetch('/api/layout/pack', { method: 'POST' });
|
||||
const j = await r.json();
|
||||
if (!j.ok) { toast('Auto-Pack fehlgeschlagen: ' + (j.error || '?'), 'error', 5000); return; }
|
||||
layoutItems = j.items;
|
||||
renderGrid();
|
||||
debouncedSave();
|
||||
toast('Layout automatisch angeordnet', 'success');
|
||||
// BUG-06: Actionable Toast mit Undo-Button
|
||||
toast('Layout automatisch angeordnet', 'success', 10000, {
|
||||
label: 'Rückgängig',
|
||||
onClick: async () => {
|
||||
try {
|
||||
const restored = JSON.parse(snapshot);
|
||||
// restore via normalen Save-Endpunkt
|
||||
const r2 = await fetch('/api/layout', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ items: restored }),
|
||||
});
|
||||
const j2 = await r2.json();
|
||||
if (!j2.ok) { toast('Undo fehlgeschlagen: ' + (j2.error || '?'), 'error', 5000); return; }
|
||||
layoutItems = j2.items;
|
||||
renderGrid();
|
||||
toast('Layout wiederhergestellt', 'info', 3000);
|
||||
} catch (err) {
|
||||
toast('Undo Fehler: ' + err.message, 'error', 5000);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// ============ Plugin-Configs ============
|
||||
|
||||
@@ -0,0 +1,58 @@
|
||||
// BUG-06 Test: Auto-Pack Undo via Snapshot
|
||||
//
|
||||
// Erwartung:
|
||||
// 1) packAll() speichert layoutItems als JSON vor dem Pack
|
||||
// 2) Toast wird mit action={label:"Rückgängig", onClick} aufgerufen (statt nur Text)
|
||||
// 3) onClick sendet POST /api/layout mit den snapshot-Items
|
||||
// 4) Modal-Text erwähnt "10 Sekunden lang rückgängig"
|
||||
// 5) CSS: .toast-action Klasse vorhanden
|
||||
// 6) toast()-Funktion unterstützt action-Parameter (4-Args-Signatur)
|
||||
|
||||
const fs = require('fs');
|
||||
const html = fs.readFileSync('templates/index.html', 'utf8');
|
||||
const js = html.match(/<script>\s*\n([\s\S]*?)<\/script>/)[1];
|
||||
|
||||
function check(name, fn) {
|
||||
const r = fn();
|
||||
console.log((r ? '✓' : '✗') + ' ' + name);
|
||||
if (!r) process.exitCode = 1;
|
||||
}
|
||||
|
||||
// 1) Snapshot wird erstellt (JSON.stringify vor pack-Call)
|
||||
check('packAll speichert Snapshot (JSON.stringify(layoutItems))', () =>
|
||||
/async function packAll[\s\S]*?JSON\.stringify\(layoutItems\)/.test(js));
|
||||
|
||||
// 2) Toast mit action-Parameter
|
||||
check('Toast wird mit action-Parameter aufgerufen', () =>
|
||||
/toast\([^,]+,\s*['"]success['"],\s*\d+,\s*\{[\s\S]*?label:\s*['"]Rückgängig['"]/.test(js));
|
||||
|
||||
// 3) Undo-Click ruft POST /api/layout mit snapshot
|
||||
check('Undo ruft POST /api/layout mit snapshot-Items', () =>
|
||||
/fetch\(['"]\/api\/layout['"][\s\S]*?JSON\.parse\(snapshot\)/.test(js));
|
||||
|
||||
// 4) Modal-Body erwähnt Undo-Möglichkeit
|
||||
check('Modal-Body erwähnt "rückgängig"', () =>
|
||||
/rückgängig machen/i.test(js));
|
||||
|
||||
// 5) CSS: .toast-action Klasse
|
||||
check('.toast-action CSS-Klasse vorhanden', () => {
|
||||
const css = html.match(/<style>([\s\S]*?)<\/style>/g).join('\n');
|
||||
return /\.toast-action\s*\{/.test(css);
|
||||
});
|
||||
|
||||
// 6) toast() hat 4-Args Signatur
|
||||
check('toast() unterstützt 4-Args (action-Parameter)', () =>
|
||||
/function toast\(\s*text[^)]*\)\s*\{/.test(js) && /action\s*=/.test(js));
|
||||
|
||||
// 7) Undo-Button ruft bei Click onClick und dismissed Toast
|
||||
check('Action-Button: Click ruft onClick + dismiss', () =>
|
||||
/\.toast-action['"]\)\.addEventListener\(['"]click['"][\s\S]*?action\.onClick\(\)/.test(js));
|
||||
|
||||
// 8) Pointer-events:auto auf Action-Button (sonst klickt Toast-Click-Handler)
|
||||
check('.toast-action hat pointer-events:auto', () => {
|
||||
const css = html.match(/<style>([\s\S]*?)<\/style>/g).join('\n');
|
||||
return /\.toast-action\s*\{[^}]*pointer-events:\s*auto/.test(css);
|
||||
});
|
||||
|
||||
console.log('\n========');
|
||||
process.exit(process.exitCode || 0);
|
||||
Reference in New Issue
Block a user