mirror of
https://github.com/vector-im/element-web.git
synced 2024-11-15 20:54:59 +08:00
Add room and user avatars to rte (#10497)
Co-authored-by: Michael Telatynski <7t3chguy@gmail.com>
This commit is contained in:
parent
5c0e5eb0fb
commit
e03eac12c3
@ -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",
|
||||
|
@ -106,19 +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;
|
||||
/* 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;
|
||||
box-sizing: border-box;
|
||||
max-width: 100%;
|
||||
overflow: hidden;
|
||||
@ -126,12 +118,40 @@ 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 */
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
|
||||
/* avatar pseudo element */
|
||||
&::before {
|
||||
/* 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;
|
||||
vertical-align: 0.12rem;
|
||||
|
||||
background-repeat: no-repeat;
|
||||
background-size: $font-16px;
|
||||
border-radius: $font-16px;
|
||||
|
||||
color: $avatar-initial-color;
|
||||
font-weight: normal;
|
||||
font-size: $font-10-4px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -15,13 +15,13 @@ 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 { 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 {
|
||||
/**
|
||||
@ -37,55 +37,6 @@ interface WysiwygAutocompleteProps {
|
||||
handleMention: FormattingFunctions["mention"];
|
||||
}
|
||||
|
||||
/**
|
||||
* Builds the query for the `<Autocomplete />` 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}`;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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
|
||||
*
|
||||
* @param completion - the item selected from the autocomplete, currently treated as a room completion
|
||||
* @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);
|
||||
});
|
||||
}
|
||||
|
||||
// if we haven't managed to find the room, use the alias as a fallback
|
||||
return roomForAutocomplete?.name || alias;
|
||||
}
|
||||
|
||||
/**
|
||||
* Given the current suggestion from the rust model and a handler function, this component
|
||||
* will display the legacy `<Autocomplete />` component (as used in the BasicMessageComposer)
|
||||
@ -99,22 +50,14 @@ const WysiwygAutocomplete = forwardRef(
|
||||
const client = useMatrixClientContext();
|
||||
|
||||
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;
|
||||
// TODO handle all of the completion types
|
||||
// Using this to pick out the ones we can handle during implementation
|
||||
if (client && room && completion.href && (completion.type === "room" || completion.type === "user")) {
|
||||
handleMention(
|
||||
completion.href,
|
||||
getMentionDisplayText(completion, client),
|
||||
getMentionAttributes(completion, client, room),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -0,0 +1,137 @@
|
||||
/*
|
||||
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 `<Autocomplete />` 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 {
|
||||
// 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 || "");
|
||||
|
||||
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 = defaultLetterContent;
|
||||
if (avatarUrl === defaultAvatarUrl) {
|
||||
initialLetter = Avatar.getInitialLetter(name) ?? defaultLetterContent;
|
||||
}
|
||||
|
||||
return {
|
||||
"data-mention-type": completion.type,
|
||||
"style": `--avatar-background: url(${avatarUrl}); --avatar-letter: '${initialLetter}'`,
|
||||
};
|
||||
} else if (completion.type === "room") {
|
||||
// logic as used in RoomPillPart.setAvatar in parts.ts
|
||||
const mentionedRoom = getRoomFromCompletion(completion, client);
|
||||
const aliasFromCompletion = completion.completion;
|
||||
|
||||
let initialLetter = defaultLetterContent;
|
||||
let avatarUrl = Avatar.avatarUrlForRoom(mentionedRoom ?? null, 16, 16, "crop");
|
||||
if (!avatarUrl) {
|
||||
initialLetter = Avatar.getInitialLetter(mentionedRoom?.name || aliasFromCompletion) ?? defaultLetterContent;
|
||||
avatarUrl = Avatar.defaultAvatarUrlForString(mentionedRoom?.roomId ?? aliasFromCompletion);
|
||||
}
|
||||
|
||||
return {
|
||||
"data-mention-type": completion.type,
|
||||
"style": `--avatar-background: url(${avatarUrl}); --avatar-letter: '${initialLetter}'`,
|
||||
};
|
||||
}
|
||||
|
||||
return {};
|
||||
}
|
@ -0,0 +1,224 @@
|
||||
/*
|
||||
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 { mocked } from "jest-mock";
|
||||
|
||||
import { ICompletion } from "../../../../../../src/autocomplete/Autocompleter";
|
||||
import {
|
||||
buildQuery,
|
||||
getRoomFromCompletion,
|
||||
getMentionDisplayText,
|
||||
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";
|
||||
const mockRoom = mkRoom(mockClient, mockRoomId);
|
||||
|
||||
const createMockCompletion = (props: Partial<ICompletion>): ICompletion => {
|
||||
return {
|
||||
completion: "mock",
|
||||
range: { beginning: true, start: 0, end: 0 },
|
||||
...props,
|
||||
};
|
||||
};
|
||||
|
||||
jest.mock("../../../../../../src/Avatar");
|
||||
|
||||
beforeEach(() => jest.clearAllMocks());
|
||||
afterAll(() => jest.restoreAllMocks());
|
||||
|
||||
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");
|
||||
});
|
||||
});
|
||||
|
||||
describe("getRoomFromCompletion", () => {
|
||||
const createMockRoomCompletion = (props: Partial<ICompletion>): ICompletion => {
|
||||
return createMockCompletion({ ...props, type: "room" });
|
||||
};
|
||||
|
||||
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);
|
||||
});
|
||||
});
|
||||
|
||||
describe("getMentionDisplayText", () => {
|
||||
it("returns an empty string if we are not handling a user or a room type", () => {
|
||||
const nonHandledCompletionTypes = ["at-room", "community", "command"] as const;
|
||||
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);
|
||||
});
|
||||
});
|
||||
|
||||
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 }));
|
||||
|
||||
nonHandledCompletions.forEach((completion) => {
|
||||
expect(getMentionAttributes(completion, mockClient, mockRoom)).toEqual({});
|
||||
});
|
||||
});
|
||||
|
||||
const testAvatarUrlForString = "www.stringUrl.com";
|
||||
const testAvatarUrlForMember = "www.memberUrl.com";
|
||||
const testAvatarUrlForRoom = "www.roomUrl.com";
|
||||
const testInitialLetter = "z";
|
||||
|
||||
const mockAvatar = mocked(_mockAvatar);
|
||||
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}'`,
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
@ -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"
|
||||
|
Loading…
Reference in New Issue
Block a user