Consolidates a message's "reply", "reply all", and "forward" button into 2 buttons

and put them in the top-right corner of the Message screen.

The Composer is removed from the Message screen and become a screen on its own that's
shown when the user clicks one of these "reply" buttons in the Message screen.

Signed-off-by: Cyrille Bollu <cyrpub@bollu.be>

===========================================
(here's the detail of this squashed commit)

Fixed a typo in a console.log message

Consolidate reply buttons:

1) Adds 3 action buttons in Message component (they are not yet
   properly placed but at least they work)
2) Removes the Composer component from the Message component; It is
   now only shown within the FolderContent component.
3) Updates FolderContent component to display Composer not only for 'new'
   messages, but also for reply, reply all, and forward messages
4) Updates NewMessageDetail to handle the reply, reply all, and forward cases

Fixes buildFowardSubject to buildForwardSubject

Consolidate message reply buttons: Wraps the 3 ActionButtons in an
Actions component

Consolidate message reply buttons: Removes old forward button

Consolidate message reply buttons: fixes a travis prettier/prettier error

Consolidate message reply buttons: Removes registration of Composer
component from Message.vue as it is not used anymore there.

Consolidate message reply buttons: fixes some travis no-redeclare errors

Consolidate message reply buttons: fixes a travis prettier/prettier error

Adds 'reply-all' and 'reply' icons

using @icon-black-white instead of @icon-color for icon-reply,
icon-reply-all, and icon-forward

* In Message.vue, message-action "Reply all" is always visible and switches to
"Reply" when there's only one recipient in the original message.
* An Actions menu is added to show "Reply to sender only" and "forward" buttons
when needed.

Better formatting of the 'reply' button

Fixes the initializing of a new message's data when replying or
forwarding a message.

Puts the "Send' button next to the attachments buttons in the Composer

Corrects the @include icon-black-white for icons reply, replay-all,
and forward

Better positioning of the message's actions buttons and
removal of the "reply" label to save space

Nicer reply button

Sets component appcontentdetails' width to 100%

