remove twemoji

This commit is contained in:
array-in-a-matrix 2023-03-21 15:03:11 -04:00
parent 042e4f5a03
commit ced8514e1b
10 changed files with 278 additions and 358 deletions

19
package-lock.json generated
View file

@ -36,8 +36,7 @@
"react-google-recaptcha": "2.1.0",
"react-modal": "3.16.1",
"sanitize-html": "2.8.0",
"tippy.js": "6.3.7",
"twemoji": "14.0.2"
"tippy.js": "6.3.7"
},
"devDependencies": {
"@esbuild-plugins/node-globals-polyfill": "0.2.3",
@ -5005,22 +5004,6 @@
"typescript": ">=2.8.0 || >= 3.2.0-dev || >= 3.3.0-dev || >= 3.4.0-dev || >= 3.5.0-dev || >= 3.6.0-dev || >= 3.6.0-beta || >= 3.7.0-dev || >= 3.7.0-beta"
}
},
"node_modules/twemoji": {
"version": "14.0.2",
"resolved": "https://registry.npmjs.org/twemoji/-/twemoji-14.0.2.tgz",
"integrity": "sha512-BzOoXIe1QVdmsUmZ54xbEH+8AgtOKUiG53zO5vVP2iUu6h5u9lN15NcuS6te4OY96qx0H7JK9vjjl9WQbkTRuA==",
"dependencies": {
"fs-extra": "^8.0.1",
"jsonfile": "^5.0.0",
"twemoji-parser": "14.0.0",
"universalify": "^0.1.2"
}
},
"node_modules/twemoji-parser": {
"version": "14.0.0",
"resolved": "https://registry.npmjs.org/twemoji-parser/-/twemoji-parser-14.0.0.tgz",
"integrity": "sha512-9DUOTGLOWs0pFWnh1p6NF+C3CkQ96PWmEFwhOVmT3WbecRC+68AIqpsnJXygfkFcp4aXbOp8Dwbhh/HQgvoRxA=="
},
"node_modules/type-check": {
"version": "0.4.0",
"resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz",

View file

@ -46,8 +46,7 @@
"react-google-recaptcha": "2.1.0",
"react-modal": "3.16.1",
"sanitize-html": "2.8.0",
"tippy.js": "6.3.7",
"twemoji": "14.0.2"
"tippy.js": "6.3.7"
},
"devDependencies": {
"@esbuild-plugins/node-globals-polyfill": "0.2.3",

View file

@ -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,
};

View file

@ -8,7 +8,7 @@
display: flex;
max-width: 90vw;
max-height: 90vh;
&__content {
@extend .cp-fx__item-one;
@extend .cp-fx__column;
@ -28,27 +28,20 @@
@include dir.side(border, none, 1px solid var(--bg-surface-border));
position: relative;
& .ic-btn-surface {
opacity: 0.8;
}
}
&__nav-custom,
&__nav-twemoji {
&__nav-custom {
@extend .cp-fx__column;
}
&__nav-twemoji {
background-color: var(--bg-surface);
position: sticky;
bottom: -70%;
z-index: 999;
}
}
.emoji-board__content__search {
padding: var(--sp-extra-tight);
position: relative;
& .ic-raw {
position: absolute;
@include dir.prop(left, var(--sp-normal), unset);
@ -101,7 +94,7 @@
--emoji-padding: 6px;
position: relative;
margin-bottom: var(--sp-normal);
&__header {
position: sticky;
top: 0;
@ -134,4 +127,4 @@
border-radius: var(--bo-radius);
}
}
}
}

View file

