Skip to content

Commit

Permalink
Merge pull request #37 from niuware/development
Browse files Browse the repository at this point in the history
Enhance image/link input and edit
  • Loading branch information
niuware authored Oct 15, 2019
2 parents 4763761 + cfc4019 commit cd9be87
Show file tree
Hide file tree
Showing 7 changed files with 170 additions and 80 deletions.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ The Material-UI Rich Text Editor and Viewer

<img src="http://niuware.github.io/public/assets/mui-rte/editor-w-controls-1-2-0.png" width="600" />

**mui-rte** is a complete text editor and viewer for `material-ui` v3 and v4 based on `draft-js` and written in Typescript. It is ready to use out of the box yet supports user defined block, style, callback, and decorators definitions as well as toolbars and style customization to enhance the editor to all needs.
**mui-rte** is a complete text editor and viewer for `material-ui` v3 and v4 based on `draft-js` and written in Typescript. It is ready to use out of the box yet supports user defined block, style, callback, and decorator definitions as well as toolbar and theme customization to enhance the editor to all needs.

## Installation

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.5.2",
"version": "1.6.0",
"description": "Material-UI Rich Text Editor and Viewer",
"keywords": [
"material-ui",
Expand Down
83 changes: 72 additions & 11 deletions src/MUIRichTextEditor.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,8 @@ import { Paper } from '@material-ui/core'
import {
Editor, EditorState, convertFromRaw, RichUtils, AtomicBlockUtils,
CompositeDecorator, convertToRaw, DefaultDraftBlockRenderMap, DraftEditorCommand,
DraftHandleValue, DraftStyleMap, ContentBlock, DraftDecorator, getVisibleSelectionRect
DraftHandleValue, DraftStyleMap, ContentBlock, DraftDecorator, getVisibleSelectionRect,
SelectionState, Modifier, ContentState
} from 'draft-js'
import EditorControls, { TEditorControl, TCustomControl } from './components/EditorControls'
import Link from './components/Link'
Expand Down Expand Up @@ -145,6 +146,7 @@ const MUIRichTextEditor: RefForwardingComponent<any, IMUIRichTextEditorProps> =
style: undefined,
block: undefined
})
const [focusImageKey, setFocusImageKey] = useState("")

const editorRef = useRef(null)
const selectionRef = useRef<TStateOffset>({
Expand Down Expand Up @@ -202,11 +204,9 @@ const MUIRichTextEditor: RefForwardingComponent<any, IMUIRichTextEditorProps> =
block: DefaultDraftBlockRenderMap.merge(blockRenderMap, Immutable.Map(customBlockMap))
})
setEditorState(editorState)
const editor: HTMLElement = (editorRef.current as any).editor
editor.addEventListener("mouseup", handleSetToolbarPosition)
toggleMouseUpListener(true)
return () => {
const editor: HTMLElement = (editorRef.current as any).editor
editor.removeEventListener("mouseup", handleSetToolbarPosition)
toggleMouseUpListener()
}
}, [props.value])

Expand All @@ -219,12 +219,20 @@ const MUIRichTextEditor: RefForwardingComponent<any, IMUIRichTextEditorProps> =
toolbarPositionRef.current = state.toolbarPosition
}, [state.toolbarPosition])

