From 04cf441c5ae18400c6b4865b0b37a71718dc9d46 Mon Sep 17 00:00:00 2001 From: Ola Bini Date: Thu, 31 Jul 2014 19:29:33 -0300 Subject: Add web-ui based on previous code --- web-ui/app/js/dispatchers/left_pane_dispatcher.js | 51 + .../app/js/dispatchers/middle_pane_dispatcher.js | 36 + web-ui/app/js/dispatchers/right_pane_dispatcher.js | 93 ++ web-ui/app/js/foundation/off_canvas.js | 21 + web-ui/app/js/helpers/contenttype.js | 164 +++ web-ui/app/js/helpers/iterator.js | 43 + web-ui/app/js/helpers/triggering.js | 13 + web-ui/app/js/helpers/view_helper.js | 145 +++ web-ui/app/js/lib/highlightRegex.js | 127 +++ web-ui/app/js/lib/html-sanitizer.js | 1064 ++++++++++++++++++++ web-ui/app/js/lib/html4-defs.js | 640 ++++++++++++ web-ui/app/js/lib/html_whitelister.js | 70 ++ web-ui/app/js/mail_list/domain/refresher.js | 25 + web-ui/app/js/mail_list/ui/mail_item_factory.js | 49 + .../app/js/mail_list/ui/mail_items/draft_item.js | 55 + .../mail_list/ui/mail_items/generic_mail_item.js | 97 ++ web-ui/app/js/mail_list/ui/mail_items/mail_item.js | 63 ++ web-ui/app/js/mail_list/ui/mail_items/sent_item.js | 62 ++ web-ui/app/js/mail_list/ui/mail_list.js | 185 ++++ .../app/js/mail_list_actions/ui/compose_trigger.js | 31 + .../js/mail_list_actions/ui/delete_many_trigger.js | 31 + .../js/mail_list_actions/ui/mail_list_actions.js | 50 + .../mail_list_actions/ui/mark_as_unread_trigger.js | 31 + .../ui/mark_many_as_read_trigger.js | 31 + .../js/mail_list_actions/ui/pagination_trigger.js | 50 + .../app/js/mail_list_actions/ui/refresh_trigger.js | 28 + .../ui/toggle_check_all_trigger.js | 33 + web-ui/app/js/mail_view/data/mail_builder.js | 79 ++ web-ui/app/js/mail_view/data/mail_sender.js | 74 ++ web-ui/app/js/mail_view/ui/compose_box.js | 63 ++ web-ui/app/js/mail_view/ui/draft_box.js | 74 ++ web-ui/app/js/mail_view/ui/draft_save_status.js | 25 + web-ui/app/js/mail_view/ui/forward_box.js | 66 ++ web-ui/app/js/mail_view/ui/mail_actions.js | 70 ++ web-ui/app/js/mail_view/ui/mail_view.js | 242 +++++ .../js/mail_view/ui/no_message_selected_pane.js | 25 + web-ui/app/js/mail_view/ui/recipients/recipient.js | 36 + .../app/js/mail_view/ui/recipients/recipients.js | 127 +++ .../js/mail_view/ui/recipients/recipients_input.js | 140 +++ .../mail_view/ui/recipients/recipients_iterator.js | 41 + web-ui/app/js/mail_view/ui/reply_box.js | 102 ++ web-ui/app/js/mail_view/ui/reply_section.js | 102 ++ web-ui/app/js/mail_view/ui/send_button.js | 85 ++ web-ui/app/js/main.js | 58 ++ web-ui/app/js/mixins/with_compose_inline.js | 63 ++ .../app/js/mixins/with_enable_disable_on_event.js | 34 + web-ui/app/js/mixins/with_hide_and_show.js | 14 + web-ui/app/js/mixins/with_mail_edit_base.js | 182 ++++ web-ui/app/js/monkey_patching/all.js | 1 + web-ui/app/js/monkey_patching/array.js | 11 + web-ui/app/js/monkey_patching/post_message.js | 16 + web-ui/app/js/page/default.js | 87 ++ web-ui/app/js/page/events.js | 168 ++++ web-ui/app/js/page/pane_contract_expand.js | 34 + web-ui/app/js/page/router.js | 51 + web-ui/app/js/page/router/url_params.js | 40 + web-ui/app/js/search/results_highlighter.js | 53 + web-ui/app/js/search/search_trigger.js | 68 ++ web-ui/app/js/services/delete_service.js | 43 + web-ui/app/js/services/mail_service.js | 254 +++++ web-ui/app/js/services/model/mail.js | 147 +++ web-ui/app/js/tags/data/tags.js | 42 + web-ui/app/js/tags/ui/tag.js | 94 ++ web-ui/app/js/tags/ui/tag_base.js | 24 + web-ui/app/js/tags/ui/tag_list.js | 93 ++ web-ui/app/js/tags/ui/tag_shortcut.js | 68 ++ web-ui/app/js/user_alerts/ui/user_alerts.js | 35 + web-ui/app/js/views/i18n.js | 18 + web-ui/app/js/views/recipientListFormatter.js | 16 + web-ui/app/js/views/templates.js | 46 + 70 files changed, 6399 insertions(+) create mode 100644 web-ui/app/js/dispatchers/left_pane_dispatcher.js create mode 100644 web-ui/app/js/dispatchers/middle_pane_dispatcher.js create mode 100644 web-ui/app/js/dispatchers/right_pane_dispatcher.js create mode 100644 web-ui/app/js/foundation/off_canvas.js create mode 100644 web-ui/app/js/helpers/contenttype.js create mode 100644 web-ui/app/js/helpers/iterator.js create mode 100644 web-ui/app/js/helpers/triggering.js create mode 100644 web-ui/app/js/helpers/view_helper.js create mode 100644 web-ui/app/js/lib/highlightRegex.js create mode 100644 web-ui/app/js/lib/html-sanitizer.js create mode 100644 web-ui/app/js/lib/html4-defs.js create mode 100644 web-ui/app/js/lib/html_whitelister.js create mode 100644 web-ui/app/js/mail_list/domain/refresher.js create mode 100644 web-ui/app/js/mail_list/ui/mail_item_factory.js create mode 100644 web-ui/app/js/mail_list/ui/mail_items/draft_item.js create mode 100644 web-ui/app/js/mail_list/ui/mail_items/generic_mail_item.js create mode 100644 web-ui/app/js/mail_list/ui/mail_items/mail_item.js create mode 100644 web-ui/app/js/mail_list/ui/mail_items/sent_item.js create mode 100644 web-ui/app/js/mail_list/ui/mail_list.js create mode 100644 web-ui/app/js/mail_list_actions/ui/compose_trigger.js create mode 100644 web-ui/app/js/mail_list_actions/ui/delete_many_trigger.js create mode 100644 web-ui/app/js/mail_list_actions/ui/mail_list_actions.js create mode 100644 web-ui/app/js/mail_list_actions/ui/mark_as_unread_trigger.js create mode 100644 web-ui/app/js/mail_list_actions/ui/mark_many_as_read_trigger.js create mode 100644 web-ui/app/js/mail_list_actions/ui/pagination_trigger.js create mode 100644 web-ui/app/js/mail_list_actions/ui/refresh_trigger.js create mode 100644 web-ui/app/js/mail_list_actions/ui/toggle_check_all_trigger.js create mode 100644 web-ui/app/js/mail_view/data/mail_builder.js create mode 100644 web-ui/app/js/mail_view/data/mail_sender.js create mode 100644 web-ui/app/js/mail_view/ui/compose_box.js create mode 100644 web-ui/app/js/mail_view/ui/draft_box.js create mode 100644 web-ui/app/js/mail_view/ui/draft_save_status.js create mode 100644 web-ui/app/js/mail_view/ui/forward_box.js create mode 100644 web-ui/app/js/mail_view/ui/mail_actions.js create mode 100644 web-ui/app/js/mail_view/ui/mail_view.js create mode 100644 web-ui/app/js/mail_view/ui/no_message_selected_pane.js create mode 100644 web-ui/app/js/mail_view/ui/recipients/recipient.js create mode 100644 web-ui/app/js/mail_view/ui/recipients/recipients.js create mode 100644 web-ui/app/js/mail_view/ui/recipients/recipients_input.js create mode 100644 web-ui/app/js/mail_view/ui/recipients/recipients_iterator.js create mode 100644 web-ui/app/js/mail_view/ui/reply_box.js create mode 100644 web-ui/app/js/mail_view/ui/reply_section.js create mode 100644 web-ui/app/js/mail_view/ui/send_button.js create mode 100644 web-ui/app/js/main.js create mode 100644 web-ui/app/js/mixins/with_compose_inline.js create mode 100644 web-ui/app/js/mixins/with_enable_disable_on_event.js create mode 100644 web-ui/app/js/mixins/with_hide_and_show.js create mode 100644 web-ui/app/js/mixins/with_mail_edit_base.js create mode 100644 web-ui/app/js/monkey_patching/all.js create mode 100644 web-ui/app/js/monkey_patching/array.js create mode 100644 web-ui/app/js/monkey_patching/post_message.js create mode 100644 web-ui/app/js/page/default.js create mode 100644 web-ui/app/js/page/events.js create mode 100644 web-ui/app/js/page/pane_contract_expand.js create mode 100644 web-ui/app/js/page/router.js create mode 100644 web-ui/app/js/page/router/url_params.js create mode 100644 web-ui/app/js/search/results_highlighter.js create mode 100644 web-ui/app/js/search/search_trigger.js create mode 100644 web-ui/app/js/services/delete_service.js create mode 100644 web-ui/app/js/services/mail_service.js create mode 100644 web-ui/app/js/services/model/mail.js create mode 100644 web-ui/app/js/tags/data/tags.js create mode 100644 web-ui/app/js/tags/ui/tag.js create mode 100644 web-ui/app/js/tags/ui/tag_base.js create mode 100644 web-ui/app/js/tags/ui/tag_list.js create mode 100644 web-ui/app/js/tags/ui/tag_shortcut.js create mode 100644 web-ui/app/js/user_alerts/ui/user_alerts.js create mode 100644 web-ui/app/js/views/i18n.js create mode 100644 web-ui/app/js/views/recipientListFormatter.js create mode 100644 web-ui/app/js/views/templates.js (limited to 'web-ui/app/js') diff --git a/web-ui/app/js/dispatchers/left_pane_dispatcher.js b/web-ui/app/js/dispatchers/left_pane_dispatcher.js new file mode 100644 index 00000000..8fd2b81d --- /dev/null +++ b/web-ui/app/js/dispatchers/left_pane_dispatcher.js @@ -0,0 +1,51 @@ +define( + [ + 'flight/lib/component', + 'page/router/url_params', + 'page/events' + ], + + function(defineComponent, urlParams, events) { + 'use strict'; + + return defineComponent(leftPaneDispatcher); + + function leftPaneDispatcher() { + var initialized = false; + + this.refreshTagList = function () { + this.trigger(document, events.tags.want, { caller: this.$node }); + }; + + this.loadTags = function (ev, data) { + this.trigger(document, events.ui.tagList.load, data); + }; + + this.selectTag = function (ev, data) { + var tag = (data && data.tag) || urlParams.getTag(); + this.trigger(document, events.ui.tag.select, { tag: tag }); + }; + + this.pushUrlState = function (ev, data) { + if (initialized) { + this.trigger(document, events.router.pushState, data); + } + initialized = true; + + if (data.skipMailListRefresh) { + return; + } + + this.trigger(document, events.ui.mails.fetchByTag, data); + }; + + this.after('initialize', function () { + this.on(this.$node, events.tags.received, this.loadTags); + this.on(document, events.dispatchers.tags.refreshTagList, this.refreshTagList); + this.on(document, events.ui.tags.loaded, this.selectTag); + this.on(document, events.ui.tag.selected, this.pushUrlState); + this.trigger(document, events.tags.want, { caller: this.$node } ); + }); + } + } +); diff --git a/web-ui/app/js/dispatchers/middle_pane_dispatcher.js b/web-ui/app/js/dispatchers/middle_pane_dispatcher.js new file mode 100644 index 00000000..26c32235 --- /dev/null +++ b/web-ui/app/js/dispatchers/middle_pane_dispatcher.js @@ -0,0 +1,36 @@ +define(['flight/lib/component', 'page/events', 'helpers/triggering'], function(defineComponent, events, triggering) { + 'use strict'; + + return defineComponent(function() { + this.defaultAttrs({ + middlePane: '#middle-pane' + }); + + this.refreshMailList = function (ev, data) { + this.trigger(document, events.ui.mails.fetchByTag, data); + }; + + this.cleanSelected = function(ev, data) { + this.trigger(document, events.ui.mails.cleanSelected); + }; + + this.resetScroll = function() { + this.select('middlePane').scrollTop(0); + }; + + this.updateMiddlePaneHeight = function() { + var vh = $(window).height(); + var top = $("#main").outerHeight() + $("#top-pane").outerHeight(); + this.select('middlePane').css({height: (vh - top) + 'px'}); + }; + + this.after('initialize', function () { + this.on(document, events.dispatchers.middlePane.refreshMailList, this.refreshMailList); + this.on(document, events.dispatchers.middlePane.cleanSelected, this.cleanSelected); + this.on(document, events.dispatchers.middlePane.resetScroll, this.resetScroll); + + this.updateMiddlePaneHeight(); + $(window).on('resize', this.updateMiddlePaneHeight.bind(this)); + }); + }); +}); diff --git a/web-ui/app/js/dispatchers/right_pane_dispatcher.js b/web-ui/app/js/dispatchers/right_pane_dispatcher.js new file mode 100644 index 00000000..3e62e581 --- /dev/null +++ b/web-ui/app/js/dispatchers/right_pane_dispatcher.js @@ -0,0 +1,93 @@ +/*global Smail */ + +define( + [ + 'flight/lib/component', + 'mail_view/ui/compose_box', + 'mail_view/ui/mail_view', + 'mail_view/ui/reply_section', + 'mail_view/ui/draft_box', + 'mail_view/ui/no_message_selected_pane', + 'page/events' + ], + + function(defineComponent, ComposeBox, MailView, ReplySection, DraftBox, NoMessageSelectedPane, events) { + 'use strict'; + + return defineComponent(rightPaneDispatcher); + + function rightPaneDispatcher() { + this.defaultAttrs({ + rightPane: '#right-pane', + composeBox: 'compose-box', + mailView: 'mail-view', + noMessageSelectedPane: 'no-message-selected-pane', + replySection: 'reply-section', + draftBox: 'draft-box', + currentTag: '' + }); + + this.createAndAttach = function(newContainer) { + var stage = $('
', { id: newContainer }); + this.select('rightPane').append(stage); + return stage; + }; + + this.reset = function (newContainer) { + this.trigger(document, events.dispatchers.rightPane.clear); + this.select('rightPane').empty(); + var stage = this.createAndAttach(newContainer); + return stage; + }; + + this.openComposeBox = function() { + var stage = this.reset(this.attr.composeBox); + ComposeBox.attachTo(stage, {currentTag: this.attr.currentTag}); + }; + + this.openMail = function(ev, data) { + var stage = this.reset(this.attr.mailView); + MailView.attachTo(stage, data); + + var replySectionContainer = this.createAndAttach(this.attr.replySection); + ReplySection.attachTo(replySectionContainer, { ident: data.ident }); + }; + + this.initializeNoMessageSelectedPane = function () { + var stage = this.reset(this.attr.noMessageSelectedPane); + NoMessageSelectedPane.attachTo(stage); + this.trigger(document, events.dispatchers.middlePane.cleanSelected); + }; + + this.openNoMessageSelectedPane = function(ev, data) { + this.initializeNoMessageSelectedPane(); + + this.trigger(document, events.router.pushState, { tag: this.attr.currentTag, isDisplayNoMessageSelected: true }); + }; + + this.openDraft = function (ev, data) { + var stage = this.reset(this.attr.draftBox); + DraftBox.attachTo(stage, { mailIdent: data.ident, currentTag: this.attr.currentTag }); + }; + + this.selectTag = function(ev, data) { + this.trigger(document, events.ui.tags.loaded, {tag: data.tag}); + }; + + this.saveTag = function(ev, data) { + this.attr.currentTag = data.tag; + }; + + this.after('initialize', function () { + this.on(document, events.dispatchers.rightPane.openComposeBox, this.openComposeBox); + this.on(document, events.dispatchers.rightPane.openDraft, this.openDraft); + this.on(document, events.ui.mail.open, this.openMail); + this.on(document, events.dispatchers.rightPane.openNoMessageSelected, this.openNoMessageSelectedPane); + this.on(document, events.dispatchers.rightPane.selectTag, this.selectTag); + this.on(document, events.ui.tag.selected, this.saveTag); + this.on(document, events.dispatchers.rightPane.openNoMessageSelectedWithoutPushState, this.initializeNoMessageSelectedPane); + this.initializeNoMessageSelectedPane(); + }); + } + } +); diff --git a/web-ui/app/js/foundation/off_canvas.js b/web-ui/app/js/foundation/off_canvas.js new file mode 100644 index 00000000..7dfd6f34 --- /dev/null +++ b/web-ui/app/js/foundation/off_canvas.js @@ -0,0 +1,21 @@ +define(['flight/lib/component', 'page/events'], function (defineComponent, events) { + + return defineComponent(function() { + + this.toggleSlider = function (){ + $('.off-canvas-wrap').foundation('offcanvas', 'toggle', 'move-right'); + }; + + this.closeSlider = function (){ + if ($('.off-canvas-wrap').attr('class').indexOf('move-right') > -1) { + $('.off-canvas-wrap').foundation('offcanvas', 'toggle', 'move-right'); + } + }; + + this.after('initialize', function () { + this.on($('.left-off-canvas-toggle'), 'click', this.toggleSlider); + this.on($('#middle-pane-container'), 'click', this.closeSlider); + this.on($('#right-pane'), 'click', this.closeSlider); + }); + }); +}); diff --git a/web-ui/app/js/helpers/contenttype.js b/web-ui/app/js/helpers/contenttype.js new file mode 100644 index 00000000..81519452 --- /dev/null +++ b/web-ui/app/js/helpers/contenttype.js @@ -0,0 +1,164 @@ +define([], function () { + var exports = {}; + + // Licence: PUBLIC DOMAIN + // Author: Austin Wright + + function MediaType(s, p){ + this.type = ''; + this.params = {}; + if(typeof s=='string'){ + var c = splitQuotedString(s); + this.type = c.shift(); + for(var i=0; i=0){ + offset = [delim,quote].reduce(findNextChar, 1/0); + if(offset===1/0) break; + switch(str[offset]){ + case quote: + // Skip to end of quoted string + while(1){ + offset=str.indexOf(quote, offset+1); + if(offset<0) break; + if(str[offset-1]==='\\') continue; + break; + } + continue; + case delim: + res.push(str.substr(start, offset-start).trim()); + start = ++offset; + break; + } + } + res.push(str.substr(start).trim()); + return res; + } + exports.splitQuotedString = splitQuotedString; + + // Split a list of content types found in an Accept header + // Maybe use it like: splitContentTypes(request.headers.accept).map(parseMedia) + function splitContentTypes(str){ + return splitQuotedString(str, ','); + } + exports.splitContentTypes = splitContentTypes; + + function parseMedia(str){ + var o = new MediaType(str); + if(o.q===undefined) o.q=1; + return o; + } + exports.parseMedia = parseMedia; + + // Pick an ideal representation to send given a list of representations to choose from and the client-preferred list + function select(reps, accept){ + var cr = {q:0}; + var ca = {q:0}; + var cq = 0; + for(var i=0; i=0){ + if(aq*rq>cq){ + ca = a; + cr = r; + cq = ca.q*cr.q; + if(cq===1 && cr.type) return cr; + } + } + } + } + return cr.type&&cr; + } + exports.select = select; + + // Determine if one media type is a subset of another + // If a is a superset of b (b is smaller than a), return 1 + // If b is a superset of a, return -1 + // If they are the exact same, return 0 + // If they are disjoint, return null + function mediaCmp(a, b){ + if(a.type==='*/*' && b.type!=='*/*') return 1; + else if(a.type!=='*/*' && b.type==='*/*') return -1; + var ac = (a.type||'').split('/'); + var bc = (b.type||'').split('/'); + if(ac[0]=='*' && bc[0]!='*') return 1; + if(ac[0]!='*' && bc[0]=='*') return -1; + if(a.type!==b.type) return null; + var ap = a.params || {}; + var bp = b.params || {}; + var ak = Object.keys(ap); + var bk = Object.keys(bp); + if(ak.length < bk.length) return 1; + if(ak.length > bk.length) return -1; + var k = ak.concat(bk).sort(); + var dir = 0; + for(var n in ap){ + if(ap[n] && !bp[n]){ if(dir<0) return null; else dir=1; } + if(!ap[n] && bp[n]){ if(dir>0) return null; else dir=-1; } + } + return dir; + } + exports.mediaCmp = mediaCmp; + + return exports; +}); diff --git a/web-ui/app/js/helpers/iterator.js b/web-ui/app/js/helpers/iterator.js new file mode 100644 index 00000000..9d8358a7 --- /dev/null +++ b/web-ui/app/js/helpers/iterator.js @@ -0,0 +1,43 @@ +define(function () { + + return Iterator; + + function Iterator(elems, startingIndex) { + + this.index = startingIndex || 0; + this.elems = elems; + + this.hasPrevious = function () { + return this.index != 0; + }; + + this.hasNext = function () { + return this.index < this.elems.length - 1; + }; + + this.previous = function () { + return this.elems[--this.index]; + }; + + this.next = function () { + return this.elems[++this.index]; + }; + + this.current = function () { + return this.elems[this.index]; + }; + + this.hasElements = function () { + return this.elems.length > 0; + }; + + this.removeCurrent = function () { + var removed = this.current(), + toRemove = this.index; + + !this.hasNext() && this.index--; + this.elems.remove(toRemove); + return removed; + }; + } +}); \ No newline at end of file diff --git a/web-ui/app/js/helpers/triggering.js b/web-ui/app/js/helpers/triggering.js new file mode 100644 index 00000000..7c8ae136 --- /dev/null +++ b/web-ui/app/js/helpers/triggering.js @@ -0,0 +1,13 @@ +define([], function() { + 'use strict'; + + return function(that, event, data, on) { + return function() { + if(on) { + that.trigger(on, event, data || {}); + } else { + that.trigger(event, data || {}); + } + }; + }; +}); diff --git a/web-ui/app/js/helpers/view_helper.js b/web-ui/app/js/helpers/view_helper.js new file mode 100644 index 00000000..3fa9edc1 --- /dev/null +++ b/web-ui/app/js/helpers/view_helper.js @@ -0,0 +1,145 @@ +define( + [ + 'helpers/contenttype', + 'lib/html_whitelister', + 'views/i18n', + 'quoted-printable/quoted-printable' + ], + function(contentType, htmlWhitelister, i18n_lib, quotedPrintable) { + 'use strict'; + + function formatStatusClasses(ss) { + return _.map(ss, function(s) { + return 'status-' + s; + }).join(' '); + } + + function addParagraphsToPlainText(plainTextBodyPart) { + return _.map(plainTextBodyPart.split('\n'), function (paragraph) { + return '

' + paragraph + '

'; + }).join(''); + } + + function isQuotedPrintableBodyPart (bodyPart) { + return bodyPart.headers['Content-Transfer-Encoding'] && bodyPart.headers['Content-Transfer-Encoding'] === 'quoted-printable'; + } + + function getHtmlContentType (mail) { + return _.find(mail.availableBodyPartsContentType(), function (contentType) { + return contentType.indexOf('text/html') >= 0; + }); + } + + function getSanitizedAndDecodedMailBody (bodyPart) { + var body; + + if (isQuotedPrintableBodyPart(bodyPart)) { + body = quotedPrintable.decode(bodyPart.body); + } else { + body = bodyPart.body; + } + + return htmlWhitelister.sanitize(body, htmlWhitelister.tagPolicy); + } + + function formatMailBody (mail) { + if (mail.isMailMultipartAlternative()) { + var htmlContentType; + + htmlContentType = getHtmlContentType(mail); + + if (htmlContentType) { + return $(getSanitizedAndDecodedMailBody(mail.getMailPartByContentType(htmlContentType))); + } + + return $(addParagraphsToPlainText(mail.getMailMultiParts[0])); + } + + return $(addParagraphsToPlainText(mail.body)); + + /* + var body; + // probably parse MIME parts and ugliness here + // content_type: "multipart/alternative; boundary="----=_Part_1115_17865397.1370312509342"" + var mediaType = new contentType.MediaType(mail.header.content_type); + if(mediaType.type === 'multipart/alternative') { + var parsedBodyParts = getMailMultiParts(mail.body, mediaType); + var selectedBodyPart = getHtmlMailPart(parsedBodyParts) || getPlainTextMailPart(parsedBodyParts) || parsedBodyParts[0]; + body = selectedBodyPart.body; + + if (isQuotedPrintableBodyPart(selectedBodyPart)) { + body = quotedPrintable.decode(body); + } + } else { + body = addParagraphsToPlainText(mail.body); + } + return $(htmlWhitelister.sanitize(body, htmlWhitelister.tagPolicy)); + */ + } + + function moveCaretToEnd(el) { + if (typeof el.selectionStart == "number") { + el.selectionStart = el.selectionEnd = el.value.length; + } else if (typeof el.createTextRange != "undefined") { + el.focus(); + var range = el.createTextRange(); + range.collapse(false); + range.select(); + } + } + + function fixedSizeNumber(num, size) { + var res = num.toString(); + while(res.length < size) { + res = "0" + res; + } + return res; + } + + function getFormattedDate(date){ + var today = createTodayDate(); + if (date.getTime() > today.getTime()) { + return fixedSizeNumber(date.getHours(), 2) + ":" + fixedSizeNumber(date.getMinutes(), 2); + } else { + return "" + date.getFullYear() + "-" + fixedSizeNumber(date.getMonth() + 1, 2) + "-" + fixedSizeNumber(date.getDate(), 2); + } + } + + function createTodayDate() { + var today = new Date(); + today.setHours(0); + today.setMinutes(0); + today.setSeconds(0); + return today; + } + + function moveCaretToEndOfText() { + var self = this; + + moveCaretToEnd(self); + window.setTimeout(function() { + moveCaretToEnd(self); + }, 1); + } + + function quoteMail(mail) { + var quotedLines = _.map(mail.body.split('\n'), function (line) { + return '> ' + line; + }); + + return '\n\n' + quotedLines.join('\n'); + } + + function i18n(text) { + return i18n_lib.get(text); + } + + return { + formatStatusClasses: formatStatusClasses, + formatMailBody: formatMailBody, + moveCaretToEndOfText: moveCaretToEndOfText, + getFormattedDate: getFormattedDate, + quoteMail: quoteMail, + i18n: i18n + }; +}); diff --git a/web-ui/app/js/lib/highlightRegex.js b/web-ui/app/js/lib/highlightRegex.js new file mode 100644 index 00000000..17caaa23 --- /dev/null +++ b/web-ui/app/js/lib/highlightRegex.js @@ -0,0 +1,127 @@ +/* + * jQuery Highlight Regex Plugin v0.1.2 + * + * Based on highlight v3 by Johann Burkard + * http://johannburkard.de/blog/programming/javascript/highlight-javascript-text-higlighting-jquery-plugin.html + * + * (c) 2009-13 Jacob Rothstein + * MIT license + */ + +;(function( $ ) { + + + + var normalize = function( node ) { + if ( ! ( node && node.childNodes )) return + + var children = $.makeArray( node.childNodes ) + , prevTextNode = null + + $.each( children, function( i, child ) { + if ( child.nodeType === 3 ) { + if ( child.nodeValue === "" ) { + + node.removeChild( child ) + + } else if ( prevTextNode !== null ) { + + prevTextNode.nodeValue += child.nodeValue; + node.removeChild( child ) + + } else { + + prevTextNode = child + + } + } else { + prevTextNode = null + + if ( child.childNodes ) { + normalize( child ) + } + } + }) + } + + + + + $.fn.highlightRegex = function( regex, options ) { + + if ( typeof regex === 'object' && !(regex.constructor.name == 'RegExp' || regex instanceof RegExp ) ) { + options = regex + regex = undefined + } + + if ( typeof options === 'undefined' ) options = {} + + options.className = options.className || 'highlight' + options.tagType = options.tagType || 'span' + options.attrs = options.attrs || {} + + if ( typeof regex === 'undefined' || regex.source === '' ) { + + $( this ).find( options.tagType + '.' + options.className ).each( function() { + + $( this ).replaceWith( $( this ).text() ) + + normalize( $( this ).parent().get( 0 )) + + }) + + } else { + + $( this ).each( function() { + + var elt = $( this ).get( 0 ) + + normalize( elt ) + + $.each( $.makeArray( elt.childNodes ), function( i, searchnode ) { + + var spannode, middlebit, middleclone, pos, match, parent + + normalize( searchnode ) + + if ( searchnode.nodeType == 3 ) { + + // don't re-highlight the same node over and over + if ( $(searchnode).parent(options.tagType + '.' + options.className).length ) { + return; + } + + while ( searchnode.data && + ( pos = searchnode.data.search( regex )) >= 0 ) { + + match = searchnode.data.slice( pos ).match( regex )[ 0 ] + + if ( match.length > 0 ) { + + spannode = document.createElement( options.tagType ) + spannode.className = options.className + $(spannode).attr(options.attrs) + + parent = searchnode.parentNode + middlebit = searchnode.splitText( pos ) + searchnode = middlebit.splitText( match.length ) + middleclone = middlebit.cloneNode( true ) + + spannode.appendChild( middleclone ) + parent.replaceChild( spannode, middlebit ) + + } else break + } + + } else { + + $( searchnode ).highlightRegex( regex, options ) + + } + }) + }) + } + + return $( this ) + } +})( jQuery ); diff --git a/web-ui/app/js/lib/html-sanitizer.js b/web-ui/app/js/lib/html-sanitizer.js new file mode 100644 index 00000000..80fb0041 --- /dev/null +++ b/web-ui/app/js/lib/html-sanitizer.js @@ -0,0 +1,1064 @@ +// Copyright (C) 2006 Google Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +/** + * @fileoverview + * An HTML sanitizer that can satisfy a variety of security policies. + * + *

