582 lines
18 KiB
Vue
582 lines
18 KiB
Vue
<script setup>
|
|
import { nextTick, ref, watch } from 'vue'
|
|
import { marked } from 'marked'
|
|
import { featureSpeichern } from '../api.js'
|
|
import { oeffneMenue } from '../composables/kontextmenue.js'
|
|
import { erledige } from '../composables/pruefung.js'
|
|
import ModalBox from './ModalBox.vue'
|
|
import ProblemHinweis from './ProblemHinweis.vue'
|
|
|
|
const props = defineProps({
|
|
projekt: { type: String, required: true },
|
|
feature: { type: Object, required: true },
|
|
vollmodus: { type: Boolean, default: false }, // frisch angelegt: direkt im Vollmodus öffnen
|
|
probleme: { type: Array, default: () => [] },
|
|
})
|
|
const emit = defineEmits(['aktualisiert', 'entfernen'])
|
|
|
|
function kopie(sekt) {
|
|
return {
|
|
titel: sekt.titel,
|
|
art: sekt.art,
|
|
ziel: sekt.ziel,
|
|
regeln: sekt.regeln,
|
|
funktionen: sekt.funktionen
|
|
.map(f => ({ text: f.text, ziel: f.ziel, regeln: f.regeln, testen: f.testen }))
|
|
.sort((a, b) => a.text.localeCompare(b.text, 'de', { sensitivity: 'base' })),
|
|
sonstiges: sekt.sonstiges, // nicht angezeigt, aber beim Speichern erhalten
|
|
}
|
|
}
|
|
|
|
const s = ref(kopie(props.feature.sektionen))
|
|
watch(() => props.feature, f => { s.value = kopie(f.sektionen) })
|
|
|
|
const aktiv = ref(null) // {feld, i, j, original} — das eine gerade offene Eingabefeld
|
|
const gross = ref(props.vollmodus) // ausführliche Ansicht
|
|
const entwurf = ref('')
|
|
const meldung = ref('')
|
|
const feldRef = ref(null)
|
|
|
|
// Lokale, selbst geschriebene Daten — kein Sanitizing nötig
|
|
const md = (text) => marked.parse(text)
|
|
|
|
function istAktiv(feld, i, j) {
|
|
const a = aktiv.value
|
|
return !!a && a.feld === feld && a.i === i && a.j === j
|
|
}
|
|
|
|
function wert(feld, i, j) {
|
|
if (feld === 'titel') return s.value.titel
|
|
if (feld === 'ziel') return i === undefined ? s.value.ziel : s.value.funktionen[i].ziel
|
|
if (feld === 'funktion') return s.value.funktionen[i].text
|
|
if (feld === 'testen') return s.value.funktionen[i].testen
|
|
return i === undefined ? s.value.regeln : s.value.funktionen[i].regeln
|
|
}
|
|
|
|
async function starte(feld, i, j) {
|
|
if (!gross.value) return // Bearbeiten nur in der ausführlichen Ansicht
|
|
const original = wert(feld, i, j)
|
|
aktiv.value = { feld, i, j, original }
|
|
entwurf.value = original
|
|
await nextTick()
|
|
const el = Array.isArray(feldRef.value) ? feldRef.value[0] : feldRef.value
|
|
el?.focus()
|
|
if (feld === 'titel') el?.select()
|
|
}
|
|
|
|
function abbrechen() {
|
|
aktiv.value = null
|
|
}
|
|
|
|
async function fertig() {
|
|
const a = aktiv.value
|
|
if (!a) return
|
|
aktiv.value = null
|
|
const neu = entwurf.value.trim()
|
|
if (neu === a.original) return
|
|
|
|
if (a.feld === 'titel') {
|
|
if (!neu) return // Titel ist Pflicht — leere Eingabe verwerfen
|
|
s.value.titel = neu
|
|
} else if (a.feld === 'funktion') {
|
|
if (!neu) return // Funktionstitel ebenfalls; entfernt wird über das Kontextmenü
|
|
s.value.funktionen[a.i].text = neu
|
|
} else if (a.feld === 'ziel') {
|
|
if (a.i === undefined) s.value.ziel = neu
|
|
else s.value.funktionen[a.i].ziel = neu
|
|
} else if (a.feld === 'regeln') {
|
|
if (a.i === undefined) s.value.regeln = neu
|
|
else s.value.funktionen[a.i].regeln = neu
|
|
} else if (a.feld === 'testen') {
|
|
s.value.funktionen[a.i].testen = neu
|
|
}
|
|
await speichern()
|
|
}
|
|
|
|
// Rechtsklick auf eine Funktion (entfernen) oder auf die Karte (erstellen/Feature entfernen)
|
|
const modal = ref(null) // {art: 'erstellen'} | {art: 'entfernen', i}
|
|
|
|
function funktionsMenue(e, i) {
|
|
oeffneMenue(e, [
|
|
{ text: '✕ Funktion entfernen', aktion: () => (modal.value = { art: 'entfernen', i }) },
|
|
])
|
|
}
|
|
|
|
function kartenMenue(e) {
|
|
const eintraege = []
|
|
if (gross.value) {
|
|
eintraege.push({ text: '+ Funktion erstellen', aktion: () => (modal.value = { art: 'erstellen' }) })
|
|
}
|
|
eintraege.push({ text: '✕ Feature entfernen', aktion: () => emit('entfernen') })
|
|
oeffneMenue(e, eintraege)
|
|
}
|
|
|
|
async function funktionErstellen(titel) {
|
|
s.value.funktionen.push({ text: titel, ziel: '', regeln: '', testen: '' })
|
|
modal.value = null
|
|
await speichern()
|
|
}
|
|
|
|
async function funktionEntfernen() {
|
|
s.value.funktionen.splice(modal.value.i, 1)
|
|
modal.value = null
|
|
await speichern()
|
|
}
|
|
|
|
function wechsleArt() {
|
|
s.value.art = s.value.art === 'kern' ? 'optional' : 'kern'
|
|
speichern()
|
|
}
|
|
|
|
const TRENNER = '-'.repeat(40)
|
|
|
|
// Aus dem Karten-Zustand wird die Markdown-Datei zusammengesetzt — sie bleibt die Wahrheit
|
|
function baueRoh() {
|
|
const teile = [
|
|
`<!-- titel -->\n${s.value.titel}`,
|
|
`<!-- art -->\n${s.value.art}`,
|
|
`<!-- ziel -->\n${s.value.ziel}`,
|
|
]
|
|
if (s.value.regeln) teile.push(`<!-- regeln -->\n${s.value.regeln}`)
|
|
const funktionen = s.value.funktionen
|
|
.map(f => {
|
|
const bloecke = [`<!-- funktion -->\n${f.text}`]
|
|
if (f.ziel) bloecke.push(`<!-- ziel -->\n${f.ziel}`)
|
|
if (f.regeln) bloecke.push(`<!-- regeln -->\n${f.regeln}`)
|
|
if (f.testen) bloecke.push(`<!-- testen -->\n${f.testen}`)
|
|
return bloecke.join('\n')
|
|
})
|
|
.join('\n')
|
|
if (funktionen) teile.push(funktionen)
|
|
if (s.value.sonstiges) teile.push(`<!-- sonstiges -->\n${s.value.sonstiges}`)
|
|
return teile.join(`\n${TRENNER}\n`) + '\n'
|
|
}
|
|
|
|
async function speichern() {
|
|
meldung.value = ''
|
|
try {
|
|
const f = await featureSpeichern(props.projekt, props.feature.id, baueRoh())
|
|
emit('aktualisiert', props.feature.id, f) // id kann sich durch Titeländerung ändern
|
|
} catch (e) {
|
|
meldung.value = e.message
|
|
}
|
|
}
|
|
|
|
function zeilen(text) {
|
|
return Math.max(2, text.split('\n').length)
|
|
}
|
|
|
|
// Blocktext („- Eintrag“ je Zeile) für die Listen-Anzeige zerlegen
|
|
function punkte(text) {
|
|
return text.split('\n').map(z => z.replace(/^[-*]\s*/, '').trim()).filter(Boolean)
|
|
}
|
|
|
|
// Kompaktmodus zum Lesen, Vollmodus zum Anpassen — MMB auf die Karte
|
|
// oder das Augen-Icon schaltet um
|
|
function wechsleAnsicht() {
|
|
gross.value = !gross.value
|
|
}
|
|
|
|
// Prüfung: fehlerhafte Karten öffnen sich, damit die Stellen sichtbar sind
|
|
watch(() => props.probleme.length, n => { if (n) gross.value = true }, { immediate: true })
|
|
|
|
function problemeAn(feld, funktion = '', original = null) {
|
|
return props.probleme.filter(p =>
|
|
p.feld === feld &&
|
|
(p.funktion || '') === funktion &&
|
|
(original === null || p.original === original))
|
|
}
|
|
|
|
// Vorschlag übernehmen: Titel und Ziel ganz ersetzen, bei Regeln und Testen die Zeile
|
|
function anwenden(p) {
|
|
const ziel = p.funktion ? s.value.funktionen.find(f => f.text === p.funktion) : s.value
|
|
if (!ziel) return
|
|
if (p.feld === 'titel') {
|
|
if (p.funktion) ziel.text = p.vorschlag
|
|
else ziel.titel = p.vorschlag
|
|
} else if (p.feld === 'ziel') {
|
|
ziel.ziel = p.vorschlag
|
|
} else {
|
|
const feld = p.feld === 'testen' ? 'testen' : 'regeln'
|
|
ziel[feld] = ziel[feld]
|
|
.split('\n')
|
|
.map(z => (z.replace(/^[-*]\s*/, '').trim() === p.original ? `- ${p.vorschlag}` : z))
|
|
.join('\n')
|
|
}
|
|
}
|
|
|
|
async function annehmen(p) {
|
|
anwenden(p)
|
|
erledige(p)
|
|
await speichern()
|
|
}
|
|
|
|
function ablehnen(p) {
|
|
erledige(p)
|
|
}
|
|
|
|
async function alleAnnehmen() {
|
|
props.probleme.forEach(p => { anwenden(p); erledige(p) })
|
|
await speichern()
|
|
}
|
|
|
|
function alleAblehnen() {
|
|
props.probleme.forEach(erledige)
|
|
}
|
|
</script>
|
|
|
|
<template>
|
|
<article
|
|
class="karte"
|
|
:class="[s.art, { gross, fehlerhaft: probleme.length }]"
|
|
@mousedown.middle.prevent
|
|
@auxclick.middle.prevent="wechsleAnsicht"
|
|
@contextmenu.prevent.stop="kartenMenue"
|
|
>
|
|
<header>
|
|
<input
|
|
v-if="istAktiv('titel')"
|
|
ref="feldRef"
|
|
v-model="entwurf"
|
|
class="titel-feld"
|
|
@blur="fertig"
|
|
@keyup.enter="$event.target.blur()"
|
|
@keydown.esc="abbrechen"
|
|
>
|
|
<h3 v-else :title="gross ? 'Klicken zum Bearbeiten' : null" @click="starte('titel')">{{ s.titel }}</h3>
|
|
<button
|
|
v-if="gross"
|
|
class="badge"
|
|
:class="s.art === 'kern' ? 'blau' : 'gruen'"
|
|
title="Klicken zum Umschalten"
|
|
@click="wechsleArt"
|
|
>{{ s.art }}</button>
|
|
<span v-else class="badge" :class="s.art === 'kern' ? 'blau' : 'gruen'">{{ s.art }}</span>
|
|
<button
|
|
class="icon-knopf"
|
|
:title="gross ? 'Kompaktmodus' : 'Vollmodus'"
|
|
@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>
|
|
</header>
|
|
<ProblemHinweis :probleme="problemeAn('titel')" @annehmen="annehmen" @ablehnen="ablehnen" />
|
|
|
|
<section :class="{ leer: !s.ziel && !istAktiv('ziel') }">
|
|
<p class="gruppe">Ziel</p>
|
|
<textarea
|
|
v-if="istAktiv('ziel')"
|
|
ref="feldRef"
|
|
v-model="entwurf"
|
|
:rows="zeilen(entwurf)"
|
|
@blur="fertig"
|
|
@keydown.esc="abbrechen"
|
|
/>
|
|
<div v-else-if="s.ziel" class="inhalt klickbar" v-html="md(s.ziel)" @click="starte('ziel')" />
|
|
<p v-else-if="gross" class="hinweis" @click="starte('ziel')">Ziel ergänzen…</p>
|
|
<ProblemHinweis :probleme="problemeAn('ziel')" @annehmen="annehmen" @ablehnen="ablehnen" />
|
|
</section>
|
|
|
|
<!-- Karten-Regeln nur in der ausführlichen Ansicht -->
|
|
<section v-if="gross" :class="{ leer: !s.regeln && !istAktiv('regeln') }">
|
|
<p class="gruppe">Regeln</p>
|
|
<textarea
|
|
v-if="istAktiv('regeln')"
|
|
ref="feldRef"
|
|
v-model="entwurf"
|
|
:rows="zeilen(entwurf)"
|
|
@blur="fertig"
|
|
@keydown.esc="abbrechen"
|
|
/>
|
|
<div v-else-if="s.regeln" class="klickbar" @click="starte('regeln')">
|
|
<template v-for="(r, k) in punkte(s.regeln)" :key="k">
|
|
<div class="regel" :class="{ fehler: problemeAn('regeln', '', r).length }">{{ r }}</div>
|
|
<ProblemHinweis
|
|
:probleme="problemeAn('regeln', '', r)"
|
|
@annehmen="annehmen"
|
|
@ablehnen="ablehnen"
|
|
/>
|
|
</template>
|
|
</div>
|
|
<p v-else-if="gross" class="hinweis" @click="starte('regeln')">Regeln ergänzen…</p>
|
|
</section>
|
|
|
|
<section :class="{ leer: !s.funktionen.length }">
|
|
<p class="gruppe">Funktionen</p>
|
|
<div class="liste">
|
|
<div
|
|
v-for="(f, i) in s.funktionen"
|
|
:key="i"
|
|
class="funktion"
|
|
:class="{ offen: aktiv && aktiv.i === i }"
|
|
@contextmenu.prevent.stop="funktionsMenue($event, i)"
|
|
>
|
|
<div class="zeile kopf">
|
|
<span class="pfeil">▸</span>
|
|
<input
|
|
v-if="istAktiv('funktion', i)"
|
|
ref="feldRef"
|
|
v-model="entwurf"
|
|
@blur="fertig"
|
|
@keyup.enter="$event.target.blur()"
|
|
@keydown.esc="abbrechen"
|
|
>
|
|
<div
|
|
v-else
|
|
class="funktionstitel klickbar"
|
|
:class="{ fehler: problemeAn('titel', f.text).length }"
|
|
@click="starte('funktion', i)"
|
|
>{{ f.text }}</div>
|
|
</div>
|
|
<ProblemHinweis :probleme="problemeAn('titel', f.text)" @annehmen="annehmen" @ablehnen="ablehnen" />
|
|
|
|
<!-- Ziel, Regeln und Testen nur im Vollmodus -->
|
|
<div class="details">
|
|
<!-- Ziel der Funktion -->
|
|
<div v-if="istAktiv('ziel', i) || f.ziel" class="block">
|
|
<p class="mini">Ziel</p>
|
|
<textarea
|
|
v-if="istAktiv('ziel', i)"
|
|
ref="feldRef"
|
|
v-model="entwurf"
|
|
:rows="zeilen(entwurf)"
|
|
@blur="fertig"
|
|
@keydown.esc="abbrechen"
|
|
/>
|
|
<div v-else class="inhalt klickbar" v-html="md(f.ziel)" @click="starte('ziel', i)" />
|
|
<ProblemHinweis :probleme="problemeAn('ziel', f.text)" @annehmen="annehmen" @ablehnen="ablehnen" />
|
|
</div>
|
|
<button v-if="!f.ziel && !istAktiv('ziel', i)" class="hinzu" @click="starte('ziel', i)">+ Ziel</button>
|
|
|
|
<!-- Regeln als Punktliste, ein Block je Funktion -->
|
|
<div v-if="istAktiv('regeln', i) || f.regeln" class="block">
|
|
<p class="mini">Regeln</p>
|
|
<textarea
|
|
v-if="istAktiv('regeln', i)"
|
|
ref="feldRef"
|
|
v-model="entwurf"
|
|
:rows="zeilen(entwurf)"
|
|
@blur="fertig"
|
|
@keydown.esc="abbrechen"
|
|
/>
|
|
<div v-else class="klickbar" @click="starte('regeln', i)">
|
|
<template v-for="(r, k) in punkte(f.regeln)" :key="k">
|
|
<div class="regel" :class="{ fehler: problemeAn('regeln', f.text, r).length }">{{ r }}</div>
|
|
<ProblemHinweis
|
|
:probleme="problemeAn('regeln', f.text, r)"
|
|
@annehmen="annehmen"
|
|
@ablehnen="ablehnen"
|
|
/>
|
|
</template>
|
|
</div>
|
|
</div>
|
|
<button v-if="!f.regeln && !istAktiv('regeln', i)" class="hinzu" @click="starte('regeln', i)">+ Regeln</button>
|
|
|
|
<!-- Testen als nummerierte Schritte -->
|
|
<div v-if="istAktiv('testen', i) || f.testen" class="block">
|
|
<p class="mini">Testen</p>
|
|
<textarea
|
|
v-if="istAktiv('testen', i)"
|
|
ref="feldRef"
|
|
v-model="entwurf"
|
|
:rows="zeilen(entwurf)"
|
|
@blur="fertig"
|
|
@keydown.esc="abbrechen"
|
|
/>
|
|
<div v-else class="klickbar" @click="starte('testen', i)">
|
|
<template v-for="(schritt, k) in punkte(f.testen)" :key="k">
|
|
<div class="schritt" :class="{ fehler: problemeAn('testen', f.text, schritt).length }">
|
|
<span class="nr">{{ k + 1 }}.</span>{{ schritt }}
|
|
</div>
|
|
<ProblemHinweis
|
|
:probleme="problemeAn('testen', f.text, schritt)"
|
|
@annehmen="annehmen"
|
|
@ablehnen="ablehnen"
|
|
/>
|
|
</template>
|
|
</div>
|
|
</div>
|
|
|
|
<button v-if="!f.testen && !istAktiv('testen', i)" class="hinzu" @click="starte('testen', i)">+ Testen</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</section>
|
|
|
|
<p v-if="meldung" class="meldung">{{ meldung }}</p>
|
|
|
|
<ModalBox
|
|
v-if="modal && modal.art === 'erstellen'"
|
|
titel="Funktion erstellen"
|
|
platzhalter="Funktionstitel"
|
|
@bestaetigt="funktionErstellen"
|
|
@abbrechen="modal = null"
|
|
/>
|
|
<ModalBox
|
|
v-else-if="modal"
|
|
titel="Funktion entfernen"
|
|
:frage="`„${s.funktionen[modal.i].text}“ entfernen?`"
|
|
knopf="Entfernen"
|
|
@bestaetigt="funktionEntfernen"
|
|
@abbrechen="modal = null"
|
|
/>
|
|
</article>
|
|
</template>
|
|
|
|
<style scoped>
|
|
.karte.kern { border-color: var(--akzent); }
|
|
.karte.optional { border-color: var(--gruen); }
|
|
/* Prüfung: fehlerhafte Karte und Stelle rot */
|
|
.karte.fehlerhaft { border-color: var(--rot); }
|
|
.fehler { color: var(--rot); }
|
|
.alle { white-space: nowrap; }
|
|
|
|
/* Vollmodus: volle Breite, Funktionen als Boxen in responsiven Spalten,
|
|
alle Details offen */
|
|
.karte.gross { column-span: all; display: block; }
|
|
.karte.gross .liste { columns: 320px; 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;
|
|
}
|
|
|
|
header {
|
|
display: flex;
|
|
align-items: baseline;
|
|
gap: 0.5rem;
|
|
padding-bottom: 0.55rem;
|
|
border-bottom: 1px solid var(--rand);
|
|
}
|
|
h3 { margin: 0; font-size: 1rem; flex: 1; }
|
|
/* Im Bearbeitungsmodus zeigt Hover die klickbare Titelfläche */
|
|
.karte.gross h3 {
|
|
padding: 0.1rem 0.3rem;
|
|
margin-left: -0.3rem;
|
|
border-radius: 4px;
|
|
}
|
|
.karte.gross h3:hover { background: var(--code-bg); }
|
|
.titel-feld { flex: 1; font-weight: 600; padding: 0.1rem 0.4rem; }
|
|
.badge { cursor: pointer; }
|
|
|
|
section { padding: 0.6rem 0; }
|
|
section + section { border-top: 1px solid var(--rand); }
|
|
.gruppe {
|
|
font-size: 0.72rem;
|
|
font-weight: 600;
|
|
text-transform: uppercase;
|
|
letter-spacing: 0.06em;
|
|
color: var(--akzent);
|
|
margin: 0 0 0.15rem;
|
|
}
|
|
|
|
/* Leere Sektionen nur in der ausführlichen Ansicht */
|
|
section.leer { display: none; }
|
|
|
|
.hinweis {
|
|
color: var(--dezent);
|
|
font-size: 0.85rem;
|
|
padding: 0.1rem 0.3rem;
|
|
margin: 0.1rem 0 0.1rem -0.3rem;
|
|
border-radius: 4px;
|
|
cursor: text;
|
|
}
|
|
.hinweis:hover { background: var(--code-bg); }
|
|
.hinzu {
|
|
border: none;
|
|
background: none;
|
|
color: var(--dezent);
|
|
font-size: 0.8rem;
|
|
padding: 0.1rem 0;
|
|
}
|
|
.hinzu:hover { color: var(--akzent); }
|
|
|
|
.zeile {
|
|
display: flex;
|
|
align-items: baseline;
|
|
gap: 0.3rem;
|
|
}
|
|
.zeile .inhalt, .zeile input, .zeile textarea { flex: 1; min-width: 0; }
|
|
|
|
/* Funktionsliste */
|
|
.zeile.kopf { padding: 0.15rem 0.2rem; border-radius: 4px; }
|
|
.karte.gross .zeile.kopf:hover { background: var(--code-bg); }
|
|
.funktionstitel { flex: 1; }
|
|
.pfeil { color: var(--dezent); font-size: 0.75rem; }
|
|
|
|
/* Ziel, Regeln und Testen nur im Vollmodus */
|
|
.details { display: none; font-size: 0.9rem; }
|
|
|
|
/* Regeln (Punktliste) und Testen (nummerierte Schritte) als getönte Blöcke */
|
|
.block {
|
|
background: var(--code-bg);
|
|
border-radius: 6px;
|
|
padding: 0.3rem 0.55rem 0.35rem;
|
|
margin: 0.3rem 0 0.1rem;
|
|
}
|
|
.mini {
|
|
font-size: 0.68rem;
|
|
font-weight: 600;
|
|
text-transform: uppercase;
|
|
letter-spacing: 0.05em;
|
|
color: var(--akzent);
|
|
margin: 0 0 0.1rem;
|
|
}
|
|
.block .klickbar { border-radius: 4px; }
|
|
.block .klickbar:hover { background: var(--bg); }
|
|
.regel, .schritt {
|
|
position: relative;
|
|
padding-left: 1.15rem;
|
|
}
|
|
.regel::before {
|
|
content: "•";
|
|
position: absolute;
|
|
left: 0.35rem;
|
|
color: var(--dezent);
|
|
}
|
|
.nr {
|
|
position: absolute;
|
|
left: 0.15rem;
|
|
color: var(--dezent);
|
|
font-size: 0.85em;
|
|
}
|
|
/* „+ Regeln"/„+ Testen" bündig zur Listen-Einrückung */
|
|
.details .hinzu {
|
|
display: block;
|
|
padding: 0.05rem 0 0.05rem 0.55rem;
|
|
margin: 0.05rem 0;
|
|
}
|
|
|
|
textarea {
|
|
width: 100%;
|
|
resize: vertical;
|
|
font-family: ui-monospace, monospace;
|
|
font-size: 0.85rem;
|
|
line-height: 1.5;
|
|
}
|
|
/* Karte: MMB schaltet den Modus um; Texte sind nur im Vollmodus bearbeitbar */
|
|
.karte { cursor: pointer; }
|
|
.klickbar { user-select: none; }
|
|
.karte.gross .klickbar, .karte.gross h3 { cursor: text; }
|
|
.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(ul) { margin: 0.2rem 0; padding-left: 1.2rem; }
|
|
.inhalt :deep(li) { margin: 0.1rem 0; }
|
|
</style>
|