From 5123841540e8ea9b035cd74080b86d3bbb9a3cf3 Mon Sep 17 00:00:00 2001 From: RainLoop Team Date: Wed, 29 Jul 2015 21:21:24 +0400 Subject: [PATCH] Pgp improvements --- dev/Common/Audio.js | 2 - dev/Common/Globals.js | 2 + dev/Common/Translator.js | 27 ++- dev/Common/Utils.js | 8 +- dev/Component/Select.js | 7 + dev/Storage/Settings.js | 1 - dev/Stores/User/Message.js | 18 +- dev/Stores/User/Pgp.js | 22 ++ dev/Styles/OpenPgpKey.less | 66 ++++++ dev/View/Popup/ComposeOpenPgp.js | 224 ++++++++++++++---- gulpfile.js | 6 +- package.json | 3 +- rainloop/v/0.0.0/app/i18n/langs.ini | 6 + .../0.0.0/app/libraries/RainLoop/Service.php | 3 +- rainloop/v/0.0.0/app/templates/Index.html | 2 +- .../templates/Views/Components/Select.html | 2 +- .../Views/User/PopupsComposeOpenPgp.html | 77 ++++-- vendors/flags/flags-fixed.css | 1 + 18 files changed, 397 insertions(+), 80 deletions(-) diff --git a/dev/Common/Audio.js b/dev/Common/Audio.js index 8e437fc88..13863510d 100644 --- a/dev/Common/Audio.js +++ b/dev/Common/Audio.js @@ -74,9 +74,7 @@ // Audio.prototype.record = function () // { // this.getUserMedia({audio:true}, function () { -// window.console.log(arguments); // }, function(oError) { -// window.console.log(arguments); // }); // }; diff --git a/dev/Common/Globals.js b/dev/Common/Globals.js index 19a194552..acb582852 100644 --- a/dev/Common/Globals.js +++ b/dev/Common/Globals.js @@ -1,4 +1,6 @@ +/* global RL_COMMUNITY */ + (function () { 'use strict'; diff --git a/dev/Common/Translator.js b/dev/Common/Translator.js index b9fd187ea..5a5e9d87f 100644 --- a/dev/Common/Translator.js +++ b/dev/Common/Translator.js @@ -10,6 +10,7 @@ ko = require('ko'), Enums = require('Common/Enums'), + Utils = require('Common/Utils'), Globals = require('Common/Globals') ; @@ -24,6 +25,8 @@ this.trigger = ko.observable(false); this.i18n = _.bind(this.i18n, this); + + this.init(); } Translator.prototype.data = {}; @@ -293,29 +296,41 @@ { var self = this, - $html = $('html'), - fEmptyFunction = function () {}, iStart = (new Date()).getTime() ; - $html.addClass('rl-changing-language'); + Globals.$html.addClass('rl-changing-language'); $.ajax({ 'url': require('Common/Links').langLink(sLanguage, bAdmin), 'dataType': 'script', 'cache': true }) - .fail(fFail || fEmptyFunction) + .fail(fFail || Utils.emptyFunction) .done(function () { _.delay(function () { + self.reloadData(); - (fDone || fEmptyFunction)(); - $html.removeClass('rl-changing-language'); + + (fDone || Utils.emptyFunction)(); + + Globals.$html + .removeClass('rl-changing-language') + .removeClass('rl-rtl rl-ltr') + .addClass(-1 < Utils.inArray(sLanguage, ['ar', 'he', 'ur']) ? 'rl-rtl' : 'rl-ltr') +// .attr('dir', -1 < Utils.inArray(sLanguage, ['ar', 'he', 'ur']) ? 'rtl' : 'ltr') + ; + }, 500 < (new Date()).getTime() - iStart ? 1 : 500); }) ; }; + Translator.prototype.init = function () + { + Globals.$html.addClass('rl-' + (Globals.$html.attr('dir') || 'ltr')); + }; + module.exports = new Translator(); }()); diff --git a/dev/Common/Utils.js b/dev/Common/Utils.js index 49119012d..ed59539b0 100644 --- a/dev/Common/Utils.js +++ b/dev/Common/Utils.js @@ -852,7 +852,11 @@ }, convertPre = function () { - return (arguments && 1 < arguments.length) ? arguments[1].toString().replace(/[\n]/gm, '
') : ''; + return (arguments && 1 < arguments.length) ? + arguments[1].toString() + .replace(/[\n]/gm, '
') + .replace(/[\r]/gm, '') + : ''; }, fixAttibuteValue = function () { @@ -868,7 +872,7 @@ sText = sHtml .replace(/\u0002([\s\S]*)\u0002/gm, '\u200C$1\u200C') .replace(/]*><\/p>/gi, '') - .replace(/]*>([\s\S\r\n]*)<\/pre>/gmi, convertPre) + .replace(/]*>([\s\S\r\n\t]*)<\/pre>/gmi, convertPre) .replace(/[\s]+/gm, ' ') .replace(/((?:href|data)\s?=\s?)("[^"]+?"|'[^']+?')/gmi, fixAttibuteValue) .replace(/]*>/gmi, '\n') diff --git a/dev/Component/Select.js b/dev/Component/Select.js index a987e7669..046f7603a 100644 --- a/dev/Component/Select.js +++ b/dev/Component/Select.js @@ -7,6 +7,7 @@ _ = require('_'), Utils = require('Common/Utils'), + Translator = require('Common/Translator'), AbstractInput = require('Component/AbstractInput') ; @@ -26,6 +27,12 @@ this.optionsText = oParams.optionsText || null; this.optionsValue = oParams.optionsValue || null; + this.optionsCaption = oParams.optionsCaption || null; + + if (this.optionsCaption) + { + this.optionsCaption = Translator.i18n(this.optionsCaption); + } this.defautOptionsAfterRender = Utils.defautOptionsAfterRender; } diff --git a/dev/Storage/Settings.js b/dev/Storage/Settings.js index ef12c2a8f..3fecebfc2 100644 --- a/dev/Storage/Settings.js +++ b/dev/Storage/Settings.js @@ -16,7 +16,6 @@ { this.oSettings = window['rainloopAppData'] || {}; this.oSettings = Utils.isNormal(this.oSettings) ? this.oSettings : {}; -// window.console.log(this.oSettings); } SettingsStorage.prototype.oSettings = null; diff --git a/dev/Stores/User/Message.js b/dev/Stores/User/Message.js index 718ba6310..53b019820 100644 --- a/dev/Stores/User/Message.js +++ b/dev/Stores/User/Message.js @@ -526,6 +526,7 @@ var bNew = false, bIsHtml = false, + sTag = 'div', bHasExternals = false, bHasInternals = false, oBody = null, @@ -584,9 +585,6 @@ bHasExternals = !!oData.Result.HasExternals; bHasInternals = !!oData.Result.HasInternals; - oBody = $('
').hide().addClass('rl-cache-class'); - oBody.data('rl-cache-count', ++Globals.iMessageBodyCacheCount); - if (Utils.isNormal(oData.Result.Html) && '' !== oData.Result.Html) { bIsHtml = true; @@ -625,6 +623,10 @@ ).html() ; } + else + { + sResultHtml = '
' + sResultHtml + '
'; + } sPlain = ''; @@ -633,12 +635,20 @@ oMessage.isPgpSigned(bPgpSigned); oMessage.isPgpEncrypted(bPgpEncrypted); } + else + { + sResultHtml = '
' + sResultHtml + '
'; + } } else { bIsHtml = false; + sResultHtml = '
' + sResultHtml + '
'; } + oBody = $('
').hide().addClass('rl-cache-class'); + oBody.data('rl-cache-count', ++Globals.iMessageBodyCacheCount); + oBody .html(Utils.findEmailAndLinks(sResultHtml)) .addClass('b-text-part ' + (bIsHtml ? 'html' : 'plain')) @@ -753,8 +763,6 @@ { if (sFolder && sUid) { - window.console.log(sFolder, sUid); - this.message(this.staticMessage.populateByMessageListItem(null)); this.message().folderFullNameRaw = sFolder; this.message().uid = sUid; diff --git a/dev/Stores/User/Pgp.js b/dev/Stores/User/Pgp.js index 44815d225..c6ae88594 100644 --- a/dev/Stores/User/Pgp.js +++ b/dev/Stores/User/Pgp.js @@ -82,6 +82,28 @@ }), true)); }; + /** + * @param {string} sEmail + * @return {?} + */ + PgpUserStore.prototype.findPublicKeyByEmailNotNative = function (sEmail) + { + return _.find(this.openpgpkeysPublic(), function (oItem) { + return oItem && sEmail === oItem.email; + }) || null; + }; + + /** + * @param {string} sEmail + * @return {?} + */ + PgpUserStore.prototype.findPrivateKeyByEmailNotNative = function (sEmail) + { + return _.find(this.openpgpkeysPrivate(), function (oItem) { + return oItem && sEmail === oItem.email; + }) || null; + }; + /** * @param {string} sEmail * @param {string=} sPassword diff --git a/dev/Styles/OpenPgpKey.less b/dev/Styles/OpenPgpKey.less index 8b2d753da..b9ff7a9f2 100644 --- a/dev/Styles/OpenPgpKey.less +++ b/dev/Styles/OpenPgpKey.less @@ -19,6 +19,72 @@ } } + .b-compose-open-pgp-content { + &.modal { + width: 700px; + } + + .key-list { + + background-color: #f9f9f9; + border-radius: 5px; + padding: 10px 15px; + margin-top: 10px; + min-height: 40px; + + &-wrp { + overflow: hidden; + text-overflow: ellipsis; + + &.empty { + text-align: center; + padding-top: 10px; + color: #aaa; + font-size: 16px; + } + } + + &__item { + + color: #333; + white-space: nowrap; + + &-delete { + display: inline; + cursor: pointer; + margin-right: 5px; + } + + &-name { + margin-right: 5px; + color: #333; + display: inline; + + &.empty { + color: red; + } + } + + &-error { + margin-right: 5px; + color: red; + display: inline; + } + + &-hash { + margin-right: 5px; + color: #aaa; + display: inline; + } + } + } + + .key-actions { + margin-top: 10px; + min-height: 40px; + } + } + .b-message-open-pgp-content { &.modal { width: 700px; diff --git a/dev/View/Popup/ComposeOpenPgp.js b/dev/View/Popup/ComposeOpenPgp.js index 55a3f638e..2ead22bc8 100644 --- a/dev/View/Popup/ComposeOpenPgp.js +++ b/dev/View/Popup/ComposeOpenPgp.js @@ -28,88 +28,137 @@ { AbstractView.call(this, 'Popups', 'PopupsComposeOpenPgp'); + var self = this; + + this.optionsCaption = Translator.i18n('@i18n/Add a public key'); + this.notification = ko.observable(''); - this.sign = ko.observable(true); - this.encrypt = ko.observable(true); + this.sign = ko.observable(false); + this.encrypt = ko.observable(false); this.password = ko.observable(''); this.password.focus = ko.observable(false); this.buttonFocus = ko.observable(false); - this.from = ko.observable(''); - this.to = ko.observableArray([]); this.text = ko.observable(''); + this.selectedPublicKey = ko.observable(null); - this.resultCallback = null; + this.signKey = ko.observable(null); + this.encryptKeys = ko.observableArray([]); + + this.encryptKeysView = ko.computed(function () { + return _.compact(_.map(this.encryptKeys(), function (oKey) { + return oKey ? oKey.key : null; + })); + }, this); + + this.publicKeysOptions = ko.computed(function () { + return _.compact(_.map(PgpStore.openpgpkeysPublic(), function (oKey) { + return -1 < Utils.inArray(oKey, self.encryptKeysView()) ? null : { + 'id': oKey.guid, + 'name': '(' + oKey.id.substr(-6) + ') ' + oKey.user, + 'key': oKey + }; + })); + }); this.submitRequest = ko.observable(false); + this.resultCallback = null; + // commands this.doCommand = Utils.createCommand(this, function () { var - self = this, bResult = true, oPrivateKey = null, + aPrivateKeys = [], aPublicKeys = [] ; this.submitRequest(true); - if (bResult && this.sign() && '' === this.from()) - { - this.notification(Translator.i18n('PGP_NOTIFICATIONS/SPECIFY_FROM_EMAIL')); - bResult = false; - } - if (bResult && this.sign()) { - oPrivateKey = PgpStore.findPrivateKeyByEmail(this.from(), this.password()); - if (!oPrivateKey) + if (!this.signKey()) + { + this.notification(Translator.i18n('PGP_NOTIFICATIONS/SPECIFY_FROM_EMAIL')); + bResult = false; + } + else if (!this.signKey().key) { this.notification(Translator.i18n('PGP_NOTIFICATIONS/NO_PRIVATE_KEY_FOUND_FOR', { - 'EMAIL': this.from() + 'EMAIL': this.signKey().email })); bResult = false; } - } - if (bResult && this.encrypt() && 0 === this.to().length) - { - this.notification(Translator.i18n('PGP_NOTIFICATIONS/SPECIFY_AT_LEAST_ONE_RECIPIENT')); - bResult = false; + if (bResult) + { + aPrivateKeys = this.signKey().key.getNativeKeys(); + oPrivateKey = aPrivateKeys[0] || null; + + try + { + if (oPrivateKey) + { + oPrivateKey.decrypt(Utils.pString(this.password())); + } + } + catch (e) + { + oPrivateKey = null; + } + + if (!oPrivateKey) + { + this.notification(Translator.i18n('PGP_NOTIFICATIONS/NO_PRIVATE_KEY_FOUND')); + bResult = false; + } + } } if (bResult && this.encrypt()) { - aPublicKeys = []; - _.each(this.to(), function (sEmail) { - var aKeys = PgpStore.findPublicKeysByEmail(sEmail); - if (0 === aKeys.length && bResult) - { - self.notification(Translator.i18n('PGP_NOTIFICATIONS/NO_PUBLIC_KEYS_FOUND_FOR', { - 'EMAIL': sEmail - })); + if (0 === this.encryptKeys().length) + { + this.notification(Translator.i18n('PGP_NOTIFICATIONS/NO_PUBLIC_KEYS_FOUND')); + bResult = false; + } + else if (this.encryptKeys()) + { + aPublicKeys = []; + _.each(this.encryptKeys(), function (oKey) { + if (oKey && oKey.key) + { + aPublicKeys = aPublicKeys.concat(_.compact(_.flatten(oKey.key.getNativeKeys()))); + } + else if (oKey && oKey.email) + { + self.notification(Translator.i18n('PGP_NOTIFICATIONS/NO_PUBLIC_KEYS_FOUND_FOR', { + 'EMAIL': oKey.email + })); + + bResult = false; + } + }); + + if (bResult && (0 === aPublicKeys.length || this.encryptKeys().length !== aPublicKeys.length)) + { bResult = false; } - - aPublicKeys = aPublicKeys.concat(aKeys); - }); - - if (bResult && (0 === aPublicKeys.length || this.to().length !== aPublicKeys.length)) - { - bResult = false; } } - _.delay(function () { + if (bResult && self.resultCallback) + { + _.delay(function () { - if (self.resultCallback && bResult) - { var oPromise = null; + try { if (oPrivateKey && 0 === aPublicKeys.length) @@ -154,17 +203,56 @@ })); } } - } - self.submitRequest(false); + self.submitRequest(false); - }, 10); + }, 10); + } + + return bResult; }, function () { return !this.submitRequest() && (this.sign() || this.encrypt()); }); + this.addCommand = Utils.createCommand(this, function () { + + var + sKeyId = this.selectedPublicKey(), + aKeys = this.encryptKeys(), + oOption = sKeyId ? _.find(this.publicKeysOptions(), function (oItem) { + return oItem && sKeyId === oItem.id; + }) : null + ; + + if (oOption) + { + aKeys.push({ + 'empty': !oOption.key, + 'selected': ko.observable(!!oOption.key), + 'user': oOption.key.user, + 'hash': oOption.key.id.substr(-6), + 'key': oOption.key + }); + + this.encryptKeys(aKeys); + } + + }, function () { + return !this.submitRequest() && this.selectedPublicKey(); + }); + + this.selectedPublicKey.subscribe(function (sValue) { + if (sValue) + { + this.addCommand(); + } + }, this); + this.sDefaultKeyScope = Enums.KeyState.PopupComposeOpenPGP; + this.defautOptionsAfterRender = Utils.defautOptionsAfterRender; + + this.deletePublickKey = _.bind(this.deletePublickKey, this); kn.constructorEnd(this); } @@ -172,16 +260,24 @@ kn.extendAsViewModel(['View/Popup/ComposeOpenPgp', 'PopupsComposeOpenPgpViewModel'], ComposeOpenPgpPopupView); _.extend(ComposeOpenPgpPopupView.prototype, AbstractView.prototype); + ComposeOpenPgpPopupView.prototype.deletePublickKey = function (oKey) + { + this.encryptKeys.remove(oKey); + }; + ComposeOpenPgpPopupView.prototype.clearPopup = function () { this.notification(''); + this.sign(false); + this.encrypt(false); + this.password(''); this.password.focus(false); this.buttonFocus(false); - this.from(''); - this.to([]); + this.signKey(null); + this.encryptKeys([]); this.text(''); this.submitRequest(false); @@ -231,6 +327,8 @@ var aRec = [], + sEmail = '', + oKey = null, oEmail = new EmailModel() ; @@ -258,8 +356,44 @@ return '' === oEmail.email ? false : oEmail.email; })); - this.from(oIdentity ? oIdentity.email() : ''); - this.to(aRec); + if (oIdentity && oIdentity.email()) + { + sEmail = oIdentity.email(); + oKey = PgpStore.findPrivateKeyByEmailNotNative(sEmail); + if (oKey) + { + this.signKey({ + 'user': oKey.user || sEmail, + 'hash': oKey.id.substr(-6), + 'key': oKey + }); + } + } + + if (this.signKey()) + { + this.sign(true); + } + + if (aRec && 0 < aRec.length) + { + this.encryptKeys(_.compact(_.map(aRec, function (sEmail) { + var oKey = PgpStore.findPublicKeyByEmailNotNative(sEmail) || null; + return { + 'empty': !oKey, + 'selected': ko.observable(!!oKey), + 'user': oKey ? (oKey.user || sEmail) : sEmail, + 'hash': oKey ? oKey.id.substr(-6) : '', + 'key': oKey + }; + }))); + + if (0 < this.encryptKeys().length) + { + this.encrypt(true); + } + } + this.text(sText); }; diff --git a/gulpfile.js b/gulpfile.js index 6bce4423f..540b0a738 100644 --- a/gulpfile.js +++ b/gulpfile.js @@ -47,6 +47,7 @@ var plumber = require('gulp-plumber'), gulpif = require('gulp-if'), eol = require('gulp-eol'), + livereload = require('gulp-livereload'), gutil = require('gulp-util') ; @@ -242,6 +243,7 @@ gulp.task('css:main-begin', ['less:main'], function() { // .pipe(csslint.reporter()) .pipe(eol('\n', true)) .pipe(gulp.dest(cfg.paths.staticCSS)) + .pipe(livereload()) ; }); @@ -330,7 +332,7 @@ gulp.task('js:webpack:clear', function() { .pipe(require('gulp-rimraf')()); }); -gulp.task('js:webpack', ['js:webpack:clear'], function(callback) { +gulp.task('js:webpack', [/*'js:webpack:clear'*/], function(callback) { var webpack = require('webpack'), @@ -678,12 +680,14 @@ gulp.task('owncloud+', ['package:community-off', 'owncloud-']); //WATCH gulp.task('watch', ['fast'], function() { cfg.watch = true; + livereload.listen(); gulp.watch(cfg.paths.globjs, {interval: 1000}, ['js:app', 'js:admin']); gulp.watch(cfg.paths.less.main.watch, {interval: 1000}, ['css:main']); }); gulp.task('watch+', ['fast+'], function() { cfg.watch = true; + livereload.listen(); gulp.watch(cfg.paths.globjs, {interval: 1000}, ['js:app', 'js:admin']); gulp.watch(cfg.paths.less.main.watch, {interval: 1000}, ['css:main']); }); diff --git a/package.json b/package.json index 9e208ed56..918f2d74e 100644 --- a/package.json +++ b/package.json @@ -73,6 +73,7 @@ "gulp-through": "~0.3.0", "lodash": "~3.9.3", "gulp-if": "~1.2.5", - "node-notifier": "~4.2.3" + "node-notifier": "~4.2.3", + "gulp-livereload": "~3.8.0" } } diff --git a/rainloop/v/0.0.0/app/i18n/langs.ini b/rainloop/v/0.0.0/app/i18n/langs.ini index 9f19d3d74..88bea1b60 100644 --- a/rainloop/v/0.0.0/app/i18n/langs.ini +++ b/rainloop/v/0.0.0/app/i18n/langs.ini @@ -104,6 +104,9 @@ LANG_BS = "Bosanski" LANG_BS_BS = "Bosanski" LANG_BS_BA = "Bosanski" +LANG_AR = "‏العربية‏" +LANG_AR_AR = "‏العربية‏" + [LANGS_NAMES_EN] LANG_EN = "English" LANG_EN_US = "English (US)" @@ -210,3 +213,6 @@ LANG_SR_RS = "Serbian" LANG_BS = "Bosnian" LANG_BS_BS = "Bosnian" LANG_BS_BA = "Bosnian" + +LANG_AR = "Arabic" +LANG_AR_AR = "Arabic" diff --git a/rainloop/v/0.0.0/app/libraries/RainLoop/Service.php b/rainloop/v/0.0.0/app/libraries/RainLoop/Service.php index 5f2c47814..040f04945 100644 --- a/rainloop/v/0.0.0/app/libraries/RainLoop/Service.php +++ b/rainloop/v/0.0.0/app/libraries/RainLoop/Service.php @@ -266,7 +266,8 @@ class Service '{{BaseAppOpenPgpScriptLink}}' => $aData['OpenPgpJsLink'], '{{BaseAppMainCommonScriptLink}}' => $aData['AppJsCommonLink'], '{{BaseAppMainScriptLink}}' => $aData['AppJsLink'], - '{{BaseDir}}' => \in_array($aData['Language'], array('ar', 'he', 'ur')) ? 'rtl' : 'ltr' + '{{BaseDir}}' => 'ltr' +// '{{BaseDir}}' => \in_array($aData['Language'], array('ar', 'he', 'ur')) ? 'rtl' : 'ltr' ); $aTemplateParameters['{{BaseHash}}'] = \md5( diff --git a/rainloop/v/0.0.0/app/templates/Index.html b/rainloop/v/0.0.0/app/templates/Index.html index 081b99915..da86d980c 100644 --- a/rainloop/v/0.0.0/app/templates/Index.html +++ b/rainloop/v/0.0.0/app/templates/Index.html @@ -1,5 +1,5 @@ - + diff --git a/rainloop/v/0.0.0/app/templates/Views/Components/Select.html b/rainloop/v/0.0.0/app/templates/Views/Components/Select.html index 62d3d4a82..5d6532716 100644 --- a/rainloop/v/0.0.0/app/templates/Views/Components/Select.html +++ b/rainloop/v/0.0.0/app/templates/Views/Components/Select.html @@ -4,7 +4,7 @@    + optionsCaption: optionsCaption, css: className, optionsAfterRender: defautOptionsAfterRender">   diff --git a/rainloop/v/0.0.0/app/templates/Views/User/PopupsComposeOpenPgp.html b/rainloop/v/0.0.0/app/templates/Views/User/PopupsComposeOpenPgp.html index 68e6051a0..22020aba9 100644 --- a/rainloop/v/0.0.0/app/templates/Views/User/PopupsComposeOpenPgp.html +++ b/rainloop/v/0.0.0/app/templates/Views/User/PopupsComposeOpenPgp.html @@ -8,34 +8,83 @@