diff --git a/src/index.html b/src/index.html
index dc456e1..4d50df6 100644
--- a/src/index.html
+++ b/src/index.html
@@ -31,7 +31,7 @@
-
+
diff --git a/src/js/app.js b/src/js/app.js
index 1b908c2..785a907 100644
--- a/src/js/app.js
+++ b/src/js/app.js
@@ -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']);
});
-}
+}
\ No newline at end of file
diff --git a/src/js/controller/app/contacts.js b/src/js/controller/app/contacts.js
index 7286199..58bd3d4 100644
--- a/src/js/controller/app/contacts.js
+++ b/src/js/controller/app/contacts.js
@@ -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;
\ No newline at end of file
diff --git a/src/js/controller/app/mail-list.js b/src/js/controller/app/mail-list.js
index d033f91..4de5e6c 100644
--- a/src/js/controller/app/mail-list.js
+++ b/src/js/controller/app/mail-list.js
@@ -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;
diff --git a/src/js/controller/app/navigation.js b/src/js/controller/app/navigation.js
index 0c75e97..a122186 100644
--- a/src/js/controller/app/navigation.js
+++ b/src/js/controller/app/navigation.js
@@ -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;
\ No newline at end of file
diff --git a/src/js/controller/app/read.js b/src/js/controller/app/read.js
index f60b552..af394fc 100644
--- a/src/js/controller/app/read.js
+++ b/src/js/controller/app/read.js
@@ -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 = /
]+\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;
\ No newline at end of file
diff --git a/src/js/controller/app/write.js b/src/js/controller/app/write.js
index 3f345fc..ab94d2a 100644
--- a/src/js/controller/app/write.js
+++ b/src/js/controller/app/write.js
@@ -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;
\ No newline at end of file
diff --git a/src/js/controller/login/login-new-device.js b/src/js/controller/login/login-new-device.js
index fe0de28..af602bd 100644
--- a/src/js/controller/login/login-new-device.js
+++ b/src/js/controller/login/login-new-device.js
@@ -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;
\ No newline at end of file
diff --git a/src/js/directive/common.js b/src/js/directive/common.js
index dde171e..483c39e 100644
--- a/src/js/directive/common.js
+++ b/src/js/directive/common.js
@@ -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;
\ No newline at end of file
+});
\ No newline at end of file
diff --git a/src/js/directive/contacts.js b/src/js/directive/contacts.js
new file mode 100644
index 0000000..26441d7
--- /dev/null
+++ b/src/js/directive/contacts.js
@@ -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);
+ }
+ };
+});
\ No newline at end of file
diff --git a/src/js/directive/index.js b/src/js/directive/index.js
new file mode 100644
index 0000000..f7eef61
--- /dev/null
+++ b/src/js/directive/index.js
@@ -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');
\ No newline at end of file
diff --git a/src/js/directive/key-shortcuts.js b/src/js/directive/key-shortcuts.js
new file mode 100644
index 0000000..2ccd8e7
--- /dev/null
+++ b/src/js/directive/key-shortcuts.js
@@ -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();
+ }
+
+ });
+ };
+});
\ No newline at end of file
diff --git a/src/js/directive/login-new-device.js b/src/js/directive/login-new-device.js
new file mode 100644
index 0000000..5d610ea
--- /dev/null
+++ b/src/js/directive/login-new-device.js
@@ -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]);
+ });
+ };
+});
\ No newline at end of file
diff --git a/src/js/directive/mail-list.js b/src/js/directive/mail-list.js
new file mode 100644
index 0000000..27f622b
--- /dev/null
+++ b/src/js/directive/mail-list.js
@@ -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();
+ });
+ }
+ };
+});
\ No newline at end of file
diff --git a/src/js/directive/read.js b/src/js/directive/read.js
new file mode 100644
index 0000000..32dffeb
--- /dev/null
+++ b/src/js/directive/read.js
@@ -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 = /
]+\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
+ });
+ }
+ };
+});
\ No newline at end of file
diff --git a/src/js/directive/write.js b/src/js/directive/write.js
new file mode 100644
index 0000000..41f2ff3
--- /dev/null
+++ b/src/js/directive/write.js
@@ -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);
+ }
+ };
+});
\ No newline at end of file