update
This commit is contained in:
@@ -65,7 +65,7 @@ Spezifische Hinweise für Features
|
||||
<!-- testen -->
|
||||
- Vollmodus öffnen
|
||||
- Klick auf Regeln
|
||||
- Inhalt anpassen
|
||||
- Inhalt angepasst
|
||||
<!-- funktion -->
|
||||
Titel anpassen
|
||||
<!-- regeln -->
|
||||
@@ -74,6 +74,7 @@ Titel anpassen
|
||||
- Vollmodus öffnen
|
||||
- Klick auf Titel
|
||||
- Inhalt ändern
|
||||
- Titel aktualisiert
|
||||
<!-- funktion -->
|
||||
Ziel anpassen
|
||||
<!-- ziel -->
|
||||
@@ -82,3 +83,4 @@ Beschreibt kurz das Kernziel vom Feature
|
||||
- Vollmodus öffnen
|
||||
- Klick auf Ziel
|
||||
- Inhalt anpassen
|
||||
- Ziel angepasst
|
||||
|
||||
@@ -32,7 +32,7 @@ Funktion erstellen
|
||||
- RMB auf Featurecard
|
||||
- Klick auf "Funktion erstellen"
|
||||
- Namen im Modal eingeben
|
||||
- Bestätigen
|
||||
- Funktion erstellt
|
||||
<!-- funktion -->
|
||||
Regeln anpassen
|
||||
<!-- ziel -->
|
||||
|
||||
@@ -50,10 +50,10 @@ Prüfung ausführen
|
||||
- Prüf-Icon in Sidebar Navigation
|
||||
<!-- testen -->
|
||||
- LMB auf Icon
|
||||
- Prüfung wird gestartet
|
||||
- Prüfung gestartet
|
||||
- Rechte Sidebar geöffnet
|
||||
- Fortschritt unter Icon angezeigt
|
||||
- Probleme werden live gelistet
|
||||
- Probleme live gelistet
|
||||
<!-- funktion -->
|
||||
Stellen einzeln prüfen
|
||||
<!-- ziel -->
|
||||
|
||||
@@ -32,7 +32,7 @@ Projekt aufrufen
|
||||
Projekt entfernen
|
||||
<!-- regeln -->
|
||||
- x-Icon
|
||||
- Auch Features entfernen
|
||||
- Features mit entfernen
|
||||
<!-- testen -->
|
||||
- RMB auf Projektname
|
||||
- Klick auf "Projekt entfernen"
|
||||
@@ -48,3 +48,4 @@ Projekt erstellen
|
||||
- Klick auf "Projekt erstellen"
|
||||
- Im Modal Namen eingeben
|
||||
- Bestätigen
|
||||
- Projekt erstellt
|
||||
|
||||
@@ -107,15 +107,30 @@ onMounted(async () => {
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
/* App-Shell: feste Höhe, drei eigene Scrollbereiche. Vorher wanderten die
|
||||
Leisten beim Scrollen mit nach oben weg. */
|
||||
.raster {
|
||||
display: grid;
|
||||
grid-template-columns: 220px 1fr;
|
||||
min-height: 100vh;
|
||||
grid-template-columns: 15rem 1fr;
|
||||
height: 100vh;
|
||||
overflow: hidden;
|
||||
}
|
||||
.raster.pruefung { grid-template-columns: 220px 1fr 260px; }
|
||||
.raster.pruefung { grid-template-columns: 15rem 1fr 20rem; }
|
||||
main {
|
||||
padding: 1.2rem 1.5rem;
|
||||
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 {
|
||||
color: var(--dezent);
|
||||
|
||||
@@ -2,11 +2,11 @@
|
||||
--bg: #f6f7f9;
|
||||
--panel: #ffffff;
|
||||
--text: #1a202c;
|
||||
--dezent: #64748b;
|
||||
--dezent: #556070; /* dunkler als slate-500: 4.34 war unter dem Lesbarkeitsminimum */
|
||||
--rand: #e2e8f0;
|
||||
--akzent: #2563eb;
|
||||
--akzent-text: #ffffff;
|
||||
--gruen: #16a34a;
|
||||
--gruen: #166534; /* auf gruen-bg reichte green-600 nicht (3.0) */
|
||||
--gruen-bg: #dcfce7;
|
||||
--gelb: #b45309;
|
||||
--gelb-bg: #fef3c7;
|
||||
@@ -75,16 +75,24 @@ input, textarea {
|
||||
padding: 0.9rem 1.1rem;
|
||||
}
|
||||
|
||||
/* Rahmenlose Icon-Knöpfe (✎ ✕ …) */
|
||||
/* Rahmenlose Icon-Knöpfe (✎ ✕ …) — mindestens 1.75rem Klickfläche */
|
||||
.icon-knopf {
|
||||
border: 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);
|
||||
font-size: 0.95rem;
|
||||
line-height: 1;
|
||||
}
|
||||
.icon-knopf:hover { color: var(--text); }
|
||||
.icon-knopf.rot:hover { color: var(--rot); }
|
||||
.icon-knopf:hover { color: var(--text); background: var(--bg); }
|
||||
.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; }
|
||||
|
||||
@@ -135,7 +143,10 @@ input, textarea {
|
||||
.badge {
|
||||
font-size: 0.72rem;
|
||||
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;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.04em;
|
||||
|
||||
@@ -272,6 +272,7 @@ function alleAblehnen() {
|
||||
<template>
|
||||
<article
|
||||
class="karte"
|
||||
:data-feature="feature.id"
|
||||
:class="[s.art, { gross, fehlerhaft: probleme.length }]"
|
||||
@mousedown.middle.prevent
|
||||
@auxclick.middle.prevent="wechsleAnsicht"
|
||||
@@ -306,8 +307,17 @@ function alleAblehnen() {
|
||||
@click="wechsleAnsicht"
|
||||
>👁</button>
|
||||
<span v-if="probleme.length" class="alle">
|
||||
<button class="icon-knopf" title="Alle annehmen" @click="alleAnnehmen">✓</button>
|
||||
<button class="icon-knopf rot" title="Alle ablehnen" @click="alleAblehnen">✕</button>
|
||||
<span class="anzahl">{{ probleme.length }}</span>
|
||||
<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>
|
||||
</header>
|
||||
<ProblemHinweis :probleme="problemeAn('titel')" @annehmen="annehmen" @ablehnen="ablehnen" />
|
||||
@@ -482,9 +492,33 @@ function alleAblehnen() {
|
||||
.karte.optional { border-color: var(--gruen); }
|
||||
/* Prüfung: fehlerhafte Karte und Stelle 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); }
|
||||
.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,
|
||||
alle Details offen */
|
||||
@@ -553,7 +587,9 @@ section.leer { display: none; }
|
||||
background: none;
|
||||
color: var(--dezent);
|
||||
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); }
|
||||
|
||||
|
||||
@@ -117,10 +117,14 @@ watch(() => props.projekt, laden, { immediate: true })
|
||||
|
||||
<style scoped>
|
||||
.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 {
|
||||
columns: 340px;
|
||||
columns: 22rem;
|
||||
column-gap: 0.9rem;
|
||||
max-width: 100rem;
|
||||
margin: 0 auto;
|
||||
}
|
||||
.gitter > * {
|
||||
/* inline-block macht die Karte atomar — Firefox fragmentiert sie sonst
|
||||
|
||||
@@ -1,80 +1,115 @@
|
||||
<script setup>
|
||||
import { vorschlagsArt } from '../composables/pruefung.js'
|
||||
import { vergleich, vorschlagsArt } from '../composables/pruefung.js'
|
||||
|
||||
defineProps({
|
||||
probleme: { type: Array, required: true },
|
||||
})
|
||||
const emit = defineEmits(['annehmen', 'ablehnen'])
|
||||
|
||||
// Was der Vorschlag bewirkt, steht nicht in der Antwort — es ergibt sich aus dem
|
||||
// Vergleich mit der alten Zeile. Bei entfernen steht dort die Zeile, die weggeht.
|
||||
const ZEICHEN = { ergaenzen: '+', entfernen: '−' }
|
||||
const zeichen = (p) => ZEICHEN[vorschlagsArt(p)] || ''
|
||||
const text = (p) => (vorschlagsArt(p) === 'entfernen' ? p.original : p.vorschlag)
|
||||
// Was das ✓ bewirkt, soll man vorher sehen. Darum die Gegenüberstellung
|
||||
// statt eines bloßen Ersatztextes.
|
||||
const TITEL = { ergaenzen: 'Ergänzen', entfernen: 'Streichen', anpassen: 'Ändern' }
|
||||
const ZEICHEN = { weg: '−', neu: '+', gleich: '' }
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div v-for="(p, i) in probleme" :key="i" class="hinweis-block" @click.stop>
|
||||
<div class="kopf">
|
||||
<p class="problem">{{ p.problem }}</p>
|
||||
<span class="art">{{ TITEL[vorschlagsArt(p)] }}</span>
|
||||
<span class="knoepfe">
|
||||
<button class="icon-knopf" title="Annehmen" @click="emit('annehmen', p)">✓</button>
|
||||
<button class="icon-knopf rot" title="Ablehnen" @click="emit('ablehnen', p)">✕</button>
|
||||
<button class="icon-knopf uebernehmen" title="Übernehmen" @click="emit('annehmen', p)">✓</button>
|
||||
<button class="icon-knopf rot" title="Verwerfen" @click="emit('ablehnen', p)">✕</button>
|
||||
</span>
|
||||
</div>
|
||||
<p class="vorschlag" :class="vorschlagsArt(p)">
|
||||
<span v-if="zeichen(p)" class="zeichen">{{ zeichen(p) }}</span>{{ text(p) }}
|
||||
</p>
|
||||
<p v-if="p.regel" class="regel" :title="p.regel">{{ p.regel }}</p>
|
||||
<p class="problem">{{ p.problem }}</p>
|
||||
|
||||
<ul class="vergleich">
|
||||
<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>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.hinweis-block {
|
||||
border-left: 2px solid var(--rot);
|
||||
background: var(--rot-bg);
|
||||
border-radius: 0 4px 4px 0;
|
||||
padding: 0.25rem 0.5rem 0.35rem;
|
||||
margin: 0.15rem 0 0.3rem;
|
||||
border: 1px solid var(--rand);
|
||||
border-left: 3px solid var(--akzent);
|
||||
background: var(--panel);
|
||||
border-radius: 0 6px 6px 0;
|
||||
padding: 0.4rem 0.55rem 0.45rem;
|
||||
margin: 0.3rem 0 0.4rem;
|
||||
min-width: 0;
|
||||
cursor: default;
|
||||
}
|
||||
/* Zwei feste Zeilen: Marke und Knöpfe oben, Begründung darunter. Nebeneinander
|
||||
zerfaserte der Kopf auf schmalen Karten. */
|
||||
.kopf {
|
||||
display: flex;
|
||||
align-items: baseline;
|
||||
align-items: center;
|
||||
gap: 0.4rem;
|
||||
}
|
||||
.problem {
|
||||
margin: 0;
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
font-size: 0.72rem;
|
||||
.knoepfe { margin-left: auto; }
|
||||
.art {
|
||||
font-size: 0.66rem;
|
||||
font-weight: 700;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.04em;
|
||||
line-height: 1.35;
|
||||
color: var(--rot);
|
||||
}
|
||||
.knoepfe { white-space: nowrap; }
|
||||
.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;
|
||||
letter-spacing: 0.06em;
|
||||
color: var(--akzent-text);
|
||||
background: var(--akzent);
|
||||
border-radius: 4px;
|
||||
padding: 0.05rem 0.35rem;
|
||||
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 {
|
||||
display: inline-block;
|
||||
width: 1rem;
|
||||
font-weight: 600;
|
||||
text-decoration: none;
|
||||
flex: none;
|
||||
width: 0.9rem;
|
||||
font-weight: 700;
|
||||
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>
|
||||
|
||||
@@ -1,5 +1,21 @@
|
||||
<script setup>
|
||||
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>
|
||||
|
||||
<template>
|
||||
@@ -22,9 +38,11 @@ import { laeuft, prozent, fertig, gesamt, meldung, probleme, schliesse } from '.
|
||||
|
||||
<ul>
|
||||
<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>
|
||||
<span class="problem">{{ p.problem }}</span>
|
||||
<span class="original">{{ p.original }}</span>
|
||||
<button class="eintrag" @click="springe(p)">
|
||||
<span class="stelle">{{ p.feature }}<template v-if="p.funktion"> · {{ p.funktion }}</template> · {{ p.feld }}</span>
|
||||
<span class="problem">{{ p.problem }}</span>
|
||||
<span class="original">{{ erste(p.original) }}</span>
|
||||
</button>
|
||||
</li>
|
||||
</ul>
|
||||
</aside>
|
||||
@@ -38,7 +56,17 @@ import { laeuft, prozent, fertig, gesamt, meldung, probleme, schliesse } from '.
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0.4rem;
|
||||
min-height: 0;
|
||||
overflow-y: auto;
|
||||
overscroll-behavior: contain;
|
||||
}
|
||||
|
||||
@media (max-width: 46rem) {
|
||||
.panel {
|
||||
border-left: none;
|
||||
border-top: 1px solid var(--rand);
|
||||
max-height: 33vh;
|
||||
}
|
||||
}
|
||||
header {
|
||||
display: flex;
|
||||
@@ -56,13 +84,21 @@ header {
|
||||
.fortschritt { margin: 0; font-size: 0.9rem; }
|
||||
.dezent { color: var(--dezent); font-size: 0.85rem; }
|
||||
ul { list-style: none; margin: 0.3rem 0 0; padding: 0; }
|
||||
li {
|
||||
border-top: 1px solid var(--rand);
|
||||
padding: 0.35rem 0;
|
||||
li { border-top: 1px solid var(--rand); }
|
||||
.eintrag {
|
||||
width: 100%;
|
||||
border: none;
|
||||
background: none;
|
||||
border-radius: 6px;
|
||||
text-align: left;
|
||||
padding: 0.4rem 0.4rem;
|
||||
margin: 0.1rem -0.4rem;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0.1rem;
|
||||
}
|
||||
.eintrag:hover { background: var(--bg); }
|
||||
.eintrag:focus-visible { outline: 2px solid var(--akzent); outline-offset: -2px; }
|
||||
.stelle {
|
||||
font-size: 0.72rem;
|
||||
text-transform: uppercase;
|
||||
|
||||
@@ -79,7 +79,7 @@ async function entfernen() {
|
||||
<aside @contextmenu.prevent="leistenMenue">
|
||||
<header>
|
||||
<p class="gruppe">Projekte</p>
|
||||
<slot />
|
||||
<span class="werkzeuge"><slot /></span>
|
||||
</header>
|
||||
<ul>
|
||||
<li
|
||||
@@ -131,13 +131,35 @@ aside {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
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 {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: 0.4rem;
|
||||
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 {
|
||||
font-size: 0.72rem;
|
||||
font-weight: 600;
|
||||
|
||||
@@ -12,4 +12,9 @@ export function schliesseMenue() {
|
||||
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'
|
||||
}
|
||||
|
||||
// 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
|
||||
// vor dem Lauf; würde die Liste jede Sekunde neu gesetzt, verlöre benenneUm() seine Wirkung.
|
||||
function uebernehmen(stand) {
|
||||
|
||||
Reference in New Issue
Block a user