Added keyboard shortcuts (part 1) (#70)

Scrolling in message view (#109)
This commit is contained in:
RainLoop Team 2014-04-08 01:03:58 +04:00
parent 3936a818b7
commit 8979687f88
37 changed files with 2095 additions and 333 deletions

View file

@ -56,6 +56,7 @@
"ifvisible" : true, "ifvisible" : true,
"crossroads" : true, "crossroads" : true,
"hasher" : true, "hasher" : true,
"key" : true,
"Jua" : true, "Jua" : true,
"_" : true, "_" : true,
"Dropbox" : true "Dropbox" : true

View file

@ -145,6 +145,7 @@ module.exports = function (grunt) {
"vendors/knockout-projections/knockout-projections-1.0.0.min.js", "vendors/knockout-projections/knockout-projections-1.0.0.min.js",
"vendors/ssm/ssm.min.js", "vendors/ssm/ssm.min.js",
"vendors/jua/jua.min.js", "vendors/jua/jua.min.js",
"vendors/keymaster/keymaster.js",
"vendors/ifvisible/ifvisible.min.js", "vendors/ifvisible/ifvisible.min.js",
"vendors/jquery-magnific-popup/jquery.magnific-popup.min.js", "vendors/jquery-magnific-popup/jquery.magnific-popup.min.js",
"vendors/bootstrap/js/bootstrap.min.js", "vendors/bootstrap/js/bootstrap.min.js",
@ -233,8 +234,8 @@ module.exports = function (grunt) {
options: { options: {
stripBanners: true, stripBanners: true,
banner: '/*! RainLoop Webmail Main Module (c) RainLoop Team | Licensed under CC BY-NC-SA 3.0 */\n' + banner: '/*! RainLoop Webmail Main Module (c) RainLoop Team | Licensed under CC BY-NC-SA 3.0 */\n' +
'(function (window, $, ko, crossroads, hasher, moment, Jua, _, ifvisible) {\n', '(function (window, $, ko, crossroads, hasher, moment, Jua, _, ifvisible, key) {\n',
footer: '\n\n}(window, jQuery, ko, crossroads, hasher, moment, Jua, _, ifvisible));' footer: '\n\n}(window, jQuery, ko, crossroads, hasher, moment, Jua, _, ifvisible, key));'
}, },
src: [ src: [
"dev/Common/_Begin.js", "dev/Common/_Begin.js",
@ -287,6 +288,7 @@ module.exports = function (grunt) {
"dev/ViewModels/PopupsLanguagesViewModel.js", "dev/ViewModels/PopupsLanguagesViewModel.js",
"dev/ViewModels/PopupsTwoFactorTestViewModel.js", "dev/ViewModels/PopupsTwoFactorTestViewModel.js",
"dev/ViewModels/PopupsAskViewModel.js", "dev/ViewModels/PopupsAskViewModel.js",
"dev/ViewModels/PopupsKeyboardShortcutsHelpViewModel.js",
"dev/ViewModels/LoginViewModel.js", "dev/ViewModels/LoginViewModel.js",

View file

@ -27,6 +27,18 @@ Enums.StateType = {
'Admin': 1 'Admin': 1
}; };
/**
* @enum {string}
*/
Enums.KeyState = {
'None': 'none',
'ContactList': 'contact-list',
'MessageList': 'message-list',
'MessageView': 'message-view',
'Compose': 'compose',
'PopupAsk': 'popup-ask'
};
/** /**
* @enum {number} * @enum {number}
*/ */

View file

@ -137,10 +137,12 @@ Selector.prototype.goUp = function ()
this.newSelectPosition(Enums.EventKeyCode.Up, false); this.newSelectPosition(Enums.EventKeyCode.Up, false);
}; };
Selector.prototype.init = function (oContentVisible, oContentScrollable) Selector.prototype.init = function (oContentVisible, oContentScrollable, sKeyScope)
{ {
this.oContentVisible = oContentVisible; this.oContentVisible = oContentVisible;
this.oContentScrollable = oContentScrollable; this.oContentScrollable = oContentScrollable;
sKeyScope = sKeyScope || 'all';
if (this.oContentVisible && this.oContentScrollable) if (this.oContentVisible && this.oContentScrollable)
{ {
@ -183,27 +185,43 @@ Selector.prototype.init = function (oContentVisible, oContentScrollable)
}) })
; ;
$(window.document).on('keydown', function (oEvent) { key('space, enter', sKeyScope, function () {
var bResult = true; return false;
if (oEvent && self.bUseKeyboard && !Utils.inFocus()) });
key('up, shift+up, down, shift+down, insert, home, end, pageup, pagedown', sKeyScope, function (event, handler) {
if (event && handler && handler.shortcut)
{ {
if (-1 < Utils.inArray(oEvent.keyCode, [Enums.EventKeyCode.Up, Enums.EventKeyCode.Down, Enums.EventKeyCode.Insert, var iKey = 0, aCodes = null;
Enums.EventKeyCode.Home, Enums.EventKeyCode.End, Enums.EventKeyCode.PageUp, Enums.EventKeyCode.PageDown])) switch (handler.shortcut)
{ {
self.newSelectPosition(oEvent.keyCode, oEvent.shiftKey); case 'up':
bResult = false; case 'shift+up':
iKey = Enums.EventKeyCode.Up;
break;
case 'down':
case 'shift+down':
iKey = Enums.EventKeyCode.Down;
break;
case 'insert':
case 'home':
case 'end':
case 'pageup':
case 'pagedown':
aCodes = key.getPressedKeyCodes();
if (aCodes && aCodes[0])
{
iKey = aCodes[0];
}
break;
} }
else if (Enums.EventKeyCode.Delete === oEvent.keyCode && !oEvent.ctrlKey && !oEvent.shiftKey)
if (0 < iKey)
{ {
if (self.oCallbacks['onDelete']) self.newSelectPosition(iKey, key.shift);
{ return false;
self.oCallbacks['onDelete']();
}
bResult = false;
} }
} }
return bResult;
}); });
} }
}; };

View file

@ -1719,3 +1719,31 @@ Utils.selectElement = function (element)
} }
/* jshint onevar: true */ /* jshint onevar: true */
}; };
Utils.disableKeyFilter = function ()
{
if (window.key)
{
key.filter = function () {
return true;
};
}
};
Utils.restoreKeyFilter = function ()
{
if (window.key)
{
key.filter = function (event) {
var
element = event.target || event.srcElement,
tagName = element ? element.tagName : ''
;
tagName = tagName.toUpperCase();
return !(tagName === 'INPUT' || tagName === 'SELECT' || tagName === 'TEXTAREA' ||
(element && tagName === 'DIV' && 'editorHtmlArea' === element.className && element.contentEditable)
);
};
}
};

View file

@ -63,14 +63,11 @@ KnoinAbstractViewModel.prototype.cancelCommand = KnoinAbstractViewModel.prototyp
KnoinAbstractViewModel.prototype.registerPopupEscapeKey = function () KnoinAbstractViewModel.prototype.registerPopupEscapeKey = function ()
{ {
var self = this; key('esc', _.bind(function () {
$window.on('keydown', function (oEvent) { if (this.modalVisibility && this.modalVisibility())
if (oEvent && Enums.EventKeyCode.Esc === oEvent.keyCode && self.modalVisibility())
{ {
Utils.delegateRun(self, 'cancelCommand'); Utils.delegateRun(this, 'cancelCommand');
return false; return false;
} }
}, this));
return true;
});
}; };

View file

@ -12,8 +12,9 @@ function ContactModel()
this.readOnly = false; this.readOnly = false;
this.scopeType = Enums.ContactScopeType.Default; this.scopeType = Enums.ContactScopeType.Default;
this.checked = ko.observable(false); this.focused = ko.observable(false);
this.selected = ko.observable(false); this.selected = ko.observable(false);
this.checked = ko.observable(false);
this.deleted = ko.observable(false); this.deleted = ko.observable(false);
this.shared = ko.observable(false); this.shared = ko.observable(false);
} }
@ -118,6 +119,10 @@ ContactModel.prototype.lineAsCcc = function ()
{ {
aResult.push('shared'); aResult.push('shared');
} }
if (this.focused())
{
aResult.push('focused');
}
return aResult.join(' '); return aResult.join(' ');
}; };

View file

@ -24,6 +24,7 @@ function FolderModel()
this.type = ko.observable(Enums.FolderType.User); this.type = ko.observable(Enums.FolderType.User);
this.focused = ko.observable(false);
this.selected = ko.observable(false); this.selected = ko.observable(false);
this.edited = ko.observable(false); this.edited = ko.observable(false);
this.collapsed = ko.observable(true); this.collapsed = ko.observable(true);

View file

