parent
cd07907392
commit
71251293e4
4 changed files with 129 additions and 71 deletions
|
@ -42,6 +42,10 @@ export default class MessageComposer extends React.Component {
|
|||
this.state = {
|
||||
autocompleteQuery: '',
|
||||
selection: null,
|
||||
selectionInfo: {
|
||||
style: [],
|
||||
blockType: null,
|
||||
},
|
||||
};
|
||||
|
||||
}
|
||||
|
@ -127,10 +131,11 @@ export default class MessageComposer extends React.Component {
|
|||
});
|
||||
}
|
||||
|
||||
onInputContentChanged(content: string, selection: {start: number, end: number}) {
|
||||
onInputContentChanged(content: string, selection: {start: number, end: number}, selectionInfo) {
|
||||
this.setState({
|
||||
autocompleteQuery: content,
|
||||
selection,
|
||||
selectionInfo,
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -155,6 +160,10 @@ export default class MessageComposer extends React.Component {
|
|||
}
|
||||
}
|
||||
|
||||
onFormatButtonClicked(name: "bold" | "italic" | "strike" | "quote" | "bullet" | "numbullet", event) {
|
||||
this.messageComposerInput.onFormatButtonClicked(name, event);
|
||||
}
|
||||
|
||||
render() {
|
||||
var me = this.props.room.getMember(MatrixClientPeg.get().credentials.userId);
|
||||
var uploadInputStyle = {display: 'none'};
|
||||
|
@ -207,6 +216,12 @@ export default class MessageComposer extends React.Component {
|
|||
</div>
|
||||
);
|
||||
|
||||
const formattingButton = (
|
||||
<img title="Text Formatting"
|
||||
src="img/button-text-formatting.svg"
|
||||
key="controls_formatting" />
|
||||
);
|
||||
|
||||
controls.push(
|
||||
<MessageComposerInput
|
||||
ref={c => this.messageComposerInput = c}
|
||||
|
@ -218,6 +233,7 @@ export default class MessageComposer extends React.Component {
|
|||
onDownArrow={this.onDownArrow}
|
||||
tabComplete={this.props.tabComplete} // used for old messagecomposerinput/tabcomplete
|
||||
onContentChanged={this.onInputContentChanged} />,
|
||||
formattingButton,
|
||||
uploadButton,
|
||||
hangupButton,
|
||||
callButton,
|
||||
|
@ -242,6 +258,17 @@ export default class MessageComposer extends React.Component {
|
|||
</div>;
|
||||
}
|
||||
|
||||
|
||||
const {style, blockType} = this.state.selectionInfo;
|
||||
const formatButtons = ["bold", "italic", "strike", "quote", "bullet", "numbullet"].map(
|
||||
name => {
|
||||
const active = style.includes(name) || blockType === name;
|
||||
const suffix = active ? '-o-n' : '';
|
||||
const onFormatButtonClicked = this.onFormatButtonClicked.bind(this, name);
|
||||
return <img className="mx_MessageComposer_format_button" title={name} onClick={onFormatButtonClicked} key={name} src={`img/button-text-${name}${suffix}.svg`} height="17" />;
|
||||
},
|
||||
);
|
||||
|
||||
return (
|
||||
<div className="mx_MessageComposer mx_fadable" style={{ opacity: this.props.opacity }}>
|
||||
{autoComplete}
|
||||
|
@ -250,6 +277,11 @@ export default class MessageComposer extends React.Component {
|
|||
{controls}
|
||||
</div>
|
||||
</div>
|
||||
{UserSettingsStore.isFeatureEnabled('rich_text_editor') ?
|
||||
<div className="mx_MessageComposer_formatbar">
|
||||
{formatButtons}
|
||||
</div> : null
|
||||
}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -32,6 +32,7 @@ import {Editor, EditorState, RichUtils, CompositeDecorator,
|
|||
getDefaultKeyBinding, KeyBindingUtil, ContentState} from 'draft-js';
|
||||
|
||||
import {stateToMarkdown} from 'draft-js-export-markdown';
|
||||
import classNames from 'classnames';
|
||||
|
||||
import MatrixClientPeg from '../../../MatrixClientPeg';
|
||||
import type {MatrixClient} from 'matrix-js-sdk/lib/matrix';
|
||||
|
@ -359,9 +360,12 @@ export default class MessageComposerInput extends React.Component {
|
|||
}
|
||||
|
||||
if (this.props.onContentChanged) {
|
||||
this.props.onContentChanged(editorState.getCurrentContent().getPlainText(),
|
||||
RichText.selectionStateToTextOffsets(editorState.getSelection(),
|
||||
editorState.getCurrentContent().getBlocksAsArray()));
|
||||
const textContent = editorState.getCurrentContent().getPlainText();
|
||||
const selection = RichText.selectionStateToTextOffsets(editorState.getSelection(),
|
||||
editorState.getCurrentContent().getBlocksAsArray());
|
||||
const selectionInfo = this.getSelectionInfo(editorState);
|
||||
|
||||
this.props.onContentChanged(textContent, selection, selectionInfo);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -418,6 +422,7 @@ export default class MessageComposerInput extends React.Component {
|
|||
this.setEditorState(newState);
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
|
@ -536,12 +541,79 @@ export default class MessageComposerInput extends React.Component {
|
|||
setTimeout(() => this.refs.editor.focus(), 50);
|
||||
}
|
||||
|
||||
render() {
|
||||
let className = "mx_MessageComposer_input";
|
||||
onFormatButtonClicked(name: "bold" | "italic" | "strike" | "quote" | "bullet" | "numbullet", e) {
|
||||
const style = {
|
||||
bold: 'BOLD',
|
||||
italic: 'ITALIC',
|
||||
strike: 'STRIKETHROUGH',
|
||||
}[name];
|
||||
|
||||
if (this.state.isRichtextEnabled) {
|
||||
className += " mx_MessageComposer_input_rte"; // placeholder indicator for RTE mode
|
||||
if (style) {
|
||||
e.preventDefault();
|
||||
this.setEditorState(RichUtils.toggleInlineStyle(this.state.editorState, style));
|
||||
} else {
|
||||
const blockType = {
|
||||
quote: 'blockquote',
|
||||
bullet: 'unordered-list-item',
|
||||
numbullet: 'ordered-list-item',
|
||||
}[name];
|
||||
|
||||
if (blockType) {
|
||||
e.preventDefault();
|
||||
this.setEditorState(RichUtils.toggleBlockType(this.state.editorState, blockType));
|
||||
} else {
|
||||
console.error(`Unknown formatting style "${name}", ignoring.`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* returns inline style and block type of current SelectionState so MessageComposer can render formatting
|
||||
buttons. */
|
||||
getSelectionInfo(editorState: EditorState) {
|
||||
const styleName = {
|
||||
BOLD: 'bold',
|
||||
ITALIC: 'italic',
|
||||
STRIKETHROUGH: 'strike',
|
||||
};
|
||||
|
||||
const originalStyle = editorState.getCurrentInlineStyle().toArray();
|
||||
const style = originalStyle
|
||||
.map(style => styleName[style] || null)
|
||||
.filter(styleName => !!styleName);
|
||||
|
||||
const blockName = {
|
||||
blockquote: 'quote',
|
||||
'unordered-list-item': 'bullet',
|
||||
'ordered-list-item': 'numbullet',
|
||||
};
|
||||
const originalBlockType = editorState.getCurrentContent()
|
||||
.getBlockForKey(editorState.getSelection().getStartKey())
|
||||
.getType();
|
||||
const blockType = blockName[originalBlockType] || null;
|
||||
|
||||
return {
|
||||
style,
|
||||
blockType,
|
||||
};
|
||||
}
|
||||
|
||||
render() {
|
||||
const {editorState} = this.state;
|
||||
|
||||
// From https://github.com/facebook/draft-js/blob/master/examples/rich/rich.html#L92
|
||||
// If the user changes block type before entering any text, we can
|
||||
// either style the placeholder or hide it.
|
||||
let hidePlaceholder = false;
|
||||
const contentState = editorState.getCurrentContent();
|
||||
if (!contentState.hasText()) {
|
||||
if (contentState.getBlockMap().first().getType() !== 'unstyled') {
|
||||
hidePlaceholder = true;
|
||||
}
|
||||
}
|
||||
|
||||
const className = classNames('mx_MessageComposer_input', {
|
||||
mx_MessageComposer_input_empty: hidePlaceholder,
|
||||
});
|
||||
|
||||
return (
|
||||
<div className={className}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue