Add Mentions with react-mentions

pull/1381/head
Audric Ackermann 4 years ago
parent b2e362a36b
commit b5af8eb215
No known key found for this signature in database
GPG Key ID: 999F434D76324AD4

@ -13,7 +13,6 @@
clipboard,
BlockedNumberController,
lokiPublicChatAPI,
JobQueue,
*/
/* eslint-disable more/no-then */

@ -123,9 +123,11 @@ const sendViaOnion = async (srvPubKey, url, fetchOptions, options = {}) => {
options.requestNumber
);
if (typeof result === 'number') {
window.log.error('sendOnionRequestLsrpcDest() returned a number indicating an error: ', result);
window.log.error(
'sendOnionRequestLsrpcDest() returned a number indicating an error: ',
result
);
}
} catch (e) {
log.error(
'loki_app_dot_net:::sendViaOnion - lokiRpcUtils error',

@ -11,7 +11,12 @@ export interface LokiPublicChatFactoryInterface {
channelId: number,
conversationId: string
): Promise<LokiPublicChannelAPI | null>;
getListOfMembers(): Promise<Array<{ authorPhoneNumber: string }>>;
getListOfMembers(): Promise<
Array<{ authorPhoneNumber: string; authorProfileName?: string }>
>;
setListOfMembers(
members: Array<{ authorPhoneNumber: string; authorProfileName?: string }>
);
}
declare class LokiPublicChatFactoryAPI

@ -92,7 +92,6 @@ const {
MediaGallery,
} = require('../../ts/components/conversation/media-gallery/MediaGallery');
const { Message } = require('../../ts/components/conversation/Message');
const { MessageBody } = require('../../ts/components/conversation/MessageBody');
const {
MessageDetail,
} = require('../../ts/components/conversation/MessageDetail');
@ -266,7 +265,6 @@ exports.setup = (options = {}) => {
SessionPasswordPrompt,
MediaGallery,
Message,
MessageBody,
MessageDetail,
Quote,
Types: {

@ -62,6 +62,7 @@
"@types/emoji-mart": "^2.11.3",
"@types/moment": "^2.13.0",
"@types/rc-slider": "^8.6.5",
"@types/react-mentions": "^3.3.1",
"@types/react-mic": "^12.4.1",
"@types/styled-components": "^5.1.4",
"abort-controller": "3.0.0",
@ -105,12 +106,12 @@
"protobufjs": "^6.9.0",
"rc-slider": "^8.7.1",
"react": "^16.13.1",
"react-autosize-textarea": "^7.0.0",
"react-contexify": "^4.1.1",
"react-dom": "16.8.3",
"react-emoji": "^0.5.0",
"react-emoji-render": "^1.2.4",
"react-h5-audio-player": "^3.2.0",
"react-mentions": "^4.0.2",
"react-portal": "^4.2.0",
"react-qr-svg": "^2.2.1",
"react-redux": "7.2.1",

@ -12,9 +12,6 @@
min-width: 20px;
}
.invisible {
visibility: hidden;
}
.existing-member {
color: green;
@ -45,31 +42,35 @@
}
}
.member-list-container,
.create-group-dialog,
.add-moderators-dialog,
.remove-moderators-dialog,
.invite-friends-dialog {
.member-item {
padding: 4px;
user-select: none;
white-space: nowrap;
text-overflow: ellipsis;
overflow: hidden;
.name-part {
font-weight: 300;
margin-inline-start: 12px;
}
.pubkey-part {
margin-inline-start: 10px;
opacity: 0.6;
}
.invisible {
visibility: hidden;
}
.member-item {
@include themify($themes) {
background-color: themed('cellBackground');
color: themed('textColor');
}
padding: 4px;
white-space: nowrap;
text-overflow: ellipsis;
overflow: hidden;
.name-part {
font-weight: 300;
margin-inline-start: 12px;
}
.pubkey-part {
margin-inline-start: 10px;
opacity: 0.6;
}
}
.mention-profile-name {
color: rgb(194, 244, 255);
background-color: rgb(66, 121, 150);

@ -4,81 +4,56 @@ import { RenderTextCallbackType } from '../../types/Util';
import classNames from 'classnames';
import { MultiDeviceProtocol } from '../../session/protocols';
import { FindMember } from '../../util';
import { useInterval } from '../../hooks/useInterval';
interface MentionProps {
key: number;
key: string;
text: string;
convoId: string;
}
interface MentionState {
found: any;
us: boolean;
}
class Mention extends React.Component<MentionProps, MentionState> {
private intervalHandle: any = null;
constructor(props: any) {
super(props);
this.tryRenameMention = this.tryRenameMention.bind(this);
}
public componentWillMount() {
this.setState({ found: false });
// TODO: give up after some period of time?
this.intervalHandle = setInterval(this.tryRenameMention, 30000);
this.tryRenameMention().ignore();
}
public componentWillUnmount() {
this.clearOurInterval();
}
public render() {
if (this.state.found) {
// TODO: We don't have to search the database of message just to know that the message is for us!
const us = this.state.us;
const className = classNames(
'mention-profile-name',
us && 'mention-profile-name-us'
);
const profileName = this.state.found.authorProfileName;
const displayedName =
profileName && profileName.length > 0 ? profileName : 'Anonymous';
const Mention = (props: MentionProps) => {
const [found, setFound] = React.useState<any>(undefined);
const [us, setUs] = React.useState(false);
return <span className={className}>{displayedName}</span>;
} else {
return (
<span className="mention-profile-name">
{window.shortenPubkey(this.props.text)}
</span>
const tryRenameMention = async () => {
if (!found) {
const foundMember = await FindMember.findMember(
props.text.slice(1),
props.convoId
);
if (foundMember) {
const itsUs = await MultiDeviceProtocol.isOurDevice(
foundMember.authorPhoneNumber
);
setUs(itsUs);
setFound(foundMember);
// FIXME stop this interval once we found it.
}
}
}
};
private clearOurInterval() {
clearInterval(this.intervalHandle);
}
useInterval(() => void tryRenameMention(), 10000);
private async tryRenameMention() {
const bound = this.clearOurInterval.bind(this);
const found = await FindMember.findMember(
this.props.text.slice(1),
this.props.convoId,
bound
if (found) {
// TODO: We don't have to search the database of message just to know that the message is for us!
const className = classNames(
'mention-profile-name',
us && 'mention-profile-name-us'
);
if (found) {
const us = await MultiDeviceProtocol.isOurDevice(found.authorPhoneNumber);
this.setState({ found, us });
this.clearOurInterval();
}
const profileName = found.authorProfileName;
const displayedName =
profileName && profileName.length > 0 ? profileName : 'Anonymous';
return <span className={className}>{displayedName}</span>;
} else {
return (
<span className="mention-profile-name">
{window.shortenPubkey(props.text)}
</span>
);
}
}
};
interface Props {
text: string;
@ -109,15 +84,16 @@ export class AddMentions extends React.Component<Props> {
if (!match) {
return renderOther({ text, key: 0 });
}
while (match) {
count++;
const key = count;
if (last < match.index) {
const otherText = text.slice(last, match.index);
results.push(renderOther({ text: otherText, key: count++ }));
results.push(renderOther({ text: otherText, key }));
}
const pubkey = text.slice(match.index, FIND_MENTIONS.lastIndex);
results.push(<Mention text={pubkey} key={count++} convoId={convoId} />);
results.push(<Mention text={pubkey} key={`${key}`} convoId={convoId} />);
// @ts-ignore
last = FIND_MENTIONS.lastIndex;

@ -21,22 +21,19 @@ interface MemberItemProps {
checkmarked: boolean;
}
class MemberItem extends React.Component<MemberItemProps> {
export class MemberItem extends React.Component<MemberItemProps> {
constructor(props: any) {
super(props);
this.handleClick = this.handleClick.bind(this);
}
public render() {
const name = this.props.member.authorProfileName;
const pubkey = this.props.member.authorPhoneNumber;
const selected = this.props.selected;
const existingMember = this.props.existingMember;
const {authorProfileName: name, authorPhoneNumber: pubkey, selected, existingMember, checkmarked} = this.props.member;
const shortPubkey = window.shortenPubkey(pubkey);
let markType: 'none' | 'kicked' | 'added' | 'existing' = 'none';
if (this.props.checkmarked) {
if (checkmarked) {
if (existingMember) {
markType = 'kicked';
} else {
@ -65,7 +62,6 @@ class MemberItem extends React.Component<MemberItemProps> {
default:
// do nothing
}
const mark = markType === 'kicked' ? '✘' : '✔';
return (
@ -124,16 +120,16 @@ export class MemberList extends React.Component<MemberListProps> {
}
public render() {
const { members } = this.props;
const { members, selected } = this.props;
const itemList = members.map(item => {
const selected = item === this.props.selected;
const isSelected = item === selected;
return (
<MemberItem
key={item.id}
member={item}
selected={selected}
selected={isSelected}
checkmarked={item.checkmarked}
existingMember={item.existingMember}
i18n={this.props.i18n}

@ -22,7 +22,7 @@ interface Props {
}
const renderMentions: RenderTextCallbackType = ({ text, key, convoId }) => (
<AddMentions key={key} text={text} convoId={convoId} />
<AddMentions key={key} text={text} convoId={convoId || ''} />
);
const renderDefault: RenderTextCallbackType = ({ text }) => text;

@ -4,8 +4,6 @@ import _, { debounce } from 'lodash';
import { Attachment, AttachmentType } from '../../../types/Attachment';
import * as MIME from '../../../types/MIME';
import TextareaAutosize from 'react-autosize-textarea';
import { SessionIconButton, SessionIconSize, SessionIconType } from '../icon';
import { SessionEmojiPanel } from './SessionEmojiPanel';
import { SessionRecording } from './SessionRecording';
@ -24,8 +22,10 @@ import {
LINK_PREVIEW_TIMEOUT,
SessionStagedLinkPreview,
} from './SessionStagedLinkPreview';
import { AbortController, AbortSignal } from 'abort-controller';
import { AbortController } from 'abort-controller';
import { SessionQuotedMessageComposition } from './SessionQuotedMessageComposition';
import { Mention, MentionsInput } from 'react-mentions';
import { MemberItem } from '../../conversation/MemberList';
export interface ReplyingToMessageProps {
convoId: string;
@ -61,6 +61,8 @@ interface Props {
isPrivate: boolean;
isKickedFromGroup: boolean;
leftGroup: boolean;
conversationKey: string;
isPublic: boolean;
quotedMessageProps?: ReplyingToMessageProps;
removeQuotedMessage: () => void;
@ -83,24 +85,54 @@ interface State {
stagedLinkPreview?: StagedLinkPreviewData;
}
const sendMessageStyle = {
control: {
wordBreak: 'break-all',
},
input: {
overflow: 'auto',
maxHeight: 70,
wordBreak: 'break-all',
padding: '0px',
margin: '0px',
},
highlighter: {
boxSizing: 'border-box',
overflow: 'hidden',
maxHeight: 70,
},
flexGrow: 1,
minHeight: '24px',
width: '100%',
};
const getDefaultState = () => {
return {
message: '',
voiceRecording: undefined,
showRecordingView: false,
mediaSetting: null,
showEmojiPanel: false,
ignoredLink: undefined,
stagedLinkPreview: undefined,
};
};
export class SessionCompositionBox extends React.Component<Props, State> {
private readonly textarea: React.RefObject<HTMLTextAreaElement>;
private readonly textarea: React.RefObject<any>;
private readonly fileInput: React.RefObject<HTMLInputElement>;
private emojiPanel: any;
private linkPreviewAbortController?: AbortController;
private container: any;
private mentionsData: Array<{ display: string; id: string }>;
constructor(props: any) {
super(props);
this.state = {
message: '',
voiceRecording: undefined,
showRecordingView: false,
mediaSetting: null,
showEmojiPanel: false,
};
this.state = getDefaultState();
this.textarea = props.textarea;
this.fileInput = React.createRef();
this.mentionsData = [];
// Emojis
this.emojiPanel = null;
@ -132,6 +164,8 @@ export class SessionCompositionBox extends React.Component<Props, State> {
this.onKeyDown = this.onKeyDown.bind(this);
this.onChange = this.onChange.bind(this);
this.focusCompositionBox = this.focusCompositionBox.bind(this);
this.fetchUsersForGroup = this.fetchUsersForGroup.bind(this);
}
public async componentWillMount() {
@ -147,6 +181,13 @@ export class SessionCompositionBox extends React.Component<Props, State> {
this.linkPreviewAbortController?.abort();
this.linkPreviewAbortController = undefined;
}
public componentDidUpdate(prevProps: Props, prevState: State) {
// reset the state on new conversation key
if (prevProps.conversationKey !== this.props.conversationKey) {
this.setState(getDefaultState());
this.mentionsData = [];
}
}
public render() {
const { showRecordingView } = this.state;
@ -253,19 +294,59 @@ export class SessionCompositionBox extends React.Component<Props, State> {
className="send-message-input"
role="main"
onClick={this.focusCompositionBox}
ref={el => {
this.container = el;
}}
>
<TextareaAutosize
rows={1}
maxRows={3}
ref={this.textarea}
spellCheck={false}
placeholder={messagePlaceHolder}
maxLength={Constants.CONVERSATION.MAX_MESSAGE_BODY_LENGTH}
onKeyDown={this.onKeyDown}
value={message}
<MentionsInput
value={this.state.message}
onChange={this.onChange}
onKeyDown={this.onKeyDown}
placeholder={messagePlaceHolder}
spellCheck={false}
inputRef={this.textarea}
disabled={!typingEnabled}
/>
maxLength={Constants.CONVERSATION.MAX_MESSAGE_BODY_LENGTH}
rows={1}
// maxRows={3}
style={sendMessageStyle}
suggestionsPortalHost={this.container}
allowSuggestionsAboveCursor={true}
>
<Mention
appendSpaceOnAdd={true}
markup="@(__id__)" // @__id__ does not work, see cleanMentions()
trigger="@"
displayTransform={id => `@${id}`}
data={this.fetchUsersForGroup}
renderSuggestion={(
suggestion,
_search,
_highlightedDisplay,
_index,
focused
) => (
<MemberItem
i18n={window.i18n}
selected={focused}
// tslint:disable-next-line: no-empty
onClicked={() => {}}
existingMember={false}
member={{
id: `${suggestion.id}`,
authorPhoneNumber: `${suggestion.id}`,
selected: false,
authorProfileName: `${suggestion.display}`,
authorName: `${suggestion.display}`,
existingMember: false,
checkmarked: false,
authorAvatarPath: '',
}}
checkmarked={false}
/>
)}
/>
</MentionsInput>
</div>
{typingEnabled && (
@ -301,6 +382,84 @@ export class SessionCompositionBox extends React.Component<Props, State> {
</>
);
}
private fetchUsersForGroup(query: any, callback: any) {
if (!query) {
return;
}
if (this.props.isPublic) {
this.fetchUsersForOpenGroup(query, callback);
return;
}
if (!this.props.isPrivate) {
this.fetchUsersForClosedGroup(query, callback);
return;
}
}
private fetchUsersForOpenGroup(query: any, callback: any) {
if (!query) {
return;
}
void window.lokiPublicChatAPI
.getListOfMembers()
.then(members =>
members
.filter(d => !!d)
.filter(d => d.authorProfileName !== 'Anonymous')
.filter(d =>
d.authorProfileName?.toLowerCase()?.includes(query.toLowerCase())
)
)
// Transform the users to what react-mentions expects
.then(members => {
const toRet = members.map(user => ({
display: user.authorProfileName,
id: user.authorPhoneNumber,
}));
return toRet;
})
.then(callback);
}
private fetchUsersForClosedGroup(query: any, callback: any) {
if (!query) {
return;
}
const conversationModel = window.ConversationController.get(
this.props.conversationKey
);
if (!conversationModel) {
return;
}
const allPubKeys = conversationModel.get('members');
const allMembers = allPubKeys.map(pubKey => {
const conv = window.ConversationController.get(pubKey);
let profileName = 'Anonymous';
if (conv) {
profileName = conv.getProfileName();
}
return {
id: pubKey,
authorPhoneNumber: pubKey,
authorProfileName: profileName,
};
});
const members = allMembers
.filter(d => !!d)
.filter(d => d.authorProfileName !== 'Anonymous')
.filter(d =>
d.authorProfileName?.toLowerCase()?.includes(query.toLowerCase())
);
// Transform the users to what react-mentions expects
const mentionsData = members.map(user => ({
display: user.authorProfileName,
id: user.authorPhoneNumber,
}));
this.mentionsData = mentionsData;
callback(mentionsData);
}
private renderStagedLinkPreview(): JSX.Element {
// Don't generate link previews if user has turned them off
@ -495,7 +654,25 @@ export class SessionCompositionBox extends React.Component<Props, State> {
// tslint:disable-next-line: cyclomatic-complexity
private async onSendMessage() {
const messagePlaintext = this.parseEmojis(this.state.message);
// replace all @(xxx) by @xxx
const cleanMentions = (text: string): string => {
const mentionRegex = /@\(05[0-9a-f]{64}\)/g;
const matches = text.match(mentionRegex);
let replacedMentions = text;
(matches || []).forEach(match => {
const replacedMention = match.substring(2, match.length - 1);
replacedMentions = replacedMentions.replace(
match,
`@${replacedMention}`
);
});
return replacedMentions;
};
const messagePlaintext = cleanMentions(
this.parseEmojis(this.state.message)
);
const { isBlocked, isPrivate, leftGroup, isKickedFromGroup } = this.props;

@ -77,6 +77,7 @@ export class SessionConversation extends React.Component<Props, State> {
private readonly compositionBoxRef: React.RefObject<HTMLDivElement>;
private readonly messageContainerRef: React.RefObject<HTMLDivElement>;
private dragCounter: number;
private publicMembersRefreshTimeout?: NodeJS.Timeout;
constructor(props: any) {
super(props);
@ -145,6 +146,8 @@ export class SessionConversation extends React.Component<Props, State> {
this.handleDragOut = this.handleDragOut.bind(this);
this.handleDrag = this.handleDrag.bind(this);
this.handleDrop = this.handleDrop.bind(this);
this.updateMemberList = this.updateMemberList.bind(this);
}
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
@ -172,6 +175,22 @@ export class SessionConversation extends React.Component<Props, State> {
div?.addEventListener('dragover', this.handleDrag);
div?.addEventListener('drop', this.handleDrop);
}, 100);
// if the conversation changed, we have to stop our refresh of member list
if (this.publicMembersRefreshTimeout) {
global.clearInterval(this.publicMembersRefreshTimeout);
this.publicMembersRefreshTimeout = undefined;
}
// if the newConversation changed, and is public, start our refresh members list
if (newConversation.isPublic) {
// TODO use abort controller to stop those requests too
void this.updateMemberList();
this.publicMembersRefreshTimeout = global.setInterval(
this.updateMemberList,
10000
);
}
}
// if we do not have a model, unregister for events
if (!newConversation) {
@ -203,6 +222,11 @@ export class SessionConversation extends React.Component<Props, State> {
div?.removeEventListener('dragleave', this.handleDragOut);
div?.removeEventListener('dragover', this.handleDrag);
div?.removeEventListener('drop', this.handleDrop);
if (this.publicMembersRefreshTimeout) {
global.clearInterval(this.publicMembersRefreshTimeout);
this.publicMembersRefreshTimeout = undefined;
}
}
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
@ -291,6 +315,8 @@ export class SessionConversation extends React.Component<Props, State> {
leftGroup={conversation.leftGroup}
isKickedFromGroup={conversation.isKickedFromGroup}
isPrivate={conversation.type === 'direct'}
isPublic={conversation.isPublic || false}
conversationKey={conversationKey}
sendMessage={sendMessageFn}
stagedAttachments={stagedAttachments}
onMessageSending={this.onMessageSending}
@ -1138,4 +1164,25 @@ export class SessionConversation extends React.Component<Props, State> {
this.setState({ isDraggingFile: false });
}
}
private async updateMemberList() {
const allPubKeys = await window.Signal.Data.getPubkeysInPublicConversation(
this.props.conversationKey
);
const allMembers = allPubKeys.map((pubKey: string) => {
const conv = window.ConversationController.get(pubKey);
let profileName = 'Anonymous';
if (conv) {
profileName = conv.getProfileName();
}
return {
id: pubKey,
authorPhoneNumber: pubKey,
authorProfileName: profileName,
};
});
window.lokiPublicChatAPI.setListOfMembers(allMembers);
}
}

@ -1,6 +1,6 @@
import React from 'react';
import styled from 'styled-components';
import { useNetwork } from './useNetwork';
import { useNetwork } from '../../../hooks/useNetwork';
type ContainerProps = {
show: boolean;

@ -33,7 +33,9 @@ export async function downloadAttachment(attachment: any) {
// FIXME "178" test to remove once this is fixed server side.
if (!res.response || !res.response.data || res.response.data.length === 178) {
if (res.response.data.length === 178) {
window.log.error('Data of 178 length corresponds of a 404 returned as 200 by file.getsession.org.');
window.log.error(
'Data of 178 length corresponds of a 404 returned as 200 by file.getsession.org.'
);
}
throw new Error(
`downloadAttachment: invalid response for ${attachment.url}`

@ -17,8 +17,7 @@ export function pushToastError(
title={title}
description={description}
type={SessionToastType.Error}
/>,
{ toastId: id }
/>
);
}
@ -32,8 +31,7 @@ export function pushToastWarning(
title={title}
description={description}
type={SessionToastType.Warning}
/>,
{ toastId: id }
/>
);
}
@ -43,8 +41,7 @@ export function pushToastInfo(id: string, title: string, description?: string) {
title={title}
description={description}
type={SessionToastType.Info}
/>,
{ toastId: id }
/>
);
}

1
ts/window.d.ts vendored

@ -71,7 +71,6 @@ declare global {
lokiMessageAPI: LokiMessageInterface;
lokiPublicChatAPI: LokiPublicChatFactoryInterface;
lokiSnodeAPI: LokiSnodeAPI;
lokiPublicChatAPI: LokiPublicChatFactoryAPI;
mnemonic: RecoveryPhraseUtil;
onLogin: any;
passwordUtil: any;

@ -105,6 +105,13 @@
resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.11.5.tgz#c7ff6303df71080ec7a4f5b8c003c58f1cf51037"
integrity sha512-X9rD8qqm695vgmeaQ4fvz/o3+Wk4ZzQvSHkDBgpYKxpD4qTAUm88ZKtHkVqIOsYFFbIQ6wQYhC6q7pjqVK0E0Q==
"@babel/runtime@7.4.5":
version "7.4.5"
resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.4.5.tgz#582bb531f5f9dc67d2fcb682979894f75e253f12"
integrity sha512-TuI4qpWZP6lGOGIuGWtp9sPluqYICmbk8T/1vpSysqJxRPkudh/ofFWyqdcMsDf2s7KvDL4/YHgKyvcS3g9CJQ==
dependencies:
regenerator-runtime "^0.13.2"
"@babel/runtime@^7.0.0", "@babel/runtime@^7.1.2":
version "7.8.7"
resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.8.7.tgz#8fefce9802db54881ba59f90bb28719b4996324d"
@ -119,6 +126,13 @@
dependencies:
regenerator-runtime "^0.13.4"
"@babel/runtime@^7.3.4", "@babel/runtime@^7.8.7":
version "7.12.5"
resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.12.5.tgz#410e7e487441e1b360c29be715d870d9b985882e"
integrity sha512-plcc+hbExy3McchJCEQG3knOsuh3HH+Prx1P6cLIkET/0dLuQDEnrT+s27Axgc9bqfsmNUNHfscgMUdBpC9xfg==
dependencies:
regenerator-runtime "^0.13.4"
"@babel/runtime@^7.5.5":
version "7.12.0"
resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.12.0.tgz#98bd7666186969c04be893d747cf4a6c6c8fa6b0"
@ -126,13 +140,6 @@
dependencies:
regenerator-runtime "^0.13.4"
"@babel/runtime@^7.8.7":
version "7.12.5"
resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.12.5.tgz#410e7e487441e1b360c29be715d870d9b985882e"
integrity sha512-plcc+hbExy3McchJCEQG3knOsuh3HH+Prx1P6cLIkET/0dLuQDEnrT+s27Axgc9bqfsmNUNHfscgMUdBpC9xfg==
dependencies:
regenerator-runtime "^0.13.4"
"@babel/template@^7.10.4":
version "7.10.4"
resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.10.4.tgz#3251996c4200ebc71d1a8fc405fba940f36ba278"
@ -599,6 +606,13 @@
dependencies:
"@types/react" "*"
"@types/react-mentions@^3.3.1":
version "3.3.1"
resolved "https://registry.yarnpkg.com/@types/react-mentions/-/react-mentions-3.3.1.tgz#f0fa1138f7fbf3012546260b634cbf23f735c0fe"
integrity sha512-o27Fb89lX4pGcA6lMkE+n7d/jqE9Zty3DYIUTVL0SBx8YV4uiqjI6d/34zVL2bUxq1mXSHb34hm2i358C/rRoA==
dependencies:
"@types/react" "*"
"@types/react-mic@^12.4.1":
version "12.4.1"
resolved "https://registry.yarnpkg.com/@types/react-mic/-/react-mic-12.4.1.tgz#7c261988c3be918b108df642a14101873b42846b"
@ -1276,11 +1290,6 @@ autoprefixer@^6.3.1:
postcss "^5.2.16"
postcss-value-parser "^3.2.3"
autosize@^4.0.2:
version "4.0.2"
resolved "https://registry.yarnpkg.com/autosize/-/autosize-4.0.2.tgz#073cfd07c8bf45da4b9fd153437f5bafbba1e4c9"
integrity sha512-jnSyH2d+qdfPGpWlcuhGiHmqBJ6g3X+8T+iRwFrHPLVcdoGJE/x6Qicm6aDHfTsbgZKxyV8UU/YB2p4cjKDRRA==
aws-sign2@~0.7.0:
version "0.7.0"
resolved "https://registry.yarnpkg.com/aws-sign2/-/aws-sign2-0.7.0.tgz#b46e890934a9591f2d2f6f86d7e6a9f1b3fe76a8"
@ -2450,11 +2459,6 @@ compression@^1.7.3:
safe-buffer "5.1.2"
vary "~1.1.2"
computed-style@~0.1.3:
version "0.1.4"
resolved "https://registry.yarnpkg.com/computed-style/-/computed-style-0.1.4.tgz#7f344fd8584b2e425bedca4a1afc0e300bb05d74"
integrity sha1-fzRP2FhLLkJb7cpKGvwOMAuwXXQ=
concat-map@0.0.1:
version "0.0.1"
resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b"
@ -5365,7 +5369,7 @@ internal-ip@1.2.0:
dependencies:
meow "^3.3.0"
invariant@^2.2.1, invariant@^2.2.2:
invariant@^2.2.1, invariant@^2.2.2, invariant@^2.2.4:
version "2.2.4"
resolved "https://registry.yarnpkg.com/invariant/-/invariant-2.2.4.tgz#610f3c92c9359ce1db616e538008d23ff35158e6"
integrity sha512-phJfQVBuaJM5raOpJjSfkiD6BpbCE4Ns//LaXl6wGYtUBY83nWS6Rf9tXm2e8VaK60JEjYldbPif/A2B1C2gNA==
@ -6237,13 +6241,6 @@ lie@*:
dependencies:
immediate "~3.0.5"
line-height@^0.3.1:
version "0.3.1"
resolved "https://registry.yarnpkg.com/line-height/-/line-height-0.3.1.tgz#4b1205edde182872a5efa3c8f620b3187a9c54c9"
integrity sha1-SxIF7d4YKHKl76PI9iCzGHqcVMk=
dependencies:
computed-style "~0.1.3"
linkify-it@2.0.3:
version "2.0.3"
resolved "https://registry.yarnpkg.com/linkify-it/-/linkify-it-2.0.3.tgz#d94a4648f9b1c179d64fa97291268bdb6ce9434f"
@ -8326,7 +8323,7 @@ promise-inflight@^1.0.1:
resolved "https://registry.yarnpkg.com/promise-inflight/-/promise-inflight-1.0.1.tgz#98472870bf228132fcbdd868129bad12c3c029e3"
integrity sha1-mEcocL8igTL8vdhoEputEsPAKeM=
prop-types@15.x, prop-types@^15.5.10, prop-types@^15.5.4, prop-types@^15.5.6, prop-types@^15.5.8, prop-types@^15.6.0, prop-types@^15.6.1, prop-types@^15.6.2, prop-types@^15.7.2:
prop-types@15.x, prop-types@^15.5.10, prop-types@^15.5.4, prop-types@^15.5.8, prop-types@^15.6.0, prop-types@^15.6.1, prop-types@^15.6.2, prop-types@^15.7.2:
version "15.7.2"
resolved "https://registry.yarnpkg.com/prop-types/-/prop-types-15.7.2.tgz#52c41e75b8c87e72b9d9360e0206b99dcbffa6c5"
integrity sha512-8QQikdH7//R2vurIJSutZ1smHYTcLpRWEOlHnzcWHmBYrOGUysKwSsrC89BCiFj3CbrfJ/nXFdJepOVrY1GCHQ==
@ -8649,15 +8646,6 @@ rc@^1.2.1, rc@^1.2.7, rc@^1.2.8:
minimist "^1.2.0"
strip-json-comments "~2.0.1"
react-autosize-textarea@^7.0.0:
version "7.0.0"
resolved "https://registry.yarnpkg.com/react-autosize-textarea/-/react-autosize-textarea-7.0.0.tgz#4f633e4238de7ba73c1da8fdc307353c50f1c5ab"
integrity sha512-rGQLpGUaELvzy3NKzp0kkcppaUtZTptsyR0PGuLotaJDjwRbT0DpD000yCzETpXseJQ/eMsyVGDDHXjXP93u8w==
dependencies:
autosize "^4.0.2"
line-height "^0.3.1"
prop-types "^15.5.6"
react-codemirror2@^4.2.1:
version "4.3.0"
resolved "https://registry.yarnpkg.com/react-codemirror2/-/react-codemirror2-4.3.0.tgz#e79aedca4da60d22402d2cd74f2885a3e5c009fd"
@ -8805,6 +8793,16 @@ react-lifecycles-compat@^3.0.4:
resolved "https://registry.yarnpkg.com/react-lifecycles-compat/-/react-lifecycles-compat-3.0.4.tgz#4f1a273afdfc8f3488a8c516bfda78f872352362"
integrity sha512-fBASbA6LnOU9dOU2eW7aQ8xmYBSXUIWr+UmF9b1efZBazGNO+rcXT/icdKnYm2pTwcRylVUYwW7H1PHfLekVzA==
react-mentions@^4.0.2:
version "4.0.2"
resolved "https://registry.yarnpkg.com/react-mentions/-/react-mentions-4.0.2.tgz#a3c06ee9b0da7c792227b0fb4620b04c9c891189"
integrity sha512-NlWzoAHWeeciewwUqH5jF0PT1wqVKCeHUwy5BvcMspDR3M22jPtqQLsTBJTcLuu+c7dzy/f3snaa0S+UJnyJiA==
dependencies:
"@babel/runtime" "7.4.5"
invariant "^2.2.4"
prop-types "^15.5.8"
substyle "^9.1.0"
react-portal@^4.2.0:
version "4.2.1"
resolved "https://registry.yarnpkg.com/react-portal/-/react-portal-4.2.1.tgz#12c1599238c06fb08a9800f3070bea2a3f78b1a6"
@ -9145,6 +9143,11 @@ regenerator-runtime@^0.11.0:
resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.11.1.tgz#be05ad7f9bf7d22e056f9726cee5017fbf19e2e9"
integrity sha512-MguG95oij0fC3QV3URf4V2SDYGJhJnJGqvIIgdECeODCT98wSWDAJ94SSuVpYQUoTcGUIL6L4yNB7j1DFFHSBg==
regenerator-runtime@^0.13.2:
version "0.13.7"
resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.13.7.tgz#cac2dacc8a1ea675feaabaeb8ae833898ae46f55"
integrity sha512-a54FxoJDIr27pgf7IgeQGxmqUNYrcV338lf/6gH456HZ/PhX+5BcwHXG9ajESmwe6WRO0tAzRUrRmNONWgkrew==
regenerator-runtime@^0.13.4:
version "0.13.5"
resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.13.5.tgz#d878a1d094b4306d10b9096484b33ebd55e26697"
@ -10354,6 +10357,14 @@ styled-components@5.1.1:
shallowequal "^1.1.0"
supports-color "^5.5.0"
substyle@^9.1.0:
version "9.3.0"
resolved "https://registry.yarnpkg.com/substyle/-/substyle-9.3.0.tgz#569af81723f74cd895b08b6b1e6bc06727f2a2bd"
integrity sha512-OK6A6EpqOfRvlwOnrgwFKIi8UDJwCQ2UB5cIJGMEFvl3zUUA83XDbRUJizECj66CdeZ9pGjkmwRxyc/9wBGQMA==
dependencies:
"@babel/runtime" "^7.3.4"
invariant "^2.2.4"
sumchecker@^2.0.2:
version "2.0.2"
resolved "https://registry.yarnpkg.com/sumchecker/-/sumchecker-2.0.2.tgz#0f42c10e5d05da5d42eea3e56c3399a37d6c5b3e"

Loading…
Cancel
Save