diff --git a/src/autocomplete/Autocompleter.js b/src/autocomplete/Autocompleter.js
index e47c5dd59c..6fadf53a61 100644
--- a/src/autocomplete/Autocompleter.js
+++ b/src/autocomplete/Autocompleter.js
@@ -34,6 +34,12 @@ export type Completion = {
component: ?Component,
range: SelectionRange,
command: ?string,
+ // An entity applied during the replacement (using draftjs@0.8.1 Entity.create)
+ entity: ? {
+ type: string,
+ mutability: string,
+ data: ?Object,
+ },
};
const PROVIDERS = [
diff --git a/src/autocomplete/RoomProvider.js b/src/autocomplete/RoomProvider.js
index bf8495a90e..ed737a7d96 100644
--- a/src/autocomplete/RoomProvider.js
+++ b/src/autocomplete/RoomProvider.js
@@ -52,9 +52,16 @@ export default class RoomProvider extends AutocompleteProvider {
};
}));
completions = this.matcher.match(command[0]).map(room => {
- let displayAlias = getDisplayAliasForRoom(room.room) || room.roomId;
+ const displayAlias = getDisplayAliasForRoom(room.room) || room.roomId;
return {
- completion: displayAlias + ' ',
+ completion: displayAlias,
+ entity: {
+ type: 'LINK',
+ mutability: 'IMMUTABLE',
+ data: {
+ url: 'https://matrix.to/#/' + displayAlias,
+ },
+ },
component: (
} title={room.name} description={displayAlias} />
),
diff --git a/src/autocomplete/UserProvider.js b/src/autocomplete/UserProvider.js
index f052716b4b..c17d56312b 100644
--- a/src/autocomplete/UserProvider.js
+++ b/src/autocomplete/UserProvider.js
@@ -51,16 +51,17 @@ export default class UserProvider extends AutocompleteProvider {
let completions = [];
let {command, range} = this.getCurrentCommand(query, selection, force);
if (command) {
- completions = this.matcher.match(command[0]).slice(0, 4).map((user) => {
- let displayName = (user.name || user.userId || '').replace(' (IRC)', ''); // FIXME when groups are done
- let completion = displayName;
- if (range.start === 0) {
- completion += ': ';
- } else {
- completion += ' ';
- }
+ completions = this.matcher.match(command[0]).map((user) => {
+ const displayName = (user.name || user.userId || '').replace(' (IRC)', ''); // FIXME when groups are done
return {
- completion,
+ completion: displayName,
+ entity: {
+ type: 'LINK',
+ mutability: 'IMMUTABLE',
+ data: {
+ url: 'https://matrix.to/#/' + user.userId,
+ },
+ },
component: (
}
diff --git a/src/components/views/rooms/MessageComposerInput.js b/src/components/views/rooms/MessageComposerInput.js
index 2f0901018d..a2bbe7a812 100644
--- a/src/components/views/rooms/MessageComposerInput.js
+++ b/src/components/views/rooms/MessageComposerInput.js
@@ -190,9 +190,16 @@ export default class MessageComposerInput extends React.Component {
const MemberAvatar = sdk.getComponent('avatars.MemberAvatar');
const RoomAvatar = sdk.getComponent('avatars.RoomAvatar');
const {url} = Entity.get(props.entityKey).getData();
- const matrixToMatch = REGEX_MATRIXTO.exec(url);
- const isUserPill = matrixToMatch[2] === '@';
- const isRoomPill = matrixToMatch[2] === '#' || matrixToMatch[2] === '!';
+
+ // Default to the empty array if no match for simplicity
+ // resource and prefix will be undefined instead of throwing
+ const matrixToMatch = REGEX_MATRIXTO.exec(url) || [];
+
+ const resource = matrixToMatch[1]; // The room/user ID
+ const prefix = matrixToMatch[2]; // The first character of prefix
+
+ const isUserPill = prefix === '@';
+ const isRoomPill = prefix === '#' || prefix === '!';
const classes = classNames({
"mx_UserPill": isUserPill,
@@ -203,14 +210,14 @@ export default class MessageComposerInput extends React.Component {
if (isUserPill) {
// If this user is not a member of this room, default to the empty
// member. This could be improved by doing an async profile lookup.
- const member = this.props.room.getMember(matrixToMatch[1]) ||
- new RoomMember(null, matrixToMatch[1]);
+ const member = this.props.room.getMember(resource) ||
+ new RoomMember(null, resource);
avatar = member ? : null;
} else if (isRoomPill) {
- const room = matrixToMatch[2] === '#' ?
+ const room = prefix === '#' ?
MatrixClientPeg.get().getRooms().find((r) => {
- return r.getCanonicalAlias() === matrixToMatch[1];
- }) : MatrixClientPeg.get().getRoom(matrixToMatch[1]);
+ return r.getCanonicalAlias() === resource;
+ }) : MatrixClientPeg.get().getRoom(resource);
avatar = room ? : null;
}
@@ -910,12 +917,24 @@ export default class MessageComposerInput extends React.Component {
return false;
}
- const {range = {}, completion = ''} = displayedCompletion;
+ const {range = {}, completion = '', entity = null} = displayedCompletion;
+ let entityKey;
+ if (entity) {
+ entityKey = Entity.create(
+ entity.type,
+ entity.mutability,
+ entity.data,
+ );
+ }
const contentState = Modifier.replaceText(
activeEditorState.getCurrentContent(),
- RichText.textOffsetsToSelectionState(range, activeEditorState.getCurrentContent().getBlocksAsArray()),
+ RichText.textOffsetsToSelectionState(
+ range, activeEditorState.getCurrentContent().getBlocksAsArray(),
+ ),
completion,
+ null,
+ entityKey,
);
let editorState = EditorState.push(activeEditorState, contentState, 'insert-characters');