Add password reset localization and chat configuration
- Implemented German and English localization for password reset functionality. - Added WebSocket URL resolution logic in chat services to support various environments and configurations. - Created centralized chat configuration for event keys and payload mappings. - Developed RoomsView component for admin chat room management, including create, edit, and delete functionalities.
This commit is contained in:
148
frontend/src/dialogues/chat/MultiChat.vue
Normal file
148
frontend/src/dialogues/chat/MultiChat.vue
Normal file
@@ -0,0 +1,148 @@
|
||||
<template>
|
||||
<DialogWidget ref="dialog" :title="$t('chat.multichat.title')" :modal="true" width="40em" height="32em" name="MultiChat">
|
||||
<div class="multi-chat-top">
|
||||
<select v-model="selectedRoom" class="room-select">
|
||||
<option v-for="room in rooms" :key="room.id" :value="room.id">{{ room.title }}</option>
|
||||
</select>
|
||||
<div class="options-popdown">
|
||||
<label>
|
||||
<input type="checkbox" v-model="autoscroll" />
|
||||
{{ $t('chat.multichat.autoscroll') }}
|
||||
</label>
|
||||
<!-- Weitere Optionen können hier ergänzt werden -->
|
||||
</div>
|
||||
</div>
|
||||
<div class="multi-chat-output" ref="output" @mouseenter="mouseOverOutput = true" @mouseleave="mouseOverOutput = false">
|
||||
<div v-for="msg in messages" :key="msg.id" class="chat-message">
|
||||
<span class="user">{{ msg.user }}:</span> <span class="text">{{ msg.text }}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="multi-chat-input">
|
||||
<input v-model="input" @keyup.enter="sendMessage" class="chat-input" :placeholder="$t('chat.multichat.placeholder')" />
|
||||
<button @click="sendMessage" class="send-btn">{{ $t('chat.multichat.send') }}</button>
|
||||
<button @click="shout" class="mini-btn">{{ $t('chat.multichat.shout') }}</button>
|
||||
<button @click="action" class="mini-btn">{{ $t('chat.multichat.action') }}</button>
|
||||
<button @click="roll" class="mini-btn">{{ $t('chat.multichat.roll') }}</button>
|
||||
</div>
|
||||
</DialogWidget>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import DialogWidget from '@/components/DialogWidget.vue';
|
||||
|
||||
export default {
|
||||
name: 'MultiChat',
|
||||
components: { DialogWidget },
|
||||
data() {
|
||||
return {
|
||||
rooms: [],
|
||||
selectedRoom: null,
|
||||
autoscroll: true,
|
||||
mouseOverOutput: false,
|
||||
messages: [],
|
||||
input: ''
|
||||
};
|
||||
},
|
||||
watch: {
|
||||
messages() {
|
||||
this.$nextTick(this.handleAutoscroll);
|
||||
},
|
||||
autoscroll(val) {
|
||||
if (val) this.handleAutoscroll();
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
open(rooms = []) {
|
||||
this.rooms = rooms;
|
||||
this.selectedRoom = rooms.length ? rooms[0].id : null;
|
||||
this.autoscroll = true;
|
||||
this.messages = [];
|
||||
this.input = '';
|
||||
this.$refs.dialog.open();
|
||||
},
|
||||
handleAutoscroll() {
|
||||
if (this.autoscroll && !this.mouseOverOutput) {
|
||||
const out = this.$refs.output;
|
||||
if (out) out.scrollTop = out.scrollHeight;
|
||||
}
|
||||
},
|
||||
sendMessage() {
|
||||
if (!this.input.trim()) return;
|
||||
this.messages.push({ id: Date.now(), user: 'Ich', text: this.input });
|
||||
this.input = '';
|
||||
},
|
||||
shout() {
|
||||
// Schreien-Logik
|
||||
},
|
||||
action() {
|
||||
// Aktion-Logik
|
||||
},
|
||||
roll() {
|
||||
// Würfeln-Logik
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.multi-chat-top {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 1em;
|
||||
margin-bottom: 0.5em;
|
||||
}
|
||||
.room-select {
|
||||
min-width: 10em;
|
||||
}
|
||||
.options-popdown {
|
||||
background: #f5f5f5;
|
||||
border-radius: 4px;
|
||||
padding: 0.3em 0.8em;
|
||||
font-size: 0.95em;
|
||||
}
|
||||
.multi-chat-output {
|
||||
background: #222;
|
||||
color: #fff;
|
||||
height: 16em;
|
||||
overflow-y: auto;
|
||||
margin-bottom: 0.5em;
|
||||
padding: 0.7em;
|
||||
border-radius: 4px;
|
||||
font-size: 1em;
|
||||
}
|
||||
.chat-message {
|
||||
margin-bottom: 0.3em;
|
||||
}
|
||||
.user {
|
||||
font-weight: bold;
|
||||
color: #90caf9;
|
||||
}
|
||||
.multi-chat-input {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.5em;
|
||||
}
|
||||
.chat-input {
|
||||
flex: 1;
|
||||
padding: 0.4em 0.7em;
|
||||
border-radius: 3px;
|
||||
border: 1px solid #bbb;
|
||||
}
|
||||
.send-btn {
|
||||
padding: 0.3em 1.1em;
|
||||
border-radius: 3px;
|
||||
background: #1976d2;
|
||||
color: #fff;
|
||||
border: none;
|
||||
cursor: pointer;
|
||||
}
|
||||
.mini-btn {
|
||||
padding: 0.2em 0.7em;
|
||||
font-size: 0.95em;
|
||||
border-radius: 3px;
|
||||
background: #eee;
|
||||
color: #333;
|
||||
border: 1px solid #bbb;
|
||||
cursor: pointer;
|
||||
}
|
||||
</style>
|
||||
1711
frontend/src/dialogues/chat/MultiChatDialog.vue
Normal file
1711
frontend/src/dialogues/chat/MultiChatDialog.vue
Normal file
File diff suppressed because it is too large
Load Diff
@@ -1,26 +1,26 @@
|
||||
<template>
|
||||
<DialogWidget ref="dialog" title="randomchat.title" icon="dice24.png" :show-close="true" :buttons="buttons"
|
||||
<DialogWidget ref="dialog" title="chat.randomchat.title" icon="dice24.png" :show-close="true" :buttons="buttons"
|
||||
:modal="false" :isTitleTranslated="true" @close="closeDialog" name="RandomChat">
|
||||
<div v-if="chatIsRunning" class="randomchat">
|
||||
<div class="headline">
|
||||
{{ $t("randomchat.agerange") }}
|
||||
{{ $t("chat.randomchat.agerange") }}
|
||||
<input type="number" v-model="agefromsearch" min="18" max="150" size="5" />
|
||||
-
|
||||
<input type="number" v-model="agetosearch" min="18" max="150" size="5" />
|
||||
<span class="multiselect">
|
||||
{{ $t("randomchat.gendersearch") }}
|
||||
{{ $t("chat.randomchat.gendersearch") }}
|
||||
<div>
|
||||
<label><input type="checkbox" v-model="searchmale" />{{ $t("randomchat.gender.male") }}</label>
|
||||
<label><input type="checkbox" v-model="searchfemale" />{{ $t("randomchat.gender.female")
|
||||
<label><input type="checkbox" v-model="searchmale" />{{ $t("chat.randomchat.gender.male") }}</label>
|
||||
<label><input type="checkbox" v-model="searchfemale" />{{ $t("chat.randomchat.gender.female")
|
||||
}}</label>
|
||||
</div>
|
||||
</span>
|
||||
<label><input type="checkbox" v-model="camonlysearch" />{{ $t("randomchat.camonly") }}</label>
|
||||
<label><input type="checkbox" v-model="showcam" />{{ $t("randomchat.showcam") }}</label>
|
||||
<img v-if="isLoggedIn" src="/images/icons/friendsadd16.png" :tooltip="$t('randomchat.addfriend')" />
|
||||
<label><input type="checkbox" v-model="autosearch" />{{ $t("randomchat.autosearch") }}</label>
|
||||
<button @click="nextUser" v-if="partner != null">{{ $t("randomchat.jumptonext") }}</button>
|
||||
<button @click="startSearch" v-if="partner == null && !searching">{{ $t("randomchat.startsearch")
|
||||
<label><input type="checkbox" v-model="camonlysearch" />{{ $t("chat.randomchat.camonly") }}</label>
|
||||
<label><input type="checkbox" v-model="showcam" />{{ $t("chat.randomchat.showcam") }}</label>
|
||||
<img v-if="isLoggedIn" src="/images/icons/friendsadd16.png" :tooltip="$t('chat.randomchat.addfriend')" />
|
||||
<label><input type="checkbox" v-model="autosearch" />{{ $t("chat.randomchat.autosearch") }}</label>
|
||||
<button @click="nextUser" v-if="partner != null">{{ $t("chat.randomchat.jumptonext") }}</button>
|
||||
<button @click="startSearch" v-if="partner == null && !searching">{{ $t("chat.randomchat.startsearch")
|
||||
}}</button>
|
||||
</div>
|
||||
<div class="output">
|
||||
@@ -28,7 +28,7 @@
|
||||
</div>
|
||||
<div class="inputline">
|
||||
<label>
|
||||
{{ $t("randomchat.input") }}
|
||||
{{ $t("chat.randomchat.input") }}
|
||||
<input type="text" v-model="inputtext" @keyup.enter="sendMessage" />
|
||||
</label>
|
||||
<img src="/images/icons/enter16.png" @click="sendMessage" />
|
||||
@@ -37,20 +37,20 @@
|
||||
</div>
|
||||
<div v-else>
|
||||
<div>
|
||||
<label>{{ $t("randomchat.age") }}
|
||||
<label>{{ $t("chat.randomchat.age") }}
|
||||
<input type="number" v-model="age" min="18" max="150" value="18" />
|
||||
</label>
|
||||
</div>
|
||||
<div>
|
||||
<label>{{ $t("randomchat.gender.title") }}
|
||||
<label>{{ $t("chat.randomchat.gender.title") }}
|
||||
<select v-model="gender">
|
||||
<option value="f">{{ $t("randomchat.gender.female") }}</option>
|
||||
<option value="m">{{ $t("randomchat.gender.male") }}</option>
|
||||
<option value="f">{{ $t("chat.randomchat.gender.female") }}</option>
|
||||
<option value="m">{{ $t("chat.randomchat.gender.male") }}</option>
|
||||
</select>
|
||||
</label>
|
||||
</div>
|
||||
<div>
|
||||
<button @click="startRandomChat()">{{ $t("randomchat.start") }}</button>
|
||||
<button @click="startRandomChat()">{{ $t("chat.randomchat.start") }}</button>
|
||||
</div>
|
||||
</div>
|
||||
</DialogWidget>
|
||||
@@ -60,6 +60,7 @@
|
||||
import DialogWidget from '@/components/DialogWidget.vue';
|
||||
import { mapGetters } from 'vuex';
|
||||
import axios from 'axios';
|
||||
import DOMPurify from 'dompurify';
|
||||
|
||||
export default {
|
||||
name: 'RandomChatDialog',
|
||||
@@ -69,7 +70,8 @@ export default {
|
||||
computed: {
|
||||
...mapGetters(['isLoggedIn', 'user']),
|
||||
buttons() {
|
||||
return [{ text: this.$t('randomchat.close') }];
|
||||
// Use translation key; DialogWidget will translate when isTitleTranslated=true
|
||||
return [{ text: 'chat.randomchat.close' }];
|
||||
},
|
||||
},
|
||||
data() {
|
||||
@@ -113,26 +115,6 @@ export default {
|
||||
this.$refs.dialog.open();
|
||||
},
|
||||
|
||||
async closeDialog() {
|
||||
// ① Stoppe alle laufenden Intervalle
|
||||
if (this.searchInterval) {
|
||||
clearInterval(this.searchInterval);
|
||||
this.searchInterval = null;
|
||||
}
|
||||
if (this.messagesInterval) {
|
||||
clearInterval(this.messagesInterval);
|
||||
this.messagesInterval = null;
|
||||
}
|
||||
// ② verlasse Chat auf Server-Seite
|
||||
this.$refs.dialog.close();
|
||||
await axios.post('/api/chat/exit', { id: this.userId });
|
||||
await this.removeUserFromChat();
|
||||
// Reset-Status
|
||||
this.chatIsRunning = false;
|
||||
this.partner = null;
|
||||
this.messages = [];
|
||||
},
|
||||
|
||||
async registerUser() {
|
||||
try {
|
||||
const response = await axios.post('/api/chat/register', {
|
||||
@@ -146,9 +128,29 @@ export default {
|
||||
},
|
||||
|
||||
async closeDialog() {
|
||||
// Stop intervals first
|
||||
if (this.searchInterval) {
|
||||
clearInterval(this.searchInterval);
|
||||
this.searchInterval = null;
|
||||
}
|
||||
if (this.messagesInterval) {
|
||||
clearInterval(this.messagesInterval);
|
||||
this.messagesInterval = null;
|
||||
}
|
||||
// Inform backend and cleanup
|
||||
try {
|
||||
if (this.userId) {
|
||||
await axios.post('/api/chat/exit', { id: this.userId });
|
||||
await this.removeUserFromChat();
|
||||
}
|
||||
} catch (e) {
|
||||
// ignore
|
||||
}
|
||||
// Reset state and close widget
|
||||
this.chatIsRunning = false;
|
||||
this.partner = null;
|
||||
this.messages = [];
|
||||
this.$refs.dialog.close();
|
||||
await axios.post('/api/chat/exit', { id: this.userId });
|
||||
await this.removeUserFromChat();
|
||||
},
|
||||
|
||||
async startRandomChat() {
|
||||
@@ -160,7 +162,7 @@ export default {
|
||||
async startSearch() {
|
||||
this.searching = true;
|
||||
await this.findMatch();
|
||||
this.messages.push({ type: 'system', tr: 'randomchat.waitingForMatch' });
|
||||
this.messages.push({ type: 'system', tr: 'chat.randomchat.waitingForMatch' });
|
||||
this.searchInterval = setInterval(this.findMatch, 500);
|
||||
},
|
||||
|
||||
@@ -174,10 +176,10 @@ export default {
|
||||
if (response.data.status === 'matched') {
|
||||
this.searching = false;
|
||||
clearInterval(this.searchInterval);
|
||||
const initText = this.$t('randomchat.chatpartner')
|
||||
const initText = this.$t('chat.randomchat.chatpartner')
|
||||
.replace(
|
||||
'<gender>',
|
||||
this.$t(`randomchat.partnergender${response.data.user.gender}`)
|
||||
this.$t(`chat.randomchat.partnergender${response.data.user.gender}`)
|
||||
)
|
||||
.replace('<age>', response.data.user.age);
|
||||
this.messages = [{ type: 'system', text: initText }];
|
||||
@@ -220,7 +222,7 @@ export default {
|
||||
activities.forEach((act) => {
|
||||
if (act.activity === 'otheruserleft') {
|
||||
this.partner = null;
|
||||
this.messages.push({ type: 'system', tr: 'randomchat.userleftchat' });
|
||||
this.messages.push({ type: 'system', tr: 'chat.randomchat.userleftchat' });
|
||||
}
|
||||
});
|
||||
this.messages.push(...newMsgs);
|
||||
@@ -237,10 +239,10 @@ export default {
|
||||
async nextUser() {
|
||||
await axios.post('/api/chat/leave', { id: this.userId });
|
||||
this.partner = null;
|
||||
this.messages.push({ type: 'system', tr: 'randomchat.selfstopped' });
|
||||
this.messages.push({ type: 'system', tr: 'chat.randomchat.selfstopped' });
|
||||
if (this.autosearch) {
|
||||
this.searchInterval = setInterval(this.findMatch, 500);
|
||||
this.messages.push({ type: 'system', tr: 'randomchat.waitingForMatch' });
|
||||
this.messages.push({ type: 'system', tr: 'chat.randomchat.waitingForMatch' });
|
||||
}
|
||||
},
|
||||
|
||||
@@ -250,7 +252,7 @@ export default {
|
||||
return `<span class="rc-system">${txt}</span>`;
|
||||
}
|
||||
const cls = message.type === 'self' ? 'rc-self' : 'rc-partner';
|
||||
const who = message.type === 'self' ? this.$t('randomchat.self') : this.$t('randomchat.partner');
|
||||
const who = message.type === 'self' ? this.$t('chat.randomchat.self') : this.$t('chat.randomchat.partner');
|
||||
return `<span class="${cls}">${who}: </span>${message.text}`;
|
||||
},
|
||||
},
|
||||
|
||||
Reference in New Issue
Block a user