mirror of
https://github.com/the-djmaze/snappymail.git
synced 2025-09-21 12:34:22 +08:00
Fixed Opening an email with specific content „hangs” RainLoop in the browser (Closes #308)
Code refactoring
This commit is contained in:
parent
af43329902
commit
7a374ebe03
40 changed files with 1100 additions and 181 deletions
|
@ -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
|
||||
{
|
||||
|
|
38
dev/Boot.js
38
dev/Boot.js
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -118,7 +118,7 @@
|
|||
/**
|
||||
* @type {*}
|
||||
*/
|
||||
Globals.__APP = null;
|
||||
Globals.__APP__ = null;
|
||||
|
||||
/**
|
||||
* @type {Object}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
};
|
||||
|
||||
|
|
|
@ -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, '>').replace(/</g, '<')
|
||||
.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(/&/ig, 'amp_amp_12345_amp_amp'))
|
||||
.linkify()
|
||||
.find('.linkified').removeClass('linkified').end()
|
||||
.html()
|
||||
.replace(/amp_amp_12345_amp_amp/g, '&')
|
||||
;
|
||||
}
|
||||
sHtml = Autolinker.link(sHtml, {
|
||||
'newWindow': true,
|
||||
'stripPrefix': false,
|
||||
'urls': true,
|
||||
'email': true,
|
||||
'twitter': false
|
||||
});
|
||||
|
||||
// if ($.fn && $.fn.linkify)
|
||||
// {
|
||||
// sHtml = Globals.$div.html(sHtml.replace(/&/ig, 'amp_amp_12345_amp_amp'))
|
||||
// .linkify()
|
||||
// .find('.linkified').removeClass('linkified').end()
|
||||
// .html()
|
||||
// .replace(/amp_amp_12345_amp_amp/g, '&')
|
||||
// ;
|
||||
// }
|
||||
|
||||
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
5
dev/External/ko.js
vendored
|
@ -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(),
|
||||
|
|
|
@ -8,7 +8,7 @@
|
|||
|
||||
Enums = require('Common/Enums'),
|
||||
Utils = require('Common/Utils'),
|
||||
FilterConditionModel = require('Model:FilterCondition')
|
||||
FilterConditionModel = require('Model/FilterCondition')
|
||||
;
|
||||
|
||||
/**
|
|
@ -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);
|
|
@ -47,7 +47,7 @@
|
|||
SettingsContacts.prototype.onBuild = function ()
|
||||
{
|
||||
Data.contactsAutosave.subscribe(function (bValue) {
|
||||
Remote.saveSettings(Utils.emptyFunction, {
|
||||
Remote.saveSettings(null, {
|
||||
'ContactsAutosave': bValue ? '1' : '0'
|
||||
});
|
||||
});
|
||||
|
|
|
@ -30,7 +30,7 @@
|
|||
SettingsFilters.prototype.addFilter = function ()
|
||||
{
|
||||
var
|
||||
FilterModel = require('Model:Filter')
|
||||
FilterModel = require('Model/Filter')
|
||||
;
|
||||
|
||||
require('App:Knoin').showScreenPopup(
|
||||
|
|
|
@ -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'
|
||||
});
|
||||
});
|
||||
|
|
|
@ -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;
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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];
|
||||
|
|
|
@ -381,10 +381,7 @@
|
|||
}
|
||||
else if (Data.useThreads())
|
||||
{
|
||||
if (Globals.__APP)
|
||||
{
|
||||
Globals.__APP.reloadFlagsCurrentMessageListAndMessageFromCache();
|
||||
}
|
||||
require('App:RainLoop').reloadFlagsCurrentMessageListAndMessageFromCache();
|
||||
}
|
||||
};
|
||||
|
||||
|
|
|
@ -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);
|
||||
|
||||
|
|
|
@ -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())
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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')
|
||||
|
|
|
@ -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')
|
||||
|
|
|
@ -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);
|
||||
|
||||
|
|
|
@ -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())
|
||||
|
|
|
@ -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',
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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
607
vendors/Autolinker/Autolinker.js
vendored
Normal 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 (<a>) 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 <a href="http://www.yahoo.com">http://www.yahoo.com</a>`
|
||||
*
|
||||
* @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 (<a>) 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 (<a>) 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 <a href="http://www.yahoo.com">http://www.yahoo.com</a>`
|
||||
*
|
||||
* 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
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
22
vendors/Autolinker/LICENSE
vendored
Normal 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
255
vendors/Autolinker/README.md
vendored
Normal 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 (<a>) 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 <a> 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 </a> 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.
|
|
@ -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',
|
||||
'_': '_',
|
||||
|
|
Loading…
Add table
Reference in a new issue