added reply support
This commit is contained in:
parent
109e2fa82d
commit
717ffe560f
9 changed files with 208 additions and 22 deletions
18
public/res/ic/outlined/bin.svg
Normal file
18
public/res/ic/outlined/bin.svg
Normal file
|
@ -0,0 +1,18 @@
|
||||||
|
<?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">
|
||||||
|
<g>
|
||||||
|
<g>
|
||||||
|
<g>
|
||||||
|
<rect x="9" y="8" width="2" height="8"/>
|
||||||
|
</g>
|
||||||
|
<g>
|
||||||
|
<rect x="13" y="8" width="2" height="8"/>
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
<path d="M21,3h-5l-1.4-1.4C14.2,1.2,13.7,1,13.2,1h-2.3c-0.5,0-1,0.2-1.4,0.6L8,3H3v2h2v14c0,1.1,0.9,2,2,2h10c1.1,0,2-0.9,2-2V5h2
|
||||||
|
V3z M17,19H7V5h10V19z"/>
|
||||||
|
</g>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 735 B |
13
public/res/ic/outlined/emoji-add.svg
Normal file
13
public/res/ic/outlined/emoji-add.svg
Normal file
|
@ -0,0 +1,13 @@
|
||||||
|
<?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">
|
||||||
|
<g>
|
||||||
|
<path d="M12,20c-4.4,0-8-3.6-8-8s3.6-8,8-8V2C6.5,2,2,6.5,2,12s4.5,10,10,10s10-4.5,10-10h-2C20,16.4,16.4,20,12,20z"/>
|
||||||
|
<circle cx="9.5" cy="8.5" r="1.5"/>
|
||||||
|
<circle cx="14.5" cy="8.5" r="1.5"/>
|
||||||
|
<path d="M6,12c0,3.3,2.7,6,6,6s6-2.7,6-6h-2c0,2.2-1.8,4-4,4s-4-1.8-4-4H6z"/>
|
||||||
|
<polygon points="20.8,3.3 20.8,0 19.3,0 19.3,3.3 16,3.3 16,4.8 19.3,4.8 19.3,8 20.8,8 20.8,4.8 24,4.8 24,3.3 "/>
|
||||||
|
</g>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 839 B |
|
@ -113,7 +113,7 @@ function MessageContent({ content, isMarkdown, isEdited }) {
|
||||||
<div className="text text-b1">
|
<div className="text text-b1">
|
||||||
{ isMarkdown ? genMarkdown(content) : linkifyContent(content) }
|
{ isMarkdown ? genMarkdown(content) : linkifyContent(content) }
|
||||||
</div>
|
</div>
|
||||||
{ isEdited && <Text className="message__edited" variant="b3">(edited)</Text>}
|
{ isEdited && <Text className="message__content-edited" variant="b3">(edited)</Text>}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -139,15 +139,19 @@ MessageReactionGroup.propTypes = {
|
||||||
};
|
};
|
||||||
|
|
||||||
function genReactionMsg(userIds, reaction) {
|
function genReactionMsg(userIds, reaction) {
|
||||||
let msg = '';
|
const genLessContText = (text) => <span style={{ opacity: '.6' }}>{text}</span>;
|
||||||
|
let msg = <></>;
|
||||||
userIds.forEach((userId, index) => {
|
userIds.forEach((userId, index) => {
|
||||||
if (index === 0) msg += getUsername(userId);
|
if (index === 0) msg = <>{getUsername(userId)}</>;
|
||||||
else if (index === userIds.length - 1) msg += ` and ${getUsername(userId)}`;
|
// eslint-disable-next-line react/jsx-one-expression-per-line
|
||||||
else msg += `, ${getUsername(userId)}`;
|
else if (index === userIds.length - 1) msg = <>{msg}{genLessContText(' and ')}{getUsername(userId)}</>;
|
||||||
|
// eslint-disable-next-line react/jsx-one-expression-per-line
|
||||||
|
else msg = <>{msg}{genLessContText(', ')}{getUsername(userId)}</>;
|
||||||
});
|
});
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
{`${msg} reacted with`}
|
{msg}
|
||||||
|
{genLessContText(' reacted with')}
|
||||||
{parse(twemoji.parse(reaction))}
|
{parse(twemoji.parse(reaction))}
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
|
@ -179,8 +183,19 @@ MessageReaction.propTypes = {
|
||||||
onClick: PropTypes.func.isRequired,
|
onClick: PropTypes.func.isRequired,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
function MessageOptions({ children }) {
|
||||||
|
return (
|
||||||
|
<div className="message__options">
|
||||||
|
{children}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
MessageOptions.propTypes = {
|
||||||
|
children: PropTypes.node.isRequired,
|
||||||
|
};
|
||||||
|
|
||||||
function Message({
|
function Message({
|
||||||
avatar, header, reply, content, reactions,
|
avatar, header, reply, content, reactions, options,
|
||||||
}) {
|
}) {
|
||||||
const msgClass = header === null ? ' message--content-only' : ' message--full';
|
const msgClass = header === null ? ' message--content-only' : ' message--full';
|
||||||
return (
|
return (
|
||||||
|
@ -193,6 +208,7 @@ function Message({
|
||||||
{reply !== null && reply}
|
{reply !== null && reply}
|
||||||
{content}
|
{content}
|
||||||
{reactions !== null && reactions}
|
{reactions !== null && reactions}
|
||||||
|
{options !== null && options}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
@ -202,6 +218,7 @@ Message.defaultProps = {
|
||||||
header: null,
|
header: null,
|
||||||
reply: null,
|
reply: null,
|
||||||
reactions: null,
|
reactions: null,
|
||||||
|
options: null,
|
||||||
};
|
};
|
||||||
Message.propTypes = {
|
Message.propTypes = {
|
||||||
avatar: PropTypes.node,
|
avatar: PropTypes.node,
|
||||||
|
@ -209,6 +226,7 @@ Message.propTypes = {
|
||||||
reply: PropTypes.node,
|
reply: PropTypes.node,
|
||||||
content: PropTypes.node.isRequired,
|
content: PropTypes.node.isRequired,
|
||||||
reactions: PropTypes.node,
|
reactions: PropTypes.node,
|
||||||
|
options: PropTypes.node,
|
||||||
};
|
};
|
||||||
|
|
||||||
export {
|
export {
|
||||||
|
@ -218,5 +236,6 @@ export {
|
||||||
MessageContent,
|
MessageContent,
|
||||||
MessageReactionGroup,
|
MessageReactionGroup,
|
||||||
MessageReaction,
|
MessageReaction,
|
||||||
|
MessageOptions,
|
||||||
PlaceholderMessage,
|
PlaceholderMessage,
|
||||||
};
|
};
|
||||||
|
|
|
@ -8,6 +8,9 @@
|
||||||
|
|
||||||
&:hover {
|
&:hover {
|
||||||
background-color: var(--bg-surface-hover);
|
background-color: var(--bg-surface-hover);
|
||||||
|
& .message__options {
|
||||||
|
display: flex;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
[dir=rtl] & {
|
[dir=rtl] & {
|
||||||
|
@ -21,8 +24,7 @@
|
||||||
padding-top: 6px;
|
padding-top: 6px;
|
||||||
}
|
}
|
||||||
|
|
||||||
&__avatar-container,
|
&__avatar-container{
|
||||||
&__profile {
|
|
||||||
margin-right: var(--sp-tight);
|
margin-right: var(--sp-tight);
|
||||||
|
|
||||||
[dir=rtl] & {
|
[dir=rtl] & {
|
||||||
|
@ -36,6 +38,8 @@
|
||||||
&__main-container {
|
&__main-container {
|
||||||
flex: 1;
|
flex: 1;
|
||||||
min-width: 0;
|
min-width: 0;
|
||||||
|
|
||||||
|
position: relative;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -49,9 +53,6 @@
|
||||||
&__avatar-container {
|
&__avatar-container {
|
||||||
width: var(--av-small);
|
width: var(--av-small);
|
||||||
}
|
}
|
||||||
&__edited {
|
|
||||||
color: var(--tc-surface-low);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.ph-msg {
|
.ph-msg {
|
||||||
|
@ -106,6 +107,12 @@
|
||||||
flex: 1;
|
flex: 1;
|
||||||
min-width: 0;
|
min-width: 0;
|
||||||
color: var(--tc-surface-high);
|
color: var(--tc-surface-high);
|
||||||
|
margin-right: var(--sp-tight);
|
||||||
|
|
||||||
|
[dir=rtl] & {
|
||||||
|
margin-left: var(--sp-tight);
|
||||||
|
margin-right: 0;
|
||||||
|
}
|
||||||
|
|
||||||
& > .text {
|
& > .text {
|
||||||
color: inherit;
|
color: inherit;
|
||||||
|
@ -144,6 +151,9 @@
|
||||||
& a {
|
& a {
|
||||||
word-break: break-all;
|
word-break: break-all;
|
||||||
}
|
}
|
||||||
|
&-edited {
|
||||||
|
color: var(--tc-surface-low);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
.message__reactions {
|
.message__reactions {
|
||||||
display: flex;
|
display: flex;
|
||||||
|
@ -205,6 +215,22 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
.message__options {
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
right: 60px;
|
||||||
|
transform: translateY(-50%);
|
||||||
|
|
||||||
|
border-radius: var(--bo-radius);
|
||||||
|
box-shadow: var(--bs-surface-border);
|
||||||
|
background-color: var(--bg-surface-low);
|
||||||
|
display: none;
|
||||||
|
|
||||||
|
[dir=rtl] & {
|
||||||
|
left: 60px;
|
||||||
|
right: unset;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// markdown formating
|
// markdown formating
|
||||||
.message__content {
|
.message__content {
|
||||||
|
|
|
@ -13,6 +13,7 @@ import { diffMinutes, isNotInSameDay } from '../../../util/common';
|
||||||
|
|
||||||
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 {
|
import {
|
||||||
Message,
|
Message,
|
||||||
MessageHeader,
|
MessageHeader,
|
||||||
|
@ -20,12 +21,16 @@ import {
|
||||||
MessageContent,
|
MessageContent,
|
||||||
MessageReactionGroup,
|
MessageReactionGroup,
|
||||||
MessageReaction,
|
MessageReaction,
|
||||||
|
MessageOptions,
|
||||||
PlaceholderMessage,
|
PlaceholderMessage,
|
||||||
} from '../../molecules/message/Message';
|
} from '../../molecules/message/Message';
|
||||||
import * as Media from '../../molecules/media/Media';
|
import * as Media from '../../molecules/media/Media';
|
||||||
import ChannelIntro from '../../molecules/channel-intro/ChannelIntro';
|
import ChannelIntro from '../../molecules/channel-intro/ChannelIntro';
|
||||||
import TimelineChange from '../../molecules/message/TimelineChange';
|
import TimelineChange from '../../molecules/message/TimelineChange';
|
||||||
|
|
||||||
|
import ReplyArrowIC from '../../../../public/res/ic/outlined/reply-arrow.svg';
|
||||||
|
import BinIC from '../../../../public/res/ic/outlined/bin.svg';
|
||||||
|
|
||||||
import { parseReply, parseTimelineChange } from './common';
|
import { parseReply, parseTimelineChange } from './common';
|
||||||
|
|
||||||
const MAX_MSG_DIFF_MINUTES = 5;
|
const MAX_MSG_DIFF_MINUTES = 5;
|
||||||
|
@ -335,6 +340,19 @@ function ChannelViewContent({
|
||||||
}
|
}
|
||||||
</MessageReactionGroup>
|
</MessageReactionGroup>
|
||||||
);
|
);
|
||||||
|
const userOptions = (
|
||||||
|
<MessageOptions>
|
||||||
|
<IconButton
|
||||||
|
onClick={() => {
|
||||||
|
viewEvent.emit('reply_to', mEvent.getSender(), mEvent.getId(), isMedia(mEvent) ? mEvent.getContent().body : content);
|
||||||
|
}}
|
||||||
|
src={ReplyArrowIC}
|
||||||
|
size="extra-small"
|
||||||
|
tooltip="Reply"
|
||||||
|
/>
|
||||||
|
<IconButton src={BinIC} size="extra-small" tooltip="Delete" />
|
||||||
|
</MessageOptions>
|
||||||
|
);
|
||||||
|
|
||||||
const myMessageEl = (
|
const myMessageEl = (
|
||||||
<Message
|
<Message
|
||||||
|
@ -344,6 +362,7 @@ function ChannelViewContent({
|
||||||
reply={userReply}
|
reply={userReply}
|
||||||
content={userContent}
|
content={userContent}
|
||||||
reactions={userReactions}
|
reactions={userReactions}
|
||||||
|
options={userOptions}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
|
@ -9,12 +9,15 @@ import initMatrix from '../../../client/initMatrix';
|
||||||
import cons from '../../../client/state/cons';
|
import cons from '../../../client/state/cons';
|
||||||
import settings from '../../../client/state/settings';
|
import settings from '../../../client/state/settings';
|
||||||
import { bytesToSize } from '../../../util/common';
|
import { bytesToSize } from '../../../util/common';
|
||||||
|
import { getUsername } from '../../../util/matrixUtil';
|
||||||
|
import colorMXID from '../../../util/colorMXID';
|
||||||
|
|
||||||
import Text from '../../atoms/text/Text';
|
import Text from '../../atoms/text/Text';
|
||||||
import RawIcon from '../../atoms/system-icons/RawIcon';
|
import RawIcon from '../../atoms/system-icons/RawIcon';
|
||||||
import IconButton from '../../atoms/button/IconButton';
|
import IconButton from '../../atoms/button/IconButton';
|
||||||
import ContextMenu from '../../atoms/context-menu/ContextMenu';
|
import ContextMenu from '../../atoms/context-menu/ContextMenu';
|
||||||
import ScrollView from '../../atoms/scroll/ScrollView';
|
import ScrollView from '../../atoms/scroll/ScrollView';
|
||||||
|
import { MessageReply } from '../../molecules/message/Message';
|
||||||
import EmojiBoard from '../emoji-board/EmojiBoard';
|
import EmojiBoard from '../emoji-board/EmojiBoard';
|
||||||
|
|
||||||
import CirclePlusIC from '../../../../public/res/ic/outlined/circle-plus.svg';
|
import CirclePlusIC from '../../../../public/res/ic/outlined/circle-plus.svg';
|
||||||
|
@ -25,6 +28,7 @@ import VLCIC from '../../../../public/res/ic/outlined/vlc.svg';
|
||||||
import VolumeFullIC from '../../../../public/res/ic/outlined/volume-full.svg';
|
import VolumeFullIC from '../../../../public/res/ic/outlined/volume-full.svg';
|
||||||
import MarkdownIC from '../../../../public/res/ic/outlined/markdown.svg';
|
import MarkdownIC from '../../../../public/res/ic/outlined/markdown.svg';
|
||||||
import FileIC from '../../../../public/res/ic/outlined/file.svg';
|
import FileIC from '../../../../public/res/ic/outlined/file.svg';
|
||||||
|
import CrossIC from '../../../../public/res/ic/outlined/cross.svg';
|
||||||
|
|
||||||
const CMD_REGEX = /(\/|>[#*@]|:)(\S*)$/;
|
const CMD_REGEX = /(\/|>[#*@]|:)(\S*)$/;
|
||||||
let isTyping = false;
|
let isTyping = false;
|
||||||
|
@ -35,6 +39,7 @@ function ChannelViewInput({
|
||||||
}) {
|
}) {
|
||||||
const [attachment, setAttachment] = useState(null);
|
const [attachment, setAttachment] = useState(null);
|
||||||
const [isMarkdown, setIsMarkdown] = useState(settings.isMarkdown);
|
const [isMarkdown, setIsMarkdown] = useState(settings.isMarkdown);
|
||||||
|
const [replyTo, setReplyTo] = useState(null);
|
||||||
|
|
||||||
const textAreaRef = useRef(null);
|
const textAreaRef = useRef(null);
|
||||||
const inputBaseRef = useRef(null);
|
const inputBaseRef = useRef(null);
|
||||||
|
@ -123,17 +128,24 @@ function ChannelViewInput({
|
||||||
deactivateCmd();
|
deactivateCmd();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function setUpReply(userId, eventId, content) {
|
||||||
|
setReplyTo({ userId, eventId, content });
|
||||||
|
roomsInput.setReplyTo(roomId, { userId, eventId, content });
|
||||||
|
}
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
roomsInput.on(cons.events.roomsInput.UPLOAD_PROGRESS_CHANGES, uploadingProgress);
|
roomsInput.on(cons.events.roomsInput.UPLOAD_PROGRESS_CHANGES, uploadingProgress);
|
||||||
roomsInput.on(cons.events.roomsInput.ATTACHMENT_CANCELED, clearAttachment);
|
roomsInput.on(cons.events.roomsInput.ATTACHMENT_CANCELED, clearAttachment);
|
||||||
roomsInput.on(cons.events.roomsInput.FILE_UPLOADED, clearAttachment);
|
roomsInput.on(cons.events.roomsInput.FILE_UPLOADED, clearAttachment);
|
||||||
viewEvent.on('cmd_error', errorCmd);
|
viewEvent.on('cmd_error', errorCmd);
|
||||||
viewEvent.on('cmd_fired', firedCmd);
|
viewEvent.on('cmd_fired', firedCmd);
|
||||||
|
viewEvent.on('reply_to', setUpReply);
|
||||||
if (textAreaRef?.current !== null) {
|
if (textAreaRef?.current !== null) {
|
||||||
isTyping = false;
|
isTyping = false;
|
||||||
textAreaRef.current.focus();
|
textAreaRef.current.focus();
|
||||||
textAreaRef.current.value = roomsInput.getMessage(roomId);
|
textAreaRef.current.value = roomsInput.getMessage(roomId);
|
||||||
setAttachment(roomsInput.getAttachment(roomId));
|
setAttachment(roomsInput.getAttachment(roomId));
|
||||||
|
setReplyTo(roomsInput.getReplyTo(roomId));
|
||||||
}
|
}
|
||||||
return () => {
|
return () => {
|
||||||
roomsInput.removeListener(cons.events.roomsInput.UPLOAD_PROGRESS_CHANGES, uploadingProgress);
|
roomsInput.removeListener(cons.events.roomsInput.UPLOAD_PROGRESS_CHANGES, uploadingProgress);
|
||||||
|
@ -141,6 +153,7 @@ function ChannelViewInput({
|
||||||
roomsInput.removeListener(cons.events.roomsInput.FILE_UPLOADED, clearAttachment);
|
roomsInput.removeListener(cons.events.roomsInput.FILE_UPLOADED, clearAttachment);
|
||||||
viewEvent.removeListener('cmd_error', errorCmd);
|
viewEvent.removeListener('cmd_error', errorCmd);
|
||||||
viewEvent.removeListener('cmd_fired', firedCmd);
|
viewEvent.removeListener('cmd_fired', firedCmd);
|
||||||
|
viewEvent.removeListener('reply_to', setUpReply);
|
||||||
if (isCmdActivated) deactivateCmd();
|
if (isCmdActivated) deactivateCmd();
|
||||||
if (textAreaRef?.current === null) return;
|
if (textAreaRef?.current === null) return;
|
||||||
|
|
||||||
|
@ -180,6 +193,7 @@ function ChannelViewInput({
|
||||||
timelineScroll.reachBottom();
|
timelineScroll.reachBottom();
|
||||||
viewEvent.emit('message_sent');
|
viewEvent.emit('message_sent');
|
||||||
textAreaRef.current.style.height = 'unset';
|
textAreaRef.current.style.height = 'unset';
|
||||||
|
if (replyTo !== null) setReplyTo(null);
|
||||||
}
|
}
|
||||||
|
|
||||||
function processTyping(msg) {
|
function processTyping(msg) {
|
||||||
|
@ -316,8 +330,31 @@ function ChannelViewInput({
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function attachReply() {
|
||||||
|
return (
|
||||||
|
<div className="channel-reply">
|
||||||
|
<IconButton
|
||||||
|
onClick={() => {
|
||||||
|
roomsInput.cancelReplyTo(roomId);
|
||||||
|
setReplyTo(null);
|
||||||
|
}}
|
||||||
|
src={CrossIC}
|
||||||
|
tooltip="Cancel reply"
|
||||||
|
size="extra-small"
|
||||||
|
/>
|
||||||
|
<MessageReply
|
||||||
|
userId={replyTo.userId}
|
||||||
|
name={getUsername(replyTo.userId)}
|
||||||
|
color={colorMXID(replyTo.userId)}
|
||||||
|
content={replyTo.content}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
|
{ replyTo !== null && attachReply()}
|
||||||
{ attachment !== null && attachFile() }
|
{ attachment !== null && attachFile() }
|
||||||
<form className="channel-input" onSubmit={(e) => { e.preventDefault(); }}>
|
<form className="channel-input" onSubmit={(e) => { e.preventDefault(); }}>
|
||||||
{
|
{
|
||||||
|
|
|
@ -99,3 +99,18 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.channel-reply {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
background-color: var(--bg-surface-low);
|
||||||
|
border-bottom: 1px solid var(--bg-surface-border);
|
||||||
|
|
||||||
|
& .ic-btn-surface {
|
||||||
|
margin: 0 13px 0 17px;
|
||||||
|
border-radius: 0;
|
||||||
|
[dir=rtl] & {
|
||||||
|
margin: 0 17px 0 13px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -80,13 +80,32 @@ function getVideoThumbnail(video, width, height, mimeType) {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function getFormatedBody(markdown) {
|
function getFormattedBody(markdown) {
|
||||||
const reader = new Parser();
|
const reader = new Parser();
|
||||||
const writer = new HtmlRenderer();
|
const writer = new HtmlRenderer();
|
||||||
const parsed = reader.parse(markdown);
|
const parsed = reader.parse(markdown);
|
||||||
return writer.render(parsed);
|
return writer.render(parsed);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function getReplyFormattedBody(roomId, reply) {
|
||||||
|
const replyToLink = `<a href="https://matrix.to/#/${roomId}/${reply.eventId}">In reply to</a>`;
|
||||||
|
const userLink = `<a href="https://matrix.to/#/${reply.userId}">${reply.userId}</a>`;
|
||||||
|
return `<mx-reply><blockquote>${replyToLink}${userLink}<br />${reply.content}</blockquote></mx-reply>`;
|
||||||
|
}
|
||||||
|
|
||||||
|
function bindReplyToContent(roomId, reply, content) {
|
||||||
|
const newContent = { ...content };
|
||||||
|
newContent.body = `> <${reply.userId}> ${reply.content}`;
|
||||||
|
newContent.body += `\n\n${content.body}`;
|
||||||
|
newContent.format = 'org.matrix.custom.html';
|
||||||
|
newContent['m.relates_to'] = content['m.relates_to'] || {};
|
||||||
|
newContent['m.relates_to']['m.in_reply_to'] = { event_id: reply.eventId };
|
||||||
|
|
||||||
|
const formattedReply = getReplyFormattedBody(roomId, reply);
|
||||||
|
newContent.formatted_body = formattedReply + (content.formatted_body || content.body);
|
||||||
|
return newContent;
|
||||||
|
}
|
||||||
|
|
||||||
class RoomsInput extends EventEmitter {
|
class RoomsInput extends EventEmitter {
|
||||||
constructor(mx) {
|
constructor(mx) {
|
||||||
super();
|
super();
|
||||||
|
@ -98,6 +117,7 @@ class RoomsInput extends EventEmitter {
|
||||||
cleanEmptyEntry(roomId) {
|
cleanEmptyEntry(roomId) {
|
||||||
const input = this.getInput(roomId);
|
const input = this.getInput(roomId);
|
||||||
const isEmpty = typeof input.attachment === 'undefined'
|
const isEmpty = typeof input.attachment === 'undefined'
|
||||||
|
&& typeof input.replyTo === 'undefined'
|
||||||
&& (typeof input.message === 'undefined' || input.message === '');
|
&& (typeof input.message === 'undefined' || input.message === '');
|
||||||
if (isEmpty) {
|
if (isEmpty) {
|
||||||
this.roomIdToInput.delete(roomId);
|
this.roomIdToInput.delete(roomId);
|
||||||
|
@ -121,6 +141,25 @@ class RoomsInput extends EventEmitter {
|
||||||
return input.message;
|
return input.message;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
setReplyTo(roomId, replyTo) {
|
||||||
|
const input = this.getInput(roomId);
|
||||||
|
input.replyTo = replyTo;
|
||||||
|
this.roomIdToInput.set(roomId, input);
|
||||||
|
}
|
||||||
|
|
||||||
|
getReplyTo(roomId) {
|
||||||
|
const input = this.getInput(roomId);
|
||||||
|
if (typeof input.replyTo === 'undefined') return null;
|
||||||
|
return input.replyTo;
|
||||||
|
}
|
||||||
|
|
||||||
|
cancelReplyTo(roomId) {
|
||||||
|
const input = this.getInput(roomId);
|
||||||
|
if (typeof input.replyTo === 'undefined') return;
|
||||||
|
delete input.replyTo;
|
||||||
|
this.roomIdToInput.set(roomId, input);
|
||||||
|
}
|
||||||
|
|
||||||
setAttachment(roomId, file) {
|
setAttachment(roomId, file) {
|
||||||
const input = this.getInput(roomId);
|
const input = this.getInput(roomId);
|
||||||
input.attachment = {
|
input.attachment = {
|
||||||
|
@ -145,13 +184,9 @@ class RoomsInput extends EventEmitter {
|
||||||
this.matrixClient.cancelUpload(uploadingPromise);
|
this.matrixClient.cancelUpload(uploadingPromise);
|
||||||
delete input.attachment.uploadingPromise;
|
delete input.attachment.uploadingPromise;
|
||||||
}
|
}
|
||||||
if (input.message) {
|
|
||||||
delete input.attachment;
|
delete input.attachment;
|
||||||
delete input.isSending;
|
delete input.isSending;
|
||||||
this.roomIdToInput.set(roomId, input);
|
this.roomIdToInput.set(roomId, input);
|
||||||
} else {
|
|
||||||
this.roomIdToInput.delete(roomId);
|
|
||||||
}
|
|
||||||
this.emit(cons.events.roomsInput.ATTACHMENT_CANCELED, roomId);
|
this.emit(cons.events.roomsInput.ATTACHMENT_CANCELED, roomId);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -168,13 +203,16 @@ class RoomsInput extends EventEmitter {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.getMessage(roomId).trim() !== '') {
|
if (this.getMessage(roomId).trim() !== '') {
|
||||||
const content = {
|
let content = {
|
||||||
body: input.message,
|
body: input.message,
|
||||||
msgtype: 'm.text',
|
msgtype: 'm.text',
|
||||||
};
|
};
|
||||||
if (settings.isMarkdown) {
|
if (settings.isMarkdown) {
|
||||||
content.format = 'org.matrix.custom.html';
|
content.format = 'org.matrix.custom.html';
|
||||||
content.formatted_body = getFormatedBody(input.message);
|
content.formatted_body = getFormattedBody(input.message);
|
||||||
|
}
|
||||||
|
if (typeof input.replyTo !== 'undefined') {
|
||||||
|
content = bindReplyToContent(roomId, input.replyTo, content);
|
||||||
}
|
}
|
||||||
this.matrixClient.sendMessage(roomId, content);
|
this.matrixClient.sendMessage(roomId, content);
|
||||||
}
|
}
|
||||||
|
|
|
@ -221,6 +221,7 @@
|
||||||
|
|
||||||
html {
|
html {
|
||||||
height: 100%;
|
height: 100%;
|
||||||
|
overflow: hidden;
|
||||||
}
|
}
|
||||||
|
|
||||||
body {
|
body {
|
||||||
|
|
Loading…
Reference in a new issue