mirror of
https://github.com/moparisthebest/mail
synced 2024-11-25 10:22:18 -05:00
Merge pull request #227 from whiteout-io/dev/cleanup
Move directives to seperate files, cleanup app.js
This commit is contained in:
commit
738d2671f7
@ -31,7 +31,7 @@
|
||||
<script src="js/app.min.js"></script>
|
||||
</head>
|
||||
|
||||
<body key-shortcuts>
|
||||
<body wo-key-shortcuts>
|
||||
|
||||
<!-- inline icons have to come first, hide immediately with inline styles -->
|
||||
<div style="width: 0; height: 0; visibility: hidden;">
|
||||
|
@ -14,32 +14,11 @@ if (typeof window.applicationCache !== 'undefined') {
|
||||
};
|
||||
}
|
||||
|
||||
var axe = require('axe-logger'),
|
||||
AddAccountCtrl = require('./controller/login/add-account'),
|
||||
CreateAccountCtrl = require('./controller/login/create-account'),
|
||||
ValidatePhoneCtrl = require('./controller/login/validate-phone'),
|
||||
LoginCtrl = require('./controller/login/login'),
|
||||
LoginInitialCtrl = require('./controller/login/login-initial'),
|
||||
LoginNewDeviceCtrl = require('./controller/login/login-new-device'),
|
||||
LoginExistingCtrl = require('./controller/login/login-existing'),
|
||||
LoginPrivateKeyDownloadCtrl = require('./controller/login/login-privatekey-download'),
|
||||
LoginSetCredentialsCtrl = require('./controller/login/login-set-credentials'),
|
||||
DialogCtrl = require('./controller/app/dialog'),
|
||||
AccountCtrl = require('./controller/app/account'),
|
||||
SetPassphraseCtrl = require('./controller/app/set-passphrase'),
|
||||
PrivateKeyUploadCtrl = require('./controller/app/privatekey-upload'),
|
||||
ContactsCtrl = require('./controller/app/contacts'),
|
||||
AboutCtrl = require('./controller/app/about'),
|
||||
MailListCtrl = require('./controller/app/mail-list'),
|
||||
ReadCtrl = require('./controller/app/read'),
|
||||
WriteCtrl = require('./controller/app/write'),
|
||||
NavigationCtrl = require('./controller/app/navigation'),
|
||||
ActionBarCtrl = require('./controller/app/action-bar'),
|
||||
StatusDisplayCtrl = require('./controller/app/status-display');
|
||||
var axe = require('axe-logger');
|
||||
|
||||
// include angular modules
|
||||
require('./app-config');
|
||||
require('./directive/common');
|
||||
require('./directive');
|
||||
require('./util');
|
||||
require('./crypto');
|
||||
require('./service');
|
||||
@ -56,12 +35,6 @@ var app = angular.module('mail', [
|
||||
'woCrypto',
|
||||
'woServices',
|
||||
'woEmail',
|
||||
'navigation',
|
||||
'mail-list',
|
||||
'write',
|
||||
'read',
|
||||
'contacts',
|
||||
'login-new-device',
|
||||
'infinite-scroll'
|
||||
]);
|
||||
|
||||
@ -69,43 +42,43 @@ var app = angular.module('mail', [
|
||||
app.config(function($routeProvider, $animateProvider) {
|
||||
$routeProvider.when('/login', {
|
||||
templateUrl: 'tpl/login.html',
|
||||
controller: LoginCtrl
|
||||
controller: require('./controller/login/login')
|
||||
});
|
||||
$routeProvider.when('/add-account', {
|
||||
templateUrl: 'tpl/add-account.html',
|
||||
controller: AddAccountCtrl
|
||||
controller: require('./controller/login/add-account')
|
||||
});
|
||||
$routeProvider.when('/create-account', {
|
||||
templateUrl: 'tpl/create-account.html',
|
||||
controller: CreateAccountCtrl
|
||||
controller: require('./controller/login/create-account')
|
||||
});
|
||||
$routeProvider.when('/validate-phone', {
|
||||
templateUrl: 'tpl/validate-phone.html',
|
||||
controller: ValidatePhoneCtrl
|
||||
controller: require('./controller/login/validate-phone')
|
||||
});
|
||||
$routeProvider.when('/login-set-credentials', {
|
||||
templateUrl: 'tpl/login-set-credentials.html',
|
||||
controller: LoginSetCredentialsCtrl
|
||||
controller: require('./controller/login/login-set-credentials')
|
||||
});
|
||||
$routeProvider.when('/login-existing', {
|
||||
templateUrl: 'tpl/login-existing.html',
|
||||
controller: LoginExistingCtrl
|
||||
controller: require('./controller/login/login-existing')
|
||||
});
|
||||
$routeProvider.when('/login-initial', {
|
||||
templateUrl: 'tpl/login-initial.html',
|
||||
controller: LoginInitialCtrl
|
||||
controller: require('./controller/login/login-initial')
|
||||
});
|
||||
$routeProvider.when('/login-new-device', {
|
||||
templateUrl: 'tpl/login-new-device.html',
|
||||
controller: LoginNewDeviceCtrl
|
||||
controller: require('./controller/login/login-new-device')
|
||||
});
|
||||
$routeProvider.when('/login-privatekey-download', {
|
||||
templateUrl: 'tpl/login-privatekey-download.html',
|
||||
controller: LoginPrivateKeyDownloadCtrl
|
||||
controller: require('./controller/login/login-privatekey-download')
|
||||
});
|
||||
$routeProvider.when('/account', {
|
||||
templateUrl: 'tpl/desktop.html',
|
||||
controller: NavigationCtrl,
|
||||
controller: require('./controller/app/navigation'),
|
||||
reloadOnSearch: false // don't reload controllers in main app when query params change
|
||||
});
|
||||
$routeProvider.otherwise({
|
||||
@ -124,17 +97,17 @@ app.run(function($rootScope) {
|
||||
});
|
||||
|
||||
// inject controllers from ng-included view templates
|
||||
app.controller('ReadCtrl', ReadCtrl);
|
||||
app.controller('WriteCtrl', WriteCtrl);
|
||||
app.controller('MailListCtrl', MailListCtrl);
|
||||
app.controller('AccountCtrl', AccountCtrl);
|
||||
app.controller('SetPassphraseCtrl', SetPassphraseCtrl);
|
||||
app.controller('PrivateKeyUploadCtrl', PrivateKeyUploadCtrl);
|
||||
app.controller('ContactsCtrl', ContactsCtrl);
|
||||
app.controller('AboutCtrl', AboutCtrl);
|
||||
app.controller('DialogCtrl', DialogCtrl);
|
||||
app.controller('ActionBarCtrl', ActionBarCtrl);
|
||||
app.controller('StatusDisplayCtrl', StatusDisplayCtrl);
|
||||
app.controller('ReadCtrl', require('./controller/app/read'));
|
||||
app.controller('WriteCtrl', require('./controller/app/write'));
|
||||
app.controller('MailListCtrl', require('./controller/app/mail-list'));
|
||||
app.controller('AccountCtrl', require('./controller/app/account'));
|
||||
app.controller('SetPassphraseCtrl', require('./controller/app/set-passphrase'));
|
||||
app.controller('PrivateKeyUploadCtrl', require('./controller/app/privatekey-upload'));
|
||||
app.controller('ContactsCtrl', require('./controller/app/contacts'));
|
||||
app.controller('AboutCtrl', require('./controller/app/about'));
|
||||
app.controller('DialogCtrl', require('./controller/app/dialog'));
|
||||
app.controller('ActionBarCtrl', require('./controller/app/action-bar'));
|
||||
app.controller('StatusDisplayCtrl', require('./controller/app/status-display'));
|
||||
|
||||
//
|
||||
// Manual angular bootstraping
|
||||
@ -155,4 +128,4 @@ function bootstrap() {
|
||||
angular.element(document).ready(function() {
|
||||
angular.bootstrap(document, ['mail']);
|
||||
});
|
||||
}
|
||||
}
|
@ -99,28 +99,4 @@ var ContactsCtrl = function($scope, keychain, pgp, dialog) {
|
||||
|
||||
};
|
||||
|
||||
//
|
||||
// Directives
|
||||
//
|
||||
|
||||
var ngModule = angular.module('contacts', []);
|
||||
|
||||
ngModule.directive('keyfileInput', function() {
|
||||
return function(scope, elm) {
|
||||
elm.on('change', function(e) {
|
||||
for (var i = 0; i < e.target.files.length; i++) {
|
||||
importKey(e.target.files.item(i));
|
||||
}
|
||||
});
|
||||
|
||||
function importKey(file) {
|
||||
var reader = new FileReader();
|
||||
reader.onload = function(e) {
|
||||
scope.importKey(e.target.result);
|
||||
};
|
||||
reader.readAsText(file);
|
||||
}
|
||||
};
|
||||
});
|
||||
|
||||
module.exports = ContactsCtrl;
|
@ -329,83 +329,9 @@ var MailListCtrl = function($scope, $timeout, $location, $filter, status, notifi
|
||||
};
|
||||
|
||||
//
|
||||
// Directives
|
||||
// helper functions
|
||||
//
|
||||
|
||||
var ngModule = angular.module('mail-list', []);
|
||||
|
||||
ngModule.directive('listScroll', function() {
|
||||
return {
|
||||
link: function(scope, elm, attrs) {
|
||||
var model = attrs.listScroll,
|
||||
listEl = elm[0],
|
||||
scrollTimeout;
|
||||
|
||||
/*
|
||||
* iterates over the mails in the mail list and loads their bodies if they are visible in the viewport
|
||||
*/
|
||||
scope.loadVisibleBodies = function() {
|
||||
var listBorder = listEl.getBoundingClientRect(),
|
||||
top = listBorder.top,
|
||||
bottom = listBorder.bottom,
|
||||
listItems = listEl.children[0].children,
|
||||
inViewport = false,
|
||||
listItem, message,
|
||||
isPartiallyVisibleTop, isPartiallyVisibleBottom, isVisible,
|
||||
displayMessages = scope[model];
|
||||
|
||||
if (!top && !bottom) {
|
||||
// list not visible
|
||||
return;
|
||||
}
|
||||
|
||||
for (var i = 0, len = listItems.length; i < len; i++) {
|
||||
// the n-th list item (the dom representation of an message) corresponds to
|
||||
// the n-th message model in the filteredMessages array
|
||||
listItem = listItems.item(i).getBoundingClientRect();
|
||||
|
||||
if (!displayMessages || displayMessages.length <= i) {
|
||||
// stop if i get larger than the size of filtered messages
|
||||
break;
|
||||
}
|
||||
message = displayMessages[i];
|
||||
|
||||
|
||||
isPartiallyVisibleTop = listItem.top < top && listItem.bottom > top; // a portion of the list item is visible on the top
|
||||
isPartiallyVisibleBottom = listItem.top < bottom && listItem.bottom > bottom; // a portion of the list item is visible on the bottom
|
||||
isVisible = (listItem.top || listItem.bottom) && listItem.top >= top && listItem.bottom <= bottom; // the list item is visible as a whole
|
||||
|
||||
if (isPartiallyVisibleTop || isVisible || isPartiallyVisibleBottom) {
|
||||
// we are now iterating over visible elements
|
||||
inViewport = true;
|
||||
// load mail body of visible
|
||||
scope.getBody(message);
|
||||
} else if (inViewport) {
|
||||
// we are leaving the viewport, so stop iterating over the items
|
||||
break;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// load body when scrolling
|
||||
listEl.onscroll = function() {
|
||||
if (scrollTimeout) {
|
||||
// remove timeout so that only scroll end
|
||||
clearTimeout(scrollTimeout);
|
||||
}
|
||||
scrollTimeout = setTimeout(function() {
|
||||
scope.loadVisibleBodies();
|
||||
}, 300);
|
||||
};
|
||||
|
||||
// load the visible message bodies, when the list is re-initialized and when scrolling stopped
|
||||
scope.$watchCollection(model, function() {
|
||||
scope.loadVisibleBodies();
|
||||
});
|
||||
}
|
||||
};
|
||||
});
|
||||
|
||||
function byUidDescending(a, b) {
|
||||
if (a.uid < b.uid) {
|
||||
return 1;
|
||||
|
@ -169,57 +169,4 @@ var NavigationCtrl = function($scope, $location, account, email, outbox, notific
|
||||
}
|
||||
};
|
||||
|
||||
//
|
||||
// Directives
|
||||
//
|
||||
|
||||
var ngModule = angular.module('navigation', []);
|
||||
ngModule.directive('keyShortcuts', function($timeout) {
|
||||
return function(scope, elm) {
|
||||
elm.bind('keydown', function(e) {
|
||||
// global state is not yet set, ignore keybaord shortcuts
|
||||
if (!scope.state) {
|
||||
return;
|
||||
}
|
||||
|
||||
var modifier = e.ctrlKey || e.metaKey;
|
||||
|
||||
if (modifier && e.keyCode === 78 && scope.state.lightbox !== 'write') {
|
||||
// n -> new mail
|
||||
e.preventDefault();
|
||||
scope.state.writer.write();
|
||||
scope.$apply();
|
||||
|
||||
} else if (modifier && e.keyCode === 70 && scope.state.lightbox !== 'write') {
|
||||
// f -> find
|
||||
e.preventDefault();
|
||||
scope.state.mailList.searching = true;
|
||||
$timeout(function() {
|
||||
scope.state.mailList.searching = false;
|
||||
}, 200);
|
||||
scope.$apply();
|
||||
|
||||
} else if (modifier && e.keyCode === 82 && scope.state.lightbox !== 'write' && scope.state.mailList.selected) {
|
||||
// r -> reply
|
||||
e.preventDefault();
|
||||
scope.state.writer.write(scope.state.mailList.selected);
|
||||
scope.$apply();
|
||||
|
||||
} else if (e.keyCode === 27 && scope.state.lightbox !== undefined) {
|
||||
// escape -> close current lightbox
|
||||
e.preventDefault();
|
||||
scope.state.lightbox = undefined;
|
||||
scope.$apply();
|
||||
|
||||
} else if (e.keyCode === 27 && scope.state.nav.open) {
|
||||
// escape -> close nav view
|
||||
e.preventDefault();
|
||||
scope.state.nav.toggle(false);
|
||||
scope.$apply();
|
||||
}
|
||||
|
||||
});
|
||||
};
|
||||
});
|
||||
|
||||
module.exports = NavigationCtrl;
|
@ -168,140 +168,4 @@ var ReadCtrl = function($scope, $location, email, invitation, outbox, pgp, keych
|
||||
};
|
||||
};
|
||||
|
||||
//
|
||||
// Directives
|
||||
//
|
||||
|
||||
var ngModule = angular.module('read', []);
|
||||
|
||||
ngModule.directive('replySelection', function() {
|
||||
return function(scope, elm) {
|
||||
var popover, visible;
|
||||
|
||||
popover = angular.element(document.querySelector('.reply-selection'));
|
||||
visible = false;
|
||||
|
||||
elm.on('touchstart click', appear);
|
||||
elm.parent().parent().on('touchstart click', disappear);
|
||||
popover.on('touchstart click', disappear);
|
||||
|
||||
function appear(e) {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
|
||||
visible = true;
|
||||
|
||||
// set popover position
|
||||
var top = elm[0].offsetTop;
|
||||
var left = elm[0].offsetLeft;
|
||||
var width = elm[0].offsetWidth;
|
||||
var height = elm[0].offsetHeight;
|
||||
|
||||
popover[0].style.transition = 'opacity 0.1s linear';
|
||||
popover[0].style.top = (top + height) + 'px';
|
||||
popover[0].style.left = (left + width / 2 - popover[0].offsetWidth / 2) + 'px';
|
||||
popover[0].style.opacity = '1';
|
||||
}
|
||||
|
||||
function disappear() {
|
||||
if (!visible) {
|
||||
return;
|
||||
}
|
||||
|
||||
popover[0].style.transition = 'opacity 0.25s linear, top 0.25s step-end, left 0.25s step-end';
|
||||
popover[0].style.opacity = '0';
|
||||
popover[0].style.top = '-9999px';
|
||||
popover[0].style.left = '-9999px';
|
||||
visible = false;
|
||||
}
|
||||
};
|
||||
});
|
||||
|
||||
ngModule.directive('frameLoad', function($timeout, $window) {
|
||||
return function(scope, elm) {
|
||||
var iframe = elm[0];
|
||||
|
||||
scope.$watch('state.read.open', function(open) {
|
||||
if (open) {
|
||||
// trigger rendering of iframe
|
||||
// otherwise scale to fit would not compute correct dimensions on mobile
|
||||
displayText(scope.state.mailList.selected ? scope.state.mailList.selected.body : undefined);
|
||||
displayHtml(scope.state.mailList.selected ? scope.state.mailList.selected.html : undefined);
|
||||
}
|
||||
});
|
||||
|
||||
$window.addEventListener('resize', scaleToFit);
|
||||
|
||||
iframe.onload = function() {
|
||||
// set listeners
|
||||
scope.$watch('state.mailList.selected.body', displayText);
|
||||
scope.$watch('state.mailList.selected.html', displayHtml);
|
||||
// display initial message body
|
||||
scope.$apply();
|
||||
};
|
||||
|
||||
function displayText(body) {
|
||||
var mail = scope.state.mailList.selected;
|
||||
if ((mail && mail.html) || (mail && mail.encrypted && !mail.decrypted)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// send text body for rendering in iframe
|
||||
iframe.contentWindow.postMessage({
|
||||
text: body
|
||||
}, '*');
|
||||
|
||||
$timeout(scaleToFit, 0);
|
||||
}
|
||||
|
||||
function displayHtml(html) {
|
||||
if (!html) {
|
||||
return;
|
||||
}
|
||||
|
||||
// if there are image tags in the html?
|
||||
var hasImages = /<img[^>]+\bsrc=['"][^'">]+['"]/ig.test(html);
|
||||
scope.showImageButton = hasImages;
|
||||
|
||||
iframe.contentWindow.postMessage({
|
||||
html: html,
|
||||
removeImages: hasImages // avoids doing unnecessary work on the html
|
||||
}, '*');
|
||||
|
||||
// only add a scope function to reload the html if there are images
|
||||
if (hasImages) {
|
||||
// reload WITH images
|
||||
scope.displayImages = function() {
|
||||
scope.showImageButton = false;
|
||||
iframe.contentWindow.postMessage({
|
||||
html: html,
|
||||
removeImages: false
|
||||
}, '*');
|
||||
};
|
||||
}
|
||||
|
||||
$timeout(scaleToFit, 0);
|
||||
}
|
||||
|
||||
// transform scale iframe (necessary on iOS) to fit container width
|
||||
function scaleToFit() {
|
||||
var parentWidth = elm.parent().width();
|
||||
var w = elm.width();
|
||||
var scale = '';
|
||||
|
||||
if (w > parentWidth) {
|
||||
scale = parentWidth / w;
|
||||
scale = 'scale(' + scale + ',' + scale + ')';
|
||||
}
|
||||
|
||||
elm.css({
|
||||
'-webkit-transform-origin': '0 0',
|
||||
'transform-origin': '0 0',
|
||||
'-webkit-transform': scale,
|
||||
'transform': scale
|
||||
});
|
||||
}
|
||||
};
|
||||
});
|
||||
|
||||
module.exports = ReadCtrl;
|
@ -438,61 +438,4 @@ var WriteCtrl = function($scope, $window, $filter, $q, appConfig, auth, keychain
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
//
|
||||
// Directives
|
||||
//
|
||||
|
||||
var ngModule = angular.module('write', []);
|
||||
|
||||
ngModule.directive('focusInput', function($timeout, $parse) {
|
||||
return {
|
||||
//scope: true, // optionally create a child scope
|
||||
link: function(scope, element, attrs) {
|
||||
var model = $parse(attrs.focusInput);
|
||||
scope.$watch(model, function(value) {
|
||||
if (value === true) {
|
||||
$timeout(function() {
|
||||
element.find('input').first().focus();
|
||||
}, 100);
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
});
|
||||
|
||||
ngModule.directive('focusInputOnClick', function() {
|
||||
return {
|
||||
//scope: true, // optionally create a child scope
|
||||
link: function(scope, element) {
|
||||
element.on('click', function() {
|
||||
element.find('input').first().focus();
|
||||
});
|
||||
}
|
||||
};
|
||||
});
|
||||
|
||||
ngModule.directive('attachmentInput', function() {
|
||||
return function(scope, elm) {
|
||||
elm.on('change', function(e) {
|
||||
for (var i = 0; i < e.target.files.length; i++) {
|
||||
addAttachment(e.target.files.item(i));
|
||||
}
|
||||
});
|
||||
|
||||
function addAttachment(file) {
|
||||
var reader = new FileReader();
|
||||
reader.onload = function(e) {
|
||||
scope.attachments.push({
|
||||
filename: file.name,
|
||||
mimeType: file.type,
|
||||
content: new Uint8Array(e.target.result)
|
||||
});
|
||||
scope.$digest();
|
||||
};
|
||||
reader.readAsArrayBuffer(file);
|
||||
}
|
||||
};
|
||||
});
|
||||
|
||||
module.exports = WriteCtrl;
|
@ -107,39 +107,4 @@ var LoginExistingCtrl = function($scope, $location, $routeParams, email, auth, p
|
||||
};
|
||||
};
|
||||
|
||||
var ngModule = angular.module('login-new-device', []);
|
||||
ngModule.directive('fileReader', function() {
|
||||
return function(scope, elm) {
|
||||
elm.bind('change', function(e) {
|
||||
var files = e.target.files,
|
||||
reader = new FileReader();
|
||||
|
||||
if (files.length === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
reader.onload = function(e) {
|
||||
var rawKeys = e.target.result,
|
||||
index = rawKeys.indexOf('-----BEGIN PGP PRIVATE KEY BLOCK-----'),
|
||||
keyParts;
|
||||
|
||||
if (index === -1) {
|
||||
scope.displayError(new Error('Error parsing private PGP key block!'));
|
||||
return;
|
||||
}
|
||||
|
||||
keyParts = {
|
||||
publicKeyArmored: rawKeys.substring(0, index).trim(),
|
||||
privateKeyArmored: rawKeys.substring(index, rawKeys.length).trim()
|
||||
};
|
||||
|
||||
scope.$apply(function() {
|
||||
scope.key = keyParts;
|
||||
});
|
||||
};
|
||||
reader.readAsText(files[0]);
|
||||
});
|
||||
};
|
||||
});
|
||||
|
||||
module.exports = LoginExistingCtrl;
|
@ -1,6 +1,6 @@
|
||||
'use strict';
|
||||
|
||||
var ngModule = angular.module('woDirectives', []);
|
||||
var ngModule = angular.module('woDirectives');
|
||||
|
||||
ngModule.directive('woTouch', function($parse) {
|
||||
var className = 'wo-touch-active';
|
||||
@ -330,6 +330,4 @@ ngModule.directive('woInputCode', function() {
|
||||
});
|
||||
}
|
||||
};
|
||||
});
|
||||
|
||||
module.exports = ngModule;
|
||||
});
|
21
src/js/directive/contacts.js
Normal file
21
src/js/directive/contacts.js
Normal file
@ -0,0 +1,21 @@
|
||||
'use strict';
|
||||
|
||||
var ngModule = angular.module('woDirectives');
|
||||
|
||||
ngModule.directive('keyfileInput', function() {
|
||||
return function(scope, elm) {
|
||||
elm.on('change', function(e) {
|
||||
for (var i = 0; i < e.target.files.length; i++) {
|
||||
importKey(e.target.files.item(i));
|
||||
}
|
||||
});
|
||||
|
||||
function importKey(file) {
|
||||
var reader = new FileReader();
|
||||
reader.onload = function(e) {
|
||||
scope.importKey(e.target.result);
|
||||
};
|
||||
reader.readAsText(file);
|
||||
}
|
||||
};
|
||||
});
|
11
src/js/directive/index.js
Normal file
11
src/js/directive/index.js
Normal file
@ -0,0 +1,11 @@
|
||||
'use strict';
|
||||
|
||||
angular.module('woDirectives', []);
|
||||
|
||||
require('./common');
|
||||
require('./key-shortcuts');
|
||||
require('./mail-list');
|
||||
require('./write');
|
||||
require('./read');
|
||||
require('./contacts');
|
||||
require('./login-new-device');
|
51
src/js/directive/key-shortcuts.js
Normal file
51
src/js/directive/key-shortcuts.js
Normal file
@ -0,0 +1,51 @@
|
||||
'use strict';
|
||||
|
||||
var ngModule = angular.module('woDirectives');
|
||||
|
||||
ngModule.directive('woKeyShortcuts', function($timeout) {
|
||||
return function(scope, elm) {
|
||||
elm.bind('keydown', function(e) {
|
||||
// global state is not yet set, ignore keybaord shortcuts
|
||||
if (!scope.state) {
|
||||
return;
|
||||
}
|
||||
|
||||
var modifier = e.ctrlKey || e.metaKey;
|
||||
|
||||
if (modifier && e.keyCode === 78 && scope.state.lightbox !== 'write') {
|
||||
// n -> new mail
|
||||
e.preventDefault();
|
||||
scope.state.writer.write();
|
||||
scope.$apply();
|
||||
|
||||
} else if (modifier && e.keyCode === 70 && scope.state.lightbox !== 'write') {
|
||||
// f -> find
|
||||
e.preventDefault();
|
||||
scope.state.mailList.searching = true;
|
||||
$timeout(function() {
|
||||
scope.state.mailList.searching = false;
|
||||
}, 200);
|
||||
scope.$apply();
|
||||
|
||||
} else if (modifier && e.keyCode === 82 && scope.state.lightbox !== 'write' && scope.state.mailList.selected) {
|
||||
// r -> reply
|
||||
e.preventDefault();
|
||||
scope.state.writer.write(scope.state.mailList.selected);
|
||||
scope.$apply();
|
||||
|
||||
} else if (e.keyCode === 27 && scope.state.lightbox !== undefined) {
|
||||
// escape -> close current lightbox
|
||||
e.preventDefault();
|
||||
scope.state.lightbox = undefined;
|
||||
scope.$apply();
|
||||
|
||||
} else if (e.keyCode === 27 && scope.state.nav.open) {
|
||||
// escape -> close nav view
|
||||
e.preventDefault();
|
||||
scope.state.nav.toggle(false);
|
||||
scope.$apply();
|
||||
}
|
||||
|
||||
});
|
||||
};
|
||||
});
|
37
src/js/directive/login-new-device.js
Normal file
37
src/js/directive/login-new-device.js
Normal file
@ -0,0 +1,37 @@
|
||||
'use strict';
|
||||
|
||||
var ngModule = angular.module('woDirectives');
|
||||
|
||||
ngModule.directive('fileReader', function() {
|
||||
return function(scope, elm) {
|
||||
elm.bind('change', function(e) {
|
||||
var files = e.target.files,
|
||||
reader = new FileReader();
|
||||
|
||||
if (files.length === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
reader.onload = function(e) {
|
||||
var rawKeys = e.target.result,
|
||||
index = rawKeys.indexOf('-----BEGIN PGP PRIVATE KEY BLOCK-----'),
|
||||
keyParts;
|
||||
|
||||
if (index === -1) {
|
||||
scope.displayError(new Error('Error parsing private PGP key block!'));
|
||||
return;
|
||||
}
|
||||
|
||||
keyParts = {
|
||||
publicKeyArmored: rawKeys.substring(0, index).trim(),
|
||||
privateKeyArmored: rawKeys.substring(index, rawKeys.length).trim()
|
||||
};
|
||||
|
||||
scope.$apply(function() {
|
||||
scope.key = keyParts;
|
||||
});
|
||||
};
|
||||
reader.readAsText(files[0]);
|
||||
});
|
||||
};
|
||||
});
|
75
src/js/directive/mail-list.js
Normal file
75
src/js/directive/mail-list.js
Normal file
@ -0,0 +1,75 @@
|
||||
'use strict';
|
||||
|
||||
var ngModule = angular.module('woDirectives');
|
||||
|
||||
ngModule.directive('listScroll', function() {
|
||||
return {
|
||||
link: function(scope, elm, attrs) {
|
||||
var model = attrs.listScroll,
|
||||
listEl = elm[0],
|
||||
scrollTimeout;
|
||||
|
||||
/*
|
||||
* iterates over the mails in the mail list and loads their bodies if they are visible in the viewport
|
||||
*/
|
||||
scope.loadVisibleBodies = function() {
|
||||
var listBorder = listEl.getBoundingClientRect(),
|
||||
top = listBorder.top,
|
||||
bottom = listBorder.bottom,
|
||||
listItems = listEl.children[0].children,
|
||||
inViewport = false,
|
||||
listItem, message,
|
||||
isPartiallyVisibleTop, isPartiallyVisibleBottom, isVisible,
|
||||
displayMessages = scope[model];
|
||||
|
||||
if (!top && !bottom) {
|
||||
// list not visible
|
||||
return;
|
||||
}
|
||||
|
||||
for (var i = 0, len = listItems.length; i < len; i++) {
|
||||
// the n-th list item (the dom representation of an message) corresponds to
|
||||
// the n-th message model in the filteredMessages array
|
||||
listItem = listItems.item(i).getBoundingClientRect();
|
||||
|
||||
if (!displayMessages || displayMessages.length <= i) {
|
||||
// stop if i get larger than the size of filtered messages
|
||||
break;
|
||||
}
|
||||
message = displayMessages[i];
|
||||
|
||||
|
||||
isPartiallyVisibleTop = listItem.top < top && listItem.bottom > top; // a portion of the list item is visible on the top
|
||||
isPartiallyVisibleBottom = listItem.top < bottom && listItem.bottom > bottom; // a portion of the list item is visible on the bottom
|
||||
isVisible = (listItem.top || listItem.bottom) && listItem.top >= top && listItem.bottom <= bottom; // the list item is visible as a whole
|
||||
|
||||
if (isPartiallyVisibleTop || isVisible || isPartiallyVisibleBottom) {
|
||||
// we are now iterating over visible elements
|
||||
inViewport = true;
|
||||
// load mail body of visible
|
||||
scope.getBody(message);
|
||||
} else if (inViewport) {
|
||||
// we are leaving the viewport, so stop iterating over the items
|
||||
break;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// load body when scrolling
|
||||
listEl.onscroll = function() {
|
||||
if (scrollTimeout) {
|
||||
// remove timeout so that only scroll end
|
||||
clearTimeout(scrollTimeout);
|
||||
}
|
||||
scrollTimeout = setTimeout(function() {
|
||||
scope.loadVisibleBodies();
|
||||
}, 300);
|
||||
};
|
||||
|
||||
// load the visible message bodies, when the list is re-initialized and when scrolling stopped
|
||||
scope.$watchCollection(model, function() {
|
||||
scope.loadVisibleBodies();
|
||||
});
|
||||
}
|
||||
};
|
||||
});
|
133
src/js/directive/read.js
Normal file
133
src/js/directive/read.js
Normal file
@ -0,0 +1,133 @@
|
||||
'use strict';
|
||||
|
||||
var ngModule = angular.module('woDirectives');
|
||||
|
||||
ngModule.directive('replySelection', function() {
|
||||
return function(scope, elm) {
|
||||
var popover, visible;
|
||||
|
||||
popover = angular.element(document.querySelector('.reply-selection'));
|
||||
visible = false;
|
||||
|
||||
elm.on('touchstart click', appear);
|
||||
elm.parent().parent().on('touchstart click', disappear);
|
||||
popover.on('touchstart click', disappear);
|
||||
|
||||
function appear(e) {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
|
||||
visible = true;
|
||||
|
||||
// set popover position
|
||||
var top = elm[0].offsetTop;
|
||||
var left = elm[0].offsetLeft;
|
||||
var width = elm[0].offsetWidth;
|
||||
var height = elm[0].offsetHeight;
|
||||
|
||||
popover[0].style.transition = 'opacity 0.1s linear';
|
||||
popover[0].style.top = (top + height) + 'px';
|
||||
popover[0].style.left = (left + width / 2 - popover[0].offsetWidth / 2) + 'px';
|
||||
popover[0].style.opacity = '1';
|
||||
}
|
||||
|
||||
function disappear() {
|
||||
if (!visible) {
|
||||
return;
|
||||
}
|
||||
|
||||
popover[0].style.transition = 'opacity 0.25s linear, top 0.25s step-end, left 0.25s step-end';
|
||||
popover[0].style.opacity = '0';
|
||||
popover[0].style.top = '-9999px';
|
||||
popover[0].style.left = '-9999px';
|
||||
visible = false;
|
||||
}
|
||||
};
|
||||
});
|
||||
|
||||
ngModule.directive('frameLoad', function($timeout, $window) {
|
||||
return function(scope, elm) {
|
||||
var iframe = elm[0];
|
||||
|
||||
scope.$watch('state.read.open', function(open) {
|
||||
if (open) {
|
||||
// trigger rendering of iframe
|
||||
// otherwise scale to fit would not compute correct dimensions on mobile
|
||||
displayText(scope.state.mailList.selected ? scope.state.mailList.selected.body : undefined);
|
||||
displayHtml(scope.state.mailList.selected ? scope.state.mailList.selected.html : undefined);
|
||||
}
|
||||
});
|
||||
|
||||
$window.addEventListener('resize', scaleToFit);
|
||||
|
||||
iframe.onload = function() {
|
||||
// set listeners
|
||||
scope.$watch('state.mailList.selected.body', displayText);
|
||||
scope.$watch('state.mailList.selected.html', displayHtml);
|
||||
// display initial message body
|
||||
scope.$apply();
|
||||
};
|
||||
|
||||
function displayText(body) {
|
||||
var mail = scope.state.mailList.selected;
|
||||
if ((mail && mail.html) || (mail && mail.encrypted && !mail.decrypted)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// send text body for rendering in iframe
|
||||
iframe.contentWindow.postMessage({
|
||||
text: body
|
||||
}, '*');
|
||||
|
||||
$timeout(scaleToFit, 0);
|
||||
}
|
||||
|
||||
function displayHtml(html) {
|
||||
if (!html) {
|
||||
return;
|
||||
}
|
||||
|
||||
// if there are image tags in the html?
|
||||
var hasImages = /<img[^>]+\bsrc=['"][^'">]+['"]/ig.test(html);
|
||||
scope.showImageButton = hasImages;
|
||||
|
||||
iframe.contentWindow.postMessage({
|
||||
html: html,
|
||||
removeImages: hasImages // avoids doing unnecessary work on the html
|
||||
}, '*');
|
||||
|
||||
// only add a scope function to reload the html if there are images
|
||||
if (hasImages) {
|
||||
// reload WITH images
|
||||
scope.displayImages = function() {
|
||||
scope.showImageButton = false;
|
||||
iframe.contentWindow.postMessage({
|
||||
html: html,
|
||||
removeImages: false
|
||||
}, '*');
|
||||
};
|
||||
}
|
||||
|
||||
$timeout(scaleToFit, 0);
|
||||
}
|
||||
|
||||
// transform scale iframe (necessary on iOS) to fit container width
|
||||
function scaleToFit() {
|
||||
var parentWidth = elm.parent().width();
|
||||
var w = elm.width();
|
||||
var scale = '';
|
||||
|
||||
if (w > parentWidth) {
|
||||
scale = parentWidth / w;
|
||||
scale = 'scale(' + scale + ',' + scale + ')';
|
||||
}
|
||||
|
||||
elm.css({
|
||||
'-webkit-transform-origin': '0 0',
|
||||
'transform-origin': '0 0',
|
||||
'-webkit-transform': scale,
|
||||
'transform': scale
|
||||
});
|
||||
}
|
||||
};
|
||||
});
|
53
src/js/directive/write.js
Normal file
53
src/js/directive/write.js
Normal file
@ -0,0 +1,53 @@
|
||||
'use strict';
|
||||
|
||||
var ngModule = angular.module('woDirectives');
|
||||
|
||||
ngModule.directive('focusInput', function($timeout, $parse) {
|
||||
return {
|
||||
//scope: true, // optionally create a child scope
|
||||
link: function(scope, element, attrs) {
|
||||
var model = $parse(attrs.focusInput);
|
||||
scope.$watch(model, function(value) {
|
||||
if (value === true) {
|
||||
$timeout(function() {
|
||||
element.find('input').first().focus();
|
||||
}, 100);
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
});
|
||||
|
||||
ngModule.directive('focusInputOnClick', function() {
|
||||
return {
|
||||
//scope: true, // optionally create a child scope
|
||||
link: function(scope, element) {
|
||||
element.on('click', function() {
|
||||
element.find('input').first().focus();
|
||||
});
|
||||
}
|
||||
};
|
||||
});
|
||||
|
||||
ngModule.directive('attachmentInput', function() {
|
||||
return function(scope, elm) {
|
||||
elm.on('change', function(e) {
|
||||
for (var i = 0; i < e.target.files.length; i++) {
|
||||
addAttachment(e.target.files.item(i));
|
||||
}
|
||||
});
|
||||
|
||||
function addAttachment(file) {
|
||||
var reader = new FileReader();
|
||||
reader.onload = function(e) {
|
||||
scope.attachments.push({
|
||||
filename: file.name,
|
||||
mimeType: file.type,
|
||||
content: new Uint8Array(e.target.result)
|
||||
});
|
||||
scope.$digest();
|
||||
};
|
||||
reader.readAsArrayBuffer(file);
|
||||
}
|
||||
};
|
||||
});
|
Loading…
Reference in New Issue
Block a user