@ -35,6 +35,7 @@ function MessageModel()
this.forwarded = ko.observable(false); this.forwarded = ko.observable(false);
this.isReadReceipt = ko.observable(false); this.isReadReceipt = ko.observable(false);
this.focused = ko.observable(false);
this.selected = ko.observable(false); this.selected = ko.observable(false);
this.checked = ko.observable(false); this.checked = ko.observable(false);
this.hasAttachments = ko.observable(false); this.hasAttachments = ko.observable(false);
@ -514,6 +515,10 @@ MessageModel.prototype.lineAsCcc = function ()
{ {
aResult.push('forwarded'); aResult.push('forwarded');
} }
if (this.focused())
{
aResult.push('focused');
}
if (this.hasAttachments()) if (this.hasAttachments())
{ {
aResult.push('withAttachments'); aResult.push('withAttachments');

View file

@ -37,6 +37,13 @@ MailBoxScreen.prototype.setNewTitle = function ()
MailBoxScreen.prototype.onShow = function () MailBoxScreen.prototype.onShow = function ()
{ {
this.setNewTitle(); this.setNewTitle();
RL.data().keyScope(Enums.KeyState.MessageList);
};
MailBoxScreen.prototype.onHide = function ()
{
RL.data().keyScope(Enums.KeyState.None);
}; };
/** /**

View file

@ -37,12 +37,27 @@ function WebMailDataStorage()
this.accountIncLogin = ko.observable(''); this.accountIncLogin = ko.observable('');
this.accountOutLogin = ko.observable(''); this.accountOutLogin = ko.observable('');
this.projectHash = ko.observable(''); this.projectHash = ko.observable('');
this.threading = ko.observable(false); this.threading = ko.observable(false);
this.lastFoldersHash = '';
this.lastFoldersHash = '';
this.remoteSuggestions = false; this.remoteSuggestions = false;
this.keyScope = ko.observable(Enums.KeyState.None);
this.keyScope.subscribe(function (sValue) {
if (Enums.KeyState.Compose === sValue)
{
Utils.disableKeyFilter();
}
else
{
Utils.restoreKeyFilter();
}
// window.console.log(sValue);
key.setScope(sValue);
});
// system folders // system folders
this.sentFolder = ko.observable(''); this.sentFolder = ko.observable('');
this.draftFolder = ko.observable(''); this.draftFolder = ko.observable('');
@ -257,6 +272,23 @@ function WebMailDataStorage()
this.messageLoading = ko.observable(false); this.messageLoading = ko.observable(false);
this.messageLoadingThrottle = ko.observable(false).extend({'throttle': 50}); this.messageLoadingThrottle = ko.observable(false).extend({'throttle': 50});
this.message.focused = ko.observable(false);
this.message.subscribe(function (oMessage) {
if (!oMessage)
{
this.message.focused(false);
}
else if (Enums.Layout.NoPreview === this.layout())
{
this.message.focused(true);
}
}, this);
this.message.focused.subscribe(function (bValue) {
RL.data().keyScope(bValue ? Enums.KeyState.MessageView : Enums.KeyState.MessageList);
});
this.messageLoading.subscribe(function (bValue) { this.messageLoading.subscribe(function (bValue) {
this.messageLoadingThrottle(bValue); this.messageLoadingThrottle(bValue);
}, this); }, this);

View file

@ -42,6 +42,7 @@
@import "SystemDropDown.less"; @import "SystemDropDown.less";
@import "Login.less"; @import "Login.less";
@import "Ask.less"; @import "Ask.less";
@import "Shortcuts.less";
@import "FolderList.less"; @import "FolderList.less";
@import "FolderClear.less"; @import "FolderClear.less";
@import "FolderCreate.less"; @import "FolderCreate.less";

View file

@ -478,6 +478,10 @@ html.rl-no-preview-pane {
} }
} }
&.message-focused .b-content {
.opacity(97);
}
&.hideMessageListCheckbox { &.hideMessageListCheckbox {
.checkedParent, .checkboxCkeckAll { .checkedParent, .checkboxCkeckAll {
display: none !important; display: none !important;

View file

@ -30,10 +30,9 @@ html.rl-no-preview-pane {
top: 50px + @rlLowMargin; top: 50px + @rlLowMargin;
bottom: @rlLowMargin + @rlBottomMargin; bottom: @rlLowMargin + @rlBottomMargin;
right: @rlLowMargin; right: @rlLowMargin;
left: 0; left: -1px;
overflow: hidden; overflow: hidden;
border: @rlLowBorderSize solid @rlMainDarkColor; border: @rlLowBorderSize solid @rlMainDarkColor;
border-left: 0px;
.border-top-right-radius(@rlLowBorderRadius); .border-top-right-radius(@rlLowBorderRadius);
.border-bottom-right-radius(@rlLowBorderRadius); .border-bottom-right-radius(@rlLowBorderRadius);
@ -351,6 +350,17 @@ html.rl-no-preview-pane {
} }
} }
} }
&.message-focused .messageItemHeader {
background-color: #fafafa !important;
}
&.message-focused .b-content {
z-index: 102;
.box-shadow(0px 2px 8px rgba(0, 0, 0, 0.3));
border-color: darken(@rlMainDarkColor, 5%);
.border-radius(@rlLowBorderRadius);
}
} }
html.rl-no-preview-pane .messageView { html.rl-no-preview-pane .messageView {

View file

@ -0,0 +1,7 @@
.popups {
.b-shortcuts-content {
.modal-header {
background-color: #fff;
}
}
}

View file

@ -24,11 +24,19 @@ Utils.extendAsViewModel('MailBoxFolderListViewModel', MailBoxFolderListViewModel
MailBoxFolderListViewModel.prototype.onBuild = function (oDom) MailBoxFolderListViewModel.prototype.onBuild = function (oDom)
{ {
oDom oDom
.on('click', '.b-folders .b-content', function () {
if (RL.data().useKeyboardShortcuts() && RL.data().message.focused())
{
RL.data().message.focused(false);
}
})
.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 bCollapsed = false
; ;
if (oFolder && oEvent) if (oFolder && oEvent)
{ {
bCollapsed = oFolder.collapsed(); bCollapsed = oFolder.collapsed();

View file

@ -16,6 +16,7 @@ function MailBoxMessageListViewModel()
this.popupVisibility = RL.popupVisibility; this.popupVisibility = RL.popupVisibility;
this.message = oData.message;
this.messageList = oData.messageList; this.messageList = oData.messageList;
this.currentMessage = oData.currentMessage; this.currentMessage = oData.currentMessage;
this.isMessageSelected = oData.isMessageSelected; this.isMessageSelected = oData.isMessageSelected;
@ -214,13 +215,6 @@ function MailBoxMessageListViewModel()
return oMessage ? oMessage.generateUid() : ''; return oMessage ? oMessage.generateUid() : '';
}); });
this.selector.on('onDelete', _.bind(function () {
if (0 < RL.data().messageListCheckedOrSelected().length)
{
this.deleteCommand();
}
}, this));
RL RL
.sub('mailbox.message-list.selector.go-down', function () { .sub('mailbox.message-list.selector.go-down', function () {
this.selector.goDown(); this.selector.goDown();
@ -540,31 +534,15 @@ MailBoxMessageListViewModel.prototype.onBuild = function (oDom)
return false; return false;
}); });
this.selector.init(this.oContentVisible, this.oContentScrollable); this.selector.init(this.oContentVisible, this.oContentScrollable, Enums.KeyState.MessageList);
$document.on('keydown', function (oEvent) {
var
bResult = true,
iKeyCode = oEvent ? oEvent.keyCode : 0
;
if (oEvent && self.viewModelVisibility() && oData.useKeyboardShortcuts() && !RL.popupVisibility() && !oData.messageFullScreenMode() && !Utils.inFocus())
{
if (Enums.Layout.NoPreview !== oData.layout() || (!oData.message() && (Enums.EventKeyCode.Delete === iKeyCode || Enums.EventKeyCode.A === iKeyCode)))
{
if (oEvent.ctrlKey && Enums.EventKeyCode.A === iKeyCode)
{
self.checkAll(!(self.checkAll() && !self.isIncompleteChecked()));
bResult = false;
}
}
}
return bResult;
});
oDom oDom
.on('click', '.messageList .b-message-list-wrapper', function () {
if (oData.useKeyboardShortcuts() && self.message.focused())
{
self.message.focused(false);
}
})
.on('click', '.e-pagenator .e-page', function () { .on('click', '.e-pagenator .e-page', function () {
var oPage = ko.dataFor(this); var oPage = ko.dataFor(this);
if (oPage) if (oPage)
@ -625,6 +603,7 @@ MailBoxMessageListViewModel.prototype.onBuild = function (oDom)
}, this).extend({'notify': 'always'}); }, this).extend({'notify': 'always'});
this.initUploaderForAppend(); this.initUploaderForAppend();
this.initShortcuts();
if (!Globals.bMobileDevice && !!RL.settingsGet('AllowPrefetch') && ifvisible) if (!Globals.bMobileDevice && !!RL.settingsGet('AllowPrefetch') && ifvisible)
{ {
@ -636,6 +615,121 @@ MailBoxMessageListViewModel.prototype.onBuild = function (oDom)
} }
}; };
MailBoxMessageListViewModel.prototype.initShortcuts = function ()
{
var
self = this,
oData = RL.data()
;
// disable print
key('ctrl+p, command+p', Enums.KeyState.MessageList, function () {
if (oData.useKeyboardShortcuts())
{
return false;
}
});
// delete
key('delete, shift+delete', Enums.KeyState.MessageList, function (event, handler) {
if (oData.useKeyboardShortcuts() && event)
{
if (0 < RL.data().messageListCheckedOrSelected().length)
{
if (handler && 'shift+delete' === handler.shortcut)
{
self.deleteWithoutMoveCommand();
}
else
{
self.deleteCommand();
}
}
return false;
}
});
// check all
key('ctrl+a, command+a', Enums.KeyState.MessageList, function () {
if (oData.useKeyboardShortcuts())
{
self.checkAll(!(self.checkAll() && !self.isIncompleteChecked()));
return false;
}
});
// new message (open compose popup)
key('c,n', [Enums.KeyState.MessageList, Enums.KeyState.MessageView], function () {
if (oData.useKeyboardShortcuts())
{
kn.showScreenPopup(PopupsComposeViewModel);
return false;
}
});
// shortcuts help
key('shift+/', [Enums.KeyState.MessageList, Enums.KeyState.MessageView], function () {
if (oData.useKeyboardShortcuts())
{
kn.showScreenPopup(PopupsKeyboardShortcutsHelpViewModel);
return false;
}
});
// search input focus
key('/', [Enums.KeyState.MessageList, Enums.KeyState.MessageView], function () {
if (oData.useKeyboardShortcuts())
{
if (self.message())
{
self.message.focused(false);
}
self.inputMessageListSearchFocus(true);
return false;
}
});
// cancel search
key('esc', Enums.KeyState.MessageList, function () {
if (oData.useKeyboardShortcuts() && '' !== self.messageListSearchDesc())
{
self.cancelSearch();
return false;
}
});
// change focused state
key('tab, enter', Enums.KeyState.MessageList, function () {
if (oData.useKeyboardShortcuts())
{
if (self.message())
{
self.message.focused(true);
}
return false;
}
});
key('ctrl+left, command+left', Enums.KeyState.MessageView, function () {
if (oData.useKeyboardShortcuts())
{
// TODO
return false;
}
});
key('ctrl+right, command+right', Enums.KeyState.MessageView, function () {
if (oData.useKeyboardShortcuts())
{
// TODO
return false;
}
});
};
MailBoxMessageListViewModel.prototype.prefetchNextTick = function () MailBoxMessageListViewModel.prototype.prefetchNextTick = function ()
{ {
if (!this.bPrefetch && !ifvisible.now() && this.viewModelVisibility()) if (!this.bPrefetch && !ifvisible.now() && this.viewModelVisibility())

View file

@ -21,6 +21,7 @@ function MailBoxMessageViewViewModel()
this.oMessageScrollerDom = null; this.oMessageScrollerDom = null;
this.keyScope = oData.keyScope;
this.message = oData.message; this.message = oData.message;
this.messageLoading = oData.messageLoading; this.messageLoading = oData.messageLoading;
this.messageLoadingThrottle = oData.messageLoadingThrottle; this.messageLoadingThrottle = oData.messageLoadingThrottle;
@ -36,6 +37,7 @@ function MailBoxMessageViewViewModel()
this.fullScreenMode = oData.messageFullScreenMode; this.fullScreenMode = oData.messageFullScreenMode;
this.showFullInfo = ko.observable(false); this.showFullInfo = ko.observable(false);
this.messageDomFocused = ko.observable(false);
this.messageVisibility = ko.computed(function () { this.messageVisibility = ko.computed(function () {
return !this.messageLoadingThrottle() && !!this.message(); return !this.messageLoadingThrottle() && !!this.message();
@ -68,36 +70,39 @@ function MailBoxMessageViewViewModel()
}, this.messageVisibility); }, this.messageVisibility);
this.deleteCommand = Utils.createCommand(this, function () { this.deleteCommand = Utils.createCommand(this, function () {
if (this.message()) if (this.message())
{ {
RL.deleteMessagesFromFolder(Enums.FolderType.Trash, RL.deleteMessagesFromFolder(Enums.FolderType.Trash,
this.message().folderFullNameRaw, this.message().folderFullNameRaw,
[this.message().uid], true); [this.message().uid], true);
} }
}, this.messageVisibility);
this.deleteWithoutMoveCommand = Utils.createCommand(this, function () {
if (this.message())
{
RL.deleteMessagesFromFolder(Enums.FolderType.Trash,
RL.data().currentFolderFullNameRaw(),
RL.data().messageListCheckedOrSelectedUidsWithSubMails(), false);
}
}, this.messageVisibility); }, this.messageVisibility);
this.archiveCommand = Utils.createCommand(this, function () { this.archiveCommand = Utils.createCommand(this, function () {
if (this.message()) if (this.message())
{ {
RL.deleteMessagesFromFolder(Enums.FolderType.Archive, RL.deleteMessagesFromFolder(Enums.FolderType.Archive,
this.message().folderFullNameRaw, this.message().folderFullNameRaw,
[this.message().uid], true); [this.message().uid], true);
} }
}, this.messageVisibility); }, this.messageVisibility);
this.spamCommand = Utils.createCommand(this, function () { this.spamCommand = Utils.createCommand(this, function () {
if (this.message()) if (this.message())
{ {
RL.deleteMessagesFromFolder(Enums.FolderType.Spam, RL.deleteMessagesFromFolder(Enums.FolderType.Spam,
this.message().folderFullNameRaw, this.message().folderFullNameRaw,
[this.message().uid], true); [this.message().uid], true);
} }
}, this.messageVisibility); }, this.messageVisibility);
// viewer // viewer
@ -291,29 +296,14 @@ MailBoxMessageViewViewModel.prototype.onBuild = function (oDom)
self = this, self = this,
oData = RL.data() oData = RL.data()
; ;
$document.on('keydown', function (oEvent) {
var this.fullScreenMode.subscribe(function (bValue) {
bResult = true, if (bValue)
iKeyCode = oEvent ? oEvent.keyCode : 0
;
if (0 < iKeyCode && (Enums.EventKeyCode.Esc === iKeyCode) &&
self.viewModelVisibility() && oData.useKeyboardShortcuts() && !Utils.inFocus() && oData.message())
{ {
self.fullScreenMode(false); self.message.focused(true);
if (Enums.Layout.NoPreview === oData.layout())
{
RL.historyBack();
}
bResult = false;
} }
}, this);
return bResult;
});
$('.attachmentsPlace', oDom).magnificPopup({ $('.attachmentsPlace', oDom).magnificPopup({
'delegate': '.magnificPopupImage:visible', 'delegate': '.magnificPopupImage:visible',
'type': 'image', 'type': 'image',
@ -335,6 +325,12 @@ MailBoxMessageViewViewModel.prototype.onBuild = function (oDom)
}); });
oDom oDom
.on('click', '.messageView .messageItem', function () {
if (oData.useKeyboardShortcuts() && self.message())
{
self.message.focused(true);
}
})
.on('mousedown', 'a', function (oEvent) { .on('mousedown', 'a', function (oEvent) {
// setup maito protocol // setup maito protocol
return !(oEvent && 3 !== oEvent['which'] && RL.mailToHelper($(this).attr('href'))); return !(oEvent && 3 !== oEvent['which'] && RL.mailToHelper($(this).attr('href')));
@ -358,8 +354,171 @@ MailBoxMessageViewViewModel.prototype.onBuild = function (oDom)
}) })
; ;
this.message.focused.subscribe(function (bValue) {
this.messageDomFocused(!!bValue);
}, this);
this.keyScope.subscribe(function (sValue) {
if (Enums.KeyState.MessageView === sValue && this.message.focused())
{
this.messageDomFocused(true);
}
}, this);
this.oMessageScrollerDom = oDom.find('.messageItem .content'); this.oMessageScrollerDom = oDom.find('.messageItem .content');
this.oMessageScrollerDom = this.oMessageScrollerDom && this.oMessageScrollerDom[0] ? this.oMessageScrollerDom : null; this.oMessageScrollerDom = this.oMessageScrollerDom && this.oMessageScrollerDom[0] ? this.oMessageScrollerDom : null;
this.initShortcuts();
};
/**
* @return {boolean}
*/
MailBoxMessageViewViewModel.prototype.escShortcuts = function ()
{
if (this.viewModelVisibility() && RL.data().useKeyboardShortcuts() && this.message())
{
if (this.fullScreenMode())
{
this.fullScreenMode(false);
}
else
{
this.message.focused(false);
}
if (Enums.Layout.NoPreview === RL.data().layout())
{
RL.historyBack();
}
return false;
}
};
MailBoxMessageViewViewModel.prototype.initShortcuts = function ()
{
var
self = this,
oData = RL.data()
;
// exit fullscreen, back
key('esc', Enums.KeyState.MessageView, _.bind(this.escShortcuts, this));
// fullscreen
key('enter', Enums.KeyState.MessageView, function () {
if (oData.useKeyboardShortcuts())
{
self.toggleFullScreen();
return false;
}
});
// reply
key('r', Enums.KeyState.MessageView, function () {
if (oData.useKeyboardShortcuts())
{
self.replyCommand();
return false;
}
});
// replaAll
key('a', Enums.KeyState.MessageView, function () {
if (oData.useKeyboardShortcuts())
{
self.replyAllCommand();
return false;
}
});
// forward
key('f', Enums.KeyState.MessageView, function () {
if (oData.useKeyboardShortcuts())
{
self.forwardCommand();
return false;
}
});
// message information
key('i', Enums.KeyState.MessageView, function () {
if (oData.useKeyboardShortcuts())
{
self.showFullInfo(!self.showFullInfo());
return false;
}
});
key('ctrl+left, command+left', Enums.KeyState.MessageView, function () {
if (oData.useKeyboardShortcuts())
{
self.goDownCommand();
return false;
}
});
key('ctrl+right, command+right', Enums.KeyState.MessageView, function () {
if (oData.useKeyboardShortcuts())
{
self.goUpCommand();
return false;
}
});
// print
key('ctrl+p, command+p', Enums.KeyState.MessageView, function () {
if (oData.useKeyboardShortcuts())
{
if (self.message())
{
self.message().printMessage();
}
return false;
}
});
// archive
// key('delete', Enums.KeyState.MessageView, function () {
// if (oData.useKeyboardShortcuts())
// {
// self.archiveCommand();
// return false;
// }
// });
// delete
key('delete, shift+delete', Enums.KeyState.MessageView, function (event, handler) {
if (oData.useKeyboardShortcuts() && event)
{
self.deleteCommand();
if (handler && 'shift+delete' === handler.shortcut)
{
// self.deleteWithoutMoveCommand();
self.deleteCommand();
}
else
{
self.deleteCommand();
}
return false;
}
});
// change focused state
key('tab', Enums.KeyState.MessageView, function () {
if (oData.useKeyboardShortcuts())
{
if (self.message())
{
self.message.focused(false);
}
return false;
}
});
}; };
/** /**

View file

@ -19,6 +19,7 @@ function PopupsAskViewModel()
this.fNoAction = null; this.fNoAction = null;
this.bDisabeCloseOnEsc = true; this.bDisabeCloseOnEsc = true;
this.sKeyScope = Enums.KeyState.MessageList;
Knoin.constructorEnd(this); Knoin.constructorEnd(this);
} }
@ -40,22 +41,22 @@ PopupsAskViewModel.prototype.clearPopup = function ()
PopupsAskViewModel.prototype.yesClick = function () PopupsAskViewModel.prototype.yesClick = function ()
{ {
this.cancelCommand();
if (Utils.isFunc(this.fYesAction)) if (Utils.isFunc(this.fYesAction))
{ {
this.fYesAction.call(null); this.fYesAction.call(null);
} }
this.cancelCommand();
}; };
PopupsAskViewModel.prototype.noClick = function () PopupsAskViewModel.prototype.noClick = function ()
{ {
this.cancelCommand();
if (Utils.isFunc(this.fNoAction)) if (Utils.isFunc(this.fNoAction))
{ {
this.fNoAction.call(null); this.fNoAction.call(null);
} }
this.cancelCommand();
}; };
/** /**
@ -82,6 +83,9 @@ PopupsAskViewModel.prototype.onShow = function (sAskDesc, fYesFunc, fNoFunc, sYe
{ {
this.yesButton(sNoButton); this.yesButton(sNoButton);
} }
this.sKeyScope = RL.data().keyScope();
RL.data().keyScope(Enums.KeyState.PopupAsk);
}; };
PopupsAskViewModel.prototype.onFocus = function () PopupsAskViewModel.prototype.onFocus = function ()
@ -91,31 +95,25 @@ PopupsAskViewModel.prototype.onFocus = function ()
PopupsAskViewModel.prototype.onHide = function () PopupsAskViewModel.prototype.onHide = function ()
{ {
RL.data().keyScope(this.sKeyScope);
}; };
PopupsAskViewModel.prototype.onBuild = function () PopupsAskViewModel.prototype.onBuild = function ()
{ {
var self = this; key('tab, right, left', Enums.KeyState.PopupAsk, _.bind(function () {
$window.on('keydown', function (oEvent) { if (this.modalVisibility())
var bResult = true;
if (oEvent && self.modalVisibility())
{ {
if (Enums.EventKeyCode.Tab === oEvent.keyCode || Enums.EventKeyCode.Right === oEvent.keyCode || Enums.EventKeyCode.Left === oEvent.keyCode) if (this.yesFocus())
{ {
if (self.yesFocus()) this.noFocus(true);
{ }
self.noFocus(true); else
} {
else this.yesFocus(true);
{
self.yesFocus(true);
}
bResult = false;
} }
}
return bResult; return false;
}); }
}, this));
}; };

View file

@ -351,7 +351,8 @@ function PopupsComposeViewModel()
// this.driveCallback = _.bind(this.driveCallback, this); // this.driveCallback = _.bind(this.driveCallback, this);
this.bDisabeCloseOnEsc = true; this.bDisabeCloseOnEsc = true;
this.sKeyScope = Enums.KeyState.MessageList;
Knoin.constructorEnd(this); Knoin.constructorEnd(this);
} }
@ -568,6 +569,8 @@ PopupsComposeViewModel.prototype.onHide = function ()
{ {
this.reset(); this.reset();
kn.routeOn(); kn.routeOn();
RL.data().keyScope(this.sKeyScope);
}; };
/** /**
@ -639,6 +642,9 @@ PopupsComposeViewModel.prototype.editor = function (fOnInit)
PopupsComposeViewModel.prototype.onShow = function (sType, oMessageOrArray, aToEmails) PopupsComposeViewModel.prototype.onShow = function (sType, oMessageOrArray, aToEmails)
{ {
kn.routeOff(); kn.routeOff();
this.sKeyScope = RL.data().keyScope();
RL.data().keyScope(Enums.KeyState.Compose);
var var
self = this, self = this,
@ -914,6 +920,11 @@ PopupsComposeViewModel.prototype.tryToClosePopup = function ()
}]); }]);
}; };
PopupsComposeViewModel.prototype.useShortcuts = function ()
{
return this.modalVisibility() && RL.data().useKeyboardShortcuts();
};
PopupsComposeViewModel.prototype.onBuild = function () PopupsComposeViewModel.prototype.onBuild = function ()
{ {
this.initUploader(); this.initUploader();
@ -923,29 +934,25 @@ PopupsComposeViewModel.prototype.onBuild = function ()
oScript = null oScript = null
; ;
$window.on('keydown', function (oEvent) { key('ctrl+s, command+s', Enums.KeyState.Compose, function () {
var bResult = true; if (self.useShortcuts())
if (oEvent && self.modalVisibility() && RL.data().useKeyboardShortcuts())
{ {
if (oEvent.ctrlKey && !oEvent.shiftKey && !oEvent.altKey && Enums.EventKeyCode.S === oEvent.keyCode) self.saveCommand();
{ return false;
self.saveCommand();
bResult = false;
}
else if (oEvent.ctrlKey && !oEvent.shiftKey && !oEvent.altKey && Enums.EventKeyCode.Enter === oEvent.keyCode)
{
self.sendCommand();
bResult = false;
}
else if (Enums.EventKeyCode.Esc === oEvent.keyCode)
{
self.tryToClosePopup();
bResult = false;
}
} }
});
return bResult; key('ctrl+enter, command+enter', Enums.KeyState.Compose, function () {
if (self.useShortcuts())
{
self.sendCommand();
return false;
}
});
key('esc', Enums.KeyState.Compose, function () {
self.tryToClosePopup();
return false;
}); });
$window.on('resize', function () { $window.on('resize', function () {

View file

@ -171,10 +171,6 @@ function PopupsContactsViewModel()
return oContact ? oContact.generateUid() : ''; return oContact ? oContact.generateUid() : '';
}); });
this.selector.on('onDelete', _.bind(function () {
this.deleteCommand();
}, this));
this.newCommand = Utils.createCommand(this, function () { this.newCommand = Utils.createCommand(this, function () {
this.populateViewContact(null); this.populateViewContact(null);
this.currentContact(null); this.currentContact(null);
@ -307,6 +303,8 @@ function PopupsContactsViewModel()
} }
}, this); }, this);
this.sKeyScope = Enums.KeyState.MessageList;
Knoin.constructorEnd(this); Knoin.constructorEnd(this);
} }
@ -577,10 +575,18 @@ PopupsContactsViewModel.prototype.onBuild = function (oDom)
this.oContentVisible = $('.b-list-content', oDom); this.oContentVisible = $('.b-list-content', oDom);
this.oContentScrollable = $('.content', this.oContentVisible); this.oContentScrollable = $('.content', this.oContentVisible);
this.selector.init(this.oContentVisible, this.oContentScrollable); this.selector.init(this.oContentVisible, this.oContentScrollable, Enums.KeyState.ContactList);
var self = this; var self = this;
key('delete', Enums.KeyState.ContactList, function () {
if (RL.data().useKeyboardShortcuts())
{
self.deleteCommand();
return false;
}
});
ko.computed(function () { ko.computed(function () {
var var
bModalVisibility = this.modalVisibility(), bModalVisibility = this.modalVisibility(),
@ -607,6 +613,9 @@ PopupsContactsViewModel.prototype.onShow = function ()
{ {
kn.routeOff(); kn.routeOff();
this.reloadContactList(true); this.reloadContactList(true);
this.sKeyScope = RL.data().keyScope();
RL.data().keyScope(Enums.KeyState.ContactList);
}; };
PopupsContactsViewModel.prototype.onHide = function () PopupsContactsViewModel.prototype.onHide = function ()
@ -619,4 +628,6 @@ PopupsContactsViewModel.prototype.onHide = function ()
_.each(this.contacts(), function (oItem) { _.each(this.contacts(), function (oItem) {
oItem.checked(false); oItem.checked(false);
}); });
RL.data().keyScope(this.sKeyScope);
}; };

View file

@ -0,0 +1,27 @@
/* RainLoop Webmail (c) RainLoop Team | Licensed under CC BY-NC-SA 3.0 */
/**
* @constructor
* @extends KnoinAbstractViewModel
*/
function PopupsKeyboardShortcutsHelpViewModel()
{
KnoinAbstractViewModel.call(this, 'Popups', 'PopupsKeyboardShortcutsHelp');
this.sKeyScope = Enums.KeyState.None;
Knoin.constructorEnd(this);
}
Utils.extendAsViewModel('PopupsKeyboardShortcutsHelpViewModel', PopupsKeyboardShortcutsHelpViewModel);
PopupsKeyboardShortcutsHelpViewModel.prototype.onShow = function ()
{
this.sKeyScope = RL.data().keyScope();
RL.data().keyScope(Enums.KeyState.None);
};
PopupsKeyboardShortcutsHelpViewModel.prototype.onHide = function ()
{
RL.data().keyScope(this.sKeyScope);
};

View file

@ -127,14 +127,11 @@ PopupsPluginViewModel.prototype.tryToClosePopup = function ()
PopupsPluginViewModel.prototype.onBuild = function () PopupsPluginViewModel.prototype.onBuild = function ()
{ {
var self = this; key('esc', _.bind(function () {
$window.on('keydown', function (oEvent) { if (this.modalVisibility())
var bResult = true;
if (oEvent && Enums.EventKeyCode.Esc === oEvent.keyCode && self.modalVisibility())
{ {
self.tryToClosePopup(); this.tryToClosePopup();
bResult = false; return false;
} }
return bResult; }));
});
}; };

View file

@ -1,5 +1,6 @@
<div id="rl-sub-left"> <div id="rl-sub-left">
<div class="messageList g-ui-user-select-none" data-bind="css: {'message-selected': isMessageSelected, 'loading': messageListCompleteLoadingThrottle, 'hideMessageListCheckbox': !useCheckboxesInList() }"> <div class="messageList g-ui-user-select-none"
data-bind="css: {'message-selected': isMessageSelected, 'message-focused': message.focused, 'loading': messageListCompleteLoadingThrottle, 'hideMessageListCheckbox': !useCheckboxesInList() }">
<div class="toolbar"> <div class="toolbar">
<div class="btn-toolbar"> <div class="btn-toolbar">
<div class="btn-group"> <div class="btn-group">
@ -111,10 +112,6 @@
</a> </a>
</div> </div>
<i class="checkboxCkeckAll" data-bind="css: checkAll() ? (isIncompleteChecked() ? 'icon-checkbox-partial' : 'icon-checkbox-checked') : 'icon-checkbox-unchecked'"></i> <i class="checkboxCkeckAll" data-bind="css: checkAll() ? (isIncompleteChecked() ? 'icon-checkbox-partial' : 'icon-checkbox-checked') : 'icon-checkbox-unchecked'"></i>
<!--
<i class="checkboxCkeckAll" data-bind="css: checkAll() ? (isIncompleteChecked() ? 'icon-checkbox-partial' : 'icon-checkbox-checked') : 'icon-checkbox-unchecked', visible: !messageListCompleteLoadingThrottle()"></i>
<i class="icon-spinner animated" style="margin-top: 4px;" data-bind="visible: messageListCompleteLoadingThrottle"></i>
-->
</div> </div>
</div> </div>
<div class="mainDelimiter toolbarDelimiter"></div> <div class="mainDelimiter toolbarDelimiter"></div>

View file

@ -1,5 +1,5 @@
<div id="rl-sub-right"> <div id="rl-sub-right">
<div class="messageView" data-bind="css: {'message-selected': isMessageSelected}"> <div class="messageView" data-bind="css: {'message-selected': isMessageSelected, 'message-focused': message.focused}">
<div class="toolbar g-ui-user-select-none"> <div class="toolbar g-ui-user-select-none">
<div class="messageButtons btn-toolbar"> <div class="messageButtons btn-toolbar">
<div class="btn-group" data-placement="bottom" data-bind="visible: !usePreviewPane(), tooltip: 'MESSAGE/BUTTON_CLOSE'"> <div class="btn-group" data-placement="bottom" data-bind="visible: !usePreviewPane(), tooltip: 'MESSAGE/BUTTON_CLOSE'">
@ -131,7 +131,7 @@
</div> </div>
<div data-bind="visible: message"> <div data-bind="visible: message">
<div class="messageItem" data-bind="css: viewLineAsCcc(), nano: true"> <div class="messageItem" data-bind="css: viewLineAsCcc(), nano: true">
<div class="content g-scrollbox"> <div class="content g-scrollbox" tabindex="0" data-bind="hasfocus: messageDomFocused">
<div class="content-wrapper"> <div class="content-wrapper">
<div> <div>
<span class="buttonUp" data-bind="click: scrollToTop"> <span class="buttonUp" data-bind="click: scrollToTop">

View file

@ -0,0 +1,17 @@
<div class="popups">
<div class="modal hide b-shortcuts-content" data-bind="modal: modalVisibility">
<div class="modal-header">
<button type="button" class="close" data-bind="command: cancelCommand">&times;</button>
<h3>
Keyboard shortcuts help
</h3>
</div>
<div class="modal-body">
<br />
Coming soon!
<br />
<br />
<br />
</div>
</div>
</div>

View file

@ -6416,6 +6416,9 @@ html.rl-no-preview-pane #rl-right .ui-resizable-handle {
.popups .b-ask-content .desc-place { .popups .b-ask-content .desc-place {
font-size: 18px; font-size: 18px;
} }
.popups .b-shortcuts-content .modal-header {
background-color: #fff;
}
.b-folders .b-toolbar { .b-folders .b-toolbar {
position: absolute; position: absolute;
top: 0; top: 0;
@ -7048,6 +7051,10 @@ html.rl-no-preview-pane .messageList.message-selected {
.messageList .b-content .messageListItem.flagged .flagOn { .messageList .b-content .messageListItem.flagged .flagOn {
display: inline-block; display: inline-block;
} }
.messageList.message-focused .b-content {
opacity: 0.97;
filter: alpha(opacity=97);
}
.messageList.hideMessageListCheckbox .checkedParent, .messageList.hideMessageListCheckbox .checkedParent,
.messageList.hideMessageListCheckbox .checkboxCkeckAll { .messageList.hideMessageListCheckbox .checkboxCkeckAll {
display: none !important; display: none !important;
@ -7096,10 +7103,9 @@ html.rl-no-preview-pane .messageView.message-selected {
top: 58px; top: 58px;
bottom: 13px; bottom: 13px;
right: 8px; right: 8px;
left: 0; left: -1px;
overflow: hidden; overflow: hidden;
border: 1px solid #aaaaaa; border: 1px solid #aaaaaa;
border-left: 0px;
-webkit-border-top-right-radius: 3px; -webkit-border-top-right-radius: 3px;
-moz-border-radius-topright: 3px; -moz-border-radius-topright: 3px;
border-top-right-radius: 3px; border-top-right-radius: 3px;
@ -7380,6 +7386,19 @@ html.rl-no-preview-pane .messageView.message-selected {
border-left: 2px solid red; border-left: 2px solid red;
color: red; color: red;
} }
.messageView.message-focused .messageItemHeader {
background-color: #fafafa !important;
}
.messageView.message-focused .b-content {
z-index: 102;
-webkit-box-shadow: 0px 2px 8px rgba(0, 0, 0, 0.3);
-moz-box-shadow: 0px 2px 8px rgba(0, 0, 0, 0.3);
box-shadow: 0px 2px 8px rgba(0, 0, 0, 0.3);
border-color: #9d9d9d;
-webkit-border-radius: 3px;
-moz-border-radius: 3px;
border-radius: 3px;
}
html.rl-no-preview-pane .messageView .toolbar { html.rl-no-preview-pane .messageView .toolbar {
padding-left: 1px; padding-left: 1px;
} }

File diff suppressed because one or more lines are too long

View file

@ -367,6 +367,18 @@ Enums.StateType = {
'Admin': 1 'Admin': 1
}; };
/**
* @enum {string}
*/
Enums.KeyState = {
'None': 'none',
'ContactList': 'contact-list',
'MessageList': 'message-list',
'MessageView': 'message-view',
'Compose': 'compose',
'PopupAsk': 'popup-ask'
};
/** /**
* @enum {number} * @enum {number}
*/ */
@ -2421,6 +2433,34 @@ Utils.selectElement = function (element)
/* jshint onevar: true */ /* jshint onevar: true */
}; };
Utils.disableKeyFilter = function ()
{
if (window.key)
{
key.filter = function () {
return true;
};
}
};
Utils.restoreKeyFilter = function ()
{
if (window.key)
{
key.filter = function (event) {
var
element = event.target || event.srcElement,
tagName = element ? element.tagName : ''
;
tagName = tagName.toUpperCase();
return !(tagName === 'INPUT' || tagName === 'SELECT' || tagName === 'TEXTAREA' ||
(element && tagName === 'DIV' && 'editorHtmlArea' === element.className && element.contentEditable)
);
};
}
};
// Base64 encode / decode // Base64 encode / decode
// http://www.webtoolkit.info/ // http://www.webtoolkit.info/
@ -3889,16 +3929,13 @@ KnoinAbstractViewModel.prototype.cancelCommand = KnoinAbstractViewModel.prototyp
KnoinAbstractViewModel.prototype.registerPopupEscapeKey = function () KnoinAbstractViewModel.prototype.registerPopupEscapeKey = function ()
{ {
var self = this; key('esc', _.bind(function () {
$window.on('keydown', function (oEvent) { if (this.modalVisibility && this.modalVisibility())
if (oEvent && Enums.EventKeyCode.Esc === oEvent.keyCode && self.modalVisibility())
{ {
Utils.delegateRun(self, 'cancelCommand'); Utils.delegateRun(this, 'cancelCommand');
return false; return false;
} }
}, this));
return true;
});
}; };
/** /**
@ -5072,16 +5109,13 @@ PopupsPluginViewModel.prototype.tryToClosePopup = function ()
PopupsPluginViewModel.prototype.onBuild = function () PopupsPluginViewModel.prototype.onBuild = function ()
{ {
var self = this; key('esc', _.bind(function () {
$window.on('keydown', function (oEvent) { if (this.modalVisibility())
var bResult = true;
if (oEvent && Enums.EventKeyCode.Esc === oEvent.keyCode && self.modalVisibility())
{ {
self.tryToClosePopup(); this.tryToClosePopup();
bResult = false; return false;
} }
return bResult; }));
});
}; };
/** /**
@ -5279,6 +5313,7 @@ function PopupsAskViewModel()
this.fNoAction = null; this.fNoAction = null;
this.bDisabeCloseOnEsc = true; this.bDisabeCloseOnEsc = true;
this.sKeyScope = Enums.KeyState.MessageList;
Knoin.constructorEnd(this); Knoin.constructorEnd(this);
} }
@ -5300,22 +5335,22 @@ PopupsAskViewModel.prototype.clearPopup = function ()
PopupsAskViewModel.prototype.yesClick = function () PopupsAskViewModel.prototype.yesClick = function ()
{ {
this.cancelCommand();
if (Utils.isFunc(this.fYesAction)) if (Utils.isFunc(this.fYesAction))
{ {
this.fYesAction.call(null); this.fYesAction.call(null);
} }
this.cancelCommand();
}; };
PopupsAskViewModel.prototype.noClick = function () PopupsAskViewModel.prototype.noClick = function ()
{ {
this.cancelCommand();
if (Utils.isFunc(this.fNoAction)) if (Utils.isFunc(this.fNoAction))
{ {
this.fNoAction.call(null); this.fNoAction.call(null);
} }
this.cancelCommand();
}; };
/** /**
@ -5342,6 +5377,9 @@ PopupsAskViewModel.prototype.onShow = function (sAskDesc, fYesFunc, fNoFunc, sYe
{ {
this.yesButton(sNoButton); this.yesButton(sNoButton);
} }
this.sKeyScope = RL.data().keyScope();
RL.data().keyScope(Enums.KeyState.PopupAsk);
}; };
PopupsAskViewModel.prototype.onFocus = function () PopupsAskViewModel.prototype.onFocus = function ()
@ -5351,32 +5389,26 @@ PopupsAskViewModel.prototype.onFocus = function ()
PopupsAskViewModel.prototype.onHide = function () PopupsAskViewModel.prototype.onHide = function ()
{ {
RL.data().keyScope(this.sKeyScope);
}; };
PopupsAskViewModel.prototype.onBuild = function () PopupsAskViewModel.prototype.onBuild = function ()
{ {
var self = this; key('tab, right, left', Enums.KeyState.PopupAsk, _.bind(function () {
$window.on('keydown', function (oEvent) { if (this.modalVisibility())
var bResult = true;
if (oEvent && self.modalVisibility())
{ {
if (Enums.EventKeyCode.Tab === oEvent.keyCode || Enums.EventKeyCode.Right === oEvent.keyCode || Enums.EventKeyCode.Left === oEvent.keyCode) if (this.yesFocus())
{ {
if (self.yesFocus()) this.noFocus(true);
{ }
self.noFocus(true); else
} {
else this.yesFocus(true);
{
self.yesFocus(true);
}
bResult = false;
} }
}
return bResult; return false;
}); }
}, this));
}; };

File diff suppressed because one or more lines are too long

View file

@ -1,5 +1,5 @@
/*! RainLoop Webmail Main Module (c) RainLoop Team | Licensed under CC BY-NC-SA 3.0 */ /*! RainLoop Webmail Main Module (c) RainLoop Team | Licensed under CC BY-NC-SA 3.0 */
(function (window, $, ko, crossroads, hasher, moment, Jua, _, ifvisible) { (function (window, $, ko, crossroads, hasher, moment, Jua, _, ifvisible, key) {
'use strict'; 'use strict';
@ -371,6 +371,18 @@ Enums.StateType = {
'Admin': 1 'Admin': 1
}; };
/**
* @enum {string}
*/
Enums.KeyState = {
'None': 'none',
'ContactList': 'contact-list',
'MessageList': 'message-list',
'MessageView': 'message-view',
'Compose': 'compose',
'PopupAsk': 'popup-ask'
};
/** /**
* @enum {number} * @enum {number}
*/ */
@ -2425,6 +2437,34 @@ Utils.selectElement = function (element)
/* jshint onevar: true */ /* jshint onevar: true */
}; };
Utils.disableKeyFilter = function ()
{
if (window.key)
{
key.filter = function () {
return true;
};
}
};
Utils.restoreKeyFilter = function ()
{
if (window.key)
{
key.filter = function (event) {
var
element = event.target || event.srcElement,
tagName = element ? element.tagName : ''
;
tagName = tagName.toUpperCase();
return !(tagName === 'INPUT' || tagName === 'SELECT' || tagName === 'TEXTAREA' ||
(element && tagName === 'DIV' && 'editorHtmlArea' === element.className && element.contentEditable)
);
};
}
};
// Base64 encode / decode // Base64 encode / decode
// http://www.webtoolkit.info/ // http://www.webtoolkit.info/
@ -3979,10 +4019,12 @@ Selector.prototype.goUp = function ()
this.newSelectPosition(Enums.EventKeyCode.Up, false); this.newSelectPosition(Enums.EventKeyCode.Up, false);
}; };
Selector.prototype.init = function (oContentVisible, oContentScrollable) Selector.prototype.init = function (oContentVisible, oContentScrollable, sKeyScope)
{ {
this.oContentVisible = oContentVisible; this.oContentVisible = oContentVisible;
this.oContentScrollable = oContentScrollable; this.oContentScrollable = oContentScrollable;
sKeyScope = sKeyScope || 'all';
if (this.oContentVisible && this.oContentScrollable) if (this.oContentVisible && this.oContentScrollable)
{ {
@ -4025,27 +4067,43 @@ Selector.prototype.init = function (oContentVisible, oContentScrollable)
}) })
; ;
$(window.document).on('keydown', function (oEvent) { key('space, enter', sKeyScope, function () {
var bResult = true; return false;
if (oEvent && self.bUseKeyboard && !Utils.inFocus()) });
key('up, shift+up, down, shift+down, insert, home, end, pageup, pagedown', sKeyScope, function (event, handler) {
if (event && handler && handler.shortcut)
{ {
if (-1 < Utils.inArray(oEvent.keyCode, [Enums.EventKeyCode.Up, Enums.EventKeyCode.Down, Enums.EventKeyCode.Insert, var iKey = 0, aCodes = null;
Enums.EventKeyCode.Home, Enums.EventKeyCode.End, Enums.EventKeyCode.PageUp, Enums.EventKeyCode.PageDown])) switch (handler.shortcut)
{ {
self.newSelectPosition(oEvent.keyCode, oEvent.shiftKey); case 'up':
bResult = false; case 'shift+up':
iKey = Enums.EventKeyCode.Up;
break;
case 'down':
case 'shift+down':
iKey = Enums.EventKeyCode.Down;
break;
case 'insert':
case 'home':
case 'end':
case 'pageup':
case 'pagedown':
aCodes = key.getPressedKeyCodes();
if (aCodes && aCodes[0])
{
iKey = aCodes[0];
}
break;
} }
else if (Enums.EventKeyCode.Delete === oEvent.keyCode && !oEvent.ctrlKey && !oEvent.shiftKey)
if (0 < iKey)
{ {
if (self.oCallbacks['onDelete']) self.newSelectPosition(iKey, key.shift);
{ return false;
self.oCallbacks['onDelete']();
}
bResult = false;
} }
} }
return bResult;
}); });
} }
}; };
@ -4706,16 +4764,13 @@ KnoinAbstractViewModel.prototype.cancelCommand = KnoinAbstractViewModel.prototyp
KnoinAbstractViewModel.prototype.registerPopupEscapeKey = function () KnoinAbstractViewModel.prototype.registerPopupEscapeKey = function ()
{ {
var self = this; key('esc', _.bind(function () {
$window.on('keydown', function (oEvent) { if (this.modalVisibility && this.modalVisibility())
if (oEvent && Enums.EventKeyCode.Esc === oEvent.keyCode && self.modalVisibility())
{ {
Utils.delegateRun(self, 'cancelCommand'); Utils.delegateRun(this, 'cancelCommand');
return false; return false;
} }
}, this));
return true;
});
}; };
/** /**
@ -5556,8 +5611,9 @@ function ContactModel()
this.readOnly = false; this.readOnly = false;
this.scopeType = Enums.ContactScopeType.Default; this.scopeType = Enums.ContactScopeType.Default;
this.checked = ko.observable(false); this.focused = ko.observable(false);
this.selected = ko.observable(false); this.selected = ko.observable(false);
this.checked = ko.observable(false);
this.deleted = ko.observable(false); this.deleted = ko.observable(false);
this.shared = ko.observable(false); this.shared = ko.observable(false);
} }
@ -5662,6 +5718,10 @@ ContactModel.prototype.lineAsCcc = function ()
{ {
aResult.push('shared'); aResult.push('shared');
} }
if (this.focused())
{
aResult.push('focused');
}
return aResult.join(' '); return aResult.join(' ');
}; };
@ -6020,6 +6080,7 @@ function MessageModel()
this.forwarded = ko.observable(false); this.forwarded = ko.observable(false);
this.isReadReceipt = ko.observable(false); this.isReadReceipt = ko.observable(false);
this.focused = ko.observable(false);
this.selected = ko.observable(false); this.selected = ko.observable(false);
this.checked = ko.observable(false); this.checked = ko.observable(false);
this.hasAttachments = ko.observable(false); this.hasAttachments = ko.observable(false);
@ -6499,6 +6560,10 @@ MessageModel.prototype.lineAsCcc = function ()
{ {
aResult.push('forwarded'); aResult.push('forwarded');
} }
if (this.focused())
{
aResult.push('focused');
}
if (this.hasAttachments()) if (this.hasAttachments())
{ {
aResult.push('withAttachments'); aResult.push('withAttachments');
@ -7189,6 +7254,7 @@ function FolderModel()
this.type = ko.observable(Enums.FolderType.User); this.type = ko.observable(Enums.FolderType.User);
this.focused = ko.observable(false);
this.selected = ko.observable(false); this.selected = ko.observable(false);
this.edited = ko.observable(false); this.edited = ko.observable(false);
this.collapsed = ko.observable(true); this.collapsed = ko.observable(true);
@ -8262,7 +8328,8 @@ function PopupsComposeViewModel()
// this.driveCallback = _.bind(this.driveCallback, this); // this.driveCallback = _.bind(this.driveCallback, this);
this.bDisabeCloseOnEsc = true; this.bDisabeCloseOnEsc = true;
this.sKeyScope = Enums.KeyState.MessageList;
Knoin.constructorEnd(this); Knoin.constructorEnd(this);
} }
@ -8479,6 +8546,8 @@ PopupsComposeViewModel.prototype.onHide = function ()
{ {
this.reset(); this.reset();
kn.routeOn(); kn.routeOn();
RL.data().keyScope(this.sKeyScope);
}; };
/** /**
@ -8550,6 +8619,9 @@ PopupsComposeViewModel.prototype.editor = function (fOnInit)
PopupsComposeViewModel.prototype.onShow = function (sType, oMessageOrArray, aToEmails) PopupsComposeViewModel.prototype.onShow = function (sType, oMessageOrArray, aToEmails)
{ {
kn.routeOff(); kn.routeOff();
this.sKeyScope = RL.data().keyScope();
RL.data().keyScope(Enums.KeyState.Compose);
var var
self = this, self = this,
@ -8825,6 +8897,11 @@ PopupsComposeViewModel.prototype.tryToClosePopup = function ()
}]); }]);
}; };
PopupsComposeViewModel.prototype.useShortcuts = function ()
{
return this.modalVisibility() && RL.data().useKeyboardShortcuts();
};
PopupsComposeViewModel.prototype.onBuild = function () PopupsComposeViewModel.prototype.onBuild = function ()
{ {
this.initUploader(); this.initUploader();
@ -8834,29 +8911,25 @@ PopupsComposeViewModel.prototype.onBuild = function ()
oScript = null oScript = null
; ;
$window.on('keydown', function (oEvent) { key('ctrl+s, command+s', Enums.KeyState.Compose, function () {
var bResult = true; if (self.useShortcuts())
if (oEvent && self.modalVisibility() && RL.data().useKeyboardShortcuts())
{ {
if (oEvent.ctrlKey && !oEvent.shiftKey && !oEvent.altKey && Enums.EventKeyCode.S === oEvent.keyCode) self.saveCommand();
{ return false;
self.saveCommand();
bResult = false;
}
else if (oEvent.ctrlKey && !oEvent.shiftKey && !oEvent.altKey && Enums.EventKeyCode.Enter === oEvent.keyCode)
{
self.sendCommand();
bResult = false;
}
else if (Enums.EventKeyCode.Esc === oEvent.keyCode)
{
self.tryToClosePopup();
bResult = false;
}
} }
});
return bResult; key('ctrl+enter, command+enter', Enums.KeyState.Compose, function () {
if (self.useShortcuts())
{
self.sendCommand();
return false;
}
});
key('esc', Enums.KeyState.Compose, function () {
self.tryToClosePopup();
return false;
}); });
$window.on('resize', function () { $window.on('resize', function () {
@ -9569,10 +9642,6 @@ function PopupsContactsViewModel()
return oContact ? oContact.generateUid() : ''; return oContact ? oContact.generateUid() : '';
}); });
this.selector.on('onDelete', _.bind(function () {
this.deleteCommand();
}, this));
this.newCommand = Utils.createCommand(this, function () { this.newCommand = Utils.createCommand(this, function () {
this.populateViewContact(null); this.populateViewContact(null);
this.currentContact(null); this.currentContact(null);
@ -9705,6 +9774,8 @@ function PopupsContactsViewModel()
} }
}, this); }, this);
this.sKeyScope = Enums.KeyState.MessageList;
Knoin.constructorEnd(this); Knoin.constructorEnd(this);
} }
@ -9975,10 +10046,18 @@ PopupsContactsViewModel.prototype.onBuild = function (oDom)
this.oContentVisible = $('.b-list-content', oDom); this.oContentVisible = $('.b-list-content', oDom);
this.oContentScrollable = $('.content', this.oContentVisible); this.oContentScrollable = $('.content', this.oContentVisible);
this.selector.init(this.oContentVisible, this.oContentScrollable); this.selector.init(this.oContentVisible, this.oContentScrollable, Enums.KeyState.ContactList);
var self = this; var self = this;
key('delete', Enums.KeyState.ContactList, function () {
if (RL.data().useKeyboardShortcuts())
{
self.deleteCommand();
return false;
}
});
ko.computed(function () { ko.computed(function () {
var var
bModalVisibility = this.modalVisibility(), bModalVisibility = this.modalVisibility(),
@ -10005,6 +10084,9 @@ PopupsContactsViewModel.prototype.onShow = function ()
{ {
kn.routeOff(); kn.routeOff();
this.reloadContactList(true); this.reloadContactList(true);
this.sKeyScope = RL.data().keyScope();
RL.data().keyScope(Enums.KeyState.ContactList);
}; };
PopupsContactsViewModel.prototype.onHide = function () PopupsContactsViewModel.prototype.onHide = function ()
@ -10017,6 +10099,8 @@ PopupsContactsViewModel.prototype.onHide = function ()
_.each(this.contacts(), function (oItem) { _.each(this.contacts(), function (oItem) {
oItem.checked(false); oItem.checked(false);
}); });
RL.data().keyScope(this.sKeyScope);
}; };
/** /**
@ -10936,6 +11020,7 @@ function PopupsAskViewModel()
this.fNoAction = null; this.fNoAction = null;
this.bDisabeCloseOnEsc = true; this.bDisabeCloseOnEsc = true;
this.sKeyScope = Enums.KeyState.MessageList;
Knoin.constructorEnd(this); Knoin.constructorEnd(this);
} }
@ -10957,22 +11042,22 @@ PopupsAskViewModel.prototype.clearPopup = function ()
PopupsAskViewModel.prototype.yesClick = function () PopupsAskViewModel.prototype.yesClick = function ()
{ {
this.cancelCommand();
if (Utils.isFunc(this.fYesAction)) if (Utils.isFunc(this.fYesAction))
{ {
this.fYesAction.call(null); this.fYesAction.call(null);
} }
this.cancelCommand();
}; };
PopupsAskViewModel.prototype.noClick = function () PopupsAskViewModel.prototype.noClick = function ()
{ {
this.cancelCommand();
if (Utils.isFunc(this.fNoAction)) if (Utils.isFunc(this.fNoAction))
{ {
this.fNoAction.call(null); this.fNoAction.call(null);
} }
this.cancelCommand();
}; };
/** /**
@ -10999,6 +11084,9 @@ PopupsAskViewModel.prototype.onShow = function (sAskDesc, fYesFunc, fNoFunc, sYe
{ {
this.yesButton(sNoButton); this.yesButton(sNoButton);
} }
this.sKeyScope = RL.data().keyScope();
RL.data().keyScope(Enums.KeyState.PopupAsk);
}; };
PopupsAskViewModel.prototype.onFocus = function () PopupsAskViewModel.prototype.onFocus = function ()
@ -11008,35 +11096,55 @@ PopupsAskViewModel.prototype.onFocus = function ()
PopupsAskViewModel.prototype.onHide = function () PopupsAskViewModel.prototype.onHide = function ()
{ {
RL.data().keyScope(this.sKeyScope);
}; };
PopupsAskViewModel.prototype.onBuild = function () PopupsAskViewModel.prototype.onBuild = function ()
{ {
var self = this; key('tab, right, left', Enums.KeyState.PopupAsk, _.bind(function () {
$window.on('keydown', function (oEvent) { if (this.modalVisibility())
var bResult = true;
if (oEvent && self.modalVisibility())
{ {
if (Enums.EventKeyCode.Tab === oEvent.keyCode || Enums.EventKeyCode.Right === oEvent.keyCode || Enums.EventKeyCode.Left === oEvent.keyCode) if (this.yesFocus())
{ {
if (self.yesFocus()) this.noFocus(true);
{ }
self.noFocus(true); else
} {
else this.yesFocus(true);
{
self.yesFocus(true);
}
bResult = false;
} }
}
return bResult; return false;
}); }
}, this));
}; };
/**
* @constructor
* @extends KnoinAbstractViewModel
*/
function PopupsKeyboardShortcutsHelpViewModel()
{
KnoinAbstractViewModel.call(this, 'Popups', 'PopupsKeyboardShortcutsHelp');
this.sKeyScope = Enums.KeyState.None;
Knoin.constructorEnd(this);
}
Utils.extendAsViewModel('PopupsKeyboardShortcutsHelpViewModel', PopupsKeyboardShortcutsHelpViewModel);
PopupsKeyboardShortcutsHelpViewModel.prototype.onShow = function ()
{
this.sKeyScope = RL.data().keyScope();
RL.data().keyScope(Enums.KeyState.None);
};
PopupsKeyboardShortcutsHelpViewModel.prototype.onHide = function ()
{
RL.data().keyScope(this.sKeyScope);
};
/** /**
* @constructor * @constructor
* @extends KnoinAbstractViewModel * @extends KnoinAbstractViewModel
@ -11461,11 +11569,19 @@ Utils.extendAsViewModel('MailBoxFolderListViewModel', MailBoxFolderListViewModel
MailBoxFolderListViewModel.prototype.onBuild = function (oDom) MailBoxFolderListViewModel.prototype.onBuild = function (oDom)
{ {
oDom oDom
.on('click', '.b-folders .b-content', function () {
if (RL.data().useKeyboardShortcuts() && RL.data().message.focused())
{
RL.data().message.focused(false);
}
})
.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 bCollapsed = false
; ;
if (oFolder && oEvent) if (oFolder && oEvent)
{ {
bCollapsed = oFolder.collapsed(); bCollapsed = oFolder.collapsed();
@ -11582,6 +11698,7 @@ function MailBoxMessageListViewModel()
this.popupVisibility = RL.popupVisibility; this.popupVisibility = RL.popupVisibility;
this.message = oData.message;
this.messageList = oData.messageList; this.messageList = oData.messageList;
this.currentMessage = oData.currentMessage; this.currentMessage = oData.currentMessage;
this.isMessageSelected = oData.isMessageSelected; this.isMessageSelected = oData.isMessageSelected;
@ -11780,13 +11897,6 @@ function MailBoxMessageListViewModel()
return oMessage ? oMessage.generateUid() : ''; return oMessage ? oMessage.generateUid() : '';
}); });
this.selector.on('onDelete', _.bind(function () {
if (0 < RL.data().messageListCheckedOrSelected().length)
{
this.deleteCommand();
}
}, this));
RL RL
.sub('mailbox.message-list.selector.go-down', function () { .sub('mailbox.message-list.selector.go-down', function () {
this.selector.goDown(); this.selector.goDown();
@ -12106,31 +12216,15 @@ MailBoxMessageListViewModel.prototype.onBuild = function (oDom)
return false; return false;
}); });
this.selector.init(this.oContentVisible, this.oContentScrollable); this.selector.init(this.oContentVisible, this.oContentScrollable, Enums.KeyState.MessageList);
$document.on('keydown', function (oEvent) {
var
bResult = true,
iKeyCode = oEvent ? oEvent.keyCode : 0
;
if (oEvent && self.viewModelVisibility() && oData.useKeyboardShortcuts() && !RL.popupVisibility() && !oData.messageFullScreenMode() && !Utils.inFocus())
{
if (Enums.Layout.NoPreview !== oData.layout() || (!oData.message() && (Enums.EventKeyCode.Delete === iKeyCode || Enums.EventKeyCode.A === iKeyCode)))
{
if (oEvent.ctrlKey && Enums.EventKeyCode.A === iKeyCode)
{
self.checkAll(!(self.checkAll() && !self.isIncompleteChecked()));
bResult = false;
}
}
}
return bResult;
});
oDom oDom
.on('click', '.messageList .b-message-list-wrapper', function () {
if (oData.useKeyboardShortcuts() && self.message.focused())
{
self.message.focused(false);
}
})
.on('click', '.e-pagenator .e-page', function () { .on('click', '.e-pagenator .e-page', function () {
var oPage = ko.dataFor(this); var oPage = ko.dataFor(this);
if (oPage) if (oPage)
@ -12191,6 +12285,7 @@ MailBoxMessageListViewModel.prototype.onBuild = function (oDom)
}, this).extend({'notify': 'always'}); }, this).extend({'notify': 'always'});
this.initUploaderForAppend(); this.initUploaderForAppend();
this.initShortcuts();
if (!Globals.bMobileDevice && !!RL.settingsGet('AllowPrefetch') && ifvisible) if (!Globals.bMobileDevice && !!RL.settingsGet('AllowPrefetch') && ifvisible)
{ {
@ -12202,6 +12297,121 @@ MailBoxMessageListViewModel.prototype.onBuild = function (oDom)
} }
}; };
MailBoxMessageListViewModel.prototype.initShortcuts = function ()
{
var
self = this,
oData = RL.data()
;
// disable print
key('ctrl+p, command+p', Enums.KeyState.MessageList, function () {
if (oData.useKeyboardShortcuts())
{
return false;
}
});
// delete
key('delete, shift+delete', Enums.KeyState.MessageList, function (event, handler) {
if (oData.useKeyboardShortcuts() && event)
{
if (0 < RL.data().messageListCheckedOrSelected().length)
{
if (handler && 'shift+delete' === handler.shortcut)
{
self.deleteWithoutMoveCommand();
}
else
{
self.deleteCommand();
}
}
return false;
}
});
// check all
key('ctrl+a, command+a', Enums.KeyState.MessageList, function () {
if (oData.useKeyboardShortcuts())
{
self.checkAll(!(self.checkAll() && !self.isIncompleteChecked()));
return false;
}
});
// new message (open compose popup)
key('c,n', [Enums.KeyState.MessageList, Enums.KeyState.MessageView], function () {
if (oData.useKeyboardShortcuts())
{
kn.showScreenPopup(PopupsComposeViewModel);
return false;
}
});
// shortcuts help
key('shift+/', [Enums.KeyState.MessageList, Enums.KeyState.MessageView], function () {
if (oData.useKeyboardShortcuts())
{
kn.showScreenPopup(PopupsKeyboardShortcutsHelpViewModel);
return false;
}
});
// search input focus
key('/', [Enums.KeyState.MessageList, Enums.KeyState.MessageView], function () {
if (oData.useKeyboardShortcuts())
{
if (self.message())
{
self.message.focused(false);
}
self.inputMessageListSearchFocus(true);
return false;
}
});
// cancel search
key('esc', Enums.KeyState.MessageList, function () {
if (oData.useKeyboardShortcuts() && '' !== self.messageListSearchDesc())
{
self.cancelSearch();
return false;
}
});
// change focused state
key('tab, enter', Enums.KeyState.MessageList, function () {
if (oData.useKeyboardShortcuts())
{
if (self.message())
{
self.message.focused(true);
}
return false;
}
});
key('ctrl+left, command+left', Enums.KeyState.MessageView, function () {
if (oData.useKeyboardShortcuts())
{
// TODO
return false;
}
});
key('ctrl+right, command+right', Enums.KeyState.MessageView, function () {
if (oData.useKeyboardShortcuts())
{
// TODO
return false;
}
});
};
MailBoxMessageListViewModel.prototype.prefetchNextTick = function () MailBoxMessageListViewModel.prototype.prefetchNextTick = function ()
{ {
if (!this.bPrefetch && !ifvisible.now() && this.viewModelVisibility()) if (!this.bPrefetch && !ifvisible.now() && this.viewModelVisibility())
@ -12329,6 +12539,7 @@ function MailBoxMessageViewViewModel()
this.oMessageScrollerDom = null; this.oMessageScrollerDom = null;
this.keyScope = oData.keyScope;
this.message = oData.message; this.message = oData.message;
this.messageLoading = oData.messageLoading; this.messageLoading = oData.messageLoading;
this.messageLoadingThrottle = oData.messageLoadingThrottle; this.messageLoadingThrottle = oData.messageLoadingThrottle;
@ -12344,6 +12555,7 @@ function MailBoxMessageViewViewModel()
this.fullScreenMode = oData.messageFullScreenMode; this.fullScreenMode = oData.messageFullScreenMode;
this.showFullInfo = ko.observable(false); this.showFullInfo = ko.observable(false);
this.messageDomFocused = ko.observable(false);
this.messageVisibility = ko.computed(function () { this.messageVisibility = ko.computed(function () {
return !this.messageLoadingThrottle() && !!this.message(); return !this.messageLoadingThrottle() && !!this.message();
@ -12376,36 +12588,39 @@ function MailBoxMessageViewViewModel()
}, this.messageVisibility); }, this.messageVisibility);
this.deleteCommand = Utils.createCommand(this, function () { this.deleteCommand = Utils.createCommand(this, function () {
if (this.message()) if (this.message())
{ {
RL.deleteMessagesFromFolder(Enums.FolderType.Trash, RL.deleteMessagesFromFolder(Enums.FolderType.Trash,
this.message().folderFullNameRaw, this.message().folderFullNameRaw,
[this.message().uid], true); [this.message().uid], true);
} }
}, this.messageVisibility);
this.deleteWithoutMoveCommand = Utils.createCommand(this, function () {
if (this.message())
{
RL.deleteMessagesFromFolder(Enums.FolderType.Trash,
RL.data().currentFolderFullNameRaw(),
RL.data().messageListCheckedOrSelectedUidsWithSubMails(), false);
}
}, this.messageVisibility); }, this.messageVisibility);
this.archiveCommand = Utils.createCommand(this, function () { this.archiveCommand = Utils.createCommand(this, function () {
if (this.message()) if (this.message())
{ {
RL.deleteMessagesFromFolder(Enums.FolderType.Archive, RL.deleteMessagesFromFolder(Enums.FolderType.Archive,
this.message().folderFullNameRaw, this.message().folderFullNameRaw,
[this.message().uid], true); [this.message().uid], true);
} }
}, this.messageVisibility); }, this.messageVisibility);
this.spamCommand = Utils.createCommand(this, function () { this.spamCommand = Utils.createCommand(this, function () {
if (this.message()) if (this.message())
{ {
RL.deleteMessagesFromFolder(Enums.FolderType.Spam, RL.deleteMessagesFromFolder(Enums.FolderType.Spam,
this.message().folderFullNameRaw, this.message().folderFullNameRaw,
[this.message().uid], true); [this.message().uid], true);
} }
}, this.messageVisibility); }, this.messageVisibility);
// viewer // viewer
@ -12599,29 +12814,14 @@ MailBoxMessageViewViewModel.prototype.onBuild = function (oDom)
self = this, self = this,
oData = RL.data() oData = RL.data()
; ;
$document.on('keydown', function (oEvent) {
var this.fullScreenMode.subscribe(function (bValue) {
bResult = true, if (bValue)
iKeyCode = oEvent ? oEvent.keyCode : 0
;
if (0 < iKeyCode && (Enums.EventKeyCode.Esc === iKeyCode) &&
self.viewModelVisibility() && oData.useKeyboardShortcuts() && !Utils.inFocus() && oData.message())
{ {
self.fullScreenMode(false); self.message.focused(true);
if (Enums.Layout.NoPreview === oData.layout())
{
RL.historyBack();
}
bResult = false;
} }
}, this);
return bResult;
});
$('.attachmentsPlace', oDom).magnificPopup({ $('.attachmentsPlace', oDom).magnificPopup({
'delegate': '.magnificPopupImage:visible', 'delegate': '.magnificPopupImage:visible',
'type': 'image', 'type': 'image',
@ -12643,6 +12843,12 @@ MailBoxMessageViewViewModel.prototype.onBuild = function (oDom)
}); });
oDom oDom
.on('click', '.messageView .messageItem', function () {
if (oData.useKeyboardShortcuts() && self.message())
{
self.message.focused(true);
}
})
.on('mousedown', 'a', function (oEvent) { .on('mousedown', 'a', function (oEvent) {
// setup maito protocol // setup maito protocol
return !(oEvent && 3 !== oEvent['which'] && RL.mailToHelper($(this).attr('href'))); return !(oEvent && 3 !== oEvent['which'] && RL.mailToHelper($(this).attr('href')));
@ -12666,8 +12872,171 @@ MailBoxMessageViewViewModel.prototype.onBuild = function (oDom)
}) })
; ;
this.message.focused.subscribe(function (bValue) {
this.messageDomFocused(!!bValue);
}, this);
this.keyScope.subscribe(function (sValue) {
if (Enums.KeyState.MessageView === sValue && this.message.focused())
{
this.messageDomFocused(true);
}
}, this);
this.oMessageScrollerDom = oDom.find('.messageItem .content'); this.oMessageScrollerDom = oDom.find('.messageItem .content');
this.oMessageScrollerDom = this.oMessageScrollerDom && this.oMessageScrollerDom[0] ? this.oMessageScrollerDom : null; this.oMessageScrollerDom = this.oMessageScrollerDom && this.oMessageScrollerDom[0] ? this.oMessageScrollerDom : null;
this.initShortcuts();
};
/**
* @return {boolean}
*/
MailBoxMessageViewViewModel.prototype.escShortcuts = function ()
{
if (this.viewModelVisibility() && RL.data().useKeyboardShortcuts() && this.message())
{
if (this.fullScreenMode())
{
this.fullScreenMode(false);
}
else
{
this.message.focused(false);
}
if (Enums.Layout.NoPreview === RL.data().layout())
{
RL.historyBack();
}
return false;
}
};
MailBoxMessageViewViewModel.prototype.initShortcuts = function ()
{
var
self = this,
oData = RL.data()
;
// exit fullscreen, back
key('esc', Enums.KeyState.MessageView, _.bind(this.escShortcuts, this));
// fullscreen
key('enter', Enums.KeyState.MessageView, function () {
if (oData.useKeyboardShortcuts())
{
self.toggleFullScreen();
return false;
}
});
// reply
key('r', Enums.KeyState.MessageView, function () {
if (oData.useKeyboardShortcuts())
{
self.replyCommand();
return false;
}
});
// replaAll
key('a', Enums.KeyState.MessageView, function () {
if (oData.useKeyboardShortcuts())
{
self.replyAllCommand();
return false;
}
});
// forward
key('f', Enums.KeyState.MessageView, function () {
if (oData.useKeyboardShortcuts())
{
self.forwardCommand();
return false;
}
});
// message information
key('i', Enums.KeyState.MessageView, function () {
if (oData.useKeyboardShortcuts())
{
self.showFullInfo(!self.showFullInfo());
return false;
}
});
key('ctrl+left, command+left', Enums.KeyState.MessageView, function () {
if (oData.useKeyboardShortcuts())
{
self.goDownCommand();
return false;
}
});
key('ctrl+right, command+right', Enums.KeyState.MessageView, function () {
if (oData.useKeyboardShortcuts())
{
self.goUpCommand();
return false;
}
});
// print
key('ctrl+p, command+p', Enums.KeyState.MessageView, function () {
if (oData.useKeyboardShortcuts())
{
if (self.message())
{
self.message().printMessage();
}
return false;
}
});
// archive
// key('delete', Enums.KeyState.MessageView, function () {
// if (oData.useKeyboardShortcuts())
// {
// self.archiveCommand();
// return false;
// }
// });
// delete
key('delete, shift+delete', Enums.KeyState.MessageView, function (event, handler) {
if (oData.useKeyboardShortcuts() && event)
{
self.deleteCommand();
if (handler && 'shift+delete' === handler.shortcut)
{
// self.deleteWithoutMoveCommand();
self.deleteCommand();
}
else
{
self.deleteCommand();
}
return false;
}
});
// change focused state
key('tab', Enums.KeyState.MessageView, function () {
if (oData.useKeyboardShortcuts())
{
if (self.message())
{
self.message.focused(false);
}
return false;
}
});
}; };
/** /**
@ -14241,12 +14610,27 @@ function WebMailDataStorage()
this.accountIncLogin = ko.observable(''); this.accountIncLogin = ko.observable('');
this.accountOutLogin = ko.observable(''); this.accountOutLogin = ko.observable('');
this.projectHash = ko.observable(''); this.projectHash = ko.observable('');
this.threading = ko.observable(false); this.threading = ko.observable(false);
this.lastFoldersHash = '';
this.lastFoldersHash = '';
this.remoteSuggestions = false; this.remoteSuggestions = false;
this.keyScope = ko.observable(Enums.KeyState.None);
this.keyScope.subscribe(function (sValue) {
if (Enums.KeyState.Compose === sValue)
{
Utils.disableKeyFilter();
}
else
{
Utils.restoreKeyFilter();
}
// window.console.log(sValue);
key.setScope(sValue);
});
// system folders // system folders
this.sentFolder = ko.observable(''); this.sentFolder = ko.observable('');
this.draftFolder = ko.observable(''); this.draftFolder = ko.observable('');
@ -14461,6 +14845,23 @@ function WebMailDataStorage()
this.messageLoading = ko.observable(false); this.messageLoading = ko.observable(false);
this.messageLoadingThrottle = ko.observable(false).extend({'throttle': 50}); this.messageLoadingThrottle = ko.observable(false).extend({'throttle': 50});
this.message.focused = ko.observable(false);
this.message.subscribe(function (oMessage) {
if (!oMessage)
{
this.message.focused(false);
}
else if (Enums.Layout.NoPreview === this.layout())
{
this.message.focused(true);
}
}, this);
this.message.focused.subscribe(function (bValue) {
RL.data().keyScope(bValue ? Enums.KeyState.MessageView : Enums.KeyState.MessageList);
});
this.messageLoading.subscribe(function (bValue) { this.messageLoading.subscribe(function (bValue) {
this.messageLoadingThrottle(bValue); this.messageLoadingThrottle(bValue);
}, this); }, this);
@ -17063,6 +17464,13 @@ MailBoxScreen.prototype.setNewTitle = function ()
MailBoxScreen.prototype.onShow = function () MailBoxScreen.prototype.onShow = function ()
{ {
this.setNewTitle(); this.setNewTitle();
RL.data().keyScope(Enums.KeyState.MessageList);
};
MailBoxScreen.prototype.onHide = function ()
{
RL.data().keyScope(Enums.KeyState.None);
}; };
/** /**
@ -18752,4 +19160,4 @@ if (window.SimplePace) {
window.SimplePace.add(10); window.SimplePace.add(10);
} }
}(window, jQuery, ko, crossroads, hasher, moment, Jua, _, ifvisible)); }(window, jQuery, ko, crossroads, hasher, moment, Jua, _, ifvisible, key));

File diff suppressed because one or more lines are too long

View file

@ -227,6 +227,314 @@ this.j[c]=i,g.on("click",function(){var a=b.b.d("onDialog");a&&a()}).on("change"
j.c=e;j.h=0;j.a={};j.m={};j.q=f;j.e=f;j.on=function(a,b){this.m[a]=b;return this};j.g=function(a,b){this.m[a]&&this.m[a].apply(f,b||[])};j.d=function(a){return this.m[a]||f};j.cancel=function(a){this.e.cancel(a)};j.p=function(){return B.p};j.v=function(a){this.c=!!a};j.k=function(){return this.e.k()};j.s=function(a){this.r(w(),a)}; j.c=e;j.h=0;j.a={};j.m={};j.q=f;j.e=f;j.on=function(a,b){this.m[a]=b;return this};j.g=function(a,b){this.m[a]&&this.m[a].apply(f,b||[])};j.d=function(a){return this.m[a]||f};j.cancel=function(a){this.e.cancel(a)};j.p=function(){return B.p};j.v=function(a){this.c=!!a};j.k=function(){return this.e.k()};j.s=function(a){this.r(w(),a)};
j.r=function(a,b){var c=this.d("onSelect");if(b&&(!c||h!==c(a,b))){this.e.t(a);var g=this.e.u,d=this.e;this.q.defer(function(){return g.apply(q(d)?f:d,Array.prototype.slice.call(arguments))},a,b)}else this.e.cancel(a)};l.Jua=B; j.r=function(a,b){var c=this.d("onSelect");if(b&&(!c||h!==c(a,b))){this.e.t(a);var g=this.e.u,d=this.e;this.q.defer(function(){return g.apply(q(d)?f:d,Array.prototype.slice.call(arguments))},a,b)}else this.e.cancel(a)};l.Jua=B;
// keymaster.js
// (c) 2011-2013 Thomas Fuchs
// keymaster.js may be freely distributed under the MIT license.
;(function(global){
var k,
_handlers = {},
_mods = { 16: false, 18: false, 17: false, 91: false },
_scope = 'all',
// modifier keys
_MODIFIERS = {
'⇧': 16, shift: 16,
'⌥': 18, alt: 18, option: 18,
'⌃': 17, ctrl: 17, control: 17,
'⌘': 91, command: 91
},
// special keys
_MAP = {
backspace: 8, tab: 9, clear: 12,
enter: 13, 'return': 13,
esc: 27, escape: 27, space: 32,
left: 37, up: 38,
right: 39, down: 40,
insert: 45,
del: 46, 'delete': 46,
home: 36, end: 35,
pageup: 33, pagedown: 34,
',': 188, '.': 190, '/': 191,
'`': 192, '-': 189, '=': 187,
';': 186, '\'': 222,
'[': 219, ']': 221, '\\': 220
},
code = function(x){
return _MAP[x] || x.toUpperCase().charCodeAt(0);
},
_downKeys = [];
for(k=1;k<20;k++) _MAP['f'+k] = 111+k;
// IE doesn't support Array#indexOf, so have a simple replacement
function index(array, item){
var i = array.length;
while(i--) if(array[i]===item) return i;
return -1;
}
// for comparing mods before unassignment
function compareArray(a1, a2) {
if (a1.length != a2.length) return false;
for (var i = 0; i < a1.length; i++) {
if (a1[i] !== a2[i]) return false;
}
return true;
}
var modifierMap = {
16:'shiftKey',
18:'altKey',
17:'ctrlKey',
91:'metaKey'
};
function updateModifierKey(event) {
for(k in _mods) _mods[k] = event[modifierMap[k]];
};
// handle keydown event
function dispatch(event) {
var key, handler, k, i, modifiersMatch, scope;
key = event.keyCode;
if (index(_downKeys, key) == -1) {
_downKeys.push(key);
}
// if a modifier key, set the key.<modifierkeyname> property to true and return
if(key == 93 || key == 224) key = 91; // right command on webkit, command on Gecko
if(key in _mods) {
_mods[key] = true;
// 'assignKey' from inside this closure is exported to window.key
for(k in _MODIFIERS) if(_MODIFIERS[k] == key) assignKey[k] = true;
return;
}
updateModifierKey(event);
// see if we need to ignore the keypress (filter() can can be overridden)
// by default ignore key presses if a select, textarea, or input is focused
if(!assignKey.filter.call(this, event)) return;
// abort if no potentially matching shortcuts found
if (!(key in _handlers)) return;
scope = getScope();
// for each potential shortcut
for (i = 0; i < _handlers[key].length; i++) {
handler = _handlers[key][i];
// see if it's in the current scope
if(handler.scope == scope || handler.scope == 'all'){
// check if modifiers match if any
modifiersMatch = handler.mods.length > 0;
for(k in _mods)
if((!_mods[k] && index(handler.mods, +k) > -1) ||
(_mods[k] && index(handler.mods, +k) == -1)) modifiersMatch = false;
// call the handler and stop the event if neccessary
if((handler.mods.length == 0 && !_mods[16] && !_mods[18] && !_mods[17] && !_mods[91]) || modifiersMatch){
if(handler.method(event, handler)===false){
if(event.preventDefault) event.preventDefault();
else event.returnValue = false;
if(event.stopPropagation) event.stopPropagation();
if(event.cancelBubble) event.cancelBubble = true;
}
}
}
}
};
// unset modifier keys on keyup
function clearModifier(event){
var key = event.keyCode, k,
i = index(_downKeys, key);
// remove key from _downKeys
if (i >= 0) {
_downKeys.splice(i, 1);
}
if(key == 93 || key == 224) key = 91;
if(key in _mods) {
_mods[key] = false;
for(k in _MODIFIERS) if(_MODIFIERS[k] == key) assignKey[k] = false;
}
};
function resetModifiers() {
for(k in _mods) _mods[k] = false;
for(k in _MODIFIERS) assignKey[k] = false;
};
// parse and assign shortcut
function assignKey(key, scope, method){
var keys, mods, bScopeIsArray = false;
keys = getKeys(key);
if (method === undefined) {
method = scope;
scope = 'all';
}
bScopeIsArray = !!(typeof scope !== 'string' && scope.length && typeof scope[0] === 'string');
// for each shortcut
for (var i = 0; i < keys.length; i++) {
// set modifier keys if any
mods = [];
key = keys[i].split('+');
if (key.length > 1){
mods = getMods(key);
key = [key[key.length-1]];
}
// convert to keycode and...
key = key[0];
key = code(key);
// ...store handler
if (!(key in _handlers)) _handlers[key] = [];
if (bScopeIsArray) {
for (var j = 0; j < scope.length; j++) {
_handlers[key].push({ shortcut: keys[i], scope: scope[j], method: method, key: keys[i], mods: mods });
}
} else {
_handlers[key].push({ shortcut: keys[i], scope: scope, method: method, key: keys[i], mods: mods });
}
}
};
// unbind all handlers for given key in current scope
function unbindKey(key, scope) {
var multipleKeys, keys,
mods = [],
i, j, obj;
multipleKeys = getKeys(key);
for (j = 0; j < multipleKeys.length; j++) {
keys = multipleKeys[j].split('+');
if (keys.length > 1) {
mods = getMods(keys);
key = keys[keys.length - 1];
}
key = code(key);
if (scope === undefined) {
scope = getScope();
}
if (!_handlers[key]) {
return;
}
for (i = 0; i < _handlers[key].length; i++) {
obj = _handlers[key][i];
// only clear handlers if correct scope and mods match
if (obj.scope === scope && compareArray(obj.mods, mods)) {
_handlers[key][i] = {};
}
}
}
};
// Returns true if the key with code 'keyCode' is currently down
// Converts strings into key codes.
function isPressed(keyCode) {
if (typeof(keyCode)=='string') {
keyCode = code(keyCode);
}
return index(_downKeys, keyCode) != -1;
}
function getPressedKeyCodes() {
return _downKeys.slice(0);
}
function filter(event){
var tagName = (event.target || event.srcElement).tagName;
// ignore keypressed in any elements that support keyboard data input
return !(tagName == 'INPUT' || tagName == 'SELECT' || tagName == 'TEXTAREA');
}
// initialize key.<modifier> to false
for(k in _MODIFIERS) assignKey[k] = false;
// set current scope (default 'all')
function setScope(scope){ _scope = scope || 'all' };
function getScope(){ return _scope || 'all' };
// delete all handlers for a given scope
function deleteScope(scope){
var key, handlers, i;
for (key in _handlers) {
handlers = _handlers[key];
for (i = 0; i < handlers.length; ) {
if (handlers[i].scope === scope) handlers.splice(i, 1);
else i++;
}
}
};
// abstract key logic for assign and unassign
function getKeys(key) {
var keys;
key = key.replace(/\s/g, '');
keys = key.split(',');
if ((keys[keys.length - 1]) == '') {
keys[keys.length - 2] += ',';
}
return keys;
}
// abstract mods logic for assign and unassign
function getMods(key) {
var mods = key.slice(0, key.length - 1);
for (var mi = 0; mi < mods.length; mi++)
mods[mi] = _MODIFIERS[mods[mi]];
return mods;
}
// cross-browser events
function addEvent(object, event, method) {
if (object.addEventListener)
object.addEventListener(event, method, false);
else if(object.attachEvent)
object.attachEvent('on'+event, function(){ method(window.event) });
};
// set the handlers globally on document
addEvent(document, 'keydown', function(event) { dispatch(event) }); // Passing _scope to a callback to ensure it remains the same by execution. Fixes #48
addEvent(document, 'keyup', clearModifier);
// reset modifiers to false whenever the window is (re)focused.
addEvent(window, 'focus', resetModifiers);
// store previously defined key
var previousKey = global.key;
// restore previously defined key and return reference to our key object
function noConflict() {
var k = global.key;
global.key = previousKey;
return k;
}
// set window.key and window.key.set/get/deleteScope, and the default filter
global.key = assignKey;
global.key.setScope = setScope;
global.key.getScope = getScope;
global.key.deleteScope = deleteScope;
global.key.filter = filter;
global.key.isPressed = isPressed;
global.key.getPressedKeyCodes = getPressedKeyCodes;
global.key.noConflict = noConflict;
global.key.unbind = unbindKey;
if(typeof module !== 'undefined') module.exports = key;
})(this);
/*!ifvisible.js v1.0.0 (c) 2013 Serkan Yersen | MIT */ /*!ifvisible.js v1.0.0 (c) 2013 Serkan Yersen | MIT */
(function(){"use strict";var a,b,c,d,e,f,g,h,i,j,k,l,m,n;i={},c=document,k=!1,l="active",g=6e4,f=!1,b=function(){var a,b,c,d,e,f;return a=function(){return(65536*(1+Math.random())|0).toString(16).substring(1)},e=function(){return a()+a()+"-"+a()+"-"+a()+"-"+a()+"-"+a()+a()+a()},f={},c="__ceGUID",b=function(a,b,d){return a[c]=void 0,a[c]||(a[c]="ifvisible.object.event.identifier"),f[a[c]]||(f[a[c]]={}),f[a[c]][b]||(f[a[c]][b]=[]),f[a[c]][b].push(d)},d=function(a,b,d){var e,g,h,i,j;if(a[c]&&f[a[c]]&&f[a[c]][b]){for(i=f[a[c]][b],j=[],g=0,h=i.length;h>g;g++)e=i[g],j.push(e(d||{}));return j}},{add:b,fire:d}}(),a=function(){var a;return a=!1,function(b,c,d){return a||(a=b.addEventListener?function(a,b,c){return a.addEventListener(b,c,!1)}:b.attachEvent?function(a,b,c){return a.attachEvent("on"+b,c,!1)}:function(a,b,c){return a["on"+b]=c}),a(b,c,d)}}(),d=function(a,b){var d;return c.createEventObject?a.fireEvent("on"+b,d):(d=c.createEvent("HTMLEvents"),d.initEvent(b,!0,!0),!a.dispatchEvent(d))},h=function(){var a,b,d,e,f;for(e=void 0,f=3,d=c.createElement("div"),a=d.getElementsByTagName("i"),b=function(){return d.innerHTML="<!--[if gt IE "+ ++f+"]><i></i><![endif]-->",a[0]};b(););return f>4?f:e}(),e=!1,n=void 0,"undefined"!=typeof c.hidden?(e="hidden",n="visibilitychange"):"undefined"!=typeof c.mozHidden?(e="mozHidden",n="mozvisibilitychange"):"undefined"!=typeof c.msHidden?(e="msHidden",n="msvisibilitychange"):"undefined"!=typeof c.webkitHidden&&(e="webkitHidden",n="webkitvisibilitychange"),m=function(){var b,d;return b=!1,d=function(){return clearTimeout(b),"active"!==l&&i.wakeup(),f=+new Date,b=setTimeout(function(){return"active"===l?i.idle():void 0},g)},d(),a(c,"mousemove",d),a(c,"keyup",d),a(window,"scroll",d),i.focus(d)},j=function(){var b;return k?!0:(e===!1?(b="blur",9>h&&(b="focusout"),a(window,b,function(){return i.blur()}),a(window,"focus",function(){return i.focus()})):a(c,n,function(){return c[e]?i.blur():i.focus()},!1),k=!0,m())},i={setIdleDuration:function(a){return g=1e3*a},getIdleDuration:function(){return g},getIdleInfo:function(){var a,b;return a=+new Date,b={},"idle"===l?(b.isIdle=!0,b.idleFor=a-f,b.timeLeft=0,b.timeLeftPer=100):(b.isIdle=!1,b.idleFor=a-f,b.timeLeft=f+g-a,b.timeLeftPer=(100-100*b.timeLeft/g).toFixed(2)),b},focus:function(a){return"function"==typeof a?this.on("focus",a):(l="active",b.fire(this,"focus"),b.fire(this,"wakeup"),b.fire(this,"statusChanged",{status:l}))},blur:function(a){return"function"==typeof a?this.on("blur",a):(l="hidden",b.fire(this,"blur"),b.fire(this,"idle"),b.fire(this,"statusChanged",{status:l}))},idle:function(a){return"function"==typeof a?this.on("idle",a):(l="idle",b.fire(this,"idle"),b.fire(this,"statusChanged",{status:l}))},wakeup:function(a){return"function"==typeof a?this.on("wakeup",a):(l="active",b.fire(this,"wakeup"),b.fire(this,"statusChanged",{status:l}))},on:function(a,c){return j(),b.add(this,a,c)},onEvery:function(a,b){var c;return j(),c=setInterval(function(){return"active"===l?b():void 0},1e3*a),{stop:function(){return clearInterval(c)},code:c,callback:b}},now:function(){return j(),"active"===l}},"function"==typeof define&&define.amd?define(function(){return i}):window.ifvisible=i}).call(this); (function(){"use strict";var a,b,c,d,e,f,g,h,i,j,k,l,m,n;i={},c=document,k=!1,l="active",g=6e4,f=!1,b=function(){var a,b,c,d,e,f;return a=function(){return(65536*(1+Math.random())|0).toString(16).substring(1)},e=function(){return a()+a()+"-"+a()+"-"+a()+"-"+a()+"-"+a()+a()+a()},f={},c="__ceGUID",b=function(a,b,d){return a[c]=void 0,a[c]||(a[c]="ifvisible.object.event.identifier"),f[a[c]]||(f[a[c]]={}),f[a[c]][b]||(f[a[c]][b]=[]),f[a[c]][b].push(d)},d=function(a,b,d){var e,g,h,i,j;if(a[c]&&f[a[c]]&&f[a[c]][b]){for(i=f[a[c]][b],j=[],g=0,h=i.length;h>g;g++)e=i[g],j.push(e(d||{}));return j}},{add:b,fire:d}}(),a=function(){var a;return a=!1,function(b,c,d){return a||(a=b.addEventListener?function(a,b,c){return a.addEventListener(b,c,!1)}:b.attachEvent?function(a,b,c){return a.attachEvent("on"+b,c,!1)}:function(a,b,c){return a["on"+b]=c}),a(b,c,d)}}(),d=function(a,b){var d;return c.createEventObject?a.fireEvent("on"+b,d):(d=c.createEvent("HTMLEvents"),d.initEvent(b,!0,!0),!a.dispatchEvent(d))},h=function(){var a,b,d,e,f;for(e=void 0,f=3,d=c.createElement("div"),a=d.getElementsByTagName("i"),b=function(){return d.innerHTML="<!--[if gt IE "+ ++f+"]><i></i><![endif]-->",a[0]};b(););return f>4?f:e}(),e=!1,n=void 0,"undefined"!=typeof c.hidden?(e="hidden",n="visibilitychange"):"undefined"!=typeof c.mozHidden?(e="mozHidden",n="mozvisibilitychange"):"undefined"!=typeof c.msHidden?(e="msHidden",n="msvisibilitychange"):"undefined"!=typeof c.webkitHidden&&(e="webkitHidden",n="webkitvisibilitychange"),m=function(){var b,d;return b=!1,d=function(){return clearTimeout(b),"active"!==l&&i.wakeup(),f=+new Date,b=setTimeout(function(){return"active"===l?i.idle():void 0},g)},d(),a(c,"mousemove",d),a(c,"keyup",d),a(window,"scroll",d),i.focus(d)},j=function(){var b;return k?!0:(e===!1?(b="blur",9>h&&(b="focusout"),a(window,b,function(){return i.blur()}),a(window,"focus",function(){return i.focus()})):a(c,n,function(){return c[e]?i.blur():i.focus()},!1),k=!0,m())},i={setIdleDuration:function(a){return g=1e3*a},getIdleDuration:function(){return g},getIdleInfo:function(){var a,b;return a=+new Date,b={},"idle"===l?(b.isIdle=!0,b.idleFor=a-f,b.timeLeft=0,b.timeLeftPer=100):(b.isIdle=!1,b.idleFor=a-f,b.timeLeft=f+g-a,b.timeLeftPer=(100-100*b.timeLeft/g).toFixed(2)),b},focus:function(a){return"function"==typeof a?this.on("focus",a):(l="active",b.fire(this,"focus"),b.fire(this,"wakeup"),b.fire(this,"statusChanged",{status:l}))},blur:function(a){return"function"==typeof a?this.on("blur",a):(l="hidden",b.fire(this,"blur"),b.fire(this,"idle"),b.fire(this,"statusChanged",{status:l}))},idle:function(a){return"function"==typeof a?this.on("idle",a):(l="idle",b.fire(this,"idle"),b.fire(this,"statusChanged",{status:l}))},wakeup:function(a){return"function"==typeof a?this.on("wakeup",a):(l="active",b.fire(this,"wakeup"),b.fire(this,"statusChanged",{status:l}))},on:function(a,c){return j(),b.add(this,a,c)},onEvery:function(a,b){var c;return j(),c=setInterval(function(){return"active"===l?b():void 0},1e3*a),{stop:function(){return clearInterval(c)},code:c,callback:b}},now:function(){return j(),"active"===l}},"function"==typeof define&&define.amd?define(function(){return i}):window.ifvisible=i}).call(this);

