mirror of
https://github.com/wassname/talk.git
synced 2026-07-17 11:33:39 +08:00
Better dom handling, disable state
This commit is contained in:
@@ -16,3 +16,14 @@
|
||||
margin: 12px 0 0 12px;
|
||||
color: #bbb;
|
||||
}
|
||||
|
||||
.toolbarDisabled {
|
||||
background: #f8f8f8;
|
||||
cursor: default;
|
||||
}
|
||||
|
||||
.contentEditableDisabled {
|
||||
background: #fafafa;
|
||||
color: #888;
|
||||
cursor: default;
|
||||
}
|
||||
|
||||
@@ -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 (
|
||||
<div className={cn(styles.root, className)}>
|
||||
<Toolbar className={toolbarClassName}>{this.renderButtons()}</Toolbar>
|
||||
{!value && <div className={styles.placeholder}>{placeholder}</div>}
|
||||
<div className={classNames.root}>
|
||||
<Toolbar className={classNames.toolbar}>{this.renderButtons()}</Toolbar>
|
||||
{!value && <div className={classNames.placeholder}>{placeholder}</div>}
|
||||
<ContentEditable
|
||||
id={inputId}
|
||||
onClick={this.handleClick}
|
||||
onMouseUp={this.handleMouseUp}
|
||||
onKeyUp={this.handleSelectionChange}
|
||||
onKeyPress={this.handleKeyPress}
|
||||
onKeyDown={this.handleKeyDown}
|
||||
onKeyUp={this.handleKeyDown}
|
||||
className={cn(contentClassName, styles.contentEditable)}
|
||||
className={classNames.content}
|
||||
ref={this.handleRef}
|
||||
html={value}
|
||||
disabled={false}
|
||||
disabled={disabled}
|
||||
onChange={this.handleChange}
|
||||
/>
|
||||
</div>
|
||||
@@ -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,
|
||||
};
|
||||
|
||||
@@ -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',
|
||||
|
||||
@@ -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',
|
||||
|
||||
@@ -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',
|
||||
|
||||
@@ -35,3 +35,9 @@
|
||||
border-radius: 3px;
|
||||
background-color: #ddd;
|
||||
}
|
||||
|
||||
.button:disabled{
|
||||
color: #bbb;
|
||||
cursor: default;
|
||||
background-color: inherit;
|
||||
}
|
||||
|
||||
@@ -12,6 +12,7 @@ class Button extends React.Component {
|
||||
children,
|
||||
active,
|
||||
activeClassName,
|
||||
disabled,
|
||||
} = this.props;
|
||||
return (
|
||||
<button
|
||||
@@ -20,6 +21,7 @@ class Button extends React.Component {
|
||||
})}
|
||||
title={title}
|
||||
onClick={onClick}
|
||||
disabled={disabled}
|
||||
>
|
||||
{children}
|
||||
</button>
|
||||
@@ -34,6 +36,7 @@ Button.propTypes = {
|
||||
onClick: PropTypes.func,
|
||||
children: PropTypes.node,
|
||||
active: PropTypes.bool,
|
||||
disabled: PropTypes.bool,
|
||||
};
|
||||
|
||||
export default Button;
|
||||
|
||||
@@ -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 (
|
||||
<Button
|
||||
className={className}
|
||||
title={title}
|
||||
onClick={this.handleClick}
|
||||
active={this.state.active}
|
||||
disabled={disabled || this.state.disabled}
|
||||
>
|
||||
{children}
|
||||
</Button>
|
||||
@@ -54,6 +65,7 @@ const createToggle = (execCommand, getCurrentState, { onEnter } = {}) => {
|
||||
className: PropTypes.string,
|
||||
title: PropTypes.string,
|
||||
children: PropTypes.node,
|
||||
disabled: PropTypes.bool,
|
||||
};
|
||||
return Toggle;
|
||||
};
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user