@kedao/editor
Version:
Rich Text Editor Based On Draft.js
376 lines • 18.1 kB
JavaScript
var __rest = (this && this.__rest) || function (s, e) {
var t = {};
for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0)
t[p] = s[p];
if (s != null && typeof Object.getOwnPropertySymbols === "function")
for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) {
if (e.indexOf(p[i]) < 0 && Object.prototype.propertyIsEnumerable.call(s, p[i]))
t[p[i]] = s[p[i]];
}
return t;
};
import React from 'react';
import Finder from '@kedao/finder';
import { ColorUtils, ContentUtils } from '@kedao/utils';
import { Editor, EditorState } from 'draft-js';
import { Map } from 'immutable';
import mergeClassNames from 'merge-class-names';
import languages from '../languages';
import getKeyBindingFn from '../configs/keybindings';
import defaultProps from '../configs/props';
import { keyCommandHandlers, returnHandlers, beforeInputHandlers, dropHandlers, droppedFilesHandlers, copyHandlers, pastedFilesHandlers, pastedTextHandlers, compositionStartHandler } from '../configs/handlers';
import { getBlockRendererFn, getBlockRenderMap, getBlockStyleFn, getCustomStyleMap, getCustomStyleFn, getDecorators } from '../renderers';
import { compositeStyleImportFn, compositeStyleExportFn, compositeEntityImportFn, compositeEntityExportFn, compositeBlockImportFn, compositeBlockExportFn, getPropInterceptors } from '../helpers/extension';
import ControlBar from '../components/business/ControlBar';
import 'draft-js/dist/Draft.css';
import 'assets/scss/_base.scss';
const buildHooks = (hooks) => (hookName, defaultReturns = {}) => {
return hooks[hookName] || (() => defaultReturns);
};
const filterColors = (colors, colors2) => {
return colors
.filter((item) => {
return !colors2.find((color) => color.toLowerCase() === item.toLowerCase());
})
.filter((item, index, array) => array.indexOf(item) === index);
};
const isControlEnabled = (props, controlName) => {
return ([...props.controls, ...props.extendControls].find((item) => item === controlName || item.key === controlName) && props.excludeControls.indexOf(controlName) === -1);
};
const getConvertOptions = (props) => {
const editorId = props.editorId || props.id;
const convertOptions = Object.assign(Object.assign(Object.assign({}, defaultProps.converts), props.converts), { fontFamilies: props.fontFamilies });
convertOptions.styleImportFn = compositeStyleImportFn(convertOptions.styleImportFn, editorId);
convertOptions.styleExportFn = compositeStyleExportFn(convertOptions.styleExportFn, editorId);
convertOptions.entityImportFn = compositeEntityImportFn(convertOptions.entityImportFn, editorId);
convertOptions.entityExportFn = compositeEntityExportFn(convertOptions.entityExportFn, editorId);
convertOptions.blockImportFn = compositeBlockImportFn(convertOptions.blockImportFn, editorId);
convertOptions.blockExportFn = compositeBlockExportFn(convertOptions.blockExportFn, editorId);
return convertOptions;
};
class KedaoEditor extends React.Component {
constructor(props) {
super(props);
this.onChange = (editorState, callback) => {
let newEditorState = Object.assign({}, editorState);
if (!(editorState instanceof EditorState)) {
newEditorState = EditorState.set(editorState, {
decorator: this.editorDecorators
});
}
if (!newEditorState.convertOptions) {
newEditorState.setConvertOptions(getConvertOptions(this.editorProps));
}
this.setState({ editorState: newEditorState }, () => {
if (this.props.onChange) {
this.props.onChange(newEditorState);
}
if (callback) {
callback(newEditorState);
}
});
};
this.getDraftInstance = () => {
return this.draftInstance;
};
this.getFinderInstance = () => {
return this.finder;
};
this.getValue = () => {
return this.state.editorState;
};
this.setValue = (editorState, callback) => {
return this.onChange(editorState, callback);
};
this.forceRender = () => {
const selectionState = this.state.editorState.getSelection();
this.setValue(EditorState.set(this.state.editorState, {
decorator: this.editorDecorators
}), () => {
this.setValue(EditorState.forceSelection(this.state.editorState, selectionState));
});
};
this.onTab = (event) => {
if (keyCommandHandlers('tab', this.state.editorState, this) === 'handled') {
event.preventDefault();
}
if (this.editorProps.onTab) {
this.editorProps.onTab(event);
}
};
this.onFocus = () => {
this.isFocused = true;
if (this.editorProps.onFocus) {
this.editorProps.onFocus(this.state.editorState);
}
};
this.onBlur = () => {
this.isFocused = false;
if (this.editorProps.onBlur) {
this.editorProps.onBlur(this.state.editorState);
}
};
this.requestFocus = () => {
setTimeout(() => this.draftInstance.focus(), 0);
};
this.handleKeyCommand = (command, editorState) => keyCommandHandlers(command, editorState, this);
this.handleReturn = (event, editorState) => returnHandlers(event, editorState, this);
this.handleBeforeInput = (chars, editorState) => beforeInputHandlers(chars, editorState, this);
this.handleDrop = (selectionState, dataTransfer) => dropHandlers(selectionState, dataTransfer, this);
this.handleDroppedFiles = (selectionState, files) => droppedFilesHandlers(selectionState, files, this);
this.handlePastedFiles = (files) => pastedFilesHandlers(files, this);
this.handleCopyContent = (event) => copyHandlers(event, this);
this.handlePastedText = (text, html, editorState) => pastedTextHandlers(text, html, editorState, this);
this.handleCompositionStart = (event) => compositionStartHandler(event, this);
this.undo = () => {
this.setValue(ContentUtils.undo(this.state.editorState));
};
this.redo = () => {
this.setValue(ContentUtils.redo(this.state.editorState));
};
this.removeSelectionInlineStyles = () => {
this.setValue(ContentUtils.removeSelectionInlineStyles(this.state.editorState));
};
this.insertHorizontalLine = () => {
this.setValue(ContentUtils.insertHorizontalLine(this.state.editorState));
};
this.clearEditorContent = () => {
this.setValue(ContentUtils.clear(this.state.editorState), (editorState) => {
this.setValue(ContentUtils.toggleSelectionIndent(editorState, 0));
});
};
this.toggleFullscreen = (fullscreen) => {
this.setState((prevState) => ({
isFullscreen: typeof fullscreen !== 'undefined'
? fullscreen
: !prevState.isFullscreen
}), () => {
if (this.editorProps.onFullscreen) {
this.editorProps.onFullscreen(this.state.isFullscreen);
}
});
};
this.setEditorContainerNode = (containerNode) => {
this.containerNode = containerNode;
};
this.editorProps = this.getEditorProps(props);
this.editorDecorators = getDecorators(this.editorProps.editorId || this.editorProps.id);
this.controlBarInstance = React.createRef();
this.isFocused = false;
this.isLiving = false;
this.finder = null;
this.valueInitialized = !!(this.props.defaultValue || this.props.value);
const defaultEditorState = (this.props.defaultValue || this.props.value) instanceof EditorState
? this.props.defaultValue || this.props.value
: EditorState.createEmpty(this.editorDecorators);
defaultEditorState.setConvertOptions(getConvertOptions(this.editorProps));
let tempColors = [];
if (ContentUtils.isEditorState(defaultEditorState)) {
const colors = ColorUtils.detectColorsFromDraftState(defaultEditorState.toRAW(true));
defaultEditorState.setConvertOptions(getConvertOptions(this.editorProps));
tempColors = filterColors(colors, this.editorProps.colors);
}
this.state = {
tempColors,
editorState: defaultEditorState,
isFullscreen: false
};
this.containerNode = null;
}
// eslint-disable-next-line camelcase
UNSAFE_componentWillMount() {
if (isControlEnabled(this.editorProps, 'media')) {
const { language, media } = this.editorProps;
const { uploadFn, validateFn, items } = Object.assign(Object.assign({}, defaultProps.media), media);
this.finder = new Finder({
items,
language,
uploader: uploadFn,
validator: validateFn
});
this.forceUpdate();
}
}
componentDidMount() {
this.isLiving = true;
}
// eslint-disable-next-line camelcase
UNSAFE_componentWillReceiveProps(props) {
this.editorProps = this.getEditorProps(props);
const { value: editorState } = props;
const { media, language } = this.editorProps;
const currentProps = this.getEditorProps();
if (!isControlEnabled(currentProps, 'media') &&
isControlEnabled(this.editorProps, 'media') &&
!this.finder) {
const { uploadFn, validateFn, items } = Object.assign(Object.assign({}, defaultProps.media), media);
this.finder = new Finder({
items,
language,
uploader: uploadFn,
validator: validateFn
});
this.forceUpdate();
}
if ((media === null || media === void 0 ? void 0 : media.items) && this.finder) {
this.finder.setItems(media.items);
}
let nextEditorState;
if (!this.valueInitialized &&
typeof this.props.defaultValue === 'undefined' &&
ContentUtils.isEditorState(props.defaultValue)) {
nextEditorState = props.defaultValue;
}
else if (ContentUtils.isEditorState(editorState)) {
nextEditorState = editorState;
}
if (nextEditorState) {
if (nextEditorState && nextEditorState !== this.state.editorState) {
const tempColors = ColorUtils.detectColorsFromDraftState(nextEditorState.toRAW(true));
nextEditorState.setConvertOptions(getConvertOptions(this.editorProps));
this.setState((prevState) => ({
tempColors: filterColors([...prevState.tempColors, ...tempColors], currentProps.colors),
editorState: nextEditorState
}), () => {
if (this.props.onChange) {
this.props.onChange(nextEditorState);
}
});
}
else {
this.setState({
editorState: nextEditorState
});
}
}
}
componentDidUpdate(prevProps, prevState) {
if (prevState.editorState !== this.state.editorState) {
this.state.editorState.setConvertOptions(getConvertOptions(this.editorProps));
}
}
componentWillUnmount() {
this.isLiving = false;
if (this.controlBarInstance) {
this.controlBarInstance.closeFinder();
}
}
getEditorProps(props = this.props) {
const { value, defaultValue, onChange } = props, restProps = __rest(props, ["value", "defaultValue", "onChange"]); // eslint-disable-line no-unused-vars
const propInterceptors = getPropInterceptors(restProps.editorId || restProps.id);
if (propInterceptors.length === 0) {
return restProps;
}
let porpsMap = Map(restProps);
propInterceptors.forEach((interceptor) => {
porpsMap = porpsMap.merge(Map(interceptor(porpsMap.toJS(), this) || {}));
});
return porpsMap.toJS();
}
lockOrUnlockEditor(editorLocked) {
this.setState({ editorLocked });
}
render() {
let { editorId, controls, media, language, hooks, placeholder } = this.editorProps;
const { id, excludeControls, extendControls, readOnly, disabled, colors, colorPicker, colorPickerTheme, colorPickerAutoHide, fontSizes, fontFamilies, emojis, fixPlaceholder, headings, imageControls, imageResizable, imageEqualRatio, lineHeights, letterSpacings, textAligns, textBackgroundColor, allowInsertLinkText, defaultLinkTarget, extendAtomics, className, style, controlBarClassName, controlBarStyle, contentClassName, contentStyle, stripPastedStyles, componentBelowControlBar } = this.editorProps;
const { isFullscreen, editorState } = this.state;
editorId = editorId || id;
hooks = buildHooks(hooks);
controls = controls.filter((item) => excludeControls.indexOf(item) === -1);
language =
(typeof language === 'function'
? language(languages, '@kedao/editor')
: languages[language]) || languages[defaultProps.language];
const externalMedias = Object.assign(Object.assign({}, defaultProps.media.externals), (media === null || media === void 0 ? void 0 : media.externals));
const accepts = Object.assign(Object.assign({}, defaultProps.media.accepts), (media === null || media === void 0 ? void 0 : media.accepts));
media = Object.assign(Object.assign(Object.assign({}, defaultProps.media), media), { externalMedias, accepts });
if (!media.uploadFn) {
media.video = false;
media.audio = false;
}
const controlBarProps = {
editor: this,
editorState,
finder: this.finder,
ref: this.controlBarInstance,
getContainerNode: () => this.containerNode,
className: controlBarClassName,
style: controlBarStyle,
colors: [...colors, ...this.state.tempColors],
colorPicker,
colorPickerTheme,
colorPickerAutoHide,
hooks,
editorId,
media,
controls,
language,
extendControls,
headings,
fontSizes,
fontFamilies,
emojis,
lineHeights,
letterSpacings,
textAligns,
textBackgroundColor,
allowInsertLinkText,
defaultLinkTarget
};
const { unitExportFn } = editorState.convertOptions;
const commonProps = {
editor: this,
editorId,
hooks,
editorState,
containerNode: this.containerNode,
imageControls,
imageResizable,
language,
extendAtomics,
imageEqualRatio
};
const blockRendererFn = getBlockRendererFn(commonProps, this.editorProps.blockRendererFn);
const blockRenderMap = getBlockRenderMap(commonProps, this.editorProps.blockRenderMap);
const blockStyleFn = getBlockStyleFn(this.editorProps.blockStyleFn);
const customStyleMap = getCustomStyleMap(commonProps, this.editorProps.customStyleMap);
const customStyleFn = getCustomStyleFn(commonProps, {
fontFamilies,
unitExportFn,
customStyleFn: this.editorProps.customStyleFn
});
const keyBindingFn = getKeyBindingFn(this.editorProps.keyBindingFn);
const mixedProps = {};
if (this.state.editorLocked ||
this.editorProps.disabled ||
this.editorProps.readOnly ||
this.editorProps.draftProps.readOnly) {
mixedProps.readOnly = true;
}
if (placeholder &&
fixPlaceholder &&
editorState.isEmpty() &&
editorState.getCurrentContent().getFirstBlock().getType() !== 'unstyled') {
placeholder = '';
}
const draftProps = Object.assign(Object.assign({ ref: (instance) => {
this.draftInstance = instance;
}, editorState, handleKeyCommand: this.handleKeyCommand, handleReturn: this.handleReturn, handleBeforeInput: this.handleBeforeInput, handleDrop: this.handleDrop, handleDroppedFiles: this.handleDroppedFiles, handlePastedText: this.handlePastedText, handlePastedFiles: this.handlePastedFiles, onChange: this.onChange, onTab: this.onTab, onFocus: this.onFocus, onBlur: this.onBlur, blockRenderMap,
blockRendererFn,
blockStyleFn,
customStyleMap,
customStyleFn,
keyBindingFn,
placeholder,
stripPastedStyles }, this.editorProps.draftProps), mixedProps);
return (React.createElement("div", { style: style, ref: this.setEditorContainerNode, className: mergeClassNames('bf-container', className, disabled && 'disabled', readOnly && 'read-only', isFullscreen && 'fullscreen') },
React.createElement(ControlBar, Object.assign({}, controlBarProps)),
componentBelowControlBar,
React.createElement("div", { onCompositionStart: this.handleCompositionStart, className: `bf-content ${contentClassName}`, onCopy: this.handleCopyContent, style: contentStyle },
React.createElement(Editor, Object.assign({}, draftProps)))));
}
}
KedaoEditor.defaultProps = defaultProps;
export default KedaoEditor;
export { EditorState };
//# sourceMappingURL=index.js.map