mirror of
https://github.com/codex-team/editor.js
synced 2024-05-03 23:23:23 +02:00
fix(slash): do not handle / + shift/alt, support for ascii keyboard (#2599)
* fix(slash): do not handle / + shift/alt, support for ascii keyboard * support keyboards without physical '/'
This commit is contained in:
parent
9542551d84
commit
b619946e8f
|
@ -32,6 +32,7 @@
|
|||
"ArrayLike": true,
|
||||
"InputEvent": true,
|
||||
"unknown": true,
|
||||
"requestAnimationFrame": true
|
||||
"requestAnimationFrame": true,
|
||||
"navigator": true
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,5 +1,10 @@
|
|||
# Changelog
|
||||
|
||||
### 2.29.1
|
||||
|
||||
- `Fix` — Toolbox wont be shown when Slash pressed with along with Shift or Alt
|
||||
- `Fix` — Toolbox will be opened when Slash pressed in non-US keyboard layout where there is no physical '/' key.
|
||||
|
||||
### 2.29.0
|
||||
|
||||
- `New` — Editor Config now has the `style.nonce` attribute that could be used to allowlist editor style tag for Content Security Policy "style-src"
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "@editorjs/editorjs",
|
||||
"version": "2.29.0",
|
||||
"version": "2.29.1",
|
||||
"description": "Editor.js — Native JS, based on API and Open Source",
|
||||
"main": "dist/editorjs.umd.js",
|
||||
"module": "dist/editorjs.mjs",
|
||||
|
|
|
@ -52,13 +52,24 @@ export default class BlockEvents extends Module {
|
|||
case _.keyCodes.TAB:
|
||||
this.tabPressed(event);
|
||||
break;
|
||||
case _.keyCodes.SLASH:
|
||||
if (event.ctrlKey || event.metaKey) {
|
||||
this.commandSlashPressed();
|
||||
} else {
|
||||
this.slashPressed();
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
/**
|
||||
* We check for "key" here since on different keyboard layouts "/" can be typed as "Shift + 7" etc
|
||||
*
|
||||
* @todo probably using "beforeInput" event would be better here
|
||||
*/
|
||||
if (event.key === '/' && !event.ctrlKey && !event.metaKey) {
|
||||
this.slashPressed();
|
||||
}
|
||||
|
||||
/**
|
||||
* If user pressed "Ctrl + /" or "Cmd + /" — open Block Settings
|
||||
* We check for "code" here since on different keyboard layouts there can be different keys in place of Slash.
|
||||
*/
|
||||
if (event.code === 'Slash' && (event.ctrlKey || event.metaKey)) {
|
||||
event.preventDefault();
|
||||
this.commandSlashPressed();
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -10,6 +10,7 @@ import Toolbox, { ToolboxEvent } from '../../ui/toolbox';
|
|||
import { IconMenu, IconPlus } from '@codexteam/icons';
|
||||
import { BlockHovered } from '../../events/BlockHovered';
|
||||
import { beautifyShortcut } from '../../utils';
|
||||
import { getKeyboardKeyForCode } from '../../utils/keyboard';
|
||||
|
||||
/**
|
||||
* @todo Tab on non-empty block should open Block Settings of the hoveredBlock (not where caret is set)
|
||||
|
@ -352,7 +353,7 @@ export default class Toolbar extends Module<ToolbarNodes> {
|
|||
/**
|
||||
* Draws Toolbar elements
|
||||
*/
|
||||
private make(): void {
|
||||
private async make(): Promise<void> {
|
||||
this.nodes.wrapper = $.make('div', this.CSS.toolbar);
|
||||
/**
|
||||
* @todo detect test environment and add data-cy="toolbar" to use it in tests instead of class name
|
||||
|
@ -414,10 +415,11 @@ export default class Toolbar extends Module<ToolbarNodes> {
|
|||
|
||||
const blockTunesTooltip = $.make('div');
|
||||
const blockTunesTooltipEl = $.text(I18n.ui(I18nInternalNS.ui.blockTunes.toggler, 'Click to tune'));
|
||||
const slashRealKey = await getKeyboardKeyForCode('Slash', '/');
|
||||
|
||||
blockTunesTooltip.appendChild(blockTunesTooltipEl);
|
||||
blockTunesTooltip.appendChild($.make('div', this.CSS.plusButtonShortcut, {
|
||||
textContent: beautifyShortcut('CMD + /'),
|
||||
textContent: beautifyShortcut(`CMD + ${slashRealKey}`),
|
||||
}));
|
||||
|
||||
tooltip.onHover(this.nodes.settingsToggler, blockTunesTooltip, {
|
||||
|
@ -585,7 +587,7 @@ export default class Toolbar extends Module<ToolbarNodes> {
|
|||
/**
|
||||
* Make Toolbar
|
||||
*/
|
||||
this.make();
|
||||
void this.make();
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
54
src/components/utils/keyboard.ts
Normal file
54
src/components/utils/keyboard.ts
Normal file
|
@ -0,0 +1,54 @@
|
|||
declare global {
|
||||
/**
|
||||
* https://developer.mozilla.org/en-US/docs/Web/API/KeyboardLayoutMap
|
||||
*/
|
||||
interface KeyboardLayoutMap {
|
||||
get(key: string): string | undefined;
|
||||
has(key: string): boolean;
|
||||
size: number;
|
||||
entries(): IterableIterator<[string, string]>;
|
||||
keys(): IterableIterator<string>;
|
||||
values(): IterableIterator<string>;
|
||||
forEach(callbackfn: (value: string, key: string, map: KeyboardLayoutMap) => void, thisArg?: unknown): void;
|
||||
}
|
||||
|
||||
/**
|
||||
* The getLayoutMap() method of the Keyboard interface returns a Promise
|
||||
* that resolves with an instance of KeyboardLayoutMap which is a map-like object
|
||||
* with functions for retrieving the strings associated with specific physical keys.
|
||||
* https://developer.mozilla.org/en-US/docs/Web/API/Keyboard/getLayoutMap
|
||||
*/
|
||||
interface Keyboard {
|
||||
getLayoutMap(): Promise<KeyboardLayoutMap>;
|
||||
}
|
||||
|
||||
interface Navigator {
|
||||
/**
|
||||
* Keyboard API. Not supported by Firefox and Safari.
|
||||
*/
|
||||
keyboard?: Keyboard;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns real layout-related keyboard key for a given key code.
|
||||
* For example, for "Slash" it will return "/" on US keyboard and "-" on Spanish keyboard.
|
||||
*
|
||||
* Works with Keyboard API which is not supported by Firefox and Safari. So fallback is used for these browsers.
|
||||
*
|
||||
* @see https://developer.mozilla.org/en-US/docs/Web/API/Keyboard
|
||||
* @param code - {@link https://www.w3.org/TR/uievents-code/#key-alphanumeric-writing-system}
|
||||
* @param fallback - fallback value to be returned if Keyboard API is not supported (Safari, Firefox)
|
||||
*/
|
||||
export async function getKeyboardKeyForCode(code: string, fallback: string): Promise<string> {
|
||||
const keyboard = navigator.keyboard;
|
||||
|
||||
if (!keyboard) {
|
||||
return fallback;
|
||||
}
|
||||
|
||||
const map = await keyboard.getLayoutMap();
|
||||
const key = map.get(code);
|
||||
|
||||
return key || fallback;
|
||||
}
|
|
@ -19,10 +19,37 @@ describe('Slash keydown', function () {
|
|||
.click()
|
||||
.type('/');
|
||||
|
||||
cy.get('[data-cy="toolbox"]')
|
||||
.get('.ce-popover')
|
||||
cy.get('[data-cy="toolbox"] .ce-popover')
|
||||
.should('be.visible');
|
||||
});
|
||||
|
||||
[
|
||||
'ctrl',
|
||||
'cmd',
|
||||
].forEach((key) => {
|
||||
it(`should not open Toolbox if Slash pressed with ${key}`, () => {
|
||||
cy.createEditor({
|
||||
data: {
|
||||
blocks: [
|
||||
{
|
||||
type: 'paragraph',
|
||||
data: {
|
||||
text: '',
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
});
|
||||
|
||||
cy.get('[data-cy=editorjs]')
|
||||
.find('.ce-paragraph')
|
||||
.click()
|
||||
.type(`{${key}}/`);
|
||||
|
||||
cy.get('[data-cy="toolbox"] .ce-popover')
|
||||
.should('not.be.visible');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('pressed in non-empty block', function () {
|
||||
|
@ -45,8 +72,7 @@ describe('Slash keydown', function () {
|
|||
.click()
|
||||
.type('/');
|
||||
|
||||
cy.get('[data-cy="toolbox"]')
|
||||
.get('.ce-popover')
|
||||
cy.get('[data-cy="toolbox"] .ce-popover')
|
||||
.should('not.be.visible');
|
||||
|
||||
/**
|
||||
|
@ -80,8 +106,7 @@ describe('CMD+Slash keydown', function () {
|
|||
.click()
|
||||
.type('{cmd}/');
|
||||
|
||||
cy.get('[data-cy="block-tunes"]')
|
||||
.get('.ce-popover')
|
||||
cy.get('[data-cy="block-tunes"] .ce-popover')
|
||||
.should('be.visible');
|
||||
});
|
||||
});
|
||||
|
|
|
@ -38,7 +38,6 @@ class SomePlugin {
|
|||
|
||||
describe('Flipper', () => {
|
||||
it('should prevent plugins event handlers from being called while keyboard navigation', () => {
|
||||
const SLASH_KEY_CODE = 191;
|
||||
const ARROW_DOWN_KEY_CODE = 40;
|
||||
const ENTER_KEY_CODE = 13;
|
||||
|
||||
|
@ -72,7 +71,7 @@ describe('Flipper', () => {
|
|||
cy.get('[data-cy=editorjs]')
|
||||
.get('.cdx-some-plugin')
|
||||
// Open tunes menu
|
||||
.trigger('keydown', { keyCode: SLASH_KEY_CODE, ctrlKey: true })
|
||||
.trigger('keydown', { code: 'Slash', ctrlKey: true })
|
||||
// Navigate to delete button (the second button)
|
||||
.trigger('keydown', { keyCode: ARROW_DOWN_KEY_CODE })
|
||||
.trigger('keydown', { keyCode: ARROW_DOWN_KEY_CODE });
|
||||
|
|
Loading…
Reference in a new issue