|
1 | 1 | <!-- |
2 | | - - SPDX-FileCopyrightText: 2021 Nextcloud GmbH and Nextcloud contributors |
| 2 | + - SPDX-FileCopyrightText: 2021-2026 Nextcloud GmbH and Nextcloud contributors |
3 | 3 | - SPDX-License-Identifier: AGPL-3.0-or-later |
4 | 4 | --> |
5 | 5 | <!-- Standard Actions menu for Envelopes --> |
|
29 | 29 | </template> |
30 | 30 | {{ t('mail', 'Forward') }} |
31 | 31 | </ActionButton> |
| 32 | + <ActionButton |
| 33 | + :close-after-click="false" |
| 34 | + :description="t('mail', 'Only for message recipients')" |
| 35 | + @click.prevent="onCopyMessageLink"> |
| 36 | + <template #icon> |
| 37 | + <CheckIcon |
| 38 | + v-if="copied" |
| 39 | + :size="20" /> |
| 40 | + <ContentCopyIcon |
| 41 | + v-else |
| 42 | + :title="t('mail', 'Copy direct link')" |
| 43 | + :size="20" /> |
| 44 | + </template> |
| 45 | + {{ copied ? t('mail', 'Link copied') : t('mail', 'Copy direct link') }} |
| 46 | + </ActionButton> |
32 | 47 | <ActionButton |
33 | 48 | v-if="hasWriteAcl" |
34 | 49 | :close-after-click="true" |
@@ -277,6 +292,7 @@ import CalendarClock from 'vue-material-design-icons/CalendarClockOutline.vue' |
277 | 292 | import CheckIcon from 'vue-material-design-icons/Check.vue' |
278 | 293 | import TaskIcon from 'vue-material-design-icons/CheckboxMarkedCirclePlusOutline.vue' |
279 | 294 | import ChevronLeft from 'vue-material-design-icons/ChevronLeft.vue' |
| 295 | +import ContentCopyIcon from 'vue-material-design-icons/ContentCopy.vue' |
280 | 296 | import DotsHorizontalIcon from 'vue-material-design-icons/DotsHorizontal.vue' |
281 | 297 | import FilterIcon from 'vue-material-design-icons/FilterOutline.vue' |
282 | 298 | import InformationIcon from 'vue-material-design-icons/InformationOutline.vue' |
@@ -321,6 +337,7 @@ export default { |
321 | 337 | AlarmIcon, |
322 | 338 | PrinterIcon, |
323 | 339 | FilterIcon, |
| 340 | + ContentCopyIcon, |
324 | 341 | }, |
325 | 342 |
|
326 | 343 | props: { |
@@ -367,6 +384,8 @@ export default { |
367 | 384 | snoozeActionsOpen: false, |
368 | 385 | forwardMessages: this.envelope.databaseId, |
369 | 386 | customSnoozeDateTime: new Date(moment().add(2, 'hours').minute(0).second(0).valueOf()), |
| 387 | + copied: false, |
| 388 | + copyResetTimer: null, |
370 | 389 | } |
371 | 390 | }, |
372 | 391 |
|
@@ -634,6 +653,35 @@ export default { |
634 | 653 | this.$emit('print') |
635 | 654 | }, |
636 | 655 |
|
| 656 | + async onCopyMessageLink() { |
| 657 | + if (this.copyResetTimer) { |
| 658 | + clearTimeout(this.copyResetTimer) |
| 659 | + } |
| 660 | +
|
| 661 | + if (!this.envelope || typeof this.envelope.messageId !== 'string' || !this.envelope.messageId.trim()) { |
| 662 | + showError(t('mail', 'Could not generate direct link: Message ID is missing')) |
| 663 | + return |
| 664 | + } |
| 665 | +
|
| 666 | + const trimmedMessageId = this.envelope.messageId.trim().replace(/^<|>$/g, '') |
| 667 | + const url = window.location.origin + generateUrl('/apps/mail/open/' + encodeURIComponent(trimmedMessageId)) |
| 668 | +
|
| 669 | + try { |
| 670 | + await navigator.clipboard.writeText(url) |
| 671 | + this.copied = true |
| 672 | + showSuccess(t('mail', 'Direct link copied to clipboard')) |
| 673 | + } catch (error) { |
| 674 | + // Fallback for cases where clipboard API is not available (e.g. non-HTTPS localhost) |
| 675 | + // or permission is denied. This exactly matches Nextcloud's useCopy composable behavior. |
| 676 | + window.prompt(t('mail', 'Copy direct link'), url) |
| 677 | + } finally { |
| 678 | + this.copyResetTimer = setTimeout(() => { |
| 679 | + this.copied = false |
| 680 | + this.localMoreActionsOpen = false |
| 681 | + }, 2000) |
| 682 | + } |
| 683 | + }, |
| 684 | +
|
637 | 685 | isSieveEnabled() { |
638 | 686 | return this.account.sieveEnabled |
639 | 687 | }, |
|
0 commit comments