This commit is contained in:
team3
2026-09-17 15:40:28 +02:00
parent a1684cf54f
commit ea54a86db4
30 changed files with 3680 additions and 989 deletions

View File

@@ -0,0 +1,212 @@
<script setup>
// Ein Kommentar in der Diskussion: der Text mit Name, Datum und
// „Kommentieren“. Antworten rücken darunter ein — die Komponente ruft sich dafür selbst auf, so tief
// es geht. Ab der vierten Ebene nicht weiter, sonst bleibt auf dem Handy
// kein Platz für den Text.
import { ref } from 'vue'
import { rendern } from '../markdown'
import MarkdownFeld from './MarkdownFeld.vue'
const props = defineProps({
kommentar: { type: Object, required: true },
watchedId: { type: Number, required: true },
angemeldet: Boolean,
host: Boolean,
tiefe: { type: Number, default: 0 },
})
const antwortet = ref(false)
const entwurf = ref('')
async function antworten() {
const text = entwurf.value.trim()
if (!text) return
const resp = await fetch(`/api/watched/${props.watchedId}/notes`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ text, parent_id: props.kommentar.id }),
})
if (resp.ok) {
entwurf.value = ''
antwortet.value = false
}
}
async function loeschen() {
await fetch(`/api/watched-notes/${props.kommentar.id}`, { method: 'DELETE' })
}
function datum(iso) {
return new Date(iso).toLocaleDateString('de-DE', {
day: '2-digit',
month: '2-digit',
year: 'numeric',
})
}
</script>
<template>
<li class="zeile">
<div class="inhalt">
<div class="markdown" v-html="rendern(kommentar.text)"></div>
<p class="meta">
<span :class="{ eigen: kommentar.mine }">{{ kommentar.name || 'Anonym' }}</span>
<span class="punkt">·</span>{{ datum(kommentar.created_at) }}
<template v-if="angemeldet">
<span class="punkt">·</span>
<button type="button" class="link" :class="{ offen: antwortet }" @click="antwortet = !antwortet">
Kommentieren
</button>
</template>
</p>
<MarkdownFeld
v-if="antwortet"
v-model="entwurf"
kompakt
class="neu"
:platzhalter="`Antwort an ${kommentar.name || 'Anonym'} …`"
knopf="Antworten"
@senden="antworten"
@abbrechen="antwortet = false"
/>
<ul v-if="kommentar.replies.length" class="antworten" :class="{ flach: tiefe >= 3 }">
<Kommentar
v-for="k in kommentar.replies"
:key="k.id"
:kommentar="k"
:watched-id="watchedId"
:angemeldet="angemeldet"
:host="host"
:tiefe="tiefe + 1"
/>
</ul>
</div>
<!-- Erst beim Überfahren sichtbar, sonst ist jede Zeile voller Knöpfe -->
<div class="aktionen">
<button
v-if="kommentar.mine || host"
type="button"
class="weg"
:title="kommentar.replies.length ? 'Samt allen Antworten darunter' : 'Kommentar entfernen'"
@click="loeschen"
>
×
</button>
</div>
</li>
</template>
<style scoped>
.zeile {
display: flex;
gap: 0.8rem;
align-items: flex-start;
padding: 0.55rem 0.5rem;
border-bottom: 1px solid var(--line);
border-radius: 6px;
}
.zeile:hover {
background: rgba(255, 255, 255, 0.02);
}
.inhalt {
display: flex;
flex: 1;
min-width: 0;
flex-direction: column;
gap: 0.15rem;
}
.meta {
color: var(--muted);
font-size: 0.76rem;
line-height: 1.4;
}
.punkt {
margin: 0 0.35rem;
}
.eigen {
color: var(--accent);
font-weight: 600;
}
.link {
padding: 0;
color: var(--muted);
font-size: inherit;
text-decoration: underline;
text-decoration-color: transparent;
text-underline-offset: 2px;
}
.link:hover,
.link.offen {
color: var(--text);
text-decoration-color: currentColor;
}
.neu {
margin-top: 0.4rem;
}
/* Antworten hängen eingerückt unter dem Kommentar */
.antworten {
margin-top: 0.4rem;
padding-left: 0.4rem;
list-style: none;
border-left: 2px solid var(--line);
}
.antworten .zeile {
padding-right: 0;
border-bottom: 0;
}
/* Ab der vierten Ebene nicht weiter einrücken */
.antworten.flach {
padding-left: 0;
border-left: 0;
}
.aktionen {
display: flex;
flex-shrink: 0;
opacity: 0;
transition: opacity 0.12s;
}
.zeile:hover > .aktionen,
.zeile:focus-within > .aktionen {
opacity: 1;
}
.weg {
padding: 0 0.25rem;
color: var(--muted);
font-size: 1.05rem;
line-height: 1.2;
}
.weg:hover {
color: var(--accent);
}
@media (hover: none) {
.aktionen {
opacity: 1;
}
}
@media (max-width: 640px), (pointer: coarse) {
.link,
.weg {
padding: 0.3rem 0.4rem;
}
}
</style>