compact filter list: one row per rule, smaller global type scale
Pico scales the root font up to 125% on wide screens, which made the whole admin UI oversized. Pin it to 93.75% and tighten spacing tokens. The filter list is now a dense table-like list: switch, priority, name, condition chips, target folder, match stats and icon buttons on a single row (~35 rules fit on one screen instead of ~4). Warnings collapse into a ⚠ icon with tooltip; attachment and date conditions render as short chips; the full condition list is in the row tooltip; double-click opens the rule. Account picker, summary and main actions share one header row. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
+23
-3
@@ -3,8 +3,23 @@
|
||||
:root {
|
||||
--app-bg: #f6f7f9;
|
||||
--app-border: #e3e6ea;
|
||||
/* Pico skaliert die Schrift auf breiten Bildschirmen bis 125% hoch —
|
||||
für eine Verwaltungsoberfläche viel zu groß. Fest und kompakter. */
|
||||
--pico-font-size: 93.75%;
|
||||
--pico-line-height: 1.45;
|
||||
--pico-spacing: 0.9rem;
|
||||
--pico-form-element-spacing-vertical: 0.45rem;
|
||||
--pico-form-element-spacing-horizontal: 0.7rem;
|
||||
--pico-typography-spacing-vertical: 0.8rem;
|
||||
--pico-block-spacing-vertical: 1rem;
|
||||
--pico-block-spacing-horizontal: 1rem;
|
||||
}
|
||||
|
||||
h1 { font-size: 1.6rem; }
|
||||
h2 { font-size: 1.3rem; }
|
||||
h3 { font-size: 1.1rem; }
|
||||
h4 { font-size: 1rem; }
|
||||
|
||||
body {
|
||||
background: var(--app-bg);
|
||||
}
|
||||
@@ -12,9 +27,14 @@ body {
|
||||
nav.container-fluid {
|
||||
background: #fff;
|
||||
border-bottom: 1px solid var(--app-border);
|
||||
padding-top: 0.25rem;
|
||||
padding-bottom: 0.25rem;
|
||||
margin-bottom: 1.5rem;
|
||||
padding-top: 0;
|
||||
padding-bottom: 0;
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
nav li {
|
||||
padding-top: 0.6rem;
|
||||
padding-bottom: 0.6rem;
|
||||
}
|
||||
|
||||
nav .brand {
|
||||
|
||||
+114
-93
@@ -2,19 +2,18 @@
|
||||
{% block title %}Filterregeln — IMAP Mail Filter{% endblock %}
|
||||
{% block content %}
|
||||
<div class="page-head">
|
||||
<div>
|
||||
<h1>Filterregeln</h1>
|
||||
<p class="muted">Regeln werden von oben nach unten (niedrige Priorität zuerst) auf neue Mails angewendet.</p>
|
||||
<h1>Filterregeln</h1>
|
||||
<select id="account-select" class="account-pick" onchange="onAccountChange()" title="Konto">
|
||||
<option value="">— Konto wählen —</option>
|
||||
{% for acc in accounts %}
|
||||
<option value="{{ acc.id }}" {{ 'selected' if selected_account_id == acc.id else '' }}>{{ acc.name }}</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
<span id="filters-summary" class="muted small-text"></span>
|
||||
<div class="toolbar-actions">
|
||||
<button class="secondary outline" onclick="resetAllProcessed()" title="Verwirft den 'verarbeitet'-Marker aller Mails. Beim nächsten Poll werden alle Mails im Ordner neu gegen alle Regeln geprüft. Sicher — schon verschobene Mails liegen nicht mehr in der Quelle.">Alle neu prüfen</button>
|
||||
<button onclick="openNewFilter()">+ Neue Regel</button>
|
||||
</div>
|
||||
<label class="account-pick">
|
||||
Konto
|
||||
<select id="account-select" onchange="onAccountChange()">
|
||||
<option value="">— Konto wählen —</option>
|
||||
{% for acc in accounts %}
|
||||
<option value="{{ acc.id }}" {{ 'selected' if selected_account_id == acc.id else '' }}>{{ acc.name }}</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div id="toolbar" class="toolbar" hidden>
|
||||
@@ -26,13 +25,7 @@
|
||||
<button data-q="warn" onclick="setQuick('warn')">⚠ Warnungen</button>
|
||||
<button data-q="never" onclick="setQuick('never')">Nie getroffen</button>
|
||||
</div>
|
||||
<div class="toolbar-actions">
|
||||
<button onclick="openNewFilter()">+ Neue Regel</button>
|
||||
<button class="secondary outline" onclick="resetAllProcessed()" title="Verwirft den 'verarbeitet'-Marker aller Mails. Beim nächsten Poll werden alle Mails im Ordner neu gegen alle Regeln geprüft. Sicher — schon verschobene Mails liegen nicht mehr in der Quelle.">Alle neu prüfen</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="filters-summary" class="muted small-text"></div>
|
||||
<div id="filters-container">
|
||||
<p class="muted">Bitte ein Konto auswählen.</p>
|
||||
</div>
|
||||
@@ -121,50 +114,55 @@
|
||||
|
||||
{% block scripts %}
|
||||
<style>
|
||||
.page-head { display:flex; justify-content:space-between; align-items:flex-end; gap:1rem; flex-wrap:wrap; margin-bottom:1rem; }
|
||||
.page-head h1 { margin-bottom:0.25rem; }
|
||||
.page-head p { margin:0; }
|
||||
.account-pick { min-width:240px; margin-bottom:0; }
|
||||
.account-pick select { margin-bottom:0; }
|
||||
.muted { color: var(--pico-muted-color); }
|
||||
.small-text { font-size:0.85rem; }
|
||||
|
||||
.toolbar { display:flex; gap:0.6rem; align-items:center; flex-wrap:wrap; margin-bottom:0.75rem; }
|
||||
.toolbar input[type=search] { flex:1 1 320px; margin-bottom:0; }
|
||||
.toolbar button { margin-bottom:0; padding:0.45rem 0.9rem; font-size:0.9rem; }
|
||||
.toolbar-actions { display:flex; gap:0.5rem; }
|
||||
.seg { display:inline-flex; flex-wrap:wrap; order:3; border:1px solid var(--app-border, #e3e6ea); border-radius:var(--pico-border-radius); overflow:hidden; background:#fff; }
|
||||
.page-head { display:flex; align-items:center; gap:0.75rem; flex-wrap:wrap; margin-bottom:0.75rem; }
|
||||
.page-head h1 { margin:0; }
|
||||
.page-head .account-pick { width:auto; min-width:200px; margin:0; padding-top:0.3rem; padding-bottom:0.3rem; font-size:0.9rem; }
|
||||
.page-head #filters-summary { flex:1; }
|
||||
.toolbar-actions { display:flex; gap:0.4rem; }
|
||||
.toolbar-actions button { margin:0; padding:0.35rem 0.8rem; font-size:0.85rem; }
|
||||
|
||||
.toolbar { display:flex; gap:0.5rem; align-items:center; flex-wrap:wrap; margin-bottom:0.6rem; }
|
||||
.toolbar input[type=search] { flex:1 1 280px; margin:0; padding-top:0.3rem; padding-bottom:0.3rem; font-size:0.9rem; }
|
||||
.seg { display:inline-flex; flex-wrap:wrap; border:1px solid var(--app-border, #e3e6ea); border-radius:var(--pico-border-radius); overflow:hidden; background:#fff; }
|
||||
.toolbar .seg button { --pico-background-color:transparent; --pico-border-color:transparent; --pico-color:#374151;
|
||||
border-radius:0; padding:0.35rem 0.8rem; font-size:0.85rem; box-shadow:none; }
|
||||
margin:0; border-radius:0; padding:0.3rem 0.7rem; font-size:0.8rem; box-shadow:none; }
|
||||
.toolbar .seg button + button { border-left:1px solid var(--app-border, #e3e6ea); }
|
||||
.toolbar .seg button:hover { --pico-background-color:#f1f3f5; }
|
||||
.toolbar .seg button.active { --pico-background-color:var(--pico-primary); --pico-color:var(--pico-primary-inverse); }
|
||||
|
||||
.rule-list { display:flex; flex-direction:column; gap:0.5rem; }
|
||||
.rule { display:grid; grid-template-columns:auto 1fr auto; gap:0.75rem 1rem; align-items:start;
|
||||
padding:0.75rem 1rem; border:1px solid var(--pico-muted-border-color); border-radius:var(--pico-border-radius);
|
||||
background:var(--pico-card-background-color); }
|
||||
.rule.disabled { opacity:0.55; }
|
||||
.rule.has-warn { border-left:4px solid #e0a800; }
|
||||
.rule .toggle { margin:0.2rem 0 0; }
|
||||
.rule-title { display:flex; align-items:center; gap:0.5rem; flex-wrap:wrap; font-weight:600; }
|
||||
.prio { font-size:0.75rem; font-weight:600; padding:0.05rem 0.45rem; border-radius:999px; background:var(--pico-muted-border-color); color:var(--pico-muted-color); }
|
||||
.badge { font-size:0.72rem; font-weight:600; padding:0.05rem 0.45rem; border-radius:999px; }
|
||||
.badge.stop { background:#fde2e1; color:#9b1c1c; }
|
||||
.badge.folder { background:#e8eefc; color:#274b9f; font-weight:500; }
|
||||
.chips { display:flex; flex-wrap:wrap; gap:0.35rem; margin-top:0.4rem; }
|
||||
.chip { font-size:0.8rem; padding:0.1rem 0.5rem; border-radius:999px; background:#f1f3f5; border:1px solid #e3e6ea; white-space:nowrap; max-width:100%; overflow:hidden; text-overflow:ellipsis; }
|
||||
/* Kompakte Zeilenliste: eine Zeile pro Regel, feste Spalten */
|
||||
.rules { background:#fff; border:1px solid var(--app-border, #e3e6ea); border-radius:var(--pico-border-radius); overflow:hidden; font-size:0.85rem; }
|
||||
.rules .r { display:grid; grid-template-columns:34px 38px minmax(140px, 1.1fr) minmax(0, 2.4fr) minmax(120px, 1fr) 118px 84px;
|
||||
gap:0.6rem; align-items:center; padding:0.3rem 0.75rem; border-bottom:1px solid #eef0f3; min-height:36px; }
|
||||
.rules .r:last-child { border-bottom:0; }
|
||||
.rules .r:hover { background:#f8f9fb; }
|
||||
.rules .r.head { background:#f6f7f9; font-size:0.72rem; font-weight:600; text-transform:uppercase; letter-spacing:0.03em; color:var(--pico-muted-color); min-height:0; padding-top:0.35rem; padding-bottom:0.35rem; }
|
||||
.rules .r.head:hover { background:#f6f7f9; }
|
||||
.rules .r.disabled > *:not(.c-toggle):not(.c-btns) { opacity:0.45; }
|
||||
.rules .r.warn { box-shadow:inset 3px 0 0 #e0a800; }
|
||||
.rules input[role=switch] { margin:0; width:1.9em; height:1.05em; }
|
||||
.c-prio { color:var(--pico-muted-color); font-variant-numeric:tabular-nums; text-align:right; }
|
||||
.c-name { font-weight:600; overflow:hidden; text-overflow:ellipsis; white-space:nowrap; }
|
||||
.c-name .warn-ico { cursor:help; margin-left:0.25rem; }
|
||||
.c-name .tag { font-size:0.68rem; font-weight:600; padding:0 0.35rem; border-radius:3px; margin-left:0.3rem; vertical-align:1px; }
|
||||
.tag.stop { background:#fde2e1; color:#9b1c1c; }
|
||||
.tag.src { background:#e8eefc; color:#274b9f; }
|
||||
.c-cond { display:flex; gap:0.25rem; overflow:hidden; white-space:nowrap; mask-image:linear-gradient(to right, #000 92%, transparent); }
|
||||
.chip { font-size:0.75rem; padding:0 0.4rem; border-radius:3px; background:#f1f3f5; border:1px solid #e6e9ed; white-space:nowrap; line-height:1.55; }
|
||||
.chip b { font-weight:600; }
|
||||
.chip.neg { background:#fff4e5; border-color:#ffd8a8; }
|
||||
.chip.action { background:#e6f6ec; border-color:#b7e4c7; }
|
||||
.arrow { color:var(--pico-muted-color); font-size:0.85rem; align-self:center; }
|
||||
.warn-line { margin-top:0.4rem; font-size:0.8rem; color:#8a6100; }
|
||||
.rule-side { text-align:right; display:flex; flex-direction:column; align-items:flex-end; gap:0.4rem; }
|
||||
.stat { font-size:0.8rem; color:var(--pico-muted-color); white-space:nowrap; }
|
||||
.stat strong { color:var(--pico-color); }
|
||||
.stat.never { color:#b36b00; }
|
||||
.rule-buttons { display:flex; gap:0.35rem; }
|
||||
.rule-buttons button { margin:0; padding:0.25rem 0.6rem; font-size:0.8rem; }
|
||||
.c-target { overflow:hidden; text-overflow:ellipsis; white-space:nowrap; color:#1f6f43; }
|
||||
.c-stat { font-size:0.78rem; color:var(--pico-muted-color); white-space:nowrap; text-align:right; font-variant-numeric:tabular-nums; }
|
||||
.c-stat strong { color:var(--pico-color); }
|
||||
.c-stat.never { color:#b36b00; }
|
||||
.c-btns { display:flex; justify-content:flex-end; gap:0.1rem; }
|
||||
.c-btns button { --pico-background-color:transparent; --pico-border-color:transparent; --pico-color:var(--pico-muted-color);
|
||||
margin:0; padding:0.15rem 0.35rem; font-size:0.95rem; line-height:1; box-shadow:none; }
|
||||
.c-btns button:hover { --pico-background-color:#eef0f3; --pico-color:var(--pico-color); }
|
||||
.c-btns button.del:hover { --pico-color:#b42318; }
|
||||
|
||||
dialog article.rule-dialog { width:min(960px, 96vw); max-width:none; }
|
||||
.dlg-section { margin-bottom:1.25rem; }
|
||||
@@ -206,9 +204,13 @@ dialog article.rule-dialog { width:min(960px, 96vw); max-width:none; }
|
||||
.folder-new-input input { margin-bottom:0; padding:0.3rem 0.5rem; font-size:0.9rem; height:auto; }
|
||||
.folder-new-input button { margin-bottom:0; padding:0.3rem 0.6rem; font-size:0.85rem; white-space:nowrap; }
|
||||
|
||||
@media (max-width: 1000px) {
|
||||
.rules .r { grid-template-columns:34px minmax(0,1fr) 84px; }
|
||||
.rules .r .c-prio, .rules .r .c-stat, .rules .r.head { display:none; }
|
||||
.rules .r .c-cond, .rules .r .c-target { grid-column:2 / 3; }
|
||||
.rules .r .c-btns { grid-row:1; grid-column:3; }
|
||||
}
|
||||
@media (max-width: 760px) {
|
||||
.rule { grid-template-columns:auto 1fr; }
|
||||
.rule-side { grid-column:1 / -1; flex-direction:row; justify-content:space-between; align-items:center; }
|
||||
.grid-3 { grid-template-columns:1fr; }
|
||||
.grid-3 .span-2 { grid-column:auto; }
|
||||
.switches { padding-top:0; flex-direction:row; gap:1rem; }
|
||||
@@ -354,22 +356,50 @@ function filterHaystack(f) {
|
||||
return parts.join(' ').toLowerCase();
|
||||
}
|
||||
|
||||
function condChip(c) {
|
||||
// Kurzform einer Bedingung für die Listenzeile, z.B. "Von: billing@hetzner.com",
|
||||
// "Betreff = Rechnung", "📎", "ab 01.01.26"
|
||||
const COND_ORDER = {from: 0, to: 1, subject: 2, body: 3, has_attachment: 4, date: 5};
|
||||
const DATE_SHORT = {after: 'ab', before: 'vor', on_date: 'am', date_range: '', year: 'Jahr',
|
||||
last_n_days: 'letzte Tage:', last_n_weeks: 'letzte Wochen:', last_n_months: 'letzte Monate:',
|
||||
older_than_days: 'älter Tage:', older_than_weeks: 'älter Wochen:', older_than_months: 'älter Monate:'};
|
||||
|
||||
function condText(c) {
|
||||
const neg = c.negate ? 'NICHT ' : '';
|
||||
if (c.field === 'has_attachment') {
|
||||
const yes = (c.value === 'true') !== !!c.negate;
|
||||
return `<span class="chip">${yes ? '📎 mit Anhang' : 'ohne Anhang'}</span>`;
|
||||
return ((c.value === 'true') !== !!c.negate) ? '📎 Anhang' : 'ohne Anhang';
|
||||
}
|
||||
if (c.field === 'date') {
|
||||
return `<span class="chip${c.negate ? ' neg' : ''}">${neg}Datum <b>${esc(matchLabel(c.match_type))}</b> ${esc(fmtDateValue(c.value))}</span>`;
|
||||
}
|
||||
return `<span class="chip${c.negate ? ' neg' : ''}" title="${esc(c.value)}">${neg}${esc(fieldLabel(c.field))} ${esc(matchLabel(c.match_type))} <b>${esc(c.value)}</b></span>`;
|
||||
if (c.field === 'date') return `${neg}${DATE_SHORT[c.match_type] ?? matchLabel(c.match_type)} ${fmtDateValue(c.value)}`.trim();
|
||||
const op = c.match_type === 'exact' ? ' =' : c.match_type === 'regex' ? ' ~' : ':';
|
||||
return `${neg}${fieldLabel(c.field)}${op} ${c.value}`;
|
||||
}
|
||||
|
||||
function actChip(a) {
|
||||
if (a.action_type === 'move') return `<span class="chip action">📁 <b>${esc(a.parameter || '?')}</b></span>`;
|
||||
if (a.action_type === 'forward') return `<span class="chip action">↪ an <b>${esc(a.parameter || '?')}</b></span>`;
|
||||
return `<span class="chip action">${esc(actionLabel(a.action_type))}</span>`;
|
||||
function condChip(c) {
|
||||
// Häufige Bedingungen in der Liste platzsparend darstellen
|
||||
if (c.field === 'has_attachment') {
|
||||
const yes = (c.value === 'true') !== !!c.negate;
|
||||
return `<span class="chip" title="${yes ? 'mit Anhang' : 'ohne Anhang'}">${yes ? '📎' : '🚫📎'}</span>`;
|
||||
}
|
||||
if (c.field === 'date') {
|
||||
const short = condText(c).replace(/(\d{2})\.(\d{2})\.\d{2}(\d{2})/g, '$1.$2.$3');
|
||||
const warn = c.match_type === 'on_date' ? ' style="background:#fff3cd;border-color:#ffe08a"' : '';
|
||||
return `<span class="chip${c.negate ? ' neg' : ''}"${warn} title="${esc(condText(c))}">${esc(short)}</span>`;
|
||||
}
|
||||
const text = condText(c);
|
||||
const i = text.indexOf(': ');
|
||||
const html = i > 0 && c.field !== 'date' ? `${esc(text.slice(0, i + 1))} <b>${esc(text.slice(i + 2))}</b>` : esc(text);
|
||||
const cls = c.negate ? ' neg' : '';
|
||||
const warn = c.field === 'date' && c.match_type === 'on_date' ? ' style="background:#fff3cd;border-color:#ffe08a"' : '';
|
||||
return `<span class="chip${cls}"${warn}>${html}</span>`;
|
||||
}
|
||||
|
||||
function targetText(f) {
|
||||
return (f.actions || []).map(a => {
|
||||
if (a.action_type === 'move') return '📁 ' + (a.parameter || '?').replace(/^INBOX[./]/, '');
|
||||
if (a.action_type === 'forward') return '↪ ' + (a.parameter || '?');
|
||||
if (a.action_type === 'delete') return '🗑 Papierkorb';
|
||||
if (a.action_type === 'mark_read') return '✓ gelesen';
|
||||
return a.action_type;
|
||||
}).join(', ') || '—';
|
||||
}
|
||||
|
||||
function renderFilters() {
|
||||
@@ -403,39 +433,30 @@ function renderFilters() {
|
||||
return;
|
||||
}
|
||||
|
||||
let html = '<div class="rule-list">';
|
||||
let html = `<div class="rules">
|
||||
<div class="r head"><span></span><span style="text-align:right">Prio</span><span>Name</span><span>Bedingungen</span><span>Ziel</span><span style="text-align:right">Treffer</span><span></span></div>`;
|
||||
for (const {f, warnings} of visible) {
|
||||
const conds = [...(f.conditions || [])].sort((a, b) => (COND_ORDER[a.field] ?? 9) - (COND_ORDER[b.field] ?? 9));
|
||||
const condTitle = conds.map(condText).join('\n');
|
||||
const stat = f.match_count
|
||||
? `<span class="stat"><strong>${f.match_count}</strong> Treffer · zuletzt ${fmtDate(f.last_match_at)}</span>`
|
||||
: `<span class="stat never">noch kein Treffer</span>`;
|
||||
? `<span class="c-stat" title="${f.match_count} Treffer, zuletzt ${fmtDate(f.last_match_at)}"><strong>${f.match_count}</strong> · ${fmtDate(f.last_match_at).slice(0, 6)}</span>`
|
||||
: `<span class="c-stat never" title="Noch kein Treffer seit Einführung der Statistik">—</span>`;
|
||||
html += `
|
||||
<div class="rule${f.enabled ? '' : ' disabled'}${warnings.length ? ' has-warn' : ''}">
|
||||
<input type="checkbox" role="switch" class="toggle" ${f.enabled ? 'checked' : ''} title="${f.enabled ? 'Aktiv — klicken zum Deaktivieren' : 'Inaktiv — klicken zum Aktivieren'}" onchange="toggleEnabled(${f.id}, this.checked)">
|
||||
<div>
|
||||
<div class="rule-title">
|
||||
<span class="prio" title="Priorität">${f.priority}</span>
|
||||
<span>${esc(f.name)}</span>
|
||||
${f.source_folder !== 'INBOX' ? `<span class="badge folder" title="Quellordner">aus ${esc(f.source_folder)}</span>` : ''}
|
||||
${f.stop_processing ? '<span class="badge stop" title="Nach Treffer keine weiteren Regeln">stop</span>' : ''}
|
||||
</div>
|
||||
<div class="chips">
|
||||
${(f.conditions || []).map(condChip).join('')}
|
||||
<span class="arrow">→</span>
|
||||
${(f.actions || []).map(actChip).join('') || '<span class="chip">keine Aktion</span>'}
|
||||
</div>
|
||||
${warnings.length ? `<div class="warn-line">⚠ ${warnings.map(esc).join('<br>⚠ ')}</div>` : ''}
|
||||
</div>
|
||||
<div class="rule-side">
|
||||
${stat}
|
||||
<div class="rule-buttons">
|
||||
<button class="outline" onclick="editFilter(${f.id})">Bearbeiten</button>
|
||||
<button class="outline secondary" onclick="duplicateFilter(${f.id})" title="Als Vorlage für neue Regel verwenden">Kopie</button>
|
||||
<button class="outline contrast" onclick="deleteFilter(${f.id})" title="Löschen">✕</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="r${f.enabled ? '' : ' disabled'}${warnings.length ? ' warn' : ''}" ondblclick="editFilter(${f.id})">
|
||||
<span class="c-toggle"><input type="checkbox" role="switch" ${f.enabled ? 'checked' : ''} title="${f.enabled ? 'Aktiv' : 'Inaktiv'}" onchange="toggleEnabled(${f.id}, this.checked)"></span>
|
||||
<span class="c-prio">${f.priority}</span>
|
||||
<span class="c-name" title="${esc(f.name)}">${esc(f.name)}${f.stop_processing ? '<span class="tag stop" title="Nach Treffer keine weiteren Regeln">stop</span>' : ''}${f.source_folder !== 'INBOX' ? `<span class="tag src" title="Quellordner">${esc(f.source_folder)}</span>` : ''}${warnings.length ? `<span class="warn-ico" title="${esc(warnings.join('\n'))}">⚠️</span>` : ''}</span>
|
||||
<span class="c-cond" title="${esc(condTitle)}">${conds.map(condChip).join('') || '<span class="chip">keine Bedingung</span>'}</span>
|
||||
<span class="c-target" title="${esc(targetText(f))}">${esc(targetText(f))}</span>
|
||||
${stat}
|
||||
<span class="c-btns">
|
||||
<button onclick="editFilter(${f.id})" title="Bearbeiten">✎</button>
|
||||
<button onclick="duplicateFilter(${f.id})" title="Als Vorlage kopieren">⧉</button>
|
||||
<button class="del" onclick="deleteFilter(${f.id})" title="Löschen">🗑</button>
|
||||
</span>
|
||||
</div>`;
|
||||
}
|
||||
html += '</div>';
|
||||
html += '</div><p class="muted small-text" style="margin-top:0.4rem">Doppelklick auf eine Zeile öffnet die Regel. Regeln laufen von niedriger zu hoher Priorität.</p>';
|
||||
container.innerHTML = html;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user