diff --git a/ts/quill/memberRepository.ts b/ts/quill/memberRepository.ts index cccf6fab0bdd..29ad861074c7 100644 --- a/ts/quill/memberRepository.ts +++ b/ts/quill/memberRepository.ts @@ -5,9 +5,37 @@ import Fuse from 'fuse.js'; import { get } from 'lodash'; import type { ConversationType } from '../state/ducks/conversations'; -import type { ServiceIdString } from '../types/ServiceId'; +import type { AciString } from '../types/ServiceId'; +import { isAciString } from '../types/ServiceId'; import { filter, map } from '../util/iterables'; import { removeDiacritics } from '../util/removeDiacritics'; +import { isNotNil } from '../util/isNotNil'; + +export type MemberType = Omit & + Readonly<{ + aci: AciString; + }>; + +function toMember({ + serviceId, + ...restOfConvo +}: ConversationType): MemberType | undefined { + if (!isAciString(serviceId)) { + return undefined; + } + + return { + ...restOfConvo, + aci: serviceId, + }; +} + +// Exported for testing +export function _toMembers( + conversations: ReadonlyArray +): Array { + return conversations.map(toMember).filter(isNotNil); +} const FUSE_OPTIONS = { location: 0, @@ -17,7 +45,7 @@ const FUSE_OPTIONS = { minMatchCharLength: 1, keys: ['name', 'firstName', 'profileName', 'title'], getFn( - conversation: Readonly, + conversation: Readonly, path: string | Array ): ReadonlyArray | string { // It'd be nice to avoid this cast, but Fuse's types don't allow it. @@ -40,21 +68,21 @@ const FUSE_OPTIONS = { }; export class MemberRepository { + private members: ReadonlyArray; private isFuseReady = false; - private fuse: Fuse = new Fuse( - [], - FUSE_OPTIONS - ); + private fuse = new Fuse([], FUSE_OPTIONS); - constructor(private members: ReadonlyArray = []) {} + constructor(conversations: ReadonlyArray = []) { + this.members = _toMembers(conversations); + } - updateMembers(members: ReadonlyArray): void { - this.members = members; + updateMembers(conversations: ReadonlyArray): void { + this.members = _toMembers(conversations); this.isFuseReady = false; } - getMembers(omit?: ConversationType): ReadonlyArray { + getMembers(omit?: Pick): ReadonlyArray { if (omit) { return this.members.filter(({ id }) => id !== omit.id); } @@ -62,26 +90,22 @@ export class MemberRepository { return this.members; } - getMemberById(id?: string): ConversationType | undefined { + getMemberById(id?: string): MemberType | undefined { return id ? this.members.find(({ id: memberId }) => memberId === id) : undefined; } - getMemberByServiceId( - serviceId?: ServiceIdString - ): ConversationType | undefined { - return serviceId - ? this.members.find( - ({ serviceId: memberServiceId }) => memberServiceId === serviceId - ) + getMemberByAci(aci?: AciString): MemberType | undefined { + return aci + ? this.members.find(({ aci: memberAci }) => memberAci === aci) : undefined; } search( pattern: string, - omit?: ConversationType - ): ReadonlyArray { + omit?: Pick + ): ReadonlyArray { if (!this.isFuseReady) { this.fuse.setCollection(this.members); this.isFuseReady = true; diff --git a/ts/quill/mentions/completion.tsx b/ts/quill/mentions/completion.tsx index 50819a2b92ed..10ce086fe9f8 100644 --- a/ts/quill/mentions/completion.tsx +++ b/ts/quill/mentions/completion.tsx @@ -13,9 +13,10 @@ import { createPortal } from 'react-dom'; import type { ConversationType } from '../../state/ducks/conversations'; import { Avatar, AvatarSize } from '../../components/Avatar'; import type { LocalizerType, ThemeType } from '../../types/Util'; -import type { MemberRepository } from '../memberRepository'; +import type { MemberType, MemberRepository } from '../memberRepository'; import type { PreferredBadgeSelectorType } from '../../state/selectors/badges'; import { matchBlotTextPartitions } from '../util'; +import type { MentionBlotValue } from '../util'; import { handleOutsideClick } from '../../util/handleOutsideClick'; import { sameWidthModifier } from '../../util/popperUtil'; import { UserText } from '../../components/UserText'; @@ -32,7 +33,7 @@ export type MentionCompletionOptions = { const MENTION_REGEX = /(?:^|\W)@([-+\p{L}\p{M}\p{N}]*)$/u; export class MentionCompletion { - results: ReadonlyArray; + results: ReadonlyArray; index: number; @@ -106,7 +107,7 @@ export class MentionCompletion { this.clearResults(); } - possiblyShowMemberResults(): ReadonlyArray { + possiblyShowMemberResults(): ReadonlyArray { const range = this.quill.getSelection(); if (range) { @@ -121,7 +122,7 @@ export class MentionCompletion { if (leftTokenTextMatch) { const [, leftTokenText] = leftTokenTextMatch; - let results: ReadonlyArray = []; + let results: ReadonlyArray = []; const memberRepository = this.options.memberRepositoryRef.current; @@ -194,7 +195,7 @@ export class MentionCompletion { } insertMention( - mention: ConversationType, + member: MemberType, index: number, range: number, withTrailingSpace = false @@ -202,6 +203,11 @@ export class MentionCompletion { // The mention + space we add won't be formatted unless we manually provide attributes const attributes = this.getAttributesForInsert(range - 1); + const mention: MentionBlotValue = { + aci: member.aci, + title: member.title, + }; + const delta = new Delta() .retain(index) .delete(range) @@ -261,7 +267,7 @@ export class MentionCompletion { {memberResults.map((member, index) => (