autocomplete polishing
* suppress autocomplete when navigating through history * only search for slashcommands if in the first block of the editor * handle suffix returns from providers correctly * fix bugs when pressing ctrl-a, typing and then tab to complete a replacement by collapsing selection to anchor when inserting a completion in the editor
This commit is contained in:
parent
c967ecc4e5
commit
5605439e76
2 changed files with 23 additions and 8 deletions
|
@ -114,7 +114,7 @@ export default class Autocomplete extends React.Component {
|
||||||
|
|
||||||
processQuery(query, selection) {
|
processQuery(query, selection) {
|
||||||
return this.autocompleter.getCompletions(
|
return this.autocompleter.getCompletions(
|
||||||
query, selection, this.state.forceComplete,
|
query, selection, this.state.forceComplete
|
||||||
).then((completions) => {
|
).then((completions) => {
|
||||||
// Only ever process the completions for the most recent query being processed
|
// Only ever process the completions for the most recent query being processed
|
||||||
if (query !== this.queryRequested) {
|
if (query !== this.queryRequested) {
|
||||||
|
|
|
@ -178,6 +178,7 @@ export default class MessageComposerInput extends React.Component {
|
||||||
this.plainWithIdPills = new PlainWithPillsSerializer({ pillFormat: 'id' });
|
this.plainWithIdPills = new PlainWithPillsSerializer({ pillFormat: 'id' });
|
||||||
this.plainWithPlainPills = new PlainWithPillsSerializer({ pillFormat: 'plain' });
|
this.plainWithPlainPills = new PlainWithPillsSerializer({ pillFormat: 'plain' });
|
||||||
|
|
||||||
|
this.suppressAutoComplete = false;
|
||||||
this.direction = '';
|
this.direction = '';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -535,6 +536,8 @@ export default class MessageComposerInput extends React.Component {
|
||||||
|
|
||||||
onKeyDown = (ev: Event, change: Change, editor: Editor) => {
|
onKeyDown = (ev: Event, change: Change, editor: Editor) => {
|
||||||
|
|
||||||
|
this.suppressAutoComplete = false;
|
||||||
|
|
||||||
// skip void nodes - see
|
// skip void nodes - see
|
||||||
// https://github.com/ianstormtaylor/slate/issues/762#issuecomment-304855095
|
// https://github.com/ianstormtaylor/slate/issues/762#issuecomment-304855095
|
||||||
if (ev.keyCode === KeyCode.LEFT) {
|
if (ev.keyCode === KeyCode.LEFT) {
|
||||||
|
@ -978,6 +981,8 @@ export default class MessageComposerInput extends React.Component {
|
||||||
// we skip it for now given we know we're about to setState anyway
|
// we skip it for now given we know we're about to setState anyway
|
||||||
editorState = change.value;
|
editorState = change.value;
|
||||||
|
|
||||||
|
this.suppressAutoComplete = true;
|
||||||
|
|
||||||
this.setState({ editorState }, ()=>{
|
this.setState({ editorState }, ()=>{
|
||||||
this.refs.editor.focus();
|
this.refs.editor.focus();
|
||||||
});
|
});
|
||||||
|
@ -1061,13 +1066,15 @@ export default class MessageComposerInput extends React.Component {
|
||||||
let editorState = activeEditorState;
|
let editorState = activeEditorState;
|
||||||
|
|
||||||
if (range) {
|
if (range) {
|
||||||
const change = editorState.change().moveOffsetsTo(range.start, range.end);
|
const change = editorState.change()
|
||||||
|
.collapseToAnchor()
|
||||||
|
.moveOffsetsTo(range.start, range.end);
|
||||||
editorState = change.value;
|
editorState = change.value;
|
||||||
}
|
}
|
||||||
|
|
||||||
const change = editorState.change().insertInlineAtRange(
|
const change = editorState.change()
|
||||||
editorState.selection, inline
|
.insertInlineAtRange(editorState.selection, inline)
|
||||||
);
|
.insertText(suffix);
|
||||||
editorState = change.value;
|
editorState = change.value;
|
||||||
|
|
||||||
this.setState({ editorState, originalEditorState: activeEditorState }, ()=>{
|
this.setState({ editorState, originalEditorState: activeEditorState }, ()=>{
|
||||||
|
@ -1185,13 +1192,12 @@ export default class MessageComposerInput extends React.Component {
|
||||||
}
|
}
|
||||||
|
|
||||||
getAutocompleteQuery(editorState: Value) {
|
getAutocompleteQuery(editorState: Value) {
|
||||||
// FIXME: do we really want to regenerate this every time the control is rerendered?
|
|
||||||
|
|
||||||
// We can just return the current block where the selection begins, which
|
// We can just return the current block where the selection begins, which
|
||||||
// should be enough to capture any autocompletion input, given autocompletion
|
// should be enough to capture any autocompletion input, given autocompletion
|
||||||
// providers only search for the first match which intersects with the current selection.
|
// providers only search for the first match which intersects with the current selection.
|
||||||
// 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.
|
||||||
|
|
||||||
return editorState.document.getDescendant(editorState.selection.anchorKey).text;
|
return editorState.document.getDescendant(editorState.selection.anchorKey).text;
|
||||||
|
|
||||||
// Don't send markdown links to the autocompleter
|
// Don't send markdown links to the autocompleter
|
||||||
|
@ -1199,10 +1205,19 @@ export default class MessageComposerInput extends React.Component {
|
||||||
}
|
}
|
||||||
|
|
||||||
getSelectionRange(editorState: Value) {
|
getSelectionRange(editorState: Value) {
|
||||||
|
let beginning = false;
|
||||||
|
const query = this.getAutocompleteQuery(editorState);
|
||||||
|
const firstChild = editorState.document.nodes.get(0);
|
||||||
|
const firstGrandChild = firstChild && firstChild.nodes.get(0);
|
||||||
|
beginning = (firstChild && firstGrandChild &&
|
||||||
|
firstChild.object === 'block' && firstGrandChild.object === 'text' &&
|
||||||
|
editorState.selection.anchorKey === 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
|
||||||
start: editorState.selection.anchorOffset,
|
start: editorState.selection.anchorOffset,
|
||||||
end: (editorState.selection.anchorKey == editorState.selection.focusKey) ?
|
end: (editorState.selection.anchorKey == editorState.selection.focusKey) ?
|
||||||
editorState.selection.focusOffset : editorState.selection.anchorOffset,
|
editorState.selection.focusOffset : editorState.selection.anchorOffset,
|
||||||
|
@ -1273,7 +1288,7 @@ export default class MessageComposerInput extends React.Component {
|
||||||
room={this.props.room}
|
room={this.props.room}
|
||||||
onConfirm={this.setDisplayedCompletion}
|
onConfirm={this.setDisplayedCompletion}
|
||||||
onSelectionChange={this.setDisplayedCompletion}
|
onSelectionChange={this.setDisplayedCompletion}
|
||||||
query={this.getAutocompleteQuery(activeEditorState)}
|
query={ this.suppressAutoComplete ? '' : this.getAutocompleteQuery(activeEditorState) }
|
||||||
selection={this.getSelectionRange(activeEditorState)}
|
selection={this.getSelectionRange(activeEditorState)}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue