Merge pull request #2894 from nextcloud/enh/eslint

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

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

@ -1,28 +1,7 @@
module.exports = {
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,

10644
package-lock.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>&gt; ' + original.value.replace(/\n/g, '<br>&gt; ')
const htmlBody = `<blockquote>${original.value}</blockquote>`
switch (original.format) {
case 'plain':
const plainBody = '<br>&gt; ' + original.value.replace(/\n/g, '<br>&gt; ')
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> &lt;{{ curAlias.alias }}&gt;
<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)

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

@ -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)(\/|\\))/
},
{