Merge pull request #1228 from matrix-org/luke/feature-auto-complete-matrixto-pills

Implement composer completion user/room pill insertion
This commit is contained in:
Luke Barnard 2017-07-19 17:19:47 +01:00 committed by GitHub
commit 3a53fabb87
4 changed files with 54 additions and 21 deletions

View file

@ -34,6 +34,12 @@ export type Completion = {
component: ?Component, component: ?Component,
range: SelectionRange, range: SelectionRange,
command: ?string, command: ?string,
// An entity applied during the replacement (using draftjs@0.8.1 Entity.create)
entity: ? {
type: string,
mutability: string,
data: ?Object,
},
}; };
const PROVIDERS = [ const PROVIDERS = [

View file

@ -52,9 +52,16 @@ export default class RoomProvider extends AutocompleteProvider {
}; };
})); }));
completions = this.matcher.match(command[0]).map(room => { completions = this.matcher.match(command[0]).map(room => {
let displayAlias = getDisplayAliasForRoom(room.room) || room.roomId; const displayAlias = getDisplayAliasForRoom(room.room) || room.roomId;
return { return {
completion: displayAlias + ' ', completion: displayAlias,
entity: {
type: 'LINK',
mutability: 'IMMUTABLE',
data: {
url: 'https://matrix.to/#/' + displayAlias,
},
},
component: ( component: (
<PillCompletion initialComponent={<RoomAvatar width={24} height={24} room={room.room} />} title={room.name} description={displayAlias} /> <PillCompletion initialComponent={<RoomAvatar width={24} height={24} room={room.room} />} title={room.name} description={displayAlias} />
), ),

View file

@ -51,16 +51,17 @@ export default class UserProvider extends AutocompleteProvider {
let completions = []; let completions = [];
let {command, range} = this.getCurrentCommand(query, selection, force); let {command, range} = this.getCurrentCommand(query, selection, force);
if (command) { if (command) {
completions = this.matcher.match(command[0]).slice(0, 4).map((user) => { completions = this.matcher.match(command[0]).map((user) => {
let displayName = (user.name || user.userId || '').replace(' (IRC)', ''); // FIXME when groups are done const displayName = (user.name || user.userId || '').replace(' (IRC)', ''); // FIXME when groups are done
let completion = displayName;
if (range.start === 0) {
completion += ': ';
} else {
completion += ' ';
}
return { return {
completion, completion: displayName,
entity: {
type: 'LINK',
mutability: 'IMMUTABLE',
data: {
url: 'https://matrix.to/#/' + user.userId,
},
},
component: ( component: (
<PillCompletion <PillCompletion
initialComponent={<MemberAvatar member={user} width={24} height={24}/>} initialComponent={<MemberAvatar member={user} width={24} height={24}/>}

View file

@ -190,9 +190,16 @@ export default class MessageComposerInput extends React.Component {
const MemberAvatar = sdk.getComponent('avatars.MemberAvatar'); const MemberAvatar = sdk.getComponent('avatars.MemberAvatar');
const RoomAvatar = sdk.getComponent('avatars.RoomAvatar'); const RoomAvatar = sdk.getComponent('avatars.RoomAvatar');
const {url} = Entity.get(props.entityKey).getData(); const {url} = Entity.get(props.entityKey).getData();
const matrixToMatch = REGEX_MATRIXTO.exec(url);
const isUserPill = matrixToMatch[2] === '@'; // Default to the empty array if no match for simplicity
const isRoomPill = matrixToMatch[2] === '#' || matrixToMatch[2] === '!'; // 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({ const classes = classNames({
"mx_UserPill": isUserPill, "mx_UserPill": isUserPill,
@ -203,14 +210,14 @@ export default class MessageComposerInput extends React.Component {
if (isUserPill) { if (isUserPill) {
// If this user is not a member of this room, default to the empty // 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. // member. This could be improved by doing an async profile lookup.
const member = this.props.room.getMember(matrixToMatch[1]) || const member = this.props.room.getMember(resource) ||
new RoomMember(null, matrixToMatch[1]); new RoomMember(null, resource);
avatar = member ? <MemberAvatar member={member} width={16} height={16}/> : null; avatar = member ? <MemberAvatar member={member} width={16} height={16}/> : null;
} else if (isRoomPill) { } else if (isRoomPill) {
const room = matrixToMatch[2] === '#' ? const room = prefix === '#' ?
MatrixClientPeg.get().getRooms().find((r) => { MatrixClientPeg.get().getRooms().find((r) => {
return r.getCanonicalAlias() === matrixToMatch[1]; return r.getCanonicalAlias() === resource;
}) : MatrixClientPeg.get().getRoom(matrixToMatch[1]); }) : MatrixClientPeg.get().getRoom(resource);
avatar = room ? <RoomAvatar room={room} width={16} height={16}/> : null; avatar = room ? <RoomAvatar room={room} width={16} height={16}/> : null;
} }
@ -910,12 +917,24 @@ export default class MessageComposerInput extends React.Component {
return false; 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( const contentState = Modifier.replaceText(
activeEditorState.getCurrentContent(), activeEditorState.getCurrentContent(),
RichText.textOffsetsToSelectionState(range, activeEditorState.getCurrentContent().getBlocksAsArray()), RichText.textOffsetsToSelectionState(
range, activeEditorState.getCurrentContent().getBlocksAsArray(),
),
completion, completion,
null,
entityKey,
); );
let editorState = EditorState.push(activeEditorState, contentState, 'insert-characters'); let editorState = EditorState.push(activeEditorState, contentState, 'insert-characters');