diff --git a/src/components/MenuEnvelope.vue b/src/components/MenuEnvelope.vue index 0f1fe735ce..09dcc65182 100644 --- a/src/components/MenuEnvelope.vue +++ b/src/components/MenuEnvelope.vue @@ -1,5 +1,5 @@ @@ -178,6 +178,20 @@ {{ t('mail', 'Print message') }} + + + {{ copied ? t('mail', 'Link copied') : t('mail', 'Copy direct link for other user. This only works if they have received the same email.') }} + @@ -277,6 +291,7 @@ import CalendarClock from 'vue-material-design-icons/CalendarClockOutline.vue' import CheckIcon from 'vue-material-design-icons/Check.vue' import TaskIcon from 'vue-material-design-icons/CheckboxMarkedCirclePlusOutline.vue' import ChevronLeft from 'vue-material-design-icons/ChevronLeft.vue' +import ContentCopyIcon from 'vue-material-design-icons/ContentCopy.vue' import DotsHorizontalIcon from 'vue-material-design-icons/DotsHorizontal.vue' import FilterIcon from 'vue-material-design-icons/FilterOutline.vue' import InformationIcon from 'vue-material-design-icons/InformationOutline.vue' @@ -321,6 +336,7 @@ export default { AlarmIcon, PrinterIcon, FilterIcon, + ContentCopyIcon, }, props: { @@ -367,6 +383,8 @@ export default { snoozeActionsOpen: false, forwardMessages: this.envelope.databaseId, customSnoozeDateTime: new Date(moment().add(2, 'hours').minute(0).second(0).valueOf()), + copied: false, + copyResetTimer: null, } }, @@ -634,6 +652,35 @@ export default { this.$emit('print') }, + async onCopyMessageLink() { + if (this.copyResetTimer) { + clearTimeout(this.copyResetTimer) + } + + if (!this.envelope || typeof this.envelope.messageId !== 'string' || !this.envelope.messageId.trim()) { + showError(t('mail', 'Could not generate direct link: Message ID is missing')) + return + } + + const trimmedMessageId = this.envelope.messageId.trim().replace(/^<|>$/g, '') + const url = window.location.origin + generateUrl('/apps/mail/open/' + encodeURIComponent(trimmedMessageId)) + + try { + await navigator.clipboard.writeText(url) + this.copied = true + showSuccess(t('mail', 'Direct link copied to clipboard')) + } catch (error) { + // Fallback for cases where clipboard API is not available (e.g. non-HTTPS localhost) + // or permission is denied. This exactly matches Nextcloud's useCopy composable behavior. + window.prompt(t('mail', 'Copy direct link for other user. This only works if they have received the same email.'), url) + } finally { + this.copyResetTimer = setTimeout(() => { + this.copied = false + this.localMoreActionsOpen = false + }, 2000) + } + }, + isSieveEnabled() { return this.account.sieveEnabled },