Update to new version of slate
Lots of fixes here as a lot of the API has changed (eg. anchorKey / offsetKey are now anchor.key and offset.key, and collapseFocusToThing is moveFocusToThing). Also changes the ref to a function (sorry for lumping this into the same PR). Hopefully will fix https://github.com/vector-im/riot-web/issues/7105
This commit is contained in:
parent
88f969acdf
commit
c1f51a76dd
2 changed files with 99 additions and 69 deletions
|
@ -88,10 +88,10 @@
|
||||||
"react-gemini-scrollbar": "matrix-org/react-gemini-scrollbar#5e97aef",
|
"react-gemini-scrollbar": "matrix-org/react-gemini-scrollbar#5e97aef",
|
||||||
"resize-observer-polyfill": "^1.5.0",
|
"resize-observer-polyfill": "^1.5.0",
|
||||||
"sanitize-html": "^1.18.4",
|
"sanitize-html": "^1.18.4",
|
||||||
"slate": "0.34.7",
|
"slate": "^0.41.2",
|
||||||
"slate-html-serializer": "^0.6.1",
|
"slate-html-serializer": "^0.6.1",
|
||||||
"slate-md-serializer": "matrix-org/slate-md-serializer#f7c4ad3",
|
"slate-md-serializer": "matrix-org/slate-md-serializer#f7c4ad3",
|
||||||
"slate-react": "^0.12.4",
|
"slate-react": "^0.18.10",
|
||||||
"text-encoding-utf-8": "^1.0.1",
|
"text-encoding-utf-8": "^1.0.1",
|
||||||
"url": "^0.11.0",
|
"url": "^0.11.0",
|
||||||
"velocity-vector": "vector-im/velocity#059e3b2",
|
"velocity-vector": "vector-im/velocity#059e3b2",
|
||||||
|
|
|
@ -106,6 +106,17 @@ const MARK_TAGS = {
|
||||||
s: 'deleted', // deprecated
|
s: 'deleted', // deprecated
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const SLATE_SCHEMA = {
|
||||||
|
inlines: {
|
||||||
|
pill: {
|
||||||
|
isVoid: true,
|
||||||
|
},
|
||||||
|
emoji: {
|
||||||
|
isVoid: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
function onSendMessageFailed(err, room) {
|
function onSendMessageFailed(err, room) {
|
||||||
// XXX: temporary logging to try to diagnose
|
// XXX: temporary logging to try to diagnose
|
||||||
// https://github.com/vector-im/riot-web/issues/3148
|
// https://github.com/vector-im/riot-web/issues/3148
|
||||||
|
@ -116,10 +127,10 @@ function onSendMessageFailed(err, room) {
|
||||||
}
|
}
|
||||||
|
|
||||||
function rangeEquals(a: Range, b: Range): boolean {
|
function rangeEquals(a: Range, b: Range): boolean {
|
||||||
return (a.anchorKey === b.anchorKey
|
return (a.anchor.key === b.anchor.key
|
||||||
&& a.anchorOffset === b.anchorOffset
|
&& a.anchor.offset === b.anchorOffset
|
||||||
&& a.focusKey === b.focusKey
|
&& a.focus.key === b.focusKey
|
||||||
&& a.focusOffset === b.focusOffset
|
&& a.focus.offset === b.focusOffset
|
||||||
&& a.isFocused === b.isFocused
|
&& a.isFocused === b.isFocused
|
||||||
&& a.isBackward === b.isBackward);
|
&& a.isBackward === b.isBackward);
|
||||||
}
|
}
|
||||||
|
@ -239,7 +250,6 @@ export default class MessageComposerInput extends React.Component {
|
||||||
completion: el.innerText,
|
completion: el.innerText,
|
||||||
completionId: m[1],
|
completionId: m[1],
|
||||||
},
|
},
|
||||||
isVoid: true,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
|
@ -345,8 +355,12 @@ export default class MessageComposerInput extends React.Component {
|
||||||
dis.unregister(this.dispatcherRef);
|
dis.unregister(this.dispatcherRef);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
_collectEditor = (e) => {
|
||||||
|
this._editor = e;
|
||||||
|
}
|
||||||
|
|
||||||
onAction = (payload) => {
|
onAction = (payload) => {
|
||||||
const editor = this.refs.editor;
|
const editor = this._editor;
|
||||||
let editorState = this.state.editorState;
|
let editorState = this.state.editorState;
|
||||||
|
|
||||||
switch (payload.action) {
|
switch (payload.action) {
|
||||||
|
@ -402,10 +416,14 @@ export default class MessageComposerInput extends React.Component {
|
||||||
}
|
}
|
||||||
|
|
||||||
// XXX: this is to bring back the focus in a sane place and add a paragraph after it
|
// XXX: this is to bring back the focus in a sane place and add a paragraph after it
|
||||||
change = change.select({
|
change = change.select(Range.create({
|
||||||
anchorKey: quote.key,
|
anchor: {
|
||||||
focusKey: quote.key,
|
key: quote.key,
|
||||||
}).collapseToEndOfBlock().insertBlock(Block.create(DEFAULT_NODE)).focus();
|
},
|
||||||
|
focus: {
|
||||||
|
key: quote.key,
|
||||||
|
},
|
||||||
|
})).collapseToEndOfBlock().insertBlock(Block.create(DEFAULT_NODE)).focus();
|
||||||
|
|
||||||
this.onChange(change);
|
this.onChange(change);
|
||||||
} else {
|
} else {
|
||||||
|
@ -497,15 +515,15 @@ export default class MessageComposerInput extends React.Component {
|
||||||
|
|
||||||
if (this.direction !== '') {
|
if (this.direction !== '') {
|
||||||
const focusedNode = editorState.focusInline || editorState.focusText;
|
const focusedNode = editorState.focusInline || editorState.focusText;
|
||||||
if (focusedNode.isVoid) {
|
if (editorState.schema.isVoid(focusedNode)) {
|
||||||
// XXX: does this work in RTL?
|
// XXX: does this work in RTL?
|
||||||
const edge = this.direction === 'Previous' ? 'End' : 'Start';
|
const edge = this.direction === 'Previous' ? 'End' : 'Start';
|
||||||
if (editorState.isCollapsed) {
|
if (editorState.selection.isCollapsed) {
|
||||||
change = change[`collapseTo${ edge }Of${ this.direction }Text`]();
|
change = change[`moveTo${ edge }Of${ this.direction }Text`]();
|
||||||
} else {
|
} else {
|
||||||
const block = this.direction === 'Previous' ? editorState.previousText : editorState.nextText;
|
const block = this.direction === 'Previous' ? editorState.previousText : editorState.nextText;
|
||||||
if (block) {
|
if (block) {
|
||||||
change = change[`moveFocusTo${ edge }Of`](block);
|
change = change[`moveFocusTo${ edge }OfNode`](block);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
editorState = change.value;
|
editorState = change.value;
|
||||||
|
@ -522,7 +540,7 @@ export default class MessageComposerInput extends React.Component {
|
||||||
this.autocomplete.hide();
|
this.autocomplete.hide();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!editorState.document.isEmpty) {
|
if (Plain.serialize(editorState) !== '') {
|
||||||
this.onTypingActivity();
|
this.onTypingActivity();
|
||||||
} else {
|
} else {
|
||||||
this.onFinishedTyping();
|
this.onFinishedTyping();
|
||||||
|
@ -543,10 +561,14 @@ export default class MessageComposerInput extends React.Component {
|
||||||
const unicodeEmoji = shortnameToUnicode(EMOJI_UNICODE_TO_SHORTNAME[emojiUc]);
|
const unicodeEmoji = shortnameToUnicode(EMOJI_UNICODE_TO_SHORTNAME[emojiUc]);
|
||||||
|
|
||||||
const range = Range.create({
|
const range = Range.create({
|
||||||
anchorKey: editorState.selection.startKey,
|
anchor: {
|
||||||
anchorOffset: currentStartOffset - emojiMatch[1].length - 1,
|
key: editorState.selection.startKey,
|
||||||
focusKey: editorState.selection.startKey,
|
offset: currentStartOffset - emojiMatch[1].length - 1,
|
||||||
focusOffset: currentStartOffset - 1,
|
},
|
||||||
|
focus: {
|
||||||
|
key: editorState.selection.startKey,
|
||||||
|
offset: currentStartOffset - 1,
|
||||||
|
},
|
||||||
});
|
});
|
||||||
change = change.insertTextAtRange(range, unicodeEmoji);
|
change = change.insertTextAtRange(range, unicodeEmoji);
|
||||||
editorState = change.value;
|
editorState = change.value;
|
||||||
|
@ -560,15 +582,18 @@ export default class MessageComposerInput extends React.Component {
|
||||||
let match;
|
let match;
|
||||||
while ((match = EMOJI_REGEX.exec(node.text)) !== null) {
|
while ((match = EMOJI_REGEX.exec(node.text)) !== null) {
|
||||||
const range = Range.create({
|
const range = Range.create({
|
||||||
anchorKey: node.key,
|
anchor: {
|
||||||
anchorOffset: match.index,
|
key: node.key,
|
||||||
focusKey: node.key,
|
offset: match.index,
|
||||||
focusOffset: match.index + match[0].length,
|
},
|
||||||
|
focus: {
|
||||||
|
key: node.key,
|
||||||
|
offset: match.index + match[0].length,
|
||||||
|
},
|
||||||
});
|
});
|
||||||
const inline = Inline.create({
|
const inline = Inline.create({
|
||||||
type: 'emoji',
|
type: 'emoji',
|
||||||
data: { emojiUnicode: match[0] },
|
data: { emojiUnicode: match[0] },
|
||||||
isVoid: true,
|
|
||||||
});
|
});
|
||||||
change = change.insertInlineAtRange(range, inline);
|
change = change.insertInlineAtRange(range, inline);
|
||||||
editorState = change.value;
|
editorState = change.value;
|
||||||
|
@ -580,10 +605,10 @@ export default class MessageComposerInput extends React.Component {
|
||||||
// emoji picker can leave the selection stuck in the emoji's
|
// emoji picker can leave the selection stuck in the emoji's
|
||||||
// child text. This seems to happen due to selection getting
|
// child text. This seems to happen due to selection getting
|
||||||
// moved in the normalisation phase after calculating these changes
|
// moved in the normalisation phase after calculating these changes
|
||||||
if (editorState.anchorKey &&
|
if (editorState.selection.anchor.key &&
|
||||||
editorState.document.getParent(editorState.anchorKey).type === 'emoji')
|
editorState.document.getParent(editorState.selection.anchor.key).type === 'emoji')
|
||||||
{
|
{
|
||||||
change = change.collapseToStartOfNextText();
|
change = change.moveToStartOfNextText();
|
||||||
editorState = change.value;
|
editorState = change.value;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -673,7 +698,7 @@ export default class MessageComposerInput extends React.Component {
|
||||||
editorState: this.createEditorState(enabled, editorState),
|
editorState: this.createEditorState(enabled, editorState),
|
||||||
isRichTextEnabled: enabled,
|
isRichTextEnabled: enabled,
|
||||||
}, ()=>{
|
}, ()=>{
|
||||||
this.refs.editor.focus();
|
this._editor.focus();
|
||||||
});
|
});
|
||||||
|
|
||||||
SettingsStore.setValue("MessageComposerInput.isRichTextEnabled", null, SettingLevel.ACCOUNT, enabled);
|
SettingsStore.setValue("MessageComposerInput.isRichTextEnabled", null, SettingLevel.ACCOUNT, enabled);
|
||||||
|
@ -760,7 +785,9 @@ export default class MessageComposerInput extends React.Component {
|
||||||
// drop a point in history so the user can undo a word
|
// drop a point in history so the user can undo a word
|
||||||
// XXX: this seems nasty but adding to history manually seems a no-go
|
// XXX: this seems nasty but adding to history manually seems a no-go
|
||||||
ev.preventDefault();
|
ev.preventDefault();
|
||||||
return change.setOperationFlag("skip", false).setOperationFlag("merge", false).insertText(ev.key);
|
return change.withoutMerging(() => {
|
||||||
|
change.insertText(ev.key);
|
||||||
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
onBackspace = (ev: KeyboardEvent, change: Change): Change => {
|
onBackspace = (ev: KeyboardEvent, change: Change): Change => {
|
||||||
|
@ -771,23 +798,25 @@ export default class MessageComposerInput extends React.Component {
|
||||||
const { editorState } = this.state;
|
const { editorState } = this.state;
|
||||||
|
|
||||||
// Allow Ctrl/Cmd-Backspace when focus starts at the start of the composer (e.g select-all)
|
// Allow Ctrl/Cmd-Backspace when focus starts at the start of the composer (e.g select-all)
|
||||||
// for some reason if slate sees you Ctrl-backspace and your anchorOffset=0 it just resets your focus
|
// for some reason if slate sees you Ctrl-backspace and your anchor.offset=0 it just resets your focus
|
||||||
if (!editorState.isCollapsed && editorState.anchorOffset === 0) {
|
// XXX: Doing this now seems to put slate into a broken state, and it didn't appear to be doing
|
||||||
|
// what it claims to do on the old version of slate anyway...
|
||||||
|
/*if (!editorState.isCollapsed && editorState.selection.anchor.offset === 0) {
|
||||||
return change.delete();
|
return change.delete();
|
||||||
}
|
}*/
|
||||||
|
|
||||||
if (this.state.isRichTextEnabled) {
|
if (this.state.isRichTextEnabled) {
|
||||||
// let backspace exit lists
|
// let backspace exit lists
|
||||||
const isList = this.hasBlock('list-item');
|
const isList = this.hasBlock('list-item');
|
||||||
|
|
||||||
if (isList && editorState.anchorOffset == 0) {
|
if (isList && editorState.selection.anchor.offset == 0) {
|
||||||
change
|
change
|
||||||
.setBlocks(DEFAULT_NODE)
|
.setBlocks(DEFAULT_NODE)
|
||||||
.unwrapBlock('bulleted-list')
|
.unwrapBlock('bulleted-list')
|
||||||
.unwrapBlock('numbered-list');
|
.unwrapBlock('numbered-list');
|
||||||
return change;
|
return change;
|
||||||
}
|
}
|
||||||
else if (editorState.anchorOffset == 0 && editorState.isCollapsed) {
|
else if (editorState.selection.anchor.offset == 0 && editorState.isCollapsed) {
|
||||||
// turn blocks back into paragraphs
|
// turn blocks back into paragraphs
|
||||||
if ((this.hasBlock('block-quote') ||
|
if ((this.hasBlock('block-quote') ||
|
||||||
this.hasBlock('heading1') ||
|
this.hasBlock('heading1') ||
|
||||||
|
@ -803,7 +832,7 @@ export default class MessageComposerInput extends React.Component {
|
||||||
|
|
||||||
// remove paragraphs entirely if they're nested
|
// remove paragraphs entirely if they're nested
|
||||||
const parent = editorState.document.getParent(editorState.anchorBlock.key);
|
const parent = editorState.document.getParent(editorState.anchorBlock.key);
|
||||||
if (editorState.anchorOffset == 0 &&
|
if (editorState.selection.anchor.offset == 0 &&
|
||||||
this.hasBlock('paragraph') &&
|
this.hasBlock('paragraph') &&
|
||||||
parent.nodes.size == 1 &&
|
parent.nodes.size == 1 &&
|
||||||
parent.object !== 'document')
|
parent.object !== 'document')
|
||||||
|
@ -942,8 +971,8 @@ export default class MessageComposerInput extends React.Component {
|
||||||
const collapseAndOffsetSelection = (selection, offset) => {
|
const collapseAndOffsetSelection = (selection, offset) => {
|
||||||
const key = selection.endKey();
|
const key = selection.endKey();
|
||||||
return new Range({
|
return new Range({
|
||||||
anchorKey: key, anchorOffset: offset,
|
anchorKey: key, anchor.offset: offset,
|
||||||
focusKey: key, focusOffset: offset,
|
focus.key: key, focus.offset: offset,
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -1000,18 +1029,16 @@ export default class MessageComposerInput extends React.Component {
|
||||||
.insertFragment(fragment.document);
|
.insertFragment(fragment.document);
|
||||||
} else {
|
} else {
|
||||||
// in MD mode we don't want the rich content pasted as the magic was annoying people so paste plain
|
// in MD mode we don't want the rich content pasted as the magic was annoying people so paste plain
|
||||||
return change
|
return change.withoutMerging(() => {
|
||||||
.setOperationFlag("skip", false)
|
change.insertText(transfer.text);
|
||||||
.setOperationFlag("merge", false)
|
});
|
||||||
.insertText(transfer.text);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
case 'text':
|
case 'text':
|
||||||
// don't skip/merge so that multiple consecutive pastes can be undone individually
|
// don't skip/merge so that multiple consecutive pastes can be undone individually
|
||||||
return change
|
return change.withoutMerging(() => {
|
||||||
.setOperationFlag("skip", false)
|
change.insertText(transfer.text);
|
||||||
.setOperationFlag("merge", false)
|
});
|
||||||
.insertText(transfer.text);
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -1066,7 +1093,7 @@ export default class MessageComposerInput extends React.Component {
|
||||||
this.setState({
|
this.setState({
|
||||||
editorState: this.createEditorState(),
|
editorState: this.createEditorState(),
|
||||||
}, ()=>{
|
}, ()=>{
|
||||||
this.refs.editor.focus();
|
this._editor.focus();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
if (cmd.promise) {
|
if (cmd.promise) {
|
||||||
|
@ -1196,7 +1223,7 @@ export default class MessageComposerInput extends React.Component {
|
||||||
|
|
||||||
this.setState({
|
this.setState({
|
||||||
editorState: this.createEditorState(),
|
editorState: this.createEditorState(),
|
||||||
}, ()=>{ this.refs.editor.focus() });
|
}, ()=>{ this._editor.focus() });
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
};
|
};
|
||||||
|
@ -1216,9 +1243,9 @@ export default class MessageComposerInput extends React.Component {
|
||||||
|
|
||||||
// and we must be at the edge of the document (up=start, down=end)
|
// and we must be at the edge of the document (up=start, down=end)
|
||||||
if (up) {
|
if (up) {
|
||||||
if (!selection.isAtStartOf(document)) return;
|
if (!selection.anchor.isAtStartOfNode(document)) return;
|
||||||
} else {
|
} else {
|
||||||
if (!selection.isAtEndOf(document)) return;
|
if (!selection.anchor.isAtEndOfNode(document)) return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const selected = this.selectHistory(up);
|
const selected = this.selectHistory(up);
|
||||||
|
@ -1275,7 +1302,7 @@ export default class MessageComposerInput extends React.Component {
|
||||||
this.suppressAutoComplete = true;
|
this.suppressAutoComplete = true;
|
||||||
|
|
||||||
this.setState({ editorState }, ()=>{
|
this.setState({ editorState }, ()=>{
|
||||||
this.refs.editor.focus();
|
this._editor.focus();
|
||||||
});
|
});
|
||||||
return true;
|
return true;
|
||||||
};
|
};
|
||||||
|
@ -1345,15 +1372,11 @@ export default class MessageComposerInput extends React.Component {
|
||||||
inline = Inline.create({
|
inline = Inline.create({
|
||||||
type: 'pill',
|
type: 'pill',
|
||||||
data: { completion, completionId, href },
|
data: { completion, completionId, href },
|
||||||
// we can't put text in here otherwise the editor tries to select it
|
|
||||||
isVoid: true,
|
|
||||||
});
|
});
|
||||||
} else if (completion === '@room') {
|
} else if (completion === '@room') {
|
||||||
inline = Inline.create({
|
inline = Inline.create({
|
||||||
type: 'pill',
|
type: 'pill',
|
||||||
data: { completion, completionId },
|
data: { completion, completionId },
|
||||||
// we can't put text in here otherwise the editor tries to select it
|
|
||||||
isVoid: true,
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1361,8 +1384,9 @@ export default class MessageComposerInput extends React.Component {
|
||||||
|
|
||||||
if (range) {
|
if (range) {
|
||||||
const change = editorState.change()
|
const change = editorState.change()
|
||||||
.collapseToAnchor()
|
.moveToAnchor()
|
||||||
.moveOffsetsTo(range.start, range.end)
|
.moveAnchorTo(range.start)
|
||||||
|
.moveFocusTo(range.end)
|
||||||
.focus();
|
.focus();
|
||||||
editorState = change.value;
|
editorState = change.value;
|
||||||
}
|
}
|
||||||
|
@ -1433,6 +1457,7 @@ export default class MessageComposerInput extends React.Component {
|
||||||
room={this.props.room}
|
room={this.props.room}
|
||||||
shouldShowPillAvatar={shouldShowPillAvatar}
|
shouldShowPillAvatar={shouldShowPillAvatar}
|
||||||
isSelected={isSelected}
|
isSelected={isSelected}
|
||||||
|
{...attributes}
|
||||||
/>;
|
/>;
|
||||||
}
|
}
|
||||||
else if (Pill.isPillUrl(url)) {
|
else if (Pill.isPillUrl(url)) {
|
||||||
|
@ -1441,12 +1466,14 @@ export default class MessageComposerInput extends React.Component {
|
||||||
room={this.props.room}
|
room={this.props.room}
|
||||||
shouldShowPillAvatar={shouldShowPillAvatar}
|
shouldShowPillAvatar={shouldShowPillAvatar}
|
||||||
isSelected={isSelected}
|
isSelected={isSelected}
|
||||||
|
{...attributes}
|
||||||
/>;
|
/>;
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
const { text } = node;
|
const { text } = node;
|
||||||
return <a href={url} {...props.attributes}>
|
return <a href={url} {...props.attributes}>
|
||||||
{ text }
|
{ text }
|
||||||
|
{...attributes}
|
||||||
</a>;
|
</a>;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1458,7 +1485,9 @@ export default class MessageComposerInput extends React.Component {
|
||||||
const className = classNames('mx_emojione', {
|
const className = classNames('mx_emojione', {
|
||||||
mx_emojione_selected: isSelected
|
mx_emojione_selected: isSelected
|
||||||
});
|
});
|
||||||
return <img className={ className } src={ uri } title={ shortname } alt={ emojiUnicode }/>;
|
const style = {};
|
||||||
|
if (props.selected) style.border = '1px solid blue';
|
||||||
|
return <img className={ className } src={ uri } title={ shortname } alt={ emojiUnicode } style={style}/>;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
@ -1486,7 +1515,7 @@ export default class MessageComposerInput extends React.Component {
|
||||||
// of focusing it doesn't then cancel the format button being pressed
|
// of focusing it doesn't then cancel the format button being pressed
|
||||||
// FIXME: can we just tell handleKeyCommand's change to invoke .focus()?
|
// FIXME: can we just tell handleKeyCommand's change to invoke .focus()?
|
||||||
if (document.activeElement && document.activeElement.className !== 'mx_MessageComposer_editor') {
|
if (document.activeElement && document.activeElement.className !== 'mx_MessageComposer_editor') {
|
||||||
this.refs.editor.focus();
|
this._editor.focus();
|
||||||
setTimeout(()=>{
|
setTimeout(()=>{
|
||||||
this.handleKeyCommand(name);
|
this.handleKeyCommand(name);
|
||||||
}, 500); // can't find any callback to hook this to. onFocus and onChange and willComponentUpdate fire too early.
|
}, 500); // can't find any callback to hook this to. onFocus and onChange and willComponentUpdate fire too early.
|
||||||
|
@ -1503,8 +1532,8 @@ export default class MessageComposerInput extends React.Component {
|
||||||
// This avoids us having to serialize the whole thing to plaintext and convert
|
// This avoids us having to serialize the whole thing to plaintext and convert
|
||||||
// selection offsets in & out of the plaintext domain.
|
// selection offsets in & out of the plaintext domain.
|
||||||
|
|
||||||
if (editorState.selection.anchorKey) {
|
if (editorState.selection.anchor.key) {
|
||||||
return editorState.document.getDescendant(editorState.selection.anchorKey).text;
|
return editorState.document.getDescendant(editorState.selection.anchor.key).text;
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
return '';
|
return '';
|
||||||
|
@ -1518,16 +1547,16 @@ export default class MessageComposerInput extends React.Component {
|
||||||
const firstGrandChild = firstChild && firstChild.nodes.get(0);
|
const firstGrandChild = firstChild && firstChild.nodes.get(0);
|
||||||
beginning = (firstChild && firstGrandChild &&
|
beginning = (firstChild && firstGrandChild &&
|
||||||
firstChild.object === 'block' && firstGrandChild.object === 'text' &&
|
firstChild.object === 'block' && firstGrandChild.object === 'text' &&
|
||||||
editorState.selection.anchorKey === firstGrandChild.key);
|
editorState.selection.anchor.key === firstGrandChild.key);
|
||||||
|
|
||||||
// return a character range suitable for handing to an autocomplete provider.
|
// return a character range suitable for handing to an autocomplete provider.
|
||||||
// the range is relative to the anchor of the current editor selection.
|
// the range is relative to the anchor of the current editor selection.
|
||||||
// if the selection spans multiple blocks, then we collapse it for the calculation.
|
// if the selection spans multiple blocks, then we collapse it for the calculation.
|
||||||
const range = {
|
const range = {
|
||||||
beginning, // whether the selection is in the first block of the editor or not
|
beginning, // whether the selection is in the first block of the editor or not
|
||||||
start: editorState.selection.anchorOffset,
|
start: editorState.selection.anchor.offset,
|
||||||
end: (editorState.selection.anchorKey == editorState.selection.focusKey) ?
|
end: (editorState.selection.anchor.key == editorState.selection.focus.key) ?
|
||||||
editorState.selection.focusOffset : editorState.selection.anchorOffset,
|
editorState.selection.focus.offset : editorState.selection.anchor.offset,
|
||||||
}
|
}
|
||||||
if (range.start > range.end) {
|
if (range.start > range.end) {
|
||||||
const tmp = range.start;
|
const tmp = range.start;
|
||||||
|
@ -1543,7 +1572,7 @@ export default class MessageComposerInput extends React.Component {
|
||||||
};
|
};
|
||||||
|
|
||||||
focusComposer = () => {
|
focusComposer = () => {
|
||||||
this.refs.editor.focus();
|
this._editor.focus();
|
||||||
};
|
};
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
|
@ -1553,7 +1582,7 @@ export default class MessageComposerInput extends React.Component {
|
||||||
mx_MessageComposer_input_error: this.state.someCompletions === false,
|
mx_MessageComposer_input_error: this.state.someCompletions === false,
|
||||||
});
|
});
|
||||||
|
|
||||||
const isEmpty = this.state.editorState.document.isEmpty;
|
const isEmpty = Plain.serialize(this.state.editorState) === '';
|
||||||
|
|
||||||
let {placeholder} = this.props;
|
let {placeholder} = this.props;
|
||||||
// XXX: workaround for placeholder being shown when there is a formatting block e.g blockquote but no text
|
// XXX: workaround for placeholder being shown when there is a formatting block e.g blockquote but no text
|
||||||
|
@ -1579,7 +1608,7 @@ export default class MessageComposerInput extends React.Component {
|
||||||
onMouseDown={this.onMarkdownToggleClicked}
|
onMouseDown={this.onMarkdownToggleClicked}
|
||||||
title={this.state.isRichTextEnabled ? _t("Markdown is disabled") : _t("Markdown is enabled")}
|
title={this.state.isRichTextEnabled ? _t("Markdown is disabled") : _t("Markdown is enabled")}
|
||||||
src={`img/button-md-${!this.state.isRichTextEnabled}.png`} />
|
src={`img/button-md-${!this.state.isRichTextEnabled}.png`} />
|
||||||
<Editor ref="editor"
|
<Editor ref={this._collectEditor}
|
||||||
dir="auto"
|
dir="auto"
|
||||||
className="mx_MessageComposer_editor"
|
className="mx_MessageComposer_editor"
|
||||||
placeholder={placeholder}
|
placeholder={placeholder}
|
||||||
|
@ -1591,6 +1620,7 @@ export default class MessageComposerInput extends React.Component {
|
||||||
renderMark={this.renderMark}
|
renderMark={this.renderMark}
|
||||||
// disable spell check for the placeholder because browsers don't like "unencrypted"
|
// disable spell check for the placeholder because browsers don't like "unencrypted"
|
||||||
spellCheck={!isEmpty}
|
spellCheck={!isEmpty}
|
||||||
|
schema={SLATE_SCHEMA}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
Loading…
Reference in a new issue