20
vendors/keymaster/MIT-LICENSE vendored Normal file
View file

@ -0,0 +1,20 @@
Copyright (c) 2011-2013 Thomas Fuchs
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.

212
vendors/keymaster/README.markdown vendored Normal file
View file

@ -0,0 +1,212 @@
# keymaster.js
Keymaster is a simple micro-library for defining and
dispatching keyboard shortcuts in web applications.
It has no dependencies.
*Its a work in progress (e.g. beta), so spare me your nerdrage and instead
contribute! Patches are welcome, but they are not guaranteed to make
it in.*
## Usage
Include `keymaster.js` in your web app*, by loading it as usual:
```html
<script src="keymaster.js"></script>
```
Keymaster has no dependencies and can be used completely standalone.
It should not interfere with any JavaScript libraries or frameworks.
_*Preferably use a minified version that fits your workflow. You can
run `make` to have UglifyJS (if you have it installed) create a
`keymaster.min.js` file for you._
## Defining shortcuts
One global method is exposed, `key` which defines shortcuts when
called directly.
```javascript
// define short of 'a'
key('a', function(){ alert('you pressed a!') });
// returning false stops the event and prevents default browser events
key('ctrl+r', function(){ alert('stopped reload!'); return false });
// multiple shortcuts that do the same thing
key('⌘+r, ctrl+r', function(){ });
```
The handler method is called with two arguments set, the keydown `event` fired, and
an object containing, among others, the following two properties:
`shortcut`: a string that contains the shortcut used, e.g. `ctrl+r`
`scope`: a string describing the scope (or `all`)
```javascript
key('⌘+r, ctrl+r', function(event, handler){
console.log(handler.shortcut, handler.scope);
});
// "ctrl+r", "all"
```
## Supported keys
Keymaster understands the following modifiers:
`⇧`, `shift`, `option`, `⌥`, `alt`, `ctrl`, `control`, `command`, and `⌘`.
The following special keys can be used for shortcuts:
`backspace`, `tab`, `clear`, `enter`, `return`, `esc`, `escape`, `space`,
`up`, `down`, `left`, `right`, `home`, `end`, `pageup`, `pagedown`, `del`, `delete`
and `f1` through `f19`.
## Modifier key queries
At any point in time (even in code other than key shortcut handlers),
you can query the `key` object for the state of any keys. This
allows easy implementation of things like shift+click handlers. For example,
`key.shift` is `true` if the shift key is currently pressed.
```javascript
if(key.shift) alert('shift is pressed, OMGZ!');
```
## Other key queries
At any point in time (even in code other than key shortcut handlers),
you can query the `key` object for the state of any key. This
is very helpful for game development using a game loop. For example,
`key.isPressed(77)` is `true` if the M key is currently pressed.
```javascript
if(key.isPressed("M")) alert('M key is pressed, can ya believe it!?');
if(key.isPressed(77)) alert('M key is pressed, can ya believe it!?');
```
You can also get these as an array using...
```javascript
key.getPressedKeyCodes() // returns an array of key codes currently pressed
```
## Scopes
If you want to reuse the same shortcut for separate areas in your single page app,
Keymaster supports switching between scopes. Use the `key.setScope` method to set scope.
```javascript
// define shortcuts with a scope
key('o, enter', 'issues', function(){ /* do something */ });
key('o, enter', 'files', function(){ /* do something else */ });
// set the scope (only 'all' and 'issues' shortcuts will be honored)
key.setScope('issues'); // default scope is 'all'
```
## Filter key presses
By default, when an `INPUT`, `SELECT` or `TEXTAREA` element is focused, Keymaster doesn't process any shortcuts.
You can change this by overwriting `key.filter` with a new function. This function is called before
Keymaster processes shortcuts, with the keydown event as argument.
If your function returns false, then the no shortcuts will be processed.
Here's the default implementation for reference:
```javascript
function filter(event){
var tagName = (event.target || event.srcElement).tagName;
return !(tagName == 'INPUT' || tagName == 'SELECT' || tagName == 'TEXTAREA');
}
```
If you only want _some_ shortcuts to work while in an input element, you can change the scope in the
`key.filter` function. Here's an example implementation, setting the scope to either `'input'` or `'other'`.
Don't forget to return `true` so the any shortcuts get processed.
```javascript
key.filter = function(event){
var tagName = (event.target || event.srcElement).tagName;
key.setScope(/^(INPUT|TEXTAREA|SELECT)$/.test(tagName) ? 'input' : 'other');
return true;
}
```
However a more robust way to handle this is to use proper
focus and blur event handlers on your input element, and change scopes there as you see fit.
## noConflict mode
You can call ```key.noConflict``` to remove the ```key``` function from global scope and restore whatever ```key``` was defined to before Keymaster was loaded. Calling ```key.noConflict``` will return the Keymaster ```key``` function.
```javascript
var k = key.noConflict();
k('a', function() { /* ... */ });
key()
// --> TypeError: 'undefined' is not a function
```
## Unbinding shortcuts
Similar to defining shortcuts, they can be unbound using `key.unbind`.
```javascript
// unbind 'a' handler
key.unbind('a');
// unbind a key only for a single scope
// when no scope is specified it defaults to the current scope (key.getScope())
key.unbind('o, enter', 'issues');
key.unbind('o, enter', 'files');
```
## Notes
Keymaster should work with any browser that fires `keyup` and `keydown` events,
and is tested with IE (6+), Safari, Firefox and Chrome.
See [http://madrobby.github.com/keymaster/](http://madrobby.github.com/keymaster/) for a live demo.
## CoffeeScript
If you're using CoffeeScript, configuring key shortcuts couldn't be simpler:
```coffeescript
key 'a', -> alert('you pressed a!')
key '⌘+r, ctrl+r', ->
alert 'stopped reload!'
off
key 'o, enter', 'issues', ->
whatevs()
alert 'shift is pressed, OMGZ!' if key.shift
```
## Contributing
To contribute, please fork Keymaster, add your patch and tests for it (in the `test/` folder) and
submit a pull request.
## TODOs
* Finish test suite
Keymaster is (c) 2011-2013 Thomas Fuchs and may be freely distributed under the MIT license.
See the `MIT-LICENSE` file.

306
vendors/keymaster/keymaster.js vendored Normal file
View file

@ -0,0 +1,306 @@
// keymaster.js
// (c) 2011-2013 Thomas Fuchs
// keymaster.js may be freely distributed under the MIT license.
;(function(global){
var k,
_handlers = {},
_mods = { 16: false, 18: false, 17: false, 91: false },
_scope = 'all',
// modifier keys
_MODIFIERS = {
'⇧': 16, shift: 16,
'⌥': 18, alt: 18, option: 18,
'⌃': 17, ctrl: 17, control: 17,
'⌘': 91, command: 91
},
// special keys
_MAP = {
backspace: 8, tab: 9, clear: 12,
enter: 13, 'return': 13,
esc: 27, escape: 27, space: 32,
left: 37, up: 38,
right: 39, down: 40,
insert: 45,
del: 46, 'delete': 46,
home: 36, end: 35,
pageup: 33, pagedown: 34,
',': 188, '.': 190, '/': 191,
'`': 192, '-': 189, '=': 187,
';': 186, '\'': 222,
'[': 219, ']': 221, '\\': 220
},
code = function(x){
return _MAP[x] || x.toUpperCase().charCodeAt(0);
},
_downKeys = [];
for(k=1;k<20;k++) _MAP['f'+k] = 111+k;
// IE doesn't support Array#indexOf, so have a simple replacement
function index(array, item){
var i = array.length;
while(i--) if(array[i]===item) return i;
return -1;
}
// for comparing mods before unassignment
function compareArray(a1, a2) {
if (a1.length != a2.length) return false;
for (var i = 0; i < a1.length; i++) {
if (a1[i] !== a2[i]) return false;
}
return true;
}
var modifierMap = {
16:'shiftKey',
18:'altKey',
17:'ctrlKey',
91:'metaKey'
};
function updateModifierKey(event) {
for(k in _mods) _mods[k] = event[modifierMap[k]];
};
// handle keydown event
function dispatch(event) {
var key, handler, k, i, modifiersMatch, scope;
key = event.keyCode;
if (index(_downKeys, key) == -1) {
_downKeys.push(key);
}
// if a modifier key, set the key.<modifierkeyname> property to true and return
if(key == 93 || key == 224) key = 91; // right command on webkit, command on Gecko
if(key in _mods) {
_mods[key] = true;
// 'assignKey' from inside this closure is exported to window.key
for(k in _MODIFIERS) if(_MODIFIERS[k] == key) assignKey[k] = true;
return;
}
updateModifierKey(event);
// see if we need to ignore the keypress (filter() can can be overridden)
// by default ignore key presses if a select, textarea, or input is focused
if(!assignKey.filter.call(this, event)) return;
// abort if no potentially matching shortcuts found
if (!(key in _handlers)) return;
scope = getScope();
// for each potential shortcut
for (i = 0; i < _handlers[key].length; i++) {
handler = _handlers[key][i];
// see if it's in the current scope
if(handler.scope == scope || handler.scope == 'all'){
// check if modifiers match if any
modifiersMatch = handler.mods.length > 0;
for(k in _mods)
if((!_mods[k] && index(handler.mods, +k) > -1) ||
(_mods[k] && index(handler.mods, +k) == -1)) modifiersMatch = false;
// call the handler and stop the event if neccessary
if((handler.mods.length == 0 && !_mods[16] && !_mods[18] && !_mods[17] && !_mods[91]) || modifiersMatch){
if(handler.method(event, handler)===false){
if(event.preventDefault) event.preventDefault();
else event.returnValue = false;
if(event.stopPropagation) event.stopPropagation();
if(event.cancelBubble) event.cancelBubble = true;
}
}
}
}
};
// unset modifier keys on keyup
function clearModifier(event){
var key = event.keyCode, k,
i = index(_downKeys, key);
// remove key from _downKeys
if (i >= 0) {
_downKeys.splice(i, 1);
}
if(key == 93 || key == 224) key = 91;
if(key in _mods) {
_mods[key] = false;
for(k in _MODIFIERS) if(_MODIFIERS[k] == key) assignKey[k] = false;
}
};
function resetModifiers() {
for(k in _mods) _mods[k] = false;
for(k in _MODIFIERS) assignKey[k] = false;
};
// parse and assign shortcut
function assignKey(key, scope, method){
var keys, mods, bScopeIsArray = false;
keys = getKeys(key);
if (method === undefined) {
method = scope;
scope = 'all';
}
bScopeIsArray = !!(typeof scope !== 'string' && scope.length && typeof scope[0] === 'string');
// for each shortcut
for (var i = 0; i < keys.length; i++) {
// set modifier keys if any
mods = [];
key = keys[i].split('+');
if (key.length > 1){
mods = getMods(key);
key = [key[key.length-1]];
}
// convert to keycode and...
key = key[0];
key = code(key);
// ...store handler
if (!(key in _handlers)) _handlers[key] = [];
if (bScopeIsArray) {
for (var j = 0; j < scope.length; j++) {
_handlers[key].push({ shortcut: keys[i], scope: scope[j], method: method, key: keys[i], mods: mods });
}
} else {
_handlers[key].push({ shortcut: keys[i], scope: scope, method: method, key: keys[i], mods: mods });
}
}
};
// unbind all handlers for given key in current scope
function unbindKey(key, scope) {
var multipleKeys, keys,
mods = [],
i, j, obj;
multipleKeys = getKeys(key);
for (j = 0; j < multipleKeys.length; j++) {
keys = multipleKeys[j].split('+');
if (keys.length > 1) {
mods = getMods(keys);
key = keys[keys.length - 1];
}
key = code(key);
if (scope === undefined) {
scope = getScope();
}
if (!_handlers[key]) {
return;
}
for (i = 0; i < _handlers[key].length; i++) {
obj = _handlers[key][i];
// only clear handlers if correct scope and mods match
if (obj.scope === scope && compareArray(obj.mods, mods)) {
_handlers[key][i] = {};
}
}
}
};
// Returns true if the key with code 'keyCode' is currently down
// Converts strings into key codes.
function isPressed(keyCode) {
if (typeof(keyCode)=='string') {
keyCode = code(keyCode);
}
return index(_downKeys, keyCode) != -1;
}
function getPressedKeyCodes() {
return _downKeys.slice(0);
}
function filter(event){
var tagName = (event.target || event.srcElement).tagName;
// ignore keypressed in any elements that support keyboard data input
return !(tagName == 'INPUT' || tagName == 'SELECT' || tagName == 'TEXTAREA');
}
// initialize key.<modifier> to false
for(k in _MODIFIERS) assignKey[k] = false;
// set current scope (default 'all')
function setScope(scope){ _scope = scope || 'all' };
function getScope(){ return _scope || 'all' };
// delete all handlers for a given scope
function deleteScope(scope){
var key, handlers, i;
for (key in _handlers) {
handlers = _handlers[key];
for (i = 0; i < handlers.length; ) {
if (handlers[i].scope === scope) handlers.splice(i, 1);
else i++;
}
}
};
// abstract key logic for assign and unassign
function getKeys(key) {
var keys;
key = key.replace(/\s/g, '');
keys = key.split(',');
if ((keys[keys.length - 1]) == '') {
keys[keys.length - 2] += ',';
}
return keys;
}
// abstract mods logic for assign and unassign
function getMods(key) {
var mods = key.slice(0, key.length - 1);
for (var mi = 0; mi < mods.length; mi++)
mods[mi] = _MODIFIERS[mods[mi]];
return mods;
}
// cross-browser events
function addEvent(object, event, method) {
if (object.addEventListener)
object.addEventListener(event, method, false);
else if(object.attachEvent)
object.attachEvent('on'+event, function(){ method(window.event) });
};
// set the handlers globally on document
addEvent(document, 'keydown', function(event) { dispatch(event) }); // Passing _scope to a callback to ensure it remains the same by execution. Fixes #48
addEvent(document, 'keyup', clearModifier);
// reset modifiers to false whenever the window is (re)focused.
addEvent(window, 'focus', resetModifiers);
// store previously defined key
var previousKey = global.key;
// restore previously defined key and return reference to our key object
function noConflict() {
var k = global.key;
global.key = previousKey;
return k;
}
// set window.key and window.key.set/get/deleteScope, and the default filter
global.key = assignKey;
global.key.setScope = setScope;
global.key.getScope = getScope;
global.key.deleteScope = deleteScope;
global.key.filter = filter;
global.key.isPressed = isPressed;
global.key.getPressedKeyCodes = getPressedKeyCodes;
global.key.noConflict = noConflict;
global.key.unbind = unbindKey;
if(typeof module !== 'undefined') module.exports = key;
})(this);

11
vendors/keymaster/package.json vendored Normal file
View file

@ -0,0 +1,11 @@
{
"name": "keymaster",
"description": "library for defining and dispatching keyboard shortcuts",
"version": "1.6.2",
"author": "Thomas Fuchs <thomas@slash7.com> (http://mir.aculo.us)",
"repository": {
"type": "git",
"url": "https://github.com/madrobby/keymaster"
},
"main": "./keymaster.js"
}