Add support for mentions in private group chats

pull/565/head
Maxim Shishmarev 6 years ago
parent 90f1d4a6aa
commit f5e9a870f7

@ -573,8 +573,12 @@
? expireTimerStart + expirationLength ? expireTimerStart + expirationLength
: null; : null;
// TODO: investigate why conversation is undefined
// for the public group chat
const conversation = this.getConversation(); const conversation = this.getConversation();
const isGroup = conversation && !conversation.isPrivate();
const convoId = conversation ? conversation.id : undefined;
const isGroup = !!conversation && !conversation.isPrivate();
const attachments = this.get('attachments') || []; const attachments = this.get('attachments') || [];
const firstAttachment = attachments[0]; const firstAttachment = attachments[0];
@ -592,6 +596,7 @@
authorProfileName: contact.profileName, authorProfileName: contact.profileName,
authorPhoneNumber: contact.phoneNumber, authorPhoneNumber: contact.phoneNumber,
conversationType: isGroup ? 'group' : 'direct', conversationType: isGroup ? 'group' : 'direct',
convoId,
attachments: attachments attachments: attachments
.filter(attachment => !attachment.error) .filter(attachment => !attachment.error)
.map(attachment => this.getPropsForAttachment(attachment)), .map(attachment => this.getPropsForAttachment(attachment)),

@ -2389,7 +2389,7 @@
// Note: schedule the member list handler shortly afterwards, so // Note: schedule the member list handler shortly afterwards, so
// that the input element has time to update its cursor position to // that the input element has time to update its cursor position to
// what the user would expect // what the user would expect
if (this.model.isPublic()) { if (this.model.get('type') === 'group') {
window.requestAnimationFrame(this.maybeShowMembers.bind(this, event)); window.requestAnimationFrame(this.maybeShowMembers.bind(this, event));
} }
@ -2521,10 +2521,31 @@
return query; return query;
}; };
let allMembers = window.lokiPublicChatAPI.getListOfMembers(); let allMembers;
allMembers = allMembers.filter(d => !!d);
allMembers = allMembers.filter(d => d.authorProfileName !== 'Anonymous'); if (this.model.isPublic()) {
allMembers = _.uniq(allMembers, true, d => d.authorPhoneNumber); let members = window.lokiPublicChatAPI.getListOfMembers();
members = members.filter(d => !!d);
members = members.filter(d => d.authorProfileName !== 'Anonymous');
allMembers = _.uniq(members, true, d => d.authorPhoneNumber);
} else {
const members = this.model.get('members');
if (!members || members.length === 0) {
return;
}
const privateConvos = window
.getConversations()
.models.filter(d => d.isPrivate());
const memberConvos = members
.map(m => privateConvos.find(c => c.id === m))
.filter(c => !!c);
allMembers = memberConvos.map(m => ({
id: m.id,
authorPhoneNumber: m.id,
authorProfileName: m.getLokiProfile().displayName,
}));
}
const cursorPos = event.target.selectionStart; const cursorPos = event.target.selectionStart;

