From 29bb93f39910c234b59a25b04ec64e099513266e Mon Sep 17 00:00:00 2001
From: Paul <paulmakles@gmail.com>
Date: Tue, 22 Jun 2021 19:34:52 +0100
Subject: [PATCH] Remove twemoji dependency. Support re-connecting after going
 / starting offline.

---
 VERSION                                       |   1 +
 package.json                                  |   1 -
 publish.sh                                    |   7 +
 src/components/common/Emoji.tsx               |  37 ++++-
 .../common/messaging/embed/Embed.tsx          |  17 --
 .../navigation/items/Item.module.scss         | 156 ------------------
 .../intermediate/popovers/ImageViewer.tsx     |  14 +-
 .../intermediate/popovers/UserProfile.tsx     |  43 +++--
 src/context/revoltjs/events.ts                |  56 ++++---
 src/version.ts                                |   2 +-
 vite.config.ts                                |   5 +
 yarn.lock                                     |  45 -----
 12 files changed, 108 insertions(+), 276 deletions(-)
 create mode 100644 VERSION
 create mode 100644 publish.sh

diff --git a/VERSION b/VERSION
new file mode 100644
index 0000000..c096572
--- /dev/null
+++ b/VERSION
@@ -0,0 +1 @@
+1.0.0-vite
\ No newline at end of file
diff --git a/package.json b/package.json
index d739a38..bce943b 100644
--- a/package.json
+++ b/package.json
@@ -78,7 +78,6 @@
     "sass": "^1.35.1",
     "shade-blend-color": "^1.0.0",
     "styled-components": "^5.3.0",
-    "twemoji": "^13.1.0",
     "typescript": "^4.3.2",
     "ulid": "^2.3.0",
     "use-resize-observer": "^7.0.0",
diff --git a/publish.sh b/publish.sh
new file mode 100644
index 0000000..427d025
--- /dev/null
+++ b/publish.sh
@@ -0,0 +1,7 @@
+#!/bin/bash
+version=$(cat VERSION)
+
+                  docker build -t revoltchat/client:${version} . &&
+docker tag revoltchat/client:${version} revoltchat/client:latest &&
+                        docker push revoltchat/client:${version} &&
+                            docker push revoltchat/client:latest
diff --git a/src/components/common/Emoji.tsx b/src/components/common/Emoji.tsx
index 74e381b..791a003 100644
--- a/src/components/common/Emoji.tsx
+++ b/src/components/common/Emoji.tsx
@@ -1,22 +1,43 @@
-import twemoji from 'twemoji';
+import { EmojiPacks } from '../../redux/reducers/settings';
 
 var EMOJI_PACK = 'mutant';
 const REVISION = 3;
 
-/*export function setEmojiPack(pack: EmojiPacks) {
+export function setEmojiPack(pack: EmojiPacks) {
     EMOJI_PACK = pack;
-}*/
+}
+
+// Originally taken from Twemoji source code,
+// re-written by bree to be more readable.
+function codePoints(rune: string) {
+    const pairs = [];
+    let low = 0;
+    let i = 0;
+    
+    while (i < rune.length) {
+        const charCode = rune.charCodeAt(i++);
+        if (low) {
+            pairs.push(0x10000 + ((low - 0xd800) << 10) + (charCode - 0xdc00));
+            low = 0;
+        } else if (0xd800 <= charCode && charCode <= 0xdbff) {
+            low = charCode;
+        } else {
+            pairs.push(charCode);
+        }
+    }
+    
+    return pairs;
+}
 
 // Taken from Twemoji source code.
 // scripts/build.js#344
 // grabTheRightIcon(rawText);
 const UFE0Fg = /\uFE0F/g;
 const U200D = String.fromCharCode(0x200D);