(this doesn't leave enough space for the "appt-content-list" div though)

Modifies reply-all.vsg and forward.svg so that every <path></path> tag
is on a single line, as nextcloud server's IconsCacher's colorizeSvg
method doesn't support those tags being on multiple lines.

* Puts email actions button on the far right side (justified)
* Tries to put a label on action "reply" when screen is large enough
  (doesn't work yet though)

Fixes a typo in forward.svg

Do not use "width: 100%" for appcontentdetails but rather "flex-grow: 1"
so as to not shrink app-content-list's width

Tidies up 'forward' and 'reply-all' icons

Sets focus to message body when Composer is opened

Removes unused generateIconUrl Message function

Removes reply button's label on screen smaller than 600px wide.

Removes transparency=0,5 from icon reply.svg

Adds buttons 'mark read/unread', and 'delete' as message actions

Ellipses message title and adresses when space is short.

Message wouldn't be delete when therewas no next/envelope.

Optimise reply.svg

Signed-off-by: John Molakvoæ (skjnldsv) <skjnldsv@protonmail.com>
Signed-off-by: Cyrille Bollu <cyrpub@bollu.be>

Consolidated reply buttons: Replaces the "Send" button by an action
menu in the toright corner of the Composer component.

Signed-off-by: Cyrille Bollu <cyrpub@bollu.be>
Signed-off-by: Christoph Wurst <christoph@winzerhof-wurst.at>
Signed-off-by: Cyrille Bollu <cyrpub@bollu.be>

The height of the Composer 'send button shouldn't grow when attachments are added.

2 eslint fixes
This commit is contained in:
Cyrille Bollu 2019-05-24 11:26:47 +02:00 коммит произвёл Cyrille Bollu
Родитель f650fb6d1e
Коммит 69c01c6466
14 изменённых файлов: 318 добавлений и 153 удалений

Просмотреть файл

@ -290,12 +290,13 @@
height: 90px;
z-index: 100;
position: fixed; // ie fallback
position: -webkit-sticky; // ios/safari fallback
position: sticky;
top: $header-height;
background: -webkit-linear-gradient(var(--color-main-background) , var(--color-main-background) 80%, rgba(255,255,255,0));
background: -o-linear-gradient(var(--color-main-background) , var(--color-main-background) 80%, rgba(255,255,255,0));
background: -moz-linear-gradient(var(--color-main-background) , var(--color-main-background) 80%, rgba(255,255,255,0));
background: linear-gradient(var(--color-main-background) , var(--color-main-background) 80%, rgba(255,255,255,0));
background: -webkit-linear-gradient(var(--color-main-background), var(--color-main-background) 80%, rgba(255,255,255,0));
background: -o-linear-gradient(var(--color-main-background), var(--color-main-background) 80%, rgba(255,255,255,0));
background: -moz-linear-gradient(var(--color-main-background), var(--color-main-background) 80%, rgba(255,255,255,0));
background: linear-gradient(var(--color-main-background), var(--color-main-background) 80%, rgba(255,255,255,0));
}
.transparency {
@ -349,8 +350,9 @@
@include icon-color('trash', 'mail', $color-black);
}
@include icon-black-white('reply', 'mail', 1);
@include icon-black-white('reply-all', 'mail', 1);
@include icon-black-white('forward', 'mail', 1);
// FIXES for core apps.scss

Просмотреть файл

@ -1,57 +0,0 @@
@media only screen and (max-width: 768px) {
/* full width for message list on mobile */
#mail-messages {
width: 100%;
}
/* correctly center loading icon on mobile */
#mail-message-list-loading {
width: 100%;
}
/* overlay message detail on top of message list */
#mail-message {
z-index: 100;
background: #fff;
width: 100%;
left: 0;
height: 100%;
top: 0;
box-shadow: 0 0 100px rgba(100, 100, 100, .9);
}
#mail-message-close {
display: block;
position: fixed;
right: 0;
top: 45px;
width: 44px;
height: 44px;
background-size: 24px;
z-index: 101;
cursor: pointer;
-ms-filter: "progid:DXImageTransform.Microsoft.Alpha(Opacity=40)";
opacity: .4;
}
#mail-message-close:hover,
#mail-message-close:focus {
-ms-filter: "progid:DXImageTransform.Microsoft.Alpha(Opacity=60)";
opacity: .6;
}
.hidden-mobile {
display: none;
}
textarea.message-body {
padding-right: 12px;
}
#mail-content,
.mail-message-attachments {
margin: 0 10px 50px 30px;
}
/* end of media query */
}

58
css/mobile.scss Normal file
Просмотреть файл

@ -0,0 +1,58 @@
@media only screen and (max-width: $breakpoint-mobile) {
/* full width for message list on mobile */
#mail-messages {
width: 100%;
}
/* correctly center loading icon on mobile */
#mail-message-list-loading {
width: 100%;
}
/* overlay message detail on top of message list */
#mail-message {
z-index: 100;
background: #fff;
width: 100%;
left: 0;
height: 100%;
top: 0;
}
#mail-message-close {
display: block;
position: fixed;
right: 0;
top: 45px;
width: 44px;
height: 44px;
background-size: 24px;
z-index: 101;
cursor: pointer;
-ms-filter: 'progid:DXImageTransform.Microsoft.Alpha(Opacity=40)';
opacity: 0.4;
}
#mail-message-close:hover,
#mail-message-close:focus {
-ms-filter: 'progid:DXImageTransform.Microsoft.Alpha(Opacity=60)';
opacity: 0.6;
}
.hidden-mobile {
display: none;
}
textarea.message-body {
padding-right: 12px;
}
#mail-content,
.mail-message-attachments {
margin: 0 10px 50px 30px;
}
/* reply-forward actions align to the far right */
#mail-message-header #mail-message-actions {
margin-right: 5px;
}
}

Двоичные данные
img/forward.png Normal file

Двоичный файл не отображается.

После

Ширина:  |  Высота:  |  Размер: 319 B

1
img/forward.svg Normal file
Просмотреть файл

@ -0,0 +1 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?><svg xmlns="http://www.w3.org/2000/svg" height="16" width="16"><path d="M1 15s.391-7.803 7-10V1l7 7-7 7v-4c-5.116 0-7 4-7 4z"/></svg>

После

Ширина:  |  Высота:  |  Размер: 187 B

Двоичные данные
img/reply-all.png Normal file

Двоичный файл не отображается.

После

Ширина:  |  Высота:  |  Размер: 331 B

1
img/reply-all.svg Normal file
Просмотреть файл

@ -0,0 +1 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?><svg xmlns="http://www.w3.org/2000/svg" height="16" width="16"><path d="M15 13.5s-.308-6.13-5.5-7.857V2.5L4 8l5.5 5.5v-3.143c4.02 0 5.5 3.143 5.5 3.143z"/><path d="M6.768 2.232L1 8l5.768 5.768V12l-4-4 4-4z"/></svg>

После

Ширина:  |  Высота:  |  Размер: 268 B

Просмотреть файл

@ -1,6 +1 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" xmlns="http://www.w3.org/2000/svg" height="16" width="16" version="1.1" xmlns:cc="http://creativecommons.org/ns#" xmlns:dc="http://purl.org/dc/elements/1.1/">
<g opacity=".5" transform="matrix(-1,0,0,1,16,-1036.4)">
<path d="m1 1051.4s0.39147-7.8025 7-10v-4l7 7-7 7v-4c-5.1157 0-7 4-7 4z"/>
</g>
</svg>
<svg xmlns="http://www.w3.org/2000/svg" height="16" width="16"><path d="M15 15s-.4-7.8-7-10V1L1 8l7 7v-4c5.1 0 7 4 7 4z"/></svg>

До

Ширина:  |  Высота:  |  Размер: 423 B

После

Ширина:  |  Высота:  |  Размер: 128 B

36
package-lock.json сгенерированный
Просмотреть файл

