Catch pageup/down and ctrl-home/end at the top level

Make the scroll keys work when the focus is outside the message panel.
pull/21833/head
Richard van der Hoff 2016-04-05 13:14:11 +01:00
parent c5c9cad0cb
commit 8b8ee21765
7 changed files with 189 additions and 23 deletions

32
src/KeyCode.js Normal file
View File

@ -0,0 +1,32 @@
/*
Copyright 2016 OpenMarket Ltd
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
/* a selection of key codes, as used in KeyboardEvent.keyCode */
module.exports = {
BACKSPACE: 8,
TAB: 9,
ENTER: 13,
SHIFT: 16,
PAGE_UP: 33,
PAGE_DOWN: 34,
END: 35,
HOME: 36,
LEFT: 37,
UP: 38,
RIGHT: 39,
DOWN: 40,
DELETE: 46,
};

View File

@ -35,6 +35,7 @@ var Tinter = require("../../Tinter");
var sdk = require('../../index');
var MatrixTools = require('../../MatrixTools');
var linkifyMatrix = require("../../linkify-matrix");
var KeyCode = require('../../KeyCode');
module.exports = React.createClass({
displayName: 'MatrixChat',
@ -722,11 +723,10 @@ module.exports = React.createClass({
},
onKeyDown: function(ev) {
if (ev.altKey) {
/*
// Remove this for now as ctrl+alt = alt-gr so this breaks keyboards which rely on alt-gr for numbers
// Will need to find a better meta key if anyone actually cares about using this.
if (ev.ctrlKey && ev.keyCode > 48 && ev.keyCode < 58) {
if (ev.altKey && ev.ctrlKey && ev.keyCode > 48 && ev.keyCode < 58) {
dis.dispatch({
action: 'view_indexed_room',
roomIndex: ev.keyCode - 49,
@ -736,18 +736,45 @@ module.exports = React.createClass({
return;
}
*/
switch (ev.keyCode) {
case 38:
dis.dispatch({action: 'view_prev_room'});
ev.stopPropagation();
ev.preventDefault();
break;
case 40:
dis.dispatch({action: 'view_next_room'});
ev.stopPropagation();
ev.preventDefault();
break;
}
var handled = false;
switch (ev.keyCode) {
case KeyCode.UP:
case KeyCode.DOWN:
if (ev.altKey) {
var action = ev.keyCode == KeyCode.UP ?
'view_prev_room' : 'view_next_room';
dis.dispatch({action: action});
handled = true;
}
break;
case KeyCode.PAGE_UP:
case KeyCode.PAGE_DOWN:
this._onScrollKeyPressed(ev);
handled = true;
break;
case KeyCode.HOME:
case KeyCode.END:
if (ev.ctrlKey) {
this._onScrollKeyPressed(ev);
handled = true;
}
break;
}
if (handled) {
ev.stopPropagation();
ev.preventDefault();
}
},
/** dispatch a page-up/page-down/etc to the appropriate component */
_onScrollKeyPressed(ev) {
if (this.refs.roomView) {
this.refs.roomView.handleScrollKey(ev);
}
},

View File

@ -132,6 +132,14 @@ module.exports = React.createClass({
}
},
/* jump to the top of the content.
*/
scrollToTop: function() {
if (this.refs.scrollPanel) {
this.refs.scrollPanel.scrollToTop();
}
},
/* jump to the bottom of the content.
*/
scrollToBottom: function() {
@ -139,6 +147,26 @@ module.exports = React.createClass({
this.refs.scrollPanel.scrollToBottom();
}
},
/**
* Page up/down.
*
* mult: -1 to page up, +1 to page down
*/
scrollRelative: function(mult) {
if (this.refs.scrollPanel) {
this.refs.scrollPanel.scrollRelative(mult);
}
},
/**
* Scroll up/down in response to a scroll key
*/
handleScrollKey: function(ev) {
if (this.refs.scrollPanel) {
this.refs.handleScrollKey(ev);
}
},
/* jump to the given event id.
*

View File

@ -1115,6 +1115,24 @@ module.exports = React.createClass({
}
},
/**
* called by the parent component when PageUp/Down/etc is pressed.
*
* We pass it down to the scroll panel.
*/
handleScrollKey: function(ev) {
var panel;
if(this.refs.searchResultsPanel) {
panel = this.refs.searchResultsPanel;
} else if(this.refs.messagePanel) {
panel = this.refs.messagePanel;
}
if(panel) {
panel.handleScrollKey(ev);
}
},
// this has to be a proper method rather than an unnamed function,
// otherwise react calls it with null on each update.
_gatherTimelinePanelRef: function(r) {

View File

@ -18,6 +18,7 @@ var React = require("react");
var ReactDOM = require("react-dom");
var GeminiScrollbar = require('react-gemini-scrollbar');
var q = require("q");
var KeyCode = require('../../KeyCode');
var DEBUG_SCROLL = false;
// var DEBUG_SCROLL = true;
@ -326,6 +327,17 @@ module.exports = React.createClass({
this.scrollState = {stuckAtBottom: true};
},
/**
* jump to the top of the content.
*/
scrollToTop: function() {
this._setScrollTop(0);
this._saveScrollState();
},
/**
* jump to the bottom of the content.
*/
scrollToBottom: function() {
// the easiest way to make sure that the scroll state is correctly
// saved is to do the scroll, then save the updated state. (Calculating
@ -335,6 +347,45 @@ module.exports = React.createClass({
this._saveScrollState();
},
/**
* Page up/down.
*
* mult: -1 to page up, +1 to page down
*/
scrollRelative: function(mult) {
var scrollNode = this._getScrollNode();
var delta = mult * scrollNode.clientHeight * 0.5;
this._setScrollTop(scrollNode.scrollTop + delta);
this._saveScrollState();
},
/**
* Scroll up/down in response to a scroll key
*/
handleScrollKey: function(ev) {
switch (ev.keyCode) {
case KeyCode.PAGE_UP:
this.scrollRelative(-1);
break;
case KeyCode.PAGE_DOWN:
this.scrollRelative(1);
break;
case KeyCode.HOME:
if (ev.ctrlKey) {
this.scrollToTop();
}
break;
case KeyCode.END:
if (ev.ctrlKey) {
this.scrollToBottom();
}
break;
}
},
/* Scroll the panel to bring the DOM node with the scroll token
* `scrollToken` into view.
*

View File

@ -27,6 +27,7 @@ var dis = require("../../dispatcher");
var ObjectUtils = require('../../ObjectUtils');
var Modal = require("../../Modal");
var UserActivity = require("../../UserActivity");
var KeyCode = require('../../KeyCode');
var PAGINATE_SIZE = 20;
var INITIAL_SIZE = 20;
@ -520,6 +521,23 @@ var TimelinePanel = React.createClass({
return null;
},
/**
* called by the parent component when PageUp/Down/etc is pressed.
*
* We pass it down to the scroll panel.
*/
handleScrollKey: function(ev) {
if (!this.refs.messagePanel) { return; }
// jump to the live timeline on ctrl-end, rather than the end of the
// timeline window.
if (ev.ctrlKey && ev.keyCode == KeyCode.END) {
this.jumpToLiveTimeline();
} else {
this.refs.messagePanel.handleScrollKey(ev);
}
},
_initTimeline: function(props) {
var initialEvent = props.eventId;
var pixelOffset = props.eventPixelOffset;

View File

@ -34,15 +34,7 @@ var MemberEntry = require("../../../TabCompleteEntries").MemberEntry;
var sdk = require('../../../index');
var dis = require("../../../dispatcher");
var KeyCode = {
ENTER: 13,
BACKSPACE: 8,
DELETE: 46,
TAB: 9,
SHIFT: 16,
UP: 38,
DOWN: 40
};
var KeyCode = require("../../../KeyCode");
var TYPING_USER_TIMEOUT = 10000;
var TYPING_SERVER_TIMEOUT = 30000;