mirror of
https://github.com/array-in-a-matrix/xinny.git
synced 2024-05-21 13:47:27 -04:00
remove twemoji
This commit is contained in:
parent
5c5acbd763
commit
6da274d1a9
|
@ -2,16 +2,21 @@ import React from 'react';
|
|||
import PropTypes from 'prop-types';
|
||||
import './Dialog.scss';
|
||||
|
||||
import { twemojify } from '../../../util/twemojify';
|
||||
|
||||
import Text from '../../atoms/text/Text';
|
||||
import Header, { TitleWrapper } from '../../atoms/header/Header';
|
||||
import ScrollView from '../../atoms/scroll/ScrollView';
|
||||
import RawModal from '../../atoms/modal/RawModal';
|
||||
|
||||
function Dialog({
|
||||
className, isOpen, title, onAfterOpen, onAfterClose,
|
||||
contentOptions, onRequestClose, closeFromOutside, children,
|
||||
className,
|
||||
isOpen,
|
||||
title,
|
||||
onAfterOpen,
|
||||
onAfterClose,
|
||||
contentOptions,
|
||||
onRequestClose,
|
||||
closeFromOutside,
|
||||
children,
|
||||
invisibleScroll,
|
||||
}) {
|
||||
return (
|
||||
|
@ -28,19 +33,19 @@ function Dialog({
|
|||
<div className="dialog__content">
|
||||
<Header>
|
||||
<TitleWrapper>
|
||||
{
|
||||
typeof title === 'string'
|
||||
? <Text variant="h2" weight="medium" primary>{twemojify(title)}</Text>
|
||||
: title
|
||||
}
|
||||
{typeof title === 'string' ? (
|
||||
<Text variant="h2" weight="medium" primary>
|
||||
{title}
|
||||
</Text>
|
||||
) : (
|
||||
title
|
||||
)}
|
||||
</TitleWrapper>
|
||||
{contentOptions}
|
||||
</Header>
|
||||
<div className="dialog__content__wrapper">
|
||||
<ScrollView autoHide={!invisibleScroll} invisible={invisibleScroll}>
|
||||
<div className="dialog__content-container">
|
||||
{children}
|
||||
</div>
|
||||
<div className="dialog__content-container">{children}</div>
|
||||
</ScrollView>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -1,7 +1,5 @@
|
|||
/* eslint-disable react/prop-types */
|
||||
import React, {
|
||||
useState, useEffect, useCallback, useRef,
|
||||
} from 'react';
|
||||
import React, { useState, useEffect, useCallback, useRef } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import './Message.scss';
|
||||
|
||||
|
@ -9,13 +7,20 @@ import { twemojify } from '../../../util/twemojify';
|
|||
|
||||
import initMatrix from '../../../client/initMatrix';
|
||||
import {
|
||||
getUsername, getUsernameOfRoomMember, parseReply, trimHTMLReply,
|
||||
getUsername,
|
||||
getUsernameOfRoomMember,
|
||||
parseReply,
|
||||
trimHTMLReply,
|
||||
} from '../../../util/matrixUtil';
|
||||
import colorMXID from '../../../util/colorMXID';
|
||||
import { getEventCords } from '../../../util/common';
|
||||
import { redactEvent, sendReaction } from '../../../client/action/roomTimeline';
|
||||
import {
|
||||
openEmojiBoard, openProfileViewer, openReadReceipts, openViewSource, replyTo,
|
||||
openEmojiBoard,
|
||||
openProfileViewer,
|
||||
openReadReceipts,
|
||||
openViewSource,
|
||||
replyTo,
|
||||
} from '../../../client/action/navigation';
|
||||
import { sanitizeCustomHtml } from '../../../util/sanitize';
|
||||
|
||||
|
@ -27,7 +32,11 @@ import Input from '../../atoms/input/Input';
|
|||
import Avatar from '../../atoms/avatar/Avatar';
|
||||
import IconButton from '../../atoms/button/IconButton';
|
||||
import Time from '../../atoms/time/Time';
|
||||
import ContextMenu, { MenuHeader, MenuItem, MenuBorder } from '../../atoms/context-menu/ContextMenu';
|
||||
import ContextMenu, {
|
||||
MenuHeader,
|
||||
MenuItem,
|
||||
MenuBorder,
|
||||
} from '../../atoms/context-menu/ContextMenu';
|
||||
import * as Media from '../media/Media';
|
||||
|
||||
import ReplyArrowIC from '../../../../public/res/ic/outlined/reply-arrow.svg';
|
||||
|
@ -61,9 +70,7 @@ function PlaceholderMessage() {
|
|||
);
|
||||
}
|
||||
|
||||
const MessageAvatar = React.memo(({
|
||||
roomId, avatarSrc, userId, username,
|
||||
}) => (
|
||||
const MessageAvatar = React.memo(({ roomId, avatarSrc, userId, username }) => (
|
||||
<div className="message__avatar-container">
|
||||
<button type="button" onClick={() => openProfileViewer(userId, roomId)}>
|
||||
<Avatar imageSrc={avatarSrc} text={username} bgColor={colorMXID(userId)} size="small" />
|
||||
|
@ -71,9 +78,7 @@ const MessageAvatar = React.memo(({
|
|||
</div>
|
||||
));
|
||||
|
||||
const MessageHeader = React.memo(({
|
||||
userId, username, timestamp, fullTime,
|
||||
}) => (
|
||||
const MessageHeader = React.memo(({ userId, username, timestamp, fullTime }) => (
|
||||
<div className="message__header">
|
||||
<Text
|
||||
style={{ color: colorMXID(userId) }}
|
||||
|
@ -82,8 +87,8 @@ const MessageHeader = React.memo(({
|
|||
weight="medium"
|
||||
span
|
||||
>
|
||||
<span>{twemojify(username)}</span>
|
||||
<span>{twemojify(userId)}</span>
|
||||
<span>{username}</span>
|
||||
<span>{userId}</span>
|
||||
</Text>
|
||||
<div className="message__time">
|
||||
<Text variant="b3">
|
||||
|
@ -107,9 +112,7 @@ function MessageReply({ name, color, body }) {
|
|||
<div className="message__reply">
|
||||
<Text variant="b2">
|
||||
<RawIcon color={color} size="extra-small" src={ReplyArrowIC} />
|
||||
<span style={{ color }}>{twemojify(name)}</span>
|
||||
{' '}
|
||||
{twemojify(body)}
|
||||
<span style={{ color }}>{name}</span> {body}
|
||||
</Text>
|
||||
</div>
|
||||
);
|
||||
|
@ -143,7 +146,9 @@ const MessageReplyWrapper = React.memo(({ roomTimeline, eventId }) => {
|
|||
const username = getUsernameOfRoomMember(mEvent.sender);
|
||||
|
||||
if (isMountedRef.current === false) return;
|
||||
const fallbackBody = mEvent.isRedacted() ? '*** This message has been deleted ***' : '*** Unable to load reply ***';
|
||||
const fallbackBody = mEvent.isRedacted()
|
||||
? '*** This message has been deleted ***'
|
||||
: '*** Unable to load reply ***';
|
||||
let parsedBody = parseReply(rawBody)?.body ?? rawBody ?? fallbackBody;
|
||||
if (editedList && parsedBody.startsWith(' * ')) {
|
||||
parsedBody = parsedBody.slice(3);
|
||||
|
@ -197,13 +202,7 @@ MessageReplyWrapper.propTypes = {
|
|||
eventId: PropTypes.string.isRequired,
|
||||
};
|
||||
|
||||
const MessageBody = React.memo(({
|
||||
senderName,
|
||||
body,
|
||||
isCustomHTML,
|
||||
isEdited,
|
||||
msgType,
|
||||
}) => {
|
||||
const MessageBody = React.memo(({ senderName, body, isCustomHTML, isEdited, msgType }) => {
|
||||
// if body is not string it is a React element.
|
||||
if (typeof body !== 'string') return <div className="message__body">{body}</div>;
|
||||
|
||||
|
@ -215,14 +214,14 @@ const MessageBody = React.memo(({
|
|||
undefined,
|
||||
true,
|
||||
false,
|
||||
true,
|
||||
true
|
||||
);
|
||||
} catch {
|
||||
console.error('Malformed custom html: ', body);
|
||||
content = twemojify(body, undefined);
|
||||
content = body;
|
||||
}
|
||||
} else {
|
||||
content = twemojify(body, undefined, true);
|
||||
content = body;
|
||||
}
|
||||
|
||||
// Determine if this message should render with large emojis
|
||||
|
@ -240,10 +239,14 @@ const MessageBody = React.memo(({
|
|||
const nEmojis = content.filter((e) => e.type === 'img').length;
|
||||
|
||||
// Make sure there's no text besides whitespace and variation selector U+FE0F
|
||||
if (nEmojis <= 10 && content.every((element) => (
|
||||
(typeof element === 'object' && element.type === 'img')
|
||||
|| (typeof element === 'string' && /^[\s\ufe0f]*$/g.test(element))
|
||||
))) {
|
||||
if (
|
||||
nEmojis <= 10 &&
|
||||
content.every(
|
||||
(element) =>
|
||||
(typeof element === 'object' && element.type === 'img') ||
|
||||
(typeof element === 'string' && /^[\s\ufe0f]*$/g.test(element))
|
||||
)
|
||||
) {
|
||||
emojiOnly = true;
|
||||
}
|
||||
}
|
||||
|
@ -251,22 +254,25 @@ const MessageBody = React.memo(({
|
|||
if (!isCustomHTML) {
|
||||
// If this is a plaintext message, wrap it in a <p> element (automatically applying
|
||||
// white-space: pre-wrap) in order to preserve newlines
|
||||
content = (<p className="message__body-plain">{content}</p>);
|
||||
content = <p className="message__body-plain">{content}</p>;
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="message__body">
|
||||
<div dir="auto" className={`text ${emojiOnly ? 'text-h1' : 'text-b1'}`}>
|
||||
{ msgType === 'm.emote' && (
|
||||
{msgType === 'm.emote' && (
|
||||
<>
|
||||
{'* '}
|
||||
{twemojify(senderName)}
|
||||
{' '}
|
||||
{senderName}{' '}
|
||||
</>
|
||||
)}
|
||||
{ content }
|
||||
{content}
|
||||
</div>
|
||||
{ isEdited && <Text className="message__body-edited" variant="b3">(edited)</Text>}
|
||||
{isEdited && (
|
||||
<Text className="message__body-edited" variant="b3">
|
||||
(edited)
|
||||
</Text>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
});
|
||||
|
@ -305,7 +311,13 @@ function MessageEdit({ body, onSave, onCancel }) {
|
|||
};
|
||||
|
||||
return (
|
||||
<form className="message__edit" onSubmit={(e) => { e.preventDefault(); onSave(editInputRef.current.value, body); }}>
|
||||
<form
|
||||
className="message__edit"
|
||||
onSubmit={(e) => {
|
||||
e.preventDefault();
|
||||
onSave(editInputRef.current.value, body);
|
||||
}}
|
||||
>
|
||||
<Input
|
||||
forwardRef={editInputRef}
|
||||
onKeyDown={handleKeyDown}
|
||||
|
@ -316,7 +328,9 @@ function MessageEdit({ body, onSave, onCancel }) {
|
|||
autoFocus
|
||||
/>
|
||||
<div className="message__edit-btns">
|
||||
<Button type="submit" variant="primary">Save</Button>
|
||||
<Button type="submit" variant="primary">
|
||||
Save
|
||||
</Button>
|
||||
<Button onClick={onCancel}>Cancel</Button>
|
||||
</div>
|
||||
</form>
|
||||
|
@ -366,23 +380,19 @@ function genReactionMsg(userIds, reaction, shortcode) {
|
|||
<>
|
||||
{userIds.map((userId, index) => (
|
||||
<React.Fragment key={userId}>
|
||||
{twemojify(getUsername(userId))}
|
||||
{getUsername(userId)}
|
||||
{index < userIds.length - 1 && (
|
||||
<span style={{ opacity: '.6' }}>
|
||||
{index === userIds.length - 2 ? ' and ' : ', '}
|
||||
</span>
|
||||
<span style={{ opacity: '.6' }}>{index === userIds.length - 2 ? ' and ' : ', '}</span>
|
||||
)}
|
||||
</React.Fragment>
|
||||
))}
|
||||
<span style={{ opacity: '.6' }}>{' reacted with '}</span>
|
||||
{twemojify(shortcode ? `:${shortcode}:` : reaction, { className: 'react-emoji' })}
|
||||
{shortcode ? `:${shortcode}:` : reaction}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
function MessageReaction({
|
||||
reaction, shortcode, count, users, isActive, onClick,
|
||||
}) {
|
||||
function MessageReaction({ reaction, shortcode, count, users, isActive, onClick }) {
|
||||
let customEmojiUrl = null;
|
||||
if (reaction.match(/^mxc:\/\/\S+$/)) {
|
||||
customEmojiUrl = initMatrix.matrixClient.mxcUrlToHttp(reaction);
|
||||
|
@ -390,19 +400,32 @@ function MessageReaction({
|
|||
return (
|
||||
<Tooltip
|
||||
className="msg__reaction-tooltip"
|
||||
content={<Text variant="b2">{users.length > 0 ? genReactionMsg(users, reaction, shortcode) : 'Unable to load who has reacted'}</Text>}
|
||||
content={
|
||||
<Text variant="b2">
|
||||
{users.length > 0
|
||||
? genReactionMsg(users, reaction, shortcode)
|
||||
: 'Unable to load who has reacted'}
|
||||
</Text>
|
||||
}
|
||||
>
|
||||
<button
|
||||
onClick={onClick}
|
||||
type="button"
|
||||
className={`msg__reaction${isActive ? ' msg__reaction--active' : ''}`}
|
||||
>
|
||||
{
|
||||
customEmojiUrl
|
||||
? <img className="react-emoji" draggable="false" alt={shortcode ?? reaction} src={customEmojiUrl} />
|
||||
: twemojify(reaction, { className: 'react-emoji' })
|
||||
}
|
||||
<Text variant="b3" className="msg__reaction-count">{count}</Text>
|
||||
{customEmojiUrl ? (
|
||||
<img
|
||||
className="react-emoji"
|
||||
draggable="false"
|
||||
alt={shortcode ?? reaction}
|
||||
src={customEmojiUrl}
|
||||
/>
|
||||
) : (
|
||||
reaction
|
||||
)}
|
||||
<Text variant="b3" className="msg__reaction-count">
|
||||
{count}
|
||||
</Text>
|
||||
</button>
|
||||
</Tooltip>
|
||||
);
|
||||
|
@ -468,21 +491,19 @@ function MessageReactionGroup({ roomTimeline, mEvent }) {
|
|||
|
||||
return (
|
||||
<div className="message__reactions text text-b3 noselect">
|
||||
{
|
||||
Object.keys(reactions).map((key) => (
|
||||
<MessageReaction
|
||||
key={key}
|
||||
reaction={key}
|
||||
shortcode={reactions[key].shortcode}
|
||||
count={reactions[key].count}
|
||||
users={reactions[key].users}
|
||||
isActive={reactions[key].isActive}
|
||||
onClick={() => {
|
||||
toggleEmoji(roomId, mEvent.getId(), key, reactions[key].shortcode, roomTimeline);
|
||||
}}
|
||||
/>
|
||||
))
|
||||
}
|
||||
{Object.keys(reactions).map((key) => (
|
||||
<MessageReaction
|
||||
key={key}
|
||||
reaction={key}
|
||||
shortcode={reactions[key].shortcode}
|
||||
count={reactions[key].count}
|
||||
users={reactions[key].users}
|
||||
isActive={reactions[key].isActive}
|
||||
onClick={() => {
|
||||
toggleEmoji(roomId, mEvent.getId(), key, reactions[key].shortcode, roomTimeline);
|
||||
}}
|
||||
/>
|
||||
))}
|
||||
{canSendReaction && (
|
||||
<IconButton
|
||||
onClick={(e) => {
|
||||
|
@ -503,11 +524,11 @@ MessageReactionGroup.propTypes = {
|
|||
|
||||
function isMedia(mE) {
|
||||
return (
|
||||
mE.getContent()?.msgtype === 'm.file'
|
||||
|| mE.getContent()?.msgtype === 'm.image'
|
||||
|| mE.getContent()?.msgtype === 'm.audio'
|
||||
|| mE.getContent()?.msgtype === 'm.video'
|
||||
|| mE.getType() === 'm.sticker'
|
||||
mE.getContent()?.msgtype === 'm.file' ||
|
||||
mE.getContent()?.msgtype === 'm.image' ||
|
||||
mE.getContent()?.msgtype === 'm.audio' ||
|
||||
mE.getContent()?.msgtype === 'm.video' ||
|
||||
mE.getType() === 'm.sticker'
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -523,9 +544,7 @@ function handleOpenViewSource(mEvent, roomTimeline) {
|
|||
openViewSource(editedMEvent !== undefined ? editedMEvent : mEvent);
|
||||
}
|
||||
|
||||
const MessageOptions = React.memo(({
|
||||
roomTimeline, mEvent, edit, reply,
|
||||
}) => {
|
||||
const MessageOptions = React.memo(({ roomTimeline, mEvent, edit, reply }) => {
|
||||
const { roomId, room } = roomTimeline;
|
||||
const mx = initMatrix.matrixClient;
|
||||
const senderId = mEvent.getSender();
|
||||
|
@ -544,19 +563,9 @@ const MessageOptions = React.memo(({
|
|||
tooltip="Add reaction"
|
||||
/>
|
||||
)}
|
||||
<IconButton
|
||||
onClick={() => reply()}
|
||||
src={ReplyArrowIC}
|
||||
size="extra-small"
|
||||
tooltip="Reply"
|
||||
/>
|
||||
{(senderId === mx.getUserId() && !isMedia(mEvent)) && (
|
||||
<IconButton
|
||||
onClick={() => edit(true)}
|
||||
src={PencilIC}
|
||||
size="extra-small"
|
||||
tooltip="Edit"
|
||||
/>
|
||||
<IconButton onClick={() => reply()} src={ReplyArrowIC} size="extra-small" tooltip="Reply" />
|
||||
{senderId === mx.getUserId() && !isMedia(mEvent) && (
|
||||
<IconButton onClick={() => edit(true)} src={PencilIC} size="extra-small" tooltip="Edit" />
|
||||
)}
|
||||
<ContextMenu
|
||||
content={() => (
|
||||
|
@ -568,10 +577,7 @@ const MessageOptions = React.memo(({
|
|||
>
|
||||
Read receipts
|
||||
</MenuItem>
|
||||
<MenuItem
|
||||
iconSrc={CmdIC}
|
||||
onClick={() => handleOpenViewSource(mEvent, roomTimeline)}
|
||||
>
|
||||
<MenuItem iconSrc={CmdIC} onClick={() => handleOpenViewSource(mEvent, roomTimeline)}>
|
||||
View source
|
||||
</MenuItem>
|
||||
{(canIRedact || senderId === mx.getUserId()) && (
|
||||
|
@ -585,7 +591,7 @@ const MessageOptions = React.memo(({
|
|||
'Delete message',
|
||||
'Are you sure that you want to delete this message?',
|
||||
'Delete',
|
||||
'danger',
|
||||
'danger'
|
||||
);
|
||||
if (!isConfirmed) return;
|
||||
redactEvent(roomId, mEvent.getId());
|
||||
|
@ -619,7 +625,8 @@ MessageOptions.propTypes = {
|
|||
function genMediaContent(mE) {
|
||||
const mx = initMatrix.matrixClient;
|
||||
const mContent = mE.getContent();
|
||||
if (!mContent || !mContent.body) return <span style={{ color: 'var(--bg-danger)' }}>Malformed event</span>;
|
||||
if (!mContent || !mContent.body)
|
||||
return <span style={{ color: 'var(--bg-danger)' }}>Malformed event</span>;
|
||||
|
||||
let mediaMXC = mContent?.url;
|
||||
const isEncryptedFile = typeof mediaMXC === 'undefined';
|
||||
|
@ -627,7 +634,8 @@ function genMediaContent(mE) {
|
|||
|
||||
let thumbnailMXC = mContent?.info?.thumbnail_url;
|
||||
|
||||
if (typeof mediaMXC === 'undefined' || mediaMXC === '') return <span style={{ color: 'var(--bg-danger)' }}>Malformed event</span>;
|
||||
if (typeof mediaMXC === 'undefined' || mediaMXC === '')
|
||||
return <span style={{ color: 'var(--bg-danger)' }}>Malformed event</span>;
|
||||
|
||||
let msgType = mE.getContent()?.msgtype;
|
||||
const safeMimetype = getBlobSafeMimeType(mContent.info?.mimetype);
|
||||
|
@ -717,13 +725,19 @@ function getEditedBody(editedMEvent) {
|
|||
}
|
||||
|
||||
function Message({
|
||||
mEvent, isBodyOnly, roomTimeline,
|
||||
focus, fullTime, isEdit, setEdit, cancelEdit,
|
||||
mEvent,
|
||||
isBodyOnly,
|
||||
roomTimeline,
|
||||
focus,
|
||||
fullTime,
|
||||
isEdit,
|
||||
setEdit,
|
||||
cancelEdit,
|
||||
}) {
|
||||
const roomId = mEvent.getRoomId();
|
||||
const { editedTimeline, reactionTimeline } = roomTimeline ?? {};
|
||||
|
||||
const className = ['message', (isBodyOnly ? 'message--body-only' : 'message--full')];
|
||||
const className = ['message', isBodyOnly ? 'message--body-only' : 'message--full'];
|
||||
if (focus) className.push('message--focus');
|
||||
const content = mEvent.getContent();
|
||||
const eventId = mEvent.getId();
|
||||
|
@ -731,7 +745,8 @@ function Message({
|
|||
const senderId = mEvent.getSender();
|
||||
let { body } = content;
|
||||
const username = mEvent.sender ? getUsernameOfRoomMember(mEvent.sender) : getUsername(senderId);
|
||||
const avatarSrc = mEvent.sender?.getAvatarUrl(initMatrix.matrixClient.baseUrl, 36, 36, 'crop') ?? null;
|
||||
const avatarSrc =
|
||||
mEvent.sender?.getAvatarUrl(initMatrix.matrixClient.baseUrl, 36, 36, 'crop') ?? null;
|
||||
let isCustomHTML = content.format === 'org.matrix.custom.html';
|
||||
let customHTML = isCustomHTML ? content.formatted_body : null;
|
||||
|
||||
|
@ -765,18 +780,16 @@ function Message({
|
|||
|
||||
return (
|
||||
<div className={className.join(' ')}>
|
||||
{
|
||||
isBodyOnly
|
||||
? <div className="message__avatar-container" />
|
||||
: (
|
||||
<MessageAvatar
|
||||
roomId={roomId}
|
||||
avatarSrc={avatarSrc}
|
||||
userId={senderId}
|
||||
username={username}
|
||||
/>
|
||||
)
|
||||
}
|
||||
{isBodyOnly ? (
|
||||
<div className="message__avatar-container" />
|
||||
) : (
|
||||
<MessageAvatar
|
||||
roomId={roomId}
|
||||
avatarSrc={avatarSrc}
|
||||
userId={senderId}
|
||||
username={username}
|
||||
/>
|
||||
)}
|
||||
<div className="message__main-container">
|
||||
{!isBodyOnly && (
|
||||
<MessageHeader
|
||||
|
@ -787,10 +800,7 @@ function Message({
|
|||
/>
|
||||
)}
|
||||
{roomTimeline && isReply && (
|
||||
<MessageReplyWrapper
|
||||
roomTimeline={roomTimeline}
|
||||
eventId={mEvent.replyEventId}
|
||||
/>
|
||||
<MessageReplyWrapper roomTimeline={roomTimeline} eventId={mEvent.replyEventId} />
|
||||
)}
|
||||
{!isEdit && (
|
||||
<MessageBody
|
||||
|
@ -803,9 +813,11 @@ function Message({
|
|||
)}
|
||||
{isEdit && (
|
||||
<MessageEdit
|
||||
body={(customHTML
|
||||
? html(customHTML, { kind: 'edit', onlyPlain: true }).plain
|
||||
: plain(body, { kind: 'edit', onlyPlain: true }).plain)}
|
||||
body={
|
||||
customHTML
|
||||
? html(customHTML, { kind: 'edit', onlyPlain: true }).plain
|
||||
: plain(body, { kind: 'edit', onlyPlain: true }).plain
|
||||
}
|
||||
onSave={(newBody, oldBody) => {
|
||||
if (newBody !== oldBody) {
|
||||
initMatrix.roomsInput.sendEditedMessage(roomId, mEvent, newBody);
|
||||
|
@ -815,16 +827,9 @@ function Message({
|
|||
onCancel={cancelEdit}
|
||||
/>
|
||||
)}
|
||||
{haveReactions && (
|
||||
<MessageReactionGroup roomTimeline={roomTimeline} mEvent={mEvent} />
|
||||
)}
|
||||
{haveReactions && <MessageReactionGroup roomTimeline={roomTimeline} mEvent={mEvent} />}
|
||||
{roomTimeline && !isEdit && (
|
||||
<MessageOptions
|
||||
roomTimeline={roomTimeline}
|
||||
mEvent={mEvent}
|
||||
edit={edit}
|
||||
reply={reply}
|
||||
/>
|
||||
<MessageOptions roomTimeline={roomTimeline} mEvent={mEvent} edit={edit} reply={reply} />
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -2,16 +2,12 @@ import React from 'react';
|
|||
import PropTypes from 'prop-types';
|
||||
import './PeopleSelector.scss';
|
||||
|
||||
import { twemojify } from '../../../util/twemojify';
|
||||
|
||||
import { blurOnBubbling } from '../../atoms/button/script';
|
||||
|
||||
import Text from '../../atoms/text/Text';
|
||||
import Avatar from '../../atoms/avatar/Avatar';
|
||||
|
||||
function PeopleSelector({
|
||||
avatarSrc, name, color, peopleRole, onClick,
|
||||
}) {
|
||||
function PeopleSelector({ avatarSrc, name, color, peopleRole, onClick }) {
|
||||
return (
|
||||
<div className="people-selector__container">
|
||||
<button
|
||||
|
@ -21,8 +17,14 @@ function PeopleSelector({
|
|||
type="button"
|
||||
>
|
||||
<Avatar imageSrc={avatarSrc} text={name} bgColor={color} size="extra-small" />
|
||||
<Text className="people-selector__name" variant="b1">{twemojify(name)}</Text>
|
||||
{peopleRole !== null && <Text className="people-selector__role" variant="b3">{peopleRole}</Text>}
|
||||
<Text className="people-selector__name" variant="b1">
|
||||
{name}
|
||||
</Text>
|
||||
{peopleRole !== null && (
|
||||
<Text className="people-selector__role" variant="b3">
|
||||
{peopleRole}
|
||||
</Text>
|
||||
)}
|
||||
</button>
|
||||
</div>
|
||||
);
|
||||
|
|
|
@ -2,8 +2,6 @@ import React from 'react';
|
|||
import PropTypes from 'prop-types';
|
||||
import './PopupWindow.scss';
|
||||
|
||||
import { twemojify } from '../../../util/twemojify';
|
||||
|
||||
import Text from '../../atoms/text/Text';
|
||||
import IconButton from '../../atoms/button/IconButton';
|
||||
import { MenuItem } from '../../atoms/context-menu/ContextMenu';
|
||||
|
@ -13,19 +11,11 @@ import RawModal from '../../atoms/modal/RawModal';
|
|||
|
||||
import ChevronLeftIC from '../../../../public/res/ic/outlined/chevron-left.svg';
|
||||
|
||||
function PWContentSelector({
|
||||
selected, variant, iconSrc,
|
||||
type, onClick, children,
|
||||
}) {
|
||||
function PWContentSelector({ selected, variant, iconSrc, type, onClick, children }) {
|
||||
const pwcsClass = selected ? ' pw-content-selector--selected' : '';
|
||||
return (
|
||||
<div className={`pw-content-selector${pwcsClass}`}>
|
||||
<MenuItem
|
||||
variant={variant}
|
||||
iconSrc={iconSrc}
|
||||
type={type}
|
||||
onClick={onClick}
|
||||
>
|
||||
<MenuItem variant={variant} iconSrc={iconSrc} type={type} onClick={onClick}>
|
||||
{children}
|
||||
</MenuItem>
|
||||
</div>
|
||||
|
@ -49,9 +39,16 @@ PWContentSelector.propTypes = {
|
|||
};
|
||||
|
||||
function PopupWindow({
|
||||
className, isOpen, title, contentTitle,
|
||||
drawer, drawerOptions, contentOptions,
|
||||
onAfterClose, onRequestClose, children,
|
||||
className,
|
||||
isOpen,
|
||||
title,
|
||||
contentTitle,
|
||||
drawer,
|
||||
drawerOptions,
|
||||
contentOptions,
|
||||
onAfterClose,
|
||||
onRequestClose,
|
||||
children,
|
||||
}) {
|
||||
const haveDrawer = drawer !== null;
|
||||
const cTitle = contentTitle !== null ? contentTitle : title;
|
||||
|
@ -69,21 +66,26 @@ function PopupWindow({
|
|||
{haveDrawer && (
|
||||
<div className="pw__drawer">
|
||||
<Header>
|
||||
<IconButton size="small" src={ChevronLeftIC} onClick={onRequestClose} tooltip="Back" />
|
||||
<IconButton
|
||||
size="small"
|
||||
src={ChevronLeftIC}
|
||||
onClick={onRequestClose}
|
||||
tooltip="Back"
|
||||
/>
|
||||
<TitleWrapper>
|
||||
{
|
||||
typeof title === 'string'
|
||||
? <Text variant="s1" weight="medium" primary>{twemojify(title)}</Text>
|
||||
: title
|
||||
}
|
||||
{typeof title === 'string' ? (
|
||||
<Text variant="s1" weight="medium" primary>
|
||||
{title}
|
||||
</Text>
|
||||
) : (
|
||||
title
|
||||
)}
|
||||
</TitleWrapper>
|
||||
{drawerOptions}
|
||||
</Header>
|
||||
<div className="pw__drawer__content__wrapper">
|
||||
<ScrollView invisible>
|
||||
<div className="pw__drawer__content">
|
||||
{drawer}
|
||||
</div>
|
||||
<div className="pw__drawer__content">{drawer}</div>
|
||||
</ScrollView>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -91,19 +93,19 @@ function PopupWindow({
|
|||
<div className="pw__content">
|
||||
<Header>
|
||||
<TitleWrapper>
|
||||
{
|
||||
typeof cTitle === 'string'
|
||||
? <Text variant="h2" weight="medium" primary>{twemojify(cTitle)}</Text>
|
||||
: cTitle
|
||||
}
|
||||
{typeof cTitle === 'string' ? (
|
||||
<Text variant="h2" weight="medium" primary>
|
||||
{cTitle}
|
||||
</Text>
|
||||
) : (
|
||||
cTitle
|
||||
)}
|
||||
</TitleWrapper>
|
||||
{contentOptions}
|
||||
</Header>
|
||||
<div className="pw__content__wrapper">
|
||||
<ScrollView autoHide>
|
||||
<div className="pw__content-container">
|
||||
{children}
|
||||
</div>
|
||||
<div className="pw__content-container">{children}</div>
|
||||
</ScrollView>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -1,8 +1,6 @@
|
|||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
|
||||
import { twemojify } from '../../../util/twemojify';
|
||||
|
||||
import initMatrix from '../../../client/initMatrix';
|
||||
import { openInviteUser } from '../../../client/action/navigation';
|
||||
import * as roomActions from '../../../client/action/room';
|
||||
|
@ -37,7 +35,7 @@ function RoomOptions({ roomId, afterOptionSelect }) {
|
|||
'Leave room',
|
||||
`Are you sure that you want to leave "${room.name}" room?`,
|
||||
'Leave',
|
||||
'danger',
|
||||
'danger'
|
||||
);
|
||||
if (!isConfirmed) return;
|
||||
roomActions.leave(roomId);
|
||||
|
@ -45,16 +43,16 @@ function RoomOptions({ roomId, afterOptionSelect }) {
|
|||
|
||||
return (
|
||||
<div style={{ maxWidth: '256px' }}>
|
||||
<MenuHeader>{twemojify(`Options for ${initMatrix.matrixClient.getRoom(roomId)?.name}`)}</MenuHeader>
|
||||
<MenuItem iconSrc={TickMarkIC} onClick={handleMarkAsRead}>Mark as read</MenuItem>
|
||||
<MenuItem
|
||||
iconSrc={AddUserIC}
|
||||
onClick={handleInviteClick}
|
||||
disabled={!canInvite}
|
||||
>
|
||||
<MenuHeader>{`Options for ${initMatrix.matrixClient.getRoom(roomId)?.name}`}</MenuHeader>
|
||||
<MenuItem iconSrc={TickMarkIC} onClick={handleMarkAsRead}>
|
||||
Mark as read
|
||||
</MenuItem>
|
||||
<MenuItem iconSrc={AddUserIC} onClick={handleInviteClick} disabled={!canInvite}>
|
||||
Invite
|
||||
</MenuItem>
|
||||
<MenuItem iconSrc={LeaveArrowIC} variant="danger" onClick={handleLeaveClick}>Leave</MenuItem>
|
||||
<MenuItem iconSrc={LeaveArrowIC} variant="danger" onClick={handleLeaveClick}>
|
||||
Leave
|
||||
</MenuItem>
|
||||
<MenuHeader>Notification</MenuHeader>
|
||||
<RoomNotification roomId={roomId} />
|
||||
</div>
|
||||
|
|
|
@ -2,8 +2,6 @@ import React, { useState, useEffect } from 'react';
|
|||
import PropTypes from 'prop-types';
|
||||
import './RoomProfile.scss';
|
||||
|
||||
import { twemojify } from '../../../util/twemojify';
|
||||
|
||||
import initMatrix from '../../../client/initMatrix';
|
||||
import cons from '../../../client/state/cons';
|
||||
import colorMXID from '../../../util/colorMXID';
|
||||
|
@ -33,7 +31,9 @@ function RoomProfile({ roomId }) {
|
|||
const mx = initMatrix.matrixClient;
|
||||
const isDM = initMatrix.roomList.directs.has(roomId);
|
||||
let avatarSrc = mx.getRoom(roomId).getAvatarUrl(mx.baseUrl, 36, 36, 'crop');
|
||||
avatarSrc = isDM ? mx.getRoom(roomId).getAvatarFallbackMember()?.getAvatarUrl(mx.baseUrl, 36, 36, 'crop') : avatarSrc;
|
||||
avatarSrc = isDM
|
||||
? mx.getRoom(roomId).getAvatarFallbackMember()?.getAvatarUrl(mx.baseUrl, 36, 36, 'crop')
|
||||
: avatarSrc;
|
||||
const room = mx.getRoom(roomId);
|
||||
const { currentState } = room;
|
||||
const roomName = room.name;
|
||||
|
@ -122,7 +122,7 @@ function RoomProfile({ roomId }) {
|
|||
'Remove avatar',
|
||||
'Are you sure that you want to remove room avatar?',
|
||||
'Remove',
|
||||
'caution',
|
||||
'caution'
|
||||
);
|
||||
if (isConfirmed) {
|
||||
await mx.sendStateEvent(roomId, 'm.room.avatar', { url }, '');
|
||||
|
@ -132,15 +132,45 @@ function RoomProfile({ roomId }) {
|
|||
|
||||
const renderEditNameAndTopic = () => (
|
||||
<form className="room-profile__edit-form" onSubmit={handleOnSubmit}>
|
||||
{canChangeName && <Input value={roomName} name="room-name" disabled={status.type === cons.status.IN_FLIGHT} label="Name" />}
|
||||
{canChangeTopic && <Input value={roomTopic} name="room-topic" disabled={status.type === cons.status.IN_FLIGHT} minHeight={100} resizable label="Topic" />}
|
||||
{(!canChangeName || !canChangeTopic) && <Text variant="b3">{`You have permission to change ${room.isSpaceRoom() ? 'space' : 'room'} ${canChangeName ? 'name' : 'topic'} only.`}</Text>}
|
||||
{ status.type === cons.status.IN_FLIGHT && <Text variant="b2">{status.msg}</Text>}
|
||||
{ status.type === cons.status.SUCCESS && <Text style={{ color: 'var(--tc-positive-high)' }} variant="b2">{status.msg}</Text>}
|
||||
{ status.type === cons.status.ERROR && <Text style={{ color: 'var(--tc-danger-high)' }} variant="b2">{status.msg}</Text>}
|
||||
{ status.type !== cons.status.IN_FLIGHT && (
|
||||
{canChangeName && (
|
||||
<Input
|
||||
value={roomName}
|
||||
name="room-name"
|
||||
disabled={status.type === cons.status.IN_FLIGHT}
|
||||
label="Name"
|
||||
/>
|
||||
)}
|
||||
{canChangeTopic && (
|
||||
<Input
|
||||
value={roomTopic}
|
||||
name="room-topic"
|
||||
disabled={status.type === cons.status.IN_FLIGHT}
|
||||
minHeight={100}
|
||||
resizable
|
||||
label="Topic"
|
||||
/>
|
||||
)}
|
||||
{(!canChangeName || !canChangeTopic) && (
|
||||
<Text variant="b3">{`You have permission to change ${
|
||||
room.isSpaceRoom() ? 'space' : 'room'
|
||||
} ${canChangeName ? 'name' : 'topic'} only.`}</Text>
|
||||
)}
|
||||
{status.type === cons.status.IN_FLIGHT && <Text variant="b2">{status.msg}</Text>}
|
||||
{status.type === cons.status.SUCCESS && (
|
||||
<Text style={{ color: 'var(--tc-positive-high)' }} variant="b2">
|
||||
{status.msg}
|
||||
</Text>
|
||||
)}
|
||||
{status.type === cons.status.ERROR && (
|
||||
<Text style={{ color: 'var(--tc-danger-high)' }} variant="b2">
|
||||
{status.msg}
|
||||
</Text>
|
||||
)}
|
||||
{status.type !== cons.status.IN_FLIGHT && (
|
||||
<div>
|
||||
<Button type="submit" variant="primary">Save</Button>
|
||||
<Button type="submit" variant="primary">
|
||||
Save
|
||||
</Button>
|
||||
<Button onClick={handleCancelEditing}>Cancel</Button>
|
||||
</div>
|
||||
)}
|
||||
|
@ -148,10 +178,15 @@ function RoomProfile({ roomId }) {
|
|||
);
|
||||
|
||||
const renderNameAndTopic = () => (
|
||||
<div className="room-profile__display" style={{ marginBottom: avatarSrc && canChangeAvatar ? '24px' : '0' }}>
|
||||
<div
|
||||
className="room-profile__display"
|
||||
style={{ marginBottom: avatarSrc && canChangeAvatar ? '24px' : '0' }}
|
||||
>
|
||||
<div>
|
||||
<Text variant="h2" weight="medium" primary>{twemojify(roomName)}</Text>
|
||||
{ (canChangeName || canChangeTopic) && (
|
||||
<Text variant="h2" weight="medium" primary>
|
||||
{roomName}
|
||||
</Text>
|
||||
{(canChangeName || canChangeTopic) && (
|
||||
<IconButton
|
||||
src={PencilIC}
|
||||
size="extra-small"
|
||||
|
@ -161,15 +196,17 @@ function RoomProfile({ roomId }) {
|
|||
)}
|
||||
</div>
|
||||
<Text variant="b3">{room.getCanonicalAlias() || room.roomId}</Text>
|
||||
{roomTopic && <Text variant="b2">{twemojify(roomTopic, undefined, true)}</Text>}
|
||||
{roomTopic && <Text variant="b2">{roomTopic}</Text>}
|
||||
</div>
|
||||
);
|
||||
|
||||
return (
|
||||
<div className="room-profile">
|
||||
<div className="room-profile__content">
|
||||
{ !canChangeAvatar && <Avatar imageSrc={avatarSrc} text={roomName} bgColor={colorMXID(roomId)} size="large" />}
|
||||
{ canChangeAvatar && (
|
||||
{!canChangeAvatar && (
|
||||
<Avatar imageSrc={avatarSrc} text={roomName} bgColor={colorMXID(roomId)} size="large" />
|
||||
)}
|
||||
{canChangeAvatar && (
|
||||
<ImageUpload
|
||||
text={roomName}
|
||||
bgColor={colorMXID(roomId)}
|
||||
|
|
|
@ -2,7 +2,6 @@ import React from 'react';
|
|||
import PropTypes from 'prop-types';
|
||||
import './RoomSelector.scss';
|
||||
|
||||
import { twemojify } from '../../../util/twemojify';
|
||||
import colorMXID from '../../../util/colorMXID';
|
||||
|
||||
import Text from '../../atoms/text/Text';
|
||||
|
@ -11,8 +10,13 @@ import NotificationBadge from '../../atoms/badge/NotificationBadge';
|
|||
import { blurOnBubbling } from '../../atoms/button/script';
|
||||
|
||||
function RoomSelectorWrapper({
|
||||
isSelected, isMuted, isUnread, onClick,
|
||||
content, options, onContextMenu,
|
||||
isSelected,
|
||||
isMuted,
|
||||
isUnread,
|
||||
onClick,
|
||||
content,
|
||||
options,
|
||||
onContextMenu,
|
||||
}) {
|
||||
const classes = ['room-selector'];
|
||||
if (isMuted) classes.push('room-selector--muted');
|
||||
|
@ -50,16 +54,26 @@ RoomSelectorWrapper.propTypes = {
|
|||
};
|
||||
|
||||
function RoomSelector({
|
||||
name, parentName, roomId, imageSrc, iconSrc,
|
||||
isSelected, isMuted, isUnread, notificationCount, isAlert,
|
||||
options, onClick, onContextMenu,
|
||||
name,
|
||||
parentName,
|
||||
roomId,
|
||||
imageSrc,
|
||||
iconSrc,
|
||||
isSelected,
|
||||
isMuted,
|
||||
isUnread,
|
||||
notificationCount,
|
||||
isAlert,
|
||||
options,
|
||||
onClick,
|
||||
onContextMenu,
|
||||
}) {
|
||||
return (
|
||||
<RoomSelectorWrapper
|
||||
isSelected={isSelected}
|
||||
isMuted={isMuted}
|
||||
isUnread={isUnread}
|
||||
content={(
|
||||
content={
|
||||
<>
|
||||
<Avatar
|
||||
text={name}
|
||||
|
@ -70,22 +84,22 @@ function RoomSelector({
|
|||
size="extra-small"
|
||||
/>
|
||||
<Text variant="b1" weight={isUnread ? 'medium' : 'normal'}>
|
||||
{twemojify(name)}
|
||||
{name}
|
||||
{parentName && (
|
||||
<Text variant="b3" span>
|
||||
{' — '}
|
||||
{twemojify(parentName)}
|
||||
{parentName}
|
||||
</Text>
|
||||
)}
|
||||
</Text>
|
||||
{ isUnread && (
|
||||
{isUnread && (
|
||||
<NotificationBadge
|
||||
alert={isAlert}
|
||||
content={notificationCount !== 0 ? notificationCount : null}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
}
|
||||
options={options}
|
||||
onClick={onClick}
|
||||
onContextMenu={onContextMenu}
|
||||
|
@ -110,10 +124,7 @@ RoomSelector.propTypes = {
|
|||
isSelected: PropTypes.bool,
|
||||
isMuted: PropTypes.bool,
|
||||
isUnread: PropTypes.bool.isRequired,
|
||||
notificationCount: PropTypes.oneOfType([
|
||||
PropTypes.string,
|
||||
PropTypes.number,
|
||||
]).isRequired,
|
||||
notificationCount: PropTypes.oneOfType([PropTypes.string, PropTypes.number]).isRequired,
|
||||
isAlert: PropTypes.bool.isRequired,
|
||||
options: PropTypes.node,
|
||||
onClick: PropTypes.func.isRequired,
|
||||
|
|
|
@ -2,46 +2,35 @@ import React from 'react';
|
|||
import PropTypes from 'prop-types';
|
||||
import './RoomTile.scss';
|
||||
|
||||
import { twemojify } from '../../../util/twemojify';
|
||||
|
||||
import colorMXID from '../../../util/colorMXID';
|
||||
|
||||
import Text from '../../atoms/text/Text';
|
||||
import Avatar from '../../atoms/avatar/Avatar';
|
||||
|
||||
function RoomTile({
|
||||
avatarSrc, name, id,
|
||||
inviterName, memberCount, desc, options,
|
||||
}) {
|
||||
function RoomTile({ avatarSrc, name, id, inviterName, memberCount, desc, options }) {
|
||||
return (
|
||||
<div className="room-tile">
|
||||
<div className="room-tile__avatar">
|
||||
<Avatar
|
||||
imageSrc={avatarSrc}
|
||||
bgColor={colorMXID(id)}
|
||||
text={name}
|
||||
/>
|
||||
<Avatar imageSrc={avatarSrc} bgColor={colorMXID(id)} text={name} />
|
||||
</div>
|
||||
<div className="room-tile__content">
|
||||
<Text variant="s1">{twemojify(name)}</Text>
|
||||
<Text variant="s1">{name}</Text>
|
||||
<Text variant="b3">
|
||||
{
|
||||
inviterName !== null
|
||||
? `Invited by ${inviterName} to ${id}${memberCount === null ? '' : ` • ${memberCount} members`}`
|
||||
: id + (memberCount === null ? '' : ` • ${memberCount} members`)
|
||||
}
|
||||
{inviterName !== null
|
||||
? `Invited by ${inviterName} to ${id}${
|
||||
memberCount === null ? '' : ` • ${memberCount} members`
|
||||
}`
|
||||
: id + (memberCount === null ? '' : ` • ${memberCount} members`)}
|
||||
</Text>
|
||||
{
|
||||
desc !== null && (typeof desc === 'string')
|
||||
? <Text className="room-tile__content__desc" variant="b2">{twemojify(desc, undefined, true)}</Text>
|
||||
: desc
|
||||
}
|
||||
{desc !== null && typeof desc === 'string' ? (
|
||||
<Text className="room-tile__content__desc" variant="b2">
|
||||
{desc}
|
||||
</Text>
|
||||
) : (
|
||||
desc
|
||||
)}
|
||||
</div>
|
||||
{ options !== null && (
|
||||
<div className="room-tile__options">
|
||||
{options}
|
||||
</div>
|
||||
)}
|
||||
{options !== null && <div className="room-tile__options">{options}</div>}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
@ -58,10 +47,7 @@ RoomTile.propTypes = {
|
|||
name: PropTypes.string.isRequired,
|
||||
id: PropTypes.string.isRequired,
|
||||
inviterName: PropTypes.string,
|
||||
memberCount: PropTypes.oneOfType([
|
||||
PropTypes.string,
|
||||
PropTypes.number,
|
||||
]),
|
||||
memberCount: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
|
||||
desc: PropTypes.node,
|
||||
options: PropTypes.node,
|
||||
};
|
||||
|
|
|
@ -2,38 +2,32 @@ import React from 'react';
|
|||
import PropTypes from 'prop-types';
|
||||
import './SidebarAvatar.scss';
|
||||
|
||||
import { twemojify } from '../../../util/twemojify';
|
||||
|
||||
import Text from '../../atoms/text/Text';
|
||||
import Tooltip from '../../atoms/tooltip/Tooltip';
|
||||
import { blurOnBubbling } from '../../atoms/button/script';
|
||||
|
||||
const SidebarAvatar = React.forwardRef(({
|
||||
className, tooltip, active, onClick,
|
||||
onContextMenu, avatar, notificationBadge,
|
||||
}, ref) => {
|
||||
const classes = ['sidebar-avatar'];
|
||||
if (active) classes.push('sidebar-avatar--active');
|
||||
if (className) classes.push(className);
|
||||
return (
|
||||
<Tooltip
|
||||
content={<Text variant="b1">{twemojify(tooltip)}</Text>}
|
||||
placement="right"
|
||||
>
|
||||
<button
|
||||
ref={ref}
|
||||
className={classes.join(' ')}
|
||||
type="button"
|
||||
onMouseUp={(e) => blurOnBubbling(e, '.sidebar-avatar')}
|
||||
onClick={onClick}
|
||||
onContextMenu={onContextMenu}
|
||||
>
|
||||
{avatar}
|
||||
{notificationBadge}
|
||||
</button>
|
||||
</Tooltip>
|
||||
);
|
||||
});
|
||||
const SidebarAvatar = React.forwardRef(
|
||||
({ className, tooltip, active, onClick, onContextMenu, avatar, notificationBadge }, ref) => {
|
||||
const classes = ['sidebar-avatar'];
|
||||
if (active) classes.push('sidebar-avatar--active');
|
||||
if (className) classes.push(className);
|
||||
return (
|
||||
<Tooltip content={<Text variant="b1">{tooltip}</Text>} placement="right">
|
||||
<button
|
||||
ref={ref}
|
||||
className={classes.join(' ')}
|
||||
type="button"
|
||||
onMouseUp={(e) => blurOnBubbling(e, '.sidebar-avatar')}
|
||||
onClick={onClick}
|
||||
onContextMenu={onContextMenu}
|
||||
>
|
||||
{avatar}
|
||||
{notificationBadge}
|
||||
</button>
|
||||
</Tooltip>
|
||||
);
|
||||
}
|
||||
);
|
||||
SidebarAvatar.defaultProps = {
|
||||
className: null,
|
||||
active: false,
|
||||
|
|
|
@ -2,8 +2,6 @@ import React, { useState, useEffect } from 'react';
|
|||
import PropTypes from 'prop-types';
|
||||
import './SpaceAddExisting.scss';
|
||||
|
||||
import { twemojify } from '../../../util/twemojify';
|
||||
|
||||
import initMatrix from '../../../client/initMatrix';
|
||||
import cons from '../../../client/state/cons';
|
||||
import navigation from '../../../client/state/navigation';
|
||||
|
@ -33,14 +31,12 @@ function SpaceAddExistingContent({ roomId }) {
|
|||
const [selected, setSelected] = useState([]);
|
||||
const [searchIds, setSearchIds] = useState(null);
|
||||
const mx = initMatrix.matrixClient;
|
||||
const {
|
||||
spaces, rooms, directs, roomIdToParents,
|
||||
} = initMatrix.roomList;
|
||||
const { spaces, rooms, directs, roomIdToParents } = initMatrix.roomList;
|
||||
|
||||
useEffect(() => {
|
||||
const allIds = [...spaces, ...rooms, ...directs].filter((rId) => (
|
||||
rId !== roomId && !roomIdToParents.get(rId)?.has(roomId)
|
||||
));
|
||||
const allIds = [...spaces, ...rooms, ...directs].filter(
|
||||
(rId) => rId !== roomId && !roomIdToParents.get(rId)?.has(roomId)
|
||||
);
|
||||
setAllRoomIds(allIds);
|
||||
}, [roomId]);
|
||||
|
||||
|
@ -68,20 +64,25 @@ function SpaceAddExistingContent({ roomId }) {
|
|||
via.push(getIdServer(rId));
|
||||
}
|
||||
|
||||
return mx.sendStateEvent(roomId, 'm.space.child', {
|
||||
auto_join: false,
|
||||
suggested: false,
|
||||
via,
|
||||
}, rId);
|
||||
return mx.sendStateEvent(
|
||||
roomId,
|
||||
'm.space.child',
|
||||
{
|
||||
auto_join: false,
|
||||
suggested: false,
|
||||
via,
|
||||
},
|
||||
rId
|
||||
);
|
||||
});
|
||||
|
||||
mountStore.setItem(true);
|
||||
await Promise.allSettled(promises);
|
||||
if (mountStore.getItem() !== true) return;
|
||||
|
||||
const allIds = [...spaces, ...rooms, ...directs].filter((rId) => (
|
||||
rId !== roomId && !roomIdToParents.get(rId)?.has(roomId) && !selected.includes(rId)
|
||||
));
|
||||
const allIds = [...spaces, ...rooms, ...directs].filter(
|
||||
(rId) => rId !== roomId && !roomIdToParents.get(rId)?.has(roomId) && !selected.includes(rId)
|
||||
);
|
||||
setAllRoomIds(allIds);
|
||||
setProcess(null);
|
||||
setSelected([]);
|
||||
|
@ -98,9 +99,7 @@ function SpaceAddExistingContent({ roomId }) {
|
|||
const searchedIds = allRoomIds.filter((rId) => {
|
||||
let name = mx.getRoom(rId)?.name;
|
||||
if (!name) return false;
|
||||
name = name.normalize('NFKC')
|
||||
.toLocaleLowerCase()
|
||||
.replace(/\s/g, '');
|
||||
name = name.normalize('NFKC').toLocaleLowerCase().replace(/\s/g, '');
|
||||
return name.includes(term);
|
||||
});
|
||||
setSearchIds(searchedIds);
|
||||
|
@ -114,66 +113,64 @@ function SpaceAddExistingContent({ roomId }) {
|
|||
|
||||
return (
|
||||
<>
|
||||
<form onSubmit={(ev) => { ev.preventDefault(); }}>
|
||||
<form
|
||||
onSubmit={(ev) => {
|
||||
ev.preventDefault();
|
||||
}}
|
||||
>
|
||||
<RawIcon size="small" src={SearchIC} />
|
||||
<Input
|
||||
name="searchInput"
|
||||
onChange={handleSearch}
|
||||
placeholder="Search room"
|
||||
autoFocus
|
||||
/>
|
||||
<Input name="searchInput" onChange={handleSearch} placeholder="Search room" autoFocus />
|
||||
<IconButton size="small" type="button" onClick={handleSearchClear} src={CrossIC} />
|
||||
</form>
|
||||
{searchIds?.length === 0 && <Text>No results found</Text>}
|
||||
{
|
||||
(searchIds || allRoomIds).map((rId) => {
|
||||
const room = mx.getRoom(rId);
|
||||
let imageSrc = room.getAvatarFallbackMember()?.getAvatarUrl(mx.baseUrl, 24, 24, 'crop') || null;
|
||||
if (imageSrc === null) imageSrc = room.getAvatarUrl(mx.baseUrl, 24, 24, 'crop') || null;
|
||||
{(searchIds || allRoomIds).map((rId) => {
|
||||
const room = mx.getRoom(rId);
|
||||
let imageSrc =
|
||||
room.getAvatarFallbackMember()?.getAvatarUrl(mx.baseUrl, 24, 24, 'crop') || null;
|
||||
if (imageSrc === null) imageSrc = room.getAvatarUrl(mx.baseUrl, 24, 24, 'crop') || null;
|
||||
|
||||
const parentSet = roomIdToParents.get(rId);
|
||||
const parentNames = parentSet
|
||||
? [...parentSet].map((parentId) => mx.getRoom(parentId).name)
|
||||
: undefined;
|
||||
const parents = parentNames ? parentNames.join(', ') : null;
|
||||
const parentSet = roomIdToParents.get(rId);
|
||||
const parentNames = parentSet
|
||||
? [...parentSet].map((parentId) => mx.getRoom(parentId).name)
|
||||
: undefined;
|
||||
const parents = parentNames ? parentNames.join(', ') : null;
|
||||
|
||||
const handleSelect = () => toggleSelection(rId);
|
||||
const handleSelect = () => toggleSelection(rId);
|
||||
|
||||
return (
|
||||
<RoomSelector
|
||||
key={rId}
|
||||
name={room.name}
|
||||
parentName={parents}
|
||||
roomId={rId}
|
||||
imageSrc={directs.has(rId) ? imageSrc : null}
|
||||
iconSrc={
|
||||
directs.has(rId)
|
||||
? null
|
||||
: joinRuleToIconSrc(room.getJoinRule(), room.isSpaceRoom())
|
||||
}
|
||||
isUnread={false}
|
||||
notificationCount={0}
|
||||
isAlert={false}
|
||||
onClick={handleSelect}
|
||||
options={(
|
||||
<Checkbox
|
||||
isActive={selected.includes(rId)}
|
||||
variant="positive"
|
||||
onToggle={handleSelect}
|
||||
tabIndex={-1}
|
||||
disabled={process !== null}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
);
|
||||
})
|
||||
}
|
||||
return (
|
||||
<RoomSelector
|
||||
key={rId}
|
||||
name={room.name}
|
||||
parentName={parents}
|
||||
roomId={rId}
|
||||
imageSrc={directs.has(rId) ? imageSrc : null}
|
||||
iconSrc={
|
||||
directs.has(rId) ? null : joinRuleToIconSrc(room.getJoinRule(), room.isSpaceRoom())
|
||||
}
|
||||
isUnread={false}
|
||||
notificationCount={0}
|
||||
isAlert={false}
|
||||
onClick={handleSelect}
|
||||
options={
|
||||
<Checkbox
|
||||
isActive={selected.includes(rId)}
|
||||
variant="positive"
|
||||
onToggle={handleSelect}
|
||||
tabIndex={-1}
|
||||
disabled={process !== null}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
{selected.length !== 0 && (
|
||||
<div className="space-add-existing__footer">
|
||||
{process && <Spinner size="small" />}
|
||||
<Text weight="medium">{process || `${selected.length} item selected`}</Text>
|
||||
{ !process && (
|
||||
<Button onClick={handleAdd} variant="primary">Add</Button>
|
||||
{!process && (
|
||||
<Button onClick={handleAdd} variant="primary">
|
||||
Add
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
|
@ -209,20 +206,16 @@ function SpaceAddExisting() {
|
|||
<Dialog
|
||||
isOpen={roomId !== null}
|
||||
className="space-add-existing"
|
||||
title={(
|
||||
title={
|
||||
<Text variant="s1" weight="medium" primary>
|
||||
{roomId && twemojify(room.name)}
|
||||
{roomId && room.name}
|
||||
<span style={{ color: 'var(--tc-surface-low)' }}> — add existing rooms</span>
|
||||
</Text>
|
||||
)}
|
||||
}
|
||||
contentOptions={<IconButton src={CrossIC} onClick={requestClose} tooltip="Close" />}
|
||||
onRequestClose={requestClose}
|
||||
>
|
||||
{
|
||||
roomId
|
||||
? <SpaceAddExistingContent roomId={roomId} />
|
||||
: <div />
|
||||
}
|
||||
{roomId ? <SpaceAddExistingContent roomId={roomId} /> : <div />}
|
||||
</Dialog>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -1,10 +1,12 @@
|
|||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
|
||||
import { twemojify } from '../../../util/twemojify';
|
||||
|
||||
import initMatrix from '../../../client/initMatrix';
|
||||
import { openSpaceSettings, openSpaceManage, openInviteUser } from '../../../client/action/navigation';
|
||||
import {
|
||||
openSpaceSettings,
|
||||
openSpaceManage,
|
||||
openInviteUser,
|
||||
} from '../../../client/action/navigation';
|
||||
import { markAsRead } from '../../../client/action/notifications';
|
||||
import { leave } from '../../../client/action/room';
|
||||
import {
|
||||
|
@ -74,7 +76,7 @@ function SpaceOptions({ roomId, afterOptionSelect }) {
|
|||
'Leave space',
|
||||
`Are you sure that you want to leave "${room.name}" space?`,
|
||||
'Leave',
|
||||
'danger',
|
||||
'danger'
|
||||
);
|
||||
if (!isConfirmed) return;
|
||||
leave(roomId);
|
||||
|
@ -82,34 +84,29 @@ function SpaceOptions({ roomId, afterOptionSelect }) {
|
|||
|
||||
return (
|
||||
<div style={{ maxWidth: 'calc(var(--navigation-drawer-width) - var(--sp-normal))' }}>
|
||||
<MenuHeader>{twemojify(`Options for ${initMatrix.matrixClient.getRoom(roomId)?.name}`)}</MenuHeader>
|
||||
<MenuItem iconSrc={TickMarkIC} onClick={handleMarkAsRead}>Mark as read</MenuItem>
|
||||
<MenuHeader>{`Options for ${initMatrix.matrixClient.getRoom(roomId)?.name}`}</MenuHeader>
|
||||
<MenuItem iconSrc={TickMarkIC} onClick={handleMarkAsRead}>
|
||||
Mark as read
|
||||
</MenuItem>
|
||||
<MenuItem
|
||||
onClick={handleCategorizeClick}
|
||||
iconSrc={isCategorized ? CategoryFilledIC : CategoryIC}
|
||||
>
|
||||
{isCategorized ? 'Uncategorize subspaces' : 'Categorize subspaces'}
|
||||
</MenuItem>
|
||||
<MenuItem
|
||||
onClick={handlePinClick}
|
||||
iconSrc={isPinned ? PinFilledIC : PinIC}
|
||||
>
|
||||
<MenuItem onClick={handlePinClick} iconSrc={isPinned ? PinFilledIC : PinIC}>
|
||||
{isPinned ? 'Unpin from sidebar' : 'Pin to sidebar'}
|
||||
</MenuItem>
|
||||
<MenuItem
|
||||
iconSrc={AddUserIC}
|
||||
onClick={handleInviteClick}
|
||||
disabled={!canInvite}
|
||||
>
|
||||
<MenuItem iconSrc={AddUserIC} onClick={handleInviteClick} disabled={!canInvite}>
|
||||
Invite
|
||||
</MenuItem>
|
||||
<MenuItem onClick={handleManageRoom} iconSrc={HashSearchIC}>Manage rooms</MenuItem>
|
||||
<MenuItem onClick={handleSettingsClick} iconSrc={SettingsIC}>Settings</MenuItem>
|
||||
<MenuItem
|
||||
variant="danger"
|
||||
onClick={handleLeaveClick}
|
||||
iconSrc={LeaveArrowIC}
|
||||
>
|
||||
<MenuItem onClick={handleManageRoom} iconSrc={HashSearchIC}>
|
||||
Manage rooms
|
||||
</MenuItem>
|
||||
<MenuItem onClick={handleSettingsClick} iconSrc={SettingsIC}>
|
||||
Settings
|
||||
</MenuItem>
|
||||
<MenuItem variant="danger" onClick={handleLeaveClick} iconSrc={LeaveArrowIC}>
|
||||
Leave
|
||||
</MenuItem>
|
||||
</div>
|
||||
|
|
Loading…
Reference in a new issue