Remove twemoji dependency.

Support re-connecting after going / starting offline.
This commit is contained in:
Paul 2021-06-22 19:34:52 +01:00
parent 01efee817f
commit 29bb93f399
12 changed files with 108 additions and 276 deletions

1
VERSION Normal file
View file

@ -0,0 +1 @@
1.0.0-vite

View file

@ -78,7 +78,6 @@
"sass": "^1.35.1", "sass": "^1.35.1",
"shade-blend-color": "^1.0.0", "shade-blend-color": "^1.0.0",
"styled-components": "^5.3.0", "styled-components": "^5.3.0",
"twemoji": "^13.1.0",
"typescript": "^4.3.2", "typescript": "^4.3.2",
"ulid": "^2.3.0", "ulid": "^2.3.0",
"use-resize-observer": "^7.0.0", "use-resize-observer": "^7.0.0",

7
publish.sh Normal file
View file

@ -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

View file

@ -1,22 +1,43 @@
import twemoji from 'twemoji'; import { EmojiPacks } from '../../redux/reducers/settings';
var EMOJI_PACK = 'mutant'; var EMOJI_PACK = 'mutant';
const REVISION = 3; const REVISION = 3;
/*export function setEmojiPack(pack: EmojiPacks) { export function setEmojiPack(pack: EmojiPacks) {
EMOJI_PACK = pack; 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. // Taken from Twemoji source code.
// scripts/build.js#344 // scripts/build.js#344
// grabTheRightIcon(rawText); // grabTheRightIcon(rawText);
const UFE0Fg = /\uFE0F/g; const UFE0Fg = /\uFE0F/g;
const U200D = String.fromCharCode(0x200D); const U200D = String.fromCharCode(0x200D);
function toCodePoint(emoji: string) { function toCodePoint(rune: string) {
return twemoji.convert.toCodePoint(emoji.indexOf(U200D) < 0 ? return codePoints(rune.indexOf(U200D) < 0 ? rune.replace(UFE0Fg, '') : rune)
emoji.replace(UFE0Fg, '') : .map((val) => val.toString(16))
emoji .join("-")
);
} }
function parseEmoji(emoji: string) { function parseEmoji(emoji: string) {

View file

@ -52,23 +52,6 @@ export default function Embed({ embed }: Props) {
switch (embed.type) { switch (embed.type) {
case 'Website': { 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. // Determine special embed size.
let mw, mh; let mw, mh;
let largeMedia = (embed.special && embed.special.type !== 'None') || embed.image?.size === 'Large'; let largeMedia = (embed.special && embed.special.type !== 'None') || embed.image?.size === 'Large';

View file

@ -145,159 +145,3 @@
background: var(--error); 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;
}
}
}*/

View file

