Files
yourpart3/frontend/src/views/social/ForumTopicView.vue
Torsten Schulz (local) a02fe1f008
All checks were successful
Deploy to production / deploy (push) Successful in 2m1s
feat(Moderation): implement moderation reports feature
- Added moderationRouter to handle moderation-related API routes.
- Introduced new methods in AdminController for fetching all regions, region types, and creating regions.
- Enhanced adminRouter with routes for moderation reports and status updates.
- Updated navigationController to include moderation reports in the admin menu.
- Implemented frontend components for reporting messages in the forum and managing moderation reports.
- Added internationalization support for moderation-related texts in multiple languages.
2026-04-27 14:52:19 +02:00

230 lines
6.6 KiB
Vue

<template>
<div class="forum-topic-view">
<section class="forum-topic-hero surface-card">
<div>
<div class="forum-topic-back link" @click="openForum()">{{ $t('socialnetwork.forum.title') }} {{ forumName }}</div>
<h2 v-if="forumTopic">{{ forumTopic }}</h2>
<p>{{ $t('socialnetwork.forum.topicIntro') }}</p>
</div>
</section>
<section class="forum-topic-messages">
<ul class="messages">
<li v-for="message in messages" :key="message.id" class="surface-card">
<div v-html="sanitizedMessage(message)"></div>
<div class="footer">
<span class="footer-left">
<span class="link" @click="openProfile(message.lastMessageUser.hashedId)">
{{ message.lastMessageUser.username }}
</span>
<button type="button" class="report-btn" @click="reportMessage(message)">
{{ $t('socialnetwork.forum.reportAction') }}
</button>
</span>
<span>{{ new Date(message.createdAt).toLocaleString() }}</span>
</div>
</li>
</ul>
</section>
<div class="editor-container surface-card">
<EditorContent v-if="editor" :editor="editor" class="editor" />
</div>
<button @click="saveNewMessage">{{ $t('socialnetwork.forum.createNewMesssage') }}</button>
</div>
</template>
<script>
import { Editor, EditorContent } from '@tiptap/vue-3'
import StarterKit from '@tiptap/starter-kit'
import apiClient from '../../utils/axios'
import DOMPurify from 'dompurify'
export default {
name: 'ForumTopicView',
components: {
EditorContent,
},
data() {
return {
forumTopicId: '',
forumTopic: null,
forumName: null,
forumId: 0,
messages: [],
editor: null,
}
},
mounted() {
this.forumTopicId = this.$route.params.id;
this.loadForumTopic();
this.editor = new Editor({
extensions: [StarterKit],
content: '',
editable: true,
editorProps: { attributes: { class: 'pm-root' } },
});
},
beforeUnmount() {
if (this.editor) {
this.editor.destroy();
}
},
methods: {
async loadForumTopic() {
try {
const response = await apiClient.get(`/api/forum/topic/${this.forumTopicId}`);
this.setContent(response.data);
} catch (error) {
console.error(error);
}
},
setContent(data) {
this.forumTopic = data.title;
this.forumName = data.forum.name;
this.forumId = data.forum.id;
this.messages = data.messages;
},
async openProfile(id) {
this.$root.$refs.userProfileDialog.userId = id;
this.$root.$refs.userProfileDialog.open();
},
async saveNewMessage() {
const content = this.editor ? this.editor.getHTML() : '';
if (!content.trim()) return;
try {
const url = `/api/forum/topic/${this.forumTopicId}/message`;
const response = await apiClient.post(url, { content });
this.editor.commands.clearContent();
this.setContent(response.data);
} catch (error) {
console.error(error);
}
},
openForum() {
this.$router.push(`/socialnetwork/forum/${this.forumId}`);
},
sanitizedMessage(message) {
return DOMPurify.sanitize(message.text);
},
async reportMessage(message) {
const reason = window.prompt(this.$t('socialnetwork.forum.reportPrompt'));
if (reason == null) return;
const trimmed = String(reason || '').trim();
if (trimmed.length < 3) {
this.$root.$refs.messageDialog?.open(
this.$t('socialnetwork.forum.reportReasonTooShort'),
this.$t('error.title')
);
return;
}
try {
await apiClient.post('/api/moderation/reports', {
targetType: 'forum_message',
targetId: message.id,
reason: trimmed,
details: '',
});
this.$root.$refs.messageDialog?.open(
this.$t('socialnetwork.forum.reportSubmitted'),
this.$t('message.title')
);
} catch (error) {
console.error('Error creating moderation report:', error);
this.$root.$refs.messageDialog?.open(
this.$t('socialnetwork.forum.reportError'),
this.$t('error.title')
);
}
}
}
}
</script>
<style lang="scss" scoped>
.forum-topic-view {
max-width: 860px;
margin: 0 auto;
padding-bottom: 24px;
}
.forum-topic-hero {
padding: 24px 26px;
margin-bottom: 16px;
}
.forum-topic-back {
margin-bottom: 10px;
font-weight: 700;
}
.forum-topic-hero p {
margin: 0;
color: var(--color-text-secondary);
}
.messages {
list-style-type: none;
padding: 0;
margin: 0;
}
.messages > li {
margin-bottom: 0.75em;
padding: 1rem 1.1rem;
}
.messages > li > .footer {
color: var(--color-text-muted);
font-size: 0.8em;
margin-top: 0.5em;
display: flex;
align-items: center;
gap: 8px;
}
.footer-left {
flex: 1;
display: inline-flex;
align-items: center;
gap: 10px;
}
.messages > li > .footer > span:last-child {
text-align: right;
}
.report-btn {
min-height: auto;
padding: 2px 8px;
font-size: 0.75rem;
border-radius: 999px;
background: rgba(255, 255, 255, 0.78);
}
.editor-container {
margin-top: 1rem;
padding: 0;
min-height: 260px;
background-color: white;
overflow: hidden;
}
.editor {
min-height: 260px;
outline: none;
cursor: text;
}
.editor :deep(.ProseMirror) {
min-height: 260px;
outline: none;
padding: 14px;
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); }
</style>