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:
commit
3a53fabb87
4 changed files with 54 additions and 21 deletions
|
@ -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 = [
|
||||||
|
|
|
@ -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} />
|
||||||
),
|
),
|
||||||
|
|
|
@ -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}/>}
|
||||||
|
|
|
@ -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');
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue