unify setState() and onChange()

also make emoji autocomplete work again
also remove the onInputContentChanged prop
also slateify the onInputStateChanged prop
pull/21833/head
Matthew Hodgson 2018-05-13 23:34:00 +01:00
parent a247ea2f77
commit 7405b49b44
2 changed files with 42 additions and 139 deletions

View File

@ -35,7 +35,6 @@ export default class MessageComposer extends React.Component {
this.onUploadFileSelected = this.onUploadFileSelected.bind(this); this.onUploadFileSelected = this.onUploadFileSelected.bind(this);
this.uploadFiles = this.uploadFiles.bind(this); this.uploadFiles = this.uploadFiles.bind(this);
this.onVoiceCallClick = this.onVoiceCallClick.bind(this); this.onVoiceCallClick = this.onVoiceCallClick.bind(this);
this.onInputContentChanged = this.onInputContentChanged.bind(this);
this._onAutocompleteConfirm = this._onAutocompleteConfirm.bind(this); this._onAutocompleteConfirm = this._onAutocompleteConfirm.bind(this);
this.onToggleFormattingClicked = this.onToggleFormattingClicked.bind(this); this.onToggleFormattingClicked = this.onToggleFormattingClicked.bind(this);
this.onToggleMarkdownClicked = this.onToggleMarkdownClicked.bind(this); this.onToggleMarkdownClicked = this.onToggleMarkdownClicked.bind(this);
@ -44,13 +43,10 @@ export default class MessageComposer extends React.Component {
this._onRoomViewStoreUpdate = this._onRoomViewStoreUpdate.bind(this); this._onRoomViewStoreUpdate = this._onRoomViewStoreUpdate.bind(this);
this.state = { this.state = {
autocompleteQuery: '',
selection: null,
inputState: { inputState: {
style: [], marks: [],
blockType: null, blockType: null,
isRichtextEnabled: SettingsStore.getValue('MessageComposerInput.isRichTextEnabled'), isRichtextEnabled: SettingsStore.getValue('MessageComposerInput.isRichTextEnabled'),
wordCount: 0,
}, },
showFormatting: SettingsStore.getValue('MessageComposer.showFormatting'), showFormatting: SettingsStore.getValue('MessageComposer.showFormatting'),
isQuoting: Boolean(RoomViewStore.getQuotingEvent()), isQuoting: Boolean(RoomViewStore.getQuotingEvent()),
@ -209,13 +205,6 @@ export default class MessageComposer extends React.Component {
// this._startCallApp(true); // this._startCallApp(true);
} }
onInputContentChanged(content: string, selection: {start: number, end: number}) {
this.setState({
autocompleteQuery: content,
selection,
});
}
onInputStateChanged(inputState) { onInputStateChanged(inputState) {
this.setState({inputState}); this.setState({inputState});
} }
@ -348,7 +337,6 @@ export default class MessageComposer extends React.Component {
room={this.props.room} room={this.props.room}
placeholder={placeholderText} placeholder={placeholderText}
onFilesPasted={this.uploadFiles} onFilesPasted={this.uploadFiles}
onContentChanged={this.onInputContentChanged}
onInputStateChanged={this.onInputStateChanged} />, onInputStateChanged={this.onInputStateChanged} />,
formattingButton, formattingButton,
stickerpickerButton, stickerpickerButton,
@ -365,10 +353,10 @@ export default class MessageComposer extends React.Component {
); );
} }
const {style, blockType} = this.state.inputState; const {marks, blockType} = this.state.inputState;
const formatButtons = ["bold", "italic", "strike", "underline", "code", "quote", "bullet", "numbullet"].map( const formatButtons = ["bold", "italic", "strike", "underline", "code", "quote", "bullet", "numbullet"].map(
(name) => { (name) => {
const active = style.includes(name) || blockType === name; const active = marks.includes(name) || blockType === name;
const suffix = active ? '-o-n' : ''; const suffix = active ? '-o-n' : '';
const onFormatButtonClicked = this.onFormatButtonClicked.bind(this, name); const onFormatButtonClicked = this.onFormatButtonClicked.bind(this, name);
const className = 'mx_MessageComposer_format_button mx_filterFlipColor'; const className = 'mx_MessageComposer_format_button mx_filterFlipColor';

View File

@ -184,23 +184,6 @@ export default class MessageComposerInput extends React.Component {
this.direction = ''; this.direction = '';
} }
/*
findPillEntities(contentState: ContentState, contentBlock: ContentBlock, callback) {
contentBlock.findEntityRanges(
(character) => {
const entityKey = character.getEntity();
return (
entityKey !== null &&
(
contentState.getEntity(entityKey).getType() === 'LINK' ||
contentState.getEntity(entityKey).getType() === ENTITY_TYPES.AT_ROOM_PILL
)
);
}, callback,
);
}
*/
/* /*
* "Does the right thing" to create an Editor value, based on: * "Does the right thing" to create an Editor value, based on:
* - whether we've got rich text mode enabled * - whether we've got rich text mode enabled
@ -226,14 +209,12 @@ export default class MessageComposerInput extends React.Component {
} }
componentWillUpdate(nextProps, nextState) { componentWillUpdate(nextProps, nextState) {
/*
// this is dirty, but moving all this state to MessageComposer is dirtier // this is dirty, but moving all this state to MessageComposer is dirtier
if (this.props.onInputStateChanged && nextState !== this.state) { if (this.props.onInputStateChanged && nextState !== this.state) {
const state = this.getSelectionInfo(nextState.editorState); const state = this.getSelectionInfo(nextState.editorState);
state.isRichtextEnabled = nextState.isRichtextEnabled; state.isRichtextEnabled = nextState.isRichtextEnabled;
this.props.onInputStateChanged(state); this.props.onInputStateChanged(state);
} }
*/
} }
onAction = (payload) => { onAction = (payload) => {
@ -375,6 +356,19 @@ export default class MessageComposerInput extends React.Component {
} }
} }
if (editorState.document.getFirstText().text !== '') {
this.onTypingActivity();
} else {
this.onFinishedTyping();
}
/*
// XXX: what was this ever doing?
if (!state.hasOwnProperty('originalEditorState')) {
state.originalEditorState = null;
}
*/
/* /*
editorState = RichText.attachImmutableEntitiesToEmoji(editorState); editorState = RichText.attachImmutableEntitiesToEmoji(editorState);
@ -405,6 +399,10 @@ export default class MessageComposerInput extends React.Component {
// Reset selection // Reset selection
editorState = EditorState.forceSelection(editorState, currentSelection); editorState = EditorState.forceSelection(editorState, currentSelection);
} }
*/
const text = editorState.startText.text;
const currentStartOffset = editorState.startOffset;
// Automatic replacement of plaintext emoji to Unicode emoji // Automatic replacement of plaintext emoji to Unicode emoji
if (SettingsStore.getValue('MessageComposerInput.autoReplaceEmoji')) { if (SettingsStore.getValue('MessageComposerInput.autoReplaceEmoji')) {
@ -415,61 +413,16 @@ export default class MessageComposerInput extends React.Component {
const emojiUc = asciiList[emojiMatch[1]]; const emojiUc = asciiList[emojiMatch[1]];
// hex unicode -> shortname -> actual unicode // hex unicode -> shortname -> actual unicode
const unicodeEmoji = shortnameToUnicode(EMOJI_UNICODE_TO_SHORTNAME[emojiUc]); const unicodeEmoji = shortnameToUnicode(EMOJI_UNICODE_TO_SHORTNAME[emojiUc]);
const newContentState = Modifier.replaceText(
editorState.getCurrentContent(), const range = Range.create({
currentSelection.merge({ anchorKey: editorState.selection.startKey,
anchorOffset: currentStartOffset - emojiMatch[1].length - 1, anchorOffset: currentStartOffset - emojiMatch[1].length - 1,
focusKey: editorState.selection.startKey,
focusOffset: currentStartOffset, focusOffset: currentStartOffset,
}),
unicodeEmoji,
);
editorState = EditorState.push(
editorState,
newContentState,
'insert-characters',
);
editorState = EditorState.forceSelection(editorState, newContentState.getSelectionAfter());
}
}
*/
/* Since a modification was made, set originalEditorState to null, since newState is now our original */
this.setState({
editorState,
originalEditorState: null,
}); });
}; change = change.insertTextAtRange(range, unicodeEmoji);
editorState = change.value;
/**
* We're overriding setState here because it's the most convenient way to monitor changes to the editorState.
* Doing it using a separate function that calls setState is a possibility (and was the old approach), but that
* approach requires a callback and an extra setState whenever trying to set multiple state properties.
*
* @param state
* @param callback
*/
setState(state, callback) {
/*
if (state.editorState != null) {
state.editorState = RichText.attachImmutableEntitiesToEmoji(
state.editorState);
// Hide the autocomplete if the cursor location changes but the plaintext
// content stays the same. We don't hide if the pt has changed because the
// autocomplete will probably have different completions to show.
if (
!state.editorState.getSelection().equals(
this.state.editorState.getSelection(),
)
&& state.editorState.getCurrentContent().getPlainText() ===
this.state.editorState.getCurrentContent().getPlainText()
) {
this.autocomplete.hide();
} }
if (state.editorState.getCurrentContent().hasText()) {
this.onTypingActivity();
} else {
this.onFinishedTyping();
} }
// Record the editor state for this room so that it can be retrieved after // Record the editor state for this room so that it can be retrieved after
@ -477,29 +430,15 @@ export default class MessageComposerInput extends React.Component {
dis.dispatch({ dis.dispatch({
action: 'editor_state', action: 'editor_state',
room_id: this.props.room.roomId, room_id: this.props.room.roomId,
editor_state: state.editorState.getCurrentContent(), editor_state: editorState,
}); });
if (!state.hasOwnProperty('originalEditorState')) { /* Since a modification was made, set originalEditorState to null, since newState is now our original */
state.originalEditorState = null; this.setState({
} editorState,
} originalEditorState: null,
*/
super.setState(state, () => {
if (callback != null) {
callback();
}
/*
const textContent = this.state.editorState.getCurrentContent().getPlainText();
const selection = RichText.selectionStateToTextOffsets(
this.state.editorState.getSelection(),
this.state.editorState.getCurrentContent().getBlocksAsArray());
if (this.props.onContentChanged) {
this.props.onContentChanged(textContent, selection);
}
*/
}); });
} };
enableRichtext(enabled: boolean) { enableRichtext(enabled: boolean) {
if (enabled === this.state.isRichtextEnabled) return; if (enabled === this.state.isRichtextEnabled) return;
@ -1133,36 +1072,12 @@ export default class MessageComposerInput extends React.Component {
/* returns inline style and block type of current SelectionState so MessageComposer can render formatting /* returns inline style and block type of current SelectionState so MessageComposer can render formatting
buttons. */ buttons. */
getSelectionInfo(editorState: Value) { getSelectionInfo(editorState: Value) {
return {};
/*
const styleName = {
BOLD: _td('bold'),
ITALIC: _td('italic'),
STRIKETHROUGH: _td('strike'),
UNDERLINE: _td('underline'),
};
const originalStyle = editorState.getCurrentInlineStyle().toArray();
const style = originalStyle
.map((style) => styleName[style] || null)
.filter((styleName) => !!styleName);
const blockName = {
'code-block': _td('code'),
'blockquote': _td('quote'),
'unordered-list-item': _td('bullet'),
'ordered-list-item': _td('numbullet'),
};
const originalBlockType = editorState.getCurrentContent()
.getBlockForKey(editorState.getSelection().getStartKey())
.getType();
const blockType = blockName[originalBlockType] || null;
return { return {
style, marks: editorState.activeMarks,
blockType, // XXX: shouldn't we return all the types of blocks in the current selection,
// not just the anchor?
blockType: editorState.anchorBlock.type,
}; };
*/
} }
getAutocompleteQuery(editorState: Value) { getAutocompleteQuery(editorState: Value) {