Narindra ezway e6a18fd529 Add MailRepository and enhance calendar functionalities
- Introduced MailRepository for mailbox ID retrieval.
- Updated CalendarObjectCreatedListener to handle cookies.
- Modified VCalendarHelpers to include MIME type extraction.
- Enhanced TalkService with room token retrieval logic.
- Updated ProviderController to integrate external API for sharing.
- Refactored EmailBoxController to utilize MailRepository for mailbox ID.
2025-03-20 17:11:06 +03:00

568 lines
16 KiB
Vue

<template>
<div id="attachments">
<input ref="localAttachments"
class="attachments-input"
type="file"
multiple
@change="onLocalAttachmentSelected">
<div class="attachments-summary">
<div class="attachments-summary-inner">
<Paperclip :size="20" />
<div v-if="attachments.length > 0" class="attachments-summary-inner-label">
{{ n('calendar', '{count} attachment', '{count} attachments', attachments.length, { count: attachments.length }) }}
</div>
<div v-else class="attachments-summary-inner-label">
{{ t('calendar', 'No attachments') }}
</div>
</div>
<NcActions v-if="!isReadOnly">
<template #icon>
<Plus :size="20" />
</template>
<NcActionButton @click="openFilesModal()">
<template #icon>
<Folder :size="20" />
</template>
{{ t('calendar', 'Add from Files') }}
</NcActionButton>
<NcActionButton @click="clickOnUploadButton">
<template #icon>
<Upload :size="20" />
</template>
{{ t('calendar', 'Upload from device') }}
</NcActionButton>
<NcActionButton @click="openEmailBoxModal">
<template #icon>
<Email :size="20" />
</template>
{{ t('calendar', 'Depuis un email') }}
</NcActionButton>
</NcActions>
</div>
<div v-if="attachments.length > 0">
<ul class="attachments-list">
<NcListItem v-for="attachment in attachments"
:key="attachment.path"
class="attachments-list-item"
:force-display-actions="true"
:name="getBaseName(attachment.fileName)"
@click="openFile(attachment.uri)">
<template #icon>
<img :src="getPreview(attachment)" class="attachment-icon">
</template>
<template #actions>
<NcActionButton v-if="!isReadOnly"
@click="deleteAttachmentFromEvent(attachment)">
<template #icon>
<Close :size="20" />
</template>
{{ t('calendar', 'Delete file') }}
</NcActionButton>
</template>
</NcListItem>
</ul>
</div>
<NcDialog :open.sync="showOpenConfirmation"
:name="t('calendar', 'Confirmation')"
:buttons="openConfirmationButtons">
<p class="external-link-message">
{{ openConfirmationMessage }}
</p>
</NcDialog>
<NcModal v-if="showModal" name="" @close="resetMailBoxModal">
<div class="modal__content" v-if="!showAttachmentsSection && !showAttachmentsSection">
<NcButton style="text-align: center;" @click="syncEmailBox()" v-if="!loadingDataEmailBox">
<template #icon>
<Reload :size="20" />
</template>
Actualiser
</NcButton>
<template v-if="mailBoxData.length > 0" v-for="mail in mailBoxData">
<ul>
<NcListItem
@click="getAttachments(mail)"
:name="getLabelEmailBox(mail.from) + ' : ' + getSubstringText(mail.subject, 30) "
:bold="false"
counterType="outlined">
<template #subname>
{{ getSubstringText(mail.previewText ,50) }}
</template>
<template v-if="hasAttachements(mail)" #icon>
<Paperclip :size="20" />
</template>
</NcListItem>
</ul>
</template>
</div>
<div class="modal__content__attachments" v-if="showAttachmentsSection">
<ul class="attachments-list">
<NcActionButton @click="displayAttachmentsSection()">
<template #icon>
<ArrowLeft :size="20" />
</template>
Revenir dans la listes des emails
</NcActionButton>
<div class="image-list-attachement " v-if="!loadingDataEmailBox && mailAttachments.length > 0" v-for="attachment in mailAttachments">
<div v-if="attachment.isImage" class="image-item-attachement " @click="pickEmailAttachement(attachment)">
<img :src="attachment.downloadUrl" :alt="attachment.fileName ">
<label>{{ attachment.fileName }}</label>
</div>
<div v-else-if="! attachment.isImage && !attachment.isCalendarEvent" class="image-item-attachement " @click="pickEmailAttachement(attachment)">
<label>{{ 'Pièce joint : ' + attachment.fileName }}</label>
</div>
<div v-else-if="attachment.isCalendarEvent" class="image-item-attachement " >
<label>{{ 'Pièce joint : ' + attachment.fileName }} ( nNon allouée)</label>
</div>
</div>
<template v-if="!mailAttachments.length && !loadingDataEmailBox" >
<p> Pas d'Attachements</p>
</template>
</ul>
</div>
<div class="modal__content" v-if="loadingDataEmailBox" >
<div >
<NcLoadingIcon :size="40" appearance="light" :name="loadingText" />
</div>
<p style="text-align: center;"> {{ loadingText }}</p>
</div>
</NcModal>
</div>
</template>
<script>
import {
NcListItem,
NcActions,
NcActionButton,
NcDialog,
NcModal,
NcButton,
NcAvatar,
NcLoadingIcon,
} from '@nextcloud/vue'
import axios from "@nextcloud/axios";
import Upload from 'vue-material-design-icons/Upload.vue'
import Close from 'vue-material-design-icons/Close.vue'
import Folder from 'vue-material-design-icons/Folder.vue'
import Paperclip from 'vue-material-design-icons/Paperclip.vue'
import Plus from 'vue-material-design-icons/Plus.vue'
import Email from 'vue-material-design-icons/Email.vue'
import ArrowLeft from 'vue-material-design-icons/ArrowLeft.vue'
import Reload from 'vue-material-design-icons/Reload.vue'
import { generateUrl, getBaseUrl } from '@nextcloud/router'
import { getFilePickerBuilder, showError } from '@nextcloud/dialogs'
import logger from '../../../utils/logger.js'
import {
uploadLocalAttachment,
getFileInfo,
uploadRemoteFile
} from '../../../services/attachmentService.js'
import { parseXML } from 'webdav'
export default {
name: 'AttachmentsList',
components: {
NcListItem,
NcActions,
NcActionButton,
Upload,
Close,
Folder,
Paperclip,
Plus,
NcDialog,
NcModal,
NcButton,
Email,
ArrowLeft,
Reload,
NcAvatar,
NcLoadingIcon
},
props: {
calendarObjectInstance: {
type: Object,
required: true,
},
isReadOnly: {
type: Boolean,
default: true,
},
},
data() {
return {
uploading: false,
showOpenConfirmation: false,
openConfirmationMessage: '',
openConfirmationButtons: [],
showModal: false,
showAttachmentsSection: false,
loadingDataEmailBox: false,
mailBoxData: [],
mailAttachments: [],
loadingText: 'Chargement ...',
}
},
computed: {
currentUser() {
return this.$store.getters.getCurrentUserPrincipal
},
attachments() {
return this.calendarObjectInstance.attachments
},
},
methods: {
addAttachmentWithProperty(calendarObjectInstance, sharedData) {
this.$store.commit('addAttachmentWithProperty', {
calendarObjectInstance,
sharedData,
})
},
deleteAttachmentFromEvent(attachment) {
this.$store.commit('deleteAttachment', {
calendarObjectInstance: this.calendarObjectInstance,
attachment,
})
},
async openFilesModal() {
const picker = getFilePickerBuilder(t('calendar', 'Choose a file to add as attachment'))
.setMultiSelect(false)
.allowDirectories(true)
.addButton({
label: t('calendar', 'Pick'),
type: 'primary',
callback: (nodes) => logger.debug('Picked attachment', { nodes }),
})
.build()
try {
const filename = await picker.pick(t('calendar', 'Choose a file to share as a link'))
if (!this.isDuplicateAttachment(filename)) {
// TODO do not share Move this to PHP
const data = await getFileInfo(filename, this.currentUser.dav.userId)
const davRes = await parseXML(data)
const davRespObj = davRes?.multistatus?.response[0]?.propstat?.prop
davRespObj.fileName = filename
davRespObj.url = generateUrl(`/f/${davRespObj.fileid}`)
davRespObj.value = davRespObj.url
this.addAttachmentWithProperty(this.calendarObjectInstance, davRespObj)
}
} catch (error) {
}
},
isDuplicateAttachment(path) {
return this.attachments.find(attachment => {
if (attachment.fileName === path) {
showError(t('calendar', 'Attachment {name} already exist!', { name: this.getBaseName(path) }))
return true
}
return false
})
},
clickOnUploadButton() {
this.$refs.localAttachments.click()
},
async onLocalAttachmentSelected(e) {
try {
const attachmentsFolder = await this.$store.dispatch('createAttachmentsFolder')
const attachments = await uploadLocalAttachment(attachmentsFolder, Array.from(e.target.files), this.currentUser.dav)
// TODO do not share file, move to PHP
attachments.map(async attachment => {
const data = await getFileInfo(`${attachmentsFolder}/${attachment.path}`, this.currentUser.dav.userId)
const davRes = await parseXML(data)
const davRespObj = davRes?.multistatus?.response[0]?.propstat?.prop
davRespObj.fileName = attachment.path
davRespObj.url = generateUrl(`/f/${davRespObj.fileid}`)
davRespObj.value = davRespObj.url
this.addAttachmentWithProperty(this.calendarObjectInstance, davRespObj)
})
e.target.value = ''
} catch (error) {
logger.error('Could not upload attachment(s)', { error })
showError(t('calendar', 'Could not upload attachment(s)'))
}
},
getIcon(mime) {
return OC.MimeType.getIconUrl(mime)
},
getPreview(attachment) {
if (attachment.xNcHasPreview) {
return generateUrl(`/core/preview?fileId=${attachment.xNcFileId}&x=100&y=100&a=0`)
}
return attachment.formatType
? OC.MimeType.getIconUrl(attachment.formatType)
: OC.MimeType.getIconUrl('folder')
},
getBaseName(name) {
return name.split('/').pop()
},
openFile(rawUrl) {
let url
try {
url = new URL(rawUrl, getBaseUrl())
} catch (error) {
logger.error(`Refusing to open invalid URL: ${rawUrl}`, { error })
return
}
const baseUrl = new URL(getBaseUrl())
if (url.href.startsWith(baseUrl.href)) {
// URL belongs to this instance and is safe
window.open(url.href, '_blank', 'noopener noreferrer')
return
}
// Otherwise, show a confirmation dialog
this.openConfirmationMessage = t('calendar', 'You are about to navigate to {host}. Are you sure to proceed? Link: {link}', {
host: url.host,
link: url.href,
})
this.openConfirmationButtons = [
{
label: t('calendar', 'Cancel'),
callback: () => {
this.showOpenConfirmation = false
},
},
{
label: t('calendar', 'Proceed'),
type: 'primary',
callback: () => {
window.open(url.href, '_blank', 'noopener noreferrer')
}
},
]
this.showOpenConfirmation = true
},
async syncEmailBox(){
this.loadingText = 'Synchronisation encours ...'
this.loadingDataEmailBox = true
this.mailBoxData = [];
await this.loadEmailsBox(true)
this.loadingDataEmailBox = false
},
async openEmailBoxModal(){
this.loadingText = 'Chargement des emails ...'
this.showModal = true
this.loadingDataEmailBox = true
await this.loadEmailsBox()
this.loadingDataEmailBox = false
},
async getAttachments(mail){
this.loadingText = 'Récupération des piecs jointes ...'
this.loadingDataEmailBox = true
this.showAttachmentsSection = true
this.mailAttachments = [] ;
await this.loadAttachments(mail)
this.loadingDataEmailBox = false
},
async pickEmailAttachement(attachement){
this.loadingText = 'Ajout de l\'attachement encours ...'
this.loadingDataEmailBox = true
await this.pickEmailAttachementTodavFile(attachement)
this.closeMailBoxModal()
this.resetMailBoxModal()
},
resetMailBoxModal() {
this.mailBoxData = []
this.mailAttachments = []
this.showModal = false
this.showAttachmentsSection = false
this.loadingDataEmailBox = false
this.loadingText = 'Chargement ...'
},
displayAttachmentsSection() {
this.showAttachmentsSection = false
this.loadingDataEmailBox = false
},
closeMailBoxModal() {
this.showModal = false
this.showAttachmentsSection = false
this.loadingDataEmailBox = false
this.loadingText = 'Chargement ...'
},
async loadEmailsBox(withSync = false) {
const ajaxUrl = generateUrl('/apps/calendar/load-email-box')
await axios.get(ajaxUrl , { params: { withSync } })
.then(response => {
console.log(response.data.success);
if (!response.data.success && response.data.message == "MAILBOX_NOT_FOUND") {
this.closeMailBoxModal();
showError(t('calendar', 'Veuillez configurer le compte apps email pour pouvoir utiliser cette fonctionnalité.' , {
timeout : 7000,
}))
return;
}
this.mailBoxData = response.data;
})
.catch(error => {
console.log(error)
})
},
async loadAttachments(mail) {
if (!this.hasAttachements(mail)) {
this.mailAttachments = [] ;
this.loadingDataEmailBox = false
return;
}
const ajaxUrl = generateUrl('/apps/calendar/load-email-attachement/' + mail.databaseId)
await axios.get(ajaxUrl)
.then(response => {
console.log(response.data)
this.mailAttachments = response.data.attachments
})
.catch(error => {
console.log(error)
})
},
async pickEmailAttachementTodavFile(attachement) {
const attachmentsFolder = await this.$store.dispatch('createAttachmentsFolder')
const attachment = await uploadRemoteFile(attachmentsFolder, attachement.downloadUrl, attachement.fileName ,this.currentUser.dav)
// TODO do not share file, move to PHP
const data = await getFileInfo(`${attachmentsFolder}/${attachment.path}`, this.currentUser.dav.userId)
const davRes = await parseXML(data)
const davRespObj = davRes?.multistatus?.response[0]?.propstat?.prop
davRespObj.fileName = attachment.path
davRespObj.url = generateUrl(`/f/${davRespObj.fileid}`)
davRespObj.value = davRespObj.url
this.addAttachmentWithProperty(this.calendarObjectInstance, davRespObj)
},
getLabelEmailBox(from = []) {
if (from.length) {
return from[0].label ?? from[0].email
}
return ''
},
getSubstringText(text = '' , length = 20) {
if (text.length < length) {
return text;
}
return text.substring(0, length) + '...'
},
hasAttachements(mail) {
return mail.flags.hasAttachments;
}
},
}
</script>
<style lang="scss" scoped>
.attachments-input {
display: none;
}
.attachments-summary {
display:flex;
align-items: center;
justify-content: space-between;
padding-left: 6px;
.attachments-summary-inner {
display:flex;
align-items: center;
span {
width: 34px;
height: 34px;
margin-left: -10px;
margin-right: 5px;
}
.attachments-summary-inner-label {
padding: 0 7px;
font-weight: bold;
}
}
}
.attachments-list {
margin: 0 -8px;
.attachments-list-item {
// Reduce height to 44px
:deep(.list-item) {
padding: 0 8px;
}
:deep(.list-item-content__wrapper) {
height: 44px;
}
:deep(.list-item-content) {
// Align text with other properties
padding-left: 18px;
}
:deep(.line-one__title) {
font-weight: unset;
}
}
}
#attachments .empty-content {
margin-top: 1rem;
text-align: center;
}
.button-group {
display: flex;
align-content: center;
justify-content: center;
button:first-child {
margin-right: 6px;
}
}
.attachment-icon {
width: 24px;
height: 24px;
border-radius: var(--border-radius);
}
.external-link-message {
overflow-wrap: break-word;
}
.modal__content {
margin: 10px;
}
.modal__content__attachments {
margin: 10px;
text-align: center;
}
.attachments-list{
margin-top: 20px;
}
.image-list-attachement {
display: flex;
flex-direction: column;
align-items: flex-start;
padding: 10px;
}
.image-item-attachement {
display: flex;
align-items: center;
margin: 10px 0;
}
.image-item-attachement img {
width: 100px; /* Largeur de l'image */
height: auto; /* Hauteur automatique pour garder le ratio */
margin-right: 10px; /* Espace entre l'image et la légende */
}
label {
font-size: 16px; /* Taille de police pour la légende */
}
</style>