mirror of
				https://github.com/cinnyapp/cinny.git
				synced 2025-11-04 06:20:28 +03:00 
			
		
		
		
	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",
 | 
			
		||||
    "formik": "^2.2.9",
 | 
			
		||||
    "html-react-parser": "^1.2.7",
 | 
			
		||||
    "linkify-html": "^3.0.3",
 | 
			
		||||
    "linkify-react": "^3.0.3",
 | 
			
		||||
    "linkifyjs": "^3.0.3",
 | 
			
		||||
    "matrix-js-sdk": "^12.4.1",
 | 
			
		||||
| 
						 | 
				
			
			@ -34,10 +35,7 @@
 | 
			
		|||
    "react-autosize-textarea": "^7.1.0",
 | 
			
		||||
    "react-dom": "^17.0.2",
 | 
			
		||||
    "react-google-recaptcha": "^2.1.0",
 | 
			
		||||
    "react-markdown": "^6.0.1",
 | 
			
		||||
    "react-modal": "^3.13.1",
 | 
			
		||||
    "react-syntax-highlighter": "^15.4.3",
 | 
			
		||||
    "remark-gfm": "^1.0.0",
 | 
			
		||||
    "sanitize-html": "^2.5.3",
 | 
			
		||||
    "tippy.js": "^6.3.1",
 | 
			
		||||
    "twemoji": "^13.1.0"
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -32,7 +32,8 @@
 | 
			
		|||
 | 
			
		||||