@ -8,12 +8,14 @@ declare global {
lokiPublicChatAPI: any; lokiPublicChatAPI: any;
shortenPubkey: any; shortenPubkey: any;
pubkeyPattern: any; pubkeyPattern: any;
getConversations: any;
} }
} }
interface MentionProps { interface MentionProps {
key: number; key: number;
text: string; text: string;
convoId: string;
} }
interface MentionState { interface MentionState {
@ -78,13 +80,41 @@ class Mention extends React.Component<MentionProps, MentionState> {
} }
private findMember(pubkey: String) { private findMember(pubkey: String) {
const members = window.lokiPublicChatAPI.getListOfMembers(); let groupMembers;
if (!members) {
return null; const groupConvos = window.getConversations().models.filter((d: any) => {
return !d.isPrivate();
});
const thisConvo = groupConvos.find((d: any) => {
return d.id === this.props.convoId;
});
if (thisConvo.isPublic()) {
// TODO: make this work for other public chats as well
groupMembers = window.lokiPublicChatAPI
.getListOfMembers()
.filter((m: any) => !!m);
} else {
const privateConvos = window
.getConversations()
.models.filter((d: any) => d.isPrivate());
const members = thisConvo.attributes.members;
if (!members) {
return null;
}
const memberConversations = members
.map((m: any) => privateConvos.find((c: any) => c.id === m))
.filter((c: any) => !!c);
groupMembers = memberConversations.map((m: any) => {
return {
id: m.id,
authorPhoneNumber: m.id,
authorProfileName: m.getLokiProfile().displayName,
};
});
} }
const filtered = members.filter((m: any) => !!m);
return filtered.find( return groupMembers.find(
({ authorPhoneNumber: pn }: any) => pn && pn === pubkey ({ authorPhoneNumber: pn }: any) => pn && pn === pubkey
); );
} }
@ -93,6 +123,7 @@ class Mention extends React.Component<MentionProps, MentionState> {
interface Props { interface Props {
text: string; text: string;
renderOther?: RenderTextCallbackType; renderOther?: RenderTextCallbackType;
convoId: string;
} }
export class AddMentions extends React.Component<Props> { export class AddMentions extends React.Component<Props> {
@ -101,7 +132,7 @@ export class AddMentions extends React.Component<Props> {
}; };
public render() { public render() {
const { text, renderOther } = this.props; const { text, renderOther, convoId } = this.props;
const results: Array<any> = []; const results: Array<any> = [];
const FIND_MENTIONS = window.pubkeyPattern; const FIND_MENTIONS = window.pubkeyPattern;
@ -126,7 +157,7 @@ export class AddMentions extends React.Component<Props> {
} }
const pubkey = text.slice(match.index, FIND_MENTIONS.lastIndex); const pubkey = text.slice(match.index, FIND_MENTIONS.lastIndex);
results.push(<Mention text={pubkey} key={count++} />); results.push(<Mention text={pubkey} key={count++} convoId={convoId} />);
// @ts-ignore // @ts-ignore
last = FIND_MENTIONS.lastIndex; last = FIND_MENTIONS.lastIndex;

@ -6,6 +6,7 @@ interface Props {
text: string; text: string;
/** Allows you to customize now non-newlines are rendered. Simplest is just a <span>. */ /** Allows you to customize now non-newlines are rendered. Simplest is just a <span>. */
renderNonNewLine?: RenderTextCallbackType; renderNonNewLine?: RenderTextCallbackType;
convoId: string;
} }
export class AddNewLines extends React.Component<Props> { export class AddNewLines extends React.Component<Props> {
@ -14,7 +15,7 @@ export class AddNewLines extends React.Component<Props> {
}; };
public render() { public render() {
const { text, renderNonNewLine } = this.props; const { text, renderNonNewLine, convoId } = this.props;
const results: Array<any> = []; const results: Array<any> = [];
const FIND_NEWLINES = /\n/g; const FIND_NEWLINES = /\n/g;
@ -29,14 +30,14 @@ export class AddNewLines extends React.Component<Props> {
let count = 1; let count = 1;
if (!match) { if (!match) {
return renderNonNewLine({ text, key: 0 }); return renderNonNewLine({ text, key: 0, convoId });
} }
while (match) { while (match) {
if (last < match.index) { if (last < match.index) {
const textWithNoNewline = text.slice(last, match.index); const textWithNoNewline = text.slice(last, match.index);
results.push( results.push(
renderNonNewLine({ text: textWithNoNewline, key: count++ }) renderNonNewLine({ text: textWithNoNewline, key: count++, convoId })
); );
} }
@ -48,7 +49,9 @@ export class AddNewLines extends React.Component<Props> {
} }
if (last < text.length) { if (last < text.length) {
results.push(renderNonNewLine({ text: text.slice(last), key: count++ })); results.push(
renderNonNewLine({ text: text.slice(last), key: count++, convoId })
);
} }
return results; return results;

@ -56,17 +56,25 @@ interface Props {
/** Allows you to customize now non-newlines are rendered. Simplest is just a <span>. */ /** Allows you to customize now non-newlines are rendered. Simplest is just a <span>. */
renderNonEmoji?: RenderTextCallbackType; renderNonEmoji?: RenderTextCallbackType;
i18n: LocalizerType; i18n: LocalizerType;
isPublic?: boolean; isGroup?: boolean;
convoId: string;
} }
export class Emojify extends React.Component<Props> { export class Emojify extends React.Component<Props> {
public static defaultProps: Partial<Props> = { public static defaultProps: Partial<Props> = {
renderNonEmoji: ({ text }) => text || '', renderNonEmoji: ({ text }) => text || '',
isPublic: false, isGroup: false,
}; };
public render() { public render() {
const { text, sizeClass, renderNonEmoji, i18n, isPublic } = this.props; const {
text,
sizeClass,
renderNonEmoji,
i18n,
isGroup,
convoId,
} = this.props;
const results: Array<any> = []; const results: Array<any> = [];
const regex = getRegex(); const regex = getRegex();
@ -81,14 +89,19 @@ export class Emojify extends React.Component<Props> {
let count = 1; let count = 1;
if (!match) { if (!match) {
return renderNonEmoji({ text, key: 0, isPublic }); return renderNonEmoji({ text, key: 0, isGroup, convoId });
} }
while (match) { while (match) {
if (last < match.index) { if (last < match.index) {
const textWithNoEmoji = text.slice(last, match.index); const textWithNoEmoji = text.slice(last, match.index);
results.push( results.push(
renderNonEmoji({ text: textWithNoEmoji, key: count++, isPublic }) renderNonEmoji({
text: textWithNoEmoji,
key: count++,
isGroup,
convoId,
})
); );
} }
@ -100,7 +113,12 @@ export class Emojify extends React.Component<Props> {
if (last < text.length) { if (last < text.length) {
results.push( results.push(
renderNonEmoji({ text: text.slice(last), key: count++, isPublic }) renderNonEmoji({
text: text.slice(last),
key: count++,
isGroup,
convoId,
})
); );
} }

@ -93,6 +93,7 @@ export interface Props {
isExpired: boolean; isExpired: boolean;
expirationLength?: number; expirationLength?: number;
expirationTimestamp?: number; expirationTimestamp?: number;
convoId: string;
isP2p?: boolean; isP2p?: boolean;
isPublic?: boolean; isPublic?: boolean;
isRss?: boolean; isRss?: boolean;
@ -590,6 +591,7 @@ export class Message extends React.PureComponent<Props, State> {
i18n, i18n,
quote, quote,
isPublic, isPublic,
convoId,
} = this.props; } = this.props;
if (!quote) { if (!quote) {
@ -614,6 +616,8 @@ export class Message extends React.PureComponent<Props, State> {
text={quote.text} text={quote.text}
attachment={quote.attachment} attachment={quote.attachment}
isIncoming={direction === 'incoming'} isIncoming={direction === 'incoming'}
conversationType={conversationType}
convoId={convoId}
isPublic={isPublic} isPublic={isPublic}
authorPhoneNumber={displayedPubkey} authorPhoneNumber={displayedPubkey}
authorProfileName={quote.authorProfileName} authorProfileName={quote.authorProfileName}
@ -718,7 +722,16 @@ export class Message extends React.PureComponent<Props, State> {
} }
public renderText() { public renderText() {
const { text, textPending, i18n, direction, status, isRss } = this.props; const {
text,
textPending,
i18n,
direction,
status,
isRss,
conversationType,
convoId,
} = this.props;
const contents = const contents =
direction === 'incoming' && status === 'error' direction === 'incoming' && status === 'error'
@ -745,7 +758,8 @@ export class Message extends React.PureComponent<Props, State> {
isRss={isRss} isRss={isRss}
i18n={i18n} i18n={i18n}
textPending={textPending} textPending={textPending}
isPublic={this.props.isPublic} isGroup={conversationType === 'group'}
convoId={convoId}
/> />
</div> </div>
); );

@ -16,12 +16,13 @@ interface Props {
disableJumbomoji?: boolean; disableJumbomoji?: boolean;
/** If set, links will be left alone instead of turned into clickable `<a>` tags. */ /** If set, links will be left alone instead of turned into clickable `<a>` tags. */
disableLinks?: boolean; disableLinks?: boolean;
isPublic?: boolean; isGroup?: boolean;
i18n: LocalizerType; i18n: LocalizerType;
convoId: string;
} }
// eslint-disable-next-line
const renderMentions: RenderTextCallbackType = ({ text, key }) => ( const renderMentions: RenderTextCallbackType = ({ text, key, convoId }) => (
<AddMentions key={key} text={text} /> <AddMentions key={key} text={text} convoId={convoId} />
); );
const renderDefault: RenderTextCallbackType = ({ text }) => text; const renderDefault: RenderTextCallbackType = ({ text }) => text;
@ -29,15 +30,17 @@ const renderDefault: RenderTextCallbackType = ({ text }) => text;
const renderNewLines: RenderTextCallbackType = ({ const renderNewLines: RenderTextCallbackType = ({
text: textWithNewLines, text: textWithNewLines,
key, key,
isPublic, isGroup,
convoId,
}) => { }) => {
const renderOther = isPublic ? renderMentions : renderDefault; const renderOther = isGroup ? renderMentions : renderDefault;
return ( return (
<AddNewLines <AddNewLines
key={key} key={key}
text={textWithNewLines} text={textWithNewLines}
renderNonNewLine={renderOther} renderNonNewLine={renderOther}
convoId={convoId}
/> />
); );
}; };
@ -48,14 +51,16 @@ const renderEmoji = ({
key, key,
sizeClass, sizeClass,
renderNonEmoji, renderNonEmoji,
isPublic, isGroup,
convoId,
}: { }: {
i18n: LocalizerType; i18n: LocalizerType;
text: string; text: string;
key: number; key: number;
sizeClass?: SizeClassType; sizeClass?: SizeClassType;
renderNonEmoji: RenderTextCallbackType; renderNonEmoji: RenderTextCallbackType;
isPublic?: boolean; isGroup?: boolean;
convoId?: string;
}) => ( }) => (
<Emojify <Emojify
i18n={i18n} i18n={i18n}
@ -63,7 +68,8 @@ const renderEmoji = ({
text={text} text={text}
sizeClass={sizeClass} sizeClass={sizeClass}
renderNonEmoji={renderNonEmoji} renderNonEmoji={renderNonEmoji}
isPublic={isPublic} isGroup={isGroup}
convoId={convoId}
/> />
); );
@ -75,7 +81,7 @@ const renderEmoji = ({
*/ */
export class MessageBody extends React.Component<Props> { export class MessageBody extends React.Component<Props> {
public static defaultProps: Partial<Props> = { public static defaultProps: Partial<Props> = {
isPublic: false, isGroup: false,
}; };
public addDownloading(jsx: JSX.Element): JSX.Element { public addDownloading(jsx: JSX.Element): JSX.Element {
@ -102,7 +108,8 @@ export class MessageBody extends React.Component<Props> {
disableLinks, disableLinks,
isRss, isRss,
i18n, i18n,
isPublic, isGroup,
convoId,
} = this.props; } = this.props;
const sizeClass = disableJumbomoji ? undefined : getSizeClass(text); const sizeClass = disableJumbomoji ? undefined : getSizeClass(text);
const textWithPending = textPending ? `${text}...` : text; const textWithPending = textPending ? `${text}...` : text;
@ -115,7 +122,8 @@ export class MessageBody extends React.Component<Props> {
sizeClass, sizeClass,
key: 0, key: 0,
renderNonEmoji: renderNewLines, renderNonEmoji: renderNewLines,
isPublic, isGroup,
convoId,
}) })
); );
} }
@ -131,7 +139,8 @@ export class MessageBody extends React.Component<Props> {
sizeClass, sizeClass,
key, key,
renderNonEmoji: renderNewLines, renderNonEmoji: renderNewLines,
isPublic, isGroup,
convoId,
}); });
}} }}
/> />

@ -19,6 +19,8 @@ interface Props {
i18n: LocalizerType; i18n: LocalizerType;
isFromMe: boolean; isFromMe: boolean;
isIncoming: boolean; isIncoming: boolean;
conversationType: 'group' | 'direct';
convoId: string;
isPublic?: boolean; isPublic?: boolean;
withContentAbove: boolean; withContentAbove: boolean;
onClick?: () => void; onClick?: () => void;
@ -215,7 +217,14 @@ export class Quote extends React.Component<Props, State> {
} }
public renderText() { public renderText() {
const { i18n, text, attachment, isIncoming, isPublic } = this.props; const {
i18n,
text,
attachment,
isIncoming,
conversationType,
convoId,
} = this.props;
if (text) { if (text) {
return ( return (
@ -227,7 +236,8 @@ export class Quote extends React.Component<Props, State> {
)} )}
> >
<MessageBody <MessageBody
isPublic={isPublic} isGroup={conversationType === 'group'}
convoId={convoId}
text={text} text={text}
disableLinks={true} disableLinks={true}
i18n={i18n} i18n={i18n}

@ -2,7 +2,8 @@ export type RenderTextCallbackType = (
options: { options: {
text: string; text: string;
key: number; key: number;
isPublic?: boolean; isGroup?: boolean;
convoId?: string;
} }
) => JSX.Element | string; ) => JSX.Element | string;

Loading…
Cancel
Save