MSC3531 - Implementing message hiding pending moderation (#7518)

Signed-off-by: David Teller <davidt@element.io>
This commit is contained in:
David Teller 2022-01-17 16:04:37 +01:00 committed by GitHub
parent c612014936
commit 6b870ba1a9
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
17 changed files with 345 additions and 22 deletions

View file

@ -0,0 +1,55 @@
/*
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 from "react";
import { MatrixEvent } from "matrix-js-sdk/src/models/event";
import { _t } from "../../../languageHandler";
import { IBodyProps } from "./IBodyProps";
interface IProps {
mxEvent: MatrixEvent;
}
/**
* A message hidden from the user pending moderation.
*
* Note: This component must not be used when the user is the author of the message
* or has a sufficient powerlevel to see the message.
*/
const HiddenBody = React.forwardRef<any, IProps | IBodyProps>(({ mxEvent }, ref) => {
let text;
const visibility = mxEvent.messageVisibility();
switch (visibility.visible) {
case true:
throw new Error("HiddenBody should only be applied to hidden messages");
case false:
if (visibility.reason) {
text = _t("Message pending moderation: %(reason)s", { reason: visibility.reason });
} else {
text = _t("Message pending moderation");
}
break;
}
return (
<span className="mx_HiddenBody" ref={ref}>
{ text }
</span>
);
});
export default HiddenBody;

View file

@ -44,6 +44,14 @@ export interface IBodyProps {
permalinkCreator: RoomPermalinkCreator;
mediaEventHelper: MediaEventHelper;
/*
If present and `true`, the message has been marked as hidden pending moderation
(see MSC3531) **but** the current user can see the message nevertheless (with
a marker), either because they are a moderator or because they are the original
author of the message.
*/
isSeeingThroughMessageHiddenForModeration?: boolean;
// helper function to access relations for this event
getRelationsForEvent?: (eventId: string, relationType: string, eventType: string) => Relations;
}

View file

@ -31,6 +31,7 @@ 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';
// onMessageAllowed is handled internally
interface IProps extends Omit<IBodyProps, "onMessageAllowed"> {
@ -40,6 +41,8 @@ interface IProps extends Omit<IBodyProps, "onMessageAllowed"> {
// helper function to access relations for this event
getRelationsForEvent?: (eventId: string, relationType: string, eventType: string) => Relations;
isSeeingThroughMessageHiddenForModeration?: boolean;
}
@replaceableComponent("views.messages.MessageEvent")
@ -47,7 +50,10 @@ export default class MessageEvent extends React.Component<IProps> implements IMe
private body: React.RefObject<React.Component | IOperableEventTile> = createRef();
private mediaHelper: MediaEventHelper;
public constructor(props: IProps) {
static contextType = MatrixClientContext;
public context!: React.ContextType<typeof MatrixClientContext>;
public constructor(props: IProps, context: React.ContextType<typeof MatrixClientContext>) {
super(props);
if (MediaEventHelper.isEligible(this.props.mxEvent)) {
@ -171,6 +177,7 @@ export default class MessageEvent extends React.Component<IProps> implements IMe
permalinkCreator={this.props.permalinkCreator}
mediaEventHelper={this.mediaHelper}
getRelationsForEvent={this.props.getRelationsForEvent}
isSeeingThroughMessageHiddenForModeration={this.props.isSeeingThroughMessageHiddenForModeration}
/> : null;
}
}

View file

@ -297,7 +297,9 @@ export default class TextualBody extends React.Component<IBodyProps, IState> {
nextProps.showUrlPreview !== this.props.showUrlPreview ||
nextProps.editState !== this.props.editState ||
nextState.links !== this.state.links ||
nextState.widgetHidden !== this.state.widgetHidden);
nextState.widgetHidden !== this.state.widgetHidden ||
nextProps.isSeeingThroughMessageHiddenForModeration
!== this.props.isSeeingThroughMessageHiddenForModeration);
}
private calculateUrlPreview(): void {
@ -504,6 +506,29 @@ export default class TextualBody extends React.Component<IBodyProps, IState> {
);
}
/**
* Render a marker informing the user that, while they can see the message,
* it is hidden for other users.
*/
private renderPendingModerationMarker() {
let text;
const visibility = this.props.mxEvent.messageVisibility();
switch (visibility.visible) {
case true:
throw new Error("renderPendingModerationMarker should only be applied to hidden messages");
case false:
if (visibility.reason) {
text = _t("Message pending moderation: %(reason)s", { reason: visibility.reason });
} else {
text = _t("Message pending moderation");
}
break;
}
return (
<span className="mx_EventTile_pendingModeration">{ `(${text})` }</span>
);
}
render() {
if (this.props.editState) {
return <EditMessageComposer editState={this.props.editState} className="mx_EventTile_content" />;
@ -554,6 +579,12 @@ export default class TextualBody extends React.Component<IBodyProps, IState> {
{ this.renderEditedMarker() }
</>;
}
if (this.props.isSeeingThroughMessageHiddenForModeration) {
body = <>
{ body }
{ this.renderPendingModerationMarker() }
</>;
}
if (this.props.highlightLink) {
body = <a href={this.props.highlightLink}>{ body }</a>;