1
0
mirror of https://github.com/laurent22/joplin.git synced 2025-03-11 14:09:55 +02:00

Desktop: Accessibility: Prevent overwrite mode toggle shortcut from conflicting with screen reader shortcuts (#11447)

This commit is contained in:
Henry Heino 2024-11-27 04:18:16 -08:00 committed by GitHub
parent 257b58f4fb
commit 4e8b896688
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
5 changed files with 143 additions and 10 deletions

View File

@ -918,6 +918,7 @@ packages/editor/CodeMirror/utils/growSelectionToNode.js
packages/editor/CodeMirror/utils/handlePasteEvent.js
packages/editor/CodeMirror/utils/isCursorAtBeginning.js
packages/editor/CodeMirror/utils/isInSyntaxNode.js
packages/editor/CodeMirror/utils/keyUpHandlerExtension.js
packages/editor/CodeMirror/utils/overwriteModeExtension.test.js
packages/editor/CodeMirror/utils/overwriteModeExtension.js
packages/editor/CodeMirror/utils/searchExtension.js

1
.gitignore vendored
View File

@ -894,6 +894,7 @@ packages/editor/CodeMirror/utils/growSelectionToNode.js
packages/editor/CodeMirror/utils/handlePasteEvent.js
packages/editor/CodeMirror/utils/isCursorAtBeginning.js
packages/editor/CodeMirror/utils/isInSyntaxNode.js
packages/editor/CodeMirror/utils/keyUpHandlerExtension.js
packages/editor/CodeMirror/utils/overwriteModeExtension.test.js
packages/editor/CodeMirror/utils/overwriteModeExtension.js
packages/editor/CodeMirror/utils/searchExtension.js

View File

@ -0,0 +1,66 @@
import { EditorView, ViewPlugin } from '@codemirror/view';
interface OnKeyUpEvent {
domEvent: KeyboardEvent;
view: EditorView;
// true if other keys were pressed (and possibly released) during the time
// that this key was down (and before it was released).
otherKeysWerePressed: boolean;
}
// Should return true if the event was handled
type OnKeyUpHandler = (event: OnKeyUpEvent)=> boolean;
// Should return true if the given event matches the key that should
// be handled by the extension:
type IsTargetKeyCallback = (event: KeyboardEvent)=> boolean;
// CodeMirror's built-in keyboard event handlers trigger on key down, rather than on
// key up. In some cases, it's useful to trigger keyboard shortcuts on key up instead.
// For example, if a shortcut should only activate if it isn't pressed at the same time
// as other keys.
const keyUpHandlerExtension = (isTargetKey: IsTargetKeyCallback, onKeyUp: OnKeyUpHandler) => {
return ViewPlugin.fromClass(class {
private otherKeysWerePressed = false;
private targetKeyDown = false;
public constructor(private view: EditorView) {
view.contentDOM.addEventListener('keydown', this.onKeyDown);
view.contentDOM.addEventListener('keyup', this.onKeyUp);
}
public destroy() {
this.view?.contentDOM?.removeEventListener('keyup', this.onKeyUp);
}
private onKeyDown = (event: KeyboardEvent) => {
if (isTargetKey(event)) {
this.targetKeyDown = true;
} else if (this.targetKeyDown) {
this.otherKeysWerePressed = true;
}
};
private onKeyUp = (event: KeyboardEvent) => {
if (isTargetKey(event)) {
if (this.targetKeyDown && !event.defaultPrevented) {
const handled = onKeyUp({
domEvent: event,
view: this.view,
otherKeysWerePressed: this.otherKeysWerePressed,
});
if (handled) {
event.preventDefault();
}
}
this.targetKeyDown = false;
this.otherKeysWerePressed = false;
} else if (this.targetKeyDown) {
this.otherKeysWerePressed = true;
}
};
});
};
export default keyUpHandlerExtension;

View File

@ -1,6 +1,6 @@
import { EditorSelection } from '@codemirror/state';
import createTestEditor from '../testUtil/createTestEditor';
import overwriteModeExtension, { toggleOverwrite } from './overwriteModeExtension';
import overwriteModeExtension, { overwriteModeEnabled, toggleOverwrite } from './overwriteModeExtension';
import typeText from '../testUtil/typeText';
import pressReleaseKey from '../testUtil/pressReleaseKey';
@ -36,6 +36,8 @@ describe('overwriteModeExtension', () => {
const editor = await createEditor('Test!');
pressReleaseKey(editor, { key: 'Insert', code: 'Insert' });
expect(overwriteModeEnabled(editor)).toBe(true);
typeText(editor, 'Exam');
expect(editor.state.doc.toString()).toBe('Exam!');
@ -44,6 +46,35 @@ describe('overwriteModeExtension', () => {
expect(editor.state.doc.toString()).toBe('Example!');
});
test('should be disabled by pressing <escape>', async () => {
const editor = await createEditor('Test!', true);
expect(overwriteModeEnabled(editor)).toBe(true);
pressReleaseKey(editor, { key: 'Escape', code: 'Escape' });
expect(overwriteModeEnabled(editor)).toBe(false);
// Escape should not re-enable the extension
pressReleaseKey(editor, { key: 'Escape', code: 'Escape' });
expect(overwriteModeEnabled(editor)).toBe(false);
});
test('<insert> should not toggle overwrite mode if other keys are pressed', async () => {
// On Linux, the Orca screen reader's default "do screen reader action" key is
// <insert>. To avoid toggling insert mode when users perform screen reader actions,
// pressing <insert> should only toggle insert mode in certain cases.
const editor = await createEditor('Test');
const insertKey = { code: 'Insert', key: 'Insert ' };
editor.contentDOM.dispatchEvent(new KeyboardEvent('keydown', insertKey));
expect(overwriteModeEnabled(editor)).toBe(false);
// Pressing & releasing the space key should prevent insert mode from being enabled
pressReleaseKey(editor, { code: 'Space', key: 'Space' });
editor.contentDOM.dispatchEvent(new KeyboardEvent('keyup', insertKey));
expect(overwriteModeEnabled(editor)).toBe(false);
});
test('should insert text if the cursor is at the end of a line', async () => {
const editor = await createEditor('\nTest', true);
typeText(editor, 'Test! This is a test! ');

View File

@ -1,5 +1,6 @@
import { keymap, EditorView } from '@codemirror/view';
import { EditorView, keymap } from '@codemirror/view';
import { StateField, Facet, StateEffect } from '@codemirror/state';
import keyUpHandlerExtension from './keyUpHandlerExtension';
const overwriteModeFacet = Facet.define({
combine: values => values[0] ?? false,
@ -62,17 +63,50 @@ const overwriteModeState = StateField.define({
],
});
export const overwriteModeEnabled = (view: EditorView) => {
return view.state.field(overwriteModeState);
};
const setOverwriteModeEnabled = (enabled: boolean, view: EditorView) => {
view.dispatch({
effects: [
toggleOverwrite.of(enabled),
EditorView.announce.of(
// TODO: Localize:
enabled ? 'Overwrite mode enabled' : 'Overwrite mode disabled',
),
],
});
};
const overwriteModeExtension = [
overwriteModeState,
keymap.of([{
key: 'Insert',
run: (view) => {
view.dispatch({
effects: toggleOverwrite.of(!view.state.field(overwriteModeState)),
});
return false;
keymap.of([
{
// The <escape> keyboard shortcut may be more easily discoverable for users
// who enter overwrite mode unintentionally.
key: 'Escape',
run: (view) => {
if (overwriteModeEnabled(view)) {
setOverwriteModeEnabled(false, view);
return true;
}
return false;
},
},
}]),
]),
keyUpHandlerExtension(
(event) => (
event.code === 'Insert' && !event.shiftKey && !event.altKey && !event.metaKey && !event.ctrlKey
),
({ view, otherKeysWerePressed }) => {
if (otherKeysWerePressed) {
return false;
}
setOverwriteModeEnabled(!overwriteModeEnabled(view), view);
return true;
},
),
];
export default overwriteModeExtension;