Keyboard shortcuts for modifying options menu, creating new note, and deleting note
This commit is contained in:
@@ -23,7 +23,8 @@ angular.module('app')
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
.controller('EditorCtrl', function ($sce, $timeout, authManager, $rootScope, actionsManager,
|
.controller('EditorCtrl', function ($sce, $timeout, authManager, $rootScope, actionsManager,
|
||||||
syncManager, modelManager, themeManager, componentManager, storageManager, sessionHistory, privilegesManager) {
|
syncManager, modelManager, themeManager, componentManager, storageManager, sessionHistory,
|
||||||
|
privilegesManager, keyboardManager) {
|
||||||
|
|
||||||
this.spellcheck = true;
|
this.spellcheck = true;
|
||||||
this.componentManager = componentManager;
|
this.componentManager = componentManager;
|
||||||
@@ -121,6 +122,9 @@ angular.module('app')
|
|||||||
this.showExtensions = false;
|
this.showExtensions = false;
|
||||||
this.showMenu = false;
|
this.showMenu = false;
|
||||||
this.noteStatus = null;
|
this.noteStatus = null;
|
||||||
|
// When setting alt key down and deleting note, an alert will come up and block the key up event when alt is released.
|
||||||
|
// We reset it on set note so that the alt menu restores to default.
|
||||||
|
this.altKeyDown = false;
|
||||||
this.loadTagsString();
|
this.loadTagsString();
|
||||||
|
|
||||||
let onReady = () => {
|
let onReady = () => {
|
||||||
@@ -796,10 +800,24 @@ angular.module('app')
|
|||||||
syncManager.sync();
|
syncManager.sync();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
this.altKeyObserver = keyboardManager.addKeyObserver({
|
||||||
|
modifiers: [KeyboardManager.KeyModifierAlt],
|
||||||
|
onKeyDown: () => {
|
||||||
|
this.altKeyDown = true;
|
||||||
|
},
|
||||||
|
onKeyUp: () => {
|
||||||
|
this.altKeyDown = false;
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
this.deleteKeyObserver = keyboardManager.addKeyObserver({
|
||||||
|
key: KeyboardManager.KeyBackspace,
|
||||||
|
notElementIds: ["note-text-editor", "note-title-editor"],
|
||||||
|
modifiers: [KeyboardManager.KeyModifierMeta],
|
||||||
|
onKeyDown: () => {
|
||||||
|
this.deleteNote();
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
/*
|
/*
|
||||||
Editor Customization
|
Editor Customization
|
||||||
@@ -810,49 +828,57 @@ angular.module('app')
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
this.loadedTabListener = true;
|
this.loadedTabListener = true;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Insert 4 spaces when a tab key is pressed,
|
* Insert 4 spaces when a tab key is pressed,
|
||||||
* only used when inside of the text editor.
|
* only used when inside of the text editor.
|
||||||
* If the shift key is pressed first, this event is
|
* If the shift key is pressed first, this event is
|
||||||
* not fired.
|
* not fired.
|
||||||
*/
|
*/
|
||||||
var parent = this;
|
|
||||||
var handleTab = function (event) {
|
const editor = document.getElementById("note-text-editor");
|
||||||
if(!event.shiftKey && event.which == 9) {
|
this.tabObserver = keyboardManager.addKeyObserver({
|
||||||
if(parent.note.locked) {
|
element: editor,
|
||||||
|
key: KeyboardManager.KeyTab,
|
||||||
|
onKeyDown: (event) => {
|
||||||
|
if(event.shiftKey) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if(this.note.locked) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
|
|
||||||
// Using document.execCommand gives us undo support
|
// Using document.execCommand gives us undo support
|
||||||
if(!document.execCommand("insertText", false, "\t")) {
|
let insertSuccessful = document.execCommand("insertText", false, "\t");
|
||||||
|
if(!insertSuccessful) {
|
||||||
// document.execCommand works great on Chrome/Safari but not Firefox
|
// document.execCommand works great on Chrome/Safari but not Firefox
|
||||||
var start = this.selectionStart;
|
var start = editor.selectionStart;
|
||||||
var end = this.selectionEnd;
|
var end = editor.selectionEnd;
|
||||||
var spaces = " ";
|
var spaces = " ";
|
||||||
|
|
||||||
// Insert 4 spaces
|
// Insert 4 spaces
|
||||||
this.value = this.value.substring(0, start)
|
editor.value = editor.value.substring(0, start)
|
||||||
+ spaces + this.value.substring(end);
|
+ spaces + editor.value.substring(end);
|
||||||
|
|
||||||
// Place cursor 4 spaces away from where
|
// Place cursor 4 spaces away from where
|
||||||
// the tab key was pressed
|
// the tab key was pressed
|
||||||
this.selectionStart = this.selectionEnd = start + 4;
|
editor.selectionStart = editor.selectionEnd = start + 4;
|
||||||
}
|
}
|
||||||
|
|
||||||
parent.note.text = this.value;
|
this.note.text = editor.value;
|
||||||
parent.changesMade({bypassDebouncer: true});
|
this.changesMade({bypassDebouncer: true});
|
||||||
}
|
},
|
||||||
}
|
})
|
||||||
|
|
||||||
var element = document.getElementById("note-text-editor");
|
// This handles when the editor itself is destroyed, and not when our controller is destroyed.
|
||||||
element.addEventListener('keydown', handleTab);
|
angular.element(editor).on('$destroy', function(){
|
||||||
|
if(this.tabObserver) {
|
||||||
angular.element(element).on('$destroy', function(){
|
keyboardManager.removeKeyObserver(this.tabObserver);
|
||||||
window.removeEventListener('keydown', handleTab);
|
|
||||||
this.loadedTabListener = false;
|
this.loadedTabListener = false;
|
||||||
}.bind(this))
|
|
||||||
}
|
}
|
||||||
|
}.bind(this));
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -23,7 +23,7 @@ angular.module('app')
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
.controller('NotesCtrl', function (authManager, $timeout, $rootScope, modelManager,
|
.controller('NotesCtrl', function (authManager, $timeout, $rootScope, modelManager,
|
||||||
syncManager, storageManager, desktopManager, privilegesManager) {
|
syncManager, storageManager, desktopManager, privilegesManager, keyboardManager) {
|
||||||
|
|
||||||
this.panelController = {};
|
this.panelController = {};
|
||||||
this.searchSubmitted = false;
|
this.searchSubmitted = false;
|
||||||
@@ -566,4 +566,15 @@ angular.module('app')
|
|||||||
return result;
|
return result;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// In the browser we're not allowed to override cmd/ctrl + n, so we have to use Control modifier as well.
|
||||||
|
// These rules don't apply to desktop, but probably better to be consistent.
|
||||||
|
this.newNoteKeyObserver = keyboardManager.addKeyObserver({
|
||||||
|
key: "n",
|
||||||
|
modifiers: [KeyboardManager.KeyModifierMeta, KeyboardManager.KeyModifierCtrl],
|
||||||
|
onKeyDown: (event) => {
|
||||||
|
event.preventDefault();
|
||||||
|
this.createNewNote();
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|||||||
94
app/assets/javascripts/app/services/keyboardManager.js
Normal file
94
app/assets/javascripts/app/services/keyboardManager.js
Normal file
@@ -0,0 +1,94 @@
|
|||||||
|
class KeyboardManager {
|
||||||
|
|
||||||
|
constructor($timeout) {
|
||||||
|
this.observers = [];
|
||||||
|
|
||||||
|
this.$timeout = $timeout;
|
||||||
|
|
||||||
|
KeyboardManager.KeyTab = "Tab";
|
||||||
|
KeyboardManager.KeyBackspace = "Backspace";
|
||||||
|
|
||||||
|
KeyboardManager.KeyModifierShift = "Shift";
|
||||||
|
KeyboardManager.KeyModifierCtrl = "Control";
|
||||||
|
// ⌘ key on Mac, ⊞ key on Windows
|
||||||
|
KeyboardManager.KeyModifierMeta = "Meta";
|
||||||
|
KeyboardManager.KeyModifierAlt = "Alt";
|
||||||
|
|
||||||
|
KeyboardManager.KeyEventDown = "KeyEventDown";
|
||||||
|
KeyboardManager.KeyEventUp = "KeyEventUp";
|
||||||
|
|
||||||
|
window.addEventListener('keydown', this.handleKeyDown.bind(this));
|
||||||
|
window.addEventListener('keyup', this.handleKeyUp.bind(this));
|
||||||
|
}
|
||||||
|
|
||||||
|
eventMatchesKeyAndModifiers(event, key, modifiers = []) {
|
||||||
|
|
||||||
|
for(let modifier of modifiers) {
|
||||||
|
// For a modifier like ctrlKey, must check both event.ctrlKey and event.key.
|
||||||
|
// That's because on keyup, event.ctrlKey would be false, but event.key == Control would be true.
|
||||||
|
let matches = (
|
||||||
|
((event.ctrlKey || event.key == KeyboardManager.KeyModifierCtrl) && modifier === KeyboardManager.KeyModifierCtrl) ||
|
||||||
|
((event.metaKey || event.key == KeyboardManager.KeyModifierMeta) && modifier === KeyboardManager.KeyModifierMeta) ||
|
||||||
|
((event.altKey || event.key == KeyboardManager.KeyModifierAlt) && modifier === KeyboardManager.KeyModifierAlt) ||
|
||||||
|
((event.shiftKey || event.key == KeyboardManager.KeyModifierShift) && modifier === KeyboardManager.KeyModifierShift)
|
||||||
|
)
|
||||||
|
|
||||||
|
if(!matches) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Modifers match, check key
|
||||||
|
if(!key) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
return key == event.key;
|
||||||
|
}
|
||||||
|
|
||||||
|
notifyObserver(event, keyEventType) {
|
||||||
|
for(let observer of this.observers) {
|
||||||
|
if(observer.element && event.target != observer.element) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(observer.notElement && observer.notElement == event.target) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(observer.notElementIds && observer.notElementIds.includes(event.target.id)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(this.eventMatchesKeyAndModifiers(event, observer.key, observer.modifiers)) {
|
||||||
|
let callback = keyEventType == KeyboardManager.KeyEventDown ? observer.onKeyDown : observer.onKeyUp;
|
||||||
|
if(callback) {
|
||||||
|
this.$timeout(() => {
|
||||||
|
callback(event);
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
handleKeyDown(event) {
|
||||||
|
this.notifyObserver(event, KeyboardManager.KeyEventDown);
|
||||||
|
}
|
||||||
|
|
||||||
|
handleKeyUp(event) {
|
||||||
|
this.notifyObserver(event, KeyboardManager.KeyEventUp);
|
||||||
|
}
|
||||||
|
|
||||||
|
addKeyObserver({key, modifiers, onKeyDown, onKeyUp, element, notElement, notElementIds}) {
|
||||||
|
let observer = {key, modifiers, onKeyDown, onKeyUp, element, notElement, notElementIds};
|
||||||
|
this.observers.push(observer);
|
||||||
|
return observer;
|
||||||
|
}
|
||||||
|
|
||||||
|
removeKeyObserver(observer) {
|
||||||
|
this.observers.splice(this.observers.indexOf(observer), 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
angular.module('app').service('keyboardManager', KeyboardManager);
|
||||||
@@ -38,13 +38,13 @@
|
|||||||
%menu-row{"label" => "ctrl.note.locked ? 'Unlock' : 'Lock'", "action" => "ctrl.selectedMenuItem(true); ctrl.toggleLockNote()", "desc" => "'Locking notes prevents unintentional editing'"}
|
%menu-row{"label" => "ctrl.note.locked ? 'Unlock' : 'Lock'", "action" => "ctrl.selectedMenuItem(true); ctrl.toggleLockNote()", "desc" => "'Locking notes prevents unintentional editing'"}
|
||||||
%menu-row{"label" => "ctrl.note.content.protected ? 'Unprotect' : 'Protect'", "action" => "ctrl.selectedMenuItem(true); ctrl.toggleProtectNote()", "desc" => "'Protecting a note will require credentials to view it (Manage Privileges via Account menu)'"}
|
%menu-row{"label" => "ctrl.note.content.protected ? 'Unprotect' : 'Protect'", "action" => "ctrl.selectedMenuItem(true); ctrl.toggleProtectNote()", "desc" => "'Protecting a note will require credentials to view it (Manage Privileges via Account menu)'"}
|
||||||
%menu-row{"label" => "'Preview'", "circle" => "ctrl.note.content.hidePreview ? 'danger' : 'success'", "circle-align" => "'right'", "action" => "ctrl.selectedMenuItem(true); ctrl.toggleNotePreview()", "desc" => "'Hide or unhide the note preview from the list of notes'"}
|
%menu-row{"label" => "'Preview'", "circle" => "ctrl.note.content.hidePreview ? 'danger' : 'success'", "circle-align" => "'right'", "action" => "ctrl.selectedMenuItem(true); ctrl.toggleNotePreview()", "desc" => "'Hide or unhide the note preview from the list of notes'"}
|
||||||
%menu-row{"ng-show" => "!ctrl.note.content.trashed && !ctrl.note.errorDecrypting", "label" => "'Move to Trash'", "action" => "ctrl.selectedMenuItem(); ctrl.deleteNote()", "stylekit-class" => "'warning'", "desc" => "'Send this note to the trash'"}
|
%menu-row{"ng-show" => "!ctrl.altKeyDown && !ctrl.note.content.trashed && !ctrl.note.errorDecrypting", "label" => "'Move to Trash'", "action" => "ctrl.selectedMenuItem(); ctrl.deleteNote()", "stylekit-class" => "'warning'", "desc" => "'Send this note to the trash'"}
|
||||||
%menu-row{"ng-show" => "!ctrl.note.content.trashed && ctrl.note.errorDecrypting", "label" => "'Delete Permanently'", "action" => "ctrl.selectedMenuItem(); ctrl.deleteNotePermanantely()", "stylekit-class" => "'danger'", "desc" => "'Delete this note permanently from all your devices'"}
|
%menu-row{"ng-show" => "!ctrl.note.content.trashed && ctrl.note.errorDecrypting", "label" => "'Delete Permanently'", "action" => "ctrl.selectedMenuItem(); ctrl.deleteNotePermanantely()", "stylekit-class" => "'danger'", "desc" => "'Delete this note permanently from all your devices'"}
|
||||||
|
|
||||||
%div{"ng-if" => "ctrl.note.content.trashed"}
|
%div{"ng-if" => "ctrl.note.content.trashed || ctrl.altKeyDown"}
|
||||||
%menu-row{"label" => "'Restore'", "action" => "ctrl.selectedMenuItem(true); ctrl.restoreTrashedNote()", "stylekit-class" => "'info'", "desc" => "'Undelete this note and restore it back into your notes'"}
|
%menu-row{"label" => "'Restore'", "ng-show" => "ctrl.note.content.trashed", "action" => "ctrl.selectedMenuItem(true); ctrl.restoreTrashedNote()", "stylekit-class" => "'info'", "desc" => "'Undelete this note and restore it back into your notes'"}
|
||||||
%menu-row{"label" => "'Delete Permanently'", "action" => "ctrl.selectedMenuItem(true); ctrl.deleteNotePermanantely()", "stylekit-class" => "'danger'", "desc" => "'Delete this note permanently from all your devices'"}
|
%menu-row{"label" => "'Delete Permanently'", "action" => "ctrl.selectedMenuItem(true); ctrl.deleteNotePermanantely()", "stylekit-class" => "'danger'", "desc" => "'Delete this note permanently from all your devices'"}
|
||||||
%menu-row{"label" => "'Empty Trash'", "subtitle" => "ctrl.getTrashCount() + ' notes in trash'", "action" => "ctrl.selectedMenuItem(true); ctrl.emptyTrash()", "stylekit-class" => "'danger'", "desc" => "'Permanently delete all notes in the trash'"}
|
%menu-row{"label" => "'Empty Trash'", "ng-show" => "ctrl.note.content.trashed || !ctrl.altKeyDown", "subtitle" => "ctrl.getTrashCount() + ' notes in trash'", "action" => "ctrl.selectedMenuItem(true); ctrl.emptyTrash()", "stylekit-class" => "'danger'", "desc" => "'Permanently delete all notes in the trash'"}
|
||||||
|
|
||||||
.sk-menu-panel-section
|
.sk-menu-panel-section
|
||||||
.sk-menu-panel-header
|
.sk-menu-panel-header
|
||||||
|
|||||||
Reference in New Issue
Block a user