const handleSetToolbarPosition = () => {
const handleMouseUp = (event: any) => {
if (event.target.nodeName === "IMG"){
return
}
setTimeout(() => {
const selection = (editorStateRef.current as any).getSelection()
const selection = editorStateRef.current!.getSelection()
if (selection.isCollapsed() || (toolbarPositionRef !== undefined &&
selectionRef.current.start === selection.getStartOffset() &&
selectionRef.current.end === selection.getEndOffset())) {
const selectionInfo = getSelectionInfo(editorStateRef.current!)
if (selectionInfo.entityType === "IMAGE") {
focusImage(selectionInfo.block)
return
}
setState({
...state,
toolbarPosition: undefined
Expand Down Expand Up @@ -352,13 +360,14 @@ const MUIRichTextEditor: RefForwardingComponent<any, IMUIRichTextEditorProps> =
}
}

const handlePromptForMedia = (style: string, toolbarMode: boolean) => {
const handlePromptForMedia = (style: string, toolbarMode: boolean, newState?: EditorState) => {
const lastState = newState || editorState
let url = ''
let width = undefined
let height = undefined
let urlKey = undefined
const selectionInfo = getSelectionInfo(editorState)
const contentState = editorState.getCurrentContent()
const selectionInfo = getSelectionInfo(lastState)
const contentState = lastState.getCurrentContent()
const linkKey = selectionInfo.linkKey

if (linkKey) {
Expand All @@ -380,6 +389,17 @@ const MUIRichTextEditor: RefForwardingComponent<any, IMUIRichTextEditorProps> =
})
}

const toggleMouseUpListener = (addAfter = false) => {
const editor: HTMLElement = (editorRef.current as any).editor
if (!editor) {
return
}
editor.removeEventListener("mouseup", handleMouseUp)
if (addAfter) {
editor.addEventListener("mouseup", handleMouseUp)
}
}

const removeLink = () => {
const selection = editorState.getSelection()
updateStateForPopover(RichUtils.toggleLink(editorState, selection, null))
Expand Down Expand Up @@ -427,9 +447,35 @@ const MUIRichTextEditor: RefForwardingComponent<any, IMUIRichTextEditorProps> =
updateStateForPopover(replaceEditorState)
}

const removeMedia = () => {
const blockKey = editorState.getSelection().getStartKey()
const contentState = editorState.getCurrentContent()
const mediaBlock = contentState.getBlockForKey(blockKey)
const removeBlockContentState = Modifier.removeRange(
contentState,
new SelectionState({
anchorKey: mediaBlock.getKey(),
anchorOffset: 0,
focusKey: mediaBlock.getKey(),
focusOffset: mediaBlock.getLength(),
}),
'backward'
)
const blockMap = removeBlockContentState.getBlockMap().delete(mediaBlock.getKey())
var withoutAtomic = removeBlockContentState.merge({
blockMap,
selectionAfter: contentState.getSelectionAfter()
})
const newEditorState = EditorState.push(editorState, withoutAtomic as ContentState, "remove-range")
setEditorState(newEditorState)
}

const confirmMedia = (url?: string, width?: number, height?: number) => {
const { urlKey } = state
if (!url) {
if (urlKey) {
removeMedia()
}
setState({
...state,
anchorUrlPopover: undefined
Expand Down Expand Up @@ -466,6 +512,7 @@ const MUIRichTextEditor: RefForwardingComponent<any, IMUIRichTextEditorProps> =
entityKey, ' ')
replaceEditorState = EditorState.forceSelection(newEditorState, newEditorState.getCurrentContent().getSelectionAfter())
}
setFocusImageKey("")
updateStateForPopover(replaceEditorState)
}

Expand Down Expand Up @@ -506,6 +553,15 @@ const MUIRichTextEditor: RefForwardingComponent<any, IMUIRichTextEditorProps> =
)
}

const focusImage = (block: ContentBlock) => {
const newSeletion = SelectionState.createEmpty(block.getKey())
const newEditorState = EditorState.forceSelection(editorStateRef.current!, newSeletion)
editorStateRef.current = newEditorState
setFocusImageKey(block.getKey())
setEditorState(newEditorState)
handlePromptForMedia("", false, newEditorState)
}

const blockRenderer = (contentBlock: ContentBlock) => {
const blockType = contentBlock.getType()
if (blockType === 'atomic') {
Expand All @@ -516,7 +572,12 @@ const MUIRichTextEditor: RefForwardingComponent<any, IMUIRichTextEditorProps> =
if (type === 'IMAGE') {
return {
component: Image,
editable: false
editable: false,
props: {
onClick: focusImage,
readOnly: props.readOnly,
focusKey: focusImageKey
}
}
}
}
Expand Down
5 changes: 4 additions & 1 deletion src/components/EditorControls.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -239,7 +239,10 @@ const EditorControls: FunctionComponent<IBlockStyleControlsProps> = (props) => {
}
else if (style.type === "block") {
const selection = editorState.getSelection()
active = style.style === editorState.getCurrentContent().getBlockForKey(selection.getStartKey()).getType()
const block = editorState.getCurrentContent().getBlockForKey(selection.getStartKey())
if (block) {
active = style.style === block.getType()
}
action = props.onToggleBlock
}
else {
Expand Down
35 changes: 28 additions & 7 deletions src/components/Image.tsx
Original file line number Diff line number Diff line change
@@ -1,26 +1,47 @@
import * as React from 'react'
import React, { FunctionComponent } from 'react'
import classNames from 'classnames'
import { ContentState, ContentBlock } from 'draft-js'
import { createStyles, withStyles, WithStyles, Theme } from '@material-ui/core/styles'

import { createStyles, withStyles, WithStyles } from '@material-ui/core/styles'

const styles = () => createStyles({
const styles = ({ shadows }: Theme) => createStyles({
root: {
},
editable: {
cursor: "pointer",
"&:hover": {
boxShadow: shadows[3]
}
},
focused: {
boxShadow: shadows[3]
}
})

interface IImageProps extends WithStyles<typeof styles> {
block: ContentBlock
contentState: ContentState
blockProps: any
onClick: (block: ContentBlock) => void
}

const Image: React.FC<IImageProps> = (props: IImageProps) => {
const Image: FunctionComponent<IImageProps> = (props) => {
const { url, width, height } = props.contentState.getEntity(props.block.getEntityAt(0)).getData()
const { onClick, readOnly, focusKey } = props.blockProps
return (
<img
src={url}
className={props.classes.root}
className={classNames(props.classes.root, {
[props.classes.editable]: !readOnly,
[props.classes.focused]: !readOnly && focusKey === props.block.getKey()
})}
width={width}
height={height}
height={height}
onClick={() => {
if (readOnly) {
return
}
onClick(props.block)
}}
/>
)
}
Expand Down
Loading

0 comments on commit cd9be87

Please sign in to comment.