Merge remote-tracking branch 'origin/develop' into hs/media-previews-server-config
This commit is contained in:
@@ -86,7 +86,7 @@
|
||||
"@fontsource/inter": "^5",
|
||||
"@formatjs/intl-segmenter": "^11.5.7",
|
||||
"@matrix-org/analytics-events": "^0.29.2",
|
||||
"@matrix-org/emojibase-bindings": "^1.4.0",
|
||||
"@matrix-org/emojibase-bindings": "^1.3.4",
|
||||
"@matrix-org/react-sdk-module-api": "^2.4.0",
|
||||
"@matrix-org/spec": "^1.7.0",
|
||||
"@sentry/browser": "^9.0.0",
|
||||
@@ -109,7 +109,7 @@
|
||||
"diff-dom": "^5.0.0",
|
||||
"diff-match-patch": "^1.0.5",
|
||||
"domutils": "^3.2.2",
|
||||
"emojibase-regex": "16.0.0",
|
||||
"emojibase-regex": "15.3.2",
|
||||
"escape-html": "^1.0.3",
|
||||
"file-saver": "^2.0.5",
|
||||
"filesize": "10.1.6",
|
||||
|
||||
Binary file not shown.
@@ -147,11 +147,12 @@ export function avatarUrlForRoom(
|
||||
width?: number,
|
||||
height?: number,
|
||||
resizeMethod?: ResizeMethod,
|
||||
avatarMxcOverride?: string,
|
||||
): string | null {
|
||||
if (!room) return null; // null-guard
|
||||
|
||||
if (room.getMxcAvatarUrl()) {
|
||||
const media = mediaFromMxc(room.getMxcAvatarUrl() ?? undefined);
|
||||
const mxc = avatarMxcOverride ?? room.getMxcAvatarUrl();
|
||||
if (mxc) {
|
||||
const media = mediaFromMxc(mxc);
|
||||
if (width !== undefined && height !== undefined) {
|
||||
return media.getThumbnailOfSourceHttp(width, height, resizeMethod);
|
||||
}
|
||||
|
||||
@@ -7,20 +7,20 @@ Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
|
||||
import React, { useCallback, useMemo, type ComponentProps } from "react";
|
||||
import { type Room, RoomType, KnownMembership } from "matrix-js-sdk/src/matrix";
|
||||
import { type Room, RoomType, KnownMembership, EventType } from "matrix-js-sdk/src/matrix";
|
||||
import { type RoomAvatarEventContent } from "matrix-js-sdk/src/types";
|
||||
|
||||
import BaseAvatar from "./BaseAvatar";
|
||||
import ImageView from "../elements/ImageView";
|
||||
import Modal from "../../../Modal";
|
||||
import * as Avatar from "../../../Avatar";
|
||||
import DMRoomMap from "../../../utils/DMRoomMap";
|
||||
import { mediaFromMxc } from "../../../customisations/Media";
|
||||
import { type IOOBData } from "../../../stores/ThreepidInviteStore";
|
||||
import { LocalRoom } from "../../../models/LocalRoom";
|
||||
import { filterBoolean } from "../../../utils/arrays";
|
||||
import { MediaPreviewValue } from "../../../@types/media_preview";
|
||||
import { useRoomAvatar } from "../../../hooks/room/useRoomAvatar";
|
||||
import { useSettingValue } from "../../../hooks/useSettings";
|
||||
import { useRoomState } from "../../../hooks/useRoomState";
|
||||
import { useRoomIdName } from "../../../hooks/room/useRoomIdName";
|
||||
import { MediaPreviewValue } from "../../../@types/media_preview";
|
||||
|
||||
interface IProps extends Omit<ComponentProps<typeof BaseAvatar>, "name" | "idName" | "url" | "onClick" | "size"> {
|
||||
// Room may be left unset here, but if it is,
|
||||
@@ -36,37 +36,16 @@ interface IProps extends Omit<ComponentProps<typeof BaseAvatar>, "name" | "idNam
|
||||
onClick?(): void;
|
||||
}
|
||||
|
||||
export function idNameForRoom(room: Room): string {
|
||||
const dmMapUserId = DMRoomMap.shared().getUserIdForRoomId(room.roomId);
|
||||
// If the room is a DM, we use the other user's ID for the color hash
|
||||
// in order to match the room avatar with their avatar
|
||||
if (dmMapUserId) return dmMapUserId;
|
||||
|
||||
if (room instanceof LocalRoom && room.targets.length === 1) {
|
||||
return room.targets[0].userId;
|
||||
}
|
||||
|
||||
return room.roomId;
|
||||
}
|
||||
|
||||
const RoomAvatar: React.FC<IProps> = ({ room, viewAvatarOnClick, onClick, oobData, ...otherProps }) => {
|
||||
const size = otherProps.size ?? "36px";
|
||||
|
||||
const RoomAvatar: React.FC<IProps> = ({ room, viewAvatarOnClick, onClick, oobData, size = "36px", ...otherProps }) => {
|
||||
const roomName = room?.name ?? oobData?.name ?? "?";
|
||||
const roomAvatarMxc = useRoomAvatar(room);
|
||||
const roomIdName = useMemo(() => {
|
||||
if (room) {
|
||||
return idNameForRoom(room);
|
||||
} else {
|
||||
return oobData?.roomId;
|
||||
}
|
||||
}, [oobData, room]);
|
||||
const avatarEvent = useRoomState(room, (state) => state.getStateEvents(EventType.RoomAvatar, ""));
|
||||
const roomIdName = useRoomIdName(room, oobData);
|
||||
|
||||
const mediaPreviewEnabled =
|
||||
const showAvatarsOnInvites =
|
||||
useSettingValue("mediaPreviewConfig", room?.roomId).invite_avatars === MediaPreviewValue.On;
|
||||
|
||||
const onRoomAvatarClick = useCallback(() => {
|
||||
const avatarUrl = Avatar.avatarUrlForRoom(room ?? null, undefined, undefined, undefined);
|
||||
const avatarUrl = Avatar.avatarUrlForRoom(room ?? null);
|
||||
if (!avatarUrl) return;
|
||||
const params = {
|
||||
src: avatarUrl,
|
||||
@@ -77,15 +56,14 @@ const RoomAvatar: React.FC<IProps> = ({ room, viewAvatarOnClick, onClick, oobDat
|
||||
}, [room]);
|
||||
|
||||
const urls = useMemo(() => {
|
||||
// Apparently parseInt ignores suffixes.
|
||||
const sizeInt = parseInt(size, 10);
|
||||
const myMembership = room?.getMyMembership();
|
||||
if (myMembership === KnownMembership.Invite || !myMembership) {
|
||||
if (!mediaPreviewEnabled) {
|
||||
// The user has opted out of showing avatars, so return no urls here.
|
||||
return [];
|
||||
}
|
||||
if (!showAvatarsOnInvites && (myMembership === KnownMembership.Invite || !myMembership)) {
|
||||
// The user has opted out of showing avatars, so return no urls here.
|
||||
return [];
|
||||
}
|
||||
|
||||
// parseInt ignores suffixes.
|
||||
const sizeInt = parseInt(size, 10);
|
||||
let oobAvatar: string | null = null;
|
||||
if (oobData?.avatarUrl) {
|
||||
oobAvatar = mediaFromMxc(oobData?.avatarUrl).getThumbnailOfSourceHttp(sizeInt, sizeInt, "crop");
|
||||
@@ -93,9 +71,15 @@ const RoomAvatar: React.FC<IProps> = ({ room, viewAvatarOnClick, onClick, oobDat
|
||||
|
||||
return filterBoolean([
|
||||
oobAvatar, // highest priority
|
||||
roomAvatarMxc && Avatar.avatarUrlForRoom(room ?? null, sizeInt, sizeInt, "crop"),
|
||||
Avatar.avatarUrlForRoom(
|
||||
room ?? null,
|
||||
sizeInt,
|
||||
sizeInt,
|
||||
"crop",
|
||||
avatarEvent?.getContent<RoomAvatarEventContent>().url,
|
||||
),
|
||||
]);
|
||||
}, [mediaPreviewEnabled, room, size, roomAvatarMxc, oobData]);
|
||||
}, [showAvatarsOnInvites, room, size, avatarEvent, oobData]);
|
||||
|
||||
return (
|
||||
<BaseAvatar
|
||||
|
||||
@@ -52,14 +52,14 @@ function getDmMember(room: Room): RoomMember | null {
|
||||
return otherUserId ? room.getMember(otherUserId) : null;
|
||||
}
|
||||
|
||||
export const useDmMember = (room: Room): RoomMember | null => {
|
||||
const [dmMember, setDmMember] = useState<RoomMember | null>(getDmMember(room));
|
||||
export const useDmMember = (room?: Room): RoomMember | null => {
|
||||
const [dmMember, setDmMember] = useState<RoomMember | null>(room ? getDmMember(room) : null);
|
||||
const updateDmMember = (): void => {
|
||||
setDmMember(getDmMember(room));
|
||||
setDmMember(room ? getDmMember(room) : null);
|
||||
};
|
||||
|
||||
useEventEmitter(room.currentState, RoomStateEvent.Members, updateDmMember);
|
||||
useEventEmitter(room.client, ClientEvent.AccountData, updateDmMember);
|
||||
useEventEmitter(room?.currentState, RoomStateEvent.Members, updateDmMember);
|
||||
useEventEmitter(room?.client, ClientEvent.AccountData, updateDmMember);
|
||||
useEffect(updateDmMember, [room]);
|
||||
|
||||
return dmMember;
|
||||
|
||||
@@ -70,7 +70,7 @@ export default class RoomAvatarEvent extends React.Component<IProps> {
|
||||
className="mx_RoomAvatarEvent_avatar"
|
||||
onClick={this.onAvatarClick}
|
||||
>
|
||||
<RoomAvatar size="14px" oobData={oobData} />
|
||||
<RoomAvatar room={room ?? undefined} size="14px" oobData={oobData} />
|
||||
</AccessibleButton>
|
||||
),
|
||||
},
|
||||
|
||||
@@ -7,7 +7,7 @@ Please see LICENSE files in the repository root for full details.
|
||||
|
||||
import React, { createRef } from "react";
|
||||
import classNames from "classnames";
|
||||
import { ContentHelpers, EventType } from "matrix-js-sdk/src/matrix";
|
||||
import { ContentHelpers, EventType, type Room } from "matrix-js-sdk/src/matrix";
|
||||
|
||||
import { _t } from "../../../languageHandler";
|
||||
import { MatrixClientPeg } from "../../../MatrixClientPeg";
|
||||
@@ -15,7 +15,8 @@ import Field from "../elements/Field";
|
||||
import AccessibleButton, { type ButtonEvent } from "../elements/AccessibleButton";
|
||||
import AvatarSetting from "../settings/AvatarSetting";
|
||||
import { htmlSerializeFromMdIfNeeded } from "../../../editor/serialize";
|
||||
import { idNameForRoom } from "../avatars/RoomAvatar";
|
||||
import DMRoomMap from "../../../utils/DMRoomMap";
|
||||
import { LocalRoom } from "../../../models/LocalRoom";
|
||||
|
||||
interface IProps {
|
||||
roomId: string;
|
||||
@@ -36,6 +37,19 @@ interface IState {
|
||||
canSetAvatar: boolean;
|
||||
}
|
||||
|
||||
function idNameForRoom(room: Room): string {
|
||||
const dmMapUserId = DMRoomMap.shared().getUserIdForRoomId(room.roomId);
|
||||
// If the room is a DM, we use the other user's ID for the color hash
|
||||
// in order to match the room avatar with their avatar
|
||||
if (dmMapUserId) return dmMapUserId;
|
||||
|
||||
if (room instanceof LocalRoom && room.targets.length === 1) {
|
||||
return room.targets[0].userId;
|
||||
}
|
||||
|
||||
return room.roomId;
|
||||
}
|
||||
|
||||
// TODO: Merge with ProfileSettings?
|
||||
export default class RoomProfileSettings extends React.Component<IProps, IState> {
|
||||
private avatarUpload = createRef<HTMLInputElement>();
|
||||
|
||||
@@ -52,6 +52,7 @@ const InviteButton: React.FC<Props> = ({ vm }) => {
|
||||
Icon={InviteIcon}
|
||||
disabled={disabled}
|
||||
aria-label={_t("action|invite")}
|
||||
type="button"
|
||||
/>
|
||||
</OptionalTooltip>
|
||||
);
|
||||
@@ -67,6 +68,7 @@ const InviteButton: React.FC<Props> = ({ vm }) => {
|
||||
className="mx_MemberListHeaderView_invite_large"
|
||||
disabled={!vm.canInvite}
|
||||
onClick={vm.onInviteButtonClick}
|
||||
type="button"
|
||||
>
|
||||
{_t("action|invite")}
|
||||
</Button>
|
||||
|
||||
32
src/hooks/room/useRoomIdName.ts
Normal file
32
src/hooks/room/useRoomIdName.ts
Normal file
@@ -0,0 +1,32 @@
|
||||
/*
|
||||
Copyright 2025 New Vector Ltd.
|
||||
|
||||
SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial
|
||||
Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
|
||||
import { type Room } from "matrix-js-sdk/src/matrix";
|
||||
|
||||
import { useDmMember } from "../../components/views/avatars/WithPresenceIndicator.tsx";
|
||||
import { LocalRoom } from "../../models/LocalRoom.ts";
|
||||
|
||||
/**
|
||||
* Determine a stable ID for generating hash colours. If the room
|
||||
* is a DM (or local room), then the other user's ID will be used.
|
||||
* @param oobData - out-of-band information about the room
|
||||
* @returns An ID string, or undefined if the room and oobData are undefined.
|
||||
*/
|
||||
export function useRoomIdName(room?: Room, oobData?: { roomId?: string }): string | undefined {
|
||||
const dmMember = useDmMember(room);
|
||||
if (dmMember) {
|
||||
// If the room is a DM, we use the other user's ID for the color hash
|
||||
// in order to match the room avatar with their avatar
|
||||
return dmMember.userId;
|
||||
} else if (room instanceof LocalRoom && room.targets.length === 1) {
|
||||
return room.targets[0].userId;
|
||||
} else if (room) {
|
||||
return room.roomId;
|
||||
} else {
|
||||
return oobData?.roomId;
|
||||
}
|
||||
}
|
||||
@@ -8,7 +8,7 @@ Please see LICENSE files in the repository root for full details.
|
||||
|
||||
import { render, waitFor } from "jest-matrix-react";
|
||||
import { mocked } from "jest-mock";
|
||||
import { JoinRule, type MatrixClient, PendingEventOrdering, Room } from "matrix-js-sdk/src/matrix";
|
||||
import { JoinRule, type MatrixClient, PendingEventOrdering, Room, RoomMember } from "matrix-js-sdk/src/matrix";
|
||||
import React from "react";
|
||||
import userEvent from "@testing-library/user-event";
|
||||
|
||||
@@ -79,6 +79,7 @@ describe("DecoratedRoomAvatar", () => {
|
||||
} as unknown as DMRoomMap;
|
||||
jest.spyOn(DMRoomMap, "shared").mockReturnValue(dmRoomMap);
|
||||
jest.spyOn(DecoratedRoomAvatar.prototype as any, "getPresenceIcon").mockImplementation(() => "ONLINE");
|
||||
jest.spyOn(room, "getMember").mockReturnValue(new RoomMember(room.roomId, DM_USER_ID));
|
||||
|
||||
const { container, asFragment } = renderComponent();
|
||||
|
||||
|
||||
@@ -8,7 +8,7 @@ Please see LICENSE files in the repository root for full details.
|
||||
|
||||
import React from "react";
|
||||
import { render } from "jest-matrix-react";
|
||||
import { type MatrixClient, Room } from "matrix-js-sdk/src/matrix";
|
||||
import { EventType, type MatrixClient, MatrixEvent, Room, RoomMember } from "matrix-js-sdk/src/matrix";
|
||||
import { mocked } from "jest-mock";
|
||||
|
||||
import RoomAvatar from "../../../../../src/components/views/avatars/RoomAvatar";
|
||||
@@ -61,6 +61,7 @@ describe("RoomAvatar", () => {
|
||||
it("should render as expected for a DM room", () => {
|
||||
const userId = "@dm_user@example.com";
|
||||
const room = new Room("!room:example.com", client, client.getSafeUserId());
|
||||
room.getMember = jest.fn().mockImplementation(() => new RoomMember(room.roomId, userId));
|
||||
room.name = "DM room";
|
||||
mocked(DMRoomMap.shared().getUserIdForRoomId).mockReturnValue(userId);
|
||||
expect(render(<RoomAvatar room={room} />).container).toMatchSnapshot();
|
||||
@@ -78,6 +79,17 @@ describe("RoomAvatar", () => {
|
||||
jest.spyOn(room, "getMxcAvatarUrl").mockImplementation(() => "mxc://example.com/foobar");
|
||||
room.name = "test room";
|
||||
room.updateMyMembership("invite");
|
||||
room.currentState.setStateEvents([
|
||||
new MatrixEvent({
|
||||
sender: "@sender:server",
|
||||
room_id: room.roomId,
|
||||
type: EventType.RoomAvatar,
|
||||
state_key: "",
|
||||
content: {
|
||||
url: "mxc://example.com/foobar",
|
||||
},
|
||||
}),
|
||||
]);
|
||||
expect(render(<RoomAvatar room={room} />).container).toMatchSnapshot();
|
||||
});
|
||||
it("should not render an invite avatar if the user has disabled it", () => {
|
||||
|
||||
40
yarn.lock
40
yarn.lock
@@ -2158,13 +2158,13 @@
|
||||
resolved "https://registry.yarnpkg.com/@matrix-org/analytics-events/-/analytics-events-0.29.2.tgz#20d9877f11d5e411f1610f396f9e490673d6da50"
|
||||
integrity sha512-kpCdf6DBxgE7MbBbYr7FvahrktHHtiph3QN10I6nBAAPQ+hmR3aZHBECxjxLQ9RxvtBF9nlKK4bgy2YrNp6j3A==
|
||||
|
||||
"@matrix-org/emojibase-bindings@^1.4.0":
|
||||
version "1.4.0"
|
||||
resolved "https://registry.yarnpkg.com/@matrix-org/emojibase-bindings/-/emojibase-bindings-1.4.0.tgz#ad1f917b03cd1fcf049bc3de809beb6cbae78009"
|
||||
integrity sha512-5PsY183hHK04I8uBCIoyVvZefu/VJYB5YhoM7DAHn0WQtedn70ZCES9iUxcyMRFGzfwiiqd+ArsK8VwLN5JEVA==
|
||||
"@matrix-org/emojibase-bindings@^1.3.4":
|
||||
version "1.3.4"
|
||||
resolved "https://registry.yarnpkg.com/@matrix-org/emojibase-bindings/-/emojibase-bindings-1.3.4.tgz#b0dad8e8b8bbe433e419b59e38f933bcdaf9c271"
|
||||
integrity sha512-+nhBg0dxjy3U4/Tn6WIsnzqiqazc0pfStc2dkSBxDnc4xnimDB6vcIad53fUIsl7SeT50ake0hhnBJs0ZDDk6Q==
|
||||
dependencies:
|
||||
emojibase "^16.0.0"
|
||||
emojibase-data "^16.0.3"
|
||||
emojibase "^15.3.1"
|
||||
emojibase-data "^15.3.1"
|
||||
|
||||
"@matrix-org/matrix-sdk-crypto-wasm@^14.0.1":
|
||||
version "14.0.1"
|
||||
@@ -3738,7 +3738,7 @@
|
||||
classnames "^2.5.1"
|
||||
vaul "^1.0.0"
|
||||
|
||||
"@vector-im/matrix-wysiwyg-wasm@link:../../../.cache/yarn/v6/npm-@vector-im-matrix-wysiwyg-2.38.3-cc54d8b3e9472bcd8e622126ba364ee31952cd8a-integrity/node_modules/bindings/wysiwyg-wasm":
|
||||
"@vector-im/matrix-wysiwyg-wasm@link:../../../../Library/Caches/Yarn/v6/npm-@vector-im-matrix-wysiwyg-2.38.3-cc54d8b3e9472bcd8e622126ba364ee31952cd8a-integrity/node_modules/bindings/wysiwyg-wasm":
|
||||
version "0.0.0"
|
||||
uid ""
|
||||
|
||||
@@ -3747,7 +3747,7 @@
|
||||
resolved "https://registry.yarnpkg.com/@vector-im/matrix-wysiwyg/-/matrix-wysiwyg-2.38.3.tgz#cc54d8b3e9472bcd8e622126ba364ee31952cd8a"
|
||||
integrity sha512-fqo8P55Vc/t0vxpFar9RDJN5gKEjJmzrLo+O4piDbFda6VrRoqrWAtiu0Au0g6B4hRDPKIuFupk8v9Ja7q8Hvg==
|
||||
dependencies:
|
||||
"@vector-im/matrix-wysiwyg-wasm" "link:../../Library/Caches/Yarn/v6/npm-@vector-im-matrix-wysiwyg-2.38.3-cc54d8b3e9472bcd8e622126ba364ee31952cd8a-integrity/node_modules/bindings/wysiwyg-wasm"
|
||||
"@vector-im/matrix-wysiwyg-wasm" "link:../../../.cache/yarn/v6/npm-@vector-im-matrix-wysiwyg-2.38.3-cc54d8b3e9472bcd8e622126ba364ee31952cd8a-integrity/node_modules/bindings/wysiwyg-wasm"
|
||||
|
||||
"@webassemblyjs/ast@1.14.1", "@webassemblyjs/ast@^1.14.1":
|
||||
version "1.14.1"
|
||||
@@ -5941,20 +5941,20 @@ emoji-regex@^9.2.2:
|
||||
resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-9.2.2.tgz#840c8803b0d8047f4ff0cf963176b32d4ef3ed72"
|
||||
integrity sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==
|
||||
|
||||
emojibase-data@^16.0.3:
|
||||
version "16.0.3"
|
||||
resolved "https://registry.yarnpkg.com/emojibase-data/-/emojibase-data-16.0.3.tgz#564ddfe11a2fdcba24975335f857dc85ee895027"
|
||||
integrity sha512-MopInVCDZeXvqBMPJxnvYUyKw9ImJZqIDr2sABo6acVSPev5IDYX+mf+0tsu96JJyc3INNvgIf06Eso7bdTX2Q==
|
||||
emojibase-data@^15.3.1:
|
||||
version "15.3.2"
|
||||
resolved "https://registry.yarnpkg.com/emojibase-data/-/emojibase-data-15.3.2.tgz#2742246bfe14f16a7829b42ca156dec09934cf85"
|
||||
integrity sha512-TpDyTDDTdqWIJixV5sTA6OQ0P0JfIIeK2tFRR3q56G9LK65ylAZ7z3KyBXokpvTTJ+mLUXQXbLNyVkjvnTLE+A==
|
||||
|
||||
emojibase-regex@16.0.0:
|
||||
version "16.0.0"
|
||||
resolved "https://registry.yarnpkg.com/emojibase-regex/-/emojibase-regex-16.0.0.tgz#e648d8789dc22c6adc9a10b1af47135559f65a88"
|
||||
integrity sha512-ZMp31BkzBWNW+T73of6NURL6nXQa5GkfKneOkr3cEwBDVllbW/2nuva7NO0J3RjaQ07+SZQNgPTGZ4JlIhmM2Q==
|
||||
emojibase-regex@15.3.2:
|
||||
version "15.3.2"
|
||||
resolved "https://registry.yarnpkg.com/emojibase-regex/-/emojibase-regex-15.3.2.tgz#5175231715b86d4b437754527288844a6c29318f"
|
||||
integrity sha512-ue6BVeb2qu33l97MkxcOoyMJlg6Tug3eTv2z1at+M9TjvlWKvdmAPvZIDG1JbT2RH3FSyJNLucO5K5H/yxT03w==
|
||||
|
||||
emojibase@^16.0.0:
|
||||
version "16.0.0"
|
||||
resolved "https://registry.yarnpkg.com/emojibase/-/emojibase-16.0.0.tgz#9da603b7d740645d0a5d21c6dcfb97c53d6f96c7"
|
||||
integrity sha512-Nw2m7JLIO4Ou2X/yZPRNscHQXVbbr6SErjkJ7EooG7MbR3yDZszCv9KTizsXFc7yZl0n3WF+qUKIC/Lw6H9xaQ==
|
||||
emojibase@^15.3.1:
|
||||
version "15.3.1"
|
||||
resolved "https://registry.yarnpkg.com/emojibase/-/emojibase-15.3.1.tgz#7f6ff5482486f23e59a457de64e974bd35f3c9a3"
|
||||
integrity sha512-GNsjHnG2J3Ktg684Fs/vZR/6XpOSkZPMAv85EHrr6br2RN2cJNwdS4am/3YSK3y+/gOv2kmoK3GGdahXdMxg2g==
|
||||
|
||||
emojis-list@^3.0.0:
|
||||
version "3.0.0"
|
||||
|
||||
Reference in New Issue
Block a user