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 @@--- --
---+ ++++ + -++++ No private key found +++++++ () +++ +++ + + +-++ No public keys selected ++++++++ +++ () +++ +++ (Public key not found) ++- -- +++diff --git a/vendors/flags/flags-fixed.css b/vendors/flags/flags-fixed.css index 799431bbc..2402e12e0 100644 --- a/vendors/flags/flags-fixed.css +++ b/vendors/flags/flags-fixed.css @@ -17,6 +17,7 @@ .flag.flag-en-uk {background-position: -176px -44px} .flag.flag-en-ca {background-position: -48px -22px} +.flag.flag-ar, .flag.flag-ar-ar {background-position: -160px 0} .flag.flag-bg, .flag.flag-bg-bg {background-position: -80px -11px} .flag.flag-nl, .flag.flag-nl-nl {background-position: -80px -110px} .flag.flag-pl, .flag.flag-pl-pl {background-position: -32px -121px}+ ++++