Merge pull request #47 from Bilb/allow-mention-yourself-everywhere

feat: allow to mention ourselves everywhere
pull/3281/head
Audric Ackermann 3 months ago committed by GitHub
commit 4f2330ea95
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194

@ -526,7 +526,7 @@ export class SessionConversation extends Component<Props, State> {
return {
id: pubKey,
authorProfileName: profileName,
display: profileName,
};
});

@ -1,10 +1,9 @@
import _, { debounce, isEmpty } from 'lodash';
import _, { debounce, isEmpty, uniq } from 'lodash';
import { connect } from 'react-redux';
import styled from 'styled-components';
import { AbortController } from 'abort-controller';
import { SuggestionDataItem } from 'react-mentions';
import autoBind from 'auto-bind';
import { Component, RefObject, createRef } from 'react';
@ -16,7 +15,7 @@ import { SessionRecording } from '../SessionRecording';
import { SettingsKey } from '../../../data/settings-key';
import { showLinkSharingConfirmationModalDialog } from '../../../interactions/conversationInteractions';
import { ConvoHub } from '../../../session/conversations';
import { ToastUtils } from '../../../session/utils';
import { ToastUtils, UserUtils } from '../../../session/utils';
import { ReduxConversationType } from '../../../state/ducks/conversations';
import { removeAllStagedAttachmentsInConversation } from '../../../state/ducks/stagedAttachments';
import { StateType } from '../../../state/reducer';
@ -32,7 +31,6 @@ import {
} from '../../../state/selectors/selectedConversation';
import { AttachmentType } from '../../../types/Attachment';
import { processNewAttachment } from '../../../types/MessageAttachment';
import { FixedBaseEmoji } from '../../../types/Reaction';
import { AttachmentUtil } from '../../../util';
import {
StagedAttachmentImportedType,
@ -60,6 +58,9 @@ import { CompositionTextArea } from './CompositionTextArea';
import { cleanMentions, mentionsRegex } from './UserMentions';
import { HTMLDirection } from '../../../util/i18n/rtlSupport';
import { PubKey } from '../../../session/types';
import { localize } from '../../../localization/localeTools';
import type { FixedBaseEmoji } from '../../../types/Reaction';
import type { SessionSuggestionDataItem } from './types';
export interface ReplyingToMessageProps {
convoId: string;
@ -441,7 +442,7 @@ class CompositionBoxInner extends Component<Props, State> {
}}
container={this.container}
textAreaRef={this.textarea}
fetchUsersForGroup={this.fetchUsersForGroup}
fetchMentionData={this.fetchMentionData}
typingEnabled={this.props.typingEnabled}
onKeyDown={this.onKeyDown}
/>
@ -464,85 +465,49 @@ class CompositionBoxInner extends Component<Props, State> {
);
}
/* eslint-enable @typescript-eslint/no-misused-promises */
private fetchUsersForOpenGroup(
query: string,
callback: (data: Array<SuggestionDataItem>) => void
) {
const mentionsInput = getMentionsInput(window?.inboxStore?.getState() || []);
const filtered =
mentionsInput
private filterMentionDataByQuery(query: string, mentionData: Array<SessionSuggestionDataItem>) {
return (
mentionData
.filter(d => !!d)
.filter(d => d.authorProfileName !== 'Anonymous')
.filter(d => d.authorProfileName?.toLowerCase()?.includes(query.toLowerCase()))
// Transform the users to what react-mentions expects
.map(user => {
return {
display: user.authorProfileName,
id: user.id,
};
}) || [];
callback(filtered);
}
private fetchUsersForGroup(query: string, callback: (data: Array<SuggestionDataItem>) => void) {
let overriddenQuery = query;
if (!query) {
overriddenQuery = '';
}
if (!this.props.selectedConversation) {
return;
}
if (this.props.selectedConversation.isPrivate) {
return;
}
if (this.props.selectedConversation.isPublic) {
this.fetchUsersForOpenGroup(overriddenQuery, callback);
return;
}
// can only be a closed group here
this.fetchUsersForClosedGroup(overriddenQuery, callback);
.filter(
d =>
d.display?.toLowerCase()?.includes(query.toLowerCase()) ||
d.id?.toLowerCase()?.includes(query.toLowerCase())
) || []
);
}
private fetchUsersForClosedGroup(
query: string,
callback: (data: Array<SuggestionDataItem>) => void
) {
private membersInThisChat(): Array<SessionSuggestionDataItem> {
const { selectedConversation } = this.props;
if (!selectedConversation) {
return;
return [];
}
const allPubKeys = selectedConversation.members;
if (!allPubKeys || allPubKeys.length === 0) {
return;
if (selectedConversation.isPublic) {
return getMentionsInput(window?.inboxStore?.getState() || []);
}
const allMembers = allPubKeys.map(pubKey => {
const convo = ConvoHub.use().get(pubKey);
const profileName = convo?.getNicknameOrRealUsernameOrPlaceholder() || PubKey.shorten(pubKey);
const members = selectedConversation.isPrivate
? uniq([UserUtils.getOurPubKeyStrFromCache(), selectedConversation.id])
: selectedConversation.members || [];
return members.map(m => {
return {
id: pubKey,
authorProfileName: profileName,
id: m,
display: UserUtils.isUsFromCache(m)
? localize('you').toString()
: ConvoHub.use().get(m)?.getNicknameOrRealUsernameOrPlaceholder() || PubKey.shorten(m),
};
});
// keep anonymous members so we can still quote them with their id
const members = allMembers
.filter(d => !!d)
.filter(
d =>
d.authorProfileName?.toLowerCase()?.includes(query.toLowerCase()) || !d.authorProfileName
);
}
private fetchMentionData(query: string): Array<SessionSuggestionDataItem> {
let overriddenQuery = query;
if (!query) {
overriddenQuery = '';
}
if (!this.props.selectedConversation) {
return [];
}
// Transform the users to what react-mentions expects
const mentionsData = members.map(user => ({
display: user.authorProfileName || PubKey.shorten(user.id),
id: user.id,
}));
callback(mentionsData);
return this.filterMentionDataByQuery(overriddenQuery, this.membersInThisChat());
}
private renderStagedLinkPreview(): JSX.Element | null {

@ -13,6 +13,7 @@ import { renderUserMentionRow, styleForCompositionBoxSuggestions } from './UserM
import { HTMLDirection, useHTMLDirection } from '../../../util/i18n/rtlSupport';
import { ConvoHub } from '../../../session/conversations';
import { Constants } from '../../../session';
import type { SessionSuggestionDataItem } from './types';
const sendMessageStyle = (dir?: HTMLDirection) => {
return {
@ -43,13 +44,13 @@ type Props = {
setDraft: (draft: string) => void;
container: RefObject<HTMLDivElement>;
textAreaRef: RefObject<HTMLTextAreaElement>;
fetchUsersForGroup: (query: string, callback: (data: any) => void) => void;
fetchMentionData: (query: string) => Array<SessionSuggestionDataItem>;
typingEnabled: boolean;
onKeyDown: (event: any) => void;
};
export const CompositionTextArea = (props: Props) => {
const { draft, setDraft, container, textAreaRef, fetchUsersForGroup, typingEnabled, onKeyDown } =
const { draft, setDraft, container, textAreaRef, fetchMentionData, typingEnabled, onKeyDown } =
props;
const [lastBumpTypingMessageLength, setLastBumpTypingMessageLength] = useState(0);
@ -139,10 +140,10 @@ export const CompositionTextArea = (props: Props) => {
markup="@ᅭ__id__ᅲ__display__ᅭ" // ᅭ = \uFFD2 is one of the forbidden char for a display name (check displayNameRegex)
trigger="@"
// this is only for the composition box visible content. The real stuff on the backend box is the @markup
displayTransform={(_id, display) =>
htmlDirection === 'rtl' ? `${display}@` : `@${display}`
}
data={fetchUsersForGroup}
displayTransform={(_id, display) => {
return htmlDirection === 'rtl' ? `${display}@` : `@${display}`;
}}
data={fetchMentionData}
renderSuggestion={renderUserMentionRow}
/>
<Mention

@ -1,7 +1,8 @@
import { SearchIndex } from 'emoji-mart';
import { SuggestionDataItem } from 'react-mentions';
import styled from 'styled-components';
import type { SuggestionDataItem } from 'react-mentions';
import { searchSync } from '../../../util/emoji';
import type { SessionSuggestionDataItem } from './types';
const EmojiQuickResult = styled.span`
display: flex;
@ -27,7 +28,7 @@ export const renderEmojiQuickResultRow = (suggestion: SuggestionDataItem) => {
);
};
export const searchEmojiForQuery = (query: string): Array<SuggestionDataItem> => {
export const searchEmojiForQuery = (query: string): Array<SessionSuggestionDataItem> => {
if (query.length === 0 || !SearchIndex) {
return [];
}

@ -1,4 +1,4 @@
import { SuggestionDataItem } from 'react-mentions';
import type { SuggestionDataItem } from 'react-mentions';
import { MemberListItem } from '../../MemberListItem';
import { HTMLDirection } from '../../../util/i18n/rtlSupport';
@ -36,7 +36,7 @@ export const styleForCompositionBoxSuggestions = (dir: HTMLDirection = 'ltr') =>
return styles;
};
export const renderUserMentionRow = (suggestion: SuggestionDataItem) => {
export const renderUserMentionRow = (suggestion: Pick<SuggestionDataItem, 'id'>) => {
return (
<MemberListItem
key={`suggestion-list-${suggestion.id}`}

@ -0,0 +1,4 @@
export interface SessionSuggestionDataItem {
id: string;
display: string;
}

@ -1,6 +1,6 @@
import type { SetupI18nReturnType } from '../types/localizer';
import { setupI18n } from '../util/i18n/i18n';
import {CrowdinLocale, isCrowdinLocale} from '../localization/constants';
import { CrowdinLocale, isCrowdinLocale } from '../localization/constants';
export function normalizeLocaleName(locale: string) {
const dashedLocale = locale.replaceAll('_', '-');
@ -28,7 +28,7 @@ export function normalizeLocaleName(locale: string) {
}
function resolveLocale(crowdinLocale: string): CrowdinLocale {
const locale = normalizeLocaleName(crowdinLocale)
const locale = normalizeLocaleName(crowdinLocale);
if (isCrowdinLocale(locale)) {
return locale;
}

@ -27,6 +27,7 @@ import { AttachmentType } from '../../types/Attachment';
import { CONVERSATION_PRIORITIES, ConversationTypeEnum } from '../../models/types';
import { WithConvoId, WithMessageHash, WithMessageId } from '../../session/types/with';
import { cancelUpdatesToDispatch } from '../../models/message';
import type { SessionSuggestionDataItem } from '../../components/conversation/composition/types';
export type MessageModelPropsWithoutConvoProps = {
propsForMessage: PropsForMessageWithoutConvoProps;
@ -295,14 +296,9 @@ export type ConversationsStateType = {
animateQuotedMessageId?: string;
shouldHighlightMessage: boolean;
nextMessageToPlayId?: string;
mentionMembers: MentionsMembersType;
mentionMembers: Array<SessionSuggestionDataItem>;
};
export type MentionsMembersType = Array<{
id: string;
authorProfileName: string;
}>;
function buildQuoteId(sender: string, timestamp: number) {
return `${timestamp}-${sender}`;
}
@ -915,7 +911,7 @@ const conversationsSlice = createSlice({
},
updateMentionsMembers(
state: ConversationsStateType,
action: PayloadAction<MentionsMembersType>
action: PayloadAction<Array<SessionSuggestionDataItem>>
) {
window?.log?.info('updating mentions input members length', action.payload?.length);
state.mentionMembers = action.payload;

@ -8,7 +8,6 @@ import {
ConversationLookupType,
ConversationsStateType,
lookupQuote,
MentionsMembersType,
MessageModelPropsWithConvoProps,
MessageModelPropsWithoutConvoProps,
PropsForQuote,
@ -40,6 +39,7 @@ import { PubKey } from '../../session/types';
import { UserGroupsWrapperActions } from '../../webworker/workers/browser/libsession_worker_interface';
import { getSelectedConversationKey } from './selectedConversation';
import { getModeratorsOutsideRedux } from './sogsRoomInfo';
import type { SessionSuggestionDataItem } from '../../components/conversation/composition/types';
export const getConversations = (state: StateType): ConversationsStateType => state.conversations;
@ -532,7 +532,7 @@ export const getShouldHighlightMessage = (state: StateType): boolean =>
export const getNextMessageToPlayId = (state: StateType): string | undefined =>
state.conversations.nextMessageToPlayId || undefined;
export const getMentionsInput = (state: StateType): MentionsMembersType =>
export const getMentionsInput = (state: StateType): Array<SessionSuggestionDataItem> =>
state.conversations.mentionMembers;
/// Those calls are just related to ordering messages in the redux store.

Loading…
Cancel
Save