Skip to content

Commit

Permalink
Merge pull request #64 from niuware/development
Browse files Browse the repository at this point in the history
Implement key bindings feature
  • Loading branch information
niuware authored Dec 2, 2019
2 parents 03822f5 + a0642e4 commit 00f2e65
Show file tree
Hide file tree
Showing 5 changed files with 105 additions and 7 deletions.
23 changes: 22 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.|


<br />
Expand Down Expand Up @@ -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.|

<br />

`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.|

<br />

`TDraftEditorProps`

|Property|Type||description|
|---|---|---|---|
|spellCheck|`boolean`|optional|Use browser spelling check.|
|stripPastedStyles|`boolean`|optional|Remove styles when pasting text into the editor.|

<br />

Expand Down
36 changes: 36 additions & 0 deletions examples/key-bindings/index.tsx
Original file line number Diff line number Diff line change
@@ -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 (
<MUIRichTextEditor
label="Press CMD + C to clear the editor or CMD + K to add 'italic' style to the selection..."
onSave={save}
controls={["title", "italic", "save"]}
keyCommands={[
{
key: 67, // C
name: "clear-all",
callback: (_) => {
return EditorState.createEmpty()
}
},
{
key: 75, // K
name: "toggle-italic",
callback: (editorState: EditorState) => {
const newState = RichUtils.toggleInlineStyle(editorState, "ITALIC")
return newState
}
}
]}
/>
)
}

export default KeyBindings
2 changes: 2 additions & 0 deletions examples/main.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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 = () => {

Expand All @@ -36,6 +37,7 @@ const App = () => {
<button onClick={() => setSample(<Events />)}>Events</button>
<button onClick={() => setSample(<LoadHTML />)}>Load from HTML</button>
<button onClick={() => setSample(<ResetValue />)}>Reset value</button>
<button onClick={() => setSample(<KeyBindings />)}>Key Bindings</button>
<div style={{
margin: "20px 0"
}}>
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -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",
Expand Down
49 changes: 44 additions & 5 deletions src/MUIRichTextEditor.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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'
Expand Down Expand Up @@ -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<typeof styles> {
id?: string
value?: any
Expand All @@ -85,13 +96,15 @@ interface IMUIRichTextEditorProps extends WithStyles<typeof styles> {
inheritFontSize?: boolean
error?: boolean
controls?: Array<TToolbarControl>
onSave?: (data: string) => void
onChange?: (state: EditorState) => void
customControls?: TCustomControl[],
customControls?: TCustomControl[]
decorators?: TDecorator[]
toolbar?: boolean
inlineToolbar?: boolean
inlineToolbarControls?: Array<TToolbarControl>
draftEditorProps?: TDraftEditorProps
keyCommands?: TKeyCommand[]
onSave?: (data: string) => void
onChange?: (state: EditorState) => void
}

type IMUIRichTextEditorState = {
Expand Down Expand Up @@ -136,6 +149,8 @@ const styleRenderMap: DraftStyleMap = {
}
}

const { hasCommandModifier } = KeyBindingUtil

const findLinkEntities = (contentBlock: any, callback: any, contentState: any) => {
contentBlock.findEntityRanges(
(character: any) => {
Expand Down Expand Up @@ -351,6 +366,16 @@ const MUIRichTextEditor: RefForwardingComponent<any, IMUIRichTextEditorProps> =
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"
}

Expand All @@ -372,7 +397,9 @@ const MUIRichTextEditor: RefForwardingComponent<any, IMUIRichTextEditorProps> =
}
}
else {
refocus()
if (!editorState.getSelection().isCollapsed()) {
refocus()
}
}
}
break
Expand Down Expand Up @@ -643,6 +670,16 @@ const MUIRichTextEditor: RefForwardingComponent<any, IMUIRichTextEditorProps> =
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
Expand Down Expand Up @@ -711,7 +748,9 @@ const MUIRichTextEditor: RefForwardingComponent<any, IMUIRichTextEditorProps> =
onChange={handleChange}
readOnly={props.readOnly}
handleKeyCommand={handleKeyCommand}
keyBindingFn={keyBindingFn}
ref={editorRef}
{...props.draftEditorProps}
/>
</div>
</div>
Expand Down

0 comments on commit 00f2e65

Please sign in to comment.