@ -3,9 +3,6 @@ import React, { useState, useEffect } from 'react';
import PropTypes from 'prop-types';
import './RoomViewCmdBar.scss';
import parse from 'html-react-parser';
import twemoji from 'twemoji';
import { twemojify, TWEMOJI_BASE_URL } from '../../../util/twemojify';
import initMatrix from '../../../client/initMatrix';
import { getEmojiForCompletion } from '../emoji-board/custom-emoji';
@ -51,19 +48,6 @@ function renderSuggestions({ prefix, option, suggestions }, fireCmd) {
function renderEmojiSuggestion(emPrefix, emos) {
const mx = initMatrix.matrixClient;
// Renders a small Twemoji
function renderTwemoji(emoji) {
return parse(
twemoji.parse(emoji.unicode, {
attributes: () => ({
unicode: emoji.unicode,
shortcodes: emoji.shortcodes?.toString(),
}),
base: TWEMOJI_BASE_URL,
})
);
}
// Render a custom emoji
function renderCustomEmoji(emoji) {
return (
@ -76,12 +60,9 @@ function renderSuggestions({ prefix, option, suggestions }, fireCmd) {
);
}
// Dynamically render either a custom emoji or twemoji based on what the input is
// Dynamically render either a custom emoji
function renderEmoji(emoji) {
if (emoji.mxc) {
return renderCustomEmoji(emoji);
}
return renderTwemoji(emoji);
return renderCustomEmoji(emoji);
}
return emos.map((emoji) => (
@ -111,7 +92,7 @@ function renderSuggestions({ prefix, option, suggestions }, fireCmd) {
});
}}
>
<Text variant="b2">{twemojify(member.name)}</Text>
<Text variant="b2">{}</Text>
</CmdItem>
));
}

View file