+ * The HTML sanitizer is built around a SAX parser and HTML element and + * attributes schemas. + * + * If the cssparser is loaded, inline styles are sanitized using the + * css property and value schemas. Else they are remove during + * sanitization. + * + * If it exists, uses parseCssDeclarations, sanitizeCssProperty, cssSchema + * + * @author mikesamuel@gmail.com + * @author jasvir@gmail.com + * \@requires html4, URI + * \@overrides window + * \@provides html, html_sanitize + */ + +// The Turkish i seems to be a non-issue, but abort in case it is. +if ('I'.toLowerCase() !== 'i') { throw 'I/i problem'; } + +/** + * \@namespace + */ +define(['lib/html4-defs'], function (html4) { +var html = (function(html4) { + + // For closure compiler + var parseCssDeclarations, sanitizeCssProperty, cssSchema; + if ('undefined' !== typeof window) { + parseCssDeclarations = window['parseCssDeclarations']; + sanitizeCssProperty = window['sanitizeCssProperty']; + cssSchema = window['cssSchema']; + } + + // The keys of this object must be 'quoted' or JSCompiler will mangle them! + // This is a partial list -- lookupEntity() uses the host browser's parser + // (when available) to implement full entity lookup. + // Note that entities are in general case-sensitive; the uppercase ones are + // explicitly defined by HTML5 (presumably as compatibility). + var ENTITIES = { + 'lt': '<', + 'LT': '<', + 'gt': '>', + 'GT': '>', + 'amp': '&', + 'AMP': '&', + 'quot': '"', + 'apos': '\'', + 'nbsp': '\240' + }; + + // Patterns for types of entity/character reference names. + var decimalEscapeRe = /^#(\d+)$/; + var hexEscapeRe = /^#x([0-9A-Fa-f]+)$/; + // contains every entity per http://www.w3.org/TR/2011/WD-html5-20110113/named-character-references.html + var safeEntityNameRe = /^[A-Za-z][A-za-z0-9]+$/; + // Used as a hook to invoke the browser's entity parsing.