Merge remote-tracking branch 'origin/develop' into travis/voice-messages/download
This commit is contained in:
commit
5994111e5d
325 changed files with 5224 additions and 4075 deletions
218
src/components/views/messages/CallEvent.tsx
Normal file
218
src/components/views/messages/CallEvent.tsx
Normal file
|
@ -0,0 +1,218 @@
|
|||
/*
|
||||
Copyright 2021 Šimon Brandner <simon.bra.ag@gmail.com>
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
|
||||
import { MatrixEvent } from "matrix-js-sdk/src/models/event";
|
||||
import { _t, _td } from '../../../languageHandler';
|
||||
import MemberAvatar from '../avatars/MemberAvatar';
|
||||
import CallEventGrouper, { CallEventGrouperEvent, CustomCallState } from '../../structures/CallEventGrouper';
|
||||
import AccessibleButton from '../elements/AccessibleButton';
|
||||
import { CallErrorCode, CallState } from 'matrix-js-sdk/src/webrtc/call';
|
||||
import InfoTooltip, { InfoTooltipKind } from '../elements/InfoTooltip';
|
||||
import classNames from 'classnames';
|
||||
import AccessibleTooltipButton from '../elements/AccessibleTooltipButton';
|
||||
|
||||
interface IProps {
|
||||
mxEvent: MatrixEvent;
|
||||
callEventGrouper: CallEventGrouper;
|
||||
}
|
||||
|
||||
interface IState {
|
||||
callState: CallState | CustomCallState;
|
||||
silenced: boolean;
|
||||
}
|
||||
|
||||
const TEXTUAL_STATES: Map<CallState | CustomCallState, string> = new Map([
|
||||
[CallState.Connected, _td("Connected")],
|
||||
[CallState.Connecting, _td("Connecting")],
|
||||
]);
|
||||
|
||||
export default class CallEvent extends React.Component<IProps, IState> {
|
||||
constructor(props: IProps) {
|
||||
super(props);
|
||||
|
||||
this.state = {
|
||||
callState: this.props.callEventGrouper.state,
|
||||
silenced: false,
|
||||
};
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
this.props.callEventGrouper.addListener(CallEventGrouperEvent.StateChanged, this.onStateChanged);
|
||||
this.props.callEventGrouper.addListener(CallEventGrouperEvent.SilencedChanged, this.onSilencedChanged);
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
this.props.callEventGrouper.removeListener(CallEventGrouperEvent.StateChanged, this.onStateChanged);
|
||||
this.props.callEventGrouper.removeListener(CallEventGrouperEvent.SilencedChanged, this.onSilencedChanged);
|
||||
}
|
||||
|
||||
private onSilencedChanged = (newState) => {
|
||||
this.setState({ silenced: newState });
|
||||
};
|
||||
|
||||
private onStateChanged = (newState: CallState) => {
|
||||
this.setState({ callState: newState });
|
||||
};
|
||||
|
||||
private renderContent(state: CallState | CustomCallState): JSX.Element {
|
||||
if (state === CallState.Ringing) {
|
||||
const silenceClass = classNames({
|
||||
"mx_CallEvent_iconButton": true,
|
||||
"mx_CallEvent_unSilence": this.state.silenced,
|
||||
"mx_CallEvent_silence": !this.state.silenced,
|
||||
});
|
||||
|
||||
return (
|
||||
<div className="mx_CallEvent_content">
|
||||
<AccessibleTooltipButton
|
||||
className={silenceClass}
|
||||
onClick={this.props.callEventGrouper.toggleSilenced}
|
||||
title={this.state.silenced ? _t("Sound on"): _t("Silence call")}
|
||||
/>
|
||||
<AccessibleButton
|
||||
className="mx_CallEvent_content_button mx_CallEvent_content_button_reject"
|
||||
onClick={this.props.callEventGrouper.rejectCall}
|
||||
kind="danger"
|
||||
>
|
||||
<span> { _t("Decline") } </span>
|
||||
</AccessibleButton>
|
||||
<AccessibleButton
|
||||
className="mx_CallEvent_content_button mx_CallEvent_content_button_answer"
|
||||
onClick={this.props.callEventGrouper.answerCall}
|
||||
kind="primary"
|
||||
>
|
||||
<span> { _t("Accept") } </span>
|
||||
</AccessibleButton>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
if (state === CallState.Ended) {
|
||||
const hangupReason = this.props.callEventGrouper.hangupReason;
|
||||
|
||||
if ([CallErrorCode.UserHangup, "user hangup"].includes(hangupReason) || !hangupReason) {
|
||||
// workaround for https://github.com/vector-im/element-web/issues/5178
|
||||
// it seems Android randomly sets a reason of "user hangup" which is
|
||||
// interpreted as an error code :(
|
||||
// https://github.com/vector-im/riot-android/issues/2623
|
||||
// Also the correct hangup code as of VoIP v1 (with underscore)
|
||||
// Also, if we don't have a reason
|
||||
return (
|
||||
<div className="mx_CallEvent_content">
|
||||
{ _t("This call has ended") }
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
let reason;
|
||||
if (hangupReason === CallErrorCode.IceFailed) {
|
||||
// We couldn't establish a connection at all
|
||||
reason = _t("Could not connect media");
|
||||
} else if (hangupReason === "ice_timeout") {
|
||||
// We established a connection but it died
|
||||
reason = _t("Connection failed");
|
||||
} else if (hangupReason === CallErrorCode.NoUserMedia) {
|
||||
// The other side couldn't open capture devices
|
||||
reason = _t("Their device couldn't start the camera or microphone");
|
||||
} else if (hangupReason === "unknown_error") {
|
||||
// An error code the other side doesn't have a way to express
|
||||
// (as opposed to an error code they gave but we don't know about,
|
||||
// in which case we show the error code)
|
||||
reason = _t("An unknown error occurred");
|
||||
} else if (hangupReason === CallErrorCode.InviteTimeout) {
|
||||
reason = _t("No answer");
|
||||
} else if (hangupReason === CallErrorCode.UserBusy) {
|
||||
reason = _t("The user you called is busy.");
|
||||
} else {
|
||||
reason = _t('Unknown failure: %(reason)s)', { reason: hangupReason });
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="mx_CallEvent_content">
|
||||
<InfoTooltip
|
||||
tooltip={reason}
|
||||
className="mx_CallEvent_content_tooltip"
|
||||
kind={InfoTooltipKind.Warning}
|
||||
/>
|
||||
{ _t("This call has failed") }
|
||||
</div>
|
||||
);
|
||||
}
|
||||
if (Array.from(TEXTUAL_STATES.keys()).includes(state)) {
|
||||
return (
|
||||
<div className="mx_CallEvent_content">
|
||||
{ TEXTUAL_STATES.get(state) }
|
||||
</div>
|
||||
);
|
||||
}
|
||||
if (state === CustomCallState.Missed) {
|
||||
return (
|
||||
<div className="mx_CallEvent_content">
|
||||
{ _t("You missed this call") }
|
||||
<AccessibleButton
|
||||
className="mx_CallEvent_content_button mx_CallEvent_content_button_callBack"
|
||||
onClick={this.props.callEventGrouper.callBack}
|
||||
kind="primary"
|
||||
>
|
||||
<span> { _t("Call back") } </span>
|
||||
</AccessibleButton>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="mx_CallEvent_content">
|
||||
{ _t("The call is in an unknown state!") }
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
render() {
|
||||
const event = this.props.mxEvent;
|
||||
const sender = event.sender ? event.sender.name : event.getSender();
|
||||
const isVoice = this.props.callEventGrouper.isVoice;
|
||||
const callType = isVoice ? _t("Voice call") : _t("Video call");
|
||||
const content = this.renderContent(this.state.callState);
|
||||
const className = classNames({
|
||||
mx_CallEvent: true,
|
||||
mx_CallEvent_voice: isVoice,
|
||||
mx_CallEvent_video: !isVoice,
|
||||
});
|
||||
|
||||
return (
|
||||
<div className={className}>
|
||||
<div className="mx_CallEvent_info">
|
||||
<MemberAvatar
|
||||
member={event.sender}
|
||||
width={32}
|
||||
height={32}
|
||||
/>
|
||||
<div className="mx_CallEvent_info_basic">
|
||||
<div className="mx_CallEvent_sender">
|
||||
{ sender }
|
||||
</div>
|
||||
<div className="mx_CallEvent_type">
|
||||
<div className="mx_CallEvent_type_icon"></div>
|
||||
{ callType }
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{ content }
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
|
@ -110,20 +110,20 @@ export default class EditHistoryMessage extends React.PureComponent {
|
|||
if (!this.props.mxEvent.isRedacted() && !this.props.isBaseEvent && this.state.canRedact) {
|
||||
redactButton = (
|
||||
<AccessibleButton onClick={this._onRedactClick}>
|
||||
{_t("Remove")}
|
||||
{ _t("Remove") }
|
||||
</AccessibleButton>
|
||||
);
|
||||
}
|
||||
const viewSourceButton = (
|
||||
<AccessibleButton onClick={this._onViewSourceClick}>
|
||||
{_t("View Source")}
|
||||
{ _t("View Source") }
|
||||
</AccessibleButton>
|
||||
);
|
||||
// disabled remove button when not allowed
|
||||
return (
|
||||
<div className="mx_MessageActionBar">
|
||||
{redactButton}
|
||||
{viewSourceButton}
|
||||
{ redactButton }
|
||||
{ viewSourceButton }
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
@ -146,11 +146,11 @@ export default class EditHistoryMessage extends React.PureComponent {
|
|||
contentContainer = (
|
||||
<div className="mx_EventTile_content" ref={this._content}>*
|
||||
<span className="mx_MEmoteBody_sender">{ name }</span>
|
||||
{contentElements}
|
||||
{ contentElements }
|
||||
</div>
|
||||
);
|
||||
} else {
|
||||
contentContainer = <div className="mx_EventTile_content" ref={this._content}>{contentElements}</div>;
|
||||
contentContainer = <div className="mx_EventTile_content" ref={this._content}>{ contentElements }</div>;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -165,7 +165,7 @@ export default class EditHistoryMessage extends React.PureComponent {
|
|||
<li>
|
||||
<div className={classes}>
|
||||
<div className="mx_EventTile_line">
|
||||
<span className="mx_MessageTimestamp">{timestamp}</span>
|
||||
<span className="mx_MessageTimestamp">{ timestamp }</span>
|
||||
{ contentContainer }
|
||||
{ this._renderActionBar() }
|
||||
</div>
|
||||
|
|
|
@ -273,13 +273,6 @@ export default class MImageBody extends React.Component<IBodyProps, IState> {
|
|||
this.downloadImage();
|
||||
this.setState({ showImage: true });
|
||||
} // else don't download anything because we don't want to display anything.
|
||||
|
||||
this._afterComponentDidMount();
|
||||
}
|
||||
|
||||
// To be overridden by subclasses (e.g. MStickerBody) for further
|
||||
// initialisation after componentDidMount
|
||||
_afterComponentDidMount() {
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
|
@ -394,7 +387,7 @@ export default class MImageBody extends React.Component<IBodyProps, IState> {
|
|||
// Overidden by MStickerBody
|
||||
protected wrapImage(contentUrl: string, children: JSX.Element): JSX.Element {
|
||||
return <a href={contentUrl} onClick={this.onClick}>
|
||||
{children}
|
||||
{ children }
|
||||
</a>;
|
||||
}
|
||||
|
||||
|
@ -402,9 +395,9 @@ export default class MImageBody extends React.Component<IBodyProps, IState> {
|
|||
protected getPlaceholder(width: number, height: number): JSX.Element {
|
||||
const blurhash = this.props.mxEvent.getContent().info[BLURHASH_FIELD];
|
||||
if (blurhash) return <Blurhash hash={blurhash} width={width} height={height} />;
|
||||
return <div className="mx_MImageBody_thumbnail_spinner">
|
||||
return (
|
||||
<InlineSpinner w={32} h={32} />
|
||||
</div>;
|
||||
);
|
||||
}
|
||||
|
||||
// Overidden by MStickerBody
|
||||
|
@ -464,7 +457,7 @@ export class HiddenImagePlaceholder extends React.PureComponent<PlaceholderIProp
|
|||
<div className={className} style={{ maxWidth: maxWidth }}>
|
||||
<div className='mx_HiddenImagePlaceholder_button'>
|
||||
<span className='mx_HiddenImagePlaceholder_eye' />
|
||||
<span>{_t("Show image")}</span>
|
||||
<span>{ _t("Show image") }</span>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
|
|
@ -131,7 +131,7 @@ export default class MKeyVerificationRequest extends React.Component<IProps> {
|
|||
const accepted = request.ready || request.started || request.done;
|
||||
if (accepted) {
|
||||
stateLabel = (<AccessibleButton onClick={this.openRequest}>
|
||||
{this.acceptedLabel(request.receivingUserId)}
|
||||
{ this.acceptedLabel(request.receivingUserId) }
|
||||
</AccessibleButton>);
|
||||
} else if (request.cancelled) {
|
||||
stateLabel = this.cancelledLabel(request.cancellingUserId);
|
||||
|
@ -140,7 +140,7 @@ export default class MKeyVerificationRequest extends React.Component<IProps> {
|
|||
} else if (request.declining) {
|
||||
stateLabel = _t("Declining …");
|
||||
}
|
||||
stateNode = (<div className="mx_cryptoEvent_state">{stateLabel}</div>);
|
||||
stateNode = (<div className="mx_cryptoEvent_state">{ stateLabel }</div>);
|
||||
}
|
||||
|
||||
if (!request.initiatedByMe) {
|
||||
|
@ -150,10 +150,10 @@ export default class MKeyVerificationRequest extends React.Component<IProps> {
|
|||
if (request.canAccept) {
|
||||
stateNode = (<div className="mx_cryptoEvent_buttons">
|
||||
<AccessibleButton kind="danger" onClick={this.onRejectClicked}>
|
||||
{_t("Decline")}
|
||||
{ _t("Decline") }
|
||||
</AccessibleButton>
|
||||
<AccessibleButton kind="primary" onClick={this.onAcceptClicked}>
|
||||
{_t("Accept")}
|
||||
{ _t("Accept") }
|
||||
</AccessibleButton>
|
||||
</div>);
|
||||
}
|
||||
|
|
|
@ -297,7 +297,7 @@ export default class MessageActionBar extends React.PureComponent {
|
|||
|
||||
// aria-live=off to not have this read out automatically as navigating around timeline, gets repetitive.
|
||||
return <Toolbar className="mx_MessageActionBar" aria-label={_t("Message Actions")} aria-live="off">
|
||||
{toolbarOpts}
|
||||
{ toolbarOpts }
|
||||
</Toolbar>;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -45,7 +45,7 @@ export default class MessageTimestamp extends React.Component<IProps> {
|
|||
title={formatFullDate(date, this.props.showTwelveHour)}
|
||||
aria-hidden={true}
|
||||
>
|
||||
{timestamp}
|
||||
{ timestamp }
|
||||
</span>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -41,10 +41,10 @@ export default class MjolnirBody extends React.Component {
|
|||
|
||||
render() {
|
||||
return (
|
||||
<div className='mx_MjolnirBody'><i>{_t(
|
||||
<div className='mx_MjolnirBody'><i>{ _t(
|
||||
"You have ignored this user, so their message is hidden. <a>Show anyways.</a>",
|
||||
{}, { a: (sub) => <a href="#" onClick={this._onAllowClick}>{sub}</a> },
|
||||
)}</i></div>
|
||||
{}, { a: (sub) => <a href="#" onClick={this._onAllowClick}>{ sub }</a> },
|
||||
) }</i></div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -199,7 +199,7 @@ export default class ReactionsRow extends React.PureComponent<IProps, IState> {
|
|||
href="#"
|
||||
onClick={this.onShowAllClick}
|
||||
>
|
||||
{_t("Show all")}
|
||||
{ _t("Show all") }
|
||||
</a>;
|
||||
}
|
||||
|
||||
|
|
|
@ -142,12 +142,12 @@ export default class ReactionsRowButton extends React.PureComponent<IProps, ISta
|
|||
onMouseLeave={this.onMouseLeave}
|
||||
>
|
||||
<span className="mx_ReactionsRowButton_content" aria-hidden="true">
|
||||
{content}
|
||||
{ content }
|
||||
</span>
|
||||
<span className="mx_ReactionsRowButton_count" aria-hidden="true">
|
||||
{count}
|
||||
{ count }
|
||||
</span>
|
||||
{tooltip}
|
||||
{ tooltip }
|
||||
</AccessibleButton>;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -51,7 +51,7 @@ export default class ReactionsRowButtonTooltip extends React.PureComponent<IProp
|
|||
senders.push(name);
|
||||
}
|
||||
const shortName = unicodeToShortcode(content);
|
||||
tooltipLabel = <div>{_t(
|
||||
tooltipLabel = <div>{ _t(
|
||||
"<reactors/><reactedWith>reacted with %(shortName)s</reactedWith>",
|
||||
{
|
||||
shortName,
|
||||
|
@ -59,7 +59,7 @@ export default class ReactionsRowButtonTooltip extends React.PureComponent<IProp
|
|||
{
|
||||
reactors: () => {
|
||||
return <div className="mx_Tooltip_title">
|
||||
{formatCommaSeparatedList(senders, 6)}
|
||||
{ formatCommaSeparatedList(senders, 6) }
|
||||
</div>;
|
||||
},
|
||||
reactedWith: (sub) => {
|
||||
|
@ -67,11 +67,11 @@ export default class ReactionsRowButtonTooltip extends React.PureComponent<IProp
|
|||
return null;
|
||||
}
|
||||
return <div className="mx_Tooltip_sub">
|
||||
{sub}
|
||||
{ sub }
|
||||
</div>;
|
||||
},
|
||||
},
|
||||
)}</div>;
|
||||
) }</div>;
|
||||
}
|
||||
|
||||
let tooltip;
|
||||
|
|
|
@ -56,7 +56,7 @@ export default class RoomCreate extends React.Component {
|
|||
const predecessorPermalink = permalinkCreator.forEvent(predecessor['event_id']);
|
||||
const link = (
|
||||
<a href={predecessorPermalink} onClick={this._onLinkClicked}>
|
||||
{_t("Click here to see older messages.")}
|
||||
{ _t("Click here to see older messages.") }
|
||||
</a>
|
||||
);
|
||||
|
||||
|
|
|
@ -450,10 +450,10 @@ export default class TextualBody extends React.Component<IBodyProps, IState> {
|
|||
|
||||
const tooltip = <div>
|
||||
<div className="mx_Tooltip_title">
|
||||
{_t("Edited at %(date)s", { date: dateString })}
|
||||
{ _t("Edited at %(date)s", { date: dateString }) }
|
||||
</div>
|
||||
<div className="mx_Tooltip_sub">
|
||||
{_t("Click to view edits")}
|
||||
{ _t("Click to view edits") }
|
||||
</div>
|
||||
</div>;
|
||||
|
||||
|
@ -464,7 +464,7 @@ export default class TextualBody extends React.Component<IBodyProps, IState> {
|
|||
title={_t("Edited at %(date)s. Click to view edits.", { date: dateString })}
|
||||
tooltip={tooltip}
|
||||
>
|
||||
<span>{`(${_t("edited")})`}</span>
|
||||
<span>{ `(${_t("edited")})` }</span>
|
||||
</AccessibleTooltipButton>
|
||||
);
|
||||
}
|
||||
|
@ -488,8 +488,8 @@ export default class TextualBody extends React.Component<IBodyProps, IState> {
|
|||
});
|
||||
if (this.props.replacingEventId) {
|
||||
body = <>
|
||||
{body}
|
||||
{this.renderEditedMarker()}
|
||||
{ body }
|
||||
{ this.renderEditedMarker() }
|
||||
</>;
|
||||
}
|
||||
|
||||
|
|
|
@ -14,9 +14,10 @@ See the License for the specific language governing permissions and
|
|||
limitations under the License.
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
import { MatrixEvent } from 'matrix-js-sdk/src/models/event';
|
||||
import React from "react";
|
||||
import { MatrixEvent } from "matrix-js-sdk/src/models/event";
|
||||
|
||||
import RoomContext from "../../../contexts/RoomContext";
|
||||
import * as TextForEvent from "../../../TextForEvent";
|
||||
import { replaceableComponent } from "../../../utils/replaceableComponent";
|
||||
|
||||
|
@ -26,11 +27,11 @@ interface IProps {
|
|||
|
||||
@replaceableComponent("views.messages.TextualEvent")
|
||||
export default class TextualEvent extends React.Component<IProps> {
|
||||
render() {
|
||||
const text = TextForEvent.textForEvent(this.props.mxEvent, true);
|
||||
if (!text || (text as string).length === 0) return null;
|
||||
return (
|
||||
<div className="mx_TextualEvent">{ text }</div>
|
||||
);
|
||||
static contextType = RoomContext;
|
||||
|
||||
public render() {
|
||||
const text = TextForEvent.textForEvent(this.props.mxEvent, true, this.context?.showHiddenEventsInTimeline);
|
||||
if (!text) return null;
|
||||
return <div className="mx_TextualEvent">{ text }</div>;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -67,14 +67,14 @@ export default class TileErrorBoundary extends React.Component<IProps, IState> {
|
|||
let submitLogsButton;
|
||||
if (SdkConfig.get().bug_report_endpoint_url) {
|
||||
submitLogsButton = <a onClick={this.onBugReport} href="#">
|
||||
{_t("Submit logs")}
|
||||
{ _t("Submit logs") }
|
||||
</a>;
|
||||
}
|
||||
|
||||
return (<div className={classNames(classes)}>
|
||||
<div className="mx_EventTile_line">
|
||||
<span>
|
||||
{_t("Can't load this message")}
|
||||
{ _t("Can't load this message") }
|
||||
{ mxEvent && ` (${mxEvent.getType()})` }
|
||||
{ submitLogsButton }
|
||||
</span>
|
||||
|
|
|
@ -16,12 +16,19 @@ limitations under the License.
|
|||
*/
|
||||
|
||||
import React, { forwardRef } from "react";
|
||||
import { MatrixEvent } from "matrix-js-sdk/src";
|
||||
|
||||
export default forwardRef(({ mxEvent }, ref) => {
|
||||
interface IProps {
|
||||
mxEvent: MatrixEvent;
|
||||
children?: React.ReactNode;
|
||||
}
|
||||
|
||||
export default forwardRef(({ mxEvent, children }: IProps, ref: React.RefObject<HTMLSpanElement>) => {
|
||||
const text = mxEvent.getContent().body;
|
||||
return (
|
||||
<span className="mx_UnknownBody" ref={ref}>
|
||||
{ text }
|
||||
{ children }
|
||||
</span>
|
||||
);
|
||||
});
|
|
@ -60,9 +60,9 @@ export default class ViewSourceEvent extends React.PureComponent {
|
|||
|
||||
let content;
|
||||
if (expanded) {
|
||||
content = <pre>{JSON.stringify(mxEvent, null, 4)}</pre>;
|
||||
content = <pre>{ JSON.stringify(mxEvent, null, 4) }</pre>;
|
||||
} else {
|
||||
content = <code>{`{ "type": ${mxEvent.getType()} }`}</code>;
|
||||
content = <code>{ `{ "type": ${mxEvent.getType()} }` }</code>;
|
||||
}
|
||||
|
||||
const classes = classNames("mx_ViewSourceEvent mx_EventTile_content", {
|
||||
|
@ -70,7 +70,7 @@ export default class ViewSourceEvent extends React.PureComponent {
|
|||
});
|
||||
|
||||
return <span className={classes}>
|
||||
{content}
|
||||
{ content }
|
||||
<a
|
||||
className="mx_ViewSourceEvent_toggle"
|
||||
href="#"
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue