feat: user handle links to dm (#54)
* feat: adds linkify matrix replacement with slight mods * feat: adds fetching the bot accounts from the backend * feat: atoms for verified bots * fix(wallet): updates link storage to correct orders * refactor(dm): extracts openDmForUser * chore: removes community bot atom * chore: removes last wallet bot usage * chore: removes trailing commapull/27073/head
parent
069e3c85e6
commit
fc6280ad83
|
@ -18,6 +18,7 @@
|
|||
"src/components/structures/HomePage.tsx": "src/components/structures/HomePage.tsx",
|
||||
"src/components/views/dialogs/spotlight/SpotlightDialog.tsx": "src/components/views/dialogs/spotlight/SpotlightDialog.tsx",
|
||||
"src/components/views/elements/Pill.tsx": "src/components/views/elements/Pill.tsx",
|
||||
"src/linkify-matrix.ts": "src/linkify-matrix.ts",
|
||||
"src/components/structures/LeftPanel.tsx": "src/components/structures/LeftPanel.tsx",
|
||||
"src/components/views/rooms/RoomList.tsx": "src/components/views/rooms/RoomList.tsx",
|
||||
"src/components/views/rooms/RoomSublist.tsx": "src/components/views/rooms/RoomSublist.tsx"
|
||||
|
|
|
@ -47,7 +47,5 @@
|
|||
"participant_limit": 8,
|
||||
"brand": "Element Call"
|
||||
},
|
||||
"map_style_url": "https://api.maptiler.com/maps/streets/style.json?key=fU3vlMsMn4Jb6dnEIFsx",
|
||||
"community_bot_user_id": "@communitybot:superhero.com",
|
||||
"wallet_bot_user_id": "@walletbot:superhero.com"
|
||||
"map_style_url": "https://api.maptiler.com/maps/streets/style.json?key=fU3vlMsMn4Jb6dnEIFsx"
|
||||
}
|
||||
|
|
18
src/atoms.ts
18
src/atoms.ts
|
@ -1,4 +1,5 @@
|
|||
import { atomWithStorage } from "jotai/utils";
|
||||
import { getDefaultStore } from "jotai/index";
|
||||
|
||||
type TokenThreshold = {
|
||||
threshold: string;
|
||||
|
@ -10,10 +11,17 @@ export type BareUser = {
|
|||
rawDisplayName: string;
|
||||
};
|
||||
|
||||
type BotAccounts = {
|
||||
communityBot: string;
|
||||
superheroBot: string;
|
||||
blockchainBot: string;
|
||||
};
|
||||
|
||||
export const verifiedAccountsAtom = atomWithStorage<Record<string, string>>("VERIFIED_ACCOUNTS", {});
|
||||
export const verifiedBotsAtom = atomWithStorage<Record<string, string>>("VERIFIED_BOTS", {});
|
||||
export const botAccountsAtom = atomWithStorage<BotAccounts | null>("BOT_ACCOUNTS", null);
|
||||
export const minimumTokenThresholdAtom = atomWithStorage<Record<string, TokenThreshold>>("TOKEN_THRESHOLD", {});
|
||||
export const communityBotAtom = atomWithStorage<BareUser>("COMMUNITY_BOT", {
|
||||
userId: "",
|
||||
rawDisplayName: "",
|
||||
});
|
||||
|
||||
export function getBotAccountData(): BotAccounts | null {
|
||||
const defaultStore = getDefaultStore();
|
||||
return defaultStore.get(botAccountsAtom) as BotAccounts | null;
|
||||
}
|
||||
|
|
|
@ -22,11 +22,13 @@ import { useMatrixClientContext } from "matrix-react-sdk/src/contexts/MatrixClie
|
|||
import { DirectoryMember, startDmOnFirstMessage } from "matrix-react-sdk/src/utils/direct-messages";
|
||||
import { getHomePageUrl } from "matrix-react-sdk/src/utils/pages";
|
||||
import * as React from "react";
|
||||
import { useAtom } from "jotai";
|
||||
|
||||
import { Icon as ChatScreenShot } from "../../../res/themes/superhero/img/arts/chat-screenshot.svg";
|
||||
import { Icon as ChromeIcon } from "../../../res/themes/superhero/img/icons/chrome.svg";
|
||||
import { Icon as FirefoxIcon } from "../../../res/themes/superhero/img/icons/firefox.svg";
|
||||
import { Icon as SuperheroLogo } from "../../../res/themes/superhero/img/logos/superhero-logo.svg";
|
||||
import { botAccountsAtom } from "../../atoms";
|
||||
|
||||
interface IProps {
|
||||
justRegistered?: boolean;
|
||||
|
@ -36,6 +38,7 @@ const HomePage: React.FC<IProps> = () => {
|
|||
const cli = useMatrixClientContext();
|
||||
const config: any = SdkConfig.get();
|
||||
const pageUrl = getHomePageUrl(config, cli);
|
||||
const [botAccounts] = useAtom(botAccountsAtom);
|
||||
|
||||
if (pageUrl) {
|
||||
return <EmbeddedPage className="mx_HomePage" url={pageUrl} scrollbar={true} />;
|
||||
|
@ -81,7 +84,11 @@ const HomePage: React.FC<IProps> = () => {
|
|||
<div className="mx_HomePage_default_buttons">
|
||||
<AccessibleButton
|
||||
onClick={(): void => {
|
||||
startDmOnFirstMessage(cli, [new DirectoryMember({ user_id: config.wallet_bot_user_id })]);
|
||||
startDmOnFirstMessage(cli, [
|
||||
new DirectoryMember({
|
||||
user_id: botAccounts?.superheroBot || "",
|
||||
}),
|
||||
]);
|
||||
}}
|
||||
className="mx_HomePage_button_custom"
|
||||
>
|
||||
|
|
|
@ -1,26 +1,16 @@
|
|||
import React, { useContext, useState } from "react";
|
||||
import MatrixClientContext from "matrix-react-sdk/src/contexts/MatrixClientContext";
|
||||
import AccessibleButton from "matrix-react-sdk/src/components/views/elements/AccessibleButton";
|
||||
import { MatrixClient, RoomMember, User } from "matrix-js-sdk/src/matrix";
|
||||
import { DirectoryMember, startDmOnFirstMessage } from "matrix-react-sdk/src/utils/direct-messages";
|
||||
import { useAtom } from "jotai";
|
||||
|
||||
import { Member } from "../right_panel/UserInfo";
|
||||
import { Icon as SendMessage } from "../../../../res/themes/superhero/img/icons/send.svg";
|
||||
import { BareUser, communityBotAtom } from "../../../atoms";
|
||||
import { BareUser, botAccountsAtom } from "../../../atoms";
|
||||
import { openDmForUser } from "../../../utils";
|
||||
|
||||
/**
|
||||
* Converts the member to a DirectoryMember and starts a DM with them.
|
||||
*/
|
||||
async function openDmForUser(matrixClient: MatrixClient, user: Member | BareUser): Promise<void> {
|
||||
const avatarUrl = user instanceof User ? user.avatarUrl : user instanceof RoomMember ? user.getMxcAvatarUrl() : "";
|
||||
const startDmUser = new DirectoryMember({
|
||||
user_id: user.userId,
|
||||
display_name: user.rawDisplayName,
|
||||
avatar_url: avatarUrl,
|
||||
});
|
||||
await startDmOnFirstMessage(matrixClient, [startDmUser]);
|
||||
}
|
||||
|
||||
export const MessageButton = ({
|
||||
member,
|
||||
|
@ -51,7 +41,12 @@ export const MessageButton = ({
|
|||
};
|
||||
|
||||
export const MessageCommunityBotButton = ({ text = "Send Message" }: { text?: string }): JSX.Element => {
|
||||
const [communityBot] = useAtom(communityBotAtom);
|
||||
const [botAccounts] = useAtom(botAccountsAtom);
|
||||
|
||||
return <MessageButton member={communityBot} text={text} />;
|
||||
const botUser = {
|
||||
userId: botAccounts?.communityBot || "",
|
||||
rawDisplayName: "Community Bot",
|
||||
} as Member;
|
||||
|
||||
return <MessageButton member={botUser} text={text} />;
|
||||
};
|
||||
|
|
|
@ -1,7 +1,20 @@
|
|||
import { useAtom } from "jotai";
|
||||
import React, { useCallback, useEffect } from "react";
|
||||
|
||||
import { communityBotAtom, minimumTokenThresholdAtom, verifiedAccountsAtom, verifiedBotsAtom } from "../atoms";
|
||||
import { minimumTokenThresholdAtom, verifiedAccountsAtom, botAccountsAtom } from "../atoms";
|
||||
|
||||
type BotAccounts = {
|
||||
domain: string;
|
||||
communityBot: {
|
||||
userId: string;
|
||||
};
|
||||
superheroBot: {
|
||||
userId: string;
|
||||
};
|
||||
blockchainBot: {
|
||||
userId: string;
|
||||
};
|
||||
};
|
||||
|
||||
const useMinimumTokenThreshold = (config: any): void => {
|
||||
const [, setMinimumTokenThreshold] = useAtom(minimumTokenThresholdAtom);
|
||||
|
@ -38,15 +51,7 @@ const useMinimumTokenThreshold = (config: any): void => {
|
|||
*/
|
||||
export const SuperheroProvider = ({ children, config }: any): any => {
|
||||
const [verifiedAccounts, setVerifiedAccounts] = useAtom(verifiedAccountsAtom);
|
||||
const [, setVerifiedBots] = useAtom(verifiedBotsAtom);
|
||||
const [, setCommunityBot] = useAtom(communityBotAtom);
|
||||
|
||||
useEffect(() => {
|
||||
setCommunityBot({
|
||||
userId: config.community_bot_user_id,
|
||||
rawDisplayName: "Community DAO Room Bot",
|
||||
});
|
||||
}, [setCommunityBot, config.community_bot_user_id]);
|
||||
const [, setBotAccounts] = useAtom(botAccountsAtom);
|
||||
|
||||
function loadVerifiedAccounts(): void {
|
||||
if (config.bots_backend_url) {
|
||||
|
@ -61,15 +66,26 @@ export const SuperheroProvider = ({ children, config }: any): any => {
|
|||
}
|
||||
}
|
||||
|
||||
function loadVerifiedBots(): void {
|
||||
setVerifiedBots({
|
||||
[config.community_bot_user_id]: "true",
|
||||
[config.wallet_bot_user_id]: "true",
|
||||
});
|
||||
}
|
||||
useEffect(() => {
|
||||
if (config.bots_backend_url) {
|
||||
fetch(`${config.bots_backend_url}/ui/bot-accounts`, {
|
||||
method: "GET",
|
||||
})
|
||||
.then((res) => res.json())
|
||||
.then((data: BotAccounts) => {
|
||||
setBotAccounts({
|
||||
communityBot: "@" + data.communityBot.userId + ":" + data.domain,
|
||||
superheroBot: "@" + data.superheroBot.userId + ":" + data.domain,
|
||||
blockchainBot: "@" + data.blockchainBot.userId + ":" + data.domain,
|
||||
});
|
||||
})
|
||||
.catch(() => {
|
||||
//
|
||||
});
|
||||
}
|
||||
}, [config.bots_backend_url, setBotAccounts]);
|
||||
|
||||
useEffect(() => {
|
||||
loadVerifiedBots();
|
||||
if (!verifiedAccounts?.length) {
|
||||
loadVerifiedAccounts();
|
||||
}
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import { useMemo } from "react";
|
||||
import { useAtom } from "jotai";
|
||||
|
||||
import { verifiedBotsAtom } from "../atoms";
|
||||
import { botAccountsAtom, getBotAccountData } from "../atoms";
|
||||
|
||||
/**
|
||||
* Custom hook to check if a bot is verified.
|
||||
|
@ -9,11 +9,25 @@ import { verifiedBotsAtom } from "../atoms";
|
|||
* @returns A boolean indicating whether the bot is verified or not.
|
||||
*/
|
||||
export function useVerifiedBot(botId?: string): boolean {
|
||||
const [verifiedBots] = useAtom(verifiedBotsAtom);
|
||||
const [botAccounts] = useAtom(botAccountsAtom);
|
||||
|
||||
const isVerifiedBot: boolean = useMemo(() => {
|
||||
return !!(botId && !!verifiedBots[botId]);
|
||||
}, [botId, verifiedBots]);
|
||||
|
||||
return isVerifiedBot;
|
||||
return useMemo(() => {
|
||||
return !!(
|
||||
botId &&
|
||||
(botId === botAccounts?.communityBot ||
|
||||
botId === botAccounts?.superheroBot ||
|
||||
botId === botAccounts?.blockchainBot)
|
||||
);
|
||||
}, [botId, botAccounts]);
|
||||
}
|
||||
|
||||
export function isVerifiedBot(botId?: string): boolean {
|
||||
const botAccounts = getBotAccountData();
|
||||
|
||||
return !!(
|
||||
botId &&
|
||||
(botId === botAccounts?.communityBot ||
|
||||
botId === botAccounts?.superheroBot ||
|
||||
botId === botAccounts?.blockchainBot)
|
||||
);
|
||||
}
|
||||
|
|
|
@ -0,0 +1,286 @@
|
|||
/*
|
||||
Copyright 2015, 2016 OpenMarket Ltd
|
||||
Copyright 2019 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 * as linkifyjs from "linkifyjs";
|
||||
import { EventListeners, Opts, registerCustomProtocol, registerPlugin } from "linkifyjs";
|
||||
import linkifyElement from "linkify-element";
|
||||
import linkifyString from "linkify-string";
|
||||
import { getHttpUriForMxc, User } from "matrix-js-sdk/src/matrix";
|
||||
import { ViewUserPayload } from "matrix-react-sdk/src/dispatcher/payloads/ViewUserPayload";
|
||||
import { Action } from "matrix-react-sdk/src/dispatcher/actions";
|
||||
import { ViewRoomPayload } from "matrix-react-sdk/src/dispatcher/payloads/ViewRoomPayload";
|
||||
import {
|
||||
parsePermalink,
|
||||
tryTransformEntityToPermalink,
|
||||
tryTransformPermalinkToLocalHref,
|
||||
} from "matrix-react-sdk/src/utils/permalinks/Permalinks";
|
||||
import { MatrixClientPeg } from "matrix-react-sdk/src/MatrixClientPeg";
|
||||
import dis from "matrix-react-sdk/src/dispatcher/dispatcher";
|
||||
import { PERMITTED_URL_SCHEMES } from "matrix-react-sdk/src/utils/UrlUtils";
|
||||
|
||||
import { isVerifiedBot } from "./hooks/useVerifiedBot";
|
||||
import { openDmForUser } from "./utils";
|
||||
|
||||
export enum Type {
|
||||
URL = "url",
|
||||
UserId = "userid",
|
||||
RoomAlias = "roomalias",
|
||||
}
|
||||
|
||||
function matrixOpaqueIdLinkifyParser({
|
||||
scanner,
|
||||
parser,
|
||||
token,
|
||||
name,
|
||||
}: {
|
||||
scanner: linkifyjs.ScannerInit;
|
||||
parser: linkifyjs.ParserInit;
|
||||
token: "#" | "+" | "@";
|
||||
name: Type;
|
||||
}): void {
|
||||
const {
|
||||
DOT,
|
||||
// IPV4 necessity
|
||||
NUM,
|
||||
COLON,
|
||||
SYM,
|
||||
SLASH,
|
||||
EQUALS,
|
||||
HYPHEN,
|
||||
UNDERSCORE,
|
||||
} = scanner.tokens;
|
||||
|
||||
// Contains NUM, WORD, UWORD, EMOJI, TLD, UTLD, SCHEME, SLASH_SCHEME and LOCALHOST plus custom protocols (e.g. "matrix")
|
||||
const { domain } = scanner.tokens.groups;
|
||||
|
||||
// Tokens we need that are not contained in the domain group
|
||||
const additionalLocalpartTokens = [DOT, SYM, SLASH, EQUALS, UNDERSCORE, HYPHEN];
|
||||
const additionalDomainpartTokens = [HYPHEN];
|
||||
|
||||
const matrixToken = linkifyjs.createTokenClass(name, { isLink: true });
|
||||
const matrixTokenState = new linkifyjs.State(matrixToken) as any as linkifyjs.State<linkifyjs.MultiToken>; // linkify doesn't appear to type this correctly
|
||||
|
||||
const matrixTokenWithPort = linkifyjs.createTokenClass(name, { isLink: true });
|
||||
const matrixTokenWithPortState = new linkifyjs.State(
|
||||
matrixTokenWithPort,
|
||||
) as any as linkifyjs.State<linkifyjs.MultiToken>; // linkify doesn't appear to type this correctly
|
||||
|
||||
const INITIAL_STATE = parser.start.tt(token);
|
||||
|
||||
// Localpart
|
||||
const LOCALPART_STATE = new linkifyjs.State<linkifyjs.MultiToken>();
|
||||
INITIAL_STATE.ta(domain, LOCALPART_STATE);
|
||||
INITIAL_STATE.ta(additionalLocalpartTokens, LOCALPART_STATE);
|
||||
LOCALPART_STATE.ta(domain, LOCALPART_STATE);
|
||||
LOCALPART_STATE.ta(additionalLocalpartTokens, LOCALPART_STATE);
|
||||
|
||||
// Domainpart
|
||||
const DOMAINPART_STATE_DOT = LOCALPART_STATE.tt(COLON);
|
||||
DOMAINPART_STATE_DOT.ta(domain, matrixTokenState);
|
||||
DOMAINPART_STATE_DOT.ta(additionalDomainpartTokens, matrixTokenState);
|
||||
matrixTokenState.ta(domain, matrixTokenState);
|
||||
matrixTokenState.ta(additionalDomainpartTokens, matrixTokenState);
|
||||
matrixTokenState.tt(DOT, DOMAINPART_STATE_DOT);
|
||||
|
||||
// Port suffixes
|
||||
matrixTokenState.tt(COLON).tt(NUM, matrixTokenWithPortState);
|
||||
}
|
||||
|
||||
function onUserClick(event: MouseEvent, userId: string): void {
|
||||
event.preventDefault();
|
||||
|
||||
const client = MatrixClientPeg.get();
|
||||
if (client && isVerifiedBot(userId)) {
|
||||
void openDmForUser(client, { userId, rawDisplayName: userId });
|
||||
} else {
|
||||
dis.dispatch<ViewUserPayload>({
|
||||
action: Action.ViewUser,
|
||||
member: new User(userId),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function onAliasClick(event: MouseEvent, roomAlias: string): void {
|
||||
event.preventDefault();
|
||||
dis.dispatch<ViewRoomPayload>({
|
||||
action: Action.ViewRoom,
|
||||
room_alias: roomAlias,
|
||||
metricsTrigger: "Timeline",
|
||||
metricsViaKeyboard: false,
|
||||
});
|
||||
}
|
||||
|
||||
const escapeRegExp = function (s: string): string {
|
||||
return s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
||||
};
|
||||
|
||||
// Recognise URLs from both our local and official Element deployments.
|
||||
// Anyone else really should be using matrix.to. vector:// allowed to support Element Desktop relative links.
|
||||
export const ELEMENT_URL_PATTERN =
|
||||
"^(?:vector://|https?://)?(?:" +
|
||||
escapeRegExp(window.location.host + window.location.pathname) +
|
||||
"|" +
|
||||
"(?:www\\.)?(?:riot|vector)\\.im/(?:app|beta|staging|develop)/|" +
|
||||
"(?:app|beta|staging|develop)\\.element\\.io/" +
|
||||
")(#.*)";
|
||||
|
||||
export const options: Opts = {
|
||||
events: function (href: string, type: string): EventListeners {
|
||||
switch (type as Type) {
|
||||
case Type.URL: {
|
||||
// intercept local permalinks to users and show them like userids (in userinfo of current room)
|
||||
try {
|
||||
const permalink = parsePermalink(href);
|
||||
if (permalink?.userId) {
|
||||
return {
|
||||
click: function (e: MouseEvent): void {
|
||||
onUserClick(e, permalink.userId!);
|
||||
},
|
||||
};
|
||||
} else {
|
||||
// for events, rooms etc. (anything other than users)
|
||||
const localHref = tryTransformPermalinkToLocalHref(href);
|
||||
if (localHref !== href) {
|
||||
// it could be converted to a localHref -> therefore handle locally
|
||||
return {
|
||||
click: function (e: MouseEvent): void {
|
||||
e.preventDefault();
|
||||
window.location.hash = localHref;
|
||||
},
|
||||
};
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
// OK fine, it's not actually a permalink
|
||||
}
|
||||
break;
|
||||
}
|
||||
case Type.UserId:
|
||||
return {
|
||||
click: function (e: MouseEvent): void {
|
||||
const userId = parsePermalink(href)?.userId ?? href;
|
||||
if (userId) onUserClick(e, userId);
|
||||
},
|
||||
};
|
||||
case Type.RoomAlias:
|
||||
return {
|
||||
click: function (e: MouseEvent): void {
|
||||
const alias = parsePermalink(href)?.roomIdOrAlias ?? href;
|
||||
if (alias) onAliasClick(e, alias);
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
return {};
|
||||
},
|
||||
|
||||
formatHref: function (href: string, type: Type | string): string {
|
||||
switch (type) {
|
||||
case "url":
|
||||
if (href.startsWith("mxc://") && MatrixClientPeg.get()) {
|
||||
return getHttpUriForMxc(MatrixClientPeg.get()!.baseUrl, href);
|
||||
}
|
||||
// fallthrough
|
||||
case Type.RoomAlias:
|
||||
case Type.UserId:
|
||||
default: {
|
||||
return tryTransformEntityToPermalink(MatrixClientPeg.safeGet(), href) ?? "";
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
attributes: {
|
||||
rel: "noreferrer noopener",
|
||||
},
|
||||
|
||||
ignoreTags: ["pre", "code"],
|
||||
|
||||
className: "linkified",
|
||||
|
||||
target: function (href: string, type: Type | string): string {
|
||||
if (type === Type.URL) {
|
||||
try {
|
||||
const transformed = tryTransformPermalinkToLocalHref(href);
|
||||
if (
|
||||
transformed !== href || // if it could be converted to handle locally for matrix symbols e.g. @user:server.tdl and matrix.to
|
||||
decodeURIComponent(href).match(ELEMENT_URL_PATTERN) // for https links to Element domains
|
||||
) {
|
||||
return "";
|
||||
} else {
|
||||
return "_blank";
|
||||
}
|
||||
} catch (e) {
|
||||
// malformed URI
|
||||
}
|
||||
}
|
||||
return "";
|
||||
},
|
||||
};
|
||||
|
||||
// Run the plugins
|
||||
registerPlugin(Type.RoomAlias, ({ scanner, parser }) => {
|
||||
const token = scanner.tokens.POUND as "#";
|
||||
matrixOpaqueIdLinkifyParser({
|
||||
scanner,
|
||||
parser,
|
||||
token,
|
||||
name: Type.RoomAlias,
|
||||
});
|
||||
});
|
||||
|
||||
registerPlugin(Type.UserId, ({ scanner, parser }) => {
|
||||
const token = scanner.tokens.AT as "@";
|
||||
matrixOpaqueIdLinkifyParser({
|
||||
scanner,
|
||||
parser,
|
||||
token,
|
||||
name: Type.UserId,
|
||||
});
|
||||
});
|
||||
|
||||
// Linkify supports some common protocols but not others, register all permitted url schemes if unsupported
|
||||
// https://github.com/Hypercontext/linkifyjs/blob/f4fad9df1870259622992bbfba38bfe3d0515609/packages/linkifyjs/src/scanner.js#L133-L141
|
||||
// This also handles registering the `matrix:` protocol scheme
|
||||
const linkifySupportedProtocols = ["file", "mailto", "http", "https", "ftp", "ftps"];
|
||||
const optionalSlashProtocols = [
|
||||
"bitcoin",
|
||||
"geo",
|
||||
"im",
|
||||
"magnet",
|
||||
"mailto",
|
||||
"matrix",
|
||||
"news",
|
||||
"openpgp4fpr",
|
||||
"sip",
|
||||
"sms",
|
||||
"smsto",
|
||||
"tel",
|
||||
"urn",
|
||||
"xmpp",
|
||||
];
|
||||
|
||||
PERMITTED_URL_SCHEMES.forEach((scheme) => {
|
||||
if (!linkifySupportedProtocols.includes(scheme)) {
|
||||
registerCustomProtocol(scheme, optionalSlashProtocols.includes(scheme));
|
||||
}
|
||||
});
|
||||
|
||||
registerCustomProtocol("mxc", false);
|
||||
|
||||
export const linkify = linkifyjs;
|
||||
export const _linkifyElement = linkifyElement;
|
||||
export const _linkifyString = linkifyString;
|
|
@ -0,0 +1,15 @@
|
|||
import { MatrixClient, RoomMember, User } from "matrix-js-sdk/src/matrix";
|
||||
import { DirectoryMember, startDmOnFirstMessage } from "matrix-react-sdk/src/utils/direct-messages";
|
||||
|
||||
import { Member } from "./components/views/right_panel/UserInfo";
|
||||
import { BareUser } from "./atoms";
|
||||
|
||||
export async function openDmForUser(matrixClient: MatrixClient, user: Member | BareUser): Promise<void> {
|
||||
const avatarUrl = user instanceof User ? user.avatarUrl : user instanceof RoomMember ? user.getMxcAvatarUrl() : "";
|
||||
const startDmUser = new DirectoryMember({
|
||||
user_id: user.userId,
|
||||
display_name: user.rawDisplayName,
|
||||
avatar_url: avatarUrl,
|
||||
});
|
||||
await startDmOnFirstMessage(matrixClient, [startDmUser]);
|
||||
}
|
Loading…
Reference in New Issue