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:
parent
a8a168b0a7
commit
1d7fbc841e
8 changed files with 119 additions and 2 deletions
|
@ -14,7 +14,7 @@ import colorMXID from '../../../util/colorMXID';
|
|||
import { getEventCords } from '../../../util/common';
|
||||
import { redactEvent, sendReaction } from '../../../client/action/roomTimeline';
|
||||
import {
|
||||
openEmojiBoard, openProfileViewer, openReadReceipts, replyTo,
|
||||
openEmojiBoard, openProfileViewer, openReadReceipts, openViewSource, replyTo,
|
||||
} from '../../../client/action/navigation';
|
||||
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 PencilIC from '../../../../public/res/ic/outlined/pencil.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';
|
||||
|
||||
function PlaceholderMessage() {
|
||||
|
@ -510,6 +511,12 @@ const MessageOptions = React.memo(({
|
|||
>
|
||||
Read receipts
|
||||
</MenuItem>
|
||||
<MenuItem
|
||||
iconSrc={CmdIC}
|
||||
onClick={() => openViewSource(mEvent)}
|
||||
>
|
||||
View source
|
||||
</MenuItem>
|
||||
{(canIRedact || senderId === mx.getUserId()) && (
|
||||
<>
|
||||
<MenuBorder />
|
||||
|
|
|
@ -51,7 +51,7 @@ PWContentSelector.propTypes = {
|
|||
function PopupWindow({
|
||||
className, isOpen, title, contentTitle,
|
||||
drawer, drawerOptions, contentOptions,
|
||||
onRequestClose, children,
|
||||
onAfterClose, onRequestClose, children,
|
||||
}) {
|
||||
const haveDrawer = drawer !== null;
|
||||
const cTitle = contentTitle !== null ? contentTitle : title;
|
||||
|
@ -60,6 +60,7 @@ function PopupWindow({
|
|||
<RawModal
|
||||
className={`${className === null ? '' : `${className} `}pw-model`}
|
||||
isOpen={isOpen}
|
||||
onAfterClose={onAfterClose}
|
||||
onRequestClose={onRequestClose}
|
||||
size={haveDrawer ? 'large' : 'medium'}
|
||||
>
|
||||
|
@ -116,6 +117,7 @@ PopupWindow.defaultProps = {
|
|||
contentTitle: null,
|
||||
drawerOptions: null,
|
||||
contentOptions: null,
|
||||
onAfterClose: null,
|
||||
onRequestClose: null,
|
||||
};
|
||||
|
||||
|
@ -127,6 +129,7 @@ PopupWindow.propTypes = {
|
|||
drawer: PropTypes.node,
|
||||
drawerOptions: PropTypes.node,
|
||||
contentOptions: PropTypes.node,
|
||||
onAfterClose: PropTypes.func,
|
||||
onRequestClose: PropTypes.func,
|
||||
children: PropTypes.node.isRequired,
|
||||
};
|
||||
|
|
|
@ -4,11 +4,13 @@ import ReadReceipts from '../read-receipts/ReadReceipts';
|
|||
import ProfileViewer from '../profile-viewer/ProfileViewer';
|
||||
import SpaceAddExisting from '../../molecules/space-add-existing/SpaceAddExisting';
|
||||
import Search from '../search/Search';
|
||||
import ViewSource from '../view-source/ViewSource';
|
||||
|
||||
function Dialogs() {
|
||||
return (
|
||||
<>
|
||||
<ReadReceipts />
|
||||
<ViewSource />
|
||||
<ProfileViewer />
|
||||
<SpaceAddExisting />
|
||||
<Search />
|
||||
|
|
73
src/app/organisms/view-source/ViewSource.jsx
Normal file
73
src/app/organisms/view-source/ViewSource.jsx
Normal 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;
|
17
src/app/organisms/view-source/ViewSource.scss
Normal file
17
src/app/organisms/view-source/ViewSource.scss
Normal 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;
|
||||
}
|
||||
}
|
|
@ -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) {
|
||||
appDispatcher.dispatch({
|
||||
type: cons.actions.navigation.CLICK_REPLY_TO,
|
||||
|
|
|
@ -42,6 +42,7 @@ const cons = {
|
|||
OPEN_SETTINGS: 'OPEN_SETTINGS',
|
||||
OPEN_EMOJIBOARD: 'OPEN_EMOJIBOARD',
|
||||
OPEN_READRECEIPTS: 'OPEN_READRECEIPTS',
|
||||
OPEN_VIEWSOURCE: 'OPEN_VIEWSOURCE',
|
||||
CLICK_REPLY_TO: 'CLICK_REPLY_TO',
|
||||
OPEN_SEARCH: 'OPEN_SEARCH',
|
||||
OPEN_REUSABLE_CONTEXT_MENU: 'OPEN_REUSABLE_CONTEXT_MENU',
|
||||
|
@ -82,6 +83,7 @@ const cons = {
|
|||
PROFILE_VIEWER_OPENED: 'PROFILE_VIEWER_OPENED',
|
||||
EMOJIBOARD_OPENED: 'EMOJIBOARD_OPENED',
|
||||
READRECEIPTS_OPENED: 'READRECEIPTS_OPENED',
|
||||
VIEWSOURCE_OPENED: 'VIEWSOURCE_OPENED',
|
||||
REPLY_TO_CLICKED: 'REPLY_TO_CLICKED',
|
||||
SEARCH_OPENED: 'SEARCH_OPENED',
|
||||
REUSABLE_CONTEXT_MENU_OPENED: 'REUSABLE_CONTEXT_MENU_OPENED',
|
||||
|
|
|
@ -137,6 +137,12 @@ class Navigation extends EventEmitter {
|
|||
action.userIds,
|
||||
);
|
||||
},
|
||||
[cons.actions.navigation.OPEN_VIEWSOURCE]: () => {
|
||||
this.emit(
|
||||
cons.events.navigation.VIEWSOURCE_OPENED,
|
||||
action.event,
|
||||
);
|
||||
},
|
||||
[cons.actions.navigation.CLICK_REPLY_TO]: () => {
|
||||
this.emit(
|
||||
cons.events.navigation.REPLY_TO_CLICKED,
|
||||
|
|
Loading…
Reference in a new issue