diff --git a/ElectronClient/app/gui/NoteList.jsx b/ElectronClient/app/gui/NoteList.jsx index c7b8ebe66a..759ae919c9 100644 --- a/ElectronClient/app/gui/NoteList.jsx +++ b/ElectronClient/app/gui/NoteList.jsx @@ -365,22 +365,7 @@ class NoteListComponent extends React.Component { 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(); - } + this.focusNoteId_(newSelectedNote.id); event.preventDefault(); } @@ -389,6 +374,40 @@ class NoteListComponent extends React.Component { event.preventDefault(); await this.confirmDeleteNotes(noteIds); } + + if (noteIds.length && keyCode === 32) { // SPACE + event.preventDefault(); + + const notes = BaseModel.modelsByIds(this.props.notes, noteIds); + const todos = notes.filter(n => !!n.is_todo); + if (!todos.length) return; + + for (let i = 0; i < todos.length; i++) { + const toggledTodo = Note.toggleTodoCompleted(todos[i]); + await Note.save(toggledTodo); + } + + this.focusNoteId_(todos[0].id); + } + } + + focusNoteId_(noteId) { + // - 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(noteId)) { + if (this.focusItemIID_) clearInterval(this.focusItemIID_); + this.focusItemIID_ = setInterval(() => { + if (this.itemAnchorRef(noteId)) { + this.itemAnchorRef(noteId).focus(); + clearInterval(this.focusItemIID_) + this.focusItemIID_ = null; + } + }, 10); + } else { + this.itemAnchorRef(noteId).focus(); + } } componentWillUnmount() { diff --git a/ReactNativeClient/lib/BaseModel.js b/ReactNativeClient/lib/BaseModel.js index 3758069c52..41ae835bdb 100644 --- a/ReactNativeClient/lib/BaseModel.js +++ b/ReactNativeClient/lib/BaseModel.js @@ -51,6 +51,16 @@ class BaseModel { return -1; } + static modelsByIds(items, ids) { + const output = []; + for (let i = 0; i < items.length; i++) { + if (ids.indexOf(items[i].id) >= 0) { + output.push(items[i]); + } + } + return output; + } + // 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) { diff --git a/ReactNativeClient/lib/models/Note.js b/ReactNativeClient/lib/models/Note.js index 0a016ba915..3b394de933 100644 --- a/ReactNativeClient/lib/models/Note.js +++ b/ReactNativeClient/lib/models/Note.js @@ -474,6 +474,19 @@ class Note extends BaseItem { return this.changeNoteType(note, !!note.is_todo ? 'note' : 'todo'); } + static toggleTodoCompleted(note) { + if (!('todo_completed' in note)) throw new Error('Missing "todo_completed" property'); + + note = Object.assign({}, note); + if (note.todo_completed) { + note.todo_completed = 0; + } else { + note.todo_completed = Date.now(); + } + + return note; + } + static async duplicate(noteId, options = null) { const changes = options && options.changes; const uniqueTitle = options && options.uniqueTitle;