Merge pull request #2894 from nextcloud/enh/eslint

Use eslint config
This commit is contained in:
John Molakvoæ 2020-08-03 09:27:48 +02:00 коммит произвёл GitHub
Родитель d4941ad01a 5cda717371
Коммит 9a17879c65
Не найден ключ, соответствующий данной подписи
Идентификатор ключа GPG: 4AEE18F83AFDEB23
98 изменённых файлов: 5382 добавлений и 7159 удалений

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

@ -1,28 +1,7 @@
module.exports = { module.exports = {
root: true,
env: {
node: true,
amd: true,
jquery: true,
mocha: true,
},
extends: [ extends: [
'plugin:@nextcloud/recommended', '@nextcloud'
'plugin:prettier/recommended',
'plugin:vue/recommended',
'prettier/vue',
'eslint:recommended',
], ],
rules: {
'no-console': process.env.NODE_ENV === 'production' ? 'error' : 'off',
'no-debugger': process.env.NODE_ENV === 'production' ? 'error' : 'off',
'no-unused-vars': 'off',
'vue/no-v-html': 'off',
'no-case-declarations': 'off',
},
parserOptions: {
parser: 'babel-eslint',
},
globals: { globals: {
expect: true, expect: true,
OC: true, OC: true,

10644
package-lock.json сгенерированный

Разница между файлами не показана из-за своего большого размера Загрузить разницу

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

@ -9,8 +9,8 @@
"build": "NODE_ENV=production webpack --progress --hide-modules --config webpack.prod.js", "build": "NODE_ENV=production webpack --progress --hide-modules --config webpack.prod.js",
"dev": "NODE_ENV=development webpack --config webpack.dev.js", "dev": "NODE_ENV=development webpack --config webpack.dev.js",
"watch": "NODE_ENV=development webpack --progress --watch --config webpack.dev.js", "watch": "NODE_ENV=development webpack --progress --watch --config webpack.dev.js",
"lint": "eslint --ext .js,.vue src", "lint": "eslint --ext .js,.vue --ignore-pattern tests src",
"lint:autofix": "eslint --ext .js,.vue src --fix", "lint:fix": "eslint --ext .js,.vue --ignore-pattern tests src --fix",
"test": "mochapack --webpack-config webpack.test.js --require src/tests/setup.js \"src/tests/**/*.spec.js\"", "test": "mochapack --webpack-config webpack.test.js --require src/tests/setup.js \"src/tests/**/*.spec.js\"",
"test:watch": "mochapack -w --webpack-config webpack.test.js --require src/tests/setup.js \"src/tests/**/*.spec.js\"" "test:watch": "mochapack -w --webpack-config webpack.test.js --require src/tests/setup.js \"src/tests/**/*.spec.js\""
}, },
@ -53,6 +53,7 @@
"printscout": "2.0.3", "printscout": "2.0.3",
"ramda": "^0.27.0", "ramda": "^0.27.0",
"raw-loader": "^4.0.1", "raw-loader": "^4.0.1",
"stylelint": "^13.6.1",
"v-tooltip": "^2.0.3", "v-tooltip": "^2.0.3",
"vue": "^2.6.11", "vue": "^2.6.11",
"vue-autosize": "^1.0.2", "vue-autosize": "^1.0.2",
@ -76,16 +77,20 @@
"@babel/plugin-syntax-dynamic-import": "^7.8.3", "@babel/plugin-syntax-dynamic-import": "^7.8.3",
"@babel/preset-env": "^7.10.3", "@babel/preset-env": "^7.10.3",
"@nextcloud/browserslist-config": "^1.0.0", "@nextcloud/browserslist-config": "^1.0.0",
"@nextcloud/eslint-config": "^2.0.0",
"@nextcloud/eslint-plugin": "^1.4.0", "@nextcloud/eslint-plugin": "^1.4.0",
"@vue/test-utils": "^1.0.3", "@vue/test-utils": "^1.0.3",
"babel-eslint": "^10.1.0", "babel-eslint": "^10.1.0",
"babel-loader": "^8.1.0", "babel-loader": "^8.1.0",
"browserslist-config-nextcloud": "0.1.0",
"chai": "^4.2.0", "chai": "^4.2.0",
"css-loader": "^3.6.0", "css-loader": "^3.6.0",
"eslint": "^6.8.0", "eslint": "^6.8.0",
"eslint-config-prettier": "^6.11.0", "eslint-config-standard": "^14.1.1",
"eslint-plugin-prettier": "^3.1.4", "eslint-loader": "^4.0.2",
"eslint-plugin-import": "^2.22.0",
"eslint-plugin-node": "^11.1.0",
"eslint-plugin-promise": "^4.2.1",
"eslint-plugin-standard": "^4.0.1",
"eslint-plugin-vue": "^6.2.2", "eslint-plugin-vue": "^6.2.2",
"file-loader": "^6.0.0", "file-loader": "^6.0.0",
"jsdom": "^16.2.2", "jsdom": "^16.2.2",
@ -93,7 +98,6 @@
"mocha": "^7.2.0", "mocha": "^7.2.0",
"mochapack": "^1.1.15", "mochapack": "^1.1.15",
"node-sass": "^4.14.1", "node-sass": "^4.14.1",
"prettier": "2.0.5",
"sass-loader": "^8.0.2", "sass-loader": "^8.0.2",
"sinon": "^9.0.2", "sinon": "^9.0.2",
"sinon-chai": "^3.4.0", "sinon-chai": "^3.4.0",
@ -106,14 +110,5 @@
"webpack-cli": "^3.3.12", "webpack-cli": "^3.3.12",
"webpack-merge": "^4.2.2", "webpack-merge": "^4.2.2",
"webpack-node-externals": "^1.7.2" "webpack-node-externals": "^1.7.2"
},
"prettier": {
"bracketSpacing": false,
"singleQuote": true,
"semi": false,
"useTabs": true,
"printWidth": 120,
"tabWidth": 4,
"trailingComma": "es5"
} }
} }

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