@mixin scroll {
 | 
			
		||||
  overflow: hidden;
 | 
			
		||||
  overscroll-behavior: none;
 | 
			
		||||
  // Below code stop scroll when x-scrollable content come in timeline
 | 
			
		||||
  // overscroll-behavior: none;
 | 
			
		||||
  @extend .firefox-scrollbar;
 | 
			
		||||
  @extend .webkit-scrollbar;
 | 
			
		||||
  @extend .webkit-scrollbar-track;
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -3,11 +3,7 @@ import React, { useState, useRef } from 'react';
 | 
			
		|||
import PropTypes from 'prop-types';
 | 
			
		||||
import './Message.scss';
 | 
			
		||||
 | 
			
		||||
import Linkify from 'linkify-react';
 | 
			
		||||
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 linkifyHtml from 'linkify-html';
 | 
			
		||||
import parse from 'html-react-parser';
 | 
			
		||||
import twemoji from 'twemoji';
 | 
			
		||||
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 BinIC from '../../../../public/res/ic/outlined/bin.svg';
 | 
			
		||||
 | 
			
		||||
const components = {
 | 
			
		||||
  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>;
 | 
			
		||||
}
 | 
			
		||||
import sanitize from './sanitize';
 | 
			
		||||
 | 
			
		||||
function PlaceholderMessage() {
 | 
			
		||||
  return (
 | 
			
		||||
| 
						 | 
				
			
			@ -91,7 +61,7 @@ function MessageHeader({
 | 
			
		|||
  return (
 | 
			
		||||
    <div className="message__header">
 | 
			
		||||
      <div style={{ color }} className="message__profile">
 | 
			
		||||
        <Text variant="b1">{name}</Text>
 | 
			
		||||
        <Text variant="b1">{parse(twemoji.parse(name))}</Text>
 | 
			
		||||
        <Text variant="b1">{userId}</Text>
 | 
			
		||||
      </div>
 | 
			
		||||
      <div className="message__time">
 | 
			
		||||
| 
						 | 
				
			
			@ -112,7 +82,7 @@ function MessageReply({ name, color, body }) {
 | 
			
		|||
    <div className="message__reply">
 | 
			
		||||
      <Text variant="b2">
 | 
			
		||||
        <RawIcon color={color} size="extra-small" src={ReplyArrowIC} />
 | 
			
		||||
        <span style={{ color }}>{name}</span>
 | 
			
		||||
        <span style={{ color }}>{parse(twemoji.parse(name))}</span>
 | 
			
		||||
        <>{` ${body}`}</>
 | 
			
		||||
      </Text>
 | 
			
		||||
    </div>
 | 
			
		||||
| 
						 | 
				
			
			@ -132,11 +102,16 @@ function MessageBody({
 | 
			
		|||
  isEdited,
 | 
			
		||||
  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 (
 | 
			
		||||
    <div className="message__body">
 | 
			
		||||
      <div className="text text-b1">
 | 
			
		||||
        { msgType === 'm.emote' && `* ${senderName} ` }
 | 
			
		||||
        { isCustomHTML ? genMarkdown(body) : linkifyContent(body) }
 | 
			
		||||
        { parse(linkified) }
 | 
			
		||||
      </div>
 | 
			
		||||
      { isEdited && <Text className="message__body-edited" variant="b3">(edited)</Text>}
 | 
			
		||||
    </div>
 | 
			
		||||
| 
						 | 
				
			
			@ -383,18 +358,16 @@ function parseReply(rawBody) {
 | 
			
		|||
    body,
 | 
			
		||||
  };
 | 
			
		||||
}
 | 
			
		||||
function getEditedBody(eventId, editedTimeline) {
 | 
			
		||||
  const editedList = editedTimeline.get(eventId);
 | 
			
		||||
  const editedMEvent = editedList[editedList.length - 1];
 | 
			
		||||
function getEditedBody(editedMEvent) {
 | 
			
		||||
  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 parsedContent = parseReply(newContent.body);
 | 
			
		||||
  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 }) {
 | 
			
		||||
| 
						 | 
				
			
			@ -406,7 +379,7 @@ function Message({ mEvent, isBodyOnly, roomTimeline }) {
 | 
			
		|||
  } = roomTimeline;
 | 
			
		||||
 | 
			
		||||
  const className = ['message', (isBodyOnly ? 'message--body-only' : 'message--full')];
 | 
			
		||||
  const content = mEvent.getWireContent();
 | 
			
		||||
  const content = mEvent.getContent();
 | 
			
		||||
  const eventId = mEvent.getId();
 | 
			
		||||
  const msgType = content?.msgtype;
 | 
			
		||||
  const senderId = mEvent.getSender();
 | 
			
		||||
| 
						 | 
				
			
			@ -419,16 +392,18 @@ function Message({ mEvent, isBodyOnly, roomTimeline }) {
 | 
			
		|||
  if (typeof body === 'undefined') return null;
 | 
			
		||||
  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 canIRedact = room.currentState.hasSufficientPowerLevelFor('redact', myPowerlevel);
 | 
			
		||||
 | 
			
		||||
  let [reply, reactions, isCustomHTML] = [null, null, content.format === 'org.matrix.custom.html'];
 | 
			
		||||
  const [isEdited, haveReactions] = [editedTimeline.has(eventId), reactionTimeline.has(eventId)];
 | 
			
		||||
  const isReply = typeof content['m.relates_to']?.['m.in_reply_to'] !== 'undefined';
 | 
			
		||||
  let customHTML = isCustomHTML ? content.formatted_body : null;
 | 
			
		||||
 | 
			
		||||
  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;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -500,7 +475,7 @@ function Message({ mEvent, isBodyOnly, roomTimeline }) {
 | 
			
		|||
          <MessageBody
 | 
			
		||||
            senderName={username}
 | 
			
		||||
            isCustomHTML={isCustomHTML}
 | 
			
		||||
            body={isMedia(mEvent) ? genMediaContent(mEvent) : body}
 | 
			
		||||
            body={isMedia(mEvent) ? genMediaContent(mEvent) : customHTML ?? body}
 | 
			
		||||
            msgType={msgType}
 | 
			
		||||
            isEdited={isEdited}
 | 
			
		||||
          />
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,5 +1,14 @@
 | 
			
		|||
@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,
 | 
			
		||||
.ph-msg {
 | 
			
		||||
  padding: var(--sp-ultra-tight) var(--sp-normal);
 | 
			
		||||
| 
						 | 
				
			
			@ -107,6 +116,10 @@
 | 
			
		|||
  display: flex;
 | 
			
		||||
  align-items: baseline;
 | 
			
		||||
 | 
			
		||||
  & img.emoji {
 | 
			
		||||
    @extend .custom-emoji;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  & .message__profile {
 | 
			
		||||
    min-width: 0;
 | 
			
		||||
    color: var(--tc-surface-high);
 | 
			
		||||
| 
						 | 
				
			
			@ -142,6 +155,10 @@
 | 
			
		|||
  }
 | 
			
		||||
}
 | 
			
		||||
.message__reply {
 | 
			
		||||
  & img.emoji {
 | 
			
		||||
    @extend .custom-emoji;
 | 
			
		||||
    height: 14px;
 | 
			
		||||
  }
 | 
			
		||||
  .text {
 | 
			
		||||
    color: var(--tc-surface-low);
 | 
			
		||||
    white-space: nowrap;
 | 
			
		||||
| 
						 | 
				
			
			@ -163,6 +180,45 @@
 | 
			
		|||
  & a {
 | 
			
		||||
    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 {
 | 
			
		||||
    color: var(--tc-surface-low);
 | 
			
		||||
  }
 | 
			
		||||
| 
						 | 
				
			
			@ -327,10 +383,20 @@
 | 
			
		|||
    @include scrollbar.scroll__h;
 | 
			
		||||
    @include scrollbar.scroll--auto-hide;
 | 
			
		||||
  }
 | 
			
		||||
  & pre code {
 | 
			
		||||
    color: var(--tc-surface-normal) !important;
 | 
			
		||||
  & 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;
 | 
			
		||||
      white-space: pre;
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
  & blockquote {
 | 
			
		||||
    display: inline-block;
 | 
			
		||||
    max-width: 100%;
 | 
			
		||||
    padding-left: var(--sp-extra-tight);
 | 
			
		||||
    border-left: 4px solid var(--bg-surface-active);
 | 
			
		||||
    white-space: initial !important;
 | 
			
		||||
| 
						 | 
				
			
			@ -372,15 +438,21 @@
 | 
			
		|||
    list-style: none;
 | 
			
		||||
  }
 | 
			
		||||
  & table {
 | 
			
		||||
    display: -webkit-box;
 | 
			
		||||
    width: 100%;
 | 
			
		||||
    background-color: var(--bg-surface-hover);
 | 
			
		||||
    border-radius: calc(var(--bo-radius) / 2);
 | 
			
		||||
    border-spacing: 0;
 | 
			
		||||
    border: 1px solid var(--bg-surface-border);
 | 
			
		||||
    @include scrollbar.scroll;
 | 
			
		||||
    @include scrollbar.scroll__h;
 | 
			
		||||
    @include scrollbar.scroll--auto-hide;
 | 
			
		||||
 | 
			
		||||
    & td, & th {
 | 
			
		||||
      padding: var(--sp-extra-tight);
 | 
			
		||||
      border: 1px solid var(--bg-surface-border);
 | 
			
		||||
      border-width: 0 1px 1px 0;
 | 
			
		||||
      white-space: pre;
 | 
			
		||||
      &:last-child {
 | 
			
		||||
        border-width: 0;
 | 
			
		||||
        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 {
 | 
			
		||||
    margin-top: var(--sp-extra-loose);
 | 
			
		||||
    max-width: 640px;
 | 
			
		||||
    width: calc(100% - 88px);
 | 
			
		||||
  }
 | 
			
		||||
  &__name {
 | 
			
		||||
    color: var(--tc-surface-high);
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -106,7 +106,6 @@ function PeopleDrawer({ roomId }) {
 | 
			
		|||
    let isGettingMembers = true;
 | 
			
		||||
    const updateMemberList = (event) => {
 | 
			
		||||
      if (isGettingMembers) return;
 | 
			
		||||
      console.log(event?.event?.room_id);
 | 
			
		||||
      if (event && event?.event?.room_id !== roomId) return;
 | 
			
		||||
      setMemberList(
 | 
			
		||||
        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 */
 | 
			
		||||
import React, { useState, useEffect, useLayoutEffect } from 'react';
 | 
			
		||||
import PropTypes from 'prop-types';
 | 
			
		||||
| 
						 | 
				
			
			@ -8,6 +10,7 @@ import dateFormat from 'dateformat';
 | 
			
		|||
import initMatrix from '../../../client/initMatrix';
 | 
			
		||||
import cons from '../../../client/state/cons';
 | 
			
		||||
import { diffMinutes, isNotInSameDay } from '../../../util/common';
 | 
			
		||||
import { openProfileViewer } from '../../../client/action/navigation';
 | 
			
		||||
 | 
			
		||||
import Divider from '../../atoms/divider/Divider';
 | 
			
		||||
import { Message, PlaceholderMessage } from '../../molecules/message/Message';
 | 
			
		||||
| 
						 | 
				
			
			@ -188,8 +191,16 @@ function RoomViewContent({
 | 
			
		|||
    }
 | 
			
		||||
  }, [onStateUpdate]);
 | 
			
		||||
 | 
			
		||||
  const handleOnClickCapture = (e) => {
 | 
			
		||||
    const { target } = e;
 | 
			
		||||
    const userId = target.getAttribute('data-mx-pill');
 | 
			
		||||
    if (!userId) return;
 | 
			
		||||
 | 
			
		||||
    openProfileViewer(userId, roomId);
 | 
			
		||||
  };
 | 
			
		||||
 | 
			
		||||
  let prevMEvent = null;
 | 
			
		||||
  function renderMessage(mEvent) {
 | 
			
		||||
  const renderMessage = (mEvent) => {
 | 
			
		||||
    const isContentOnly = (prevMEvent !== null && prevMEvent.getType() !== 'm.room.member'
 | 
			
		||||
      && diffMinutes(mEvent.getDate(), prevMEvent.getDate()) <= MAX_MSG_DIFF_MINUTES
 | 
			
		||||
      && prevMEvent.getSender() === mEvent.getSender()
 | 
			
		||||
| 
						 | 
				
			
			@ -222,7 +233,7 @@ function RoomViewContent({
 | 
			
		|||
        <Message mEvent={mEvent} isBodyOnly={isContentOnly} roomTimeline={roomTimeline} />
 | 
			
		||||
      </React.Fragment>
 | 
			
		||||
    );
 | 
			
		||||
  }
 | 
			
		||||
  };
 | 
			
		||||
 | 
			
		||||
  const renderTimeline = () => {
 | 
			
		||||
    const { timeline } = roomTimeline;
 | 
			
		||||
| 
						 | 
				
			
			@ -249,7 +260,7 @@ function RoomViewContent({
 | 
			
		|||
  };
 | 
			
		||||
 | 
			
		||||
  return (
 | 
			
		||||
    <div className="room-view__content">
 | 
			
		||||
    <div className="room-view__content" onClick={handleOnClickCapture}>
 | 
			
		||||
      <div className="timeline__wrapper">
 | 
			
		||||
        { renderTimeline() }
 | 
			
		||||
      </div>
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue