added support for msg editing [#40]
This commit is contained in:
parent
fe3d2e0af4
commit
ac4c0ec1f6
4 changed files with 307 additions and 191 deletions
8
public/res/ic/outlined/pencil.svg
Normal file
8
public/res/ic/outlined/pencil.svg
Normal file
|
@ -0,0 +1,8 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<!-- Generator: Adobe Illustrator 18.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||||
|
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
||||||
|
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
|
||||||
|
viewBox="0 0 24 24" enable-background="new 0 0 24 24" xml:space="preserve">
|
||||||
|
<path d="M20.6,5.6l-2.2-2.2C18,3,17.5,2.8,17,2.8S16,3,15.6,3.4L3,16v5h5L20.6,8.4C21.4,7.6,21.4,6.4,20.6,5.6z M7.2,19H5v-2.2
|
||||||
|
l9.2-9.2l2.2,2.2L7.2,19z M15.6,6.2L17,4.8c0,0,0,0,0,0L19.2,7l-1.4,1.4L15.6,6.2z"/>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 652 B |
|
@ -44,6 +44,7 @@
|
||||||
justify-content: start;
|
justify-content: start;
|
||||||
border-radius: 0;
|
border-radius: 0;
|
||||||
box-shadow: none;
|
box-shadow: none;
|
||||||
|
white-space: nowrap;
|
||||||
|
|
||||||
.text:first-child {
|
.text:first-child {
|
||||||
margin: {
|
margin: {
|
||||||
|
|
|
@ -16,11 +16,13 @@ import { openEmojiBoard, openReadReceipts } from '../../../client/action/navigat
|
||||||
import Divider from '../../atoms/divider/Divider';
|
import Divider from '../../atoms/divider/Divider';
|
||||||
import Avatar from '../../atoms/avatar/Avatar';
|
import Avatar from '../../atoms/avatar/Avatar';
|
||||||
import IconButton from '../../atoms/button/IconButton';
|
import IconButton from '../../atoms/button/IconButton';
|
||||||
|
import ContextMenu, { MenuHeader, MenuItem, MenuBorder } from '../../atoms/context-menu/ContextMenu';
|
||||||
import {
|
import {
|
||||||
Message,
|
Message,
|
||||||
MessageHeader,
|
MessageHeader,
|
||||||
MessageReply,
|
MessageReply,
|
||||||
MessageContent,
|
MessageContent,
|
||||||
|
MessageEdit,
|
||||||
MessageReactionGroup,
|
MessageReactionGroup,
|
||||||
MessageReaction,
|
MessageReaction,
|
||||||
MessageOptions,
|
MessageOptions,
|
||||||
|
@ -32,6 +34,8 @@ import TimelineChange from '../../molecules/message/TimelineChange';
|
||||||
|
|
||||||
import ReplyArrowIC from '../../../../public/res/ic/outlined/reply-arrow.svg';
|
import ReplyArrowIC from '../../../../public/res/ic/outlined/reply-arrow.svg';
|
||||||
import EmojiAddIC from '../../../../public/res/ic/outlined/emoji-add.svg';
|
import EmojiAddIC from '../../../../public/res/ic/outlined/emoji-add.svg';
|
||||||
|
import VerticalMenuIC from '../../../../public/res/ic/outlined/vertical-menu.svg';
|
||||||
|
import PencilIC from '../../../../public/res/ic/outlined/pencil.svg';
|
||||||
import TickMarkIC from '../../../../public/res/ic/outlined/tick-mark.svg';
|
import TickMarkIC from '../../../../public/res/ic/outlined/tick-mark.svg';
|
||||||
import BinIC from '../../../../public/res/ic/outlined/bin.svg';
|
import BinIC from '../../../../public/res/ic/outlined/bin.svg';
|
||||||
|
|
||||||
|
@ -182,193 +186,6 @@ function pickEmoji(e, roomId, eventId, roomTimeline) {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function genMessage(roomId, prevMEvent, mEvent, roomTimeline, viewEvent) {
|
|
||||||
const mx = initMatrix.matrixClient;
|
|
||||||
const myPowerlevel = roomTimeline.room.getMember(mx.getUserId()).powerLevel;
|
|
||||||
const canIRedact = roomTimeline.room.currentState.hasSufficientPowerLevelFor('redact', myPowerlevel);
|
|
||||||
|
|
||||||
const isContentOnly = (
|
|
||||||
prevMEvent !== null
|
|
||||||
&& prevMEvent.getType() !== 'm.room.member'
|
|
||||||
&& diffMinutes(mEvent.getDate(), prevMEvent.getDate()) <= MAX_MSG_DIFF_MINUTES
|
|
||||||
&& prevMEvent.getSender() === mEvent.getSender()
|
|
||||||
);
|
|
||||||
|
|
||||||
let content = mEvent.getContent().body;
|
|
||||||
if (typeof content === 'undefined') return null;
|
|
||||||
let reply = null;
|
|
||||||
let reactions = null;
|
|
||||||
let isMarkdown = mEvent.getContent().format === 'org.matrix.custom.html';
|
|
||||||
const isReply = typeof mEvent.getWireContent()['m.relates_to']?.['m.in_reply_to'] !== 'undefined';
|
|
||||||
const isEdited = roomTimeline.editedTimeline.has(mEvent.getId());
|
|
||||||
const haveReactions = roomTimeline.reactionTimeline.has(mEvent.getId());
|
|
||||||
|
|
||||||
if (isReply) {
|
|
||||||
const parsedContent = parseReply(content);
|
|
||||||
if (parsedContent !== null) {
|
|
||||||
const c = roomTimeline.room.currentState;
|
|
||||||
const ID = parsedContent.userId || c.getUserIdsWithDisplayName(parsedContent.displayName)[0];
|
|
||||||
reply = {
|
|
||||||
color: colorMXID(ID || parsedContent.displayName),
|
|
||||||
to: parsedContent.displayName || getUsername(parsedContent.userId),
|
|
||||||
content: parsedContent.replyContent,
|
|
||||||
};
|
|
||||||
content = parsedContent.content;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (isEdited) {
|
|
||||||
const editedList = roomTimeline.editedTimeline.get(mEvent.getId());
|
|
||||||
const latestEdited = editedList[editedList.length - 1];
|
|
||||||
if (typeof latestEdited.getContent()['m.new_content'] === 'undefined') return null;
|
|
||||||
const latestEditBody = latestEdited.getContent()['m.new_content'].body;
|
|
||||||
const parsedEditedContent = parseReply(latestEditBody);
|
|
||||||
isMarkdown = latestEdited.getContent()['m.new_content'].format === 'org.matrix.custom.html';
|
|
||||||
if (parsedEditedContent === null) {
|
|
||||||
content = latestEditBody;
|
|
||||||
} else {
|
|
||||||
content = parsedEditedContent.content;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (haveReactions) {
|
|
||||||
reactions = [];
|
|
||||||
roomTimeline.reactionTimeline.get(mEvent.getId()).forEach((rEvent) => {
|
|
||||||
if (rEvent.getRelation() === null) return;
|
|
||||||
function alreadyHaveThisReaction(rE) {
|
|
||||||
for (let i = 0; i < reactions.length; i += 1) {
|
|
||||||
if (reactions[i].key === rE.getRelation().key) return true;
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
if (alreadyHaveThisReaction(rEvent)) {
|
|
||||||
for (let i = 0; i < reactions.length; i += 1) {
|
|
||||||
if (reactions[i].key === rEvent.getRelation().key) {
|
|
||||||
reactions[i].users.push(rEvent.getSender());
|
|
||||||
if (reactions[i].isActive !== true) {
|
|
||||||
const myUserId = initMatrix.matrixClient.getUserId();
|
|
||||||
reactions[i].isActive = rEvent.getSender() === myUserId;
|
|
||||||
if (reactions[i].isActive) reactions[i].id = rEvent.getId();
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
reactions.push({
|
|
||||||
id: rEvent.getId(),
|
|
||||||
key: rEvent.getRelation().key,
|
|
||||||
users: [rEvent.getSender()],
|
|
||||||
isActive: (rEvent.getSender() === initMatrix.matrixClient.getUserId()),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
const senderMXIDColor = colorMXID(mEvent.sender.userId);
|
|
||||||
const userAvatar = isContentOnly ? null : (
|
|
||||||
<Avatar
|
|
||||||
imageSrc={mEvent.sender.getAvatarUrl(initMatrix.matrixClient.baseUrl, 36, 36, 'crop')}
|
|
||||||
text={getUsername(mEvent.sender.userId).slice(0, 1)}
|
|
||||||
bgColor={senderMXIDColor}
|
|
||||||
size="small"
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
const userHeader = isContentOnly ? null : (
|
|
||||||
<MessageHeader
|
|
||||||
userId={mEvent.sender.userId}
|
|
||||||
name={getUsername(mEvent.sender.userId)}
|
|
||||||
color={senderMXIDColor}
|
|
||||||
time={`${dateFormat(mEvent.getDate(), 'hh:MM TT')}`}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
const userReply = reply === null ? null : (
|
|
||||||
<MessageReply
|
|
||||||
name={reply.to}
|
|
||||||
color={reply.color}
|
|
||||||
content={reply.content}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
const userContent = (
|
|
||||||
<MessageContent
|
|
||||||
isMarkdown={isMarkdown}
|
|
||||||
content={isMedia(mEvent) ? genMediaContent(mEvent) : content}
|
|
||||||
isEdited={isEdited}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
const userReactions = reactions === null ? null : (
|
|
||||||
<MessageReactionGroup>
|
|
||||||
{
|
|
||||||
reactions.map((reaction) => (
|
|
||||||
<MessageReaction
|
|
||||||
key={reaction.id}
|
|
||||||
reaction={reaction.key}
|
|
||||||
users={reaction.users}
|
|
||||||
isActive={reaction.isActive}
|
|
||||||
onClick={() => {
|
|
||||||
toggleEmoji(roomId, mEvent.getId(), reaction.key, roomTimeline);
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
))
|
|
||||||
}
|
|
||||||
<IconButton
|
|
||||||
onClick={(e) => pickEmoji(e, roomId, mEvent.getId(), roomTimeline)}
|
|
||||||
src={EmojiAddIC}
|
|
||||||
size="extra-small"
|
|
||||||
tooltip="Add reaction"
|
|
||||||
/>
|
|
||||||
</MessageReactionGroup>
|
|
||||||
);
|
|
||||||
const userOptions = (
|
|
||||||
<MessageOptions>
|
|
||||||
<IconButton
|
|
||||||
onClick={(e) => pickEmoji(e, roomId, mEvent.getId(), roomTimeline)}
|
|
||||||
src={EmojiAddIC}
|
|
||||||
size="extra-small"
|
|
||||||
tooltip="Add reaction"
|
|
||||||
/>
|
|
||||||
<IconButton
|
|
||||||
onClick={() => {
|
|
||||||
viewEvent.emit('reply_to', mEvent.getSender(), mEvent.getId(), isMedia(mEvent) ? mEvent.getContent().body : content);
|
|
||||||
}}
|
|
||||||
src={ReplyArrowIC}
|
|
||||||
size="extra-small"
|
|
||||||
tooltip="Reply"
|
|
||||||
/>
|
|
||||||
<IconButton
|
|
||||||
onClick={() => openReadReceipts(roomId, mEvent.getId())}
|
|
||||||
src={TickMarkIC}
|
|
||||||
size="extra-small"
|
|
||||||
tooltip="Read receipts"
|
|
||||||
/>
|
|
||||||
{(canIRedact || mEvent.getSender() === mx.getUserId()) && (
|
|
||||||
<IconButton
|
|
||||||
onClick={() => {
|
|
||||||
if (window.confirm('Are you sure you want to delete this event')) {
|
|
||||||
redactEvent(roomId, mEvent.getId());
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
src={BinIC}
|
|
||||||
size="extra-small"
|
|
||||||
tooltip="Delete"
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
</MessageOptions>
|
|
||||||
);
|
|
||||||
|
|
||||||
const myMessageEl = (
|
|
||||||
<Message
|
|
||||||
key={mEvent.getId()}
|
|
||||||
avatar={userAvatar}
|
|
||||||
header={userHeader}
|
|
||||||
reply={userReply}
|
|
||||||
content={userContent}
|
|
||||||
reactions={userReactions}
|
|
||||||
options={userOptions}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
return myMessageEl;
|
|
||||||
}
|
|
||||||
|
|
||||||
let wasAtBottom = true;
|
let wasAtBottom = true;
|
||||||
function ChannelViewContent({
|
function ChannelViewContent({
|
||||||
roomId, roomTimeline, timelineScroll, viewEvent,
|
roomId, roomTimeline, timelineScroll, viewEvent,
|
||||||
|
@ -376,6 +193,7 @@ function ChannelViewContent({
|
||||||
const [isReachedTimelineEnd, setIsReachedTimelineEnd] = useState(false);
|
const [isReachedTimelineEnd, setIsReachedTimelineEnd] = useState(false);
|
||||||
const [onStateUpdate, updateState] = useState(null);
|
const [onStateUpdate, updateState] = useState(null);
|
||||||
const [onPagination, setOnPagination] = useState(null);
|
const [onPagination, setOnPagination] = useState(null);
|
||||||
|
const [editEvent, setEditEvent] = useState(null);
|
||||||
const mx = initMatrix.matrixClient;
|
const mx = initMatrix.matrixClient;
|
||||||
|
|
||||||
function autoLoadTimeline() {
|
function autoLoadTimeline() {
|
||||||
|
@ -453,6 +271,250 @@ function ChannelViewContent({
|
||||||
}, [onStateUpdate]);
|
}, [onStateUpdate]);
|
||||||
|
|
||||||
let prevMEvent = null;
|
let prevMEvent = null;
|
||||||
|
function genMessage(mEvent) {
|
||||||
|
const myPowerlevel = roomTimeline.room.getMember(mx.getUserId()).powerLevel;
|
||||||
|
const canIRedact = roomTimeline.room.currentState.hasSufficientPowerLevelFor('redact', myPowerlevel);
|
||||||
|
|
||||||
|
const isContentOnly = (
|
||||||
|
prevMEvent !== null
|
||||||
|
&& prevMEvent.getType() !== 'm.room.member'
|
||||||
|
&& diffMinutes(mEvent.getDate(), prevMEvent.getDate()) <= MAX_MSG_DIFF_MINUTES
|
||||||
|
&& prevMEvent.getSender() === mEvent.getSender()
|
||||||
|
);
|
||||||
|
|
||||||
|
let content = mEvent.getContent().body;
|
||||||
|
if (typeof content === 'undefined') return null;
|
||||||
|
let reply = null;
|
||||||
|
let reactions = null;
|
||||||
|
let isMarkdown = mEvent.getContent().format === 'org.matrix.custom.html';
|
||||||
|
const isReply = typeof mEvent.getWireContent()['m.relates_to']?.['m.in_reply_to'] !== 'undefined';
|
||||||
|
const isEdited = roomTimeline.editedTimeline.has(mEvent.getId());
|
||||||
|
const haveReactions = roomTimeline.reactionTimeline.has(mEvent.getId());
|
||||||
|
|
||||||
|
if (isReply) {
|
||||||
|
const parsedContent = parseReply(content);
|
||||||
|
if (parsedContent !== null) {
|
||||||
|
const c = roomTimeline.room.currentState;
|
||||||
|
const displayNameToUserIds = c.getUserIdsWithDisplayName(parsedContent.displayName);
|
||||||
|
const ID = parsedContent.userId || displayNameToUserIds[0];
|
||||||
|
reply = {
|
||||||
|
color: colorMXID(ID || parsedContent.displayName),
|
||||||
|
to: parsedContent.displayName || getUsername(parsedContent.userId),
|
||||||
|
content: parsedContent.replyContent,
|
||||||
|
};
|
||||||
|
content = parsedContent.content;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isEdited) {
|
||||||
|
const editedList = roomTimeline.editedTimeline.get(mEvent.getId());
|
||||||
|
const latestEdited = editedList[editedList.length - 1];
|
||||||
|
if (typeof latestEdited.getContent()['m.new_content'] === 'undefined') return null;
|
||||||
|
const latestEditBody = latestEdited.getContent()['m.new_content'].body;
|
||||||
|
const parsedEditedContent = parseReply(latestEditBody);
|
||||||
|
isMarkdown = latestEdited.getContent()['m.new_content'].format === 'org.matrix.custom.html';
|
||||||
|
if (parsedEditedContent === null) {
|
||||||
|
content = latestEditBody;
|
||||||
|
} else {
|
||||||
|
content = parsedEditedContent.content;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (haveReactions) {
|
||||||
|
reactions = [];
|
||||||
|
roomTimeline.reactionTimeline.get(mEvent.getId()).forEach((rEvent) => {
|
||||||
|
if (rEvent.getRelation() === null) return;
|
||||||
|
function alreadyHaveThisReaction(rE) {
|
||||||
|
for (let i = 0; i < reactions.length; i += 1) {
|
||||||
|
if (reactions[i].key === rE.getRelation().key) return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (alreadyHaveThisReaction(rEvent)) {
|
||||||
|
for (let i = 0; i < reactions.length; i += 1) {
|
||||||
|
if (reactions[i].key === rEvent.getRelation().key) {
|
||||||
|
reactions[i].users.push(rEvent.getSender());
|
||||||
|
if (reactions[i].isActive !== true) {
|
||||||
|
const myUserId = initMatrix.matrixClient.getUserId();
|
||||||
|
reactions[i].isActive = rEvent.getSender() === myUserId;
|
||||||
|
if (reactions[i].isActive) reactions[i].id = rEvent.getId();
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
reactions.push({
|
||||||
|
id: rEvent.getId(),
|
||||||
|
key: rEvent.getRelation().key,
|
||||||
|
users: [rEvent.getSender()],
|
||||||
|
isActive: (rEvent.getSender() === initMatrix.matrixClient.getUserId()),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const senderMXIDColor = colorMXID(mEvent.sender.userId);
|
||||||
|
const userAvatar = isContentOnly ? null : (
|
||||||
|
<Avatar
|
||||||
|
imageSrc={mEvent.sender.getAvatarUrl(initMatrix.matrixClient.baseUrl, 36, 36, 'crop')}
|
||||||
|
text={getUsername(mEvent.sender.userId).slice(0, 1)}
|
||||||
|
bgColor={senderMXIDColor}
|
||||||
|
size="small"
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
const userHeader = isContentOnly ? null : (
|
||||||
|
<MessageHeader
|
||||||
|
userId={mEvent.sender.userId}
|
||||||
|
name={getUsername(mEvent.sender.userId)}
|
||||||
|
color={senderMXIDColor}
|
||||||
|
time={`${dateFormat(mEvent.getDate(), 'hh:MM TT')}`}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
const userReply = reply === null ? null : (
|
||||||
|
<MessageReply
|
||||||
|
name={reply.to}
|
||||||
|
color={reply.color}
|
||||||
|
content={reply.content}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
const userContent = (
|
||||||
|
<MessageContent
|
||||||
|
isMarkdown={isMarkdown}
|
||||||
|
content={isMedia(mEvent) ? genMediaContent(mEvent) : content}
|
||||||
|
isEdited={isEdited}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
const userReactions = reactions === null ? null : (
|
||||||
|
<MessageReactionGroup>
|
||||||
|
{
|
||||||
|
reactions.map((reaction) => (
|
||||||
|
<MessageReaction
|
||||||
|
key={reaction.id}
|
||||||
|
reaction={reaction.key}
|
||||||
|
users={reaction.users}
|
||||||
|
isActive={reaction.isActive}
|
||||||
|
onClick={() => {
|
||||||
|
toggleEmoji(roomId, mEvent.getId(), reaction.key, roomTimeline);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
))
|
||||||
|
}
|
||||||
|
<IconButton
|
||||||
|
onClick={(e) => pickEmoji(e, roomId, mEvent.getId(), roomTimeline)}
|
||||||
|
src={EmojiAddIC}
|
||||||
|
size="extra-small"
|
||||||
|
tooltip="Add reaction"
|
||||||
|
/>
|
||||||
|
</MessageReactionGroup>
|
||||||
|
);
|
||||||
|
const userOptions = (
|
||||||
|
<MessageOptions>
|
||||||
|
<IconButton
|
||||||
|
onClick={(e) => pickEmoji(e, roomId, mEvent.getId(), roomTimeline)}
|
||||||
|
src={EmojiAddIC}
|
||||||
|
size="extra-small"
|
||||||
|
tooltip="Add reaction"
|
||||||
|
/>
|
||||||
|
<IconButton
|
||||||
|
onClick={() => {
|
||||||
|
viewEvent.emit('reply_to', mEvent.getSender(), mEvent.getId(), isMedia(mEvent) ? mEvent.getContent().body : content);
|
||||||
|
}}
|
||||||
|
src={ReplyArrowIC}
|
||||||
|
size="extra-small"
|
||||||
|
tooltip="Reply"
|
||||||
|
/>
|
||||||
|
{(mEvent.getSender() === mx.getUserId() && !isMedia(mEvent)) && (
|
||||||
|
<IconButton
|
||||||
|
onClick={() => setEditEvent(mEvent)}
|
||||||
|
src={PencilIC}
|
||||||
|
size="extra-small"
|
||||||
|
tooltip="Edit"
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
<ContextMenu
|
||||||
|
content={() => (
|
||||||
|
<>
|
||||||
|
<MenuHeader>Options</MenuHeader>
|
||||||
|
<MenuItem
|
||||||
|
iconSrc={EmojiAddIC}
|
||||||
|
onClick={(e) => pickEmoji(e, roomId, mEvent.getId(), roomTimeline)}
|
||||||
|
>
|
||||||
|
Add reaciton
|
||||||
|
</MenuItem>
|
||||||
|
<MenuItem
|
||||||
|
iconSrc={ReplyArrowIC}
|
||||||
|
onClick={() => {
|
||||||
|
viewEvent.emit('reply_to', mEvent.getSender(), mEvent.getId(), isMedia(mEvent) ? mEvent.getContent().body : content);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Reply
|
||||||
|
</MenuItem>
|
||||||
|
{(mEvent.getSender() === mx.getUserId() && !isMedia(mEvent)) && (
|
||||||
|
<MenuItem iconSrc={PencilIC} onClick={() => setEditEvent(mEvent)}>Edit</MenuItem>
|
||||||
|
)}
|
||||||
|
<MenuItem
|
||||||
|
iconSrc={TickMarkIC}
|
||||||
|
onClick={() => openReadReceipts(roomId, mEvent.getId())}
|
||||||
|
>
|
||||||
|
Read receipts
|
||||||
|
</MenuItem>
|
||||||
|
{(canIRedact || mEvent.getSender() === mx.getUserId()) && (
|
||||||
|
<>
|
||||||
|
<MenuBorder />
|
||||||
|
<MenuItem
|
||||||
|
variant="danger"
|
||||||
|
iconSrc={BinIC}
|
||||||
|
onClick={() => {
|
||||||
|
if (window.confirm('Are you sure you want to delete this event')) {
|
||||||
|
redactEvent(roomId, mEvent.getId());
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Delete
|
||||||
|
</MenuItem>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
render={(toggleMenu) => (
|
||||||
|
<IconButton
|
||||||
|
onClick={toggleMenu}
|
||||||
|
src={VerticalMenuIC}
|
||||||
|
size="extra-small"
|
||||||
|
tooltip="Options"
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
</MessageOptions>
|
||||||
|
);
|
||||||
|
|
||||||
|
const isEditingEvent = editEvent?.getId() === mEvent.getId();
|
||||||
|
const myMessageEl = (
|
||||||
|
<Message
|
||||||
|
key={mEvent.getId()}
|
||||||
|
avatar={userAvatar}
|
||||||
|
header={userHeader}
|
||||||
|
reply={userReply}
|
||||||
|
content={editEvent !== null && isEditingEvent ? null : userContent}
|
||||||
|
editContent={editEvent !== null && isEditingEvent ? (
|
||||||
|
<MessageEdit
|
||||||
|
content={content}
|
||||||
|
onSave={(newBody) => {
|
||||||
|
if (newBody !== content) {
|
||||||
|
initMatrix.roomsInput.sendEditedMessage(roomId, mEvent, newBody);
|
||||||
|
}
|
||||||
|
setEditEvent(null);
|
||||||
|
}}
|
||||||
|
onCancel={() => setEditEvent(null)}
|
||||||
|
/>
|
||||||
|
) : null}
|
||||||
|
reactions={userReactions}
|
||||||
|
options={editEvent !== null && isEditingEvent ? null : userOptions}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
return myMessageEl;
|
||||||
|
}
|
||||||
|
|
||||||
function renderMessage(mEvent) {
|
function renderMessage(mEvent) {
|
||||||
if (mEvent.getType() === 'm.room.create') return genChannelIntro(mEvent, roomTimeline);
|
if (mEvent.getType() === 'm.room.create') return genChannelIntro(mEvent, roomTimeline);
|
||||||
if (
|
if (
|
||||||
|
@ -472,7 +534,7 @@ function ChannelViewContent({
|
||||||
}
|
}
|
||||||
|
|
||||||
if (mEvent.getType() !== 'm.room.member') {
|
if (mEvent.getType() !== 'm.room.member') {
|
||||||
const messageComp = genMessage(roomId, prevMEvent, mEvent, roomTimeline, viewEvent);
|
const messageComp = genMessage(mEvent);
|
||||||
prevMEvent = mEvent;
|
prevMEvent = mEvent;
|
||||||
return (
|
return (
|
||||||
<React.Fragment key={`box-${mEvent.getId()}`}>
|
<React.Fragment key={`box-${mEvent.getId()}`}>
|
||||||
|
|
|
@ -86,7 +86,10 @@ function getFormattedBody(markdown) {
|
||||||
extensions: [gfm()],
|
extensions: [gfm()],
|
||||||
htmlExtensions: [gfmHtml],
|
htmlExtensions: [gfmHtml],
|
||||||
});
|
});
|
||||||
return result;
|
const bodyParts = result.match(/^(<p>)(.*)(<\/p>)$/);
|
||||||
|
if (bodyParts === null) return result;
|
||||||
|
if (bodyParts[2].indexOf('</p>') >= 0) return result;
|
||||||
|
return bodyParts[2];
|
||||||
}
|
}
|
||||||
|
|
||||||
function getReplyFormattedBody(roomId, reply) {
|
function getReplyFormattedBody(roomId, reply) {
|
||||||
|
@ -212,8 +215,11 @@ class RoomsInput extends EventEmitter {
|
||||||
msgtype: 'm.text',
|
msgtype: 'm.text',
|
||||||
};
|
};
|
||||||
if (settings.isMarkdown) {
|
if (settings.isMarkdown) {
|
||||||
content.format = 'org.matrix.custom.html';
|
const formattedBody = getFormattedBody(input.message);
|
||||||
content.formatted_body = getFormattedBody(input.message);
|
if (formattedBody !== input.message) {
|
||||||
|
content.format = 'org.matrix.custom.html';
|
||||||
|
content.formatted_body = formattedBody;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if (typeof input.replyTo !== 'undefined') {
|
if (typeof input.replyTo !== 'undefined') {
|
||||||
content = bindReplyToContent(roomId, input.replyTo, content);
|
content = bindReplyToContent(roomId, input.replyTo, content);
|
||||||
|
@ -326,6 +332,45 @@ class RoomsInput extends EventEmitter {
|
||||||
}
|
}
|
||||||
return { url };
|
return { url };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async sendEditedMessage(roomId, mEvent, editedBody) {
|
||||||
|
const isReply = typeof mEvent.getWireContent()['m.relates_to']?.['m.in_reply_to'] !== 'undefined';
|
||||||
|
|
||||||
|
const content = {
|
||||||
|
body: ` * ${editedBody}`,
|
||||||
|
msgtype: 'm.text',
|
||||||
|
'm.new_content': {
|
||||||
|
body: editedBody,
|
||||||
|
msgtype: 'm.text',
|
||||||
|
},
|
||||||
|
'm.relates_to': {
|
||||||
|
event_id: mEvent.getId(),
|
||||||
|
rel_type: 'm.replace',
|
||||||
|
},
|
||||||
|
};
|
||||||
|
if (settings.isMarkdown) {
|
||||||
|
const formattedBody = getFormattedBody(editedBody);
|
||||||
|
if (formattedBody !== editedBody) {
|
||||||
|
content.formatted_body = ` * ${formattedBody}`;
|
||||||
|
content.format = 'org.matrix.custom.html';
|
||||||
|
content['m.new_content'].formatted_body = formattedBody;
|
||||||
|
content['m.new_content'].format = 'org.matrix.custom.html';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (isReply) {
|
||||||
|
const evBody = mEvent.getContent().body;
|
||||||
|
const replyHead = evBody.slice(0, evBody.indexOf('\n\n'));
|
||||||
|
const evFBody = mEvent.getContent().formatted_body;
|
||||||
|
const fReplyHead = evFBody.slice(0, evFBody.indexOf('</mx-reply>'));
|
||||||
|
|
||||||
|
content.format = 'org.matrix.custom.html';
|
||||||
|
content.formatted_body = `${fReplyHead}</mx-reply>${(content.formatted_body || content.body)}`;
|
||||||
|
|
||||||
|
content.body = `${replyHead}\n\n${content.body}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.matrixClient.sendMessage(roomId, content);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export default RoomsInput;
|
export default RoomsInput;
|
||||||
|
|
Loading…
Reference in a new issue