- 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.
568 lines
16 KiB
Vue
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>
|