Add option to view event source (#320)
authorginnyTheCat <ginnythecat@lelux.net>
Tue, 22 Feb 2022 13:31:04 +0000 (14:31 +0100)
committerGitHub <noreply@github.com>
Tue, 22 Feb 2022 13:31:04 +0000 (19:01 +0530)
* Add view source

* Add view source cons

* Change design

* Use PopupWindow instead of Dialog

* Undo changes to Dialog.jsx

src/app/molecules/message/Message.jsx
src/app/molecules/popup-window/PopupWindow.jsx
src/app/organisms/pw/Dialogs.jsx
src/app/organisms/view-source/ViewSource.jsx [new file with mode: 0644]
src/app/organisms/view-source/ViewSource.scss [new file with mode: 0644]
src/client/action/navigation.js
src/client/state/cons.js
src/client/state/navigation.js

index d3205d6925731d5ecf2305e1e42e2cc52eafb69d..c1ceda87b0182d50e8dcffe1f1397e2a63718158 100644 (file)
@@ -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 />
index 43e421ead6bddcd0037f6e138e8298adb757be7e..1133252ac3b80017cc5fb19a8b2a739c4b2731d7 100644 (file)
@@ -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,
 };
index b5038e5650a72fbced1ed709d106fbba131f3a5d..55c37f36a607da131f830224a97b81f8e1d1577a 100644 (file)
@@ -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 />
diff --git a/src/app/organisms/view-source/ViewSource.jsx b/src/app/organisms/view-source/ViewSource.jsx
new file mode 100644 (file)
index 0000000..a79a4d9
--- /dev/null
@@ -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;
diff --git a/src/app/organisms/view-source/ViewSource.scss b/src/app/organisms/view-source/ViewSource.scss
new file mode 100644 (file)
index 0000000..81b53f3
--- /dev/null
@@ -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;
+  }
+}
index 964a3f5b6df6710c2891f5aa1d483d0e4412525d..ab01e38a3a83327fe94bb3076f765db71913c03c 100644 (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) {
   appDispatcher.dispatch({
     type: cons.actions.navigation.CLICK_REPLY_TO,
index 2d20dbeebef49c2ba39ba44b01cd7d2fc54288aa..8ff6eede85825ea9977f0a49ccc51b050bc2cd46 100644 (file)
@@ -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',
index 52d5f1504e662fd794da5f689c218e579fd35c7a..91fca2daff3b9d0561ebcaaf99aad6c3593b41d2 100644 (file)
@@ -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,