mirror of
https://github.com/array-in-a-matrix/xinny.git
synced 2024-05-21 05:37:23 -04:00
remove twemoji
This commit is contained in:
parent
d6c8036a81
commit
5c5acbd763
|
@ -2,7 +2,6 @@ import React, { useState, useEffect, useRef } from 'react';
|
|||
import PropTypes from 'prop-types';
|
||||
import './CreateRoom.scss';
|
||||
|
||||
import { twemojify } from '../../../util/twemojify';
|
||||
import initMatrix from '../../../client/initMatrix';
|
||||
import cons from '../../../client/state/cons';
|
||||
import navigation from '../../../client/state/navigation';
|
||||
|
@ -92,7 +91,7 @@ function CreateRoomContent({ isSpace, parentId, onRequestClose }) {
|
|||
topic,
|
||||
joinRule,
|
||||
alias: roomAlias,
|
||||
isEncrypted: (isSpace || joinRule === 'public') ? false : isEncrypted,
|
||||
isEncrypted: isSpace || joinRule === 'public' ? false : isEncrypted,
|
||||
powerLevel,
|
||||
isSpace,
|
||||
parentId,
|
||||
|
@ -131,36 +130,35 @@ function CreateRoomContent({ isSpace, parentId, onRequestClose }) {
|
|||
|
||||
const joinRules = ['invite', 'restricted', 'public'];
|
||||
const joinRuleShortText = ['Private', 'Restricted', 'Public'];
|
||||
const joinRuleText = ['Private (invite only)', 'Restricted (space member can join)', 'Public (anyone can join)'];
|
||||
const joinRuleText = [
|
||||
'Private (invite only)',
|
||||
'Restricted (space member can join)',
|
||||
'Public (anyone can join)',
|
||||
];
|
||||
const jrRoomIC = [HashLockIC, HashIC, HashGlobeIC];
|
||||
const jrSpaceIC = [SpaceLockIC, SpaceIC, SpaceGlobeIC];
|
||||
const handleJoinRule = (evt) => {
|
||||
openReusableContextMenu(
|
||||
'bottom',
|
||||
getEventCords(evt, '.btn-surface'),
|
||||
(closeMenu) => (
|
||||
<>
|
||||
<MenuHeader>Visibility (who can join)</MenuHeader>
|
||||
{
|
||||
joinRules.map((rule) => (
|
||||
<MenuItem
|
||||
key={rule}
|
||||
variant={rule === joinRule ? 'positive' : 'surface'}
|
||||
iconSrc={
|
||||
isSpace
|
||||
? jrSpaceIC[joinRules.indexOf(rule)]
|
||||
: jrRoomIC[joinRules.indexOf(rule)]
|
||||
}
|
||||
onClick={() => { closeMenu(); setJoinRule(rule); }}
|
||||
disabled={!parentId && rule === 'restricted'}
|
||||
>
|
||||
{ joinRuleText[joinRules.indexOf(rule)] }
|
||||
</MenuItem>
|
||||
))
|
||||
}
|
||||
</>
|
||||
),
|
||||
);
|
||||
openReusableContextMenu('bottom', getEventCords(evt, '.btn-surface'), (closeMenu) => (
|
||||
<>
|
||||
<MenuHeader>Visibility (who can join)</MenuHeader>
|
||||
{joinRules.map((rule) => (
|
||||
<MenuItem
|
||||
key={rule}
|
||||
variant={rule === joinRule ? 'positive' : 'surface'}
|
||||
iconSrc={
|
||||
isSpace ? jrSpaceIC[joinRules.indexOf(rule)] : jrRoomIC[joinRules.indexOf(rule)]
|
||||
}
|
||||
onClick={() => {
|
||||
closeMenu();
|
||||
setJoinRule(rule);
|
||||
}}
|
||||
disabled={!parentId && rule === 'restricted'}
|
||||
>
|
||||
{joinRuleText[joinRules.indexOf(rule)]}
|
||||
</MenuItem>
|
||||
))}
|
||||
</>
|
||||
));
|
||||
};
|
||||
|
||||
return (
|
||||
|
@ -168,50 +166,64 @@ function CreateRoomContent({ isSpace, parentId, onRequestClose }) {
|
|||
<form className="create-room__form" onSubmit={handleSubmit}>
|
||||
<SettingTile
|
||||
title="Visibility"
|
||||
options={(
|
||||
options={
|
||||
<Button onClick={handleJoinRule} iconSrc={ChevronBottomIC}>
|
||||
{joinRuleShortText[joinRules.indexOf(joinRule)]}
|
||||
</Button>
|
||||
)}
|
||||
content={<Text variant="b3">{`Select who can join this ${isSpace ? 'space' : 'room'}.`}</Text>}
|
||||
}
|
||||
content={
|
||||
<Text variant="b3">{`Select who can join this ${isSpace ? 'space' : 'room'}.`}</Text>
|
||||
}
|
||||
/>
|
||||
{joinRule === 'public' && (
|
||||
<div>
|
||||
<Text className="create-room__address__label" variant="b2">{isSpace ? 'Space address' : 'Room address'}</Text>
|
||||
<Text className="create-room__address__label" variant="b2">
|
||||
{isSpace ? 'Space address' : 'Room address'}
|
||||
</Text>
|
||||
<div className="create-room__address">
|
||||
<Text variant="b1">#</Text>
|
||||
<Input
|
||||
value={addressValue}
|
||||
onChange={validateAddress}
|
||||
state={(isValidAddress === false) ? 'error' : 'normal'}
|
||||
state={isValidAddress === false ? 'error' : 'normal'}
|
||||
forwardRef={addressRef}
|
||||
placeholder="my_address"
|
||||
required
|
||||
/>
|
||||
<Text variant="b1">{`:${userHs}`}</Text>
|
||||
</div>
|
||||
{isValidAddress === false && <Text className="create-room__address__tip" variant="b3"><span style={{ color: 'var(--bg-danger)' }}>{`#${addressValue}:${userHs} is already in use`}</span></Text>}
|
||||
{isValidAddress === false && (
|
||||
<Text className="create-room__address__tip" variant="b3">
|
||||
<span
|
||||
style={{ color: 'var(--bg-danger)' }}
|
||||
>{`#${addressValue}:${userHs} is already in use`}</span>
|
||||
</Text>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
{!isSpace && joinRule !== 'public' && (
|
||||
<SettingTile
|
||||
title="Enable end-to-end encryption"
|
||||
options={<Toggle isActive={isEncrypted} onToggle={setIsEncrypted} />}
|
||||
content={<Text variant="b3">You can’t disable this later. Bridges & most bots won’t work yet.</Text>}
|
||||
content={
|
||||
<Text variant="b3">
|
||||
You can’t disable this later. Bridges & most bots won’t work yet.
|
||||
</Text>
|
||||
}
|
||||
/>
|
||||
)}
|
||||
<SettingTile
|
||||
title="Select your role"
|
||||
options={(
|
||||
options={
|
||||
<SegmentControl
|
||||
selected={roleIndex}
|
||||
segments={[{ text: 'Admin' }, { text: 'Founder' }]}
|
||||
onSelect={setRoleIndex}
|
||||
/>
|
||||
)}
|
||||
content={(
|
||||
}
|
||||
content={
|
||||
<Text variant="b3">Selecting Admin sets 100 power level whereas Founder sets 101.</Text>
|
||||
)}
|
||||
}
|
||||
/>
|
||||
<Input name="topic" minHeight={174} resizable label="Topic (optional)" />
|
||||
<div className="create-room__name-wrapper">
|
||||
|
@ -231,7 +243,11 @@ function CreateRoomContent({ isSpace, parentId, onRequestClose }) {
|
|||
<Text>{`Creating ${isSpace ? 'space' : 'room'}...`}</Text>
|
||||
</div>
|
||||
)}
|
||||
{typeof creatingError === 'string' && <Text className="create-room__error" variant="b3">{creatingError}</Text>}
|
||||
{typeof creatingError === 'string' && (
|
||||
<Text className="create-room__error" variant="b3">
|
||||
{creatingError}
|
||||
</Text>
|
||||
)}
|
||||
</form>
|
||||
</div>
|
||||
);
|
||||
|
@ -275,27 +291,22 @@ function CreateRoom() {
|
|||
return (
|
||||
<Dialog
|
||||
isOpen={create !== null}
|
||||
title={(
|
||||
title={
|
||||
<Text variant="s1" weight="medium" primary>
|
||||
{parentId ? twemojify(room.name) : 'Home'}
|
||||
{parentId ? room.name : 'Home'}
|
||||
<span style={{ color: 'var(--tc-surface-low)' }}>
|
||||
{` — create ${isSpace ? 'space' : 'room'}`}
|
||||
</span>
|
||||
</Text>
|
||||
)}
|
||||
}
|
||||
contentOptions={<IconButton src={CrossIC} onClick={onRequestClose} tooltip="Close" />}
|
||||
onRequestClose={onRequestClose}
|
||||
>
|
||||
{
|
||||
create
|
||||
? (
|
||||
<CreateRoomContent
|
||||
isSpace={isSpace}
|
||||
parentId={parentId}
|
||||
onRequestClose={onRequestClose}
|
||||
/>
|
||||
) : <div />
|
||||
}
|
||||
{create ? (
|
||||
<CreateRoomContent isSpace={isSpace} parentId={parentId} onRequestClose={onRequestClose} />
|
||||
) : (
|
||||
<div />
|
||||
)}
|
||||
</Dialog>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -55,7 +55,6 @@ const EmojiGroup = React.memo(({ name, groupEmojis }) => {
|
|||
unicode: emoji.unicode,
|
||||
shortcodes: emoji.shortcodes?.toString(),
|
||||
hexcode: emoji.hexcode,
|
||||
loading: 'lazy',
|
||||
}),
|
||||
base: TWEMOJI_BASE_URL,
|
||||
})
|
||||
|
@ -340,7 +339,7 @@ function EmojiBoard({ onSelect, searchRef }) {
|
|||
</ScrollView>
|
||||
</div>
|
||||
<div ref={emojiInfo} className="emoji-board__content__info">
|
||||
<div>{parse(twemoji.parse('🙂', { base: TWEMOJI_BASE_URL }))}</div>
|
||||
<div>{parse('🙂')}</div>
|
||||
<Text>:slight_smile:</Text>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -2,7 +2,6 @@
|
|||
import React, { useState, useEffect } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import './EmojiVerification.scss';
|
||||
import { twemojify } from '../../../util/twemojify';
|
||||
|
||||
import initMatrix from '../../../client/initMatrix';
|
||||
import cons from '../../../client/state/cons';
|
||||
|
@ -30,8 +29,9 @@ function EmojiVerificationContent({ data, requestClose }) {
|
|||
|
||||
const beginVerification = async () => {
|
||||
if (
|
||||
isCrossVerified(mx.deviceId)
|
||||
&& (mx.getCrossSigningId() === null || await mx.crypto.crossSigningInfo.isStoredInKeyCache('self_signing') === false)
|
||||
isCrossVerified(mx.deviceId) &&
|
||||
(mx.getCrossSigningId() === null ||
|
||||
(await mx.crypto.crossSigningInfo.isStoredInKeyCache('self_signing')) === false)
|
||||
) {
|
||||
if (!hasPrivateKey(getDefaultSSKey())) {
|
||||
const keyData = await accessSecretStorage('Emoji verification');
|
||||
|
@ -106,16 +106,20 @@ function EmojiVerificationContent({ data, requestClose }) {
|
|||
{sas.sas.emoji.map((emoji, i) => (
|
||||
// eslint-disable-next-line react/no-array-index-key
|
||||
<div className="emoji-verification__emoji-block" key={`${emoji[1]}-${i}`}>
|
||||
<Text variant="h1">{twemojify(emoji[0])}</Text>
|
||||
<Text variant="h1">{emoji[0]}</Text>
|
||||
<Text>{emoji[1]}</Text>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
<div className="emoji-verification__buttons">
|
||||
{process ? renderWait() : (
|
||||
{process ? (
|
||||
renderWait()
|
||||
) : (
|
||||
<>
|
||||
<Button variant="primary" onClick={sasConfirm}>They match</Button>
|
||||
<Button onClick={sasMismatch}>{'They don\'t match'}</Button>
|
||||
<Button variant="primary" onClick={sasConfirm}>
|
||||
They match
|
||||
</Button>
|
||||
<Button onClick={sasMismatch}>{"They don't match"}</Button>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
|
@ -127,9 +131,7 @@ function EmojiVerificationContent({ data, requestClose }) {
|
|||
return (
|
||||
<div className="emoji-verification__content">
|
||||
<Text>Please accept the request from other device.</Text>
|
||||
<div className="emoji-verification__buttons">
|
||||
{renderWait()}
|
||||
</div>
|
||||
<div className="emoji-verification__buttons">{renderWait()}</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
@ -138,11 +140,13 @@ function EmojiVerificationContent({ data, requestClose }) {
|
|||
<div className="emoji-verification__content">
|
||||
<Text>Click accept to start the verification process.</Text>
|
||||
<div className="emoji-verification__buttons">
|
||||
{
|
||||
process
|
||||
? renderWait()
|
||||
: <Button variant="primary" onClick={beginVerification}>Accept</Button>
|
||||
}
|
||||
{process ? (
|
||||
renderWait()
|
||||
) : (
|
||||
<Button variant="primary" onClick={beginVerification}>
|
||||
Accept
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
@ -180,19 +184,19 @@ function EmojiVerification() {
|
|||
<Dialog
|
||||
isOpen={data !== null}
|
||||
className="emoji-verification"
|
||||
title={(
|
||||
title={
|
||||
<Text variant="s1" weight="medium" primary>
|
||||
Emoji verification
|
||||
</Text>
|
||||
)}
|
||||
}
|
||||
contentOptions={<IconButton src={CrossIC} onClick={requestClose} tooltip="Close" />}
|
||||
onRequestClose={requestClose}
|
||||
>
|
||||
{
|
||||
data !== null
|
||||
? <EmojiVerificationContent data={data} requestClose={requestClose} />
|
||||
: <div />
|
||||
}
|
||||
{data !== null ? (
|
||||
<EmojiVerificationContent data={data} requestClose={requestClose} />
|
||||
) : (
|
||||
<div />
|
||||
)}
|
||||
</Dialog>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -2,8 +2,6 @@ import React, { useState, useEffect, useRef } from 'react';
|
|||
import PropTypes from 'prop-types';
|
||||
import './DrawerBreadcrumb.scss';
|
||||
|
||||
import { twemojify } from '../../../util/twemojify';
|
||||
|
||||
import initMatrix from '../../../client/initMatrix';
|
||||
import cons from '../../../client/state/cons';
|
||||
import { selectTab, selectSpace } from '../../../client/action/navigation';
|
||||
|
@ -49,8 +47,9 @@ function DrawerBreadcrumb({ spaceId }) {
|
|||
}, [spaceId]);
|
||||
|
||||
function getHomeNotiExcept(childId) {
|
||||
const orphans = roomList.getOrphans()
|
||||
.filter((id) => (id !== childId))
|
||||
const orphans = roomList
|
||||
.getOrphans()
|
||||
.filter((id) => id !== childId)
|
||||
.filter((id) => !accountData.spaceShortcut.has(id));
|
||||
|
||||
let noti = null;
|
||||
|
@ -94,36 +93,35 @@ function DrawerBreadcrumb({ spaceId }) {
|
|||
<div className="drawer-breadcrumb__wrapper">
|
||||
<ScrollView ref={scrollRef} horizontal vertical={false} invisible>
|
||||
<div className="drawer-breadcrumb">
|
||||
{
|
||||
spacePath.map((id, index) => {
|
||||
const noti = (id !== cons.tabs.HOME && index < spacePath.length)
|
||||
? getNotiExcept(id, (index === spacePath.length - 1) ? null : spacePath[index + 1])
|
||||
: getHomeNotiExcept((index === spacePath.length - 1) ? null : spacePath[index + 1]);
|
||||
{spacePath.map((id, index) => {
|
||||
const noti =
|
||||
id !== cons.tabs.HOME && index < spacePath.length
|
||||
? getNotiExcept(id, index === spacePath.length - 1 ? null : spacePath[index + 1])
|
||||
: getHomeNotiExcept(index === spacePath.length - 1 ? null : spacePath[index + 1]);
|
||||
|
||||
return (
|
||||
<React.Fragment
|
||||
key={id}
|
||||
return (
|
||||
<React.Fragment key={id}>
|
||||
{index !== 0 && <RawIcon size="extra-small" src={ChevronRightIC} />}
|
||||
<Button
|
||||
className={
|
||||
index === spacePath.length - 1 ? 'drawer-breadcrumb__btn--selected' : ''
|
||||
}
|
||||
onClick={() => {
|
||||
if (id === cons.tabs.HOME) selectTab(id);
|
||||
else selectSpace(id);
|
||||
}}
|
||||
>
|
||||
{ index !== 0 && <RawIcon size="extra-small" src={ChevronRightIC} />}
|
||||
<Button
|
||||
className={index === spacePath.length - 1 ? 'drawer-breadcrumb__btn--selected' : ''}
|
||||
onClick={() => {
|
||||
if (id === cons.tabs.HOME) selectTab(id);
|
||||
else selectSpace(id);
|
||||
}}
|
||||
>
|
||||
<Text variant="b2">{id === cons.tabs.HOME ? 'Home' : twemojify(mx.getRoom(id).name)}</Text>
|
||||
{ noti !== null && (
|
||||
<NotificationBadge
|
||||
alert={noti.highlight !== 0}
|
||||
content={noti.total > 0 ? abbreviateNumber(noti.total) : null}
|
||||
/>
|
||||
)}
|
||||
</Button>
|
||||
</React.Fragment>
|
||||
);
|
||||
})
|
||||
}
|
||||
<Text variant="b2">{id === cons.tabs.HOME ? 'Home' : mx.getRoom(id).name}</Text>
|
||||
{noti !== null && (
|
||||
<NotificationBadge
|
||||
alert={noti.highlight !== 0}
|
||||
content={noti.total > 0 ? abbreviateNumber(noti.total) : null}
|
||||
/>
|
||||
)}
|
||||
</Button>
|
||||
</React.Fragment>
|
||||
);
|
||||
})}
|
||||
<div style={{ width: 'var(--sp-extra-tight)', height: '100%' }} />
|
||||
</div>
|
||||
</ScrollView>
|
||||
|
|
|
@ -2,13 +2,16 @@ import React from 'react';
|
|||
import PropTypes from 'prop-types';
|
||||
import './DrawerHeader.scss';
|
||||
|
||||
import { twemojify } from '../../../util/twemojify';
|
||||
|
||||
import initMatrix from '../../../client/initMatrix';
|
||||
import cons from '../../../client/state/cons';
|
||||
import {
|
||||
openPublicRooms, openCreateRoom, openSpaceManage, openJoinAlias,
|
||||
openSpaceAddExisting, openInviteUser, openReusableContextMenu,
|
||||
openPublicRooms,
|
||||
openCreateRoom,
|
||||
openSpaceManage,
|
||||
openJoinAlias,
|
||||
openSpaceAddExisting,
|
||||
openInviteUser,
|
||||
openReusableContextMenu,
|
||||
} from '../../../client/action/navigation';
|
||||
import { getEventCords } from '../../../util/common';
|
||||
|
||||
|
@ -40,46 +43,64 @@ export function HomeSpaceOptions({ spaceId, afterOptionSelect }) {
|
|||
<MenuHeader>Add rooms or spaces</MenuHeader>
|
||||
<MenuItem
|
||||
iconSrc={SpacePlusIC}
|
||||
onClick={() => { afterOptionSelect(); openCreateRoom(true, spaceId); }}
|
||||
onClick={() => {
|
||||
afterOptionSelect();
|
||||
openCreateRoom(true, spaceId);
|
||||
}}
|
||||
disabled={!canManage}
|
||||
>
|
||||
Create new space
|
||||
</MenuItem>
|
||||
<MenuItem
|
||||
iconSrc={HashPlusIC}
|
||||
onClick={() => { afterOptionSelect(); openCreateRoom(false, spaceId); }}
|
||||
onClick={() => {
|
||||
afterOptionSelect();
|
||||
openCreateRoom(false, spaceId);
|
||||
}}
|
||||
disabled={!canManage}
|
||||
>
|
||||
Create new room
|
||||
</MenuItem>
|
||||
{ !spaceId && (
|
||||
{!spaceId && (
|
||||
<MenuItem
|
||||
iconSrc={HashGlobeIC}
|
||||
onClick={() => { afterOptionSelect(); openPublicRooms(); }}
|
||||
onClick={() => {
|
||||
afterOptionSelect();
|
||||
openPublicRooms();
|
||||
}}
|
||||
>
|
||||
Explore public rooms
|
||||
</MenuItem>
|
||||
)}
|
||||
{ !spaceId && (
|
||||
{!spaceId && (
|
||||
<MenuItem
|
||||
iconSrc={PlusIC}
|
||||
onClick={() => { afterOptionSelect(); openJoinAlias(); }}
|
||||
onClick={() => {
|
||||
afterOptionSelect();
|
||||
openJoinAlias();
|
||||
}}
|
||||
>
|
||||
Join with address
|
||||
</MenuItem>
|
||||
)}
|
||||
{ spaceId && (
|
||||
{spaceId && (
|
||||
<MenuItem
|
||||
iconSrc={PlusIC}
|
||||
onClick={() => { afterOptionSelect(); openSpaceAddExisting(spaceId); }}
|
||||
onClick={() => {
|
||||
afterOptionSelect();
|
||||
openSpaceAddExisting(spaceId);
|
||||
}}
|
||||
disabled={!canManage}
|
||||
>
|
||||
Add existing
|
||||
</MenuItem>
|
||||
)}
|
||||
{ spaceId && (
|
||||
{spaceId && (
|
||||
<MenuItem
|
||||
onClick={() => { afterOptionSelect(); openSpaceManage(spaceId); }}
|
||||
onClick={() => {
|
||||
afterOptionSelect();
|
||||
openSpaceManage(spaceId);
|
||||
}}
|
||||
iconSrc={HashSearchIC}
|
||||
>
|
||||
Manage rooms
|
||||
|
@ -102,24 +123,20 @@ function DrawerHeader({ selectedTab, spaceId }) {
|
|||
|
||||
const isDMTab = selectedTab === cons.tabs.DIRECTS;
|
||||
const room = mx.getRoom(spaceId);
|
||||
const spaceName = isDMTab ? null : (room?.name || null);
|
||||
const spaceName = isDMTab ? null : room?.name || null;
|
||||
|
||||
const openSpaceOptions = (e) => {
|
||||
e.preventDefault();
|
||||
openReusableContextMenu(
|
||||
'bottom',
|
||||
getEventCords(e, '.header'),
|
||||
(closeMenu) => <SpaceOptions roomId={spaceId} afterOptionSelect={closeMenu} />,
|
||||
);
|
||||
openReusableContextMenu('bottom', getEventCords(e, '.header'), (closeMenu) => (
|
||||
<SpaceOptions roomId={spaceId} afterOptionSelect={closeMenu} />
|
||||
));
|
||||
};
|
||||
|
||||
const openHomeSpaceOptions = (e) => {
|
||||
e.preventDefault();
|
||||
openReusableContextMenu(
|
||||
'right',
|
||||
getEventCords(e, '.ic-btn'),
|
||||
(closeMenu) => <HomeSpaceOptions spaceId={spaceId} afterOptionSelect={closeMenu} />,
|
||||
);
|
||||
openReusableContextMenu('right', getEventCords(e, '.ic-btn'), (closeMenu) => (
|
||||
<HomeSpaceOptions spaceId={spaceId} afterOptionSelect={closeMenu} />
|
||||
));
|
||||
};
|
||||
|
||||
return (
|
||||
|
@ -132,18 +149,31 @@ function DrawerHeader({ selectedTab, spaceId }) {
|
|||
onMouseUp={(e) => blurOnBubbling(e, '.drawer-header__btn')}
|
||||
>
|
||||
<TitleWrapper>
|
||||
<Text variant="s1" weight="medium" primary>{twemojify(spaceName)}</Text>
|
||||
<Text variant="s1" weight="medium" primary>
|
||||
{spaceName}
|
||||
</Text>
|
||||
</TitleWrapper>
|
||||
<RawIcon size="small" src={ChevronBottomIC} />
|
||||
</button>
|
||||
) : (
|
||||
<TitleWrapper>
|
||||
<Text variant="s1" weight="medium" primary>{tabName}</Text>
|
||||
<Text variant="s1" weight="medium" primary>
|
||||
{tabName}
|
||||
</Text>
|
||||
</TitleWrapper>
|
||||
)}
|
||||
|
||||
{ isDMTab && <IconButton onClick={() => openInviteUser()} tooltip="Start DM" src={PlusIC} size="small" /> }
|
||||
{ !isDMTab && <IconButton onClick={openHomeSpaceOptions} tooltip="Add rooms/spaces" src={PlusIC} size="small" /> }
|
||||
{isDMTab && (
|
||||
<IconButton onClick={() => openInviteUser()} tooltip="Start DM" src={PlusIC} size="small" />
|
||||
)}
|
||||
{!isDMTab && (
|
||||
<IconButton
|
||||
onClick={openHomeSpaceOptions}
|
||||
tooltip="Add rooms/spaces"
|
||||
src={PlusIC}
|
||||
size="small"
|
||||
/>
|
||||
)}
|
||||
</Header>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
import React, { useState, useEffect, useRef } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { twemojify } from '../../../util/twemojify';
|
||||
|
||||
import initMatrix from '../../../client/initMatrix';
|
||||
import colorMXID from '../../../util/colorMXID';
|
||||
|
@ -22,7 +21,9 @@ function ProfileEditor({ userId }) {
|
|||
const user = mx.getUser(mx.getUserId());
|
||||
|
||||
const displayNameRef = useRef(null);
|
||||
const [avatarSrc, setAvatarSrc] = useState(user.avatarUrl ? mx.mxcUrlToHttp(user.avatarUrl, 80, 80, 'crop') : null);
|
||||
const [avatarSrc, setAvatarSrc] = useState(
|
||||
user.avatarUrl ? mx.mxcUrlToHttp(user.avatarUrl, 80, 80, 'crop') : null
|
||||
);
|
||||
const [username, setUsername] = useState(user.displayName);
|
||||
const [disabled, setDisabled] = useState(true);
|
||||
|
||||
|
@ -44,7 +45,7 @@ function ProfileEditor({ userId }) {
|
|||
'Remove avatar',
|
||||
'Are you sure that you want to remove avatar?',
|
||||
'Remove',
|
||||
'caution',
|
||||
'caution'
|
||||
);
|
||||
if (isConfirmed) {
|
||||
mx.setAvatarUrl('');
|
||||
|
@ -79,7 +80,10 @@ function ProfileEditor({ userId }) {
|
|||
<form
|
||||
className="profile-editor__form"
|
||||
style={{ marginBottom: avatarSrc ? '24px' : '0' }}
|
||||
onSubmit={(e) => { e.preventDefault(); saveDisplayName(); }}
|
||||
onSubmit={(e) => {
|
||||
e.preventDefault();
|
||||
saveDisplayName();
|
||||
}}
|
||||
>
|
||||
<Input
|
||||
label={`Display name of ${mx.getUserId()}`}
|
||||
|
@ -87,7 +91,9 @@ function ProfileEditor({ userId }) {
|
|||
value={mx.getUser(mx.getUserId()).displayName}
|
||||
forwardRef={displayNameRef}
|
||||
/>
|
||||
<Button variant="primary" type="submit" disabled={disabled}>Save</Button>
|
||||
<Button variant="primary" type="submit" disabled={disabled}>
|
||||
Save
|
||||
</Button>
|
||||
<Button onClick={cancelDisplayNameChanges}>Cancel</Button>
|
||||
</form>
|
||||
);
|
||||
|
@ -95,7 +101,9 @@ function ProfileEditor({ userId }) {
|
|||
const renderInfo = () => (
|
||||
<div className="profile-editor__info" style={{ marginBottom: avatarSrc ? '24px' : '0' }}>
|
||||
<div>
|
||||
<Text variant="h2" primary weight="medium">{twemojify(username) ?? userId}</Text>
|
||||
<Text variant="h2" primary weight="medium">
|
||||
{username ?? userId}
|
||||
</Text>
|
||||
<IconButton
|
||||
src={PencilIC}
|
||||
size="extra-small"
|
||||
|
@ -116,9 +124,7 @@ function ProfileEditor({ userId }) {
|
|||
onUpload={handleAvatarUpload}
|
||||
onRequestRemove={() => handleAvatarUpload(null)}
|
||||
/>
|
||||
{
|
||||
isEditing ? renderForm() : renderInfo()
|
||||
}
|
||||
{isEditing ? renderForm() : renderInfo()}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -2,8 +2,6 @@ import React, { useState, useEffect, useRef } from 'react';
|
|||
import PropTypes from 'prop-types';
|
||||
import './ProfileViewer.scss';
|
||||
|
||||
import { twemojify } from '../../../util/twemojify';
|
||||
|
||||
import initMatrix from '../../../client/initMatrix';
|
||||
import cons from '../../../client/state/cons';
|
||||
import navigation from '../../../client/state/navigation';
|
||||
|
@ -11,7 +9,11 @@ import { selectRoom, openReusableContextMenu } from '../../../client/action/navi
|
|||
import * as roomActions from '../../../client/action/room';
|
||||
|
||||
import {
|
||||
getUsername, getUsernameOfRoomMember, getPowerLabel, hasDMWith, hasDevices,
|
||||
getUsername,
|
||||
getUsernameOfRoomMember,
|
||||
getPowerLabel,
|
||||
hasDMWith,
|
||||
hasDevices,
|
||||
} from '../../../util/matrixUtil';
|
||||
import { getEventCords } from '../../../util/common';
|
||||
import colorMXID from '../../../util/colorMXID';
|
||||
|
@ -34,25 +36,21 @@ import CrossIC from '../../../../public/res/ic/outlined/cross.svg';
|
|||
import { useForceUpdate } from '../../hooks/useForceUpdate';
|
||||
import { confirmDialog } from '../../molecules/confirm-dialog/ConfirmDialog';
|
||||
|
||||
function ModerationTools({
|
||||
roomId, userId,
|
||||
}) {
|
||||
function ModerationTools({ roomId, userId }) {
|
||||
const mx = initMatrix.matrixClient;
|
||||
const room = mx.getRoom(roomId);
|
||||
const roomMember = room.getMember(userId);
|
||||
|
||||
const myPowerLevel = room.getMember(mx.getUserId())?.powerLevel || 0;
|
||||
const powerLevel = roomMember?.powerLevel || 0;
|
||||
const canIKick = (
|
||||
roomMember?.membership === 'join'
|
||||
&& room.currentState.hasSufficientPowerLevelFor('kick', myPowerLevel)
|
||||
&& powerLevel < myPowerLevel
|
||||
);
|
||||
const canIBan = (
|
||||
['join', 'leave'].includes(roomMember?.membership)
|
||||
&& room.currentState.hasSufficientPowerLevelFor('ban', myPowerLevel)
|
||||
&& powerLevel < myPowerLevel
|
||||
);
|
||||
const canIKick =
|
||||
roomMember?.membership === 'join' &&
|
||||
room.currentState.hasSufficientPowerLevelFor('kick', myPowerLevel) &&
|
||||
powerLevel < myPowerLevel;
|
||||
const canIBan =
|
||||
['join', 'leave'].includes(roomMember?.membership) &&
|
||||
room.currentState.hasSufficientPowerLevelFor('ban', myPowerLevel) &&
|
||||
powerLevel < myPowerLevel;
|
||||
|
||||
const handleKick = (e) => {
|
||||
e.preventDefault();
|
||||
|
@ -120,13 +118,14 @@ function SessionInfo({ userId }) {
|
|||
<div className="session-info__chips">
|
||||
{devices === null && <Text variant="b2">Loading sessions...</Text>}
|
||||
{devices?.length === 0 && <Text variant="b2">No session found.</Text>}
|
||||
{devices !== null && (devices.map((device) => (
|
||||
<Chip
|
||||
key={device.deviceId}
|
||||
iconSrc={ShieldEmptyIC}
|
||||
text={device.getDisplayName() || device.deviceId}
|
||||
/>
|
||||
)))}
|
||||
{devices !== null &&
|
||||
devices.map((device) => (
|
||||
<Chip
|
||||
key={device.deviceId}
|
||||
iconSrc={ShieldEmptyIC}
|
||||
text={device.getDisplayName() || device.deviceId}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
@ -137,7 +136,9 @@ function SessionInfo({ userId }) {
|
|||
onClick={() => setIsVisible(!isVisible)}
|
||||
iconSrc={isVisible ? ChevronBottomIC : ChevronRightIC}
|
||||
>
|
||||
<Text variant="b2">{`View ${devices?.length > 0 ? `${devices.length} ` : ''}sessions`}</Text>
|
||||
<Text variant="b2">{`View ${
|
||||
devices?.length > 0 ? `${devices.length} ` : ''
|
||||
}sessions`}</Text>
|
||||
</MenuItem>
|
||||
{renderSessionChips()}
|
||||
</div>
|
||||
|
@ -164,7 +165,8 @@ function ProfileFooter({ roomId, userId, onRequestClose }) {
|
|||
|
||||
const myPowerlevel = room.getMember(mx.getUserId())?.powerLevel || 0;
|
||||
const userPL = room.getMember(userId)?.powerLevel || 0;
|
||||
const canIKick = room.currentState.hasSufficientPowerLevelFor('kick', myPowerlevel) && userPL < myPowerlevel;
|
||||
const canIKick =
|
||||
room.currentState.hasSufficientPowerLevelFor('kick', myPowerlevel) && userPL < myPowerlevel;
|
||||
|
||||
const isBanned = member?.membership === 'ban';
|
||||
|
||||
|
@ -246,31 +248,19 @@ function ProfileFooter({ roomId, userId, onRequestClose }) {
|
|||
|
||||
return (
|
||||
<div className="profile-viewer__buttons">
|
||||
<Button
|
||||
variant="primary"
|
||||
onClick={openDM}
|
||||
disabled={isCreatingDM}
|
||||
>
|
||||
<Button variant="primary" onClick={openDM} disabled={isCreatingDM}>
|
||||
{isCreatingDM ? 'Creating room...' : 'Message'}
|
||||
</Button>
|
||||
{ isBanned && canIKick && (
|
||||
<Button
|
||||
variant="positive"
|
||||
onClick={() => roomActions.unban(roomId, userId)}
|
||||
>
|
||||
{isBanned && canIKick && (
|
||||
<Button variant="positive" onClick={() => roomActions.unban(roomId, userId)}>
|
||||
Unban
|
||||
</Button>
|
||||
)}
|
||||
{ (isInvited ? canIKick : room.canInvite(mx.getUserId())) && isInvitable && (
|
||||
<Button
|
||||
onClick={toggleInvite}
|
||||
disabled={isInviting}
|
||||
>
|
||||
{
|
||||
isInvited
|
||||
? `${isInviting ? 'Disinviting...' : 'Disinvite'}`
|
||||
: `${isInviting ? 'Inviting...' : 'Invite'}`
|
||||
}
|
||||
{(isInvited ? canIKick : room.canInvite(mx.getUserId())) && isInvitable && (
|
||||
<Button onClick={toggleInvite} disabled={isInviting}>
|
||||
{isInvited
|
||||
? `${isInviting ? 'Disinviting...' : 'Disinvite'}`
|
||||
: `${isInviting ? 'Inviting...' : 'Invite'}`}
|
||||
</Button>
|
||||
)}
|
||||
<Button
|
||||
|
@ -278,11 +268,9 @@ function ProfileFooter({ roomId, userId, onRequestClose }) {
|
|||
onClick={toggleIgnore}
|
||||
disabled={isIgnoring}
|
||||
>
|
||||
{
|
||||
isUserIgnored
|
||||
? `${isIgnoring ? 'Unignoring...' : 'Unignore'}`
|
||||
: `${isIgnoring ? 'Ignoring...' : 'Ignore'}`
|
||||
}
|
||||
{isUserIgnored
|
||||
? `${isIgnoring ? 'Unignoring...' : 'Unignore'}`
|
||||
: `${isIgnoring ? 'Ignoring...' : 'Ignore'}`}
|
||||
</Button>
|
||||
</div>
|
||||
);
|
||||
|
@ -326,8 +314,8 @@ function useRerenderOnProfileChange(roomId, userId) {
|
|||
useEffect(() => {
|
||||
const handleProfileChange = (mEvent, member) => {
|
||||
if (
|
||||
mEvent.getRoomId() === roomId
|
||||
&& (member.userId === userId || member.userId === mx.getUserId())
|
||||
mEvent.getRoomId() === roomId &&
|
||||
(member.userId === userId || member.userId === mx.getUserId())
|
||||
) {
|
||||
forceUpdate();
|
||||
}
|
||||
|
@ -352,20 +340,22 @@ function ProfileViewer() {
|
|||
const roomMember = room.getMember(userId);
|
||||
const username = roomMember ? getUsernameOfRoomMember(roomMember) : getUsername(userId);
|
||||
const avatarMxc = roomMember?.getMxcAvatarUrl?.() || mx.getUser(userId)?.avatarUrl;
|
||||
const avatarUrl = (avatarMxc && avatarMxc !== 'null') ? mx.mxcUrlToHttp(avatarMxc, 80, 80, 'crop') : null;
|
||||
const avatarUrl =
|
||||
avatarMxc && avatarMxc !== 'null' ? mx.mxcUrlToHttp(avatarMxc, 80, 80, 'crop') : null;
|
||||
|
||||
const powerLevel = roomMember?.powerLevel || 0;
|
||||
const myPowerLevel = room.getMember(mx.getUserId())?.powerLevel || 0;
|
||||
|
||||
const canChangeRole = (
|
||||
room.currentState.maySendEvent('m.room.power_levels', mx.getUserId())
|
||||
&& (powerLevel < myPowerLevel || userId === mx.getUserId())
|
||||
);
|
||||
const canChangeRole =
|
||||
room.currentState.maySendEvent('m.room.power_levels', mx.getUserId()) &&
|
||||
(powerLevel < myPowerLevel || userId === mx.getUserId());
|
||||
|
||||
const handleChangePowerLevel = async (newPowerLevel) => {
|
||||
if (newPowerLevel === powerLevel) return;
|
||||
const SHARED_POWER_MSG = 'You will not be able to undo this change as you are promoting the user to have the same power level as yourself. Are you sure?';
|
||||
const DEMOTING_MYSELF_MSG = 'You will not be able to undo this change as you are demoting yourself. Are you sure?';
|
||||
const SHARED_POWER_MSG =
|
||||
'You will not be able to undo this change as you are promoting the user to have the same power level as yourself. Are you sure?';
|
||||
const DEMOTING_MYSELF_MSG =
|
||||
'You will not be able to undo this change as you are demoting yourself. Are you sure?';
|
||||
|
||||
const isSharedPower = newPowerLevel === myPowerLevel;
|
||||
const isDemotingMyself = userId === mx.getUserId();
|
||||
|
@ -374,7 +364,7 @@ function ProfileViewer() {
|
|||
'Change power level',
|
||||
isSharedPower ? SHARED_POWER_MSG : DEMOTING_MYSELF_MSG,
|
||||
'Change',
|
||||
'caution',
|
||||
'caution'
|
||||
);
|
||||
if (!isConfirmed) return;
|
||||
roomActions.setPowerLevel(roomId, userId, newPowerLevel);
|
||||
|
@ -384,20 +374,16 @@ function ProfileViewer() {
|
|||
};
|
||||
|
||||
const handlePowerSelector = (e) => {
|
||||
openReusableContextMenu(
|
||||
'bottom',
|
||||
getEventCords(e, '.btn-surface'),
|
||||
(closeMenu) => (
|
||||
<PowerLevelSelector
|
||||
value={powerLevel}
|
||||
max={myPowerLevel}
|
||||
onSelect={(pl) => {
|
||||
closeMenu();
|
||||
handleChangePowerLevel(pl);
|
||||
}}
|
||||
/>
|
||||
),
|
||||
);
|
||||
openReusableContextMenu('bottom', getEventCords(e, '.btn-surface'), (closeMenu) => (
|
||||
<PowerLevelSelector
|
||||
value={powerLevel}
|
||||
max={myPowerLevel}
|
||||
onSelect={(pl) => {
|
||||
closeMenu();
|
||||
handleChangePowerLevel(pl);
|
||||
}}
|
||||
/>
|
||||
));
|
||||
};
|
||||
|
||||
return (
|
||||
|
@ -405,8 +391,10 @@ function ProfileViewer() {
|
|||
<div className="profile-viewer__user">
|
||||
<Avatar imageSrc={avatarUrl} text={username} bgColor={colorMXID(userId)} size="large" />
|
||||
<div className="profile-viewer__user__info">
|
||||
<Text variant="s1" weight="medium">{twemojify(username)}</Text>
|
||||
<Text variant="b2">{twemojify(userId)}</Text>
|
||||
<Text variant="s1" weight="medium">
|
||||
{username}
|
||||
</Text>
|
||||
<Text variant="b2">{userId}</Text>
|
||||
</div>
|
||||
<div className="profile-viewer__user__role">
|
||||
<Text variant="b3">Role</Text>
|
||||
|
@ -420,7 +408,7 @@ function ProfileViewer() {
|
|||
</div>
|
||||
<ModerationTools roomId={roomId} userId={userId} />
|
||||
<SessionInfo userId={userId} />
|
||||
{ userId !== mx.getUserId() && (
|
||||
{userId !== mx.getUserId() && (
|
||||
<ProfileFooter roomId={roomId} userId={userId} onRequestClose={closeDialog} />
|
||||
)}
|
||||
</div>
|
||||
|
|
|
@ -3,7 +3,6 @@ import React, { useState } from 'react';
|
|||
import './CrossSigning.scss';
|
||||
import FileSaver from 'file-saver';
|
||||
import { Formik } from 'formik';
|
||||
import { twemojify } from '../../../util/twemojify';
|
||||
|
||||
import initMatrix from '../../../client/initMatrix';
|
||||
import { openReusableDialog } from '../../../client/action/navigation';
|
||||
|
@ -22,15 +21,17 @@ import { useCrossSigningStatus } from '../../hooks/useCrossSigningStatus';
|
|||
const failedDialog = () => {
|
||||
const renderFailure = (requestClose) => (
|
||||
<div className="cross-signing__failure">
|
||||
<Text variant="h1">{twemojify('❌')}</Text>
|
||||
<Text variant="h1">{'❌'}</Text>
|
||||
<Text weight="medium">Failed to setup cross signing. Please try again.</Text>
|
||||
<Button onClick={requestClose}>Close</Button>
|
||||
</div>
|
||||
);
|
||||
|
||||
openReusableDialog(
|
||||
<Text variant="s1" weight="medium">Setup cross signing</Text>,
|
||||
renderFailure,
|
||||
<Text variant="s1" weight="medium">
|
||||
Setup cross signing
|
||||
</Text>,
|
||||
renderFailure
|
||||
);
|
||||
};
|
||||
|
||||
|
@ -48,11 +49,11 @@ const securityKeyDialog = (key) => {
|
|||
const renderSecurityKey = () => (
|
||||
<div className="cross-signing__key">
|
||||
<Text weight="medium">Please save this security key somewhere safe.</Text>
|
||||
<Text className="cross-signing__key-text">
|
||||
{key.encodedPrivateKey}
|
||||
</Text>
|
||||
<Text className="cross-signing__key-text">{key.encodedPrivateKey}</Text>
|
||||
<div className="cross-signing__key-btn">
|
||||
<Button variant="primary" onClick={() => copyKey(key)}>Copy</Button>
|
||||
<Button variant="primary" onClick={() => copyKey(key)}>
|
||||
Copy
|
||||
</Button>
|
||||
<Button onClick={() => downloadKey(key)}>Download</Button>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -62,8 +63,10 @@ const securityKeyDialog = (key) => {
|
|||
downloadKey();
|
||||
|
||||
openReusableDialog(
|
||||
<Text variant="s1" weight="medium">Security Key</Text>,
|
||||
() => renderSecurityKey(),
|
||||
<Text variant="s1" weight="medium">
|
||||
Security Key
|
||||
</Text>,
|
||||
() => renderSecurityKey()
|
||||
);
|
||||
};
|
||||
|
||||
|
@ -112,7 +115,7 @@ function CrossSigningSetup() {
|
|||
errors.phrase = 'Phrase must contain 8-127 characters with no space.';
|
||||
}
|
||||
if (values.confirmPhrase.length > 0 && values.confirmPhrase !== values.phrase) {
|
||||
errors.confirmPhrase = 'Phrase don\'t match.';
|
||||
errors.confirmPhrase = "Phrase don't match.";
|
||||
}
|
||||
return errors;
|
||||
};
|
||||
|
@ -121,10 +124,14 @@ function CrossSigningSetup() {
|
|||
<div className="cross-signing__setup">
|
||||
<div className="cross-signing__setup-entry">
|
||||
<Text>
|
||||
We will generate a <b>Security Key</b>,
|
||||
which you can use to manage messages backup and session verification.
|
||||
We will generate a <b>Security Key</b>, which you can use to manage messages backup and
|
||||
session verification.
|
||||
</Text>
|
||||
{genWithPhrase !== false && <Button variant="primary" onClick={() => setup()} disabled={genWithPhrase !== undefined}>Generate Key</Button>}
|
||||
{genWithPhrase !== false && (
|
||||
<Button variant="primary" onClick={() => setup()} disabled={genWithPhrase !== undefined}>
|
||||
Generate Key
|
||||
</Button>
|
||||
)}
|
||||
{genWithPhrase === false && <Spinner size="small" />}
|
||||
</div>
|
||||
<Text className="cross-signing__setup-divider">OR</Text>
|
||||
|
@ -133,9 +140,7 @@ function CrossSigningSetup() {
|
|||
onSubmit={(values) => setup(values.phrase)}
|
||||
validate={validator}
|
||||
>
|
||||
{({
|
||||
values, errors, handleChange, handleSubmit,
|
||||
}) => (
|
||||
{({ values, errors, handleChange, handleSubmit }) => (
|
||||
<form
|
||||
className="cross-signing__setup-entry"
|
||||
onSubmit={handleSubmit}
|
||||
|
@ -143,8 +148,8 @@ function CrossSigningSetup() {
|
|||
>
|
||||
<Text>
|
||||
Alternatively you can also set a <b>Security Phrase </b>
|
||||
so you don't have to remember long Security Key,
|
||||
and optionally save the Key as backup.
|
||||
so you don't have to remember long Security Key, and optionally save the Key as
|
||||
backup.
|
||||
</Text>
|
||||
<Input
|
||||
name="phrase"
|
||||
|
@ -155,7 +160,11 @@ function CrossSigningSetup() {
|
|||
required
|
||||
disabled={genWithPhrase !== undefined}
|
||||
/>
|
||||
{errors.phrase && <Text variant="b3" className="cross-signing__error">{errors.phrase}</Text>}
|
||||
{errors.phrase && (
|
||||
<Text variant="b3" className="cross-signing__error">
|
||||
{errors.phrase}
|
||||
</Text>
|
||||
)}
|
||||
<Input
|
||||
name="confirmPhrase"
|
||||
value={values.confirmPhrase}
|
||||
|
@ -165,8 +174,16 @@ function CrossSigningSetup() {
|
|||
required
|
||||
disabled={genWithPhrase !== undefined}
|
||||
/>
|
||||
{errors.confirmPhrase && <Text variant="b3" className="cross-signing__error">{errors.confirmPhrase}</Text>}
|
||||
{genWithPhrase !== true && <Button variant="primary" type="submit" disabled={genWithPhrase !== undefined}>Set Phrase & Generate Key</Button>}
|
||||
{errors.confirmPhrase && (
|
||||
<Text variant="b3" className="cross-signing__error">
|
||||
{errors.confirmPhrase}
|
||||
</Text>
|
||||
)}
|
||||
{genWithPhrase !== true && (
|
||||
<Button variant="primary" type="submit" disabled={genWithPhrase !== undefined}>
|
||||
Set Phrase & Generate Key
|
||||
</Button>
|
||||
)}
|
||||
{genWithPhrase === true && <Spinner size="small" />}
|
||||
</form>
|
||||
)}
|
||||
|
@ -177,31 +194,36 @@ function CrossSigningSetup() {
|
|||
|
||||
const setupDialog = () => {
|
||||
openReusableDialog(
|
||||
<Text variant="s1" weight="medium">Setup cross signing</Text>,
|
||||
() => <CrossSigningSetup />,
|
||||
<Text variant="s1" weight="medium">
|
||||
Setup cross signing
|
||||
</Text>,
|
||||
() => <CrossSigningSetup />
|
||||
);
|
||||
};
|
||||
|
||||
function CrossSigningReset() {
|
||||
return (
|
||||
<div className="cross-signing__reset">
|
||||
<Text variant="h1">{twemojify('✋🧑🚒🤚')}</Text>
|
||||
<Text variant="h1">{'✋🧑🚒🤚'}</Text>
|
||||
<Text weight="medium">Resetting cross-signing keys is permanent.</Text>
|
||||
<Text>
|
||||
Anyone you have verified with will see security alerts and your message backup will be lost.
|
||||
You almost certainly do not want to do this,
|
||||
unless you have lost <b>Security Key</b> or <b>Phrase</b> and
|
||||
every session you can cross-sign from.
|
||||
Anyone you have verified with will see security alerts and your message backup will be lost.
|
||||
You almost certainly do not want to do this, unless you have lost <b>Security Key</b> or{' '}
|
||||
<b>Phrase</b> and every session you can cross-sign from.
|
||||
</Text>
|
||||
<Button variant="danger" onClick={setupDialog}>Reset</Button>
|
||||
<Button variant="danger" onClick={setupDialog}>
|
||||
Reset
|
||||
</Button>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
const resetDialog = () => {
|
||||
openReusableDialog(
|
||||
<Text variant="s1" weight="medium">Reset cross signing</Text>,
|
||||
() => <CrossSigningReset />,
|
||||
<Text variant="s1" weight="medium">
|
||||
Reset cross signing
|
||||
</Text>,
|
||||
() => <CrossSigningReset />
|
||||
);
|
||||
};
|
||||
|
||||
|
@ -210,12 +232,23 @@ function CrossSignin() {
|
|||
return (
|
||||
<SettingTile
|
||||
title="Cross signing"
|
||||
content={<Text variant="b3">Setup to verify and keep track of all your sessions. Also required to backup encrypted message.</Text>}
|
||||
options={(
|
||||
isCSEnabled
|
||||
? <Button variant="danger" onClick={resetDialog}>Reset</Button>
|
||||
: <Button variant="primary" onClick={setupDialog}>Setup</Button>
|
||||
)}
|
||||
content={
|
||||
<Text variant="b3">
|
||||
Setup to verify and keep track of all your sessions. Also required to backup encrypted
|
||||
message.
|
||||
</Text>
|
||||
}
|
||||
options={
|
||||
isCSEnabled ? (
|
||||
<Button variant="danger" onClick={resetDialog}>
|
||||
Reset
|
||||
</Button>
|
||||
) : (
|
||||
<Button variant="primary" onClick={setupDialog}>
|
||||
Setup
|
||||
</Button>
|
||||
)
|
||||
}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -2,7 +2,6 @@
|
|||
import React, { useState, useEffect } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import './KeyBackup.scss';
|
||||
import { twemojify } from '../../../util/twemojify';
|
||||
|
||||
import initMatrix from '../../../client/initMatrix';
|
||||
import { openReusableDialog } from '../../../client/action/navigation';
|
||||
|
@ -34,10 +33,7 @@ function CreateKeyBackupDialog({ keyData }) {
|
|||
let info;
|
||||
|
||||
try {
|
||||
info = await mx.prepareKeyBackupVersion(
|
||||
null,
|
||||
{ secureSecretStorage: true },
|
||||
);
|
||||
info = await mx.prepareKeyBackupVersion(null, { secureSecretStorage: true });
|
||||
info = await mx.createKeyBackupVersion(info);
|
||||
await mx.scheduleAllGroupSessionsForBackup();
|
||||
if (!mountStore.getItem()) return;
|
||||
|
@ -65,7 +61,7 @@ function CreateKeyBackupDialog({ keyData }) {
|
|||
)}
|
||||
{done === true && (
|
||||
<>
|
||||
<Text variant="h1">{twemojify('✅')}</Text>
|
||||
<Text variant="h1">{'✅'}</Text>
|
||||
<Text>Successfully created backup</Text>
|
||||
</>
|
||||
)}
|
||||
|
@ -104,12 +100,9 @@ function RestoreKeyBackupDialog({ keyData }) {
|
|||
|
||||
try {
|
||||
const backupInfo = await mx.getKeyBackupVersion();
|
||||
const info = await mx.restoreKeyBackupWithSecretStorage(
|
||||
backupInfo,
|
||||
undefined,
|
||||
undefined,
|
||||
{ progressCallback },
|
||||
);
|
||||
const info = await mx.restoreKeyBackupWithSecretStorage(backupInfo, undefined, undefined, {
|
||||
progressCallback,
|
||||
});
|
||||
if (!mountStore.getItem()) return;
|
||||
setStatus({ done: `Successfully restored backup keys (${info.imported}/${info.total}).` });
|
||||
} catch (e) {
|
||||
|
@ -138,7 +131,7 @@ function RestoreKeyBackupDialog({ keyData }) {
|
|||
)}
|
||||
{status.done && (
|
||||
<>
|
||||
<Text variant="h1">{twemojify('✅')}</Text>
|
||||
<Text variant="h1">{'✅'}</Text>
|
||||
<Text>{status.done}</Text>
|
||||
</>
|
||||
)}
|
||||
|
@ -176,14 +169,16 @@ function DeleteKeyBackupDialog({ requestClose }) {
|
|||
|
||||
return (
|
||||
<div className="key-backup__delete">
|
||||
<Text variant="h1">{twemojify('🗑')}</Text>
|
||||
<Text variant="h1">{'🗑'}</Text>
|
||||
<Text weight="medium">Deleting key backup is permanent.</Text>
|
||||
<Text>All encrypted messages keys stored on server will be deleted.</Text>
|
||||
{
|
||||
isDeleting
|
||||
? <Spinner size="small" />
|
||||
: <Button variant="danger" onClick={deleteBackup}>Delete</Button>
|
||||
}
|
||||
{isDeleting ? (
|
||||
<Spinner size="small" />
|
||||
) : (
|
||||
<Button variant="danger" onClick={deleteBackup}>
|
||||
Delete
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
@ -224,9 +219,11 @@ function KeyBackup() {
|
|||
if (keyData === null) return;
|
||||
|
||||
openReusableDialog(
|
||||
<Text variant="s1" weight="medium">Create Key Backup</Text>,
|
||||
<Text variant="s1" weight="medium">
|
||||
Create Key Backup
|
||||
</Text>,
|
||||
() => <CreateKeyBackupDialog keyData={keyData} />,
|
||||
() => fetchKeyBackupVersion(),
|
||||
() => fetchKeyBackupVersion()
|
||||
);
|
||||
};
|
||||
|
||||
|
@ -235,29 +232,44 @@ function KeyBackup() {
|
|||
if (keyData === null) return;
|
||||
|
||||
openReusableDialog(
|
||||
<Text variant="s1" weight="medium">Restore Key Backup</Text>,
|
||||
() => <RestoreKeyBackupDialog keyData={keyData} />,
|
||||
<Text variant="s1" weight="medium">
|
||||
Restore Key Backup
|
||||
</Text>,
|
||||
() => <RestoreKeyBackupDialog keyData={keyData} />
|
||||
);
|
||||
};
|
||||
|
||||
const openDeleteKeyBackup = () => openReusableDialog(
|
||||
<Text variant="s1" weight="medium">Delete Key Backup</Text>,
|
||||
(requestClose) => (
|
||||
<DeleteKeyBackupDialog
|
||||
requestClose={(isDone) => {
|
||||
if (isDone) setKeyBackup(null);
|
||||
requestClose();
|
||||
}}
|
||||
/>
|
||||
),
|
||||
);
|
||||
const openDeleteKeyBackup = () =>
|
||||
openReusableDialog(
|
||||
<Text variant="s1" weight="medium">
|
||||
Delete Key Backup
|
||||
</Text>,
|
||||
(requestClose) => (
|
||||
<DeleteKeyBackupDialog
|
||||
requestClose={(isDone) => {
|
||||
if (isDone) setKeyBackup(null);
|
||||
requestClose();
|
||||
}}
|
||||
/>
|
||||
)
|
||||
);
|
||||
|
||||
const renderOptions = () => {
|
||||
if (keyBackup === undefined) return <Spinner size="small" />;
|
||||
if (keyBackup === null) return <Button variant="primary" onClick={openCreateKeyBackup}>Create Backup</Button>;
|
||||
if (keyBackup === null)
|
||||
return (
|
||||
<Button variant="primary" onClick={openCreateKeyBackup}>
|
||||
Create Backup
|
||||
</Button>
|
||||
);
|
||||
return (
|
||||
<>
|
||||
<IconButton src={DownloadIC} variant="positive" onClick={openRestoreKeyBackup} tooltip="Restore backup" />
|
||||
<IconButton
|
||||
src={DownloadIC}
|
||||
variant="positive"
|
||||
onClick={openRestoreKeyBackup}
|
||||
tooltip="Restore backup"
|
||||
/>
|
||||
<IconButton src={BinIC} onClick={openDeleteKeyBackup} tooltip="Delete backup" />
|
||||
</>
|
||||
);
|
||||
|
@ -266,9 +278,12 @@ function KeyBackup() {
|
|||
return (
|
||||
<SettingTile
|
||||
title="Encrypted messages backup"
|
||||
content={(
|
||||
content={
|
||||
<>
|
||||
<Text variant="b3">Online backup your encrypted messages keys with your account data in case you lose access to your sessions. Your keys will be secured with a unique Security Key.</Text>
|
||||
<Text variant="b3">
|
||||
Online backup your encrypted messages keys with your account data in case you lose
|
||||
access to your sessions. Your keys will be secured with a unique Security Key.
|
||||
</Text>
|
||||
{!isCSEnabled && (
|
||||
<InfoCard
|
||||
style={{ marginTop: 'var(--sp-ultra-tight)' }}
|
||||
|
@ -279,7 +294,7 @@ function KeyBackup() {
|
|||
/>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
}
|
||||
options={isCSEnabled ? renderOptions() : null}
|
||||
/>
|
||||
);
|
||||
|
|
|
@ -80,7 +80,7 @@ function AppearanceSection() {
|
|||
{ text: 'Butter' },
|
||||
{ text: 'Nord Dark' },
|
||||
{ text: 'Cyberpunk' },
|
||||
{ text: 'Almond Dark'},
|
||||
{ text: 'Almond Dark' },
|
||||
]}
|
||||
onSelect={(index) => {
|
||||
if (settings.useSystemTheme) toggleSystemTheme();
|
||||
|
@ -328,28 +328,6 @@ function AboutSection() {
|
|||
.
|
||||
</Text>
|
||||
</li>
|
||||
<li>
|
||||
{/* eslint-disable-next-line react/jsx-one-expression-per-line */}
|
||||
<Text>
|
||||
The{' '}
|
||||
<a href="https://twemoji.twitter.com" target="_blank" rel="noreferrer noopener">
|
||||
Twemoji
|
||||
</a>{' '}
|
||||
emoji art is ©{' '}
|
||||
<a href="https://twemoji.twitter.com" target="_blank" rel="noreferrer noopener">
|
||||
Twitter, Inc and other contributors
|
||||
</a>{' '}
|
||||
used under the terms of{' '}
|
||||
<a
|
||||
href="https://creativecommons.org/licenses/by/4.0/"
|
||||
target="_blank"
|
||||
rel="noreferrer noopener"
|
||||
>
|
||||
CC-BY 4.0
|
||||
</a>
|
||||
.
|
||||
</Text>
|
||||
</li>
|
||||
<li>
|
||||
{/* eslint-disable-next-line react/jsx-one-expression-per-line */}
|
||||
<Text>
|
||||
|
|
|
@ -3,8 +3,6 @@ import React, { useState, useEffect } from 'react';
|
|||
import PropTypes from 'prop-types';
|
||||
import './SpaceManage.scss';
|
||||
|
||||
import { twemojify } from '../../../util/twemojify';
|
||||
|
||||
import initMatrix from '../../../client/initMatrix';
|
||||
import cons from '../../../client/state/cons';
|
||||
import navigation from '../../../client/state/navigation';
|
||||
|
@ -37,32 +35,37 @@ function SpaceManageBreadcrumb({ path, onSelect }) {
|
|||
<div className="space-manage-breadcrumb__wrapper">
|
||||
<ScrollView horizontal vertical={false} invisible>
|
||||
<div className="space-manage-breadcrumb">
|
||||
{
|
||||
path.map((item, index) => (
|
||||
<React.Fragment key={item.roomId}>
|
||||
{index > 0 && <RawIcon size="extra-small" src={ChevronRightIC} />}
|
||||
<Button onClick={() => onSelect(item.roomId, item.name)}>
|
||||
<Text variant="b2">{twemojify(item.name)}</Text>
|
||||
</Button>
|
||||
</React.Fragment>
|
||||
))
|
||||
}
|
||||
{path.map((item, index) => (
|
||||
<React.Fragment key={item.roomId}>
|
||||
{index > 0 && <RawIcon size="extra-small" src={ChevronRightIC} />}
|
||||
<Button onClick={() => onSelect(item.roomId, item.name)}>
|
||||
<Text variant="b2">{item.name}</Text>
|
||||
</Button>
|
||||
</React.Fragment>
|
||||
))}
|
||||
</div>
|
||||
</ScrollView>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
SpaceManageBreadcrumb.propTypes = {
|
||||
path: PropTypes.arrayOf(PropTypes.exact({
|
||||
roomId: PropTypes.string,
|
||||
name: PropTypes.string,
|
||||
})).isRequired,
|
||||
path: PropTypes.arrayOf(
|
||||
PropTypes.exact({
|
||||
roomId: PropTypes.string,
|
||||
name: PropTypes.string,
|
||||
})
|
||||
).isRequired,
|
||||
onSelect: PropTypes.func.isRequired,
|
||||
};
|
||||
|
||||
function SpaceManageItem({
|
||||
parentId, roomInfo, onSpaceClick, requestClose,
|
||||
isSelected, onSelect, roomHierarchy,
|
||||
parentId,
|
||||
roomInfo,
|
||||
onSpaceClick,
|
||||
requestClose,
|
||||
isSelected,
|
||||
onSelect,
|
||||
roomHierarchy,
|
||||
}) {
|
||||
const [isExpand, setIsExpand] = useState(false);
|
||||
const [isJoining, setIsJoining] = useState(false);
|
||||
|
@ -72,8 +75,11 @@ function SpaceManageItem({
|
|||
const parentRoom = mx.getRoom(parentId);
|
||||
const isSpace = roomInfo.room_type === 'm.space';
|
||||
const roomId = roomInfo.room_id;
|
||||
const canManage = parentRoom?.currentState.maySendStateEvent('m.space.child', mx.getUserId()) || false;
|
||||
const isSuggested = parentRoom?.currentState.getStateEvents('m.space.child', roomId)?.getContent().suggested === true;
|
||||
const canManage =
|
||||
parentRoom?.currentState.maySendStateEvent('m.space.child', mx.getUserId()) || false;
|
||||
const isSuggested =
|
||||
parentRoom?.currentState.getStateEvents('m.space.child', roomId)?.getContent().suggested ===
|
||||
true;
|
||||
|
||||
const room = mx.getRoom(roomId);
|
||||
const isJoined = !!(room?.getMyMembership() === 'join' || null);
|
||||
|
@ -103,17 +109,13 @@ function SpaceManageItem({
|
|||
bgColor={colorMXID(roomId)}
|
||||
imageSrc={isDM ? imageSrc : null}
|
||||
iconColor="var(--ic-surface-low)"
|
||||
iconSrc={
|
||||
isDM
|
||||
? null
|
||||
: joinRuleToIconSrc((roomInfo.join_rules || roomInfo.join_rule), isSpace)
|
||||
}
|
||||
iconSrc={isDM ? null : joinRuleToIconSrc(roomInfo.join_rules || roomInfo.join_rule, isSpace)}
|
||||
size="extra-small"
|
||||
/>
|
||||
);
|
||||
const roomNameJSX = (
|
||||
<Text>
|
||||
{twemojify(name)}
|
||||
{name}
|
||||
<Text variant="b3" span>{` • ${roomInfo.num_joined_members} members`}</Text>
|
||||
</Text>
|
||||
);
|
||||
|
@ -130,11 +132,11 @@ function SpaceManageItem({
|
|||
);
|
||||
|
||||
return (
|
||||
<div
|
||||
className={`space-manage-item${isSpace ? '--space' : ''}`}
|
||||
>
|
||||
<div className={`space-manage-item${isSpace ? '--space' : ''}`}>
|
||||
<div>
|
||||
{canManage && <Checkbox isActive={isSelected} onToggle={() => onSelect(roomId)} variant="positive" />}
|
||||
{canManage && (
|
||||
<Checkbox isActive={isSelected} onToggle={() => onSelect(roomId)} variant="positive" />
|
||||
)}
|
||||
<button
|
||||
className="space-manage-item__btn"
|
||||
onClick={isSpace ? () => onSpaceClick(roomId, name) : null}
|
||||
|
@ -145,13 +147,15 @@ function SpaceManageItem({
|
|||
{isSuggested && <Text variant="b2">Suggested</Text>}
|
||||
</button>
|
||||
{roomInfo.topic && expandBtnJsx}
|
||||
{
|
||||
isJoined
|
||||
? <Button onClick={handleOpen}>Open</Button>
|
||||
: <Button variant="primary" onClick={handleJoin} disabled={isJoining}>{isJoining ? 'Joining...' : 'Join'}</Button>
|
||||
}
|
||||
{isJoined ? (
|
||||
<Button onClick={handleOpen}>Open</Button>
|
||||
) : (
|
||||
<Button variant="primary" onClick={handleJoin} disabled={isJoining}>
|
||||
{isJoining ? 'Joining...' : 'Join'}
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
{isExpand && roomInfo.topic && <Text variant="b2">{twemojify(roomInfo.topic, undefined, true)}</Text>}
|
||||
{isExpand && roomInfo.topic && <Text variant="b2">{roomInfo.topic}</Text>}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
@ -201,9 +205,11 @@ function SpaceManageFooter({ parentId, selected }) {
|
|||
<div className="space-manage__footer">
|
||||
{process && <Spinner size="small" />}
|
||||
<Text weight="medium">{process || `${selected.length} item selected`}</Text>
|
||||
{ !process && (
|
||||
{!process && (
|
||||
<>
|
||||
<Button onClick={handleRemove} variant="danger">Remove</Button>
|
||||
<Button onClick={handleRemove} variant="danger">
|
||||
Remove
|
||||
</Button>
|
||||
<Button
|
||||
onClick={() => handleToggleSuggested(!allSuggested)}
|
||||
variant={allSuggested ? 'surface' : 'primary'}
|
||||
|
@ -336,20 +342,19 @@ function SpaceManageContent({ roomId, requestClose }) {
|
|||
if (!currentHierarchy) loadRoomHierarchy();
|
||||
return (
|
||||
<div className="space-manage__content">
|
||||
{spacePath.length > 1 && (
|
||||
<SpaceManageBreadcrumb path={spacePath} onSelect={addPathItem} />
|
||||
)}
|
||||
<Text variant="b3" weight="bold">Rooms and spaces</Text>
|
||||
{spacePath.length > 1 && <SpaceManageBreadcrumb path={spacePath} onSelect={addPathItem} />}
|
||||
<Text variant="b3" weight="bold">
|
||||
Rooms and spaces
|
||||
</Text>
|
||||
<div className="space-manage__content-items">
|
||||
{!isLoading && currentHierarchy?.rooms?.length === 1 && (
|
||||
<Text>
|
||||
Either the space contains private rooms or you need to join space to view it's rooms.
|
||||
</Text>
|
||||
)}
|
||||
{currentHierarchy && (currentHierarchy.rooms?.map((roomInfo) => (
|
||||
roomInfo.room_id === currentPath.roomId
|
||||
? null
|
||||
: (
|
||||
{currentHierarchy &&
|
||||
currentHierarchy.rooms?.map((roomInfo) =>
|
||||
roomInfo.room_id === currentPath.roomId ? null : (
|
||||
<SpaceManageItem
|
||||
key={roomInfo.room_id}
|
||||
isSelected={selected.includes(roomInfo.room_id)}
|
||||
|
@ -361,7 +366,7 @@ function SpaceManageContent({ roomId, requestClose }) {
|
|||
onSelect={handleSelected}
|
||||
/>
|
||||
)
|
||||
)))}
|
||||
)}
|
||||
{!currentHierarchy && <Text>loading...</Text>}
|
||||
</div>
|
||||
{currentHierarchy?.canLoadMore && !isLoading && (
|
||||
|
@ -410,20 +415,16 @@ function SpaceManage() {
|
|||
<PopupWindow
|
||||
isOpen={roomId !== null}
|
||||
className="space-manage"
|
||||
title={(
|
||||
title={
|
||||
<Text variant="s1" weight="medium" primary>
|
||||
{roomId && twemojify(room.name)}
|
||||
{roomId && room.name}
|
||||
<span style={{ color: 'var(--tc-surface-low)' }}> — manage rooms</span>
|
||||
</Text>
|
||||
)}
|
||||
}
|
||||
contentOptions={<IconButton src={CrossIC} onClick={requestClose} tooltip="Close" />}
|
||||
onRequestClose={requestClose}
|
||||
>
|
||||
{
|
||||
roomId
|
||||
? <SpaceManageContent roomId={roomId} requestClose={requestClose} />
|
||||
: <div />
|
||||
}
|
||||
{roomId ? <SpaceManageContent roomId={roomId} requestClose={requestClose} /> : <div />}
|
||||
</PopupWindow>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -2,8 +2,6 @@ import React, { useState, useEffect } from 'react';
|
|||
import PropTypes from 'prop-types';
|
||||
import './SpaceSettings.scss';
|
||||
|
||||
import { twemojify } from '../../../util/twemojify';
|
||||
|
||||
import initMatrix from '../../../client/initMatrix';
|
||||
import cons from '../../../client/state/cons';
|
||||
import navigation from '../../../client/state/navigation';
|
||||
|
@ -48,23 +46,28 @@ const tabText = {
|
|||
PERMISSIONS: 'Permissions',
|
||||
};
|
||||
|
||||
const tabItems = [{
|
||||
iconSrc: SettingsIC,
|
||||
text: tabText.GENERAL,
|
||||
disabled: false,
|
||||
}, {
|
||||
iconSrc: UserIC,
|
||||
text: tabText.MEMBERS,
|
||||
disabled: false,
|
||||
}, {
|
||||
iconSrc: EmojiIC,
|
||||
text: tabText.EMOJIS,
|
||||
disabled: false,
|
||||
}, {
|
||||
iconSrc: ShieldUserIC,
|
||||
text: tabText.PERMISSIONS,
|
||||
disabled: false,
|
||||
}];
|
||||
const tabItems = [
|
||||
{
|
||||
iconSrc: SettingsIC,
|
||||
text: tabText.GENERAL,
|
||||
disabled: false,
|
||||
},
|
||||
{
|
||||
iconSrc: UserIC,
|
||||
text: tabText.MEMBERS,
|
||||
disabled: false,
|
||||
},
|
||||
{
|
||||
iconSrc: EmojiIC,
|
||||
text: tabText.EMOJIS,
|
||||
disabled: false,
|
||||
},
|
||||
{
|
||||
iconSrc: ShieldUserIC,
|
||||
text: tabText.PERMISSIONS,
|
||||
disabled: false,
|
||||
},
|
||||
];
|
||||
|
||||
function GeneralSettings({ roomId }) {
|
||||
const isPinned = initMatrix.accountData.spaceShortcut.has(roomId);
|
||||
|
@ -103,7 +106,7 @@ function GeneralSettings({ roomId }) {
|
|||
'Leave space',
|
||||
`Are you sure that you want to leave "${roomName}" space?`,
|
||||
'Leave',
|
||||
'danger',
|
||||
'danger'
|
||||
);
|
||||
if (isConfirmed) leave(roomId);
|
||||
}}
|
||||
|
@ -165,12 +168,12 @@ function SpaceSettings() {
|
|||
<PopupWindow
|
||||
isOpen={isOpen}
|
||||
className="space-settings"
|
||||
title={(
|
||||
title={
|
||||
<Text variant="s1" weight="medium" primary>
|
||||
{isOpen && twemojify(room.name)}
|
||||
{isOpen && room.name}
|
||||
<span style={{ color: 'var(--tc-surface-low)' }}> — space settings</span>
|
||||
</Text>
|
||||
)}
|
||||
}
|
||||
contentOptions={<IconButton src={CrossIC} onClick={requestClose} tooltip="Close" />}
|
||||
onRequestClose={requestClose}
|
||||
>
|
||||
|
|
Loading…
Reference in a new issue