2017-06-01 17:18:06 +03:00
|
|
|
/*
|
2017-06-01 19:29:40 +03:00
|
|
|
Copyright 2016 Aviral Dasgupta
|
2018-06-19 13:52:48 +03:00
|
|
|
Copyright 2017, 2018 New Vector Ltd
|
2017-06-01 17:18:06 +03:00
|
|
|
|
|
|
|
Licensed under the Apache License, Version 2.0 (the "License");
|
|
|
|
you may not use this file except in compliance with the License.
|
|
|
|
You may obtain a copy of the License at
|
|
|
|
|
|
|
|
http://www.apache.org/licenses/LICENSE-2.0
|
|
|
|
|
|
|
|
Unless required by applicable law or agreed to in writing, software
|
|
|
|
distributed under the License is distributed on an "AS IS" BASIS,
|
|
|
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
|
|
See the License for the specific language governing permissions and
|
|
|
|
limitations under the License.
|
|
|
|
*/
|
|
|
|
|
2016-09-13 13:11:52 +03:00
|
|
|
// @flow
|
|
|
|
|
|
|
|
import type {Component} from 'react';
|
2018-06-19 14:06:13 +03:00
|
|
|
import {Room} from 'matrix-js-sdk';
|
2016-06-01 14:24:21 +03:00
|
|
|
import CommandProvider from './CommandProvider';
|
2018-06-19 13:52:48 +03:00
|
|
|
import CommunityProvider from './CommunityProvider';
|
2016-06-12 14:32:46 +03:00
|
|
|
import DuckDuckGoProvider from './DuckDuckGoProvider';
|
|
|
|
import RoomProvider from './RoomProvider';
|
|
|
|
import UserProvider from './UserProvider';
|
2016-06-17 02:28:09 +03:00
|
|
|
import EmojiProvider from './EmojiProvider';
|
2017-11-02 21:54:25 +03:00
|
|
|
import NotifProvider from './NotifProvider';
|
2019-11-14 16:52:17 +03:00
|
|
|
import {timeout} from "../utils/promise";
|
2016-09-13 13:11:52 +03:00
|
|
|
|
|
|
|
export type SelectionRange = {
|
2018-07-09 02:52:27 +03:00
|
|
|
beginning: boolean, // whether the selection is in the first block of the editor or not
|
|
|
|
start: number, // byte offset relative to the start anchor of the current editor selection.
|
|
|
|
end: number, // byte offset relative to the end anchor of the current editor selection.
|
2016-09-13 13:11:52 +03:00
|
|
|
};
|
|
|
|
|
|
|
|
export type Completion = {
|
|
|
|
completion: string,
|
|
|
|
component: ?Component,
|
|
|
|
range: SelectionRange,
|
|
|
|
command: ?string,
|
2017-07-20 18:49:23 +03:00
|
|
|
// If provided, apply a LINK entity to the completion with the
|
|
|
|
// data = { url: href }.
|
|
|
|
href: ?string,
|
2016-09-13 13:11:52 +03:00
|
|
|
};
|
2016-06-01 14:24:21 +03:00
|
|
|
|
2016-06-12 14:32:46 +03:00
|
|
|
const PROVIDERS = [
|
2016-06-20 11:22:55 +03:00
|
|
|
UserProvider,
|
2016-06-12 14:32:46 +03:00
|
|
|
RoomProvider,
|
2016-07-03 19:45:13 +03:00
|
|
|
EmojiProvider,
|
2017-11-02 21:54:25 +03:00
|
|
|
NotifProvider,
|
2016-09-13 13:11:52 +03:00
|
|
|
CommandProvider,
|
2018-06-19 13:52:48 +03:00
|
|
|
CommunityProvider,
|
2016-09-13 13:11:52 +03:00
|
|
|
DuckDuckGoProvider,
|
2017-11-02 20:51:08 +03:00
|
|
|
];
|
2016-06-01 14:24:21 +03:00
|
|
|
|
2016-09-13 13:11:52 +03:00
|
|
|
// Providers will get rejected if they take longer than this.
|
|
|
|
const PROVIDER_COMPLETION_TIMEOUT = 3000;
|
|
|
|
|
2017-11-02 20:51:08 +03:00
|
|
|
export default class Autocompleter {
|
2018-06-19 14:06:13 +03:00
|
|
|
constructor(room: Room) {
|
2017-11-02 20:51:08 +03:00
|
|
|
this.room = room;
|
2019-01-11 16:54:11 +03:00
|
|
|
this.providers = PROVIDERS.map((Prov) => {
|
|
|
|
return new Prov(room);
|
2017-11-02 20:51:08 +03:00
|
|
|
});
|
|
|
|
}
|
2016-09-13 13:11:52 +03:00
|
|
|
|
2017-11-02 20:51:08 +03:00
|
|
|
destroy() {
|
|
|
|
this.providers.forEach((p) => {
|
|
|
|
p.destroy();
|
|
|
|
});
|
|
|
|
}
|
2016-09-13 13:11:52 +03:00
|
|
|
|
2017-11-02 20:51:08 +03:00
|
|
|
async getCompletions(query: string, selection: SelectionRange, force: boolean = false): Array<Completion> {
|
2017-11-02 21:11:18 +03:00
|
|
|
/* Note: This intentionally waits for all providers to return,
|
2017-11-02 20:51:08 +03:00
|
|
|
otherwise, we run into a condition where new completions are displayed
|
|
|
|
while the user is interacting with the list, which makes it difficult
|
|
|
|
to predict whether an action will actually do what is intended
|
|
|
|
*/
|
2019-11-14 16:52:17 +03:00
|
|
|
const completionsList = await Promise.all(this.providers.map(provider => {
|
|
|
|
return timeout(provider.getCompletions(query, selection, force), null, PROVIDER_COMPLETION_TIMEOUT);
|
|
|
|
}));
|
|
|
|
|
|
|
|
// map then filter to maintain the index for the map-operation, for this.providers to line up
|
|
|
|
return completionsList.map((completions, i) => {
|
|
|
|
if (!completions || !completions.length) return;
|
2017-11-02 20:51:08 +03:00
|
|
|
|
|
|
|
return {
|
2019-11-14 16:52:17 +03:00
|
|
|
completions,
|
2017-11-02 20:51:08 +03:00
|
|
|
provider: this.providers[i],
|
|
|
|
|
|
|
|
/* the currently matched "command" the completer tried to complete
|
|
|
|
* we pass this through so that Autocomplete can figure out when to
|
|
|
|
* re-show itself once hidden.
|
|
|
|
*/
|
|
|
|
command: this.providers[i].getCurrentCommand(query, selection, force),
|
|
|
|
};
|
2019-11-14 16:52:17 +03:00
|
|
|
}).filter(Boolean);
|
2017-11-02 20:51:08 +03:00
|
|
|
}
|
2016-06-01 14:24:21 +03:00
|
|
|
}
|