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
6 changed files with 406 additions and 82 deletions
|
@ -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 {};
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue