Split DM create form into separate username and server fields with smart defaults.
This commit is contained in:
parent
00935aecff
commit
58ec12d42d
4 changed files with 90 additions and 34 deletions
|
|
@ -369,11 +369,12 @@
|
||||||
"no_direct_messages_desc": "You do not have any direct messages yet.",
|
"no_direct_messages_desc": "You do not have any direct messages yet.",
|
||||||
"direct_message": "Direct Message",
|
"direct_message": "Direct Message",
|
||||||
"create_chat": "Create Chat",
|
"create_chat": "Create Chat",
|
||||||
"create_chat_subtitle": "Start a private, encrypted chat by entering a user ID.",
|
"create_chat_subtitle": "Start a private, encrypted chat by entering a username.",
|
||||||
"chats": "Chats",
|
"chats": "Chats",
|
||||||
"user_id": "User ID",
|
"username": "Username",
|
||||||
"user_id_placeholder": "@username:server",
|
"username_placeholder": "username",
|
||||||
"invalid_user_id": "Please enter a valid User ID.",
|
"server": "Server",
|
||||||
|
"invalid_user_id": "Please enter a valid username and server.",
|
||||||
"options": "Options",
|
"options": "Options",
|
||||||
"e2e_encryption": "End-to-End Encryption",
|
"e2e_encryption": "End-to-End Encryption",
|
||||||
"e2e_encryption_desc": "Once this feature is enabled, it can't be disabled after the room is created.",
|
"e2e_encryption_desc": "Once this feature is enabled, it can't be disabled after the room is created.",
|
||||||
|
|
|
||||||
|
|
@ -369,11 +369,12 @@
|
||||||
"no_direct_messages_desc": "У вас ещё нет личных сообщений.",
|
"no_direct_messages_desc": "У вас ещё нет личных сообщений.",
|
||||||
"direct_message": "Новый чат",
|
"direct_message": "Новый чат",
|
||||||
"create_chat": "Новый чат",
|
"create_chat": "Новый чат",
|
||||||
"create_chat_subtitle": "Начните приватный зашифрованный чат, указав ID пользователя.",
|
"create_chat_subtitle": "Начните приватный зашифрованный чат, указав имя пользователя.",
|
||||||
"chats": "Чаты",
|
"chats": "Чаты",
|
||||||
"user_id": "ID пользователя",
|
"username": "Имя пользователя",
|
||||||
"user_id_placeholder": "@username:server",
|
"username_placeholder": "username",
|
||||||
"invalid_user_id": "Введите корректный ID пользователя.",
|
"server": "Сервер",
|
||||||
|
"invalid_user_id": "Введите корректные имя пользователя и сервер.",
|
||||||
"options": "Параметры",
|
"options": "Параметры",
|
||||||
"e2e_encryption": "Сквозное шифрование",
|
"e2e_encryption": "Сквозное шифрование",
|
||||||
"e2e_encryption_desc": "После включения эту функцию нельзя отключить после создания комнаты.",
|
"e2e_encryption_desc": "После включения эту функцию нельзя отключить после создания комнаты.",
|
||||||
|
|
|
||||||
|
|
@ -1,11 +1,30 @@
|
||||||
import { Box, Button, color, config, Icon, Icons, Input, Spinner, Switch, Text } from 'folds';
|
import {
|
||||||
|
Box,
|
||||||
|
Button,
|
||||||
|
color,
|
||||||
|
config,
|
||||||
|
Icon,
|
||||||
|
Icons,
|
||||||
|
Input,
|
||||||
|
Spinner,
|
||||||
|
Switch,
|
||||||
|
Text,
|
||||||
|
toRem,
|
||||||
|
} from 'folds';
|
||||||
import React, { FormEventHandler, useCallback, useState } from 'react';
|
import React, { FormEventHandler, useCallback, useState } from 'react';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import { ICreateRoomStateEvent, MatrixError, Preset, Visibility } from 'matrix-js-sdk';
|
import { ICreateRoomStateEvent, MatrixError, Preset, Visibility } from 'matrix-js-sdk';
|
||||||
import { useNavigate } from 'react-router-dom';
|
import { useNavigate } from 'react-router-dom';
|
||||||
import { SettingTile } from '../../components/setting-tile';
|
import { SettingTile } from '../../components/setting-tile';
|
||||||
import { SequenceCard } from '../../components/sequence-card';
|
import { SequenceCard } from '../../components/sequence-card';
|
||||||
import { addRoomIdToMDirect, getDMRoomFor, isUserId } from '../../utils/matrix';
|
import {
|
||||||
|
addRoomIdToMDirect,
|
||||||
|
getDMRoomFor,
|
||||||
|
getMxIdLocalPart,
|
||||||
|
getMxIdServer,
|
||||||
|
isServerName,
|
||||||
|
isUserId,
|
||||||
|
} from '../../utils/matrix';
|
||||||
import { useMatrixClient } from '../../hooks/useMatrixClient';
|
import { useMatrixClient } from '../../hooks/useMatrixClient';
|
||||||
import { AsyncStatus, useAsyncCallback } from '../../hooks/useAsyncCallback';
|
import { AsyncStatus, useAsyncCallback } from '../../hooks/useAsyncCallback';
|
||||||
import { ErrorCode } from '../../cs-errorcode';
|
import { ErrorCode } from '../../cs-errorcode';
|
||||||
|
|
@ -14,6 +33,8 @@ import { createRoomEncryptionState } from '../../components/create-room';
|
||||||
import { useAlive } from '../../hooks/useAlive';
|
import { useAlive } from '../../hooks/useAlive';
|
||||||
import { getDirectRoomPath } from '../../pages/pathUtils';
|
import { getDirectRoomPath } from '../../pages/pathUtils';
|
||||||
|
|
||||||
|
const FALLBACK_SERVER = 'vojo.chat';
|
||||||
|
|
||||||
type CreateChatProps = {
|
type CreateChatProps = {
|
||||||
defaultUserId?: string;
|
defaultUserId?: string;
|
||||||
};
|
};
|
||||||
|
|
@ -23,6 +44,10 @@ export function CreateChat({ defaultUserId }: CreateChatProps) {
|
||||||
const alive = useAlive();
|
const alive = useAlive();
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
|
|
||||||
|
const userServer = getMxIdServer(mx.getSafeUserId()) ?? FALLBACK_SERVER;
|
||||||
|
const defaultUsername = defaultUserId ? getMxIdLocalPart(defaultUserId) : undefined;
|
||||||
|
const defaultServer = defaultUserId ? getMxIdServer(defaultUserId) : undefined;
|
||||||
|
|
||||||
const [encryption, setEncryption] = useState(false);
|
const [encryption, setEncryption] = useState(false);
|
||||||
const [invalidUserId, setInvalidUserId] = useState(false);
|
const [invalidUserId, setInvalidUserId] = useState(false);
|
||||||
|
|
||||||
|
|
@ -57,28 +82,42 @@ export function CreateChat({ defaultUserId }: CreateChatProps) {
|
||||||
setInvalidUserId(false);
|
setInvalidUserId(false);
|
||||||
|
|
||||||
const target = evt.target as HTMLFormElement | undefined;
|
const target = evt.target as HTMLFormElement | undefined;
|
||||||
const userIdInput = target?.userIdInput as HTMLInputElement | undefined;
|
const usernameInput = target?.usernameInput as HTMLInputElement | undefined;
|
||||||
const userId = userIdInput?.value.trim();
|
const serverInput = target?.serverInput as HTMLInputElement | undefined;
|
||||||
|
let rawUsername = usernameInput?.value.trim() ?? '';
|
||||||
|
const server = serverInput?.value.trim() || userServer;
|
||||||
|
|
||||||
if (!userIdInput || !userId) return;
|
if (!usernameInput || !rawUsername) return;
|
||||||
if (!isUserId(userId)) {
|
|
||||||
|
// If user pasted a full MXID like @alice:matrix.org, split it into fields
|
||||||
|
if (isUserId(rawUsername)) {
|
||||||
|
const parsedLocal = getMxIdLocalPart(rawUsername);
|
||||||
|
const parsedServer = getMxIdServer(rawUsername);
|
||||||
|
if (parsedLocal && parsedServer) {
|
||||||
|
rawUsername = parsedLocal;
|
||||||
|
usernameInput.value = parsedLocal;
|
||||||
|
if (serverInput) serverInput.value = parsedServer;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const username = rawUsername.replace(/^@/, '');
|
||||||
|
if (!username || /[@:\s]/.test(username) || !isServerName(server)) {
|
||||||
setInvalidUserId(true);
|
setInvalidUserId(true);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Route to an existing DM (encrypted or not) before creating a new room;
|
const userId = `@${username}:${server}`;
|
||||||
// otherwise every submit from the card's "Message" button creates a new
|
|
||||||
// duplicate room even when a perfectly fine DM already exists.
|
|
||||||
const existing = getDMRoomFor(mx, userId);
|
const existing = getDMRoomFor(mx, userId);
|
||||||
if (existing) {
|
if (existing) {
|
||||||
userIdInput.value = '';
|
usernameInput.value = '';
|
||||||
navigate(getDirectRoomPath(existing.roomId));
|
navigate(getDirectRoomPath(existing.roomId));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
create(userId, encryption).then((roomId) => {
|
create(userId, encryption).then((roomId) => {
|
||||||
if (alive()) {
|
if (alive()) {
|
||||||
userIdInput.value = '';
|
usernameInput.value = '';
|
||||||
navigate(getDirectRoomPath(roomId));
|
navigate(getDirectRoomPath(roomId));
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
@ -87,11 +126,13 @@ export function CreateChat({ defaultUserId }: CreateChatProps) {
|
||||||
return (
|
return (
|
||||||
<Box as="form" onSubmit={handleSubmit} grow="Yes" direction="Column" gap="500">
|
<Box as="form" onSubmit={handleSubmit} grow="Yes" direction="Column" gap="500">
|
||||||
<Box direction="Column" gap="100">
|
<Box direction="Column" gap="100">
|
||||||
<Text size="L400">{t('Direct.user_id')}</Text>
|
<Box direction="Row" wrap="Wrap" gap="200">
|
||||||
|
<Box grow="Yes" style={{ minWidth: toRem(120) }} direction="Column" gap="100">
|
||||||
|
<Text size="L400">{t('Direct.username')}</Text>
|
||||||
<Input
|
<Input
|
||||||
defaultValue={defaultUserId}
|
defaultValue={defaultUsername}
|
||||||
placeholder={t('Direct.user_id_placeholder')}
|
placeholder={t('Direct.username_placeholder')}
|
||||||
name="userIdInput"
|
name="usernameInput"
|
||||||
variant="SurfaceVariant"
|
variant="SurfaceVariant"
|
||||||
size="500"
|
size="500"
|
||||||
radii="400"
|
radii="400"
|
||||||
|
|
@ -100,6 +141,21 @@ export function CreateChat({ defaultUserId }: CreateChatProps) {
|
||||||
autoComplete="off"
|
autoComplete="off"
|
||||||
disabled={disabled}
|
disabled={disabled}
|
||||||
/>
|
/>
|
||||||
|
</Box>
|
||||||
|
<Box shrink="No" style={{ minWidth: toRem(150) }} direction="Column" gap="100">
|
||||||
|
<Text size="L400">{t('Direct.server')}</Text>
|
||||||
|
<Input
|
||||||
|
defaultValue={defaultServer && defaultServer !== userServer ? defaultServer : undefined}
|
||||||
|
placeholder={userServer}
|
||||||
|
name="serverInput"
|
||||||
|
variant="SurfaceVariant"
|
||||||
|
size="500"
|
||||||
|
radii="400"
|
||||||
|
autoComplete="off"
|
||||||
|
disabled={disabled}
|
||||||
|
/>
|
||||||
|
</Box>
|
||||||
|
</Box>
|
||||||
{invalidUserId && (
|
{invalidUserId && (
|
||||||
<Box style={{ color: color.Critical.Main }} alignItems="Center" gap="100">
|
<Box style={{ color: color.Critical.Main }} alignItems="Center" gap="100">
|
||||||
<Icon src={Icons.Warning} filled size="50" />
|
<Icon src={Icons.Warning} filled size="50" />
|
||||||
|
|
|
||||||
|
|
@ -61,9 +61,7 @@ export function DirectCreate() {
|
||||||
<PageHeroSection>
|
<PageHeroSection>
|
||||||
<Box direction="Column" gap="700">
|
<Box direction="Column" gap="700">
|
||||||
<PageHero
|
<PageHero
|
||||||
icon={<Icon size="600" src={Icons.Mention} />}
|
|
||||||
title={t('Direct.create_chat')}
|
title={t('Direct.create_chat')}
|
||||||
subTitle={t('Direct.create_chat_subtitle')}
|
|
||||||
/>
|
/>
|
||||||
<CreateChat defaultUserId={userId} />
|
<CreateChat defaultUserId={userId} />
|
||||||
</Box>
|
</Box>
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue