Welcome to mirror list, hosted at ThFree Co, Russian Federation.

github.com/nextcloud/mail.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorChristoph Wurst <ChristophWurst@users.noreply.github.com>2019-03-06 20:36:04 +0300
committerGitHub <noreply@github.com>2019-03-06 20:36:04 +0300
commit5ab96c39d64f803bb1a977b90a805a1895ba9618 (patch)
tree87324f3fa96241c98595fb04b375d88ef1a63f50 /src
parentcf3e7e374a17a84a98c116e780dde234096f8a7f (diff)
parentf1bafd430b0f0e2c7a52afc00bf6bd7ea732c478 (diff)
Merge pull request #1587 from nextcloud/lint/components
Apply lint:autofix on all components
Diffstat (limited to 'src')
-rw-r--r--src/components/AccountForm.vue471
-rw-r--r--src/components/Address.vue52
-rw-r--r--src/components/AddressList.vue28
-rw-r--r--src/components/AppSettingsMenu.vue78
-rw-r--r--src/components/Avatar.vue73
-rw-r--r--src/components/Composer.vue791
-rw-r--r--src/components/ComposerAttachments.vue201
-rw-r--r--src/components/EmptyFolder.vue6
-rw-r--r--src/components/Envelope.vue128
-rw-r--r--src/components/EnvelopeList.vue428
-rw-r--r--src/components/Error.vue48
-rw-r--r--src/components/FolderContent.vue228
-rw-r--r--src/components/Loading.vue19
-rw-r--r--src/components/Message.vue419
-rw-r--r--src/components/MessageAttachment.vue412
-rw-r--r--src/components/MessageAttachments.vue146
-rw-r--r--src/components/MessageHTMLBody.vue113
-rw-r--r--src/components/MessagePlainTextBody.vue36
-rw-r--r--src/components/Moment.vue50
-rw-r--r--src/components/Navigation.vue256
-rw-r--r--src/components/NewMessageDetail.vue353
21 files changed, 2146 insertions, 2190 deletions
diff --git a/src/components/AccountForm.vue b/src/components/AccountForm.vue
index f556c6b4d..745a35772 100644
--- a/src/components/AccountForm.vue
+++ b/src/components/AccountForm.vue
@@ -1,242 +1,246 @@
<template>
- <div id="account-form">
- <tabs
- :options="{ useUrlFragment: false, defaultTabHash: settingsPage ? 'manual' : 'auto' }"
- @changed="onModeChanged"
- cache-lifetime="0"
- >
- <tab :name="t('mail', 'Auto')" id="auto" key="auto">
- <label for="auto-name">{{ t('mail', 'Name') }}</label>
- <input
- type="text"
- id="auto-name"
- :placeholder="t('mail', 'Name')"
- v-model="autoConfig.accountName"
- :disabled="loading"
- autofocus
- >
- <label for="auto-address">{{ t('mail', 'Mail Address') }}</label>
- <input
- type="email"
- id="auto-address"
- :placeholder="t('mail', 'Mail Address')"
- v-model="autoConfig.emailAddress"
- :disabled="loading"
- required
- >
- <label for="auto-password">{{ t('mail', 'Password') }}</label>
- <input
- type="password"
- id="auto-password"
- :placeholder="t('mail', 'Password')"
- v-model="autoConfig.password"
- :disabled="loading"
- required
- >
- </tab>
- <tab :name="t('mail', 'Manual')" id="manual" key="manual">
- <label for="man-name">{{ t('mail', 'Name') }}</label>
- <input
- type="text"
- id="man-name"
- :placeholder="t('mail', 'Name')"
- v-model="manualConfig.accountName"
- :disabled="loading"
- autofocus
- >
- <label for="man-address">{{ t('mail', 'Mail Address') }}</label>
- <input
- type="email"
- id="man-address"
- :placeholder="t('mail', 'Mail Address')"
- v-model="manualConfig.emailAddress"
- :disabled="loading"
- required
- >
+ <div id="account-form">
+ <tabs
+ :options="{useUrlFragment: false, defaultTabHash: settingsPage ? 'manual' : 'auto'}"
+ cache-lifetime="0"
+ @changed="onModeChanged"
+ >
+ <tab id="auto" key="auto" :name="t('mail', 'Auto')">
+ <label for="auto-name">{{ t('mail', 'Name') }}</label>
+ <input
+ id="auto-name"
+ v-model="autoConfig.accountName"
+ type="text"
+ :placeholder="t('mail', 'Name')"
+ :disabled="loading"
+ autofocus
+ />
+ <label for="auto-address">{{ t('mail', 'Mail Address') }}</label>
+ <input
+ id="auto-address"
+ v-model="autoConfig.emailAddress"
+ type="email"
+ :placeholder="t('mail', 'Mail Address')"
+ :disabled="loading"
+ required
+ />
+ <label for="auto-password">{{ t('mail', 'Password') }}</label>
+ <input
+ id="auto-password"
+ v-model="autoConfig.password"
+ type="password"
+ :placeholder="t('mail', 'Password')"
+ :disabled="loading"
+ required
+ />
+ </tab>
+ <tab id="manual" key="manual" :name="t('mail', 'Manual')">
+ <label for="man-name">{{ t('mail', 'Name') }}</label>
+ <input
+ id="man-name"
+ v-model="manualConfig.accountName"
+ type="text"
+ :placeholder="t('mail', 'Name')"
+ :disabled="loading"
+ autofocus
+ />
+ <label for="man-address">{{ t('mail', 'Mail Address') }}</label>
+ <input
+ id="man-address"
+ v-model="manualConfig.emailAddress"
+ type="email"
+ :placeholder="t('mail', 'Mail Address')"
+ :disabled="loading"
+ required
+ />
- <h3>{{ t('mail', 'IMAP Settings') }}</h3>
- <label for="man-imap-host">{{ t('mail', 'IMAP Host') }}</label>
- <input
- type="text"
- id="man-imap-host"
- :placeholder="t('mail', 'IMAP Host')"
- v-model="manualConfig.imapHost"
- :disabled="loading"
- required
- >
- <h4>{{ t('mail', 'IMAP Security') }}</h4>
- <div class="flex-row">
- <input
- type="radio"
- id="man-imap-sec-none"
- name="man-imap-sec"
- v-model="manualConfig.imapSslMode"
- :disabled="loading"
- @change="onImapSslModeChange"
- value="none"
- >
- <label
- class="button"
- for="man-imap-sec-none"
- :class="{primary: manualConfig.imapSslMode === 'none' }"
- >{{ t('mail', 'None') }}</label>
- <input
- type="radio"
- id="man-imap-sec-ssl"
- name="man-imap-sec"
- v-model="manualConfig.imapSslMode"
- :disabled="loading"
- @change="onImapSslModeChange"
- value="ssl"
- >
- <label
- class="button"
- for="man-imap-sec-ssl"
- :class="{primary: manualConfig.imapSslMode === 'ssl' }"
- >{{ t('mail', 'SSL/TLS') }}</label>
- <input
- type="radio"
- id="man-imap-sec-tls"
- name="man-imap-sec"
- v-model="manualConfig.imapSslMode"
- :disabled="loading"
- @change="onImapSslModeChange"
- value="tls"
- >
- <label
- class="button"
- for="man-imap-sec-tls"
- :class="{primary: manualConfig.imapSslMode === 'tls' }"
- >{{ t('mail', 'STARTTLS') }}</label>
- </div>
- <label for="man-imap-port">{{ t('mail', 'IMAP Port') }}</label>
- <input
- type="number"
- id="man-imap-port"
- :placeholder="t('mail', 'IMAP Port')"
- v-model="manualConfig.imapPort"
- :disabled="loading"
- required
- >
- <label for="man-imap-user">{{ t('mail', 'IMAP User') }}</label>
- <input
- type="text"
- id="man-imap-user"
- :placeholder="t('mail', 'IMAP User')"
- v-model="manualConfig.imapUser"
- :disabled="loading"
- required
- >
- <label for="man-imap-password">{{ t('mail', 'IMAP Password') }}</label>
- <input
- type="password"
- id="man-imap-password"
- :placeholder="t('mail', 'IMAP Password')"
- v-model="manualConfig.imapPassword"
- :disabled="loading"
- required
- >
+ <h3>{{ t('mail', 'IMAP Settings') }}</h3>
+ <label for="man-imap-host">{{ t('mail', 'IMAP Host') }}</label>
+ <input
+ id="man-imap-host"
+ v-model="manualConfig.imapHost"
+ type="text"
+ :placeholder="t('mail', 'IMAP Host')"
+ :disabled="loading"
+ required
+ />
+ <h4>{{ t('mail', 'IMAP Security') }}</h4>
+ <div class="flex-row">
+ <input
+ id="man-imap-sec-none"
+ v-model="manualConfig.imapSslMode"
+ type="radio"
+ name="man-imap-sec"
+ :disabled="loading"
+ value="none"
+ @change="onImapSslModeChange"
+ />
+ <label
+ class="button"
+ for="man-imap-sec-none"
+ :class="{primary: manualConfig.imapSslMode === 'none'}"
+ >{{ t('mail', 'None') }}</label
+ >
+ <input
+ id="man-imap-sec-ssl"
+ v-model="manualConfig.imapSslMode"
+ type="radio"
+ name="man-imap-sec"
+ :disabled="loading"
+ value="ssl"
+ @change="onImapSslModeChange"
+ />
+ <label
+ class="button"
+ for="man-imap-sec-ssl"
+ :class="{primary: manualConfig.imapSslMode === 'ssl'}"
+ >{{ t('mail', 'SSL/TLS') }}</label
+ >
+ <input
+ id="man-imap-sec-tls"
+ v-model="manualConfig.imapSslMode"
+ type="radio"
+ name="man-imap-sec"
+ :disabled="loading"
+ value="tls"
+ @change="onImapSslModeChange"
+ />
+ <label
+ class="button"
+ for="man-imap-sec-tls"
+ :class="{primary: manualConfig.imapSslMode === 'tls'}"
+ >{{ t('mail', 'STARTTLS') }}</label
+ >
+ </div>
+ <label for="man-imap-port">{{ t('mail', 'IMAP Port') }}</label>
+ <input
+ id="man-imap-port"
+ v-model="manualConfig.imapPort"
+ type="number"
+ :placeholder="t('mail', 'IMAP Port')"
+ :disabled="loading"
+ required
+ />
+ <label for="man-imap-user">{{ t('mail', 'IMAP User') }}</label>
+ <input
+ id="man-imap-user"
+ v-model="manualConfig.imapUser"
+ type="text"
+ :placeholder="t('mail', 'IMAP User')"
+ :disabled="loading"
+ required
+ />
+ <label for="man-imap-password">{{ t('mail', 'IMAP Password') }}</label>
+ <input
+ id="man-imap-password"
+ v-model="manualConfig.imapPassword"
+ type="password"
+ :placeholder="t('mail', 'IMAP Password')"
+ :disabled="loading"
+ required
+ />
- <h3>{{ t('mail', 'SMTP Settings') }}</h3>
- <input
- type="text"
- ref="smtpHost"
- name="smtp-host"
- :placeholder="t('mail', 'SMTP Host')"
- v-model="manualConfig.smtpHost"
- :disabled="loading"
- required
- >
- <h4>{{ t('mail', 'SMTP Security') }}</h4>
- <div class="flex-row">
- <input
- type="radio"
- id="man-smtp-sec-none"
- name="man-smtp-sec"
- v-model="manualConfig.smtpSslMode"
- :disabled="loading"
- @change="onSmtpSslModeChange"
- value="none"
- >
- <label
- class="button"
- for="man-smtp-sec-none"
- :class="{primary: manualConfig.smtpSslMode === 'none' }"
- >{{ t('mail', 'None') }}</label>
- <input
- type="radio"
- id="man-smtp-sec-ssl"
- name="man-smtp-sec"
- v-model="manualConfig.smtpSslMode"
- :disabled="loading"
- @change="onSmtpSslModeChange"
- value="ssl"
- >
- <label
- class="button"
- for="man-smtp-sec-ssl"
- :class="{primary: manualConfig.smtpSslMode === 'ssl' }"
- >{{ t('mail', 'SSL/TLS') }}</label>
- <input
- type="radio"
- id="man-smtp-sec-tls"
- name="man-smtp-sec"
- v-model="manualConfig.smtpSslMode"
- :disabled="loading"
- @change="onSmtpSslModeChange"
- value="tls"
- >
- <label
- class="button"
- for="man-smtp-sec-tls"
- :class="{primary: manualConfig.smtpSslMode === 'tls' }"
- >{{ t('mail', 'STARTTLS') }}</label>
- </div>
- <label for="man-smtp-port">{{ t('mail', 'SMTP Port') }}</label>
- <input
- type="number"
- id="man-smtp-port"
- :placeholder="t('mail', 'SMTP Port')"
- v-model="manualConfig.smtpPort"
- :disabled="loading"
- required
- >
- <label for="man-smtp-user">{{ t('mail', 'SMTP User') }}</label>
- <input
- type="text"
- id="man-smtp-user"
- :placeholder="t('mail', 'SMTP User')"
- v-model="manualConfig.smtpUser"
- :disabled="loading"
- required
- >
- <label for="man-smtp-password">{{ t('mail', 'SMTP Password') }}</label>
- <input
- type="password"
- id="man-smtp-password"
- :placeholder="t('mail', 'SMTP Password')"
- v-model="manualConfig.smtpPassword"
- :disabled="loading"
- required
- >
- </tab>
- </tabs>
- <input
- type="submit"
- class="primary"
- v-on:click="onSubmit"
- :disabled="loading"
- :value="submitButtonText"
- >
- </div>
+ <h3>{{ t('mail', 'SMTP Settings') }}</h3>
+ <input
+ ref="smtpHost"
+ v-model="manualConfig.smtpHost"
+ type="text"
+ name="smtp-host"
+ :placeholder="t('mail', 'SMTP Host')"
+ :disabled="loading"
+ required
+ />
+ <h4>{{ t('mail', 'SMTP Security') }}</h4>
+ <div class="flex-row">
+ <input
+ id="man-smtp-sec-none"
+ v-model="manualConfig.smtpSslMode"
+ type="radio"
+ name="man-smtp-sec"
+ :disabled="loading"
+ value="none"
+ @change="onSmtpSslModeChange"
+ />
+ <label
+ class="button"
+ for="man-smtp-sec-none"
+ :class="{primary: manualConfig.smtpSslMode === 'none'}"
+ >{{ t('mail', 'None') }}</label
+ >
+ <input
+ id="man-smtp-sec-ssl"
+ v-model="manualConfig.smtpSslMode"
+ type="radio"
+ name="man-smtp-sec"
+ :disabled="loading"
+ value="ssl"
+ @change="onSmtpSslModeChange"
+ />
+ <label
+ class="button"
+ for="man-smtp-sec-ssl"
+ :class="{primary: manualConfig.smtpSslMode === 'ssl'}"
+ >{{ t('mail', 'SSL/TLS') }}</label
+ >
+ <input
+ id="man-smtp-sec-tls"
+ v-model="manualConfig.smtpSslMode"
+ type="radio"
+ name="man-smtp-sec"
+ :disabled="loading"
+ value="tls"
+ @change="onSmtpSslModeChange"
+ />
+ <label
+ class="button"
+ for="man-smtp-sec-tls"
+ :class="{primary: manualConfig.smtpSslMode === 'tls'}"
+ >{{ t('mail', 'STARTTLS') }}</label
+ >
+ </div>
+ <label for="man-smtp-port">{{ t('mail', 'SMTP Port') }}</label>
+ <input
+ id="man-smtp-port"
+ v-model="manualConfig.smtpPort"
+ type="number"
+ :placeholder="t('mail', 'SMTP Port')"
+ :disabled="loading"
+ required
+ />
+ <label for="man-smtp-user">{{ t('mail', 'SMTP User') }}</label>
+ <input
+ id="man-smtp-user"
+ v-model="manualConfig.smtpUser"
+ type="text"
+ :placeholder="t('mail', 'SMTP User')"
+ :disabled="loading"
+ required
+ />
+ <label for="man-smtp-password">{{ t('mail', 'SMTP Password') }}</label>
+ <input
+ id="man-smtp-password"
+ v-model="manualConfig.smtpPassword"
+ type="password"
+ :placeholder="t('mail', 'SMTP Password')"
+ :disabled="loading"
+ required
+ />
+ </tab>
+ </tabs>
+ <input type="submit" class="primary" :disabled="loading" :value="submitButtonText" @click="onSubmit" />
+ </div>
</template>
<script>
import _ from 'underscore'
-import { Tab, Tabs } from 'vue-tabs-component'
+import {Tab, Tabs} from 'vue-tabs-component'
export default {
name: 'AccountForm',
+ components: {
+ Tab,
+ Tabs,
+ },
props: {
displayName: {
type: String,
@@ -253,12 +257,9 @@ export default {
account: {
type: Object,
required: false,
+ default: () => undefined,
},
},
- components: {
- Tab,
- Tabs,
- },
data() {
const fromAccountOr = (prop, def) => {
if (!_.isUndefined(this.account)) {
@@ -290,9 +291,7 @@ export default {
smtpUser: fromAccountOr('smtpUser', ''),
smtpPassword: '',
},
- submitButtonText: this.account
- ? t('mail', 'Save')
- : t('mail', 'Connect'),
+ submitButtonText: this.account ? t('mail', 'Save') : t('mail', 'Connect'),
}
},
computed: {
diff --git a/src/components/Address.vue b/src/components/Address.vue
index 13f149ace..641817d0a 100644
--- a/src/components/Address.vue
+++ b/src/components/Address.vue
@@ -1,30 +1,34 @@
<template>
- <router-link :to="newMessageRoute"
- exact
- v-tooltip.bottom="email">{{ label }}
- </router-link>
+ <router-link v-tooltip.bottom="email" :to="newMessageRoute" exact>{{ label }} </router-link>
</template>
<script>
- export default {
- name: "Address",
- props: [
- 'email',
- 'label',
- ],
- computed: {
- newMessageRoute () {
- return {
- name: 'message',
- params: {
- accountId: this.$route.params.accountId,
- folderId: this.$route.params.folderId,
- messageUid: 'new'
- }, query: {
- to: this.email
- }
- }
+export default {
+ name: 'Address',
+ props: {
+ email: {
+ type: String,
+ required: true,
+ },
+ label: {
+ type: String,
+ required: true,
+ },
+ },
+ computed: {
+ newMessageRoute() {
+ return {
+ name: 'message',
+ params: {
+ accountId: this.$route.params.accountId,
+ folderId: this.$route.params.folderId,
+ messageUid: 'new',
+ },
+ query: {
+ to: this.email,
+ },
}
- }
- }
+ },
+ },
+}
</script>
diff --git a/src/components/AddressList.vue b/src/components/AddressList.vue
index 05634dc87..d0d08fe84 100644
--- a/src/components/AddressList.vue
+++ b/src/components/AddressList.vue
@@ -1,23 +1,25 @@
<template>
<span>
<template v-for="(entry, idx) in entries">
- <Address :key="entry.email"
- :email="entry.email"
- :label="entry.label"/><!--
- --><span
- v-if="idx+1 < entries.length">, </span>
+ <Address :key="entry.email" :email="entry.email" :label="entry.label" /><!--
+ --><span v-if="idx + 1 < entries.length" :key="entry.email">, </span>
</template>
</span>
</template>
<script>
- import Address from "./Address";
+import Address from './Address'
- export default {
- name: "AddressList",
- components: {Address},
- props: [
- 'entries'
- ]
- }
+export default {
+ name: 'AddressList',
+ components: {
+ Address,
+ },
+ props: {
+ entries: {
+ type: Array,
+ required: true,
+ },
+ },
+}
</script>
diff --git a/src/components/AppSettingsMenu.vue b/src/components/AppSettingsMenu.vue
index 067a3903a..a49774530 100644
--- a/src/components/AppSettingsMenu.vue
+++ b/src/components/AppSettingsMenu.vue
@@ -1,25 +1,24 @@
<template>
<div>
- <router-link to="/setup"
- class="button new-button">{{ t('mail', 'Add mail account') }}
- </router-link>
+ <router-link to="/setup" class="button new-button">{{ t('mail', 'Add mail account') }} </router-link>
- <p v-if="loadingAvatarSettings"
- class="avatar-settings">
+ <p v-if="loadingAvatarSettings" class="avatar-settings">
<span class="icon-loading-small"></span>
{{ t('mail', 'Use Gravatar and favicon avatars') }}
</p>
<p v-else>
- <input class="checkbox"
- id="gravatar-enabled"
- type="checkbox"
- :checked="useExternalAvatars"
- @change="onToggleExternalAvatars">
+ <input
+ id="gravatar-enabled"
+ class="checkbox"
+ type="checkbox"
+ :checked="useExternalAvatars"
+ @change="onToggleExternalAvatars"
+ />
<label for="gravatar-enabled">{{ t('mail', 'Use Gravatar and favicon avatars') }}</label>
</p>
<p class="app-settings-hint">
- <router-link :to="{ name: 'keyboardShortcuts' }">
+ <router-link :to="{name: 'keyboardShortcuts'}">
{{ t('mail', 'Keyboard shortcuts') }}
</router-link>
</p>
@@ -27,39 +26,40 @@
</template>
<script>
- export default {
- name: "AppSettingsMenu",
- data () {
- return {
- loadingAvatarSettings: false,
- }
- },
- computed: {
- useExternalAvatars () {
- return this.$store.getters.getPreference('external-avatars', 'true') === 'true'
- }
+export default {
+ name: 'AppSettingsMenu',
+ data() {
+ return {
+ loadingAvatarSettings: false,
+ }
+ },
+ computed: {
+ useExternalAvatars() {
+ return this.$store.getters.getPreference('external-avatars', 'true') === 'true'
},
- methods: {
- onToggleExternalAvatars (e) {
- this.loadingAvatarSettings = true
+ },
+ methods: {
+ onToggleExternalAvatars(e) {
+ this.loadingAvatarSettings = true
- this.$store.dispatch('savePreference', {
+ this.$store
+ .dispatch('savePreference', {
key: 'external-avatars',
value: e.target.checked ? 'true' : 'false',
})
- .catch(console.error.bind(this))
- .then(() => {
- this.loadingAvatarSettings = false
- })
- }
- }
- }
+ .catch(console.error.bind(this))
+ .then(() => {
+ this.loadingAvatarSettings = false
+ })
+ },
+ },
+}
</script>
<style scoped>
- p.avatar-settings span.icon-loading-small {
- display: inline-block;
- vertical-align: middle;
- padding: 5px 0;
- }
-</style> \ No newline at end of file
+p.avatar-settings span.icon-loading-small {
+ display: inline-block;
+ vertical-align: middle;
+ padding: 5px 0;
+}
+</style>
diff --git a/src/components/Avatar.vue b/src/components/Avatar.vue
index cd705b501..5521f4fe8 100644
--- a/src/components/Avatar.vue
+++ b/src/components/Avatar.vue
@@ -20,50 +20,47 @@
-->
<template>
- <BaseAvatar v-if="loading || !hasAvatar"
- :displayName="displayName"/>
- <BaseAvatar v-else
- :displayName="displayName"
- :url="avatarUrl"/>
+ <BaseAvatar v-if="loading || !hasAvatar" :display-name="displayName" />
+ <BaseAvatar v-else :display-name="displayName" :url="avatarUrl" />
</template>
<script>
- import _ from 'lodash'
- import {Avatar as BaseAvatar} from 'nextcloud-vue'
+import _ from 'lodash'
+import {Avatar as BaseAvatar} from 'nextcloud-vue'
- import {fetchAvatarUrlMemoized} from '../service/AvatarService'
+import {fetchAvatarUrlMemoized} from '../service/AvatarService'
- export default {
- name: 'Avatar',
- props: {
- displayName: {
- type: String,
- required: true,
- },
- email: {
- type: String,
- }
+export default {
+ name: 'Avatar',
+ components: {
+ BaseAvatar,
+ },
+ props: {
+ displayName: {
+ type: String,
+ required: true,
},
- data() {
- return {
- loading: true,
- avatarUrl: undefined,
- }
+ email: {
+ type: String,
+ required: true,
},
- computed: {
- hasAvatar() {
- return !_.isUndefined(this.avatarUrl)
- }
- },
- components: {
- BaseAvatar
- },
- mounted () {
- fetchAvatarUrlMemoized(this.email)
- .then(url => {
- this.avatarUrl = url
- this.loading = false
- })
+ },
+ data() {
+ return {
+ loading: true,
+ avatarUrl: undefined,
}
- }
+ },
+ computed: {
+ hasAvatar() {
+ return !_.isUndefined(this.avatarUrl)
+ },
+ },
+ mounted() {
+ fetchAvatarUrlMemoized(this.email).then(url => {
+ this.avatarUrl = url
+ this.loading = false
+ })
+ },
+}
</script>
diff --git a/src/components/Composer.vue b/src/components/Composer.vue
index 0a7a8e68b..8f8ca9a46 100644
--- a/src/components/Composer.vue
+++ b/src/components/Composer.vue
@@ -1,477 +1,470 @@
<template>
- <div v-if="state === STATES.EDITING"
- class="message-composer">
+ <div v-if="state === STATES.EDITING" class="message-composer">
<div class="composer-fields mail-account">
<label class="to-label transparency" for="from">
{{ t('mail', 'from') }}
</label>
- <Multiselect :options="aliases"
- id="from"
- v-model="selectedAlias"
- @keyup="onInputChanged"
- label="name" track-by="id"
- :customLabel="formatAliases" />
+ <Multiselect
+ id="from"
+ v-model="selectedAlias"
+ :options="aliases"
+ label="name"
+ track-by="id"
+ :custom-label="formatAliases"
+ @keyup="onInputChanged"
+ />
</div>
<div class="composer-fields">
<label class="to-label transparency" for="to">
{{ t('mail', 'to') }}
</label>
- <Multiselect :options="selectableRecipients"
- id="to"
- @keyup="onInputChanged"
- :taggable="true"
- @tag="onNewToAddr"
- @search-change="onAutocomplete"
- v-model="selectTo"
- label="label"
- track-by="email"
- :multiple="true" />
- <a v-if="!showCC"
- href="#"
- @click.prevent="showCC = true">
- {{ t ('mail', '+ cc/bcc') }}
+ <Multiselect
+ id="to"
+ v-model="selectTo"
+ :options="selectableRecipients"
+ :taggable="true"
+ label="label"
+ track-by="email"
+ :multiple="true"
+ @keyup="onInputChanged"
+ @tag="onNewToAddr"
+ @search-change="onAutocomplete"
+ />
+ <a v-if="!showCC" href="#" @click.prevent="showCC = true">
+ {{ t('mail', '+ cc/bcc') }}
</a>
</div>
- <div class="composer-fields"
- v-if="showCC">
+ <div v-if="showCC" class="composer-fields">
<label for="cc" class="cc-label transparency">
{{ t('mail', 'cc') }}
</label>
- <Multiselect :options="selectableRecipients"
- id="cc"
- @keyup="onInputChanged"
- :taggable="true"
- @tag="onNewCcAddr"
- @search-change="onAutocomplete"
- v-model="selectCc"
- label="label" track-by="email"
- :multiple="true" />
+ <Multiselect
+ id="cc"
+ v-model="selectCc"
+ :options="selectableRecipients"
+ :taggable="true"
+ label="label"
+ track-by="email"
+ :multiple="true"
+ @keyup="onInputChanged"
+ @tag="onNewCcAddr"
+ @search-change="onAutocomplete"
+ />
</div>
- <div class="composer-fields"
- v-if="showCC">
+ <div v-if="showCC" class="composer-fields">
<label for="bcc" class="bcc-label transparency">
{{ t('mail', 'bcc') }}
</label>
- <Multiselect :options="selectableRecipients"
- id="bcc"
- @keyup="onInputChanged"
- :taggable="true"
- @tag="onNewBccAddr"
- @search-change="onAutocomplete"
- v-model="selectBcc"
- label="label" track-by="email"
- :multiple="true" />
+ <Multiselect
+ id="bcc"
+ v-model="selectBcc"
+ :options="selectableRecipients"
+ :taggable="true"
+ label="label"
+ track-by="email"
+ :multiple="true"
+ @keyup="onInputChanged"
+ @tag="onNewBccAddr"
+ @search-change="onAutocomplete"
+ />
</div>
<div class="composer-fields">
<label for="subject" class="subject-label transparency">
{{ t('mail', 'Subject') }}
</label>
- <input type="text"
- id="subject"
- name="subject"
- v-model="subjectVal"
- v-on:keyup="onInputChanged"
- class="subject" autocomplete="off"
- :placeholder="t('mail', 'Subject')"/>
+ <input
+ id="subject"
+ v-model="subjectVal"
+ type="text"
+ name="subject"
+ class="subject"
+ autocomplete="off"
+ :placeholder="t('mail', 'Subject')"
+ @keyup="onInputChanged"
+ />
</div>
- <div v-if="noReply"
- class="warning noreply-box">
+ <div v-if="noReply" class="warning noreply-box">
{{ t('mail', 'Note that the mail came from a noreply address so your reply will probably not be read.') }}
</div>
<div class="composer-fields">
- <textarea name="body"
- class="message-body"
- v-autosize
- v-model="bodyVal"
- @keyup="onInputChanged"
- @keypress="onBodyKeyPress"
- :placeholder="t('mail', 'Message …')">{{message}}</textarea>
+ <textarea
+ v-model="bodyVal"
+ v-autosize
+ name="body"
+ class="message-body"
+ :placeholder="t('mail', 'Message …')"
+ @keyup="onInputChanged"
+ @keypress="onBodyKeyPress"
+ ></textarea>
</div>
<div class="submit-message-wrapper">
- <input class="submit-message send primary"
- type="submit"
- :value="submitButtonTitle"
- v-on:click="onSend">
+ <input class="submit-message send primary" type="submit" :value="submitButtonTitle" @click="onSend" />
</div>
- <ComposerAttachments v-model="attachments"
- @upload="onAttachmentsUploading"/>
- <span id="draft-status" v-if="savingDraft === true">{{ t('mail', 'Saving draft …') }}</span>
- <span id="draft-status" v-else-if="savingDraft === false">{{ t('mail', 'Draft saved') }}</span>
+ <ComposerAttachments v-model="attachments" @upload="onAttachmentsUploading" />
+ <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>
- <Loading v-else-if="state === STATES.UPLOADING"
- :hint="t('mail', 'Uploading attachments …')" />
- <Loading v-else-if="state === STATES.SENDING"
- :hint="t('mail', 'Sending …')" />
- <div v-else-if="state === STATES.ERROR"
- class="emptycontent">
+ <Loading v-else-if="state === STATES.UPLOADING" :hint="t('mail', 'Uploading attachments …')" />
+ <Loading v-else-if="state === STATES.SENDING" :hint="t('mail', 'Sending …')" />
+ <div v-else-if="state === STATES.ERROR" class="emptycontent">
<h2>{{ t('mail', 'Error sending your message') }}</h2>
<p v-if="errorText">{{ errorText }}</p>
- <button v-on:click="state = STATES.EDITING"
- class="button">{{ t('mail', 'Go back') }}</button>
- <button v-on:click="onSend"
- class="button primary">{{ t('mail', 'Retry') }}</button>
+ <button class="button" @click="state = STATES.EDITING">{{ t('mail', 'Go back') }}</button>
+ <button class="button primary" @click="onSend">{{ t('mail', 'Retry') }}</button>
</div>
- <div v-else
- class="emptycontent">
+ <div v-else class="emptycontent">
<h2 v-if="!isReply">{{ t('mail', 'Message sent!') }}</h2>
<h2 v-else>{{ t('mail', 'Reply sent!') }}</h2>
- <button v-on:click="reset"
- v-if="!isReply"
- class="button primary">{{ t('mail', 'Write another message') }}</button>
+ <button v-if="!isReply" class="button primary" @click="reset">
+ {{ t('mail', 'Write another message') }}
+ </button>
</div>
</template>
<script>
- import _ from 'lodash'
- import Autosize from 'vue-autosize'
- import debouncePromise from 'debounce-promise'
- import {Multiselect} from 'nextcloud-vue'
- import Vue from 'vue'
+import _ from 'lodash'
+import Autosize from 'vue-autosize'
+import debouncePromise from 'debounce-promise'
+import {Multiselect} from 'nextcloud-vue'
+import Vue from 'vue'
- import {findRecipient} from '../service/AutocompleteService'
- import Loading from './Loading'
- import ComposerAttachments from './ComposerAttachments'
+import {findRecipient} from '../service/AutocompleteService'
+import Loading from './Loading'
+import ComposerAttachments from './ComposerAttachments'
- const debouncedSearch = debouncePromise(findRecipient, 500)
+const debouncedSearch = debouncePromise(findRecipient, 500)
- Vue.use(Autosize)
+Vue.use(Autosize)
- const STATES = Object.seal({
- EDITING: 0,
- UPLOADING: 1,
- SENDING: 2,
- ERROR: 3,
- FINISHED: 4,
- })
+const STATES = Object.seal({
+ EDITING: 0,
+ UPLOADING: 1,
+ SENDING: 2,
+ ERROR: 3,
+ FINISHED: 4,
+})
- export default {
- name: 'Composer',
- components: {
- ComposerAttachments,
- Loading,
- Multiselect,
+export default {
+ name: 'Composer',
+ components: {
+ ComposerAttachments,
+ Loading,
+ Multiselect,
+ },
+ props: {
+ replyTo: {
+ type: Object,
+ default: () => undefined,
},
- props: {
- replyTo: {
- type: Object,
- },
- fromAccount: {
- type: Number,
- },
- to: {
- type: Array,
- default: () => [],
- },
- cc: {
- type: Array,
- default: () => [],
- },
- bcc: {
- type: Array,
- default: () => [],
- },
- subject: {
- type: String,
- default: '',
- },
- body: {
- type: String,
- default: ''
- },
- draft: {
- type: Function,
- required: true,
- },
- send: {
- type: Function,
- required: true,
+ fromAccount: {
+ type: Number,
+ default: () => undefined,
+ },
+ to: {
+ type: Array,
+ default: () => [],
+ },
+ cc: {
+ type: Array,
+ default: () => [],
+ },
+ bcc: {
+ type: Array,
+ default: () => [],
+ },
+ subject: {
+ type: String,
+ default: '',
+ },
+ body: {
+ type: String,
+ default: '',
+ },
+ draft: {
+ type: Function,
+ required: true,
+ },
+ send: {
+ type: Function,
+ required: true,
+ },
+ },
+ data() {
+ return {
+ showCC: this.cc.length > 0,
+ selectedAlias: -1, // Fixed in `beforeMount`
+ autocompleteRecipients: this.to.concat(this.cc).concat(this.bcc),
+ newRecipients: [],
+ subjectVal: this.subject,
+ bodyVal: this.body,
+ attachments: [],
+ noReply: this.to.some(to => to.email.startsWith('noreply@') || to.email.startsWith('no-reply@')),
+ submitButtonTitle: t('mail', 'Send'),
+ draftsPromise: Promise.resolve(),
+ attachmentsPromise: Promise.resolve(),
+ savingDraft: undefined,
+ saveDraftDebounced: _.debounce(this.saveDraft, 700),
+ state: STATES.EDITING,
+ errorText: undefined,
+ STATES,
+ selectTo: this.to,
+ selectCc: this.cc,
+ selectBcc: this.bcc,
+ }
+ },
+ computed: {
+ aliases() {
+ return this.$store.getters.getAccounts().filter(a => !a.isUnified)
+ },
+ selectableRecipients() {
+ return this.newRecipients.concat(this.autocompleteRecipients)
+ },
+ isReply() {
+ return !_.isUndefined(this.replyTo)
+ },
+ },
+ beforeMount() {
+ if (this.fromAccount) {
+ this.selectedAlias = this.aliases.find(alias => alias.id === this.fromAccount)
+ }
+ this.selectedAlias = this.aliases[0]
+ },
+ methods: {
+ recipientToRfc822(recipient) {
+ if (recipient.email === recipient.label) {
+ // From mailto or sender without proper label
+ return recipient.email
+ } else if (recipient.label === '') {
+ // Invalid label
+ return recipient.email
+ } else if (recipient.email.search(/^[a-zA-Z]+:[a-zA-Z]+$/) === 0) {
+ // Group integration
+ return recipient.email
+ } else {
+ // Proper layout with label
+ return `"${recipient.label}" <${recipient.email}>`
}
},
- data () {
- return {
- showCC: this.cc.length > 0,
- selectedAlias: -1, // Fixed in `beforeMount`
- autocompleteRecipients: this.to.concat(this.cc).concat(this.bcc),
- newRecipients: [],
- subjectVal: this.subject,
- bodyVal: this.body,
- attachments: [],
- noReply: this.to.some(to =>
- to.email.startsWith('noreply@') || to.email.startsWith('no-reply@')
- ),
- message: '',
- submitButtonTitle: t('mail', 'Send'),
- draftsPromise: Promise.resolve(),
- attachmentsPromise: Promise.resolve(),
- savingDraft: undefined,
- saveDraftDebounced: _.debounce(this.saveDraft, 700),
- state: STATES.EDITING,
- errorText: undefined,
- STATES,
- selectTo: this.to,
- selectCc: this.cc,
- selectBcc: this.bcc,
+ getMessageData() {
+ return uid => {
+ return {
+ account: this.selectedAlias.id,
+ to: this.selectTo.map(this.recipientToRfc822).join(', '),
+ cc: this.selectCc.map(this.recipientToRfc822).join(', '),
+ bcc: this.selectBcc.map(this.recipientToRfc822).join(', '),
+ draftUID: uid,
+ subject: this.subjectVal,
+ body: this.bodyVal,
+ attachments: this.attachments,
+ folderId: this.replyTo ? this.replyTo.folderId : undefined,
+ messageId: this.replyTo ? this.replyTo.messageId : undefined,
+ }
}
},
- beforeMount () {
- if (this.fromAccount) {
- this.selectedAlias = this.aliases.find(alias => alias.id === this.fromAccount)
+ saveDraft(data) {
+ this.savingDraft = true
+ this.draftsPromise = this.draftsPromise
+ .then(uid => this.draft(data(uid)))
+ .catch(console.error.bind(this))
+ .then(uid => {
+ this.savingDraft = false
+ return uid
+ })
+ },
+ onInputChanged() {
+ this.saveDraftDebounced(this.getMessageData())
+ },
+ onAutocomplete(term) {
+ if (_.isUndefined(term) || term === '') {
+ return
}
- this.selectedAlias = this.aliases[0]
+ debouncedSearch(term).then(results => {
+ this.autocompleteRecipients = _.uniqBy(this.autocompleteRecipients.concat(results), 'email')
+ })
},
- computed: {
- aliases () {
- return this.$store.getters.getAccounts()
- .filter(a => !a.isUnified)
- },
- selectableRecipients () {
- return this.newRecipients.concat(this.autocompleteRecipients)
- },
- isReply () {
- return !_.isUndefined(this.replyTo)
+ onAttachmentsUploading(uploaded) {
+ this.attachmentsPromise = this.attachmentsPromise
+ .then(() => uploaded)
+ .catch(console.error.bind(this))
+ .then(() => console.debug('attachments uploaded'))
+ },
+ onBodyKeyPress(event) {
+ // CTRL+Enter sends the message
+ if (event.keyCode === 13 && event.ctrlKey) {
+ return this.onSend()
}
},
- methods: {
- recipientToRfc822 (recipient) {
- if (recipient.email === recipient.label) {
- // From mailto or sender without proper label
- return recipient.email
- } else if (recipient.label === '') {
- // Invalid label
- return recipient.email
- } else if (recipient.email.search(/^[a-zA-Z]+:[a-zA-Z]+$/) === 0) {
- // Group integration
- return recipient.email
- } else {
- // Proper layout with label
- return `"${recipient.label}" <${recipient.email}>`
- }
- },
- getMessageData () {
- return uid => {
- return {
- account: this.selectedAlias.id,
- to: this.selectTo.map(this.recipientToRfc822).join(', '),
- cc: this.selectCc.map(this.recipientToRfc822).join(', '),
- bcc: this.selectBcc.map(this.recipientToRfc822).join(', '),
- draftUID: uid,
- subject: this.subjectVal,
- body: this.bodyVal,
- attachments: this.attachments,
- folderId: this.replyTo ? this.replyTo.folderId : undefined,
- messageId: this.replyTo ? this.replyTo.messageId : undefined,
- }
- }
- },
- saveDraft (data) {
- this.savingDraft = true
- this.draftsPromise = this.draftsPromise
- .then(uid => this.draft(data(uid)))
- .catch(console.error.bind(this))
- .then(uid => {
- this.savingDraft = false
- return uid
- })
- },
- onInputChanged () {
- this.saveDraftDebounced(this.getMessageData())
- },
- onAutocomplete (term) {
- if (_.isUndefined(term) || term === '') {
- return
- }
- debouncedSearch(term)
- .then(results => {
- this.autocompleteRecipients = _.uniqBy(
- this.autocompleteRecipients.concat(results),
- 'email',
- )
- })
- },
- onAttachmentsUploading (uploaded) {
- this.attachmentsPromise = this.attachmentsPromise
- .then(() => uploaded)
- .catch(console.error.bind(this))
- .then(() => console.debug('attachments uploaded'))
- },
- onBodyKeyPress (event) {
- // CTRL+Enter sends the message
- if (event.keyCode === 13 && event.ctrlKey) {
- return this.onSend()
- }
- },
- onNewToAddr (addr) {
- this.onNewAddr(addr, this.selectTo)
- },
- onNewCcAddr (addr) {
- this.onNewAddr(addr, this.selectCc)
- },
- onNewBccAddr (addr) {
- this.onNewAddr(addr, this.selectBcc)
- },
- onNewAddr (addr, list) {
- const res = {
- label: addr, // TODO: parse if possible
- email: addr, // TODO: parse if possible
- }
- this.newRecipients.push(res)
- list.push(res)
- },
- onSend () {
- this.state = STATES.UPLOADING
-
- return this.attachmentsPromise
- .then(() => this.state = STATES.SENDING)
- .then(() => this.draftsPromise)
- .then(this.getMessageData())
- .then(data => this.send(data))
- .then(() => console.info('message sent'))
- .then(() => this.state = STATES.FINISHED)
- .catch(e => {
- console.error('could not send message', e)
- if (e && e.toString) {
- this.errorText = e.toString()
- }
- this.state = STATES.ERROR
- })
- },
- reset () {
- this.selectTo = []
- this.selectCc = []
- this.selectBcc = []
- this.subjectVal = ''
- this.bodyVal = ''
- this.attachments = []
- this.errorText = undefined
- this.state = STATES.EDITING
- },
- /**
- * Format aliases for the Multiselect
- * @returns {string}
- */
- formatAliases(alias) {
- return `${alias.name} <${alias.emailAddress}>`
+ onNewToAddr(addr) {
+ this.onNewAddr(addr, this.selectTo)
+ },
+ onNewCcAddr(addr) {
+ this.onNewAddr(addr, this.selectCc)
+ },
+ onNewBccAddr(addr) {
+ this.onNewAddr(addr, this.selectBcc)
+ },
+ onNewAddr(addr, list) {
+ const res = {
+ label: addr, // TODO: parse if possible
+ email: addr, // TODO: parse if possible
}
- }
- }
+ this.newRecipients.push(res)
+ list.push(res)
+ },
+ onSend() {
+ this.state = STATES.UPLOADING
+
+ return this.attachmentsPromise
+ .then(() => (this.state = STATES.SENDING))
+ .then(() => this.draftsPromise)
+ .then(this.getMessageData())
+ .then(data => this.send(data))
+ .then(() => console.info('message sent'))
+ .then(() => (this.state = STATES.FINISHED))
+ .catch(e => {
+ console.error('could not send message', e)
+ if (e && e.toString) {
+ this.errorText = e.toString()
+ }
+ this.state = STATES.ERROR
+ })
+ },
+ reset() {
+ this.selectTo = []
+ this.selectCc = []
+ this.selectBcc = []
+ this.subjectVal = ''
+ this.bodyVal = ''
+ this.attachments = []
+ this.errorText = undefined
+ this.state = STATES.EDITING
+ },
+ /**
+ * Format aliases for the Multiselect
+ * @returns {string}
+ */
+ formatAliases(alias) {
+ return `${alias.name} <${alias.emailAddress}>`
+ },
+ },
+}
</script>
<style scoped>
- .message-composer {
- margin: 0;
- margin-bottom: 10px; /* line up with the send button */
- z-index: 100;
- }
-
- #reply-composer .message-composer {
- margin: 0;
- }
+.message-composer {
+ margin: 0;
+ margin-bottom: 10px; /* line up with the send button */
+ z-index: 100;
+}
- .composer-fields.mail-account > .multiselect {
- max-width: none;
- min-height: auto;
- width: 350px;
- }
+#reply-composer .message-composer {
+ margin: 0;
+}
- .composer-fields {
- display: flex;
- align-items: center;
- border-top: 1px solid var(--color-border);
- padding-right: 30px;
- }
- .composer-fields .multiselect,
- .composer-fields input,
- .composer-fields textarea {
- flex-grow: 1;
- max-width: none;
- border: none;
- border-radius: 0px;
- }
- .noreply-box {
- margin-top: 0;
- background: #fdffc3;
- padding-left: 64px;
- }
+.composer-fields.mail-account > .multiselect {
+ max-width: none;
+ min-height: auto;
+ width: 350px;
+}
- #to,
- #cc,
- #bcc,
- input.subject,
- textarea.message-body {
- padding: 12px;
- margin: 0;
- }
+.composer-fields {
+ display: flex;
+ align-items: center;
+ border-top: 1px solid var(--color-border);
+ padding-right: 30px;
+}
+.composer-fields .multiselect,
+.composer-fields input,
+.composer-fields textarea {
+ flex-grow: 1;
+ max-width: none;
+ border: none;
+ border-radius: 0px;
+}
+.noreply-box {
+ margin-top: 0;
+ background: #fdffc3;
+ padding-left: 64px;
+}
- #to {
- padding-right: 60px; /* for cc-bcc-toggle */
- }
+#to,
+#cc,
+#bcc,
+input.subject,
+textarea.message-body {
+ padding: 12px;
+ margin: 0;
+}
- input.cc,
- input.bcc,
- input.subject,
- textarea.message-body {
- border-top: none;
- }
+#to {
+ padding-right: 60px; /* for cc-bcc-toggle */
+}
- input.subject {
- font-size: 20px;
- font-weight: 300;
- }
+input.cc,
+input.bcc,
+input.subject,
+textarea.message-body {
+ border-top: none;
+}
- textarea.message-body {
- min-height: 300px;
- resize: none;
- padding-right: 25%;
- }
+input.subject {
+ font-size: 20px;
+ font-weight: 300;
+}
- #draft-status {
- padding: 5px;
- opacity: 0.5;
- font-size: small;
- }
+textarea.message-body {
+ min-height: 300px;
+ resize: none;
+ padding-right: 25%;
+}
- label.to-label,
- label.cc-label,
- label.bcc-label,
- label.subject-label {
- padding: 12px;
- padding-left: 30px;
- cursor: text;
- opacity: .5;
- width: 90px;
- text-align: right;
- }
+#draft-status {
+ padding: 5px;
+ opacity: 0.5;
+ font-size: small;
+}
- label.bcc-label {
- top: initial;
- bottom: 0;
- }
+label.to-label,
+label.cc-label,
+label.bcc-label,
+label.subject-label {
+ padding: 12px;
+ padding-left: 30px;
+ cursor: text;
+ opacity: 0.5;
+ width: 90px;
+ text-align: right;
+}
- textarea.reply {
- min-height: 100px;
- }
+label.bcc-label {
+ top: initial;
+ bottom: 0;
+}
- input.submit-message,
- .submit-message-wrapper {
- position: fixed;
- bottom: 10px;
- right: 15px;
- }
+textarea.reply {
+ min-height: 100px;
+}
- .submit-message-wrapper {
- position: fixed;
- height: 36px;
- width: 60px;
- }
+input.submit-message,
+.submit-message-wrapper {
+ position: fixed;
+ bottom: 10px;
+ right: 15px;
+}
- .submit-message.send {
- padding: 12px;
- }
+.submit-message-wrapper {
+ position: fixed;
+ height: 36px;
+ width: 60px;
+}
+.submit-message.send {
+ padding: 12px;
+}
</style>
<style>
- .multiselect .multiselect__tags {
- border: none !important;
- }
+.multiselect .multiselect__tags {
+ border: none !important;
+}
</style>
diff --git a/src/components/ComposerAttachments.vue b/src/components/ComposerAttachments.vue
index a4b1be033..b674ccd40 100644
--- a/src/components/ComposerAttachments.vue
+++ b/src/components/ComposerAttachments.vue
@@ -22,143 +22,126 @@
<template>
<div class="new-message-attachments">
<ul>
- <li v-for="attachment in value">
+ <li v-for="attachment in value" :key="attachment.id">
<div class="new-message-attachment-name">
- {{attachment.displayName}}
+ {{ attachment.displayName }}
</div>
- <div class="new-message-attachments-action svg icon-delete"
- v-on:click="onDelete(attachment)"></div>
+ <div class="new-message-attachments-action svg icon-delete" @click="onDelete(attachment)"></div>
</li>
</ul>
- <button class="button"
- :disabled="uploading"
- v-on:click="onAddLocalAttachment">
- <span :class="{ 'icon-upload' : !uploading, 'icon-loading-small': uploading }"/>
- {{ uploading ?
- t('mail', 'Uploading …') :
- t('mail', 'Upload attachment')
- }}
+ <button class="button" :disabled="uploading" @click="onAddLocalAttachment">
+ <span :class="{'icon-upload': !uploading, 'icon-loading-small': uploading}"></span>
+ {{ uploading ? t('mail', 'Uploading …') : t('mail', 'Upload attachment') }}
</button>
- <button class="button"
- v-on:click="onAddCloudAttachment">
- <span class="icon-folder"/>
+ <button class="button" @click="onAddCloudAttachment">
+ <span class="icon-folder" />
{{ t('mail', 'Add attachment from Files') }}
</button>
- <input type="file"
- ref="localAttachments"
- v-on:change="onLocalAttachmentSelected"
- multiple
- style="display: none;">
+ <input ref="localAttachments" type="file" multiple style="display: none;" @change="onLocalAttachmentSelected" />
</div>
</template>
<script>
- import _ from 'lodash'
- import {translate as t} from 'nextcloud-server/dist/l10n'
- import {pickFileOrDirectory} from 'nextcloud-server/dist/files'
+import _ from 'lodash'
+import {translate as t} from 'nextcloud-server/dist/l10n'
+import {pickFileOrDirectory} from 'nextcloud-server/dist/files'
- import {uploadLocalAttachment} from '../service/AttachmentService'
+import {uploadLocalAttachment} from '../service/AttachmentService'
- export default {
- name: 'ComposerAttachments',
- data () {
+export default {
+ name: 'ComposerAttachments',
+ props: {
+ value: {
+ type: Array,
+ required: true,
+ },
+ },
+ data() {
+ return {
+ uploading: false,
+ }
+ },
+ methods: {
+ onAddLocalAttachment() {
+ this.$refs.localAttachments.click()
+ },
+ fileNameToAttachment(name, id) {
return {
- uploading: false,
+ fileName: name,
+ displayName: _.trimStart(name, '/'),
+ id,
+ isLocal: !_.isUndefined(id),
}
},
- props: {
- value: {
- type: Array,
- required: true,
- }
+ emitNewAttachment(attachment) {
+ this.$emit('input', this.value.concat([attachment]))
},
- methods: {
- onAddLocalAttachment () {
- this.$refs.localAttachments.click()
- },
- fileNameToAttachment (name, id) {
- return {
- fileName: name,
- displayName: _.trimStart(name, '/'),
- id,
- isLocal: !_.isUndefined(id)
- }
- },
- emitNewAttachment (attachment) {
- this.$emit('input', this.value.concat([attachment]))
- },
- onLocalAttachmentSelected (e) {
- this.uploading = true
+ onLocalAttachmentSelected(e) {
+ this.uploading = true
- const done = Promise.all(
- _.map(
- e.target.files,
- file => uploadLocalAttachment(file)
- .then(({file, id}) => {
- console.info('uploaded')
- return this.emitNewAttachment(
- this.fileNameToAttachment(file.name, id)
- )
- })
- )
+ const done = Promise.all(
+ _.map(e.target.files, file =>
+ uploadLocalAttachment(file).then(({file, id}) => {
+ console.info('uploaded')
+ return this.emitNewAttachment(this.fileNameToAttachment(file.name, id))
+ })
)
- .catch(console.error.bind(this))
- .then(() => this.uploading = false)
+ )
+ .catch(console.error.bind(this))
+ .then(() => (this.uploading = false))
- this.$emit('upload', done)
+ this.$emit('upload', done)
- return done
- },
- onAddCloudAttachment () {
- return pickFileOrDirectory(t('mail', 'Choose a file to add as attachment'))
- .then(path => this.emitNewAttachment(
- this.fileNameToAttachment(path)
- ))
- .catch(console.error.bind(this))
- },
- onDelete (attachment) {
- this.$emit('input', this.value.filter(a => a !== attachment))
- }
- }
- }
+ return done
+ },
+ onAddCloudAttachment() {
+ return pickFileOrDirectory(t('mail', 'Choose a file to add as attachment'))
+ .then(path => this.emitNewAttachment(this.fileNameToAttachment(path)))
+ .catch(console.error.bind(this))
+ },
+ onDelete(attachment) {
+ this.$emit('input', this.value.filter(a => a !== attachment))
+ },
+ },
+}
</script>
<style scoped>
- button {
- /* TODO: remove for Nextcloud 15+ */
- /* https://github.com/nextcloud/server/pull/12138 */
- display: inline-block;
- }
+button {
+ /* TODO: remove for Nextcloud 15+ */
+ /* https://github.com/nextcloud/server/pull/12138 */
+ display: inline-block;
+}
- .new-message-attachments li {
- padding: 10px;
- }
+.new-message-attachments li {
+ padding: 10px;
+}
- .new-message-attachments-action {
- display: inline-block;
- vertical-align: middle;
- padding: 22px;
- opacity: .5;
- }
+.new-message-attachments-action {
+ display: inline-block;
+ vertical-align: middle;
+ padding: 22px;
+ opacity: 0.5;
+}
- /* attachment filenames */
- .new-message-attachment-name {
- display: inline-block;
- }
+/* attachment filenames */
+.new-message-attachment-name {
+ display: inline-block;
+}
- /* Colour the filename with a different color during attachment upload */
- .new-message-attachment-name.upload-ongoing {
- color: #0082c9;
- }
+/* Colour the filename with a different color during attachment upload */
+.new-message-attachment-name.upload-ongoing {
+ color: #0082c9;
+}
- /* Colour the filename in red if the attachment upload failed */
- .new-message-attachment-name.upload-warning {
- color: #d2322d;
- }
+/* Colour the filename in red if the attachment upload failed */
+.new-message-attachment-name.upload-warning {
+ color: #d2322d;
+}
- /* Red ProgressBar for failed attachment uploads */
- .new-message-attachment-name.upload-warning .ui-progressbar-value {
- border: 1px solid #e9322d;
- background: #e9322d;
- }
+/* Red ProgressBar for failed attachment uploads */
+.new-message-attachment-name.upload-warning .ui-progressbar-value {
+ border: 1px solid #e9322d;
+ background: #e9322d;
+}
</style>
diff --git a/src/components/EmptyFolder.vue b/src/components/EmptyFolder.vue
index 6126ee039..cf8d7f868 100644
--- a/src/components/EmptyFolder.vue
+++ b/src/components/EmptyFolder.vue
@@ -6,7 +6,7 @@
</template>
<script>
- export default {
- name: 'EmptyFolder'
- };
+export default {
+ name: 'EmptyFolder',
+}
</script>
diff --git a/src/components/Envelope.vue b/src/components/Envelope.vue
index dba07db5c..50f076aca 100644
--- a/src/components/Envelope.vue
+++ b/src/components/Envelope.vue
@@ -1,58 +1,38 @@
<template>
- <router-link
- class="app-content-list-item"
- :class="{ unseen: data.flags.unseen, draft }"
- :to="link"
- >
- <div class="mail-message-account-color"
- v-if="showAccountColor"
- :style="{ 'background-color': accountColor }">
- </div>
- <div
- class="app-content-list-item-star icon-starred"
- :data-starred="data.flags.flagged ? 'true':'false'"
- @click.prevent="toggleFlagged"
- />
- <div class="app-content-list-item-icon">
- <Avatar :displayName="sender"
- :email="senderEmail"/>
- </div>
- <div
- class="app-content-list-item-line-one"
- :title="sender"
- >
- {{ sender }}
- </div>
- <div
- class="app-content-list-item-line-two"
- :title="data.subject"
- >
- <span
- v-if="data.flags.answered"
- class="icon-reply"
- />
- <span
- v-if="data.flags.hasAttachments"
- class="icon-public icon-attachment"
- />
- <span
- v-if="draft"
- class="draft"
- >
- <em>{{ t('mail', 'Draft: ') }}</em>
- </span>
- {{ data.subject }}
- </div>
- <div class="app-content-list-item-details date">
- <Moment :timestamp="data.dateInt" />
- </div>
- <Action class="app-content-list-item-menu"
- :actions="actions" />
- </router-link>
+ <router-link class="app-content-list-item" :class="{unseen: data.flags.unseen, draft}" :to="link">
+ <div
+ v-if="showAccountColor"
+ class="mail-message-account-color"
+ :style="{'background-color': accountColor}"
+ ></div>
+ <div
+ class="app-content-list-item-star icon-starred"
+ :data-starred="data.flags.flagged ? 'true' : 'false'"
+ @click.prevent="toggleFlagged"
+ />
+ <div class="app-content-list-item-icon">
+ <Avatar :display-name="sender" :email="senderEmail" />
+ </div>
+ <div class="app-content-list-item-line-one" :title="sender">
+ {{ sender }}
+ </div>
+ <div class="app-content-list-item-line-two" :title="data.subject">
+ <span v-if="data.flags.answered" class="icon-reply" />
+ <span v-if="data.flags.hasAttachments" class="icon-public icon-attachment" />
+ <span v-if="draft" class="draft">
+ <em>{{ t('mail', 'Draft: ') }}</em>
+ </span>
+ {{ data.subject }}
+ </div>
+ <div class="app-content-list-item-details date">
+ <Moment :timestamp="data.dateInt" />
+ </div>
+ <Action class="app-content-list-item-menu" :actions="actions" />
+ </router-link>
</template>
<script>
-import { Action } from 'nextcloud-vue'
+import {Action} from 'nextcloud-vue'
import Moment from './Moment'
import Avatar from './Avatar'
@@ -68,27 +48,25 @@ export default {
props: {
data: {
type: Object,
- required: true,
- },
- showAccountColor: {
+ required: true,
+ },
+ showAccountColor: {
type: Boolean,
- default: false,
- }
+ default: false,
+ },
},
computed: {
accountColor() {
- return calculateAccountColor(
- this.$store.getters.getAccount(this.data.accountId).emailAddress
- )
- },
- draft() {
+ return calculateAccountColor(this.$store.getters.getAccount(this.data.accountId).emailAddress)
+ },
+ draft() {
return this.data.flags.draft
- },
- link() {
+ },
+ link() {
if (this.draft) {
// TODO: does not work with a unified drafts folder
- // the query should also contain the account and folder
- // id for that to work
+ // the query should also contain the account and folder
+ // id for that to work
return {
name: 'message',
params: {
@@ -97,7 +75,7 @@ export default {
messageUid: 'new',
draftUid: this.data.uid,
},
- exact: true
+ exact: true,
}
} else {
return {
@@ -105,12 +83,12 @@ export default {
params: {
accountId: this.$route.params.accountId,
folderId: this.$route.params.folderId,
- messageUid: this.data.uid
+ messageUid: this.data.uid,
},
- exact: true
+ exact: true,
}
- }
- },
+ }
+ },
sender() {
if (this.data.from.length === 0) {
// No sender
@@ -120,11 +98,13 @@ export default {
const first = this.data.from[0]
return first.label || first.email
},
- senderEmail() {
+ senderEmail() {
if (this.data.from.length > 0) {
return this.data.from[0].email
+ } else {
+ return undefined
}
- },
+ },
actions() {
return [
{
@@ -140,8 +120,8 @@ export default {
action: () => {
this.$emit('delete', this.data)
this.$store.dispatch('deleteMessage', this.data)
- }
- }
+ },
+ },
]
},
},
diff --git a/src/components/EnvelopeList.vue b/src/components/EnvelopeList.vue
index 7ea81467a..5a4938c99 100644
--- a/src/components/EnvelopeList.vue
+++ b/src/components/EnvelopeList.vue
@@ -1,256 +1,264 @@
<template>
- <transition-group name="list"
- tag="div"
- class="app-content-list"
- v-infinite-scroll="loadMore"
- infinite-scroll-disabled="loading"
- infinite-scroll-distance="30"
- v-scroll="onScroll"
- v-shortkey.once="shortkeys"
- @shortkey.native="handleShortcut">
- <div id="list-refreshing"
- key="loading"
- class="icon-loading-small"
- :class="{refreshing: refreshing}"/>
- <EmptyFolder v-if="envelopes.length === 0"
- key="empty"/>
- <Envelope v-else
- v-for="env in envelopes"
- :key="env.uid"
- :data="env"
- :show-account-color="folder.isUnified"
- @delete="onEnvelopeDeleted"/>
- <div id="load-more-mail-messages"
- key="loadingMore"
- :class="{'icon-loading-small': loadingMore}"/>
+ <transition-group
+ v-infinite-scroll="loadMore"
+ v-scroll="onScroll"
+ v-shortkey.once="shortkeys"
+ name="list"
+ tag="div"
+ class="app-content-list"
+ infinite-scroll-disabled="loading"
+ infinite-scroll-distance="30"
+ @shortkey.native="handleShortcut"
+ >
+ <div id="list-refreshing" key="loading" class="icon-loading-small" :class="{refreshing: refreshing}" />
+ <EmptyFolder v-if="envelopes.length === 0" key="empty" />
+ <Envelope
+ v-for="env in envelopes"
+ v-else
+ :key="env.uid"
+ :data="env"
+ :show-account-color="folder.isUnified"
+ @delete="onEnvelopeDeleted"
+ />
+ <div id="load-more-mail-messages" key="loadingMore" :class="{'icon-loading-small': loadingMore}" />
</transition-group>
</template>
<script>
- import _ from 'lodash'
- import infiniteScroll from 'vue-infinite-scroll'
- import vuescroll from 'vue-scroll'
- import Vue from 'vue'
+import _ from 'lodash'
+import infiniteScroll from 'vue-infinite-scroll'
+import vuescroll from 'vue-scroll'
+import Vue from 'vue'
- import EmptyFolder from './EmptyFolder'
- import Envelope from './Envelope'
+import EmptyFolder from './EmptyFolder'
+import Envelope from './Envelope'
- Vue.use(vuescroll, {throttle: 600})
+Vue.use(vuescroll, {throttle: 600})
- export default {
- name: "EnvelopeList",
- props: {
- account: {
- type: Object,
- required: true,
- },
- folder: {
- type: Object,
- required: true,
- },
- envelopes: {
- type: Array,
- required: true,
- },
- searchQuery: {
- type: String,
- required: false,
- default: undefined,
- }
+export default {
+ name: 'EnvelopeList',
+ components: {
+ Envelope,
+ EmptyFolder,
+ },
+ directives: {
+ infiniteScroll,
+ },
+ props: {
+ account: {
+ type: Object,
+ required: true,
},
- components: {
- Envelope,
- EmptyFolder,
+ folder: {
+ type: Object,
+ required: true,
},
- directives: {
- infiniteScroll,
+ envelopes: {
+ type: Array,
+ required: true,
},
- data () {
- return {
- loadingMore: false,
- refreshing: false,
- shortkeys: {
- del: ['del'],
- flag: ['s'],
- next: ['arrowright'],
- prev: ['arrowleft'],
- refresh: ['r'],
- unseen: ['u']
- }
- }
+ searchQuery: {
+ type: String,
+ required: false,
+ default: undefined,
},
- computed: {
- isSearch () {
- return !_.isUndefined(this.searchQuery)
- }
+ },
+ data() {
+ return {
+ loadingMore: false,
+ refreshing: false,
+ shortkeys: {
+ del: ['del'],
+ flag: ['s'],
+ next: ['arrowright'],
+ prev: ['arrowleft'],
+ refresh: ['r'],
+ unseen: ['u'],
+ },
+ }
+ },
+ computed: {
+ isSearch() {
+ return !_.isUndefined(this.searchQuery)
},
- methods: {
- loadMore () {
- this.loadingMore = true
+ },
+ methods: {
+ loadMore() {
+ this.loadingMore = true
- this.$store.dispatch('fetchNextEnvelopePage', {
+ this.$store
+ .dispatch('fetchNextEnvelopePage', {
accountId: this.$route.params.accountId,
folderId: this.$route.params.folderId,
query: this.searchQuery,
- }).catch(console.error.bind(this)).then(() => {
+ })
+ .catch(console.error.bind(this))
+ .then(() => {
this.loadingMore = false
})
- },
- sync () {
- this.refreshing = true
+ },
+ sync() {
+ this.refreshing = true
- this.$store.dispatch('syncEnvelopes', {
+ this.$store
+ .dispatch('syncEnvelopes', {
accountId: this.$route.params.accountId,
folderId: this.$route.params.folderId,
- }).catch(console.error.bind(this)).then(() => {
+ })
+ .catch(console.error.bind(this))
+ .then(() => {
this.refreshing = false
})
- },
- onEnvelopeDeleted (envelope) {
- const envelopes = this.envelopes
- const idx = this.envelopes.indexOf(envelope)
- if (idx === -1) {
- console.debug('envelope to delete does not exist in envelope list')
- return
- }
+ },
+ onEnvelopeDeleted(envelope) {
+ const envelopes = this.envelopes
+ const idx = this.envelopes.indexOf(envelope)
+ if (idx === -1) {
+ console.debug('envelope to delete does not exist in envelope list')
+ return
+ }
- let next
- if (idx === 0) {
- next = envelopes[idx + 1]
- } else {
- next = envelopes[idx - 1]
- }
+ let next
+ if (idx === 0) {
+ next = envelopes[idx + 1]
+ } else {
+ next = envelopes[idx - 1]
+ }
- if (!next) {
- console.debug('no next/previous envelope, not navigating')
- return
- }
+ if (!next) {
+ console.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,
- }
- });
- },
- onScroll (e, p) {
- if (p.scrollTop === 0 && !this.refreshing) {
- return this.sync()
- }
- },
- handleShortcut (e) {
- const envelopes = this.envelopes
- const currentUid = this.$route.params.messageUid
+ // 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,
+ },
+ })
+ },
+ onScroll(e, p) {
+ if (p.scrollTop === 0 && !this.refreshing) {
+ return this.sync()
+ }
+ },
+ handleShortcut(e) {
+ const envelopes = this.envelopes
+ const currentUid = this.$route.params.messageUid
- if (!currentUid) {
- console.debug('ignoring shortcut: no envelope selected')
- return
- }
+ if (!currentUid) {
+ console.debug('ignoring shortcut: no envelope selected')
+ return
+ }
- const current = envelopes.filter(e => e.uid == currentUid)
- if (current.length === 0) {
- console.debug('ignoring shortcut: currently displayed messages is not in current envelope list')
- return
- }
+ const current = envelopes.filter(e => e.uid == currentUid)
+ if (current.length === 0) {
+ console.debug('ignoring shortcut: currently displayed messages is not in current envelope list')
+ return
+ }
- const env = current[0]
- const idx = envelopes.indexOf(env)
+ const env = current[0]
+ const idx = envelopes.indexOf(env)
- switch (e.srcKey) {
- case 'next':
- case 'prev':
- let next
- if (e.srcKey === 'next') {
- next = envelopes[idx + 1]
- } else {
- next = envelopes[idx - 1]
- }
+ switch (e.srcKey) {
+ case 'next':
+ case 'prev':
+ let next
+ if (e.srcKey === 'next') {
+ next = envelopes[idx + 1]
+ } else {
+ next = envelopes[idx - 1]
+ }
- if (!next) {
- console.debug('ignoring shortcut: head or tail of envelope list reached', envelopes, idx, e.srcKey)
- return
- }
+ if (!next) {
+ console.debug(
+ 'ignoring shortcut: head or tail of envelope list reached',
+ envelopes,
+ idx,
+ e.srcKey
+ )
+ 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,
- }
- })
- break
- case 'del':
- console.debug('deleting', env)
- this.$store.dispatch('deleteMessage', env)
- .catch(console.error.bind(this))
+ // 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,
+ },
+ })
+ break
+ case 'del':
+ console.debug('deleting', env)
+ this.$store.dispatch('deleteMessage', env).catch(console.error.bind(this))
- break
- case 'flag':
- console.debug('flagging envelope via shortkey', env)
- this.$store.dispatch('toggleEnvelopeFlagged', env)
- .catch(console.error.bind(this))
- break
- case 'refresh':
- console.debug('syncing envelopes via shortkey')
- if (!this.refreshing) {
- this.sync()
- }
+ break
+ case 'flag':
+ console.debug('flagging envelope via shortkey', env)
+ this.$store.dispatch('toggleEnvelopeFlagged', env).catch(console.error.bind(this))
+ break
+ case 'refresh':
+ console.debug('syncing envelopes via shortkey')
+ if (!this.refreshing) {
+ this.sync()
+ }
- break
- case 'unseen':
- console.debug('marking message as seen/unseen via shortkey', env)
- this.$store.dispatch('toggleEnvelopeSeen', env)
- .catch(console.error.bind(this))
- break
- default:
- console.warn('shortcut ' + e.srcKey + ' is unknown. ignoring.')
- }
+ break
+ case 'unseen':
+ console.debug('marking message as seen/unseen via shortkey', env)
+ this.$store.dispatch('toggleEnvelopeSeen', env).catch(console.error.bind(this))
+ break
+ default:
+ console.warn('shortcut ' + e.srcKey + ' is unknown. ignoring.')
}
- }
- }
+ },
+ },
+}
</script>
<style scoped>
- #load-more-mail-messages {
- margin: 10px auto;
- padding: 10px;
- margin-top: 50px;
- margin-bottom: 200px;
- }
+#load-more-mail-messages {
+ margin: 10px auto;
+ padding: 10px;
+ margin-top: 50px;
+ margin-bottom: 200px;
+}
- /* TODO: put this in core icons.css as general rule for buttons with icons */
- #load-more-mail-messages.icon-loading-small {
- padding-left: 32px;
- background-position: 9px center;
- }
+/* TODO: put this in core icons.css as general rule for buttons with icons */
+#load-more-mail-messages.icon-loading-small {
+ padding-left: 32px;
+ background-position: 9px center;
+}
- #list-refreshing {
- overflow-y: hidden;
- min-height: 0;
+#list-refreshing {
+ overflow-y: hidden;
+ min-height: 0;
- transition-property: all;
- transition-duration: .5s;
- transition-timing-function: ease-in;
- }
+ transition-property: all;
+ transition-duration: 0.5s;
+ transition-timing-function: ease-in;
+}
- #list-refreshing.refreshing {
- min-height: 32px;
- }
+#list-refreshing.refreshing {
+ min-height: 32px;
+}
- .list-enter-active, .list-leave-active {
- transition: all 1s;
- }
+.list-enter-active,
+.list-leave-active {
+ transition: all 1s;
+}
- .list-enter, .list-leave-to {
- opacity: 0;
- height: 0px;
- transform: scaleY(0);
- }
+.list-enter,
+.list-leave-to {
+ opacity: 0;
+ height: 0px;
+ transform: scaleY(0);
+}
</style>
diff --git a/src/components/Error.vue b/src/components/Error.vue
index 7ef8645ee..e603b5d8a 100644
--- a/src/components/Error.vue
+++ b/src/components/Error.vue
@@ -24,36 +24,34 @@
<h2>{{ error }}</h2>
<p>{{ message }}</p>
<p v-if="data && data.debug">
- <a class="button"
- :href="reportUrl"
- target="_blank"
- rel="noopener">{{ t('mail', 'Report this bug') }}</a>
+ <a class="button" :href="reportUrl" target="_blank" rel="noopener">{{ t('mail', 'Report this bug') }}</a>
</p>
</div>
</template>
<script>
- import {getReportUrl} from '../util/CrashReport'
+import {getReportUrl} from '../util/CrashReport'
- export default {
- name: 'Error',
- props: {
- error: {
- type: String,
- required: true,
- },
- message: {
- type: String,
- required: true,
- },
- data: {
- type: Object,
- }
+export default {
+ name: 'Error',
+ props: {
+ error: {
+ type: String,
+ required: true,
},
- computed: {
- reportUrl() {
- return getReportUrl(this.data)
- }
- }
- }
+ message: {
+ type: String,
+ required: true,
+ },
+ data: {
+ type: Object,
+ default: () => undefined,
+ },
+ },
+ computed: {
+ reportUrl() {
+ return getReportUrl(this.data)
+ },
+ },
+}
</script>
diff --git a/src/components/FolderContent.vue b/src/components/FolderContent.vue
index 27a09129e..ee5b57136 100644
--- a/src/components/FolderContent.vue
+++ b/src/components/FolderContent.vue
@@ -1,145 +1,131 @@
<template>
<div id="app-content-wrapper">
- <Loading v-if="loading"
- :hint="t('mail', 'Loading messages')"/>
+ <Loading v-if="loading" :hint="t('mail', 'Loading messages')" />
<template v-else>
- <EnvelopeList :account="account"
- :folder="folder"
- :envelopes="envelopes"
- :searchQuery="searchQuery"/>
- <NewMessageDetail v-if="newMessage"/>
- <Message v-else-if="hasMessages"/>
+ <EnvelopeList :account="account" :folder="folder" :envelopes="envelopes" :search-query="searchQuery" />
+ <NewMessageDetail v-if="newMessage" />
+ <Message v-else-if="hasMessages" />
</template>
</div>
</template>
<script>
- import _ from 'lodash';
+import _ from 'lodash'
- import Message from "./Message";
- import EnvelopeList from "./EnvelopeList";
- import NewMessageDetail from "./NewMessageDetail";
- import Loading from "./Loading";
+import Message from './Message'
+import EnvelopeList from './EnvelopeList'
+import NewMessageDetail from './NewMessageDetail'
+import Loading from './Loading'
- export default {
- name: "FolderContent",
- components: {
- Loading,
- NewMessageDetail,
- Message,
- EnvelopeList,
+export default {
+ name: 'FolderContent',
+ components: {
+ Loading,
+ NewMessageDetail,
+ Message,
+ EnvelopeList,
+ },
+ props: {
+ account: {
+ type: Object,
+ required: true,
},
- props: {
- account: {
- type: Object,
- required: true,
- },
- folder: {
- type: Object,
- required: true,
- },
+ folder: {
+ type: Object,
+ required: true,
},
- data () {
- return {
- loading: true,
- searchQuery: undefined,
- alive: false,
+ },
+ data() {
+ return {
+ loading: true,
+ searchQuery: undefined,
+ alive: false,
+ }
+ },
+ computed: {
+ hasMessages() {
+ return this.$store.getters.getEnvelopes(this.account.id, this.folder.id).length > 0
+ },
+ newMessage() {
+ return this.$route.params.messageUid === 'new'
+ },
+ envelopes() {
+ if (_.isUndefined(this.searchQuery)) {
+ return this.$store.getters.getEnvelopes(this.account.id, this.folder.id)
+ } else {
+ return this.$store.getters.getSearchEnvelopes(this.account.id, this.folder.id)
}
},
- computed: {
- hasMessages () {
- return this.$store.getters.getEnvelopes(
- this.account.id,
- this.folder.id,
- ).length > 0
- },
- newMessage () {
- return this.$route.params.messageUid === 'new'
- },
- envelopes () {
- if (_.isUndefined(this.searchQuery)) {
- return this.$store.getters.getEnvelopes(
- this.account.id,
- this.folder.id,
- )
- } else {
- return this.$store.getters.getSearchEnvelopes(
- this.account.id,
- this.folder.id,
- )
- }
-
+ },
+ watch: {
+ $route(to, from) {
+ if (to.name === 'folder') {
+ // Navigate (back) to the folder view -> (re)fetch data
+ this.fetchData()
}
},
- created () {
- this.alive = true
+ },
+ created() {
+ this.alive = true
- new OCA.Search(this.searchProxy, this.clearSearchProxy)
+ new OCA.Search(this.searchProxy, this.clearSearchProxy)
- this.fetchData()
- },
- beforeDestroy () {
- this.alive = false
- },
- watch: {
- '$route' (to, from) {
- if (to.name === 'folder') {
- // Navigate (back) to the folder view -> (re)fetch data
- this.fetchData()
- }
- }
- },
- methods: {
- fetchData () {
- this.loading = true
+ this.fetchData()
+ },
+ beforeDestroy() {
+ this.alive = false
+ },
+ methods: {
+ fetchData() {
+ this.loading = true
- this.$store.dispatch(
- 'fetchEnvelopes', {
- accountId: this.account.id,
- folderId: this.folder.id,
- query: this.searchQuery,
- })
- .then(() => {
- const envelopes = this.envelopes
- console.debug('envelopes fetched', envelopes)
+ this.$store
+ .dispatch('fetchEnvelopes', {
+ accountId: this.account.id,
+ folderId: this.folder.id,
+ query: this.searchQuery,
+ })
+ .then(() => {
+ const envelopes = this.envelopes
+ console.debug('envelopes fetched', envelopes)
- this.loading = false
+ this.loading = false
- if (this.$route.name !== 'message' && envelopes.length > 0) {
- // Show first message
- let first = envelopes[0];
+ if (this.$route.name !== 'message' && envelopes.length > 0) {
+ // Show first message
+ let first = envelopes[0]
- // Keep the selected account-folder combination, but navigate to the message
- // (it's not a bug that we don't use first.accountId and first.folderId here)
- this.$router.replace({
- name: 'message',
- params: {
- accountId: this.account.id,
- folderId: this.folder.id,
- messageUid: first.uid,
- }
- })
- }
- });
- },
- searchProxy (query) {
- if (this.alive) {
- this.search(query)
- }
- },
- clearSearchProxy () {
- if (this.alive) {
- this.clearSearch()
- }
- },
- search (query) {
- this.searchQuery = query
+ // Keep the selected account-folder combination, but navigate to the message
+ // (it's not a bug that we don't use first.accountId and first.folderId here)
+ this.$router.replace({
+ name: 'message',
+ params: {
+ accountId: this.account.id,
+ folderId: this.folder.id,
+ messageUid: first.uid,
+ },
+ })
+ }
+ })
+ },
+ searchProxy(query) {
+ if (this.alive) {
+ this.search(query)
+ }
+ },
+ clearSearchProxy() {
+ if (this.alive) {
+ this.clearSearch()
+ }
+ },
+ search(query) {
+ this.searchQuery = query
- this.fetchData()
- },
- clearSearch () {
- this.searchQuery = undefined
- },
- }
- }
+ this.fetchData()
+ },
+ clearSearch() {
+ this.searchQuery = undefined
+ },
+ },
+}
</script>
diff --git a/src/components/Loading.vue b/src/components/Loading.vue
index 82f1a8a7f..4547301f3 100644
--- a/src/components/Loading.vue
+++ b/src/components/Loading.vue
@@ -7,14 +7,15 @@
</template>
<script>
- export default {
- name: "Loading",
- props: {
- hint: String
- }
- }
+export default {
+ name: 'Loading',
+ props: {
+ hint: {
+ type: String,
+ default: () => undefined,
+ },
+ },
+}
</script>
-<style scoped>
-
-</style> \ No newline at end of file
+<style scoped></style>
diff --git a/src/components/Message.vue b/src/components/Message.vue
index d2a313f58..742cba2da 100644
--- a/src/components/Message.vue
+++ b/src/components/Message.vue
@@ -1,265 +1,256 @@
<template>
<div class="app-content-details">
- <Loading v-if="loading"/>
- <Error v-else-if="!message"
- :error="error && error.message ? error.message : t('mail', 'Not found')"
- :message="errorMessage"
- :data="error">
+ <Loading v-if="loading" />
+ <Error
+ v-else-if="!message"
+ :error="error && error.message ? error.message : t('mail', 'Not found')"
+ :message="errorMessage"
+ :data="error"
+ >
</Error>
<template v-else>
<div id="mail-message-header" class="section">
- <h2 :title="message.subject">{{message.subject}}</h2>
+ <h2 :title="message.subject">{{ message.subject }}</h2>
<p class="transparency">
- <AddressList :entries="message.from"/>
- to <!-- TODO: translate -->
- <AddressList :entries="message.to"/>
+ <AddressList :entries="message.from" />
+ to
+ <!-- TODO: translate -->
+ <AddressList :entries="message.to" />
<template v-if="message.cc.length">
- (cc <!-- TODO: translate -->
- <AddressList :entries="message.cc"/><!--
+ (cc
+ <!-- TODO: translate -->
+ <AddressList :entries="message.cc" /><!--
-->)
</template>
</p>
</div>
<div class="mail-message-body">
- <MessageHTMLBody v-if="message.hasHtmlBody"
- :url="htmlUrl"
- @loaded="onHtmlBodyLoaded"/>
- <MessagePlainTextBody v-else
- :body="message.body"
- :signature="message.signature"/>
+ <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 type="button"
- id="forward-button"
- value="Forward"
- @click="forwardMessage">
+ <input id="forward-button" type="button" value="Forward" @click="forwardMessage" />
</div>
- <Composer v-if="!message.hasHtmlBody || htmlBodyLoaded"
- :fromAccount="message.accountId"
- :to="replyRecipient.to"
- :cc="replyRecipient.cc"
- :subject="replySubject"
- :body="replyBody"
- :replyTo="replyTo"
- :send="sendReply"
- :draft="saveReplyDraft"/>
+ <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>
</div>
</template>
<script>
- import { generateUrl } from 'nextcloud-server/dist/router'
+import {generateUrl} from 'nextcloud-server/dist/router'
- 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'
- import MessageHTMLBody from './MessageHTMLBody'
- import MessagePlainTextBody from './MessagePlainTextBody'
- import Loading from './Loading'
- import MessageAttachments from './MessageAttachments'
- import {saveDraft, sendMessage} from '../service/MessageService'
+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'
+import MessageHTMLBody from './MessageHTMLBody'
+import MessagePlainTextBody from './MessagePlainTextBody'
+import Loading from './Loading'
+import MessageAttachments from './MessageAttachments'
+import {saveDraft, sendMessage} from '../service/MessageService'
- export default {
- name: 'Message',
- components: {
- AddressList,
- Composer,
- Error,
- Loading,
- MessageAttachments,
- MessageHTMLBody,
- MessagePlainTextBody,
+export default {
+ name: 'Message',
+ components: {
+ AddressList,
+ Composer,
+ Error,
+ Loading,
+ MessageAttachments,
+ MessageHTMLBody,
+ MessagePlainTextBody,
+ },
+ data() {
+ return {
+ loading: true,
+ message: undefined,
+ errorMessage: '',
+ error: undefined,
+ htmlBodyLoaded: false,
+ replyRecipient: {},
+ replySubject: '',
+ replyBody: '',
+ }
+ },
+ computed: {
+ htmlUrl() {
+ return generateUrl('/apps/mail/api/accounts/{accountId}/folders/{folderId}/messages/{id}/html', {
+ accountId: this.message.accountId,
+ folderId: this.message.folderId,
+ id: this.message.id,
+ })
},
- data () {
+ replyTo() {
return {
- loading: true,
- message: undefined,
- errorMessage: '',
- error: undefined,
- htmlBodyLoaded: false,
- replyRecipient: {},
- replySubject: '',
- replyBody: '',
- }
- },
- computed: {
- htmlUrl () {
- return generateUrl('/apps/mail/api/accounts/{accountId}/folders/{folderId}/messages/{id}/html', {
- accountId: this.message.accountId,
- folderId: this.message.folderId,
- id: this.message.id
- })
- },
- replyTo () {
- return {
- accountId: this.message.accountId,
- folderId: this.message.folderId,
- messageId: this.message.id,
- }
+ accountId: this.message.accountId,
+ folderId: this.message.folderId,
+ messageId: this.message.id,
}
},
- created () {
+ },
+ watch: {
+ $route(to, from) {
this.fetchMessage()
},
- watch: {
- '$route' (to, from) {
- this.fetchMessage()
- }
- },
- methods: {
- fetchMessage () {
- this.loading = true
- this.message = undefined
- this.errorMessage = ''
- this.error = undefined
- this.replyRecipient = {}
- this.replySubject = ''
- this.replyBody = ''
- this.htmlBodyLoaded = false
+ },
+ created() {
+ this.fetchMessage()
+ },
+ methods: {
+ fetchMessage() {
+ this.loading = true
+ this.message = undefined
+ this.errorMessage = ''
+ this.error = undefined
+ this.replyRecipient = {}
+ this.replySubject = ''
+ this.replyBody = ''
+ this.htmlBodyLoaded = false
- const messageUid = this.$route.params.messageUid
+ const messageUid = this.$route.params.messageUid
- this.$store.dispatch('fetchMessage', messageUid)
- .then(message => {
- // TODO: add timeout so that message isn't flagged when only viewed
- // for a few seconds
- if (message.uid !== this.$route.params.messageUid) {
- console.debug('User navigated away, loaded message won\'t be shown nor flagged as seen')
- return
- }
-
- this.message = message
+ this.$store
+ .dispatch('fetchMessage', messageUid)
+ .then(message => {
+ // TODO: add timeout so that message isn't flagged when only viewed
+ // for a few seconds
+ if (message.uid !== this.$route.params.messageUid) {
+ console.debug("User navigated away, loaded message won't be shown nor flagged as seen")
+ return
+ }
- if (_.isUndefined(this.message)) {
- console.info('message could not be found', messageUid)
- this.errorMessage = getRandomMessageErrorMessage()
- this.loading = false
- return
- }
+ this.message = message
- const account = this.$store.getters.getAccount(message.accountId)
- this.replyRecipient = buildReplyRecipients(message, {
- label: account.name,
- email: account.emailAddress,
- })
- this.replySubject = buildReplySubject(message.subject)
+ if (_.isUndefined(this.message)) {
+ console.info('message could not be found', messageUid)
+ this.errorMessage = getRandomMessageErrorMessage()
+ this.loading = false
+ return
+ }
- if (!message.hasHtmlBody) {
- this.setReplyText(message.body)
- }
+ const account = this.$store.getters.getAccount(message.accountId)
+ this.replyRecipient = buildReplyRecipients(message, {
+ label: account.name,
+ email: account.emailAddress,
+ })
+ this.replySubject = buildReplySubject(message.subject)
- this.loading = false
+ if (!message.hasHtmlBody) {
+ this.setReplyText(message.body)
+ }
- const envelope = this.$store.getters.getEnvelope(message.accountId, message.folderId, message.id);
- if (!envelope.flags.unseen) {
- // Already seen -> no change necessary
- return
- }
+ this.loading = false
- return this.$store.dispatch('toggleEnvelopeSeen', envelope)
- })
- .catch(err => {
- console.error('could not load message ', messageUid, err)
- if (err.isError) {
- this.errorMessage = t('mail', 'Could not load your message')
- this.error = err
- this.loading = false
- }
- })
- },
- setReplyText (text) {
- const bodyText = htmlToText(text)
+ const envelope = this.$store.getters.getEnvelope(message.accountId, message.folderId, message.id)
+ if (!envelope.flags.unseen) {
+ // Already seen -> no change necessary
+ return
+ }
- this.$store.commit('setMessageBodyText', {
- uid: this.message.uid,
- bodyText,
+ return this.$store.dispatch('toggleEnvelopeSeen', envelope)
})
-
- this.replyBody = buildReplyBody(
- this.message.bodyText,
- this.message.from[0],
- this.message.dateInt,
- )
- },
- onHtmlBodyLoaded (bodyString) {
- this.setReplyText(bodyString)
- this.htmlBodyLoaded = true
- },
- saveReplyDraft (data) {
- return saveDraft(data.account, data)
- .then(({uid}) => uid)
- },
- sendReply (data) {
- return sendMessage(data.account, data)
- },
- forwardMessage () {
- this.$router.push({
- name: 'message',
- params: {
- accountId: this.$route.params.accountId,
- folderId: this.$route.params.folderId,
- messageUid: 'new',
- },
- query: {
- uid: this.message.uid,
+ .catch(err => {
+ console.error('could not load message ', messageUid, err)
+ if (err.isError) {
+ this.errorMessage = t('mail', 'Could not load your message')
+ this.error = err
+ this.loading = false
}
- });
- }
- }
- }
+ })
+ },
+ setReplyText(text) {
+ const bodyText = htmlToText(text)
+
+ this.$store.commit('setMessageBodyText', {
+ uid: this.message.uid,
+ bodyText,
+ })
+
+ this.replyBody = buildReplyBody(this.message.bodyText, this.message.from[0], this.message.dateInt)
+ },
+ onHtmlBodyLoaded(bodyString) {
+ this.setReplyText(bodyString)
+ this.htmlBodyLoaded = true
+ },
+ saveReplyDraft(data) {
+ return saveDraft(data.account, data).then(({uid}) => uid)
+ },
+ sendReply(data) {
+ return sendMessage(data.account, data)
+ },
+ forwardMessage() {
+ this.$router.push({
+ name: 'message',
+ params: {
+ accountId: this.$route.params.accountId,
+ folderId: this.$route.params.folderId,
+ messageUid: 'new',
+ },
+ query: {
+ uid: this.message.uid,
+ },
+ })
+ },
+ },
+}
</script>
<style>
- .mail-message-body {
- margin-bottom: 100px;
- }
+.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 h2,
+#mail-message-header p {
+ white-space: nowrap;
+ overflow: hidden;
+ text-overflow: ellipsis;
+ padding-bottom: 7px;
+ margin-bottom: 0;
+}
- #mail-content,
- .mail-message-attachments {
- margin: 10px 10px 50px 30px;
- }
+#mail-content,
+.mail-message-attachments {
+ margin: 10px 10px 50px 30px;
+}
- .mail-message-attachments {
- margin-top: 10px;
- }
+.mail-message-attachments {
+ margin-top: 10px;
+}
- #mail-content iframe {
- width: 100%;
- }
+#mail-content iframe {
+ width: 100%;
+}
- #show-images-text {
- display: none;
- }
+#show-images-text {
+ display: none;
+}
- #mail-content a,
- .mail-signature a {
- color: #07d;
- border-bottom: 1px dotted #07d;
- text-decoration: none;
- word-wrap: break-word;
- }
+#mail-content a,
+.mail-signature a {
+ color: #07d;
+ border-bottom: 1px dotted #07d;
+ text-decoration: none;
+ word-wrap: break-word;
+}
- #mail-message-header .transparency {
- opacity: .6;
- }
+#mail-message-header .transparency {
+ opacity: 0.6;
+}
- #mail-message-header .transparency a {
- font-weight: bold;
- }
+#mail-message-header .transparency a {
+ font-weight: bold;
+}
</style>
diff --git a/src/components/MessageAttachment.vue b/src/components/MessageAttachment.vue
index 03c357937..227a771b4 100644
--- a/src/components/MessageAttachment.vue
+++ b/src/components/MessageAttachment.vue
@@ -20,241 +20,251 @@
-->
<template>
- <div class="attachment"
- v-on:click="download">
- <img v-if="isImage"
- class="mail-attached-image"
- :src="url">
- <img class="attachment-icon"
- :src="mimeUrl"/>
- <span class="attachment-name"
- :title="label">{{fileName}}
- <span class="attachment-size">({{humanReadable(size)}})</span>
+ <div class="attachment" @click="download">
+ <img v-if="isImage" class="mail-attached-image" :src="url" />
+ <img class="attachment-icon" :src="mimeUrl" />
+ <span class="attachment-name" :title="label"
+ >{{ fileName }}
+ <span class="attachment-size">({{ humanReadable(size) }})</span>
</span>
- <button v-if="isCalendarEvent"
- class="button attachment-import calendar"
- :class="{'icon-add' : !loadingCalendars, 'icon-loading-small': loadingCalendars}"
- :disabled="loadingCalendars"
- v-on:click.stop="loadCalendars"
- :title="t('mail', 'Import into calendar')"></button>
- <button class="button icon-download attachment-download"
- :title="t('mail', 'Download attachment')"></button>
- <button class="attachment-save-to-cloud"
- :class="{'icon-folder' : !savingToCloud, 'icon-loading-small': savingToCloud}"
- :disabled="savingToCloud"
- v-on:click.stop="saveToCloud"
- :title="t('mail', 'Save to Files')"></button>
- <div class="popovermenu bubble attachment-import-popover hidden"
- :class="{open: showCalendarPopover}"
- v-on-click-outside="closeCalendarPopover">
- <PopoverMenu :menu="calendarMenuEntries"/>
+ <button
+ v-if="isCalendarEvent"
+ class="button attachment-import calendar"
+ :class="{'icon-add': !loadingCalendars, 'icon-loading-small': loadingCalendars}"
+ :disabled="loadingCalendars"
+ :title="t('mail', 'Import into calendar')"
+ @click.stop="loadCalendars"
+ ></button>
+ <button class="button icon-download attachment-download" :title="t('mail', 'Download attachment')"></button>
+ <button
+ class="attachment-save-to-cloud"
+ :class="{'icon-folder': !savingToCloud, 'icon-loading-small': savingToCloud}"
+ :disabled="savingToCloud"
+ :title="t('mail', 'Save to Files')"
+ @click.stop="saveToCloud"
+ ></button>
+ <div
+ v-on-click-outside="closeCalendarPopover"
+ class="popovermenu bubble attachment-import-popover hidden"
+ :class="{open: showCalendarPopover}"
+ >
+ <PopoverMenu :menu="calendarMenuEntries" />
</div>
</div>
</template>
<script>
- import {formatFileSize} from 'nextcloud-server/dist/format'
- import {mixin as onClickOutside} from 'vue-on-click-outside'
- import {pickFileOrDirectory} from 'nextcloud-server/dist/files'
- import {PopoverMenu} from 'nextcloud-vue'
+import {formatFileSize} from 'nextcloud-server/dist/format'
+import {mixin as onClickOutside} from 'vue-on-click-outside'
+import {pickFileOrDirectory} from 'nextcloud-server/dist/files'
+import {PopoverMenu} from 'nextcloud-vue'
- import {parseUid} from '../util/EnvelopeUidParser'
+import {parseUid} from '../util/EnvelopeUidParser'
- import {
- downloadAttachment,
- saveAttachmentToFiles
- } from '../service/AttachmentService'
- import {getUserCalendars, importCalendarEvent} from '../service/DAVService'
+import {downloadAttachment, saveAttachmentToFiles} from '../service/AttachmentService'
+import {getUserCalendars, importCalendarEvent} from '../service/DAVService'
- export default {
- name: "MessageAttachment",
- components: {
- PopoverMenu
+export default {
+ name: 'MessageAttachment',
+ components: {
+ PopoverMenu,
+ },
+ mixins: [onClickOutside],
+ props: {
+ id: {
+ type: Number,
+ required: true,
},
- mixins: [
- onClickOutside
- ],
- props: {
- id: Number,
- fileName: String,
- url: String,
- size: Number,
- mimeUrl: String,
- isImage: Boolean,
- isCalendarEvent: Boolean,
+ fileName: {
+ type: String,
+ required: true,
},
- data () {
- return {
- savingToCloud: false,
- loadingCalendars: false,
- calendars: [],
- showCalendarPopover: false,
- }
+ url: {
+ type: String,
+ required: true,
},
- computed: {
- label () {
- return this.fileName + " (" + formatFileSize(this.size) + ")"
- },
- calendarMenuEntries () {
- return this.calendars.map(cal => {
- return {
- icon: 'icon-add',
- text: cal.displayname,
- action: this.importCalendar(cal.url),
- }
- })
- }
+ size: {
+ type: Number,
+ required: true,
+ },
+ mimeUrl: {
+ type: String,
+ required: true,
+ },
+ isImage: {
+ type: Boolean,
+ default: false,
},
- methods: {
- humanReadable (size) {
- return formatFileSize(size)
- },
- saveToCloud () {
- const saveAttachment = (accountId, folderId, messageId, attachmentId) => directory => {
- return saveAttachmentToFiles(
- accountId,
- folderId,
- messageId,
- attachmentId,
- directory
- )
+ isCalendarEvent: {
+ type: Boolean,
+ default: false,
+ },
+ },
+ data() {
+ return {
+ savingToCloud: false,
+ loadingCalendars: false,
+ calendars: [],
+ showCalendarPopover: false,
+ }
+ },
+ computed: {
+ label() {
+ return this.fileName + ' (' + formatFileSize(this.size) + ')'
+ },
+ calendarMenuEntries() {
+ return this.calendars.map(cal => {
+ return {
+ icon: 'icon-add',
+ text: cal.displayname,
+ action: this.importCalendar(cal.url),
}
- const {accountId, folderId, id} = parseUid(this.$route.params.messageUid)
+ })
+ },
+ },
+ methods: {
+ humanReadable(size) {
+ return formatFileSize(size)
+ },
+ saveToCloud() {
+ const saveAttachment = (accountId, folderId, messageId, attachmentId) => directory => {
+ return saveAttachmentToFiles(accountId, folderId, messageId, attachmentId, directory)
+ }
+ const {accountId, folderId, id} = parseUid(this.$route.params.messageUid)
- return pickFileOrDirectory(
- t('mail', 'Choose a folder to store the attachment in'),
- false,
- ['httpd/unix-directory'],
- true
- )
- .then(dest => {
- this.savingToCloud = true
- return dest
- })
- .then(saveAttachment(accountId, folderId, id, this.id))
- .then(() => console.info('saved'))
- .catch(e => console.error('not saved', e))
- .then(() => this.savingToCloud = false)
- },
- download () {
- window.open(this.url)
- window.focus();
- },
- loadCalendars () {
- this.loadingCalendars = true
- getUserCalendars()
- .then(calendars => {
- this.calendars = calendars
- this.showCalendarPopover = true
- this.loadingCalendars = false
- })
- },
- closeCalendarPopover () {
- this.showCalendarPopover = false
- },
- importCalendar (url) {
- return () => {
- downloadAttachment(this.url)
- .then(importCalendarEvent(url))
- .then(() => console.info('calendar imported'))
- .catch(e => console.error('import error', e))
- .then(() => this.showCalendarPopover = false)
- }
+ return pickFileOrDirectory(
+ t('mail', 'Choose a folder to store the attachment in'),
+ false,
+ ['httpd/unix-directory'],
+ true
+ )
+ .then(dest => {
+ this.savingToCloud = true
+ return dest
+ })
+ .then(saveAttachment(accountId, folderId, id, this.id))
+ .then(() => console.info('saved'))
+ .catch(e => console.error('not saved', e))
+ .then(() => (this.savingToCloud = false))
+ },
+ download() {
+ window.open(this.url)
+ window.focus()
+ },
+ loadCalendars() {
+ this.loadingCalendars = true
+ getUserCalendars().then(calendars => {
+ this.calendars = calendars
+ this.showCalendarPopover = true
+ this.loadingCalendars = false
+ })
+ },
+ closeCalendarPopover() {
+ this.showCalendarPopover = false
+ },
+ importCalendar(url) {
+ return () => {
+ downloadAttachment(this.url)
+ .then(importCalendarEvent(url))
+ .then(() => console.info('calendar imported'))
+ .catch(e => console.error('import error', e))
+ .then(() => (this.showCalendarPopover = false))
}
- }
- }
+ },
+ },
+}
</script>
<style scoped>
- .attachment {
- position: relative;
- display: inline-block;
- border: 1px solid var(--color-border);
- border-radius: 3px;
- margin: 0 10px 10px 0;
- padding: 5px;
- }
+.attachment {
+ position: relative;
+ display: inline-block;
+ border: 1px solid var(--color-border);
+ border-radius: 3px;
+ margin: 0 10px 10px 0;
+ padding: 5px;
+}
- .attachment:hover,
- .attachment span:hover {
- background-color: var(--color-background-dark);
- cursor: pointer;
- }
+.attachment:hover,
+.attachment span:hover {
+ background-color: var(--color-background-dark);
+ cursor: pointer;
+}
- @media only screen and (max-width: 768px) {
- .attachment {
- width: calc(100% - 5px);
- }
+@media only screen and (max-width: 768px) {
+ .attachment {
+ width: calc(100% - 5px);
}
+}
- @media only screen and (min-width: 769px) and (max-width: 1400px) {
- .attachment {
- width: calc(50% - 10px);
- }
+@media only screen and (min-width: 769px) and (max-width: 1400px) {
+ .attachment {
+ width: calc(50% - 10px);
}
+}
- @media only screen and (min-width: 1401px) {
- .attachment {
- width: calc(33% - 12px);
- }
+@media only screen and (min-width: 1401px) {
+ .attachment {
+ width: calc(33% - 12px);
}
+}
- .mail-attached-image {
- display: block;
- max-width: 100%;
- max-height: 120px;
- }
+.mail-attached-image {
+ display: block;
+ max-width: 100%;
+ max-height: 120px;
+}
- .attachment-save-to-cloud,
- .attachment-download,
- .attachment-import {
- position: absolute;
- height: 32px;
- width: 32px;
- margin: 0;
- bottom: 0;
- background-color: transparent;
- border-color: transparent;
- }
+.attachment-save-to-cloud,
+.attachment-download,
+.attachment-import {
+ position: absolute;
+ height: 32px;
+ width: 32px;
+ margin: 0;
+ bottom: 0;
+ background-color: transparent;
+ border-color: transparent;
+}
- .attachment-save-to-cloud {
- right: 0;
- }
+.attachment-save-to-cloud {
+ right: 0;
+}
- .attachment-download {
- right: 32px;
- opacity: .6;
- }
+.attachment-download {
+ right: 32px;
+ opacity: 0.6;
+}
- .attachment-import {
- right: 64px;
- }
+.attachment-import {
+ right: 64px;
+}
- .attachment-import-popover {
- right: 32px;
- top: 42px;
- }
+.attachment-import-popover {
+ right: 32px;
+ top: 42px;
+}
- .attachment-import-popover::after {
- right: 32px;
- }
+.attachment-import-popover::after {
+ right: 32px;
+}
- .attachment-name {
- display: inline-block;
- width: calc(100% - 108px);
- white-space: nowrap;
- overflow: hidden;
- text-overflow: ellipsis;
- vertical-align: middle;
- }
+.attachment-name {
+ display: inline-block;
+ width: calc(100% - 108px);
+ white-space: nowrap;
+ overflow: hidden;
+ text-overflow: ellipsis;
+ vertical-align: middle;
+}
- /* show attachment size less prominent */
- .attachment-size {
- -ms-filter: "progid:DXImageTransform.Microsoft.Alpha(Opacity=50)";
- opacity: .5;
- }
+/* show attachment size less prominent */
+.attachment-size {
+ -ms-filter: 'progid:DXImageTransform.Microsoft.Alpha(Opacity=50)';
+ opacity: 0.5;
+}
- .attachment-icon {
- vertical-align: middle;
- }
+.attachment-icon {
+ vertical-align: middle;
+}
</style>
diff --git a/src/components/MessageAttachments.vue b/src/components/MessageAttachments.vue
index eefb00c4f..248a605b3 100644
--- a/src/components/MessageAttachments.vue
+++ b/src/components/MessageAttachments.vue
@@ -22,21 +22,25 @@
<template>
<div class="mail-message-attachments">
<div class="attachments">
- <MessageAttachment v-for="attachment in attachments"
- :key="attachment.id"
- :id="attachment.id"
- :fileName="attachment.fileName"
- :size="attachment.size"
- :url="attachment.downloadUrl"
- :isImage="attachment.isImage"
- :isCalendarEvent="attachment.isCalendarEvent"
- :mimeUrl="attachment.mimeUrl"/>
+ <MessageAttachment
+ v-for="attachment in attachments"
+ :id="attachment.id"
+ :key="attachment.id"
+ :file-name="attachment.fileName"
+ :size="attachment.size"
+ :url="attachment.downloadUrl"
+ :is-image="attachment.isImage"
+ :is-calendar-event="attachment.isCalendarEvent"
+ :mime-url="attachment.mimeUrl"
+ />
</div>
<p v-if="moreThanOne">
- <button class="attachments-save-to-cloud"
- :class="{'icon-folder' : !savingToCloud, 'icon-loading-small' : savingToCloud}"
- :disabled="savingToCloud"
- v-on:click="saveAll">
+ <button
+ class="attachments-save-to-cloud"
+ :class="{'icon-folder': !savingToCloud, 'icon-loading-small': savingToCloud}"
+ :disabled="savingToCloud"
+ @click="saveAll"
+ >
{{ t('mail', 'Save all to Files') }}
</button>
</p>
@@ -44,74 +48,72 @@
</template>
<script>
- import MessageAttachment from './MessageAttachment'
- import {parseUid} from '../util/EnvelopeUidParser'
- import {saveAttachmentsToFiles} from '../service/AttachmentService'
+import MessageAttachment from './MessageAttachment'
+import {parseUid} from '../util/EnvelopeUidParser'
+import {saveAttachmentsToFiles} from '../service/AttachmentService'
- export default {
- name: "MessageAttachments",
- components: {
- MessageAttachment
+export default {
+ name: 'MessageAttachments',
+ components: {
+ MessageAttachment,
+ },
+ props: {
+ attachments: {
+ type: Array,
+ required: true,
},
- props: {
- attachments: Array,
+ },
+ data() {
+ return {
+ savingToCloud: false,
+ }
+ },
+ computed: {
+ moreThanOne() {
+ return this.attachments.length > 1
},
- data () {
- return {
- savingToCloud: false,
+ },
+ methods: {
+ saveAll() {
+ const pickDestination = () => {
+ return new Promise((res, rej) => {
+ OC.dialogs.filepicker(
+ t('mail', 'Choose a folder to store the attachments in'),
+ res,
+ false,
+ 'httpd/unix-directory',
+ true
+ )
+ })
}
- },
- computed: {
- moreThanOne () {
- return this.attachments.length > 1
+ const saveAttachments = (accountId, folderId, messageId) => directory => {
+ return saveAttachmentsToFiles(accountId, folderId, messageId, directory)
}
- },
- methods: {
- saveAll () {
- const pickDestination = () => {
- return new Promise((res, rej) => {
- OC.dialogs.filepicker(
- t('mail', 'Choose a folder to store the attachments in'),
- res,
- false,
- 'httpd/unix-directory',
- true
- )
- })
- }
- const saveAttachments = (accountId, folderId, messageId) => directory => {
- return saveAttachmentsToFiles(
- accountId,
- folderId,
- messageId,
- directory
- )
- }
- const {accountId, folderId, id} = parseUid(this.$route.params.messageUid)
+ const {accountId, folderId, id} = parseUid(this.$route.params.messageUid)
- return pickDestination()
- .then(dest => {
- this.savingToCloud = true
- return dest
- })
- .then(saveAttachments(accountId, folderId, id))
- .then(() => console.info('saved'))
- .catch(e => console.error('not saved', e))
- .then(() => this.savingToCloud = false)
- }
- }
- }
+ return pickDestination()
+ .then(dest => {
+ this.savingToCloud = true
+ return dest
+ })
+ .then(saveAttachments(accountId, folderId, id))
+ .then(() => console.info('saved'))
+ .catch(e => console.error('not saved', e))
+ .then(() => (this.savingToCloud = false))
+ },
+ },
+}
</script>
<style>
- .mail-message-attachments {
- margin-bottom: 20px;
- }
+.mail-message-attachments {
+ margin-bottom: 20px;
+}
- /* show icon + text for Download all button
+/* show icon + text for Download all button
as well as when there is only one attachment */
- .attachments-save-to-cloud {
- background-position: 9px center;
- padding-left: 32px;
- }
+.attachments-save-to-cloud {
+ background-position: 9px center;
+ padding-left: 32px;
+}
</style>
diff --git a/src/components/MessageHTMLBody.vue b/src/components/MessageHTMLBody.vue
index b784597f2..6b5a0c33e 100644
--- a/src/components/MessageHTMLBody.vue
+++ b/src/components/MessageHTMLBody.vue
@@ -6,76 +6,71 @@
{{ t('mail', 'Show images from this sender') }}
</button>
</div>
- <div v-if="loading"
- class="icon-loading"/>
- <div :class="{hidden: loading}"
- id="message-container">
- <iframe id="message-frame"
- ref="iframe"
- @load="onMessageFrameLoad"
- :src="url"
- seamless/>
+ <div v-if="loading" class="icon-loading" />
+ <div id="message-container" :class="{hidden: loading}">
+ <iframe id="message-frame" ref="iframe" :src="url" seamless @load="onMessageFrameLoad" />
</div>
</div>
</template>
<script>
- export default {
- name: "MessageHTMLBody",
- props: {
- url: {
- type: String,
- required: true,
- },
+export default {
+ name: 'MessageHTMLBody',
+ props: {
+ url: {
+ type: String,
+ required: true,
},
- data () {
- return {
- loading: true,
- hasBlockedContent: false,
- }
+ },
+ data() {
+ return {
+ loading: true,
+ hasBlockedContent: false,
+ }
+ },
+ methods: {
+ getIframeDoc() {
+ const iframe = this.$refs.iframe
+ return iframe.contentDocument || iframe.contentWindow.document
},
- methods: {
- getIframeDoc () {
- const iframe = this.$refs.iframe
- return iframe.contentDocument || iframe.contentWindow.document
- },
- onMessageFrameLoad () {
- const iframeDoc = this.getIframeDoc()
- const iframeBody = iframeDoc.querySelectorAll('body')[0]
- this.hasBlockedContent = iframeDoc.querySelectorAll('[data-original-src]').length > 0
- || iframeDoc.querySelectorAll('[data-original-style]').length > 0
+ onMessageFrameLoad() {
+ const iframeDoc = this.getIframeDoc()
+ const iframeBody = iframeDoc.querySelectorAll('body')[0]
+ this.hasBlockedContent =
+ iframeDoc.querySelectorAll('[data-original-src]').length > 0 ||
+ iframeDoc.querySelectorAll('[data-original-style]').length > 0
- this.$emit('loaded', iframeBody.outerHTML)
- this.loading = false
- },
- onShowBlockedContent () {
- const iframeDoc = this.getIframeDoc()
- iframeDoc.querySelectorAll('[data-original-src]').forEach(
- node => node.setAttribute('src', node.getAttribute('data-original-src'))
- )
- iframeDoc.querySelectorAll('[data-original-style]').forEach(node =>
- node.setAttribute('style', node.getAttribute('data-original-style'))
- )
+ this.$emit('loaded', iframeBody.outerHTML)
+ this.loading = false
+ },
+ onShowBlockedContent() {
+ const iframeDoc = this.getIframeDoc()
+ iframeDoc
+ .querySelectorAll('[data-original-src]')
+ .forEach(node => node.setAttribute('src', node.getAttribute('data-original-src')))
+ iframeDoc
+ .querySelectorAll('[data-original-style]')
+ .forEach(node => node.setAttribute('style', node.getAttribute('data-original-style')))
- this.hasBlockedContent = false
- }
- }
- };
+ this.hasBlockedContent = false
+ },
+ },
+}
</script>
<style scoped>
- #message-container {
- position: relative;
- width: 100%;
- height: 0;
- padding-bottom: 56.25%;
- }
+#message-container {
+ position: relative;
+ width: 100%;
+ height: 0;
+ padding-bottom: 56.25%;
+}
- #message-frame {
- position: absolute;
- top: 0;
- left: 0;
- width: 100%;
- height: 100%;
- }
+#message-frame {
+ position: absolute;
+ top: 0;
+ left: 0;
+ width: 100%;
+ height: 100%;
+}
</style>
diff --git a/src/components/MessagePlainTextBody.vue b/src/components/MessagePlainTextBody.vue
index f1337e9a4..738e544ef 100644
--- a/src/components/MessagePlainTextBody.vue
+++ b/src/components/MessagePlainTextBody.vue
@@ -1,26 +1,30 @@
<template>
<div>
- <div id="mail-content" v-html="body" />
- <div v-if="signature"
- class="mail-signature"
- v-html="signature" />
+ <div id="mail-content" v-html="body"></div>
+ <div v-if="signature" class="mail-signature" v-html="signature"></div>
</div>
</template>
<script>
- export default {
- name: "MessagePlainTextBody",
- props: [
- 'body',
- 'signature',
- ]
- };
+export default {
+ name: 'MessagePlainTextBody',
+ props: {
+ body: {
+ type: String,
+ required: true,
+ },
+ signature: {
+ type: String,
+ default: () => undefined,
+ },
+ },
+}
</script>
<style scoped>
- .mail-signature {
- font-family: monospace;
- opacity: 0.5;
- line-height: initial;
- }
+.mail-signature {
+ font-family: monospace;
+ opacity: 0.5;
+ line-height: initial;
+}
</style>
diff --git a/src/components/Moment.vue b/src/components/Moment.vue
index 358212c2a..8b6f80fa9 100644
--- a/src/components/Moment.vue
+++ b/src/components/Moment.vue
@@ -1,30 +1,34 @@
<template>
- <span class="live-relative-timestamp"
- :data-timestamp="timestamp * 1000"
- :title="title">{{ formatted }}</span>
+ <span class="live-relative-timestamp" :data-timestamp="timestamp * 1000" :title="title">{{ formatted }}</span>
</template>
<script>
- import { getLocale } from 'nextcloud-server/dist/l10n'
- import moment from 'moment';
+import {getLocale} from 'nextcloud-server/dist/l10n'
+import moment from 'moment'
- if (typeof OC !== 'undefined') {
- moment.locale(getLocale());
- }
+if (typeof OC !== 'undefined') {
+ moment.locale(getLocale())
+}
- export default {
- name: "Moment",
- props: [
- 'timestamp',
- 'format'
- ],
- computed: {
- title () {
- return moment.unix(this.timestamp).format(this.format || 'LLL');
- },
- formatted () {
- return moment.unix(this.timestamp).fromNow();
- }
- }
- }
+export default {
+ name: 'Moment',
+ props: {
+ timestamp: {
+ type: Number,
+ required: true,
+ },
+ format: {
+ type: String,
+ default: 'LLL',
+ },
+ },
+ computed: {
+ title() {
+ return moment.unix(this.timestamp).format(this.format)
+ },
+ formatted() {
+ return moment.unix(this.timestamp).fromNow()
+ },
+ },
+}
</script>
diff --git a/src/components/Navigation.vue b/src/components/Navigation.vue
index 6ba82d12e..a5b791942 100644
--- a/src/components/Navigation.vue
+++ b/src/components/Navigation.vue
@@ -21,161 +21,159 @@
<template>
<AppNavigation>
- <AppNavigationNew :text="t('mail', 'New message')"
- buttonId="mail_new_message"
- buttonClass="icon-add"
- @click="onNewMessage"/>
+ <AppNavigationNew
+ :text="t('mail', 'New message')"
+ button-id="mail_new_message"
+ button-class="icon-add"
+ @click="onNewMessage"
+ />
<ul id="accounts-list">
<template v-for="group in menu">
- <AppNavigationItem v-for="item in group"
- :key="item.key"
- :item="item"/>
- <AppNavigationSpacer />
+ <AppNavigationItem v-for="item in group.items" :key="item.key" :item="item" />
+ <AppNavigationSpacer :key="group.id" />
</template>
</ul>
<AppNavigationSettings :title="t('mail', 'Settings')">
- <AppSettingsMenu/>
+ <AppSettingsMenu />
</AppNavigationSettings>
</AppNavigation>
</template>
<script>
- import {translate as t} from 'nextcloud-server/dist/l10n'
- import {
- AppContent,
+import {translate as t} from 'nextcloud-server/dist/l10n'
+import {
+ AppContent,
+ AppNavigation,
+ AppNavigationItem,
+ AppNavigationNew,
+ AppNavigationSettings,
+ AppNavigationSpacer,
+} from 'nextcloud-vue'
+
+import {calculateAccountColor} from '../util/AccountColor'
+
+const SHOW_COLLAPSED = Object.seal(['inbox', 'flagged', 'drafts', 'sent'])
+
+import AppSettingsMenu from '../components/AppSettingsMenu'
+
+export default {
+ name: 'Navigation',
+ components: {
AppNavigation,
AppNavigationItem,
AppNavigationNew,
AppNavigationSettings,
- AppNavigationSpacer
- } from 'nextcloud-vue'
-
- import {calculateAccountColor} from '../util/AccountColor'
+ AppNavigationSpacer,
+ AppSettingsMenu,
+ },
+ computed: {
+ menu() {
+ return this.$store.getters.getAccounts().map(account => {
+ const items = []
- const SHOW_COLLAPSED = Object.seal([
- 'inbox',
- 'flagged',
- 'drafts',
- 'sent'
- ]);
+ const isError = account.error
- import AppSettingsMenu from '../components/AppSettingsMenu'
-
- export default {
- name: "Navigation",
- components: {
- AppNavigation,
- AppNavigationItem,
- AppNavigationNew,
- AppNavigationSettings,
- AppNavigationSpacer,
- AppSettingsMenu,
- },
- computed: {
- menu() {
- return this.$store.getters.getAccounts().map(account => {
- const items = []
-
- const isError = account.error
+ if (account.isUnified !== true && account.visible !== false) {
+ const route = {
+ name: 'accountSettings',
+ params: {
+ accountId: account.id,
+ },
+ }
- if (account.isUnified !== true && account.visible !== false) {
- items.push({
- id: 'account' + account.id,
- key: 'account' + account.id,
- text: account.emailAddress,
- bullet: isError ? undefined : calculateAccountColor(account.name), // TODO
- icon: isError ? 'icon-error' : undefined,
- router: {
- name: 'accountSettings',
- params: {
- accountId: account.id,
- }
- },
- utils: {
- actions: [
- {
- icon: 'icon-settings',
- text: t('mail', 'Edit'),
- action: () => {
- this.$router.push(route)
- },
+ items.push({
+ id: 'account' + account.id,
+ key: 'account' + account.id,
+ text: account.emailAddress,
+ bullet: isError ? undefined : calculateAccountColor(account.name), // TODO
+ icon: isError ? 'icon-error' : undefined,
+ router: route,
+ utils: {
+ actions: [
+ {
+ icon: 'icon-settings',
+ text: t('mail', 'Edit'),
+ action: () => {
+ this.$router.push(route) // eslint-disable-line
},
- {
- icon: 'icon-delete',
- text: t('mail', 'Delete'),
- action: () => {
- this.$store.dispatch('deleteAccount', account)
- .catch(console.error.bind(this))
- }
- }
- ]
- }
- })
- }
+ },
+ {
+ icon: 'icon-delete',
+ text: t('mail', 'Delete'),
+ action: () => {
+ this.$store.dispatch('deleteAccount', account).catch(console.error.bind(this))
+ },
+ },
+ ],
+ },
+ })
+ }
- const folderToEntry = folder => {
- let icon = 'folder';
- if (folder.specialRole) {
- icon = folder.specialRole;
- }
+ const folderToEntry = folder => {
+ let icon = 'folder'
+ if (folder.specialRole) {
+ icon = folder.specialRole
+ }
- return {
- id: 'account' + account.id + '_' + folder.id,
- key: 'account' + account.id + '_' + folder.id,
- text: folder.name,
- icon: 'icon-' + icon,
- router: {
- name: 'folder',
- params: {
- accountId: account.id,
- folderId: folder.id,
- },
- exact: false,
- },
- utils: {
- counter: folder.unread,
+ return {
+ id: 'account' + account.id + '_' + folder.id,
+ key: 'account' + account.id + '_' + folder.id,
+ text: folder.name,
+ icon: 'icon-' + icon,
+ router: {
+ name: 'folder',
+ params: {
+ accountId: account.id,
+ folderId: folder.id,
},
- collapsible: true,
- opened: folder.opened,
- children: folder.folders.map(folderToEntry)
- }
+ exact: false,
+ },
+ utils: {
+ counter: folder.unread,
+ },
+ collapsible: true,
+ opened: folder.opened,
+ children: folder.folders.map(folderToEntry),
}
+ }
- this.$store.getters.getFolders(account.id)
- .filter(folder => !account.collapsed || SHOW_COLLAPSED.indexOf(folder.specialRole) !== -1)
- .map(folderToEntry)
- .forEach(i => items.push(i))
+ this.$store.getters
+ .getFolders(account.id)
+ .filter(folder => !account.collapsed || SHOW_COLLAPSED.indexOf(folder.specialRole) !== -1)
+ .map(folderToEntry)
+ .forEach(i => items.push(i))
- if (!account.isUnified && account.folders.length > 0) {
- items.push({
- id: 'collapse-' + account.id,
- key: 'collapse-' + account.id,
- classes: ['collapse-folders'],
- text: account.collapsed ? t('mail', 'Show all folders') : t('mail', 'Collapse folders'),
- action: () => this.$store.commit('toggleAccountCollapsed', account.id)
- })
- }
+ if (!account.isUnified && account.folders.length > 0) {
+ items.push({
+ id: 'collapse-' + account.id,
+ key: 'collapse-' + account.id,
+ classes: ['collapse-folders'],
+ text: account.collapsed ? t('mail', 'Show all folders') : t('mail', 'Collapse folders'),
+ action: () => this.$store.commit('toggleAccountCollapsed', account.id),
+ })
+ }
- return items
- })
- }
+ return {
+ id: account.id,
+ items,
+ }
+ })
},
- methods: {
- onNewMessage () {
- // FIXME: assumes that we're on the 'message' route already
- this.$router.push({
- name: 'message',
- params: {
- accountId: this.$route.params.accountId,
- folderId: this.$route.params.folderId,
- messageUid: 'new',
- }
- });
- }
+ },
+ methods: {
+ onNewMessage() {
+ // FIXME: assumes that we're on the 'message' route already
+ this.$router.push({
+ name: 'message',
+ params: {
+ accountId: this.$route.params.accountId,
+ folderId: this.$route.params.folderId,
+ messageUid: 'new',
+ },
+ })
},
- }
+ },
+}
</script>
-<style scoped>
-
-</style> \ No newline at end of file
+<style scoped></style>
diff --git a/src/components/NewMessageDetail.vue b/src/components/NewMessageDetail.vue
index cd4eba3a4..bf479ac8d 100644
--- a/src/components/NewMessageDetail.vue
+++ b/src/components/NewMessageDetail.vue
@@ -1,199 +1,200 @@
<template>
<div class="app-content-details">
- <Loading v-if="loading"/>
- <Error v-else-if="error"
- :error="error && error.message ? error.message : t('mail', 'Not found')"
- :message="errorMessage"
- :data="error">
+ <Loading v-if="loading" />
+ <Error
+ v-else-if="error"
+ :error="error && error.message ? error.message : t('mail', 'Not found')"
+ :message="errorMessage"
+ :data="error"
+ >
</Error>
- <Composer v-else
- :fromAccount="composerData.accountId"
- :to="composerData.to"
- :cc="composerData.cc"
- :bcc="composerData.bcc"
- :subject="composerData.subject"
- :body="composerData.body"
- :draft="saveDraft"
- :send="sendMessage"/>
+ <Composer
+ v-else
+ :from-account="composerData.accountId"
+ :to="composerData.to"
+ :cc="composerData.cc"
+ :bcc="composerData.bcc"
+ :subject="composerData.subject"
+ :body="composerData.body"
+ :draft="saveDraft"
+ :send="sendMessage"
+ />
</div>
</template>
<script>
- import _ from 'lodash'
-
- import {buildFowardSubject, buildReplyBody} from '../ReplyBuilder'
- import Composer from './Composer'
- import Error from './Error'
- import Loading from './Loading'
- import {saveDraft, sendMessage} from '../service/MessageService'
-
- export default {
- name: 'NewMessageDetail',
- components: {
- Composer,
- Error,
- Loading,
- },
- data() {
- return {
- loading: false,
- draft: undefined,
- errorMessage: '',
- error: undefined,
- }
- },
- computed: {
- composerData () {
- if (!_.isUndefined(this.draft)) {
- console.info('todo: handle draft data')
- return {
- to: this.draft.to,
- cc: this.draft.cc,
- bcc: this.draft.bcc, // TODO: impl in composer
- subject: this.draft.subject,
- body: this.draft.body,
- }
- } else if (!_.isUndefined(this.$route.query.uid)) {
- // Forwarded message
-
- const message = this.$store.getters.getMessageByUid(this.$route.query.uid)
- console.debug('forwaring message', message)
-
- return {
- to: [],
- cc: [],
- subject: buildFowardSubject(message.subject),
- body: buildReplyBody(
- message.bodyText,
- message.from[0],
- message.dateInt,
- )
- }
- } else {
- // New or mailto: message
-
- let accountId
- // Only preselect an account when we're not in a unified mailbox
- if (this.$route.params.accountId !== 0
- && this.$route.params.accountId !== '0') {
- accountId = parseInt(this.$route.params.accountId, 10)
- }
+import _ from 'lodash'
+
+import {buildFowardSubject, buildReplyBody} from '../ReplyBuilder'
+import Composer from './Composer'
+import {getRandomMessageErrorMessage} from '../util/ErrorMessageFactory'
+import Error from './Error'
+import Loading from './Loading'
+import {saveDraft, sendMessage} from '../service/MessageService'
+
+export default {
+ name: 'NewMessageDetail',
+ components: {
+ Composer,
+ Error,
+ Loading,
+ },
+ data() {
+ return {
+ loading: false,
+ draft: undefined,
+ errorMessage: '',
+ error: undefined,
+ }
+ },
+ computed: {
+ composerData() {
+ if (!_.isUndefined(this.draft)) {
+ console.info('todo: handle draft data')
+ return {
+ to: this.draft.to,
+ cc: this.draft.cc,
+ bcc: this.draft.bcc, // TODO: impl in composer
+ subject: this.draft.subject,
+ body: this.draft.body,
+ }
+ } else if (!_.isUndefined(this.$route.query.uid)) {
+ // Forwarded message
- return {
- accountId,
- to: this.stringToRecipients(this.$route.query.to),
- cc: this.stringToRecipients(this.$route.query.cc),
- subject: this.$route.query.subject || '',
- body: this.$route.query.body || '',
- }
+ const message = this.$store.getters.getMessageByUid(this.$route.query.uid)
+ console.debug('forwaring message', message)
+
+ return {
+ to: [],
+ cc: [],
+ subject: buildFowardSubject(message.subject),
+ body: buildReplyBody(message.bodyText, message.from[0], message.dateInt),
+ }
+ } else {
+ // New or mailto: message
+
+ let accountId
+ // Only preselect an account when we're not in a unified mailbox
+ if (this.$route.params.accountId !== 0 && this.$route.params.accountId !== '0') {
+ accountId = parseInt(this.$route.params.accountId, 10)
+ }
+
+ return {
+ accountId,
+ to: this.stringToRecipients(this.$route.query.to),
+ cc: this.stringToRecipients(this.$route.query.cc),
+ subject: this.$route.query.subject || '',
+ body: this.$route.query.body || '',
}
}
},
- created () {
+ },
+ watch: {
+ $route(to, from) {
+ // `saveDraft` replaced the current URL with the updated draft UID
+ // in that case we don't really start a new draft but just keep the
+ // URL consistent, hence not loading anything
+ if (this.draft && to.name === 'message' && to.params.draftUid === this.draft.uid) {
+ console.debug('detected navigation to current (new) draft UID, not reloading')
+ return
+ }
+
this.fetchMessage()
},
- watch: {
- '$route' (to, from) {
- // `saveDraft` replaced the current URL with the updated draft UID
- // in that case we don't really start a new draft but just keep the
- // URL consistent, hence not loading anything
- if (this.draft
- && to.name === 'message'
- && to.params.draftUid === this.draft.uid) {
- console.debug('detected navigation to current (new) draft UID, not reloading')
- return
- }
-
- this.fetchMessage()
+ },
+ created() {
+ this.fetchMessage()
+ },
+ methods: {
+ stringToRecipients(str) {
+ if (_.isUndefined(str)) {
+ return []
}
- },
- methods: {
- stringToRecipients (str) {
- if (_.isUndefined(str)) {
- return []
- }
- return [
- {
- label: str,
- email: str,
- }
- ]
- },
- fetchMessage () {
- this.draft = undefined
- this.errorMessage = ''
- this.error = undefined
-
- const draftUid = this.$route.params.draftUid
- if (_.isUndefined(draftUid)) {
- console.debug('not a draft, nothing to fetch')
- // Nothing to fetch
- return
- }
+ return [
+ {
+ label: str,
+ email: str,
+ },
+ ]
+ },
+ fetchMessage() {
+ this.draft = undefined
+ this.errorMessage = ''
+ this.error = undefined
+
+ const draftUid = this.$route.params.draftUid
+ if (_.isUndefined(draftUid)) {
+ console.debug('not a draft, nothing to fetch')
+ // Nothing to fetch
+ return
+ }
- this.loading = true
+ this.loading = true
- this.$store.dispatch('fetchMessage', draftUid)
- .then(draft => {
- if (draft.uid !== this.$route.params.draftUid) {
- console.debug('User navigated away, loaded draft won\'t be shown')
- return
- }
+ this.$store
+ .dispatch('fetchMessage', draftUid)
+ .then(draft => {
+ if (draft.uid !== this.$route.params.draftUid) {
+ console.debug("User navigated away, loaded draft won't be shown")
+ return
+ }
- this.draft = draft
+ this.draft = draft
- if (_.isUndefined(this.draft)) {
- console.info('draft could not be found', draftUid)
- this.errorMessage = getRandomMessageErrorMessage()
- this.loading = false
- return
- }
+ if (_.isUndefined(this.draft)) {
+ console.info('draft could not be found', draftUid)
+ this.errorMessage = getRandomMessageErrorMessage()
+ this.loading = false
+ return
+ }
+ this.loading = false
+ })
+ .catch(err => {
+ console.error('could not load draft ' + draftUid, err)
+ if (err.isError) {
+ this.errorMessage = t('mail', 'Could not load your draft')
+ this.error = err
this.loading = false
- })
- .catch(err => {
- console.error('could not load draft ' + draftUid, err)
- if (err.isError) {
- this.errorMessage = t('mail', 'Could not load your draft')
- this.error = err
- this.loading = false
- }
- })
- },
- saveDraft (data) {
- if (_.isUndefined(data.draftUID) && this.draft) {
- console.debug('draft data does not have a draftUID, adding one')
- data.draftUID = this.draft.id
- }
- return saveDraft(data.account, data)
- .then(({uid}) => {
- if (_.isUndefined(this.draft)) {
- return uid
- }
-
- console.info('replacing draft ' + this.draft.uid + ' with ' + uid)
- const update = {
- draft: this.draft,
- uid,
- data,
- }
- return this.$store.dispatch('replaceDraft', update)
- .then(() => this.$router.replace({
- name: 'message',
- params: {
- accountId: this.$route.params.accountId,
- folderId: this.$route.params.folderId,
- messageUid: 'new',
- draftUid: this.draft.uid,
- }
- }))
- .then(() => uid)
- })
- },
- sendMessage (data) {
- return sendMessage(data.account, data)
+ }
+ })
+ },
+ saveDraft(data) {
+ if (_.isUndefined(data.draftUID) && this.draft) {
+ console.debug('draft data does not have a draftUID, adding one')
+ data.draftUID = this.draft.id
}
- }
- }
+ return saveDraft(data.account, data).then(({uid}) => {
+ if (_.isUndefined(this.draft)) {
+ return uid
+ }
+
+ console.info('replacing draft ' + this.draft.uid + ' with ' + uid)
+ const update = {
+ draft: this.draft,
+ uid,
+ data,
+ }
+ return this.$store
+ .dispatch('replaceDraft', update)
+ .then(() =>
+ this.$router.replace({
+ name: 'message',
+ params: {
+ accountId: this.$route.params.accountId,
+ folderId: this.$route.params.folderId,
+ messageUid: 'new',
+ draftUid: this.draft.uid,
+ },
+ })
+ )
+ .then(() => uid)
+ })
+ },
+ sendMessage(data) {
+ return sendMessage(data.account, data)
+ },
+ },
+}
</script>