From 1bbe5d4cd2d6664bac04939698dfdaf9496bf534 Mon Sep 17 00:00:00 2001 From: Alun Turner Date: Tue, 28 Mar 2023 14:44:46 +0100 Subject: [PATCH 01/25] refactor to prep for avatar work --- .../components/WysiwygAutocomplete.tsx | 85 +++++++++++-------- 1 file changed, 48 insertions(+), 37 deletions(-) diff --git a/src/components/views/rooms/wysiwyg_composer/components/WysiwygAutocomplete.tsx b/src/components/views/rooms/wysiwyg_composer/components/WysiwygAutocomplete.tsx index 5ad7b078841..620fdb5ad1a 100644 --- a/src/components/views/rooms/wysiwyg_composer/components/WysiwygAutocomplete.tsx +++ b/src/components/views/rooms/wysiwyg_composer/components/WysiwygAutocomplete.tsx @@ -16,7 +16,7 @@ limitations under the License. import React, { ForwardedRef, forwardRef } from "react"; import { MatrixClient, Room } from "matrix-js-sdk/src/matrix"; -import { FormattingFunctions, MappedSuggestion } from "@matrix-org/matrix-wysiwyg"; +import { Attributes, FormattingFunctions, MappedSuggestion } from "@matrix-org/matrix-wysiwyg"; import { useRoomContext } from "../../../../../contexts/RoomContext"; import Autocomplete from "../../Autocomplete"; @@ -56,34 +56,50 @@ function buildQuery(suggestion: MappedSuggestion | null): string { } /** - * Given a room type mention, determine the text that should be displayed in the mention - * TODO expand this function to more generally handle outputting the display text from a - * given completion + * Given an autocomplete suggestion, determine the text to display in the pill * - * @param completion - the item selected from the autocomplete, currently treated as a room completion + * @param completion - the item selected from the autocomplete * @param client - the MatrixClient is required for us to look up the correct room mention text * @returns the text to display in the mention */ -function getRoomMentionText(completion: ICompletion, client: MatrixClient): string { - const roomId = completion.completionId; - const alias = completion.completion; - - let roomForAutocomplete: Room | null | undefined; - - // Not quite sure if the logic here makes sense - specifically calling .getRoom with an alias - // that doesn't start with #, but keeping the logic the same as in PartCreator.roomPill for now - if (roomId) { - roomForAutocomplete = client.getRoom(roomId); - } else if (!alias.startsWith("#")) { - roomForAutocomplete = client.getRoom(alias); - } else { - roomForAutocomplete = client.getRooms().find((r) => { - return r.getCanonicalAlias() === alias || r.getAltAliases().includes(alias); - }); +function getMentionDisplayText(completion: ICompletion, client: MatrixClient): string { + if (completion.type === "user") { + return completion.completion; + } else if (completion.type === "room") { + const roomId = completion.completionId; + const alias = completion.completion; + + let roomForAutocomplete: Room | null | undefined; + + // Not quite sure if the logic here makes sense - specifically calling .getRoom with an alias + // that doesn't start with #, but keeping the logic the same as in PartCreator.roomPill for now + if (roomId) { + roomForAutocomplete = client.getRoom(roomId); + } else if (!alias.startsWith("#")) { + roomForAutocomplete = client.getRoom(alias); + } else { + roomForAutocomplete = client.getRooms().find((r) => { + return r.getCanonicalAlias() === alias || r.getAltAliases().includes(alias); + }); + } + + // if we haven't managed to find the room, use the alias as a fallback + return roomForAutocomplete?.name || alias; } +} - // if we haven't managed to find the room, use the alias as a fallback - return roomForAutocomplete?.name || alias; +/** + * For a given completion, the attributes will change depending on the completion type + * + * @param completion - the item selected from the autocomplete + * @param client - the MatrixClient is required for us to look up the correct room mention text + * @returns an object of attributes containing HTMLAnchor attributes or data-* attri + */ +function getMentionAttributes(completion: ICompletion, client: MatrixClient): Attributes { + return { + "data-mention-type": completion.type, + "style": "this-will-be-the-avatar-url", + }; } /** @@ -101,20 +117,15 @@ const WysiwygAutocomplete = forwardRef( function handleConfirm(completion: ICompletion): void { if (!completion.href || !client) return; - switch (completion.type) { - case "user": - handleMention(completion.href, completion.completion); - break; - case "room": { - handleMention(completion.href, getRoomMentionText(completion, client)); - break; - } - // TODO implement the command functionality - // case "command": - // console.log("/command functionality not yet in place"); - // break; - default: - break; + console.log(completion, " <<< comp "); + + // TODO handle the other type functionality like completion.type === "command" + if (completion.type === "room" || completion.type === "user") { + handleMention( + completion.href, + getMentionDisplayText(completion, client), + getMentionAttributes(completion, client), + ); } } From d1b5a3128493b0843b44b61f6a54a39d0b476b39 Mon Sep 17 00:00:00 2001 From: Alun Turner Date: Tue, 28 Mar 2023 16:06:06 +0100 Subject: [PATCH 02/25] make an avatar appear --- .../wysiwyg_composer/components/_Editor.pcss | 19 +++++++++ .../components/WysiwygAutocomplete.tsx | 39 ++++++++++++++++--- 2 files changed, 52 insertions(+), 6 deletions(-) diff --git a/res/css/views/rooms/wysiwyg_composer/components/_Editor.pcss b/res/css/views/rooms/wysiwyg_composer/components/_Editor.pcss index b15d3b74c17..0d6905daf17 100644 --- a/res/css/views/rooms/wysiwyg_composer/components/_Editor.pcss +++ b/res/css/views/rooms/wysiwyg_composer/components/_Editor.pcss @@ -132,6 +132,25 @@ limitations under the License. cursor: unset; /* We don't want indicate clickability */ text-overflow: ellipsis; white-space: nowrap; + + /* avatar psuedo element */ + &::before { + display: inline-block; + content: var(--avatar-letter); + width: $font-16px; + min-width: $font-16px; /* ensure the avatar is not compressed */ + height: $font-16px; + margin-inline-end: 0.24rem; + background: var(--avatar-background), $background; + color: $avatar-initial-color; + background-repeat: no-repeat; + background-size: $font-16px; + border-radius: $font-16px; + text-align: center; + font-weight: normal; + line-height: $font-16px; + font-size: $font-10-4px; + } } } diff --git a/src/components/views/rooms/wysiwyg_composer/components/WysiwygAutocomplete.tsx b/src/components/views/rooms/wysiwyg_composer/components/WysiwygAutocomplete.tsx index 620fdb5ad1a..57d559cad06 100644 --- a/src/components/views/rooms/wysiwyg_composer/components/WysiwygAutocomplete.tsx +++ b/src/components/views/rooms/wysiwyg_composer/components/WysiwygAutocomplete.tsx @@ -18,6 +18,7 @@ import React, { ForwardedRef, forwardRef } from "react"; import { MatrixClient, Room } from "matrix-js-sdk/src/matrix"; import { Attributes, FormattingFunctions, MappedSuggestion } from "@matrix-org/matrix-wysiwyg"; +import * as Avatar from "../../../../../Avatar"; import { useRoomContext } from "../../../../../contexts/RoomContext"; import Autocomplete from "../../Autocomplete"; import { ICompletion } from "../../../../../autocomplete/Autocompleter"; @@ -95,10 +96,36 @@ function getMentionDisplayText(completion: ICompletion, client: MatrixClient): s * @param client - the MatrixClient is required for us to look up the correct room mention text * @returns an object of attributes containing HTMLAnchor attributes or data-* attri */ -function getMentionAttributes(completion: ICompletion, client: MatrixClient): Attributes { +function getMentionAttributes(completion: ICompletion, client: MatrixClient, room: Room): Attributes { + let background = "background"; + let letter = "letter"; + if (completion.type === "user") { + // TODO try and get the avatar background url and avatar letter for a user + // looks like we need a RoomMember + // which you get by calling this.room.getMember(userId) + // and it looks like the userId is actually the display name which is completion.completion + const member = room.getMember(completion.completionId); + + if (!member) return; + + const name = member.name || member.userId; + const defaultAvatarUrl = Avatar.defaultAvatarUrlForString(member.userId); + const avatarUrl = Avatar.avatarUrlForMember(member, 16, 16, "crop"); + let initialLetter = ""; + if (avatarUrl === defaultAvatarUrl) { + initialLetter = Avatar.getInitialLetter(name) ?? ""; + } + + background = `url(${avatarUrl})`; + letter = `'${initialLetter}'`; // not a mistake, need to ensure it's there + } + if (completion.type === "room") { + // TODO try and get the avatar background url and avatar letter for a room + } + return { "data-mention-type": completion.type, - "style": "this-will-be-the-avatar-url", + "style": `--avatar-background: ${background}; --avatar-letter: ${letter}`, }; } @@ -117,14 +144,14 @@ const WysiwygAutocomplete = forwardRef( function handleConfirm(completion: ICompletion): void { if (!completion.href || !client) return; - console.log(completion, " <<< comp "); - - // TODO handle the other type functionality like completion.type === "command" + // for now we can use this if to make sure we handle only the mentions we know we can handle properly + // in the model + // TODO handle all of the completion types if (completion.type === "room" || completion.type === "user") { handleMention( completion.href, getMentionDisplayText(completion, client), - getMentionAttributes(completion, client), + getMentionAttributes(completion, client, room), ); } } From 852ad1a1bec20c7d64c96a4938f83ff19e77da82 Mon Sep 17 00:00:00 2001 From: Alun Turner Date: Wed, 29 Mar 2023 09:27:27 +0100 Subject: [PATCH 03/25] align user avatar correctly --- .../wysiwyg_composer/components/_Editor.pcss | 27 ++++++++++++++++--- 1 file changed, 24 insertions(+), 3 deletions(-) diff --git a/res/css/views/rooms/wysiwyg_composer/components/_Editor.pcss b/res/css/views/rooms/wysiwyg_composer/components/_Editor.pcss index 0d6905daf17..be7bd454020 100644 --- a/res/css/views/rooms/wysiwyg_composer/components/_Editor.pcss +++ b/res/css/views/rooms/wysiwyg_composer/components/_Editor.pcss @@ -110,7 +110,7 @@ limitations under the License. padding: $font-1px 0.4em; line-height: $font-17px; border-radius: $font-16px; - vertical-align: text-top; + /* vertical-align: text-top; turning this off stops the vertical alignement issue */ /* TODO turning this on hides the cursor from the composer for some reason, so comment out for now and assess if it's needed when we add the Avatars @@ -119,6 +119,7 @@ limitations under the License. Potential fix is using display: inline, width: fit-content */ + display: inline; /* this, combined with omitting vertical-align, looks good */ box-sizing: border-box; max-width: 100%; overflow: hidden; @@ -133,8 +134,19 @@ limitations under the License. text-overflow: ellipsis; white-space: nowrap; - /* avatar psuedo element */ + /* avatar pseudo element */ + /* TODO figure out why the application of this alters the + * vertical alignment of the pill - bottom appears clipped and + * the avatar appears too high (but I think it may be correct, + * it just looks this way because the body of the pill is lower) + */ &::before { + /* from _Pill */ + margin-inline-start: -0.3em; /* Otherwise the gap is too large */ + margin-inline-end: 0.2em; + min-width: $font-16px; /* ensure the avatar is not compressed */ + + /* from _BasicMessageComposer */ display: inline-block; content: var(--avatar-letter); width: $font-16px; @@ -148,8 +160,17 @@ limitations under the License. border-radius: $font-16px; text-align: center; font-weight: normal; - line-height: $font-16px; + line-height: $font-17px; /* amend to match a text */ font-size: $font-10-4px; + + /* TODO consolidate this whole block of CSS - there's some useless + * and overwritten stuff here, so pick the useful stuff out and + * arrange into a block that makes sense to read + */ + + /* overrides to get the positioning just right */ + vertical-align: -3px; + margin-inline-start: -0.4rem; } } } From 0a78b162d570e93d818ed8aa5ae80adae8a5b523 Mon Sep 17 00:00:00 2001 From: Alun Turner Date: Wed, 29 Mar 2023 09:37:07 +0100 Subject: [PATCH 04/25] add room avatar logic --- .../components/WysiwygAutocomplete.tsx | 28 +++++++++++++++---- 1 file changed, 23 insertions(+), 5 deletions(-) diff --git a/src/components/views/rooms/wysiwyg_composer/components/WysiwygAutocomplete.tsx b/src/components/views/rooms/wysiwyg_composer/components/WysiwygAutocomplete.tsx index 57d559cad06..556257d7567 100644 --- a/src/components/views/rooms/wysiwyg_composer/components/WysiwygAutocomplete.tsx +++ b/src/components/views/rooms/wysiwyg_composer/components/WysiwygAutocomplete.tsx @@ -100,10 +100,6 @@ function getMentionAttributes(completion: ICompletion, client: MatrixClient, roo let background = "background"; let letter = "letter"; if (completion.type === "user") { - // TODO try and get the avatar background url and avatar letter for a user - // looks like we need a RoomMember - // which you get by calling this.room.getMember(userId) - // and it looks like the userId is actually the display name which is completion.completion const member = room.getMember(completion.completionId); if (!member) return; @@ -120,7 +116,29 @@ function getMentionAttributes(completion: ICompletion, client: MatrixClient, roo letter = `'${initialLetter}'`; // not a mistake, need to ensure it's there } if (completion.type === "room") { - // TODO try and get the avatar background url and avatar letter for a room + console.log(completion, "<< { + return r.getCanonicalAlias() === alias || r.getAltAliases().includes(alias); + }); + } + + let initialLetter = ""; + let avatarUrl = Avatar.avatarUrlForRoom(room ?? null, 16, 16, "crop"); + if (!avatarUrl) { + initialLetter = Avatar.getInitialLetter(room?.name || alias) ?? ""; + avatarUrl = Avatar.defaultAvatarUrlForString(room?.roomId ?? alias); + } + + background = `url(${avatarUrl})`; + letter = `'${initialLetter}'`; // not a mistake, need to ensure it's there } return { From ae27f565e08f199fb00680c30c178ea3d151db89 Mon Sep 17 00:00:00 2001 From: Alun Turner Date: Wed, 29 Mar 2023 09:48:05 +0100 Subject: [PATCH 05/25] add another util for finding room from the client --- .../components/WysiwygAutocomplete.tsx | 31 +++++++++++++++++-- 1 file changed, 29 insertions(+), 2 deletions(-) diff --git a/src/components/views/rooms/wysiwyg_composer/components/WysiwygAutocomplete.tsx b/src/components/views/rooms/wysiwyg_composer/components/WysiwygAutocomplete.tsx index 556257d7567..3a54d5cbd94 100644 --- a/src/components/views/rooms/wysiwyg_composer/components/WysiwygAutocomplete.tsx +++ b/src/components/views/rooms/wysiwyg_composer/components/WysiwygAutocomplete.tsx @@ -56,6 +56,35 @@ function buildQuery(suggestion: MappedSuggestion | null): string { return `${suggestion.keyChar}${suggestion.text}`; } +/** + * Find the room from the completion by looking it up using the client from the context + * we are currently in + * + * @param completion - the completion from the autocomplete + * @param client - the current client we are using + * @returns a Room if one is found, null otherwise + */ +function getRoomFromCompletion(completion: ICompletion, client: MatrixClient): Room | null { + const roomId = completion.completionId; + const alias = completion.completion; + + let roomToReturn: Room | null | undefined; + + // Not quite sure if the logic here makes sense - specifically calling .getRoom with an alias + // that doesn't start with #, but keeping the logic the same as in PartCreator.roomPill for now + if (roomId) { + roomToReturn = client.getRoom(roomId); + } else if (!alias.startsWith("#")) { + roomToReturn = client.getRoom(alias); + } else { + roomToReturn = client.getRooms().find((r) => { + return r.getCanonicalAlias() === alias || r.getAltAliases().includes(alias); + }); + } + + return roomToReturn ?? null; +} + /** * Given an autocomplete suggestion, determine the text to display in the pill * @@ -116,7 +145,6 @@ function getMentionAttributes(completion: ICompletion, client: MatrixClient, roo letter = `'${initialLetter}'`; // not a mistake, need to ensure it's there } if (completion.type === "room") { - console.log(completion, "<< Date: Wed, 29 Mar 2023 09:55:17 +0100 Subject: [PATCH 06/25] use newly extracted util function to reduce repetition --- .../components/WysiwygAutocomplete.tsx | 63 ++++++------------- 1 file changed, 18 insertions(+), 45 deletions(-) diff --git a/src/components/views/rooms/wysiwyg_composer/components/WysiwygAutocomplete.tsx b/src/components/views/rooms/wysiwyg_composer/components/WysiwygAutocomplete.tsx index 3a54d5cbd94..5eb486a390a 100644 --- a/src/components/views/rooms/wysiwyg_composer/components/WysiwygAutocomplete.tsx +++ b/src/components/views/rooms/wysiwyg_composer/components/WysiwygAutocomplete.tsx @@ -66,7 +66,7 @@ function buildQuery(suggestion: MappedSuggestion | null): string { */ function getRoomFromCompletion(completion: ICompletion, client: MatrixClient): Room | null { const roomId = completion.completionId; - const alias = completion.completion; + const aliasFromCompletion = completion.completion; let roomToReturn: Room | null | undefined; @@ -74,11 +74,11 @@ function getRoomFromCompletion(completion: ICompletion, client: MatrixClient): R // that doesn't start with #, but keeping the logic the same as in PartCreator.roomPill for now if (roomId) { roomToReturn = client.getRoom(roomId); - } else if (!alias.startsWith("#")) { - roomToReturn = client.getRoom(alias); + } else if (!aliasFromCompletion.startsWith("#")) { + roomToReturn = client.getRoom(aliasFromCompletion); } else { roomToReturn = client.getRooms().find((r) => { - return r.getCanonicalAlias() === alias || r.getAltAliases().includes(alias); + return r.getCanonicalAlias() === aliasFromCompletion || r.getAltAliases().includes(aliasFromCompletion); }); } @@ -96,25 +96,9 @@ function getMentionDisplayText(completion: ICompletion, client: MatrixClient): s if (completion.type === "user") { return completion.completion; } else if (completion.type === "room") { - const roomId = completion.completionId; - const alias = completion.completion; - - let roomForAutocomplete: Room | null | undefined; - - // Not quite sure if the logic here makes sense - specifically calling .getRoom with an alias - // that doesn't start with #, but keeping the logic the same as in PartCreator.roomPill for now - if (roomId) { - roomForAutocomplete = client.getRoom(roomId); - } else if (!alias.startsWith("#")) { - roomForAutocomplete = client.getRoom(alias); - } else { - roomForAutocomplete = client.getRooms().find((r) => { - return r.getCanonicalAlias() === alias || r.getAltAliases().includes(alias); - }); - } - - // if we haven't managed to find the room, use the alias as a fallback - return roomForAutocomplete?.name || alias; + // try and get the room and use it's name, if not available, fall back to + // completion.completion + return getRoomFromCompletion(completion, client)?.name || completion.completion; } } @@ -129,13 +113,13 @@ function getMentionAttributes(completion: ICompletion, client: MatrixClient, roo let background = "background"; let letter = "letter"; if (completion.type === "user") { - const member = room.getMember(completion.completionId); + const mentionedMember = room.getMember(completion.completionId); - if (!member) return; + if (!mentionedMember) return; - const name = member.name || member.userId; - const defaultAvatarUrl = Avatar.defaultAvatarUrlForString(member.userId); - const avatarUrl = Avatar.avatarUrlForMember(member, 16, 16, "crop"); + const name = mentionedMember.name || mentionedMember.userId; + const defaultAvatarUrl = Avatar.defaultAvatarUrlForString(mentionedMember.userId); + const avatarUrl = Avatar.avatarUrlForMember(mentionedMember, 16, 16, "crop"); let initialLetter = ""; if (avatarUrl === defaultAvatarUrl) { initialLetter = Avatar.getInitialLetter(name) ?? ""; @@ -143,26 +127,15 @@ function getMentionAttributes(completion: ICompletion, client: MatrixClient, roo background = `url(${avatarUrl})`; letter = `'${initialLetter}'`; // not a mistake, need to ensure it's there - } - if (completion.type === "room") { - const roomId = completion.completionId; - const alias = completion.completion; - // TODO note this is the same logic as in getMentionDisplayText - extract into - // a separate function - let room: Room | undefined; - if (roomId || alias[0] !== "#") { - room = client.getRoom(roomId || alias) ?? undefined; - } else { - room = client.getRooms().find((r) => { - return r.getCanonicalAlias() === alias || r.getAltAliases().includes(alias); - }); - } + } else if (completion.type === "room") { + const mentionedRoom = getRoomFromCompletion(completion, client); + const aliasFromCompletion = completion.completion; let initialLetter = ""; - let avatarUrl = Avatar.avatarUrlForRoom(room ?? null, 16, 16, "crop"); + let avatarUrl = Avatar.avatarUrlForRoom(mentionedRoom ?? null, 16, 16, "crop"); if (!avatarUrl) { - initialLetter = Avatar.getInitialLetter(room?.name || alias) ?? ""; - avatarUrl = Avatar.defaultAvatarUrlForString(room?.roomId ?? alias); + initialLetter = Avatar.getInitialLetter(mentionedRoom?.name || aliasFromCompletion) ?? ""; + avatarUrl = Avatar.defaultAvatarUrlForString(mentionedRoom?.roomId ?? aliasFromCompletion); } background = `url(${avatarUrl})`; From c8d61d48f4808c1ec8120ad8112912f26c6706ef Mon Sep 17 00:00:00 2001 From: Alun Turner Date: Mon, 3 Apr 2023 11:05:57 +0100 Subject: [PATCH 07/25] fix TS errors --- .../components/WysiwygAutocomplete.tsx | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/src/components/views/rooms/wysiwyg_composer/components/WysiwygAutocomplete.tsx b/src/components/views/rooms/wysiwyg_composer/components/WysiwygAutocomplete.tsx index 5eb486a390a..cbfbbcc27e3 100644 --- a/src/components/views/rooms/wysiwyg_composer/components/WysiwygAutocomplete.tsx +++ b/src/components/views/rooms/wysiwyg_composer/components/WysiwygAutocomplete.tsx @@ -100,6 +100,7 @@ function getMentionDisplayText(completion: ICompletion, client: MatrixClient): s // completion.completion return getRoomFromCompletion(completion, client)?.name || completion.completion; } + return ""; } /** @@ -113,7 +114,7 @@ function getMentionAttributes(completion: ICompletion, client: MatrixClient, roo let background = "background"; let letter = "letter"; if (completion.type === "user") { - const mentionedMember = room.getMember(completion.completionId); + const mentionedMember = room.getMember(completion.completionId || ""); if (!mentionedMember) return; @@ -161,11 +162,9 @@ const WysiwygAutocomplete = forwardRef( const client = useMatrixClientContext(); function handleConfirm(completion: ICompletion): void { - if (!completion.href || !client) return; - - // for now we can use this if to make sure we handle only the mentions we know we can handle properly - // in the model - if (completion.type === "room" || completion.type === "user") { + // TODO handle all of the completion types + // Using this to pick out the ones we can handle during implementation + if (room && (completion.type === "room" || completion.type === "user")) { handleMention( completion.href, getMentionDisplayText(completion, client), From 7dd317e4bca331ecbd6d1be8f94ddb022601e008 Mon Sep 17 00:00:00 2001 From: Alun Turner Date: Fri, 31 Mar 2023 13:48:44 +0100 Subject: [PATCH 08/25] extract helper functions to new file --- .../components/WysiwygAutocomplete.tsx | 116 +-------------- .../wysiwyg_composer/utils/autocomplete.ts | 132 ++++++++++++++++++ 2 files changed, 134 insertions(+), 114 deletions(-) create mode 100644 src/components/views/rooms/wysiwyg_composer/utils/autocomplete.ts diff --git a/src/components/views/rooms/wysiwyg_composer/components/WysiwygAutocomplete.tsx b/src/components/views/rooms/wysiwyg_composer/components/WysiwygAutocomplete.tsx index cbfbbcc27e3..9d89682c95f 100644 --- a/src/components/views/rooms/wysiwyg_composer/components/WysiwygAutocomplete.tsx +++ b/src/components/views/rooms/wysiwyg_composer/components/WysiwygAutocomplete.tsx @@ -15,14 +15,13 @@ limitations under the License. */ import React, { ForwardedRef, forwardRef } from "react"; -import { MatrixClient, Room } from "matrix-js-sdk/src/matrix"; -import { Attributes, FormattingFunctions, MappedSuggestion } from "@matrix-org/matrix-wysiwyg"; +import { FormattingFunctions, MappedSuggestion } from "@matrix-org/matrix-wysiwyg"; -import * as Avatar from "../../../../../Avatar"; import { useRoomContext } from "../../../../../contexts/RoomContext"; import Autocomplete from "../../Autocomplete"; import { ICompletion } from "../../../../../autocomplete/Autocompleter"; import { useMatrixClientContext } from "../../../../../contexts/MatrixClientContext"; +import { getMentionDisplayText, getMentionAttributes, buildQuery } from "../utils/autocomplete"; interface WysiwygAutocompleteProps { /** @@ -38,117 +37,6 @@ interface WysiwygAutocompleteProps { handleMention: FormattingFunctions["mention"]; } -/** - * Builds the query for the `` component from the rust suggestion. This - * will change as we implement handling / commands. - * - * @param suggestion - represents if the rust model is tracking a potential mention - * @returns an empty string if we can not generate a query, otherwise a query beginning - * with @ for a user query, # for a room or space query - */ -function buildQuery(suggestion: MappedSuggestion | null): string { - if (!suggestion || !suggestion.keyChar || suggestion.type === "command") { - // if we have an empty key character, we do not build a query - // TODO implement the command functionality - return ""; - } - - return `${suggestion.keyChar}${suggestion.text}`; -} - -/** - * Find the room from the completion by looking it up using the client from the context - * we are currently in - * - * @param completion - the completion from the autocomplete - * @param client - the current client we are using - * @returns a Room if one is found, null otherwise - */ -function getRoomFromCompletion(completion: ICompletion, client: MatrixClient): Room | null { - const roomId = completion.completionId; - const aliasFromCompletion = completion.completion; - - let roomToReturn: Room | null | undefined; - - // Not quite sure if the logic here makes sense - specifically calling .getRoom with an alias - // that doesn't start with #, but keeping the logic the same as in PartCreator.roomPill for now - if (roomId) { - roomToReturn = client.getRoom(roomId); - } else if (!aliasFromCompletion.startsWith("#")) { - roomToReturn = client.getRoom(aliasFromCompletion); - } else { - roomToReturn = client.getRooms().find((r) => { - return r.getCanonicalAlias() === aliasFromCompletion || r.getAltAliases().includes(aliasFromCompletion); - }); - } - - return roomToReturn ?? null; -} - -/** - * Given an autocomplete suggestion, determine the text to display in the pill - * - * @param completion - the item selected from the autocomplete - * @param client - the MatrixClient is required for us to look up the correct room mention text - * @returns the text to display in the mention - */ -function getMentionDisplayText(completion: ICompletion, client: MatrixClient): string { - if (completion.type === "user") { - return completion.completion; - } else if (completion.type === "room") { - // try and get the room and use it's name, if not available, fall back to - // completion.completion - return getRoomFromCompletion(completion, client)?.name || completion.completion; - } - return ""; -} - -/** - * For a given completion, the attributes will change depending on the completion type - * - * @param completion - the item selected from the autocomplete - * @param client - the MatrixClient is required for us to look up the correct room mention text - * @returns an object of attributes containing HTMLAnchor attributes or data-* attri - */ -function getMentionAttributes(completion: ICompletion, client: MatrixClient, room: Room): Attributes { - let background = "background"; - let letter = "letter"; - if (completion.type === "user") { - const mentionedMember = room.getMember(completion.completionId || ""); - - if (!mentionedMember) return; - - const name = mentionedMember.name || mentionedMember.userId; - const defaultAvatarUrl = Avatar.defaultAvatarUrlForString(mentionedMember.userId); - const avatarUrl = Avatar.avatarUrlForMember(mentionedMember, 16, 16, "crop"); - let initialLetter = ""; - if (avatarUrl === defaultAvatarUrl) { - initialLetter = Avatar.getInitialLetter(name) ?? ""; - } - - background = `url(${avatarUrl})`; - letter = `'${initialLetter}'`; // not a mistake, need to ensure it's there - } else if (completion.type === "room") { - const mentionedRoom = getRoomFromCompletion(completion, client); - const aliasFromCompletion = completion.completion; - - let initialLetter = ""; - let avatarUrl = Avatar.avatarUrlForRoom(mentionedRoom ?? null, 16, 16, "crop"); - if (!avatarUrl) { - initialLetter = Avatar.getInitialLetter(mentionedRoom?.name || aliasFromCompletion) ?? ""; - avatarUrl = Avatar.defaultAvatarUrlForString(mentionedRoom?.roomId ?? aliasFromCompletion); - } - - background = `url(${avatarUrl})`; - letter = `'${initialLetter}'`; // not a mistake, need to ensure it's there - } - - return { - "data-mention-type": completion.type, - "style": `--avatar-background: ${background}; --avatar-letter: ${letter}`, - }; -} - /** * Given the current suggestion from the rust model and a handler function, this component * will display the legacy `` component (as used in the BasicMessageComposer) diff --git a/src/components/views/rooms/wysiwyg_composer/utils/autocomplete.ts b/src/components/views/rooms/wysiwyg_composer/utils/autocomplete.ts new file mode 100644 index 00000000000..7fdf129311d --- /dev/null +++ b/src/components/views/rooms/wysiwyg_composer/utils/autocomplete.ts @@ -0,0 +1,132 @@ +/* +Copyright 2023 The Matrix.org Foundation C.I.C. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +import { Attributes, MappedSuggestion } from "@matrix-org/matrix-wysiwyg"; +import { MatrixClient, Room } from "matrix-js-sdk/src/matrix"; + +import { ICompletion } from "../../../../../autocomplete/Autocompleter"; +import * as Avatar from "../../../../../Avatar"; + +/** + * Builds the query for the `` component from the rust suggestion. This + * will change as we implement handling / commands. + * + * @param suggestion - represents if the rust model is tracking a potential mention + * @returns an empty string if we can not generate a query, otherwise a query beginning + * with @ for a user query, # for a room or space query + */ +export function buildQuery(suggestion: MappedSuggestion | null): string { + if (!suggestion || !suggestion.keyChar || suggestion.type === "command") { + // if we have an empty key character, we do not build a query + // TODO implement the command functionality + return ""; + } + + return `${suggestion.keyChar}${suggestion.text}`; +} + +/** + * Find the room from the completion by looking it up using the client from the context + * we are currently in + * + * @param completion - the completion from the autocomplete + * @param client - the current client we are using + * @returns a Room if one is found, null otherwise + */ +export function getRoomFromCompletion(completion: ICompletion, client: MatrixClient): Room | null { + const roomId = completion.completionId; + const aliasFromCompletion = completion.completion; + + let roomToReturn: Room | null | undefined; + + // Not quite sure if the logic here makes sense - specifically calling .getRoom with an alias + // that doesn't start with #, but keeping the logic the same as in PartCreator.roomPill for now + if (roomId) { + roomToReturn = client.getRoom(roomId); + } else if (!aliasFromCompletion.startsWith("#")) { + roomToReturn = client.getRoom(aliasFromCompletion); + } else { + roomToReturn = client.getRooms().find((r) => { + return r.getCanonicalAlias() === aliasFromCompletion || r.getAltAliases().includes(aliasFromCompletion); + }); + } + + return roomToReturn ?? null; +} + +/** + * Given an autocomplete suggestion, determine the text to display in the pill + * + * @param completion - the item selected from the autocomplete + * @param client - the MatrixClient is required for us to look up the correct room mention text + * @returns the text to display in the mention + */ +export function getMentionDisplayText(completion: ICompletion, client: MatrixClient): string { + if (completion.type === "user") { + return completion.completion; + } else if (completion.type === "room") { + // try and get the room and use it's name, if not available, fall back to + // completion.completion + return getRoomFromCompletion(completion, client)?.name || completion.completion; + } + return ""; +} + +/** + * For a given completion, the attributes will change depending on the completion type + * + * @param completion - the item selected from the autocomplete + * @param client - the MatrixClient is required for us to look up the correct room mention text + * @returns an object of attributes containing HTMLAnchor attributes or data-* attri + */ +export function getMentionAttributes(completion: ICompletion, client: MatrixClient, room: Room): Attributes { + let background = "background"; + let letter = "letter"; + if (completion.type === "user") { + const mentionedMember = room.getMember(completion.completionId || ""); + + if (!mentionedMember) return {}; + + const name = mentionedMember.name || mentionedMember.userId; + const defaultAvatarUrl = Avatar.defaultAvatarUrlForString(mentionedMember.userId); + const avatarUrl = Avatar.avatarUrlForMember(mentionedMember, 16, 16, "crop"); + let initialLetter = ""; + if (avatarUrl === defaultAvatarUrl) { + initialLetter = Avatar.getInitialLetter(name) ?? ""; + } + + background = `url(${avatarUrl})`; + letter = `'${initialLetter}'`; // not a mistake, need to ensure it's there + } else if (completion.type === "room") { + const mentionedRoom = getRoomFromCompletion(completion, client); + const aliasFromCompletion = completion.completion; + + let initialLetter = ""; + let avatarUrl = Avatar.avatarUrlForRoom(mentionedRoom ?? null, 16, 16, "crop"); + if (!avatarUrl) { + initialLetter = Avatar.getInitialLetter(mentionedRoom?.name || aliasFromCompletion) ?? ""; + avatarUrl = Avatar.defaultAvatarUrlForString(mentionedRoom?.roomId ?? aliasFromCompletion); + } + + background = `url(${avatarUrl})`; + letter = `'${initialLetter}'`; // not a mistake, need to ensure it's there + } + + return { + "data-mention-type": completion.type, + "style": `--avatar-background: ${background}; --avatar-letter: ${letter}`, + }; +} From c093a534d15882a705f30579140fa8d876ada1ff Mon Sep 17 00:00:00 2001 From: Alun Turner Date: Fri, 31 Mar 2023 14:06:05 +0100 Subject: [PATCH 09/25] create test file, add buildQuery tests --- .../utils/autocomplete-test.ts | 44 +++++++++++++++++++ 1 file changed, 44 insertions(+) create mode 100644 test/components/views/rooms/wysiwyg_composer/utils/autocomplete-test.ts diff --git a/test/components/views/rooms/wysiwyg_composer/utils/autocomplete-test.ts b/test/components/views/rooms/wysiwyg_composer/utils/autocomplete-test.ts new file mode 100644 index 00000000000..cd3c34a89d2 --- /dev/null +++ b/test/components/views/rooms/wysiwyg_composer/utils/autocomplete-test.ts @@ -0,0 +1,44 @@ +/* +Copyright 2023 The Matrix.org Foundation C.I.C. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +import { + buildQuery, + getRoomFrom, + getMentionDisplayText, + getMentionAttributes, +} from "../../../../../../src/components/views/rooms/wysiwyg_composer/utils/autocomplete"; + +describe("buildQuery", () => { + it("returns an empty string for a falsy argument", () => { + expect(buildQuery(null)).toBe(""); + }); + + it("returns an empty string when keyChar is falsy", () => { + const noKeyCharSuggestion = { keyChar: "" as const, text: "test", type: "unknown" as const }; + expect(buildQuery(noKeyCharSuggestion)).toBe(""); + }); + + it("returns an empty string when suggestion is a command", () => { + // TODO alter this test when commands are implemented + const commandSuggestion = { keyChar: "/" as const, text: "slash", type: "command" as const }; + expect(buildQuery(commandSuggestion)).toBe(""); + }); + + it("combines the keyChar and text of the suggestion in the query", () => { + const handledSuggestion = { keyChar: "@" as const, text: "alice", type: "mention" as const }; + expect(buildQuery(handledSuggestion)).toBe("@alice"); + }); +}); From 9215be3e99e48875eb70ee25166a2f6fc18b032d Mon Sep 17 00:00:00 2001 From: Alun Turner Date: Fri, 31 Mar 2023 15:53:51 +0100 Subject: [PATCH 10/25] add getRoomFromCompletion tests --- .../utils/autocomplete-test.ts | 52 ++++++++++++++++++- 1 file changed, 51 insertions(+), 1 deletion(-) diff --git a/test/components/views/rooms/wysiwyg_composer/utils/autocomplete-test.ts b/test/components/views/rooms/wysiwyg_composer/utils/autocomplete-test.ts index cd3c34a89d2..1bbc7784e14 100644 --- a/test/components/views/rooms/wysiwyg_composer/utils/autocomplete-test.ts +++ b/test/components/views/rooms/wysiwyg_composer/utils/autocomplete-test.ts @@ -14,12 +14,15 @@ See the License for the specific language governing permissions and limitations under the License. */ +import { Room } from "matrix-js-sdk"; +import { ICompletion } from "../../../../../../src/autocomplete/Autocompleter"; import { buildQuery, - getRoomFrom, + getRoomFromCompletion, getMentionDisplayText, getMentionAttributes, } from "../../../../../../src/components/views/rooms/wysiwyg_composer/utils/autocomplete"; +import { createTestClient } from "../../../../../test-utils"; describe("buildQuery", () => { it("returns an empty string for a falsy argument", () => { @@ -42,3 +45,50 @@ describe("buildQuery", () => { expect(buildQuery(handledSuggestion)).toBe("@alice"); }); }); + +describe("getRoomFromCompletion", () => { + const mockClient = createTestClient(); + + const createMockRoomCompletion = (props: Partial): ICompletion => { + return { + type: "room", + completion: "mock", + range: { beginning: true, start: 0, end: 0 }, + ...props, + }; + }; + + beforeEach(() => jest.clearAllMocks()); + afterAll(() => jest.restoreAllMocks()); + + it("calls getRoom with completionId if present in the completion", () => { + const testId = "arbitraryId"; + const completionWithId = createMockRoomCompletion({ completionId: testId }); + + getRoomFromCompletion(completionWithId, mockClient); + + expect(mockClient.getRoom).toHaveBeenCalledWith(testId); + }); + + it("calls getRoom with completion if present and correct format", () => { + const testCompletion = "arbitraryCompletion"; + const completionWithId = createMockRoomCompletion({ completionId: testCompletion }); + + getRoomFromCompletion(completionWithId, mockClient); + + expect(mockClient.getRoom).toHaveBeenCalledWith(testCompletion); + }); + + it("calls getRooms if no completionId is present and completion starts with #", () => { + const completionWithId = createMockRoomCompletion({ completion: "#hash" }); + + const result = getRoomFromCompletion(completionWithId, mockClient); + + expect(mockClient.getRoom).not.toHaveBeenCalled(); + expect(mockClient.getRooms).toHaveBeenCalled(); + + // in this case, because the mock client returns an empty array of rooms + // from the call to get rooms, we'd expect the result to be null + expect(result).toBe(null); + }); +}); From 8b0559f4deefc6ac326f28346f2c5ae8c8b0c39a Mon Sep 17 00:00:00 2001 From: Alun Turner Date: Fri, 31 Mar 2023 16:16:04 +0100 Subject: [PATCH 11/25] test getMentionDisplayText --- .../utils/autocomplete-test.ts | 60 +++++++++++++++---- 1 file changed, 48 insertions(+), 12 deletions(-) diff --git a/test/components/views/rooms/wysiwyg_composer/utils/autocomplete-test.ts b/test/components/views/rooms/wysiwyg_composer/utils/autocomplete-test.ts index 1bbc7784e14..86631d2fc1d 100644 --- a/test/components/views/rooms/wysiwyg_composer/utils/autocomplete-test.ts +++ b/test/components/views/rooms/wysiwyg_composer/utils/autocomplete-test.ts @@ -14,7 +14,6 @@ See the License for the specific language governing permissions and limitations under the License. */ -import { Room } from "matrix-js-sdk"; import { ICompletion } from "../../../../../../src/autocomplete/Autocompleter"; import { buildQuery, @@ -24,6 +23,11 @@ import { } from "../../../../../../src/components/views/rooms/wysiwyg_composer/utils/autocomplete"; import { createTestClient } from "../../../../../test-utils"; +const mockClient = createTestClient(); + +beforeEach(() => jest.clearAllMocks()); +afterAll(() => jest.restoreAllMocks()); + describe("buildQuery", () => { it("returns an empty string for a falsy argument", () => { expect(buildQuery(null)).toBe(""); @@ -46,21 +50,19 @@ describe("buildQuery", () => { }); }); -describe("getRoomFromCompletion", () => { - const mockClient = createTestClient(); +const createMockCompletion = (props: Partial): ICompletion => { + return { + completion: "mock", + range: { beginning: true, start: 0, end: 0 }, + ...props, + }; +}; +describe("getRoomFromCompletion", () => { const createMockRoomCompletion = (props: Partial): ICompletion => { - return { - type: "room", - completion: "mock", - range: { beginning: true, start: 0, end: 0 }, - ...props, - }; + return createMockCompletion({ ...props, type: "room" }); }; - beforeEach(() => jest.clearAllMocks()); - afterAll(() => jest.restoreAllMocks()); - it("calls getRoom with completionId if present in the completion", () => { const testId = "arbitraryId"; const completionWithId = createMockRoomCompletion({ completionId: testId }); @@ -92,3 +94,37 @@ describe("getRoomFromCompletion", () => { expect(result).toBe(null); }); }); + +describe("getMentionDisplayText", () => { + it("returns an empty string if we are not handling a user or a room type", () => { + const nonHandledCompletionTypes = ["at-room", "community", "command"]; + const nonHandledCompletions = nonHandledCompletionTypes.map((type) => createMockCompletion({ type })); + + nonHandledCompletions.forEach((completion) => { + expect(getMentionDisplayText(completion, mockClient)).toBe(""); + }); + }); + + it("returns the completion if we are handling a user", () => { + const testCompletion = "display this"; + const userCompletion = createMockCompletion({ type: "user", completion: testCompletion }); + + expect(getMentionDisplayText(userCompletion, mockClient)).toBe(testCompletion); + }); + + it("returns the room name when the room has a valid completionId", () => { + const testCompletionId = "testId"; + const userCompletion = createMockCompletion({ type: "room", completionId: testCompletionId }); + + // as this uses the mockClient, the name will be the mock room name returned from there + expect(getMentionDisplayText(userCompletion, mockClient)).toBe(mockClient.getRoom("")?.name); + }); + + it("falls back to the completion for a room if completion starts with #", () => { + const testCompletion = "#hash"; + const userCompletion = createMockCompletion({ type: "room", completion: testCompletion }); + + // as this uses the mockClient, the name will be the mock room name returned from there + expect(getMentionDisplayText(userCompletion, mockClient)).toBe(testCompletion); + }); +}); From de4f781b24fff69fdf1afdef31dadcf336c87fbd Mon Sep 17 00:00:00 2001 From: Alun Turner Date: Fri, 31 Mar 2023 16:26:46 +0100 Subject: [PATCH 12/25] start off the getMentionAttributes tests --- .../wysiwyg_composer/utils/autocomplete.ts | 13 +++++--- .../utils/autocomplete-test.ts | 33 +++++++++++++------ 2 files changed, 32 insertions(+), 14 deletions(-) diff --git a/src/components/views/rooms/wysiwyg_composer/utils/autocomplete.ts b/src/components/views/rooms/wysiwyg_composer/utils/autocomplete.ts index 7fdf129311d..d808b42046a 100644 --- a/src/components/views/rooms/wysiwyg_composer/utils/autocomplete.ts +++ b/src/components/views/rooms/wysiwyg_composer/utils/autocomplete.ts @@ -110,6 +110,10 @@ export function getMentionAttributes(completion: ICompletion, client: MatrixClie background = `url(${avatarUrl})`; letter = `'${initialLetter}'`; // not a mistake, need to ensure it's there + return { + "data-mention-type": completion.type, + "style": `--avatar-background: ${background}; --avatar-letter: ${letter}`, + }; } else if (completion.type === "room") { const mentionedRoom = getRoomFromCompletion(completion, client); const aliasFromCompletion = completion.completion; @@ -123,10 +127,11 @@ export function getMentionAttributes(completion: ICompletion, client: MatrixClie background = `url(${avatarUrl})`; letter = `'${initialLetter}'`; // not a mistake, need to ensure it's there + return { + "data-mention-type": completion.type, + "style": `--avatar-background: ${background}; --avatar-letter: ${letter}`, + }; } - return { - "data-mention-type": completion.type, - "style": `--avatar-background: ${background}; --avatar-letter: ${letter}`, - }; + return {}; } diff --git a/test/components/views/rooms/wysiwyg_composer/utils/autocomplete-test.ts b/test/components/views/rooms/wysiwyg_composer/utils/autocomplete-test.ts index 86631d2fc1d..b1a6f9f6356 100644 --- a/test/components/views/rooms/wysiwyg_composer/utils/autocomplete-test.ts +++ b/test/components/views/rooms/wysiwyg_composer/utils/autocomplete-test.ts @@ -21,9 +21,19 @@ import { getMentionDisplayText, getMentionAttributes, } from "../../../../../../src/components/views/rooms/wysiwyg_composer/utils/autocomplete"; -import { createTestClient } from "../../../../../test-utils"; +import { createTestClient, mkRoom } from "../../../../../test-utils"; const mockClient = createTestClient(); +const mockRoomId = "mockRoomId"; +const mockRoom = mkRoom(mockClient, mockRoomId); + +const createMockCompletion = (props: Partial): ICompletion => { + return { + completion: "mock", + range: { beginning: true, start: 0, end: 0 }, + ...props, + }; +}; beforeEach(() => jest.clearAllMocks()); afterAll(() => jest.restoreAllMocks()); @@ -50,14 +60,6 @@ describe("buildQuery", () => { }); }); -const createMockCompletion = (props: Partial): ICompletion => { - return { - completion: "mock", - range: { beginning: true, start: 0, end: 0 }, - ...props, - }; -}; - describe("getRoomFromCompletion", () => { const createMockRoomCompletion = (props: Partial): ICompletion => { return createMockCompletion({ ...props, type: "room" }); @@ -97,7 +99,7 @@ describe("getRoomFromCompletion", () => { describe("getMentionDisplayText", () => { it("returns an empty string if we are not handling a user or a room type", () => { - const nonHandledCompletionTypes = ["at-room", "community", "command"]; + const nonHandledCompletionTypes = ["at-room", "community", "command"] as const; const nonHandledCompletions = nonHandledCompletionTypes.map((type) => createMockCompletion({ type })); nonHandledCompletions.forEach((completion) => { @@ -128,3 +130,14 @@ describe("getMentionDisplayText", () => { expect(getMentionDisplayText(userCompletion, mockClient)).toBe(testCompletion); }); }); + +describe("getMentionAttributes", () => { + it("returns an empty object for completion types other than room or user", () => { + const nonHandledCompletionTypes = ["at-room", "community", "command"] as const; + const nonHandledCompletions = nonHandledCompletionTypes.map((type) => createMockCompletion({ type })); + + nonHandledCompletions.forEach((completion) => { + expect(getMentionAttributes(completion, mockClient, mockRoom)).toEqual({}); + }); + }); +}); From e6f8cb4d42dcf2e7aef49b6db69be58352d16cb1 Mon Sep 17 00:00:00 2001 From: Alun Turner Date: Mon, 3 Apr 2023 10:44:34 +0100 Subject: [PATCH 13/25] write user and room tests for getMentionAttributes --- .../utils/autocomplete-test.ts | 79 +++++++++++++++++++ 1 file changed, 79 insertions(+) diff --git a/test/components/views/rooms/wysiwyg_composer/utils/autocomplete-test.ts b/test/components/views/rooms/wysiwyg_composer/utils/autocomplete-test.ts index b1a6f9f6356..7eeedd2d46c 100644 --- a/test/components/views/rooms/wysiwyg_composer/utils/autocomplete-test.ts +++ b/test/components/views/rooms/wysiwyg_composer/utils/autocomplete-test.ts @@ -22,6 +22,7 @@ import { getMentionAttributes, } from "../../../../../../src/components/views/rooms/wysiwyg_composer/utils/autocomplete"; import { createTestClient, mkRoom } from "../../../../../test-utils"; +import * as mockAvatar from "../../../../../../src/Avatar"; const mockClient = createTestClient(); const mockRoomId = "mockRoomId"; @@ -35,6 +36,8 @@ const createMockCompletion = (props: Partial): ICompletion => { }; }; +jest.mock("../../../../../../src/Avatar"); + beforeEach(() => jest.clearAllMocks()); afterAll(() => jest.restoreAllMocks()); @@ -132,6 +135,7 @@ describe("getMentionDisplayText", () => { }); describe("getMentionAttributes", () => { + // TODO handle all completion types it("returns an empty object for completion types other than room or user", () => { const nonHandledCompletionTypes = ["at-room", "community", "command"] as const; const nonHandledCompletions = nonHandledCompletionTypes.map((type) => createMockCompletion({ type })); @@ -140,4 +144,79 @@ describe("getMentionAttributes", () => { expect(getMentionAttributes(completion, mockClient, mockRoom)).toEqual({}); }); }); + + const testAvatarUrlForString = "www.stringUrl.com"; + const testAvatarUrlForMember = "www.memberUrl.com"; + const testAvatarUrlForRoom = "www.roomUrl.com"; + const testInitialLetter = "z"; + + // TODO figure out how to appease TS here + mockAvatar.defaultAvatarUrlForString.mockReturnValue(testAvatarUrlForString); + mockAvatar.avatarUrlForMember.mockReturnValue(testAvatarUrlForMember); + mockAvatar.avatarUrlForRoom.mockReturnValue(testAvatarUrlForRoom); + mockAvatar.getInitialLetter.mockReturnValue(testInitialLetter); + + describe("user mentions", () => { + it("returns an empty object when no member can be found", () => { + const userCompletion = createMockCompletion({ type: "user" }); + + // mock not being able to find a member + mockRoom.getMember.mockImplementationOnce(() => null); + + const result = getMentionAttributes(userCompletion, mockClient, mockRoom); + expect(result).toEqual({}); + }); + + it("returns expected attributes when avatar url is not default", () => { + const userCompletion = createMockCompletion({ type: "user" }); + + const result = getMentionAttributes(userCompletion, mockClient, mockRoom); + + expect(result).toEqual({ + "data-mention-type": "user", + "style": `--avatar-background: url(${testAvatarUrlForMember}); --avatar-letter: ''`, + }); + }); + + it("returns expected style attributes when avatar url matches default", () => { + const userCompletion = createMockCompletion({ type: "user" }); + + // mock a single implementation of avatarUrlForMember to make it match the default + mockAvatar.avatarUrlForMember.mockReturnValueOnce(testAvatarUrlForString); + + const result = getMentionAttributes(userCompletion, mockClient, mockRoom); + + expect(result).toEqual({ + "data-mention-type": "user", + "style": `--avatar-background: url(${testAvatarUrlForString}); --avatar-letter: '${testInitialLetter}'`, + }); + }); + }); + + describe("room mentions", () => { + it("returns expected attributes when avatar url for room is truthy", () => { + const userCompletion = createMockCompletion({ type: "room" }); + + const result = getMentionAttributes(userCompletion, mockClient, mockRoom); + + expect(result).toEqual({ + "data-mention-type": "room", + "style": `--avatar-background: url(${testAvatarUrlForRoom}); --avatar-letter: ''`, + }); + }); + + it("returns expected style attributes when avatar url for room is falsy", () => { + const userCompletion = createMockCompletion({ type: "room" }); + + // mock a single implementation of avatarUrlForRoom to make it falsy + mockAvatar.avatarUrlForRoom.mockReturnValueOnce(null); + + const result = getMentionAttributes(userCompletion, mockClient, mockRoom); + + expect(result).toEqual({ + "data-mention-type": "room", + "style": `--avatar-background: url(${testAvatarUrlForString}); --avatar-letter: '${testInitialLetter}'`, + }); + }); + }); }); From 6b60a737cb22e0dafa654d7a8e97f0e93ba8734c Mon Sep 17 00:00:00 2001 From: Alun Turner Date: Mon, 3 Apr 2023 10:53:41 +0100 Subject: [PATCH 14/25] use mocked to satisfy TS --- .../views/rooms/wysiwyg_composer/utils/autocomplete-test.ts | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/test/components/views/rooms/wysiwyg_composer/utils/autocomplete-test.ts b/test/components/views/rooms/wysiwyg_composer/utils/autocomplete-test.ts index 7eeedd2d46c..ca828b57bf5 100644 --- a/test/components/views/rooms/wysiwyg_composer/utils/autocomplete-test.ts +++ b/test/components/views/rooms/wysiwyg_composer/utils/autocomplete-test.ts @@ -14,6 +14,8 @@ See the License for the specific language governing permissions and limitations under the License. */ +import { mocked } from "jest-mock"; + import { ICompletion } from "../../../../../../src/autocomplete/Autocompleter"; import { buildQuery, @@ -22,7 +24,7 @@ import { getMentionAttributes, } from "../../../../../../src/components/views/rooms/wysiwyg_composer/utils/autocomplete"; import { createTestClient, mkRoom } from "../../../../../test-utils"; -import * as mockAvatar from "../../../../../../src/Avatar"; +import * as _mockAvatar from "../../../../../../src/Avatar"; const mockClient = createTestClient(); const mockRoomId = "mockRoomId"; @@ -150,7 +152,7 @@ describe("getMentionAttributes", () => { const testAvatarUrlForRoom = "www.roomUrl.com"; const testInitialLetter = "z"; - // TODO figure out how to appease TS here + const mockAvatar = mocked(_mockAvatar); mockAvatar.defaultAvatarUrlForString.mockReturnValue(testAvatarUrlForString); mockAvatar.avatarUrlForMember.mockReturnValue(testAvatarUrlForMember); mockAvatar.avatarUrlForRoom.mockReturnValue(testAvatarUrlForRoom); From a6e4072cae41d1ff758a16c28854cf570c89ce4c Mon Sep 17 00:00:00 2001 From: Alun Turner Date: Mon, 3 Apr 2023 11:13:49 +0100 Subject: [PATCH 15/25] correct typo --- res/css/views/rooms/wysiwyg_composer/components/_Editor.pcss | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/res/css/views/rooms/wysiwyg_composer/components/_Editor.pcss b/res/css/views/rooms/wysiwyg_composer/components/_Editor.pcss index be7bd454020..96c047035d7 100644 --- a/res/css/views/rooms/wysiwyg_composer/components/_Editor.pcss +++ b/res/css/views/rooms/wysiwyg_composer/components/_Editor.pcss @@ -110,7 +110,7 @@ limitations under the License. padding: $font-1px 0.4em; line-height: $font-17px; border-radius: $font-16px; - /* vertical-align: text-top; turning this off stops the vertical alignement issue */ + /* vertical-align: text-top; turning this off stops the vertical alignment issue */ /* TODO turning this on hides the cursor from the composer for some reason, so comment out for now and assess if it's needed when we add the Avatars From b2ff75c86de210b372f8dce09496c6376fd18a21 Mon Sep 17 00:00:00 2001 From: Alun Turner Date: Mon, 3 Apr 2023 11:15:49 +0100 Subject: [PATCH 16/25] fix TS errors --- .../rooms/wysiwyg_composer/components/WysiwygAutocomplete.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/components/views/rooms/wysiwyg_composer/components/WysiwygAutocomplete.tsx b/src/components/views/rooms/wysiwyg_composer/components/WysiwygAutocomplete.tsx index 9d89682c95f..8a90753ce9d 100644 --- a/src/components/views/rooms/wysiwyg_composer/components/WysiwygAutocomplete.tsx +++ b/src/components/views/rooms/wysiwyg_composer/components/WysiwygAutocomplete.tsx @@ -52,9 +52,9 @@ const WysiwygAutocomplete = forwardRef( function handleConfirm(completion: ICompletion): void { // TODO handle all of the completion types // Using this to pick out the ones we can handle during implementation - if (room && (completion.type === "room" || completion.type === "user")) { + if (client && room && (completion.type === "room" || completion.type === "user")) { handleMention( - completion.href, + completion.href || "", getMentionDisplayText(completion, client), getMentionAttributes(completion, client, room), ); From bdb6d57b987d8afae7510e1e5922de77f67135f2 Mon Sep 17 00:00:00 2001 From: Alun Turner Date: Mon, 3 Apr 2023 11:21:52 +0100 Subject: [PATCH 17/25] add comments --- .../views/rooms/wysiwyg_composer/utils/autocomplete.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/components/views/rooms/wysiwyg_composer/utils/autocomplete.ts b/src/components/views/rooms/wysiwyg_composer/utils/autocomplete.ts index d808b42046a..9cceb424f73 100644 --- a/src/components/views/rooms/wysiwyg_composer/utils/autocomplete.ts +++ b/src/components/views/rooms/wysiwyg_composer/utils/autocomplete.ts @@ -96,6 +96,7 @@ export function getMentionAttributes(completion: ICompletion, client: MatrixClie let background = "background"; let letter = "letter"; if (completion.type === "user") { + // logic as used in UserPillPart.setAvatar in parts.ts const mentionedMember = room.getMember(completion.completionId || ""); if (!mentionedMember) return {}; @@ -115,6 +116,7 @@ export function getMentionAttributes(completion: ICompletion, client: MatrixClie "style": `--avatar-background: ${background}; --avatar-letter: ${letter}`, }; } else if (completion.type === "room") { + // logic as used in RoomPillPart.setAvatar in parts.ts const mentionedRoom = getRoomFromCompletion(completion, client); const aliasFromCompletion = completion.completion; From 9c867fc6dc2c5dba7333031eddd7d39dfb71a1f2 Mon Sep 17 00:00:00 2001 From: Alun Turner Date: Mon, 3 Apr 2023 12:36:38 +0100 Subject: [PATCH 18/25] consolidate new CSS, use rem over px, remove comments --- .../wysiwyg_composer/components/_Editor.pcss | 52 ++++++------------- 1 file changed, 16 insertions(+), 36 deletions(-) diff --git a/res/css/views/rooms/wysiwyg_composer/components/_Editor.pcss b/res/css/views/rooms/wysiwyg_composer/components/_Editor.pcss index 96c047035d7..2fa774fe1e6 100644 --- a/res/css/views/rooms/wysiwyg_composer/components/_Editor.pcss +++ b/res/css/views/rooms/wysiwyg_composer/components/_Editor.pcss @@ -106,20 +106,11 @@ limitations under the License. in the current composer, there don't appear to be any styles associated with those classes in this repo */ a[data-mention-type] { - /* these entries duplicate mx_Pill from _Pill.pcss */ + /* combine mx_Pill from _Pill.pcss */ padding: $font-1px 0.4em; line-height: $font-17px; border-radius: $font-16px; - /* vertical-align: text-top; turning this off stops the vertical alignment issue */ - /* TODO turning this on hides the cursor from the composer for some - reason, so comment out for now and assess if it's needed when we add - the Avatars - display: inline-flex; - align-items: center; not required with the above turned off - - Potential fix is using display: inline, width: fit-content - */ - display: inline; /* this, combined with omitting vertical-align, looks good */ + display: inline; box-sizing: border-box; max-width: 100%; overflow: hidden; @@ -127,7 +118,7 @@ limitations under the License. color: $accent-fg-color; background-color: $pill-bg-color; - /* combining the overrides from _BasicMessageComposer.pcss */ + /* ...with the overrides from _BasicMessageComposer.pcss */ user-select: all; position: relative; cursor: unset; /* We don't want indicate clickability */ @@ -135,42 +126,31 @@ limitations under the License. white-space: nowrap; /* avatar pseudo element */ - /* TODO figure out why the application of this alters the - * vertical alignment of the pill - bottom appears clipped and - * the avatar appears too high (but I think it may be correct, - * it just looks this way because the body of the pill is lower) - */ &::before { - /* from _Pill */ - margin-inline-start: -0.3em; /* Otherwise the gap is too large */ - margin-inline-end: 0.2em; - min-width: $font-16px; /* ensure the avatar is not compressed */ - - /* from _BasicMessageComposer */ + /* After consolidation, all of the styling from _Pill.scss was being overridden, + so take what is in _BasicMessageComposer.pcss as the starting point */ display: inline-block; content: var(--avatar-letter); + background: var(--avatar-background), $background; + width: $font-16px; min-width: $font-16px; /* ensure the avatar is not compressed */ height: $font-16px; + line-height: $font-16px; + text-align: center; + + /* Get the positioning of the avatar just right for consistency with timeline */ + margin-inline-start: -0.4rem; margin-inline-end: 0.24rem; - background: var(--avatar-background), $background; - color: $avatar-initial-color; + vertical-align: -0.3rem; + background-repeat: no-repeat; background-size: $font-16px; border-radius: $font-16px; - text-align: center; + + color: $avatar-initial-color; font-weight: normal; - line-height: $font-17px; /* amend to match a text */ font-size: $font-10-4px; - - /* TODO consolidate this whole block of CSS - there's some useless - * and overwritten stuff here, so pick the useful stuff out and - * arrange into a block that makes sense to read - */ - - /* overrides to get the positioning just right */ - vertical-align: -3px; - margin-inline-start: -0.4rem; } } } From bd25c1aa3c43d1f7cb0cf376e6ebe64deb63bd0f Mon Sep 17 00:00:00 2001 From: Alun Turner Date: Mon, 3 Apr 2023 14:04:38 +0100 Subject: [PATCH 19/25] change how missing href is handled --- .../rooms/wysiwyg_composer/components/WysiwygAutocomplete.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/components/views/rooms/wysiwyg_composer/components/WysiwygAutocomplete.tsx b/src/components/views/rooms/wysiwyg_composer/components/WysiwygAutocomplete.tsx index 8a90753ce9d..21e090128dc 100644 --- a/src/components/views/rooms/wysiwyg_composer/components/WysiwygAutocomplete.tsx +++ b/src/components/views/rooms/wysiwyg_composer/components/WysiwygAutocomplete.tsx @@ -52,9 +52,9 @@ const WysiwygAutocomplete = forwardRef( function handleConfirm(completion: ICompletion): void { // TODO handle all of the completion types // Using this to pick out the ones we can handle during implementation - if (client && room && (completion.type === "room" || completion.type === "user")) { + if (client && room && completion.href && (completion.type === "room" || completion.type === "user")) { handleMention( - completion.href || "", + completion.href, getMentionDisplayText(completion, client), getMentionAttributes(completion, client, room), ); From 003972656b5cb0c5b63f96848531449481bbe74b Mon Sep 17 00:00:00 2001 From: Alun Turner Date: Wed, 5 Apr 2023 10:13:47 +0100 Subject: [PATCH 20/25] bump rte to 2.0.0 --- package.json | 2 +- yarn.lock | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/package.json b/package.json index a1d5c3a1f47..ec22f337179 100644 --- a/package.json +++ b/package.json @@ -61,7 +61,7 @@ "dependencies": { "@babel/runtime": "^7.12.5", "@matrix-org/analytics-events": "^0.5.0", - "@matrix-org/matrix-wysiwyg": "^1.4.1", + "@matrix-org/matrix-wysiwyg": "^2.0.0", "@matrix-org/react-sdk-module-api": "^0.0.4", "@sentry/browser": "^7.0.0", "@sentry/tracing": "^7.0.0", diff --git a/yarn.lock b/yarn.lock index bcfed948ad2..31e8f3c2f31 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1704,10 +1704,10 @@ resolved "https://registry.yarnpkg.com/@matrix-org/matrix-sdk-crypto-js/-/matrix-sdk-crypto-js-0.1.0-alpha.5.tgz#60ede2c43b9d808ba8cf46085a3b347b290d9658" integrity sha512-2KjAgWNGfuGLNjJwsrs6gGX157vmcTfNrA4u249utgnMPbJl7QwuUqh1bGxQ0PpK06yvZjgPlkna0lTbuwtuQw== -"@matrix-org/matrix-wysiwyg@^1.4.1": - version "1.4.1" - resolved "https://registry.yarnpkg.com/@matrix-org/matrix-wysiwyg/-/matrix-wysiwyg-1.4.1.tgz#80c392f036dc4d6ef08a91c4964a68682e977079" - integrity sha512-B8sxY3pE2XyRyQ1g7cx0YjGaDZ1A0Uh5XxS/lNdxQ/0ctRJj6IBy7KtiUjxDRdA15ioZnf6aoJBRkBSr02qhaw== +"@matrix-org/matrix-wysiwyg@^2.0.0": + version "2.0.0" + resolved "https://registry.yarnpkg.com/@matrix-org/matrix-wysiwyg/-/matrix-wysiwyg-2.0.0.tgz#913eb5faa5d43c7a4ee9bda68de1aa1dcc49a11d" + integrity sha512-xRYFwsf6Jx7KTCpwU91mVhPA8q/c+oOVyK98NnexyK/IcQS7BMFAns5GZX9b6ZEy38u30KoxeN6mxvxi+ysQgg== "@matrix-org/olm@https://gitlab.matrix.org/api/v4/projects/27/packages/npm/@matrix-org/olm/-/@matrix-org/olm-3.2.14.tgz": version "3.2.14" From 284673502deebaec3dc849800519074ba9763201 Mon Sep 17 00:00:00 2001 From: Alun Turner Date: Wed, 5 Apr 2023 13:39:40 +0100 Subject: [PATCH 21/25] tweak vertical alignment --- res/css/views/rooms/wysiwyg_composer/components/_Editor.pcss | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/res/css/views/rooms/wysiwyg_composer/components/_Editor.pcss b/res/css/views/rooms/wysiwyg_composer/components/_Editor.pcss index af30aed2c69..ee4ada0ed32 100644 --- a/res/css/views/rooms/wysiwyg_composer/components/_Editor.pcss +++ b/res/css/views/rooms/wysiwyg_composer/components/_Editor.pcss @@ -142,7 +142,7 @@ limitations under the License. /* Get the positioning of the avatar just right for consistency with timeline */ margin-inline-start: -0.4rem; margin-inline-end: 0.24rem; - vertical-align: -0.3rem; + vertical-align: 0.16rem; background-repeat: no-repeat; background-size: $font-16px; From 4a0b0fe5b063eb0c4cb8a2b384515e71e95d3733 Mon Sep 17 00:00:00 2001 From: Alun Turner Date: Wed, 5 Apr 2023 13:40:46 +0100 Subject: [PATCH 22/25] always ensure attributes have initial content --- .../wysiwyg_composer/utils/autocomplete.ts | 19 +++++++++---------- 1 file changed, 9 insertions(+), 10 deletions(-) diff --git a/src/components/views/rooms/wysiwyg_composer/utils/autocomplete.ts b/src/components/views/rooms/wysiwyg_composer/utils/autocomplete.ts index 9cceb424f73..aac0c4dd8d8 100644 --- a/src/components/views/rooms/wysiwyg_composer/utils/autocomplete.ts +++ b/src/components/views/rooms/wysiwyg_composer/utils/autocomplete.ts @@ -93,8 +93,7 @@ export function getMentionDisplayText(completion: ICompletion, client: MatrixCli * @returns an object of attributes containing HTMLAnchor attributes or data-* attri */ export function getMentionAttributes(completion: ICompletion, client: MatrixClient, room: Room): Attributes { - let background = "background"; - let letter = "letter"; + const defaultLetterContent = "-"; if (completion.type === "user") { // logic as used in UserPillPart.setAvatar in parts.ts const mentionedMember = room.getMember(completion.completionId || ""); @@ -104,13 +103,13 @@ export function getMentionAttributes(completion: ICompletion, client: MatrixClie const name = mentionedMember.name || mentionedMember.userId; const defaultAvatarUrl = Avatar.defaultAvatarUrlForString(mentionedMember.userId); const avatarUrl = Avatar.avatarUrlForMember(mentionedMember, 16, 16, "crop"); - let initialLetter = ""; + let initialLetter = defaultLetterContent; if (avatarUrl === defaultAvatarUrl) { - initialLetter = Avatar.getInitialLetter(name) ?? ""; + initialLetter = Avatar.getInitialLetter(name) ?? defaultLetterContent; } - background = `url(${avatarUrl})`; - letter = `'${initialLetter}'`; // not a mistake, need to ensure it's there + const background = `url(${avatarUrl})`; + const letter = `'${initialLetter}'`; // not a mistake, need to ensure it's there return { "data-mention-type": completion.type, "style": `--avatar-background: ${background}; --avatar-letter: ${letter}`, @@ -120,15 +119,15 @@ export function getMentionAttributes(completion: ICompletion, client: MatrixClie const mentionedRoom = getRoomFromCompletion(completion, client); const aliasFromCompletion = completion.completion; - let initialLetter = ""; + let initialLetter = defaultLetterContent; let avatarUrl = Avatar.avatarUrlForRoom(mentionedRoom ?? null, 16, 16, "crop"); if (!avatarUrl) { - initialLetter = Avatar.getInitialLetter(mentionedRoom?.name || aliasFromCompletion) ?? ""; + initialLetter = Avatar.getInitialLetter(mentionedRoom?.name || aliasFromCompletion) ?? defaultLetterContent; avatarUrl = Avatar.defaultAvatarUrlForString(mentionedRoom?.roomId ?? aliasFromCompletion); } - background = `url(${avatarUrl})`; - letter = `'${initialLetter}'`; // not a mistake, need to ensure it's there + const background = `url(${avatarUrl})`; + const letter = `'${initialLetter}'`; // not a mistake, need to ensure it's there return { "data-mention-type": completion.type, "style": `--avatar-background: ${background}; --avatar-letter: ${letter}`, From 1b68099c226c4ea2fd270fdd5f3603098074c65c Mon Sep 17 00:00:00 2001 From: Alun Turner Date: Wed, 5 Apr 2023 13:45:40 +0100 Subject: [PATCH 23/25] always ensure we have content in CSS variable --- .../rooms/wysiwyg_composer/utils/autocomplete.ts | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/src/components/views/rooms/wysiwyg_composer/utils/autocomplete.ts b/src/components/views/rooms/wysiwyg_composer/utils/autocomplete.ts index aac0c4dd8d8..d1f066a7bc4 100644 --- a/src/components/views/rooms/wysiwyg_composer/utils/autocomplete.ts +++ b/src/components/views/rooms/wysiwyg_composer/utils/autocomplete.ts @@ -93,7 +93,10 @@ export function getMentionDisplayText(completion: ICompletion, client: MatrixCli * @returns an object of attributes containing HTMLAnchor attributes or data-* attri */ export function getMentionAttributes(completion: ICompletion, client: MatrixClient, room: Room): Attributes { + // to ensure that we always have something set in the --avatar-letter CSS variable + // as otherwise alignment varies depending on whether the content is empty or not const defaultLetterContent = "-"; + if (completion.type === "user") { // logic as used in UserPillPart.setAvatar in parts.ts const mentionedMember = room.getMember(completion.completionId || ""); @@ -108,11 +111,9 @@ export function getMentionAttributes(completion: ICompletion, client: MatrixClie initialLetter = Avatar.getInitialLetter(name) ?? defaultLetterContent; } - const background = `url(${avatarUrl})`; - const letter = `'${initialLetter}'`; // not a mistake, need to ensure it's there return { "data-mention-type": completion.type, - "style": `--avatar-background: ${background}; --avatar-letter: ${letter}`, + "style": `--avatar-background: url(${avatarUrl}); --avatar-letter: '${initialLetter}'`, }; } else if (completion.type === "room") { // logic as used in RoomPillPart.setAvatar in parts.ts @@ -126,11 +127,9 @@ export function getMentionAttributes(completion: ICompletion, client: MatrixClie avatarUrl = Avatar.defaultAvatarUrlForString(mentionedRoom?.roomId ?? aliasFromCompletion); } - const background = `url(${avatarUrl})`; - const letter = `'${initialLetter}'`; // not a mistake, need to ensure it's there return { "data-mention-type": completion.type, - "style": `--avatar-background: ${background}; --avatar-letter: ${letter}`, + "style": `--avatar-background: url(${avatarUrl}); --avatar-letter: '${initialLetter}'`, }; } From c82c269990c6fe1f45b91a855278e7fdd533b347 Mon Sep 17 00:00:00 2001 From: Alun Turner Date: Wed, 5 Apr 2023 13:46:38 +0100 Subject: [PATCH 24/25] update tests --- .../views/rooms/wysiwyg_composer/utils/autocomplete-test.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/components/views/rooms/wysiwyg_composer/utils/autocomplete-test.ts b/test/components/views/rooms/wysiwyg_composer/utils/autocomplete-test.ts index ca828b57bf5..2612f037f2d 100644 --- a/test/components/views/rooms/wysiwyg_composer/utils/autocomplete-test.ts +++ b/test/components/views/rooms/wysiwyg_composer/utils/autocomplete-test.ts @@ -176,7 +176,7 @@ describe("getMentionAttributes", () => { expect(result).toEqual({ "data-mention-type": "user", - "style": `--avatar-background: url(${testAvatarUrlForMember}); --avatar-letter: ''`, + "style": `--avatar-background: url(${testAvatarUrlForMember}); --avatar-letter: '-'`, }); }); @@ -203,7 +203,7 @@ describe("getMentionAttributes", () => { expect(result).toEqual({ "data-mention-type": "room", - "style": `--avatar-background: url(${testAvatarUrlForRoom}); --avatar-letter: ''`, + "style": `--avatar-background: url(${testAvatarUrlForRoom}); --avatar-letter: '-'`, }); }); From bab9f73e2c27d4d20ec85f1dc3dfe55882e5ad4d Mon Sep 17 00:00:00 2001 From: Alun Turner Date: Wed, 5 Apr 2023 13:51:23 +0100 Subject: [PATCH 25/25] tweak vertical align again --- res/css/views/rooms/wysiwyg_composer/components/_Editor.pcss | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/res/css/views/rooms/wysiwyg_composer/components/_Editor.pcss b/res/css/views/rooms/wysiwyg_composer/components/_Editor.pcss index ee4ada0ed32..50f76635983 100644 --- a/res/css/views/rooms/wysiwyg_composer/components/_Editor.pcss +++ b/res/css/views/rooms/wysiwyg_composer/components/_Editor.pcss @@ -142,7 +142,7 @@ limitations under the License. /* Get the positioning of the avatar just right for consistency with timeline */ margin-inline-start: -0.4rem; margin-inline-end: 0.24rem; - vertical-align: 0.16rem; + vertical-align: 0.12rem; background-repeat: no-repeat; background-size: $font-16px;