mirror of
https://github.com/wassname/talk.git
synced 2026-07-15 11:26:58 +08:00
Refactor n active state
This commit is contained in:
@@ -1,20 +0,0 @@
|
||||
.button > i {
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
.button {
|
||||
background-color: transparent;
|
||||
padding: 3px;
|
||||
border: none;
|
||||
color: #4e4e4e;
|
||||
margin-right: 3px;
|
||||
}
|
||||
|
||||
.button:hover{
|
||||
cursor: pointer;
|
||||
border-radius: 3px;
|
||||
background-color: #eae8e8;
|
||||
}
|
||||
.icon {
|
||||
font-size: 20px;
|
||||
}
|
||||
@@ -1,15 +1,5 @@
|
||||
.contentEditable {
|
||||
.commentContent {
|
||||
composes: content from "./CommentContent.css";
|
||||
background: #fff;
|
||||
border: solid 1px #bbb;
|
||||
min-height: 120px;
|
||||
box-sizing: border-box;
|
||||
outline: 0;
|
||||
overflow-y: auto;
|
||||
width: 100%;
|
||||
padding: 10px;
|
||||
font-style: unset;
|
||||
margin-bottom: 3px;
|
||||
}
|
||||
|
||||
.placeholder {
|
||||
@@ -17,3 +7,7 @@
|
||||
margin: 12px 0 0 12px;
|
||||
color: #bbb;
|
||||
}
|
||||
|
||||
.icon {
|
||||
font-size: 20px;
|
||||
}
|
||||
|
||||
@@ -4,19 +4,18 @@ import styles from './Editor.css';
|
||||
import cn from 'classnames';
|
||||
import { PLUGIN_NAME } from '../constants';
|
||||
import { htmlNormalizer } from '../utils';
|
||||
import ContentEditable from 'react-contenteditable';
|
||||
import Toolbar from './Toolbar';
|
||||
import Button from './Button';
|
||||
import bowser from 'bowser';
|
||||
import RTE from './rte/RTE';
|
||||
import { Icon } from 'plugin-api/beta/client/components/ui';
|
||||
import { Bold, Italic, Blockquote } from './rte/buttons';
|
||||
|
||||
class Editor extends React.Component {
|
||||
ref = null;
|
||||
handleRef = ref => (this.ref = ref);
|
||||
|
||||
handleChange = evt => {
|
||||
handleChange = c => {
|
||||
this.props.onInputChange({
|
||||
body: this.ref.htmlEl.innerText,
|
||||
richTextBody: evt.target.value,
|
||||
body: c.text,
|
||||
richTextBody: c.html,
|
||||
});
|
||||
};
|
||||
|
||||
@@ -46,52 +45,10 @@ class Editor extends React.Component {
|
||||
this.props.unregisterHook(this.normalizeHook);
|
||||
}
|
||||
|
||||
hasAncestor(tag) {
|
||||
const sel = window.getSelection();
|
||||
const range = sel.getRangeAt(0);
|
||||
let cur = range.startContainer;
|
||||
do {
|
||||
if (cur.nodeName === tag) {
|
||||
return true;
|
||||
}
|
||||
cur = cur.parentNode;
|
||||
} while (cur);
|
||||
return false;
|
||||
}
|
||||
|
||||
formatBold = () => {
|
||||
document.execCommand('bold');
|
||||
this.ref.htmlEl.focus();
|
||||
};
|
||||
|
||||
formatItalic = () => {
|
||||
document.execCommand('italic');
|
||||
this.ref.htmlEl.focus();
|
||||
};
|
||||
|
||||
formatBlockquote = () => {
|
||||
if (this.hasAncestor('BLOCKQUOTE')) {
|
||||
document.execCommand('outdent');
|
||||
} else {
|
||||
if (bowser.msie) {
|
||||
document.execCommand('indent');
|
||||
} else {
|
||||
document.execCommand('formatBlock', false, 'blockquote');
|
||||
}
|
||||
}
|
||||
this.ref.htmlEl.focus();
|
||||
};
|
||||
|
||||
outdentOnEnter = e => {
|
||||
if (e.key === 'Enter' && !e.shiftKey) {
|
||||
setTimeout(() => {
|
||||
document.execCommand('outdent');
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
render() {
|
||||
const inputId = `${this.props.id}-rte`;
|
||||
const { id, placeholder, label, disabled } = this.props;
|
||||
|
||||
const inputId = `${id}-rte`;
|
||||
return (
|
||||
<div className={cn(styles.root, `${PLUGIN_NAME}-container`)}>
|
||||
<label
|
||||
@@ -99,32 +56,26 @@ class Editor extends React.Component {
|
||||
className="screen-reader-text"
|
||||
aria-hidden={true}
|
||||
>
|
||||
{this.props.label}
|
||||
{label}
|
||||
</label>
|
||||
<Toolbar>
|
||||
<Button icon="format_bold" title="bold" onClick={this.formatBold} />
|
||||
<Button
|
||||
icon="format_italic"
|
||||
title="italic"
|
||||
onClick={this.formatItalic}
|
||||
/>
|
||||
<Button
|
||||
icon="format_quote"
|
||||
title="quote"
|
||||
onClick={this.formatBlockquote}
|
||||
/>
|
||||
</Toolbar>
|
||||
{!this.props.input.body && (
|
||||
<div className={styles.placeholder}>{this.props.placeholder}</div>
|
||||
)}
|
||||
<ContentEditable
|
||||
id={inputId}
|
||||
onKeyPress={this.outdentOnEnter}
|
||||
className={styles.contentEditable}
|
||||
ref={this.handleRef}
|
||||
html={this.getHTML()}
|
||||
disabled={false}
|
||||
<RTE
|
||||
inputId={inputId}
|
||||
contentClassName={styles.commentContent}
|
||||
onChange={this.handleChange}
|
||||
value={this.getHTML()}
|
||||
disabled={disabled}
|
||||
placeholder={placeholder}
|
||||
buttons={[
|
||||
<Bold key="bold" title="bold">
|
||||
<Icon className={styles.icon} name={'format_bold'} />
|
||||
</Bold>,
|
||||
<Italic key="italic" title="italic">
|
||||
<Icon className={styles.icon} name={'format_italic'} />
|
||||
</Italic>,
|
||||
<Blockquote key="blockquote" title="blockquote">
|
||||
<Icon className={styles.icon} name={'format_quote'} />
|
||||
</Blockquote>,
|
||||
]}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
@@ -137,7 +88,6 @@ Editor.propTypes = {
|
||||
onInputChange: PropTypes.func,
|
||||
disabled: PropTypes.bool,
|
||||
comment: PropTypes.object,
|
||||
classNames: PropTypes.object,
|
||||
registerHook: PropTypes.func,
|
||||
unregisterHook: PropTypes.func,
|
||||
isReply: PropTypes.bool,
|
||||
|
||||
@@ -0,0 +1,37 @@
|
||||
.buttonReset {
|
||||
user-select: none;
|
||||
outline: invert none medium;
|
||||
border: none;
|
||||
touch-action: manipulation;
|
||||
padding: 0;
|
||||
overflow: hidden;
|
||||
|
||||
-webkit-tap-highlight-color: rgba(0, 0, 0, 0) !important;
|
||||
&::-moz-focus-inner: {
|
||||
border: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.button > i {
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
.button {
|
||||
composes: buttonReset;
|
||||
background-color: transparent;
|
||||
padding: 3px;
|
||||
border: none;
|
||||
color: #4e4e4e;
|
||||
margin-right: 3px;
|
||||
}
|
||||
|
||||
.button:hover{
|
||||
cursor: pointer;
|
||||
border-radius: 3px;
|
||||
background-color: #eae8e8;
|
||||
}
|
||||
|
||||
.active {
|
||||
border-radius: 3px;
|
||||
background-color: #ddd;
|
||||
}
|
||||
+17
-7
@@ -1,29 +1,39 @@
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import styles from './Button.css';
|
||||
import { Icon, BareButton } from 'plugin-api/beta/client/components/ui';
|
||||
import cn from 'classnames';
|
||||
|
||||
class Button extends React.Component {
|
||||
render() {
|
||||
const { className, icon, title, onClick } = this.props;
|
||||
const {
|
||||
className,
|
||||
title,
|
||||
onClick,
|
||||
children,
|
||||
active,
|
||||
activeClassName,
|
||||
} = this.props;
|
||||
return (
|
||||
<BareButton
|
||||
className={cn(className, styles.button)}
|
||||
<button
|
||||
className={cn(className, styles.button, {
|
||||
[cn(styles.active, activeClassName)]: active,
|
||||
})}
|
||||
title={title}
|
||||
onClick={onClick}
|
||||
>
|
||||
<Icon className={styles.icon} name={icon} />
|
||||
</BareButton>
|
||||
{children}
|
||||
</button>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Button.propTypes = {
|
||||
icon: PropTypes.string.isRequired,
|
||||
className: PropTypes.string,
|
||||
activeClassName: PropTypes.string,
|
||||
title: PropTypes.string,
|
||||
onClick: PropTypes.func,
|
||||
children: PropTypes.node,
|
||||
active: PropTypes.bool,
|
||||
};
|
||||
|
||||
export default Button;
|
||||
@@ -0,0 +1,18 @@
|
||||
.contentEditable {
|
||||
background: #fff;
|
||||
border: solid 1px #bbb;
|
||||
min-height: 120px;
|
||||
box-sizing: border-box;
|
||||
outline: 0;
|
||||
overflow-y: auto;
|
||||
width: 100%;
|
||||
padding: 10px;
|
||||
font-style: unset;
|
||||
margin-bottom: 3px;
|
||||
}
|
||||
|
||||
.placeholder {
|
||||
position: absolute;
|
||||
margin: 12px 0 0 12px;
|
||||
color: #bbb;
|
||||
}
|
||||
@@ -0,0 +1,135 @@
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import styles from './RTE.css';
|
||||
import cn from 'classnames';
|
||||
import ContentEditable from 'react-contenteditable';
|
||||
import Toolbar from './Toolbar';
|
||||
|
||||
class Editor extends React.Component {
|
||||
ref = null;
|
||||
handleRef = ref => (this.ref = ref);
|
||||
buttonsRef = {};
|
||||
|
||||
get contentEditable() {
|
||||
return this.ref.htmlEl;
|
||||
}
|
||||
|
||||
createButtonRefHandler(key) {
|
||||
return ref => {
|
||||
if (ref) {
|
||||
this.buttonsRef[key] = ref;
|
||||
} else {
|
||||
delete this.buttonsRef[key];
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
hasAncestor(tag) {
|
||||
const sel = window.getSelection();
|
||||
const range = sel.getRangeAt(0);
|
||||
let cur = range.startContainer;
|
||||
do {
|
||||
if (cur.nodeName === tag) {
|
||||
return true;
|
||||
}
|
||||
cur = cur.parentNode;
|
||||
} while (cur);
|
||||
return false;
|
||||
}
|
||||
|
||||
forEachButton(callback) {
|
||||
Object.keys(this.buttonsRef).map(k => callback(this.buttonsRef[k]));
|
||||
}
|
||||
|
||||
handleChange = evt => {
|
||||
this.props.onChange({
|
||||
text: this.ref.htmlEl.innerText,
|
||||
html: evt.target.value,
|
||||
});
|
||||
};
|
||||
|
||||
handleSelectionChange = () => {
|
||||
this.forEachButton(b => {
|
||||
b.onSelectionChange && b.onSelectionChange();
|
||||
});
|
||||
};
|
||||
|
||||
handleClick = () => {
|
||||
this.handleSelectionChange();
|
||||
};
|
||||
|
||||
handleKeyDown = () => {
|
||||
this.handleSelectionChange();
|
||||
};
|
||||
|
||||
handleKeyUp = () => {
|
||||
this.handleSelectionChange();
|
||||
};
|
||||
|
||||
handleKeyPress = e => {
|
||||
this.handleSelectionChange();
|
||||
if (e.key === 'Enter' && !e.shiftKey) {
|
||||
setTimeout(() => {
|
||||
document.execCommand('outdent');
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
renderButtons() {
|
||||
return this.props.buttons.map(b => {
|
||||
return React.cloneElement(b, {
|
||||
rte: this,
|
||||
ref: this.createButtonRefHandler(b.key),
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
render() {
|
||||
const {
|
||||
className,
|
||||
contentClassName,
|
||||
toolbarClassName,
|
||||
value,
|
||||
placeholder,
|
||||
inputId,
|
||||
} = this.props;
|
||||
|
||||
return (
|
||||
<div className={cn(styles.root, className)}>
|
||||
<Toolbar className={toolbarClassName}>{this.renderButtons()}</Toolbar>
|
||||
{!value && <div className={styles.placeholder}>{placeholder}</div>}
|
||||
<ContentEditable
|
||||
id={inputId}
|
||||
onClick={this.handleClick}
|
||||
onKeyPress={this.handleKeyPress}
|
||||
onKeyDown={this.handleKeyDown}
|
||||
onKeyUp={this.handleKeyDown}
|
||||
className={cn(contentClassName, styles.contentEditable)}
|
||||
ref={this.handleRef}
|
||||
html={value}
|
||||
disabled={false}
|
||||
onChange={this.handleChange}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Editor.defaultProps = {
|
||||
buttons: [],
|
||||
};
|
||||
|
||||
Editor.propTypes = {
|
||||
buttons: PropTypes.array,
|
||||
inputId: PropTypes.string,
|
||||
input: PropTypes.object,
|
||||
onChange: PropTypes.func,
|
||||
disabled: PropTypes.bool,
|
||||
className: PropTypes.string,
|
||||
contentClassName: PropTypes.string,
|
||||
toolbarClassName: PropTypes.string,
|
||||
placeholder: PropTypes.string,
|
||||
value: PropTypes.string,
|
||||
};
|
||||
|
||||
export default Editor;
|
||||
@@ -0,0 +1,27 @@
|
||||
import createToggle from '../factories/createToggle';
|
||||
import { hasAncestor } from '../utils';
|
||||
import bowser from 'bowser';
|
||||
|
||||
const execCommand = () => {
|
||||
if (hasAncestor('BLOCKQUOTE')) {
|
||||
document.execCommand('outdent');
|
||||
} else {
|
||||
if (bowser.msie) {
|
||||
document.execCommand('indent');
|
||||
} else {
|
||||
document.execCommand('formatBlock', false, 'blockquote');
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const syncState = () => {
|
||||
return hasAncestor('BLOCKQUOTE');
|
||||
};
|
||||
|
||||
const Blockquote = createToggle(execCommand, syncState);
|
||||
|
||||
Blockquote.defaultProps = {
|
||||
children: 'Blockquote',
|
||||
};
|
||||
|
||||
export default Blockquote;
|
||||
@@ -0,0 +1,12 @@
|
||||
import createToggle from '../factories/createToggle';
|
||||
|
||||
const execCommand = () => document.execCommand('bold');
|
||||
const syncState = () => document.queryCommandState('bold');
|
||||
|
||||
const Bold = createToggle(execCommand, syncState);
|
||||
|
||||
Bold.defaultProps = {
|
||||
children: 'Bold',
|
||||
};
|
||||
|
||||
export default Bold;
|
||||
@@ -0,0 +1,12 @@
|
||||
import createToggle from '../factories/createToggle';
|
||||
|
||||
const execCommand = () => document.execCommand('italic');
|
||||
const syncState = () => document.queryCommandState('italic');
|
||||
|
||||
const Italic = createToggle(execCommand, syncState);
|
||||
|
||||
Italic.defaultProps = {
|
||||
children: 'Italic',
|
||||
};
|
||||
|
||||
export default Italic;
|
||||
@@ -0,0 +1,3 @@
|
||||
export { default as Bold } from './Bold';
|
||||
export { default as Italic } from './Italic';
|
||||
export { default as Blockquote } from './Blockquote';
|
||||
@@ -0,0 +1,57 @@
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import Button from '../Button';
|
||||
|
||||
const createToggle = (execCommand, getCurrentState) => {
|
||||
class Toggle extends React.Component {
|
||||
state = {
|
||||
active: false,
|
||||
};
|
||||
|
||||
formatToggle = () => {
|
||||
execCommand();
|
||||
this.props.rte.contentEditable.focus();
|
||||
};
|
||||
|
||||
handleClick = () => {
|
||||
this.formatToggle();
|
||||
this.syncState();
|
||||
};
|
||||
|
||||
syncState = () => {
|
||||
if (this.state.active !== getCurrentState()) {
|
||||
this.setState(state => ({
|
||||
active: !state.active,
|
||||
}));
|
||||
}
|
||||
};
|
||||
|
||||
onSelectionChange() {
|
||||
this.syncState();
|
||||
}
|
||||
|
||||
render() {
|
||||
const { className, title, children } = this.props;
|
||||
return (
|
||||
<Button
|
||||
className={className}
|
||||
title={title}
|
||||
onClick={this.handleClick}
|
||||
active={this.state.active}
|
||||
>
|
||||
{children}
|
||||
</Button>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Toggle.propTypes = {
|
||||
rte: PropTypes.object,
|
||||
className: PropTypes.string,
|
||||
title: PropTypes.string,
|
||||
children: PropTypes.node,
|
||||
};
|
||||
return Toggle;
|
||||
};
|
||||
|
||||
export default createToggle;
|
||||
@@ -0,0 +1,12 @@
|
||||
export function hasAncestor(tag) {
|
||||
const sel = window.getSelection();
|
||||
const range = sel.getRangeAt(0);
|
||||
let cur = range.startContainer;
|
||||
do {
|
||||
if (cur.nodeName === tag) {
|
||||
return true;
|
||||
}
|
||||
cur = cur.parentNode;
|
||||
} while (cur);
|
||||
return false;
|
||||
}
|
||||
Reference in New Issue
Block a user