diff --git a/CHANGELOG.md b/CHANGELOG.md index e35fcb641..53cd1084a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,10 @@ > Documentation: [draftail.org/docs/next/getting-started](https://www.draftail.org/docs/next/getting-started) +### Fixed + +- Fix plugin `keyBindingFn`s not being called. [#246](https://github.com/springload/draftail/pull/246) + ## [[v1.4.0]](https://github.com/springload/draftail/releases/tag/v1.4.0) > Documentation: [draftail.org/docs/getting-started](https://www.draftail.org/docs/getting-started) diff --git a/examples/plugins/sectionBreakPlugin.js b/examples/plugins/sectionBreakPlugin.js index 70753e959..561db1688 100644 --- a/examples/plugins/sectionBreakPlugin.js +++ b/examples/plugins/sectionBreakPlugin.js @@ -1,13 +1,24 @@ // @flow import React from "react"; import type { Component, Node } from "react"; -import { ContentBlock, EditorState, Modifier } from "draft-js"; +import { ContentBlock, EditorState, KeyBindingUtil, Modifier } from "draft-js"; import { ToolbarButton } from "../../lib"; +type PluginFunctions = { + setEditorState: (EditorState) => void, +}; + const BREAK_ICON = "M0 16h4v2h-4zM6 16h6v2h-6zM14 16h4v2h-4zM20 16h6v2h-6zM28 16h4v2h-4zM27.5 0l0.5 14h-24l0.5-14h1l0.5 12h20l0.5-12zM4.5 32l-0.5-12h24l-0.5 12h-1l-0.5-10h-20l-0.5 10zM0 512h128v64H0v-64zm192 0h192v64H192v-64zm256 0h128v64H448v-64zm192 0h192v64H640v-64zm256 0h128v64H896v-64zM880 0l16 448H128L144 0h32l16 384h640L848 0h32zM144 1024l-16-384h768l-16 384h-32l-16-320H192l-16 320h-32z"; +const { isOptionKeyCommand } = KeyBindingUtil; +// Copied from behavior.js. +// Hack relying on the internals of Draft.js. +// See https://github.com/facebook/draft-js/pull/869 +// $FlowFixMe +const IS_MAC_OS = isOptionKeyCommand({ altKey: "test" }) === "test"; + const insertSectionBreak = (editorState: EditorState) => { const content = editorState.getCurrentContent(); @@ -35,7 +46,7 @@ export const SectionBreakControl = ({ getEditorState, onChange }: Props) => ( { onChange(insertSectionBreak(getEditorState())); }} @@ -70,6 +81,24 @@ const sectionBreakPlugin = (config: {| return null; }, + keyBindingFn(e: SyntheticKeyboardEvent<>) { + const KeyS = 83; + if ((e.metaKey || e.ctrlKey) && e.altKey && e.keyCode === KeyS) { + return "section-break"; + } + return undefined; + }, + handleKeyCommand( + command: string, + editorState: EditorState, + { setEditorState }: PluginFunctions, + ) { + if (command === "section-break") { + setEditorState(insertSectionBreak(editorState)); + return "handled"; + } + return "not-handled"; + }, }; }; diff --git a/lib/components/DraftailEditor.js b/lib/components/DraftailEditor.js index 8ebbe3f78..ed0763bc9 100644 --- a/lib/components/DraftailEditor.js +++ b/lib/components/DraftailEditor.js @@ -971,11 +971,7 @@ class DraftailEditor extends Component { autoCorrect={autoCorrect} ariaDescribedBy={ariaDescribedBy} handleReturn={this.handleReturn} - keyBindingFn={behavior.getKeyBindingFn( - blockTypes, - inlineStyles, - entityTypes, - )} + defaultKeyBindings={false} handleKeyCommand={this.handleKeyCommand} handleBeforeInput={this.handleBeforeInput} handlePastedText={this.handlePastedText} @@ -985,7 +981,19 @@ class DraftailEditor extends Component { blockRendererFn={this.blockRenderer} blockRenderMap={behavior.getBlockRenderMap(blockTypes)} blockStyleFn={behavior.blockStyleFn} - plugins={plugins} + // Include the keyBindingFn in a plugin here so that + // other plugin keyBindingFn's are still called, while + // still being able to override the Draft.js oversensitive + // keyboard shortcuts. + plugins={plugins.concat([ + { + keyBindingFn: behavior.getKeyBindingFn( + blockTypes, + inlineStyles, + entityTypes, + ), + }, + ])} // $FlowFixMe decorators={decorators.concat(entityDecorators)} /> diff --git a/lib/components/DraftailEditor.test.js b/lib/components/DraftailEditor.test.js index 309c2c3f9..20a87da02 100644 --- a/lib/components/DraftailEditor.test.js +++ b/lib/components/DraftailEditor.test.js @@ -1502,8 +1502,31 @@ describe("DraftailEditor", () => { expect( shallowNoLifecycle() .find(Editor) - .prop("plugins"), + .prop("plugins") + .slice(0, -1), ).toEqual([{ test: true }]); }); + + it("contains keyBindingFn for default behaviour", () => { + const getBinding = jest.spyOn(behavior, "getKeyBindingFn"); + const plugins = shallowNoLifecycle() + .find(Editor) + .prop("plugins"); + expect(getBinding).toHaveBeenCalled(); + const keyBindingFn = getBinding.mock.results[0].value; + expect(plugins).toEqual([{ keyBindingFn }]); + }); + + it("passes plugin keyBindingFns to Draft-JS", () => { + const pluginBinding = jest.fn(); + const keyBindingFn = shallowNoLifecycle( + , + ) + .find(Editor) + .dive() + .prop("keyBindingFn"); + keyBindingFn(new KeyboardEvent("keydown", { key: "e" })); + expect(pluginBinding).toHaveBeenCalled(); + }); }); }); diff --git a/lib/components/__snapshots__/DraftailEditor.test.js.snap b/lib/components/__snapshots__/DraftailEditor.test.js.snap index f9d998dd6..475778cab 100644 --- a/lib/components/__snapshots__/DraftailEditor.test.js.snap +++ b/lib/components/__snapshots__/DraftailEditor.test.js.snap @@ -87,7 +87,7 @@ exports[`DraftailEditor #maxListNesting 1`] = ` customStyleMap={Object {}} decorators={Array []} defaultBlockRenderMap={true} - defaultKeyBindings={true} + defaultKeyBindings={false} defaultKeyCommands={true} editorState={ EditorState { @@ -176,13 +176,18 @@ exports[`DraftailEditor #maxListNesting 1`] = ` handleKeyCommand={[Function]} handlePastedText={[Function]} handleReturn={[Function]} - keyBindingFn={[Function]} onBlur={[Function]} onChange={[Function]} onFocus={[Function]} onTab={[Function]} placeholder={null} - plugins={Array []} + plugins={ + Array [ + Object { + "keyBindingFn": [Function], + }, + ] + } readOnly={false} spellCheck={false} stripPastedStyles={true} @@ -282,7 +287,7 @@ exports[`DraftailEditor empty 1`] = ` customStyleMap={Object {}} decorators={Array []} defaultBlockRenderMap={true} - defaultKeyBindings={true} + defaultKeyBindings={false} defaultKeyCommands={true} editorState={ EditorState { @@ -371,13 +376,18 @@ exports[`DraftailEditor empty 1`] = ` handleKeyCommand={[Function]} handlePastedText={[Function]} handleReturn={[Function]} - keyBindingFn={[Function]} onBlur={[Function]} onChange={[Function]} onFocus={[Function]} onTab={[Function]} placeholder={null} - plugins={Array []} + plugins={ + Array [ + Object { + "keyBindingFn": [Function], + }, + ] + } readOnly={false} spellCheck={false} stripPastedStyles={true}