From a0642e497d7530cc4d6440d2b094317b94cca214 Mon Sep 17 00:00:00 2001 From: Erik Lopez Date: Mon, 2 Dec 2019 15:49:49 +0900 Subject: [PATCH] Implement key bindings feature * Add draft-js Editor properties * Fix a bug when refocusing the editor on custom click actions --- README.md | 23 +++++++++++++++- examples/key-bindings/index.tsx | 36 ++++++++++++++++++++++++ examples/main.tsx | 2 ++ package.json | 2 +- src/MUIRichTextEditor.tsx | 49 +++++++++++++++++++++++++++++---- 5 files changed, 105 insertions(+), 7 deletions(-) create mode 100644 examples/key-bindings/index.tsx diff --git a/README.md b/README.md index 4ace498..6f68fd5 100644 --- a/README.md +++ b/README.md @@ -238,6 +238,8 @@ Object.assign(defaultTheme, { |toolbar|`boolean`|optional|Defines if the main toolbar should be rendered.| |inlineToolbar|`boolean`|optional|Defines if the inline toolbar should be rendered.| |inlineToolbarControls|`string[]`|optional|List of controls to display in the inline toolbar. Available values are: "bold", "italic", "underline", "strikethrough", "highlight", "link", "clear", and user defined inline controls. If not provided and `inlineToolbar` is `true` the following inline styles will be displayed: bold, italic, underline and clear.| +|keyCommands|`TKeyCommand[]`|optional|Defines an array of `TKeyCommand` objects for adding key bindings to the editor.| +|draftEditorProps|`TDraftEditorProps`|optional|Defines an object containing specific `draft-js` `Editor` properties.|
@@ -274,7 +276,26 @@ Object.assign(defaultTheme, { |Property|Type||description| |---|---|---|---| |component|`React.FunctionComponent`|required|The React component to use for rendering the decorator.| -|regex|`RegExp`|required|The regular expression to match a decorator.| +|regex|`RegExp`|required|The regular expression to match a decorator.| + +
+ +`TKeyCommand` + +|Property|Type||description| +|---|---|---|---| +|key|`number`|required|The code of the key to bind.| +|name|`string`|required|The name of the command.| +|callback|`(state: EditorState) => EditorState`|required|The callback function to execute when the key binding is matched. It should return the `EditorState` to set.| + +
+ +`TDraftEditorProps` + +|Property|Type||description| +|---|---|---|---| +|spellCheck|`boolean`|optional|Use browser spelling check.| +|stripPastedStyles|`boolean`|optional|Remove styles when pasting text into the editor.|
diff --git a/examples/key-bindings/index.tsx b/examples/key-bindings/index.tsx new file mode 100644 index 0000000..6416be3 --- /dev/null +++ b/examples/key-bindings/index.tsx @@ -0,0 +1,36 @@ +import React from 'react' +import { EditorState, RichUtils } from 'draft-js' +import MUIRichTextEditor from '../../' + +const save = (data: string) => { + console.log(data) +} + +const KeyBindings = () => { + return ( + { + return EditorState.createEmpty() + } + }, + { + key: 75, // K + name: "toggle-italic", + callback: (editorState: EditorState) => { + const newState = RichUtils.toggleInlineStyle(editorState, "ITALIC") + return newState + } + } + ]} + /> + ) +} + +export default KeyBindings \ No newline at end of file diff --git a/examples/main.tsx b/examples/main.tsx index 4cb5154..b3b7f56 100644 --- a/examples/main.tsx +++ b/examples/main.tsx @@ -12,6 +12,7 @@ import CustomInlineToolbar from './custom-inline-toolbar' import LoadHTML from './load-html' import ResetValue from './reset-value' import AtomicCustomBlock from './atomic-custom-block' +import KeyBindings from './key-bindings' const App = () => { @@ -36,6 +37,7 @@ const App = () => { +
diff --git a/package.json b/package.json index 357bbed..442da3d 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "mui-rte", - "version": "1.9.3", + "version": "1.10.0", "description": "Material-UI Rich Text Editor and Viewer", "keywords": [ "material-ui", diff --git a/src/MUIRichTextEditor.tsx b/src/MUIRichTextEditor.tsx index 7667392..f535dd8 100644 --- a/src/MUIRichTextEditor.tsx +++ b/src/MUIRichTextEditor.tsx @@ -8,7 +8,7 @@ import { Editor, EditorState, convertFromRaw, RichUtils, AtomicBlockUtils, CompositeDecorator, convertToRaw, DefaultDraftBlockRenderMap, DraftEditorCommand, DraftHandleValue, DraftStyleMap, ContentBlock, DraftDecorator, getVisibleSelectionRect, - SelectionState + SelectionState, KeyBindingUtil, getDefaultKeyBinding } from 'draft-js' import Toolbar, { TToolbarControl, TCustomControl } from './components/Toolbar' import Link from './components/Link' @@ -77,6 +77,17 @@ export type TDecorator = { regex: RegExp } +type TDraftEditorProps = { + spellCheck?: boolean + stripPastedStyles?: boolean +} + +type TKeyCommand = { + key: number + name: string + callback: (state: EditorState) => EditorState +} + interface IMUIRichTextEditorProps extends WithStyles { id?: string value?: any @@ -85,13 +96,15 @@ interface IMUIRichTextEditorProps extends WithStyles { inheritFontSize?: boolean error?: boolean controls?: Array - onSave?: (data: string) => void - onChange?: (state: EditorState) => void - customControls?: TCustomControl[], + customControls?: TCustomControl[] decorators?: TDecorator[] toolbar?: boolean inlineToolbar?: boolean inlineToolbarControls?: Array + draftEditorProps?: TDraftEditorProps + keyCommands?: TKeyCommand[] + onSave?: (data: string) => void + onChange?: (state: EditorState) => void } type IMUIRichTextEditorState = { @@ -136,6 +149,8 @@ const styleRenderMap: DraftStyleMap = { } } +const { hasCommandModifier } = KeyBindingUtil + const findLinkEntities = (contentBlock: any, callback: any, contentState: any) => { contentBlock.findEntityRanges( (character: any) => { @@ -351,6 +366,16 @@ const MUIRichTextEditor: RefForwardingComponent = handleChange(newState) return "handled" } + else { + if (props.keyCommands) { + const keyCommand = props.keyCommands.find(comm => comm.name === command) + if (keyCommand) { + const newState = keyCommand.callback(editorState) + handleChange(newState) + return "handled" + } + } + } return "not-handled" } @@ -372,7 +397,9 @@ const MUIRichTextEditor: RefForwardingComponent = } } else { - refocus() + if (!editorState.getSelection().isCollapsed()) { + refocus() + } } } break @@ -643,6 +670,16 @@ const MUIRichTextEditor: RefForwardingComponent = return AtomicBlockUtils.insertAtomicBlock(newEditorStateRaw, entityKey, ' ') } + const keyBindingFn = (e: React.KeyboardEvent<{}>): string | null => { + if (hasCommandModifier(e) && props.keyCommands) { + const comm = props.keyCommands.find(comm => comm.key === e.keyCode) + if (comm) { + return comm.name + } + } + return getDefaultKeyBinding(e) + } + const renderToolbar = props.toolbar === undefined || props.toolbar const inlineToolbarControls = props.inlineToolbarControls || ["bold", "italic", "underline", "clear"] const editable = props.readOnly === undefined || !props.readOnly @@ -711,7 +748,9 @@ const MUIRichTextEditor: RefForwardingComponent = onChange={handleChange} readOnly={props.readOnly} handleKeyCommand={handleKeyCommand} + keyBindingFn={keyBindingFn} ref={editorRef} + {...props.draftEditorProps} />