@ -1,14 +1,11 @@
/* eslint-disable jsx-a11y/no-static-element-interactions */
/* eslint-disable jsx-a11y/click-events-have-key-events */
/* eslint-disable react/prop-types */
import React, {
useState, useEffect, useLayoutEffect, useCallback, useRef,
} from 'react';
import React, { useState, useEffect, useLayoutEffect, useCallback, useRef } from 'react';
import PropTypes from 'prop-types';
import './RoomViewContent.scss';
import dateFormat from 'dateformat';
import { twemojify } from '../../../util/twemojify';
import initMatrix from '../../../client/initMatrix';
import cons from '../../../client/state/cons';
@ -44,11 +41,7 @@ function loadingMsgPlaceholders(key, count = 2) {
return pl;
};
return (
<React.Fragment key={`placeholder-container${key}`}>
{genPlaceholders()}
</React.Fragment>
);
return <React.Fragment key={`placeholder-container${key}`}>{genPlaceholders()}</React.Fragment>;
}
function RoomIntroContainer({ event, timeline }) {
@ -59,28 +52,27 @@ function RoomIntroContainer({ event, timeline }) {
const roomTopic = room.currentState.getStateEvents('m.room.topic')[0]?.getContent().topic;
const isDM = roomList.directs.has(timeline.roomId);
let avatarSrc = room.getAvatarUrl(mx.baseUrl, 80, 80, 'crop');
avatarSrc = isDM ? room.getAvatarFallbackMember()?.getAvatarUrl(mx.baseUrl, 80, 80, 'crop') : avatarSrc;
avatarSrc = isDM
? room.getAvatarFallbackMember()?.getAvatarUrl(mx.baseUrl, 80, 80, 'crop')
: avatarSrc;
const heading = isDM ? room.name : `Welcome to ${room.name}`;
const topic = twemojify(roomTopic || '', undefined, true);
const nameJsx = twemojify(room.name);
const desc = isDM
? (
<>
This is the beginning of your direct message history with @
<b>{nameJsx}</b>
{'. '}
{topic}
</>
)
: (
<>
{'This is the beginning of the '}
<b>{nameJsx}</b>
{' room. '}
{topic}
</>
);
const topic = roomTopic || '';
const nameJsx = room.name;
const desc = isDM ? (
<>
This is the beginning of your direct message history with @<b>{nameJsx}</b>
{'. '}
{topic}
</>
) : (
<>
{'This is the beginning of the '}
<b>{nameJsx}</b>
{' room. '}
{topic}
</>
);
useEffect(() => {
const handleUpdate = () => nameForceUpdate();
@ -118,21 +110,13 @@ function handleOnClickCapture(e) {
}
}
function renderEvent(
roomTimeline,
mEvent,
prevMEvent,
isFocus,
isEdit,
setEdit,
cancelEdit,
) {
const isBodyOnly = (prevMEvent !== null
&& prevMEvent.getSender() === mEvent.getSender()
&& prevMEvent.getType() !== 'm.room.member'
&& prevMEvent.getType() !== 'm.room.create'
&& diffMinutes(mEvent.getDate(), prevMEvent.getDate()) <= MAX_MSG_DIFF_MINUTES
);
function renderEvent(roomTimeline, mEvent, prevMEvent, isFocus, isEdit, setEdit, cancelEdit) {
const isBodyOnly =
prevMEvent !== null &&
prevMEvent.getSender() === mEvent.getSender() &&
prevMEvent.getType() !== 'm.room.member' &&
prevMEvent.getType() !== 'm.room.create' &&
diffMinutes(mEvent.getDate(), prevMEvent.getDate()) <= MAX_MSG_DIFF_MINUTES;
const timestamp = mEvent.getTs();
if (mEvent.getType() === 'm.room.member') {
@ -221,7 +205,7 @@ function usePaginate(
readUptoEvtStore,
forceUpdateLimit,
timelineScrollRef,
eventLimitRef,
eventLimitRef
) {
const [info, setInfo] = useState(null);
@ -234,10 +218,12 @@ function usePaginate(
readUptoEvtStore.setItem(roomTimeline.findEventByIdInTimelineSet(readUpToId));
}
limit.paginate(backwards, PAG_LIMIT, roomTimeline.timeline.length);
setTimeout(() => setInfo({
backwards,
loaded,
}));
setTimeout(() =>
setInfo({
backwards,
loaded,
})
);
};
roomTimeline.on(cons.events.roomTimeline.PAGINATED, handlePaginatedFromServer);
return () => {
@ -283,17 +269,17 @@ function useHandleScroll(
readUptoEvtStore,
forceUpdateLimit,
timelineScrollRef,
eventLimitRef,
eventLimitRef
) {
const handleScroll = useCallback(() => {
const timelineScroll = timelineScrollRef.current;
const limit = eventLimitRef.current;
requestAnimationFrame(() => {
// emit event to toggle scrollToBottom button visibility
const isAtBottom = (
timelineScroll.bottom < 16 && !roomTimeline.canPaginateForward()
&& limit.length >= roomTimeline.timeline.length
);
const isAtBottom =
timelineScroll.bottom < 16 &&
!roomTimeline.canPaginateForward() &&
limit.length >= roomTimeline.timeline.length;
roomTimeline.emit(cons.events.roomTimeline.AT_BOTTOM, isAtBottom);
if (isAtBottom && readUptoEvtStore.getItem()) {
requestAnimationFrame(() => markAsRead(roomTimeline.roomId));
@ -363,7 +349,8 @@ function useEventArrive(roomTimeline, readUptoEvtStore, timelineScrollRef, event
setEvent(event);
return;
}
const isRelates = (event.getType() === 'm.reaction' || event.getRelation()?.rel_type === 'm.replace');
const isRelates =
event.getType() === 'm.reaction' || event.getRelation()?.rel_type === 'm.replace';
if (isRelates) {
setEvent(event);
return;
@ -409,7 +396,7 @@ function RoomViewContent({ eventId, roomTimeline }) {
readUptoEvtStore,
forceUpdateLimit,
timelineScrollRef,
eventLimitRef,
eventLimitRef
);
const [handleScroll, handleScrollToLive] = useHandleScroll(
roomTimeline,
@ -417,7 +404,7 @@ function RoomViewContent({ eventId, roomTimeline }) {
readUptoEvtStore,
forceUpdateLimit,
timelineScrollRef,
eventLimitRef,
eventLimitRef
);
const newEvent = useEventArrive(roomTimeline, readUptoEvtStore, timelineScrollRef, eventLimitRef);
@ -476,41 +463,46 @@ function RoomViewContent({ eventId, roomTimeline }) {
useEffect(() => {
const timelineScroll = timelineScrollRef.current;
if (!roomTimeline.initialized) return;
if (timelineScroll.bottom < 16 && !roomTimeline.canPaginateForward() && document.visibilityState === 'visible') {
if (
timelineScroll.bottom < 16 &&
!roomTimeline.canPaginateForward() &&
document.visibilityState === 'visible'
) {
timelineScroll.scrollToBottom();
} else {
timelineScroll.tryRestoringScroll();
}
}, [newEvent]);
const listenKeyboard = useCallback((event) => {
if (event.ctrlKey || event.altKey || event.metaKey) return;
if (event.key !== 'ArrowUp') return;
if (navigation.isRawModalVisible) return;
const listenKeyboard = useCallback(
(event) => {
if (event.ctrlKey || event.altKey || event.metaKey) return;
if (event.key !== 'ArrowUp') return;
if (navigation.isRawModalVisible) return;
if (document.activeElement.id !== 'message-textarea') return;
if (document.activeElement.value !== '') return;
if (document.activeElement.id !== 'message-textarea') return;
if (document.activeElement.value !== '') return;
const {
timeline: tl, activeTimeline, liveTimeline, matrixClient: mx,
} = roomTimeline;
const limit = eventLimitRef.current;
if (activeTimeline !== liveTimeline) return;
if (tl.length > limit.length) return;
const { timeline: tl, activeTimeline, liveTimeline, matrixClient: mx } = roomTimeline;
const limit = eventLimitRef.current;
if (activeTimeline !== liveTimeline) return;
if (tl.length > limit.length) return;
const mTypes = ['m.text'];
for (let i = tl.length - 1; i >= 0; i -= 1) {
const mE = tl[i];
if (
mE.getSender() === mx.getUserId()
&& mE.getType() === 'm.room.message'
&& mTypes.includes(mE.getContent()?.msgtype)
) {
setEditEventId(mE.getId());
return;
const mTypes = ['m.text'];
for (let i = tl.length - 1; i >= 0; i -= 1) {
const mE = tl[i];
if (
mE.getSender() === mx.getUserId() &&
mE.getType() === 'm.room.message' &&
mTypes.includes(mE.getContent()?.msgtype)
) {
setEditEventId(mE.getId());
return;
}
}
}
}, [roomTimeline]);
},
[roomTimeline]
);
useEffect(() => {
document.body.addEventListener('keydown', listenKeyboard);
@ -551,7 +543,7 @@ function RoomViewContent({ eventId, roomTimeline }) {
if (i === 0 && !roomTimeline.canPaginateBackward()) {
if (mEvent.getType() === 'm.room.create') {
tl.push(
<RoomIntroContainer key={mEvent.getId()} event={mEvent} timeline={roomTimeline} />,
<RoomIntroContainer key={mEvent.getId()} event={mEvent} timeline={roomTimeline} />
);
itemCountIndex += 1;
// eslint-disable-next-line no-continue
@ -564,9 +556,10 @@ function RoomViewContent({ eventId, roomTimeline }) {
let isNewEvent = false;
if (!unreadDivider) {
unreadDivider = (readUptoEvent
&& prevMEvent?.getTs() <= readUptoEvent.getTs()
&& readUptoEvent.getTs() < mEvent.getTs());
unreadDivider =
readUptoEvent &&
prevMEvent?.getTs() <= readUptoEvent.getTs() &&
readUptoEvent.getTs() < mEvent.getTs();
if (unreadDivider) {
isNewEvent = true;
tl.push(<Divider key={`new-${mEvent.getId()}`} variant="positive" text="New messages" />);
@ -576,7 +569,12 @@ function RoomViewContent({ eventId, roomTimeline }) {
}
const dayDivider = prevMEvent && !isInSameDay(mEvent.getDate(), prevMEvent.getDate());
if (dayDivider) {
tl.push(<Divider key={`divider-${mEvent.getId()}`} text={`${dateFormat(mEvent.getDate(), 'mmmm dd, yyyy')}`} />);
tl.push(
<Divider
key={`divider-${mEvent.getId()}`}
text={`${dateFormat(mEvent.getDate(), 'mmmm dd, yyyy')}`}
/>
);
itemCountIndex += 1;
}
@ -584,15 +582,17 @@ function RoomViewContent({ eventId, roomTimeline }) {
const isFocus = focusId === mEvent.getId();
if (isFocus) jumpToItemIndex = itemCountIndex;
tl.push(renderEvent(
roomTimeline,
mEvent,
isNewEvent ? null : prevMEvent,
isFocus,
editEventId === mEvent.getId(),
setEditEventId,
cancelEdit,
));
tl.push(
renderEvent(
roomTimeline,
mEvent,
isNewEvent ? null : prevMEvent,
isFocus,
editEventId === mEvent.getId(),
setEditEventId,
cancelEdit
)
);
itemCountIndex += 1;
}
if (roomTimeline.canPaginateForward() || limit.length < timeline.length) {
@ -606,7 +606,7 @@ function RoomViewContent({ eventId, roomTimeline }) {
<ScrollView onScroll={handleTimelineScroll} ref={timelineSVRef} autoHide>
<div className="room-view__content" onClick={handleOnClickCapture}>
<div className="timeline__wrapper">
{ roomTimeline.initialized ? renderTimeline() : loadingMsgPlaceholders('loading', 3) }
{roomTimeline.initialized ? renderTimeline() : loadingMsgPlaceholders('loading', 3)}
</div>
</div>
</ScrollView>

View file

@ -1,7 +1,5 @@
import React from 'react';
import { twemojify } from '../../../util/twemojify';
import initMatrix from '../../../client/initMatrix';
import { getUsername, getUsernameOfRoomMember } from '../../../util/matrixUtil';
@ -10,83 +8,83 @@ function getTimelineJSXMessages() {
join(user) {
return (
<>
<b>{twemojify(user)}</b>
<b>{user}</b>
{' joined the room'}
</>
);
},
leave(user, reason) {
const reasonMsg = (typeof reason === 'string') ? `: ${reason}` : '';
const reasonMsg = typeof reason === 'string' ? `: ${reason}` : '';
return (
<>
<b>{twemojify(user)}</b>
<b>{user}</b>
{' left the room'}
{twemojify(reasonMsg)}
{reasonMsg}
</>
);
},
invite(inviter, user) {
return (
<>
<b>{twemojify(inviter)}</b>
<b>{inviter}</b>
{' invited '}
<b>{twemojify(user)}</b>
<b>{user}</b>
</>
);
},
cancelInvite(inviter, user) {
return (
<>
<b>{twemojify(inviter)}</b>
<b>{inviter}</b>
{' canceled '}
<b>{twemojify(user)}</b>
{'\'s invite'}
<b>{user}</b>
&apos;s invite
</>
);
},
rejectInvite(user) {
return (
<>
<b>{twemojify(user)}</b>
<b>{user}</b>
{' rejected the invitation'}
</>
);
},
kick(actor, user, reason) {
const reasonMsg = (typeof reason === 'string') ? `: ${reason}` : '';
const reasonMsg = typeof reason === 'string' ? `: ${reason}` : '';
return (
<>
<b>{twemojify(actor)}</b>
<b>{actor}</b>
{' kicked '}
<b>{twemojify(user)}</b>
{twemojify(reasonMsg)}
<b>{user}</b>
{reasonMsg}
</>
);
},
ban(actor, user, reason) {
const reasonMsg = (typeof reason === 'string') ? `: ${reason}` : '';
const reasonMsg = typeof reason === 'string' ? `: ${reason}` : '';
return (
<>
<b>{twemojify(actor)}</b>
<b>{actor}</b>
{' banned '}
<b>{twemojify(user)}</b>
{twemojify(reasonMsg)}
<b>{user}</b>
{reasonMsg}
</>
);
},
unban(actor, user) {
return (
<>
<b>{twemojify(actor)}</b>
<b>{actor}</b>
{' unbanned '}
<b>{twemojify(user)}</b>
<b>{user}</b>
</>
);
},
avatarSets(user) {
return (
<>
<b>{twemojify(user)}</b>
<b>{user}</b>
{' set a avatar'}
</>
);
@ -94,7 +92,7 @@ function getTimelineJSXMessages() {
avatarChanged(user) {
return (
<>
<b>{twemojify(user)}</b>
<b>{user}</b>
{' changed their avatar'}
</>
);
@ -102,7 +100,7 @@ function getTimelineJSXMessages() {
avatarRemoved(user) {
return (
<>
<b>{twemojify(user)}</b>
<b>{user}</b>
{' removed their avatar'}
</>
);
@ -110,27 +108,27 @@ function getTimelineJSXMessages() {
nameSets(user, newName) {
return (
<>
<b>{twemojify(user)}</b>
<b>{user}</b>
{' set display name to '}
<b>{twemojify(newName)}</b>
<b>{newName}</b>
</>
);
},
nameChanged(user, newName) {
return (
<>
<b>{twemojify(user)}</b>
<b>{user}</b>
{' changed their display name to '}
<b>{twemojify(newName)}</b>
<b>{newName}</b>
</>
);
},
nameRemoved(user, lastName) {
return (
<>
<b>{twemojify(user)}</b>
<b>{user}</b>
{' removed their display name '}
<b>{twemojify(lastName)}</b>
<b>{lastName}</b>
</>
);
},
@ -143,28 +141,46 @@ function getUsersActionJsx(roomId, userIds, actionStr) {
if (room?.getMember(userId)) return getUsernameOfRoomMember(room.getMember(userId));
return getUsername(userId);
};
const getUserJSX = (userId) => <b>{twemojify(getUserDisplayName(userId))}</b>;
const getUserJSX = (userId) => <b>{getUserDisplayName(userId)}</b>;
if (!Array.isArray(userIds)) return 'Idle';
if (userIds.length === 0) return 'Idle';
const MAX_VISIBLE_COUNT = 3;
const u1Jsx = getUserJSX(userIds[0]);
// eslint-disable-next-line react/jsx-one-expression-per-line
if (userIds.length === 1) return <>{u1Jsx} is {actionStr}</>;
if (userIds.length === 1)
return (
<>
{u1Jsx} is {actionStr}
</>
);
const u2Jsx = getUserJSX(userIds[1]);
// eslint-disable-next-line react/jsx-one-expression-per-line
if (userIds.length === 2) return <>{u1Jsx} and {u2Jsx} are {actionStr}</>;
if (userIds.length === 2)
return (
<>
{u1Jsx} and {u2Jsx} are {actionStr}
</>
);
const u3Jsx = getUserJSX(userIds[2]);
if (userIds.length === 3) {
// eslint-disable-next-line react/jsx-one-expression-per-line
return <>{u1Jsx}, {u2Jsx} and {u3Jsx} are {actionStr}</>;
return (
<>
{u1Jsx}, {u2Jsx} and {u3Jsx} are {actionStr}
</>
);
}
const othersCount = userIds.length - MAX_VISIBLE_COUNT;
// eslint-disable-next-line react/jsx-one-expression-per-line
return <>{u1Jsx}, {u2Jsx}, {u3Jsx} and {othersCount} others are {actionStr}</>;
return (
<>
{u1Jsx}, {u2Jsx}, {u3Jsx} and {othersCount} others are {actionStr}
</>
);
}
function parseTimelineChange(mEvent) {
@ -180,18 +196,27 @@ function parseTimelineChange(mEvent) {
const userName = getUsername(mEvent.getStateKey());
switch (content.membership) {
case 'invite': return makeReturnObj('invite', tJSXMsgs.invite(senderName, userName));
case 'ban': return makeReturnObj('leave', tJSXMsgs.ban(senderName, userName, content.reason));
case 'invite':
return makeReturnObj('invite', tJSXMsgs.invite(senderName, userName));
case 'ban':
return makeReturnObj('leave', tJSXMsgs.ban(senderName, userName, content.reason));
case 'join':
if (prevContent.membership === 'join') {
if (content.displayname !== prevContent.displayname) {
if (typeof content.displayname === 'undefined') return makeReturnObj('avatar', tJSXMsgs.nameRemoved(sender, prevContent.displayname));
if (typeof prevContent.displayname === 'undefined') return makeReturnObj('avatar', tJSXMsgs.nameSets(sender, content.displayname));
return makeReturnObj('avatar', tJSXMsgs.nameChanged(prevContent.displayname, content.displayname));
if (typeof content.displayname === 'undefined')
return makeReturnObj('avatar', tJSXMsgs.nameRemoved(sender, prevContent.displayname));
if (typeof prevContent.displayname === 'undefined')
return makeReturnObj('avatar', tJSXMsgs.nameSets(sender, content.displayname));
return makeReturnObj(
'avatar',
tJSXMsgs.nameChanged(prevContent.displayname, content.displayname)
);
}
if (content.avatar_url !== prevContent.avatar_url) {
if (typeof content.avatar_url === 'undefined') return makeReturnObj('avatar', tJSXMsgs.avatarRemoved(content.displayname));
if (typeof prevContent.avatar_url === 'undefined') return makeReturnObj('avatar', tJSXMsgs.avatarSets(content.displayname));
if (typeof content.avatar_url === 'undefined')
return makeReturnObj('avatar', tJSXMsgs.avatarRemoved(content.displayname));
if (typeof prevContent.avatar_url === 'undefined')
return makeReturnObj('avatar', tJSXMsgs.avatarSets(content.displayname));
return makeReturnObj('avatar', tJSXMsgs.avatarChanged(content.displayname));
}
return null;
@ -200,23 +225,25 @@ function parseTimelineChange(mEvent) {
case 'leave':
if (sender === mEvent.getStateKey()) {
switch (prevContent.membership) {
case 'invite': return makeReturnObj('invite-cancel', tJSXMsgs.rejectInvite(senderName));
default: return makeReturnObj('leave', tJSXMsgs.leave(senderName, content.reason));
case 'invite':
return makeReturnObj('invite-cancel', tJSXMsgs.rejectInvite(senderName));
default:
return makeReturnObj('leave', tJSXMsgs.leave(senderName, content.reason));
}
}
switch (prevContent.membership) {
case 'invite': return makeReturnObj('invite-cancel', tJSXMsgs.cancelInvite(senderName, userName));
case 'ban': return makeReturnObj('other', tJSXMsgs.unban(senderName, userName));
case 'invite':
return makeReturnObj('invite-cancel', tJSXMsgs.cancelInvite(senderName, userName));
case 'ban':
return makeReturnObj('other', tJSXMsgs.unban(senderName, userName));
// sender is not target and made the target leave,
// if not from invite/ban then this is a kick
default: return makeReturnObj('leave', tJSXMsgs.kick(senderName, userName, content.reason));
default:
return makeReturnObj('leave', tJSXMsgs.kick(senderName, userName, content.reason));
}
default: return null;
default:
return null;
}
}
export {
getTimelineJSXMessages,
getUsersActionJsx,
parseTimelineChange,
};
export { getTimelineJSXMessages, getUsersActionJsx, parseTimelineChange };

View file

@ -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 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 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>
)
}
/>
);
}

View file

@ -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>

View file

@ -1,60 +0,0 @@
/* eslint-disable import/prefer-default-export */
import React, { lazy, Suspense } from 'react';
import linkifyHtml from 'linkify-html';
import parse from 'html-react-parser';
import twemoji from 'twemoji';
import { sanitizeText } from './sanitize';
export const TWEMOJI_BASE_URL = 'https://cdn.jsdelivr.net/gh/twitter/twemoji@14.0.2/assets/';
const Math = lazy(() => import('../app/atoms/math/Math'));
const mathOptions = {
replace: (node) => {
const maths = node.attribs?.['data-mx-maths'];
if (maths) {
return (
<Suspense fallback={<code>{maths}</code>}>
<Math
content={maths}
throwOnError={false}
errorColor="var(--tc-danger-normal)"
displayMode={node.name === 'div'}
/>
</Suspense>
);
}
return null;
},
};
/**
* @param {string} text - text to twemojify
* @param {object|undefined} opts - options for tweomoji.parse
* @param {boolean} [linkify=false] - convert links to html tags (default: false)
* @param {boolean} [sanitize=true] - sanitize html text (default: true)
* @param {boolean} [maths=false] - render maths (default: false)
* @returns React component
*/
export function twemojify(text, opts, linkify = false, sanitize = true, maths = false) {
if (typeof text !== 'string') return text;
let content = text;
const options = opts ?? { base: TWEMOJI_BASE_URL };
if (!options.base) {
options.base = TWEMOJI_BASE_URL;
}
if (sanitize) {
content = sanitizeText(content);
}
content = twemoji.parse(content, options);
if (linkify) {
content = linkifyHtml(content, {
target: '_blank',
rel: 'noreferrer noopener',
});
}
return parse(content, maths ? mathOptions : null);
}