@ -3110,7 +3110,7 @@
},
"strip-ansi": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz",
"resolved": "http://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz",
"integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=",
"dev": true,
"requires": {
@ -3468,7 +3468,7 @@
},
"json5": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/json5/-/json5-1.0.1.tgz",
"resolved": "http://registry.npmjs.org/json5/-/json5-1.0.1.tgz",
"integrity": "sha512-aKS4WQjPenRxiQsC93MNfjx+nbF4PAdYzmd/1JIj8HYzqfbu86beTuNgXDzPknWk0n0uARlyewZo4s++ES36Ow==",
"dev": true,
"requires": {
@ -3488,7 +3488,7 @@
},
"minimist": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz",
"resolved": "http://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz",
"integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=",
"dev": true
},
@ -4128,7 +4128,7 @@
},
"strip-ansi": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz",
"resolved": "http://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz",
"integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=",
"dev": true,
"requires": {
@ -5322,7 +5322,7 @@
"dependencies": {
"minimist": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz",
"resolved": "http://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz",
"integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ="
}
}
@ -5523,7 +5523,7 @@
},
"strip-ansi": {
"version": "5.2.0",
"resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz",
"resolved": "http://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz",
"integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==",
"dev": true,
"requires": {
@ -5890,7 +5890,7 @@
},
"json5": {
"version": "0.5.1",
"resolved": "https://registry.npmjs.org/json5/-/json5-0.5.1.tgz",
"resolved": "http://registry.npmjs.org/json5/-/json5-0.5.1.tgz",
"integrity": "sha1-Hq3nrMASA0rYTiOWdn6tn6VJWCE="
},
"jsprim": {
@ -6189,7 +6189,7 @@
"dependencies": {
"minimist": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz",
"resolved": "http://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz",
"integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=",
"dev": true
}
@ -6305,7 +6305,7 @@
},
"minimist": {
"version": "0.0.8",
"resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz",
"resolved": "http://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz",
"integrity": "sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0=",
"dev": true
},
@ -6486,7 +6486,7 @@
},
"yargs": {
"version": "13.2.2",
"resolved": "https://registry.npmjs.org/yargs/-/yargs-13.2.2.tgz",
"resolved": "http://registry.npmjs.org/yargs/-/yargs-13.2.2.tgz",
"integrity": "sha512-WyEoxgyTD3w5XRpAQNYUB9ycVH/PQrToaTXdYXRdOXvEy1l19br+VJsc0vcO8PTGg5ro/l/GY7F/JMEBmI0BxA==",
"dev": true,
"requires": {
@ -8371,7 +8371,7 @@
},
"yargs": {
"version": "7.1.0",
"resolved": "https://registry.npmjs.org/yargs/-/yargs-7.1.0.tgz",
"resolved": "http://registry.npmjs.org/yargs/-/yargs-7.1.0.tgz",
"integrity": "sha1-a6MY6xaWFyf10oT46gA+jWFU0Mg=",
"dev": true,
"requires": {
@ -8927,7 +8927,7 @@
},
"strip-ansi": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz",
"resolved": "http://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz",
"integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=",
"dev": true,
"requires": {
@ -8946,7 +8946,7 @@
},
"strip-ansi": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz",
"resolved": "http://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz",
"integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=",
"requires": {
"ansi-regex": "^2.0.0"
@ -9052,7 +9052,7 @@
},
"strip-ansi": {
"version": "5.2.0",
"resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz",
"resolved": "http://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz",
"integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==",
"dev": true,
"requires": {
@ -9528,7 +9528,7 @@
},
"json5": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/json5/-/json5-1.0.1.tgz",
"resolved": "http://registry.npmjs.org/json5/-/json5-1.0.1.tgz",
"integrity": "sha512-aKS4WQjPenRxiQsC93MNfjx+nbF4PAdYzmd/1JIj8HYzqfbu86beTuNgXDzPknWk0n0uARlyewZo4s++ES36Ow==",
"dev": true,
"requires": {
@ -9548,7 +9548,7 @@
},
"minimist": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz",
"resolved": "http://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz",
"integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=",
"dev": true
},
@ -10960,7 +10960,7 @@
},
"wrap-ansi": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-2.1.0.tgz",
"resolved": "http://registry.npmjs.org/wrap-ansi/-/wrap-ansi-2.1.0.tgz",
"integrity": "sha1-2Pw9KE3QV5T+hJc8rs3Rz4JP3YU=",
"dev": true,
"requires": {
@ -11223,7 +11223,7 @@
},
"yargs": {
"version": "12.0.5",
"resolved": "https://registry.npmjs.org/yargs/-/yargs-12.0.5.tgz",
"resolved": "http://registry.npmjs.org/yargs/-/yargs-12.0.5.tgz",
"integrity": "sha512-Lhz8TLaYnxq/2ObqHDql8dX8CJi97oHxrjUcYtzKbbykPtVW9WB+poxI+NM2UIzsMgNCZTIf0AQwsjK5yMAqZw==",
"dev": true,
"requires": {

Просмотреть файл

@ -111,7 +111,7 @@ export const buildReplySubject = original => {
// TODO: https://en.wikipedia.org/wiki/List_of_email_subject_abbreviations#Abbreviations_in_other_languages
const forwardPrepends = ['fwd']
export const buildFowardSubject = original => {
export const buildForwardSubject = original => {
if (forwardPrepends.some(prepend => original.toLowerCase().startsWith(`${prepend}:`))) {
return original
}

Просмотреть файл

@ -91,6 +91,7 @@
</div>
<div class="composer-fields">
<textarea
ref="body"
v-model="bodyVal"
v-autosize
name="body"
@ -100,12 +101,14 @@
@keypress="onBodyKeyPress"
></textarea>
</div>
<Actions class="submit-message-wrapper app-content-list-item-menu" menu-align="right">
<ActionButton icon="icon-mail" @click="onSend">{{ t('mail', 'Reply') }}</ActionButton>
<ActionButton icon="icon-delete" @click="onSend">{{ t('mail', 'Reply all') }}</ActionButton>
<ActionButton icon="icon-delete" @click="onSend">{{ t('mail', 'Forward') }}</ActionButton>
</Actions>
<ComposerAttachments v-model="attachments" @upload="onAttachmentsUploading" />
<div class="composer-actions">
<div>
<ComposerAttachments v-model="attachments" @upload="onAttachmentsUploading" />
</div>
<div>
<input class="submit-message send primary" type="submit" :value="submitButtonTitle" @click="onSend" />
</div>
</div>
<span v-if="savingDraft === true" id="draft-status">{{ t('mail', 'Saving draft ') }}</span>
<span v-else-if="savingDraft === false" id="draft-status">{{ t('mail', 'Draft saved') }}</span>
</div>
@ -137,7 +140,6 @@ import {findRecipient} from '../service/AutocompleteService'
import Loading from './Loading'
import Logger from '../logger'
import ComposerAttachments from './ComposerAttachments'
import {Actions, ActionButton} from 'nextcloud-vue'
const debouncedSearch = debouncePromise(findRecipient, 500)
@ -154,8 +156,6 @@ const STATES = Object.seal({
export default {
name: 'Composer',
components: {
Actions,
ActionButton,
ComposerAttachments,
Loading,
Multiselect,
@ -243,6 +243,10 @@ export default {
}
this.bodyVal = this.bodyWithSignature(this.selectedAlias, this.body)
},
mounted: function() {
this.$refs.body.focus()
this.$refs.body.setSelectionRange(0, 0)
},
methods: {
recipientToRfc822(recipient) {
if (recipient.email === recipient.label) {
@ -382,6 +386,12 @@ export default {
margin: 0;
}
.composer-actions {
display: flex;
flex-direction: row;
align-items: flex-end;
}
.composer-fields.mail-account > .multiselect {
max-width: none;
min-height: auto;
@ -465,13 +475,6 @@ label.bcc-label {
textarea.reply {
min-height: 100px;
}
.submit-message-wrapper {
position: fixed;
top: 135px;
right: 15px;
z-index: 9999; /* always on top */
}
</style>
<style>

Просмотреть файл

@ -73,7 +73,11 @@ export default {
return this.hasMessages && this.$route.name === 'message'
},
newMessage() {
return this.$route.params.messageUid === 'new'
return (
this.$route.params.messageUid === 'new' ||
this.$route.params.messageUid === 'reply' ||
this.$route.params.messageUid === 'replyAll'
)
},
envelopes() {
if (this.searchQuery === undefined) {

Просмотреть файл

@ -1,5 +1,5 @@
<template>
<AppContentDetails>
<AppContentDetails id="mail-message">
<Loading v-if="loading" />
<Error
v-else-if="!message"
@ -10,38 +10,54 @@
</Error>
<template v-else>
<div id="mail-message-header" class="section">
<h2 :title="message.subject">{{ message.subject }}</h2>
<p class="transparency">
<AddressList :entries="message.from" />
to
<!-- TODO: translate -->
<AddressList :entries="message.to" />
<template v-if="message.cc.length">
(cc
<div id="mail-message-header-fields">
<h2 :title="message.subject">{{ message.subject }}</h2>
<p class="transparency">
<AddressList :entries="message.from" />
to
<!-- TODO: translate -->
<AddressList :entries="message.cc" /><!--
-->)
</template>
</p>
<AddressList :entries="message.to" />
<template v-if="message.cc.length">
(cc
<!-- TODO: translate -->
<AddressList :entries="message.cc" /><!--
-->)
</template>
</p>
</div>
<div id="mail-message-actions">
<div
:class="
hasMultipleRecipients
? 'icon-reply-all-white button primary'
: 'icon-reply-white button primary'
"
@click="hasMultipleRecipients ? replyAll() : replyMessage()"
>
<span class="action-label">{{ t('mail', 'Reply') }}</span>
</div>
<Actions id="mail-message-actions-menu" class="app-content-list-item-menu" menu-align="right">
<ActionButton v-if="hasMultipleRecipients" icon="icon-reply" @click="replyMessage">
{{ t('mail', 'Reply to sender only') }}
</ActionButton>
<ActionButton icon="icon-forward" @click="forwardMessage">
{{ t('mail', 'Forward') }}
</ActionButton>
<ActionButton icon="icon-mail" @click="onToggleSeen">
{{ envelope.flags.unseen ? t('mail', 'Mark read') : t('mail', 'Mark unread') }}
</ActionButton>
<ActionButton icon="icon-delete" @click="onDelete">
{{ t('mail', 'Delete') }}
</ActionButton>
</Actions>
</div>
</div>
<div class="mail-message-body">
<MessageHTMLBody v-if="message.hasHtmlBody" :url="htmlUrl" @loaded="onHtmlBodyLoaded" />
<MessagePlainTextBody v-else :body="message.body" :signature="message.signature" />
<MessageAttachments :attachments="message.attachments" />
<div id="reply-composer"></div>
<input id="forward-button" type="button" :value="t('mail', 'Forward')" @click="forwardMessage" />
</div>
<Composer
v-if="!message.hasHtmlBody || htmlBodyLoaded"
:from-account="message.accountId"
:to="replyRecipient.to"
:cc="replyRecipient.cc"
:subject="replySubject"
:body="replyBody"
:reply-to="replyTo"
:send="sendReply"
:draft="saveReplyDraft"
/>
</template>
</AppContentDetails>
</template>
@ -49,10 +65,10 @@
<script>
import AppContentDetails from 'nextcloud-vue/dist/Components/AppContentDetails'
import {generateUrl} from 'nextcloud-router'
import {Actions, ActionButton} from 'nextcloud-vue'
import AddressList from './AddressList'
import {buildReplyBody, buildRecipients as buildReplyRecipients, buildReplySubject} from '../ReplyBuilder'
import Composer from './Composer'
import Error from './Error'
import {getRandomMessageErrorMessage} from '../util/ErrorMessageFactory'
import {htmlToText} from '../util/HtmlHelper'
@ -66,9 +82,10 @@ import {saveDraft, sendMessage} from '../service/MessageService'
export default {
name: 'Message',
components: {
ActionButton,
Actions,
AddressList,
AppContentDetails,
Composer,
Error,
Loading,
MessageAttachments,
@ -85,6 +102,7 @@ export default {
replyRecipient: {},
replySubject: '',
replyBody: '',
envelope: '',
}
},
computed: {
@ -102,6 +120,9 @@ export default {
messageId: this.message.id,
}
},
hasMultipleRecipients() {
return this.replyRecipient.to.concat(this.replyRecipient.cc).length > 1
},
},
watch: {
$route(to, from) {
@ -148,6 +169,7 @@ export default {
label: account.name,
email: account.emailAddress,
})
this.replySubject = buildReplySubject(message.subject)
if (!message.hasHtmlBody) {
@ -156,13 +178,13 @@ export default {
this.loading = false
const envelope = this.$store.getters.getEnvelope(message.accountId, message.folderId, message.id)
if (!envelope.flags.unseen) {
this.envelope = this.$store.getters.getEnvelope(message.accountId, message.folderId, message.id)
if (!this.envelope.flags.unseen) {
// Already seen -> no change necessary
return
}
return this.$store.dispatch('toggleEnvelopeSeen', envelope)
return this.$store.dispatch('toggleEnvelopeSeen', this.envelope)
})
.catch(error => {
Logger.error('could not load message ', {messageUid, error})
@ -193,6 +215,32 @@ export default {
sendReply(data) {
return sendMessage(data.account, data)
},
replyMessage() {
this.$router.push({
name: 'message',
params: {
accountId: this.$route.params.accountId,
folderId: this.$route.params.folderId,
messageUid: 'reply',
},
query: {
uid: this.message.uid,
},
})
},
replyAll() {
this.$router.push({
name: 'message',
params: {
accountId: this.$route.params.accountId,
folderId: this.$route.params.folderId,
messageUid: 'replyAll',
},
query: {
uid: this.message.uid,
},
})
},
forwardMessage() {
this.$router.push({
name: 'message',
@ -206,22 +254,93 @@ export default {
},
})
},
onToggleSeen() {
this.$store.dispatch('toggleEnvelopeSeen', this.envelope)
},
onDelete(e) {
// Don't try to navigate to the deleted message
e.preventDefault()
let envelopes = this.$store.getters.getEnvelopes(this.$route.params.accountId, this.$route.params.folderId)
const idx = envelopes.indexOf(this.envelope)
let next
if (idx === -1) {
Logger.debug('envelope to delete does not exist in envelope list')
return
} else if (idx === 0) {
next = envelopes[idx + 1]
} else {
next = envelopes[idx - 1]
}
this.$emit('delete', this.envelope)
this.$store.dispatch('deleteMessage', this.envelope)
if (!next) {
Logger.debug('no next/previous envelope, not navigating')
return
}
// Keep the selected account-folder combination, but navigate to a different message
// (it's not a bug that we don't use next.accountId and next.folderId here)
this.$router.push({
name: 'message',
params: {
accountId: this.$route.params.accountId,
folderId: this.$route.params.folderId,
messageUid: next.uid,
},
})
},
},
}
</script>
<style>
<style lang="scss">
#mail-message {
flex-grow: 1;
}
.mail-message-body {
margin-bottom: 100px;
}
#mail-message-header h2,
#mail-message-header p {
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
padding-bottom: 7px;
margin-bottom: 0;
#mail-message-header {
display: flex;
flex-direction: row;
justify-content: space-between;
align-items: center;
padding: 30px 0;
// somehow ios doesn't care about this !important rule
// so we have to manually set left/right padding to chidren
// for 100% to be used
box-sizing: content-box !important;
height: 44px;
width: 100%;
}
#mail-message-header-fields {
// initial width
width: 0;
padding-left: 44px;
// grow and try to fill 100%
flex: 1 1 auto;
h2,
p {
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
padding-bottom: 7px;
margin-bottom: 0;
}
.transparency {
opacity: 0.6;
a {
font-weight: bold;
}
}
}
#mail-content,
@ -249,16 +368,43 @@ export default {
word-wrap: break-word;
}
#mail-message-header .transparency {
opacity: 0.6;
#mail-message-actions {
display: flex;
flex-direction: row;
justify-content: flex-end;
margin-left: 10px;
margin-right: 35px;
height: 44px;
}
#mail-message-header .transparency a {
font-weight: bold;
.icon-reply-white,
.icon-reply-all-white {
height: 44px;
min-width: 44px;
margin: 0;
padding: 11px 10px 10px 25px;
}
/* Show action button label and move icon to the left
on screens larger than 600px */
@media only screen and (max-width: 600px) {
.action-label {
display: none;
}
}
@media only screen and (min-width: 600px) {
.icon-reply-white,
.icon-reply-all-white {
background-position: 5px center;
}
}
#mail-message-actions-menu {
margin-left: 5px;
}
@media print {
#mail-message-header {
#mail-message-header-fields {
position: relative;
}

Просмотреть файл

@ -25,7 +25,7 @@
<script>
import AppContentDetails from 'nextcloud-vue/dist/Components/AppContentDetails'
import {buildFowardSubject, buildReplyBody} from '../ReplyBuilder'
import {buildForwardSubject, buildReplyBody, buildReplySubject} from '../ReplyBuilder'
import Composer from './Composer'
import {getRandomMessageErrorMessage} from '../util/ErrorMessageFactory'
import Error from './Error'
@ -64,12 +64,24 @@ export default {
// Forwarded message
const message = this.$store.getters.getMessageByUid(this.$route.query.uid)
Logger.debug('forwaring message', message)
Logger.debug('forwarding or replying to message', message)
// message headers set for 'reply' actions by default
let subject = buildReplySubject(message.subject)
let msgTo = message.from
let msgCc = []
if (this.$route.params.messageUid === 'replyAll') {
msgCc = message.to.concat(message.cc)
} else if (this.$route.params.messageUid !== 'reply') {
// forward
subject = buildForwardSubject(message.subject)
msgTo = []
}
return {
to: [],
cc: [],
subject: buildFowardSubject(message.subject),
to: msgTo,
cc: msgCc,
subject: subject,
body: buildReplyBody(message.bodyText, message.from[0], message.dateInt),
}
} else {