Merge pull request #1993 from matrix-org/t3chguy/community_autocomplete
Autocomplete and Pillify Communities
This commit is contained in:
commit
5596dc4e57
13 changed files with 205 additions and 33 deletions
|
@ -4,6 +4,7 @@
|
||||||
|
|
||||||
.mx_UserPill,
|
.mx_UserPill,
|
||||||
.mx_RoomPill,
|
.mx_RoomPill,
|
||||||
|
.mx_GroupPill,
|
||||||
.mx_AtRoomPill {
|
.mx_AtRoomPill {
|
||||||
border-radius: 16px;
|
border-radius: 16px;
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
|
@ -13,7 +14,8 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
.mx_EventTile_body .mx_UserPill,
|
.mx_EventTile_body .mx_UserPill,
|
||||||
.mx_EventTile_body .mx_RoomPill {
|
.mx_EventTile_body .mx_RoomPill,
|
||||||
|
.mx_EventTile_body .mx_GroupPill {
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -35,14 +37,25 @@
|
||||||
|
|
||||||
/* More specific to override `.markdown-body a` color */
|
/* More specific to override `.markdown-body a` color */
|
||||||
.mx_EventTile_content .markdown-body a.mx_RoomPill,
|
.mx_EventTile_content .markdown-body a.mx_RoomPill,
|
||||||
.mx_RoomPill {
|
.mx_EventTile_content .markdown-body a.mx_GroupPill,
|
||||||
|
.mx_RoomPill,
|
||||||
|
.mx_GroupPill {
|
||||||
color: $accent-fg-color;
|
color: $accent-fg-color;
|
||||||
background-color: $rte-room-pill-color;
|
background-color: $rte-room-pill-color;
|
||||||
padding-right: 5px;
|
padding-right: 5px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* More specific to override `.markdown-body a` color */
|
||||||
|
.mx_EventTile_content .markdown-body a.mx_GroupPill,
|
||||||
|
.mx_GroupPill {
|
||||||
|
color: $accent-fg-color;
|
||||||
|
background-color: $rte-group-pill-color;
|
||||||
|
padding-right: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
.mx_UserPill .mx_BaseAvatar,
|
.mx_UserPill .mx_BaseAvatar,
|
||||||
.mx_RoomPill .mx_BaseAvatar,
|
.mx_RoomPill .mx_BaseAvatar,
|
||||||
|
.mx_GroupPill .mx_BaseAvatar,
|
||||||
.mx_AtRoomPill .mx_BaseAvatar {
|
.mx_AtRoomPill .mx_BaseAvatar {
|
||||||
position: relative;
|
position: relative;
|
||||||
left: -3px;
|
left: -3px;
|
||||||
|
|
|
@ -97,6 +97,7 @@ $voip-accept-color: #80f480;
|
||||||
$rte-bg-color: #e9e9e9;
|
$rte-bg-color: #e9e9e9;
|
||||||
$rte-code-bg-color: rgba(0, 0, 0, 0.04);
|
$rte-code-bg-color: rgba(0, 0, 0, 0.04);
|
||||||
$rte-room-pill-color: #aaa;
|
$rte-room-pill-color: #aaa;
|
||||||
|
$rte-group-pill-color: #aaa;
|
||||||
|
|
||||||
// ********************
|
// ********************
|
||||||
|
|
||||||
|
|
|
@ -216,10 +216,17 @@ const sanitizeHtmlParams = {
|
||||||
m = attribs.href.match(linkifyMatrix.MATRIXTO_URL_PATTERN);
|
m = attribs.href.match(linkifyMatrix.MATRIXTO_URL_PATTERN);
|
||||||
if (m) {
|
if (m) {
|
||||||
const entity = m[1];
|
const entity = m[1];
|
||||||
if (entity[0] === '@') {
|
switch (entity[0]) {
|
||||||
attribs.href = '#/user/' + entity;
|
case '@':
|
||||||
} else if (entity[0] === '#' || entity[0] === '!') {
|
attribs.href = '#/user/' + entity;
|
||||||
attribs.href = '#/room/' + entity;
|
break;
|
||||||
|
case '+':
|
||||||
|
attribs.href = '#/group/' + entity;
|
||||||
|
break;
|
||||||
|
case '#':
|
||||||
|
case '!':
|
||||||
|
attribs.href = '#/room/' + entity;
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
delete attribs.target;
|
delete attribs.target;
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
/*
|
/*
|
||||||
Copyright 2016 Aviral Dasgupta
|
Copyright 2016 Aviral Dasgupta
|
||||||
Copyright 2017 Vector Creations Ltd
|
Copyright 2017 Vector Creations Ltd
|
||||||
Copyright 2017 New Vector Ltd
|
Copyright 2017, 2018 New Vector Ltd
|
||||||
|
|
||||||
Licensed under the Apache License, Version 2.0 (the "License");
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
you may not use this file except in compliance with the License.
|
you may not use this file except in compliance with the License.
|
||||||
|
@ -36,7 +36,7 @@ export default class AutocompleteProvider {
|
||||||
/**
|
/**
|
||||||
* Of the matched commands in the query, returns the first that contains or is contained by the selection, or null.
|
* Of the matched commands in the query, returns the first that contains or is contained by the selection, or null.
|
||||||
*/
|
*/
|
||||||
getCurrentCommand(query: string, selection: {start: number, end: number}, force: boolean = false): ?string {
|
getCurrentCommand(query: string, selection: SelectionRange, force: boolean = false): ?string {
|
||||||
let commandRegex = this.commandRegex;
|
let commandRegex = this.commandRegex;
|
||||||
|
|
||||||
if (force && this.shouldForceComplete()) {
|
if (force && this.shouldForceComplete()) {
|
||||||
|
@ -51,14 +51,14 @@ export default class AutocompleteProvider {
|
||||||
|
|
||||||
let match;
|
let match;
|
||||||
while ((match = commandRegex.exec(query)) != null) {
|
while ((match = commandRegex.exec(query)) != null) {
|
||||||
let matchStart = match.index,
|
const start = match.index;
|
||||||
matchEnd = matchStart + match[0].length;
|
const end = start + match[0].length;
|
||||||
if (selection.start <= matchEnd && selection.end >= matchStart) {
|
if (selection.start <= end && selection.end >= start) {
|
||||||
return {
|
return {
|
||||||
command: match,
|
command: match,
|
||||||
range: {
|
range: {
|
||||||
start: matchStart,
|
start,
|
||||||
end: matchEnd,
|
end,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
/*
|
/*
|
||||||
Copyright 2016 Aviral Dasgupta
|
Copyright 2016 Aviral Dasgupta
|
||||||
Copyright 2017 New Vector Ltd
|
Copyright 2017, 2018 New Vector Ltd
|
||||||
|
|
||||||
Licensed under the Apache License, Version 2.0 (the "License");
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
you may not use this file except in compliance with the License.
|
you may not use this file except in compliance with the License.
|
||||||
|
@ -18,7 +18,9 @@ limitations under the License.
|
||||||
// @flow
|
// @flow
|
||||||
|
|
||||||
import type {Component} from 'react';
|
import type {Component} from 'react';
|
||||||
|
import {Room} from 'matrix-js-sdk';
|
||||||
import CommandProvider from './CommandProvider';
|
import CommandProvider from './CommandProvider';
|
||||||
|
import CommunityProvider from './CommunityProvider';
|
||||||
import DuckDuckGoProvider from './DuckDuckGoProvider';
|
import DuckDuckGoProvider from './DuckDuckGoProvider';
|
||||||
import RoomProvider from './RoomProvider';
|
import RoomProvider from './RoomProvider';
|
||||||
import UserProvider from './UserProvider';
|
import UserProvider from './UserProvider';
|
||||||
|
@ -47,6 +49,7 @@ const PROVIDERS = [
|
||||||
EmojiProvider,
|
EmojiProvider,
|
||||||
NotifProvider,
|
NotifProvider,
|
||||||
CommandProvider,
|
CommandProvider,
|
||||||
|
CommunityProvider,
|
||||||
DuckDuckGoProvider,
|
DuckDuckGoProvider,
|
||||||
];
|
];
|
||||||
|
|
||||||
|
@ -54,7 +57,7 @@ const PROVIDERS = [
|
||||||
const PROVIDER_COMPLETION_TIMEOUT = 3000;
|
const PROVIDER_COMPLETION_TIMEOUT = 3000;
|
||||||
|
|
||||||
export default class Autocompleter {
|
export default class Autocompleter {
|
||||||
constructor(room) {
|
constructor(room: Room) {
|
||||||
this.room = room;
|
this.room = room;
|
||||||
this.providers = PROVIDERS.map((p) => {
|
this.providers = PROVIDERS.map((p) => {
|
||||||
return new p(room);
|
return new p(room);
|
||||||
|
|
|
@ -22,8 +22,8 @@ import {_t} from '../languageHandler';
|
||||||
import AutocompleteProvider from './AutocompleteProvider';
|
import AutocompleteProvider from './AutocompleteProvider';
|
||||||
import FuzzyMatcher from './FuzzyMatcher';
|
import FuzzyMatcher from './FuzzyMatcher';
|
||||||
import {TextualCompletion} from './Components';
|
import {TextualCompletion} from './Components';
|
||||||
|
import type {Completion, SelectionRange} from "./Autocompleter";
|
||||||
import {CommandMap} from '../SlashCommands';
|
import {CommandMap} from '../SlashCommands';
|
||||||
import type {SelectionRange} from "./Autocompleter";
|
|
||||||
|
|
||||||
const COMMANDS = Object.values(CommandMap);
|
const COMMANDS = Object.values(CommandMap);
|
||||||
|
|
||||||
|
@ -37,7 +37,7 @@ export default class CommandProvider extends AutocompleteProvider {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
async getCompletions(query: string, selection: SelectionRange, force?: boolean) {
|
async getCompletions(query: string, selection: SelectionRange, force?: boolean): Array<Completion> {
|
||||||
const {command, range} = this.getCurrentCommand(query, selection);
|
const {command, range} = this.getCurrentCommand(query, selection);
|
||||||
if (!command) return [];
|
if (!command) return [];
|
||||||
|
|
||||||
|
|
110
src/autocomplete/CommunityProvider.js
Normal file
110
src/autocomplete/CommunityProvider.js
Normal file
|
@ -0,0 +1,110 @@
|
||||||
|
/*
|
||||||
|
Copyright 2018 New Vector Ltd
|
||||||
|
|
||||||
|
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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import React from 'react';
|
||||||
|
import { _t } from '../languageHandler';
|
||||||
|
import AutocompleteProvider from './AutocompleteProvider';
|
||||||
|
import MatrixClientPeg from '../MatrixClientPeg';
|
||||||
|
import FuzzyMatcher from './FuzzyMatcher';
|
||||||
|
import {PillCompletion} from './Components';
|
||||||
|
import sdk from '../index';
|
||||||
|
import _sortBy from 'lodash/sortBy';
|
||||||
|
import {makeGroupPermalink} from "../matrix-to";
|
||||||
|
import type {Completion, SelectionRange} from "./Autocompleter";
|
||||||
|
import FlairStore from "../stores/FlairStore";
|
||||||
|
|
||||||
|
const COMMUNITY_REGEX = /(?=\+)(\S*)/g;
|
||||||
|
|
||||||
|
function score(query, space) {
|
||||||
|
const index = space.indexOf(query);
|
||||||
|
if (index === -1) {
|
||||||
|
return Infinity;
|
||||||
|
} else {
|
||||||
|
return index;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default class CommunityProvider extends AutocompleteProvider {
|
||||||
|
constructor() {
|
||||||
|
super(COMMUNITY_REGEX);
|
||||||
|
this.matcher = new FuzzyMatcher([], {
|
||||||
|
keys: ['groupId', 'name', 'shortDescription'],
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async getCompletions(query: string, selection: SelectionRange, force?: boolean = false): Array<Completion> {
|
||||||
|
const BaseAvatar = sdk.getComponent('views.avatars.BaseAvatar');
|
||||||
|
|
||||||
|
// Disable autocompletions when composing commands because of various issues
|
||||||
|
// (see https://github.com/vector-im/riot-web/issues/4762)
|
||||||
|
if (/^(\/join|\/leave)/.test(query)) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
const cli = MatrixClientPeg.get();
|
||||||
|
let completions = [];
|
||||||
|
const {command, range} = this.getCurrentCommand(query, selection, force);
|
||||||
|
if (command) {
|
||||||
|
const joinedGroups = cli.getGroups().filter(({myMembership}) => myMembership === 'join');
|
||||||
|
|
||||||
|
const groups = (await Promise.all(joinedGroups.map(async ({groupId}) => {
|
||||||
|
try {
|
||||||
|
return FlairStore.getGroupProfileCached(cli, groupId);
|
||||||
|
} catch (e) { // if FlairStore failed, fall back to just groupId
|
||||||
|
return Promise.resolve({
|
||||||
|
name: '',
|
||||||
|
groupId,
|
||||||
|
avatarUrl: '',
|
||||||
|
shortDescription: '',
|
||||||
|
});
|
||||||
|
}
|
||||||
|
})));
|
||||||
|
|
||||||
|
this.matcher.setObjects(groups);
|
||||||
|
|
||||||
|
const matchedString = command[0];
|
||||||
|
completions = this.matcher.match(matchedString);
|
||||||
|
completions = _sortBy(completions, [
|
||||||
|
(c) => score(matchedString, c.groupId),
|
||||||
|
(c) => c.groupId.length,
|
||||||
|
]).map(({avatarUrl, groupId, name}) => ({
|
||||||
|
completion: groupId,
|
||||||
|
suffix: ' ',
|
||||||
|
href: makeGroupPermalink(groupId),
|
||||||
|
component: (
|
||||||
|
<PillCompletion initialComponent={
|
||||||
|
<BaseAvatar name={name || groupId}
|
||||||
|
width={24} height={24}
|
||||||
|
url={avatarUrl ? cli.mxcUrlToHttp(avatarUrl, 24, 24) : null} />
|
||||||
|
} title={name} description={groupId} />
|
||||||
|
),
|
||||||
|
range,
|
||||||
|
}))
|
||||||
|
.slice(0, 4);
|
||||||
|
}
|
||||||
|
return completions;
|
||||||
|
}
|
||||||
|
|
||||||
|
getName() {
|
||||||
|
return '💬 ' + _t('Communities');
|
||||||
|
}
|
||||||
|
|
||||||
|
renderCompletions(completions: [React.Component]): ?React.Component {
|
||||||
|
return <div className="mx_Autocomplete_Completion_container_pill mx_Autocomplete_Completion_container_truncate">
|
||||||
|
{ completions }
|
||||||
|
</div>;
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,7 +1,7 @@
|
||||||
/*
|
/*
|
||||||
Copyright 2016 Aviral Dasgupta
|
Copyright 2016 Aviral Dasgupta
|
||||||
Copyright 2017 Vector Creations Ltd
|
Copyright 2017 Vector Creations Ltd
|
||||||
Copyright 2017 New Vector Ltd
|
Copyright 2017, 2018 New Vector Ltd
|
||||||
|
|
||||||
Licensed under the Apache License, Version 2.0 (the "License");
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
you may not use this file except in compliance with the License.
|
you may not use this file except in compliance with the License.
|
||||||
|
@ -22,6 +22,7 @@ import AutocompleteProvider from './AutocompleteProvider';
|
||||||
import 'whatwg-fetch';
|
import 'whatwg-fetch';
|
||||||
|
|
||||||
import {TextualCompletion} from './Components';
|
import {TextualCompletion} from './Components';
|
||||||
|
import type {SelectionRange} from "./Autocompleter";
|
||||||
|
|
||||||
const DDG_REGEX = /\/ddg\s+(.+)$/g;
|
const DDG_REGEX = /\/ddg\s+(.+)$/g;
|
||||||
const REFERRER = 'vector';
|
const REFERRER = 'vector';
|
||||||
|
@ -36,7 +37,7 @@ export default class DuckDuckGoProvider extends AutocompleteProvider {
|
||||||
+ `&format=json&no_redirect=1&no_html=1&t=${encodeURIComponent(REFERRER)}`;
|
+ `&format=json&no_redirect=1&no_html=1&t=${encodeURIComponent(REFERRER)}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
async getCompletions(query: string, selection: {start: number, end: number}) {
|
async getCompletions(query: string, selection: SelectionRange, force?: boolean = false) {
|
||||||
const {command, range} = this.getCurrentCommand(query, selection);
|
const {command, range} = this.getCurrentCommand(query, selection);
|
||||||
if (!query || !command) {
|
if (!query || !command) {
|
||||||
return [];
|
return [];
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
/*
|
/*
|
||||||
Copyright 2016 Aviral Dasgupta
|
Copyright 2016 Aviral Dasgupta
|
||||||
Copyright 2017 Vector Creations Ltd
|
Copyright 2017 Vector Creations Ltd
|
||||||
Copyright 2017 New Vector Ltd
|
Copyright 2017, 2018 New Vector Ltd
|
||||||
|
|
||||||
Licensed under the Apache License, Version 2.0 (the "License");
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
you may not use this file except in compliance with the License.
|
you may not use this file except in compliance with the License.
|
||||||
|
@ -19,11 +19,11 @@ limitations under the License.
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { _t } from '../languageHandler';
|
import { _t } from '../languageHandler';
|
||||||
import AutocompleteProvider from './AutocompleteProvider';
|
import AutocompleteProvider from './AutocompleteProvider';
|
||||||
import {emojioneList, shortnameToImage, shortnameToUnicode, asciiRegexp, unicodeRegexp} from 'emojione';
|
import {shortnameToUnicode, asciiRegexp, unicodeRegexp} from 'emojione';
|
||||||
import FuzzyMatcher from './FuzzyMatcher';
|
import FuzzyMatcher from './FuzzyMatcher';
|
||||||
import sdk from '../index';
|
import sdk from '../index';
|
||||||
import {PillCompletion} from './Components';
|
import {PillCompletion} from './Components';
|
||||||
import type {SelectionRange, Completion} from './Autocompleter';
|
import type {Completion, SelectionRange} from './Autocompleter';
|
||||||
import _uniq from 'lodash/uniq';
|
import _uniq from 'lodash/uniq';
|
||||||
import _sortBy from 'lodash/sortBy';
|
import _sortBy from 'lodash/sortBy';
|
||||||
import SettingsStore from "../settings/SettingsStore";
|
import SettingsStore from "../settings/SettingsStore";
|
||||||
|
@ -95,7 +95,7 @@ export default class EmojiProvider extends AutocompleteProvider {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
async getCompletions(query: string, selection: SelectionRange) {
|
async getCompletions(query: string, selection: SelectionRange, force?: boolean): Array<Completion> {
|
||||||
if (SettingsStore.getValue("MessageComposerInput.dontSuggestEmoji")) {
|
if (SettingsStore.getValue("MessageComposerInput.dontSuggestEmoji")) {
|
||||||
return []; // don't give any suggestions if the user doesn't want them
|
return []; // don't give any suggestions if the user doesn't want them
|
||||||
}
|
}
|
||||||
|
|
|
@ -20,6 +20,7 @@ import { _t } from '../languageHandler';
|
||||||
import MatrixClientPeg from '../MatrixClientPeg';
|
import MatrixClientPeg from '../MatrixClientPeg';
|
||||||
import {PillCompletion} from './Components';
|
import {PillCompletion} from './Components';
|
||||||
import sdk from '../index';
|
import sdk from '../index';
|
||||||
|
import type {Completion, SelectionRange} from "./Autocompleter";
|
||||||
|
|
||||||
const AT_ROOM_REGEX = /@\S*/g;
|
const AT_ROOM_REGEX = /@\S*/g;
|
||||||
|
|
||||||
|
@ -29,7 +30,7 @@ export default class NotifProvider extends AutocompleteProvider {
|
||||||
this.room = room;
|
this.room = room;
|
||||||
}
|
}
|
||||||
|
|
||||||
async getCompletions(query: string, selection: {start: number, end: number}, force = false) {
|
async getCompletions(query: string, selection: SelectionRange, force?:boolean = false): Array<Completion> {
|
||||||
const RoomAvatar = sdk.getComponent('views.avatars.RoomAvatar');
|
const RoomAvatar = sdk.getComponent('views.avatars.RoomAvatar');
|
||||||
|
|
||||||
const client = MatrixClientPeg.get();
|
const client = MatrixClientPeg.get();
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
/*
|
/*
|
||||||
Copyright 2016 Aviral Dasgupta
|
Copyright 2016 Aviral Dasgupta
|
||||||
Copyright 2017 Vector Creations Ltd
|
Copyright 2017 Vector Creations Ltd
|
||||||
Copyright 2017 New Vector Ltd
|
Copyright 2017, 2018 New Vector Ltd
|
||||||
|
|
||||||
Licensed under the Apache License, Version 2.0 (the "License");
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
you may not use this file except in compliance with the License.
|
you may not use this file except in compliance with the License.
|
||||||
|
@ -26,6 +26,7 @@ import {getDisplayAliasForRoom} from '../Rooms';
|
||||||
import sdk from '../index';
|
import sdk from '../index';
|
||||||
import _sortBy from 'lodash/sortBy';
|
import _sortBy from 'lodash/sortBy';
|
||||||
import {makeRoomPermalink} from "../matrix-to";
|
import {makeRoomPermalink} from "../matrix-to";
|
||||||
|
import type {Completion, SelectionRange} from "./Autocompleter";
|
||||||
|
|
||||||
const ROOM_REGEX = /(?=#)(\S*)/g;
|
const ROOM_REGEX = /(?=#)(\S*)/g;
|
||||||
|
|
||||||
|
@ -46,7 +47,7 @@ export default class RoomProvider extends AutocompleteProvider {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
async getCompletions(query: string, selection: {start: number, end: number}, force = false) {
|
async getCompletions(query: string, selection: SelectionRange, force?: boolean = false): Array<Completion> {
|
||||||
const RoomAvatar = sdk.getComponent('views.avatars.RoomAvatar');
|
const RoomAvatar = sdk.getComponent('views.avatars.RoomAvatar');
|
||||||
|
|
||||||
// Disable autocompletions when composing commands because of various issues
|
// Disable autocompletions when composing commands because of various issues
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
/*
|
/*
|
||||||
Copyright 2016 Aviral Dasgupta
|
Copyright 2016 Aviral Dasgupta
|
||||||
Copyright 2017 Vector Creations Ltd
|
Copyright 2017 Vector Creations Ltd
|
||||||
Copyright 2017 New Vector Ltd
|
Copyright 2017, 2018 New Vector Ltd
|
||||||
|
|
||||||
Licensed under the Apache License, Version 2.0 (the "License");
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
you may not use this file except in compliance with the License.
|
you may not use this file except in compliance with the License.
|
||||||
|
@ -23,12 +23,12 @@ import AutocompleteProvider from './AutocompleteProvider';
|
||||||
import {PillCompletion} from './Components';
|
import {PillCompletion} from './Components';
|
||||||
import sdk from '../index';
|
import sdk from '../index';
|
||||||
import FuzzyMatcher from './FuzzyMatcher';
|
import FuzzyMatcher from './FuzzyMatcher';
|
||||||
import _pull from 'lodash/pull';
|
|
||||||
import _sortBy from 'lodash/sortBy';
|
import _sortBy from 'lodash/sortBy';
|
||||||
import MatrixClientPeg from '../MatrixClientPeg';
|
import MatrixClientPeg from '../MatrixClientPeg';
|
||||||
|
|
||||||
import type {Room, RoomMember} from 'matrix-js-sdk';
|
import type {Room, RoomMember} from 'matrix-js-sdk';
|
||||||
import {makeUserPermalink} from "../matrix-to";
|
import {makeUserPermalink} from "../matrix-to";
|
||||||
|
import type {SelectionRange} from "./Autocompleter";
|
||||||
|
|
||||||
const USER_REGEX = /@\S*/g;
|
const USER_REGEX = /@\S*/g;
|
||||||
|
|
||||||
|
@ -36,7 +36,7 @@ export default class UserProvider extends AutocompleteProvider {
|
||||||
users: Array<RoomMember> = null;
|
users: Array<RoomMember> = null;
|
||||||
room: Room = null;
|
room: Room = null;
|
||||||
|
|
||||||
constructor(room) {
|
constructor(room: Room) {
|
||||||
super(USER_REGEX, {
|
super(USER_REGEX, {
|
||||||
keys: ['name'],
|
keys: ['name'],
|
||||||
});
|
});
|
||||||
|
@ -87,7 +87,7 @@ export default class UserProvider extends AutocompleteProvider {
|
||||||
this.users = null;
|
this.users = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
async getCompletions(query: string, selection: {start: number, end: number}, force = false) {
|
async getCompletions(query: string, selection: SelectionRange, force?: boolean = false) {
|
||||||
const MemberAvatar = sdk.getComponent('views.avatars.MemberAvatar');
|
const MemberAvatar = sdk.getComponent('views.avatars.MemberAvatar');
|
||||||
|
|
||||||
// Disable autocompletions when composing commands because of various issues
|
// Disable autocompletions when composing commands because of various issues
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
/*
|
/*
|
||||||
Copyright 2017 Vector Creations Ltd
|
Copyright 2017 Vector Creations Ltd
|
||||||
|
Copyright 2018 New Vector Ltd
|
||||||
|
|
||||||
Licensed under the Apache License, Version 2.0 (the "License");
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
you may not use this file except in compliance with the License.
|
you may not use this file except in compliance with the License.
|
||||||
|
@ -22,12 +23,13 @@ import PropTypes from 'prop-types';
|
||||||
import MatrixClientPeg from '../../../MatrixClientPeg';
|
import MatrixClientPeg from '../../../MatrixClientPeg';
|
||||||
import { MATRIXTO_URL_PATTERN } from '../../../linkify-matrix';
|
import { MATRIXTO_URL_PATTERN } from '../../../linkify-matrix';
|
||||||
import { getDisplayAliasForRoom } from '../../../Rooms';
|
import { getDisplayAliasForRoom } from '../../../Rooms';
|
||||||
|
import FlairStore from "../../../stores/FlairStore";
|
||||||
|
|
||||||
const REGEX_MATRIXTO = new RegExp(MATRIXTO_URL_PATTERN);
|
const REGEX_MATRIXTO = new RegExp(MATRIXTO_URL_PATTERN);
|
||||||
|
|
||||||
// For URLs of matrix.to links in the timeline which have been reformatted by
|
// For URLs of matrix.to links in the timeline which have been reformatted by
|
||||||
// HttpUtils transformTags to relative links. This excludes event URLs (with `[^\/]*`)
|
// HttpUtils transformTags to relative links. This excludes event URLs (with `[^\/]*`)
|
||||||
const REGEX_LOCAL_MATRIXTO = /^#\/(?:user|room)\/(([\#\!\@\+])[^\/]*)$/;
|
const REGEX_LOCAL_MATRIXTO = /^#\/(?:user|room|group)\/(([#!@+])[^\/]*)$/;
|
||||||
|
|
||||||
const Pill = React.createClass({
|
const Pill = React.createClass({
|
||||||
statics: {
|
statics: {
|
||||||
|
@ -45,6 +47,7 @@ const Pill = React.createClass({
|
||||||
},
|
},
|
||||||
TYPE_USER_MENTION: 'TYPE_USER_MENTION',
|
TYPE_USER_MENTION: 'TYPE_USER_MENTION',
|
||||||
TYPE_ROOM_MENTION: 'TYPE_ROOM_MENTION',
|
TYPE_ROOM_MENTION: 'TYPE_ROOM_MENTION',
|
||||||
|
TYPE_GROUP_MENTION: 'TYPE_GROUP_MENTION',
|
||||||
TYPE_AT_ROOM_MENTION: 'TYPE_AT_ROOM_MENTION', // '@room' mention
|
TYPE_AT_ROOM_MENTION: 'TYPE_AT_ROOM_MENTION', // '@room' mention
|
||||||
},
|
},
|
||||||
|
|
||||||
|
@ -81,12 +84,14 @@ const Pill = React.createClass({
|
||||||
|
|
||||||
// The member related to the user pill
|
// The member related to the user pill
|
||||||
member: null,
|
member: null,
|
||||||
|
// The group related to the group pill
|
||||||
|
group: null,
|
||||||
// The room related to the room pill
|
// The room related to the room pill
|
||||||
room: null,
|
room: null,
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
|
||||||
componentWillReceiveProps(nextProps) {
|
async componentWillReceiveProps(nextProps) {
|
||||||
let regex = REGEX_MATRIXTO;
|
let regex = REGEX_MATRIXTO;
|
||||||
if (nextProps.inMessage) {
|
if (nextProps.inMessage) {
|
||||||
regex = REGEX_LOCAL_MATRIXTO;
|
regex = REGEX_LOCAL_MATRIXTO;
|
||||||
|
@ -109,9 +114,11 @@ const Pill = React.createClass({
|
||||||
'@': Pill.TYPE_USER_MENTION,
|
'@': Pill.TYPE_USER_MENTION,
|
||||||
'#': Pill.TYPE_ROOM_MENTION,
|
'#': Pill.TYPE_ROOM_MENTION,
|
||||||
'!': Pill.TYPE_ROOM_MENTION,
|
'!': Pill.TYPE_ROOM_MENTION,
|
||||||
|
'+': Pill.TYPE_GROUP_MENTION,
|
||||||
}[prefix];
|
}[prefix];
|
||||||
|
|
||||||
let member;
|
let member;
|
||||||
|
let group;
|
||||||
let room;
|
let room;
|
||||||
switch (pillType) {
|
switch (pillType) {
|
||||||
case Pill.TYPE_AT_ROOM_MENTION: {
|
case Pill.TYPE_AT_ROOM_MENTION: {
|
||||||
|
@ -140,8 +147,21 @@ const Pill = React.createClass({
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
case Pill.TYPE_GROUP_MENTION: {
|
||||||
|
const cli = MatrixClientPeg.get();
|
||||||
|
|
||||||
|
try {
|
||||||
|
group = await FlairStore.getGroupProfileCached(cli, resourceId);
|
||||||
|
} catch (e) { // if FlairStore failed, fall back to just groupId
|
||||||
|
group = {
|
||||||
|
groupId: resourceId,
|
||||||
|
avatarUrl: null,
|
||||||
|
name: null,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
this.setState({resourceId, pillType, member, room});
|
this.setState({resourceId, pillType, member, group, room});
|
||||||
},
|
},
|
||||||
|
|
||||||
componentWillMount() {
|
componentWillMount() {
|
||||||
|
@ -179,6 +199,7 @@ const Pill = React.createClass({
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
render: function() {
|
render: function() {
|
||||||
|
const BaseAvatar = sdk.getComponent('views.avatars.BaseAvatar');
|
||||||
const MemberAvatar = sdk.getComponent('avatars.MemberAvatar');
|
const MemberAvatar = sdk.getComponent('avatars.MemberAvatar');
|
||||||
const RoomAvatar = sdk.getComponent('avatars.RoomAvatar');
|
const RoomAvatar = sdk.getComponent('avatars.RoomAvatar');
|
||||||
|
|
||||||
|
@ -229,6 +250,20 @@ const Pill = React.createClass({
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
case Pill.TYPE_GROUP_MENTION: {
|
||||||
|
if (this.state.group) {
|
||||||
|
const {avatarUrl, groupId, name} = this.state.group;
|
||||||
|
const cli = MatrixClientPeg.get();
|
||||||
|
|
||||||
|
linkText = groupId;
|
||||||
|
if (this.props.shouldShowPillAvatar) {
|
||||||
|
avatar = <BaseAvatar name={name || groupId} width={16} height={16}
|
||||||
|
url={avatarUrl ? cli.mxcUrlToHttp(avatarUrl, 16, 16) : null} />;
|
||||||
|
}
|
||||||
|
pillClass = 'mx_GroupPill';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
const classes = classNames(pillClass, {
|
const classes = classNames(pillClass, {
|
||||||
|
|
Loading…
Reference in a new issue