Skip to content

Commit

Permalink
[rewrite branch] Fix keyboard shortcuts (#2284)
Browse files Browse the repository at this point in the history
* disable keybindings Monaco is overriding

* also remove selectHighlights

* remove cursorUndo, jumpToBracket

* first hack at checkbox insertion

* change tag toggle back to a toggle

* Expand detection of tasks and bullets for various line contents

* fix cmd+A select all

* fix tag/editor toggle

* use a context key to always show keyboard hint in the context menu

* use editorCommand instead of appCommand for insertChecklist

* fix Electron menu items

Co-authored-by: Dennis Snell <dennis.snell@automattic.com>
  • Loading branch information
codebykat and dmsnell authored Aug 21, 2020
1 parent c3edeb3 commit 45b27a9
Show file tree
Hide file tree
Showing 5 changed files with 106 additions and 31 deletions.
4 changes: 4 additions & 0 deletions desktop/menus/edit-menu.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,12 @@ const buildEditMenu = (settings, isAuthenticated) => {
{
label: '&Undo',
click: editorCommandSender({ action: 'undo' }),
accelerator: 'CommandOrControl+Z',
},
{
label: '&Redo',
click: editorCommandSender({ action: 'redo' }),
accelerator: 'CommandOrControl+Shift+Z',
},
{
type: 'separator',
Expand All @@ -33,6 +35,7 @@ const buildEditMenu = (settings, isAuthenticated) => {
{
label: '&Select All',
click: editorCommandSender({ action: 'selectAll' }),
accelerator: 'CommandOrControl+A',
},
{ type: 'separator' },
{
Expand All @@ -45,6 +48,7 @@ const buildEditMenu = (settings, isAuthenticated) => {
label: 'Search &Notes…',
visible: isAuthenticated,
click: appCommandSender({ action: 'focusSearchField' }),
accelerator: 'CommandOrControl+Shift+S',
},
{ type: 'separator' },
{
Expand Down
4 changes: 2 additions & 2 deletions desktop/menus/format-menu.js
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
const { appCommandSender } = require('./utils');
const { editorCommandSender } = require('./utils');

const buildFormatMenu = (isAuthenticated) => {
isAuthenticated = isAuthenticated || false;
const submenu = [
{
label: 'Insert &Checklist',
accelerator: 'CommandOrControl+Shift+C',
click: appCommandSender({ action: 'insertChecklist' }),
click: editorCommandSender({ action: 'insertChecklist' }),
},
];

Expand Down
13 changes: 4 additions & 9 deletions lib/app.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,6 @@ type DispatchProps = {
closeNote: () => any;
createNote: () => any;
focusSearchField: () => any;
openTagList: () => any;
setLineLength: (length: T.LineLength) => any;
setNoteDisplay: (displayMode: T.ListDisplayMode) => any;
setSortType: (sortType: T.SortType) => any;
Expand All @@ -45,6 +44,7 @@ type DispatchProps = {
toggleSortOrder: () => any;
toggleSortTagsAlpha: () => any;
toggleSpellCheck: () => any;
toggleTagList: () => any;
};

type Props = OwnProps & StateProps & DispatchProps;
Expand Down Expand Up @@ -76,13 +76,8 @@ class AppComponent extends Component<Props> {
const cmdOrCtrl = (ctrlKey || metaKey) && ctrlKey !== metaKey;

// open tag list
if (
cmdOrCtrl &&
shiftKey &&
'KeyU' === code &&
!this.props.showNavigation
) {
this.props.openTagList();
if (cmdOrCtrl && shiftKey && 'KeyU' === code) {
this.props.toggleTagList();

event.stopPropagation();
event.preventDefault();
Expand Down Expand Up @@ -186,7 +181,6 @@ const mapDispatchToProps: S.MapDispatch<DispatchProps> = (dispatch) => {
closeNote: () => dispatch(closeNote()),
createNote: () => dispatch(createNote()),
focusSearchField: () => dispatch(actions.ui.focusSearchField()),
openTagList: () => dispatch(toggleNavigation()),
setLineLength: (length) => dispatch(settingsActions.setLineLength(length)),
setNoteDisplay: (displayMode) =>
dispatch(settingsActions.setNoteDisplay(displayMode)),
Expand All @@ -197,6 +191,7 @@ const mapDispatchToProps: S.MapDispatch<DispatchProps> = (dispatch) => {
toggleSortOrder: () => dispatch(settingsActions.toggleSortOrder()),
toggleSortTagsAlpha: () => dispatch(settingsActions.toggleSortTagsAlpha()),
toggleSpellCheck: () => dispatch(settingsActions.toggleSpellCheck()),
toggleTagList: () => dispatch(toggleNavigation()),
};
};

Expand Down
112 changes: 96 additions & 16 deletions lib/note-content-editor.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,11 @@ import * as T from './types';

const SPEED_DELAY = 120;

type OwnProps = {
storeFocusEditor: (focusSetter: () => any) => any;
storeHasFocus: (focusGetter: () => boolean) => any;
};

type StateProps = {
editorSelection: [number, number, 'RTL' | 'LTR'];
fontSize: number;
Expand All @@ -42,7 +47,7 @@ type DispatchProps = {
) => any;
};

type Props = StateProps & DispatchProps;
type Props = OwnProps & StateProps & DispatchProps;

type OwnState = {
content: string;
Expand Down Expand Up @@ -86,7 +91,6 @@ class NoteContentEditor extends Component<Props> {

componentDidMount() {
const { noteId } = this.props;
window.addEventListener('keydown', this.handleKeys, true);
this.bootTimer = setTimeout(() => {
if (noteId === this.props.noteId) {
this.setState({
Expand All @@ -95,14 +99,15 @@ class NoteContentEditor extends Component<Props> {
});
}
}, SPEED_DELAY);
this.props.storeFocusEditor(this.focusEditor);
this.props.storeHasFocus(this.hasFocus);
}

componentWillUnmount() {
if (this.bootTimer) {
clearTimeout(this.bootTimer);
}
window.electron?.removeListener('editorCommand');
window.removeEventListener('keydown', this.handleKeys, true);
}

componentDidUpdate(prevProps: Props) {
Expand Down Expand Up @@ -147,22 +152,52 @@ class NoteContentEditor extends Component<Props> {
}
}

handleKeys = (event: KeyboardEvent) => {
if (!this.props.keyboardShortcuts) {
focusEditor = () => this.editor?.focus();

hasFocus = () => this.editor?.hasTextFocus();

insertOrRemoveCheckboxes = (editor: Editor.IStandaloneCodeEditor) => {
// todo: we're not disabling this if !this.props.keyboardShortcuts, do we want to?
const model = editor.getModel();
if (!model) {
return;
}

const { code, ctrlKey, metaKey, shiftKey } = event;
const cmdOrCtrl = ctrlKey || metaKey;

if (cmdOrCtrl && shiftKey && 'KeyC' === code) {
this.props.insertTask();
event.stopPropagation();
event.preventDefault();
return false;
const position = editor.getPosition();
if (!position) {
return;
}
const lineNumber = position.lineNumber;
const thisLine = model.getLineContent(lineNumber);

// "(1)A line without leading space"
// "(1 )A line with leading space"
// "(1 )(3\ue000 )A line with a task and leading space"
// "(1 )(2- )A line with a bullet"
// "(1 )(2* )(3\ue001 )Bulleted task"
const match = /^(\s*)([-+*\u2022]\s*)?([\ue000\ue001]\s+)?/.exec(thisLine);
if (!match) {
// this shouldn't be able to fail since it requires no characters
return;
}

return true;
const [fullMatch, prefixSpace, bullet, existingTask] = match;
const hasTask = 'undefined' !== typeof existingTask;

const lineOffset = prefixSpace.length + (bullet?.length ?? 0) + 1;
const text = hasTask ? '' : '\ue000 ';
const range = new this.monaco.Range(
lineNumber,
lineOffset,
lineNumber,
lineOffset + (existingTask?.length ?? 0)
);

const identifier = { major: 1, minor: 1 };
const op = { identifier, range, text, forceMoveMarkers: true };
editor.executeEdits('insertOrRemoveCheckboxes', [op]);

this.props.insertTask();
};

editorInit: EditorWillMount = () => {
Expand Down Expand Up @@ -200,16 +235,61 @@ class NoteContentEditor extends Component<Props> {
this.editor = editor;
this.monaco = monaco;

// remove keybindings; see https://github.com/microsoft/monaco-editor/issues/287
const shortcutsToDisable = [
'cursorUndo', // meta+U
'editor.action.commentLine', // meta+/
'editor.action.jumpToBracket', // shift+meta+\
'editor.action.transposeLetters', // ctrl+T
'editor.action.triggerSuggest', // ctrl+space
'expandLineSelection', // meta+L
// search shortcuts
'actions.find',
'actions.findWithSelection',
'editor.action.addSelectionToNextFindMatch',
'editor.action.nextMatchFindAction',
'editor.action.selectHighlights',
];
// let Electron menus trigger these
if (window.electron) {
shortcutsToDisable.push('undo', 'redo', 'editor.action.selectAll');
}
shortcutsToDisable.forEach(function (action) {
editor._standaloneKeybindingService.addDynamicKeybinding('-' + action);
});

// disable editor keybindings for Electron since it is handled by editorCommand
// doing it this way will always show the keyboard hint in the context menu!
editor.createContextKey(
'allowBrowserKeybinding',
window.electron ? false : true
);

editor.addAction({
id: 'insertChecklist',
label: 'Insert Checklist',
keybindings: [
monaco.KeyMod.CtrlCmd | monaco.KeyMod.Shift | monaco.KeyCode.KEY_C,
],
contextMenuGroupId: '9_cutcopypaste',
contextMenuOrder: 9,
keybindingContext: 'allowBrowserKeybinding',
run: this.insertOrRemoveCheckboxes,
});

window.electron?.receive('editorCommand', (command) => {
switch (command.action) {
case 'insertChecklist':
editor.trigger('editorCommand', 'insertChecklist', null);
return;
case 'redo':
editor.trigger('', 'redo');
editor.trigger('editorCommand', 'redo', null);
return;
case 'selectAll':
editor.setSelection(editor.getModel().getFullModelRange());
return;
case 'undo':
editor.trigger('', 'undo');
editor.trigger('editorCommand', 'undo', null);
return;
}
});
Expand Down
4 changes: 0 additions & 4 deletions lib/state/electron/middleware.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,10 +29,6 @@ export const middleware: S.Middleware = ({ dispatch, getState }) => {
dispatch(actions.ui.focusSearchField());
return;

case 'insertChecklist':
dispatch({ type: 'INSERT_TASK' });
return;

case 'showDialog':
dispatch(actions.ui.showDialog(command.dialog));
return;
Expand Down

0 comments on commit 45b27a9

Please sign in to comment.