feat(stellungnahme): Tag-Pill-Filter, flache Gruppen, Sticky-Footer, Auto-Save, Auto-Focus
Filter: - Tag-Pill-Reihe in Step 2 oberhalb der Suche. Multi-Select, OR-Logik zwischen Pills, AND mit Text-Suche. Pills aus allen Nicht-Fetch-Tags der deduplizierten Fragmente, alphabetisch sortiert. "Filter zurücksetzen" erscheint bei ≥1 aktivem Pill. Empty-State erklärt was nicht matcht (Text/Pills/beides). Akkordeons raus (Variante B): - Gruppen-Header bleiben als sichtbare Trenner (Label + Anzahl + "Alle"- Toggle), aber Click-to-Expand-Mechanik weg — Items immer offen. expandedGroups + toggleGroup als dead code entfernt. Bedienkomfort: - Sticky Mobile-Footer für alle 5 Step-Nav-Rows: bottom-2 mit weißem BG + Shadow + Backdrop-Blur. Auf Desktop unverändert (sm:static). - Auto-Save-Indikator "✓ Gespeichert" rechts oben, blitzt 2s nach jeder Mutation. Pure UI — wir speichern eh schon in localStorage. - Auto-Focus per Svelte-Action beim Step-Wechsel: Step 2 → Such-Input, Step 3 → Betroffenheit-Textarea, Step 4 → Name-Input. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
@@ -89,18 +89,74 @@
|
||||
|
||||
// Quick-filter for allgemeine arguments (matches title / text / bulletPoints)
|
||||
let argFilter = $state("");
|
||||
// Tag-Pill-Filter (OR-Logik): wenn ≥1 Tag aktiv, matchen nur Fragmente
|
||||
// mit mindestens einem aktiven Tag. Kombiniert mit argFilter via AND.
|
||||
let activeTags = $state<Set<string>>(new Set());
|
||||
|
||||
function tagLabel(slug: string, name?: string): string {
|
||||
if (name) return name;
|
||||
return slug
|
||||
.replace(/^tag-?/, "")
|
||||
.replace(/-/g, " ")
|
||||
.replace(/^\w/, (c) => c.toUpperCase());
|
||||
}
|
||||
|
||||
function fragmentTagSlugs(f: TextFragment): string[] {
|
||||
return (f.tags ?? [])
|
||||
.map((t) => (typeof t === "string" ? t : t._slug))
|
||||
.filter((s): s is string => !!s && s !== fetchTagSlug);
|
||||
}
|
||||
|
||||
const availableTags = $derived.by((): { slug: string; label: string }[] => {
|
||||
const map = new Map<string, string>();
|
||||
for (const f of allgemeineDeduped) {
|
||||
for (const t of f.tags ?? []) {
|
||||
const slug = typeof t === "string" ? t : t._slug;
|
||||
if (!slug || slug === fetchTagSlug) continue;
|
||||
if (!map.has(slug)) {
|
||||
const name = typeof t === "string" ? undefined : t.name;
|
||||
map.set(slug, tagLabel(slug, name));
|
||||
}
|
||||
}
|
||||
}
|
||||
return Array.from(map.entries())
|
||||
.map(([slug, label]) => ({ slug, label }))
|
||||
.sort((a, b) => a.label.localeCompare(b.label, "de"));
|
||||
});
|
||||
|
||||
function toggleTag(slug: string) {
|
||||
const next = new Set(activeTags);
|
||||
if (next.has(slug)) next.delete(slug);
|
||||
else next.add(slug);
|
||||
activeTags = next;
|
||||
}
|
||||
|
||||
function clearTagFilter() {
|
||||
activeTags = new Set();
|
||||
}
|
||||
|
||||
const filteredGruppen = $derived.by((): Group[] => {
|
||||
const q = argFilter.trim().toLowerCase();
|
||||
if (!q) return allgemeineGruppen;
|
||||
const tagFilterActive = activeTags.size > 0;
|
||||
if (!q && !tagFilterActive) return allgemeineGruppen;
|
||||
return allgemeineGruppen
|
||||
.map((g) => ({
|
||||
...g,
|
||||
items: g.items.filter((f) => {
|
||||
const hay = [f.title, f.text, ...(f.bulletPoints ?? [])]
|
||||
.filter(Boolean)
|
||||
.join(" ")
|
||||
.toLowerCase();
|
||||
return hay.includes(q);
|
||||
// Text-Match
|
||||
if (q) {
|
||||
const hay = [f.title, f.text, ...(f.bulletPoints ?? [])]
|
||||
.filter(Boolean)
|
||||
.join(" ")
|
||||
.toLowerCase();
|
||||
if (!hay.includes(q)) return false;
|
||||
}
|
||||
// Tag-Match (OR über aktive Tags)
|
||||
if (tagFilterActive) {
|
||||
const slugs = fragmentTagSlugs(f);
|
||||
if (!slugs.some((s) => activeTags.has(s))) return false;
|
||||
}
|
||||
return true;
|
||||
}),
|
||||
}))
|
||||
.filter((g) => g.items.length > 0);
|
||||
@@ -115,6 +171,21 @@
|
||||
let historyExpanded = $state(false);
|
||||
let mounted = $state(false);
|
||||
let hadSavedState = $state(false);
|
||||
// Auto-Save-Indikator: idle → saving (kurz) → saved (2s sichtbar) → idle.
|
||||
let saveStatus = $state<"idle" | "saving" | "saved">("idle");
|
||||
let savedHideTimer: ReturnType<typeof setTimeout> | null = null;
|
||||
function flashSaved() {
|
||||
saveStatus = "saved";
|
||||
if (savedHideTimer) clearTimeout(savedHideTimer);
|
||||
savedHideTimer = setTimeout(() => {
|
||||
saveStatus = "idle";
|
||||
}, 2000);
|
||||
}
|
||||
|
||||
// Svelte-Action: fokussiert das Element beim Mount (Step-Wechsel).
|
||||
function autofocus(node: HTMLElement) {
|
||||
setTimeout(() => node.focus({ preventScroll: true }), 0);
|
||||
}
|
||||
|
||||
function saveState() {
|
||||
try {
|
||||
@@ -133,7 +204,10 @@
|
||||
|
||||
$effect(() => {
|
||||
void [selectedSlugs, persoenlicheEingabe, absenderName, absenderAnschrift, absenderOrt, argNotes, openingOverride, closingOverride];
|
||||
if (mounted) saveState();
|
||||
if (mounted) {
|
||||
saveState();
|
||||
flashSaved();
|
||||
}
|
||||
});
|
||||
|
||||
// ── Step state ───────────────────────────────────────────────────────────────
|
||||
@@ -142,7 +216,6 @@
|
||||
let step = $state<Step>(0);
|
||||
|
||||
let selectedSlugs = $state<Set<string>>(new Set());
|
||||
let expandedGroups = $state<Set<string>>(new Set()); // empty = all collapsed
|
||||
let expandedItems = $state<Set<string>>(new Set()); // per-item text expand
|
||||
|
||||
function toggleItem(slug: string) {
|
||||
@@ -201,13 +274,6 @@
|
||||
expandedItems = nextExpanded;
|
||||
}
|
||||
|
||||
function toggleGroup(slug: string) {
|
||||
const next = new Set(expandedGroups);
|
||||
if (next.has(slug)) next.delete(slug);
|
||||
else next.add(slug);
|
||||
expandedGroups = next;
|
||||
}
|
||||
|
||||
function toggleGroupSelection(group: Group) {
|
||||
const slugs = group.items.map((f) => f._slug);
|
||||
const allSelected = slugs.every((s) => selectedSlugs.has(s));
|
||||
@@ -217,11 +283,6 @@
|
||||
else next.add(s);
|
||||
}
|
||||
selectedSlugs = next;
|
||||
// Expand on select, collapse on deselect
|
||||
const nextGroups = new Set(expandedGroups);
|
||||
if (allSelected) nextGroups.delete(group.slug);
|
||||
else nextGroups.add(group.slug);
|
||||
expandedGroups = nextGroups;
|
||||
}
|
||||
|
||||
// Combined selection for output — ortskonkrete first, then allgemeine
|
||||
@@ -325,7 +386,6 @@ ${"=".repeat(64)}`;
|
||||
}
|
||||
selectedSlugs = new Set();
|
||||
expandedItems = new Set();
|
||||
expandedGroups = new Set();
|
||||
argNotes = {};
|
||||
openingOverride = null;
|
||||
closingOverride = null;
|
||||
@@ -551,7 +611,15 @@ ${"=".repeat(64)}`;
|
||||
<div class="h-px flex-1 {s < step ? 'bg-wald-400' : 'bg-stein-200'}"></div>
|
||||
{/if}
|
||||
{/each}
|
||||
</div>
|
||||
</div>
|
||||
<div class="mb-6 flex items-center justify-end gap-3 text-xs">
|
||||
{#if saveStatus === "saved"}
|
||||
<span class="flex items-center gap-1 text-wald-600 transition-opacity" aria-live="polite">
|
||||
<Icon icon="mdi:check-circle" class="size-3.5" aria-hidden="true" />
|
||||
Gespeichert
|
||||
</span>
|
||||
{/if}
|
||||
<span class="text-stein-400">Schritt {step + 1} von {TOTAL_STEPS} – {STEP_LABELS[step]}</span>
|
||||
</div>
|
||||
|
||||
<!-- ── Step 0: Gebietskontext ─────────────────────────────────────────────── -->
|
||||
@@ -666,7 +734,7 @@ ${"=".repeat(64)}`;
|
||||
Dieser Assistent hilft Ihnen, eine individuelle Einwendung für den
|
||||
<strong>2. Entwurf des Regionalplans Südwestthüringen</strong> zu verfassen.
|
||||
Sie wählen zuerst ortskonkrete Argumente für Ihr Gebiet, dann allgemeine Einwände.
|
||||
</p>
|
||||
</p>
|
||||
<div class="sticky bottom-2 z-10 flex flex-wrap items-center gap-3 rounded-lg border border-stein-200 bg-white/95 p-3 shadow-lg backdrop-blur sm:static sm:border-0 sm:bg-transparent sm:p-0 sm:shadow-none sm:backdrop-blur-none">
|
||||
<button
|
||||
class="rounded-lg bg-wald-700 px-5 py-2.5 text-sm font-semibold text-white transition hover:bg-wald-800 disabled:opacity-40"
|
||||
@@ -735,7 +803,7 @@ ${"=".repeat(64)}`;
|
||||
Für dieses Gebiet sind noch keine ortskonkreten Argumente hinterlegt.
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
|
||||
<div class="sticky bottom-2 z-10 flex items-center gap-3 rounded-lg border border-stein-200 bg-white/95 p-3 shadow-lg backdrop-blur sm:static sm:border-0 sm:bg-transparent sm:p-0 sm:shadow-none sm:backdrop-blur-none">
|
||||
<button class="rounded-lg border border-stein-300 px-4 py-2 text-sm text-stein-600 hover:bg-stein-50" onclick={() => goTo(0)}>← Zurück</button>
|
||||
<button class="rounded-lg bg-wald-700 px-5 py-2.5 text-sm font-semibold text-white transition hover:bg-wald-800" onclick={() => goTo(2)}>
|
||||
@@ -767,30 +835,50 @@ ${"=".repeat(64)}`;
|
||||
class="w-full rounded-lg border border-stein-300 py-2 pl-9 pr-3 text-sm text-stein-800 focus:border-wald-500 focus:outline-none focus:ring-2 focus:ring-wald-200"
|
||||
placeholder="Argumente durchsuchen …"
|
||||
bind:value={argFilter}
|
||||
aria-label="Argumente durchsuchen"
|
||||
use:autofocus
|
||||
/>
|
||||
</div>
|
||||
{#if availableTags.length > 1}
|
||||
<div class="mb-4 flex flex-wrap items-center gap-1.5" role="group" aria-label="Nach Themen filtern">
|
||||
{#each availableTags as tag}
|
||||
{@const active = activeTags.has(tag.slug)}
|
||||
<button
|
||||
type="button"
|
||||
onclick={() => toggleTag(tag.slug)}
|
||||
aria-pressed={active}
|
||||
class="rounded-full border px-2.5 py-1 text-xs font-medium transition-colors
|
||||
{active
|
||||
? 'border-wald-700 bg-wald-700 text-white'
|
||||
: 'border-stein-300 bg-white text-stein-600 hover:border-wald-400 hover:text-wald-700'}"
|
||||
>{tag.label}</button>
|
||||
{/each}
|
||||
{#if activeTags.size > 0}
|
||||
<button
|
||||
type="button"
|
||||
onclick={clearTagFilter}
|
||||
class="ml-1 text-xs text-stein-400 hover:text-stein-700 underline"
|
||||
>Filter zurücksetzen</button>
|
||||
{/if}
|
||||
</div>
|
||||
{/if}
|
||||
{#if filteredGruppen.length === 0}
|
||||
{#if filteredGruppen.length === 0}
|
||||
<p class="mb-6 rounded-lg border border-stein-200 bg-stein-50 px-4 py-3 text-sm text-stein-500">
|
||||
Keine Argumente {argFilter ? `passen zu „${argFilter}"` : ""}{argFilter && activeTags.size > 0 ? " und " : ""}{activeTags.size > 0 ? "matchen die gewählten Themen" : ""}.
|
||||
</p>
|
||||
{/if}
|
||||
<div class="mb-6 space-y-2">
|
||||
{#each filteredGruppen as group}
|
||||
{@const groupSelectedCount = group.items.filter((f) => selectedSlugs.has(f._slug)).length}
|
||||
{@const allSelected = groupSelectedCount === group.items.length}
|
||||
{@const allSelected = groupSelectedCount === group.items.length}
|
||||
<div class="rounded-lg border {groupSelectedCount > 0 ? 'border-stein-400' : 'border-stein-200'} overflow-hidden">
|
||||
<div class="rounded-lg border {groupSelectedCount > 0 ? 'border-stein-400' : 'border-stein-200'} overflow-hidden">
|
||||
<!-- Group header (immer offen, dient als Trenner) -->
|
||||
<div class="flex items-center gap-2 bg-stein-50 px-3 py-2.5">
|
||||
<button
|
||||
class="flex flex-1 items-center gap-2 text-left"
|
||||
onclick={() => toggleGroup(group.slug)}
|
||||
aria-expanded={isExpanded}
|
||||
>
|
||||
<Icon icon={isExpanded ? "mdi:chevron-down" : "mdi:chevron-right"} class="size-4 shrink-0 text-stein-400" />
|
||||
<span class="font-medium text-stein-800 text-sm">{group.label}</span>
|
||||
<div class="flex items-center gap-2 bg-stein-50 px-3 py-2.5">
|
||||
<span class="flex flex-1 items-center gap-2 text-sm font-medium text-stein-800">
|
||||
{group.label}
|
||||
<span class="text-xs font-normal text-stein-500">
|
||||
{groupSelectedCount > 0 ? `${groupSelectedCount}/` : ""}{group.items.length}
|
||||
</span>
|
||||
</span>
|
||||
</span>
|
||||
<button
|
||||
class="rounded px-2 py-0.5 text-xs font-medium transition
|
||||
@@ -801,9 +889,7 @@ ${"=".repeat(64)}`;
|
||||
{allSelected ? "Alle ab" : "Alle"}
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<!-- Group items -->
|
||||
{#if isExpanded}
|
||||
|
||||
<div class="divide-y divide-stein-100">
|
||||
{#each group.items as f}
|
||||
<div class="transition-colors {selectedSlugs.has(f._slug) ? 'bg-wald-50' : 'bg-white'}">
|
||||
@@ -844,7 +930,6 @@ ${"=".repeat(64)}`;
|
||||
{/if}
|
||||
</div>
|
||||
{/each}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{/each}
|
||||
@@ -854,7 +939,7 @@ ${"=".repeat(64)}`;
|
||||
Keine allgemeinen Argumente hinterlegt.
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
|
||||
<div class="sticky bottom-2 z-10 flex items-center gap-3 rounded-lg border border-stein-200 bg-white/95 p-3 shadow-lg backdrop-blur sm:static sm:border-0 sm:bg-transparent sm:p-0 sm:shadow-none sm:backdrop-blur-none">
|
||||
<button class="rounded-lg border border-stein-300 px-4 py-2 text-sm text-stein-600 hover:bg-stein-50" onclick={() => goTo(1)}>← Zurück</button>
|
||||
<button
|
||||
@@ -882,9 +967,10 @@ ${"=".repeat(64)}`;
|
||||
class="w-full rounded-lg border border-stein-300 px-4 py-3 text-sm text-stein-800 focus:border-wald-500 focus:outline-none focus:ring-2 focus:ring-wald-200"
|
||||
rows="6"
|
||||
placeholder="Meine Betroffenheit: …"
|
||||
bind:value={persoenlicheEingabe}
|
||||
use:autofocus
|
||||
></textarea>
|
||||
<p class="mt-1 text-xs text-stein-400">Bleibt auf Ihrem Gerät. Wird nicht übertragen.</p>
|
||||
<p class="mt-1 text-xs text-stein-400">Bleibt auf Ihrem Gerät. Wird nicht übertragen.</p>
|
||||
<div class="sticky bottom-2 z-10 mt-5 flex items-center gap-3 rounded-lg border border-stein-200 bg-white/95 p-3 shadow-lg backdrop-blur sm:static sm:border-0 sm:bg-transparent sm:p-0 sm:shadow-none sm:backdrop-blur-none">
|
||||
<button class="rounded-lg border border-stein-300 px-4 py-2 text-sm text-stein-600 hover:bg-stein-50" onclick={() => goTo(2)}>← Zurück</button>
|
||||
<button class="rounded-lg bg-wald-700 px-5 py-2.5 text-sm font-semibold text-white transition hover:bg-wald-800" onclick={() => goTo(4)}>Weiter →</button>
|
||||
@@ -899,7 +985,7 @@ ${"=".repeat(64)}`;
|
||||
|
||||
<div class="mb-5 grid gap-3 sm:grid-cols-2">
|
||||
<div>
|
||||
<label class="mb-1 block text-xs font-medium text-stein-600" for="absender-name">Vor- und Nachname</label>
|
||||
<label class="mb-1 block text-xs font-medium text-stein-600" for="absender-name">Vor- und Nachname</label>
|
||||
<input id="absender-name" type="text" class="w-full rounded-lg border border-stein-300 px-3 py-2 text-sm focus:border-wald-500 focus:outline-none focus:ring-2 focus:ring-wald-200" placeholder="Max Mustermann" bind:value={absenderName} use:autofocus />
|
||||
</div>
|
||||
<div>
|
||||
@@ -954,7 +1040,7 @@ ${"=".repeat(64)}`;
|
||||
</div>
|
||||
</details>
|
||||
</div>
|
||||
|
||||
|
||||
<div class="sticky bottom-2 z-10 mb-2 flex gap-3 rounded-lg border border-stein-200 bg-white/95 p-3 shadow-lg backdrop-blur sm:static sm:border-0 sm:bg-transparent sm:p-0 sm:shadow-none sm:backdrop-blur-none">
|
||||
<button class="rounded-lg border border-stein-300 px-4 py-2 text-sm text-stein-600 hover:bg-stein-50" onclick={() => goTo(3)}>← Zurück</button>
|
||||
<button
|
||||
|
||||
@@ -123,6 +123,24 @@
|
||||
"content-save-outline": {
|
||||
"body": "<path fill=\"currentColor\" d=\"M17 3H5a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h14c1.1 0 2-.9 2-2V7zm2 16H5V5h11.17L19 7.83zm-7-7c-1.66 0-3 1.34-3 3s1.34 3 3 3s3-1.34 3-3s-1.34-3-3-3M6 6h9v4H6z\"/>"
|
||||
},
|
||||
"home-group": {
|
||||
"body": "<path fill=\"currentColor\" d=\"M17 16h-2v6h-3v-5H8v5H5v-6H3l7-6zM6 2l4 4H9v3H7V6H5v3H3V6H2zm12 1l5 5h-1v4h-3V9h-2v3h-1.66L14 10.87V8h-1z\"/>"
|
||||
},
|
||||
"vector-square": {
|
||||
"body": "<path fill=\"currentColor\" d=\"M2 2h6v2h8V2h6v6h-2v8h2v6h-6v-2H8v2H2v-6h2V8H2zm14 6V6H8v2H6v8h2v2h8v-2h2V8zM4 4v2h2V4zm14 0v2h2V4zM4 18v2h2v-2zm14 0v2h2v-2z\"/>"
|
||||
},
|
||||
"wind-turbine": {
|
||||
"body": "<path fill=\"currentColor\" d=\"m13.33 11.67l2.88 2.91c1.41-1.42 0-2.83 0-2.83l-1.49-1.51c.18-.38.28-.8.28-1.24c0-1.05-.54-1.97-1.36-2.5L15 2.11c-1.91-.58-2.5 1.33-2.5 1.33l-.81 2.59c-1.23.13-2.23.97-2.56 2.15L4.67 9.63c.64 1.9 2.53 1.27 2.53 1.27l2.07-.67c.34.74.96 1.31 1.73 1.59V19s-2 0-2 2v1h6v-1s0-2-2-2v-7.18c.12-.04.23-.1.33-.15M10.5 9A1.5 1.5 0 0 1 12 7.5A1.5 1.5 0 0 1 13.5 9a1.5 1.5 0 0 1-1.5 1.5A1.5 1.5 0 0 1 10.5 9\"/>"
|
||||
},
|
||||
"arrow-expand-vertical": {
|
||||
"body": "<path fill=\"currentColor\" d=\"M13 9v6h3l-4 4l-4-4h3V9H8l4-4l4 4zM4 2h16v2H4zm0 18h16v2H4z\"/>"
|
||||
},
|
||||
"domain": {
|
||||
"body": "<path fill=\"currentColor\" d=\"M18 15h-2v2h2m0-6h-2v2h2m2 6h-8v-2h2v-2h-2v-2h2v-2h-2V9h8M10 7H8V5h2m0 6H8V9h2m0 6H8v-2h2m0 6H8v-2h2M6 7H4V5h2m0 6H4V9h2m0 6H4v-2h2m0 6H4v-2h2m6-10V3H2v18h20V7z\"/>"
|
||||
},
|
||||
"flag-outline": {
|
||||
"body": "<path fill=\"currentColor\" d=\"m12.36 6l.4 2H18v6h-3.36l-.4-2H7V6zM14 4H5v17h2v-7h5.6l.4 2h7V6h-5.6\"/>"
|
||||
},
|
||||
"transmission-tower": {
|
||||
"body": "<path fill=\"currentColor\" d=\"m8.28 5.45l-1.78-.9L7.76 2h8.47l1.27 2.55l-1.78.89L15 4H9zM18.62 8h-4.53l-.79-3h-2.6l-.79 3H5.38L4.1 10.55l1.79.89l.73-1.44h10.76l.72 1.45l1.79-.89zm-.85 14H15.7l-.24-.9L12 15.9l-3.47 5.2l-.23.9H6.23l2.89-11h2.07l-.36 1.35L12 14.1l1.16-1.75l-.35-1.35h2.07zm-6.37-7l-.9-1.35l-1.18 4.48zm3.28 3.12l-1.18-4.48l-.9 1.36z\"/>"
|
||||
},
|
||||
@@ -220,9 +238,6 @@
|
||||
"tag-outline": {
|
||||
"body": "<path fill=\"currentColor\" d=\"m21.41 11.58l-9-9A2 2 0 0 0 11 2H4a2 2 0 0 0-2 2v7a2 2 0 0 0 .59 1.42l9 9A2 2 0 0 0 13 22a2 2 0 0 0 1.41-.59l7-7A2 2 0 0 0 22 13a2 2 0 0 0-.59-1.42M13 20l-9-9V4h7l9 9M6.5 5A1.5 1.5 0 1 1 5 6.5A1.5 1.5 0 0 1 6.5 5\"/>"
|
||||
},
|
||||
"flag-outline": {
|
||||
"body": "<path fill=\"currentColor\" d=\"m12.36 6l.4 2H18v6h-3.36l-.4-2H7V6zM14 4H5v17h2v-7h5.6l.4 2h7V6h-5.6\"/>"
|
||||
},
|
||||
"information-outline": {
|
||||
"body": "<path fill=\"currentColor\" d=\"M11 9h2V7h-2m1 13c-4.41 0-8-3.59-8-8s3.59-8 8-8s8 3.59 8 8s-3.59 8-8 8m0-18A10 10 0 0 0 2 12a10 10 0 0 0 10 10a10 10 0 0 0 10-10A10 10 0 0 0 12 2m-1 15h2v-6h-2z\"/>"
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user