diff --git a/plugins/talk-plugin-rich-text/client/components/rte/RTE.css b/plugins/talk-plugin-rich-text/client/components/rte/RTE.css index ab24828bd..2bf1feaf5 100644 --- a/plugins/talk-plugin-rich-text/client/components/rte/RTE.css +++ b/plugins/talk-plugin-rich-text/client/components/rte/RTE.css @@ -16,3 +16,14 @@ margin: 12px 0 0 12px; color: #bbb; } + +.toolbarDisabled { + background: #f8f8f8; + cursor: default; +} + +.contentEditableDisabled { + background: #fafafa; + color: #888; + cursor: default; +} diff --git a/plugins/talk-plugin-rich-text/client/components/rte/RTE.js b/plugins/talk-plugin-rich-text/client/components/rte/RTE.js index 422109292..2694c3a26 100644 --- a/plugins/talk-plugin-rich-text/client/components/rte/RTE.js +++ b/plugins/talk-plugin-rich-text/client/components/rte/RTE.js @@ -27,6 +27,7 @@ class RTE extends React.Component { } handleChange = () => { + this.handleSelectionChange(); this.props.onChange({ text: this.ref.htmlEl.innerText, html: this.ref.htmlEl.innerHTML, @@ -60,18 +61,10 @@ class RTE extends React.Component { return handled; }; - handleClick = () => { + handleMouseUp = () => { setTimeout(() => this.handleSelectionChange()); }; - handleKeyDown = () => { - this.handleSelectionChange(); - }; - - handleKeyUp = () => { - this.handleSelectionChange(); - }; - handleKeyPress = e => { this.handleSelectionChange(); if (e.key === 'Enter') { @@ -92,36 +85,51 @@ class RTE extends React.Component { renderButtons() { return this.props.buttons.map(b => { return React.cloneElement(b, { + disabled: this.props.disabled, api: this.api, ref: this.createButtonRefHandler(b.key), }); }); } + getClassNames() { + const { disabled } = this.props; + return { + toolbar: cn(this.props.toolbarClassName, { + [this.props.toolbarClassNameDisabled]: disabled, + [styles.toolbarDisabled]: disabled, + }), + content: cn(styles.contentEditable, this.props.contentClassName, { + [this.props.contentClassNameDisabled]: disabled, + [styles.contentEditableDisabled]: disabled, + }), + root: cn(this.props.className, { + [this.props.classNameDisabled]: disabled, + }), + placeholder: cn(styles.placeholder, this.props.placeholderClassName, { + [this.props.placeholderClassNameDisabled]: disabled, + }), + }; + } + render() { - const { - className, - contentClassName, - toolbarClassName, - value, - placeholder, - inputId, - } = this.props; + const { value, placeholder, inputId, disabled } = this.props; + + const classNames = this.getClassNames(); return ( -
- {this.renderButtons()} - {!value &&
{placeholder}
} +
+ {this.renderButtons()} + {!value &&
{placeholder}
}
@@ -140,8 +148,13 @@ RTE.propTypes = { onChange: PropTypes.func, disabled: PropTypes.bool, className: PropTypes.string, + classNameDisabled: PropTypes.string, contentClassName: PropTypes.string, + contentClassNameDisabled: PropTypes.string, toolbarClassName: PropTypes.string, + toolbarClassNameDisabled: PropTypes.string, + placeholderClassName: PropTypes.string, + placeholderClassNameDisabled: PropTypes.string, placeholder: PropTypes.string, value: PropTypes.string, }; diff --git a/plugins/talk-plugin-rich-text/client/components/rte/buttons/Blockquote.js b/plugins/talk-plugin-rich-text/client/components/rte/buttons/Blockquote.js index 7d3b76d6f..d12429339 100644 --- a/plugins/talk-plugin-rich-text/client/components/rte/buttons/Blockquote.js +++ b/plugins/talk-plugin-rich-text/client/components/rte/buttons/Blockquote.js @@ -36,11 +36,11 @@ function execCommand() { insertNodes(node); selectNode(node); } - this.broadcastChange(); } + this.broadcastChange(); } -function syncState() { +function isActive() { return !!findIntersectingTag('BLOCKQUOTE'); } @@ -52,7 +52,7 @@ const onEnter = node => { return true; }; -const Blockquote = createToggle(execCommand, syncState, { onEnter }); +const Blockquote = createToggle(execCommand, { onEnter, isActive }); Blockquote.defaultProps = { children: 'Blockquote', diff --git a/plugins/talk-plugin-rich-text/client/components/rte/buttons/Bold.js b/plugins/talk-plugin-rich-text/client/components/rte/buttons/Bold.js index 4988b0679..7f6259c77 100644 --- a/plugins/talk-plugin-rich-text/client/components/rte/buttons/Bold.js +++ b/plugins/talk-plugin-rich-text/client/components/rte/buttons/Bold.js @@ -1,9 +1,9 @@ import createToggle from '../factories/createToggle'; const execCommand = () => document.execCommand('bold'); -const syncState = () => document.queryCommandState('bold'); +const isActive = () => document.queryCommandState('bold'); -const Bold = createToggle(execCommand, syncState); +const Bold = createToggle(execCommand, { isActive }); Bold.defaultProps = { children: 'Bold', diff --git a/plugins/talk-plugin-rich-text/client/components/rte/buttons/Italic.js b/plugins/talk-plugin-rich-text/client/components/rte/buttons/Italic.js index 6387e4237..f7fc6a4f4 100644 --- a/plugins/talk-plugin-rich-text/client/components/rte/buttons/Italic.js +++ b/plugins/talk-plugin-rich-text/client/components/rte/buttons/Italic.js @@ -1,9 +1,9 @@ import createToggle from '../factories/createToggle'; const execCommand = () => document.execCommand('italic'); -const syncState = () => document.queryCommandState('italic'); +const isActive = () => document.queryCommandState('italic'); -const Italic = createToggle(execCommand, syncState); +const Italic = createToggle(execCommand, { isActive }); Italic.defaultProps = { children: 'Italic', diff --git a/plugins/talk-plugin-rich-text/client/components/rte/components/Button.css b/plugins/talk-plugin-rich-text/client/components/rte/components/Button.css index daedd54ec..070906205 100644 --- a/plugins/talk-plugin-rich-text/client/components/rte/components/Button.css +++ b/plugins/talk-plugin-rich-text/client/components/rte/components/Button.css @@ -35,3 +35,9 @@ border-radius: 3px; background-color: #ddd; } + +.button:disabled{ + color: #bbb; + cursor: default; + background-color: inherit; +} diff --git a/plugins/talk-plugin-rich-text/client/components/rte/components/Button.js b/plugins/talk-plugin-rich-text/client/components/rte/components/Button.js index f7bc75941..f99648cb7 100644 --- a/plugins/talk-plugin-rich-text/client/components/rte/components/Button.js +++ b/plugins/talk-plugin-rich-text/client/components/rte/components/Button.js @@ -12,6 +12,7 @@ class Button extends React.Component { children, active, activeClassName, + disabled, } = this.props; return ( @@ -34,6 +36,7 @@ Button.propTypes = { onClick: PropTypes.func, children: PropTypes.node, active: PropTypes.bool, + disabled: PropTypes.bool, }; export default Button; diff --git a/plugins/talk-plugin-rich-text/client/components/rte/factories/createToggle.js b/plugins/talk-plugin-rich-text/client/components/rte/factories/createToggle.js index 040dc6543..b307ae199 100644 --- a/plugins/talk-plugin-rich-text/client/components/rte/factories/createToggle.js +++ b/plugins/talk-plugin-rich-text/client/components/rte/factories/createToggle.js @@ -2,14 +2,19 @@ import React from 'react'; import PropTypes from 'prop-types'; import Button from '../components/Button'; -const createToggle = (execCommand, getCurrentState, { onEnter } = {}) => { +const createToggle = ( + execCommand, + { onEnter, isActive = () => false, isDisabled = () => false } = {} +) => { class Toggle extends React.Component { state = { active: false, + disabled: false, }; execCommand = () => execCommand.apply(this.props.api); - getCurrentState = () => getCurrentState.apply(this.props.api); + isActive = () => isActive.apply(this.props.api); + isDisabled = () => isDisabled.apply(this.props.api); onEnter = (...args) => onEnter && onEnter.apply(this.props.api, args); formatToggle = () => { @@ -23,11 +28,16 @@ const createToggle = (execCommand, getCurrentState, { onEnter } = {}) => { }; syncState = () => { - if (this.state.active !== this.getCurrentState()) { + if (this.state.active !== this.isActive()) { this.setState(state => ({ active: !state.active, })); } + if (this.state.disabled !== this.isDisabled()) { + this.setState(state => ({ + disabled: !state.disabled, + })); + } }; onSelectionChange() { @@ -35,13 +45,14 @@ const createToggle = (execCommand, getCurrentState, { onEnter } = {}) => { } render() { - const { className, title, children } = this.props; + const { className, title, children, disabled } = this.props; return ( @@ -54,6 +65,7 @@ const createToggle = (execCommand, getCurrentState, { onEnter } = {}) => { className: PropTypes.string, title: PropTypes.string, children: PropTypes.node, + disabled: PropTypes.bool, }; return Toggle; }; diff --git a/plugins/talk-plugin-rich-text/client/components/rte/lib/dom.js b/plugins/talk-plugin-rich-text/client/components/rte/lib/dom.js index 24a22f317..e611de234 100644 --- a/plugins/talk-plugin-rich-text/client/components/rte/lib/dom.js +++ b/plugins/talk-plugin-rich-text/client/components/rte/lib/dom.js @@ -118,11 +118,24 @@ function ensureEndMarker(node) { } } +export function nodeContains(node, lookFor) { + if (node.isSameNode(lookFor)) { + return true; + } + for (let i = 0; i < node.childNodes.length; i++) { + if (nodeContains(node.childNodes[i], lookFor)) { + return true; + } + } + return false; +} + export function selectionIsInside(node) { const range = getSelectionRange(); return ( range && - (node.contains(range.startContainer) || node.contains(range.endContainer)) + (nodeContains(node, range.startContainer) || + nodeContains(node, range.endContainer)) ); } @@ -251,6 +264,9 @@ export function getWholeLine(node) { return [node]; } const child = lastParentBeforeBlock(node); + if (child.tagName === 'BR') { + return [...getLeftOfNode(child), child]; + } return [...getLeftOfNode(child), child, ...getRightOfNode(child)]; } @@ -302,13 +318,13 @@ export function getSelectedChildren(ancestor) { for (let i = 0; i < ancestor.childNodes.length; i++) { const node = ancestor.childNodes[i]; if (!foundStart) { - if (node.contains(start)) { + if (nodeContains(node, start)) { foundStart = true; } } if (foundStart) { result.push(node); - if (node.contains(end)) { + if (nodeContains(node, end)) { break; } }