Migrate to eslint-config-nextcloud
Signed-off-by: John Molakvoæ (skjnldsv) <skjnldsv@protonmail.com>
This commit is contained in:
Родитель
c49f2f5c00
Коммит
5cda717371
23
.eslintrc.js
23
.eslintrc.js
|
@ -1,28 +1,7 @@
|
|||
module.exports = {
|
||||
root: true,
|
||||
env: {
|
||||
node: true,
|
||||
amd: true,
|
||||
jquery: true,
|
||||
mocha: true,
|
||||
},
|
||||
extends: [
|
||||
'plugin:@nextcloud/recommended',
|
||||
'plugin:prettier/recommended',
|
||||
'plugin:vue/recommended',
|
||||
'prettier/vue',
|
||||
'eslint:recommended',
|
||||
'@nextcloud'
|
||||
],
|
||||
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: {
|
||||
expect: true,
|
||||
OC: true,
|
||||
|
|
Разница между файлами не показана из-за своего большого размера
Загрузить разницу
25
package.json
25
package.json
|
@ -9,8 +9,8 @@
|
|||
"build": "NODE_ENV=production webpack --progress --hide-modules --config webpack.prod.js",
|
||||
"dev": "NODE_ENV=development webpack --config webpack.dev.js",
|
||||
"watch": "NODE_ENV=development webpack --progress --watch --config webpack.dev.js",
|
||||
"lint": "eslint --ext .js,.vue src",
|
||||
"lint:autofix": "eslint --ext .js,.vue src --fix",
|
||||
"lint": "eslint --ext .js,.vue --ignore-pattern tests src",
|
||||
"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: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",
|
||||
"ramda": "^0.27.0",
|
||||
"raw-loader": "^4.0.1",
|
||||
"stylelint": "^13.6.1",
|
||||
"v-tooltip": "^2.0.3",
|
||||
"vue": "^2.6.11",
|
||||
"vue-autosize": "^1.0.2",
|
||||
|
@ -76,16 +77,20 @@
|
|||
"@babel/plugin-syntax-dynamic-import": "^7.8.3",
|
||||
"@babel/preset-env": "^7.10.3",
|
||||
"@nextcloud/browserslist-config": "^1.0.0",
|
||||
"@nextcloud/eslint-config": "^2.0.0",
|
||||
"@nextcloud/eslint-plugin": "^1.4.0",
|
||||
"@vue/test-utils": "^1.0.3",
|
||||
"babel-eslint": "^10.1.0",
|
||||
"babel-loader": "^8.1.0",
|
||||
"browserslist-config-nextcloud": "0.1.0",
|
||||
"chai": "^4.2.0",
|
||||
"css-loader": "^3.6.0",
|
||||
"eslint": "^6.8.0",
|
||||
"eslint-config-prettier": "^6.11.0",
|
||||
"eslint-plugin-prettier": "^3.1.4",
|
||||
"eslint-config-standard": "^14.1.1",
|
||||
"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",
|
||||
"file-loader": "^6.0.0",
|
||||
"jsdom": "^16.2.2",
|
||||
|
@ -93,7 +98,6 @@
|
|||
"mocha": "^7.2.0",
|
||||
"mochapack": "^1.1.15",
|
||||
"node-sass": "^4.14.1",
|
||||
"prettier": "2.0.5",
|
||||
"sass-loader": "^8.0.2",
|
||||
"sinon": "^9.0.2",
|
||||
"sinon-chai": "^3.4.0",
|
||||
|
@ -106,14 +110,5 @@
|
|||
"webpack-cli": "^3.3.12",
|
||||
"webpack-merge": "^4.2.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>
|
||||
import logger from './logger'
|
||||
import {matchError} from './errors/match'
|
||||
import { matchError } from './errors/match'
|
||||
import MailboxLockedError from './errors/MailboxLockedError'
|
||||
|
||||
export default {
|
||||
|
@ -36,7 +36,7 @@ export default {
|
|||
},
|
||||
methods: {
|
||||
sync() {
|
||||
setTimeout(async () => {
|
||||
setTimeout(async() => {
|
||||
try {
|
||||
await this.$store.dispatch('syncInboxes')
|
||||
|
||||
|
@ -44,10 +44,10 @@ export default {
|
|||
} catch (error) {
|
||||
matchError(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) {
|
||||
logger.error('Background sync failed: ' + error.message, {error})
|
||||
logger.error('Background sync failed: ' + error.message, { error })
|
||||
},
|
||||
})
|
||||
} finally {
|
||||
|
|
|
@ -23,36 +23,34 @@
|
|||
import moment from '@nextcloud/moment'
|
||||
import negate from 'lodash/fp/negate'
|
||||
|
||||
import {html} from './util/text'
|
||||
import { html } from './util/text'
|
||||
|
||||
/**
|
||||
* @param {Text} original
|
||||
* @param {object} from
|
||||
* @param {Number} date
|
||||
* @return {Text}
|
||||
* @param {Text} original original
|
||||
* @param {object} from from
|
||||
* @param {Number} date date
|
||||
* @returns {Text}
|
||||
*/
|
||||
export const buildReplyBody = (original, from, date) => {
|
||||
const start = '<p></p><p></p>'
|
||||
const plainBody = '<br>> ' + original.value.replace(/\n/g, '<br>> ')
|
||||
const htmlBody = `<blockquote>${original.value}</blockquote>`
|
||||
|
||||
switch (original.format) {
|
||||
case 'plain':
|
||||
const plainBody = '<br>> ' + original.value.replace(/\n/g, '<br>> ')
|
||||
|
||||
if (from) {
|
||||
const dateString = moment.unix(date).format('LLL')
|
||||
return html(`${start}"${from.label}" ${from.email} – ${dateString}` + plainBody)
|
||||
} else {
|
||||
return html(`${start}${plainBody}`)
|
||||
}
|
||||
case 'html':
|
||||
const htmlBody = `<blockquote>${original.value}</blockquote>`
|
||||
|
||||
if (from) {
|
||||
const dateString = moment.unix(date).format('LLL')
|
||||
return html(`${start}"${from.label}" ${from.email} – ${dateString}<br>${htmlBody}`)
|
||||
} else {
|
||||
return html(`${start}${htmlBody}`)
|
||||
}
|
||||
case 'plain':
|
||||
if (from) {
|
||||
const dateString = moment.unix(date).format('LLL')
|
||||
return html(`${start}"${from.label}" ${from.email} – ${dateString}` + plainBody)
|
||||
} else {
|
||||
return html(`${start}${plainBody}`)
|
||||
}
|
||||
case 'html':
|
||||
if (from) {
|
||||
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}`)
|
||||
|
@ -108,7 +106,7 @@ export const buildRecipients = (envelope, ownAddress) => {
|
|||
}
|
||||
|
||||
// edge case: pure self-sent email
|
||||
if (to.length == 0) {
|
||||
if (to.length === 0) {
|
||||
to = envelope.from
|
||||
}
|
||||
|
||||
|
|
|
@ -1,11 +1,10 @@
|
|||
<template>
|
||||
<form id="account-form" @submit.prevent="onSubmit">
|
||||
<tabs
|
||||
<Tabs
|
||||
:options="{useUrlFragment: false, defaultTabHash: settingsPage ? 'manual' : 'auto'}"
|
||||
cache-lifetime="0"
|
||||
@changed="onModeChanged"
|
||||
>
|
||||
<tab id="auto" key="auto" :name="t('mail', 'Auto')">
|
||||
@changed="onModeChanged">
|
||||
<Tab id="auto" key="auto" :name="t('mail', 'Auto')">
|
||||
<label for="auto-name">{{ t('mail', 'Name') }}</label>
|
||||
<input
|
||||
id="auto-name"
|
||||
|
@ -13,8 +12,7 @@
|
|||
type="text"
|
||||
:placeholder="t('mail', 'Name')"
|
||||
:disabled="loading"
|
||||
autofocus
|
||||
/>
|
||||
autofocus>
|
||||
<label for="auto-address">{{ t('mail', 'Mail Address') }}</label>
|
||||
<input
|
||||
id="auto-address"
|
||||
|
@ -22,8 +20,7 @@
|
|||
type="email"
|
||||
:placeholder="t('mail', 'Mail Address')"
|
||||
:disabled="loading"
|
||||
required
|
||||
/>
|
||||
required>
|
||||
<label for="auto-password">{{ t('mail', 'Password') }}</label>
|
||||
<input
|
||||
id="auto-password"
|
||||
|
@ -31,18 +28,16 @@
|
|||
type="password"
|
||||
:placeholder="t('mail', 'Password')"
|
||||
:disabled="loading"
|
||||
required
|
||||
/>
|
||||
</tab>
|
||||
<tab id="manual" key="manual" :name="t('mail', 'Manual')">
|
||||
required>
|
||||
</Tab>
|
||||
<Tab id="manual" key="manual" :name="t('mail', 'Manual')">
|
||||
<label for="man-name">{{ t('mail', 'Name') }}</label>
|
||||
<input
|
||||
id="man-name"
|
||||
v-model="manualConfig.accountName"
|
||||
type="text"
|
||||
:placeholder="t('mail', 'Name')"
|
||||
:disabled="loading"
|
||||
/>
|
||||
:disabled="loading">
|
||||
<label for="man-address">{{ t('mail', 'Mail Address') }}</label>
|
||||
<input
|
||||
id="man-address"
|
||||
|
@ -50,8 +45,7 @@
|
|||
type="email"
|
||||
:placeholder="t('mail', 'Mail Address')"
|
||||
:disabled="loading"
|
||||
required
|
||||
/>
|
||||
required>
|
||||
|
||||
<h3>{{ t('mail', 'IMAP Settings') }}</h3>
|
||||
<label for="man-imap-host">{{ t('mail', 'IMAP Host') }}</label>
|
||||
|
@ -61,8 +55,7 @@
|
|||
type="text"
|
||||
:placeholder="t('mail', 'IMAP Host')"
|
||||
:disabled="loading"
|
||||
required
|
||||
/>
|
||||
required>
|
||||
<h4>{{ t('mail', 'IMAP Security') }}</h4>
|
||||
<div class="flex-row">
|
||||
<input
|
||||
|
@ -72,14 +65,11 @@
|
|||
name="man-imap-sec"
|
||||
:disabled="loading"
|
||||
value="none"
|
||||
@change="onImapSslModeChange"
|
||||
/>
|
||||
@change="onImapSslModeChange">
|
||||
<label
|
||||
class="button"
|
||||
for="man-imap-sec-none"
|
||||
:class="{primary: manualConfig.imapSslMode === 'none'}"
|
||||
>{{ t('mail', 'None') }}</label
|
||||
>
|
||||
:class="{primary: manualConfig.imapSslMode === 'none'}">{{ t('mail', 'None') }}</label>
|
||||
<input
|
||||
id="man-imap-sec-ssl"
|
||||
v-model="manualConfig.imapSslMode"
|
||||
|
@ -87,14 +77,11 @@
|
|||
name="man-imap-sec"
|
||||
:disabled="loading"
|
||||
value="ssl"
|
||||
@change="onImapSslModeChange"
|
||||
/>
|
||||
@change="onImapSslModeChange">
|
||||
<label
|
||||
class="button"
|
||||
for="man-imap-sec-ssl"
|
||||
:class="{primary: manualConfig.imapSslMode === 'ssl'}"
|
||||
>{{ t('mail', 'SSL/TLS') }}</label
|
||||
>
|
||||
:class="{primary: manualConfig.imapSslMode === 'ssl'}">{{ t('mail', 'SSL/TLS') }}</label>
|
||||
<input
|
||||
id="man-imap-sec-tls"
|
||||
v-model="manualConfig.imapSslMode"
|
||||
|
@ -102,14 +89,11 @@
|
|||
name="man-imap-sec"
|
||||
:disabled="loading"
|
||||
value="tls"
|
||||
@change="onImapSslModeChange"
|
||||
/>
|
||||
@change="onImapSslModeChange">
|
||||
<label
|
||||
class="button"
|
||||
for="man-imap-sec-tls"
|
||||
:class="{primary: manualConfig.imapSslMode === 'tls'}"
|
||||
>{{ t('mail', 'STARTTLS') }}</label
|
||||
>
|
||||
:class="{primary: manualConfig.imapSslMode === 'tls'}">{{ t('mail', 'STARTTLS') }}</label>
|
||||
</div>
|
||||
<label for="man-imap-port">{{ t('mail', 'IMAP Port') }}</label>
|
||||
<input
|
||||
|
@ -118,8 +102,7 @@
|
|||
type="number"
|
||||
:placeholder="t('mail', 'IMAP Port')"
|
||||
:disabled="loading"
|
||||
required
|
||||
/>
|
||||
required>
|
||||
<label for="man-imap-user">{{ t('mail', 'IMAP User') }}</label>
|
||||
<input
|
||||
id="man-imap-user"
|
||||
|
@ -127,8 +110,7 @@
|
|||
type="text"
|
||||
:placeholder="t('mail', 'IMAP User')"
|
||||
:disabled="loading"
|
||||
required
|
||||
/>
|
||||
required>
|
||||
<label for="man-imap-password">{{ t('mail', 'IMAP Password') }}</label>
|
||||
<input
|
||||
id="man-imap-password"
|
||||
|
@ -136,8 +118,7 @@
|
|||
type="password"
|
||||
:placeholder="t('mail', 'IMAP Password')"
|
||||
:disabled="loading"
|
||||
required
|
||||
/>
|
||||
required>
|
||||
|
||||
<h3>{{ t('mail', 'SMTP Settings') }}</h3>
|
||||
<input
|
||||
|
@ -147,8 +128,7 @@
|
|||
name="smtp-host"
|
||||
:placeholder="t('mail', 'SMTP Host')"
|
||||
:disabled="loading"
|
||||
required
|
||||
/>
|
||||
required>
|
||||
<h4>{{ t('mail', 'SMTP Security') }}</h4>
|
||||
<div class="flex-row">
|
||||
<input
|
||||
|
@ -158,14 +138,11 @@
|
|||
name="man-smtp-sec"
|
||||
:disabled="loading"
|
||||
value="none"
|
||||
@change="onSmtpSslModeChange"
|
||||
/>
|
||||
@change="onSmtpSslModeChange">
|
||||
<label
|
||||
class="button"
|
||||
for="man-smtp-sec-none"
|
||||
:class="{primary: manualConfig.smtpSslMode === 'none'}"
|
||||
>{{ t('mail', 'None') }}</label
|
||||
>
|
||||
:class="{primary: manualConfig.smtpSslMode === 'none'}">{{ t('mail', 'None') }}</label>
|
||||
<input
|
||||
id="man-smtp-sec-ssl"
|
||||
v-model="manualConfig.smtpSslMode"
|
||||
|
@ -173,14 +150,11 @@
|
|||
name="man-smtp-sec"
|
||||
:disabled="loading"
|
||||
value="ssl"
|
||||
@change="onSmtpSslModeChange"
|
||||
/>
|
||||
@change="onSmtpSslModeChange">
|
||||
<label
|
||||
class="button"
|
||||
for="man-smtp-sec-ssl"
|
||||
:class="{primary: manualConfig.smtpSslMode === 'ssl'}"
|
||||
>{{ t('mail', 'SSL/TLS') }}</label
|
||||
>
|
||||
:class="{primary: manualConfig.smtpSslMode === 'ssl'}">{{ t('mail', 'SSL/TLS') }}</label>
|
||||
<input
|
||||
id="man-smtp-sec-tls"
|
||||
v-model="manualConfig.smtpSslMode"
|
||||
|
@ -188,14 +162,11 @@
|
|||
name="man-smtp-sec"
|
||||
:disabled="loading"
|
||||
value="tls"
|
||||
@change="onSmtpSslModeChange"
|
||||
/>
|
||||
@change="onSmtpSslModeChange">
|
||||
<label
|
||||
class="button"
|
||||
for="man-smtp-sec-tls"
|
||||
:class="{primary: manualConfig.smtpSslMode === 'tls'}"
|
||||
>{{ t('mail', 'STARTTLS') }}</label
|
||||
>
|
||||
:class="{primary: manualConfig.smtpSslMode === 'tls'}">{{ t('mail', 'STARTTLS') }}</label>
|
||||
</div>
|
||||
<label for="man-smtp-port">{{ t('mail', 'SMTP Port') }}</label>
|
||||
<input
|
||||
|
@ -204,8 +175,7 @@
|
|||
type="number"
|
||||
:placeholder="t('mail', 'SMTP Port')"
|
||||
:disabled="loading"
|
||||
required
|
||||
/>
|
||||
required>
|
||||
<label for="man-smtp-user">{{ t('mail', 'SMTP User') }}</label>
|
||||
<input
|
||||
id="man-smtp-user"
|
||||
|
@ -213,8 +183,7 @@
|
|||
type="text"
|
||||
:placeholder="t('mail', 'SMTP User')"
|
||||
:disabled="loading"
|
||||
required
|
||||
/>
|
||||
required>
|
||||
<label for="man-smtp-password">{{ t('mail', 'SMTP Password') }}</label>
|
||||
<input
|
||||
id="man-smtp-password"
|
||||
|
@ -222,17 +191,20 @@
|
|||
type="password"
|
||||
:placeholder="t('mail', 'SMTP Password')"
|
||||
:disabled="loading"
|
||||
required
|
||||
/>
|
||||
</tab>
|
||||
</tabs>
|
||||
<slot name="feedback"></slot>
|
||||
<input type="submit" class="primary" :disabled="loading" :value="submitButtonText" @click.prevent="onSubmit" />
|
||||
required>
|
||||
</Tab>
|
||||
</Tabs>
|
||||
<slot name="feedback" />
|
||||
<input type="submit"
|
||||
class="primary"
|
||||
:disabled="loading"
|
||||
:value="submitButtonText"
|
||||
@click.prevent="onSubmit">
|
||||
</form>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import {Tab, Tabs} from 'vue-tabs-component'
|
||||
import { Tab, Tabs } from 'vue-tabs-component'
|
||||
|
||||
import logger from '../logger'
|
||||
|
||||
|
@ -331,24 +303,24 @@ export default {
|
|||
},
|
||||
onImapSslModeChange() {
|
||||
switch (this.manualConfig.imapSslMode) {
|
||||
case 'none':
|
||||
case 'tls':
|
||||
this.manualConfig.imapPort = 143
|
||||
break
|
||||
case 'ssl':
|
||||
this.manualConfig.imapPort = 993
|
||||
break
|
||||
case 'none':
|
||||
case 'tls':
|
||||
this.manualConfig.imapPort = 143
|
||||
break
|
||||
case 'ssl':
|
||||
this.manualConfig.imapPort = 993
|
||||
break
|
||||
}
|
||||
},
|
||||
onSmtpSslModeChange() {
|
||||
switch (this.manualConfig.smtpSslMode) {
|
||||
case 'none':
|
||||
case 'tls':
|
||||
this.manualConfig.smtpPort = 587
|
||||
break
|
||||
case 'ssl':
|
||||
this.manualConfig.smtpPort = 465
|
||||
break
|
||||
case 'none':
|
||||
case 'tls':
|
||||
this.manualConfig.smtpPort = 587
|
||||
break
|
||||
case 'ssl':
|
||||
this.manualConfig.smtpPort = 465
|
||||
break
|
||||
}
|
||||
},
|
||||
saveChanges() {
|
||||
|
@ -365,12 +337,12 @@ export default {
|
|||
}
|
||||
},
|
||||
onSubmit(event) {
|
||||
console.debug('account form submitted', {event})
|
||||
console.debug('account form submitted', { event })
|
||||
|
||||
this.loading = true
|
||||
|
||||
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))
|
||||
},
|
||||
},
|
||||
|
|
|
@ -1,6 +1,13 @@
|
|||
<template>
|
||||
<router-link v-if="email !== label" v-tooltip.bottom="email" :to="newMessageRoute" exact>{{ label }}</router-link>
|
||||
<router-link v-else :to="newMessageRoute" exact>{{ label }}</router-link>
|
||||
<router-link v-if="email !== label"
|
||||
v-tooltip.bottom="email"
|
||||
:to="newMessageRoute"
|
||||
exact>
|
||||
{{ label }}
|
||||
</router-link>
|
||||
<router-link v-else :to="newMessageRoute" exact>
|
||||
{{ label }}
|
||||
</router-link>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
|
|
|
@ -23,7 +23,7 @@
|
|||
<li v-for="curAlias in aliases" :key="curAlias.id">
|
||||
<strong>{{ curAlias.name }}</strong> <{{ curAlias.alias }}>
|
||||
|
||||
<button class="icon-delete" @click="deleteAlias(curAlias)"></button>
|
||||
<button class="icon-delete" @click="deleteAlias(curAlias)" />
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
|
@ -34,8 +34,7 @@
|
|||
v-model="alias.aliasName"
|
||||
type="text"
|
||||
:placeholder="t('mail', 'Name')"
|
||||
:disabled="loading"
|
||||
/>
|
||||
:disabled="loading">
|
||||
|
||||
<input
|
||||
v-if="addMode"
|
||||
|
@ -44,8 +43,7 @@
|
|||
v-model="alias.alias"
|
||||
type="email"
|
||||
:placeholder="t('mail', 'Mail Address')"
|
||||
:disabled="loading"
|
||||
/>
|
||||
:disabled="loading">
|
||||
</div>
|
||||
|
||||
<div>
|
||||
|
@ -58,8 +56,7 @@
|
|||
class="primary"
|
||||
:class="loading ? 'icon-loading-small-dark' : 'icon-checkmark-white'"
|
||||
:disabled="loading"
|
||||
@click="saveAlias"
|
||||
>
|
||||
@click="saveAlias">
|
||||
{{ t('mail', 'Save') }}
|
||||
</button>
|
||||
</div>
|
||||
|
@ -82,7 +79,7 @@ export default {
|
|||
return {
|
||||
addMode: false,
|
||||
loading: false,
|
||||
alias: {aliasName: this.account.name, alias: ''},
|
||||
alias: { aliasName: this.account.name, alias: '' },
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
|
@ -100,15 +97,15 @@ export default {
|
|||
},
|
||||
async deleteAlias(alias) {
|
||||
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')
|
||||
this.loading = false
|
||||
},
|
||||
async saveAlias() {
|
||||
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')
|
||||
this.alias = {aliasName: this.account.name, alias: ''}
|
||||
this.alias = { aliasName: this.account.name, alias: '' }
|
||||
this.loading = false
|
||||
},
|
||||
},
|
||||
|
|
|
@ -20,7 +20,10 @@
|
|||
-->
|
||||
|
||||
<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>
|
||||
|
||||
<script>
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
</router-link>
|
||||
|
||||
<p v-if="loadingOptOutSettings" class="app-settings">
|
||||
<span class="icon-loading-small"></span>
|
||||
<span class="icon-loading-small" />
|
||||
{{ text }}
|
||||
</p>
|
||||
<p v-else class="app-settings">
|
||||
|
@ -14,13 +14,12 @@
|
|||
class="checkbox"
|
||||
type="checkbox"
|
||||
:checked="useDataCollection"
|
||||
@change="onToggleCollectData"
|
||||
/>
|
||||
@change="onToggleCollectData">
|
||||
<label for="data-collection-toggle">{{ text }}</label>
|
||||
</p>
|
||||
|
||||
<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') }}
|
||||
</p>
|
||||
<p v-else class="app-settings">
|
||||
|
@ -29,8 +28,7 @@
|
|||
class="checkbox"
|
||||
type="checkbox"
|
||||
:checked="useExternalAvatars"
|
||||
@change="onToggleExternalAvatars"
|
||||
/>
|
||||
@change="onToggleExternalAvatars">
|
||||
<label for="gravatar-enabled">{{ t('mail', 'Use Gravatar and favicon avatars') }}</label>
|
||||
</p>
|
||||
<p>
|
||||
|
@ -48,7 +46,7 @@
|
|||
|
||||
<script>
|
||||
import Logger from '../logger'
|
||||
import {generateUrl} from '@nextcloud/router'
|
||||
import { generateUrl } from '@nextcloud/router'
|
||||
|
||||
export default {
|
||||
name: 'AppSettingsMenu',
|
||||
|
@ -77,7 +75,7 @@ export default {
|
|||
key: 'external-avatars',
|
||||
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(() => {
|
||||
this.loadingAvatarSettings = false
|
||||
})
|
||||
|
@ -90,19 +88,19 @@ export default {
|
|||
key: 'collect-data',
|
||||
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(() => {
|
||||
this.loadingOptOutSettings = false
|
||||
})
|
||||
},
|
||||
registerProtocolHandler: function () {
|
||||
registerProtocolHandler() {
|
||||
if (window.navigator.registerProtocolHandler) {
|
||||
var url =
|
||||
window.location.protocol + '//' + window.location.host + generateUrl('apps/mail/compose?uri=%s')
|
||||
const url
|
||||
= window.location.protocol + '//' + window.location.host + generateUrl('apps/mail/compose?uri=%s')
|
||||
try {
|
||||
window.navigator.registerProtocolHandler('mailto', url, OC.theme.name + ' Mail')
|
||||
} catch (err) {
|
||||
Logger.error('could not register protocol handler', {err})
|
||||
Logger.error('could not register protocol handler', { err })
|
||||
}
|
||||
}
|
||||
},
|
||||
|
|
|
@ -21,13 +21,16 @@
|
|||
|
||||
<template>
|
||||
<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>
|
||||
|
||||
<script>
|
||||
import BaseAvatar from '@nextcloud/vue/dist/Components/Avatar'
|
||||
|
||||
import {fetchAvatarUrlMemoized} from '../service/AvatarService'
|
||||
import { fetchAvatarUrlMemoized } from '../service/AvatarService'
|
||||
|
||||
export default {
|
||||
name: 'Avatar',
|
||||
|
|
|
@ -15,8 +15,7 @@
|
|||
:custom-label="formatAliases"
|
||||
:placeholder="t('mail', 'Select account')"
|
||||
:clear-on-select="false"
|
||||
@keyup="onInputChanged"
|
||||
/>
|
||||
@keyup="onInputChanged" />
|
||||
</div>
|
||||
<div class="composer-fields">
|
||||
<label class="to-label" for="to">
|
||||
|
@ -37,9 +36,11 @@
|
|||
:preserve-search="true"
|
||||
@keyup="onInputChanged"
|
||||
@tag="onNewToAddr"
|
||||
@search-change="onAutocomplete"
|
||||
/>
|
||||
<a v-if="!showCC" class="copy-toggle" href="#" @click.prevent="showCC = true">
|
||||
@search-change="onAutocomplete" />
|
||||
<a v-if="!showCC"
|
||||
class="copy-toggle"
|
||||
href="#"
|
||||
@click.prevent="showCC = true">
|
||||
{{ t('mail', '+ Cc/Bcc') }}
|
||||
</a>
|
||||
</div>
|
||||
|
@ -61,8 +62,7 @@
|
|||
:preserve-search="true"
|
||||
@keyup="onInputChanged"
|
||||
@tag="onNewCcAddr"
|
||||
@search-change="onAutocomplete"
|
||||
>
|
||||
@search-change="onAutocomplete">
|
||||
<span slot="noOptions">{{ t('mail', 'No contacts found.') }}</span>
|
||||
</Multiselect>
|
||||
</div>
|
||||
|
@ -83,8 +83,7 @@
|
|||
:preserve-search="true"
|
||||
@keyup="onInputChanged"
|
||||
@tag="onNewBccAddr"
|
||||
@search-change="onAutocomplete"
|
||||
>
|
||||
@search-change="onAutocomplete">
|
||||
<span slot="noOptions">{{ t('mail', 'No contacts found.') }}</span>
|
||||
</Multiselect>
|
||||
</div>
|
||||
|
@ -100,8 +99,7 @@
|
|||
class="subject"
|
||||
autocomplete="off"
|
||||
:placeholder="t('mail', 'Subject …')"
|
||||
@keyup="onInputChanged"
|
||||
/>
|
||||
@keyup="onInputChanged">
|
||||
</div>
|
||||
<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.') }}
|
||||
|
@ -124,8 +122,7 @@
|
|||
:placeholder="t('mail', 'Write message …')"
|
||||
:focus="isReply"
|
||||
:bus="bus"
|
||||
@input="onInputChanged"
|
||||
></TextEditor>
|
||||
@input="onInputChanged" />
|
||||
<TextEditor
|
||||
v-else-if="!encrypt && !editorPlainText"
|
||||
key="editor-rich"
|
||||
|
@ -136,16 +133,14 @@
|
|||
:placeholder="t('mail', 'Write message …')"
|
||||
:focus="isReply"
|
||||
:bus="bus"
|
||||
@input="onInputChanged"
|
||||
></TextEditor>
|
||||
@input="onInputChanged" />
|
||||
<MailvelopeEditor
|
||||
v-else
|
||||
ref="mailvelopeEditor"
|
||||
v-model="bodyVal"
|
||||
:recipients="allRecipients"
|
||||
:quoted-text="body"
|
||||
:is-reply-or-forward="isReply || isForward"
|
||||
/>
|
||||
:is-reply-or-forward="isReply || isForward" />
|
||||
</div>
|
||||
<div class="composer-actions">
|
||||
<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>
|
||||
</p>
|
||||
<Actions>
|
||||
<ActionButton icon="icon-upload" @click="onAddLocalAttachment">{{
|
||||
t('mail', 'Upload attachment')
|
||||
}}</ActionButton>
|
||||
<ActionButton icon="icon-folder" @click="onAddCloudAttachment">{{
|
||||
t('mail', 'Add attachment from Files')
|
||||
}}</ActionButton>
|
||||
<ActionButton :disabled="encrypt" icon="icon-folder" @click="onAddCloudAttachmentLink">{{
|
||||
t('mail', 'Add attachment link from Files')
|
||||
}}</ActionButton>
|
||||
<ActionButton icon="icon-upload" @click="onAddLocalAttachment">
|
||||
{{
|
||||
t('mail', 'Upload attachment')
|
||||
}}
|
||||
</ActionButton>
|
||||
<ActionButton icon="icon-folder" @click="onAddCloudAttachment">
|
||||
{{
|
||||
t('mail', 'Add attachment from Files')
|
||||
}}
|
||||
</ActionButton>
|
||||
<ActionButton :disabled="encrypt" icon="icon-folder" @click="onAddCloudAttachmentLink">
|
||||
{{
|
||||
t('mail', 'Add attachment link from Files')
|
||||
}}
|
||||
</ActionButton>
|
||||
<ActionCheckbox
|
||||
:checked="!encrypt && !editorPlainText"
|
||||
:disabled="encrypt"
|
||||
@check="editorMode = 'html'"
|
||||
@uncheck="editorMode = 'plaintext'"
|
||||
>{{ t('mail', 'Enable formatting') }}</ActionCheckbox
|
||||
>
|
||||
@uncheck="editorMode = 'plaintext'">
|
||||
{{ t('mail', 'Enable formatting') }}
|
||||
</ActionCheckbox>
|
||||
<ActionCheckbox
|
||||
v-if="mailvelope.available"
|
||||
:checked="encrypt"
|
||||
@check="encrypt = true"
|
||||
@uncheck="encrypt = false"
|
||||
>{{ t('mail', 'Encrypt message with Mailvelope') }}</ActionCheckbox
|
||||
>
|
||||
<ActionLink v-else href="https://www.mailvelope.com/" target="_blank" icon="icon-password">{{
|
||||
t('mail', 'Looking for a way to encrypt your emails? Install the Mailvelope browser extension!')
|
||||
}}</ActionLink>
|
||||
@uncheck="encrypt = false">
|
||||
{{ t('mail', 'Encrypt message with Mailvelope') }}
|
||||
</ActionCheckbox>
|
||||
<ActionLink v-else
|
||||
href="https://www.mailvelope.com/"
|
||||
target="_blank"
|
||||
icon="icon-password">
|
||||
{{
|
||||
t('mail', 'Looking for a way to encrypt your emails? Install the Mailvelope browser extension!')
|
||||
}}
|
||||
</ActionLink>
|
||||
</Actions>
|
||||
<div>
|
||||
<input
|
||||
|
@ -188,8 +194,7 @@
|
|||
type="submit"
|
||||
:value="submitButtonTitle"
|
||||
:disabled="!canSend"
|
||||
@click="onSend"
|
||||
/>
|
||||
@click="onSend">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -198,9 +203,15 @@
|
|||
<Loading v-else-if="state === STATES.SENDING" :hint="t('mail', 'Sending …')" />
|
||||
<div v-else-if="state === STATES.ERROR" class="emptycontent">
|
||||
<h2>{{ t('mail', 'Error sending your message') }}</h2>
|
||||
<p v-if="errorText">{{ errorText }}</p>
|
||||
<button class="button" @click="state = STATES.EDITING">{{ t('mail', 'Go back') }}</button>
|
||||
<button class="button primary" @click="onSend">{{ t('mail', 'Retry') }}</button>
|
||||
<p v-if="errorText">
|
||||
{{ errorText }}
|
||||
</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 v-else class="emptycontent">
|
||||
<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 ActionLink from '@nextcloud/vue/dist/Components/ActionLink'
|
||||
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 ComposerAttachments from './ComposerAttachments'
|
||||
import {findRecipient} from '../service/AutocompleteService'
|
||||
import {detect, html, plain, toHtml, toPlain} from '../util/text'
|
||||
import { findRecipient } from '../service/AutocompleteService'
|
||||
import { detect, html, plain, toHtml, toPlain } from '../util/text'
|
||||
import Loading from './Loading'
|
||||
import logger from '../logger'
|
||||
import TextEditor from './TextEditor'
|
||||
import {buildReplyBody} from '../ReplyBuilder'
|
||||
import { buildReplyBody } from '../ReplyBuilder'
|
||||
import MailvelopeEditor from './MailvelopeEditor'
|
||||
import {getMailvelope} from '../crypto/mailvelope'
|
||||
import {isPgpgMessage} from '../crypto/pgp'
|
||||
import { getMailvelope } from '../crypto/mailvelope'
|
||||
import { isPgpgMessage } from '../crypto/pgp'
|
||||
|
||||
const debouncedSearch = debouncePromise(findRecipient, 500)
|
||||
|
||||
|
@ -367,7 +378,7 @@ export default {
|
|||
selectableRecipients() {
|
||||
return this.newRecipients
|
||||
.concat(this.autocompleteRecipients)
|
||||
.map((recipient) => ({...recipient, label: recipient.label || recipient.email}))
|
||||
.map((recipient) => ({ ...recipient, label: recipient.label || recipient.email }))
|
||||
},
|
||||
isForward() {
|
||||
return this.forwardFrom !== undefined
|
||||
|
@ -445,7 +456,7 @@ export default {
|
|||
|
||||
const recipients = this.allRecipients.map((r) => r.email)
|
||||
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)
|
||||
},
|
||||
initBody() {
|
||||
|
@ -508,12 +519,12 @@ export default {
|
|||
.then((uid) => {
|
||||
const draftData = data(uid)
|
||||
if (
|
||||
!uid &&
|
||||
!draftData.subject &&
|
||||
!draftData.body &&
|
||||
!draftData.cc &&
|
||||
!draftData.bcc &&
|
||||
!draftData.to
|
||||
!uid
|
||||
&& !draftData.subject
|
||||
&& !draftData.body
|
||||
&& !draftData.cc
|
||||
&& !draftData.bcc
|
||||
&& !draftData.to
|
||||
) {
|
||||
// this might happen after a call to reset()
|
||||
// where the text input gets reset as well
|
||||
|
@ -554,7 +565,7 @@ export default {
|
|||
onAttachmentsUploading(uploaded) {
|
||||
this.attachmentsPromise = this.attachmentsPromise
|
||||
.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'))
|
||||
},
|
||||
async onMailvelopeLoaded(mailvelope) {
|
||||
|
@ -601,7 +612,7 @@ export default {
|
|||
.then(() => logger.info('message sent'))
|
||||
.then(() => (this.state = STATES.FINISHED))
|
||||
.catch((error) => {
|
||||
logger.error('could not send message', {error})
|
||||
logger.error('could not send message', { error })
|
||||
if (error && error.toString) {
|
||||
this.errorText = error.toString()
|
||||
}
|
||||
|
@ -632,6 +643,7 @@ export default {
|
|||
},
|
||||
/**
|
||||
* Format aliases for the Multiselect
|
||||
* @param {Object} alias the alias to format
|
||||
* @returns {string}
|
||||
*/
|
||||
formatAliases(alias) {
|
||||
|
|
|
@ -27,29 +27,33 @@
|
|||
<div class="new-message-attachment-name">
|
||||
{{ attachment.displayName }}
|
||||
</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 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>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
<input ref="localAttachments" type="file" multiple style="display: none;" @change="onLocalAttachmentSelected" />
|
||||
<input ref="localAttachments"
|
||||
type="file"
|
||||
multiple
|
||||
style="display: none;"
|
||||
@change="onLocalAttachmentSelected">
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import map from 'lodash/fp/map'
|
||||
import trimStart from 'lodash/fp/trimCharsStart'
|
||||
import {getRequestToken} from '@nextcloud/auth'
|
||||
import {translate as t} from '@nextcloud/l10n'
|
||||
import {getFilePickerBuilder} from '@nextcloud/dialogs'
|
||||
import { getRequestToken } from '@nextcloud/auth'
|
||||
import { translate as t } from '@nextcloud/l10n'
|
||||
import { getFilePickerBuilder } from '@nextcloud/dialogs'
|
||||
import Vue from 'vue'
|
||||
|
||||
import Logger from '../logger'
|
||||
import {uploadLocalAttachment} from '../service/AttachmentService'
|
||||
import {shareFile} from '../service/FileSharingService'
|
||||
import { uploadLocalAttachment } from '../service/AttachmentService'
|
||||
import { shareFile } from '../service/FileSharingService'
|
||||
|
||||
export default {
|
||||
name: 'ComposerAttachments',
|
||||
|
@ -73,7 +77,7 @@ export default {
|
|||
uploadProgress() {
|
||||
let uploaded = 0
|
||||
let total = 0
|
||||
for (let id in this.uploads) {
|
||||
for (const id in this.uploads) {
|
||||
uploaded += this.uploads[id].uploaded
|
||||
total += this.uploads[id].total
|
||||
}
|
||||
|
@ -115,14 +119,14 @@ export default {
|
|||
uploaded: 0,
|
||||
})
|
||||
|
||||
return uploadLocalAttachment(file, progress(file.name)).then(({file, id}) => {
|
||||
return uploadLocalAttachment(file, progress(file.name)).then(({ file, id }) => {
|
||||
Logger.info('uploaded')
|
||||
return this.emitNewAttachment(this.fileNameToAttachment(file.name, id))
|
||||
})
|
||||
})(e.target.files)
|
||||
|
||||
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))
|
||||
|
||||
this.$emit('upload', done)
|
||||
|
@ -135,19 +139,19 @@ export default {
|
|||
return picker
|
||||
.pick(t('mail', 'Choose a file to add as attachment'))
|
||||
.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() {
|
||||
const picker = getFilePickerBuilder(t('mail', 'Choose a file to share as a link')).build()
|
||||
|
||||
return picker
|
||||
.pick(t('mail', 'Choose a file to share as a link'))
|
||||
.then(async (path) => {
|
||||
.then(async(path) => {
|
||||
const url = await shareFile(path, getRequestToken())
|
||||
|
||||
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) {
|
||||
this.$emit(
|
||||
|
|
|
@ -26,11 +26,19 @@
|
|||
{{ t('mail', 'Preferred writing mode for new messages and replies.') }}
|
||||
</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">
|
||||
{{ t('mail', 'Plain text') }}
|
||||
</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">
|
||||
{{ t('mail', 'Rich text') }}
|
||||
</label>
|
||||
|
@ -67,7 +75,7 @@ export default {
|
|||
Logger.info('editor mode updated')
|
||||
})
|
||||
.catch((error) => {
|
||||
Logger.error('could not update editor mode', {error})
|
||||
Logger.error('could not update editor mode', { error })
|
||||
this.editorMode = oldVal
|
||||
throw error
|
||||
})
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
<template>
|
||||
<div id="emptycontent">
|
||||
<div class="icon-mail"></div>
|
||||
<div class="icon-mail" />
|
||||
<h2>{{ t('mail', 'No messages in this folder') }}</h2>
|
||||
</div>
|
||||
</template>
|
||||
|
|
|
@ -3,31 +3,30 @@
|
|||
<div
|
||||
v-if="folder.isUnified"
|
||||
class="mail-message-account-color"
|
||||
:style="{'background-color': accountColor}"
|
||||
></div>
|
||||
:style="{'background-color': accountColor}" />
|
||||
<div
|
||||
v-if="data.flags.flagged"
|
||||
class="app-content-list-item-star icon-starred"
|
||||
:data-starred="data.flags.flagged ? 'true' : 'false'"
|
||||
@click.prevent="onToggleFlagged"
|
||||
></div>
|
||||
@click.prevent="onToggleFlagged" />
|
||||
<div
|
||||
v-if="data.flags.important"
|
||||
class="app-content-list-item-star icon-important"
|
||||
:data-starred="data.flags.important ? 'true' : 'false'"
|
||||
@click.prevent="onToggleImportant"
|
||||
v-html="importantSvg"
|
||||
></div>
|
||||
v-html="importantSvg" />
|
||||
<div
|
||||
v-if="data.flags.junk"
|
||||
class="app-content-list-item-star icon-junk"
|
||||
:data-starred="data.flags.junk ? 'true' : 'false'"
|
||||
@click.prevent="onToggleJunk"
|
||||
></div>
|
||||
@click.prevent="onToggleJunk" />
|
||||
<div class="app-content-list-item-icon">
|
||||
<Avatar :display-name="addresses" :email="avatarEmail" />
|
||||
<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" />
|
||||
</p>
|
||||
</div>
|
||||
|
@ -46,22 +45,34 @@
|
|||
<Moment :timestamp="data.dateInt" />
|
||||
</div>
|
||||
<Actions class="app-content-list-item-menu" menu-align="right">
|
||||
<ActionButton icon="icon-starred" @click.prevent="onToggleFlagged">{{
|
||||
data.flags.flagged ? t('mail', 'Unfavorite') : t('mail', 'Favorite')
|
||||
}}</ActionButton>
|
||||
<ActionButton icon="icon-important" @click.prevent="onToggleImportant">{{
|
||||
data.flags.important ? t('mail', 'Mark unimportant') : t('mail', 'Mark important')
|
||||
}}</ActionButton>
|
||||
<ActionButton icon="icon-mail" @click.prevent="onToggleSeen">{{
|
||||
data.flags.seen ? t('mail', 'Mark unread') : t('mail', 'Mark read')
|
||||
}}</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>
|
||||
<ActionButton icon="icon-starred" @click.prevent="onToggleFlagged">
|
||||
{{
|
||||
data.flags.flagged ? t('mail', 'Unfavorite') : t('mail', 'Favorite')
|
||||
}}
|
||||
</ActionButton>
|
||||
<ActionButton icon="icon-important" @click.prevent="onToggleImportant">
|
||||
{{
|
||||
data.flags.important ? t('mail', 'Mark unimportant') : t('mail', 'Mark important')
|
||||
}}
|
||||
</ActionButton>
|
||||
<ActionButton icon="icon-mail" @click.prevent="onToggleSeen">
|
||||
{{
|
||||
data.flags.seen ? t('mail', 'Mark unread') : t('mail', 'Mark read')
|
||||
}}
|
||||
</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>
|
||||
</router-link>
|
||||
</template>
|
||||
|
@ -73,7 +84,7 @@ import Moment from './Moment'
|
|||
import importantSvg from '../../img/important.svg'
|
||||
|
||||
import Avatar from './Avatar'
|
||||
import {calculateAccountColor} from '../util/AccountColor'
|
||||
import { calculateAccountColor } from '../util/AccountColor'
|
||||
|
||||
export default {
|
||||
name: 'Envelope',
|
||||
|
@ -145,7 +156,7 @@ export default {
|
|||
addresses() {
|
||||
// Show recipients' label/address in a sent folder
|
||||
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 recipients.length > 0 ? recipients.join(', ') : t('mail', 'Blind copy recipients only')
|
||||
|
@ -156,7 +167,7 @@ export default {
|
|||
avatarEmail() {
|
||||
// Show first recipients' avatar in a sent folder (or undefined when sent to Bcc only)
|
||||
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 recipients.length > 0 ? recipients[0] : undefined
|
||||
|
|
|
@ -10,11 +10,13 @@
|
|||
}}</span>
|
||||
</div>
|
||||
<Actions class="app-content-list-item-menu" menu-align="right">
|
||||
<ActionButton icon="icon-starred" @click.prevent="favoriteOrUnfavoriteAll">{{
|
||||
areAllSelectedFavorite
|
||||
? t('mail', 'Unfavorite ' + selection.length)
|
||||
: t('mail', 'Favorite ' + selection.length)
|
||||
}}</ActionButton>
|
||||
<ActionButton icon="icon-starred" @click.prevent="favoriteOrUnfavoriteAll">
|
||||
{{
|
||||
areAllSelectedFavorite
|
||||
? t('mail', 'Unfavorite ' + selection.length)
|
||||
: t('mail', 'Favorite ' + selection.length)
|
||||
}}
|
||||
</ActionButton>
|
||||
<ActionButton icon="icon-close" @click.prevent="unselectAll">
|
||||
{{ t('mail', 'Unselect ' + selection.length) }}
|
||||
</ActionButton>
|
||||
|
@ -25,7 +27,10 @@
|
|||
</div>
|
||||
</transition>
|
||||
<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
|
||||
v-for="env in envelopes"
|
||||
:key="env.uuid"
|
||||
|
@ -34,14 +39,12 @@
|
|||
:selected="isEnvelopeSelected(envelopes.indexOf(env))"
|
||||
:select-mode="selectMode"
|
||||
@delete="$emit('delete', env.uuid)"
|
||||
@update:selected="onEnvelopeSelectToggle(env, ...$event)"
|
||||
/>
|
||||
@update:selected="onEnvelopeSelectToggle(env, ...$event)" />
|
||||
<div
|
||||
v-if="loadMoreButton && !loadingMore"
|
||||
:key="'list-collapse-' + searchQuery"
|
||||
class="load-more"
|
||||
@click="$emit('loadMore')"
|
||||
>
|
||||
@click="$emit('loadMore')">
|
||||
{{ t('mail', 'Load more') }}
|
||||
</div>
|
||||
<div id="load-more-mail-messages" key="loadingMore" :class="{'icon-loading-small': loadingMore}" />
|
||||
|
@ -116,28 +119,28 @@ export default {
|
|||
},
|
||||
methods: {
|
||||
isEnvelopeSelected(idx) {
|
||||
if (this.selection.length == 0) {
|
||||
if (this.selection.length === 0) {
|
||||
return false
|
||||
}
|
||||
|
||||
return this.selection.includes(idx)
|
||||
},
|
||||
markSelectedSeenOrUnseen() {
|
||||
let seenFlag = this.areAllSelectedRead
|
||||
const seenFlag = this.areAllSelectedRead
|
||||
this.selection.forEach((envelopeId) => {
|
||||
this.$store.dispatch('markEnvelopeSeenOrUnseen', {
|
||||
envelope: this.envelopes[envelopeId],
|
||||
seenFlag: seenFlag,
|
||||
seenFlag,
|
||||
})
|
||||
})
|
||||
this.unselectAll()
|
||||
},
|
||||
favoriteOrUnfavoriteAll() {
|
||||
let favFlag = !this.areAllSelectedFavorite
|
||||
const favFlag = !this.areAllSelectedFavorite
|
||||
this.selection.forEach((envelopeId) => {
|
||||
this.$store.dispatch('markEnvelopeFavoriteOrUnfavorite', {
|
||||
envelope: this.envelopes[envelopeId],
|
||||
favFlag: favFlag,
|
||||
favFlag,
|
||||
})
|
||||
})
|
||||
this.unselectAll()
|
||||
|
@ -145,7 +148,7 @@ export default {
|
|||
deleteAllSelected() {
|
||||
this.selection.forEach((envelopeId) => {
|
||||
// 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
|
||||
if (envelopeId === 0) {
|
||||
next = this.envelopes[envelopeId + 1]
|
||||
|
@ -179,7 +182,6 @@ export default {
|
|||
this.selection.splice(this.selection.indexOf(idx), 1)
|
||||
}
|
||||
|
||||
return
|
||||
},
|
||||
unselectAll() {
|
||||
this.envelopes.forEach((env) => {
|
||||
|
|
|
@ -24,13 +24,16 @@
|
|||
<h2>{{ error }}</h2>
|
||||
<p>{{ message }}</p>
|
||||
<p v-if="data && data.debug">
|
||||
<a class="button" :href="reportUrl" target="_blank" rel="noopener">{{ t('mail', 'Report this bug') }}</a>
|
||||
<a class="button"
|
||||
:href="reportUrl"
|
||||
target="_blank"
|
||||
rel="noopener">{{ t('mail', 'Report this bug') }}</a>
|
||||
</p>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import {getReportUrl} from '../util/CrashReport'
|
||||
import { getReportUrl } from '../util/CrashReport'
|
||||
|
||||
export default {
|
||||
name: 'Error',
|
||||
|
|
|
@ -6,22 +6,19 @@
|
|||
:key="idx"
|
||||
:data="entry"
|
||||
:calendars="calendars"
|
||||
:message-id="messageId"
|
||||
/>
|
||||
:message-id="messageId" />
|
||||
<FlightReservation
|
||||
v-else-if="entry['@type'] === 'FlightReservation'"
|
||||
:key="idx"
|
||||
:data="entry"
|
||||
:calendars="calendars"
|
||||
:message-id="messageId"
|
||||
/>
|
||||
:message-id="messageId" />
|
||||
<TrainReservation
|
||||
v-else-if="entry['@type'] === 'TrainReservation'"
|
||||
:key="idx"
|
||||
:data="entry"
|
||||
:calendars="calendars"
|
||||
:message-id="messageId"
|
||||
/>
|
||||
:message-id="messageId" />
|
||||
<span v-else :key="idx">{{
|
||||
t('mail', 'Itinerary for {type} is not supported yet', {type: entry['@type']})
|
||||
}}</span>
|
||||
|
@ -32,7 +29,7 @@
|
|||
<script>
|
||||
import once from 'lodash/fp/once'
|
||||
|
||||
import {getUserCalendars} from '../service/DAVService'
|
||||
import { getUserCalendars } from '../service/DAVService'
|
||||
import logger from '../logger'
|
||||
import EventReservation from './itinerary/EventReservation'
|
||||
import FlightReservation from './itinerary/FlightReservation'
|
||||
|
@ -65,7 +62,7 @@ export default {
|
|||
mounted() {
|
||||
getUserCalendarsOnce()
|
||||
.then((calendars) => (this.calendars = calendars))
|
||||
.catch((error) => logger.error('Could not load calendars', {error}))
|
||||
.catch((error) => logger.error('Could not load calendars', { error }))
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
|
|
@ -1,12 +1,12 @@
|
|||
<template>
|
||||
<div v-if="hint" class="emptycontent">
|
||||
<a class="icon-loading"></a>
|
||||
<a class="icon-loading" />
|
||||
<h2>{{ hint }}</h2>
|
||||
<transition name="fade">
|
||||
<em v-if="slowHint && slow">{{ slowHint }}</em>
|
||||
</transition>
|
||||
</div>
|
||||
<div v-else class="container icon-loading"></div>
|
||||
<div v-else class="container icon-loading" />
|
||||
</template>
|
||||
|
||||
<script>
|
||||
|
|
|
@ -25,9 +25,8 @@
|
|||
<Loading
|
||||
v-else-if="loadingCacheInitialization"
|
||||
:hint="t('mail', 'Loading messages')"
|
||||
: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>
|
||||
:slow-hint="t('mail', 'Indexing your messages. This can take a bit longer for larger mailboxes.')" />
|
||||
<EmptyMailboxSection v-else-if="isPriorityInbox && !hasMessages" key="empty" />
|
||||
<EmptyMailbox v-else-if="!hasMessages" key="empty" />
|
||||
<EnvelopeList
|
||||
v-else
|
||||
|
@ -39,24 +38,22 @@
|
|||
:loading-more="loadingMore"
|
||||
:load-more-button="showLoadMore"
|
||||
@delete="onDelete"
|
||||
@loadMore="loadMore"
|
||||
/>
|
||||
@loadMore="loadMore" />
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import EmptyMailbox from './EmptyMailbox'
|
||||
import EnvelopeList from './EnvelopeList'
|
||||
import Error from './Error'
|
||||
import {findIndex, propEq} from 'ramda'
|
||||
import { findIndex, propEq } from 'ramda'
|
||||
import isMobile from '@nextcloud/vue/dist/Mixins/isMobile'
|
||||
import Loading from './Loading'
|
||||
import logger from '../logger'
|
||||
import MailboxLockedError from '../errors/MailboxLockedError'
|
||||
import MailboxNotCachedError from '../errors/MailboxNotCachedError'
|
||||
import {matchError} from '../errors/match'
|
||||
import {wait} from '../util/wait'
|
||||
import { matchError } from '../errors/match'
|
||||
import { wait } from '../util/wait'
|
||||
import EmptyMailboxSection from './EmptyMailboxSection'
|
||||
import {normalizedEnvelopeListId} from '../store/normalization'
|
||||
|
||||
export default {
|
||||
name: 'Mailbox',
|
||||
|
@ -191,13 +188,13 @@ export default {
|
|||
limit: this.initialPageSize,
|
||||
})
|
||||
|
||||
logger.debug(envelopes.length + ' envelopes fetched', {envelopes})
|
||||
logger.debug(envelopes.length + ' envelopes fetched', { envelopes })
|
||||
|
||||
this.loadingEnvelopes = false
|
||||
|
||||
if (this.openFirst && !this.isMobile && this.$route.name !== 'message' && envelopes.length > 0) {
|
||||
// Show first message
|
||||
let first = envelopes[0]
|
||||
const first = envelopes[0]
|
||||
|
||||
// Keep the selected account-folder combination, but navigate to the message
|
||||
// (it's not a bug that we don't use first.accountId and first.folderId here)
|
||||
|
@ -213,26 +210,26 @@ export default {
|
|||
}
|
||||
} catch (error) {
|
||||
await matchError(error, {
|
||||
[MailboxLockedError.getName()]: async (error) => {
|
||||
logger.info('Mailbox is locked', {error})
|
||||
[MailboxLockedError.getName()]: async(error) => {
|
||||
logger.info('Mailbox is locked', { error })
|
||||
|
||||
await wait(15 * 1000)
|
||||
// Keep trying
|
||||
await this.loadEnvelopes()
|
||||
},
|
||||
[MailboxNotCachedError.getName()]: async (error) => {
|
||||
logger.info('Mailbox not cached. Triggering initialization', {error})
|
||||
[MailboxNotCachedError.getName()]: async(error) => {
|
||||
logger.info('Mailbox not cached. Triggering initialization', { error })
|
||||
this.loadingEnvelopes = false
|
||||
|
||||
try {
|
||||
await this.initializeCache()
|
||||
} catch (error) {
|
||||
logger.error('Could not initialize cache', {error})
|
||||
logger.error('Could not initialize cache', { error })
|
||||
this.error = error
|
||||
}
|
||||
},
|
||||
default: (error) => {
|
||||
logger.error('Could not fetch envelopes', {error})
|
||||
logger.error('Could not fetch envelopes', { error })
|
||||
this.loadingEnvelopes = false
|
||||
this.error = error
|
||||
},
|
||||
|
@ -261,7 +258,7 @@ export default {
|
|||
this.endReached = true
|
||||
}
|
||||
} catch (error) {
|
||||
logger.error('could not fetch next envelope page', {error})
|
||||
logger.error('could not fetch next envelope page', { error })
|
||||
} finally {
|
||||
this.loadingMore = false
|
||||
}
|
||||
|
@ -283,82 +280,82 @@ export default {
|
|||
|
||||
const env = current[0]
|
||||
const idx = envelopes.indexOf(env)
|
||||
let next
|
||||
|
||||
switch (e.srcKey) {
|
||||
case 'next':
|
||||
case 'prev':
|
||||
let next
|
||||
if (e.srcKey === 'next') {
|
||||
next = envelopes[idx + 1]
|
||||
} else {
|
||||
next = envelopes[idx - 1]
|
||||
}
|
||||
case 'next':
|
||||
case 'prev':
|
||||
if (e.srcKey === 'next') {
|
||||
next = envelopes[idx + 1]
|
||||
} else {
|
||||
next = envelopes[idx - 1]
|
||||
}
|
||||
|
||||
if (!next) {
|
||||
logger.debug('ignoring shortcut: head or tail of envelope list reached', {
|
||||
envelopes,
|
||||
idx,
|
||||
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,
|
||||
},
|
||||
if (!next) {
|
||||
logger.debug('ignoring shortcut: head or tail of envelope list reached', {
|
||||
envelopes,
|
||||
idx,
|
||||
srcKey: e.srcKey,
|
||||
})
|
||||
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,
|
||||
error,
|
||||
})
|
||||
)
|
||||
return
|
||||
}
|
||||
|
||||
break
|
||||
case 'flag':
|
||||
logger.debug('flagging envelope via shortkey', {env})
|
||||
this.$store.dispatch('toggleEnvelopeFlagged', env).catch((error) =>
|
||||
logger.error('could not flag envelope via shortkey', {
|
||||
// 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
|
||||
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
|
||||
case 'refresh':
|
||||
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.')
|
||||
break
|
||||
case 'flag':
|
||||
logger.debug('flagging envelope via shortkey', { env })
|
||||
this.$store.dispatch('toggleEnvelopeFlagged', env).catch((error) =>
|
||||
logger.error('could not flag envelope via shortkey', {
|
||||
env,
|
||||
error,
|
||||
})
|
||||
)
|
||||
break
|
||||
case 'refresh':
|
||||
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() {
|
||||
|
@ -374,10 +371,10 @@ export default {
|
|||
} catch (error) {
|
||||
matchError(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) {
|
||||
logger.error('Could not sync envelopes: ' + error.message, {error})
|
||||
logger.error('Could not sync envelopes: ' + error.message, { error })
|
||||
},
|
||||
})
|
||||
} finally {
|
||||
|
@ -436,7 +433,7 @@ export default {
|
|||
|
||||
logger.debug("Mailbox sync'ed in background")
|
||||
} catch (error) {
|
||||
logger.error('Background sync failed: ' + error.message, {error})
|
||||
logger.error('Background sync failed: ' + error.message, { error })
|
||||
}
|
||||
},
|
||||
stopInterval() {
|
||||
|
|
|
@ -9,20 +9,18 @@
|
|||
:show-details="showMessage"
|
||||
:infinite-scroll-disabled="false"
|
||||
:infinite-scroll-distance="10"
|
||||
@shortkey.native="onShortcut"
|
||||
>
|
||||
@shortkey.native="onShortcut">
|
||||
<Mailbox
|
||||
v-if="!folder.isPriorityInbox"
|
||||
:account="account"
|
||||
:folder="folder"
|
||||
:search-query="query"
|
||||
:bus="bus"
|
||||
/>
|
||||
:bus="bus" />
|
||||
<template v-else>
|
||||
<li class="app-content-list-item">
|
||||
<SectionTitle class="important" :name="t('mail', 'Important')" />
|
||||
<Popover trigger="hover focus">
|
||||
<button slot="trigger" class="button icon-info"></button>
|
||||
<button slot="trigger" class="button icon-info" />
|
||||
{{
|
||||
t(
|
||||
'mail',
|
||||
|
@ -40,8 +38,7 @@
|
|||
:is-priority-inbox="true"
|
||||
:initial-page-size="5"
|
||||
:collapsible="true"
|
||||
:bus="bus"
|
||||
/>
|
||||
:bus="bus" />
|
||||
<SectionTitle class="app-content-list-item starred" :name="t('mail', 'Favorites')" />
|
||||
<Mailbox
|
||||
class="namestarred"
|
||||
|
@ -51,8 +48,7 @@
|
|||
:paginate="'manual'"
|
||||
:is-priority-inbox="true"
|
||||
:initial-page-size="5"
|
||||
:bus="bus"
|
||||
/>
|
||||
:bus="bus" />
|
||||
<SectionTitle class="app-content-list-item other" :name="t('mail', 'Other')" />
|
||||
<Mailbox
|
||||
class="nameother"
|
||||
|
@ -61,8 +57,7 @@
|
|||
:open-first="false"
|
||||
:search-query="appendToSearch('not:starred not:important')"
|
||||
:is-priority-inbox="true"
|
||||
:bus="bus"
|
||||
/>
|
||||
:bus="bus" />
|
||||
</template>
|
||||
</AppContentList>
|
||||
<NewMessageDetail v-if="newMessage" />
|
||||
|
@ -87,8 +82,8 @@ import Mailbox from './Mailbox'
|
|||
import Message from './Message'
|
||||
import NewMessageDetail from './NewMessageDetail'
|
||||
import NoMessageSelected from './NoMessageSelected'
|
||||
import {normalizedEnvelopeListId} from '../store/normalization'
|
||||
import {UNIFIED_ACCOUNT_ID, UNIFIED_INBOX_ID} from '../store/constants'
|
||||
import { normalizedEnvelopeListId } from '../store/normalization'
|
||||
import { UNIFIED_ACCOUNT_ID, UNIFIED_INBOX_ID } from '../store/constants'
|
||||
|
||||
export default {
|
||||
name: 'MailboxMessage',
|
||||
|
@ -164,15 +159,16 @@ export default {
|
|||
},
|
||||
newMessage() {
|
||||
return (
|
||||
this.$route.params.messageUuid === 'new' ||
|
||||
this.$route.params.messageUuid === 'reply' ||
|
||||
this.$route.params.messageUuid === 'replyAll'
|
||||
this.$route.params.messageUuid === 'new'
|
||||
|| this.$route.params.messageUuid === 'reply'
|
||||
|| this.$route.params.messageUuid === 'replyAll'
|
||||
)
|
||||
},
|
||||
},
|
||||
created() {
|
||||
this.alive = true
|
||||
|
||||
// eslint-disable-next-line no-new
|
||||
new OCA.Search(this.searchProxy, this.clearSearchProxy)
|
||||
},
|
||||
beforeDestroy() {
|
||||
|
@ -193,7 +189,7 @@ export default {
|
|||
this.bus.$emit('delete', envelopeUid)
|
||||
},
|
||||
onScroll(event) {
|
||||
logger.debug('scroll', {event})
|
||||
logger.debug('scroll', { event })
|
||||
|
||||
this.bus.$emit('loadMore')
|
||||
},
|
||||
|
|
|
@ -20,12 +20,12 @@
|
|||
-->
|
||||
|
||||
<template>
|
||||
<div id="mailvelope-composer"></div>
|
||||
<div id="mailvelope-composer" />
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import logger from '../logger'
|
||||
import {isPgpgMessage} from '../crypto/pgp'
|
||||
import { isPgpgMessage } from '../crypto/pgp'
|
||||
|
||||
export default {
|
||||
name: 'MailvelopeEditor',
|
||||
|
@ -64,9 +64,9 @@ export default {
|
|||
methods: {
|
||||
async pull() {
|
||||
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)
|
||||
logger.info('message encryted', {armored})
|
||||
logger.info('message encryted', { armored })
|
||||
|
||||
this.$emit('input', armored)
|
||||
},
|
||||
|
|
|
@ -5,13 +5,13 @@
|
|||
v-else-if="!message"
|
||||
:error="error && error.message ? error.message : t('mail', 'Not found')"
|
||||
:message="errorMessage"
|
||||
:data="error"
|
||||
>
|
||||
</Error>
|
||||
:data="error" />
|
||||
<template v-else>
|
||||
<div id="mail-message-header">
|
||||
<div id="mail-message-header-fields">
|
||||
<h2 :title="message.subject">{{ message.subject }}</h2>
|
||||
<h2 :title="message.subject">
|
||||
{{ message.subject }}
|
||||
</h2>
|
||||
<p class="transparency">
|
||||
<AddressList :entries="message.from" />
|
||||
{{ t('mail', 'to') }}
|
||||
|
@ -28,8 +28,7 @@
|
|||
? 'icon-reply-all-white button primary'
|
||||
: 'icon-reply-white button primary'
|
||||
"
|
||||
@click="hasMultipleRecipients ? replyAll() : replyMessage()"
|
||||
>
|
||||
@click="hasMultipleRecipients ? replyAll() : replyMessage()">
|
||||
<span class="action-label">{{ t('mail', 'Reply') }}</span>
|
||||
</div>
|
||||
<Actions id="mail-message-actions-menu" class="app-content-list-item-menu" menu-align="right">
|
||||
|
@ -39,12 +38,16 @@
|
|||
<ActionButton icon="icon-forward" @click="forwardMessage">
|
||||
{{ t('mail', 'Forward') }}
|
||||
</ActionButton>
|
||||
<ActionButton icon="icon-starred" @click.prevent="onToggleFlagged">{{
|
||||
envelope.flags.flagged ? t('mail', 'Unfavorite') : t('mail', 'Favorite')
|
||||
}}</ActionButton>
|
||||
<ActionButton icon="icon-important" @click.prevent="onToggleImportant">{{
|
||||
envelope.flags.important ? t('mail', 'Mark unimportant') : t('mail', 'Mark important')
|
||||
}}</ActionButton>
|
||||
<ActionButton icon="icon-starred" @click.prevent="onToggleFlagged">
|
||||
{{
|
||||
envelope.flags.flagged ? t('mail', 'Unfavorite') : t('mail', 'Favorite')
|
||||
}}
|
||||
</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">
|
||||
{{ envelope.flags.seen ? t('mail', 'Mark unread') : t('mail', 'Mark read') }}
|
||||
</ActionButton>
|
||||
|
@ -55,8 +58,7 @@
|
|||
<ActionButton
|
||||
:icon="sourceLoading ? 'icon-loading-small' : 'icon-details'"
|
||||
:disabled="sourceLoading"
|
||||
@click="onShowSource"
|
||||
>
|
||||
@click="onShowSource">
|
||||
{{ t('mail', 'View source') }}
|
||||
</ActionButton>
|
||||
<ActionButton icon="icon-delete" @click.prevent="onDelete">
|
||||
|
@ -80,11 +82,13 @@
|
|||
<MessagePlainTextBody v-else :body="message.body" :signature="message.signature" />
|
||||
<Popover v-if="message.attachments[0]" class="attachment-popover">
|
||||
<Actions slot="trigger">
|
||||
<ActionButton icon="icon-public icon-attachment">Attachments</ActionButton>
|
||||
<ActionButton icon="icon-public icon-attachment">
|
||||
Attachments
|
||||
</ActionButton>
|
||||
</Actions>
|
||||
<MessageAttachments :attachments="message.attachments" />
|
||||
</Popover>
|
||||
<div id="reply-composer"></div>
|
||||
<div id="reply-composer" />
|
||||
</div>
|
||||
</template>
|
||||
</AppContentDetails>
|
||||
|
@ -97,14 +101,14 @@ import Popover from '@nextcloud/vue/dist/Components/Popover'
|
|||
import AppContentDetails from '@nextcloud/vue/dist/Components/AppContentDetails'
|
||||
import axios from '@nextcloud/axios'
|
||||
import Modal from '@nextcloud/vue/dist/Components/Modal'
|
||||
import {generateUrl} from '@nextcloud/router'
|
||||
import { generateUrl } from '@nextcloud/router'
|
||||
|
||||
import AddressList from './AddressList'
|
||||
import {buildRecipients as buildReplyRecipients, buildReplySubject} from '../ReplyBuilder'
|
||||
import { buildRecipients as buildReplyRecipients, buildReplySubject } from '../ReplyBuilder'
|
||||
import Error from './Error'
|
||||
import {getRandomMessageErrorMessage} from '../util/ErrorMessageFactory'
|
||||
import {html, plain} from '../util/text'
|
||||
import {isPgpgMessage} from '../crypto/pgp'
|
||||
import { getRandomMessageErrorMessage } from '../util/ErrorMessageFactory'
|
||||
import { html, plain } from '../util/text'
|
||||
import { isPgpgMessage } from '../crypto/pgp'
|
||||
import Itinerary from './Itinerary'
|
||||
import MessageEncryptedBody from './MessageEncryptedBody'
|
||||
import MessageHTMLBody from './MessageHTMLBody'
|
||||
|
@ -172,16 +176,16 @@ export default {
|
|||
watch: {
|
||||
$route(to, from) {
|
||||
if (
|
||||
from.name === to.name &&
|
||||
Number.parseInt(from.params.accountId, 10) === Number.parseInt(to.params.accountId, 10) &&
|
||||
from.params.folderId === to.params.folderId &&
|
||||
from.params.messageUuid === to.params.messageUuid &&
|
||||
from.params.filter === to.params.filter
|
||||
from.name === to.name
|
||||
&& Number.parseInt(from.params.accountId, 10) === Number.parseInt(to.params.accountId, 10)
|
||||
&& from.params.folderId === to.params.folderId
|
||||
&& from.params.messageUuid === to.params.messageUuid
|
||||
&& from.params.filter === to.params.filter
|
||||
) {
|
||||
logger.debug('navigated but the message is still the same')
|
||||
return
|
||||
}
|
||||
logger.debug('navigated to another message', {to, from})
|
||||
logger.debug('navigated to another message', { to, from })
|
||||
this.fetchMessage()
|
||||
},
|
||||
},
|
||||
|
@ -204,7 +208,7 @@ export default {
|
|||
this.$store.dispatch('fetchEnvelope', 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
|
||||
// for a few seconds
|
||||
if (message && message.uuid !== this.$route.params.messageUuid) {
|
||||
|
@ -216,7 +220,7 @@ export default {
|
|||
this.message = message
|
||||
|
||||
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.loading = false
|
||||
return
|
||||
|
@ -236,7 +240,7 @@ export default {
|
|||
return this.$store.dispatch('toggleEnvelopeSeen', envelope)
|
||||
}
|
||||
} catch (error) {
|
||||
logger.error('could not load message ', {messageUuid, error})
|
||||
logger.error('could not load message ', { messageUuid, error })
|
||||
if (error.isError) {
|
||||
this.errorMessage = t('mail', 'Could not load your message')
|
||||
this.error = error
|
||||
|
|
|
@ -21,10 +21,10 @@
|
|||
|
||||
<template>
|
||||
<div class="attachment" @click="download">
|
||||
<img v-if="isImage" class="mail-attached-image" :src="url" />
|
||||
<img class="attachment-icon" :src="mimeUrl" />
|
||||
<span class="attachment-name" :title="label"
|
||||
>{{ name }}
|
||||
<img v-if="isImage" class="mail-attached-image" :src="url">
|
||||
<img class="attachment-icon" :src="mimeUrl">
|
||||
<span class="attachment-name"
|
||||
:title="label">{{ name }}
|
||||
<span class="attachment-size">({{ humanReadable(size) }})</span>
|
||||
</span>
|
||||
<button
|
||||
|
@ -33,38 +33,35 @@
|
|||
:class="{'icon-add': !loadingCalendars, 'icon-loading-small': loadingCalendars}"
|
||||
:disabled="loadingCalendars"
|
||||
:title="t('mail', 'Import into calendar')"
|
||||
@click.stop="loadCalendars"
|
||||
></button>
|
||||
<button class="button icon-download attachment-download" :title="t('mail', 'Download attachment')"></button>
|
||||
@click.stop="loadCalendars" />
|
||||
<button class="button icon-download attachment-download" :title="t('mail', 'Download attachment')" />
|
||||
<button
|
||||
class="attachment-save-to-cloud"
|
||||
:class="{'icon-folder': !savingToCloud, 'icon-loading-small': savingToCloud}"
|
||||
:disabled="savingToCloud"
|
||||
:title="t('mail', 'Save to Files')"
|
||||
@click.stop="saveToCloud"
|
||||
></button>
|
||||
@click.stop="saveToCloud" />
|
||||
<div
|
||||
v-on-click-outside="closeCalendarPopover"
|
||||
class="popovermenu bubble attachment-import-popover hidden"
|
||||
:class="{open: showCalendarPopover}"
|
||||
>
|
||||
:class="{open: showCalendarPopover}">
|
||||
<PopoverMenu :menu="calendarMenuEntries" />
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import {formatFileSize} from '@nextcloud/files'
|
||||
import {mixin as onClickOutside} from 'vue-on-click-outside'
|
||||
import {translate as t} from '@nextcloud/l10n'
|
||||
import {getFilePickerBuilder} from '@nextcloud/dialogs'
|
||||
import { formatFileSize } from '@nextcloud/files'
|
||||
import { mixin as onClickOutside } from 'vue-on-click-outside'
|
||||
import { translate as t } from '@nextcloud/l10n'
|
||||
import { getFilePickerBuilder } from '@nextcloud/dialogs'
|
||||
import PopoverMenu from '@nextcloud/vue/dist/Components/PopoverMenu'
|
||||
|
||||
import {parseUuid} from '../util/EnvelopeUidParser'
|
||||
import { parseUuid } from '../util/EnvelopeUidParser'
|
||||
import Logger from '../logger'
|
||||
|
||||
import {downloadAttachment, saveAttachmentToFiles} from '../service/AttachmentService'
|
||||
import {getUserCalendars, importCalendarEvent} from '../service/DAVService'
|
||||
import { downloadAttachment, saveAttachmentToFiles } from '../service/AttachmentService'
|
||||
import { getUserCalendars, importCalendarEvent } from '../service/DAVService'
|
||||
|
||||
export default {
|
||||
name: 'MessageAttachment',
|
||||
|
@ -146,7 +143,7 @@ export default {
|
|||
const saveAttachment = (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'))
|
||||
.setMultiSelect(false)
|
||||
.addMimeTypeFilter('httpd/unix-directory')
|
||||
|
@ -162,7 +159,7 @@ export default {
|
|||
})
|
||||
.then(saveAttachment(accountId, folderId, uid, this.id))
|
||||
.then(() => Logger.info('saved'))
|
||||
.catch((e) => Logger.error('not saved', {error: e}))
|
||||
.catch((e) => Logger.error('not saved', { error: e }))
|
||||
.then(() => (this.savingToCloud = false))
|
||||
},
|
||||
download() {
|
||||
|
@ -185,7 +182,7 @@ export default {
|
|||
downloadAttachment(this.url)
|
||||
.then(importCalendarEvent(url))
|
||||
.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))
|
||||
}
|
||||
},
|
||||
|
|
|
@ -32,16 +32,14 @@
|
|||
:is-image="attachment.isImage"
|
||||
:is-calendar-event="attachment.isCalendarEvent"
|
||||
:mime="attachment.mime"
|
||||
:mime-url="attachment.mimeUrl"
|
||||
/>
|
||||
:mime-url="attachment.mimeUrl" />
|
||||
</div>
|
||||
<p v-if="moreThanOne">
|
||||
<button
|
||||
class="attachments-save-to-cloud"
|
||||
:class="{'icon-folder': !savingToCloud, 'icon-loading-small': savingToCloud}"
|
||||
:disabled="savingToCloud"
|
||||
@click="saveAll"
|
||||
>
|
||||
@click="saveAll">
|
||||
{{ t('mail', 'Save all to Files') }}
|
||||
</button>
|
||||
</p>
|
||||
|
@ -49,9 +47,9 @@
|
|||
</template>
|
||||
|
||||
<script>
|
||||
import {getFilePickerBuilder} from '@nextcloud/dialogs'
|
||||
import {parseUuid} from '../util/EnvelopeUidParser'
|
||||
import {saveAttachmentsToFiles} from '../service/AttachmentService'
|
||||
import { getFilePickerBuilder } from '@nextcloud/dialogs'
|
||||
import { parseUuid } from '../util/EnvelopeUidParser'
|
||||
import { saveAttachmentsToFiles } from '../service/AttachmentService'
|
||||
|
||||
import MessageAttachment from './MessageAttachment'
|
||||
import Logger from '../logger'
|
||||
|
@ -89,7 +87,7 @@ export default {
|
|||
const saveAttachments = (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
|
||||
.pick()
|
||||
|
@ -99,7 +97,7 @@ export default {
|
|||
})
|
||||
.then(saveAttachments(accountId, folderId, uid))
|
||||
.then(() => Logger.info('saved'))
|
||||
.catch((error) => Logger.error('not saved', {error}))
|
||||
.catch((error) => Logger.error('not saved', { error }))
|
||||
.then(() => (this.savingToCloud = false))
|
||||
},
|
||||
},
|
||||
|
|
|
@ -1,12 +1,12 @@
|
|||
<template>
|
||||
<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>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import {getMailvelope} from '../crypto/mailvelope'
|
||||
import { getMailvelope } from '../crypto/mailvelope'
|
||||
|
||||
export default {
|
||||
name: 'MessageEncryptedBody',
|
||||
|
|
|
@ -8,16 +8,20 @@
|
|||
</div>
|
||||
<div v-if="loading" class="icon-loading" />
|
||||
<div id="message-container" :class="{hidden: loading}">
|
||||
<iframe id="message-frame" ref="iframe" :src="url" seamless @load="onMessageFrameLoad" />
|
||||
<iframe id="message-frame"
|
||||
ref="iframe"
|
||||
:src="url"
|
||||
seamless
|
||||
@load="onMessageFrameLoad" />
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import PrintScout from 'printscout'
|
||||
const scout = new PrintScout()
|
||||
|
||||
import logger from '../logger'
|
||||
const scout = new PrintScout()
|
||||
|
||||
export default {
|
||||
name: 'MessageHTMLBody',
|
||||
|
@ -48,10 +52,9 @@ export default {
|
|||
},
|
||||
onMessageFrameLoad() {
|
||||
const iframeDoc = this.getIframeDoc()
|
||||
const iframeBody = iframeDoc.querySelectorAll('body')[0]
|
||||
this.hasBlockedContent =
|
||||
iframeDoc.querySelectorAll('[data-original-src]').length > 0 ||
|
||||
iframeDoc.querySelectorAll('[data-original-style]').length > 0
|
||||
this.hasBlockedContent
|
||||
= iframeDoc.querySelectorAll('[data-original-src]').length > 0
|
||||
|| iframeDoc.querySelectorAll('[data-original-style]').length > 0
|
||||
|
||||
this.loading = false
|
||||
},
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
<template>
|
||||
<div>
|
||||
<div id="mail-content" v-html="htmlBody"></div>
|
||||
<div v-if="signature" class="mail-signature" v-html="htmlSignature"></div>
|
||||
<div id="mail-content" v-html="htmlBody" />
|
||||
<div v-if="signature" class="mail-signature" v-html="htmlSignature" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
|
|
|
@ -25,8 +25,7 @@
|
|||
:text="t('mail', 'New message')"
|
||||
button-id="mail_new_message"
|
||||
button-class="icon-add"
|
||||
@click="onNewMessage"
|
||||
/>
|
||||
@click="onNewMessage" />
|
||||
<ul id="accounts-list">
|
||||
<template v-for="group in menu">
|
||||
<NavigationAccount
|
||||
|
@ -35,32 +34,28 @@
|
|||
:account="group.account"
|
||||
:first-folder="group.folders[0]"
|
||||
:is-first="isFirst(group.account)"
|
||||
:is-last="isLast(group.account)"
|
||||
/>
|
||||
:is-last="isLast(group.account)" />
|
||||
<template v-for="item in group.folders">
|
||||
<NavigationFolder
|
||||
v-show="
|
||||
!group.isCollapsible ||
|
||||
!group.account.collapsed ||
|
||||
SHOW_COLLAPSED.indexOf(item.specialRole) !== -1
|
||||
!group.account.collapsed ||
|
||||
SHOW_COLLAPSED.indexOf(item.specialRole) !== -1
|
||||
"
|
||||
:key="item.key"
|
||||
:account="group.account"
|
||||
:folder="item"
|
||||
/>
|
||||
:folder="item" />
|
||||
<NavigationFolder
|
||||
v-if="!group.account.isUnified && item.specialRole === 'inbox'"
|
||||
:key="item.key + '-starred'"
|
||||
:account="group.account"
|
||||
:folder="item"
|
||||
filter="starred"
|
||||
/>
|
||||
filter="starred" />
|
||||
</template>
|
||||
<NavigationAccountExpandCollapse
|
||||
v-if="!group.account.isUnified && group.isCollapsible"
|
||||
:key="'collapse-' + group.account.id"
|
||||
:account="group.account"
|
||||
/>
|
||||
:account="group.account" />
|
||||
<AppNavigationSpacer :key="'spacer-' + group.account.id" />
|
||||
</template>
|
||||
</ul>
|
||||
|
@ -81,10 +76,10 @@ import NavigationAccount from './NavigationAccount'
|
|||
import NavigationAccountExpandCollapse from './NavigationAccountExpandCollapse'
|
||||
import NavigationFolder from './NavigationFolder'
|
||||
|
||||
const SHOW_COLLAPSED = Object.seal(['inbox', 'flagged', 'drafts', 'sent'])
|
||||
|
||||
import AppSettingsMenu from '../components/AppSettingsMenu'
|
||||
|
||||
const SHOW_COLLAPSED = Object.seal(['inbox', 'flagged', 'drafts', 'sent'])
|
||||
|
||||
export default {
|
||||
name: 'Navigation',
|
||||
components: {
|
||||
|
@ -127,8 +122,8 @@ export default {
|
|||
// FIXME: this assumes that there's at least one folder
|
||||
const folderId = this.$route.params.folderId || this.$store.getters.getFolders(accountId)[0].id
|
||||
if (
|
||||
this.$router.currentRoute.name === 'message' &&
|
||||
this.$router.currentRoute.params.messageUuid === 'new'
|
||||
this.$router.currentRoute.name === 'message'
|
||||
&& this.$router.currentRoute.params.messageUuid === 'new'
|
||||
) {
|
||||
// If we already show the composer, navigating to it would be pointless (and doesn't work)
|
||||
// instead trigger an event to reset the composer
|
||||
|
|
|
@ -29,8 +29,7 @@
|
|||
:title="account.emailAddress"
|
||||
:to="firstFolderRoute"
|
||||
:exact="true"
|
||||
@update:menuOpen="onMenuToggle"
|
||||
>
|
||||
@update:menuOpen="onMenuToggle">
|
||||
<!-- Color dot -->
|
||||
<AppNavigationIconBullet v-if="bulletColor" slot="icon" :color="bulletColor" />
|
||||
|
||||
|
@ -45,8 +44,7 @@
|
|||
<ActionCheckbox
|
||||
:checked="account.showSubscribedOnly"
|
||||
:disabled="savingShowOnlySubscribed"
|
||||
@update:checked="changeShowSubscribedOnly"
|
||||
>
|
||||
@update:checked="changeShowSubscribedOnly">
|
||||
{{ t('mail', 'Show only subscribed folders') }}
|
||||
</ActionCheckbox>
|
||||
<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 ActionInput from '@nextcloud/vue/dist/Components/ActionInput'
|
||||
import ActionText from '@nextcloud/vue/dist/Components/ActionText'
|
||||
import {formatFileSize} from '@nextcloud/files'
|
||||
import {generateUrl} from '@nextcloud/router'
|
||||
import { formatFileSize } from '@nextcloud/files'
|
||||
import { generateUrl } from '@nextcloud/router'
|
||||
|
||||
import {calculateAccountColor} from '../util/AccountColor'
|
||||
import { calculateAccountColor } from '../util/AccountColor'
|
||||
import logger from '../logger'
|
||||
import {fetchQuota} from '../service/AccountService'
|
||||
import { fetchQuota } from '../service/AccountService'
|
||||
|
||||
export default {
|
||||
name: 'NavigationAccount',
|
||||
|
@ -176,10 +174,10 @@ export default {
|
|||
logger.info('creating folder ' + name)
|
||||
this.menuOpen = false
|
||||
this.$store
|
||||
.dispatch('createFolder', {account: this.account, name})
|
||||
.dispatch('createFolder', { account: this.account, name })
|
||||
.then(() => logger.info(`folder ${name} created`))
|
||||
.catch((error) => {
|
||||
logger.error('could not create folder', {error})
|
||||
logger.error('could not create folder', { error })
|
||||
throw error
|
||||
})
|
||||
this.editing = false
|
||||
|
@ -191,17 +189,17 @@ export default {
|
|||
},
|
||||
removeAccount() {
|
||||
const id = this.account.id
|
||||
logger.info('delete account', {account: this.account})
|
||||
logger.info('delete account', { account: this.account })
|
||||
OC.dialogs.confirmDestructive(
|
||||
t(
|
||||
'mail',
|
||||
'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,
|
||||
confirm: t('mail', 'Remove {email}', {email: this.account.emailAddress}),
|
||||
confirm: t('mail', 'Remove {email}', { email: this.account.emailAddress }),
|
||||
confirmClasses: 'error',
|
||||
cancel: t('mail', 'Cancel'),
|
||||
},
|
||||
|
@ -218,7 +216,7 @@ export default {
|
|||
// TODO: update store and handle this more efficiently
|
||||
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
|
||||
}
|
||||
|
@ -226,13 +224,13 @@ export default {
|
|||
},
|
||||
changeAccountOrderUp() {
|
||||
this.$store
|
||||
.dispatch('moveAccount', {account: this.account, up: true})
|
||||
.catch((error) => logger.error('could not move account up', {error}))
|
||||
.dispatch('moveAccount', { account: this.account, up: true })
|
||||
.catch((error) => logger.error('could not move account up', { error }))
|
||||
},
|
||||
changeAccountOrderDown() {
|
||||
this.$store
|
||||
.dispatch('moveAccount', {account: this.account})
|
||||
.catch((error) => logger.error('could not move account down', {error}))
|
||||
.dispatch('moveAccount', { account: this.account })
|
||||
.catch((error) => logger.error('could not move account down', { error }))
|
||||
},
|
||||
changeShowSubscribedOnly(onlySubscribed) {
|
||||
this.savingShowOnlySubscribed = true
|
||||
|
@ -248,7 +246,7 @@ export default {
|
|||
logger.info('show only subscribed folders updated to ' + onlySubscribed)
|
||||
})
|
||||
.catch((error) => {
|
||||
logger.error('could not update subscription mode', {error})
|
||||
logger.error('could not update subscription mode', { error })
|
||||
this.savingShowOnlySubscribed = false
|
||||
throw error
|
||||
})
|
||||
|
|
|
@ -31,16 +31,14 @@
|
|||
:title="title"
|
||||
:to="to"
|
||||
:open.sync="showSubFolders"
|
||||
@update:menuOpen="onMenuToggle"
|
||||
>
|
||||
@update:menuOpen="onMenuToggle">
|
||||
<!-- actions -->
|
||||
<template slot="actions">
|
||||
<template>
|
||||
<ActionText
|
||||
v-if="!account.isUnified && folder.specialRole !== 'flagged'"
|
||||
icon="icon-info"
|
||||
:title="folderId"
|
||||
>
|
||||
:title="folderId">
|
||||
{{ statsText }}
|
||||
</ActionText>
|
||||
|
||||
|
@ -49,16 +47,14 @@
|
|||
icon="icon-mail"
|
||||
:title="t('mail', 'Mark all as read')"
|
||||
:disabled="loadingMarkAsRead"
|
||||
@click="markAsRead"
|
||||
>
|
||||
@click="markAsRead">
|
||||
{{ t('mail', 'Mark all messages of this folder as read') }}
|
||||
</ActionButton>
|
||||
|
||||
<ActionButton
|
||||
v-if="!editing && top && !account.isUnified && folder.specialRole !== 'flagged'"
|
||||
icon="icon-folder"
|
||||
@click="openCreateFolder"
|
||||
>
|
||||
@click="openCreateFolder">
|
||||
{{ t('mail', 'Add subfolder') }}
|
||||
</ActionButton>
|
||||
<ActionInput v-if="editing" icon="icon-folder" @submit.prevent.stop="createFolder" />
|
||||
|
@ -71,8 +67,7 @@
|
|||
icon="icon-settings"
|
||||
:title="t('mail', 'Clear cache')"
|
||||
:disabled="clearingCache"
|
||||
@click="clearCache"
|
||||
>
|
||||
@click="clearCache">
|
||||
{{ t('mail', 'Clear locally cached data, in case there are issues with synchronization.') }}
|
||||
</ActionButton>
|
||||
</template>
|
||||
|
@ -87,8 +82,7 @@
|
|||
:key="genId(subFolder)"
|
||||
:account="account"
|
||||
:folder="subFolder"
|
||||
:top="false"
|
||||
/>
|
||||
:top="false" />
|
||||
</AppNavigationItem>
|
||||
</template>
|
||||
|
||||
|
@ -99,11 +93,11 @@ import ActionButton from '@nextcloud/vue/dist/Components/ActionButton'
|
|||
import ActionInput from '@nextcloud/vue/dist/Components/ActionInput'
|
||||
import ActionText from '@nextcloud/vue/dist/Components/ActionText'
|
||||
|
||||
import {clearCache} from '../service/MessageService'
|
||||
import {getFolderStats} from '../service/FolderService'
|
||||
import { clearCache } from '../service/MessageService'
|
||||
import { getFolderStats } from '../service/FolderService'
|
||||
import logger from '../logger'
|
||||
import {translatePlural as n} from '@nextcloud/l10n'
|
||||
import {translate as translateMailboxName} from '../i18n/MailboxTranslator'
|
||||
import { translatePlural as n } from '@nextcloud/l10n'
|
||||
import { translate as translateMailboxName } from '../i18n/MailboxTranslator'
|
||||
|
||||
export default {
|
||||
name: 'NavigationFolder',
|
||||
|
@ -148,8 +142,8 @@ export default {
|
|||
computed: {
|
||||
visible() {
|
||||
return (
|
||||
this.account.showSubscribedOnly === false ||
|
||||
(this.folder.attributes && this.folder.attributes.includes('\\subscribed'))
|
||||
this.account.showSubscribedOnly === false
|
||||
|| (this.folder.attributes && this.folder.attributes.includes('\\subscribed'))
|
||||
)
|
||||
},
|
||||
title() {
|
||||
|
@ -211,6 +205,8 @@ export default {
|
|||
methods: {
|
||||
/**
|
||||
* Generate unique key id for a specific folder
|
||||
* @param {Object} folder the folder to gen id for
|
||||
* @returns {string}
|
||||
*/
|
||||
genId(folder) {
|
||||
return 'account-' + this.account.id + '_' + folder.id
|
||||
|
@ -237,10 +233,10 @@ export default {
|
|||
|
||||
try {
|
||||
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
|
||||
} catch (error) {
|
||||
this.folderStats = {error: true}
|
||||
this.folderStats = { error: true }
|
||||
logger.error(`could not load folder stats for ${this.folder.id}`, error)
|
||||
}
|
||||
},
|
||||
|
@ -257,7 +253,7 @@ export default {
|
|||
name: withPrefix,
|
||||
})
|
||||
} catch (error) {
|
||||
logger.error(`could not create folder ${withPrefix}`, {error})
|
||||
logger.error(`could not create folder ${withPrefix}`, { error })
|
||||
throw error
|
||||
} finally {
|
||||
this.editing = false
|
||||
|
@ -279,7 +275,7 @@ export default {
|
|||
folderId: this.folder.id,
|
||||
})
|
||||
.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))
|
||||
},
|
||||
async clearCache() {
|
||||
|
|
|
@ -5,9 +5,7 @@
|
|||
v-else-if="error"
|
||||
:error="error && error.message ? error.message : t('mail', 'Not found')"
|
||||
:message="errorMessage"
|
||||
:data="error"
|
||||
>
|
||||
</Error>
|
||||
:data="error" />
|
||||
<Composer
|
||||
v-else
|
||||
:from-account="composerData.accountId"
|
||||
|
@ -19,24 +17,23 @@
|
|||
:draft="saveDraft"
|
||||
:send="sendMessage"
|
||||
:reply-to="composerData.replyTo"
|
||||
:forward-from="composerData.forwardFrom"
|
||||
/>
|
||||
:forward-from="composerData.forwardFrom" />
|
||||
</AppContentDetails>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import AppContentDetails from '@nextcloud/vue/dist/Components/AppContentDetails'
|
||||
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 Error from './Error'
|
||||
import {getRandomMessageErrorMessage} from '../util/ErrorMessageFactory'
|
||||
import {detect, html, plain, toPlain} from '../util/text'
|
||||
import { getRandomMessageErrorMessage } from '../util/ErrorMessageFactory'
|
||||
import { detect, html, plain, toPlain } from '../util/text'
|
||||
import Loading from './Loading'
|
||||
import logger from '../logger'
|
||||
import {saveDraft, sendMessage} from '../service/MessageService'
|
||||
import { saveDraft, sendMessage } from '../service/MessageService'
|
||||
|
||||
export default {
|
||||
name: 'NewMessageDetail',
|
||||
|
@ -59,7 +56,7 @@ export default {
|
|||
computed: {
|
||||
composerData() {
|
||||
if (this.draft !== undefined) {
|
||||
logger.info('todo: handle draft data', {draft: this.draft})
|
||||
logger.info('todo: handle draft data', { draft: this.draft })
|
||||
return {
|
||||
to: this.draft.to,
|
||||
cc: this.draft.cc,
|
||||
|
@ -70,7 +67,7 @@ export default {
|
|||
} else if (this.$route.query.uuid !== undefined) {
|
||||
// Forward or reply to a message
|
||||
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') {
|
||||
logger.debug('simple reply')
|
||||
|
@ -85,7 +82,7 @@ export default {
|
|||
replyTo: message,
|
||||
}
|
||||
} 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 recipients = buildReplyRecipients(message, {
|
||||
email: account.emailAddress,
|
||||
|
@ -185,7 +182,7 @@ export default {
|
|||
this.draft = draft
|
||||
|
||||
if (this.draft === undefined) {
|
||||
logger.info('draft could not be found', {draftUid})
|
||||
logger.info('draft could not be found', { draftUid })
|
||||
this.errorMessage = getRandomMessageErrorMessage()
|
||||
this.loading = false
|
||||
return
|
||||
|
@ -194,7 +191,7 @@ export default {
|
|||
this.loading = false
|
||||
})
|
||||
.catch((error) => {
|
||||
logger.error('could not load draft ' + draftUid, {error})
|
||||
logger.error('could not load draft ' + draftUid, { error })
|
||||
if (error.isError) {
|
||||
this.errorMessage = t('mail', 'Could not load your draft')
|
||||
this.error = error
|
||||
|
@ -214,7 +211,7 @@ export default {
|
|||
return
|
||||
}
|
||||
|
||||
logger.debug('original message fetched', {message})
|
||||
logger.debug('original message fetched', { message })
|
||||
this.original = message
|
||||
|
||||
let body = plain(message.body || '')
|
||||
|
@ -232,7 +229,7 @@ export default {
|
|||
}
|
||||
this.originalBody = body
|
||||
} catch (error) {
|
||||
logger.error('could not load original message ' + uid, {error})
|
||||
logger.error('could not load original message ' + uid, { error })
|
||||
if (error.isError) {
|
||||
this.errorMessage = t('mail', 'Could not load original message')
|
||||
this.error = error
|
||||
|
@ -251,7 +248,7 @@ export default {
|
|||
...data,
|
||||
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) {
|
||||
return uid
|
||||
}
|
||||
|
@ -279,7 +276,7 @@ export default {
|
|||
})
|
||||
},
|
||||
sendMessage(data) {
|
||||
logger.debug('sending message', {data})
|
||||
logger.debug('sending message', { data })
|
||||
const dataForServer = {
|
||||
...data,
|
||||
body: data.isHtml ? data.body.value : toPlain(data.body).value,
|
||||
|
|
|
@ -21,9 +21,9 @@
|
|||
|
||||
<template>
|
||||
<AppContentDetails>
|
||||
<div class="icon icon-mail"></div>
|
||||
<div class="icon icon-mail" />
|
||||
<h2>{{ t('mail', 'No message selected') }}</h2>
|
||||
<p></p>
|
||||
<p />
|
||||
</AppContentDetails>
|
||||
</template>
|
||||
|
||||
|
|
|
@ -25,13 +25,15 @@
|
|||
<p class="settings-hint">
|
||||
{{ t('mail', 'A signature is added to the text of new messages and replies.') }}
|
||||
</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
|
||||
class="primary"
|
||||
:class="loading ? 'icon-loading-small-dark' : 'icon-checkmark-white'"
|
||||
:disabled="loading"
|
||||
@click="saveSignature"
|
||||
>
|
||||
@click="saveSignature">
|
||||
{{ t('mail', 'Save signature') }}
|
||||
</button>
|
||||
<button v-if="signature" class="button-text" @click="deleteSignature">
|
||||
|
@ -43,7 +45,7 @@
|
|||
<script>
|
||||
import logger from '../logger'
|
||||
import TextEditor from './TextEditor'
|
||||
import {detect, html, toHtml} from '../util/text'
|
||||
import { detect, toHtml } from '../util/text'
|
||||
import Vue from 'vue'
|
||||
|
||||
export default {
|
||||
|
@ -73,14 +75,14 @@ export default {
|
|||
this.loading = true
|
||||
|
||||
this.$store
|
||||
.dispatch('updateAccountSignature', {account: this.account, signature: null})
|
||||
.dispatch('updateAccountSignature', { account: this.account, signature: null })
|
||||
.then(() => {
|
||||
logger.info('signature deleted')
|
||||
this.signature = ''
|
||||
this.loading = false
|
||||
})
|
||||
.catch((error) => {
|
||||
logger.error('could not delete account signature', {error})
|
||||
logger.error('could not delete account signature', { error })
|
||||
throw error
|
||||
})
|
||||
},
|
||||
|
@ -88,13 +90,13 @@ export default {
|
|||
this.loading = true
|
||||
|
||||
this.$store
|
||||
.dispatch('updateAccountSignature', {account: this.account, signature: this.signature})
|
||||
.dispatch('updateAccountSignature', { account: this.account, signature: this.signature })
|
||||
.then(() => {
|
||||
logger.info('signature updated')
|
||||
this.loading = false
|
||||
})
|
||||
.catch((error) => {
|
||||
logger.error('could not update account signature', {error})
|
||||
logger.error('could not update account signature', { error })
|
||||
throw error
|
||||
})
|
||||
},
|
||||
|
|
|
@ -26,8 +26,7 @@
|
|||
:config="config"
|
||||
:editor="editor"
|
||||
@input="onInput"
|
||||
@ready="onEditorReady"
|
||||
></ckeditor>
|
||||
@ready="onEditorReady" />
|
||||
</template>
|
||||
|
||||
<script>
|
||||
|
@ -42,7 +41,7 @@ import ItalicPlugin from '@ckeditor/ckeditor5-basic-styles/src/italic'
|
|||
import LinkPlugin from '@ckeditor/ckeditor5-link/src/link'
|
||||
import ParagraphPlugin from '@ckeditor/ckeditor5-paragraph/src/paragraph'
|
||||
|
||||
import {getLanguage} from '@nextcloud/l10n'
|
||||
import { getLanguage } from '@nextcloud/l10n'
|
||||
|
||||
import logger from '../logger'
|
||||
|
||||
|
@ -135,7 +134,7 @@ export default {
|
|||
onEditorReady(editor) {
|
||||
const schema = editor.model.schema
|
||||
|
||||
logger.debug('CKEditor editor is ready', {editor, schema})
|
||||
logger.debug('CKEditor editor is ready', { editor, schema })
|
||||
|
||||
this.editorInstance = editor
|
||||
|
||||
|
|
|
@ -25,9 +25,9 @@
|
|||
v-for="(calendar, idx) in cals"
|
||||
:key="idx"
|
||||
:icon="calendar.loading ? 'icon-loading-small' : 'icon-add'"
|
||||
@click="onImport(calendar)"
|
||||
>{{ t('mail', 'Import into {calendar}', {calendar: calendar.displayname}) }}</ActionButton
|
||||
>
|
||||
@click="onImport(calendar)">
|
||||
{{ t('mail', 'Import into {calendar}', {calendar: calendar.displayname}) }}
|
||||
</ActionButton>
|
||||
</Actions>
|
||||
</template>
|
||||
|
||||
|
|
|
@ -22,10 +22,18 @@
|
|||
<template>
|
||||
<div class="reservation">
|
||||
<div class="event">
|
||||
<div class="event-name">{{ eventName }}</div>
|
||||
<div v-if="location" class="venue">{{ location }}</div>
|
||||
<div v-if="date">{{ date }}</div>
|
||||
<div v-if="time">{{ time }}</div>
|
||||
<div class="event-name">
|
||||
{{ eventName }}
|
||||
</div>
|
||||
<div v-if="location" class="venue">
|
||||
{{ location }}
|
||||
</div>
|
||||
<div v-if="date">
|
||||
{{ date }}
|
||||
</div>
|
||||
<div v-if="time">
|
||||
{{ time }}
|
||||
</div>
|
||||
</div>
|
||||
<CalendarImport v-if="canImport" :calendars="calendars" :handler="handleImport" />
|
||||
</div>
|
||||
|
@ -35,15 +43,15 @@
|
|||
import ical from 'ical.js'
|
||||
import md5 from 'md5'
|
||||
import moment from '@nextcloud/moment'
|
||||
import {showError, showSuccess} from '@nextcloud/dialogs'
|
||||
import { showError, showSuccess } from '@nextcloud/dialogs'
|
||||
|
||||
import CalendarImport from './CalendarImport'
|
||||
import {importCalendarEvent} from '../../service/DAVService'
|
||||
import { importCalendarEvent } from '../../service/DAVService'
|
||||
import logger from '../../logger'
|
||||
|
||||
export default {
|
||||
name: 'EventReservation',
|
||||
components: {CalendarImport},
|
||||
components: { CalendarImport },
|
||||
props: {
|
||||
data: {
|
||||
type: Object,
|
||||
|
@ -124,14 +132,14 @@ export default {
|
|||
|
||||
const cal = new ical.Component('VCALENDAR')
|
||||
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())
|
||||
.then(() => {
|
||||
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) => {
|
||||
logger.error('Could not import event', {error})
|
||||
logger.error('Could not import event', { error })
|
||||
showError(t('mail', 'Could not create event'))
|
||||
})
|
||||
},
|
||||
|
|
|
@ -1,22 +1,42 @@
|
|||
<template>
|
||||
<div class="reservation">
|
||||
<div class="departure">
|
||||
<div class="iata">{{ data.reservationFor.departureAirport.iataCode }}</div>
|
||||
<div class="airport">{{ data.reservationFor.departureAirport.name }}</div>
|
||||
<div v-if="departureDate">{{ departureDate }}</div>
|
||||
<div v-if="departureTime">{{ departureTime }}</div>
|
||||
<div class="iata">
|
||||
{{ data.reservationFor.departureAirport.iataCode }}
|
||||
</div>
|
||||
<div class="airport">
|
||||
{{ data.reservationFor.departureAirport.name }}
|
||||
</div>
|
||||
<div v-if="departureDate">
|
||||
{{ departureDate }}
|
||||
</div>
|
||||
<div v-if="departureTime">
|
||||
{{ departureTime }}
|
||||
</div>
|
||||
</div>
|
||||
<div class="connection">
|
||||
<div><AirplaneIcon :title="t('mail', 'Airplane')" /></div>
|
||||
<div>{{ flightNumber }}</div>
|
||||
<div v-if="reservation">{{ t('mail', 'Reservation {id}', {id: reservation}) }}</div>
|
||||
<div v-else><ArrowIcon decorative /></div>
|
||||
<div v-if="reservation">
|
||||
{{ t('mail', 'Reservation {id}', {id: reservation}) }}
|
||||
</div>
|
||||
<div v-else>
|
||||
<ArrowIcon decorative />
|
||||
</div>
|
||||
</div>
|
||||
<div class="arrival">
|
||||
<div class="iata">{{ data.reservationFor.arrivalAirport.iataCode }}</div>
|
||||
<div class="airport">{{ data.reservationFor.arrivalAirport.name }}</div>
|
||||
<div v-if="arrivalDate">{{ arrivalDate }}</div>
|
||||
<div v-if="arrivalTime">{{ arrivalTime }}</div>
|
||||
<div class="iata">
|
||||
{{ data.reservationFor.arrivalAirport.iataCode }}
|
||||
</div>
|
||||
<div class="airport">
|
||||
{{ data.reservationFor.arrivalAirport.name }}
|
||||
</div>
|
||||
<div v-if="arrivalDate">
|
||||
{{ arrivalDate }}
|
||||
</div>
|
||||
<div v-if="arrivalTime">
|
||||
{{ arrivalTime }}
|
||||
</div>
|
||||
</div>
|
||||
<CalendarImport :calendars="calendars" :handler="handleImport" />
|
||||
</div>
|
||||
|
@ -28,10 +48,10 @@ import ArrowIcon from 'vue-material-design-icons/ArrowRight'
|
|||
import ical from 'ical.js'
|
||||
import md5 from 'md5'
|
||||
import moment from '@nextcloud/moment'
|
||||
import {showError, showSuccess} from '@nextcloud/dialogs'
|
||||
import { showError, showSuccess } from '@nextcloud/dialogs'
|
||||
|
||||
import CalendarImport from './CalendarImport'
|
||||
import {importCalendarEvent} from '../../service/DAVService'
|
||||
import { importCalendarEvent } from '../../service/DAVService'
|
||||
import logger from '../../logger'
|
||||
|
||||
export default {
|
||||
|
@ -118,15 +138,15 @@ export default {
|
|||
|
||||
const cal = new ical.Component('VCALENDAR')
|
||||
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())
|
||||
.then(() => {
|
||||
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) => {
|
||||
logger.error('Could not import event', {error})
|
||||
logger.error('Could not import event', { error })
|
||||
showError(t('mail', 'Could not create event'))
|
||||
})
|
||||
},
|
||||
|
|
|
@ -1,9 +1,15 @@
|
|||
<template>
|
||||
<div class="reservation">
|
||||
<div class="departure">
|
||||
<div class="station">{{ data.reservationFor.departureStation.name }}</div>
|
||||
<div v-if="departureDate">{{ departureDate }}</div>
|
||||
<div v-if="departureTime">{{ departureTime }}</div>
|
||||
<div class="station">
|
||||
{{ data.reservationFor.departureStation.name }}
|
||||
</div>
|
||||
<div v-if="departureDate">
|
||||
{{ departureDate }}
|
||||
</div>
|
||||
<div v-if="departureTime">
|
||||
{{ departureTime }}
|
||||
</div>
|
||||
</div>
|
||||
<div class="connection">
|
||||
<div><TrainIcon :title="t('mail', 'Train')" /></div>
|
||||
|
@ -11,9 +17,15 @@
|
|||
<div><ArrowIcon decorative /></div>
|
||||
</div>
|
||||
<div class="arrival">
|
||||
<div class="station">{{ data.reservationFor.arrivalStation.name }}</div>
|
||||
<div v-if="arrivalDate">{{ arrivalDate }}</div>
|
||||
<div v-if="arrivalTime">{{ arrivalTime }}</div>
|
||||
<div class="station">
|
||||
{{ data.reservationFor.arrivalStation.name }}
|
||||
</div>
|
||||
<div v-if="arrivalDate">
|
||||
{{ arrivalDate }}
|
||||
</div>
|
||||
<div v-if="arrivalTime">
|
||||
{{ arrivalTime }}
|
||||
</div>
|
||||
</div>
|
||||
<CalendarImport v-if="canImport" :calendars="calendars" :handler="handleImport" />
|
||||
</div>
|
||||
|
@ -24,11 +36,11 @@ import ArrowIcon from 'vue-material-design-icons/ArrowRight'
|
|||
import ical from 'ical.js'
|
||||
import md5 from 'md5'
|
||||
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 CalendarImport from './CalendarImport'
|
||||
import {importCalendarEvent} from '../../service/DAVService'
|
||||
import { importCalendarEvent } from '../../service/DAVService'
|
||||
import logger from '../../logger'
|
||||
|
||||
export default {
|
||||
|
@ -85,8 +97,8 @@ export default {
|
|||
},
|
||||
canImport() {
|
||||
return (
|
||||
('departureTime' in this.data.reservationFor && 'arrivalTime' in this.data.reservationFor) ||
|
||||
'departureDay' in this.data.reservationFor
|
||||
('departureTime' in this.data.reservationFor && 'arrivalTime' in this.data.reservationFor)
|
||||
|| 'departureDay' in this.data.reservationFor
|
||||
)
|
||||
},
|
||||
},
|
||||
|
@ -130,15 +142,15 @@ export default {
|
|||
|
||||
const cal = new ical.Component('VCALENDAR')
|
||||
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())
|
||||
.then(() => {
|
||||
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) => {
|
||||
logger.error('Could not import event', {error})
|
||||
logger.error('Could not import event', { error })
|
||||
showError(t('mail', 'Could not create event'))
|
||||
})
|
||||
},
|
||||
|
|
|
@ -25,8 +25,8 @@
|
|||
<span v-if="data.uid">uid={{ data.uid }}</span>
|
||||
<span v-if="data.email">email={{ email }}</span>
|
||||
</b>
|
||||
<br />
|
||||
{{ t('mail', 'Email: {email}', {email}) }}<br />
|
||||
<br>
|
||||
{{ t('mail', 'Email: {email}', {email}) }}<br>
|
||||
{{
|
||||
t('mail', 'IMAP: {user} on {host}:{port} ({ssl} encryption)', {
|
||||
user: imapUser,
|
||||
|
@ -34,7 +34,7 @@
|
|||
port: imapPort,
|
||||
ssl: imapSslMode,
|
||||
})
|
||||
}}<br />
|
||||
}}<br>
|
||||
{{
|
||||
t('mail', 'SMTP: {user} on {host}:{port} ({ssl} encryption)', {
|
||||
user: smtpUser,
|
||||
|
@ -42,7 +42,7 @@
|
|||
port: smtpPort,
|
||||
ssl: smtpSslMode,
|
||||
})
|
||||
}}<br />
|
||||
}}<br>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
|
|
|
@ -37,7 +37,10 @@
|
|||
}}
|
||||
</p>
|
||||
<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">
|
||||
{{ t('mail', 'Provision an account for every user') }}
|
||||
</label>
|
||||
|
@ -45,55 +48,55 @@
|
|||
<div v-if="active" class="form-preview-row">
|
||||
<form @submit.prevent="submit">
|
||||
<div class="settings-group">
|
||||
<div class="group-title">{{ t('mail', 'General') }}</div>
|
||||
<div class="group-title">
|
||||
{{ t('mail', 'General') }}
|
||||
</div>
|
||||
<div class="group-inputs">
|
||||
<label for="mail-provision-email"> {{ t('mail', 'Email address') }}* </label>
|
||||
<br />
|
||||
<br>
|
||||
<input
|
||||
id="mail-provision-email"
|
||||
v-model="emailTemplate"
|
||||
:disabled="loading"
|
||||
name="email"
|
||||
type="text"
|
||||
/>
|
||||
type="text">
|
||||
</div>
|
||||
</div>
|
||||
<div class="settings-group">
|
||||
<div class="group-title">{{ t('mail', 'IMAP') }}</div>
|
||||
<div class="group-title">
|
||||
{{ t('mail', 'IMAP') }}
|
||||
</div>
|
||||
<div class="group-inputs">
|
||||
<label for="mail-provision-imap-user">
|
||||
{{ t('mail', 'User') }}*
|
||||
<br />
|
||||
<br>
|
||||
<input
|
||||
id="mail-provision-imap-user"
|
||||
v-model="imapUser"
|
||||
:disabled="loading"
|
||||
name="email"
|
||||
type="text"
|
||||
/>
|
||||
type="text">
|
||||
</label>
|
||||
<div class="flex-row">
|
||||
<label for="mail-provision-imap-host">
|
||||
{{ t('mail', 'Host') }}
|
||||
<br />
|
||||
<br>
|
||||
<input
|
||||
id="mail-provision-imap-host"
|
||||
v-model="imapHost"
|
||||
:disabled="loading"
|
||||
name="email"
|
||||
type="text"
|
||||
/>
|
||||
type="text">
|
||||
</label>
|
||||
<label for="mail-provision-imap-port">
|
||||
{{ t('mail', 'Port') }}
|
||||
<br />
|
||||
<br>
|
||||
<input
|
||||
id="mail-provision-imap-port"
|
||||
v-model="imapPort"
|
||||
:disabled="loading"
|
||||
name="email"
|
||||
type="number"
|
||||
/>
|
||||
type="number">
|
||||
</label>
|
||||
</div>
|
||||
<div class="flex-row">
|
||||
|
@ -103,81 +106,71 @@
|
|||
type="radio"
|
||||
name="man-imap-sec"
|
||||
:disabled="loading"
|
||||
value="none"
|
||||
/>
|
||||
value="none">
|
||||
<label
|
||||
class="button"
|
||||
for="mail-provision-imap-user-none"
|
||||
:class="{primary: imapSslMode === 'none'}"
|
||||
>{{ t('mail', 'None') }}</label
|
||||
>
|
||||
:class="{primary: imapSslMode === 'none'}">{{ t('mail', 'None') }}</label>
|
||||
<input
|
||||
id="mail-provision-imap-user-ssl"
|
||||
v-model="imapSslMode"
|
||||
type="radio"
|
||||
name="man-imap-sec"
|
||||
:disabled="loading"
|
||||
value="ssl"
|
||||
/>
|
||||
value="ssl">
|
||||
<label
|
||||
class="button"
|
||||
for="mail-provision-imap-user-ssl"
|
||||
:class="{primary: imapSslMode === 'ssl'}"
|
||||
>{{ t('mail', 'SSL/TLS') }}</label
|
||||
>
|
||||
:class="{primary: imapSslMode === 'ssl'}">{{ t('mail', 'SSL/TLS') }}</label>
|
||||
<input
|
||||
id="mail-provision-imap-user-tls"
|
||||
v-model="imapSslMode"
|
||||
type="radio"
|
||||
name="man-imap-sec"
|
||||
:disabled="loading"
|
||||
value="tls"
|
||||
/>
|
||||
value="tls">
|
||||
<label
|
||||
class="button"
|
||||
for="mail-provision-imap-user-tls"
|
||||
:class="{primary: imapSslMode === 'tls'}"
|
||||
>{{ t('mail', 'STARTTLS') }}</label
|
||||
>
|
||||
:class="{primary: imapSslMode === 'tls'}">{{ t('mail', 'STARTTLS') }}</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="settings-group">
|
||||
<div class="group-title">{{ t('mail', 'SMTP') }}</div>
|
||||
<div class="group-title">
|
||||
{{ t('mail', 'SMTP') }}
|
||||
</div>
|
||||
<div class="group-inputs">
|
||||
<label for="mail-provision-smtp-user">
|
||||
{{ t('mail', 'User') }}*
|
||||
<br />
|
||||
<br>
|
||||
<input
|
||||
id="mail-provision-smtp-user"
|
||||
v-model="smtpUser"
|
||||
:disabled="loading"
|
||||
name="email"
|
||||
type="text"
|
||||
/>
|
||||
type="text">
|
||||
</label>
|
||||
<div class="flex-row">
|
||||
<label for="mail-provision-imap-host">
|
||||
{{ t('mail', 'Host') }}
|
||||
<br />
|
||||
<br>
|
||||
<input
|
||||
id="mail-provision-smtp-host"
|
||||
v-model="smtpHost"
|
||||
:disabled="loading"
|
||||
name="email"
|
||||
type="text"
|
||||
/>
|
||||
type="text">
|
||||
</label>
|
||||
<label for="mail-provision-smtp-port">
|
||||
{{ t('mail', 'Port') }}
|
||||
<br />
|
||||
<br>
|
||||
<input
|
||||
id="mail-provision-smtp-port"
|
||||
v-model="smtpPort"
|
||||
:disabled="loading"
|
||||
name="email"
|
||||
type="number"
|
||||
/>
|
||||
type="number">
|
||||
</label>
|
||||
</div>
|
||||
<div class="flex-row">
|
||||
|
@ -187,61 +180,50 @@
|
|||
type="radio"
|
||||
name="man-smtp-sec"
|
||||
:disabled="loading"
|
||||
value="none"
|
||||
/>
|
||||
value="none">
|
||||
<label
|
||||
class="button"
|
||||
for="mail-provision-smtp-user-none"
|
||||
:class="{primary: smtpSslMode === 'none'}"
|
||||
>{{ t('mail', 'None') }}</label
|
||||
>
|
||||
:class="{primary: smtpSslMode === 'none'}">{{ t('mail', 'None') }}</label>
|
||||
<input
|
||||
id="mail-provision-smtp-user-ssl"
|
||||
v-model="smtpSslMode"
|
||||
type="radio"
|
||||
name="man-smtp-sec"
|
||||
:disabled="loading"
|
||||
value="ssl"
|
||||
/>
|
||||
value="ssl">
|
||||
<label
|
||||
class="button"
|
||||
for="mail-provision-smtp-user-ssl"
|
||||
:class="{primary: smtpSslMode === 'ssl'}"
|
||||
>{{ t('mail', 'SSL/TLS') }}</label
|
||||
>
|
||||
:class="{primary: smtpSslMode === 'ssl'}">{{ t('mail', 'SSL/TLS') }}</label>
|
||||
<input
|
||||
id="mail-provision-smtp-user-tls"
|
||||
v-model="smtpSslMode"
|
||||
type="radio"
|
||||
name="man-smtp-sec"
|
||||
:disabled="loading"
|
||||
value="tls"
|
||||
/>
|
||||
value="tls">
|
||||
<label
|
||||
class="button"
|
||||
for="mail-provision-smtp-user-tls"
|
||||
:class="{primary: smtpSslMode === 'tls'}"
|
||||
>{{ t('mail', 'STARTTLS') }}</label
|
||||
>
|
||||
:class="{primary: smtpSslMode === 'tls'}">{{ t('mail', 'STARTTLS') }}</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="settings-group">
|
||||
<div class="group-title"></div>
|
||||
<div class="group-title" />
|
||||
<div class="group-inputs">
|
||||
<input
|
||||
type="submit"
|
||||
class="primary"
|
||||
:disabled="loading"
|
||||
:value="t('mail', 'Apply and create/update for all users')"
|
||||
/>
|
||||
:value="t('mail', 'Apply and create/update for all users')">
|
||||
<input
|
||||
type="button"
|
||||
:disabled="loading"
|
||||
:value="t('mail', 'Disable and un-provision existing accounts')"
|
||||
@click="disable"
|
||||
/>
|
||||
<br />
|
||||
@click="disable">
|
||||
<br>
|
||||
<small>{{
|
||||
t('mail', "* %USERID% and %EMAIL% will be replaced with the user's UID and email")
|
||||
}}</small>
|
||||
|
@ -267,11 +249,11 @@
|
|||
<script>
|
||||
import logger from '../../logger'
|
||||
import ProvisionPreview from './ProvisionPreview'
|
||||
import {disableProvisioning, saveProvisioningSettings} from '../../service/SettingsService'
|
||||
import { disableProvisioning, saveProvisioningSettings } from '../../service/SettingsService'
|
||||
|
||||
export default {
|
||||
name: 'ProvisioningSettings',
|
||||
components: {ProvisionPreview},
|
||||
components: { ProvisionPreview },
|
||||
props: {
|
||||
settings: {
|
||||
type: Object,
|
||||
|
@ -317,7 +299,7 @@ export default {
|
|||
},
|
||||
},
|
||||
beforeMount() {
|
||||
logger.debug('provisioning settings loaded', {settings: this.settings})
|
||||
logger.debug('provisioning settings loaded', { settings: this.settings })
|
||||
},
|
||||
methods: {
|
||||
submit() {
|
||||
|
@ -339,7 +321,7 @@ export default {
|
|||
})
|
||||
.catch((error) => {
|
||||
// TODO: show user feedback
|
||||
logger.error('Could not save provisioning settings', {error})
|
||||
logger.error('Could not save provisioning settings', { error })
|
||||
})
|
||||
.then(() => {
|
||||
this.loading = false
|
||||
|
@ -353,7 +335,7 @@ export default {
|
|||
logger.info('deprovisioned successfully')
|
||||
})
|
||||
.catch((error) => {
|
||||
logger.error('could not deprovision accounts', {error})
|
||||
logger.error('could not deprovision accounts', { error })
|
||||
})
|
||||
.then(() => {
|
||||
this.active = false
|
||||
|
|
|
@ -26,11 +26,11 @@ let mailvelope
|
|||
const loadMailvelopeStatically = () => window.mailvelope
|
||||
|
||||
const loadMailvelopeDynamically = () =>
|
||||
new Promise((res) => {
|
||||
window.addEventListener('mailvelope', () => res(window.mailvelope), false)
|
||||
new Promise((resolve) => {
|
||||
window.addEventListener('mailvelope', () => resolve(window.mailvelope), false)
|
||||
})
|
||||
|
||||
export const getMailvelope = async () => {
|
||||
export const getMailvelope = async() => {
|
||||
if (mailvelope) {
|
||||
return mailvelope
|
||||
}
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
/*
|
||||
/**
|
||||
* @copyright 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/>.
|
||||
*/
|
||||
|
||||
import startsWith from 'lodash/fp/startsWith'
|
||||
|
||||
/**
|
||||
* @param {Text} message
|
||||
* @return {boolean|*}
|
||||
* @param {Text} message the message
|
||||
* @returns {boolean|*}
|
||||
*/
|
||||
export const isPgpgMessage = (message) =>
|
||||
message.format === 'plain' && message.value.startsWith('-----BEGIN PGP MESSAGE-----')
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
/*
|
||||
/**
|
||||
* @copyright 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 {
|
||||
|
||||
constructor(message) {
|
||||
super(message)
|
||||
this.name = MailboxLockedError.getName()
|
||||
|
@ -29,4 +30,5 @@ export default class MailboxLockedError extends Error {
|
|||
static getName() {
|
||||
return 'MailboxLockedError'
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -20,6 +20,7 @@
|
|||
*/
|
||||
|
||||
export default class MailboxNotCachedError extends Error {
|
||||
|
||||
constructor(message) {
|
||||
super(message)
|
||||
this.name = MailboxNotCachedError.getName()
|
||||
|
@ -29,4 +30,5 @@ export default class MailboxNotCachedError extends Error {
|
|||
static getName() {
|
||||
return 'MailboxNotCachedError'
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -20,6 +20,7 @@
|
|||
*/
|
||||
|
||||
export default class SyncIncompleteError extends Error {
|
||||
|
||||
constructor(message) {
|
||||
super(message)
|
||||
this.name = SyncIncompleteError.getName()
|
||||
|
@ -29,4 +30,5 @@ export default class SyncIncompleteError extends Error {
|
|||
static getName() {
|
||||
return 'SyncIncompleteError'
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
/*
|
||||
/**
|
||||
* @copyright 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) => {
|
||||
if (!('response' in axiosError)) {
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
/*
|
||||
/**
|
||||
* @copyright 2020 Christoph Wurst <christoph@winzerhof-wurst.at>
|
||||
*
|
||||
* @author 2020 Christoph Wurst <christoph@winzerhof-wurst.at>
|
||||
|
@ -20,15 +20,15 @@
|
|||
*/
|
||||
|
||||
/**
|
||||
* @param {Error} error
|
||||
* @param {object} matches
|
||||
* @param {Error} error error
|
||||
* @param {object} matches matches
|
||||
*/
|
||||
export const matchError = async (error, matches) => {
|
||||
export const matchError = async(error, matches) => {
|
||||
if (error.name in matches) {
|
||||
return await Promise.resolve(matches[error.name](error))
|
||||
}
|
||||
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)
|
||||
}
|
||||
|
|
|
@ -28,7 +28,7 @@ export const parseErrorResponse = (resp) => {
|
|||
return resp
|
||||
}
|
||||
|
||||
const {debug, type, code, message, trace} = resp.data.data || {}
|
||||
const { debug, type, code, message, trace } = resp.data.data || {}
|
||||
|
||||
return {
|
||||
isError: true,
|
||||
|
|
|
@ -19,7 +19,7 @@
|
|||
* 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) => {
|
||||
if (folder.specialUse.includes('all')) {
|
||||
|
|
|
@ -19,6 +19,6 @@
|
|||
* 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()
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
/*
|
||||
/**
|
||||
* @copyright 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/>.
|
||||
*/
|
||||
|
||||
import {generateFilePath} from '@nextcloud/router'
|
||||
import {getRequestToken} from '@nextcloud/auth'
|
||||
import {loadState} from '@nextcloud/initial-state'
|
||||
import { generateFilePath } from '@nextcloud/router'
|
||||
import { getRequestToken } from '@nextcloud/auth'
|
||||
import { loadState } from '@nextcloud/initial-state'
|
||||
import Vue from 'vue'
|
||||
|
||||
import AdminSettings from './components/settings/AdminSettings'
|
||||
import Nextcloud from './mixins/Nextcloud'
|
||||
|
||||
// eslint-disable-next-line camelcase
|
||||
__webpack_nonce__ = btoa(getRequestToken())
|
||||
// eslint-disable-next-line camelcase
|
||||
__webpack_public_path__ = generateFilePath('mail', '', 'js/')
|
||||
|
||||
Vue.mixin(Nextcloud)
|
||||
|
|
17
src/main.js
17
src/main.js
|
@ -1,4 +1,4 @@
|
|||
/*
|
||||
/**
|
||||
* @copyright Copyright (c) 2018 Christoph Wurst <christoph@winzerhof-wurst.at>
|
||||
*
|
||||
* @author Christoph Wurst <christoph@winzerhof-wurst.at>
|
||||
|
@ -21,9 +21,9 @@
|
|||
*/
|
||||
|
||||
import Vue from 'vue'
|
||||
import {getRequestToken} from '@nextcloud/auth'
|
||||
import {sync} from 'vuex-router-sync'
|
||||
import {generateFilePath} from '@nextcloud/router'
|
||||
import { getRequestToken } from '@nextcloud/auth'
|
||||
import { sync } from 'vuex-router-sync'
|
||||
import { generateFilePath } from '@nextcloud/router'
|
||||
import VueShortKey from 'vue-shortkey'
|
||||
import VTooltip from 'v-tooltip'
|
||||
|
||||
|
@ -31,16 +31,18 @@ import App from './App'
|
|||
import Nextcloud from './mixins/Nextcloud'
|
||||
import router from './router'
|
||||
import store from './store'
|
||||
import {fixAccountId} from './service/AccountService'
|
||||
import { fixAccountId } from './service/AccountService'
|
||||
|
||||
// eslint-disable-next-line camelcase
|
||||
__webpack_nonce__ = btoa(getRequestToken())
|
||||
// eslint-disable-next-line camelcase
|
||||
__webpack_public_path__ = generateFilePath('mail', '', 'js/')
|
||||
|
||||
sync(store, router)
|
||||
|
||||
Vue.mixin(Nextcloud)
|
||||
|
||||
Vue.use(VueShortKey, {prevent: ['input', 'div']})
|
||||
Vue.use(VueShortKey, { prevent: ['input', 'div'] })
|
||||
Vue.use(VTooltip)
|
||||
|
||||
const getPreferenceFromPage = (key) => {
|
||||
|
@ -70,11 +72,10 @@ store.commit('savePreference', {
|
|||
|
||||
const accounts = JSON.parse(atob(getPreferenceFromPage('serialized-accounts')))
|
||||
accounts.map(fixAccountId).forEach((account) => {
|
||||
const folders = account.folders
|
||||
store.commit('addAccount', account)
|
||||
})
|
||||
|
||||
new Vue({
|
||||
export default new Vue({
|
||||
el: '#content',
|
||||
router,
|
||||
store,
|
||||
|
|
|
@ -19,7 +19,7 @@
|
|||
* 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 {
|
||||
methods: {
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import Vue from 'vue'
|
||||
import Router from 'vue-router'
|
||||
import {generateUrl} from '@nextcloud/router'
|
||||
import { generateUrl } from '@nextcloud/router'
|
||||
|
||||
const AccountSettings = () => import('./views/AccountSettings')
|
||||
const Home = () => import('./views/Home')
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import {generateUrl} from '@nextcloud/router'
|
||||
import { generateUrl } from '@nextcloud/router'
|
||||
import axios from '@nextcloud/axios'
|
||||
|
||||
export const fixAccountId = (original) => {
|
||||
|
@ -25,7 +25,7 @@ export const create = (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,
|
||||
})
|
||||
|
||||
|
@ -36,7 +36,7 @@ export const patch = (account, data) => {
|
|||
}
|
||||
|
||||
export const update = (data) => {
|
||||
const url = generateUrl(`/apps/mail/api/accounts/{id}`, {
|
||||
const url = generateUrl('/apps/mail/api/accounts/{id}', {
|
||||
id: data.accountId,
|
||||
})
|
||||
|
||||
|
@ -47,7 +47,7 @@ export const update = (data) => {
|
|||
}
|
||||
|
||||
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,
|
||||
})
|
||||
const data = {
|
||||
|
@ -74,7 +74,7 @@ export const fetch = (id) => {
|
|||
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', {
|
||||
id,
|
||||
})
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
import {generateUrl} from '@nextcloud/router'
|
||||
import { generateUrl } from '@nextcloud/router'
|
||||
import axios from '@nextcloud/axios'
|
||||
|
||||
export const createAlias = async (account, data) => {
|
||||
const url = generateUrl(`/apps/mail/api/accounts/{id}/aliases`, {
|
||||
export const createAlias = async(account, data) => {
|
||||
const url = generateUrl('/apps/mail/api/accounts/{id}/aliases', {
|
||||
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}', {
|
||||
id: account.accountId,
|
||||
aliasId: alias.id,
|
||||
|
|
|
@ -20,7 +20,7 @@
|
|||
*/
|
||||
|
||||
import Axios from '@nextcloud/axios'
|
||||
import {generateUrl} from '@nextcloud/router'
|
||||
import { generateUrl } from '@nextcloud/router'
|
||||
|
||||
export function saveAttachmentToFiles(accountId, folderId, messageId, attachmentId, directory) {
|
||||
const url = generateUrl(
|
||||
|
@ -56,7 +56,7 @@ export const uploadLocalAttachment = (file, progress) => {
|
|||
|
||||
return Axios.post(url, data, opts)
|
||||
.then((resp) => resp.data)
|
||||
.then(({id}) => {
|
||||
.then(({ id }) => {
|
||||
return {
|
||||
file,
|
||||
id,
|
||||
|
|
|
@ -20,7 +20,7 @@
|
|||
*/
|
||||
|
||||
import Axios from '@nextcloud/axios'
|
||||
import {generateUrl} from '@nextcloud/router'
|
||||
import { generateUrl } from '@nextcloud/router'
|
||||
|
||||
export const findRecipient = (term) => {
|
||||
const url = generateUrl('/apps/mail/api/autoComplete?term={term}', {
|
||||
|
|
|
@ -22,7 +22,7 @@
|
|||
|
||||
import memoize from 'lodash/fp/memoize'
|
||||
import Axios from '@nextcloud/axios'
|
||||
import {generateUrl} from '@nextcloud/router'
|
||||
import { generateUrl } from '@nextcloud/router'
|
||||
|
||||
export const fetchAvatarUrl = (email) => {
|
||||
if (email === null) {
|
||||
|
@ -38,7 +38,7 @@ export const fetchAvatarUrl = (email) => {
|
|||
.then((avatar) => {
|
||||
if (avatar.isExternal) {
|
||||
return generateUrl('/apps/mail/api/avatars/image/{email}', {
|
||||
email: email,
|
||||
email,
|
||||
})
|
||||
} else {
|
||||
return avatar.url
|
||||
|
|
|
@ -17,10 +17,10 @@
|
|||
*
|
||||
*/
|
||||
|
||||
import {Client} from 'davclient.js'
|
||||
import { Client } from 'davclient.js'
|
||||
import ical from 'ical.js'
|
||||
import {getCurrentUser, getRequestToken} from '@nextcloud/auth'
|
||||
import {generateRemoteUrl} from '@nextcloud/router'
|
||||
import { getCurrentUser, getRequestToken } from '@nextcloud/auth'
|
||||
import { generateRemoteUrl } from '@nextcloud/router'
|
||||
import Axios from '@nextcloud/axios'
|
||||
|
||||
import Logger from '../logger'
|
||||
|
@ -54,15 +54,15 @@ const getResponseCodeFromHTTPResponse = (t) => {
|
|||
|
||||
const getACLFromResponse = (properties) => {
|
||||
let canWrite = false
|
||||
let acl = properties['{DAV:}acl']
|
||||
const acl = properties['{DAV:}acl']
|
||||
if (acl) {
|
||||
for (var k = 0; k < acl.length; k++) {
|
||||
var href = acl[k].getElementsByTagNameNS('DAV:', 'href')
|
||||
for (let k = 0; k < acl.length; k++) {
|
||||
let href = acl[k].getElementsByTagNameNS('DAV:', 'href')
|
||||
if (href.length === 0) {
|
||||
continue
|
||||
}
|
||||
href = href[0].textContent
|
||||
var writeNode = acl[k].getElementsByTagNameNS('DAV:', 'write')
|
||||
const writeNode = acl[k].getElementsByTagNameNS('DAV:', 'write')
|
||||
if (writeNode.length > 0) {
|
||||
canWrite = true
|
||||
}
|
||||
|
@ -86,7 +86,7 @@ const getCalendarData = (properties) => {
|
|||
|
||||
const components = properties['{urn:ietf:params:xml:ns:caldav}supported-calendar-component-set'] || []
|
||||
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)) {
|
||||
data.components[name] = true
|
||||
}
|
||||
|
@ -99,7 +99,7 @@ const getCalendarData = (properties) => {
|
|||
* @returns {Promise}
|
||||
*/
|
||||
export const getUserCalendars = () => {
|
||||
var url = generateRemoteUrl('dav/calendars') + '/' + getCurrentUser().uid + '/'
|
||||
const url = generateRemoteUrl('dav/calendars') + '/' + getCurrentUser().uid + '/'
|
||||
|
||||
return client
|
||||
.propFind(url, props, 1, {
|
||||
|
@ -156,7 +156,7 @@ const splitCalendar = (data) => {
|
|||
allObjects[componentName] = {}
|
||||
|
||||
vobjects.forEach((vobject) => {
|
||||
var uid = vobject.getFirstPropertyValue('uid')
|
||||
const uid = vobject.getFirstPropertyValue('uid')
|
||||
allObjects[componentName][uid] = allObjects[componentName][uid] || []
|
||||
allObjects[componentName][uid].push(vobject)
|
||||
})
|
||||
|
@ -165,11 +165,11 @@ const splitCalendar = (data) => {
|
|||
const split = []
|
||||
componentNames.forEach((componentName) => {
|
||||
split[componentName] = []
|
||||
for (let objectsId in allObjects[componentName]) {
|
||||
for (const objectsId in allObjects[componentName]) {
|
||||
const objects = allObjects[componentName][objectsId]
|
||||
const component = createICalElement()
|
||||
timezones.forEach(component.addSubcomponent.bind(component))
|
||||
for (let objectId in objects) {
|
||||
for (const objectId in objects) {
|
||||
component.addSubcomponent(objects[objectId])
|
||||
}
|
||||
split[componentName].push(component.toString())
|
||||
|
@ -184,8 +184,8 @@ const splitCalendar = (data) => {
|
|||
}
|
||||
|
||||
/**
|
||||
* @param {String} url
|
||||
* @param {Object} data
|
||||
* @param {String} url the url
|
||||
* @param {Object} data the data
|
||||
* @returns {Promise}
|
||||
*/
|
||||
export const importCalendarEvent = (url) => (data) => {
|
||||
|
@ -198,9 +198,9 @@ export const importCalendarEvent = (url) => (data) => {
|
|||
const file = splitCalendar(data)
|
||||
const components = ['vevent', 'vjournal', 'vtodo']
|
||||
components.forEach((componentName) => {
|
||||
for (let componentId in file.split[componentName]) {
|
||||
for (const componentId in file.split[componentName]) {
|
||||
const component = file.split[componentName][componentId]
|
||||
Logger.info('importing event component', {component})
|
||||
Logger.info('importing event component', { component })
|
||||
promises.push(
|
||||
Promise.resolve(
|
||||
Axios.put(url + getRandomString() + '.ics', component, {
|
||||
|
|
|
@ -26,8 +26,8 @@
|
|||
// Slightly modified for use in Mail
|
||||
|
||||
import axios from '@nextcloud/axios'
|
||||
import {generateOcsUrl} from '@nextcloud/router'
|
||||
import {showError} from '@nextcloud/dialogs'
|
||||
import { generateOcsUrl } from '@nextcloud/router'
|
||||
import { showError } from '@nextcloud/dialogs'
|
||||
|
||||
/**
|
||||
* 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
|
||||
* @returns {string} url share link
|
||||
*/
|
||||
const shareFile = async function (path, token) {
|
||||
const shareFile = async function(path, token) {
|
||||
try {
|
||||
const res = await axios.post(generateOcsUrl('apps/files_sharing/api/v1', 2) + 'shares', {
|
||||
shareType: 3, // OC.Share.SHARE_TYPE_LINK,
|
||||
|
@ -45,21 +45,21 @@ const shareFile = async function (path, token) {
|
|||
return res.data.ocs.data.url
|
||||
} catch (error) {
|
||||
if (
|
||||
error.response &&
|
||||
error.response.data &&
|
||||
error.response.data.ocs &&
|
||||
error.response.data.ocs.meta &&
|
||||
error.response.data.ocs.meta.message
|
||||
error.response
|
||||
&& error.response.data
|
||||
&& error.response.data.ocs
|
||||
&& error.response.data.ocs.meta
|
||||
&& error.response.data.ocs.meta.message
|
||||
) {
|
||||
console.error(`Error while sharing file: ${error.response.data.ocs.meta.message || 'Unknown error'}`)
|
||||
showError(error.response.data.ocs.meta.message)
|
||||
throw error
|
||||
} else {
|
||||
console.error(`Error while sharing file: Unknown error`)
|
||||
console.error('Error while sharing file: Unknown error')
|
||||
showError(t('mail', 'Error while sharing file'))
|
||||
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'
|
||||
|
||||
export function fetchAll(accountId) {
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
import {generateUrl} from '@nextcloud/router'
|
||||
import { generateUrl } from '@nextcloud/router'
|
||||
import axios from '@nextcloud/axios'
|
||||
import {curry, map} from 'ramda'
|
||||
import { curry, map } from 'ramda'
|
||||
|
||||
import {parseErrorResponse} from '../http/ErrorResponseParser'
|
||||
import {convertAxiosError} from '../errors/convert'
|
||||
import { parseErrorResponse } from '../http/ErrorResponseParser'
|
||||
import { convertAxiosError } from '../errors/convert'
|
||||
import SyncIncompleteError from '../errors/SyncIncompleteError'
|
||||
|
||||
const amendEnvelopeWithIds = curry((accountId, folderId, envelope) => ({
|
||||
|
@ -116,10 +116,7 @@ export function setEnvelopeFlag(accountId, folderId, uid, flag, value) {
|
|||
|
||||
return axios
|
||||
.put(url, {
|
||||
flags: flags,
|
||||
})
|
||||
.then(() => {
|
||||
value
|
||||
flags,
|
||||
})
|
||||
}
|
||||
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
/*
|
||||
/**
|
||||
* @copyright 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 {translate as t, translatePlural as n} from '@nextcloud/l10n'
|
||||
import {generateFilePath} from '@nextcloud/router'
|
||||
import { translate as t, translatePlural as n } from '@nextcloud/l10n'
|
||||
import { generateFilePath } from '@nextcloud/router'
|
||||
|
||||
import Logger from '../logger'
|
||||
|
||||
/**
|
||||
* @todo use Notification.requestPermission().then once all browsers support promise API
|
||||
*
|
||||
* @return {Promise}
|
||||
* @returns {Promise}
|
||||
*/
|
||||
const request = () => {
|
||||
if (!('Notification' in window)) {
|
||||
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') {
|
||||
return Promise.resolve()
|
||||
} else if (Notification.permission === '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')
|
||||
|
@ -49,13 +49,13 @@ const showNotification = (title, body, icon) => {
|
|||
request().then(() => {
|
||||
if (document.querySelector(':focus') !== null) {
|
||||
Logger.debug('browser is active. notification request is ignored')
|
||||
return
|
||||
|
||||
}
|
||||
})
|
||||
|
||||
const notification = new Notification(title, {
|
||||
body: body,
|
||||
icon: icon,
|
||||
body,
|
||||
icon,
|
||||
})
|
||||
notification.onclick = () => {
|
||||
window.focus()
|
||||
|
|
|
@ -21,7 +21,7 @@
|
|||
*/
|
||||
|
||||
import Axios from '@nextcloud/axios'
|
||||
import {generateUrl} from '@nextcloud/router'
|
||||
import { generateUrl } from '@nextcloud/router'
|
||||
|
||||
export const savePreference = (key, value) => {
|
||||
const url = generateUrl('/apps/mail/api/preferences/{key}', {
|
||||
|
|
|
@ -20,7 +20,7 @@
|
|||
*/
|
||||
|
||||
import axios from '@nextcloud/axios'
|
||||
import {generateUrl} from '@nextcloud/router'
|
||||
import { generateUrl } from '@nextcloud/router'
|
||||
|
||||
export const saveProvisioningSettings = (config) => {
|
||||
const url = generateUrl('/apps/mail/api/settings/provisioning')
|
||||
|
|
|
@ -40,7 +40,7 @@ import {
|
|||
where,
|
||||
} from 'ramda'
|
||||
|
||||
import {savePreference} from '../service/PreferenceService'
|
||||
import { savePreference } from '../service/PreferenceService'
|
||||
import {
|
||||
create as createAccount,
|
||||
deleteAccount,
|
||||
|
@ -50,7 +50,7 @@ import {
|
|||
update as updateAccount,
|
||||
updateSignature,
|
||||
} 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 {
|
||||
deleteMessage,
|
||||
fetchEnvelope,
|
||||
|
@ -59,16 +59,16 @@ import {
|
|||
setEnvelopeFlag,
|
||||
syncEnvelopes,
|
||||
} from '../service/MessageService'
|
||||
import {createAlias, deleteAlias} from '../service/AliasService'
|
||||
import { createAlias, deleteAlias } from '../service/AliasService'
|
||||
import logger from '../logger'
|
||||
import {normalizedEnvelopeListId} from './normalization'
|
||||
import {showNewMessagesNotification} from '../service/NotificationService'
|
||||
import {parseUuid} from '../util/EnvelopeUidParser'
|
||||
import {matchError} from '../errors/match'
|
||||
import { normalizedEnvelopeListId } from './normalization'
|
||||
import { showNewMessagesNotification } from '../service/NotificationService'
|
||||
import { parseUuid } from '../util/EnvelopeUidParser'
|
||||
import { matchError } from '../errors/match'
|
||||
import SyncIncompleteError from '../errors/SyncIncompleteError'
|
||||
import MailboxLockedError from '../errors/MailboxLockedError'
|
||||
import {wait} from '../util/wait'
|
||||
import {UNIFIED_ACCOUNT_ID, UNIFIED_INBOX_ID} from './constants'
|
||||
import { wait } from '../util/wait'
|
||||
import { UNIFIED_ACCOUNT_ID, UNIFIED_INBOX_ID } from './constants'
|
||||
|
||||
const PAGE_SIZE = 20
|
||||
|
||||
|
@ -87,27 +87,27 @@ const findIndividualFolders = curry((getFolders, specialRole) =>
|
|||
const combineEnvelopeLists = pipe(flatten, orderBy(prop('dateInt'), 'desc'))
|
||||
|
||||
export default {
|
||||
savePreference({commit, getters}, {key, value}) {
|
||||
return savePreference(key, value).then(({value}) => {
|
||||
savePreference({ commit, getters }, { key, value }) {
|
||||
return savePreference(key, value).then(({ value }) => {
|
||||
commit('savePreference', {
|
||||
key,
|
||||
value,
|
||||
})
|
||||
})
|
||||
},
|
||||
fetchAccounts({commit, getters}) {
|
||||
fetchAccounts({ commit, getters }) {
|
||||
return fetchAllAccounts().then((accounts) => {
|
||||
accounts.forEach((account) => commit('addAccount', account))
|
||||
return getters.accounts
|
||||
})
|
||||
},
|
||||
fetchAccount({commit}, id) {
|
||||
fetchAccount({ commit }, id) {
|
||||
return fetchAccount(id).then((account) => {
|
||||
commit('addAccount', account)
|
||||
return account
|
||||
})
|
||||
},
|
||||
createAccount({commit}, config) {
|
||||
createAccount({ commit }, config) {
|
||||
return createAccount(config).then((account) => {
|
||||
logger.debug(`account ${account.id} created, fetching folders …`, account)
|
||||
return fetchAllFolders(account.id)
|
||||
|
@ -119,42 +119,42 @@ export default {
|
|||
.then(() => account)
|
||||
})
|
||||
},
|
||||
updateAccount({commit}, config) {
|
||||
updateAccount({ commit }, config) {
|
||||
return updateAccount(config).then((account) => {
|
||||
console.debug('account updated', account)
|
||||
commit('editAccount', account)
|
||||
return account
|
||||
})
|
||||
},
|
||||
patchAccount({commit}, {account, data}) {
|
||||
patchAccount({ commit }, { account, data }) {
|
||||
return patchAccount(account, data).then((account) => {
|
||||
console.debug('account patched', account, data)
|
||||
commit('patchAccount', {account, data})
|
||||
commit('patchAccount', { account, data })
|
||||
return account
|
||||
})
|
||||
},
|
||||
updateAccountSignature({commit}, {account, signature}) {
|
||||
updateAccountSignature({ commit }, { account, signature }) {
|
||||
return updateSignature(account, signature).then(() => {
|
||||
console.debug('account signature updated')
|
||||
const updated = Object.assign({}, account, {signature})
|
||||
const updated = Object.assign({}, account, { signature })
|
||||
commit('editAccount', updated)
|
||||
return account
|
||||
})
|
||||
},
|
||||
deleteAccount({commit}, account) {
|
||||
deleteAccount({ commit }, account) {
|
||||
return deleteAccount(account.id).catch((err) => {
|
||||
console.error('could not delete account', err)
|
||||
throw err
|
||||
})
|
||||
},
|
||||
createFolder({commit}, {account, name}) {
|
||||
createFolder({ commit }, { account, name }) {
|
||||
return createFolder(account.id, name).then((folder) => {
|
||||
console.debug(`folder ${name} created for account ${account.id}`, {folder})
|
||||
commit('addFolder', {account, folder})
|
||||
console.debug(`folder ${name} created for account ${account.id}`, { folder })
|
||||
commit('addFolder', { account, folder })
|
||||
commit('expandAccount', account.id)
|
||||
})
|
||||
},
|
||||
moveAccount({commit, getters}, {account, up}) {
|
||||
moveAccount({ commit, getters }, { account, up }) {
|
||||
const accounts = getters.accounts
|
||||
const index = accounts.indexOf(account)
|
||||
if (up) {
|
||||
|
@ -171,12 +171,12 @@ export default {
|
|||
if (account.id === 0) {
|
||||
return
|
||||
}
|
||||
commit('saveAccountsOrder', {account, order: idx})
|
||||
return patchAccount(account, {order: idx})
|
||||
commit('saveAccountsOrder', { account, order: idx })
|
||||
return patchAccount(account, { order: idx })
|
||||
})
|
||||
)
|
||||
},
|
||||
markFolderRead({getters, dispatch}, {accountId, folderId}) {
|
||||
markFolderRead({ getters, dispatch }, { accountId, folderId }) {
|
||||
const folder = getters.getFolder(accountId, folderId)
|
||||
|
||||
if (folder.isUnified) {
|
||||
|
@ -194,13 +194,13 @@ export default {
|
|||
|
||||
return markFolderRead(accountId, folderId).then(
|
||||
dispatch('syncEnvelopes', {
|
||||
accountId: accountId,
|
||||
folderId: folderId,
|
||||
accountId,
|
||||
folderId,
|
||||
})
|
||||
)
|
||||
},
|
||||
fetchEnvelope({commit, getters}, uuid) {
|
||||
const {accountId, folderId, uid} = parseUuid(uuid)
|
||||
fetchEnvelope({ commit, getters }, uuid) {
|
||||
const { accountId, folderId, uid } = parseUuid(uuid)
|
||||
|
||||
const cached = getters.getEnvelope(accountId, folderId, uid)
|
||||
if (cached) {
|
||||
|
@ -222,7 +222,7 @@ export default {
|
|||
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)
|
||||
|
||||
if (folder.isUnified) {
|
||||
|
@ -275,7 +275,7 @@ export default {
|
|||
)
|
||||
)(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)
|
||||
|
||||
if (folder.isUnified) {
|
||||
|
@ -378,7 +378,7 @@ export default {
|
|||
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)
|
||||
|
||||
if (folder.isUnified) {
|
||||
|
@ -462,11 +462,11 @@ export default {
|
|||
return matchError(error, {
|
||||
[SyncIncompleteError.getName()]() {
|
||||
console.warn('(initial) sync is incomplete, retriggering')
|
||||
return dispatch('syncEnvelopes', {accountId, folderId, query, init})
|
||||
return dispatch('syncEnvelopes', { accountId, folderId, query, init })
|
||||
},
|
||||
[MailboxLockedError.getName()](error) {
|
||||
logger.info('Sync failed because the mailbox is locked, retriggering', {error})
|
||||
return wait(1500).then(() => dispatch('syncEnvelopes', {accountId, folderId, query, init}))
|
||||
logger.info('Sync failed because the mailbox is locked, retriggering', { error })
|
||||
return wait(1500).then(() => dispatch('syncEnvelopes', { accountId, folderId, query, init }))
|
||||
},
|
||||
default(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(
|
||||
getters.accounts
|
||||
.filter((a) => !a.isUnified)
|
||||
.map((account) => {
|
||||
return Promise.all(
|
||||
getters.getFolders(account.id).map(async (folder) => {
|
||||
getters.getFolders(account.id).map(async(folder) => {
|
||||
if (folder.specialRole !== 'inbox') {
|
||||
return
|
||||
}
|
||||
|
@ -510,7 +510,7 @@ export default {
|
|||
// Make sure the priority inbox is updated as well
|
||||
logger.info('updating priority inbox')
|
||||
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 list = folder.envelopeLists[normalizedEnvelopeListId(query)]
|
||||
if (list === undefined) {
|
||||
|
@ -531,7 +531,7 @@ export default {
|
|||
showNewMessagesNotification(newMessages)
|
||||
}
|
||||
},
|
||||
toggleEnvelopeFlagged({commit, getters}, envelope) {
|
||||
toggleEnvelopeFlagged({ commit, getters }, envelope) {
|
||||
// Change immediately and switch back on error
|
||||
const oldState = envelope.flags.flagged
|
||||
commit('flagEnvelope', {
|
||||
|
@ -551,7 +551,7 @@ export default {
|
|||
})
|
||||
})
|
||||
},
|
||||
toggleEnvelopeImportant({commit, getters}, envelope) {
|
||||
toggleEnvelopeImportant({ commit, getters }, envelope) {
|
||||
// Change immediately and switch back on error
|
||||
const oldState = envelope.flags.important
|
||||
commit('flagEnvelope', {
|
||||
|
@ -571,7 +571,7 @@ export default {
|
|||
})
|
||||
})
|
||||
},
|
||||
toggleEnvelopeSeen({commit, getters}, envelope) {
|
||||
toggleEnvelopeSeen({ commit, getters }, envelope) {
|
||||
// Change immediately and switch back on error
|
||||
const oldState = envelope.flags.seen
|
||||
commit('flagEnvelope', {
|
||||
|
@ -591,7 +591,7 @@ export default {
|
|||
})
|
||||
})
|
||||
},
|
||||
toggleEnvelopeJunk({commit, getters}, envelope) {
|
||||
toggleEnvelopeJunk({ commit, getters }, envelope) {
|
||||
// Change immediately and switch back on error
|
||||
const oldState = envelope.flags.junk
|
||||
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
|
||||
const oldState = envelope.flags.flagged
|
||||
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
|
||||
const oldState = envelope.flags.unseen
|
||||
commit('flagEnvelope', {
|
||||
|
@ -651,8 +651,8 @@ export default {
|
|||
})
|
||||
})
|
||||
},
|
||||
fetchMessage({commit}, uuid) {
|
||||
const {accountId, folderId, uid} = parseUuid(uuid)
|
||||
fetchMessage({ commit }, uuid) {
|
||||
const { accountId, folderId, uid } = parseUuid(uuid)
|
||||
return fetchMessage(accountId, folderId, uid).then((message) => {
|
||||
// Only commit if not undefined (not found)
|
||||
if (message) {
|
||||
|
@ -666,43 +666,43 @@ export default {
|
|||
return message
|
||||
})
|
||||
},
|
||||
replaceDraft({getters, commit}, {draft, uid, data}) {
|
||||
replaceDraft({ getters, commit }, { draft, uid, data }) {
|
||||
commit('updateDraft', {
|
||||
draft,
|
||||
data,
|
||||
newUid: uid,
|
||||
})
|
||||
},
|
||||
deleteMessage({getters, commit}, {accountId, folderId, uid}) {
|
||||
commit('removeEnvelope', {accountId, folderId, uid})
|
||||
deleteMessage({ getters, commit }, { accountId, folderId, uid }) {
|
||||
commit('removeEnvelope', { accountId, folderId, uid })
|
||||
|
||||
return deleteMessage(accountId, folderId, uid)
|
||||
.then(() => {
|
||||
const folder = getters.getFolder(accountId, folderId)
|
||||
if (!folder) {
|
||||
logger.error('could not find folder', {accountId, folderId})
|
||||
logger.error('could not find folder', { accountId, folderId })
|
||||
return
|
||||
}
|
||||
commit('removeMessage', {accountId, folder, uid})
|
||||
console.log('message removed')
|
||||
commit('removeMessage', { accountId, folder, uid })
|
||||
console.debug('message removed')
|
||||
})
|
||||
.catch((err) => {
|
||||
console.error('could not delete message', err)
|
||||
const envelope = getters.getEnvelope(accountId, folderId, uid)
|
||||
if (envelope) {
|
||||
commit('addEnvelope', {accountId, folderId, envelope})
|
||||
commit('addEnvelope', { accountId, folderId, envelope })
|
||||
} else {
|
||||
logger.error('could not find envelope', {accountId, folderId, uid})
|
||||
logger.error('could not find envelope', { accountId, folderId, uid })
|
||||
}
|
||||
throw err
|
||||
})
|
||||
},
|
||||
async createAlias({commit}, {account, aliasToAdd}) {
|
||||
async createAlias({ commit }, { 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)
|
||||
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
|
||||
* 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_INBOX_ID = btoa('inbox')
|
||||
|
|
|
@ -19,10 +19,10 @@
|
|||
* 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 {normalizedEnvelopeListId, normalizedFolderId, normalizedMessageId} from './normalization'
|
||||
import { UNIFIED_ACCOUNT_ID } from './constants'
|
||||
import { normalizedEnvelopeListId, normalizedFolderId, normalizedMessageId } from './normalization'
|
||||
|
||||
export const getters = {
|
||||
getPreference: (state) => (key, def) => {
|
||||
|
|
|
@ -30,7 +30,7 @@ import {
|
|||
PRIORITY_INBOX_UID,
|
||||
} from './constants'
|
||||
import actions from './actions'
|
||||
import {getters} from './getters'
|
||||
import { getters } from './getters'
|
||||
import mutations from './mutations'
|
||||
|
||||
Vue.use(Vuex)
|
||||
|
|
|
@ -23,11 +23,11 @@ import orderBy from 'lodash/fp/orderBy'
|
|||
import sortedUniqBy from 'lodash/fp/sortedUniqBy'
|
||||
import Vue from 'vue'
|
||||
|
||||
import {buildMailboxHierarchy} from '../imap/MailboxHierarchy'
|
||||
import {havePrefix} from '../imap/MailboxPrefix'
|
||||
import {normalizedFolderId, normalizedMessageId, normalizedEnvelopeListId} from './normalization'
|
||||
import {sortMailboxes} from '../imap/MailboxSorter'
|
||||
import {UNIFIED_ACCOUNT_ID} from './constants'
|
||||
import { buildMailboxHierarchy } from '../imap/MailboxHierarchy'
|
||||
import { havePrefix } from '../imap/MailboxPrefix'
|
||||
import { normalizedFolderId, normalizedMessageId, normalizedEnvelopeListId } from './normalization'
|
||||
import { sortMailboxes } from '../imap/MailboxSorter'
|
||||
import { UNIFIED_ACCOUNT_ID } from './constants'
|
||||
|
||||
const addFolderToState = (state, account) => (folder) => {
|
||||
const id = normalizedFolderId(account.id, folder.id)
|
||||
|
@ -43,7 +43,7 @@ const sortAccounts = (accounts) => {
|
|||
}
|
||||
|
||||
export default {
|
||||
savePreference(state, {key, value}) {
|
||||
savePreference(state, { key, value }) {
|
||||
Vue.set(state.preferences, key, value)
|
||||
},
|
||||
addAccount(state, account) {
|
||||
|
@ -70,10 +70,10 @@ export default {
|
|||
editAccount(state, 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))
|
||||
},
|
||||
saveAccountsOrder(state, {account, order}) {
|
||||
saveAccountsOrder(state, { account, order }) {
|
||||
Vue.set(account, 'order', order)
|
||||
Vue.set(
|
||||
state,
|
||||
|
@ -87,7 +87,7 @@ export default {
|
|||
expandAccount(state, accountId) {
|
||||
state.accounts[accountId].collapsed = false
|
||||
},
|
||||
addFolder(state, {account, folder}) {
|
||||
addFolder(state, { account, folder }) {
|
||||
// Flatten the existing ones before updating the hierarchy
|
||||
const existing = account.folders.map((id) => state.folders[id])
|
||||
existing.forEach((folder) => {
|
||||
|
@ -110,7 +110,7 @@ export default {
|
|||
account.folders.push(id)
|
||||
})
|
||||
},
|
||||
addEnvelope(state, {accountId, folderId, query, envelope}) {
|
||||
addEnvelope(state, { accountId, folderId, query, envelope }) {
|
||||
const folder = state.folders[normalizedFolderId(accountId, folderId)]
|
||||
Vue.set(state.envelopes, envelope.uuid, envelope)
|
||||
const listId = normalizedEnvelopeListId(query)
|
||||
|
@ -133,17 +133,17 @@ export default {
|
|||
)
|
||||
})
|
||||
},
|
||||
updateEnvelope(state, {envelope}) {
|
||||
updateEnvelope(state, { envelope }) {
|
||||
const existing = state.envelopes[envelope.uid]
|
||||
if (!existing) {
|
||||
return
|
||||
}
|
||||
Vue.set(existing, 'flags', envelope.flags)
|
||||
},
|
||||
flagEnvelope(state, {envelope, flag, value}) {
|
||||
flagEnvelope(state, { envelope, flag, value }) {
|
||||
envelope.flags[flag] = value
|
||||
},
|
||||
removeEnvelope(state, {accountId, folderId, uid}) {
|
||||
removeEnvelope(state, { accountId, folderId, uid }) {
|
||||
const folder = state.folders[normalizedFolderId(accountId, folderId)]
|
||||
for (const listId in folder.envelopeLists) {
|
||||
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)
|
||||
message.accountId = accountId
|
||||
message.folderId = folderId
|
||||
message.uuid = uuid
|
||||
Vue.set(state.messages, uuid, message)
|
||||
},
|
||||
updateDraft(state, {draft, data, newUid}) {
|
||||
updateDraft(state, { draft, data, newUid }) {
|
||||
// Update draft's UID
|
||||
const oldUid = draft.uid
|
||||
const uid = normalizedMessageId(draft.accountId, draft.folderId, newUid)
|
||||
|
@ -217,13 +217,13 @@ export default {
|
|||
Vue.set(state.envelopes, 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))
|
||||
},
|
||||
createAlias(state, {account, alias}) {
|
||||
createAlias(state, { account, alias }) {
|
||||
account.aliases.push(alias)
|
||||
},
|
||||
deleteAlias(state, {account, alias}) {
|
||||
deleteAlias(state, { account, alias }) {
|
||||
account.aliases.splice(account.aliases.indexOf(alias), 1)
|
||||
},
|
||||
}
|
||||
|
|
|
@ -19,7 +19,7 @@
|
|||
* 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) => {
|
||||
return `${accountId}-${folderId}`
|
||||
|
|
|
@ -20,8 +20,8 @@
|
|||
*
|
||||
*/
|
||||
|
||||
import {buildReplyBody, buildRecipients, buildReplySubject} from '../../ReplyBuilder'
|
||||
import {html, plain} from '../../util/text'
|
||||
import { buildReplyBody, buildRecipients, buildReplySubject } from '../../ReplyBuilder'
|
||||
import { html, plain } from '../../util/text'
|
||||
|
||||
describe('ReplyBuilder', () => {
|
||||
it('creates a reply body without any sender', () => {
|
||||
|
@ -51,7 +51,7 @@ describe('ReplyBuilder', () => {
|
|||
|
||||
let envelope
|
||||
|
||||
beforeEach(function () {
|
||||
beforeEach(function() {
|
||||
envelope = {}
|
||||
})
|
||||
|
||||
|
@ -93,7 +93,7 @@ describe('ReplyBuilder', () => {
|
|||
envelope.to = [b, c]
|
||||
envelope.cc = []
|
||||
|
||||
var reply = buildRecipients(envelope, b)
|
||||
const reply = buildRecipients(envelope, b)
|
||||
|
||||
assertSameAddressList(reply.from, [b])
|
||||
assertSameAddressList(reply.to, [a, c])
|
||||
|
@ -189,7 +189,7 @@ describe('ReplyBuilder', () => {
|
|||
envelope.to = [a, b]
|
||||
envelope.cc = []
|
||||
|
||||
var reply = buildRecipients(envelope, a)
|
||||
const reply = buildRecipients(envelope, a)
|
||||
|
||||
assertSameAddressList(reply.from, [a])
|
||||
assertSameAddressList(reply.to, [b])
|
||||
|
@ -219,7 +219,7 @@ describe('ReplyBuilder', () => {
|
|||
envelope.to = [a]
|
||||
envelope.cc = []
|
||||
|
||||
var reply = buildRecipients(envelope, a)
|
||||
const reply = buildRecipients(envelope, a)
|
||||
|
||||
assertSameAddressList(reply.from, [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 Address from '../../../components/Address.vue'
|
||||
|
|
|
@ -19,14 +19,14 @@
|
|||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import {getMailvelope} from '../../../crypto/mailvelope'
|
||||
import { getMailvelope } from '../../../crypto/mailvelope'
|
||||
|
||||
describe('mailvelope', () => {
|
||||
afterEach(() => {
|
||||
delete window.mailvelope
|
||||
})
|
||||
|
||||
it('loads statically', async () => {
|
||||
it('loads statically', async() => {
|
||||
window.mailvelope = {
|
||||
mock: 3,
|
||||
}
|
||||
|
@ -36,7 +36,7 @@ describe('mailvelope', () => {
|
|||
expect(mailvelope).to.deep.equal(window.mailvelope)
|
||||
})
|
||||
|
||||
it('loads dynamically', async () => {
|
||||
it('loads dynamically', async() => {
|
||||
const p = getMailvelope()
|
||||
window.mailvelope = {
|
||||
mock: 3,
|
||||
|
|
|
@ -19,8 +19,8 @@
|
|||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import {isPgpgMessage} from '../../../crypto/pgp'
|
||||
import {html, plain} from '../../../util/text'
|
||||
import { isPgpgMessage } from '../../../crypto/pgp'
|
||||
import { html, plain } from '../../../util/text'
|
||||
|
||||
describe('pgp', () => {
|
||||
it('detects non-pgp messages', () => {
|
||||
|
|
|
@ -19,7 +19,7 @@
|
|||
* 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', () => {
|
||||
it('ignores errors without a response', () => {
|
||||
|
|
|
@ -19,7 +19,7 @@
|
|||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import {matchError} from '../../../errors/match'
|
||||
import { matchError } from '../../../errors/match'
|
||||
|
||||
describe('match', () => {
|
||||
it('throws an error when nothing matches', (done) => {
|
||||
|
|
|
@ -19,7 +19,7 @@
|
|||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import {translate} from '../../../i18n/MailboxTranslator'
|
||||
import { translate } from '../../../i18n/MailboxTranslator'
|
||||
|
||||
describe('MailboxTranslator', () => {
|
||||
it('translates the inbox', () => {
|
||||
|
|
|
@ -19,7 +19,7 @@
|
|||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import {buildMailboxHierarchy} from '../../../imap/MailboxHierarchy'
|
||||
import { buildMailboxHierarchy } from '../../../imap/MailboxHierarchy'
|
||||
|
||||
describe('mailboxHierarchyBuilder', () => {
|
||||
it('handles empty collections', () => {
|
||||
|
|
|
@ -19,7 +19,7 @@
|
|||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import {havePrefix} from '../../../imap/MailboxPrefix'
|
||||
import { havePrefix } from '../../../imap/MailboxPrefix'
|
||||
|
||||
describe('MailboxPrefix', () => {
|
||||
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/>.
|
||||
*/
|
||||
|
||||
import {sortMailboxes} from '../../../imap/MailboxSorter'
|
||||
import { sortMailboxes } from '../../../imap/MailboxSorter'
|
||||
|
||||
describe('mailboxSorter', () => {
|
||||
it('sorts ordinary mailboxes', () => {
|
||||
|
|
|
@ -20,14 +20,14 @@
|
|||
*/
|
||||
|
||||
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 actions from '../../../store/actions'
|
||||
import * as MessageService from '../../../service/MessageService'
|
||||
import * as NotificationService from '../../../service/NotificationService'
|
||||
import {normalizedMessageId} from '../../../store/normalization'
|
||||
import {UNIFIED_ACCOUNT_ID, UNIFIED_INBOX_ID} from '../../../store/constants'
|
||||
import { normalizedMessageId } from '../../../store/normalization'
|
||||
import { UNIFIED_ACCOUNT_ID, UNIFIED_INBOX_ID } from '../../../store/constants'
|
||||
|
||||
const mockEnvelope = curry((accountId, folderId, uid) => ({
|
||||
accountId,
|
||||
|
@ -71,7 +71,7 @@ describe('Vuex store actions', () => {
|
|||
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({
|
||||
id: 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({
|
||||
id: 13,
|
||||
accountId: 13,
|
||||
|
@ -179,7 +179,7 @@ describe('Vuex store actions', () => {
|
|||
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 page2 = reverse(range(30, 35))
|
||||
const msgs1 = reverse(range(10, 30))
|
||||
|
@ -243,7 +243,7 @@ describe('Vuex store actions', () => {
|
|||
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 page2 = reverse(range(30, 35))
|
||||
const msgs1 = reverse(range(25, 30))
|
||||
|
@ -325,7 +325,7 @@ describe('Vuex store actions', () => {
|
|||
sinon.restore()
|
||||
})
|
||||
|
||||
it('fetches the inbox first', async () => {
|
||||
it('fetches the inbox first', async() => {
|
||||
context.getters.accounts.push({
|
||||
id: 13,
|
||||
accountId: 13,
|
||||
|
@ -392,7 +392,7 @@ describe('Vuex store actions', () => {
|
|||
expect(NotificationService.showNewMessagesNotification).not.have.been.called
|
||||
})
|
||||
|
||||
it('syncs each individual mailbox', async () => {
|
||||
it('syncs each individual mailbox', async() => {
|
||||
context.getters.accounts.push({
|
||||
id: 13,
|
||||
accountId: 13,
|
||||
|
@ -448,7 +448,7 @@ describe('Vuex store actions', () => {
|
|||
accountId: 13,
|
||||
folderId: 'INBOX',
|
||||
})
|
||||
.returns(Promise.resolve([{id: 123}, {id: 321}]))
|
||||
.returns(Promise.resolve([{ id: 123 }, { id: 321 }]))
|
||||
|
||||
await actions.syncInboxes(context)
|
||||
|
||||
|
|
|
@ -19,9 +19,9 @@
|
|||
* 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))
|
||||
|
||||
|
|
|
@ -19,7 +19,7 @@
|
|||
* 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', () => {
|
||||
it('creates a unique folder ID', () => {
|
||||
|
|
|
@ -19,7 +19,7 @@
|
|||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import {parseUuid} from '../../../util/EnvelopeUidParser'
|
||||
import { parseUuid } from '../../../util/EnvelopeUidParser'
|
||||
|
||||
describe('EnvelopeUidParser', () => {
|
||||
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('toPlain', () => {
|
||||
|
@ -89,9 +89,9 @@ describe('text', () => {
|
|||
|
||||
it('converts deeply nested elements to text', () => {
|
||||
const source = html(
|
||||
'<html>' +
|
||||
'<body><p>Hello!</p><p>this <i>is</i> <b>some</b> random <strong>text</strong></p></body>' +
|
||||
'</html>'
|
||||
'<html>'
|
||||
+ '<body><p>Hello!</p><p>this <i>is</i> <b>some</b> random <strong>text</strong></p></body>'
|
||||
+ '</html>'
|
||||
)
|
||||
const expected = plain('Hello!\n\nthis is some random text')
|
||||
|
||||
|
@ -111,7 +111,7 @@ describe('text', () => {
|
|||
|
||||
it('preserves quotes', () => {
|
||||
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.
|
||||
>
|
||||
|
|
|
@ -19,7 +19,7 @@
|
|||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import {wait} from '../../../util/wait'
|
||||
import { wait } from '../../../util/wait'
|
||||
|
||||
describe('wait', () => {
|
||||
it('waits', (done) => {
|
||||
|
|
|
@ -38,8 +38,8 @@ const flattenError = (error) => {
|
|||
}
|
||||
|
||||
const flattenTrace = (trace) => {
|
||||
return trace.reduce(function (acc, entry) {
|
||||
var text = ''
|
||||
return trace.reduce(function(acc, entry) {
|
||||
let text = ''
|
||||
if (entry.class) {
|
||||
text += ' at ' + entry.class + '::' + entry.function
|
||||
} else {
|
||||
|
@ -54,22 +54,22 @@ const flattenTrace = (trace) => {
|
|||
|
||||
export const getReportUrl = (error) => {
|
||||
console.error(error)
|
||||
var message = error.message || 'An unkown error occurred.'
|
||||
let message = error.message || 'An unkown error occurred.'
|
||||
if (!message.endsWith('.')) {
|
||||
message += '.'
|
||||
}
|
||||
var builder = new IssueTemplateBuilder()
|
||||
var template = builder
|
||||
const builder = new IssueTemplateBuilder()
|
||||
const template = builder
|
||||
.addEmptyStepsToReproduce()
|
||||
.addExpectedActualBehaviour()
|
||||
.addLogs('Error', flattenError(error))
|
||||
.render()
|
||||
|
||||
return (
|
||||
'https://github.com/nextcloud/mail/issues/new' +
|
||||
'?title=' +
|
||||
encodeURIComponent(message) +
|
||||
'&body=' +
|
||||
encodeURIComponent(template)
|
||||
'https://github.com/nextcloud/mail/issues/new'
|
||||
+ '?title='
|
||||
+ encodeURIComponent(message)
|
||||
+ '&body='
|
||||
+ encodeURIComponent(template)
|
||||
)
|
||||
}
|
||||
|
|
|
@ -17,7 +17,7 @@
|
|||
*
|
||||
*/
|
||||
|
||||
import {translate as t} from '@nextcloud/l10n'
|
||||
import { translate as t } from '@nextcloud/l10n'
|
||||
|
||||
const smileys = [':-(', ':-/', ':-\\', ':-|', ":'-(", ":'-/", ":'-\\", ":'-|"]
|
||||
|
||||
|
@ -26,7 +26,7 @@ const getRandomSmiley = () => {
|
|||
}
|
||||
|
||||
/**
|
||||
* @param {Folder} folder
|
||||
* @param {Folder} folder a folder
|
||||
* @returns {string}
|
||||
*/
|
||||
export const getRandomFolderErrorMessage = (folder) => {
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
/*
|
||||
/**
|
||||
* @copyright 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/>.
|
||||
*/
|
||||
|
||||
import {curry} from 'ramda'
|
||||
import {fromString} from 'html-to-text'
|
||||
import { curry } from 'ramda'
|
||||
import { fromString } from 'html-to-text'
|
||||
|
||||
/**
|
||||
* @type {Text}
|
||||
*/
|
||||
class Text {
|
||||
|
||||
constructor(format, value) {
|
||||
this.format = format
|
||||
this.value = value
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {Text} other
|
||||
* @return {Text}
|
||||
* @param {Text} other other
|
||||
* @returns {Text}
|
||||
*/
|
||||
append(other) {
|
||||
if (this.format !== other.format) {
|
||||
|
@ -42,6 +43,7 @@ class Text {
|
|||
|
||||
return new Text(this.format, this.value + other.value)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -99,8 +101,8 @@ export const isPlain = isFormat('plain')
|
|||
export const isHtml = isFormat('html')
|
||||
|
||||
/**
|
||||
* @param {Text} text
|
||||
* @return {Text}
|
||||
* @param {Text} text text
|
||||
* @returns {Text}
|
||||
*/
|
||||
export const toPlain = (text) => {
|
||||
if (text.format === 'plain') {
|
||||
|
@ -114,12 +116,12 @@ export const toPlain = (text) => {
|
|||
ignoreImage: true,
|
||||
wordwrap: false,
|
||||
format: {
|
||||
blockquote: function (element, fn, options) {
|
||||
blockquote(element, fn, options) {
|
||||
return fn(element.children, options)
|
||||
.replace(/\n\n\n/g, '\n\n') // remove triple line breaks
|
||||
.replace(/^/gm, '> ') // add > quotation to each line
|
||||
},
|
||||
paragraph: function (element, fn, options) {
|
||||
paragraph(element, fn, options) {
|
||||
return fn(element.children, options) + '\n\n'
|
||||
},
|
||||
},
|
||||
|
@ -134,8 +136,8 @@ export const toPlain = (text) => {
|
|||
}
|
||||
|
||||
/**
|
||||
* @param {Text} text
|
||||
* @return {Text}
|
||||
* @param {Text} text text
|
||||
* @returns {Text}
|
||||
*/
|
||||
export const toHtml = (text) => {
|
||||
if (text.format === 'html') {
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
/*
|
||||
/**
|
||||
* @copyright 2020 Christoph Wurst <christoph@winzerhof-wurst.at>
|
||||
*
|
||||
* @author 2020 Christoph Wurst <christoph@winzerhof-wurst.at>
|
||||
|
@ -20,7 +20,7 @@
|
|||
*/
|
||||
|
||||
export const wait = (ms) => {
|
||||
return new Promise((res) => {
|
||||
setTimeout(res, ms)
|
||||
return new Promise((resolve) => {
|
||||
setTimeout(resolve, ms)
|
||||
})
|
||||
}
|
||||
|
|
|
@ -10,8 +10,7 @@
|
|||
v-if="!account.provisioned"
|
||||
class="button icon-rename"
|
||||
href="#account-form"
|
||||
:title="t('mail', 'Change name')"
|
||||
></a>
|
||||
:title="t('mail', 'Change name')" />
|
||||
</p>
|
||||
<AliasSettings v-if="!account.provisioned" :account="account" />
|
||||
</div>
|
||||
|
@ -25,8 +24,7 @@
|
|||
:display-name="displayName"
|
||||
:email="email"
|
||||
:save="onSave"
|
||||
:account="account"
|
||||
/>
|
||||
:account="account" />
|
||||
</div>
|
||||
</div>
|
||||
</AppContent>
|
||||
|
@ -71,7 +69,7 @@ export default {
|
|||
},
|
||||
methods: {
|
||||
onSave(data) {
|
||||
Logger.log('saving data', {data})
|
||||
Logger.log('saving data', { data })
|
||||
return this.$store
|
||||
.dispatch('updateAccount', {
|
||||
...data,
|
||||
|
@ -79,7 +77,7 @@ export default {
|
|||
})
|
||||
.then((account) => account)
|
||||
.catch((error) => {
|
||||
Logger.error('account update failed:', {error})
|
||||
Logger.error('account update failed:', { error })
|
||||
|
||||
throw error
|
||||
})
|
||||
|
|
|
@ -35,12 +35,12 @@ export default {
|
|||
watch: {
|
||||
$route(to, from) {
|
||||
if (
|
||||
from.name === 'message' &&
|
||||
to.name === 'folder' &&
|
||||
!this.isMobile &&
|
||||
Number.parseInt(from.params.accountId, 10) === Number.parseInt(to.params.accountId, 10) &&
|
||||
from.params.folderId === to.params.folderId &&
|
||||
from.params.filter === to.params.filter
|
||||
from.name === 'message'
|
||||
&& to.name === 'folder'
|
||||
&& !this.isMobile
|
||||
&& Number.parseInt(from.params.accountId, 10) === Number.parseInt(to.params.accountId, 10)
|
||||
&& from.params.folderId === to.params.folderId
|
||||
&& 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", {
|
||||
to,
|
||||
|
@ -55,9 +55,9 @@ export default {
|
|||
|
||||
if (this.$route.name === 'home' && accounts.length > 1) {
|
||||
// Show first account
|
||||
let firstAccount = accounts[0]
|
||||
const firstAccount = accounts[0]
|
||||
// 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)
|
||||
|
||||
|
@ -80,9 +80,9 @@ export default {
|
|||
}
|
||||
|
||||
// Show first account
|
||||
let firstAccount = accounts[0]
|
||||
const firstAccount = accounts[0]
|
||||
// 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)
|
||||
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
<Content app-name="mail">
|
||||
<Navigation v-if="hasAccounts" />
|
||||
<div id="emptycontent">
|
||||
<div class="icon-mail"></div>
|
||||
<div class="icon-mail" />
|
||||
<h2>{{ t('mail', 'Connect your mail account') }}</h2>
|
||||
<AccountForm :display-name="displayName" :email="email" :save="onSave">
|
||||
<template v-if="error" #feedback class="warning">
|
||||
|
@ -15,8 +15,8 @@
|
|||
|
||||
<script>
|
||||
import Content from '@nextcloud/vue/dist/Components/Content'
|
||||
import {loadState} from '@nextcloud/initial-state'
|
||||
import {translate as t} from '@nextcloud/l10n'
|
||||
import { loadState } from '@nextcloud/initial-state'
|
||||
import { translate as t } from '@nextcloud/l10n'
|
||||
|
||||
import AccountForm from '../components/AccountForm'
|
||||
import Navigation from '../components/Navigation'
|
||||
|
@ -56,7 +56,7 @@ export default {
|
|||
return account
|
||||
})
|
||||
.catch((error) => {
|
||||
logger.error('Could not create account', {error})
|
||||
logger.error('Could not create account', { error })
|
||||
|
||||
if (error.message) {
|
||||
this.error = error.message
|
||||
|
|
|
@ -48,7 +48,6 @@ module.exports = {
|
|||
{
|
||||
test: /\.js$/,
|
||||
loader: 'babel-loader',
|
||||
exclude: /node_modules/,
|
||||
exclude: /node_modules(?!(\/|\\)(@ckeditor)(\/|\\))/
|
||||
},
|
||||
{
|
||||
|
|
Загрузка…
Ссылка в новой задаче