mirror of
https://github.com/array-in-a-matrix/xinny.git
synced 2024-05-19 12:50:17 -04:00
remove twemoji
This commit is contained in:
parent
042e4f5a03
commit
ced8514e1b
19
package-lock.json
generated
19
package-lock.json
generated
|
@ -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",
|
||||
|
|
|
@ -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",
|
||||
|
|
|
@ -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,
|
||||
};
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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>
|
||||
));
|
||||
}
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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>
|
||||
'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 };
|
||||
|
|
|
@ -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>
|
||||
)
|
||||
}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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);
|
||||
}
|
Loading…
Reference in a new issue