Add option to view event source (#320)

* Add view source

* Add view source cons

* Change design

* Use PopupWindow instead of Dialog

* Undo changes to Dialog.jsx
This commit is contained in:
ginnyTheCat 2022-02-22 14:31:04 +01:00 committed by GitHub
parent a8a168b0a7
commit 1d7fbc841e
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
8 changed files with 119 additions and 2 deletions

View file

@ -14,7 +14,7 @@ import colorMXID from '../../../util/colorMXID';
import { getEventCords } from '../../../util/common'; import { getEventCords } from '../../../util/common';
import { redactEvent, sendReaction } from '../../../client/action/roomTimeline'; import { redactEvent, sendReaction } from '../../../client/action/roomTimeline';
import { import {
openEmojiBoard, openProfileViewer, openReadReceipts, replyTo, openEmojiBoard, openProfileViewer, openReadReceipts, openViewSource, replyTo,
} from '../../../client/action/navigation'; } from '../../../client/action/navigation';
import { sanitizeCustomHtml } from '../../../util/sanitize'; import { sanitizeCustomHtml } from '../../../util/sanitize';
@ -33,6 +33,7 @@ import EmojiAddIC from '../../../../public/res/ic/outlined/emoji-add.svg';
import VerticalMenuIC from '../../../../public/res/ic/outlined/vertical-menu.svg'; import VerticalMenuIC from '../../../../public/res/ic/outlined/vertical-menu.svg';
import PencilIC from '../../../../public/res/ic/outlined/pencil.svg'; import PencilIC from '../../../../public/res/ic/outlined/pencil.svg';
import TickMarkIC from '../../../../public/res/ic/outlined/tick-mark.svg'; import TickMarkIC from '../../../../public/res/ic/outlined/tick-mark.svg';
import CmdIC from '../../../../public/res/ic/outlined/cmd.svg';
import BinIC from '../../../../public/res/ic/outlined/bin.svg'; import BinIC from '../../../../public/res/ic/outlined/bin.svg';
function PlaceholderMessage() { function PlaceholderMessage() {
@ -510,6 +511,12 @@ const MessageOptions = React.memo(({
> >
Read receipts Read receipts
</MenuItem> </MenuItem>
<MenuItem
iconSrc={CmdIC}
onClick={() => openViewSource(mEvent)}
>
View source
</MenuItem>
{(canIRedact || senderId === mx.getUserId()) && ( {(canIRedact || senderId === mx.getUserId()) && (
<> <>
<MenuBorder /> <MenuBorder />

View file

@ -51,7 +51,7 @@ PWContentSelector.propTypes = {
function PopupWindow({ function PopupWindow({
className, isOpen, title, contentTitle, className, isOpen, title, contentTitle,
drawer, drawerOptions, contentOptions, drawer, drawerOptions, contentOptions,
onRequestClose, children, onAfterClose, onRequestClose, children,
}) { }) {
const haveDrawer = drawer !== null; const haveDrawer = drawer !== null;
const cTitle = contentTitle !== null ? contentTitle : title; const cTitle = contentTitle !== null ? contentTitle : title;
@ -60,6 +60,7 @@ function PopupWindow({
<RawModal <RawModal
className={`${className === null ? '' : `${className} `}pw-model`} className={`${className === null ? '' : `${className} `}pw-model`}
isOpen={isOpen} isOpen={isOpen}
onAfterClose={onAfterClose}
onRequestClose={onRequestClose} onRequestClose={onRequestClose}
size={haveDrawer ? 'large' : 'medium'} size={haveDrawer ? 'large' : 'medium'}
> >
@ -116,6 +117,7 @@ PopupWindow.defaultProps = {
contentTitle: null, contentTitle: null,
drawerOptions: null, drawerOptions: null,
contentOptions: null, contentOptions: null,
onAfterClose: null,
onRequestClose: null, onRequestClose: null,
}; };
@ -127,6 +129,7 @@ PopupWindow.propTypes = {
drawer: PropTypes.node, drawer: PropTypes.node,
drawerOptions: PropTypes.node, drawerOptions: PropTypes.node,
contentOptions: PropTypes.node, contentOptions: PropTypes.node,
onAfterClose: PropTypes.func,
onRequestClose: PropTypes.func, onRequestClose: PropTypes.func,
children: PropTypes.node.isRequired, children: PropTypes.node.isRequired,
}; };

View file

@ -4,11 +4,13 @@ import ReadReceipts from '../read-receipts/ReadReceipts';
import ProfileViewer from '../profile-viewer/ProfileViewer'; import ProfileViewer from '../profile-viewer/ProfileViewer';
import SpaceAddExisting from '../../molecules/space-add-existing/SpaceAddExisting'; import SpaceAddExisting from '../../molecules/space-add-existing/SpaceAddExisting';
import Search from '../search/Search'; import Search from '../search/Search';
import ViewSource from '../view-source/ViewSource';
function Dialogs() { function Dialogs() {
return ( return (
<> <>
<ReadReceipts /> <ReadReceipts />
<ViewSource />
<ProfileViewer /> <ProfileViewer />
<SpaceAddExisting /> <SpaceAddExisting />
<Search /> <Search />

View file

@ -0,0 +1,73 @@
import React, { useEffect, useState } from 'react';
import PropTypes from 'prop-types';
import './ViewSource.scss';
import cons from '../../../client/state/cons';
import navigation from '../../../client/state/navigation';
import IconButton from '../../atoms/button/IconButton';
import { MenuHeader } from '../../atoms/context-menu/ContextMenu';
import ScrollView from '../../atoms/scroll/ScrollView';
import PopupWindow from '../../molecules/popup-window/PopupWindow';
import CrossIC from '../../../../public/res/ic/outlined/cross.svg';
function ViewSourceBlock({ title, json }) {
return (
<div className="view-source__card">
<MenuHeader>{title}</MenuHeader>
<ScrollView horizontal vertical={false} autoHide>
<pre className="text text-b1">
<code className="language-json">
{JSON.stringify(json, null, 2)}
</code>
</pre>
</ScrollView>
</div>
);
}
ViewSourceBlock.propTypes = {
title: PropTypes.string.isRequired,
json: PropTypes.shape({}).isRequired,
};
function ViewSource() {
const [isOpen, setIsOpen] = useState(false);
const [event, setEvent] = useState(null);
useEffect(() => {
const loadViewSource = (e) => {
setEvent(e);
setIsOpen(true);
};
navigation.on(cons.events.navigation.VIEWSOURCE_OPENED, loadViewSource);
return () => {
navigation.removeListener(cons.events.navigation.VIEWSOURCE_OPENED, loadViewSource);
};
}, []);
const handleAfterClose = () => {
setEvent(null);
};
const renderViewSource = () => (
<div className="view-source">
{event.isEncrypted() && <ViewSourceBlock title="Decrypted source" json={event.getEffectiveEvent()} />}
<ViewSourceBlock title="Original source" json={event.event} />
</div>
);
return (
<PopupWindow
isOpen={isOpen}
title="View source"
onAfterClose={handleAfterClose}
onRequestClose={() => setIsOpen(false)}
contentOptions={<IconButton src={CrossIC} onClick={() => setIsOpen(false)} tooltip="Close" />}
>
{event && renderViewSource()}
</PopupWindow>
);
}
export default ViewSource;

View file

@ -0,0 +1,17 @@
@use '../../partials/dir';
.view-source {
@include dir.side(margin, var(--sp-normal), var(--sp-extra-tight));
& pre {
padding: var(--sp-extra-tight);
}
&__card {
margin: var(--sp-normal) 0;
background-color: var(--bg-surface-hover);
border-radius: var(--bo-radius);
box-shadow: var(--bs-surface-border);
overflow: hidden;
}
}

View file

@ -109,6 +109,13 @@ export function openReadReceipts(roomId, userIds) {
}); });
} }
export function openViewSource(event) {
appDispatcher.dispatch({
type: cons.actions.navigation.OPEN_VIEWSOURCE,
event,
});
}
export function replyTo(userId, eventId, body) { export function replyTo(userId, eventId, body) {
appDispatcher.dispatch({ appDispatcher.dispatch({
type: cons.actions.navigation.CLICK_REPLY_TO, type: cons.actions.navigation.CLICK_REPLY_TO,

View file

@ -42,6 +42,7 @@ const cons = {
OPEN_SETTINGS: 'OPEN_SETTINGS', OPEN_SETTINGS: 'OPEN_SETTINGS',
OPEN_EMOJIBOARD: 'OPEN_EMOJIBOARD', OPEN_EMOJIBOARD: 'OPEN_EMOJIBOARD',
OPEN_READRECEIPTS: 'OPEN_READRECEIPTS', OPEN_READRECEIPTS: 'OPEN_READRECEIPTS',
OPEN_VIEWSOURCE: 'OPEN_VIEWSOURCE',
CLICK_REPLY_TO: 'CLICK_REPLY_TO', CLICK_REPLY_TO: 'CLICK_REPLY_TO',
OPEN_SEARCH: 'OPEN_SEARCH', OPEN_SEARCH: 'OPEN_SEARCH',
OPEN_REUSABLE_CONTEXT_MENU: 'OPEN_REUSABLE_CONTEXT_MENU', OPEN_REUSABLE_CONTEXT_MENU: 'OPEN_REUSABLE_CONTEXT_MENU',
@ -82,6 +83,7 @@ const cons = {
PROFILE_VIEWER_OPENED: 'PROFILE_VIEWER_OPENED', PROFILE_VIEWER_OPENED: 'PROFILE_VIEWER_OPENED',
EMOJIBOARD_OPENED: 'EMOJIBOARD_OPENED', EMOJIBOARD_OPENED: 'EMOJIBOARD_OPENED',
READRECEIPTS_OPENED: 'READRECEIPTS_OPENED', READRECEIPTS_OPENED: 'READRECEIPTS_OPENED',
VIEWSOURCE_OPENED: 'VIEWSOURCE_OPENED',
REPLY_TO_CLICKED: 'REPLY_TO_CLICKED', REPLY_TO_CLICKED: 'REPLY_TO_CLICKED',
SEARCH_OPENED: 'SEARCH_OPENED', SEARCH_OPENED: 'SEARCH_OPENED',
REUSABLE_CONTEXT_MENU_OPENED: 'REUSABLE_CONTEXT_MENU_OPENED', REUSABLE_CONTEXT_MENU_OPENED: 'REUSABLE_CONTEXT_MENU_OPENED',

View file

@ -137,6 +137,12 @@ class Navigation extends EventEmitter {
action.userIds, action.userIds,
); );
}, },
[cons.actions.navigation.OPEN_VIEWSOURCE]: () => {
this.emit(
cons.events.navigation.VIEWSOURCE_OPENED,
action.event,
);
},
[cons.actions.navigation.CLICK_REPLY_TO]: () => { [cons.actions.navigation.CLICK_REPLY_TO]: () => {
this.emit( this.emit(
cons.events.navigation.REPLY_TO_CLICKED, cons.events.navigation.REPLY_TO_CLICKED,