parent
9d2212ae2e
commit
90b6200f96
@ -0,0 +1,68 @@ |
||||
<template> |
||||
<div class="flex items-center gap-2"> |
||||
<BaseButton |
||||
:label="label" |
||||
:size="size" |
||||
type="primary" |
||||
icon="attachment" |
||||
@click="showFileDialog" |
||||
/> |
||||
<div v-if="files.length > 0"> |
||||
<p class="text-gray-500" v-for="file in files" :key="file.name"> |
||||
{{ file.name }} |
||||
</p> |
||||
</div> |
||||
<input |
||||
ref="inputFile" |
||||
type="file" |
||||
class="hidden" |
||||
:accept="acceptFileType" |
||||
@change="filesSelected" |
||||
multiple |
||||
/> |
||||
</div> |
||||
</template> |
||||
|
||||
<script setup> |
||||
import BaseButton from "./BaseButton.vue" |
||||
import { computed, ref } from "vue" |
||||
|
||||
const props = defineProps({ |
||||
modelValue: Array, |
||||
label: String, |
||||
accept: { |
||||
type: String, |
||||
default: "", |
||||
}, |
||||
size: { |
||||
type: String, |
||||
default: "normal", |
||||
}, |
||||
}) |
||||
|
||||
const emit = defineEmits(["update:modelValue"]) |
||||
|
||||
const inputFile = ref(null) |
||||
|
||||
const acceptFileType = computed(() => { |
||||
if (props.accept === "image") { |
||||
return "image/*" |
||||
} |
||||
return props.accept |
||||
}) |
||||
|
||||
const files = computed({ |
||||
get: () => props.modelValue, |
||||
set: (newValue) => { |
||||
emit("update:modelValue", newValue) |
||||
}, |
||||
}) |
||||
|
||||
const filesSelected = () => { |
||||
files.value = Array.from(inputFile.value.files) |
||||
} |
||||
|
||||
const showFileDialog = () => { |
||||
inputFile.value.click() |
||||
} |
||||
</script> |
@ -0,0 +1,157 @@ |
||||
<template> |
||||
<div class="social-group-show group-info text-center"> |
||||
<div class="group-header"> |
||||
<h1 class="group-title">{{ groupInfo?.title || '...' }}</h1> |
||||
<p class="group-description">{{ groupInfo?.description }}</p> |
||||
</div> |
||||
</div> |
||||
<div v-if="!isLoading" class="discussion"> |
||||
<BaseButton @click="goBack" class="back-button mb-8" icon="back" type="button" :label="t('Back to the list')" /> |
||||
<h2>{{ firstMessageTitle || 'Discussion Thread' }}</h2> |
||||
<div class="message-list mt-8"> |
||||
<MessageItem |
||||
v-for="message in messages" |
||||
:key="message.id" |
||||
:message="message" |
||||
:currentUser="user" |
||||
:indentation="0" |
||||
:isMainMessage="message.parentId === null || message.parentId === 0" |
||||
:isModerator="groupInfo.isModerator" |
||||
@replyMessage="openDialogForReply" |
||||
@editMessage="openDialogForEdit" |
||||
@deleteMessage="deleteMessage" |
||||
/> |
||||
</div> |
||||
</div> |
||||
|
||||
<Dialog header="Reply/Edit Message" v-model:visible="showMessageDialog" modal closable> |
||||
<form @submit.prevent="handleSubmit"> |
||||
<BaseInputText v-if="isEditMode" id="title" :label="t('Title')" v-model="messageTitle" :isInvalid="titleError" /> |
||||
<BaseEditor editorId="messageEditor" v-model="messageContent" title="Message" /> |
||||
<BaseFileUploadMultiple v-model="files" :label="t('Add files')" accept="image/png, image/jpeg" /> |
||||
<BaseButton type="button" :label="t('Send message')" icon="save" @click="handleSubmit" class="mt-8" /> |
||||
</form> |
||||
</Dialog> |
||||
</template> |
||||
|
||||
<script setup> |
||||
import { onMounted, reactive, computed, ref, toRefs } from "vue" |
||||
import { useRouter, useRoute } from "vue-router" |
||||
import { useSocialInfo } from "../../composables/useSocialInfo" |
||||
import axios from "axios" |
||||
import MessageItem from "./MessageItem.vue" |
||||
import BaseButton from "../basecomponents/BaseButton.vue" |
||||
import { useI18n } from "vue-i18n" |
||||
import BaseInputText from "../basecomponents/BaseInputText.vue" |
||||
import BaseEditor from "../basecomponents/BaseEditor.vue" |
||||
import BaseFileUploadMultiple from "../basecomponents/BaseFileUploadMultiple.vue" |
||||
|
||||
const router = useRouter() |
||||
const route = useRoute() |
||||
const { user, groupInfo, isGroup, loadGroup, isLoading } = useSocialInfo() |
||||
|
||||
const messages = ref([]) |
||||
const { t } = useI18n() |
||||
|
||||
const firstMessageTitle = computed(() => { |
||||
return messages.value.length > 0 ? messages.value[0].title : null |
||||
}) |
||||
const showMessageDialog = ref(false) |
||||
const isEditMode = ref(false) |
||||
const currentMessageId = ref(null) |
||||
const state = reactive({ |
||||
messageTitle: '', |
||||
messageContent: '', |
||||
files: [], |
||||
titleError: false, |
||||
}) |
||||
const { messageTitle, messageContent, files, titleError } = toRefs(state) |
||||
const getIndentation = (message) => { |
||||
let indent = 0 |
||||
let parent = messages.value.find(m => m.id === message.parentId) |
||||
while (parent) { |
||||
indent += 30 |
||||
parent = messages.value.find(m => m.id === parent.parentId) |
||||
} |
||||
return `${indent}px` |
||||
} |
||||
const fetchMessages = async () => { |
||||
const groupId = route.params.group_id |
||||
const discussionId = route.params.discussion_id |
||||
try { |
||||
const response = await axios.get(`/social-network/group/${groupId}/discussion/${discussionId}/messages`) |
||||
messages.value = response.data |
||||
} catch (error) { |
||||
console.error('Error fetching messages:', error) |
||||
} |
||||
} |
||||
function openDialogForReply(message) { |
||||
isEditMode.value = false |
||||
currentMessageId.value = message.id |
||||
showMessageDialog.value = true |
||||
} |
||||
|
||||
function openDialogForEdit(message) { |
||||
isEditMode.value = true |
||||
currentMessageId.value = message.id |
||||
messageTitle.value = message.title |
||||
messageContent.value = message.content |
||||
showMessageDialog.value = true |
||||
} |
||||
|
||||
async function handleSubmit() { |
||||
if (isEditMode.value && title.value.trim() === '') { |
||||
titleError.value = true |
||||
return |
||||
} |
||||
const filesArray = files.value |
||||
const formData = new FormData() |
||||
formData.append('action', isEditMode.value ? 'edit_message_group' : 'reply_message_group') |
||||
formData.append('title', messageTitle.value) |
||||
formData.append('content', messageContent.value) |
||||
if (isEditMode.value) { |
||||
formData.append('messageId', currentMessageId.value) |
||||
} else { |
||||
formData.append('parentId', currentMessageId.value) |
||||
} |
||||
formData.append('userId', user.value.id) |
||||
formData.append('groupId', groupInfo.value.id) |
||||
for (let i = 0; i < filesArray.length; i++) { |
||||
formData.append('files[]', filesArray[i]) |
||||
} |
||||
try { |
||||
await axios.post('/social-network/group-action', formData, { |
||||
headers: { 'Content-Type': 'multipart/form-data' }, |
||||
}) |
||||
showMessageDialog.value = false |
||||
await fetchMessages() |
||||
} catch (error) { |
||||
console.error('Error submitting the form:', error) |
||||
} |
||||
} |
||||
|
||||
const deleteMessage = async (message) => { |
||||
try { |
||||
const confirmed = confirm(`Are you sure you want to delete this message: ${message.title}?`) |
||||
if (!confirmed) { |
||||
return |
||||
} |
||||
const data = { |
||||
action: 'delete_message_group', |
||||
messageId: message.id, |
||||
userId: user.value.id, |
||||
groupId: groupInfo.value.id |
||||
} |
||||
await axios.post('/social-network/group-action', data) |
||||
await router.push({ name: 'UserGroupShow', params: { group_id: groupInfo.value.id } }) |
||||
} catch (error) { |
||||
console.error('Error deleting the message:', error) |
||||
} |
||||
} |
||||
onMounted(() => { |
||||
fetchMessages() |
||||
}) |
||||
const goBack = () => { |
||||
router.back() |
||||
} |
||||
</script> |
@ -0,0 +1,66 @@ |
||||
<template> |
||||
<div class="message-item social-group-messages" :style="{ paddingLeft: indentation + 'px' }"> |
||||
<div class="message-avatar"> |
||||
<img :src="message.avatar" alt="Avatar" class="avatar"> |
||||
</div> |
||||
<div class="message-body"> |
||||
<div class="message-meta"> |
||||
<span class="message-author">{{ message.user }}</span> |
||||
<span class="message-date">{{ relativeDatetime(message.created) }}</span> |
||||
</div> |
||||
<div class="message-content" v-html="message.content"></div> |
||||
<div class="message-attachments mt-8" v-if="message.attachment && message.attachment.length"> |
||||
<div v-for="(attachment, index) in message.attachment" :key="index" class="attachment-link"> |
||||
<a :href="attachment.link" target="_blank">{{ attachment.filename }}</a> |
||||
<span> ({{ formatSize(attachment.size) }})</span> |
||||
</div> |
||||
</div> |
||||
<div class="message-actions"> |
||||
<BaseIcon icon="reply" size="normal" @click="$emit('replyMessage', message)" /> |
||||
<div> |
||||
<BaseIcon icon="edit" v-if="isMessageCreator(message)" size="normal" @click="$emit('editMessage', message)" /> |
||||
<BaseIcon icon="delete" size="normal" v-if="isMainMessage && isModerator" @click="$emit('deleteMessage', message)" /> |
||||
</div> |
||||
</div> |
||||
<div class="child-messages"> |
||||
<MessageItem |
||||
v-for="child in message.children" |
||||
:key="child.id" |
||||
:message="child" |
||||
:currentUser="currentUser" |
||||
:indentation="indentation + 20" |
||||
@replyMessage="$emit('replyMessage', $event)" |
||||
@editMessage="$emit('editMessage', $event)" |
||||
@deleteMessage="$emit('deleteMessage', $event)" |
||||
/> |
||||
</div> |
||||
</div> |
||||
</div> |
||||
</template> |
||||
|
||||
<script setup> |
||||
import BaseIcon from "../basecomponents/BaseIcon.vue" |
||||
import { useFormatDate } from "../../composables/formatDate" |
||||
|
||||
const { relativeDatetime } = useFormatDate() |
||||
const { message, indentation, currentUser, isMainMessage, isModerator } = defineProps({ |
||||
message: Object, |
||||
indentation: { |
||||
type: Number, |
||||
default: 0, |
||||
}, |
||||
currentUser: Object, |
||||
isMainMessage: Boolean, |
||||
isModerator: Boolean |
||||
}) |
||||
const formatSize = (size) => { |
||||
if (size < 1024) return size + ' B' |
||||
let i = Math.floor(Math.log(size) / Math.log(1024)) |
||||
let num = (size / Math.pow(1024, i)).toFixed(2) |
||||
let unit = ['B', 'KB', 'MB', 'GB', 'TB'][i] |
||||
return `${num} ${unit}` |
||||
} |
||||
const isMessageCreator = (message) => { |
||||
return message.senderId === currentUser.id |
||||
} |
||||
</script> |
Loading…
Reference in new issue