Merge remote-tracking branch 'origin/release-v3.43.0'
# Conflicts: # CHANGELOG.md # package.json # src/components/views/messages/MessageActionBar.tsx
This commit is contained in:
commit
6b0156d876
680 changed files with 12433 additions and 5108 deletions
|
@ -21,7 +21,6 @@ import { logger } from "matrix-js-sdk/src/logger";
|
|||
|
||||
import { _t } from '../../../languageHandler';
|
||||
import { formatFullDateNoTime } from '../../../DateUtils';
|
||||
import { replaceableComponent } from "../../../utils/replaceableComponent";
|
||||
import { MatrixClientPeg } from '../../../MatrixClientPeg';
|
||||
import dis from '../../../dispatcher/dispatcher';
|
||||
import { Action } from '../../../dispatcher/actions';
|
||||
|
@ -61,7 +60,6 @@ interface IState {
|
|||
jumpToDateEnabled: boolean;
|
||||
}
|
||||
|
||||
@replaceableComponent("views.messages.DateSeparator")
|
||||
export default class DateSeparator extends React.Component<IProps, IState> {
|
||||
private settingWatcherRef = null;
|
||||
|
||||
|
|
|
@ -58,8 +58,8 @@ export default class DisambiguatedProfile extends React.Component<IProps> {
|
|||
});
|
||||
|
||||
return (
|
||||
<div className="mx_DisambiguatedProfile" dir="auto" onClick={onClick}>
|
||||
<span className={displayNameClasses}>
|
||||
<div className="mx_DisambiguatedProfile" onClick={onClick}>
|
||||
<span className={displayNameClasses} dir="auto">
|
||||
{ rawDisplayName }
|
||||
</span>
|
||||
{ mxidElement }
|
||||
|
|
|
@ -22,7 +22,6 @@ import { MediaEventHelper } from "../../../utils/MediaEventHelper";
|
|||
import { RovingAccessibleTooltipButton } from "../../../accessibility/RovingTabIndex";
|
||||
import Spinner from "../elements/Spinner";
|
||||
import { _t, _td } from "../../../languageHandler";
|
||||
import { replaceableComponent } from "../../../utils/replaceableComponent";
|
||||
import { FileDownloader } from "../../../utils/FileDownloader";
|
||||
|
||||
interface IProps {
|
||||
|
@ -40,7 +39,6 @@ interface IState {
|
|||
tooltip: string;
|
||||
}
|
||||
|
||||
@replaceableComponent("views.messages.DownloadActionButton")
|
||||
export default class DownloadActionButton extends React.PureComponent<IProps, IState> {
|
||||
private downloader = new FileDownloader();
|
||||
|
||||
|
|
|
@ -26,7 +26,6 @@ import { _t } from '../../../languageHandler';
|
|||
import { MatrixClientPeg } from '../../../MatrixClientPeg';
|
||||
import Modal from '../../../Modal';
|
||||
import RedactedBody from "./RedactedBody";
|
||||
import { replaceableComponent } from "../../../utils/replaceableComponent";
|
||||
import AccessibleButton from "../elements/AccessibleButton";
|
||||
import ConfirmAndWaitRedactDialog from "../dialogs/ConfirmAndWaitRedactDialog";
|
||||
import ViewSource from "../../structures/ViewSource";
|
||||
|
@ -50,7 +49,6 @@ interface IState {
|
|||
sendStatus: EventStatus;
|
||||
}
|
||||
|
||||
@replaceableComponent("views.messages.EditHistoryMessage")
|
||||
export default class EditHistoryMessage extends React.PureComponent<IProps, IState> {
|
||||
private content = createRef<HTMLDivElement>();
|
||||
private pills: Element[] = [];
|
||||
|
|
|
@ -14,6 +14,7 @@ See the License for the specific language governing permissions and
|
|||
limitations under the License.
|
||||
*/
|
||||
|
||||
import React, { LegacyRef } from "react";
|
||||
import { MatrixEvent } from "matrix-js-sdk/src/models/event";
|
||||
import { Relations } from "matrix-js-sdk/src/models/relations";
|
||||
|
||||
|
@ -52,4 +53,6 @@ export interface IBodyProps {
|
|||
|
||||
// helper function to access relations for this event
|
||||
getRelationsForEvent?: (eventId: string, relationType: string, eventType: string) => Relations;
|
||||
|
||||
ref?: React.RefObject<any> | LegacyRef<any>;
|
||||
}
|
||||
|
|
|
@ -17,7 +17,6 @@ limitations under the License.
|
|||
import React from "react";
|
||||
import { logger } from "matrix-js-sdk/src/logger";
|
||||
|
||||
import { replaceableComponent } from "../../../utils/replaceableComponent";
|
||||
import { Playback } from "../../../audio/Playback";
|
||||
import InlineSpinner from '../elements/InlineSpinner';
|
||||
import { _t } from "../../../languageHandler";
|
||||
|
@ -35,7 +34,6 @@ interface IState {
|
|||
playback?: Playback;
|
||||
}
|
||||
|
||||
@replaceableComponent("views.messages.MAudioBody")
|
||||
export default class MAudioBody extends React.PureComponent<IBodyProps, IState> {
|
||||
static contextType = RoomContext;
|
||||
public context!: React.ContextType<typeof RoomContext>;
|
||||
|
|
162
src/components/views/messages/MBeaconBody.tsx
Normal file
162
src/components/views/messages/MBeaconBody.tsx
Normal file
|
@ -0,0 +1,162 @@
|
|||
/*
|
||||
Copyright 2022 The Matrix.org Foundation C.I.C.
|
||||
|
||||
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, { useContext, useEffect, useState } from 'react';
|
||||
import { Beacon, BeaconEvent, MatrixEvent } from 'matrix-js-sdk/src/matrix';
|
||||
import { BeaconLocationState } from 'matrix-js-sdk/src/content-helpers';
|
||||
import { randomString } from 'matrix-js-sdk/src/randomstring';
|
||||
|
||||
import { Icon as LocationMarkerIcon } from '../../../../res/img/element-icons/location.svg';
|
||||
import MatrixClientContext from '../../../contexts/MatrixClientContext';
|
||||
import { useEventEmitterState } from '../../../hooks/useEventEmitter';
|
||||
import { _t } from '../../../languageHandler';
|
||||
import Modal from '../../../Modal';
|
||||
import { useBeacon } from '../../../utils/beacon';
|
||||
import { isSelfLocation } from '../../../utils/location';
|
||||
import { BeaconDisplayStatus, getBeaconDisplayStatus } from '../beacon/displayStatus';
|
||||
import BeaconStatus from '../beacon/BeaconStatus';
|
||||
import Spinner from '../elements/Spinner';
|
||||
import Map from '../location/Map';
|
||||
import SmartMarker from '../location/SmartMarker';
|
||||
import OwnBeaconStatus from '../beacon/OwnBeaconStatus';
|
||||
import BeaconViewDialog from '../beacon/BeaconViewDialog';
|
||||
import { IBodyProps } from "./IBodyProps";
|
||||
|
||||
const useBeaconState = (beaconInfoEvent: MatrixEvent): {
|
||||
beacon?: Beacon;
|
||||
description?: string;
|
||||
latestLocationState?: BeaconLocationState;
|
||||
isLive?: boolean;
|
||||
} => {
|
||||
const beacon = useBeacon(beaconInfoEvent);
|
||||
|
||||
const isLive = useEventEmitterState(
|
||||
beacon,
|
||||
BeaconEvent.LivenessChange,
|
||||
() => beacon?.isLive);
|
||||
|
||||
const latestLocationState = useEventEmitterState(
|
||||
beacon,
|
||||
BeaconEvent.LocationUpdate,
|
||||
() => beacon?.latestLocationState);
|
||||
|
||||
if (!beacon) {
|
||||
return {};
|
||||
}
|
||||
|
||||
const { description } = beacon.beaconInfo;
|
||||
|
||||
return {
|
||||
beacon,
|
||||
description,
|
||||
isLive,
|
||||
latestLocationState,
|
||||
};
|
||||
};
|
||||
|
||||
// multiple instances of same map might be in document
|
||||
// eg thread and main timeline, reply
|
||||
// maplibregl needs a unique id to attach the map instance to
|
||||
const useUniqueId = (eventId: string): string => {
|
||||
const [id, setId] = useState(`${eventId}_${randomString(8)}`);
|
||||
|
||||
useEffect(() => {
|
||||
setId(`${eventId}_${randomString(8)}`);
|
||||
}, [eventId]);
|
||||
|
||||
return id;
|
||||
};
|
||||
|
||||
const MBeaconBody: React.FC<IBodyProps> = React.forwardRef(({ mxEvent }, ref) => {
|
||||
const {
|
||||
beacon,
|
||||
isLive,
|
||||
latestLocationState,
|
||||
} = useBeaconState(mxEvent);
|
||||
const mapId = useUniqueId(mxEvent.getId());
|
||||
|
||||
const matrixClient = useContext(MatrixClientContext);
|
||||
const [error, setError] = useState<Error>();
|
||||
const displayStatus = getBeaconDisplayStatus(isLive, latestLocationState, error);
|
||||
const markerRoomMember = isSelfLocation(mxEvent.getContent()) ? mxEvent.sender : undefined;
|
||||
const isOwnBeacon = beacon?.beaconInfoOwner === matrixClient.getUserId();
|
||||
|
||||
const onClick = () => {
|
||||
if (displayStatus !== BeaconDisplayStatus.Active) {
|
||||
return;
|
||||
}
|
||||
Modal.createTrackedDialog(
|
||||
'Beacon View',
|
||||
'',
|
||||
BeaconViewDialog,
|
||||
{
|
||||
roomId: mxEvent.getRoomId(),
|
||||
matrixClient,
|
||||
focusBeacon: beacon,
|
||||
},
|
||||
"mx_BeaconViewDialog_wrapper",
|
||||
false, // isPriority
|
||||
true, // isStatic
|
||||
);
|
||||
};
|
||||
|
||||
return (
|
||||
<div className='mx_MBeaconBody' ref={ref}>
|
||||
{ displayStatus === BeaconDisplayStatus.Active ?
|
||||
<Map
|
||||
id={mapId}
|
||||
centerGeoUri={latestLocationState.uri}
|
||||
onError={setError}
|
||||
onClick={onClick}
|
||||
className="mx_MBeaconBody_map"
|
||||
>
|
||||
{
|
||||
({ map }) =>
|
||||
<SmartMarker
|
||||
map={map}
|
||||
id={`${mapId}-marker`}
|
||||
geoUri={latestLocationState.uri}
|
||||
roomMember={markerRoomMember}
|
||||
useMemberColor
|
||||
/>
|
||||
}
|
||||
</Map>
|
||||
: <div className='mx_MBeaconBody_map mx_MBeaconBody_mapFallback'>
|
||||
{ displayStatus === BeaconDisplayStatus.Loading ?
|
||||
<Spinner h={32} w={32} /> :
|
||||
<LocationMarkerIcon className='mx_MBeaconBody_mapFallbackIcon' />
|
||||
}
|
||||
</div>
|
||||
}
|
||||
{ isOwnBeacon ?
|
||||
<OwnBeaconStatus
|
||||
className='mx_MBeaconBody_chin'
|
||||
beacon={beacon}
|
||||
displayStatus={displayStatus}
|
||||
/> :
|
||||
<BeaconStatus
|
||||
className='mx_MBeaconBody_chin'
|
||||
beacon={beacon}
|
||||
displayStatus={displayStatus}
|
||||
label={_t('View live location')}
|
||||
/>
|
||||
}
|
||||
</div>
|
||||
);
|
||||
});
|
||||
|
||||
export default MBeaconBody;
|
||||
|
|
@ -21,7 +21,6 @@ import { logger } from "matrix-js-sdk/src/logger";
|
|||
import { _t } from '../../../languageHandler';
|
||||
import Modal from '../../../Modal';
|
||||
import AccessibleButton from "../elements/AccessibleButton";
|
||||
import { replaceableComponent } from "../../../utils/replaceableComponent";
|
||||
import { mediaFromContent } from "../../../customisations/Media";
|
||||
import ErrorDialog from "../dialogs/ErrorDialog";
|
||||
import { presentableTextForFile } from "../../../utils/FileUtils";
|
||||
|
@ -106,7 +105,6 @@ interface IState {
|
|||
decryptedBlob?: Blob;
|
||||
}
|
||||
|
||||
@replaceableComponent("views.messages.MFileBody")
|
||||
export default class MFileBody extends React.Component<IProps, IState> {
|
||||
static contextType = RoomContext;
|
||||
public context!: React.ContextType<typeof RoomContext>;
|
||||
|
|
|
@ -28,9 +28,8 @@ import Modal from '../../../Modal';
|
|||
import { _t } from '../../../languageHandler';
|
||||
import SettingsStore from "../../../settings/SettingsStore";
|
||||
import Spinner from '../elements/Spinner';
|
||||
import { replaceableComponent } from "../../../utils/replaceableComponent";
|
||||
import { Media, mediaFromContent } from "../../../customisations/Media";
|
||||
import { BLURHASH_FIELD, createThumbnail } from "../../../ContentMessages";
|
||||
import { BLURHASH_FIELD, createThumbnail } from "../../../utils/image-media";
|
||||
import { IMediaEventContent } from '../../../customisations/models/IMediaEventContent';
|
||||
import ImageView from '../elements/ImageView';
|
||||
import { IBodyProps } from "./IBodyProps";
|
||||
|
@ -60,7 +59,6 @@ interface IState {
|
|||
placeholder: Placeholder;
|
||||
}
|
||||
|
||||
@replaceableComponent("views.messages.MImageBody")
|
||||
export default class MImageBody extends React.Component<IBodyProps, IState> {
|
||||
static contextType = RoomContext;
|
||||
public context!: React.ContextType<typeof RoomContext>;
|
||||
|
@ -366,10 +364,12 @@ export default class MImageBody extends React.Component<IBodyProps, IState> {
|
|||
|
||||
let infoWidth: number;
|
||||
let infoHeight: number;
|
||||
let infoSvg = false;
|
||||
|
||||
if (content.info?.w && content.info?.h) {
|
||||
infoWidth = content.info.w;
|
||||
infoHeight = content.info.h;
|
||||
infoSvg = content.info.mimetype.startsWith("image/svg");
|
||||
} else {
|
||||
// Whilst the image loads, display nothing. We also don't display a blurhash image
|
||||
// because we don't really know what size of image we'll end up with.
|
||||
|
@ -451,6 +451,11 @@ export default class MImageBody extends React.Component<IBodyProps, IState> {
|
|||
'mx_MImageBody_placeholder--blurhash': this.props.mxEvent.getContent().info?.[BLURHASH_FIELD],
|
||||
});
|
||||
|
||||
// many SVGs don't have an intrinsic size if used in <img> elements.
|
||||
// due to this we have to set our desired width directly.
|
||||
// this way if the image is forced to shrink, the height adapts appropriately.
|
||||
const sizing = infoSvg ? { maxHeight, maxWidth, width: maxWidth } : { maxHeight, maxWidth };
|
||||
|
||||
const thumbnail = (
|
||||
<div className="mx_MImageBody_thumbnail_container" style={{ maxHeight, maxWidth, aspectRatio: `${infoWidth}/${infoHeight}` }}>
|
||||
<SwitchTransition mode="out-in">
|
||||
|
@ -465,7 +470,7 @@ export default class MImageBody extends React.Component<IBodyProps, IState> {
|
|||
</CSSTransition>
|
||||
</SwitchTransition>
|
||||
|
||||
<div style={{ maxHeight, maxWidth }}>
|
||||
<div style={sizing}>
|
||||
{ img }
|
||||
{ gifLabel }
|
||||
</div>
|
||||
|
|
|
@ -22,14 +22,12 @@ import WidgetStore from "../../../stores/WidgetStore";
|
|||
import EventTileBubble from "./EventTileBubble";
|
||||
import { MatrixClientPeg } from "../../../MatrixClientPeg";
|
||||
import { Container, WidgetLayoutStore } from "../../../stores/widgets/WidgetLayoutStore";
|
||||
import { replaceableComponent } from "../../../utils/replaceableComponent";
|
||||
|
||||
interface IProps {
|
||||
mxEvent: MatrixEvent;
|
||||
timestamp?: JSX.Element;
|
||||
}
|
||||
|
||||
@replaceableComponent("views.messages.MJitsiWidgetEvent")
|
||||
export default class MJitsiWidgetEvent extends React.PureComponent<IProps> {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
|
|
@ -28,7 +28,6 @@ import { MatrixClientPeg } from '../../../MatrixClientPeg';
|
|||
import { _t } from '../../../languageHandler';
|
||||
import { getNameForEventRoom, userLabelForEventRoom } from '../../../utils/KeyVerificationStateObserver';
|
||||
import EventTileBubble from "./EventTileBubble";
|
||||
import { replaceableComponent } from "../../../utils/replaceableComponent";
|
||||
|
||||
interface IProps {
|
||||
/* the MatrixEvent to show */
|
||||
|
@ -36,7 +35,6 @@ interface IProps {
|
|||
timestamp?: JSX.Element;
|
||||
}
|
||||
|
||||
@replaceableComponent("views.messages.MKeyVerificationConclusion")
|
||||
export default class MKeyVerificationConclusion extends React.Component<IProps> {
|
||||
constructor(props: IProps) {
|
||||
super(props);
|
||||
|
|
|
@ -24,7 +24,6 @@ import { _t } from '../../../languageHandler';
|
|||
import { getNameForEventRoom, userLabelForEventRoom } from '../../../utils/KeyVerificationStateObserver';
|
||||
import { RightPanelPhases } from '../../../stores/right-panel/RightPanelStorePhases';
|
||||
import EventTileBubble from "./EventTileBubble";
|
||||
import { replaceableComponent } from "../../../utils/replaceableComponent";
|
||||
import AccessibleButton from '../elements/AccessibleButton';
|
||||
import RightPanelStore from '../../../stores/right-panel/RightPanelStore';
|
||||
|
||||
|
@ -33,7 +32,6 @@ interface IProps {
|
|||
timestamp?: JSX.Element;
|
||||
}
|
||||
|
||||
@replaceableComponent("views.messages.MKeyVerificationRequest")
|
||||
export default class MKeyVerificationRequest extends React.Component<IProps> {
|
||||
public componentDidMount() {
|
||||
const request = this.props.mxEvent.verificationRequest;
|
||||
|
|
|
@ -15,97 +15,48 @@ limitations under the License.
|
|||
*/
|
||||
|
||||
import React from 'react';
|
||||
import maplibregl from 'maplibre-gl';
|
||||
import { MatrixEvent } from 'matrix-js-sdk/src/models/event';
|
||||
import {
|
||||
M_ASSET,
|
||||
LocationAssetType,
|
||||
ILocationContent,
|
||||
} from 'matrix-js-sdk/src/@types/location';
|
||||
import { ClientEvent, IClientWellKnown } from 'matrix-js-sdk/src/client';
|
||||
|
||||
import { replaceableComponent } from "../../../utils/replaceableComponent";
|
||||
import { IBodyProps } from "./IBodyProps";
|
||||
import { _t } from '../../../languageHandler';
|
||||
import MemberAvatar from '../avatars/MemberAvatar';
|
||||
import Modal from '../../../Modal';
|
||||
import {
|
||||
parseGeoUri,
|
||||
locationEventGeoUri,
|
||||
createMap,
|
||||
getLocationShareErrorMessage,
|
||||
LocationShareError,
|
||||
isSelfLocation,
|
||||
} from '../../../utils/location';
|
||||
import LocationViewDialog from '../location/LocationViewDialog';
|
||||
import MatrixClientContext from '../../../contexts/MatrixClientContext';
|
||||
import TooltipTarget from '../elements/TooltipTarget';
|
||||
import { Alignment } from '../elements/Tooltip';
|
||||
import AccessibleButton from '../elements/AccessibleButton';
|
||||
import { tileServerFromWellKnown } from '../../../utils/WellKnownUtils';
|
||||
import MatrixClientContext from '../../../contexts/MatrixClientContext';
|
||||
import LocationViewDialog from '../location/LocationViewDialog';
|
||||
import Map from '../location/Map';
|
||||
import SmartMarker from '../location/SmartMarker';
|
||||
import { IBodyProps } from "./IBodyProps";
|
||||
|
||||
interface IState {
|
||||
error: Error;
|
||||
}
|
||||
|
||||
@replaceableComponent("views.messages.MLocationBody")
|
||||
export default class MLocationBody extends React.Component<IBodyProps, IState> {
|
||||
public static contextType = MatrixClientContext;
|
||||
public context!: React.ContextType<typeof MatrixClientContext>;
|
||||
private coords: GeolocationCoordinates;
|
||||
private bodyId: string;
|
||||
private markerId: string;
|
||||
private map?: maplibregl.Map = null;
|
||||
private mapId: string;
|
||||
|
||||
constructor(props: IBodyProps) {
|
||||
super(props);
|
||||
|
||||
const randomString = Math.random().toString(16).slice(2, 10);
|
||||
// multiple instances of same map might be in document
|
||||
// eg thread and main timeline, reply
|
||||
const idSuffix = `${props.mxEvent.getId()}_${randomString}`;
|
||||
this.bodyId = `mx_MLocationBody_${idSuffix}`;
|
||||
this.markerId = `mx_MLocationBody_marker_${idSuffix}`;
|
||||
this.coords = parseGeoUri(locationEventGeoUri(this.props.mxEvent));
|
||||
this.mapId = `mx_MLocationBody_${idSuffix}`;
|
||||
|
||||
this.state = {
|
||||
error: undefined,
|
||||
};
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
if (this.state.error) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.context.on(ClientEvent.ClientWellKnown, this.updateStyleUrl);
|
||||
|
||||
this.map = createMap(
|
||||
this.coords,
|
||||
false,
|
||||
this.bodyId,
|
||||
this.markerId,
|
||||
(e: Error) => this.setState({ error: e }),
|
||||
);
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
this.context.off(ClientEvent.ClientWellKnown, this.updateStyleUrl);
|
||||
}
|
||||
|
||||
private updateStyleUrl = (clientWellKnown: IClientWellKnown) => {
|
||||
const style = tileServerFromWellKnown(clientWellKnown)?.["map_style_url"];
|
||||
if (style) {
|
||||
this.map?.setStyle(style);
|
||||
}
|
||||
};
|
||||
|
||||
private onClick = (
|
||||
event: React.MouseEvent<HTMLDivElement, MouseEvent>,
|
||||
) => {
|
||||
// Don't open map if we clicked the attribution button
|
||||
const target = event.target as Element;
|
||||
if (target.classList.contains("maplibregl-ctrl-attrib-button")) {
|
||||
return;
|
||||
}
|
||||
|
||||
private onClick = () => {
|
||||
Modal.createTrackedDialog(
|
||||
'Location View',
|
||||
'',
|
||||
|
@ -120,38 +71,23 @@ export default class MLocationBody extends React.Component<IBodyProps, IState> {
|
|||
);
|
||||
};
|
||||
|
||||
private onError = (error) => {
|
||||
this.setState({ error });
|
||||
};
|
||||
|
||||
render(): React.ReactElement<HTMLDivElement> {
|
||||
return this.state.error ?
|
||||
<LocationBodyFallbackContent error={this.state.error} event={this.props.mxEvent} /> :
|
||||
<LocationBodyContent
|
||||
mxEvent={this.props.mxEvent}
|
||||
bodyId={this.bodyId}
|
||||
markerId={this.markerId}
|
||||
error={this.state.error}
|
||||
mapId={this.mapId}
|
||||
onError={this.onError}
|
||||
tooltip={_t("Expand map")}
|
||||
onClick={this.onClick}
|
||||
/>;
|
||||
}
|
||||
}
|
||||
|
||||
export function isSelfLocation(locationContent: ILocationContent): boolean {
|
||||
const asset = M_ASSET.findIn(locationContent) as { type: string };
|
||||
const assetType = asset?.type ?? LocationAssetType.Self;
|
||||
return assetType == LocationAssetType.Self;
|
||||
}
|
||||
|
||||
interface ILocationBodyContentProps {
|
||||
mxEvent: MatrixEvent;
|
||||
bodyId: string;
|
||||
markerId: string;
|
||||
error: Error;
|
||||
tooltip?: string;
|
||||
onClick?: (event: React.MouseEvent<HTMLDivElement, MouseEvent>) => void;
|
||||
zoomButtons?: boolean;
|
||||
onZoomIn?: () => void;
|
||||
onZoomOut?: () => void;
|
||||
}
|
||||
|
||||
export const LocationBodyFallbackContent: React.FC<{ event: MatrixEvent, error: Error }> = ({ error, event }) => {
|
||||
const errorType = error?.message as LocationShareError;
|
||||
const message = `${_t('Unable to load map')}: ${getLocationShareErrorMessage(errorType)}`;
|
||||
|
@ -169,75 +105,54 @@ export const LocationBodyFallbackContent: React.FC<{ event: MatrixEvent, error:
|
|||
</div>;
|
||||
};
|
||||
|
||||
export function LocationBodyContent(props: ILocationBodyContentProps):
|
||||
React.ReactElement<HTMLDivElement> {
|
||||
const mapDiv = <div
|
||||
id={props.bodyId}
|
||||
onClick={props.onClick}
|
||||
className="mx_MLocationBody_map"
|
||||
/>;
|
||||
interface LocationBodyContentProps {
|
||||
mxEvent: MatrixEvent;
|
||||
mapId: string;
|
||||
tooltip?: string;
|
||||
onError: (error: Error) => void;
|
||||
onClick?: () => void;
|
||||
}
|
||||
export const LocationBodyContent: React.FC<LocationBodyContentProps> = ({
|
||||
mxEvent,
|
||||
mapId,
|
||||
tooltip,
|
||||
onError,
|
||||
onClick,
|
||||
}) => {
|
||||
// only pass member to marker when should render avatar marker
|
||||
const markerRoomMember = isSelfLocation(mxEvent.getContent()) ? mxEvent.sender : undefined;
|
||||
const geoUri = locationEventGeoUri(mxEvent);
|
||||
|
||||
const markerContents = (
|
||||
isSelfLocation(props.mxEvent.getContent())
|
||||
? <MemberAvatar
|
||||
member={props.mxEvent.sender}
|
||||
width={27}
|
||||
height={27}
|
||||
viewUserOnClick={false}
|
||||
/>
|
||||
: <div className="mx_MLocationBody_markerContents" />
|
||||
);
|
||||
const mapElement = (<Map
|
||||
id={mapId}
|
||||
centerGeoUri={geoUri}
|
||||
onClick={onClick}
|
||||
onError={onError}
|
||||
className="mx_MLocationBody_map"
|
||||
>
|
||||
{
|
||||
({ map }) =>
|
||||
<SmartMarker
|
||||
map={map}
|
||||
id={`${mapId}-marker`}
|
||||
geoUri={geoUri}
|
||||
roomMember={markerRoomMember}
|
||||
/>
|
||||
}
|
||||
</Map>);
|
||||
|
||||
return <div className="mx_MLocationBody">
|
||||
{
|
||||
props.tooltip
|
||||
tooltip
|
||||
? <TooltipTarget
|
||||
label={props.tooltip}
|
||||
label={tooltip}
|
||||
alignment={Alignment.InnerBottom}
|
||||
maxParentWidth={450}
|
||||
>
|
||||
{ mapDiv }
|
||||
{ mapElement }
|
||||
</TooltipTarget>
|
||||
: mapDiv
|
||||
}
|
||||
<div className="mx_MLocationBody_marker" id={props.markerId}>
|
||||
<div className="mx_MLocationBody_markerBorder">
|
||||
{ markerContents }
|
||||
</div>
|
||||
<div
|
||||
className="mx_MLocationBody_pointer"
|
||||
/>
|
||||
</div>
|
||||
{
|
||||
props.zoomButtons
|
||||
? <ZoomButtons
|
||||
onZoomIn={props.onZoomIn}
|
||||
onZoomOut={props.onZoomOut}
|
||||
/>
|
||||
: null
|
||||
: mapElement
|
||||
}
|
||||
</div>;
|
||||
}
|
||||
|
||||
interface IZoomButtonsProps {
|
||||
onZoomIn: () => void;
|
||||
onZoomOut: () => void;
|
||||
}
|
||||
|
||||
function ZoomButtons(props: IZoomButtonsProps): React.ReactElement<HTMLDivElement> {
|
||||
return <div className="mx_MLocationBody_zoomButtons">
|
||||
<AccessibleButton
|
||||
onClick={props.onZoomIn}
|
||||
title={_t("Zoom in")}
|
||||
>
|
||||
<div className="mx_MLocationBody_zoomButton mx_MLocationBody_plusButton" />
|
||||
</AccessibleButton>
|
||||
<AccessibleButton
|
||||
onClick={props.onZoomOut}
|
||||
title={_t("Zoom out")}
|
||||
>
|
||||
<div className="mx_MLocationBody_zoomButton mx_MLocationBody_minusButton" />
|
||||
</AccessibleButton>
|
||||
</div>;
|
||||
}
|
||||
};
|
||||
|
||||
|
|
|
@ -32,7 +32,6 @@ import {
|
|||
import { RelatedRelations } from "matrix-js-sdk/src/models/related-relations";
|
||||
|
||||
import { _t } from '../../../languageHandler';
|
||||
import { replaceableComponent } from "../../../utils/replaceableComponent";
|
||||
import Modal from '../../../Modal';
|
||||
import { IBodyProps } from "./IBodyProps";
|
||||
import { formatCommaSeparatedList } from '../../../utils/FormattingUtils';
|
||||
|
@ -210,7 +209,6 @@ export function launchPollEditor(mxEvent: MatrixEvent, getRelationsForEvent?: Ge
|
|||
}
|
||||
}
|
||||
|
||||
@replaceableComponent("views.messages.MPollBody")
|
||||
export default class MPollBody extends React.Component<IBodyProps, IState> {
|
||||
public static contextType = MatrixClientContext;
|
||||
public context!: React.ContextType<typeof MatrixClientContext>;
|
||||
|
|
|
@ -17,11 +17,9 @@ limitations under the License.
|
|||
import React from 'react';
|
||||
|
||||
import MImageBody from './MImageBody';
|
||||
import { replaceableComponent } from "../../../utils/replaceableComponent";
|
||||
import { BLURHASH_FIELD } from "../../../ContentMessages";
|
||||
import { BLURHASH_FIELD } from "../../../utils/image-media";
|
||||
import Tooltip from "../elements/Tooltip";
|
||||
|
||||
@replaceableComponent("views.messages.MStickerBody")
|
||||
export default class MStickerBody extends MImageBody {
|
||||
// Mostly empty to prevent default behaviour of MImageBody
|
||||
protected onClick = (ev: React.MouseEvent) => {
|
||||
|
|
|
@ -21,9 +21,8 @@ import { logger } from "matrix-js-sdk/src/logger";
|
|||
import { _t } from '../../../languageHandler';
|
||||
import SettingsStore from "../../../settings/SettingsStore";
|
||||
import InlineSpinner from '../elements/InlineSpinner';
|
||||
import { replaceableComponent } from "../../../utils/replaceableComponent";
|
||||
import { mediaFromContent } from "../../../customisations/Media";
|
||||
import { BLURHASH_FIELD } from "../../../ContentMessages";
|
||||
import { BLURHASH_FIELD } from "../../../utils/image-media";
|
||||
import { IMediaEventContent } from "../../../customisations/models/IMediaEventContent";
|
||||
import { IBodyProps } from "./IBodyProps";
|
||||
import MFileBody from "./MFileBody";
|
||||
|
@ -40,7 +39,6 @@ interface IState {
|
|||
blurhashUrl: string;
|
||||
}
|
||||
|
||||
@replaceableComponent("views.messages.MVideoBody")
|
||||
export default class MVideoBody extends React.PureComponent<IBodyProps, IState> {
|
||||
static contextType = RoomContext;
|
||||
public context!: React.ContextType<typeof RoomContext>;
|
||||
|
|
|
@ -16,14 +16,12 @@ limitations under the License.
|
|||
|
||||
import React from "react";
|
||||
|
||||
import { replaceableComponent } from "../../../utils/replaceableComponent";
|
||||
import InlineSpinner from '../elements/InlineSpinner';
|
||||
import { _t } from "../../../languageHandler";
|
||||
import RecordingPlayback from "../audio_messages/RecordingPlayback";
|
||||
import MAudioBody from "./MAudioBody";
|
||||
import MFileBody from "./MFileBody";
|
||||
|
||||
@replaceableComponent("views.messages.MVoiceMessageBody")
|
||||
export default class MVoiceMessageBody extends MAudioBody {
|
||||
// A voice message is an audio file but rendered in a special way.
|
||||
public render() {
|
||||
|
|
|
@ -17,12 +17,10 @@ limitations under the License.
|
|||
import React from "react";
|
||||
|
||||
import MAudioBody from "./MAudioBody";
|
||||
import { replaceableComponent } from "../../../utils/replaceableComponent";
|
||||
import MVoiceMessageBody from "./MVoiceMessageBody";
|
||||
import { IBodyProps } from "./IBodyProps";
|
||||
import { isVoiceMessage } from "../../../utils/EventUtils";
|
||||
|
||||
@replaceableComponent("views.messages.MVoiceOrAudioBody")
|
||||
export default class MVoiceOrAudioBody extends React.PureComponent<IBodyProps> {
|
||||
public render() {
|
||||
if (!this.props.forExport && isVoiceMessage(this.props.mxEvent)) {
|
||||
|
|
|
@ -16,7 +16,7 @@ See the License for the specific language governing permissions and
|
|||
limitations under the License.
|
||||
*/
|
||||
|
||||
import React, { ReactElement, useEffect } from 'react';
|
||||
import React, { ReactElement, useContext, useEffect } from 'react';
|
||||
import { EventStatus, MatrixEvent, MatrixEventEvent } from 'matrix-js-sdk/src/models/event';
|
||||
import classNames from 'classnames';
|
||||
import { MsgType, RelationType } from 'matrix-js-sdk/src/@types/event';
|
||||
|
@ -26,12 +26,11 @@ import type { Relations } from 'matrix-js-sdk/src/models/relations';
|
|||
import { _t } from '../../../languageHandler';
|
||||
import dis from '../../../dispatcher/dispatcher';
|
||||
import ContextMenu, { aboveLeftOf, ContextMenuTooltipButton, useContextMenu } from '../../structures/ContextMenu';
|
||||
import { isContentActionable, canEditContent, editEvent } from '../../../utils/EventUtils';
|
||||
import { isContentActionable, canEditContent, editEvent, canCancel } from '../../../utils/EventUtils';
|
||||
import RoomContext, { TimelineRenderingType } from "../../../contexts/RoomContext";
|
||||
import Toolbar from "../../../accessibility/Toolbar";
|
||||
import { RovingAccessibleTooltipButton, useRovingTabIndex } from "../../../accessibility/RovingTabIndex";
|
||||
import { replaceableComponent } from "../../../utils/replaceableComponent";
|
||||
import MessageContextMenu, { canCancel } from "../context_menus/MessageContextMenu";
|
||||
import MessageContextMenu from "../context_menus/MessageContextMenu";
|
||||
import Resend from "../../../Resend";
|
||||
import { MatrixClientPeg } from "../../../MatrixClientPeg";
|
||||
import { MediaEventHelper } from "../../../utils/MediaEventHelper";
|
||||
|
@ -40,13 +39,14 @@ import SettingsStore from '../../../settings/SettingsStore';
|
|||
import { RoomPermalinkCreator } from '../../../utils/permalinks/Permalinks';
|
||||
import ReplyChain from '../elements/ReplyChain';
|
||||
import ReactionPicker from "../emojipicker/ReactionPicker";
|
||||
import { CardContext } from '../right_panel/BaseCard';
|
||||
import { CardContext } from '../right_panel/context';
|
||||
import { showThread } from "../../../dispatcher/dispatch-actions/threads";
|
||||
import { shouldDisplayReply } from '../../../utils/Reply';
|
||||
import { Key } from "../../../Keyboard";
|
||||
import { ALTERNATE_KEY_NAME } from "../../../accessibility/KeyboardShortcuts";
|
||||
import { UserTab } from '../dialogs/UserSettingsDialog';
|
||||
import { UserTab } from '../dialogs/UserTab';
|
||||
import { Action } from '../../../dispatcher/actions';
|
||||
import SdkConfig from "../../../SdkConfig";
|
||||
|
||||
interface IOptionsButtonProps {
|
||||
mxEvent: MatrixEvent;
|
||||
|
@ -156,6 +156,81 @@ const ReactButton: React.FC<IReactButtonProps> = ({ mxEvent, reactions, onFocusC
|
|||
</React.Fragment>;
|
||||
};
|
||||
|
||||
interface IReplyInThreadButton {
|
||||
mxEvent: MatrixEvent;
|
||||
}
|
||||
|
||||
const ReplyInThreadButton = ({ mxEvent }: IReplyInThreadButton) => {
|
||||
const context = useContext(CardContext);
|
||||
|
||||
const relationType = mxEvent?.getRelation()?.rel_type;
|
||||
const hasARelation = !!relationType && relationType !== RelationType.Thread;
|
||||
const firstTimeSeeingThreads = !localStorage.getItem("mx_seen_feature_thread");
|
||||
const threadsEnabled = SettingsStore.getValue("feature_thread");
|
||||
|
||||
if (!threadsEnabled && !Thread.hasServerSideSupport) {
|
||||
// hide the prompt if the user would only have degraded mode
|
||||
return null;
|
||||
}
|
||||
|
||||
const onClick = (): void => {
|
||||
if (firstTimeSeeingThreads) {
|
||||
localStorage.setItem("mx_seen_feature_thread", "true");
|
||||
}
|
||||
|
||||
if (!SettingsStore.getValue("feature_thread")) {
|
||||
dis.dispatch({
|
||||
action: Action.ViewUserSettings,
|
||||
initialTabId: UserTab.Labs,
|
||||
});
|
||||
} else if (mxEvent.isThreadRelation) {
|
||||
showThread({
|
||||
rootEvent: mxEvent.getThread().rootEvent,
|
||||
initialEvent: mxEvent,
|
||||
scroll_into_view: true,
|
||||
highlighted: true,
|
||||
push: context.isCard,
|
||||
});
|
||||
} else {
|
||||
showThread({
|
||||
rootEvent: mxEvent,
|
||||
push: context.isCard,
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
return <RovingAccessibleTooltipButton
|
||||
className="mx_MessageActionBar_maskButton mx_MessageActionBar_threadButton"
|
||||
|
||||
disabled={hasARelation}
|
||||
tooltip={<>
|
||||
<div className="mx_Tooltip_title">
|
||||
{ !hasARelation
|
||||
? _t("Reply in thread")
|
||||
: _t("Can't create a thread from an event with an existing relation") }
|
||||
</div>
|
||||
{ !hasARelation && (
|
||||
<div className="mx_Tooltip_sub">
|
||||
{ SettingsStore.getValue("feature_thread")
|
||||
? _t("Beta feature")
|
||||
: _t("Beta feature. Click to learn more.")
|
||||
}
|
||||
</div>
|
||||
) }
|
||||
</>}
|
||||
|
||||
title={!hasARelation
|
||||
? _t("Reply in thread")
|
||||
: _t("Can't create a thread from an event with an existing relation")}
|
||||
|
||||
onClick={onClick}
|
||||
>
|
||||
{ firstTimeSeeingThreads && !threadsEnabled && (
|
||||
<div className="mx_Indicator" />
|
||||
) }
|
||||
</RovingAccessibleTooltipButton>;
|
||||
};
|
||||
|
||||
interface IMessageActionBarProps {
|
||||
mxEvent: MatrixEvent;
|
||||
reactions?: Relations;
|
||||
|
@ -173,7 +248,6 @@ interface IMessageActionBarProps {
|
|||
) => Relations;
|
||||
}
|
||||
|
||||
@replaceableComponent("views.messages.MessageActionBar")
|
||||
export default class MessageActionBar extends React.PureComponent<IMessageActionBarProps> {
|
||||
public static contextType = RoomContext;
|
||||
|
||||
|
@ -225,21 +299,6 @@ export default class MessageActionBar extends React.PureComponent<IMessageAction
|
|||
});
|
||||
};
|
||||
|
||||
private onThreadClick = (isCard: boolean): void => {
|
||||
if (localStorage.getItem("mx_seen_feature_thread") === null) {
|
||||
localStorage.setItem("mx_seen_feature_thread", "true");
|
||||
}
|
||||
|
||||
if (!SettingsStore.getValue("feature_thread")) {
|
||||
dis.dispatch({
|
||||
action: Action.ViewUserSettings,
|
||||
initialTabId: UserTab.Labs,
|
||||
});
|
||||
} else {
|
||||
showThread({ rootEvent: this.props.mxEvent, push: isCard });
|
||||
}
|
||||
};
|
||||
|
||||
private onEditClick = (): void => {
|
||||
editEvent(this.props.mxEvent, this.context.timelineRenderingType, this.props.getRelationsForEvent);
|
||||
};
|
||||
|
@ -254,6 +313,15 @@ export default class MessageActionBar extends React.PureComponent<IMessageAction
|
|||
return null;
|
||||
}
|
||||
|
||||
if (!SettingsStore.getBetaInfo("feature_thread") &&
|
||||
!SettingsStore.getValue("feature_thread") &&
|
||||
!SdkConfig.get("show_labs_settings")
|
||||
) {
|
||||
// Hide the beta prompt if there is no UI to enable it,
|
||||
// e.g if config.json disables it and doesn't enable show labs flags
|
||||
return false;
|
||||
}
|
||||
|
||||
const inNotThreadTimeline = this.context.timelineRenderingType !== TimelineRenderingType.Thread;
|
||||
|
||||
const isAllowedMessageType = !this.forbiddenThreadHeadMsgType.includes(
|
||||
|
@ -316,44 +384,7 @@ export default class MessageActionBar extends React.PureComponent<IMessageAction
|
|||
key="cancel"
|
||||
/>;
|
||||
|
||||
const relationType = this.props.mxEvent?.getRelation()?.rel_type;
|
||||
const hasARelation = !!relationType && relationType !== RelationType.Thread;
|
||||
const firstTimeSeeingThreads = localStorage.getItem("mx_seen_feature_thread") === null &&
|
||||
!SettingsStore.getValue("feature_thread");
|
||||
const threadTooltipButton = <CardContext.Consumer key="thread">
|
||||
{ context =>
|
||||
<RovingAccessibleTooltipButton
|
||||
className="mx_MessageActionBar_maskButton mx_MessageActionBar_threadButton"
|
||||
|
||||
disabled={hasARelation}
|
||||
tooltip={<>
|
||||
<div className="mx_Tooltip_title">
|
||||
{ !hasARelation
|
||||
? _t("Reply in thread")
|
||||
: _t("Can't create a thread from an event with an existing relation") }
|
||||
</div>
|
||||
{ !hasARelation && (
|
||||
<div className="mx_Tooltip_sub">
|
||||
{ SettingsStore.getValue("feature_thread")
|
||||
? _t("Beta feature")
|
||||
: _t("Beta feature. Click to learn more.")
|
||||
}
|
||||
</div>
|
||||
) }
|
||||
</>}
|
||||
|
||||
title={!hasARelation
|
||||
? _t("Reply in thread")
|
||||
: _t("Can't create a thread from an event with an existing relation")}
|
||||
|
||||
onClick={this.onThreadClick.bind(null, context.isCard)}
|
||||
>
|
||||
{ firstTimeSeeingThreads && (
|
||||
<div className="mx_Indicator" />
|
||||
) }
|
||||
</RovingAccessibleTooltipButton>
|
||||
}
|
||||
</CardContext.Consumer>;
|
||||
const threadTooltipButton = <ReplyInThreadButton mxEvent={this.props.mxEvent} />;
|
||||
|
||||
// We show a different toolbar for failed events, so detect that first.
|
||||
const mxEvent = this.props.mxEvent;
|
||||
|
|
|
@ -17,27 +17,36 @@ limitations under the License.
|
|||
import React, { createRef } from 'react';
|
||||
import { EventType, MsgType } from "matrix-js-sdk/src/@types/event";
|
||||
import { Relations } from 'matrix-js-sdk/src/models/relations';
|
||||
import { M_BEACON_INFO } from 'matrix-js-sdk/src/@types/beacon';
|
||||
import { M_LOCATION } from 'matrix-js-sdk/src/@types/location';
|
||||
import { M_POLL_START } from "matrix-events-sdk";
|
||||
|
||||
import * as sdk from '../../../index';
|
||||
import SettingsStore from "../../../settings/SettingsStore";
|
||||
import { Mjolnir } from "../../../mjolnir/Mjolnir";
|
||||
import RedactedBody from "./RedactedBody";
|
||||
import UnknownBody from "./UnknownBody";
|
||||
import { replaceableComponent } from "../../../utils/replaceableComponent";
|
||||
import { IMediaBody } from "./IMediaBody";
|
||||
import { IOperableEventTile } from "../context_menus/MessageContextMenu";
|
||||
import { MediaEventHelper } from "../../../utils/MediaEventHelper";
|
||||
import { ReactAnyComponent } from "../../../@types/common";
|
||||
import { IBodyProps } from "./IBodyProps";
|
||||
import MatrixClientContext from '../../../contexts/MatrixClientContext';
|
||||
import TextualBody from "./TextualBody";
|
||||
import MImageBody from "./MImageBody";
|
||||
import MFileBody from "./MFileBody";
|
||||
import MVoiceOrAudioBody from "./MVoiceOrAudioBody";
|
||||
import MVideoBody from "./MVideoBody";
|
||||
import MStickerBody from "./MStickerBody";
|
||||
import MPollBody from "./MPollBody";
|
||||
import MLocationBody from "./MLocationBody";
|
||||
import MjolnirBody from "./MjolnirBody";
|
||||
import MBeaconBody from "./MBeaconBody";
|
||||
import { IEventTileOps } from "../rooms/EventTile";
|
||||
|
||||
// onMessageAllowed is handled internally
|
||||
interface IProps extends Omit<IBodyProps, "onMessageAllowed"> {
|
||||
interface IProps extends Omit<IBodyProps, "onMessageAllowed" | "mediaEventHelper"> {
|
||||
/* overrides for the msgtype-specific components, used by ReplyTile to override file rendering */
|
||||
overrideBodyTypes?: Record<string, React.Component>;
|
||||
overrideEventTypes?: Record<string, React.Component>;
|
||||
overrideBodyTypes?: Record<string, typeof React.Component>;
|
||||
overrideEventTypes?: Record<string, typeof React.Component>;
|
||||
|
||||
// helper function to access relations for this event
|
||||
getRelationsForEvent?: (eventId: string, relationType: string, eventType: string) => Relations;
|
||||
|
@ -45,7 +54,10 @@ interface IProps extends Omit<IBodyProps, "onMessageAllowed"> {
|
|||
isSeeingThroughMessageHiddenForModeration?: boolean;
|
||||
}
|
||||
|
||||
@replaceableComponent("views.messages.MessageEvent")
|
||||
export interface IOperableEventTile {
|
||||
getEventTileOps(): IEventTileOps;
|
||||
}
|
||||
|
||||
export default class MessageEvent extends React.Component<IProps> implements IMediaBody, IOperableEventTile {
|
||||
private body: React.RefObject<React.Component | IOperableEventTile> = createRef();
|
||||
private mediaHelper: MediaEventHelper;
|
||||
|
@ -72,23 +84,27 @@ export default class MessageEvent extends React.Component<IProps> implements IMe
|
|||
}
|
||||
}
|
||||
|
||||
private get bodyTypes(): Record<string, React.Component> {
|
||||
private get bodyTypes(): Record<string, typeof React.Component> {
|
||||
return {
|
||||
[MsgType.Text]: sdk.getComponent('messages.TextualBody'),
|
||||
[MsgType.Notice]: sdk.getComponent('messages.TextualBody'),
|
||||
[MsgType.Emote]: sdk.getComponent('messages.TextualBody'),
|
||||
[MsgType.Image]: sdk.getComponent('messages.MImageBody'),
|
||||
[MsgType.File]: sdk.getComponent('messages.MFileBody'),
|
||||
[MsgType.Audio]: sdk.getComponent('messages.MVoiceOrAudioBody'),
|
||||
[MsgType.Video]: sdk.getComponent('messages.MVideoBody'),
|
||||
[MsgType.Text]: TextualBody,
|
||||
[MsgType.Notice]: TextualBody,
|
||||
[MsgType.Emote]: TextualBody,
|
||||
[MsgType.Image]: MImageBody,
|
||||
[MsgType.File]: MFileBody,
|
||||
[MsgType.Audio]: MVoiceOrAudioBody,
|
||||
[MsgType.Video]: MVideoBody,
|
||||
|
||||
...(this.props.overrideBodyTypes || {}),
|
||||
};
|
||||
}
|
||||
|
||||
private get evTypes(): Record<string, React.Component> {
|
||||
private get evTypes(): Record<string, React.ComponentType<Partial<IBodyProps>>> {
|
||||
return {
|
||||
[EventType.Sticker]: sdk.getComponent('messages.MStickerBody'),
|
||||
[EventType.Sticker]: MStickerBody,
|
||||
[M_POLL_START.name]: MPollBody,
|
||||
[M_POLL_START.altName]: MPollBody,
|
||||
[M_BEACON_INFO.name]: MBeaconBody,
|
||||
[M_BEACON_INFO.altName]: MBeaconBody,
|
||||
|
||||
...(this.props.overrideEventTypes || {}),
|
||||
};
|
||||
|
@ -110,7 +126,7 @@ export default class MessageEvent extends React.Component<IProps> implements IMe
|
|||
const content = this.props.mxEvent.getContent();
|
||||
const type = this.props.mxEvent.getType();
|
||||
const msgtype = content.msgtype;
|
||||
let BodyType: ReactAnyComponent = RedactedBody;
|
||||
let BodyType: React.ComponentType<Partial<IBodyProps>> | ReactAnyComponent = RedactedBody;
|
||||
if (!this.props.mxEvent.isRedacted()) {
|
||||
// only resolve BodyType if event is not redacted
|
||||
if (type && this.evTypes[type]) {
|
||||
|
@ -125,17 +141,12 @@ export default class MessageEvent extends React.Component<IProps> implements IMe
|
|||
BodyType = UnknownBody;
|
||||
}
|
||||
|
||||
// TODO: move to eventTypes when Polls spec stabilises
|
||||
if (M_POLL_START.matches(type)) {
|
||||
BodyType = sdk.getComponent('messages.MPollBody');
|
||||
}
|
||||
|
||||
// TODO: move to eventTypes when location sharing spec stabilises
|
||||
if (
|
||||
M_LOCATION.matches(type) ||
|
||||
(type === EventType.RoomMessage && msgtype === MsgType.Location)
|
||||
) {
|
||||
BodyType = sdk.getComponent('messages.MLocationBody');
|
||||
BodyType = MLocationBody;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -149,7 +160,7 @@ export default class MessageEvent extends React.Component<IProps> implements IMe
|
|||
const serverBanned = Mjolnir.sharedInstance().isServerBanned(userDomain);
|
||||
|
||||
if (userBanned || serverBanned) {
|
||||
BodyType = sdk.getComponent('messages.MjolnirBody');
|
||||
BodyType = MjolnirBody;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -18,7 +18,6 @@ limitations under the License.
|
|||
import React from 'react';
|
||||
|
||||
import { formatFullDate, formatTime, formatFullTime, formatRelativeTime } from '../../../DateUtils';
|
||||
import { replaceableComponent } from "../../../utils/replaceableComponent";
|
||||
|
||||
interface IProps {
|
||||
ts: number;
|
||||
|
@ -28,7 +27,6 @@ interface IProps {
|
|||
showRelative?: boolean;
|
||||
}
|
||||
|
||||
@replaceableComponent("views.messages.MessageTimestamp")
|
||||
export default class MessageTimestamp extends React.Component<IProps> {
|
||||
public render() {
|
||||
const date = new Date(this.props.ts);
|
||||
|
|
|
@ -18,7 +18,6 @@ import React from 'react';
|
|||
import { MatrixEvent } from "matrix-js-sdk/src/models/event";
|
||||
|
||||
import { _t } from '../../../languageHandler';
|
||||
import { replaceableComponent } from "../../../utils/replaceableComponent";
|
||||
import AccessibleButton from '../elements/AccessibleButton';
|
||||
|
||||
interface IProps {
|
||||
|
@ -26,7 +25,6 @@ interface IProps {
|
|||
onMessageAllowed: () => void;
|
||||
}
|
||||
|
||||
@replaceableComponent("views.messages.MjolnirBody")
|
||||
export default class MjolnirBody extends React.Component<IProps> {
|
||||
private onAllowClick = (e: React.MouseEvent): void => {
|
||||
e.preventDefault();
|
||||
|
|
|
@ -21,7 +21,6 @@ import { Relations, RelationsEvent } from "matrix-js-sdk/src/models/relations";
|
|||
|
||||
import { _t } from '../../../languageHandler';
|
||||
import { isContentActionable } from '../../../utils/EventUtils';
|
||||
import { replaceableComponent } from "../../../utils/replaceableComponent";
|
||||
import { ContextMenuTooltipButton } from "../../../accessibility/context_menu/ContextMenuTooltipButton";
|
||||
import ContextMenu, { aboveLeftOf, useContextMenu } from "../../structures/ContextMenu";
|
||||
import ReactionPicker from "../emojipicker/ReactionPicker";
|
||||
|
@ -74,7 +73,6 @@ interface IState {
|
|||
showAll: boolean;
|
||||
}
|
||||
|
||||
@replaceableComponent("views.messages.ReactionsRow")
|
||||
export default class ReactionsRow extends React.PureComponent<IProps, IState> {
|
||||
static contextType = RoomContext;
|
||||
public context!: React.ContextType<typeof RoomContext>;
|
||||
|
|
|
@ -21,7 +21,6 @@ import { MatrixEvent } from "matrix-js-sdk/src/models/event";
|
|||
import { _t } from '../../../languageHandler';
|
||||
import { formatCommaSeparatedList } from '../../../utils/FormattingUtils';
|
||||
import dis from "../../../dispatcher/dispatcher";
|
||||
import { replaceableComponent } from "../../../utils/replaceableComponent";
|
||||
import ReactionsRowButtonTooltip from "./ReactionsRowButtonTooltip";
|
||||
import AccessibleButton from "../elements/AccessibleButton";
|
||||
import MatrixClientContext from "../../../contexts/MatrixClientContext";
|
||||
|
@ -46,7 +45,6 @@ interface IState {
|
|||
tooltipVisible: boolean;
|
||||
}
|
||||
|
||||
@replaceableComponent("views.messages.ReactionsRowButton")
|
||||
export default class ReactionsRowButton extends React.PureComponent<IProps, IState> {
|
||||
static contextType = MatrixClientContext;
|
||||
|
||||
|
|
|
@ -20,7 +20,6 @@ import { MatrixEvent } from "matrix-js-sdk/src/models/event";
|
|||
import { unicodeToShortcode } from '../../../HtmlUtils';
|
||||
import { _t } from '../../../languageHandler';
|
||||
import { formatCommaSeparatedList } from '../../../utils/FormattingUtils';
|
||||
import { replaceableComponent } from "../../../utils/replaceableComponent";
|
||||
import Tooltip from "../elements/Tooltip";
|
||||
import MatrixClientContext from "../../../contexts/MatrixClientContext";
|
||||
|
||||
|
@ -34,7 +33,6 @@ interface IProps {
|
|||
visible: boolean;
|
||||
}
|
||||
|
||||
@replaceableComponent("views.messages.ReactionsRowButtonTooltip")
|
||||
export default class ReactionsRowButtonTooltip extends React.PureComponent<IProps> {
|
||||
static contextType = MatrixClientContext;
|
||||
|
||||
|
|
|
@ -23,7 +23,6 @@ import { MatrixClientPeg } from '../../../MatrixClientPeg';
|
|||
import { _t } from '../../../languageHandler';
|
||||
import Modal from '../../../Modal';
|
||||
import AccessibleButton from '../elements/AccessibleButton';
|
||||
import { replaceableComponent } from "../../../utils/replaceableComponent";
|
||||
import { mediaFromMxc } from "../../../customisations/Media";
|
||||
import RoomAvatar from "../avatars/RoomAvatar";
|
||||
import ImageView from "../elements/ImageView";
|
||||
|
@ -33,7 +32,6 @@ interface IProps {
|
|||
mxEvent: MatrixEvent;
|
||||
}
|
||||
|
||||
@replaceableComponent("views.messages.RoomAvatarEvent")
|
||||
export default class RoomAvatarEvent extends React.Component<IProps> {
|
||||
private onAvatarClick = (): void => {
|
||||
const cli = MatrixClientPeg.get();
|
||||
|
|
|
@ -24,7 +24,6 @@ import { RoomPermalinkCreator } from '../../../utils/permalinks/Permalinks';
|
|||
import { _t } from '../../../languageHandler';
|
||||
import { MatrixClientPeg } from '../../../MatrixClientPeg';
|
||||
import EventTileBubble from "./EventTileBubble";
|
||||
import { replaceableComponent } from "../../../utils/replaceableComponent";
|
||||
import { ViewRoomPayload } from "../../../dispatcher/payloads/ViewRoomPayload";
|
||||
|
||||
interface IProps {
|
||||
|
@ -33,7 +32,6 @@ interface IProps {
|
|||
timestamp?: JSX.Element;
|
||||
}
|
||||
|
||||
@replaceableComponent("views.messages.RoomCreate")
|
||||
export default class RoomCreate extends React.Component<IProps> {
|
||||
private onLinkClicked = (e: React.MouseEvent): void => {
|
||||
e.preventDefault();
|
||||
|
|
|
@ -19,7 +19,6 @@ import { MatrixEvent } from "matrix-js-sdk/src/models/event";
|
|||
import { MsgType } from "matrix-js-sdk/src/@types/event";
|
||||
|
||||
import MatrixClientContext from "../../../contexts/MatrixClientContext";
|
||||
import { replaceableComponent } from "../../../utils/replaceableComponent";
|
||||
import DisambiguatedProfile from "./DisambiguatedProfile";
|
||||
import RoomContext, { TimelineRenderingType } from '../../../contexts/RoomContext';
|
||||
import SettingsStore from "../../../settings/SettingsStore";
|
||||
|
@ -30,7 +29,6 @@ interface IProps {
|
|||
onClick?(): void;
|
||||
}
|
||||
|
||||
@replaceableComponent("views.messages.SenderProfile")
|
||||
export default class SenderProfile extends React.PureComponent<IProps> {
|
||||
public static contextType = MatrixClientContext;
|
||||
public context!: React.ContextType<typeof MatrixClientContext>;
|
||||
|
|
|
@ -33,7 +33,6 @@ import { IntegrationManagers } from "../../../integrations/IntegrationManagers";
|
|||
import { isPermalinkHost, tryTransformPermalinkToLocalHref } from "../../../utils/permalinks/Permalinks";
|
||||
import { copyPlaintext } from "../../../utils/strings";
|
||||
import AccessibleTooltipButton from "../elements/AccessibleTooltipButton";
|
||||
import { replaceableComponent } from "../../../utils/replaceableComponent";
|
||||
import UIStore from "../../../stores/UIStore";
|
||||
import { ComposerInsertPayload } from "../../../dispatcher/payloads/ComposerInsertPayload";
|
||||
import { Action } from "../../../dispatcher/actions";
|
||||
|
@ -59,7 +58,6 @@ interface IState {
|
|||
widgetHidden: boolean;
|
||||
}
|
||||
|
||||
@replaceableComponent("views.messages.TextualBody")
|
||||
export default class TextualBody extends React.Component<IBodyProps, IState> {
|
||||
private readonly contentRef = createRef<HTMLSpanElement>();
|
||||
|
||||
|
|
|
@ -19,18 +19,16 @@ import { MatrixEvent } from "matrix-js-sdk/src/models/event";
|
|||
|
||||
import RoomContext from "../../../contexts/RoomContext";
|
||||
import * as TextForEvent from "../../../TextForEvent";
|
||||
import { replaceableComponent } from "../../../utils/replaceableComponent";
|
||||
|
||||
interface IProps {
|
||||
mxEvent: MatrixEvent;
|
||||
}
|
||||
|
||||
@replaceableComponent("views.messages.TextualEvent")
|
||||
export default class TextualEvent extends React.Component<IProps> {
|
||||
static contextType = RoomContext;
|
||||
|
||||
public render() {
|
||||
const text = TextForEvent.textForEvent(this.props.mxEvent, true, this.context?.showHiddenEventsInTimeline);
|
||||
const text = TextForEvent.textForEvent(this.props.mxEvent, true, this.context?.showHiddenEvents);
|
||||
if (!text) return null;
|
||||
return <div className="mx_TextualEvent">{ text }</div>;
|
||||
}
|
||||
|
|
|
@ -21,7 +21,6 @@ import { MatrixEvent } from "matrix-js-sdk/src/models/event";
|
|||
import { _t } from '../../../languageHandler';
|
||||
import Modal from '../../../Modal';
|
||||
import SdkConfig from "../../../SdkConfig";
|
||||
import { replaceableComponent } from "../../../utils/replaceableComponent";
|
||||
import BugReportDialog from '../dialogs/BugReportDialog';
|
||||
import AccessibleButton from '../elements/AccessibleButton';
|
||||
import SettingsStore from "../../../settings/SettingsStore";
|
||||
|
@ -37,7 +36,6 @@ interface IState {
|
|||
error: Error;
|
||||
}
|
||||
|
||||
@replaceableComponent("views.messages.TileErrorBoundary")
|
||||
export default class TileErrorBoundary extends React.Component<IProps, IState> {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
|
|
@ -18,7 +18,6 @@ import React from 'react';
|
|||
import { MatrixEvent, MatrixEventEvent } from 'matrix-js-sdk/src/matrix';
|
||||
import classNames from 'classnames';
|
||||
|
||||
import { replaceableComponent } from "../../../utils/replaceableComponent";
|
||||
import { MatrixClientPeg } from "../../../MatrixClientPeg";
|
||||
import { _t } from '../../../languageHandler';
|
||||
import AccessibleButton from '../elements/AccessibleButton';
|
||||
|
@ -31,7 +30,6 @@ interface IState {
|
|||
expanded: boolean;
|
||||
}
|
||||
|
||||
@replaceableComponent("views.messages.ViewSourceEvent")
|
||||
export default class ViewSourceEvent extends React.PureComponent<IProps, IState> {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue