Files
yourpart3/frontend/src/views/social/ForumView.vue
Torsten Schulz (local) 6ad66da836 feat(localization): expand language support and enhance UI for user settings
- Added support for additional UI locales including Cebuano and Spanish, improving accessibility for a broader user base.
- Updated language selection components in the AppHeader and SettingsWidget to reflect new language options, enhancing user experience.
- Enhanced localization of various UI elements across components, ensuring consistent language representation and improved user engagement.
- Implemented logic to synchronize user language preferences with backend settings, providing a seamless experience when changing languages.
2026-04-02 07:54:44 +02:00

365 lines
10 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">{{ $t('socialnetwork.forum.kicker') }}</span>
<h2>{{ $t('socialnetwork.forum.title') }} {{ forumName }}</h2>
<p>{{ $t('socialnetwork.forum.intro') }}</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>{{ $t('socialnetwork.forum.createTitle') }}</h3>
<p>{{ $t('socialnetwork.forum.createIntro') }}</p>
</div>
<button type="button" class="button-secondary" @click="cancelCreation">{{ $t('socialnetwork.forum.cancelCreation') }}</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">{{ $t('socialnetwork.forum.creationHint') }}</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 || $t('socialnetwork.forum.communityFallback') }}
</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: ForumID 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 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: {
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, this.$t('socialnetwork.forum.topicCreated'))
} catch (err) {
console.error('Fehler beim Erstellen des Themas', err)
showApiError(this, err, this.$t('socialnetwork.forum.topicCreateError'))
}
},
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>