mirror of
https://github.com/oxen-io/session-desktop.git
synced 2023-12-14 02:12:57 +01:00
Add support for mentions in private group chats
This commit is contained in:
parent
90f1d4a6aa
commit
f5e9a870f7
9 changed files with 154 additions and 42 deletions
|
@ -573,8 +573,12 @@
|
|||
? expireTimerStart + expirationLength
|
||||
: null;
|
||||
|
||||
// TODO: investigate why conversation is undefined
|
||||
// for the public group chat
|
||||
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 firstAttachment = attachments[0];
|
||||
|
@ -592,6 +596,7 @@
|
|||
authorProfileName: contact.profileName,
|
||||
authorPhoneNumber: contact.phoneNumber,
|
||||
conversationType: isGroup ? 'group' : 'direct',
|
||||
convoId,
|
||||
attachments: attachments
|
||||
.filter(attachment => !attachment.error)
|
||||
.map(attachment => this.getPropsForAttachment(attachment)),
|
||||
|
|
|
@ -2389,7 +2389,7 @@
|
|||
// Note: schedule the member list handler shortly afterwards, so
|
||||
// that the input element has time to update its cursor position to
|
||||
// what the user would expect
|
||||
if (this.model.isPublic()) {
|
||||
if (this.model.get('type') === 'group') {
|
||||
window.requestAnimationFrame(this.maybeShowMembers.bind(this, event));
|
||||
}
|
||||
|
||||
|
@ -2521,10 +2521,31 @@
|
|||
return query;
|
||||
};
|
||||
|
||||
let allMembers = window.lokiPublicChatAPI.getListOfMembers();
|
||||
allMembers = allMembers.filter(d => !!d);
|
||||
allMembers = allMembers.filter(d => d.authorProfileName !== 'Anonymous');
|
||||
allMembers = _.uniq(allMembers, true, d => d.authorPhoneNumber);
|
||||
let allMembers;
|
||||
|
||||
if (this.model.isPublic()) {
|
||||
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;
|
||||
|
||||
|
|
|
@ -8,12 +8,14 @@ declare global {
|
|||
lokiPublicChatAPI: any;
|
||||
shortenPubkey: any;
|
||||
pubkeyPattern: any;
|
||||
getConversations: any;
|
||||
}
|
||||
}
|
||||
|
||||
interface MentionProps {
|
||||
key: number;
|
||||
text: string;
|
||||
convoId: string;
|
||||
}
|
||||
|
||||
interface MentionState {
|
||||
|
@ -78,13 +80,41 @@ class Mention extends React.Component<MentionProps, MentionState> {
|
|||
}
|
||||
|
||||
private findMember(pubkey: String) {
|
||||
const members = window.lokiPublicChatAPI.getListOfMembers();
|
||||
if (!members) {
|
||||
return null;
|
||||
}
|
||||
const filtered = members.filter((m: any) => !!m);
|
||||
let groupMembers;
|
||||
|
||||
return filtered.find(
|
||||
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,
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
return groupMembers.find(
|
||||
({ authorPhoneNumber: pn }: any) => pn && pn === pubkey
|
||||
);
|
||||
}
|
||||
|
@ -93,6 +123,7 @@ class Mention extends React.Component<MentionProps, MentionState> {
|
|||
interface Props {
|
||||
text: string;
|
||||
renderOther?: RenderTextCallbackType;
|
||||
convoId: string;
|
||||
}
|
||||
|
||||
export class AddMentions extends React.Component<Props> {
|
||||
|
@ -101,7 +132,7 @@ export class AddMentions extends React.Component<Props> {
|
|||
};
|
||||
|
||||
public render() {
|
||||
const { text, renderOther } = this.props;
|
||||
const { text, renderOther, convoId } = this.props;
|
||||
const results: Array<any> = [];
|
||||
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);
|
||||
results.push(<Mention text={pubkey} key={count++} />);
|
||||
results.push(<Mention text={pubkey} key={count++} convoId={convoId} />);
|
||||
|
||||
// @ts-ignore
|
||||
last = FIND_MENTIONS.lastIndex;
|
||||
|
|
|
@ -6,6 +6,7 @@ interface Props {
|
|||
text: string;
|
||||
/** Allows you to customize now non-newlines are rendered. Simplest is just a <span>. */
|
||||
renderNonNewLine?: RenderTextCallbackType;
|
||||
convoId: string;
|
||||
}
|
||||
|
||||
export class AddNewLines extends React.Component<Props> {
|
||||
|
@ -14,7 +15,7 @@ export class AddNewLines extends React.Component<Props> {
|
|||
};
|
||||
|
||||
public render() {
|
||||
const { text, renderNonNewLine } = this.props;
|
||||
const { text, renderNonNewLine, convoId } = this.props;
|
||||
const results: Array<any> = [];
|
||||
const FIND_NEWLINES = /\n/g;
|
||||
|
||||
|
@ -29,14 +30,14 @@ export class AddNewLines extends React.Component<Props> {
|
|||
let count = 1;
|
||||
|
||||
if (!match) {
|
||||
return renderNonNewLine({ text, key: 0 });
|
||||
return renderNonNewLine({ text, key: 0, convoId });
|
||||
}
|
||||
|
||||
while (match) {
|
||||
if (last < match.index) {
|
||||
const textWithNoNewline = text.slice(last, match.index);
|
||||
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) {
|
||||
results.push(renderNonNewLine({ text: text.slice(last), key: count++ }));
|
||||
results.push(
|
||||
renderNonNewLine({ text: text.slice(last), key: count++, convoId })
|
||||
);
|
||||
}
|
||||
|
||||
return results;
|
||||
|
|
|
@ -56,17 +56,25 @@ interface Props {
|
|||
/** Allows you to customize now non-newlines are rendered. Simplest is just a <span>. */
|
||||
renderNonEmoji?: RenderTextCallbackType;
|
||||
i18n: LocalizerType;
|
||||
isPublic?: boolean;
|
||||
isGroup?: boolean;
|
||||
convoId: string;
|
||||
}
|
||||
|
||||
export class Emojify extends React.Component<Props> {
|
||||
public static defaultProps: Partial<Props> = {
|
||||
renderNonEmoji: ({ text }) => text || '',
|
||||
isPublic: false,
|
||||
isGroup: false,
|
||||
};
|
||||
|
||||
public render() {
|
||||
const { text, sizeClass, renderNonEmoji, i18n, isPublic } = this.props;
|
||||
const {
|
||||
text,
|
||||
sizeClass,
|
||||
renderNonEmoji,
|
||||
i18n,
|
||||
isGroup,
|
||||
convoId,
|
||||
} = this.props;
|
||||
const results: Array<any> = [];
|
||||
const regex = getRegex();
|
||||
|
||||
|
@ -81,14 +89,19 @@ export class Emojify extends React.Component<Props> {
|
|||
let count = 1;
|
||||
|
||||
if (!match) {
|
||||
return renderNonEmoji({ text, key: 0, isPublic });
|
||||
return renderNonEmoji({ text, key: 0, isGroup, convoId });
|
||||
}
|
||||
|
||||
while (match) {
|
||||
if (last < match.index) {
|
||||
const textWithNoEmoji = text.slice(last, match.index);
|
||||
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) {
|
||||
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;
|
||||
expirationLength?: number;
|
||||
expirationTimestamp?: number;
|
||||
convoId: string;
|
||||
isP2p?: boolean;
|
||||
isPublic?: boolean;
|
||||
isRss?: boolean;
|
||||
|
@ -590,6 +591,7 @@ export class Message extends React.PureComponent<Props, State> {
|
|||
i18n,
|
||||
quote,
|
||||
isPublic,
|
||||
convoId,
|
||||
} = this.props;
|
||||
|
||||
if (!quote) {
|
||||
|
@ -614,6 +616,8 @@ export class Message extends React.PureComponent<Props, State> {
|
|||
text={quote.text}
|
||||
attachment={quote.attachment}
|
||||
isIncoming={direction === 'incoming'}
|
||||
conversationType={conversationType}
|
||||
convoId={convoId}
|
||||
isPublic={isPublic}
|
||||
authorPhoneNumber={displayedPubkey}
|
||||
authorProfileName={quote.authorProfileName}
|
||||
|
@ -718,7 +722,16 @@ export class Message extends React.PureComponent<Props, State> {
|
|||
}
|
||||
|
||||
public renderText() {
|
||||
const { text, textPending, i18n, direction, status, isRss } = this.props;
|
||||
const {
|
||||
text,
|
||||
textPending,
|
||||
i18n,
|
||||
direction,
|
||||
status,
|
||||
isRss,
|
||||
conversationType,
|
||||
convoId,
|
||||
} = this.props;
|
||||
|
||||
const contents =
|
||||
direction === 'incoming' && status === 'error'
|
||||
|
@ -745,7 +758,8 @@ export class Message extends React.PureComponent<Props, State> {
|
|||
isRss={isRss}
|
||||
i18n={i18n}
|
||||
textPending={textPending}
|
||||
isPublic={this.props.isPublic}
|
||||
isGroup={conversationType === 'group'}
|
||||
convoId={convoId}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
|
|
|
@ -16,12 +16,13 @@ interface Props {
|
|||
disableJumbomoji?: boolean;
|
||||
/** If set, links will be left alone instead of turned into clickable `<a>` tags. */
|
||||
disableLinks?: boolean;
|
||||
isPublic?: boolean;
|
||||
isGroup?: boolean;
|
||||
i18n: LocalizerType;
|
||||
convoId: string;
|
||||
}
|
||||
|
||||
const renderMentions: RenderTextCallbackType = ({ text, key }) => (
|
||||
<AddMentions key={key} text={text} />
|
||||
// eslint-disable-next-line
|
||||
const renderMentions: RenderTextCallbackType = ({ text, key, convoId }) => (
|
||||
<AddMentions key={key} text={text} convoId={convoId} />
|
||||
);
|
||||
|
||||
const renderDefault: RenderTextCallbackType = ({ text }) => text;
|
||||
|
@ -29,15 +30,17 @@ const renderDefault: RenderTextCallbackType = ({ text }) => text;
|
|||
const renderNewLines: RenderTextCallbackType = ({
|
||||
text: textWithNewLines,
|
||||
key,
|
||||
isPublic,
|
||||
isGroup,
|
||||
convoId,
|
||||
}) => {
|
||||
const renderOther = isPublic ? renderMentions : renderDefault;
|
||||
const renderOther = isGroup ? renderMentions : renderDefault;
|
||||
|
||||
return (
|
||||
<AddNewLines
|
||||
key={key}
|
||||
text={textWithNewLines}
|
||||
renderNonNewLine={renderOther}
|
||||
convoId={convoId}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
@ -48,14 +51,16 @@ const renderEmoji = ({
|
|||
key,
|
||||
sizeClass,
|
||||
renderNonEmoji,
|
||||
isPublic,
|
||||
isGroup,
|
||||
convoId,
|
||||
}: {
|
||||
i18n: LocalizerType;
|
||||
text: string;
|
||||
key: number;
|
||||
sizeClass?: SizeClassType;
|
||||
renderNonEmoji: RenderTextCallbackType;
|
||||
isPublic?: boolean;
|
||||
isGroup?: boolean;
|
||||
convoId?: string;
|
||||
}) => (
|
||||
<Emojify
|
||||
i18n={i18n}
|
||||
|
@ -63,7 +68,8 @@ const renderEmoji = ({
|
|||
text={text}
|
||||
sizeClass={sizeClass}
|
||||
renderNonEmoji={renderNonEmoji}
|
||||
isPublic={isPublic}
|
||||
isGroup={isGroup}
|
||||
convoId={convoId}
|
||||
/>
|
||||
);
|
||||
|
||||
|
@ -75,7 +81,7 @@ const renderEmoji = ({
|
|||
*/
|
||||
export class MessageBody extends React.Component<Props> {
|
||||
public static defaultProps: Partial<Props> = {
|
||||
isPublic: false,
|
||||
isGroup: false,
|
||||
};
|
||||
|
||||
public addDownloading(jsx: JSX.Element): JSX.Element {
|
||||
|
@ -102,7 +108,8 @@ export class MessageBody extends React.Component<Props> {
|
|||
disableLinks,
|
||||
isRss,
|
||||
i18n,
|
||||
isPublic,
|
||||
isGroup,
|
||||
convoId,
|
||||
} = this.props;
|
||||
const sizeClass = disableJumbomoji ? undefined : getSizeClass(text);
|
||||
const textWithPending = textPending ? `${text}...` : text;
|
||||
|
@ -115,7 +122,8 @@ export class MessageBody extends React.Component<Props> {
|
|||
sizeClass,
|
||||
key: 0,
|
||||
renderNonEmoji: renderNewLines,
|
||||
isPublic,
|
||||
isGroup,
|
||||
convoId,
|
||||
})
|
||||
);
|
||||
}
|
||||
|
@ -131,7 +139,8 @@ export class MessageBody extends React.Component<Props> {
|
|||
sizeClass,
|
||||
key,
|
||||
renderNonEmoji: renderNewLines,
|
||||
isPublic,
|
||||
isGroup,
|
||||
convoId,
|
||||
});
|
||||
}}
|
||||
/>
|
||||
|
|
|
@ -19,6 +19,8 @@ interface Props {
|
|||
i18n: LocalizerType;
|
||||
isFromMe: boolean;
|
||||
isIncoming: boolean;
|
||||
conversationType: 'group' | 'direct';
|
||||
convoId: string;
|
||||
isPublic?: boolean;
|
||||
withContentAbove: boolean;
|
||||
onClick?: () => void;
|
||||
|
@ -215,7 +217,14 @@ export class Quote extends React.Component<Props, State> {
|
|||
}
|
||||
|
||||
public renderText() {
|
||||
const { i18n, text, attachment, isIncoming, isPublic } = this.props;
|
||||
const {
|
||||
i18n,
|
||||
text,
|
||||
attachment,
|
||||
isIncoming,
|
||||
conversationType,
|
||||
convoId,
|
||||
} = this.props;
|
||||
|
||||
if (text) {
|
||||
return (
|
||||
|
@ -227,7 +236,8 @@ export class Quote extends React.Component<Props, State> {
|
|||
)}
|
||||
>
|
||||
<MessageBody
|
||||
isPublic={isPublic}
|
||||
isGroup={conversationType === 'group'}
|
||||
convoId={convoId}
|
||||
text={text}
|
||||
disableLinks={true}
|
||||
i18n={i18n}
|
||||
|
|
|
@ -2,7 +2,8 @@ export type RenderTextCallbackType = (
|
|||
options: {
|
||||
text: string;
|
||||
key: number;
|
||||
isPublic?: boolean;
|
||||
isGroup?: boolean;
|
||||
convoId?: string;
|
||||
}
|
||||
) => JSX.Element | string;
|
||||
|
||||
|
|
Loading…
Reference in a new issue