react-draft-wysiwyg
Version:
A wysiwyg on top of DraftJS.
191 lines (168 loc) • 5.66 kB
JavaScript
/* @flow */
import React, { Component } from 'react';
import PropTypes from 'prop-types';
import { RichUtils, EditorState, Modifier } from 'draft-js';
import {
getSelectionText,
getEntityRange,
getSelectionEntity,
} from 'draftjs-utils';
import linkifyIt from 'linkify-it';
import LayoutComponent from './Component';
const linkify = linkifyIt();
class Link extends Component {
static propTypes = {
editorState: PropTypes.object.isRequired,
onChange: PropTypes.func.isRequired,
modalHandler: PropTypes.object,
config: PropTypes.object,
translations: PropTypes.object,
};
state: Object = {
expanded: false,
link: undefined,
selectionText: undefined,
};
componentWillMount(): void {
const { editorState, modalHandler } = this.props;
if (editorState) {
this.setState({
currentEntity: getSelectionEntity(editorState),
});
}
modalHandler.registerCallBack(this.expandCollapse);
}
componentWillReceiveProps(properties: Object): void {
const newState = {};
if (properties.editorState &&
this.props.editorState !== properties.editorState) {
newState.currentEntity = getSelectionEntity(properties.editorState);
}
this.setState(newState);
}
componentWillUnmount(): void {
const { modalHandler } = this.props;
modalHandler.deregisterCallBack(this.expandCollapse);
}
onExpandEvent: Function = (): void => {
this.signalExpanded = !this.state.expanded;
};
onChange = (action, title, target, targetOption) => {
if (action === 'link') {
const links = linkify.match(target);
const linkifiedTarget = links && links[0] ? links[0].url : '';
this.addLink(title, linkifiedTarget, targetOption);
} else {
this.removeLink();
}
}
getCurrentValues = () => {
const { editorState } = this.props;
const { currentEntity } = this.state;
const contentState = editorState.getCurrentContent();
const currentValues = {};
if (currentEntity && (contentState.getEntity(currentEntity).get('type') === 'LINK')) {
currentValues.link = {};
const entityRange = currentEntity && getEntityRange(editorState, currentEntity);
currentValues.link.target = currentEntity && contentState.getEntity(currentEntity).get('data').url;
currentValues.link.targetOption = currentEntity && contentState.getEntity(currentEntity).get('data').targetOption;
currentValues.link.title = (entityRange && entityRange.text);
}
currentValues.selectionText = getSelectionText(editorState);
return currentValues;
}
doExpand: Function = (): void => {
this.setState({
expanded: true,
});
};
expandCollapse: Function = (): void => {
this.setState({
expanded: this.signalExpanded,
});
this.signalExpanded = false;
}
doCollapse: Function = (): void => {
this.setState({
expanded: false,
});
};
removeLink: Function = (): void => {
const { editorState, onChange } = this.props;
const { currentEntity } = this.state;
let selection = editorState.getSelection();
if (currentEntity) {
const entityRange = getEntityRange(editorState, currentEntity);
selection = selection.merge({
anchorOffset: entityRange.start,
focusOffset: entityRange.end,
});
onChange(RichUtils.toggleLink(editorState, selection, null));
}
};
addLink: Function = (linkTitle, linkTarget, linkTargetOption): void => {
const { editorState, onChange } = this.props;
const { currentEntity } = this.state;
let selection = editorState.getSelection();
if (currentEntity) {
const entityRange = getEntityRange(editorState, currentEntity);
selection = selection.merge({
anchorOffset: entityRange.start,
focusOffset: entityRange.end,
});
}
const entityKey = editorState
.getCurrentContent()
.createEntity('LINK', 'MUTABLE', { url: linkTarget, targetOption: linkTargetOption })
.getLastCreatedEntityKey();
let contentState = Modifier.replaceText(
editorState.getCurrentContent(),
selection,
`${linkTitle}`,
editorState.getCurrentInlineStyle(),
entityKey,
);
let newEditorState = EditorState.push(editorState, contentState, 'insert-characters');
// insert a blank space after link
selection = newEditorState.getSelection().merge({
anchorOffset: selection.get('anchorOffset') + linkTitle.length,
focusOffset: selection.get('anchorOffset') + linkTitle.length,
});
newEditorState = EditorState.acceptSelection(newEditorState, selection);
contentState = Modifier.insertText(
newEditorState.getCurrentContent(),
selection,
' ',
newEditorState.getCurrentInlineStyle(),
undefined,
);
onChange(EditorState.push(newEditorState, contentState, 'insert-characters'));
this.doCollapse();
};
render(): Object {
const { config, translations } = this.props;
const { expanded } = this.state;
const { link, selectionText } = this.getCurrentValues();
const LinkComponent = config.component || LayoutComponent;
return (
<LinkComponent
config={config}
translations={translations}
expanded={expanded}
onExpandEvent={this.onExpandEvent}
doExpand={this.doExpand}
doCollapse={this.doCollapse}
currentState={{
link,
selectionText,
}}
onChange={this.onChange}
/>
);
}
}
export default Link;
// todo refct
// 1. better action names here
// 2. align update signatue
// 3. align current value signature