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

github.com/nextcloud/mail.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorChristoph Wurst <ChristophWurst@users.noreply.github.com>2019-03-06 12:53:05 +0300
committerGitHub <noreply@github.com>2019-03-06 12:53:05 +0300
commitd695f42d635585328507a123c0e147521701ea4d (patch)
tree76eadc2feba3d264ac837e71f04f232267afdfc1
parent6f6a81b6d976d00c1e0e94d76768c4607b69a96c (diff)
parent4413d44e3dd6193890caa14ad6abbc34665b8484 (diff)
Merge pull request #1507 from nextcloud/feature/account-settingsv0.12.0-beta5
Add account settings page
-rw-r--r--lib/Controller/AccountsController.php16
-rw-r--r--package-lock.json5
-rw-r--r--package.json1
-rw-r--r--src/components/AccountForm.vue762
-rw-r--r--src/service/AccountService.js16
-rw-r--r--src/store/actions.js9
-rw-r--r--src/store/actions.js.orig865
-rw-r--r--src/store/mutations.js7
-rw-r--r--src/views/AccountSettings.vue116
-rw-r--r--tests/Controller/AccountsControllerTest.php4
10 files changed, 1391 insertions, 410 deletions
diff --git a/lib/Controller/AccountsController.php b/lib/Controller/AccountsController.php
index 88d2d6f75..15af56962 100644
--- a/lib/Controller/AccountsController.php
+++ b/lib/Controller/AccountsController.php
@@ -153,7 +153,21 @@ class AccountsController extends Controller {
* @return JSONResponse
* @throws ClientException
*/
- public function update(int $id, string $accountName, string $emailAddress, string $password, string $imapHost, int $imapPort, string $imapSslMode, string $imapUser, string $imapPassword, string $smtpHost, int $smtpPort, string $smtpSslMode, string $smtpUser, string $smtpPassword, bool $autoDetect): JSONResponse {
+ public function update(int $id,
+ bool $autoDetect,
+ string $accountName,
+ string $emailAddress,
+ string $password = null,
+ string $imapHost = null,
+ int $imapPort = null,
+ string $imapSslMode = null,
+ string $imapUser = null,
+ string $imapPassword = null,
+ string $smtpHost = null,
+ int $smtpPort = null,
+ string $smtpSslMode = null,
+ string $smtpUser = null,
+ string $smtpPassword = null): JSONResponse {
$account = null;
$errorMessage = null;
try {
diff --git a/package-lock.json b/package-lock.json
index 02b15ab39..c7948d7e3 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -9533,6 +9533,11 @@
"integrity": "sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c=",
"dev": true
},
+ "underscore": {
+ "version": "1.9.1",
+ "resolved": "https://registry.npmjs.org/underscore/-/underscore-1.9.1.tgz",
+ "integrity": "sha512-5/4etnCkd9c8gwgowi5/om/mYO5ajCaOgdzj/oW+0eQV9WxKBDZw5+ycmKmeaTXjInS/W0BzpGLo2xR2aBwZdg=="
+ },
"unicode-canonical-property-names-ecmascript": {
"version": "1.0.4",
"resolved": "https://registry.npmjs.org/unicode-canonical-property-names-ecmascript/-/unicode-canonical-property-names-ecmascript-1.0.4.tgz",
diff --git a/package.json b/package.json
index f83f9b6ae..b257ba09f 100644
--- a/package.json
+++ b/package.json
@@ -29,6 +29,7 @@
"nextcloud-vue": "^0.8.0",
"nextcloud_issuetemplate_builder": "^0.1.0",
"remove": "^0.1.5",
+ "underscore": "^1.9.1",
"v-tooltip": "^2.0.0-rc.33",
"vue": "^2.6.8",
"vue-autosize": "^1.0.2",
diff --git a/src/components/AccountForm.vue b/src/components/AccountForm.vue
index 9aa35f657..f556c6b4d 100644
--- a/src/components/AccountForm.vue
+++ b/src/components/AccountForm.vue
@@ -1,398 +1,432 @@
<template>
- <div id="account-form">
- <tabs :options="{ useUrlFragment: false, defaultTabHash: 'auto' }"
- @changed="onModeChanged"
- cache-lifetime="0">
- <tab :name="t('mail', 'Auto')"
- id="auto"
- key="auto">
- <label for="auto-name">
- {{ t('mail', 'Name') }}
- </label>
- <input type="text"
- id="auto-name"
- :placeholder="t('mail', 'Name')"
- v-model="autoConfig.accountName"
- :disabled="loading"
- autofocus/>
- <label for="auto-address">
- {{ t('mail', 'Mail Address') }}
- </label>
- <input type="email"
- id="auto-address"
- :placeholder="t('mail', 'Mail Address')"
- v-model="autoConfig.emailAddress"
- :disabled="loading"
- required/>
- <label for="auto-password">
- {{ t('mail', 'Password') }}
- </label>
- <input type="password"
- id="auto-password"
- :placeholder="t('mail', 'Password')"
- v-model="autoConfig.password"
- :disabled="loading"
- required/>
- </tab>
- <tab :name="t('mail', 'Manual')"
- id="manual"
- key="manual">
- <label for="man-name">
- {{ t('mail', 'Name') }}
- </label>
- <input type="text"
- id="man-name"
- :placeholder="t('mail', 'Name')"
- v-model="manualConfig.accountName"
- :disabled="loading"
- autofocus/>
- <label for="man-address">
- {{ t('mail', 'Mail Address') }}
- </label>
- <input type="email"
- id="man-address"
- :placeholder="t('mail', 'Mail Address')"
- v-model="manualConfig.emailAddress"
- :disabled="loading"
- required/>
+ <div id="account-form">
+ <tabs
+ :options="{ useUrlFragment: false, defaultTabHash: settingsPage ? 'manual' : 'auto' }"
+ @changed="onModeChanged"
+ cache-lifetime="0"
+ >
+ <tab :name="t('mail', 'Auto')" id="auto" key="auto">
+ <label for="auto-name">{{ t('mail', 'Name') }}</label>
+ <input
+ type="text"
+ id="auto-name"
+ :placeholder="t('mail', 'Name')"
+ v-model="autoConfig.accountName"
+ :disabled="loading"
+ autofocus
+ >
+ <label for="auto-address">{{ t('mail', 'Mail Address') }}</label>
+ <input
+ type="email"
+ id="auto-address"
+ :placeholder="t('mail', 'Mail Address')"
+ v-model="autoConfig.emailAddress"
+ :disabled="loading"
+ required
+ >
+ <label for="auto-password">{{ t('mail', 'Password') }}</label>
+ <input
+ type="password"
+ id="auto-password"
+ :placeholder="t('mail', 'Password')"
+ v-model="autoConfig.password"
+ :disabled="loading"
+ required
+ >
+ </tab>
+ <tab :name="t('mail', 'Manual')" id="manual" key="manual">
+ <label for="man-name">{{ t('mail', 'Name') }}</label>
+ <input
+ type="text"
+ id="man-name"
+ :placeholder="t('mail', 'Name')"
+ v-model="manualConfig.accountName"
+ :disabled="loading"
+ autofocus
+ >
+ <label for="man-address">{{ t('mail', 'Mail Address') }}</label>
+ <input
+ type="email"
+ id="man-address"
+ :placeholder="t('mail', 'Mail Address')"
+ v-model="manualConfig.emailAddress"
+ :disabled="loading"
+ required
+ >
- <h3>{{ t('mail', 'IMAP Settings') }}</h3>
- <label for="man-imap-host">
- {{ t('mail', 'IMAP Host') }}
- </label>
- <input type="text"
- id="man-imap-host"
- :placeholder="t('mail', 'IMAP Host')"
- v-model="manualConfig.imapHost"
- :disabled="loading"
- required/>
- <h4>{{ t('mail', 'IMAP Security') }}</h4>
- <div class="flex-row">
- <input type="radio"
- id="man-imap-sec-none"
- name="man-imap-sec"
- v-model="manualConfig.imapSslMode"
- :disabled="loading"
- @change="onImapSslModeChange"
- value="none">
- <label class="button"
- for="man-imap-sec-none"
- :class="{primary: manualConfig.imapSslMode === 'none' }">
- {{ t('mail', 'None') }}
- </label>
- <input type="radio"
- id="man-imap-sec-ssl"
- name="man-imap-sec"
- v-model="manualConfig.imapSslMode"
- :disabled="loading"
- @change="onImapSslModeChange"
- value="ssl">
- <label class="button"
- for="man-imap-sec-ssl"
- :class="{primary: manualConfig.imapSslMode === 'ssl' }">
- {{ t('mail', 'SSL/TLS') }}
- </label>
- <input type="radio"
- id="man-imap-sec-tls"
- name="man-imap-sec"
- v-model="manualConfig.imapSslMode"
- :disabled="loading"
- @change="onImapSslModeChange"
- value="tls">
- <label class="button"
- for="man-imap-sec-tls"
- :class="{primary: manualConfig.imapSslMode === 'tls' }">
- {{ t('mail', 'STARTTLS') }}
- </label>
- </div>
- <label for="man-imap-port">
- {{ t('mail', 'IMAP Port') }}
- </label>
- <input type="number"
- id="man-imap-port"
- :placeholder="t('mail', 'IMAP Port')"
- v-model="manualConfig.imapPort"
- :disabled="loading"
- required/>
- <label for="man-imap-user">
- {{ t('mail', 'IMAP User') }}
- </label>
- <input type="text"
- id="man-imap-user"
- :placeholder="t('mail', 'IMAP User')"
- v-model="manualConfig.imapUser"
- :disabled="loading"
- required/>
- <label for="man-imap-password">
- {{ t('mail', 'IMAP Password') }}
- </label>
- <input type="password"
- id="man-imap-password"
- :placeholder="t('mail', 'IMAP Password')"
- v-model="manualConfig.imapPassword"
- :disabled="loading"
- required/>
+ <h3>{{ t('mail', 'IMAP Settings') }}</h3>
+ <label for="man-imap-host">{{ t('mail', 'IMAP Host') }}</label>
+ <input
+ type="text"
+ id="man-imap-host"
+ :placeholder="t('mail', 'IMAP Host')"
+ v-model="manualConfig.imapHost"
+ :disabled="loading"
+ required
+ >
+ <h4>{{ t('mail', 'IMAP Security') }}</h4>
+ <div class="flex-row">
+ <input
+ type="radio"
+ id="man-imap-sec-none"
+ name="man-imap-sec"
+ v-model="manualConfig.imapSslMode"
+ :disabled="loading"
+ @change="onImapSslModeChange"
+ value="none"
+ >
+ <label
+ class="button"
+ for="man-imap-sec-none"
+ :class="{primary: manualConfig.imapSslMode === 'none' }"
+ >{{ t('mail', 'None') }}</label>
+ <input
+ type="radio"
+ id="man-imap-sec-ssl"
+ name="man-imap-sec"
+ v-model="manualConfig.imapSslMode"
+ :disabled="loading"
+ @change="onImapSslModeChange"
+ value="ssl"
+ >
+ <label
+ class="button"
+ for="man-imap-sec-ssl"
+ :class="{primary: manualConfig.imapSslMode === 'ssl' }"
+ >{{ t('mail', 'SSL/TLS') }}</label>
+ <input
+ type="radio"
+ id="man-imap-sec-tls"
+ name="man-imap-sec"
+ v-model="manualConfig.imapSslMode"
+ :disabled="loading"
+ @change="onImapSslModeChange"
+ value="tls"
+ >
+ <label
+ class="button"
+ for="man-imap-sec-tls"
+ :class="{primary: manualConfig.imapSslMode === 'tls' }"
+ >{{ t('mail', 'STARTTLS') }}</label>
+ </div>
+ <label for="man-imap-port">{{ t('mail', 'IMAP Port') }}</label>
+ <input
+ type="number"
+ id="man-imap-port"
+ :placeholder="t('mail', 'IMAP Port')"
+ v-model="manualConfig.imapPort"
+ :disabled="loading"
+ required
+ >
+ <label for="man-imap-user">{{ t('mail', 'IMAP User') }}</label>
+ <input
+ type="text"
+ id="man-imap-user"
+ :placeholder="t('mail', 'IMAP User')"
+ v-model="manualConfig.imapUser"
+ :disabled="loading"
+ required
+ >
+ <label for="man-imap-password">{{ t('mail', 'IMAP Password') }}</label>
+ <input
+ type="password"
+ id="man-imap-password"
+ :placeholder="t('mail', 'IMAP Password')"
+ v-model="manualConfig.imapPassword"
+ :disabled="loading"
+ required
+ >
- <h3>{{ t('mail', 'SMTP Settings') }}</h3>
- <input type="text"
- ref="smtpHost"
- name="smtp-host"
- :placeholder="t('mail', 'SMTP Host')"
- v-model="manualConfig.smtpHost"
- :disabled="loading"
- required/>
- <h4>{{ t('mail', 'SMTP Security') }}</h4>
- <div class="flex-row">
- <input type="radio"
- id="man-smtp-sec-none"
- name="man-smtp-sec"
- v-model="manualConfig.smtpSslMode"
- :disabled="loading"
- @change="onSmtpSslModeChange"
- value="none">
- <label class="button"
- for="man-smtp-sec-none"
- :class="{primary: manualConfig.smtpSslMode === 'none' }">
- {{ t('mail', 'None') }}
- </label>
- <input type="radio"
- id="man-smtp-sec-ssl"
- name="man-smtp-sec"
- v-model="manualConfig.smtpSslMode"
- :disabled="loading"
- @change="onSmtpSslModeChange"
- value="ssl">
- <label class="button"
- for="man-smtp-sec-ssl"
- :class="{primary: manualConfig.smtpSslMode === 'ssl' }">
- {{ t('mail', 'SSL/TLS') }}
- </label>
- <input type="radio"
- id="man-smtp-sec-tls"
- name="man-smtp-sec"
- v-model="manualConfig.smtpSslMode"
- :disabled="loading"
- @change="onSmtpSslModeChange"
- value="tls">
- <label class="button"
- for="man-smtp-sec-tls"
- :class="{primary: manualConfig.smtpSslMode === 'tls' }">
- {{ t('mail', 'STARTTLS') }}
- </label>
- </div>
- <label for="man-smtp-port">
- {{ t('mail', 'SMTP Port') }}
- </label>
- <input type="number"
- id="man-smtp-port"
- :placeholder="t('mail', 'SMTP Port')"
- v-model="manualConfig.smtpPort"
- :disabled="loading"
- required/>
- <label for="man-smtp-user">
- {{ t('mail', 'SMTP User') }}
- </label>
- <input type="text"
- id="man-smtp-user"
- :placeholder="t('mail', 'SMTP User')"
- v-model="manualConfig.smtpUser"
- :disabled="loading"
- required/>
- <label for="man-smtp-password">
- {{ t('mail', 'SMTP Password') }}
- </label>
- <input type="password"
- id="man-smtp-password"
- :placeholder="t('mail', 'SMTP Password')"
- v-model="manualConfig.smtpPassword"
- :disabled="loading"
- required/>
- </tab>
- </tabs>
- <input type="submit"
- class="primary"
- v-on:click="onSubmit"
- :disabled="loading"
- :value="t('mail', 'Connect')"/>
- </div>
+ <h3>{{ t('mail', 'SMTP Settings') }}</h3>
+ <input
+ type="text"
+ ref="smtpHost"
+ name="smtp-host"
+ :placeholder="t('mail', 'SMTP Host')"
+ v-model="manualConfig.smtpHost"
+ :disabled="loading"
+ required
+ >
+ <h4>{{ t('mail', 'SMTP Security') }}</h4>
+ <div class="flex-row">
+ <input
+ type="radio"
+ id="man-smtp-sec-none"
+ name="man-smtp-sec"
+ v-model="manualConfig.smtpSslMode"
+ :disabled="loading"
+ @change="onSmtpSslModeChange"
+ value="none"
+ >
+ <label
+ class="button"
+ for="man-smtp-sec-none"
+ :class="{primary: manualConfig.smtpSslMode === 'none' }"
+ >{{ t('mail', 'None') }}</label>
+ <input
+ type="radio"
+ id="man-smtp-sec-ssl"
+ name="man-smtp-sec"
+ v-model="manualConfig.smtpSslMode"
+ :disabled="loading"
+ @change="onSmtpSslModeChange"
+ value="ssl"
+ >
+ <label
+ class="button"
+ for="man-smtp-sec-ssl"
+ :class="{primary: manualConfig.smtpSslMode === 'ssl' }"
+ >{{ t('mail', 'SSL/TLS') }}</label>
+ <input
+ type="radio"
+ id="man-smtp-sec-tls"
+ name="man-smtp-sec"
+ v-model="manualConfig.smtpSslMode"
+ :disabled="loading"
+ @change="onSmtpSslModeChange"
+ value="tls"
+ >
+ <label
+ class="button"
+ for="man-smtp-sec-tls"
+ :class="{primary: manualConfig.smtpSslMode === 'tls' }"
+ >{{ t('mail', 'STARTTLS') }}</label>
+ </div>
+ <label for="man-smtp-port">{{ t('mail', 'SMTP Port') }}</label>
+ <input
+ type="number"
+ id="man-smtp-port"
+ :placeholder="t('mail', 'SMTP Port')"
+ v-model="manualConfig.smtpPort"
+ :disabled="loading"
+ required
+ >
+ <label for="man-smtp-user">{{ t('mail', 'SMTP User') }}</label>
+ <input
+ type="text"
+ id="man-smtp-user"
+ :placeholder="t('mail', 'SMTP User')"
+ v-model="manualConfig.smtpUser"
+ :disabled="loading"
+ required
+ >
+ <label for="man-smtp-password">{{ t('mail', 'SMTP Password') }}</label>
+ <input
+ type="password"
+ id="man-smtp-password"
+ :placeholder="t('mail', 'SMTP Password')"
+ v-model="manualConfig.smtpPassword"
+ :disabled="loading"
+ required
+ >
+ </tab>
+ </tabs>
+ <input
+ type="submit"
+ class="primary"
+ v-on:click="onSubmit"
+ :disabled="loading"
+ :value="submitButtonText"
+ >
+ </div>
</template>
<script>
- import {Tab, Tabs} from 'vue-tabs-component'
+import _ from 'underscore'
+import { Tab, Tabs } from 'vue-tabs-component'
- export default {
- name: 'AccountForm',
- props: {
- displayName: {
- type: String,
- default: '',
- },
- email: {
- type: String,
- default: '',
- },
- save: {
- type: Function,
- required: true,
- }
+export default {
+ name: 'AccountForm',
+ props: {
+ displayName: {
+ type: String,
+ default: '',
},
- components: {
- Tab,
- Tabs,
+ email: {
+ type: String,
+ default: '',
},
- data () {
- return {
- loading: false,
- mode: 'auto',
- autoConfig: {
- accountName: this.displayName,
- emailAddress: this.email,
- password: '',
- },
- manualConfig: {
- accountName: '',
- emailAddress: '',
- accountName: '',
- imapHost: '',
- imapPort: 993,
- imapSslMode: 'ssl',
- imapUser: '',
- imapPassword: '',
- smtpHost: '',
- smtpPort: 587,
- smtpSslMode: 'tls',
- smtpUser: '',
- smtpPassword: '',
- }
- };
+ save: {
+ type: Function,
+ required: true,
},
- methods: {
- onModeChanged (e) {
- this.mode = e.tab.id
-
- if (this.mode === 'manual') {
- if (this.manualConfig.accountName === '') {
- this.manualConfig.accountName = this.autoConfig.accountName
- }
- if (this.manualConfig.emailAddress === '') {
- this.manualConfig.emailAddress = this.autoConfig.emailAddress
- }
+ account: {
+ type: Object,
+ required: false,
+ },
+ },
+ components: {
+ Tab,
+ Tabs,
+ },
+ data() {
+ const fromAccountOr = (prop, def) => {
+ if (!_.isUndefined(this.account)) {
+ return this.account[prop]
+ } else {
+ return def
+ }
+ }
- // IMAP
- if (this.manualConfig.imapUser === '') {
- this.manualConfig.imapUser = this.autoConfig.emailAddress
- }
- if (this.manualConfig.imapPassword === '') {
- this.manualConfig.imapPassword = this.autoConfig.password
- }
+ return {
+ loading: false,
+ mode: 'auto',
+ autoConfig: {
+ accountName: this.displayName,
+ emailAddress: this.email,
+ password: '',
+ },
+ manualConfig: {
+ accountName: '',
+ emailAddress: '',
+ imapHost: fromAccountOr('imapHost', ''),
+ imapPort: fromAccountOr('imapPort', 993),
+ imapSslMode: fromAccountOr('imapSslMode', 'ssl'),
+ imapUser: fromAccountOr('imapUser', ''),
+ imapPassword: '',
+ smtpHost: fromAccountOr('smtpHost', ''),
+ smtpPort: fromAccountOr('smtpPort', 587),
+ smtpSslMode: fromAccountOr('smtpSslMode', 'tls'),
+ smtpUser: fromAccountOr('smtpUser', ''),
+ smtpPassword: '',
+ },
+ submitButtonText: this.account
+ ? t('mail', 'Save')
+ : t('mail', 'Connect'),
+ }
+ },
+ computed: {
+ settingsPage() {
+ return !_.isUndefined(this.account)
+ },
+ },
+ methods: {
+ onModeChanged(e) {
+ this.mode = e.tab.id
- // SMTP
- if (this.manualConfig.smtpUser === '') {
- this.manualConfig.smtpUser = this.autoConfig.emailAddress
- }
- if (this.manualConfig.smtpPassword === '') {
- this.manualConfig.smtpPassword = this.autoConfig.password
- }
+ if (this.mode === 'manual') {
+ if (this.manualConfig.accountName === '') {
+ this.manualConfig.accountName = this.autoConfig.accountName
}
- },
- onImapSslModeChange: function () {
- switch (this.manualConfig.imapSslMode) {
- case 'none':
- case 'tls':
- this.manualConfig.imapPort = 143;
- break;
- case 'ssl':
- this.manualConfig.imapPort = 993;
- break;
+ if (this.manualConfig.emailAddress === '') {
+ this.manualConfig.emailAddress = this.autoConfig.emailAddress
}
- },
- onSmtpSslModeChange: function () {
- switch (this.manualConfig.smtpSslMode) {
- case 'none':
- case 'tls':
- this.manualConfig.smtpPort = 587;
- break;
- case 'ssl':
- this.manualConfig.smtpPort = 465;
- break;
+
+ // IMAP
+ if (this.manualConfig.imapUser === '') {
+ this.manualConfig.imapUser = this.autoConfig.emailAddress
}
- },
- saveChanges () {
- if (this.mode === 'auto') {
- return this.save({
- autoDetect: true,
- ...this.autoConfig,
- })
- } else {
- return this.save({
- autoDetect: false,
- ...this.manualConfig,
- })
+ if (this.manualConfig.imapPassword === '') {
+ this.manualConfig.imapPassword = this.autoConfig.password
}
- },
- onSubmit: function () {
- this.loading = true
- this.saveChanges()
- .catch(console.error.bind(this))
- .then(() => this.loading = false)
+ // SMTP
+ if (this.manualConfig.smtpUser === '') {
+ this.manualConfig.smtpUser = this.autoConfig.emailAddress
+ }
+ if (this.manualConfig.smtpPassword === '') {
+ this.manualConfig.smtpPassword = this.autoConfig.password
+ }
}
- }
- };
+ },
+ onImapSslModeChange: function() {
+ switch (this.manualConfig.imapSslMode) {
+ case 'none':
+ case 'tls':
+ this.manualConfig.imapPort = 143
+ break
+ case 'ssl':
+ this.manualConfig.imapPort = 993
+ break
+ }
+ },
+ onSmtpSslModeChange: function() {
+ switch (this.manualConfig.smtpSslMode) {
+ case 'none':
+ case 'tls':
+ this.manualConfig.smtpPort = 587
+ break
+ case 'ssl':
+ this.manualConfig.smtpPort = 465
+ break
+ }
+ },
+ saveChanges() {
+ if (this.mode === 'auto') {
+ return this.save({
+ autoDetect: true,
+ ...this.autoConfig,
+ })
+ } else {
+ return this.save({
+ autoDetect: false,
+ ...this.manualConfig,
+ })
+ }
+ },
+ onSubmit: function() {
+ this.loading = true
+
+ this.saveChanges()
+ .catch(console.error.bind(this))
+ .then(() => (this.loading = false))
+ },
+ },
+}
</script>
<style>
- .tabs-component-tabs {
- display: flex;
- }
+.tabs-component-tabs {
+ display: flex;
+}
- .tabs-component-tab {
- flex-grow: 1;
- color: var(--color-text-lighter);
- font-weight: bold;
- }
+.tabs-component-tab {
+ flex-grow: 1;
+ color: var(--color-text-lighter);
+ font-weight: bold;
+}
- .tabs-component-tab.is-active {
- border-bottom: 1px solid black;
- }
+.tabs-component-tab.is-active {
+ border-bottom: 1px solid black;
+}
- .tabs-component-panels {
- padding-top: 20px;
- }
+.tabs-component-panels {
+ padding-top: 20px;
+}
- .tabs-component-panels label {
- text-align: left;
- width: 100%;
- display: inline-block;
- }
+.tabs-component-panels label {
+ text-align: left;
+ width: 100%;
+ display: inline-block;
+}
- .tabs-component-panels input,
- .tabs-component-panels select {
- margin-bottom: 10px;
- }
+.tabs-component-panels input,
+.tabs-component-panels select {
+ margin-bottom: 10px;
+}
</style>
<style scoped>
- h4 {
- text-align: left;
- }
+h4 {
+ text-align: left;
+}
- .flex-row {
- display: flex;
- }
+.flex-row {
+ display: flex;
+}
- label.button {
- display: inline-block;
- text-align: center;
- flex-grow: 1;
- }
+label.button {
+ display: inline-block;
+ text-align: center;
+ flex-grow: 1;
+}
- input[type="radio"] {
- display: none;
- }
+input[type='radio'] {
+ display: none;
+}
- input[type=radio][disabled] + label {
- cursor: default;
- opacity: 0.5;
- }
+input[type='radio'][disabled] + label {
+ cursor: default;
+ opacity: 0.5;
+}
</style>
diff --git a/src/service/AccountService.js b/src/service/AccountService.js
index d1cf00193..dcb424a13 100644
--- a/src/service/AccountService.js
+++ b/src/service/AccountService.js
@@ -1,10 +1,10 @@
-import {generateUrl} from 'nextcloud-server/dist/router'
+import { generateUrl } from 'nextcloud-server/dist/router'
import HttpClient from 'nextcloud-axios'
export const fixAccountId = original => {
return {
id: original.accountId,
- ...original
+ ...original,
}
}
@@ -16,6 +16,16 @@ export const create = data => {
.then(fixAccountId)
}
+export const update = data => {
+ const url = generateUrl(`/apps/mail/api/accounts/{id}`, {
+ id: data.accountId,
+ })
+
+ return HttpClient.put(url, data)
+ .then(resp => resp.data)
+ .then(fixAccountId)
+}
+
export const fetchAll = () => {
const url = generateUrl('/apps/mail/api/accounts')
@@ -24,7 +34,7 @@ export const fetchAll = () => {
export const fetch = id => {
const url = generateUrl('/apps/mail/api/accounts/{id}', {
- id
+ id,
})
return HttpClient.get(url).then(resp => fixAccountId(resp.data))
diff --git a/src/store/actions.js b/src/store/actions.js
index 52ae67188..856575512 100644
--- a/src/store/actions.js
+++ b/src/store/actions.js
@@ -24,6 +24,7 @@ import _ from "lodash";
import {savePreference} from "../service/PreferenceService";
import {
create as createAccount,
+ update as updateAccount,
deleteAccount,
fetch as fetchAccount,
fetchAll as fetchAllAccounts
@@ -69,6 +70,14 @@ export default {
return account
})
},
+ updateAccount ({commit}, config) {
+ return updateAccount(config)
+ .then(account => {
+ console.debug('account updated', account)
+ commit('editAccount', account)
+ return account
+ })
+ },
deleteAccount ({commit}, account) {
return deleteAccount(account.id)
.then(account => {
diff --git a/src/store/actions.js.orig b/src/store/actions.js.orig
new file mode 100644
index 000000000..c3ffb03f3
--- /dev/null
+++ b/src/store/actions.js.orig
@@ -0,0 +1,865 @@
+<<<<<<< HEAD:src/store.js
+import _ from 'lodash'
+import Vue from 'vue'
+import Vuex from 'vuex'
+import { translate as t } from 'nextcloud-server/dist/l10n'
+
+import {
+ create as createAccount,
+ update as updateAccount,
+ fetch as fetchAccount,
+ fetchAll as fetchAllAccounts,
+} from './service/AccountService'
+import { fetchAll as fetchAllFolders } from './service/FolderService'
+=======
+/*
+ * @copyright 2019 Christoph Wurst <christoph@winzerhof-wurst.at>
+ *
+ * @author 2019 Christoph Wurst <christoph@winzerhof-wurst.at>
+ *
+ * @license GNU AGPL version 3 or any later version
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * 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 _ from "lodash";
+
+import {savePreference} from "../service/PreferenceService";
+import {
+ create as createAccount,
+ deleteAccount,
+ fetch as fetchAccount,
+ fetchAll as fetchAllAccounts
+} from "../service/AccountService";
+import {fetchAll as fetchAllFolders} from "../service/FolderService";
+>>>>>>> 12b4a7d848da1bf5d72db31f1a3b7e42c09af590:src/store/actions.js
+import {
+ deleteMessage,
+ fetchEnvelopes,
+ fetchMessage,
+ setEnvelopeFlag,
+<<<<<<< HEAD:src/store.js
+ syncEnvelopes,
+} from './service/MessageService'
+import { showNewMessagesNotification } from './service/NotificationService'
+import { savePreference } from './service/PreferenceService'
+import { parseUid } from './util/EnvelopeUidParser'
+import { value } from './util/undefined'
+
+Vue.use(Vuex)
+
+const UNIFIED_ACCOUNT_ID = 0
+const UNIFIED_INBOX_ID = 'inbox'
+const UNIFIED_INBOX_UID = UNIFIED_ACCOUNT_ID + '-' + UNIFIED_INBOX_ID
+
+export const mutations = {
+ savePreference(state, { key, value }) {
+ Vue.set(state.preferences, key, value)
+ },
+ addAccount(state, account) {
+ account.folders = []
+ account.collapsed = true
+ Vue.set(state.accounts, account.id, account)
+ Vue.set(
+ state,
+ 'accountList',
+ _.sortBy(state.accountList.concat([account.id]))
+ )
+ },
+ editAccount(state, account) {
+ Vue.set(
+ state.accounts,
+ account.id,
+ Object.assign({}, state.accounts[account.id], account)
+ )
+ },
+ toggleAccountCollapsed(state, accountId) {
+ state.accounts[accountId].collapsed = !state.accounts[accountId]
+ .collapsed
+ },
+ addFolder(state, { account, folder }) {
+ const addToState = folder => {
+ const id = account.id + '-' + folder.id
+ folder.accountId = account.id
+ folder.envelopes = []
+ folder.searchEnvelopes = []
+ Vue.set(state.folders, id, folder)
+ return id
+ }
+
+ // Add all folders (including subfolders ot state, but only toplevel to account
+ const id = addToState(folder)
+ folder.folders.forEach(addToState)
+
+ account.folders.push(id)
+ },
+ updateFolderSyncToken(state, { folder, syncToken }) {
+ folder.syncToken = syncToken
+ },
+ addEnvelope(state, { accountId, folder, envelope }) {
+ const uid = accountId + '-' + folder.id + '-' + envelope.id
+ envelope.accountId = accountId
+ envelope.folderId = folder.id
+ envelope.uid = uid
+ Vue.set(state.envelopes, uid, envelope)
+ Vue.set(
+ folder,
+ 'envelopes',
+ _.sortedUniq(
+ _.orderBy(
+ folder.envelopes.concat([uid]),
+ id => state.envelopes[id].dateInt,
+ 'desc'
+ )
+ )
+ )
+ },
+ addSearchEnvelopes(state, { accountId, folder, envelopes, clear }) {
+ const uids = envelopes.map(envelope => {
+ const uid = accountId + '-' + folder.id + '-' + envelope.id
+ envelope.accountId = accountId
+ envelope.folderId = folder.id
+ envelope.uid = uid
+ Vue.set(state.envelopes, uid, envelope)
+ return uid
+ })
+
+ if (clear) {
+ Vue.set(folder, 'searchEnvelopes', uids)
+ } else {
+ Vue.set(
+ folder,
+ 'searchEnvelopes',
+ _.sortedUniq(
+ _.orderBy(
+ folder.searchEnvelopes.concat(uids),
+ id => state.envelopes[id].dateInt,
+ 'desc'
+ )
+ )
+ )
+ }
+ },
+ addUnifiedEnvelope(state, { folder, envelope }) {
+ Vue.set(
+ folder,
+ 'envelopes',
+ _.sortedUniq(
+ _.orderBy(
+ folder.envelopes.concat([envelope.uid]),
+ id => state.envelopes[id].dateInt,
+ 'desc'
+ )
+ )
+ )
+ },
+ addUnifiedEnvelopes(state, { folder, uids }) {
+ Vue.set(folder, 'envelopes', uids)
+ },
+ addUnifiedSearchEnvelopes(state, { folder, uids }) {
+ Vue.set(folder, 'searchEnvelopes', uids)
+ },
+ flagEnvelope(state, { envelope, flag, value }) {
+ envelope.flags[flag] = value
+ },
+ removeEnvelope(state, { accountId, folder, id }) {
+ const envelopeUid = accountId + '-' + folder.id + '-' + id
+ const idx = folder.envelopes.indexOf(envelopeUid)
+ if (idx < 0) {
+ console.warn('envelope does not exist', accountId, folder.id, id)
+ return
+ }
+ folder.envelopes.splice(idx, 1)
+
+ const unifiedAccount = state.accounts[UNIFIED_ACCOUNT_ID]
+ unifiedAccount.folders
+ .map(fId => state.folders[fId])
+ .filter(f => f.specialRole === folder.specialRole)
+ .forEach(folder => {
+ const idx = folder.envelopes.indexOf(envelopeUid)
+ if (idx < 0) {
+ console.warn(
+ 'envelope does not exist in unified mailbox',
+ accountId,
+ folder.id,
+ id
+ )
+ return
+ }
+ folder.envelopes.splice(idx, 1)
+ })
+
+ Vue.delete(folder.envelopes, envelopeUid)
+ },
+ addMessage(state, { accountId, folderId, message }) {
+ const uid = accountId + '-' + folderId + '-' + message.id
+ message.accountId = accountId
+ message.folderId = folderId
+ message.uid = uid
+ Vue.set(state.messages, uid, message)
+ },
+ updateDraft(state, { draft, data, newUid }) {
+ // Update draft's UID
+ const oldUid = draft.uid
+ const uid = draft.accountId + '-' + draft.folderId + '-' + newUid
+ console.debug('saving draft as UID ' + uid)
+ draft.uid = uid
+
+ // TODO: strategy to keep the full draft object in sync, not just the visible
+ // changes
+ draft.subject = data.subject
+
+ // Update ref in folder's envelope list
+ const envs =
+ state.folders[draft.accountId + '-' + draft.folderId].envelopes
+ const idx = envs.indexOf(oldUid)
+ if (idx < 0) {
+ console.warn(
+ 'not replacing draft ' +
+ oldUid +
+ ' in envelope list because it did not exist'
+ )
+ } else {
+ envs[idx] = uid
+ }
+
+ // Move message/envelope objects to new keys
+ Vue.delete(state.envelopes, oldUid)
+ Vue.delete(state.messages, oldUid)
+ Vue.set(state.envelopes, uid, draft)
+ Vue.set(state.messages, uid, draft)
+ },
+ setMessageBodyText(state, { uid, bodyText }) {
+ Vue.set(state.messages[uid], 'bodyText', bodyText)
+ },
+ removeMessage(state, { accountId, folderId, id }) {
+ Vue.delete(state.messages, accountId + '-' + folderId + '-' + id)
+ },
+}
+
+export const actions = {
+ savePreference({ commit, getters }, { key, value }) {
+ return savePreference(key, value).then(({ value }) => {
+ commit('savePreference', {
+ key,
+ value,
+=======
+ syncEnvelopes
+} from "../service/MessageService";
+import {showNewMessagesNotification} from "../service/NotificationService";
+import {parseUid} from "../util/EnvelopeUidParser";
+
+export default {
+ savePreference ({commit, getters}, {key, value}) {
+ return savePreference(key, value)
+ .then(({value}) => {
+ commit('savePreference', {
+ key,
+ value,
+ })
+>>>>>>> 12b4a7d848da1bf5d72db31f1a3b7e42c09af590:src/store/actions.js
+ })
+ })
+ },
+ fetchAccounts({ commit, getters }) {
+ return fetchAllAccounts().then(accounts => {
+ accounts.forEach(account => commit('addAccount', account))
+ return getters.getAccounts()
+ })
+ },
+ fetchAccount({ commit }, id) {
+ return fetchAccount(id).then(account => {
+ commit('addAccount', account)
+ return account
+ })
+ },
+ createAccount({ commit }, config) {
+ return createAccount(config).then(account => {
+ commit('addAccount', account)
+ return account
+ })
+ },
+<<<<<<< HEAD:src/store.js
+ updateAccount({ commit }, config) {
+ return updateAccount(config).then(account => {
+ console.debug('account updated', account)
+ commit('editAccount', account)
+ return account
+ })
+ },
+ fetchFolders({ commit, getters }, { accountId }) {
+=======
+ deleteAccount ({commit}, account) {
+ return deleteAccount(account.id)
+ .then(account => {
+ console.debug('account deleted')
+ location.reload() // TODO: better handling of this
+ })
+ .catch(err => {
+ console.error('could not delete account', err)
+ throw err
+ })
+ },
+ fetchFolders ({commit, getters}, {accountId}) {
+>>>>>>> 12b4a7d848da1bf5d72db31f1a3b7e42c09af590:src/store/actions.js
+ if (getters.getAccount(accountId).isUnified) {
+ return Promise.resolve(getters.getFolders(accountId))
+ }
+
+ return fetchAllFolders(accountId).then(folders => {
+ let account = getters.getAccount(accountId)
+
+ folders.forEach(folder => {
+ commit('addFolder', {
+ account,
+ folder,
+ })
+ })
+ return folders
+ })
+ },
+ fetchEnvelopes(
+ { state, commit, getters, dispatch },
+ { accountId, folderId, query }
+ ) {
+ const folder = getters.getFolder(accountId, folderId)
+ const isSearch = !_.isUndefined(query)
+ if (folder.isUnified) {
+ // Fetch and combine envelopes of all individual folders
+ //
+ // The last envelope is excluded to efficiently build the next unified
+ // pages (fetch only individual pages that do not have sufficient local
+ // data)
+ //
+ // TODO: handle short/ending streams and show their last element as well
+ return Promise.all(
+ getters
+ .getAccounts()
+ .filter(account => !account.isUnified)
+ .map(account =>
+ Promise.all(
+ getters
+ .getFolders(account.id)
+ .filter(
+ f => f.specialRole === folder.specialRole
+ )
+ .map(folder =>
+ dispatch('fetchEnvelopes', {
+ accountId: account.id,
+ folderId: folder.id,
+ query,
+ })
+ )
+ )
+ )
+ )
+ .then(res => res.map(envs => envs.slice(0, 19)))
+ .then(res => _.flattenDepth(res, 2))
+ .then(envs => _.orderBy(envs, env => env.dateInt, 'desc'))
+ .then(envs => _.slice(envs, 0, 19)) // 19 to handle non-overlapping streams
+ .then(envelopes => {
+ if (!isSearch) {
+ commit('addUnifiedEnvelopes', {
+ folder,
+ uids: envelopes.map(e => e.uid),
+ })
+ } else {
+ commit('addUnifiedSearchEnvelopes', {
+ folder,
+ uids: envelopes.map(e => e.uid),
+ })
+ }
+ return envelopes
+ })
+ }
+
+ return fetchEnvelopes(accountId, folderId, query).then(envelopes => {
+ let folder = getters.getFolder(accountId, folderId)
+
+ if (!isSearch) {
+ envelopes.forEach(envelope =>
+ commit('addEnvelope', {
+ accountId,
+ folder,
+ envelope,
+ })
+ )
+ } else {
+ commit('addSearchEnvelopes', {
+ accountId,
+ folder,
+ envelopes,
+ clear: true,
+ })
+ }
+ return envelopes
+ })
+ },
+ fetchNextUnifiedEnvelopePage(
+ { state, commit, getters, dispatch },
+ { accountId, folderId, query }
+ ) {
+ const folder = getters.getFolder(accountId, folderId)
+ const isSearch = !_.isUndefined(query)
+ const list = isSearch ? 'searchEnvelopes' : 'envelopes'
+
+ // We only care about folders of the same type/role
+ const individualFolders = _.flatten(
+ getters
+ .getAccounts()
+ .filter(a => !a.isUnified)
+ .map(a =>
+ getters
+ .getFolders(a.id)
+ .filter(f => f.specialRole === folder.specialRole)
+ )
+ )
+ // Build a sorted list of all currently known envelopes (except last elem)
+ const knownEnvelopes = _.orderBy(
+ _.flatten(
+ individualFolders.map(f => f[list].slice(0, f[list].length - 1))
+ ),
+ id => state.envelopes[id].dateInt,
+ 'desc'
+ )
+ // The index of the last element in the current unified mailbox determines
+ // the new offset
+ const tailId = _.last(folder[list])
+ const tailIdx = knownEnvelopes.indexOf(tailId)
+ if (tailIdx === -1) {
+ return Promise.reject(
+ new Error(
+ 'current envelopes do not contain unified mailbox tail. this should not have happened'
+ )
+ )
+ }
+
+ // Select the next page, based on offline data
+ const nextCandidates = knownEnvelopes.slice(tailIdx + 1, tailIdx + 20)
+
+ // Now, let's check if any of the "streams" have reached their ends.
+ // In that case, we attempt to fetch more elements recursively
+ //
+ // In case of an empty next page we always fetch all streams (this might be redundant)
+ //
+ // Their end was reached if the last known (oldest) envelope is an element
+ // of the offline page
+ // TODO: what about streams that ended before? Is it safe to ignore those?
+ const needFetch = individualFolders
+ .filter(f => !_.isEmpty(f[list]))
+ .filter(f => {
+ const lastShown = f[list][f[list].length - 2]
+ return (
+ nextCandidates.length <= 18 ||
+ nextCandidates.indexOf(lastShown) !== -1
+ )
+ })
+
+ if (_.isEmpty(needFetch)) {
+ if (!isSearch) {
+ commit('addUnifiedEnvelopes', {
+ folder,
+ uids: _.sortedUniq(
+ _.orderBy(
+ folder[list].concat(nextCandidates),
+ id => state.envelopes[id].dateInt,
+ 'desc'
+ )
+ ),
+ })
+ } else {
+ commit('addUnifiedSearchEnvelopes', {
+ folder,
+ uids: _.sortedUniq(
+ _.orderBy(
+ folder[list].concat(nextCandidates),
+ id => state.envelopes[id].dateInt,
+ 'desc'
+ )
+ ),
+ })
+ }
+ } else {
+ return Promise.all(
+ needFetch.map(f =>
+ dispatch('fetchNextEnvelopePage', {
+ accountId: f.accountId,
+ folderId: f.id,
+ query,
+ })
+ )
+ ).then(() => {
+ return dispatch('fetchNextUnifiedEnvelopePage', {
+ accountId,
+ folderId,
+ query,
+ })
+ })
+ }
+ },
+ fetchNextEnvelopePage(
+ { commit, getters, dispatch },
+ { accountId, folderId, query }
+ ) {
+ const folder = getters.getFolder(accountId, folderId)
+ const isSearch = !_.isUndefined(query)
+ const list = isSearch ? 'searchEnvelopes' : 'envelopes'
+
+ if (folder.isUnified) {
+ return dispatch('fetchNextUnifiedEnvelopePage', {
+ accountId,
+ folderId,
+ query,
+ })
+ }
+
+ const lastEnvelopeId = folder[list][folder.envelopes.length - 1]
+ if (typeof lastEnvelopeId === 'undefined') {
+ console.error('folder is empty', folder[list])
+ return Promise.reject(
+ new Error(
+ 'Local folder has no envelopes, cannot determine cursor'
+ )
+ )
+ }
+ const lastEnvelope = getters.getEnvelopeById(lastEnvelopeId)
+ if (typeof lastEnvelope === 'undefined') {
+ return Promise.reject(
+ new Error(
+ 'Cannot find last envelope. Required for the folder cursor'
+ )
+ )
+ }
+
+ return fetchEnvelopes(
+ accountId,
+ folderId,
+ query,
+ lastEnvelope.dateInt
+ ).then(envelopes => {
+ if (!isSearch) {
+ envelopes.forEach(envelope =>
+ commit('addEnvelope', {
+ accountId,
+ folder,
+ envelope,
+ })
+ )
+ } else {
+ commit('addSearchEnvelopes', {
+ accountId,
+ folder,
+ envelopes,
+ clear: false,
+ })
+ }
+
+ return envelopes
+ })
+ },
+ syncEnvelopes({ commit, getters, dispatch }, { accountId, folderId }) {
+ const folder = getters.getFolder(accountId, folderId)
+
+ if (folder.isUnified) {
+ return Promise.all(
+ getters
+ .getAccounts()
+ .filter(account => !account.isUnified)
+ .map(account =>
+ Promise.all(
+ getters
+ .getFolders(account.id)
+ .filter(
+ f => f.specialRole === folder.specialRole
+ )
+ .map(folder =>
+ dispatch('syncEnvelopes', {
+ accountId: account.id,
+ folderId: folder.id,
+ })
+ )
+ )
+ )
+ )
+ }
+
+ const syncToken = folder.syncToken
+ const uids = getters
+ .getEnvelopes(accountId, folderId)
+ .map(env => env.id)
+
+ return syncEnvelopes(accountId, folderId, syncToken, uids).then(
+ syncData => {
+ const unifiedFolder = getters.getUnifiedFolder(
+ folder.specialRole
+ )
+
+ syncData.newMessages.forEach(envelope => {
+ commit('addEnvelope', {
+ accountId,
+ folder,
+ envelope,
+ })
+ if (unifiedFolder) {
+ commit('addUnifiedEnvelope', {
+ folder: unifiedFolder,
+ envelope,
+ })
+ }
+ })
+ syncData.changedMessages.forEach(envelope => {
+ commit('addEnvelope', {
+ accountId,
+ folder,
+ envelope,
+ })
+ })
+ syncData.vanishedMessages.forEach(id => {
+ commit('removeEnvelope', {
+ accountId,
+ folder,
+ id,
+ })
+ // Already removed from unified inbox
+ })
+ commit('updateFolderSyncToken', {
+ folder,
+ syncToken: syncData.token,
+ })
+
+ return syncData.newMessages
+ }
+ )
+ },
+ syncInboxes({ getters, dispatch }) {
+ return Promise.all(
+ getters
+ .getAccounts()
+ .filter(a => !a.isUnified)
+ .map(account => {
+ return Promise.all(
+ getters.getFolders(account.id).map(folder => {
+ if (folder.specialRole !== 'inbox') {
+ return
+ }
+
+ return dispatch('syncEnvelopes', {
+ accountId: account.id,
+ folderId: folder.id,
+ })
+ })
+ )
+ })
+ ).then(results => {
+ const newMessages = _.flatMapDeep(results).filter(
+ _.negate(_.isUndefined)
+ )
+ if (newMessages.length > 0) {
+ showNewMessagesNotification(newMessages)
+ }
+ })
+ },
+ toggleEnvelopeFlagged({ commit, getters }, envelope) {
+ // Change immediately and switch back on error
+ const oldState = envelope.flags.flagged
+ commit('flagEnvelope', {
+ envelope,
+ flag: 'flagged',
+ value: !oldState,
+ })
+
+ setEnvelopeFlag(
+ envelope.accountId,
+ envelope.folderId,
+ envelope.id,
+ 'flagged',
+ !oldState
+ ).catch(e => {
+ console.error('could not toggle message flagged state', e)
+
+ // Revert change
+ commit('flagEnvelope', {
+ envelope,
+ flag: 'flagged',
+ value: oldState,
+ })
+ })
+ },
+ toggleEnvelopeSeen({ commit, getters }, envelope) {
+ // Change immediately and switch back on error
+ const oldState = envelope.flags.unseen
+ commit('flagEnvelope', {
+ envelope,
+ flag: 'unseen',
+ value: !oldState,
+ })
+
+ setEnvelopeFlag(
+ envelope.accountId,
+ envelope.folderId,
+ envelope.id,
+ 'unseen',
+ !oldState
+ ).catch(e => {
+ console.error('could not toggle message unseen state', e)
+
+ // Revert change
+ commit('flagEnvelope', {
+ envelope,
+ flag: 'unseen',
+ value: oldState,
+ })
+ })
+ },
+ fetchMessage({ commit }, uid) {
+ const { accountId, folderId, id } = parseUid(uid)
+ return fetchMessage(accountId, folderId, id).then(message => {
+ // Only commit if not undefined (not found)
+ if (message) {
+ commit('addMessage', {
+ accountId,
+ folderId,
+ message,
+ })
+ }
+
+ return message
+ })
+ },
+ replaceDraft({ getters, commit }, { draft, uid, data }) {
+ commit('updateDraft', {
+ draft,
+ data,
+ newUid: uid,
+ })
+ },
+ deleteMessage({ getters, commit }, envelope) {
+ const folder = getters.getFolder(envelope.accountId, envelope.folderId)
+ commit('removeEnvelope', {
+ accountId: envelope.accountId,
+ folder,
+ id: envelope.id,
+ })
+
+ return deleteMessage(envelope.accountId, envelope.folderId, envelope.id)
+ .then(() => {
+ commit('removeMessage', {
+ accountId: envelope.accountId,
+ folder,
+ id: envelope.id,
+ })
+ console.log('message removed')
+ })
+ .catch(err => {
+ console.error('could not delete message', err)
+ commit('addEnvelope', {
+ accountId: envelope.accountId,
+ folder,
+ envelope,
+ })
+ throw err
+ })
+<<<<<<< HEAD:src/store.js
+ },
+}
+
+export const getters = {
+ getPreference: state => (key, def) => {
+ return value(state.preferences[key]).or(def)
+ },
+ getAccount: state => id => {
+ return state.accounts[id]
+ },
+ getAccounts: state => () => {
+ return state.accountList.map(id => state.accounts[id])
+ },
+ getFolder: state => (accountId, folderId) => {
+ return state.folders[accountId + '-' + folderId]
+ },
+ getFolders: state => accountId => {
+ return state.accounts[accountId].folders.map(
+ folderId => state.folders[folderId]
+ )
+ },
+ getUnifiedFolder: state => specialRole => {
+ return _.head(
+ state.accounts[UNIFIED_ACCOUNT_ID].folders
+ .map(folderId => state.folders[folderId])
+ .filter(folder => folder.specialRole === specialRole)
+ )
+ },
+ getEnvelope: state => (accountId, folderId, id) => {
+ return state.envelopes[accountId + '-' + folderId + '-' + id]
+ },
+ getEnvelopeById: state => id => {
+ return state.envelopes[id]
+ },
+ getEnvelopes: (state, getters) => (accountId, folderId) => {
+ return getters
+ .getFolder(accountId, folderId)
+ .envelopes.map(msgId => state.envelopes[msgId])
+ },
+ getSearchEnvelopes: (state, getters) => (accountId, folderId) => {
+ return getters
+ .getFolder(accountId, folderId)
+ .searchEnvelopes.map(msgId => state.envelopes[msgId])
+ },
+ getMessage: state => (accountId, folderId, id) => {
+ return state.messages[accountId + '-' + folderId + '-' + id]
+ },
+ getMessageByUid: state => uid => {
+ return state.messages[uid]
+ },
+}
+
+export default new Vuex.Store({
+ strict: process.env.NODE_ENV !== 'production',
+ state: {
+ preferences: {},
+ accounts: {
+ [UNIFIED_ACCOUNT_ID]: {
+ id: UNIFIED_ACCOUNT_ID,
+ isUnified: true,
+ folders: [UNIFIED_INBOX_UID],
+ collapsed: false,
+ emailAddress: '',
+ name: '',
+ },
+ },
+ accountList: [UNIFIED_ACCOUNT_ID],
+ folders: {
+ [UNIFIED_INBOX_UID]: {
+ id: UNIFIED_INBOX_ID,
+ accountId: 0,
+ isUnified: true,
+ specialRole: 'inbox',
+ name: t('mail', 'All inboxes'), // TODO,
+ unread: 0,
+ folders: [],
+ envelopes: [],
+ searchEnvelopes: [],
+ },
+ },
+ envelopes: {},
+ messages: {},
+ autocompleteEntries: [],
+ },
+ getters,
+ mutations,
+ actions,
+})
+=======
+ }
+}
+>>>>>>> 12b4a7d848da1bf5d72db31f1a3b7e42c09af590:src/store/actions.js
diff --git a/src/store/mutations.js b/src/store/mutations.js
index 1c8c4898a..4da2571cd 100644
--- a/src/store/mutations.js
+++ b/src/store/mutations.js
@@ -38,6 +38,13 @@ export default {
_.sortBy(state.accountList.concat([account.id]))
)
},
+ editAccount(state, account) {
+ Vue.set(
+ state.accounts,
+ account.id,
+ Object.assign({}, state.accounts[account.id], account)
+ )
+ },
toggleAccountCollapsed (state, accountId) {
state.accounts[accountId].collapsed = !state.accounts[accountId].collapsed
},
diff --git a/src/views/AccountSettings.vue b/src/views/AccountSettings.vue
index 9ec47e23f..5489cd9b2 100644
--- a/src/views/AccountSettings.vue
+++ b/src/views/AccountSettings.vue
@@ -1,50 +1,86 @@
<template>
- <AppContent app-name="mail">
- <template slot="navigation">
- <AppNavigationNew :text="t('mail', 'New message')"
- buttonId="mail_new_message"
- buttonClass="icon-add"
- @click="onNewMessage"/>
- <ul id="accounts-list">
- <AppNavigationItem v-for="item in menu"
- :key="item.key"
- :item="item"/>
- </ul>
- <AppNavigationSettings :title="t('mail', 'Settings')">
- <AppSettingsMenu />
- </AppNavigationSettings>
- </template>
- <template slot="content">
- TODO: implement account settings
- </template>
- </AppContent>
+ <AppContent app-name="mail">
+ <template slot="navigation">
+ <AppNavigationNew
+ :text="t('mail', 'New message')"
+ buttonId="mail_new_message"
+ buttonClass="icon-add"
+ @click="onNewMessage"
+ />
+ <ul id="accounts-list">
+ <AppNavigationItem v-for="item in menu" :key="item.key" :item="item"/>
+ </ul>
+ <AppNavigationSettings :title="t('mail', 'Settings')">
+ <AppSettingsMenu/>
+ </AppNavigationSettings>
+ </template>
+ <template slot="content">
+ <div class="section" id="account-info">
+ <h2>{{ t ('mail', 'Account Settings') }} - {{ email }}</h2>
+ </div>
+ <div class="section">
+ <div id="mail-settings">
+ <AccountForm :displayName="displayName" :email="email" :save="onSave" :account="account"/>
+ </div>
+ </div>
+ </template>
+ </AppContent>
</template>
<script>
- import {
+import {
+ AppContent,
+ AppNavigationItem,
+ AppNavigationNew,
+ AppNavigationSettings,
+} from 'nextcloud-vue'
+import AppSettingsMenu from '../components/AppSettingsMenu'
+import AccountForm from '../components/AccountForm'
+
+import SidebarItems from '../mixins/SidebarItems'
+
+export default {
+ name: 'AccountSettings',
+ components: {
+ AccountForm,
AppContent,
AppNavigationItem,
AppNavigationNew,
- AppNavigationSettings
- } from 'nextcloud-vue'
- import AppSettingsMenu from '../components/AppSettingsMenu'
-
- import SidebarItems from '../mixins/SidebarItems'
+ AppNavigationSettings,
+ AppSettingsMenu,
+ },
+ extends: SidebarItems,
+ computed: {
+ menu() {
+ return this.buildMenu()
+ },
+ account() {
+ return this.$store.getters.getAccount(this.$route.params.accountId)
+ },
+ displayName() {
+ return this.$store.getters.getAccount(this.$route.params.accountId)
+ .name
+ },
+ email() {
+ return this.$store.getters.getAccount(this.$route.params.accountId)
+ .emailAddress
+ },
+ },
+ methods: {
+ onSave(data) {
+ console.log('data to save:', data)
+ return this.$store
+ .dispatch('updateAccount', {
+ ...data,
+ accountId: this.$route.params.accountId,
+ })
+ .then(account => account)
+ .catch(err => {
+ console.error('account update failed:', err)
- export default {
- name: 'AccountSettings',
- extends: SidebarItems,
- components: {
- AppContent,
- AppNavigationItem,
- AppNavigationNew,
- AppNavigationSettings,
- AppSettingsMenu,
+ throw err
+ })
},
- computed: {
- menu () {
- return this.buildMenu()
- }
- }
- }
+ },
+}
</script>
diff --git a/tests/Controller/AccountsControllerTest.php b/tests/Controller/AccountsControllerTest.php
index 518b61fb5..826e9ebea 100644
--- a/tests/Controller/AccountsControllerTest.php
+++ b/tests/Controller/AccountsControllerTest.php
@@ -327,7 +327,7 @@ class AccountsControllerTest extends TestCase {
->with($accountName, $email, $imapHost, $imapPort, $imapSslMode, $imapUser, $imapPassword, $smtpHost, $smtpPort, $smtpSslMode, $smtpUser, $smtpPassword, $this->userId, $id)
->willReturn($account);
- $response = $this->controller->update($id, $accountName, $email, $password, $imapHost, $imapPort, $imapSslMode, $imapUser, $imapPassword, $smtpHost, $smtpPort, $smtpSslMode, $smtpUser, $smtpPassword, $autoDetect);
+ $response = $this->controller->update($id, $autoDetect, $accountName, $email, $password, $imapHost, $imapPort, $imapSslMode, $imapUser, $imapPassword, $smtpHost, $smtpPort, $smtpSslMode, $smtpUser, $smtpPassword);
$expectedResponse = new JSONResponse($account);
@@ -356,7 +356,7 @@ class AccountsControllerTest extends TestCase {
->willThrowException(new Exception());
$this->expectException(ClientException::class);
- $this->controller->update($id, $accountName, $email, $password, $imapHost, $imapPort, $imapSslMode, $imapUser, $imapPassword, $smtpHost, $smtpPort, $smtpSslMode, $smtpUser, $smtpPassword, $autoDetect);
+ $this->controller->update($id, $autoDetect, $accountName, $email, $password, $imapHost, $imapPort, $imapSslMode, $imapUser, $imapPassword, $smtpHost, $smtpPort, $smtpSslMode, $smtpUser, $smtpPassword);
}
public function testSendNewMessage() {