update
This commit is contained in:
14
README.md
14
README.md
@@ -12,8 +12,8 @@ Rein manuelle Pflege: Sidebar links = Projekte, Hauptbereich = Feature-Cards.
|
|||||||
Markdown-Dateien unter `daten/<projekt>/<feature>.md` — Projekt = Ordner, die Datei ist
|
Markdown-Dateien unter `daten/<projekt>/<feature>.md` — Projekt = Ordner, die Datei ist
|
||||||
die Wahrheit. Der Editor bearbeitet immer den rohen Text; geparst wird nur für die Anzeige.
|
die Wahrheit. Der Editor bearbeitet immer den rohen Text; geparst wird nur für die Anzeige.
|
||||||
|
|
||||||
Format (Marker allein auf einer Zeile; `<!-- funktion -->` ist wiederholbar, jede
|
Format (Marker allein auf einer Zeile; `<!-- funktion -->` ist wiederholbar, `<!-- regel -->`
|
||||||
`<!-- regel -->` gehört zur davorstehenden Funktion; Trennzeilen aus `---` sind Optik):
|
und `<!-- flow -->` gehören zur davorstehenden Funktion; Trennzeilen aus `---` sind Optik):
|
||||||
|
|
||||||
```markdown
|
```markdown
|
||||||
<!-- titel -->
|
<!-- titel -->
|
||||||
@@ -26,14 +26,16 @@ Kurzer Satz, was das Ziel ist.
|
|||||||
Anmeldung mit E-Mail
|
Anmeldung mit E-Mail
|
||||||
<!-- regel -->
|
<!-- regel -->
|
||||||
Passwort nie loggen
|
Passwort nie loggen
|
||||||
|
<!-- flow -->
|
||||||
|
- Formular öffnen
|
||||||
|
- Daten eingeben
|
||||||
<!-- funktion -->
|
<!-- funktion -->
|
||||||
Abmeldung
|
Abmeldung
|
||||||
<!-- sonstiges -->
|
|
||||||
Notizen.
|
|
||||||
```
|
```
|
||||||
|
|
||||||
Text vor dem ersten Marker, Inhalt unbekannter Marker und Regeln ohne Funktion erscheinen
|
Funktionen erscheinen als Titelliste; Regeln und Flow zeigt die Karte erst beim Hover über
|
||||||
in der Anzeige unter „Sonstiges"; die Datei bleibt unangetastet. Bearbeitet wird live auf
|
die Funktion. Text vor dem ersten Marker, Inhalt unbekannter Marker und verwaiste
|
||||||
|
regel/flow-Blöcke werden nicht angezeigt, aber beim Speichern erhalten. Bearbeitet wird live auf
|
||||||
der Karte (Klick ins Feld, Verlassen speichert, Escape verwirft); jedes Speichern setzt
|
der Karte (Klick ins Feld, Verlassen speichert, Escape verwirft); jedes Speichern setzt
|
||||||
die Datei neu zusammen. Titeländerungen benennen die Datei passend um.
|
die Datei neu zusammen. Titeländerungen benennen die Datei passend um.
|
||||||
|
|
||||||
|
|||||||
@@ -1,8 +1,9 @@
|
|||||||
"""Zerlegt den rohen Feature-Text in Sektionen — nur für die Anzeige.
|
"""Zerlegt den rohen Feature-Text in Sektionen — nur für die Anzeige.
|
||||||
|
|
||||||
Format: titel (eine Zeile), ziel, dann wiederholbare <!-- funktion -->-Blöcke,
|
Format: titel (eine Zeile), art, ziel, dann wiederholbare <!-- funktion -->-Blöcke;
|
||||||
denen <!-- regel -->-Blöcke zugeordnet werden, zuletzt sonstiges.
|
<!-- regel -->- und <!-- flow -->-Blöcke gehören zur davorstehenden Funktion.
|
||||||
Präambel, unbekannte Marker und Regeln ohne Funktion landen in `sonstiges`.
|
Präambel, unbekannte Marker und verwaiste regel/flow-Blöcke landen in `sonstiges`
|
||||||
|
(wird nicht angezeigt, aber beim Speichern erhalten).
|
||||||
Trennzeilen aus Bindestrichen (---) sind reine Optik und werden ignoriert.
|
Trennzeilen aus Bindestrichen (---) sind reine Optik und werden ignoriert.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
@@ -46,27 +47,24 @@ def zerlege(roh: str) -> dict:
|
|||||||
elif schluessel == "ziel":
|
elif schluessel == "ziel":
|
||||||
ziel.append(text)
|
ziel.append(text)
|
||||||
elif schluessel == "funktion":
|
elif schluessel == "funktion":
|
||||||
funktionen.append({"text": text, "regeln": []})
|
funktionen.append({"text": text, "regeln": [], "flow": []})
|
||||||
elif schluessel == "regel" and funktionen:
|
elif schluessel == "regel" and funktionen:
|
||||||
funktionen[-1]["regeln"].append(text)
|
funktionen[-1]["regeln"].append(text)
|
||||||
else: # Präambel, unbekannter Marker, regel ohne funktion
|
elif schluessel == "flow" and funktionen:
|
||||||
|
funktionen[-1]["flow"].append(text)
|
||||||
|
else: # Präambel, unbekannter Marker, regel/flow ohne funktion
|
||||||
sonstiges.append(text)
|
sonstiges.append(text)
|
||||||
|
|
||||||
|
for f in funktionen:
|
||||||
|
f["flow"] = "\n".join(f["flow"])
|
||||||
|
|
||||||
# Titel ist eine Zeile: erste nicht-leere
|
# Titel ist eine Zeile: erste nicht-leere
|
||||||
titel_zeilen = [z.strip() for t in titel for z in t.splitlines() if z.strip()]
|
titel_zeilen = [z.strip() for t in titel for z in t.splitlines() if z.strip()]
|
||||||
|
|
||||||
# Funktionen-Bereich mit Markern rekonstruiert — für das Editor-Feld
|
|
||||||
teile = []
|
|
||||||
for f in funktionen:
|
|
||||||
teile.append(f"<!-- funktion -->\n{f['text']}")
|
|
||||||
for r in f["regeln"]:
|
|
||||||
teile.append(f"<!-- regel -->\n{r}")
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
"titel": titel_zeilen[0] if titel_zeilen else "",
|
"titel": titel_zeilen[0] if titel_zeilen else "",
|
||||||
"art": "optional" if art and art[0].strip().lower() == "optional" else "kern",
|
"art": "optional" if art and art[0].strip().lower() == "optional" else "kern",
|
||||||
"ziel": "\n\n".join(ziel),
|
"ziel": "\n\n".join(ziel),
|
||||||
"funktionen": funktionen,
|
"funktionen": funktionen,
|
||||||
"funktionen_roh": "\n".join(teile),
|
|
||||||
"sonstiges": "\n\n".join(sonstiges),
|
"sonstiges": "\n\n".join(sonstiges),
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,6 +3,9 @@ from parser import zerlege
|
|||||||
VOLL = """<!-- titel -->
|
VOLL = """<!-- titel -->
|
||||||
Login
|
Login
|
||||||
----------------------------------------
|
----------------------------------------
|
||||||
|
<!-- art -->
|
||||||
|
kern
|
||||||
|
----------------------------------------
|
||||||
<!-- ziel -->
|
<!-- ziel -->
|
||||||
Nutzer können sich anmelden.
|
Nutzer können sich anmelden.
|
||||||
----------------------------------------
|
----------------------------------------
|
||||||
@@ -12,33 +15,32 @@ Anmeldung mit E-Mail
|
|||||||
Passwort nie loggen
|
Passwort nie loggen
|
||||||
<!-- regel -->
|
<!-- regel -->
|
||||||
Sperre nach 5 Fehlversuchen
|
Sperre nach 5 Fehlversuchen
|
||||||
|
<!-- flow -->
|
||||||
|
- Formular öffnen
|
||||||
|
- Daten eingeben
|
||||||
<!-- funktion -->
|
<!-- funktion -->
|
||||||
Abmeldung
|
Abmeldung
|
||||||
----------------------------------------
|
|
||||||
<!-- sonstiges -->
|
|
||||||
Offen: 2FA.
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
|
||||||
def test_alle_sektionen():
|
def test_alle_sektionen():
|
||||||
s = zerlege(VOLL)
|
s = zerlege(VOLL)
|
||||||
assert s["titel"] == "Login"
|
assert s["titel"] == "Login"
|
||||||
|
assert s["art"] == "kern"
|
||||||
assert s["ziel"] == "Nutzer können sich anmelden."
|
assert s["ziel"] == "Nutzer können sich anmelden."
|
||||||
assert s["funktionen"] == [
|
assert s["funktionen"] == [
|
||||||
{"text": "Anmeldung mit E-Mail", "regeln": ["Passwort nie loggen", "Sperre nach 5 Fehlversuchen"]},
|
{
|
||||||
{"text": "Abmeldung", "regeln": []},
|
"text": "Anmeldung mit E-Mail",
|
||||||
|
"regeln": ["Passwort nie loggen", "Sperre nach 5 Fehlversuchen"],
|
||||||
|
"flow": "- Formular öffnen\n- Daten eingeben",
|
||||||
|
},
|
||||||
|
{"text": "Abmeldung", "regeln": [], "flow": ""},
|
||||||
]
|
]
|
||||||
assert s["sonstiges"] == "Offen: 2FA."
|
|
||||||
|
|
||||||
|
|
||||||
def test_funktionen_roh_rekonstruiert_marker():
|
def test_mehrere_flow_bloecke_werden_verbunden():
|
||||||
s = zerlege(VOLL)
|
s = zerlege("<!-- funktion -->\nX\n<!-- flow -->\n- a\n<!-- flow -->\n- b\n")
|
||||||
assert s["funktionen_roh"] == (
|
assert s["funktionen"][0]["flow"] == "- a\n- b"
|
||||||
"<!-- funktion -->\nAnmeldung mit E-Mail\n"
|
|
||||||
"<!-- regel -->\nPasswort nie loggen\n"
|
|
||||||
"<!-- regel -->\nSperre nach 5 Fehlversuchen\n"
|
|
||||||
"<!-- funktion -->\nAbmeldung"
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
def test_trenner_zeilen_werden_ignoriert():
|
def test_trenner_zeilen_werden_ignoriert():
|
||||||
@@ -57,10 +59,10 @@ def test_unbekannter_marker_landet_in_sonstiges():
|
|||||||
assert s["sonstiges"] == "Geheim"
|
assert s["sonstiges"] == "Geheim"
|
||||||
|
|
||||||
|
|
||||||
def test_regel_ohne_funktion_landet_in_sonstiges():
|
def test_regel_und_flow_ohne_funktion_landen_in_sonstiges():
|
||||||
s = zerlege("<!-- regel -->\nverwaist\n")
|
s = zerlege("<!-- regel -->\nverwaist\n<!-- flow -->\n- auch\n")
|
||||||
assert s["funktionen"] == []
|
assert s["funktionen"] == []
|
||||||
assert s["sonstiges"] == "verwaist"
|
assert s["sonstiges"] == "verwaist\n\n- auch"
|
||||||
|
|
||||||
|
|
||||||
def test_doppelter_marker_wird_konkateniert():
|
def test_doppelter_marker_wird_konkateniert():
|
||||||
@@ -70,7 +72,7 @@ def test_doppelter_marker_wird_konkateniert():
|
|||||||
|
|
||||||
def test_leerer_text():
|
def test_leerer_text():
|
||||||
s = zerlege("")
|
s = zerlege("")
|
||||||
assert s == {"titel": "", "art": "kern", "ziel": "", "funktionen": [], "funktionen_roh": "", "sonstiges": ""}
|
assert s == {"titel": "", "art": "kern", "ziel": "", "funktionen": [], "sonstiges": ""}
|
||||||
|
|
||||||
|
|
||||||
def test_art_optional():
|
def test_art_optional():
|
||||||
|
|||||||
19
daten/Planer/darkmode.md
Normal file
19
daten/Planer/darkmode.md
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
<!-- titel -->
|
||||||
|
Darkmode
|
||||||
|
----------------------------------------
|
||||||
|
<!-- art -->
|
||||||
|
optional
|
||||||
|
----------------------------------------
|
||||||
|
<!-- ziel -->
|
||||||
|
|
||||||
|
----------------------------------------
|
||||||
|
<!-- funktion -->
|
||||||
|
Darkmode umschalten
|
||||||
|
<!-- regel -->
|
||||||
|
Icon: ☾ / ☀
|
||||||
|
<!-- regel -->
|
||||||
|
Folgt beim ersten Start der System-Einstellung
|
||||||
|
<!-- regel -->
|
||||||
|
Zustand bleibt gespeichert
|
||||||
|
<!-- flow -->
|
||||||
|
- Klick auf Icon in der Sidebar oben
|
||||||
78
daten/Planer/features.md
Normal file
78
daten/Planer/features.md
Normal file
@@ -0,0 +1,78 @@
|
|||||||
|
<!-- titel -->
|
||||||
|
Features
|
||||||
|
----------------------------------------
|
||||||
|
<!-- art -->
|
||||||
|
kern
|
||||||
|
----------------------------------------
|
||||||
|
<!-- ziel -->
|
||||||
|
|
||||||
|
----------------------------------------
|
||||||
|
<!-- funktion -->
|
||||||
|
Ausführliche Ansicht umschalten
|
||||||
|
<!-- regel -->
|
||||||
|
Volle Breite
|
||||||
|
<!-- regel -->
|
||||||
|
Funktionen als Boxen in zwei Spalten
|
||||||
|
<!-- regel -->
|
||||||
|
Alle Details offen
|
||||||
|
<!-- flow -->
|
||||||
|
- Klick auf freie Kartenfläche
|
||||||
|
- Nochmal klicken schaltet zurück
|
||||||
|
<!-- funktion -->
|
||||||
|
Feature als Datei speichern
|
||||||
|
<!-- regel -->
|
||||||
|
Projekt = Ordner unter daten/
|
||||||
|
<!-- regel -->
|
||||||
|
Feature = Markdown-Datei im Projektordner
|
||||||
|
<!-- regel -->
|
||||||
|
Titeländerung benennt die Datei um
|
||||||
|
<!-- regel -->
|
||||||
|
Versionierbar per git
|
||||||
|
<!-- funktion -->
|
||||||
|
Feature erstellen
|
||||||
|
<!-- regel -->
|
||||||
|
Icon: +
|
||||||
|
<!-- regel -->
|
||||||
|
Titel direkt zur Bearbeitung geöffnet
|
||||||
|
<!-- regel -->
|
||||||
|
Karten alphabetisch nach Titel sortiert
|
||||||
|
<!-- flow -->
|
||||||
|
- Klick auf leere Card
|
||||||
|
- Card wird erstellt
|
||||||
|
<!-- funktion -->
|
||||||
|
Features entfernen
|
||||||
|
<!-- regel -->
|
||||||
|
Icon: x
|
||||||
|
<!-- flow -->
|
||||||
|
- Hover über Card
|
||||||
|
- Auf Icon klicken
|
||||||
|
- Icon wird zum Bestätigungs Icon
|
||||||
|
- Klick zum entfernen
|
||||||
|
<!-- funktion -->
|
||||||
|
Kern/Optional umschalten
|
||||||
|
<!-- regel -->
|
||||||
|
Kern blau umrandet
|
||||||
|
<!-- regel -->
|
||||||
|
Optional grün umrandet
|
||||||
|
<!-- flow -->
|
||||||
|
- Klick auf Badge
|
||||||
|
<!-- funktion -->
|
||||||
|
Titel anpassen
|
||||||
|
<!-- regel -->
|
||||||
|
Enter schließt ab
|
||||||
|
<!-- regel -->
|
||||||
|
Verlassen speichert
|
||||||
|
<!-- regel -->
|
||||||
|
Escape verwirft
|
||||||
|
<!-- flow -->
|
||||||
|
- Hover über Card
|
||||||
|
- Klick auf Titel
|
||||||
|
- Inhalt ändern
|
||||||
|
<!-- funktion -->
|
||||||
|
Ziel anpassen
|
||||||
|
<!-- regel -->
|
||||||
|
Leer: Platzhalter „Ziel ergänzen" klicken
|
||||||
|
<!-- flow -->
|
||||||
|
- Hover über Card
|
||||||
|
- Klick auf Zieltext
|
||||||
|
- Inhalt anpassen
|
||||||
59
daten/Planer/funktionen.md
Normal file
59
daten/Planer/funktionen.md
Normal file
@@ -0,0 +1,59 @@
|
|||||||
|
<!-- titel -->
|
||||||
|
Funktionen
|
||||||
|
----------------------------------------
|
||||||
|
<!-- art -->
|
||||||
|
kern
|
||||||
|
----------------------------------------
|
||||||
|
<!-- ziel -->
|
||||||
|
|
||||||
|
----------------------------------------
|
||||||
|
<!-- funktion -->
|
||||||
|
Flow anpassen
|
||||||
|
<!-- regel -->
|
||||||
|
Ein Flow je Funktion
|
||||||
|
<!-- regel -->
|
||||||
|
Leeren entfernt den Flow
|
||||||
|
<!-- flow -->
|
||||||
|
- Hover über Funktion
|
||||||
|
- Klick auf Flow oder + Flow
|
||||||
|
- Inhalt anpassen
|
||||||
|
<!-- funktion -->
|
||||||
|
Funktion anpassen
|
||||||
|
<!-- flow -->
|
||||||
|
- Klick auf Text
|
||||||
|
- Inhalt ändern
|
||||||
|
<!-- funktion -->
|
||||||
|
Funktion entfernen
|
||||||
|
<!-- regel -->
|
||||||
|
Feld leeren entfernt auch
|
||||||
|
<!-- flow -->
|
||||||
|
- Hover über Funktion
|
||||||
|
- Klick auf x
|
||||||
|
<!-- funktion -->
|
||||||
|
Funktion hinzufügen
|
||||||
|
<!-- regel -->
|
||||||
|
Alphabetisch sortiert
|
||||||
|
<!-- regel -->
|
||||||
|
Neue Funktion rückt nach dem Speichern an ihre Position
|
||||||
|
<!-- flow -->
|
||||||
|
- Hover über Card
|
||||||
|
- Klick auf + Funktion
|
||||||
|
- Inhalt eingeben
|
||||||
|
<!-- funktion -->
|
||||||
|
Regel anpassen
|
||||||
|
<!-- flow -->
|
||||||
|
- Klick auf Text
|
||||||
|
- Inhalt ändern
|
||||||
|
<!-- funktion -->
|
||||||
|
Regel entfernen
|
||||||
|
<!-- regel -->
|
||||||
|
Feld leeren entfernt auch
|
||||||
|
<!-- flow -->
|
||||||
|
- Hover über Regel
|
||||||
|
- Klick auf x
|
||||||
|
<!-- funktion -->
|
||||||
|
Regel hinzufügen
|
||||||
|
<!-- flow -->
|
||||||
|
- Hover über Funktion
|
||||||
|
- Klick auf + Regel
|
||||||
|
- Inhalt eingeben
|
||||||
47
daten/Planer/projekte.md
Normal file
47
daten/Planer/projekte.md
Normal file
@@ -0,0 +1,47 @@
|
|||||||
|
<!-- titel -->
|
||||||
|
Projekte
|
||||||
|
----------------------------------------
|
||||||
|
<!-- art -->
|
||||||
|
kern
|
||||||
|
----------------------------------------
|
||||||
|
<!-- ziel -->
|
||||||
|
|
||||||
|
----------------------------------------
|
||||||
|
<!-- funktion -->
|
||||||
|
Projekt anpassen
|
||||||
|
<!-- regel -->
|
||||||
|
Icon: Stift
|
||||||
|
<!-- regel -->
|
||||||
|
Enter bestätigt
|
||||||
|
<!-- regel -->
|
||||||
|
Escape bricht ab
|
||||||
|
<!-- flow -->
|
||||||
|
- Hover über Projektname
|
||||||
|
- Klick auf Icon
|
||||||
|
- Inhalt anpassen
|
||||||
|
<!-- funktion -->
|
||||||
|
Projekt auswählen
|
||||||
|
<!-- regel -->
|
||||||
|
Aktives Projekt ist hervorgehoben
|
||||||
|
<!-- flow -->
|
||||||
|
- Klick auf Name
|
||||||
|
- Features werden im Hauptbereich angezeigt
|
||||||
|
<!-- funktion -->
|
||||||
|
Projekt entfernen
|
||||||
|
<!-- regel -->
|
||||||
|
Icon: x
|
||||||
|
<!-- flow -->
|
||||||
|
- Hover über Name
|
||||||
|
- Klick auf Icon
|
||||||
|
- Icon wird zum Bestätigungs Icon
|
||||||
|
- Klick zum entfernen
|
||||||
|
<!-- funktion -->
|
||||||
|
Projekt erstellen
|
||||||
|
<!-- regel -->
|
||||||
|
Alphabetisch in der Sidebar sortiert
|
||||||
|
<!-- regel -->
|
||||||
|
Feature-Anzahl neben dem Namen
|
||||||
|
<!-- flow -->
|
||||||
|
- Inputfeld titel eingeben
|
||||||
|
- Mit Icon oder Enter erstellen
|
||||||
|
- Projekt wird direkt ausgewählt
|
||||||
@@ -1,11 +0,0 @@
|
|||||||
<!-- titel -->
|
|
||||||
Neues Feature
|
|
||||||
----------------------------------------
|
|
||||||
<!-- art -->
|
|
||||||
kern
|
|
||||||
----------------------------------------
|
|
||||||
<!-- ziel -->
|
|
||||||
adsda
|
|
||||||
----------------------------------------
|
|
||||||
<!-- sonstiges -->
|
|
||||||
|
|
||||||
@@ -15,8 +15,10 @@ function kopie(sekt) {
|
|||||||
titel: sekt.titel,
|
titel: sekt.titel,
|
||||||
art: sekt.art,
|
art: sekt.art,
|
||||||
ziel: sekt.ziel,
|
ziel: sekt.ziel,
|
||||||
funktionen: sekt.funktionen.map(f => ({ text: f.text, regeln: [...f.regeln] })),
|
funktionen: sekt.funktionen
|
||||||
sonstiges: sekt.sonstiges,
|
.map(f => ({ text: f.text, regeln: [...f.regeln], flow: f.flow }))
|
||||||
|
.sort((a, b) => a.text.localeCompare(b.text, 'de', { sensitivity: 'base' })),
|
||||||
|
sonstiges: sekt.sonstiges, // nicht angezeigt, aber beim Speichern erhalten
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -39,8 +41,8 @@ function istAktiv(feld, i, j) {
|
|||||||
function wert(feld, i, j) {
|
function wert(feld, i, j) {
|
||||||
if (feld === 'titel') return s.value.titel
|
if (feld === 'titel') return s.value.titel
|
||||||
if (feld === 'ziel') return s.value.ziel
|
if (feld === 'ziel') return s.value.ziel
|
||||||
if (feld === 'sonstiges') return s.value.sonstiges
|
|
||||||
if (feld === 'funktion') return s.value.funktionen[i].text
|
if (feld === 'funktion') return s.value.funktionen[i].text
|
||||||
|
if (feld === 'flow') return s.value.funktionen[i].flow
|
||||||
return s.value.funktionen[i].regeln[j]
|
return s.value.funktionen[i].regeln[j]
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -77,11 +79,11 @@ async function fertig() {
|
|||||||
s.value.titel = neu
|
s.value.titel = neu
|
||||||
} else if (a.feld === 'ziel') {
|
} else if (a.feld === 'ziel') {
|
||||||
s.value.ziel = neu
|
s.value.ziel = neu
|
||||||
} else if (a.feld === 'sonstiges') {
|
|
||||||
s.value.sonstiges = neu
|
|
||||||
} else if (a.feld === 'funktion') {
|
} else if (a.feld === 'funktion') {
|
||||||
if (!neu) s.value.funktionen.splice(a.i, 1) // leeren = löschen
|
if (!neu) s.value.funktionen.splice(a.i, 1) // leeren = löschen
|
||||||
else s.value.funktionen[a.i].text = neu
|
else s.value.funktionen[a.i].text = neu
|
||||||
|
} else if (a.feld === 'flow') {
|
||||||
|
s.value.funktionen[a.i].flow = neu
|
||||||
} else if (a.feld === 'regel') {
|
} else if (a.feld === 'regel') {
|
||||||
if (!neu) s.value.funktionen[a.i].regeln.splice(a.j, 1)
|
if (!neu) s.value.funktionen[a.i].regeln.splice(a.j, 1)
|
||||||
else s.value.funktionen[a.i].regeln[a.j] = neu
|
else s.value.funktionen[a.i].regeln[a.j] = neu
|
||||||
@@ -96,8 +98,8 @@ function abbrechenLeer(a) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function neueFunktion() {
|
function neueFunktion() {
|
||||||
s.value.funktionen.push({ text: '', regeln: [] })
|
s.value.funktionen.unshift({ text: '', regeln: [], flow: '' })
|
||||||
starte('funktion', s.value.funktionen.length - 1)
|
starte('funktion', 0)
|
||||||
}
|
}
|
||||||
|
|
||||||
function neueRegel(i) {
|
function neueRegel(i) {
|
||||||
@@ -130,10 +132,14 @@ function baueRoh() {
|
|||||||
`<!-- ziel -->\n${s.value.ziel}`,
|
`<!-- ziel -->\n${s.value.ziel}`,
|
||||||
]
|
]
|
||||||
const funktionen = s.value.funktionen
|
const funktionen = s.value.funktionen
|
||||||
.map(f => [`<!-- funktion -->\n${f.text}`, ...f.regeln.map(r => `<!-- regel -->\n${r}`)].join('\n'))
|
.map(f => {
|
||||||
|
const bloecke = [`<!-- funktion -->\n${f.text}`, ...f.regeln.map(r => `<!-- regel -->\n${r}`)]
|
||||||
|
if (f.flow) bloecke.push(`<!-- flow -->\n${f.flow}`)
|
||||||
|
return bloecke.join('\n')
|
||||||
|
})
|
||||||
.join('\n')
|
.join('\n')
|
||||||
if (funktionen) teile.push(funktionen)
|
if (funktionen) teile.push(funktionen)
|
||||||
teile.push(`<!-- sonstiges -->\n${s.value.sonstiges}`)
|
if (s.value.sonstiges) teile.push(`<!-- sonstiges -->\n${s.value.sonstiges}`)
|
||||||
return teile.join(`\n${TRENNER}\n`) + '\n'
|
return teile.join(`\n${TRENNER}\n`) + '\n'
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -147,8 +153,15 @@ async function speichern() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const loeschBestaetigung = ref(false)
|
||||||
|
|
||||||
async function loeschen() {
|
async function loeschen() {
|
||||||
if (!confirm(`Feature „${s.value.titel}“ löschen?`)) return
|
// Erster Klick: Icon wird zum Bestätigungs-Icon, zweiter Klick löscht
|
||||||
|
if (!loeschBestaetigung.value) {
|
||||||
|
loeschBestaetigung.value = true
|
||||||
|
return
|
||||||
|
}
|
||||||
|
loeschBestaetigung.value = false
|
||||||
try {
|
try {
|
||||||
await featureLoeschen(props.projekt, props.feature.id)
|
await featureLoeschen(props.projekt, props.feature.id)
|
||||||
emit('geloescht')
|
emit('geloescht')
|
||||||
@@ -161,13 +174,31 @@ function zeilen(text) {
|
|||||||
return Math.max(2, text.split('\n').length)
|
return Math.max(2, text.split('\n').length)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Flow-Text („- Schritt“ je Zeile) für die nummerierte Anzeige zerlegen
|
||||||
|
function flowSchritte(flow) {
|
||||||
|
return flow.split('\n').map(z => z.replace(/^[-*]\s*/, '').trim()).filter(Boolean)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ausführliche Ansicht: Klick auf freie Kartenfläche schaltet um
|
||||||
|
const gross = ref(false)
|
||||||
|
|
||||||
|
function wechsleAnsicht(e) {
|
||||||
|
if (e.target.closest('button, input, textarea, .klickbar, h3')) return
|
||||||
|
gross.value = !gross.value
|
||||||
|
}
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
if (props.titelBearbeiten) starte('titel')
|
if (props.titelBearbeiten) starte('titel')
|
||||||
})
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<article class="karte" :class="s.art">
|
<article
|
||||||
|
class="karte"
|
||||||
|
:class="[s.art, { gross }]"
|
||||||
|
@click="wechsleAnsicht"
|
||||||
|
@mouseleave="loeschBestaetigung = false"
|
||||||
|
>
|
||||||
<header>
|
<header>
|
||||||
<input
|
<input
|
||||||
v-if="istAktiv('titel')"
|
v-if="istAktiv('titel')"
|
||||||
@@ -185,7 +216,12 @@ onMounted(() => {
|
|||||||
title="Klicken zum Umschalten"
|
title="Klicken zum Umschalten"
|
||||||
@click="wechsleArt"
|
@click="wechsleArt"
|
||||||
>{{ s.art }}</button>
|
>{{ s.art }}</button>
|
||||||
<button class="icon-knopf rot werkzeug" title="Feature löschen" @click="loeschen">✕</button>
|
<button
|
||||||
|
class="icon-knopf rot werkzeug"
|
||||||
|
:class="{ bestaetigen: loeschBestaetigung }"
|
||||||
|
:title="loeschBestaetigung ? 'Wirklich löschen?' : 'Feature löschen'"
|
||||||
|
@click="loeschen"
|
||||||
|
>{{ loeschBestaetigung ? '✓' : '✕' }}</button>
|
||||||
</header>
|
</header>
|
||||||
|
|
||||||
<section :class="{ leer: !s.ziel && !istAktiv('ziel') }">
|
<section :class="{ leer: !s.ziel && !istAktiv('ziel') }">
|
||||||
@@ -204,50 +240,66 @@ onMounted(() => {
|
|||||||
|
|
||||||
<section :class="{ leer: !s.funktionen.length }">
|
<section :class="{ leer: !s.funktionen.length }">
|
||||||
<p class="gruppe">Funktionen</p>
|
<p class="gruppe">Funktionen</p>
|
||||||
<div v-for="(f, i) in s.funktionen" :key="i" class="funktion">
|
<button class="hinzu werkzeug" @click="neueFunktion">+ Funktion</button>
|
||||||
<div class="zeile">
|
<div class="liste">
|
||||||
<textarea
|
<div
|
||||||
|
v-for="(f, i) in s.funktionen"
|
||||||
|
:key="i"
|
||||||
|
class="funktion"
|
||||||
|
:class="{ offen: aktiv && aktiv.i === i }"
|
||||||
|
>
|
||||||
|
<div class="zeile kopf">
|
||||||
|
<span class="pfeil">▸</span>
|
||||||
|
<input
|
||||||
v-if="istAktiv('funktion', i)"
|
v-if="istAktiv('funktion', i)"
|
||||||
ref="feldRef"
|
ref="feldRef"
|
||||||
v-model="entwurf"
|
v-model="entwurf"
|
||||||
|
@blur="fertig"
|
||||||
|
@keyup.enter="$event.target.blur()"
|
||||||
|
@keydown.esc="abbrechen"
|
||||||
|
>
|
||||||
|
<div v-else class="funktionstitel klickbar" @click="starte('funktion', i)">{{ f.text }}</div>
|
||||||
|
<button class="icon-knopf rot" title="Funktion entfernen" @click="entferneFunktion(i)">✕</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Regeln und Flow nur beim Hover über die Funktion (oder während der Bearbeitung) -->
|
||||||
|
<div class="details">
|
||||||
|
<!-- Regeln als Punktliste -->
|
||||||
|
<div v-if="f.regeln.length" class="regeln">
|
||||||
|
<div v-for="(r, j) in f.regeln" :key="j" class="zeile">
|
||||||
|
<textarea
|
||||||
|
v-if="istAktiv('regel', i, j)"
|
||||||
|
ref="feldRef"
|
||||||
|
v-model="entwurf"
|
||||||
|
:rows="zeilen(entwurf)"
|
||||||
|
@blur="fertig"
|
||||||
|
@keydown.esc="abbrechen"
|
||||||
|
/>
|
||||||
|
<div v-else class="regel klickbar" @click="starte('regel', i, j)">{{ r }}</div>
|
||||||
|
<button class="icon-knopf rot" title="Regel entfernen" @click="entferneRegel(i, j)">✕</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<button class="hinzu" @click="neueRegel(i)">+ Regel</button>
|
||||||
|
|
||||||
|
<!-- Flow als nummerierte Schritte -->
|
||||||
|
<textarea
|
||||||
|
v-if="istAktiv('flow', i)"
|
||||||
|
ref="feldRef"
|
||||||
|
v-model="entwurf"
|
||||||
:rows="zeilen(entwurf)"
|
:rows="zeilen(entwurf)"
|
||||||
@blur="fertig"
|
@blur="fertig"
|
||||||
@keydown.esc="abbrechen"
|
@keydown.esc="abbrechen"
|
||||||
/>
|
/>
|
||||||
<div v-else class="inhalt klickbar" v-html="md(f.text)" @click="starte('funktion', i)" />
|
<div v-else-if="f.flow" class="flow klickbar" @click="starte('flow', i)">
|
||||||
<button class="icon-knopf rot werkzeug" title="Funktion entfernen" @click="entferneFunktion(i)">✕</button>
|
<div v-for="(schritt, k) in flowSchritte(f.flow)" :key="k" class="schritt">
|
||||||
</div>
|
<span class="nr">{{ k + 1 }}.</span>{{ schritt }}
|
||||||
<div class="regeln">
|
</div>
|
||||||
<div v-for="(r, j) in f.regeln" :key="j" class="zeile">
|
|
||||||
<textarea
|
|
||||||
v-if="istAktiv('regel', i, j)"
|
|
||||||
ref="feldRef"
|
|
||||||
v-model="entwurf"
|
|
||||||
:rows="zeilen(entwurf)"
|
|
||||||
@blur="fertig"
|
|
||||||
@keydown.esc="abbrechen"
|
|
||||||
/>
|
|
||||||
<div v-else class="inhalt klickbar" v-html="md(r)" @click="starte('regel', i, j)" />
|
|
||||||
<button class="icon-knopf rot werkzeug" title="Regel entfernen" @click="entferneRegel(i, j)">✕</button>
|
|
||||||
</div>
|
</div>
|
||||||
<button class="hinzu werkzeug" @click="neueRegel(i)">+ Regel</button>
|
|
||||||
|
<button v-if="!f.flow && !istAktiv('flow', i)" class="hinzu" @click="starte('flow', i)">+ Flow</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<button class="hinzu werkzeug" @click="neueFunktion">+ Funktion</button>
|
</div>
|
||||||
</section>
|
|
||||||
|
|
||||||
<section :class="{ leer: !s.sonstiges && !istAktiv('sonstiges') }">
|
|
||||||
<p class="gruppe">Sonstiges</p>
|
|
||||||
<textarea
|
|
||||||
v-if="istAktiv('sonstiges')"
|
|
||||||
ref="feldRef"
|
|
||||||
v-model="entwurf"
|
|
||||||
:rows="zeilen(entwurf)"
|
|
||||||
@blur="fertig"
|
|
||||||
@keydown.esc="abbrechen"
|
|
||||||
/>
|
|
||||||
<div v-else-if="s.sonstiges" class="inhalt klickbar" v-html="md(s.sonstiges)" @click="starte('sonstiges')" />
|
|
||||||
<p v-else class="hinweis" @click="starte('sonstiges')">Sonstiges ergänzen…</p>
|
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
<p v-if="meldung" class="meldung">{{ meldung }}</p>
|
<p v-if="meldung" class="meldung">{{ meldung }}</p>
|
||||||
@@ -255,9 +307,34 @@ onMounted(() => {
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
|
.karte { cursor: pointer; }
|
||||||
.karte.kern { border-color: var(--akzent); }
|
.karte.kern { border-color: var(--akzent); }
|
||||||
.karte.optional { border-color: var(--gruen); }
|
.karte.optional { border-color: var(--gruen); }
|
||||||
|
|
||||||
|
/* Ausführliche Ansicht: volle Breite, Funktionen in Spalten, alle Details offen.
|
||||||
|
Jede Funktion als eigene Box, Zufüge-Knöpfe nur beim Hover über die Box. */
|
||||||
|
.karte.gross { column-span: all; display: block; }
|
||||||
|
.karte.gross .liste { columns: 2; column-gap: 1.2rem; }
|
||||||
|
.karte.gross section.leer { display: block; }
|
||||||
|
.karte.gross .funktion {
|
||||||
|
break-inside: avoid;
|
||||||
|
border: 1px solid var(--rand);
|
||||||
|
border-radius: 8px;
|
||||||
|
padding: 0.35rem 0.6rem 0.45rem;
|
||||||
|
margin-bottom: 0.6rem;
|
||||||
|
}
|
||||||
|
.karte.gross .pfeil { display: none; }
|
||||||
|
.karte.gross .zeile.kopf { font-weight: 600; padding-left: 0; }
|
||||||
|
.karte.gross .details {
|
||||||
|
display: block;
|
||||||
|
border-left: none;
|
||||||
|
margin: 0.1rem 0 0;
|
||||||
|
padding-left: 0.2rem;
|
||||||
|
}
|
||||||
|
.karte.gross .details .hinzu { display: none; }
|
||||||
|
.karte.gross .funktion:hover .details .hinzu { display: block; }
|
||||||
|
.icon-knopf.bestaetigen { color: var(--rot); }
|
||||||
|
|
||||||
header {
|
header {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: baseline;
|
align-items: baseline;
|
||||||
@@ -265,9 +342,9 @@ header {
|
|||||||
padding-bottom: 0.55rem;
|
padding-bottom: 0.55rem;
|
||||||
border-bottom: 1px solid var(--rand);
|
border-bottom: 1px solid var(--rand);
|
||||||
}
|
}
|
||||||
h3 { margin: 0; font-size: 1rem; cursor: text; flex: 1; }
|
h3 { margin: 0; font-size: 1rem; cursor: pointer; flex: 1; }
|
||||||
.badge { cursor: pointer; }
|
|
||||||
.titel-feld { flex: 1; font-weight: 600; padding: 0.1rem 0.4rem; }
|
.titel-feld { flex: 1; font-weight: 600; padding: 0.1rem 0.4rem; }
|
||||||
|
.badge { cursor: pointer; }
|
||||||
|
|
||||||
section { padding: 0.6rem 0; }
|
section { padding: 0.6rem 0; }
|
||||||
section + section { border-top: 1px solid var(--rand); }
|
section + section { border-top: 1px solid var(--rand); }
|
||||||
@@ -285,7 +362,7 @@ section.leer { display: none; }
|
|||||||
.karte:hover section.leer { display: block; }
|
.karte:hover section.leer { display: block; }
|
||||||
.werkzeug { opacity: 0; transition: opacity 0.1s; }
|
.werkzeug { opacity: 0; transition: opacity 0.1s; }
|
||||||
.karte:hover .werkzeug, .werkzeug:focus { opacity: 1; }
|
.karte:hover .werkzeug, .werkzeug:focus { opacity: 1; }
|
||||||
/* „+ Funktion"/„+ Regel" belegen ohne Hover auch keinen Platz */
|
/* „+ Funktion" belegt ohne Hover auch keinen Platz */
|
||||||
.hinzu.werkzeug { display: none; }
|
.hinzu.werkzeug { display: none; }
|
||||||
.karte:hover .hinzu.werkzeug { display: block; }
|
.karte:hover .hinzu.werkzeug { display: block; }
|
||||||
|
|
||||||
@@ -293,7 +370,7 @@ section.leer { display: none; }
|
|||||||
color: var(--dezent);
|
color: var(--dezent);
|
||||||
font-size: 0.85rem;
|
font-size: 0.85rem;
|
||||||
margin: 0.1rem 0;
|
margin: 0.1rem 0;
|
||||||
cursor: text;
|
cursor: pointer;
|
||||||
}
|
}
|
||||||
.hinzu {
|
.hinzu {
|
||||||
border: none;
|
border: none;
|
||||||
@@ -309,22 +386,70 @@ section.leer { display: none; }
|
|||||||
align-items: baseline;
|
align-items: baseline;
|
||||||
gap: 0.3rem;
|
gap: 0.3rem;
|
||||||
}
|
}
|
||||||
.zeile .inhalt, .zeile textarea { flex: 1; min-width: 0; }
|
.zeile .inhalt, .zeile input, .zeile textarea { flex: 1; min-width: 0; }
|
||||||
.zeile .icon-knopf { font-size: 0.8rem; }
|
/* ✕ nur auf der Zeile, über der die Maus steht */
|
||||||
|
.zeile .icon-knopf { font-size: 0.8rem; opacity: 0; }
|
||||||
|
.zeile:hover .icon-knopf { opacity: 1; }
|
||||||
|
|
||||||
.funktion + .funktion {
|
/* Funktionsliste: Zeilen mit Aufklapp-Pfeil */
|
||||||
margin-top: 0.5rem;
|
.zeile.kopf { padding: 0.15rem 0.2rem; border-radius: 4px; }
|
||||||
padding-top: 0.5rem;
|
.zeile.kopf:hover { background: var(--code-bg); }
|
||||||
border-top: 1px dashed var(--rand);
|
.funktionstitel { flex: 1; }
|
||||||
}
|
.pfeil {
|
||||||
.regeln {
|
|
||||||
margin: 0.25rem 0 0 0.3rem;
|
|
||||||
padding-left: 0.6rem;
|
|
||||||
border-left: 2px solid var(--rand);
|
|
||||||
color: var(--dezent);
|
color: var(--dezent);
|
||||||
|
font-size: 0.75rem;
|
||||||
|
display: inline-block;
|
||||||
|
transition: transform 0.12s;
|
||||||
|
}
|
||||||
|
.funktion:hover .pfeil, .funktion.offen .pfeil {
|
||||||
|
transform: rotate(90deg);
|
||||||
|
color: var(--akzent);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Regeln und Flow erst beim Hover über die Funktion; offen bleiben während Bearbeitung */
|
||||||
|
.details { display: none; }
|
||||||
|
.funktion:hover .details, .funktion.offen .details {
|
||||||
|
display: block;
|
||||||
|
margin: 0.15rem 0 0.45rem 0.35rem;
|
||||||
|
padding: 0.1rem 0 0.1rem 0.75rem;
|
||||||
|
border-left: 2px solid var(--akzent);
|
||||||
font-size: 0.9rem;
|
font-size: 0.9rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Regeln als Punktliste, Flow als nummerierte Schritte */
|
||||||
|
.regeln { margin: 0.15rem 0; }
|
||||||
|
.regel {
|
||||||
|
flex: 1;
|
||||||
|
position: relative;
|
||||||
|
padding-left: 1.15rem;
|
||||||
|
border-radius: 4px;
|
||||||
|
}
|
||||||
|
.regel::before {
|
||||||
|
content: "•";
|
||||||
|
position: absolute;
|
||||||
|
left: 0.35rem;
|
||||||
|
color: var(--dezent);
|
||||||
|
}
|
||||||
|
.regel:hover { background: var(--code-bg); }
|
||||||
|
/* „+ Regel"/„+ Flow" bündig zur Listen-Einrückung */
|
||||||
|
.details .hinzu {
|
||||||
|
display: block;
|
||||||
|
padding: 0.05rem 0 0.05rem 1.15rem;
|
||||||
|
margin: 0.05rem 0;
|
||||||
|
}
|
||||||
|
.flow { border-radius: 4px; }
|
||||||
|
.flow:hover { background: var(--code-bg); }
|
||||||
|
.schritt {
|
||||||
|
position: relative;
|
||||||
|
padding-left: 1.15rem;
|
||||||
|
}
|
||||||
|
.nr {
|
||||||
|
position: absolute;
|
||||||
|
left: 0.15rem;
|
||||||
|
color: var(--dezent);
|
||||||
|
font-size: 0.85em;
|
||||||
|
}
|
||||||
|
|
||||||
textarea {
|
textarea {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
resize: vertical;
|
resize: vertical;
|
||||||
@@ -332,8 +457,10 @@ textarea {
|
|||||||
font-size: 0.85rem;
|
font-size: 0.85rem;
|
||||||
line-height: 1.5;
|
line-height: 1.5;
|
||||||
}
|
}
|
||||||
.klickbar { cursor: text; border-radius: 4px; }
|
.klickbar { cursor: pointer; user-select: none; }
|
||||||
.klickbar:hover { background: var(--code-bg); }
|
.inhalt.klickbar { border-radius: 4px; }
|
||||||
|
.inhalt.klickbar:hover { background: var(--code-bg); }
|
||||||
|
h3, .hinweis { user-select: none; }
|
||||||
|
|
||||||
.inhalt :deep(p) { margin: 0.2rem 0; }
|
.inhalt :deep(p) { margin: 0.2rem 0; }
|
||||||
.inhalt :deep(ul) { margin: 0.2rem 0; padding-left: 1.2rem; }
|
.inhalt :deep(ul) { margin: 0.2rem 0; padding-left: 1.2rem; }
|
||||||
|
|||||||
@@ -69,11 +69,19 @@ watch(() => props.projekt, laden, { immediate: true })
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
|
/* Spaltenlayout: unterschiedlich hohe Karten stapeln sich ohne Lücken */
|
||||||
.gitter {
|
.gitter {
|
||||||
display: grid;
|
columns: 340px;
|
||||||
grid-template-columns: repeat(auto-fill, minmax(320px, 1fr));
|
column-gap: 0.9rem;
|
||||||
gap: 0.9rem;
|
}
|
||||||
align-items: start;
|
.gitter > * {
|
||||||
|
/* inline-block macht die Karte atomar — Firefox fragmentiert sie sonst
|
||||||
|
trotz break-inside: avoid mitten im Inhalt */
|
||||||
|
break-inside: avoid;
|
||||||
|
display: inline-block;
|
||||||
|
vertical-align: top;
|
||||||
|
margin-bottom: 0.9rem;
|
||||||
|
width: 100%;
|
||||||
}
|
}
|
||||||
/* Geisterkarte statt Kopfzeilen-Knopf */
|
/* Geisterkarte statt Kopfzeilen-Knopf */
|
||||||
.neu {
|
.neu {
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ const meldung = ref('')
|
|||||||
const bearbeitet = ref(null) // Projektname, der gerade umbenannt wird
|
const bearbeitet = ref(null) // Projektname, der gerade umbenannt wird
|
||||||
const umbenennung = ref('')
|
const umbenennung = ref('')
|
||||||
const umbenennFeld = ref(null)
|
const umbenennFeld = ref(null)
|
||||||
|
const loeschBestaetigung = ref(null) // Projektname, dessen ✕ auf Bestätigung wartet
|
||||||
|
|
||||||
async function anlegen() {
|
async function anlegen() {
|
||||||
meldung.value = ''
|
meldung.value = ''
|
||||||
@@ -49,8 +50,13 @@ async function umbenennen(alt) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async function loeschen(name) {
|
async function loeschen(name) {
|
||||||
|
// Erster Klick: Icon wird zum Bestätigungs-Icon, zweiter Klick löscht
|
||||||
|
if (loeschBestaetigung.value !== name) {
|
||||||
|
loeschBestaetigung.value = name
|
||||||
|
return
|
||||||
|
}
|
||||||
|
loeschBestaetigung.value = null
|
||||||
meldung.value = ''
|
meldung.value = ''
|
||||||
if (!confirm(`Projekt „${name}“ mit allen Features löschen?`)) return
|
|
||||||
try {
|
try {
|
||||||
await projektLoeschen(name)
|
await projektLoeschen(name)
|
||||||
emit('geaendert')
|
emit('geaendert')
|
||||||
@@ -72,6 +78,7 @@ async function loeschen(name) {
|
|||||||
:key="p.name"
|
:key="p.name"
|
||||||
:class="{ aktiv: p.name === aktiv }"
|
:class="{ aktiv: p.name === aktiv }"
|
||||||
@click="emit('auswaehlen', p.name)"
|
@click="emit('auswaehlen', p.name)"
|
||||||
|
@mouseleave="loeschBestaetigung = null"
|
||||||
>
|
>
|
||||||
<template v-if="bearbeitet === p.name">
|
<template v-if="bearbeitet === p.name">
|
||||||
<input
|
<input
|
||||||
@@ -88,7 +95,12 @@ async function loeschen(name) {
|
|||||||
<span class="anzahl">{{ p.anzahl }}</span>
|
<span class="anzahl">{{ p.anzahl }}</span>
|
||||||
<span class="aktionen" @click.stop>
|
<span class="aktionen" @click.stop>
|
||||||
<button class="icon-knopf" title="Umbenennen" @click="starteUmbenennen(p.name)">✎</button>
|
<button class="icon-knopf" title="Umbenennen" @click="starteUmbenennen(p.name)">✎</button>
|
||||||
<button class="icon-knopf rot" title="Löschen" @click="loeschen(p.name)">✕</button>
|
<button
|
||||||
|
class="icon-knopf rot"
|
||||||
|
:class="{ bestaetigen: loeschBestaetigung === p.name }"
|
||||||
|
:title="loeschBestaetigung === p.name ? 'Wirklich löschen?' : 'Löschen'"
|
||||||
|
@click="loeschen(p.name)"
|
||||||
|
>{{ loeschBestaetigung === p.name ? '✓' : '✕' }}</button>
|
||||||
</span>
|
</span>
|
||||||
</template>
|
</template>
|
||||||
</li>
|
</li>
|
||||||
@@ -147,6 +159,7 @@ li.aktiv { background: var(--bg); font-weight: 600; }
|
|||||||
li:hover .aktionen { display: inline-flex; }
|
li:hover .aktionen { display: inline-flex; }
|
||||||
li:hover .anzahl { display: none; }
|
li:hover .anzahl { display: none; }
|
||||||
li input { width: 100%; padding: 0.1rem 0.4rem; }
|
li input { width: 100%; padding: 0.1rem 0.4rem; }
|
||||||
|
.icon-knopf.bestaetigen { color: var(--rot); }
|
||||||
.neu {
|
.neu {
|
||||||
display: flex;
|
display: flex;
|
||||||
gap: 0.4rem;
|
gap: 0.4rem;
|
||||||
|
|||||||
Reference in New Issue
Block a user