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 { 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 />
|
||||||
|
|
|
@ -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,
|
||||||
};
|
};
|
||||||
|
|
|
@ -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 />
|
||||||
|
|
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) {
|
export function replyTo(userId, eventId, body) {
|
||||||
appDispatcher.dispatch({
|
appDispatcher.dispatch({
|
||||||
type: cons.actions.navigation.CLICK_REPLY_TO,
|
type: cons.actions.navigation.CLICK_REPLY_TO,
|
||||||
|
|
|
@ -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',
|
||||||
|
|
|
@ -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,
|
||||||
|
|
Loading…
Reference in a new issue