diff --git a/CHANGELOG.md b/CHANGELOG.md index e5e91fbe..d0306ead 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,12 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/) and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html). +## 0.6.15 - 2019-12-26 +### Fixed +- Focus loss in plugins with react-select dependency (#288) +- Data loss when swapping plugin blocks with blur update (#286) +- Placeholder trimmed when movableBlocks prop is active (#283) + ## 0.6.14 - 2019-11-30 ### Added - Add onAction function to listen to reorder blocks button clicks (#282) diff --git a/package.json b/package.json index b225bd2f..400225ba 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "megadraft", - "version": "0.6.14", + "version": "0.6.15", "description": "Rich Text editor built on top of draft.js", "main": "lib/Megadraft.js", "dependencies": { @@ -71,8 +71,8 @@ "webpack-dev-server": "^3.4.1" }, "peerDependencies": { - "react": "^15.6.1 || ^16.1.0", - "react-dom": "^15.6.1 || ^16.1.0" + "react": "^16.1.0", + "react-dom": "^16.1.0" }, "scripts": { "start": "gulp dev-server", diff --git a/src/components/ActionsProvider.js b/src/components/ActionsProvider.js index 458f15ab..024cf303 100644 --- a/src/components/ActionsProvider.js +++ b/src/components/ActionsProvider.js @@ -7,42 +7,18 @@ import React from "react"; import PropTypes from "prop-types"; -export default class ActionsProvider extends React.Component { - constructor(props) { - super(props); - } +export const defaultAction = () => {}; - getChildContext() { - return { - onAction: this.props.onAction - }; - } +export const ActionsContext = React.createContext({ onAction: defaultAction }); - render() { - return this.props.children; - } -} +const ActionsProvider = ({ onAction = defaultAction, children }) => ( + + {children} + +); -ActionsProvider.childContextTypes = { +ActionsProvider.propTypes = { onAction: PropTypes.func }; -export function withActions(WrappedComponent) { - class WithActionsHOC extends React.Component { - constructor(props) { - super(props); - } - - render() { - return ( - - ); - } - } - - WithActionsHOC.contextTypes = { - onAction: PropTypes.func - }; - - return WithActionsHOC; -} +export default ActionsProvider; diff --git a/src/components/MegadraftEditor.js b/src/components/MegadraftEditor.js index d0c3ff4a..6ce93dc5 100644 --- a/src/components/MegadraftEditor.js +++ b/src/components/MegadraftEditor.js @@ -43,12 +43,10 @@ import notFoundPlugin from "../plugins/not-found/plugin"; import DEFAULT_PLUGINS from "../plugins/default"; import DEFAULT_ACTIONS from "../actions/default"; import DEFAULT_ENTITY_INPUTS from "../entity_inputs/default"; -import ActionsProvider from "./ActionsProvider"; +import ActionsProvider, { defaultAction } from "./ActionsProvider"; const NO_RESET_STYLE_DEFAULT = ["ordered-list-item", "unordered-list-item"]; -const noop = () => {}; - export default class MegadraftEditor extends Component { static defaultProps = { actions: DEFAULT_ACTIONS, @@ -62,7 +60,10 @@ export default class MegadraftEditor extends Component { this.state = { readOnly: this.props.readOnly || false, hasFocus: false, - scrollRef: "" + scrollRef: "", + swapUp: false, + swapDown: false, + didSwap: false }; this.onChange = ::this.onChange; @@ -91,7 +92,7 @@ export default class MegadraftEditor extends Component { this.keyBindings = this.props.keyBindings || []; - this.onAction = this.props.onAction || noop; + this.onAction = this.props.onAction || defaultAction; this.extendedBlockRenderMap = Immutable.OrderedMap().withMutations(r => { for (let [blockType, data] of DefaultDraftBlockRenderMap.entrySeq()) { @@ -104,6 +105,8 @@ export default class MegadraftEditor extends Component { swapDown={this.swapDown} isFirstBlock={this.isFirstBlock} isLastBlock={this.isLastBlock} + onAction={this.onAction} + isAtomic={blockType === "atomic"} /> ) : ( @@ -352,7 +355,9 @@ export default class MegadraftEditor extends Component { } handleClassEditor(identifier) { - let classEditor = identifier; + let classEditor = this.props.movableBlocks + ? `${identifier} movable` + : identifier; let contentState = this.props.editorState.getCurrentContent(); // If the user changes block type before entering any text, we can // either style the placeholder or hide it. @@ -383,8 +388,22 @@ export default class MegadraftEditor extends Component { clearTimeout(this.blurTimeoutID); } - componentDidUpdate(prevProps) { - if (prevProps.editorState !== this.props.editorState) { + componentDidUpdate() { + if (this.state.swapUp || this.state.swapDown) { + const swapFunction = this.state.swapUp ? swapDataUp : swapDataDown; + + const newEditorState = swapFunction({ + editorState: this.props.editorState, + currentKey: this.state.scrollRef + }); + + this.onChange(newEditorState); + this.setState({ + didSwap: true, + swapUp: false, + swapDown: false + }); + } else if (this.state.didSwap) { const control = document.querySelector(`[id*="${this.state.scrollRef}"]`); if (control) { @@ -394,6 +413,9 @@ export default class MegadraftEditor extends Component { control.classList.toggle("move-control--swapped"); }; + const input = control.querySelector("[type=text]"); + input && input.focus(); + control.scrollIntoView({ block: "center" }); window.scroll(0, window.pageYOffset - control.clientHeight / 2); @@ -403,7 +425,10 @@ export default class MegadraftEditor extends Component { swapEffect(); }, 300); - this.setState({ scrollRef: "" }); + this.setState({ + didSwap: false, + scrollRef: "" + }); } } } @@ -467,21 +492,27 @@ export default class MegadraftEditor extends Component { } swapUp = currentKey => { - const newEditorState = swapDataUp({ - editorState: this.props.editorState, - currentKey + document.activeElement.blur(); + + this.forceUpdate(() => { + this.setState({ + swapUp: true, + swapDown: false, + scrollRef: currentKey + }); }); - this.onChange(newEditorState); - this.setState({ scrollRef: currentKey }); }; swapDown = currentKey => { - const newEditorState = swapDataDown({ - editorState: this.props.editorState, - currentKey + document.activeElement.blur(); + + this.forceUpdate(() => { + this.setState({ + swapUp: false, + swapDown: true, + scrollRef: currentKey + }); }); - this.onChange(newEditorState); - this.setState({ scrollRef: currentKey }); }; isFirstBlock = currentKey => { diff --git a/src/components/ModalPluginItem.js b/src/components/ModalPluginItem.js index 0699f9d2..d79126a5 100644 --- a/src/components/ModalPluginItem.js +++ b/src/components/ModalPluginItem.js @@ -6,10 +6,12 @@ import React, { Component } from "react"; -import { withActions } from "./ActionsProvider"; +import { ActionsContext } from "./ActionsProvider"; import { PLUGINS_MODAL_ADD_PLUGIN } from "../constants"; -class ModalPluginItem extends Component { +export default class ModalPluginItem extends Component { + static contextType = ActionsContext; + constructor(props) { super(props); this.handleClick = ::this.handleClick; @@ -33,7 +35,7 @@ class ModalPluginItem extends Component { key={item.type} className="megadraft-modal__item" onClick={() => { - this.props.onAction({ + this.context.onAction({ type: PLUGINS_MODAL_ADD_PLUGIN, pluginName: item.title }); @@ -67,5 +69,3 @@ class ModalPluginItem extends Component { ); } } - -export default withActions(ModalPluginItem); diff --git a/src/components/MoveControl.js b/src/components/MoveControl.js index 688a132d..99aafc3c 100644 --- a/src/components/MoveControl.js +++ b/src/components/MoveControl.js @@ -7,7 +7,6 @@ import React from "react"; import icons from "../icons"; import MegadraftBlock from "./MegadraftBlock"; -import { withActions } from "./ActionsProvider"; import { BLOCK_SWAP_UP, BLOCK_SWAP_DOWN } from "../constants"; @@ -60,9 +59,13 @@ const Control = ({ onClickDown, isFirst, isLast, - onAction + onAction, + isAtomic }) => ( -
+
{children}
@@ -79,10 +82,11 @@ const Controlled = ({ keySwapDown, isFirstBlock, isLastBlock, + isAtomic, swapUp, swapDown, - children, - onAction + onAction, + children }) => { const onClickUp = () => swapUp(keySwapUp); const onClickDown = () => swapDown(keySwapDown); @@ -95,7 +99,7 @@ const Controlled = ({ id={ keySwapUp !== keySwapDown ? `${keySwapUp}-${keySwapDown}` : keySwapUp } - {...{ onClickUp, onClickDown, isFirst, isLast, onAction }} + {...{ onClickUp, onClickDown, isFirst, isLast, onAction, isAtomic }} > {children} @@ -103,44 +107,43 @@ const Controlled = ({ ); }; -export default withActions( - ({ - wrapper, - swapUp, - swapDown, - children, - isFirstBlock, - isLastBlock, - onAction - }) => { - const arrayChildren = React.Children.toArray(children); - const firstChildKey = arrayChildren[0].props.children.key; - const lastChildKey = - arrayChildren[arrayChildren.length - 1].props.children.key; - - const controlledChildren = React.Children.map(children, child => { - const currentKey = child.props.children.key; - return ( - - {child} - - ); - }); +export default ({ + wrapper, + swapUp, + swapDown, + children, + isFirstBlock, + isLastBlock, + onAction, + isAtomic +}) => { + const arrayChildren = React.Children.toArray(children); + const firstChildKey = arrayChildren[0].props.children.key; + const lastChildKey = + arrayChildren[arrayChildren.length - 1].props.children.key; - return wrapper ? ( + const controlledChildren = React.Children.map(children, child => { + const currentKey = child.props.children.key; + return ( - {React.cloneElement(wrapper, [], children)} + {child} - ) : ( - controlledChildren ); - } -); + }); + + return wrapper ? ( + + {React.cloneElement(wrapper, [], children)} + + ) : ( + controlledChildren + ); +}; diff --git a/src/components/PluginsModal.js b/src/components/PluginsModal.js index 16db9d2d..76a3607b 100644 --- a/src/components/PluginsModal.js +++ b/src/components/PluginsModal.js @@ -9,10 +9,12 @@ import React, { Component } from "react"; import Modal from "backstage-modal"; import ModalPluginList from "./ModalPluginList"; -import { withActions } from "./ActionsProvider"; +import { ActionsContext } from "./ActionsProvider"; import { PLUGINS_MODAL_CLOSE } from "../constants"; -class PluginsModal extends Component { +export default class PluginsModal extends Component { + static contextType = ActionsContext; + constructor(props) { super(props); this.onCloseRequest = ::this.onCloseRequest; @@ -26,7 +28,7 @@ class PluginsModal extends Component { return; } document.body.classList.remove("megadraft-modal--open"); - this.props.onAction({ type: PLUGINS_MODAL_CLOSE }); + this.context.onAction({ type: PLUGINS_MODAL_CLOSE }); this.props.toggleModalVisibility(); } @@ -52,5 +54,3 @@ class PluginsModal extends Component { ); } } - -export default withActions(PluginsModal); diff --git a/src/components/Sidebar.js b/src/components/Sidebar.js index 9f219bb1..2ff75ce2 100644 --- a/src/components/Sidebar.js +++ b/src/components/Sidebar.js @@ -16,14 +16,16 @@ import { SIDEBAR_CLICK_MORE } from "../constants"; -import { withActions } from "./ActionsProvider"; +import { ActionsContext } from "./ActionsProvider"; import "setimmediate"; import PluginsModal from "./PluginsModal"; import { getSelectedBlockElement } from "../utils"; -class BlockStylesComponent extends Component { +class BlockStyles extends Component { + static contextType = ActionsContext; + constructor(props) { super(props); this.state = { @@ -70,7 +72,7 @@ class BlockStylesComponent extends Component {