update
This commit is contained in:
@@ -65,7 +65,7 @@ Spezifische Hinweise für Features
|
|||||||
<!-- testen -->
|
<!-- testen -->
|
||||||
- Vollmodus öffnen
|
- Vollmodus öffnen
|
||||||
- Klick auf Regeln
|
- Klick auf Regeln
|
||||||
- Inhalt anpassen
|
- Inhalt angepasst
|
||||||
<!-- funktion -->
|
<!-- funktion -->
|
||||||
Titel anpassen
|
Titel anpassen
|
||||||
<!-- regeln -->
|
<!-- regeln -->
|
||||||
@@ -74,6 +74,7 @@ Titel anpassen
|
|||||||
- Vollmodus öffnen
|
- Vollmodus öffnen
|
||||||
- Klick auf Titel
|
- Klick auf Titel
|
||||||
- Inhalt ändern
|
- Inhalt ändern
|
||||||
|
- Titel aktualisiert
|
||||||
<!-- funktion -->
|
<!-- funktion -->
|
||||||
Ziel anpassen
|
Ziel anpassen
|
||||||
<!-- ziel -->
|
<!-- ziel -->
|
||||||
@@ -82,3 +83,4 @@ Beschreibt kurz das Kernziel vom Feature
|
|||||||
- Vollmodus öffnen
|
- Vollmodus öffnen
|
||||||
- Klick auf Ziel
|
- Klick auf Ziel
|
||||||
- Inhalt anpassen
|
- Inhalt anpassen
|
||||||
|
- Ziel angepasst
|
||||||
|
|||||||
@@ -32,7 +32,7 @@ Funktion erstellen
|
|||||||
- RMB auf Featurecard
|
- RMB auf Featurecard
|
||||||
- Klick auf "Funktion erstellen"
|
- Klick auf "Funktion erstellen"
|
||||||
- Namen im Modal eingeben
|
- Namen im Modal eingeben
|
||||||
- Bestätigen
|
- Funktion erstellt
|
||||||
<!-- funktion -->
|
<!-- funktion -->
|
||||||
Regeln anpassen
|
Regeln anpassen
|
||||||
<!-- ziel -->
|
<!-- ziel -->
|
||||||
|
|||||||
@@ -50,10 +50,10 @@ Prüfung ausführen
|
|||||||
- Prüf-Icon in Sidebar Navigation
|
- Prüf-Icon in Sidebar Navigation
|
||||||
<!-- testen -->
|
<!-- testen -->
|
||||||
- LMB auf Icon
|
- LMB auf Icon
|
||||||
- Prüfung wird gestartet
|
- Prüfung gestartet
|
||||||
- Rechte Sidebar geöffnet
|
- Rechte Sidebar geöffnet
|
||||||
- Fortschritt unter Icon angezeigt
|
- Fortschritt unter Icon angezeigt
|
||||||
- Probleme werden live gelistet
|
- Probleme live gelistet
|
||||||
<!-- funktion -->
|
<!-- funktion -->
|
||||||
Stellen einzeln prüfen
|
Stellen einzeln prüfen
|
||||||
<!-- ziel -->
|
<!-- ziel -->
|
||||||
|
|||||||
@@ -32,7 +32,7 @@ Projekt aufrufen
|
|||||||
Projekt entfernen
|
Projekt entfernen
|
||||||
<!-- regeln -->
|
<!-- regeln -->
|
||||||
- x-Icon
|
- x-Icon
|
||||||
- Auch Features entfernen
|
- Features mit entfernen
|
||||||
<!-- testen -->
|
<!-- testen -->
|
||||||
- RMB auf Projektname
|
- RMB auf Projektname
|
||||||
- Klick auf "Projekt entfernen"
|
- Klick auf "Projekt entfernen"
|
||||||
@@ -48,3 +48,4 @@ Projekt erstellen
|
|||||||
- Klick auf "Projekt erstellen"
|
- Klick auf "Projekt erstellen"
|
||||||
- Im Modal Namen eingeben
|
- Im Modal Namen eingeben
|
||||||
- Bestätigen
|
- Bestätigen
|
||||||
|
- Projekt erstellt
|
||||||
|
|||||||
@@ -107,15 +107,30 @@ onMounted(async () => {
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
|
/* App-Shell: feste Höhe, drei eigene Scrollbereiche. Vorher wanderten die
|
||||||
|
Leisten beim Scrollen mit nach oben weg. */
|
||||||
.raster {
|
.raster {
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-template-columns: 220px 1fr;
|
grid-template-columns: 15rem 1fr;
|
||||||
min-height: 100vh;
|
height: 100vh;
|
||||||
|
overflow: hidden;
|
||||||
}
|
}
|
||||||
.raster.pruefung { grid-template-columns: 220px 1fr 260px; }
|
.raster.pruefung { grid-template-columns: 15rem 1fr 20rem; }
|
||||||
main {
|
main {
|
||||||
padding: 1.2rem 1.5rem;
|
padding: 1.2rem 1.5rem;
|
||||||
min-width: 0;
|
min-width: 0;
|
||||||
|
overflow-y: auto;
|
||||||
|
overscroll-behavior: contain;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Schmale Fenster: die Projektliste legt sich als Leiste nach oben */
|
||||||
|
@media (max-width: 46rem) {
|
||||||
|
.raster, .raster.pruefung {
|
||||||
|
grid-template-columns: 1fr;
|
||||||
|
grid-template-rows: auto 1fr;
|
||||||
|
}
|
||||||
|
.raster.pruefung { grid-template-rows: auto 1fr auto; }
|
||||||
|
main { padding: 0.9rem 1rem; }
|
||||||
}
|
}
|
||||||
.leer {
|
.leer {
|
||||||
color: var(--dezent);
|
color: var(--dezent);
|
||||||
|
|||||||
@@ -2,11 +2,11 @@
|
|||||||
--bg: #f6f7f9;
|
--bg: #f6f7f9;
|
||||||
--panel: #ffffff;
|
--panel: #ffffff;
|
||||||
--text: #1a202c;
|
--text: #1a202c;
|
||||||
--dezent: #64748b;
|
--dezent: #556070; /* dunkler als slate-500: 4.34 war unter dem Lesbarkeitsminimum */
|
||||||
--rand: #e2e8f0;
|
--rand: #e2e8f0;
|
||||||
--akzent: #2563eb;
|
--akzent: #2563eb;
|
||||||
--akzent-text: #ffffff;
|
--akzent-text: #ffffff;
|
||||||
--gruen: #16a34a;
|
--gruen: #166534; /* auf gruen-bg reichte green-600 nicht (3.0) */
|
||||||
--gruen-bg: #dcfce7;
|
--gruen-bg: #dcfce7;
|
||||||
--gelb: #b45309;
|
--gelb: #b45309;
|
||||||
--gelb-bg: #fef3c7;
|
--gelb-bg: #fef3c7;
|
||||||
@@ -75,16 +75,24 @@ input, textarea {
|
|||||||
padding: 0.9rem 1.1rem;
|
padding: 0.9rem 1.1rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Rahmenlose Icon-Knöpfe (✎ ✕ …) */
|
/* Rahmenlose Icon-Knöpfe (✎ ✕ …) — mindestens 1.75rem Klickfläche */
|
||||||
.icon-knopf {
|
.icon-knopf {
|
||||||
border: none;
|
border: none;
|
||||||
background: none;
|
background: none;
|
||||||
padding: 0.1rem 0.3rem;
|
padding: 0;
|
||||||
|
min-width: 1.75rem;
|
||||||
|
height: 1.75rem;
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
border-radius: 6px;
|
||||||
color: var(--dezent);
|
color: var(--dezent);
|
||||||
font-size: 0.95rem;
|
font-size: 0.95rem;
|
||||||
|
line-height: 1;
|
||||||
}
|
}
|
||||||
.icon-knopf:hover { color: var(--text); }
|
.icon-knopf:hover { color: var(--text); background: var(--bg); }
|
||||||
.icon-knopf.rot:hover { color: var(--rot); }
|
.icon-knopf.rot:hover { color: var(--rot); background: var(--rot-bg); }
|
||||||
|
.icon-knopf:focus-visible { outline: 2px solid var(--akzent); outline-offset: 1px; }
|
||||||
|
|
||||||
.meldung { color: var(--rot); font-size: 0.85rem; }
|
.meldung { color: var(--rot); font-size: 0.85rem; }
|
||||||
|
|
||||||
@@ -135,7 +143,10 @@ input, textarea {
|
|||||||
.badge {
|
.badge {
|
||||||
font-size: 0.72rem;
|
font-size: 0.72rem;
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
padding: 0.1rem 0.5rem;
|
min-height: 1.5rem;
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
padding: 0.1rem 0.6rem;
|
||||||
border-radius: 99px;
|
border-radius: 99px;
|
||||||
text-transform: uppercase;
|
text-transform: uppercase;
|
||||||
letter-spacing: 0.04em;
|
letter-spacing: 0.04em;
|
||||||
|
|||||||
@@ -272,6 +272,7 @@ function alleAblehnen() {
|
|||||||
<template>
|
<template>
|
||||||
<article
|
<article
|
||||||
class="karte"
|
class="karte"
|
||||||
|
:data-feature="feature.id"
|
||||||
:class="[s.art, { gross, fehlerhaft: probleme.length }]"
|
:class="[s.art, { gross, fehlerhaft: probleme.length }]"
|
||||||
@mousedown.middle.prevent
|
@mousedown.middle.prevent
|
||||||
@auxclick.middle.prevent="wechsleAnsicht"
|
@auxclick.middle.prevent="wechsleAnsicht"
|
||||||
@@ -306,8 +307,17 @@ function alleAblehnen() {
|
|||||||
@click="wechsleAnsicht"
|
@click="wechsleAnsicht"
|
||||||
>👁</button>
|
>👁</button>
|
||||||
<span v-if="probleme.length" class="alle">
|
<span v-if="probleme.length" class="alle">
|
||||||
<button class="icon-knopf" title="Alle annehmen" @click="alleAnnehmen">✓</button>
|
<span class="anzahl">{{ probleme.length }}</span>
|
||||||
<button class="icon-knopf rot" title="Alle ablehnen" @click="alleAblehnen">✕</button>
|
<button
|
||||||
|
class="icon-knopf uebernehmen"
|
||||||
|
:title="`Alle ${probleme.length} Vorschläge übernehmen`"
|
||||||
|
@click="alleAnnehmen"
|
||||||
|
>✓</button>
|
||||||
|
<button
|
||||||
|
class="icon-knopf rot"
|
||||||
|
:title="`Alle ${probleme.length} Vorschläge verwerfen`"
|
||||||
|
@click="alleAblehnen"
|
||||||
|
>✕</button>
|
||||||
</span>
|
</span>
|
||||||
</header>
|
</header>
|
||||||
<ProblemHinweis :probleme="problemeAn('titel')" @annehmen="annehmen" @ablehnen="ablehnen" />
|
<ProblemHinweis :probleme="problemeAn('titel')" @annehmen="annehmen" @ablehnen="ablehnen" />
|
||||||
@@ -482,9 +492,33 @@ function alleAblehnen() {
|
|||||||
.karte.optional { border-color: var(--gruen); }
|
.karte.optional { border-color: var(--gruen); }
|
||||||
/* Prüfung: fehlerhafte Karte und Stelle rot */
|
/* Prüfung: fehlerhafte Karte und Stelle rot */
|
||||||
.karte.fehlerhaft { border-color: var(--rot); }
|
.karte.fehlerhaft { border-color: var(--rot); }
|
||||||
|
/* Kurz hervorheben, wenn das Panel hierher springt */
|
||||||
|
.karte.gesucht { box-shadow: 0 0 0 3px var(--akzent); }
|
||||||
|
.karte { transition: box-shadow 0.25s; }
|
||||||
.fehler { color: var(--rot); }
|
.fehler { color: var(--rot); }
|
||||||
.badge.fehler { color: var(--rot); background: var(--rot-bg); }
|
.badge.fehler { color: var(--rot); background: var(--rot-bg); }
|
||||||
.alle { white-space: nowrap; }
|
/* Sammelaktionen der Karte — abgesetzt, damit sie nicht wie die Knöpfe
|
||||||
|
eines einzelnen Vorschlags aussehen */
|
||||||
|
.alle {
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 0.15rem;
|
||||||
|
white-space: nowrap;
|
||||||
|
margin-left: 0.15rem;
|
||||||
|
padding-left: 0.4rem;
|
||||||
|
border-left: 1px solid var(--rand);
|
||||||
|
}
|
||||||
|
.anzahl {
|
||||||
|
font-size: 0.7rem;
|
||||||
|
font-weight: 700;
|
||||||
|
color: var(--akzent-text);
|
||||||
|
background: var(--rot);
|
||||||
|
border-radius: 99px;
|
||||||
|
min-width: 1.1rem;
|
||||||
|
padding: 0.05rem 0.3rem;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
.icon-knopf.uebernehmen:hover { color: var(--gruen); background: var(--gruen-bg); }
|
||||||
|
|
||||||
/* Vollmodus: volle Breite, Funktionen als Boxen in responsiven Spalten,
|
/* Vollmodus: volle Breite, Funktionen als Boxen in responsiven Spalten,
|
||||||
alle Details offen */
|
alle Details offen */
|
||||||
@@ -553,7 +587,9 @@ section.leer { display: none; }
|
|||||||
background: none;
|
background: none;
|
||||||
color: var(--dezent);
|
color: var(--dezent);
|
||||||
font-size: 0.8rem;
|
font-size: 0.8rem;
|
||||||
padding: 0.1rem 0;
|
min-height: 1.5rem;
|
||||||
|
padding: 0.1rem 0.3rem;
|
||||||
|
margin-left: -0.3rem;
|
||||||
}
|
}
|
||||||
.hinzu:hover { color: var(--akzent); }
|
.hinzu:hover { color: var(--akzent); }
|
||||||
|
|
||||||
|
|||||||
@@ -117,10 +117,14 @@ watch(() => props.projekt, laden, { immediate: true })
|
|||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
.bereich { min-height: 100%; }
|
.bereich { min-height: 100%; }
|
||||||
/* Spaltenlayout: unterschiedlich hohe Karten stapeln sich ohne Lücken */
|
/* Spaltenlayout: unterschiedlich hohe Karten stapeln sich ohne Lücken.
|
||||||
|
Die Breite ist gedeckelt — auf sehr breiten Schirmen klumpten die Karten sonst
|
||||||
|
links und rechts blieb eine tote Fläche. */
|
||||||
.gitter {
|
.gitter {
|
||||||
columns: 340px;
|
columns: 22rem;
|
||||||
column-gap: 0.9rem;
|
column-gap: 0.9rem;
|
||||||
|
max-width: 100rem;
|
||||||
|
margin: 0 auto;
|
||||||
}
|
}
|
||||||
.gitter > * {
|
.gitter > * {
|
||||||
/* inline-block macht die Karte atomar — Firefox fragmentiert sie sonst
|
/* inline-block macht die Karte atomar — Firefox fragmentiert sie sonst
|
||||||
|
|||||||
@@ -1,80 +1,115 @@
|
|||||||
<script setup>
|
<script setup>
|
||||||
import { vorschlagsArt } from '../composables/pruefung.js'
|
import { vergleich, vorschlagsArt } from '../composables/pruefung.js'
|
||||||
|
|
||||||
defineProps({
|
defineProps({
|
||||||
probleme: { type: Array, required: true },
|
probleme: { type: Array, required: true },
|
||||||
})
|
})
|
||||||
const emit = defineEmits(['annehmen', 'ablehnen'])
|
const emit = defineEmits(['annehmen', 'ablehnen'])
|
||||||
|
|
||||||
// Was der Vorschlag bewirkt, steht nicht in der Antwort — es ergibt sich aus dem
|
// Was das ✓ bewirkt, soll man vorher sehen. Darum die Gegenüberstellung
|
||||||
// Vergleich mit der alten Zeile. Bei entfernen steht dort die Zeile, die weggeht.
|
// statt eines bloßen Ersatztextes.
|
||||||
const ZEICHEN = { ergaenzen: '+', entfernen: '−' }
|
const TITEL = { ergaenzen: 'Ergänzen', entfernen: 'Streichen', anpassen: 'Ändern' }
|
||||||
const zeichen = (p) => ZEICHEN[vorschlagsArt(p)] || ''
|
const ZEICHEN = { weg: '−', neu: '+', gleich: '' }
|
||||||
const text = (p) => (vorschlagsArt(p) === 'entfernen' ? p.original : p.vorschlag)
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div v-for="(p, i) in probleme" :key="i" class="hinweis-block" @click.stop>
|
<div v-for="(p, i) in probleme" :key="i" class="hinweis-block" @click.stop>
|
||||||
<div class="kopf">
|
<div class="kopf">
|
||||||
<p class="problem">{{ p.problem }}</p>
|
<span class="art">{{ TITEL[vorschlagsArt(p)] }}</span>
|
||||||
<span class="knoepfe">
|
<span class="knoepfe">
|
||||||
<button class="icon-knopf" title="Annehmen" @click="emit('annehmen', p)">✓</button>
|
<button class="icon-knopf uebernehmen" title="Übernehmen" @click="emit('annehmen', p)">✓</button>
|
||||||
<button class="icon-knopf rot" title="Ablehnen" @click="emit('ablehnen', p)">✕</button>
|
<button class="icon-knopf rot" title="Verwerfen" @click="emit('ablehnen', p)">✕</button>
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<p class="vorschlag" :class="vorschlagsArt(p)">
|
<p class="problem">{{ p.problem }}</p>
|
||||||
<span v-if="zeichen(p)" class="zeichen">{{ zeichen(p) }}</span>{{ text(p) }}
|
|
||||||
</p>
|
<ul class="vergleich">
|
||||||
<p v-if="p.regel" class="regel" :title="p.regel">{{ p.regel }}</p>
|
<li v-for="(z, k) in vergleich(p)" :key="k" :class="z.art">
|
||||||
|
<span class="zeichen">{{ ZEICHEN[z.art] }}</span><span class="text">{{ z.text }}</span>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
<p v-if="p.regel" class="regel">{{ p.regel }}</p>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
.hinweis-block {
|
.hinweis-block {
|
||||||
border-left: 2px solid var(--rot);
|
border: 1px solid var(--rand);
|
||||||
background: var(--rot-bg);
|
border-left: 3px solid var(--akzent);
|
||||||
border-radius: 0 4px 4px 0;
|
background: var(--panel);
|
||||||
padding: 0.25rem 0.5rem 0.35rem;
|
border-radius: 0 6px 6px 0;
|
||||||
margin: 0.15rem 0 0.3rem;
|
padding: 0.4rem 0.55rem 0.45rem;
|
||||||
|
margin: 0.3rem 0 0.4rem;
|
||||||
min-width: 0;
|
min-width: 0;
|
||||||
cursor: default;
|
cursor: default;
|
||||||
}
|
}
|
||||||
|
/* Zwei feste Zeilen: Marke und Knöpfe oben, Begründung darunter. Nebeneinander
|
||||||
|
zerfaserte der Kopf auf schmalen Karten. */
|
||||||
.kopf {
|
.kopf {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: baseline;
|
align-items: center;
|
||||||
gap: 0.4rem;
|
gap: 0.4rem;
|
||||||
}
|
}
|
||||||
.problem {
|
.knoepfe { margin-left: auto; }
|
||||||
margin: 0;
|
.art {
|
||||||
flex: 1;
|
font-size: 0.66rem;
|
||||||
min-width: 0;
|
font-weight: 700;
|
||||||
font-size: 0.72rem;
|
|
||||||
text-transform: uppercase;
|
text-transform: uppercase;
|
||||||
letter-spacing: 0.04em;
|
letter-spacing: 0.06em;
|
||||||
line-height: 1.35;
|
color: var(--akzent-text);
|
||||||
color: var(--rot);
|
background: var(--akzent);
|
||||||
}
|
border-radius: 4px;
|
||||||
.knoepfe { white-space: nowrap; }
|
padding: 0.05rem 0.35rem;
|
||||||
.vorschlag {
|
|
||||||
margin: 0.1rem 0 0;
|
|
||||||
font-size: 0.85rem;
|
|
||||||
overflow-wrap: anywhere;
|
|
||||||
white-space: pre-line; /* mehrzeilige Vorschläge bleiben mehrzeilig */
|
|
||||||
}
|
|
||||||
.vorschlag.entfernen { text-decoration: line-through; color: var(--dezent); }
|
|
||||||
/* Die belegte Katalogzeile — macht den Vorschlag nachprüfbar */
|
|
||||||
.regel {
|
|
||||||
margin: 0.15rem 0 0;
|
|
||||||
font-size: 0.72rem;
|
|
||||||
color: var(--dezent);
|
|
||||||
overflow: hidden;
|
|
||||||
text-overflow: ellipsis;
|
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
}
|
}
|
||||||
|
.problem {
|
||||||
|
margin: 0.15rem 0 0.3rem;
|
||||||
|
font-size: 0.78rem;
|
||||||
|
line-height: 1.35;
|
||||||
|
color: var(--dezent);
|
||||||
|
}
|
||||||
|
.knoepfe { display: flex; white-space: nowrap; }
|
||||||
|
.icon-knopf.uebernehmen:hover { color: var(--gruen); background: var(--gruen-bg); }
|
||||||
|
|
||||||
|
/* Gegenüberstellung: was weg ist, was neu ist, was bleibt */
|
||||||
|
.vergleich {
|
||||||
|
list-style: none;
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
font-size: 0.85rem;
|
||||||
|
}
|
||||||
|
.vergleich li {
|
||||||
|
display: flex;
|
||||||
|
gap: 0.15rem;
|
||||||
|
border-radius: 3px;
|
||||||
|
padding: 0.02rem 0.15rem;
|
||||||
|
}
|
||||||
.zeichen {
|
.zeichen {
|
||||||
display: inline-block;
|
flex: none;
|
||||||
width: 1rem;
|
width: 0.9rem;
|
||||||
font-weight: 600;
|
font-weight: 700;
|
||||||
text-decoration: none;
|
text-align: center;
|
||||||
|
}
|
||||||
|
.text { min-width: 0; overflow-wrap: anywhere; }
|
||||||
|
.vergleich li.weg {
|
||||||
|
color: var(--rot);
|
||||||
|
background: var(--rot-bg);
|
||||||
|
}
|
||||||
|
.vergleich li.weg .text { text-decoration: line-through; }
|
||||||
|
.vergleich li.neu {
|
||||||
|
color: var(--gruen);
|
||||||
|
background: var(--gruen-bg);
|
||||||
|
}
|
||||||
|
.vergleich li.gleich { color: var(--dezent); }
|
||||||
|
|
||||||
|
/* Die belegte Katalogzeile — macht den Vorschlag nachprüfbar */
|
||||||
|
.regel {
|
||||||
|
margin: 0.35rem 0 0;
|
||||||
|
padding-top: 0.25rem;
|
||||||
|
border-top: 1px solid var(--rand);
|
||||||
|
font-size: 0.72rem;
|
||||||
|
line-height: 1.35;
|
||||||
|
color: var(--dezent);
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -1,5 +1,21 @@
|
|||||||
<script setup>
|
<script setup>
|
||||||
import { laeuft, prozent, fertig, gesamt, meldung, probleme, schliesse } from '../composables/pruefung.js'
|
import { laeuft, prozent, fertig, gesamt, meldung, probleme, schliesse } from '../composables/pruefung.js'
|
||||||
|
|
||||||
|
// Nur die erste Zeile — mehrzeilige Stellen liefen hier sonst zu einem Klumpen zusammen
|
||||||
|
function erste(text) {
|
||||||
|
const zeilen = (text || '').split('\n').filter(z => z.trim())
|
||||||
|
if (!zeilen.length) return ''
|
||||||
|
return zeilen.length > 1 ? `${zeilen[0]} …` : zeilen[0]
|
||||||
|
}
|
||||||
|
|
||||||
|
// Die Liste ist ein Inhaltsverzeichnis: ein Klick führt zur Karte
|
||||||
|
function springe(p) {
|
||||||
|
const karte = document.querySelector(`[data-feature="${CSS.escape(p.feature)}"]`)
|
||||||
|
if (!karte) return
|
||||||
|
karte.scrollIntoView({ behavior: 'smooth', block: 'center' })
|
||||||
|
karte.classList.add('gesucht')
|
||||||
|
setTimeout(() => karte.classList.remove('gesucht'), 1200)
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
@@ -22,9 +38,11 @@ import { laeuft, prozent, fertig, gesamt, meldung, probleme, schliesse } from '.
|
|||||||
|
|
||||||
<ul>
|
<ul>
|
||||||
<li v-for="(p, i) in probleme" :key="i">
|
<li v-for="(p, i) in probleme" :key="i">
|
||||||
<span class="stelle">{{ p.feature }}<template v-if="p.funktion"> · {{ p.funktion }}</template> · {{ p.feld }}</span>
|
<button class="eintrag" @click="springe(p)">
|
||||||
<span class="problem">{{ p.problem }}</span>
|
<span class="stelle">{{ p.feature }}<template v-if="p.funktion"> · {{ p.funktion }}</template> · {{ p.feld }}</span>
|
||||||
<span class="original">{{ p.original }}</span>
|
<span class="problem">{{ p.problem }}</span>
|
||||||
|
<span class="original">{{ erste(p.original) }}</span>
|
||||||
|
</button>
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
</aside>
|
</aside>
|
||||||
@@ -38,7 +56,17 @@ import { laeuft, prozent, fertig, gesamt, meldung, probleme, schliesse } from '.
|
|||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
gap: 0.4rem;
|
gap: 0.4rem;
|
||||||
|
min-height: 0;
|
||||||
overflow-y: auto;
|
overflow-y: auto;
|
||||||
|
overscroll-behavior: contain;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 46rem) {
|
||||||
|
.panel {
|
||||||
|
border-left: none;
|
||||||
|
border-top: 1px solid var(--rand);
|
||||||
|
max-height: 33vh;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
header {
|
header {
|
||||||
display: flex;
|
display: flex;
|
||||||
@@ -56,13 +84,21 @@ header {
|
|||||||
.fortschritt { margin: 0; font-size: 0.9rem; }
|
.fortschritt { margin: 0; font-size: 0.9rem; }
|
||||||
.dezent { color: var(--dezent); font-size: 0.85rem; }
|
.dezent { color: var(--dezent); font-size: 0.85rem; }
|
||||||
ul { list-style: none; margin: 0.3rem 0 0; padding: 0; }
|
ul { list-style: none; margin: 0.3rem 0 0; padding: 0; }
|
||||||
li {
|
li { border-top: 1px solid var(--rand); }
|
||||||
border-top: 1px solid var(--rand);
|
.eintrag {
|
||||||
padding: 0.35rem 0;
|
width: 100%;
|
||||||
|
border: none;
|
||||||
|
background: none;
|
||||||
|
border-radius: 6px;
|
||||||
|
text-align: left;
|
||||||
|
padding: 0.4rem 0.4rem;
|
||||||
|
margin: 0.1rem -0.4rem;
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
gap: 0.1rem;
|
gap: 0.1rem;
|
||||||
}
|
}
|
||||||
|
.eintrag:hover { background: var(--bg); }
|
||||||
|
.eintrag:focus-visible { outline: 2px solid var(--akzent); outline-offset: -2px; }
|
||||||
.stelle {
|
.stelle {
|
||||||
font-size: 0.72rem;
|
font-size: 0.72rem;
|
||||||
text-transform: uppercase;
|
text-transform: uppercase;
|
||||||
|
|||||||
@@ -79,7 +79,7 @@ async function entfernen() {
|
|||||||
<aside @contextmenu.prevent="leistenMenue">
|
<aside @contextmenu.prevent="leistenMenue">
|
||||||
<header>
|
<header>
|
||||||
<p class="gruppe">Projekte</p>
|
<p class="gruppe">Projekte</p>
|
||||||
<slot />
|
<span class="werkzeuge"><slot /></span>
|
||||||
</header>
|
</header>
|
||||||
<ul>
|
<ul>
|
||||||
<li
|
<li
|
||||||
@@ -131,13 +131,35 @@ aside {
|
|||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
gap: 0.4rem;
|
gap: 0.4rem;
|
||||||
|
min-height: 0; /* sonst wächst die Leiste über die Zeile hinaus */
|
||||||
|
overflow-y: auto; /* lange Projektlisten scrollen für sich */
|
||||||
|
overscroll-behavior: contain;
|
||||||
}
|
}
|
||||||
header {
|
header {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: space-between;
|
gap: 0.4rem;
|
||||||
margin-bottom: 0.2rem;
|
margin-bottom: 0.2rem;
|
||||||
}
|
}
|
||||||
|
/* Werkzeuge rechts zusammen, nicht über die Breite verteilt */
|
||||||
|
.werkzeuge {
|
||||||
|
margin-left: auto;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 0.1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 46rem) {
|
||||||
|
aside {
|
||||||
|
border-right: none;
|
||||||
|
border-bottom: 1px solid var(--rand);
|
||||||
|
padding: 0.6rem 1rem;
|
||||||
|
max-height: 40vh;
|
||||||
|
}
|
||||||
|
ul { display: flex; flex-wrap: wrap; gap: 0.3rem; }
|
||||||
|
li { padding: 0.25rem 0.7rem; background: var(--bg); }
|
||||||
|
.name { flex: none; }
|
||||||
|
}
|
||||||
.gruppe {
|
.gruppe {
|
||||||
font-size: 0.72rem;
|
font-size: 0.72rem;
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
|
|||||||
@@ -12,4 +12,9 @@ export function schliesseMenue() {
|
|||||||
kontextMenue.value = null
|
kontextMenue.value = null
|
||||||
}
|
}
|
||||||
|
|
||||||
window.addEventListener('click', schliesseMenue)
|
// Capture-Phase: Elemente wie der Vorschlags-Hinweis stoppen das Bubbling mit
|
||||||
|
// @click.stop — in der Bubble-Phase bliebe das Menü dann offen stehen.
|
||||||
|
window.addEventListener('click', (e) => {
|
||||||
|
if (e.target.closest?.('.kontextmenue')) return // Auswahl darf zuerst laufen
|
||||||
|
schliesseMenue()
|
||||||
|
}, true)
|
||||||
|
|||||||
@@ -39,6 +39,36 @@ export function vorschlagsArt(p) {
|
|||||||
return 'anpassen'
|
return 'anpassen'
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Längste gemeinsame Teilfolge — die Grundlage für „was bleibt, was ändert sich"
|
||||||
|
function _gemeinsam(a, b) {
|
||||||
|
const t = Array.from({ length: a.length + 1 }, () => new Array(b.length + 1).fill(0))
|
||||||
|
for (let i = a.length - 1; i >= 0; i--) {
|
||||||
|
for (let j = b.length - 1; j >= 0; j--) {
|
||||||
|
t[i][j] = a[i] === b[j] ? t[i + 1][j + 1] + 1 : Math.max(t[i + 1][j], t[i][j + 1])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return t
|
||||||
|
}
|
||||||
|
|
||||||
|
// Der Vorschlag als Gegenüberstellung: was weg ist, was neu ist, was bleibt.
|
||||||
|
// Nur so sieht man vor dem Klick, was das ✓ wirklich tut.
|
||||||
|
export function vergleich(p) {
|
||||||
|
const alt = (p.original || '').split('\n').map(z => z.trim()).filter(Boolean)
|
||||||
|
const neu = vorschlagsZeilen(p)
|
||||||
|
const t = _gemeinsam(alt, neu)
|
||||||
|
const raus = []
|
||||||
|
let i = 0
|
||||||
|
let j = 0
|
||||||
|
while (i < alt.length && j < neu.length) {
|
||||||
|
if (alt[i] === neu[j]) raus.push({ art: 'gleich', text: alt[i++] }), j++
|
||||||
|
else if (t[i + 1][j] >= t[i][j + 1]) raus.push({ art: 'weg', text: alt[i++] })
|
||||||
|
else raus.push({ art: 'neu', text: neu[j++] })
|
||||||
|
}
|
||||||
|
while (i < alt.length) raus.push({ art: 'weg', text: alt[i++] })
|
||||||
|
while (j < neu.length) raus.push({ art: 'neu', text: neu[j++] })
|
||||||
|
return raus
|
||||||
|
}
|
||||||
|
|
||||||
// Neue Probleme werden angehängt, nicht ersetzt. Der Server kennt nur die Namen von
|
// Neue Probleme werden angehängt, nicht ersetzt. Der Server kennt nur die Namen von
|
||||||
// vor dem Lauf; würde die Liste jede Sekunde neu gesetzt, verlöre benenneUm() seine Wirkung.
|
// vor dem Lauf; würde die Liste jede Sekunde neu gesetzt, verlöre benenneUm() seine Wirkung.
|
||||||
function uebernehmen(stand) {
|
function uebernehmen(stand) {
|
||||||
|
|||||||
Reference in New Issue
Block a user