365 lines
10 KiB
Vue
365 lines
10 KiB
Vue
<template>
|
||
<div class="forum-view">
|
||
<section class="forum-hero surface-card">
|
||
<div>
|
||
<span class="forum-kicker">Community-Forum</span>
|
||
<h2>{{ $t('socialnetwork.forum.title') }} {{ forumName }}</h2>
|
||
<p>Themen, Diskussionen und neue Beiträge an einem strukturierten Ort.</p>
|
||
</div>
|
||
<div class="creationtoggler">
|
||
<button @click="toggleCreation">
|
||
{{ $t(!inCreation
|
||
? 'socialnetwork.forum.showNewTopic'
|
||
: 'socialnetwork.forum.hideNewTopic') }}
|
||
</button>
|
||
</div>
|
||
</section>
|
||
|
||
<section v-if="inCreation" class="forum-creation surface-card">
|
||
<div class="forum-creation__header">
|
||
<div>
|
||
<h3>Neues Thema verfassen</h3>
|
||
<p>Erst Titel setzen, dann den Beitrag schreiben und anschließend direkt veröffentlichen.</p>
|
||
</div>
|
||
<button type="button" class="button-secondary" @click="cancelCreation">Abbrechen</button>
|
||
</div>
|
||
<label class="newtitle">
|
||
<span>{{ $t('socialnetwork.forum.topic') }}</span>
|
||
<input ref="titleInput" type="text" v-model="newTitle" />
|
||
</label>
|
||
<div class="editor-container">
|
||
<EditorContent v-if="editor" :editor="editor" class="editor" />
|
||
</div>
|
||
<div class="forum-creation__actions">
|
||
<button :disabled="!canSaveTopic" @click="saveNewTopic">
|
||
{{ $t('socialnetwork.forum.createNewTopic') }}
|
||
</button>
|
||
<span class="forum-creation__hint">Titel und Inhalt müssen beide ausgefüllt sein.</span>
|
||
</div>
|
||
</section>
|
||
|
||
<section v-else-if="titles.length > 0" class="forum-topics surface-card">
|
||
<ul class="topic-list">
|
||
<li v-for="topic in titles" :key="topic.id" class="topic-card">
|
||
<button type="button" class="topic-card__main" @click="openTopic(topic.id)">
|
||
<strong>{{ topic.title }}</strong>
|
||
<span class="topic-card__meta">
|
||
{{ topic.user?.username || topic.owner?.username || 'Community' }}
|
||
</span>
|
||
</button>
|
||
</li>
|
||
</ul>
|
||
<div class="pagination">
|
||
<button @click="goToPage(page-1)" :disabled="page<=1">‹</button>
|
||
<span>{{ page }} / {{ totalPages }}</span>
|
||
<button @click="goToPage(page+1)" :disabled="page>=totalPages">›</button>
|
||
</div>
|
||
</section>
|
||
|
||
<div v-else class="forum-empty surface-card">
|
||
<p>{{ $t('socialnetwork.forum.noTitles') }}</p>
|
||
<button type="button" @click="toggleCreation">{{ $t('socialnetwork.forum.createNewTopic') }}</button>
|
||
</div>
|
||
</div>
|
||
</template>
|
||
|
||
<script>
|
||
import { Editor, EditorContent } from '@tiptap/vue-3'
|
||
import StarterKit from '@tiptap/starter-kit'
|
||
import apiClient from '../../utils/axios'
|
||
import { showApiError, showSuccess } from '@/utils/feedback.js';
|
||
|
||
export default {
|
||
name: 'ForumView',
|
||
components: { EditorContent },
|
||
data() {
|
||
return {
|
||
forumName: '',
|
||
page: 1,
|
||
numberOfItems: 0,
|
||
titles: [],
|
||
inCreation: false,
|
||
newTitle: '',
|
||
editor: null,
|
||
}
|
||
},
|
||
computed: {
|
||
// Neu: Forum‑ID immer direkt aus der Route ziehen
|
||
forumId() {
|
||
return this.$route.params.id
|
||
},
|
||
totalPages() {
|
||
return Math.ceil(this.numberOfItems / 25)
|
||
},
|
||
canSaveTopic() {
|
||
const content = this.editor ? this.editor.getText().trim() : '';
|
||
return this.newTitle.trim().length >= 3 && content.length > 0;
|
||
}
|
||
},
|
||
watch: {
|
||
// Sobald sich die Route‑ID ändert, zurück auf Seite 1 und neu laden
|
||
forumId: {
|
||
handler() {
|
||
this.page = 1
|
||
this.loadForum()
|
||
},
|
||
immediate: true
|
||
},
|
||
// Wenn sich die Seite ändert, ebenfalls neu laden
|
||
page(newPage, oldPage) {
|
||
if (newPage !== oldPage) {
|
||
this.loadForum()
|
||
}
|
||
}
|
||
},
|
||
async mounted() {
|
||
// Editor initialisieren
|
||
this.editor = new Editor({
|
||
extensions: [StarterKit],
|
||
content: '',
|
||
editable: true,
|
||
editorProps: { attributes: { class: 'pm-root' } },
|
||
})
|
||
},
|
||
beforeUnmount() {
|
||
if (this.editor) this.editor.destroy()
|
||
},
|
||
methods: {
|
||
focusTitleInput() {
|
||
this.$nextTick(() => this.$refs.titleInput?.focus?.());
|
||
},
|
||
toggleCreation() {
|
||
this.inCreation = !this.inCreation;
|
||
if (this.inCreation && this.editor) {
|
||
this.editor.commands.setContent('');
|
||
this.newTitle = '';
|
||
this.focusTitleInput();
|
||
}
|
||
},
|
||
cancelCreation() {
|
||
this.inCreation = false;
|
||
this.newTitle = '';
|
||
if (this.editor) {
|
||
this.editor.commands.setContent('');
|
||
}
|
||
},
|
||
async loadForum() {
|
||
try {
|
||
const { data } = await apiClient.get(
|
||
`/api/forum/${this.forumId}/${this.page}`
|
||
)
|
||
this.forumName = data.name
|
||
this.titles = data.titles
|
||
this.numberOfItems = data.totalTopics
|
||
} catch (err) {
|
||
console.error('Fehler beim Laden des Forums', err)
|
||
}
|
||
},
|
||
async saveNewTopic() {
|
||
const content = this.editor ? this.editor.getHTML() : ''
|
||
if (!this.canSaveTopic) return
|
||
try {
|
||
const { data } = await apiClient.post(
|
||
'/api/forum/topic',
|
||
{
|
||
forumId: this.forumId,
|
||
title: this.newTitle,
|
||
content
|
||
}
|
||
)
|
||
// Neu: Server kann aktuelle Liste zurückliefern
|
||
this.forumName = data.name
|
||
this.titles = data.titles
|
||
this.numberOfItems = data.totalTopics
|
||
this.page = data.page
|
||
this.inCreation = false
|
||
this.newTitle = ''
|
||
this.editor?.commands.setContent('')
|
||
showSuccess(this, 'Thema erfolgreich erstellt.')
|
||
} catch (err) {
|
||
console.error('Fehler beim Erstellen des Themas', err)
|
||
showApiError(this, err, 'Fehler beim Erstellen des Themas')
|
||
}
|
||
},
|
||
goToPage(page) {
|
||
if (page >= 1 && page <= this.totalPages) {
|
||
this.page = page
|
||
}
|
||
},
|
||
openProfile(id) {
|
||
this.$root.$refs.userProfileDialog.userId = id
|
||
this.$root.$refs.userProfileDialog.open()
|
||
},
|
||
openTopic(topicId) {
|
||
this.$router.push(`/socialnetwork/forumtopic/${topicId}`)
|
||
},
|
||
}
|
||
}
|
||
</script>
|
||
|
||
<style lang="scss" scoped>
|
||
.forum-view {
|
||
max-width: var(--content-max-width);
|
||
margin: 0 auto;
|
||
padding-bottom: 24px;
|
||
}
|
||
|
||
.forum-hero {
|
||
display: flex;
|
||
justify-content: space-between;
|
||
align-items: flex-end;
|
||
gap: 18px;
|
||
padding: 24px 26px;
|
||
margin-bottom: 16px;
|
||
}
|
||
|
||
.forum-kicker {
|
||
display: inline-block;
|
||
margin-bottom: 10px;
|
||
padding: 4px 10px;
|
||
border-radius: 999px;
|
||
background: rgba(248, 162, 43, 0.14);
|
||
color: #8a5411;
|
||
font-size: 0.75rem;
|
||
font-weight: 700;
|
||
text-transform: uppercase;
|
||
letter-spacing: 0.06em;
|
||
}
|
||
|
||
.forum-hero p {
|
||
margin: 0;
|
||
color: var(--color-text-secondary);
|
||
}
|
||
|
||
.creationtoggler {
|
||
margin-bottom: 0;
|
||
}
|
||
|
||
.forum-creation,
|
||
.forum-topics,
|
||
.forum-empty {
|
||
padding: 22px;
|
||
}
|
||
|
||
.forum-creation__header {
|
||
display: flex;
|
||
justify-content: space-between;
|
||
gap: 16px;
|
||
margin-bottom: 16px;
|
||
}
|
||
|
||
.forum-creation__header h3 {
|
||
margin: 0 0 6px;
|
||
}
|
||
|
||
.forum-creation__header p,
|
||
.forum-creation__hint {
|
||
margin: 0;
|
||
color: var(--color-text-secondary);
|
||
}
|
||
|
||
.forum-creation__actions {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 14px;
|
||
flex-wrap: wrap;
|
||
}
|
||
|
||
.newtitle {
|
||
display: flex;
|
||
flex-direction: column;
|
||
gap: 0.6em;
|
||
margin-bottom: 1rem;
|
||
}
|
||
|
||
.editor-container {
|
||
margin: 1em 0;
|
||
border: 1px solid var(--color-border);
|
||
border-radius: var(--radius-lg);
|
||
padding: 0;
|
||
min-height: 260px;
|
||
background-color: white;
|
||
}
|
||
.editor {
|
||
min-height: 260px;
|
||
outline: none;
|
||
cursor: text;
|
||
}
|
||
.editor :deep(.ProseMirror) {
|
||
min-height: 260px;
|
||
outline: none;
|
||
padding: 10px;
|
||
box-sizing: border-box;
|
||
width: 100%;
|
||
}
|
||
.editor :deep(.ProseMirror p) { margin: 0 0 .6rem; }
|
||
.editor :deep(.ProseMirror p:first-child) { margin-top: 0; }
|
||
.editor :deep(.ProseMirror-focused) { outline: 2px solid rgba(100,150,255,.35); }
|
||
|
||
.topic-list {
|
||
list-style: none;
|
||
padding: 0;
|
||
margin: 0;
|
||
}
|
||
|
||
.topic-card + .topic-card {
|
||
margin-top: 12px;
|
||
}
|
||
|
||
.topic-card__main {
|
||
width: 100%;
|
||
justify-content: space-between;
|
||
background: rgba(255, 255, 255, 0.72);
|
||
border: 1px solid var(--color-border);
|
||
box-shadow: none;
|
||
padding: 14px 16px;
|
||
border-radius: var(--radius-lg);
|
||
color: var(--color-text-primary);
|
||
}
|
||
|
||
.topic-card__main strong {
|
||
text-align: left;
|
||
}
|
||
|
||
.topic-card__meta {
|
||
color: var(--color-text-muted);
|
||
font-size: 0.82rem;
|
||
white-space: nowrap;
|
||
}
|
||
|
||
.pagination {
|
||
display: flex;
|
||
justify-content: center;
|
||
gap: 0.5em;
|
||
margin: 1em 0;
|
||
}
|
||
|
||
.pagination span {
|
||
padding: 0.5em;
|
||
}
|
||
|
||
.forum-empty {
|
||
color: var(--color-text-secondary);
|
||
text-align: center;
|
||
}
|
||
|
||
.forum-empty p {
|
||
margin-bottom: 12px;
|
||
}
|
||
|
||
@media (max-width: 960px) {
|
||
.forum-hero {
|
||
flex-direction: column;
|
||
align-items: flex-start;
|
||
}
|
||
|
||
.forum-creation__header {
|
||
flex-direction: column;
|
||
}
|
||
|
||
.topic-card__main {
|
||
align-items: flex-start;
|
||
}
|
||
}
|
||
</style>
|