feat(communication): add chat reactions emojis and images
This commit is contained in:
@@ -7,7 +7,7 @@ const router = useRouter()
|
||||
|
||||
const baseRooms = ref([]), projectRooms = ref([]), directRooms = ref([]), collaborationRooms = ref([])
|
||||
const invitations = ref([]), invitationBusy = ref(''), collaborationError = ref(''), listError = ref('')
|
||||
const unreadRooms = ref({}), messages = ref([]), members = ref([]), draft = ref('')
|
||||
const unreadRooms = ref({}), messages = ref([]), members = ref([]), draft = ref(''), draftImage = ref(null)
|
||||
const requestedRoom = () => typeof route.query.room === 'string' ? route.query.room : 'allgemein'
|
||||
const activeRoomKey = ref(requestedRoom())
|
||||
const loading = ref(true), roomLoading = ref(false), sending = ref(false), creating = ref(false)
|
||||
@@ -49,7 +49,7 @@ const activeRoom = computed(() => visibleRooms.value.find(room => room.key === a
|
||||
const isCollaboration = computed(() => activeRoom.value?.source === 'collaboration')
|
||||
const isInternal = computed(() => activeRoom.value?.source === 'internal')
|
||||
|
||||
function clearMessages() { messages.value = []; members.value = []; draft.value = ''; syncAfterId = 0; roomLoading.value = false; sending.value = false }
|
||||
function clearMessages() { messages.value = []; members.value = []; draft.value = ''; draftImage.value = null; syncAfterId = 0; roomLoading.value = false; sending.value = false }
|
||||
function mergeMessages(incoming) {
|
||||
const byId = new Map(messages.value.map(message => [message.id, message]))
|
||||
for (const message of incoming || []) byId.set(message.id, message)
|
||||
@@ -208,13 +208,13 @@ async function respondToInvitation(id, accept) {
|
||||
finally { if (currentScope(scope)) invitationBusy.value = '' }
|
||||
}
|
||||
async function sendMessage() {
|
||||
const text = draft.value.trim(), request = selection()
|
||||
if (!text || sending.value || !isInternal.value || !activeRoom.value?.exists) return
|
||||
const text = draft.value.trim(), image = draftImage.value, request = selection()
|
||||
if ((!text && !image) || sending.value || !isInternal.value || !activeRoom.value?.exists) return
|
||||
sending.value = true; draft.value = ''
|
||||
try {
|
||||
const message = await $api(nativeEndpoint(request.key, '/messages'), { method: 'POST', body: { text } })
|
||||
const message = await $api(nativeEndpoint(request.key, '/messages'), { method: 'POST', body: { text, ...(image ? { image } : {}) } })
|
||||
if (!currentSelection(request)) return
|
||||
mergeMessages([message]); await markRead(request); await scrollToBottom(request)
|
||||
draftImage.value = null; mergeMessages([message]); await markRead(request); await scrollToBottom(request)
|
||||
} catch (error) {
|
||||
if (currentSelection(request)) { draft.value = text; toast.add({ title: errorMessage(error), color: 'error' }) }
|
||||
} finally { if (currentSelection(request)) sending.value = false }
|
||||
@@ -371,21 +371,22 @@ onBeforeUnmount(() => {
|
||||
<p class="font-medium">Noch keine Nachrichten</p>
|
||||
<p class="text-sm">Beginne die Unterhaltung.</p>
|
||||
</div>
|
||||
<div v-else class="space-y-4">
|
||||
<ChatReactions v-else :key="`${session}:${activeRoomKey}`" v-slot="{ reactions, react, busy }" :api="$api" :endpoint="nativeEndpoint(activeRoomKey)" :messages="messages" @error="error => toast.add({ title: errorMessage(error), color: 'error' })">
|
||||
<div class="space-y-4">
|
||||
<div v-for="message in messages" :key="message.id" class="flex" :class="message.own ? 'justify-end' : 'justify-start'">
|
||||
<div class="max-w-[80%] rounded-2xl px-4 py-2" :class="message.own ? 'bg-primary-600 text-white' : 'bg-gray-100 text-gray-900'">
|
||||
<div v-if="!message.own" class="mb-1 text-xs font-semibold text-primary-600">{{ message.senderDisplayName }}</div>
|
||||
<p class="whitespace-pre-wrap break-words text-sm">{{ message.body }}</p>
|
||||
<ChatImage v-if="message.image" :api="$api" :endpoint="nativeEndpoint(activeRoomKey)" :message="message" />
|
||||
<p v-if="message.body" class="whitespace-pre-wrap break-words text-sm">{{ message.body }}</p>
|
||||
<div class="mt-1 text-right text-[11px]" :class="message.own ? 'text-primary-100' : 'text-gray-400'">{{ formatMessageTime(message.timestamp) }}</div>
|
||||
<ChatReactionBar :reactions="reactions[message.id] || []" :busy="busy" @react="react(message.id, $event)" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</ChatReactions>
|
||||
</div>
|
||||
|
||||
<form class="flex items-end gap-3 border-t border-gray-200 p-4" @submit.prevent="sendMessage">
|
||||
<UTextarea v-model="draft" class="flex-1" autoresize :rows="1" :maxrows="6" placeholder="Nachricht schreiben …" :disabled="!activeRoom?.exists" @keydown.enter.exact.prevent="sendMessage" />
|
||||
<UButton type="submit" icon="i-heroicons-paper-airplane" :loading="sending" :disabled="!draft.trim() || !activeRoom?.exists">Senden</UButton>
|
||||
</form>
|
||||
<ChatComposer :key="`${session}:${activeRoomKey}`" v-model="draft" v-model:image="draftImage" :busy="sending" :disabled="!activeRoom?.exists || roomLoading" @send="sendMessage" />
|
||||
</main>
|
||||
|
||||
<aside v-if="!isCollaboration" class="hidden min-h-0 overflow-y-auto border-l border-gray-200 p-4 lg:block">
|
||||
|
||||
Reference in New Issue
Block a user