@ -3,6 +3,8 @@ import Modal from "../../../components/ui/Modal";
import { useContext, useEffect } from "preact/hooks"; import { useContext, useEffect } from "preact/hooks";
import { AppContext } from "../../revoltjs/RevoltClient"; import { AppContext } from "../../revoltjs/RevoltClient";
import { Attachment, EmbedImage } from "revolt.js/dist/api/objects"; 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 { interface Props {
onClose: () => void; onClose: () => void;
@ -11,6 +13,12 @@ interface Props {
} }
export function ImageViewer({ attachment, embed, onClose }: 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; if (attachment && attachment.metadata.type !== "Image") return null;
const client = useContext(AppContext); const client = useContext(AppContext);
@ -31,13 +39,13 @@ export function ImageViewer({ attachment, embed, onClose }: Props) {
{ attachment && { attachment &&
<> <>
<img src={client.generateFileURL(attachment)} /> <img src={client.generateFileURL(attachment)} />
{/*<AttachmentActions attachment={attachment} />*/} <AttachmentActions attachment={attachment} />
</> </>
} }
{ embed && { embed &&
<> <>
{/*<img src={proxyImage(embed.url)} />*/} <img src={proxyImage(embed.url)} />
{/*<EmbedMediaActions embed={embed} />*/} <EmbedMediaActions embed={embed} />
</> </>
} }
</div> </div>

View file

@ -1,22 +1,24 @@
import { decodeTime } from "ulid"; import { decodeTime } from "ulid";
import { Link, useHistory } from "react-router-dom";
import { Localizer, Text } from "preact-i18n"; import { Localizer, Text } from "preact-i18n";
import styles from "./UserProfile.module.scss"; import styles from "./UserProfile.module.scss";
import Modal from "../../../components/ui/Modal"; import Modal from "../../../components/ui/Modal";
import { Route } from "revolt.js/dist/api/routes"; import { Route } from "revolt.js/dist/api/routes";
import { Users } from "revolt.js/dist/api/objects"; import { Users } from "revolt.js/dist/api/objects";
import { useIntermediate } from "../Intermediate"; import { useIntermediate } from "../Intermediate";
import { Link, useHistory } from "react-router-dom";
import { CashStack } from "@styled-icons/bootstrap"; import { CashStack } from "@styled-icons/bootstrap";
import Preloader from "../../../components/ui/Preloader"; import Preloader from "../../../components/ui/Preloader";
import Tooltip from '../../../components/common/Tooltip'; import Tooltip from '../../../components/common/Tooltip';
import IconButton from "../../../components/ui/IconButton";
import Markdown from '../../../components/markdown/Markdown'; import Markdown from '../../../components/markdown/Markdown';
import { UserPermission } from "revolt.js/dist/api/permissions";
import UserIcon from '../../../components/common/user/UserIcon'; import UserIcon from '../../../components/common/user/UserIcon';
import ChannelIcon from '../../../components/common/ChannelIcon'; import ChannelIcon from '../../../components/common/ChannelIcon';
import UserStatus from '../../../components/common/user/UserStatus'; import UserStatus from '../../../components/common/user/UserStatus';
import { Mail, Edit, UserPlus, Shield } from "@styled-icons/feather"; 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 { useContext, useEffect, useLayoutEffect, useState } from "preact/hooks";
import { AppContext, ClientStatus, StatusContext } from "../../revoltjs/RevoltClient"; import { AppContext, ClientStatus, StatusContext } from "../../revoltjs/RevoltClient";
import { useChannels, useForceUpdate, useUserPermission, useUsers } from "../../revoltjs/hooks";
interface Props { interface Props {
user_id: string; user_id: string;
@ -43,10 +45,10 @@ export function UserProfile({ user_id, onClose, dummy, dummyProfile }: Props) {
undefined | null | Route<"GET", "/users/id/mutual">["response"] undefined | null | Route<"GET", "/users/id/mutual">["response"]
>(undefined); >(undefined);
const history = useHistory();
const client = useContext(AppContext); const client = useContext(AppContext);
const status = useContext(StatusContext); const status = useContext(StatusContext);
const [tab, setTab] = useState("profile"); const [tab, setTab] = useState("profile");
const history = useHistory();
const ctx = useForceUpdate(); const ctx = useForceUpdate();
const all_users = useUsers(undefined, ctx); const all_users = useUsers(undefined, ctx);
@ -60,6 +62,8 @@ export function UserProfile({ user_id, onClose, dummy, dummyProfile }: Props) {
return null; return null;
} }
const permissions = useUserPermission(user!._id, ctx);
useLayoutEffect(() => { useLayoutEffect(() => {
if (!user_id) return; if (!user_id) return;
if (typeof profile !== 'undefined') setProfile(undefined); if (typeof profile !== 'undefined') setProfile(undefined);
@ -93,17 +97,12 @@ export function UserProfile({ user_id, onClose, dummy, dummyProfile }: Props) {
) { ) {
setProfile(null); setProfile(null);
// ! FIXME: in the future, also check if mutual guilds if (permissions & UserPermission.ViewProfile) {
// ! maybe just allow mutual group to allow profile viewing
/*if (
user.relationship === Users.Relationship.Friend ||
user.relationship === Users.Relationship.User
) {*/
client.users client.users
.fetchProfile(user_id) .fetchProfile(user_id)
.then(data => setProfile(data)) .then(data => setProfile(data))
.catch(() => {}); .catch(() => {});
//} }
} }
}, [profile, status]); }, [profile, status]);
@ -157,35 +156,31 @@ export function UserProfile({ user_id, onClose, dummy, dummyProfile }: Props) {
<Text id="app.context_menu.message_user" /> <Text id="app.context_menu.message_user" />
} }
> >
{/*<IconButton <IconButton
onClick={() => { onClick={() => {
onClose(); onClose();
history.push(`/open/${user_id}`); history.push(`/open/${user_id}`);
}} }}>
>*/}
<Mail size={30} strokeWidth={1.5} /> <Mail size={30} strokeWidth={1.5} />
{/*</IconButton>*/} </IconButton>
</Tooltip> </Tooltip>
</Localizer> </Localizer>
)} )}
{user.relationship === Users.Relationship.User && ( {user.relationship === Users.Relationship.User && (
/*<IconButton <IconButton
onClick={() => { onClick={() => {
onClose(); onClose();
if (dummy) return; if (dummy) return;
history.push(`/settings/profile`); history.push(`/settings/profile`);
}} }}>
>*/
<Edit size={28} strokeWidth={1.5} /> <Edit size={28} strokeWidth={1.5} />
/*</IconButton>*/ </IconButton>
)} )}
{(user.relationship === Users.Relationship.Incoming || {(user.relationship === Users.Relationship.Incoming ||
user.relationship === Users.Relationship.None) && ( user.relationship === Users.Relationship.None) && (
/*<IconButton <IconButton onClick={() => client.users.addFriend(user.username)}>
onClick={() => client.users.addFriend(user.username)}
>*/
<UserPlus size={28} strokeWidth={1.5} /> <UserPlus size={28} strokeWidth={1.5} />
/*</IconButton>*/ </IconButton>
)} )}
</div> </div>
<div className={styles.tabs}> <div className={styles.tabs}>

View file

