mirror of
https://github.com/the-djmaze/snappymail.git
synced 2026-07-13 03:27:39 +03:00
More fixes for scrutinizer-ci
This commit is contained in:
parent
c7e97b78d6
commit
59b32241bf
16 changed files with 116 additions and 191 deletions
44
dev/External/ko.js
vendored
44
dev/External/ko.js
vendored
|
|
@ -4,7 +4,6 @@ var
|
||||||
ko = window.ko,
|
ko = window.ko,
|
||||||
_ = require('_'),
|
_ = require('_'),
|
||||||
$ = require('$'),
|
$ = require('$'),
|
||||||
JSON = require('JSON'),
|
|
||||||
Opentip = require('Opentip'),
|
Opentip = require('Opentip'),
|
||||||
Pikaday = require('pikaday'),
|
Pikaday = require('pikaday'),
|
||||||
|
|
||||||
|
|
@ -83,10 +82,10 @@ ko.bindingHandlers.editor = {
|
||||||
|
|
||||||
ko.bindingHandlers.json = {
|
ko.bindingHandlers.json = {
|
||||||
init: function(oElement, fValueAccessor) {
|
init: function(oElement, fValueAccessor) {
|
||||||
$(oElement).text(JSON.stringify(ko.unwrap(fValueAccessor())));
|
$(oElement).text(window.JSON.stringify(ko.unwrap(fValueAccessor())));
|
||||||
},
|
},
|
||||||
update: function(oElement, fValueAccessor) {
|
update: function(oElement, fValueAccessor) {
|
||||||
$(oElement).text(JSON.stringify(ko.unwrap(fValueAccessor())));
|
$(oElement).text(window.JSON.stringify(ko.unwrap(fValueAccessor())));
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -198,8 +197,6 @@ ko.bindingHandlers.tooltip = {
|
||||||
update: function(oElement, fValueAccessor) {
|
update: function(oElement, fValueAccessor) {
|
||||||
|
|
||||||
var
|
var
|
||||||
bi18n = true,
|
|
||||||
sValue = '',
|
|
||||||
$oEl = $(oElement),
|
$oEl = $(oElement),
|
||||||
fValue = fValueAccessor(),
|
fValue = fValueAccessor(),
|
||||||
bMobile = 'on' === ($oEl.data('tooltip-mobile') || 'off'),
|
bMobile = 'on' === ($oEl.data('tooltip-mobile') || 'off'),
|
||||||
|
|
@ -207,13 +204,11 @@ ko.bindingHandlers.tooltip = {
|
||||||
|
|
||||||
if ((!Globals.bMobileDevice || bMobile) && oElement.__opentip)
|
if ((!Globals.bMobileDevice || bMobile) && oElement.__opentip)
|
||||||
{
|
{
|
||||||
bi18n = 'on' === ($oEl.data('tooltip-i18n') || 'on');
|
var sValue = !ko.isObservable(fValue) && _.isFunction(fValue) ? fValue() : ko.unwrap(fValue);
|
||||||
sValue = !ko.isObservable(fValue) && _.isFunction(fValue) ? fValue() : ko.unwrap(fValue);
|
|
||||||
|
|
||||||
if (sValue)
|
if (sValue)
|
||||||
{
|
{
|
||||||
oElement.__opentip.setContent(
|
var i18n = 'on' === ($oEl.data('tooltip-i18n') || 'on');
|
||||||
bi18n ? require('Common/Translator').i18n(sValue) : sValue);
|
oElement.__opentip.setContent(i18n ? require('Common/Translator').i18n(sValue) : sValue);
|
||||||
oElement.__opentip.activate();
|
oElement.__opentip.activate();
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
|
|
@ -636,8 +631,6 @@ ko.bindingHandlers.draggable = {
|
||||||
|
|
||||||
$(sDroppableSelector).each(function() {
|
$(sDroppableSelector).each(function() {
|
||||||
var
|
var
|
||||||
moveUp = null,
|
|
||||||
moveDown = null,
|
|
||||||
$this = $(this),
|
$this = $(this),
|
||||||
oOffset = $this.offset(),
|
oOffset = $this.offset(),
|
||||||
bottomPos = oOffset.top + $this.height();
|
bottomPos = oOffset.top + $this.height();
|
||||||
|
|
@ -649,7 +642,7 @@ ko.bindingHandlers.draggable = {
|
||||||
{
|
{
|
||||||
if (oEvent.pageY >= bottomPos - iTriggerZone && oEvent.pageY <= bottomPos)
|
if (oEvent.pageY >= bottomPos - iTriggerZone && oEvent.pageY <= bottomPos)
|
||||||
{
|
{
|
||||||
moveUp = function() {
|
var moveUp = function() {
|
||||||
$this.scrollTop($this.scrollTop() + iScrollSpeed);
|
$this.scrollTop($this.scrollTop() + iScrollSpeed);
|
||||||
Utils.windowResize();
|
Utils.windowResize();
|
||||||
};
|
};
|
||||||
|
|
@ -660,7 +653,7 @@ ko.bindingHandlers.draggable = {
|
||||||
|
|
||||||
if (oEvent.pageY >= oOffset.top && oEvent.pageY <= oOffset.top + iTriggerZone)
|
if (oEvent.pageY >= oOffset.top && oEvent.pageY <= oOffset.top + iTriggerZone)
|
||||||
{
|
{
|
||||||
moveDown = function() {
|
var moveDown = function() {
|
||||||
$this.scrollTop($this.scrollTop() - iScrollSpeed);
|
$this.scrollTop($this.scrollTop() - iScrollSpeed);
|
||||||
Utils.windowResize();
|
Utils.windowResize();
|
||||||
};
|
};
|
||||||
|
|
@ -869,19 +862,14 @@ ko.bindingHandlers.emailsTags = {
|
||||||
'parseHook': function(aInput) {
|
'parseHook': function(aInput) {
|
||||||
|
|
||||||
return _.map(aInput, function(sInputValue) {
|
return _.map(aInput, function(sInputValue) {
|
||||||
|
var value = Utils.trim(sInputValue);
|
||||||
var
|
if ('' !== value)
|
||||||
sValue = Utils.trim(sInputValue),
|
|
||||||
oEmail = null;
|
|
||||||
|
|
||||||
if ('' !== sValue)
|
|
||||||
{
|
{
|
||||||
oEmail = new EmailModel();
|
var email = new EmailModel();
|
||||||
oEmail.mailsoParse(sValue);
|
email.mailsoParse(value);
|
||||||
return [oEmail.toLine(false), oEmail];
|
return [email.toLine(false), email];
|
||||||
}
|
}
|
||||||
|
return [value, null];
|
||||||
return [sValue, null];
|
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
@ -976,15 +964,13 @@ ko.bindingHandlers.command = {
|
||||||
ko.bindingHandlers[jqElement.is('form') ? 'submit' : 'click']
|
ko.bindingHandlers[jqElement.is('form') ? 'submit' : 'click']
|
||||||
.init.apply(oViewModel, Array.prototype.slice.call(arguments)); // eslint-disable-line prefer-rest-params
|
.init.apply(oViewModel, Array.prototype.slice.call(arguments)); // eslint-disable-line prefer-rest-params
|
||||||
},
|
},
|
||||||
|
|
||||||
update: function(oElement, fValueAccessor) {
|
update: function(oElement, fValueAccessor) {
|
||||||
|
|
||||||
var
|
var
|
||||||
bResult = true,
|
|
||||||
jqElement = $(oElement),
|
jqElement = $(oElement),
|
||||||
oCommand = fValueAccessor();
|
oCommand = fValueAccessor(),
|
||||||
|
|
||||||
bResult = oCommand.enabled();
|
bResult = oCommand.enabled();
|
||||||
|
|
||||||
jqElement.toggleClass('command-not-enabled', !bResult);
|
jqElement.toggleClass('command-not-enabled', !bResult);
|
||||||
|
|
||||||
if (bResult)
|
if (bResult)
|
||||||
|
|
|
||||||
|
|
@ -56,9 +56,7 @@ AbstractAjaxPromises.prototype.ajaxRequest = function(sAction, bPost, iTimeOut,
|
||||||
var self = this;
|
var self = this;
|
||||||
return new Promise(function(resolve, reject) {
|
return new Promise(function(resolve, reject) {
|
||||||
|
|
||||||
var
|
var iStart = Utils.microtime();
|
||||||
oH = null,
|
|
||||||
iStart = Utils.microtime();
|
|
||||||
|
|
||||||
iTimeOut = Utils.isNormal(iTimeOut) ? iTimeOut : Consts.DEFAULT_AJAX_TIMEOUT;
|
iTimeOut = Utils.isNormal(iTimeOut) ? iTimeOut : Consts.DEFAULT_AJAX_TIMEOUT;
|
||||||
sAdditionalGetString = Utils.isUnd(sAdditionalGetString) ? '' : Utils.pString(sAdditionalGetString);
|
sAdditionalGetString = Utils.isUnd(sAdditionalGetString) ? '' : Utils.pString(sAdditionalGetString);
|
||||||
|
|
@ -72,7 +70,7 @@ AbstractAjaxPromises.prototype.ajaxRequest = function(sAction, bPost, iTimeOut,
|
||||||
|
|
||||||
self.setTrigger(fTrigger, true);
|
self.setTrigger(fTrigger, true);
|
||||||
|
|
||||||
oH = $.ajax({
|
var oH = $.ajax({
|
||||||
type: bPost ? 'POST' : 'GET',
|
type: bPost ? 'POST' : 'GET',
|
||||||
url: Links.ajax(sAdditionalGetString),
|
url: Links.ajax(sAdditionalGetString),
|
||||||
async: true,
|
async: true,
|
||||||
|
|
@ -81,10 +79,10 @@ AbstractAjaxPromises.prototype.ajaxRequest = function(sAction, bPost, iTimeOut,
|
||||||
timeout: iTimeOut,
|
timeout: iTimeOut,
|
||||||
global: true
|
global: true
|
||||||
}).always(function(oData, sTextStatus) {
|
}).always(function(oData, sTextStatus) {
|
||||||
|
|
||||||
var
|
var
|
||||||
bCached = false,
|
bCached = false,
|
||||||
oErrorData = null,
|
oErrorData = null;
|
||||||
sType = Enums.StorageResultType.Error;
|
|
||||||
|
|
||||||
if (oData && oData.Time)
|
if (oData && oData.Time)
|
||||||
{
|
{
|
||||||
|
|
@ -92,6 +90,7 @@ AbstractAjaxPromises.prototype.ajaxRequest = function(sAction, bPost, iTimeOut,
|
||||||
}
|
}
|
||||||
|
|
||||||
// backward capability
|
// backward capability
|
||||||
|
var sType = Enums.StorageResultType.Error;
|
||||||
switch (true)
|
switch (true)
|
||||||
{
|
{
|
||||||
case 'success' === sTextStatus && oData && oData.Result && sAction === oData.Action:
|
case 'success' === sTextStatus && oData && oData.Result && sAction === oData.Action:
|
||||||
|
|
@ -100,7 +99,9 @@ AbstractAjaxPromises.prototype.ajaxRequest = function(sAction, bPost, iTimeOut,
|
||||||
case 'abort' === sTextStatus && (!oData || !oData.__aborted__):
|
case 'abort' === sTextStatus && (!oData || !oData.__aborted__):
|
||||||
sType = Enums.StorageResultType.Abort;
|
sType = Enums.StorageResultType.Abort;
|
||||||
break;
|
break;
|
||||||
// no default
|
default:
|
||||||
|
sType = Enums.StorageResultType.Error;
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
Plugins.runHook('ajax-default-response', [sAction,
|
Plugins.runHook('ajax-default-response', [sAction,
|
||||||
|
|
|
||||||
|
|
@ -57,30 +57,20 @@ PromisesUserPopulator.prototype.folderResponseParseRec = function(sNamespace, aF
|
||||||
{
|
{
|
||||||
var
|
var
|
||||||
self = this,
|
self = this,
|
||||||
iIndex = 0,
|
|
||||||
iLen = 0,
|
|
||||||
oFolder = null,
|
|
||||||
oCacheFolder = null,
|
|
||||||
bDisplaySpecSetting = FolderStore.displaySpecSetting(),
|
bDisplaySpecSetting = FolderStore.displaySpecSetting(),
|
||||||
sFolderFullNameRaw = '',
|
|
||||||
aSubFolders = [],
|
|
||||||
aList = [];
|
aList = [];
|
||||||
|
|
||||||
for (iIndex = 0, iLen = aFolders.length; iIndex < iLen; iIndex++)
|
_.each(aFolders, function(oFolder) {
|
||||||
{
|
|
||||||
oFolder = aFolders[iIndex];
|
|
||||||
if (oFolder)
|
if (oFolder)
|
||||||
{
|
{
|
||||||
sFolderFullNameRaw = oFolder.FullNameRaw;
|
var oCacheFolder = Cache.getFolderFromCacheList(oFolder.FullNameRaw);
|
||||||
|
|
||||||
oCacheFolder = Cache.getFolderFromCacheList(sFolderFullNameRaw);
|
|
||||||
if (!oCacheFolder)
|
if (!oCacheFolder)
|
||||||
{
|
{
|
||||||
oCacheFolder = FolderModel.newInstanceFromJson(oFolder);
|
oCacheFolder = FolderModel.newInstanceFromJson(oFolder);
|
||||||
if (oCacheFolder)
|
if (oCacheFolder)
|
||||||
{
|
{
|
||||||
Cache.setFolderToCacheList(sFolderFullNameRaw, oCacheFolder);
|
Cache.setFolderToCacheList(oFolder.FullNameRaw, oCacheFolder);
|
||||||
Cache.setFolderFullNameRaw(oCacheFolder.fullNameHash, sFolderFullNameRaw, oCacheFolder);
|
Cache.setFolderFullNameRaw(oCacheFolder.fullNameHash, oFolder.FullNameRaw, oCacheFolder);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -115,18 +105,17 @@ PromisesUserPopulator.prototype.folderResponseParseRec = function(sNamespace, aF
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
aSubFolders = oFolder.SubFolders;
|
if (oFolder.SubFolders && 'Collection/FolderCollection' === oFolder.SubFolders['@Object'] &&
|
||||||
if (aSubFolders && 'Collection/FolderCollection' === aSubFolders['@Object'] &&
|
oFolder.SubFolders['@Collection'] && Utils.isArray(oFolder.SubFolders['@Collection']))
|
||||||
aSubFolders['@Collection'] && Utils.isArray(aSubFolders['@Collection']))
|
|
||||||
{
|
{
|
||||||
oCacheFolder.subFolders(
|
oCacheFolder.subFolders(
|
||||||
this.folderResponseParseRec(sNamespace, aSubFolders['@Collection'], expandedFolders));
|
self.folderResponseParseRec(sNamespace, oFolder.SubFolders['@Collection'], expandedFolders));
|
||||||
}
|
}
|
||||||
|
|
||||||
aList.push(oCacheFolder);
|
aList.push(oCacheFolder);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
});
|
||||||
|
|
||||||
return aList;
|
return aList;
|
||||||
};
|
};
|
||||||
|
|
@ -137,7 +126,6 @@ PromisesUserPopulator.prototype.foldersList = function(oData)
|
||||||
oData['@Collection'] && Utils.isArray(oData['@Collection']))
|
oData['@Collection'] && Utils.isArray(oData['@Collection']))
|
||||||
{
|
{
|
||||||
var
|
var
|
||||||
folderList = [],
|
|
||||||
expandedFolders = Local.get(Enums.ClientSideKeyName.ExpandedFolders),
|
expandedFolders = Local.get(Enums.ClientSideKeyName.ExpandedFolders),
|
||||||
iLimit = Utils.pInt(Settings.appSettingsGet('folderSpecLimit')),
|
iLimit = Utils.pInt(Settings.appSettingsGet('folderSpecLimit')),
|
||||||
iC = Utils.pInt(oData.CountRec);
|
iC = Utils.pInt(oData.CountRec);
|
||||||
|
|
@ -145,8 +133,9 @@ PromisesUserPopulator.prototype.foldersList = function(oData)
|
||||||
iLimit = 100 < iLimit ? 100 : (10 > iLimit ? 10 : iLimit);
|
iLimit = 100 < iLimit ? 100 : (10 > iLimit ? 10 : iLimit);
|
||||||
|
|
||||||
FolderStore.displaySpecSetting(0 >= iC || iLimit < iC);
|
FolderStore.displaySpecSetting(0 >= iC || iLimit < iC);
|
||||||
folderList = this.folderResponseParseRec(Utils.isUnd(oData.Namespace) ? '' : oData.Namespace, oData['@Collection'], expandedFolders);
|
|
||||||
FolderStore.folderList(folderList); // @todo optimization required
|
FolderStore.folderList(this.folderResponseParseRec(
|
||||||
|
Utils.isUnd(oData.Namespace) ? '' : oData.Namespace, oData['@Collection'], expandedFolders)); // @todo optimization required
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -135,7 +135,6 @@ AbstractAjaxRemote.prototype.ajaxRequest = function(fResultCallback, oParameters
|
||||||
bPost = '' === sGetAdd,
|
bPost = '' === sGetAdd,
|
||||||
oHeaders = {},
|
oHeaders = {},
|
||||||
iStart = (new window.Date()).getTime(),
|
iStart = (new window.Date()).getTime(),
|
||||||
oDefAjax = null,
|
|
||||||
sAction = '';
|
sAction = '';
|
||||||
|
|
||||||
oParameters = oParameters || {};
|
oParameters = oParameters || {};
|
||||||
|
|
@ -165,7 +164,7 @@ AbstractAjaxRemote.prototype.ajaxRequest = function(fResultCallback, oParameters
|
||||||
oParameters.XToken = Settings.appSettingsGet('token');
|
oParameters.XToken = Settings.appSettingsGet('token');
|
||||||
}
|
}
|
||||||
|
|
||||||
oDefAjax = $.ajax({
|
var oDefAjax = $.ajax({
|
||||||
type: bPost ? 'POST' : 'GET',
|
type: bPost ? 'POST' : 'GET',
|
||||||
url: Links.ajax(sGetAdd),
|
url: Links.ajax(sGetAdd),
|
||||||
async: true,
|
async: true,
|
||||||
|
|
|
||||||
|
|
@ -41,23 +41,22 @@ MenuSettingsAdminView.prototype.onBuild = function(oDom)
|
||||||
|
|
||||||
var
|
var
|
||||||
sH = '',
|
sH = '',
|
||||||
iIndex = -1,
|
|
||||||
bUp = handler && 'up' === handler.shortcut,
|
bUp = handler && 'up' === handler.shortcut,
|
||||||
$items = $('.b-admin-menu .e-item', oDom);
|
$items = $('.b-admin-menu .e-item', oDom);
|
||||||
|
|
||||||
if (event && $items.length)
|
if (event && $items.length)
|
||||||
{
|
{
|
||||||
iIndex = $items.index($items.filter('.selected'));
|
var index = $items.index($items.filter('.selected'));
|
||||||
if (bUp && 0 < iIndex)
|
if (bUp && 0 < index)
|
||||||
{
|
{
|
||||||
iIndex -= 1;
|
index -= 1;
|
||||||
}
|
}
|
||||||
else if (!bUp && iIndex < $items.length - 1)
|
else if (!bUp && index < $items.length - 1)
|
||||||
{
|
{
|
||||||
iIndex += 1;
|
index += 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
sH = $items.eq(iIndex).attr('href');
|
sH = $items.eq(index).attr('href');
|
||||||
if (sH)
|
if (sH)
|
||||||
{
|
{
|
||||||
kn.setHash(sH, false, true);
|
kn.setHash(sH, false, true);
|
||||||
|
|
|
||||||
|
|
@ -30,27 +30,29 @@ function AddOpenPgpKeyPopupView()
|
||||||
|
|
||||||
var
|
var
|
||||||
count = 30,
|
count = 30,
|
||||||
match = null,
|
keyTrimmed = Utils.trim(this.key()),
|
||||||
key = Utils.trim(this.key()),
|
|
||||||
reg = /[\-]{3,6}BEGIN[\s]PGP[\s](PRIVATE|PUBLIC)[\s]KEY[\s]BLOCK[\-]{3,6}[\s\S]+?[\-]{3,6}END[\s]PGP[\s](PRIVATE|PUBLIC)[\s]KEY[\s]BLOCK[\-]{3,6}/gi,
|
reg = /[\-]{3,6}BEGIN[\s]PGP[\s](PRIVATE|PUBLIC)[\s]KEY[\s]BLOCK[\-]{3,6}[\s\S]+?[\-]{3,6}END[\s]PGP[\s](PRIVATE|PUBLIC)[\s]KEY[\s]BLOCK[\-]{3,6}/gi,
|
||||||
openpgpKeyring = PgpStore.openpgpKeyring,
|
openpgpKeyring = PgpStore.openpgpKeyring;
|
||||||
done = false;
|
|
||||||
|
|
||||||
if (/[\n]/.test(key))
|
if (/[\n]/.test(keyTrimmed))
|
||||||
{
|
{
|
||||||
key = key.replace(/[\r]+/g, '').replace(/[\n]{2,}/g, '\n\n');
|
keyTrimmed = keyTrimmed.replace(/[\r]+/g, '').replace(/[\n]{2,}/g, '\n\n');
|
||||||
}
|
}
|
||||||
|
|
||||||
this.key.error('' === key);
|
this.key.error('' === keyTrimmed);
|
||||||
|
|
||||||
if (!openpgpKeyring || this.key.error())
|
if (!openpgpKeyring || this.key.error())
|
||||||
{
|
{
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var
|
||||||
|
done = false,
|
||||||
|
match = null;
|
||||||
|
|
||||||
do
|
do
|
||||||
{
|
{
|
||||||
match = reg.exec(key);
|
match = reg.exec(keyTrimmed);
|
||||||
if (match && 0 < count)
|
if (match && 0 < count)
|
||||||
{
|
{
|
||||||
if (match[0] && match[1] && match[2] && match[1] === match[2])
|
if (match[0] && match[1] && match[2] && match[1] === match[2])
|
||||||
|
|
|
||||||
|
|
@ -5,7 +5,6 @@ var
|
||||||
$ = require('$'),
|
$ = require('$'),
|
||||||
ko = require('ko'),
|
ko = require('ko'),
|
||||||
key = require('key'),
|
key = require('key'),
|
||||||
JSON = require('JSON'),
|
|
||||||
Jua = require('Jua'),
|
Jua = require('Jua'),
|
||||||
|
|
||||||
Enums = require('Common/Enums'),
|
Enums = require('Common/Enums'),
|
||||||
|
|
@ -1481,7 +1480,7 @@ ComposePopupView.prototype.driveCallback = function(sAccessToken, oData)
|
||||||
if (oRequest && oRequest.responseText)
|
if (oRequest && oRequest.responseText)
|
||||||
{
|
{
|
||||||
var
|
var
|
||||||
oResponse = JSON.parse(oRequest.responseText),
|
oResponse = window.JSON.parse(oRequest.responseText),
|
||||||
fExport = function(oItem, sMimeType, sExt) {
|
fExport = function(oItem, sMimeType, sExt) {
|
||||||
if (oItem && oItem.exportLinks)
|
if (oItem && oItem.exportLinks)
|
||||||
{
|
{
|
||||||
|
|
@ -1849,12 +1848,10 @@ ComposePopupView.prototype.addMessageAsAttachment = function(oMessage)
|
||||||
{
|
{
|
||||||
if (oMessage)
|
if (oMessage)
|
||||||
{
|
{
|
||||||
var
|
var sTemp = oMessage.subject();
|
||||||
oAttachment = null,
|
|
||||||
sTemp = oMessage.subject();
|
|
||||||
|
|
||||||
sTemp = '.eml' === sTemp.substr(-4).toLowerCase() ? sTemp : sTemp + '.eml';
|
sTemp = '.eml' === sTemp.substr(-4).toLowerCase() ? sTemp : sTemp + '.eml';
|
||||||
oAttachment = new ComposeAttachmentModel(
|
|
||||||
|
var oAttachment = new ComposeAttachmentModel(
|
||||||
oMessage.requestHash, sTemp, oMessage.size()
|
oMessage.requestHash, sTemp, oMessage.size()
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -87,7 +87,6 @@ function ComposeOpenPgpPopupView()
|
||||||
var
|
var
|
||||||
bResult = true,
|
bResult = true,
|
||||||
oPrivateKey = null,
|
oPrivateKey = null,
|
||||||
aPrivateKeys = [],
|
|
||||||
aPublicKeys = [];
|
aPublicKeys = [];
|
||||||
|
|
||||||
this.submitRequest(true);
|
this.submitRequest(true);
|
||||||
|
|
@ -110,7 +109,7 @@ function ComposeOpenPgpPopupView()
|
||||||
|
|
||||||
if (bResult)
|
if (bResult)
|
||||||
{
|
{
|
||||||
aPrivateKeys = this.signKey().key.getNativeKeys();
|
var aPrivateKeys = this.signKey().key.getNativeKeys();
|
||||||
oPrivateKey = aPrivateKeys[0] || null;
|
oPrivateKey = aPrivateKeys[0] || null;
|
||||||
|
|
||||||
try
|
try
|
||||||
|
|
@ -248,22 +247,19 @@ function ComposeOpenPgpPopupView()
|
||||||
|
|
||||||
var
|
var
|
||||||
sKeyId = this.selectedPrivateKey(),
|
sKeyId = this.selectedPrivateKey(),
|
||||||
oKey = null,
|
|
||||||
oOption = sKeyId ? _.find(this.privateKeysOptions(), function(oItem) {
|
oOption = sKeyId ? _.find(this.privateKeysOptions(), function(oItem) {
|
||||||
return oItem && sKeyId === oItem.id;
|
return oItem && sKeyId === oItem.id;
|
||||||
}) : null;
|
}) : null;
|
||||||
|
|
||||||
if (oOption)
|
if (oOption)
|
||||||
{
|
{
|
||||||
oKey = {
|
this.signKey({
|
||||||
'empty': !oOption.key,
|
'empty': !oOption.key,
|
||||||
'selected': ko.observable(!!oOption.key),
|
'selected': ko.observable(!!oOption.key),
|
||||||
'users': oOption.key.users,
|
'users': oOption.key.users,
|
||||||
'hash': oOption.key.id.substr(-8).toUpperCase(),
|
'hash': oOption.key.id.substr(-8).toUpperCase(),
|
||||||
'key': oOption.key
|
'key': oOption.key
|
||||||
};
|
});
|
||||||
|
|
||||||
this.signKey(oKey);
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
@ -402,7 +398,6 @@ ComposeOpenPgpPopupView.prototype.onShow = function(fCallback, sText, oIdentity,
|
||||||
self = this,
|
self = this,
|
||||||
aRec = [],
|
aRec = [],
|
||||||
sEmail = '',
|
sEmail = '',
|
||||||
aKeys = [],
|
|
||||||
oEmail = new EmailModel();
|
oEmail = new EmailModel();
|
||||||
|
|
||||||
this.resultCallback = fCallback;
|
this.resultCallback = fCallback;
|
||||||
|
|
@ -433,7 +428,8 @@ ComposeOpenPgpPopupView.prototype.onShow = function(fCallback, sText, oIdentity,
|
||||||
{
|
{
|
||||||
sEmail = oIdentity.email();
|
sEmail = oIdentity.email();
|
||||||
aRec.unshift(sEmail);
|
aRec.unshift(sEmail);
|
||||||
aKeys = PgpStore.findAllPrivateKeysByEmailNotNative(sEmail);
|
|
||||||
|
var aKeys = PgpStore.findAllPrivateKeysByEmailNotNative(sEmail);
|
||||||
if (aKeys && aKeys[0])
|
if (aKeys && aKeys[0])
|
||||||
{
|
{
|
||||||
this.signKey({
|
this.signKey({
|
||||||
|
|
|
||||||
|
|
@ -38,18 +38,14 @@ function FolderSystemPopupView()
|
||||||
], null, null, null, null, null, true);
|
], null, null, null, null, null, true);
|
||||||
}, this);
|
}, this);
|
||||||
|
|
||||||
var
|
|
||||||
fSaveSystemFolders = null,
|
|
||||||
fCallback = null;
|
|
||||||
|
|
||||||
this.sentFolder = FolderStore.sentFolder;
|
this.sentFolder = FolderStore.sentFolder;
|
||||||
this.draftFolder = FolderStore.draftFolder;
|
this.draftFolder = FolderStore.draftFolder;
|
||||||
this.spamFolder = FolderStore.spamFolder;
|
this.spamFolder = FolderStore.spamFolder;
|
||||||
this.trashFolder = FolderStore.trashFolder;
|
this.trashFolder = FolderStore.trashFolder;
|
||||||
this.archiveFolder = FolderStore.archiveFolder;
|
this.archiveFolder = FolderStore.archiveFolder;
|
||||||
|
|
||||||
|
var
|
||||||
fSaveSystemFolders = _.debounce(function() {
|
fSaveSystemFolders = _.debounce(function() {
|
||||||
|
|
||||||
Settings.settingsSet('SentFolder', FolderStore.sentFolder());
|
Settings.settingsSet('SentFolder', FolderStore.sentFolder());
|
||||||
Settings.settingsSet('DraftFolder', FolderStore.draftFolder());
|
Settings.settingsSet('DraftFolder', FolderStore.draftFolder());
|
||||||
Settings.settingsSet('SpamFolder', FolderStore.spamFolder());
|
Settings.settingsSet('SpamFolder', FolderStore.spamFolder());
|
||||||
|
|
@ -67,8 +63,8 @@ function FolderSystemPopupView()
|
||||||
|
|
||||||
}, Enums.Magics.Time1s);
|
}, Enums.Magics.Time1s);
|
||||||
|
|
||||||
|
var
|
||||||
fCallback = function() {
|
fCallback = function() {
|
||||||
|
|
||||||
Settings.settingsSet('SentFolder', FolderStore.sentFolder());
|
Settings.settingsSet('SentFolder', FolderStore.sentFolder());
|
||||||
Settings.settingsSet('DraftFolder', FolderStore.draftFolder());
|
Settings.settingsSet('DraftFolder', FolderStore.draftFolder());
|
||||||
Settings.settingsSet('SpamFolder', FolderStore.spamFolder());
|
Settings.settingsSet('SpamFolder', FolderStore.spamFolder());
|
||||||
|
|
|
||||||
|
|
@ -39,15 +39,13 @@ function MessageOpenPgpPopupView()
|
||||||
|
|
||||||
_.delay(_.bind(function() {
|
_.delay(_.bind(function() {
|
||||||
|
|
||||||
var
|
var oPrivateKey = null;
|
||||||
oPrivateKeys = [],
|
|
||||||
oPrivateKey = null;
|
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
if (this.resultCallback && this.selectedKey())
|
if (this.resultCallback && this.selectedKey())
|
||||||
{
|
{
|
||||||
oPrivateKeys = this.selectedKey().getNativeKeys();
|
var oPrivateKeys = this.selectedKey().getNativeKeys();
|
||||||
oPrivateKey = oPrivateKeys && oPrivateKeys[0] ? oPrivateKeys[0] : null;
|
oPrivateKey = oPrivateKeys && oPrivateKeys[0] ? oPrivateKeys[0] : null;
|
||||||
|
|
||||||
if (oPrivateKey)
|
if (oPrivateKey)
|
||||||
|
|
|
||||||
|
|
@ -58,17 +58,13 @@ function NewOpenPgpKeyPopupView()
|
||||||
|
|
||||||
_.delay(function() {
|
_.delay(function() {
|
||||||
|
|
||||||
var mPromise = false;
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
|
||||||
mPromise = PgpStore.openpgp.generateKey({
|
PgpStore.openpgp.generateKey({
|
||||||
userIds: [oUserId],
|
userIds: [oUserId],
|
||||||
numBits: Utils.pInt(self.keyBitLength()),
|
numBits: Utils.pInt(self.keyBitLength()),
|
||||||
passphrase: Utils.trim(self.password())
|
passphrase: Utils.trim(self.password())
|
||||||
});
|
}).then(function(mKeyPair) {
|
||||||
|
|
||||||
mPromise.then(function(mKeyPair) {
|
|
||||||
|
|
||||||
self.submitRequest(false);
|
self.submitRequest(false);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -118,13 +118,10 @@ FolderListMailBoxUserView.prototype.onBuild = function(oDom)
|
||||||
oDom
|
oDom
|
||||||
.on('click', '.b-folders .e-item .e-link .e-collapsed-sign', function(oEvent) {
|
.on('click', '.b-folders .e-item .e-link .e-collapsed-sign', function(oEvent) {
|
||||||
|
|
||||||
var
|
var oFolder = ko.dataFor(this);
|
||||||
oFolder = ko.dataFor(this),
|
|
||||||
bCollapsed = false;
|
|
||||||
|
|
||||||
if (oFolder && oEvent)
|
if (oFolder && oEvent)
|
||||||
{
|
{
|
||||||
bCollapsed = oFolder.collapsed();
|
var bCollapsed = oFolder.collapsed();
|
||||||
require('App/User').default.setExpandedFolder(oFolder.fullNameHash, bCollapsed);
|
require('App/User').default.setExpandedFolder(oFolder.fullNameHash, bCollapsed);
|
||||||
|
|
||||||
oFolder.collapsed(!bCollapsed);
|
oFolder.collapsed(!bCollapsed);
|
||||||
|
|
@ -142,13 +139,12 @@ FolderListMailBoxUserView.prototype.onBuild = function(oDom)
|
||||||
key('up, down', Enums.KeyState.FolderList, function(event, handler) {
|
key('up, down', Enums.KeyState.FolderList, function(event, handler) {
|
||||||
|
|
||||||
var
|
var
|
||||||
iIndex = -1,
|
|
||||||
iKeyCode = handler && 'up' === handler.shortcut ? Enums.EventKeyCode.Up : Enums.EventKeyCode.Down,
|
iKeyCode = handler && 'up' === handler.shortcut ? Enums.EventKeyCode.Up : Enums.EventKeyCode.Down,
|
||||||
$items = $('.b-folders .e-item .e-link:not(.hidden):visible', oDom);
|
$items = $('.b-folders .e-item .e-link:not(.hidden):visible', oDom);
|
||||||
|
|
||||||
if (event && $items.length)
|
if (event && $items.length)
|
||||||
{
|
{
|
||||||
iIndex = $items.index($items.filter('.focused'));
|
var iIndex = $items.index($items.filter('.focused'));
|
||||||
if (-1 < iIndex)
|
if (-1 < iIndex)
|
||||||
{
|
{
|
||||||
$items.eq(iIndex).removeClass('focused');
|
$items.eq(iIndex).removeClass('focused');
|
||||||
|
|
@ -182,17 +178,13 @@ FolderListMailBoxUserView.prototype.onBuild = function(oDom)
|
||||||
});
|
});
|
||||||
|
|
||||||
key('space', Enums.KeyState.FolderList, function() {
|
key('space', Enums.KeyState.FolderList, function() {
|
||||||
var
|
var $items = $('.b-folders .e-item .e-link:not(.hidden).focused', oDom);
|
||||||
bCollapsed = true,
|
|
||||||
oFolder = null,
|
|
||||||
$items = $('.b-folders .e-item .e-link:not(.hidden).focused', oDom);
|
|
||||||
|
|
||||||
if ($items.length && $items[0])
|
if ($items.length && $items[0])
|
||||||
{
|
{
|
||||||
oFolder = ko.dataFor($items[0]);
|
var oFolder = ko.dataFor($items[0]);
|
||||||
if (oFolder)
|
if (oFolder)
|
||||||
{
|
{
|
||||||
bCollapsed = oFolder.collapsed();
|
var bCollapsed = oFolder.collapsed();
|
||||||
require('App/User').default.setExpandedFolder(oFolder.fullNameHash, bCollapsed);
|
require('App/User').default.setExpandedFolder(oFolder.fullNameHash, bCollapsed);
|
||||||
oFolder.collapsed(!bCollapsed);
|
oFolder.collapsed(!bCollapsed);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -538,14 +538,9 @@ MessageListMailBoxUserView.prototype.setAction = function(sFolderFullNameRaw, mU
|
||||||
*/
|
*/
|
||||||
MessageListMailBoxUserView.prototype.setActionForAll = function(sFolderFullNameRaw, iSetAction)
|
MessageListMailBoxUserView.prototype.setActionForAll = function(sFolderFullNameRaw, iSetAction)
|
||||||
{
|
{
|
||||||
var
|
|
||||||
oFolder = null,
|
|
||||||
aMessages = MessageStore.messageList();
|
|
||||||
|
|
||||||
if ('' !== sFolderFullNameRaw)
|
if ('' !== sFolderFullNameRaw)
|
||||||
{
|
{
|
||||||
oFolder = Cache.getFolderFromCacheList(sFolderFullNameRaw);
|
var oFolder = Cache.getFolderFromCacheList(sFolderFullNameRaw);
|
||||||
|
|
||||||
if (oFolder)
|
if (oFolder)
|
||||||
{
|
{
|
||||||
switch (iSetAction)
|
switch (iSetAction)
|
||||||
|
|
@ -554,7 +549,7 @@ MessageListMailBoxUserView.prototype.setActionForAll = function(sFolderFullNameR
|
||||||
oFolder = Cache.getFolderFromCacheList(sFolderFullNameRaw);
|
oFolder = Cache.getFolderFromCacheList(sFolderFullNameRaw);
|
||||||
if (oFolder)
|
if (oFolder)
|
||||||
{
|
{
|
||||||
_.each(aMessages, function(oMessage) {
|
_.each(MessageStore.messageList(), function(oMessage) {
|
||||||
oMessage.unseen(false);
|
oMessage.unseen(false);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
@ -568,7 +563,7 @@ MessageListMailBoxUserView.prototype.setActionForAll = function(sFolderFullNameR
|
||||||
oFolder = Cache.getFolderFromCacheList(sFolderFullNameRaw);
|
oFolder = Cache.getFolderFromCacheList(sFolderFullNameRaw);
|
||||||
if (oFolder)
|
if (oFolder)
|
||||||
{
|
{
|
||||||
_.each(aMessages, function(oMessage) {
|
_.each(MessageStore.messageList(), function(oMessage) {
|
||||||
oMessage.unseen(true);
|
oMessage.unseen(true);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
@ -644,18 +639,15 @@ MessageListMailBoxUserView.prototype.flagMessages = function(oCurrentMessage)
|
||||||
|
|
||||||
MessageListMailBoxUserView.prototype.flagMessagesFast = function(bFlag)
|
MessageListMailBoxUserView.prototype.flagMessagesFast = function(bFlag)
|
||||||
{
|
{
|
||||||
var
|
var aChecked = this.messageListCheckedOrSelected();
|
||||||
aChecked = this.messageListCheckedOrSelected(),
|
|
||||||
aFlagged = [];
|
|
||||||
|
|
||||||
if (0 < aChecked.length)
|
if (0 < aChecked.length)
|
||||||
{
|
{
|
||||||
aFlagged = _.filter(aChecked, function(oMessage) {
|
if (Utils.isUnd(bFlag))
|
||||||
|
{
|
||||||
|
var aFlagged = _.filter(aChecked, function(oMessage) {
|
||||||
return oMessage.flagged();
|
return oMessage.flagged();
|
||||||
});
|
});
|
||||||
|
|
||||||
if (Utils.isUnd(bFlag))
|
|
||||||
{
|
|
||||||
this.setAction(aChecked[0].folderFullNameRaw, true,
|
this.setAction(aChecked[0].folderFullNameRaw, true,
|
||||||
aChecked.length === aFlagged.length ? Enums.MessageSetAction.UnsetFlag : Enums.MessageSetAction.SetFlag, aChecked);
|
aChecked.length === aFlagged.length ? Enums.MessageSetAction.UnsetFlag : Enums.MessageSetAction.SetFlag, aChecked);
|
||||||
}
|
}
|
||||||
|
|
@ -669,18 +661,15 @@ MessageListMailBoxUserView.prototype.flagMessagesFast = function(bFlag)
|
||||||
|
|
||||||
MessageListMailBoxUserView.prototype.seenMessagesFast = function(bSeen)
|
MessageListMailBoxUserView.prototype.seenMessagesFast = function(bSeen)
|
||||||
{
|
{
|
||||||
var
|
var aChecked = this.messageListCheckedOrSelected();
|
||||||
aChecked = this.messageListCheckedOrSelected(),
|
|
||||||
aUnseen = [];
|
|
||||||
|
|
||||||
if (0 < aChecked.length)
|
if (0 < aChecked.length)
|
||||||
{
|
{
|
||||||
aUnseen = _.filter(aChecked, function(oMessage) {
|
if (Utils.isUnd(bSeen))
|
||||||
|
{
|
||||||
|
var aUnseen = _.filter(aChecked, function(oMessage) {
|
||||||
return oMessage.unseen();
|
return oMessage.unseen();
|
||||||
});
|
});
|
||||||
|
|
||||||
if (Utils.isUnd(bSeen))
|
|
||||||
{
|
|
||||||
this.setAction(aChecked[0].folderFullNameRaw, true,
|
this.setAction(aChecked[0].folderFullNameRaw, true,
|
||||||
0 < aUnseen.length ? Enums.MessageSetAction.SetSeen : Enums.MessageSetAction.UnsetSeen, aChecked);
|
0 < aUnseen.length ? Enums.MessageSetAction.SetSeen : Enums.MessageSetAction.UnsetSeen, aChecked);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -302,25 +302,15 @@ function MessageViewMailBoxUserView()
|
||||||
}, this);
|
}, this);
|
||||||
|
|
||||||
this.viewFromDkimStatusIconClass = ko.computed(function() {
|
this.viewFromDkimStatusIconClass = ko.computed(function() {
|
||||||
|
|
||||||
var sResult = 'icon-none iconcolor-display-none';
|
|
||||||
// var sResult = 'icon-warning-alt iconcolor-grey';
|
|
||||||
switch (this.viewFromDkimData()[0])
|
switch (this.viewFromDkimData()[0])
|
||||||
{
|
{
|
||||||
case 'none':
|
case 'none':
|
||||||
break;
|
return 'icon-none iconcolor-display-none';
|
||||||
case 'pass':
|
case 'pass':
|
||||||
sResult = 'icon-ok iconcolor-green';
|
return 'icon-ok iconcolor-green';
|
||||||
// sResult = 'icon-warning-alt iconcolor-green';
|
|
||||||
break;
|
|
||||||
default:
|
default:
|
||||||
sResult = 'icon-warning-alt iconcolor-red';
|
return 'icon-warning-alt iconcolor-red';
|
||||||
break;
|
|
||||||
// no default
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return sResult;
|
|
||||||
|
|
||||||
}, this);
|
}, this);
|
||||||
|
|
||||||
this.viewFromDkimStatusTitle = ko.computed(function() {
|
this.viewFromDkimStatusTitle = ko.computed(function() {
|
||||||
|
|
@ -465,7 +455,6 @@ MessageViewMailBoxUserView.prototype.detectDomBackgroundColor = function(oDom)
|
||||||
var
|
var
|
||||||
iLimit = 5,
|
iLimit = 5,
|
||||||
sResult = '',
|
sResult = '',
|
||||||
aC = null,
|
|
||||||
fFindDom = function(oInputDom) {
|
fFindDom = function(oInputDom) {
|
||||||
var children = oInputDom ? oInputDom.children() : null;
|
var children = oInputDom ? oInputDom.children() : null;
|
||||||
return (children && 1 === children.length && children.is('table,div,center')) ? children : null;
|
return (children && 1 === children.length && children.is('table,div,center')) ? children : null;
|
||||||
|
|
@ -486,7 +475,7 @@ MessageViewMailBoxUserView.prototype.detectDomBackgroundColor = function(oDom)
|
||||||
|
|
||||||
if (oDom && 1 === oDom.length)
|
if (oDom && 1 === oDom.length)
|
||||||
{
|
{
|
||||||
aC = oDom;
|
var aC = oDom;
|
||||||
while ('' === sResult)
|
while ('' === sResult)
|
||||||
{
|
{
|
||||||
iLimit -= 1;
|
iLimit -= 1;
|
||||||
|
|
@ -643,7 +632,6 @@ MessageViewMailBoxUserView.prototype.onBuild = function(oDom)
|
||||||
{
|
{
|
||||||
var
|
var
|
||||||
self = this,
|
self = this,
|
||||||
oScript = null,
|
|
||||||
// sErrorMessage = Translator.i18n('PREVIEW_POPUP/IMAGE_ERROR'),
|
// sErrorMessage = Translator.i18n('PREVIEW_POPUP/IMAGE_ERROR'),
|
||||||
fCheckHeaderHeight = _.bind(this.checkHeaderHeight, this);
|
fCheckHeaderHeight = _.bind(this.checkHeaderHeight, this);
|
||||||
|
|
||||||
|
|
@ -675,7 +663,7 @@ MessageViewMailBoxUserView.prototype.onBuild = function(oDom)
|
||||||
|
|
||||||
if (this.dropboxEnabled() && this.dropboxApiKey() && !window.Dropbox)
|
if (this.dropboxEnabled() && this.dropboxApiKey() && !window.Dropbox)
|
||||||
{
|
{
|
||||||
oScript = window.document.createElement('script');
|
var oScript = window.document.createElement('script');
|
||||||
oScript.type = 'text/javascript';
|
oScript.type = 'text/javascript';
|
||||||
oScript.src = 'https://www.dropbox.com/static/api/2/dropins.js';
|
oScript.src = 'https://www.dropbox.com/static/api/2/dropins.js';
|
||||||
$(oScript).attr('id', 'dropboxjs').attr('data-app-key', self.dropboxApiKey());
|
$(oScript).attr('id', 'dropboxjs').attr('data-app-key', self.dropboxApiKey());
|
||||||
|
|
|
||||||
|
|
@ -47,14 +47,12 @@ MenuSettingsUserView.prototype.onBuild = function(oDom)
|
||||||
key('up, down', Enums.KeyState.Settings, _.throttle(function(event, handler) {
|
key('up, down', Enums.KeyState.Settings, _.throttle(function(event, handler) {
|
||||||
|
|
||||||
var
|
var
|
||||||
sH = '',
|
|
||||||
iIndex = -1,
|
|
||||||
bUp = handler && 'up' === handler.shortcut,
|
bUp = handler && 'up' === handler.shortcut,
|
||||||
$items = $('.b-settings-menu .e-item', oDom);
|
$items = $('.b-settings-menu .e-item', oDom);
|
||||||
|
|
||||||
if (event && $items.length)
|
if (event && $items.length)
|
||||||
{
|
{
|
||||||
iIndex = $items.index($items.filter('.selected'));
|
var iIndex = $items.index($items.filter('.selected'));
|
||||||
if (bUp && 0 < iIndex)
|
if (bUp && 0 < iIndex)
|
||||||
{
|
{
|
||||||
iIndex -= 1;
|
iIndex -= 1;
|
||||||
|
|
@ -64,7 +62,7 @@ MenuSettingsUserView.prototype.onBuild = function(oDom)
|
||||||
iIndex += 1;
|
iIndex += 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
sH = $items.eq(iIndex).attr('href');
|
var sH = $items.eq(iIndex).attr('href');
|
||||||
if (sH)
|
if (sH)
|
||||||
{
|
{
|
||||||
kn.setHash(sH, false, true);
|
kn.setHash(sH, false, true);
|
||||||
|
|
|
||||||
|
|
@ -55,7 +55,6 @@ module.exports = {
|
||||||
},
|
},
|
||||||
externals: {
|
externals: {
|
||||||
'window': 'window',
|
'window': 'window',
|
||||||
'JSON': 'window.JSON',
|
|
||||||
'progressJs': 'window.progressJs',
|
'progressJs': 'window.progressJs',
|
||||||
'moment': 'window.moment',
|
'moment': 'window.moment',
|
||||||
'ifvisible': 'window.ifvisible',
|
'ifvisible': 'window.ifvisible',
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue