mirror of
https://github.com/moparisthebest/mail
synced 2024-11-25 18:32:20 -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>
|
<script src="js/app.min.js"></script>
|
||||||
</head>
|
</head>
|
||||||
|
|
||||||
<body key-shortcuts>
|
<body wo-key-shortcuts>
|
||||||
|
|
||||||
<!-- inline icons have to come first, hide immediately with inline styles -->
|
<!-- inline icons have to come first, hide immediately with inline styles -->
|
||||||
<div style="width: 0; height: 0; visibility: hidden;">
|
<div style="width: 0; height: 0; visibility: hidden;">
|
||||||
|
@ -14,32 +14,11 @@ if (typeof window.applicationCache !== 'undefined') {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
var axe = require('axe-logger'),
|
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');
|
|
||||||
|
|
||||||
// include angular modules
|
// include angular modules
|
||||||
require('./app-config');
|
require('./app-config');
|
||||||
require('./directive/common');
|
require('./directive');
|
||||||
require('./util');
|
require('./util');
|
||||||
require('./crypto');
|
require('./crypto');
|
||||||
require('./service');
|
require('./service');
|
||||||
@ -56,12 +35,6 @@ var app = angular.module('mail', [
|
|||||||
'woCrypto',
|
'woCrypto',
|
||||||
'woServices',
|
'woServices',
|
||||||
'woEmail',
|
'woEmail',
|
||||||
'navigation',
|
|
||||||
'mail-list',
|
|
||||||
'write',
|
|
||||||
'read',
|
|
||||||
'contacts',
|
|
||||||
'login-new-device',
|
|
||||||
'infinite-scroll'
|
'infinite-scroll'
|
||||||
]);
|
]);
|
||||||
|
|
||||||
@ -69,43 +42,43 @@ var app = angular.module('mail', [
|
|||||||
app.config(function($routeProvider, $animateProvider) {
|
app.config(function($routeProvider, $animateProvider) {
|
||||||
$routeProvider.when('/login', {
|
$routeProvider.when('/login', {
|
||||||
templateUrl: 'tpl/login.html',
|
templateUrl: 'tpl/login.html',
|
||||||
controller: LoginCtrl
|
controller: require('./controller/login/login')
|
||||||
});
|
});
|
||||||
$routeProvider.when('/add-account', {
|
$routeProvider.when('/add-account', {
|
||||||
templateUrl: 'tpl/add-account.html',
|
templateUrl: 'tpl/add-account.html',
|
||||||
controller: AddAccountCtrl
|
controller: require('./controller/login/add-account')
|
||||||
});
|
});
|
||||||
$routeProvider.when('/create-account', {
|
$routeProvider.when('/create-account', {
|
||||||
templateUrl: 'tpl/create-account.html',
|
templateUrl: 'tpl/create-account.html',
|
||||||
controller: CreateAccountCtrl
|
controller: require('./controller/login/create-account')
|
||||||
});
|
});
|
||||||
$routeProvider.when('/validate-phone', {
|
$routeProvider.when('/validate-phone', {
|
||||||
templateUrl: 'tpl/validate-phone.html',
|
templateUrl: 'tpl/validate-phone.html',
|
||||||
controller: ValidatePhoneCtrl
|
controller: require('./controller/login/validate-phone')
|
||||||
});
|
});
|
||||||
$routeProvider.when('/login-set-credentials', {
|
$routeProvider.when('/login-set-credentials', {
|
||||||
templateUrl: 'tpl/login-set-credentials.html',
|
templateUrl: 'tpl/login-set-credentials.html',
|
||||||
controller: LoginSetCredentialsCtrl
|
controller: require('./controller/login/login-set-credentials')
|
||||||
});
|
});
|
||||||
$routeProvider.when('/login-existing', {
|
$routeProvider.when('/login-existing', {
|
||||||
templateUrl: 'tpl/login-existing.html',
|
templateUrl: 'tpl/login-existing.html',
|
||||||
controller: LoginExistingCtrl
|
controller: require('./controller/login/login-existing')
|
||||||
});
|
});
|
||||||
$routeProvider.when('/login-initial', {
|
$routeProvider.when('/login-initial', {
|
||||||
templateUrl: 'tpl/login-initial.html',
|
templateUrl: 'tpl/login-initial.html',
|
||||||
controller: LoginInitialCtrl
|
controller: require('./controller/login/login-initial')
|
||||||
});
|
});
|
||||||
$routeProvider.when('/login-new-device', {
|
$routeProvider.when('/login-new-device', {
|
||||||
templateUrl: 'tpl/login-new-device.html',
|
templateUrl: 'tpl/login-new-device.html',
|
||||||
controller: LoginNewDeviceCtrl
|
controller: require('./controller/login/login-new-device')
|
||||||
});
|
});
|
||||||
$routeProvider.when('/login-privatekey-download', {
|
$routeProvider.when('/login-privatekey-download', {
|
||||||
templateUrl: 'tpl/login-privatekey-download.html',
|
templateUrl: 'tpl/login-privatekey-download.html',
|
||||||
controller: LoginPrivateKeyDownloadCtrl
|
controller: require('./controller/login/login-privatekey-download')
|
||||||
});
|
});
|
||||||
$routeProvider.when('/account', {
|
$routeProvider.when('/account', {
|
||||||
templateUrl: 'tpl/desktop.html',
|
templateUrl: 'tpl/desktop.html',
|
||||||
controller: NavigationCtrl,
|
controller: require('./controller/app/navigation'),
|
||||||
reloadOnSearch: false // don't reload controllers in main app when query params change
|
reloadOnSearch: false // don't reload controllers in main app when query params change
|
||||||
});
|
});
|
||||||
$routeProvider.otherwise({
|
$routeProvider.otherwise({
|
||||||
@ -124,17 +97,17 @@ app.run(function($rootScope) {
|
|||||||
});
|
});
|
||||||
|
|
||||||
// inject controllers from ng-included view templates
|
// inject controllers from ng-included view templates
|
||||||
app.controller('ReadCtrl', ReadCtrl);
|
app.controller('ReadCtrl', require('./controller/app/read'));
|
||||||
app.controller('WriteCtrl', WriteCtrl);
|
app.controller('WriteCtrl', require('./controller/app/write'));
|
||||||
app.controller('MailListCtrl', MailListCtrl);
|
app.controller('MailListCtrl', require('./controller/app/mail-list'));
|
||||||
app.controller('AccountCtrl', AccountCtrl);
|
app.controller('AccountCtrl', require('./controller/app/account'));
|
||||||
app.controller('SetPassphraseCtrl', SetPassphraseCtrl);
|
app.controller('SetPassphraseCtrl', require('./controller/app/set-passphrase'));
|
||||||
app.controller('PrivateKeyUploadCtrl', PrivateKeyUploadCtrl);
|
app.controller('PrivateKeyUploadCtrl', require('./controller/app/privatekey-upload'));
|
||||||
app.controller('ContactsCtrl', ContactsCtrl);
|
app.controller('ContactsCtrl', require('./controller/app/contacts'));
|
||||||
app.controller('AboutCtrl', AboutCtrl);
|
app.controller('AboutCtrl', require('./controller/app/about'));
|
||||||
app.controller('DialogCtrl', DialogCtrl);
|
app.controller('DialogCtrl', require('./controller/app/dialog'));
|
||||||
app.controller('ActionBarCtrl', ActionBarCtrl);
|
app.controller('ActionBarCtrl', require('./controller/app/action-bar'));
|
||||||
app.controller('StatusDisplayCtrl', StatusDisplayCtrl);
|
app.controller('StatusDisplayCtrl', require('./controller/app/status-display'));
|
||||||
|
|
||||||
//
|
//
|
||||||
// Manual angular bootstraping
|
// Manual angular bootstraping
|
||||||
|
@ -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;
|
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) {
|
function byUidDescending(a, b) {
|
||||||
if (a.uid < b.uid) {
|
if (a.uid < b.uid) {
|
||||||
return 1;
|
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;
|
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;
|
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;
|
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;
|
module.exports = LoginExistingCtrl;
|
@ -1,6 +1,6 @@
|
|||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
var ngModule = angular.module('woDirectives', []);
|
var ngModule = angular.module('woDirectives');
|
||||||
|
|
||||||
ngModule.directive('woTouch', function($parse) {
|
ngModule.directive('woTouch', function($parse) {
|
||||||
var className = 'wo-touch-active';
|
var className = 'wo-touch-active';
|
||||||
@ -331,5 +331,3 @@ 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