-function toCodePoint(emoji: string) {
-    return twemoji.convert.toCodePoint(emoji.indexOf(U200D) < 0 ?
-        emoji.replace(UFE0Fg, '') :
-        emoji
-    );
+function toCodePoint(rune: string) {
+    return codePoints(rune.indexOf(U200D) < 0 ? rune.replace(UFE0Fg, '') : rune)
+        .map((val) => val.toString(16))
+        .join("-")
 }
 
 function parseEmoji(emoji: string) {
diff --git a/src/components/common/messaging/embed/Embed.tsx b/src/components/common/messaging/embed/Embed.tsx
index 647731d..f0bd8d7 100644
--- a/src/components/common/messaging/embed/Embed.tsx
+++ b/src/components/common/messaging/embed/Embed.tsx
@@ -52,23 +52,6 @@ export default function Embed({ embed }: Props) {
 
     switch (embed.type) {
         case 'Website': {
-            // ! FIXME: move this to january
-            /*if (embed.url && YOUTUBE_RE.test(embed.url)) {
-                embed.color = '#FF424F';
-            }
-
-            if (embed.url && TWITCH_RE.test(embed.url)) {
-                embed.color = '#7B68EE';
-            }
-
-            if (embed.url && SPOTIFY_RE.test(embed.url)) {
-                embed.color = '#1ABC9C';
-            }
-
-            if (embed.url && SOUNDCLOUD_RE.test(embed.url)) {
-                embed.color = '#FF7F50';
-            }*/
-
             // Determine special embed size.
             let mw, mh;
             let largeMedia = (embed.special && embed.special.type !== 'None') || embed.image?.size === 'Large';
diff --git a/src/components/navigation/items/Item.module.scss b/src/components/navigation/items/Item.module.scss
index d06e98f..27116eb 100644
--- a/src/components/navigation/items/Item.module.scss
+++ b/src/components/navigation/items/Item.module.scss
@@ -145,159 +145,3 @@
         background: var(--error);
     }
 }
-
-/* ! FIXME: check if anything is missing, then remove this block
-.olditem {
-    display: flex;
-    user-select: none;
-    align-items: center;
-    flex-direction: row;
-
-    gap: 8px;
-    height: 48px;
-    padding: 0 8px;
-    cursor: pointer;
-    font-size: 16px;
-    border-radius: 6px;
-    box-sizing: content-box;
-    transition: .1s ease background-color;
-
-    color: var(--tertiary-foreground);
-    stroke: var(--tertiary-foreground);
-
-    .avatar {
-        flex-shrink: 0;
-        height: 32px;
-        flex-shrink: 0;
-        padding: 10px 0;
-        box-sizing: content-box;
-
-        img {
-            width: 32px;
-            height: 32px;
-            border-radius: 50%;
-        }
-    }
-
-    div {
-        overflow: hidden;
-        white-space: nowrap;
-        text-overflow: ellipsis;
-        transition: color .1s ease-in-out;
-
-        &.content {
-            gap: 8px;
-            flex-grow: 1;
-            min-width: 0;
-            display: flex;
-            align-items: center;
-            flex-direction: row;
-
-            svg {
-                flex-shrink: 0;
-            }
-
-            span {
-                overflow: hidden;
-                white-space: nowrap;
-                text-overflow: ellipsis;
-            }
-        }
-
-        &.name {
-            flex-grow: 1;
-            display: flex;
-            flex-direction: column;
-            font-size: .90625rem;
-            font-weight: 600;
-
-            .subText {
-                font-size: .6875rem;
-                margin-top: -1px;
-                color: var(--tertiary-foreground);
-                font-weight: 500;
-            }
-        }
-
-        &.unread {
-            width: 6px;
-            height: 6px;
-            margin: 9px;
-            flex-shrink: 0;
-            border-radius: 50%;
-            background: var(--foreground);
-        }
-
-        &.button {
-            flex-shrink: 0;
-
-            .icon {
-                opacity: 0;
-                display: none;
-                transition: 0.1s ease opacity;
-            }
-        }
-    }
-
-    &[data-active="true"] {
-        color: var(--foreground);
-        stroke: var(--foreground);
-        background: var(--hover);
-        cursor: default;
-
-        .subText {
-            color: var(--secondary-foreground) !important;
-        }
-
-        .unread {
-            display: none;
-        }
-    }
-
-    &[data-alert="true"] {
-        color: var(--secondary-foreground);
-    }
-
-    &[data-type="user"] {
-        opacity: 0.4;
-        color: var(--foreground);
-        transition: 0.15s ease opacity;
-        cursor: pointer;
-
-        &[data-online="true"],
-        &:hover {
-            opacity: 1;
-            //background: none;
-        }
-    }
-
-    &[data-size="compact"] {
-        margin-bottom: 2px;
-        height: 32px;
-        transition: border-inline-start .1s ease-in-out;
-        border-inline-start: 4px solid transparent;
-
-        &[data-active="true"] {
-            border-inline-start: 4px solid var(--accent);
-            border-radius: 4px;
-        }
-    }
-
-    &[data-size="small"] {
-        margin-bottom: 2px;
-        height: 42px;
-    }
-
-    &:hover {
-        background: var(--hover);
-
-        div.button .unread {
-            display: none;
-        }
-
-        div.button .icon {
-            opacity: 1;
-            display: block;
-        }
-    }
-}*/
diff --git a/src/context/intermediate/popovers/ImageViewer.tsx b/src/context/intermediate/popovers/ImageViewer.tsx
index ae6754e..e42d6ca 100644
--- a/src/context/intermediate/popovers/ImageViewer.tsx
+++ b/src/context/intermediate/popovers/ImageViewer.tsx
@@ -3,6 +3,8 @@ import Modal from "../../../components/ui/Modal";
 import { useContext, useEffect } from "preact/hooks";
 import { AppContext } from "../../revoltjs/RevoltClient";
 import { Attachment, EmbedImage } from "revolt.js/dist/api/objects";
+import EmbedMediaActions from "../../../components/common/messaging/embed/EmbedMediaActions";
+import AttachmentActions from "../../../components/common/messaging/attachments/AttachmentActions";
 
 interface Props {
     onClose: () => void;
@@ -11,6 +13,12 @@ interface Props {
 }
 
 export function ImageViewer({ attachment, embed, onClose }: Props) {
+    // ! FIXME: temp code
+    // ! add proxy function to client
+    function proxyImage(url: string) {
+        return 'https://jan.revolt.chat/proxy?url=' + encodeURIComponent(url);
+    }
+    
     if (attachment && attachment.metadata.type !== "Image") return null;
     const client = useContext(AppContext);
 
@@ -31,13 +39,13 @@ export function ImageViewer({ attachment, embed, onClose }: Props) {
                 { attachment &&
                     <>
                         <img src={client.generateFileURL(attachment)} />
-                        {/*<AttachmentActions attachment={attachment} />*/}
+                        <AttachmentActions attachment={attachment} />
                     </>
                 }
                 { embed &&
                     <>
-                        {/*<img src={proxyImage(embed.url)} />*/}
-                        {/*<EmbedMediaActions embed={embed} />*/}
+                        <img src={proxyImage(embed.url)} />
+                        <EmbedMediaActions embed={embed} />
                     </>
                 }
             </div>
diff --git a/src/context/intermediate/popovers/UserProfile.tsx b/src/context/intermediate/popovers/UserProfile.tsx
index 2bcddb3..385829a 100644
--- a/src/context/intermediate/popovers/UserProfile.tsx
+++ b/src/context/intermediate/popovers/UserProfile.tsx
@@ -1,22 +1,24 @@
 import { decodeTime } from "ulid";
+import { Link, useHistory } from "react-router-dom";
 import { Localizer, Text } from "preact-i18n";
 import styles from "./UserProfile.module.scss";
 import Modal from "../../../components/ui/Modal";
 import { Route } from "revolt.js/dist/api/routes";
 import { Users } from "revolt.js/dist/api/objects";
 import { useIntermediate } from "../Intermediate";
-import { Link, useHistory } from "react-router-dom";
 import { CashStack } from "@styled-icons/bootstrap";
 import Preloader from "../../../components/ui/Preloader";
 import Tooltip from '../../../components/common/Tooltip';
+import IconButton from "../../../components/ui/IconButton";
 import Markdown from '../../../components/markdown/Markdown';
+import { UserPermission } from "revolt.js/dist/api/permissions";
 import UserIcon from '../../../components/common/user/UserIcon';
 import ChannelIcon from '../../../components/common/ChannelIcon';
 import UserStatus from '../../../components/common/user/UserStatus';
 import { Mail, Edit, UserPlus, Shield } from "@styled-icons/feather";
-import { useChannels, useForceUpdate, useUsers } from "../../revoltjs/hooks";
 import { useContext, useEffect, useLayoutEffect, useState } from "preact/hooks";
 import { AppContext, ClientStatus, StatusContext } from "../../revoltjs/RevoltClient";
+import { useChannels, useForceUpdate, useUserPermission, useUsers } from "../../revoltjs/hooks";
 
 interface Props {
     user_id: string;
@@ -43,23 +45,25 @@ export function UserProfile({ user_id, onClose, dummy, dummyProfile }: Props) {
         undefined | null | Route<"GET", "/users/id/mutual">["response"]
     >(undefined);
 
+    const history = useHistory();
     const client = useContext(AppContext);
     const status = useContext(StatusContext);
     const [tab, setTab] = useState("profile");
-    const history = useHistory();
 
     const ctx = useForceUpdate();
     const all_users = useUsers(undefined, ctx);
     const channels = useChannels(undefined, ctx);
-
+    
     const user = all_users.find(x => x!._id === user_id);
     const users = mutual?.users ? all_users.filter(x => mutual.users.includes(x!._id)) : undefined;
-
+    
     if (!user) {
         useEffect(onClose, []);
         return null;
     }
 
+    const permissions = useUserPermission(user!._id, ctx);
+
     useLayoutEffect(() => {
         if (!user_id) return;
         if (typeof profile !== 'undefined') setProfile(undefined);
@@ -93,17 +97,12 @@ export function UserProfile({ user_id, onClose, dummy, dummyProfile }: Props) {
         ) {
             setProfile(null);
 
-            // ! FIXME: in the future, also check if mutual guilds
-            // ! maybe just allow mutual group to allow profile viewing
-            /*if (
-                user.relationship === Users.Relationship.Friend ||
-                user.relationship === Users.Relationship.User
-            ) {*/
+            if (permissions & UserPermission.ViewProfile) {
                 client.users
                     .fetchProfile(user_id)
                     .then(data => setProfile(data))
                     .catch(() => {});
-            //}
+            }
         }
     }, [profile, status]);
 
@@ -157,35 +156,31 @@ export function UserProfile({ user_id, onClose, dummy, dummyProfile }: Props) {
                                     <Text id="app.context_menu.message_user" />
                                 }
                             >
-                                {/*<IconButton
+                                <IconButton
                                     onClick={() => {
                                         onClose();
                                         history.push(`/open/${user_id}`);
-                                    }}
-                                >*/}
+                                    }}>
                                     <Mail size={30} strokeWidth={1.5} />
-                                {/*</IconButton>*/}
+                                </IconButton>
                             </Tooltip>
                         </Localizer>
                     )}
                     {user.relationship === Users.Relationship.User && (
-                        /*<IconButton
+                        <IconButton
                             onClick={() => {
                                 onClose();
                                 if (dummy) return;
                                 history.push(`/settings/profile`);
-                            }}
-                        >*/
+                            }}>
                             <Edit size={28} strokeWidth={1.5} />
-                        /*</IconButton>*/
+                        </IconButton>
                     )}
                     {(user.relationship === Users.Relationship.Incoming ||
                         user.relationship === Users.Relationship.None) && (
-                        /*<IconButton
-                            onClick={() => client.users.addFriend(user.username)}
-                        >*/
+                        <IconButton onClick={() => client.users.addFriend(user.username)}>
                             <UserPlus size={28} strokeWidth={1.5} />
-                        /*</IconButton>*/
+                        </IconButton>
                     )}
                 </div>
                 <div className={styles.tabs}>
diff --git a/src/context/revoltjs/events.ts b/src/context/revoltjs/events.ts
index b6fba62..b97375f 100644
--- a/src/context/revoltjs/events.ts
+++ b/src/context/revoltjs/events.ts
@@ -18,23 +18,28 @@ export function registerEvents({
     operations,
     dispatcher
 }: { operations: ClientOperations } & WithDispatcher, setStatus: StateUpdater<ClientStatus>, client: Client) {
+    function attemptReconnect() {
+        if (preventReconnect) return;
+        function reconnect() {
+            preventUntil = +new Date() + 2000;
+            client.websocket.connect().catch(err => console.error(err));
+        }
+
+        if (+new Date() > preventUntil) {
+            setTimeout(reconnect, 2000);
+        } else {
+            reconnect();
+        }
+    }
+
     const listeners = {
         connecting: () =>
             operations.ready() && setStatus(ClientStatus.CONNECTING),
 
         dropped: () => {
-            operations.ready() && setStatus(ClientStatus.DISCONNECTED);
-
-            if (preventReconnect) return;
-            function reconnect() {
-                preventUntil = +new Date() + 2000;
-                client.websocket.connect().catch(err => console.error(err));
-            }
-
-            if (+new Date() > preventUntil) {
-                setTimeout(reconnect, 2000);
-            } else {
-                reconnect();
+            if (operations.ready()) {
+                setStatus(ClientStatus.DISCONNECTED);
+                attemptReconnect();
             }
         },
 
@@ -79,9 +84,7 @@ export function registerEvents({
             }
         },
 
-        ready: () => {
-            setStatus(ClientStatus.ONLINE);
-        }
+        ready: () => setStatus(ClientStatus.ONLINE)
     };
 
     let listenerFunc: { [key: string]: Function };
@@ -101,20 +104,31 @@ export function registerEvents({
         client.addListener(listener, (listenerFunc as any)[listener]);
     }
 
-    /*const online = () =>
-        operations.ready() && setStatus(ClientStatus.RECONNECTING);
-    const offline = () =>
-        operations.ready() && setStatus(ClientStatus.OFFLINE);
+    const online = () => {
+        if (operations.ready()) {
+            setStatus(ClientStatus.RECONNECTING);
+            setReconnectDisallowed(false);
+            attemptReconnect();
+        }
+    };
+    
+    const offline = () => {
+        if (operations.ready()) {
+            setReconnectDisallowed(true);
+            client.websocket.disconnect();
+            setStatus(ClientStatus.OFFLINE);
+        }
+    };
 
     window.addEventListener("online", online);
     window.addEventListener("offline", offline);
 
     return () => {
         for (const listener of Object.keys(listenerFunc)) {
-            RevoltClient.removeListener(listener, (listenerFunc as any)[listener]);
+            client.removeListener(listener, (listenerFunc as any)[listener]);
         }
 
         window.removeEventListener("online", online);
         window.removeEventListener("offline", offline);
-    };*/
+    };
 }
diff --git a/src/version.ts b/src/version.ts
index 4c86db3..63b6810 100644
--- a/src/version.ts
+++ b/src/version.ts
@@ -1 +1 @@
-export const APP_VERSION = "1.0.0-vite";
+export const APP_VERSION = "__APP_VERSION__";
diff --git a/vite.config.ts b/vite.config.ts
index 8da8448..e797ce4 100644
--- a/vite.config.ts
+++ b/vite.config.ts
@@ -19,6 +19,10 @@ function getGitRevision() {
   }
 }
 
+function getVersion() {
+  return readFileSync('VERSION').toString();
+}
+
 export default defineConfig({
   plugins: [
     preact(),
@@ -51,6 +55,7 @@ export default defineConfig({
     }),
     replace({
       __GIT_REVISION__: getGitRevision(),
+      __APP_VERSION__: getVersion(),
       preventAssignment: true
     })
   ],
diff --git a/yarn.lock b/yarn.lock
index 31f59fe..18f56c4 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -2252,15 +2252,6 @@ from@~0:
   resolved "https://registry.yarnpkg.com/from/-/from-0.1.7.tgz#83c60afc58b9c56997007ed1a768b3ab303a44fe"
   integrity sha1-g8YK/Fi5xWmXAH7Rp2izqzA6RP4=
 
-fs-extra@^8.0.1:
-  version "8.1.0"
-  resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-8.1.0.tgz#49d43c45a88cd9677668cb7be1b46efdb8d2e1c0"
-  integrity sha512-yhlQgA6mnOJUKOsRUFsgJdQCvkKhcz8tlZG5HBQfReYZy46OwLcY+Zia0mtdHsOo9y/hP+CxMN0TU9QxoOtG4g==
-  dependencies:
-    graceful-fs "^4.2.0"
-    jsonfile "^4.0.0"
-    universalify "^0.1.0"
-
 fs-extra@^9.0.1:
   version "9.1.0"
   resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-9.1.0.tgz#5954460c764a8da2094ba3554bf839e6b9a7c86d"
@@ -2648,22 +2639,6 @@ json5@^2.1.2:
   dependencies:
     minimist "^1.2.5"
 
-jsonfile@^4.0.0:
-  version "4.0.0"
-  resolved "https://registry.yarnpkg.com/jsonfile/-/jsonfile-4.0.0.tgz#8771aae0799b64076b76640fca058f9c10e33ecb"
-  integrity sha1-h3Gq4HmbZAdrdmQPygWPnBDjPss=
-  optionalDependencies:
-    graceful-fs "^4.1.6"
-
-jsonfile@^5.0.0:
-  version "5.0.0"
-  resolved "https://registry.yarnpkg.com/jsonfile/-/jsonfile-5.0.0.tgz#e6b718f73da420d612823996fdf14a03f6ff6922"
-  integrity sha512-NQRZ5CRo74MhMMC3/3r5g2k4fjodJ/wh8MxjFbCViWKFjxrnudWSY5vomh+23ZaXzAS7J3fBZIR2dV6WbmfM0w==
-  dependencies:
-    universalify "^0.1.2"
-  optionalDependencies:
-    graceful-fs "^4.1.6"
-
 jsonfile@^6.0.1:
   version "6.1.0"
   resolved "https://registry.yarnpkg.com/jsonfile/-/jsonfile-6.1.0.tgz#bc55b2634793c679ec6403094eb13698a6ec0aae"
@@ -3778,21 +3753,6 @@ tsutils@^3.17.1, tsutils@^3.21.0:
   dependencies:
     tslib "^1.8.1"
 
-twemoji-parser@13.1.0:
-  version "13.1.0"
-  resolved "https://registry.yarnpkg.com/twemoji-parser/-/twemoji-parser-13.1.0.tgz#65e7e449c59258791b22ac0b37077349127e3ea4"
-  integrity sha512-AQOzLJpYlpWMy8n+0ATyKKZzWlZBJN+G0C+5lhX7Ftc2PeEVdUU/7ns2Pn2vVje26AIZ/OHwFoUbdv6YYD/wGg==
-
-twemoji@^13.1.0:
-  version "13.1.0"
-  resolved "https://registry.yarnpkg.com/twemoji/-/twemoji-13.1.0.tgz#65bb71e966dae56f0d42c30176f04cbdae109913"
-  integrity sha512-e3fZRl2S9UQQdBFLYXtTBT6o4vidJMnpWUAhJA+yLGR+kaUTZAt3PixC0cGvvxWSuq2MSz/o0rJraOXrWw/4Ew==
-  dependencies:
-    fs-extra "^8.0.1"
-    jsonfile "^5.0.0"
-    twemoji-parser "13.1.0"
-    universalify "^0.1.2"
-
 type-check@^0.4.0, type-check@~0.4.0:
   version "0.4.0"
   resolved "https://registry.yarnpkg.com/type-check/-/type-check-0.4.0.tgz#07b8203bfa7056c0657050e3ccd2c37730bab8f1"
@@ -3870,11 +3830,6 @@ unique-string@^2.0.0:
   dependencies:
     crypto-random-string "^2.0.0"
 
-universalify@^0.1.0, universalify@^0.1.2:
-  version "0.1.2"
-  resolved "https://registry.yarnpkg.com/universalify/-/universalify-0.1.2.tgz#b646f69be3942dabcecc9d6639c80dc105efaa66"
-  integrity sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==
-
 universalify@^2.0.0:
   version "2.0.0"
   resolved "https://registry.yarnpkg.com/universalify/-/universalify-2.0.0.tgz#75a4984efedc4b08975c5aeb73f530d02df25717"
-- 
GitLab