From 3cd0bcc5131ae9ae980539d2560ec4741c8d548a Mon Sep 17 00:00:00 2001 From: Tankred Hase Date: Wed, 10 Dec 2014 21:41:16 +0100 Subject: [PATCH] Move directives to seperate files, cleanup app.js --- src/index.html | 2 +- src/js/app.js | 75 ++++------- src/js/controller/app/contacts.js | 24 ---- src/js/controller/app/mail-list.js | 76 +---------- src/js/controller/app/navigation.js | 53 -------- src/js/controller/app/read.js | 136 -------------------- src/js/controller/app/write.js | 57 -------- src/js/controller/login/login-new-device.js | 35 ----- src/js/directive/common.js | 6 +- src/js/directive/contacts.js | 21 +++ src/js/directive/index.js | 11 ++ src/js/directive/key-shortcuts.js | 51 ++++++++ src/js/directive/login-new-device.js | 37 ++++++ src/js/directive/mail-list.js | 75 +++++++++++ src/js/directive/read.js | 133 +++++++++++++++++++ src/js/directive/write.js | 53 ++++++++ 16 files changed, 409 insertions(+), 436 deletions(-) create mode 100644 src/js/directive/contacts.js create mode 100644 src/js/directive/index.js create mode 100644 src/js/directive/key-shortcuts.js create mode 100644 src/js/directive/login-new-device.js create mode 100644 src/js/directive/mail-list.js create mode 100644 src/js/directive/read.js create mode 100644 src/js/directive/write.js 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