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