Add Vue
Signed-off-by: Christoph Wurst <christoph@winzerhof-wurst.at>
This commit is contained in:
Родитель
1ecf67c6af
Коммит
0ff788a14d
|
@ -0,0 +1,3 @@
|
|||
> 1%
|
||||
last 2 versions
|
||||
not ie <= 8
|
|
@ -0,0 +1,17 @@
|
|||
module.exports = {
|
||||
root: true,
|
||||
env: {
|
||||
node: true
|
||||
},
|
||||
'extends': [
|
||||
'plugin:vue/essential',
|
||||
'eslint:recommended'
|
||||
],
|
||||
rules: {
|
||||
'no-console': process.env.NODE_ENV === 'production' ? 'error' : 'off',
|
||||
'no-debugger': process.env.NODE_ENV === 'production' ? 'error' : 'off'
|
||||
},
|
||||
parserOptions: {
|
||||
parser: 'babel-eslint'
|
||||
}
|
||||
}
|
|
@ -0,0 +1,5 @@
|
|||
module.exports = {
|
||||
presets: [
|
||||
'@vue/app'
|
||||
]
|
||||
}
|
|
@ -0,0 +1,30 @@
|
|||
<template>
|
||||
<div id="content" class="app-mail">
|
||||
<div id="app-navigation" class="icon-loading">
|
||||
<div id="mail-new-message-fixed"
|
||||
class="app-navigation-new">
|
||||
</div>
|
||||
<ul id="usergrouplist"></ul>
|
||||
<div id="app-settings">
|
||||
<div id="app-settings-header">
|
||||
<button class="settings-button"
|
||||
data-apps-slide-toggle="#app-settings-content"><?php p($l->
|
||||
t('Settings'));?>
|
||||
</button>
|
||||
</div>
|
||||
<div id="app-settings-content"></div>
|
||||
</div>
|
||||
</div>
|
||||
<div id="app-content">
|
||||
<div id="app-content-wrapper"></div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {}
|
||||
</script>
|
||||
|
||||
<style>
|
||||
|
||||
</style>
|
153
js/app.js
153
js/app.js
|
@ -1,153 +0,0 @@
|
|||
/**
|
||||
* @author Christoph Wurst <christoph@winzerhof-wurst.at>
|
||||
*
|
||||
* Mail
|
||||
*
|
||||
* This code is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License, version 3,
|
||||
* as published by the Free Software Foundation.
|
||||
*
|
||||
* 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, version 3,
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||
*
|
||||
*/
|
||||
|
||||
define(function(require) {
|
||||
'use strict';
|
||||
|
||||
// Enable ES6 promise polyfill
|
||||
require('es6-promise').polyfill();
|
||||
|
||||
var $ = require('jquery');
|
||||
var Backbone = require('backbone');
|
||||
var Marionette = require('backbone.marionette');
|
||||
var OC = require('OC');
|
||||
var AppView = require('views/appview');
|
||||
var Search = require('search');
|
||||
var Radio = require('radio');
|
||||
var Router = require('router');
|
||||
var AccountController = require('controller/accountcontroller');
|
||||
var RouteController = require('routecontroller');
|
||||
|
||||
// Load controllers/services
|
||||
require('controller/foldercontroller');
|
||||
require('controller/messagecontroller');
|
||||
require('service/accountservice');
|
||||
require('service/avatarservice');
|
||||
require('service/aliasesservice');
|
||||
require('service/attachmentservice');
|
||||
require('service/backgroundsyncservice');
|
||||
require('service/davservice');
|
||||
require('service/folderservice');
|
||||
require('service/foldersyncservice');
|
||||
require('service/messageservice');
|
||||
require('service/preferenceservice');
|
||||
require('util/notificationhandler');
|
||||
|
||||
var Mail = Marionette.Application.extend({
|
||||
|
||||
_useExternalAvatars: false,
|
||||
|
||||
getUseExternalAvatars: function() {
|
||||
return this._useExternalAvatars;
|
||||
},
|
||||
|
||||
/**
|
||||
* Register the mailto protocol handler
|
||||
*/
|
||||
registerProtocolHandler: function() {
|
||||
if (window.navigator.registerProtocolHandler) {
|
||||
var url = window.location.protocol + '//' +
|
||||
window.location.host +
|
||||
OC.generateUrl('apps/mail/compose?uri=%s');
|
||||
try {
|
||||
window.navigator
|
||||
.registerProtocolHandler('mailto', url, OC.theme.name + ' Mail');
|
||||
} catch (e) {
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
requestNotificationPermissions: function() {
|
||||
Radio.ui.trigger('notification:request');
|
||||
},
|
||||
|
||||
/**
|
||||
* Register the actual search module in the search proxy
|
||||
*/
|
||||
setUpSearch: function() {
|
||||
new OCA.Search(Search.filter, function() {
|
||||
Search.filter('');
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Start syncing accounts in the background
|
||||
*
|
||||
* @param {AccountCollection} accounts
|
||||
*/
|
||||
startBackgroundSync: function(accounts) {
|
||||
Radio.sync.trigger('start', accounts);
|
||||
}
|
||||
});
|
||||
|
||||
Mail = new Mail();
|
||||
|
||||
Mail.on('start', function() {
|
||||
this._useExternalAvatars = $('#external-avatars').val() === 'true';
|
||||
|
||||
this.view = new AppView();
|
||||
|
||||
Radio.ui.trigger('content:loading', t('mail', 'Loading accounts'));
|
||||
|
||||
this.registerProtocolHandler();
|
||||
this.requestNotificationPermissions();
|
||||
this.setUpSearch();
|
||||
|
||||
var _this = this;
|
||||
AccountController.loadAccounts().then(function(accounts) {
|
||||
_this.router = new Router({
|
||||
controller: new RouteController(accounts)
|
||||
});
|
||||
Backbone.history.start();
|
||||
_this.startBackgroundSync(accounts);
|
||||
});
|
||||
|
||||
/**
|
||||
* Detects pasted text by browser plugins, and other software.
|
||||
* Check for changes in message bodies every second.
|
||||
*/
|
||||
setInterval((function() {
|
||||
// Begin the loop.
|
||||
return function() {
|
||||
|
||||
// Define which elements hold the message body.
|
||||
var MessageBody = $('.message-body');
|
||||
|
||||
/**
|
||||
* If the message body is displayed and has content:
|
||||
* Prepare the message body content for processing.
|
||||
* If there is new message body content to process:
|
||||
* Resize the text area.
|
||||
* Toggle the send button, based on whether the message is ready or not.
|
||||
* Prepare the new message body content for future processing.
|
||||
*/
|
||||
if (MessageBody.val()) {
|
||||
var OldMessageBody = MessageBody.val();
|
||||
var NewMessageBody = MessageBody.val();
|
||||
if (NewMessageBody !== OldMessageBody) {
|
||||
MessageBody.trigger('autosize.resize');
|
||||
OldMessageBody = NewMessageBody;
|
||||
}
|
||||
}
|
||||
};
|
||||
})(), 1000);
|
||||
});
|
||||
|
||||
return Mail;
|
||||
});
|
|
@ -1,57 +0,0 @@
|
|||
/* global Promise */
|
||||
|
||||
/**
|
||||
* Mail
|
||||
*
|
||||
* This file is licensed under the Affero General Public License version 3 or
|
||||
* later. See the COPYING file.
|
||||
*
|
||||
* @author Christoph Wurst <christoph@winzerhof-wurst.at>
|
||||
* @copyright Christoph Wurst 2016, 2017
|
||||
*/
|
||||
|
||||
define(function(require) {
|
||||
'use strict';
|
||||
|
||||
var CrashReport = require('crashreport');
|
||||
var FolderController = require('controller/foldercontroller');
|
||||
var Radio = require('radio');
|
||||
|
||||
/**
|
||||
* Load all accounts
|
||||
*
|
||||
* @returns {Promise}
|
||||
*/
|
||||
function loadAccounts() {
|
||||
// Do not show sidebar content until everything has been loaded
|
||||
Radio.ui.trigger('sidebar:loading');
|
||||
|
||||
return Radio.account.request('entities').then(function(accounts) {
|
||||
if (accounts.length === 0) {
|
||||
Radio.navigation.trigger('setup');
|
||||
Radio.ui.trigger('sidebar:accounts');
|
||||
return Promise.resolve(accounts);
|
||||
}
|
||||
|
||||
return Promise.all(accounts.map(function(account) {
|
||||
return FolderController.loadAccountFolders(account);
|
||||
})).then(function() {
|
||||
return accounts;
|
||||
});
|
||||
}).then(function(accounts) {
|
||||
// Show accounts regardless of the result of
|
||||
// loading the folders
|
||||
Radio.ui.trigger('sidebar:accounts');
|
||||
|
||||
return accounts;
|
||||
}, function(e) {
|
||||
console.error(e);
|
||||
CrashReport.report(e);
|
||||
Radio.ui.trigger('error:show', t('mail', 'Error while loading the accounts.'));
|
||||
});
|
||||
}
|
||||
|
||||
return {
|
||||
loadAccounts: loadAccounts
|
||||
};
|
||||
});
|
|
@ -1,250 +0,0 @@
|
|||
/* global Promise */
|
||||
|
||||
/**
|
||||
* @author Christoph Wurst <christoph@winzerhof-wurst.at>
|
||||
*
|
||||
* Mail
|
||||
*
|
||||
* This code is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License, version 3,
|
||||
* as published by the Free Software Foundation.
|
||||
*
|
||||
* 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, version 3,
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||
*
|
||||
*/
|
||||
|
||||
define(function(require) {
|
||||
'use strict';
|
||||
|
||||
var $ = require('jquery');
|
||||
var _ = require('underscore');
|
||||
var CrashReport = require('crashreport');
|
||||
var Radio = require('radio');
|
||||
var ErrorMessageFactory = require('util/errormessagefactory');
|
||||
|
||||
Radio.folder.reply('message:delete', deleteMessage);
|
||||
|
||||
/**
|
||||
* @param {Account} account
|
||||
* @returns {Promise}
|
||||
*/
|
||||
function loadFolders(account) {
|
||||
return Radio.folder.request('entities', account)
|
||||
.catch(CrashReport.report);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {Account} account
|
||||
* @param {Folder} folder
|
||||
* @param {string} searchQuery
|
||||
* @param {bool} openFirstMessage
|
||||
* @returns {Promise}
|
||||
*/
|
||||
function loadFolderMessages(account, folder, searchQuery, openFirstMessage) {
|
||||
openFirstMessage = openFirstMessage !== false;
|
||||
|
||||
// Set folder active
|
||||
Radio.folder.trigger('setactive', account, folder);
|
||||
|
||||
if (folder.get('noSelect')) {
|
||||
Radio.ui.trigger('content:error', t('mail', 'Can not load this folder.'));
|
||||
require('state').currentAccount = account;
|
||||
require('state').currentFolder = folder;
|
||||
Radio.ui.trigger('messagesview:message:setactive', null);
|
||||
require('state').currentlyLoading = null;
|
||||
return Promise.resolve();
|
||||
} else {
|
||||
return Radio.message.request('entities', account, folder, {
|
||||
cache: true,
|
||||
filter: searchQuery,
|
||||
replace: true
|
||||
}).then(function(messages) {
|
||||
Radio.ui.trigger('foldercontent:show', account, folder, {
|
||||
searchQuery: searchQuery
|
||||
});
|
||||
require('state').currentlyLoading = null;
|
||||
require('state').currentAccount = account;
|
||||
require('state').currentFolder = folder;
|
||||
Radio.ui.trigger('messagesview:message:setactive', null);
|
||||
|
||||
// Fade out the message composer
|
||||
$('#mail_new_message').prop('disabled', false);
|
||||
|
||||
if (messages.length > 0) {
|
||||
if (openFirstMessage) {
|
||||
var message = messages.first();
|
||||
Radio.message.trigger('load', message.folder.account, message.folder, message);
|
||||
}
|
||||
}
|
||||
}, function(error) {
|
||||
console.error('error while loading messages: ', error);
|
||||
var icon;
|
||||
if (folder.get('specialRole')) {
|
||||
icon = 'icon-' + folder.get('specialRole');
|
||||
}
|
||||
Radio.ui.trigger('content:error', ErrorMessageFactory.getRandomFolderErrorMessage(folder), icon);
|
||||
|
||||
// Set the old folder as being active
|
||||
var oldFolder = require('state').currentFolder;
|
||||
Radio.folder.trigger('setactive', account, oldFolder);
|
||||
}).catch(CrashReport.report);
|
||||
}
|
||||
}
|
||||
|
||||
var loadFolderMessagesDebounced = _.debounce(loadFolderMessages, 1000);
|
||||
|
||||
/**
|
||||
* @param {Account} account
|
||||
* @param {Folder} folder
|
||||
* @param {bool} loadFirstMessage
|
||||
* @returns {Promise}
|
||||
*/
|
||||
function showFolder(account, folder, loadFirstMessage) {
|
||||
loadFirstMessage = loadFirstMessage !== false;
|
||||
Radio.ui.trigger('search:set', '');
|
||||
Radio.ui.trigger('content:loading', folder.get('name'));
|
||||
|
||||
return new Promise(function(resolve, reject) {
|
||||
_.defer(function() {
|
||||
var loading = loadFolderMessages(account, folder, undefined, loadFirstMessage);
|
||||
|
||||
// Save current folder
|
||||
Radio.folder.trigger('setactive', account, folder);
|
||||
require('state').currentAccount = account;
|
||||
require('state').currentFolder = folder;
|
||||
|
||||
loading.then(resolve).catch(reject);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {Account} account
|
||||
* @param {Folder} folder
|
||||
* @param {string} query
|
||||
* @returns {Promise}
|
||||
*/
|
||||
function searchFolder(account, folder, query) {
|
||||
// If this was triggered by a URL change, we set the search input manually
|
||||
Radio.ui.trigger('search:set', query);
|
||||
|
||||
Radio.ui.trigger('content:loading', t('mail', 'Searching for {query}', {
|
||||
query: query
|
||||
}));
|
||||
_.defer(function() {
|
||||
loadFolderMessagesDebounced(account, folder, query);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {Folder} folder
|
||||
* @param {Folder} currentFolder
|
||||
* @returns {Array} array of two folders, the first one is the individual
|
||||
*/
|
||||
function getSpecificAndUnifiedFolder(folder, currentFolder) {
|
||||
// Case 1: we're currently in a unified folder
|
||||
if (currentFolder.account.get('accountId') === -1) {
|
||||
return [folder, currentFolder];
|
||||
}
|
||||
|
||||
// Locate unified folder if existent
|
||||
var unifiedAccount = require('state').accounts.get(-1);
|
||||
var unifiedFolder = unifiedAccount ? unifiedAccount.folders.first() : null;
|
||||
|
||||
// Case 2: we're in a specific folder and a unified one is available too
|
||||
if (currentFolder.get('specialRole') === 'inbox' && unifiedFolder) {
|
||||
return [folder, unifiedFolder];
|
||||
}
|
||||
|
||||
// Case 3: we're in a specific folder, but there's no unified one
|
||||
return [folder, null];
|
||||
}
|
||||
|
||||
/**
|
||||
* Call supplied function with folder as first parameter, if
|
||||
* the folder is not undefined
|
||||
*
|
||||
* @param {Array<Folder>} folders
|
||||
* @param {Function} fn
|
||||
* @returns {mixed}
|
||||
*/
|
||||
function applyOnFolders(folders, fn) {
|
||||
folders.forEach(function(folder) {
|
||||
if (!folder) {
|
||||
// ignore
|
||||
return;
|
||||
}
|
||||
|
||||
return fn(folder);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {Message} message
|
||||
* @param {Folder} currentFolder
|
||||
* @returns {Promise}
|
||||
*/
|
||||
function deleteMessage(message, currentFolder) {
|
||||
var folders = getSpecificAndUnifiedFolder(message.folder, currentFolder);
|
||||
|
||||
applyOnFolders(folders, function(folder) {
|
||||
// Update total counter and prevent negative values
|
||||
folder.set('total', Math.max(0, folder.get('total')-1));
|
||||
|
||||
if(message.get('flags').get('unseen') === true) {
|
||||
folder.set('unseen', Math.max(0, folder.get('unseen')-1));
|
||||
}
|
||||
});
|
||||
|
||||
var searchCollection = currentFolder.messages;
|
||||
var index = searchCollection.indexOf(message);
|
||||
// Select previous or first
|
||||
if (index === 0) {
|
||||
index = 1;
|
||||
} else {
|
||||
index = index - 1;
|
||||
}
|
||||
var nextMessage = searchCollection.at(index);
|
||||
|
||||
// Remove message
|
||||
applyOnFolders(folders, function(folder) {
|
||||
folder.messages.remove(message);
|
||||
});
|
||||
|
||||
if (require('state').currentMessage && require('state').currentMessage.get('id') === message.id) {
|
||||
if (nextMessage) {
|
||||
Radio.message.trigger('load', nextMessage.folder.account, nextMessage.folder, nextMessage);
|
||||
} else {
|
||||
Radio.ui.trigger('message:empty');
|
||||
}
|
||||
}
|
||||
|
||||
return Radio.message.request('delete', message)
|
||||
.catch(function(err) {
|
||||
console.error(err);
|
||||
|
||||
Radio.ui.trigger('error:show', t('mail', 'Error while deleting message.'));
|
||||
|
||||
applyOnFolders(folders, function(folder) {
|
||||
// Restore counter
|
||||
|
||||
folder.set('total', folder.previousAttributes.total);
|
||||
|
||||
// Add the message to the collection again
|
||||
folder.addMessage(message);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
return {
|
||||
loadAccountFolders: loadFolders,
|
||||
showFolder: showFolder,
|
||||
searchFolder: searchFolder
|
||||
};
|
||||
});
|
|
@ -1,209 +0,0 @@
|
|||
/**
|
||||
* @author Christoph Wurst <christoph@winzerhof-wurst.at>
|
||||
*
|
||||
* Mail
|
||||
*
|
||||
* This code is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License, version 3,
|
||||
* as published by the Free Software Foundation.
|
||||
*
|
||||
* 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, version 3,
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||
*
|
||||
*/
|
||||
|
||||
define(function(require) {
|
||||
'use strict';
|
||||
|
||||
var $ = require('jquery');
|
||||
var _ = require('underscore');
|
||||
var OC = require('OC');
|
||||
var Radio = require('radio');
|
||||
var ErrorMessageFactory = require('util/errormessagefactory');
|
||||
|
||||
Radio.message.on('load', load);
|
||||
Radio.message.on('forward', openForwardComposer);
|
||||
Radio.message.on('flag', flagMessage);
|
||||
Radio.message.on('move', moveMessage);
|
||||
|
||||
/**
|
||||
* @param {Account} account
|
||||
* @param {Folder} folder
|
||||
* @param {Message} message
|
||||
* @param {object} options
|
||||
*/
|
||||
function load(account, folder, message, options) {
|
||||
options = options || {};
|
||||
var defaultOptions = {
|
||||
force: false
|
||||
};
|
||||
_.defaults(options, defaultOptions);
|
||||
|
||||
// Do not reload email when clicking same again
|
||||
if (require('state').currentMessage && require('state').currentMessage.get('id') === message.get('id')) {
|
||||
return;
|
||||
}
|
||||
|
||||
// TODO: expression is useless?
|
||||
if (!options.force && false) {
|
||||
return;
|
||||
}
|
||||
|
||||
// check if message is a draft
|
||||
var draft = require('state').currentFolder.get('specialRole') === 'drafts';
|
||||
|
||||
// close email first
|
||||
// Check if message is open
|
||||
if (require('state').currentMessage !== null) {
|
||||
var lastMessage = require('state').currentMessage;
|
||||
Radio.ui.trigger('messagesview:message:setactive', null);
|
||||
if (lastMessage.get('id') === message.get('id')) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
Radio.ui.trigger('message:loading');
|
||||
|
||||
// Set current Message as active
|
||||
Radio.ui.trigger('messagesview:message:setactive', message);
|
||||
require('state').currentMessageBody = '';
|
||||
|
||||
// Fade out the message composer
|
||||
$('#mail_new_message').prop('disabled', false);
|
||||
|
||||
Radio.message.request('entity', account, folder, message.get('id')).then(function(messageBody) {
|
||||
if (draft) {
|
||||
Radio.ui.trigger('composer:show', messageBody, true);
|
||||
} else {
|
||||
Radio.ui.trigger('message:show', message, messageBody);
|
||||
}
|
||||
}, function() {
|
||||
Radio.ui.trigger('message:error', ErrorMessageFactory.getRandomMessageErrorMessage());
|
||||
});
|
||||
}
|
||||
|
||||
function openForwardComposer() {
|
||||
var header = '\n\n\n\n-------- ' +
|
||||
t('mail', 'Forwarded message') +
|
||||
' --------\n';
|
||||
|
||||
// TODO: find a better way to get the current message body
|
||||
var data = {
|
||||
subject: 'Fwd: ' + require('state').currentMessageSubject,
|
||||
body: header + require('state').currentMessageBody.replace(/<br \/>/g, '\n')
|
||||
};
|
||||
|
||||
if (require('state').currentAccount.get('accountId') !== -1) {
|
||||
data.accountId = require('state').currentAccount.get('accountId');
|
||||
}
|
||||
|
||||
Radio.ui.trigger('composer:show', data);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {Message} message
|
||||
* @param {number} attachmentId
|
||||
* @param {function} callback
|
||||
* @returns {Promise}
|
||||
*/
|
||||
function saveAttachmentToFiles(message, attachmentId, callback) {
|
||||
var saveAll = _.isUndefined(attachmentId);
|
||||
|
||||
return new Promise(function(resolve, reject) {
|
||||
OC.dialogs.filepicker(
|
||||
t('mail', 'Choose a folder to store the attachment in'),
|
||||
function(path) {
|
||||
if (typeof callback === 'function') {
|
||||
callback();
|
||||
}
|
||||
Radio.message.request('save:cloud', message, attachmentId, path).then(function() {
|
||||
if (saveAll) {
|
||||
Radio.ui.trigger('error:show', t('mail', 'Attachments saved to Files.'));
|
||||
} else {
|
||||
Radio.ui.trigger('error:show', t('mail', 'Attachment saved to Files.'));
|
||||
}
|
||||
resolve();
|
||||
}, function() {
|
||||
if (saveAll) {
|
||||
Radio.ui.trigger('error:show', t('mail', 'Error while saving attachments to Files.'));
|
||||
} else {
|
||||
Radio.ui.trigger('error:show', t('mail', 'Error while saving attachment to Files.'));
|
||||
}
|
||||
reject();
|
||||
});
|
||||
}, false, 'httpd/unix-directory', true);
|
||||
});
|
||||
}
|
||||
|
||||
function flagMessage(message, flag, value) {
|
||||
var folder = message.folder;
|
||||
var account = folder.account;
|
||||
var prevUnseen = folder.get('unseen');
|
||||
|
||||
if (message.get('flags').get(flag) === value) {
|
||||
// Nothing to do
|
||||
return;
|
||||
}
|
||||
message.get('flags').set(flag, value);
|
||||
|
||||
// Update folder counter
|
||||
if (flag === 'unseen') {
|
||||
var unseen = Math.max(0, prevUnseen + (value ? 1 : -1));
|
||||
folder.set('unseen', unseen);
|
||||
}
|
||||
|
||||
// Update the folder to reflect the new unread count
|
||||
Radio.ui.trigger('title:update');
|
||||
|
||||
Radio.message.request('flag', account, folder, message, flag, value).
|
||||
catch(function() {
|
||||
Radio.ui.trigger('error:show', t('mail', 'Message flag could not be set.'));
|
||||
|
||||
// Restore previous state
|
||||
message.get('flags').set(flag, !value);
|
||||
folder.set('unseen', prevUnseen);
|
||||
Radio.ui.trigger('title:update');
|
||||
});
|
||||
}
|
||||
|
||||
function moveMessage(sourceAccount, sourceFolder, message, destAccount,
|
||||
destFolder) {
|
||||
if (sourceAccount.get('accountId') === destAccount.get('accountId')
|
||||
&& sourceFolder.get('id') === destFolder.get('id')) {
|
||||
// Nothing to move
|
||||
return;
|
||||
}
|
||||
|
||||
sourceFolder.messages.remove(message);
|
||||
destFolder.addMessage(message);
|
||||
|
||||
Radio.message.request('move', sourceAccount, sourceFolder, message, destAccount, destFolder).
|
||||
then(function() {
|
||||
// TODO: update counters
|
||||
}, function() {
|
||||
Radio.ui.trigger('error:show', t('mail', 'Could not move message.'));
|
||||
sourceFolder.addMessage(message);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {Account} account
|
||||
* @param {Folder} folder
|
||||
* @param {number} messageId
|
||||
* @param {function} callback
|
||||
* @returns {Promise}
|
||||
*/
|
||||
function saveAttachmentsToFiles(message, callback) {
|
||||
return saveAttachmentToFiles(message, null, callback);
|
||||
}
|
||||
|
||||
return {
|
||||
saveAttachmentToFiles: saveAttachmentToFiles,
|
||||
saveAttachmentsToFiles: saveAttachmentsToFiles
|
||||
};
|
||||
});
|
37
js/init.js
37
js/init.js
|
@ -1,37 +0,0 @@
|
|||
/**
|
||||
* @author Christoph Wurst <christoph@winzerhof-wurst.at>
|
||||
*
|
||||
* Mail
|
||||
*
|
||||
* This code is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License, version 3,
|
||||
* as published by the Free Software Foundation.
|
||||
*
|
||||
* 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, version 3,
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||
*
|
||||
*/
|
||||
|
||||
define(function(require) {
|
||||
var $ = require('jquery');
|
||||
var OC = require('OC');
|
||||
var App = require('app');
|
||||
|
||||
$(function() {
|
||||
// Conigure CSRF token
|
||||
$.ajaxSetup({
|
||||
headers: {
|
||||
requesttoken: OC.requestToken
|
||||
}
|
||||
});
|
||||
|
||||
// Start app when the page is ready
|
||||
console.log('Starting Mail …');
|
||||
App.start();
|
||||
});
|
||||
});
|
|
@ -0,0 +1,12 @@
|
|||
import Vue from 'vue'
|
||||
import App from './App.vue'
|
||||
import router from './router'
|
||||
import store from './store'
|
||||
|
||||
Vue.config.productionTip = false
|
||||
|
||||
new Vue({
|
||||
router,
|
||||
store,
|
||||
render: h => h(App)
|
||||
}).$mount('#content')
|
|
@ -1,105 +0,0 @@
|
|||
/**
|
||||
* Mail
|
||||
*
|
||||
* This file is licensed under the Affero General Public License version 3 or
|
||||
* later. See the COPYING file.
|
||||
*
|
||||
* @author Christoph Wurst <christoph@winzerhof-wurst.at>
|
||||
* @copyright Christoph Wurst 2015, 2017
|
||||
*/
|
||||
|
||||
define(function(require) {
|
||||
'use strict';
|
||||
|
||||
var _ = require('underscore');
|
||||
var Backbone = require('backbone');
|
||||
var FolderCollection = require('models/foldercollection');
|
||||
var AliasesCollection = require('models/aliasescollection');
|
||||
var OC = require('OC');
|
||||
|
||||
/**
|
||||
* @class Account
|
||||
*/
|
||||
var Account = Backbone.Model.extend({
|
||||
defaults: {
|
||||
aliases: [],
|
||||
specialFolders: [],
|
||||
isUnified: false
|
||||
},
|
||||
idAttribute: 'accountId',
|
||||
url: function() {
|
||||
return OC.generateUrl('apps/mail/api/accounts');
|
||||
},
|
||||
initialize: function() {
|
||||
this.folders = new FolderCollection();
|
||||
this.set('aliases', new AliasesCollection(this.get('aliases')));
|
||||
},
|
||||
_getFolderByIdRecursively: function(folder, folderId) {
|
||||
if (!folder) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (folder.get('id') === folderId) {
|
||||
return folder;
|
||||
}
|
||||
|
||||
var subFolders = folder.folders;
|
||||
if (!subFolders) {
|
||||
return null;
|
||||
}
|
||||
for (var i = 0; i < subFolders.length; i++) {
|
||||
var subFolder = this._getFolderByIdRecursively(subFolders.at(i), folderId);
|
||||
if (subFolder) {
|
||||
return subFolder;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
},
|
||||
/**
|
||||
* @param {Folder} folder
|
||||
*/
|
||||
addFolder: function(folder) {
|
||||
folder.account = this;
|
||||
this.folders.add(folder);
|
||||
},
|
||||
getFolderById: function(folderId) {
|
||||
if (!this.folders) {
|
||||
return undefined;
|
||||
}
|
||||
for (var i = 0; i < this.folders.length; i++) {
|
||||
var result = this._getFolderByIdRecursively(this.folders.at(i), folderId);
|
||||
if (result) {
|
||||
return result;
|
||||
}
|
||||
}
|
||||
return undefined;
|
||||
},
|
||||
getSpecialFolder: function() {
|
||||
if (!this.folders) {
|
||||
return undefined;
|
||||
}
|
||||
return _.find(this.folders, function(folder) {
|
||||
// TODO: handle special folders in subfolder properly
|
||||
if (folder.get('specialRole') === 'draft') {
|
||||
return true;
|
||||
}
|
||||
});
|
||||
},
|
||||
toJSON: function() {
|
||||
var data = Backbone.Model.prototype.toJSON.call(this);
|
||||
if (data.folders && data.folders.toJSON) {
|
||||
data.folders = data.folders.toJSON();
|
||||
}
|
||||
if (data.aliases && data.aliases.toJSON) {
|
||||
data.aliases = data.aliases.toJSON();
|
||||
}
|
||||
if (!data.id) {
|
||||
data.id = this.cid;
|
||||
}
|
||||
return data;
|
||||
}
|
||||
});
|
||||
|
||||
return Account;
|
||||
});
|
|
@ -1,32 +0,0 @@
|
|||
/**
|
||||
* Mail
|
||||
*
|
||||
* This file is licensed under the Affero General Public License version 3 or
|
||||
* later. See the COPYING file.
|
||||
*
|
||||
* @author Christoph Wurst <christoph@winzerhof-wurst.at>
|
||||
* @copyright Christoph Wurst 2015, 2016
|
||||
*/
|
||||
|
||||
define(function(require) {
|
||||
'use strict';
|
||||
|
||||
var Backbone = require('backbone');
|
||||
var Account = require('models/account');
|
||||
var OC = require('OC');
|
||||
|
||||
/**
|
||||
* @class AccountCollection
|
||||
*/
|
||||
var AccountCollection = Backbone.Collection.extend({
|
||||
model: Account,
|
||||
url: function() {
|
||||
return OC.generateUrl('apps/mail/api/accounts');
|
||||
},
|
||||
comparator: function(account) {
|
||||
return account.get('accountId');
|
||||
}
|
||||
});
|
||||
|
||||
return AccountCollection;
|
||||
});
|
|
@ -1,38 +0,0 @@
|
|||
/**
|
||||
* Mail
|
||||
*
|
||||
* This file is licensed under the Affero General Public License version 3 or
|
||||
* later. See the COPYING file.
|
||||
*
|
||||
* @author Tahaa Karim <tahaalibra@gmail.com>
|
||||
* @copyright Tahaa Karim 2016
|
||||
*/
|
||||
|
||||
define(function(require) {
|
||||
'use strict';
|
||||
|
||||
var Backbone = require('backbone');
|
||||
|
||||
/**
|
||||
* @class Alias
|
||||
*/
|
||||
var Alias = Backbone.Model.extend({
|
||||
defaults: {
|
||||
},
|
||||
initialize: function() {
|
||||
|
||||
},
|
||||
toJSON: function() {
|
||||
var data = Backbone.Model.prototype.toJSON.call(this);
|
||||
if (data.alias && data.alias.toJSON) {
|
||||
data.alias = data.alias.toJSON();
|
||||
}
|
||||
if (!data.id) {
|
||||
data.id = this.cid;
|
||||
}
|
||||
return data;
|
||||
}
|
||||
});
|
||||
|
||||
return Alias;
|
||||
});
|
|
@ -1,25 +0,0 @@
|
|||
/**
|
||||
* Mail
|
||||
*
|
||||
* This file is licensed under the Affero General Public License version 3 or
|
||||
* later. See the COPYING file.
|
||||
*
|
||||
* @author Tahaa Karim <tahaalibra@gmail.com>
|
||||
* @copyright Tahaa Karim 2016
|
||||
*/
|
||||
|
||||
define(function(require) {
|
||||
'use strict';
|
||||
|
||||
var Backbone = require('backbone');
|
||||
var Alias = require('models/alias');
|
||||
|
||||
/**
|
||||
* @class AliasesCollection
|
||||
*/
|
||||
var AliasesCollection = Backbone.Collection.extend({
|
||||
model: Alias
|
||||
});
|
||||
|
||||
return AliasesCollection;
|
||||
});
|
|
@ -1,45 +0,0 @@
|
|||
/**
|
||||
* Mail
|
||||
*
|
||||
* This file is licensed under the Affero General Public License version 3 or
|
||||
* later. See the COPYING file.
|
||||
*
|
||||
* @author Christoph Wurst <christoph@winzerhof-wurst.at>
|
||||
* @copyright Christoph Wurst 2017
|
||||
*/
|
||||
|
||||
define(function(require) {
|
||||
'use strict';
|
||||
|
||||
var Backbone = require('backbone');
|
||||
var _ = require('underscore');
|
||||
|
||||
/**
|
||||
* @class Attachment
|
||||
*/
|
||||
var Attachment = Backbone.Model.extend({
|
||||
defaults: {
|
||||
isLocal: false
|
||||
},
|
||||
|
||||
initialize: function() {
|
||||
if (_.isUndefined(this.get('id'))) {
|
||||
this.set('id', _.uniqueId());
|
||||
}
|
||||
|
||||
var s = this.get('fileName');
|
||||
|
||||
if (_.isUndefined(s)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (s.charAt(0) === '/') {
|
||||
s = s.substr(1);
|
||||
}
|
||||
|
||||
this.set('displayName', s);
|
||||
}
|
||||
});
|
||||
|
||||
return Attachment;
|
||||
});
|
|
@ -1,25 +0,0 @@
|
|||
/**
|
||||
* Mail
|
||||
*
|
||||
* This file is licensed under the Affero General Public License version 3 or
|
||||
* later. See the COPYING file.
|
||||
*
|
||||
* @author Christoph Wurst <christoph@winzerhof-wurst.at>
|
||||
* @copyright Christoph Wurst 2015
|
||||
*/
|
||||
|
||||
define(function(require) {
|
||||
'use strict';
|
||||
|
||||
var Backbone = require('backbone');
|
||||
var Attachment = require('models/attachment');
|
||||
|
||||
/**
|
||||
* @class AttachmentCollection
|
||||
*/
|
||||
var AttachmentCollection = Backbone.Collection.extend({
|
||||
model: Attachment
|
||||
});
|
||||
|
||||
return AttachmentCollection;
|
||||
});
|
|
@ -1,26 +0,0 @@
|
|||
/**
|
||||
* @author Christoph Wurst <christoph@winzerhof-wurst.at>
|
||||
*
|
||||
* Mail
|
||||
*
|
||||
* This code is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License, version 3,
|
||||
* as published by the Free Software Foundation.
|
||||
*
|
||||
* 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, version 3,
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||
*
|
||||
*/
|
||||
|
||||
define(function(require) {
|
||||
'use strict';
|
||||
|
||||
var Backbone = require('backbone');
|
||||
|
||||
return Backbone.Model.extend({});
|
||||
});
|
|
@ -1,95 +0,0 @@
|
|||
/**
|
||||
* Mail
|
||||
*
|
||||
* This file is licensed under the Affero General Public License version 3 or
|
||||
* later. See the COPYING file.
|
||||
*
|
||||
* @author Christoph Wurst <christoph@winzerhof-wurst.at>
|
||||
* @copyright Christoph Wurst 2015
|
||||
*/
|
||||
|
||||
define(function(require) {
|
||||
'use strict';
|
||||
|
||||
var _ = require('underscore');
|
||||
var Backbone = require('backbone');
|
||||
|
||||
/**
|
||||
* @class Folder
|
||||
*/
|
||||
var Folder = Backbone.Model.extend({
|
||||
messages: undefined,
|
||||
account: undefined,
|
||||
folder: undefined,
|
||||
folders: undefined,
|
||||
defaults: {
|
||||
open: false,
|
||||
folders: [],
|
||||
messagesLoaded: false
|
||||
},
|
||||
|
||||
initialize: function() {
|
||||
var FolderCollection = require('models/foldercollection');
|
||||
var MessageCollection = require('models/messagecollection');
|
||||
var UnifiedMessageCollection = require('models/unifiedmessagecollection');
|
||||
this.account = this.get('account');
|
||||
this.unset('account');
|
||||
this.folders = new FolderCollection(this.get('folders') || []);
|
||||
this.folders.forEach(_.bind(function(folder) {
|
||||
folder.account = this.account;
|
||||
}, this));
|
||||
this.unset('folders');
|
||||
if (this.account && this.account.get('isUnified') === true) {
|
||||
this.messages = new UnifiedMessageCollection();
|
||||
} else {
|
||||
this.messages = new MessageCollection();
|
||||
}
|
||||
},
|
||||
|
||||
toggleOpen: function() {
|
||||
this.set({open: !this.get('open')});
|
||||
},
|
||||
|
||||
/**
|
||||
* @param {Message} message
|
||||
*/
|
||||
addMessage: function(message) {
|
||||
if (this.account.id !== -1) {
|
||||
// Non-unified folder messages should keep their source folder
|
||||
message.folder = this;
|
||||
}
|
||||
message = this.messages.add(message);
|
||||
if (this.account.id === -1) {
|
||||
message.set('unifiedId', this.messages.getUnifiedId(message));
|
||||
}
|
||||
return message;
|
||||
},
|
||||
|
||||
/**
|
||||
* @param {Array<Message>} messages
|
||||
*/
|
||||
addMessages: function(messages) {
|
||||
var _this = this;
|
||||
return _.map(messages, _this.addMessage, this);
|
||||
},
|
||||
|
||||
/**
|
||||
* @param {Folder} folder
|
||||
*/
|
||||
|
||||
addFolder: function(folder) {
|
||||
folder = this.folders.add(folder);
|
||||
folder.account = this.account;
|
||||
},
|
||||
|
||||
toJSON: function() {
|
||||
var data = Backbone.Model.prototype.toJSON.call(this);
|
||||
if (!data.id) {
|
||||
data.id = this.cid;
|
||||
}
|
||||
return data;
|
||||
}
|
||||
});
|
||||
|
||||
return Folder;
|
||||
});
|
|
@ -1,25 +0,0 @@
|
|||
/**
|
||||
* Mail
|
||||
*
|
||||
* This file is licensed under the Affero General Public License version 3 or
|
||||
* later. See the COPYING file.
|
||||
*
|
||||
* @author Christoph Wurst <christoph@winzerhof-wurst.at>
|
||||
* @copyright Christoph Wurst 2015, 2016
|
||||
*/
|
||||
|
||||
define(function(require) {
|
||||
'use strict';
|
||||
|
||||
var Backbone = require('backbone');
|
||||
var Folder = require('models/folder');
|
||||
|
||||
/**
|
||||
* @class FolderCollection
|
||||
*/
|
||||
var FolderCollection = Backbone.Collection.extend({
|
||||
model: Folder
|
||||
});
|
||||
|
||||
return FolderCollection;
|
||||
});
|
|
@ -1,36 +0,0 @@
|
|||
/**
|
||||
* Mail
|
||||
*
|
||||
* This file is licensed under the Affero General Public License version 3 or
|
||||
* later. See the COPYING file.
|
||||
*
|
||||
* @author Christoph Wurst <christoph@winzerhof-wurst.at>
|
||||
* @author Luc Calaresu <dev@calaresu.com>
|
||||
*/
|
||||
|
||||
define(function(require) {
|
||||
'use strict';
|
||||
|
||||
var Attachment = require('models/attachment');
|
||||
|
||||
var LocalAttachment = Attachment.extend({
|
||||
|
||||
defaults: {
|
||||
progress: 0,
|
||||
uploadStatus: 0, /* 0=pending, 1=ongoing, 2=error, 3=success */
|
||||
isLocal: true
|
||||
},
|
||||
|
||||
/**
|
||||
* @param {Event} evt
|
||||
*/
|
||||
onProgress: function(evt) {
|
||||
if (evt.lengthComputable) {
|
||||
this.set('uploadStatus', 1);
|
||||
this.set('progress', evt.loaded / evt.total);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
return LocalAttachment;
|
||||
});
|
|
@ -1,57 +0,0 @@
|
|||
/**
|
||||
* Mail
|
||||
*
|
||||
* This file is licensed under the Affero General Public License version 3 or
|
||||
* later. See the COPYING file.
|
||||
*
|
||||
* @author Christoph Wurst <christoph@winzerhof-wurst.at>
|
||||
* @copyright Christoph Wurst 2015
|
||||
*/
|
||||
|
||||
define(function(require) {
|
||||
'use strict';
|
||||
|
||||
var Backbone = require('backbone');
|
||||
var MessageFlags = require('models/messageflags');
|
||||
|
||||
/**
|
||||
* @class Message
|
||||
*/
|
||||
var Message = Backbone.Model.extend({
|
||||
folder: undefined,
|
||||
defaults: {
|
||||
flags: [],
|
||||
active: false,
|
||||
from: [],
|
||||
to: [],
|
||||
cc: [],
|
||||
bcc: []
|
||||
},
|
||||
initialize: function() {
|
||||
this.set('flags', new MessageFlags(this.get('flags')));
|
||||
if (this.get('folder')) {
|
||||
// Folder should be a simple property
|
||||
this.folder = this.get('folder');
|
||||
this.unset('folder');
|
||||
}
|
||||
this.listenTo(this.get('flags'), 'change', this._transformEvent);
|
||||
this.set('dateMicro', this.get('dateInt') * 1000);
|
||||
},
|
||||
_transformEvent: function() {
|
||||
this.trigger('change');
|
||||
this.trigger('change:flags', this);
|
||||
},
|
||||
toJSON: function() {
|
||||
var data = Backbone.Model.prototype.toJSON.call(this);
|
||||
if (data.flags && data.flags.toJSON) {
|
||||
data.flags = data.flags.toJSON();
|
||||
}
|
||||
if (!data.id) {
|
||||
data.id = this.cid;
|
||||
}
|
||||
return data;
|
||||
}
|
||||
});
|
||||
|
||||
return Message;
|
||||
});
|
|
@ -1,28 +0,0 @@
|
|||
/**
|
||||
* Mail
|
||||
*
|
||||
* This file is licensed under the Affero General Public License version 3 or
|
||||
* later. See the COPYING file.
|
||||
*
|
||||
* @author Christoph Wurst <christoph@winzerhof-wurst.at>
|
||||
* @copyright Christoph Wurst 2015
|
||||
*/
|
||||
|
||||
define(function(require) {
|
||||
'use strict';
|
||||
|
||||
var Backbone = require('backbone');
|
||||
var Message = require('models/message');
|
||||
|
||||
/**
|
||||
* @class MessageCollection
|
||||
*/
|
||||
var MessageCollection = Backbone.Collection.extend({
|
||||
model: Message,
|
||||
comparator: function(message) {
|
||||
return message.get('dateInt') * -1;
|
||||
}
|
||||
});
|
||||
|
||||
return MessageCollection;
|
||||
});
|
|
@ -1,26 +0,0 @@
|
|||
/**
|
||||
* Mail
|
||||
*
|
||||
* This file is licensed under the Affero General Public License version 3 or
|
||||
* later. See the COPYING file.
|
||||
*
|
||||
* @author Christoph Wurst <christoph@winzerhof-wurst.at>
|
||||
* @copyright Christoph Wurst 2015
|
||||
*/
|
||||
|
||||
define(function(require) {
|
||||
'use strict';
|
||||
|
||||
var Backbone = require('backbone');
|
||||
|
||||
/**
|
||||
* @class MessageFlags
|
||||
*/
|
||||
var MessageFlags = Backbone.Model.extend({
|
||||
defaults: {
|
||||
answered: false
|
||||
}
|
||||
});
|
||||
|
||||
return MessageFlags;
|
||||
});
|
|
@ -1,32 +0,0 @@
|
|||
/**
|
||||
* Mail
|
||||
*
|
||||
* This file is licensed under the Affero General Public License version 3 or
|
||||
* later. See the COPYING file.
|
||||
*
|
||||
* @author Christoph Wurst <christoph@winzerhof-wurst.at>
|
||||
* @copyright Christoph Wurst 2017
|
||||
*/
|
||||
|
||||
define(function(require) {
|
||||
'use strict';
|
||||
|
||||
var MessageCollection = require('models/messagecollection');
|
||||
|
||||
/**
|
||||
* @class UnifiedMessageCollection
|
||||
*/
|
||||
var UnifiedMessageCollection = MessageCollection.extend({
|
||||
|
||||
modelId: function(attrs) {
|
||||
return attrs.unifiedId;
|
||||
},
|
||||
|
||||
getUnifiedId: function(message) {
|
||||
return message.id + '-' + message.folder.id + '-' + message.folder.account.id;
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
return UnifiedMessageCollection;
|
||||
});
|
42
js/radio.js
42
js/radio.js
|
@ -1,42 +0,0 @@
|
|||
/**
|
||||
* Mail
|
||||
*
|
||||
* This file is licensed under the Affero General Public License version 3 or
|
||||
* later. See the COPYING file.
|
||||
*
|
||||
* @author Christoph Wurst <christoph@winzerhof-wurst.at>
|
||||
* @copyright Christoph Wurst 2016
|
||||
*/
|
||||
|
||||
define(function(require) {
|
||||
'use strict';
|
||||
|
||||
var _ = require('underscore');
|
||||
var Radio = require('backbone.radio');
|
||||
|
||||
var channelNames = [
|
||||
'account',
|
||||
'aliases',
|
||||
'attachment',
|
||||
'avatar',
|
||||
'folder',
|
||||
'dav',
|
||||
'keyboard',
|
||||
'message',
|
||||
'navigation',
|
||||
'notification',
|
||||
'preference',
|
||||
'state',
|
||||
'sync',
|
||||
'ui'
|
||||
];
|
||||
|
||||
var channels = {};
|
||||
_.each(channelNames, function(channelName) {
|
||||
channels[channelName] = Radio.channel(channelName);
|
||||
// Uncomment the following line for debugging
|
||||
// Radio.tuneIn(channelName);
|
||||
});
|
||||
|
||||
return channels;
|
||||
});
|
|
@ -1,193 +0,0 @@
|
|||
/* global Promise */
|
||||
|
||||
/**
|
||||
* @author Christoph Wurst <christoph@winzerhof-wurst.at>
|
||||
*
|
||||
* Mail
|
||||
*
|
||||
* This code is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License, version 3,
|
||||
* as published by the Free Software Foundation.
|
||||
*
|
||||
* 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, version 3,
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||
*
|
||||
*/
|
||||
|
||||
define(function(require) {
|
||||
'use strict';
|
||||
|
||||
var _ = require('underscore');
|
||||
var Backbone = require('backbone');
|
||||
var CrashReport = require('crashreport');
|
||||
var Radio = require('radio');
|
||||
var FolderController = require('controller/foldercontroller');
|
||||
|
||||
/**
|
||||
* @class RoutingController
|
||||
*/
|
||||
var RoutingController = function(accounts) {
|
||||
this.initialize(accounts);
|
||||
};
|
||||
|
||||
RoutingController.prototype = {
|
||||
accounts: undefined,
|
||||
initialize: function(accounts) {
|
||||
this.accounts = accounts;
|
||||
|
||||
Radio.navigation.on('folder', _.bind(this.showFolder, this));
|
||||
Radio.navigation.on('search', _.bind(this.searchFolder, this));
|
||||
Radio.navigation.on('setup', _.bind(this.showSetup, this));
|
||||
Radio.navigation.on('accountsettings', _.bind(this.showAccountSettings, this));
|
||||
Radio.navigation.on('keyboardshortcuts', _.bind(this.showKeyboardShortcuts, this));
|
||||
},
|
||||
_navigate: function(route, options) {
|
||||
options = options || {};
|
||||
Backbone.history.navigate(route, options);
|
||||
},
|
||||
|
||||
/**
|
||||
* Handle mailto links
|
||||
*
|
||||
* @returns {Promise}
|
||||
*/
|
||||
_handleMailto: function(params) {
|
||||
var composerOptions = {};
|
||||
params = params.split('&');
|
||||
|
||||
_.each(params, function(param) {
|
||||
param = param.split('=');
|
||||
var key = param[0];
|
||||
var value = param[1];
|
||||
value = decodeURIComponent((value).replace(/\+/g, '%20'));
|
||||
|
||||
switch (key) {
|
||||
case 'mailto':
|
||||
case 'to':
|
||||
composerOptions.to = value;
|
||||
break;
|
||||
case 'cc':
|
||||
composerOptions.cc = value;
|
||||
break;
|
||||
case 'bcc':
|
||||
composerOptions.bcc = value;
|
||||
break;
|
||||
case 'subject':
|
||||
composerOptions.subject = value;
|
||||
break;
|
||||
case 'body':
|
||||
composerOptions.body = value;
|
||||
break;
|
||||
}
|
||||
});
|
||||
|
||||
return this.default(true).then(function() {
|
||||
Radio.ui.trigger('composer:show', composerOptions);
|
||||
}).catch(CrashReport.report);
|
||||
},
|
||||
|
||||
/**
|
||||
* @param {bool} showComposer
|
||||
* @returns {Promise}
|
||||
*/
|
||||
default: function(showComposer) {
|
||||
this._navigate('');
|
||||
var _this = this;
|
||||
if (this.accounts.isEmpty()) {
|
||||
// No account configured -> show setup
|
||||
return _this.showSetup();
|
||||
}
|
||||
|
||||
// Show first folder of first account
|
||||
var firstAccount = this.accounts.at(0);
|
||||
var firstFolder = firstAccount.folders.at(0);
|
||||
return _this.showFolder(firstAccount.get('accountId'), firstFolder.get('id'), showComposer);
|
||||
},
|
||||
|
||||
/**
|
||||
* @param {int} accountId
|
||||
* @param {string} folderId
|
||||
* @param {bool} showComposer
|
||||
* @returns {Promise}
|
||||
*/
|
||||
showFolder: function(accountId, folderId, showComposer) {
|
||||
this._navigate('accounts/' + accountId + '/folders/' + folderId);
|
||||
var _this = this;
|
||||
var account = this.accounts.get(accountId);
|
||||
if (_.isUndefined(account)) {
|
||||
// Unknown account id -> redirect
|
||||
Radio.ui.trigger('error:show', t('mail', 'Invalid account'));
|
||||
return _this.default();
|
||||
}
|
||||
|
||||
var folder = account.getFolderById(folderId);
|
||||
if (_.isUndefined(folder)) {
|
||||
folder = account.folders.at(0);
|
||||
Radio.ui.trigger('error:show', t('mail', 'Invalid folder'));
|
||||
this._navigate('accounts/' + accountId + '/folders/' + folder.get('id'));
|
||||
return Promise.resolve();
|
||||
}
|
||||
return FolderController.showFolder(account, folder, !showComposer);
|
||||
},
|
||||
|
||||
searchFolder: function(accountId, folderId, query) {
|
||||
if (!query || query === '') {
|
||||
this.showFolder(accountId, folderId);
|
||||
return;
|
||||
}
|
||||
|
||||
this._navigate('accounts/' + accountId + '/folders/' + folderId + '/search/' + query);
|
||||
var account = this.accounts.get(accountId);
|
||||
if (_.isUndefined(account)) {
|
||||
// Unknown account id -> redirect
|
||||
Radio.ui.trigger('error:show', t('mail', 'Invalid account'));
|
||||
this.default();
|
||||
return;
|
||||
}
|
||||
|
||||
var folder = account.getFolderById(folderId);
|
||||
if (_.isUndefined(folder)) {
|
||||
folder = account.folders.at(0);
|
||||
Radio.ui.trigger('error:show', t('mail', 'Invalid folder'));
|
||||
this._navigate('accounts/' + accountId + '/folders/' + folder.get('id'));
|
||||
}
|
||||
FolderController.searchFolder(account, folder, query);
|
||||
},
|
||||
mailTo: function(params) {
|
||||
this._handleMailto(params);
|
||||
},
|
||||
|
||||
/**
|
||||
* @returns {Promise}
|
||||
*/
|
||||
showSetup: function() {
|
||||
this._navigate('setup');
|
||||
Radio.ui.trigger('navigation:hide');
|
||||
Radio.ui.trigger('setup:show');
|
||||
return Promise.resolve();
|
||||
},
|
||||
showKeyboardShortcuts: function() {
|
||||
this._navigate('shortcuts');
|
||||
Radio.ui.trigger('keyboardShortcuts:show');
|
||||
},
|
||||
showAccountSettings: function(accountId) {
|
||||
this._navigate('accounts/' + accountId + '/settings');
|
||||
var account = this.accounts.get(accountId);
|
||||
if (_.isUndefined(account)) {
|
||||
// Unknown account id -> redirect
|
||||
Radio.ui.trigger('error:show', t('mail', 'Invalid account'));
|
||||
this.default();
|
||||
return;
|
||||
}
|
||||
Radio.ui.trigger('navigation:hide');
|
||||
Radio.ui.trigger('accountsettings:show', account);
|
||||
}
|
||||
};
|
||||
|
||||
return RoutingController;
|
||||
});
|
60
js/router.js
60
js/router.js
|
@ -1,41 +1,25 @@
|
|||
/**
|
||||
* @author Christoph Wurst <christoph@winzerhof-wurst.at>
|
||||
*
|
||||
* Mail
|
||||
*
|
||||
* This code is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License, version 3,
|
||||
* as published by the Free Software Foundation.
|
||||
*
|
||||
* 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, version 3,
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||
*
|
||||
*/
|
||||
import Vue from 'vue'
|
||||
import Router from 'vue-router'
|
||||
import Home from './views/Home.vue'
|
||||
|
||||
define(function(require) {
|
||||
'use strict';
|
||||
Vue.use(Router)
|
||||
|
||||
var Marionette = require('backbone.marionette');
|
||||
|
||||
/**
|
||||
* @class Router
|
||||
*/
|
||||
var Router = Marionette.AppRouter.extend({
|
||||
appRoutes: {
|
||||
'': 'default',
|
||||
'accounts/:accountId/folders/:folderId': 'showFolder',
|
||||
'accounts/:accountId/folders/:folderId/search/:query': 'searchFolder',
|
||||
'mailto(?:params)': 'mailTo',
|
||||
'setup': 'showSetup',
|
||||
'shortcuts': 'showKeyboardShortcuts',
|
||||
'accounts/:accountId/settings': 'showAccountSettings'
|
||||
export default new Router({
|
||||
mode: 'history',
|
||||
base: process.env.BASE_URL,
|
||||
routes: [
|
||||
{
|
||||
path: '/',
|
||||
name: 'home',
|
||||
component: Home
|
||||
},
|
||||
{
|
||||
path: '/about',
|
||||
name: 'about',
|
||||
// route level code-splitting
|
||||
// this generates a separate chunk (about.[hash].js) for this route
|
||||
// which is lazy-loaded when the route is visited.
|
||||
component: () => import(/* webpackChunkName: "about" */ './views/About.vue')
|
||||
}
|
||||
});
|
||||
|
||||
return Router;
|
||||
});
|
||||
]
|
||||
})
|
||||
|
|
32
js/search.js
32
js/search.js
|
@ -1,32 +0,0 @@
|
|||
/**
|
||||
* Mail
|
||||
*
|
||||
* This file is licensed under the Affero General Public License version 3 or
|
||||
* later. See the COPYING file.
|
||||
*
|
||||
* @author Christoph Wurst <christoph@winzerhof-wurst.at>
|
||||
* @copyright Christoph Wurst 2015, 2016
|
||||
*/
|
||||
|
||||
define(function(require) {
|
||||
'use strict';
|
||||
|
||||
var Radio = require('radio');
|
||||
var lastQuery = '';
|
||||
|
||||
function filter(query) {
|
||||
if (query !== lastQuery) {
|
||||
lastQuery = query;
|
||||
|
||||
if (require('state').currentAccount && require('state').currentFolder) {
|
||||
var accountId = require('state').currentAccount.get('accountId');
|
||||
var folderId = require('state').currentFolder.get('id');
|
||||
Radio.navigation.trigger('search', accountId, folderId, query);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
filter: filter
|
||||
};
|
||||
});
|
82
js/state.js
82
js/state.js
|
@ -1,82 +0,0 @@
|
|||
/**
|
||||
* Mail
|
||||
*
|
||||
* This file is licensed under the Affero General Public License version 3 or
|
||||
* later. See the COPYING file.
|
||||
*
|
||||
* @author Christoph Wurst <christoph@winzerhof-wurst.at>
|
||||
* @copyright Christoph Wurst 2015
|
||||
*/
|
||||
|
||||
define(function(require) {
|
||||
'use strict';
|
||||
|
||||
var Radio = require('radio');
|
||||
var AccountCollection = require('models/accountcollection');
|
||||
|
||||
var state = {};
|
||||
|
||||
var accounts = new AccountCollection();
|
||||
var currentAccount = null;
|
||||
var currentFolder = null;
|
||||
var currentMessage = null;
|
||||
var currentMessageSubject = null;
|
||||
var currentMessageBody = '';
|
||||
|
||||
Object.defineProperties(state, {
|
||||
accounts: {
|
||||
get: function() {
|
||||
return accounts;
|
||||
},
|
||||
set: function(acc) {
|
||||
accounts = acc;
|
||||
}
|
||||
},
|
||||
currentAccount: {
|
||||
get: function() {
|
||||
return currentAccount;
|
||||
},
|
||||
set: function(account) {
|
||||
currentAccount = account;
|
||||
}
|
||||
},
|
||||
currentFolder: {
|
||||
get: function() {
|
||||
return currentFolder;
|
||||
},
|
||||
set: function(newFolder) {
|
||||
var oldFolder = currentFolder;
|
||||
currentFolder = newFolder;
|
||||
if (newFolder !== oldFolder) {
|
||||
Radio.ui.trigger('folder:changed');
|
||||
}
|
||||
}
|
||||
},
|
||||
currentMessage: {
|
||||
get: function() {
|
||||
return currentMessage;
|
||||
},
|
||||
set: function(newMessage) {
|
||||
currentMessage = newMessage;
|
||||
}
|
||||
},
|
||||
currentMessageSubject: {
|
||||
get: function() {
|
||||
return currentMessageSubject;
|
||||
},
|
||||
set: function(subject) {
|
||||
currentMessageSubject = subject;
|
||||
}
|
||||
},
|
||||
currentMessageBody: {
|
||||
get: function() {
|
||||
return currentMessageBody;
|
||||
},
|
||||
set: function(body) {
|
||||
currentMessageBody = body;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
return state;
|
||||
});
|
|
@ -0,0 +1,12 @@
|
|||
import Vue from 'vue'
|
||||
import Vuex from 'vuex'
|
||||
|
||||
Vue.use(Vuex)
|
||||
|
||||
export default new Vuex.Store({
|
||||
state: {
|
||||
accounts: []
|
||||
},
|
||||
mutations: {},
|
||||
actions: {}
|
||||
})
|
|
@ -1,40 +0,0 @@
|
|||
/* global md5 */
|
||||
|
||||
/**
|
||||
* @author Christoph Wurst <christoph@winzerhof-wurst.at>
|
||||
*
|
||||
* Mail
|
||||
*
|
||||
* This code is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License, version 3,
|
||||
* as published by the Free Software Foundation.
|
||||
*
|
||||
* 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, version 3,
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||
*
|
||||
*/
|
||||
|
||||
define(function(require) {
|
||||
'use strict';
|
||||
|
||||
var Handlebars = require('handlebars');
|
||||
|
||||
return function(account) {
|
||||
var hash = md5(account);
|
||||
var hue = null;
|
||||
if (typeof hash.toHsl === 'function') {
|
||||
var hsl = hash.toHsl();
|
||||
hue = Math.round(hsl[0] / 40) * 40;
|
||||
return new Handlebars.SafeString('hsl(' + hue + ', ' + hsl[1] + '%, ' + hsl[2] + '%)');
|
||||
} else {
|
||||
var maxRange = parseInt('ffffffffffffffffffffffffffffffff', 16);
|
||||
hue = parseInt(hash, 16) / maxRange * 256;
|
||||
return new Handlebars.SafeString('hsl(' + hue + ', 90%, 65%)');
|
||||
}
|
||||
};
|
||||
});
|
|
@ -1,29 +0,0 @@
|
|||
/* global formatDate */
|
||||
|
||||
/**
|
||||
* @author Christoph Wurst <christoph@winzerhof-wurst.at>
|
||||
*
|
||||
* Mail
|
||||
*
|
||||
* This code is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License, version 3,
|
||||
* as published by the Free Software Foundation.
|
||||
*
|
||||
* 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, version 3,
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||
*
|
||||
*/
|
||||
|
||||
define(function() {
|
||||
'use strict';
|
||||
|
||||
return function(dateInt) {
|
||||
var lastModified = new Date(dateInt * 1000);
|
||||
return formatDate(lastModified);
|
||||
};
|
||||
});
|
|
@ -1,28 +0,0 @@
|
|||
/* global humanFileSize */
|
||||
|
||||
/**
|
||||
* @author Christoph Wurst <christoph@winzerhof-wurst.at>
|
||||
*
|
||||
* Mail
|
||||
*
|
||||
* This code is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License, version 3,
|
||||
* as published by the Free Software Foundation.
|
||||
*
|
||||
* 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, version 3,
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||
*
|
||||
*/
|
||||
|
||||
define(function() {
|
||||
'use strict';
|
||||
|
||||
return function(size) {
|
||||
return humanFileSize(size);
|
||||
};
|
||||
});
|
|
@ -1,32 +0,0 @@
|
|||
/**
|
||||
* @author Christoph Wurst <christoph@winzerhof-wurst.at>
|
||||
*
|
||||
* Mail
|
||||
*
|
||||
* This code is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License, version 3,
|
||||
* as published by the Free Software Foundation.
|
||||
*
|
||||
* 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, version 3,
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||
*
|
||||
*/
|
||||
|
||||
define(function() {
|
||||
'use strict';
|
||||
|
||||
return function(options) {
|
||||
var hasCc = this.cc.length > 0;
|
||||
var hasBcc = this.bcc.length > 0;
|
||||
if (hasCc || hasBcc) {
|
||||
return options.fn(this);
|
||||
} else {
|
||||
return options.inverse(this);
|
||||
}
|
||||
};
|
||||
});
|
|
@ -1,39 +0,0 @@
|
|||
/**
|
||||
* @author Jakob Sack <mail@jakobsack.de>
|
||||
*
|
||||
* Mail
|
||||
*
|
||||
* This code is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License, version 3,
|
||||
* as published by the Free Software Foundation.
|
||||
*
|
||||
* 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, version 3,
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||
*
|
||||
*/
|
||||
|
||||
define(function() {
|
||||
'use strict';
|
||||
|
||||
function isToNoreply(recipient) {
|
||||
var user = recipient.email.substring(0, recipient.email.lastIndexOf('@'));
|
||||
return ['noreply', 'no-reply'].indexOf(user) !== -1;
|
||||
}
|
||||
|
||||
return function(options) {
|
||||
var noreplyInTo = this.to.some(isToNoreply);
|
||||
var noreplyInCc = this.cc.some(isToNoreply);
|
||||
var noreplyInBcc = this.bcc.some(isToNoreply);
|
||||
|
||||
if (noreplyInTo || noreplyInCc || noreplyInBcc) {
|
||||
return options.fn(this);
|
||||
} else {
|
||||
return options.inverse(this);
|
||||
}
|
||||
};
|
||||
});
|
|
@ -1,54 +0,0 @@
|
|||
/**
|
||||
* @author Christoph Wurst <christoph@winzerhof-wurst.at>
|
||||
*
|
||||
* Mail
|
||||
*
|
||||
* This code is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License, version 3,
|
||||
* as published by the Free Software Foundation.
|
||||
*
|
||||
* 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, version 3,
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||
*
|
||||
*/
|
||||
|
||||
define(function(require) {
|
||||
'use strict';
|
||||
|
||||
var _ = require('underscore');
|
||||
var Handlebars = require('handlebars');
|
||||
|
||||
return function(addressList) {
|
||||
var currentAccount = require('state').currentAccount;
|
||||
|
||||
var str = _.reduce(addressList, function(memo, value, index) {
|
||||
if (index !== 0) {
|
||||
memo += ', ';
|
||||
}
|
||||
var label = value.label
|
||||
.replace(/(^"|"$)/g, '')
|
||||
.replace(/(^'|'$)/g, '');
|
||||
label = Handlebars.Utils.escapeExpression(label);
|
||||
var email = Handlebars.Utils.escapeExpression(value.email);
|
||||
|
||||
if (currentAccount && (email === currentAccount.get('emailAddress') ||
|
||||
_.find(currentAccount.get('aliases').
|
||||
toJSON(), function(alias) {
|
||||
return alias.alias === email;
|
||||
}))) {
|
||||
label = t('mail', 'you');
|
||||
}
|
||||
var title = t('mail', 'Send message to {email}', {email: email});
|
||||
memo += '<span class="tooltip-mailto" title="' + title + '">';
|
||||
memo += '<a class="link-mailto" data-email="' + email + '" data-label="' + label + '">';
|
||||
memo += label + '</a></span>';
|
||||
return memo;
|
||||
}, '');
|
||||
return new Handlebars.SafeString(str);
|
||||
};
|
||||
});
|
|
@ -1,44 +0,0 @@
|
|||
/**
|
||||
* @author Christoph Wurst <christoph@winzerhof-wurst.at>
|
||||
*
|
||||
* Mail
|
||||
*
|
||||
* This code is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License, version 3,
|
||||
* as published by the Free Software Foundation.
|
||||
*
|
||||
* 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, version 3,
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||
*
|
||||
*/
|
||||
|
||||
define(function(require) {
|
||||
'use strict';
|
||||
|
||||
var _ = require('underscore');
|
||||
var Handlebars = require('handlebars');
|
||||
|
||||
return function(addressList) {
|
||||
var str = _.reduce(addressList, function(memo, value, index) {
|
||||
if (index !== 0) {
|
||||
memo += ', ';
|
||||
}
|
||||
var label = value.label
|
||||
.replace(/(^"|"$)/g, '')
|
||||
.replace(/(^'|'$)/g, '');
|
||||
label = Handlebars.Utils.escapeExpression(label);
|
||||
var email = Handlebars.Utils.escapeExpression(value.email);
|
||||
if (label === email) {
|
||||
return memo + email;
|
||||
} else {
|
||||
return memo + '"' + label + '" <' + email + '>';
|
||||
}
|
||||
}, '');
|
||||
return str;
|
||||
};
|
||||
});
|
|
@ -1,33 +0,0 @@
|
|||
/* global relative_modified_date */
|
||||
|
||||
/**
|
||||
* @author Christoph Wurst <christoph@winzerhof-wurst.at>
|
||||
*
|
||||
* Mail
|
||||
*
|
||||
* This code is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License, version 3,
|
||||
* as published by the Free Software Foundation.
|
||||
*
|
||||
* 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, version 3,
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||
*
|
||||
*/
|
||||
|
||||
|
||||
define(function() {
|
||||
'use strict';
|
||||
|
||||
return function(dateInt) {
|
||||
var lastModified = new Date(dateInt * 1000);
|
||||
var lastModifiedTime = Math.round(lastModified.getTime() / 1000);
|
||||
// jscs:disable requireCamelCaseOrUpperCaseIdentifiers
|
||||
return relative_modified_date(lastModifiedTime);
|
||||
// jscs:enable requireCamelCaseOrUpperCaseIdentifiers
|
||||
};
|
||||
});
|
|
@ -1,26 +0,0 @@
|
|||
/**
|
||||
* @author Christoph Wurst <christoph@winzerhof-wurst.at>
|
||||
*
|
||||
* Mail
|
||||
*
|
||||
* This code is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License, version 3,
|
||||
* as published by the Free Software Foundation.
|
||||
*
|
||||
* 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, version 3,
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||
*
|
||||
*/
|
||||
|
||||
define(function() {
|
||||
'use strict';
|
||||
|
||||
return function(text) {
|
||||
return t('mail', text);
|
||||
};
|
||||
});
|
|
@ -1,32 +0,0 @@
|
|||
/**
|
||||
* @author Christoph Wurst <christoph@winzerhof-wurst.at>
|
||||
*
|
||||
* Mail
|
||||
*
|
||||
* This code is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License, version 3,
|
||||
* as published by the Free Software Foundation.
|
||||
*
|
||||
* 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, version 3,
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||
*
|
||||
*/
|
||||
|
||||
define(function() {
|
||||
'use strict';
|
||||
|
||||
return function(options) {
|
||||
var hasCc = this.cc.length > 0;
|
||||
var hasBcc = this.bcc.length > 0;
|
||||
if (!hasCc && !hasBcc) {
|
||||
return options.fn(this);
|
||||
} else {
|
||||
return options.inverse(this);
|
||||
}
|
||||
};
|
||||
});
|
|
@ -1,113 +0,0 @@
|
|||
<form method="post">
|
||||
<div class="hidden-visually">
|
||||
<!-- Hack for Safari and Chromium/Chrome which ignore autocomplete="off" -->
|
||||
<input type="text" id="fake_user" name="fake_user"
|
||||
autocomplete="off" tabindex="-1">
|
||||
<input type="password" id="fake_password" name="fake_password"
|
||||
autocomplete="off" tabindex="-1">
|
||||
</div>
|
||||
<fieldset>
|
||||
<div id="emptycontent">
|
||||
<div class="icon-mail"></div>
|
||||
<h2>{{ t 'Connect your mail account' }}</h2>
|
||||
</div>
|
||||
<p class="grouptop">
|
||||
<input type="text"
|
||||
name="account-name"
|
||||
placeholder="{{ t 'Name' }}"
|
||||
value="{{ config.name }}"
|
||||
autofocus />
|
||||
</p>
|
||||
<p class="groupmiddle">
|
||||
<input type="email"
|
||||
name="mail-address"
|
||||
placeholder="{{ t 'Mail Address' }}"
|
||||
value="{{ config.emailAddress }}"
|
||||
required />
|
||||
</p>
|
||||
<p class="groupbottom">
|
||||
<input type="password"
|
||||
name="mail-password"
|
||||
placeholder="{{ t 'Password' }}"
|
||||
value="{{ config.password }}"
|
||||
required />
|
||||
</p>
|
||||
|
||||
<a class="toggle-manual-mode icon-caret-dark">{{ t 'Manual configuration' }}</a>
|
||||
|
||||
<div class="manual-inputs">
|
||||
<p class="grouptop">
|
||||
<input type="text"
|
||||
name="imap-host"
|
||||
placeholder="{{ t 'IMAP Host' }}"
|
||||
value="{{ config.imapHost }}" />
|
||||
</p>
|
||||
<p class="groupmiddle" id="setup-imap-ssl">
|
||||
<select id="setup-imap-ssl-mode"
|
||||
name="imap-sslmode"
|
||||
title="{{ t 'IMAP security' }}">
|
||||
<option value="none">{{ t 'None' }}</option>
|
||||
<option value="ssl">{{ t 'SSL/TLS' }}</option>
|
||||
<option value="tls">{{ t 'STARTTLS' }}</option>
|
||||
</select>
|
||||
</p>
|
||||
<p class="groupmiddle">
|
||||
<input type="number"
|
||||
name="imap-port"
|
||||
placeholder="{{ t 'IMAP Port' }}"
|
||||
value="{{ config.imapPort }}" />
|
||||
</p>
|
||||
<p class="groupmiddle">
|
||||
<input type="text"
|
||||
name="imap-user"
|
||||
placeholder="{{ t 'IMAP User' }}"
|
||||
value="{{ config.imapUser }}" />
|
||||
</p>
|
||||
<p class="groupbottom">
|
||||
<input type="password"
|
||||
name="imap-password"
|
||||
placeholder="{{ t 'IMAP Password' }}"
|
||||
value="{{ config.imapPassword }}"
|
||||
required />
|
||||
</p>
|
||||
<p class="grouptop">
|
||||
<input type="text"
|
||||
name="smtp-host"
|
||||
placeholder="{{ t 'SMTP Host' }}"
|
||||
value="{{ config.smtpHost }}" />
|
||||
</p>
|
||||
<p class="groupmiddle" id="setup-smtp-ssl">
|
||||
<select id="setup-smtp-ssl-mode"
|
||||
name="mail-smtp-sslmode"
|
||||
title="{{ t 'SMTP security' }}">
|
||||
<option value="none">{{ t 'None' }}</option>
|
||||
<option value="ssl">{{ t 'SSL/TLS' }}</option>
|
||||
<option value="tls">{{ t 'STARTTLS' }}</option>
|
||||
</select>
|
||||
</p>
|
||||
<p class="groupmiddle">
|
||||
<input type="number"
|
||||
name="smtp-port"
|
||||
placeholder="{{ t 'SMTP Port' }}"
|
||||
value="{{ config.smtpPort }}" />
|
||||
</p>
|
||||
<p class="groupmiddle">
|
||||
<input type="text"
|
||||
name="smtp-user"
|
||||
placeholder="{{ t 'SMTP User' }}"
|
||||
value="{{ config.smtpUser }}" />
|
||||
</p>
|
||||
<p class="groupbottom">
|
||||
<input type="password"
|
||||
name="smtp-password"
|
||||
placeholder="{{ t 'SMTP Password' }}"
|
||||
value="{{ config.smtpPassword }}"
|
||||
required />
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<input type="submit"
|
||||
class="primary"
|
||||
value="{{ t 'Connect' }}"/>
|
||||
</fieldset>
|
||||
</form>
|
|
@ -1,43 +0,0 @@
|
|||
{{#unless isUnifiedInbox}}
|
||||
<li class="{{#if hasMenu}} with-menu {{/if}}">
|
||||
{{#if emailAddress}}
|
||||
<div class="app-navigation-entry-bullet" style="background-color: {{accountColor emailAddress}}"></div>
|
||||
{{/if}}
|
||||
<a>{{emailAddress}}</a>
|
||||
|
||||
{{#if hasMenu}}
|
||||
<div class="app-navigation-entry-utils">
|
||||
<ul>
|
||||
<li class="app-navigation-entry-utils-menu-button svg"><button></button></li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="app-navigation-entry-menu">
|
||||
<ul>
|
||||
<li>
|
||||
<a href="#" class="menuitem action action-settings permanent">
|
||||
<span class="icon icon-rename"></span>
|
||||
<span>{{ t 'Settings' }}</span>
|
||||
</a>
|
||||
</li>
|
||||
{{#if isDeletable}}
|
||||
<li>
|
||||
<a href="#" class="menuitem action action-delete permanent">
|
||||
<span class="icon icon-delete"></span>
|
||||
<span>{{ t 'Delete account' }}</span>
|
||||
</a>
|
||||
</li>
|
||||
{{/if}}
|
||||
</ul>
|
||||
</div>
|
||||
{{/if}}
|
||||
</li>
|
||||
{{/unless}}
|
||||
|
||||
<div class="folders"></div>
|
||||
{{#unless isUnifiedInbox}}
|
||||
{{#if hasFolders}}
|
||||
<li class="account-toggle-collapse">
|
||||
<a>{{toggleCollapseMessage}}</a>
|
||||
</li>
|
||||
{{/if}}
|
||||
{{/unless}}
|
|
@ -1,22 +0,0 @@
|
|||
<div class="section" id="account-info">
|
||||
<h2>{{ t 'Account Settings' }} - {{ email }}</h2>
|
||||
</div>
|
||||
|
||||
<div class="section" id="aliases">
|
||||
<h2>{{ t 'Aliases' }}</h2>
|
||||
<div id="aliases-list"></div>
|
||||
<form name="aliasForm" method="post">
|
||||
<input type="text" name="alias" id="alias" placeholder="{{ t 'Mail address' }}">
|
||||
<input type="text" name="alias-name" id="alias-name" placeholder="{{ t 'Display Name' }}">
|
||||
<input type="submit" value="{{ t 'Save' }}">
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<div class="section">
|
||||
<h2>{{ t 'Account' }}</h2>
|
||||
<div id="mail-settings-status">
|
||||
<div id="mail-settings-loading" class="icon-loading-small" style="display: none; float: left; margin-right: 10px;"></div>
|
||||
<span id="mail-settings-msg" class="msg success" style="display: none;">Saved</span>
|
||||
</div>
|
||||
<div id="mail-settings"></div>
|
||||
</div>
|
|
@ -1,14 +0,0 @@
|
|||
<table class="grid">
|
||||
<tbody>
|
||||
<tr id="{{ aliases.id }}">
|
||||
<td style="padding:0 5px 0 5px;">
|
||||
{{ aliases.alias }}
|
||||
</td>
|
||||
<td style="padding:0 5px 0 5px;">
|
||||
{{ aliases.name }}
|
||||
</td>
|
||||
<td style="padding:0 5px 0 5px;"> <button type="submit" value="{{ id }}" class="icon-delete"></button>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
|
@ -1,2 +0,0 @@
|
|||
<div class="new-message-attachment-name">{{displayName}}</div>
|
||||
<div class="new-message-attachments-action svg icon-delete"></div>
|
|
@ -1,8 +0,0 @@
|
|||
<ul></ul>
|
||||
<button type="button" id="add-local-attachment" style="display: inline-block;">
|
||||
<span class="icon-upload"/> {{ t 'Upload attachment' }}
|
||||
</button>
|
||||
<button type="button" id="add-cloud-attachment" style="display: inline-block;">
|
||||
<span class="icon-folder"/> {{ t 'Add attachment from Files' }}
|
||||
</button>
|
||||
<input type="file" multiple id="local-attachments" style="display: none;">
|
|
@ -1 +0,0 @@
|
|||
<a class="select-calendar" data-calendar-url="{{url}}">{{displayname}}</a>
|
|
@ -1,50 +0,0 @@
|
|||
<div class="message-composer">
|
||||
<select class="mail-account">
|
||||
{{#each aliases}}
|
||||
<option value="{{id}}">{{ t 'from' }} {{name}} <{{emailAddress}}></option>
|
||||
{{/each}}
|
||||
</select>
|
||||
<div class="composer-fields">
|
||||
<a href="#" class="composer-cc-bcc-toggle transparency
|
||||
{{#ifHasCC}}
|
||||
hidden
|
||||
{{/ifHasCC}}">{{ t '+ cc/bcc' }}</a>
|
||||
<input type="text" name="to"
|
||||
value="{{printAddressListPlain to}}"
|
||||
class="to recipient-autocomplete" />
|
||||
<label class="to-label transparency" for="to">{{ t 'to' }}</label>
|
||||
<div class="composer-cc-bcc
|
||||
{{#unlessHasCC}}
|
||||
hidden
|
||||
{{/unlessHasCC}}">
|
||||
<input type="text"
|
||||
name="cc"
|
||||
class="cc recipient-autocomplete"
|
||||
value="{{printAddressListPlain cc}}"
|
||||
/>
|
||||
<label for="cc" class="cc-label transparency">{{ t 'cc' }}</label>
|
||||
<input type="text"
|
||||
name="bcc"
|
||||
class="bcc recipient-autocomplete"
|
||||
value="{{printAddressListPlain bcc}}"
|
||||
/>
|
||||
<label for="bcc" class="bcc-label transparency">{{ t 'bcc' }}</label>
|
||||
</div>
|
||||
{{#ifToNoreply}}
|
||||
<div class="warning noreply-box">{{ t 'Note that the mail came from a noreply address so your reply will probably not be read.' }}</div>
|
||||
{{/ifToNoreply}}
|
||||
{{#unless isReply}}
|
||||
<input type="text" name="subject" value="{{subject}}" class="subject" autocomplete="off"
|
||||
placeholder="{{ t 'Subject' }}" />
|
||||
{{/unless}}
|
||||
<textarea name="body"
|
||||
class="message-body"
|
||||
placeholder="{{ t 'Message …' }}">{{message}}</textarea>
|
||||
</div>
|
||||
<div class="submit-message-wrapper">
|
||||
<input class="submit-message send primary" type="submit" value="{{submitButtonTitle}}" disabled>
|
||||
<div class="submit-message-wrapper-inside" ></div>
|
||||
</div>
|
||||
<div class="new-message-attachments">
|
||||
</div>
|
||||
</div>
|
|
@ -1,2 +0,0 @@
|
|||
<div class="icon-mail"></div>
|
||||
<h2>{{ t 'No messages in this folder' }}</h2>
|
|
@ -1 +0,0 @@
|
|||
<div class="container hidden-mobile"></div>
|
|
@ -1,8 +0,0 @@
|
|||
<div class="">
|
||||
{{#if icon}}<div class="{{icon}}"></div>{{/if}}
|
||||
<h2>{{{ text }}}</h2>
|
||||
{{# if canRetry }}
|
||||
<br>
|
||||
<button class="retry">{{ t 'Try again' }}</button>
|
||||
{{/if}}
|
||||
</div>
|
|
@ -1,15 +0,0 @@
|
|||
{{#if folders}}
|
||||
<button class="collapse"></button>
|
||||
{{/if}}
|
||||
<a class="folder
|
||||
{{#if specialRole}} icon-{{specialRole}} svg{{/if}}
|
||||
{{#if noSelect}} no-select {{/if}}"
|
||||
href="{{#if noSelect}}#{{else}}{{url}}{{/if}}">
|
||||
{{name}}
|
||||
</a>
|
||||
<div class="app-navigation-entry-utils">
|
||||
<ul>
|
||||
<li class="app-navigation-entry-utils-counter">{{#if count}}{{count}}{{/if}}</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="folders"></div>
|
|
@ -1,2 +0,0 @@
|
|||
<div class="app-content-list"></div>
|
||||
<div class="app-content-details"></div>
|
|
@ -1,48 +0,0 @@
|
|||
<div id="app-shortcuts" class="section">
|
||||
|
||||
<h2>{{t 'Keyboard shortcuts'}}</h2>
|
||||
|
||||
<p>{{t 'Speed up your Mail experience with these quick shortcuts.'}}</p>
|
||||
|
||||
<dl>
|
||||
<div>
|
||||
<dt><kbd>C</kbd></dt>
|
||||
<dd>{{t 'Compose new message'}}</dd>
|
||||
</div>
|
||||
<div>
|
||||
<dt><kbd>K</kbd> or <kbd>←</kbd></dt>
|
||||
<dd>{{t 'Newer message'}}</dd>
|
||||
</div>
|
||||
<div>
|
||||
<dt><kbd>J</kbd> or <kbd>→</kbd></dt>
|
||||
<dd>{{t 'Older message'}}</dd>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<dt><kbd>S</kbd></dt>
|
||||
<dd>{{ t 'Toggle star' }}</dd>
|
||||
</div>
|
||||
<div>
|
||||
<dt><kbd>U</kbd></dt>
|
||||
<dd>{{ t 'Toggle unread' }}</dd>
|
||||
</div>
|
||||
<div>
|
||||
<dt><kbd>Del</kbd></dt>
|
||||
<dd>{{ t 'Delete' }}</dd>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<dt><kbd>Ctrl</kbd> + <kbd>F</kbd></dt>
|
||||
<dd>{{ t 'Search' }}</dd>
|
||||
</div>
|
||||
<div>
|
||||
<dt><kbd>Ctrl</kbd> + <kbd>Enter</kbd></dt>
|
||||
<dd>{{ t 'Send' }}</dd>
|
||||
</div>
|
||||
<div>
|
||||
<dt><kbd>R</kbd></dt>
|
||||
<dd>{{t 'Refresh'}}</dd>
|
||||
</div>
|
||||
</dl>
|
||||
|
||||
</div>
|
|
@ -1,8 +0,0 @@
|
|||
{{#if hint}}
|
||||
<div class="emptycontent">
|
||||
<a class="icon-loading"></a>
|
||||
<h2>{{{ hint }}}</h2>
|
||||
</div>
|
||||
{{else}}
|
||||
<div class="container icon-loading"></div>
|
||||
{{/if}}
|
|
@ -1,12 +0,0 @@
|
|||
{{#if isImage}}
|
||||
<img class="mail-attached-image" src="{{downloadUrl}}">
|
||||
<br>
|
||||
{{/if}}
|
||||
<img class="attachment-icon" src="{{mimeUrl}}" />
|
||||
<span class="attachment-name" title="{{fileName}} ({{humanFileSize size}})">{{fileName}} <span class="attachment-size">({{humanFileSize size}})</span></span>
|
||||
{{#if isCalendarEvent}}
|
||||
<button class="button icon-add attachment-import calendar" title="{{ t 'Import into calendar' }}"></button>
|
||||
{{/if}}
|
||||
<button class="button icon-download attachment-download" title="{{ t 'Download attachment' }}"></button>
|
||||
<button class="icon-folder attachment-save-to-cloud" title="{{ t 'Save to Files' }}"></button>
|
||||
<div class="popovermenu bubble attachment-import-popover hidden"></div>
|
|
@ -1,8 +0,0 @@
|
|||
<div class="attachments">
|
||||
|
||||
</div>
|
||||
{{#if moreThanOne}}
|
||||
<p>
|
||||
<button class="icon-folder attachments-save-to-cloud">{{ t 'Save all to Files' }}</button>
|
||||
</p>
|
||||
{{/if}}
|
|
@ -1,58 +0,0 @@
|
|||
<div class="app-content-list-item {{#if flags.unseen}}unseen{{/if}} {{#if active}}active{{/if}}" data-message-id="{{id}}">
|
||||
{{#if isUnified}}
|
||||
<div class="mail-message-account-color" style="background-color: {{accountColor accountMail}}"></div>
|
||||
{{/if}}
|
||||
|
||||
{{#if flags.flagged}}
|
||||
<div class="star app-content-list-item-star icon-starred" data-starred="true"></div>
|
||||
{{else}}
|
||||
<div class="star app-content-list-item-star icon-star" data-starred="false"></div>
|
||||
{{/if}}
|
||||
|
||||
<div class="app-content-list-item-icon sender-image avatardiv">
|
||||
{{#if senderImage}}
|
||||
<img src="{{senderImage}}" width="32px" height="32px" />
|
||||
{{else}}
|
||||
<div class="avatar" data-user="{{sender.label}}" data-size="32"></div>
|
||||
{{/if}}
|
||||
</div>
|
||||
|
||||
<div class="app-content-list-item-line-one" title="{{label}}">{{label}}</div>
|
||||
<div class="app-content-list-item-line-two" title="{{subject}}">
|
||||
{{#if flags.answered}}
|
||||
<span class="icon-reply"></span>
|
||||
{{/if}}
|
||||
|
||||
{{#if flags.hasAttachments}}
|
||||
<span class="icon-public icon-attachment"></span>
|
||||
{{/if}}
|
||||
{{subject}}
|
||||
</div>
|
||||
<div class="app-content-list-item-details date">
|
||||
<span class="modified live-relative-timestamp"
|
||||
data-timestamp="{{dateMicro}}"
|
||||
title="{{formatDate dateInt}}">
|
||||
{{relativeModifiedDate dateInt}}
|
||||
</span>
|
||||
</div>
|
||||
<div class="app-content-list-item-menu">
|
||||
<div class="icon-more toggle-menu"></div>
|
||||
<div class="popovermenu">
|
||||
<ul>
|
||||
<li>
|
||||
<button class="action toggle-read">
|
||||
<span class="icon-mail"></span>
|
||||
<span>{{ t 'Toggle read' }}</span>
|
||||
</button>
|
||||
</li>
|
||||
<li>
|
||||
<button class="action delete">
|
||||
<span class="icon-delete"></span>
|
||||
<span>{{ t 'Delete' }}</span>
|
||||
</button>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
|
@ -1,5 +0,0 @@
|
|||
<div id="mail-message-list-loading"
|
||||
class="icon-loading-small"
|
||||
style="display: none"></div>
|
||||
<div id="mail-message-list"></div>
|
||||
<div id="load-more-mail-messages"></div>
|
|
@ -1,37 +0,0 @@
|
|||
<div id="mail-message-close" class="icon-close"></div>
|
||||
<div id="mail-message-header" class="section">
|
||||
<h2 title="{{subject}}">{{subject}}</h2>
|
||||
<p class="transparency">
|
||||
{{printAddressList from}}
|
||||
{{ t 'to' }}
|
||||
{{printAddressList to}}
|
||||
{{#if cc.length}}
|
||||
({{ t 'cc' }} {{printAddressList cc}})
|
||||
{{/if}}
|
||||
</p>
|
||||
</div>
|
||||
<div class="mail-message-body">
|
||||
<div id="mail-content">
|
||||
{{#if hasHtmlBody}}
|
||||
<div id="show-images-text">
|
||||
{{ t 'The images have been blocked to protect your privacy.' }}
|
||||
<button id="show-images-button">{{ t 'Show images from this sender' }}</button>
|
||||
</div>
|
||||
<div class="icon-loading">
|
||||
<iframe src="{{htmlBodyUrl}}" seamless>
|
||||
</iframe>
|
||||
</div>
|
||||
{{else}}
|
||||
{{{body}}}
|
||||
{{/if}}
|
||||
</div>
|
||||
{{#if signature}}
|
||||
<div class="mail-signature">
|
||||
{{{signature}}}
|
||||
</div>
|
||||
{{/if}}
|
||||
|
||||
<div class="mail-message-attachments"></div>
|
||||
<div id="reply-composer"></div>
|
||||
<input type="button" id="forward-button" value="{{ t 'Forward' }}">
|
||||
</div>
|
|
@ -1,3 +0,0 @@
|
|||
<button type="button"
|
||||
id="mail_new_message"
|
||||
class="icon-add">{{ t 'New message' }}</button>
|
|
@ -1,4 +0,0 @@
|
|||
<div id="emptycontent" class="emptycontent-search">
|
||||
<div class="icon-search"></div>
|
||||
<h2>{{ t 'No search results for' }} {{ searchTerm }}</h2>
|
||||
</div>
|
|
@ -1,25 +0,0 @@
|
|||
<div id="mailsettings">
|
||||
<ul id="settings-accounts" class="mailaccount-list">
|
||||
</ul>
|
||||
|
||||
<a id="new-mail-account"
|
||||
class="button new-button"
|
||||
href="{{addAccountUrl}}">{{ t 'Add mail account' }}</a>
|
||||
|
||||
<p>
|
||||
<input class="checkbox"
|
||||
id="gravatar-enabled"
|
||||
{{#if useExternalAvatars}}checked="checked"{{/if}}
|
||||
type="checkbox">
|
||||
<label for="gravatar-enabled">{{ t 'Use Gravatar and favicon avatars' }}</label>
|
||||
</p>
|
||||
|
||||
<p class="app-settings-hint">
|
||||
<a id="keyboard-shortcuts"
|
||||
href="{{keyboardShortcutUrl}}">{{ t 'Keyboard shortcuts' }}</a>
|
||||
</p>
|
||||
|
||||
<p class="app-settings-hint">
|
||||
{{{ t 'Looking to encrypt your emails? Install the <a href="https://www.mailvelope.com/" target="_blank">Mailvelope browser extension</a>!' }}}
|
||||
</p>
|
||||
</div>
|
|
@ -1 +0,0 @@
|
|||
<div class="setup-content container"></div>
|
|
@ -0,0 +1,19 @@
|
|||
// A custom Nightwatch assertion.
|
||||
// The assertion name is the filename.
|
||||
// Example usage:
|
||||
//
|
||||
// browser.assert.elementCount(selector, count)
|
||||
//
|
||||
// For more information on custom assertions see:
|
||||
// http://nightwatchjs.org/guide#writing-custom-assertions
|
||||
|
||||
exports.assertion = function elementCount (selector, count) {
|
||||
this.message = `Testing if element <${selector}> has count: ${count}`
|
||||
this.expected = count
|
||||
this.pass = val => val === count
|
||||
this.value = res => res.value
|
||||
function evaluator (_selector) {
|
||||
return document.querySelectorAll(_selector).length
|
||||
}
|
||||
this.command = cb => this.api.execute(evaluator, [selector], cb)
|
||||
}
|
|
@ -0,0 +1,14 @@
|
|||
// For authoring Nightwatch tests, see
|
||||
// http://nightwatchjs.org/guide#usage
|
||||
|
||||
module.exports = {
|
||||
'default e2e tests': browser => {
|
||||
browser
|
||||
.url(process.env.VUE_DEV_SERVER_URL)
|
||||
.waitForElementVisible('#app', 5000)
|
||||
.assert.elementPresent('.hello')
|
||||
.assert.containsText('h1', 'Welcome to Your Vue.js App')
|
||||
.assert.elementCount('img', 1)
|
||||
.end()
|
||||
}
|
||||
}
|
|
@ -1,12 +0,0 @@
|
|||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>Jasmine Spec Runner</title>
|
||||
<link rel="stylesheet" type="text/css" href="../vendor/jasmine-core/lib/jasmine-core/jasmine.css">
|
||||
<script type="text/javascript" src="../vendor/requirejs/require.js"></script>
|
||||
<script type="text/javascript" src="Views_Attachments_Spec.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
<div id="sandbox" style="overflow: hidden; height: 1px;"></div>
|
||||
</body>
|
||||
</html>
|
|
@ -1,103 +0,0 @@
|
|||
/* global expect, spyOn */
|
||||
|
||||
/**
|
||||
* @author Christoph Wurst <christoph@winzerhof-wurst.at>
|
||||
*
|
||||
* Mail
|
||||
*
|
||||
* This code is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License, version 3,
|
||||
* as published by the Free Software Foundation.
|
||||
*
|
||||
* 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, version 3,
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||
*
|
||||
*/
|
||||
|
||||
define([
|
||||
'jquery',
|
||||
'app',
|
||||
'radio',
|
||||
'backbone',
|
||||
'controller/accountcontroller',
|
||||
'models/accountcollection'
|
||||
], function($, Mail, Radio, Backbone, AccountController,
|
||||
AccountCollection) {
|
||||
describe('App', function() {
|
||||
|
||||
beforeEach(function() {
|
||||
jasmine.Ajax.install();
|
||||
$('testcontainer').remove();
|
||||
$('body')
|
||||
.append('testcontainer')
|
||||
.append(
|
||||
'<input type="hidden" id="config-installed-version" value="0.6.1">'
|
||||
+ '<input type="hidden" id="serialized-accounts" value="">'
|
||||
+ '<div id="user-displayname">Jane Doe</div>'
|
||||
+ '<div id="user-email">jane@doe.cz</div>'
|
||||
+ '<div id="app">'
|
||||
+ ' <div id="app-navigation" class="icon-loading">'
|
||||
+ ' <div id="mail-new-message-fixed"></div>'
|
||||
+ ' <div id="usergrouplist"></div>'
|
||||
+ ' <div id="app-settings">'
|
||||
+ ' <div id="app-settings-header">'
|
||||
+ ' <button class="settings-button" data-apps-slide-toggle="#app-settings-content"><?php p($l->t("Settings"));?></button>'
|
||||
+ ' </div>'
|
||||
+ ' <div id="app-settings-content"></div>'
|
||||
+ ' </div>'
|
||||
+ ' </div>'
|
||||
+ ' <div id="app-content">'
|
||||
+ ' <div class="mail-content container">'
|
||||
+ ' <div class="container icon-loading"></div>'
|
||||
+ ' </div>'
|
||||
+ ' </div>'
|
||||
+ '</div>');
|
||||
});
|
||||
|
||||
afterEach(function() {
|
||||
jasmine.Ajax.uninstall();
|
||||
});
|
||||
|
||||
it('starts', function(done) {
|
||||
var resolve;
|
||||
var accountsPromise = new Promise(function(res) {
|
||||
resolve = res;
|
||||
});
|
||||
|
||||
spyOn(Radio.ui, 'trigger');
|
||||
spyOn(Backbone.history, 'start');
|
||||
spyOn(AccountController, 'loadAccounts').and.callFake(function() {
|
||||
return accountsPromise;
|
||||
});
|
||||
spyOn(Mail, 'startBackgroundSync');
|
||||
|
||||
// No ajax calls so far
|
||||
expect(jasmine.Ajax.requests.count()).toBe(0);
|
||||
|
||||
// Let's go…
|
||||
Mail.start();
|
||||
|
||||
expect(Radio.ui.trigger).toHaveBeenCalledWith('content:loading', 'Loading accounts');
|
||||
|
||||
var accounts = new AccountCollection([
|
||||
{
|
||||
accountId: 44,
|
||||
name: 'Jane Doe',
|
||||
email: 'jane@doe.se'
|
||||
}
|
||||
]);
|
||||
resolve(accounts);
|
||||
|
||||
accountsPromise.then(function() {
|
||||
// The promise is resolved asynchronously, so we have to use the
|
||||
// promise here too
|
||||
expect(Backbone.history.start).toHaveBeenCalled();
|
||||
}).then(done).catch(done.fail);
|
||||
});
|
||||
});
|
||||
});
|
|
@ -1,64 +0,0 @@
|
|||
/* global spyOn */
|
||||
|
||||
/**
|
||||
* @author Christoph Wurst <christoph@winzerhof-wurst.at>
|
||||
*
|
||||
* Mail
|
||||
*
|
||||
* This code is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License, version 3,
|
||||
* as published by the Free Software Foundation.
|
||||
*
|
||||
* 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, version 3,
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||
*
|
||||
*/
|
||||
|
||||
define(['models/account',
|
||||
'models/foldercollection',
|
||||
'models/aliasescollection',
|
||||
'models/folder',
|
||||
'OC'
|
||||
], function(Account, FolderCollection, AliasCollection, Folder, OC) {
|
||||
describe('Account test', function() {
|
||||
var account;
|
||||
|
||||
beforeEach(function() {
|
||||
account = new Account();
|
||||
});
|
||||
|
||||
it('has collections as default attributes', function() {
|
||||
var folders = account.folders;
|
||||
var aliases = account.get('aliases');
|
||||
|
||||
expect(folders instanceof FolderCollection).toBe(true);
|
||||
expect(aliases instanceof AliasCollection).toBe(true);
|
||||
});
|
||||
|
||||
it('uses accountId as id attribute', function() {
|
||||
expect(account.idAttribute).toBe('accountId');
|
||||
});
|
||||
|
||||
it('has the correct URL', function() {
|
||||
spyOn(OC, 'generateUrl').and.returnValue('index.php/apps/mail/api/accounts');
|
||||
|
||||
var url = account.url();
|
||||
|
||||
expect(url).toBe('index.php/apps/mail/api/accounts');
|
||||
});
|
||||
|
||||
it('adds folders to its collection', function() {
|
||||
var folder = new Folder();
|
||||
|
||||
account.addFolder(folder);
|
||||
|
||||
expect(account.folders.length).toBe(1);
|
||||
expect(folder.account).toBe(account);
|
||||
});
|
||||
});
|
||||
});
|
|
@ -1,55 +0,0 @@
|
|||
/**
|
||||
* @author Christoph Wurst <christoph@winzerhof-wurst.at>
|
||||
*
|
||||
* Mail
|
||||
*
|
||||
* This code is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License, version 3,
|
||||
* as published by the Free Software Foundation.
|
||||
*
|
||||
* 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, version 3,
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||
*
|
||||
*/
|
||||
|
||||
define([
|
||||
'models/accountcollection',
|
||||
'models/account',
|
||||
'OC'
|
||||
], function(AccountCollection, Account, OC) {
|
||||
describe('AccountCollection', function() {
|
||||
var collection;
|
||||
var account;
|
||||
|
||||
beforeEach(function() {
|
||||
collection = new AccountCollection();
|
||||
});
|
||||
|
||||
it('contains accounts', function() {
|
||||
expect(collection.model).toBe(Account);
|
||||
});
|
||||
|
||||
it('uses the right URL', function() {
|
||||
spyOn(OC, 'generateUrl').and.returnValue('index.php/apps/mail/api/accounts');
|
||||
|
||||
var url = collection.url();
|
||||
|
||||
expect(OC.generateUrl).toHaveBeenCalled();
|
||||
expect(url).toBe('index.php/apps/mail/api/accounts');
|
||||
});
|
||||
|
||||
it('sorts accounts by accountId', function() {
|
||||
account = new Account();
|
||||
account.set('accountId', 12);
|
||||
|
||||
var cmp = collection.comparator(account);
|
||||
|
||||
expect(cmp).toBe(12);
|
||||
});
|
||||
});
|
||||
});
|
|
@ -1,41 +0,0 @@
|
|||
/**
|
||||
* @author Christoph Wurst <christoph@winzerhof-wurst.at>
|
||||
*
|
||||
* Mail
|
||||
*
|
||||
* This code is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License, version 3,
|
||||
* as published by the Free Software Foundation.
|
||||
*
|
||||
* 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, version 3,
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||
*
|
||||
*/
|
||||
|
||||
|
||||
define(['models/alias'], function(Alias) {
|
||||
describe('Alias test', function() {
|
||||
var alias;
|
||||
|
||||
beforeEach(function() {
|
||||
alias = new Alias();
|
||||
});
|
||||
|
||||
it('serializes the alias model', function() {
|
||||
alias.set('id', 123);
|
||||
alias.set('alias', 'alias@example.com');
|
||||
|
||||
var serialized = alias.toJSON();
|
||||
|
||||
expect(serialized).toEqual({
|
||||
id: 123,
|
||||
alias: 'alias@example.com'
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
|
@ -1,35 +0,0 @@
|
|||
/**
|
||||
* @author Christoph Wurst <christoph@winzerhof-wurst.at>
|
||||
*
|
||||
* Mail
|
||||
*
|
||||
* This code is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License, version 3,
|
||||
* as published by the Free Software Foundation.
|
||||
*
|
||||
* 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, version 3,
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||
*
|
||||
*/
|
||||
|
||||
define([
|
||||
'models/aliasescollection',
|
||||
'models/alias'
|
||||
], function(AliasesCollection, Alias) {
|
||||
describe('AliasesCollection', function() {
|
||||
var collection;
|
||||
|
||||
beforeEach(function() {
|
||||
collection = new AliasesCollection();
|
||||
});
|
||||
|
||||
it('contains aliases', function() {
|
||||
expect(collection.model).toBe(Alias);
|
||||
});
|
||||
});
|
||||
});
|
|
@ -1,56 +0,0 @@
|
|||
/* global expect */
|
||||
|
||||
/**
|
||||
* @author Christoph Wurst <christoph@winzerhof-wurst.at>
|
||||
*
|
||||
* Mail
|
||||
*
|
||||
* This code is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License, version 3,
|
||||
* as published by the Free Software Foundation.
|
||||
*
|
||||
* 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, version 3,
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||
*
|
||||
*/
|
||||
|
||||
define([
|
||||
'models/attachment'
|
||||
], function(Attachment) {
|
||||
describe('Attachment', function() {
|
||||
var attachment;
|
||||
|
||||
it('has an random id', function() {
|
||||
var a1 = new Attachment({
|
||||
fileName: '/file1.png'
|
||||
});
|
||||
var a2 = new Attachment({
|
||||
fileName: '/cat.jpg'
|
||||
});
|
||||
|
||||
expect(a1.get('id')).toBeDefined();
|
||||
expect(a2.get('id')).toBeDefined();
|
||||
|
||||
expect(a1.get('id')).not.toBe(a2.get('id'));
|
||||
});
|
||||
|
||||
it('ha no displayName if fileName is not set', function() {
|
||||
attachment = new Attachment();
|
||||
|
||||
expect(attachment.get('displayName')).toBeUndefined();
|
||||
});
|
||||
|
||||
it('removes leading slash from display name', function() {
|
||||
attachment = new Attachment({
|
||||
fileName: '/my/file.jpg'
|
||||
});
|
||||
|
||||
expect(attachment.get('displayName')).toBe('my/file.jpg');
|
||||
});
|
||||
});
|
||||
});
|
|
@ -1,37 +0,0 @@
|
|||
/* global expect */
|
||||
|
||||
/**
|
||||
* @author Christoph Wurst <christoph@winzerhof-wurst.at>
|
||||
*
|
||||
* Mail
|
||||
*
|
||||
* This code is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License, version 3,
|
||||
* as published by the Free Software Foundation.
|
||||
*
|
||||
* 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, version 3,
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||
*
|
||||
*/
|
||||
|
||||
define([
|
||||
'models/attachments',
|
||||
'models/attachment'
|
||||
], function(Attachents, Attachment) {
|
||||
describe('Attachments', function() {
|
||||
var attachments;
|
||||
|
||||
beforeEach(function() {
|
||||
attachments = new Attachents();
|
||||
});
|
||||
|
||||
it('contains attachments', function() {
|
||||
expect(attachments.model).toBe(Attachment);
|
||||
});
|
||||
});
|
||||
});
|
|
@ -1,77 +0,0 @@
|
|||
/* global expect */
|
||||
|
||||
/**
|
||||
* @author Christoph Wurst <christoph@winzerhof-wurst.at>
|
||||
*
|
||||
* Mail
|
||||
*
|
||||
* This code is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License, version 3,
|
||||
* as published by the Free Software Foundation.
|
||||
*
|
||||
* 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, version 3,
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||
*
|
||||
*/
|
||||
|
||||
define([
|
||||
'models/folder',
|
||||
'models/account',
|
||||
'models/messagecollection',
|
||||
'models/message'
|
||||
], function(Folder, Account, MessageCollection, Message) {
|
||||
describe('Folder', function() {
|
||||
var account;
|
||||
var folder;
|
||||
|
||||
beforeEach(function() {
|
||||
account = new Account();
|
||||
folder = new Folder();
|
||||
account.addFolder(folder);
|
||||
});
|
||||
|
||||
it('has messages', function() {
|
||||
expect(folder.messages instanceof MessageCollection).toBe(true);
|
||||
});
|
||||
|
||||
it('toggles open', function() {
|
||||
expect(folder.get('open')).toBe(false);
|
||||
|
||||
folder.toggleOpen();
|
||||
|
||||
expect(folder.get('open')).toBe(true);
|
||||
|
||||
folder.toggleOpen();
|
||||
|
||||
expect(folder.get('open')).toBe(false);
|
||||
});
|
||||
|
||||
it('assigns itself to an added message', function() {
|
||||
var message = new Message();
|
||||
|
||||
expect(folder.messages.length).toBe(0);
|
||||
|
||||
folder.addMessage(message);
|
||||
|
||||
expect(folder.messages.length).toBe(1);
|
||||
expect(message.folder).toBe(folder);
|
||||
});
|
||||
|
||||
it('assigns itself to added messages', function() {
|
||||
var messages = [{}, {}];
|
||||
|
||||
expect(folder.messages.length).toBe(0);
|
||||
|
||||
folder.addMessages(messages);
|
||||
|
||||
expect(folder.messages.length).toBe(2);
|
||||
expect(folder.messages.at(0).folder).toBe(folder);
|
||||
expect(folder.messages.at(1).folder).toBe(folder);
|
||||
});
|
||||
});
|
||||
});
|
|
@ -1,35 +0,0 @@
|
|||
/**
|
||||
* @author Christoph Wurst <christoph@winzerhof-wurst.at>
|
||||
*
|
||||
* Mail
|
||||
*
|
||||
* This code is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License, version 3,
|
||||
* as published by the Free Software Foundation.
|
||||
*
|
||||
* 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, version 3,
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||
*
|
||||
*/
|
||||
|
||||
define([
|
||||
'models/foldercollection',
|
||||
'models/folder'
|
||||
], function(FolderCollection, Folder) {
|
||||
describe('FolderCollection', function() {
|
||||
var collection;
|
||||
|
||||
beforeEach(function() {
|
||||
collection = new FolderCollection();
|
||||
});
|
||||
|
||||
it('contains folders', function() {
|
||||
expect(collection.model).toBe(Folder);
|
||||
});
|
||||
});
|
||||
});
|
|
@ -1,69 +0,0 @@
|
|||
/* global expect */
|
||||
|
||||
/**
|
||||
* @author Luc Calaresu <dev@calaresu.com>
|
||||
*
|
||||
* Mail
|
||||
*
|
||||
* This code is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License, version 3,
|
||||
* as published by the Free Software Foundation.
|
||||
*
|
||||
* 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, version 3,
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||
*
|
||||
*/
|
||||
|
||||
define([
|
||||
'models/localattachment'
|
||||
], function(LocalAttachment) {
|
||||
|
||||
/* LocalAttachment derivates from Attachment */
|
||||
/* We just test the specifics since Attachment is already tested */
|
||||
describe('LocalAttachment', function() {
|
||||
var attachment;
|
||||
|
||||
it('has an initial progress and status equal to 0', function() {
|
||||
var a1 = new LocalAttachment({
|
||||
fileName: '/file1.png'
|
||||
});
|
||||
|
||||
expect(a1.get('progress')).toBe(0);
|
||||
expect(a1.get('uploadStatus')).toBe(0);
|
||||
});
|
||||
|
||||
it('updates its attributes on upload progress', function() {
|
||||
attachment = new LocalAttachment();
|
||||
/* simulate a call to 'onProgress' with some example values */
|
||||
var progressEvent = {
|
||||
lengthComputable: true,
|
||||
loaded: 500,
|
||||
total: 1000
|
||||
};
|
||||
attachment.onProgress(progressEvent);
|
||||
|
||||
/* we expect the status to be 'ONGOING' and the value 0.5 (=500/1000) */
|
||||
expect(attachment.get('progress')).toBe(0.5);
|
||||
expect(attachment.get('uploadStatus')).toBe(1);
|
||||
});
|
||||
|
||||
it('does not update its attributes if progress is not computable', function() {
|
||||
attachment = new LocalAttachment();
|
||||
/* simulate a call to 'onProgress' with some example values */
|
||||
var progressEvent = {
|
||||
lengthComputable: false,
|
||||
loaded: 1000,
|
||||
total: 1000
|
||||
};
|
||||
attachment.onProgress(progressEvent);
|
||||
|
||||
expect(attachment.get('progress')).toBe(0);
|
||||
expect(attachment.get('uploadStatus')).toBe(0);
|
||||
});
|
||||
});
|
||||
});
|
|
@ -1,49 +0,0 @@
|
|||
/**
|
||||
* @author Christoph Wurst <christoph@winzerhof-wurst.at>
|
||||
*
|
||||
* Mail
|
||||
*
|
||||
* This code is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License, version 3,
|
||||
* as published by the Free Software Foundation.
|
||||
*
|
||||
* 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, version 3,
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||
*
|
||||
*/
|
||||
|
||||
define([
|
||||
'models/message',
|
||||
'models/messageflags'
|
||||
], function(Message, MessageFlags) {
|
||||
describe('Message', function() {
|
||||
var message;
|
||||
|
||||
beforeEach(function() {
|
||||
message = new Message();
|
||||
});
|
||||
|
||||
it('has flags and is inactive by default', function() {
|
||||
expect(message.get('flags') instanceof MessageFlags).toBe(true);
|
||||
expect(message.get('active')).toBe(false);
|
||||
});
|
||||
|
||||
it('triggers a change event whenever flags change', function() {
|
||||
var hnd1 = jasmine.createSpy('handler1');
|
||||
var hnd2 = jasmine.createSpy('handler2');
|
||||
|
||||
message.on('change', hnd1);
|
||||
message.on('change', hnd2);
|
||||
|
||||
message.get('flags').trigger('change');
|
||||
|
||||
expect(hnd1).toHaveBeenCalled();
|
||||
expect(hnd2).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
});
|
|
@ -1,43 +0,0 @@
|
|||
/**
|
||||
* @author Christoph Wurst <christoph@winzerhof-wurst.at>
|
||||
*
|
||||
* Mail
|
||||
*
|
||||
* This code is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License, version 3,
|
||||
* as published by the Free Software Foundation.
|
||||
*
|
||||
* 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, version 3,
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||
*
|
||||
*/
|
||||
|
||||
define(['models/messagecollection',
|
||||
'models/message'],
|
||||
function(MessageCollection, Message) {
|
||||
describe('MessageCollection', function() {
|
||||
var collection;
|
||||
|
||||
beforeEach(function() {
|
||||
collection = new MessageCollection();
|
||||
});
|
||||
|
||||
it('contains messages', function() {
|
||||
expect(collection.model).toBe(Message);
|
||||
});
|
||||
|
||||
it('compares messages by date', function() {
|
||||
var message = new Message();
|
||||
message.set('dateInt', 12345);
|
||||
|
||||
var cmp = collection.comparator(message);
|
||||
|
||||
expect(cmp).toBe(-12345);
|
||||
});
|
||||
});
|
||||
});
|
|
@ -1,34 +0,0 @@
|
|||
/**
|
||||
* @author Christoph Wurst <christoph@winzerhof-wurst.at>
|
||||
*
|
||||
* Mail
|
||||
*
|
||||
* This code is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License, version 3,
|
||||
* as published by the Free Software Foundation.
|
||||
*
|
||||
* 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, version 3,
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||
*
|
||||
*/
|
||||
|
||||
define([
|
||||
'models/messageflags'
|
||||
], function(MessageFlag) {
|
||||
describe('MessageFlag', function() {
|
||||
var messageFlag;
|
||||
|
||||
beforeEach(function() {
|
||||
messageFlag = new MessageFlag();
|
||||
});
|
||||
|
||||
it('is answered by default', function() {
|
||||
expect(messageFlag.get('answered')).toBe(false);
|
||||
});
|
||||
});
|
||||
});
|
|
@ -1,191 +0,0 @@
|
|||
/* global expect */
|
||||
|
||||
/**
|
||||
* @copyright 2017 Christoph Wurst <christoph@winzerhof-wurst.at>
|
||||
*
|
||||
* @author 2017 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/>.
|
||||
*
|
||||
*/
|
||||
|
||||
define([
|
||||
'replybuilder',
|
||||
'models/message',
|
||||
'models/folder',
|
||||
'models/account',
|
||||
'backbone'
|
||||
], function(ReplyBuilder, Message, Folder, Account, Backbone) {
|
||||
|
||||
describe('ReplyBuilder', function() {
|
||||
|
||||
var message, messageBody, folder, account;
|
||||
|
||||
beforeEach(function() {
|
||||
message = new Message();
|
||||
messageBody = new Backbone.Model();
|
||||
folder = new Folder();
|
||||
account = new Account();
|
||||
account.addFolder(folder);
|
||||
folder.addMessage(message);
|
||||
});
|
||||
|
||||
var createAddress = function(addr) {
|
||||
return {
|
||||
label: addr,
|
||||
email: addr
|
||||
};
|
||||
};
|
||||
|
||||
var setEmail = function(message, address) {
|
||||
message.folder.account.set('emailAddress', address.email);
|
||||
};
|
||||
|
||||
var assertSameAddressList = function(l1, l2) {
|
||||
var rawL1 = l1.map(function(a) {
|
||||
return a.email;
|
||||
});
|
||||
var rawL2 = l2.map(function(a) {
|
||||
return a.email;
|
||||
});
|
||||
rawL1.sort();
|
||||
rawL2.sort();
|
||||
expect(rawL1).toEqual(rawL2);
|
||||
};
|
||||
|
||||
// b -> a to a -as b
|
||||
it('handles a one-on-one reply', function() {
|
||||
var a = createAddress('a@domain.tld');
|
||||
var b = createAddress('b@domain.tld');
|
||||
messageBody.set('from', [b]);
|
||||
messageBody.set('to', [a]);
|
||||
messageBody.set('cc', []);
|
||||
setEmail(message, a);
|
||||
|
||||
var reply = ReplyBuilder.buildReply(message, messageBody);
|
||||
|
||||
assertSameAddressList(reply.from, [a]);
|
||||
assertSameAddressList(reply.to, [b]);
|
||||
assertSameAddressList(reply.cc, []);
|
||||
});
|
||||
|
||||
it('handles simple group reply', function() {
|
||||
var a = createAddress('a@domain.tld');
|
||||
var b = createAddress('b@domain.tld');
|
||||
var c = createAddress('c@domain.tld');
|
||||
messageBody.set('from', [a]);
|
||||
messageBody.set('to', [b, c]);
|
||||
messageBody.set('cc', []);
|
||||
setEmail(message, b);
|
||||
|
||||
var reply = ReplyBuilder.buildReply(message, messageBody);
|
||||
|
||||
assertSameAddressList(reply.from, [b]);
|
||||
assertSameAddressList(reply.to, [a, c]);
|
||||
assertSameAddressList(reply.cc, []);
|
||||
});
|
||||
|
||||
|
||||
it('handles group reply with CC', function() {
|
||||
var a = createAddress('a@domain.tld');
|
||||
var b = createAddress('b@domain.tld');
|
||||
var c = createAddress('c@domain.tld');
|
||||
var d = createAddress('d@domain.tld');
|
||||
messageBody.set('from', [a]);
|
||||
messageBody.set('to', [b, c]);
|
||||
messageBody.set('cc', [d]);
|
||||
setEmail(message, b);
|
||||
|
||||
var reply = ReplyBuilder.buildReply(message, messageBody);
|
||||
|
||||
assertSameAddressList(reply.from, [b]);
|
||||
assertSameAddressList(reply.to, [a, c]);
|
||||
assertSameAddressList(reply.cc, [d]);
|
||||
});
|
||||
|
||||
it('handles group reply of CC address', function() {
|
||||
var a = createAddress('a@domain.tld');
|
||||
var b = createAddress('b@domain.tld');
|
||||
var c = createAddress('c@domain.tld');
|
||||
var d = createAddress('d@domain.tld');
|
||||
messageBody.set('from', [a]);
|
||||
messageBody.set('to', [b, c]);
|
||||
messageBody.set('cc', [d]);
|
||||
setEmail(message, d);
|
||||
|
||||
var reply = ReplyBuilder.buildReply(message, messageBody);
|
||||
|
||||
assertSameAddressList(reply.from, [d]);
|
||||
assertSameAddressList(reply.to, [a, b, c]);
|
||||
assertSameAddressList(reply.cc, []);
|
||||
});
|
||||
|
||||
it('handles group reply of CC address with many CCs', function() {
|
||||
var a = createAddress('a@domain.tld');
|
||||
var b = createAddress('b@domain.tld');
|
||||
var c = createAddress('c@domain.tld');
|
||||
var d = createAddress('d@domain.tld');
|
||||
var e = createAddress('e@domain.tld');
|
||||
messageBody.set('from', [a]);
|
||||
messageBody.set('to', [b, c]);
|
||||
messageBody.set('cc', [d, e]);
|
||||
setEmail(message, e);
|
||||
|
||||
var reply = ReplyBuilder.buildReply(message, messageBody);
|
||||
|
||||
assertSameAddressList(reply.from, [e]);
|
||||
assertSameAddressList(reply.to, [a, b, c]);
|
||||
assertSameAddressList(reply.cc, [d]);
|
||||
});
|
||||
|
||||
it('handles reply of message where the recipient is in the CC', function() {
|
||||
var ali = createAddress('ali@domain.tld');
|
||||
var bob = createAddress('bob@domain.tld');
|
||||
var me = createAddress('c@domain.tld');
|
||||
var dani = createAddress('d@domain.tld');
|
||||
|
||||
messageBody.set('from', [ali]);
|
||||
messageBody.set('to', [bob]);
|
||||
messageBody.set('cc', [me, dani]);
|
||||
setEmail(message, me);
|
||||
|
||||
var reply = ReplyBuilder.buildReply(message, messageBody);
|
||||
|
||||
assertSameAddressList(reply.from, [me]);
|
||||
assertSameAddressList(reply.to, [ali, bob]);
|
||||
assertSameAddressList(reply.cc, [dani]);
|
||||
});
|
||||
|
||||
it('handles jan\'s reply to nina\'s mesage to a mailing list', function() {
|
||||
var nina = createAddress('nina@nc.com');
|
||||
var list = createAddress('list@nc.com');
|
||||
var jan = createAddress('jan@nc.com');
|
||||
|
||||
messageBody.set('from', [nina]);
|
||||
messageBody.set('to', [list]);
|
||||
messageBody.set('cc', []);
|
||||
setEmail(message, jan);
|
||||
|
||||
var reply = ReplyBuilder.buildReply(message, messageBody);
|
||||
|
||||
assertSameAddressList(reply.from, [jan]);
|
||||
assertSameAddressList(reply.to, [nina, list]);
|
||||
assertSameAddressList(reply.cc, []);
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
});
|
|
@ -1,78 +0,0 @@
|
|||
/* global sinon, expect */
|
||||
|
||||
/**
|
||||
* @copyright 2017 Christoph Wurst <christoph@winzerhof-wurst.at>
|
||||
*
|
||||
* @author 2017 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/>.
|
||||
*
|
||||
*/
|
||||
|
||||
define([
|
||||
'service/avatarservice'
|
||||
], function(AvatarService) {
|
||||
'use strict';
|
||||
|
||||
describe('AvatarService', function() {
|
||||
var server;
|
||||
|
||||
beforeEach(function() {
|
||||
server = sinon.fakeServer.create();
|
||||
});
|
||||
|
||||
afterEach(function() {
|
||||
server.restore();
|
||||
});
|
||||
|
||||
it('does not load the image if no avatar is available', function(done) {
|
||||
var loading = AvatarService.loadAvatar('user@domain.com');
|
||||
|
||||
expect(server.requests.length).toBe(1);
|
||||
server.requests[0].respond(
|
||||
404,
|
||||
{
|
||||
'Content-Type': 'application/json'
|
||||
});
|
||||
|
||||
loading.then(function(url) {
|
||||
expect(url).toBe(undefined);
|
||||
done();
|
||||
}).catch(done.fail);
|
||||
});
|
||||
|
||||
it('does load the image if an avatar is available', function(done) {
|
||||
var loading = AvatarService.loadAvatar('user@domain.com');
|
||||
|
||||
expect(server.requests.length).toBe(1);
|
||||
server.requests[0].respond(
|
||||
200,
|
||||
{
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
JSON.stringify({
|
||||
isExternal: true,
|
||||
mime: 'image/jpeg',
|
||||
url: 'https://domain.com/favicon.ico'
|
||||
}));
|
||||
|
||||
loading.then(function(url) {
|
||||
expect(url).not.toBe(undefined);
|
||||
done();
|
||||
}).catch(done.fail);
|
||||
});
|
||||
});
|
||||
});
|
|
@ -1,49 +0,0 @@
|
|||
/* global expect, Promise, spyOn */
|
||||
|
||||
/**
|
||||
* @author Christoph Wurst <christoph@winzerhof-wurst.at>
|
||||
*
|
||||
* Mail
|
||||
*
|
||||
* This code is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License, version 3,
|
||||
* as published by the Free Software Foundation.
|
||||
*
|
||||
* 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, version 3,
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||
*
|
||||
*/
|
||||
|
||||
define([
|
||||
'radio',
|
||||
'models/account',
|
||||
'models/folder',
|
||||
'service/backgroundsyncservice'
|
||||
], function(Radio, Account, Folder, BackgroundSyncService) {
|
||||
describe('Background sync service', function() {
|
||||
it('fails', function(done) {
|
||||
spyOn(Radio.sync, 'request').and.returnValue(Promise.resolve([]));
|
||||
spyOn(Radio.ui, 'trigger');
|
||||
var account = new Account({
|
||||
accountId: -1,
|
||||
isUnified: true
|
||||
});
|
||||
var folder = new Folder({
|
||||
account: account
|
||||
});
|
||||
account.addFolder(folder);
|
||||
|
||||
BackgroundSyncService.sync(account).then(function() {
|
||||
expect(Radio.sync.request).toHaveBeenCalledWith('sync:folder', folder);
|
||||
expect(Radio.ui.trigger).
|
||||
toHaveBeenCalledWith('notification:mail:show', []);
|
||||
done();
|
||||
}).catch(done.fail);
|
||||
});
|
||||
});
|
||||
});
|
|
@ -1,496 +0,0 @@
|
|||
/* global sinon, expect */
|
||||
|
||||
/**
|
||||
* @author Christoph Wurst <christoph@winzerhof-wurst.at>
|
||||
*
|
||||
* Mail
|
||||
*
|
||||
* This code is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License, version 3,
|
||||
* as published by the Free Software Foundation.
|
||||
*
|
||||
* 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, version 3,
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||
*
|
||||
*/
|
||||
|
||||
define([
|
||||
'service/foldersyncservice',
|
||||
'models/account',
|
||||
'models/accountcollection',
|
||||
'models/folder',
|
||||
'models/message',
|
||||
'state'
|
||||
], function(FolderSyncService, Account, AccountCollection, Folder, Message,
|
||||
State) {
|
||||
|
||||
describe('FolderSyncService', function() {
|
||||
var server;
|
||||
|
||||
beforeEach(function() {
|
||||
State.accounts = new AccountCollection();
|
||||
server = sinon.fakeServer.create();
|
||||
});
|
||||
|
||||
afterEach(function() {
|
||||
State.accounts = new AccountCollection();
|
||||
server.restore();
|
||||
});
|
||||
|
||||
it('syncs the sync token of a single folder', function(done) {
|
||||
var account = new Account({
|
||||
accountId: 15
|
||||
});
|
||||
var folder = new Folder({
|
||||
id: 'SU5CT1g=',
|
||||
syncToken: 'oldToken',
|
||||
account: account
|
||||
});
|
||||
folder.addMessage(new Message({
|
||||
id: 123
|
||||
}));
|
||||
folder.addMessage(new Message({
|
||||
id: 124
|
||||
}));
|
||||
|
||||
var syncing = FolderSyncService.syncFolder(folder);
|
||||
|
||||
expect(server.requests.length).toBe(1);
|
||||
server.requests[0].respond(
|
||||
200,
|
||||
{
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
JSON.stringify({
|
||||
token: 'newToken',
|
||||
newMessages: [
|
||||
{
|
||||
id: 125
|
||||
}
|
||||
],
|
||||
changedMessages: [],
|
||||
vanishedMessages: []
|
||||
})
|
||||
);
|
||||
|
||||
syncing.then(function() {
|
||||
expect(folder.messages.pluck('id')).toEqual([123, 124, 125]);
|
||||
done();
|
||||
}).catch(function(e) {
|
||||
console.error(e);
|
||||
done.fail(e);
|
||||
});
|
||||
});
|
||||
|
||||
it('syncs new messages in a single folder', function(done) {
|
||||
var account = new Account({
|
||||
accountId: 15
|
||||
});
|
||||
var folder = new Folder({
|
||||
id: 'SU5CT1g=',
|
||||
syncToken: 'oldToken',
|
||||
account: account
|
||||
});
|
||||
folder.addMessage(new Message({
|
||||
id: 123
|
||||
}));
|
||||
folder.addMessage(new Message({
|
||||
id: 124
|
||||
}));
|
||||
|
||||
var syncing = FolderSyncService.syncFolder(folder);
|
||||
|
||||
expect(server.requests.length).toBe(1);
|
||||
server.requests[0].respond(
|
||||
200,
|
||||
{
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
JSON.stringify({
|
||||
token: 'newToken',
|
||||
newMessages: [
|
||||
{
|
||||
id: 125
|
||||
}
|
||||
],
|
||||
changedMessages: [],
|
||||
vanishedMessages: []
|
||||
})
|
||||
);
|
||||
|
||||
syncing.then(function() {
|
||||
expect(folder.messages.pluck('id')).toEqual([123, 124, 125]);
|
||||
done();
|
||||
}).catch(function(e) {
|
||||
console.error(e);
|
||||
done.fail(e);
|
||||
});
|
||||
});
|
||||
|
||||
it('syncs changed messages in a single folder', function(done) {
|
||||
var account = new Account({
|
||||
accountId: 15
|
||||
});
|
||||
var folder = new Folder({
|
||||
id: 'SU5CT1g=',
|
||||
syncToken: 'oldToken',
|
||||
account: account
|
||||
});
|
||||
folder.addMessage(new Message({
|
||||
id: 123,
|
||||
subject: 'old subject'
|
||||
}));
|
||||
folder.addMessage(new Message({
|
||||
id: 124
|
||||
}));
|
||||
|
||||
var syncing = FolderSyncService.syncFolder(folder);
|
||||
|
||||
expect(server.requests.length).toBe(1);
|
||||
server.requests[0].respond(
|
||||
200,
|
||||
{
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
JSON.stringify({
|
||||
token: 'newToken',
|
||||
newMessages: [],
|
||||
changedMessages: [
|
||||
{
|
||||
id: 123,
|
||||
subject: 'new subject'
|
||||
}
|
||||
],
|
||||
vanishedMessages: []
|
||||
})
|
||||
);
|
||||
|
||||
syncing.then(function() {
|
||||
expect(folder.messages.get(123).
|
||||
get('subject')).toEqual('new subject');
|
||||
done();
|
||||
}).catch(function(e) {
|
||||
console.error(e);
|
||||
done.fail(e);
|
||||
});
|
||||
});
|
||||
|
||||
it('syncs vanished messages in a single folder', function(done) {
|
||||
var account = new Account({
|
||||
accountId: 15
|
||||
});
|
||||
var folder = new Folder({
|
||||
id: 'SU5CT1g=',
|
||||
syncToken: 'oldToken',
|
||||
account: account
|
||||
});
|
||||
folder.addMessage(new Message({
|
||||
id: 123,
|
||||
subject: 'old subject'
|
||||
}));
|
||||
folder.addMessage(new Message({
|
||||
id: 124
|
||||
}));
|
||||
|
||||
var syncing = FolderSyncService.syncFolder(folder);
|
||||
|
||||
expect(server.requests.length).toBe(1);
|
||||
server.requests[0].respond(
|
||||
200,
|
||||
{
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
JSON.stringify({
|
||||
token: 'newToken',
|
||||
newMessages: [],
|
||||
changedMessages: [],
|
||||
vanishedMessages: [
|
||||
123
|
||||
]
|
||||
})
|
||||
);
|
||||
|
||||
syncing.then(function() {
|
||||
expect(folder.messages.pluck('id')).toEqual([124]);
|
||||
done();
|
||||
}).catch(function(e) {
|
||||
console.error(e);
|
||||
done.fail(e);
|
||||
});
|
||||
});
|
||||
|
||||
it('syncs the unified inbox, even if no accounts are configured', function(
|
||||
done) {
|
||||
var account = new Account({
|
||||
accountId: -1,
|
||||
isUnified: true
|
||||
});
|
||||
var folder = new Folder({
|
||||
account: account
|
||||
});
|
||||
|
||||
var syncing = FolderSyncService.syncFolder(folder);
|
||||
expect(server.requests.length).toBe(0);
|
||||
|
||||
syncing.then(done).catch(done.fail);
|
||||
});
|
||||
|
||||
describe('unified inbox with two accounts and three inboxes', function() {
|
||||
var account, acc1, acc2;
|
||||
var folder, folder11, folder12, folder21, folder22;
|
||||
|
||||
beforeEach(function() {
|
||||
account = new Account({
|
||||
accountId: -1,
|
||||
isUnified: true
|
||||
});
|
||||
folder = new Folder({
|
||||
account: account
|
||||
});
|
||||
acc1 = new Account({
|
||||
accountId: 1
|
||||
});
|
||||
folder11 = new Folder({
|
||||
id: 'inbox11',
|
||||
specialRole: 'inbox'
|
||||
});
|
||||
folder12 = new Folder({
|
||||
specialRole: 'sent'
|
||||
});
|
||||
acc2 = new Account({
|
||||
accountId: 2
|
||||
});
|
||||
folder21 = new Folder({
|
||||
id: 'inbox21',
|
||||
specialRole: 'inbox'
|
||||
});
|
||||
folder22 = new Folder({
|
||||
id: 'inbox22',
|
||||
specialRole: 'inbox'
|
||||
});
|
||||
account.addFolder(folder);
|
||||
acc1.addFolder(folder11);
|
||||
acc1.addFolder(folder12);
|
||||
acc2.addFolder(folder21);
|
||||
acc2.addFolder(folder22);
|
||||
State.accounts.add(account);
|
||||
State.accounts.add(acc1);
|
||||
State.accounts.add(acc2);
|
||||
});
|
||||
|
||||
it('syncs all changes', function(done) {
|
||||
// Add some messages
|
||||
var message211 = new Message({
|
||||
id: 234,
|
||||
subject: 'old sub',
|
||||
account: acc2
|
||||
});
|
||||
folder21.addMessage(message211);
|
||||
folder.addMessage(folder21.messages.get(234));
|
||||
var message221 = new Message({
|
||||
id: 345,
|
||||
account: acc2
|
||||
});
|
||||
folder22.addMessage(message221);
|
||||
folder.addMessage(folder22.messages.get(345));
|
||||
|
||||
var syncing = FolderSyncService.syncFolder(folder);
|
||||
expect(server.requests.length).toBe(3);
|
||||
|
||||
server.requests[0].respond(
|
||||
200,
|
||||
{
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
JSON.stringify({
|
||||
token: 'newToken',
|
||||
newMessages: [
|
||||
{
|
||||
id: 123
|
||||
}
|
||||
],
|
||||
changedMessages: [],
|
||||
vanishedMessages: []
|
||||
})
|
||||
);
|
||||
server.requests[1].respond(
|
||||
200,
|
||||
{
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
JSON.stringify({
|
||||
token: 'newToken',
|
||||
newMessages: [],
|
||||
changedMessages: [
|
||||
{
|
||||
id: 234,
|
||||
subject: 'new sub'
|
||||
}
|
||||
],
|
||||
vanishedMessages: []
|
||||
})
|
||||
);
|
||||
server.requests[2].respond(
|
||||
200,
|
||||
{
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
JSON.stringify({
|
||||
token: 'newToken',
|
||||
newMessages: [],
|
||||
changedMessages: [],
|
||||
vanishedMessages: [
|
||||
345
|
||||
]
|
||||
})
|
||||
);
|
||||
|
||||
syncing.then(function() {
|
||||
// New message saved to first inbox
|
||||
expect(folder11.messages.pluck('id')).toEqual([123]);
|
||||
// Update applied in second inbox
|
||||
expect(folder21.messages.get(234).get('subject')).toEqual('new sub');
|
||||
// Vanished message in third inbox is removed
|
||||
expect(folder22.messages.pluck('id')).toEqual([]);
|
||||
|
||||
done();
|
||||
}).catch(done.fail);
|
||||
});
|
||||
|
||||
it('removes vanished messages', function(done) {
|
||||
// Add some messages
|
||||
var message211 = new Message({
|
||||
id: 234,
|
||||
subject: 'Message 1',
|
||||
account: acc2
|
||||
});
|
||||
folder21.addMessage(message211);
|
||||
folder.addMessage(message211);
|
||||
|
||||
// Check initial state
|
||||
expect(folder21.messages.pluck('id')).toEqual([234]);
|
||||
expect(folder.messages.pluck('id')).toEqual([234]);
|
||||
|
||||
var syncing = FolderSyncService.syncFolder(folder);
|
||||
expect(server.requests.length).toBe(3);
|
||||
|
||||
server.requests[0].respond(
|
||||
200,
|
||||
{
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
JSON.stringify({
|
||||
token: 'newToken',
|
||||
newMessages: [],
|
||||
changedMessages: [],
|
||||
vanishedMessages: []
|
||||
}));
|
||||
server.requests[1].respond(
|
||||
200,
|
||||
{
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
JSON.stringify({
|
||||
token: 'newToken',
|
||||
newMessages: [],
|
||||
changedMessages: [],
|
||||
vanishedMessages: [
|
||||
234
|
||||
]
|
||||
}));
|
||||
server.requests[2].respond(
|
||||
200,
|
||||
{
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
JSON.stringify({
|
||||
token: 'newToken',
|
||||
newMessages: [],
|
||||
changedMessages: [],
|
||||
vanishedMessages: []
|
||||
}));
|
||||
|
||||
syncing.then(function() {
|
||||
// Vanished message in third inbox is removed
|
||||
expect(folder22.messages.pluck('id')).toEqual([]);
|
||||
|
||||
// Message was also removed from the unified folder
|
||||
expect(folder.messages.pluck('id')).toEqual([]);
|
||||
|
||||
done();
|
||||
}).catch(done.fail);
|
||||
});
|
||||
});
|
||||
|
||||
it('syncs the unified inbox when an individual one changes', function(done) {
|
||||
var account = new Account({
|
||||
accountId: -1,
|
||||
isUnified: true
|
||||
});
|
||||
var folder = new Folder({
|
||||
account: account
|
||||
});
|
||||
var acc1 = new Account({
|
||||
accountId: 1
|
||||
});
|
||||
var folder11 = new Folder({
|
||||
specialRole: 'inbox'
|
||||
});
|
||||
var folder12 = new Folder({
|
||||
specialRole: 'sent'
|
||||
});
|
||||
var acc2 = new Account({
|
||||
accountId: 2
|
||||
});
|
||||
var folder21 = new Folder({
|
||||
specialRole: 'inbox'
|
||||
});
|
||||
var folder22 = new Folder({
|
||||
specialRole: 'inbox'
|
||||
});
|
||||
account.addFolder(folder);
|
||||
acc1.addFolder(folder11);
|
||||
acc1.addFolder(folder12);
|
||||
acc2.addFolder(folder21);
|
||||
acc2.addFolder(folder22);
|
||||
State.accounts.add(account);
|
||||
State.accounts.add(acc1);
|
||||
State.accounts.add(acc2);
|
||||
|
||||
var syncing = FolderSyncService.syncFolder(folder11);
|
||||
expect(server.requests.length).toBe(1);
|
||||
|
||||
server.requests[0].respond(
|
||||
200,
|
||||
{
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
JSON.stringify({
|
||||
token: 'newToken',
|
||||
newMessages: [
|
||||
{
|
||||
id: 123
|
||||
}
|
||||
],
|
||||
changedMessages: [],
|
||||
vanishedMessages: []
|
||||
})
|
||||
);
|
||||
|
||||
syncing.then(function() {
|
||||
// New message saved to first inbox
|
||||
expect(folder11.messages.pluck('id')).toEqual([123]);
|
||||
// Unified inbox was updated too
|
||||
expect(folder.messages.pluck('id')).toEqual([123]);
|
||||
|
||||
done();
|
||||
}).catch(done.fail);
|
||||
});
|
||||
});
|
||||
|
||||
});
|
|
@ -1,198 +0,0 @@
|
|||
/* global sinon */
|
||||
|
||||
/**
|
||||
* @author Christoph Wurst <christoph@winzerhof-wurst.at>
|
||||
*
|
||||
* Mail
|
||||
*
|
||||
* This code is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License, version 3,
|
||||
* as published by the Free Software Foundation.
|
||||
*
|
||||
* 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, version 3,
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||
*
|
||||
*/
|
||||
|
||||
define([
|
||||
'underscore',
|
||||
'service/messageservice',
|
||||
'models/account',
|
||||
'models/accountcollection',
|
||||
'models/folder',
|
||||
'models/message',
|
||||
'state'
|
||||
], function(_, MessageService, Account, AccountCollection, Folder, Message,
|
||||
State) {
|
||||
'use strict';
|
||||
|
||||
describe('MessageService', function() {
|
||||
var server;
|
||||
|
||||
beforeEach(function() {
|
||||
State.accounts = new AccountCollection();
|
||||
server = sinon.fakeServer.create();
|
||||
});
|
||||
|
||||
afterEach(function() {
|
||||
State.accounts = new AccountCollection();
|
||||
server.restore();
|
||||
});
|
||||
|
||||
function getTestAccounts(nrAccounts) {
|
||||
var unifiedAccount = new Account({
|
||||
accountId: -1,
|
||||
isUnified: true
|
||||
});
|
||||
var unifiedInbox = new Folder({
|
||||
id: 'inbox',
|
||||
specialRole: 'inbox',
|
||||
account: unifiedAccount
|
||||
});
|
||||
unifiedAccount.addFolder(unifiedInbox);
|
||||
State.accounts.add(unifiedAccount);
|
||||
|
||||
return _.range(1, nrAccounts + 1).map(function(id) {
|
||||
var account = new Account({
|
||||
accountId: id
|
||||
});
|
||||
var inbox = new Folder({
|
||||
id: 'inbox',
|
||||
specialRole: 'inbox',
|
||||
account: account
|
||||
});
|
||||
var otherFolder = new Folder({
|
||||
id: 'something',
|
||||
account: account
|
||||
});
|
||||
account.addFolder(inbox);
|
||||
account.addFolder(otherFolder);
|
||||
State.accounts.add(account);
|
||||
return account;
|
||||
});
|
||||
}
|
||||
|
||||
function createMessages(count) {
|
||||
return _.range(count).map(function(id) {
|
||||
return new Message({
|
||||
id: id * 100,
|
||||
dateInt: id * 1000
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
it('fetches the next page of an individual folder', function(done) {
|
||||
var account = new Account();
|
||||
var folder = new Folder({
|
||||
id: 'XYZ'
|
||||
});
|
||||
account.addFolder(folder);
|
||||
var fetching = MessageService.getNextMessagePage(account, folder);
|
||||
|
||||
expect(server.requests.length).toBe(1);
|
||||
|
||||
server.requests[0].respond(
|
||||
200,
|
||||
{
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
JSON.stringify([
|
||||
{
|
||||
id: 123,
|
||||
subject: 'hello'
|
||||
}
|
||||
])
|
||||
);
|
||||
|
||||
expect(server.requests.length).toBe(1);
|
||||
|
||||
fetching.then(function(messages) {
|
||||
expect(messages.length).toBe(1);
|
||||
done();
|
||||
}).catch(done.fail);
|
||||
});
|
||||
|
||||
it('propagates fetch errors', function(done) {
|
||||
var account = new Account();
|
||||
var folder = new Folder({
|
||||
id: 'XYZ',
|
||||
account: account
|
||||
});
|
||||
account.addFolder(folder);
|
||||
var fetching = MessageService.getNextMessagePage(account, folder);
|
||||
|
||||
expect(server.requests.length).toBe(1);
|
||||
|
||||
server.requests[0].respond(
|
||||
500,
|
||||
{
|
||||
'Content-Type': 'application/json'
|
||||
});
|
||||
|
||||
fetching.then(done.catch).catch(done);
|
||||
});
|
||||
|
||||
it('loads unified inbox page and uses local data if enough data is available', function(
|
||||
done) {
|
||||
var testAccounts = getTestAccounts(2);
|
||||
var unifiedAccount = State.accounts.get(-1);
|
||||
var unifiedInbox = unifiedAccount.folders.first();
|
||||
var inbox1 = testAccounts[0].folders.first();
|
||||
var inbox2 = testAccounts[1].folders.first();
|
||||
|
||||
inbox1.addMessages(createMessages(25));
|
||||
inbox2.addMessages(createMessages(25));
|
||||
|
||||
var fetching = MessageService.getNextMessagePage(unifiedAccount, unifiedInbox);
|
||||
|
||||
expect(server.requests.length).toBe(0);
|
||||
|
||||
fetching.then(function() {
|
||||
expect(unifiedInbox.messages.length).toBe(20);
|
||||
done();
|
||||
}).catch(done.fail);
|
||||
});
|
||||
|
||||
it('loads unified inbox page and fetches pages where necessary', function(
|
||||
done) {
|
||||
var testAccounts = getTestAccounts(2);
|
||||
var unifiedAccount = State.accounts.get(-1);
|
||||
var unifiedInbox = unifiedAccount.folders.first();
|
||||
var inbox1 = testAccounts[0].folders.first();
|
||||
var inbox2 = testAccounts[1].folders.first();
|
||||
|
||||
inbox1.addMessages(createMessages(25));
|
||||
inbox2.addMessages(createMessages(5));
|
||||
|
||||
var fetching = MessageService.getNextMessagePage(unifiedAccount, unifiedInbox);
|
||||
|
||||
expect(server.requests.length).toBe(1);
|
||||
|
||||
server.requests[0].respond(
|
||||
200,
|
||||
{
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
JSON.stringify([
|
||||
{
|
||||
id: 123,
|
||||
subject: 'hello',
|
||||
dateInt: 26000
|
||||
}
|
||||
])
|
||||
);
|
||||
|
||||
fetching.then(function() {
|
||||
expect(unifiedInbox.messages.length).toBe(20);
|
||||
done();
|
||||
}).catch(done.fail);
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
});
|
|
@ -1,92 +0,0 @@
|
|||
/* global sinon, expect */
|
||||
|
||||
/**
|
||||
* @copyright 2017 Christoph Wurst <christoph@winzerhof-wurst.at>
|
||||
*
|
||||
* @author 2017 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/>.
|
||||
*
|
||||
*/
|
||||
|
||||
define([
|
||||
'service/preferenceservice'
|
||||
], function(PreferenceService) {
|
||||
'use strict';
|
||||
|
||||
describe('PreferenceService', function() {
|
||||
var server;
|
||||
|
||||
beforeEach(function() {
|
||||
server = sinon.fakeServer.create();
|
||||
});
|
||||
|
||||
afterEach(function() {
|
||||
server.restore();
|
||||
});
|
||||
|
||||
it('retrieves a preference from the back-end', function(done) {
|
||||
var retrieving = PreferenceService.getPreference('test');
|
||||
|
||||
expect(server.requests.length).toBe(1);
|
||||
server.requests[0].respond(
|
||||
200,
|
||||
{
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
JSON.stringify({
|
||||
value: '123'
|
||||
})
|
||||
);
|
||||
|
||||
retrieving.then(function(value) {
|
||||
expect(value).toBe('123');
|
||||
|
||||
done();
|
||||
}).catch(done.fail);
|
||||
});
|
||||
|
||||
it('retrieves an error from the back-end', function(done) {
|
||||
var retrieving = PreferenceService.getPreference('test');
|
||||
|
||||
expect(server.requests.length).toBe(1);
|
||||
server.requests[0].respond(500, null, null);
|
||||
|
||||
retrieving.then(done.fail).catch(done);
|
||||
});
|
||||
|
||||
it('stores a preference on the back-end', function(done) {
|
||||
var storing = PreferenceService.savePreference('test', '123');
|
||||
|
||||
expect(server.requests.length).toBe(1);
|
||||
server.requests[0].respond(
|
||||
200,
|
||||
{
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
JSON.stringify({
|
||||
value: '123'
|
||||
})
|
||||
);
|
||||
|
||||
storing.then(function(value) {
|
||||
expect(value).toBe('123');
|
||||
|
||||
done();
|
||||
}).catch(done.fail);
|
||||
});
|
||||
});
|
||||
});
|
|
@ -1,82 +0,0 @@
|
|||
/* global spyOn, expect */
|
||||
|
||||
/**
|
||||
* @author Christoph Wurst <christoph@winzerhof-wurst.at>
|
||||
*
|
||||
* Mail
|
||||
*
|
||||
* This code is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License, version 3,
|
||||
* as published by the Free Software Foundation.
|
||||
*
|
||||
* 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, version 3,
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||
*
|
||||
*/
|
||||
|
||||
define([
|
||||
'service/accountservice',
|
||||
'OC'
|
||||
], function(AccountService, OC) {
|
||||
|
||||
describe('AccountService', function() {
|
||||
|
||||
beforeEach(function() {
|
||||
jasmine.Ajax.install();
|
||||
});
|
||||
|
||||
afterEach(function() {
|
||||
jasmine.Ajax.uninstall();
|
||||
});
|
||||
|
||||
it('creates a new account on the server', function(done) {
|
||||
spyOn(OC, 'generateUrl').and.returnValue('index.php/apps/mail/api/accounts');
|
||||
|
||||
var promise = AccountService.createAccount({
|
||||
email: 'email@example.com',
|
||||
password: '12345'
|
||||
});
|
||||
|
||||
expect(OC.generateUrl).toHaveBeenCalledWith('apps/mail/api/accounts');
|
||||
|
||||
expect(jasmine.Ajax.requests.count())
|
||||
.toBe(1);
|
||||
expect(jasmine.Ajax.requests.mostRecent().url)
|
||||
.toBe('index.php/apps/mail/api/accounts');
|
||||
jasmine.Ajax.requests.mostRecent().respondWith({
|
||||
'status': 200,
|
||||
'contentType': 'application/json',
|
||||
'responseText': '{}'
|
||||
});
|
||||
|
||||
promise.then(done).catch(done.fail);
|
||||
});
|
||||
|
||||
it('handle account creation errors correctly', function(done) {
|
||||
spyOn(OC, 'generateUrl').and.returnValue('index.php/apps/mail/api/accounts');
|
||||
|
||||
var creating = AccountService.createAccount({
|
||||
email: 'email@example.com',
|
||||
password: '12345'
|
||||
});
|
||||
|
||||
expect(OC.generateUrl).toHaveBeenCalledWith('apps/mail/api/accounts');
|
||||
|
||||
expect(jasmine.Ajax.requests.count()).toBe(1);
|
||||
expect(jasmine.Ajax.requests.mostRecent().url)
|
||||
.toBe('index.php/apps/mail/api/accounts');
|
||||
jasmine.Ajax.requests.mostRecent().respondWith({
|
||||
'status': 500,
|
||||
'contentType': 'application/json',
|
||||
'responseText': '{}'
|
||||
});
|
||||
|
||||
creating.catch(done);
|
||||
});
|
||||
});
|
||||
});
|
|
@ -1,124 +0,0 @@
|
|||
/**
|
||||
* @author Luc Calaresu <dev@calaresu.com>
|
||||
*
|
||||
* Mail
|
||||
*
|
||||
* This code is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License, version 3,
|
||||
* as published by the Free Software Foundation.
|
||||
*
|
||||
* 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, version 3,
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||
*
|
||||
*/
|
||||
|
||||
define([
|
||||
'service/attachmentservice',
|
||||
'OC',
|
||||
'radio',
|
||||
], function(AttachmentService, OC, Radio) {
|
||||
|
||||
describe('AttachmentService', function() {
|
||||
|
||||
var fakeModel;
|
||||
var fakeFile;
|
||||
|
||||
beforeEach(function() {
|
||||
jasmine.Ajax.install();
|
||||
fakeModel = jasmine.createSpyObj('fakeModel', ['onProgress', 'set', 'get', 'unset']);
|
||||
fakeFile = {filename: 'file.zip', size: '12345' };
|
||||
});
|
||||
|
||||
afterEach(function() {
|
||||
jasmine.Ajax.uninstall();
|
||||
});
|
||||
|
||||
/* when everything went as expected on attachment upload */
|
||||
it('uploads a new attachment on the server', function(done) {
|
||||
spyOn(OC, 'generateUrl').and.returnValue('index.php/apps/mail/api/attachments');
|
||||
|
||||
jasmine.Ajax.stubRequest('index.php/apps/mail/api/attachments').andReturn({
|
||||
status: 200,
|
||||
contentType: 'application/json',
|
||||
responseText: '{"id": "33", "fileName": "file.zip"}'
|
||||
});
|
||||
|
||||
var promise = AttachmentService.uploadLocalAttachment(fakeFile, fakeModel);
|
||||
expect(OC.generateUrl).toHaveBeenCalledWith('/apps/mail/api/attachments');
|
||||
|
||||
promise
|
||||
.then(function() {
|
||||
expect(jasmine.Ajax.requests.count()).toBe(1);
|
||||
expect(jasmine.Ajax.requests.mostRecent().url).toBe('index.php/apps/mail/api/attachments');
|
||||
done();
|
||||
})
|
||||
.catch(function(error) {
|
||||
console.error(error);
|
||||
done.fail('Attachment upload is not supposed to fail', error);
|
||||
});
|
||||
});
|
||||
|
||||
/* when an error occurred on server side on attachment upload */
|
||||
it('handles errors during attachment uploads', function(done) {
|
||||
spyOn(OC, 'generateUrl').and.returnValue('index.php/apps/mail/api/attachments');
|
||||
|
||||
jasmine.Ajax.stubRequest('index.php/apps/mail/api/attachments').andReturn({
|
||||
status: 500,
|
||||
contentType: 'text/plain'
|
||||
});
|
||||
|
||||
var promise = AttachmentService.uploadLocalAttachment(fakeFile, fakeModel);
|
||||
expect(OC.generateUrl).toHaveBeenCalledWith('/apps/mail/api/attachments');
|
||||
|
||||
promise
|
||||
.then(function() {
|
||||
done.fail('Attachment upload is supposed to fail');
|
||||
})
|
||||
.catch(function() {
|
||||
expect(jasmine.Ajax.requests.count()).toBe(1);
|
||||
expect(jasmine.Ajax.requests.mostRecent().url).toBe('index.php/apps/mail/api/attachments');
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
/* when upload finished with success */
|
||||
it('handles upload finished with success', function() {
|
||||
// progress=1, for upload succeeded
|
||||
fakeModel.progress = 1;
|
||||
AttachmentService.uploadLocalAttachmentFinished(fakeModel, 33);
|
||||
|
||||
// the model ID and upload status are updated on succes
|
||||
expect(fakeModel.set).toHaveBeenCalledWith('id', 33);
|
||||
expect(fakeModel.set).toHaveBeenCalledWith('uploadStatus', 3);
|
||||
|
||||
// make sure the xhr request has been removed from the model
|
||||
expect(fakeModel.unset).toHaveBeenCalledWith('uploadRequest');
|
||||
|
||||
});
|
||||
|
||||
/* when a problem occured on upload */
|
||||
it('handles upload finished with error', function() {
|
||||
// progress=1, but no id returned from the server
|
||||
fakeModel.progress = 1;
|
||||
AttachmentService.uploadLocalAttachmentFinished(fakeModel);
|
||||
// the upload status are updated on succes
|
||||
expect(fakeModel.set).toHaveBeenCalledWith('uploadStatus', 2);
|
||||
// make sure the xhr request has been removed from the model
|
||||
expect(fakeModel.unset).toHaveBeenCalledWith('uploadRequest');
|
||||
|
||||
// == id returned from server but file not uploaded ==
|
||||
// not sure that is possible though
|
||||
fakeModel.progress = 0.5;
|
||||
AttachmentService.uploadLocalAttachmentFinished(fakeModel, 33);
|
||||
// the upload status are updated on succes
|
||||
expect(fakeModel.set).toHaveBeenCalledWith('uploadStatus', 2);
|
||||
// make sure the xhr request has been removed from the model
|
||||
expect(fakeModel.unset).toHaveBeenCalledWith('uploadRequest');
|
||||
});
|
||||
});
|
||||
});
|
|
@ -1,75 +0,0 @@
|
|||
var allTestFiles = [];
|
||||
var TEST_REGEXP = /(spec|test)\.js$/i;
|
||||
|
||||
// Get a list of all the test files to include
|
||||
Object.keys(window.__karma__.files).forEach(function(file) {
|
||||
if (TEST_REGEXP.test(file)) {
|
||||
// Normalize paths to RequireJS module names.
|
||||
// If you require sub-dependencies of test files to be loaded as-is (requiring file extension)
|
||||
// then do not normalize the paths
|
||||
var normalizedTestModule = file.replace(/^\/base\/js\/|\.js$/g, '');
|
||||
if (normalizedTestModule.substring(0, 'tests'.length) === 'tests') {
|
||||
allTestFiles.push(normalizedTestModule);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
window.t = function(app, text) {
|
||||
if (app !== 'mail') {
|
||||
throw 'wrong app used to for translation';
|
||||
}
|
||||
return text;
|
||||
};
|
||||
|
||||
OC = {
|
||||
Notification: {
|
||||
showTemporary: function() {
|
||||
|
||||
}
|
||||
},
|
||||
filePath: function(app, type, path) {
|
||||
return type + '/' + path;
|
||||
},
|
||||
generateUrl: function(url, params) {
|
||||
var props = [];
|
||||
for (var prop in params) {
|
||||
props.push(prop);
|
||||
}
|
||||
return '/base/' + props.reduce(function(url, paramName) {
|
||||
var param = params[paramName];
|
||||
return url.replace('{' + paramName + '}', param);
|
||||
}, url);
|
||||
},
|
||||
linkToRemote: function() {
|
||||
|
||||
}
|
||||
};
|
||||
|
||||
OCA = {};
|
||||
OCA.Search = function() {
|
||||
|
||||
};
|
||||
|
||||
// jQuery module stubs
|
||||
$.fn.tooltip = function() {
|
||||
|
||||
};
|
||||
|
||||
$.fn.droppable = function() {
|
||||
|
||||
};
|
||||
|
||||
$.fn.imageplaceholder = function() {
|
||||
|
||||
};
|
||||
|
||||
formatDate = function(arg) {
|
||||
return arg;
|
||||
};
|
||||
|
||||
relative_modified_date = function(arg) {
|
||||
return arg;
|
||||
};
|
||||
|
||||
// es6-object-assign
|
||||
window.ObjectAssign.polyfill();
|
|
@ -1,27 +0,0 @@
|
|||
/**
|
||||
* @copyright 2017 Christoph Wurst <christoph@winzerhof-wurst.at>
|
||||
*
|
||||
* @author 2017 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/>.
|
||||
*
|
||||
*/
|
||||
|
||||
define([
|
||||
'app'
|
||||
], function() {
|
||||
console.info('Mail initialized for testing');
|
||||
});
|
|
@ -0,0 +1,8 @@
|
|||
module.exports = {
|
||||
env: {
|
||||
mocha: true
|
||||
},
|
||||
rules: {
|
||||
'import/no-extraneous-dependencies': 'off'
|
||||
}
|
||||
}
|
|
@ -0,0 +1,13 @@
|
|||
import { expect } from 'chai'
|
||||
import { shallowMount } from '@vue/test-utils'
|
||||
import HelloWorld from '@/components/HelloWorld.vue'
|
||||
|
||||
describe('HelloWorld.vue', () => {
|
||||
it('renders props.msg when passed', () => {
|
||||
const msg = 'new message'
|
||||
const wrapper = shallowMount(HelloWorld, {
|
||||
propsData: { msg }
|
||||
})
|
||||
expect(wrapper.text()).to.include(msg)
|
||||
})
|
||||
})
|
|
@ -1,111 +0,0 @@
|
|||
/* global expect */
|
||||
|
||||
/**
|
||||
* @copyright 2017 Christoph Wurst <christoph@winzerhof-wurst.at>
|
||||
*
|
||||
* @author 2017 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/>.
|
||||
*
|
||||
*/
|
||||
|
||||
define([
|
||||
'util/htmlhelper'
|
||||
], function(HtmlHelper) {
|
||||
'use strict';
|
||||
|
||||
describe('HtmlHelper', function() {
|
||||
it('preserves breaks', function() {
|
||||
var html = 'line1<br>line2';
|
||||
var expected = 'line1\nline2';
|
||||
|
||||
var actual = HtmlHelper.htmlToText(html);
|
||||
|
||||
expect(actual).toBe(expected);
|
||||
});
|
||||
|
||||
it('concats divs', function() {
|
||||
var html = '<div>one</div><div>two</div>';
|
||||
var expected = 'onetwo';
|
||||
|
||||
var actual = HtmlHelper.htmlToText(html);
|
||||
|
||||
expect(actual).toBe(expected);
|
||||
});
|
||||
|
||||
it('does not produce large number of line breaks for nested elements', function() {
|
||||
var html =
|
||||
'<div>' +
|
||||
' <div>' +
|
||||
' line1' +
|
||||
' </div>' +
|
||||
'</div>' +
|
||||
'<div>line2</div>';
|
||||
var expected = ' line1 line2';
|
||||
|
||||
var actual = HtmlHelper.htmlToText(html);
|
||||
|
||||
expect(actual).toBe(expected);
|
||||
});
|
||||
|
||||
it('converts blocks to text', function() {
|
||||
var html = '<div>hello</div>';
|
||||
var expected = 'hello';
|
||||
|
||||
var actual = HtmlHelper.htmlToText(html);
|
||||
|
||||
expect(actual).toBe(expected);
|
||||
});
|
||||
|
||||
it('converts paragraphs to text', function() {
|
||||
var html = '<p>hello</p>';
|
||||
var expected = 'hello';
|
||||
|
||||
var actual = HtmlHelper.htmlToText(html);
|
||||
|
||||
expect(actual).toBe(expected);
|
||||
});
|
||||
|
||||
it('converts lists to text', function() {
|
||||
var html = '<ul><li>one</li><li>two</li><li>three</li></ul>';
|
||||
var expected = ' * one\n * two\n * three';
|
||||
|
||||
var actual = HtmlHelper.htmlToText(html);
|
||||
|
||||
expect(actual).toBe(expected);
|
||||
});
|
||||
|
||||
it('converts deeply nested elements to text', function() {
|
||||
var html = '<html>'
|
||||
+ '<body><p>Hello!</p><p>this <i>is</i> <b>some</b> random <strong>text</strong></p></body>'
|
||||
+ '</html>';
|
||||
var expected = 'Hello!\n\nthis is some random text';
|
||||
|
||||
var actual = HtmlHelper.htmlToText(html);
|
||||
|
||||
expect(actual).toBe(expected);
|
||||
});
|
||||
|
||||
it('does not leak internal redirection URLs', function() {
|
||||
var html = '<a href="https://localhost/apps/mail/redirect?src=domain.tld">domain.tld</a>';
|
||||
var expected = 'domain.tld';
|
||||
|
||||
var actual = HtmlHelper.htmlToText(html);
|
||||
|
||||
expect(actual).toBe(expected);
|
||||
});
|
||||
});
|
||||
});
|
|
@ -1,95 +0,0 @@
|
|||
/**
|
||||
* @author Christoph Wurst <christoph@winzerhof-wurst.at>
|
||||
*
|
||||
* Mail
|
||||
*
|
||||
* This code is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License, version 3,
|
||||
* as published by the Free Software Foundation.
|
||||
*
|
||||
* 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, version 3,
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||
*
|
||||
*/
|
||||
|
||||
define([
|
||||
'util/notificationhandler',
|
||||
'radio'
|
||||
], function(NotificationHandler, Radio) {
|
||||
|
||||
describe('NotificationHandler', function() {
|
||||
var notification;
|
||||
var originalNotification;
|
||||
|
||||
function getNotificationMock() {
|
||||
var mock = function(title, options) {
|
||||
this.title = title;
|
||||
this.options = options;
|
||||
// Hack to get ref to notification object
|
||||
notification = this;
|
||||
};
|
||||
mock.requestPermission = function() {
|
||||
|
||||
};
|
||||
mock.prototype = {
|
||||
getTitle: function() {
|
||||
return this.title;
|
||||
},
|
||||
getOptions: function() {
|
||||
return this.options;
|
||||
},
|
||||
close: function() {
|
||||
|
||||
}
|
||||
};
|
||||
return mock;
|
||||
}
|
||||
|
||||
beforeEach(function() {
|
||||
originalNotification = window.Notification;
|
||||
jasmine.clock().install();
|
||||
});
|
||||
|
||||
afterEach(function() {
|
||||
window.Notification = originalNotification;
|
||||
jasmine.clock().uninstall();
|
||||
notification = undefined;
|
||||
});
|
||||
|
||||
it('requests notification permissions', function() {
|
||||
window.Notification = getNotificationMock();
|
||||
spyOn(window.Notification, 'requestPermission');
|
||||
|
||||
Radio.ui.trigger('notification:request');
|
||||
|
||||
expect(window.Notification.requestPermission).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should do nothing if notifications are not supported by the browser', function() {
|
||||
window.Notification = undefined;
|
||||
|
||||
NotificationHandler.showNotification('a', 'b');
|
||||
|
||||
expect(notification).toBe(undefined);
|
||||
});
|
||||
|
||||
it('should construct and show a new notification', function() {
|
||||
window.Notification = getNotificationMock();
|
||||
spyOn(Radio.navigation, 'trigger');
|
||||
|
||||
NotificationHandler.showNotification('a', 'b');
|
||||
|
||||
// A new notification should have been created
|
||||
expect(notification).not.toBe(undefined);
|
||||
|
||||
// Check click handler
|
||||
expect(Radio.navigation.trigger).not.toHaveBeenCalled();
|
||||
expect(notification.onclick).not.toBe(undefined);
|
||||
});
|
||||
});
|
||||
});
|
|
@ -1,43 +0,0 @@
|
|||
/* global expect */
|
||||
|
||||
/**
|
||||
* Mail
|
||||
*
|
||||
* This file is licensed under the Affero General Public License version 3 or
|
||||
* later. See the COPYING file.
|
||||
*
|
||||
* @author Christoph Wurst <christoph@winzerhof-wurst.at>
|
||||
* @copyright Christoph Wurst 2017
|
||||
*/
|
||||
|
||||
define([
|
||||
'views/accountview',
|
||||
'models/account',
|
||||
], function(AccountView, Account) {
|
||||
|
||||
describe('Account view', function() {
|
||||
|
||||
var account;
|
||||
var accountView;
|
||||
|
||||
beforeEach(function() {
|
||||
account = new Account();
|
||||
accountView = new AccountView({
|
||||
model: account
|
||||
});
|
||||
});
|
||||
|
||||
it('has a delete button if the account is deletable', function() {
|
||||
accountView.render();
|
||||
|
||||
expect(accountView.$el.html()).toContain('Delete');
|
||||
});
|
||||
|
||||
it('has no delete button if the account is not deletable', function() {
|
||||
account.set('accountId', -2);
|
||||
accountView.render();
|
||||
|
||||
expect(accountView.$el.html()).not.toContain('Delete');
|
||||
});
|
||||
});
|
||||
});
|
|
@ -1,128 +0,0 @@
|
|||
/* global expect */
|
||||
|
||||
/**
|
||||
* @author Luc Calaresu <dev@calaresu.com>
|
||||
*
|
||||
* Mail
|
||||
*
|
||||
* This code is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License, version 3,
|
||||
* as published by the Free Software Foundation.
|
||||
*
|
||||
* 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, version 3,
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||
*
|
||||
*/
|
||||
|
||||
|
||||
define([
|
||||
'views/attachmentview',
|
||||
'models/localattachment',
|
||||
'models/attachment',
|
||||
'radio',
|
||||
'jquery-ui'
|
||||
], function(AttachmentView, LocalAttachment, Attachment, Radio) {
|
||||
|
||||
describe('AttachmentView', function() {
|
||||
|
||||
var view;
|
||||
var model;
|
||||
|
||||
describe('on local attachment', function() {
|
||||
|
||||
beforeEach(function() {
|
||||
// on local attachment, we use the LocalAttachment model
|
||||
model = new LocalAttachment({
|
||||
fileName: 'test.zip',
|
||||
uploadRequest: true
|
||||
});
|
||||
view = new AttachmentView({
|
||||
model: model
|
||||
});
|
||||
view.render();
|
||||
view.bindUIElements();
|
||||
});
|
||||
|
||||
it('should exist', function() {
|
||||
expect(view).toBeDefined();
|
||||
});
|
||||
|
||||
it('should have a progress bar set at 0', function() {
|
||||
// jquery-ui progressbar is initialised
|
||||
expect(view.ui.attachmentName.attr('role')).toBe('progressbar');
|
||||
// value of progressbar is set to 0
|
||||
expect(view.ui.attachmentName.attr('aria-valuenow')).toBe('0');
|
||||
});
|
||||
|
||||
it('should allow attachment removal', function() {
|
||||
/* We just test that the 'upload:abort' event has been sent */
|
||||
/* The deletion action itself is tested on the attachmentService tests */
|
||||
spyOn(Radio.attachment, 'request');
|
||||
view.removeAttachment();
|
||||
expect(Radio.attachment.request).toHaveBeenCalledWith('upload:abort', model);
|
||||
});
|
||||
|
||||
it('should update the attachment colour on upload status changes', function() {
|
||||
// Update the model progress value and make sure the css class has been added
|
||||
model.set('uploadStatus', 1); // uploading
|
||||
expect(view.ui.attachmentName.attr('class')).toContain('upload-ongoing');
|
||||
|
||||
model.set('uploadStatus', 2); // error
|
||||
expect(view.ui.attachmentName.attr('class')).not.toContain('upload-ongoing');
|
||||
expect(view.ui.attachmentName.attr('class')).toContain('upload-warning');
|
||||
|
||||
model.set('uploadStatus', 3); // success
|
||||
expect(view.ui.attachmentName.attr('class')).not.toContain('upload-ongoing');
|
||||
expect(view.ui.attachmentName.attr('class')).not.toContain('upload-warning');
|
||||
});
|
||||
|
||||
it('should update the progress bar on model progress changes', function() {
|
||||
spyOn(view.ui.attachmentName, 'progressbar');
|
||||
// Update the model progress value and make sure the view has been updated
|
||||
model.set('progress', 0.5);
|
||||
expect(view.ui.attachmentName.progressbar).toHaveBeenCalledWith(
|
||||
'option', 'value', 0.5
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('on attachment from Files', function() {
|
||||
|
||||
beforeEach(function() {
|
||||
// on attachment from Files, we use the Attachment model
|
||||
model = new Attachment({
|
||||
fileName: 'test.zip'
|
||||
});
|
||||
view = new AttachmentView({
|
||||
model: model
|
||||
});
|
||||
view.render();
|
||||
});
|
||||
|
||||
it('should exist', function() {
|
||||
expect(view).toBeDefined();
|
||||
});
|
||||
|
||||
it('should not have a progress bar', function() {
|
||||
// jquery-ui progressbar is NOT initialised
|
||||
expect(view.ui.attachmentName.attr('role')).toBe(undefined);
|
||||
// and css style upload-ongoing has NOT been added
|
||||
expect(view.ui.attachmentName.attr('class')).not.toContain('upload-ongoing');
|
||||
});
|
||||
|
||||
it('should allow attachment removal', function() {
|
||||
/* We just test that the 'upload:abort' event has been sent */
|
||||
/* The deletion action itself is tested on the attachmentService tests */
|
||||
spyOn(Radio.attachment, 'request');
|
||||
view.removeAttachment();
|
||||
expect(Radio.attachment.request).toHaveBeenCalledWith('upload:abort', model);
|
||||
});
|
||||
});
|
||||
|
||||
});
|
||||
});
|
|
@ -1,46 +0,0 @@
|
|||
/* global expect */
|
||||
|
||||
/**
|
||||
* Mail
|
||||
*
|
||||
* This file is licensed under the Affero General Public License version 3 or
|
||||
* later. See the COPYING file.
|
||||
*
|
||||
* @author Christoph Wurst <christoph@winzerhof-wurst.at>
|
||||
* @copyright Christoph Wurst 2017
|
||||
*/
|
||||
|
||||
define([
|
||||
'views/attachmentsview',
|
||||
'jquery'
|
||||
], function(AttachmentView, $) {
|
||||
|
||||
describe('AttachmentsView', function() {
|
||||
|
||||
var view;
|
||||
|
||||
beforeEach(function() {
|
||||
$('body').append('<div id="#mail-attachments-template"></div>');
|
||||
view = new AttachmentView({});
|
||||
});
|
||||
|
||||
afterEach(function() {
|
||||
view.remove();
|
||||
$('#mail-attachments-template').remove();
|
||||
});
|
||||
|
||||
it('produces the correct HTML', function() {
|
||||
view.render();
|
||||
|
||||
expect(view.el.innerHTML)
|
||||
.toContain('<ul></ul>\n\
|
||||
<button type="button" id="add-local-attachment" style="display: inline-block;">\n\
|
||||
<span class="icon-upload"></span> Upload attachment\n\
|
||||
</button>\n\
|
||||
<button type="button" id="add-cloud-attachment" style="display: inline-block;">\n\
|
||||
<span class="icon-folder"></span> Add attachment from Files\n\
|
||||
</button>\n\
|
||||
<input type="file" multiple="" id="local-attachments" style="display: none;">\n');
|
||||
});
|
||||
});
|
||||
});
|
|
@ -1,40 +0,0 @@
|
|||
/**
|
||||
* @author Steffen Lindner <mail@steffen-lindner.de>
|
||||
*
|
||||
* Mail
|
||||
*
|
||||
* This code is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License, version 3,
|
||||
* as published by the Free Software Foundation.
|
||||
*
|
||||
* 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, version 3,
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||
*
|
||||
*/
|
||||
|
||||
|
||||
define(['views/calendarview', 'views/helper'], function(CalendarView) {
|
||||
|
||||
describe('CalendarView', function () {
|
||||
|
||||
var calendarview;
|
||||
|
||||
beforeEach(function () {
|
||||
calendarview = new CalendarView({});
|
||||
});
|
||||
|
||||
it('produces the correct HTML', function () {
|
||||
calendarview.render();
|
||||
|
||||
html = calendarview.el.innerHTML.trim();
|
||||
expected_html = '<a class="select-calendar" data-calendar-url=""></a>';
|
||||
expect(html).toContain(expected_html);
|
||||
|
||||
});
|
||||
});
|
||||
});
|
|
@ -1,275 +0,0 @@
|
|||
/* global expect, spyOn */
|
||||
|
||||
/**
|
||||
* @author Christoph Wurst <christoph@winzerhof-wurst.at>
|
||||
*
|
||||
* Mail
|
||||
*
|
||||
* This code is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License, version 3,
|
||||
* as published by the Free Software Foundation.
|
||||
*
|
||||
* 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, version 3,
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||
*
|
||||
*/
|
||||
|
||||
define([
|
||||
'views/composerview',
|
||||
'models/accountcollection',
|
||||
'models/attachment',
|
||||
'models/localattachment',
|
||||
'state'
|
||||
], function(ComposerView, AccountCollection, Attachment, LocalAttachment, State) {
|
||||
describe('ComposerView', function() {
|
||||
var accounts;
|
||||
|
||||
beforeEach(function() {
|
||||
accounts = new AccountCollection([
|
||||
{
|
||||
accountId: 13,
|
||||
name: 'Jane Nextcloud',
|
||||
emailAddress: 'jane@nextcloud.com',
|
||||
folders: [
|
||||
{
|
||||
id: 'inbox'
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
accountId: 14,
|
||||
name: 'John Nextcloud',
|
||||
emailAddress: 'john@nextcloud.com',
|
||||
folders: [
|
||||
{
|
||||
id: 'inbox'
|
||||
}
|
||||
]
|
||||
}
|
||||
]);
|
||||
|
||||
State.currentAccount = accounts.first();
|
||||
});
|
||||
|
||||
it('creates a view to composer a new message', function() {
|
||||
var view = new ComposerView({
|
||||
accounts: accounts
|
||||
});
|
||||
spyOn(view, 'saveDraft');
|
||||
|
||||
expect(view.type).toBe('new');
|
||||
expect(view.isReply()).toBe(false);
|
||||
expect(view.account).toBe(accounts.at(0));
|
||||
expect(view.repliedMessage).toBeNull();
|
||||
});
|
||||
|
||||
it('creates a reply composer', function() {
|
||||
var account = accounts.at(1);
|
||||
var folder = account.folders.first();
|
||||
var view = new ComposerView({
|
||||
accounts: accounts,
|
||||
account: account,
|
||||
folder: folder,
|
||||
type: 'reply'
|
||||
});
|
||||
spyOn(view, 'saveDraft');
|
||||
|
||||
expect(view.type).toBe('reply');
|
||||
expect(view.isReply()).toBe(true);
|
||||
expect(view.account).toBe(account);
|
||||
expect(view.folder).toBe(folder);
|
||||
});
|
||||
|
||||
it('doesn\'t have draft UID at creation', function() {
|
||||
var view = new ComposerView({
|
||||
accounts: accounts
|
||||
});
|
||||
spyOn(view, 'saveDraft');
|
||||
|
||||
expect(view.draftUID).toBeUndefined();
|
||||
});
|
||||
|
||||
it('creates the correct list of selectable accounts withoug aliases', function() {
|
||||
var view = new ComposerView({
|
||||
accounts: accounts
|
||||
});
|
||||
spyOn(view, 'saveDraft');
|
||||
|
||||
var expected = [
|
||||
{
|
||||
id: 1,
|
||||
accountId: 13,
|
||||
aliasId: null,
|
||||
emailAddress: 'jane@nextcloud.com',
|
||||
name: 'Jane Nextcloud'
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
accountId: 14,
|
||||
aliasId: null,
|
||||
emailAddress: 'john@nextcloud.com',
|
||||
name: 'John Nextcloud'
|
||||
}
|
||||
];
|
||||
expect(view.buildAliases()).toEqual(expected);
|
||||
});
|
||||
|
||||
it('renders correctly', function() {
|
||||
var view = new ComposerView({
|
||||
accounts: accounts
|
||||
});
|
||||
spyOn(view, 'saveDraft');
|
||||
|
||||
view.render();
|
||||
|
||||
var $el = view.$el;
|
||||
|
||||
// Two accounts should be selectable
|
||||
expect($el.find('select.mail-account').
|
||||
children().length).toBe(2);
|
||||
});
|
||||
|
||||
describe('with attachments', function () {
|
||||
var view;
|
||||
var localAttachment;
|
||||
|
||||
beforeEach(function() {
|
||||
var accounts = new AccountCollection([
|
||||
{
|
||||
accountId: 13,
|
||||
name: 'Jane Nextcloud',
|
||||
emailAddress: 'jane@nextcloud.com',
|
||||
folders: [
|
||||
{
|
||||
id: 'inbox'
|
||||
}
|
||||
]
|
||||
}
|
||||
]);
|
||||
State.currentAccount = accounts.first();
|
||||
|
||||
view = new ComposerView({
|
||||
accounts: accounts
|
||||
});
|
||||
spyOn(view, 'saveDraft');
|
||||
|
||||
view.render();
|
||||
view.bindUIElements();
|
||||
|
||||
localAttachment = new LocalAttachment({
|
||||
fileName: 'test.zip'
|
||||
});
|
||||
});
|
||||
|
||||
it('calls onInputChanged and checkAllAttachmentsValid when the attachment list changed', function() {
|
||||
spyOn(view, 'onInputChanged');
|
||||
spyOn(view, 'checkAllAttachmentsValid');
|
||||
view.bindAttachments();
|
||||
view.attachments.add(localAttachment);
|
||||
expect(view.onInputChanged).toHaveBeenCalled();
|
||||
expect(view.checkAllAttachmentsValid).toHaveBeenCalled();
|
||||
});
|
||||
it('disables the send button when a local attachment is not valid', function() {
|
||||
// we put something into the 'To:' input, so the submit button can be activated
|
||||
view.$('.to').val('test@mailserver');
|
||||
view.onInputChanged();
|
||||
|
||||
// and we chack how the button reacts when checkAllAttachmentsValid return false
|
||||
spyOn(view, 'checkAllAttachmentsValid').and.returnValue(false);
|
||||
expect(view.$('.submit-message').attr('disabled')).toBe(undefined);
|
||||
view.attachments.add(localAttachment);
|
||||
expect(view.$('.submit-message').attr('disabled')).toBe('disabled');
|
||||
});
|
||||
|
||||
it('enables the send button when all local attachment are valid', function() {
|
||||
// we put something into the 'To:' input, so the submit button can be activated
|
||||
view.$('.to').val('test@mailserver');
|
||||
view.onInputChanged();
|
||||
|
||||
// and we chack how the button reacts when checkAllAttachmentsValid return false
|
||||
spyOn(view, 'checkAllAttachmentsValid').and.returnValue(true);
|
||||
expect(view.$('.submit-message').attr('disabled')).toBe(undefined);
|
||||
view.attachments.add(localAttachment);
|
||||
expect(view.$('.submit-message').attr('disabled')).toBe(undefined);
|
||||
});
|
||||
|
||||
describe('from local source (upload)', function () {
|
||||
it('detects when all local attachments are valid', function() {
|
||||
var localAttachmentA = new LocalAttachment({
|
||||
fileName: 'test.zip',
|
||||
uploadStatus: 3 // success
|
||||
});
|
||||
var localAttachmentB = new LocalAttachment({
|
||||
fileName: 'test2.zip',
|
||||
uploadStatus: 3 // success
|
||||
});
|
||||
view.attachments.add(localAttachmentA);
|
||||
view.attachments.add(localAttachmentB);
|
||||
expect(view.checkAllAttachmentsValid()).toBe(true);
|
||||
});
|
||||
|
||||
it('detects when at least one local attachment is pending', function() {
|
||||
var localAttachmentA = new LocalAttachment({
|
||||
fileName: 'test.zip',
|
||||
uploadStatus: 0 // pending
|
||||
});
|
||||
var localAttachmentB = new LocalAttachment({
|
||||
fileName: 'test2.zip',
|
||||
uploadStatus: 3 // success
|
||||
});
|
||||
view.attachments.add(localAttachmentA);
|
||||
view.attachments.add(localAttachmentB);
|
||||
expect(view.checkAllAttachmentsValid()).toBe(false);
|
||||
});
|
||||
|
||||
it('detects when at least one local attachment is ongoing', function() {
|
||||
var localAttachmentA = new LocalAttachment({
|
||||
fileName: 'test.zip',
|
||||
uploadStatus: 1 // ongoing
|
||||
});
|
||||
var localAttachmentB = new LocalAttachment({
|
||||
fileName: 'test2.zip',
|
||||
uploadStatus: 3 // success
|
||||
});
|
||||
view.attachments.add(localAttachmentA);
|
||||
view.attachments.add(localAttachmentB);
|
||||
expect(view.checkAllAttachmentsValid()).toBe(false);
|
||||
});
|
||||
|
||||
it('detects when at least one local attachment is broken', function() {
|
||||
var localAttachmentA = new LocalAttachment({
|
||||
fileName: 'test.zip',
|
||||
uploadStatus: 2 // error
|
||||
});
|
||||
var localAttachmentB = new LocalAttachment({
|
||||
fileName: 'test2.zip',
|
||||
uploadStatus: 3 // success
|
||||
});
|
||||
view.attachments.add(localAttachmentA);
|
||||
view.attachments.add(localAttachmentB);
|
||||
expect(view.checkAllAttachmentsValid()).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe('from Files', function () {
|
||||
it('always consider attachments from Files as valid', function() {
|
||||
var localAttachmentA = new Attachment({
|
||||
fileName: 'test.zip'
|
||||
});
|
||||
var localAttachmentB = new Attachment({
|
||||
fileName: 'test2.zip'
|
||||
});
|
||||
view.attachments.add(localAttachmentA);
|
||||
view.attachments.add(localAttachmentB);
|
||||
expect(view.checkAllAttachmentsValid()).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
});
|
||||
});
|
||||
});
|
|
@ -1,43 +0,0 @@
|
|||
/**
|
||||
* @author Steffen Lindner <mail@steffen-lidnner.de>
|
||||
*
|
||||
* Mail
|
||||
*
|
||||
* This code is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License, version 3,
|
||||
* as published by the Free Software Foundation.
|
||||
*
|
||||
* 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, version 3,
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||
*
|
||||
*/
|
||||
|
||||
|
||||
define(['views/emptyfolderview', 'views/helper'], function(EmptyfolderView) {
|
||||
|
||||
describe('EmptyfolderView', function () {
|
||||
|
||||
var emptyfolderview;
|
||||
|
||||
beforeEach(function () {
|
||||
emptyfolderview = new EmptyfolderView({});
|
||||
});
|
||||
|
||||
describe('Rendering', function () {
|
||||
|
||||
it('produces the correct HTML', function () {
|
||||
emptyfolderview.render();
|
||||
|
||||
html = emptyfolderview.el.innerHTML.trim();
|
||||
expected_html = '<div class="icon-mail"></div>\n<h2>No messages in this folder</h2>';
|
||||
expect(html).toContain(expected_html);
|
||||
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
Некоторые файлы не были показаны из-за слишком большого количества измененных файлов Показать больше
Загрузка…
Ссылка в новой задаче