Files
yourpart3/frontend/src/views/social/ForumView.vue

303 lines
8.1 KiB
Vue
Raw Blame History

This file contains invisible Unicode characters
This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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.
<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 Beitraege an einem strukturierten Ort.</p>
</div>
<div class="creationtoggler">
<button @click="createNewTopic">
{{ $t(!inCreation
? 'socialnetwork.forum.showNewTopic'
: 'socialnetwork.forum.hideNewTopic') }}
</button>
</div>
</section>
<section v-if="inCreation" class="forum-creation surface-card">
<label class="newtitle">
<span>{{ $t('socialnetwork.forum.topic') }}</span>
<input type="text" v-model="newTitle" />
</label>
<div class="editor-container">
<EditorContent v-if="editor" :editor="editor" class="editor" />
</div>
<button @click="saveNewTopic">
{{ $t('socialnetwork.forum.createNewTopic') }}
</button>
</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">
{{ $t('socialnetwork.forum.noTitles') }}
</div>
</div>
</template>
<script>
import { Editor, EditorContent } from '@tiptap/vue-3'
import StarterKit from '@tiptap/starter-kit'
import apiClient from '../../utils/axios'
export default {
name: 'ForumView',
components: { EditorContent },
data() {
return {
forumName: '',
page: 1,
numberOfItems: 0,
titles: [],
inCreation: false,
newTitle: '',
editor: null,
}
},
computed: {
// Neu: ForumID immer direkt aus der Route ziehen
forumId() {
return this.$route.params.id
},
totalPages() {
return Math.ceil(this.numberOfItems / 25)
}
},
watch: {
// Sobald sich die RouteID ä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: {
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)
}
},
createNewTopic() {
this.inCreation = !this.inCreation
if (this.inCreation && this.editor) {
this.editor.commands.setContent('')
this.$nextTick(() => this.editor?.commands.focus('end'))
}
},
async saveNewTopic() {
const content = this.editor ? this.editor.getHTML() : ''
if (!this.newTitle.trim() || !content.trim()) 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 = ''
} catch (err) {
console.error('Fehler beim Erstellen des Themas', err)
}
},
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;
}
.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;
}
@media (max-width: 960px) {
.forum-hero {
flex-direction: column;
align-items: flex-start;
}
.topic-card__main {
align-items: flex-start;
}
}
</style>