Fixed Opening an email with specific content „hangs” RainLoop in the browser (Closes #308)

Code refactoring
This commit is contained in:
RainLoop Team 2014-09-05 19:53:44 +04:00
parent af43329902
commit 7a374ebe03
40 changed files with 1100 additions and 181 deletions

View file

@ -25,12 +25,12 @@
Cache = require('Storage:RainLoop:Cache'),
Remote = require('Storage:RainLoop:Remote'),
EmailModel = require('Model:Email'),
FolderModel = require('Model:Folder'),
MessageModel = require('Model:Message'),
AccountModel = require('Model:Account'),
IdentityModel = require('Model:Identity'),
OpenPgpKeyModel = require('Model:OpenPgpKey'),
EmailModel = require('Model/Email'),
FolderModel = require('Model/Folder'),
MessageModel = require('Model/Message'),
AccountModel = require('Model/Account'),
IdentityModel = require('Model/Identity'),
OpenPgpKeyModel = require('Model/OpenPgpKey'),
AbstractApp = require('App:Abstract')
;
@ -1298,19 +1298,20 @@
{
this.setTitle(Utils.i18n('TITLES/LOADING'));
this.folders(_.bind(function (bValue) {
require.ensure([], function () {
if (bValue)
{
require.ensure([], function () {
self.folders(_.bind(function (bValue) {
kn.hideLoading();
kn.hideLoading();
if (bValue)
{
if (window.$LAB && window.crypto && window.crypto.getRandomValues && Settings.capa(Enums.Capa.OpenPGP))
{
window.$LAB.script(window.openpgp ? '' : LinkBuilder.openPgpJs()).wait(function () {
if (window.openpgp)
{
Data.openpgp = window.openpgp;
Data.openpgpKeyring = new window.openpgp.Keyring();
Data.capaOpenPGP(true);
@ -1400,21 +1401,20 @@
self.initLayoutResizer('#rl-left', '#rl-right', Enums.ClientSideKeyName.FolderListSize);
});
}
});
}
else
{
kn.hideLoading();
}
else
{
self.bootstartLoginScreen();
}
self.bootstartLoginScreen();
}
if (window.SimplePace)
{
window.SimplePace.set(100);
}
if (window.SimplePace)
{
window.SimplePace.set(100);
}
}, this));
}, self));
});
}
else
{

View file

@ -15,26 +15,25 @@
Utils = require('Common/Utils'),
Enums = require('Common/Enums'),
EmailModel = require('Model:Email')
EmailModel = require('Model/Email')
;
Globals.__APP = App;
Globals.__APP__ = App;
Plugins.__boot = App;
Plugins.__remote = App.remote();
Plugins.__data = App.data();
Globals.$win
.keydown(Utils.killCtrlAandS)
.keyup(Utils.killCtrlAandS)
.unload(function () {
Globals.bUnload = true;
})
;
Globals.$html.addClass(Globals.bMobileDevice ? 'mobile' : 'no-mobile');
Globals.$win.keydown(Utils.killCtrlAandS).keyup(Utils.killCtrlAandS);
Globals.$win.unload(function () {
Globals.bUnload = true;
});
Globals.$html.on('click.dropdown.data-api', function () {
Utils.detectDropdownVisibility();
});
Globals.$html
.addClass(Globals.bMobileDevice ? 'mobile' : 'no-mobile')
.on('click.dropdown.data-api', function () {
Utils.detectDropdownVisibility();
})
;
// export
window['rl'] = window['rl'] || {};
@ -49,7 +48,6 @@
window['__APP_BOOT'] = function (fCall) {
// boot
$(function () {
if (window['rainloopTEMPLATES'] && window['rainloopTEMPLATES'][0])
@ -59,7 +57,11 @@
_.delay(function () {
App.bootstart();
Globals.$html.removeClass('no-js rl-booted-trigger').addClass('rl-booted');
Globals.$html
.removeClass('no-js rl-booted-trigger')
.addClass('rl-booted')
;
}, 10);
}

View file

@ -118,7 +118,7 @@
/**
* @type {*}
*/
Globals.__APP = null;
Globals.__APP__ = null;
/**
* @type {Object}

View file

@ -6,6 +6,7 @@
var
_ = require('_'),
Globals = require('Common/Globals'),
Utils = require('Common/Utils')
;
@ -14,19 +15,15 @@
*/
function Plugins()
{
this.__boot = null;
this.__data = null;
this.__remote = null;
this.oSettings = require('Storage:Settings');
this.oViewModelsHooks = {};
this.oSimpleHooks = {};
}
Plugins.prototype.__boot = null;
Plugins.prototype.__data = null;
Plugins.prototype.__remote = null;
/**
* @type {Object}
*/
Plugins.prototype.oSettings = {};
/**
* @type {Object}
@ -90,9 +87,9 @@
*/
Plugins.prototype.remoteRequest = function (fCallback, sAction, oParameters, iTimeout, sGetAdd, aAbortActions)
{
if (this.__remote)
if (Globals.__APP__)
{
this.__remote.defaultRequest(fCallback, sAction, oParameters, iTimeout, sGetAdd, aAbortActions);
Globals.__APP__.remote().defaultRequest(fCallback, sAction, oParameters, iTimeout, sGetAdd, aAbortActions);
}
};

View file

@ -10,6 +10,7 @@
_ = require('_'),
$ = require('$'),
ko = require('ko'),
Autolinker = require('Autolinker'),
Enums = require('Common/Enums'),
Consts = require('Common/Consts'),
@ -146,7 +147,7 @@
oEmailModel = null,
sEmail = sMailToUrl.replace(/\?.+$/, ''),
sQueryString = sMailToUrl.replace(/^[^\?]*\?/, ''),
EmailModel = require('Model:Email')
EmailModel = require('Model/Email')
;
oEmailModel = new EmailModel();
@ -1517,10 +1518,10 @@
/**
* @param {string} sPlain
* @param {boolean} bLinkify = false
* @param {boolean} bFindEmailAndLinks = false
* @return {string}
*/
Utils.plainToHtml = function (sPlain, bLinkify)
Utils.plainToHtml = function (sPlain, bFindEmailAndLinks)
{
sPlain = sPlain.toString().replace(/\r/g, '');
@ -1551,9 +1552,16 @@
}
else if (!bStart && bIn)
{
bIn = false;
aNextText.push('~~~/blockquote~~~');
aNextText.push(sLine);
if ('' !== sLine)
{
bIn = false;
aNextText.push('~~~/blockquote~~~');
aNextText.push(sLine);
}
else
{
aNextText.push(sLine);
}
}
else if (bStart && bIn)
{
@ -1578,6 +1586,7 @@
sPlain = aText.join("\n");
sPlain = sPlain
// .replace(/~~~\/blockquote~~~\n~~~blockquote~~~/g, '\n')
.replace(/&/g, '&')
.replace(/>/g, '&gt;').replace(/</g, '&lt;')
.replace(/~~~blockquote~~~[\s]*/g, '<blockquote>')
@ -1585,7 +1594,7 @@
.replace(/[\-_~]{10,}/g, '<hr />')
.replace(/\n/g, '<br />');
return bLinkify ? Utils.linkify(sPlain) : sPlain;
return bFindEmailAndLinks ? Utils.findEmailAndLinks(sPlain) : sPlain;
};
window.rainloop_Utils_htmlToPlain = Utils.htmlToPlain;
@ -1595,17 +1604,25 @@
* @param {string} sHtml
* @return {string}
*/
Utils.linkify = function (sHtml)
Utils.findEmailAndLinks = function (sHtml)
{
if ($.fn && $.fn.linkify)
{
sHtml = Globals.$div.html(sHtml.replace(/&amp;/ig, 'amp_amp_12345_amp_amp'))
.linkify()
.find('.linkified').removeClass('linkified').end()
.html()
.replace(/amp_amp_12345_amp_amp/g, '&amp;')
;
}
sHtml = Autolinker.link(sHtml, {
'newWindow': true,
'stripPrefix': false,
'urls': true,
'email': true,
'twitter': false
});
// if ($.fn && $.fn.linkify)
// {
// sHtml = Globals.$div.html(sHtml.replace(/&amp;/ig, 'amp_amp_12345_amp_amp'))
// .linkify()
// .find('.linkified').removeClass('linkified').end()
// .html()
// .replace(/amp_amp_12345_amp_amp/g, '&amp;')
// ;
// }
return sHtml;
};
@ -1936,6 +1953,29 @@
}
};
/**
* @param {string} sLanguage
* @param {Function=} fDone
* @param {Function=} fFail
* @param {Function=} fAllways
*/
Utils.reloadLanguage = function (sLanguage, fDone, fFail, fAllways)
{
$.ajax({
'url': require('Common/LinkBuilder').langLink(sLanguage),
'dataType': 'script',
'cache': true
})
.done(function () {
Utils.i18nReload();
(fDone || Utils.emptyFunction)();
})
.fail(fFail || Utils.emptyFunction)
.always(fAllways || Utils.emptyFunction)
;
};
module.exports = Utils;
}());

5
dev/External/ko.js vendored
View file

@ -568,7 +568,7 @@
var
Utils = require('Common/Utils'),
EmailModel = require('Model:Email'),
EmailModel = require('Model/Email'),
$oEl = $(oElement),
fValue = fValueAccessor(),
@ -600,7 +600,6 @@
{
oEmail = new EmailModel();
oEmail.mailsoParse(sValue);
oEmail.clearDuplicateName();
return [oEmail.toLine(false), oEmail];
}
@ -642,7 +641,7 @@
var
Utils = require('Common/Utils'),
ContactTagModel = require('Model:ContactTag'),
ContactTagModel = require('Model/ContactTag'),
$oEl = $(oElement),
fValue = fValueAccessor(),

View file

@ -8,7 +8,7 @@
Enums = require('Common/Enums'),
Utils = require('Common/Utils'),
FilterConditionModel = require('Model:FilterCondition')
FilterConditionModel = require('Model/FilterCondition')
;
/**

View file

@ -15,8 +15,8 @@
Globals = require('Common/Globals'),
LinkBuilder = require('Common/LinkBuilder'),
EmailModel = require('Model:Email'),
AttachmentModel = require('Model:Attachment')
EmailModel = require('Model/Email'),
AttachmentModel = require('Model/Attachment')
;
/**
@ -431,8 +431,8 @@
MessageModel.prototype.initUpdateByMessageJson = function (oJsonMessage)
{
var
Data = require('Storage:RainLoop:Data'),
bResult = false,
Data = require('Storage:RainLoop:Data'),
iPriority = Enums.MessagePriority.Normal
;
@ -1156,7 +1156,7 @@
try
{
mPgpMessage = window.openpgp.cleartext.readArmored(this.plainRaw);
mPgpMessage = Data.openpgp.cleartext.readArmored(this.plainRaw);
if (mPgpMessage && mPgpMessage.getText)
{
this.pgpSignedVerifyStatus(
@ -1226,7 +1226,7 @@
try
{
mPgpMessage = window.openpgp.message.readArmored(this.plainRaw);
mPgpMessage = Data.openpgp.message.readArmored(this.plainRaw);
if (mPgpMessage && oPrivateKey && mPgpMessage.decrypt)
{
this.pgpSignedVerifyStatus(Enums.SignedVerifyStatus.Unverified);

View file

@ -47,7 +47,7 @@
SettingsContacts.prototype.onBuild = function ()
{
Data.contactsAutosave.subscribe(function (bValue) {
Remote.saveSettings(Utils.emptyFunction, {
Remote.saveSettings(null, {
'ContactsAutosave': bValue ? '1' : '0'
});
});

View file

@ -30,7 +30,7 @@
SettingsFilters.prototype.addFilter = function ()
{
var
FilterModel = require('Model:Filter')
FilterModel = require('Model/Filter')
;
require('App:Knoin').showScreenPopup(

View file

@ -76,28 +76,23 @@
self.languageTrigger(Enums.SaveSettingsStep.Animate);
$.ajax({
'url': LinkBuilder.langLink(sValue),
'dataType': 'script',
'cache': true
}).done(function() {
Utils.i18nReload();
Utils.reloadLanguage(sValue, function() {
self.languageTrigger(Enums.SaveSettingsStep.TrueResult);
}).fail(function() {
}, function() {
self.languageTrigger(Enums.SaveSettingsStep.FalseResult);
}).always(function() {
}, function() {
_.delay(function () {
self.languageTrigger(Enums.SaveSettingsStep.Idle);
}, 1000);
});
Remote.saveSettings(Utils.emptyFunction, {
Remote.saveSettings(null, {
'Language': sValue
});
});
Data.editorDefaultType.subscribe(function (sValue) {
Remote.saveSettings(Utils.emptyFunction, {
Remote.saveSettings(null, {
'EditorDefaultType': sValue
});
});
@ -109,20 +104,20 @@
});
Data.showImages.subscribe(function (bValue) {
Remote.saveSettings(Utils.emptyFunction, {
Remote.saveSettings(null, {
'ShowImages': bValue ? '1' : '0'
});
});
Data.interfaceAnimation.subscribe(function (sValue) {
Remote.saveSettings(Utils.emptyFunction, {
Remote.saveSettings(null, {
'InterfaceAnimation': sValue
});
});
Data.useDesktopNotifications.subscribe(function (bValue) {
Utils.timeOutAction('SaveDesktopNotifications', function () {
Remote.saveSettings(Utils.emptyFunction, {
Remote.saveSettings(null, {
'DesktopNotifications': bValue ? '1' : '0'
});
}, 3000);
@ -130,7 +125,7 @@
Data.replySameFolder.subscribe(function (bValue) {
Utils.timeOutAction('SaveReplySameFolder', function () {
Remote.saveSettings(Utils.emptyFunction, {
Remote.saveSettings(null, {
'ReplySameFolder': bValue ? '1' : '0'
});
}, 3000);
@ -140,7 +135,7 @@
Data.messageList([]);
Remote.saveSettings(Utils.emptyFunction, {
Remote.saveSettings(null, {
'UseThreads': bValue ? '1' : '0'
});
});
@ -149,13 +144,13 @@
Data.messageList([]);
Remote.saveSettings(Utils.emptyFunction, {
Remote.saveSettings(null, {
'Layout': nValue
});
});
Data.useCheckboxesInList.subscribe(function (bValue) {
Remote.saveSettings(Utils.emptyFunction, {
Remote.saveSettings(null, {
'UseCheckboxesInList': bValue ? '1' : '0'
});
});

View file

@ -29,8 +29,8 @@
this.themeTrigger = ko.observable(Enums.SaveSettingsStep.Idle).extend({'throttle': 100});
this.oLastAjax = null;
this.iTimer = 0;
this.oThemeAjaxRequest = null;
Data.theme.subscribe(function (sValue) {
@ -62,12 +62,12 @@
window.clearTimeout(self.iTimer);
self.themeTrigger(Enums.SaveSettingsStep.Animate);
if (this.oLastAjax && this.oLastAjax.abort)
if (this.oThemeAjaxRequest && this.oThemeAjaxRequest.abort)
{
this.oLastAjax.abort();
this.oThemeAjaxRequest.abort();
}
this.oLastAjax = $.ajax({
this.oThemeAjaxRequest = $.ajax({
'url': sUrl,
'dataType': 'json'
}).done(function(aData) {
@ -103,7 +103,7 @@
self.themeTrigger(Enums.SaveSettingsStep.Idle);
}, 1000);
self.oLastAjax = null;
self.oThemeAjaxRequest = null;
});
}

View file

@ -64,9 +64,9 @@
if (Consts.Values.TokenErrorLimit < Globals.iTokenErrorCount)
{
if (Globals.__APP)
if (Globals.__APP__)
{
Globals.__APP.loginAndLogoutReload(true);
Globals.__APP__.loginAndLogoutReload(true);
}
}
@ -77,9 +77,9 @@
window.__rlah_clear();
}
if (Globals.__APP)
if (Globals.__APP__)
{
Globals.__APP.loginAndLogoutReload(true);
Globals.__APP__.loginAndLogoutReload(true);
}
}
}

View file

@ -22,7 +22,7 @@
kn = require('App:Knoin'),
MessageModel = require('Model:Message'),
MessageModel = require('Model/Message'),
LocalStorage = require('Storage:LocalStorage'),
AbstractData = require('Storage:Abstract:Data')
@ -323,10 +323,7 @@
if (Enums.Layout.NoPreview === this.layout() &&
-1 < window.location.hash.indexOf('message-preview'))
{
if (Globals.__APP)
{
Globals.__APP.historyBack();
}
require('App:RainLoop').historyBack();
}
}
else if (Enums.Layout.NoPreview === this.layout())
@ -443,6 +440,7 @@
// other
this.capaOpenPGP = ko.observable(false);
this.openpgpkeys = ko.observableArray([]);
this.openpgp = null;
this.openpgpKeyring = null;
this.openpgpkeysPublic = this.openpgpkeys.filter(function (oItem) {
@ -782,7 +780,6 @@
};
/**
* @private
* @param {Object} oMessageTextBody
*/
DataStorage.prototype.initBlockquoteSwitcher = function (oMessageTextBody)
@ -795,22 +792,24 @@
if ($oList && 0 < $oList.length)
{
$oList.each(function () {
var $self = $(this), iH = $self.height();
if (0 === iH || 100 < iH)
{
$self.addClass('rl-bq-switcher hidden-bq');
$('<span class="rlBlockquoteSwitcher"><i class="icon-ellipsis" /></span>')
.insertBefore($self)
.click(function () {
$self.toggleClass('hidden-bq');
Utils.windowResize();
})
.after('<br />')
.before('<br />')
;
}
});
_.delay(function () {
$oList.each(function () {
var $self = $(this), iH = $self.height();
if (0 === iH || 150 < iH)
{
$self.addClass('rl-bq-switcher hidden-bq');
$('<span class="rlBlockquoteSwitcher"><i class="icon-ellipsis" /></span>')
.insertBefore($self)
.click(function () {
$self.toggleClass('hidden-bq');
Utils.windowResize();
})
.after('<br />')
.before('<br />')
;
}
});
}, 100);
}
}
};
@ -818,6 +817,7 @@
DataStorage.prototype.setMessage = function (oData, bCached)
{
var
self = this,
bIsHtml = false,
bHasExternals = false,
bHasInternals = false,
@ -908,7 +908,7 @@
}
oBody
.html(Utils.linkify(sResultHtml))
.html(Utils.findEmailAndLinks(sResultHtml))
.addClass('b-text-part ' + (bIsHtml ? 'html' : 'plain'))
;
@ -961,10 +961,7 @@
Cache.initMessageFlagsFromCache(oMessage);
if (oMessage.unseen())
{
if (Globals.__APP)
{
Globals.__APP.setMessageSeen(oMessage);
}
require('App:RainLoop').setMessageSeen(oMessage);
}
Utils.windowResize();
@ -991,6 +988,7 @@
DataStorage.prototype.findPublicKeysByEmail = function (sEmail)
{
var self = this;
return _.compact(_.map(this.openpgpkeysPublic(), function (oItem) {
var oKey = null;
@ -998,7 +996,7 @@
{
try
{
oKey = window.openpgp.key.readArmored(oItem.armor);
oKey = self.openpgp.key.readArmored(oItem.armor);
if (oKey && !oKey.err && oKey.keys && oKey.keys[0])
{
return oKey.keys[0];
@ -1020,6 +1018,7 @@
DataStorage.prototype.findPrivateKeyByEmail = function (sEmail, sPassword)
{
var
self = this,
oPrivateKey = null,
oKey = _.find(this.openpgpkeysPrivate(), function (oItem) {
return oItem && sEmail === oItem.email;
@ -1030,7 +1029,7 @@
{
try
{
oPrivateKey = window.openpgp.key.readArmored(oKey.armor);
oPrivateKey = self.openpgp.key.readArmored(oKey.armor);
if (oPrivateKey && !oPrivateKey.err && oPrivateKey.keys && oPrivateKey.keys[0])
{
oPrivateKey = oPrivateKey.keys[0];

View file

@ -381,10 +381,7 @@
}
else if (Data.useThreads())
{
if (Globals.__APP)
{
Globals.__APP.reloadFlagsCurrentMessageListAndMessageFromCache();
}
require('App:RainLoop').reloadFlagsCurrentMessageListAndMessageFromCache();
}
};

View file

@ -337,18 +337,16 @@
_.delay(function () {
Data.language.subscribe(function (sValue) {
self.langRequest(true);
$.ajax({
'url': LinkBuilder.langLink(sValue),
'dataType': 'script',
'cache': true
}).done(function() {
Utils.reloadLanguage(sValue, function() {
self.bSendLanguage = true;
Utils.i18nReload();
$.cookie('rllang', Data.language(), {'expires': 30});
}).always(function() {
$.cookie('rllang', sValue, {'expires': 30});
}, null, function() {
self.langRequest(false);
});
});
}, 50);

View file

@ -4,7 +4,6 @@
'use strict';
var
window = require('window'),
_ = require('_'),
ko = require('ko'),
key = require('key'),
@ -14,7 +13,7 @@
Data = require('Storage:RainLoop:Data'),
EmailModel = require('Model:Email'),
EmailModel = require('Model/Email'),
kn = require('App:Knoin'),
KnoinAbstractViewModel = require('Knoin:AbstractViewModel')
@ -114,19 +113,19 @@
if (oPrivateKey && 0 === aPublicKeys.length)
{
self.resultCallback(
window.openpgp.signClearMessage([oPrivateKey], self.text())
Data.openpgp.signClearMessage([oPrivateKey], self.text())
);
}
else if (oPrivateKey && 0 < aPublicKeys.length)
{
self.resultCallback(
window.openpgp.signAndEncryptMessage(aPublicKeys, oPrivateKey, self.text())
Data.openpgp.signAndEncryptMessage(aPublicKeys, oPrivateKey, self.text())
);
}
else if (!oPrivateKey && 0 < aPublicKeys.length)
{
self.resultCallback(
window.openpgp.encryptMessage(aPublicKeys, self.text())
Data.openpgp.encryptMessage(aPublicKeys, self.text())
);
}
}

View file

@ -25,7 +25,7 @@
Cache = require('Storage:RainLoop:Cache'),
Remote = require('Storage:RainLoop:Remote'),
ComposeAttachmentModel = require('Model:ComposeAttachment'),
ComposeAttachmentModel = require('Model/ComposeAttachment'),
kn = require('App:Knoin'),
KnoinAbstractViewModel = require('Knoin:AbstractViewModel')

View file

@ -20,10 +20,10 @@
Data = require('Storage:RainLoop:Data'),
Remote = require('Storage:RainLoop:Remote'),
EmailModel = require('Model:Email'),
ContactModel = require('Model:Contact'),
ContactTagModel = require('Model:ContactTag'),
ContactPropertyModel = require('Model:ContactProperty'),
EmailModel = require('Model/Email'),
ContactModel = require('Model/Contact'),
ContactTagModel = require('Model/ContactTag'),
ContactPropertyModel = require('Model/ContactProperty'),
kn = require('App:Knoin'),
KnoinAbstractViewModel = require('Knoin:AbstractViewModel')

View file

@ -22,7 +22,7 @@
{
KnoinAbstractViewModel.call(this, 'Popups', 'PopupsLanguages');
this.Data = Globals.__APP.data(); // TODO
this.Data = Globals.__APP__.data(); // TODO
this.exp = ko.observable(false);

View file

@ -4,7 +4,6 @@
'use strict';
var
window = require('window'),
_ = require('_'),
ko = require('ko'),
@ -62,8 +61,8 @@
this.submitRequest(true);
_.delay(function () {
// mKeyPair = window.openpgp.generateKeyPair(1, Utils.pInt(self.keyBitLength()), sUserID, Utils.trim(self.password()));
mKeyPair = window.openpgp.generateKeyPair({
// mKeyPair = Data.openpgp.generateKeyPair(1, Utils.pInt(self.keyBitLength()), sUserID, Utils.trim(self.password()));
mKeyPair = Data.openpgp.generateKeyPair({
'userId': sUserID,
'numBits': Utils.pInt(self.keyBitLength()),
'passphrase': Utils.trim(self.password())

View file

@ -144,7 +144,7 @@ cfg.paths.js = {
'vendors/jquery-lazyload/jquery.lazyload.min.js',
'vendors/jquery-nanoscroller/jquery.nanoscroller-0.7.min.js',
'vendors/jquery-wakeup/jquery.wakeup.min.js',
'vendors/jquery-linkify/jquery.linkify.min.js',
// 'vendors/jquery-linkify/jquery.linkify.min.js',
'vendors/inputosaurus/inputosaurus.min.js',
'vendors/moment/min/moment.min.js ',
'vendors/routes/signals.min.js',
@ -154,6 +154,7 @@ cfg.paths.js = {
'vendors/knockout-projections/knockout-projections-1.0.0.min.js',
'vendors/ssm/ssm.min.js',
'vendors/jua/jua.min.js',
'vendors/Autolinker/Autolinker.min.js',
'vendors/jsbn/bundle.js',
'vendors/keymaster/keymaster.js',
'vendors/ifvisible/ifvisible.min.js',

View file

@ -2650,13 +2650,22 @@ class Actions
$bResult = false;
$oConfig = $this->Config();
$sLogin = \trim($this->GetActionParam('Login', ''));
$sPassword = $this->GetActionParam('Password', '');
$sNewPassword = $this->GetActionParam('NewPassword', '');
$this->Logger()->AddSecret($sPassword);
$this->Logger()->AddSecret($sNewPassword);
if ($oConfig->ValidatePassword($sPassword))
{
$bResult = true;
if (0 < strlen($sLogin))
{
$oConfig->Set('security', 'admin_login', $sLogin);
}
$oConfig->SetPassword($sNewPassword);
$bResult = true;
}
return $this->DefaultResponse(__FUNCTION__, $bResult ? $oConfig->Save() : false);
@ -7400,6 +7409,8 @@ class Actions
$mResult['Plain'] = $sPlain;
// $mResult['Plain'] = 0 === \strlen($sPlain) ? '' : \MailSo\Base\HtmlUtils::ConvertPlainToHtml($sPlain);
$this->Logger()->WriteDump($mResult['Html']);
$mResult['TextHash'] = \md5($mResult['Html'].$mResult['Plain']);
$mResult['TextPartIsTrimmed'] = $mResponse->TextPartIsTrimmed();

View file

@ -1,5 +1,5 @@
<!DOCTYPE html>
<html id="id-html" class="no-js rl-booted-trigger rl-started-trigger">
<html class="no-js rl-booted-trigger rl-started-trigger">
<head>
<meta charset="utf-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1" />
@ -8,7 +8,7 @@
<noscript>
<meta http-equiv="refresh" content="0; URL=./?/NoScript" />
</noscript>
<!--[if lte IE 7]>
<!--[if lte IE 8]>
<meta http-equiv="refresh" content="0; URL=./?/BadBrowser" />
<![endif]-->
<script type="text/javascript" data-cfasync="false">

607
vendors/Autolinker/Autolinker.js vendored Normal file
View file

@ -0,0 +1,607 @@
/*!
* Autolinker.js
* 0.11.0
*
* Copyright(c) 2014 Gregory Jacobs <greg@greg-jacobs.com>
* MIT Licensed. http://www.opensource.org/licenses/mit-license.php
*
* https://github.com/gregjacobs/Autolinker.js
*/
/*global define, module */
/*jshint undef:true, smarttabs:true */
// Set up Autolinker appropriately for the environment.
( function( root, factory ) {
if( typeof define === 'function' && define.amd ) {
define( factory ); // Define as AMD module if an AMD loader is present (ex: RequireJS).
} else if( typeof exports !== 'undefined' ) {
module.exports = factory(); // Define as CommonJS module for Node.js, if available.
} else {
root.Autolinker = factory(); // Finally, define as a browser global if no module loader.
}
}( this, function() {
/**
* @class Autolinker
* @extends Object
*
* Utility class used to process a given string of text, and wrap the URLs, email addresses, and Twitter handles in
* the appropriate anchor (&lt;a&gt;) tags to turn them into links.
*
* Any of the configuration options may be provided in an Object (map) provided to the Autolinker constructor, which
* will configure how the {@link #link link()} method will process the links.
*
* For example:
*
* var autolinker = new Autolinker( {
* newWindow : false,
* truncate : 30
* } );
*
* var html = autolinker.link( "Joe went to www.yahoo.com" );
* // produces: 'Joe went to <a href="http://www.yahoo.com">yahoo.com</a>'
*
*
* The {@link #static-link static link()} method may also be used to inline options into a single call, which may
* be more convenient for one-off uses. For example:
*
* var html = Autolinker.link( "Joe went to www.yahoo.com", {
* newWindow : false,
* truncate : 30
* } );
* // produces: 'Joe went to <a href="http://www.yahoo.com">yahoo.com</a>'
*
* @constructor
* @param {Object} [config] The configuration options for the Autolinker instance, specified in an Object (map).
*/
var Autolinker = function( cfg ) {
cfg = cfg || {};
// Assign the properties of `cfg` onto the Autolinker instance
for( var prop in cfg )
if( cfg.hasOwnProperty( prop ) ) this[ prop ] = cfg[ prop ];
};
Autolinker.prototype = {
constructor : Autolinker, // fix constructor property
/**
* @cfg {Boolean} newWindow
*
* `true` if the links should open in a new window, `false` otherwise.
*/
newWindow : true,
/**
* @cfg {Boolean} stripPrefix
*
* `true` if 'http://' or 'https://' and/or the 'www.' should be stripped from the beginning of links, `false` otherwise.
*/
stripPrefix : true,
/**
* @cfg {Number} truncate
*
* A number for how many characters long URLs/emails/twitter handles should be truncated to inside the text of
* a link. If the URL/email/twitter is over this number of characters, it will be truncated to this length by
* adding a two period ellipsis ('..') into the middle of the string.
*
* For example: A url like 'http://www.yahoo.com/some/long/path/to/a/file' truncated to 25 characters might look
* something like this: 'http://www...th/to/a/file'
*/
/**
* @cfg {Boolean} twitter
*
* `true` if Twitter handles ("@example") should be automatically linked, `false` if they should not be.
*/
twitter : true,
/**
* @cfg {Boolean} email
*
* `true` if email addresses should be automatically linked, `false` if they should not be.
*/
email : true,
/**
* @cfg {Boolean} urls
*
* `true` if miscellaneous URLs should be automatically linked, `false` if they should not be.
*/
urls : true,
/**
* @cfg {String} className
*
* A CSS class name to add to the generated links. This class will be added to all links, as well as this class
* plus url/email/twitter suffixes for styling url/email/twitter links differently.
*
* For example, if this config is provided as "myLink", then:
*
* 1) URL links will have the CSS classes: "myLink myLink-url"
* 2) Email links will have the CSS classes: "myLink myLink-email", and
* 3) Twitter links will have the CSS classes: "myLink myLink-twitter"
*/
className : "",
/**
* @private
* @property {RegExp} matcherRegex
*
* The regular expression that matches URLs, email addresses, and Twitter handles.
*
* This regular expression has the following capturing groups:
*
* 1. Group that is used to determine if there is a Twitter handle match (i.e. @someTwitterUser). Simply check for its
* existence to determine if there is a Twitter handle match. The next couple of capturing groups give information
* about the Twitter handle match.
* 2. The whitespace character before the @sign in a Twitter handle. This is needed because there are no lookbehinds in
* JS regular expressions, and can be used to reconstruct the original string in a replace().
* 3. The Twitter handle itself in a Twitter match. If the match is '@someTwitterUser', the handle is 'someTwitterUser'.
* 4. Group that matches an email address. Used to determine if the match is an email address, as well as holding the full
* address. Ex: 'me@my.com'
* 5. Group that matches a URL in the input text. Ex: 'http://google.com', 'www.google.com', or just 'google.com'.
* This also includes a path, url parameters, or hash anchors. Ex: google.com/path/to/file?q1=1&q2=2#myAnchor
* 6. A protocol-relative ('//') match for the case of a 'www.' prefixed URL. Will be an empty string if it is not a
* protocol-relative match. We need to know the character before the '//' in order to determine if it is a valid match
* or the // was in a string we don't want to auto-link.
* 7. A protocol-relative ('//') match for the case of a known TLD prefixed URL. Will be an empty string if it is not a
* protocol-relative match. See #6 for more info.
*/
matcherRegex : (function() {
var twitterRegex = /(^|[^\w])@(\w{1,15})/, // For matching a twitter handle. Ex: @gregory_jacobs
emailRegex = /(?:[\-;:&=\+\$,\w\.]+@)/, // something@ for email addresses (a.k.a. local-part)
protocolRegex = /(?:[A-Za-z]{3,9}:(?:\/\/)?)/, // match protocol, allow in format http:// or mailto:
wwwRegex = /(?:www\.)/, // starting with 'www.'
domainNameRegex = /[A-Za-z0-9\.\-]*[A-Za-z0-9\-]/, // anything looking at all like a domain, non-unicode domains, not ending in a period
tldRegex = /\.(?:international|construction|contractors|enterprises|photography|productions|foundation|immobilien|industries|management|properties|technology|christmas|community|directory|education|equipment|institute|marketing|solutions|vacations|bargains|boutique|builders|catering|cleaning|clothing|computer|democrat|diamonds|graphics|holdings|lighting|partners|plumbing|supplies|training|ventures|academy|careers|company|cruises|domains|exposed|flights|florist|gallery|guitars|holiday|kitchen|neustar|okinawa|recipes|rentals|reviews|shiksha|singles|support|systems|agency|berlin|camera|center|coffee|condos|dating|estate|events|expert|futbol|kaufen|luxury|maison|monash|museum|nagoya|photos|repair|report|social|supply|tattoo|tienda|travel|viajes|villas|vision|voting|voyage|actor|build|cards|cheap|codes|dance|email|glass|house|mango|ninja|parts|photo|shoes|solar|today|tokyo|tools|watch|works|aero|arpa|asia|best|bike|blue|buzz|camp|club|cool|coop|farm|fish|gift|guru|info|jobs|kiwi|kred|land|limo|link|menu|mobi|moda|name|pics|pink|post|qpon|rich|ruhr|sexy|tips|vote|voto|wang|wien|wiki|zone|bar|bid|biz|cab|cat|ceo|com|edu|gov|int|kim|mil|net|onl|org|pro|pub|red|tel|uno|wed|xxx|xyz|ac|ad|ae|af|ag|ai|al|am|an|ao|aq|ar|as|at|au|aw|ax|az|ba|bb|bd|be|bf|bg|bh|bi|bj|bm|bn|bo|br|bs|bt|bv|bw|by|bz|ca|cc|cd|cf|cg|ch|ci|ck|cl|cm|cn|co|cr|cu|cv|cw|cx|cy|cz|de|dj|dk|dm|do|dz|ec|ee|eg|er|es|et|eu|fi|fj|fk|fm|fo|fr|ga|gb|gd|ge|gf|gg|gh|gi|gl|gm|gn|gp|gq|gr|gs|gt|gu|gw|gy|hk|hm|hn|hr|ht|hu|id|ie|il|im|in|io|iq|ir|is|it|je|jm|jo|jp|ke|kg|kh|ki|km|kn|kp|kr|kw|ky|kz|la|lb|lc|li|lk|lr|ls|lt|lu|lv|ly|ma|mc|md|me|mg|mh|mk|ml|mm|mn|mo|mp|mq|mr|ms|mt|mu|mv|mw|mx|my|mz|na|nc|ne|nf|ng|ni|nl|no|np|nr|nu|nz|om|pa|pe|pf|pg|ph|pk|pl|pm|pn|pr|ps|pt|pw|py|qa|re|ro|rs|ru|rw|sa|sb|sc|sd|se|sg|sh|si|sj|sk|sl|sm|sn|so|sr|st|su|sv|sx|sy|sz|tc|td|tf|tg|th|tj|tk|tl|tm|tn|to|tp|tr|tt|tv|tw|tz|ua|ug|uk|us|uy|uz|va|vc|ve|vg|vi|vn|vu|wf|ws|ye|yt|za|zm|zw)\b/, // match our known top level domains (TLDs)
// Allow optional path, query string, and hash anchor, not ending in the following characters: "!:,.;"
// http://blog.codinghorror.com/the-problem-with-urls/
urlSuffixRegex = /(?:[\-A-Za-z0-9+&@#\/%?=~_()|!:,.;]*[\-A-Za-z0-9+&@#\/%=~_()|])?/; // note: optional part of the full regex
return new RegExp( [
'(', // *** Capturing group $1, which can be used to check for a twitter handle match. Use group $3 for the actual twitter handle though. $2 may be used to reconstruct the original string in a replace()
// *** Capturing group $2, which matches the whitespace character before the '@' sign (needed because of no lookbehinds), and
// *** Capturing group $3, which matches the actual twitter handle
twitterRegex.source,
')',
'|',
'(', // *** Capturing group $4, which is used to determine an email match
emailRegex.source,
domainNameRegex.source,
tldRegex.source,
')',
'|',
'(', // *** Capturing group $5, which is used to match a URL
'(?:', // parens to cover match for protocol (optional), and domain
'(?:', // non-capturing paren for a protocol-prefixed url (ex: http://google.com)
protocolRegex.source,
domainNameRegex.source,
')',
'|',
'(?:', // non-capturing paren for a 'www.' prefixed url (ex: www.google.com)
'(.?//)?', // *** Capturing group $6 for an optional protocol-relative URL. Must be at the beginning of the string or start with a non-word character
wwwRegex.source,
domainNameRegex.source,
')',
'|',
'(?:', // non-capturing paren for known a TLD url (ex: google.com)
'(.?//)?', // *** Capturing group $7 for an optional protocol-relative URL. Must be at the beginning of the string or start with a non-word character
domainNameRegex.source,
tldRegex.source,
')',
')',
urlSuffixRegex.source, // match for path, query string, and/or hash anchor
')'
].join( "" ), 'gi' );
} )(),
/**
* @private
* @property {RegExp} protocolRelativeRegex
*
* The regular expression used to find protocol-relative URLs. A protocol-relative URL is, for example, "//yahoo.com"
*
* This regular expression needs to match the character before the '//', in order to determine if we should actually
* autolink a protocol-relative URL. For instance, we want to autolink something like "//google.com", but we
* don't want to autolink something like "abc//google.com"
*/
protocolRelativeRegex : /(.)?\/\//,
/**
* @private
* @property {RegExp} htmlRegex
*
* The regular expression used to pull out HTML tags from a string. Handles namespaced HTML tags and
* attribute names, as specified by http://www.w3.org/TR/html-markup/syntax.html.
*
* Capturing groups:
*
* 1. If it is an end tag, this group will have the '/'.
* 2. The tag name.
*/
htmlRegex : (function() {
var tagNameRegex = /[0-9a-zA-Z:]+/,
attrNameRegex = /[^\s\0"'>\/=\x01-\x1F\x7F]+/, // the unicode range accounts for excluding control chars, and the delete char
attrValueRegex = /(?:".*?"|'.*?'|[^'"=<>`\s]+)/; // double quoted, single quoted, or unquoted attribute values
return new RegExp( [
'<(/)?', // Beginning of a tag. Either '<' for a start tag, or '</' for an end tag. The slash or an empty string is Capturing Group 1.
// The tag name (Capturing Group 2)
'(' + tagNameRegex.source + ')',
// Zero or more attributes following the tag name
'(?:',
'\\s+', // one or more whitespace chars before an attribute
attrNameRegex.source,
'(?:\\s*=\\s*' + attrValueRegex.source + ')?', // optional '=[value]'
')*',
'\\s*', // any trailing spaces before the closing '>'
'>'
].join( "" ), 'g' );
} )(),
/**
* @private
* @property {RegExp} urlPrefixRegex
*
* A regular expression used to remove the 'http://' or 'https://' and/or the 'www.' from URLs.
*/
urlPrefixRegex: /^(https?:\/\/)?(www\.)?/i,
/**
* Automatically links URLs, email addresses, and Twitter handles found in the given chunk of HTML.
* Does not link URLs found within HTML tags.
*
* For instance, if given the text: `You should go to http://www.yahoo.com`, then the result
* will be `You should go to &lt;a href="http://www.yahoo.com"&gt;http://www.yahoo.com&lt;/a&gt;`
*
* @method link
* @param {String} textOrHtml The HTML or text to link URLs, email addresses, and Twitter handles within.
* @return {String} The HTML, with URLs/emails/twitter handles automatically linked.
*/
link : function( textOrHtml ) {
return this.processHtml( textOrHtml );
},
/**
* Processes the given HTML to auto-link URLs/emails/Twitter handles.
*
* Finds the text around any HTML elements in the input `html`, which will be the text that is processed.
* Any original HTML elements will be left as-is, as well as the text that is already wrapped in anchor tags.
*
* @private
* @method processHtml
* @param {String} html The input text or HTML to process in order to auto-link.
* @return {String}
*/
processHtml : function( html ) {
// Loop over the HTML string, ignoring HTML tags, and processing the text that lies between them,
// wrapping the URLs in anchor tags
var htmlRegex = this.htmlRegex,
currentResult,
inBetweenTagsText,
lastIndex = 0,
anchorTagStackCount = 0,
resultHtml = [];
while( ( currentResult = htmlRegex.exec( html ) ) !== null ) {
var tagText = currentResult[ 0 ],
tagName = currentResult[ 2 ],
isClosingTag = !!currentResult[ 1 ];
inBetweenTagsText = html.substring( lastIndex, currentResult.index );
lastIndex = currentResult.index + tagText.length;
// Process around anchor tags, and any inner text / html they may have
if( tagName === 'a' ) {
if( !isClosingTag ) { // it's the start <a> tag
anchorTagStackCount++;
resultHtml.push( this.processTextNode( inBetweenTagsText ) );
} else { // it's the end </a> tag
anchorTagStackCount = Math.max( anchorTagStackCount - 1, 0 ); // attempt to handle extraneous </a> tags by making sure the stack count never goes below 0
if( anchorTagStackCount === 0 ) {
resultHtml.push( inBetweenTagsText ); // We hit the matching </a> tag, simply add all of the text from the start <a> tag to the end </a> tag without linking it
}
}
} else if( anchorTagStackCount === 0 ) { // not within an anchor tag, link the "in between" text
resultHtml.push( this.processTextNode( inBetweenTagsText ) );
} else {
// if we have a tag that is in between anchor tags (ex: <a href="..."><b>google.com</b></a>),
// just append the inner text
resultHtml.push( inBetweenTagsText );
}
resultHtml.push( tagText ); // now add the text of the tag itself verbatim
}
// Process any remaining text after the last HTML element. Will process all of the text if there were no HTML elements.
if( lastIndex < html.length ) {
var processedTextNode = this.processTextNode( html.substring( lastIndex ) );
resultHtml.push( processedTextNode );
}
return resultHtml.join( "" );
},
/**
* Process the text that lies inbetween HTML tags. This method does the actual wrapping of URLs with
* anchor tags.
*
* @private
* @param {String} text The text to auto-link.
* @return {String} The text with anchor tags auto-filled.
*/
processTextNode : function( text ) {
var me = this, // for closures
matcherRegex = this.matcherRegex,
enableTwitter = this.twitter,
enableEmailAddresses = this.email,
enableUrls = this.urls;
return text.replace( matcherRegex, function( matchStr, $1, $2, $3, $4, $5, $6, $7 ) {
var twitterMatch = $1,
twitterHandlePrefixWhitespaceChar = $2, // The whitespace char before the @ sign in a Twitter handle match. This is needed because of no lookbehinds in JS regexes
twitterHandle = $3, // The actual twitterUser (i.e the word after the @ sign in a Twitter handle match)
emailAddress = $4, // For both determining if it is an email address, and stores the actual email address
urlMatch = $5, // The matched URL string
protocolRelativeMatch = $6 || $7, // The '//' for a protocol-relative match, with the character that comes before the '//'
prefixStr = "", // A string to use to prefix the anchor tag that is created. This is needed for the Twitter handle match
suffixStr = ""; // A string to suffix the anchor tag that is created. This is used if there is a trailing parenthesis that should not be auto-linked.
// Early exits with no replacements for:
// 1) Disabled link types
// 2) URL matches which do not have at least have one period ('.') in the domain name (effectively skipping over
// matches like "abc:def")
// 3) A protocol-relative url match (a URL beginning with '//') whose previous character is a word character
// (effectively skipping over strings like "abc//google.com")
if(
( twitterMatch && !enableTwitter ) || ( emailAddress && !enableEmailAddresses ) || ( urlMatch && !enableUrls ) ||
( urlMatch && urlMatch.indexOf( '.' ) === -1 ) || // At least one period ('.') must exist in the URL match for us to consider it an actual URL
( urlMatch && /^[A-Za-z]{3,9}:/.test( urlMatch ) && !/:.*?[A-Za-z]/.test( urlMatch ) ) || // At least one letter character must exist in the domain name after a protocol match. Ex: skip over something like "git:1.0"
( protocolRelativeMatch && /^[\w]\/\//.test( protocolRelativeMatch ) ) // a protocol-relative match which has a word character in front of it (so we can skip something like "abc//google.com")
) {
return matchStr;
}
// Handle a closing parenthesis at the end of the match, and exclude it if there is not a matching open parenthesis
// in the match. This handles cases like the string "wikipedia.com/something_(disambiguation)" (which should be auto-
// linked, and when it is enclosed in parenthesis itself, such as: "(wikipedia.com/something_(disambiguation))" (in
// which the outer parens should *not* be auto-linked.
var lastChar = matchStr.charAt( matchStr.length - 1 );
if( lastChar === ')' ) {
var openParensMatch = matchStr.match( /\(/g ),
closeParensMatch = matchStr.match( /\)/g ),
numOpenParens = ( openParensMatch && openParensMatch.length ) || 0,
numCloseParens = ( closeParensMatch && closeParensMatch.length ) || 0;
if( numOpenParens < numCloseParens ) {
matchStr = matchStr.substr( 0, matchStr.length - 1 ); // remove the trailing ")"
suffixStr = ")"; // this will be added after the <a> tag
}
}
var anchorHref = matchStr, // initialize both of these
anchorText = matchStr, // values as the full match
linkType;
// Process the urls that are found. We need to change URLs like "www.yahoo.com" to "http://www.yahoo.com" (or the browser
// will try to direct the user to "http://current-domain.com/www.yahoo.com"), and we need to prefix 'mailto:' to email addresses.
if( twitterMatch ) {
linkType = 'twitter';
prefixStr = twitterHandlePrefixWhitespaceChar;
anchorHref = 'https://twitter.com/' + twitterHandle;
anchorText = '@' + twitterHandle;
} else if( emailAddress ) {
linkType = 'email';
anchorHref = 'mailto:' + emailAddress;
anchorText = emailAddress;
} else { // url match
linkType = 'url';
if( protocolRelativeMatch ) {
// Strip off any protocol-relative '//' from the anchor text (leaving the previous non-word character
// intact, if there is one)
var protocolRelRegex = new RegExp( "^" + me.protocolRelativeRegex.source ), // for this one, we want to only match at the beginning of the string
charBeforeMatch = protocolRelativeMatch.match( protocolRelRegex )[ 1 ] || "";
prefixStr = charBeforeMatch + prefixStr; // re-add the character before the '//' to what will be placed before the <a> tag
anchorHref = anchorHref.replace( protocolRelRegex, "//" ); // remove the char before the match for the href
anchorText = anchorText.replace( protocolRelRegex, "" ); // remove both the char before the match and the '//' for the anchor text
} else if( !/^[A-Za-z]{3,9}:/i.test( anchorHref ) ) {
// url string doesn't begin with a protocol, assume http://
anchorHref = 'http://' + anchorHref;
}
}
// wrap the match in an anchor tag
var anchorTag = me.createAnchorTag( linkType, anchorHref, anchorText );
return prefixStr + anchorTag + suffixStr;
} );
},
/**
* Generates the actual anchor (&lt;a&gt;) tag to use in place of a source url/email/twitter link.
*
* @private
* @param {"url"/"email"/"twitter"} linkType The type of link that an anchor tag is being generated for.
* @param {String} anchorHref The href for the anchor tag.
* @param {String} anchorText The anchor tag's text (i.e. what will be displayed).
* @return {String} The full HTML for the anchor tag.
*/
createAnchorTag : function( linkType, anchorHref, anchorText ) {
var attributesStr = this.createAnchorAttrsStr( linkType, anchorHref );
anchorText = this.processAnchorText( anchorText );
return '<a ' + attributesStr + '>' + anchorText + '</a>';
},
/**
* Creates the string which will be the HTML attributes for the anchor (&lt;a&gt;) tag being generated.
*
* @private
* @param {"url"/"email"/"twitter"} linkType The type of link that an anchor tag is being generated for.
* @param {String} href The href for the anchor tag.
* @return {String} The anchor tag's attribute. Ex: `href="http://google.com" class="myLink myLink-url" target="_blank"`
*/
createAnchorAttrsStr : function( linkType, anchorHref ) {
var attrs = [ 'href="' + anchorHref + '"' ]; // we'll always have the `href` attribute
var cssClass = this.createCssClass( linkType );
if( cssClass ) {
attrs.push( 'class="' + cssClass + '"' );
}
if( this.newWindow ) {
attrs.push( 'target="_blank"' );
}
return attrs.join( " " );
},
/**
* Creates the CSS class that will be used for a given anchor tag, based on the `linkType` and the {@link #className}
* config.
*
* @private
* @param {"url"/"email"/"twitter"} linkType The type of link that an anchor tag is being generated for.
* @return {String} The CSS class string for the link. Example return: "myLink myLink-url". If no {@link #className}
* was configured, returns an empty string.
*/
createCssClass : function( linkType ) {
var className = this.className;
if( !className )
return "";
else
return className + " " + className + "-" + linkType; // ex: "myLink myLink-url", "myLink myLink-email", or "myLink myLink-twitter"
},
/**
* Processes the `anchorText` by stripping the URL prefix (if {@link #stripPrefix} is `true`), removing
* any trailing slash, and truncating the text according to the {@link #truncate} config.
*
* @private
* @param {String} anchorText The anchor tag's text (i.e. what will be displayed).
* @return {String} The processed `anchorText`.
*/
processAnchorText : function( anchorText ) {
if( this.stripPrefix ) {
anchorText = this.stripUrlPrefix( anchorText );
}
anchorText = this.removeTrailingSlash( anchorText ); // remove trailing slash, if there is one
anchorText = this.doTruncate( anchorText );
return anchorText;
},
/**
* Strips the URL prefix (such as "http://" or "https://") from the given text.
*
* @private
* @param {String} text The text of the anchor that is being generated, for which to strip off the
* url prefix (such as stripping off "http://")
* @return {String} The `anchorText`, with the prefix stripped.
*/
stripUrlPrefix : function( text ) {
return text.replace( this.urlPrefixRegex, '' );
},
/**
* Removes any trailing slash from the given `anchorText`, in prepration for the text to be displayed.
*
* @private
* @param {String} anchorText The text of the anchor that is being generated, for which to remove any trailing
* slash ('/') that may exist.
* @return {String} The `anchorText`, with the trailing slash removed.
*/
removeTrailingSlash : function( anchorText ) {
if( anchorText.charAt( anchorText.length - 1 ) === '/' ) {
anchorText = anchorText.slice( 0, -1 );
}
return anchorText;
},
/**
* Performs the truncation of the `anchorText`, if the `anchorText` is longer than the {@link #truncate} option.
* Truncates the text to 2 characters fewer than the {@link #truncate} option, and adds ".." to the end.
*
* @private
* @param {String} text The anchor tag's text (i.e. what will be displayed).
* @return {String} The truncated anchor text.
*/
doTruncate : function( anchorText ) {
var truncateLen = this.truncate;
// Truncate the anchor text if it is longer than the provided 'truncate' option
if( truncateLen && anchorText.length > truncateLen ) {
anchorText = anchorText.substring( 0, truncateLen - 2 ) + '..';
}
return anchorText;
}
};
/**
* Automatically links URLs, email addresses, and Twitter handles found in the given chunk of HTML.
* Does not link URLs found within HTML tags.
*
* For instance, if given the text: `You should go to http://www.yahoo.com`, then the result
* will be `You should go to &lt;a href="http://www.yahoo.com"&gt;http://www.yahoo.com&lt;/a&gt;`
*
* Example:
*
* var linkedText = Autolinker.link( "Go to google.com", { newWindow: false } );
* // Produces: "Go to <a href="http://google.com">google.com</a>"
*
* @static
* @method link
* @param {String} html The HTML text to link URLs within.
* @param {Object} [options] Any of the configuration options for the Autolinker class, specified in an Object (map).
* See the class description for an example call.
* @return {String} The HTML text, with URLs automatically linked
*/
Autolinker.link = function( text, options ) {
var autolinker = new Autolinker( options );
return autolinker.link( text );
};
return Autolinker;
} ) );

10
vendors/Autolinker/Autolinker.min.js vendored Normal file

File diff suppressed because one or more lines are too long

22
vendors/Autolinker/LICENSE vendored Normal file
View file

@ -0,0 +1,22 @@
The MIT License (MIT)
Copyright (c) 2014 Gregory Jacobs (http://greg-jacobs.com)
Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the
"Software"), to deal in the Software without restriction, including
without limitation the rights to use, copy, modify, merge, publish,
distribute, sublicense, and/or sell copies of the Software, and to
permit persons to whom the Software is furnished to do so, subject to
the following conditions:
The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

255
vendors/Autolinker/README.md vendored Normal file
View file

@ -0,0 +1,255 @@
# Autolinker.js
Because I had so much trouble finding a good autolinking implementation out in the wild, I decided to roll my own. It
seemed that everything I found out there was either an implementation that didn't cover every case, or was just limited
in one way or another.
So, this utility attempts to handle everything. It:
- Autolinks URLs, whether or not they start with the protocol (i.e. 'http://'). In other words, it will automatically link the
text "google.com", as well as "http://google.com".
- Will properly handle URLs with special characters
- Will properly handle URLs with query parameters or a named anchor (i.e. hash)
- Will autolink email addresses.
- Will autolink Twitter handles.
- Will properly handle HTML input. The utility will not change the `href` attribute inside anchor (&lt;a&gt;) tags (or any other
tag/attribute for that matter), and will not accidentally wrap the inner text of an anchor tag with a new one (which would cause
doubly-nested anchor tags).
Hope that this utility helps you as well!
## Installation
#### Download
Simply clone or download the zip of the project, and link to either `dist/Autolinker.js` or `dist/Autolinker.min.js` with a script tag:
```html
<script src="path/to/Autolinker.min.js"></script>
```
#### Using with the [Bower](http://bower.io) package manager:
Command line:
```shell
bower install Autolinker.js --save
```
#### Using with [Node.js](http://nodejs.org) via [npm](https://www.npmjs.org/):
Command Line:
```shell
npm install autolinker --save
```
JavaScript:
```javascript
var Autolinker = require( 'autolinker' );
// note: npm wants an all-lowercase package name, but the utility is a class and should be
// aliased with a captial letter
```
## Usage
Using the static `link()` method:
```javascript
var linkedText = Autolinker.link( textToAutolink[, options] );
```
Using as a class:
```javascript
var autolinker = new Autolinker( [ options ] );
var linkedText = autolinker.link( textToAutoLink );
```
#### Example:
```javascript
var linkedText = Autolinker.link( "Check out google.com", { className: "myLink" } );
// Produces: "Check out <a class="myLink myLink-url" href="http://google.com" target="_blank">google.com</a>"
```
## Options
These are the options which may be specified for linking. These are specified by providing an Object as the second parameter to `Autolinker.link()`. These include:
- **newWindow** : Boolean<br />
`true` to have the links should open in a new window when clicked, `false` otherwise. Defaults to `true`.<br /><br />
- **stripPrefix** : Boolean<br />
`true` to have the 'http://' or 'https://' and/or the 'www.' stripped from the beginning of links, `false` otherwise. Defaults to `true`.<br /><br />
- **truncate** : Number<br />
A number for how many characters long URLs/emails/twitter handles should be truncated to inside the text of a link. If the URL/email/twitter is over the number of characters, it will be truncated to this length by replacing the end of the string with a two period ellipsis ('..').<br /><br />
Example: a url like 'http://www.yahoo.com/some/long/path/to/a/file' truncated to 25 characters may look like this: 'yahoo.com/some/long/pat..'<br />
- **className** : String<br />
A CSS class name to add to the generated anchor tags. This class will be added to all links, as well as this class
plus "url"/"email"/"twitter" suffixes for styling url/email/twitter links differently.
For example, if this config is provided as "myLink", then:
1) URL links will have the CSS classes: "myLink myLink-url"<br />
2) Email links will have the CSS classes: "myLink myLink-email", and<br />
3) Twitter links will have the CSS classes: "myLink myLink-twitter"<br />
- **urls** : Boolean<br />
`true` to have URLs auto-linked, `false` to skip auto-linking of URLs. Defaults to `true`.<br />
- **email** : Boolean<br />
`true` to have email addresses auto-linked, `false` to skip auto-linking of email addresses. Defaults to `true`.<br /><br />
- **twitter** : Boolean<br />
`true` to have Twitter handles auto-linked, `false` to skip auto-linking of Twitter handles. Defaults to `true`.
For example, if you wanted to disable links from opening in new windows, you could do:
```javascript
var linkedText = Autolinker.link( "Check out google.com", { newWindow: false } );
// Produces: "Check out <a href="http://google.com">google.com</a>"
```
And if you wanted to truncate the length of URLs (while also not opening in a new window), you could do:
```javascript
var linkedText = Autolinker.link( "http://www.yahoo.com/some/long/path/to/a/file", { truncate: 25, newWindow: false } );
// Produces: "<a href="http://www.yahoo.com/some/long/path/to/a/file">yahoo.com/some/long/pat..</a>"
```
## More Examples
One could update an entire DOM element that has unlinked text to auto-link them as such:
```javascript
var myTextEl = document.getElementById( 'text' );
myTextEl.innerHTML = Autolinker.link( myTextEl.innerHTML );
```
Using the same pre-configured Autolinker instance in multiple locations of a codebase (usually by dependency injection):
```javascript
var autolinker = new Autolinker( { newWindow: false, truncate: 25 } );
//...
autolinker.link( "Check out http://www.yahoo.com/some/long/path/to/a/file" );
// Produces: "Check out <a href="http://www.yahoo.com/some/long/path/to/a/file">yahoo.com/some/long/pat..</a>"
//...
autolinker.link( "Go to www.google.com" );
// Produces: "Go to <a href="http://www.google.com">google.com</a>"
```
## Changelog:
### 0.11.0
- Allow Autolinker to link fully-capitalized URLs/Emails/Twitter handles.
### 0.10.1
- Added fix to not autolink strings like "version:1.0", which were accidentally being interpreted as a protocol:domain string.
### 0.10.0
- Added support for protocol-relative URLs (ex: `//google.com`, which will effectively either have the `http://` or `https://`
protocol depending on the protocol that is hosting the website)
### 0.9.4
- Fixed an issue where a string in the form of `abc:def` would be autolinked as a protocol and domain name URL. Autolinker now
requires the domain name to have at least one period in it to be considered.
### 0.9.3
- Fixed an issue where Twitter handles wouldn't be autolinked if they existed as the sole entity within parenthesis or brackets
(thanks [@busticated](https://github.com/busticated) for pointing this out and providing unit tests)
### 0.9.2
- Fixed an issue with nested tags within an existing &lt;a&gt; tag, where the nested tags' inner text would be accidentally
removed from the output (thanks [@mjsabin01](https://github.com/mjsabin01))
### 0.9.1
- Added a patch to attempt to better handle extraneous &lt;/a&gt; tags in the input string if any exist. This is for when the
input may have some invalid markup (for instance, on sites which allow user comments, blog posts, etc.).
### 0.9.0
- Added better support for the processing of existing HTML in the input string. Now handles namespaced tags, and attribute names
with dashes or any other Unicode character (thanks [@aziraphale](https://github.com/aziraphale))
### 0.8.0
- Added `className` option for easily styling produced links (thanks [@busticated](https://github.com/busticated))
- Refactored into a JS class. Autolinker can now be instantiated using:
```javascript
var autolinker = new Autolinker( { newWindow: false, truncate: 25 } );
autolinker.link( "Check out http://www.yahoo.com/some/long/path/to/a/file" );
// Produces: "Check out <a href="http://www.yahoo.com/some/long/path/to/a/file">yahoo.com/some/long/pat..</a>"
```
This allows options to be set on a single instance, and used throughout a codebase by injecting the `autolinker` instance as a dependency to the modules/classes that use it. (Note: Autolinker may still be used with the static `Autolinker.link()` method as was previously available as well.)
### 0.7.0
- Changed build system to Grunt.
- Added AMD and CommonJS module loading support (ex: RequireJS, and Node.js's module loader).
- Added command line Jasmine test runner (`grunt test`)
- Upgraded Jasmine from 1.3.1 to 2.0
- Added license header to dist files.
(Thanks to [@busticated](https://github.com/busticated)!)
### 0.6.1
- Added LICENSE file to repository.
### 0.6.0
- Added options for granular control of which types are linked (urls, email addresses, and/or twitter handles).
(thanks [@aziraphale](https://github.com/aziraphale))
### 0.5.0
- Simplified the path / query string / hash processing into a single regular expression instead of 3 separate ones.
- Added support for parenthesis in URLs, such as: `en.wikipedia.org/wiki/IANA_(disambiguation)` (thanks [@dandv](https://github.com/dandv))
- Add all known top-level domains (TLDs) (thanks [@wouter0100](https://github.com/wouter0100))
### 0.4.0
Merged pull requests from [@afeld](https://github.com/afeld):
- strip protocol and 'www.' by default - fixes #1
- truncate URLs from the end
- make simpler regex for detecting prefix
- remove trailing slashes from URLs, and handle periods at the end of paths
- re-use domain+TLD regexes for email matching
- add .me and .io to list of TLDs
Thanks Aidan :)
### 0.3.1
- Fixed an issue with handling nested HTML tags within anchor tags.
### 0.3
- Implemented the `truncate` option.
### 0.2
- Implemented autolinking Twitter handles.
### 0.1
* Initial implementation, which autolinks URLs and email addresses. Working on linking Twitter handles.

View file

@ -20,6 +20,7 @@ module.exports = {
extensions: ['', '.js'],
alias: {
"ko": __dirname + "/dev/External/ko.js",
"openpgp": __dirname + "/vendors/openpgp/openpgp-0.7.2.min.js",
"Knoin:AbstractBoot": __dirname + "/dev/Knoin/KnoinAbstractBoot.js",
"Knoin:AbstractScreen": __dirname + "/dev/Knoin/KnoinAbstractScreen.js",
@ -31,20 +32,6 @@ module.exports = {
"App:RainLoop": __dirname + "/dev/Apps/RainLoopApp.js",
"App:Admin": __dirname + "/dev/Apps/AdminApp.js",
"Model:Account": __dirname + "/dev/Models/AccountModel.js",
"Model:Attachment": __dirname + "/dev/Models/AttachmentModel.js",
"Model:ComposeAttachment": __dirname + "/dev/Models/ComposeAttachmentModel.js",
"Model:Contact": __dirname + "/dev/Models/ContactModel.js",
"Model:ContactProperty": __dirname + "/dev/Models/ContactPropertyModel.js",
"Model:ContactTag": __dirname + "/dev/Models/ContactTagModel.js",
"Model:Email": __dirname + "/dev/Models/EmailModel.js",
"Model:Filter": __dirname + "/dev/Models/FilterModel.js",
"Model:FilterCondition": __dirname + "/dev/Models/FilterConditionModel.js",
"Model:Folder": __dirname + "/dev/Models/FolderModel.js",
"Model:Identity": __dirname + "/dev/Models/IdentityModel.js",
"Model:Message": __dirname + "/dev/Models/MessageModel.js",
"Model:OpenPgpKey": __dirname + "/dev/Models/OpenPgpKeyModel.js",
"Storage:LocalStorage": __dirname + "/dev/Storages/LocalStorage.js",
"Storage:LocalStorage:Cookie": __dirname + "/dev/Storages/LocalStorages/CookieDriver.js",
"Storage:LocalStorage:LocalStorage": __dirname + "/dev/Storages/LocalStorages/LocalStorageDriver.js",
@ -137,8 +124,9 @@ module.exports = {
'moment': 'moment',
'ifvisible': 'ifvisible',
'crossroads': 'crossroads',
'Jua': 'Jua',
'hasher': 'hasher',
'Jua': 'Jua',
'Autolinker': 'Autolinker',
'ssm': 'ssm',
'key': 'key',
'_': '_',