@ -18,13 +18,7 @@ export function registerEvents({
operations, operations,
dispatcher dispatcher
}: { operations: ClientOperations } & WithDispatcher, setStatus: StateUpdater<ClientStatus>, client: Client) { }: { operations: ClientOperations } & WithDispatcher, setStatus: StateUpdater<ClientStatus>, client: Client) {
const listeners = { function attemptReconnect() {
connecting: () =>
operations.ready() && setStatus(ClientStatus.CONNECTING),
dropped: () => {
operations.ready() && setStatus(ClientStatus.DISCONNECTED);
if (preventReconnect) return; if (preventReconnect) return;
function reconnect() { function reconnect() {
preventUntil = +new Date() + 2000; preventUntil = +new Date() + 2000;
@ -36,6 +30,17 @@ export function registerEvents({
} else { } else {
reconnect(); reconnect();
} }
}
const listeners = {
connecting: () =>
operations.ready() && setStatus(ClientStatus.CONNECTING),
dropped: () => {
if (operations.ready()) {
setStatus(ClientStatus.DISCONNECTED);
attemptReconnect();
}
}, },
packet: (packet: ClientboundNotification) => { packet: (packet: ClientboundNotification) => {
@ -79,9 +84,7 @@ export function registerEvents({
} }
}, },
ready: () => { ready: () => setStatus(ClientStatus.ONLINE)
setStatus(ClientStatus.ONLINE);
}
}; };
let listenerFunc: { [key: string]: Function }; let listenerFunc: { [key: string]: Function };
@ -101,20 +104,31 @@ export function registerEvents({
client.addListener(listener, (listenerFunc as any)[listener]); client.addListener(listener, (listenerFunc as any)[listener]);
} }
/*const online = () => const online = () => {
operations.ready() && setStatus(ClientStatus.RECONNECTING); if (operations.ready()) {
const offline = () => setStatus(ClientStatus.RECONNECTING);
operations.ready() && setStatus(ClientStatus.OFFLINE); setReconnectDisallowed(false);
attemptReconnect();
}
};
const offline = () => {
if (operations.ready()) {
setReconnectDisallowed(true);
client.websocket.disconnect();
setStatus(ClientStatus.OFFLINE);
}
};
window.addEventListener("online", online); window.addEventListener("online", online);
window.addEventListener("offline", offline); window.addEventListener("offline", offline);
return () => { return () => {
for (const listener of Object.keys(listenerFunc)) { 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("online", online);
window.removeEventListener("offline", offline); window.removeEventListener("offline", offline);
};*/ };
} }

View file

@ -1 +1 @@
export const APP_VERSION = "1.0.0-vite"; export const APP_VERSION = "__APP_VERSION__";

View file

@ -19,6 +19,10 @@ function getGitRevision() {
} }
} }
function getVersion() {
return readFileSync('VERSION').toString();
}
export default defineConfig({ export default defineConfig({
plugins: [ plugins: [
preact(), preact(),
@ -51,6 +55,7 @@ export default defineConfig({
}), }),
replace({ replace({
__GIT_REVISION__: getGitRevision(), __GIT_REVISION__: getGitRevision(),
__APP_VERSION__: getVersion(),
preventAssignment: true preventAssignment: true
}) })
], ],

View file

@ -2252,15 +2252,6 @@ from@~0:
resolved "https://registry.yarnpkg.com/from/-/from-0.1.7.tgz#83c60afc58b9c56997007ed1a768b3ab303a44fe" resolved "https://registry.yarnpkg.com/from/-/from-0.1.7.tgz#83c60afc58b9c56997007ed1a768b3ab303a44fe"
integrity sha1-g8YK/Fi5xWmXAH7Rp2izqzA6RP4= 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: fs-extra@^9.0.1:
version "9.1.0" version "9.1.0"
resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-9.1.0.tgz#5954460c764a8da2094ba3554bf839e6b9a7c86d" resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-9.1.0.tgz#5954460c764a8da2094ba3554bf839e6b9a7c86d"
@ -2648,22 +2639,6 @@ json5@^2.1.2:
dependencies: dependencies:
minimist "^1.2.5" 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: jsonfile@^6.0.1:
version "6.1.0" version "6.1.0"
resolved "https://registry.yarnpkg.com/jsonfile/-/jsonfile-6.1.0.tgz#bc55b2634793c679ec6403094eb13698a6ec0aae" 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: dependencies:
tslib "^1.8.1" 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: type-check@^0.4.0, type-check@~0.4.0:
version "0.4.0" version "0.4.0"
resolved "https://registry.yarnpkg.com/type-check/-/type-check-0.4.0.tgz#07b8203bfa7056c0657050e3ccd2c37730bab8f1" resolved "https://registry.yarnpkg.com/type-check/-/type-check-0.4.0.tgz#07b8203bfa7056c0657050e3ccd2c37730bab8f1"
@ -3870,11 +3830,6 @@ unique-string@^2.0.0:
dependencies: dependencies:
crypto-random-string "^2.0.0" 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: universalify@^2.0.0:
version "2.0.0" version "2.0.0"
resolved "https://registry.yarnpkg.com/universalify/-/universalify-2.0.0.tgz#75a4984efedc4b08975c5aeb73f530d02df25717" resolved "https://registry.yarnpkg.com/universalify/-/universalify-2.0.0.tgz#75a4984efedc4b08975c5aeb73f530d02df25717"