Skip to content

Commit 1cb78a3

Browse files
committed
feat: Add 'copy direct link' action to message menu
Implements a UI button in the MenuEnvelope to generate and copy a direct link to the message using the new deep link route. Utilizes the standard Nextcloud clipboard fallback via window.prompt for non-secure contexts. Signed-off-by: SoleroTG <github-29h@solero.quietmail.eu>
1 parent b133d88 commit 1cb78a3

1 file changed

Lines changed: 49 additions & 1 deletion

File tree

src/components/MenuEnvelope.vue

Lines changed: 49 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
<!--
2-
- SPDX-FileCopyrightText: 2021 Nextcloud GmbH and Nextcloud contributors
2+
- SPDX-FileCopyrightText: 2021-2026 Nextcloud GmbH and Nextcloud contributors
33
- SPDX-License-Identifier: AGPL-3.0-or-later
44
-->
55
<!-- Standard Actions menu for Envelopes -->
@@ -29,6 +29,21 @@
2929
</template>
3030
{{ t('mail', 'Forward') }}
3131
</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>
3247
<ActionButton
3348
v-if="hasWriteAcl"
3449
:close-after-click="true"
@@ -277,6 +292,7 @@ import CalendarClock from 'vue-material-design-icons/CalendarClockOutline.vue'
277292
import CheckIcon from 'vue-material-design-icons/Check.vue'
278293
import TaskIcon from 'vue-material-design-icons/CheckboxMarkedCirclePlusOutline.vue'
279294
import ChevronLeft from 'vue-material-design-icons/ChevronLeft.vue'
295+
import ContentCopyIcon from 'vue-material-design-icons/ContentCopy.vue'
280296
import DotsHorizontalIcon from 'vue-material-design-icons/DotsHorizontal.vue'
281297
import FilterIcon from 'vue-material-design-icons/FilterOutline.vue'
282298
import InformationIcon from 'vue-material-design-icons/InformationOutline.vue'
@@ -321,6 +337,7 @@ export default {
321337
AlarmIcon,
322338
PrinterIcon,
323339
FilterIcon,
340+
ContentCopyIcon,
324341
},
325342
326343
props: {
@@ -367,6 +384,8 @@ export default {
367384
snoozeActionsOpen: false,
368385
forwardMessages: this.envelope.databaseId,
369386
customSnoozeDateTime: new Date(moment().add(2, 'hours').minute(0).second(0).valueOf()),
387+
copied: false,
388+
copyResetTimer: null,
370389
}
371390
},
372391
@@ -634,6 +653,35 @@ export default {
634653
this.$emit('print')
635654
},
636655
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+
637685
isSieveEnabled() {
638686
return this.account.sieveEnabled
639687
},

0 commit comments

Comments
 (0)