@ -26,7 +26,7 @@
<script> <script>
import logger from './logger' import logger from './logger'
import {matchError} from './errors/match' import { matchError } from './errors/match'
import MailboxLockedError from './errors/MailboxLockedError' import MailboxLockedError from './errors/MailboxLockedError'
export default { export default {
@ -36,7 +36,7 @@ export default {
}, },
methods: { methods: {
sync() { sync() {
setTimeout(async () => { setTimeout(async() => {
try { try {
await this.$store.dispatch('syncInboxes') await this.$store.dispatch('syncInboxes')
@ -44,10 +44,10 @@ export default {
} catch (error) { } catch (error) {
matchError(error, { matchError(error, {
[MailboxLockedError.name](error) { [MailboxLockedError.name](error) {
logger.info('Background sync failed because a mailbox is locked', {error}) logger.info('Background sync failed because a mailbox is locked', { error })
}, },
default(error) { default(error) {
logger.error('Background sync failed: ' + error.message, {error}) logger.error('Background sync failed: ' + error.message, { error })
}, },
}) })
} finally { } finally {

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

@ -23,36 +23,34 @@
import moment from '@nextcloud/moment' import moment from '@nextcloud/moment'
import negate from 'lodash/fp/negate' import negate from 'lodash/fp/negate'
import {html} from './util/text' import { html } from './util/text'
/** /**
* @param {Text} original * @param {Text} original original
* @param {object} from * @param {object} from from
* @param {Number} date * @param {Number} date date
* @return {Text} * @returns {Text}
*/ */
export const buildReplyBody = (original, from, date) => { export const buildReplyBody = (original, from, date) => {
const start = '<p></p><p></p>' const start = '<p></p><p></p>'
const plainBody = '<br>&gt; ' + original.value.replace(/\n/g, '<br>&gt; ')
const htmlBody = `<blockquote>${original.value}</blockquote>`
switch (original.format) { switch (original.format) {
case 'plain': case 'plain':
const plainBody = '<br>&gt; ' + original.value.replace(/\n/g, '<br>&gt; ') if (from) {
const dateString = moment.unix(date).format('LLL')
if (from) { return html(`${start}"${from.label}" ${from.email}${dateString}` + plainBody)
const dateString = moment.unix(date).format('LLL') } else {
return html(`${start}"${from.label}" ${from.email}${dateString}` + plainBody) return html(`${start}${plainBody}`)
} else { }
return html(`${start}${plainBody}`) case 'html':
} if (from) {
case 'html': const dateString = moment.unix(date).format('LLL')
const htmlBody = `<blockquote>${original.value}</blockquote>` return html(`${start}"${from.label}" ${from.email}${dateString}<br>${htmlBody}`)
} else {
if (from) { return html(`${start}${htmlBody}`)
const dateString = moment.unix(date).format('LLL') }
return html(`${start}"${from.label}" ${from.email}${dateString}<br>${htmlBody}`)
} else {
return html(`${start}${htmlBody}`)
}
} }
throw new Error(`can't build a reply for the format ${original.format}`) throw new Error(`can't build a reply for the format ${original.format}`)
@ -108,7 +106,7 @@ export const buildRecipients = (envelope, ownAddress) => {
} }
// edge case: pure self-sent email // edge case: pure self-sent email
if (to.length == 0) { if (to.length === 0) {
to = envelope.from to = envelope.from
} }

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

@ -1,11 +1,10 @@
<template> <template>
<form id="account-form" @submit.prevent="onSubmit"> <form id="account-form" @submit.prevent="onSubmit">
<tabs <Tabs
:options="{useUrlFragment: false, defaultTabHash: settingsPage ? 'manual' : 'auto'}" :options="{useUrlFragment: false, defaultTabHash: settingsPage ? 'manual' : 'auto'}"
cache-lifetime="0" cache-lifetime="0"
@changed="onModeChanged" @changed="onModeChanged">
> <Tab id="auto" key="auto" :name="t('mail', 'Auto')">
<tab id="auto" key="auto" :name="t('mail', 'Auto')">
<label for="auto-name">{{ t('mail', 'Name') }}</label> <label for="auto-name">{{ t('mail', 'Name') }}</label>
<input <input
id="auto-name" id="auto-name"
@ -13,8 +12,7 @@
type="text" type="text"
:placeholder="t('mail', 'Name')" :placeholder="t('mail', 'Name')"
:disabled="loading" :disabled="loading"
autofocus autofocus>
/>
<label for="auto-address">{{ t('mail', 'Mail Address') }}</label> <label for="auto-address">{{ t('mail', 'Mail Address') }}</label>
<input <input
id="auto-address" id="auto-address"
@ -22,8 +20,7 @@
type="email" type="email"
:placeholder="t('mail', 'Mail Address')" :placeholder="t('mail', 'Mail Address')"
:disabled="loading" :disabled="loading"
required required>
/>
<label for="auto-password">{{ t('mail', 'Password') }}</label> <label for="auto-password">{{ t('mail', 'Password') }}</label>
<input <input
id="auto-password" id="auto-password"
@ -31,18 +28,16 @@
type="password" type="password"
:placeholder="t('mail', 'Password')" :placeholder="t('mail', 'Password')"
:disabled="loading" :disabled="loading"
required required>
/> </Tab>
</tab> <Tab id="manual" key="manual" :name="t('mail', 'Manual')">
<tab id="manual" key="manual" :name="t('mail', 'Manual')">
<label for="man-name">{{ t('mail', 'Name') }}</label> <label for="man-name">{{ t('mail', 'Name') }}</label>
<input <input
id="man-name" id="man-name"
v-model="manualConfig.accountName" v-model="manualConfig.accountName"
type="text" type="text"
:placeholder="t('mail', 'Name')" :placeholder="t('mail', 'Name')"
:disabled="loading" :disabled="loading">
/>
<label for="man-address">{{ t('mail', 'Mail Address') }}</label> <label for="man-address">{{ t('mail', 'Mail Address') }}</label>
<input <input
id="man-address" id="man-address"
@ -50,8 +45,7 @@
type="email" type="email"
:placeholder="t('mail', 'Mail Address')" :placeholder="t('mail', 'Mail Address')"
:disabled="loading" :disabled="loading"
required required>
/>
<h3>{{ t('mail', 'IMAP Settings') }}</h3> <h3>{{ t('mail', 'IMAP Settings') }}</h3>
<label for="man-imap-host">{{ t('mail', 'IMAP Host') }}</label> <label for="man-imap-host">{{ t('mail', 'IMAP Host') }}</label>
@ -61,8 +55,7 @@
type="text" type="text"
:placeholder="t('mail', 'IMAP Host')" :placeholder="t('mail', 'IMAP Host')"
:disabled="loading" :disabled="loading"
required required>
/>
<h4>{{ t('mail', 'IMAP Security') }}</h4> <h4>{{ t('mail', 'IMAP Security') }}</h4>
<div class="flex-row"> <div class="flex-row">
<input <input
@ -72,14 +65,11 @@
name="man-imap-sec" name="man-imap-sec"
:disabled="loading" :disabled="loading"
value="none" value="none"
@change="onImapSslModeChange" @change="onImapSslModeChange">
/>
<label <label
class="button" class="button"
for="man-imap-sec-none" for="man-imap-sec-none"
:class="{primary: manualConfig.imapSslMode === 'none'}" :class="{primary: manualConfig.imapSslMode === 'none'}">{{ t('mail', 'None') }}</label>
>{{ t('mail', 'None') }}</label
>
<input <input
id="man-imap-sec-ssl" id="man-imap-sec-ssl"
v-model="manualConfig.imapSslMode" v-model="manualConfig.imapSslMode"
@ -87,14 +77,11 @@
name="man-imap-sec" name="man-imap-sec"
:disabled="loading" :disabled="loading"
value="ssl" value="ssl"
@change="onImapSslModeChange" @change="onImapSslModeChange">
/>
<label <label
class="button" class="button"
for="man-imap-sec-ssl" for="man-imap-sec-ssl"
:class="{primary: manualConfig.imapSslMode === 'ssl'}" :class="{primary: manualConfig.imapSslMode === 'ssl'}">{{ t('mail', 'SSL/TLS') }}</label>
>{{ t('mail', 'SSL/TLS') }}</label
>
<input <input
id="man-imap-sec-tls" id="man-imap-sec-tls"
v-model="manualConfig.imapSslMode" v-model="manualConfig.imapSslMode"
@ -102,14 +89,11 @@
name="man-imap-sec" name="man-imap-sec"
:disabled="loading" :disabled="loading"
value="tls" value="tls"
@change="onImapSslModeChange" @change="onImapSslModeChange">
/>
<label <label
class="button" class="button"
for="man-imap-sec-tls" for="man-imap-sec-tls"
:class="{primary: manualConfig.imapSslMode === 'tls'}" :class="{primary: manualConfig.imapSslMode === 'tls'}">{{ t('mail', 'STARTTLS') }}</label>
>{{ t('mail', 'STARTTLS') }}</label
>
</div> </div>
<label for="man-imap-port">{{ t('mail', 'IMAP Port') }}</label> <label for="man-imap-port">{{ t('mail', 'IMAP Port') }}</label>
<input <input
@ -118,8 +102,7 @@
type="number" type="number"
:placeholder="t('mail', 'IMAP Port')" :placeholder="t('mail', 'IMAP Port')"
:disabled="loading" :disabled="loading"
required required>
/>
<label for="man-imap-user">{{ t('mail', 'IMAP User') }}</label> <label for="man-imap-user">{{ t('mail', 'IMAP User') }}</label>
<input <input
id="man-imap-user" id="man-imap-user"
@ -127,8 +110,7 @@
type="text" type="text"
:placeholder="t('mail', 'IMAP User')" :placeholder="t('mail', 'IMAP User')"
:disabled="loading" :disabled="loading"
required required>
/>
<label for="man-imap-password">{{ t('mail', 'IMAP Password') }}</label> <label for="man-imap-password">{{ t('mail', 'IMAP Password') }}</label>
<input <input
id="man-imap-password" id="man-imap-password"
@ -136,8 +118,7 @@
type="password" type="password"
:placeholder="t('mail', 'IMAP Password')" :placeholder="t('mail', 'IMAP Password')"
:disabled="loading" :disabled="loading"
required required>
/>
<h3>{{ t('mail', 'SMTP Settings') }}</h3> <h3>{{ t('mail', 'SMTP Settings') }}</h3>
<input <input
@ -147,8 +128,7 @@
name="smtp-host" name="smtp-host"
:placeholder="t('mail', 'SMTP Host')" :placeholder="t('mail', 'SMTP Host')"
:disabled="loading" :disabled="loading"
required required>
/>
<h4>{{ t('mail', 'SMTP Security') }}</h4> <h4>{{ t('mail', 'SMTP Security') }}</h4>
<div class="flex-row"> <div class="flex-row">
<input <input
@ -158,14 +138,11 @@
name="man-smtp-sec" name="man-smtp-sec"
:disabled="loading" :disabled="loading"
value="none" value="none"
@change="onSmtpSslModeChange" @change="onSmtpSslModeChange">
/>
<label <label
class="button" class="button"
for="man-smtp-sec-none" for="man-smtp-sec-none"
:class="{primary: manualConfig.smtpSslMode === 'none'}" :class="{primary: manualConfig.smtpSslMode === 'none'}">{{ t('mail', 'None') }}</label>
>{{ t('mail', 'None') }}</label
>
<input <input
id="man-smtp-sec-ssl" id="man-smtp-sec-ssl"
v-model="manualConfig.smtpSslMode" v-model="manualConfig.smtpSslMode"
@ -173,14 +150,11 @@
name="man-smtp-sec" name="man-smtp-sec"
:disabled="loading" :disabled="loading"
value="ssl" value="ssl"
@change="onSmtpSslModeChange" @change="onSmtpSslModeChange">
/>
<label <label
class="button" class="button"
for="man-smtp-sec-ssl" for="man-smtp-sec-ssl"
:class="{primary: manualConfig.smtpSslMode === 'ssl'}" :class="{primary: manualConfig.smtpSslMode === 'ssl'}">{{ t('mail', 'SSL/TLS') }}</label>
>{{ t('mail', 'SSL/TLS') }}</label
>
<input <input
id="man-smtp-sec-tls" id="man-smtp-sec-tls"
v-model="manualConfig.smtpSslMode" v-model="manualConfig.smtpSslMode"
@ -188,14 +162,11 @@
name="man-smtp-sec" name="man-smtp-sec"
:disabled="loading" :disabled="loading"
value="tls" value="tls"
@change="onSmtpSslModeChange" @change="onSmtpSslModeChange">
/>
<label <label
class="button" class="button"
for="man-smtp-sec-tls" for="man-smtp-sec-tls"
:class="{primary: manualConfig.smtpSslMode === 'tls'}" :class="{primary: manualConfig.smtpSslMode === 'tls'}">{{ t('mail', 'STARTTLS') }}</label>
>{{ t('mail', 'STARTTLS') }}</label
>
</div> </div>
<label for="man-smtp-port">{{ t('mail', 'SMTP Port') }}</label> <label for="man-smtp-port">{{ t('mail', 'SMTP Port') }}</label>
<input <input
@ -204,8 +175,7 @@
type="number" type="number"
:placeholder="t('mail', 'SMTP Port')" :placeholder="t('mail', 'SMTP Port')"
:disabled="loading" :disabled="loading"
required required>
/>
<label for="man-smtp-user">{{ t('mail', 'SMTP User') }}</label> <label for="man-smtp-user">{{ t('mail', 'SMTP User') }}</label>
<input <input
id="man-smtp-user" id="man-smtp-user"
@ -213,8 +183,7 @@
type="text" type="text"
:placeholder="t('mail', 'SMTP User')" :placeholder="t('mail', 'SMTP User')"
:disabled="loading" :disabled="loading"
required required>
/>
<label for="man-smtp-password">{{ t('mail', 'SMTP Password') }}</label> <label for="man-smtp-password">{{ t('mail', 'SMTP Password') }}</label>
<input <input
id="man-smtp-password" id="man-smtp-password"
@ -222,17 +191,20 @@
type="password" type="password"
:placeholder="t('mail', 'SMTP Password')" :placeholder="t('mail', 'SMTP Password')"
:disabled="loading" :disabled="loading"
required required>
/> </Tab>
</tab> </Tabs>
</tabs> <slot name="feedback" />
<slot name="feedback"></slot> <input type="submit"
<input type="submit" class="primary" :disabled="loading" :value="submitButtonText" @click.prevent="onSubmit" /> class="primary"
:disabled="loading"
:value="submitButtonText"
@click.prevent="onSubmit">
</form> </form>
</template> </template>
<script> <script>
import {Tab, Tabs} from 'vue-tabs-component' import { Tab, Tabs } from 'vue-tabs-component'
import logger from '../logger' import logger from '../logger'
@ -331,24 +303,24 @@ export default {
}, },
onImapSslModeChange() { onImapSslModeChange() {
switch (this.manualConfig.imapSslMode) { switch (this.manualConfig.imapSslMode) {
case 'none': case 'none':
case 'tls': case 'tls':
this.manualConfig.imapPort = 143 this.manualConfig.imapPort = 143
break break
case 'ssl': case 'ssl':
this.manualConfig.imapPort = 993 this.manualConfig.imapPort = 993
break break
} }
}, },
onSmtpSslModeChange() { onSmtpSslModeChange() {
switch (this.manualConfig.smtpSslMode) { switch (this.manualConfig.smtpSslMode) {
case 'none': case 'none':
case 'tls': case 'tls':
this.manualConfig.smtpPort = 587 this.manualConfig.smtpPort = 587
break break
case 'ssl': case 'ssl':
this.manualConfig.smtpPort = 465 this.manualConfig.smtpPort = 465
break break
} }
}, },
saveChanges() { saveChanges() {
@ -365,12 +337,12 @@ export default {
} }
}, },
onSubmit(event) { onSubmit(event) {
console.debug('account form submitted', {event}) console.debug('account form submitted', { event })
this.loading = true this.loading = true
this.saveChanges() this.saveChanges()
.catch((error) => logger.error('could not save account details', {error})) .catch((error) => logger.error('could not save account details', { error }))
.then(() => (this.loading = false)) .then(() => (this.loading = false))
}, },
}, },

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

@ -1,6 +1,13 @@
<template> <template>
<router-link v-if="email !== label" v-tooltip.bottom="email" :to="newMessageRoute" exact>{{ label }}</router-link> <router-link v-if="email !== label"
<router-link v-else :to="newMessageRoute" exact>{{ label }}</router-link> v-tooltip.bottom="email"
:to="newMessageRoute"
exact>
{{ label }}
</router-link>
<router-link v-else :to="newMessageRoute" exact>
{{ label }}
</router-link>
</template> </template>
<script> <script>

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

@ -23,7 +23,7 @@
<li v-for="curAlias in aliases" :key="curAlias.id"> <li v-for="curAlias in aliases" :key="curAlias.id">
<strong>{{ curAlias.name }}</strong> &lt;{{ curAlias.alias }}&gt; <strong>{{ curAlias.name }}</strong> &lt;{{ curAlias.alias }}&gt;
<button class="icon-delete" @click="deleteAlias(curAlias)"></button> <button class="icon-delete" @click="deleteAlias(curAlias)" />
</li> </li>
</ul> </ul>
@ -34,8 +34,7 @@
v-model="alias.aliasName" v-model="alias.aliasName"
type="text" type="text"
:placeholder="t('mail', 'Name')" :placeholder="t('mail', 'Name')"
:disabled="loading" :disabled="loading">
/>
<input <input
v-if="addMode" v-if="addMode"
@ -44,8 +43,7 @@
v-model="alias.alias" v-model="alias.alias"
type="email" type="email"
:placeholder="t('mail', 'Mail Address')" :placeholder="t('mail', 'Mail Address')"
:disabled="loading" :disabled="loading">
/>
</div> </div>
<div> <div>
@ -58,8 +56,7 @@
class="primary" class="primary"
:class="loading ? 'icon-loading-small-dark' : 'icon-checkmark-white'" :class="loading ? 'icon-loading-small-dark' : 'icon-checkmark-white'"
:disabled="loading" :disabled="loading"
@click="saveAlias" @click="saveAlias">
>
{{ t('mail', 'Save') }} {{ t('mail', 'Save') }}
</button> </button>
</div> </div>
@ -82,7 +79,7 @@ export default {
return { return {
addMode: false, addMode: false,
loading: false, loading: false,
alias: {aliasName: this.account.name, alias: ''}, alias: { aliasName: this.account.name, alias: '' },
} }
}, },
computed: { computed: {
@ -100,15 +97,15 @@ export default {
}, },
async deleteAlias(alias) { async deleteAlias(alias) {
this.loading = true this.loading = true
await this.$store.dispatch('deleteAlias', {account: this.account, aliasToDelete: alias}) await this.$store.dispatch('deleteAlias', { account: this.account, aliasToDelete: alias })
logger.info('alias deleted') logger.info('alias deleted')
this.loading = false this.loading = false
}, },
async saveAlias() { async saveAlias() {
this.loading = true this.loading = true
await this.$store.dispatch('createAlias', {account: this.account, aliasToAdd: this.alias}) await this.$store.dispatch('createAlias', { account: this.account, aliasToAdd: this.alias })
logger.info('alias added') logger.info('alias added')
this.alias = {aliasName: this.account.name, alias: ''} this.alias = { aliasName: this.account.name, alias: '' }
this.loading = false this.loading = false
}, },
}, },

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

@ -20,7 +20,10 @@
--> -->
<template> <template>
<div v-if="isMobile" class="toggle icon-confirm" tabindex="0" @click="$emit('close')"></div> <div v-if="isMobile"
class="toggle icon-confirm"
tabindex="0"
@click="$emit('close')" />
</template> </template>
<script> <script>

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

@ -5,7 +5,7 @@
</router-link> </router-link>
<p v-if="loadingOptOutSettings" class="app-settings"> <p v-if="loadingOptOutSettings" class="app-settings">
<span class="icon-loading-small"></span> <span class="icon-loading-small" />
{{ text }} {{ text }}
</p> </p>
<p v-else class="app-settings"> <p v-else class="app-settings">
@ -14,13 +14,12 @@
class="checkbox" class="checkbox"
type="checkbox" type="checkbox"
:checked="useDataCollection" :checked="useDataCollection"
@change="onToggleCollectData" @change="onToggleCollectData">
/>
<label for="data-collection-toggle">{{ text }}</label> <label for="data-collection-toggle">{{ text }}</label>
</p> </p>
<p v-if="loadingAvatarSettings" class="app-settings avatar-settings"> <p v-if="loadingAvatarSettings" class="app-settings avatar-settings">
<span class="icon-loading-small"></span> <span class="icon-loading-small" />
{{ t('mail', 'Use Gravatar and favicon avatars') }} {{ t('mail', 'Use Gravatar and favicon avatars') }}
</p> </p>
<p v-else class="app-settings"> <p v-else class="app-settings">
@ -29,8 +28,7 @@
class="checkbox" class="checkbox"
type="checkbox" type="checkbox"
:checked="useExternalAvatars" :checked="useExternalAvatars"
@change="onToggleExternalAvatars" @change="onToggleExternalAvatars">
/>
<label for="gravatar-enabled">{{ t('mail', 'Use Gravatar and favicon avatars') }}</label> <label for="gravatar-enabled">{{ t('mail', 'Use Gravatar and favicon avatars') }}</label>
</p> </p>
<p> <p>
@ -48,7 +46,7 @@
<script> <script>
import Logger from '../logger' import Logger from '../logger'
import {generateUrl} from '@nextcloud/router' import { generateUrl } from '@nextcloud/router'
export default { export default {
name: 'AppSettingsMenu', name: 'AppSettingsMenu',
@ -77,7 +75,7 @@ export default {
key: 'external-avatars', key: 'external-avatars',
value: e.target.checked ? 'true' : 'false', value: e.target.checked ? 'true' : 'false',
}) })
.catch((error) => Logger.error('could not save preferences', {error})) .catch((error) => Logger.error('could not save preferences', { error }))
.then(() => { .then(() => {
this.loadingAvatarSettings = false this.loadingAvatarSettings = false
}) })
@ -90,19 +88,19 @@ export default {
key: 'collect-data', key: 'collect-data',
value: e.target.checked ? 'true' : 'false', value: e.target.checked ? 'true' : 'false',
}) })
.catch((error) => Logger.error('could not save preferences', {error})) .catch((error) => Logger.error('could not save preferences', { error }))
.then(() => { .then(() => {
this.loadingOptOutSettings = false this.loadingOptOutSettings = false
}) })
}, },
registerProtocolHandler: function () { registerProtocolHandler() {
if (window.navigator.registerProtocolHandler) { if (window.navigator.registerProtocolHandler) {
var url = const url
window.location.protocol + '//' + window.location.host + generateUrl('apps/mail/compose?uri=%s') = window.location.protocol + '//' + window.location.host + generateUrl('apps/mail/compose?uri=%s')
try { try {
window.navigator.registerProtocolHandler('mailto', url, OC.theme.name + ' Mail') window.navigator.registerProtocolHandler('mailto', url, OC.theme.name + ' Mail')
} catch (err) { } catch (err) {
Logger.error('could not register protocol handler', {err}) Logger.error('could not register protocol handler', { err })
} }
} }
}, },

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

@ -21,13 +21,16 @@
<template> <template>
<BaseAvatar v-if="loading || !hasAvatar" :display-name="displayName" :size="40" /> <BaseAvatar v-if="loading || !hasAvatar" :display-name="displayName" :size="40" />
<BaseAvatar v-else :display-name="displayName" :url="avatarUrl" :size="40" /> <BaseAvatar v-else
:display-name="displayName"
:url="avatarUrl"
:size="40" />
</template> </template>
<script> <script>
import BaseAvatar from '@nextcloud/vue/dist/Components/Avatar' import BaseAvatar from '@nextcloud/vue/dist/Components/Avatar'
import {fetchAvatarUrlMemoized} from '../service/AvatarService' import { fetchAvatarUrlMemoized } from '../service/AvatarService'
export default { export default {
name: 'Avatar', name: 'Avatar',

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

@ -15,8 +15,7 @@
:custom-label="formatAliases" :custom-label="formatAliases"
:placeholder="t('mail', 'Select account')" :placeholder="t('mail', 'Select account')"
:clear-on-select="false" :clear-on-select="false"
@keyup="onInputChanged" @keyup="onInputChanged" />
/>
</div> </div>
<div class="composer-fields"> <div class="composer-fields">
<label class="to-label" for="to"> <label class="to-label" for="to">
@ -37,9 +36,11 @@
:preserve-search="true" :preserve-search="true"
@keyup="onInputChanged" @keyup="onInputChanged"
@tag="onNewToAddr" @tag="onNewToAddr"
@search-change="onAutocomplete" @search-change="onAutocomplete" />
/> <a v-if="!showCC"
<a v-if="!showCC" class="copy-toggle" href="#" @click.prevent="showCC = true"> class="copy-toggle"
href="#"
@click.prevent="showCC = true">
{{ t('mail', '+ Cc/Bcc') }} {{ t('mail', '+ Cc/Bcc') }}
</a> </a>
</div> </div>
@ -61,8 +62,7 @@
:preserve-search="true" :preserve-search="true"
@keyup="onInputChanged" @keyup="onInputChanged"
@tag="onNewCcAddr" @tag="onNewCcAddr"
@search-change="onAutocomplete" @search-change="onAutocomplete">
>
<span slot="noOptions">{{ t('mail', 'No contacts found.') }}</span> <span slot="noOptions">{{ t('mail', 'No contacts found.') }}</span>
</Multiselect> </Multiselect>
</div> </div>
@ -83,8 +83,7 @@
:preserve-search="true" :preserve-search="true"
@keyup="onInputChanged" @keyup="onInputChanged"
@tag="onNewBccAddr" @tag="onNewBccAddr"
@search-change="onAutocomplete" @search-change="onAutocomplete">
>
<span slot="noOptions">{{ t('mail', 'No contacts found.') }}</span> <span slot="noOptions">{{ t('mail', 'No contacts found.') }}</span>
</Multiselect> </Multiselect>
</div> </div>
@ -100,8 +99,7 @@
class="subject" class="subject"
autocomplete="off" autocomplete="off"
:placeholder="t('mail', 'Subject …')" :placeholder="t('mail', 'Subject …')"
@keyup="onInputChanged" @keyup="onInputChanged">
/>
</div> </div>
<div v-if="noReply" class="warning noreply-warning"> <div v-if="noReply" class="warning noreply-warning">
{{ t('mail', 'This message came from a noreply address so your reply will probably not be read.') }} {{ t('mail', 'This message came from a noreply address so your reply will probably not be read.') }}
@ -124,8 +122,7 @@
:placeholder="t('mail', 'Write message …')" :placeholder="t('mail', 'Write message …')"
:focus="isReply" :focus="isReply"
:bus="bus" :bus="bus"
@input="onInputChanged" @input="onInputChanged" />
></TextEditor>
<TextEditor <TextEditor
v-else-if="!encrypt && !editorPlainText" v-else-if="!encrypt && !editorPlainText"
key="editor-rich" key="editor-rich"
@ -136,16 +133,14 @@
:placeholder="t('mail', 'Write message …')" :placeholder="t('mail', 'Write message …')"
:focus="isReply" :focus="isReply"
:bus="bus" :bus="bus"
@input="onInputChanged" @input="onInputChanged" />
></TextEditor>
<MailvelopeEditor <MailvelopeEditor
v-else v-else
ref="mailvelopeEditor" ref="mailvelopeEditor"
v-model="bodyVal" v-model="bodyVal"
:recipients="allRecipients" :recipients="allRecipients"
:quoted-text="body" :quoted-text="body"
:is-reply-or-forward="isReply || isForward" :is-reply-or-forward="isReply || isForward" />
/>
</div> </div>
<div class="composer-actions"> <div class="composer-actions">
<ComposerAttachments v-model="attachments" :bus="bus" @upload="onAttachmentsUploading" /> <ComposerAttachments v-model="attachments" :bus="bus" @upload="onAttachmentsUploading" />
@ -155,32 +150,43 @@
<span v-else-if="savingDraft === false" id="draft-status">{{ t('mail', 'Draft saved') }}</span> <span v-else-if="savingDraft === false" id="draft-status">{{ t('mail', 'Draft saved') }}</span>
</p> </p>
<Actions> <Actions>
<ActionButton icon="icon-upload" @click="onAddLocalAttachment">{{ <ActionButton icon="icon-upload" @click="onAddLocalAttachment">
t('mail', 'Upload attachment') {{
}}</ActionButton> t('mail', 'Upload attachment')
<ActionButton icon="icon-folder" @click="onAddCloudAttachment">{{ }}
t('mail', 'Add attachment from Files') </ActionButton>
}}</ActionButton> <ActionButton icon="icon-folder" @click="onAddCloudAttachment">
<ActionButton :disabled="encrypt" icon="icon-folder" @click="onAddCloudAttachmentLink">{{ {{
t('mail', 'Add attachment link from Files') t('mail', 'Add attachment from Files')
}}</ActionButton> }}
</ActionButton>
<ActionButton :disabled="encrypt" icon="icon-folder" @click="onAddCloudAttachmentLink">
{{
t('mail', 'Add attachment link from Files')
}}
</ActionButton>
<ActionCheckbox <ActionCheckbox
:checked="!encrypt && !editorPlainText" :checked="!encrypt && !editorPlainText"
:disabled="encrypt" :disabled="encrypt"
@check="editorMode = 'html'" @check="editorMode = 'html'"
@uncheck="editorMode = 'plaintext'" @uncheck="editorMode = 'plaintext'">
>{{ t('mail', 'Enable formatting') }}</ActionCheckbox {{ t('mail', 'Enable formatting') }}
> </ActionCheckbox>
<ActionCheckbox <ActionCheckbox
v-if="mailvelope.available" v-if="mailvelope.available"
:checked="encrypt" :checked="encrypt"
@check="encrypt = true" @check="encrypt = true"
@uncheck="encrypt = false" @uncheck="encrypt = false">
>{{ t('mail', 'Encrypt message with Mailvelope') }}</ActionCheckbox {{ t('mail', 'Encrypt message with Mailvelope') }}
> </ActionCheckbox>
<ActionLink v-else href="https://www.mailvelope.com/" target="_blank" icon="icon-password">{{ <ActionLink v-else
t('mail', 'Looking for a way to encrypt your emails? Install the Mailvelope browser extension!') href="https://www.mailvelope.com/"
}}</ActionLink> target="_blank"
icon="icon-password">
{{
t('mail', 'Looking for a way to encrypt your emails? Install the Mailvelope browser extension!')
}}
</ActionLink>
</Actions> </Actions>
<div> <div>
<input <input
@ -188,8 +194,7 @@
type="submit" type="submit"
:value="submitButtonTitle" :value="submitButtonTitle"
:disabled="!canSend" :disabled="!canSend"
@click="onSend" @click="onSend">
/>
</div> </div>
</div> </div>
</div> </div>
@ -198,9 +203,15 @@
<Loading v-else-if="state === STATES.SENDING" :hint="t('mail', 'Sending …')" /> <Loading v-else-if="state === STATES.SENDING" :hint="t('mail', 'Sending …')" />
<div v-else-if="state === STATES.ERROR" class="emptycontent"> <div v-else-if="state === STATES.ERROR" class="emptycontent">
<h2>{{ t('mail', 'Error sending your message') }}</h2> <h2>{{ t('mail', 'Error sending your message') }}</h2>
<p v-if="errorText">{{ errorText }}</p> <p v-if="errorText">
<button class="button" @click="state = STATES.EDITING">{{ t('mail', 'Go back') }}</button> {{ errorText }}
<button class="button primary" @click="onSend">{{ t('mail', 'Retry') }}</button> </p>
<button class="button" @click="state = STATES.EDITING">
{{ t('mail', 'Go back') }}
</button>
<button class="button primary" @click="onSend">
{{ t('mail', 'Retry') }}
</button>
</div> </div>
<div v-else class="emptycontent"> <div v-else class="emptycontent">
<h2>{{ t('mail', 'Message sent!') }}</h2> <h2>{{ t('mail', 'Message sent!') }}</h2>
@ -220,19 +231,19 @@ import ActionButton from '@nextcloud/vue/dist/Components/ActionButton'
import ActionCheckbox from '@nextcloud/vue/dist/Components/ActionCheckbox' import ActionCheckbox from '@nextcloud/vue/dist/Components/ActionCheckbox'
import ActionLink from '@nextcloud/vue/dist/Components/ActionLink' import ActionLink from '@nextcloud/vue/dist/Components/ActionLink'
import Multiselect from '@nextcloud/vue/dist/Components/Multiselect' import Multiselect from '@nextcloud/vue/dist/Components/Multiselect'
import {translate as t} from '@nextcloud/l10n' import { translate as t } from '@nextcloud/l10n'
import Vue from 'vue' import Vue from 'vue'
import ComposerAttachments from './ComposerAttachments' import ComposerAttachments from './ComposerAttachments'
import {findRecipient} from '../service/AutocompleteService' import { findRecipient } from '../service/AutocompleteService'
import {detect, html, plain, toHtml, toPlain} from '../util/text' import { detect, html, plain, toHtml, toPlain } from '../util/text'
import Loading from './Loading' import Loading from './Loading'
import logger from '../logger' import logger from '../logger'
import TextEditor from './TextEditor' import TextEditor from './TextEditor'
import {buildReplyBody} from '../ReplyBuilder' import { buildReplyBody } from '../ReplyBuilder'
import MailvelopeEditor from './MailvelopeEditor' import MailvelopeEditor from './MailvelopeEditor'
import {getMailvelope} from '../crypto/mailvelope' import { getMailvelope } from '../crypto/mailvelope'
import {isPgpgMessage} from '../crypto/pgp' import { isPgpgMessage } from '../crypto/pgp'
const debouncedSearch = debouncePromise(findRecipient, 500) const debouncedSearch = debouncePromise(findRecipient, 500)
@ -367,7 +378,7 @@ export default {
selectableRecipients() { selectableRecipients() {
return this.newRecipients return this.newRecipients
.concat(this.autocompleteRecipients) .concat(this.autocompleteRecipients)
.map((recipient) => ({...recipient, label: recipient.label || recipient.email})) .map((recipient) => ({ ...recipient, label: recipient.label || recipient.email }))
}, },
isForward() { isForward() {
return this.forwardFrom !== undefined return this.forwardFrom !== undefined
@ -445,7 +456,7 @@ export default {
const recipients = this.allRecipients.map((r) => r.email) const recipients = this.allRecipients.map((r) => r.email)
const keysValid = await this.mailvelope.keyRing.validKeyForAddress(recipients) const keysValid = await this.mailvelope.keyRing.validKeyForAddress(recipients)
logger.debug('recipients keys validated', {recipients, keysValid}) logger.debug('recipients keys validated', { recipients, keysValid })
this.mailvelope.keysMissing = recipients.filter((r) => keysValid[r] === false) this.mailvelope.keysMissing = recipients.filter((r) => keysValid[r] === false)
}, },
initBody() { initBody() {
@ -508,12 +519,12 @@ export default {
.then((uid) => { .then((uid) => {
const draftData = data(uid) const draftData = data(uid)
if ( if (
!uid && !uid
!draftData.subject && && !draftData.subject
!draftData.body && && !draftData.body
!draftData.cc && && !draftData.cc
!draftData.bcc && && !draftData.bcc
!draftData.to && !draftData.to
) { ) {
// this might happen after a call to reset() // this might happen after a call to reset()
// where the text input gets reset as well // where the text input gets reset as well
@ -554,7 +565,7 @@ export default {
onAttachmentsUploading(uploaded) { onAttachmentsUploading(uploaded) {
this.attachmentsPromise = this.attachmentsPromise this.attachmentsPromise = this.attachmentsPromise
.then(() => uploaded) .then(() => uploaded)
.catch((error) => logger.error('could not upload attachments', {error})) .catch((error) => logger.error('could not upload attachments', { error }))
.then(() => logger.debug('attachments uploaded')) .then(() => logger.debug('attachments uploaded'))
}, },
async onMailvelopeLoaded(mailvelope) { async onMailvelopeLoaded(mailvelope) {
@ -601,7 +612,7 @@ export default {
.then(() => logger.info('message sent')) .then(() => logger.info('message sent'))
.then(() => (this.state = STATES.FINISHED)) .then(() => (this.state = STATES.FINISHED))
.catch((error) => { .catch((error) => {
logger.error('could not send message', {error}) logger.error('could not send message', { error })
if (error && error.toString) { if (error && error.toString) {
this.errorText = error.toString() this.errorText = error.toString()
} }
@ -632,6 +643,7 @@ export default {
}, },
/** /**
* Format aliases for the Multiselect * Format aliases for the Multiselect
* @param {Object} alias the alias to format
* @returns {string} * @returns {string}
*/ */
formatAliases(alias) { formatAliases(alias) {

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

@ -27,29 +27,33 @@
<div class="new-message-attachment-name"> <div class="new-message-attachment-name">
{{ attachment.displayName }} {{ attachment.displayName }}
</div> </div>
<div class="new-message-attachments-action svg icon-delete" @click="onDelete(attachment)"></div> <div class="new-message-attachments-action svg icon-delete" @click="onDelete(attachment)" />
</li> </li>
<li v-if="uploading" class="attachments-upload-progress"> <li v-if="uploading" class="attachments-upload-progress">
<div :class="{'icon-loading-small': uploading}"></div> <div :class="{'icon-loading-small': uploading}" />
<div>{{ uploading ? t('mail', 'Uploading {percent}% …', {percent: uploadProgress}) : '' }}</div> <div>{{ uploading ? t('mail', 'Uploading {percent}% …', {percent: uploadProgress}) : '' }}</div>
</li> </li>
</ul> </ul>
<input ref="localAttachments" type="file" multiple style="display: none;" @change="onLocalAttachmentSelected" /> <input ref="localAttachments"
type="file"
multiple
style="display: none;"
@change="onLocalAttachmentSelected">
</div> </div>
</template> </template>
<script> <script>
import map from 'lodash/fp/map' import map from 'lodash/fp/map'
import trimStart from 'lodash/fp/trimCharsStart' import trimStart from 'lodash/fp/trimCharsStart'
import {getRequestToken} from '@nextcloud/auth' import { getRequestToken } from '@nextcloud/auth'
import {translate as t} from '@nextcloud/l10n' import { translate as t } from '@nextcloud/l10n'
import {getFilePickerBuilder} from '@nextcloud/dialogs' import { getFilePickerBuilder } from '@nextcloud/dialogs'
import Vue from 'vue' import Vue from 'vue'
import Logger from '../logger' import Logger from '../logger'
import {uploadLocalAttachment} from '../service/AttachmentService' import { uploadLocalAttachment } from '../service/AttachmentService'
import {shareFile} from '../service/FileSharingService' import { shareFile } from '../service/FileSharingService'
export default { export default {
name: 'ComposerAttachments', name: 'ComposerAttachments',
@ -73,7 +77,7 @@ export default {
uploadProgress() { uploadProgress() {
let uploaded = 0 let uploaded = 0
let total = 0 let total = 0
for (let id in this.uploads) { for (const id in this.uploads) {
uploaded += this.uploads[id].uploaded uploaded += this.uploads[id].uploaded
total += this.uploads[id].total total += this.uploads[id].total
} }
@ -115,14 +119,14 @@ export default {
uploaded: 0, uploaded: 0,
}) })
return uploadLocalAttachment(file, progress(file.name)).then(({file, id}) => { return uploadLocalAttachment(file, progress(file.name)).then(({ file, id }) => {
Logger.info('uploaded') Logger.info('uploaded')
return this.emitNewAttachment(this.fileNameToAttachment(file.name, id)) return this.emitNewAttachment(this.fileNameToAttachment(file.name, id))
}) })
})(e.target.files) })(e.target.files)
const done = Promise.all(promises) const done = Promise.all(promises)
.catch((error) => Logger.error('could not upload all attachments', {error})) .catch((error) => Logger.error('could not upload all attachments', { error }))
.then(() => (this.uploading = false)) .then(() => (this.uploading = false))
this.$emit('upload', done) this.$emit('upload', done)
@ -135,19 +139,19 @@ export default {
return picker return picker
.pick(t('mail', 'Choose a file to add as attachment')) .pick(t('mail', 'Choose a file to add as attachment'))
.then((path) => this.emitNewAttachment(this.fileNameToAttachment(path))) .then((path) => this.emitNewAttachment(this.fileNameToAttachment(path)))
.catch((error) => Logger.error('could not choose a file as attachment', {error})) .catch((error) => Logger.error('could not choose a file as attachment', { error }))
}, },
onAddCloudAttachmentLink() { onAddCloudAttachmentLink() {
const picker = getFilePickerBuilder(t('mail', 'Choose a file to share as a link')).build() const picker = getFilePickerBuilder(t('mail', 'Choose a file to share as a link')).build()
return picker return picker
.pick(t('mail', 'Choose a file to share as a link')) .pick(t('mail', 'Choose a file to share as a link'))
.then(async (path) => { .then(async(path) => {
const url = await shareFile(path, getRequestToken()) const url = await shareFile(path, getRequestToken())
return this.appendToBodyAtCursor(`<a href="${url}">${url}</a>`) return this.appendToBodyAtCursor(`<a href="${url}">${url}</a>`)
}) })
.catch((error) => Logger.error('could not choose a file as attachment link', {error})) .catch((error) => Logger.error('could not choose a file as attachment link', { error }))
}, },
onDelete(attachment) { onDelete(attachment) {
this.$emit( this.$emit(

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

@ -26,11 +26,19 @@
{{ t('mail', 'Preferred writing mode for new messages and replies.') }} {{ t('mail', 'Preferred writing mode for new messages and replies.') }}
</p> </p>
<p> <p>
<input id="plaintext" v-model="mode" type="radio" class="radio" value="plaintext" /> <input id="plaintext"
v-model="mode"
type="radio"
class="radio"
value="plaintext">
<label :class="{primary: mode === 'plaintext'}" for="plaintext"> <label :class="{primary: mode === 'plaintext'}" for="plaintext">
{{ t('mail', 'Plain text') }} {{ t('mail', 'Plain text') }}
</label> </label>
<input id="richtext" v-model="mode" type="radio" class="radio" value="richtext" /> <input id="richtext"
v-model="mode"
type="radio"
class="radio"
value="richtext">
<label :class="{primary: mode === 'richtext'}" for="richtext"> <label :class="{primary: mode === 'richtext'}" for="richtext">
{{ t('mail', 'Rich text') }} {{ t('mail', 'Rich text') }}
</label> </label>
@ -67,7 +75,7 @@ export default {
Logger.info('editor mode updated') Logger.info('editor mode updated')
}) })
.catch((error) => { .catch((error) => {
Logger.error('could not update editor mode', {error}) Logger.error('could not update editor mode', { error })
this.editorMode = oldVal this.editorMode = oldVal
throw error throw error
}) })

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

@ -1,6 +1,6 @@
<template> <template>
<div id="emptycontent"> <div id="emptycontent">
<div class="icon-mail"></div> <div class="icon-mail" />
<h2>{{ t('mail', 'No messages in this folder') }}</h2> <h2>{{ t('mail', 'No messages in this folder') }}</h2>
</div> </div>
</template> </template>

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

@ -3,31 +3,30 @@
<div <div
v-if="folder.isUnified" v-if="folder.isUnified"
class="mail-message-account-color" class="mail-message-account-color"
:style="{'background-color': accountColor}" :style="{'background-color': accountColor}" />
></div>
<div <div
v-if="data.flags.flagged" v-if="data.flags.flagged"
class="app-content-list-item-star icon-starred" class="app-content-list-item-star icon-starred"
:data-starred="data.flags.flagged ? 'true' : 'false'" :data-starred="data.flags.flagged ? 'true' : 'false'"
@click.prevent="onToggleFlagged" @click.prevent="onToggleFlagged" />
></div>
<div <div
v-if="data.flags.important" v-if="data.flags.important"
class="app-content-list-item-star icon-important" class="app-content-list-item-star icon-important"
:data-starred="data.flags.important ? 'true' : 'false'" :data-starred="data.flags.important ? 'true' : 'false'"
@click.prevent="onToggleImportant" @click.prevent="onToggleImportant"
v-html="importantSvg" v-html="importantSvg" />
></div>
<div <div
v-if="data.flags.junk" v-if="data.flags.junk"
class="app-content-list-item-star icon-junk" class="app-content-list-item-star icon-junk"
:data-starred="data.flags.junk ? 'true' : 'false'" :data-starred="data.flags.junk ? 'true' : 'false'"
@click.prevent="onToggleJunk" @click.prevent="onToggleJunk" />
></div>
<div class="app-content-list-item-icon"> <div class="app-content-list-item-icon">
<Avatar :display-name="addresses" :email="avatarEmail" /> <Avatar :display-name="addresses" :email="avatarEmail" />
<p v-if="selectMode" class="app-content-list-item-select-checkbox"> <p v-if="selectMode" class="app-content-list-item-select-checkbox">
<input :id="`select-checkbox-${data.uid}`" class="checkbox" type="checkbox" :checked="selected" /> <input :id="`select-checkbox-${data.uid}`"
class="checkbox"
type="checkbox"
:checked="selected">
<label :for="`select-checkbox-${data.uid}`" @click.prevent="toggleSelected" /> <label :for="`select-checkbox-${data.uid}`" @click.prevent="toggleSelected" />
</p> </p>
</div> </div>
@ -46,22 +45,34 @@
<Moment :timestamp="data.dateInt" /> <Moment :timestamp="data.dateInt" />
</div> </div>
<Actions class="app-content-list-item-menu" menu-align="right"> <Actions class="app-content-list-item-menu" menu-align="right">
<ActionButton icon="icon-starred" @click.prevent="onToggleFlagged">{{ <ActionButton icon="icon-starred" @click.prevent="onToggleFlagged">
data.flags.flagged ? t('mail', 'Unfavorite') : t('mail', 'Favorite') {{
}}</ActionButton> data.flags.flagged ? t('mail', 'Unfavorite') : t('mail', 'Favorite')
<ActionButton icon="icon-important" @click.prevent="onToggleImportant">{{ }}
data.flags.important ? t('mail', 'Mark unimportant') : t('mail', 'Mark important') </ActionButton>
}}</ActionButton> <ActionButton icon="icon-important" @click.prevent="onToggleImportant">
<ActionButton icon="icon-mail" @click.prevent="onToggleSeen">{{ {{
data.flags.seen ? t('mail', 'Mark unread') : t('mail', 'Mark read') data.flags.important ? t('mail', 'Mark unimportant') : t('mail', 'Mark important')
}}</ActionButton> }}
<ActionButton icon="icon-junk" @click.prevent="onToggleJunk">{{ </ActionButton>
data.flags.junk ? t('mail', 'Mark not spam') : t('mail', 'Mark as spam') <ActionButton icon="icon-mail" @click.prevent="onToggleSeen">
}}</ActionButton> {{
<ActionButton icon="icon-checkmark" :close-after-click="true" @click.prevent="onSelect">{{ data.flags.seen ? t('mail', 'Mark unread') : t('mail', 'Mark read')
selected ? t('mail', 'Unselect') : t('mail', 'Select') }}
}}</ActionButton> </ActionButton>
<ActionButton icon="icon-delete" @click.prevent="onDelete">{{ t('mail', 'Delete') }}</ActionButton> <ActionButton icon="icon-junk" @click.prevent="onToggleJunk">
{{
data.flags.junk ? t('mail', 'Mark not spam') : t('mail', 'Mark as spam')
}}
</ActionButton>
<ActionButton icon="icon-checkmark" :close-after-click="true" @click.prevent="onSelect">
{{
selected ? t('mail', 'Unselect') : t('mail', 'Select')
}}
</ActionButton>
<ActionButton icon="icon-delete" @click.prevent="onDelete">
{{ t('mail', 'Delete') }}
</ActionButton>
</Actions> </Actions>
</router-link> </router-link>
</template> </template>
@ -73,7 +84,7 @@ import Moment from './Moment'
import importantSvg from '../../img/important.svg' import importantSvg from '../../img/important.svg'
import Avatar from './Avatar' import Avatar from './Avatar'
import {calculateAccountColor} from '../util/AccountColor' import { calculateAccountColor } from '../util/AccountColor'
export default { export default {
name: 'Envelope', name: 'Envelope',
@ -145,7 +156,7 @@ export default {
addresses() { addresses() {
// Show recipients' label/address in a sent folder // Show recipients' label/address in a sent folder
if (this.folder.specialRole === 'sent') { if (this.folder.specialRole === 'sent') {
let recipients = [this.data.to, this.data.cc].flat().map(function (recipient) { const recipients = [this.data.to, this.data.cc].flat().map(function(recipient) {
return recipient.label ? recipient.label : recipient.email return recipient.label ? recipient.label : recipient.email
}) })
return recipients.length > 0 ? recipients.join(', ') : t('mail', 'Blind copy recipients only') return recipients.length > 0 ? recipients.join(', ') : t('mail', 'Blind copy recipients only')
@ -156,7 +167,7 @@ export default {
avatarEmail() { avatarEmail() {
// Show first recipients' avatar in a sent folder (or undefined when sent to Bcc only) // Show first recipients' avatar in a sent folder (or undefined when sent to Bcc only)
if (this.folder.specialRole === 'sent') { if (this.folder.specialRole === 'sent') {
let recipients = [this.data.to, this.data.cc].flat().map(function (recipient) { const recipients = [this.data.to, this.data.cc].flat().map(function(recipient) {
return recipient.email return recipient.email
}) })
return recipients.length > 0 ? recipients[0] : undefined return recipients.length > 0 ? recipients[0] : undefined

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

@ -10,11 +10,13 @@
}}</span> }}</span>
</div> </div>
<Actions class="app-content-list-item-menu" menu-align="right"> <Actions class="app-content-list-item-menu" menu-align="right">
<ActionButton icon="icon-starred" @click.prevent="favoriteOrUnfavoriteAll">{{ <ActionButton icon="icon-starred" @click.prevent="favoriteOrUnfavoriteAll">
areAllSelectedFavorite {{
? t('mail', 'Unfavorite ' + selection.length) areAllSelectedFavorite
: t('mail', 'Favorite ' + selection.length) ? t('mail', 'Unfavorite ' + selection.length)
}}</ActionButton> : t('mail', 'Favorite ' + selection.length)
}}
</ActionButton>
<ActionButton icon="icon-close" @click.prevent="unselectAll"> <ActionButton icon="icon-close" @click.prevent="unselectAll">
{{ t('mail', 'Unselect ' + selection.length) }} {{ t('mail', 'Unselect ' + selection.length) }}
</ActionButton> </ActionButton>
@ -25,7 +27,10 @@
</div> </div>
</transition> </transition>
<transition-group name="list"> <transition-group name="list">
<div id="list-refreshing" key="loading" class="icon-loading-small" :class="{refreshing: refreshing}" /> <div id="list-refreshing"
key="loading"
class="icon-loading-small"
:class="{refreshing: refreshing}" />
<Envelope <Envelope
v-for="env in envelopes" v-for="env in envelopes"
:key="env.uuid" :key="env.uuid"
@ -34,14 +39,12 @@
:selected="isEnvelopeSelected(envelopes.indexOf(env))" :selected="isEnvelopeSelected(envelopes.indexOf(env))"
:select-mode="selectMode" :select-mode="selectMode"
@delete="$emit('delete', env.uuid)" @delete="$emit('delete', env.uuid)"
@update:selected="onEnvelopeSelectToggle(env, ...$event)" @update:selected="onEnvelopeSelectToggle(env, ...$event)" />
/>
<div <div
v-if="loadMoreButton && !loadingMore" v-if="loadMoreButton && !loadingMore"
:key="'list-collapse-' + searchQuery" :key="'list-collapse-' + searchQuery"
class="load-more" class="load-more"
@click="$emit('loadMore')" @click="$emit('loadMore')">
>
{{ t('mail', 'Load more') }} {{ t('mail', 'Load more') }}
</div> </div>
<div id="load-more-mail-messages" key="loadingMore" :class="{'icon-loading-small': loadingMore}" /> <div id="load-more-mail-messages" key="loadingMore" :class="{'icon-loading-small': loadingMore}" />
@ -116,28 +119,28 @@ export default {
}, },
methods: { methods: {
isEnvelopeSelected(idx) { isEnvelopeSelected(idx) {
if (this.selection.length == 0) { if (this.selection.length === 0) {
return false return false
} }
return this.selection.includes(idx) return this.selection.includes(idx)
}, },
markSelectedSeenOrUnseen() { markSelectedSeenOrUnseen() {
let seenFlag = this.areAllSelectedRead const seenFlag = this.areAllSelectedRead
this.selection.forEach((envelopeId) => { this.selection.forEach((envelopeId) => {
this.$store.dispatch('markEnvelopeSeenOrUnseen', { this.$store.dispatch('markEnvelopeSeenOrUnseen', {
envelope: this.envelopes[envelopeId], envelope: this.envelopes[envelopeId],
seenFlag: seenFlag, seenFlag,
}) })
}) })
this.unselectAll() this.unselectAll()
}, },
favoriteOrUnfavoriteAll() { favoriteOrUnfavoriteAll() {
let favFlag = !this.areAllSelectedFavorite const favFlag = !this.areAllSelectedFavorite
this.selection.forEach((envelopeId) => { this.selection.forEach((envelopeId) => {
this.$store.dispatch('markEnvelopeFavoriteOrUnfavorite', { this.$store.dispatch('markEnvelopeFavoriteOrUnfavorite', {
envelope: this.envelopes[envelopeId], envelope: this.envelopes[envelopeId],
favFlag: favFlag, favFlag,
}) })
}) })
this.unselectAll() this.unselectAll()
@ -145,7 +148,7 @@ export default {
deleteAllSelected() { deleteAllSelected() {
this.selection.forEach((envelopeId) => { this.selection.forEach((envelopeId) => {
// Navigate if the message being deleted is the one currently viewed // Navigate if the message being deleted is the one currently viewed
if (this.envelopes[envelopeId].uuid == this.$route.params.messageUuid) { if (this.envelopes[envelopeId].uuid === this.$route.params.messageUuid) {
let next let next
if (envelopeId === 0) { if (envelopeId === 0) {
next = this.envelopes[envelopeId + 1] next = this.envelopes[envelopeId + 1]
@ -179,7 +182,6 @@ export default {
this.selection.splice(this.selection.indexOf(idx), 1) this.selection.splice(this.selection.indexOf(idx), 1)
} }
return
}, },
unselectAll() { unselectAll() {
this.envelopes.forEach((env) => { this.envelopes.forEach((env) => {

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

@ -24,13 +24,16 @@
<h2>{{ error }}</h2> <h2>{{ error }}</h2>
<p>{{ message }}</p> <p>{{ message }}</p>
<p v-if="data && data.debug"> <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> </p>
</div> </div>
</template> </template>
<script> <script>
import {getReportUrl} from '../util/CrashReport' import { getReportUrl } from '../util/CrashReport'
export default { export default {
name: 'Error', name: 'Error',

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

@ -6,22 +6,19 @@
:key="idx" :key="idx"
:data="entry" :data="entry"
:calendars="calendars" :calendars="calendars"
:message-id="messageId" :message-id="messageId" />
/>
<FlightReservation <FlightReservation
v-else-if="entry['@type'] === 'FlightReservation'" v-else-if="entry['@type'] === 'FlightReservation'"
:key="idx" :key="idx"
:data="entry" :data="entry"
:calendars="calendars" :calendars="calendars"
:message-id="messageId" :message-id="messageId" />
/>
<TrainReservation <TrainReservation
v-else-if="entry['@type'] === 'TrainReservation'" v-else-if="entry['@type'] === 'TrainReservation'"
:key="idx" :key="idx"
:data="entry" :data="entry"
:calendars="calendars" :calendars="calendars"
:message-id="messageId" :message-id="messageId" />
/>
<span v-else :key="idx">{{ <span v-else :key="idx">{{
t('mail', 'Itinerary for {type} is not supported yet', {type: entry['@type']}) t('mail', 'Itinerary for {type} is not supported yet', {type: entry['@type']})
}}</span> }}</span>
@ -32,7 +29,7 @@
<script> <script>
import once from 'lodash/fp/once' import once from 'lodash/fp/once'
import {getUserCalendars} from '../service/DAVService' import { getUserCalendars } from '../service/DAVService'
import logger from '../logger' import logger from '../logger'
import EventReservation from './itinerary/EventReservation' import EventReservation from './itinerary/EventReservation'
import FlightReservation from './itinerary/FlightReservation' import FlightReservation from './itinerary/FlightReservation'
@ -65,7 +62,7 @@ export default {
mounted() { mounted() {
getUserCalendarsOnce() getUserCalendarsOnce()
.then((calendars) => (this.calendars = calendars)) .then((calendars) => (this.calendars = calendars))
.catch((error) => logger.error('Could not load calendars', {error})) .catch((error) => logger.error('Could not load calendars', { error }))
}, },
} }
</script> </script>

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

@ -1,12 +1,12 @@
<template> <template>
<div v-if="hint" class="emptycontent"> <div v-if="hint" class="emptycontent">
<a class="icon-loading"></a> <a class="icon-loading" />
<h2>{{ hint }}</h2> <h2>{{ hint }}</h2>
<transition name="fade"> <transition name="fade">
<em v-if="slowHint && slow">{{ slowHint }}</em> <em v-if="slowHint && slow">{{ slowHint }}</em>
</transition> </transition>
</div> </div>
<div v-else class="container icon-loading"></div> <div v-else class="container icon-loading" />
</template> </template>
<script> <script>

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

@ -25,9 +25,8 @@
<Loading <Loading
v-else-if="loadingCacheInitialization" v-else-if="loadingCacheInitialization"
:hint="t('mail', 'Loading messages')" :hint="t('mail', 'Loading messages')"
:slow-hint="t('mail', 'Indexing your messages. This can take a bit longer for larger mailboxes.')" :slow-hint="t('mail', 'Indexing your messages. This can take a bit longer for larger mailboxes.')" />
/> <EmptyMailboxSection v-else-if="isPriorityInbox && !hasMessages" key="empty" />
<EmptyMailboxSection v-else-if="isPriorityInbox && !hasMessages" key="empty"></EmptyMailboxSection>
<EmptyMailbox v-else-if="!hasMessages" key="empty" /> <EmptyMailbox v-else-if="!hasMessages" key="empty" />
<EnvelopeList <EnvelopeList
v-else v-else
@ -39,24 +38,22 @@
:loading-more="loadingMore" :loading-more="loadingMore"
:load-more-button="showLoadMore" :load-more-button="showLoadMore"
@delete="onDelete" @delete="onDelete"
@loadMore="loadMore" @loadMore="loadMore" />
/>
</template> </template>
<script> <script>
import EmptyMailbox from './EmptyMailbox' import EmptyMailbox from './EmptyMailbox'
import EnvelopeList from './EnvelopeList' import EnvelopeList from './EnvelopeList'
import Error from './Error' import Error from './Error'
import {findIndex, propEq} from 'ramda' import { findIndex, propEq } from 'ramda'
import isMobile from '@nextcloud/vue/dist/Mixins/isMobile' import isMobile from '@nextcloud/vue/dist/Mixins/isMobile'
import Loading from './Loading' import Loading from './Loading'
import logger from '../logger' import logger from '../logger'
import MailboxLockedError from '../errors/MailboxLockedError' import MailboxLockedError from '../errors/MailboxLockedError'
import MailboxNotCachedError from '../errors/MailboxNotCachedError' import MailboxNotCachedError from '../errors/MailboxNotCachedError'
import {matchError} from '../errors/match' import { matchError } from '../errors/match'
import {wait} from '../util/wait' import { wait } from '../util/wait'
import EmptyMailboxSection from './EmptyMailboxSection' import EmptyMailboxSection from './EmptyMailboxSection'
import {normalizedEnvelopeListId} from '../store/normalization'
export default { export default {
name: 'Mailbox', name: 'Mailbox',
@ -191,13 +188,13 @@ export default {
limit: this.initialPageSize, limit: this.initialPageSize,
}) })
logger.debug(envelopes.length + ' envelopes fetched', {envelopes}) logger.debug(envelopes.length + ' envelopes fetched', { envelopes })
this.loadingEnvelopes = false this.loadingEnvelopes = false
if (this.openFirst && !this.isMobile && this.$route.name !== 'message' && envelopes.length > 0) { if (this.openFirst && !this.isMobile && this.$route.name !== 'message' && envelopes.length > 0) {
// Show first message // Show first message
let first = envelopes[0] const first = envelopes[0]
// Keep the selected account-folder combination, but navigate to the message // 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) // (it's not a bug that we don't use first.accountId and first.folderId here)
@ -213,26 +210,26 @@ export default {
} }
} catch (error) { } catch (error) {
await matchError(error, { await matchError(error, {
[MailboxLockedError.getName()]: async (error) => { [MailboxLockedError.getName()]: async(error) => {
logger.info('Mailbox is locked', {error}) logger.info('Mailbox is locked', { error })
await wait(15 * 1000) await wait(15 * 1000)
// Keep trying // Keep trying
await this.loadEnvelopes() await this.loadEnvelopes()
}, },
[MailboxNotCachedError.getName()]: async (error) => { [MailboxNotCachedError.getName()]: async(error) => {
logger.info('Mailbox not cached. Triggering initialization', {error}) logger.info('Mailbox not cached. Triggering initialization', { error })
this.loadingEnvelopes = false this.loadingEnvelopes = false
try { try {
await this.initializeCache() await this.initializeCache()
} catch (error) { } catch (error) {
logger.error('Could not initialize cache', {error}) logger.error('Could not initialize cache', { error })
this.error = error this.error = error
} }
}, },
default: (error) => { default: (error) => {
logger.error('Could not fetch envelopes', {error}) logger.error('Could not fetch envelopes', { error })
this.loadingEnvelopes = false this.loadingEnvelopes = false
this.error = error this.error = error
}, },
@ -261,7 +258,7 @@ export default {
this.endReached = true this.endReached = true
} }
} catch (error) { } catch (error) {
logger.error('could not fetch next envelope page', {error}) logger.error('could not fetch next envelope page', { error })
} finally { } finally {
this.loadingMore = false this.loadingMore = false
} }
@ -283,82 +280,82 @@ export default {
const env = current[0] const env = current[0]
const idx = envelopes.indexOf(env) const idx = envelopes.indexOf(env)
let next
switch (e.srcKey) { switch (e.srcKey) {
case 'next': case 'next':
case 'prev': case 'prev':
let next if (e.srcKey === 'next') {
if (e.srcKey === 'next') { next = envelopes[idx + 1]
next = envelopes[idx + 1] } else {
} else { next = envelopes[idx - 1]
next = envelopes[idx - 1] }
}
if (!next) { if (!next) {
logger.debug('ignoring shortcut: head or tail of envelope list reached', { logger.debug('ignoring shortcut: head or tail of envelope list reached', {
envelopes, envelopes,
idx, idx,
srcKey: e.srcKey, srcKey: 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,
filter: this.$route.params.filter ? this.$route.params.filter : undefined,
messageUuid: next.uuid,
},
}) })
break return
case 'del': }
logger.debug('deleting', {env})
this.onDelete(env.uuid)
this.$store
.dispatch('deleteMessage', {
accountId: env.accountId,
folderId: env.folderId,
uid: env.uid,
})
.catch((error) =>
logger.error('could not delete envelope', {
env,
error,
})
)
break // Keep the selected account-folder combination, but navigate to a different message
case 'flag': // (it's not a bug that we don't use next.accountId and next.folderId here)
logger.debug('flagging envelope via shortkey', {env}) this.$router.push({
this.$store.dispatch('toggleEnvelopeFlagged', env).catch((error) => name: 'message',
logger.error('could not flag envelope via shortkey', { params: {
accountId: this.$route.params.accountId,
folderId: this.$route.params.folderId,
filter: this.$route.params.filter ? this.$route.params.filter : undefined,
messageUuid: next.uuid,
},
})
break
case 'del':
logger.debug('deleting', { env })
this.onDelete(env.uuid)
this.$store
.dispatch('deleteMessage', {
accountId: env.accountId,
folderId: env.folderId,
uid: env.uid,
})
.catch((error) =>
logger.error('could not delete envelope', {
env, env,
error, error,
}) })
) )
break
case 'refresh':
logger.debug('syncing envelopes via shortkey')
if (!this.refreshing) {
this.sync()
}
break break
case 'unseen': case 'flag':
logger.debug('marking message as seen/unseen via shortkey', {env}) logger.debug('flagging envelope via shortkey', { env })
this.$store.dispatch('toggleEnvelopeSeen', env).catch((error) => this.$store.dispatch('toggleEnvelopeFlagged', env).catch((error) =>
logger.error('could not mark envelope as seen/unseen via shortkey', { logger.error('could not flag envelope via shortkey', {
env, env,
error, error,
}) })
) )
break break
default: case 'refresh':
logger.warn('shortcut ' + e.srcKey + ' is unknown. ignoring.') logger.debug('syncing envelopes via shortkey')
if (!this.refreshing) {
this.sync()
}
break
case 'unseen':
logger.debug('marking message as seen/unseen via shortkey', { env })
this.$store.dispatch('toggleEnvelopeSeen', env).catch((error) =>
logger.error('could not mark envelope as seen/unseen via shortkey', {
env,
error,
})
)
break
default:
logger.warn('shortcut ' + e.srcKey + ' is unknown. ignoring.')
} }
}, },
async sync() { async sync() {
@ -374,10 +371,10 @@ export default {
} catch (error) { } catch (error) {
matchError(error, { matchError(error, {
[MailboxLockedError.getName()](error) { [MailboxLockedError.getName()](error) {
logger.info('Background sync failed because the mailbox is locked', {error}) logger.info('Background sync failed because the mailbox is locked', { error })
}, },
default(error) { default(error) {
logger.error('Could not sync envelopes: ' + error.message, {error}) logger.error('Could not sync envelopes: ' + error.message, { error })
}, },
}) })
} finally { } finally {
@ -436,7 +433,7 @@ export default {
logger.debug("Mailbox sync'ed in background") logger.debug("Mailbox sync'ed in background")
} catch (error) { } catch (error) {
logger.error('Background sync failed: ' + error.message, {error}) logger.error('Background sync failed: ' + error.message, { error })
} }
}, },
stopInterval() { stopInterval() {

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

@ -9,20 +9,18 @@
:show-details="showMessage" :show-details="showMessage"
:infinite-scroll-disabled="false" :infinite-scroll-disabled="false"
:infinite-scroll-distance="10" :infinite-scroll-distance="10"
@shortkey.native="onShortcut" @shortkey.native="onShortcut">
>
<Mailbox <Mailbox
v-if="!folder.isPriorityInbox" v-if="!folder.isPriorityInbox"
:account="account" :account="account"
:folder="folder" :folder="folder"
:search-query="query" :search-query="query"
:bus="bus" :bus="bus" />
/>
<template v-else> <template v-else>
<li class="app-content-list-item"> <li class="app-content-list-item">
<SectionTitle class="important" :name="t('mail', 'Important')" /> <SectionTitle class="important" :name="t('mail', 'Important')" />
<Popover trigger="hover focus"> <Popover trigger="hover focus">
<button slot="trigger" class="button icon-info"></button> <button slot="trigger" class="button icon-info" />
{{ {{
t( t(
'mail', 'mail',
@ -40,8 +38,7 @@
:is-priority-inbox="true" :is-priority-inbox="true"
:initial-page-size="5" :initial-page-size="5"
:collapsible="true" :collapsible="true"
:bus="bus" :bus="bus" />
/>
<SectionTitle class="app-content-list-item starred" :name="t('mail', 'Favorites')" /> <SectionTitle class="app-content-list-item starred" :name="t('mail', 'Favorites')" />
<Mailbox <Mailbox
class="namestarred" class="namestarred"
@ -51,8 +48,7 @@
:paginate="'manual'" :paginate="'manual'"
:is-priority-inbox="true" :is-priority-inbox="true"
:initial-page-size="5" :initial-page-size="5"
:bus="bus" :bus="bus" />
/>
<SectionTitle class="app-content-list-item other" :name="t('mail', 'Other')" /> <SectionTitle class="app-content-list-item other" :name="t('mail', 'Other')" />
<Mailbox <Mailbox
class="nameother" class="nameother"
@ -61,8 +57,7 @@
:open-first="false" :open-first="false"
:search-query="appendToSearch('not:starred not:important')" :search-query="appendToSearch('not:starred not:important')"
:is-priority-inbox="true" :is-priority-inbox="true"
:bus="bus" :bus="bus" />
/>
</template> </template>
</AppContentList> </AppContentList>
<NewMessageDetail v-if="newMessage" /> <NewMessageDetail v-if="newMessage" />
@ -87,8 +82,8 @@ import Mailbox from './Mailbox'
import Message from './Message' import Message from './Message'
import NewMessageDetail from './NewMessageDetail' import NewMessageDetail from './NewMessageDetail'
import NoMessageSelected from './NoMessageSelected' import NoMessageSelected from './NoMessageSelected'
import {normalizedEnvelopeListId} from '../store/normalization' import { normalizedEnvelopeListId } from '../store/normalization'
import {UNIFIED_ACCOUNT_ID, UNIFIED_INBOX_ID} from '../store/constants' import { UNIFIED_ACCOUNT_ID, UNIFIED_INBOX_ID } from '../store/constants'
export default { export default {
name: 'MailboxMessage', name: 'MailboxMessage',
@ -164,15 +159,16 @@ export default {
}, },
newMessage() { newMessage() {
return ( return (
this.$route.params.messageUuid === 'new' || this.$route.params.messageUuid === 'new'
this.$route.params.messageUuid === 'reply' || || this.$route.params.messageUuid === 'reply'
this.$route.params.messageUuid === 'replyAll' || this.$route.params.messageUuid === 'replyAll'
) )
}, },
}, },
created() { created() {
this.alive = true this.alive = true
// eslint-disable-next-line no-new
new OCA.Search(this.searchProxy, this.clearSearchProxy) new OCA.Search(this.searchProxy, this.clearSearchProxy)
}, },
beforeDestroy() { beforeDestroy() {
@ -193,7 +189,7 @@ export default {
this.bus.$emit('delete', envelopeUid) this.bus.$emit('delete', envelopeUid)
}, },
onScroll(event) { onScroll(event) {
logger.debug('scroll', {event}) logger.debug('scroll', { event })
this.bus.$emit('loadMore') this.bus.$emit('loadMore')
}, },

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

@ -20,12 +20,12 @@
--> -->
<template> <template>
<div id="mailvelope-composer"></div> <div id="mailvelope-composer" />
</template> </template>
<script> <script>
import logger from '../logger' import logger from '../logger'
import {isPgpgMessage} from '../crypto/pgp' import { isPgpgMessage } from '../crypto/pgp'
export default { export default {
name: 'MailvelopeEditor', name: 'MailvelopeEditor',
@ -64,9 +64,9 @@ export default {
methods: { methods: {
async pull() { async pull() {
const recipients = this.recipients.map((r) => r.email) const recipients = this.recipients.map((r) => r.email)
logger.info('encrypting message', {recipients}) logger.info('encrypting message', { recipients })
const armored = await this.editor.encrypt(recipients) const armored = await this.editor.encrypt(recipients)
logger.info('message encryted', {armored}) logger.info('message encryted', { armored })
this.$emit('input', armored) this.$emit('input', armored)
}, },

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

@ -5,13 +5,13 @@
v-else-if="!message" v-else-if="!message"
:error="error && error.message ? error.message : t('mail', 'Not found')" :error="error && error.message ? error.message : t('mail', 'Not found')"
:message="errorMessage" :message="errorMessage"
:data="error" :data="error" />
>
</Error>
<template v-else> <template v-else>
<div id="mail-message-header"> <div id="mail-message-header">
<div id="mail-message-header-fields"> <div id="mail-message-header-fields">
<h2 :title="message.subject">{{ message.subject }}</h2> <h2 :title="message.subject">
{{ message.subject }}
</h2>
<p class="transparency"> <p class="transparency">
<AddressList :entries="message.from" /> <AddressList :entries="message.from" />
{{ t('mail', 'to') }} {{ t('mail', 'to') }}
@ -28,8 +28,7 @@
? 'icon-reply-all-white button primary' ? 'icon-reply-all-white button primary'
: 'icon-reply-white button primary' : 'icon-reply-white button primary'
" "
@click="hasMultipleRecipients ? replyAll() : replyMessage()" @click="hasMultipleRecipients ? replyAll() : replyMessage()">
>
<span class="action-label">{{ t('mail', 'Reply') }}</span> <span class="action-label">{{ t('mail', 'Reply') }}</span>
</div> </div>
<Actions id="mail-message-actions-menu" class="app-content-list-item-menu" menu-align="right"> <Actions id="mail-message-actions-menu" class="app-content-list-item-menu" menu-align="right">
@ -39,12 +38,16 @@
<ActionButton icon="icon-forward" @click="forwardMessage"> <ActionButton icon="icon-forward" @click="forwardMessage">
{{ t('mail', 'Forward') }} {{ t('mail', 'Forward') }}
</ActionButton> </ActionButton>
<ActionButton icon="icon-starred" @click.prevent="onToggleFlagged">{{ <ActionButton icon="icon-starred" @click.prevent="onToggleFlagged">
envelope.flags.flagged ? t('mail', 'Unfavorite') : t('mail', 'Favorite') {{
}}</ActionButton> envelope.flags.flagged ? t('mail', 'Unfavorite') : t('mail', 'Favorite')
<ActionButton icon="icon-important" @click.prevent="onToggleImportant">{{ }}
envelope.flags.important ? t('mail', 'Mark unimportant') : t('mail', 'Mark important') </ActionButton>
}}</ActionButton> <ActionButton icon="icon-important" @click.prevent="onToggleImportant">
{{
envelope.flags.important ? t('mail', 'Mark unimportant') : t('mail', 'Mark important')
}}
</ActionButton>
<ActionButton icon="icon-mail" @click="onToggleSeen"> <ActionButton icon="icon-mail" @click="onToggleSeen">
{{ envelope.flags.seen ? t('mail', 'Mark unread') : t('mail', 'Mark read') }} {{ envelope.flags.seen ? t('mail', 'Mark unread') : t('mail', 'Mark read') }}
</ActionButton> </ActionButton>
@ -55,8 +58,7 @@
<ActionButton <ActionButton
:icon="sourceLoading ? 'icon-loading-small' : 'icon-details'" :icon="sourceLoading ? 'icon-loading-small' : 'icon-details'"
:disabled="sourceLoading" :disabled="sourceLoading"
@click="onShowSource" @click="onShowSource">
>
{{ t('mail', 'View source') }} {{ t('mail', 'View source') }}
</ActionButton> </ActionButton>
<ActionButton icon="icon-delete" @click.prevent="onDelete"> <ActionButton icon="icon-delete" @click.prevent="onDelete">
@ -80,11 +82,13 @@
<MessagePlainTextBody v-else :body="message.body" :signature="message.signature" /> <MessagePlainTextBody v-else :body="message.body" :signature="message.signature" />
<Popover v-if="message.attachments[0]" class="attachment-popover"> <Popover v-if="message.attachments[0]" class="attachment-popover">
<Actions slot="trigger"> <Actions slot="trigger">
<ActionButton icon="icon-public icon-attachment">Attachments</ActionButton> <ActionButton icon="icon-public icon-attachment">
Attachments
</ActionButton>
</Actions> </Actions>
<MessageAttachments :attachments="message.attachments" /> <MessageAttachments :attachments="message.attachments" />
</Popover> </Popover>
<div id="reply-composer"></div> <div id="reply-composer" />
</div> </div>
</template> </template>
</AppContentDetails> </AppContentDetails>
@ -97,14 +101,14 @@ import Popover from '@nextcloud/vue/dist/Components/Popover'
import AppContentDetails from '@nextcloud/vue/dist/Components/AppContentDetails' import AppContentDetails from '@nextcloud/vue/dist/Components/AppContentDetails'
import axios from '@nextcloud/axios' import axios from '@nextcloud/axios'
import Modal from '@nextcloud/vue/dist/Components/Modal' import Modal from '@nextcloud/vue/dist/Components/Modal'
import {generateUrl} from '@nextcloud/router' import { generateUrl } from '@nextcloud/router'
import AddressList from './AddressList' import AddressList from './AddressList'
import {buildRecipients as buildReplyRecipients, buildReplySubject} from '../ReplyBuilder' import { buildRecipients as buildReplyRecipients, buildReplySubject } from '../ReplyBuilder'
import Error from './Error' import Error from './Error'
import {getRandomMessageErrorMessage} from '../util/ErrorMessageFactory' import { getRandomMessageErrorMessage } from '../util/ErrorMessageFactory'
import {html, plain} from '../util/text' import { html, plain } from '../util/text'
import {isPgpgMessage} from '../crypto/pgp' import { isPgpgMessage } from '../crypto/pgp'
import Itinerary from './Itinerary' import Itinerary from './Itinerary'
import MessageEncryptedBody from './MessageEncryptedBody' import MessageEncryptedBody from './MessageEncryptedBody'
import MessageHTMLBody from './MessageHTMLBody' import MessageHTMLBody from './MessageHTMLBody'
@ -172,16 +176,16 @@ export default {
watch: { watch: {
$route(to, from) { $route(to, from) {
if ( if (
from.name === to.name && from.name === to.name
Number.parseInt(from.params.accountId, 10) === Number.parseInt(to.params.accountId, 10) && && Number.parseInt(from.params.accountId, 10) === Number.parseInt(to.params.accountId, 10)
from.params.folderId === to.params.folderId && && from.params.folderId === to.params.folderId
from.params.messageUuid === to.params.messageUuid && && from.params.messageUuid === to.params.messageUuid
from.params.filter === to.params.filter && from.params.filter === to.params.filter
) { ) {
logger.debug('navigated but the message is still the same') logger.debug('navigated but the message is still the same')
return return
} }
logger.debug('navigated to another message', {to, from}) logger.debug('navigated to another message', { to, from })
this.fetchMessage() this.fetchMessage()
}, },
}, },
@ -204,7 +208,7 @@ export default {
this.$store.dispatch('fetchEnvelope', messageUuid), this.$store.dispatch('fetchEnvelope', messageUuid),
this.$store.dispatch('fetchMessage', messageUuid), this.$store.dispatch('fetchMessage', messageUuid),
]) ])
logger.debug('envelope and message fetched', {envelope, message}) logger.debug('envelope and message fetched', { envelope, message })
// TODO: add timeout so that message isn't flagged when only viewed // TODO: add timeout so that message isn't flagged when only viewed
// for a few seconds // for a few seconds
if (message && message.uuid !== this.$route.params.messageUuid) { if (message && message.uuid !== this.$route.params.messageUuid) {
@ -216,7 +220,7 @@ export default {
this.message = message this.message = message
if (envelope === undefined || message === undefined) { if (envelope === undefined || message === undefined) {
logger.info('message could not be found', {messageUuid: messageUuid, envelope, message}) logger.info('message could not be found', { messageUuid, envelope, message })
this.errorMessage = getRandomMessageErrorMessage() this.errorMessage = getRandomMessageErrorMessage()
this.loading = false this.loading = false
return return
@ -236,7 +240,7 @@ export default {
return this.$store.dispatch('toggleEnvelopeSeen', envelope) return this.$store.dispatch('toggleEnvelopeSeen', envelope)
} }
} catch (error) { } catch (error) {
logger.error('could not load message ', {messageUuid, error}) logger.error('could not load message ', { messageUuid, error })
if (error.isError) { if (error.isError) {
this.errorMessage = t('mail', 'Could not load your message') this.errorMessage = t('mail', 'Could not load your message')
this.error = error this.error = error

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

@ -21,10 +21,10 @@
<template> <template>
<div class="attachment" @click="download"> <div class="attachment" @click="download">
<img v-if="isImage" class="mail-attached-image" :src="url" /> <img v-if="isImage" class="mail-attached-image" :src="url">
<img class="attachment-icon" :src="mimeUrl" /> <img class="attachment-icon" :src="mimeUrl">
<span class="attachment-name" :title="label" <span class="attachment-name"
>{{ name }} :title="label">{{ name }}
<span class="attachment-size">({{ humanReadable(size) }})</span> <span class="attachment-size">({{ humanReadable(size) }})</span>
</span> </span>
<button <button
@ -33,38 +33,35 @@
:class="{'icon-add': !loadingCalendars, 'icon-loading-small': loadingCalendars}" :class="{'icon-add': !loadingCalendars, 'icon-loading-small': loadingCalendars}"
:disabled="loadingCalendars" :disabled="loadingCalendars"
:title="t('mail', 'Import into calendar')" :title="t('mail', 'Import into calendar')"
@click.stop="loadCalendars" @click.stop="loadCalendars" />
></button> <button class="button icon-download attachment-download" :title="t('mail', 'Download attachment')" />
<button class="button icon-download attachment-download" :title="t('mail', 'Download attachment')"></button>
<button <button
class="attachment-save-to-cloud" class="attachment-save-to-cloud"
:class="{'icon-folder': !savingToCloud, 'icon-loading-small': savingToCloud}" :class="{'icon-folder': !savingToCloud, 'icon-loading-small': savingToCloud}"
:disabled="savingToCloud" :disabled="savingToCloud"
:title="t('mail', 'Save to Files')" :title="t('mail', 'Save to Files')"
@click.stop="saveToCloud" @click.stop="saveToCloud" />
></button>
<div <div
v-on-click-outside="closeCalendarPopover" v-on-click-outside="closeCalendarPopover"
class="popovermenu bubble attachment-import-popover hidden" class="popovermenu bubble attachment-import-popover hidden"
:class="{open: showCalendarPopover}" :class="{open: showCalendarPopover}">
>
<PopoverMenu :menu="calendarMenuEntries" /> <PopoverMenu :menu="calendarMenuEntries" />
</div> </div>
</div> </div>
</template> </template>
<script> <script>
import {formatFileSize} from '@nextcloud/files' import { formatFileSize } from '@nextcloud/files'
import {mixin as onClickOutside} from 'vue-on-click-outside' import { mixin as onClickOutside } from 'vue-on-click-outside'
import {translate as t} from '@nextcloud/l10n' import { translate as t } from '@nextcloud/l10n'
import {getFilePickerBuilder} from '@nextcloud/dialogs' import { getFilePickerBuilder } from '@nextcloud/dialogs'
import PopoverMenu from '@nextcloud/vue/dist/Components/PopoverMenu' import PopoverMenu from '@nextcloud/vue/dist/Components/PopoverMenu'
import {parseUuid} from '../util/EnvelopeUidParser' import { parseUuid } from '../util/EnvelopeUidParser'
import Logger from '../logger' import Logger from '../logger'
import {downloadAttachment, saveAttachmentToFiles} from '../service/AttachmentService' import { downloadAttachment, saveAttachmentToFiles } from '../service/AttachmentService'
import {getUserCalendars, importCalendarEvent} from '../service/DAVService' import { getUserCalendars, importCalendarEvent } from '../service/DAVService'
export default { export default {
name: 'MessageAttachment', name: 'MessageAttachment',
@ -146,7 +143,7 @@ export default {
const saveAttachment = (accountId, folderId, messageId, attachmentId) => (directory) => { const saveAttachment = (accountId, folderId, messageId, attachmentId) => (directory) => {
return saveAttachmentToFiles(accountId, folderId, messageId, attachmentId, directory) return saveAttachmentToFiles(accountId, folderId, messageId, attachmentId, directory)
} }
const {accountId, folderId, uid} = parseUuid(this.$route.params.messageUuid) const { accountId, folderId, uid } = parseUuid(this.$route.params.messageUuid)
const picker = getFilePickerBuilder(t('mail', 'Choose a folder to store the attachment in')) const picker = getFilePickerBuilder(t('mail', 'Choose a folder to store the attachment in'))
.setMultiSelect(false) .setMultiSelect(false)
.addMimeTypeFilter('httpd/unix-directory') .addMimeTypeFilter('httpd/unix-directory')
@ -162,7 +159,7 @@ export default {
}) })
.then(saveAttachment(accountId, folderId, uid, this.id)) .then(saveAttachment(accountId, folderId, uid, this.id))
.then(() => Logger.info('saved')) .then(() => Logger.info('saved'))
.catch((e) => Logger.error('not saved', {error: e})) .catch((e) => Logger.error('not saved', { error: e }))
.then(() => (this.savingToCloud = false)) .then(() => (this.savingToCloud = false))
}, },
download() { download() {
@ -185,7 +182,7 @@ export default {
downloadAttachment(this.url) downloadAttachment(this.url)
.then(importCalendarEvent(url)) .then(importCalendarEvent(url))
.then(() => Logger.info('calendar imported')) .then(() => Logger.info('calendar imported'))
.catch((e) => Logger.error('import error', {error: e})) .catch((e) => Logger.error('import error', { error: e }))
.then(() => (this.showCalendarPopover = false)) .then(() => (this.showCalendarPopover = false))
} }
}, },

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

@ -32,16 +32,14 @@
:is-image="attachment.isImage" :is-image="attachment.isImage"
:is-calendar-event="attachment.isCalendarEvent" :is-calendar-event="attachment.isCalendarEvent"
:mime="attachment.mime" :mime="attachment.mime"
:mime-url="attachment.mimeUrl" :mime-url="attachment.mimeUrl" />
/>
</div> </div>
<p v-if="moreThanOne"> <p v-if="moreThanOne">
<button <button
class="attachments-save-to-cloud" class="attachments-save-to-cloud"
:class="{'icon-folder': !savingToCloud, 'icon-loading-small': savingToCloud}" :class="{'icon-folder': !savingToCloud, 'icon-loading-small': savingToCloud}"
:disabled="savingToCloud" :disabled="savingToCloud"
@click="saveAll" @click="saveAll">
>
{{ t('mail', 'Save all to Files') }} {{ t('mail', 'Save all to Files') }}
</button> </button>
</p> </p>
@ -49,9 +47,9 @@
</template> </template>
<script> <script>
import {getFilePickerBuilder} from '@nextcloud/dialogs' import { getFilePickerBuilder } from '@nextcloud/dialogs'
import {parseUuid} from '../util/EnvelopeUidParser' import { parseUuid } from '../util/EnvelopeUidParser'
import {saveAttachmentsToFiles} from '../service/AttachmentService' import { saveAttachmentsToFiles } from '../service/AttachmentService'
import MessageAttachment from './MessageAttachment' import MessageAttachment from './MessageAttachment'
import Logger from '../logger' import Logger from '../logger'
@ -89,7 +87,7 @@ export default {
const saveAttachments = (accountId, folderId, messageId) => (directory) => { const saveAttachments = (accountId, folderId, messageId) => (directory) => {
return saveAttachmentsToFiles(accountId, folderId, messageId, directory) return saveAttachmentsToFiles(accountId, folderId, messageId, directory)
} }
const {accountId, folderId, uid} = parseUuid(this.$route.params.messageUuid) const { accountId, folderId, uid } = parseUuid(this.$route.params.messageUuid)
return picker return picker
.pick() .pick()
@ -99,7 +97,7 @@ export default {
}) })
.then(saveAttachments(accountId, folderId, uid)) .then(saveAttachments(accountId, folderId, uid))
.then(() => Logger.info('saved')) .then(() => Logger.info('saved'))
.catch((error) => Logger.error('not saved', {error})) .catch((error) => Logger.error('not saved', { error }))
.then(() => (this.savingToCloud = false)) .then(() => (this.savingToCloud = false))
}, },
}, },

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

@ -1,12 +1,12 @@
<template> <template>
<div> <div>
<div v-if="mailvelope" id="mail-content"></div> <div v-if="mailvelope" id="mail-content" />
<span v-else>{{ t('mail', 'This message is encrypted with PGP. Install Mailvelope to decrypt it.') }}</span> <span v-else>{{ t('mail', 'This message is encrypted with PGP. Install Mailvelope to decrypt it.') }}</span>
</div> </div>
</template> </template>
<script> <script>
import {getMailvelope} from '../crypto/mailvelope' import { getMailvelope } from '../crypto/mailvelope'
export default { export default {
name: 'MessageEncryptedBody', name: 'MessageEncryptedBody',

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

@ -8,16 +8,20 @@
</div> </div>
<div v-if="loading" class="icon-loading" /> <div v-if="loading" class="icon-loading" />
<div id="message-container" :class="{hidden: loading}"> <div id="message-container" :class="{hidden: loading}">
<iframe id="message-frame" ref="iframe" :src="url" seamless @load="onMessageFrameLoad" /> <iframe id="message-frame"
ref="iframe"
:src="url"
seamless
@load="onMessageFrameLoad" />
</div> </div>
</div> </div>
</template> </template>
<script> <script>
import PrintScout from 'printscout' import PrintScout from 'printscout'
const scout = new PrintScout()
import logger from '../logger' import logger from '../logger'
const scout = new PrintScout()
export default { export default {
name: 'MessageHTMLBody', name: 'MessageHTMLBody',
@ -48,10 +52,9 @@ export default {
}, },
onMessageFrameLoad() { onMessageFrameLoad() {
const iframeDoc = this.getIframeDoc() const iframeDoc = this.getIframeDoc()
const iframeBody = iframeDoc.querySelectorAll('body')[0] this.hasBlockedContent
this.hasBlockedContent = = iframeDoc.querySelectorAll('[data-original-src]').length > 0
iframeDoc.querySelectorAll('[data-original-src]').length > 0 || || iframeDoc.querySelectorAll('[data-original-style]').length > 0
iframeDoc.querySelectorAll('[data-original-style]').length > 0
this.loading = false this.loading = false
}, },

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

@ -1,7 +1,7 @@
<template> <template>
<div> <div>
<div id="mail-content" v-html="htmlBody"></div> <div id="mail-content" v-html="htmlBody" />
<div v-if="signature" class="mail-signature" v-html="htmlSignature"></div> <div v-if="signature" class="mail-signature" v-html="htmlSignature" />
</div> </div>
</template> </template>

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

@ -25,8 +25,7 @@
:text="t('mail', 'New message')" :text="t('mail', 'New message')"
button-id="mail_new_message" button-id="mail_new_message"
button-class="icon-add" button-class="icon-add"
@click="onNewMessage" @click="onNewMessage" />
/>
<ul id="accounts-list"> <ul id="accounts-list">
<template v-for="group in menu"> <template v-for="group in menu">
<NavigationAccount <NavigationAccount
@ -35,32 +34,28 @@
:account="group.account" :account="group.account"
:first-folder="group.folders[0]" :first-folder="group.folders[0]"
:is-first="isFirst(group.account)" :is-first="isFirst(group.account)"
:is-last="isLast(group.account)" :is-last="isLast(group.account)" />
/>
<template v-for="item in group.folders"> <template v-for="item in group.folders">
<NavigationFolder <NavigationFolder
v-show=" v-show="
!group.isCollapsible || !group.isCollapsible ||
!group.account.collapsed || !group.account.collapsed ||
SHOW_COLLAPSED.indexOf(item.specialRole) !== -1 SHOW_COLLAPSED.indexOf(item.specialRole) !== -1
" "
:key="item.key" :key="item.key"
:account="group.account" :account="group.account"
:folder="item" :folder="item" />
/>
<NavigationFolder <NavigationFolder
v-if="!group.account.isUnified && item.specialRole === 'inbox'" v-if="!group.account.isUnified && item.specialRole === 'inbox'"
:key="item.key + '-starred'" :key="item.key + '-starred'"
:account="group.account" :account="group.account"
:folder="item" :folder="item"
filter="starred" filter="starred" />
/>
</template> </template>
<NavigationAccountExpandCollapse <NavigationAccountExpandCollapse
v-if="!group.account.isUnified && group.isCollapsible" v-if="!group.account.isUnified && group.isCollapsible"
:key="'collapse-' + group.account.id" :key="'collapse-' + group.account.id"
:account="group.account" :account="group.account" />
/>
<AppNavigationSpacer :key="'spacer-' + group.account.id" /> <AppNavigationSpacer :key="'spacer-' + group.account.id" />
</template> </template>
</ul> </ul>
@ -81,10 +76,10 @@ import NavigationAccount from './NavigationAccount'
import NavigationAccountExpandCollapse from './NavigationAccountExpandCollapse' import NavigationAccountExpandCollapse from './NavigationAccountExpandCollapse'
import NavigationFolder from './NavigationFolder' import NavigationFolder from './NavigationFolder'
const SHOW_COLLAPSED = Object.seal(['inbox', 'flagged', 'drafts', 'sent'])
import AppSettingsMenu from '../components/AppSettingsMenu' import AppSettingsMenu from '../components/AppSettingsMenu'
const SHOW_COLLAPSED = Object.seal(['inbox', 'flagged', 'drafts', 'sent'])
export default { export default {
name: 'Navigation', name: 'Navigation',
components: { components: {
@ -127,8 +122,8 @@ export default {
// FIXME: this assumes that there's at least one folder // FIXME: this assumes that there's at least one folder
const folderId = this.$route.params.folderId || this.$store.getters.getFolders(accountId)[0].id const folderId = this.$route.params.folderId || this.$store.getters.getFolders(accountId)[0].id
if ( if (
this.$router.currentRoute.name === 'message' && this.$router.currentRoute.name === 'message'
this.$router.currentRoute.params.messageUuid === 'new' && this.$router.currentRoute.params.messageUuid === 'new'
) { ) {
// If we already show the composer, navigating to it would be pointless (and doesn't work) // If we already show the composer, navigating to it would be pointless (and doesn't work)
// instead trigger an event to reset the composer // instead trigger an event to reset the composer

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

@ -29,8 +29,7 @@
:title="account.emailAddress" :title="account.emailAddress"
:to="firstFolderRoute" :to="firstFolderRoute"
:exact="true" :exact="true"
@update:menuOpen="onMenuToggle" @update:menuOpen="onMenuToggle">
>
<!-- Color dot --> <!-- Color dot -->
<AppNavigationIconBullet v-if="bulletColor" slot="icon" :color="bulletColor" /> <AppNavigationIconBullet v-if="bulletColor" slot="icon" :color="bulletColor" />
@ -45,8 +44,7 @@
<ActionCheckbox <ActionCheckbox
:checked="account.showSubscribedOnly" :checked="account.showSubscribedOnly"
:disabled="savingShowOnlySubscribed" :disabled="savingShowOnlySubscribed"
@update:checked="changeShowSubscribedOnly" @update:checked="changeShowSubscribedOnly">
>
{{ t('mail', 'Show only subscribed folders') }} {{ t('mail', 'Show only subscribed folders') }}
</ActionCheckbox> </ActionCheckbox>
<ActionButton v-if="!editing" icon="icon-folder" @click="openCreateFolder"> <ActionButton v-if="!editing" icon="icon-folder" @click="openCreateFolder">
@ -77,12 +75,12 @@ import ActionButton from '@nextcloud/vue/dist/Components/ActionButton'
import ActionCheckbox from '@nextcloud/vue/dist/Components/ActionCheckbox' import ActionCheckbox from '@nextcloud/vue/dist/Components/ActionCheckbox'
import ActionInput from '@nextcloud/vue/dist/Components/ActionInput' import ActionInput from '@nextcloud/vue/dist/Components/ActionInput'
import ActionText from '@nextcloud/vue/dist/Components/ActionText' import ActionText from '@nextcloud/vue/dist/Components/ActionText'
import {formatFileSize} from '@nextcloud/files' import { formatFileSize } from '@nextcloud/files'
import {generateUrl} from '@nextcloud/router' import { generateUrl } from '@nextcloud/router'
import {calculateAccountColor} from '../util/AccountColor' import { calculateAccountColor } from '../util/AccountColor'
import logger from '../logger' import logger from '../logger'
import {fetchQuota} from '../service/AccountService' import { fetchQuota } from '../service/AccountService'
export default { export default {
name: 'NavigationAccount', name: 'NavigationAccount',
@ -176,10 +174,10 @@ export default {
logger.info('creating folder ' + name) logger.info('creating folder ' + name)
this.menuOpen = false this.menuOpen = false
this.$store this.$store
.dispatch('createFolder', {account: this.account, name}) .dispatch('createFolder', { account: this.account, name })
.then(() => logger.info(`folder ${name} created`)) .then(() => logger.info(`folder ${name} created`))
.catch((error) => { .catch((error) => {
logger.error('could not create folder', {error}) logger.error('could not create folder', { error })
throw error throw error
}) })
this.editing = false this.editing = false
@ -191,17 +189,17 @@ export default {
}, },
removeAccount() { removeAccount() {
const id = this.account.id const id = this.account.id
logger.info('delete account', {account: this.account}) logger.info('delete account', { account: this.account })
OC.dialogs.confirmDestructive( OC.dialogs.confirmDestructive(
t( t(
'mail', 'mail',
'The account for {email} and cached email data will be removed from Nextcloud, but not from your email provider.', 'The account for {email} and cached email data will be removed from Nextcloud, but not from your email provider.',
{email: this.account.emailAddress} { email: this.account.emailAddress }
), ),
t('mail', 'Remove account {email}', {email: this.account.emailAddress}), t('mail', 'Remove account {email}', { email: this.account.emailAddress }),
{ {
type: OC.dialogs.YES_NO_BUTTONS, type: OC.dialogs.YES_NO_BUTTONS,
confirm: t('mail', 'Remove {email}', {email: this.account.emailAddress}), confirm: t('mail', 'Remove {email}', { email: this.account.emailAddress }),
confirmClasses: 'error', confirmClasses: 'error',
cancel: t('mail', 'Cancel'), cancel: t('mail', 'Cancel'),
}, },
@ -218,7 +216,7 @@ export default {
// TODO: update store and handle this more efficiently // TODO: update store and handle this more efficiently
location.href = generateUrl('/apps/mail') location.href = generateUrl('/apps/mail')
}) })
.catch((error) => logger.error('could not delete account', {error})) .catch((error) => logger.error('could not delete account', { error }))
} }
this.loading.delete = false this.loading.delete = false
} }
@ -226,13 +224,13 @@ export default {
}, },
changeAccountOrderUp() { changeAccountOrderUp() {
this.$store this.$store
.dispatch('moveAccount', {account: this.account, up: true}) .dispatch('moveAccount', { account: this.account, up: true })
.catch((error) => logger.error('could not move account up', {error})) .catch((error) => logger.error('could not move account up', { error }))
}, },
changeAccountOrderDown() { changeAccountOrderDown() {
this.$store this.$store
.dispatch('moveAccount', {account: this.account}) .dispatch('moveAccount', { account: this.account })
.catch((error) => logger.error('could not move account down', {error})) .catch((error) => logger.error('could not move account down', { error }))
}, },
changeShowSubscribedOnly(onlySubscribed) { changeShowSubscribedOnly(onlySubscribed) {
this.savingShowOnlySubscribed = true this.savingShowOnlySubscribed = true
@ -248,7 +246,7 @@ export default {
logger.info('show only subscribed folders updated to ' + onlySubscribed) logger.info('show only subscribed folders updated to ' + onlySubscribed)
}) })
.catch((error) => { .catch((error) => {
logger.error('could not update subscription mode', {error}) logger.error('could not update subscription mode', { error })
this.savingShowOnlySubscribed = false this.savingShowOnlySubscribed = false
throw error throw error
}) })

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

@ -31,16 +31,14 @@
:title="title" :title="title"
:to="to" :to="to"
:open.sync="showSubFolders" :open.sync="showSubFolders"
@update:menuOpen="onMenuToggle" @update:menuOpen="onMenuToggle">
>
<!-- actions --> <!-- actions -->
<template slot="actions"> <template slot="actions">
<template> <template>
<ActionText <ActionText
v-if="!account.isUnified && folder.specialRole !== 'flagged'" v-if="!account.isUnified && folder.specialRole !== 'flagged'"
icon="icon-info" icon="icon-info"
:title="folderId" :title="folderId">
>
{{ statsText }} {{ statsText }}
</ActionText> </ActionText>
@ -49,16 +47,14 @@
icon="icon-mail" icon="icon-mail"
:title="t('mail', 'Mark all as read')" :title="t('mail', 'Mark all as read')"
:disabled="loadingMarkAsRead" :disabled="loadingMarkAsRead"
@click="markAsRead" @click="markAsRead">
>
{{ t('mail', 'Mark all messages of this folder as read') }} {{ t('mail', 'Mark all messages of this folder as read') }}
</ActionButton> </ActionButton>
<ActionButton <ActionButton
v-if="!editing && top && !account.isUnified && folder.specialRole !== 'flagged'" v-if="!editing && top && !account.isUnified && folder.specialRole !== 'flagged'"
icon="icon-folder" icon="icon-folder"
@click="openCreateFolder" @click="openCreateFolder">
>
{{ t('mail', 'Add subfolder') }} {{ t('mail', 'Add subfolder') }}
</ActionButton> </ActionButton>
<ActionInput v-if="editing" icon="icon-folder" @submit.prevent.stop="createFolder" /> <ActionInput v-if="editing" icon="icon-folder" @submit.prevent.stop="createFolder" />
@ -71,8 +67,7 @@
icon="icon-settings" icon="icon-settings"
:title="t('mail', 'Clear cache')" :title="t('mail', 'Clear cache')"
:disabled="clearingCache" :disabled="clearingCache"
@click="clearCache" @click="clearCache">
>
{{ t('mail', 'Clear locally cached data, in case there are issues with synchronization.') }} {{ t('mail', 'Clear locally cached data, in case there are issues with synchronization.') }}
</ActionButton> </ActionButton>
</template> </template>
@ -87,8 +82,7 @@
:key="genId(subFolder)" :key="genId(subFolder)"
:account="account" :account="account"
:folder="subFolder" :folder="subFolder"
:top="false" :top="false" />
/>
</AppNavigationItem> </AppNavigationItem>
</template> </template>
@ -99,11 +93,11 @@ import ActionButton from '@nextcloud/vue/dist/Components/ActionButton'
import ActionInput from '@nextcloud/vue/dist/Components/ActionInput' import ActionInput from '@nextcloud/vue/dist/Components/ActionInput'
import ActionText from '@nextcloud/vue/dist/Components/ActionText' import ActionText from '@nextcloud/vue/dist/Components/ActionText'
import {clearCache} from '../service/MessageService' import { clearCache } from '../service/MessageService'
import {getFolderStats} from '../service/FolderService' import { getFolderStats } from '../service/FolderService'
import logger from '../logger' import logger from '../logger'
import {translatePlural as n} from '@nextcloud/l10n' import { translatePlural as n } from '@nextcloud/l10n'
import {translate as translateMailboxName} from '../i18n/MailboxTranslator' import { translate as translateMailboxName } from '../i18n/MailboxTranslator'
export default { export default {
name: 'NavigationFolder', name: 'NavigationFolder',
@ -148,8 +142,8 @@ export default {
computed: { computed: {
visible() { visible() {
return ( return (
this.account.showSubscribedOnly === false || this.account.showSubscribedOnly === false
(this.folder.attributes && this.folder.attributes.includes('\\subscribed')) || (this.folder.attributes && this.folder.attributes.includes('\\subscribed'))
) )
}, },
title() { title() {
@ -211,6 +205,8 @@ export default {
methods: { methods: {
/** /**
* Generate unique key id for a specific folder * Generate unique key id for a specific folder
* @param {Object} folder the folder to gen id for
* @returns {string}
*/ */
genId(folder) { genId(folder) {
return 'account-' + this.account.id + '_' + folder.id return 'account-' + this.account.id + '_' + folder.id
@ -237,10 +233,10 @@ export default {
try { try {
const stats = await getFolderStats(this.account.id, this.folder.id) const stats = await getFolderStats(this.account.id, this.folder.id)
logger.debug('loaded folder stats', {stats}) logger.debug('loaded folder stats', { stats })
this.folderStats = stats this.folderStats = stats
} catch (error) { } catch (error) {
this.folderStats = {error: true} this.folderStats = { error: true }
logger.error(`could not load folder stats for ${this.folder.id}`, error) logger.error(`could not load folder stats for ${this.folder.id}`, error)
} }
}, },
@ -257,7 +253,7 @@ export default {
name: withPrefix, name: withPrefix,
}) })
} catch (error) { } catch (error) {
logger.error(`could not create folder ${withPrefix}`, {error}) logger.error(`could not create folder ${withPrefix}`, { error })
throw error throw error
} finally { } finally {
this.editing = false this.editing = false
@ -279,7 +275,7 @@ export default {
folderId: this.folder.id, folderId: this.folder.id,
}) })
.then(() => logger.info(`folder ${this.folder.id} marked as read`)) .then(() => logger.info(`folder ${this.folder.id} marked as read`))
.catch((error) => logger.error(`could not mark folder ${this.folder.id} as read`, {error})) .catch((error) => logger.error(`could not mark folder ${this.folder.id} as read`, { error }))
.then(() => (this.loadingMarkAsRead = false)) .then(() => (this.loadingMarkAsRead = false))
}, },
async clearCache() { async clearCache() {

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

@ -5,9 +5,7 @@
v-else-if="error" v-else-if="error"
:error="error && error.message ? error.message : t('mail', 'Not found')" :error="error && error.message ? error.message : t('mail', 'Not found')"
:message="errorMessage" :message="errorMessage"
:data="error" :data="error" />
>
</Error>
<Composer <Composer
v-else v-else
:from-account="composerData.accountId" :from-account="composerData.accountId"
@ -19,24 +17,23 @@
:draft="saveDraft" :draft="saveDraft"
:send="sendMessage" :send="sendMessage"
:reply-to="composerData.replyTo" :reply-to="composerData.replyTo"
:forward-from="composerData.forwardFrom" :forward-from="composerData.forwardFrom" />
/>
</AppContentDetails> </AppContentDetails>
</template> </template>
<script> <script>
import AppContentDetails from '@nextcloud/vue/dist/Components/AppContentDetails' import AppContentDetails from '@nextcloud/vue/dist/Components/AppContentDetails'
import Axios from '@nextcloud/axios' import Axios from '@nextcloud/axios'
import {generateUrl} from '@nextcloud/router' import { generateUrl } from '@nextcloud/router'
import {buildForwardSubject, buildReplySubject, buildRecipients as buildReplyRecipients} from '../ReplyBuilder' import { buildForwardSubject, buildReplySubject, buildRecipients as buildReplyRecipients } from '../ReplyBuilder'
import Composer from './Composer' import Composer from './Composer'
import Error from './Error' import Error from './Error'
import {getRandomMessageErrorMessage} from '../util/ErrorMessageFactory' import { getRandomMessageErrorMessage } from '../util/ErrorMessageFactory'
import {detect, html, plain, toPlain} from '../util/text' import { detect, html, plain, toPlain } from '../util/text'
import Loading from './Loading' import Loading from './Loading'
import logger from '../logger' import logger from '../logger'
import {saveDraft, sendMessage} from '../service/MessageService' import { saveDraft, sendMessage } from '../service/MessageService'
export default { export default {
name: 'NewMessageDetail', name: 'NewMessageDetail',
@ -59,7 +56,7 @@ export default {
computed: { computed: {
composerData() { composerData() {
if (this.draft !== undefined) { if (this.draft !== undefined) {
logger.info('todo: handle draft data', {draft: this.draft}) logger.info('todo: handle draft data', { draft: this.draft })
return { return {
to: this.draft.to, to: this.draft.to,
cc: this.draft.cc, cc: this.draft.cc,
@ -70,7 +67,7 @@ export default {
} else if (this.$route.query.uuid !== undefined) { } else if (this.$route.query.uuid !== undefined) {
// Forward or reply to a message // Forward or reply to a message
const message = this.original const message = this.original
logger.debug('forwarding or replying to message', {message}) logger.debug('forwarding or replying to message', { message })
if (this.$route.params.messageUuid === 'reply') { if (this.$route.params.messageUuid === 'reply') {
logger.debug('simple reply') logger.debug('simple reply')
@ -85,7 +82,7 @@ export default {
replyTo: message, replyTo: message,
} }
} else if (this.$route.params.messageUuid === 'replyAll') { } else if (this.$route.params.messageUuid === 'replyAll') {
logger.debug('replying to all', {original: this.original}) logger.debug('replying to all', { original: this.original })
const account = this.$store.getters.getAccount(message.accountId) const account = this.$store.getters.getAccount(message.accountId)
const recipients = buildReplyRecipients(message, { const recipients = buildReplyRecipients(message, {
email: account.emailAddress, email: account.emailAddress,
@ -185,7 +182,7 @@ export default {
this.draft = draft this.draft = draft
if (this.draft === undefined) { if (this.draft === undefined) {
logger.info('draft could not be found', {draftUid}) logger.info('draft could not be found', { draftUid })
this.errorMessage = getRandomMessageErrorMessage() this.errorMessage = getRandomMessageErrorMessage()
this.loading = false this.loading = false
return return
@ -194,7 +191,7 @@ export default {
this.loading = false this.loading = false
}) })
.catch((error) => { .catch((error) => {
logger.error('could not load draft ' + draftUid, {error}) logger.error('could not load draft ' + draftUid, { error })
if (error.isError) { if (error.isError) {
this.errorMessage = t('mail', 'Could not load your draft') this.errorMessage = t('mail', 'Could not load your draft')
this.error = error this.error = error
@ -214,7 +211,7 @@ export default {
return return
} }
logger.debug('original message fetched', {message}) logger.debug('original message fetched', { message })
this.original = message this.original = message
let body = plain(message.body || '') let body = plain(message.body || '')
@ -232,7 +229,7 @@ export default {
} }
this.originalBody = body this.originalBody = body
} catch (error) { } catch (error) {
logger.error('could not load original message ' + uid, {error}) logger.error('could not load original message ' + uid, { error })
if (error.isError) { if (error.isError) {
this.errorMessage = t('mail', 'Could not load original message') this.errorMessage = t('mail', 'Could not load original message')
this.error = error this.error = error
@ -251,7 +248,7 @@ export default {
...data, ...data,
body: data.isHtml ? data.body.value : toPlain(data.body).value, body: data.isHtml ? data.body.value : toPlain(data.body).value,
} }
return saveDraft(data.account, dataForServer).then(({uid}) => { return saveDraft(data.account, dataForServer).then(({ uid }) => {
if (this.draft === undefined) { if (this.draft === undefined) {
return uid return uid
} }
@ -279,7 +276,7 @@ export default {
}) })
}, },
sendMessage(data) { sendMessage(data) {
logger.debug('sending message', {data}) logger.debug('sending message', { data })
const dataForServer = { const dataForServer = {
...data, ...data,
body: data.isHtml ? data.body.value : toPlain(data.body).value, body: data.isHtml ? data.body.value : toPlain(data.body).value,

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

@ -21,9 +21,9 @@
<template> <template>
<AppContentDetails> <AppContentDetails>
<div class="icon icon-mail"></div> <div class="icon icon-mail" />
<h2>{{ t('mail', 'No message selected') }}</h2> <h2>{{ t('mail', 'No message selected') }}</h2>
<p></p> <p />
</AppContentDetails> </AppContentDetails>
</template> </template>

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

@ -25,13 +25,15 @@
<p class="settings-hint"> <p class="settings-hint">
{{ t('mail', 'A signature is added to the text of new messages and replies.') }} {{ t('mail', 'A signature is added to the text of new messages and replies.') }}
</p> </p>
<TextEditor v-model="signature" :html="true" :placeholder="t('mail', 'Signature …')" :bus="bus" /> <TextEditor v-model="signature"
:html="true"
:placeholder="t('mail', 'Signature …')"
:bus="bus" />
<button <button
class="primary" class="primary"
:class="loading ? 'icon-loading-small-dark' : 'icon-checkmark-white'" :class="loading ? 'icon-loading-small-dark' : 'icon-checkmark-white'"
:disabled="loading" :disabled="loading"
@click="saveSignature" @click="saveSignature">
>
{{ t('mail', 'Save signature') }} {{ t('mail', 'Save signature') }}
</button> </button>
<button v-if="signature" class="button-text" @click="deleteSignature"> <button v-if="signature" class="button-text" @click="deleteSignature">
@ -43,7 +45,7 @@
<script> <script>
import logger from '../logger' import logger from '../logger'
import TextEditor from './TextEditor' import TextEditor from './TextEditor'
import {detect, html, toHtml} from '../util/text' import { detect, toHtml } from '../util/text'
import Vue from 'vue' import Vue from 'vue'
export default { export default {
@ -73,14 +75,14 @@ export default {
this.loading = true this.loading = true
this.$store this.$store
.dispatch('updateAccountSignature', {account: this.account, signature: null}) .dispatch('updateAccountSignature', { account: this.account, signature: null })
.then(() => { .then(() => {
logger.info('signature deleted') logger.info('signature deleted')
this.signature = '' this.signature = ''
this.loading = false this.loading = false
}) })
.catch((error) => { .catch((error) => {
logger.error('could not delete account signature', {error}) logger.error('could not delete account signature', { error })
throw error throw error
}) })
}, },
@ -88,13 +90,13 @@ export default {
this.loading = true this.loading = true
this.$store this.$store
.dispatch('updateAccountSignature', {account: this.account, signature: this.signature}) .dispatch('updateAccountSignature', { account: this.account, signature: this.signature })
.then(() => { .then(() => {
logger.info('signature updated') logger.info('signature updated')
this.loading = false this.loading = false
}) })
.catch((error) => { .catch((error) => {
logger.error('could not update account signature', {error}) logger.error('could not update account signature', { error })
throw error throw error
}) })
}, },

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

@ -26,8 +26,7 @@
:config="config" :config="config"
:editor="editor" :editor="editor"
@input="onInput" @input="onInput"
@ready="onEditorReady" @ready="onEditorReady" />
></ckeditor>
</template> </template>
<script> <script>
@ -42,7 +41,7 @@ import ItalicPlugin from '@ckeditor/ckeditor5-basic-styles/src/italic'
import LinkPlugin from '@ckeditor/ckeditor5-link/src/link' import LinkPlugin from '@ckeditor/ckeditor5-link/src/link'
import ParagraphPlugin from '@ckeditor/ckeditor5-paragraph/src/paragraph' import ParagraphPlugin from '@ckeditor/ckeditor5-paragraph/src/paragraph'
import {getLanguage} from '@nextcloud/l10n' import { getLanguage } from '@nextcloud/l10n'
import logger from '../logger' import logger from '../logger'
@ -135,7 +134,7 @@ export default {
onEditorReady(editor) { onEditorReady(editor) {
const schema = editor.model.schema const schema = editor.model.schema
logger.debug('CKEditor editor is ready', {editor, schema}) logger.debug('CKEditor editor is ready', { editor, schema })
this.editorInstance = editor this.editorInstance = editor

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

@ -25,9 +25,9 @@
v-for="(calendar, idx) in cals" v-for="(calendar, idx) in cals"
:key="idx" :key="idx"
:icon="calendar.loading ? 'icon-loading-small' : 'icon-add'" :icon="calendar.loading ? 'icon-loading-small' : 'icon-add'"
@click="onImport(calendar)" @click="onImport(calendar)">
>{{ t('mail', 'Import into {calendar}', {calendar: calendar.displayname}) }}</ActionButton {{ t('mail', 'Import into {calendar}', {calendar: calendar.displayname}) }}
> </ActionButton>
</Actions> </Actions>
</template> </template>

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

@ -22,10 +22,18 @@
<template> <template>
<div class="reservation"> <div class="reservation">
<div class="event"> <div class="event">
<div class="event-name">{{ eventName }}</div> <div class="event-name">
<div v-if="location" class="venue">{{ location }}</div> {{ eventName }}
<div v-if="date">{{ date }}</div> </div>
<div v-if="time">{{ time }}</div> <div v-if="location" class="venue">
{{ location }}
</div>
<div v-if="date">
{{ date }}
</div>
<div v-if="time">
{{ time }}
</div>
</div> </div>
<CalendarImport v-if="canImport" :calendars="calendars" :handler="handleImport" /> <CalendarImport v-if="canImport" :calendars="calendars" :handler="handleImport" />
</div> </div>
@ -35,15 +43,15 @@
import ical from 'ical.js' import ical from 'ical.js'
import md5 from 'md5' import md5 from 'md5'
import moment from '@nextcloud/moment' import moment from '@nextcloud/moment'
import {showError, showSuccess} from '@nextcloud/dialogs' import { showError, showSuccess } from '@nextcloud/dialogs'
import CalendarImport from './CalendarImport' import CalendarImport from './CalendarImport'
import {importCalendarEvent} from '../../service/DAVService' import { importCalendarEvent } from '../../service/DAVService'
import logger from '../../logger' import logger from '../../logger'
export default { export default {
name: 'EventReservation', name: 'EventReservation',
components: {CalendarImport}, components: { CalendarImport },
props: { props: {
data: { data: {
type: Object, type: Object,
@ -124,14 +132,14 @@ export default {
const cal = new ical.Component('VCALENDAR') const cal = new ical.Component('VCALENDAR')
cal.addSubcomponent(event) cal.addSubcomponent(event)
logger.debug('generated calendar event from event reservation data', {ical: cal.toString()}) logger.debug('generated calendar event from event reservation data', { ical: cal.toString() })
return importCalendarEvent(calendar.url)(cal.toString()) return importCalendarEvent(calendar.url)(cal.toString())
.then(() => { .then(() => {
logger.debug('event successfully imported') logger.debug('event successfully imported')
showSuccess(t('mail', 'Event imported into {calendar}', {calendar: calendar.displayname})) showSuccess(t('mail', 'Event imported into {calendar}', { calendar: calendar.displayname }))
}) })
.catch((error) => { .catch((error) => {
logger.error('Could not import event', {error}) logger.error('Could not import event', { error })
showError(t('mail', 'Could not create event')) showError(t('mail', 'Could not create event'))
}) })
}, },

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

@ -1,22 +1,42 @@
<template> <template>
<div class="reservation"> <div class="reservation">
<div class="departure"> <div class="departure">
<div class="iata">{{ data.reservationFor.departureAirport.iataCode }}</div> <div class="iata">
<div class="airport">{{ data.reservationFor.departureAirport.name }}</div> {{ data.reservationFor.departureAirport.iataCode }}
<div v-if="departureDate">{{ departureDate }}</div> </div>
<div v-if="departureTime">{{ departureTime }}</div> <div class="airport">
{{ data.reservationFor.departureAirport.name }}
</div>
<div v-if="departureDate">
{{ departureDate }}
</div>
<div v-if="departureTime">
{{ departureTime }}
</div>
</div> </div>
<div class="connection"> <div class="connection">
<div><AirplaneIcon :title="t('mail', 'Airplane')" /></div> <div><AirplaneIcon :title="t('mail', 'Airplane')" /></div>
<div>{{ flightNumber }}</div> <div>{{ flightNumber }}</div>
<div v-if="reservation">{{ t('mail', 'Reservation {id}', {id: reservation}) }}</div> <div v-if="reservation">
<div v-else><ArrowIcon decorative /></div> {{ t('mail', 'Reservation {id}', {id: reservation}) }}
</div>
<div v-else>
<ArrowIcon decorative />
</div>
</div> </div>
<div class="arrival"> <div class="arrival">
<div class="iata">{{ data.reservationFor.arrivalAirport.iataCode }}</div> <div class="iata">
<div class="airport">{{ data.reservationFor.arrivalAirport.name }}</div> {{ data.reservationFor.arrivalAirport.iataCode }}
<div v-if="arrivalDate">{{ arrivalDate }}</div> </div>
<div v-if="arrivalTime">{{ arrivalTime }}</div> <div class="airport">
{{ data.reservationFor.arrivalAirport.name }}
</div>
<div v-if="arrivalDate">
{{ arrivalDate }}
</div>
<div v-if="arrivalTime">
{{ arrivalTime }}
</div>
</div> </div>
<CalendarImport :calendars="calendars" :handler="handleImport" /> <CalendarImport :calendars="calendars" :handler="handleImport" />
</div> </div>
@ -28,10 +48,10 @@ import ArrowIcon from 'vue-material-design-icons/ArrowRight'
import ical from 'ical.js' import ical from 'ical.js'
import md5 from 'md5' import md5 from 'md5'
import moment from '@nextcloud/moment' import moment from '@nextcloud/moment'
import {showError, showSuccess} from '@nextcloud/dialogs' import { showError, showSuccess } from '@nextcloud/dialogs'
import CalendarImport from './CalendarImport' import CalendarImport from './CalendarImport'
import {importCalendarEvent} from '../../service/DAVService' import { importCalendarEvent } from '../../service/DAVService'
import logger from '../../logger' import logger from '../../logger'
export default { export default {
@ -118,15 +138,15 @@ export default {
const cal = new ical.Component('VCALENDAR') const cal = new ical.Component('VCALENDAR')
cal.addSubcomponent(event) cal.addSubcomponent(event)
logger.debug('generated calendar event from flight reservation data', {ical: cal.toString()}) logger.debug('generated calendar event from flight reservation data', { ical: cal.toString() })
return importCalendarEvent(calendar.url)(cal.toString()) return importCalendarEvent(calendar.url)(cal.toString())
.then(() => { .then(() => {
logger.debug('event successfully imported') logger.debug('event successfully imported')
showSuccess(t('mail', 'Event imported into {calendar}', {calendar: calendar.displayname})) showSuccess(t('mail', 'Event imported into {calendar}', { calendar: calendar.displayname }))
}) })
.catch((error) => { .catch((error) => {
logger.error('Could not import event', {error}) logger.error('Could not import event', { error })
showError(t('mail', 'Could not create event')) showError(t('mail', 'Could not create event'))
}) })
}, },

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

@ -1,9 +1,15 @@
<template> <template>
<div class="reservation"> <div class="reservation">
<div class="departure"> <div class="departure">
<div class="station">{{ data.reservationFor.departureStation.name }}</div> <div class="station">
<div v-if="departureDate">{{ departureDate }}</div> {{ data.reservationFor.departureStation.name }}
<div v-if="departureTime">{{ departureTime }}</div> </div>
<div v-if="departureDate">
{{ departureDate }}
</div>
<div v-if="departureTime">
{{ departureTime }}
</div>
</div> </div>
<div class="connection"> <div class="connection">
<div><TrainIcon :title="t('mail', 'Train')" /></div> <div><TrainIcon :title="t('mail', 'Train')" /></div>
@ -11,9 +17,15 @@
<div><ArrowIcon decorative /></div> <div><ArrowIcon decorative /></div>
</div> </div>
<div class="arrival"> <div class="arrival">
<div class="station">{{ data.reservationFor.arrivalStation.name }}</div> <div class="station">
<div v-if="arrivalDate">{{ arrivalDate }}</div> {{ data.reservationFor.arrivalStation.name }}
<div v-if="arrivalTime">{{ arrivalTime }}</div> </div>
<div v-if="arrivalDate">
{{ arrivalDate }}
</div>
<div v-if="arrivalTime">
{{ arrivalTime }}
</div>
</div> </div>
<CalendarImport v-if="canImport" :calendars="calendars" :handler="handleImport" /> <CalendarImport v-if="canImport" :calendars="calendars" :handler="handleImport" />
</div> </div>
@ -24,11 +36,11 @@ import ArrowIcon from 'vue-material-design-icons/ArrowRight'
import ical from 'ical.js' import ical from 'ical.js'
import md5 from 'md5' import md5 from 'md5'
import moment from '@nextcloud/moment' import moment from '@nextcloud/moment'
import {showError, showSuccess} from '@nextcloud/dialogs' import { showError, showSuccess } from '@nextcloud/dialogs'
import TrainIcon from 'vue-material-design-icons/Train' import TrainIcon from 'vue-material-design-icons/Train'
import CalendarImport from './CalendarImport' import CalendarImport from './CalendarImport'
import {importCalendarEvent} from '../../service/DAVService' import { importCalendarEvent } from '../../service/DAVService'
import logger from '../../logger' import logger from '../../logger'
export default { export default {
@ -85,8 +97,8 @@ export default {
}, },
canImport() { canImport() {
return ( return (
('departureTime' in this.data.reservationFor && 'arrivalTime' in this.data.reservationFor) || ('departureTime' in this.data.reservationFor && 'arrivalTime' in this.data.reservationFor)
'departureDay' in this.data.reservationFor || 'departureDay' in this.data.reservationFor
) )
}, },
}, },
@ -130,15 +142,15 @@ export default {
const cal = new ical.Component('VCALENDAR') const cal = new ical.Component('VCALENDAR')
cal.addSubcomponent(event) cal.addSubcomponent(event)
logger.debug('generated calendar event from train reservation data', {ical: cal.toString()}) logger.debug('generated calendar event from train reservation data', { ical: cal.toString() })
return importCalendarEvent(calendar.url)(cal.toString()) return importCalendarEvent(calendar.url)(cal.toString())
.then(() => { .then(() => {
logger.debug('event successfully imported') logger.debug('event successfully imported')
showSuccess(t('mail', 'Event imported into {calendar}', {calendar: calendar.displayname})) showSuccess(t('mail', 'Event imported into {calendar}', { calendar: calendar.displayname }))
}) })
.catch((error) => { .catch((error) => {
logger.error('Could not import event', {error}) logger.error('Could not import event', { error })
showError(t('mail', 'Could not create event')) showError(t('mail', 'Could not create event'))
}) })
}, },

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

@ -25,8 +25,8 @@
<span v-if="data.uid">uid={{ data.uid }}</span> <span v-if="data.uid">uid={{ data.uid }}</span>
<span v-if="data.email">email={{ email }}</span> <span v-if="data.email">email={{ email }}</span>
</b> </b>
<br /> <br>
{{ t('mail', 'Email: {email}', {email}) }}<br /> {{ t('mail', 'Email: {email}', {email}) }}<br>
{{ {{
t('mail', 'IMAP: {user} on {host}:{port} ({ssl} encryption)', { t('mail', 'IMAP: {user} on {host}:{port} ({ssl} encryption)', {
user: imapUser, user: imapUser,
@ -34,7 +34,7 @@
port: imapPort, port: imapPort,
ssl: imapSslMode, ssl: imapSslMode,
}) })
}}<br /> }}<br>
{{ {{
t('mail', 'SMTP: {user} on {host}:{port} ({ssl} encryption)', { t('mail', 'SMTP: {user} on {host}:{port} ({ssl} encryption)', {
user: smtpUser, user: smtpUser,
@ -42,7 +42,7 @@
port: smtpPort, port: smtpPort,
ssl: smtpSslMode, ssl: smtpSslMode,
}) })
}}<br /> }}<br>
</div> </div>
</template> </template>

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

@ -37,7 +37,10 @@
}} }}
</p> </p>
<div> <div>
<input id="mail-provision-toggle" v-model="active" type="checkbox" class="checkbox" /> <input id="mail-provision-toggle"
v-model="active"
type="checkbox"
class="checkbox">
<label for="mail-provision-toggle"> <label for="mail-provision-toggle">
{{ t('mail', 'Provision an account for every user') }} {{ t('mail', 'Provision an account for every user') }}
</label> </label>
@ -45,55 +48,55 @@
<div v-if="active" class="form-preview-row"> <div v-if="active" class="form-preview-row">
<form @submit.prevent="submit"> <form @submit.prevent="submit">
<div class="settings-group"> <div class="settings-group">
<div class="group-title">{{ t('mail', 'General') }}</div> <div class="group-title">
{{ t('mail', 'General') }}
</div>
<div class="group-inputs"> <div class="group-inputs">
<label for="mail-provision-email"> {{ t('mail', 'Email address') }}* </label> <label for="mail-provision-email"> {{ t('mail', 'Email address') }}* </label>
<br /> <br>
<input <input
id="mail-provision-email" id="mail-provision-email"
v-model="emailTemplate" v-model="emailTemplate"
:disabled="loading" :disabled="loading"
name="email" name="email"
type="text" type="text">
/>
</div> </div>
</div> </div>
<div class="settings-group"> <div class="settings-group">
<div class="group-title">{{ t('mail', 'IMAP') }}</div> <div class="group-title">
{{ t('mail', 'IMAP') }}
</div>
<div class="group-inputs"> <div class="group-inputs">
<label for="mail-provision-imap-user"> <label for="mail-provision-imap-user">
{{ t('mail', 'User') }}* {{ t('mail', 'User') }}*
<br /> <br>
<input <input
id="mail-provision-imap-user" id="mail-provision-imap-user"
v-model="imapUser" v-model="imapUser"
:disabled="loading" :disabled="loading"
name="email" name="email"
type="text" type="text">
/>
</label> </label>
<div class="flex-row"> <div class="flex-row">
<label for="mail-provision-imap-host"> <label for="mail-provision-imap-host">
{{ t('mail', 'Host') }} {{ t('mail', 'Host') }}
<br /> <br>
<input <input
id="mail-provision-imap-host" id="mail-provision-imap-host"
v-model="imapHost" v-model="imapHost"
:disabled="loading" :disabled="loading"
name="email" name="email"
type="text" type="text">
/>
</label> </label>
<label for="mail-provision-imap-port"> <label for="mail-provision-imap-port">
{{ t('mail', 'Port') }} {{ t('mail', 'Port') }}
<br /> <br>
<input <input
id="mail-provision-imap-port" id="mail-provision-imap-port"
v-model="imapPort" v-model="imapPort"
:disabled="loading" :disabled="loading"
name="email" name="email"
type="number" type="number">
/>
</label> </label>
</div> </div>
<div class="flex-row"> <div class="flex-row">
@ -103,81 +106,71 @@
type="radio" type="radio"
name="man-imap-sec" name="man-imap-sec"
:disabled="loading" :disabled="loading"
value="none" value="none">
/>
<label <label
class="button" class="button"
for="mail-provision-imap-user-none" for="mail-provision-imap-user-none"
:class="{primary: imapSslMode === 'none'}" :class="{primary: imapSslMode === 'none'}">{{ t('mail', 'None') }}</label>
>{{ t('mail', 'None') }}</label
>
<input <input
id="mail-provision-imap-user-ssl" id="mail-provision-imap-user-ssl"
v-model="imapSslMode" v-model="imapSslMode"
type="radio" type="radio"
name="man-imap-sec" name="man-imap-sec"
:disabled="loading" :disabled="loading"
value="ssl" value="ssl">
/>
<label <label
class="button" class="button"
for="mail-provision-imap-user-ssl" for="mail-provision-imap-user-ssl"
:class="{primary: imapSslMode === 'ssl'}" :class="{primary: imapSslMode === 'ssl'}">{{ t('mail', 'SSL/TLS') }}</label>
>{{ t('mail', 'SSL/TLS') }}</label
>
<input <input
id="mail-provision-imap-user-tls" id="mail-provision-imap-user-tls"
v-model="imapSslMode" v-model="imapSslMode"
type="radio" type="radio"
name="man-imap-sec" name="man-imap-sec"
:disabled="loading" :disabled="loading"
value="tls" value="tls">
/>
<label <label
class="button" class="button"
for="mail-provision-imap-user-tls" for="mail-provision-imap-user-tls"
:class="{primary: imapSslMode === 'tls'}" :class="{primary: imapSslMode === 'tls'}">{{ t('mail', 'STARTTLS') }}</label>
>{{ t('mail', 'STARTTLS') }}</label
>
</div> </div>
</div> </div>
</div> </div>
<div class="settings-group"> <div class="settings-group">
<div class="group-title">{{ t('mail', 'SMTP') }}</div> <div class="group-title">
{{ t('mail', 'SMTP') }}
</div>
<div class="group-inputs"> <div class="group-inputs">
<label for="mail-provision-smtp-user"> <label for="mail-provision-smtp-user">
{{ t('mail', 'User') }}* {{ t('mail', 'User') }}*
<br /> <br>
<input <input
id="mail-provision-smtp-user" id="mail-provision-smtp-user"
v-model="smtpUser" v-model="smtpUser"
:disabled="loading" :disabled="loading"
name="email" name="email"
type="text" type="text">
/>
</label> </label>
<div class="flex-row"> <div class="flex-row">
<label for="mail-provision-imap-host"> <label for="mail-provision-imap-host">
{{ t('mail', 'Host') }} {{ t('mail', 'Host') }}
<br /> <br>
<input <input
id="mail-provision-smtp-host" id="mail-provision-smtp-host"
v-model="smtpHost" v-model="smtpHost"
:disabled="loading" :disabled="loading"
name="email" name="email"
type="text" type="text">
/>
</label> </label>
<label for="mail-provision-smtp-port"> <label for="mail-provision-smtp-port">
{{ t('mail', 'Port') }} {{ t('mail', 'Port') }}
<br /> <br>
<input <input
id="mail-provision-smtp-port" id="mail-provision-smtp-port"
v-model="smtpPort" v-model="smtpPort"
:disabled="loading" :disabled="loading"
name="email" name="email"
type="number" type="number">
/>
</label> </label>
</div> </div>
<div class="flex-row"> <div class="flex-row">
@ -187,61 +180,50 @@
type="radio" type="radio"
name="man-smtp-sec" name="man-smtp-sec"
:disabled="loading" :disabled="loading"
value="none" value="none">
/>
<label <label
class="button" class="button"
for="mail-provision-smtp-user-none" for="mail-provision-smtp-user-none"
:class="{primary: smtpSslMode === 'none'}" :class="{primary: smtpSslMode === 'none'}">{{ t('mail', 'None') }}</label>
>{{ t('mail', 'None') }}</label
>
<input <input
id="mail-provision-smtp-user-ssl" id="mail-provision-smtp-user-ssl"
v-model="smtpSslMode" v-model="smtpSslMode"
type="radio" type="radio"
name="man-smtp-sec" name="man-smtp-sec"
:disabled="loading" :disabled="loading"
value="ssl" value="ssl">
/>
<label <label
class="button" class="button"
for="mail-provision-smtp-user-ssl" for="mail-provision-smtp-user-ssl"
:class="{primary: smtpSslMode === 'ssl'}" :class="{primary: smtpSslMode === 'ssl'}">{{ t('mail', 'SSL/TLS') }}</label>
>{{ t('mail', 'SSL/TLS') }}</label
>
<input <input
id="mail-provision-smtp-user-tls" id="mail-provision-smtp-user-tls"
v-model="smtpSslMode" v-model="smtpSslMode"
type="radio" type="radio"
name="man-smtp-sec" name="man-smtp-sec"
:disabled="loading" :disabled="loading"
value="tls" value="tls">
/>
<label <label
class="button" class="button"
for="mail-provision-smtp-user-tls" for="mail-provision-smtp-user-tls"
:class="{primary: smtpSslMode === 'tls'}" :class="{primary: smtpSslMode === 'tls'}">{{ t('mail', 'STARTTLS') }}</label>
>{{ t('mail', 'STARTTLS') }}</label
>
</div> </div>
</div> </div>
</div> </div>
<div class="settings-group"> <div class="settings-group">
<div class="group-title"></div> <div class="group-title" />
<div class="group-inputs"> <div class="group-inputs">
<input <input
type="submit" type="submit"
class="primary" class="primary"
:disabled="loading" :disabled="loading"
:value="t('mail', 'Apply and create/update for all users')" :value="t('mail', 'Apply and create/update for all users')">
/>
<input <input
type="button" type="button"
:disabled="loading" :disabled="loading"
:value="t('mail', 'Disable and un-provision existing accounts')" :value="t('mail', 'Disable and un-provision existing accounts')"
@click="disable" @click="disable">
/> <br>
<br />
<small>{{ <small>{{
t('mail', "* %USERID% and %EMAIL% will be replaced with the user's UID and email") t('mail', "* %USERID% and %EMAIL% will be replaced with the user's UID and email")
}}</small> }}</small>
@ -267,11 +249,11 @@
<script> <script>
import logger from '../../logger' import logger from '../../logger'
import ProvisionPreview from './ProvisionPreview' import ProvisionPreview from './ProvisionPreview'
import {disableProvisioning, saveProvisioningSettings} from '../../service/SettingsService' import { disableProvisioning, saveProvisioningSettings } from '../../service/SettingsService'
export default { export default {
name: 'ProvisioningSettings', name: 'ProvisioningSettings',
components: {ProvisionPreview}, components: { ProvisionPreview },
props: { props: {
settings: { settings: {
type: Object, type: Object,
@ -317,7 +299,7 @@ export default {
}, },
}, },
beforeMount() { beforeMount() {
logger.debug('provisioning settings loaded', {settings: this.settings}) logger.debug('provisioning settings loaded', { settings: this.settings })
}, },
methods: { methods: {
submit() { submit() {
@ -339,7 +321,7 @@ export default {
}) })
.catch((error) => { .catch((error) => {
// TODO: show user feedback // TODO: show user feedback
logger.error('Could not save provisioning settings', {error}) logger.error('Could not save provisioning settings', { error })
}) })
.then(() => { .then(() => {
this.loading = false this.loading = false
@ -353,7 +335,7 @@ export default {
logger.info('deprovisioned successfully') logger.info('deprovisioned successfully')
}) })
.catch((error) => { .catch((error) => {
logger.error('could not deprovision accounts', {error}) logger.error('could not deprovision accounts', { error })
}) })
.then(() => { .then(() => {
this.active = false this.active = false

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

@ -26,11 +26,11 @@ let mailvelope
const loadMailvelopeStatically = () => window.mailvelope const loadMailvelopeStatically = () => window.mailvelope
const loadMailvelopeDynamically = () => const loadMailvelopeDynamically = () =>
new Promise((res) => { new Promise((resolve) => {
window.addEventListener('mailvelope', () => res(window.mailvelope), false) window.addEventListener('mailvelope', () => resolve(window.mailvelope), false)
}) })
export const getMailvelope = async () => { export const getMailvelope = async() => {
if (mailvelope) { if (mailvelope) {
return mailvelope return mailvelope
} }

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

@ -1,4 +1,4 @@
/* /**
* @copyright 2020 Christoph Wurst <christoph@winzerhof-wurst.at> * @copyright 2020 Christoph Wurst <christoph@winzerhof-wurst.at>
* *
* @author 2020 Christoph Wurst <christoph@winzerhof-wurst.at> * @author 2020 Christoph Wurst <christoph@winzerhof-wurst.at>
@ -19,11 +19,9 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>. * along with this program. If not, see <http://www.gnu.org/licenses/>.
*/ */
import startsWith from 'lodash/fp/startsWith'
/** /**
* @param {Text} message * @param {Text} message the message
* @return {boolean|*} * @returns {boolean|*}
*/ */
export const isPgpgMessage = (message) => export const isPgpgMessage = (message) =>
message.format === 'plain' && message.value.startsWith('-----BEGIN PGP MESSAGE-----') message.format === 'plain' && message.value.startsWith('-----BEGIN PGP MESSAGE-----')

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

@ -1,4 +1,4 @@
/* /**
* @copyright 2020 Christoph Wurst <christoph@winzerhof-wurst.at> * @copyright 2020 Christoph Wurst <christoph@winzerhof-wurst.at>
* *
* @author 2020 Christoph Wurst <christoph@winzerhof-wurst.at> * @author 2020 Christoph Wurst <christoph@winzerhof-wurst.at>
@ -20,6 +20,7 @@
*/ */
export default class MailboxLockedError extends Error { export default class MailboxLockedError extends Error {
constructor(message) { constructor(message) {
super(message) super(message)
this.name = MailboxLockedError.getName() this.name = MailboxLockedError.getName()
@ -29,4 +30,5 @@ export default class MailboxLockedError extends Error {
static getName() { static getName() {
return 'MailboxLockedError' return 'MailboxLockedError'
} }
} }

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

@ -20,6 +20,7 @@
*/ */
export default class MailboxNotCachedError extends Error { export default class MailboxNotCachedError extends Error {
constructor(message) { constructor(message) {
super(message) super(message)
this.name = MailboxNotCachedError.getName() this.name = MailboxNotCachedError.getName()
@ -29,4 +30,5 @@ export default class MailboxNotCachedError extends Error {
static getName() { static getName() {
return 'MailboxNotCachedError' return 'MailboxNotCachedError'
} }
} }

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

@ -20,6 +20,7 @@
*/ */
export default class SyncIncompleteError extends Error { export default class SyncIncompleteError extends Error {
constructor(message) { constructor(message) {
super(message) super(message)
this.name = SyncIncompleteError.getName() this.name = SyncIncompleteError.getName()
@ -29,4 +30,5 @@ export default class SyncIncompleteError extends Error {
static getName() { static getName() {
return 'SyncIncompleteError' return 'SyncIncompleteError'
} }
} }

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

@ -1,4 +1,4 @@
/* /**
* @copyright 2020 Christoph Wurst <christoph@winzerhof-wurst.at> * @copyright 2020 Christoph Wurst <christoph@winzerhof-wurst.at>
* *
* @author 2020 Christoph Wurst <christoph@winzerhof-wurst.at> * @author 2020 Christoph Wurst <christoph@winzerhof-wurst.at>
@ -28,7 +28,8 @@ const map = {
} }
/** /**
* @param {object} axiosError * @param {Object} axiosError the axios Error
* @returns {Error}
*/ */
export const convertAxiosError = (axiosError) => { export const convertAxiosError = (axiosError) => {
if (!('response' in axiosError)) { if (!('response' in axiosError)) {

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

@ -1,4 +1,4 @@
/* /**
* @copyright 2020 Christoph Wurst <christoph@winzerhof-wurst.at> * @copyright 2020 Christoph Wurst <christoph@winzerhof-wurst.at>
* *
* @author 2020 Christoph Wurst <christoph@winzerhof-wurst.at> * @author 2020 Christoph Wurst <christoph@winzerhof-wurst.at>
@ -20,15 +20,15 @@
*/ */
/** /**
* @param {Error} error * @param {Error} error error
* @param {object} matches * @param {object} matches matches
*/ */
export const matchError = async (error, matches) => { export const matchError = async(error, matches) => {
if (error.name in matches) { if (error.name in matches) {
return await Promise.resolve(matches[error.name](error)) return await Promise.resolve(matches[error.name](error))
} }
if ('default' in matches) { if ('default' in matches) {
return await Promise.resolve(matches['default'](error)) return await Promise.resolve(matches.default(error))
} }
throw new Error('unhandled error in match: ' + error.name) throw new Error('unhandled error in match: ' + error.name)
} }

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

@ -28,7 +28,7 @@ export const parseErrorResponse = (resp) => {
return resp return resp
} }
const {debug, type, code, message, trace} = resp.data.data || {} const { debug, type, code, message, trace } = resp.data.data || {}
return { return {
isError: true, isError: true,

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

@ -19,7 +19,7 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>. * along with this program. If not, see <http://www.gnu.org/licenses/>.
*/ */
import {translate as t} from '@nextcloud/l10n' import { translate as t } from '@nextcloud/l10n'
const translateSpecial = (folder) => { const translateSpecial = (folder) => {
if (folder.specialUse.includes('all')) { if (folder.specialUse.includes('all')) {

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

@ -19,6 +19,6 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>. * along with this program. If not, see <http://www.gnu.org/licenses/>.
*/ */
import {getLoggerBuilder} from '@nextcloud/logger' import { getLoggerBuilder } from '@nextcloud/logger'
export default getLoggerBuilder().setApp('mail').detectUser().build() export default getLoggerBuilder().setApp('mail').detectUser().build()

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

@ -1,4 +1,4 @@
/* /**
* @copyright 2019 Christoph Wurst <christoph@winzerhof-wurst.at> * @copyright 2019 Christoph Wurst <christoph@winzerhof-wurst.at>
* *
* @author 2019 Christoph Wurst <christoph@winzerhof-wurst.at> * @author 2019 Christoph Wurst <christoph@winzerhof-wurst.at>
@ -19,15 +19,17 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>. * along with this program. If not, see <http://www.gnu.org/licenses/>.
*/ */
import {generateFilePath} from '@nextcloud/router' import { generateFilePath } from '@nextcloud/router'
import {getRequestToken} from '@nextcloud/auth' import { getRequestToken } from '@nextcloud/auth'
import {loadState} from '@nextcloud/initial-state' import { loadState } from '@nextcloud/initial-state'
import Vue from 'vue' import Vue from 'vue'
import AdminSettings from './components/settings/AdminSettings' import AdminSettings from './components/settings/AdminSettings'
import Nextcloud from './mixins/Nextcloud' import Nextcloud from './mixins/Nextcloud'
// eslint-disable-next-line camelcase
__webpack_nonce__ = btoa(getRequestToken()) __webpack_nonce__ = btoa(getRequestToken())
// eslint-disable-next-line camelcase
__webpack_public_path__ = generateFilePath('mail', '', 'js/') __webpack_public_path__ = generateFilePath('mail', '', 'js/')
Vue.mixin(Nextcloud) Vue.mixin(Nextcloud)

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

@ -1,4 +1,4 @@
/* /**
* @copyright Copyright (c) 2018 Christoph Wurst <christoph@winzerhof-wurst.at> * @copyright Copyright (c) 2018 Christoph Wurst <christoph@winzerhof-wurst.at>
* *
* @author Christoph Wurst <christoph@winzerhof-wurst.at> * @author Christoph Wurst <christoph@winzerhof-wurst.at>
@ -21,9 +21,9 @@
*/ */
import Vue from 'vue' import Vue from 'vue'
import {getRequestToken} from '@nextcloud/auth' import { getRequestToken } from '@nextcloud/auth'
import {sync} from 'vuex-router-sync' import { sync } from 'vuex-router-sync'
import {generateFilePath} from '@nextcloud/router' import { generateFilePath } from '@nextcloud/router'
import VueShortKey from 'vue-shortkey' import VueShortKey from 'vue-shortkey'
import VTooltip from 'v-tooltip' import VTooltip from 'v-tooltip'
@ -31,16 +31,18 @@ import App from './App'
import Nextcloud from './mixins/Nextcloud' import Nextcloud from './mixins/Nextcloud'
import router from './router' import router from './router'
import store from './store' import store from './store'
import {fixAccountId} from './service/AccountService' import { fixAccountId } from './service/AccountService'
// eslint-disable-next-line camelcase
__webpack_nonce__ = btoa(getRequestToken()) __webpack_nonce__ = btoa(getRequestToken())
// eslint-disable-next-line camelcase
__webpack_public_path__ = generateFilePath('mail', '', 'js/') __webpack_public_path__ = generateFilePath('mail', '', 'js/')
sync(store, router) sync(store, router)
Vue.mixin(Nextcloud) Vue.mixin(Nextcloud)
Vue.use(VueShortKey, {prevent: ['input', 'div']}) Vue.use(VueShortKey, { prevent: ['input', 'div'] })
Vue.use(VTooltip) Vue.use(VTooltip)
const getPreferenceFromPage = (key) => { const getPreferenceFromPage = (key) => {
@ -70,11 +72,10 @@ store.commit('savePreference', {
const accounts = JSON.parse(atob(getPreferenceFromPage('serialized-accounts'))) const accounts = JSON.parse(atob(getPreferenceFromPage('serialized-accounts')))
accounts.map(fixAccountId).forEach((account) => { accounts.map(fixAccountId).forEach((account) => {
const folders = account.folders
store.commit('addAccount', account) store.commit('addAccount', account)
}) })
new Vue({ export default new Vue({
el: '#content', el: '#content',
router, router,
store, store,

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

@ -19,7 +19,7 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>. * along with this program. If not, see <http://www.gnu.org/licenses/>.
*/ */
import {translate as t, translatePlural as n} from '@nextcloud/l10n' import { translate as t, translatePlural as n } from '@nextcloud/l10n'
export default { export default {
methods: { methods: {

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

@ -1,6 +1,6 @@
import Vue from 'vue' import Vue from 'vue'
import Router from 'vue-router' import Router from 'vue-router'
import {generateUrl} from '@nextcloud/router' import { generateUrl } from '@nextcloud/router'
const AccountSettings = () => import('./views/AccountSettings') const AccountSettings = () => import('./views/AccountSettings')
const Home = () => import('./views/Home') const Home = () => import('./views/Home')

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

@ -1,4 +1,4 @@
import {generateUrl} from '@nextcloud/router' import { generateUrl } from '@nextcloud/router'
import axios from '@nextcloud/axios' import axios from '@nextcloud/axios'
export const fixAccountId = (original) => { export const fixAccountId = (original) => {
@ -25,7 +25,7 @@ export const create = (data) => {
} }
export const patch = (account, data) => { export const patch = (account, data) => {
const url = generateUrl(`/apps/mail/api/accounts/{id}`, { const url = generateUrl('/apps/mail/api/accounts/{id}', {
id: account.accountId, id: account.accountId,
}) })
@ -36,7 +36,7 @@ export const patch = (account, data) => {
} }
export const update = (data) => { export const update = (data) => {
const url = generateUrl(`/apps/mail/api/accounts/{id}`, { const url = generateUrl('/apps/mail/api/accounts/{id}', {
id: data.accountId, id: data.accountId,
}) })
@ -47,7 +47,7 @@ export const update = (data) => {
} }
export const updateSignature = (account, signature) => { export const updateSignature = (account, signature) => {
const url = generateUrl(`/apps/mail/api/accounts/{id}/signature`, { const url = generateUrl('/apps/mail/api/accounts/{id}/signature', {
id: account.id, id: account.id,
}) })
const data = { const data = {
@ -74,7 +74,7 @@ export const fetch = (id) => {
return axios.get(url).then((resp) => fixAccountId(resp.data)) return axios.get(url).then((resp) => fixAccountId(resp.data))
} }
export const fetchQuota = async (id) => { export const fetchQuota = async(id) => {
const url = generateUrl('/apps/mail/api/accounts/{id}/quota', { const url = generateUrl('/apps/mail/api/accounts/{id}/quota', {
id, id,
}) })

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

@ -1,8 +1,8 @@
import {generateUrl} from '@nextcloud/router' import { generateUrl } from '@nextcloud/router'
import axios from '@nextcloud/axios' import axios from '@nextcloud/axios'
export const createAlias = async (account, data) => { export const createAlias = async(account, data) => {
const url = generateUrl(`/apps/mail/api/accounts/{id}/aliases`, { const url = generateUrl('/apps/mail/api/accounts/{id}/aliases', {
id: account.accountId, id: account.accountId,
}) })
@ -18,7 +18,7 @@ export const createAlias = async (account, data) => {
}) })
} }
export const deleteAlias = async (account, alias) => { export const deleteAlias = async(account, alias) => {
const url = generateUrl('/apps/mail/api/accounts/{id}/aliases/{aliasId}', { const url = generateUrl('/apps/mail/api/accounts/{id}/aliases/{aliasId}', {
id: account.accountId, id: account.accountId,
aliasId: alias.id, aliasId: alias.id,

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

@ -20,7 +20,7 @@
*/ */
import Axios from '@nextcloud/axios' import Axios from '@nextcloud/axios'
import {generateUrl} from '@nextcloud/router' import { generateUrl } from '@nextcloud/router'
export function saveAttachmentToFiles(accountId, folderId, messageId, attachmentId, directory) { export function saveAttachmentToFiles(accountId, folderId, messageId, attachmentId, directory) {
const url = generateUrl( const url = generateUrl(
@ -56,7 +56,7 @@ export const uploadLocalAttachment = (file, progress) => {
return Axios.post(url, data, opts) return Axios.post(url, data, opts)
.then((resp) => resp.data) .then((resp) => resp.data)
.then(({id}) => { .then(({ id }) => {
return { return {
file, file,
id, id,

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

@ -20,7 +20,7 @@
*/ */
import Axios from '@nextcloud/axios' import Axios from '@nextcloud/axios'
import {generateUrl} from '@nextcloud/router' import { generateUrl } from '@nextcloud/router'
export const findRecipient = (term) => { export const findRecipient = (term) => {
const url = generateUrl('/apps/mail/api/autoComplete?term={term}', { const url = generateUrl('/apps/mail/api/autoComplete?term={term}', {

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

@ -22,7 +22,7 @@
import memoize from 'lodash/fp/memoize' import memoize from 'lodash/fp/memoize'
import Axios from '@nextcloud/axios' import Axios from '@nextcloud/axios'
import {generateUrl} from '@nextcloud/router' import { generateUrl } from '@nextcloud/router'
export const fetchAvatarUrl = (email) => { export const fetchAvatarUrl = (email) => {
if (email === null) { if (email === null) {
@ -38,7 +38,7 @@ export const fetchAvatarUrl = (email) => {
.then((avatar) => { .then((avatar) => {
if (avatar.isExternal) { if (avatar.isExternal) {
return generateUrl('/apps/mail/api/avatars/image/{email}', { return generateUrl('/apps/mail/api/avatars/image/{email}', {
email: email, email,
}) })
} else { } else {
return avatar.url return avatar.url

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

@ -17,10 +17,10 @@
* *
*/ */
import {Client} from 'davclient.js' import { Client } from 'davclient.js'
import ical from 'ical.js' import ical from 'ical.js'
import {getCurrentUser, getRequestToken} from '@nextcloud/auth' import { getCurrentUser, getRequestToken } from '@nextcloud/auth'
import {generateRemoteUrl} from '@nextcloud/router' import { generateRemoteUrl } from '@nextcloud/router'
import Axios from '@nextcloud/axios' import Axios from '@nextcloud/axios'
import Logger from '../logger' import Logger from '../logger'
@ -54,15 +54,15 @@ const getResponseCodeFromHTTPResponse = (t) => {
const getACLFromResponse = (properties) => { const getACLFromResponse = (properties) => {
let canWrite = false let canWrite = false
let acl = properties['{DAV:}acl'] const acl = properties['{DAV:}acl']
if (acl) { if (acl) {
for (var k = 0; k < acl.length; k++) { for (let k = 0; k < acl.length; k++) {
var href = acl[k].getElementsByTagNameNS('DAV:', 'href') let href = acl[k].getElementsByTagNameNS('DAV:', 'href')
if (href.length === 0) { if (href.length === 0) {
continue continue
} }
href = href[0].textContent href = href[0].textContent
var writeNode = acl[k].getElementsByTagNameNS('DAV:', 'write') const writeNode = acl[k].getElementsByTagNameNS('DAV:', 'write')
if (writeNode.length > 0) { if (writeNode.length > 0) {
canWrite = true canWrite = true
} }
@ -86,7 +86,7 @@ const getCalendarData = (properties) => {
const components = properties['{urn:ietf:params:xml:ns:caldav}supported-calendar-component-set'] || [] const components = properties['{urn:ietf:params:xml:ns:caldav}supported-calendar-component-set'] || []
for (let i = 0; i < components.length; i++) { for (let i = 0; i < components.length; i++) {
var name = components[i].attributes.getNamedItem('name').textContent.toLowerCase() const name = components[i].attributes.getNamedItem('name').textContent.toLowerCase()
if (Object.hasOwnProperty.call(data.components, name)) { if (Object.hasOwnProperty.call(data.components, name)) {
data.components[name] = true data.components[name] = true
} }
@ -99,7 +99,7 @@ const getCalendarData = (properties) => {
* @returns {Promise} * @returns {Promise}
*/ */
export const getUserCalendars = () => { export const getUserCalendars = () => {
var url = generateRemoteUrl('dav/calendars') + '/' + getCurrentUser().uid + '/' const url = generateRemoteUrl('dav/calendars') + '/' + getCurrentUser().uid + '/'
return client return client
.propFind(url, props, 1, { .propFind(url, props, 1, {
@ -156,7 +156,7 @@ const splitCalendar = (data) => {
allObjects[componentName] = {} allObjects[componentName] = {}
vobjects.forEach((vobject) => { vobjects.forEach((vobject) => {
var uid = vobject.getFirstPropertyValue('uid') const uid = vobject.getFirstPropertyValue('uid')
allObjects[componentName][uid] = allObjects[componentName][uid] || [] allObjects[componentName][uid] = allObjects[componentName][uid] || []
allObjects[componentName][uid].push(vobject) allObjects[componentName][uid].push(vobject)
}) })
@ -165,11 +165,11 @@ const splitCalendar = (data) => {
const split = [] const split = []
componentNames.forEach((componentName) => { componentNames.forEach((componentName) => {
split[componentName] = [] split[componentName] = []
for (let objectsId in allObjects[componentName]) { for (const objectsId in allObjects[componentName]) {
const objects = allObjects[componentName][objectsId] const objects = allObjects[componentName][objectsId]
const component = createICalElement() const component = createICalElement()
timezones.forEach(component.addSubcomponent.bind(component)) timezones.forEach(component.addSubcomponent.bind(component))
for (let objectId in objects) { for (const objectId in objects) {
component.addSubcomponent(objects[objectId]) component.addSubcomponent(objects[objectId])
} }
split[componentName].push(component.toString()) split[componentName].push(component.toString())
@ -184,8 +184,8 @@ const splitCalendar = (data) => {
} }
/** /**
* @param {String} url * @param {String} url the url
* @param {Object} data * @param {Object} data the data
* @returns {Promise} * @returns {Promise}
*/ */
export const importCalendarEvent = (url) => (data) => { export const importCalendarEvent = (url) => (data) => {
@ -198,9 +198,9 @@ export const importCalendarEvent = (url) => (data) => {
const file = splitCalendar(data) const file = splitCalendar(data)
const components = ['vevent', 'vjournal', 'vtodo'] const components = ['vevent', 'vjournal', 'vtodo']
components.forEach((componentName) => { components.forEach((componentName) => {
for (let componentId in file.split[componentName]) { for (const componentId in file.split[componentName]) {
const component = file.split[componentName][componentId] const component = file.split[componentName][componentId]
Logger.info('importing event component', {component}) Logger.info('importing event component', { component })
promises.push( promises.push(
Promise.resolve( Promise.resolve(
Axios.put(url + getRandomString() + '.ics', component, { Axios.put(url + getRandomString() + '.ics', component, {

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

@ -26,8 +26,8 @@
// Slightly modified for use in Mail // Slightly modified for use in Mail
import axios from '@nextcloud/axios' import axios from '@nextcloud/axios'
import {generateOcsUrl} from '@nextcloud/router' import { generateOcsUrl } from '@nextcloud/router'
import {showError} from '@nextcloud/dialogs' import { showError } from '@nextcloud/dialogs'
/** /**
* Makes a share link for a given file or directory. * Makes a share link for a given file or directory.
@ -35,7 +35,7 @@ import {showError} from '@nextcloud/dialogs'
* @param {string} token The conversation's token * @param {string} token The conversation's token
* @returns {string} url share link * @returns {string} url share link
*/ */
const shareFile = async function (path, token) { const shareFile = async function(path, token) {
try { try {
const res = await axios.post(generateOcsUrl('apps/files_sharing/api/v1', 2) + 'shares', { const res = await axios.post(generateOcsUrl('apps/files_sharing/api/v1', 2) + 'shares', {
shareType: 3, // OC.Share.SHARE_TYPE_LINK, shareType: 3, // OC.Share.SHARE_TYPE_LINK,
@ -45,21 +45,21 @@ const shareFile = async function (path, token) {
return res.data.ocs.data.url return res.data.ocs.data.url
} catch (error) { } catch (error) {
if ( if (
error.response && error.response
error.response.data && && error.response.data
error.response.data.ocs && && error.response.data.ocs
error.response.data.ocs.meta && && error.response.data.ocs.meta
error.response.data.ocs.meta.message && error.response.data.ocs.meta.message
) { ) {
console.error(`Error while sharing file: ${error.response.data.ocs.meta.message || 'Unknown error'}`) console.error(`Error while sharing file: ${error.response.data.ocs.meta.message || 'Unknown error'}`)
showError(error.response.data.ocs.meta.message) showError(error.response.data.ocs.meta.message)
throw error throw error
} else { } else {
console.error(`Error while sharing file: Unknown error`) console.error('Error while sharing file: Unknown error')
showError(t('mail', 'Error while sharing file')) showError(t('mail', 'Error while sharing file'))
throw error throw error
} }
} }
} }
export {shareFile} export { shareFile }

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

@ -1,4 +1,4 @@
import {generateUrl} from '@nextcloud/router' import { generateUrl } from '@nextcloud/router'
import Axios from '@nextcloud/axios' import Axios from '@nextcloud/axios'
export function fetchAll(accountId) { export function fetchAll(accountId) {

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

@ -1,9 +1,9 @@
import {generateUrl} from '@nextcloud/router' import { generateUrl } from '@nextcloud/router'
import axios from '@nextcloud/axios' import axios from '@nextcloud/axios'
import {curry, map} from 'ramda' import { curry, map } from 'ramda'
import {parseErrorResponse} from '../http/ErrorResponseParser' import { parseErrorResponse } from '../http/ErrorResponseParser'
import {convertAxiosError} from '../errors/convert' import { convertAxiosError } from '../errors/convert'
import SyncIncompleteError from '../errors/SyncIncompleteError' import SyncIncompleteError from '../errors/SyncIncompleteError'
const amendEnvelopeWithIds = curry((accountId, folderId, envelope) => ({ const amendEnvelopeWithIds = curry((accountId, folderId, envelope) => ({
@ -116,10 +116,7 @@ export function setEnvelopeFlag(accountId, folderId, uid, flag, value) {
return axios return axios
.put(url, { .put(url, {
flags: flags, flags,
})
.then(() => {
value
}) })
} }

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

@ -1,4 +1,4 @@
/* /**
* @copyright 2018 Christoph Wurst <christoph@winzerhof-wurst.at> * @copyright 2018 Christoph Wurst <christoph@winzerhof-wurst.at>
* *
* @author 2018 Christoph Wurst <christoph@winzerhof-wurst.at> * @author 2018 Christoph Wurst <christoph@winzerhof-wurst.at>
@ -20,25 +20,25 @@
*/ */
import uniq from 'lodash/fp/uniq' import uniq from 'lodash/fp/uniq'
import {translate as t, translatePlural as n} from '@nextcloud/l10n' import { translate as t, translatePlural as n } from '@nextcloud/l10n'
import {generateFilePath} from '@nextcloud/router' import { generateFilePath } from '@nextcloud/router'
import Logger from '../logger' import Logger from '../logger'
/** /**
* @todo use Notification.requestPermission().then once all browsers support promise API * @todo use Notification.requestPermission().then once all browsers support promise API
* *
* @return {Promise} * @returns {Promise}
*/ */
const request = () => { const request = () => {
if (!('Notification' in window)) { if (!('Notification' in window)) {
Logger.info('browser does not support desktop notifications') Logger.info('browser does not support desktop notifications')
return Promise.reject() return Promise.reject(new Error('browser does not support desktop notifications'))
} else if (Notification.permission === 'granted') { } else if (Notification.permission === 'granted') {
return Promise.resolve() return Promise.resolve()
} else if (Notification.permission === 'denied') { } else if (Notification.permission === 'denied') {
Logger.info('desktop notifications are denied') Logger.info('desktop notifications are denied')
return Promise.reject() return Promise.reject(new Error('desktop notifications are denied'))
} }
Logger.info('requesting permissions to show desktop notifications') Logger.info('requesting permissions to show desktop notifications')
@ -49,13 +49,13 @@ const showNotification = (title, body, icon) => {
request().then(() => { request().then(() => {
if (document.querySelector(':focus') !== null) { if (document.querySelector(':focus') !== null) {
Logger.debug('browser is active. notification request is ignored') Logger.debug('browser is active. notification request is ignored')
return
} }
}) })
const notification = new Notification(title, { const notification = new Notification(title, {
body: body, body,
icon: icon, icon,
}) })
notification.onclick = () => { notification.onclick = () => {
window.focus() window.focus()

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

@ -21,7 +21,7 @@
*/ */
import Axios from '@nextcloud/axios' import Axios from '@nextcloud/axios'
import {generateUrl} from '@nextcloud/router' import { generateUrl } from '@nextcloud/router'
export const savePreference = (key, value) => { export const savePreference = (key, value) => {
const url = generateUrl('/apps/mail/api/preferences/{key}', { const url = generateUrl('/apps/mail/api/preferences/{key}', {

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

@ -20,7 +20,7 @@
*/ */
import axios from '@nextcloud/axios' import axios from '@nextcloud/axios'
import {generateUrl} from '@nextcloud/router' import { generateUrl } from '@nextcloud/router'
export const saveProvisioningSettings = (config) => { export const saveProvisioningSettings = (config) => {
const url = generateUrl('/apps/mail/api/settings/provisioning') const url = generateUrl('/apps/mail/api/settings/provisioning')

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

@ -40,7 +40,7 @@ import {
where, where,
} from 'ramda' } from 'ramda'
import {savePreference} from '../service/PreferenceService' import { savePreference } from '../service/PreferenceService'
import { import {
create as createAccount, create as createAccount,
deleteAccount, deleteAccount,
@ -50,7 +50,7 @@ import {
update as updateAccount, update as updateAccount,
updateSignature, updateSignature,
} from '../service/AccountService' } from '../service/AccountService'
import {create as createFolder, fetchAll as fetchAllFolders, markFolderRead} from '../service/FolderService' import { create as createFolder, fetchAll as fetchAllFolders, markFolderRead } from '../service/FolderService'
import { import {
deleteMessage, deleteMessage,
fetchEnvelope, fetchEnvelope,
@ -59,16 +59,16 @@ import {
setEnvelopeFlag, setEnvelopeFlag,
syncEnvelopes, syncEnvelopes,
} from '../service/MessageService' } from '../service/MessageService'
import {createAlias, deleteAlias} from '../service/AliasService' import { createAlias, deleteAlias } from '../service/AliasService'
import logger from '../logger' import logger from '../logger'
import {normalizedEnvelopeListId} from './normalization' import { normalizedEnvelopeListId } from './normalization'
import {showNewMessagesNotification} from '../service/NotificationService' import { showNewMessagesNotification } from '../service/NotificationService'
import {parseUuid} from '../util/EnvelopeUidParser' import { parseUuid } from '../util/EnvelopeUidParser'
import {matchError} from '../errors/match' import { matchError } from '../errors/match'
import SyncIncompleteError from '../errors/SyncIncompleteError' import SyncIncompleteError from '../errors/SyncIncompleteError'
import MailboxLockedError from '../errors/MailboxLockedError' import MailboxLockedError from '../errors/MailboxLockedError'
import {wait} from '../util/wait' import { wait } from '../util/wait'
import {UNIFIED_ACCOUNT_ID, UNIFIED_INBOX_ID} from './constants' import { UNIFIED_ACCOUNT_ID, UNIFIED_INBOX_ID } from './constants'
const PAGE_SIZE = 20 const PAGE_SIZE = 20
@ -87,27 +87,27 @@ const findIndividualFolders = curry((getFolders, specialRole) =>
const combineEnvelopeLists = pipe(flatten, orderBy(prop('dateInt'), 'desc')) const combineEnvelopeLists = pipe(flatten, orderBy(prop('dateInt'), 'desc'))
export default { export default {
savePreference({commit, getters}, {key, value}) { savePreference({ commit, getters }, { key, value }) {
return savePreference(key, value).then(({value}) => { return savePreference(key, value).then(({ value }) => {
commit('savePreference', { commit('savePreference', {
key, key,
value, value,
}) })
}) })
}, },
fetchAccounts({commit, getters}) { fetchAccounts({ commit, getters }) {
return fetchAllAccounts().then((accounts) => { return fetchAllAccounts().then((accounts) => {
accounts.forEach((account) => commit('addAccount', account)) accounts.forEach((account) => commit('addAccount', account))
return getters.accounts return getters.accounts
}) })
}, },
fetchAccount({commit}, id) { fetchAccount({ commit }, id) {
return fetchAccount(id).then((account) => { return fetchAccount(id).then((account) => {
commit('addAccount', account) commit('addAccount', account)
return account return account
}) })
}, },
createAccount({commit}, config) { createAccount({ commit }, config) {
return createAccount(config).then((account) => { return createAccount(config).then((account) => {
logger.debug(`account ${account.id} created, fetching folders …`, account) logger.debug(`account ${account.id} created, fetching folders …`, account)
return fetchAllFolders(account.id) return fetchAllFolders(account.id)
@ -119,42 +119,42 @@ export default {
.then(() => account) .then(() => account)
}) })
}, },
updateAccount({commit}, config) { updateAccount({ commit }, config) {
return updateAccount(config).then((account) => { return updateAccount(config).then((account) => {
console.debug('account updated', account) console.debug('account updated', account)
commit('editAccount', account) commit('editAccount', account)
return account return account
}) })
}, },
patchAccount({commit}, {account, data}) { patchAccount({ commit }, { account, data }) {
return patchAccount(account, data).then((account) => { return patchAccount(account, data).then((account) => {
console.debug('account patched', account, data) console.debug('account patched', account, data)
commit('patchAccount', {account, data}) commit('patchAccount', { account, data })
return account return account
}) })
}, },
updateAccountSignature({commit}, {account, signature}) { updateAccountSignature({ commit }, { account, signature }) {
return updateSignature(account, signature).then(() => { return updateSignature(account, signature).then(() => {
console.debug('account signature updated') console.debug('account signature updated')
const updated = Object.assign({}, account, {signature}) const updated = Object.assign({}, account, { signature })
commit('editAccount', updated) commit('editAccount', updated)
return account return account
}) })
}, },
deleteAccount({commit}, account) { deleteAccount({ commit }, account) {
return deleteAccount(account.id).catch((err) => { return deleteAccount(account.id).catch((err) => {
console.error('could not delete account', err) console.error('could not delete account', err)
throw err throw err
}) })
}, },
createFolder({commit}, {account, name}) { createFolder({ commit }, { account, name }) {
return createFolder(account.id, name).then((folder) => { return createFolder(account.id, name).then((folder) => {
console.debug(`folder ${name} created for account ${account.id}`, {folder}) console.debug(`folder ${name} created for account ${account.id}`, { folder })
commit('addFolder', {account, folder}) commit('addFolder', { account, folder })
commit('expandAccount', account.id) commit('expandAccount', account.id)
}) })
}, },
moveAccount({commit, getters}, {account, up}) { moveAccount({ commit, getters }, { account, up }) {
const accounts = getters.accounts const accounts = getters.accounts
const index = accounts.indexOf(account) const index = accounts.indexOf(account)
if (up) { if (up) {
@ -171,12 +171,12 @@ export default {
if (account.id === 0) { if (account.id === 0) {
return return
} }
commit('saveAccountsOrder', {account, order: idx}) commit('saveAccountsOrder', { account, order: idx })
return patchAccount(account, {order: idx}) return patchAccount(account, { order: idx })
}) })
) )
}, },
markFolderRead({getters, dispatch}, {accountId, folderId}) { markFolderRead({ getters, dispatch }, { accountId, folderId }) {
const folder = getters.getFolder(accountId, folderId) const folder = getters.getFolder(accountId, folderId)
if (folder.isUnified) { if (folder.isUnified) {
@ -194,13 +194,13 @@ export default {
return markFolderRead(accountId, folderId).then( return markFolderRead(accountId, folderId).then(
dispatch('syncEnvelopes', { dispatch('syncEnvelopes', {
accountId: accountId, accountId,
folderId: folderId, folderId,
}) })
) )
}, },
fetchEnvelope({commit, getters}, uuid) { fetchEnvelope({ commit, getters }, uuid) {
const {accountId, folderId, uid} = parseUuid(uuid) const { accountId, folderId, uid } = parseUuid(uuid)
const cached = getters.getEnvelope(accountId, folderId, uid) const cached = getters.getEnvelope(accountId, folderId, uid)
if (cached) { if (cached) {
@ -222,7 +222,7 @@ export default {
return getters.getEnvelope(accountId, folderId, uid) return getters.getEnvelope(accountId, folderId, uid)
}) })
}, },
fetchEnvelopes({state, commit, getters, dispatch}, {accountId, folderId, query}) { fetchEnvelopes({ state, commit, getters, dispatch }, { accountId, folderId, query }) {
const folder = getters.getFolder(accountId, folderId) const folder = getters.getFolder(accountId, folderId)
if (folder.isUnified) { if (folder.isUnified) {
@ -275,7 +275,7 @@ export default {
) )
)(accountId, folderId, query, undefined, PAGE_SIZE) )(accountId, folderId, query, undefined, PAGE_SIZE)
}, },
fetchNextEnvelopePage({commit, getters, dispatch}, {accountId, folderId, query, rec = true}) { fetchNextEnvelopePage({ commit, getters, dispatch }, { accountId, folderId, query, rec = true }) {
const folder = getters.getFolder(accountId, folderId) const folder = getters.getFolder(accountId, folderId)
if (folder.isUnified) { if (folder.isUnified) {
@ -378,7 +378,7 @@ export default {
return envelopes return envelopes
}) })
}, },
syncEnvelopes({commit, getters, dispatch}, {accountId, folderId, query, init = false}) { syncEnvelopes({ commit, getters, dispatch }, { accountId, folderId, query, init = false }) {
const folder = getters.getFolder(accountId, folderId) const folder = getters.getFolder(accountId, folderId)
if (folder.isUnified) { if (folder.isUnified) {
@ -462,11 +462,11 @@ export default {
return matchError(error, { return matchError(error, {
[SyncIncompleteError.getName()]() { [SyncIncompleteError.getName()]() {
console.warn('(initial) sync is incomplete, retriggering') console.warn('(initial) sync is incomplete, retriggering')
return dispatch('syncEnvelopes', {accountId, folderId, query, init}) return dispatch('syncEnvelopes', { accountId, folderId, query, init })
}, },
[MailboxLockedError.getName()](error) { [MailboxLockedError.getName()](error) {
logger.info('Sync failed because the mailbox is locked, retriggering', {error}) logger.info('Sync failed because the mailbox is locked, retriggering', { error })
return wait(1500).then(() => dispatch('syncEnvelopes', {accountId, folderId, query, init})) return wait(1500).then(() => dispatch('syncEnvelopes', { accountId, folderId, query, init }))
}, },
default(error) { default(error) {
console.error('Could not sync envelopes: ' + error.message, error) console.error('Could not sync envelopes: ' + error.message, error)
@ -474,13 +474,13 @@ export default {
}) })
}) })
}, },
async syncInboxes({getters, dispatch}) { async syncInboxes({ getters, dispatch }) {
const results = await Promise.all( const results = await Promise.all(
getters.accounts getters.accounts
.filter((a) => !a.isUnified) .filter((a) => !a.isUnified)
.map((account) => { .map((account) => {
return Promise.all( return Promise.all(
getters.getFolders(account.id).map(async (folder) => { getters.getFolders(account.id).map(async(folder) => {
if (folder.specialRole !== 'inbox') { if (folder.specialRole !== 'inbox') {
return return
} }
@ -510,7 +510,7 @@ export default {
// Make sure the priority inbox is updated as well // Make sure the priority inbox is updated as well
logger.info('updating priority inbox') logger.info('updating priority inbox')
for (const query of ['is:important not:starred', 'is:starred not:important', 'not:starred not:important']) { for (const query of ['is:important not:starred', 'is:starred not:important', 'not:starred not:important']) {
logger.info("sync'ing priority inbox section", {query}) logger.info("sync'ing priority inbox section", { query })
const folder = getters.getFolder(UNIFIED_ACCOUNT_ID, UNIFIED_INBOX_ID) const folder = getters.getFolder(UNIFIED_ACCOUNT_ID, UNIFIED_INBOX_ID)
const list = folder.envelopeLists[normalizedEnvelopeListId(query)] const list = folder.envelopeLists[normalizedEnvelopeListId(query)]
if (list === undefined) { if (list === undefined) {
@ -531,7 +531,7 @@ export default {
showNewMessagesNotification(newMessages) showNewMessagesNotification(newMessages)
} }
}, },
toggleEnvelopeFlagged({commit, getters}, envelope) { toggleEnvelopeFlagged({ commit, getters }, envelope) {
// Change immediately and switch back on error // Change immediately and switch back on error
const oldState = envelope.flags.flagged const oldState = envelope.flags.flagged
commit('flagEnvelope', { commit('flagEnvelope', {
@ -551,7 +551,7 @@ export default {
}) })
}) })
}, },
toggleEnvelopeImportant({commit, getters}, envelope) { toggleEnvelopeImportant({ commit, getters }, envelope) {
// Change immediately and switch back on error // Change immediately and switch back on error
const oldState = envelope.flags.important const oldState = envelope.flags.important
commit('flagEnvelope', { commit('flagEnvelope', {
@ -571,7 +571,7 @@ export default {
}) })
}) })
}, },
toggleEnvelopeSeen({commit, getters}, envelope) { toggleEnvelopeSeen({ commit, getters }, envelope) {
// Change immediately and switch back on error // Change immediately and switch back on error
const oldState = envelope.flags.seen const oldState = envelope.flags.seen
commit('flagEnvelope', { commit('flagEnvelope', {
@ -591,7 +591,7 @@ export default {
}) })
}) })
}, },
toggleEnvelopeJunk({commit, getters}, envelope) { toggleEnvelopeJunk({ commit, getters }, envelope) {
// Change immediately and switch back on error // Change immediately and switch back on error
const oldState = envelope.flags.junk const oldState = envelope.flags.junk
commit('flagEnvelope', { commit('flagEnvelope', {
@ -611,7 +611,7 @@ export default {
}) })
}) })
}, },
markEnvelopeFavoriteOrUnfavorite({commit, getters}, {envelope, favFlag}) { markEnvelopeFavoriteOrUnfavorite({ commit, getters }, { envelope, favFlag }) {
// Change immediately and switch back on error // Change immediately and switch back on error
const oldState = envelope.flags.flagged const oldState = envelope.flags.flagged
commit('flagEnvelope', { commit('flagEnvelope', {
@ -631,7 +631,7 @@ export default {
}) })
}) })
}, },
markEnvelopeSeenOrUnseen({commit, getters}, {envelope, seenFlag}) { markEnvelopeSeenOrUnseen({ commit, getters }, { envelope, seenFlag }) {
// Change immediately and switch back on error // Change immediately and switch back on error
const oldState = envelope.flags.unseen const oldState = envelope.flags.unseen
commit('flagEnvelope', { commit('flagEnvelope', {
@ -651,8 +651,8 @@ export default {
}) })
}) })
}, },
fetchMessage({commit}, uuid) { fetchMessage({ commit }, uuid) {
const {accountId, folderId, uid} = parseUuid(uuid) const { accountId, folderId, uid } = parseUuid(uuid)
return fetchMessage(accountId, folderId, uid).then((message) => { return fetchMessage(accountId, folderId, uid).then((message) => {
// Only commit if not undefined (not found) // Only commit if not undefined (not found)
if (message) { if (message) {
@ -666,43 +666,43 @@ export default {
return message return message
}) })
}, },
replaceDraft({getters, commit}, {draft, uid, data}) { replaceDraft({ getters, commit }, { draft, uid, data }) {
commit('updateDraft', { commit('updateDraft', {
draft, draft,
data, data,
newUid: uid, newUid: uid,
}) })
}, },
deleteMessage({getters, commit}, {accountId, folderId, uid}) { deleteMessage({ getters, commit }, { accountId, folderId, uid }) {
commit('removeEnvelope', {accountId, folderId, uid}) commit('removeEnvelope', { accountId, folderId, uid })
return deleteMessage(accountId, folderId, uid) return deleteMessage(accountId, folderId, uid)
.then(() => { .then(() => {
const folder = getters.getFolder(accountId, folderId) const folder = getters.getFolder(accountId, folderId)
if (!folder) { if (!folder) {
logger.error('could not find folder', {accountId, folderId}) logger.error('could not find folder', { accountId, folderId })
return return
} }
commit('removeMessage', {accountId, folder, uid}) commit('removeMessage', { accountId, folder, uid })
console.log('message removed') console.debug('message removed')
}) })
.catch((err) => { .catch((err) => {
console.error('could not delete message', err) console.error('could not delete message', err)
const envelope = getters.getEnvelope(accountId, folderId, uid) const envelope = getters.getEnvelope(accountId, folderId, uid)
if (envelope) { if (envelope) {
commit('addEnvelope', {accountId, folderId, envelope}) commit('addEnvelope', { accountId, folderId, envelope })
} else { } else {
logger.error('could not find envelope', {accountId, folderId, uid}) logger.error('could not find envelope', { accountId, folderId, uid })
} }
throw err throw err
}) })
}, },
async createAlias({commit}, {account, aliasToAdd}) { async createAlias({ commit }, { account, aliasToAdd }) {
const alias = await createAlias(account, aliasToAdd) const alias = await createAlias(account, aliasToAdd)
commit('createAlias', {account, alias}) commit('createAlias', { account, alias })
}, },
async deleteAlias({commit}, {account, aliasToDelete}) { async deleteAlias({ commit }, { account, aliasToDelete }) {
await deleteAlias(account, aliasToDelete) await deleteAlias(account, aliasToDelete)
commit('deleteAlias', {account, alias: aliasToDelete}) commit('deleteAlias', { account, alias: aliasToDelete })
}, },
} }

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

@ -18,7 +18,7 @@
* You should have received a copy of the GNU Affero General Public License * You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>. * along with this program. If not, see <http://www.gnu.org/licenses/>.
*/ */
import {normalizedFolderId} from './normalization' import { normalizedFolderId } from './normalization'
export const UNIFIED_ACCOUNT_ID = 0 export const UNIFIED_ACCOUNT_ID = 0
export const UNIFIED_INBOX_ID = btoa('inbox') export const UNIFIED_INBOX_ID = btoa('inbox')

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

@ -19,10 +19,10 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>. * along with this program. If not, see <http://www.gnu.org/licenses/>.
*/ */
import {defaultTo, head} from 'ramda' import { defaultTo, head } from 'ramda'
import {UNIFIED_ACCOUNT_ID} from './constants' import { UNIFIED_ACCOUNT_ID } from './constants'
import {normalizedEnvelopeListId, normalizedFolderId, normalizedMessageId} from './normalization' import { normalizedEnvelopeListId, normalizedFolderId, normalizedMessageId } from './normalization'
export const getters = { export const getters = {
getPreference: (state) => (key, def) => { getPreference: (state) => (key, def) => {

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

@ -30,7 +30,7 @@ import {
PRIORITY_INBOX_UID, PRIORITY_INBOX_UID,
} from './constants' } from './constants'
import actions from './actions' import actions from './actions'
import {getters} from './getters' import { getters } from './getters'
import mutations from './mutations' import mutations from './mutations'
Vue.use(Vuex) Vue.use(Vuex)

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

@ -23,11 +23,11 @@ import orderBy from 'lodash/fp/orderBy'
import sortedUniqBy from 'lodash/fp/sortedUniqBy' import sortedUniqBy from 'lodash/fp/sortedUniqBy'
import Vue from 'vue' import Vue from 'vue'
import {buildMailboxHierarchy} from '../imap/MailboxHierarchy' import { buildMailboxHierarchy } from '../imap/MailboxHierarchy'
import {havePrefix} from '../imap/MailboxPrefix' import { havePrefix } from '../imap/MailboxPrefix'
import {normalizedFolderId, normalizedMessageId, normalizedEnvelopeListId} from './normalization' import { normalizedFolderId, normalizedMessageId, normalizedEnvelopeListId } from './normalization'
import {sortMailboxes} from '../imap/MailboxSorter' import { sortMailboxes } from '../imap/MailboxSorter'
import {UNIFIED_ACCOUNT_ID} from './constants' import { UNIFIED_ACCOUNT_ID } from './constants'
const addFolderToState = (state, account) => (folder) => { const addFolderToState = (state, account) => (folder) => {
const id = normalizedFolderId(account.id, folder.id) const id = normalizedFolderId(account.id, folder.id)
@ -43,7 +43,7 @@ const sortAccounts = (accounts) => {
} }
export default { export default {
savePreference(state, {key, value}) { savePreference(state, { key, value }) {
Vue.set(state.preferences, key, value) Vue.set(state.preferences, key, value)
}, },
addAccount(state, account) { addAccount(state, account) {
@ -70,10 +70,10 @@ export default {
editAccount(state, account) { editAccount(state, account) {
Vue.set(state.accounts, account.id, Object.assign({}, state.accounts[account.id], account)) Vue.set(state.accounts, account.id, Object.assign({}, state.accounts[account.id], account))
}, },
patchAccount(state, {account, data}) { patchAccount(state, { account, data }) {
Vue.set(state.accounts, account.id, Object.assign({}, state.accounts[account.id], data)) Vue.set(state.accounts, account.id, Object.assign({}, state.accounts[account.id], data))
}, },
saveAccountsOrder(state, {account, order}) { saveAccountsOrder(state, { account, order }) {
Vue.set(account, 'order', order) Vue.set(account, 'order', order)
Vue.set( Vue.set(
state, state,
@ -87,7 +87,7 @@ export default {
expandAccount(state, accountId) { expandAccount(state, accountId) {
state.accounts[accountId].collapsed = false state.accounts[accountId].collapsed = false
}, },
addFolder(state, {account, folder}) { addFolder(state, { account, folder }) {
// Flatten the existing ones before updating the hierarchy // Flatten the existing ones before updating the hierarchy
const existing = account.folders.map((id) => state.folders[id]) const existing = account.folders.map((id) => state.folders[id])
existing.forEach((folder) => { existing.forEach((folder) => {
@ -110,7 +110,7 @@ export default {
account.folders.push(id) account.folders.push(id)
}) })
}, },
addEnvelope(state, {accountId, folderId, query, envelope}) { addEnvelope(state, { accountId, folderId, query, envelope }) {
const folder = state.folders[normalizedFolderId(accountId, folderId)] const folder = state.folders[normalizedFolderId(accountId, folderId)]
Vue.set(state.envelopes, envelope.uuid, envelope) Vue.set(state.envelopes, envelope.uuid, envelope)
const listId = normalizedEnvelopeListId(query) const listId = normalizedEnvelopeListId(query)
@ -133,17 +133,17 @@ export default {
) )
}) })
}, },
updateEnvelope(state, {envelope}) { updateEnvelope(state, { envelope }) {
const existing = state.envelopes[envelope.uid] const existing = state.envelopes[envelope.uid]
if (!existing) { if (!existing) {
return return
} }
Vue.set(existing, 'flags', envelope.flags) Vue.set(existing, 'flags', envelope.flags)
}, },
flagEnvelope(state, {envelope, flag, value}) { flagEnvelope(state, { envelope, flag, value }) {
envelope.flags[flag] = value envelope.flags[flag] = value
}, },
removeEnvelope(state, {accountId, folderId, uid}) { removeEnvelope(state, { accountId, folderId, uid }) {
const folder = state.folders[normalizedFolderId(accountId, folderId)] const folder = state.folders[normalizedFolderId(accountId, folderId)]
for (const listId in folder.envelopeLists) { for (const listId in folder.envelopeLists) {
if (!Object.hasOwnProperty.call(folder.envelopeLists, listId)) { if (!Object.hasOwnProperty.call(folder.envelopeLists, listId)) {
@ -184,14 +184,14 @@ export default {
} }
}) })
}, },
addMessage(state, {accountId, folderId, message}) { addMessage(state, { accountId, folderId, message }) {
const uuid = normalizedMessageId(accountId, folderId, message.uid) const uuid = normalizedMessageId(accountId, folderId, message.uid)
message.accountId = accountId message.accountId = accountId
message.folderId = folderId message.folderId = folderId
message.uuid = uuid message.uuid = uuid
Vue.set(state.messages, uuid, message) Vue.set(state.messages, uuid, message)
}, },
updateDraft(state, {draft, data, newUid}) { updateDraft(state, { draft, data, newUid }) {
// Update draft's UID // Update draft's UID
const oldUid = draft.uid const oldUid = draft.uid
const uid = normalizedMessageId(draft.accountId, draft.folderId, newUid) const uid = normalizedMessageId(draft.accountId, draft.folderId, newUid)
@ -217,13 +217,13 @@ export default {
Vue.set(state.envelopes, uid, draft) Vue.set(state.envelopes, uid, draft)
Vue.set(state.messages, uid, draft) Vue.set(state.messages, uid, draft)
}, },
removeMessage(state, {accountId, folderId, uid}) { removeMessage(state, { accountId, folderId, uid }) {
Vue.delete(state.messages, normalizedMessageId(accountId, folderId, uid)) Vue.delete(state.messages, normalizedMessageId(accountId, folderId, uid))
}, },
createAlias(state, {account, alias}) { createAlias(state, { account, alias }) {
account.aliases.push(alias) account.aliases.push(alias)
}, },
deleteAlias(state, {account, alias}) { deleteAlias(state, { account, alias }) {
account.aliases.splice(account.aliases.indexOf(alias), 1) account.aliases.splice(account.aliases.indexOf(alias), 1)
}, },
} }

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

@ -19,7 +19,7 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>. * along with this program. If not, see <http://www.gnu.org/licenses/>.
*/ */
import {curry, defaultTo} from 'ramda' import { curry, defaultTo } from 'ramda'
export const normalizedFolderId = curry((accountId, folderId) => { export const normalizedFolderId = curry((accountId, folderId) => {
return `${accountId}-${folderId}` return `${accountId}-${folderId}`

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

@ -20,8 +20,8 @@
* *
*/ */
import {buildReplyBody, buildRecipients, buildReplySubject} from '../../ReplyBuilder' import { buildReplyBody, buildRecipients, buildReplySubject } from '../../ReplyBuilder'
import {html, plain} from '../../util/text' import { html, plain } from '../../util/text'
describe('ReplyBuilder', () => { describe('ReplyBuilder', () => {
it('creates a reply body without any sender', () => { it('creates a reply body without any sender', () => {
@ -51,7 +51,7 @@ describe('ReplyBuilder', () => {
let envelope let envelope
beforeEach(function () { beforeEach(function() {
envelope = {} envelope = {}
}) })
@ -93,7 +93,7 @@ describe('ReplyBuilder', () => {
envelope.to = [b, c] envelope.to = [b, c]
envelope.cc = [] envelope.cc = []
var reply = buildRecipients(envelope, b) const reply = buildRecipients(envelope, b)
assertSameAddressList(reply.from, [b]) assertSameAddressList(reply.from, [b])
assertSameAddressList(reply.to, [a, c]) assertSameAddressList(reply.to, [a, c])
@ -189,7 +189,7 @@ describe('ReplyBuilder', () => {
envelope.to = [a, b] envelope.to = [a, b]
envelope.cc = [] envelope.cc = []
var reply = buildRecipients(envelope, a) const reply = buildRecipients(envelope, a)
assertSameAddressList(reply.from, [a]) assertSameAddressList(reply.from, [a])
assertSameAddressList(reply.to, [b]) assertSameAddressList(reply.to, [b])
@ -219,7 +219,7 @@ describe('ReplyBuilder', () => {
envelope.to = [a] envelope.to = [a]
envelope.cc = [] envelope.cc = []
var reply = buildRecipients(envelope, a) const reply = buildRecipients(envelope, a)
assertSameAddressList(reply.from, [a]) assertSameAddressList(reply.from, [a])
assertSameAddressList(reply.to, [a]) assertSameAddressList(reply.to, [a])

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

@ -1,4 +1,4 @@
import {createLocalVue, shallowMount} from '@vue/test-utils' import { createLocalVue, shallowMount } from '@vue/test-utils'
import VTooltip from 'v-tooltip' import VTooltip from 'v-tooltip'
import Address from '../../../components/Address.vue' import Address from '../../../components/Address.vue'

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

@ -19,14 +19,14 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>. * along with this program. If not, see <http://www.gnu.org/licenses/>.
*/ */
import {getMailvelope} from '../../../crypto/mailvelope' import { getMailvelope } from '../../../crypto/mailvelope'
describe('mailvelope', () => { describe('mailvelope', () => {
afterEach(() => { afterEach(() => {
delete window.mailvelope delete window.mailvelope
}) })
it('loads statically', async () => { it('loads statically', async() => {
window.mailvelope = { window.mailvelope = {
mock: 3, mock: 3,
} }
@ -36,7 +36,7 @@ describe('mailvelope', () => {
expect(mailvelope).to.deep.equal(window.mailvelope) expect(mailvelope).to.deep.equal(window.mailvelope)
}) })
it('loads dynamically', async () => { it('loads dynamically', async() => {
const p = getMailvelope() const p = getMailvelope()
window.mailvelope = { window.mailvelope = {
mock: 3, mock: 3,

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

@ -19,8 +19,8 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>. * along with this program. If not, see <http://www.gnu.org/licenses/>.
*/ */
import {isPgpgMessage} from '../../../crypto/pgp' import { isPgpgMessage } from '../../../crypto/pgp'
import {html, plain} from '../../../util/text' import { html, plain } from '../../../util/text'
describe('pgp', () => { describe('pgp', () => {
it('detects non-pgp messages', () => { it('detects non-pgp messages', () => {

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

@ -19,7 +19,7 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>. * along with this program. If not, see <http://www.gnu.org/licenses/>.
*/ */
import {convertAxiosError} from '../../../errors/convert' import { convertAxiosError } from '../../../errors/convert'
describe('convert error', () => { describe('convert error', () => {
it('ignores errors without a response', () => { it('ignores errors without a response', () => {

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

@ -19,7 +19,7 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>. * along with this program. If not, see <http://www.gnu.org/licenses/>.
*/ */
import {matchError} from '../../../errors/match' import { matchError } from '../../../errors/match'
describe('match', () => { describe('match', () => {
it('throws an error when nothing matches', (done) => { it('throws an error when nothing matches', (done) => {

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

@ -19,7 +19,7 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>. * along with this program. If not, see <http://www.gnu.org/licenses/>.
*/ */
import {translate} from '../../../i18n/MailboxTranslator' import { translate } from '../../../i18n/MailboxTranslator'
describe('MailboxTranslator', () => { describe('MailboxTranslator', () => {
it('translates the inbox', () => { it('translates the inbox', () => {

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

@ -19,7 +19,7 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>. * along with this program. If not, see <http://www.gnu.org/licenses/>.
*/ */
import {buildMailboxHierarchy} from '../../../imap/MailboxHierarchy' import { buildMailboxHierarchy } from '../../../imap/MailboxHierarchy'
describe('mailboxHierarchyBuilder', () => { describe('mailboxHierarchyBuilder', () => {
it('handles empty collections', () => { it('handles empty collections', () => {

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

@ -19,7 +19,7 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>. * along with this program. If not, see <http://www.gnu.org/licenses/>.
*/ */
import {havePrefix} from '../../../imap/MailboxPrefix' import { havePrefix } from '../../../imap/MailboxPrefix'
describe('MailboxPrefix', () => { describe('MailboxPrefix', () => {
it('does not find a prefix if there is none', () => { it('does not find a prefix if there is none', () => {

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

@ -19,7 +19,7 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>. * along with this program. If not, see <http://www.gnu.org/licenses/>.
*/ */
import {sortMailboxes} from '../../../imap/MailboxSorter' import { sortMailboxes } from '../../../imap/MailboxSorter'
describe('mailboxSorter', () => { describe('mailboxSorter', () => {
it('sorts ordinary mailboxes', () => { it('sorts ordinary mailboxes', () => {

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

@ -20,14 +20,14 @@
*/ */
import sinon from 'sinon' import sinon from 'sinon'
import {curry, prop, range, reverse} from 'ramda' import { curry, prop, range, reverse } from 'ramda'
import orderBy from 'lodash/fp/orderBy' import orderBy from 'lodash/fp/orderBy'
import actions from '../../../store/actions' import actions from '../../../store/actions'
import * as MessageService from '../../../service/MessageService' import * as MessageService from '../../../service/MessageService'
import * as NotificationService from '../../../service/NotificationService' import * as NotificationService from '../../../service/NotificationService'
import {normalizedMessageId} from '../../../store/normalization' import { normalizedMessageId } from '../../../store/normalization'
import {UNIFIED_ACCOUNT_ID, UNIFIED_INBOX_ID} from '../../../store/constants' import { UNIFIED_ACCOUNT_ID, UNIFIED_INBOX_ID } from '../../../store/constants'
const mockEnvelope = curry((accountId, folderId, uid) => ({ const mockEnvelope = curry((accountId, folderId, uid) => ({
accountId, accountId,
@ -71,7 +71,7 @@ describe('Vuex store actions', () => {
expect(envelopes).to.be.empty expect(envelopes).to.be.empty
}) })
it('creates a unified page from one mailbox', async () => { it('creates a unified page from one mailbox', async() => {
context.getters.accounts.push({ context.getters.accounts.push({
id: 13, id: 13,
accountId: 13, accountId: 13,
@ -134,7 +134,7 @@ describe('Vuex store actions', () => {
}) })
}) })
it('fetches the next individual page', async () => { it('fetches the next individual page', async() => {
context.getters.accounts.push({ context.getters.accounts.push({
id: 13, id: 13,
accountId: 13, accountId: 13,
@ -179,7 +179,7 @@ describe('Vuex store actions', () => {
expect(context.commit).to.have.callCount(20) expect(context.commit).to.have.callCount(20)
}) })
it('builds the next unified page with local data', async () => { it('builds the next unified page with local data', async() => {
const page1 = reverse(range(25, 30)) const page1 = reverse(range(25, 30))
const page2 = reverse(range(30, 35)) const page2 = reverse(range(30, 35))
const msgs1 = reverse(range(10, 30)) const msgs1 = reverse(range(10, 30))
@ -243,7 +243,7 @@ describe('Vuex store actions', () => {
expect(page.length).to.equal(20) expect(page.length).to.equal(20)
}) })
it('builds the next unified page with partial fetch', async () => { it('builds the next unified page with partial fetch', async() => {
const page1 = reverse(range(25, 30)) const page1 = reverse(range(25, 30))
const page2 = reverse(range(30, 35)) const page2 = reverse(range(30, 35))
const msgs1 = reverse(range(25, 30)) const msgs1 = reverse(range(25, 30))
@ -325,7 +325,7 @@ describe('Vuex store actions', () => {
sinon.restore() sinon.restore()
}) })
it('fetches the inbox first', async () => { it('fetches the inbox first', async() => {
context.getters.accounts.push({ context.getters.accounts.push({
id: 13, id: 13,
accountId: 13, accountId: 13,
@ -392,7 +392,7 @@ describe('Vuex store actions', () => {
expect(NotificationService.showNewMessagesNotification).not.have.been.called expect(NotificationService.showNewMessagesNotification).not.have.been.called
}) })
it('syncs each individual mailbox', async () => { it('syncs each individual mailbox', async() => {
context.getters.accounts.push({ context.getters.accounts.push({
id: 13, id: 13,
accountId: 13, accountId: 13,
@ -448,7 +448,7 @@ describe('Vuex store actions', () => {
accountId: 13, accountId: 13,
folderId: 'INBOX', folderId: 'INBOX',
}) })
.returns(Promise.resolve([{id: 123}, {id: 321}])) .returns(Promise.resolve([{ id: 123 }, { id: 321 }]))
await actions.syncInboxes(context) await actions.syncInboxes(context)

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

@ -19,9 +19,9 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>. * along with this program. If not, see <http://www.gnu.org/licenses/>.
*/ */
import {curry, mapObjIndexed} from 'ramda' import { curry, mapObjIndexed } from 'ramda'
import {getters} from '../../../store/getters' import { getters } from '../../../store/getters'
const bindGetterToState = curry((getters, state, num, key) => getters[key](state, getters)) const bindGetterToState = curry((getters, state, num, key) => getters[key](state, getters))

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

@ -19,7 +19,7 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>. * along with this program. If not, see <http://www.gnu.org/licenses/>.
*/ */
import {normalizedFolderId, normalizedMessageId} from '../../../store/normalization' import { normalizedFolderId, normalizedMessageId } from '../../../store/normalization'
describe('Vuex store normalization', () => { describe('Vuex store normalization', () => {
it('creates a unique folder ID', () => { it('creates a unique folder ID', () => {

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

@ -19,7 +19,7 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>. * along with this program. If not, see <http://www.gnu.org/licenses/>.
*/ */
import {parseUuid} from '../../../util/EnvelopeUidParser' import { parseUuid } from '../../../util/EnvelopeUidParser'
describe('EnvelopeUidParser', () => { describe('EnvelopeUidParser', () => {
it('parses a simple UID', () => { it('parses a simple UID', () => {

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

@ -20,7 +20,7 @@
* *
*/ */
import {html, toPlain, plain, detect} from '../../../util/text' import { html, toPlain, plain, detect } from '../../../util/text'
describe('text', () => { describe('text', () => {
describe('toPlain', () => { describe('toPlain', () => {
@ -89,9 +89,9 @@ describe('text', () => {
it('converts deeply nested elements to text', () => { it('converts deeply nested elements to text', () => {
const source = html( const source = html(
'<html>' + '<html>'
'<body><p>Hello!</p><p>this <i>is</i> <b>some</b> random <strong>text</strong></p></body>' + + '<body><p>Hello!</p><p>this <i>is</i> <b>some</b> random <strong>text</strong></p></body>'
'</html>' + '</html>'
) )
const expected = plain('Hello!\n\nthis is some random text') const expected = plain('Hello!\n\nthis is some random text')
@ -111,7 +111,7 @@ describe('text', () => {
it('preserves quotes', () => { it('preserves quotes', () => {
const source = html( const source = html(
`<blockquote><div><b>yes.</b></div><div><br /></div><div>Am Montag, den 21.10.2019, 16:51 +0200 schrieb Christoph Wurst:</div><blockquote style="margin:0 0 0 .8ex;border-left:2px #729fcf solid;padding-left:1ex;"><div>ok cool</div><div><br /></div><div>Am Montag, den 21.10.2019, 16:51 +0200 schrieb Christoph Wurst:</div><blockquote style="margin:0 0 0 .8ex;border-left:2px #729fcf solid;padding-left:1ex;"><div>Hello</div><div><br /></div><div>this is some t<i>e</i>xt</div><div><br /></div><div>yes</div><div><br /></div><div>cheers</div><br></blockquote><br></blockquote></blockquote>` '<blockquote><div><b>yes.</b></div><div><br /></div><div>Am Montag, den 21.10.2019, 16:51 +0200 schrieb Christoph Wurst:</div><blockquote style="margin:0 0 0 .8ex;border-left:2px #729fcf solid;padding-left:1ex;"><div>ok cool</div><div><br /></div><div>Am Montag, den 21.10.2019, 16:51 +0200 schrieb Christoph Wurst:</div><blockquote style="margin:0 0 0 .8ex;border-left:2px #729fcf solid;padding-left:1ex;"><div>Hello</div><div><br /></div><div>this is some t<i>e</i>xt</div><div><br /></div><div>yes</div><div><br /></div><div>cheers</div><br></blockquote><br></blockquote></blockquote>'
) )
const expected = plain(`> yes. const expected = plain(`> yes.
> >

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

@ -19,7 +19,7 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>. * along with this program. If not, see <http://www.gnu.org/licenses/>.
*/ */
import {wait} from '../../../util/wait' import { wait } from '../../../util/wait'
describe('wait', () => { describe('wait', () => {
it('waits', (done) => { it('waits', (done) => {

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

@ -38,8 +38,8 @@ const flattenError = (error) => {
} }
const flattenTrace = (trace) => { const flattenTrace = (trace) => {
return trace.reduce(function (acc, entry) { return trace.reduce(function(acc, entry) {
var text = '' let text = ''
if (entry.class) { if (entry.class) {
text += ' at ' + entry.class + '::' + entry.function text += ' at ' + entry.class + '::' + entry.function
} else { } else {
@ -54,22 +54,22 @@ const flattenTrace = (trace) => {
export const getReportUrl = (error) => { export const getReportUrl = (error) => {
console.error(error) console.error(error)
var message = error.message || 'An unkown error occurred.' let message = error.message || 'An unkown error occurred.'
if (!message.endsWith('.')) { if (!message.endsWith('.')) {
message += '.' message += '.'
} }
var builder = new IssueTemplateBuilder() const builder = new IssueTemplateBuilder()
var template = builder const template = builder
.addEmptyStepsToReproduce() .addEmptyStepsToReproduce()
.addExpectedActualBehaviour() .addExpectedActualBehaviour()
.addLogs('Error', flattenError(error)) .addLogs('Error', flattenError(error))
.render() .render()
return ( return (
'https://github.com/nextcloud/mail/issues/new' + 'https://github.com/nextcloud/mail/issues/new'
'?title=' + + '?title='
encodeURIComponent(message) + + encodeURIComponent(message)
'&body=' + + '&body='
encodeURIComponent(template) + encodeURIComponent(template)
) )
} }

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

@ -17,7 +17,7 @@
* *
*/ */
import {translate as t} from '@nextcloud/l10n' import { translate as t } from '@nextcloud/l10n'
const smileys = [':-(', ':-/', ':-\\', ':-|', ":'-(", ":'-/", ":'-\\", ":'-|"] const smileys = [':-(', ':-/', ':-\\', ':-|', ":'-(", ":'-/", ":'-\\", ":'-|"]
@ -26,7 +26,7 @@ const getRandomSmiley = () => {
} }
/** /**
* @param {Folder} folder * @param {Folder} folder a folder
* @returns {string} * @returns {string}
*/ */
export const getRandomFolderErrorMessage = (folder) => { export const getRandomFolderErrorMessage = (folder) => {

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

@ -1,4 +1,4 @@
/* /**
* @copyright 2020 Christoph Wurst <christoph@winzerhof-wurst.at> * @copyright 2020 Christoph Wurst <christoph@winzerhof-wurst.at>
* *
* @author 2020 Christoph Wurst <christoph@winzerhof-wurst.at> * @author 2020 Christoph Wurst <christoph@winzerhof-wurst.at>
@ -19,21 +19,22 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>. * along with this program. If not, see <http://www.gnu.org/licenses/>.
*/ */
import {curry} from 'ramda' import { curry } from 'ramda'
import {fromString} from 'html-to-text' import { fromString } from 'html-to-text'
/** /**
* @type {Text} * @type {Text}
*/ */
class Text { class Text {
constructor(format, value) { constructor(format, value) {
this.format = format this.format = format
this.value = value this.value = value
} }
/** /**
* @param {Text} other * @param {Text} other other
* @return {Text} * @returns {Text}
*/ */
append(other) { append(other) {
if (this.format !== other.format) { if (this.format !== other.format) {
@ -42,6 +43,7 @@ class Text {
return new Text(this.format, this.value + other.value) return new Text(this.format, this.value + other.value)
} }
} }
/** /**
@ -99,8 +101,8 @@ export const isPlain = isFormat('plain')
export const isHtml = isFormat('html') export const isHtml = isFormat('html')
/** /**
* @param {Text} text * @param {Text} text text
* @return {Text} * @returns {Text}
*/ */
export const toPlain = (text) => { export const toPlain = (text) => {
if (text.format === 'plain') { if (text.format === 'plain') {
@ -114,12 +116,12 @@ export const toPlain = (text) => {
ignoreImage: true, ignoreImage: true,
wordwrap: false, wordwrap: false,
format: { format: {
blockquote: function (element, fn, options) { blockquote(element, fn, options) {
return fn(element.children, options) return fn(element.children, options)
.replace(/\n\n\n/g, '\n\n') // remove triple line breaks .replace(/\n\n\n/g, '\n\n') // remove triple line breaks
.replace(/^/gm, '> ') // add > quotation to each line .replace(/^/gm, '> ') // add > quotation to each line
}, },
paragraph: function (element, fn, options) { paragraph(element, fn, options) {
return fn(element.children, options) + '\n\n' return fn(element.children, options) + '\n\n'
}, },
}, },
@ -134,8 +136,8 @@ export const toPlain = (text) => {
} }
/** /**
* @param {Text} text * @param {Text} text text
* @return {Text} * @returns {Text}
*/ */
export const toHtml = (text) => { export const toHtml = (text) => {
if (text.format === 'html') { if (text.format === 'html') {

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

@ -1,4 +1,4 @@
/* /**
* @copyright 2020 Christoph Wurst <christoph@winzerhof-wurst.at> * @copyright 2020 Christoph Wurst <christoph@winzerhof-wurst.at>
* *
* @author 2020 Christoph Wurst <christoph@winzerhof-wurst.at> * @author 2020 Christoph Wurst <christoph@winzerhof-wurst.at>
@ -20,7 +20,7 @@
*/ */
export const wait = (ms) => { export const wait = (ms) => {
return new Promise((res) => { return new Promise((resolve) => {
setTimeout(res, ms) setTimeout(resolve, ms)
}) })
} }

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

@ -10,8 +10,7 @@
v-if="!account.provisioned" v-if="!account.provisioned"
class="button icon-rename" class="button icon-rename"
href="#account-form" href="#account-form"
:title="t('mail', 'Change name')" :title="t('mail', 'Change name')" />
></a>
</p> </p>
<AliasSettings v-if="!account.provisioned" :account="account" /> <AliasSettings v-if="!account.provisioned" :account="account" />
</div> </div>
@ -25,8 +24,7 @@
:display-name="displayName" :display-name="displayName"
:email="email" :email="email"
:save="onSave" :save="onSave"
:account="account" :account="account" />
/>
</div> </div>
</div> </div>
</AppContent> </AppContent>
@ -71,7 +69,7 @@ export default {
}, },
methods: { methods: {
onSave(data) { onSave(data) {
Logger.log('saving data', {data}) Logger.log('saving data', { data })
return this.$store return this.$store
.dispatch('updateAccount', { .dispatch('updateAccount', {
...data, ...data,
@ -79,7 +77,7 @@ export default {
}) })
.then((account) => account) .then((account) => account)
.catch((error) => { .catch((error) => {
Logger.error('account update failed:', {error}) Logger.error('account update failed:', { error })
throw error throw error
}) })

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

@ -35,12 +35,12 @@ export default {
watch: { watch: {
$route(to, from) { $route(to, from) {
if ( if (
from.name === 'message' && from.name === 'message'
to.name === 'folder' && && to.name === 'folder'
!this.isMobile && && !this.isMobile
Number.parseInt(from.params.accountId, 10) === Number.parseInt(to.params.accountId, 10) && && Number.parseInt(from.params.accountId, 10) === Number.parseInt(to.params.accountId, 10)
from.params.folderId === to.params.folderId && && from.params.folderId === to.params.folderId
from.params.filter === to.params.filter && from.params.filter === to.params.filter
) { ) {
logger.warn("navigation from a message to just the folder. we don't want that, do we? let's go back", { logger.warn("navigation from a message to just the folder. we don't want that, do we? let's go back", {
to, to,
@ -55,9 +55,9 @@ export default {
if (this.$route.name === 'home' && accounts.length > 1) { if (this.$route.name === 'home' && accounts.length > 1) {
// Show first account // Show first account
let firstAccount = accounts[0] const firstAccount = accounts[0]
// FIXME: this assumes that there's at least one folder // FIXME: this assumes that there's at least one folder
let firstFolder = this.$store.getters.getFolders(firstAccount.id)[0] const firstFolder = this.$store.getters.getFolders(firstAccount.id)[0]
console.debug('loading first folder of first account', firstAccount.id, firstFolder.id) console.debug('loading first folder of first account', firstAccount.id, firstFolder.id)
@ -80,9 +80,9 @@ export default {
} }
// Show first account // Show first account
let firstAccount = accounts[0] const firstAccount = accounts[0]
// FIXME: this assumes that there's at least one folder // FIXME: this assumes that there's at least one folder
let firstFolder = this.$store.getters.getFolders(firstAccount.id)[0] const firstFolder = this.$store.getters.getFolders(firstAccount.id)[0]
console.debug('loading composer with first account and folder', firstAccount.id, firstFolder.id) console.debug('loading composer with first account and folder', firstAccount.id, firstFolder.id)

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

@ -2,7 +2,7 @@
<Content app-name="mail"> <Content app-name="mail">
<Navigation v-if="hasAccounts" /> <Navigation v-if="hasAccounts" />
<div id="emptycontent"> <div id="emptycontent">
<div class="icon-mail"></div> <div class="icon-mail" />
<h2>{{ t('mail', 'Connect your mail account') }}</h2> <h2>{{ t('mail', 'Connect your mail account') }}</h2>
<AccountForm :display-name="displayName" :email="email" :save="onSave"> <AccountForm :display-name="displayName" :email="email" :save="onSave">
<template v-if="error" #feedback class="warning"> <template v-if="error" #feedback class="warning">
@ -15,8 +15,8 @@
<script> <script>
import Content from '@nextcloud/vue/dist/Components/Content' import Content from '@nextcloud/vue/dist/Components/Content'
import {loadState} from '@nextcloud/initial-state' import { loadState } from '@nextcloud/initial-state'
import {translate as t} from '@nextcloud/l10n' import { translate as t } from '@nextcloud/l10n'
import AccountForm from '../components/AccountForm' import AccountForm from '../components/AccountForm'
import Navigation from '../components/Navigation' import Navigation from '../components/Navigation'
@ -56,7 +56,7 @@ export default {
return account return account
}) })
.catch((error) => { .catch((error) => {
logger.error('Could not create account', {error}) logger.error('Could not create account', { error })
if (error.message) { if (error.message) {
this.error = error.message this.error = error.message

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

@ -48,7 +48,6 @@ module.exports = {
{ {
test: /\.js$/, test: /\.js$/,
loader: 'babel-loader', loader: 'babel-loader',
exclude: /node_modules/,
exclude: /node_modules(?!(\/|\\)(@ckeditor)(\/|\\))/ exclude: /node_modules(?!(\/|\\)(@ckeditor)(\/|\\))/
}, },
{ {