Signed-off-by: Ajay Bura <ajbura@gmail.com>
This commit is contained in:
parent
7e7a5e692e
commit
2479dc4096
9 changed files with 271 additions and 2048 deletions
2007
package-lock.json
generated
2007
package-lock.json
generated
File diff suppressed because it is too large
Load diff
|
@ -24,6 +24,7 @@
|
||||||
"flux": "^4.0.1",
|
"flux": "^4.0.1",
|
||||||
"formik": "^2.2.9",
|
"formik": "^2.2.9",
|
||||||
"html-react-parser": "^1.2.7",
|
"html-react-parser": "^1.2.7",
|
||||||
|
"linkify-html": "^3.0.3",
|
||||||
"linkify-react": "^3.0.3",
|
"linkify-react": "^3.0.3",
|
||||||
"linkifyjs": "^3.0.3",
|
"linkifyjs": "^3.0.3",
|
||||||
"matrix-js-sdk": "^12.4.1",
|
"matrix-js-sdk": "^12.4.1",
|
||||||
|
@ -34,10 +35,7 @@
|
||||||
"react-autosize-textarea": "^7.1.0",
|
"react-autosize-textarea": "^7.1.0",
|
||||||
"react-dom": "^17.0.2",
|
"react-dom": "^17.0.2",
|
||||||
"react-google-recaptcha": "^2.1.0",
|
"react-google-recaptcha": "^2.1.0",
|
||||||
"react-markdown": "^6.0.1",
|
|
||||||
"react-modal": "^3.13.1",
|
"react-modal": "^3.13.1",
|
||||||
"react-syntax-highlighter": "^15.4.3",
|
|
||||||
"remark-gfm": "^1.0.0",
|
|
||||||
"sanitize-html": "^2.5.3",
|
"sanitize-html": "^2.5.3",
|
||||||
"tippy.js": "^6.3.1",
|
"tippy.js": "^6.3.1",
|
||||||
"twemoji": "^13.1.0"
|
"twemoji": "^13.1.0"
|
||||||
|
|
|
@ -32,7 +32,8 @@
|
||||||
|
|
||||||
@mixin scroll {
|
@mixin scroll {
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
overscroll-behavior: none;
|
// Below code stop scroll when x-scrollable content come in timeline
|
||||||
|
// overscroll-behavior: none;
|
||||||
@extend .firefox-scrollbar;
|
@extend .firefox-scrollbar;
|
||||||
@extend .webkit-scrollbar;
|
@extend .webkit-scrollbar;
|
||||||
@extend .webkit-scrollbar-track;
|
@extend .webkit-scrollbar-track;
|
||||||
|
|
|
@ -3,11 +3,7 @@ import React, { useState, useRef } from 'react';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import './Message.scss';
|
import './Message.scss';
|
||||||
|
|
||||||
import Linkify from 'linkify-react';
|
import linkifyHtml from 'linkify-html';
|
||||||
import ReactMarkdown from 'react-markdown';
|
|
||||||
import gfm from 'remark-gfm';
|
|
||||||
import { Prism as SyntaxHighlighter } from 'react-syntax-highlighter';
|
|
||||||
import { coy } from 'react-syntax-highlighter/dist/esm/styles/prism';
|
|
||||||
import parse from 'html-react-parser';
|
import parse from 'html-react-parser';
|
||||||
import twemoji from 'twemoji';
|
import twemoji from 'twemoji';
|
||||||
import dateFormat from 'dateformat';
|
import dateFormat from 'dateformat';
|
||||||
|
@ -38,33 +34,7 @@ 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';
|
||||||
|
|
||||||
const components = {
|
import sanitize from './sanitize';
|
||||||
code({
|
|
||||||
// eslint-disable-next-line react/prop-types
|
|
||||||
inline, className, children,
|
|
||||||
}) {
|
|
||||||
const match = /language-(\w+)/.exec(className || '');
|
|
||||||
return !inline && match ? (
|
|
||||||
<SyntaxHighlighter
|
|
||||||
style={coy}
|
|
||||||
language={match[1]}
|
|
||||||
PreTag="div"
|
|
||||||
showLineNumbers
|
|
||||||
>
|
|
||||||
{String(children).replace(/\n$/, '')}
|
|
||||||
</SyntaxHighlighter>
|
|
||||||
) : (
|
|
||||||
<code className={className}>{String(children)}</code>
|
|
||||||
);
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
function linkifyContent(content) {
|
|
||||||
return <Linkify options={{ target: { url: '_blank' } }}>{content}</Linkify>;
|
|
||||||
}
|
|
||||||
function genMarkdown(content) {
|
|
||||||
return <ReactMarkdown remarkPlugins={[gfm]} components={components} linkTarget="_blank">{content}</ReactMarkdown>;
|
|
||||||
}
|
|
||||||
|
|
||||||
function PlaceholderMessage() {
|
function PlaceholderMessage() {
|
||||||
return (
|
return (
|
||||||
|
@ -91,7 +61,7 @@ function MessageHeader({
|
||||||
return (
|
return (
|
||||||
<div className="message__header">
|
<div className="message__header">
|
||||||
<div style={{ color }} className="message__profile">
|
<div style={{ color }} className="message__profile">
|
||||||
<Text variant="b1">{name}</Text>
|
<Text variant="b1">{parse(twemoji.parse(name))}</Text>
|
||||||
<Text variant="b1">{userId}</Text>
|
<Text variant="b1">{userId}</Text>
|
||||||
</div>
|
</div>
|
||||||
<div className="message__time">
|
<div className="message__time">
|
||||||
|
@ -112,7 +82,7 @@ function MessageReply({ name, color, body }) {
|
||||||
<div className="message__reply">
|
<div className="message__reply">
|
||||||
<Text variant="b2">
|
<Text variant="b2">
|
||||||
<RawIcon color={color} size="extra-small" src={ReplyArrowIC} />
|
<RawIcon color={color} size="extra-small" src={ReplyArrowIC} />
|
||||||
<span style={{ color }}>{name}</span>
|
<span style={{ color }}>{parse(twemoji.parse(name))}</span>
|
||||||
<>{` ${body}`}</>
|
<>{` ${body}`}</>
|
||||||
</Text>
|
</Text>
|
||||||
</div>
|
</div>
|
||||||
|
@ -132,11 +102,16 @@ function MessageBody({
|
||||||
isEdited,
|
isEdited,
|
||||||
msgType,
|
msgType,
|
||||||
}) {
|
}) {
|
||||||
|
// if body is not string it is a React( element.
|
||||||
|
if (typeof body !== 'string') return <div className="message__body">{body}</div>;
|
||||||
|
|
||||||
|
const content = twemoji.parse(isCustomHTML ? sanitize(body) : body);
|
||||||
|
const linkified = linkifyHtml(content, { target: '_blank', rel: 'noreferrer noopener' });
|
||||||
return (
|
return (
|
||||||
<div className="message__body">
|
<div className="message__body">
|
||||||
<div className="text text-b1">
|
<div className="text text-b1">
|
||||||
{ msgType === 'm.emote' && `* ${senderName} ` }
|
{ msgType === 'm.emote' && `* ${senderName} ` }
|
||||||
{ isCustomHTML ? genMarkdown(body) : linkifyContent(body) }
|
{ parse(linkified) }
|
||||||
</div>
|
</div>
|
||||||
{ isEdited && <Text className="message__body-edited" variant="b3">(edited)</Text>}
|
{ isEdited && <Text className="message__body-edited" variant="b3">(edited)</Text>}
|
||||||
</div>
|
</div>
|
||||||
|
@ -383,18 +358,16 @@ function parseReply(rawBody) {
|
||||||
body,
|
body,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
function getEditedBody(eventId, editedTimeline) {
|
function getEditedBody(editedMEvent) {
|
||||||
const editedList = editedTimeline.get(eventId);
|
|
||||||
const editedMEvent = editedList[editedList.length - 1];
|
|
||||||
const newContent = editedMEvent.getContent()['m.new_content'];
|
const newContent = editedMEvent.getContent()['m.new_content'];
|
||||||
if (typeof newContent === 'undefined') return [null, false];
|
if (typeof newContent === 'undefined') return [null, false, null];
|
||||||
|
|
||||||
const isCustomHTML = newContent.format === 'org.matrix.custom.html';
|
const isCustomHTML = newContent.format === 'org.matrix.custom.html';
|
||||||
const parsedContent = parseReply(newContent.body);
|
const parsedContent = parseReply(newContent.body);
|
||||||
if (parsedContent === null) {
|
if (parsedContent === null) {
|
||||||
return [newContent.body, isCustomHTML];
|
return [newContent.body, isCustomHTML, newContent.formatted_body ?? null];
|
||||||
}
|
}
|
||||||
return [parsedContent.body, isCustomHTML];
|
return [parsedContent.body, isCustomHTML, newContent.formatted_body ?? null];
|
||||||
}
|
}
|
||||||
|
|
||||||
function Message({ mEvent, isBodyOnly, roomTimeline }) {
|
function Message({ mEvent, isBodyOnly, roomTimeline }) {
|
||||||
|
@ -406,7 +379,7 @@ function Message({ mEvent, isBodyOnly, roomTimeline }) {
|
||||||
} = roomTimeline;
|
} = roomTimeline;
|
||||||
|
|
||||||
const className = ['message', (isBodyOnly ? 'message--body-only' : 'message--full')];
|
const className = ['message', (isBodyOnly ? 'message--body-only' : 'message--full')];
|
||||||
const content = mEvent.getWireContent();
|
const content = mEvent.getContent();
|
||||||
const eventId = mEvent.getId();
|
const eventId = mEvent.getId();
|
||||||
const msgType = content?.msgtype;
|
const msgType = content?.msgtype;
|
||||||
const senderId = mEvent.getSender();
|
const senderId = mEvent.getSender();
|
||||||
|
@ -419,16 +392,18 @@ function Message({ mEvent, isBodyOnly, roomTimeline }) {
|
||||||
if (typeof body === 'undefined') return null;
|
if (typeof body === 'undefined') return null;
|
||||||
if (msgType === 'm.emote') className.push('message--type-emote');
|
if (msgType === 'm.emote') className.push('message--type-emote');
|
||||||
|
|
||||||
// TODO: these line can be moved to option menu
|
|
||||||
const myPowerlevel = room.getMember(mx.getUserId())?.powerLevel;
|
const myPowerlevel = room.getMember(mx.getUserId())?.powerLevel;
|
||||||
const canIRedact = room.currentState.hasSufficientPowerLevelFor('redact', myPowerlevel);
|
const canIRedact = room.currentState.hasSufficientPowerLevelFor('redact', myPowerlevel);
|
||||||
|
|
||||||
let [reply, reactions, isCustomHTML] = [null, null, content.format === 'org.matrix.custom.html'];
|
let [reply, reactions, isCustomHTML] = [null, null, content.format === 'org.matrix.custom.html'];
|
||||||
const [isEdited, haveReactions] = [editedTimeline.has(eventId), reactionTimeline.has(eventId)];
|
const [isEdited, haveReactions] = [editedTimeline.has(eventId), reactionTimeline.has(eventId)];
|
||||||
const isReply = typeof content['m.relates_to']?.['m.in_reply_to'] !== 'undefined';
|
const isReply = typeof content['m.relates_to']?.['m.in_reply_to'] !== 'undefined';
|
||||||
|
let customHTML = isCustomHTML ? content.formatted_body : null;
|
||||||
|
|
||||||
if (isEdited) {
|
if (isEdited) {
|
||||||
[body, isCustomHTML] = getEditedBody(eventId, editedTimeline);
|
const editedList = editedTimeline.get(eventId);
|
||||||
|
const editedMEvent = editedList[editedList.length - 1];
|
||||||
|
[body, isCustomHTML, customHTML] = getEditedBody(editedMEvent);
|
||||||
if (typeof body !== 'string') return null;
|
if (typeof body !== 'string') return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -500,7 +475,7 @@ function Message({ mEvent, isBodyOnly, roomTimeline }) {
|
||||||
<MessageBody
|
<MessageBody
|
||||||
senderName={username}
|
senderName={username}
|
||||||
isCustomHTML={isCustomHTML}
|
isCustomHTML={isCustomHTML}
|
||||||
body={isMedia(mEvent) ? genMediaContent(mEvent) : body}
|
body={isMedia(mEvent) ? genMediaContent(mEvent) : customHTML ?? body}
|
||||||
msgType={msgType}
|
msgType={msgType}
|
||||||
isEdited={isEdited}
|
isEdited={isEdited}
|
||||||
/>
|
/>
|
||||||
|
|
|
@ -1,5 +1,14 @@
|
||||||
@use '../../atoms/scroll/scrollbar';
|
@use '../../atoms/scroll/scrollbar';
|
||||||
|
|
||||||
|
.custom-emoji {
|
||||||
|
height: var(--fs-b1);
|
||||||
|
margin: 0 !important;
|
||||||
|
margin-right: 2px !important;
|
||||||
|
padding: 0 !important;
|
||||||
|
position: relative;
|
||||||
|
top: 2px;
|
||||||
|
}
|
||||||
|
|
||||||
.message,
|
.message,
|
||||||
.ph-msg {
|
.ph-msg {
|
||||||
padding: var(--sp-ultra-tight) var(--sp-normal);
|
padding: var(--sp-ultra-tight) var(--sp-normal);
|
||||||
|
@ -107,6 +116,10 @@
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: baseline;
|
align-items: baseline;
|
||||||
|
|
||||||
|
& img.emoji {
|
||||||
|
@extend .custom-emoji;
|
||||||
|
}
|
||||||
|
|
||||||
& .message__profile {
|
& .message__profile {
|
||||||
min-width: 0;
|
min-width: 0;
|
||||||
color: var(--tc-surface-high);
|
color: var(--tc-surface-high);
|
||||||
|
@ -142,6 +155,10 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.message__reply {
|
.message__reply {
|
||||||
|
& img.emoji {
|
||||||
|
@extend .custom-emoji;
|
||||||
|
height: 14px;
|
||||||
|
}
|
||||||
.text {
|
.text {
|
||||||
color: var(--tc-surface-low);
|
color: var(--tc-surface-low);
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
|
@ -163,6 +180,45 @@
|
||||||
& a {
|
& a {
|
||||||
word-break: break-word;
|
word-break: break-word;
|
||||||
}
|
}
|
||||||
|
& img.emoji,
|
||||||
|
& img[data-mx-emoticon] {
|
||||||
|
@extend .custom-emoji;
|
||||||
|
}
|
||||||
|
& span[data-mx-pill] {
|
||||||
|
background-color: hsla(0, 0%, 64%, 0.15);
|
||||||
|
padding: 0 2px;
|
||||||
|
border-radius: 4px;
|
||||||
|
cursor: pointer;
|
||||||
|
font-weight: 500;
|
||||||
|
&:hover {
|
||||||
|
background-color: hsla(0, 0%, 64%, 0.3);
|
||||||
|
color: var(--tc-surface-high);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
& span[data-mx-spoiler] {
|
||||||
|
border-radius: 4px;
|
||||||
|
background-color: rgba(124, 124, 124, 0.5);
|
||||||
|
color:transparent;
|
||||||
|
-webkit-touch-callout: none;
|
||||||
|
-webkit-user-select: none;
|
||||||
|
-khtml-user-select: none;
|
||||||
|
-moz-user-select: none;
|
||||||
|
-ms-user-select: none;
|
||||||
|
user-select: none;
|
||||||
|
& > * {
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
|
&:focus, &:hover {
|
||||||
|
background-color: transparent;
|
||||||
|
color: inherit;
|
||||||
|
user-select: initial;
|
||||||
|
& > * {
|
||||||
|
opacity: inherit;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
&-edited {
|
&-edited {
|
||||||
color: var(--tc-surface-low);
|
color: var(--tc-surface-low);
|
||||||
}
|
}
|
||||||
|
@ -327,10 +383,20 @@
|
||||||
@include scrollbar.scroll__h;
|
@include scrollbar.scroll__h;
|
||||||
@include scrollbar.scroll--auto-hide;
|
@include scrollbar.scroll--auto-hide;
|
||||||
}
|
}
|
||||||
& pre code {
|
& pre {
|
||||||
|
display: inline-block;
|
||||||
|
max-width: 100%;
|
||||||
|
@include scrollbar.scroll;
|
||||||
|
@include scrollbar.scroll__h;
|
||||||
|
@include scrollbar.scroll--auto-hide;
|
||||||
|
& code {
|
||||||
color: var(--tc-surface-normal) !important;
|
color: var(--tc-surface-normal) !important;
|
||||||
|
white-space: pre;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
& blockquote {
|
& blockquote {
|
||||||
|
display: inline-block;
|
||||||
|
max-width: 100%;
|
||||||
padding-left: var(--sp-extra-tight);
|
padding-left: var(--sp-extra-tight);
|
||||||
border-left: 4px solid var(--bg-surface-active);
|
border-left: 4px solid var(--bg-surface-active);
|
||||||
white-space: initial !important;
|
white-space: initial !important;
|
||||||
|
@ -372,15 +438,21 @@
|
||||||
list-style: none;
|
list-style: none;
|
||||||
}
|
}
|
||||||
& table {
|
& table {
|
||||||
|
display: -webkit-box;
|
||||||
|
width: 100%;
|
||||||
background-color: var(--bg-surface-hover);
|
background-color: var(--bg-surface-hover);
|
||||||
border-radius: calc(var(--bo-radius) / 2);
|
border-radius: calc(var(--bo-radius) / 2);
|
||||||
border-spacing: 0;
|
border-spacing: 0;
|
||||||
border: 1px solid var(--bg-surface-border);
|
border: 1px solid var(--bg-surface-border);
|
||||||
|
@include scrollbar.scroll;
|
||||||
|
@include scrollbar.scroll__h;
|
||||||
|
@include scrollbar.scroll--auto-hide;
|
||||||
|
|
||||||
& td, & th {
|
& td, & th {
|
||||||
padding: var(--sp-extra-tight);
|
padding: var(--sp-extra-tight);
|
||||||
border: 1px solid var(--bg-surface-border);
|
border: 1px solid var(--bg-surface-border);
|
||||||
border-width: 0 1px 1px 0;
|
border-width: 0 1px 1px 0;
|
||||||
|
white-space: pre;
|
||||||
&:last-child {
|
&:last-child {
|
||||||
border-width: 0;
|
border-width: 0;
|
||||||
border-bottom-width: 1px;
|
border-bottom-width: 1px;
|
||||||
|
|
144
src/app/molecules/message/sanitize.js
Normal file
144
src/app/molecules/message/sanitize.js
Normal file
|
@ -0,0 +1,144 @@
|
||||||
|
import sanitizeHtml from 'sanitize-html';
|
||||||
|
import initMatrix from '../../../client/initMatrix';
|
||||||
|
|
||||||
|
function sanitizeColorizedTag(tagName, attributes) {
|
||||||
|
const attribs = { ...attributes };
|
||||||
|
const styles = [];
|
||||||
|
if (attributes['data-mx-color']) {
|
||||||
|
styles.push(`color: ${attributes['data-mx-color']};`);
|
||||||
|
}
|
||||||
|
if (attributes['data-mx-bg-color']) {
|
||||||
|
styles.push(`background-color: ${attributes['data-mx-bg-color']};`);
|
||||||
|
}
|
||||||
|
attribs.style = styles.join(' ');
|
||||||
|
|
||||||
|
return { tagName, attribs };
|
||||||
|
}
|
||||||
|
|
||||||
|
function sanitizeLinkTag(tagName, attribs) {
|
||||||
|
const userLink = attribs.href.match(/^https?:\/\/matrix.to\/#\/(@.+:.+)/);
|
||||||
|
if (userLink !== null) {
|
||||||
|
// convert user link to pill
|
||||||
|
const userId = userLink[1];
|
||||||
|
return {
|
||||||
|
tagName: 'span',
|
||||||
|
attribs: {
|
||||||
|
'data-mx-pill': userId,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
tagName,
|
||||||
|
attribs: {
|
||||||
|
...attribs,
|
||||||
|
target: '_blank',
|
||||||
|
rel: 'noreferrer noopener',
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function sanitizeCodeTag(tagName, attributes) {
|
||||||
|
const attribs = { ...attributes };
|
||||||
|
let classes = [];
|
||||||
|
if (attributes.class) {
|
||||||
|
classes = attributes.class.split(/\s+/).filter((className) => className.match(/^language-(\w+)/));
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
tagName,
|
||||||
|
attribs: {
|
||||||
|
...attribs,
|
||||||
|
class: classes.join(' '),
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function sanitizeImgTag(tagName, attributes) {
|
||||||
|
const mx = initMatrix.matrixClient;
|
||||||
|
const { src } = attributes;
|
||||||
|
const attribs = { ...attributes };
|
||||||
|
delete attribs.src;
|
||||||
|
|
||||||
|
if (src.match(/^mxc:\/\//)) {
|
||||||
|
attribs.src = mx.mxcUrlToHttp(src);
|
||||||
|
}
|
||||||
|
|
||||||
|
return { tagName, attribs };
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function sanitize(body) {
|
||||||
|
return sanitizeHtml(body, {
|
||||||
|
allowedTags: [
|
||||||
|
'font',
|
||||||
|
'del',
|
||||||
|
'h1',
|
||||||
|
'h2',
|
||||||
|
'h3',
|
||||||
|
'h4',
|
||||||
|
'h5',
|
||||||
|
'h6',
|
||||||
|
'blockquote',
|
||||||
|
'p',
|
||||||
|
'a',
|
||||||
|
'ul',
|
||||||
|
'ol',
|
||||||
|
'sup',
|
||||||
|
'sub',
|
||||||
|
'li',
|
||||||
|
'b',
|
||||||
|
'i',
|
||||||
|
'u',
|
||||||
|
'strong',
|
||||||
|
'em',
|
||||||
|
'strike',
|
||||||
|
'code',
|
||||||
|
'hr',
|
||||||
|
'br',
|
||||||
|
'div',
|
||||||
|
'table',
|
||||||
|
'thead',
|
||||||
|
'tbody',
|
||||||
|
'tr',
|
||||||
|
'th',
|
||||||
|
'td',
|
||||||
|
'caption',
|
||||||
|
'pre',
|
||||||
|
'span',
|
||||||
|
'img',
|
||||||
|
'details',
|
||||||
|
'summary',
|
||||||
|
],
|
||||||
|
allowedClasses: {},
|
||||||
|
allowedAttributes: {
|
||||||
|
ol: ['start'],
|
||||||
|
img: ['width', 'height', 'alt', 'title', 'src', 'data-mx-emoticon'],
|
||||||
|
a: ['name', 'target', 'href', 'rel'],
|
||||||
|
code: ['class'],
|
||||||
|
font: ['data-mx-bg-color', 'data-mx-color', 'color', 'style'],
|
||||||
|
span: ['data-mx-bg-color', 'data-mx-color', 'data-mx-spoiler', 'style', 'data-mx-pill'],
|
||||||
|
},
|
||||||
|
allowProtocolRelative: false,
|
||||||
|
allowedSchemesByTag: {
|
||||||
|
a: ['https', 'http', 'ftp', 'mailto', 'magnet'],
|
||||||
|
img: ['https', 'http'],
|
||||||
|
},
|
||||||
|
allowedStyles: {
|
||||||
|
'*': {
|
||||||
|
color: [/^#(0x)?[0-9a-f]+$/i],
|
||||||
|
'background-color': [/^#(0x)?[0-9a-f]+$/i],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
nestingLimit: 100,
|
||||||
|
nonTextTags: [
|
||||||
|
'style', 'script', 'textarea', 'option', 'mx-reply',
|
||||||
|
],
|
||||||
|
transformTags: {
|
||||||
|
a: sanitizeLinkTag,
|
||||||
|
img: sanitizeImgTag,
|
||||||
|
code: sanitizeCodeTag,
|
||||||
|
font: sanitizeColorizedTag,
|
||||||
|
span: sanitizeColorizedTag,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
|
@ -13,7 +13,7 @@
|
||||||
|
|
||||||
.room-intro__content {
|
.room-intro__content {
|
||||||
margin-top: var(--sp-extra-loose);
|
margin-top: var(--sp-extra-loose);
|
||||||
max-width: 640px;
|
width: calc(100% - 88px);
|
||||||
}
|
}
|
||||||
&__name {
|
&__name {
|
||||||
color: var(--tc-surface-high);
|
color: var(--tc-surface-high);
|
||||||
|
|
|
@ -106,7 +106,6 @@ function PeopleDrawer({ roomId }) {
|
||||||
let isGettingMembers = true;
|
let isGettingMembers = true;
|
||||||
const updateMemberList = (event) => {
|
const updateMemberList = (event) => {
|
||||||
if (isGettingMembers) return;
|
if (isGettingMembers) return;
|
||||||
console.log(event?.event?.room_id);
|
|
||||||
if (event && event?.event?.room_id !== roomId) return;
|
if (event && event?.event?.room_id !== roomId) return;
|
||||||
setMemberList(
|
setMemberList(
|
||||||
simplyfiMembers(
|
simplyfiMembers(
|
||||||
|
|
|
@ -1,3 +1,5 @@
|
||||||
|
/* eslint-disable jsx-a11y/no-static-element-interactions */
|
||||||
|
/* eslint-disable jsx-a11y/click-events-have-key-events */
|
||||||
/* eslint-disable react/prop-types */
|
/* eslint-disable react/prop-types */
|
||||||
import React, { useState, useEffect, useLayoutEffect } from 'react';
|
import React, { useState, useEffect, useLayoutEffect } from 'react';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
|
@ -8,6 +10,7 @@ import dateFormat from 'dateformat';
|
||||||
import initMatrix from '../../../client/initMatrix';
|
import initMatrix from '../../../client/initMatrix';
|
||||||
import cons from '../../../client/state/cons';
|
import cons from '../../../client/state/cons';
|
||||||
import { diffMinutes, isNotInSameDay } from '../../../util/common';
|
import { diffMinutes, isNotInSameDay } from '../../../util/common';
|
||||||
|
import { openProfileViewer } from '../../../client/action/navigation';
|
||||||
|
|
||||||
import Divider from '../../atoms/divider/Divider';
|
import Divider from '../../atoms/divider/Divider';
|
||||||
import { Message, PlaceholderMessage } from '../../molecules/message/Message';
|
import { Message, PlaceholderMessage } from '../../molecules/message/Message';
|
||||||
|
@ -188,8 +191,16 @@ function RoomViewContent({
|
||||||
}
|
}
|
||||||
}, [onStateUpdate]);
|
}, [onStateUpdate]);
|
||||||
|
|
||||||
|
const handleOnClickCapture = (e) => {
|
||||||
|
const { target } = e;
|
||||||
|
const userId = target.getAttribute('data-mx-pill');
|
||||||
|
if (!userId) return;
|
||||||
|
|
||||||
|
openProfileViewer(userId, roomId);
|
||||||
|
};
|
||||||
|
|
||||||
let prevMEvent = null;
|
let prevMEvent = null;
|
||||||
function renderMessage(mEvent) {
|
const renderMessage = (mEvent) => {
|
||||||
const isContentOnly = (prevMEvent !== null && prevMEvent.getType() !== 'm.room.member'
|
const isContentOnly = (prevMEvent !== null && prevMEvent.getType() !== 'm.room.member'
|
||||||
&& diffMinutes(mEvent.getDate(), prevMEvent.getDate()) <= MAX_MSG_DIFF_MINUTES
|
&& diffMinutes(mEvent.getDate(), prevMEvent.getDate()) <= MAX_MSG_DIFF_MINUTES
|
||||||
&& prevMEvent.getSender() === mEvent.getSender()
|
&& prevMEvent.getSender() === mEvent.getSender()
|
||||||
|
@ -222,7 +233,7 @@ function RoomViewContent({
|
||||||
<Message mEvent={mEvent} isBodyOnly={isContentOnly} roomTimeline={roomTimeline} />
|
<Message mEvent={mEvent} isBodyOnly={isContentOnly} roomTimeline={roomTimeline} />
|
||||||
</React.Fragment>
|
</React.Fragment>
|
||||||
);
|
);
|
||||||
}
|
};
|
||||||
|
|
||||||
const renderTimeline = () => {
|
const renderTimeline = () => {
|
||||||
const { timeline } = roomTimeline;
|
const { timeline } = roomTimeline;
|
||||||
|
@ -249,7 +260,7 @@ function RoomViewContent({
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="room-view__content">
|
<div className="room-view__content" onClick={handleOnClickCapture}>
|
||||||
<div className="timeline__wrapper">
|
<div className="timeline__wrapper">
|
||||||
{ renderTimeline() }
|
{ renderTimeline() }
|
||||||
</div>
|
</div>
|
||||||
|
|
Loading…
Reference in a new issue