Files
horror/frontend/src/components/Kommentar.vue
2026-09-17 15:40:28 +02:00

213 lines
4.4 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<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>