diff options
Diffstat (limited to 'web-ui/app')
33 files changed, 303 insertions, 133 deletions
diff --git a/web-ui/app/index.html b/web-ui/app/index.html index 2d35662c..4d6f3037 100644 --- a/web-ui/app/index.html +++ b/web-ui/app/index.html @@ -9,8 +9,7 @@ <meta name="viewport" content="width=device-width"> <link href="assets/bower_components/font-awesome/css/font-awesome.min.css" rel="stylesheet" type="text/css"> <link href="assets/bower_components/jquery-file-upload/css/jquery.fileupload.css" rel="stylesheet" type="text/css"> -<link href="assets/css/opensans.css" rel="stylesheet" type="text/css"> -<link rel="stylesheet" href="assets/css/main.css"> +<link rel="stylesheet" href="assets/css/style.css"> </head> <body> @@ -56,7 +55,7 @@ </div> <div class="off-canvas-wrap content" data-offcanvas> - <header id="main" > + <header class="message-panel-container" > <div id="user-alerts" class="message-panel"></div> </header> @@ -100,6 +99,7 @@ <script src="assets/bower_components/foundation/js/foundation/foundation.reveal.js" ></script> <script src="assets/bower_components/foundation/js/foundation/foundation.offcanvas.js"></script> <script src="assets/js/foundation/initialize_foundation.js"></script> +<script src="assets/bower_components/iframe-resizer/js/iframeResizer.min.js"></script> <script src="assets/bower_components/requirejs/require.js" data-main="assets/js/main.js"></script> <!--usemin_end--> diff --git a/web-ui/app/js/helpers/sanitizer.js b/web-ui/app/js/helpers/sanitizer.js index eea1f0f7..443e8602 100644 --- a/web-ui/app/js/helpers/sanitizer.js +++ b/web-ui/app/js/helpers/sanitizer.js @@ -23,6 +23,16 @@ define(['DOMPurify', 'he'], function (DOMPurify, he) { */ var sanitizer = {}; + sanitizer.whitelist = [{ + // highlight tag open + pre: '<em class="search-highlight">', + post: '<em class="search-highlight">' + }, { + // highlight tag close + pre: '</em>', + post: '</em>' + }]; + /** * Adds html line breaks to a plaintext with line breaks (incl carriage return) * @@ -55,16 +65,24 @@ define(['DOMPurify', 'he'], function (DOMPurify, he) { }; /** - * Runs a given dirty body through he, thereby encoding everything - * as HTML entities. - * - * @param {string} dirtyBody The unsanitized string - * @return {string} Safe-to-display HTML string - */ + * Runs a given dirty body through he, thereby encoding everything + * as HTML entities. + * + * @param {string} dirtyBody The unsanitized string + * @return {string} Safe-to-display HTML string + */ sanitizer.purifyText = function (dirtyBody) { - return he.encode(dirtyBody, { + var escapedBody = he.encode(dirtyBody, { encodeEverything: true }); + + this.whitelist.forEach(function(entry) { + while (escapedBody.indexOf(entry.pre) > -1) { + escapedBody = escapedBody.replace(entry.pre, entry.post); + } + }); + + return escapedBody; }; /** diff --git a/web-ui/app/js/mail_list/ui/mail_list.js b/web-ui/app/js/mail_list/ui/mail_list.js index 18d36049..0f6c4fb5 100644 --- a/web-ui/app/js/mail_list/ui/mail_list.js +++ b/web-ui/app/js/mail_list/ui/mail_list.js @@ -81,7 +81,6 @@ define( this.renderMails = function (mails) { _.each(mails, this.appendMail, this); this.trigger(document, events.search.highlightResults, {where: '#mail-list'}); - this.trigger(document, events.search.highlightResults, {where: '.bodyArea'}); this.trigger(document, events.search.highlightResults, {where: '.subjectArea'}); this.trigger(document, events.search.highlightResults, {where: '.msg-header .recipients'}); }; diff --git a/web-ui/app/js/mail_view/ui/mail_view.js b/web-ui/app/js/mail_view/ui/mail_view.js index dfc57585..d952fed7 100644 --- a/web-ui/app/js/mail_view/ui/mail_view.js +++ b/web-ui/app/js/mail_view/ui/mail_view.js @@ -71,9 +71,52 @@ define( attachments: attachments })); - this.$node.find('.bodyArea').html(viewHelpers.formatMailBody(data.mail)); + var $iframe = $("#read-sandbox"); + var iframe = $iframe[0]; + + var content = viewHelpers.formatMailBody(data.mail); + + iframe.onload = function() { + // use iframe-resizer to dynamically adapt iframe size to its content + var config = { + resizedCallback: scaleToFit, + checkOrigin: false + }; + $iframe.iFrameResize(config); + + // transform scale iframe to fit container width + // necessary if iframe is wider than container + function scaleToFit() { + var parentWidth = $iframe.parent().width(); + var w = $iframe.width(); + var scale = 'none'; + + // only scale html mails + var mail = data.mail; + if (mail && mail.htmlBody && (w > parentWidth)) { + scale = parentWidth / w; + scale = 'scale(' + scale + ',' + scale + ')'; + } + + $iframe.css({ + '-webkit-transform-origin': '0 0', + '-moz-transform-origin': '0 0', + '-ms-transform-origin': '0 0', + 'transform-origin': '0 0', + '-webkit-transform': scale, + '-moz-transform': scale, + '-ms-transform': scale, + 'transform': scale + }); + } + + iframe.contentWindow.postMessage({ + html: content + }, '*'); + }; + + - this.trigger(document, events.search.highlightResults, {where: '.bodyArea'}); this.trigger(document, events.search.highlightResults, {where: '.subjectArea'}); this.trigger(document, events.search.highlightResults, {where: '.msg-header .recipients'}); this.trigger(document, events.ui.replyBox.showReplyContainer); @@ -214,9 +257,17 @@ define( this.trigger(events.mail.want, {mail: this.attr.ident, caller: this}); }; + this.highlightMailContent = function (event, data) { + // we can't directly manipulate the iFrame to highlight the content + // so we need to take an indirection where we directly manipulate + // the mail content to accomodate the highlighting + this.trigger(document, events.mail.highlightMailContent, data); + }; + this.after('initialize', function () { - this.on(this, events.mail.here, this.displayMail); this.on(this, events.mail.notFound, this.openNoMessageSelectedPane); + this.on(this, events.mail.here, this.highlightMailContent); + this.on(document, events.mail.display, this.displayMail); this.on(document, events.dispatchers.rightPane.clear, this.teardown); this.on(document, events.mail.tags.updated, this.tagsUpdated); this.on(document, events.mail.deleted, this.mailDeleted); diff --git a/web-ui/app/js/page/events.js b/web-ui/app/js/page/events.js index 7a0dbf9d..ad15e76e 100644 --- a/web-ui/app/js/page/events.js +++ b/web-ui/app/js/page/events.js @@ -121,6 +121,8 @@ define(function () { mail: { here: 'mail:here', want: 'mail:want', + display: 'mail:display', + highlightMailContent: 'mail:highlightMailContent', send: 'mail:send', send_failed: 'mail:send_failed', sent: 'mail:sent', diff --git a/web-ui/app/js/sandbox.js b/web-ui/app/js/sandbox.js new file mode 100644 index 00000000..f9e708d6 --- /dev/null +++ b/web-ui/app/js/sandbox.js @@ -0,0 +1,9 @@ +(function () { + 'use strict'; + + window.onmessage = function (e) { + if (e.data.html) { + document.body.innerHTML = e.data.html; + } + }; +})(); diff --git a/web-ui/app/js/search/results_highlighter.js b/web-ui/app/js/search/results_highlighter.js index 9e3ba167..831be0cd 100644 --- a/web-ui/app/js/search/results_highlighter.js +++ b/web-ui/app/js/search/results_highlighter.js @@ -40,6 +40,7 @@ define( var domIdent = data.where; if(this.attr.keywords) { _.each(this.attr.keywords, function (keyword) { + keyword = escapeRegExp(keyword); $(domIdent).highlightRegex(new RegExp(keyword, 'i'), { tagType: 'em', className: 'search-highlight' @@ -57,12 +58,40 @@ define( }); }; + this.highlightString = function (string) { + _.each(this.attr.keywords, function (keyword) { + keyword = escapeRegExp(keyword); + var regex = new RegExp('(' + keyword + ')', 'ig'); + string = string.replace(regex, '<em class="search-highlight">$1</em>'); + }); + return string; + }; + + /* + * Alter data.mail.textPlainBody to highlight each of this.attr.keywords + * and pass it back to the mail_view when done + */ + this.highlightMailContent = function(ev, data){ + var mail = data.mail; + mail.textPlainBody = this.highlightString(mail.textPlainBody); + this.trigger(document, events.mail.display, data); + }; + + /* + * Escapes the special charaters used regular expressions that + * would cause problems with strings in the RegExp constructor + */ + function escapeRegExp(string){ + return string.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); + } + this.after('initialize', function () { this.on(document, events.search.perform, this.getKeywordsSearch); this.on(document, events.ui.tag.select, this.clearHighlights); this.on(document, events.search.resetHighlight, this.clearHighlights); this.on(document, events.search.highlightResults, this.highlightResults); + this.on(document, events.mail.highlightMailContent, this.highlightMailContent); }); } }); diff --git a/web-ui/app/js/user_alerts/ui/user_alerts.js b/web-ui/app/js/user_alerts/ui/user_alerts.js index b02762aa..e944a7a5 100644 --- a/web-ui/app/js/user_alerts/ui/user_alerts.js +++ b/web-ui/app/js/user_alerts/ui/user_alerts.js @@ -32,20 +32,26 @@ define( dismissTimeout: 3000 }); - this.render = function (message) { + this.render = function(message) { this.$node.html(templates.userAlerts.message(message)); this.show(); setTimeout(this.hide.bind(this), this.attr.dismissTimeout); }; - this.displayMessage = function (ev, data) { - this.render({ message: {content: data.message, class: (data.class || 'success')}}); + this.displayMessage = function(ev, data) { + this.render({ + message: { + content: data.message, + class: 'message-panel__growl--' + (data.class || 'success') + } + }); }; - this.after('initialize', function () { + this.after('initialize', function() { this.on(document, events.ui.userAlerts.displayMessage, this.displayMessage); }); } } ); + diff --git a/web-ui/app/sandbox.html b/web-ui/app/sandbox.html new file mode 100644 index 00000000..13a86f25 --- /dev/null +++ b/web-ui/app/sandbox.html @@ -0,0 +1,17 @@ +<!DOCTYPE html> +<html> + +<head> + <meta charset="utf-8"> + <link href="css/opensans.css" rel="stylesheet" type="text/css"> + <link href="css/sandbox.css" rel="stylesheet" type="text/css"> + + <!--usemin_start--> + <script src="js/sandbox.js"></script> + <script src="bower_components/iframe-resizer/js/iframeResizer.contentWindow.min.js"></script> + <!--usemin_end--> +</head> + +<body></body> + +</html> diff --git a/web-ui/app/scss/_alerts.scss b/web-ui/app/scss/_alerts.scss deleted file mode 100644 index cfb31cbe..00000000 --- a/web-ui/app/scss/_alerts.scss +++ /dev/null @@ -1,23 +0,0 @@ -.message-panel { - width: 100%; - margin: 10px auto; - position: fixed; - z-index: 10000; - text-align: center; - span{ - padding: 5px 60px; - &.success { - background: $warning; - color: darken($warning, 50%); - border: 1px solid darken($warning, 10%); - @include box-shadow(1px 1px 3px darken($warning, 60%)); - } - &.error { - font-weight: bold; - color: white; - background: $error; - border: 1px solid darken($error, 10%); - @include box-shadow(1px 1px 3px darken($error, 60%)); - } - } -} diff --git a/web-ui/app/scss/_mascot.scss b/web-ui/app/scss/_mascot.scss deleted file mode 100644 index 74279063..00000000 --- a/web-ui/app/scss/_mascot.scss +++ /dev/null @@ -1,47 +0,0 @@ -#no-message-selected-pane { - position: absolute; - top: 0; - left: 0; - width: 100%; - height: 100vh; - - z-index: -100; - background: $contrast; - padding: 30px; - vertical-align:middle; - text-align:center; - -webkit-transform: translate3d(0, 0, 0); - &:before{ - content: ''; - display: inline-block; - height: 100%; - vertical-align: middle; - margin-right: -0.25em; - } - .scene{ - display:inline-block; - vertical-align:middle; - } - - .text{ - color:$medium_dark_grey; - margin-bottom: 40px; - } -} - -#no-mails-available-pane { - text-align: center; - line-height: 100vh; - margin-top: -130px; - - .scene{ - display:inline-block; - vertical-align:middle; - } - - .text{ - color:$medium_dark_grey; - margin-bottom: 40px; - } - -} diff --git a/web-ui/app/scss/_mixins.scss b/web-ui/app/scss/_mixins.scss index 5bb84105..4583c55d 100644 --- a/web-ui/app/scss/_mixins.scss +++ b/web-ui/app/scss/_mixins.scss @@ -1,4 +1,4 @@ -@import 'colors'; +@import 'base/colors'; // SHARED MIXINS @mixin btn-transition { diff --git a/web-ui/app/scss/_others.scss b/web-ui/app/scss/_others.scss new file mode 100644 index 00000000..e73ed33d --- /dev/null +++ b/web-ui/app/scss/_others.scss @@ -0,0 +1,7 @@ +.no-padding { + padding: 0; +} + +.text-right { + text-align: right; +} diff --git a/web-ui/app/scss/_read.scss b/web-ui/app/scss/_read.scss index 2c079408..236a7c7c 100644 --- a/web-ui/app/scss/_read.scss +++ b/web-ui/app/scss/_read.scss @@ -66,6 +66,13 @@ .bodyArea { padding: 10px 30px 0 30px; + box-sizing: border-box; + + iframe { + box-sizing: inherit; + border: none; + width: 100%; + } } .attachmentsAreaWrap { diff --git a/web-ui/app/scss/_colors.scss b/web-ui/app/scss/base/_colors.scss index de5f76b3..de5f76b3 100644 --- a/web-ui/app/scss/_colors.scss +++ b/web-ui/app/scss/base/_colors.scss diff --git a/web-ui/app/scss/opensans.scss b/web-ui/app/scss/base/_fonts.scss index ada6a025..ada6a025 100644 --- a/web-ui/app/scss/opensans.scss +++ b/web-ui/app/scss/base/_fonts.scss diff --git a/web-ui/app/scss/base/_scaffolding.scss b/web-ui/app/scss/base/_scaffolding.scss new file mode 100644 index 00000000..b8b5fa3b --- /dev/null +++ b/web-ui/app/scss/base/_scaffolding.scss @@ -0,0 +1,10 @@ +html { + height: 100% ; +} + +body { + min-height: 100% ; + overflow: hidden; + background: $white; +} + diff --git a/web-ui/app/scss/layout/_message-panel.scss b/web-ui/app/scss/layout/_message-panel.scss new file mode 100644 index 00000000..e311a9bf --- /dev/null +++ b/web-ui/app/scss/layout/_message-panel.scss @@ -0,0 +1,9 @@ +message-panel-container { + overflow: hidden; + position: fixed; + top: 0; + width: 100% ; + position: relative; + margin-bottom: 0; +} + diff --git a/web-ui/app/scss/main.scss b/web-ui/app/scss/main.scss deleted file mode 100644 index b582a5d5..00000000 --- a/web-ui/app/scss/main.scss +++ /dev/null @@ -1,37 +0,0 @@ -@import "compass/css3"; -@import "reset"; -@import "foundation"; -@import "colors"; -@import "mixins"; -@import "alerts"; -@import "read"; -@import "reply"; -@import "compose"; -@import "security"; -@import "mascot"; -@import "styles"; - -html { - height:100%; -} -body { - min-height:100%; - overflow: hidden; - background: $white; -} -header#main { - overflow: hidden; - position: fixed; - top: 0; - width: 100%; - position: relative; - margin-bottom: 0; -} - -.no-padding { - padding: 0; -} - -.text-right { - text-align: right; -} diff --git a/web-ui/app/scss/mixins/_position-helpers.scss b/web-ui/app/scss/mixins/_position-helpers.scss new file mode 100644 index 00000000..254bfc6c --- /dev/null +++ b/web-ui/app/scss/mixins/_position-helpers.scss @@ -0,0 +1,9 @@ +@mixin absolute-center-unknown-height-width() { + margin: auto; + position: absolute; + left: 50%; + top: 50%; + -ms-transform: translate(-50%, -50%); + -webkit-transform: translate(-50%, -50%); + transform: translate(-50%, -50%); +} diff --git a/web-ui/app/scss/sandbox.scss b/web-ui/app/scss/sandbox.scss new file mode 100644 index 00000000..3cb4c441 --- /dev/null +++ b/web-ui/app/scss/sandbox.scss @@ -0,0 +1,20 @@ +$search-highlight: #FFEF29; + +body { + font-family: "Open Sans", "Microsoft YaHei", "Hiragino Sans GB", "Hiragino Sans GB W3", "微软雅黑", "Helvetica Neue", Arial, sans-serif; + font-size: 13px; + line-height: 1.2em; + background: white; + color: #333; + padding: 0; + margin: 0; + font-weight: normal; + -webkit-font-smoothing: antialiased; + font-style: normal; + box-sizing: border-box; + word-wrap: break-word; +} + +.search-highlight { + background-color: $search-highlight; +} diff --git a/web-ui/app/scss/style.scss b/web-ui/app/scss/style.scss new file mode 100644 index 00000000..d27b3031 --- /dev/null +++ b/web-ui/app/scss/style.scss @@ -0,0 +1,35 @@ +// vendor stylesheets and resets +@import "vendor/reset"; +@import "compass/css3"; +@import "vendor/foundation"; + +// basic configuration +@import "base/fonts"; +@import "base/colors"; +@import "base/scaffolding"; + +// mixins +@import "mixins/position-helpers"; +@import "mixins"; + +// templates +@import "templates/no-content-placeholder"; + +// layout +@import "layout/message-panel"; + +// views +@import "views/message-panel"; +@import "views/no-message-selected"; +@import "views/no-mails-available"; + +// misc stuff +@import "others"; + +// TODO +@import "compose"; +@import "read"; +@import "reply"; +@import "security"; +@import "styles"; + diff --git a/web-ui/app/scss/templates/_no-content-placeholder.scss b/web-ui/app/scss/templates/_no-content-placeholder.scss new file mode 100644 index 00000000..c6807011 --- /dev/null +++ b/web-ui/app/scss/templates/_no-content-placeholder.scss @@ -0,0 +1,5 @@ +.no-content-placeholder { + @include absolute-center-unknown-height-width; + + color: $medium_dark_grey; +} diff --git a/web-ui/app/scss/foundation.scss b/web-ui/app/scss/vendor/_foundation.scss index 7918cf26..7918cf26 100644 --- a/web-ui/app/scss/foundation.scss +++ b/web-ui/app/scss/vendor/_foundation.scss diff --git a/web-ui/app/scss/reset.scss b/web-ui/app/scss/vendor/_reset.scss index 55f8d054..55f8d054 100644 --- a/web-ui/app/scss/reset.scss +++ b/web-ui/app/scss/vendor/_reset.scss diff --git a/web-ui/app/scss/views/_message-panel.scss b/web-ui/app/scss/views/_message-panel.scss new file mode 100644 index 00000000..4a0a7a6b --- /dev/null +++ b/web-ui/app/scss/views/_message-panel.scss @@ -0,0 +1,26 @@ +.message-panel { + width: 100%; + margin: 10px auto; + position: fixed; + z-index: 10000; + text-align: center; + + &__growl { + padding: 5px 60px; + + &--success { + background: $warning; + color: darken($warning, 50%); + border: 1px solid darken($warning, 10%); + @include box-shadow(1px 1px 3px darken($warning, 60%)); + } + + &--error { + font-weight: bold; + color: white; + background: $error; + border: 1px solid darken($error, 10%); + @include box-shadow(1px 1px 3px darken($error, 60%)); + } + } +} diff --git a/web-ui/app/scss/views/_no-mails-available.scss b/web-ui/app/scss/views/_no-mails-available.scss new file mode 100644 index 00000000..bf5d256a --- /dev/null +++ b/web-ui/app/scss/views/_no-mails-available.scss @@ -0,0 +1,3 @@ +.no-mails-available-pane { + @extend .no-content-placeholder; +} diff --git a/web-ui/app/scss/views/_no-message-selected.scss b/web-ui/app/scss/views/_no-message-selected.scss new file mode 100644 index 00000000..0e367bf2 --- /dev/null +++ b/web-ui/app/scss/views/_no-message-selected.scss @@ -0,0 +1,14 @@ +.no-message-selected-pane { + background: $contrast; + position: absolute; + top: 0; + left: 0; + width: 100%; + height: 100%; + + &__text { + @extend .no-content-placeholder; + + margin-bottom: 40px; // aligns label with "no results for XYZ" + } +} diff --git a/web-ui/app/templates/compose/no_mails_available.hbs b/web-ui/app/templates/compose/no_mails_available.hbs index 6388d7db..147f3533 100644 --- a/web-ui/app/templates/compose/no_mails_available.hbs +++ b/web-ui/app/templates/compose/no_mails_available.hbs @@ -1,7 +1,7 @@ -<div class="scene"> +<div class="no-mails-available-pane"> {{#if forSearch }} - <div class="text">{{t 'NO RESULTS FOR'}}: '{{ forSearch }}'.</div> + {{t 'NO RESULTS FOR'}}: '{{ forSearch }}'. {{else}} - <div class="text">{{t 'NO EMAILS IN'}} '{{ tag }}'.</div> + {{t 'NO EMAILS IN'}} '{{ tag }}'. {{/if}} </div> diff --git a/web-ui/app/templates/compose/no_message_selected.hbs b/web-ui/app/templates/compose/no_message_selected.hbs index 0442192d..71aa6267 100644 --- a/web-ui/app/templates/compose/no_message_selected.hbs +++ b/web-ui/app/templates/compose/no_message_selected.hbs @@ -1,3 +1,3 @@ -<div class="scene"> - <div class="text">{{t 'NOTHING SELECTED'}}.</div> +<div class="no-message-selected-pane"> + <div class="no-message-selected-pane__text">{{t 'NOTHING SELECTED'}}.</div> </div> diff --git a/web-ui/app/templates/mails/full_view.hbs b/web-ui/app/templates/mails/full_view.hbs index f9ec084a..3ff109e9 100644 --- a/web-ui/app/templates/mails/full_view.hbs +++ b/web-ui/app/templates/mails/full_view.hbs @@ -64,6 +64,7 @@ </header> <div class="bodyArea column large-12"> + <iframe id="read-sandbox" sandbox="allow-popups allow-scripts" src="sandbox/sandbox.html" scrolling="no"></iframe> </div> {{#if attachments}} diff --git a/web-ui/app/templates/search/search_trigger.hbs b/web-ui/app/templates/search/search_trigger.hbs index 2261d154..f2c410a4 100644 --- a/web-ui/app/templates/search/search_trigger.hbs +++ b/web-ui/app/templates/search/search_trigger.hbs @@ -1,3 +1,3 @@ <form> - <input type="search" placeholder="{{t 'search-placeholder'}}"></input> + <input type="search" pattern="[a-zA-Z0-9\s]{3,}" placeholder="{{t 'search-placeholder'}}"></input> </form> diff --git a/web-ui/app/templates/user_alerts/message.hbs b/web-ui/app/templates/user_alerts/message.hbs index 3a0055eb..abba1f91 100644 --- a/web-ui/app/templates/user_alerts/message.hbs +++ b/web-ui/app/templates/user_alerts/message.hbs @@ -1 +1 @@ -<span class="{{ message.class }}">{{ message.content }}</span> +<span class="message-panel__growl {{ message.class }}">{{ message.content }}</span> |