mirror of
https://github.com/laurent22/joplin.git
synced 2024-11-27 08:21:03 +02:00
Desktop: Move up and down in note list using arrow keys
This commit is contained in:
parent
76b211eb6d
commit
998bdf3b56
@ -6,13 +6,23 @@ class ItemList extends React.Component {
|
||||
super();
|
||||
|
||||
this.scrollTop_ = 0;
|
||||
|
||||
this.listRef = React.createRef();
|
||||
|
||||
this.onScroll = this.onScroll.bind(this);
|
||||
this.onKeyDown = this.onKeyDown.bind(this);
|
||||
}
|
||||
|
||||
visibleItemCount(props) {
|
||||
if (typeof props === 'undefined') props = this.props;
|
||||
return Math.ceil(props.style.height / props.itemHeight);
|
||||
}
|
||||
|
||||
updateStateItemIndexes(props) {
|
||||
if (typeof props === 'undefined') props = this.props;
|
||||
|
||||
const topItemIndex = Math.floor(this.scrollTop_ / props.itemHeight);
|
||||
const visibleItemCount = Math.ceil(props.style.height / props.itemHeight);
|
||||
const visibleItemCount = this.visibleItemCount();
|
||||
|
||||
let bottomItemIndex = topItemIndex + visibleItemCount;
|
||||
if (bottomItemIndex >= props.items.length) bottomItemIndex = props.items.length - 1;
|
||||
@ -31,8 +41,31 @@ class ItemList extends React.Component {
|
||||
this.updateStateItemIndexes(newProps);
|
||||
}
|
||||
|
||||
onScroll(scrollTop) {
|
||||
onScroll(event) {
|
||||
this.scrollTop_ = event.target.scrollTop;
|
||||
this.updateStateItemIndexes();
|
||||
}
|
||||
|
||||
onKeyDown(event) {
|
||||
if (this.props.onKeyDown) this.props.onKeyDown(event);
|
||||
}
|
||||
|
||||
makeItemIndexVisible(itemIndex) {
|
||||
const top = Math.min(this.props.items.length - 1, this.state.topItemIndex + 1);
|
||||
const bottom = Math.max(0, this.state.bottomItemIndex - 1)
|
||||
|
||||
if (itemIndex >= top && itemIndex <= bottom) return;
|
||||
|
||||
let scrollTop = 0;
|
||||
if (itemIndex < top) {
|
||||
scrollTop = this.props.itemHeight * itemIndex;
|
||||
} else {
|
||||
scrollTop = this.props.itemHeight * itemIndex - this.visibleItemCount();
|
||||
}
|
||||
|
||||
this.scrollTop_ = scrollTop;
|
||||
this.listRef.current.scrollTop = scrollTop;
|
||||
|
||||
this.updateStateItemIndexes();
|
||||
}
|
||||
|
||||
@ -61,10 +94,8 @@ class ItemList extends React.Component {
|
||||
let classes = ['item-list'];
|
||||
if (this.props.className) classes.push(this.props.className);
|
||||
|
||||
const that = this;
|
||||
|
||||
return (
|
||||
<div className={classes.join(' ')} style={style} onScroll={ (event) => { this.onScroll(event.target.scrollTop) }}>
|
||||
<div ref={this.listRef} className={classes.join(' ')} style={style} onScroll={this.onScroll} onKeyDown={this.onKeyDown}>
|
||||
{ itemComps }
|
||||
</div>
|
||||
);
|
||||
|
@ -22,7 +22,11 @@ class NoteListComponent extends React.Component {
|
||||
constructor() {
|
||||
super();
|
||||
|
||||
this.itemListRef = React.createRef();
|
||||
this.itemAnchorRefs_ = {};
|
||||
|
||||
this.itemRenderer = this.itemRenderer.bind(this);
|
||||
this.onKeyDown = this.onKeyDown.bind(this);
|
||||
}
|
||||
|
||||
style() {
|
||||
@ -304,11 +308,15 @@ class NoteListComponent extends React.Component {
|
||||
<i style={watchedIconStyle} className={"fa fa-external-link"}></i>
|
||||
);
|
||||
|
||||
if (!this.itemAnchorRefs_[item.id]) this.itemAnchorRefs_[item.id] = React.createRef();
|
||||
const ref = this.itemAnchorRefs_[item.id];
|
||||
|
||||
// Need to include "todo_completed" in key so that checkbox is updated when
|
||||
// item is changed via sync.
|
||||
return <div key={item.id + '_' + item.todo_completed} style={style}>
|
||||
{checkbox}
|
||||
<a
|
||||
ref={ref}
|
||||
className="list-item"
|
||||
onContextMenu={(event) => this.itemContextMenu(event)}
|
||||
href="#"
|
||||
@ -324,6 +332,62 @@ class NoteListComponent extends React.Component {
|
||||
</div>
|
||||
}
|
||||
|
||||
itemAnchorRef(itemId) {
|
||||
if (this.itemAnchorRefs_[itemId] && this.itemAnchorRefs_[itemId].current) return this.itemAnchorRefs_[itemId].current;
|
||||
return null;
|
||||
}
|
||||
|
||||
onKeyDown(event) {
|
||||
const keyCode = event.keyCode;
|
||||
const noteIds = this.props.selectedNoteIds;
|
||||
|
||||
if (noteIds.length === 1 && (keyCode === 40 || keyCode === 38)) { // DOWN / UP
|
||||
const noteId = noteIds[0];
|
||||
let noteIndex = BaseModel.modelIndexById(this.props.notes, noteId);
|
||||
const inc = keyCode === 38 ? -1 : +1;
|
||||
|
||||
noteIndex += inc;
|
||||
|
||||
if (noteIndex < 0) noteIndex = 0;
|
||||
if (noteIndex > this.props.notes.length - 1) noteIndex = this.props.notes.length - 1;
|
||||
|
||||
const newSelectedNote = this.props.notes[noteIndex];
|
||||
|
||||
this.props.dispatch({
|
||||
type: 'NOTE_SELECT',
|
||||
id: newSelectedNote.id,
|
||||
});
|
||||
|
||||
this.itemListRef.current.makeItemIndexVisible(noteIndex);
|
||||
|
||||
// - We need to focus the item manually otherwise focus might be lost when the
|
||||
// list is scrolled and items within it are being rebuilt.
|
||||
// - We need to use an interval because when leaving the arrow pressed, the rendering
|
||||
// of items might lag behind and so the ref is not yet available at this point.
|
||||
if (!this.itemAnchorRef(newSelectedNote.id)) {
|
||||
if (this.focusItemIID_) clearInterval(this.focusItemIID_);
|
||||
this.focusItemIID_ = setInterval(() => {
|
||||
if (this.itemAnchorRef(newSelectedNote.id)) {
|
||||
this.itemAnchorRef(newSelectedNote.id).focus();
|
||||
clearInterval(this.focusItemIID_)
|
||||
this.focusItemIID_ = null;
|
||||
}
|
||||
}, 10);
|
||||
} else {
|
||||
this.itemAnchorRef(newSelectedNote.id).focus();
|
||||
}
|
||||
|
||||
event.preventDefault();
|
||||
}
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
if (this.focusItemIID_) {
|
||||
clearInterval(this.focusItemIID_);
|
||||
this.focusItemIID_ = null;
|
||||
}
|
||||
}
|
||||
|
||||
render() {
|
||||
const theme = themeStyle(this.props.theme);
|
||||
const style = this.props.style;
|
||||
@ -345,11 +409,13 @@ class NoteListComponent extends React.Component {
|
||||
|
||||
return (
|
||||
<ItemList
|
||||
ref={this.itemListRef}
|
||||
itemHeight={this.style().listItem.height}
|
||||
style={style}
|
||||
className={"note-list"}
|
||||
items={notes}
|
||||
itemRenderer={this.itemRenderer}
|
||||
onKeyDown={this.onKeyDown}
|
||||
></ItemList>
|
||||
);
|
||||
}
|
||||
|
@ -44,6 +44,13 @@ class BaseModel {
|
||||
return null;
|
||||
}
|
||||
|
||||
static modelIndexById(items, id) {
|
||||
for (let i = 0; i < items.length; i++) {
|
||||
if (items[i].id == id) return i;
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
// Prefer the use of this function to compare IDs as it handles the case where
|
||||
// one ID is null and the other is "", in which case they are actually considered to be the same.
|
||||
static idsEqual(id1, id2) {
|
||||
|
Loading…
Reference in New Issue
Block a user