From 73d51a3e29ff14c2e0601cfd403dbef6c59663b3 Mon Sep 17 00:00:00 2001 From: Paul Makles Date: Thu, 19 May 2022 13:49:38 +0100 Subject: [PATCH 001/151] fix: edit bot av / bg --- src/pages/settings/panes/MyBots.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/pages/settings/panes/MyBots.tsx b/src/pages/settings/panes/MyBots.tsx index 861b5f33..a85a6d48 100644 --- a/src/pages/settings/panes/MyBots.tsx +++ b/src/pages/settings/panes/MyBots.tsx @@ -159,7 +159,7 @@ function BotCard({ bot, onDelete, onUpdate }: Props) { // Remove user headers for this request delete headers?.["x-user-id"]; delete headers?.["x-session-token"]; - return data; + return JSON.stringify(data); }, }, ); @@ -184,7 +184,7 @@ function BotCard({ bot, onDelete, onUpdate }: Props) { // Remove user headers for this request delete headers?.["x-user-id"]; delete headers?.["x-session-token"]; - return data; + return JSON.stringify(data); }, }, ); From 6fcdbd1cefa1227402651b12994f26a947c9e775 Mon Sep 17 00:00:00 2001 From: Paul Makles Date: Sat, 21 May 2022 16:59:34 +0100 Subject: [PATCH 002/151] fix: remove explicit cast to string for `msg.content` --- external/lang | 2 +- src/components/common/messaging/Message.tsx | 7 +++++-- src/lib/ContextMenus.tsx | 2 +- src/pages/channels/messaging/MessageEditor.tsx | 2 +- 4 files changed, 8 insertions(+), 5 deletions(-) diff --git a/external/lang b/external/lang index bac88cff..9f9f1bf8 160000 --- a/external/lang +++ b/external/lang @@ -1 +1 @@ -Subproject commit bac88cffd196a2afacf7d726e4f7ef19bd6bd94c +Subproject commit 9f9f1bf8ee2ce099fec081a3aa2405c4c1d22678 diff --git a/src/components/common/messaging/Message.tsx b/src/components/common/messaging/Message.tsx index 032d3cb6..82928e8e 100644 --- a/src/components/common/messaging/Message.tsx +++ b/src/components/common/messaging/Message.tsx @@ -57,7 +57,7 @@ const Message = observer( const { openScreen } = useIntermediate(); - const content = message.content as string; + const content = message.content; const head = preferHead || (message.reply_ids && message.reply_ids.length > 0); @@ -168,7 +168,10 @@ const Message = observer( 0 || content.length > 0} + hasContent={ + index > 0 || + (content ? content.length > 0 : false) + } /> ))} {message.embeds?.map((embed, index) => ( diff --git a/src/lib/ContextMenus.tsx b/src/lib/ContextMenus.tsx index 2e38dfa9..6c1f134c 100644 --- a/src/lib/ContextMenus.tsx +++ b/src/lib/ContextMenus.tsx @@ -209,7 +209,7 @@ export default function ContextMenus() { .get(data.message.channel)! .sendMessage({ nonce: data.message.id, - content: data.message.data.content as string, + content: data.message.data.content, replies: data.message.data.replies, }) .catch(fail); diff --git a/src/pages/channels/messaging/MessageEditor.tsx b/src/pages/channels/messaging/MessageEditor.tsx index b137561d..8e64361a 100644 --- a/src/pages/channels/messaging/MessageEditor.tsx +++ b/src/pages/channels/messaging/MessageEditor.tsx @@ -48,7 +48,7 @@ interface Props { } export default function MessageEditor({ message, finish }: Props) { - const [content, setContent] = useState((message.content as string) ?? ""); + const [content, setContent] = useState(message.content ?? ""); const { focusTaken } = useContext(IntermediateContext); const { openScreen } = useIntermediate(); From 32d37777f2cb6876f265970893531d9ae6e1dbe0 Mon Sep 17 00:00:00 2001 From: kaname-png Date: Sun, 8 May 2022 19:40:01 -0600 Subject: [PATCH 003/151] fix(components): wrong calculation of dimensions of embeds layouts --- src/components/common/messaging/embed/Embed.tsx | 2 +- src/pages/settings/panes/MyBots.tsx | 5 +++-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/src/components/common/messaging/embed/Embed.tsx b/src/components/common/messaging/embed/Embed.tsx index e980b944..10201989 100644 --- a/src/components/common/messaging/embed/Embed.tsx +++ b/src/components/common/messaging/embed/Embed.tsx @@ -59,7 +59,7 @@ export default function Embed({ embed }: Props) { if (embed.type === "Text") { mw = MAX_EMBED_WIDTH; - mh = 0; + mh = 1; } else { switch (embed.special?.type) { case "YouTube": diff --git a/src/pages/settings/panes/MyBots.tsx b/src/pages/settings/panes/MyBots.tsx index a85a6d48..de3ac0f2 100644 --- a/src/pages/settings/panes/MyBots.tsx +++ b/src/pages/settings/panes/MyBots.tsx @@ -582,8 +582,9 @@ export const MyBots = observer(() => { x.interactions_url = changes.interactions_url; if ( - changes.remove === - ["InteractionsURL"] + changes.remove?.includes( + "InteractionsURL", + ) ) x.interactions_url = undefined; } From e46a6d55d69560e575c70884fca29f350c99a1e4 Mon Sep 17 00:00:00 2001 From: kaname-png Date: Sun, 8 May 2022 19:59:20 -0600 Subject: [PATCH 004/151] chore: minor ui fixes for mybots page --- src/context/revoltjs/FileUploads.tsx | 3 +++ src/pages/settings/panes/MyBots.tsx | 1 + 2 files changed, 4 insertions(+) diff --git a/src/context/revoltjs/FileUploads.tsx b/src/context/revoltjs/FileUploads.tsx index 5fef3e12..ee8291c5 100644 --- a/src/context/revoltjs/FileUploads.tsx +++ b/src/context/revoltjs/FileUploads.tsx @@ -244,6 +244,9 @@ export function FileUploader(props: Props) { [styles.icon]: style === "icon", [styles.banner]: style === "banner", })} + style={{ + alignItems: props.style === "icon" ? "center" : "none", + }} data-uploading={uploading}>
) : ( Date: Mon, 23 May 2022 22:19:55 +0200 Subject: [PATCH 005/151] feat: Add option to show Send button on Desktop (#628) --- src/components/common/messaging/MessageBox.tsx | 6 +++++- src/components/settings/AppearanceShims.tsx | 18 ++++++++++++++++++ src/mobx/stores/Settings.ts | 1 + src/pages/settings/panes/Appearance.tsx | 6 ++++++ 4 files changed, 30 insertions(+), 1 deletion(-) diff --git a/src/components/common/messaging/MessageBox.tsx b/src/components/common/messaging/MessageBox.tsx index 6cb659b7..ce528b2a 100644 --- a/src/components/common/messaging/MessageBox.tsx +++ b/src/components/common/messaging/MessageBox.tsx @@ -637,7 +637,11 @@ export default observer(({ channel }: Props) => { */} e.preventDefault()}> diff --git a/src/components/settings/AppearanceShims.tsx b/src/components/settings/AppearanceShims.tsx index 4f619a04..3b3f4f09 100644 --- a/src/components/settings/AppearanceShims.tsx +++ b/src/components/settings/AppearanceShims.tsx @@ -211,6 +211,24 @@ export const DisplayLigaturesShim = observer(() => { ); }); +/** + * Component providing a way to toggle showing the send button on desktop. + */ +export const ShowSendButtonShim = observer(() => { + const settings = useApplicationState().settings; + + return ( + settings.set("appearance:show_send_button", v)} + description={ + + }> + + + ); +}); + /** * Component providing a way to toggle seasonal themes. */ diff --git a/src/mobx/stores/Settings.ts b/src/mobx/stores/Settings.ts index 8fed5083..56a36454 100644 --- a/src/mobx/stores/Settings.ts +++ b/src/mobx/stores/Settings.ts @@ -30,6 +30,7 @@ export interface ISettings { "appearance:ligatures": boolean; "appearance:seasonal": boolean; "appearance:transparency": boolean; + "appearance:show_send_button": boolean; "appearance:theme:base": "dark" | "light"; "appearance:theme:overrides": Partial; diff --git a/src/pages/settings/panes/Appearance.tsx b/src/pages/settings/panes/Appearance.tsx index a38c9cb8..5a7440c3 100644 --- a/src/pages/settings/panes/Appearance.tsx +++ b/src/pages/settings/panes/Appearance.tsx @@ -16,6 +16,7 @@ import { ThemeCustomCSSShim, DisplaySeasonalShim, DisplayTransparencyShim, + ShowSendButtonShim, } from "../../../components/settings/AppearanceShims"; import ThemeOverrides from "../../../components/settings/appearance/ThemeOverrides"; import ThemeTools from "../../../components/settings/appearance/ThemeTools"; @@ -28,6 +29,11 @@ export const Appearance = observer(() => {

+

+ +

+ +

From be12c6da206ade0a70c07df75512945d25013be9 Mon Sep 17 00:00:00 2001 From: Paul Makles Date: Mon, 23 May 2022 21:21:29 +0100 Subject: [PATCH 006/151] chore: migrate Button to @revoltchat/ui (#617) * chore: start moving Button over * fix: convert ServerIdentityModal * fix: modal button styling * fix: popover styles * fix: clean up references to ui/*.* * fix: button sizing Co-authored-by: Ed L --- .prettierrc.js | 1 + src/components/common/AgeGate.tsx | 7 +- .../common/messaging/embed/EmbedInvite.tsx | 3 +- src/components/navigation/right/Search.tsx | 5 +- .../settings/appearance/ThemeTools.tsx | 12 +- src/components/ui/Button.tsx | 148 ------------------ src/components/ui/Modal.tsx | 12 +- .../modals/ExternalLinkPrompt.tsx | 5 +- .../intermediate/modals/Onboarding.tsx | 3 +- src/context/intermediate/modals/Prompt.tsx | 20 ++- .../intermediate/modals/SessionsPrompt.tsx | 47 +++--- .../intermediate/popovers/CreateBot.tsx | 5 +- .../intermediate/popovers/ModifyAccount.tsx | 2 +- .../popovers/ServerIdentityModal.tsx | 5 +- .../intermediate/popovers/UserProfile.tsx | 8 +- src/pages/channels/voice/VoiceHeader.tsx | 7 +- src/pages/invite/Invite.tsx | 7 +- src/pages/invite/InviteBot.tsx | 9 +- src/pages/login/forms/Form.tsx | 23 ++- src/pages/login/forms/MailProvider.tsx | 2 +- src/pages/settings/channel/Overview.tsx | 5 +- src/pages/settings/panes/Account.tsx | 7 +- src/pages/settings/panes/Audio.tsx | 10 +- src/pages/settings/panes/MyBots.tsx | 7 +- src/pages/settings/panes/Native.tsx | 11 +- src/pages/settings/panes/Plugins.tsx | 5 +- src/pages/settings/panes/Profile.tsx | 5 +- src/pages/settings/panes/Sessions.tsx | 22 +-- src/pages/settings/server/Members.tsx | 6 +- src/pages/settings/server/Overview.tsx | 5 +- tsconfig.json | 2 +- ui/index.html | 13 -- ui/ui.tsx | 87 ---------- vite.config.ts | 1 - 34 files changed, 156 insertions(+), 361 deletions(-) delete mode 100644 src/components/ui/Button.tsx delete mode 100644 ui/index.html delete mode 100644 ui/ui.tsx diff --git a/.prettierrc.js b/.prettierrc.js index d9170ab2..d016bb18 100644 --- a/.prettierrc.js +++ b/.prettierrc.js @@ -4,6 +4,7 @@ module.exports = { jsxBracketSameLine: true, importOrder: [ "preact|classnames|.scss$", + "^@revoltchat", "/(lib)", "/(redux|mobx)", "/(context)", diff --git a/src/components/common/AgeGate.tsx b/src/components/common/AgeGate.tsx index 6641877b..825721b8 100644 --- a/src/components/common/AgeGate.tsx +++ b/src/components/common/AgeGate.tsx @@ -6,10 +6,11 @@ import styled from "styled-components/macro"; import { Text } from "preact-i18n"; import { useState } from "preact/hooks"; +import { Button } from "@revoltchat/ui"; + import { useApplicationState } from "../../mobx/State"; import { SECTION_NSFW } from "../../mobx/stores/Layout"; -import Button from "../ui/Button"; import Checkbox from "../ui/Checkbox"; import { Children } from "../../types/Preact"; @@ -85,11 +86,11 @@ export default observer((props: Props) => {
- @@ -61,8 +65,8 @@ export default function ThemeTools() { }> {currentNickname !== "" && (
{isPublicBot && ( - diff --git a/src/pages/channels/voice/VoiceHeader.tsx b/src/pages/channels/voice/VoiceHeader.tsx index 92634247..0d906868 100644 --- a/src/pages/channels/voice/VoiceHeader.tsx +++ b/src/pages/channels/voice/VoiceHeader.tsx @@ -6,14 +6,14 @@ import { VolumeFull, VolumeMute, } from "@styled-icons/boxicons-solid"; -import { Hashnode, Speakerdeck, Teamspeak } from "@styled-icons/simple-icons"; import { observer } from "mobx-react-lite"; import styled from "styled-components/macro"; import { Text } from "preact-i18n"; import { useMemo } from "preact/hooks"; -import VoiceClient from "../../../lib/vortex/VoiceClient"; +import { Button } from "@revoltchat/ui"; + import { voiceState, VoiceStatus } from "../../../lib/vortex/VoiceState"; import { useIntermediate } from "../../../context/intermediate/Intermediate"; @@ -21,7 +21,6 @@ import { useClient } from "../../../context/revoltjs/RevoltClient"; import Tooltip from "../../../components/common/Tooltip"; import UserIcon from "../../../components/common/user/UserIcon"; -import Button from "../../../components/ui/Button"; interface Props { id: string; @@ -145,7 +144,7 @@ export default observer(({ id }: Props) => {
- diff --git a/src/pages/invite/Invite.tsx b/src/pages/invite/Invite.tsx index 801426ab..363e1a39 100644 --- a/src/pages/invite/Invite.tsx +++ b/src/pages/invite/Invite.tsx @@ -7,6 +7,8 @@ import styles from "./Invite.module.scss"; import { Text } from "preact-i18n"; import { useContext, useEffect, useState } from "preact/hooks"; +import { Button } from "@revoltchat/ui"; + import { defer } from "../../lib/defer"; import { TextReact } from "../../lib/i18n"; @@ -22,7 +24,6 @@ import { takeError } from "../../context/revoltjs/util"; import ServerIcon from "../../components/common/ServerIcon"; import UserIcon from "../../components/common/user/UserIcon"; -import Button from "../../components/ui/Button"; import Overline from "../../components/ui/Overline"; import Preloader from "../../components/ui/Preloader"; @@ -71,7 +72,7 @@ export default function Invite() {
-

diff --git a/src/pages/settings/panes/Account.tsx b/src/pages/settings/panes/Account.tsx index 08d4722c..65b4be55 100644 --- a/src/pages/settings/panes/Account.tsx +++ b/src/pages/settings/panes/Account.tsx @@ -14,6 +14,8 @@ import styles from "./Panes.module.scss"; import { Text } from "preact-i18n"; import { useContext, useEffect, useState } from "preact/hooks"; +import { Button } from "@revoltchat/ui"; + import { stopPropagation } from "../../../lib/stopPropagation"; import { useIntermediate } from "../../../context/intermediate/Intermediate"; @@ -25,7 +27,6 @@ import { import Tooltip from "../../../components/common/Tooltip"; import UserIcon from "../../../components/common/user/UserIcon"; -import Button from "../../../components/ui/Button"; import Tip from "../../../components/ui/Tip"; import CategoryButton from "../../../components/ui/fluent/CategoryButton"; @@ -101,7 +102,9 @@ export const Account = observer(() => {
- diff --git a/src/pages/settings/panes/Audio.tsx b/src/pages/settings/panes/Audio.tsx index 1e39185b..e7194bee 100644 --- a/src/pages/settings/panes/Audio.tsx +++ b/src/pages/settings/panes/Audio.tsx @@ -2,11 +2,11 @@ import styles from "./Panes.module.scss"; import { Text } from "preact-i18n"; import { useEffect, useState } from "preact/hooks"; -import { TextReact } from "../../../lib/i18n"; +import { Button } from "@revoltchat/ui"; + import { stopPropagation } from "../../../lib/stopPropagation"; import { voiceState } from "../../../lib/vortex/VoiceState"; -import Button from "../../../components/ui/Button"; import ComboBox from "../../../components/ui/ComboBox"; import Overline from "../../../components/ui/Overline"; import Tip from "../../../components/ui/Tip"; @@ -162,8 +162,10 @@ export function Audio() { {!permission && ( )} diff --git a/src/pages/settings/panes/MyBots.tsx b/src/pages/settings/panes/MyBots.tsx index 5b3299d2..1a50d726 100644 --- a/src/pages/settings/panes/MyBots.tsx +++ b/src/pages/settings/panes/MyBots.tsx @@ -11,6 +11,8 @@ import styles from "./Panes.module.scss"; import { Text } from "preact-i18n"; import { useCallback, useEffect, useState } from "preact/hooks"; +import { Button } from "@revoltchat/ui"; + import TextAreaAutoSize from "../../../lib/TextAreaAutoSize"; import { internalEmit } from "../../../lib/eventEmitter"; import { useTranslation } from "../../../lib/i18n"; @@ -26,7 +28,6 @@ import AutoComplete, { import CollapsibleSection from "../../../components/common/CollapsibleSection"; import Tooltip from "../../../components/common/Tooltip"; import UserIcon from "../../../components/common/user/UserIcon"; -import Button from "../../../components/ui/Button"; import Checkbox from "../../../components/ui/Checkbox"; import InputBox from "../../../components/ui/InputBox"; import Tip from "../../../components/ui/Tip"; @@ -343,7 +344,7 @@ function BotCard({ bot, onDelete, onUpdate }: Props) { setEditMode(false); } else setEditMode(true); }} - contrast> + palette="secondary">

diff --git a/tsconfig.json b/tsconfig.json index 1df4e75d..c4a59366 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -19,5 +19,5 @@ "types": ["vite-plugin-pwa/client"], "experimentalDecorators": true }, - "include": ["src", "ui/ui.tsx", "external/lang/Languages.ts"] + "include": ["src", "external/lang/Languages.ts"] } diff --git a/ui/index.html b/ui/index.html deleted file mode 100644 index 51074353..00000000 --- a/ui/index.html +++ /dev/null @@ -1,13 +0,0 @@ - - - - - - - Revolt UI - - -
- - - diff --git a/ui/ui.tsx b/ui/ui.tsx deleted file mode 100644 index e6db65af..00000000 --- a/ui/ui.tsx +++ /dev/null @@ -1,87 +0,0 @@ -import styled from "styled-components/macro"; - -import "../src/styles/index.scss"; -import { render } from "preact"; -import { useState } from "preact/hooks"; - -import Theme from "../src/context/Theme"; - -import Banner from "../src/components/ui/Banner"; -import Button from "../src/components/ui/Button"; -import Checkbox from "../src/components/ui/Checkbox"; -import ColourSwatches from "../src/components/ui/ColourSwatches"; -import ComboBox from "../src/components/ui/ComboBox"; -import InputBox from "../src/components/ui/InputBox"; -import Overline from "../src/components/ui/Overline"; -import Radio from "../src/components/ui/Radio"; -import Tip from "../src/components/ui/Tip"; - -export const UIDemo = styled.div` - gap: 12px; - padding: 12px; - display: flex; - flex-direction: column; - align-items: flex-start; -`; - -export function UI() { - let [checked, setChecked] = useState(false); - let [colour, setColour] = useState("#FD6671"); - let [selected, setSelected] = useState<"a" | "b" | "c">("a"); - - return ( - <> - - - - - I am a banner! - - Do you want thing?? - - - - - - - - - - - - setColour(v)} /> - I am a tip! I provide valuable information. - setSelected("a")}> - First option - - setSelected("b")}> - Second option - - setSelected("c")}> - Last option - - Normal overline - Subtle overline - Error overline - Normal overline - - Subtle overline - - - ); -} - -render( - <> - - - - - , - document.getElementById("app")!, -); diff --git a/vite.config.ts b/vite.config.ts index 4558623b..4d695ab9 100644 --- a/vite.config.ts +++ b/vite.config.ts @@ -119,7 +119,6 @@ export default defineConfig({ rollupOptions: { input: { main: resolve(__dirname, "index.html"), - ui: resolve(__dirname, "ui/index.html"), }, }, }, From a35e68799a84650c47e16f04edca96a5084a104a Mon Sep 17 00:00:00 2001 From: Paul Makles Date: Mon, 23 May 2022 21:22:22 +0100 Subject: [PATCH 007/151] chore: update language submodule --- external/lang | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/external/lang b/external/lang index 9f9f1bf8..fdac76c2 160000 --- a/external/lang +++ b/external/lang @@ -1 +1 @@ -Subproject commit 9f9f1bf8ee2ce099fec081a3aa2405c4c1d22678 +Subproject commit fdac76c297fd333f603c9abab1f39fa0b63326e5 From 8efe3ecbc047242f91357a064ab4502aa4e1cebb Mon Sep 17 00:00:00 2001 From: Jan Date: Tue, 24 May 2022 13:53:06 +0200 Subject: [PATCH 008/151] feat: add warning on self-hosted instances (#645) * feat: add warning on self-hosted instances * update text --- src/pages/login/forms/Form.tsx | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/src/pages/login/forms/Form.tsx b/src/pages/login/forms/Form.tsx index 3327b43d..3f7e32e8 100644 --- a/src/pages/login/forms/Form.tsx +++ b/src/pages/login/forms/Form.tsx @@ -15,6 +15,7 @@ import { takeError } from "../../../context/revoltjs/util"; import Overline from "../../../components/ui/Overline"; import Preloader from "../../../components/ui/Preloader"; import WaveSVG from "../../settings/assets/wave.svg"; +import { Tip } from "@revoltchat/ui"; import FormField from "../FormField"; import { CaptchaBlock, CaptchaProps } from "./CaptchaBlock"; @@ -249,6 +250,24 @@ export const Form = observer(({ page, callback }: Props) => { + {import.meta.env.VITE_API_URL && + import.meta.env.VITE_API_URL != + "https://api.revolt.chat" && ( + <> +
+ + + {" "} + + + + + + + )} )} {(page === "reset" || From 0f3b1b0491c475a24bdace817f007eb191d0da47 Mon Sep 17 00:00:00 2001 From: Paul Makles Date: Tue, 24 May 2022 12:53:51 +0100 Subject: [PATCH 009/151] chore: bump language submodule --- external/lang | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/external/lang b/external/lang index fdac76c2..56dd2317 160000 --- a/external/lang +++ b/external/lang @@ -1 +1 @@ -Subproject commit fdac76c297fd333f603c9abab1f39fa0b63326e5 +Subproject commit 56dd2317d317a0a5dcd0c77312b67f32b53390e8 From 2e13685998da8369d3a7e751e7b4cc116674faac Mon Sep 17 00:00:00 2001 From: Paul Makles Date: Tue, 24 May 2022 13:17:30 +0100 Subject: [PATCH 010/151] chore: add build / publish script [skip ci] --- scripts/publish.sh | 33 ++++++++++++++++++++++++++++----- 1 file changed, 28 insertions(+), 5 deletions(-) diff --git a/scripts/publish.sh b/scripts/publish.sh index 427d025d..ddd7ee92 100755 --- a/scripts/publish.sh +++ b/scripts/publish.sh @@ -1,7 +1,30 @@ #!/bin/bash -version=$(cat VERSION) +# Build and publish release to production server + +# Remote Server +REMOTE=revolt-de-nrb-1 + +# Remote Directory +REMOTE_DIR=/root/revite + +# Post-install script +POST_INSTALL="pm2 restart revite" + +# Assets +export REVOLT_SASS=https://github.com/revoltchat/assets + + +# 1. Build Revite +yarn +yarn build + +# 2. Archive built files +tar -czvf build.tar.gz dist + +# 3. Upload built files +scp build.tar.gz $REMOTE:$REMOTE_DIR/build.tar.gz +rm build.tar.gz + +# 4. Apply changes +ssh $REMOTE "cd $REMOTE_DIR; tar -xvzf build.tar.gz; rm build.tar.gz; $POST_INSTALL" - docker build -t revoltchat/client:${version} . && -docker tag revoltchat/client:${version} revoltchat/client:latest && - docker push revoltchat/client:${version} && - docker push revoltchat/client:latest From 7e245df17901b32156992718ebac6cd98fbecd70 Mon Sep 17 00:00:00 2001 From: Paul Makles Date: Tue, 24 May 2022 13:18:47 +0100 Subject: [PATCH 011/151] fix: correct naming error [skip ci] --- scripts/publish.sh | 2 +- scripts/setup_assets.js | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/scripts/publish.sh b/scripts/publish.sh index ddd7ee92..b3b957f0 100755 --- a/scripts/publish.sh +++ b/scripts/publish.sh @@ -11,7 +11,7 @@ REMOTE_DIR=/root/revite POST_INSTALL="pm2 restart revite" # Assets -export REVOLT_SASS=https://github.com/revoltchat/assets +export REVOLT_SAAS=https://github.com/revoltchat/assets # 1. Build Revite diff --git a/scripts/setup_assets.js b/scripts/setup_assets.js index 200b89d4..503ae2f8 100644 --- a/scripts/setup_assets.js +++ b/scripts/setup_assets.js @@ -3,8 +3,8 @@ const { copy, remove, access } = require("fs-extra"); const { exec: cexec } = require("child_process"); const { resolve } = require("path"); -let target = process.env.REVOLT_SASS; -let branch = process.env.REVOLT_SASS_BRANCH; +let target = process.env.REVOLT_SAAS; +let branch = process.env.REVOLT_SAAS_BRANCH; let DEFAULT_DIRECTORY = "public/assets_default"; let OUT_DIRECTORY = "public/assets"; From 6eee71ddab504f0b2bbd6737e93081acedf235c4 Mon Sep 17 00:00:00 2001 From: Paul Makles Date: Tue, 24 May 2022 16:18:38 +0100 Subject: [PATCH 012/151] chore: add config.yml [skip ci] --- .github/ISSUE_TEMPLATE/config.yml | 7 +++++++ 1 file changed, 7 insertions(+) create mode 100644 .github/ISSUE_TEMPLATE/config.yml diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml new file mode 100644 index 00000000..ad0d5488 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/config.yml @@ -0,0 +1,7 @@ +contact_links: + - name: Lounge Chat + url: https://rvlt.gg/Testers + about: Ask questions and discuss with others. + - name: Discussions + url: https://github.com/orgs/revoltchat/discussions + about: For larger feature requests and general question & answer. From 9e948175b3ff81ddc2f3b08428380abc9ce6c7e1 Mon Sep 17 00:00:00 2001 From: Paul Makles Date: Wed, 25 May 2022 16:43:59 +0100 Subject: [PATCH 013/151] chore: update and add new languages --- .prettierrc.js | 3 +- external/lang | 2 +- package.json | 9 +- src/mobx/stores/LocaleOptions.ts | 1 + src/pages/settings/assets/flags/brittany.svg | 161 ++++++ .../assets/{ => flags}/enchanting_table.webp | Bin .../settings/assets/{ => flags}/esperanto.svg | 0 src/pages/settings/assets/flags/kurdistan.svg | 1 + src/pages/settings/assets/flags/sources.txt | 27 + .../tamil_nadu.png} | Bin src/pages/settings/assets/flags/toki_pona.svg | 276 ++++++++++ src/pages/settings/assets/flags/veneto.svg | 499 ++++++++++++++++++ src/pages/settings/assets/toki_pona.svg | 28 - src/pages/settings/panes/Languages.tsx | 56 +- yarn.lock | 23 +- 15 files changed, 1029 insertions(+), 57 deletions(-) create mode 100644 src/pages/settings/assets/flags/brittany.svg rename src/pages/settings/assets/{ => flags}/enchanting_table.webp (100%) rename src/pages/settings/assets/{ => flags}/esperanto.svg (100%) create mode 100644 src/pages/settings/assets/flags/kurdistan.svg create mode 100644 src/pages/settings/assets/flags/sources.txt rename src/pages/settings/assets/{tamil_nadu_flag.png => flags/tamil_nadu.png} (100%) create mode 100644 src/pages/settings/assets/flags/toki_pona.svg create mode 100644 src/pages/settings/assets/flags/veneto.svg delete mode 100644 src/pages/settings/assets/toki_pona.svg diff --git a/.prettierrc.js b/.prettierrc.js index d016bb18..e3019dc2 100644 --- a/.prettierrc.js +++ b/.prettierrc.js @@ -8,7 +8,8 @@ module.exports = { "/(lib)", "/(redux|mobx)", "/(context)", - "/(ui|common)|.svg|.webp|.png|.jpg$", + "/(ui|common)$", + ".svg|.webp|.png|.jpg$", "^[./]", ], importOrderSeparation: true, diff --git a/external/lang b/external/lang index 56dd2317..a322d933 160000 --- a/external/lang +++ b/external/lang @@ -1 +1 @@ -Subproject commit 56dd2317d317a0a5dcd0c77312b67f32b53390e8 +Subproject commit a322d93399dac70a8bbb696eed6656edb0e37cd0 diff --git a/package.json b/package.json index d858deb2..9217e8f2 100644 --- a/package.json +++ b/package.json @@ -62,6 +62,7 @@ "dependencies": { "@fontsource/bitter": "^4.5.0", "@insertish/vite-plugin-babel-macros": "^1.0.5", + "@revoltchat/ui": "^1.0.31", "fs-extra": "^10.0.0", "klaw": "^3.0.0", "react-beautiful-dnd": "^13.1.0", @@ -91,7 +92,6 @@ "@fontsource/ubuntu-mono": "^4.4.5", "@hcaptcha/react-hcaptcha": "^0.3.6", "@preact/preset-vite": "^2.0.0", - "@revoltchat/ui": "1.0.31", "@rollup/plugin-replace": "^2.4.2", "@styled-icons/boxicons-logos": "^10.38.0", "@styled-icons/boxicons-regular": "^10.38.0", @@ -131,7 +131,7 @@ "markdown-it": "^12.0.6", "markdown-it-emoji": "^2.0.0", "mediasoup-client": "npm:@insertish/mediasoup-client@3.6.36-esnext", - "mobx": "^6.3.2", + "mobx": "^6.6.0", "mobx-react-lite": "^3.3.0", "preact": "^10.5.14", "preact-context-menu": "0.4.0-patch.0", @@ -162,5 +162,8 @@ "repository": "https://github.com/revoltchat/revite.git", "author": "Paul ", "license": "MIT", - "packageManager": "yarn@3.2.0" + "packageManager": "yarn@3.2.0", + "resolutions": { + "@revoltchat/ui": "portal:../components" + } } diff --git a/src/mobx/stores/LocaleOptions.ts b/src/mobx/stores/LocaleOptions.ts index 24a7161b..72abd6b2 100644 --- a/src/mobx/stores/LocaleOptions.ts +++ b/src/mobx/stores/LocaleOptions.ts @@ -31,6 +31,7 @@ export function findLanguage(lang?: string): Language { const value = Language[key as keyof typeof Language]; // Skip alternative/joke languages + if (Languages[value].cat === "const") continue; if (Languages[value].cat === "alt") continue; values.push(value); diff --git a/src/pages/settings/assets/flags/brittany.svg b/src/pages/settings/assets/flags/brittany.svg new file mode 100644 index 00000000..7376601d --- /dev/null +++ b/src/pages/settings/assets/flags/brittany.svg @@ -0,0 +1,161 @@ + + + +Created with Fabric.js 3.6.3 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/pages/settings/assets/enchanting_table.webp b/src/pages/settings/assets/flags/enchanting_table.webp similarity index 100% rename from src/pages/settings/assets/enchanting_table.webp rename to src/pages/settings/assets/flags/enchanting_table.webp diff --git a/src/pages/settings/assets/esperanto.svg b/src/pages/settings/assets/flags/esperanto.svg similarity index 100% rename from src/pages/settings/assets/esperanto.svg rename to src/pages/settings/assets/flags/esperanto.svg diff --git a/src/pages/settings/assets/flags/kurdistan.svg b/src/pages/settings/assets/flags/kurdistan.svg new file mode 100644 index 00000000..11037b91 --- /dev/null +++ b/src/pages/settings/assets/flags/kurdistan.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/pages/settings/assets/flags/sources.txt b/src/pages/settings/assets/flags/sources.txt new file mode 100644 index 00000000..98f6c95e --- /dev/null +++ b/src/pages/settings/assets/flags/sources.txt @@ -0,0 +1,27 @@ +Flag of Brittany +CC BY-SA 4.0 +https://commons.wikimedia.org/wiki/File:Flag_of_Brittany.svg + +Enchanting Table +Minecraft game render +https://minecraft.fandom.com/wiki/Enchanting_Table?file=Enchanting_Table.gif + +Flag of Esperanto +Public Domain +https://commons.wikimedia.org/wiki/File:Flag_of_Esperanto.svg + +Flag of Kurdistan +Public Domain +https://commons.wikimedia.org/wiki/File:Flag_of_Kurdistan.svg + +Tamil Nadu Flag +CC BY-SA 3.0 +https://commons.wikimedia.org/wiki/File:..Tamil_Nadu_Flag(INDIA).png + +Toki Pona Flag +Free for any use +https://www.reddit.com/r/tokipona/comments/mevzbn/a_flag_for_toki_pona/gsk3euc/ + +Flag of Veneto +CC BY-SA 3.0 +https://commons.wikimedia.org/wiki/File:Flag_of_Veneto.svg diff --git a/src/pages/settings/assets/tamil_nadu_flag.png b/src/pages/settings/assets/flags/tamil_nadu.png similarity index 100% rename from src/pages/settings/assets/tamil_nadu_flag.png rename to src/pages/settings/assets/flags/tamil_nadu.png diff --git a/src/pages/settings/assets/flags/toki_pona.svg b/src/pages/settings/assets/flags/toki_pona.svg new file mode 100644 index 00000000..371c37b9 --- /dev/null +++ b/src/pages/settings/assets/flags/toki_pona.svg @@ -0,0 +1,276 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + diff --git a/src/pages/settings/assets/flags/veneto.svg b/src/pages/settings/assets/flags/veneto.svg new file mode 100644 index 00000000..1afb7ec1 --- /dev/null +++ b/src/pages/settings/assets/flags/veneto.svg @@ -0,0 +1,499 @@ + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + PAX + tibi + mar + ce e + van + geli + sta + mevs + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/pages/settings/assets/toki_pona.svg b/src/pages/settings/assets/toki_pona.svg deleted file mode 100644 index e29c05f6..00000000 --- a/src/pages/settings/assets/toki_pona.svg +++ /dev/null @@ -1,28 +0,0 @@ - - - - -Created by potrace 1.5, written by Peter Selinger 2001-2004 - - - - - - - - - - \ No newline at end of file diff --git a/src/pages/settings/panes/Languages.tsx b/src/pages/settings/panes/Languages.tsx index 7ea9d283..a555e56d 100644 --- a/src/pages/settings/panes/Languages.tsx +++ b/src/pages/settings/panes/Languages.tsx @@ -1,3 +1,4 @@ +import { Check } from "@styled-icons/boxicons-regular"; import { observer } from "mobx-react-lite"; import styles from "./Panes.module.scss"; @@ -6,19 +7,22 @@ import { useMemo } from "preact/hooks"; import { useApplicationState } from "../../../mobx/State"; -import Emoji from "../../../components/common/Emoji"; -import Checkbox from "../../../components/ui/Checkbox"; -import Tip from "../../../components/ui/Tip"; -import enchantingTableWEBP from "../assets/enchanting_table.webp"; -import esperantoFlagSVG from "../assets/esperanto.svg"; -import tamilFlagPNG from "../assets/tamil_nadu_flag.png"; -import tokiponaSVG from "../assets/toki_pona.svg"; +import britannyFlagSVG from "../assets/flags/brittany.svg"; +import enchantingTableWEBP from "../assets/flags/enchanting_table.webp"; +import esperantoFlagSVG from "../assets/flags/esperanto.svg"; +import kurdistanFlagSVG from "../assets/flags/kurdistan.svg"; +import tamilFlagPNG from "../assets/flags/tamil_nadu.png"; +import tokiponaSVG from "../assets/flags/toki_pona.svg"; +import venetoFlagSVG from "../assets/flags/veneto.svg"; import { Language, LanguageEntry, Languages as Langs, } from "../../../../external/lang/Languages"; +import Emoji from "../../../components/common/Emoji"; +import Checkbox from "../../../components/ui/Checkbox"; +import Tip from "../../../components/ui/Tip"; type Key = [Language, LanguageEntry]; @@ -40,22 +44,48 @@ function Entry({ entry: [x, lang], selected, onSelect }: Props) { checked={selected} onChange={onSelect}>
- {lang.i18n === "eo" ? ( + {lang.i18n === "vec" ? ( + + ) : lang.i18n === "br" ? ( + + ) : lang.i18n === "ckb" ? ( + + ) : lang.i18n === "eo" ? ( ) : lang.i18n === "ta" ? ( ) : lang.emoji === "🙂" ? ( - + ) : lang.emoji === "🪄" ? ( )}
- {lang.display} + + {lang.display} {lang.verified && } + ); } diff --git a/yarn.lock b/yarn.lock index 87e1b60b..0851b0f3 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2096,20 +2096,19 @@ __metadata: languageName: node linkType: hard -"@revoltchat/ui@npm:1.0.31": - version: 1.0.31 - resolution: "@revoltchat/ui@npm:1.0.31" +"@revoltchat/ui@portal:../components::locator=client%40workspace%3A.": + version: 0.0.0-use.local + resolution: "@revoltchat/ui@portal:../components::locator=client%40workspace%3A." dependencies: "@styled-icons/boxicons-logos": ^10.38.0 "@styled-icons/boxicons-regular": ^10.38.0 "@styled-icons/boxicons-solid": ^10.38.0 - mobx: ^6.5.0 + mobx: ^6.6.0 mobx-react-lite: ^3.3.0 peerDependencies: revolt-api: "*" - checksum: 8f93757d131ae7d784e744b774f6f6b9bea4e09a3fb48762334caf23e4ab08db209649eba1435f9fa82bdf7bfbfb78100fef6d13e7e0f715d0e2c82c52abf74d languageName: node - linkType: hard + linkType: soft "@rollup/plugin-babel@npm:^5.2.0": version: 5.3.0 @@ -3386,7 +3385,7 @@ __metadata: "@hcaptcha/react-hcaptcha": ^0.3.6 "@insertish/vite-plugin-babel-macros": ^1.0.5 "@preact/preset-vite": ^2.0.0 - "@revoltchat/ui": 1.0.31 + "@revoltchat/ui": ^1.0.31 "@rollup/plugin-replace": ^2.4.2 "@styled-icons/boxicons-logos": ^10.38.0 "@styled-icons/boxicons-regular": ^10.38.0 @@ -3428,7 +3427,7 @@ __metadata: markdown-it: ^12.0.6 markdown-it-emoji: ^2.0.0 mediasoup-client: "npm:@insertish/mediasoup-client@3.6.36-esnext" - mobx: ^6.3.2 + mobx: ^6.6.0 mobx-react-lite: ^3.3.0 preact: ^10.5.14 preact-context-menu: 0.4.0-patch.0 @@ -5867,10 +5866,10 @@ __metadata: languageName: node linkType: hard -"mobx@npm:^6.5.0": - version: 6.5.0 - resolution: "mobx@npm:6.5.0" - checksum: 1210fb0b1c515b5f0ec2916296c32ca19b733e03b34f180af382d44b90668a15b4143c69bb06ca8785ebc3da3e761c6c60d0e72c945c199efc823088af1941ab +"mobx@npm:^6.6.0": + version: 6.6.0 + resolution: "mobx@npm:6.6.0" + checksum: 369b8d6830ec286e9c856c80002c7a554d46bca739b7f76432cb56b3ce1fe0a7ed7e5a994b9793d30023beeff0dd16300a501e831fa97107104e4fedc7d4af8f languageName: node linkType: hard From 1e3fe450752360bc700a5b4f9eb84e04f364ec72 Mon Sep 17 00:00:00 2001 From: Paul Makles Date: Wed, 25 May 2022 16:47:30 +0100 Subject: [PATCH 014/151] fix: unlink @revoltchat/ui --- package.json | 5 +---- yarn.lock | 13 +++++++------ 2 files changed, 8 insertions(+), 10 deletions(-) diff --git a/package.json b/package.json index 9217e8f2..c8bca196 100644 --- a/package.json +++ b/package.json @@ -162,8 +162,5 @@ "repository": "https://github.com/revoltchat/revite.git", "author": "Paul ", "license": "MIT", - "packageManager": "yarn@3.2.0", - "resolutions": { - "@revoltchat/ui": "portal:../components" - } + "packageManager": "yarn@3.2.0" } diff --git a/yarn.lock b/yarn.lock index 0851b0f3..60caf2d9 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2096,19 +2096,20 @@ __metadata: languageName: node linkType: hard -"@revoltchat/ui@portal:../components::locator=client%40workspace%3A.": - version: 0.0.0-use.local - resolution: "@revoltchat/ui@portal:../components::locator=client%40workspace%3A." +"@revoltchat/ui@npm:^1.0.31": + version: 1.0.31 + resolution: "@revoltchat/ui@npm:1.0.31" dependencies: "@styled-icons/boxicons-logos": ^10.38.0 "@styled-icons/boxicons-regular": ^10.38.0 "@styled-icons/boxicons-solid": ^10.38.0 - mobx: ^6.6.0 + mobx: ^6.5.0 mobx-react-lite: ^3.3.0 peerDependencies: revolt-api: "*" + checksum: 8f93757d131ae7d784e744b774f6f6b9bea4e09a3fb48762334caf23e4ab08db209649eba1435f9fa82bdf7bfbfb78100fef6d13e7e0f715d0e2c82c52abf74d languageName: node - linkType: soft + linkType: hard "@rollup/plugin-babel@npm:^5.2.0": version: 5.3.0 @@ -5866,7 +5867,7 @@ __metadata: languageName: node linkType: hard -"mobx@npm:^6.6.0": +"mobx@npm:^6.5.0, mobx@npm:^6.6.0": version: 6.6.0 resolution: "mobx@npm:6.6.0" checksum: 369b8d6830ec286e9c856c80002c7a554d46bca739b7f76432cb56b3ce1fe0a7ed7e5a994b9793d30023beeff0dd16300a501e831fa97107104e4fedc7d4af8f From 94dd4b464ab1434e11eb62a8d4ee7b555f36968b Mon Sep 17 00:00:00 2001 From: Paul Makles Date: Thu, 26 May 2022 11:13:52 +0100 Subject: [PATCH 015/151] chore: server list integration test --- external/lang | 2 +- package.json | 9 +- .../navigation/left/ServerListSidebar.tsx | 443 +----------------- yarn.lock | 72 ++- 4 files changed, 50 insertions(+), 476 deletions(-) diff --git a/external/lang b/external/lang index a322d933..e08176d2 160000 --- a/external/lang +++ b/external/lang @@ -1 +1 @@ -Subproject commit a322d93399dac70a8bbb696eed6656edb0e37cd0 +Subproject commit e08176d2e855e04628863c1495c3ce15624c2679 diff --git a/package.json b/package.json index c8bca196..80961870 100644 --- a/package.json +++ b/package.json @@ -132,7 +132,7 @@ "markdown-it-emoji": "^2.0.0", "mediasoup-client": "npm:@insertish/mediasoup-client@3.6.36-esnext", "mobx": "^6.6.0", - "mobx-react-lite": "^3.3.0", + "mobx-react-lite": "3.4.0", "preact": "^10.5.14", "preact-context-menu": "0.4.0-patch.0", "preact-i18n": "^2.4.0-preactx", @@ -144,7 +144,7 @@ "react-overlapping-panels": "1.2.2", "react-router-dom": "^5.2.0", "react-scroll": "^1.8.2", - "react-virtuoso": "^1.10.4", + "react-virtuoso": "^2.12.0", "revolt.js": "6.0.0-2", "rimraf": "^3.0.2", "sass": "^1.35.1", @@ -162,5 +162,8 @@ "repository": "https://github.com/revoltchat/revite.git", "author": "Paul ", "license": "MIT", - "packageManager": "yarn@3.2.0" + "packageManager": "yarn@3.2.0", + "resolutions": { + "@revoltchat/ui": "portal:../components" + } } diff --git a/src/components/navigation/left/ServerListSidebar.tsx b/src/components/navigation/left/ServerListSidebar.tsx index 2196b16d..573ef059 100644 --- a/src/components/navigation/left/ServerListSidebar.tsx +++ b/src/components/navigation/left/ServerListSidebar.tsx @@ -1,448 +1,25 @@ -import { Plus } from "@styled-icons/boxicons-regular"; -import { Cog, Compass } from "@styled-icons/boxicons-solid"; import { observer } from "mobx-react-lite"; -import { Link, useHistory, useLocation, useParams } from "react-router-dom"; -import styled, { css } from "styled-components/macro"; +import { Link, useParams } from "react-router-dom"; -import { useTriggerEvents } from "preact-context-menu"; +import { ServerList } from "@revoltchat/ui"; -import ConditionalLink from "../../../lib/ConditionalLink"; -import PaintCounter from "../../../lib/PaintCounter"; -import { isTouchscreenDevice } from "../../../lib/isTouchscreenDevice"; - -import { useApplicationState } from "../../../mobx/State"; -import { SIDEBAR_CHANNELS } from "../../../mobx/stores/Layout"; - -import { useIntermediate } from "../../../context/intermediate/Intermediate"; import { useClient } from "../../../context/revoltjs/RevoltClient"; -import ChannelIcon from "../../common/ChannelIcon"; -import ServerIcon from "../../common/ServerIcon"; -import Tooltip from "../../common/Tooltip"; -import UserHover from "../../common/user/UserHover"; -import UserIcon from "../../common/user/UserIcon"; -import IconButton from "../../ui/IconButton"; -import LineDivider from "../../ui/LineDivider"; - -import { Children } from "../../../types/Preact"; - -function Icon({ - children, - unread, - count, - size, -}: { - children: Children; - unread?: "mention" | "unread"; - count: number | 0; - size: number; -}) { - return ( - - ); -} - -const ServersBase = styled.div` - width: 58px; - height: 100%; - padding-inline-start: 2px; - - display: flex; - flex-shrink: 0; - flex-direction: column; - - ${isTouchscreenDevice && - css` - padding-bottom: 50px; - `} -`; - -const ServerList = styled.div` - flex-grow: 1; - display: flex; - overflow-y: scroll; - padding-bottom: 20px; - flex-direction: column; - - scrollbar-width: none; - - > :first-child > svg { - margin: 6px 0 6px 4px; - } - - &::-webkit-scrollbar { - width: 0px; - } -`; - -const ServerEntry = styled.div<{ active: boolean; home?: boolean }>` - height: 54px; - display: flex; - align-items: center; - - //transition: 0.2s ease height; - - :focus { - outline: 3px solid blue; - } - - > div { - height: 42px; - padding-inline-start: 6px; - - display: grid; - place-items: center; - - border-start-start-radius: 50%; - border-end-start-radius: 50%; - - &:active { - transform: translateY(1px); - } - - ${(props) => - props.active && - css` - &:active { - transform: none; - } - `} - } - - > span { - width: 0; - display: relative; - - ${(props) => - !props.active && - css` - display: none; - `} - - svg { - margin-top: 5px; - pointer-events: none; - } - } - - ${(props) => - (!props.active || props.home) && - css` - cursor: pointer; - `} -`; - -const ServerCircle = styled.div` - width: 54px; - height: 54px; - display: flex; - align-items: center; - justify-content: center; - flex-shrink: 0; - - .circle { - display: flex; - align-items: center; - justify-content: center; - background-color: var(--primary-background); - border-radius: 50%; - height: 42px; - width: 42px; - transition: background-color 0.1s ease-in; - cursor: pointer; - - > div svg { - color: var(--accent); - } - - &:active { - transform: translateY(1px); - } - } -`; - -const SettingsButton = styled.div` - width: 50px; - height: 56px; - display: grid; - place-items: center; -`; - -function Swoosh() { - const sidebarOpen = useApplicationState().layout.getSectionState( - SIDEBAR_CHANNELS, - true, - ); - const fill = sidebarOpen - ? "var(--sidebar-active)" - : "var(--primary-background)"; - - return ( - - - - - - - - - ); -} - export default observer(() => { const client = useClient(); - const state = useApplicationState(); const { server: server_id } = useParams<{ server?: string }>(); - const server = server_id ? client.servers.get(server_id) : undefined; const servers = [...client.servers.values()]; - const channels = [...client.channels.values()]; - - const history = useHistory(); - const path = useLocation().pathname; - const { openScreen } = useIntermediate(); - - let alertCount = [...client.users.values()].filter( - (x) => x.relationship === "Incoming", - ).length; - - const homeActive = - typeof server === "undefined" && - !path.startsWith("/invite") && - !path.startsWith("/discover"); return ( - - - - - -
- homeActive && history.push("/settings") - }> - - 0 ? "mention" : undefined - } - count={alertCount}> - - - -
-
-
- {channels - .filter( - (x) => - ((x.channel_type === "DirectMessage" && x.active) || - x.channel_type === "Group") && - x.unread, - ) - .map((x) => { - const unreadCount = x.mentions.length; - return ( - - -
- 0 - ? "mention" - : "unread" - } - count={unreadCount}> - {x.channel_type === - "DirectMessage" ? ( - - ) : ( - - )} - -
-
- - ); - })} - - {servers.map((server) => { - const active = server._id === server_id; - - const isUnread = server.isUnread(state.notifications); - const mentionCount = server.getMentions( - state.notifications, - ).length; - - return ( - - - - - 0 - ? "mention" - : isUnread - ? "unread" - : undefined - } - count={mentionCount}> - - - - - - ); - })} - {/**/} - - -
- - openScreen({ - id: "special_input", - type: "create_server", - }) - }> - - -
-
-
- {!isTouchscreenDevice && ( - - -
Discover Revolt
-
- NEW -
- - } - placement="right"> -
- - - - - - - -
-
-
- )} -
- - {!isTouchscreenDevice && ( - - - -
- - - -
- -
-
+ ( + + {children} + )} - -
+ /> ); }); diff --git a/yarn.lock b/yarn.lock index 60caf2d9..b69f1384 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2096,20 +2096,21 @@ __metadata: languageName: node linkType: hard -"@revoltchat/ui@npm:^1.0.31": - version: 1.0.31 - resolution: "@revoltchat/ui@npm:1.0.31" +"@revoltchat/ui@portal:../components::locator=client%40workspace%3A.": + version: 0.0.0-use.local + resolution: "@revoltchat/ui@portal:../components::locator=client%40workspace%3A." dependencies: "@styled-icons/boxicons-logos": ^10.38.0 "@styled-icons/boxicons-regular": ^10.38.0 "@styled-icons/boxicons-solid": ^10.38.0 - mobx: ^6.5.0 - mobx-react-lite: ^3.3.0 + mobx: ^6.6.0 + mobx-react-lite: ^3.4.0 + react-beautiful-dnd: ^13.1.0 + react-virtuoso: ^2.12.0 peerDependencies: revolt-api: "*" - checksum: 8f93757d131ae7d784e744b774f6f6b9bea4e09a3fb48762334caf23e4ab08db209649eba1435f9fa82bdf7bfbfb78100fef6d13e7e0f715d0e2c82c52abf74d languageName: node - linkType: hard + linkType: soft "@rollup/plugin-babel@npm:^5.2.0": version: 5.3.0 @@ -2748,21 +2749,21 @@ __metadata: languageName: node linkType: hard -"@virtuoso.dev/react-urx@npm:^0.2.5": - version: 0.2.6 - resolution: "@virtuoso.dev/react-urx@npm:0.2.6" +"@virtuoso.dev/react-urx@npm:^0.2.12": + version: 0.2.13 + resolution: "@virtuoso.dev/react-urx@npm:0.2.13" dependencies: - "@virtuoso.dev/urx": ^0.2.6 + "@virtuoso.dev/urx": ^0.2.13 peerDependencies: react: ">=16" - checksum: 877760d0f4e56e4514a1f4f2e0160a99834b06b3c24bab32e569cadd06a3cb18e651bb60824a105d0abf4cc943630c2f68d0f461931b89f9e5f3ffff497f5c2b + checksum: 173e91c21f6a8cd506ad3b72af10656897fe1951124ed9eeb1fd85575534993bea2f97cba3f81c08ae1e88a2613df348e2c80d0ceecb3021f8c8c8fe0e053ee2 languageName: node linkType: hard -"@virtuoso.dev/urx@npm:^0.2.5, @virtuoso.dev/urx@npm:^0.2.6": - version: 0.2.6 - resolution: "@virtuoso.dev/urx@npm:0.2.6" - checksum: d1942a81a828e250030a1a3dbf66545b1539c29c62d519b1bcaa1a45badf4e1baaa9efecf13238ca6c45555673fe5e12f3aba7d1c4fa2d7ab3e0a9a1504cf153 +"@virtuoso.dev/urx@npm:^0.2.12, @virtuoso.dev/urx@npm:^0.2.13": + version: 0.2.13 + resolution: "@virtuoso.dev/urx@npm:0.2.13" + checksum: 682a99cf40ccc429241268dd37495cd1ed4695ae58b5a1169c75df1630d5dc3fd8eb3aaa655f71c37f39ba9c23c0aaf4401b76d8a986986d1a38a422d596a6ba languageName: node linkType: hard @@ -3429,7 +3430,7 @@ __metadata: markdown-it-emoji: ^2.0.0 mediasoup-client: "npm:@insertish/mediasoup-client@3.6.36-esnext" mobx: ^6.6.0 - mobx-react-lite: ^3.3.0 + mobx-react-lite: 3.4.0 preact: ^10.5.14 preact-context-menu: 0.4.0-patch.0 preact-i18n: ^2.4.0-preactx @@ -3442,7 +3443,7 @@ __metadata: react-overlapping-panels: 1.2.2 react-router-dom: ^5.2.0 react-scroll: ^1.8.2 - react-virtuoso: ^1.10.4 + react-virtuoso: ^2.12.0 revolt.js: 6.0.0-2 rimraf: ^3.0.2 sass: ^1.35.1 @@ -5845,18 +5846,18 @@ __metadata: languageName: node linkType: hard -"mobx-react-lite@npm:^3.3.0": - version: 3.3.0 - resolution: "mobx-react-lite@npm:3.3.0" +"mobx-react-lite@npm:3.4.0, mobx-react-lite@npm:^3.4.0": + version: 3.4.0 + resolution: "mobx-react-lite@npm:3.4.0" peerDependencies: mobx: ^6.1.0 - react: ^16.8.0 || ^17 + react: ^16.8.0 || ^17 || ^18 peerDependenciesMeta: react-dom: optional: true react-native: optional: true - checksum: 0f55bd2009a9cedc6b81d70b88b57dc4161362a16ba6ae0af341e673ca1c627bc3c4088c0cb13133e57e6fa6748b09b4c26aff7fab26c60ed95d27e939846fa3 + checksum: 9294e127e281c8b37ec7bcaf17de479f50519e6ad485b58d7b991291900511541a5a718653759d3cf6503462c70325d025e1c2ed376d4584fb1b2d3aac9d9b48 languageName: node linkType: hard @@ -5867,7 +5868,7 @@ __metadata: languageName: node linkType: hard -"mobx@npm:^6.5.0, mobx@npm:^6.6.0": +"mobx@npm:^6.6.0": version: 6.6.0 resolution: "mobx@npm:6.6.0" checksum: 369b8d6830ec286e9c856c80002c7a554d46bca739b7f76432cb56b3ce1fe0a7ed7e5a994b9793d30023beeff0dd16300a501e831fa97107104e4fedc7d4af8f @@ -6509,16 +6510,16 @@ __metadata: languageName: node linkType: hard -"react-virtuoso@npm:^1.10.4": - version: 1.11.0 - resolution: "react-virtuoso@npm:1.11.0" +"react-virtuoso@npm:^2.12.0": + version: 2.12.0 + resolution: "react-virtuoso@npm:2.12.0" dependencies: - "@virtuoso.dev/react-urx": ^0.2.5 - "@virtuoso.dev/urx": ^0.2.5 - resize-observer-polyfill: ^1.5.1 + "@virtuoso.dev/react-urx": ^0.2.12 + "@virtuoso.dev/urx": ^0.2.12 peerDependencies: - react: ">=16" - checksum: bd0ba533a0a8a318d1fe5bd082a32ffcf1447a22dc8c9affa0d77c4ac9d7e8874ea806387e135788632dc27e189b7c0c26e78994d21c5db6015d563247ed2655 + react: ">=16 || >=17 || >= 18" + react-dom: ">=16 || >=17 || >= 18" + checksum: b40309cb6d5175bcfa8a6c648af35f14e051b31e2cb811298cbc5c37dd5179f0a307c20fa9e7232b4179d1ff3fd31599c747249899d1da5d11afd8ee659d7368 languageName: node linkType: hard @@ -6639,13 +6640,6 @@ __metadata: languageName: node linkType: hard -"resize-observer-polyfill@npm:^1.5.1": - version: 1.5.1 - resolution: "resize-observer-polyfill@npm:1.5.1" - checksum: 57e7f79489867b00ba43c9c051524a5c8f162a61d5547e99333549afc23e15c44fd43f2f318ea0261ea98c0eb3158cca261e6f48d66e1ed1cd1f340a43977094 - languageName: node - linkType: hard - "resolve-from@npm:^4.0.0": version: 4.0.0 resolution: "resolve-from@npm:4.0.0" From 588cb7c019fdf360d24c64ca5c0c35faa0ffa7bf Mon Sep 17 00:00:00 2001 From: Paul Makles Date: Fri, 27 May 2022 19:57:41 +0100 Subject: [PATCH 016/151] feat: finish reimplementation of server list --- external/lang | 2 +- package.json | 21 +- .../navigation/left/ServerListSidebar.tsx | 35 +- src/context/index.tsx | 29 +- src/mobx/stores/Layout.ts | 3 + src/pages/app.tsx | 4 +- yarn.lock | 457 ++++++++++++------ 7 files changed, 366 insertions(+), 185 deletions(-) diff --git a/external/lang b/external/lang index e08176d2..e010e46e 160000 --- a/external/lang +++ b/external/lang @@ -1 +1 @@ -Subproject commit e08176d2e855e04628863c1495c3ce15624c2679 +Subproject commit e010e46ee9f226373a253c351b50ccdeea1c8b50 diff --git a/package.json b/package.json index 80961870..155aa2f9 100644 --- a/package.json +++ b/package.json @@ -60,18 +60,15 @@ } }, "dependencies": { - "@fontsource/bitter": "^4.5.0", - "@insertish/vite-plugin-babel-macros": "^1.0.5", - "@revoltchat/ui": "^1.0.31", "fs-extra": "^10.0.0", "klaw": "^3.0.0", - "react-beautiful-dnd": "^13.1.0", "sirv-cli": "^1.0.14", "vite": "^2.6.14" }, "devDependencies": { "@babel/plugin-proposal-decorators": "^7.17.9", "@fontsource/atkinson-hyperlegible": "^4.4.5", + "@fontsource/bitter": "^4.5.7", "@fontsource/comic-neue": "^4.4.5", "@fontsource/fira-code": "^4.4.5", "@fontsource/inter": "^4.4.5", @@ -91,13 +88,15 @@ "@fontsource/ubuntu": "^4.4.5", "@fontsource/ubuntu-mono": "^4.4.5", "@hcaptcha/react-hcaptcha": "^0.3.6", + "@insertish/vite-plugin-babel-macros": "^1.0.5", "@preact/preset-vite": "^2.0.0", + "@revoltchat/ui": "^1.0.32", "@rollup/plugin-replace": "^2.4.2", "@styled-icons/boxicons-logos": "^10.38.0", "@styled-icons/boxicons-regular": "^10.38.0", "@styled-icons/boxicons-solid": "^10.38.0", "@styled-icons/simple-icons": "^10.33.0", - "@tippyjs/react": "^4.2.5", + "@tippyjs/react": "npm:4.2.5", "@traptitech/markdown-it-katex": "^3.4.3", "@traptitech/markdown-it-spoiler": "^1.1.6", "@trivago/prettier-plugin-sort-imports": "^2.0.2", @@ -107,7 +106,7 @@ "@types/node": "^15.12.4", "@types/preact-i18n": "^2.3.0", "@types/prismjs": "^1.16.5", - "@types/react-beautiful-dnd": "^13.1.2", + "@types/react-beautiful-dnd": "^13", "@types/react-helmet": "^6.1.1", "@types/react-router-dom": "^5.1.7", "@types/react-scroll": "^1.8.2", @@ -138,14 +137,15 @@ "preact-i18n": "^2.4.0-preactx", "prettier": "^2.3.1", "prismjs": "^1.23.0", - "react-device-detect": "^1.17.0", + "react-beautiful-dnd": "^13.1.0", + "react-device-detect": "npm:2.2.2", "react-helmet": "^6.1.0", "react-hook-form": "6.3.0", "react-overlapping-panels": "1.2.2", "react-router-dom": "^5.2.0", "react-scroll": "^1.8.2", "react-virtuoso": "^2.12.0", - "revolt.js": "6.0.0-2", + "revolt.js": "npm:6.0.1", "rimraf": "^3.0.2", "sass": "^1.35.1", "shade-blend-color": "^1.0.0", @@ -162,8 +162,5 @@ "repository": "https://github.com/revoltchat/revite.git", "author": "Paul ", "license": "MIT", - "packageManager": "yarn@3.2.0", - "resolutions": { - "@revoltchat/ui": "portal:../components" - } + "packageManager": "yarn@3.2.0" } diff --git a/src/components/navigation/left/ServerListSidebar.tsx b/src/components/navigation/left/ServerListSidebar.tsx index 573ef059..57b4111d 100644 --- a/src/components/navigation/left/ServerListSidebar.tsx +++ b/src/components/navigation/left/ServerListSidebar.tsx @@ -1,25 +1,40 @@ import { observer } from "mobx-react-lite"; -import { Link, useParams } from "react-router-dom"; +import { useParams } from "react-router-dom"; + +import { useCallback } from "preact/hooks"; import { ServerList } from "@revoltchat/ui"; +import { useApplicationState } from "../../../mobx/State"; + +import { useIntermediate } from "../../../context/intermediate/Intermediate"; import { useClient } from "../../../context/revoltjs/RevoltClient"; +/** + * Server list sidebar shim component + */ export default observer(() => { const client = useClient(); - + const state = useApplicationState(); + const { openScreen } = useIntermediate(); const { server: server_id } = useParams<{ server?: string }>(); - const servers = [...client.servers.values()]; + + const createServer = useCallback( + () => + openScreen({ + id: "special_input", + type: "create_server", + }), + [], + ); return ( ( - - {children} - - )} + client={client} + active={server_id} + createServer={createServer} + permit={state.notifications} + home={state.layout.getLastHomePath} /> ); }); diff --git a/src/context/index.tsx b/src/context/index.tsx index 8201f603..0d48a5bb 100644 --- a/src/context/index.tsx +++ b/src/context/index.tsx @@ -1,11 +1,14 @@ -import { BrowserRouter as Router } from "react-router-dom"; +import { BrowserRouter as Router, Link } from "react-router-dom"; +import { ContextMenuTrigger } from "preact-context-menu"; +import { Text } from "preact-i18n"; import { useEffect, useState } from "preact/hooks"; +import { LinkProvider, TextProvider, TrigProvider } from "@revoltchat/ui"; + import { hydrateState } from "../mobx/State"; import Preloader from "../components/ui/Preloader"; - import { Children } from "../types/Preact"; import Locale from "./Locale"; import Theme from "./Theme"; @@ -28,14 +31,20 @@ export default function Context({ children }: { children: Children }) { return ( - - - - {children} - - - - + + + + + + + {children} + + + + + + + ); diff --git a/src/mobx/stores/Layout.ts b/src/mobx/stores/Layout.ts index baaa157f..a6f1fd5e 100644 --- a/src/mobx/stores/Layout.ts +++ b/src/mobx/stores/Layout.ts @@ -58,6 +58,9 @@ export default class Layout implements Store, Persistent { this.lastDiscoverPath = "/discover/servers"; this.lastOpened = new ObservableMap(); this.openSections = new ObservableMap(); + + this.getLastHomePath = this.getLastHomePath.bind(this); + makeAutoObservable(this); } diff --git a/src/pages/app.tsx b/src/pages/app.tsx index 4fb2093a..ca3e1d1d 100644 --- a/src/pages/app.tsx +++ b/src/pages/app.tsx @@ -2,15 +2,15 @@ import { Route, Switch } from "react-router-dom"; import { lazy, Suspense } from "preact/compat"; +import { Masks } from "@revoltchat/ui"; + import ErrorBoundary from "../lib/ErrorBoundary"; import FakeClient from "../lib/FakeClient"; import Context from "../context"; import { CheckAuth } from "../context/revoltjs/CheckAuth"; -import Masks from "../components/ui/Masks"; import Preloader from "../components/ui/Preloader"; - import Invite from "./invite/Invite"; const Login = lazy(() => import("./login/Login")); diff --git a/yarn.lock b/yarn.lock index b69f1384..92c6acdc 100644 --- a/yarn.lock +++ b/yarn.lock @@ -5,6 +5,16 @@ __metadata: version: 6 cacheKey: 8 +"@ampproject/remapping@npm:^2.1.0": + version: 2.2.0 + resolution: "@ampproject/remapping@npm:2.2.0" + dependencies: + "@jridgewell/gen-mapping": ^0.1.0 + "@jridgewell/trace-mapping": ^0.3.9 + checksum: d74d170d06468913921d72430259424b7e4c826b5a7d39ff839a29d547efb97dc577caa8ba3fb5cf023624e9af9d09651afc3d4112a45e2050328abc9b3a2292 + languageName: node + linkType: hard + "@apideck/better-ajv-errors@npm:^0.3.1": version: 0.3.2 resolution: "@apideck/better-ajv-errors@npm:0.3.2" @@ -52,10 +62,10 @@ __metadata: languageName: node linkType: hard -"@babel/compat-data@npm:^7.16.4": - version: 7.16.8 - resolution: "@babel/compat-data@npm:7.16.8" - checksum: 10da2dac5ea9589c251412b00920889910e476c1ab24cd7095577635bc3a27c785151c89db4e26285fd39f509510ec29ab9d7e721f4fc16e4aec221cacde784b +"@babel/compat-data@npm:^7.17.10": + version: 7.17.10 + resolution: "@babel/compat-data@npm:7.17.10" + checksum: e85051087cd4690de5061909a2dd2d7f8b6434a3c2e30be6c119758db2027ae1845bcd75a81127423dd568b706ac6994a1a3d7d701069a23bf5cfe900728290b languageName: node linkType: hard @@ -107,25 +117,25 @@ __metadata: linkType: hard "@babel/core@npm:^7.13.10": - version: 7.16.7 - resolution: "@babel/core@npm:7.16.7" + version: 7.18.2 + resolution: "@babel/core@npm:7.18.2" dependencies: + "@ampproject/remapping": ^2.1.0 "@babel/code-frame": ^7.16.7 - "@babel/generator": ^7.16.7 - "@babel/helper-compilation-targets": ^7.16.7 - "@babel/helper-module-transforms": ^7.16.7 - "@babel/helpers": ^7.16.7 - "@babel/parser": ^7.16.7 + "@babel/generator": ^7.18.2 + "@babel/helper-compilation-targets": ^7.18.2 + "@babel/helper-module-transforms": ^7.18.0 + "@babel/helpers": ^7.18.2 + "@babel/parser": ^7.18.0 "@babel/template": ^7.16.7 - "@babel/traverse": ^7.16.7 - "@babel/types": ^7.16.7 + "@babel/traverse": ^7.18.2 + "@babel/types": ^7.18.2 convert-source-map: ^1.7.0 debug: ^4.1.0 gensync: ^1.0.0-beta.2 - json5: ^2.1.2 + json5: ^2.2.1 semver: ^6.3.0 - source-map: ^0.5.0 - checksum: 3206e077e76db189726c4da19a5296eae11c6c1f5abea7013e74f18708bb91616914717ff8d8ca466cc0ba9d2d2147e9a84c3c357b9ad4cba601da14107838ed + checksum: 14a4142c12e004cd2477b7610408d5788ee5dd821ee9e4de204cbb72d9c399d858d9deabc3d49914d5d7c2927548160c19bdc7524b1a9f6acc1ec96a8d9848dd languageName: node linkType: hard @@ -151,7 +161,7 @@ __metadata: languageName: node linkType: hard -"@babel/generator@npm:^7.16.7, @babel/generator@npm:^7.16.8": +"@babel/generator@npm:^7.16.8": version: 7.16.8 resolution: "@babel/generator@npm:7.16.8" dependencies: @@ -162,6 +172,17 @@ __metadata: languageName: node linkType: hard +"@babel/generator@npm:^7.18.2": + version: 7.18.2 + resolution: "@babel/generator@npm:7.18.2" + dependencies: + "@babel/types": ^7.18.2 + "@jridgewell/gen-mapping": ^0.3.0 + jsesc: ^2.5.1 + checksum: d0661e95532ddd97566d41fec26355a7b28d1cbc4df95fe80cc084c413342935911b48db20910708db39714844ddd614f61c2ec4cca3fb10181418bdcaa2e7a3 + languageName: node + linkType: hard + "@babel/helper-annotate-as-pure@npm:^7.0.0, @babel/helper-annotate-as-pure@npm:^7.14.5": version: 7.14.5 resolution: "@babel/helper-annotate-as-pure@npm:7.14.5" @@ -204,17 +225,17 @@ __metadata: languageName: node linkType: hard -"@babel/helper-compilation-targets@npm:^7.16.7": - version: 7.16.7 - resolution: "@babel/helper-compilation-targets@npm:7.16.7" +"@babel/helper-compilation-targets@npm:^7.18.2": + version: 7.18.2 + resolution: "@babel/helper-compilation-targets@npm:7.18.2" dependencies: - "@babel/compat-data": ^7.16.4 + "@babel/compat-data": ^7.17.10 "@babel/helper-validator-option": ^7.16.7 - browserslist: ^4.17.5 + browserslist: ^4.20.2 semver: ^6.3.0 peerDependencies: "@babel/core": ^7.0.0 - checksum: 7238aaee78c011a42fb5ca92e5eff098752f7b314c2111d7bb9cdd58792fcab1b9c819b59f6a0851dc210dc09dc06b30d130a23982753e70eb3111bc65204842 + checksum: 4f02e79f20c0b3f8db5049ba8c35027c41ccb3fc7884835d04e49886538e0f55702959db1bb75213c94a5708fec2dc81a443047559a4f184abb884c72c0059b4 languageName: node linkType: hard @@ -290,6 +311,13 @@ __metadata: languageName: node linkType: hard +"@babel/helper-environment-visitor@npm:^7.18.2": + version: 7.18.2 + resolution: "@babel/helper-environment-visitor@npm:7.18.2" + checksum: 1a9c8726fad454a082d077952a90f17188e92eabb3de236cb4782c49b39e3f69c327e272b965e9a20ff8abf37d30d03ffa6fd7974625a6c23946f70f7527f5e9 + languageName: node + linkType: hard + "@babel/helper-explode-assignable-expression@npm:^7.14.5": version: 7.14.5 resolution: "@babel/helper-explode-assignable-expression@npm:7.14.5" @@ -428,19 +456,19 @@ __metadata: languageName: node linkType: hard -"@babel/helper-module-transforms@npm:^7.16.7": - version: 7.16.7 - resolution: "@babel/helper-module-transforms@npm:7.16.7" +"@babel/helper-module-transforms@npm:^7.18.0": + version: 7.18.0 + resolution: "@babel/helper-module-transforms@npm:7.18.0" dependencies: "@babel/helper-environment-visitor": ^7.16.7 "@babel/helper-module-imports": ^7.16.7 - "@babel/helper-simple-access": ^7.16.7 + "@babel/helper-simple-access": ^7.17.7 "@babel/helper-split-export-declaration": ^7.16.7 "@babel/helper-validator-identifier": ^7.16.7 "@babel/template": ^7.16.7 - "@babel/traverse": ^7.16.7 - "@babel/types": ^7.16.7 - checksum: 6e930ce776c979f299cdbeaf80187f4ab086d75287b96ecc1c6896d392fcb561065f0d6219fc06fa79b4ceb4bbdc1a9847da8099aba9b077d0a9e583500fb673 + "@babel/traverse": ^7.18.0 + "@babel/types": ^7.18.0 + checksum: 824c3967c08d75bb36adc18c31dcafebcd495b75b723e2e17c6185e88daf5c6db62a6a75d9f791b5f38618a349e7cb32503e715a1b9a4e8bad4d0f43e3e6b523 languageName: node linkType: hard @@ -476,6 +504,13 @@ __metadata: languageName: node linkType: hard +"@babel/helper-plugin-utils@npm:^7.17.12": + version: 7.17.12 + resolution: "@babel/helper-plugin-utils@npm:7.17.12" + checksum: 4813cf0ddb0f143de032cb88d4207024a2334951db330f8216d6fa253ea320c02c9b2667429ef1a34b5e95d4cfbd085f6cb72d418999751c31d0baf2422cc61d + languageName: node + linkType: hard + "@babel/helper-remap-async-to-generator@npm:^7.14.5": version: 7.14.5 resolution: "@babel/helper-remap-async-to-generator@npm:7.14.5" @@ -521,12 +556,12 @@ __metadata: languageName: node linkType: hard -"@babel/helper-simple-access@npm:^7.16.7": - version: 7.16.7 - resolution: "@babel/helper-simple-access@npm:7.16.7" +"@babel/helper-simple-access@npm:^7.17.7": + version: 7.18.2 + resolution: "@babel/helper-simple-access@npm:7.18.2" dependencies: - "@babel/types": ^7.16.7 - checksum: 8d22c46c5ec2ead0686c4d5a3d1d12b5190c59be676bfe0d9d89df62b437b51d1a3df2ccfb8a77dded2e585176ebf12986accb6d45a18cff229eef3b10344f4b + "@babel/types": ^7.18.2 + checksum: c0862b56db7e120754d89273a039b128c27517389f6a4425ff24e49779791e8fe10061579171fb986be81fa076778acb847c709f6f5e396278d9c5e01360c375 languageName: node linkType: hard @@ -608,14 +643,14 @@ __metadata: languageName: node linkType: hard -"@babel/helpers@npm:^7.16.7": - version: 7.16.7 - resolution: "@babel/helpers@npm:7.16.7" +"@babel/helpers@npm:^7.18.2": + version: 7.18.2 + resolution: "@babel/helpers@npm:7.18.2" dependencies: "@babel/template": ^7.16.7 - "@babel/traverse": ^7.16.7 - "@babel/types": ^7.16.7 - checksum: 75504c76b66a29b91f954fcc0867dfe275a4cfba5b44df6d64405df74ea72f967fccfa63d62c31c423c5502d113290000c581e0e4858a214f0303d7ecf55c29f + "@babel/traverse": ^7.18.2 + "@babel/types": ^7.18.2 + checksum: 94620242f23f6d5f9b83a02b1aa1632ffb05b0815e1bb53d3b46d64aa8e771066bba1db8bd267d9091fb00134cfaeda6a8d69d1d4cc2c89658631adfa077ae70 languageName: node linkType: hard @@ -650,12 +685,12 @@ __metadata: languageName: node linkType: hard -"@babel/parser@npm:^7.1.0, @babel/parser@npm:^7.16.7, @babel/parser@npm:^7.16.8": - version: 7.16.8 - resolution: "@babel/parser@npm:7.16.8" +"@babel/parser@npm:^7.1.0, @babel/parser@npm:^7.18.0": + version: 7.18.3 + resolution: "@babel/parser@npm:7.18.3" bin: parser: ./bin/babel-parser.js - checksum: f6bc2eb1f298fcb81db34c2d343fd05d8c59dbc5419a88c1cb4d298c7a3863e4d54f5a4f38a40e1aa979e4ce355816348730b471c1d787d424ed52b270fc7be0 + checksum: 6894b3266f84b6c6b52bf09e7f61526efc35d8afa72ff0ad9aecb27a4b6de02d1ebc7f61fc3ae7c0fd8ecb5ac17083d1f27c1b3176e5eac41131d7160a9a7d88 languageName: node linkType: hard @@ -668,6 +703,15 @@ __metadata: languageName: node linkType: hard +"@babel/parser@npm:^7.16.7, @babel/parser@npm:^7.16.8": + version: 7.16.8 + resolution: "@babel/parser@npm:7.16.8" + bin: + parser: ./bin/babel-parser.js + checksum: f6bc2eb1f298fcb81db34c2d343fd05d8c59dbc5419a88c1cb4d298c7a3863e4d54f5a4f38a40e1aa979e4ce355816348730b471c1d787d424ed52b270fc7be0 + languageName: node + linkType: hard + "@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining@npm:^7.14.5": version: 7.14.5 resolution: "@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining@npm:7.14.5" @@ -963,13 +1007,13 @@ __metadata: linkType: hard "@babel/plugin-syntax-jsx@npm:^7.12.13": - version: 7.16.7 - resolution: "@babel/plugin-syntax-jsx@npm:7.16.7" + version: 7.17.12 + resolution: "@babel/plugin-syntax-jsx@npm:7.17.12" dependencies: - "@babel/helper-plugin-utils": ^7.16.7 + "@babel/helper-plugin-utils": ^7.17.12 peerDependencies: "@babel/core": ^7.0.0-0 - checksum: cd9b0e53c50e8ddb0afaf0f42e0b221a94e4f59aee32a591364266a31195c48cac5fef288d02c1c935686bda982d2e0f1ed61cceb995fc9f6fb09ef5ebecdd2b + checksum: 6acd0bbca8c3e0100ad61f3b7d0b0111cd241a0710b120b298c4aa0e07be02eccbcca61ede1e7678ade1783a0979f20305b62263df6767fa3fbf658670d82af5 languageName: node linkType: hard @@ -1062,13 +1106,13 @@ __metadata: linkType: hard "@babel/plugin-syntax-typescript@npm:^7.12.13": - version: 7.16.7 - resolution: "@babel/plugin-syntax-typescript@npm:7.16.7" + version: 7.17.12 + resolution: "@babel/plugin-syntax-typescript@npm:7.17.12" dependencies: - "@babel/helper-plugin-utils": ^7.16.7 + "@babel/helper-plugin-utils": ^7.17.12 peerDependencies: "@babel/core": ^7.0.0-0 - checksum: 661e636060609ede9a402e22603b01784c21fabb0a637e65f561c8159351fe0130bbc11fdefe31902107885e3332fc34d95eb652ac61d3f61f2d61f5da20609e + checksum: 50ab09f1953a2b0586cff9e29bf7cea3d886b48c1361a861687c2aef46356c6d73778c3341b0c051dc82a34417f19e9d759ae918353c5a98d25e85f2f6d24181 languageName: node linkType: hard @@ -1546,7 +1590,7 @@ __metadata: languageName: node linkType: hard -"@babel/runtime@npm:^7.1.2, @babel/runtime@npm:^7.10.5, @babel/runtime@npm:^7.11.2, @babel/runtime@npm:^7.12.1, @babel/runtime@npm:^7.12.13, @babel/runtime@npm:^7.14.8, @babel/runtime@npm:^7.8.4, @babel/runtime@npm:^7.9.2": +"@babel/runtime@npm:^7.1.2, @babel/runtime@npm:^7.10.5, @babel/runtime@npm:^7.11.2, @babel/runtime@npm:^7.12.1, @babel/runtime@npm:^7.12.13, @babel/runtime@npm:^7.14.8, @babel/runtime@npm:^7.8.4": version: 7.15.3 resolution: "@babel/runtime@npm:7.15.3" dependencies: @@ -1555,12 +1599,12 @@ __metadata: languageName: node linkType: hard -"@babel/runtime@npm:^7.12.5": - version: 7.16.7 - resolution: "@babel/runtime@npm:7.16.7" +"@babel/runtime@npm:^7.12.5, @babel/runtime@npm:^7.15.4, @babel/runtime@npm:^7.9.2": + version: 7.18.3 + resolution: "@babel/runtime@npm:7.18.3" dependencies: regenerator-runtime: ^0.13.4 - checksum: 47912f0aaacd1cab2e2552aaf3e6eaffbcaf2d5ac9b07a89a12ac0d42029cb92c070b0d16f825e4277c4a34677c54d8ffe85e1f7c6feb57de58f700eec67ce2f + checksum: db8526226aa02cfa35a5a7ac1a34b5f303c62a1f000c7db48cb06c6290e616483e5036ab3c4e7a84d0f3be6d4e2148d5fe5cec9564bf955f505c3e764b83d7f1 languageName: node linkType: hard @@ -1654,6 +1698,24 @@ __metadata: languageName: node linkType: hard +"@babel/traverse@npm:^7.18.0, @babel/traverse@npm:^7.18.2": + version: 7.18.2 + resolution: "@babel/traverse@npm:7.18.2" + dependencies: + "@babel/code-frame": ^7.16.7 + "@babel/generator": ^7.18.2 + "@babel/helper-environment-visitor": ^7.18.2 + "@babel/helper-function-name": ^7.17.9 + "@babel/helper-hoist-variables": ^7.16.7 + "@babel/helper-split-export-declaration": ^7.16.7 + "@babel/parser": ^7.18.0 + "@babel/types": ^7.18.2 + debug: ^4.1.0 + globals: ^11.1.0 + checksum: e21c2d550bf610406cf21ef6fbec525cb1d80b9d6d71af67552478a24ee371203cb4025b23b110ae7288a62a874ad5898daad19ad23daa95dfc8ab47a47a092f + languageName: node + linkType: hard + "@babel/types@npm:7.13.0": version: 7.13.0 resolution: "@babel/types@npm:7.13.0" @@ -1665,13 +1727,13 @@ __metadata: languageName: node linkType: hard -"@babel/types@npm:^7.0.0, @babel/types@npm:^7.16.7, @babel/types@npm:^7.16.8, @babel/types@npm:^7.3.0": - version: 7.16.8 - resolution: "@babel/types@npm:7.16.8" +"@babel/types@npm:^7.0.0, @babel/types@npm:^7.18.0, @babel/types@npm:^7.18.2, @babel/types@npm:^7.3.0": + version: 7.18.2 + resolution: "@babel/types@npm:7.18.2" dependencies: "@babel/helper-validator-identifier": ^7.16.7 to-fast-properties: ^2.0.0 - checksum: 4f6a187b2924df70e21d6e6c0822f91b1b936fe060bc92bb477b93bd8a712c88fe41a73f85c0ec53b033353374fe33e773b04ffc340ad36afd8f647dd05c4ee1 + checksum: 3750bcb9ef6f36ecf0c1477cf6010cd23f2db5cb93f6771ba84c07c08aa005934532bc81e9067192f85214c43e16731e0e3c244773071879967fd1cd22ba2144 languageName: node linkType: hard @@ -1685,6 +1747,16 @@ __metadata: languageName: node linkType: hard +"@babel/types@npm:^7.16.7, @babel/types@npm:^7.16.8": + version: 7.16.8 + resolution: "@babel/types@npm:7.16.8" + dependencies: + "@babel/helper-validator-identifier": ^7.16.7 + to-fast-properties: ^2.0.0 + checksum: 4f6a187b2924df70e21d6e6c0822f91b1b936fe060bc92bb477b93bd8a712c88fe41a73f85c0ec53b033353374fe33e773b04ffc340ad36afd8f647dd05c4ee1 + languageName: node + linkType: hard + "@babel/types@npm:^7.17.0, @babel/types@npm:^7.8.3": version: 7.17.0 resolution: "@babel/types@npm:7.17.0" @@ -1749,10 +1821,10 @@ __metadata: languageName: node linkType: hard -"@fontsource/bitter@npm:^4.5.0": - version: 4.5.0 - resolution: "@fontsource/bitter@npm:4.5.0" - checksum: f87d9cb04519586adeb3c9e7f069efecd88448c352a5b8409c6aa5046309b0424c41bcb70baaefee3dd731a5c8dafc296c430b9557df11dfde3306ce102f7c75 +"@fontsource/bitter@npm:^4.5.7": + version: 4.5.7 + resolution: "@fontsource/bitter@npm:4.5.7" + checksum: b60995e5411a04d52bc69b45221dc81828e3b13515abb2e4d338ab9f24618fbfebce35aaff9ad48a04691b7b11167c0cdfae280b2c0c0638e6425e110532ff5f languageName: node linkType: hard @@ -1933,9 +2005,9 @@ __metadata: languageName: node linkType: hard -"@insertish/oapi@npm:0.1.15": - version: 0.1.15 - resolution: "@insertish/oapi@npm:0.1.15" +"@insertish/oapi@npm:0.1.16": + version: 0.1.16 + resolution: "@insertish/oapi@npm:0.1.16" dependencies: axios: ^0.26.1 openapi-typescript: ^5.2.0 @@ -1947,7 +2019,7 @@ __metadata: optional: true bin: oapilib: cli.js - checksum: e4b34382f8f64eb6f5e6f9e3df6e607341031c4d4571169d7d7ad75e76f5dabc766de467f9f6a1cdd35818ce39724daa4a5ce487ccb5d069379a6dfab0faf8b6 + checksum: 746e447fd41c6a3925b36af1747c8fe9591e5d93bf119a6a8d22ff76b779f325ec9c0f13dd77ff1957aa54c365c0c8ac9c5efab74c63d4897efe20c8c3270032 languageName: node linkType: hard @@ -1967,6 +2039,58 @@ __metadata: languageName: node linkType: hard +"@jridgewell/gen-mapping@npm:^0.1.0": + version: 0.1.1 + resolution: "@jridgewell/gen-mapping@npm:0.1.1" + dependencies: + "@jridgewell/set-array": ^1.0.0 + "@jridgewell/sourcemap-codec": ^1.4.10 + checksum: 3bcc21fe786de6ffbf35c399a174faab05eb23ce6a03e8769569de28abbf4facc2db36a9ddb0150545ae23a8d35a7cf7237b2aa9e9356a7c626fb4698287d5cc + languageName: node + linkType: hard + +"@jridgewell/gen-mapping@npm:^0.3.0": + version: 0.3.1 + resolution: "@jridgewell/gen-mapping@npm:0.3.1" + dependencies: + "@jridgewell/set-array": ^1.0.0 + "@jridgewell/sourcemap-codec": ^1.4.10 + "@jridgewell/trace-mapping": ^0.3.9 + checksum: e9e7bb3335dea9e60872089761d4e8e089597360cdb1af90370e9d53b7d67232c1e0a3ab65fbfef4fc785745193fbc56bff9f3a6cab6c6ce3f15e12b4191f86b + languageName: node + linkType: hard + +"@jridgewell/resolve-uri@npm:^3.0.3": + version: 3.0.7 + resolution: "@jridgewell/resolve-uri@npm:3.0.7" + checksum: 94f454f4cef8f0acaad85745fd3ca6cd0d62ef731cf9f952ecb89b8b2ce5e20998cd52be31311cedc5fa5b28b1708a15f3ad9df0fe1447ee4f42959b036c4b5b + languageName: node + linkType: hard + +"@jridgewell/set-array@npm:^1.0.0": + version: 1.1.1 + resolution: "@jridgewell/set-array@npm:1.1.1" + checksum: cc5d91e0381c347e3edee4ca90b3c292df9e6e55f29acbe0dd97de8651b4730e9ab761406fd572effa79972a0edc55647b627f8c72315e276d959508853d9bf2 + languageName: node + linkType: hard + +"@jridgewell/sourcemap-codec@npm:^1.4.10": + version: 1.4.13 + resolution: "@jridgewell/sourcemap-codec@npm:1.4.13" + checksum: f14449096f60a5f921262322fef65ce0bbbfb778080b3b20212080bcefdeba621c43a58c27065bd536ecb4cc767b18eb9c45f15b6b98a4970139572b60603a1c + languageName: node + linkType: hard + +"@jridgewell/trace-mapping@npm:^0.3.9": + version: 0.3.13 + resolution: "@jridgewell/trace-mapping@npm:0.3.13" + dependencies: + "@jridgewell/resolve-uri": ^3.0.3 + "@jridgewell/sourcemap-codec": ^1.4.10 + checksum: e38254e830472248ca10a6ed1ae75af5e8514f0680245a5e7b53bc3c030fd8691d4d3115d80595b45d3badead68269769ed47ecbbdd67db1343a11f05700e75a + languageName: node + linkType: hard + "@juggle/resize-observer@npm:^3.3.1": version: 3.3.1 resolution: "@juggle/resize-observer@npm:3.3.1" @@ -2096,21 +2220,24 @@ __metadata: languageName: node linkType: hard -"@revoltchat/ui@portal:../components::locator=client%40workspace%3A.": - version: 0.0.0-use.local - resolution: "@revoltchat/ui@portal:../components::locator=client%40workspace%3A." +"@revoltchat/ui@npm:^1.0.32": + version: 1.0.32 + resolution: "@revoltchat/ui@npm:1.0.32" dependencies: "@styled-icons/boxicons-logos": ^10.38.0 "@styled-icons/boxicons-regular": ^10.38.0 "@styled-icons/boxicons-solid": ^10.38.0 - mobx: ^6.6.0 - mobx-react-lite: ^3.4.0 - react-beautiful-dnd: ^13.1.0 - react-virtuoso: ^2.12.0 peerDependencies: - revolt-api: "*" + "@tippyjs/react": "*" + mobx: "*" + mobx-react-lite: "*" + react-beautiful-dnd: "*" + react-device-detect: "*" + react-virtuoso: "*" + revolt.js: "*" + checksum: 7004add041a66932a7efc836f33648e905bd9820dd1d65890bccc93bc95cbc643ccede67dc570341fcc9c9993fffb57702dbd6ed41dac014553be18400bc313e languageName: node - linkType: soft + linkType: hard "@rollup/plugin-babel@npm:^5.2.0": version: 5.3.0 @@ -2257,7 +2384,7 @@ __metadata: languageName: node linkType: hard -"@tippyjs/react@npm:^4.2.5": +"@tippyjs/react@npm:4.2.5": version: 4.2.5 resolution: "@tippyjs/react@npm:4.2.5" dependencies: @@ -2311,15 +2438,15 @@ __metadata: linkType: hard "@types/babel__core@npm:^7.1.12": - version: 7.1.18 - resolution: "@types/babel__core@npm:7.1.18" + version: 7.1.19 + resolution: "@types/babel__core@npm:7.1.19" dependencies: "@babel/parser": ^7.1.0 "@babel/types": ^7.0.0 "@types/babel__generator": "*" "@types/babel__template": "*" "@types/babel__traverse": "*" - checksum: 2e5b5d7c84f347d3789575486e58b0df5c91613abc3d27e716274aba3048518e07e1f068250ba829e2ed58532ccc88da595ce95ba2688e7bbcd7c25a3c6627ed + checksum: 8c9fa87a1c2224cbec251683a58bebb0d74c497118034166aaa0491a4e2627998a6621fc71f8a60ffd27d9c0c52097defedf7637adc6618d0331c15adb302338 languageName: node linkType: hard @@ -2343,11 +2470,11 @@ __metadata: linkType: hard "@types/babel__traverse@npm:*": - version: 7.14.2 - resolution: "@types/babel__traverse@npm:7.14.2" + version: 7.17.1 + resolution: "@types/babel__traverse@npm:7.17.1" dependencies: "@babel/types": ^7.3.0 - checksum: a797ea09c72307569e3ee08aa3900ca744ce3091114084f2dc59b67a45ee7d01df7865252790dbfa787a7915ce892cdc820c9b920f3683292765fc656b08dc63 + checksum: 8992d8c1eaaf1c793e9184b930767883446939d2744c40ea4e9591086e79b631189dc519931ed8864f1e016742a189703c217db59b800aca84870b865009d8b4 languageName: node linkType: hard @@ -2506,7 +2633,7 @@ __metadata: languageName: node linkType: hard -"@types/react-beautiful-dnd@npm:^13.1.2": +"@types/react-beautiful-dnd@npm:^13": version: 13.1.2 resolution: "@types/react-beautiful-dnd@npm:13.1.2" dependencies: @@ -2524,15 +2651,15 @@ __metadata: languageName: node linkType: hard -"@types/react-redux@npm:^7.1.16": - version: 7.1.18 - resolution: "@types/react-redux@npm:7.1.18" +"@types/react-redux@npm:^7.1.20": + version: 7.1.24 + resolution: "@types/react-redux@npm:7.1.24" dependencies: "@types/hoist-non-react-statics": ^3.3.0 "@types/react": "*" hoist-non-react-statics: ^3.3.0 redux: ^4.0.0 - checksum: 8aa24c15df711e2a20f903843f42491316094c3a49a90dcae86dcafa8fdb2318fdfaa983e23d67840986f11131b9b8856a5d6971288d68fa8aa592adc348a942 + checksum: 6582246581331ac7fbbd44aa1f1c136c8a9c8febbcf462432ac81302263308c21e1a2e7868beb7f73bbcb52a8e67935d133cb37f5bdcb6564eaff3a811805101 languageName: node linkType: hard @@ -3205,18 +3332,18 @@ __metadata: languageName: node linkType: hard -"browserslist@npm:^4.17.5": - version: 4.19.1 - resolution: "browserslist@npm:4.19.1" +"browserslist@npm:^4.20.2": + version: 4.20.3 + resolution: "browserslist@npm:4.20.3" dependencies: - caniuse-lite: ^1.0.30001286 - electron-to-chromium: ^1.4.17 + caniuse-lite: ^1.0.30001332 + electron-to-chromium: ^1.4.118 escalade: ^3.1.1 - node-releases: ^2.0.1 + node-releases: ^2.0.3 picocolors: ^1.0.0 bin: browserslist: cli.js - checksum: c0777fd483691638fd6801e16c9d809e1d65f6d2b06db2e806654be51045cbab1452a89841a2c5caea2cbe19d621b4f1d391cffbb24512aa33280039ab345875 + checksum: 1e4b719ac2ca0fe235218a606e8b8ef16b8809e0973b924158c39fbc435a0b0fe43437ea52dd6ef5ad2efcb83fcb07431244e472270177814217f7c563651f7d languageName: node linkType: hard @@ -3284,13 +3411,20 @@ __metadata: languageName: node linkType: hard -"caniuse-lite@npm:^1.0.30001251, caniuse-lite@npm:^1.0.30001286": +"caniuse-lite@npm:^1.0.30001251": version: 1.0.30001313 resolution: "caniuse-lite@npm:1.0.30001313" checksum: 49f2dcd1fa493a09a5247dcf3a4da3b9df355131b1fc1fd08b67ae7683c300ed9b9eef6a5424b4ac7e5d1ff0e129d2a0b4adf2a6a5a04ab5c2c0b2c590e935be languageName: node linkType: hard +"caniuse-lite@npm:^1.0.30001332": + version: 1.0.30001344 + resolution: "caniuse-lite@npm:1.0.30001344" + checksum: 9dba66f796dc98632dced4c5d487d0fad219e137a27c634eec68520f2e598a613e3371b9207e15a078689a629128eca898793e37fc98841821ab481bddad51b9 + languageName: node + linkType: hard + "chalk@npm:^2.0.0, chalk@npm:^2.4.2": version: 2.4.2 resolution: "chalk@npm:2.4.2" @@ -3365,7 +3499,7 @@ __metadata: dependencies: "@babel/plugin-proposal-decorators": ^7.17.9 "@fontsource/atkinson-hyperlegible": ^4.4.5 - "@fontsource/bitter": ^4.5.0 + "@fontsource/bitter": ^4.5.7 "@fontsource/comic-neue": ^4.4.5 "@fontsource/fira-code": ^4.4.5 "@fontsource/inter": ^4.4.5 @@ -3387,13 +3521,13 @@ __metadata: "@hcaptcha/react-hcaptcha": ^0.3.6 "@insertish/vite-plugin-babel-macros": ^1.0.5 "@preact/preset-vite": ^2.0.0 - "@revoltchat/ui": ^1.0.31 + "@revoltchat/ui": ^1.0.32 "@rollup/plugin-replace": ^2.4.2 "@styled-icons/boxicons-logos": ^10.38.0 "@styled-icons/boxicons-regular": ^10.38.0 "@styled-icons/boxicons-solid": ^10.38.0 "@styled-icons/simple-icons": ^10.33.0 - "@tippyjs/react": ^4.2.5 + "@tippyjs/react": "npm:4.2.5" "@traptitech/markdown-it-katex": ^3.4.3 "@traptitech/markdown-it-spoiler": ^1.1.6 "@trivago/prettier-plugin-sort-imports": ^2.0.2 @@ -3403,7 +3537,7 @@ __metadata: "@types/node": ^15.12.4 "@types/preact-i18n": ^2.3.0 "@types/prismjs": ^1.16.5 - "@types/react-beautiful-dnd": ^13.1.2 + "@types/react-beautiful-dnd": ^13 "@types/react-helmet": ^6.1.1 "@types/react-router-dom": ^5.1.7 "@types/react-scroll": ^1.8.2 @@ -3437,14 +3571,14 @@ __metadata: prettier: ^2.3.1 prismjs: ^1.23.0 react-beautiful-dnd: ^13.1.0 - react-device-detect: ^1.17.0 + react-device-detect: "npm:2.2.2" react-helmet: ^6.1.0 react-hook-form: 6.3.0 react-overlapping-panels: 1.2.2 react-router-dom: ^5.2.0 react-scroll: ^1.8.2 react-virtuoso: ^2.12.0 - revolt.js: 6.0.0-2 + revolt.js: "npm:6.0.1" rimraf: ^3.0.2 sass: ^1.35.1 shade-blend-color: ^1.0.0 @@ -3813,10 +3947,10 @@ __metadata: languageName: node linkType: hard -"electron-to-chromium@npm:^1.4.17": - version: 1.4.45 - resolution: "electron-to-chromium@npm:1.4.45" - checksum: 8afc465bfe4873701c748626bcd3081976526b3cd9cf4b098b0a1ad90bd2be5f4933ddf1e061cc140d2793146f0901f2b96996aaecd726d0abdebcaf5d3bdbaa +"electron-to-chromium@npm:^1.4.118": + version: 1.4.140 + resolution: "electron-to-chromium@npm:1.4.140" + checksum: bf06151bdd76dbcf00c97215d0c79479a4d2116e4a1734ee319cf83865ceab56ee834b3f4347bf9c01ae5c0a953fb0b93e2f097c3ed33f6292d03bcb40af651d languageName: node linkType: hard @@ -5358,6 +5492,15 @@ __metadata: languageName: node linkType: hard +"json5@npm:^2.2.1": + version: 2.2.1 + resolution: "json5@npm:2.2.1" + bin: + json5: lib/cli.js + checksum: 74b8a23b102a6f2bf2d224797ae553a75488b5adbaee9c9b6e5ab8b510a2fc6e38f876d4c77dea672d4014a44b2399e15f2051ac2b37b87f74c0c7602003543b + languageName: node + linkType: hard + "jsonfile@npm:^6.0.1": version: 6.1.0 resolution: "jsonfile@npm:6.1.0" @@ -5846,7 +5989,7 @@ __metadata: languageName: node linkType: hard -"mobx-react-lite@npm:3.4.0, mobx-react-lite@npm:^3.4.0": +"mobx-react-lite@npm:3.4.0": version: 3.4.0 resolution: "mobx-react-lite@npm:3.4.0" peerDependencies: @@ -5946,10 +6089,10 @@ __metadata: languageName: node linkType: hard -"node-releases@npm:^2.0.1": - version: 2.0.1 - resolution: "node-releases@npm:2.0.1" - checksum: b20dd8d4bced11f75060f0387e05e76b9dc4a0451f7bb3516eade6f50499ea7768ba95d8a60d520c193402df1e58cb3fe301510cc1c1ad68949c3d57b5149866 +"node-releases@npm:^2.0.3": + version: 2.0.5 + resolution: "node-releases@npm:2.0.5" + checksum: e85d949addd19f8827f32569d2be5751e7812ccf6cc47879d49f79b5234ff4982225e39a3929315f96370823b070640fb04d79fc0ddec8b515a969a03493a42f languageName: node linkType: hard @@ -6372,15 +6515,15 @@ __metadata: languageName: node linkType: hard -"react-device-detect@npm:^1.17.0": - version: 1.17.0 - resolution: "react-device-detect@npm:1.17.0" +"react-device-detect@npm:2.2.2": + version: 2.2.2 + resolution: "react-device-detect@npm:2.2.2" dependencies: - ua-parser-js: ^0.7.24 + ua-parser-js: ^1.0.2 peerDependencies: - react: ">= 0.14.0 < 18.0.0" - react-dom: ">= 0.14.0 < 18.0.0" - checksum: bd3583e392af0e807f5329c0763d4f4f15d211363c3cfbb5308221a874faf68a7d7ac339f3c0a4d0c5878e04eaf859e9a7405f2a8ee4a57e739ac7762c1907a1 + react: ">= 0.14.0" + react-dom: ">= 0.14.0" + checksum: d9245cf5a1c1e565e88523ed6be580497d1f6a972fb100a81092943bb7e44afdcdbae0d67bebe7424c4ba5b27a5d13df7894d122307f070fc26062704f7ec788 languageName: node linkType: hard @@ -6414,13 +6557,20 @@ __metadata: languageName: node linkType: hard -"react-is@npm:^16.13.1, react-is@npm:^16.6.0, react-is@npm:^16.7.0, react-is@npm:^16.8.1": +"react-is@npm:^16.6.0, react-is@npm:^16.7.0, react-is@npm:^16.8.1": version: 16.13.1 resolution: "react-is@npm:16.13.1" checksum: f7a19ac3496de32ca9ae12aa030f00f14a3d45374f1ceca0af707c831b2a6098ef0d6bdae51bd437b0a306d7f01d4677fcc8de7c0d331eb47ad0f46130e53c5f languageName: node linkType: hard +"react-is@npm:^17.0.2": + version: 17.0.2 + resolution: "react-is@npm:17.0.2" + checksum: 9d6d111d8990dc98bc5402c1266a808b0459b5d54830bbea24c12d908b536df7883f268a7868cfaedde3dd9d4e0d574db456f84d2e6df9c4526f99bb4b5344d8 + languageName: node + linkType: hard + "react-overlapping-panels@npm:1.2.2": version: 1.2.2 resolution: "react-overlapping-panels@npm:1.2.2" @@ -6431,23 +6581,23 @@ __metadata: linkType: hard "react-redux@npm:^7.2.0": - version: 7.2.5 - resolution: "react-redux@npm:7.2.5" + version: 7.2.8 + resolution: "react-redux@npm:7.2.8" dependencies: - "@babel/runtime": ^7.12.1 - "@types/react-redux": ^7.1.16 + "@babel/runtime": ^7.15.4 + "@types/react-redux": ^7.1.20 hoist-non-react-statics: ^3.3.2 loose-envify: ^1.4.0 prop-types: ^15.7.2 - react-is: ^16.13.1 + react-is: ^17.0.2 peerDependencies: - react: ^16.8.3 || ^17 + react: ^16.8.3 || ^17 || ^18 peerDependenciesMeta: react-dom: optional: true react-native: optional: true - checksum: 04ac4a4178067cbcfc05506dfea9f7e01730093a5752f050567f7ae4a38c03c96da9d8fed051f8ab1ecede5ea8a15ee41c5f6c5eeb7f04f37e4d13e431ec7830 + checksum: ecf1933e91013f2d41bfc781515b536bf81eb1f70ff228607841094c8330fe77d522372b359687e51c0b52b9888dba73db9ac0486aace1896ab9eb9daec102d5 languageName: node linkType: hard @@ -6544,11 +6694,11 @@ __metadata: linkType: hard "redux@npm:^4.0.0, redux@npm:^4.0.4": - version: 4.1.1 - resolution: "redux@npm:4.1.1" + version: 4.2.0 + resolution: "redux@npm:4.2.0" dependencies: "@babel/runtime": ^7.9.2 - checksum: 99519438a5d20b69404ad3816307ccc189f16df04b64c50d82c415ec488ea68b656d7a2fc81b6345e8d90f095344dfea68246500f72613d76464986660bc0485 + checksum: 75f3955c89b3f18edf5411e5fb482aa2e4f41a416183e8802a6bf6472c4fc3d47675b8b321d147f8af8e0f616436ac507bf5a25f1c4d6180e797b549c7db2c1d languageName: node linkType: hard @@ -6708,20 +6858,20 @@ __metadata: languageName: node linkType: hard -"revolt-api@npm:0.5.3-5-patch.3": - version: 0.5.3-5-patch.3 - resolution: "revolt-api@npm:0.5.3-5-patch.3" +"revolt-api@npm:0.5.3-5-patch.4": + version: 0.5.3-5-patch.4 + resolution: "revolt-api@npm:0.5.3-5-patch.4" dependencies: - "@insertish/oapi": 0.1.15 + "@insertish/oapi": 0.1.16 axios: ^0.26.1 lodash.defaultsdeep: ^4.6.1 - checksum: 802d24359e64142317b16eaa40553f872f6ba4876e065704c7d7fb201f993b924076a770f74a49d95a4a494672bc4ab91284868d479c984b03688437749d4979 + checksum: 4f01c43bff96c4030d13ab0bb5dc83614445763602cfdd8b3ff1dbf61620446a22513ca259bbfc9c490f6b9b19c79d610921a252b667d25adf4040b4222d98cf languageName: node linkType: hard -"revolt.js@npm:6.0.0-2": - version: 6.0.0-2 - resolution: "revolt.js@npm:6.0.0-2" +"revolt.js@npm:6.0.1": + version: 6.0.1 + resolution: "revolt.js@npm:6.0.1" dependencies: "@insertish/exponential-backoff": 3.1.0-patch.2 "@insertish/isomorphic-ws": ^4.0.1 @@ -6732,10 +6882,10 @@ __metadata: lodash.isequal: ^4.5.0 long: ^5.2.0 mobx: ^6.3.2 - revolt-api: 0.5.3-5-patch.3 + revolt-api: 0.5.3-5-patch.4 ulid: ^2.3.0 ws: ^8.2.2 - checksum: 4ca0991f33bc0fc610ff551dc10ba0eb785694dfe4c0fde82d63c99d1b89c1083a3d9e5c3ad28f165a5bc633b8ec4b5ecd432932a1df13fd44afaf52df8af325 + checksum: 8e94d709119e3636de8dd71ffe60f0752047b33e5548235f93f36003a102b18705dbb2cb4a273de2ab49d6f594ec34b7a3c6b78e0e48f66c945e8b27573343cf languageName: node linkType: hard @@ -7420,13 +7570,20 @@ __metadata: languageName: node linkType: hard -"tiny-invariant@npm:^1.0.2, tiny-invariant@npm:^1.0.6": +"tiny-invariant@npm:^1.0.2": version: 1.1.0 resolution: "tiny-invariant@npm:1.1.0" checksum: 27d29bbb9e1d1d86e25766711c28ad91af6d67c87d561167077ac7fbce5212b97bbfe875e70bc369808e075748c825864c9b61f0e9f8652275ec86bcf4dcc924 languageName: node linkType: hard +"tiny-invariant@npm:^1.0.6": + version: 1.2.0 + resolution: "tiny-invariant@npm:1.2.0" + checksum: e09a718a7c4a499ba592cdac61f015d87427a0867ca07f50c11fd9b623f90cdba18937b515d4a5e4f43dac92370498d7bdaee0d0e7a377a61095e02c4a92eade + languageName: node + linkType: hard + "tiny-warning@npm:^1.0.0, tiny-warning@npm:^1.0.3": version: 1.0.3 resolution: "tiny-warning@npm:1.0.3" @@ -7490,9 +7647,9 @@ __metadata: linkType: hard "tslib@npm:^2.1.0": - version: 2.3.1 - resolution: "tslib@npm:2.3.1" - checksum: de17a98d4614481f7fcb5cd53ffc1aaf8654313be0291e1bfaee4b4bb31a20494b7d218ff2e15017883e8ea9626599b3b0e0229c18383ba9dce89da2adf15cb9 + version: 2.4.0 + resolution: "tslib@npm:2.4.0" + checksum: 8c4aa6a3c5a754bf76aefc38026134180c053b7bd2f81338cb5e5ebf96fefa0f417bff221592bf801077f5bf990562f6264fecbc42cd3309b33872cb6fc3b113 languageName: node linkType: hard @@ -7570,10 +7727,10 @@ __metadata: languageName: node linkType: hard -"ua-parser-js@npm:^0.7.24": - version: 0.7.28 - resolution: "ua-parser-js@npm:0.7.28" - checksum: a7da4ad54527211e878ee016c2ef64efad5c2f5a31277d36c9da93b4c89ecaa64f391ad4cf158ada76a9ad8e53004a950705ff1c2f27a52ca8bfb3f1381c39ff +"ua-parser-js@npm:^1.0.2": + version: 1.0.2 + resolution: "ua-parser-js@npm:1.0.2" + checksum: ff7f6d79a9c1a38aa85a0e751040fc7e17a0b621bda876838d14ebe55aca4e50e68da0350f181e58801c2d8a35e7db4e12473776e558910c4b7cabcec96aa3bf languageName: node linkType: hard From bdf741e0ee86fff1071c77762161f393703e4ecb Mon Sep 17 00:00:00 2001 From: Paul Makles Date: Fri, 27 May 2022 21:18:12 +0100 Subject: [PATCH 017/151] feat: add "ordering" data store --- package.json | 8 +- .../navigation/left/ServerListSidebar.tsx | 2 + src/mobx/State.ts | 4 + src/mobx/stores/Ordering.ts | 93 +++++++++++++++++++ src/mobx/stores/Sync.ts | 15 ++- yarn.lock | 24 ++--- 6 files changed, 129 insertions(+), 17 deletions(-) create mode 100644 src/mobx/stores/Ordering.ts diff --git a/package.json b/package.json index 155aa2f9..c09559e9 100644 --- a/package.json +++ b/package.json @@ -90,13 +90,13 @@ "@hcaptcha/react-hcaptcha": "^0.3.6", "@insertish/vite-plugin-babel-macros": "^1.0.5", "@preact/preset-vite": "^2.0.0", - "@revoltchat/ui": "^1.0.32", + "@revoltchat/ui": "1.0.33", "@rollup/plugin-replace": "^2.4.2", "@styled-icons/boxicons-logos": "^10.38.0", "@styled-icons/boxicons-regular": "^10.38.0", "@styled-icons/boxicons-solid": "^10.38.0", "@styled-icons/simple-icons": "^10.33.0", - "@tippyjs/react": "npm:4.2.5", + "@tippyjs/react": "4.2.6", "@traptitech/markdown-it-katex": "^3.4.3", "@traptitech/markdown-it-spoiler": "^1.1.6", "@trivago/prettier-plugin-sort-imports": "^2.0.2", @@ -138,14 +138,14 @@ "prettier": "^2.3.1", "prismjs": "^1.23.0", "react-beautiful-dnd": "^13.1.0", - "react-device-detect": "npm:2.2.2", + "react-device-detect": "2.2.2", "react-helmet": "^6.1.0", "react-hook-form": "6.3.0", "react-overlapping-panels": "1.2.2", "react-router-dom": "^5.2.0", "react-scroll": "^1.8.2", "react-virtuoso": "^2.12.0", - "revolt.js": "npm:6.0.1", + "revolt.js": "6.0.1", "rimraf": "^3.0.2", "sass": "^1.35.1", "shade-blend-color": "^1.0.0", diff --git a/src/components/navigation/left/ServerListSidebar.tsx b/src/components/navigation/left/ServerListSidebar.tsx index 57b4111d..23d2b869 100644 --- a/src/components/navigation/left/ServerListSidebar.tsx +++ b/src/components/navigation/left/ServerListSidebar.tsx @@ -35,6 +35,8 @@ export default observer(() => { createServer={createServer} permit={state.notifications} home={state.layout.getLastHomePath} + servers={state.ordering.orderedServers} + reorder={state.ordering.reorderServer} /> ); }); diff --git a/src/mobx/State.ts b/src/mobx/State.ts index a65fc561..b79461af 100644 --- a/src/mobx/State.ts +++ b/src/mobx/State.ts @@ -17,6 +17,7 @@ import Layout from "./stores/Layout"; import LocaleOptions from "./stores/LocaleOptions"; import MessageQueue from "./stores/MessageQueue"; import NotificationOptions from "./stores/NotificationOptions"; +import Ordering from "./stores/Ordering"; import Plugins from "./stores/Plugins"; import ServerConfig from "./stores/ServerConfig"; import Settings from "./stores/Settings"; @@ -41,6 +42,7 @@ export default class State { settings: Settings; sync: Sync; plugins: Plugins; + ordering: Ordering; private persistent: [string, Persistent][] = []; private disabled: Set = new Set(); @@ -62,6 +64,7 @@ export default class State { this.settings = new Settings(); this.sync = new Sync(this); this.plugins = new Plugins(this); + this.ordering = new Ordering(this); makeAutoObservable(this, { client: false, @@ -280,6 +283,7 @@ export default class State { this.queue = new MessageQueue(); this.settings = new Settings(); this.sync = new Sync(this); + this.ordering = new Ordering(this); this.save(); diff --git a/src/mobx/stores/Ordering.ts b/src/mobx/stores/Ordering.ts new file mode 100644 index 00000000..8e2ccb11 --- /dev/null +++ b/src/mobx/stores/Ordering.ts @@ -0,0 +1,93 @@ +import { action, computed, makeAutoObservable } from "mobx"; + +import { reorder } from "@revoltchat/ui"; + +import State from "../State"; +import Persistent from "../interfaces/Persistent"; +import Store from "../interfaces/Store"; +import Syncable from "../interfaces/Syncable"; + +export interface Data { + servers?: string[]; +} + +/** + * Keeps track of ordering of various elements + */ +export default class Ordering implements Store, Persistent, Syncable { + private state: State; + + /** + * Ordered list of server IDs + */ + private servers: string[]; + + /** + * Construct new Layout store. + */ + constructor(state: State) { + this.servers = []; + makeAutoObservable(this); + + this.state = state; + this.reorderServer = this.reorderServer.bind(this); + } + + get id() { + return "ordering"; + } + + toJSON() { + return { + servers: this.servers, + }; + } + + @action hydrate(data: Data) { + if (data.servers) { + this.servers = data.servers; + } + } + + apply(_key: string, data: unknown, _revision: number): void { + this.hydrate(data as Data); + } + + toSyncable(): { [key: string]: object } { + return { + ordering: this.toJSON(), + }; + } + + /** + * All known servers with ordering applied + */ + @computed get orderedServers() { + const known = new Set(this.state.client?.servers.keys() ?? []); + const ordered = [...this.servers]; + + const out = []; + for (const id of ordered) { + if (known.delete(id)) { + out.push(this.state.client!.servers.get(id)!); + } + } + + for (const id of known) { + out.push(this.state.client!.servers.get(id)!); + } + + return out; + } + + /** + * Re-order a server + */ + @action reorderServer(source: number, dest: number) { + this.servers = reorder( + this.orderedServers.map((x) => x._id), + source, + dest, + ); + } +} diff --git a/src/mobx/stores/Sync.ts b/src/mobx/stores/Sync.ts index 01acf8f4..a0d4fcfb 100644 --- a/src/mobx/stores/Sync.ts +++ b/src/mobx/stores/Sync.ts @@ -14,13 +14,19 @@ import State from "../State"; import Persistent from "../interfaces/Persistent"; import Store from "../interfaces/Store"; -export type SyncKeys = "theme" | "appearance" | "locale" | "notifications"; +export type SyncKeys = + | "theme" + | "appearance" + | "locale" + | "notifications" + | "ordering"; export const SYNC_KEYS: SyncKeys[] = [ "theme", "appearance", "locale", "notifications", + "ordering", ]; export interface Data { @@ -151,6 +157,13 @@ export default class Sync implements Store, Persistent { ); this.setRevision("notifications", notifications[0]); } + + const ordering = tryRead("ordering"); + if (ordering) { + this.state.setDisabled("ordering"); + this.state.ordering.apply("ordering", ordering[1], ordering[0]); + this.setRevision("ordering", ordering[0]); + } }); } diff --git a/yarn.lock b/yarn.lock index 92c6acdc..57906b64 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2220,9 +2220,9 @@ __metadata: languageName: node linkType: hard -"@revoltchat/ui@npm:^1.0.32": - version: 1.0.32 - resolution: "@revoltchat/ui@npm:1.0.32" +"@revoltchat/ui@npm:1.0.33": + version: 1.0.33 + resolution: "@revoltchat/ui@npm:1.0.33" dependencies: "@styled-icons/boxicons-logos": ^10.38.0 "@styled-icons/boxicons-regular": ^10.38.0 @@ -2235,7 +2235,7 @@ __metadata: react-device-detect: "*" react-virtuoso: "*" revolt.js: "*" - checksum: 7004add041a66932a7efc836f33648e905bd9820dd1d65890bccc93bc95cbc643ccede67dc570341fcc9c9993fffb57702dbd6ed41dac014553be18400bc313e + checksum: 503fbf8557d205be153343776b8803335b6444b8767e0b1fccf6a34470e4cb333118f7cb614bf16e20d22cb811e7e1160622b9557b73414ad554f8b4bbfb565b languageName: node linkType: hard @@ -2384,15 +2384,15 @@ __metadata: languageName: node linkType: hard -"@tippyjs/react@npm:4.2.5": - version: 4.2.5 - resolution: "@tippyjs/react@npm:4.2.5" +"@tippyjs/react@npm:4.2.6": + version: 4.2.6 + resolution: "@tippyjs/react@npm:4.2.6" dependencies: tippy.js: ^6.3.1 peerDependencies: react: ">=16.8" react-dom: ">=16.8" - checksum: 68a6bb8922597df105f601953f14c593a8179328026dc425db0cd5d8521cdd8ad8c6ec7b6d0707708c8ed25e5ad01c488e95a6b3de0b2f404bd71137e2b8fce9 + checksum: 8f0fba591c9dae2e1af1ae632bbc775ba5c9dd4498e50e242be70302b4c27115c6740eec44e885e294b27cb28515777b52af5b34aac9d4bab627d948add938ae languageName: node linkType: hard @@ -3521,13 +3521,13 @@ __metadata: "@hcaptcha/react-hcaptcha": ^0.3.6 "@insertish/vite-plugin-babel-macros": ^1.0.5 "@preact/preset-vite": ^2.0.0 - "@revoltchat/ui": ^1.0.32 + "@revoltchat/ui": 1.0.33 "@rollup/plugin-replace": ^2.4.2 "@styled-icons/boxicons-logos": ^10.38.0 "@styled-icons/boxicons-regular": ^10.38.0 "@styled-icons/boxicons-solid": ^10.38.0 "@styled-icons/simple-icons": ^10.33.0 - "@tippyjs/react": "npm:4.2.5" + "@tippyjs/react": 4.2.6 "@traptitech/markdown-it-katex": ^3.4.3 "@traptitech/markdown-it-spoiler": ^1.1.6 "@trivago/prettier-plugin-sort-imports": ^2.0.2 @@ -3571,14 +3571,14 @@ __metadata: prettier: ^2.3.1 prismjs: ^1.23.0 react-beautiful-dnd: ^13.1.0 - react-device-detect: "npm:2.2.2" + react-device-detect: 2.2.2 react-helmet: ^6.1.0 react-hook-form: 6.3.0 react-overlapping-panels: 1.2.2 react-router-dom: ^5.2.0 react-scroll: ^1.8.2 react-virtuoso: ^2.12.0 - revolt.js: "npm:6.0.1" + revolt.js: 6.0.1 rimraf: ^3.0.2 sass: ^1.35.1 shade-blend-color: ^1.0.0 From e2d9e41a58940278bebc3eabf3354f40f5ac03d5 Mon Sep 17 00:00:00 2001 From: Paul Makles Date: Fri, 27 May 2022 22:31:12 +0100 Subject: [PATCH 018/151] chore: bump revolt.js and @revoltchat/ui --- package.json | 4 ++-- yarn.lock | 20 ++++++++++---------- 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/package.json b/package.json index c09559e9..8b587394 100644 --- a/package.json +++ b/package.json @@ -90,7 +90,7 @@ "@hcaptcha/react-hcaptcha": "^0.3.6", "@insertish/vite-plugin-babel-macros": "^1.0.5", "@preact/preset-vite": "^2.0.0", - "@revoltchat/ui": "1.0.33", + "@revoltchat/ui": "1.0.34", "@rollup/plugin-replace": "^2.4.2", "@styled-icons/boxicons-logos": "^10.38.0", "@styled-icons/boxicons-regular": "^10.38.0", @@ -145,7 +145,7 @@ "react-router-dom": "^5.2.0", "react-scroll": "^1.8.2", "react-virtuoso": "^2.12.0", - "revolt.js": "6.0.1", + "revolt.js": "6.0.2", "rimraf": "^3.0.2", "sass": "^1.35.1", "shade-blend-color": "^1.0.0", diff --git a/yarn.lock b/yarn.lock index 57906b64..34a81618 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2220,9 +2220,9 @@ __metadata: languageName: node linkType: hard -"@revoltchat/ui@npm:1.0.33": - version: 1.0.33 - resolution: "@revoltchat/ui@npm:1.0.33" +"@revoltchat/ui@npm:1.0.34": + version: 1.0.34 + resolution: "@revoltchat/ui@npm:1.0.34" dependencies: "@styled-icons/boxicons-logos": ^10.38.0 "@styled-icons/boxicons-regular": ^10.38.0 @@ -2235,7 +2235,7 @@ __metadata: react-device-detect: "*" react-virtuoso: "*" revolt.js: "*" - checksum: 503fbf8557d205be153343776b8803335b6444b8767e0b1fccf6a34470e4cb333118f7cb614bf16e20d22cb811e7e1160622b9557b73414ad554f8b4bbfb565b + checksum: 576a147c945cc3c9c8fb0555007b1048017551139d6b6dd7f0e9af66a1f5e595a46e73b853258a5048563af80c470230048ab214784cf14f347cf61b56dbbc07 languageName: node linkType: hard @@ -3521,7 +3521,7 @@ __metadata: "@hcaptcha/react-hcaptcha": ^0.3.6 "@insertish/vite-plugin-babel-macros": ^1.0.5 "@preact/preset-vite": ^2.0.0 - "@revoltchat/ui": 1.0.33 + "@revoltchat/ui": 1.0.34 "@rollup/plugin-replace": ^2.4.2 "@styled-icons/boxicons-logos": ^10.38.0 "@styled-icons/boxicons-regular": ^10.38.0 @@ -3578,7 +3578,7 @@ __metadata: react-router-dom: ^5.2.0 react-scroll: ^1.8.2 react-virtuoso: ^2.12.0 - revolt.js: 6.0.1 + revolt.js: 6.0.2 rimraf: ^3.0.2 sass: ^1.35.1 shade-blend-color: ^1.0.0 @@ -6869,9 +6869,9 @@ __metadata: languageName: node linkType: hard -"revolt.js@npm:6.0.1": - version: 6.0.1 - resolution: "revolt.js@npm:6.0.1" +"revolt.js@npm:6.0.2": + version: 6.0.2 + resolution: "revolt.js@npm:6.0.2" dependencies: "@insertish/exponential-backoff": 3.1.0-patch.2 "@insertish/isomorphic-ws": ^4.0.1 @@ -6885,7 +6885,7 @@ __metadata: revolt-api: 0.5.3-5-patch.4 ulid: ^2.3.0 ws: ^8.2.2 - checksum: 8e94d709119e3636de8dd71ffe60f0752047b33e5548235f93f36003a102b18705dbb2cb4a273de2ab49d6f594ec34b7a3c6b78e0e48f66c945e8b27573343cf + checksum: 1b0c6ce0ceae5d20aec373f24432a5e804677c455fbc39edadaeb6ae0427a0b4fe2ea2b950df92262cd56a43e3f8759d80887b65d66111882b6ecb82e813ff38 languageName: node linkType: hard From 12b97160433151c188b077bd3e85fc2ee35bbd99 Mon Sep 17 00:00:00 2001 From: Paul Makles Date: Fri, 27 May 2022 22:32:06 +0100 Subject: [PATCH 019/151] fix: open last opened server channel instead of first --- src/pages/channels/Channel.tsx | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/src/pages/channels/Channel.tsx b/src/pages/channels/Channel.tsx index f27f5317..49d4028f 100644 --- a/src/pages/channels/Channel.tsx +++ b/src/pages/channels/Channel.tsx @@ -23,9 +23,8 @@ import MessageBox from "../../components/common/messaging/MessageBox"; import JumpToBottom from "../../components/common/messaging/bars/JumpToBottom"; import NewMessages from "../../components/common/messaging/bars/NewMessages"; import TypingIndicator from "../../components/common/messaging/bars/TypingIndicator"; -import { PageHeader } from "../../components/ui/Header"; - import RightSidebar from "../../components/navigation/RightSidebar"; +import { PageHeader } from "../../components/ui/Header"; import ChannelHeader from "./ChannelHeader"; import { MessageArea } from "./messaging/MessageArea"; import VoiceHeader from "./voice/VoiceHeader"; @@ -100,14 +99,23 @@ const PlaceholderBase = styled.div` export const Channel = observer( ({ id, server_id }: { id: string; server_id: string }) => { const client = useClient(); + const state = useApplicationState(); if (!client.channels.exists(id)) { if (server_id) { const server = client.servers.get(server_id); if (server && server.channel_ids.length > 0) { + let target_id = server.channel_ids[0]; + const last_id = state.layout.getLastOpened(server_id); + if (last_id) { + if (client.channels.has(last_id)) { + target_id = last_id; + } + } + return ( ); } From 20d31babced955ab03944b28a9a73bf4aa013395 Mon Sep 17 00:00:00 2001 From: Paul Makles Date: Sun, 29 May 2022 15:43:36 +0100 Subject: [PATCH 020/151] feat(@ui): migrate Banner component --- src/components/navigation/items/ConnectionStatus.tsx | 4 ++-- src/components/ui/Banner.tsx | 10 ---------- 2 files changed, 2 insertions(+), 12 deletions(-) delete mode 100644 src/components/ui/Banner.tsx diff --git a/src/components/navigation/items/ConnectionStatus.tsx b/src/components/navigation/items/ConnectionStatus.tsx index de0d42b7..899b3d8e 100644 --- a/src/components/navigation/items/ConnectionStatus.tsx +++ b/src/components/navigation/items/ConnectionStatus.tsx @@ -1,14 +1,14 @@ import { Text } from "preact-i18n"; import { useContext } from "preact/hooks"; +import { Banner } from "@revoltchat/ui"; + import { ClientStatus, StatusContext, useClient, } from "../../../context/revoltjs/RevoltClient"; -import Banner from "../../ui/Banner"; - export default function ConnectionStatus() { const status = useContext(StatusContext); const client = useClient(); diff --git a/src/components/ui/Banner.tsx b/src/components/ui/Banner.tsx deleted file mode 100644 index ca4704d9..00000000 --- a/src/components/ui/Banner.tsx +++ /dev/null @@ -1,10 +0,0 @@ -import styled from "styled-components/macro"; - -export default styled.div` - padding: 8px; - font-size: 14px; - text-align: center; - - color: var(--accent); - background: var(--primary-background); -`; From 4bcfa601a53bf8f19b63642139e64d932cbaf804 Mon Sep 17 00:00:00 2001 From: Paul Makles Date: Sun, 29 May 2022 16:34:54 +0100 Subject: [PATCH 021/151] feat(@ui): migrate checkbox component --- package.json | 5 +- src/components/common/AgeGate.tsx | 12 +- src/components/common/user/UserCheckbox.tsx | 4 +- src/components/settings/AppearanceShims.tsx | 42 +++--- .../settings/roles/PermissionSelect.tsx | 5 +- src/components/ui/Checkbox.tsx | 123 ---------------- src/pages/settings/channel/Overview.tsx | 11 +- src/pages/settings/panes/Experiments.tsx | 12 +- src/pages/settings/panes/Languages.tsx | 139 ++++++++++-------- src/pages/settings/panes/MyBots.tsx | 12 +- src/pages/settings/panes/Native.tsx | 72 ++++----- src/pages/settings/panes/Notifications.tsx | 36 +++-- src/pages/settings/panes/Panes.module.scss | 2 +- src/pages/settings/panes/Plugins.tsx | 54 ++----- src/pages/settings/panes/Sync.tsx | 12 +- src/pages/settings/server/Members.tsx | 23 +-- src/pages/settings/server/Roles.tsx | 27 ++-- yarn.lock | 9 +- 18 files changed, 239 insertions(+), 361 deletions(-) delete mode 100644 src/components/ui/Checkbox.tsx diff --git a/package.json b/package.json index 8b587394..bc2077b9 100644 --- a/package.json +++ b/package.json @@ -162,5 +162,8 @@ "repository": "https://github.com/revoltchat/revite.git", "author": "Paul ", "license": "MIT", - "packageManager": "yarn@3.2.0" + "packageManager": "yarn@3.2.0", + "resolutions": { + "@revoltchat/ui": "portal:../components" + } } diff --git a/src/components/common/AgeGate.tsx b/src/components/common/AgeGate.tsx index 825721b8..fc0fbdfa 100644 --- a/src/components/common/AgeGate.tsx +++ b/src/components/common/AgeGate.tsx @@ -6,13 +6,11 @@ import styled from "styled-components/macro"; import { Text } from "preact-i18n"; import { useState } from "preact/hooks"; -import { Button } from "@revoltchat/ui"; +import { Button, Checkbox } from "@revoltchat/ui"; import { useApplicationState } from "../../mobx/State"; import { SECTION_NSFW } from "../../mobx/stores/Layout"; -import Checkbox from "../ui/Checkbox"; - import { Children } from "../../types/Preact"; const Base = styled.div` @@ -81,10 +79,10 @@ export default observer((props: Props) => { layout.toggleSectionState(SECTION_NSFW, false)}> - - + title={} + value={layout.getSectionState(SECTION_NSFW, false)} + onChange={() => layout.toggleSectionState(SECTION_NSFW, false)} + />
); diff --git a/src/pages/settings/panes/Panes.module.scss b/src/pages/settings/panes/Panes.module.scss index ff5c3321..159d7342 100644 --- a/src/pages/settings/panes/Panes.module.scss +++ b/src/pages/settings/panes/Panes.module.scss @@ -485,7 +485,7 @@ } } - .entry > span > span { + .entry > div div { gap: 8px; display: flex; align-items: center; diff --git a/src/pages/settings/panes/Plugins.tsx b/src/pages/settings/panes/Plugins.tsx index 8bf45a86..cc6c6fc3 100644 --- a/src/pages/settings/panes/Plugins.tsx +++ b/src/pages/settings/panes/Plugins.tsx @@ -1,15 +1,12 @@ -import { Check } from "@styled-icons/boxicons-regular"; import { observer } from "mobx-react-lite"; -import styled from "styled-components"; import styles from "./Panes.module.scss"; import { Text } from "preact-i18n"; -import { Button } from "@revoltchat/ui"; +import { Button, Checkbox } from "@revoltchat/ui"; import { useApplicationState } from "../../../mobx/State"; -import { CheckboxBase, Checkmark } from "../../../components/ui/Checkbox"; import Tip from "../../../components/ui/Tip"; // Just keeping this here for general purpose. Should probably be exported @@ -21,45 +18,10 @@ interface Plugin { enabled: boolean | undefined; } -const CustomCheckboxBase = styled(CheckboxBase)` - margin-top: 0 !important; -`; -export interface CheckboxProps { - checked: boolean; - disabled?: boolean; - onChange: (state: boolean) => void; -} -function PluginCheckbox(props: CheckboxProps) { - // HACK HACK HACK(lexisother): THIS ENTIRE THING IS A HACK!!!! - /* - Until some reviewer points me in the right direction, I've resorted to - fabricating my own checkbox component. - "WHY?!", you might ask. Well, the normal `Checkbox` component can take - textual contents, and *also* adds a `margin-top` of 20 pixels. - We... don't need that. At all. *Especially* the margin. It makes our card - look disproportionate. - - Apologies, @insert! - */ - return ( - - - !props.disabled && props.onChange(!props.checked) - } - /> - - - - - ); -} - interface CardProps { plugin: Plugin; } + function PluginCard({ plugin }: CardProps) { const plugins = useApplicationState().plugins; @@ -70,12 +32,14 @@ function PluginCard({ plugin }: CardProps) {
-
- {plugin.namespace} / {plugin.id} -
- + {plugin.namespace} / {plugin.id} + + } onChange={() => { !plugin.enabled ? plugins.load(plugin.namespace, plugin.id) diff --git a/src/pages/settings/panes/Sync.tsx b/src/pages/settings/panes/Sync.tsx index f426c4d4..a6d26c6a 100644 --- a/src/pages/settings/panes/Sync.tsx +++ b/src/pages/settings/panes/Sync.tsx @@ -3,11 +3,11 @@ import { observer } from "mobx-react-lite"; import styles from "./Panes.module.scss"; import { Text } from "preact-i18n"; +import { Checkbox } from "@revoltchat/ui"; + import { useApplicationState } from "../../../mobx/State"; import { SyncKeys } from "../../../mobx/stores/Sync"; -import Checkbox from "../../../components/ui/Checkbox"; - export const Sync = observer(() => { const sync = useApplicationState().sync; @@ -30,15 +30,15 @@ export const Sync = observer(() => { ).map(([key, title]) => ( } description={ } - onChange={() => sync.toggle(key)}> - - + onChange={() => sync.toggle(key)} + /> ))} {/*
Last sync at 12:00 diff --git a/src/pages/settings/server/Members.tsx b/src/pages/settings/server/Members.tsx index d7cd740e..99e3e89b 100644 --- a/src/pages/settings/server/Members.tsx +++ b/src/pages/settings/server/Members.tsx @@ -9,11 +9,10 @@ import styles from "./Panes.module.scss"; import { Text } from "preact-i18n"; import { useEffect, useMemo, useState } from "preact/hooks"; -import { Button, Preloader } from "@revoltchat/ui"; +import { Button, Checkbox, Preloader } from "@revoltchat/ui"; import UserIcon from "../../../components/common/user/UserIcon"; import { Username } from "../../../components/common/user/UserShort"; -import Checkbox from "../../../components/ui/Checkbox"; import IconButton from "../../../components/ui/IconButton"; import InputBox from "../../../components/ui/InputBox"; import Overline from "../../../components/ui/Overline"; @@ -54,7 +53,15 @@ const Inner = observer(({ member }: InnerProps) => { return ( + {role.name} + + } onChange={(v) => { if (v) { setRoles([...roles, key]); @@ -63,14 +70,8 @@ const Inner = observer(({ member }: InnerProps) => { roles.filter((x) => x !== key), ); } - }}> - - {role.name} - - + }} + /> ); })}
{ diff --git a/src/pages/settings/panes/MyBots.tsx b/src/pages/settings/panes/MyBots.tsx index cd786d63..10756648 100644 --- a/src/pages/settings/panes/MyBots.tsx +++ b/src/pages/settings/panes/MyBots.tsx @@ -11,7 +11,7 @@ import styles from "./Panes.module.scss"; import { Text } from "preact-i18n"; import { useCallback, useEffect, useState } from "preact/hooks"; -import { Button, Checkbox } from "@revoltchat/ui"; +import { Button, Checkbox, InputBox } from "@revoltchat/ui"; import TextAreaAutoSize from "../../../lib/TextAreaAutoSize"; import { internalEmit } from "../../../lib/eventEmitter"; @@ -28,7 +28,6 @@ import AutoComplete, { import CollapsibleSection from "../../../components/common/CollapsibleSection"; import Tooltip from "../../../components/common/Tooltip"; import UserIcon from "../../../components/common/user/UserIcon"; -import InputBox from "../../../components/ui/InputBox"; import Tip from "../../../components/ui/Tip"; import CategoryButton from "../../../components/ui/fluent/CategoryButton"; @@ -450,6 +449,7 @@ function BotCard({ bot, onDelete, onUpdate }: Props) { { placeholder="Search for a specific user..." value={query} onChange={(e) => setQuery(e.currentTarget.value)} - contrast + palette="secondary" />
{data?.length ?? 0} Members
{members ? ( diff --git a/src/pages/settings/server/Overview.tsx b/src/pages/settings/server/Overview.tsx index f750b275..4a333bab 100644 --- a/src/pages/settings/server/Overview.tsx +++ b/src/pages/settings/server/Overview.tsx @@ -7,7 +7,7 @@ import styles from "./Panes.module.scss"; import { Text } from "preact-i18n"; import { useEffect, useState } from "preact/hooks"; -import { Button, ComboBox } from "@revoltchat/ui"; +import { Button, ComboBox, InputBox } from "@revoltchat/ui"; import TextAreaAutoSize from "../../../lib/TextAreaAutoSize"; import { noop } from "../../../lib/js"; @@ -15,8 +15,6 @@ import { noop } from "../../../lib/js"; import { FileUploader } from "../../../context/revoltjs/FileUploads"; import { getChannelName } from "../../../context/revoltjs/util"; -import InputBox from "../../../components/ui/InputBox"; - interface Props { server: Server; } @@ -70,9 +68,9 @@ export const Overview = observer(({ server }: Props) => { { setName(e.currentTarget.value); if (!changed) setChanged(true); diff --git a/src/pages/settings/server/Roles.tsx b/src/pages/settings/server/Roles.tsx index 11c27253..8aada1f6 100644 --- a/src/pages/settings/server/Roles.tsx +++ b/src/pages/settings/server/Roles.tsx @@ -12,13 +12,13 @@ import { H1, Checkbox, ColourSwatches, + InputBox, } from "@revoltchat/ui"; import { useIntermediate } from "../../../context/intermediate/Intermediate"; import { PermissionList } from "../../../components/settings/roles/PermissionList"; import { RoleOrDefault } from "../../../components/settings/roles/RoleSelection"; -import InputBox from "../../../components/ui/InputBox"; import Overline from "../../../components/ui/Overline"; interface Props { @@ -148,7 +148,7 @@ export const Roles = observer(({ server }: Props) => { name: e.currentTarget.value, }) } - contrast + palette="secondary" />

@@ -205,7 +205,7 @@ export const Roles = observer(({ server }: Props) => { ), }) } - contrast + palette="secondary" />

From ab77d4a8124788d36cc642de7bf8b548399a7876 Mon Sep 17 00:00:00 2001 From: Paul Makles Date: Mon, 30 May 2022 12:29:56 +0100 Subject: [PATCH 027/151] feat(@ui): migrate radio --- src/components/settings/AppearanceShims.tsx | 20 ++-- src/components/ui/Radio.tsx | 112 -------------------- src/context/intermediate/modals/Prompt.tsx | 23 ++-- 3 files changed, 25 insertions(+), 130 deletions(-) delete mode 100644 src/components/ui/Radio.tsx diff --git a/src/components/settings/AppearanceShims.tsx b/src/components/settings/AppearanceShims.tsx index 7e4568dd..737dba5f 100644 --- a/src/components/settings/AppearanceShims.tsx +++ b/src/components/settings/AppearanceShims.tsx @@ -6,7 +6,7 @@ import pSBC from "shade-blend-color"; import { Text } from "preact-i18n"; -import { Checkbox, ColourSwatches, ComboBox } from "@revoltchat/ui"; +import { Checkbox, ColourSwatches, ComboBox, Radio } from "@revoltchat/ui"; import TextAreaAutoSize from "../../lib/TextAreaAutoSize"; @@ -21,7 +21,6 @@ import { MONOSPACE_FONT_KEYS, } from "../../context/Theme"; -import Radio from "../ui/Radio"; import CategoryButton from "../ui/fluent/CategoryButton"; import { EmojiSelector } from "./appearance/EmojiSelector"; import { ThemeBaseSelector } from "./appearance/ThemeBaseSelector"; @@ -117,19 +116,24 @@ export const DisplayCompactShim = () => {
+ } description={ } - checked> - - + value={true} + /> + } description={ } - disabled> - - + value={false} + disabled + />
); diff --git a/src/components/ui/Radio.tsx b/src/components/ui/Radio.tsx deleted file mode 100644 index 5681ec0c..00000000 --- a/src/components/ui/Radio.tsx +++ /dev/null @@ -1,112 +0,0 @@ -import { Circle } from "@styled-icons/boxicons-regular"; -import styled, { css } from "styled-components/macro"; - -import { Children } from "../../types/Preact"; - -interface Props { - children: Children; - description?: Children; - - checked?: boolean; - disabled?: boolean; - onSelect?: () => void; -} - -interface BaseProps { - selected: boolean; -} - -const RadioBase = styled.label` - gap: 4px; - z-index: 1; - padding: 4px; - display: flex; - cursor: pointer; - align-items: center; - - font-size: 1rem; - font-weight: 600; - user-select: none; - transition: 0.2s ease all; - border-radius: var(--border-radius); - - &:hover { - background: var(--hover); - } - - > input { - display: none; - } - - > div { - margin: 4px; - width: 24px; - height: 24px; - display: grid; - place-items: center; - background: var(--foreground); - border-radius: var(--border-radius-half); - - svg { - color: var(--foreground); - /*stroke-width: 2;*/ - } - } - - ${(props) => - props.selected && - css` - color: white; - cursor: default; - background: var(--accent); - - > div { - background: white; - } - - > div svg { - color: var(--accent); - } - - &:hover { - background: var(--accent); - } - `} -`; - -const RadioDescription = styled.span` - font-size: 0.8em; - font-weight: 400; - color: var(--secondary-foreground); - - ${(props) => - props.selected && - css` - color: white; - `} -`; - -export default function Radio(props: Props) { - const selected = props.checked ?? false; - return ( - - !props.disabled && props.onSelect && props.onSelect() - }> -
- -
- - - {props.children} - {props.description && ( - - {props.description} - - )} - -
- ); -} diff --git a/src/context/intermediate/modals/Prompt.tsx b/src/context/intermediate/modals/Prompt.tsx index 6d0d8966..74c11b98 100644 --- a/src/context/intermediate/modals/Prompt.tsx +++ b/src/context/intermediate/modals/Prompt.tsx @@ -7,7 +7,7 @@ import styles from "./Prompt.module.scss"; import { Text } from "preact-i18n"; import { useContext, useEffect, useState } from "preact/hooks"; -import { InputBox } from "@revoltchat/ui"; +import { InputBox, Radio } from "@revoltchat/ui"; import { TextReact } from "../../../lib/i18n"; @@ -15,7 +15,6 @@ import Message from "../../../components/common/messaging/Message"; import UserIcon from "../../../components/common/user/UserIcon"; import Modal, { Action } from "../../../components/ui/Modal"; import Overline from "../../../components/ui/Overline"; -import Radio from "../../../components/ui/Radio"; import { Children } from "../../../types/Preact"; import { AppContext } from "../../revoltjs/RevoltClient"; import { takeError } from "../../revoltjs/util"; @@ -457,15 +456,19 @@ export const SpecialPromptModal = observer((props: SpecialProps) => { setType("Text")}> - - + title={ + + } + value={type === "Text"} + onSelect={() => setType("Text")} + /> setType("Voice")}> - - + title={ + + } + value={type === "Voice"} + onSelect={() => setType("Voice")} + /> From b4777e98168342381648257ec813e7a35174cee9 Mon Sep 17 00:00:00 2001 From: Paul Makles Date: Mon, 30 May 2022 12:40:01 +0100 Subject: [PATCH 028/151] feat(@ui): migrate line divider, preloader and save status --- .../common/messaging/attachments/TextFile.tsx | 5 +- .../common/messaging/embed/EmbedInvite.tsx | 3 +- src/components/navigation/right/Search.tsx | 3 +- src/components/ui/LineDivider.tsx | 9 -- src/components/ui/Preloader.tsx | 104 ------------------ src/components/ui/SaveStatus.tsx | 32 ------ src/context/index.tsx | 8 +- .../intermediate/modals/Onboarding.tsx | 3 +- .../intermediate/popovers/UserProfile.tsx | 3 +- src/context/revoltjs/FileUploads.tsx | 3 +- src/context/revoltjs/RequiresOnline.tsx | 2 +- src/context/revoltjs/RevoltClient.tsx | 6 +- src/lib/ContextMenus.tsx | 3 +- src/lib/contextmenu/CMNotifications.tsx | 4 +- src/pages/app.tsx | 3 +- src/pages/channels/messaging/MessageArea.tsx | 4 +- .../channels/messaging/MessageRenderer.tsx | 12 +- src/pages/discover/Discover.tsx | 3 +- src/pages/invite/Invite.tsx | 4 +- src/pages/invite/InviteBot.tsx | 3 +- src/pages/login/forms/CaptchaBlock.tsx | 4 +- src/pages/login/forms/Form.tsx | 7 +- src/pages/login/forms/FormVerify.tsx | 5 +- src/pages/settings/GenericSettings.tsx | 3 +- src/pages/settings/ServerSettings.tsx | 5 +- src/pages/settings/Settings.tsx | 4 +- src/pages/settings/panes/Sessions.tsx | 3 +- src/pages/settings/server/Bans.tsx | 3 +- src/pages/settings/server/Categories.tsx | 7 +- src/pages/settings/server/Invites.tsx | 3 +- 30 files changed, 55 insertions(+), 206 deletions(-) delete mode 100644 src/components/ui/LineDivider.tsx delete mode 100644 src/components/ui/Preloader.tsx delete mode 100644 src/components/ui/SaveStatus.tsx diff --git a/src/components/common/messaging/attachments/TextFile.tsx b/src/components/common/messaging/attachments/TextFile.tsx index b967c11a..b119bcae 100644 --- a/src/components/common/messaging/attachments/TextFile.tsx +++ b/src/components/common/messaging/attachments/TextFile.tsx @@ -5,15 +5,14 @@ import styles from "./Attachment.module.scss"; import { Text } from "preact-i18n"; import { useContext, useEffect, useState } from "preact/hooks"; +import { Button, Preloader } from "@revoltchat/ui"; + import RequiresOnline from "../../../../context/revoltjs/RequiresOnline"; import { AppContext, StatusContext, } from "../../../../context/revoltjs/RevoltClient"; -import Preloader from "../../../ui/Preloader"; -import { Button } from "@revoltchat/ui"; - interface Props { attachment: API.File; } diff --git a/src/components/common/messaging/embed/EmbedInvite.tsx b/src/components/common/messaging/embed/EmbedInvite.tsx index c8566f56..45d6c538 100644 --- a/src/components/common/messaging/embed/EmbedInvite.tsx +++ b/src/components/common/messaging/embed/EmbedInvite.tsx @@ -7,7 +7,7 @@ import styled, { css } from "styled-components/macro"; import { useContext, useEffect, useState } from "preact/hooks"; -import { Button } from "@revoltchat/ui"; +import { Button, Preloader } from "@revoltchat/ui"; import { isTouchscreenDevice } from "../../../../lib/isTouchscreenDevice"; @@ -20,7 +20,6 @@ import { takeError } from "../../../../context/revoltjs/util"; import ServerIcon from "../../../../components/common/ServerIcon"; import Overline from "../../../ui/Overline"; -import Preloader from "../../../ui/Preloader"; const EmbedInviteBase = styled.div` width: 400px; diff --git a/src/components/navigation/right/Search.tsx b/src/components/navigation/right/Search.tsx index 94576ff9..2f021471 100644 --- a/src/components/navigation/right/Search.tsx +++ b/src/components/navigation/right/Search.tsx @@ -5,13 +5,12 @@ import styled from "styled-components/macro"; import { Text } from "preact-i18n"; import { useEffect, useState } from "preact/hooks"; -import { Button, InputBox } from "@revoltchat/ui"; +import { Button, InputBox, Preloader } from "@revoltchat/ui"; import { useClient } from "../../../context/revoltjs/RevoltClient"; import Message from "../../common/messaging/Message"; import Overline from "../../ui/Overline"; -import Preloader from "../../ui/Preloader"; import { GenericSidebarBase, GenericSidebarList } from "../SidebarBase"; type SearchState = diff --git a/src/components/ui/LineDivider.tsx b/src/components/ui/LineDivider.tsx deleted file mode 100644 index b158c8ce..00000000 --- a/src/components/ui/LineDivider.tsx +++ /dev/null @@ -1,9 +0,0 @@ -import styled from "styled-components/macro"; - -export default styled.div` - height: 0; - opacity: 0.6; - flex-shrink: 0; - margin: 8px 15px; - border-top: 1px solid var(--tertiary-foreground); -`; diff --git a/src/components/ui/Preloader.tsx b/src/components/ui/Preloader.tsx deleted file mode 100644 index 8eed522c..00000000 --- a/src/components/ui/Preloader.tsx +++ /dev/null @@ -1,104 +0,0 @@ -import styled, { keyframes } from "styled-components/macro"; - -const skSpinner = keyframes` - 0%, 80%, 100% { - -webkit-transform: scale(0); - transform: scale(0); - } - 40% { - -webkit-transform: scale(1.0); - transform: scale(1.0); - } -`; - -const prRing = keyframes` - 0% { - transform: rotate(0deg); - } - 100% { - transform: rotate(360deg); - } -`; - -const PreloaderBase = styled.div` - width: 100%; - height: 100%; - - display: flex; - align-items: center; - justify-content: center; - - .spinner { - width: 58px; - display: flex; - text-align: center; - margin: 100px auto 0; - justify-content: space-between; - } - - .spinner > div { - width: 14px; - height: 14px; - background-color: var(--tertiary-foreground); - - border-radius: 100%; - display: inline-block; - animation: ${skSpinner} 1.4s infinite ease-in-out both; - } - - .spinner div:nth-child(1) { - animation-delay: -0.32s; - } - - .spinner div:nth-child(2) { - animation-delay: -0.16s; - } - - .ring { - display: inline-block; - position: relative; - width: 48px; - height: 52px; - } - - .ring div { - width: 32px; - margin: 8px; - height: 32px; - display: block; - position: absolute; - box-sizing: border-box; - border: 2px solid #fff; - border-radius: var(--border-radius-half); - animation: ${prRing} 1.2s cubic-bezier(0.5, 0, 0.5, 1) infinite; - border-color: #fff transparent transparent transparent; - } - - .ring div:nth-child(1) { - animation-delay: -0.45s; - } - - .ring div:nth-child(2) { - animation-delay: -0.3s; - } - - .ring div:nth-child(3) { - animation-delay: -0.15s; - } -`; - -interface Props { - type: "spinner" | "ring"; -} - -export default function Preloader({ type }: Props) { - return ( - -
-
-
-
-
- - ); -} diff --git a/src/components/ui/SaveStatus.tsx b/src/components/ui/SaveStatus.tsx deleted file mode 100644 index 6e813cd6..00000000 --- a/src/components/ui/SaveStatus.tsx +++ /dev/null @@ -1,32 +0,0 @@ -import { Check, CloudUpload } from "@styled-icons/boxicons-regular"; -import { Pencil } from "@styled-icons/boxicons-solid"; -import styled from "styled-components/macro"; - -const StatusBase = styled.div` - gap: 4px; - padding: 4px; - display: flex; - align-items: center; - text-transform: capitalize; -`; - -export type EditStatus = "saved" | "editing" | "saving"; -interface Props { - status: EditStatus; -} - -export default function SaveStatus({ status }: Props) { - return ( - - {status === "saved" ? ( - - ) : status === "editing" ? ( - - ) : ( - - )} - {/* FIXME: add i18n */} - {status} - - ); -} diff --git a/src/context/index.tsx b/src/context/index.tsx index 0d48a5bb..277f0126 100644 --- a/src/context/index.tsx +++ b/src/context/index.tsx @@ -4,11 +4,15 @@ import { ContextMenuTrigger } from "preact-context-menu"; import { Text } from "preact-i18n"; import { useEffect, useState } from "preact/hooks"; -import { LinkProvider, TextProvider, TrigProvider } from "@revoltchat/ui"; +import { + LinkProvider, + Preloader, + TextProvider, + TrigProvider, +} from "@revoltchat/ui"; import { hydrateState } from "../mobx/State"; -import Preloader from "../components/ui/Preloader"; import { Children } from "../types/Preact"; import Locale from "./Locale"; import Theme from "./Theme"; diff --git a/src/context/intermediate/modals/Onboarding.tsx b/src/context/intermediate/modals/Onboarding.tsx index 9e6c492c..af21e930 100644 --- a/src/context/intermediate/modals/Onboarding.tsx +++ b/src/context/intermediate/modals/Onboarding.tsx @@ -4,9 +4,8 @@ import styles from "./Onboarding.module.scss"; import { Text } from "preact-i18n"; import { useState } from "preact/hooks"; -import { Button } from "@revoltchat/ui"; +import { Button, Preloader } from "@revoltchat/ui"; -import Preloader from "../../../components/ui/Preloader"; import wideSVG from "/assets/wide.svg"; import FormField from "../../../pages/login/FormField"; diff --git a/src/context/intermediate/popovers/UserProfile.tsx b/src/context/intermediate/popovers/UserProfile.tsx index a1d9088e..e14692f4 100644 --- a/src/context/intermediate/popovers/UserProfile.tsx +++ b/src/context/intermediate/popovers/UserProfile.tsx @@ -15,7 +15,7 @@ import styles from "./UserProfile.module.scss"; import { Localizer, Text } from "preact-i18n"; import { useContext, useEffect, useLayoutEffect, useState } from "preact/hooks"; -import { Button, IconButton } from "@revoltchat/ui"; +import { Button, IconButton, Preloader } from "@revoltchat/ui"; import { noop } from "../../../lib/js"; @@ -29,7 +29,6 @@ import UserStatus from "../../../components/common/user/UserStatus"; import Markdown from "../../../components/markdown/Markdown"; import Modal from "../../../components/ui/Modal"; import Overline from "../../../components/ui/Overline"; -import Preloader from "../../../components/ui/Preloader"; import { ClientStatus, StatusContext, diff --git a/src/context/revoltjs/FileUploads.tsx b/src/context/revoltjs/FileUploads.tsx index c08ec266..db246144 100644 --- a/src/context/revoltjs/FileUploads.tsx +++ b/src/context/revoltjs/FileUploads.tsx @@ -7,11 +7,10 @@ import classNames from "classnames"; import { Text } from "preact-i18n"; import { useContext, useEffect, useState } from "preact/hooks"; -import { IconButton } from "@revoltchat/ui"; +import { IconButton, Preloader } from "@revoltchat/ui"; import { determineFileSize } from "../../lib/fileSize"; -import Preloader from "../../components/ui/Preloader"; import { useIntermediate } from "../intermediate/Intermediate"; import { AppContext } from "./RevoltClient"; import { takeError } from "./util"; diff --git a/src/context/revoltjs/RequiresOnline.tsx b/src/context/revoltjs/RequiresOnline.tsx index f3595a5d..4400bcc6 100644 --- a/src/context/revoltjs/RequiresOnline.tsx +++ b/src/context/revoltjs/RequiresOnline.tsx @@ -4,7 +4,7 @@ import styled from "styled-components/macro"; import { Text } from "preact-i18n"; import { useContext } from "preact/hooks"; -import Preloader from "../../components/ui/Preloader"; +import { Preloader } from "@revoltchat/ui"; import { Children } from "../../types/Preact"; import { ClientStatus, StatusContext } from "./RevoltClient"; diff --git a/src/context/revoltjs/RevoltClient.tsx b/src/context/revoltjs/RevoltClient.tsx index b4fefcce..44681b80 100644 --- a/src/context/revoltjs/RevoltClient.tsx +++ b/src/context/revoltjs/RevoltClient.tsx @@ -3,12 +3,12 @@ import { observer } from "mobx-react-lite"; import { Client } from "revolt.js"; import { createContext } from "preact"; -import { useContext, useEffect, useMemo, useState } from "preact/hooks"; +import { useContext, useEffect, useState } from "preact/hooks"; + +import { Preloader } from "@revoltchat/ui"; import { useApplicationState } from "../../mobx/State"; -import Preloader from "../../components/ui/Preloader"; - import { Children } from "../../types/Preact"; import { useIntermediate } from "../intermediate/Intermediate"; import { registerEvents } from "./events"; diff --git a/src/lib/ContextMenus.tsx b/src/lib/ContextMenus.tsx index 32b37d4d..9eeebd06 100644 --- a/src/lib/ContextMenus.tsx +++ b/src/lib/ContextMenus.tsx @@ -13,7 +13,7 @@ import { import { Text } from "preact-i18n"; import { useContext } from "preact/hooks"; -import { IconButton } from "@revoltchat/ui"; +import { IconButton, LineDivider } from "@revoltchat/ui"; import { useApplicationState } from "../mobx/State"; import { QueuedMessage } from "../mobx/stores/MessageQueue"; @@ -30,7 +30,6 @@ import CMNotifications from "./contextmenu/CMNotifications"; import Tooltip from "../components/common/Tooltip"; import UserStatus from "../components/common/user/UserStatus"; -import LineDivider from "../components/ui/LineDivider"; import { Children } from "../types/Preact"; import { internalEmit } from "./eventEmitter"; import { getRenderer } from "./renderer/Singleton"; diff --git a/src/lib/contextmenu/CMNotifications.tsx b/src/lib/contextmenu/CMNotifications.tsx index 0c235ede..ec65b54d 100644 --- a/src/lib/contextmenu/CMNotifications.tsx +++ b/src/lib/contextmenu/CMNotifications.tsx @@ -15,11 +15,11 @@ import { Server } from "revolt.js"; import { ContextMenuWithData, MenuItem } from "preact-context-menu"; import { Text } from "preact-i18n"; +import { LineDivider } from "@revoltchat/ui"; + import { useApplicationState } from "../../mobx/State"; import { NotificationState } from "../../mobx/stores/NotificationOptions"; -import LineDivider from "../../components/ui/LineDivider"; - import { Children } from "../../types/Preact"; interface Action { diff --git a/src/pages/app.tsx b/src/pages/app.tsx index ca3e1d1d..0fa90d89 100644 --- a/src/pages/app.tsx +++ b/src/pages/app.tsx @@ -2,7 +2,7 @@ import { Route, Switch } from "react-router-dom"; import { lazy, Suspense } from "preact/compat"; -import { Masks } from "@revoltchat/ui"; +import { Masks, Preloader } from "@revoltchat/ui"; import ErrorBoundary from "../lib/ErrorBoundary"; import FakeClient from "../lib/FakeClient"; @@ -10,7 +10,6 @@ import FakeClient from "../lib/FakeClient"; import Context from "../context"; import { CheckAuth } from "../context/revoltjs/CheckAuth"; -import Preloader from "../components/ui/Preloader"; import Invite from "./invite/Invite"; const Login = lazy(() => import("./login/Login")); diff --git a/src/pages/channels/messaging/MessageArea.tsx b/src/pages/channels/messaging/MessageArea.tsx index e954eac3..4a1e031e 100644 --- a/src/pages/channels/messaging/MessageArea.tsx +++ b/src/pages/channels/messaging/MessageArea.tsx @@ -16,6 +16,8 @@ import { useState, } from "preact/hooks"; +import { Preloader } from "@revoltchat/ui"; + import { defer } from "../../../lib/defer"; import { internalEmit, internalSubscribe } from "../../../lib/eventEmitter"; import { getRenderer } from "../../../lib/renderer/Singleton"; @@ -28,8 +30,6 @@ import { StatusContext, } from "../../../context/revoltjs/RevoltClient"; -import Preloader from "../../../components/ui/Preloader"; - import ConversationStart from "./ConversationStart"; import MessageRenderer from "./MessageRenderer"; diff --git a/src/pages/channels/messaging/MessageRenderer.tsx b/src/pages/channels/messaging/MessageRenderer.tsx index 1813190a..6195e29b 100644 --- a/src/pages/channels/messaging/MessageRenderer.tsx +++ b/src/pages/channels/messaging/MessageRenderer.tsx @@ -1,5 +1,6 @@ /* eslint-disable react-hooks/rules-of-hooks */ import { X } from "@styled-icons/boxicons-regular"; +import dayjs from "dayjs"; import isEqual from "lodash.isequal"; import { observer } from "mobx-react-lite"; import { API } from "revolt.js"; @@ -11,7 +12,7 @@ import { decodeTime } from "ulid"; import { Text } from "preact-i18n"; import { useEffect, useState } from "preact/hooks"; -import { MessageDivider } from "@revoltchat/ui"; +import { MessageDivider, Preloader } from "@revoltchat/ui"; import { internalSubscribe, internalEmit } from "../../../lib/eventEmitter"; import { ChannelRenderer } from "../../../lib/renderer/Singleton"; @@ -23,11 +24,9 @@ import { useClient } from "../../../context/revoltjs/RevoltClient"; import Message from "../../../components/common/messaging/Message"; import { SystemMessage } from "../../../components/common/messaging/SystemMessage"; -import Preloader from "../../../components/ui/Preloader"; import { Children } from "../../../types/Preact"; import ConversationStart from "./ConversationStart"; import MessageEditor from "./MessageEditor"; -import dayjs from "dayjs"; interface Props { last_id?: string; @@ -126,7 +125,12 @@ export default observer(({ last_id, renderer, highlight }: Props) => { } if (unread || date) { - render.push(); + render.push( + , + ); head = true; } diff --git a/src/pages/discover/Discover.tsx b/src/pages/discover/Discover.tsx index d596ea72..7894dc32 100644 --- a/src/pages/discover/Discover.tsx +++ b/src/pages/discover/Discover.tsx @@ -5,6 +5,8 @@ import styled, { css } from "styled-components/macro"; import { useEffect, useMemo, useRef, useState } from "preact/hooks"; +import { Preloader } from "@revoltchat/ui"; + import { isTouchscreenDevice } from "../../lib/isTouchscreenDevice"; import { useApplicationState } from "../../mobx/State"; @@ -13,7 +15,6 @@ import { Overrides } from "../../context/Theme"; import { useIntermediate } from "../../context/intermediate/Intermediate"; import Header from "../../components/ui/Header"; -import Preloader from "../../components/ui/Preloader"; const Container = styled.div` flex-grow: 1; diff --git a/src/pages/invite/Invite.tsx b/src/pages/invite/Invite.tsx index 363e1a39..2e28da93 100644 --- a/src/pages/invite/Invite.tsx +++ b/src/pages/invite/Invite.tsx @@ -7,9 +7,8 @@ import styles from "./Invite.module.scss"; import { Text } from "preact-i18n"; import { useContext, useEffect, useState } from "preact/hooks"; -import { Button } from "@revoltchat/ui"; +import { Button, Preloader } from "@revoltchat/ui"; -import { defer } from "../../lib/defer"; import { TextReact } from "../../lib/i18n"; import { useApplicationState } from "../../mobx/State"; @@ -25,7 +24,6 @@ import { takeError } from "../../context/revoltjs/util"; import ServerIcon from "../../components/common/ServerIcon"; import UserIcon from "../../components/common/user/UserIcon"; import Overline from "../../components/ui/Overline"; -import Preloader from "../../components/ui/Preloader"; export default function Invite() { const history = useHistory(); diff --git a/src/pages/invite/InviteBot.tsx b/src/pages/invite/InviteBot.tsx index 6e573e5d..72b09fcf 100644 --- a/src/pages/invite/InviteBot.tsx +++ b/src/pages/invite/InviteBot.tsx @@ -4,14 +4,13 @@ import styled from "styled-components/macro"; import { useEffect, useState } from "preact/hooks"; -import { Button, ComboBox } from "@revoltchat/ui"; +import { Button, ComboBox, Preloader } from "@revoltchat/ui"; import { useClient } from "../../context/revoltjs/RevoltClient"; import UserIcon from "../../components/common/user/UserIcon"; import Markdown from "../../components/markdown/Markdown"; import Overline from "../../components/ui/Overline"; -import Preloader from "../../components/ui/Preloader"; import Tip from "../../components/ui/Tip"; const BotInfo = styled.div` diff --git a/src/pages/login/forms/CaptchaBlock.tsx b/src/pages/login/forms/CaptchaBlock.tsx index 91124b38..9dc8888c 100644 --- a/src/pages/login/forms/CaptchaBlock.tsx +++ b/src/pages/login/forms/CaptchaBlock.tsx @@ -5,9 +5,9 @@ import styles from "../Login.module.scss"; import { Text } from "preact-i18n"; import { useEffect } from "preact/hooks"; -import { useApplicationState } from "../../../mobx/State"; +import { Preloader } from "@revoltchat/ui"; -import Preloader from "../../../components/ui/Preloader"; +import { useApplicationState } from "../../../mobx/State"; export interface CaptchaProps { onSuccess: (token?: string) => void; diff --git a/src/pages/login/forms/Form.tsx b/src/pages/login/forms/Form.tsx index 3f7e32e8..8bc2b224 100644 --- a/src/pages/login/forms/Form.tsx +++ b/src/pages/login/forms/Form.tsx @@ -6,17 +6,16 @@ import styles from "../Login.module.scss"; import { Text } from "preact-i18n"; import { useState } from "preact/hooks"; -import { Button } from "@revoltchat/ui"; +import { Button, Preloader } from "@revoltchat/ui"; +import { Tip } from "@revoltchat/ui"; import { useApplicationState } from "../../../mobx/State"; import { takeError } from "../../../context/revoltjs/util"; -import Overline from "../../../components/ui/Overline"; -import Preloader from "../../../components/ui/Preloader"; import WaveSVG from "../../settings/assets/wave.svg"; -import { Tip } from "@revoltchat/ui"; +import Overline from "../../../components/ui/Overline"; import FormField from "../FormField"; import { CaptchaBlock, CaptchaProps } from "./CaptchaBlock"; import { MailProvider } from "./MailProvider"; diff --git a/src/pages/login/forms/FormVerify.tsx b/src/pages/login/forms/FormVerify.tsx index f7ecd1b9..b2145f8a 100644 --- a/src/pages/login/forms/FormVerify.tsx +++ b/src/pages/login/forms/FormVerify.tsx @@ -2,14 +2,13 @@ import { useHistory, useParams } from "react-router-dom"; import { useContext, useEffect, useState } from "preact/hooks"; +import { Preloader } from "@revoltchat/ui"; + import { useApplicationState } from "../../../mobx/State"; -import { AppContext } from "../../../context/revoltjs/RevoltClient"; import { takeError } from "../../../context/revoltjs/util"; import Overline from "../../../components/ui/Overline"; -import Preloader from "../../../components/ui/Preloader"; - import { Form } from "./Form"; export function FormResend() { diff --git a/src/pages/settings/GenericSettings.tsx b/src/pages/settings/GenericSettings.tsx index c8b90d53..8e3721a9 100644 --- a/src/pages/settings/GenericSettings.tsx +++ b/src/pages/settings/GenericSettings.tsx @@ -13,7 +13,7 @@ import { useState, } from "preact/hooks"; -import { IconButton } from "@revoltchat/ui"; +import { IconButton, LineDivider } from "@revoltchat/ui"; import { isTouchscreenDevice } from "../../lib/isTouchscreenDevice"; @@ -22,7 +22,6 @@ import { useApplicationState } from "../../mobx/State"; import ButtonItem from "../../components/navigation/items/ButtonItem"; import Category from "../../components/ui/Category"; import Header from "../../components/ui/Header"; -import LineDivider from "../../components/ui/LineDivider"; import { Children } from "../../types/Preact"; interface Props { diff --git a/src/pages/settings/ServerSettings.tsx b/src/pages/settings/ServerSettings.tsx index 61a34448..f495329e 100644 --- a/src/pages/settings/ServerSettings.tsx +++ b/src/pages/settings/ServerSettings.tsx @@ -13,13 +13,12 @@ import { Route, Switch, useHistory, useParams } from "react-router-dom"; import styles from "./Settings.module.scss"; import { Text } from "preact-i18n"; +import { LineDivider } from "@revoltchat/ui"; + import { useIntermediate } from "../../context/intermediate/Intermediate"; import RequiresOnline from "../../context/revoltjs/RequiresOnline"; import { useClient } from "../../context/revoltjs/RevoltClient"; -import Category from "../../components/ui/Category"; -import LineDivider from "../../components/ui/LineDivider"; - import ButtonItem from "../../components/navigation/items/ButtonItem"; import { GenericSettings } from "./GenericSettings"; import { Bans } from "./server/Bans"; diff --git a/src/pages/settings/Settings.tsx b/src/pages/settings/Settings.tsx index a3a0619d..28743654 100644 --- a/src/pages/settings/Settings.tsx +++ b/src/pages/settings/Settings.tsx @@ -29,6 +29,8 @@ import { openContextMenu } from "preact-context-menu"; import { Text } from "preact-i18n"; import { useContext } from "preact/hooks"; +import { LineDivider } from "@revoltchat/ui"; + import { useApplicationState } from "../../mobx/State"; import { useIntermediate } from "../../context/intermediate/Intermediate"; @@ -38,8 +40,6 @@ import { AppContext, LogOutContext } from "../../context/revoltjs/RevoltClient"; import UserIcon from "../../components/common/user/UserIcon"; import { Username } from "../../components/common/user/UserShort"; import UserStatus from "../../components/common/user/UserStatus"; -import LineDivider from "../../components/ui/LineDivider"; - import ButtonItem from "../../components/navigation/items/ButtonItem"; import { GIT_BRANCH, GIT_REVISION, REPO_URL } from "../../revision"; import { APP_VERSION } from "../../version"; diff --git a/src/pages/settings/panes/Sessions.tsx b/src/pages/settings/panes/Sessions.tsx index 24bb1bb2..8905f38f 100644 --- a/src/pages/settings/panes/Sessions.tsx +++ b/src/pages/settings/panes/Sessions.tsx @@ -18,13 +18,12 @@ import styles from "./Panes.module.scss"; import { Text } from "preact-i18n"; import { useContext, useEffect, useState } from "preact/hooks"; -import { Button } from "@revoltchat/ui"; +import { Button, Preloader } from "@revoltchat/ui"; import { dayjs } from "../../../context/Locale"; import { useIntermediate } from "../../../context/intermediate/Intermediate"; import { AppContext } from "../../../context/revoltjs/RevoltClient"; -import Preloader from "../../../components/ui/Preloader"; import Tip from "../../../components/ui/Tip"; import CategoryButton from "../../../components/ui/fluent/CategoryButton"; diff --git a/src/pages/settings/server/Bans.tsx b/src/pages/settings/server/Bans.tsx index 2613a194..ed458946 100644 --- a/src/pages/settings/server/Bans.tsx +++ b/src/pages/settings/server/Bans.tsx @@ -8,10 +8,9 @@ import styles from "./Panes.module.scss"; import { Text } from "preact-i18n"; import { useEffect, useMemo, useState } from "preact/hooks"; -import { IconButton, InputBox } from "@revoltchat/ui"; +import { IconButton, InputBox, Preloader } from "@revoltchat/ui"; import UserIcon from "../../../components/common/user/UserIcon"; -import Preloader from "../../../components/ui/Preloader"; interface InnerProps { ban: API.ServerBan; diff --git a/src/pages/settings/server/Categories.tsx b/src/pages/settings/server/Categories.tsx index 5dd9e74c..9d73dd26 100644 --- a/src/pages/settings/server/Categories.tsx +++ b/src/pages/settings/server/Categories.tsx @@ -8,6 +8,8 @@ import { ulid } from "ulid"; import { Text } from "preact-i18n"; import { useCallback, useEffect, useMemo, useState } from "preact/hooks"; +import { SaveStatus } from "@revoltchat/ui"; + import { useAutosave } from "../../../lib/debounce"; import { Draggable, Droppable } from "../../../lib/dnd"; import { noop } from "../../../lib/js"; @@ -15,7 +17,6 @@ import { noop } from "../../../lib/js"; import { useIntermediate } from "../../../context/intermediate/Intermediate"; import ChannelIcon from "../../../components/common/ChannelIcon"; -import SaveStatus, { EditStatus } from "../../../components/ui/SaveStatus"; const KanbanEntry = styled.div` padding: 2px 4px; @@ -132,7 +133,9 @@ interface Props { } export const Categories = observer(({ server }: Props) => { - const [status, setStatus] = useState("saved"); + const [status, setStatus] = useState<"saved" | "editing" | "saving">( + "saved", + ); const [categories, setCategories] = useState( server.categories ?? [], ); diff --git a/src/pages/settings/server/Invites.tsx b/src/pages/settings/server/Invites.tsx index 204c3a94..cbd6abbb 100644 --- a/src/pages/settings/server/Invites.tsx +++ b/src/pages/settings/server/Invites.tsx @@ -7,13 +7,12 @@ import styles from "./Panes.module.scss"; import { Text } from "preact-i18n"; import { useEffect, useState } from "preact/hooks"; -import { IconButton } from "@revoltchat/ui"; +import { IconButton, Preloader } from "@revoltchat/ui"; import { getChannelName } from "../../../context/revoltjs/util"; import UserIcon from "../../../components/common/user/UserIcon"; import { Username } from "../../../components/common/user/UserShort"; -import Preloader from "../../../components/ui/Preloader"; interface InnerProps { invite: API.Invite; From f3bdbe52d9cd8489f9ba49a7ee5ba44b8f0302cb Mon Sep 17 00:00:00 2001 From: Paul Makles Date: Mon, 30 May 2022 12:47:13 +0100 Subject: [PATCH 029/151] feat(@ui): migrate textarea and tip --- src/components/ui/TextArea.module.scss | 31 -- src/components/ui/TextArea.tsx | 58 ---- src/components/ui/Tip.tsx | 72 ----- src/lib/TextAreaAutoSize.tsx | 3 +- src/pages/invite/InviteBot.tsx | 7 +- src/pages/settings/panes/Account.tsx | 4 +- src/pages/settings/panes/Audio.tsx | 7 +- src/pages/settings/panes/Languages.tsx | 20 +- src/pages/settings/panes/MyBots.tsx | 7 +- src/pages/settings/panes/Native.tsx | 5 +- src/pages/settings/panes/Plugins.tsx | 6 +- src/pages/settings/panes/Profile.tsx | 15 +- src/pages/settings/panes/Sessions.tsx | 14 +- src/pages/settings/panes/ThemeShop.tsx | 399 ------------------------- 14 files changed, 41 insertions(+), 607 deletions(-) delete mode 100644 src/components/ui/TextArea.module.scss delete mode 100644 src/components/ui/TextArea.tsx delete mode 100644 src/components/ui/Tip.tsx delete mode 100644 src/pages/settings/panes/ThemeShop.tsx diff --git a/src/components/ui/TextArea.module.scss b/src/components/ui/TextArea.module.scss deleted file mode 100644 index b6e1ab0f..00000000 --- a/src/components/ui/TextArea.module.scss +++ /dev/null @@ -1,31 +0,0 @@ -.container { - font-size: .875rem; - line-height: 20px; - position: relative; -} - -.textarea { - width: 100%; - white-space: pre-wrap; - - textarea::placeholder { - overflow: hidden; - white-space: nowrap; - text-overflow: ellipsis; - } -} - -.hide { - width: 100%; - overflow: hidden; - position: relative; -} - -.ghost { - width: 100%; - white-space: pre-wrap; - - top: 0; - position: absolute; - visibility: hidden; -} diff --git a/src/components/ui/TextArea.tsx b/src/components/ui/TextArea.tsx deleted file mode 100644 index 01c4e136..00000000 --- a/src/components/ui/TextArea.tsx +++ /dev/null @@ -1,58 +0,0 @@ -import styled, { css } from "styled-components/macro"; - -export interface TextAreaProps { - code?: boolean; - padding?: string; - lineHeight?: string; - hideBorder?: boolean; -} - -export const TEXT_AREA_BORDER_WIDTH = 2; -export const DEFAULT_TEXT_AREA_PADDING = 16; -export const DEFAULT_LINE_HEIGHT = 20; - -export default styled.textarea` - width: 100%; - resize: none; - display: block; - color: var(--foreground); - background: var(--secondary-background); - padding: ${(props) => props.padding ?? "var(--textarea-padding)"}; - line-height: ${(props) => - props.lineHeight ?? "var(--textarea-line-height)"}; - - ${(props) => - props.hideBorder && - css` - border: none; - `} - - ${(props) => - !props.hideBorder && - css` - border-radius: var(--border-radius); - transition: border-color 0.2s ease-in-out; - border: var(--input-border-width) solid transparent; - `} - - &:focus { - outline: none; - - ${(props) => - !props.hideBorder && - css` - border: var(--input-border-width) solid var(--accent); - `} - } - - ${(props) => - props.code - ? css` - font-family: var(--monospace-font), monospace; - ` - : css` - font-family: inherit; - `} - - font-variant-ligatures: var(--ligatures); -`; diff --git a/src/components/ui/Tip.tsx b/src/components/ui/Tip.tsx deleted file mode 100644 index 46f9e239..00000000 --- a/src/components/ui/Tip.tsx +++ /dev/null @@ -1,72 +0,0 @@ -import { InfoCircle } from "@styled-icons/boxicons-regular"; -import styled, { css } from "styled-components/macro"; - -import { Children } from "../../types/Preact"; - -interface Props { - warning?: boolean; - error?: boolean; -} - -export const Separator = styled.div` - height: 1px; - width: calc(100% - 10px); - background: var(--secondary-header); - margin: 18px auto; -`; - -export const TipBase = styled.div` - display: flex; - padding: 12px; - font-weight: 500; - overflow: hidden; - align-items: center; - - font-size: 14px; - background: var(--primary-header); - border-radius: var(--border-radius); - border: 2px solid var(--secondary-header); - - a { - cursor: pointer; - &:hover { - text-decoration: underline; - } - } - - svg { - flex-shrink: 0; - margin-inline-end: 10px; - } - - ${(props) => - props.warning && - css` - color: var(--warning); - border: 2px solid var(--warning); - background: var(--secondary-header); - `} - - ${(props) => - props.error && - css` - color: white; - border: 2px solid var(--error); - background: var(--error); - `} -`; - -export default function Tip( - props: Props & { children: Children; hideSeparator?: boolean }, -) { - const { children, hideSeparator, ...tipProps } = props; - return ( - <> - {!hideSeparator && } - - - {children} - - - ); -} diff --git a/src/lib/TextAreaAutoSize.tsx b/src/lib/TextAreaAutoSize.tsx index 51ae17b8..805d1145 100644 --- a/src/lib/TextAreaAutoSize.tsx +++ b/src/lib/TextAreaAutoSize.tsx @@ -3,7 +3,8 @@ import styled from "styled-components/macro"; import { RefObject } from "preact"; import { useEffect, useLayoutEffect, useRef } from "preact/hooks"; -import TextArea, { TextAreaProps } from "../components/ui/TextArea"; +import { TextArea } from "@revoltchat/ui"; +import type { TextAreaProps } from "@revoltchat/ui/esm/components/design/atoms/inputs/TextArea"; import { internalSubscribe } from "./eventEmitter"; import { isTouchscreenDevice } from "./isTouchscreenDevice"; diff --git a/src/pages/invite/InviteBot.tsx b/src/pages/invite/InviteBot.tsx index 72b09fcf..99a863a9 100644 --- a/src/pages/invite/InviteBot.tsx +++ b/src/pages/invite/InviteBot.tsx @@ -4,14 +4,13 @@ import styled from "styled-components/macro"; import { useEffect, useState } from "preact/hooks"; -import { Button, ComboBox, Preloader } from "@revoltchat/ui"; +import { Button, ComboBox, Preloader, Tip } from "@revoltchat/ui"; import { useClient } from "../../context/revoltjs/RevoltClient"; import UserIcon from "../../components/common/user/UserIcon"; import Markdown from "../../components/markdown/Markdown"; import Overline from "../../components/ui/Overline"; -import Tip from "../../components/ui/Tip"; const BotInfo = styled.div` gap: 12px; @@ -46,9 +45,7 @@ export default function InviteBot() { return (
- - This section is under construction. - + This section is under construction. {typeof data === "undefined" && } {data && ( <> diff --git a/src/pages/settings/panes/Account.tsx b/src/pages/settings/panes/Account.tsx index 65b4be55..d1687f1e 100644 --- a/src/pages/settings/panes/Account.tsx +++ b/src/pages/settings/panes/Account.tsx @@ -14,7 +14,7 @@ import styles from "./Panes.module.scss"; import { Text } from "preact-i18n"; import { useContext, useEffect, useState } from "preact/hooks"; -import { Button } from "@revoltchat/ui"; +import { Button, LineDivider, Tip } from "@revoltchat/ui"; import { stopPropagation } from "../../../lib/stopPropagation"; @@ -27,7 +27,6 @@ import { import Tooltip from "../../../components/common/Tooltip"; import UserIcon from "../../../components/common/user/UserIcon"; -import Tip from "../../../components/ui/Tip"; import CategoryButton from "../../../components/ui/fluent/CategoryButton"; export const Account = observer(() => { @@ -227,6 +226,7 @@ export const Account = observer(() => { + diff --git a/src/pages/settings/panes/Audio.tsx b/src/pages/settings/panes/Audio.tsx index 62227d07..3333b7ee 100644 --- a/src/pages/settings/panes/Audio.tsx +++ b/src/pages/settings/panes/Audio.tsx @@ -2,7 +2,7 @@ import styles from "./Panes.module.scss"; import { Text } from "preact-i18n"; import { useEffect, useState } from "preact/hooks"; -import { Button, ComboBox } from "@revoltchat/ui"; +import { Button, ComboBox, Tip } from "@revoltchat/ui"; import { stopPropagation } from "../../../lib/stopPropagation"; import { voiceState } from "../../../lib/vortex/VoiceState"; @@ -10,7 +10,6 @@ import { voiceState } from "../../../lib/vortex/VoiceState"; import opusSVG from "../assets/opus_logo.svg"; import Overline from "../../../components/ui/Overline"; -import Tip from "../../../components/ui/Tip"; { /*import OpusSVG from "../assets/opus_logo.svg";*/ @@ -96,13 +95,13 @@ export function Audio() { <> + - - {" "} - - - + {" "} + + + +
); diff --git a/src/pages/settings/panes/MyBots.tsx b/src/pages/settings/panes/MyBots.tsx index 10756648..c68f238b 100644 --- a/src/pages/settings/panes/MyBots.tsx +++ b/src/pages/settings/panes/MyBots.tsx @@ -11,7 +11,7 @@ import styles from "./Panes.module.scss"; import { Text } from "preact-i18n"; import { useCallback, useEffect, useState } from "preact/hooks"; -import { Button, Checkbox, InputBox } from "@revoltchat/ui"; +import { Button, Checkbox, InputBox, Tip } from "@revoltchat/ui"; import TextAreaAutoSize from "../../../lib/TextAreaAutoSize"; import { internalEmit } from "../../../lib/eventEmitter"; @@ -28,7 +28,6 @@ import AutoComplete, { import CollapsibleSection from "../../../components/common/CollapsibleSection"; import Tooltip from "../../../components/common/Tooltip"; import UserIcon from "../../../components/common/user/UserIcon"; -import Tip from "../../../components/ui/Tip"; import CategoryButton from "../../../components/ui/fluent/CategoryButton"; interface Data { @@ -465,9 +464,7 @@ function BotCard({ bot, onDelete, onUpdate }: Props) { {error && (
- - {error} - + {error}
)} diff --git a/src/pages/settings/panes/Native.tsx b/src/pages/settings/panes/Native.tsx index c02e131d..f1e75837 100644 --- a/src/pages/settings/panes/Native.tsx +++ b/src/pages/settings/panes/Native.tsx @@ -2,11 +2,10 @@ import { Refresh } from "@styled-icons/boxicons-regular"; import { useEffect, useState } from "preact/hooks"; -import { Button, Checkbox } from "@revoltchat/ui"; +import { Button, Checkbox, Tip } from "@revoltchat/ui"; import RLogo from "../assets/revolt_r.svg"; -import Tip from "../../../components/ui/Tip"; import CategoryButton from "../../../components/ui/fluent/CategoryButton"; export function Native() { @@ -27,7 +26,7 @@ export function Native() { return (
- Some options might require a restart. + Some options might require a restart.

App Behavior

{ const plugins = useApplicationState().plugins; return (
- + {plugins.list().map((plugin) => { diff --git a/src/pages/settings/panes/Profile.tsx b/src/pages/settings/panes/Profile.tsx index 45fe95fb..ff1dd44b 100644 --- a/src/pages/settings/panes/Profile.tsx +++ b/src/pages/settings/panes/Profile.tsx @@ -7,7 +7,7 @@ import styles from "./Panes.module.scss"; import { Text } from "preact-i18n"; import { useCallback, useContext, useEffect, useState } from "preact/hooks"; -import { Button } from "@revoltchat/ui"; +import { Button, LineDivider, Tip } from "@revoltchat/ui"; import TextAreaAutoSize from "../../../lib/TextAreaAutoSize"; import { useTranslation } from "../../../lib/i18n"; @@ -23,7 +23,6 @@ import { import AutoComplete, { useAutoComplete, } from "../../../components/common/AutoComplete"; -import Tip from "../../../components/ui/Tip"; export const Profile = observer(() => { const status = useContext(StatusContext); @@ -203,11 +202,15 @@ export const Profile = observer(() => {

+ + - Want to change your username?{" "} - switchPage("account")}> - Head over to your account settings. - + + Want to change your username?{" "} + switchPage("account")}> + Head over to your account settings. + +
); diff --git a/src/pages/settings/panes/Sessions.tsx b/src/pages/settings/panes/Sessions.tsx index 8905f38f..4652c069 100644 --- a/src/pages/settings/panes/Sessions.tsx +++ b/src/pages/settings/panes/Sessions.tsx @@ -18,13 +18,12 @@ import styles from "./Panes.module.scss"; import { Text } from "preact-i18n"; import { useContext, useEffect, useState } from "preact/hooks"; -import { Button, Preloader } from "@revoltchat/ui"; +import { Button, LineDivider, Preloader, Tip } from "@revoltchat/ui"; import { dayjs } from "../../../context/Locale"; import { useIntermediate } from "../../../context/intermediate/Intermediate"; import { AppContext } from "../../../context/revoltjs/RevoltClient"; -import Tip from "../../../components/ui/Tip"; import CategoryButton from "../../../components/ui/fluent/CategoryButton"; dayjs.extend(relativeTime); @@ -248,13 +247,14 @@ export function Sessions() { + - - {" "} - switchPage("account")}> - - + {" "} + switchPage("account")}> + + +
); diff --git a/src/pages/settings/panes/ThemeShop.tsx b/src/pages/settings/panes/ThemeShop.tsx deleted file mode 100644 index ea010736..00000000 --- a/src/pages/settings/panes/ThemeShop.tsx +++ /dev/null @@ -1,399 +0,0 @@ -/** - * ! DEPRECATED FILE - * ! DO NOT IMPORT - * - * Replaced by Revolt Discover - */ -import { Check } from "@styled-icons/boxicons-regular"; -import { - Star, - Brush, - Bookmark, - BarChartAlt2, -} from "@styled-icons/boxicons-solid"; -import styled from "styled-components/macro"; - -import { Text } from "preact-i18n"; -import { useEffect, useState } from "preact/hooks"; - -import { useApplicationState } from "../../../mobx/State"; - -import { Theme, generateVariables } from "../../../context/Theme"; - -import Tip from "../../../components/ui/Tip"; -import previewPath from "../assets/preview.svg"; - -import { GIT_REVISION } from "../../../revision"; - -export const fetchManifest = (): Promise => - fetch(`${import.meta.env.VITE_THEMES_URL}/manifest.json`).then((res) => - res.json(), - ); - -export const fetchTheme = (slug: string): Promise => - fetch(`${import.meta.env.VITE_THEMES_URL}/theme_${slug}.json`).then((res) => - res.json(), - ); - -export interface ThemeMetadata { - name: string; - creator: string; - commit?: string; - description: string; -} - -export type Manifest = { - generated: string; - themes: Record; -}; - -// TODO: ability to preview / display the settings set like in the appearance pane -const ThemeInfo = styled.article` - display: flex; - flex-direction: column; - gap: 10px; - padding: 1rem; - border-radius: var(--border-radius); - background: var(--secondary-background); - - &[data-loaded] { - .preview { - opacity: 1; - } - } - - .preview { - grid-area: preview; - aspect-ratio: 323 / 202; - - background-color: var(--secondary-background); - border-radius: calc(var(--border-radius) / 2); - - // prep style for later - outline: 3px solid transparent; - - // hide random svg parts, crop border on firefox - overflow: hidden; - - // hide until loaded - opacity: 0; - - // style button - border: 0; - margin: 0; - padding: 0; - - transition: 0.25s opacity, 0.25s outline; - - > * { - grid-area: 1 / 1; - } - - svg { - height: 100%; - width: 100%; - object-fit: contain; - } - - &:hover, - &:active, - &:focus-visible { - outline: 3px solid var(--tertiary-background); - } - } - - .name { - margin-top: 5px !important; - grid-area: name; - margin: 0; - } - - .creator { - grid-area: creator; - justify-self: end; - font-size: 0.75rem; - } - - .description { - margin-bottom: 5px; - grid-area: desc; - } - - .previewBox { - position: relative; - height: 100%; - width: 100%; - - .hover { - opacity: 0; - font-family: var(--font), sans-serif; - font-variant-ligatures: var(--ligatures); - font-weight: 600; - display: flex; - align-items: center; - justify-content: center; - color: white; - height: 100%; - width: 100%; - z-index: 10; - position: absolute; - background: rgba(0, 0, 0, 0.5); - cursor: pointer; - transition: opacity 0.2s ease-in-out; - - &:hover { - opacity: 1; - } - } - - > svg { - height: 100%; - } - } -`; - -const ThemeList = styled.div` - display: grid; - grid-template-columns: repeat(auto-fill, minmax(200px, 1fr)); - gap: 1rem; -`; - -const Banner = styled.div` - display: flex; - flex-direction: column; -`; - -const Category = styled.div` - display: flex; - gap: 8px; - align-items: center; - - .title { - display: flex; - align-items: center; - gap: 8px; - flex-grow: 1; - } - - .view { - font-size: 12px; - } -`; - -const ActiveTheme = styled.div` - display: flex; - flex-direction: column; - background: var(--secondary-background); - padding: 0; - border-radius: var(--border-radius); - gap: 8px; - overflow: hidden; - - .active-indicator { - display: flex; - gap: 6px; - align-items: center; - background: var(--accent); - width: 100%; - padding: 5px 10px; - font-size: 13px; - font-weight: 400; - color: white; - } - .title { - font-size: 1.2rem; - font-weight: 600; - } - - .author { - font-size: 12px; - margin-bottom: 5px; - } - - .theme { - width: 124px; - height: 80px; - background: var(--tertiary-background); - border-radius: 4px; - } - - .container { - display: flex; - gap: 16px; - padding: 10px 16px 16px; - } -`; - -const ThemedSVG = styled.svg<{ theme: Theme }>` - ${(props) => props.theme && generateVariables(props.theme)} -`; - -type ThemePreviewProps = Omit, "as"> & { - slug?: string; - theme?: Theme; - onThemeLoaded?: (theme: Theme) => void; -}; - -const ThemePreview = ({ theme, ...props }: ThemePreviewProps) => { - return ( - - ); -}; - -const ThemeShopRoot = styled.div` - display: grid; - gap: 1rem; - - h5 { - margin-bottom: 0; - } -`; - -export function ThemeShop() { - // setThemeList is for adding more / lazy loading in the future - const [themeList, setThemeList] = useState< - [string, ThemeMetadata][] | null - >(null); - const [themeData, setThemeData] = useState>({}); - - const themes = useApplicationState().settings.theme; - - async function fetchThemeList() { - const manifest = await fetchManifest(); - setThemeList( - Object.entries(manifest.themes).filter((x) => - x[1].commit ? x[1].commit === GIT_REVISION : true, - ), - ); - } - - async function getTheme(slug: string) { - const theme = await fetchTheme(slug); - setThemeData((data) => ({ ...data, [slug]: theme })); - } - - useEffect(() => { - fetchThemeList(); - }, []); - - useEffect(() => { - themeList?.forEach(([slug]) => { - getTheme(slug); - }); - }, [themeList]); - - return ( - -
- -
- {/* -
- Oops! Couldn't load the theme shop. Make sure you're - connected to the internet and try again. -
-
*/} - - The Theme Shop is currently under construction. - - - {/* FIXME INTEGRATE WITH MOBX */} - {/* -
- - -
-
-
theme svg goes here
-
-
Theme Title
-
- {" "} - Author -
-
This is a theme description.
-
-
-
- " contrast /> - -
- - -
- - - -
- - -
- - -
- - - -
- - -
- - -
- - - -
- - -
- - -
- - - -
*/} -
- - {themeList?.map(([slug, theme]) => ( - - -

{theme.name}

- {/* Maybe id's of the users should be included as well / instead? */} -
- {" "} - {theme.creator} -
-
{theme.description}
-
- ))} -
-
- ); -} From 74f8c552edf5f71b08fee889dd9a5ac4d35ab391 Mon Sep 17 00:00:00 2001 From: Paul Makles Date: Mon, 30 May 2022 12:56:47 +0100 Subject: [PATCH 030/151] feat(@ui): port category button --- src/components/settings/AppearanceShims.tsx | 12 +- src/components/ui/fluent/CategoryButton.tsx | 172 -------------------- src/pages/home/Home.tsx | 6 +- src/pages/settings/panes/Account.tsx | 3 +- src/pages/settings/panes/Feedback.tsx | 6 +- src/pages/settings/panes/MyBots.tsx | 9 +- src/pages/settings/panes/Native.tsx | 4 +- src/pages/settings/panes/Sessions.tsx | 10 +- src/sw.ts | 2 +- 9 files changed, 30 insertions(+), 194 deletions(-) delete mode 100644 src/components/ui/fluent/CategoryButton.tsx diff --git a/src/components/settings/AppearanceShims.tsx b/src/components/settings/AppearanceShims.tsx index 737dba5f..4e7ea7e8 100644 --- a/src/components/settings/AppearanceShims.tsx +++ b/src/components/settings/AppearanceShims.tsx @@ -6,7 +6,13 @@ import pSBC from "shade-blend-color"; import { Text } from "preact-i18n"; -import { Checkbox, ColourSwatches, ComboBox, Radio } from "@revoltchat/ui"; +import { + CategoryButton, + Checkbox, + ColourSwatches, + ComboBox, + Radio, +} from "@revoltchat/ui"; import TextAreaAutoSize from "../../lib/TextAreaAutoSize"; @@ -21,7 +27,6 @@ import { MONOSPACE_FONT_KEYS, } from "../../context/Theme"; -import CategoryButton from "../ui/fluent/CategoryButton"; import { EmojiSelector } from "./appearance/EmojiSelector"; import { ThemeBaseSelector } from "./appearance/ThemeBaseSelector"; @@ -54,8 +59,7 @@ export const ThemeShopShim = () => { action="chevron" description={ - } - hover> + }> diff --git a/src/components/ui/fluent/CategoryButton.tsx b/src/components/ui/fluent/CategoryButton.tsx deleted file mode 100644 index 923d1f40..00000000 --- a/src/components/ui/fluent/CategoryButton.tsx +++ /dev/null @@ -1,172 +0,0 @@ -import { - ChevronRight, - LinkExternal, - Pencil, -} from "@styled-icons/boxicons-regular"; -import styled, { css } from "styled-components/macro"; - -import { Children } from "../../../types/Preact"; - -interface BaseProps { - readonly hover?: boolean; - readonly account?: boolean; - readonly disabled?: boolean; - readonly largeDescription?: boolean; -} - -const CategoryBase = styled.div` - padding: 9.8px 12px; - border-radius: var(--border-radius); - margin-bottom: 10px; - color: var(--foreground); - background: var(--secondary-header); - gap: 12px; - display: flex; - align-items: center; - flex-direction: row; - overflow: hidden; - - > svg { - flex-shrink: 0; - } - - .action { - display: flex; - align-items: center; - } - - .content { - display: flex; - flex-grow: 1; - flex-direction: column; - font-weight: 600; - font-size: 14px; - - .title { - display: -webkit-box; - -webkit-box-orient: vertical; - -webkit-line-clamp: 2; - overflow: hidden; - } - - .description { - ${(props) => - props.largeDescription - ? css` - font-size: 14px; - ` - : css` - font-size: 11px; - `} - - display: -webkit-box; - -webkit-box-orient: vertical; - -webkit-line-clamp: 3; - overflow: hidden; - font-weight: 500; - color: var(--secondary-foreground); - - a:hover { - text-decoration: underline; - } - } - } - - ${(props) => - props.hover && - css` - cursor: pointer; - opacity: 1; - transition: 0.1s ease background-color; - - &:hover { - background: var(--secondary-background); - } - `} - - ${(props) => - props.disabled && - css` - opacity: 0.4; - /*.content, - .action { - color: var(--tertiary-foreground); - }*/ - - .action { - font-size: 14px; - } - `} - - ${(props) => - props.account && - css` - height: 54px; - - .content { - text-overflow: ellipsis; - overflow: hidden; - white-space: nowrap; - .title { - text-transform: uppercase; - font-size: 12px; - color: var(--secondary-foreground); - } - - .description { - font-size: 15px; - font-weight: 500 !important; - color: var(--foreground); - text-overflow: ellipsis; - white-space: nowrap; - overflow: hidden; - } - } - `} -`; - -interface Props extends BaseProps { - icon?: Children; - children?: Children; - description?: Children; - - onClick?: () => void; - action?: "chevron" | "external" | Children; -} - -export default function CategoryButton({ - icon, - children, - description, - account, - disabled, - onClick, - hover, - action, -}: Props) { - return ( - - {icon} -
-
{children}
- -
{description}
-
-
- {typeof action === "string" ? ( - action === "chevron" ? ( - - ) : ( - - ) - ) : ( - action - )} -
-
- ); -} diff --git a/src/pages/home/Home.tsx b/src/pages/home/Home.tsx index ef66bca9..56c5cfa1 100644 --- a/src/pages/home/Home.tsx +++ b/src/pages/home/Home.tsx @@ -17,6 +17,8 @@ import "./snow.scss"; import { Text } from "preact-i18n"; import { useContext, useMemo } from "preact/hooks"; +import { CategoryButton } from "@revoltchat/ui"; + import { isTouchscreenDevice } from "../../lib/isTouchscreenDevice"; import { useApplicationState } from "../../mobx/State"; @@ -24,10 +26,10 @@ import { useApplicationState } from "../../mobx/State"; import { useIntermediate } from "../../context/intermediate/Intermediate"; import { AppContext } from "../../context/revoltjs/RevoltClient"; -import { PageHeader } from "../../components/ui/Header"; -import CategoryButton from "../../components/ui/fluent/CategoryButton"; import wideSVG from "/assets/wide.svg"; +import { PageHeader } from "../../components/ui/Header"; + const Overlay = styled.div` display: grid; height: 100%; diff --git a/src/pages/settings/panes/Account.tsx b/src/pages/settings/panes/Account.tsx index d1687f1e..90fa2773 100644 --- a/src/pages/settings/panes/Account.tsx +++ b/src/pages/settings/panes/Account.tsx @@ -14,7 +14,7 @@ import styles from "./Panes.module.scss"; import { Text } from "preact-i18n"; import { useContext, useEffect, useState } from "preact/hooks"; -import { Button, LineDivider, Tip } from "@revoltchat/ui"; +import { Button, CategoryButton, LineDivider, Tip } from "@revoltchat/ui"; import { stopPropagation } from "../../../lib/stopPropagation"; @@ -27,7 +27,6 @@ import { import Tooltip from "../../../components/common/Tooltip"; import UserIcon from "../../../components/common/user/UserIcon"; -import CategoryButton from "../../../components/ui/fluent/CategoryButton"; export const Account = observer(() => { const { openScreen, writeClipboard } = useIntermediate(); diff --git a/src/pages/settings/panes/Feedback.tsx b/src/pages/settings/panes/Feedback.tsx index 106159db..66125beb 100644 --- a/src/pages/settings/panes/Feedback.tsx +++ b/src/pages/settings/panes/Feedback.tsx @@ -5,7 +5,7 @@ import { Link } from "react-router-dom"; import styles from "./Panes.module.scss"; import { Text } from "preact-i18n"; -import CategoryButton from "../../../components/ui/fluent/CategoryButton"; +import { CategoryButton } from "@revoltchat/ui"; export function Feedback() { return ( @@ -15,7 +15,6 @@ export function Feedback() { target="_blank" rel="noreferrer"> } description={ @@ -29,7 +28,6 @@ export function Feedback() { target="_blank" rel="noreferrer"> } description={ @@ -43,7 +41,6 @@ export function Feedback() { target="_blank" rel="noreferrer"> } description={ @@ -55,7 +52,6 @@ export function Feedback() { } description="You can report issues and discuss improvements with us directly here."> diff --git a/src/pages/settings/panes/MyBots.tsx b/src/pages/settings/panes/MyBots.tsx index c68f238b..c3a49d70 100644 --- a/src/pages/settings/panes/MyBots.tsx +++ b/src/pages/settings/panes/MyBots.tsx @@ -11,7 +11,13 @@ import styles from "./Panes.module.scss"; import { Text } from "preact-i18n"; import { useCallback, useEffect, useState } from "preact/hooks"; -import { Button, Checkbox, InputBox, Tip } from "@revoltchat/ui"; +import { + Button, + CategoryButton, + Checkbox, + InputBox, + Tip, +} from "@revoltchat/ui"; import TextAreaAutoSize from "../../../lib/TextAreaAutoSize"; import { internalEmit } from "../../../lib/eventEmitter"; @@ -28,7 +34,6 @@ import AutoComplete, { import CollapsibleSection from "../../../components/common/CollapsibleSection"; import Tooltip from "../../../components/common/Tooltip"; import UserIcon from "../../../components/common/user/UserIcon"; -import CategoryButton from "../../../components/ui/fluent/CategoryButton"; interface Data { _id: string; diff --git a/src/pages/settings/panes/Native.tsx b/src/pages/settings/panes/Native.tsx index f1e75837..da02d777 100644 --- a/src/pages/settings/panes/Native.tsx +++ b/src/pages/settings/panes/Native.tsx @@ -2,12 +2,10 @@ import { Refresh } from "@styled-icons/boxicons-regular"; import { useEffect, useState } from "preact/hooks"; -import { Button, Checkbox, Tip } from "@revoltchat/ui"; +import { Button, CategoryButton, Checkbox, Tip } from "@revoltchat/ui"; import RLogo from "../assets/revolt_r.svg"; -import CategoryButton from "../../../components/ui/fluent/CategoryButton"; - export function Native() { if (typeof window.native === "undefined") return null; /* eslint-disable react-hooks/rules-of-hooks */ diff --git a/src/pages/settings/panes/Sessions.tsx b/src/pages/settings/panes/Sessions.tsx index 4652c069..0e951f4c 100644 --- a/src/pages/settings/panes/Sessions.tsx +++ b/src/pages/settings/panes/Sessions.tsx @@ -18,14 +18,18 @@ import styles from "./Panes.module.scss"; import { Text } from "preact-i18n"; import { useContext, useEffect, useState } from "preact/hooks"; -import { Button, LineDivider, Preloader, Tip } from "@revoltchat/ui"; +import { + Button, + CategoryButton, + LineDivider, + Preloader, + Tip, +} from "@revoltchat/ui"; import { dayjs } from "../../../context/Locale"; import { useIntermediate } from "../../../context/intermediate/Intermediate"; import { AppContext } from "../../../context/revoltjs/RevoltClient"; -import CategoryButton from "../../../components/ui/fluent/CategoryButton"; - dayjs.extend(relativeTime); export function Sessions() { diff --git a/src/sw.ts b/src/sw.ts index 5daf6258..af71b0e2 100644 --- a/src/sw.ts +++ b/src/sw.ts @@ -11,7 +11,7 @@ cleanupOutdatedCaches(); // Generate list using scripts/locale.js // prettier-ignore -var locale_keys = ["af","am","ar-dz","ar-kw","ar-ly","ar-ma","ar-sa","ar-tn","ar","az","be","bg","bi","bm","bn","bo","br","bs","ca","cs","cv","cy","da","de-at","de-ch","de","dv","el","en-au","en-ca","en-gb","en-ie","en-il","en-in","en-nz","en-sg","en-tt","en","eo","es-do","es-pr","es-us","es","et","eu","fa","fi","fo","fr-ca","fr-ch","fr","fy","ga","gd","gl","gom-latn","gu","he","hi","hr","ht","hu","hy-am","id","is","it-ch","it","ja","jv","ka","kk","km","kn","ko","ku","ky","lb","lo","lt","lv","me","mi","mk","ml","mn","mr","ms-my","ms","mt","my","nb","ne","nl-be","nl","nn","oc-lnc","pa-in","pl","pt-br","pt","ro","ru","rw","sd","se","si","sk","sl","sq","sr-cyrl","sr","ss","sv-fi","sv","sw","ta","te","tet","tg","th","tk","tl-ph","tlh","tr","tzl","tzm-latn","tzm","ug-cn","uk","ur","uz-latn","uz","vi","x-pseudo","yo","zh-cn","zh-hk","zh-tw","zh","ang","ar","az","be","bg","bn","bottom","br","ca","ca@valencia","cs","cy","da","de","de_CH","el","en","en_US","enchantment","enm","eo","es","et","eu","fa","fi","fil","fr","frm","ga","got","he","hi","hr","hu","id","it","ja","ko","la","lb","leet","li","lt","lv","mk","ml","ms","mt","nb_NO","nl","owo","peo","piglatin","pl","pr","pt_BR","pt_PT","ro","ro_MD","ru","si","sk","sl","sq","sr","sv","ta","te","th","tlh-qaak","tokipona","tr","uk","vi","zh_Hans","zh_Hant"]; +var locale_keys = ["af","am","ar-dz","ar-kw","ar-ly","ar-ma","ar-sa","ar-tn","ar","az","be","bg","bi","bm","bn","bo","br","bs","ca","cs","cv","cy","da","de-at","de-ch","de","dv","el","en-au","en-ca","en-gb","en-ie","en-il","en-in","en-nz","en-sg","en-tt","en","eo","es-do","es-pr","es-us","es","et","eu","fa","fi","fo","fr-ca","fr-ch","fr","fy","ga","gd","gl","gom-latn","gu","he","hi","hr","ht","hu","hy-am","id","is","it-ch","it","ja","jv","ka","kk","km","kn","ko","ku","ky","lb","lo","lt","lv","me","mi","mk","ml","mn","mr","ms-my","ms","mt","my","nb","ne","nl-be","nl","nn","oc-lnc","pa-in","pl","pt-br","pt","ro","ru","rw","sd","se","si","sk","sl","sq","sr-cyrl","sr","ss","sv-fi","sv","sw","ta","te","tet","tg","th","tk","tl-ph","tlh","tr","tzl","tzm-latn","tzm","ug-cn","uk","ur","uz-latn","uz","vi","x-pseudo","yo","zh-cn","zh-hk","zh-tw","zh","ang","ar","az","be","bg","bn","bottom","br","ca","ca@valencia","ckb","contributors","cs","cy","da","de","de_CH","el","en","en_US","enchantment","enm","eo","es","et","eu","fa","fi","fil","fr","frm","ga","got","he","hi","hr","hu","id","it","ja","kmr","ko","la","lb","leet","li","lt","lv","mk","ml","ms","mt","nb_NO","nl","owo","peo","piglatin","pl","pr","pt_BR","pt_PT","ro","ro_MD","ru","si","sk","sl","sq","sr","sv","ta","te","th","tlh-qaak","tokipona","tr","uk","vec","vi","zh_Hans","zh_Hant"]; precacheAndRoute( self.__WB_MANIFEST.filter((entry) => { From 673efc0586431f34f5d4761429a0af8f0e28ef4c Mon Sep 17 00:00:00 2001 From: Paul Makles Date: Mon, 30 May 2022 12:57:30 +0100 Subject: [PATCH 031/151] fix: make context menu line divider compact --- src/lib/ContextMenus.tsx | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/lib/ContextMenus.tsx b/src/lib/ContextMenus.tsx index 9eeebd06..43645bda 100644 --- a/src/lib/ContextMenus.tsx +++ b/src/lib/ContextMenus.tsx @@ -480,7 +480,7 @@ export default function ContextMenus() { function pushDivider() { if (lastDivider || elements.length === 0) return; lastDivider = true; - elements.push(); + elements.push(); } if (server_list) { @@ -1033,7 +1033,7 @@ export default function ContextMenus() {
- + - + From 68b9d5ea797e7acc15e065a04ac3f7f9bfa9fd3d Mon Sep 17 00:00:00 2001 From: Paul Makles Date: Mon, 30 May 2022 14:42:09 +0100 Subject: [PATCH 032/151] feat(@ui): migrate category / overline and header --- src/components/common/messaging/Message.tsx | 9 +- .../common/messaging/embed/EmbedInvite.tsx | 10 +- src/components/common/user/UserHeader.tsx | 5 +- .../navigation/left/HomeSidebar.tsx | 25 +++-- .../navigation/left/ServerListSidebar.tsx | 3 + .../navigation/left/ServerSidebar.tsx | 8 +- src/components/navigation/right/Search.tsx | 15 +-- src/components/ui/Category.tsx | 55 ----------- src/components/ui/Header.tsx | 79 +--------------- src/components/ui/Overline.tsx | 91 ------------------- src/context/Locale.tsx | 13 ++- src/context/intermediate/modals/Input.tsx | 16 ++-- src/context/intermediate/modals/Prompt.tsx | 26 +++--- .../intermediate/popovers/CreateBot.tsx | 12 ++- .../intermediate/popovers/ModifyAccount.tsx | 14 ++- .../popovers/ServerIdentityModal.tsx | 11 +-- .../intermediate/popovers/UserPicker.tsx | 3 +- .../intermediate/popovers/UserProfile.tsx | 21 ++--- src/pages/Open.tsx | 8 +- src/pages/channels/ChannelHeader.tsx | 20 +--- src/pages/developer/Developer.tsx | 2 +- src/pages/discover/Discover.tsx | 6 +- src/pages/friends/Friends.tsx | 5 +- src/pages/home/Home.tsx | 2 +- src/pages/invite/Invite.tsx | 7 +- src/pages/invite/InviteBot.tsx | 7 +- src/pages/login/FormField.tsx | 12 ++- src/pages/login/forms/Form.tsx | 12 ++- src/pages/login/forms/FormVerify.tsx | 10 +- src/pages/settings/ChannelSettings.tsx | 2 - src/pages/settings/GenericSettings.tsx | 25 ++--- src/pages/settings/panes/Account.tsx | 1 - src/pages/settings/panes/Audio.tsx | 14 ++- src/pages/settings/server/Members.tsx | 4 +- src/pages/settings/server/Roles.tsx | 18 ++-- 35 files changed, 187 insertions(+), 384 deletions(-) delete mode 100644 src/components/ui/Category.tsx delete mode 100644 src/components/ui/Overline.tsx diff --git a/src/components/common/messaging/Message.tsx b/src/components/common/messaging/Message.tsx index 82928e8e..5962fce7 100644 --- a/src/components/common/messaging/Message.tsx +++ b/src/components/common/messaging/Message.tsx @@ -5,16 +5,17 @@ import { useTriggerEvents } from "preact-context-menu"; import { memo } from "preact/compat"; import { useEffect, useState } from "preact/hooks"; +import { Category } from "@revoltchat/ui"; + import { internalEmit } from "../../../lib/eventEmitter"; import { isTouchscreenDevice } from "../../../lib/isTouchscreenDevice"; import { QueuedMessage } from "../../../mobx/stores/MessageQueue"; +import { I18nError } from "../../../context/Locale"; import { useIntermediate } from "../../../context/intermediate/Intermediate"; import { useClient } from "../../../context/revoltjs/RevoltClient"; -import Overline from "../../ui/Overline"; - import { Children } from "../../../types/Preact"; import Markdown from "../../markdown/Markdown"; import UserIcon from "../user/UserIcon"; @@ -162,7 +163,9 @@ const Message = observer( {replacement ?? } {!queued && } {queued?.error && ( - + + + )} {message.attachments?.map((attachment, index) => ( )} - {joinError && } + {joinError && ( + + + + )} ); } diff --git a/src/components/common/user/UserHeader.tsx b/src/components/common/user/UserHeader.tsx index e1572dd2..0ab85bfd 100644 --- a/src/components/common/user/UserHeader.tsx +++ b/src/components/common/user/UserHeader.tsx @@ -7,13 +7,12 @@ import styled from "styled-components/macro"; import { openContextMenu } from "preact-context-menu"; import { Text, Localizer } from "preact-i18n"; -import { IconButton } from "@revoltchat/ui"; +import { Header, IconButton } from "@revoltchat/ui"; import { isTouchscreenDevice } from "../../../lib/isTouchscreenDevice"; import { useIntermediate } from "../../../context/intermediate/Intermediate"; -import Header from "../../ui/Header"; import Tooltip from "../Tooltip"; import UserStatus from "./UserStatus"; @@ -52,7 +51,7 @@ export default observer(({ user }: Props) => { const { writeClipboard } = useIntermediate(); return ( -
+
}> diff --git a/src/components/navigation/left/HomeSidebar.tsx b/src/components/navigation/left/HomeSidebar.tsx index bb471340..220f542c 100644 --- a/src/components/navigation/left/HomeSidebar.tsx +++ b/src/components/navigation/left/HomeSidebar.tsx @@ -1,3 +1,4 @@ +import { Plus } from "@styled-icons/boxicons-regular"; import { Home, UserDetail, @@ -11,6 +12,8 @@ import styled, { css } from "styled-components/macro"; import { Text } from "preact-i18n"; import { useContext, useEffect } from "preact/hooks"; +import { Category, IconButton } from "@revoltchat/ui"; + import ConditionalLink from "../../../lib/ConditionalLink"; import PaintCounter from "../../../lib/PaintCounter"; import { isTouchscreenDevice } from "../../../lib/isTouchscreenDevice"; @@ -20,7 +23,6 @@ import { useApplicationState } from "../../../mobx/State"; import { useIntermediate } from "../../../context/intermediate/Intermediate"; import { AppContext } from "../../../context/revoltjs/RevoltClient"; -import Category from "../../ui/Category"; import placeholderSVG from "../items/placeholder.svg"; import { GenericSidebarBase, GenericSidebarList } from "../SidebarBase"; @@ -125,15 +127,18 @@ export default observer(() => { )} - } - action={() => - openScreen({ - id: "special_input", - type: "create_group", - }) - } - /> + + + + openScreen({ + id: "special_input", + type: "create_group", + }) + }> + + + {channels.length === 0 && ( )} diff --git a/src/components/navigation/left/ServerListSidebar.tsx b/src/components/navigation/left/ServerListSidebar.tsx index 23d2b869..4d7c5381 100644 --- a/src/components/navigation/left/ServerListSidebar.tsx +++ b/src/components/navigation/left/ServerListSidebar.tsx @@ -30,11 +30,14 @@ export default observer(() => { return ( diff --git a/src/components/navigation/left/ServerSidebar.tsx b/src/components/navigation/left/ServerSidebar.tsx index 0e228fa4..4a771701 100644 --- a/src/components/navigation/left/ServerSidebar.tsx +++ b/src/components/navigation/left/ServerSidebar.tsx @@ -1,12 +1,12 @@ import { observer } from "mobx-react-lite"; import { Redirect, useParams } from "react-router"; -import { Server } from "revolt.js"; import styled, { css } from "styled-components/macro"; -import { Ref } from "preact"; import { useTriggerEvents } from "preact-context-menu"; import { useEffect } from "preact/hooks"; +import { Category } from "@revoltchat/ui"; + import ConditionalLink from "../../../lib/ConditionalLink"; import PaintCounter from "../../../lib/PaintCounter"; import { internalEmit } from "../../../lib/eventEmitter"; @@ -18,8 +18,6 @@ import { useClient } from "../../../context/revoltjs/RevoltClient"; import CollapsibleSection from "../../common/CollapsibleSection"; import ServerHeader from "../../common/ServerHeader"; -import Category from "../../ui/Category"; - import { ChannelButton } from "../items/ButtonItem"; import ConnectionStatus from "../items/ConnectionStatus"; @@ -126,7 +124,7 @@ export default observer(() => { }> + summary={{category.title}}> {channels} , ); diff --git a/src/components/navigation/right/Search.tsx b/src/components/navigation/right/Search.tsx index 2f021471..8694ab11 100644 --- a/src/components/navigation/right/Search.tsx +++ b/src/components/navigation/right/Search.tsx @@ -5,12 +5,11 @@ import styled from "styled-components/macro"; import { Text } from "preact-i18n"; import { useEffect, useState } from "preact/hooks"; -import { Button, InputBox, Preloader } from "@revoltchat/ui"; +import { Button, Category, Error, InputBox, Preloader } from "@revoltchat/ui"; import { useClient } from "../../../context/revoltjs/RevoltClient"; import Message from "../../common/messaging/Message"; -import Overline from "../../ui/Overline"; import { GenericSidebarBase, GenericSidebarList } from "../SidebarBase"; type SearchState = @@ -100,12 +99,14 @@ export function SearchSidebar({ close }: Props) { - - « back to members - - + + « back to members} + /> + + - + e.key === "Enter" && search()} diff --git a/src/components/ui/Category.tsx b/src/components/ui/Category.tsx deleted file mode 100644 index baaa50df..00000000 --- a/src/components/ui/Category.tsx +++ /dev/null @@ -1,55 +0,0 @@ -import { Plus } from "@styled-icons/boxicons-regular"; -import styled, { css } from "styled-components/macro"; - -import { Children } from "../../types/Preact"; - -const CategoryBase = styled.div>` - font-size: 12px; - font-weight: 700; - text-transform: uppercase; - - margin-top: 4px; - padding: 6px 0 6px 8px; - margin-bottom: 4px; - white-space: nowrap; - - display: flex; - align-items: center; - flex-direction: row; - justify-content: space-between; - - svg { - cursor: pointer; - } - - &:first-child { - margin-top: 0; - padding-top: 0; - } - - ${(props) => - props.variant === "uniform" && - css` - padding-top: 6px; - `} -`; - -type Props = Omit< - JSX.HTMLAttributes, - "children" | "as" | "action" -> & { - text: Children; - action?: () => void; - variant?: "default" | "uniform"; -}; - -export default function Category(props: Props) { - const { text, action, ...otherProps } = props; - - return ( - - {text} - {action && } - - ); -} diff --git a/src/components/ui/Header.tsx b/src/components/ui/Header.tsx index 4ff01ea5..860a294d 100644 --- a/src/components/ui/Header.tsx +++ b/src/components/ui/Header.tsx @@ -7,6 +7,8 @@ import { observer } from "mobx-react-lite"; import { useLocation } from "react-router-dom"; import styled, { css } from "styled-components/macro"; +import { Header } from "@revoltchat/ui"; + import { isTouchscreenDevice } from "../../lib/isTouchscreenDevice"; import { useApplicationState } from "../../mobx/State"; @@ -18,82 +20,11 @@ interface Props { topBorder?: boolean; bottomBorder?: boolean; - background?: boolean; - transparent?: boolean; + withBackground?: boolean; + withTransparency?: boolean; placement: "primary" | "secondary"; } -const Header = styled.div` - gap: 10px; - flex: 0 auto; - display: flex; - flex-shrink: 0; - padding: 0 16px; - font-weight: 600; - user-select: none; - align-items: center; - - height: var(--header-height); - - background-size: cover !important; - background-position: center !important; - - svg { - flex-shrink: 0; - } - - .menu { - margin-inline-end: 8px; - color: var(--secondary-foreground); - } - - ${(props) => - props.transparent - ? css` - background-color: rgba( - var(--primary-header-rgb), - max(var(--min-opacity), 0.75) - ); - backdrop-filter: blur(20px); - z-index: 20; - position: absolute; - width: 100%; - ` - : css` - background-color: var(--primary-header); - `} - - ${(props) => - props.background && - css` - height: 120px !important; - align-items: flex-end; - - text-shadow: 0px 0px 1px black; - `} - - ${(props) => - props.placement === "secondary" && - css` - background-color: var(--secondary-header); - padding: 14px; - `} - - ${(props) => - props.topBorder && - css` - border-start-start-radius: 8px; - `} - - ${(props) => - props.bottomBorder && - css` - border-end-start-radius: 8px; - `} -`; - -export default Header; - const IconContainer = styled.div` display: flex; align-items: center; @@ -128,7 +59,7 @@ export const PageHeader = observer( return (
{!noBurger && } diff --git a/src/components/ui/Overline.tsx b/src/components/ui/Overline.tsx deleted file mode 100644 index be34e010..00000000 --- a/src/components/ui/Overline.tsx +++ /dev/null @@ -1,91 +0,0 @@ -import styled, { css } from "styled-components/macro"; - -import { Text } from "preact-i18n"; - -import { Children } from "../../types/Preact"; - -type Props = Omit, "children" | "as"> & { - error?: string; - hover?: boolean; - block?: boolean; - spaced?: boolean; - noMargin?: boolean; - children?: Children; - type?: "default" | "subtle" | "error" | "accent"; -}; - -const OverlineBase = styled.div>` - display: inline; - transition: 0.2s ease filter; - - ${(props) => - props.hover && - css` - cursor: pointer; - transition: 0.2s ease filter; - - &:hover { - filter: brightness(1.2); - } - `} - - ${(props) => - !props.noMargin && - css` - margin: 0.4em 0; - `} - - ${(props) => - props.spaced && - css` - margin-top: 0.8em; - `} - - font-size: 14px; - font-weight: 600; - color: var(--foreground); - text-transform: uppercase; - - ${(props) => - props.type === "subtle" && - css` - font-size: 12px; - color: var(--secondary-foreground); - `} - - ${(props) => - props.type === "error" && - css` - font-size: 12px; - font-weight: 400; - color: var(--error); - `} - - ${(props) => - props.type === "accent" && - css` - font-size: 12px; - font-weight: 400; - color: var(--accent); - `} - - ${(props) => - props.block && - css` - display: block; - `} -`; - -export default function Overline(props: Props) { - return ( - - {props.children} - {props.children && props.error && <> · } - {props.error && ( - - {props.error} - - )} - - ); -} diff --git a/src/context/Locale.tsx b/src/context/Locale.tsx index ab7ce398..ce115a9b 100644 --- a/src/context/Locale.tsx +++ b/src/context/Locale.tsx @@ -5,9 +5,11 @@ import update from "dayjs/plugin/updateLocale"; import defaultsDeep from "lodash.defaultsdeep"; import { observer } from "mobx-react-lite"; -import { IntlProvider } from "preact-i18n"; +import { IntlProvider, Text } from "preact-i18n"; import { useCallback, useEffect, useState } from "preact/hooks"; +import { Error } from "@revoltchat/ui"; + import { useApplicationState } from "../mobx/State"; import { Languages } from "../../external/lang/Languages"; @@ -143,3 +145,12 @@ function transformLanguage(source: Dictionary) { return obj; } + +export function I18nError({ error, children }: { error: any; children?: any }) { + return ( + : undefined} + children={children} + /> + ); +} diff --git a/src/context/intermediate/modals/Input.tsx b/src/context/intermediate/modals/Input.tsx index 767bfa8e..446a2921 100644 --- a/src/context/intermediate/modals/Input.tsx +++ b/src/context/intermediate/modals/Input.tsx @@ -4,11 +4,11 @@ import { Server } from "revolt.js"; import { Text } from "preact-i18n"; import { useContext, useState } from "preact/hooks"; -import { InputBox } from "@revoltchat/ui"; +import { Category, InputBox } from "@revoltchat/ui"; import Modal from "../../../components/ui/Modal"; -import Overline from "../../../components/ui/Overline"; import { Children } from "../../../types/Preact"; +import { I18nError } from "../../Locale"; import { AppContext } from "../../revoltjs/RevoltClient"; import { takeError } from "../../revoltjs/util"; @@ -60,11 +60,15 @@ export function InputModal({ ]} onClose={onClose}> {field ? ( - - {field} - + + {field} + ) : ( - error && + error && ( + + + + ) )} - {error && } + {error && ( + + + + )} {content} ); @@ -385,9 +389,9 @@ export const SpecialPromptModal = observer((props: SpecialProps) => { id="app.special.modals.prompt.confirm_ban" fields={{ name: props.user?.username }} /> - + - + @@ -452,9 +456,9 @@ export const SpecialPromptModal = observer((props: SpecialProps) => { ]} content={ <> - + - + @@ -469,9 +473,9 @@ export const SpecialPromptModal = observer((props: SpecialProps) => { value={type === "Voice"} onSelect={() => setType("Voice")} /> - + - + setName(e.currentTarget.value)} @@ -527,9 +531,9 @@ export const SpecialPromptModal = observer((props: SpecialProps) => { ]} content={ <> - + - + setName(e.currentTarget.value)} diff --git a/src/context/intermediate/popovers/CreateBot.tsx b/src/context/intermediate/popovers/CreateBot.tsx index da57562b..42beded7 100644 --- a/src/context/intermediate/popovers/CreateBot.tsx +++ b/src/context/intermediate/popovers/CreateBot.tsx @@ -4,10 +4,11 @@ import { API } from "revolt.js"; import { Text } from "preact-i18n"; import { useContext, useState } from "preact/hooks"; -import Modal from "../../../components/ui/Modal"; -import Overline from "../../../components/ui/Overline"; +import { Category } from "@revoltchat/ui"; +import Modal from "../../../components/ui/Modal"; import FormField from "../../../pages/login/FormField"; +import { I18nError } from "../../Locale"; import { AppContext } from "../../revoltjs/RevoltClient"; import { takeError } from "../../revoltjs/util"; @@ -70,9 +71,10 @@ export function CreateBotModal({ onClose, onCreate }: Props) { error={errors.name?.message} /> {error && ( - - - + + {" "} + · + )} diff --git a/src/context/intermediate/popovers/ModifyAccount.tsx b/src/context/intermediate/popovers/ModifyAccount.tsx index c001a14d..6d241a90 100644 --- a/src/context/intermediate/popovers/ModifyAccount.tsx +++ b/src/context/intermediate/popovers/ModifyAccount.tsx @@ -3,9 +3,9 @@ import { SubmitHandler, useForm } from "react-hook-form"; import { Text } from "preact-i18n"; import { useContext, useState } from "preact/hooks"; -import Modal from "../../../components/ui/Modal"; -import Overline from "../../../components/ui/Overline"; +import { Category, Error } from "@revoltchat/ui"; +import Modal from "../../../components/ui/Modal"; import FormField from "../../../pages/login/FormField"; import { AppContext } from "../../revoltjs/RevoltClient"; import { takeError } from "../../revoltjs/util"; @@ -140,9 +140,13 @@ export function ModifyAccountModal({ onClose, field }: Props) { disabled={processing} /> {error && ( - - - + + + } + /> + )} diff --git a/src/context/intermediate/popovers/ServerIdentityModal.tsx b/src/context/intermediate/popovers/ServerIdentityModal.tsx index 00b2f8aa..c9136004 100644 --- a/src/context/intermediate/popovers/ServerIdentityModal.tsx +++ b/src/context/intermediate/popovers/ServerIdentityModal.tsx @@ -5,12 +5,11 @@ import styles from "./ServerIdentityModal.module.scss"; import { Text } from "preact-i18n"; import { useEffect, useState } from "preact/hooks"; -import { Button, InputBox } from "@revoltchat/ui"; +import { Button, Category, InputBox } from "@revoltchat/ui"; import { noop } from "../../../lib/js"; import Modal from "../../../components/ui/Modal"; -import Overline from "../../../components/ui/Overline"; import { FileUploader } from "../../revoltjs/FileUploads"; import { useClient } from "../../revoltjs/RevoltClient"; @@ -47,9 +46,9 @@ export const ServerIdentityModal = observer(({ server, onClose }: Props) => { onClose={onClose}>
- + - + { />
- + - + { if (v) { setSelected([...selected, x._id]); diff --git a/src/context/intermediate/popovers/UserProfile.tsx b/src/context/intermediate/popovers/UserProfile.tsx index e14692f4..701cce8c 100644 --- a/src/context/intermediate/popovers/UserProfile.tsx +++ b/src/context/intermediate/popovers/UserProfile.tsx @@ -15,7 +15,7 @@ import styles from "./UserProfile.module.scss"; import { Localizer, Text } from "preact-i18n"; import { useContext, useEffect, useLayoutEffect, useState } from "preact/hooks"; -import { Button, IconButton, Preloader } from "@revoltchat/ui"; +import { Button, Category, Error, IconButton, Preloader } from "@revoltchat/ui"; import { noop } from "../../../lib/js"; @@ -28,7 +28,6 @@ import { Username } from "../../../components/common/user/UserShort"; import UserStatus from "../../../components/common/user/UserStatus"; import Markdown from "../../../components/markdown/Markdown"; import Modal from "../../../components/ui/Modal"; -import Overline from "../../../components/ui/Overline"; import { ClientStatus, StatusContext, @@ -278,19 +277,19 @@ export const UserProfile = observer(
{flags & 1 ? ( /** ! FIXME: i18n this area */ - - User is suspended - + + + ) : undefined} {flags & 2 ? ( - - User deleted their account - + + + ) : undefined} {flags & 4 ? ( - - User is banned - + + + ) : undefined} {user.bot ? ( <> diff --git a/src/pages/Open.tsx b/src/pages/Open.tsx index bd686e79..20f9b3fa 100644 --- a/src/pages/Open.tsx +++ b/src/pages/Open.tsx @@ -4,6 +4,8 @@ import { useHistory, useParams } from "react-router-dom"; import { Text } from "preact-i18n"; import { useContext, useEffect } from "preact/hooks"; +import { Header } from "@revoltchat/ui"; + import { useIntermediate } from "../context/intermediate/Intermediate"; import { AppContext, @@ -11,8 +13,6 @@ import { StatusContext, } from "../context/revoltjs/RevoltClient"; -import Header from "../components/ui/Header"; - export default function Open() { const history = useHistory(); const client = useContext(AppContext); @@ -22,7 +22,7 @@ export default function Open() { if (status !== ClientStatus.ONLINE) { return ( -
+
); @@ -72,7 +72,7 @@ export default function Open() { }); return ( -
+
); diff --git a/src/pages/channels/ChannelHeader.tsx b/src/pages/channels/ChannelHeader.tsx index 69f32d50..46860e4b 100644 --- a/src/pages/channels/ChannelHeader.tsx +++ b/src/pages/channels/ChannelHeader.tsx @@ -1,31 +1,19 @@ -import { - At, - ChevronLeft, - ChevronRight, - Hash, -} from "@styled-icons/boxicons-regular"; +import { At, Hash } from "@styled-icons/boxicons-regular"; import { Notepad, Group } from "@styled-icons/boxicons-solid"; import { observer } from "mobx-react-lite"; import { Channel } from "revolt.js"; import { User } from "revolt.js"; -import styled, { css } from "styled-components/macro"; +import styled from "styled-components/macro"; import { isTouchscreenDevice } from "../../lib/isTouchscreenDevice"; -import { useApplicationState } from "../../mobx/State"; -import { SIDEBAR_CHANNELS, SIDEBAR_MEMBERS } from "../../mobx/stores/Layout"; - import { useIntermediate } from "../../context/intermediate/Intermediate"; import { getChannelName } from "../../context/revoltjs/util"; import { useStatusColour } from "../../components/common/user/UserIcon"; import UserStatus from "../../components/common/user/UserStatus"; -import Header, { - HamburgerAction, - PageHeader, -} from "../../components/ui/Header"; - import Markdown from "../../components/markdown/Markdown"; +import { PageHeader } from "../../components/ui/Header"; import HeaderActions from "./actions/HeaderActions"; export interface ChannelHeaderProps { @@ -98,7 +86,7 @@ export default observer(({ channel }: ChannelHeaderProps) => { } return ( - + {name} {isTouchscreenDevice && diff --git a/src/pages/developer/Developer.tsx b/src/pages/developer/Developer.tsx index d64cf5f6..46733c44 100644 --- a/src/pages/developer/Developer.tsx +++ b/src/pages/developer/Developer.tsx @@ -7,7 +7,7 @@ import { TextReact } from "../../lib/i18n"; import { AppContext } from "../../context/revoltjs/RevoltClient"; -import Header, { PageHeader } from "../../components/ui/Header"; +import { PageHeader } from "../../components/ui/Header"; export default function Developer() { // const voice = useContext(VoiceContext); diff --git a/src/pages/discover/Discover.tsx b/src/pages/discover/Discover.tsx index 7894dc32..7102c532 100644 --- a/src/pages/discover/Discover.tsx +++ b/src/pages/discover/Discover.tsx @@ -5,7 +5,7 @@ import styled, { css } from "styled-components/macro"; import { useEffect, useMemo, useRef, useState } from "preact/hooks"; -import { Preloader } from "@revoltchat/ui"; +import { Header, Preloader } from "@revoltchat/ui"; import { isTouchscreenDevice } from "../../lib/isTouchscreenDevice"; @@ -14,8 +14,6 @@ import { useApplicationState } from "../../mobx/State"; import { Overrides } from "../../context/Theme"; import { useIntermediate } from "../../context/intermediate/Intermediate"; -import Header from "../../components/ui/Header"; - const Container = styled.div` flex-grow: 1; display: flex; @@ -165,7 +163,7 @@ export default function Discover() { return ( {isTouchscreenDevice && ( -
+
Discover
diff --git a/src/pages/friends/Friends.tsx b/src/pages/friends/Friends.tsx index 63ba7e21..df5fa24c 100644 --- a/src/pages/friends/Friends.tsx +++ b/src/pages/friends/Friends.tsx @@ -68,7 +68,10 @@ export default observer(() => { const isEmpty = lists.reduce((p: number, n) => p + n.length, 0) === 0; return ( <> - } transparent noBurger> + } + withTransparency + noBurger>
diff --git a/src/pages/home/Home.tsx b/src/pages/home/Home.tsx index 56c5cfa1..2a5e0da6 100644 --- a/src/pages/home/Home.tsx +++ b/src/pages/home/Home.tsx @@ -86,7 +86,7 @@ export default observer(() => {
)}
- } transparent> + } withTransparency>
diff --git a/src/pages/invite/Invite.tsx b/src/pages/invite/Invite.tsx index 2e28da93..b11f0d96 100644 --- a/src/pages/invite/Invite.tsx +++ b/src/pages/invite/Invite.tsx @@ -7,7 +7,7 @@ import styles from "./Invite.module.scss"; import { Text } from "preact-i18n"; import { useContext, useEffect, useState } from "preact/hooks"; -import { Button, Preloader } from "@revoltchat/ui"; +import { Button, Category, Error, Preloader } from "@revoltchat/ui"; import { TextReact } from "../../lib/i18n"; @@ -23,7 +23,6 @@ import { takeError } from "../../context/revoltjs/util"; import ServerIcon from "../../components/common/ServerIcon"; import UserIcon from "../../components/common/user/UserIcon"; -import Overline from "../../components/ui/Overline"; export default function Invite() { const history = useHistory(); @@ -149,7 +148,9 @@ export default function Invite() { }} /> - + + +
- Add to server + Add to server - Add to group + Add to group
diff --git a/src/pages/settings/server/Members.tsx b/src/pages/settings/server/Members.tsx index 73661a6f..ff2f08b2 100644 --- a/src/pages/settings/server/Members.tsx +++ b/src/pages/settings/server/Members.tsx @@ -11,6 +11,7 @@ import { useEffect, useMemo, useState } from "preact/hooks"; import { Button, + Category, Checkbox, IconButton, InputBox, @@ -19,7 +20,6 @@ import { import UserIcon from "../../../components/common/user/UserIcon"; import { Username } from "../../../components/common/user/UserShort"; -import Overline from "../../../components/ui/Overline"; interface InnerProps { member: Member; @@ -51,7 +51,7 @@ const Inner = observer(({ member }: InnerProps) => {
{open && (
- Roles + Roles {Object.keys(server_roles).map((key) => { const role = server_roles[key]; return ( diff --git a/src/pages/settings/server/Roles.tsx b/src/pages/settings/server/Roles.tsx index 8aada1f6..aa70f81f 100644 --- a/src/pages/settings/server/Roles.tsx +++ b/src/pages/settings/server/Roles.tsx @@ -13,13 +13,13 @@ import { Checkbox, ColourSwatches, InputBox, + Category, } from "@revoltchat/ui"; import { useIntermediate } from "../../../context/intermediate/Intermediate"; import { PermissionList } from "../../../components/settings/roles/PermissionList"; import { RoleOrDefault } from "../../../components/settings/roles/RoleSelection"; -import Overline from "../../../components/ui/Overline"; interface Props { server: Server; @@ -136,9 +136,9 @@ export const Roles = observer(({ server }: Props) => { {selected !== "default" && ( <>
- + - +

{

- + - +

{

- + - +

{

- + - +

Date: Mon, 30 May 2022 15:45:14 +0100 Subject: [PATCH 033/151] feat(@ui): port Modal component --- src/components/ui/Modal.tsx | 263 ------------------ src/context/intermediate/Intermediate.tsx | 6 +- src/context/intermediate/Modals.tsx | 13 +- src/context/intermediate/Popovers.tsx | 8 +- src/context/intermediate/modals/Clipboard.tsx | 3 +- src/context/intermediate/modals/Error.tsx | 3 +- .../modals/ExternalLinkPrompt.tsx | 5 +- src/context/intermediate/modals/Input.tsx | 4 +- src/context/intermediate/modals/Prompt.tsx | 5 +- .../intermediate/modals/SessionsPrompt.tsx | 3 +- src/context/intermediate/modals/SignedOut.tsx | 3 +- .../intermediate/modals/TokenReveal.tsx | 3 +- .../intermediate/popovers/ChannelInfo.tsx | 4 +- .../intermediate/popovers/CreateBot.tsx | 4 +- .../intermediate/popovers/ImageViewer.tsx | 6 +- .../intermediate/popovers/ModifyAccount.tsx | 4 +- .../intermediate/popovers/PendingRequests.tsx | 3 +- .../popovers/ServerIdentityModal.tsx | 4 +- .../intermediate/popovers/UserPicker.tsx | 4 +- .../intermediate/popovers/UserProfile.tsx | 31 ++- src/pages/settings/panes/Panes.module.scss | 5 +- 21 files changed, 58 insertions(+), 326 deletions(-) delete mode 100644 src/components/ui/Modal.tsx diff --git a/src/components/ui/Modal.tsx b/src/components/ui/Modal.tsx deleted file mode 100644 index 06eaf0d5..00000000 --- a/src/components/ui/Modal.tsx +++ /dev/null @@ -1,263 +0,0 @@ -/* eslint-disable react-hooks/rules-of-hooks */ -import styled, { css, keyframes } from "styled-components/macro"; - -import { createPortal, useCallback, useEffect, useState } from "preact/compat"; - -import { Button } from "@revoltchat/ui"; -import { Props as ButtonProps } from "@revoltchat/ui/esm/components/design/atoms/inputs/Button"; - -import { internalSubscribe } from "../../lib/eventEmitter"; - -import { Children } from "../../types/Preact"; - -const open = keyframes` - 0% {opacity: 0;} - 70% {opacity: 0;} - 100% {opacity: 1;} -`; - -const close = keyframes` - 0% {opacity: 1;} - 70% {opacity: 0;} - 100% {opacity: 0;} -`; - -const zoomIn = keyframes` - 0% {transform: scale(0.5);} - 98% {transform: scale(1.01);} - 100% {transform: scale(1);} -`; - -const zoomOut = keyframes` - 0% {transform: scale(1);} - 100% {transform: scale(0.5);} -`; - -const ModalBase = styled.div` - top: 0; - left: 0; - width: 100%; - height: 100%; - z-index: 9999; - position: fixed; - max-height: 100%; - user-select: none; - - animation-name: ${open}; - animation-duration: 0.2s; - - display: grid; - overflow-y: auto; - place-items: center; - - color: var(--foreground); - background: rgba(0, 0, 0, 0.8); - - &.closing { - animation-name: ${close}; - animation-fill-mode: forwards; - } - - &.closing > div { - animation-name: ${zoomOut}; - } -`; - -const ModalContainer = styled.div` - overflow: hidden; - max-width: calc(100vw - 20px); - border-radius: var(--border-radius); - - animation-name: ${zoomIn}; - animation-duration: 0.25s; - animation-timing-function: cubic-bezier(0.3, 0.3, 0.18, 1.1); -`; - -const ModalContent = styled.div< - { [key in "attachment" | "noBackground" | "border" | "padding"]?: boolean } ->` - text-overflow: ellipsis; - border-radius: var(--border-radius); - - h3 { - font-size: 14px; - text-transform: uppercase; - margin: 0; - margin-bottom: 10px; - color: var(--foreground); - } - - h5 { - margin: 0; - font-size: 13px; - font-weight: 500; - color: var(--secondary-foreground); - } - - form { - display: flex; - flex-direction: column; - gap: 8px; - - > div { - margin: 0; - color: var(--secondary-foreground); - font-size: 12px; - } - } - - .description { - color: var(--tertiary-foreground); - font-size: 90%; - } - - ${(props) => - !props.noBackground && - css` - background: var(--secondary-header); - `} - - ${(props) => - props.padding && - css` - padding: 1rem; - min-width: 450px; - `} - - ${(props) => - props.attachment && - css` - border-radius: var(--border-radius) var(--border-radius) 0 0; - `} - - ${(props) => - props.border && - css` - border-radius: var(--border-radius); - border: 2px solid var(--secondary-background); - `} -`; - -const ModalActions = styled.div` - gap: 8px; - display: flex; - flex-direction: row-reverse; - padding: 1rem; - background: var(--secondary-background); - border-radius: 0 0 var(--border-radius) var(--border-radius); -`; - -export type Action = Omit< - JSX.HTMLAttributes, - "as" | "onClick" -> & { - palette?: ButtonProps["palette"]; - confirmation?: boolean; - onClick: () => void; -}; - -interface Props { - children?: Children; - title?: Children; - description?: Children; - - disallowClosing?: boolean; - noBackground?: boolean; - dontModal?: boolean; - padding?: boolean; - - onClose?: () => void; - actions?: Action[]; - disabled?: boolean; - palette?: ButtonProps["palette"]; - border?: boolean; - visible: boolean; -} - -export let isModalClosing = false; - -export default function Modal(props: Props) { - if (!props.visible) return null; - - const content = ( - - {props.title &&

{props.title}

} - - {props.description &&
{props.description}
} - {props.children} - - ); - - if (props.dontModal) { - return content; - } - - const [animateClose, setAnimateClose] = useState(false); - isModalClosing = animateClose; - const onClose = useCallback(() => { - setAnimateClose(true); - setTimeout(() => props.onClose!(), 2e2); - }, [setAnimateClose, props]); - - useEffect(() => internalSubscribe("Modal", "close", onClose), [onClose]); - - useEffect(() => { - if (props.disallowClosing) return; - - function keyDown(e: KeyboardEvent) { - if (e.key === "Escape") { - onClose(); - } - } - - document.body.addEventListener("keydown", keyDown); - return () => document.body.removeEventListener("keydown", keyDown); - }, [props.disallowClosing, onClose]); - - const confirmationAction = props.actions?.find( - (action) => action.confirmation, - ); - - useEffect(() => { - if (!confirmationAction) return; - - // ! TODO: this may be done better if we - // ! can focus the button although that - // ! doesn't seem to work... - function keyDown(e: KeyboardEvent) { - if (e.key === "Enter") { - confirmationAction!.onClick(); - } - } - - document.body.addEventListener("keydown", keyDown); - return () => document.body.removeEventListener("keydown", keyDown); - }, [confirmationAction]); - - return createPortal( - - (e.cancelBubble = true)}> - {content} - {props.actions && ( - - {props.actions.map((x, index) => ( -
writeClipboard(JSON.stringify(theme))}> }> {" "} diff --git a/src/components/settings/roles/PermissionSelect.tsx b/src/components/settings/roles/PermissionSelect.tsx index 5f921132..0a8b9742 100644 --- a/src/components/settings/roles/PermissionSelect.tsx +++ b/src/components/settings/roles/PermissionSelect.tsx @@ -117,12 +117,12 @@ export function PermissionSelect({ return ( - + {id} {disabled && } - + diff --git a/src/components/ui/Header.tsx b/src/components/ui/Header.tsx index 860a294d..f91b63a1 100644 --- a/src/components/ui/Header.tsx +++ b/src/components/ui/Header.tsx @@ -14,8 +14,6 @@ import { isTouchscreenDevice } from "../../lib/isTouchscreenDevice"; import { useApplicationState } from "../../mobx/State"; import { SIDEBAR_CHANNELS } from "../../mobx/stores/Layout"; -import { Children } from "../../types/Preact"; - interface Props { topBorder?: boolean; bottomBorder?: boolean; diff --git a/src/context/index.tsx b/src/context/index.tsx index 277f0126..8d4595cd 100644 --- a/src/context/index.tsx +++ b/src/context/index.tsx @@ -13,7 +13,6 @@ import { import { hydrateState } from "../mobx/State"; -import { Children } from "../types/Preact"; import Locale from "./Locale"; import Theme from "./Theme"; import Intermediate from "./intermediate/Intermediate"; diff --git a/src/context/intermediate/Intermediate.tsx b/src/context/intermediate/Intermediate.tsx index 94d86408..9a1ac4d1 100644 --- a/src/context/intermediate/Intermediate.tsx +++ b/src/context/intermediate/Intermediate.tsx @@ -12,7 +12,6 @@ import { determineLink } from "../../lib/links"; import { useApplicationState } from "../../mobx/State"; -import { Children } from "../../types/Preact"; import Modals from "./Modals"; export type Screen = diff --git a/src/context/intermediate/modals/Input.tsx b/src/context/intermediate/modals/Input.tsx index f84bd03d..1e70b2ac 100644 --- a/src/context/intermediate/modals/Input.tsx +++ b/src/context/intermediate/modals/Input.tsx @@ -6,7 +6,6 @@ import { useContext, useState } from "preact/hooks"; import { Category, InputBox, Modal } from "@revoltchat/ui"; -import { Children } from "../../../types/Preact"; import { I18nError } from "../../Locale"; import { AppContext } from "../../revoltjs/RevoltClient"; import { takeError } from "../../revoltjs/util"; diff --git a/src/context/intermediate/modals/Prompt.tsx b/src/context/intermediate/modals/Prompt.tsx index a01a9f8a..b3d15395 100644 --- a/src/context/intermediate/modals/Prompt.tsx +++ b/src/context/intermediate/modals/Prompt.tsx @@ -14,7 +14,6 @@ import { TextReact } from "../../../lib/i18n"; import Message from "../../../components/common/messaging/Message"; import UserIcon from "../../../components/common/user/UserIcon"; -import { Children } from "../../../types/Preact"; import { I18nError } from "../../Locale"; import { AppContext } from "../../revoltjs/RevoltClient"; import { takeError } from "../../revoltjs/util"; diff --git a/src/context/revoltjs/CheckAuth.tsx b/src/context/revoltjs/CheckAuth.tsx index 3d76ae92..be5c8eb8 100644 --- a/src/context/revoltjs/CheckAuth.tsx +++ b/src/context/revoltjs/CheckAuth.tsx @@ -2,7 +2,6 @@ import { Redirect } from "react-router-dom"; import { useApplicationState } from "../../mobx/State"; -import { Children } from "../../types/Preact"; import { useClient } from "./RevoltClient"; interface Props { diff --git a/src/context/revoltjs/RequiresOnline.tsx b/src/context/revoltjs/RequiresOnline.tsx index 4400bcc6..4835bd42 100644 --- a/src/context/revoltjs/RequiresOnline.tsx +++ b/src/context/revoltjs/RequiresOnline.tsx @@ -6,7 +6,6 @@ import { useContext } from "preact/hooks"; import { Preloader } from "@revoltchat/ui"; -import { Children } from "../../types/Preact"; import { ClientStatus, StatusContext } from "./RevoltClient"; interface Props { diff --git a/src/context/revoltjs/RevoltClient.tsx b/src/context/revoltjs/RevoltClient.tsx index 37c29428..57b0a091 100644 --- a/src/context/revoltjs/RevoltClient.tsx +++ b/src/context/revoltjs/RevoltClient.tsx @@ -9,7 +9,6 @@ import { Preloader } from "@revoltchat/ui"; import { useApplicationState } from "../../mobx/State"; -import { Children } from "../../types/Preact"; import { useIntermediate } from "../intermediate/Intermediate"; import { registerEvents } from "./events"; import { takeError } from "./util"; diff --git a/src/context/revoltjs/util.tsx b/src/context/revoltjs/util.tsx index 878cc50e..e82713a4 100644 --- a/src/context/revoltjs/util.tsx +++ b/src/context/revoltjs/util.tsx @@ -2,8 +2,6 @@ import { Channel } from "revolt.js"; import { Text } from "preact-i18n"; -import { Children } from "../../types/Preact"; - // eslint-disable-next-line @typescript-eslint/no-explicit-any export function takeError(error: any): string { if (error.response) { diff --git a/src/lib/ContextMenus.tsx b/src/lib/ContextMenus.tsx index 43645bda..9f8dabf3 100644 --- a/src/lib/ContextMenus.tsx +++ b/src/lib/ContextMenus.tsx @@ -30,7 +30,6 @@ import CMNotifications from "./contextmenu/CMNotifications"; import Tooltip from "../components/common/Tooltip"; import UserStatus from "../components/common/user/UserStatus"; -import { Children } from "../types/Preact"; import { internalEmit } from "./eventEmitter"; import { getRenderer } from "./renderer/Singleton"; diff --git a/src/lib/ErrorBoundary.tsx b/src/lib/ErrorBoundary.tsx index 10942d54..f755b788 100644 --- a/src/lib/ErrorBoundary.tsx +++ b/src/lib/ErrorBoundary.tsx @@ -6,7 +6,6 @@ import styled from "styled-components/macro"; import { useEffect, useErrorBoundary, useState } from "preact/hooks"; import { GIT_REVISION } from "../revision"; -import { Children } from "../types/Preact"; const CrashContainer = styled.div` height: 100%; diff --git a/src/lib/FakeClient.tsx b/src/lib/FakeClient.tsx index d2523ef5..861ecec3 100644 --- a/src/lib/FakeClient.tsx +++ b/src/lib/FakeClient.tsx @@ -6,8 +6,6 @@ import { useApplicationState } from "../mobx/State"; import { AppContext } from "../context/revoltjs/RevoltClient"; -import { Children } from "../types/Preact"; - export default observer(({ children }: { children: Children }) => { const config = useApplicationState().config; const client = useMemo(() => config.createClient(), [config.get()]); diff --git a/src/lib/contextmenu/CMNotifications.tsx b/src/lib/contextmenu/CMNotifications.tsx index ec65b54d..72c4197d 100644 --- a/src/lib/contextmenu/CMNotifications.tsx +++ b/src/lib/contextmenu/CMNotifications.tsx @@ -20,8 +20,6 @@ import { LineDivider } from "@revoltchat/ui"; import { useApplicationState } from "../../mobx/State"; import { NotificationState } from "../../mobx/stores/NotificationOptions"; -import { Children } from "../../types/Preact"; - interface Action { key: string; type: "channel" | "server"; diff --git a/src/lib/i18n.tsx b/src/lib/i18n.tsx index 047e2bb7..41826d94 100644 --- a/src/lib/i18n.tsx +++ b/src/lib/i18n.tsx @@ -3,8 +3,6 @@ import { useContext } from "preact/hooks"; import { Dictionary } from "../context/Locale"; -import { Children } from "../types/Preact"; - interface Fields { [key: string]: Children; } diff --git a/src/pages/RevoltApp.tsx b/src/pages/RevoltApp.tsx index e88fe208..82711ef6 100644 --- a/src/pages/RevoltApp.tsx +++ b/src/pages/RevoltApp.tsx @@ -119,7 +119,7 @@ export default function App() { {statusBar && (
Partial outage: CDN
-
+
Updates
diff --git a/src/pages/channels/messaging/MessageRenderer.tsx b/src/pages/channels/messaging/MessageRenderer.tsx index 6195e29b..fd7aa7b9 100644 --- a/src/pages/channels/messaging/MessageRenderer.tsx +++ b/src/pages/channels/messaging/MessageRenderer.tsx @@ -24,7 +24,6 @@ import { useClient } from "../../../context/revoltjs/RevoltClient"; import Message from "../../../components/common/messaging/Message"; import { SystemMessage } from "../../../components/common/messaging/SystemMessage"; -import { Children } from "../../../types/Preact"; import ConversationStart from "./ConversationStart"; import MessageEditor from "./MessageEditor"; diff --git a/src/pages/friends/Friend.tsx b/src/pages/friends/Friend.tsx index fed35293..182ffdea 100644 --- a/src/pages/friends/Friend.tsx +++ b/src/pages/friends/Friend.tsx @@ -18,7 +18,6 @@ import { useIntermediate } from "../../context/intermediate/Intermediate"; import UserIcon from "../../components/common/user/UserIcon"; import UserStatus from "../../components/common/user/UserStatus"; -import { Children } from "../../types/Preact"; interface Props { user: User; diff --git a/src/pages/friends/Friends.tsx b/src/pages/friends/Friends.tsx index df5fa24c..335462f6 100644 --- a/src/pages/friends/Friends.tsx +++ b/src/pages/friends/Friends.tsx @@ -19,7 +19,6 @@ import CollapsibleSection from "../../components/common/CollapsibleSection"; import Tooltip from "../../components/common/Tooltip"; import UserIcon from "../../components/common/user/UserIcon"; import { PageHeader } from "../../components/ui/Header"; -import { Children } from "../../types/Preact"; import { Friend } from "./Friend"; export default observer(() => { @@ -201,7 +200,7 @@ export default observer(() => { sticky large summary={ -
+
— {list.length}
}> diff --git a/src/pages/home/Home.tsx b/src/pages/home/Home.tsx index 2a5e0da6..858161c8 100644 --- a/src/pages/home/Home.tsx +++ b/src/pages/home/Home.tsx @@ -77,9 +77,9 @@ export default observer(() => {
{seasonalTheme && ( -
+
{snowflakes.map((emoji, index) => ( -
+
{emoji}
))} diff --git a/src/pages/settings/GenericSettings.tsx b/src/pages/settings/GenericSettings.tsx index 1a56d159..f4f4fc34 100644 --- a/src/pages/settings/GenericSettings.tsx +++ b/src/pages/settings/GenericSettings.tsx @@ -14,7 +14,6 @@ import { isTouchscreenDevice } from "../../lib/isTouchscreenDevice"; import { useApplicationState } from "../../mobx/State"; import ButtonItem from "../../components/navigation/items/ButtonItem"; -import { Children } from "../../types/Preact"; interface Props { pages: { diff --git a/src/pages/settings/panes/Audio.tsx b/src/pages/settings/panes/Audio.tsx index e0226cbb..093d83cd 100644 --- a/src/pages/settings/panes/Audio.tsx +++ b/src/pages/settings/panes/Audio.tsx @@ -93,7 +93,7 @@ export function Audio() { return ( <> -
+
{!permission && ( @@ -115,7 +115,7 @@ export function Audio() {

-
+
diff --git a/src/pages/settings/server/Bans.tsx b/src/pages/settings/server/Bans.tsx index ed458946..3896f6f6 100644 --- a/src/pages/settings/server/Bans.tsx +++ b/src/pages/settings/server/Bans.tsx @@ -38,8 +38,7 @@ const Inner = observer(({ ban, users, server, removeSelf }: InnerProps) => { onClick={() => { setDelete(true); server.unbanUser(ban._id.user).then(removeSelf); - }} - disabled={deleting}> + }}>
@@ -100,7 +99,7 @@ export const Bans = observer(({ server }: Props) => { - + diff --git a/src/pages/settings/server/Categories.tsx b/src/pages/settings/server/Categories.tsx index 9d73dd26..9af11841 100644 --- a/src/pages/settings/server/Categories.tsx +++ b/src/pages/settings/server/Categories.tsx @@ -292,7 +292,7 @@ export const Categories = observer(({ server }: Props) => { /> ))} -
+
setCategories([ @@ -369,7 +369,7 @@ function ListElement({ {(provided) => (
-
+
{editing ? ( @@ -422,7 +422,7 @@ function ListElement({ provided.innerRef }> -
+
{ onClick={() => { setDelete(true); server.client.deleteInvite(invite._id).then(removeSelf); - }} - disabled={deleting}> + }}>
diff --git a/src/pages/settings/server/Members.tsx b/src/pages/settings/server/Members.tsx index ff2f08b2..8b155b4c 100644 --- a/src/pages/settings/server/Members.tsx +++ b/src/pages/settings/server/Members.tsx @@ -61,7 +61,7 @@ const Inner = observer(({ member }: InnerProps) => { title={ {role.name} diff --git a/src/pages/settings/server/Overview.tsx b/src/pages/settings/server/Overview.tsx index 4a333bab..c04f1c06 100644 --- a/src/pages/settings/server/Overview.tsx +++ b/src/pages/settings/server/Overview.tsx @@ -138,7 +138,7 @@ export const Overview = observer(({ server }: Props) => { gap: "8px", alignItems: "center", }}> - {i18n} + {i18n} Date: Fri, 10 Jun 2022 14:32:21 +0100 Subject: [PATCH 046/151] feat: add disable / delete funct; bump revolt-api --- package.json | 2 +- src/context/intermediate/modals/Input.tsx | 2 +- src/context/revoltjs/RevoltClient.tsx | 10 ++++----- src/mobx/legacy/redux.ts | 2 +- src/mobx/stores/Auth.ts | 4 ++-- src/pages/settings/panes/Account.tsx | 26 +++++++++++++++++------ src/types/revolt-api.d.ts | 7 ++++++ yarn.lock | 20 ++++++++--------- 8 files changed, 47 insertions(+), 26 deletions(-) create mode 100644 src/types/revolt-api.d.ts diff --git a/package.json b/package.json index a4a89c0c..28279792 100644 --- a/package.json +++ b/package.json @@ -145,7 +145,7 @@ "react-router-dom": "^5.2.0", "react-scroll": "^1.8.2", "react-virtuoso": "^2.12.0", - "revolt.js": "6.0.2", + "revolt.js": "6.0.3", "rimraf": "^3.0.2", "sass": "^1.35.1", "shade-blend-color": "^1.0.0", diff --git a/src/context/intermediate/modals/Input.tsx b/src/context/intermediate/modals/Input.tsx index 1e70b2ac..e34cad30 100644 --- a/src/context/intermediate/modals/Input.tsx +++ b/src/context/intermediate/modals/Input.tsx @@ -177,7 +177,7 @@ export function SpecialInputModal(props: SpecialProps) { question={"Add Friend"} callback={(username) => client.api - .put(`/users/${username as ""}/friend`) + .post(`/users/friend`, { username }) .then(undefined) } /> diff --git a/src/context/revoltjs/RevoltClient.tsx b/src/context/revoltjs/RevoltClient.tsx index 57b0a091..e166c309 100644 --- a/src/context/revoltjs/RevoltClient.tsx +++ b/src/context/revoltjs/RevoltClient.tsx @@ -3,7 +3,7 @@ import { observer } from "mobx-react-lite"; import { Client } from "revolt.js"; import { createContext } from "preact"; -import { useContext, useEffect, useState } from "preact/hooks"; +import { useCallback, useContext, useEffect, useState } from "preact/hooks"; import { Preloader } from "@revoltchat/ui"; @@ -29,7 +29,7 @@ export interface ClientOperations { export const AppContext = createContext(null!); export const StatusContext = createContext(null!); -export const LogOutContext = createContext(() => {}); +export const LogOutContext = createContext((avoidReq?: boolean) => {}); type Props = { children: Children; @@ -42,10 +42,10 @@ export default observer(({ children }: Props) => { const [status, setStatus] = useState(ClientStatus.LOADING); const [loaded, setLoaded] = useState(false); - function logout() { + const logout = useCallback((avoidReq?: boolean) => { setLoaded(false); - client.logout(false); - } + client.logout(avoidReq); + }, []); useEffect(() => { if (navigator.onLine) { diff --git a/src/mobx/legacy/redux.ts b/src/mobx/legacy/redux.ts index 589fa9c4..81c5c37f 100644 --- a/src/mobx/legacy/redux.ts +++ b/src/mobx/legacy/redux.ts @@ -58,7 +58,7 @@ export interface LegacySyncOptions { export interface LegacyAuthState { accounts: { [key: string]: { - session: API.Session; + session: Session; }; }; active?: string; diff --git a/src/mobx/stores/Auth.ts b/src/mobx/stores/Auth.ts index bf1d0aec..8662bb1f 100644 --- a/src/mobx/stores/Auth.ts +++ b/src/mobx/stores/Auth.ts @@ -8,7 +8,7 @@ import Persistent from "../interfaces/Persistent"; import Store from "../interfaces/Store"; interface Account { - session: API.Session; + session: Session; } export interface Data { @@ -82,7 +82,7 @@ export default class Auth implements Store, Persistent { * Add a new session to the auth manager. * @param session Session */ - @action setSession(session: API.Session) { + @action setSession(session: Session) { this.sessions.set(session.user_id, { session }); this.current = session.user_id; } diff --git a/src/pages/settings/panes/Account.tsx b/src/pages/settings/panes/Account.tsx index 4874421f..eb67ea01 100644 --- a/src/pages/settings/panes/Account.tsx +++ b/src/pages/settings/panes/Account.tsx @@ -21,6 +21,7 @@ import { stopPropagation } from "../../../lib/stopPropagation"; import { useIntermediate } from "../../../context/intermediate/Intermediate"; import { ClientStatus, + LogOutContext, StatusContext, useClient, } from "../../../context/revoltjs/RevoltClient"; @@ -30,6 +31,7 @@ import UserIcon from "../../../components/common/user/UserIcon"; export const Account = observer(() => { const { openScreen, writeClipboard } = useIntermediate(); + const logOut = useContext(LogOutContext); const status = useContext(StatusContext); const client = useClient(); @@ -207,9 +209,15 @@ export const Account = observer(() => { "Disable your account. You won't be able to access it unless you contact support." } action="chevron" - onClick={() => { - // - }}> + onClick={() => + client.api + .post("/auth/account/disable", undefined, { + headers: { + "X-MFA-Ticket": "TICKET", + }, + }) + .then(() => logOut(true)) + }> { "Your account will be queued for deletion, a confirmation email will be sent." } action="chevron" - onClick={() => { - // - }}> + onClick={() => + client.api + .post("/auth/account/delete", undefined, { + headers: { + "X-MFA-Ticket": "TICKET", + }, + }) + .then(() => logOut(true)) + }> diff --git a/src/types/revolt-api.d.ts b/src/types/revolt-api.d.ts new file mode 100644 index 00000000..664e6ab1 --- /dev/null +++ b/src/types/revolt-api.d.ts @@ -0,0 +1,7 @@ +// TODO: re-export from revolt-api in some way +declare type Session = { + _id: string; + token: string; + name: string; + user_id: string; +}; diff --git a/yarn.lock b/yarn.lock index 88208886..0c7de6ce 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3578,7 +3578,7 @@ __metadata: react-router-dom: ^5.2.0 react-scroll: ^1.8.2 react-virtuoso: ^2.12.0 - revolt.js: 6.0.2 + revolt.js: 6.0.3 rimraf: ^3.0.2 sass: ^1.35.1 shade-blend-color: ^1.0.0 @@ -6858,20 +6858,20 @@ __metadata: languageName: node linkType: hard -"revolt-api@npm:0.5.3-5-patch.4": - version: 0.5.3-5-patch.4 - resolution: "revolt-api@npm:0.5.3-5-patch.4" +"revolt-api@npm:0.5.3-7": + version: 0.5.3-7 + resolution: "revolt-api@npm:0.5.3-7" dependencies: "@insertish/oapi": 0.1.16 axios: ^0.26.1 lodash.defaultsdeep: ^4.6.1 - checksum: 4f01c43bff96c4030d13ab0bb5dc83614445763602cfdd8b3ff1dbf61620446a22513ca259bbfc9c490f6b9b19c79d610921a252b667d25adf4040b4222d98cf + checksum: acc2f412d1be90f0cfa8a24ba715d8257813762c56ba0e48c649efff349674517036a8fa5939eda61ce31fd64a37f0c54af3d8fae56edf4596f05b10304e090e languageName: node linkType: hard -"revolt.js@npm:6.0.2": - version: 6.0.2 - resolution: "revolt.js@npm:6.0.2" +"revolt.js@npm:6.0.3": + version: 6.0.3 + resolution: "revolt.js@npm:6.0.3" dependencies: "@insertish/exponential-backoff": 3.1.0-patch.2 "@insertish/isomorphic-ws": ^4.0.1 @@ -6882,10 +6882,10 @@ __metadata: lodash.isequal: ^4.5.0 long: ^5.2.0 mobx: ^6.3.2 - revolt-api: 0.5.3-5-patch.4 + revolt-api: 0.5.3-7 ulid: ^2.3.0 ws: ^8.2.2 - checksum: 1b0c6ce0ceae5d20aec373f24432a5e804677c455fbc39edadaeb6ae0427a0b4fe2ea2b950df92262cd56a43e3f8759d80887b65d66111882b6ecb82e813ff38 + checksum: cfecbde7a9b795da75bfac3cec05f2aed5fd02cd14fcac1b2fad26285de1b887cf7ccb339e8a63a019f1e83058b041305927e029d7b5eedcc00823582d9a842a languageName: node linkType: hard From e81b8ed47296386ab42594b0d443aa4af20eaa47 Mon Sep 17 00:00:00 2001 From: Paul Makles Date: Fri, 10 Jun 2022 16:52:12 +0100 Subject: [PATCH 047/151] feat: new modal renderer + mfa flow modal --- README.md | 2 - package.json | 2 +- src/context/index.tsx | 2 + src/context/modals/ModalRenderer.tsx | 7 + src/context/modals/components/MFAFlow.tsx | 164 ++++++++++++++++++++++ src/context/modals/components/Test.tsx | 7 + src/context/modals/index.tsx | 63 +++++++++ src/context/modals/types.ts | 27 ++++ src/context/revoltjs/RevoltClient.tsx | 11 +- src/lib/js.ts | 1 + src/pages/settings/panes/Account.tsx | 41 ++++-- yarn.lock | 10 +- 12 files changed, 311 insertions(+), 26 deletions(-) create mode 100644 src/context/modals/ModalRenderer.tsx create mode 100644 src/context/modals/components/MFAFlow.tsx create mode 100644 src/context/modals/components/Test.tsx create mode 100644 src/context/modals/index.tsx create mode 100644 src/context/modals/types.ts diff --git a/README.md b/README.md index b4f645ef..60a1cdf4 100644 --- a/README.md +++ b/README.md @@ -13,8 +13,6 @@ The following code is pending a partial or full rewrite: - `src/context/intermediate`: modal system is being rewritten from scratch - `src/context/revoltjs`: client state management needs to be rewritten and include support for concurrent clients - `src/lib`: this needs to be organised -- `src/*.ts(x)`: half of these files should be moved -- `src/*.d.ts`: should be in dedicated types folder ## Stack diff --git a/package.json b/package.json index 28279792..733f29e9 100644 --- a/package.json +++ b/package.json @@ -90,7 +90,7 @@ "@hcaptcha/react-hcaptcha": "^0.3.6", "@insertish/vite-plugin-babel-macros": "^1.0.5", "@preact/preset-vite": "^2.0.0", - "@revoltchat/ui": "1.0.36", + "@revoltchat/ui": "1.0.39", "@rollup/plugin-replace": "^2.4.2", "@styled-icons/boxicons-logos": "^10.38.0", "@styled-icons/boxicons-regular": "^10.38.0", diff --git a/src/context/index.tsx b/src/context/index.tsx index 8d4595cd..89026b31 100644 --- a/src/context/index.tsx +++ b/src/context/index.tsx @@ -16,6 +16,7 @@ import { hydrateState } from "../mobx/State"; import Locale from "./Locale"; import Theme from "./Theme"; import Intermediate from "./intermediate/Intermediate"; +import ModalRenderer from "./modals/ModalRenderer"; import Client from "./revoltjs/RevoltClient"; import SyncManager from "./revoltjs/SyncManager"; @@ -44,6 +45,7 @@ export default function Context({ children }: { children: Children }) { + diff --git a/src/context/modals/ModalRenderer.tsx b/src/context/modals/ModalRenderer.tsx new file mode 100644 index 00000000..2ed3d350 --- /dev/null +++ b/src/context/modals/ModalRenderer.tsx @@ -0,0 +1,7 @@ +import { observer } from "mobx-react-lite"; + +import { modalController } from "."; + +export default observer(() => { + return modalController.render(); +}); diff --git a/src/context/modals/components/MFAFlow.tsx b/src/context/modals/components/MFAFlow.tsx new file mode 100644 index 00000000..565c4be6 --- /dev/null +++ b/src/context/modals/components/MFAFlow.tsx @@ -0,0 +1,164 @@ +import { Archive } from "@styled-icons/boxicons-regular"; +import { Key, Keyboard } from "@styled-icons/boxicons-solid"; +import { API } from "revolt.js"; + +import { Text } from "preact-i18n"; +import { useCallback, useEffect, useState } from "preact/hooks"; + +import { + Category, + CategoryButton, + InputBox, + Modal, + Preloader, +} from "@revoltchat/ui"; + +import { noopTrue } from "../../../lib/js"; + +import { useApplicationState } from "../../../mobx/State"; + +import { ModalProps } from "../types"; + +const ICONS: Record> = { + Password: Keyboard, + Totp: Key, + Recovery: Archive, +}; + +function ResponseEntry({ + type, + value, + onChange, +}: { + type: API.MFAMethod; + value?: API.MFAResponse; + onChange: (v: API.MFAResponse) => void; +}) { + if (type === "Password") { + return ( + <> + + + + + onChange({ password: e.currentTarget.value }) + } + /> + + ); + } else { + return null; + } +} + +/** + * MFA ticket creation flow + */ +export default function MFAFlow({ + callback, + onClose, + ...props +}: ModalProps<"mfa_flow">) { + const state = useApplicationState(); + + const [methods, setMethods] = useState( + props.state === "unknown" ? props.available_methods : undefined, + ); + + const [selectedMethod, setSelected] = useState(); + const [response, setResponse] = useState(); + + useEffect(() => { + if (!methods && props.state === "known") { + props.client.api.get("/auth/mfa/methods").then(setMethods); + } + }, []); + + const generateTicket = useCallback(async () => { + if (response) { + let ticket; + + if (props.state === "known") { + ticket = await props.client.api.put( + "/auth/mfa/ticket", + response, + ); + } else { + ticket = await state.config + .createClient() + .api.put("/auth/mfa/ticket", response, { + headers: { + "X-MFA-Ticket": props.ticket.token, + }, + }); + } + + callback(ticket); + return true; + } + + return false; + }, [response]); + + return ( + setSelected(undefined), + }, + ] + : [ + { + palette: "plain", + children: "Cancel", + onClick: noopTrue, + }, + ] + } + onClose={onClose}> + {methods ? ( + selectedMethod ? ( + + ) : ( + methods.map((method) => { + const Icon = ICONS[method]; + return ( + } + onClick={() => setSelected(method)}> + {method} + + ); + }) + ) + ) : ( + + )} + + ); +} diff --git a/src/context/modals/components/Test.tsx b/src/context/modals/components/Test.tsx new file mode 100644 index 00000000..ed59a414 --- /dev/null +++ b/src/context/modals/components/Test.tsx @@ -0,0 +1,7 @@ +import { Modal } from "@revoltchat/ui"; + +import { ModalProps } from "../types"; + +export default function Test({ onClose }: ModalProps<"test">) { + return ; +} diff --git a/src/context/modals/index.tsx b/src/context/modals/index.tsx new file mode 100644 index 00000000..a5843d30 --- /dev/null +++ b/src/context/modals/index.tsx @@ -0,0 +1,63 @@ +import { action, computed, makeAutoObservable } from "mobx"; +import { ulid } from "ulid"; + +import MFAFlow from "./components/MFAFlow"; +import Test from "./components/Test"; +import { Modal } from "./types"; + +type Components = Record>; + +/** + * Handles layering and displaying modals to the user. + */ +class ModalController { + stack: T[] = []; + components: Components; + + constructor(components: Components) { + this.components = components; + + makeAutoObservable(this); + this.pop = this.pop.bind(this); + } + + /** + * Display a new modal on the stack + * @param modal Modal data + */ + @action push(modal: T) { + this.stack = [ + ...this.stack, + { + ...modal, + key: ulid(), + }, + ]; + } + + /** + * Remove the top modal from the stack + */ + @action pop() { + this.stack = this.stack.slice(0, this.stack.length - 1); + } + + /** + * Render modals + */ + @computed render() { + return ( + <> + {this.stack.map((modal) => { + const Component = this.components[modal.type]; + return ; + })} + + ); + } +} + +export const modalController = new ModalController({ + mfa_flow: MFAFlow, + test: Test, +}); diff --git a/src/context/modals/types.ts b/src/context/modals/types.ts new file mode 100644 index 00000000..e15a552c --- /dev/null +++ b/src/context/modals/types.ts @@ -0,0 +1,27 @@ +import { API, Client } from "revolt.js"; + +export type Modal = { + key?: string; +} & ( + | ({ + type: "mfa_flow"; + callback: (ticket: API.MFATicket) => void; + } & ( + | { + state: "known"; + client: Client; + } + | { + state: "unknown"; + available_methods: API.MFAMethod[]; + ticket: API.MFATicket & { validated: false }; + } + )) + | { + type: "test"; + } +); + +export type ModalProps = Modal & { type: T } & { + onClose: () => void; +}; diff --git a/src/context/revoltjs/RevoltClient.tsx b/src/context/revoltjs/RevoltClient.tsx index e166c309..cfb5d4fe 100644 --- a/src/context/revoltjs/RevoltClient.tsx +++ b/src/context/revoltjs/RevoltClient.tsx @@ -42,10 +42,13 @@ export default observer(({ children }: Props) => { const [status, setStatus] = useState(ClientStatus.LOADING); const [loaded, setLoaded] = useState(false); - const logout = useCallback((avoidReq?: boolean) => { - setLoaded(false); - client.logout(avoidReq); - }, []); + const logout = useCallback( + (avoidReq?: boolean) => { + setLoaded(false); + client.logout(avoidReq); + }, + [client], + ); useEffect(() => { if (navigator.onLine) { diff --git a/src/lib/js.ts b/src/lib/js.ts index 80158291..5a35a5a2 100644 --- a/src/lib/js.ts +++ b/src/lib/js.ts @@ -1,4 +1,5 @@ /* eslint-disable @typescript-eslint/no-empty-function */ export const noop = () => {}; export const noopAsync = async () => {}; +export const noopTrue = () => true; /* eslint-enable @typescript-eslint/no-empty-function */ diff --git a/src/pages/settings/panes/Account.tsx b/src/pages/settings/panes/Account.tsx index eb67ea01..bc3849a8 100644 --- a/src/pages/settings/panes/Account.tsx +++ b/src/pages/settings/panes/Account.tsx @@ -19,6 +19,7 @@ import { Button, CategoryButton, LineDivider, Tip } from "@revoltchat/ui"; import { stopPropagation } from "../../../lib/stopPropagation"; import { useIntermediate } from "../../../context/intermediate/Intermediate"; +import { modalController } from "../../../context/modals"; import { ClientStatus, LogOutContext, @@ -210,13 +211,19 @@ export const Account = observer(() => { } action="chevron" onClick={() => - client.api - .post("/auth/account/disable", undefined, { - headers: { - "X-MFA-Ticket": "TICKET", - }, - }) - .then(() => logOut(true)) + modalController.push({ + type: "mfa_flow", + state: "known", + client, + callback: ({ token }) => + client.api + .post("/auth/account/disable", undefined, { + headers: { + "X-MFA-Ticket": token, + }, + }) + .then(() => logOut(true)), + }) }> @@ -227,13 +234,19 @@ export const Account = observer(() => { } action="chevron" onClick={() => - client.api - .post("/auth/account/delete", undefined, { - headers: { - "X-MFA-Ticket": "TICKET", - }, - }) - .then(() => logOut(true)) + modalController.push({ + type: "mfa_flow", + state: "known", + client, + callback: ({ token }) => + client.api + .post("/auth/account/delete", undefined, { + headers: { + "X-MFA-Ticket": token, + }, + }) + .then(() => logOut(true)), + }) }> diff --git a/yarn.lock b/yarn.lock index 0c7de6ce..5e07617b 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2220,9 +2220,9 @@ __metadata: languageName: node linkType: hard -"@revoltchat/ui@npm:1.0.36": - version: 1.0.36 - resolution: "@revoltchat/ui@npm:1.0.36" +"@revoltchat/ui@npm:1.0.39": + version: 1.0.39 + resolution: "@revoltchat/ui@npm:1.0.39" dependencies: "@styled-icons/boxicons-logos": ^10.38.0 "@styled-icons/boxicons-regular": ^10.38.0 @@ -2235,7 +2235,7 @@ __metadata: react-device-detect: "*" react-virtuoso: "*" revolt.js: "*" - checksum: 97eee93df28f2ca826c7cb1493e3c0efe0ab83d3ef8ea3d3ec013ff3b527f2692193ef50c8e44d144f96d49457c4d290a4dc708a38ab527f3a4290e0d05b41b5 + checksum: 0376ef1e6c90a139da613a0b76d498327c7bad63941d02eb27b9d5b8208f09c01fb45330fc4e0643554a298beee416814dd41fd9992750378491450c6f773ee0 languageName: node linkType: hard @@ -3521,7 +3521,7 @@ __metadata: "@hcaptcha/react-hcaptcha": ^0.3.6 "@insertish/vite-plugin-babel-macros": ^1.0.5 "@preact/preset-vite": ^2.0.0 - "@revoltchat/ui": 1.0.36 + "@revoltchat/ui": 1.0.39 "@rollup/plugin-replace": ^2.4.2 "@styled-icons/boxicons-logos": ^10.38.0 "@styled-icons/boxicons-regular": ^10.38.0 From 277eaa685d80b41ae86f22989405b0598c3c08c9 Mon Sep 17 00:00:00 2001 From: Paul Makles Date: Fri, 10 Jun 2022 16:53:02 +0100 Subject: [PATCH 048/151] chore(ci): stop mirroring to GitLab [skip ci] --- .github/workflows/mirroring.yml | 17 ----------------- 1 file changed, 17 deletions(-) delete mode 100644 .github/workflows/mirroring.yml diff --git a/.github/workflows/mirroring.yml b/.github/workflows/mirroring.yml deleted file mode 100644 index d196a290..00000000 --- a/.github/workflows/mirroring.yml +++ /dev/null @@ -1,17 +0,0 @@ -name: Mirroring - -on: - push: - branches: - - "master" - - "production" - -jobs: - to_gitlab: - runs-on: ubuntu-18.04 - steps: - - uses: actions/checkout@v1 - - uses: pixta-dev/repository-mirroring-action@v1 - with: - target_repo_url: git@gitlab.com:insert/revolt-vite.git - ssh_private_key: ${{ secrets.GITLAB_SSH_PRIVATE_KEY }} From 71f8fc86a4216cf55b0aa425fe0b279499a68d6a Mon Sep 17 00:00:00 2001 From: Paul Makles Date: Fri, 10 Jun 2022 17:00:37 +0100 Subject: [PATCH 049/151] chore: fix build errors --- src/context/intermediate/modals/Prompt.tsx | 20 +++++++++++++------ .../intermediate/popovers/CreateBot.tsx | 6 ++++-- .../intermediate/popovers/ModifyAccount.tsx | 5 ++++- .../intermediate/popovers/UserPicker.tsx | 2 +- src/pages/settings/Settings.tsx | 2 +- src/types/revolt-api.d.ts | 2 +- 6 files changed, 25 insertions(+), 12 deletions(-) diff --git a/src/context/intermediate/modals/Prompt.tsx b/src/context/intermediate/modals/Prompt.tsx index b3d15395..31fc2d59 100644 --- a/src/context/intermediate/modals/Prompt.tsx +++ b/src/context/intermediate/modals/Prompt.tsx @@ -168,10 +168,11 @@ export const SpecialPromptModal = observer((props: SpecialProps) => { break; } - onClose(); + return true; } catch (err) { setError(takeError(err)); setProcessing(false); + return false; } }, }, @@ -210,10 +211,11 @@ export const SpecialPromptModal = observer((props: SpecialProps) => { try { props.target.delete(); - onClose(); + return true; } catch (err) { setError(takeError(err)); setProcessing(false); + return false; } }, }, @@ -316,10 +318,11 @@ export const SpecialPromptModal = observer((props: SpecialProps) => { }) ?.kick(); - onClose(); + return true; } catch (err) { setError(takeError(err)); setProcessing(false); + return false; } }, }, @@ -366,10 +369,12 @@ export const SpecialPromptModal = observer((props: SpecialProps) => { await props.target.banUser(props.user._id, { reason, }); - onClose(); + + return true; } catch (err) { setError(takeError(err)); setProcessing(false); + return false; } }, }, @@ -438,10 +443,11 @@ export const SpecialPromptModal = observer((props: SpecialProps) => { ); } - onClose(); + return true; } catch (err) { setError(takeError(err)); setProcessing(false); + return false; } }, }, @@ -512,11 +518,13 @@ export const SpecialPromptModal = observer((props: SpecialProps) => { }, ], }); - onClose(); + setProcessing(false); + return true; } catch (err) { setError(takeError(err)); setProcessing(false); + return false; } }, }, diff --git a/src/context/intermediate/popovers/CreateBot.tsx b/src/context/intermediate/popovers/CreateBot.tsx index 515063e7..80a8032c 100644 --- a/src/context/intermediate/popovers/CreateBot.tsx +++ b/src/context/intermediate/popovers/CreateBot.tsx @@ -29,7 +29,6 @@ export function CreateBotModal({ onClose, onCreate }: Props) { try { const { bot } = await client.bots.create({ name }); onCreate(bot); - onClose(); } catch (err) { setError(takeError(err)); } @@ -43,7 +42,10 @@ export function CreateBotModal({ onClose, onCreate }: Props) { { confirmation: true, palette: "accent", - onClick: handleSubmit(onSubmit), + onClick: async () => { + await handleSubmit(onSubmit); + return true; + }, children: , }, { diff --git a/src/context/intermediate/popovers/ModifyAccount.tsx b/src/context/intermediate/popovers/ModifyAccount.tsx index e1da7573..dadb6320 100644 --- a/src/context/intermediate/popovers/ModifyAccount.tsx +++ b/src/context/intermediate/popovers/ModifyAccount.tsx @@ -74,7 +74,10 @@ export function ModifyAccountModal({ onClose, field }: Props) { actions={[ { confirmation: true, - onClick: handleSubmit(onSubmit), + onClick: async () => { + await handleSubmit(onSubmit); + return true; + }, children: field === "email" ? ( diff --git a/src/context/intermediate/popovers/UserPicker.tsx b/src/context/intermediate/popovers/UserPicker.tsx index 35af13e2..d1aeecf8 100644 --- a/src/context/intermediate/popovers/UserPicker.tsx +++ b/src/context/intermediate/popovers/UserPicker.tsx @@ -26,7 +26,7 @@ export function UserPicker(props: Props) { actions={[ { children: , - onClick: () => props.callback(selected).then(props.onClose), + onClick: () => props.callback(selected).then(() => true), }, ]}>
diff --git a/src/pages/settings/Settings.tsx b/src/pages/settings/Settings.tsx index 28743654..fd30d6b8 100644 --- a/src/pages/settings/Settings.tsx +++ b/src/pages/settings/Settings.tsx @@ -278,7 +278,7 @@ export default observer(() => { logout()} className={styles.logOut} compact> diff --git a/src/types/revolt-api.d.ts b/src/types/revolt-api.d.ts index 664e6ab1..ae1bb349 100644 --- a/src/types/revolt-api.d.ts +++ b/src/types/revolt-api.d.ts @@ -1,6 +1,6 @@ // TODO: re-export from revolt-api in some way declare type Session = { - _id: string; + _id?: string; token: string; name: string; user_id: string; From bd503782345a41861bceb793f7b20db73c754578 Mon Sep 17 00:00:00 2001 From: Paul Makles Date: Fri, 10 Jun 2022 17:20:31 +0100 Subject: [PATCH 050/151] feat: add account deletion confirmation route --- src/pages/app.tsx | 4 +++ src/pages/login/ConfirmDelete.tsx | 56 +++++++++++++++++++++++++++++++ 2 files changed, 60 insertions(+) create mode 100644 src/pages/login/ConfirmDelete.tsx diff --git a/src/pages/app.tsx b/src/pages/app.tsx index 0fa90d89..89f05b31 100644 --- a/src/pages/app.tsx +++ b/src/pages/app.tsx @@ -13,6 +13,7 @@ import { CheckAuth } from "../context/revoltjs/CheckAuth"; import Invite from "./invite/Invite"; const Login = lazy(() => import("./login/Login")); +const ConfirmDelete = lazy(() => import("./login/ConfirmDelete")); const RevoltApp = lazy(() => import("./RevoltApp")); export function App() { @@ -30,6 +31,9 @@ export function App() { + + + diff --git a/src/pages/login/ConfirmDelete.tsx b/src/pages/login/ConfirmDelete.tsx new file mode 100644 index 00000000..61f164a0 --- /dev/null +++ b/src/pages/login/ConfirmDelete.tsx @@ -0,0 +1,56 @@ +import { Check } from "@styled-icons/boxicons-regular"; +import { useParams } from "react-router-dom"; +import styled from "styled-components"; + +import { useEffect, useState } from "preact/hooks"; + +import { Modal, Preloader } from "@revoltchat/ui"; + +import { useApplicationState } from "../../mobx/State"; + +const Centre = styled.div` + display: flex; + justify-content: center; +`; + +export default function ConfirmDelete() { + const state = useApplicationState(); + const [deleted, setDeleted] = useState(true); + const { token } = useParams<{ token: string }>(); + + useEffect(() => { + state.config + .createClient() + .api.put("/auth/account/delete", { token }) + .then(() => setDeleted(true)); + }, []); + + return ( + + Your account will be deleted in 7 days. +
+ You may contact{" "} +
+ Revolt support + {" "} + to cancel the request if you wish. + + ) : ( + "Contacting the server." + ) + } + nonDismissable> + {deleted ? ( + + + + ) : ( + + )} + + ); +} From 8a2826da9111fb9ddd774ff1feb8344cd74d817b Mon Sep 17 00:00:00 2001 From: Paul Makles Date: Fri, 10 Jun 2022 17:36:59 +0100 Subject: [PATCH 051/151] fix: use `class` in markdown rendering --- src/components/common/Emoji.tsx | 2 +- src/components/markdown/Renderer.tsx | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/components/common/Emoji.tsx b/src/components/common/Emoji.tsx index efd06b65..9be2e8fd 100644 --- a/src/components/common/Emoji.tsx +++ b/src/components/common/Emoji.tsx @@ -73,7 +73,7 @@ export default function Emoji({ } export function generateEmoji(emoji: string) { - return `${emoji}`; } diff --git a/src/components/markdown/Renderer.tsx b/src/components/markdown/Renderer.tsx index 735a5ff5..57070002 100644 --- a/src/components/markdown/Renderer.tsx +++ b/src/components/markdown/Renderer.tsx @@ -48,10 +48,10 @@ export const md: MarkdownIt = MarkdownIt({ const v = Prism.languages[lang]; if (v) { const out = Prism.highlight(str, v, lang); - return `
${lang}
${out}
`; + return `
${lang}
${out}
`; } - return `
${md.utils.escapeHtml(
+        return `
${md.utils.escapeHtml(
             str,
         )}
`; }, From bdc527ebbead6e13a519f0cef7eebfa66705da09 Mon Sep 17 00:00:00 2001 From: Paul Makles Date: Fri, 10 Jun 2022 18:50:14 +0100 Subject: [PATCH 052/151] chore(ci): re-enable mirroring --- .github/workflows/mirroring.yml | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) create mode 100644 .github/workflows/mirroring.yml diff --git a/.github/workflows/mirroring.yml b/.github/workflows/mirroring.yml new file mode 100644 index 00000000..8a2cfdc1 --- /dev/null +++ b/.github/workflows/mirroring.yml @@ -0,0 +1,16 @@ +name: Mirroring + +on: + push: + branches: + - "master" + +jobs: + to_gitlab: + runs-on: ubuntu-18.04 + steps: + - uses: actions/checkout@v1 + - uses: pixta-dev/repository-mirroring-action@v1 + with: + target_repo_url: git@gitlab.com:insert/revolt-vite.git + ssh_private_key: ${{ secrets.GITLAB_SSH_PRIVATE_KEY }} From 8103cc03cf66614afa706c1160b87b219849a44c Mon Sep 17 00:00:00 2001 From: Paul Makles Date: Sun, 12 Jun 2022 12:16:15 +0100 Subject: [PATCH 053/151] chore: move "resend verification" button --- src/pages/login/forms/Form.tsx | 26 ++++++++++++-------------- 1 file changed, 12 insertions(+), 14 deletions(-) diff --git a/src/pages/login/forms/Form.tsx b/src/pages/login/forms/Form.tsx index c278ba65..c6683132 100644 --- a/src/pages/login/forms/Form.tsx +++ b/src/pages/login/forms/Form.tsx @@ -222,20 +222,12 @@ export const Form = observer(({ page, callback }: Props) => { {page === "create" && ( - <> - - {" "} - - - - - - {" "} - - - - - + + {" "} + + + + )} {page === "login" && ( <> @@ -251,6 +243,12 @@ export const Form = observer(({ page, callback }: Props) => { + + {" "} + + + + {import.meta.env.VITE_API_URL && import.meta.env.VITE_API_URL != "https://api.revolt.chat" && ( From 645e1af6dbe0b01ce111ff291145492a440e4d63 Mon Sep 17 00:00:00 2001 From: Paul Makles Date: Sun, 12 Jun 2022 15:07:30 +0100 Subject: [PATCH 054/151] chore: refactor account UI --- package.json | 5 +- src/context/index.tsx | 40 +++-- src/context/modals/components/MFAFlow.tsx | 71 ++++---- src/context/modals/types.ts | 4 +- src/pages/login/forms/FormLogin.tsx | 29 +++- src/pages/settings/panes/Account.tsx | 194 ++++++---------------- yarn.lock | 9 +- 7 files changed, 145 insertions(+), 207 deletions(-) diff --git a/package.json b/package.json index 733f29e9..8bd66244 100644 --- a/package.json +++ b/package.json @@ -162,5 +162,8 @@ "repository": "https://github.com/revoltchat/revite.git", "author": "Paul ", "license": "MIT", - "packageManager": "yarn@3.2.0" + "packageManager": "yarn@3.2.0", + "resolutions": { + "@revoltchat/ui": "portal:../components" + } } diff --git a/src/context/index.tsx b/src/context/index.tsx index 89026b31..f30f885c 100644 --- a/src/context/index.tsx +++ b/src/context/index.tsx @@ -4,12 +4,7 @@ import { ContextMenuTrigger } from "preact-context-menu"; import { Text } from "preact-i18n"; import { useEffect, useState } from "preact/hooks"; -import { - LinkProvider, - Preloader, - TextProvider, - TrigProvider, -} from "@revoltchat/ui"; +import { Preloader, UIProvider } from "@revoltchat/ui"; import { hydrateState } from "../mobx/State"; @@ -20,6 +15,13 @@ import ModalRenderer from "./modals/ModalRenderer"; import Client from "./revoltjs/RevoltClient"; import SyncManager from "./revoltjs/SyncManager"; +const uiContext = { + Link, + Text: Text as any, + Trigger: ContextMenuTrigger, + emitAction: () => {}, +}; + /** * This component provides all of the application's context layers. * @param param0 Provided children @@ -35,21 +37,17 @@ export default function Context({ children }: { children: Children }) { return ( - - - - - - - {children} - - - - - - - - + + + + + {children} + + + + + + ); diff --git a/src/context/modals/components/MFAFlow.tsx b/src/context/modals/components/MFAFlow.tsx index 565c4be6..73c4d0c8 100644 --- a/src/context/modals/components/MFAFlow.tsx +++ b/src/context/modals/components/MFAFlow.tsx @@ -3,7 +3,12 @@ import { Key, Keyboard } from "@styled-icons/boxicons-solid"; import { API } from "revolt.js"; import { Text } from "preact-i18n"; -import { useCallback, useEffect, useState } from "preact/hooks"; +import { + useCallback, + useEffect, + useLayoutEffect, + useState, +} from "preact/hooks"; import { Category, @@ -15,8 +20,6 @@ import { import { noopTrue } from "../../../lib/js"; -import { useApplicationState } from "../../../mobx/State"; - import { ModalProps } from "../types"; const ICONS: Record> = { @@ -34,12 +37,13 @@ function ResponseEntry({ value?: API.MFAResponse; onChange: (v: API.MFAResponse) => void; }) { - if (type === "Password") { - return ( - <> - - - + return ( + <> + + + + + {type === "Password" && ( - - ); - } else { - return null; - } + )} + + ); } /** * MFA ticket creation flow */ -export default function MFAFlow({ - callback, - onClose, - ...props -}: ModalProps<"mfa_flow">) { - const state = useApplicationState(); - +export default function MFAFlow({ onClose, ...props }: ModalProps<"mfa_flow">) { const [methods, setMethods] = useState( props.state === "unknown" ? props.available_methods : undefined, ); + // Current state of the modal const [selectedMethod, setSelected] = useState(); const [response, setResponse] = useState(); + // Fetch available methods if they have not been provided. useEffect(() => { if (!methods && props.state === "known") { props.client.api.get("/auth/mfa/methods").then(setMethods); } }, []); + // Always select first available method if only one available. + useLayoutEffect(() => { + if (methods && methods.length === 1) { + setSelected(methods[0]); + } + }, [methods]); + + // Callback to generate a new ticket or send response back up the chain. const generateTicket = useCallback(async () => { if (response) { - let ticket; - if (props.state === "known") { - ticket = await props.client.api.put( + const ticket = await props.client.api.put( "/auth/mfa/ticket", response, ); + + props.callback(ticket); } else { - ticket = await state.config - .createClient() - .api.put("/auth/mfa/ticket", response, { - headers: { - "X-MFA-Ticket": props.ticket.token, - }, - }); + props.callback(response); } - callback(ticket); return true; } @@ -122,8 +121,12 @@ export default function MFAFlow({ }, { palette: "plain", - children: "Back", - onClick: () => setSelected(undefined), + children: + methods!.length === 1 ? "Cancel" : "Back", + onClick: () => + methods!.length === 1 + ? true + : void setSelected(undefined), }, ] : [ diff --git a/src/context/modals/types.ts b/src/context/modals/types.ts index e15a552c..33b5e3ef 100644 --- a/src/context/modals/types.ts +++ b/src/context/modals/types.ts @@ -5,16 +5,16 @@ export type Modal = { } & ( | ({ type: "mfa_flow"; - callback: (ticket: API.MFATicket) => void; } & ( | { state: "known"; client: Client; + callback: (ticket: API.MFATicket) => void; } | { state: "unknown"; available_methods: API.MFAMethod[]; - ticket: API.MFATicket & { validated: false }; + callback: (response: API.MFAResponse) => void; } )) | { diff --git a/src/pages/login/forms/FormLogin.tsx b/src/pages/login/forms/FormLogin.tsx index e827b197..bde6da9d 100644 --- a/src/pages/login/forms/FormLogin.tsx +++ b/src/pages/login/forms/FormLogin.tsx @@ -4,6 +4,7 @@ import { API } from "revolt.js"; import { useApplicationState } from "../../../mobx/State"; import { useIntermediate } from "../../../context/intermediate/Intermediate"; +import { modalController } from "../../../context/modals"; import { Form } from "./Form"; @@ -43,14 +44,34 @@ export function FormLogin() { // This should be replaced in the future. const client = state.config.createClient(); await client.fetchConfiguration(); - const session = await client.api.post("/auth/session/login", { + + let session = await client.api.post("/auth/session/login", { ...data, friendly_name, }); - if (session.result !== "Success") { - alert("unsupported!"); - return; + if (session.result === "MFA") { + const { allowed_methods } = session; + let mfa_response: API.MFAResponse = await new Promise( + (callback) => + modalController.push({ + type: "mfa_flow", + state: "unknown", + available_methods: allowed_methods, + callback, + }), + ); + + session = await client.api.post("/auth/session/login", { + mfa_response, + mfa_ticket: session.ticket, + friendly_name, + }); + + if (session.result === "MFA") { + // unreachable code + return; + } } const s = session; diff --git a/src/pages/settings/panes/Account.tsx b/src/pages/settings/panes/Account.tsx index bc3849a8..82e58a04 100644 --- a/src/pages/settings/panes/Account.tsx +++ b/src/pages/settings/panes/Account.tsx @@ -1,22 +1,19 @@ import { At, Key, Block } from "@styled-icons/boxicons-regular"; -import { - Envelope, - HelpCircle, - Lock, - Trash, - Pencil, -} from "@styled-icons/boxicons-solid"; +import { Envelope, Lock, Trash, Pencil } from "@styled-icons/boxicons-solid"; import { observer } from "mobx-react-lite"; -import { useHistory } from "react-router-dom"; -import { API } from "revolt.js"; +import { Link } from "react-router-dom"; import styles from "./Panes.module.scss"; import { Text } from "preact-i18n"; import { useContext, useEffect, useState } from "preact/hooks"; -import { Button, CategoryButton, LineDivider, Tip } from "@revoltchat/ui"; - -import { stopPropagation } from "../../../lib/stopPropagation"; +import { + AccountDetail, + CategoryButton, + Column, + HiddenValue, + Tip, +} from "@revoltchat/ui"; import { useIntermediate } from "../../../context/intermediate/Intermediate"; import { modalController } from "../../../context/modals"; @@ -27,26 +24,14 @@ import { useClient, } from "../../../context/revoltjs/RevoltClient"; -import Tooltip from "../../../components/common/Tooltip"; -import UserIcon from "../../../components/common/user/UserIcon"; - export const Account = observer(() => { - const { openScreen, writeClipboard } = useIntermediate(); + const { openScreen } = useIntermediate(); const logOut = useContext(LogOutContext); const status = useContext(StatusContext); const client = useClient(); const [email, setEmail] = useState("..."); - const [revealEmail, setRevealEmail] = useState(false); - const [profile, setProfile] = useState( - undefined, - ); - const history = useHistory(); - - function switchPage(to: string) { - history.replace(`/settings/${to}`); - } useEffect(() => { if (email === "..." && status === ClientStatus.ONLINE) { @@ -54,124 +39,48 @@ export const Account = observer(() => { .get("/auth/account/") .then((account) => setEmail(account.email)); } - - if (profile === undefined && status === ClientStatus.ONLINE) { - client - .user!.fetchProfile() - .then((profile) => setProfile(profile ?? {})); - } - }, [client, email, profile, status]); + }, [client, email, status]); return (
-
-
- switchPage("profile")} - /> -
-
- switchPage("profile")} - /> -
- @{client.user!.username} -
-
- -
-
+ + + + + {( + [ + ["username", client.user!.username, At], + ["email", email, Envelope], + ["password", "•••••••••", Key], + ] as const + ).map(([field, value, Icon]) => ( + } + description={ + field === "email" ? ( + + ) : ( + value + ) + } + account + action={} + onClick={() => + openScreen({ + id: "modify_account", + field, + }) + }> + + + ))} - -
-
- {( - [ - [ - "username", - client.user!.username, - , - ], - ["email", email, ], - ["password", "•••••••••", ], - ] as const - ).map(([field, value, icon]) => ( - - {value}{" "} - - stopPropagation( - ev, - setRevealEmail(false), - ) - }> - - - - ) : ( - <> - •••••••••••@••••••.•••{" "} - - stopPropagation( - ev, - setRevealEmail(true), - ) - }> - - - - ) - ) : ( - value - ) - } - account - action={} - onClick={() => - openScreen({ - id: "modify_account", - field, - }) - }> - - - ))} -

+

@@ -197,10 +106,13 @@ export const Account = observer(() => { action="chevron"> View my backup codes */} +
+

+
@@ -227,6 +139,7 @@ export const Account = observer(() => { }> + } description={ @@ -250,13 +163,14 @@ export const Account = observer(() => { }> + {" "} - switchPage("profile")}> + - +
); diff --git a/yarn.lock b/yarn.lock index 5e07617b..95441f09 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2220,9 +2220,9 @@ __metadata: languageName: node linkType: hard -"@revoltchat/ui@npm:1.0.39": - version: 1.0.39 - resolution: "@revoltchat/ui@npm:1.0.39" +"@revoltchat/ui@portal:../components::locator=client%40workspace%3A.": + version: 0.0.0-use.local + resolution: "@revoltchat/ui@portal:../components::locator=client%40workspace%3A." dependencies: "@styled-icons/boxicons-logos": ^10.38.0 "@styled-icons/boxicons-regular": ^10.38.0 @@ -2235,9 +2235,8 @@ __metadata: react-device-detect: "*" react-virtuoso: "*" revolt.js: "*" - checksum: 0376ef1e6c90a139da613a0b76d498327c7bad63941d02eb27b9d5b8208f09c01fb45330fc4e0643554a298beee416814dd41fd9992750378491450c6f773ee0 languageName: node - linkType: hard + linkType: soft "@rollup/plugin-babel@npm:^5.2.0": version: 5.3.0 From 8eefc87b05fcb9c3b25160d0bdf40c502124fb7b Mon Sep 17 00:00:00 2001 From: Paul Makles Date: Sun, 12 Jun 2022 15:24:00 +0100 Subject: [PATCH 055/151] chore(refactor): clean up component folder structure --- src/components/README.md | 14 ++ .../settings/account/AccountManagement.tsx | 64 +++++++ .../settings/account/EditAccount.tsx | 75 ++++++++ .../account/MultiFactorAuthentication.tsx | 37 ++++ .../settings/appearance/EmojiSelector.tsx | 8 +- .../Shims.tsx} | 35 ++-- .../settings/appearance/ThemeBaseSelector.tsx | 4 +- .../settings/appearance/{ => assets}/dark.svg | 0 .../appearance/{ => assets}/light.svg | 0 .../appearance/{ => assets}/mutant_emoji.svg | 0 .../appearance/{ => assets}/noto_emoji.svg | 0 .../{ => assets}/openmoji_emoji.svg | 0 .../appearance/{ => assets}/twemoji_emoji.svg | 0 src/pages/settings/panes/Account.tsx | 163 ++---------------- src/pages/settings/panes/Appearance.tsx | 46 ++--- 15 files changed, 246 insertions(+), 200 deletions(-) create mode 100644 src/components/README.md create mode 100644 src/components/settings/account/AccountManagement.tsx create mode 100644 src/components/settings/account/EditAccount.tsx create mode 100644 src/components/settings/account/MultiFactorAuthentication.tsx rename src/components/settings/{AppearanceShims.tsx => appearance/Shims.tsx} (89%) rename src/components/settings/appearance/{ => assets}/dark.svg (100%) rename src/components/settings/appearance/{ => assets}/light.svg (100%) rename src/components/settings/appearance/{ => assets}/mutant_emoji.svg (100%) rename src/components/settings/appearance/{ => assets}/noto_emoji.svg (100%) rename src/components/settings/appearance/{ => assets}/openmoji_emoji.svg (100%) rename src/components/settings/appearance/{ => assets}/twemoji_emoji.svg (100%) diff --git a/src/components/README.md b/src/components/README.md new file mode 100644 index 00000000..2aa9f1cc --- /dev/null +++ b/src/components/README.md @@ -0,0 +1,14 @@ +The following folders should not be added to or modified: + +- `common` +- `markdown` +- `native` +- `ui` + +The following are part-legacy, will remain in place and will be rewritten to some degree still: + +- `navigation` + +The following are mostly good to go: + +- `settings` diff --git a/src/components/settings/account/AccountManagement.tsx b/src/components/settings/account/AccountManagement.tsx new file mode 100644 index 00000000..05d82e97 --- /dev/null +++ b/src/components/settings/account/AccountManagement.tsx @@ -0,0 +1,64 @@ +import { Block } from "@styled-icons/boxicons-regular"; +import { Trash } from "@styled-icons/boxicons-solid"; + +import { Text } from "preact-i18n"; +import { useContext } from "preact/hooks"; + +import { CategoryButton } from "@revoltchat/ui"; + +import { modalController } from "../../../context/modals"; +import { + LogOutContext, + useClient, +} from "../../../context/revoltjs/RevoltClient"; + +export default function AccountManagement() { + const logOut = useContext(LogOutContext); + const client = useClient(); + + const callback = (route: "disable" | "delete") => () => + modalController.push({ + type: "mfa_flow", + state: "known", + client, + callback: ({ token }) => + client.api + .post(`/auth/account/${route}`, undefined, { + headers: { + "X-MFA-Ticket": token, + }, + }) + .then(() => logOut(true)), + }); + + return ( + <> +

+ +

+ +
+ +
+ } + description={ + "Disable your account. You won't be able to access it unless you contact support." + } + action="chevron" + onClick={callback("disable")}> + + + + } + description={ + "Your account will be queued for deletion, a confirmation email will be sent." + } + action="chevron" + onClick={callback("delete")}> + + + + ); +} diff --git a/src/components/settings/account/EditAccount.tsx b/src/components/settings/account/EditAccount.tsx new file mode 100644 index 00000000..ce7a6088 --- /dev/null +++ b/src/components/settings/account/EditAccount.tsx @@ -0,0 +1,75 @@ +import { At } from "@styled-icons/boxicons-regular"; +import { Envelope, Key, Pencil } from "@styled-icons/boxicons-solid"; + +import { Text } from "preact-i18n"; +import { useContext, useEffect, useState } from "preact/hooks"; + +import { + AccountDetail, + CategoryButton, + Column, + HiddenValue, +} from "@revoltchat/ui"; + +import { useIntermediate } from "../../../context/intermediate/Intermediate"; +import { + ClientStatus, + StatusContext, + useClient, +} from "../../../context/revoltjs/RevoltClient"; + +export default function EditAccount() { + const client = useClient(); + const status = useContext(StatusContext); + const { openScreen } = useIntermediate(); + + const [email, setEmail] = useState("..."); + + useEffect(() => { + if (email === "..." && status === ClientStatus.ONLINE) { + client.api + .get("/auth/account/") + .then((account) => setEmail(account.email)); + } + }, [client, email, status]); + + return ( + <> + + + + + {( + [ + ["username", client.user!.username, At], + ["email", email, Envelope], + ["password", "•••••••••", Key], + ] as const + ).map(([field, value, Icon]) => ( + } + description={ + field === "email" ? ( + + ) : ( + value + ) + } + account + action={} + onClick={() => + openScreen({ + id: "modify_account", + field, + }) + }> + + + ))} + + ); +} diff --git a/src/components/settings/account/MultiFactorAuthentication.tsx b/src/components/settings/account/MultiFactorAuthentication.tsx new file mode 100644 index 00000000..e2539918 --- /dev/null +++ b/src/components/settings/account/MultiFactorAuthentication.tsx @@ -0,0 +1,37 @@ +import { Lock } from "@styled-icons/boxicons-solid"; + +import { Text } from "preact-i18n"; + +import { CategoryButton } from "@revoltchat/ui"; + +export default function MultiFactorAuthentication() { + return ( + <> +

+ +

+
+ {/**/} + Two-factor authentication is currently in-development, see{" "} + + tracking issue here + + . +
+ } + description={"Set up 2FA on your account."} + disabled + action={}> + Set up Two-factor authentication + + {/*} + description={"View and download your 2FA backup codes."} + disabled + action="chevron"> + View my backup codes + */} + + ); +} diff --git a/src/components/settings/appearance/EmojiSelector.tsx b/src/components/settings/appearance/EmojiSelector.tsx index 6ef7c346..3dea0564 100644 --- a/src/components/settings/appearance/EmojiSelector.tsx +++ b/src/components/settings/appearance/EmojiSelector.tsx @@ -2,10 +2,10 @@ import styled from "styled-components/macro"; import { Text } from "preact-i18n"; -import mutantSVG from "./mutant_emoji.svg"; -import notoSVG from "./noto_emoji.svg"; -import openmojiSVG from "./openmoji_emoji.svg"; -import twemojiSVG from "./twemoji_emoji.svg"; +import mutantSVG from "./assets/mutant_emoji.svg"; +import notoSVG from "./assets/noto_emoji.svg"; +import openmojiSVG from "./assets/openmoji_emoji.svg"; +import twemojiSVG from "./assets/twemoji_emoji.svg"; import { EmojiPack } from "../../common/Emoji"; diff --git a/src/components/settings/AppearanceShims.tsx b/src/components/settings/appearance/Shims.tsx similarity index 89% rename from src/components/settings/AppearanceShims.tsx rename to src/components/settings/appearance/Shims.tsx index 4e7ea7e8..4eeab82c 100644 --- a/src/components/settings/AppearanceShims.tsx +++ b/src/components/settings/appearance/Shims.tsx @@ -14,9 +14,9 @@ import { Radio, } from "@revoltchat/ui"; -import TextAreaAutoSize from "../../lib/TextAreaAutoSize"; +import TextAreaAutoSize from "../../../lib/TextAreaAutoSize"; -import { useApplicationState } from "../../mobx/State"; +import { useApplicationState } from "../../../mobx/State"; import { Fonts, @@ -25,15 +25,15 @@ import { MonospaceFonts, MONOSPACE_FONTS, MONOSPACE_FONT_KEYS, -} from "../../context/Theme"; +} from "../../../context/Theme"; -import { EmojiSelector } from "./appearance/EmojiSelector"; -import { ThemeBaseSelector } from "./appearance/ThemeBaseSelector"; +import { EmojiSelector } from "./EmojiSelector"; +import { ThemeBaseSelector } from "./ThemeBaseSelector"; /** * Component providing a way to switch the base theme being used. */ -export const ThemeBaseSelectorShim = observer(() => { +export const ShimThemeBaseSelector = observer(() => { const theme = useApplicationState().settings.theme; return ( { /** * Component providing a link to the theme shop. * Only appears if experiment is enabled. - * TODO: stabilise */ -export const ThemeShopShim = () => { +export const ShimThemeShop = () => { return ( { /** * Component providing a way to change current accent colour. */ -export const ThemeAccentShim = observer(() => { +export const ShimThemeAccent = observer(() => { const theme = useApplicationState().settings.theme; return ( <> @@ -90,7 +89,7 @@ export const ThemeAccentShim = observer(() => { /** * Component providing a way to edit custom CSS. */ -export const ThemeCustomCSSShim = observer(() => { +export const ShimThemeCustomCSS = observer(() => { const theme = useApplicationState().settings.theme; return ( <> @@ -111,7 +110,7 @@ export const ThemeCustomCSSShim = observer(() => { /** * Component providing a way to switch between compact and normal message view. */ -export const DisplayCompactShim = () => { +export const ShimDisplayCompact = () => { // TODO: WIP feature return ( <> @@ -146,7 +145,7 @@ export const DisplayCompactShim = () => { /** * Component providing a way to change primary text font. */ -export const DisplayFontShim = observer(() => { +export const ShimDisplayFont = observer(() => { const theme = useApplicationState().settings.theme; return ( <> @@ -169,7 +168,7 @@ export const DisplayFontShim = observer(() => { /** * Component providing a way to change secondary, monospace text font. */ -export const DisplayMonospaceFontShim = observer(() => { +export const ShimDisplayMonospaceFont = observer(() => { const theme = useApplicationState().settings.theme; return ( <> @@ -199,7 +198,7 @@ export const DisplayMonospaceFontShim = observer(() => { /** * Component providing a way to toggle font ligatures. */ -export const DisplayLigaturesShim = observer(() => { +export const ShimDisplayLigatures = observer(() => { const settings = useApplicationState().settings; if (settings.theme.getFont() !== "Inter") return null; @@ -220,7 +219,7 @@ export const DisplayLigaturesShim = observer(() => { /** * Component providing a way to toggle showing the send button on desktop. */ -export const ShowSendButtonShim = observer(() => { +export const ShimShowSendButton = observer(() => { const settings = useApplicationState().settings; return ( @@ -240,7 +239,7 @@ export const ShowSendButtonShim = observer(() => { /** * Component providing a way to toggle seasonal themes. */ -export const DisplaySeasonalShim = observer(() => { +export const ShimDisplaySeasonal = observer(() => { const settings = useApplicationState().settings; return ( @@ -260,7 +259,7 @@ export const DisplaySeasonalShim = observer(() => { /** * Component providing a way to toggle transparency effects. */ -export const DisplayTransparencyShim = observer(() => { +export const ShimDisplayTransparency = observer(() => { const settings = useApplicationState().settings; return ( @@ -280,7 +279,7 @@ export const DisplayTransparencyShim = observer(() => { /** * Component providing a way to change emoji pack. */ -export const DisplayEmojiShim = observer(() => { +export const ShimDisplayEmoji = observer(() => { const settings = useApplicationState().settings; return ( { - const { openScreen } = useIntermediate(); - const logOut = useContext(LogOutContext); - const status = useContext(StatusContext); - - const client = useClient(); - - const [email, setEmail] = useState("..."); - - useEffect(() => { - if (email === "..." && status === ClientStatus.ONLINE) { - client.api - .get("/auth/account/") - .then((account) => setEmail(account.email)); - } - }, [client, email, status]); +import AccountManagement from "../../../components/settings/account/AccountManagement"; +import EditAccount from "../../../components/settings/account/EditAccount"; +import MultiFactorAuthentication from "../../../components/settings/account/MultiFactorAuthentication"; +export function Account() { return (
- - - - - {( - [ - ["username", client.user!.username, At], - ["email", email, Envelope], - ["password", "•••••••••", Key], - ] as const - ).map(([field, value, Icon]) => ( - } - description={ - field === "email" ? ( - - ) : ( - value - ) - } - account - action={} - onClick={() => - openScreen({ - id: "modify_account", - field, - }) - }> - - - ))} - +
-

- -

-
- {/**/} - Two-factor authentication is currently in-development, see{" "} - - tracking issue here - - . -
- } - description={"Set up 2FA on your account."} - disabled - action={}> - Set up Two-factor authentication - - {/*} - description={"View and download your 2FA backup codes."} - disabled - action="chevron"> - View my backup codes - */} - +
-

- -

- -
- -
- } - description={ - "Disable your account. You won't be able to access it unless you contact support." - } - action="chevron" - onClick={() => - modalController.push({ - type: "mfa_flow", - state: "known", - client, - callback: ({ token }) => - client.api - .post("/auth/account/disable", undefined, { - headers: { - "X-MFA-Ticket": token, - }, - }) - .then(() => logOut(true)), - }) - }> - - - - } - description={ - "Your account will be queued for deletion, a confirmation email will be sent." - } - action="chevron" - onClick={() => - modalController.push({ - type: "mfa_flow", - state: "known", - client, - callback: ({ token }) => - client.api - .post("/auth/account/delete", undefined, { - headers: { - "X-MFA-Ticket": token, - }, - }) - .then(() => logOut(true)), - }) - }> - - + +
@@ -174,4 +31,4 @@ export const Account = observer(() => {
); -}); +} diff --git a/src/pages/settings/panes/Appearance.tsx b/src/pages/settings/panes/Appearance.tsx index a907688c..c3c994f4 100644 --- a/src/pages/settings/panes/Appearance.tsx +++ b/src/pages/settings/panes/Appearance.tsx @@ -7,46 +7,46 @@ import { Column } from "@revoltchat/ui"; import CollapsibleSection from "../../../components/common/CollapsibleSection"; import { - ThemeBaseSelectorShim, - ThemeShopShim, - ThemeAccentShim, - DisplayFontShim, - DisplayMonospaceFontShim, - DisplayLigaturesShim, - DisplayEmojiShim, - ThemeCustomCSSShim, - DisplaySeasonalShim, - DisplayTransparencyShim, - ShowSendButtonShim, -} from "../../../components/settings/AppearanceShims"; + ShimThemeBaseSelector, + ShimThemeShop, + ShimThemeAccent, + ShimDisplayFont, + ShimDisplayMonospaceFont, + ShimDisplayLigatures, + ShimDisplayEmoji, + ShimThemeCustomCSS, + ShimDisplaySeasonal, + ShimDisplayTransparency, + ShimShowSendButton, +} from "../../../components/settings/appearance/Shims"; import ThemeOverrides from "../../../components/settings/appearance/ThemeOverrides"; import ThemeTools from "../../../components/settings/appearance/ThemeTools"; export const Appearance = observer(() => { return (
- - + +
- +

- +

- - + +
- - + +
- +
{ id="settings_advanced_appearance" defaultValue={false} summary={}> - - + +
); From c686e85d3788168c16b9f3222838ee026cf5d91b Mon Sep 17 00:00:00 2001 From: Paul Makles Date: Sun, 12 Jun 2022 16:30:37 +0100 Subject: [PATCH 056/151] feat: add MFA recovery codes --- .../settings/account/AccountManagement.tsx | 22 ++-- .../account/MultiFactorAuthentication.tsx | 123 +++++++++++++++--- src/context/modals/ModalRenderer.tsx | 4 +- src/context/modals/components/MFAFlow.tsx | 6 + src/context/modals/components/MFARecovery.tsx | 75 +++++++++++ src/context/modals/index.tsx | 61 +++++++-- src/context/modals/types.ts | 1 + 7 files changed, 247 insertions(+), 45 deletions(-) create mode 100644 src/context/modals/components/MFARecovery.tsx diff --git a/src/components/settings/account/AccountManagement.tsx b/src/components/settings/account/AccountManagement.tsx index 05d82e97..1be77ebd 100644 --- a/src/components/settings/account/AccountManagement.tsx +++ b/src/components/settings/account/AccountManagement.tsx @@ -17,19 +17,15 @@ export default function AccountManagement() { const client = useClient(); const callback = (route: "disable" | "delete") => () => - modalController.push({ - type: "mfa_flow", - state: "known", - client, - callback: ({ token }) => - client.api - .post(`/auth/account/${route}`, undefined, { - headers: { - "X-MFA-Ticket": token, - }, - }) - .then(() => logOut(true)), - }); + modalController.mfaFlow(client).then(({ token }) => + client.api + .post(`/auth/account/${route}`, undefined, { + headers: { + "X-MFA-Ticket": token, + }, + }) + .then(() => logOut(true)), + ); return ( <> diff --git a/src/components/settings/account/MultiFactorAuthentication.tsx b/src/components/settings/account/MultiFactorAuthentication.tsx index e2539918..d6e92537 100644 --- a/src/components/settings/account/MultiFactorAuthentication.tsx +++ b/src/components/settings/account/MultiFactorAuthentication.tsx @@ -1,37 +1,122 @@ +import { ListOl } from "@styled-icons/boxicons-regular"; import { Lock } from "@styled-icons/boxicons-solid"; +import { API } from "revolt.js"; import { Text } from "preact-i18n"; +import { useCallback, useContext, useEffect, useState } from "preact/hooks"; -import { CategoryButton } from "@revoltchat/ui"; +import { CategoryButton, Column, Preloader } from "@revoltchat/ui"; +import { modalController } from "../../../context/modals"; +import { + ClientStatus, + StatusContext, + useClient, +} from "../../../context/revoltjs/RevoltClient"; + +/** + * Temporary helper function for Axios config + * @param token Token + * @returns Headers + */ +export function toConfig(token: string) { + return { + headers: { + "X-MFA-Ticket": token, + }, + }; +} + +/** + * Component for configuring MFA on an account. + */ export default function MultiFactorAuthentication() { + // Pull in prerequisites + const client = useClient(); + const status = useContext(StatusContext); + + // Keep track of MFA state + const [mfa, setMFA] = useState(); + + // Fetch the current MFA status on account + useEffect(() => { + if (!mfa && status === ClientStatus.ONLINE) { + client.api.get("/auth/mfa/").then(setMFA); + } + }, [client, mfa, status]); + + // Action called when recovery code button is pressed + const recoveryAction = useCallback(async () => { + const { token } = await modalController.mfaFlow(client); + + // Decide whether to generate or fetch. + let codes; + if (mfa!.recovery_active) { + codes = await client.api.post( + "/auth/mfa/recovery", + undefined, + toConfig(token), + ); + } else { + codes = await client.api.patch( + "/auth/mfa/recovery", + undefined, + toConfig(token), + ); + + setMFA({ + ...mfa!, + recovery_active: true, + }); + } + + // Display the codes to the user + modalController.push({ + type: "mfa_recovery", + client, + codes, + }); + }, [mfa]); + return ( <>

- {/**/} - Two-factor authentication is currently in-development, see{" "} - - tracking issue here - - . +
+ } - description={"Set up 2FA on your account."} - disabled - action={}> - Set up Two-factor authentication - - {/*} - description={"View and download your 2FA backup codes."} - disabled - action="chevron"> - View my backup codes - */} + description={ + mfa?.recovery_active + ? "View and download your 2FA backup codes." + : "Get ready to use 2FA by setting up a recovery method." + } + disabled={!mfa} + onClick={recoveryAction}> + {mfa?.recovery_active + ? "View backup codes" + : "Generate recovery codes"} +
+ + {JSON.stringify(mfa, undefined, 4)} ); } + +/*} + description={"Set up 2FA on your account."} + disabled + action={}> + Set up Two-factor authentication +*/ +/*} + description={"View and download your 2FA backup codes."} + disabled + action="chevron"> + View my backup codes +*/ diff --git a/src/context/modals/ModalRenderer.tsx b/src/context/modals/ModalRenderer.tsx index 2ed3d350..ab5c0582 100644 --- a/src/context/modals/ModalRenderer.tsx +++ b/src/context/modals/ModalRenderer.tsx @@ -2,6 +2,4 @@ import { observer } from "mobx-react-lite"; import { modalController } from "."; -export default observer(() => { - return modalController.render(); -}); +export default observer(() => modalController.rendered); diff --git a/src/context/modals/components/MFAFlow.tsx b/src/context/modals/components/MFAFlow.tsx index 73c4d0c8..17fb1823 100644 --- a/src/context/modals/components/MFAFlow.tsx +++ b/src/context/modals/components/MFAFlow.tsx @@ -22,12 +22,18 @@ import { noopTrue } from "../../../lib/js"; import { ModalProps } from "../types"; +/** + * Mapping of MFA methods to icons + */ const ICONS: Record> = { Password: Keyboard, Totp: Key, Recovery: Archive, }; +/** + * Component for handling challenge entry + */ function ResponseEntry({ type, value, diff --git a/src/context/modals/components/MFARecovery.tsx b/src/context/modals/components/MFARecovery.tsx new file mode 100644 index 00000000..7a07cc39 --- /dev/null +++ b/src/context/modals/components/MFARecovery.tsx @@ -0,0 +1,75 @@ +import styled from "styled-components"; + +import { useCallback, useState } from "preact/hooks"; + +import { Modal } from "@revoltchat/ui"; + +import { noopTrue } from "../../../lib/js"; + +import { modalController } from ".."; +import { toConfig } from "../../../components/settings/account/MultiFactorAuthentication"; +import { ModalProps } from "../types"; + +/** + * List of recovery codes + */ +const List = styled.div` + display: grid; + text-align: center; + grid-template-columns: 1fr 1fr; + font-family: var(--monospace-font), monospace; + + span { + user-select: text; + } +`; + +/** + * Recovery codes modal + */ +export default function MFARecovery({ + codes, + client, + onClose, +}: ModalProps<"mfa_recovery">) { + // Keep track of changes to recovery codes + const [known, setCodes] = useState(codes); + + // Subroutine to reset recovery codes + const reset = useCallback(async () => { + const { token } = await modalController.mfaFlow(client); + const codes = await client.api.patch( + "/auth/mfa/recovery", + undefined, + toConfig(token), + ); + setCodes(codes); + return false; + }, []); + + return ( + + + {known.map((code) => ( + {code} + ))} + + + ); +} diff --git a/src/context/modals/index.tsx b/src/context/modals/index.tsx index a5843d30..239d489b 100644 --- a/src/context/modals/index.tsx +++ b/src/context/modals/index.tsx @@ -1,7 +1,15 @@ -import { action, computed, makeAutoObservable } from "mobx"; +import { + action, + computed, + makeObservable, + observable, + runInAction, +} from "mobx"; +import type { Client, API } from "revolt.js"; import { ulid } from "ulid"; import MFAFlow from "./components/MFAFlow"; +import MFARecovery from "./components/MFARecovery"; import Test from "./components/Test"; import { Modal } from "./types"; @@ -17,15 +25,19 @@ class ModalController { constructor(components: Components) { this.components = components; - makeAutoObservable(this); - this.pop = this.pop.bind(this); + makeObservable(this, { + stack: observable, + push: action, + remove: action, + rendered: computed, + }); } /** * Display a new modal on the stack * @param modal Modal data */ - @action push(modal: T) { + push(modal: T) { this.stack = [ ...this.stack, { @@ -36,28 +48,57 @@ class ModalController { } /** - * Remove the top modal from the stack + * Remove the keyed modal from the stack */ - @action pop() { - this.stack = this.stack.slice(0, this.stack.length - 1); + remove(key: string) { + this.stack = this.stack.filter((x) => x.key !== key); } /** * Render modals */ - @computed render() { + get rendered() { return ( <> {this.stack.map((modal) => { const Component = this.components[modal.type]; - return ; + return ( + this.remove(modal.key!)} + /> + ); })} ); } } -export const modalController = new ModalController({ +/** + * Modal controller with additional helpers. + */ +class ModalControllerExtended extends ModalController { + /** + * Perform MFA flow + * @param client Client + */ + mfaFlow(client: Client) { + return runInAction( + () => + new Promise((callback: (ticket: API.MFATicket) => void) => + this.push({ + type: "mfa_flow", + state: "known", + client, + callback, + }), + ), + ); + } +} + +export const modalController = new ModalControllerExtended({ mfa_flow: MFAFlow, + mfa_recovery: MFARecovery, test: Test, }); diff --git a/src/context/modals/types.ts b/src/context/modals/types.ts index 33b5e3ef..28e69610 100644 --- a/src/context/modals/types.ts +++ b/src/context/modals/types.ts @@ -17,6 +17,7 @@ export type Modal = { callback: (response: API.MFAResponse) => void; } )) + | { type: "mfa_recovery"; codes: string[]; client: Client } | { type: "test"; } From dbb1c1e8fa01098ee0ef1af61785bfacfb7b2a0f Mon Sep 17 00:00:00 2001 From: Paul Makles Date: Sun, 12 Jun 2022 19:24:59 +0100 Subject: [PATCH 057/151] feat: finalise 2FA login --- external/lang | 2 +- package.json | 1 + .../settings/account/AccountManagement.tsx | 18 +-- .../account/MultiFactorAuthentication.tsx | 119 ++++++++++++++---- .../modals/components/MFAEnableTOTP.tsx | 76 +++++++++++ src/context/modals/components/MFAFlow.tsx | 44 +++++-- src/context/modals/components/MFARecovery.tsx | 18 +-- src/context/modals/index.tsx | 22 +++- src/context/modals/types.ts | 10 +- src/pages/login/forms/FormLogin.tsx | 10 +- yarn.lock | 10 ++ 11 files changed, 277 insertions(+), 53 deletions(-) create mode 100644 src/context/modals/components/MFAEnableTOTP.tsx diff --git a/external/lang b/external/lang index 68f72e01..c9cdbea7 160000 --- a/external/lang +++ b/external/lang @@ -1 +1 @@ -Subproject commit 68f72e01e2c450f9545e05cc702113ee966c0e9a +Subproject commit c9cdbea7edcb22641b9ea372c85a83ef8e1c1d11 diff --git a/package.json b/package.json index 8bd66244..b2e391c3 100644 --- a/package.json +++ b/package.json @@ -137,6 +137,7 @@ "preact-i18n": "^2.4.0-preactx", "prettier": "^2.3.1", "prismjs": "^1.23.0", + "qrcode.react": "^3.0.2", "react-beautiful-dnd": "^13.1.0", "react-device-detect": "2.2.2", "react-helmet": "^6.1.0", diff --git a/src/components/settings/account/AccountManagement.tsx b/src/components/settings/account/AccountManagement.tsx index 1be77ebd..7af4b367 100644 --- a/src/components/settings/account/AccountManagement.tsx +++ b/src/components/settings/account/AccountManagement.tsx @@ -17,14 +17,16 @@ export default function AccountManagement() { const client = useClient(); const callback = (route: "disable" | "delete") => () => - modalController.mfaFlow(client).then(({ token }) => - client.api - .post(`/auth/account/${route}`, undefined, { - headers: { - "X-MFA-Ticket": token, - }, - }) - .then(() => logOut(true)), + modalController.mfaFlow(client).then( + (ticket) => + ticket && + client.api + .post(`/auth/account/${route}`, undefined, { + headers: { + "X-MFA-Ticket": ticket.token, + }, + }) + .then(() => logOut(true)), ); return ( diff --git a/src/components/settings/account/MultiFactorAuthentication.tsx b/src/components/settings/account/MultiFactorAuthentication.tsx index d6e92537..d913d985 100644 --- a/src/components/settings/account/MultiFactorAuthentication.tsx +++ b/src/components/settings/account/MultiFactorAuthentication.tsx @@ -5,7 +5,7 @@ import { API } from "revolt.js"; import { Text } from "preact-i18n"; import { useCallback, useContext, useEffect, useState } from "preact/hooks"; -import { CategoryButton, Column, Preloader } from "@revoltchat/ui"; +import { CategoryButton, Tip } from "@revoltchat/ui"; import { modalController } from "../../../context/modals"; import { @@ -47,21 +47,29 @@ export default function MultiFactorAuthentication() { // Action called when recovery code button is pressed const recoveryAction = useCallback(async () => { - const { token } = await modalController.mfaFlow(client); + // Perform MFA flow first + const ticket = await modalController.mfaFlow(client); + + // Check whether action was cancelled + if (typeof ticket === "undefined") { + return; + } // Decide whether to generate or fetch. let codes; if (mfa!.recovery_active) { + // Fetch existing recovery codes codes = await client.api.post( "/auth/mfa/recovery", undefined, - toConfig(token), + toConfig(ticket.token), ); } else { + // Generate new recovery codes codes = await client.api.patch( "/auth/mfa/recovery", undefined, - toConfig(token), + toConfig(ticket.token), ); setMFA({ @@ -78,6 +86,70 @@ export default function MultiFactorAuthentication() { }); }, [mfa]); + // Action called when TOTP button is pressed + const totpAction = useCallback(async () => { + // Perform MFA flow first + const ticket = await modalController.mfaFlow(client); + + // Check whether action was cancelled + if (typeof ticket === "undefined") { + return; + } + + // Decide whether to disable or enable. + if (mfa!.totp_mfa) { + // Disable TOTP authentication + await client.api.delete("/auth/mfa/totp", toConfig(ticket.token)); + + setMFA({ + ...mfa!, + totp_mfa: false, + }); + } else { + // Generate a TOTP secret + const { secret } = await client.api.post( + "/auth/mfa/totp", + undefined, + toConfig(ticket.token), + ); + + // Open secret modal + let success; + while (!success) { + try { + // Make the user generator a token + const totp_code = await modalController.mfaEnableTOTP( + secret, + client.user!.username, + ); + + if (totp_code) { + // Check whether it is valid + await client.api.put( + "/auth/mfa/totp", + { + totp_code, + }, + toConfig(ticket.token), + ); + + // Mark as successful and activated + success = true; + + setMFA({ + ...mfa!, + totp_mfa: true, + }); + } else { + break; + } + } catch (err) {} + } + } + }, [mfa]); + + const mfaActive = !!mfa?.totp_mfa; + return ( <>

@@ -97,26 +169,29 @@ export default function MultiFactorAuthentication() { disabled={!mfa} onClick={recoveryAction}> {mfa?.recovery_active - ? "View backup codes" - : "Generate recovery codes"} + ? "View Backup Codes" + : "Generate Recovery Codes"} + + + } + description={"Set up time-based one-time password."} + disabled={!mfa || (!mfa.recovery_active && !mfa.totp_mfa)} + onClick={totpAction}> + {mfa?.totp_mfa ? "Disable" : "Enable"} Authenticator App - {JSON.stringify(mfa, undefined, 4)} + {mfa && ( + + {mfaActive + ? "Two-factor authentication is currently on!" + : "Two-factor authentication is currently off!"} + + )} ); } - -/*} - description={"Set up 2FA on your account."} - disabled - action={}> - Set up Two-factor authentication -*/ -/*} - description={"View and download your 2FA backup codes."} - disabled - action="chevron"> - View my backup codes -*/ diff --git a/src/context/modals/components/MFAEnableTOTP.tsx b/src/context/modals/components/MFAEnableTOTP.tsx new file mode 100644 index 00000000..7fc4ee2c --- /dev/null +++ b/src/context/modals/components/MFAEnableTOTP.tsx @@ -0,0 +1,76 @@ +import { QRCodeSVG } from "qrcode.react"; +import styled from "styled-components"; + +import { useState } from "preact/hooks"; + +import { Category, Centred, Column, InputBox, Modal } from "@revoltchat/ui"; + +import { ModalProps } from "../types"; + +const Code = styled.code` + user-select: all; +`; + +/** + * TOTP enable modal + */ +export default function MFAEnableTOTP({ + identifier, + secret, + callback, + onClose, +}: ModalProps<"mfa_enable_totp">) { + const uri = `otpauth://totp/Revolt:${identifier}?secret=${secret}&issuer=Revolt`; + const [value, setValue] = useState(""); + + return ( + { + callback(value.trim().replace(/\s/g, "")); + return true; + }, + confirmation: true, + }, + { + palette: "plain", + children: "Cancel", + onClick: () => { + callback(); + return true; + }, + }, + ]} + onClose={() => { + callback(); + onClose(); + }}> + + + + + + {secret} + + + + Enter Code + + setValue(e.currentTarget.value)} + /> + + ); +} diff --git a/src/context/modals/components/MFAFlow.tsx b/src/context/modals/components/MFAFlow.tsx index 17fb1823..731694e5 100644 --- a/src/context/modals/components/MFAFlow.tsx +++ b/src/context/modals/components/MFAFlow.tsx @@ -18,8 +18,6 @@ import { Preloader, } from "@revoltchat/ui"; -import { noopTrue } from "../../../lib/js"; - import { ModalProps } from "../types"; /** @@ -58,6 +56,24 @@ function ResponseEntry({ } /> )} + + {type === "Totp" && ( + + onChange({ totp_code: e.currentTarget.value }) + } + /> + )} + + {type === "Recovery" && ( + + onChange({ recovery_code: e.currentTarget.value }) + } + /> + )} ); } @@ -129,21 +145,31 @@ export default function MFAFlow({ onClose, ...props }: ModalProps<"mfa_flow">) { palette: "plain", children: methods!.length === 1 ? "Cancel" : "Back", - onClick: () => - methods!.length === 1 - ? true - : void setSelected(undefined), + onClick: () => { + if (methods!.length === 1) { + props.callback(); + return true; + } else { + setSelected(undefined); + } + }, }, ] : [ { palette: "plain", children: "Cancel", - onClick: noopTrue, + onClick: () => { + props.callback(); + return true; + }, }, ] } - onClose={onClose}> + onClose={() => { + props.callback(); + onClose(); + }}> {methods ? ( selectedMethod ? ( ) { action="chevron" icon={} onClick={() => setSelected(method)}> - {method} + ); }) diff --git a/src/context/modals/components/MFARecovery.tsx b/src/context/modals/components/MFARecovery.tsx index 7a07cc39..de8e7f60 100644 --- a/src/context/modals/components/MFARecovery.tsx +++ b/src/context/modals/components/MFARecovery.tsx @@ -37,13 +37,17 @@ export default function MFARecovery({ // Subroutine to reset recovery codes const reset = useCallback(async () => { - const { token } = await modalController.mfaFlow(client); - const codes = await client.api.patch( - "/auth/mfa/recovery", - undefined, - toConfig(token), - ); - setCodes(codes); + const ticket = await modalController.mfaFlow(client); + if (ticket) { + const codes = await client.api.patch( + "/auth/mfa/recovery", + undefined, + toConfig(ticket.token), + ); + + setCodes(codes); + } + return false; }, []); diff --git a/src/context/modals/index.tsx b/src/context/modals/index.tsx index 239d489b..852271ec 100644 --- a/src/context/modals/index.tsx +++ b/src/context/modals/index.tsx @@ -8,6 +8,7 @@ import { import type { Client, API } from "revolt.js"; import { ulid } from "ulid"; +import MFAEnableTOTP from "./components/MFAEnableTOTP"; import MFAFlow from "./components/MFAFlow"; import MFARecovery from "./components/MFARecovery"; import Test from "./components/Test"; @@ -85,7 +86,7 @@ class ModalControllerExtended extends ModalController { mfaFlow(client: Client) { return runInAction( () => - new Promise((callback: (ticket: API.MFATicket) => void) => + new Promise((callback: (ticket?: API.MFATicket) => void) => this.push({ type: "mfa_flow", state: "known", @@ -95,10 +96,29 @@ class ModalControllerExtended extends ModalController { ), ); } + + /** + * Open TOTP secret modal + * @param client Client + */ + mfaEnableTOTP(secret: string, identifier: string) { + return runInAction( + () => + new Promise((callback: (value?: string) => void) => + this.push({ + type: "mfa_enable_totp", + identifier, + secret, + callback, + }), + ), + ); + } } export const modalController = new ModalControllerExtended({ mfa_flow: MFAFlow, mfa_recovery: MFARecovery, + mfa_enable_totp: MFAEnableTOTP, test: Test, }); diff --git a/src/context/modals/types.ts b/src/context/modals/types.ts index 28e69610..4f67b9bf 100644 --- a/src/context/modals/types.ts +++ b/src/context/modals/types.ts @@ -9,15 +9,21 @@ export type Modal = { | { state: "known"; client: Client; - callback: (ticket: API.MFATicket) => void; + callback: (ticket?: API.MFATicket) => void; } | { state: "unknown"; available_methods: API.MFAMethod[]; - callback: (response: API.MFAResponse) => void; + callback: (response?: API.MFAResponse) => void; } )) | { type: "mfa_recovery"; codes: string[]; client: Client } + | { + type: "mfa_enable_totp"; + identifier: string; + secret: string; + callback: (code?: string) => void; + } | { type: "test"; } diff --git a/src/pages/login/forms/FormLogin.tsx b/src/pages/login/forms/FormLogin.tsx index bde6da9d..c358be8f 100644 --- a/src/pages/login/forms/FormLogin.tsx +++ b/src/pages/login/forms/FormLogin.tsx @@ -52,15 +52,19 @@ export function FormLogin() { if (session.result === "MFA") { const { allowed_methods } = session; - let mfa_response: API.MFAResponse = await new Promise( - (callback) => + let mfa_response: API.MFAResponse | undefined = + await new Promise((callback) => modalController.push({ type: "mfa_flow", state: "unknown", available_methods: allowed_methods, callback, }), - ); + ); + + if (typeof mfa_response === "undefined") { + throw "Cancelled"; + } session = await client.api.post("/auth/session/login", { mfa_response, diff --git a/yarn.lock b/yarn.lock index 95441f09..0d7c990e 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3569,6 +3569,7 @@ __metadata: preact-i18n: ^2.4.0-preactx prettier: ^2.3.1 prismjs: ^1.23.0 + qrcode.react: ^3.0.2 react-beautiful-dnd: ^13.1.0 react-device-detect: 2.2.2 react-helmet: ^6.1.0 @@ -6473,6 +6474,15 @@ __metadata: languageName: node linkType: hard +"qrcode.react@npm:^3.0.2": + version: 3.0.2 + resolution: "qrcode.react@npm:3.0.2" + peerDependencies: + react: ^16.8.0 || ^17.0.0 || ^18.0.0 + checksum: 4102e9f416d86808728b93dca4e90cab0b2d3eca2bfe501a26ca62237062ded2121711cfc4edf64832c63e04d34956e26c2e7088023949f9328bbaa56004777d + languageName: node + linkType: hard + "queue-microtask@npm:^1.2.2": version: 1.2.3 resolution: "queue-microtask@npm:1.2.3" From 64f19ec2c03365e42e8a4df5458c315d19086c78 Mon Sep 17 00:00:00 2001 From: Paul Makles Date: Sun, 12 Jun 2022 19:27:18 +0100 Subject: [PATCH 058/151] chore: display error on load if present --- .../account/MultiFactorAuthentication.tsx | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/src/components/settings/account/MultiFactorAuthentication.tsx b/src/components/settings/account/MultiFactorAuthentication.tsx index d913d985..f10ca559 100644 --- a/src/components/settings/account/MultiFactorAuthentication.tsx +++ b/src/components/settings/account/MultiFactorAuthentication.tsx @@ -5,7 +5,7 @@ import { API } from "revolt.js"; import { Text } from "preact-i18n"; import { useCallback, useContext, useEffect, useState } from "preact/hooks"; -import { CategoryButton, Tip } from "@revoltchat/ui"; +import { Category, CategoryButton, Error, Tip } from "@revoltchat/ui"; import { modalController } from "../../../context/modals"; import { @@ -13,6 +13,7 @@ import { StatusContext, useClient, } from "../../../context/revoltjs/RevoltClient"; +import { takeError } from "../../../context/revoltjs/util"; /** * Temporary helper function for Axios config @@ -37,11 +38,15 @@ export default function MultiFactorAuthentication() { // Keep track of MFA state const [mfa, setMFA] = useState(); + const [error, setError] = useState(); // Fetch the current MFA status on account useEffect(() => { if (!mfa && status === ClientStatus.ONLINE) { - client.api.get("/auth/mfa/").then(setMFA); + client.api + .get("/auth/mfa/") + .then(setMFA) + .catch((err) => setError(takeError(err))); } }, [client, mfa, status]); @@ -159,6 +164,12 @@ export default function MultiFactorAuthentication() {

+ {error && ( + + + + )} + } description={ From 56770d40dfc9a1c579ae7214eae026273e2ccf89 Mon Sep 17 00:00:00 2001 From: Paul Makles Date: Sun, 12 Jun 2022 19:29:17 +0100 Subject: [PATCH 059/151] chore: bump language submodule --- external/lang | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/external/lang b/external/lang index c9cdbea7..c47a2eea 160000 --- a/external/lang +++ b/external/lang @@ -1 +1 @@ -Subproject commit c9cdbea7edcb22641b9ea372c85a83ef8e1c1d11 +Subproject commit c47a2eeaad9b6684d8a7c3fe577f0af05a04cc29 From c1324108e36ab2aeae98e6b50700ce52a9a3f1af Mon Sep 17 00:00:00 2001 From: Paul Makles Date: Sun, 12 Jun 2022 19:38:29 +0100 Subject: [PATCH 060/151] fix(eslint): rules included deprecated plugin --- package.json | 19 +---- src/assets/emojis.ts | 2 +- .../navigation/right/MemberList.tsx | 2 +- .../navigation/right/MemberSidebar.tsx | 2 +- .../settings/roles/PermissionSelect.tsx | 4 +- src/context/Theme.tsx | 4 +- src/context/modals/components/MFAFlow.tsx | 4 +- src/context/revoltjs/FileUploads.tsx | 2 +- src/lib/ContextMenus.tsx | 2 +- src/lib/conversion.ts | 2 +- src/mobx/State.ts | 2 +- src/mobx/stores/Auth.ts | 2 +- src/mobx/stores/NotificationOptions.ts | 2 +- src/mobx/stores/Plugins.ts | 22 ++--- src/mobx/stores/Settings.ts | 2 +- src/mobx/stores/helpers/SAudio.ts | 6 +- src/mobx/stores/helpers/STheme.ts | 2 +- src/pages/discover/Discover.tsx | 4 +- src/pages/login/Login.tsx | 8 +- src/pages/login/forms/Form.tsx | 2 +- src/pages/login/forms/FormLogin.tsx | 2 +- src/pages/settings/channel/Permissions.tsx | 2 +- src/sw.ts | 4 +- yarn.lock | 80 ++++++++++++++++++- 24 files changed, 122 insertions(+), 61 deletions(-) diff --git a/package.json b/package.json index b2e391c3..0f158e83 100644 --- a/package.json +++ b/package.json @@ -38,24 +38,6 @@ { "varsIgnorePattern": "^_" } - ], - "require-jsdoc": [ - "error", - { - "require": { - "FunctionDeclaration": true, - "MethodDefinition": true, - "ClassDeclaration": true, - "ArrowFunctionExpression": false, - "FunctionExpression": false - }, - "ignore": { - "MethodDefinition": [ - "toJSON", - "hydrate" - ] - } - } ] } }, @@ -121,6 +103,7 @@ "detect-browser": "^5.2.0", "eslint": "^7.28.0", "eslint-config-preact": "^1.1.4", + "eslint-plugin-jsdoc": "^39.3.2", "eventemitter3": "^4.0.7", "json-stringify-deterministic": "^1.0.2", "localforage": "^1.9.0", diff --git a/src/assets/emojis.ts b/src/assets/emojis.ts index 7488ac14..aeb6764d 100644 --- a/src/assets/emojis.ts +++ b/src/assets/emojis.ts @@ -1850,7 +1850,7 @@ export const emojiDictionary = { scotland: "🏴󠁧󠁢󠁳󠁣󠁴󠁿", wales: "🏴󠁧󠁢󠁷󠁬󠁳󠁿", ...{ - "1984": "custom:1984.gif", + 1984: "custom:1984.gif", KekW: "custom:KekW.png", amogus: "custom:amogus.gif", awaa: "custom:awaa.png", diff --git a/src/components/navigation/right/MemberList.tsx b/src/components/navigation/right/MemberList.tsx index 45aef23b..8b9495ba 100644 --- a/src/components/navigation/right/MemberList.tsx +++ b/src/components/navigation/right/MemberList.tsx @@ -137,7 +137,7 @@ export default function MemberList({ server, see issue{" "} + target="_blank" rel="noreferrer"> #128 {" "} for when this will be resolved. diff --git a/src/components/navigation/right/MemberSidebar.tsx b/src/components/navigation/right/MemberSidebar.tsx index 364f4ef3..37333b45 100644 --- a/src/components/navigation/right/MemberSidebar.tsx +++ b/src/components/navigation/right/MemberSidebar.tsx @@ -182,7 +182,7 @@ export const GroupMemberSidebar = observer( ); // ! FIXME: this is temporary code until we get lazy guilds like subscriptions -const FETCHED: Set = new Set(); +const FETCHED: Set = new Set(); export function resetMemberSidebarFetched() { FETCHED.clear(); diff --git a/src/components/settings/roles/PermissionSelect.tsx b/src/components/settings/roles/PermissionSelect.tsx index 0a8b9742..0afc8c08 100644 --- a/src/components/settings/roles/PermissionSelect.tsx +++ b/src/components/settings/roles/PermissionSelect.tsx @@ -68,13 +68,13 @@ export function PermissionSelect({ } return "Neutral"; - } else { + } if (Long.fromNumber(value).and(permission).eq(permission)) { return "Allow"; } return "Neutral"; - } + }, [value]); function onSwitch(state: State) { diff --git a/src/context/Theme.tsx b/src/context/Theme.tsx index e07e0035..9d8ec18b 100644 --- a/src/context/Theme.tsx +++ b/src/context/Theme.tsx @@ -336,9 +336,9 @@ export const generateVariables = (theme: Theme) => { if (colour) { const [r, g, b] = colour; return `--${key}: ${theme[key]}; --${key}-rgb: ${r}, ${g}, ${b};`; - } else { + } return `--${key}: ${theme[key]};`; - } + }); }; diff --git a/src/context/modals/components/MFAFlow.tsx b/src/context/modals/components/MFAFlow.tsx index 731694e5..0b44c302 100644 --- a/src/context/modals/components/MFAFlow.tsx +++ b/src/context/modals/components/MFAFlow.tsx @@ -149,9 +149,9 @@ export default function MFAFlow({ onClose, ...props }: ModalProps<"mfa_flow">) { if (methods!.length === 1) { props.callback(); return true; - } else { + } setSelected(undefined); - } + }, }, ] diff --git a/src/context/revoltjs/FileUploads.tsx b/src/context/revoltjs/FileUploads.tsx index db246144..4d8a9a0c 100644 --- a/src/context/revoltjs/FileUploads.tsx +++ b/src/context/revoltjs/FileUploads.tsx @@ -72,7 +72,7 @@ export async function uploadFile( return res.data.id; } -var input: HTMLInputElement; +let input: HTMLInputElement; export function grabFiles( maxFileSize: number, cb: (files: File[]) => void, diff --git a/src/lib/ContextMenus.tsx b/src/lib/ContextMenus.tsx index 9f8dabf3..eced77dc 100644 --- a/src/lib/ContextMenus.tsx +++ b/src/lib/ContextMenus.tsx @@ -628,7 +628,7 @@ export default function ContextMenus() { } for (let i = 0; i < actions.length; i++) { - let action = actions[i]; + const action = actions[i]; if (action) { generateAction({ action, diff --git a/src/lib/conversion.ts b/src/lib/conversion.ts index f0840bb0..14469ea8 100644 --- a/src/lib/conversion.ts +++ b/src/lib/conversion.ts @@ -11,7 +11,7 @@ export function urlBase64ToUint8Array(base64String: string) { export function mapToRecord( map: Map, ) { - let record = {} as Record; + const record = {} as Record; map.forEach((v, k) => (record[k] = v)); return record; } diff --git a/src/mobx/State.ts b/src/mobx/State.ts index b79461af..4168809e 100644 --- a/src/mobx/State.ts +++ b/src/mobx/State.ts @@ -293,7 +293,7 @@ export default class State { } } -var state: State; +let state: State; export async function hydrateState() { state = new State(); diff --git a/src/mobx/stores/Auth.ts b/src/mobx/stores/Auth.ts index 8662bb1f..71cea92c 100644 --- a/src/mobx/stores/Auth.ts +++ b/src/mobx/stores/Auth.ts @@ -67,7 +67,7 @@ export default class Auth implements Store, Persistent { typeof data.sessions === "object" && data.sessions !== null ) { - let v = data.sessions; + const v = data.sessions; Object.keys(data.sessions).forEach((id) => this.sessions.set(id, v[id]), ); diff --git a/src/mobx/stores/NotificationOptions.ts b/src/mobx/stores/NotificationOptions.ts index 9632f048..5f685560 100644 --- a/src/mobx/stores/NotificationOptions.ts +++ b/src/mobx/stores/NotificationOptions.ts @@ -203,7 +203,7 @@ export default class NotificationOptions * @returns Whether this object is muted */ isMuted(target?: Channel | Server) { - var value: NotificationState | undefined; + let value: NotificationState | undefined; if (target instanceof Channel) { value = this.computeForChannel(target); } else if (target instanceof Server) { diff --git a/src/mobx/stores/Plugins.ts b/src/mobx/stores/Plugins.ts index 506decb5..08a480d5 100644 --- a/src/mobx/stores/Plugins.ts +++ b/src/mobx/stores/Plugins.ts @@ -124,7 +124,7 @@ export default class Plugins implements Store, Persistent { * @param id Plugin Id */ @computed get(namespace: string, id: string) { - return this.plugins.get(namespace + "/" + id); + return this.plugins.get(`${namespace }/${ id}`); } /** @@ -133,7 +133,7 @@ export default class Plugins implements Store, Persistent { * @returns Plugin Instance */ private getInstance(plugin: Pick) { - return this.instances.get(plugin.namespace + "/" + plugin.id); + return this.instances.get(`${plugin.namespace }/${ plugin.id}`); } /** @@ -154,12 +154,12 @@ export default class Plugins implements Store, Persistent { if (!this.state.experiments.isEnabled("plugins")) return console.error("Enable plugins in experiments!"); - let loaded = this.getInstance(plugin); + const loaded = this.getInstance(plugin); if (loaded) { this.unload(plugin.namespace, plugin.id); } - this.plugins.set(plugin.namespace + "/" + plugin.id, plugin); + this.plugins.set(`${plugin.namespace }/${ plugin.id}`, plugin); if (typeof plugin.enabled === "undefined" || plugin) { this.load(plugin.namespace, plugin.id); @@ -173,7 +173,7 @@ export default class Plugins implements Store, Persistent { */ remove(namespace: string, id: string) { this.unload(namespace, id); - this.plugins.delete(namespace + "/" + id); + this.plugins.delete(`${namespace }/${ id}`); } /** @@ -182,13 +182,13 @@ export default class Plugins implements Store, Persistent { * @param id Plugin Id */ load(namespace: string, id: string) { - let plugin = this.get(namespace, id); + const plugin = this.get(namespace, id); if (!plugin) throw "Unknown plugin!"; try { - let ns = plugin.namespace + "/" + plugin.id; + const ns = `${plugin.namespace }/${ plugin.id}`; - let instance: Instance = eval(plugin.entrypoint)(); + const instance: Instance = eval(plugin.entrypoint)(); this.instances.set(ns, { ...instance, format: plugin.format, @@ -214,11 +214,11 @@ export default class Plugins implements Store, Persistent { * @param id Plugin Id */ unload(namespace: string, id: string) { - let plugin = this.get(namespace, id); + const plugin = this.get(namespace, id); if (!plugin) throw "Unknown plugin!"; - let ns = plugin.namespace + "/" + plugin.id; - let loaded = this.getInstance(plugin); + const ns = `${plugin.namespace }/${ plugin.id}`; + const loaded = this.getInstance(plugin); if (loaded) { loaded.onUnload?.(); this.plugins.set(ns, { diff --git a/src/mobx/stores/Settings.ts b/src/mobx/stores/Settings.ts index 56a36454..cf94e6c6 100644 --- a/src/mobx/stores/Settings.ts +++ b/src/mobx/stores/Settings.ts @@ -159,7 +159,7 @@ export default class Settings @computed private pullKeys(keys: (keyof ISettings)[]) { const obj: Partial = {}; keys.forEach((key) => { - let value = this.get(key); + const value = this.get(key); if (!value) return; (obj as any)[key] = value; }); diff --git a/src/mobx/stores/helpers/SAudio.ts b/src/mobx/stores/helpers/SAudio.ts index fdc4b504..a8256e3e 100644 --- a/src/mobx/stores/helpers/SAudio.ts +++ b/src/mobx/stores/helpers/SAudio.ts @@ -82,11 +82,11 @@ export default class SAudio { getAudio(path: string) { if (this.cache.has(path)) { return this.cache.get(path)!; - } else { + } const el = new Audio(path); this.cache.set(path, el); return el; - } + } loadCache() { @@ -100,7 +100,7 @@ export default class SAudio { try { audio.play(); } catch (err) { - console.error("Hit error while playing", sound + ":", err); + console.error("Hit error while playing", `${sound }:`, err); } } } diff --git a/src/mobx/stores/helpers/STheme.ts b/src/mobx/stores/helpers/STheme.ts index 9249d6ab..cde02cf3 100644 --- a/src/mobx/stores/helpers/STheme.ts +++ b/src/mobx/stores/helpers/STheme.ts @@ -110,7 +110,7 @@ export default class STheme { for (const key of Object.keys(variables)) { const value = variables[key]; if (typeof value === "string") { - variables[key + "-contrast"] = getContrastingColour(value); + variables[`${key }-contrast`] = getContrastingColour(value); } } diff --git a/src/pages/discover/Discover.tsx b/src/pages/discover/Discover.tsx index 7102c532..66fb7268 100644 --- a/src/pages/discover/Discover.tsx +++ b/src/pages/discover/Discover.tsx @@ -120,11 +120,11 @@ export default function Discover() { useEffect(() => { function onMessage(message: MessageEvent) { - let url = new URL(message.origin); + const url = new URL(message.origin); if (!TRUSTED_HOSTS.includes(url.host)) return; try { - let data = JSON.parse(message.data); + const data = JSON.parse(message.data); if (data.source === "discover") { switch (data.type) { case "init": { diff --git a/src/pages/login/Login.tsx b/src/pages/login/Login.tsx index f4f79416..04fa8195 100644 --- a/src/pages/login/Login.tsx +++ b/src/pages/login/Login.tsx @@ -77,17 +77,17 @@ export default observer(() => { @@ -116,7 +116,7 @@ export default observer(() => { + target="_blank" rel="noreferrer"> ‎@fakurian ‏· unsplash.com diff --git a/src/pages/login/forms/Form.tsx b/src/pages/login/forms/Form.tsx index c6683132..2ec8d0eb 100644 --- a/src/pages/login/forms/Form.tsx +++ b/src/pages/login/forms/Form.tsx @@ -260,7 +260,7 @@ export const Form = observer(({ page, callback }: Props) => { + target="_blank" rel="noreferrer"> diff --git a/src/pages/login/forms/FormLogin.tsx b/src/pages/login/forms/FormLogin.tsx index c358be8f..6fc90852 100644 --- a/src/pages/login/forms/FormLogin.tsx +++ b/src/pages/login/forms/FormLogin.tsx @@ -52,7 +52,7 @@ export function FormLogin() { if (session.result === "MFA") { const { allowed_methods } = session; - let mfa_response: API.MFAResponse | undefined = + const mfa_response: API.MFAResponse | undefined = await new Promise((callback) => modalController.push({ type: "mfa_flow", diff --git a/src/pages/settings/channel/Permissions.tsx b/src/pages/settings/channel/Permissions.tsx index 662d3bcc..d728106f 100644 --- a/src/pages/settings/channel/Permissions.tsx +++ b/src/pages/settings/channel/Permissions.tsx @@ -101,7 +101,7 @@ export default observer(({ channel }: Props) => { filter={[ ...(channel.channel_type === "Group" ? [] - : ["ViewChannel" as "ViewChannel"]), + : ["ViewChannel" as const]), "ReadMessageHistory", "SendMessage", "ManageMessages", diff --git a/src/sw.ts b/src/sw.ts index af71b0e2..1daec57c 100644 --- a/src/sw.ts +++ b/src/sw.ts @@ -11,7 +11,7 @@ cleanupOutdatedCaches(); // Generate list using scripts/locale.js // prettier-ignore -var locale_keys = ["af","am","ar-dz","ar-kw","ar-ly","ar-ma","ar-sa","ar-tn","ar","az","be","bg","bi","bm","bn","bo","br","bs","ca","cs","cv","cy","da","de-at","de-ch","de","dv","el","en-au","en-ca","en-gb","en-ie","en-il","en-in","en-nz","en-sg","en-tt","en","eo","es-do","es-pr","es-us","es","et","eu","fa","fi","fo","fr-ca","fr-ch","fr","fy","ga","gd","gl","gom-latn","gu","he","hi","hr","ht","hu","hy-am","id","is","it-ch","it","ja","jv","ka","kk","km","kn","ko","ku","ky","lb","lo","lt","lv","me","mi","mk","ml","mn","mr","ms-my","ms","mt","my","nb","ne","nl-be","nl","nn","oc-lnc","pa-in","pl","pt-br","pt","ro","ru","rw","sd","se","si","sk","sl","sq","sr-cyrl","sr","ss","sv-fi","sv","sw","ta","te","tet","tg","th","tk","tl-ph","tlh","tr","tzl","tzm-latn","tzm","ug-cn","uk","ur","uz-latn","uz","vi","x-pseudo","yo","zh-cn","zh-hk","zh-tw","zh","ang","ar","az","be","bg","bn","bottom","br","ca","ca@valencia","ckb","contributors","cs","cy","da","de","de_CH","el","en","en_US","enchantment","enm","eo","es","et","eu","fa","fi","fil","fr","frm","ga","got","he","hi","hr","hu","id","it","ja","kmr","ko","la","lb","leet","li","lt","lv","mk","ml","ms","mt","nb_NO","nl","owo","peo","piglatin","pl","pr","pt_BR","pt_PT","ro","ro_MD","ru","si","sk","sl","sq","sr","sv","ta","te","th","tlh-qaak","tokipona","tr","uk","vec","vi","zh_Hans","zh_Hant"]; +const locale_keys = ["af","am","ar-dz","ar-kw","ar-ly","ar-ma","ar-sa","ar-tn","ar","az","be","bg","bi","bm","bn","bo","br","bs","ca","cs","cv","cy","da","de-at","de-ch","de","dv","el","en-au","en-ca","en-gb","en-ie","en-il","en-in","en-nz","en-sg","en-tt","en","eo","es-do","es-pr","es-us","es","et","eu","fa","fi","fo","fr-ca","fr-ch","fr","fy","ga","gd","gl","gom-latn","gu","he","hi","hr","ht","hu","hy-am","id","is","it-ch","it","ja","jv","ka","kk","km","kn","ko","ku","ky","lb","lo","lt","lv","me","mi","mk","ml","mn","mr","ms-my","ms","mt","my","nb","ne","nl-be","nl","nn","oc-lnc","pa-in","pl","pt-br","pt","ro","ru","rw","sd","se","si","sk","sl","sq","sr-cyrl","sr","ss","sv-fi","sv","sw","ta","te","tet","tg","th","tk","tl-ph","tlh","tr","tzl","tzm-latn","tzm","ug-cn","uk","ur","uz-latn","uz","vi","x-pseudo","yo","zh-cn","zh-hk","zh-tw","zh","ang","ar","az","be","bg","bn","bottom","br","ca","ca@valencia","ckb","contributors","cs","cy","da","de","de_CH","el","en","en_US","enchantment","enm","eo","es","et","eu","fa","fi","fil","fr","frm","ga","got","he","hi","hr","hu","id","it","ja","kmr","ko","la","lb","leet","li","lt","lv","mk","ml","ms","mt","nb_NO","nl","owo","peo","piglatin","pl","pr","pt_BR","pt_PT","ro","ro_MD","ru","si","sk","sl","sq","sr","sv","ta","te","th","tlh-qaak","tokipona","tr","uk","vec","vi","zh_Hans","zh_Hant"]; precacheAndRoute( self.__WB_MANIFEST.filter((entry) => { @@ -29,7 +29,7 @@ precacheAndRoute( } for (const key of locale_keys) { - if (fn.startsWith(key + ".")) { + if (fn.startsWith(`${key }.`)) { return false; } } diff --git a/yarn.lock b/yarn.lock index 0d7c990e..6e582177 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1797,6 +1797,17 @@ __metadata: languageName: node linkType: hard +"@es-joy/jsdoccomment@npm:~0.31.0": + version: 0.31.0 + resolution: "@es-joy/jsdoccomment@npm:0.31.0" + dependencies: + comment-parser: 1.3.1 + esquery: ^1.4.0 + jsdoc-type-pratt-parser: ~3.1.0 + checksum: 1691ff501559f45593e5f080d2c08dea4fadba5f48e526b9ff2943c050fbb40408f5e83968542e5b6bf47219c7573796d00bfe80dacfd1ba8187904cc475cefb + languageName: node + linkType: hard + "@eslint/eslintrc@npm:^0.4.3": version: 0.4.3 resolution: "@eslint/eslintrc@npm:0.4.3" @@ -3551,6 +3562,7 @@ __metadata: detect-browser: ^5.2.0 eslint: ^7.28.0 eslint-config-preact: ^1.1.4 + eslint-plugin-jsdoc: ^39.3.2 eventemitter3: ^4.0.7 fs-extra: ^10.0.0 json-stringify-deterministic: ^1.0.2 @@ -3682,6 +3694,13 @@ __metadata: languageName: node linkType: hard +"comment-parser@npm:1.3.1": + version: 1.3.1 + resolution: "comment-parser@npm:1.3.1" + checksum: 421e6a113a3afd548500e7174ab46a2049dccf92e82bbaa3b209031b1bdf97552aabfa1ae2a120c0b62df17e1ba70e0d8b05d68504fee78e1ef974c59bcfe718 + languageName: node + linkType: hard + "common-tags@npm:^1.8.0": version: 1.8.0 resolution: "common-tags@npm:1.8.0" @@ -3815,7 +3834,7 @@ __metadata: languageName: node linkType: hard -"debug@npm:4": +"debug@npm:4, debug@npm:^4.3.4": version: 4.3.4 resolution: "debug@npm:4.3.4" dependencies: @@ -4328,6 +4347,23 @@ __metadata: languageName: node linkType: hard +"eslint-plugin-jsdoc@npm:^39.3.2": + version: 39.3.2 + resolution: "eslint-plugin-jsdoc@npm:39.3.2" + dependencies: + "@es-joy/jsdoccomment": ~0.31.0 + comment-parser: 1.3.1 + debug: ^4.3.4 + escape-string-regexp: ^4.0.0 + esquery: ^1.4.0 + semver: ^7.3.7 + spdx-expression-parse: ^3.0.1 + peerDependencies: + eslint: ^7.0.0 || ^8.0.0 + checksum: 2fd3adb23f97c5cc8c03bd8c7338c12074e4e6d49eaee042db65317a69abd389a4c4992fbc9075fa3deabd1d89393b639683f612deac06d89950767571c03457 + languageName: node + linkType: hard + "eslint-plugin-react-hooks@npm:^4.2.0": version: 4.2.0 resolution: "eslint-plugin-react-hooks@npm:4.2.0" @@ -5421,6 +5457,13 @@ __metadata: languageName: node linkType: hard +"jsdoc-type-pratt-parser@npm:~3.1.0": + version: 3.1.0 + resolution: "jsdoc-type-pratt-parser@npm:3.1.0" + checksum: 2f437b57621f1e481918165f6cf0e48256628a9e510d8b3f88a2ab667bf2128bf8b94c628b57c43e78f555ca61983e9c282814703840dc091d2623992214a061 + languageName: node + linkType: hard + "jsesc@npm:^2.5.1": version: 2.5.2 resolution: "jsesc@npm:2.5.2" @@ -7060,6 +7103,17 @@ __metadata: languageName: node linkType: hard +"semver@npm:^7.3.7": + version: 7.3.7 + resolution: "semver@npm:7.3.7" + dependencies: + lru-cache: ^6.0.0 + bin: + semver: bin/semver.js + checksum: 2fa3e877568cd6ce769c75c211beaed1f9fce80b28338cadd9d0b6c40f2e2862bafd62c19a6cff42f3d54292b7c623277bcab8816a2b5521cf15210d43e75232 + languageName: node + linkType: hard + "serialize-javascript@npm:^4.0.0": version: 4.0.0 resolution: "serialize-javascript@npm:4.0.0" @@ -7267,6 +7321,30 @@ __metadata: languageName: node linkType: hard +"spdx-exceptions@npm:^2.1.0": + version: 2.3.0 + resolution: "spdx-exceptions@npm:2.3.0" + checksum: cb69a26fa3b46305637123cd37c85f75610e8c477b6476fa7354eb67c08128d159f1d36715f19be6f9daf4b680337deb8c65acdcae7f2608ba51931540687ac0 + languageName: node + linkType: hard + +"spdx-expression-parse@npm:^3.0.1": + version: 3.0.1 + resolution: "spdx-expression-parse@npm:3.0.1" + dependencies: + spdx-exceptions: ^2.1.0 + spdx-license-ids: ^3.0.0 + checksum: a1c6e104a2cbada7a593eaa9f430bd5e148ef5290d4c0409899855ce8b1c39652bcc88a725259491a82601159d6dc790bedefc9016c7472f7de8de7361f8ccde + languageName: node + linkType: hard + +"spdx-license-ids@npm:^3.0.0": + version: 3.0.11 + resolution: "spdx-license-ids@npm:3.0.11" + checksum: 1da1acb090257773e60b022094050e810ae9fec874dc1461f65dc0400cd42dd830ab2df6e64fb49c2db3dce386dd0362110780e1b154db7c0bb413488836aaeb + languageName: node + linkType: hard + "sprintf-js@npm:~1.0.2": version: 1.0.3 resolution: "sprintf-js@npm:1.0.3" From 7680931f5fd3d1efed9e0543b40f980fdedc1e5c Mon Sep 17 00:00:00 2001 From: Paul Makles Date: Sun, 12 Jun 2022 21:11:23 +0100 Subject: [PATCH 061/151] chore: i18n --- external/lang | 2 +- .../account/MultiFactorAuthentication.tsx | 34 +++++++++++++------ .../modals/components/MFAEnableTOTP.tsx | 15 ++++---- src/context/modals/components/MFAFlow.tsx | 34 +++++++++++++------ src/context/modals/components/MFARecovery.tsx | 13 +++---- 5 files changed, 63 insertions(+), 35 deletions(-) diff --git a/external/lang b/external/lang index c47a2eea..a38d0dc7 160000 --- a/external/lang +++ b/external/lang @@ -1 +1 @@ -Subproject commit c47a2eeaad9b6684d8a7c3fe577f0af05a04cc29 +Subproject commit a38d0dc72a39ea2b5a6f54c1c999f2021b899e50 diff --git a/src/components/settings/account/MultiFactorAuthentication.tsx b/src/components/settings/account/MultiFactorAuthentication.tsx index f10ca559..1b7bfbf8 100644 --- a/src/components/settings/account/MultiFactorAuthentication.tsx +++ b/src/components/settings/account/MultiFactorAuthentication.tsx @@ -173,15 +173,23 @@ export default function MultiFactorAuthentication() { } description={ - mfa?.recovery_active - ? "View and download your 2FA backup codes." - : "Get ready to use 2FA by setting up a recovery method." + } disabled={!mfa} onClick={recoveryAction}> - {mfa?.recovery_active - ? "View Backup Codes" - : "Generate Recovery Codes"} + - {mfa?.totp_mfa ? "Disable" : "Enable"} Authenticator App + {mfa && ( - {mfaActive - ? "Two-factor authentication is currently on!" - : "Two-factor authentication is currently off!"} + )} diff --git a/src/context/modals/components/MFAEnableTOTP.tsx b/src/context/modals/components/MFAEnableTOTP.tsx index 7fc4ee2c..68e5ca84 100644 --- a/src/context/modals/components/MFAEnableTOTP.tsx +++ b/src/context/modals/components/MFAEnableTOTP.tsx @@ -1,6 +1,7 @@ import { QRCodeSVG } from "qrcode.react"; import styled from "styled-components"; +import { Text } from "preact-i18n"; import { useState } from "preact/hooks"; import { Category, Centred, Column, InputBox, Modal } from "@revoltchat/ui"; @@ -25,14 +26,12 @@ export default function MFAEnableTOTP({ return ( } + description={} actions={[ { palette: "primary", - children: "Continue", + children: , onClick: () => { callback(value.trim().replace(/\s/g, "")); return true; @@ -41,7 +40,7 @@ export default function MFAEnableTOTP({ }, { palette: "plain", - children: "Cancel", + children: , onClick: () => { callback(); return true; @@ -65,7 +64,9 @@ export default function MFAEnableTOTP({ - Enter Code + + + ) { return ( } description={ - selectedMethod - ? "Please confirm using selected method." - : "Please select a method to authenticate your request." + } actions={ selectedMethod ? [ { palette: "primary", - children: "Confirm", + children: ( + + ), onClick: generateTicket, confirmation: true, }, { palette: "plain", - children: - methods!.length === 1 ? "Cancel" : "Back", + children: ( + + ), onClick: () => { if (methods!.length === 1) { props.callback(); return true; - } - setSelected(undefined); - + } + setSelected(undefined); }, }, ] : [ { palette: "plain", - children: "Cancel", + children: ( + + ), onClick: () => { props.callback(); return true; diff --git a/src/context/modals/components/MFARecovery.tsx b/src/context/modals/components/MFARecovery.tsx index de8e7f60..27cc37ca 100644 --- a/src/context/modals/components/MFARecovery.tsx +++ b/src/context/modals/components/MFARecovery.tsx @@ -1,5 +1,6 @@ import styled from "styled-components"; +import { Text } from "preact-i18n"; import { useCallback, useState } from "preact/hooks"; import { Modal } from "@revoltchat/ui"; @@ -49,29 +50,29 @@ export default function MFARecovery({ } return false; - }, []); + }, [client]); return ( } + description={} actions={[ { palette: "primary", - children: "Done", + children: , onClick: noopTrue, confirmation: true, }, { palette: "plain", - children: "Reset", + children: , onClick: reset, }, ]} onClose={onClose}> {known.map((code) => ( - {code} + {code} ))} From a404ff7fe052134dd584d21f7bb2dba56cac2ea2 Mon Sep 17 00:00:00 2001 From: Paul Makles Date: Sun, 12 Jun 2022 21:16:42 +0100 Subject: [PATCH 062/151] feat: add auto-update and out-of-date indicator --- README.md | 23 ++++---- external/lang | 2 +- package.json | 5 +- src/components/common/UpdateIndicator.tsx | 2 +- src/context/modals/components/OutOfDate.tsx | 47 +++++++++++++++ src/context/modals/index.tsx | 2 + src/context/modals/types.ts | 4 ++ src/main.tsx | 15 +---- src/updateWorker.ts | 64 +++++++++++++++++++++ yarn.lock | 9 +++ 10 files changed, 145 insertions(+), 28 deletions(-) create mode 100644 src/context/modals/components/OutOfDate.tsx create mode 100644 src/updateWorker.ts diff --git a/README.md b/README.md index 60a1cdf4..0d5cd8c0 100644 --- a/README.md +++ b/README.md @@ -52,17 +52,18 @@ You can now access the client at http://local.revolt.chat:3000. ## CLI Commands -| Command | Description | -| ------------------- | -------------------------------------------- | -| `yarn pull` | Setup assets required for Revite. | -| `yarn dev` | Start the Revolt client in development mode. | -| `yarn build` | Build the Revolt client. | -| `yarn preview` | Start a local server with the built client. | -| `yarn lint` | Run ESLint on the client. | -| `yarn fmt` | Run Prettier on the client. | -| `yarn typecheck` | Run TypeScript type checking on the client. | -| `yarn start` | Start a local sirv server with built client. | -| `yarn start:inject` | Inject a given API URL and start server. | +| Command | Description | +| --------------------------------------- | -------------------------------------------- | +| `yarn pull` | Setup assets required for Revite. | +| `yarn dev` | Start the Revolt client in development mode. | +| `yarn build` | Build the Revolt client. | +| `yarn preview` | Start a local server with the built client. | +| `yarn lint` | Run ESLint on the client. | +| `yarn fmt` | Run Prettier on the client. | +| `yarn typecheck` | Run TypeScript type checking on the client. | +| `yarn start` | Start a local sirv server with built client. | +| `yarn start:inject` | Inject a given API URL and start server. | +| `yarn lint \| egrep "no-literals" -B 1` | Scan for untranslated strings. | ## License diff --git a/external/lang b/external/lang index a38d0dc7..30964859 160000 --- a/external/lang +++ b/external/lang @@ -1 +1 @@ -Subproject commit a38d0dc72a39ea2b5a6f54c1c999f2021b899e50 +Subproject commit 309648592801a3bb5c1fa1702753f8dadde56cae diff --git a/package.json b/package.json index 0f158e83..86dc0c7e 100644 --- a/package.json +++ b/package.json @@ -38,7 +38,8 @@ { "varsIgnorePattern": "^_" } - ] + ], + "react/jsx-no-literals": "warn" } }, "dependencies": { @@ -92,6 +93,7 @@ "@types/react-helmet": "^6.1.1", "@types/react-router-dom": "^5.1.7", "@types/react-scroll": "^1.8.2", + "@types/semver": "^7", "@types/styled-components": "^5.1.10", "@types/twemoji": "^12.1.1", "@typescript-eslint/eslint-plugin": "^4.27.0", @@ -132,6 +134,7 @@ "revolt.js": "6.0.3", "rimraf": "^3.0.2", "sass": "^1.35.1", + "semver": "^7.3.7", "shade-blend-color": "^1.0.0", "stacktrace-js": "^2.0.2", "styled-components": "^5.3.0", diff --git a/src/components/common/UpdateIndicator.tsx b/src/components/common/UpdateIndicator.tsx index 52ef1ebe..cb3093df 100644 --- a/src/components/common/UpdateIndicator.tsx +++ b/src/components/common/UpdateIndicator.tsx @@ -9,7 +9,7 @@ import { internalSubscribe } from "../../lib/eventEmitter"; import { useApplicationState } from "../../mobx/State"; -import { updateSW } from "../../main"; +import { updateSW } from "../../updateWorker"; import Tooltip from "./Tooltip"; let pendingUpdate = false; diff --git a/src/context/modals/components/OutOfDate.tsx b/src/context/modals/components/OutOfDate.tsx new file mode 100644 index 00000000..6368e56e --- /dev/null +++ b/src/context/modals/components/OutOfDate.tsx @@ -0,0 +1,47 @@ +import { Text } from "preact-i18n"; + +import { Modal } from "@revoltchat/ui"; + +import { noop, noopTrue } from "../../../lib/js"; + +import { APP_VERSION } from "../../../version"; +import { ModalProps } from "../types"; + +export default function OutOfDate({ + onClose, + version, +}: ModalProps<"out_of_date">) { + return ( + } + description={ + <> + +
+ + + } + actions={[ + { + palette: "plain", + onClick: noop, + children: ( + + ), + }, + { + palette: "plain-secondary", + onClick: noopTrue, + children: ( + + ), + }, + ]} + onClose={onClose} + nonDismissable + /> + ); +} diff --git a/src/context/modals/index.tsx b/src/context/modals/index.tsx index 852271ec..824c31f2 100644 --- a/src/context/modals/index.tsx +++ b/src/context/modals/index.tsx @@ -11,6 +11,7 @@ import { ulid } from "ulid"; import MFAEnableTOTP from "./components/MFAEnableTOTP"; import MFAFlow from "./components/MFAFlow"; import MFARecovery from "./components/MFARecovery"; +import OutOfDate from "./components/OutOfDate"; import Test from "./components/Test"; import { Modal } from "./types"; @@ -120,5 +121,6 @@ export const modalController = new ModalControllerExtended({ mfa_flow: MFAFlow, mfa_recovery: MFARecovery, mfa_enable_totp: MFAEnableTOTP, + out_of_date: OutOfDate, test: Test, }); diff --git a/src/context/modals/types.ts b/src/context/modals/types.ts index 4f67b9bf..bbe43c37 100644 --- a/src/context/modals/types.ts +++ b/src/context/modals/types.ts @@ -24,6 +24,10 @@ export type Modal = { secret: string; callback: (code?: string) => void; } + | { + type: "out_of_date"; + version: string; + } | { type: "test"; } diff --git a/src/main.tsx b/src/main.tsx index 28081391..2ba0dd7e 100644 --- a/src/main.tsx +++ b/src/main.tsx @@ -1,21 +1,8 @@ -import { registerSW } from "virtual:pwa-register"; - import "./styles/index.scss"; import { render } from "preact"; -import { internalEmit } from "./lib/eventEmitter"; - import { App } from "./pages/app"; - -export const updateSW = registerSW({ - onNeedRefresh() { - internalEmit("PWA", "update"); - }, - onOfflineReady() { - console.info("Ready to work offline."); - // show a ready to work offline to user - }, -}); +import "./updateWorker"; // eslint-disable-next-line @typescript-eslint/no-non-null-assertion render(, document.getElementById("app")!); diff --git a/src/updateWorker.ts b/src/updateWorker.ts new file mode 100644 index 00000000..4976fe0c --- /dev/null +++ b/src/updateWorker.ts @@ -0,0 +1,64 @@ +import semver from "semver"; +import { ulid } from "ulid"; +import { registerSW } from "virtual:pwa-register"; + +import { internalEmit } from "./lib/eventEmitter"; + +import { modalController } from "./context/modals"; + +import { APP_VERSION } from "./version"; + +const INTERVAL_HOUR = 36e5; + +let forceUpdate = false; +let registration: ServiceWorkerRegistration | undefined; + +export const updateSW = registerSW({ + onNeedRefresh() { + if (forceUpdate) { + updateSW(true); + } else { + internalEmit("PWA", "update"); + } + }, + onOfflineReady() { + console.info("Ready to work offline."); + // show a ready to work offline to user + }, + onRegistered(r) { + registration = r; + + // Check for updates every hour + setInterval(() => r!.update(), INTERVAL_HOUR); + }, +}); + +/** + * Check whether the client is out of date + */ +async function checkVersion() { + const { version } = (await fetch("https://api.revolt.chat/release").then( + (res) => res.json(), + )) as { version: string }; + + if (!semver.satisfies(APP_VERSION, version)) { + // Let the worker know we should immediately refresh + forceUpdate = true; + + // Prompt service worker to update + registration?.update(); + + // Push information that the client is out of date + modalController.push({ + key: ulid(), + type: "out_of_date", + version, + }); + } +} + +if (import.meta.env.VITE_API_URL === "https://api.revolt.chat") { + // Check for critical updates hourly + checkVersion(); + setInterval(checkVersion, INTERVAL_HOUR); +} diff --git a/yarn.lock b/yarn.lock index 6e582177..52c11951 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2730,6 +2730,13 @@ __metadata: languageName: node linkType: hard +"@types/semver@npm:^7": + version: 7.3.9 + resolution: "@types/semver@npm:7.3.9" + checksum: 60bfcfdfa7f937be2c6f4b37ddb6714fb0f27b05fe4cbdfdd596a97d35ed95d13ee410efdd88e72a66449d0384220bf20055ab7d6b5df10de4990fbd20e5cbe0 + languageName: node + linkType: hard + "@types/styled-components@npm:^5.1.10": version: 5.1.13 resolution: "@types/styled-components@npm:5.1.13" @@ -3551,6 +3558,7 @@ __metadata: "@types/react-helmet": ^6.1.1 "@types/react-router-dom": ^5.1.7 "@types/react-scroll": ^1.8.2 + "@types/semver": ^7 "@types/styled-components": ^5.1.10 "@types/twemoji": ^12.1.1 "@typescript-eslint/eslint-plugin": ^4.27.0 @@ -3593,6 +3601,7 @@ __metadata: revolt.js: 6.0.3 rimraf: ^3.0.2 sass: ^1.35.1 + semver: ^7.3.7 shade-blend-color: ^1.0.0 sirv-cli: ^1.0.14 stacktrace-js: ^2.0.2 From 5eabd2861f8e34598851fb3fabc096c148a28f8f Mon Sep 17 00:00:00 2001 From: Paul Makles Date: Sun, 12 Jun 2022 21:19:27 +0100 Subject: [PATCH 063/151] chore: unlink components --- package.json | 7 ++----- yarn.lock | 11 ++++++----- 2 files changed, 8 insertions(+), 10 deletions(-) diff --git a/package.json b/package.json index 86dc0c7e..29008844 100644 --- a/package.json +++ b/package.json @@ -73,7 +73,7 @@ "@hcaptcha/react-hcaptcha": "^0.3.6", "@insertish/vite-plugin-babel-macros": "^1.0.5", "@preact/preset-vite": "^2.0.0", - "@revoltchat/ui": "1.0.39", + "@revoltchat/ui": "1.0.40", "@rollup/plugin-replace": "^2.4.2", "@styled-icons/boxicons-logos": "^10.38.0", "@styled-icons/boxicons-regular": "^10.38.0", @@ -149,8 +149,5 @@ "repository": "https://github.com/revoltchat/revite.git", "author": "Paul ", "license": "MIT", - "packageManager": "yarn@3.2.0", - "resolutions": { - "@revoltchat/ui": "portal:../components" - } + "packageManager": "yarn@3.2.0" } diff --git a/yarn.lock b/yarn.lock index 52c11951..b9489125 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2231,9 +2231,9 @@ __metadata: languageName: node linkType: hard -"@revoltchat/ui@portal:../components::locator=client%40workspace%3A.": - version: 0.0.0-use.local - resolution: "@revoltchat/ui@portal:../components::locator=client%40workspace%3A." +"@revoltchat/ui@npm:1.0.40": + version: 1.0.40 + resolution: "@revoltchat/ui@npm:1.0.40" dependencies: "@styled-icons/boxicons-logos": ^10.38.0 "@styled-icons/boxicons-regular": ^10.38.0 @@ -2246,8 +2246,9 @@ __metadata: react-device-detect: "*" react-virtuoso: "*" revolt.js: "*" + checksum: bc0bc906cdb22e8a31c862d1e87f8bd5c46cb463aa23ad773e9c683514fbe0e52ac44e9eab41dd6aa6e8e207050f9ab0590d6e51b2a4d8af6c0fb2ea899d789f languageName: node - linkType: soft + linkType: hard "@rollup/plugin-babel@npm:^5.2.0": version: 5.3.0 @@ -3538,7 +3539,7 @@ __metadata: "@hcaptcha/react-hcaptcha": ^0.3.6 "@insertish/vite-plugin-babel-macros": ^1.0.5 "@preact/preset-vite": ^2.0.0 - "@revoltchat/ui": 1.0.39 + "@revoltchat/ui": 1.0.40 "@rollup/plugin-replace": ^2.4.2 "@styled-icons/boxicons-logos": ^10.38.0 "@styled-icons/boxicons-regular": ^10.38.0 From cd8ab6739b0a188e7926174e168ce7493fd00c64 Mon Sep 17 00:00:00 2001 From: Paul Makles Date: Sun, 12 Jun 2022 22:19:41 +0100 Subject: [PATCH 064/151] feat: add changelog modal --- external/lang | 2 +- package.json | 2 +- src/assets/changelogs.ts | 38 +++++++ src/context/modals/components/Changelog.tsx | 105 ++++++++++++++++++++ src/context/modals/index.tsx | 2 + src/context/modals/types.ts | 4 + src/context/revoltjs/SyncManager.tsx | 5 +- src/mobx/State.ts | 4 + src/mobx/stores/Changelog.ts | 72 ++++++++++++++ src/mobx/stores/Sync.ts | 4 +- src/pages/settings/Settings.tsx | 5 + 11 files changed, 239 insertions(+), 4 deletions(-) create mode 100644 src/assets/changelogs.ts create mode 100644 src/context/modals/components/Changelog.tsx create mode 100644 src/mobx/stores/Changelog.ts diff --git a/external/lang b/external/lang index 30964859..296a3d98 160000 --- a/external/lang +++ b/external/lang @@ -1 +1 @@ -Subproject commit 309648592801a3bb5c1fa1702753f8dadde56cae +Subproject commit 296a3d982c6a596176c9bbbd55020cd6ce760b9a diff --git a/package.json b/package.json index 29008844..80239d6a 100644 --- a/package.json +++ b/package.json @@ -1,5 +1,5 @@ { - "version": "0.5.3-5", + "version": "0.5.3-7", "scripts": { "dev": "node scripts/setup_assets.js --check && vite", "pull": "node scripts/setup_assets.js", diff --git a/src/assets/changelogs.ts b/src/assets/changelogs.ts new file mode 100644 index 00000000..88b60f6a --- /dev/null +++ b/src/assets/changelogs.ts @@ -0,0 +1,38 @@ +type Element = + | string + | { + type: "image"; + src: string; + }; + +export interface ChangelogPost { + date: Date; + title: string; + content: Element[]; +} + +export const changelogEntries: Record = { + 1: { + date: new Date("2022-06-12T20:39:16.674Z"), + title: "Secure your account with 2FA", + content: [ + "Two-factor authentication is now available to all users, you can now head over to settings to enable recovery codes and an authenticator app.", + { + type: "image", + src: "https://autumn.revolt.chat/attachments/E21kwmuJGcASgkVLiSIW0wV3ggcaOWjW0TQF7cdFNY/image.png", + }, + "Once enabled, you will be prompted on login.", + { + type: "image", + src: "https://autumn.revolt.chat/attachments/LWRYoKR2tE1ggW_Lzm547P1pnrkNgmBaoCAfWvHE74/image.png", + }, + "Other authentication methods coming later, stay tuned!", + ], + }, +}; + +export const changelogEntryArray = Object.keys(changelogEntries).map( + (index) => changelogEntries[index as unknown as number], +); + +export const latestChangelog = changelogEntryArray.length; diff --git a/src/context/modals/components/Changelog.tsx b/src/context/modals/components/Changelog.tsx new file mode 100644 index 00000000..059ed776 --- /dev/null +++ b/src/context/modals/components/Changelog.tsx @@ -0,0 +1,105 @@ +import dayjs from "dayjs"; +import styled from "styled-components"; + +import { Text } from "preact-i18n"; +import { useMemo, useState } from "preact/hooks"; + +import { CategoryButton, Column, Modal } from "@revoltchat/ui"; +import type { Action } from "@revoltchat/ui/esm/components/design/atoms/display/Modal"; + +import { noopTrue } from "../../../lib/js"; + +import { + changelogEntries, + changelogEntryArray, + ChangelogPost, +} from "../../../assets/changelogs"; +import { ModalProps } from "../types"; + +const Image = styled.img` + border-radius: var(--border-radius); +`; + +function RenderLog({ post }: { post: ChangelogPost }) { + return ( + + {post.content.map((entry) => + typeof entry === "string" ? ( + {entry} + ) : ( + + ), + )} + + ); +} + +/** + * Changelog rendering modal + */ +export default function Changelog({ + initial, + onClose, +}: ModalProps<"changelog">) { + const [log, setLog] = useState(initial); + + const entry = useMemo( + () => (log ? changelogEntries[log] : undefined), + [log], + ); + + const actions = useMemo(() => { + const arr: Action[] = [ + { + palette: "primary", + children: , + onClick: noopTrue, + }, + ]; + + if (log) { + arr.push({ + palette: "plain-secondary", + children: , + onClick: () => { + setLog(undefined); + return false; + }, + }); + } + + return arr; + }, [log]); + + return ( + + ) + } + description={ + entry ? ( + dayjs(entry.date).calendar() + ) : ( + + ) + } + actions={actions} + onClose={onClose}> + {entry ? ( + + ) : ( + + {changelogEntryArray.map((entry, index) => ( + setLog(index + 1)}> + {entry.title} + + ))} + + )} + + ); +} diff --git a/src/context/modals/index.tsx b/src/context/modals/index.tsx index 824c31f2..dd10f78d 100644 --- a/src/context/modals/index.tsx +++ b/src/context/modals/index.tsx @@ -8,6 +8,7 @@ import { import type { Client, API } from "revolt.js"; import { ulid } from "ulid"; +import Changelog from "./components/Changelog"; import MFAEnableTOTP from "./components/MFAEnableTOTP"; import MFAFlow from "./components/MFAFlow"; import MFARecovery from "./components/MFARecovery"; @@ -118,6 +119,7 @@ class ModalControllerExtended extends ModalController { } export const modalController = new ModalControllerExtended({ + changelog: Changelog, mfa_flow: MFAFlow, mfa_recovery: MFARecovery, mfa_enable_totp: MFAEnableTOTP, diff --git a/src/context/modals/types.ts b/src/context/modals/types.ts index bbe43c37..5b1e061d 100644 --- a/src/context/modals/types.ts +++ b/src/context/modals/types.ts @@ -28,6 +28,10 @@ export type Modal = { type: "out_of_date"; version: string; } + | { + type: "changelog"; + initial?: number; + } | { type: "test"; } diff --git a/src/context/revoltjs/SyncManager.tsx b/src/context/revoltjs/SyncManager.tsx index 33cb78c6..6ed39af4 100644 --- a/src/context/revoltjs/SyncManager.tsx +++ b/src/context/revoltjs/SyncManager.tsx @@ -18,7 +18,10 @@ export default function SyncManager() { // Sync settings from Revolt. useEffect(() => { if (client) { - state.sync.pull(client); + state.sync + .pull(client) + .catch(console.error) + .finally(() => state.changelog.checkForUpdates()); } }, [client]); diff --git a/src/mobx/State.ts b/src/mobx/State.ts index 4168809e..6a9c7cfb 100644 --- a/src/mobx/State.ts +++ b/src/mobx/State.ts @@ -11,6 +11,7 @@ import { legacyMigrateForwards, LegacyState } from "./legacy/redux"; import Persistent from "./interfaces/Persistent"; import Syncable from "./interfaces/Syncable"; import Auth from "./stores/Auth"; +import Changelog from "./stores/Changelog"; import Draft from "./stores/Draft"; import Experiments from "./stores/Experiments"; import Layout from "./stores/Layout"; @@ -32,6 +33,7 @@ export const MIGRATIONS = { */ export default class State { auth: Auth; + changelog: Changelog; draft: Draft; locale: LocaleOptions; experiments: Experiments; @@ -54,6 +56,7 @@ export default class State { */ constructor() { this.auth = new Auth(); + this.changelog = new Changelog(); this.draft = new Draft(); this.locale = new LocaleOptions(); this.experiments = new Experiments(); @@ -147,6 +150,7 @@ export default class State { () => stringify(store.toJSON()), async (value) => { try { + console.log(id, "updated!"); // Save updated store to local storage. await localforage.setItem(id, JSON.parse(value)); diff --git a/src/mobx/stores/Changelog.ts b/src/mobx/stores/Changelog.ts new file mode 100644 index 00000000..410e1518 --- /dev/null +++ b/src/mobx/stores/Changelog.ts @@ -0,0 +1,72 @@ +import { action, makeAutoObservable, runInAction } from "mobx"; + +import { modalController } from "../../context/modals"; + +import { latestChangelog } from "../../assets/changelogs"; +import Persistent from "../interfaces/Persistent"; +import Store from "../interfaces/Store"; +import Syncable from "../interfaces/Syncable"; + +export interface Data { + viewed?: number; +} + +/** + * Keeps track of viewed changelog items + */ +export default class Changelog implements Store, Persistent, Syncable { + /** + * Last viewed changelog ID + */ + private viewed: number; + + /** + * Construct new Layout store. + */ + constructor() { + this.viewed = 0; + makeAutoObservable(this); + } + + get id() { + return "changelog"; + } + + toJSON() { + return { + viewed: this.viewed, + }; + } + + @action hydrate(data: Data) { + if (data.viewed) { + this.viewed = data.viewed; + } + } + + apply(_key: string, data: unknown, _revision: number): void { + this.hydrate(data as Data); + } + + toSyncable(): { [key: string]: object } { + return { + changelog: this.toJSON(), + }; + } + + /** + * Check whether there are new updates + */ + checkForUpdates() { + if (this.viewed < latestChangelog) { + modalController.push({ + type: "changelog", + initial: latestChangelog, + }); + + runInAction(() => { + this.viewed = latestChangelog; + }); + } + } +} diff --git a/src/mobx/stores/Sync.ts b/src/mobx/stores/Sync.ts index a0d4fcfb..52d1de9c 100644 --- a/src/mobx/stores/Sync.ts +++ b/src/mobx/stores/Sync.ts @@ -19,7 +19,8 @@ export type SyncKeys = | "appearance" | "locale" | "notifications" - | "ordering"; + | "ordering" + | "changelog"; export const SYNC_KEYS: SyncKeys[] = [ "theme", @@ -27,6 +28,7 @@ export const SYNC_KEYS: SyncKeys[] = [ "locale", "notifications", "ordering", + "changelog", ]; export interface Data { diff --git a/src/pages/settings/Settings.tsx b/src/pages/settings/Settings.tsx index fd30d6b8..25ed1148 100644 --- a/src/pages/settings/Settings.tsx +++ b/src/pages/settings/Settings.tsx @@ -4,6 +4,7 @@ import { Globe, LogOut, Desktop, + ListUl, } from "@styled-icons/boxicons-regular"; import { Bell, @@ -258,6 +259,10 @@ export default observer(() => { category="pages" custom={ <> + + + + Date: Sun, 12 Jun 2022 22:21:23 +0100 Subject: [PATCH 065/151] fix: make the changelog button in settings work --- external/lang | 2 +- src/pages/settings/Settings.tsx | 11 ++++++++--- 2 files changed, 9 insertions(+), 4 deletions(-) diff --git a/external/lang b/external/lang index 296a3d98..db62af5c 160000 --- a/external/lang +++ b/external/lang @@ -1 +1 @@ -Subproject commit 296a3d982c6a596176c9bbbd55020cd6ce760b9a +Subproject commit db62af5cee441c5d78bf4fc18c40f4aeffafc97d diff --git a/src/pages/settings/Settings.tsx b/src/pages/settings/Settings.tsx index 25ed1148..7b0f5c36 100644 --- a/src/pages/settings/Settings.tsx +++ b/src/pages/settings/Settings.tsx @@ -35,6 +35,7 @@ import { LineDivider } from "@revoltchat/ui"; import { useApplicationState } from "../../mobx/State"; import { useIntermediate } from "../../context/intermediate/Intermediate"; +import { modalController } from "../../context/modals"; import RequiresOnline from "../../context/revoltjs/RequiresOnline"; import { AppContext, LogOutContext } from "../../context/revoltjs/RevoltClient"; @@ -259,9 +260,13 @@ export default observer(() => { category="pages" custom={ <> - + + modalController.push({ type: "changelog" }) + }> - + { - + logout()} className={styles.logOut} From 4e22ccb2f7ce80ced99cf1cf904fcca3ccb9ad21 Mon Sep 17 00:00:00 2001 From: Paul Makles Date: Sun, 12 Jun 2022 22:24:29 +0100 Subject: [PATCH 066/151] chore: duct-tape fix for semver library breaking --- src/updateWorker.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/updateWorker.ts b/src/updateWorker.ts index 4976fe0c..b86d52e3 100644 --- a/src/updateWorker.ts +++ b/src/updateWorker.ts @@ -41,7 +41,7 @@ async function checkVersion() { (res) => res.json(), )) as { version: string }; - if (!semver.satisfies(APP_VERSION, version)) { + if (!semver.satisfies(APP_VERSION, version) && APP_VERSION !== version) { // Let the worker know we should immediately refresh forceUpdate = true; From 3e40a61624d2fa7709b39ba41b9e7b3d42c2adcd Mon Sep 17 00:00:00 2001 From: Leda <38331868+LedaThemis@users.noreply.github.com> Date: Tue, 14 Jun 2022 14:13:31 +0000 Subject: [PATCH 067/151] fix: bug in join server button when in light theme (#660) --- src/pages/invite/Invite.module.scss | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/pages/invite/Invite.module.scss b/src/pages/invite/Invite.module.scss index 36b50ddd..7fb775c9 100644 --- a/src/pages/invite/Invite.module.scss +++ b/src/pages/invite/Invite.module.scss @@ -38,7 +38,7 @@ padding: 32px 16px 16px 16px; background: rgba(0, 0, 0, 0.6); border-radius: var(--border-radius); - + h1 { margin: 0; font-weight: 500; @@ -63,7 +63,6 @@ button { margin: auto; display: block; - background: rgba(0, 0, 0, 0.8); } } } From 2e9c013ed8b5ac2f5bcd84b81b37f44f24fb0d79 Mon Sep 17 00:00:00 2001 From: Leda <38331868+LedaThemis@users.noreply.github.com> Date: Tue, 14 Jun 2022 14:17:00 +0000 Subject: [PATCH 068/151] fix: display voice channel as link in messages (#658) * fix: display voice channel as link in messages * chore: format --- src/components/markdown/Renderer.tsx | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/components/markdown/Renderer.tsx b/src/components/markdown/Renderer.tsx index 57070002..47787a8e 100644 --- a/src/components/markdown/Renderer.tsx +++ b/src/components/markdown/Renderer.tsx @@ -168,7 +168,10 @@ export default function Renderer({ content, disallowBigEmoji }: MarkdownProps) { const id = args[0] as string, channel = client.channels.get(id); - if (channel?.channel_type === "TextChannel") { + if ( + channel?.channel_type === "TextChannel" || + channel?.channel_type === "VoiceChannel" + ) { return `[#${channel.name}](/server/${channel.server_id}/channel/${id})`; } From fc0c7611d433354a5aae82ceb72bb1ca7fe4a4f1 Mon Sep 17 00:00:00 2001 From: Leda <38331868+LedaThemis@users.noreply.github.com> Date: Tue, 14 Jun 2022 14:23:58 +0000 Subject: [PATCH 069/151] fix: bug where channel icon scales with channel name (#661) Co-authored-by: Paul Makles --- src/components/navigation/items/ButtonItem.tsx | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/components/navigation/items/ButtonItem.tsx b/src/components/navigation/items/ButtonItem.tsx index 29877a91..98583a78 100644 --- a/src/components/navigation/items/ButtonItem.tsx +++ b/src/components/navigation/items/ButtonItem.tsx @@ -15,11 +15,13 @@ import { stopPropagation } from "../../../lib/stopPropagation"; import { useIntermediate } from "../../../context/intermediate/Intermediate"; +import { Children } from "../../../types/Preact"; import ChannelIcon from "../../common/ChannelIcon"; import Tooltip from "../../common/Tooltip"; import UserIcon from "../../common/user/UserIcon"; import { Username } from "../../common/user/UserShort"; import UserStatus from "../../common/user/UserStatus"; +import IconButton from "../../ui/IconButton"; type CommonProps = Omit< JSX.HTMLAttributes, @@ -164,11 +166,9 @@ export const ChannelButton = observer((props: ChannelProps) => { channel: channel._id, unread: !!alert, })}> - +
+ +
{channel.name}
{channel.channel_type === "Group" && ( From ba99cbaf2af7f2f153f1a8e33fb7b991efe3a3fb Mon Sep 17 00:00:00 2001 From: Leda <38331868+LedaThemis@users.noreply.github.com> Date: Tue, 14 Jun 2022 14:24:13 +0000 Subject: [PATCH 070/151] fix: bug in user/channel mention when query text is empty (#659) --- src/components/common/AutoComplete.tsx | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/components/common/AutoComplete.tsx b/src/components/common/AutoComplete.tsx index 3d038537..2b7d5196 100644 --- a/src/components/common/AutoComplete.tsx +++ b/src/components/common/AutoComplete.tsx @@ -88,7 +88,7 @@ export function useAutoComplete( ? "emoji" : "user", search.toLowerCase(), - j + 1, + current === ":" ? j + 1 : j, ]; } } @@ -242,7 +242,7 @@ export function useAutoComplete( ); } else if (state.type === "user") { content.splice( - index - 1, + index, search.length + 1, "<@", state.matches[state.selected]._id, @@ -250,7 +250,7 @@ export function useAutoComplete( ); } else { content.splice( - index - 1, + index, search.length + 1, "<#", state.matches[state.selected]._id, From b9da79bc1178a3a000bd0f6edb8866acf39fdd51 Mon Sep 17 00:00:00 2001 From: Paul Makles Date: Tue, 14 Jun 2022 16:27:46 +0100 Subject: [PATCH 071/151] chore: bump `preact-context-menu` --- package.json | 2 +- yarn.lock | 10 +++++----- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/package.json b/package.json index 80239d6a..4db1eacf 100644 --- a/package.json +++ b/package.json @@ -118,7 +118,7 @@ "mobx": "^6.6.0", "mobx-react-lite": "3.4.0", "preact": "^10.5.14", - "preact-context-menu": "0.4.0-patch.0", + "preact-context-menu": "0.4.1", "preact-i18n": "^2.4.0-preactx", "prettier": "^2.3.1", "prismjs": "^1.23.0", diff --git a/yarn.lock b/yarn.lock index b9489125..4ebbce01 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3586,7 +3586,7 @@ __metadata: mobx: ^6.6.0 mobx-react-lite: 3.4.0 preact: ^10.5.14 - preact-context-menu: 0.4.0-patch.0 + preact-context-menu: 0.4.1 preact-i18n: ^2.4.0-preactx prettier: ^2.3.1 prismjs: ^1.23.0 @@ -6409,12 +6409,12 @@ __metadata: languageName: node linkType: hard -"preact-context-menu@npm:0.4.0-patch.0": - version: 0.4.0-patch.0 - resolution: "preact-context-menu@npm:0.4.0-patch.0" +"preact-context-menu@npm:0.4.1": + version: 0.4.1 + resolution: "preact-context-menu@npm:0.4.1" dependencies: preact: ^10.5.14 - checksum: ca2d429f9fc96c1f131f89cb2c72d0105bc3f52c05ad33288e5b0a56b52f1d1ed5d60ae2a0c56535047daaa02c0407c63aadf632a88cfcc4897db8a47123705d + checksum: 0816c1c2024527b5f1b67213d86c6ff4654f626ad19b8fd996dd606278f42f8b742a55d257abc0e79c6c5f0d9c2718a6326b399748b64ee17334158ca30a04f0 languageName: node linkType: hard From 5835064219974797b0e1186be85bae4afc357091 Mon Sep 17 00:00:00 2001 From: Paul Makles Date: Tue, 14 Jun 2022 16:30:42 +0100 Subject: [PATCH 072/151] chore: fix merge conflicts --- src/components/navigation/items/ButtonItem.tsx | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/components/navigation/items/ButtonItem.tsx b/src/components/navigation/items/ButtonItem.tsx index 98583a78..adcd1442 100644 --- a/src/components/navigation/items/ButtonItem.tsx +++ b/src/components/navigation/items/ButtonItem.tsx @@ -15,13 +15,11 @@ import { stopPropagation } from "../../../lib/stopPropagation"; import { useIntermediate } from "../../../context/intermediate/Intermediate"; -import { Children } from "../../../types/Preact"; import ChannelIcon from "../../common/ChannelIcon"; import Tooltip from "../../common/Tooltip"; import UserIcon from "../../common/user/UserIcon"; import { Username } from "../../common/user/UserShort"; import UserStatus from "../../common/user/UserStatus"; -import IconButton from "../../ui/IconButton"; type CommonProps = Omit< JSX.HTMLAttributes, From b44779c89a3ac4e54db9af4a0a13d1323ef6b7dc Mon Sep 17 00:00:00 2001 From: Paul Makles Date: Tue, 14 Jun 2022 17:10:59 +0100 Subject: [PATCH 073/151] fix: user picker checkbox + width fixes #657 fixes #681 --- external/lang | 2 +- src/components/common/user/UserCheckbox.tsx | 17 ++++++++++++----- .../popovers/UserPicker.module.scss | 15 --------------- 3 files changed, 13 insertions(+), 21 deletions(-) diff --git a/external/lang b/external/lang index db62af5c..c8284d49 160000 --- a/external/lang +++ b/external/lang @@ -1 +1 @@ -Subproject commit db62af5cee441c5d78bf4fc18c40f4aeffafc97d +Subproject commit c8284d49b09b108e42fac4a7bdeaf3a447757b61 diff --git a/src/components/common/user/UserCheckbox.tsx b/src/components/common/user/UserCheckbox.tsx index b80ca1c1..15ffce7d 100644 --- a/src/components/common/user/UserCheckbox.tsx +++ b/src/components/common/user/UserCheckbox.tsx @@ -1,6 +1,6 @@ import { User } from "revolt.js"; -import { Checkbox } from "@revoltchat/ui"; +import { Checkbox, Row, Column } from "@revoltchat/ui"; import UserIcon from "./UserIcon"; import { Username } from "./UserShort"; @@ -9,9 +9,16 @@ type UserProps = { value: boolean; onChange: (v: boolean) => void; user: User }; export default function UserCheckbox({ user, ...props }: UserProps) { return ( - - - - + + + + + + + } + /> ); } diff --git a/src/context/intermediate/popovers/UserPicker.module.scss b/src/context/intermediate/popovers/UserPicker.module.scss index 610de5a7..f0c1e9a5 100644 --- a/src/context/intermediate/popovers/UserPicker.module.scss +++ b/src/context/intermediate/popovers/UserPicker.module.scss @@ -1,20 +1,5 @@ .list { - width: 400px; max-width: 100%; max-height: 360px; overflow-y: scroll; - - > label { - > span { - align-items: flex-start !important; - > span { - display: flex; - padding: 4px; - flex-direction: row; - gap: 10px; - justify-content: flex-start; - align-items: center; - } - } - } } From 220a28a1514951f845e2cb152f92368e1bed9776 Mon Sep 17 00:00:00 2001 From: Paul Makles Date: Tue, 14 Jun 2022 17:16:18 +0100 Subject: [PATCH 074/151] fix: bottom nav button alignment fixes #679 --- .../navigation/BottomNavigation.tsx | 23 +++++++++++-------- 1 file changed, 14 insertions(+), 9 deletions(-) diff --git a/src/components/navigation/BottomNavigation.tsx b/src/components/navigation/BottomNavigation.tsx index c7e527ba..69b3e50e 100644 --- a/src/components/navigation/BottomNavigation.tsx +++ b/src/components/navigation/BottomNavigation.tsx @@ -3,7 +3,7 @@ import { observer } from "mobx-react-lite"; import { useHistory, useLocation } from "react-router"; import styled, { css } from "styled-components/macro"; -import { IconButton } from "@revoltchat/ui"; +import { Centred, IconButton } from "@revoltchat/ui"; import ConditionalLink from "../../lib/ConditionalLink"; @@ -27,6 +27,11 @@ const Navbar = styled.div` const Button = styled.a<{ active: boolean }>` flex: 1; + color: var(--foreground); + + a { + color: inherit !important; + } > a, > div, @@ -64,7 +69,7 @@ export default observer(() => { {/* From f0d2e31b179f464cfb0f229ee14be9400a9b295b Mon Sep 17 00:00:00 2001 From: div2005 <46396764+div2005@users.noreply.github.com> Date: Tue, 14 Jun 2022 19:19:18 +0300 Subject: [PATCH 075/151] fix: bottom nav alignment is wrong (#684) Co-authored-by: div2005 Co-authored-by: Paul Makles --- .../navigation/BottomNavigation.tsx | 30 ++++++++++--------- 1 file changed, 16 insertions(+), 14 deletions(-) diff --git a/src/components/navigation/BottomNavigation.tsx b/src/components/navigation/BottomNavigation.tsx index 69b3e50e..d47b61d6 100644 --- a/src/components/navigation/BottomNavigation.tsx +++ b/src/components/navigation/BottomNavigation.tsx @@ -89,11 +89,11 @@ export default observer(() => { {/**/} From c9127d6cf3bae1838b9814d68e299ba2784eedf6 Mon Sep 17 00:00:00 2001 From: Paul Makles Date: Tue, 14 Jun 2022 17:21:52 +0100 Subject: [PATCH 076/151] fix(css): please let the torture stop --- src/components/navigation/BottomNavigation.tsx | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/src/components/navigation/BottomNavigation.tsx b/src/components/navigation/BottomNavigation.tsx index d47b61d6..2daf28a7 100644 --- a/src/components/navigation/BottomNavigation.tsx +++ b/src/components/navigation/BottomNavigation.tsx @@ -25,12 +25,18 @@ const Navbar = styled.div` height: var(--bottom-navigation-height); `; +/** + * I've decided that this whole component + * needs to be re-written 👍👍👍👍👍👍 + */ + const Button = styled.a<{ active: boolean }>` flex: 1; color: var(--foreground); - a { - color: inherit !important; + // ok + * { + color: var(--foreground) !important; } > a, @@ -114,9 +120,7 @@ export default observer(() => { - - From a190a51d0b597370e66de96f7c94592debb7705c Mon Sep 17 00:00:00 2001 From: Paul Makles Date: Tue, 14 Jun 2022 18:01:19 +0100 Subject: [PATCH 077/151] fix(qr): render the QR code consistently --- .../modals/components/MFAEnableTOTP.tsx | 23 +++++++++++++++---- 1 file changed, 18 insertions(+), 5 deletions(-) diff --git a/src/context/modals/components/MFAEnableTOTP.tsx b/src/context/modals/components/MFAEnableTOTP.tsx index 68e5ca84..0e18ef74 100644 --- a/src/context/modals/components/MFAEnableTOTP.tsx +++ b/src/context/modals/components/MFAEnableTOTP.tsx @@ -12,6 +12,17 @@ const Code = styled.code` user-select: all; `; +const Qr = styled.div` + border-radius: 4px; + background: white; + + width: 140px; + height: 140px; + + display: grid; + place-items: center; +`; + /** * TOTP enable modal */ @@ -53,11 +64,13 @@ export default function MFAEnableTOTP({ }}> - + + + {secret} From a1ef1dce5ea5973957e41bcb25fcf787a58a2196 Mon Sep 17 00:00:00 2001 From: Paul Makles Date: Fri, 17 Jun 2022 16:50:48 +0100 Subject: [PATCH 078/151] fix: remove status request closes #685 closes #643 --- src/lib/ContextMenus.tsx | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/lib/ContextMenus.tsx b/src/lib/ContextMenus.tsx index eced77dc..a88c0bc2 100644 --- a/src/lib/ContextMenus.tsx +++ b/src/lib/ContextMenus.tsx @@ -369,9 +369,7 @@ export default function ContextMenus() { case "clear_status": { - const { text: _text, ...status } = - client.user?.status ?? {}; - await client.users.edit({ status }); + await client.users.edit({ remove: ["StatusText"] }); } break; From 34e6995d86b90e6597c501930fa6f879ed3129d5 Mon Sep 17 00:00:00 2001 From: Leda <38331868+LedaThemis@users.noreply.github.com> Date: Fri, 17 Jun 2022 15:51:10 +0000 Subject: [PATCH 079/151] fix: modify account, and create bot forms not being submitted (#697) --- src/context/intermediate/popovers/CreateBot.tsx | 2 +- src/context/intermediate/popovers/ModifyAccount.tsx | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/context/intermediate/popovers/CreateBot.tsx b/src/context/intermediate/popovers/CreateBot.tsx index 80a8032c..65a71443 100644 --- a/src/context/intermediate/popovers/CreateBot.tsx +++ b/src/context/intermediate/popovers/CreateBot.tsx @@ -43,7 +43,7 @@ export function CreateBotModal({ onClose, onCreate }: Props) { confirmation: true, palette: "accent", onClick: async () => { - await handleSubmit(onSubmit); + await handleSubmit(onSubmit)(); return true; }, children: , diff --git a/src/context/intermediate/popovers/ModifyAccount.tsx b/src/context/intermediate/popovers/ModifyAccount.tsx index dadb6320..9feb5ba1 100644 --- a/src/context/intermediate/popovers/ModifyAccount.tsx +++ b/src/context/intermediate/popovers/ModifyAccount.tsx @@ -75,7 +75,7 @@ export function ModifyAccountModal({ onClose, field }: Props) { { confirmation: true, onClick: async () => { - await handleSubmit(onSubmit); + await handleSubmit(onSubmit)(); return true; }, children: From 63d5f6bb7d06660aaecb960793c25433fe67e3bb Mon Sep 17 00:00:00 2001 From: Paul Makles Date: Fri, 17 Jun 2022 16:57:13 +0100 Subject: [PATCH 080/151] fix: show update button on native if frame off --- src/components/common/UpdateIndicator.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/common/UpdateIndicator.tsx b/src/components/common/UpdateIndicator.tsx index cb3093df..25b71b0e 100644 --- a/src/components/common/UpdateIndicator.tsx +++ b/src/components/common/UpdateIndicator.tsx @@ -46,7 +46,7 @@ export default function UpdateIndicator({ style }: Props) { ); } - if (window.isNative) return null; + if (window.isNative && window.native.getConfig().frame) return null; return ( updateSW(true)}> From 0ee7b73d61ed289c96c5966980e2e3f465be768b Mon Sep 17 00:00:00 2001 From: Paul Makles Date: Sat, 18 Jun 2022 11:22:37 +0100 Subject: [PATCH 081/151] feat: re-work modal behaviour to be more natural --- package.json | 2 +- src/context/modals/ModalRenderer.tsx | 19 ++++++++++++++++++- src/context/modals/components/Changelog.tsx | 4 +++- .../modals/components/MFAEnableTOTP.tsx | 5 ++++- src/context/modals/components/MFAFlow.tsx | 16 +++++++++++++++- src/context/modals/components/MFARecovery.tsx | 4 +++- src/context/modals/index.tsx | 18 ++++++++++++++++++ src/context/modals/types.ts | 1 + src/mobx/State.ts | 1 - src/pages/settings/GenericSettings.tsx | 4 ++++ yarn.lock | 10 +++++----- 11 files changed, 72 insertions(+), 12 deletions(-) diff --git a/package.json b/package.json index 4db1eacf..62141717 100644 --- a/package.json +++ b/package.json @@ -73,7 +73,7 @@ "@hcaptcha/react-hcaptcha": "^0.3.6", "@insertish/vite-plugin-babel-macros": "^1.0.5", "@preact/preset-vite": "^2.0.0", - "@revoltchat/ui": "1.0.40", + "@revoltchat/ui": "1.0.43", "@rollup/plugin-replace": "^2.4.2", "@styled-icons/boxicons-logos": "^10.38.0", "@styled-icons/boxicons-regular": "^10.38.0", diff --git a/src/context/modals/ModalRenderer.tsx b/src/context/modals/ModalRenderer.tsx index ab5c0582..0092d93b 100644 --- a/src/context/modals/ModalRenderer.tsx +++ b/src/context/modals/ModalRenderer.tsx @@ -1,5 +1,22 @@ import { observer } from "mobx-react-lite"; +import { useEffect } from "preact/hooks"; + import { modalController } from "."; -export default observer(() => modalController.rendered); +export default observer(() => { + useEffect(() => { + function keyUp(event: KeyboardEvent) { + if (event.key === "Escape") { + modalController.pop("close"); + } else if (event.key === "Enter") { + modalController.pop("confirm"); + } + } + + document.addEventListener("keyup", keyUp); + return () => document.removeEventListener("keyup", keyUp); + }, []); + + return modalController.rendered; +}); diff --git a/src/context/modals/components/Changelog.tsx b/src/context/modals/components/Changelog.tsx index 059ed776..fd6c0498 100644 --- a/src/context/modals/components/Changelog.tsx +++ b/src/context/modals/components/Changelog.tsx @@ -40,6 +40,7 @@ function RenderLog({ post }: { post: ChangelogPost }) { export default function Changelog({ initial, onClose, + signal, }: ModalProps<"changelog">) { const [log, setLog] = useState(initial); @@ -86,7 +87,8 @@ export default function Changelog({ ) } actions={actions} - onClose={onClose}> + onClose={onClose} + signal={signal}> {entry ? ( ) : ( diff --git a/src/context/modals/components/MFAEnableTOTP.tsx b/src/context/modals/components/MFAEnableTOTP.tsx index 0e18ef74..32caa8fd 100644 --- a/src/context/modals/components/MFAEnableTOTP.tsx +++ b/src/context/modals/components/MFAEnableTOTP.tsx @@ -31,6 +31,7 @@ export default function MFAEnableTOTP({ secret, callback, onClose, + signal, }: ModalProps<"mfa_enable_totp">) { const uri = `otpauth://totp/Revolt:${identifier}?secret=${secret}&issuer=Revolt`; const [value, setValue] = useState(""); @@ -61,7 +62,9 @@ export default function MFAEnableTOTP({ onClose={() => { callback(); onClose(); - }}> + }} + signal={signal} + nonDismissable> diff --git a/src/context/modals/components/MFAFlow.tsx b/src/context/modals/components/MFAFlow.tsx index ab0a0180..21bd2e0f 100644 --- a/src/context/modals/components/MFAFlow.tsx +++ b/src/context/modals/components/MFAFlow.tsx @@ -81,7 +81,11 @@ function ResponseEntry({ /** * MFA ticket creation flow */ -export default function MFAFlow({ onClose, ...props }: ModalProps<"mfa_flow">) { +export default function MFAFlow({ + onClose, + signal, + ...props +}: ModalProps<"mfa_flow">) { const [methods, setMethods] = useState( props.state === "unknown" ? props.available_methods : undefined, ); @@ -178,6 +182,16 @@ export default function MFAFlow({ onClose, ...props }: ModalProps<"mfa_flow">) { }, ] } + // If we are logging in or have selected a method, + // don't allow the user to dismiss the modal by clicking off. + // This is to just generally prevent annoying situations + // where you accidentally close the modal while logging in + // or when switching to your password manager. + nonDismissable={ + props.state === "unknown" || + typeof selectedMethod !== "undefined" + } + signal={signal} onClose={() => { props.callback(); onClose(); diff --git a/src/context/modals/components/MFARecovery.tsx b/src/context/modals/components/MFARecovery.tsx index 27cc37ca..bed9afd1 100644 --- a/src/context/modals/components/MFARecovery.tsx +++ b/src/context/modals/components/MFARecovery.tsx @@ -32,6 +32,7 @@ export default function MFARecovery({ codes, client, onClose, + signal, }: ModalProps<"mfa_recovery">) { // Keep track of changes to recovery codes const [known, setCodes] = useState(codes); @@ -69,7 +70,8 @@ export default function MFARecovery({ onClick: reset, }, ]} - onClose={onClose}> + onClose={onClose} + signal={signal}> {known.map((code) => ( {code} diff --git a/src/context/modals/index.tsx b/src/context/modals/index.tsx index dd10f78d..61bc3e01 100644 --- a/src/context/modals/index.tsx +++ b/src/context/modals/index.tsx @@ -31,8 +31,10 @@ class ModalController { makeObservable(this, { stack: observable, push: action, + pop: action, remove: action, rendered: computed, + isVisible: computed, }); } @@ -50,6 +52,16 @@ class ModalController { ]; } + /** + * Remove the top modal from the screen + * @param signal What action to trigger + */ + pop(signal: "close" | "confirm" | "force") { + this.stack = this.stack.map((entry, index) => + index === this.stack.length - 1 ? { ...entry, signal } : entry, + ); + } + /** * Remove the keyed modal from the stack */ @@ -66,6 +78,8 @@ class ModalController { {this.stack.map((modal) => { const Component = this.components[modal.type]; return ( + // ESLint does not understand spread operator + // eslint-disable-next-line this.remove(modal.key!)} @@ -75,6 +89,10 @@ class ModalController { ); } + + get isVisible() { + return this.stack.length > 0; + } } /** diff --git a/src/context/modals/types.ts b/src/context/modals/types.ts index 5b1e061d..be3127af 100644 --- a/src/context/modals/types.ts +++ b/src/context/modals/types.ts @@ -39,4 +39,5 @@ export type Modal = { export type ModalProps = Modal & { type: T } & { onClose: () => void; + signal?: "close" | "confirm"; }; diff --git a/src/mobx/State.ts b/src/mobx/State.ts index 6a9c7cfb..ffc2ebbb 100644 --- a/src/mobx/State.ts +++ b/src/mobx/State.ts @@ -150,7 +150,6 @@ export default class State { () => stringify(store.toJSON()), async (value) => { try { - console.log(id, "updated!"); // Save updated store to local storage. await localforage.setItem(id, JSON.parse(value)); diff --git a/src/pages/settings/GenericSettings.tsx b/src/pages/settings/GenericSettings.tsx index f4f4fc34..0c6e5d94 100644 --- a/src/pages/settings/GenericSettings.tsx +++ b/src/pages/settings/GenericSettings.tsx @@ -13,6 +13,8 @@ import { isTouchscreenDevice } from "../../lib/isTouchscreenDevice"; import { useApplicationState } from "../../mobx/State"; +import { modalController } from "../../context/modals"; + import ButtonItem from "../../components/navigation/items/ButtonItem"; interface Props { @@ -61,6 +63,8 @@ export function GenericSettings({ useEffect(() => { function keyDown(e: KeyboardEvent) { if (e.key === "Escape") { + if (modalController.isVisible) return; + exitSettings(); } } diff --git a/yarn.lock b/yarn.lock index 4ebbce01..88c78fb3 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2231,9 +2231,9 @@ __metadata: languageName: node linkType: hard -"@revoltchat/ui@npm:1.0.40": - version: 1.0.40 - resolution: "@revoltchat/ui@npm:1.0.40" +"@revoltchat/ui@npm:1.0.43": + version: 1.0.43 + resolution: "@revoltchat/ui@npm:1.0.43" dependencies: "@styled-icons/boxicons-logos": ^10.38.0 "@styled-icons/boxicons-regular": ^10.38.0 @@ -2246,7 +2246,7 @@ __metadata: react-device-detect: "*" react-virtuoso: "*" revolt.js: "*" - checksum: bc0bc906cdb22e8a31c862d1e87f8bd5c46cb463aa23ad773e9c683514fbe0e52ac44e9eab41dd6aa6e8e207050f9ab0590d6e51b2a4d8af6c0fb2ea899d789f + checksum: d6a6d0cb4a2f08fea45a4d61e5599894012fbb591472ef95d34ee8ddc9e66cfdc7626e94360b7c104e59d3c64a7d0bd674d6a42f5c3cefc723574db8c1aee64e languageName: node linkType: hard @@ -3539,7 +3539,7 @@ __metadata: "@hcaptcha/react-hcaptcha": ^0.3.6 "@insertish/vite-plugin-babel-macros": ^1.0.5 "@preact/preset-vite": ^2.0.0 - "@revoltchat/ui": 1.0.40 + "@revoltchat/ui": 1.0.43 "@rollup/plugin-replace": ^2.4.2 "@styled-icons/boxicons-logos": ^10.38.0 "@styled-icons/boxicons-regular": ^10.38.0 From 374be319c443ec44470ad7c016722ac3240aeda6 Mon Sep 17 00:00:00 2001 From: Paul Makles Date: Sat, 18 Jun 2022 11:56:05 +0100 Subject: [PATCH 082/151] feat(modals): port SignedOut and SignOutSessions --- src/context/intermediate/Modals.tsx | 6 --- src/context/modals/components/Changelog.tsx | 2 +- src/context/modals/components/OutOfDate.tsx | 4 ++ .../components/SignOutSessions.tsx} | 32 +++++++------- .../components}/SignedOut.tsx | 15 ++++--- src/context/modals/index.tsx | 4 ++ src/context/modals/types.ts | 8 +++- src/context/revoltjs/RevoltClient.tsx | 3 +- src/pages/settings/panes/Sessions.tsx | 42 +++++++------------ 9 files changed, 60 insertions(+), 56 deletions(-) rename src/context/{intermediate/modals/SessionsPrompt.tsx => modals/components/SignOutSessions.tsx} (54%) rename src/context/{intermediate/modals => modals/components}/SignedOut.tsx (57%) diff --git a/src/context/intermediate/Modals.tsx b/src/context/intermediate/Modals.tsx index 9aab5648..55ecb6f8 100644 --- a/src/context/intermediate/Modals.tsx +++ b/src/context/intermediate/Modals.tsx @@ -8,8 +8,6 @@ import { ExternalLinkModal } from "./modals/ExternalLinkPrompt"; import { InputModal } from "./modals/Input"; import { OnboardingModal } from "./modals/Onboarding"; import { PromptModal } from "./modals/Prompt"; -import { SessionsModal } from "./modals/SessionsPrompt"; -import { SignedOutModal } from "./modals/SignedOut"; import { TokenRevealModal } from "./modals/TokenReveal"; export interface Props { @@ -30,8 +28,6 @@ export default function Modals({ screen, openScreen }: Props) { return ; case "error": return ; - case "signed_out": - return ; case "clipboard": return ; case "token_reveal": @@ -40,8 +36,6 @@ export default function Modals({ screen, openScreen }: Props) { return ; case "external_link_prompt": return ; - case "sessions": - return ; } return null; diff --git a/src/context/modals/components/Changelog.tsx b/src/context/modals/components/Changelog.tsx index fd6c0498..217ba109 100644 --- a/src/context/modals/components/Changelog.tsx +++ b/src/context/modals/components/Changelog.tsx @@ -35,7 +35,7 @@ function RenderLog({ post }: { post: ChangelogPost }) { } /** - * Changelog rendering modal + * Changelog modal */ export default function Changelog({ initial, diff --git a/src/context/modals/components/OutOfDate.tsx b/src/context/modals/components/OutOfDate.tsx index 6368e56e..fbbd58fa 100644 --- a/src/context/modals/components/OutOfDate.tsx +++ b/src/context/modals/components/OutOfDate.tsx @@ -7,6 +7,10 @@ import { noop, noopTrue } from "../../../lib/js"; import { APP_VERSION } from "../../../version"; import { ModalProps } from "../types"; +/** + * Out-of-date indicator which instructs users + * that their client needs to be updated + */ export default function OutOfDate({ onClose, version, diff --git a/src/context/intermediate/modals/SessionsPrompt.tsx b/src/context/modals/components/SignOutSessions.tsx similarity index 54% rename from src/context/intermediate/modals/SessionsPrompt.tsx rename to src/context/modals/components/SignOutSessions.tsx index b4df37d8..46afff05 100644 --- a/src/context/intermediate/modals/SessionsPrompt.tsx +++ b/src/context/modals/components/SignOutSessions.tsx @@ -1,31 +1,35 @@ import { Text } from "preact-i18n"; +import { useCallback } from "preact/hooks"; import { Modal } from "@revoltchat/ui"; -interface Props { - onClose: () => void; - confirm: () => void; -} +import { noopTrue } from "../../../lib/js"; + +import { ModalProps } from "../types"; + +/** + * Confirm whether a user wants to sign out of all other sessions + */ +export function SignOutSessions(props: ModalProps<"sign_out_sessions">) { + const onClick = useCallback(() => { + props.onDeleting(); + props.client.api.delete("/auth/session/all").then(props.onDelete); + return true; + }, []); -export function SessionsModal({ onClose, confirm }: Props) { return ( } actions={[ { - onClick: () => { - onClose(); - }, - confirmation: true, + onClick: noopTrue, palette: "accent", + confirmation: true, children: , }, { - onClick: () => { - confirm(); - onClose(); - }, + onClick, confirmation: true, children: , }, diff --git a/src/context/intermediate/modals/SignedOut.tsx b/src/context/modals/components/SignedOut.tsx similarity index 57% rename from src/context/intermediate/modals/SignedOut.tsx rename to src/context/modals/components/SignedOut.tsx index 24f54c3f..21c702e5 100644 --- a/src/context/intermediate/modals/SignedOut.tsx +++ b/src/context/modals/components/SignedOut.tsx @@ -2,18 +2,21 @@ import { Text } from "preact-i18n"; import { Modal } from "@revoltchat/ui"; -interface Props { - onClose: () => void; -} +import { noopTrue } from "../../../lib/js"; -export function SignedOutModal({ onClose }: Props) { +import { ModalProps } from "../types"; + +/** + * Indicate that the user has been signed out of their account + */ +export function SignedOut(props: ModalProps<"signed_out">) { return ( } actions={[ { - onClick: onClose, + onClick: noopTrue, confirmation: true, children: , }, diff --git a/src/context/modals/index.tsx b/src/context/modals/index.tsx index 61bc3e01..df66918e 100644 --- a/src/context/modals/index.tsx +++ b/src/context/modals/index.tsx @@ -13,6 +13,8 @@ import MFAEnableTOTP from "./components/MFAEnableTOTP"; import MFAFlow from "./components/MFAFlow"; import MFARecovery from "./components/MFARecovery"; import OutOfDate from "./components/OutOfDate"; +import { SignOutSessions } from "./components/SignOutSessions"; +import { SignedOut } from "./components/SignedOut"; import Test from "./components/Test"; import { Modal } from "./types"; @@ -142,5 +144,7 @@ export const modalController = new ModalControllerExtended({ mfa_recovery: MFARecovery, mfa_enable_totp: MFAEnableTOTP, out_of_date: OutOfDate, + signed_out: SignedOut, + sign_out_sessions: SignOutSessions, test: Test, }); diff --git a/src/context/modals/types.ts b/src/context/modals/types.ts index be3127af..24efb49c 100644 --- a/src/context/modals/types.ts +++ b/src/context/modals/types.ts @@ -33,7 +33,13 @@ export type Modal = { initial?: number; } | { - type: "test"; + client: Client; + onDelete: () => void; + onDeleting: () => void; + type: "sign_out_sessions"; + } + | { + type: "test" | "signed_out"; } ); diff --git a/src/context/revoltjs/RevoltClient.tsx b/src/context/revoltjs/RevoltClient.tsx index cfb5d4fe..c24915fc 100644 --- a/src/context/revoltjs/RevoltClient.tsx +++ b/src/context/revoltjs/RevoltClient.tsx @@ -10,6 +10,7 @@ import { Preloader } from "@revoltchat/ui"; import { useApplicationState } from "../../mobx/State"; import { useIntermediate } from "../intermediate/Intermediate"; +import { modalController } from "../modals"; import { registerEvents } from "./events"; import { takeError } from "./util"; @@ -68,7 +69,7 @@ export default observer(({ children }: Props) => { const error = takeError(err); if (error === "Forbidden" || error === "Unauthorized") { client.logout(true); - openScreen({ id: "signed_out" }); + modalController.push({ type: "signed_out" }); } else { setStatus(ClientStatus.DISCONNECTED); openScreen({ id: "error", error }); diff --git a/src/pages/settings/panes/Sessions.tsx b/src/pages/settings/panes/Sessions.tsx index e2233608..73e39bc3 100644 --- a/src/pages/settings/panes/Sessions.tsx +++ b/src/pages/settings/panes/Sessions.tsx @@ -27,7 +27,7 @@ import { } from "@revoltchat/ui"; import { dayjs } from "../../../context/Locale"; -import { useIntermediate } from "../../../context/intermediate/Intermediate"; +import { modalController } from "../../../context/modals"; import { AppContext } from "../../../context/revoltjs/RevoltClient"; dayjs.extend(relativeTime); @@ -43,8 +43,6 @@ export function Sessions() { const [attemptingDelete, setDelete] = useState([]); const history = useHistory(); - const { openScreen } = useIntermediate(); - function switchPage(to: string) { history.replace(`/settings/${to}`); } @@ -217,32 +215,22 @@ export function Sessions() { })}
{ - openScreen({ - id: "sessions", - confirm: async () => { - // ! FIXME: add to rAuth - const del: string[] = []; - render.forEach((session) => { - if (deviceId !== session._id) { - del.push(session._id); - } - }); - - setDelete(del); - - for (const id of del) { - await client.api.delete( - `/auth/session/${id as ""}`, - ); - } - + onClick={async () => + modalController.push({ + type: "sign_out_sessions", + client, + onDeleting: () => + setDelete( + render + .filter((x) => x._id !== deviceId) + .map((x) => x._id), + ), + onDelete: () => setSessions( sessions.filter((x) => x._id === deviceId), - ); - }, - }); - }} + ), + }) + } icon={} action={"chevron"} description={ From d10bd969000b15d26be673186b12ac44de8c915d Mon Sep 17 00:00:00 2001 From: Paul Makles Date: Sat, 18 Jun 2022 12:25:56 +0100 Subject: [PATCH 083/151] feat(modal): port Error and ShowToken --- .../common/messaging/MessageBox.tsx | 8 ++++--- src/context/intermediate/Modals.tsx | 8 ------- .../modals => modals/components}/Error.tsx | 14 ++++++------- .../components/ShowToken.tsx} | 16 +++++++------- src/context/modals/components/Test.tsx | 7 ------- src/context/modals/index.tsx | 9 ++++++-- src/context/modals/types.ts | 13 ++++++++++-- src/context/revoltjs/FileUploads.tsx | 21 ++++++++++++++----- src/context/revoltjs/RevoltClient.tsx | 9 ++++---- src/lib/ContextMenus.tsx | 17 ++++++++++++--- src/pages/Open.tsx | 17 +++++++++++---- src/pages/settings/panes/MyBots.tsx | 7 ++++--- src/pages/settings/panes/Notifications.tsx | 7 +++---- 13 files changed, 92 insertions(+), 61 deletions(-) rename src/context/{intermediate/modals => modals/components}/Error.tsx (70%) rename src/context/{intermediate/modals/TokenReveal.tsx => modals/components/ShowToken.tsx} (65%) delete mode 100644 src/context/modals/components/Test.tsx diff --git a/src/components/common/messaging/MessageBox.tsx b/src/components/common/messaging/MessageBox.tsx index 53cb1e77..0c7acc9d 100644 --- a/src/components/common/messaging/MessageBox.tsx +++ b/src/components/common/messaging/MessageBox.tsx @@ -24,7 +24,7 @@ import { import { useApplicationState } from "../../../mobx/State"; import { Reply } from "../../../mobx/stores/MessageQueue"; -import { useIntermediate } from "../../../context/intermediate/Intermediate"; +import { modalController } from "../../../context/modals"; import { FileUploader, grabFiles, @@ -148,7 +148,6 @@ export default observer(({ channel }: Props) => { }); const [typing, setTyping] = useState(false); const [replies, setReplies] = useState([]); - const { openScreen } = useIntermediate(); const client = useContext(AppContext); const translate = useTranslation(); @@ -473,7 +472,10 @@ export default observer(({ channel }: Props) => { files: [...uploadState.files, ...files], }), () => - openScreen({ id: "error", error: "FileTooLarge" }), + modalController.push({ + type: "error", + error: "FileTooLarge", + }), true, ) } diff --git a/src/context/intermediate/Modals.tsx b/src/context/intermediate/Modals.tsx index 55ecb6f8..8ff715c3 100644 --- a/src/context/intermediate/Modals.tsx +++ b/src/context/intermediate/Modals.tsx @@ -1,14 +1,10 @@ -import { internalEmit } from "../../lib/eventEmitter"; - //import { isModalClosing } from "../../components/ui/Modal"; import { Screen } from "./Intermediate"; import { ClipboardModal } from "./modals/Clipboard"; -import { ErrorModal } from "./modals/Error"; import { ExternalLinkModal } from "./modals/ExternalLinkPrompt"; import { InputModal } from "./modals/Input"; import { OnboardingModal } from "./modals/Onboarding"; import { PromptModal } from "./modals/Prompt"; -import { TokenRevealModal } from "./modals/TokenReveal"; export interface Props { screen: Screen; @@ -26,12 +22,8 @@ export default function Modals({ screen, openScreen }: Props) { return ; case "_input": return ; - case "error": - return ; case "clipboard": return ; - case "token_reveal": - return ; case "onboarding": return ; case "external_link_prompt": diff --git a/src/context/intermediate/modals/Error.tsx b/src/context/modals/components/Error.tsx similarity index 70% rename from src/context/intermediate/modals/Error.tsx rename to src/context/modals/components/Error.tsx index d9e2cd01..92e3f7c8 100644 --- a/src/context/intermediate/modals/Error.tsx +++ b/src/context/modals/components/Error.tsx @@ -2,23 +2,23 @@ import { Text } from "preact-i18n"; import { Modal } from "@revoltchat/ui"; -interface Props { - onClose: () => void; - error: string; -} +import { noopTrue } from "../../../lib/js"; -export function ErrorModal({ onClose, error }: Props) { +import { ModalProps } from "../types"; + +export function Error({ error, ...props }: ModalProps<"error">) { return ( } actions={[ { - onClick: onClose, + onClick: noopTrue, confirmation: true, children: , }, { + palette: "plain-secondary", onClick: () => location.reload(), children: , }, diff --git a/src/context/intermediate/modals/TokenReveal.tsx b/src/context/modals/components/ShowToken.tsx similarity index 65% rename from src/context/intermediate/modals/TokenReveal.tsx rename to src/context/modals/components/ShowToken.tsx index a671d322..92c7fac3 100644 --- a/src/context/intermediate/modals/TokenReveal.tsx +++ b/src/context/modals/components/ShowToken.tsx @@ -2,25 +2,23 @@ import { Text } from "preact-i18n"; import { Modal } from "@revoltchat/ui"; -interface Props { - onClose: () => void; - token: string; - username: string; -} +import { noopTrue } from "../../../lib/js"; -export function TokenRevealModal({ onClose, token, username }: Props) { +import { ModalProps } from "../types"; + +export function ShowToken({ name, token, ...props }: ModalProps<"show_token">) { return ( } actions={[ { - onClick: onClose, + onClick: noopTrue, confirmation: true, children: , }, diff --git a/src/context/modals/components/Test.tsx b/src/context/modals/components/Test.tsx deleted file mode 100644 index ed59a414..00000000 --- a/src/context/modals/components/Test.tsx +++ /dev/null @@ -1,7 +0,0 @@ -import { Modal } from "@revoltchat/ui"; - -import { ModalProps } from "../types"; - -export default function Test({ onClose }: ModalProps<"test">) { - return ; -} diff --git a/src/context/modals/index.tsx b/src/context/modals/index.tsx index df66918e..cd969190 100644 --- a/src/context/modals/index.tsx +++ b/src/context/modals/index.tsx @@ -9,13 +9,14 @@ import type { Client, API } from "revolt.js"; import { ulid } from "ulid"; import Changelog from "./components/Changelog"; +import { Error } from "./components/Error"; import MFAEnableTOTP from "./components/MFAEnableTOTP"; import MFAFlow from "./components/MFAFlow"; import MFARecovery from "./components/MFARecovery"; import OutOfDate from "./components/OutOfDate"; +import { ShowToken } from "./components/ShowToken"; import { SignOutSessions } from "./components/SignOutSessions"; import { SignedOut } from "./components/SignedOut"; -import Test from "./components/Test"; import { Modal } from "./types"; type Components = Record>; @@ -92,6 +93,9 @@ class ModalController { ); } + /** + * Whether a modal is currently visible + */ get isVisible() { return this.stack.length > 0; } @@ -140,11 +144,12 @@ class ModalControllerExtended extends ModalController { export const modalController = new ModalControllerExtended({ changelog: Changelog, + error: Error, mfa_flow: MFAFlow, mfa_recovery: MFARecovery, mfa_enable_totp: MFAEnableTOTP, out_of_date: OutOfDate, + show_token: ShowToken, signed_out: SignedOut, sign_out_sessions: SignOutSessions, - test: Test, }); diff --git a/src/context/modals/types.ts b/src/context/modals/types.ts index 24efb49c..6349c132 100644 --- a/src/context/modals/types.ts +++ b/src/context/modals/types.ts @@ -33,13 +33,22 @@ export type Modal = { initial?: number; } | { + type: "sign_out_sessions"; client: Client; onDelete: () => void; onDeleting: () => void; - type: "sign_out_sessions"; } | { - type: "test" | "signed_out"; + type: "show_token"; + name: string; + token: string; + } + | { + type: "error"; + error: string; + } + | { + type: "signed_out"; } ); diff --git a/src/context/revoltjs/FileUploads.tsx b/src/context/revoltjs/FileUploads.tsx index 4d8a9a0c..16ec2792 100644 --- a/src/context/revoltjs/FileUploads.tsx +++ b/src/context/revoltjs/FileUploads.tsx @@ -12,6 +12,7 @@ import { IconButton, Preloader } from "@revoltchat/ui"; import { determineFileSize } from "../../lib/fileSize"; import { useIntermediate } from "../intermediate/Intermediate"; +import { modalController } from "../modals"; import { AppContext } from "./RevoltClient"; import { takeError } from "./util"; @@ -139,12 +140,19 @@ export function FileUploader(props: Props) { ); } } catch (err) { - return openScreen({ id: "error", error: takeError(err) }); + return modalController.push({ + type: "error", + error: takeError(err), + }); } finally { setUploading(false); } }, - () => openScreen({ id: "error", error: "FileTooLarge" }), + () => + modalController.push({ + type: "error", + error: "FileTooLarge", + }), props.behaviour === "multi", ); } @@ -180,8 +188,8 @@ export function FileUploader(props: Props) { const blob = item.getAsFile(); if (blob) { if (blob.size > props.maxFileSize) { - openScreen({ - id: "error", + modalController.push({ + type: "error", error: "FileTooLarge", }); continue; @@ -212,7 +220,10 @@ export function FileUploader(props: Props) { const files = []; for (const item of dropped) { if (item.size > props.maxFileSize) { - openScreen({ id: "error", error: "FileTooLarge" }); + modalController.push({ + type: "error", + error: "FileTooLarge", + }); continue; } diff --git a/src/context/revoltjs/RevoltClient.tsx b/src/context/revoltjs/RevoltClient.tsx index c24915fc..733b1c9e 100644 --- a/src/context/revoltjs/RevoltClient.tsx +++ b/src/context/revoltjs/RevoltClient.tsx @@ -9,7 +9,6 @@ import { Preloader } from "@revoltchat/ui"; import { useApplicationState } from "../../mobx/State"; -import { useIntermediate } from "../intermediate/Intermediate"; import { modalController } from "../modals"; import { registerEvents } from "./events"; import { takeError } from "./util"; @@ -30,7 +29,7 @@ export interface ClientOperations { export const AppContext = createContext(null!); export const StatusContext = createContext(null!); -export const LogOutContext = createContext((avoidReq?: boolean) => {}); +export const LogOutContext = createContext<(avoidReq?: boolean) => void>(null!); type Props = { children: Children; @@ -38,7 +37,6 @@ type Props = { export default observer(({ children }: Props) => { const state = useApplicationState(); - const { openScreen } = useIntermediate(); const [client, setClient] = useState(null!); const [status, setStatus] = useState(ClientStatus.LOADING); const [loaded, setLoaded] = useState(false); @@ -72,7 +70,10 @@ export default observer(({ children }: Props) => { modalController.push({ type: "signed_out" }); } else { setStatus(ClientStatus.DISCONNECTED); - openScreen({ id: "error", error }); + modalController.push({ + type: "error", + error, + }); } }) .finally(() => setLoaded(true)); diff --git a/src/lib/ContextMenus.tsx b/src/lib/ContextMenus.tsx index a88c0bc2..7ca44ae7 100644 --- a/src/lib/ContextMenus.tsx +++ b/src/lib/ContextMenus.tsx @@ -2,8 +2,15 @@ import { ChevronRight, Trash } from "@styled-icons/boxicons-regular"; import { Cog, UserVoice } from "@styled-icons/boxicons-solid"; import { isFirefox } from "react-device-detect"; import { useHistory } from "react-router-dom"; -import { Channel, Message, Server, User, API } from "revolt.js"; -import { Permission, UserPermission } from "revolt.js"; +import { + Channel, + Message, + Server, + User, + API, + Permission, + UserPermission, +} from "revolt.js"; import { ContextMenuWithData, @@ -20,6 +27,7 @@ import { QueuedMessage } from "../mobx/stores/MessageQueue"; import { NotificationState } from "../mobx/stores/NotificationOptions"; import { Screen, useIntermediate } from "../context/intermediate/Intermediate"; +import { modalController } from "../context/modals"; import { AppContext, ClientStatus, @@ -431,7 +439,10 @@ export default function ContextMenus() { break; } })().catch((err) => { - openScreen({ id: "error", error: takeError(err) }); + modalController.push({ + type: "error", + error: takeError(err), + }); }); } diff --git a/src/pages/Open.tsx b/src/pages/Open.tsx index 20f9b3fa..20e5265f 100644 --- a/src/pages/Open.tsx +++ b/src/pages/Open.tsx @@ -6,7 +6,7 @@ import { useContext, useEffect } from "preact/hooks"; import { Header } from "@revoltchat/ui"; -import { useIntermediate } from "../context/intermediate/Intermediate"; +import { modalController } from "../context/modals"; import { AppContext, ClientStatus, @@ -18,7 +18,6 @@ export default function Open() { const client = useContext(AppContext); const status = useContext(StatusContext); const { id } = useParams<{ id: string }>(); - const { openScreen } = useIntermediate(); if (status !== ClientStatus.ONLINE) { return ( @@ -40,7 +39,12 @@ export default function Open() { client .user!.openDM() .then((channel) => history.push(`/channel/${channel?._id}`)) - .catch((error) => openScreen({ id: "error", error })); + .catch((error) => + modalController.push({ + type: "error", + error, + }), + ); return; } @@ -62,7 +66,12 @@ export default function Open() { .get(id) ?.openDM() .then((channel) => history.push(`/channel/${channel?._id}`)) - .catch((error) => openScreen({ id: "error", error })); + .catch((error) => + modalController.push({ + type: "error", + error, + }), + ); } return; diff --git a/src/pages/settings/panes/MyBots.tsx b/src/pages/settings/panes/MyBots.tsx index c3a49d70..a71f5a04 100644 --- a/src/pages/settings/panes/MyBots.tsx +++ b/src/pages/settings/panes/MyBots.tsx @@ -25,6 +25,7 @@ import { useTranslation } from "../../../lib/i18n"; import { stopPropagation } from "../../../lib/stopPropagation"; import { useIntermediate } from "../../../context/intermediate/Intermediate"; +import { modalController } from "../../../context/modals"; import { FileUploader } from "../../../context/revoltjs/FileUploads"; import { useClient } from "../../../context/revoltjs/RevoltClient"; @@ -366,10 +367,10 @@ function BotCard({ bot, onDelete, onUpdate }: Props) { onClick={(ev) => stopPropagation( ev, - openScreen({ - id: "token_reveal", + modalController.push({ + type: "show_token", token: bot.token, - username: user!.username, + name: user!.username, }), ) }> diff --git a/src/pages/settings/panes/Notifications.tsx b/src/pages/settings/panes/Notifications.tsx index b8edac46..d934a63d 100644 --- a/src/pages/settings/panes/Notifications.tsx +++ b/src/pages/settings/panes/Notifications.tsx @@ -10,12 +10,11 @@ import { urlBase64ToUint8Array } from "../../../lib/conversion"; import { useApplicationState } from "../../../mobx/State"; -import { useIntermediate } from "../../../context/intermediate/Intermediate"; +import { modalController } from "../../../context/modals"; import { AppContext } from "../../../context/revoltjs/RevoltClient"; export const Notifications = observer(() => { const client = useContext(AppContext); - const { openScreen } = useIntermediate(); const settings = useApplicationState().settings; const [pushEnabled, setPushEnabled] = useState( undefined, @@ -52,8 +51,8 @@ export const Notifications = observer(() => { await Notification.requestPermission(); if (permission !== "granted") { - return openScreen({ - id: "error", + return modalController.push({ + type: "error", error: "DeniedNotification", }); } From 241b9cd27ba5124ab5248fa4883c5fd4b4e794c0 Mon Sep 17 00:00:00 2001 From: Paul Makles Date: Sat, 18 Jun 2022 12:33:22 +0100 Subject: [PATCH 084/151] feat(modal): port Clipboard --- src/context/intermediate/Intermediate.tsx | 9 ++----- src/context/intermediate/Modals.tsx | 3 --- .../components}/Clipboard.tsx | 17 +++++++------ src/context/modals/components/Error.tsx | 2 +- src/context/modals/components/ShowToken.tsx | 10 ++++++-- .../modals/components/SignOutSessions.tsx | 4 ++- src/context/modals/components/SignedOut.tsx | 2 +- src/context/modals/index.tsx | 25 ++++++++++++++++--- src/context/modals/types.ts | 4 +++ 9 files changed, 49 insertions(+), 27 deletions(-) rename src/context/{intermediate/modals => modals/components}/Clipboard.tsx (65%) diff --git a/src/context/intermediate/Intermediate.tsx b/src/context/intermediate/Intermediate.tsx index 9a1ac4d1..b58c37de 100644 --- a/src/context/intermediate/Intermediate.tsx +++ b/src/context/intermediate/Intermediate.tsx @@ -12,6 +12,7 @@ import { determineLink } from "../../lib/links"; import { useApplicationState } from "../../mobx/State"; +import { modalController } from "../modals"; import Modals from "./Modals"; export type Screen = @@ -171,13 +172,7 @@ export default function Intermediate(props: Props) { return true; }, openScreen: (screen: Screen) => openScreen(screen), - writeClipboard: (text: string) => { - if (navigator.clipboard) { - navigator.clipboard.writeText(text); - } else { - actions.openScreen({ id: "clipboard", text }); - } - }, + writeClipboard: (a: string) => modalController.writeText(a), }; // eslint-disable-next-line }, []); diff --git a/src/context/intermediate/Modals.tsx b/src/context/intermediate/Modals.tsx index 8ff715c3..a2a91507 100644 --- a/src/context/intermediate/Modals.tsx +++ b/src/context/intermediate/Modals.tsx @@ -1,6 +1,5 @@ //import { isModalClosing } from "../../components/ui/Modal"; import { Screen } from "./Intermediate"; -import { ClipboardModal } from "./modals/Clipboard"; import { ExternalLinkModal } from "./modals/ExternalLinkPrompt"; import { InputModal } from "./modals/Input"; import { OnboardingModal } from "./modals/Onboarding"; @@ -22,8 +21,6 @@ export default function Modals({ screen, openScreen }: Props) { return ; case "_input": return ; - case "clipboard": - return ; case "onboarding": return ; case "external_link_prompt": diff --git a/src/context/intermediate/modals/Clipboard.tsx b/src/context/modals/components/Clipboard.tsx similarity index 65% rename from src/context/intermediate/modals/Clipboard.tsx rename to src/context/modals/components/Clipboard.tsx index 710e2a84..0361eb8e 100644 --- a/src/context/intermediate/modals/Clipboard.tsx +++ b/src/context/modals/components/Clipboard.tsx @@ -2,19 +2,18 @@ import { Text } from "preact-i18n"; import { Modal } from "@revoltchat/ui"; -interface Props { - onClose: () => void; - text: string; -} +import { noopTrue } from "../../../lib/js"; -export function ClipboardModal({ onClose, text }: Props) { +import { ModalProps } from "../types"; + +export default function Clipboard({ text, ...props }: ModalProps<"clipboard">) { return ( } actions={[ { - onClick: onClose, + onClick: noopTrue, confirmation: true, children: , }, @@ -25,7 +24,9 @@ export function ClipboardModal({ onClose, text }: Props) {

)} {" "} - {text} + + {text} +
); } diff --git a/src/context/modals/components/Error.tsx b/src/context/modals/components/Error.tsx index 92e3f7c8..892f5794 100644 --- a/src/context/modals/components/Error.tsx +++ b/src/context/modals/components/Error.tsx @@ -6,7 +6,7 @@ import { noopTrue } from "../../../lib/js"; import { ModalProps } from "../types"; -export function Error({ error, ...props }: ModalProps<"error">) { +export default function Error({ error, ...props }: ModalProps<"error">) { return ( ) { +export default function ShowToken({ + name, + token, + ...props +}: ModalProps<"show_token">) { return ( ) { children: , }, ]}> - {token} + + {token} + ); } diff --git a/src/context/modals/components/SignOutSessions.tsx b/src/context/modals/components/SignOutSessions.tsx index 46afff05..35b07882 100644 --- a/src/context/modals/components/SignOutSessions.tsx +++ b/src/context/modals/components/SignOutSessions.tsx @@ -10,7 +10,9 @@ import { ModalProps } from "../types"; /** * Confirm whether a user wants to sign out of all other sessions */ -export function SignOutSessions(props: ModalProps<"sign_out_sessions">) { +export default function SignOutSessions( + props: ModalProps<"sign_out_sessions">, +) { const onClick = useCallback(() => { props.onDeleting(); props.client.api.delete("/auth/session/all").then(props.onDelete); diff --git a/src/context/modals/components/SignedOut.tsx b/src/context/modals/components/SignedOut.tsx index 21c702e5..0f3fa66d 100644 --- a/src/context/modals/components/SignedOut.tsx +++ b/src/context/modals/components/SignedOut.tsx @@ -9,7 +9,7 @@ import { ModalProps } from "../types"; /** * Indicate that the user has been signed out of their account */ -export function SignedOut(props: ModalProps<"signed_out">) { +export default function SignedOut(props: ModalProps<"signed_out">) { return ( >; @@ -140,10 +141,26 @@ class ModalControllerExtended extends ModalController { ), ); } + + /** + * Write text to the clipboard + * @param text Text to write + */ + writeText(text: string) { + if (navigator.clipboard) { + navigator.clipboard.writeText(text); + } else { + this.push({ + type: "clipboard", + text, + }); + } + } } export const modalController = new ModalControllerExtended({ changelog: Changelog, + clipboard: Clipboard, error: Error, mfa_flow: MFAFlow, mfa_recovery: MFARecovery, diff --git a/src/context/modals/types.ts b/src/context/modals/types.ts index 6349c132..1b5a36e7 100644 --- a/src/context/modals/types.ts +++ b/src/context/modals/types.ts @@ -47,6 +47,10 @@ export type Modal = { type: "error"; error: string; } + | { + type: "clipboard"; + text: string; + } | { type: "signed_out"; } From b7be9f8c03df41930250282387fd6e55196f6601 Mon Sep 17 00:00:00 2001 From: Paul Makles Date: Sat, 18 Jun 2022 14:19:31 +0100 Subject: [PATCH 085/151] feat(modal): port LinkWarning --- package.json | 1 + src/context/history.ts | 5 +++ src/context/index.tsx | 5 ++- src/context/intermediate/Intermediate.tsx | 30 +------------- src/context/intermediate/Modals.tsx | 3 -- .../components/LinkWarning.tsx} | 28 ++++++------- src/context/modals/index.tsx | 41 +++++++++++++++++++ src/context/modals/types.ts | 5 +++ src/mobx/State.ts | 8 ++++ src/mobx/stores/helpers/SSecurity.ts | 6 +++ yarn.lock | 3 +- 11 files changed, 84 insertions(+), 51 deletions(-) create mode 100644 src/context/history.ts rename src/context/{intermediate/modals/ExternalLinkPrompt.tsx => modals/components/LinkWarning.tsx} (70%) diff --git a/package.json b/package.json index 62141717..21f82367 100644 --- a/package.json +++ b/package.json @@ -107,6 +107,7 @@ "eslint-config-preact": "^1.1.4", "eslint-plugin-jsdoc": "^39.3.2", "eventemitter3": "^4.0.7", + "history": "4", "json-stringify-deterministic": "^1.0.2", "localforage": "^1.9.0", "lodash.defaultsdeep": "^4.6.1", diff --git a/src/context/history.ts b/src/context/history.ts new file mode 100644 index 00000000..5e816997 --- /dev/null +++ b/src/context/history.ts @@ -0,0 +1,5 @@ +import { createBrowserHistory } from "history"; + +export const history = createBrowserHistory({ + basename: import.meta.env.BASE_URL, +}); diff --git a/src/context/index.tsx b/src/context/index.tsx index f30f885c..663fdfaa 100644 --- a/src/context/index.tsx +++ b/src/context/index.tsx @@ -1,4 +1,4 @@ -import { BrowserRouter as Router, Link } from "react-router-dom"; +import { Router, Link } from "react-router-dom"; import { ContextMenuTrigger } from "preact-context-menu"; import { Text } from "preact-i18n"; @@ -10,6 +10,7 @@ import { hydrateState } from "../mobx/State"; import Locale from "./Locale"; import Theme from "./Theme"; +import { history } from "./history"; import Intermediate from "./intermediate/Intermediate"; import ModalRenderer from "./modals/ModalRenderer"; import Client from "./revoltjs/RevoltClient"; @@ -36,7 +37,7 @@ export default function Context({ children }: { children: Children }) { if (!ready) return ; return ( - + diff --git a/src/context/intermediate/Intermediate.tsx b/src/context/intermediate/Intermediate.tsx index b58c37de..906e4c95 100644 --- a/src/context/intermediate/Intermediate.tsx +++ b/src/context/intermediate/Intermediate.tsx @@ -141,35 +141,7 @@ export default function Intermediate(props: Props) { const actions = useMemo(() => { return { openLink: (href?: string, trusted?: boolean) => { - const link = determineLink(href); - - switch (link.type) { - case "profile": { - openScreen({ id: "profile", user_id: link.id }); - return true; - } - case "navigate": { - history.push(link.path); - return true; - } - case "external": { - if ( - !trusted && - !settings.security.isTrustedOrigin( - link.url.hostname, - ) - ) { - openScreen({ - id: "external_link_prompt", - link: link.href, - }); - } else { - window.open(link.href, "_blank", "noreferrer"); - } - } - } - - return true; + return modalController.openLink(href, trusted); }, openScreen: (screen: Screen) => openScreen(screen), writeClipboard: (a: string) => modalController.writeText(a), diff --git a/src/context/intermediate/Modals.tsx b/src/context/intermediate/Modals.tsx index a2a91507..4f59c974 100644 --- a/src/context/intermediate/Modals.tsx +++ b/src/context/intermediate/Modals.tsx @@ -1,6 +1,5 @@ //import { isModalClosing } from "../../components/ui/Modal"; import { Screen } from "./Intermediate"; -import { ExternalLinkModal } from "./modals/ExternalLinkPrompt"; import { InputModal } from "./modals/Input"; import { OnboardingModal } from "./modals/Onboarding"; import { PromptModal } from "./modals/Prompt"; @@ -23,8 +22,6 @@ export default function Modals({ screen, openScreen }: Props) { return ; case "onboarding": return ; - case "external_link_prompt": - return ; } return null; diff --git a/src/context/intermediate/modals/ExternalLinkPrompt.tsx b/src/context/modals/components/LinkWarning.tsx similarity index 70% rename from src/context/intermediate/modals/ExternalLinkPrompt.tsx rename to src/context/modals/components/LinkWarning.tsx index 48e336ac..04a9e876 100644 --- a/src/context/intermediate/modals/ExternalLinkPrompt.tsx +++ b/src/context/modals/components/LinkWarning.tsx @@ -2,35 +2,32 @@ import { Text } from "preact-i18n"; import { Modal } from "@revoltchat/ui"; +import { noopTrue } from "../../../lib/js"; + import { useApplicationState } from "../../../mobx/State"; -import { useIntermediate } from "../Intermediate"; +import { ModalProps } from "../types"; -interface Props { - onClose: () => void; - link: string; -} - -export function ExternalLinkModal({ onClose, link }: Props) { - const { openLink } = useIntermediate(); +export default function LinkWarning({ + link, + callback, + ...props +}: ModalProps<"link_warning">) { const settings = useApplicationState().settings; return ( } actions={[ { - onClick: () => { - openLink(link, true); - onClose(); - }, + onClick: callback, confirmation: true, palette: "accent", children: "Continue", }, { - onClick: onClose, + onClick: noopTrue, confirmation: false, children: "Cancel", }, @@ -41,8 +38,7 @@ export function ExternalLinkModal({ onClose, link }: Props) { settings.security.addTrustedOrigin(url.hostname); } catch (e) {} - openLink(link, true); - onClose(); + return callback(); }, palette: "plain", children: ( diff --git a/src/context/modals/index.tsx b/src/context/modals/index.tsx index 1b0670fd..6a2485d0 100644 --- a/src/context/modals/index.tsx +++ b/src/context/modals/index.tsx @@ -8,9 +8,16 @@ import { import type { Client, API } from "revolt.js"; import { ulid } from "ulid"; +import { determineLink } from "../../lib/links"; + +import { getApplicationState, useApplicationState } from "../../mobx/State"; + +import { history } from "../history"; +// import { determineLink } from "../../lib/links"; import Changelog from "./components/Changelog"; import Clipboard from "./components/Clipboard"; import Error from "./components/Error"; +import LinkWarning from "./components/LinkWarning"; import MFAEnableTOTP from "./components/MFAEnableTOTP"; import MFAFlow from "./components/MFAFlow"; import MFARecovery from "./components/MFARecovery"; @@ -156,12 +163,46 @@ class ModalControllerExtended extends ModalController { }); } } + + openLink(href?: string, trusted?: boolean) { + const link = determineLink(href); + const settings = getApplicationState().settings; + + switch (link.type) { + case "profile": { + // TODO: port Profile + // openScreen({ id: "profile", user_id: link.id }); + break; + } + case "navigate": { + history.push(link.path); + break; + } + case "external": { + if ( + !trusted && + !settings.security.isTrustedOrigin(link.url.hostname) + ) { + modalController.push({ + type: "link_warning", + link: link.href, + callback: () => this.openLink(href, true) as true, + }); + } else { + window.open(link.href, "_blank", "noreferrer"); + } + } + } + + return true; + } } export const modalController = new ModalControllerExtended({ changelog: Changelog, clipboard: Clipboard, error: Error, + link_warning: LinkWarning, mfa_flow: MFAFlow, mfa_recovery: MFARecovery, mfa_enable_totp: MFAEnableTOTP, diff --git a/src/context/modals/types.ts b/src/context/modals/types.ts index 1b5a36e7..d6bb5883 100644 --- a/src/context/modals/types.ts +++ b/src/context/modals/types.ts @@ -51,6 +51,11 @@ export type Modal = { type: "clipboard"; text: string; } + | { + type: "link_warning"; + link: string; + callback: () => true; + } | { type: "signed_out"; } diff --git a/src/mobx/State.ts b/src/mobx/State.ts index ffc2ebbb..b67f03a8 100644 --- a/src/mobx/State.ts +++ b/src/mobx/State.ts @@ -311,3 +311,11 @@ export async function hydrateState() { export function useApplicationState() { return state; } + +/** + * Get the application state + * @returns Application state + */ +export function getApplicationState() { + return state; +} diff --git a/src/mobx/stores/helpers/SSecurity.ts b/src/mobx/stores/helpers/SSecurity.ts index a57d8d1f..0fce7f93 100644 --- a/src/mobx/stores/helpers/SSecurity.ts +++ b/src/mobx/stores/helpers/SSecurity.ts @@ -2,6 +2,8 @@ import { makeAutoObservable, computed, action } from "mobx"; import Settings from "../Settings"; +const TRUSTED_DOMAINS = ["revolt.chat", "revolt.wtf", "gifbox.me", "rvlt.gg"]; + /** * Helper class for changing security options. */ @@ -27,6 +29,10 @@ export default class SSecurity { } @computed isTrustedOrigin(origin: string) { + if (TRUSTED_DOMAINS.find((x) => origin.endsWith(x))) { + return true; + } + return this.settings.get("security:trustedOrigins")?.includes(origin); } } diff --git a/yarn.lock b/yarn.lock index 88c78fb3..5a43c7b5 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3574,6 +3574,7 @@ __metadata: eslint-plugin-jsdoc: ^39.3.2 eventemitter3: ^4.0.7 fs-extra: ^10.0.0 + history: 4 json-stringify-deterministic: ^1.0.2 klaw: ^3.0.0 localforage: ^1.9.0 @@ -5043,7 +5044,7 @@ __metadata: languageName: node linkType: hard -"history@npm:^4.9.0": +"history@npm:4, history@npm:^4.9.0": version: 4.10.1 resolution: "history@npm:4.10.1" dependencies: From 211ff2058a80456f8ac18b7598b8a62cfb63a6d9 Mon Sep 17 00:00:00 2001 From: Paul Makles Date: Sat, 18 Jun 2022 14:49:59 +0100 Subject: [PATCH 086/151] chore: remove legacy Redux migration --- src/mobx/State.ts | 19 --- src/mobx/legacy/redux.ts | 188 ------------------------- src/mobx/stores/NotificationOptions.ts | 16 +-- src/mobx/stores/Settings.ts | 19 +-- 4 files changed, 3 insertions(+), 239 deletions(-) delete mode 100644 src/mobx/legacy/redux.ts diff --git a/src/mobx/State.ts b/src/mobx/State.ts index b67f03a8..e742ac44 100644 --- a/src/mobx/State.ts +++ b/src/mobx/State.ts @@ -6,8 +6,6 @@ import { Client } from "revolt.js"; import { reportError } from "../lib/ErrorBoundary"; -import { legacyMigrateForwards, LegacyState } from "./legacy/redux"; - import Persistent from "./interfaces/Persistent"; import Syncable from "./interfaces/Syncable"; import Auth from "./stores/Auth"; @@ -239,23 +237,6 @@ export default class State { * Load data stores from local storage. */ async hydrate() { - // Migrate legacy Redux store. - try { - let legacy = await localforage.getItem("state"); - await localforage.removeItem("state"); - if (legacy) { - if (typeof legacy === "string") { - legacy = JSON.parse(legacy); - } - - legacyMigrateForwards(legacy as Partial, this); - await this.save(); - return; - } - } catch (err) { - reportError(err as any, "redux_migration"); - } - // Load MobX store. const sync = (await localforage.getItem("sync")) as DataSync; const { revision } = sync ?? { revision: {} }; diff --git a/src/mobx/legacy/redux.ts b/src/mobx/legacy/redux.ts deleted file mode 100644 index 81c5c37f..00000000 --- a/src/mobx/legacy/redux.ts +++ /dev/null @@ -1,188 +0,0 @@ -import { runInAction } from "mobx"; -import { API } from "revolt.js"; - -import { Fonts, MonospaceFonts, Overrides } from "../../context/Theme"; - -import { Language } from "../../../external/lang/Languages"; -import State from "../State"; -import { Data as DataAuth } from "../stores/Auth"; -import { Data as DataLocaleOptions } from "../stores/LocaleOptions"; -import { Data as DataNotificationOptions } from "../stores/NotificationOptions"; -import { ISettings } from "../stores/Settings"; -import { Data as DataSync } from "../stores/Sync"; - -export type LegacyTheme = Overrides & { - light?: boolean; - font?: Fonts; - css?: string; - monospaceFont?: MonospaceFonts; -}; - -export interface LegacyThemeOptions { - base?: string; - ligatures?: boolean; - custom?: Partial; -} - -export type LegacyEmojiPacks = "mutant" | "twemoji" | "noto" | "openmoji"; -export interface LegacyAppearanceOptions { - emojiPack?: LegacyEmojiPacks; -} - -export type LegacyNotificationState = "all" | "mention" | "none" | "muted"; - -export type LegacyNotifications = { - [key: string]: LegacyNotificationState; -}; - -export interface LegacySyncData { - locale?: Language; - theme?: LegacyThemeOptions; - appearance?: LegacyAppearanceOptions; - notifications?: LegacyNotifications; -} - -export type LegacySyncKeys = - | "theme" - | "appearance" - | "locale" - | "notifications"; - -export interface LegacySyncOptions { - disabled?: LegacySyncKeys[]; - revision?: { - [key: string]: number; - }; -} - -export interface LegacyAuthState { - accounts: { - [key: string]: { - session: Session; - }; - }; - active?: string; -} - -export interface LegacySettings { - theme?: LegacyThemeOptions; - appearance?: LegacyAppearanceOptions; -} - -export function legacyMigrateAuth(auth: LegacyAuthState): DataAuth { - return { - current: auth.active, - sessions: auth.accounts, - }; -} - -export function legacyMigrateLocale(lang: Language): DataLocaleOptions { - return { - lang, - }; -} - -export function legacyMigrateTheme( - theme: LegacyThemeOptions, -): Partial { - const { light, font, css, monospaceFont, ...variables } = - theme.custom ?? {}; - - return { - "appearance:ligatures": theme.ligatures, - "appearance:theme:base": theme.base === "light" ? "light" : "dark", - "appearance:theme:light": light, - "appearance:theme:font": font, - "appearance:theme:monoFont": monospaceFont, - "appearance:theme:css": css, - "appearance:theme:overrides": variables, - }; -} - -export function legacyMigrateAppearance( - appearance: LegacyAppearanceOptions, -): Partial { - return { - "appearance:emoji": appearance.emojiPack, - }; -} - -/** - * Remove trolling from an object - * @param inp Object to remove trolling from - * @returns Object without trolling - */ -function detroll(inp: object): ISettings { - const obj: object = {}; - Object.keys(inp) - .filter((x) => typeof (inp as any)[x] !== "undefined") - .map((x) => ((obj as any)[x] = (inp as any)[x])); - - return obj as unknown as ISettings; -} - -export function legacyMigrateNotification( - channel: LegacyNotifications, -): DataNotificationOptions { - return { - channel, - }; -} - -export function legacyMigrateSync(sync: LegacySyncOptions): DataSync { - return { - disabled: sync.disabled ?? [], - revision: { - ...sync.revision, - }, - }; -} - -export type LegacyState = { - locale: Language; - auth: LegacyAuthState; - settings: LegacySettings; - sync: LegacySyncOptions; - notifications: LegacyNotifications; -}; - -export function legacyMigrateForwards( - data: Partial, - target: State, -) { - runInAction(() => { - if ("sync" in data) { - target.sync.hydrate(legacyMigrateSync(data.sync!)); - } - - if ("locale" in data) { - target.locale.hydrate(legacyMigrateLocale(data.locale!)); - } - - if ("auth" in data) { - target.auth.hydrate(legacyMigrateAuth(data.auth!)); - } - - if ("settings" in data) { - if (data!.settings!.theme) { - target.settings.hydrate( - detroll(legacyMigrateTheme(data.settings!.theme!)), - ); - } - - if (data!.settings!.appearance) { - target.settings.hydrate( - detroll( - legacyMigrateAppearance(data.settings!.appearance!), - ), - ); - } - } - - if ("notifications" in data) { - target.notifications.hydrate( - legacyMigrateNotification(data.notifications!), - ); - } - }); -} diff --git a/src/mobx/stores/NotificationOptions.ts b/src/mobx/stores/NotificationOptions.ts index 5f685560..2b6285a2 100644 --- a/src/mobx/stores/NotificationOptions.ts +++ b/src/mobx/stores/NotificationOptions.ts @@ -1,16 +1,8 @@ import { action, computed, makeAutoObservable, ObservableMap } from "mobx"; -import { Channel } from "revolt.js"; -import { Message } from "revolt.js"; -import { Server } from "revolt.js"; +import { Channel, Message, Server } from "revolt.js"; import { mapToRecord } from "../../lib/conversion"; -import { - legacyMigrateNotification, - LegacyNotifications, -} from "../legacy/redux"; - -import { MIGRATIONS } from "../State"; import Persistent from "../interfaces/Persistent"; import Store from "../interfaces/Store"; import Syncable from "../interfaces/Syncable"; @@ -217,11 +209,7 @@ export default class NotificationOptions return false; } - @action apply(_key: "notifications", data: unknown, revision: number) { - if (revision < MIGRATIONS.REDUX) { - data = legacyMigrateNotification(data as LegacyNotifications); - } - + @action apply(_key: "notifications", data: unknown, _revision: number) { this.hydrate(data as Data); } diff --git a/src/mobx/stores/Settings.ts b/src/mobx/stores/Settings.ts index cf94e6c6..f775a366 100644 --- a/src/mobx/stores/Settings.ts +++ b/src/mobx/stores/Settings.ts @@ -2,18 +2,9 @@ import { action, computed, makeAutoObservable, ObservableMap } from "mobx"; import { mapToRecord } from "../../lib/conversion"; -import { - LegacyAppearanceOptions, - legacyMigrateAppearance, - legacyMigrateTheme, - LegacyTheme, - LegacyThemeOptions, -} from "../legacy/redux"; - import { Fonts, MonospaceFonts, Overrides } from "../../context/Theme"; import { EmojiPack } from "../../components/common/Emoji"; - import { MIGRATIONS } from "../State"; import Persistent from "../interfaces/Persistent"; import Store from "../interfaces/Store"; @@ -129,16 +120,8 @@ export default class Settings @action apply( key: "appearance" | "theme", data: unknown, - revision: number, + _revision: number, ) { - if (revision < MIGRATIONS.REDUX) { - if (key === "appearance") { - data = legacyMigrateAppearance(data as LegacyAppearanceOptions); - } else { - data = legacyMigrateTheme(data as LegacyThemeOptions); - } - } - if (key === "appearance") { this.remove("appearance:emoji"); this.remove("appearance:seasonal"); From 6755217ad25d47c452692fff8148d2f24fd517f9 Mon Sep 17 00:00:00 2001 From: Paul Makles Date: Sat, 18 Jun 2022 15:02:59 +0100 Subject: [PATCH 087/151] feat(modal): port ModifyAccount and PendingRequest --- src/components/common/user/UserIcon.tsx | 2 +- .../settings/account/EditAccount.tsx | 6 ++-- src/context/intermediate/Popovers.tsx | 8 ------ .../intermediate/popovers/PendingRequests.tsx | 28 ------------------- .../components}/ModifyAccount.tsx | 28 +++++++++---------- .../components/PendingFriendRequests.tsx | 21 ++++++++++++++ src/context/modals/index.tsx | 4 +++ src/context/modals/types.ts | 11 +++++++- src/pages/friends/Friends.tsx | 5 ++-- 9 files changed, 56 insertions(+), 57 deletions(-) delete mode 100644 src/context/intermediate/popovers/PendingRequests.tsx rename src/context/{intermediate/popovers => modals/components}/ModifyAccount.tsx (91%) create mode 100644 src/context/modals/components/PendingFriendRequests.tsx diff --git a/src/components/common/user/UserIcon.tsx b/src/components/common/user/UserIcon.tsx index 27c8cdb1..c1eade4a 100644 --- a/src/components/common/user/UserIcon.tsx +++ b/src/components/common/user/UserIcon.tsx @@ -56,7 +56,7 @@ export default observer( keyof Props | "children" | "as" >, ) => { - const client = useClient(); + const client = useApplicationState().client!; const { target, diff --git a/src/components/settings/account/EditAccount.tsx b/src/components/settings/account/EditAccount.tsx index ce7a6088..6220214b 100644 --- a/src/components/settings/account/EditAccount.tsx +++ b/src/components/settings/account/EditAccount.tsx @@ -12,6 +12,7 @@ import { } from "@revoltchat/ui"; import { useIntermediate } from "../../../context/intermediate/Intermediate"; +import { modalController } from "../../../context/modals"; import { ClientStatus, StatusContext, @@ -62,8 +63,9 @@ export default function EditAccount() { account action={} onClick={() => - openScreen({ - id: "modify_account", + modalController.push({ + type: "modify_account", + client, field, }) }> diff --git a/src/context/intermediate/Popovers.tsx b/src/context/intermediate/Popovers.tsx index 34e80f40..4d6dc501 100644 --- a/src/context/intermediate/Popovers.tsx +++ b/src/context/intermediate/Popovers.tsx @@ -8,8 +8,6 @@ import { SpecialPromptModal } from "./modals/Prompt"; import { ChannelInfo } from "./popovers/ChannelInfo"; import { CreateBotModal } from "./popovers/CreateBot"; import { ImageViewer } from "./popovers/ImageViewer"; -import { ModifyAccountModal } from "./popovers/ModifyAccount"; -import { PendingRequests } from "./popovers/PendingRequests"; import { ServerIdentityModal } from "./popovers/ServerIdentityModal"; import { UserPicker } from "./popovers/UserPicker"; import { UserProfile } from "./popovers/UserProfile"; @@ -35,12 +33,6 @@ export default function Popovers() { case "channel_info": // @ts-expect-error someone figure this out :) return ; - case "pending_requests": - // @ts-expect-error someone figure this out :) - return ; - case "modify_account": - // @ts-expect-error someone figure this out :) - return ; case "create_bot": // @ts-expect-error someone figure this out :) return ; diff --git a/src/context/intermediate/popovers/PendingRequests.tsx b/src/context/intermediate/popovers/PendingRequests.tsx deleted file mode 100644 index 2f41e5bf..00000000 --- a/src/context/intermediate/popovers/PendingRequests.tsx +++ /dev/null @@ -1,28 +0,0 @@ -import { observer } from "mobx-react-lite"; -import { User } from "revolt.js"; - -import styles from "./UserPicker.module.scss"; -import { Text } from "preact-i18n"; - -import { Modal } from "@revoltchat/ui"; - -import { Friend } from "../../../pages/friends/Friend"; - -interface Props { - users: User[]; - onClose: () => void; -} - -export const PendingRequests = observer(({ users, onClose }: Props) => { - return ( - } - onClose={onClose}> -
- {users.map((x) => ( - - ))} -
-
- ); -}); diff --git a/src/context/intermediate/popovers/ModifyAccount.tsx b/src/context/modals/components/ModifyAccount.tsx similarity index 91% rename from src/context/intermediate/popovers/ModifyAccount.tsx rename to src/context/modals/components/ModifyAccount.tsx index 9feb5ba1..b3fd934c 100644 --- a/src/context/intermediate/popovers/ModifyAccount.tsx +++ b/src/context/modals/components/ModifyAccount.tsx @@ -5,14 +5,12 @@ import { useContext, useState } from "preact/hooks"; import { Category, Error, Modal } from "@revoltchat/ui"; +import { noopTrue } from "../../../lib/js"; + import FormField from "../../../pages/login/FormField"; import { AppContext } from "../../revoltjs/RevoltClient"; import { takeError } from "../../revoltjs/util"; - -interface Props { - onClose: () => void; - field: "username" | "email" | "password"; -} +import { ModalProps } from "../types"; interface FormInputs { password: string; @@ -25,7 +23,10 @@ interface FormInputs { current_password?: string; } -export function ModifyAccountModal({ onClose, field }: Props) { +export default function ModifyAccount({ + field, + ...props +}: ModalProps<"modify_account">) { const client = useContext(AppContext); const [processing, setProcessing] = useState(false); const { handleSubmit, register, errors } = useForm(); @@ -46,19 +47,19 @@ export function ModifyAccountModal({ onClose, field }: Props) { current_password: password, email: new_email, }); - onClose(); + props.onClose(); } else if (field === "password") { await client.api.patch("/auth/account/change/password", { current_password: password, password: new_password, }); - onClose(); + props.onClose(); } else if (field === "username") { await client.api.patch("/users/@me/username", { username: new_username, password, }); - onClose(); + props.onClose(); } } catch (err) { setError(takeError(err)); @@ -68,16 +69,13 @@ export function ModifyAccountModal({ onClose, field }: Props) { return ( } disabled={processing} actions={[ { confirmation: true, - onClick: async () => { - await handleSubmit(onSubmit)(); - return true; - }, + onClick: () => void handleSubmit(onSubmit)(), children: field === "email" ? ( @@ -86,7 +84,7 @@ export function ModifyAccountModal({ onClose, field }: Props) { ), }, { - onClick: onClose, + onClick: noopTrue, children: , palette: "plain", }, diff --git a/src/context/modals/components/PendingFriendRequests.tsx b/src/context/modals/components/PendingFriendRequests.tsx new file mode 100644 index 00000000..2b9464d7 --- /dev/null +++ b/src/context/modals/components/PendingFriendRequests.tsx @@ -0,0 +1,21 @@ +import { Text } from "preact-i18n"; + +import { Column, Modal } from "@revoltchat/ui"; + +import { Friend } from "../../../pages/friends/Friend"; +import { ModalProps } from "../types"; + +export default function PendingFriendRequests({ + users, + ...props +}: ModalProps<"pending_friend_requests">) { + return ( + }> + + {users.map((x) => ( + + ))} + + + ); +} diff --git a/src/context/modals/index.tsx b/src/context/modals/index.tsx index 6a2485d0..7c370496 100644 --- a/src/context/modals/index.tsx +++ b/src/context/modals/index.tsx @@ -21,7 +21,9 @@ import LinkWarning from "./components/LinkWarning"; import MFAEnableTOTP from "./components/MFAEnableTOTP"; import MFAFlow from "./components/MFAFlow"; import MFARecovery from "./components/MFARecovery"; +import ModifyAccount from "./components/ModifyAccount"; import OutOfDate from "./components/OutOfDate"; +import PendingFriendRequests from "./components/PendingFriendRequests"; import ShowToken from "./components/ShowToken"; import SignOutSessions from "./components/SignOutSessions"; import SignedOut from "./components/SignedOut"; @@ -206,7 +208,9 @@ export const modalController = new ModalControllerExtended({ mfa_flow: MFAFlow, mfa_recovery: MFARecovery, mfa_enable_totp: MFAEnableTOTP, + modify_account: ModifyAccount, out_of_date: OutOfDate, + pending_friend_requests: PendingFriendRequests, show_token: ShowToken, signed_out: SignedOut, sign_out_sessions: SignOutSessions, diff --git a/src/context/modals/types.ts b/src/context/modals/types.ts index d6bb5883..d9ca14b0 100644 --- a/src/context/modals/types.ts +++ b/src/context/modals/types.ts @@ -1,4 +1,4 @@ -import { API, Client } from "revolt.js"; +import { API, Client, User } from "revolt.js"; export type Modal = { key?: string; @@ -56,6 +56,15 @@ export type Modal = { link: string; callback: () => true; } + | { + type: "pending_friend_requests"; + users: User[]; + } + | { + type: "modify_account"; + client: Client; + field: "username" | "email" | "password"; + } | { type: "signed_out"; } diff --git a/src/pages/friends/Friends.tsx b/src/pages/friends/Friends.tsx index 335462f6..aa3af078 100644 --- a/src/pages/friends/Friends.tsx +++ b/src/pages/friends/Friends.tsx @@ -13,6 +13,7 @@ import { TextReact } from "../../lib/i18n"; import { isTouchscreenDevice } from "../../lib/isTouchscreenDevice"; import { useIntermediate } from "../../context/intermediate/Intermediate"; +import { modalController } from "../../context/modals"; import { useClient } from "../../context/revoltjs/RevoltClient"; import CollapsibleSection from "../../components/common/CollapsibleSection"; @@ -129,8 +130,8 @@ export default observer(() => {
- openScreen({ - id: "pending_requests", + modalController.push({ + type: "pending_friend_requests", users: incoming, }) }> From f685352963eeaaec21f46e398572fdc97a501b2d Mon Sep 17 00:00:00 2001 From: Paul Makles Date: Sat, 18 Jun 2022 15:06:24 +0100 Subject: [PATCH 088/151] fix: temporarily hack in mention to profile flow --- src/context/intermediate/Intermediate.tsx | 3 +++ src/context/modals/index.tsx | 4 ++-- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/src/context/intermediate/Intermediate.tsx b/src/context/intermediate/Intermediate.tsx index 906e4c95..bfc96235 100644 --- a/src/context/intermediate/Intermediate.tsx +++ b/src/context/intermediate/Intermediate.tsx @@ -128,8 +128,11 @@ interface Props { children: Children; } +export let __thisIsAHack; + export default function Intermediate(props: Props) { const [screen, openScreen] = useState({ id: "none" }); + __thisIsAHack = openScreen; const settings = useApplicationState().settings; const history = useHistory(); diff --git a/src/context/modals/index.tsx b/src/context/modals/index.tsx index 7c370496..6f9c06a8 100644 --- a/src/context/modals/index.tsx +++ b/src/context/modals/index.tsx @@ -13,6 +13,7 @@ import { determineLink } from "../../lib/links"; import { getApplicationState, useApplicationState } from "../../mobx/State"; import { history } from "../history"; +import { __thisIsAHack } from "../intermediate/Intermediate"; // import { determineLink } from "../../lib/links"; import Changelog from "./components/Changelog"; import Clipboard from "./components/Clipboard"; @@ -172,8 +173,7 @@ class ModalControllerExtended extends ModalController { switch (link.type) { case "profile": { - // TODO: port Profile - // openScreen({ id: "profile", user_id: link.id }); + __thisIsAHack({ id: "profile", user_id: link.id }); break; } case "navigate": { From 03e177f86544393ae1c5b1b3dc05cda101369a6a Mon Sep 17 00:00:00 2001 From: Paul Makles Date: Sat, 18 Jun 2022 15:54:17 +0100 Subject: [PATCH 089/151] feat(modal): implement new server identity modal closes #172 --- package.json | 2 +- src/context/intermediate/Intermediate.tsx | 11 +- src/context/intermediate/Popovers.tsx | 6 - .../popovers/ServerIdentityModal.module.scss | 17 --- .../popovers/ServerIdentityModal.tsx | 106 -------------- .../modals/components/ServerIdentity.tsx | 138 ++++++++++++++++++ src/context/modals/index.tsx | 2 + src/context/modals/types.ts | 6 +- src/context/revoltjs/FileUploads.tsx | 4 +- src/lib/ContextMenus.tsx | 14 +- yarn.lock | 10 +- 11 files changed, 171 insertions(+), 145 deletions(-) delete mode 100644 src/context/intermediate/popovers/ServerIdentityModal.module.scss delete mode 100644 src/context/intermediate/popovers/ServerIdentityModal.tsx create mode 100644 src/context/modals/components/ServerIdentity.tsx diff --git a/package.json b/package.json index 21f82367..3e24f4d0 100644 --- a/package.json +++ b/package.json @@ -73,7 +73,7 @@ "@hcaptcha/react-hcaptcha": "^0.3.6", "@insertish/vite-plugin-babel-macros": "^1.0.5", "@preact/preset-vite": "^2.0.0", - "@revoltchat/ui": "1.0.43", + "@revoltchat/ui": "1.0.45", "@rollup/plugin-replace": "^2.4.2", "@styled-icons/boxicons-logos": "^10.38.0", "@styled-icons/boxicons-regular": "^10.38.0", diff --git a/src/context/intermediate/Intermediate.tsx b/src/context/intermediate/Intermediate.tsx index bfc96235..3b20ff0c 100644 --- a/src/context/intermediate/Intermediate.tsx +++ b/src/context/intermediate/Intermediate.tsx @@ -3,7 +3,13 @@ import { useHistory } from "react-router-dom"; import { API, Channel, Message, Server, User } from "revolt.js"; import { createContext } from "preact"; -import { useContext, useEffect, useMemo, useState } from "preact/hooks"; +import { + StateUpdater, + useContext, + useEffect, + useMemo, + useState, +} from "preact/hooks"; import type { Action } from "@revoltchat/ui/esm/components/design/atoms/display/Modal"; @@ -128,12 +134,11 @@ interface Props { children: Children; } -export let __thisIsAHack; +export let __thisIsAHack: StateUpdater; export default function Intermediate(props: Props) { const [screen, openScreen] = useState({ id: "none" }); __thisIsAHack = openScreen; - const settings = useApplicationState().settings; const history = useHistory(); const value = { diff --git a/src/context/intermediate/Popovers.tsx b/src/context/intermediate/Popovers.tsx index 4d6dc501..f5d8f347 100644 --- a/src/context/intermediate/Popovers.tsx +++ b/src/context/intermediate/Popovers.tsx @@ -1,14 +1,11 @@ import { useContext } from "preact/hooks"; -import { internalEmit } from "../../lib/eventEmitter"; - import { IntermediateContext, useIntermediate } from "./Intermediate"; import { SpecialInputModal } from "./modals/Input"; import { SpecialPromptModal } from "./modals/Prompt"; import { ChannelInfo } from "./popovers/ChannelInfo"; import { CreateBotModal } from "./popovers/CreateBot"; import { ImageViewer } from "./popovers/ImageViewer"; -import { ServerIdentityModal } from "./popovers/ServerIdentityModal"; import { UserPicker } from "./popovers/UserPicker"; import { UserProfile } from "./popovers/UserProfile"; @@ -42,9 +39,6 @@ export default function Popovers() { case "special_input": // @ts-expect-error someone figure this out :) return ; - case "server_identity": - // @ts-expect-error someone figure this out :) - return ; } return null; diff --git a/src/context/intermediate/popovers/ServerIdentityModal.module.scss b/src/context/intermediate/popovers/ServerIdentityModal.module.scss deleted file mode 100644 index f88aed03..00000000 --- a/src/context/intermediate/popovers/ServerIdentityModal.module.scss +++ /dev/null @@ -1,17 +0,0 @@ -.identityMain { - display: flex; - flex-direction: row; - gap: 10px; - - > div { - display: flex; - flex-direction: column; - gap: 10px; - } - - .buttons { - display: flex; - flex-direction: row; - gap: 10px; - } -} diff --git a/src/context/intermediate/popovers/ServerIdentityModal.tsx b/src/context/intermediate/popovers/ServerIdentityModal.tsx deleted file mode 100644 index 1ec953d9..00000000 --- a/src/context/intermediate/popovers/ServerIdentityModal.tsx +++ /dev/null @@ -1,106 +0,0 @@ -import { observer } from "mobx-react-lite"; -import { Server } from "revolt.js"; - -import styles from "./ServerIdentityModal.module.scss"; -import { Text } from "preact-i18n"; -import { useEffect, useState } from "preact/hooks"; - -import { Button, Category, InputBox, Modal } from "@revoltchat/ui"; - -import { noop } from "../../../lib/js"; - -import { FileUploader } from "../../revoltjs/FileUploads"; -import { useClient } from "../../revoltjs/RevoltClient"; - -interface Props { - server: Server; - onClose: () => void; -} - -export const ServerIdentityModal = observer(({ server, onClose }: Props) => { - const client = useClient(); - const member = client.members.getKey({ - server: server._id, - user: client.user!._id, - }); - - if (!member) return null; - - const [nickname, setNickname] = useState(""); - const [currentNickname, setCurrentNickname] = useState(""); - useEffect(() => { - setNickname(member.nickname ?? ""); - setCurrentNickname(member.nickname ?? ""); - }, [member.nickname]); - - return ( - - } - onClose={onClose}> -
-
- - - - - member.edit({ avatar }).then(noop) - } - remove={() => - member.edit({ remove: ["Avatar"] }).then(noop) - } - defaultPreview={client.user?.generateAvatarURL( - { - max_side: 256, - }, - false, - )} - previewURL={client.generateFileURL( - member.avatar ?? undefined, - { max_side: 256 }, - true, - )} - desaturateDefault - /> -
-
- - - - setNickname(e.currentTarget.value)} - /> -
- - {currentNickname !== "" && ( - - )} -
-
-
-
- ); -}); diff --git a/src/context/modals/components/ServerIdentity.tsx b/src/context/modals/components/ServerIdentity.tsx new file mode 100644 index 00000000..50c923ce --- /dev/null +++ b/src/context/modals/components/ServerIdentity.tsx @@ -0,0 +1,138 @@ +import { X } from "@styled-icons/boxicons-regular"; +import { Save } from "@styled-icons/boxicons-solid"; +import { observer } from "mobx-react-lite"; +import styled from "styled-components"; + +import { Text } from "preact-i18n"; +import { useMemo, useState } from "preact/hooks"; + +import { + Button, + Category, + Centred, + Column, + InputBox, + Modal, + Row, + Message, +} from "@revoltchat/ui"; + +import { noop } from "../../../lib/js"; + +import { FileUploader } from "../../revoltjs/FileUploads"; +import { ModalProps } from "../types"; + +const Preview = styled(Centred)` + flex-grow: 1; + border-radius: var(--border-radius); + background: var(--secondary-background); + + > div { + padding: 0; + } +`; + +export default observer( + ({ member, ...props }: ModalProps<"server_identity">) => { + const [nickname, setNickname] = useState(member.nickname ?? ""); + + const message: any = useMemo(() => { + return { + author: member.user!, + member: { + ...member, + nickname, + }, + }; + }, []); + + return ( + + }> + + + + + + + + setNickname(e.currentTarget.value) + } + /> + + + + + + + + + + + member.edit({ avatar }).then(noop) + } + remove={() => + member + .edit({ remove: ["Avatar"] }) + .then(noop) + } + defaultPreview={member.user?.generateAvatarURL( + { + max_side: 256, + }, + false, + )} + previewURL={member.client.generateFileURL( + member.avatar ?? undefined, + { max_side: 256 }, + true, + )} + desaturateDefault + /> + + + Preview + + + + + + + + ); + }, +); diff --git a/src/context/modals/index.tsx b/src/context/modals/index.tsx index 6f9c06a8..c2c062d0 100644 --- a/src/context/modals/index.tsx +++ b/src/context/modals/index.tsx @@ -25,6 +25,7 @@ import MFARecovery from "./components/MFARecovery"; import ModifyAccount from "./components/ModifyAccount"; import OutOfDate from "./components/OutOfDate"; import PendingFriendRequests from "./components/PendingFriendRequests"; +import ServerIdentity from "./components/ServerIdentity"; import ShowToken from "./components/ShowToken"; import SignOutSessions from "./components/SignOutSessions"; import SignedOut from "./components/SignedOut"; @@ -211,6 +212,7 @@ export const modalController = new ModalControllerExtended({ modify_account: ModifyAccount, out_of_date: OutOfDate, pending_friend_requests: PendingFriendRequests, + server_identity: ServerIdentity, show_token: ShowToken, signed_out: SignedOut, sign_out_sessions: SignOutSessions, diff --git a/src/context/modals/types.ts b/src/context/modals/types.ts index d9ca14b0..d95297ac 100644 --- a/src/context/modals/types.ts +++ b/src/context/modals/types.ts @@ -1,4 +1,4 @@ -import { API, Client, User } from "revolt.js"; +import { API, Client, User, Member } from "revolt.js"; export type Modal = { key?: string; @@ -65,6 +65,10 @@ export type Modal = { client: Client; field: "username" | "email" | "password"; } + | { + type: "server_identity"; + member: Member; + } | { type: "signed_out"; } diff --git a/src/context/revoltjs/FileUploads.tsx b/src/context/revoltjs/FileUploads.tsx index 16ec2792..63c24928 100644 --- a/src/context/revoltjs/FileUploads.tsx +++ b/src/context/revoltjs/FileUploads.tsx @@ -11,6 +11,8 @@ import { IconButton, Preloader } from "@revoltchat/ui"; import { determineFileSize } from "../../lib/fileSize"; +import { useApplicationState } from "../../mobx/State"; + import { useIntermediate } from "../intermediate/Intermediate"; import { modalController } from "../modals"; import { AppContext } from "./RevoltClient"; @@ -113,7 +115,7 @@ export function grabFiles( export function FileUploader(props: Props) { const { fileType, maxFileSize, remove } = props; const { openScreen } = useIntermediate(); - const client = useContext(AppContext); + const client = useApplicationState().client!; const [uploading, setUploading] = useState(false); diff --git a/src/lib/ContextMenus.tsx b/src/lib/ContextMenus.tsx index 7ca44ae7..5028bddd 100644 --- a/src/lib/ContextMenus.tsx +++ b/src/lib/ContextMenus.tsx @@ -10,6 +10,7 @@ import { API, Permission, UserPermission, + Member, } from "revolt.js"; import { @@ -101,7 +102,7 @@ type Action = | { action: "close_dm"; target: Channel } | { action: "leave_server"; target: Server } | { action: "delete_server"; target: Server } - | { action: "edit_identity"; target: Server } + | { action: "edit_identity"; target: Member } | { action: "open_notification_options"; channel?: Channel; @@ -399,9 +400,9 @@ export default function ContextMenus() { break; case "edit_identity": - openScreen({ - id: "server_identity", - server: data.target, + modalController.push({ + type: "server_identity", + member: data.target, }); break; @@ -952,7 +953,10 @@ export default function ContextMenus() { serverPermissions & Permission.ChangeAvatar ) generateAction( - { action: "edit_identity", target: server }, + { + action: "edit_identity", + target: server.member!, + }, "edit_identity", ); diff --git a/yarn.lock b/yarn.lock index 5a43c7b5..e99ce654 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2231,9 +2231,9 @@ __metadata: languageName: node linkType: hard -"@revoltchat/ui@npm:1.0.43": - version: 1.0.43 - resolution: "@revoltchat/ui@npm:1.0.43" +"@revoltchat/ui@npm:1.0.45": + version: 1.0.45 + resolution: "@revoltchat/ui@npm:1.0.45" dependencies: "@styled-icons/boxicons-logos": ^10.38.0 "@styled-icons/boxicons-regular": ^10.38.0 @@ -2246,7 +2246,7 @@ __metadata: react-device-detect: "*" react-virtuoso: "*" revolt.js: "*" - checksum: d6a6d0cb4a2f08fea45a4d61e5599894012fbb591472ef95d34ee8ddc9e66cfdc7626e94360b7c104e59d3c64a7d0bd674d6a42f5c3cefc723574db8c1aee64e + checksum: 3a4eef546b1ad941dfd47381705b5121b06e8f6781699813fd3ac9a4d559470e2a720af6123cbea7ad4d5d24a7805ebd9c1f384f7a09d2eb73ff83daf69e6a1c languageName: node linkType: hard @@ -3539,7 +3539,7 @@ __metadata: "@hcaptcha/react-hcaptcha": ^0.3.6 "@insertish/vite-plugin-babel-macros": ^1.0.5 "@preact/preset-vite": ^2.0.0 - "@revoltchat/ui": 1.0.43 + "@revoltchat/ui": 1.0.45 "@rollup/plugin-replace": ^2.4.2 "@styled-icons/boxicons-logos": ^10.38.0 "@styled-icons/boxicons-regular": ^10.38.0 From f185dec4614ee898ba90df897caacce27eaf7363 Mon Sep 17 00:00:00 2001 From: Paul Makles Date: Sat, 18 Jun 2022 17:03:04 +0100 Subject: [PATCH 090/151] feat: handle system alerts and poll rate changes --- package.json | 2 +- src/lib/eventEmitter.ts | 1 + src/pages/RevoltApp.tsx | 37 +++++++++++++++-------- src/updateWorker.ts | 67 +++++++++++++++++++++++++++++++++++++---- 4 files changed, 87 insertions(+), 20 deletions(-) diff --git a/package.json b/package.json index 3e24f4d0..841b2b79 100644 --- a/package.json +++ b/package.json @@ -1,5 +1,5 @@ { - "version": "0.5.3-7", + "version": "1.0.0", "scripts": { "dev": "node scripts/setup_assets.js --check && vite", "pull": "node scripts/setup_assets.js", diff --git a/src/lib/eventEmitter.ts b/src/lib/eventEmitter.ts index 1ad13148..c54460a9 100644 --- a/src/lib/eventEmitter.ts +++ b/src/lib/eventEmitter.ts @@ -31,3 +31,4 @@ export function internalEmit(ns: string, event: string, ...args: unknown[]) { // - PWA/update // - NewMessages/hide // - NewMessages/mark +// - System/alert diff --git a/src/pages/RevoltApp.tsx b/src/pages/RevoltApp.tsx index 82711ef6..76535ad3 100644 --- a/src/pages/RevoltApp.tsx +++ b/src/pages/RevoltApp.tsx @@ -2,14 +2,11 @@ import { Docked, OverlappingPanels, ShowIf } from "react-overlapping-panels"; import { Switch, Route, useLocation, Link } from "react-router-dom"; import styled, { css } from "styled-components/macro"; -import { useState } from "preact/hooks"; +import { useEffect, useState } from "preact/hooks"; import ContextMenus from "../lib/ContextMenus"; import { isTouchscreenDevice } from "../lib/isTouchscreenDevice"; -import { useApplicationState } from "../mobx/State"; -import { SIDEBAR_CHANNELS } from "../mobx/stores/Layout"; - import Popovers from "../context/intermediate/Popovers"; import Notifications from "../context/revoltjs/Notifications"; import StateMonitor from "../context/revoltjs/StateMonitor"; @@ -18,6 +15,7 @@ import { Titlebar } from "../components/native/Titlebar"; import BottomNavigation from "../components/navigation/BottomNavigation"; import LeftSidebar from "../components/navigation/LeftSidebar"; import RightSidebar from "../components/navigation/RightSidebar"; +import { useSystemAlert } from "../updateWorker"; import Open from "./Open"; import Channel from "./channels/Channel"; import Developer from "./developer/Developer"; @@ -112,22 +110,35 @@ export default function App() { path.startsWith("/invite") || path.includes("/settings"); + const alert = useSystemAlert(); const [statusBar, setStatusBar] = useState(false); + useEffect(() => setStatusBar(true), [alert]); return ( <> - {statusBar && ( + {alert && statusBar && ( -
Partial outage: CDN
+
{alert.text}
)} diff --git a/src/updateWorker.ts b/src/updateWorker.ts index b86d52e3..770a2a90 100644 --- a/src/updateWorker.ts +++ b/src/updateWorker.ts @@ -1,8 +1,11 @@ +import isEqual from "lodash.isequal"; import semver from "semver"; import { ulid } from "ulid"; import { registerSW } from "virtual:pwa-register"; -import { internalEmit } from "./lib/eventEmitter"; +import { useEffect, useState } from "preact/hooks"; + +import { internalEmit, internalSubscribe } from "./lib/eventEmitter"; import { modalController } from "./context/modals"; @@ -33,15 +36,67 @@ export const updateSW = registerSW({ }, }); +let currentPollRate: number; +let scheduledTask: number; + +/** + * Schedule version checker + * @param poll_rate Set poll rate in milliseconds + */ +function schedule(poll_rate = INTERVAL_HOUR) { + if (poll_rate !== currentPollRate) { + currentPollRate = poll_rate; + clearInterval(scheduledTask); + scheduledTask = setInterval( + checkVersion, + poll_rate, + ) as unknown as number; + } +} + +let currentAlert: SystemAlert | undefined; +type SystemAlert = { + text: string; + dismissable?: boolean; + actions?: { + text: string; + type: "internal" | "external"; + href: string; + }[]; +}; + +/** + * Get the current system alert + */ +export function useSystemAlert() { + const [alert, setAlert] = useState(currentAlert); + useEffect(() => internalSubscribe("System", "alert", setAlert as any), []); + return alert; +} + /** * Check whether the client is out of date */ async function checkVersion() { - const { version } = (await fetch("https://api.revolt.chat/release").then( - (res) => res.json(), - )) as { version: string }; + const { version, poll_rate, alert } = (await fetch( + "https://api.revolt.chat/release", + ).then((res) => res.json())) as { + version: string; + poll_rate?: number; + alert?: SystemAlert; + }; - if (!semver.satisfies(APP_VERSION, version) && APP_VERSION !== version) { + // Re-schedule if necessary + schedule(poll_rate); + + // Apply any active alerts + if (!isEqual(alert, currentAlert)) { + currentAlert = alert; + internalEmit("System", "alert", alert); + } + + // Check if we need to update + if (version !== "0.5.3-7" && !semver.satisfies(APP_VERSION, version)) { // Let the worker know we should immediately refresh forceUpdate = true; @@ -59,6 +114,6 @@ async function checkVersion() { if (import.meta.env.VITE_API_URL === "https://api.revolt.chat") { // Check for critical updates hourly + schedule(); checkVersion(); - setInterval(checkVersion, INTERVAL_HOUR); } From 569864167e3edff4955616ab0980b5b48ec4c3f7 Mon Sep 17 00:00:00 2001 From: Paul Makles Date: Sat, 18 Jun 2022 17:05:05 +0100 Subject: [PATCH 091/151] fix: consider whether alert is present in height calc --- src/pages/RevoltApp.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/pages/RevoltApp.tsx b/src/pages/RevoltApp.tsx index 76535ad3..c4b3631b 100644 --- a/src/pages/RevoltApp.tsx +++ b/src/pages/RevoltApp.tsx @@ -149,11 +149,11 @@ export default function App() { Date: Sat, 18 Jun 2022 17:13:00 +0100 Subject: [PATCH 092/151] fix: bump @revoltchat/ui --- external/lang | 2 +- package.json | 2 +- yarn.lock | 10 +++++----- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/external/lang b/external/lang index c8284d49..ae44e2b1 160000 --- a/external/lang +++ b/external/lang @@ -1 +1 @@ -Subproject commit c8284d49b09b108e42fac4a7bdeaf3a447757b61 +Subproject commit ae44e2b179789b363792240c16856d1215bd4221 diff --git a/package.json b/package.json index 841b2b79..5ddb877b 100644 --- a/package.json +++ b/package.json @@ -73,7 +73,7 @@ "@hcaptcha/react-hcaptcha": "^0.3.6", "@insertish/vite-plugin-babel-macros": "^1.0.5", "@preact/preset-vite": "^2.0.0", - "@revoltchat/ui": "1.0.45", + "@revoltchat/ui": "1.0.46", "@rollup/plugin-replace": "^2.4.2", "@styled-icons/boxicons-logos": "^10.38.0", "@styled-icons/boxicons-regular": "^10.38.0", diff --git a/yarn.lock b/yarn.lock index e99ce654..9dda2751 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2231,9 +2231,9 @@ __metadata: languageName: node linkType: hard -"@revoltchat/ui@npm:1.0.45": - version: 1.0.45 - resolution: "@revoltchat/ui@npm:1.0.45" +"@revoltchat/ui@npm:1.0.46": + version: 1.0.46 + resolution: "@revoltchat/ui@npm:1.0.46" dependencies: "@styled-icons/boxicons-logos": ^10.38.0 "@styled-icons/boxicons-regular": ^10.38.0 @@ -2246,7 +2246,7 @@ __metadata: react-device-detect: "*" react-virtuoso: "*" revolt.js: "*" - checksum: 3a4eef546b1ad941dfd47381705b5121b06e8f6781699813fd3ac9a4d559470e2a720af6123cbea7ad4d5d24a7805ebd9c1f384f7a09d2eb73ff83daf69e6a1c + checksum: 6effcb0d44b599fbb50a82e3ef364ab8a706216ff3deb9682c91bddd3ce3ced24e0688833a024e367a56fc0034cf76efcb8fe300a2c1b5ca820163534355df91 languageName: node linkType: hard @@ -3539,7 +3539,7 @@ __metadata: "@hcaptcha/react-hcaptcha": ^0.3.6 "@insertish/vite-plugin-babel-macros": ^1.0.5 "@preact/preset-vite": ^2.0.0 - "@revoltchat/ui": 1.0.45 + "@revoltchat/ui": 1.0.46 "@rollup/plugin-replace": ^2.4.2 "@styled-icons/boxicons-logos": ^10.38.0 "@styled-icons/boxicons-regular": ^10.38.0 From cb0a5214739b8a76708dbeddda14c54118da46d5 Mon Sep 17 00:00:00 2001 From: Paul Makles Date: Tue, 21 Jun 2022 10:57:58 +0100 Subject: [PATCH 093/151] fix: no client context on ModifyAccount fix: no reactivity on account settings closes #706 fixes #683 fixes #702 --- external/lang | 2 +- src/components/settings/account/EditAccount.tsx | 7 +++---- src/context/modals/components/ModifyAccount.tsx | 4 +++- 3 files changed, 7 insertions(+), 6 deletions(-) diff --git a/external/lang b/external/lang index ae44e2b1..50838167 160000 --- a/external/lang +++ b/external/lang @@ -1 +1 @@ -Subproject commit ae44e2b179789b363792240c16856d1215bd4221 +Subproject commit 50838167d7d253de9d08715e6a6070c3ddc9fcc2 diff --git a/src/components/settings/account/EditAccount.tsx b/src/components/settings/account/EditAccount.tsx index 6220214b..d2ccdeac 100644 --- a/src/components/settings/account/EditAccount.tsx +++ b/src/components/settings/account/EditAccount.tsx @@ -1,5 +1,6 @@ import { At } from "@styled-icons/boxicons-regular"; import { Envelope, Key, Pencil } from "@styled-icons/boxicons-solid"; +import { observer } from "mobx-react-lite"; import { Text } from "preact-i18n"; import { useContext, useEffect, useState } from "preact/hooks"; @@ -11,7 +12,6 @@ import { HiddenValue, } from "@revoltchat/ui"; -import { useIntermediate } from "../../../context/intermediate/Intermediate"; import { modalController } from "../../../context/modals"; import { ClientStatus, @@ -19,10 +19,9 @@ import { useClient, } from "../../../context/revoltjs/RevoltClient"; -export default function EditAccount() { +export default observer(() => { const client = useClient(); const status = useContext(StatusContext); - const { openScreen } = useIntermediate(); const [email, setEmail] = useState("..."); @@ -74,4 +73,4 @@ export default function EditAccount() { ))} ); -} +}); diff --git a/src/context/modals/components/ModifyAccount.tsx b/src/context/modals/components/ModifyAccount.tsx index b3fd934c..282c1e13 100644 --- a/src/context/modals/components/ModifyAccount.tsx +++ b/src/context/modals/components/ModifyAccount.tsx @@ -7,6 +7,8 @@ import { Category, Error, Modal } from "@revoltchat/ui"; import { noopTrue } from "../../../lib/js"; +import { useApplicationState } from "../../../mobx/State"; + import FormField from "../../../pages/login/FormField"; import { AppContext } from "../../revoltjs/RevoltClient"; import { takeError } from "../../revoltjs/util"; @@ -27,7 +29,7 @@ export default function ModifyAccount({ field, ...props }: ModalProps<"modify_account">) { - const client = useContext(AppContext); + const client = useApplicationState().client!; const [processing, setProcessing] = useState(false); const { handleSubmit, register, errors } = useForm(); const [error, setError] = useState(undefined); From 3b7c1cbe2017d74f53d8e8027df4f22084a4bf20 Mon Sep 17 00:00:00 2001 From: Paul Makles Date: Tue, 21 Jun 2022 11:14:04 +0100 Subject: [PATCH 094/151] chore(ci): allow any tag [skip ci] --- .github/workflows/docker.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/docker.yml b/.github/workflows/docker.yml index 543a190c..91ee31a7 100644 --- a/.github/workflows/docker.yml +++ b/.github/workflows/docker.yml @@ -5,7 +5,7 @@ on: branches: - "master" tags: - - "v*" + - "*" paths-ignore: - ".github/**" - "!.github/workflows/docker.yml" From 95ebd935ed94056eb624b7bbfbd992c0102a6fa0 Mon Sep 17 00:00:00 2001 From: Paul Makles Date: Tue, 21 Jun 2022 11:14:51 +0100 Subject: [PATCH 095/151] fix: duct-tape fix the bot edit issues fixes #629 --- package.json | 2 +- .../intermediate/popovers/CreateBot.tsx | 1 + src/pages/settings/panes/MyBots.tsx | 27 ++----------------- 3 files changed, 4 insertions(+), 26 deletions(-) diff --git a/package.json b/package.json index 5ddb877b..7e7091a4 100644 --- a/package.json +++ b/package.json @@ -1,5 +1,5 @@ { - "version": "1.0.0", + "version": "1.0.1", "scripts": { "dev": "node scripts/setup_assets.js --check && vite", "pull": "node scripts/setup_assets.js", diff --git a/src/context/intermediate/popovers/CreateBot.tsx b/src/context/intermediate/popovers/CreateBot.tsx index 65a71443..45837755 100644 --- a/src/context/intermediate/popovers/CreateBot.tsx +++ b/src/context/intermediate/popovers/CreateBot.tsx @@ -29,6 +29,7 @@ export function CreateBotModal({ onClose, onCreate }: Props) { try { const { bot } = await client.bots.create({ name }); onCreate(bot); + onClose(); } catch (err) { setError(takeError(err)); } diff --git a/src/pages/settings/panes/MyBots.tsx b/src/pages/settings/panes/MyBots.tsx index a71f5a04..3cb64855 100644 --- a/src/pages/settings/panes/MyBots.tsx +++ b/src/pages/settings/panes/MyBots.tsx @@ -99,12 +99,6 @@ function BotCard({ bot, onDelete, onUpdate }: Props) { client.api .get(`/users/${bot._id as ""}/profile`, undefined, { headers: { "x-bot-token": bot.token }, - transformRequest: (data, headers) => { - // Remove user headers for this request - delete headers?.["x-user-id"]; - delete headers?.["x-session-token"]; - return data; - }, }) .then((profile) => setProfile(profile ?? {})); // eslint-disable-next-line react-hooks/exhaustive-deps @@ -130,7 +124,8 @@ function BotCard({ bot, onDelete, onUpdate }: Props) { setSaving(true); setError(""); try { - await client.bots.edit(bot._id, changes); + if (Object.keys(changes).length > 0) + await client.bots.edit(bot._id, changes); if (changed) await editBotContent(profile?.content ?? undefined); onUpdate(changes); setChanged(false); @@ -159,12 +154,6 @@ function BotCard({ bot, onDelete, onUpdate }: Props) { avatar ? { avatar } : { remove: ["Avatar"] }, { headers: { "x-bot-token": bot.token }, - transformRequest: (data, headers) => { - // Remove user headers for this request - delete headers?.["x-user-id"]; - delete headers?.["x-session-token"]; - return JSON.stringify(data); - }, }, ); @@ -184,12 +173,6 @@ function BotCard({ bot, onDelete, onUpdate }: Props) { : { remove: ["ProfileBackground"] }, { headers: { "x-bot-token": bot.token }, - transformRequest: (data, headers) => { - // Remove user headers for this request - delete headers?.["x-user-id"]; - delete headers?.["x-session-token"]; - return JSON.stringify(data); - }, }, ); @@ -206,12 +189,6 @@ function BotCard({ bot, onDelete, onUpdate }: Props) { content ? { profile: { content } } : { remove: ["ProfileContent"] }, { headers: { "x-bot-token": bot.token }, - transformRequest: (data, headers) => { - // Remove user headers for this request - delete headers?.["x-user-id"]; - delete headers?.["x-session-token"]; - return data; - }, }, ); From 1cfcb20d4de2ea643d90f2ed33aa1238c75407c2 Mon Sep 17 00:00:00 2001 From: Paul Makles Date: Mon, 27 Jun 2022 17:56:06 +0100 Subject: [PATCH 096/151] chore(refactor): rename `context/modals` to `controllers/modals` --- external/lang | 2 +- src/components/common/messaging/MessageBox.tsx | 2 +- src/components/settings/account/AccountManagement.tsx | 3 ++- src/components/settings/account/EditAccount.tsx | 3 ++- .../settings/account/MultiFactorAuthentication.tsx | 3 ++- src/context/DO_NOT_TOUCH.md | 2 ++ src/context/index.tsx | 2 +- src/context/intermediate/Intermediate.tsx | 2 +- src/context/revoltjs/FileUploads.tsx | 2 +- src/context/revoltjs/RevoltClient.tsx | 2 +- .../index.tsx => controllers/modals/ModalController.tsx} | 7 ++++--- src/{context => controllers}/modals/ModalRenderer.tsx | 2 +- .../modals/components/Changelog.tsx | 0 .../modals/components/Clipboard.tsx | 0 src/{context => controllers}/modals/components/Error.tsx | 0 .../modals/components/LinkWarning.tsx | 0 .../modals/components/MFAEnableTOTP.tsx | 0 src/{context => controllers}/modals/components/MFAFlow.tsx | 0 .../modals/components/MFARecovery.tsx | 2 +- .../modals/components/ModifyAccount.tsx | 5 +++-- .../modals/components/OutOfDate.tsx | 0 .../modals/components/PendingFriendRequests.tsx | 0 .../modals/components/ServerIdentity.tsx | 3 ++- .../modals/components/ShowToken.tsx | 0 .../modals/components/SignOutSessions.tsx | 0 .../modals/components/SignedOut.tsx | 0 src/{context => controllers}/modals/types.ts | 0 src/lib/ContextMenus.tsx | 2 +- src/mobx/stores/Changelog.ts | 3 +-- src/pages/Open.tsx | 3 ++- src/pages/friends/Friends.tsx | 2 +- src/pages/login/forms/FormLogin.tsx | 2 +- src/pages/settings/GenericSettings.tsx | 3 +-- src/pages/settings/Settings.tsx | 2 +- src/pages/settings/panes/MyBots.tsx | 2 +- src/pages/settings/panes/Notifications.tsx | 3 ++- src/pages/settings/panes/Sessions.tsx | 3 ++- src/updateWorker.ts | 3 +-- 38 files changed, 39 insertions(+), 31 deletions(-) create mode 100644 src/context/DO_NOT_TOUCH.md rename src/{context/modals/index.tsx => controllers/modals/ModalController.tsx} (97%) rename src/{context => controllers}/modals/ModalRenderer.tsx (91%) rename src/{context => controllers}/modals/components/Changelog.tsx (100%) rename src/{context => controllers}/modals/components/Clipboard.tsx (100%) rename src/{context => controllers}/modals/components/Error.tsx (100%) rename src/{context => controllers}/modals/components/LinkWarning.tsx (100%) rename src/{context => controllers}/modals/components/MFAEnableTOTP.tsx (100%) rename src/{context => controllers}/modals/components/MFAFlow.tsx (100%) rename src/{context => controllers}/modals/components/MFARecovery.tsx (97%) rename src/{context => controllers}/modals/components/ModifyAccount.tsx (97%) rename src/{context => controllers}/modals/components/OutOfDate.tsx (100%) rename src/{context => controllers}/modals/components/PendingFriendRequests.tsx (100%) rename src/{context => controllers}/modals/components/ServerIdentity.tsx (98%) rename src/{context => controllers}/modals/components/ShowToken.tsx (100%) rename src/{context => controllers}/modals/components/SignOutSessions.tsx (100%) rename src/{context => controllers}/modals/components/SignedOut.tsx (100%) rename src/{context => controllers}/modals/types.ts (100%) diff --git a/external/lang b/external/lang index 50838167..def08f21 160000 --- a/external/lang +++ b/external/lang @@ -1 +1 @@ -Subproject commit 50838167d7d253de9d08715e6a6070c3ddc9fcc2 +Subproject commit def08f210e9edc4f203cb38611fd270761102860 diff --git a/src/components/common/messaging/MessageBox.tsx b/src/components/common/messaging/MessageBox.tsx index 0c7acc9d..4bb4dd49 100644 --- a/src/components/common/messaging/MessageBox.tsx +++ b/src/components/common/messaging/MessageBox.tsx @@ -24,7 +24,6 @@ import { import { useApplicationState } from "../../../mobx/State"; import { Reply } from "../../../mobx/stores/MessageQueue"; -import { modalController } from "../../../context/modals"; import { FileUploader, grabFiles, @@ -33,6 +32,7 @@ import { import { AppContext } from "../../../context/revoltjs/RevoltClient"; import { takeError } from "../../../context/revoltjs/util"; +import { modalController } from "../../../controllers/modals/ModalController"; import AutoComplete, { useAutoComplete } from "../AutoComplete"; import { PermissionTooltip } from "../Tooltip"; import FilePreview from "./bars/FilePreview"; diff --git a/src/components/settings/account/AccountManagement.tsx b/src/components/settings/account/AccountManagement.tsx index 7af4b367..f7ac5843 100644 --- a/src/components/settings/account/AccountManagement.tsx +++ b/src/components/settings/account/AccountManagement.tsx @@ -6,12 +6,13 @@ import { useContext } from "preact/hooks"; import { CategoryButton } from "@revoltchat/ui"; -import { modalController } from "../../../context/modals"; import { LogOutContext, useClient, } from "../../../context/revoltjs/RevoltClient"; +import { modalController } from "../../../controllers/modals/ModalController"; + export default function AccountManagement() { const logOut = useContext(LogOutContext); const client = useClient(); diff --git a/src/components/settings/account/EditAccount.tsx b/src/components/settings/account/EditAccount.tsx index d2ccdeac..929bdfb1 100644 --- a/src/components/settings/account/EditAccount.tsx +++ b/src/components/settings/account/EditAccount.tsx @@ -12,13 +12,14 @@ import { HiddenValue, } from "@revoltchat/ui"; -import { modalController } from "../../../context/modals"; import { ClientStatus, StatusContext, useClient, } from "../../../context/revoltjs/RevoltClient"; +import { modalController } from "../../../controllers/modals/ModalController"; + export default observer(() => { const client = useClient(); const status = useContext(StatusContext); diff --git a/src/components/settings/account/MultiFactorAuthentication.tsx b/src/components/settings/account/MultiFactorAuthentication.tsx index 1b7bfbf8..d1cca41d 100644 --- a/src/components/settings/account/MultiFactorAuthentication.tsx +++ b/src/components/settings/account/MultiFactorAuthentication.tsx @@ -7,7 +7,6 @@ import { useCallback, useContext, useEffect, useState } from "preact/hooks"; import { Category, CategoryButton, Error, Tip } from "@revoltchat/ui"; -import { modalController } from "../../../context/modals"; import { ClientStatus, StatusContext, @@ -15,6 +14,8 @@ import { } from "../../../context/revoltjs/RevoltClient"; import { takeError } from "../../../context/revoltjs/util"; +import { modalController } from "../../../controllers/modals/ModalController"; + /** * Temporary helper function for Axios config * @param token Token diff --git a/src/context/DO_NOT_TOUCH.md b/src/context/DO_NOT_TOUCH.md new file mode 100644 index 00000000..db75657b --- /dev/null +++ b/src/context/DO_NOT_TOUCH.md @@ -0,0 +1,2 @@ +hello do not touch `intermediate` or `revoltjs` folders +they are being rewritten diff --git a/src/context/index.tsx b/src/context/index.tsx index 663fdfaa..1785174b 100644 --- a/src/context/index.tsx +++ b/src/context/index.tsx @@ -8,11 +8,11 @@ import { Preloader, UIProvider } from "@revoltchat/ui"; import { hydrateState } from "../mobx/State"; +import ModalRenderer from "../controllers/modals/ModalRenderer"; import Locale from "./Locale"; import Theme from "./Theme"; import { history } from "./history"; import Intermediate from "./intermediate/Intermediate"; -import ModalRenderer from "./modals/ModalRenderer"; import Client from "./revoltjs/RevoltClient"; import SyncManager from "./revoltjs/SyncManager"; diff --git a/src/context/intermediate/Intermediate.tsx b/src/context/intermediate/Intermediate.tsx index 3b20ff0c..32df0c1b 100644 --- a/src/context/intermediate/Intermediate.tsx +++ b/src/context/intermediate/Intermediate.tsx @@ -18,7 +18,7 @@ import { determineLink } from "../../lib/links"; import { useApplicationState } from "../../mobx/State"; -import { modalController } from "../modals"; +import { modalController } from "../../controllers/modals/ModalController"; import Modals from "./Modals"; export type Screen = diff --git a/src/context/revoltjs/FileUploads.tsx b/src/context/revoltjs/FileUploads.tsx index 63c24928..5b32b51a 100644 --- a/src/context/revoltjs/FileUploads.tsx +++ b/src/context/revoltjs/FileUploads.tsx @@ -13,8 +13,8 @@ import { determineFileSize } from "../../lib/fileSize"; import { useApplicationState } from "../../mobx/State"; +import { modalController } from "../../controllers/modals/ModalController"; import { useIntermediate } from "../intermediate/Intermediate"; -import { modalController } from "../modals"; import { AppContext } from "./RevoltClient"; import { takeError } from "./util"; diff --git a/src/context/revoltjs/RevoltClient.tsx b/src/context/revoltjs/RevoltClient.tsx index 733b1c9e..f16f3ac1 100644 --- a/src/context/revoltjs/RevoltClient.tsx +++ b/src/context/revoltjs/RevoltClient.tsx @@ -9,7 +9,7 @@ import { Preloader } from "@revoltchat/ui"; import { useApplicationState } from "../../mobx/State"; -import { modalController } from "../modals"; +import { modalController } from "../../controllers/modals/ModalController"; import { registerEvents } from "./events"; import { takeError } from "./util"; diff --git a/src/context/modals/index.tsx b/src/controllers/modals/ModalController.tsx similarity index 97% rename from src/context/modals/index.tsx rename to src/controllers/modals/ModalController.tsx index c2c062d0..48bb810d 100644 --- a/src/context/modals/index.tsx +++ b/src/controllers/modals/ModalController.tsx @@ -10,10 +10,11 @@ import { ulid } from "ulid"; import { determineLink } from "../../lib/links"; -import { getApplicationState, useApplicationState } from "../../mobx/State"; +import { getApplicationState } from "../../mobx/State"; + +import { history } from "../../context/history"; +import { __thisIsAHack } from "../../context/intermediate/Intermediate"; -import { history } from "../history"; -import { __thisIsAHack } from "../intermediate/Intermediate"; // import { determineLink } from "../../lib/links"; import Changelog from "./components/Changelog"; import Clipboard from "./components/Clipboard"; diff --git a/src/context/modals/ModalRenderer.tsx b/src/controllers/modals/ModalRenderer.tsx similarity index 91% rename from src/context/modals/ModalRenderer.tsx rename to src/controllers/modals/ModalRenderer.tsx index 0092d93b..712a4dc4 100644 --- a/src/context/modals/ModalRenderer.tsx +++ b/src/controllers/modals/ModalRenderer.tsx @@ -2,7 +2,7 @@ import { observer } from "mobx-react-lite"; import { useEffect } from "preact/hooks"; -import { modalController } from "."; +import { modalController } from "./ModalController"; export default observer(() => { useEffect(() => { diff --git a/src/context/modals/components/Changelog.tsx b/src/controllers/modals/components/Changelog.tsx similarity index 100% rename from src/context/modals/components/Changelog.tsx rename to src/controllers/modals/components/Changelog.tsx diff --git a/src/context/modals/components/Clipboard.tsx b/src/controllers/modals/components/Clipboard.tsx similarity index 100% rename from src/context/modals/components/Clipboard.tsx rename to src/controllers/modals/components/Clipboard.tsx diff --git a/src/context/modals/components/Error.tsx b/src/controllers/modals/components/Error.tsx similarity index 100% rename from src/context/modals/components/Error.tsx rename to src/controllers/modals/components/Error.tsx diff --git a/src/context/modals/components/LinkWarning.tsx b/src/controllers/modals/components/LinkWarning.tsx similarity index 100% rename from src/context/modals/components/LinkWarning.tsx rename to src/controllers/modals/components/LinkWarning.tsx diff --git a/src/context/modals/components/MFAEnableTOTP.tsx b/src/controllers/modals/components/MFAEnableTOTP.tsx similarity index 100% rename from src/context/modals/components/MFAEnableTOTP.tsx rename to src/controllers/modals/components/MFAEnableTOTP.tsx diff --git a/src/context/modals/components/MFAFlow.tsx b/src/controllers/modals/components/MFAFlow.tsx similarity index 100% rename from src/context/modals/components/MFAFlow.tsx rename to src/controllers/modals/components/MFAFlow.tsx diff --git a/src/context/modals/components/MFARecovery.tsx b/src/controllers/modals/components/MFARecovery.tsx similarity index 97% rename from src/context/modals/components/MFARecovery.tsx rename to src/controllers/modals/components/MFARecovery.tsx index bed9afd1..33a11809 100644 --- a/src/context/modals/components/MFARecovery.tsx +++ b/src/controllers/modals/components/MFARecovery.tsx @@ -7,8 +7,8 @@ import { Modal } from "@revoltchat/ui"; import { noopTrue } from "../../../lib/js"; -import { modalController } from ".."; import { toConfig } from "../../../components/settings/account/MultiFactorAuthentication"; +import { modalController } from "../ModalController"; import { ModalProps } from "../types"; /** diff --git a/src/context/modals/components/ModifyAccount.tsx b/src/controllers/modals/components/ModifyAccount.tsx similarity index 97% rename from src/context/modals/components/ModifyAccount.tsx rename to src/controllers/modals/components/ModifyAccount.tsx index 282c1e13..2971e48e 100644 --- a/src/context/modals/components/ModifyAccount.tsx +++ b/src/controllers/modals/components/ModifyAccount.tsx @@ -9,9 +9,10 @@ import { noopTrue } from "../../../lib/js"; import { useApplicationState } from "../../../mobx/State"; +import { AppContext } from "../../../context/revoltjs/RevoltClient"; +import { takeError } from "../../../context/revoltjs/util"; + import FormField from "../../../pages/login/FormField"; -import { AppContext } from "../../revoltjs/RevoltClient"; -import { takeError } from "../../revoltjs/util"; import { ModalProps } from "../types"; interface FormInputs { diff --git a/src/context/modals/components/OutOfDate.tsx b/src/controllers/modals/components/OutOfDate.tsx similarity index 100% rename from src/context/modals/components/OutOfDate.tsx rename to src/controllers/modals/components/OutOfDate.tsx diff --git a/src/context/modals/components/PendingFriendRequests.tsx b/src/controllers/modals/components/PendingFriendRequests.tsx similarity index 100% rename from src/context/modals/components/PendingFriendRequests.tsx rename to src/controllers/modals/components/PendingFriendRequests.tsx diff --git a/src/context/modals/components/ServerIdentity.tsx b/src/controllers/modals/components/ServerIdentity.tsx similarity index 98% rename from src/context/modals/components/ServerIdentity.tsx rename to src/controllers/modals/components/ServerIdentity.tsx index 50c923ce..5a8dfc9a 100644 --- a/src/context/modals/components/ServerIdentity.tsx +++ b/src/controllers/modals/components/ServerIdentity.tsx @@ -19,7 +19,8 @@ import { import { noop } from "../../../lib/js"; -import { FileUploader } from "../../revoltjs/FileUploads"; +import { FileUploader } from "../../../context/revoltjs/FileUploads"; + import { ModalProps } from "../types"; const Preview = styled(Centred)` diff --git a/src/context/modals/components/ShowToken.tsx b/src/controllers/modals/components/ShowToken.tsx similarity index 100% rename from src/context/modals/components/ShowToken.tsx rename to src/controllers/modals/components/ShowToken.tsx diff --git a/src/context/modals/components/SignOutSessions.tsx b/src/controllers/modals/components/SignOutSessions.tsx similarity index 100% rename from src/context/modals/components/SignOutSessions.tsx rename to src/controllers/modals/components/SignOutSessions.tsx diff --git a/src/context/modals/components/SignedOut.tsx b/src/controllers/modals/components/SignedOut.tsx similarity index 100% rename from src/context/modals/components/SignedOut.tsx rename to src/controllers/modals/components/SignedOut.tsx diff --git a/src/context/modals/types.ts b/src/controllers/modals/types.ts similarity index 100% rename from src/context/modals/types.ts rename to src/controllers/modals/types.ts diff --git a/src/lib/ContextMenus.tsx b/src/lib/ContextMenus.tsx index 5028bddd..2dc652d7 100644 --- a/src/lib/ContextMenus.tsx +++ b/src/lib/ContextMenus.tsx @@ -28,7 +28,6 @@ import { QueuedMessage } from "../mobx/stores/MessageQueue"; import { NotificationState } from "../mobx/stores/NotificationOptions"; import { Screen, useIntermediate } from "../context/intermediate/Intermediate"; -import { modalController } from "../context/modals"; import { AppContext, ClientStatus, @@ -39,6 +38,7 @@ import CMNotifications from "./contextmenu/CMNotifications"; import Tooltip from "../components/common/Tooltip"; import UserStatus from "../components/common/user/UserStatus"; +import { modalController } from "../controllers/modals/ModalController"; import { internalEmit } from "./eventEmitter"; import { getRenderer } from "./renderer/Singleton"; diff --git a/src/mobx/stores/Changelog.ts b/src/mobx/stores/Changelog.ts index 410e1518..553117e0 100644 --- a/src/mobx/stores/Changelog.ts +++ b/src/mobx/stores/Changelog.ts @@ -1,8 +1,7 @@ import { action, makeAutoObservable, runInAction } from "mobx"; -import { modalController } from "../../context/modals"; - import { latestChangelog } from "../../assets/changelogs"; +import { modalController } from "../../controllers/modals/ModalController"; import Persistent from "../interfaces/Persistent"; import Store from "../interfaces/Store"; import Syncable from "../interfaces/Syncable"; diff --git a/src/pages/Open.tsx b/src/pages/Open.tsx index 20e5265f..40dd5f7a 100644 --- a/src/pages/Open.tsx +++ b/src/pages/Open.tsx @@ -6,13 +6,14 @@ import { useContext, useEffect } from "preact/hooks"; import { Header } from "@revoltchat/ui"; -import { modalController } from "../context/modals"; import { AppContext, ClientStatus, StatusContext, } from "../context/revoltjs/RevoltClient"; +import { modalController } from "../controllers/modals/ModalController"; + export default function Open() { const history = useHistory(); const client = useContext(AppContext); diff --git a/src/pages/friends/Friends.tsx b/src/pages/friends/Friends.tsx index aa3af078..e269755f 100644 --- a/src/pages/friends/Friends.tsx +++ b/src/pages/friends/Friends.tsx @@ -13,13 +13,13 @@ import { TextReact } from "../../lib/i18n"; import { isTouchscreenDevice } from "../../lib/isTouchscreenDevice"; import { useIntermediate } from "../../context/intermediate/Intermediate"; -import { modalController } from "../../context/modals"; import { useClient } from "../../context/revoltjs/RevoltClient"; import CollapsibleSection from "../../components/common/CollapsibleSection"; import Tooltip from "../../components/common/Tooltip"; import UserIcon from "../../components/common/user/UserIcon"; import { PageHeader } from "../../components/ui/Header"; +import { modalController } from "../../controllers/modals/ModalController"; import { Friend } from "./Friend"; export default observer(() => { diff --git a/src/pages/login/forms/FormLogin.tsx b/src/pages/login/forms/FormLogin.tsx index 6fc90852..95b2971a 100644 --- a/src/pages/login/forms/FormLogin.tsx +++ b/src/pages/login/forms/FormLogin.tsx @@ -4,8 +4,8 @@ import { API } from "revolt.js"; import { useApplicationState } from "../../../mobx/State"; import { useIntermediate } from "../../../context/intermediate/Intermediate"; -import { modalController } from "../../../context/modals"; +import { modalController } from "../../../controllers/modals/ModalController"; import { Form } from "./Form"; export function FormLogin() { diff --git a/src/pages/settings/GenericSettings.tsx b/src/pages/settings/GenericSettings.tsx index 0c6e5d94..ce2ff32f 100644 --- a/src/pages/settings/GenericSettings.tsx +++ b/src/pages/settings/GenericSettings.tsx @@ -13,9 +13,8 @@ import { isTouchscreenDevice } from "../../lib/isTouchscreenDevice"; import { useApplicationState } from "../../mobx/State"; -import { modalController } from "../../context/modals"; - import ButtonItem from "../../components/navigation/items/ButtonItem"; +import { modalController } from "../../controllers/modals/ModalController"; interface Props { pages: { diff --git a/src/pages/settings/Settings.tsx b/src/pages/settings/Settings.tsx index 7b0f5c36..e5628874 100644 --- a/src/pages/settings/Settings.tsx +++ b/src/pages/settings/Settings.tsx @@ -35,7 +35,6 @@ import { LineDivider } from "@revoltchat/ui"; import { useApplicationState } from "../../mobx/State"; import { useIntermediate } from "../../context/intermediate/Intermediate"; -import { modalController } from "../../context/modals"; import RequiresOnline from "../../context/revoltjs/RequiresOnline"; import { AppContext, LogOutContext } from "../../context/revoltjs/RevoltClient"; @@ -43,6 +42,7 @@ import UserIcon from "../../components/common/user/UserIcon"; import { Username } from "../../components/common/user/UserShort"; import UserStatus from "../../components/common/user/UserStatus"; import ButtonItem from "../../components/navigation/items/ButtonItem"; +import { modalController } from "../../controllers/modals/ModalController"; import { GIT_BRANCH, GIT_REVISION, REPO_URL } from "../../revision"; import { APP_VERSION } from "../../version"; import { GenericSettings } from "./GenericSettings"; diff --git a/src/pages/settings/panes/MyBots.tsx b/src/pages/settings/panes/MyBots.tsx index 3cb64855..226c4478 100644 --- a/src/pages/settings/panes/MyBots.tsx +++ b/src/pages/settings/panes/MyBots.tsx @@ -25,7 +25,6 @@ import { useTranslation } from "../../../lib/i18n"; import { stopPropagation } from "../../../lib/stopPropagation"; import { useIntermediate } from "../../../context/intermediate/Intermediate"; -import { modalController } from "../../../context/modals"; import { FileUploader } from "../../../context/revoltjs/FileUploads"; import { useClient } from "../../../context/revoltjs/RevoltClient"; @@ -35,6 +34,7 @@ import AutoComplete, { import CollapsibleSection from "../../../components/common/CollapsibleSection"; import Tooltip from "../../../components/common/Tooltip"; import UserIcon from "../../../components/common/user/UserIcon"; +import { modalController } from "../../../controllers/modals/ModalController"; interface Data { _id: string; diff --git a/src/pages/settings/panes/Notifications.tsx b/src/pages/settings/panes/Notifications.tsx index d934a63d..a6bf472b 100644 --- a/src/pages/settings/panes/Notifications.tsx +++ b/src/pages/settings/panes/Notifications.tsx @@ -10,9 +10,10 @@ import { urlBase64ToUint8Array } from "../../../lib/conversion"; import { useApplicationState } from "../../../mobx/State"; -import { modalController } from "../../../context/modals"; import { AppContext } from "../../../context/revoltjs/RevoltClient"; +import { modalController } from "../../../controllers/modals/ModalController"; + export const Notifications = observer(() => { const client = useContext(AppContext); const settings = useApplicationState().settings; diff --git a/src/pages/settings/panes/Sessions.tsx b/src/pages/settings/panes/Sessions.tsx index 73e39bc3..dcc55524 100644 --- a/src/pages/settings/panes/Sessions.tsx +++ b/src/pages/settings/panes/Sessions.tsx @@ -27,9 +27,10 @@ import { } from "@revoltchat/ui"; import { dayjs } from "../../../context/Locale"; -import { modalController } from "../../../context/modals"; import { AppContext } from "../../../context/revoltjs/RevoltClient"; +import { modalController } from "../../../controllers/modals/ModalController"; + dayjs.extend(relativeTime); export function Sessions() { diff --git a/src/updateWorker.ts b/src/updateWorker.ts index 770a2a90..5043ef90 100644 --- a/src/updateWorker.ts +++ b/src/updateWorker.ts @@ -7,8 +7,7 @@ import { useEffect, useState } from "preact/hooks"; import { internalEmit, internalSubscribe } from "./lib/eventEmitter"; -import { modalController } from "./context/modals"; - +import { modalController } from "./controllers/modals/ModalController"; import { APP_VERSION } from "./version"; const INTERVAL_HOUR = 36e5; From 80f4bb3d9821482755c18fa65ad357b30565f2ed Mon Sep 17 00:00:00 2001 From: Paul Makles Date: Tue, 28 Jun 2022 13:20:08 +0100 Subject: [PATCH 097/151] feat: build finite state machine for sessions --- src/controllers/client/ClientController.tsx | 49 ++++++ src/controllers/client/Session.tsx | 164 ++++++++++++++++++++ src/mobx/State.ts | 7 +- src/mobx/stores/Auth.ts | 37 ++--- src/mobx/stores/ServerConfig.ts | 4 +- src/types/revolt-api.d.ts | 2 + 6 files changed, 235 insertions(+), 28 deletions(-) create mode 100644 src/controllers/client/ClientController.tsx create mode 100644 src/controllers/client/Session.tsx diff --git a/src/controllers/client/ClientController.tsx b/src/controllers/client/ClientController.tsx new file mode 100644 index 00000000..25fc536c --- /dev/null +++ b/src/controllers/client/ClientController.tsx @@ -0,0 +1,49 @@ +import { action, makeAutoObservable, ObservableMap } from "mobx"; +import type { Nullable } from "revolt.js"; + +import Auth from "../../mobx/stores/Auth"; + +import Session from "./Session"; + +class ClientController { + /** + * Map of user IDs to sessions + */ + private sessions: ObservableMap; + + /** + * User ID of active session + */ + private current: Nullable; + + constructor() { + this.sessions = new ObservableMap(); + this.current = null; + + makeAutoObservable(this); + } + + /** + * Hydrate sessions and start client lifecycles. + * @param auth Authentication store + */ + @action hydrate(auth: Auth) { + for (const entry of auth.getAccounts()) { + const session = new Session(); + session.emit({ + action: "LOGIN", + session: entry.session, + }); + } + } + + getActiveSession() { + return this.sessions; + } + + isLoggedIn() { + return this.current === null; + } +} + +export const clientController = new ClientController(); diff --git a/src/controllers/client/Session.tsx b/src/controllers/client/Session.tsx new file mode 100644 index 00000000..ec9c7e45 --- /dev/null +++ b/src/controllers/client/Session.tsx @@ -0,0 +1,164 @@ +import { action, makeAutoObservable } from "mobx"; +import { Client } from "revolt.js"; + +type State = "Ready" | "Connecting" | "Online" | "Disconnected" | "Offline"; + +type Transition = + | { + action: "LOGIN"; + session: SessionPrivate; + } + | { + action: + | "SUCCESS" + | "DISCONNECT" + | "RETRY" + | "LOGOUT" + | "ONLINE" + | "OFFLINE"; + }; + +export default class Session { + state: State = window.navigator.onLine ? "Ready" : "Offline"; + client: Client | null = null; + + constructor() { + makeAutoObservable(this); + + this.onDropped = this.onDropped.bind(this); + this.onReady = this.onReady.bind(this); + this.onOnline = this.onOnline.bind(this); + this.onOffline = this.onOffline.bind(this); + + window.addEventListener("online", this.onOnline); + window.addEventListener("offline", this.onOffline); + } + + private onOnline() { + this.emit({ + action: "ONLINE", + }); + } + + private onOffline() { + this.emit({ + action: "OFFLINE", + }); + } + + private onDropped() { + this.emit({ + action: "DISCONNECT", + }); + } + + private onReady() { + this.emit({ + action: "SUCCESS", + }); + } + + private createClient() { + this.client = new Client({ + unreads: true, + autoReconnect: false, + onPongTimeout: "EXIT", + apiURL: import.meta.env.VITE_API_URL, + }); + + this.client.addListener("dropped", this.onDropped); + this.client.addListener("ready", this.onReady); + } + + private destroyClient() { + this.client!.removeAllListeners(); + this.client = null; + } + + private assert(...state: State[]) { + let found = false; + for (const target of state) { + if (this.state === target) { + found = true; + break; + } + } + + if (!found) { + throw `State must be ${state} in order to transition! (currently ${this.state})`; + } + } + + @action async emit(data: Transition) { + switch (data.action) { + // Login with session + case "LOGIN": { + this.assert("Ready"); + this.state = "Connecting"; + this.createClient(); + + try { + await this.client!.useExistingSession(data.session); + } catch (err) { + this.state = "Ready"; + throw err; + } + + break; + } + // Ready successfully received + case "SUCCESS": { + this.assert("Connecting"); + this.state = "Online"; + break; + } + // Client got disconnected + case "DISCONNECT": { + if (navigator.onLine) { + this.assert("Online"); + this.state = "Disconnected"; + + setTimeout(() => { + this.emit({ + action: "RETRY", + }); + }, 1500); + } + + break; + } + // We should try reconnecting + case "RETRY": { + this.assert("Disconnected"); + this.client!.websocket.connect(); + this.state = "Connecting"; + break; + } + // User instructed logout + case "LOGOUT": { + this.assert("Connecting", "Online", "Disconnected"); + this.state = "Ready"; + this.destroyClient(); + break; + } + // Browser went offline + case "OFFLINE": { + this.state = "Offline"; + break; + } + // Browser went online + case "ONLINE": { + this.assert("Offline"); + if (this.client) { + this.state = "Disconnected"; + this.emit({ + action: "RETRY", + }); + } else { + this.state = "Ready"; + } + break; + } + } + } +} diff --git a/src/mobx/State.ts b/src/mobx/State.ts index e742ac44..97299b69 100644 --- a/src/mobx/State.ts +++ b/src/mobx/State.ts @@ -4,8 +4,7 @@ import localforage from "localforage"; import { makeAutoObservable, reaction, runInAction } from "mobx"; import { Client } from "revolt.js"; -import { reportError } from "../lib/ErrorBoundary"; - +import { clientController } from "../controllers/client/ClientController"; import Persistent from "./interfaces/Persistent"; import Syncable from "./interfaces/Syncable"; import Auth from "./stores/Auth"; @@ -24,6 +23,7 @@ import Sync, { Data as DataSync, SyncKeys } from "./stores/Sync"; export const MIGRATIONS = { REDUX: 1640305719826, + MULTI_SERVER_CONFIG: 1656350006152, }; /** @@ -253,6 +253,9 @@ export default class State { // Post-hydration, init plugins. this.plugins.init(); + + // Push authentication information forwards to client controller. + clientController.hydrate(this.auth); } /** diff --git a/src/mobx/stores/Auth.ts b/src/mobx/stores/Auth.ts index 71cea92c..1d63e7be 100644 --- a/src/mobx/stores/Auth.ts +++ b/src/mobx/stores/Auth.ts @@ -1,6 +1,4 @@ import { action, computed, makeAutoObservable, ObservableMap } from "mobx"; -import { API } from "revolt.js"; -import { Nullable } from "revolt.js"; import { mapToRecord } from "../../lib/conversion"; @@ -13,7 +11,6 @@ interface Account { export interface Data { sessions: Record; - current?: string; } /** @@ -22,14 +19,12 @@ export interface Data { */ export default class Auth implements Store, Persistent { private sessions: ObservableMap; - private current: Nullable; /** * Construct new Auth store. */ constructor() { this.sessions = new ObservableMap(); - this.current = null; // Inject session token if it is provided. if (import.meta.env.VITE_SESSION_TOKEN) { @@ -40,8 +35,6 @@ export default class Auth implements Store, Persistent { token: import.meta.env.VITE_SESSION_TOKEN as string, }, }); - - this.current = "0"; } makeAutoObservable(this); @@ -54,7 +47,6 @@ export default class Auth implements Store, Persistent { @action toJSON() { return { sessions: JSON.parse(JSON.stringify(mapToRecord(this.sessions))), - current: this.current ?? undefined, }; } @@ -72,10 +64,6 @@ export default class Auth implements Store, Persistent { this.sessions.set(id, v[id]), ); } - - if (data.current && this.sessions.has(data.current)) { - this.current = data.current; - } } /** @@ -84,7 +72,6 @@ export default class Auth implements Store, Persistent { */ @action setSession(session: Session) { this.sessions.set(session.user_id, { session }); - this.current = session.user_id; } /** @@ -92,34 +79,38 @@ export default class Auth implements Store, Persistent { * @param user_id User ID tied to session */ @action removeSession(user_id: string) { - if (user_id == this.current) { - this.current = null; - } - this.sessions.delete(user_id); } + /** + * Get all known accounts. + * @returns Array of accounts + */ + @computed getAccounts() { + return [...this.sessions.values()]; + } + /** * Remove current session. */ - @action logout() { + /*@action logout() { this.current && this.removeSession(this.current); - } + }*/ /** * Get current session. * @returns Current session */ - @computed getSession() { + /*@computed getSession() { if (!this.current) return; return this.sessions.get(this.current)!.session; - } + }*/ /** * Check whether we are currently logged in. * @returns Whether we are logged in */ - @computed isLoggedIn() { + /*@computed isLoggedIn() { return this.current !== null; - } + }*/ } diff --git a/src/mobx/stores/ServerConfig.ts b/src/mobx/stores/ServerConfig.ts index 4212e82d..17ee1230 100644 --- a/src/mobx/stores/ServerConfig.ts +++ b/src/mobx/stores/ServerConfig.ts @@ -1,7 +1,5 @@ import { action, computed, makeAutoObservable } from "mobx"; -import { API } from "revolt.js"; -import { Client } from "revolt.js"; -import { Nullable } from "revolt.js"; +import { API, Client, Nullable } from "revolt.js"; import { isDebug } from "../../revision"; import Persistent from "../interfaces/Persistent"; diff --git a/src/types/revolt-api.d.ts b/src/types/revolt-api.d.ts index ae1bb349..577b1b52 100644 --- a/src/types/revolt-api.d.ts +++ b/src/types/revolt-api.d.ts @@ -5,3 +5,5 @@ declare type Session = { name: string; user_id: string; }; + +declare type SessionPrivate = Session; From c997286340ea19219c5e3d9bd76b04bbe1395104 Mon Sep 17 00:00:00 2001 From: Paul Makles Date: Tue, 28 Jun 2022 13:22:10 +0100 Subject: [PATCH 098/151] patch: temporarily fix issue with public invites --- src/components/common/user/UserIcon.tsx | 3 ++- src/pages/invite/Invite.tsx | 1 - 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/components/common/user/UserIcon.tsx b/src/components/common/user/UserIcon.tsx index c1eade4a..3c19ecdb 100644 --- a/src/components/common/user/UserIcon.tsx +++ b/src/components/common/user/UserIcon.tsx @@ -56,7 +56,8 @@ export default observer( keyof Props | "children" | "as" >, ) => { - const client = useApplicationState().client!; + // ! TODO: this is temporary code + const client = useClient() ?? useApplicationState().client!; const { target, diff --git a/src/pages/invite/Invite.tsx b/src/pages/invite/Invite.tsx index b11f0d96..c92ae072 100644 --- a/src/pages/invite/Invite.tsx +++ b/src/pages/invite/Invite.tsx @@ -1,5 +1,4 @@ import { ArrowBack } from "@styled-icons/boxicons-regular"; -import { autorun } from "mobx"; import { Redirect, useHistory, useParams } from "react-router-dom"; import { API } from "revolt.js"; From b53d3bce133d52b114739dd705727ef5a69c36ee Mon Sep 17 00:00:00 2001 From: Paul Makles Date: Tue, 28 Jun 2022 13:23:45 +0100 Subject: [PATCH 099/151] fix: don't display date on ("new") message divider --- src/pages/channels/messaging/MessageRenderer.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pages/channels/messaging/MessageRenderer.tsx b/src/pages/channels/messaging/MessageRenderer.tsx index fd7aa7b9..b97fac8c 100644 --- a/src/pages/channels/messaging/MessageRenderer.tsx +++ b/src/pages/channels/messaging/MessageRenderer.tsx @@ -126,7 +126,7 @@ export default observer(({ last_id, renderer, highlight }: Props) => { if (unread || date) { render.push( , ); From ce88fab71455f2b1a36c1013690386e54e51d235 Mon Sep 17 00:00:00 2001 From: Paul Makles Date: Tue, 28 Jun 2022 13:49:50 +0100 Subject: [PATCH 100/151] feat: get fsm to a working testing state --- external/lang | 2 +- src/context/index.tsx | 2 +- src/context/revoltjs/RevoltClient.tsx | 21 +++++++++++++------ src/controllers/client/ClientController.tsx | 23 +++++++++++++++++---- src/controllers/client/Session.tsx | 23 ++++++++++++++++++++- src/mobx/stores/Auth.ts | 8 ++++--- 6 files changed, 63 insertions(+), 16 deletions(-) diff --git a/external/lang b/external/lang index def08f21..50838167 160000 --- a/external/lang +++ b/external/lang @@ -1 +1 @@ -Subproject commit def08f210e9edc4f203cb38611fd270761102860 +Subproject commit 50838167d7d253de9d08715e6a6070c3ddc9fcc2 diff --git a/src/context/index.tsx b/src/context/index.tsx index 1785174b..5b30422c 100644 --- a/src/context/index.tsx +++ b/src/context/index.tsx @@ -20,7 +20,7 @@ const uiContext = { Link, Text: Text as any, Trigger: ContextMenuTrigger, - emitAction: () => {}, + emitAction: () => void {}, }; /** diff --git a/src/context/revoltjs/RevoltClient.tsx b/src/context/revoltjs/RevoltClient.tsx index f16f3ac1..b76f4585 100644 --- a/src/context/revoltjs/RevoltClient.tsx +++ b/src/context/revoltjs/RevoltClient.tsx @@ -9,6 +9,7 @@ import { Preloader } from "@revoltchat/ui"; import { useApplicationState } from "../../mobx/State"; +import { clientController } from "../../controllers/client/ClientController"; import { modalController } from "../../controllers/modals/ModalController"; import { registerEvents } from "./events"; import { takeError } from "./util"; @@ -36,8 +37,8 @@ type Props = { }; export default observer(({ children }: Props) => { - const state = useApplicationState(); - const [client, setClient] = useState(null!); + // const state = useApplicationState(); + /*const [client, setClient] = useState(null!); const [status, setStatus] = useState(ClientStatus.LOADING); const [loaded, setLoaded] = useState(false); @@ -84,16 +85,24 @@ export default observer(({ children }: Props) => { }, [state.auth.getSession()]); useEffect(() => registerEvents(state, setStatus, client), [client]); - useEffect(() => state.registerListeners(client), [client]); if (!loaded || status === ClientStatus.LOADING) { return ; + }*/ + + const session = clientController.getActiveSession(); + if (!session?.ready) { + return ; } + const client = session.client!; + const state = useApplicationState(); + useEffect(() => state.registerListeners(client), [state, client]); + return ( - - - + + + void {}}> {children} diff --git a/src/controllers/client/ClientController.tsx b/src/controllers/client/ClientController.tsx index 25fc536c..56200d97 100644 --- a/src/controllers/client/ClientController.tsx +++ b/src/controllers/client/ClientController.tsx @@ -1,4 +1,4 @@ -import { action, makeAutoObservable, ObservableMap } from "mobx"; +import { action, computed, makeAutoObservable, ObservableMap } from "mobx"; import type { Nullable } from "revolt.js"; import Auth from "../../mobx/stores/Auth"; @@ -30,20 +30,35 @@ class ClientController { @action hydrate(auth: Auth) { for (const entry of auth.getAccounts()) { const session = new Session(); + this.sessions.set(entry.session._id!, session); session.emit({ action: "LOGIN", session: entry.session, }); } + + this.current = this.sessions.keys().next().value ?? null; } - getActiveSession() { - return this.sessions; + @computed getActiveSession() { + return this.sessions.get(this.current!); } - isLoggedIn() { + @computed isLoggedIn() { return this.current === null; } + + @action logout(user_id: string) { + const session = this.sessions.get(user_id); + if (session) { + this.sessions.delete(user_id); + if (user_id === this.current) { + this.current = this.sessions.keys().next().value ?? null; + } + + session.destroy(); + } + } } export const clientController = new ClientController(); diff --git a/src/controllers/client/Session.tsx b/src/controllers/client/Session.tsx index ec9c7e45..1189e059 100644 --- a/src/controllers/client/Session.tsx +++ b/src/controllers/client/Session.tsx @@ -1,4 +1,4 @@ -import { action, makeAutoObservable } from "mobx"; +import { action, computed, makeAutoObservable } from "mobx"; import { Client } from "revolt.js"; type State = "Ready" | "Connecting" | "Online" | "Disconnected" | "Offline"; @@ -34,6 +34,17 @@ export default class Session { window.addEventListener("offline", this.onOffline); } + /** + * Initiate logout and destroy client. + */ + @action destroy() { + if (this.client) { + this.client.logout(false); + this.state = "Ready"; + this.client = null; + } + } + private onOnline() { this.emit({ action: "ONLINE", @@ -90,6 +101,8 @@ export default class Session { } @action async emit(data: Transition) { + console.info("Handle event:", data); + switch (data.action) { // Login with session case "LOGIN": { @@ -161,4 +174,12 @@ export default class Session { } } } + + /** + * Whether we are ready to render. + * @returns Boolean + */ + @computed get ready() { + return this.client?.user; + } } diff --git a/src/mobx/stores/Auth.ts b/src/mobx/stores/Auth.ts index 1d63e7be..33799f7e 100644 --- a/src/mobx/stores/Auth.ts +++ b/src/mobx/stores/Auth.ts @@ -2,6 +2,7 @@ import { action, computed, makeAutoObservable, ObservableMap } from "mobx"; import { mapToRecord } from "../../lib/conversion"; +import { clientController } from "../../controllers/client/ClientController"; import Persistent from "../interfaces/Persistent"; import Store from "../interfaces/Store"; @@ -110,7 +111,8 @@ export default class Auth implements Store, Persistent { * Check whether we are currently logged in. * @returns Whether we are logged in */ - /*@computed isLoggedIn() { - return this.current !== null; - }*/ + @computed isLoggedIn() { + // ! FIXME: temp proxy info + return clientController.getActiveSession()?.ready; + } } From 5f2311b09cd284b9cb94e300d2039dbd04a2797a Mon Sep 17 00:00:00 2001 From: Paul Makles Date: Tue, 28 Jun 2022 19:59:58 +0100 Subject: [PATCH 101/151] feat: implement `useClient` from client controller --- package.json | 1 + src/components/common/AutoComplete.tsx | 3 +- src/components/common/ChannelIcon.tsx | 7 +- src/components/common/ServerIcon.tsx | 5 +- src/components/common/messaging/Message.tsx | 4 +- .../common/messaging/MessageBox.tsx | 4 +- .../messaging/attachments/Attachment.tsx | 7 +- .../attachments/AttachmentActions.tsx | 4 +- .../messaging/attachments/ImageFile.tsx | 5 +- .../common/messaging/attachments/TextFile.tsx | 13 +-- .../messaging/bars/MessageOverlayBar.tsx | 5 +- .../common/messaging/embed/Embed.tsx | 2 +- .../common/messaging/embed/EmbedInvite.tsx | 18 ++-- .../common/messaging/embed/EmbedMedia.tsx | 3 +- src/components/common/user/UserIcon.tsx | 5 +- src/components/common/user/UserShort.tsx | 2 +- src/components/markdown/Renderer.tsx | 4 +- .../navigation/BottomNavigation.tsx | 3 +- .../navigation/items/ConnectionStatus.tsx | 36 +++---- .../navigation/left/HomeSidebar.tsx | 4 +- .../navigation/left/ServerListSidebar.tsx | 3 +- .../navigation/left/ServerSidebar.tsx | 3 +- .../navigation/right/MemberSidebar.tsx | 16 ++- src/components/navigation/right/Search.tsx | 3 +- .../settings/account/AccountManagement.tsx | 9 +- .../settings/account/EditAccount.tsx | 17 ++- .../account/MultiFactorAuthentication.tsx | 18 ++-- src/context/intermediate/modals/Input.tsx | 4 +- src/context/intermediate/modals/Prompt.tsx | 6 +- .../intermediate/popovers/CreateBot.tsx | 6 +- .../intermediate/popovers/ImageViewer.tsx | 2 +- .../intermediate/popovers/UserPicker.tsx | 2 +- .../intermediate/popovers/UserProfile.tsx | 28 ++--- src/context/revoltjs/CheckAuth.tsx | 12 +-- src/context/revoltjs/FileUploads.tsx | 8 +- src/context/revoltjs/Notifications.tsx | 6 +- src/context/revoltjs/RequiresOnline.tsx | 11 +- src/context/revoltjs/RevoltClient.tsx | 102 ++---------------- src/context/revoltjs/StateMonitor.tsx | 39 ------- src/context/revoltjs/SyncManager.tsx | 2 +- src/context/revoltjs/events.ts | 15 +-- src/controllers/client/ClientController.tsx | 81 ++++++++++++-- src/controllers/client/Session.tsx | 5 +- .../modals/components/ModifyAccount.tsx | 8 +- src/lib/ContextMenus.tsx | 13 +-- src/lib/FakeClient.tsx | 13 --- src/mobx/State.ts | 8 ++ src/mobx/stores/MessageQueue.ts | 15 +++ src/mobx/stores/Settings.ts | 8 +- src/pages/Open.tsx | 15 +-- src/pages/RevoltApp.tsx | 2 - src/pages/app.tsx | 5 +- src/pages/channels/Channel.tsx | 3 +- src/pages/channels/messaging/MessageArea.tsx | 19 ++-- .../channels/messaging/MessageRenderer.tsx | 6 +- src/pages/channels/voice/VoiceHeader.tsx | 2 +- src/pages/developer/Developer.tsx | 7 +- src/pages/friends/Friends.tsx | 2 +- src/pages/home/Home.tsx | 6 +- src/pages/invite/Invite.tsx | 22 ++-- src/pages/invite/InviteBot.tsx | 3 +- src/pages/login/ConfirmDelete.tsx | 9 +- src/pages/login/forms/FormReset.tsx | 17 +-- src/pages/login/forms/FormVerify.tsx | 14 +-- src/pages/settings/ChannelSettings.tsx | 2 +- src/pages/settings/ServerSettings.tsx | 2 +- src/pages/settings/Settings.tsx | 11 +- src/pages/settings/panes/MyBots.tsx | 5 +- src/pages/settings/panes/Notifications.tsx | 7 +- src/pages/settings/panes/Profile.tsx | 14 +-- src/pages/settings/panes/Sessions.tsx | 6 +- yarn.lock | 10 ++ 72 files changed, 330 insertions(+), 457 deletions(-) delete mode 100644 src/context/revoltjs/StateMonitor.tsx delete mode 100644 src/lib/FakeClient.tsx diff --git a/package.json b/package.json index 7e7091a4..bfbf31e5 100644 --- a/package.json +++ b/package.json @@ -106,6 +106,7 @@ "eslint": "^7.28.0", "eslint-config-preact": "^1.1.4", "eslint-plugin-jsdoc": "^39.3.2", + "eslint-plugin-mobx": "^0.0.8", "eventemitter3": "^4.0.7", "history": "4", "json-stringify-deterministic": "^1.0.2", diff --git a/src/components/common/AutoComplete.tsx b/src/components/common/AutoComplete.tsx index 2b7d5196..72c0d885 100644 --- a/src/components/common/AutoComplete.tsx +++ b/src/components/common/AutoComplete.tsx @@ -3,9 +3,8 @@ import styled, { css } from "styled-components/macro"; import { StateUpdater, useState } from "preact/hooks"; -import { useClient } from "../../context/revoltjs/RevoltClient"; - import { emojiDictionary } from "../../assets/emojis"; +import { useClient } from "../../controllers/client/ClientController"; import ChannelIcon from "./ChannelIcon"; import Emoji from "./Emoji"; import UserIcon from "./user/UserIcon"; diff --git a/src/components/common/ChannelIcon.tsx b/src/components/common/ChannelIcon.tsx index 92fafcaa..d11d6633 100644 --- a/src/components/common/ChannelIcon.tsx +++ b/src/components/common/ChannelIcon.tsx @@ -2,12 +2,9 @@ import { Hash, VolumeFull } from "@styled-icons/boxicons-regular"; import { observer } from "mobx-react-lite"; import { Channel } from "revolt.js"; -import { useContext } from "preact/hooks"; - -import { AppContext } from "../../context/revoltjs/RevoltClient"; - import fallback from "./assets/group.png"; +import { useClient } from "../../controllers/client/ClientController"; import { ImageIconBase, IconBaseProps } from "./IconBase"; interface Props extends IconBaseProps { @@ -22,7 +19,7 @@ export default observer( keyof Props | "children" | "as" >, ) => { - const client = useContext(AppContext); + const client = useClient(); const { size, diff --git a/src/components/common/ServerIcon.tsx b/src/components/common/ServerIcon.tsx index 282e9d73..a9fc1913 100644 --- a/src/components/common/ServerIcon.tsx +++ b/src/components/common/ServerIcon.tsx @@ -4,8 +4,7 @@ import styled from "styled-components/macro"; import { useContext } from "preact/hooks"; -import { AppContext } from "../../context/revoltjs/RevoltClient"; - +import { useClient } from "../../controllers/client/ClientController"; import { IconBaseProps, ImageIconBase } from "./IconBase"; interface Props extends IconBaseProps { @@ -34,7 +33,7 @@ export default observer( keyof Props | "children" | "as" >, ) => { - const client = useContext(AppContext); + const client = useClient(); const { target, attachment, size, animate, server_name, ...imgProps } = props; diff --git a/src/components/common/messaging/Message.tsx b/src/components/common/messaging/Message.tsx index a29238a0..3e15f001 100644 --- a/src/components/common/messaging/Message.tsx +++ b/src/components/common/messaging/Message.tsx @@ -14,8 +14,8 @@ import { QueuedMessage } from "../../../mobx/stores/MessageQueue"; import { I18nError } from "../../../context/Locale"; import { useIntermediate } from "../../../context/intermediate/Intermediate"; -import { useClient } from "../../../context/revoltjs/RevoltClient"; +import { useClient } from "../../../controllers/client/ClientController"; import Markdown from "../../markdown/Markdown"; import UserIcon from "../user/UserIcon"; import { Username } from "../user/UserShort"; @@ -52,7 +52,7 @@ const Message = observer( queued, hideReply, }: Props) => { - const client = useClient(); + const client = message.client; const user = message.author; const { openScreen } = useIntermediate(); diff --git a/src/components/common/messaging/MessageBox.tsx b/src/components/common/messaging/MessageBox.tsx index 4bb4dd49..86c6cf58 100644 --- a/src/components/common/messaging/MessageBox.tsx +++ b/src/components/common/messaging/MessageBox.tsx @@ -29,9 +29,9 @@ import { grabFiles, uploadFile, } from "../../../context/revoltjs/FileUploads"; -import { AppContext } from "../../../context/revoltjs/RevoltClient"; import { takeError } from "../../../context/revoltjs/util"; +import { useClient } from "../../../controllers/client/ClientController"; import { modalController } from "../../../controllers/modals/ModalController"; import AutoComplete, { useAutoComplete } from "../AutoComplete"; import { PermissionTooltip } from "../Tooltip"; @@ -148,7 +148,7 @@ export default observer(({ channel }: Props) => { }); const [typing, setTyping] = useState(false); const [replies, setReplies] = useState([]); - const client = useContext(AppContext); + const client = useClient(); const translate = useTranslation(); const renderer = getRenderer(channel); diff --git a/src/components/common/messaging/attachments/Attachment.tsx b/src/components/common/messaging/attachments/Attachment.tsx index 7e9400dc..de112e4d 100644 --- a/src/components/common/messaging/attachments/Attachment.tsx +++ b/src/components/common/messaging/attachments/Attachment.tsx @@ -3,10 +3,9 @@ import { API } from "revolt.js"; import styles from "./Attachment.module.scss"; import classNames from "classnames"; import { useTriggerEvents } from "preact-context-menu"; -import { useContext, useState } from "preact/hooks"; - -import { AppContext } from "../../../../context/revoltjs/RevoltClient"; +import { useState } from "preact/hooks"; +import { useClient } from "../../../../controllers/client/ClientController"; import AttachmentActions from "./AttachmentActions"; import { SizedGrid } from "./Grid"; import ImageFile from "./ImageFile"; @@ -21,7 +20,7 @@ interface Props { const MAX_ATTACHMENT_WIDTH = 480; export default function Attachment({ attachment, hasContent }: Props) { - const client = useContext(AppContext); + const client = useClient(); const { filename, metadata } = attachment; const [spoiler, setSpoiler] = useState(filename.startsWith("SPOILER_")); diff --git a/src/components/common/messaging/attachments/AttachmentActions.tsx b/src/components/common/messaging/attachments/AttachmentActions.tsx index f2d1781d..b6e5699d 100644 --- a/src/components/common/messaging/attachments/AttachmentActions.tsx +++ b/src/components/common/messaging/attachments/AttachmentActions.tsx @@ -15,14 +15,14 @@ import { IconButton } from "@revoltchat/ui"; import { determineFileSize } from "../../../../lib/fileSize"; -import { AppContext } from "../../../../context/revoltjs/RevoltClient"; +import { useClient } from "../../../../controllers/client/ClientController"; interface Props { attachment: API.File; } export default function AttachmentActions({ attachment }: Props) { - const client = useContext(AppContext); + const client = useClient(); const { filename, metadata, size } = attachment; const url = client.generateFileURL(attachment); diff --git a/src/components/common/messaging/attachments/ImageFile.tsx b/src/components/common/messaging/attachments/ImageFile.tsx index 252387b2..76723e20 100644 --- a/src/components/common/messaging/attachments/ImageFile.tsx +++ b/src/components/common/messaging/attachments/ImageFile.tsx @@ -5,7 +5,8 @@ import classNames from "classnames"; import { useContext, useState } from "preact/hooks"; import { useIntermediate } from "../../../../context/intermediate/Intermediate"; -import { AppContext } from "../../../../context/revoltjs/RevoltClient"; + +import { useClient } from "../../../../controllers/client/ClientController"; enum ImageLoadingState { Loading, @@ -19,7 +20,7 @@ type Props = JSX.HTMLAttributes & { export default function ImageFile({ attachment, ...props }: Props) { const [loading, setLoading] = useState(ImageLoadingState.Loading); - const client = useContext(AppContext); + const client = useClient(); const { openScreen } = useIntermediate(); const url = client.generateFileURL(attachment)!; diff --git a/src/components/common/messaging/attachments/TextFile.tsx b/src/components/common/messaging/attachments/TextFile.tsx index b119bcae..1dd2940b 100644 --- a/src/components/common/messaging/attachments/TextFile.tsx +++ b/src/components/common/messaging/attachments/TextFile.tsx @@ -3,15 +3,13 @@ import { API } from "revolt.js"; import styles from "./Attachment.module.scss"; import { Text } from "preact-i18n"; -import { useContext, useEffect, useState } from "preact/hooks"; +import { useEffect, useState } from "preact/hooks"; import { Button, Preloader } from "@revoltchat/ui"; import RequiresOnline from "../../../../context/revoltjs/RequiresOnline"; -import { - AppContext, - StatusContext, -} from "../../../../context/revoltjs/RevoltClient"; + +import { useClient } from "../../../../controllers/client/ClientController"; interface Props { attachment: API.File; @@ -23,9 +21,8 @@ export default function TextFile({ attachment }: Props) { const [gated, setGated] = useState(attachment.size > 100_000); const [content, setContent] = useState(undefined); const [loading, setLoading] = useState(false); - const status = useContext(StatusContext); - const client = useContext(AppContext); + const client = useClient(); const url = client.generateFileURL(attachment)!; useEffect(() => { @@ -56,7 +53,7 @@ export default function TextFile({ attachment }: Props) { setLoading(false); }); } - }, [content, loading, gated, status, attachment._id, attachment.size, url]); + }, [content, loading, gated, attachment._id, attachment.size, url]); return (
{ - const client = useClient(); + const client = message.client; const { openScreen, writeClipboard } = useIntermediate(); const isAuthor = message.author_id === client.user!._id; diff --git a/src/components/common/messaging/embed/Embed.tsx b/src/components/common/messaging/embed/Embed.tsx index 10201989..306f6441 100644 --- a/src/components/common/messaging/embed/Embed.tsx +++ b/src/components/common/messaging/embed/Embed.tsx @@ -5,8 +5,8 @@ import classNames from "classnames"; import { useContext } from "preact/hooks"; import { useIntermediate } from "../../../../context/intermediate/Intermediate"; -import { useClient } from "../../../../context/revoltjs/RevoltClient"; +import { useClient } from "../../../../controllers/client/ClientController"; import { MessageAreaWidthContext } from "../../../../pages/channels/messaging/MessageArea"; import Markdown from "../../../markdown/Markdown"; import Attachment from "../attachments/Attachment"; diff --git a/src/components/common/messaging/embed/EmbedInvite.tsx b/src/components/common/messaging/embed/EmbedInvite.tsx index 974f8d71..63700c8c 100644 --- a/src/components/common/messaging/embed/EmbedInvite.tsx +++ b/src/components/common/messaging/embed/EmbedInvite.tsx @@ -1,5 +1,4 @@ import { Group } from "@styled-icons/boxicons-solid"; -import { reaction } from "mobx"; import { observer } from "mobx-react-lite"; import { useHistory } from "react-router-dom"; import { Message, API } from "revolt.js"; @@ -12,14 +11,13 @@ import { Button, Category, Preloader } from "@revoltchat/ui"; import { isTouchscreenDevice } from "../../../../lib/isTouchscreenDevice"; import { I18nError } from "../../../../context/Locale"; -import { - AppContext, - ClientStatus, - StatusContext, -} from "../../../../context/revoltjs/RevoltClient"; import { takeError } from "../../../../context/revoltjs/util"; import ServerIcon from "../../../../components/common/ServerIcon"; +import { + useClient, + useSession, +} from "../../../../controllers/client/ClientController"; const EmbedInviteBase = styled.div` width: 400px; @@ -78,8 +76,8 @@ type Props = { export function EmbedInvite({ code }: Props) { const history = useHistory(); - const client = useContext(AppContext); - const status = useContext(StatusContext); + const session = useSession()!; + const client = session.client!; const [processing, setProcessing] = useState(false); const [error, setError] = useState(undefined); const [joinError, setJoinError] = useState(undefined); @@ -90,7 +88,7 @@ export function EmbedInvite({ code }: Props) { useEffect(() => { if ( typeof invite === "undefined" && - (status === ClientStatus.ONLINE || status === ClientStatus.READY) + (session.state === "Online" || session.state === "Ready") ) { client .fetchInvite(code) @@ -99,7 +97,7 @@ export function EmbedInvite({ code }: Props) { ) .catch((err) => setError(takeError(err))); } - }, [client, code, invite, status]); + }, [client, code, invite, session.state]); if (typeof invite === "undefined") { return error ? ( diff --git a/src/components/common/messaging/embed/EmbedMedia.tsx b/src/components/common/messaging/embed/EmbedMedia.tsx index f602318b..a719f0e3 100644 --- a/src/components/common/messaging/embed/EmbedMedia.tsx +++ b/src/components/common/messaging/embed/EmbedMedia.tsx @@ -4,7 +4,8 @@ import { API } from "revolt.js"; import styles from "./Embed.module.scss"; import { useIntermediate } from "../../../../context/intermediate/Intermediate"; -import { useClient } from "../../../../context/revoltjs/RevoltClient"; + +import { useClient } from "../../../../controllers/client/ClientController"; interface Props { embed: API.Embed; diff --git a/src/components/common/user/UserIcon.tsx b/src/components/common/user/UserIcon.tsx index c1eade4a..3c96b9a6 100644 --- a/src/components/common/user/UserIcon.tsx +++ b/src/components/common/user/UserIcon.tsx @@ -6,10 +6,9 @@ import styled, { css } from "styled-components/macro"; import { useApplicationState } from "../../../mobx/State"; -import { useClient } from "../../../context/revoltjs/RevoltClient"; - import fallback from "../assets/user.png"; +import { useClient } from "../../../controllers/client/ClientController"; import IconBase, { IconBaseProps } from "../IconBase"; type VoiceStatus = "muted" | "deaf"; @@ -56,7 +55,7 @@ export default observer( keyof Props | "children" | "as" >, ) => { - const client = useApplicationState().client!; + const client = useClient(); const { target, diff --git a/src/components/common/user/UserShort.tsx b/src/components/common/user/UserShort.tsx index 881d763f..5ac03d19 100644 --- a/src/components/common/user/UserShort.tsx +++ b/src/components/common/user/UserShort.tsx @@ -9,8 +9,8 @@ import { Text } from "preact-i18n"; import { internalEmit } from "../../../lib/eventEmitter"; import { useIntermediate } from "../../../context/intermediate/Intermediate"; -import { useClient } from "../../../context/revoltjs/RevoltClient"; +import { useClient } from "../../../controllers/client/ClientController"; import UserIcon from "./UserIcon"; const BotBadge = styled.div` diff --git a/src/components/markdown/Renderer.tsx b/src/components/markdown/Renderer.tsx index 47787a8e..bcce42b8 100644 --- a/src/components/markdown/Renderer.tsx +++ b/src/components/markdown/Renderer.tsx @@ -15,9 +15,9 @@ import { determineLink } from "../../lib/links"; import { dayjs } from "../../context/Locale"; import { useIntermediate } from "../../context/intermediate/Intermediate"; -import { AppContext } from "../../context/revoltjs/RevoltClient"; import { emojiDictionary } from "../../assets/emojis"; +import { useClient } from "../../controllers/client/ClientController"; import { generateEmoji } from "../common/Emoji"; import { MarkdownProps } from "./Markdown"; import Prism from "./prism"; @@ -118,7 +118,7 @@ const RE_CHANNELS = /<#([A-z0-9]{26})>/g; const RE_TIME = //g; export default function Renderer({ content, disallowBigEmoji }: MarkdownProps) { - const client = useContext(AppContext); + const client = useClient(); const { openLink } = useIntermediate(); if (typeof content === "undefined") return null; diff --git a/src/components/navigation/BottomNavigation.tsx b/src/components/navigation/BottomNavigation.tsx index 2daf28a7..1a1b44ad 100644 --- a/src/components/navigation/BottomNavigation.tsx +++ b/src/components/navigation/BottomNavigation.tsx @@ -9,8 +9,7 @@ import ConditionalLink from "../../lib/ConditionalLink"; import { useApplicationState } from "../../mobx/State"; -import { useClient } from "../../context/revoltjs/RevoltClient"; - +import { useClient } from "../../controllers/client/ClientController"; import UserIcon from "../common/user/UserIcon"; const Base = styled.div` diff --git a/src/components/navigation/items/ConnectionStatus.tsx b/src/components/navigation/items/ConnectionStatus.tsx index 899b3d8e..517c5b03 100644 --- a/src/components/navigation/items/ConnectionStatus.tsx +++ b/src/components/navigation/items/ConnectionStatus.tsx @@ -1,45 +1,43 @@ +import { observer } from "mobx-react-lite"; + import { Text } from "preact-i18n"; -import { useContext } from "preact/hooks"; import { Banner } from "@revoltchat/ui"; -import { - ClientStatus, - StatusContext, - useClient, -} from "../../../context/revoltjs/RevoltClient"; +import { useSession } from "../../../controllers/client/ClientController"; -export default function ConnectionStatus() { - const status = useContext(StatusContext); - const client = useClient(); +function ConnectionStatus() { + const session = useSession()!; - if (status === ClientStatus.OFFLINE) { + if (session.state === "Offline") { return ( ); - } else if (status === ClientStatus.DISCONNECTED) { + } else if (session.state === "Disconnected") { return (
- client.websocket.connect()}> + + session.emit({ + action: "RETRY", + }) + }>
); - } else if (status === ClientStatus.CONNECTING) { - return ( - - - - ); - } else if (status === ClientStatus.RECONNECTING) { + } else if (session.state === "Connecting") { return ( ); } + return null; } + +export default observer(ConnectionStatus); diff --git a/src/components/navigation/left/HomeSidebar.tsx b/src/components/navigation/left/HomeSidebar.tsx index 220f542c..9dcfb8e9 100644 --- a/src/components/navigation/left/HomeSidebar.tsx +++ b/src/components/navigation/left/HomeSidebar.tsx @@ -21,10 +21,10 @@ import { isTouchscreenDevice } from "../../../lib/isTouchscreenDevice"; import { useApplicationState } from "../../../mobx/State"; import { useIntermediate } from "../../../context/intermediate/Intermediate"; -import { AppContext } from "../../../context/revoltjs/RevoltClient"; import placeholderSVG from "../items/placeholder.svg"; +import { useClient } from "../../../controllers/client/ClientController"; import { GenericSidebarBase, GenericSidebarList } from "../SidebarBase"; import ButtonItem, { ChannelButton } from "../items/ButtonItem"; import ConnectionStatus from "../items/ConnectionStatus"; @@ -46,7 +46,7 @@ const Navbar = styled.div` export default observer(() => { const { pathname } = useLocation(); - const client = useContext(AppContext); + const client = useClient(); const state = useApplicationState(); const { channel: channel_id } = useParams<{ channel: string }>(); const { openScreen } = useIntermediate(); diff --git a/src/components/navigation/left/ServerListSidebar.tsx b/src/components/navigation/left/ServerListSidebar.tsx index 23d2b869..ed703a31 100644 --- a/src/components/navigation/left/ServerListSidebar.tsx +++ b/src/components/navigation/left/ServerListSidebar.tsx @@ -8,7 +8,8 @@ import { ServerList } from "@revoltchat/ui"; import { useApplicationState } from "../../../mobx/State"; import { useIntermediate } from "../../../context/intermediate/Intermediate"; -import { useClient } from "../../../context/revoltjs/RevoltClient"; + +import { useClient } from "../../../controllers/client/ClientController"; /** * Server list sidebar shim component diff --git a/src/components/navigation/left/ServerSidebar.tsx b/src/components/navigation/left/ServerSidebar.tsx index 4a771701..b2b602ff 100644 --- a/src/components/navigation/left/ServerSidebar.tsx +++ b/src/components/navigation/left/ServerSidebar.tsx @@ -14,8 +14,7 @@ import { isTouchscreenDevice } from "../../../lib/isTouchscreenDevice"; import { useApplicationState } from "../../../mobx/State"; -import { useClient } from "../../../context/revoltjs/RevoltClient"; - +import { useClient } from "../../../controllers/client/ClientController"; import CollapsibleSection from "../../common/CollapsibleSection"; import ServerHeader from "../../common/ServerHeader"; import { ChannelButton } from "../items/ButtonItem"; diff --git a/src/components/navigation/right/MemberSidebar.tsx b/src/components/navigation/right/MemberSidebar.tsx index 37333b45..3dbf1f63 100644 --- a/src/components/navigation/right/MemberSidebar.tsx +++ b/src/components/navigation/right/MemberSidebar.tsx @@ -4,14 +4,12 @@ import { observer } from "mobx-react-lite"; import { useParams } from "react-router-dom"; import { Channel, Server, User, API } from "revolt.js"; -import { useContext, useEffect, useState } from "preact/hooks"; +import { useEffect, useState } from "preact/hooks"; import { - ClientStatus, - StatusContext, + useSession, useClient, -} from "../../../context/revoltjs/RevoltClient"; - +} from "../../../controllers/client/ClientController"; import { GenericSidebarBase } from "../SidebarBase"; import MemberList, { MemberListGroup } from "./MemberList"; @@ -205,18 +203,18 @@ function shouldSkipOffline(id: string) { export const ServerMemberSidebar = observer( ({ channel }: { channel: Channel }) => { - const client = useClient(); - const status = useContext(StatusContext); + const session = useSession()!; + const client = session.client!; useEffect(() => { const server_id = channel.server_id!; - if (status === ClientStatus.ONLINE && !FETCHED.has(server_id)) { + if (session.state === "Online" && !FETCHED.has(server_id)) { FETCHED.add(server_id); channel .server!.syncMembers(shouldSkipOffline(server_id)) .catch(() => FETCHED.delete(server_id)); } - }, [status, channel]); + }, [session.state, channel]); const entries = useEntries( channel, diff --git a/src/components/navigation/right/Search.tsx b/src/components/navigation/right/Search.tsx index 13fb3d88..f12cb7d2 100644 --- a/src/components/navigation/right/Search.tsx +++ b/src/components/navigation/right/Search.tsx @@ -7,8 +7,7 @@ import { useEffect, useState } from "preact/hooks"; import { Button, Category, Error, InputBox, Preloader } from "@revoltchat/ui"; -import { useClient } from "../../../context/revoltjs/RevoltClient"; - +import { useClient } from "../../../controllers/client/ClientController"; import Message from "../../common/messaging/Message"; import { GenericSidebarBase, GenericSidebarList } from "../SidebarBase"; diff --git a/src/components/settings/account/AccountManagement.tsx b/src/components/settings/account/AccountManagement.tsx index f7ac5843..6c9c5310 100644 --- a/src/components/settings/account/AccountManagement.tsx +++ b/src/components/settings/account/AccountManagement.tsx @@ -2,19 +2,16 @@ import { Block } from "@styled-icons/boxicons-regular"; import { Trash } from "@styled-icons/boxicons-solid"; import { Text } from "preact-i18n"; -import { useContext } from "preact/hooks"; import { CategoryButton } from "@revoltchat/ui"; import { - LogOutContext, + clientController, useClient, -} from "../../../context/revoltjs/RevoltClient"; - +} from "../../../controllers/client/ClientController"; import { modalController } from "../../../controllers/modals/ModalController"; export default function AccountManagement() { - const logOut = useContext(LogOutContext); const client = useClient(); const callback = (route: "disable" | "delete") => () => @@ -27,7 +24,7 @@ export default function AccountManagement() { "X-MFA-Ticket": ticket.token, }, }) - .then(() => logOut(true)), + .then(clientController.logoutCurrent), ); return ( diff --git a/src/components/settings/account/EditAccount.tsx b/src/components/settings/account/EditAccount.tsx index 929bdfb1..2bc24994 100644 --- a/src/components/settings/account/EditAccount.tsx +++ b/src/components/settings/account/EditAccount.tsx @@ -3,7 +3,7 @@ import { Envelope, Key, Pencil } from "@styled-icons/boxicons-solid"; import { observer } from "mobx-react-lite"; import { Text } from "preact-i18n"; -import { useContext, useEffect, useState } from "preact/hooks"; +import { useEffect, useState } from "preact/hooks"; import { AccountDetail, @@ -12,27 +12,22 @@ import { HiddenValue, } from "@revoltchat/ui"; -import { - ClientStatus, - StatusContext, - useClient, -} from "../../../context/revoltjs/RevoltClient"; - +import { useSession } from "../../../controllers/client/ClientController"; import { modalController } from "../../../controllers/modals/ModalController"; export default observer(() => { - const client = useClient(); - const status = useContext(StatusContext); + const session = useSession()!; + const client = session.client!; const [email, setEmail] = useState("..."); useEffect(() => { - if (email === "..." && status === ClientStatus.ONLINE) { + if (email === "..." && session.state === "Online") { client.api .get("/auth/account/") .then((account) => setEmail(account.email)); } - }, [client, email, status]); + }, [client, email, session.state]); return ( <> diff --git a/src/components/settings/account/MultiFactorAuthentication.tsx b/src/components/settings/account/MultiFactorAuthentication.tsx index d1cca41d..5d794bf8 100644 --- a/src/components/settings/account/MultiFactorAuthentication.tsx +++ b/src/components/settings/account/MultiFactorAuthentication.tsx @@ -3,17 +3,13 @@ import { Lock } from "@styled-icons/boxicons-solid"; import { API } from "revolt.js"; import { Text } from "preact-i18n"; -import { useCallback, useContext, useEffect, useState } from "preact/hooks"; +import { useCallback, useEffect, useState } from "preact/hooks"; import { Category, CategoryButton, Error, Tip } from "@revoltchat/ui"; -import { - ClientStatus, - StatusContext, - useClient, -} from "../../../context/revoltjs/RevoltClient"; import { takeError } from "../../../context/revoltjs/util"; +import { useSession } from "../../../controllers/client/ClientController"; import { modalController } from "../../../controllers/modals/ModalController"; /** @@ -34,8 +30,8 @@ export function toConfig(token: string) { */ export default function MultiFactorAuthentication() { // Pull in prerequisites - const client = useClient(); - const status = useContext(StatusContext); + const session = useSession()!; + const client = session.client!; // Keep track of MFA state const [mfa, setMFA] = useState(); @@ -43,13 +39,13 @@ export default function MultiFactorAuthentication() { // Fetch the current MFA status on account useEffect(() => { - if (!mfa && status === ClientStatus.ONLINE) { - client.api + if (!mfa && session.state === "Online") { + client!.api .get("/auth/mfa/") .then(setMFA) .catch((err) => setError(takeError(err))); } - }, [client, mfa, status]); + }, [mfa, client, session.state]); // Action called when recovery code button is pressed const recoveryAction = useCallback(async () => { diff --git a/src/context/intermediate/modals/Input.tsx b/src/context/intermediate/modals/Input.tsx index e34cad30..d4e20b69 100644 --- a/src/context/intermediate/modals/Input.tsx +++ b/src/context/intermediate/modals/Input.tsx @@ -6,8 +6,8 @@ import { useContext, useState } from "preact/hooks"; import { Category, InputBox, Modal } from "@revoltchat/ui"; +import { useClient } from "../../../controllers/client/ClientController"; import { I18nError } from "../../Locale"; -import { AppContext } from "../../revoltjs/RevoltClient"; import { takeError } from "../../revoltjs/util"; interface Props { @@ -89,7 +89,7 @@ type SpecialProps = { onClose: () => void } & ( export function SpecialInputModal(props: SpecialProps) { const history = useHistory(); - const client = useContext(AppContext); + const client = useClient(); const { onClose } = props; switch (props.type) { diff --git a/src/context/intermediate/modals/Prompt.tsx b/src/context/intermediate/modals/Prompt.tsx index 31fc2d59..4f9986c1 100644 --- a/src/context/intermediate/modals/Prompt.tsx +++ b/src/context/intermediate/modals/Prompt.tsx @@ -5,7 +5,7 @@ import { ulid } from "ulid"; import styles from "./Prompt.module.scss"; import { Text } from "preact-i18n"; -import { useContext, useEffect, useState } from "preact/hooks"; +import { useEffect, useState } from "preact/hooks"; import { Category, Modal, InputBox, Radio } from "@revoltchat/ui"; import type { Action } from "@revoltchat/ui/esm/components/design/atoms/display/Modal"; @@ -14,8 +14,8 @@ import { TextReact } from "../../../lib/i18n"; import Message from "../../../components/common/messaging/Message"; import UserIcon from "../../../components/common/user/UserIcon"; +import { useClient } from "../../../controllers/client/ClientController"; import { I18nError } from "../../Locale"; -import { AppContext } from "../../revoltjs/RevoltClient"; import { takeError } from "../../revoltjs/util"; import { useIntermediate } from "../Intermediate"; @@ -81,7 +81,7 @@ type SpecialProps = { onClose: () => void } & ( ); export const SpecialPromptModal = observer((props: SpecialProps) => { - const client = useContext(AppContext); + const client = useClient(); const history = useHistory(); const [processing, setProcessing] = useState(false); const [error, setError] = useState(undefined); diff --git a/src/context/intermediate/popovers/CreateBot.tsx b/src/context/intermediate/popovers/CreateBot.tsx index 45837755..142996cd 100644 --- a/src/context/intermediate/popovers/CreateBot.tsx +++ b/src/context/intermediate/popovers/CreateBot.tsx @@ -2,13 +2,13 @@ import { SubmitHandler, useForm } from "react-hook-form"; import { API } from "revolt.js"; import { Text } from "preact-i18n"; -import { useContext, useState } from "preact/hooks"; +import { useState } from "preact/hooks"; import { Category, Modal } from "@revoltchat/ui"; +import { useClient } from "../../../controllers/client/ClientController"; import FormField from "../../../pages/login/FormField"; import { I18nError } from "../../Locale"; -import { AppContext } from "../../revoltjs/RevoltClient"; import { takeError } from "../../revoltjs/util"; interface Props { @@ -21,7 +21,7 @@ interface FormInputs { } export function CreateBotModal({ onClose, onCreate }: Props) { - const client = useContext(AppContext); + const client = useClient(); const { handleSubmit, register, errors } = useForm(); const [error, setError] = useState(undefined); diff --git a/src/context/intermediate/popovers/ImageViewer.tsx b/src/context/intermediate/popovers/ImageViewer.tsx index c6f8d91a..12a17f3c 100644 --- a/src/context/intermediate/popovers/ImageViewer.tsx +++ b/src/context/intermediate/popovers/ImageViewer.tsx @@ -7,7 +7,7 @@ import { Modal } from "@revoltchat/ui"; import AttachmentActions from "../../../components/common/messaging/attachments/AttachmentActions"; import EmbedMediaActions from "../../../components/common/messaging/embed/EmbedMediaActions"; -import { useClient } from "../../revoltjs/RevoltClient"; +import { useClient } from "../../../controllers/client/ClientController"; interface Props { onClose: () => void; diff --git a/src/context/intermediate/popovers/UserPicker.tsx b/src/context/intermediate/popovers/UserPicker.tsx index d1aeecf8..b152e8d5 100644 --- a/src/context/intermediate/popovers/UserPicker.tsx +++ b/src/context/intermediate/popovers/UserPicker.tsx @@ -5,7 +5,7 @@ import { useState } from "preact/hooks"; import { Modal } from "@revoltchat/ui"; import UserCheckbox from "../../../components/common/user/UserCheckbox"; -import { useClient } from "../../revoltjs/RevoltClient"; +import { useClient } from "../../../controllers/client/ClientController"; interface Props { omit?: string[]; diff --git a/src/context/intermediate/popovers/UserProfile.tsx b/src/context/intermediate/popovers/UserProfile.tsx index e5244099..5a4eb4c0 100644 --- a/src/context/intermediate/popovers/UserProfile.tsx +++ b/src/context/intermediate/popovers/UserProfile.tsx @@ -34,11 +34,7 @@ import UserIcon from "../../../components/common/user/UserIcon"; import { Username } from "../../../components/common/user/UserShort"; import UserStatus from "../../../components/common/user/UserStatus"; import Markdown from "../../../components/markdown/Markdown"; -import { - ClientStatus, - StatusContext, - useClient, -} from "../../revoltjs/RevoltClient"; +import { useSession } from "../../../controllers/client/ClientController"; import { useIntermediate } from "../Intermediate"; interface Props { @@ -63,8 +59,8 @@ export const UserProfile = observer( >(); const history = useHistory(); - const client = useClient(); - const status = useContext(StatusContext); + const session = useSession()!; + const client = session.client!; const [tab, setTab] = useState("profile"); const user = client.users.get(user_id); @@ -101,32 +97,26 @@ export const UserProfile = observer( useEffect(() => { if (dummy) return; - if ( - status === ClientStatus.ONLINE && - typeof mutual === "undefined" - ) { + if (session.state === "Online" && typeof mutual === "undefined") { setMutual(null); user.fetchMutual().then(setMutual); } - }, [mutual, status, dummy, user]); + }, [mutual, session.state, dummy, user]); useEffect(() => { if (dummy) return; - if ( - status === ClientStatus.ONLINE && - typeof profile === "undefined" - ) { + if (session.state === "Online" && typeof profile === "undefined") { setProfile(null); if (user.permission & UserPermission.ViewProfile) { user.fetchProfile().then(setProfile).catch(noop); } } - }, [profile, status, dummy, user]); + }, [profile, session.state, dummy, user]); useEffect(() => { if ( - status === ClientStatus.ONLINE && + session.state === "Online" && user.bot && typeof isPublicBot === "undefined" ) { @@ -136,7 +126,7 @@ export const UserProfile = observer( .then(() => setIsPublicBot(true)) .catch(noop); } - }, [isPublicBot, status, user, client.bots]); + }, [isPublicBot, session.state, user, client.bots]); const backgroundURL = profile && diff --git a/src/context/revoltjs/CheckAuth.tsx b/src/context/revoltjs/CheckAuth.tsx index be5c8eb8..e1dbcc55 100644 --- a/src/context/revoltjs/CheckAuth.tsx +++ b/src/context/revoltjs/CheckAuth.tsx @@ -1,8 +1,6 @@ import { Redirect } from "react-router-dom"; -import { useApplicationState } from "../../mobx/State"; - -import { useClient } from "./RevoltClient"; +import { useSession } from "../../controllers/client/ClientController"; interface Props { auth?: boolean; @@ -12,14 +10,12 @@ interface Props { } export const CheckAuth = (props: Props) => { - const auth = useApplicationState().auth; - const client = useClient(); - const ready = auth.isLoggedIn() && !!client?.user; + const session = useSession(); - if (props.auth && !ready) { + if (props.auth && !session?.ready) { if (props.blockRender) return null; return ; - } else if (!props.auth && ready) { + } else if (!props.auth && session?.ready) { if (props.blockRender) return null; return ; } diff --git a/src/context/revoltjs/FileUploads.tsx b/src/context/revoltjs/FileUploads.tsx index 5b32b51a..4ab39cbc 100644 --- a/src/context/revoltjs/FileUploads.tsx +++ b/src/context/revoltjs/FileUploads.tsx @@ -5,17 +5,15 @@ import Axios, { AxiosRequestConfig } from "axios"; import styles from "./FileUploads.module.scss"; import classNames from "classnames"; import { Text } from "preact-i18n"; -import { useContext, useEffect, useState } from "preact/hooks"; +import { useEffect, useState } from "preact/hooks"; import { IconButton, Preloader } from "@revoltchat/ui"; import { determineFileSize } from "../../lib/fileSize"; -import { useApplicationState } from "../../mobx/State"; - +import { useClient } from "../../controllers/client/ClientController"; import { modalController } from "../../controllers/modals/ModalController"; import { useIntermediate } from "../intermediate/Intermediate"; -import { AppContext } from "./RevoltClient"; import { takeError } from "./util"; type BehaviourType = @@ -115,7 +113,7 @@ export function grabFiles( export function FileUploader(props: Props) { const { fileType, maxFileSize, remove } = props; const { openScreen } = useIntermediate(); - const client = useApplicationState().client!; + const client = useClient(); const [uploading, setUploading] = useState(false); diff --git a/src/context/revoltjs/Notifications.tsx b/src/context/revoltjs/Notifications.tsx index 48178c63..c1d144a1 100644 --- a/src/context/revoltjs/Notifications.tsx +++ b/src/context/revoltjs/Notifications.tsx @@ -2,13 +2,13 @@ import { Route, Switch, useHistory, useParams } from "react-router-dom"; import { Message, User } from "revolt.js"; import { decodeTime } from "ulid"; -import { useCallback, useContext, useEffect } from "preact/hooks"; +import { useCallback, useEffect } from "preact/hooks"; import { useTranslation } from "../../lib/i18n"; import { useApplicationState } from "../../mobx/State"; -import { AppContext } from "./RevoltClient"; +import { useClient } from "../../controllers/client/ClientController"; const notifications: { [key: string]: Notification } = {}; @@ -30,7 +30,7 @@ function Notifier() { const notifs = state.notifications; const showNotification = state.settings.get("notifications:desktop"); - const client = useContext(AppContext); + const client = useClient(); const { guild: guild_id, channel: channel_id } = useParams<{ guild: string; channel: string; diff --git a/src/context/revoltjs/RequiresOnline.tsx b/src/context/revoltjs/RequiresOnline.tsx index 4835bd42..1cc65038 100644 --- a/src/context/revoltjs/RequiresOnline.tsx +++ b/src/context/revoltjs/RequiresOnline.tsx @@ -2,11 +2,10 @@ import { WifiOff } from "@styled-icons/boxicons-regular"; import styled from "styled-components/macro"; import { Text } from "preact-i18n"; -import { useContext } from "preact/hooks"; import { Preloader } from "@revoltchat/ui"; -import { ClientStatus, StatusContext } from "./RevoltClient"; +import { useSession } from "../../controllers/client/ClientController"; interface Props { children: Children; @@ -29,10 +28,12 @@ const Base = styled.div` `; export default function RequiresOnline(props: Props) { - const status = useContext(StatusContext); + const session = useSession(); - if (status === ClientStatus.CONNECTING) return ; - if (status !== ClientStatus.ONLINE && status !== ClientStatus.READY) + if (!session || session.state === "Connecting") + return ; + + if (!(session.state === "Online" || session.state === "Ready")) return ( diff --git a/src/context/revoltjs/RevoltClient.tsx b/src/context/revoltjs/RevoltClient.tsx index b76f4585..145a2550 100644 --- a/src/context/revoltjs/RevoltClient.tsx +++ b/src/context/revoltjs/RevoltClient.tsx @@ -1,113 +1,27 @@ /* eslint-disable react-hooks/rules-of-hooks */ import { observer } from "mobx-react-lite"; -import { Client } from "revolt.js"; -import { createContext } from "preact"; -import { useCallback, useContext, useEffect, useState } from "preact/hooks"; +import { useEffect } from "preact/hooks"; import { Preloader } from "@revoltchat/ui"; import { useApplicationState } from "../../mobx/State"; import { clientController } from "../../controllers/client/ClientController"; -import { modalController } from "../../controllers/modals/ModalController"; -import { registerEvents } from "./events"; -import { takeError } from "./util"; - -export enum ClientStatus { - READY, - LOADING, - OFFLINE, - DISCONNECTED, - CONNECTING, - RECONNECTING, - ONLINE, -} - -export interface ClientOperations { - logout: (shouldRequest?: boolean) => Promise; -} - -export const AppContext = createContext(null!); -export const StatusContext = createContext(null!); -export const LogOutContext = createContext<(avoidReq?: boolean) => void>(null!); type Props = { children: Children; }; export default observer(({ children }: Props) => { - // const state = useApplicationState(); - /*const [client, setClient] = useState(null!); - const [status, setStatus] = useState(ClientStatus.LOADING); - const [loaded, setLoaded] = useState(false); - - const logout = useCallback( - (avoidReq?: boolean) => { - setLoaded(false); - client.logout(avoidReq); - }, - [client], - ); - - useEffect(() => { - if (navigator.onLine) { - state.config.createClient().api.get("/").then(state.config.set); - } - }, []); - - useEffect(() => { - if (state.auth.isLoggedIn()) { - setLoaded(false); - const client = state.config.createClient(); - setClient(client); - - client - .useExistingSession(state.auth.getSession()!) - .catch((err) => { - const error = takeError(err); - if (error === "Forbidden" || error === "Unauthorized") { - client.logout(true); - modalController.push({ type: "signed_out" }); - } else { - setStatus(ClientStatus.DISCONNECTED); - modalController.push({ - type: "error", - error, - }); - } - }) - .finally(() => setLoaded(true)); - } else { - setStatus(ClientStatus.READY); - setLoaded(true); - } - }, [state.auth.getSession()]); - - useEffect(() => registerEvents(state, setStatus, client), [client]); - - if (!loaded || status === ClientStatus.LOADING) { - return ; - }*/ - const session = clientController.getActiveSession(); - if (!session?.ready) { - return ; + if (session) { + if (!session.ready) return ; + + const client = session.client!; + const state = useApplicationState(); + useEffect(() => state.registerListeners(client), [state, client]); } - const client = session.client!; - const state = useApplicationState(); - useEffect(() => state.registerListeners(client), [state, client]); - - return ( - - - void {}}> - {children} - - - - ); + return <>{children}; }); - -export const useClient = () => useContext(AppContext); diff --git a/src/context/revoltjs/StateMonitor.tsx b/src/context/revoltjs/StateMonitor.tsx deleted file mode 100644 index 34400f0b..00000000 --- a/src/context/revoltjs/StateMonitor.tsx +++ /dev/null @@ -1,39 +0,0 @@ -/** - * This file monitors the message cache to delete any queued messages that have already sent. - */ -import { Message } from "revolt.js"; - -import { useContext, useEffect } from "preact/hooks"; - -import { useApplicationState } from "../../mobx/State"; - -import { setGlobalEmojiPack } from "../../components/common/Emoji"; - -import { AppContext } from "./RevoltClient"; - -export default function StateMonitor() { - const client = useContext(AppContext); - const state = useApplicationState(); - - useEffect(() => { - function add(msg: Message) { - if (!msg.nonce) return; - if ( - !state.queue.get(msg.channel_id).find((x) => x.id === msg.nonce) - ) - return; - state.queue.remove(msg.nonce); - } - - client.addListener("message", add); - return () => client.removeListener("message", add); - }, [client]); - - // Set global emoji pack. - useEffect(() => { - const v = state.settings.get("appearance:emoji"); - v && setGlobalEmojiPack(v); - }, [state.settings.get("appearance:emoji")]); - - return null; -} diff --git a/src/context/revoltjs/SyncManager.tsx b/src/context/revoltjs/SyncManager.tsx index 6ed39af4..5d72f8c2 100644 --- a/src/context/revoltjs/SyncManager.tsx +++ b/src/context/revoltjs/SyncManager.tsx @@ -9,7 +9,7 @@ import { reportError } from "../../lib/ErrorBoundary"; import { useApplicationState } from "../../mobx/State"; -import { useClient } from "./RevoltClient"; +import { useClient } from "../../controllers/client/ClientController"; export default function SyncManager() { const client = useClient(); diff --git a/src/context/revoltjs/events.ts b/src/context/revoltjs/events.ts index a4121bc9..8232036f 100644 --- a/src/context/revoltjs/events.ts +++ b/src/context/revoltjs/events.ts @@ -1,15 +1,6 @@ -import { Client, Server } from "revolt.js"; +export const _ = ""; -import { StateUpdater } from "preact/hooks"; - -import { deleteRenderer } from "../../lib/renderer/Singleton"; - -import State from "../../mobx/State"; - -import { resetMemberSidebarFetched } from "../../components/navigation/right/MemberSidebar"; -import { ClientStatus } from "./RevoltClient"; - -export function registerEvents( +/*export function registerEvents( state: State, setStatus: StateUpdater, client: Client, @@ -86,4 +77,4 @@ export function registerEvents( window.removeEventListener("online", online); window.removeEventListener("offline", offline); }; -} +}*/ diff --git a/src/controllers/client/ClientController.tsx b/src/controllers/client/ClientController.tsx index 56200d97..df3216bd 100644 --- a/src/controllers/client/ClientController.tsx +++ b/src/controllers/client/ClientController.tsx @@ -1,11 +1,17 @@ import { action, computed, makeAutoObservable, ObservableMap } from "mobx"; -import type { Nullable } from "revolt.js"; +import { Client, Nullable } from "revolt.js"; import Auth from "../../mobx/stores/Auth"; +import { modalController } from "../modals/ModalController"; import Session from "./Session"; class ClientController { + /** + * API client + */ + private apiClient: Client; + /** * Map of user IDs to sessions */ @@ -17,10 +23,19 @@ class ClientController { private current: Nullable; constructor() { + this.apiClient = new Client({ + apiURL: import.meta.env.VITE_API_URL, + }); + this.sessions = new ObservableMap(); this.current = null; makeAutoObservable(this); + + this.logoutCurrent = this.logoutCurrent.bind(this); + + // Inject globally + (window as any).clientController = this; } /** @@ -29,12 +44,23 @@ class ClientController { */ @action hydrate(auth: Auth) { for (const entry of auth.getAccounts()) { + const user_id = entry.session.user_id!; + const session = new Session(); - this.sessions.set(entry.session._id!, session); - session.emit({ - action: "LOGIN", - session: entry.session, - }); + this.sessions.set(user_id, session); + + session + .emit({ + action: "LOGIN", + session: entry.session, + }) + .catch((error) => { + if (error === "Forbidden" || error === "Unauthorized") { + this.sessions.delete(user_id); + auth.removeSession(user_id); + modalController.push({ type: "signed_out" }); + } + }); } this.current = this.sessions.keys().next().value ?? null; @@ -44,6 +70,14 @@ class ClientController { return this.sessions.get(this.current!); } + @computed getAnonymousClient() { + return this.apiClient; + } + + @computed getAvailableClient() { + return this.getActiveSession()?.client ?? this.apiClient; + } + @computed isLoggedIn() { return this.current === null; } @@ -59,6 +93,41 @@ class ClientController { session.destroy(); } } + + @action logoutCurrent() { + if (this.current) { + this.logout(this.current); + } + } + + @action switchAccount(user_id: string) { + this.current = user_id; + } } export const clientController = new ClientController(); + +/** + * Get the currently active session. + * @returns Session + */ +export function useSession() { + return clientController.getActiveSession(); +} + +/** + * Get the currently active client or an unauthorised + * client for API requests, whichever is available. + * @returns Revolt.js Client + */ +export function useClient() { + return clientController.getAvailableClient(); +} + +/** + * Get unauthorised client for API requests. + * @returns Revolt.js Client + */ +export function useApi() { + return clientController.getAnonymousClient().api; +} diff --git a/src/controllers/client/Session.tsx b/src/controllers/client/Session.tsx index 1189e059..b19750e5 100644 --- a/src/controllers/client/Session.tsx +++ b/src/controllers/client/Session.tsx @@ -20,6 +20,7 @@ type Transition = export default class Session { state: State = window.navigator.onLine ? "Ready" : "Offline"; + user_id: string | null = null; client: Client | null = null; constructor() { @@ -83,6 +84,7 @@ export default class Session { private destroyClient() { this.client!.removeAllListeners(); + this.user_id = null; this.client = null; } @@ -101,7 +103,7 @@ export default class Session { } @action async emit(data: Transition) { - console.info("Handle event:", data); + console.info(`[FSM ${this.user_id ?? "Anonymous"}]`, data); switch (data.action) { // Login with session @@ -112,6 +114,7 @@ export default class Session { try { await this.client!.useExistingSession(data.session); + this.user_id = this.client!.user!._id; } catch (err) { this.state = "Ready"; throw err; diff --git a/src/controllers/modals/components/ModifyAccount.tsx b/src/controllers/modals/components/ModifyAccount.tsx index 2971e48e..70dd3d11 100644 --- a/src/controllers/modals/components/ModifyAccount.tsx +++ b/src/controllers/modals/components/ModifyAccount.tsx @@ -1,18 +1,16 @@ import { SubmitHandler, useForm } from "react-hook-form"; import { Text } from "preact-i18n"; -import { useContext, useState } from "preact/hooks"; +import { useState } from "preact/hooks"; import { Category, Error, Modal } from "@revoltchat/ui"; import { noopTrue } from "../../../lib/js"; -import { useApplicationState } from "../../../mobx/State"; - -import { AppContext } from "../../../context/revoltjs/RevoltClient"; import { takeError } from "../../../context/revoltjs/util"; import FormField from "../../../pages/login/FormField"; +import { useClient } from "../../client/ClientController"; import { ModalProps } from "../types"; interface FormInputs { @@ -30,7 +28,7 @@ export default function ModifyAccount({ field, ...props }: ModalProps<"modify_account">) { - const client = useApplicationState().client!; + const client = useClient(); const [processing, setProcessing] = useState(false); const { handleSubmit, register, errors } = useForm(); const [error, setError] = useState(undefined); diff --git a/src/lib/ContextMenus.tsx b/src/lib/ContextMenus.tsx index 2dc652d7..ed96762c 100644 --- a/src/lib/ContextMenus.tsx +++ b/src/lib/ContextMenus.tsx @@ -19,7 +19,6 @@ import { openContextMenu, } from "preact-context-menu"; import { Text } from "preact-i18n"; -import { useContext } from "preact/hooks"; import { IconButton, LineDivider } from "@revoltchat/ui"; @@ -28,16 +27,12 @@ import { QueuedMessage } from "../mobx/stores/MessageQueue"; import { NotificationState } from "../mobx/stores/NotificationOptions"; import { Screen, useIntermediate } from "../context/intermediate/Intermediate"; -import { - AppContext, - ClientStatus, - StatusContext, -} from "../context/revoltjs/RevoltClient"; import { takeError } from "../context/revoltjs/util"; import CMNotifications from "./contextmenu/CMNotifications"; import Tooltip from "../components/common/Tooltip"; import UserStatus from "../components/common/user/UserStatus"; +import { useSession } from "../controllers/client/ClientController"; import { modalController } from "../controllers/modals/ModalController"; import { internalEmit } from "./eventEmitter"; import { getRenderer } from "./renderer/Singleton"; @@ -122,12 +117,12 @@ type Action = // Tip: This should just be split into separate context menus per logical area. export default function ContextMenus() { const { openScreen, writeClipboard } = useIntermediate(); - const client = useContext(AppContext); + const session = useSession()!; + const client = session.client!; const userId = client.user!._id; - const status = useContext(StatusContext); - const isOnline = status === ClientStatus.ONLINE; const state = useApplicationState(); const history = useHistory(); + const isOnline = session.state === "Online"; function contextClick(data?: Action) { if (typeof data === "undefined") return; diff --git a/src/lib/FakeClient.tsx b/src/lib/FakeClient.tsx deleted file mode 100644 index 861ecec3..00000000 --- a/src/lib/FakeClient.tsx +++ /dev/null @@ -1,13 +0,0 @@ -import { observer } from "mobx-react-lite"; - -import { useMemo } from "preact/hooks"; - -import { useApplicationState } from "../mobx/State"; - -import { AppContext } from "../context/revoltjs/RevoltClient"; - -export default observer(({ children }: { children: Children }) => { - const config = useApplicationState().config; - const client = useMemo(() => config.createClient(), [config.get()]); - return {children}; -}); diff --git a/src/mobx/State.ts b/src/mobx/State.ts index 97299b69..7a951ab9 100644 --- a/src/mobx/State.ts +++ b/src/mobx/State.ts @@ -140,6 +140,9 @@ export default class State { if (client) { this.client = client; this.plugins.onClient(client); + + // Register message listener for clearing queue. + this.client.addListener("message", this.queue.onMessage); } // Register all the listeners required for saving and syncing state. @@ -225,6 +228,11 @@ export default class State { }); return () => { + // Remove any listeners attached to client. + if (client) { + client.removeListener("message", this.queue.onMessage); + } + // Stop exposing the client. this.client = undefined; diff --git a/src/mobx/stores/MessageQueue.ts b/src/mobx/stores/MessageQueue.ts index 953427ed..c4fd5d26 100644 --- a/src/mobx/stores/MessageQueue.ts +++ b/src/mobx/stores/MessageQueue.ts @@ -5,6 +5,7 @@ import { makeAutoObservable, observable, } from "mobx"; +import { Message } from "revolt.js"; import Store from "../interfaces/Store"; @@ -47,6 +48,8 @@ export default class MessageQueue implements Store { constructor() { this.messages = observable.array([]); makeAutoObservable(this); + + this.onMessage = this.onMessage.bind(this); } get id() { @@ -105,4 +108,16 @@ export default class MessageQueue implements Store { @computed get(channel: string) { return this.messages.filter((x) => x.channel === channel); } + + /** + * Handle an incoming Message + * @param message Message + */ + @action onMessage(message: Message) { + if (!message.nonce) return; + if (!this.get(message.channel_id).find((x) => x.id === message.nonce)) + return; + + this.remove(message.nonce); + } } diff --git a/src/mobx/stores/Settings.ts b/src/mobx/stores/Settings.ts index f775a366..344d65a7 100644 --- a/src/mobx/stores/Settings.ts +++ b/src/mobx/stores/Settings.ts @@ -4,8 +4,7 @@ import { mapToRecord } from "../../lib/conversion"; import { Fonts, MonospaceFonts, Overrides } from "../../context/Theme"; -import { EmojiPack } from "../../components/common/Emoji"; -import { MIGRATIONS } from "../State"; +import { EmojiPack, setGlobalEmojiPack } from "../../components/common/Emoji"; import Persistent from "../interfaces/Persistent"; import Store from "../interfaces/Store"; import Syncable from "../interfaces/Syncable"; @@ -79,6 +78,11 @@ export default class Settings * @param value Value */ @action set(key: T, value: ISettings[T]) { + // Emoji needs to be immediately applied. + if (key === 'appearance:emoji') { + setGlobalEmojiPack(value as EmojiPack); + } + this.data.set(key, value); } diff --git a/src/pages/Open.tsx b/src/pages/Open.tsx index 40dd5f7a..cf724c84 100644 --- a/src/pages/Open.tsx +++ b/src/pages/Open.tsx @@ -2,25 +2,20 @@ import { useHistory, useParams } from "react-router-dom"; import { Text } from "preact-i18n"; -import { useContext, useEffect } from "preact/hooks"; +import { useEffect } from "preact/hooks"; import { Header } from "@revoltchat/ui"; -import { - AppContext, - ClientStatus, - StatusContext, -} from "../context/revoltjs/RevoltClient"; - +import { useSession } from "../controllers/client/ClientController"; import { modalController } from "../controllers/modals/ModalController"; export default function Open() { const history = useHistory(); - const client = useContext(AppContext); - const status = useContext(StatusContext); + const session = useSession()!; + const client = session.client!; const { id } = useParams<{ id: string }>(); - if (status !== ClientStatus.ONLINE) { + if (session.state !== "Online") { return (
diff --git a/src/pages/RevoltApp.tsx b/src/pages/RevoltApp.tsx index c4b3631b..110b9b3a 100644 --- a/src/pages/RevoltApp.tsx +++ b/src/pages/RevoltApp.tsx @@ -9,7 +9,6 @@ import { isTouchscreenDevice } from "../lib/isTouchscreenDevice"; import Popovers from "../context/intermediate/Popovers"; import Notifications from "../context/revoltjs/Notifications"; -import StateMonitor from "../context/revoltjs/StateMonitor"; import { Titlebar } from "../components/native/Titlebar"; import BottomNavigation from "../components/navigation/BottomNavigation"; @@ -235,7 +234,6 @@ export default function App() { - diff --git a/src/pages/app.tsx b/src/pages/app.tsx index 89f05b31..e50a27c8 100644 --- a/src/pages/app.tsx +++ b/src/pages/app.tsx @@ -5,7 +5,6 @@ import { lazy, Suspense } from "preact/compat"; import { Masks, Preloader } from "@revoltchat/ui"; import ErrorBoundary from "../lib/ErrorBoundary"; -import FakeClient from "../lib/FakeClient"; import Context from "../context"; import { CheckAuth } from "../context/revoltjs/CheckAuth"; @@ -36,9 +35,7 @@ export function App() { - - - + diff --git a/src/pages/channels/Channel.tsx b/src/pages/channels/Channel.tsx index 49d4028f..3545fd55 100644 --- a/src/pages/channels/Channel.tsx +++ b/src/pages/channels/Channel.tsx @@ -16,8 +16,6 @@ import { isTouchscreenDevice } from "../../lib/isTouchscreenDevice"; import { useApplicationState } from "../../mobx/State"; import { SIDEBAR_MEMBERS } from "../../mobx/stores/Layout"; -import { useClient } from "../../context/revoltjs/RevoltClient"; - import AgeGate from "../../components/common/AgeGate"; import MessageBox from "../../components/common/messaging/MessageBox"; import JumpToBottom from "../../components/common/messaging/bars/JumpToBottom"; @@ -25,6 +23,7 @@ import NewMessages from "../../components/common/messaging/bars/NewMessages"; import TypingIndicator from "../../components/common/messaging/bars/TypingIndicator"; import RightSidebar from "../../components/navigation/RightSidebar"; import { PageHeader } from "../../components/ui/Header"; +import { useClient } from "../../controllers/client/ClientController"; import ChannelHeader from "./ChannelHeader"; import { MessageArea } from "./messaging/MessageArea"; import VoiceHeader from "./voice/VoiceHeader"; diff --git a/src/pages/channels/messaging/MessageArea.tsx b/src/pages/channels/messaging/MessageArea.tsx index 4a1e031e..b74a9ad9 100644 --- a/src/pages/channels/messaging/MessageArea.tsx +++ b/src/pages/channels/messaging/MessageArea.tsx @@ -25,11 +25,8 @@ import { ScrollState } from "../../../lib/renderer/types"; import { IntermediateContext } from "../../../context/intermediate/Intermediate"; import RequiresOnline from "../../../context/revoltjs/RequiresOnline"; -import { - ClientStatus, - StatusContext, -} from "../../../context/revoltjs/RevoltClient"; +import { useSession } from "../../../controllers/client/ClientController"; import ConversationStart from "./ConversationStart"; import MessageRenderer from "./MessageRenderer"; @@ -65,7 +62,7 @@ export const MESSAGE_AREA_PADDING = 82; export const MessageArea = observer(({ last_id, channel }: Props) => { const history = useHistory(); - const status = useContext(StatusContext); + const session = useSession()!; const { focusTaken } = useContext(IntermediateContext); // ? Required data for message links. @@ -213,8 +210,8 @@ export const MessageArea = observer(({ last_id, channel }: Props) => { // ? If we are waiting for network, try again. useEffect(() => { - switch (status) { - case ClientStatus.ONLINE: + switch (session.state) { + case "Online": if (renderer.state === "WAITING_FOR_NETWORK") { renderer.init(); } else { @@ -222,13 +219,13 @@ export const MessageArea = observer(({ last_id, channel }: Props) => { } break; - case ClientStatus.OFFLINE: - case ClientStatus.DISCONNECTED: - case ClientStatus.CONNECTING: + case "Offline": + case "Disconnected": + case "Connecting": renderer.markStale(); break; } - }, [renderer, status]); + }, [renderer, session.state]); // ? When the container is scrolled. // ? Also handle StayAtBottom diff --git a/src/pages/channels/messaging/MessageRenderer.tsx b/src/pages/channels/messaging/MessageRenderer.tsx index fd7aa7b9..fa90cf0e 100644 --- a/src/pages/channels/messaging/MessageRenderer.tsx +++ b/src/pages/channels/messaging/MessageRenderer.tsx @@ -3,9 +3,7 @@ import { X } from "@styled-icons/boxicons-regular"; import dayjs from "dayjs"; import isEqual from "lodash.isequal"; import { observer } from "mobx-react-lite"; -import { API } from "revolt.js"; -import { Message as MessageI } from "revolt.js"; -import { Nullable } from "revolt.js"; +import { API, Message as MessageI, Nullable } from "revolt.js"; import styled from "styled-components/macro"; import { decodeTime } from "ulid"; @@ -20,10 +18,10 @@ import { ChannelRenderer } from "../../../lib/renderer/Singleton"; import { useApplicationState } from "../../../mobx/State"; import RequiresOnline from "../../../context/revoltjs/RequiresOnline"; -import { useClient } from "../../../context/revoltjs/RevoltClient"; import Message from "../../../components/common/messaging/Message"; import { SystemMessage } from "../../../components/common/messaging/SystemMessage"; +import { useClient } from "../../../controllers/client/ClientController"; import ConversationStart from "./ConversationStart"; import MessageEditor from "./MessageEditor"; diff --git a/src/pages/channels/voice/VoiceHeader.tsx b/src/pages/channels/voice/VoiceHeader.tsx index 0d906868..242e1041 100644 --- a/src/pages/channels/voice/VoiceHeader.tsx +++ b/src/pages/channels/voice/VoiceHeader.tsx @@ -17,10 +17,10 @@ import { Button } from "@revoltchat/ui"; import { voiceState, VoiceStatus } from "../../../lib/vortex/VoiceState"; import { useIntermediate } from "../../../context/intermediate/Intermediate"; -import { useClient } from "../../../context/revoltjs/RevoltClient"; import Tooltip from "../../../components/common/Tooltip"; import UserIcon from "../../../components/common/user/UserIcon"; +import { useClient } from "../../../controllers/client/ClientController"; interface Props { id: string; diff --git a/src/pages/developer/Developer.tsx b/src/pages/developer/Developer.tsx index 46733c44..c9e7f617 100644 --- a/src/pages/developer/Developer.tsx +++ b/src/pages/developer/Developer.tsx @@ -1,18 +1,17 @@ import { Wrench } from "@styled-icons/boxicons-solid"; -import { useContext, useEffect, useState } from "preact/hooks"; +import { useEffect, useState } from "preact/hooks"; import PaintCounter from "../../lib/PaintCounter"; import { TextReact } from "../../lib/i18n"; -import { AppContext } from "../../context/revoltjs/RevoltClient"; - import { PageHeader } from "../../components/ui/Header"; +import { useClient } from "../../controllers/client/ClientController"; export default function Developer() { // const voice = useContext(VoiceContext); - const client = useContext(AppContext); + const client = useClient(); const userPermission = client.user!.permission; const [ping, setPing] = useState(client.websocket.ping); const [crash, setCrash] = useState(false); diff --git a/src/pages/friends/Friends.tsx b/src/pages/friends/Friends.tsx index e269755f..3b6fa516 100644 --- a/src/pages/friends/Friends.tsx +++ b/src/pages/friends/Friends.tsx @@ -13,12 +13,12 @@ import { TextReact } from "../../lib/i18n"; import { isTouchscreenDevice } from "../../lib/isTouchscreenDevice"; import { useIntermediate } from "../../context/intermediate/Intermediate"; -import { useClient } from "../../context/revoltjs/RevoltClient"; import CollapsibleSection from "../../components/common/CollapsibleSection"; import Tooltip from "../../components/common/Tooltip"; import UserIcon from "../../components/common/user/UserIcon"; import { PageHeader } from "../../components/ui/Header"; +import { useClient } from "../../controllers/client/ClientController"; import { modalController } from "../../controllers/modals/ModalController"; import { Friend } from "./Friend"; diff --git a/src/pages/home/Home.tsx b/src/pages/home/Home.tsx index 858161c8..9690af9d 100644 --- a/src/pages/home/Home.tsx +++ b/src/pages/home/Home.tsx @@ -15,7 +15,7 @@ import styled from "styled-components/macro"; import styles from "./Home.module.scss"; import "./snow.scss"; import { Text } from "preact-i18n"; -import { useContext, useMemo } from "preact/hooks"; +import { useMemo } from "preact/hooks"; import { CategoryButton } from "@revoltchat/ui"; @@ -24,11 +24,11 @@ import { isTouchscreenDevice } from "../../lib/isTouchscreenDevice"; import { useApplicationState } from "../../mobx/State"; import { useIntermediate } from "../../context/intermediate/Intermediate"; -import { AppContext } from "../../context/revoltjs/RevoltClient"; import wideSVG from "/assets/wide.svg"; import { PageHeader } from "../../components/ui/Header"; +import { useClient } from "../../controllers/client/ClientController"; const Overlay = styled.div` display: grid; @@ -45,7 +45,7 @@ const Overlay = styled.div` export default observer(() => { const { openScreen } = useIntermediate(); - const client = useContext(AppContext); + const client = useClient(); const state = useApplicationState(); const seasonalTheme = state.settings.get("appearance:seasonal", true); diff --git a/src/pages/invite/Invite.tsx b/src/pages/invite/Invite.tsx index b11f0d96..1b28a9f0 100644 --- a/src/pages/invite/Invite.tsx +++ b/src/pages/invite/Invite.tsx @@ -1,11 +1,10 @@ import { ArrowBack } from "@styled-icons/boxicons-regular"; -import { autorun } from "mobx"; import { Redirect, useHistory, useParams } from "react-router-dom"; import { API } from "revolt.js"; import styles from "./Invite.module.scss"; import { Text } from "preact-i18n"; -import { useContext, useEffect, useState } from "preact/hooks"; +import { useEffect, useState } from "preact/hooks"; import { Button, Category, Error, Preloader } from "@revoltchat/ui"; @@ -14,23 +13,22 @@ import { TextReact } from "../../lib/i18n"; import { useApplicationState } from "../../mobx/State"; import RequiresOnline from "../../context/revoltjs/RequiresOnline"; -import { - AppContext, - ClientStatus, - StatusContext, -} from "../../context/revoltjs/RevoltClient"; import { takeError } from "../../context/revoltjs/util"; import ServerIcon from "../../components/common/ServerIcon"; import UserIcon from "../../components/common/user/UserIcon"; +import { + useClient, + useSession, +} from "../../controllers/client/ClientController"; export default function Invite() { const history = useHistory(); - const client = useContext(AppContext); + const session = useSession(); + const client = useClient(); const layout = useApplicationState().layout; - const status = useContext(StatusContext); const { code } = useParams<{ code: string }>(); const [processing, setProcessing] = useState(false); const [error, setError] = useState(undefined); @@ -45,7 +43,7 @@ export default function Invite() { .then((data) => setInvite(data)) .catch((err) => setError(takeError(err))); } - }, [client, code, invite, status]); + }, [code, invite]); if (code === undefined) return ; @@ -154,7 +152,7 @@ export default function Invite() { + ); } else if (session.state === "Connecting") { diff --git a/src/controllers/client/ClientController.tsx b/src/controllers/client/ClientController.tsx index df3216bd..be9497ce 100644 --- a/src/controllers/client/ClientController.tsx +++ b/src/controllers/client/ClientController.tsx @@ -1,6 +1,8 @@ import { action, computed, makeAutoObservable, ObservableMap } from "mobx"; import { Client, Nullable } from "revolt.js"; +import { injectController } from "../../lib/window"; + import Auth from "../../mobx/stores/Auth"; import { modalController } from "../modals/ModalController"; @@ -35,7 +37,7 @@ class ClientController { this.logoutCurrent = this.logoutCurrent.bind(this); // Inject globally - (window as any).clientController = this; + injectController("client", this); } /** @@ -53,12 +55,14 @@ class ClientController { .emit({ action: "LOGIN", session: entry.session, + apiUrl: entry.apiUrl, }) .catch((error) => { if (error === "Forbidden" || error === "Unauthorized") { this.sessions.delete(user_id); auth.removeSession(user_id); modalController.push({ type: "signed_out" }); + session.destroy(); } }); } diff --git a/src/controllers/client/Session.tsx b/src/controllers/client/Session.tsx index b19750e5..96c203b9 100644 --- a/src/controllers/client/Session.tsx +++ b/src/controllers/client/Session.tsx @@ -7,6 +7,7 @@ type Transition = | { action: "LOGIN"; session: SessionPrivate; + apiUrl?: string; } | { action: @@ -70,12 +71,12 @@ export default class Session { }); } - private createClient() { + private createClient(apiUrl?: string) { this.client = new Client({ unreads: true, autoReconnect: false, onPongTimeout: "EXIT", - apiURL: import.meta.env.VITE_API_URL, + apiURL: apiUrl ?? import.meta.env.VITE_API_URL, }); this.client.addListener("dropped", this.onDropped); @@ -110,7 +111,7 @@ export default class Session { case "LOGIN": { this.assert("Ready"); this.state = "Connecting"; - this.createClient(); + this.createClient(data.apiUrl); try { await this.client!.useExistingSession(data.session); @@ -135,10 +136,13 @@ export default class Session { this.state = "Disconnected"; setTimeout(() => { - this.emit({ - action: "RETRY", - }); - }, 1500); + // Check we are still disconnected before retrying. + if (this.state === "Disconnected") { + this.emit({ + action: "RETRY", + }); + } + }, 1000); } break; diff --git a/src/controllers/modals/ModalController.tsx b/src/controllers/modals/ModalController.tsx index 48bb810d..8102cab8 100644 --- a/src/controllers/modals/ModalController.tsx +++ b/src/controllers/modals/ModalController.tsx @@ -9,6 +9,7 @@ import type { Client, API } from "revolt.js"; import { ulid } from "ulid"; import { determineLink } from "../../lib/links"; +import { injectController } from "../../lib/window"; import { getApplicationState } from "../../mobx/State"; @@ -52,6 +53,9 @@ class ModalController { rendered: computed, isVisible: computed, }); + + // Inject globally + injectController("modal", this); } /** diff --git a/src/lib/window.ts b/src/lib/window.ts new file mode 100644 index 00000000..f1290106 --- /dev/null +++ b/src/lib/window.ts @@ -0,0 +1,6 @@ +export function injectController(key: string, value: any) { + (window as any).controllers = { + ...((window as any).controllers ?? {}), + [key]: value, + }; +} diff --git a/src/mobx/stores/Auth.ts b/src/mobx/stores/Auth.ts index 33799f7e..12574350 100644 --- a/src/mobx/stores/Auth.ts +++ b/src/mobx/stores/Auth.ts @@ -8,6 +8,7 @@ import Store from "../interfaces/Store"; interface Account { session: Session; + apiUrl?: string; } export interface Data { @@ -70,9 +71,10 @@ export default class Auth implements Store, Persistent { /** * Add a new session to the auth manager. * @param session Session + * @param apiUrl Custom API URL */ - @action setSession(session: Session) { - this.sessions.set(session.user_id, { session }); + @action setSession(session: Session, apiUrl?: string) { + this.sessions.set(session.user_id, { session, apiUrl }); } /** From 66ae518e51464fa43f9db23322c4a5201a9ce118 Mon Sep 17 00:00:00 2001 From: Paul Makles Date: Wed, 29 Jun 2022 10:52:42 +0100 Subject: [PATCH 103/151] feat: make login functional again --- src/controllers/client/ClientController.tsx | 162 +++++++++++++++++--- src/controllers/client/Session.tsx | 9 +- src/mobx/State.ts | 4 +- src/pages/login/forms/CaptchaBlock.tsx | 4 +- src/pages/login/forms/Form.tsx | 11 +- src/pages/login/forms/FormCreate.tsx | 6 +- src/pages/login/forms/FormLogin.tsx | 104 +------------ 7 files changed, 160 insertions(+), 140 deletions(-) diff --git a/src/controllers/client/ClientController.tsx b/src/controllers/client/ClientController.tsx index be9497ce..9939df17 100644 --- a/src/controllers/client/ClientController.tsx +++ b/src/controllers/client/ClientController.tsx @@ -1,8 +1,10 @@ +import { detect } from "detect-browser"; import { action, computed, makeAutoObservable, ObservableMap } from "mobx"; -import { Client, Nullable } from "revolt.js"; +import { API, Client, Nullable } from "revolt.js"; import { injectController } from "../../lib/window"; +import { state } from "../../mobx/State"; import Auth from "../../mobx/stores/Auth"; import { modalController } from "../modals/ModalController"; @@ -14,6 +16,11 @@ class ClientController { */ private apiClient: Client; + /** + * Server configuration + */ + private configuration: API.RevoltConfig | null; + /** * Map of user IDs to sessions */ @@ -29,45 +36,38 @@ class ClientController { apiURL: import.meta.env.VITE_API_URL, }); + this.apiClient + .fetchConfiguration() + .then(() => (this.configuration = this.apiClient.configuration!)); + + this.configuration = null; this.sessions = new ObservableMap(); this.current = null; makeAutoObservable(this); + this.login = this.login.bind(this); this.logoutCurrent = this.logoutCurrent.bind(this); // Inject globally injectController("client", this); } + @action pickNextSession() { + this.current = + this.current ?? this.sessions.keys().next().value ?? null; + } + /** * Hydrate sessions and start client lifecycles. * @param auth Authentication store */ @action hydrate(auth: Auth) { for (const entry of auth.getAccounts()) { - const user_id = entry.session.user_id!; - - const session = new Session(); - this.sessions.set(user_id, session); - - session - .emit({ - action: "LOGIN", - session: entry.session, - apiUrl: entry.apiUrl, - }) - .catch((error) => { - if (error === "Forbidden" || error === "Unauthorized") { - this.sessions.delete(user_id); - auth.removeSession(user_id); - modalController.push({ type: "signed_out" }); - session.destroy(); - } - }); + this.addSession(entry); } - this.current = this.sessions.keys().next().value ?? null; + this.pickNextSession(); } @computed getActiveSession() { @@ -82,16 +82,134 @@ class ClientController { return this.getActiveSession()?.client ?? this.apiClient; } + @computed getServerConfig() { + return this.configuration; + } + @computed isLoggedIn() { return this.current === null; } + @action addSession(entry: { session: SessionPrivate; apiUrl?: string }) { + const user_id = entry.session.user_id!; + + const session = new Session(); + this.sessions.set(user_id, session); + + session + .emit({ + action: "LOGIN", + session: entry.session, + apiUrl: entry.apiUrl, + configuration: this.configuration!, + }) + .catch((error) => { + if (error === "Forbidden" || error === "Unauthorized") { + this.sessions.delete(user_id); + state.auth.removeSession(user_id); + modalController.push({ type: "signed_out" }); + session.destroy(); + } + }); + + this.pickNextSession(); + } + + async login(credentials: API.DataLogin) { + const browser = detect(); + + // Generate a friendly name for this browser + let friendly_name; + if (browser) { + let { name } = browser; + const { os } = browser; + let isiPad; + if (window.isNative) { + friendly_name = `Revolt Desktop on ${os}`; + } else { + if (name === "ios") { + name = "safari"; + } else if (name === "fxios") { + name = "firefox"; + } else if (name === "crios") { + name = "chrome"; + } + if (os === "Mac OS" && navigator.maxTouchPoints > 0) + isiPad = true; + friendly_name = `${name} on ${isiPad ? "iPadOS" : os}`; + } + } else { + friendly_name = "Unknown Device"; + } + + // Try to login with given credentials + let session = await this.apiClient.api.post("/auth/session/login", { + ...credentials, + friendly_name, + }); + + // Prompt for MFA verificaiton if necessary + if (session.result === "MFA") { + const { allowed_methods } = session; + const mfa_response: API.MFAResponse | undefined = await new Promise( + (callback) => + modalController.push({ + type: "mfa_flow", + state: "unknown", + available_methods: allowed_methods, + callback, + }), + ); + + if (typeof mfa_response === "undefined") { + throw "Cancelled"; + } + + session = await this.apiClient.api.post("/auth/session/login", { + mfa_response, + mfa_ticket: session.ticket, + friendly_name, + }); + + if (session.result === "MFA") { + // unreachable code + return; + } + } + + this.addSession({ + session, + }); + + /*const s = session; + + client.session = session; + (client as any).$updateHeaders(); + + async function login() { + state.auth.setSession(s); + } + + const { onboarding } = await client.api.get("/onboard/hello"); + + if (onboarding) { + openScreen({ + id: "onboarding", + callback: async (username: string) => + client.completeOnboarding({ username }, false).then(login), + }); + } else { + login(); + }*/ + } + @action logout(user_id: string) { const session = this.sessions.get(user_id); if (session) { this.sessions.delete(user_id); if (user_id === this.current) { - this.current = this.sessions.keys().next().value ?? null; + this.current = null; + this.pickNextSession(); } session.destroy(); diff --git a/src/controllers/client/Session.tsx b/src/controllers/client/Session.tsx index 96c203b9..e2e66b10 100644 --- a/src/controllers/client/Session.tsx +++ b/src/controllers/client/Session.tsx @@ -1,13 +1,14 @@ import { action, computed, makeAutoObservable } from "mobx"; -import { Client } from "revolt.js"; +import { API, Client } from "revolt.js"; type State = "Ready" | "Connecting" | "Online" | "Disconnected" | "Offline"; type Transition = | { action: "LOGIN"; - session: SessionPrivate; apiUrl?: string; + session: SessionPrivate; + configuration?: API.RevoltConfig; } | { action: @@ -113,6 +114,10 @@ export default class Session { this.state = "Connecting"; this.createClient(data.apiUrl); + if (data.configuration) { + this.client!.configuration = data.configuration; + } + try { await this.client!.useExistingSession(data.session); this.user_id = this.client!.user!._id; diff --git a/src/mobx/State.ts b/src/mobx/State.ts index 7a951ab9..ea1f1212 100644 --- a/src/mobx/State.ts +++ b/src/mobx/State.ts @@ -36,7 +36,7 @@ export default class State { locale: LocaleOptions; experiments: Experiments; layout: Layout; - config: ServerConfig; + private config: ServerConfig; notifications: NotificationOptions; queue: MessageQueue; settings: Settings; @@ -288,7 +288,7 @@ export default class State { } } -let state: State; +export let state: State; export async function hydrateState() { state = new State(); diff --git a/src/pages/login/forms/CaptchaBlock.tsx b/src/pages/login/forms/CaptchaBlock.tsx index 9dc8888c..c91d75e1 100644 --- a/src/pages/login/forms/CaptchaBlock.tsx +++ b/src/pages/login/forms/CaptchaBlock.tsx @@ -7,7 +7,7 @@ import { useEffect } from "preact/hooks"; import { Preloader } from "@revoltchat/ui"; -import { useApplicationState } from "../../../mobx/State"; +import { clientController } from "../../../controllers/client/ClientController"; export interface CaptchaProps { onSuccess: (token?: string) => void; @@ -15,7 +15,7 @@ export interface CaptchaProps { } export const CaptchaBlock = observer((props: CaptchaProps) => { - const configuration = useApplicationState().config.get(); + const configuration = clientController.getServerConfig(); useEffect(() => { if (!configuration?.features.captcha.enabled) { diff --git a/src/pages/login/forms/Form.tsx b/src/pages/login/forms/Form.tsx index 2ec8d0eb..2a1b7d6b 100644 --- a/src/pages/login/forms/Form.tsx +++ b/src/pages/login/forms/Form.tsx @@ -6,16 +6,14 @@ import styles from "../Login.module.scss"; import { Text } from "preact-i18n"; import { useState } from "preact/hooks"; -import { Button, Category, Preloader } from "@revoltchat/ui"; -import { Tip } from "@revoltchat/ui"; - -import { useApplicationState } from "../../../mobx/State"; +import { Button, Category, Preloader, Tip } from "@revoltchat/ui"; import { I18nError } from "../../../context/Locale"; import { takeError } from "../../../context/revoltjs/util"; import WaveSVG from "../../settings/assets/wave.svg"; +import { clientController } from "../../../controllers/client/ClientController"; import FormField from "../FormField"; import { CaptchaBlock, CaptchaProps } from "./CaptchaBlock"; import { MailProvider } from "./MailProvider"; @@ -45,7 +43,7 @@ interface FormInputs { } export const Form = observer(({ page, callback }: Props) => { - const configuration = useApplicationState().config.get(); + const configuration = clientController.getServerConfig(); const [loading, setLoading] = useState(false); const [success, setSuccess] = useState(undefined); @@ -260,7 +258,8 @@ export const Form = observer(({ page, callback }: Props) => { + target="_blank" + rel="noreferrer"> diff --git a/src/pages/login/forms/FormCreate.tsx b/src/pages/login/forms/FormCreate.tsx index 8d2e9ed7..65b4305f 100644 --- a/src/pages/login/forms/FormCreate.tsx +++ b/src/pages/login/forms/FormCreate.tsx @@ -1,9 +1,7 @@ -import { useApplicationState } from "../../../mobx/State"; - +import { useClient } from "../../../controllers/client/ClientController"; import { Form } from "./Form"; export function FormCreate() { - const config = useApplicationState().config; - const client = config.createClient(); + const client = useClient(); return client.register(data)} />; } diff --git a/src/pages/login/forms/FormLogin.tsx b/src/pages/login/forms/FormLogin.tsx index 95b2971a..d32db521 100644 --- a/src/pages/login/forms/FormLogin.tsx +++ b/src/pages/login/forms/FormLogin.tsx @@ -1,106 +1,6 @@ -import { detect } from "detect-browser"; -import { API } from "revolt.js"; - -import { useApplicationState } from "../../../mobx/State"; - -import { useIntermediate } from "../../../context/intermediate/Intermediate"; - -import { modalController } from "../../../controllers/modals/ModalController"; +import { clientController } from "../../../controllers/client/ClientController"; import { Form } from "./Form"; export function FormLogin() { - const state = useApplicationState(); - const { openScreen } = useIntermediate(); - - return ( - { - const browser = detect(); - let friendly_name; - if (browser) { - let { name } = browser; - const { os } = browser; - let isiPad; - if (window.isNative) { - friendly_name = `Revolt Desktop on ${os}`; - } else { - if (name === "ios") { - name = "safari"; - } else if (name === "fxios") { - name = "firefox"; - } else if (name === "crios") { - name = "chrome"; - } - if (os === "Mac OS" && navigator.maxTouchPoints > 0) - isiPad = true; - friendly_name = `${name} on ${isiPad ? "iPadOS" : os}`; - } - } else { - friendly_name = "Unknown Device"; - } - - // ! FIXME: temporary login flow code - // This should be replaced in the future. - const client = state.config.createClient(); - await client.fetchConfiguration(); - - let session = await client.api.post("/auth/session/login", { - ...data, - friendly_name, - }); - - if (session.result === "MFA") { - const { allowed_methods } = session; - const mfa_response: API.MFAResponse | undefined = - await new Promise((callback) => - modalController.push({ - type: "mfa_flow", - state: "unknown", - available_methods: allowed_methods, - callback, - }), - ); - - if (typeof mfa_response === "undefined") { - throw "Cancelled"; - } - - session = await client.api.post("/auth/session/login", { - mfa_response, - mfa_ticket: session.ticket, - friendly_name, - }); - - if (session.result === "MFA") { - // unreachable code - return; - } - } - - const s = session; - - client.session = session; - (client as any).$updateHeaders(); - - async function login() { - state.auth.setSession(s); - } - - const { onboarding } = await client.api.get("/onboard/hello"); - - if (onboarding) { - openScreen({ - id: "onboarding", - callback: async (username: string) => - client - .completeOnboarding({ username }, false) - .then(login), - }); - } else { - login(); - } - }} - /> - ); + return ; } From 31220db8fe545d0b88235f3d2b8774ec335b3901 Mon Sep 17 00:00:00 2001 From: Paul Makles Date: Wed, 29 Jun 2022 11:48:48 +0100 Subject: [PATCH 104/151] feat: fully working onboarding on login --- src/context/revoltjs/SyncManager.tsx | 12 ++++-- src/controllers/client/ClientController.tsx | 17 +++++--- src/controllers/client/Session.tsx | 46 ++++++++++++++++++--- 3 files changed, 60 insertions(+), 15 deletions(-) diff --git a/src/context/revoltjs/SyncManager.tsx b/src/context/revoltjs/SyncManager.tsx index 5d72f8c2..5022a373 100644 --- a/src/context/revoltjs/SyncManager.tsx +++ b/src/context/revoltjs/SyncManager.tsx @@ -9,21 +9,25 @@ import { reportError } from "../../lib/ErrorBoundary"; import { useApplicationState } from "../../mobx/State"; -import { useClient } from "../../controllers/client/ClientController"; +import { + useClient, + useSession, +} from "../../controllers/client/ClientController"; export default function SyncManager() { const client = useClient(); + const session = useSession(); const state = useApplicationState(); // Sync settings from Revolt. useEffect(() => { - if (client) { + if (session?.ready) { state.sync - .pull(client) + .pull(session.client!) .catch(console.error) .finally(() => state.changelog.checkForUpdates()); } - }, [client]); + }, [session?.ready]); // Take data updates from Revolt. useEffect(() => { diff --git a/src/controllers/client/ClientController.tsx b/src/controllers/client/ClientController.tsx index 9939df17..3012ba35 100644 --- a/src/controllers/client/ClientController.tsx +++ b/src/controllers/client/ClientController.tsx @@ -64,7 +64,7 @@ class ClientController { */ @action hydrate(auth: Auth) { for (const entry of auth.getAccounts()) { - this.addSession(entry); + this.addSession(entry, "existing"); } this.pickNextSession(); @@ -90,7 +90,10 @@ class ClientController { return this.current === null; } - @action addSession(entry: { session: SessionPrivate; apiUrl?: string }) { + @action addSession( + entry: { session: SessionPrivate; apiUrl?: string }, + knowledge: "new" | "existing", + ) { const user_id = entry.session.user_id!; const session = new Session(); @@ -102,6 +105,7 @@ class ClientController { session: entry.session, apiUrl: entry.apiUrl, configuration: this.configuration!, + knowledge, }) .catch((error) => { if (error === "Forbidden" || error === "Unauthorized") { @@ -177,9 +181,12 @@ class ClientController { } } - this.addSession({ - session, - }); + this.addSession( + { + session, + }, + "new", + ); /*const s = session; diff --git a/src/controllers/client/Session.tsx b/src/controllers/client/Session.tsx index e2e66b10..d171613b 100644 --- a/src/controllers/client/Session.tsx +++ b/src/controllers/client/Session.tsx @@ -1,6 +1,10 @@ import { action, computed, makeAutoObservable } from "mobx"; import { API, Client } from "revolt.js"; +import { state } from "../../mobx/State"; + +import { __thisIsAHack } from "../../context/intermediate/Intermediate"; + type State = "Ready" | "Connecting" | "Online" | "Disconnected" | "Offline"; type Transition = @@ -9,6 +13,8 @@ type Transition = apiUrl?: string; session: SessionPrivate; configuration?: API.RevoltConfig; + + knowledge: "new" | "existing"; } | { action: @@ -104,6 +110,17 @@ export default class Session { } } + private async continueLogin(data: Transition & { action: "LOGIN" }) { + try { + await this.client!.useExistingSession(data.session); + this.user_id = this.client!.user!._id; + state.auth.setSession(data.session); + } catch (err) { + this.state = "Ready"; + throw err; + } + } + @action async emit(data: Transition) { console.info(`[FSM ${this.user_id ?? "Anonymous"}]`, data); @@ -118,14 +135,31 @@ export default class Session { this.client!.configuration = data.configuration; } - try { - await this.client!.useExistingSession(data.session); - this.user_id = this.client!.user!._id; - } catch (err) { - this.state = "Ready"; - throw err; + if (data.knowledge === "new") { + await this.client!.fetchConfiguration(); + this.client!.session = data.session; + (this.client! as any).$updateHeaders(); + + const { onboarding } = await this.client!.api.get( + "/onboard/hello", + ); + + if (onboarding) { + __thisIsAHack({ + id: "onboarding", + callback: async (username: string) => + this.client!.completeOnboarding( + { username }, + false, + ).then(() => this.continueLogin(data)), + }); + + return; + } } + this.continueLogin(data); + break; } // Ready successfully received From 0e86f19da2bbcab092f5b62878a440b474510a16 Mon Sep 17 00:00:00 2001 From: Paul Makles Date: Wed, 29 Jun 2022 14:49:48 +0100 Subject: [PATCH 105/151] chore(doc): document client controller --- src/controllers/client/ClientController.tsx | 66 ++++++++++++++------- src/controllers/client/Session.tsx | 46 +++++++++++++- 2 files changed, 90 insertions(+), 22 deletions(-) diff --git a/src/controllers/client/ClientController.tsx b/src/controllers/client/ClientController.tsx index 3012ba35..cc8b9449 100644 --- a/src/controllers/client/ClientController.tsx +++ b/src/controllers/client/ClientController.tsx @@ -10,6 +10,9 @@ import Auth from "../../mobx/stores/Auth"; import { modalController } from "../modals/ModalController"; import Session from "./Session"; +/** + * Controls the lifecycles of clients + */ class ClientController { /** * API client @@ -36,6 +39,7 @@ class ClientController { apiURL: import.meta.env.VITE_API_URL, }); + // ! FIXME: loop until success infinitely this.apiClient .fetchConfiguration() .then(() => (this.configuration = this.apiClient.configuration!)); @@ -70,26 +74,51 @@ class ClientController { this.pickNextSession(); } + /** + * Get the currently selected session + * @returns Active Session + */ @computed getActiveSession() { return this.sessions.get(this.current!); } + /** + * Get an unauthenticated instance of the Revolt.js Client + * @returns API Client + */ @computed getAnonymousClient() { return this.apiClient; } + /** + * Get the next available client (either from session or API) + * @returns Revolt.js Client + */ @computed getAvailableClient() { return this.getActiveSession()?.client ?? this.apiClient; } + /** + * Fetch server configuration + * @returns Server Configuration + */ @computed getServerConfig() { return this.configuration; } + /** + * Check whether we are logged in right now + * @returns Whether we are logged in + */ @computed isLoggedIn() { return this.current === null; } + /** + * Start a new client lifecycle + * @param entry Session Information + * @param knowledge Whether the session is new or existing + */ @action addSession( entry: { session: SessionPrivate; apiUrl?: string }, knowledge: "new" | "existing", @@ -119,6 +148,10 @@ class ClientController { this.pickNextSession(); } + /** + * Login given a set of credentials + * @param credentials Credentials + */ async login(credentials: API.DataLogin) { const browser = detect(); @@ -181,35 +214,19 @@ class ClientController { } } + // Start client lifecycle this.addSession( { session, }, "new", ); - - /*const s = session; - - client.session = session; - (client as any).$updateHeaders(); - - async function login() { - state.auth.setSession(s); - } - - const { onboarding } = await client.api.get("/onboard/hello"); - - if (onboarding) { - openScreen({ - id: "onboarding", - callback: async (username: string) => - client.completeOnboarding({ username }, false).then(login), - }); - } else { - login(); - }*/ } + /** + * Log out of a specific user session + * @param user_id Target User ID + */ @action logout(user_id: string) { const session = this.sessions.get(user_id); if (session) { @@ -223,12 +240,19 @@ class ClientController { } } + /** + * Logout of the current session + */ @action logoutCurrent() { if (this.current) { this.logout(this.current); } } + /** + * Switch to another user session + * @param user_id Target User ID + */ @action switchAccount(user_id: string) { this.current = user_id; } diff --git a/src/controllers/client/Session.tsx b/src/controllers/client/Session.tsx index d171613b..dcd705ca 100644 --- a/src/controllers/client/Session.tsx +++ b/src/controllers/client/Session.tsx @@ -5,8 +5,14 @@ import { state } from "../../mobx/State"; import { __thisIsAHack } from "../../context/intermediate/Intermediate"; +/** + * Current lifecycle state + */ type State = "Ready" | "Connecting" | "Online" | "Disconnected" | "Offline"; +/** + * Possible transitions between states + */ type Transition = | { action: "LOGIN"; @@ -26,11 +32,17 @@ type Transition = | "OFFLINE"; }; +/** + * Client lifecycle finite state machine + */ export default class Session { state: State = window.navigator.onLine ? "Ready" : "Offline"; user_id: string | null = null; client: Client | null = null; + /** + * Create a new Session + */ constructor() { makeAutoObservable(this); @@ -44,7 +56,7 @@ export default class Session { } /** - * Initiate logout and destroy client. + * Initiate logout and destroy client */ @action destroy() { if (this.client) { @@ -54,30 +66,46 @@ export default class Session { } } + /** + * Called when user's browser signals it is online + */ private onOnline() { this.emit({ action: "ONLINE", }); } + /** + * Called when user's browser signals it is offline + */ private onOffline() { this.emit({ action: "OFFLINE", }); } + /** + * Called when the client signals it has disconnected + */ private onDropped() { this.emit({ action: "DISCONNECT", }); } + /** + * Called when the client signals it has received the Ready packet + */ private onReady() { this.emit({ action: "SUCCESS", }); } + /** + * Create a new Revolt.js Client for this Session + * @param apiUrl Optionally specify an API URL + */ private createClient(apiUrl?: string) { this.client = new Client({ unreads: true, @@ -90,12 +118,20 @@ export default class Session { this.client.addListener("ready", this.onReady); } + /** + * Destroy the client including any listeners. + */ private destroyClient() { this.client!.removeAllListeners(); + this.client!.logout(); this.user_id = null; this.client = null; } + /** + * Ensure we are in one of the given states + * @param state Possible states + */ private assert(...state: State[]) { let found = false; for (const target of state) { @@ -110,6 +146,10 @@ export default class Session { } } + /** + * Continue logging in provided onboarding is successful + * @param data Transition Data + */ private async continueLogin(data: Transition & { action: "LOGIN" }) { try { await this.client!.useExistingSession(data.session); @@ -121,6 +161,10 @@ export default class Session { } } + /** + * Transition to a new state by a certain action + * @param data Transition Data + */ @action async emit(data: Transition) { console.info(`[FSM ${this.user_id ?? "Anonymous"}]`, data); From 0261fec676228bf8e43072ad813bf407d5fe5df8 Mon Sep 17 00:00:00 2001 From: Paul Makles Date: Wed, 29 Jun 2022 16:02:35 +0100 Subject: [PATCH 106/151] chore: deprecate `RevoltClient` context --- .../navigation/right/MemberSidebar.tsx | 2 +- src/context/index.tsx | 8 +-- src/context/revoltjs/CheckAuth.tsx | 14 ++-- src/context/revoltjs/RevoltClient.tsx | 27 -------- src/controllers/client/ClientController.tsx | 27 ++++++-- src/controllers/client/Session.tsx | 2 +- src/controllers/client/jsx/Binder.tsx | 27 ++++++++ src/lib/renderer/Singleton.ts | 4 +- src/mobx/State.ts | 16 ++--- src/mobx/stores/Ordering.ts | 8 ++- src/mobx/stores/Plugins.ts | 18 ++--- src/pages/RevoltApp.tsx | 6 -- src/pages/app.tsx | 67 ++++++++++--------- 13 files changed, 118 insertions(+), 108 deletions(-) delete mode 100644 src/context/revoltjs/RevoltClient.tsx create mode 100644 src/controllers/client/jsx/Binder.tsx diff --git a/src/components/navigation/right/MemberSidebar.tsx b/src/components/navigation/right/MemberSidebar.tsx index 3dbf1f63..88e71b95 100644 --- a/src/components/navigation/right/MemberSidebar.tsx +++ b/src/components/navigation/right/MemberSidebar.tsx @@ -4,7 +4,7 @@ import { observer } from "mobx-react-lite"; import { useParams } from "react-router-dom"; import { Channel, Server, User, API } from "revolt.js"; -import { useEffect, useState } from "preact/hooks"; +import { useEffect, useLayoutEffect, useState } from "preact/hooks"; import { useSession, diff --git a/src/context/index.tsx b/src/context/index.tsx index 5b30422c..cc3fcdd1 100644 --- a/src/context/index.tsx +++ b/src/context/index.tsx @@ -8,12 +8,12 @@ import { Preloader, UIProvider } from "@revoltchat/ui"; import { hydrateState } from "../mobx/State"; +import Binder from "../controllers/client/jsx/Binder"; import ModalRenderer from "../controllers/modals/ModalRenderer"; import Locale from "./Locale"; import Theme from "./Theme"; import { history } from "./history"; import Intermediate from "./intermediate/Intermediate"; -import Client from "./revoltjs/RevoltClient"; import SyncManager from "./revoltjs/SyncManager"; const uiContext = { @@ -41,10 +41,10 @@ export default function Context({ children }: { children: Children }) { - + {children} - - + {} + diff --git a/src/context/revoltjs/CheckAuth.tsx b/src/context/revoltjs/CheckAuth.tsx index e1dbcc55..126ff925 100644 --- a/src/context/revoltjs/CheckAuth.tsx +++ b/src/context/revoltjs/CheckAuth.tsx @@ -1,6 +1,7 @@ +import { observer } from "mobx-react-lite"; import { Redirect } from "react-router-dom"; -import { useSession } from "../../controllers/client/ClientController"; +import { clientController } from "../../controllers/client/ClientController"; interface Props { auth?: boolean; @@ -9,16 +10,17 @@ interface Props { children: Children; } -export const CheckAuth = (props: Props) => { - const session = useSession(); +export const CheckAuth = observer((props: Props) => { + const loggedIn = clientController.isLoggedIn(); - if (props.auth && !session?.ready) { + // Redirect if logged out on authenticated page or vice-versa. + if (props.auth && !loggedIn) { if (props.blockRender) return null; return ; - } else if (!props.auth && session?.ready) { + } else if (!props.auth && loggedIn) { if (props.blockRender) return null; return ; } return <>{props.children}; -}; +}); diff --git a/src/context/revoltjs/RevoltClient.tsx b/src/context/revoltjs/RevoltClient.tsx deleted file mode 100644 index 145a2550..00000000 --- a/src/context/revoltjs/RevoltClient.tsx +++ /dev/null @@ -1,27 +0,0 @@ -/* eslint-disable react-hooks/rules-of-hooks */ -import { observer } from "mobx-react-lite"; - -import { useEffect } from "preact/hooks"; - -import { Preloader } from "@revoltchat/ui"; - -import { useApplicationState } from "../../mobx/State"; - -import { clientController } from "../../controllers/client/ClientController"; - -type Props = { - children: Children; -}; - -export default observer(({ children }: Props) => { - const session = clientController.getActiveSession(); - if (session) { - if (!session.ready) return ; - - const client = session.client!; - const state = useApplicationState(); - useEffect(() => state.registerListeners(client), [state, client]); - } - - return <>{children}; -}); diff --git a/src/controllers/client/ClientController.tsx b/src/controllers/client/ClientController.tsx index cc8b9449..4666ae5d 100644 --- a/src/controllers/client/ClientController.tsx +++ b/src/controllers/client/ClientController.tsx @@ -58,8 +58,9 @@ class ClientController { } @action pickNextSession() { - this.current = - this.current ?? this.sessions.keys().next().value ?? null; + this.switchAccount( + this.current ?? this.sessions.keys().next().value ?? null, + ); } /** @@ -82,6 +83,15 @@ class ClientController { return this.sessions.get(this.current!); } + /** + * Get the currently ready client + * @returns Ready Client + */ + @computed getReadyClient() { + const session = this.getActiveSession(); + return session && session.ready ? session.client! : undefined; + } + /** * Get an unauthenticated instance of the Revolt.js Client * @returns API Client @@ -111,7 +121,15 @@ class ClientController { * @returns Whether we are logged in */ @computed isLoggedIn() { - return this.current === null; + return this.current !== null; + } + + /** + * Check whether we are currently ready + * @returns Whether we are ready to render + */ + @computed isReady() { + return this.getActiveSession()?.ready; } /** @@ -127,6 +145,7 @@ class ClientController { const session = new Session(); this.sessions.set(user_id, session); + this.pickNextSession(); session .emit({ @@ -144,8 +163,6 @@ class ClientController { session.destroy(); } }); - - this.pickNextSession(); } /** diff --git a/src/controllers/client/Session.tsx b/src/controllers/client/Session.tsx index dcd705ca..11f80d71 100644 --- a/src/controllers/client/Session.tsx +++ b/src/controllers/client/Session.tsx @@ -270,6 +270,6 @@ export default class Session { * @returns Boolean */ @computed get ready() { - return this.client?.user; + return !!this.client?.user; } } diff --git a/src/controllers/client/jsx/Binder.tsx b/src/controllers/client/jsx/Binder.tsx new file mode 100644 index 00000000..6dea98ac --- /dev/null +++ b/src/controllers/client/jsx/Binder.tsx @@ -0,0 +1,27 @@ +import { observer } from "mobx-react-lite"; + +import { useEffect } from "preact/hooks"; + +import { Preloader } from "@revoltchat/ui"; + +import { state } from "../../../mobx/State"; + +import { clientController } from "../ClientController"; + +/** + * Prevent render until the client is ready to display. + * Also binds listeners from state to the current client. + */ +const Binder: React.FC = ({ children }) => { + const client = clientController.getReadyClient(); + useEffect(() => state.registerListeners(client!), [client]); + + // Block render if client is getting ready to work. + if (clientController.isLoggedIn() && !clientController.isReady()) { + return ; + } + + return <>{children}; +}; + +export default observer(Binder); diff --git a/src/lib/renderer/Singleton.ts b/src/lib/renderer/Singleton.ts index f30b9ec0..b59e7a4c 100644 --- a/src/lib/renderer/Singleton.ts +++ b/src/lib/renderer/Singleton.ts @@ -1,8 +1,6 @@ /* eslint-disable react-hooks/rules-of-hooks */ import { action, makeAutoObservable } from "mobx"; -import { Channel } from "revolt.js"; -import { Message } from "revolt.js"; -import { Nullable } from "revolt.js"; +import { Channel, Message, Nullable } from "revolt.js"; import { SimpleRenderer } from "./simple/SimpleRenderer"; import { RendererRoutines, ScrollState } from "./types"; diff --git a/src/mobx/State.ts b/src/mobx/State.ts index ea1f1212..bdc22d4f 100644 --- a/src/mobx/State.ts +++ b/src/mobx/State.ts @@ -47,8 +47,6 @@ export default class State { private persistent: [string, Persistent][] = []; private disabled: Set = new Set(); - client?: Client; - /** * Construct new State. */ @@ -67,14 +65,10 @@ export default class State { this.plugins = new Plugins(this); this.ordering = new Ordering(this); - makeAutoObservable(this, { - client: false, - }); + makeAutoObservable(this); this.register(); this.setDisabled = this.setDisabled.bind(this); - - this.client = undefined; } /** @@ -138,11 +132,11 @@ export default class State { registerListeners(client?: Client) { // If a client is present currently, expose it and provide it to plugins. if (client) { - this.client = client; + // this.client = client; this.plugins.onClient(client); // Register message listener for clearing queue. - this.client.addListener("message", this.queue.onMessage); + // this.client.addListener("message", this.queue.onMessage); } // Register all the listeners required for saving and syncing state. @@ -228,13 +222,13 @@ export default class State { }); return () => { - // Remove any listeners attached to client. + /*// Remove any listeners attached to client. if (client) { client.removeListener("message", this.queue.onMessage); } // Stop exposing the client. - this.client = undefined; + this.client = undefined;*/ // Wipe all listeners. listeners.forEach((x) => x()); diff --git a/src/mobx/stores/Ordering.ts b/src/mobx/stores/Ordering.ts index 8e2ccb11..a72e2ddb 100644 --- a/src/mobx/stores/Ordering.ts +++ b/src/mobx/stores/Ordering.ts @@ -2,6 +2,7 @@ import { action, computed, makeAutoObservable } from "mobx"; import { reorder } from "@revoltchat/ui"; +import { clientController } from "../../controllers/client/ClientController"; import State from "../State"; import Persistent from "../interfaces/Persistent"; import Store from "../interfaces/Store"; @@ -63,18 +64,19 @@ export default class Ordering implements Store, Persistent, Syncable { * All known servers with ordering applied */ @computed get orderedServers() { - const known = new Set(this.state.client?.servers.keys() ?? []); + const client = clientController.getReadyClient(); + const known = new Set(client?.servers.keys() ?? []); const ordered = [...this.servers]; const out = []; for (const id of ordered) { if (known.delete(id)) { - out.push(this.state.client!.servers.get(id)!); + out.push(client!.servers.get(id)!); } } for (const id of known) { - out.push(this.state.client!.servers.get(id)!); + out.push(client!.servers.get(id)!); } return out; diff --git a/src/mobx/stores/Plugins.ts b/src/mobx/stores/Plugins.ts index 08a480d5..711349e8 100644 --- a/src/mobx/stores/Plugins.ts +++ b/src/mobx/stores/Plugins.ts @@ -59,7 +59,7 @@ type Plugin = { type Instance = { format: 1; - onClient?: (client: Client) => {}; + onClient?: (client: Client) => void; onUnload?: () => void; }; @@ -124,7 +124,7 @@ export default class Plugins implements Store, Persistent { * @param id Plugin Id */ @computed get(namespace: string, id: string) { - return this.plugins.get(`${namespace }/${ id}`); + return this.plugins.get(`${namespace}/${id}`); } /** @@ -133,7 +133,7 @@ export default class Plugins implements Store, Persistent { * @returns Plugin Instance */ private getInstance(plugin: Pick) { - return this.instances.get(`${plugin.namespace }/${ plugin.id}`); + return this.instances.get(`${plugin.namespace}/${plugin.id}`); } /** @@ -159,7 +159,7 @@ export default class Plugins implements Store, Persistent { this.unload(plugin.namespace, plugin.id); } - this.plugins.set(`${plugin.namespace }/${ plugin.id}`, plugin); + this.plugins.set(`${plugin.namespace}/${plugin.id}`, plugin); if (typeof plugin.enabled === "undefined" || plugin) { this.load(plugin.namespace, plugin.id); @@ -173,7 +173,7 @@ export default class Plugins implements Store, Persistent { */ remove(namespace: string, id: string) { this.unload(namespace, id); - this.plugins.delete(`${namespace }/${ id}`); + this.plugins.delete(`${namespace}/${id}`); } /** @@ -186,7 +186,7 @@ export default class Plugins implements Store, Persistent { if (!plugin) throw "Unknown plugin!"; try { - const ns = `${plugin.namespace }/${ plugin.id}`; + const ns = `${plugin.namespace}/${plugin.id}`; const instance: Instance = eval(plugin.entrypoint)(); this.instances.set(ns, { @@ -198,10 +198,6 @@ export default class Plugins implements Store, Persistent { ...plugin, enabled: true, }); - - if (this.state.client) { - instance.onClient?.(this.state.client); - } } catch (error) { console.error(`Failed to load ${namespace}/${id}!`); console.error(error); @@ -217,7 +213,7 @@ export default class Plugins implements Store, Persistent { const plugin = this.get(namespace, id); if (!plugin) throw "Unknown plugin!"; - const ns = `${plugin.namespace }/${ plugin.id}`; + const ns = `${plugin.namespace}/${plugin.id}`; const loaded = this.getInstance(plugin); if (loaded) { loaded.onUnload?.(); diff --git a/src/pages/RevoltApp.tsx b/src/pages/RevoltApp.tsx index 110b9b3a..69ccf6f5 100644 --- a/src/pages/RevoltApp.tsx +++ b/src/pages/RevoltApp.tsx @@ -76,12 +76,6 @@ const Routes = styled.div.attrs({ "data-component": "routes" })<{ background: var(--primary-background); - /*background-color: rgba( - var(--primary-background-rgb), - max(var(--min-opacity), 0.75) - );*/ - //backdrop-filter: blur(10px); - ${() => isTouchscreenDevice && css` diff --git a/src/pages/app.tsx b/src/pages/app.tsx index e50a27c8..e2853fad 100644 --- a/src/pages/app.tsx +++ b/src/pages/app.tsx @@ -15,44 +15,51 @@ const Login = lazy(() => import("./login/Login")); const ConfirmDelete = lazy(() => import("./login/ConfirmDelete")); const RevoltApp = lazy(() => import("./RevoltApp")); +const LoadSuspense: React.FC = ({ children }) => ( + // @ts-expect-error Typing issue between Preact and Preact. + }>{children} +); + export function App() { return ( - {/* - // @ts-expect-error typings mis-match between preact... and preact? */} - }> - - + + + + + + - - - - - + + + + - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + - - - - + + + + ); From 1fcb3cedc12fd2a922605ab185124d3036589576 Mon Sep 17 00:00:00 2001 From: Paul Makles Date: Wed, 29 Jun 2022 16:27:57 +0100 Subject: [PATCH 107/151] feat: consistent authentication flow fix: missing suspense on login feat: re-prompt MFA if fail on login --- src/context/index.tsx | 7 ++- src/context/revoltjs/CheckAuth.tsx | 15 ++++++ src/controllers/client/ClientController.tsx | 55 +++++++++++++-------- src/controllers/client/jsx/Binder.tsx | 13 +---- src/pages/app.tsx | 4 +- 5 files changed, 57 insertions(+), 37 deletions(-) diff --git a/src/context/index.tsx b/src/context/index.tsx index cc3fcdd1..045e733e 100644 --- a/src/context/index.tsx +++ b/src/context/index.tsx @@ -41,10 +41,9 @@ export default function Context({ children }: { children: Children }) { - - {children} - {} - + {children} + + diff --git a/src/context/revoltjs/CheckAuth.tsx b/src/context/revoltjs/CheckAuth.tsx index 126ff925..eec8e8e5 100644 --- a/src/context/revoltjs/CheckAuth.tsx +++ b/src/context/revoltjs/CheckAuth.tsx @@ -1,6 +1,8 @@ import { observer } from "mobx-react-lite"; import { Redirect } from "react-router-dom"; +import { Preloader } from "@revoltchat/ui"; + import { clientController } from "../../controllers/client/ClientController"; interface Props { @@ -10,6 +12,10 @@ interface Props { children: Children; } +/** + * Check that we are logged in or out and redirect accordingly. + * Also prevent render until the client is ready to display. + */ export const CheckAuth = observer((props: Props) => { const loggedIn = clientController.isLoggedIn(); @@ -22,5 +28,14 @@ export const CheckAuth = observer((props: Props) => { return ; } + // Block render if client is getting ready to work. + if ( + props.auth && + clientController.isLoggedIn() && + !clientController.isReady() + ) { + return ; + } + return <>{props.children}; }); diff --git a/src/controllers/client/ClientController.tsx b/src/controllers/client/ClientController.tsx index 4666ae5d..fc51c4f6 100644 --- a/src/controllers/client/ClientController.tsx +++ b/src/controllers/client/ClientController.tsx @@ -7,6 +7,7 @@ import { injectController } from "../../lib/window"; import { state } from "../../mobx/State"; import Auth from "../../mobx/stores/Auth"; +import { resetMemberSidebarFetched } from "../../components/navigation/right/MemberSidebar"; import { modalController } from "../modals/ModalController"; import Session from "./Session"; @@ -205,29 +206,37 @@ class ClientController { // Prompt for MFA verificaiton if necessary if (session.result === "MFA") { const { allowed_methods } = session; - const mfa_response: API.MFAResponse | undefined = await new Promise( - (callback) => - modalController.push({ - type: "mfa_flow", - state: "unknown", - available_methods: allowed_methods, - callback, - }), - ); + while (session.result === "MFA") { + const mfa_response: API.MFAResponse | undefined = + await new Promise((callback) => + modalController.push({ + type: "mfa_flow", + state: "unknown", + available_methods: allowed_methods, + callback, + }), + ); - if (typeof mfa_response === "undefined") { - throw "Cancelled"; + if (typeof mfa_response === "undefined") { + break; + } + + try { + session = await this.apiClient.api.post( + "/auth/session/login", + { + mfa_response, + mfa_ticket: session.ticket, + friendly_name, + }, + ); + } catch (err) { + console.error("Failed login:", err); + } } - session = await this.apiClient.api.post("/auth/session/login", { - mfa_response, - mfa_ticket: session.ticket, - friendly_name, - }); - if (session.result === "MFA") { - // unreachable code - return; + throw "Cancelled"; } } @@ -247,12 +256,12 @@ class ClientController { @action logout(user_id: string) { const session = this.sessions.get(user_id); if (session) { - this.sessions.delete(user_id); if (user_id === this.current) { this.current = null; - this.pickNextSession(); } + this.sessions.delete(user_id); + this.pickNextSession(); session.destroy(); } } @@ -272,6 +281,10 @@ class ClientController { */ @action switchAccount(user_id: string) { this.current = user_id; + + // This will allow account switching to work more seamlessly, + // maybe it'll be properly / fully implemented at some point. + resetMemberSidebarFetched(); } } diff --git a/src/controllers/client/jsx/Binder.tsx b/src/controllers/client/jsx/Binder.tsx index 6dea98ac..cbec7403 100644 --- a/src/controllers/client/jsx/Binder.tsx +++ b/src/controllers/client/jsx/Binder.tsx @@ -2,26 +2,17 @@ import { observer } from "mobx-react-lite"; import { useEffect } from "preact/hooks"; -import { Preloader } from "@revoltchat/ui"; - import { state } from "../../../mobx/State"; import { clientController } from "../ClientController"; /** - * Prevent render until the client is ready to display. * Also binds listeners from state to the current client. */ -const Binder: React.FC = ({ children }) => { +const Binder: React.FC = () => { const client = clientController.getReadyClient(); useEffect(() => state.registerListeners(client!), [client]); - - // Block render if client is getting ready to work. - if (clientController.isLoggedIn() && !clientController.isReady()) { - return ; - } - - return <>{children}; + return null; }; export default observer(Binder); diff --git a/src/pages/app.tsx b/src/pages/app.tsx index e2853fad..2251e942 100644 --- a/src/pages/app.tsx +++ b/src/pages/app.tsx @@ -49,7 +49,9 @@ export function App() { - + + + From 45692999bf63189b1c6a2d2db9123842ccbf34a6 Mon Sep 17 00:00:00 2001 From: Paul Makles Date: Wed, 29 Jun 2022 16:41:26 +0100 Subject: [PATCH 108/151] chore(refactor): remove `SyncManager` --- src/context/index.tsx | 6 ++-- src/context/revoltjs/SyncManager.tsx | 50 -------------------------- src/lib/window.ts | 14 ++++++++ src/mobx/State.ts | 53 +++++++++++++++++++--------- src/mobx/stores/Plugins.ts | 11 ------ 5 files changed, 52 insertions(+), 82 deletions(-) delete mode 100644 src/context/revoltjs/SyncManager.tsx diff --git a/src/context/index.tsx b/src/context/index.tsx index 045e733e..51b0564f 100644 --- a/src/context/index.tsx +++ b/src/context/index.tsx @@ -6,7 +6,7 @@ import { useEffect, useState } from "preact/hooks"; import { Preloader, UIProvider } from "@revoltchat/ui"; -import { hydrateState } from "../mobx/State"; +import { state } from "../mobx/State"; import Binder from "../controllers/client/jsx/Binder"; import ModalRenderer from "../controllers/modals/ModalRenderer"; @@ -14,7 +14,6 @@ import Locale from "./Locale"; import Theme from "./Theme"; import { history } from "./history"; import Intermediate from "./intermediate/Intermediate"; -import SyncManager from "./revoltjs/SyncManager"; const uiContext = { Link, @@ -31,7 +30,7 @@ export default function Context({ children }: { children: Children }) { const [ready, setReady] = useState(false); useEffect(() => { - hydrateState().then(() => setReady(true)); + state.hydrate().then(() => setReady(true)); }, []); if (!ready) return ; @@ -42,7 +41,6 @@ export default function Context({ children }: { children: Children }) { {children} - diff --git a/src/context/revoltjs/SyncManager.tsx b/src/context/revoltjs/SyncManager.tsx deleted file mode 100644 index 5022a373..00000000 --- a/src/context/revoltjs/SyncManager.tsx +++ /dev/null @@ -1,50 +0,0 @@ -/** - * This file monitors changes to settings and syncs them to the server. - */ -import { ClientboundNotification } from "revolt.js"; - -import { useEffect } from "preact/hooks"; - -import { reportError } from "../../lib/ErrorBoundary"; - -import { useApplicationState } from "../../mobx/State"; - -import { - useClient, - useSession, -} from "../../controllers/client/ClientController"; - -export default function SyncManager() { - const client = useClient(); - const session = useSession(); - const state = useApplicationState(); - - // Sync settings from Revolt. - useEffect(() => { - if (session?.ready) { - state.sync - .pull(session.client!) - .catch(console.error) - .finally(() => state.changelog.checkForUpdates()); - } - }, [session?.ready]); - - // Take data updates from Revolt. - useEffect(() => { - if (!client) return; - function onPacket(packet: ClientboundNotification) { - if (packet.type === "UserSettingsUpdate") { - try { - state.sync.apply(packet.update); - } catch (err) { - reportError(err as any, "failed_sync_apply"); - } - } - } - - client.addListener("packet", onPacket); - return () => client.removeListener("packet", onPacket); - }, [client]); - - return <>; -} diff --git a/src/lib/window.ts b/src/lib/window.ts index f1290106..7ee0ce3f 100644 --- a/src/lib/window.ts +++ b/src/lib/window.ts @@ -1,3 +1,17 @@ +/** + * Inject a key into the window's globals. + * @param key Key + * @param value Value + */ +export function injectWindow(key: string, value: any) { + (window as any)[key] = value; +} + +/** + * Inject a controller into the global controllers object. + * @param key Key + * @param value Value + */ export function injectController(key: string, value: any) { (window as any).controllers = { ...((window as any).controllers ?? {}), diff --git a/src/mobx/State.ts b/src/mobx/State.ts index bdc22d4f..b48d9162 100644 --- a/src/mobx/State.ts +++ b/src/mobx/State.ts @@ -1,8 +1,11 @@ // @ts-expect-error No typings. import stringify from "json-stringify-deterministic"; import localforage from "localforage"; -import { makeAutoObservable, reaction, runInAction } from "mobx"; -import { Client } from "revolt.js"; +import { action, makeAutoObservable, reaction, runInAction } from "mobx"; +import { Client, ClientboundNotification } from "revolt.js"; + +import { reportError } from "../lib/ErrorBoundary"; +import { injectWindow } from "../lib/window"; import { clientController } from "../controllers/client/ClientController"; import Persistent from "./interfaces/Persistent"; @@ -69,6 +72,10 @@ export default class State { this.register(); this.setDisabled = this.setDisabled.bind(this); + this.onPacket = this.onPacket.bind(this); + + // Inject into window + injectWindow("state", this); } /** @@ -125,6 +132,20 @@ export default class State { } } + /** + * Consume packets from the client. + * @param packet Inbound Packet + */ + @action onPacket(packet: ClientboundNotification) { + if (packet.type === "UserSettingsUpdate") { + try { + this.sync.apply(packet.update); + } catch (err) { + reportError(err as any, "failed_sync_apply"); + } + } + } + /** * Register reaction listeners for persistent data stores. * @returns Function to dispose of listeners @@ -132,11 +153,17 @@ export default class State { registerListeners(client?: Client) { // If a client is present currently, expose it and provide it to plugins. if (client) { - // this.client = client; - this.plugins.onClient(client); - // Register message listener for clearing queue. - // this.client.addListener("message", this.queue.onMessage); + client.addListener("message", this.queue.onMessage); + + // Register listener for incoming packets. + client.addListener("packet", this.onPacket); + + // Sync settings from remote server. + state.sync + .pull(client) + .catch(console.error) + .finally(() => state.changelog.checkForUpdates()); } // Register all the listeners required for saving and syncing state. @@ -222,14 +249,12 @@ export default class State { }); return () => { - /*// Remove any listeners attached to client. + // Remove any listeners attached to client. if (client) { client.removeListener("message", this.queue.onMessage); + client.removeListener("packet", this.onPacket); } - // Stop exposing the client. - this.client = undefined;*/ - // Wipe all listeners. listeners.forEach((x) => x()); }; @@ -282,13 +307,7 @@ export default class State { } } -export let state: State; - -export async function hydrateState() { - state = new State(); - (window as any).state = state; - await state.hydrate(); -} +export const state = new State(); /** * Get the application state diff --git a/src/mobx/stores/Plugins.ts b/src/mobx/stores/Plugins.ts index 711349e8..e6b3bbb3 100644 --- a/src/mobx/stores/Plugins.ts +++ b/src/mobx/stores/Plugins.ts @@ -41,7 +41,6 @@ type Plugin = { * ```typescript * function (state: State) { * return { - * onClient: (client: Client) => {}, * onUnload: () => {} * } * } @@ -59,7 +58,6 @@ type Plugin = { type Instance = { format: 1; - onClient?: (client: Client) => void; onUnload?: () => void; }; @@ -231,13 +229,4 @@ export default class Plugins implements Store, Persistent { localforage.removeItem("revite:plugins"); window.location.reload(); } - - /** - * Push client through to plugins - */ - onClient(client: Client) { - for (const instance of this.instances.values()) { - instance.onClient?.(client); - } - } } From 05516c58233c49f259c37b7422d044b80de164b5 Mon Sep 17 00:00:00 2001 From: Paul Makles Date: Wed, 29 Jun 2022 16:46:25 +0100 Subject: [PATCH 109/151] fix: hide push notifications on electron app --- src/pages/settings/panes/Notifications.tsx | 102 +++++++++++---------- 1 file changed, 52 insertions(+), 50 deletions(-) diff --git a/src/pages/settings/panes/Notifications.tsx b/src/pages/settings/panes/Notifications.tsx index 921beb7b..d2dcdee9 100644 --- a/src/pages/settings/panes/Notifications.tsx +++ b/src/pages/settings/panes/Notifications.tsx @@ -61,57 +61,59 @@ export const Notifications = observer(() => { settings.set("notifications:desktop", desktopEnabled); }} /> - - } - description={ - - } - onChange={async (pushEnabled) => { - try { - const reg = - await navigator.serviceWorker?.getRegistration(); - if (reg) { - if (pushEnabled) { - const sub = await reg.pushManager.subscribe( - { - userVisibleOnly: true, - applicationServerKey: - urlBase64ToUint8Array( - client.configuration!.vapid, - ), - }, - ); - - // tell the server we just subscribed - const json = sub.toJSON(); - if (json.keys) { - client.api.post("/push/subscribe", { - endpoint: sub.endpoint, - ...(json.keys as { - p256dh: string; - auth: string; - }), - }); - setPushEnabled(true); - } - } else { - const sub = - await reg.pushManager.getSubscription(); - sub?.unsubscribe(); - setPushEnabled(false); - - client.api.post("/push/unsubscribe"); - } - } - } catch (err) { - console.error("Failed to enable push!", err); + {!window.native && ( + } - }} - /> + description={ + + } + onChange={async (pushEnabled) => { + try { + const reg = + await navigator.serviceWorker?.getRegistration(); + if (reg) { + if (pushEnabled) { + const sub = + await reg.pushManager.subscribe({ + userVisibleOnly: true, + applicationServerKey: + urlBase64ToUint8Array( + client.configuration! + .vapid, + ), + }); + + // tell the server we just subscribed + const json = sub.toJSON(); + if (json.keys) { + client.api.post("/push/subscribe", { + endpoint: sub.endpoint, + ...(json.keys as { + p256dh: string; + auth: string; + }), + }); + setPushEnabled(true); + } + } else { + const sub = + await reg.pushManager.getSubscription(); + sub?.unsubscribe(); + setPushEnabled(false); + + client.api.post("/push/unsubscribe"); + } + } + } catch (err) { + console.error("Failed to enable push!", err); + } + }} + /> + )}

From a2a52e237d7ac1629128becfa0630d859d6365ac Mon Sep 17 00:00:00 2001 From: Paul Makles Date: Wed, 29 Jun 2022 17:31:59 +0100 Subject: [PATCH 110/151] chore(refactor): remove `Notifications` component --- src/context/history.ts | 11 + src/context/revoltjs/Notifications.tsx | 296 ------------------------- src/mobx/State.ts | 27 ++- src/mobx/stores/NotificationOptions.ts | 283 ++++++++++++++++++++++- src/pages/RevoltApp.tsx | 2 - 5 files changed, 316 insertions(+), 303 deletions(-) delete mode 100644 src/context/revoltjs/Notifications.tsx diff --git a/src/context/history.ts b/src/context/history.ts index 5e816997..0e4d1268 100644 --- a/src/context/history.ts +++ b/src/context/history.ts @@ -3,3 +3,14 @@ import { createBrowserHistory } from "history"; export const history = createBrowserHistory({ basename: import.meta.env.BASE_URL, }); + +export const routeInformation = { + getServer: () => + /server\/([0123456789ABCDEFGHJKMNPQRSTVWXYZ]{26})/.exec( + history.location.pathname, + )?.[1], + getChannel: () => + /channel\/([0123456789ABCDEFGHJKMNPQRSTVWXYZ]{26})/.exec( + history.location.pathname, + )?.[1], +}; diff --git a/src/context/revoltjs/Notifications.tsx b/src/context/revoltjs/Notifications.tsx deleted file mode 100644 index c1d144a1..00000000 --- a/src/context/revoltjs/Notifications.tsx +++ /dev/null @@ -1,296 +0,0 @@ -import { Route, Switch, useHistory, useParams } from "react-router-dom"; -import { Message, User } from "revolt.js"; -import { decodeTime } from "ulid"; - -import { useCallback, useEffect } from "preact/hooks"; - -import { useTranslation } from "../../lib/i18n"; - -import { useApplicationState } from "../../mobx/State"; - -import { useClient } from "../../controllers/client/ClientController"; - -const notifications: { [key: string]: Notification } = {}; - -async function createNotification( - title: string, - options: globalThis.NotificationOptions, -) { - try { - return new Notification(title, options); - } catch (err) { - const sw = await navigator.serviceWorker.getRegistration(); - sw?.showNotification(title, options); - } -} - -function Notifier() { - const translate = useTranslation(); - const state = useApplicationState(); - const notifs = state.notifications; - const showNotification = state.settings.get("notifications:desktop"); - - const client = useClient(); - const { guild: guild_id, channel: channel_id } = useParams<{ - guild: string; - channel: string; - }>(); - const history = useHistory(); - - const message = useCallback( - async (msg: Message) => { - if (msg.channel_id === channel_id && document.hasFocus()) return; - if (!notifs.shouldNotify(msg)) return; - - state.settings.sounds.playSound("message"); - if (!showNotification) return; - - const effectiveName = msg.masquerade?.name ?? msg.author?.username; - - let title; - switch (msg.channel?.channel_type) { - case "SavedMessages": - return; - case "DirectMessage": - title = `@${effectiveName}`; - break; - case "Group": - if (msg.author?._id === "00000000000000000000000000") { - title = msg.channel.name; - } else { - title = `@${effectiveName} - ${msg.channel.name}`; - } - break; - case "TextChannel": - title = `@${effectiveName} (#${msg.channel.name}, ${msg.channel.server?.name})`; - break; - default: - title = msg.channel?._id; - break; - } - - let image; - if (msg.attachments) { - const imageAttachment = msg.attachments.find( - (x) => x.metadata.type === "Image", - ); - if (imageAttachment) { - image = client.generateFileURL(imageAttachment, { - max_side: 720, - }); - } - } - - let body, icon; - if (msg.content) { - body = client.markdownToText(msg.content); - - if (msg.masquerade?.avatar) { - icon = client.proxyFile(msg.masquerade.avatar); - } else { - icon = msg.author?.generateAvatarURL({ max_side: 256 }); - } - } else if (msg.system) { - const users = client.users; - - switch (msg.system.type) { - case "user_added": - case "user_remove": - { - const user = users.get(msg.system.id); - body = translate( - `app.main.channel.system.${ - msg.system.type === "user_added" - ? "added_by" - : "removed_by" - }`, - { - user: user?.username, - other_user: users.get(msg.system.by) - ?.username, - }, - ); - icon = user?.generateAvatarURL({ - max_side: 256, - }); - } - break; - case "user_joined": - case "user_left": - case "user_kicked": - case "user_banned": - { - const user = users.get(msg.system.id); - body = translate( - `app.main.channel.system.${msg.system.type}`, - { user: user?.username }, - ); - icon = user?.generateAvatarURL({ - max_side: 256, - }); - } - break; - case "channel_renamed": - { - const user = users.get(msg.system.by); - body = translate( - `app.main.channel.system.channel_renamed`, - { - user: users.get(msg.system.by)?.username, - name: msg.system.name, - }, - ); - icon = user?.generateAvatarURL({ - max_side: 256, - }); - } - break; - case "channel_description_changed": - case "channel_icon_changed": - { - const user = users.get(msg.system.by); - body = translate( - `app.main.channel.system.${msg.system.type}`, - { user: users.get(msg.system.by)?.username }, - ); - icon = user?.generateAvatarURL({ - max_side: 256, - }); - } - break; - } - } - - const notif = await createNotification(title!, { - icon, - image, - body, - timestamp: decodeTime(msg._id), - tag: msg.channel?._id, - badge: "/assets/icons/android-chrome-512x512.png", - silent: true, - }); - - if (notif) { - notif.addEventListener("click", () => { - window.focus(); - const id = msg.channel_id; - if (id !== channel_id) { - const channel = client.channels.get(id); - if (channel) { - if (channel.channel_type === "TextChannel") { - history.push( - `/server/${channel.server_id}/channel/${id}`, - ); - } else { - history.push(`/channel/${id}`); - } - } - } - }); - - notifications[msg.channel_id] = notif; - notif.addEventListener( - "close", - () => delete notifications[msg.channel_id], - ); - } - }, - [ - history, - showNotification, - translate, - channel_id, - client, - notifs, - state, - ], - ); - - const relationship = useCallback( - async (user: User) => { - if (client.user?.status?.presence === "Busy") return; - if (!showNotification) return; - - let event; - switch (user.relationship) { - case "Incoming": - event = translate("notifications.sent_request", { - person: user.username, - }); - break; - case "Friend": - event = translate("notifications.now_friends", { - person: user.username, - }); - break; - default: - return; - } - - const notif = await createNotification(event, { - icon: user.generateAvatarURL({ max_side: 256 }), - badge: "/assets/icons/android-chrome-512x512.png", - timestamp: +new Date(), - }); - - notif?.addEventListener("click", () => { - history.push(`/friends`); - }); - }, - [client.user?.status?.presence, history, showNotification, translate], - ); - - useEffect(() => { - client.addListener("message", message); - client.addListener("user/relationship", relationship); - - return () => { - client.removeListener("message", message); - client.removeListener("user/relationship", relationship); - }; - }, [ - client, - state, - guild_id, - channel_id, - showNotification, - notifs, - message, - relationship, - ]); - - useEffect(() => { - function visChange() { - if (document.visibilityState === "visible") { - if (notifications[channel_id]) { - notifications[channel_id].close(); - } - } - } - - visChange(); - - document.addEventListener("visibilitychange", visChange); - return () => - document.removeEventListener("visibilitychange", visChange); - }, [guild_id, channel_id]); - - return null; -} - -export default function NotificationsComponent() { - return ( - - - - - - - - - - - - ); -} diff --git a/src/mobx/State.ts b/src/mobx/State.ts index b48d9162..02a60e7e 100644 --- a/src/mobx/State.ts +++ b/src/mobx/State.ts @@ -39,6 +39,9 @@ export default class State { locale: LocaleOptions; experiments: Experiments; layout: Layout; + /** + * DEPRECATED + */ private config: ServerConfig; notifications: NotificationOptions; queue: MessageQueue; @@ -61,7 +64,7 @@ export default class State { this.experiments = new Experiments(); this.layout = new Layout(); this.config = new ServerConfig(); - this.notifications = new NotificationOptions(); + this.notifications = new NotificationOptions(this); this.queue = new MessageQueue(); this.settings = new Settings(); this.sync = new Sync(this); @@ -159,6 +162,17 @@ export default class State { // Register listener for incoming packets. client.addListener("packet", this.onPacket); + // Register events for notifications. + client.addListener("message", this.notifications.onMessage); + client.addListener( + "user/relationship", + this.notifications.onRelationship, + ); + document.addEventListener( + "visibilitychange", + this.notifications.onVisibilityChange, + ); + // Sync settings from remote server. state.sync .pull(client) @@ -253,6 +267,15 @@ export default class State { if (client) { client.removeListener("message", this.queue.onMessage); client.removeListener("packet", this.onPacket); + client.removeListener("message", this.notifications.onMessage); + client.removeListener( + "user/relationship", + this.notifications.onRelationship, + ); + document.removeEventListener( + "visibilitychange", + this.notifications.onVisibilityChange, + ); } // Wipe all listeners. @@ -293,7 +316,7 @@ export default class State { this.draft = new Draft(); this.experiments = new Experiments(); this.layout = new Layout(); - this.notifications = new NotificationOptions(); + this.notifications = new NotificationOptions(this); this.queue = new MessageQueue(); this.settings = new Settings(); this.sync = new Sync(this); diff --git a/src/mobx/stores/NotificationOptions.ts b/src/mobx/stores/NotificationOptions.ts index 2b6285a2..91a5fe35 100644 --- a/src/mobx/stores/NotificationOptions.ts +++ b/src/mobx/stores/NotificationOptions.ts @@ -1,8 +1,14 @@ import { action, computed, makeAutoObservable, ObservableMap } from "mobx"; -import { Channel, Message, Server } from "revolt.js"; +import { Channel, Message, Server, User } from "revolt.js"; +import { decodeTime } from "ulid"; + +import { translate } from "preact-i18n"; import { mapToRecord } from "../../lib/conversion"; +import { history, routeInformation } from "../../context/history"; + +import State from "../State"; import Persistent from "../interfaces/Persistent"; import Store from "../interfaces/Store"; import Syncable from "../interfaces/Syncable"; @@ -37,22 +43,54 @@ export interface Data { channel?: Record; } +/** + * Create a notification either directly or using service worker. + * @param title Notification Title + * @param options Notification Options + * @returns Notification + */ +async function createNotification( + title: string, + options: globalThis.NotificationOptions, +) { + try { + return new Notification(title, options); + } catch (err) { + const sw = await navigator.serviceWorker.getRegistration(); + sw?.showNotification(title, options); + } +} + /** * Manages the user's notification preferences. */ export default class NotificationOptions implements Store, Persistent, Syncable { + private state: State; + private activeNotifications: Record; + private server: ObservableMap; private channel: ObservableMap; /** * Construct new Experiments store. */ - constructor() { + constructor(state: State) { this.server = new ObservableMap(); this.channel = new ObservableMap(); - makeAutoObservable(this); + + makeAutoObservable(this, { + onMessage: false, + onRelationship: false, + }); + + this.state = state; + this.activeNotifications = {}; + + this.onMessage = this.onMessage.bind(this); + this.onRelationship = this.onRelationship.bind(this); + this.onVisibilityChange = this.onVisibilityChange.bind(this); } get id() { @@ -209,6 +247,245 @@ export default class NotificationOptions return false; } + /** + * Handle incoming messages and create a notification. + * @param message Message + */ + async onMessage(message: Message) { + // Ignore if we are currently looking and focused on the channel. + if ( + message.channel_id === routeInformation.getChannel() && + document.hasFocus() + ) + return; + + // Ignore if muted. + if (!this.shouldNotify(message)) return; + + // Play a sound and skip notif if disabled. + this.state.settings.sounds.playSound("message"); + if (!this.state.settings.get("notifications:desktop")) return; + + const effectiveName = + message.masquerade?.name ?? message.author?.username; + + let title; + switch (message.channel?.channel_type) { + case "SavedMessages": + return; + case "DirectMessage": + title = `@${effectiveName}`; + break; + case "Group": + if (message.author?._id === "00000000000000000000000000") { + title = message.channel.name; + } else { + title = `@${effectiveName} - ${message.channel.name}`; + } + break; + case "TextChannel": + title = `@${effectiveName} (#${message.channel.name}, ${message.channel.server?.name})`; + break; + default: + title = message.channel?._id; + break; + } + + let image; + if (message.attachments) { + const imageAttachment = message.attachments.find( + (x) => x.metadata.type === "Image", + ); + if (imageAttachment) { + image = message.client.generateFileURL(imageAttachment, { + max_side: 720, + }); + } + } + + let body, icon; + if (message.content) { + body = message.client.markdownToText(message.content); + + if (message.masquerade?.avatar) { + icon = message.client.proxyFile(message.masquerade.avatar); + } else { + icon = message.author?.generateAvatarURL({ max_side: 256 }); + } + } else if (message.system) { + const users = message.client.users; + + // ! FIXME: I've had to strip translations while + // ! I move stuff into the new project structure + switch (message.system.type) { + case "user_added": + case "user_remove": + { + const user = users.get(message.system.id); + body = `${user?.username} ${ + message.system.type === "user_added" + ? "added by" + : "removed by" + } ${users.get(message.system.by)?.username}`; + /*body = translate( + `app.main.channel.system.${ + message.system.type === "user_added" + ? "added_by" + : "removed_by" + }`, + { + user: user?.username, + other_user: users.get(message.system.by) + ?.username, + }, + );*/ + icon = user?.generateAvatarURL({ + max_side: 256, + }); + } + break; + case "user_joined": + case "user_left": + case "user_kicked": + case "user_banned": + { + const user = users.get(message.system.id); + body = `${user?.username}`; + /*body = translate( + `app.main.channel.system.${message.system.type}`, + { user: user?.username }, + );*/ + icon = user?.generateAvatarURL({ + max_side: 256, + }); + } + break; + case "channel_renamed": + { + const user = users.get(message.system.by); + body = `${user?.username} renamed channel to ${message.system.name}`; + /*body = translate( + `app.main.channel.system.channel_renamed`, + { + user: users.get(message.system.by)?.username, + name: message.system.name, + }, + );*/ + icon = user?.generateAvatarURL({ + max_side: 256, + }); + } + break; + case "channel_description_changed": + case "channel_icon_changed": + { + const user = users.get(message.system.by); + /*body = translate( + `app.main.channel.system.${message.system.type}`, + { user: users.get(message.system.by)?.username }, + );*/ + body = `${users.get(message.system.by)?.username}`; + icon = user?.generateAvatarURL({ + max_side: 256, + }); + } + break; + } + } + + const notif = await createNotification(title!, { + icon, + image, + body, + timestamp: decodeTime(message._id), + tag: message.channel?._id, + badge: "/assets/icons/android-chrome-512x512.png", + silent: true, + }); + + if (notif) { + notif.addEventListener("click", () => { + window.focus(); + + const id = message.channel_id; + if (id !== routeInformation.getChannel()) { + const channel = message.client.channels.get(id); + if (channel) { + if (channel.channel_type === "TextChannel") { + history.push( + `/server/${channel.server_id}/channel/${id}`, + ); + } else { + history.push(`/channel/${id}`); + } + } + } + }); + + this.activeNotifications[message.channel_id] = notif; + + notif.addEventListener( + "close", + () => delete this.activeNotifications[message.channel_id], + ); + } + } + + /** + * Handle user relationship changes. + * @param user User relationship changed with + */ + async onRelationship(user: User) { + // Ignore if disabled. + if (!this.state.settings.get("notifications:desktop")) return; + + // Check whether we are busy. + // This is checked by `shouldNotify` in the case of messages. + if (user.status?.presence === "Busy") { + return false; + } + + let event; + switch (user.relationship) { + case "Incoming": + /*event = translate("notifications.sent_request", { + person: user.username, + });*/ + event = `${user.username} sent you a friend request`; + break; + case "Friend": + /*event = translate("notifications.now_friends", { + person: user.username, + });*/ + event = `Now friends with ${user.username}`; + break; + default: + return; + } + + const notif = await createNotification(event, { + icon: user.generateAvatarURL({ max_side: 256 }), + badge: "/assets/icons/android-chrome-512x512.png", + timestamp: +new Date(), + }); + + notif?.addEventListener("click", () => { + history.push(`/friends`); + }); + } + + /** + * Called when document visibility changes. + */ + onVisibilityChange() { + if (document.visibilityState === "visible") { + const channel_id = routeInformation.getChannel()!; + if (this.activeNotifications[channel_id]) { + this.activeNotifications[channel_id].close(); + } + } + } + @action apply(_key: "notifications", data: unknown, _revision: number) { this.hydrate(data as Data); } diff --git a/src/pages/RevoltApp.tsx b/src/pages/RevoltApp.tsx index 69ccf6f5..c04917ab 100644 --- a/src/pages/RevoltApp.tsx +++ b/src/pages/RevoltApp.tsx @@ -8,7 +8,6 @@ import ContextMenus from "../lib/ContextMenus"; import { isTouchscreenDevice } from "../lib/isTouchscreenDevice"; import Popovers from "../context/intermediate/Popovers"; -import Notifications from "../context/revoltjs/Notifications"; import { Titlebar } from "../components/native/Titlebar"; import BottomNavigation from "../components/navigation/BottomNavigation"; @@ -227,7 +226,6 @@ export default function App() { - From 8501e331039ad618135278b12fa6ff4c90e885b5 Mon Sep 17 00:00:00 2001 From: Paul Makles Date: Wed, 29 Jun 2022 17:33:23 +0100 Subject: [PATCH 111/151] chore(refactor): move `RequiresOnline` into controllers --- src/components/common/messaging/attachments/TextFile.tsx | 3 +-- .../revoltjs => controllers/client/jsx}/RequiresOnline.tsx | 2 +- src/pages/channels/messaging/MessageArea.tsx | 2 +- src/pages/channels/messaging/MessageRenderer.tsx | 3 +-- src/pages/invite/Invite.tsx | 2 +- src/pages/settings/ServerSettings.tsx | 2 +- src/pages/settings/Settings.tsx | 2 +- 7 files changed, 7 insertions(+), 9 deletions(-) rename src/{context/revoltjs => controllers/client/jsx}/RequiresOnline.tsx (93%) diff --git a/src/components/common/messaging/attachments/TextFile.tsx b/src/components/common/messaging/attachments/TextFile.tsx index 1dd2940b..ff417f89 100644 --- a/src/components/common/messaging/attachments/TextFile.tsx +++ b/src/components/common/messaging/attachments/TextFile.tsx @@ -7,9 +7,8 @@ import { useEffect, useState } from "preact/hooks"; import { Button, Preloader } from "@revoltchat/ui"; -import RequiresOnline from "../../../../context/revoltjs/RequiresOnline"; - import { useClient } from "../../../../controllers/client/ClientController"; +import RequiresOnline from "../../../../controllers/client/jsx/RequiresOnline"; interface Props { attachment: API.File; diff --git a/src/context/revoltjs/RequiresOnline.tsx b/src/controllers/client/jsx/RequiresOnline.tsx similarity index 93% rename from src/context/revoltjs/RequiresOnline.tsx rename to src/controllers/client/jsx/RequiresOnline.tsx index 1cc65038..d8ed18ec 100644 --- a/src/context/revoltjs/RequiresOnline.tsx +++ b/src/controllers/client/jsx/RequiresOnline.tsx @@ -5,7 +5,7 @@ import { Text } from "preact-i18n"; import { Preloader } from "@revoltchat/ui"; -import { useSession } from "../../controllers/client/ClientController"; +import { useSession } from "../ClientController"; interface Props { children: Children; diff --git a/src/pages/channels/messaging/MessageArea.tsx b/src/pages/channels/messaging/MessageArea.tsx index b74a9ad9..c35878d9 100644 --- a/src/pages/channels/messaging/MessageArea.tsx +++ b/src/pages/channels/messaging/MessageArea.tsx @@ -24,9 +24,9 @@ import { getRenderer } from "../../../lib/renderer/Singleton"; import { ScrollState } from "../../../lib/renderer/types"; import { IntermediateContext } from "../../../context/intermediate/Intermediate"; -import RequiresOnline from "../../../context/revoltjs/RequiresOnline"; import { useSession } from "../../../controllers/client/ClientController"; +import RequiresOnline from "../../../controllers/client/jsx/RequiresOnline"; import ConversationStart from "./ConversationStart"; import MessageRenderer from "./MessageRenderer"; diff --git a/src/pages/channels/messaging/MessageRenderer.tsx b/src/pages/channels/messaging/MessageRenderer.tsx index fa90cf0e..1523e9e7 100644 --- a/src/pages/channels/messaging/MessageRenderer.tsx +++ b/src/pages/channels/messaging/MessageRenderer.tsx @@ -17,11 +17,10 @@ import { ChannelRenderer } from "../../../lib/renderer/Singleton"; import { useApplicationState } from "../../../mobx/State"; -import RequiresOnline from "../../../context/revoltjs/RequiresOnline"; - import Message from "../../../components/common/messaging/Message"; import { SystemMessage } from "../../../components/common/messaging/SystemMessage"; import { useClient } from "../../../controllers/client/ClientController"; +import RequiresOnline from "../../../controllers/client/jsx/RequiresOnline"; import ConversationStart from "./ConversationStart"; import MessageEditor from "./MessageEditor"; diff --git a/src/pages/invite/Invite.tsx b/src/pages/invite/Invite.tsx index 1b28a9f0..d7307efb 100644 --- a/src/pages/invite/Invite.tsx +++ b/src/pages/invite/Invite.tsx @@ -12,7 +12,6 @@ import { TextReact } from "../../lib/i18n"; import { useApplicationState } from "../../mobx/State"; -import RequiresOnline from "../../context/revoltjs/RequiresOnline"; import { takeError } from "../../context/revoltjs/util"; import ServerIcon from "../../components/common/ServerIcon"; @@ -21,6 +20,7 @@ import { useClient, useSession, } from "../../controllers/client/ClientController"; +import RequiresOnline from "../../controllers/client/jsx/RequiresOnline"; export default function Invite() { const history = useHistory(); diff --git a/src/pages/settings/ServerSettings.tsx b/src/pages/settings/ServerSettings.tsx index 5448250b..babd2755 100644 --- a/src/pages/settings/ServerSettings.tsx +++ b/src/pages/settings/ServerSettings.tsx @@ -16,10 +16,10 @@ import { Text } from "preact-i18n"; import { LineDivider } from "@revoltchat/ui"; import { useIntermediate } from "../../context/intermediate/Intermediate"; -import RequiresOnline from "../../context/revoltjs/RequiresOnline"; import ButtonItem from "../../components/navigation/items/ButtonItem"; import { useClient } from "../../controllers/client/ClientController"; +import RequiresOnline from "../../controllers/client/jsx/RequiresOnline"; import { GenericSettings } from "./GenericSettings"; import { Bans } from "./server/Bans"; import { Categories } from "./server/Categories"; diff --git a/src/pages/settings/Settings.tsx b/src/pages/settings/Settings.tsx index a196553a..cc697ccc 100644 --- a/src/pages/settings/Settings.tsx +++ b/src/pages/settings/Settings.tsx @@ -34,7 +34,6 @@ import { LineDivider } from "@revoltchat/ui"; import { useApplicationState } from "../../mobx/State"; import { useIntermediate } from "../../context/intermediate/Intermediate"; -import RequiresOnline from "../../context/revoltjs/RequiresOnline"; import UserIcon from "../../components/common/user/UserIcon"; import { Username } from "../../components/common/user/UserShort"; @@ -44,6 +43,7 @@ import { useClient, clientController, } from "../../controllers/client/ClientController"; +import RequiresOnline from "../../controllers/client/jsx/RequiresOnline"; import { modalController } from "../../controllers/modals/ModalController"; import { GIT_BRANCH, GIT_REVISION, REPO_URL } from "../../revision"; import { APP_VERSION } from "../../version"; From 1664aaee1529edc777ca9a0659125d65b858cd07 Mon Sep 17 00:00:00 2001 From: Paul Makles Date: Thu, 30 Jun 2022 19:06:49 +0100 Subject: [PATCH 112/151] feat: add `ServerInfo`, port `ChannelInfo` --- external/lang | 2 +- src/components/common/ServerHeader.tsx | 12 ++++- src/components/markdown/Markdown.module.scss | 2 + src/context/intermediate/Popovers.tsx | 4 -- src/controllers/modals/ModalController.tsx | 13 +++++ .../modals/components/ChannelInfo.tsx | 29 +++++++++++ .../modals/components/ServerInfo.tsx | 48 +++++++++++++++++++ src/controllers/modals/types.ts | 10 +++- src/controllers/safety/index.ts | 16 +++++++ src/pages/channels/ChannelHeader.tsx | 11 ++--- 10 files changed, 133 insertions(+), 14 deletions(-) create mode 100644 src/controllers/modals/components/ChannelInfo.tsx create mode 100644 src/controllers/modals/components/ServerInfo.tsx create mode 100644 src/controllers/safety/index.ts diff --git a/external/lang b/external/lang index 50838167..d4bc47b7 160000 --- a/external/lang +++ b/external/lang @@ -1 +1 @@ -Subproject commit 50838167d7d253de9d08715e6a6070c3ddc9fcc2 +Subproject commit d4bc47b729c7e69ce97216469692b39f4cd1640e diff --git a/src/components/common/ServerHeader.tsx b/src/components/common/ServerHeader.tsx index 0c5b3f0e..69ff5a7b 100644 --- a/src/components/common/ServerHeader.tsx +++ b/src/components/common/ServerHeader.tsx @@ -9,6 +9,7 @@ import { Text } from "preact-i18n"; import { IconButton } from "@revoltchat/ui"; +import { modalController } from "../../controllers/modals/ModalController"; import Tooltip from "./Tooltip"; interface Props { @@ -60,6 +61,9 @@ const ServerBanner = styled.div>` overflow: hidden; text-overflow: ellipsis; flex-grow: 1; + + cursor: pointer; + color: var(--foreground); } } `; @@ -121,7 +125,13 @@ export default observer(({ server }: Props) => { ) : undefined} -
{server.name}
+ + modalController.push({ type: "server_info", server }) + }> + {server.name} + {server.havePermission("ManageServer") && ( diff --git a/src/components/markdown/Markdown.module.scss b/src/components/markdown/Markdown.module.scss index c107fcfa..cff232a1 100644 --- a/src/components/markdown/Markdown.module.scss +++ b/src/components/markdown/Markdown.module.scss @@ -1,4 +1,6 @@ .markdown { + user-select: text; + :global(.emoji) { object-fit: contain; diff --git a/src/context/intermediate/Popovers.tsx b/src/context/intermediate/Popovers.tsx index f5d8f347..0dc85eed 100644 --- a/src/context/intermediate/Popovers.tsx +++ b/src/context/intermediate/Popovers.tsx @@ -3,7 +3,6 @@ import { useContext } from "preact/hooks"; import { IntermediateContext, useIntermediate } from "./Intermediate"; import { SpecialInputModal } from "./modals/Input"; import { SpecialPromptModal } from "./modals/Prompt"; -import { ChannelInfo } from "./popovers/ChannelInfo"; import { CreateBotModal } from "./popovers/CreateBot"; import { ImageViewer } from "./popovers/ImageViewer"; import { UserPicker } from "./popovers/UserPicker"; @@ -27,9 +26,6 @@ export default function Popovers() { return ; case "image_viewer": return ; - case "channel_info": - // @ts-expect-error someone figure this out :) - return ; case "create_bot": // @ts-expect-error someone figure this out :) return ; diff --git a/src/controllers/modals/ModalController.tsx b/src/controllers/modals/ModalController.tsx index 8102cab8..3f78b7bc 100644 --- a/src/controllers/modals/ModalController.tsx +++ b/src/controllers/modals/ModalController.tsx @@ -18,6 +18,7 @@ import { __thisIsAHack } from "../../context/intermediate/Intermediate"; // import { determineLink } from "../../lib/links"; import Changelog from "./components/Changelog"; +import ChannelInfo from "./components/ChannelInfo"; import Clipboard from "./components/Clipboard"; import Error from "./components/Error"; import LinkWarning from "./components/LinkWarning"; @@ -28,6 +29,7 @@ import ModifyAccount from "./components/ModifyAccount"; import OutOfDate from "./components/OutOfDate"; import PendingFriendRequests from "./components/PendingFriendRequests"; import ServerIdentity from "./components/ServerIdentity"; +import ServerInfo from "./components/ServerInfo"; import ShowToken from "./components/ShowToken"; import SignOutSessions from "./components/SignOutSessions"; import SignedOut from "./components/SignedOut"; @@ -54,6 +56,8 @@ class ModalController { isVisible: computed, }); + this.close = this.close.bind(this); + // Inject globally injectController("modal", this); } @@ -82,6 +86,13 @@ class ModalController { ); } + /** + * Close the top modal + */ + close() { + this.pop("close"); + } + /** * Remove the keyed modal from the stack */ @@ -208,6 +219,7 @@ class ModalControllerExtended extends ModalController { export const modalController = new ModalControllerExtended({ changelog: Changelog, + channel_info: ChannelInfo, clipboard: Clipboard, error: Error, link_warning: LinkWarning, @@ -218,6 +230,7 @@ export const modalController = new ModalControllerExtended({ out_of_date: OutOfDate, pending_friend_requests: PendingFriendRequests, server_identity: ServerIdentity, + server_info: ServerInfo, show_token: ShowToken, signed_out: SignedOut, sign_out_sessions: SignOutSessions, diff --git a/src/controllers/modals/components/ChannelInfo.tsx b/src/controllers/modals/components/ChannelInfo.tsx new file mode 100644 index 00000000..0e2149b0 --- /dev/null +++ b/src/controllers/modals/components/ChannelInfo.tsx @@ -0,0 +1,29 @@ +import { X } from "@styled-icons/boxicons-regular"; + +import { Column, H1, IconButton, Modal, Row } from "@revoltchat/ui"; + +import Markdown from "../../../components/markdown/Markdown"; +import { modalController } from "../ModalController"; +import { ModalProps } from "../types"; + +export default function ChannelInfo({ + channel, + ...props +}: ModalProps<"channel_info">) { + return ( + + +

{`#${channel.name}`}

+
+ + + + + }> + +
+ ); +} diff --git a/src/controllers/modals/components/ServerInfo.tsx b/src/controllers/modals/components/ServerInfo.tsx new file mode 100644 index 00000000..786026ad --- /dev/null +++ b/src/controllers/modals/components/ServerInfo.tsx @@ -0,0 +1,48 @@ +import { X } from "@styled-icons/boxicons-regular"; + +import { Text } from "preact-i18n"; + +import { Column, H1, IconButton, Modal, Row } from "@revoltchat/ui"; + +import Markdown from "../../../components/markdown/Markdown"; +import { report } from "../../safety"; +import { modalController } from "../ModalController"; +import { ModalProps } from "../types"; + +export default function ServerInfo({ + server, + ...props +}: ModalProps<"server_info">) { + return ( + + +

{server.name}

+
+ + + + + } + actions={[ + { + onClick: () => + modalController.push({ + type: "server_identity", + member: server.member!, + }), + children: "Edit Identity", + palette: "primary", + }, + { + onClick: () => report(server), + children: , + palette: "error", + }, + ]}> + +
+ ); +} diff --git a/src/controllers/modals/types.ts b/src/controllers/modals/types.ts index d95297ac..160673af 100644 --- a/src/controllers/modals/types.ts +++ b/src/controllers/modals/types.ts @@ -1,4 +1,4 @@ -import { API, Client, User, Member } from "revolt.js"; +import { API, Client, User, Member, Channel, Server } from "revolt.js"; export type Modal = { key?: string; @@ -72,6 +72,14 @@ export type Modal = { | { type: "signed_out"; } + | { + type: "channel_info"; + channel: Channel; + } + | { + type: "server_info"; + server: Server; + } ); export type ModalProps = Modal & { type: T } & { diff --git a/src/controllers/safety/index.ts b/src/controllers/safety/index.ts new file mode 100644 index 00000000..bfcb2471 --- /dev/null +++ b/src/controllers/safety/index.ts @@ -0,0 +1,16 @@ +import { Server } from "revolt.js"; + +export function report(object: Server) { + let type; + if (object instanceof Server) { + type = "Server"; + } + + window.open( + `mailto:abuse@revolt.chat?subject=${encodeURIComponent( + `${type} Report`, + )}&body=${encodeURIComponent( + `${type} ID: ${object._id}\nWrite more information here!`, + )}`, + ); +} diff --git a/src/pages/channels/ChannelHeader.tsx b/src/pages/channels/ChannelHeader.tsx index 46860e4b..e2def382 100644 --- a/src/pages/channels/ChannelHeader.tsx +++ b/src/pages/channels/ChannelHeader.tsx @@ -1,19 +1,18 @@ import { At, Hash } from "@styled-icons/boxicons-regular"; import { Notepad, Group } from "@styled-icons/boxicons-solid"; import { observer } from "mobx-react-lite"; -import { Channel } from "revolt.js"; -import { User } from "revolt.js"; +import { Channel, User } from "revolt.js"; import styled from "styled-components/macro"; import { isTouchscreenDevice } from "../../lib/isTouchscreenDevice"; -import { useIntermediate } from "../../context/intermediate/Intermediate"; import { getChannelName } from "../../context/revoltjs/util"; import { useStatusColour } from "../../components/common/user/UserIcon"; import UserStatus from "../../components/common/user/UserStatus"; import Markdown from "../../components/markdown/Markdown"; import { PageHeader } from "../../components/ui/Header"; +import { modalController } from "../../controllers/modals/ModalController"; import HeaderActions from "./actions/HeaderActions"; export interface ChannelHeaderProps { @@ -65,8 +64,6 @@ const Info = styled.div` `; export default observer(({ channel }: ChannelHeaderProps) => { - const { openScreen } = useIntermediate(); - const name = getChannelName(channel); let icon, recipient: User | undefined; switch (channel.channel_type) { @@ -114,8 +111,8 @@ export default observer(({ channel }: ChannelHeaderProps) => { - openScreen({ - id: "channel_info", + modalController.push({ + type: "channel_info", channel, }) }> From 0d3f29515e3ec8f1aab179914aa6634d323b2ded Mon Sep 17 00:00:00 2001 From: Paul Makles Date: Thu, 30 Jun 2022 19:34:04 +0100 Subject: [PATCH 113/151] feat: port `ImageViewer` --- .../messaging/attachments/ImageFile.tsx | 10 ++-- .../common/messaging/embed/Embed.tsx | 7 ++- .../common/messaging/embed/EmbedMedia.tsx | 8 +-- src/context/intermediate/Popovers.tsx | 3 - .../popovers/ChannelInfo.module.scss | 16 ------ .../intermediate/popovers/ChannelInfo.tsx | 41 -------------- .../popovers/ImageViewer.module.scss | 20 ------- .../intermediate/popovers/UserProfile.tsx | 7 ++- src/controllers/modals/ModalController.tsx | 2 + .../modals/components}/ImageViewer.tsx | 55 ++++++++++++------- src/controllers/modals/types.ts | 5 ++ 11 files changed, 58 insertions(+), 116 deletions(-) delete mode 100644 src/context/intermediate/popovers/ChannelInfo.module.scss delete mode 100644 src/context/intermediate/popovers/ChannelInfo.tsx delete mode 100644 src/context/intermediate/popovers/ImageViewer.module.scss rename src/{context/intermediate/popovers => controllers/modals/components}/ImageViewer.tsx (58%) diff --git a/src/components/common/messaging/attachments/ImageFile.tsx b/src/components/common/messaging/attachments/ImageFile.tsx index 76723e20..7c2364d2 100644 --- a/src/components/common/messaging/attachments/ImageFile.tsx +++ b/src/components/common/messaging/attachments/ImageFile.tsx @@ -2,11 +2,10 @@ import { API } from "revolt.js"; import styles from "./Attachment.module.scss"; import classNames from "classnames"; -import { useContext, useState } from "preact/hooks"; - -import { useIntermediate } from "../../../../context/intermediate/Intermediate"; +import { useState } from "preact/hooks"; import { useClient } from "../../../../controllers/client/ClientController"; +import { modalController } from "../../../../controllers/modals/ModalController"; enum ImageLoadingState { Loading, @@ -21,7 +20,6 @@ type Props = JSX.HTMLAttributes & { export default function ImageFile({ attachment, ...props }: Props) { const [loading, setLoading] = useState(ImageLoadingState.Loading); const client = useClient(); - const { openScreen } = useIntermediate(); const url = client.generateFileURL(attachment)!; return ( @@ -33,7 +31,9 @@ export default function ImageFile({ attachment, ...props }: Props) { className={classNames(styles.image, { [styles.loading]: loading !== ImageLoadingState.Loaded, })} - onClick={() => openScreen({ id: "image_viewer", attachment })} + onClick={() => + modalController.push({ type: "image_viewer", attachment }) + } onMouseDown={(ev) => ev.button === 1 && window.open(url, "_blank")} onLoad={() => setLoading(ImageLoadingState.Loaded)} onError={() => setLoading(ImageLoadingState.Error)} diff --git a/src/components/common/messaging/embed/Embed.tsx b/src/components/common/messaging/embed/Embed.tsx index 306f6441..822f30b2 100644 --- a/src/components/common/messaging/embed/Embed.tsx +++ b/src/components/common/messaging/embed/Embed.tsx @@ -7,6 +7,7 @@ import { useContext } from "preact/hooks"; import { useIntermediate } from "../../../../context/intermediate/Intermediate"; import { useClient } from "../../../../controllers/client/ClientController"; +import { modalController } from "../../../../controllers/modals/ModalController"; import { MessageAreaWidthContext } from "../../../../pages/channels/messaging/MessageArea"; import Markdown from "../../../markdown/Markdown"; import Attachment from "../attachments/Attachment"; @@ -24,7 +25,7 @@ const MAX_PREVIEW_SIZE = 150; export default function Embed({ embed }: Props) { const client = useClient(); - const { openScreen, openLink } = useIntermediate(); + const { openLink } = useIntermediate(); const maxWidth = Math.min( useContext(MessageAreaWidthContext) - CONTAINER_PADDING, MAX_EMBED_WIDTH, @@ -191,7 +192,9 @@ export default function Embed({ embed }: Props) { type="text/html" frameBorder="0" loading="lazy" - onClick={() => openScreen({ id: "image_viewer", embed })} + onClick={() => + modalController.push({ type: "image_viewer", embed }) + } onMouseDown={(ev) => ev.button === 1 && openLink(embed.url)} /> ); diff --git a/src/components/common/messaging/embed/EmbedMedia.tsx b/src/components/common/messaging/embed/EmbedMedia.tsx index a719f0e3..201b3d84 100644 --- a/src/components/common/messaging/embed/EmbedMedia.tsx +++ b/src/components/common/messaging/embed/EmbedMedia.tsx @@ -3,9 +3,8 @@ import { API } from "revolt.js"; import styles from "./Embed.module.scss"; -import { useIntermediate } from "../../../../context/intermediate/Intermediate"; - import { useClient } from "../../../../controllers/client/ClientController"; +import { modalController } from "../../../../controllers/modals/ModalController"; interface Props { embed: API.Embed; @@ -15,7 +14,6 @@ interface Props { export default function EmbedMedia({ embed, width, height }: Props) { if (embed.type !== "Website") return null; - const { openScreen } = useIntermediate(); const client = useClient(); switch (embed.special?.type) { @@ -118,8 +116,8 @@ export default function EmbedMedia({ embed, width, height }: Props) { loading="lazy" style={{ width, height }} onClick={() => - openScreen({ - id: "image_viewer", + modalController.push({ + type: "image_viewer", embed: embed.image!, }) } diff --git a/src/context/intermediate/Popovers.tsx b/src/context/intermediate/Popovers.tsx index 0dc85eed..20789cfd 100644 --- a/src/context/intermediate/Popovers.tsx +++ b/src/context/intermediate/Popovers.tsx @@ -4,7 +4,6 @@ import { IntermediateContext, useIntermediate } from "./Intermediate"; import { SpecialInputModal } from "./modals/Input"; import { SpecialPromptModal } from "./modals/Prompt"; import { CreateBotModal } from "./popovers/CreateBot"; -import { ImageViewer } from "./popovers/ImageViewer"; import { UserPicker } from "./popovers/UserPicker"; import { UserProfile } from "./popovers/UserProfile"; @@ -24,8 +23,6 @@ export default function Popovers() { case "user_picker": // @ts-expect-error someone figure this out :) return ; - case "image_viewer": - return ; case "create_bot": // @ts-expect-error someone figure this out :) return ; diff --git a/src/context/intermediate/popovers/ChannelInfo.module.scss b/src/context/intermediate/popovers/ChannelInfo.module.scss deleted file mode 100644 index ff37d169..00000000 --- a/src/context/intermediate/popovers/ChannelInfo.module.scss +++ /dev/null @@ -1,16 +0,0 @@ -.info { - .header { - display: flex; - align-items: center; - flex-direction: row; - - h1 { - margin: 0; - flex-grow: 1; - } - - div { - cursor: pointer; - } - } -} diff --git a/src/context/intermediate/popovers/ChannelInfo.tsx b/src/context/intermediate/popovers/ChannelInfo.tsx deleted file mode 100644 index d2a37f42..00000000 --- a/src/context/intermediate/popovers/ChannelInfo.tsx +++ /dev/null @@ -1,41 +0,0 @@ -import { X } from "@styled-icons/boxicons-regular"; -import { observer } from "mobx-react-lite"; -import { Channel } from "revolt.js"; - -import styles from "./ChannelInfo.module.scss"; - -import { Modal } from "@revoltchat/ui"; - -import Markdown from "../../../components/markdown/Markdown"; -import { getChannelName } from "../../revoltjs/util"; - -interface Props { - channel: Channel; - onClose: () => void; -} - -export const ChannelInfo = observer(({ channel, onClose }: Props) => { - if ( - channel.channel_type === "DirectMessage" || - channel.channel_type === "SavedMessages" - ) { - onClose(); - return null; - } - - return ( - -
-
-

{getChannelName(channel, true)}

-
- -
-
-

- -

-
-
- ); -}); diff --git a/src/context/intermediate/popovers/ImageViewer.module.scss b/src/context/intermediate/popovers/ImageViewer.module.scss deleted file mode 100644 index a450d0a7..00000000 --- a/src/context/intermediate/popovers/ImageViewer.module.scss +++ /dev/null @@ -1,20 +0,0 @@ -.viewer { - display: flex; - overflow: hidden; - flex-direction: column; - border-end-end-radius: 4px; - border-end-start-radius: 4px; - - max-width: 100vw; - - img { - width: auto; - height: auto; - max-width: 90vw; - max-height: 75vh; - object-fit: contain; - border-bottom: thin solid var(--tertiary-foreground); - - -webkit-touch-callout: default; - } -} diff --git a/src/context/intermediate/popovers/UserProfile.tsx b/src/context/intermediate/popovers/UserProfile.tsx index 5a4eb4c0..764a3b34 100644 --- a/src/context/intermediate/popovers/UserProfile.tsx +++ b/src/context/intermediate/popovers/UserProfile.tsx @@ -13,7 +13,7 @@ import { UserPermission, API } from "revolt.js"; import styles from "./UserProfile.module.scss"; import { Localizer, Text } from "preact-i18n"; -import { useContext, useEffect, useLayoutEffect, useState } from "preact/hooks"; +import { useEffect, useLayoutEffect, useState } from "preact/hooks"; import { Button, @@ -35,6 +35,7 @@ import { Username } from "../../../components/common/user/UserShort"; import UserStatus from "../../../components/common/user/UserStatus"; import Markdown from "../../../components/markdown/Markdown"; import { useSession } from "../../../controllers/client/ClientController"; +import { modalController } from "../../../controllers/modals/ModalController"; import { useIntermediate } from "../Intermediate"; interface Props { @@ -159,8 +160,8 @@ export const UserProfile = observer( hover={typeof user.avatar !== "undefined"} onClick={() => user.avatar && - openScreen({ - id: "image_viewer", + modalController.push({ + type: "image_viewer", attachment: user.avatar, }) } diff --git a/src/controllers/modals/ModalController.tsx b/src/controllers/modals/ModalController.tsx index 3f78b7bc..cc6e7f9a 100644 --- a/src/controllers/modals/ModalController.tsx +++ b/src/controllers/modals/ModalController.tsx @@ -21,6 +21,7 @@ import Changelog from "./components/Changelog"; import ChannelInfo from "./components/ChannelInfo"; import Clipboard from "./components/Clipboard"; import Error from "./components/Error"; +import ImageViewer from "./components/ImageViewer"; import LinkWarning from "./components/LinkWarning"; import MFAEnableTOTP from "./components/MFAEnableTOTP"; import MFAFlow from "./components/MFAFlow"; @@ -222,6 +223,7 @@ export const modalController = new ModalControllerExtended({ channel_info: ChannelInfo, clipboard: Clipboard, error: Error, + image_viewer: ImageViewer, link_warning: LinkWarning, mfa_flow: MFAFlow, mfa_recovery: MFARecovery, diff --git a/src/context/intermediate/popovers/ImageViewer.tsx b/src/controllers/modals/components/ImageViewer.tsx similarity index 58% rename from src/context/intermediate/popovers/ImageViewer.tsx rename to src/controllers/modals/components/ImageViewer.tsx index 12a17f3c..526ec9c4 100644 --- a/src/context/intermediate/popovers/ImageViewer.tsx +++ b/src/controllers/modals/components/ImageViewer.tsx @@ -1,23 +1,40 @@ -/* eslint-disable react-hooks/rules-of-hooks */ -import { API } from "revolt.js"; - -import styles from "./ImageViewer.module.scss"; +import styled from "styled-components"; import { Modal } from "@revoltchat/ui"; import AttachmentActions from "../../../components/common/messaging/attachments/AttachmentActions"; import EmbedMediaActions from "../../../components/common/messaging/embed/EmbedMediaActions"; -import { useClient } from "../../../controllers/client/ClientController"; +import { useClient } from "../../client/ClientController"; +import { ModalProps } from "../types"; -interface Props { - onClose: () => void; - embed?: API.Image; - attachment?: API.File; -} +const Viewer = styled.div` + display: flex; + overflow: hidden; + flex-direction: column; + border-end-end-radius: 4px; + border-end-start-radius: 4px; -type ImageMetadata = API.Metadata & { type: "Image" }; + max-width: 100vw; + + img { + width: auto; + height: auto; + max-width: 90vw; + max-height: 75vh; + object-fit: contain; + border-bottom: thin solid var(--tertiary-foreground); + + -webkit-touch-callout: default; + } +`; + +export default function ImageViewer({ + embed, + attachment, + ...props +}: ModalProps<"image_viewer">) { + const client = useClient(); -export function ImageViewer({ attachment, embed, onClose }: Props) { if (attachment && attachment.metadata.type !== "Image") { console.warn( `Attempted to use a non valid attatchment type in the image viewer: ${attachment.metadata.type}`, @@ -25,20 +42,16 @@ export function ImageViewer({ attachment, embed, onClose }: Props) { return null; } - const client = useClient(); - return ( - -
+ + {attachment && ( <> @@ -54,7 +67,7 @@ export function ImageViewer({ attachment, embed, onClose }: Props) { )} -
+
); } diff --git a/src/controllers/modals/types.ts b/src/controllers/modals/types.ts index 160673af..e2e3b854 100644 --- a/src/controllers/modals/types.ts +++ b/src/controllers/modals/types.ts @@ -80,6 +80,11 @@ export type Modal = { type: "server_info"; server: Server; } + | { + type: "image_viewer"; + embed?: API.Image; + attachment?: API.File; + } ); export type ModalProps = Modal & { type: T } & { From 401b2d4990ae3b2d3b17fd266d916048bf0b1346 Mon Sep 17 00:00:00 2001 From: Paul Makles Date: Thu, 30 Jun 2022 20:39:00 +0100 Subject: [PATCH 114/151] feat: port `UserProfile`, `Onboarding`, `CreateBot` to legacy --- src/components/common/messaging/Message.tsx | 7 ++- src/components/common/user/UserShort.tsx | 7 +-- .../navigation/right/MemberList.tsx | 40 ++++--------- src/context/intermediate/Intermediate.tsx | 2 +- src/context/intermediate/Modals.tsx | 3 - src/context/intermediate/Popovers.tsx | 12 ---- src/controllers/client/Session.tsx | 6 +- src/controllers/modals/ModalController.tsx | 11 +++- .../modals/components}/UserPicker.tsx | 39 +++++++----- .../modals/components/legacy}/CreateBot.tsx | 27 +++++---- .../components/legacy}/Onboarding.module.scss | 9 ++- .../modals/components/legacy}/Onboarding.tsx | 18 +++--- .../legacy}/UserProfile.module.scss | 0 .../modals/components/legacy}/UserProfile.tsx | 60 +++++++++---------- src/controllers/modals/types.ts | 22 +++++++ src/lib/ContextMenus.tsx | 5 +- src/pages/channels/actions/HeaderActions.tsx | 5 +- src/pages/friends/Friend.tsx | 8 ++- src/pages/settings/panes/Profile.tsx | 3 +- 19 files changed, 155 insertions(+), 129 deletions(-) rename src/{context/intermediate/popovers => controllers/modals/components}/UserPicker.tsx (67%) rename src/{context/intermediate/popovers => controllers/modals/components/legacy}/CreateBot.tsx (79%) rename src/{context/intermediate/modals => controllers/modals/components/legacy}/Onboarding.module.scss (87%) rename src/{context/intermediate/modals => controllers/modals/components/legacy}/Onboarding.tsx (87%) rename src/{context/intermediate/popovers => controllers/modals/components/legacy}/UserProfile.module.scss (100%) rename src/{context/intermediate/popovers => controllers/modals/components/legacy}/UserProfile.tsx (92%) diff --git a/src/components/common/messaging/Message.tsx b/src/components/common/messaging/Message.tsx index 3e15f001..8f684141 100644 --- a/src/components/common/messaging/Message.tsx +++ b/src/components/common/messaging/Message.tsx @@ -15,7 +15,7 @@ import { QueuedMessage } from "../../../mobx/stores/MessageQueue"; import { I18nError } from "../../../context/Locale"; import { useIntermediate } from "../../../context/intermediate/Intermediate"; -import { useClient } from "../../../controllers/client/ClientController"; +import { modalController } from "../../../controllers/modals/ModalController"; import Markdown from "../../markdown/Markdown"; import UserIcon from "../user/UserIcon"; import { Username } from "../user/UserShort"; @@ -70,7 +70,10 @@ const Message = observer( : undefined; const openProfile = () => - openScreen({ id: "profile", user_id: message.author_id }); + modalController.push({ + type: "user_profile", + user_id: message.author_id, + }); const handleUserClick = (e: MouseEvent) => { if (e.shiftKey && user?._id) { diff --git a/src/components/common/user/UserShort.tsx b/src/components/common/user/UserShort.tsx index 5ac03d19..bbe3670a 100644 --- a/src/components/common/user/UserShort.tsx +++ b/src/components/common/user/UserShort.tsx @@ -8,9 +8,8 @@ import { Text } from "preact-i18n"; import { internalEmit } from "../../../lib/eventEmitter"; -import { useIntermediate } from "../../../context/intermediate/Intermediate"; - import { useClient } from "../../../controllers/client/ClientController"; +import { modalController } from "../../../controllers/modals/ModalController"; import UserIcon from "./UserIcon"; const BotBadge = styled.div` @@ -125,9 +124,9 @@ export default function UserShort({ masquerade?: API.Masquerade; showServerIdentity?: boolean; }) { - const { openScreen } = useIntermediate(); const openProfile = () => - user && openScreen({ id: "profile", user_id: user._id }); + user && + modalController.push({ type: "user_profile", user_id: user._id }); const handleUserClick = (e: MouseEvent) => { if (e.shiftKey && user?._id) { diff --git a/src/components/navigation/right/MemberList.tsx b/src/components/navigation/right/MemberList.tsx index 8b9495ba..3913c20b 100644 --- a/src/components/navigation/right/MemberList.tsx +++ b/src/components/navigation/right/MemberList.tsx @@ -8,11 +8,7 @@ import { memo } from "preact/compat"; import { internalEmit } from "../../../lib/eventEmitter"; -import { - Screen, - useIntermediate, -} from "../../../context/intermediate/Intermediate"; - +import { modalController } from "../../../controllers/modals/ModalController"; import { UserButton } from "../items/ButtonItem"; export type MemberListGroup = { @@ -55,15 +51,7 @@ const NoOomfie = styled.div` `; const ItemContent = memo( - ({ - item, - context, - openScreen, - }: { - item: User; - context: Channel; - openScreen: (screen: Screen) => void; - }) => ( + ({ item, context }: { item: User; context: Channel }) => ( `, "mention", ); - } else - [ - openScreen({ - id: "profile", - user_id: item._id, - }), - ]; + } else { + modalController.push({ + type: "user_profile", + user_id: item._id, + }); + } }} /> ), @@ -96,8 +83,6 @@ export default function MemberList({ entries: MemberListGroup[]; context: Channel; }) { - const { openScreen } = useIntermediate(); - return ( x.users.length)} @@ -137,7 +122,8 @@ export default function MemberList({ server, see issue{" "} + target="_blank" + rel="noreferrer"> #128 {" "} for when this will be resolved. @@ -158,11 +144,7 @@ export default function MemberList({ return (
- +
); }} diff --git a/src/context/intermediate/Intermediate.tsx b/src/context/intermediate/Intermediate.tsx index 32df0c1b..e67ca0ae 100644 --- a/src/context/intermediate/Intermediate.tsx +++ b/src/context/intermediate/Intermediate.tsx @@ -159,7 +159,7 @@ export default function Intermediate(props: Props) { useEffect(() => { const openProfile = (user_id: string) => - openScreen({ id: "profile", user_id }); + modalController.push({ type: "user_profile", user_id }); const navigate = (path: string) => history.push(path); const subs = [ diff --git a/src/context/intermediate/Modals.tsx b/src/context/intermediate/Modals.tsx index 4f59c974..48e18245 100644 --- a/src/context/intermediate/Modals.tsx +++ b/src/context/intermediate/Modals.tsx @@ -1,7 +1,6 @@ //import { isModalClosing } from "../../components/ui/Modal"; import { Screen } from "./Intermediate"; import { InputModal } from "./modals/Input"; -import { OnboardingModal } from "./modals/Onboarding"; import { PromptModal } from "./modals/Prompt"; export interface Props { @@ -20,8 +19,6 @@ export default function Modals({ screen, openScreen }: Props) { return ; case "_input": return ; - case "onboarding": - return ; } return null; diff --git a/src/context/intermediate/Popovers.tsx b/src/context/intermediate/Popovers.tsx index 20789cfd..08386727 100644 --- a/src/context/intermediate/Popovers.tsx +++ b/src/context/intermediate/Popovers.tsx @@ -3,9 +3,6 @@ import { useContext } from "preact/hooks"; import { IntermediateContext, useIntermediate } from "./Intermediate"; import { SpecialInputModal } from "./modals/Input"; import { SpecialPromptModal } from "./modals/Prompt"; -import { CreateBotModal } from "./popovers/CreateBot"; -import { UserPicker } from "./popovers/UserPicker"; -import { UserProfile } from "./popovers/UserProfile"; export default function Popovers() { const { screen } = useContext(IntermediateContext); @@ -17,15 +14,6 @@ export default function Popovers() { //: internalEmit("Modal", "close"); switch (screen.id) { - case "profile": - // @ts-expect-error someone figure this out :) - return ; - case "user_picker": - // @ts-expect-error someone figure this out :) - return ; - case "create_bot": - // @ts-expect-error someone figure this out :) - return ; case "special_prompt": // @ts-expect-error someone figure this out :) return ; diff --git a/src/controllers/client/Session.tsx b/src/controllers/client/Session.tsx index 11f80d71..442b65d0 100644 --- a/src/controllers/client/Session.tsx +++ b/src/controllers/client/Session.tsx @@ -5,6 +5,8 @@ import { state } from "../../mobx/State"; import { __thisIsAHack } from "../../context/intermediate/Intermediate"; +import { modalController } from "../modals/ModalController"; + /** * Current lifecycle state */ @@ -189,8 +191,8 @@ export default class Session { ); if (onboarding) { - __thisIsAHack({ - id: "onboarding", + modalController.push({ + type: "onboarding", callback: async (username: string) => this.client!.completeOnboarding( { username }, diff --git a/src/controllers/modals/ModalController.tsx b/src/controllers/modals/ModalController.tsx index cc6e7f9a..a8169d0e 100644 --- a/src/controllers/modals/ModalController.tsx +++ b/src/controllers/modals/ModalController.tsx @@ -16,7 +16,6 @@ import { getApplicationState } from "../../mobx/State"; import { history } from "../../context/history"; import { __thisIsAHack } from "../../context/intermediate/Intermediate"; -// import { determineLink } from "../../lib/links"; import Changelog from "./components/Changelog"; import ChannelInfo from "./components/ChannelInfo"; import Clipboard from "./components/Clipboard"; @@ -34,6 +33,10 @@ import ServerInfo from "./components/ServerInfo"; import ShowToken from "./components/ShowToken"; import SignOutSessions from "./components/SignOutSessions"; import SignedOut from "./components/SignedOut"; +import { UserPicker } from "./components/UserPicker"; +import { CreateBotModal } from "./components/legacy/CreateBot"; +import { OnboardingModal } from "./components/legacy/Onboarding"; +import { UserProfile } from "./components/legacy/UserProfile"; import { Modal } from "./types"; type Components = Record>; @@ -191,7 +194,7 @@ class ModalControllerExtended extends ModalController { switch (link.type) { case "profile": { - __thisIsAHack({ id: "profile", user_id: link.id }); + this.push({ type: "user_profile", user_id: link.id }); break; } case "navigate": { @@ -222,6 +225,7 @@ export const modalController = new ModalControllerExtended({ changelog: Changelog, channel_info: ChannelInfo, clipboard: Clipboard, + create_bot: CreateBotModal, error: Error, image_viewer: ImageViewer, link_warning: LinkWarning, @@ -229,6 +233,7 @@ export const modalController = new ModalControllerExtended({ mfa_recovery: MFARecovery, mfa_enable_totp: MFAEnableTOTP, modify_account: ModifyAccount, + onboarding: OnboardingModal, out_of_date: OutOfDate, pending_friend_requests: PendingFriendRequests, server_identity: ServerIdentity, @@ -236,4 +241,6 @@ export const modalController = new ModalControllerExtended({ show_token: ShowToken, signed_out: SignedOut, sign_out_sessions: SignOutSessions, + user_picker: UserPicker, + user_profile: UserProfile, }); diff --git a/src/context/intermediate/popovers/UserPicker.tsx b/src/controllers/modals/components/UserPicker.tsx similarity index 67% rename from src/context/intermediate/popovers/UserPicker.tsx rename to src/controllers/modals/components/UserPicker.tsx index b152e8d5..b4cc6633 100644 --- a/src/context/intermediate/popovers/UserPicker.tsx +++ b/src/controllers/modals/components/UserPicker.tsx @@ -1,41 +1,50 @@ -import styles from "./UserPicker.module.scss"; +import styled from "styled-components"; + import { Text } from "preact-i18n"; -import { useState } from "preact/hooks"; +import { useMemo, useState } from "preact/hooks"; import { Modal } from "@revoltchat/ui"; import UserCheckbox from "../../../components/common/user/UserCheckbox"; -import { useClient } from "../../../controllers/client/ClientController"; +import { useClient } from "../../client/ClientController"; +import { ModalProps } from "../types"; -interface Props { - omit?: string[]; - onClose: () => void; - callback: (users: string[]) => Promise; -} +const List = styled.div` + max-width: 100%; + max-height: 360px; + overflow-y: scroll; +`; -export function UserPicker(props: Props) { +export function UserPicker({ + callback, + omit, + ...props +}: ModalProps<"user_picker">) { const [selected, setSelected] = useState([]); - const omit = [...(props.omit || []), "00000000000000000000000000"]; + const omitted = useMemo( + () => new Set([...(omit || []), "00000000000000000000000000"]), + [omit], + ); const client = useClient(); return ( } - onClose={props.onClose} actions={[ { children: , - onClick: () => props.callback(selected).then(() => true), + onClick: () => callback(selected).then(() => true), }, ]}> -
+ {[...client.users.values()] .filter( (x) => x && x.relationship === "Friend" && - !omit.includes(x._id), + !omitted.has(x._id), ) .map((x) => ( ))} -
+
); } diff --git a/src/context/intermediate/popovers/CreateBot.tsx b/src/controllers/modals/components/legacy/CreateBot.tsx similarity index 79% rename from src/context/intermediate/popovers/CreateBot.tsx rename to src/controllers/modals/components/legacy/CreateBot.tsx index 142996cd..b880ef5a 100644 --- a/src/context/intermediate/popovers/CreateBot.tsx +++ b/src/controllers/modals/components/legacy/CreateBot.tsx @@ -6,21 +6,24 @@ import { useState } from "preact/hooks"; import { Category, Modal } from "@revoltchat/ui"; -import { useClient } from "../../../controllers/client/ClientController"; -import FormField from "../../../pages/login/FormField"; -import { I18nError } from "../../Locale"; -import { takeError } from "../../revoltjs/util"; +import { noopTrue } from "../../../../lib/js"; -interface Props { - onClose: () => void; - onCreate: (bot: API.Bot) => void; -} +import { I18nError } from "../../../../context/Locale"; +import { takeError } from "../../../../context/revoltjs/util"; + +import FormField from "../../../../pages/login/FormField"; +import { useClient } from "../../../client/ClientController"; +import { modalController } from "../../ModalController"; +import { ModalProps } from "../../types"; interface FormInputs { name: string; } -export function CreateBotModal({ onClose, onCreate }: Props) { +export function CreateBotModal({ + onCreate, + ...props +}: ModalProps<"create_bot">) { const client = useClient(); const { handleSubmit, register, errors } = useForm(); const [error, setError] = useState(undefined); @@ -29,7 +32,7 @@ export function CreateBotModal({ onClose, onCreate }: Props) { try { const { bot } = await client.bots.create({ name }); onCreate(bot); - onClose(); + modalController.close(); } catch (err) { setError(takeError(err)); } @@ -37,7 +40,7 @@ export function CreateBotModal({ onClose, onCreate }: Props) { return ( } actions={[ { @@ -51,7 +54,7 @@ export function CreateBotModal({ onClose, onCreate }: Props) { }, { palette: "plain", - onClick: onClose, + onClick: noopTrue, children: , }, ]}> diff --git a/src/context/intermediate/modals/Onboarding.module.scss b/src/controllers/modals/components/legacy/Onboarding.module.scss similarity index 87% rename from src/context/intermediate/modals/Onboarding.module.scss rename to src/controllers/modals/components/legacy/Onboarding.module.scss index 887b4224..42ebfbdd 100644 --- a/src/context/intermediate/modals/Onboarding.module.scss +++ b/src/controllers/modals/components/legacy/Onboarding.module.scss @@ -1,5 +1,12 @@ .onboarding { - height: 100vh; + position: fixed; + top: 0; + left: 0; + width: 100%; + height: 100%; + + background: var(--background); + display: flex; align-items: center; flex-direction: column; diff --git a/src/context/intermediate/modals/Onboarding.tsx b/src/controllers/modals/components/legacy/Onboarding.tsx similarity index 87% rename from src/context/intermediate/modals/Onboarding.tsx rename to src/controllers/modals/components/legacy/Onboarding.tsx index af21e930..53cc681f 100644 --- a/src/context/intermediate/modals/Onboarding.tsx +++ b/src/controllers/modals/components/legacy/Onboarding.tsx @@ -6,21 +6,21 @@ import { useState } from "preact/hooks"; import { Button, Preloader } from "@revoltchat/ui"; +import { takeError } from "../../../../context/revoltjs/util"; + import wideSVG from "/assets/wide.svg"; -import FormField from "../../../pages/login/FormField"; -import { takeError } from "../../revoltjs/util"; - -interface Props { - onClose: () => void; - callback: (username: string, loginAfterSuccess?: true) => Promise; -} +import FormField from "../../../../pages/login/FormField"; +import { ModalProps } from "../../types"; interface FormInputs { username: string; } -export function OnboardingModal({ onClose, callback }: Props) { +export function OnboardingModal({ + callback, + ...props +}: ModalProps<"onboarding">) { const { handleSubmit, register } = useForm(); const [loading, setLoading] = useState(false); const [error, setError] = useState(undefined); @@ -28,7 +28,7 @@ export function OnboardingModal({ onClose, callback }: Props) { const onSubmit: SubmitHandler = ({ username }) => { setLoading(true); callback(username, true) - .then(() => onClose()) + .then(() => props.onClose()) .catch((err: unknown) => { setError(takeError(err)); setLoading(false); diff --git a/src/context/intermediate/popovers/UserProfile.module.scss b/src/controllers/modals/components/legacy/UserProfile.module.scss similarity index 100% rename from src/context/intermediate/popovers/UserProfile.module.scss rename to src/controllers/modals/components/legacy/UserProfile.module.scss diff --git a/src/context/intermediate/popovers/UserProfile.tsx b/src/controllers/modals/components/legacy/UserProfile.tsx similarity index 92% rename from src/context/intermediate/popovers/UserProfile.tsx rename to src/controllers/modals/components/legacy/UserProfile.tsx index 764a3b34..a7cc7f8e 100644 --- a/src/context/intermediate/popovers/UserProfile.tsx +++ b/src/controllers/modals/components/legacy/UserProfile.tsx @@ -24,31 +24,27 @@ import { Preloader, } from "@revoltchat/ui"; -import { noop } from "../../../lib/js"; +import { noop } from "../../../../lib/js"; -import ChannelIcon from "../../../components/common/ChannelIcon"; -import ServerIcon from "../../../components/common/ServerIcon"; -import Tooltip from "../../../components/common/Tooltip"; -import UserBadges from "../../../components/common/user/UserBadges"; -import UserIcon from "../../../components/common/user/UserIcon"; -import { Username } from "../../../components/common/user/UserShort"; -import UserStatus from "../../../components/common/user/UserStatus"; -import Markdown from "../../../components/markdown/Markdown"; -import { useSession } from "../../../controllers/client/ClientController"; -import { modalController } from "../../../controllers/modals/ModalController"; -import { useIntermediate } from "../Intermediate"; - -interface Props { - user_id: string; - dummy?: boolean; - onClose?: () => void; - dummyProfile?: API.UserProfile; -} +import ChannelIcon from "../../../../components/common/ChannelIcon"; +import ServerIcon from "../../../../components/common/ServerIcon"; +import Tooltip from "../../../../components/common/Tooltip"; +import UserBadges from "../../../../components/common/user/UserBadges"; +import UserIcon from "../../../../components/common/user/UserIcon"; +import { Username } from "../../../../components/common/user/UserShort"; +import UserStatus from "../../../../components/common/user/UserStatus"; +import Markdown from "../../../../components/markdown/Markdown"; +import { useSession } from "../../../../controllers/client/ClientController"; +import { modalController } from "../../../../controllers/modals/ModalController"; +import { ModalProps } from "../../types"; export const UserProfile = observer( - ({ user_id, onClose, dummy, dummyProfile }: Props) => { - const { openScreen, writeClipboard } = useIntermediate(); - + ({ + user_id, + dummy, + dummyProfile, + ...props + }: ModalProps<"user_profile">) => { const [profile, setProfile] = useState< undefined | null | API.UserProfile >(undefined); @@ -66,7 +62,7 @@ export const UserProfile = observer( const user = client.users.get(user_id); if (!user) { - if (onClose) useEffect(onClose, []); + if (props.onClose) useEffect(props.onClose, []); return null; } @@ -171,7 +167,7 @@ export const UserProfile = observer( - writeClipboard(user.username) + modalController.writeText(user.username) }> @{user.username} @@ -187,7 +183,7 @@ export const UserProfile = observer( @@ -200,7 +196,7 @@ export const UserProfile = observer( }> { - onClose?.(); + props.onClose?.(); history.push(`/open/${user_id}`); }}> @@ -211,7 +207,7 @@ export const UserProfile = observer( {user.relationship === "User" && !dummy && ( { - onClose?.(); + props.onClose?.(); history.push(`/settings/profile`); }}> @@ -291,8 +287,8 @@ export const UserProfile = observer(
user.bot && - openScreen({ - id: "profile", + modalController.push({ + type: "user_profile", user_id: user.bot.owner, }) } @@ -355,8 +351,8 @@ export const UserProfile = observer( x && (
- openScreen({ - id: "profile", + modalController.push({ + type: "user_profile", user_id: x._id, }) } @@ -436,7 +432,7 @@ export const UserProfile = observer( return ( diff --git a/src/controllers/modals/types.ts b/src/controllers/modals/types.ts index e2e3b854..2471b82c 100644 --- a/src/controllers/modals/types.ts +++ b/src/controllers/modals/types.ts @@ -85,6 +85,28 @@ export type Modal = { embed?: API.Image; attachment?: API.File; } + | { + type: "user_picker"; + omit?: string[]; + callback: (users: string[]) => Promise; + } + | { + type: "user_profile"; + user_id: string; + dummy?: boolean; + dummyProfile?: API.UserProfile; + } + | { + type: "create_bot"; + onCreate: (bot: API.Bot) => void; + } + | { + type: "onboarding"; + callback: ( + username: string, + loginAfterSuccess?: true, + ) => Promise; + } ); export type ModalProps = Modal & { type: T } & { diff --git a/src/lib/ContextMenus.tsx b/src/lib/ContextMenus.tsx index ed96762c..6c1d1ddc 100644 --- a/src/lib/ContextMenus.tsx +++ b/src/lib/ContextMenus.tsx @@ -314,7 +314,10 @@ export default function ContextMenus() { break; case "view_profile": - openScreen({ id: "profile", user_id: data.user._id }); + modalController.push({ + type: "user_profile", + user_id: data.user._id, + }); break; case "message_user": diff --git a/src/pages/channels/actions/HeaderActions.tsx b/src/pages/channels/actions/HeaderActions.tsx index c3bddff1..04cbe198 100644 --- a/src/pages/channels/actions/HeaderActions.tsx +++ b/src/pages/channels/actions/HeaderActions.tsx @@ -24,6 +24,7 @@ import { SIDEBAR_MEMBERS } from "../../../mobx/stores/Layout"; import { useIntermediate } from "../../../context/intermediate/Intermediate"; import UpdateIndicator from "../../../components/common/UpdateIndicator"; +import { modalController } from "../../../controllers/modals/ModalController"; import { ChannelHeaderProps } from "../ChannelHeader"; const Container = styled.div` @@ -114,8 +115,8 @@ export default function HeaderActions({ channel }: ChannelHeaderProps) { <> - openScreen({ - id: "user_picker", + modalController.push({ + type: "user_picker", omit: channel.recipient_ids!, callback: async (users) => { for (const user of users) { diff --git a/src/pages/friends/Friend.tsx b/src/pages/friends/Friend.tsx index 182ffdea..f93d3531 100644 --- a/src/pages/friends/Friend.tsx +++ b/src/pages/friends/Friend.tsx @@ -18,6 +18,7 @@ import { useIntermediate } from "../../context/intermediate/Intermediate"; import UserIcon from "../../components/common/user/UserIcon"; import UserStatus from "../../components/common/user/UserStatus"; +import { modalController } from "../../controllers/modals/ModalController"; interface Props { user: User; @@ -128,7 +129,12 @@ export const Friend = observer(({ user }: Props) => { return (
openScreen({ id: "profile", user_id: user._id })} + onClick={() => + modalController.push({ + type: "user_profile", + user_id: user._id, + }) + } {...useTriggerEvents("Menu", { user: user._id, })}> diff --git a/src/pages/settings/panes/Profile.tsx b/src/pages/settings/panes/Profile.tsx index d9875090..c0c9e0c3 100644 --- a/src/pages/settings/panes/Profile.tsx +++ b/src/pages/settings/panes/Profile.tsx @@ -12,13 +12,13 @@ import { Button, LineDivider, Tip } from "@revoltchat/ui"; import TextAreaAutoSize from "../../../lib/TextAreaAutoSize"; import { useTranslation } from "../../../lib/i18n"; -import { UserProfile } from "../../../context/intermediate/popovers/UserProfile"; import { FileUploader } from "../../../context/revoltjs/FileUploads"; import AutoComplete, { useAutoComplete, } from "../../../components/common/AutoComplete"; import { useSession } from "../../../controllers/client/ClientController"; +import { UserProfile } from "../../../controllers/modals/components/legacy/UserProfile"; export const Profile = observer(() => { const translate = useTranslation(); @@ -75,6 +75,7 @@ export const Profile = observer(() => { user_id={client.user!._id} dummy={true} dummyProfile={profile} + {...({} as any)} />
{/*

Badges

From 6bf58e83790eac2e6adba17057bad2fea71f3c7f Mon Sep 17 00:00:00 2001 From: Paul Makles Date: Fri, 1 Jul 2022 18:09:53 +0100 Subject: [PATCH 115/151] feat: test emoji picker design --- package.json | 2 +- .../common/messaging/MessageBox.tsx | 47 ++++++++++++++++--- yarn.lock | 10 ++-- 3 files changed, 47 insertions(+), 12 deletions(-) diff --git a/package.json b/package.json index bfbf31e5..67ae834d 100644 --- a/package.json +++ b/package.json @@ -73,7 +73,7 @@ "@hcaptcha/react-hcaptcha": "^0.3.6", "@insertish/vite-plugin-babel-macros": "^1.0.5", "@preact/preset-vite": "^2.0.0", - "@revoltchat/ui": "1.0.46", + "@revoltchat/ui": "1.0.50", "@rollup/plugin-replace": "^2.4.2", "@styled-icons/boxicons-logos": "^10.38.0", "@styled-icons/boxicons-regular": "^10.38.0", diff --git a/src/components/common/messaging/MessageBox.tsx b/src/components/common/messaging/MessageBox.tsx index 86c6cf58..b480860c 100644 --- a/src/components/common/messaging/MessageBox.tsx +++ b/src/components/common/messaging/MessageBox.tsx @@ -1,4 +1,4 @@ -import { Send, ShieldX } from "@styled-icons/boxicons-solid"; +import { HappyBeaming, Send, ShieldX } from "@styled-icons/boxicons-solid"; import Axios, { CancelTokenSource } from "axios"; import { observer } from "mobx-react-lite"; import { Channel } from "revolt.js"; @@ -6,9 +6,16 @@ import styled, { css } from "styled-components/macro"; import { ulid } from "ulid"; import { Text } from "preact-i18n"; -import { useCallback, useContext, useEffect, useState } from "preact/hooks"; +import { memo } from "preact/compat"; +import { + useCallback, + useContext, + useEffect, + useMemo, + useState, +} from "preact/hooks"; -import { IconButton } from "@revoltchat/ui"; +import { IconButton, Picker } from "@revoltchat/ui"; import TextAreaAutoSize from "../../../lib/TextAreaAutoSize"; import { debounce } from "../../../lib/debounce"; @@ -31,9 +38,11 @@ import { } from "../../../context/revoltjs/FileUploads"; import { takeError } from "../../../context/revoltjs/util"; +import { emojiDictionary } from "../../../assets/emojis"; import { useClient } from "../../../controllers/client/ClientController"; import { modalController } from "../../../controllers/modals/ModalController"; import AutoComplete, { useAutoComplete } from "../AutoComplete"; +import Emoji from "../Emoji"; import { PermissionTooltip } from "../Tooltip"; import FilePreview from "./bars/FilePreview"; import ReplyBar from "./bars/ReplyBar"; @@ -127,6 +136,10 @@ const FileAction = styled.div` } `; +const FloatingLayer = styled.div` + position: relative; +`; + const ThisCodeWillBeReplacedAnywaysSoIMightAsWellJustDoItThisWay__Padding = styled.div` width: 16px; `; @@ -148,6 +161,7 @@ export default observer(({ channel }: Props) => { }); const [typing, setTyping] = useState(false); const [replies, setReplies] = useState([]); + const [picker, setPicker] = useState(false); const client = useClient(); const translate = useTranslation(); @@ -457,6 +471,19 @@ export default observer(({ channel }: Props) => { : undefined, }); + const renderEmoji = useMemo( + () => + memo(({ emoji }: { emoji: string }) => ( + + setMessage(`${state.draft.get(channel._id)}\n${emoji}`) + }> + + + )), + [], + ); + return ( <> @@ -498,6 +525,14 @@ export default observer(({ channel }: Props) => { replies={replies} setReplies={setReplies} /> + + {picker && ( + + )} + {channel.havePermission("UploadFiles") ? ( @@ -622,12 +657,12 @@ export default observer(({ channel }: Props) => { - + */} - + setPicker(!picker)}> - */} + Date: Fri, 1 Jul 2022 19:23:50 +0100 Subject: [PATCH 116/151] feat: mogus vented --- src/assets/emojis.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/assets/emojis.ts b/src/assets/emojis.ts index aeb6764d..0f59412d 100644 --- a/src/assets/emojis.ts +++ b/src/assets/emojis.ts @@ -1952,5 +1952,6 @@ export const emojiDictionary = { huggies: "custom:huggies.png", noted: "custom:noted.gif", waving: "custom:waving.png", + mogusvented: "custom:mogusvented.png", }, }; From 6e70937825dcc5296cc602e5557744dcc8f3492f Mon Sep 17 00:00:00 2001 From: Paul Makles Date: Fri, 1 Jul 2022 19:32:27 +0100 Subject: [PATCH 117/151] chore: turn emoji picker into an experiment --- .../common/messaging/MessageBox.tsx | 12 ++++--- src/context/intermediate/modals/Prompt.tsx | 33 +++++++++---------- src/mobx/stores/Experiments.ts | 7 +++- 3 files changed, 28 insertions(+), 24 deletions(-) diff --git a/src/components/common/messaging/MessageBox.tsx b/src/components/common/messaging/MessageBox.tsx index b480860c..81ed3e75 100644 --- a/src/components/common/messaging/MessageBox.tsx +++ b/src/components/common/messaging/MessageBox.tsx @@ -658,11 +658,13 @@ export default observer(({ channel }: Props) => { */} - - setPicker(!picker)}> - - - + {state.experiments.isEnabled("picker") && ( + + setPicker(!picker)}> + + + + )} void; question: Children; + description?: Children; content?: Children; disabled?: boolean; actions: Action[]; @@ -31,6 +32,7 @@ interface Props { export function PromptModal({ onClose, question, + description, content, actions, disabled, @@ -39,6 +41,7 @@ export function PromptModal({ return ( @@ -133,6 +136,12 @@ export const SpecialPromptModal = observer((props: SpecialProps) => { fields={{ name }} /> } + description={ + {name} }} + /> + } actions={[ { confirmation: true, @@ -183,12 +192,6 @@ export const SpecialPromptModal = observer((props: SpecialProps) => { onClick: onClose, }, ]} - content={ - {name} }} - /> - } disabled={processing} error={error} /> @@ -199,6 +202,11 @@ export const SpecialPromptModal = observer((props: SpecialProps) => { } + description={ + + } actions={[ { confirmation: true, @@ -228,18 +236,7 @@ export const SpecialPromptModal = observer((props: SpecialProps) => { }, ]} content={ - <> -
- -
- - + } disabled={processing} error={error} diff --git a/src/mobx/stores/Experiments.ts b/src/mobx/stores/Experiments.ts index 2d3f29f4..a91318a5 100644 --- a/src/mobx/stores/Experiments.ts +++ b/src/mobx/stores/Experiments.ts @@ -10,7 +10,7 @@ import Store from "../interfaces/Store"; /** * Union type of available experiments. */ -export type Experiment = "dummy" | "offline_users" | "plugins"; +export type Experiment = "dummy" | "offline_users" | "plugins" | "picker"; /** * Currently active experiments. @@ -19,6 +19,7 @@ export const AVAILABLE_EXPERIMENTS: Experiment[] = [ "dummy", "offline_users", "plugins", + "picker", ]; /** @@ -41,6 +42,10 @@ export const EXPERIMENTS: { description: "This will enable the experimental plugin API. Only touch this if you know what you're doing.", }, + picker: { + title: "Emoji Picker", + description: "This will enable a work-in-progress emoji picker.", + }, }; export interface Data { From a24e027a48894b15a1858085b257235ac98b6969 Mon Sep 17 00:00:00 2001 From: Paul Makles Date: Mon, 4 Jul 2022 19:04:27 +0100 Subject: [PATCH 118/151] chore: clean up picker / prism imports --- package.json | 7 +- .../common/messaging/MessageBox.tsx | 17 +- src/components/markdown/prism.ts | 2 - yarn.lock | 152 ++++++++++++++++-- 4 files changed, 155 insertions(+), 23 deletions(-) diff --git a/package.json b/package.json index 67ae834d..6ee629bb 100644 --- a/package.json +++ b/package.json @@ -73,7 +73,7 @@ "@hcaptcha/react-hcaptcha": "^0.3.6", "@insertish/vite-plugin-babel-macros": "^1.0.5", "@preact/preset-vite": "^2.0.0", - "@revoltchat/ui": "1.0.50", + "@revoltchat/ui": "1.0.54", "@rollup/plugin-replace": "^2.4.2", "@styled-icons/boxicons-logos": "^10.38.0", "@styled-icons/boxicons-regular": "^10.38.0", @@ -83,6 +83,7 @@ "@traptitech/markdown-it-katex": "^3.4.3", "@traptitech/markdown-it-spoiler": "^1.1.6", "@trivago/prettier-plugin-sort-imports": "^2.0.2", + "@types/lodash": "^4", "@types/lodash.defaultsdeep": "^4.6.6", "@types/lodash.isequal": "^4.5.5", "@types/markdown-it": "^12.0.2", @@ -111,6 +112,7 @@ "history": "4", "json-stringify-deterministic": "^1.0.2", "localforage": "^1.9.0", + "lodash": "^4.17.21", "lodash.defaultsdeep": "^4.6.1", "lodash.isequal": "^4.5.0", "long": "^5.2.0", @@ -138,6 +140,9 @@ "sass": "^1.35.1", "semver": "^7.3.7", "shade-blend-color": "^1.0.0", + "slate": "^0.81.1", + "slate-history": "^0.66.0", + "slate-react": "^0.81.0", "stacktrace-js": "^2.0.2", "styled-components": "^5.3.0", "typescript": "^4.4.2", diff --git a/src/components/common/messaging/MessageBox.tsx b/src/components/common/messaging/MessageBox.tsx index 81ed3e75..d97be03a 100644 --- a/src/components/common/messaging/MessageBox.tsx +++ b/src/components/common/messaging/MessageBox.tsx @@ -475,10 +475,17 @@ export default observer(({ channel }: Props) => { () => memo(({ emoji }: { emoji: string }) => ( - setMessage(`${state.draft.get(channel._id)}\n${emoji}`) - }> - + onClick={() => { + const v = state.draft.get(channel._id); + setMessage(`${v ? `${v} ` : ""}:${emoji}:`); + }}> + )), [], @@ -528,7 +535,7 @@ export default observer(({ channel }: Props) => { {picker && ( )} diff --git a/src/components/markdown/prism.ts b/src/components/markdown/prism.ts index 1ae510c6..b76a9c54 100644 --- a/src/components/markdown/prism.ts +++ b/src/components/markdown/prism.ts @@ -34,7 +34,6 @@ import "prismjs/components/prism-r"; import "prismjs/components/prism-sql"; import "prismjs/components/prism-graphql"; import "prismjs/components/prism-shell-session"; -import "prismjs/components/prism-java"; import "prismjs/components/prism-powershell"; import "prismjs/components/prism-swift"; import "prismjs/components/prism-yaml"; @@ -87,7 +86,6 @@ import "prismjs/components/prism-moonscript"; import "prismjs/components/prism-qml"; import "prismjs/components/prism-vim"; import "prismjs/components/prism-nim"; -import "prismjs/components/prism-swift"; import "prismjs/components/prism-haml"; import "prismjs/components/prism-ada"; import "prismjs/components/prism-arduino"; diff --git a/yarn.lock b/yarn.lock index b3b969d8..4f43ba05 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2231,22 +2231,23 @@ __metadata: languageName: node linkType: hard -"@revoltchat/ui@npm:1.0.50": - version: 1.0.50 - resolution: "@revoltchat/ui@npm:1.0.50" +"@revoltchat/ui@npm:1.0.54": + version: 1.0.54 + resolution: "@revoltchat/ui@npm:1.0.54" dependencies: "@styled-icons/boxicons-logos": ^10.38.0 "@styled-icons/boxicons-regular": ^10.38.0 "@styled-icons/boxicons-solid": ^10.38.0 + "@tippyjs/react": ^4.2.6 + mobx: ^6.6.0 + mobx-react-lite: ^3.4.0 + prismjs: ^1.28.0 + react-beautiful-dnd: ^13.1.0 + react-device-detect: ^2.2.2 + react-virtuoso: ^2.12.0 peerDependencies: - "@tippyjs/react": "*" - mobx: "*" - mobx-react-lite: "*" - react-beautiful-dnd: "*" - react-device-detect: "*" - react-virtuoso: "*" revolt.js: "*" - checksum: 735df2bbdb2b1c56c7c62ba6889d7c75d17f8fbc1cb9ec41b06050470314b04ae6477dabf6dece1bf9b680d451d9cf4d6ecff629025419507fb0410661edb9ff + checksum: 57014aa30f36272825b34e2dfa327b17f3e2b4efeba78f48e1805b5b30027f7dad4431f1c852816762e10643a9ef53b51a211d48ffc3bd14d07966fd5b3b183f languageName: node linkType: hard @@ -2395,7 +2396,7 @@ __metadata: languageName: node linkType: hard -"@tippyjs/react@npm:4.2.6": +"@tippyjs/react@npm:4.2.6, @tippyjs/react@npm:^4.2.6": version: 4.2.6 resolution: "@tippyjs/react@npm:4.2.6" dependencies: @@ -2529,6 +2530,13 @@ __metadata: languageName: node linkType: hard +"@types/is-hotkey@npm:^0.1.1": + version: 0.1.7 + resolution: "@types/is-hotkey@npm:0.1.7" + checksum: bce7c8874b30f346f20cf6cfcf4a10372962924f0e1b1a925a279004faeb276242ebfbfee47bd48df57e1021f2e3078c34e25837e226960c418d5f78f83dacea + languageName: node + linkType: hard + "@types/json-schema@npm:^7.0.3, @types/json-schema@npm:^7.0.7": version: 7.0.9 resolution: "@types/json-schema@npm:7.0.9" @@ -2575,6 +2583,13 @@ __metadata: languageName: node linkType: hard +"@types/lodash@npm:^4, @types/lodash@npm:^4.14.149": + version: 4.14.182 + resolution: "@types/lodash@npm:4.14.182" + checksum: 7dd137aa9dbabd632408bd37009d984655164fa1ecc3f2b6eb94afe35bf0a5852cbab6183148d883e9c73a958b7fec9a9bcf7c8e45d41195add6a18c34958209 + languageName: node + linkType: hard + "@types/markdown-it@npm:^12.0.2": version: 12.2.1 resolution: "@types/markdown-it@npm:12.2.1" @@ -3539,7 +3554,7 @@ __metadata: "@hcaptcha/react-hcaptcha": ^0.3.6 "@insertish/vite-plugin-babel-macros": ^1.0.5 "@preact/preset-vite": ^2.0.0 - "@revoltchat/ui": 1.0.50 + "@revoltchat/ui": 1.0.54 "@rollup/plugin-replace": ^2.4.2 "@styled-icons/boxicons-logos": ^10.38.0 "@styled-icons/boxicons-regular": ^10.38.0 @@ -3549,6 +3564,7 @@ __metadata: "@traptitech/markdown-it-katex": ^3.4.3 "@traptitech/markdown-it-spoiler": ^1.1.6 "@trivago/prettier-plugin-sort-imports": ^2.0.2 + "@types/lodash": ^4 "@types/lodash.defaultsdeep": ^4.6.6 "@types/lodash.isequal": ^4.5.5 "@types/markdown-it": ^12.0.2 @@ -3579,6 +3595,7 @@ __metadata: json-stringify-deterministic: ^1.0.2 klaw: ^3.0.0 localforage: ^1.9.0 + lodash: ^4.17.21 lodash.defaultsdeep: ^4.6.1 lodash.isequal: ^4.5.0 long: ^5.2.0 @@ -3607,6 +3624,9 @@ __metadata: semver: ^7.3.7 shade-blend-color: ^1.0.0 sirv-cli: ^1.0.14 + slate: ^0.81.1 + slate-history: ^0.66.0 + slate-react: ^0.81.0 stacktrace-js: ^2.0.2 styled-components: ^5.3.0 typescript: ^4.4.2 @@ -3720,6 +3740,13 @@ __metadata: languageName: node linkType: hard +"compute-scroll-into-view@npm:^1.0.17": + version: 1.0.17 + resolution: "compute-scroll-into-view@npm:1.0.17" + checksum: b20c05a10c37813c5a6e4bf053c00f65c88d23afed7a6bd7a2a69e05e2ffc2df3483ecfe407d36bf16b8cec8be21ae1966c9c523093a03117e567156cd79a51e + languageName: node + linkType: hard + "concat-map@npm:0.0.1": version: 0.0.1 resolution: "concat-map@npm:0.0.1" @@ -3935,6 +3962,15 @@ __metadata: languageName: node linkType: hard +"direction@npm:^1.0.3": + version: 1.0.4 + resolution: "direction@npm:1.0.4" + bin: + direction: cli.js + checksum: 572ac399093d7c9f2181c96828d252922e2a962b8f31a7fc118e3f7619592c566cc2ed313baf7703f17b2be00cd3c1402550140d0c3f4f70362976376a08b095 + languageName: node + linkType: hard + "dlv@npm:^1.1.3": version: 1.1.3 resolution: "dlv@npm:1.1.3" @@ -5151,6 +5187,13 @@ __metadata: languageName: node linkType: hard +"immer@npm:^9.0.6": + version: 9.0.15 + resolution: "immer@npm:9.0.15" + checksum: 92e3d63e810e3c3c2bb61b70c45443e37ef983ad12924e3edaf03725ae5979618f5b473439bb3bb4a8c4769f25132f18dec10ea15c40f0b20da5691ff96ff611 + languageName: node + linkType: hard + "import-fresh@npm:^3.0.0, import-fresh@npm:^3.2.1": version: 3.3.0 resolution: "import-fresh@npm:3.3.0" @@ -5300,6 +5343,13 @@ __metadata: languageName: node linkType: hard +"is-hotkey@npm:^0.1.6": + version: 0.1.8 + resolution: "is-hotkey@npm:0.1.8" + checksum: 793d0cccaf804583d8f4b835e040d4b6a74cb3f8d4094cb8188ed7cc86e6edf8f650fc80fabd4309e6c29938f5b0e9a17b45504b29dbf92735ef8d780d613aa0 + languageName: node + linkType: hard + "is-lambda@npm:^1.0.1": version: 1.0.1 resolution: "is-lambda@npm:1.0.1" @@ -5344,6 +5394,13 @@ __metadata: languageName: node linkType: hard +"is-plain-object@npm:^5.0.0": + version: 5.0.0 + resolution: "is-plain-object@npm:5.0.0" + checksum: e32d27061eef62c0847d303125440a38660517e586f2f3db7c9d179ae5b6674ab0f469d519b2e25c147a1a3bc87156d0d5f4d8821e0ce4a9ee7fe1fcf11ce45c + languageName: node + linkType: hard + "is-regex@npm:^1.1.3, is-regex@npm:^1.1.4": version: 1.1.4 resolution: "is-regex@npm:1.1.4" @@ -5766,7 +5823,7 @@ __metadata: languageName: node linkType: hard -"lodash@npm:4.17.21, lodash@npm:^4.17.11, lodash@npm:^4.17.15, lodash@npm:^4.17.19, lodash@npm:^4.17.20": +"lodash@npm:4.17.21, lodash@npm:^4.17.11, lodash@npm:^4.17.15, lodash@npm:^4.17.19, lodash@npm:^4.17.20, lodash@npm:^4.17.21, lodash@npm:^4.17.4": version: 4.17.21 resolution: "lodash@npm:4.17.21" checksum: eb835a2e51d381e561e508ce932ea50a8e5a68f4ebdd771ea240d3048244a8d13658acbd502cd4829768c56f2e16bdd4340b9ea141297d472517b83868e677f7 @@ -6053,7 +6110,7 @@ __metadata: languageName: node linkType: hard -"mobx-react-lite@npm:3.4.0": +"mobx-react-lite@npm:3.4.0, mobx-react-lite@npm:^3.4.0": version: 3.4.0 resolution: "mobx-react-lite@npm:3.4.0" peerDependencies: @@ -6496,6 +6553,13 @@ __metadata: languageName: node linkType: hard +"prismjs@npm:^1.28.0": + version: 1.28.0 + resolution: "prismjs@npm:1.28.0" + checksum: bde93fb2beb45b7243219fc53855f59ee54b3fa179f315e8f9d66244d756ef984462e10561bbdc6713d3d7e051852472d7c284f5794a8791eeaefea2fb910b16 + languageName: node + linkType: hard + "progress@npm:^2.0.0": version: 2.0.3 resolution: "progress@npm:2.0.3" @@ -6588,7 +6652,7 @@ __metadata: languageName: node linkType: hard -"react-device-detect@npm:2.2.2": +"react-device-detect@npm:2.2.2, react-device-detect@npm:^2.2.2": version: 2.2.2 resolution: "react-device-detect@npm:2.2.2" dependencies: @@ -7079,6 +7143,15 @@ __metadata: languageName: node linkType: hard +"scroll-into-view-if-needed@npm:^2.2.20": + version: 2.2.29 + resolution: "scroll-into-view-if-needed@npm:2.2.29" + dependencies: + compute-scroll-into-view: ^1.0.17 + checksum: 6b888404ccf68fe2f2f1da8977e1a8a0a64a7139352e02e98621a0e8be3b3db393519ee3dcfb7c32aff3c4790a36829f1be1cccc0cfb2b90a60a61caa669eee2 + languageName: node + linkType: hard + "sdp-transform@npm:^2.14.1": version: 2.14.1 resolution: "sdp-transform@npm:2.14.1" @@ -7235,6 +7308,48 @@ __metadata: languageName: node linkType: hard +"slate-history@npm:^0.66.0": + version: 0.66.0 + resolution: "slate-history@npm:0.66.0" + dependencies: + is-plain-object: ^5.0.0 + peerDependencies: + slate: ">=0.65.3" + checksum: e83d4fbf5d4097a5b434df4846420e593598a9a3f90d3e0638f16915b1014bc8a7dc5cd87e3bc0a31a8ad5eae582a18f96bdc04f4b0b0a5a82cf05bd0377601e + languageName: node + linkType: hard + +"slate-react@npm:^0.81.0": + version: 0.81.0 + resolution: "slate-react@npm:0.81.0" + dependencies: + "@types/is-hotkey": ^0.1.1 + "@types/lodash": ^4.14.149 + direction: ^1.0.3 + is-hotkey: ^0.1.6 + is-plain-object: ^5.0.0 + lodash: ^4.17.4 + scroll-into-view-if-needed: ^2.2.20 + tiny-invariant: 1.0.6 + peerDependencies: + react: ">=16.8.0" + react-dom: ">=16.8.0" + slate: ">=0.65.3" + checksum: 157272106da0e2af7c370e61f85156b3d3b81baf4bf65f16799ed682ee6c01c49950380b76ac274b2c0c31854e23456043ff18cb42eb3db2f781a3b1ae7be4eb + languageName: node + linkType: hard + +"slate@npm:^0.81.1": + version: 0.81.1 + resolution: "slate@npm:0.81.1" + dependencies: + immer: ^9.0.6 + is-plain-object: ^5.0.0 + tiny-warning: ^1.0.3 + checksum: 550541fa6e95a81a3dfa81d863a67141659f8ac150808b6fd2c71ee5f758390a1d32e227ee6eb67f945cd79b9e6fffbcdc8db6e42b5737ea6427ee379e0afb62 + languageName: node + linkType: hard + "slice-ansi@npm:^4.0.0": version: 4.0.0 resolution: "slice-ansi@npm:4.0.0" @@ -7678,6 +7793,13 @@ __metadata: languageName: node linkType: hard +"tiny-invariant@npm:1.0.6": + version: 1.0.6 + resolution: "tiny-invariant@npm:1.0.6" + checksum: c90b34beea3cb10c49531e754afb0999033a2d7edffef6602922de27678d8a96dcbc0e8f0a959bc272859281b0cd1305b711e25d5edfb1da5fc7135e2a992961 + languageName: node + linkType: hard + "tiny-invariant@npm:^1.0.2": version: 1.1.0 resolution: "tiny-invariant@npm:1.1.0" From 23dec324765362cfd71545b277a1848ad118f2ef Mon Sep 17 00:00:00 2001 From: Paul Makles Date: Tue, 5 Jul 2022 15:49:08 +0100 Subject: [PATCH 119/151] feat(refactor): partially rewrite appearance settings --- package.json | 2 +- .../settings/appearance/AdvancedOptions.tsx | 63 ++++ .../settings/appearance/AppearanceOptions.tsx | 63 ++++ .../settings/appearance/ChatOptions.tsx | 56 ++++ src/components/settings/appearance/Shims.tsx | 290 ------------------ .../settings/appearance/ThemeOverrides.tsx | 175 +---------- .../settings/appearance/ThemeSelection.tsx | 64 ++++ .../appearance/{ => legacy}/EmojiSelector.tsx | 2 +- .../settings/appearance/legacy/README.md | 1 + .../{ => legacy}/ThemeBaseSelector.tsx | 0 .../appearance/legacy/ThemeOverrides.tsx | 170 ++++++++++ .../appearance/{ => legacy}/ThemeTools.tsx | 6 +- .../appearance/{ => legacy}/assets/dark.svg | 0 .../appearance/{ => legacy}/assets/light.svg | 0 .../{ => legacy}/assets/mutant_emoji.svg | 0 .../{ => legacy}/assets/noto_emoji.svg | 0 .../{ => legacy}/assets/openmoji_emoji.svg | 0 .../{ => legacy}/assets/twemoji_emoji.svg | 0 src/pages/settings/panes/Appearance.tsx | 48 +-- yarn.lock | 10 +- 20 files changed, 443 insertions(+), 507 deletions(-) create mode 100644 src/components/settings/appearance/AdvancedOptions.tsx create mode 100644 src/components/settings/appearance/AppearanceOptions.tsx create mode 100644 src/components/settings/appearance/ChatOptions.tsx delete mode 100644 src/components/settings/appearance/Shims.tsx create mode 100644 src/components/settings/appearance/ThemeSelection.tsx rename src/components/settings/appearance/{ => legacy}/EmojiSelector.tsx (98%) create mode 100644 src/components/settings/appearance/legacy/README.md rename src/components/settings/appearance/{ => legacy}/ThemeBaseSelector.tsx (100%) create mode 100644 src/components/settings/appearance/legacy/ThemeOverrides.tsx rename src/components/settings/appearance/{ => legacy}/ThemeTools.tsx (93%) rename src/components/settings/appearance/{ => legacy}/assets/dark.svg (100%) rename src/components/settings/appearance/{ => legacy}/assets/light.svg (100%) rename src/components/settings/appearance/{ => legacy}/assets/mutant_emoji.svg (100%) rename src/components/settings/appearance/{ => legacy}/assets/noto_emoji.svg (100%) rename src/components/settings/appearance/{ => legacy}/assets/openmoji_emoji.svg (100%) rename src/components/settings/appearance/{ => legacy}/assets/twemoji_emoji.svg (100%) diff --git a/package.json b/package.json index 6ee629bb..a22ed1cf 100644 --- a/package.json +++ b/package.json @@ -73,7 +73,7 @@ "@hcaptcha/react-hcaptcha": "^0.3.6", "@insertish/vite-plugin-babel-macros": "^1.0.5", "@preact/preset-vite": "^2.0.0", - "@revoltchat/ui": "1.0.54", + "@revoltchat/ui": "1.0.58", "@rollup/plugin-replace": "^2.4.2", "@styled-icons/boxicons-logos": "^10.38.0", "@styled-icons/boxicons-regular": "^10.38.0", diff --git a/src/components/settings/appearance/AdvancedOptions.tsx b/src/components/settings/appearance/AdvancedOptions.tsx new file mode 100644 index 00000000..d6b1c2a6 --- /dev/null +++ b/src/components/settings/appearance/AdvancedOptions.tsx @@ -0,0 +1,63 @@ +import { observer } from "mobx-react-lite"; + +import { Text } from "preact-i18n"; + +import { ObservedInputElement } from "@revoltchat/ui"; + +import TextAreaAutoSize from "../../../lib/TextAreaAutoSize"; + +import { useApplicationState } from "../../../mobx/State"; + +import { + MonospaceFonts, + MONOSPACE_FONTS, + MONOSPACE_FONT_KEYS, +} from "../../../context/Theme"; + +/** + * ! LEGACY + * Component providing a way to edit custom CSS. + */ +export const ShimThemeCustomCSS = observer(() => { + const theme = useApplicationState().settings.theme; + return ( + <> +

+ +

+ theme.setCSS(ev.currentTarget.value)} + /> + + ); +}); + +export default function AdvancedOptions() { + const settings = useApplicationState().settings; + return ( + <> + {/** Combo box of available monospaced fonts */} +

+ +

+ settings.theme.getMonospaceFont()} + onChange={(value) => + settings.theme.setMonospaceFont(value as MonospaceFonts) + } + options={MONOSPACE_FONT_KEYS.map((value) => ({ + value, + name: MONOSPACE_FONTS[value as keyof typeof MONOSPACE_FONTS] + .name, + }))} + /> + {/** Custom CSS */} + + + ); +} diff --git a/src/components/settings/appearance/AppearanceOptions.tsx b/src/components/settings/appearance/AppearanceOptions.tsx new file mode 100644 index 00000000..3d2bd73a --- /dev/null +++ b/src/components/settings/appearance/AppearanceOptions.tsx @@ -0,0 +1,63 @@ +import { Text } from "preact-i18n"; + +import { Column, ObservedInputElement } from "@revoltchat/ui"; + +import { useApplicationState } from "../../../mobx/State"; + +export default function AppearanceOptions() { + const settings = useApplicationState().settings; + + return ( + <> +

+ +

+ {/* Option to toggle "send message" button on desktop. */} + + settings.get("appearance:show_send_button") ?? false + } + onChange={(v) => settings.set("appearance:show_send_button", v)} + title={ + + } + description={ + + } + /> +
+

+ +

+ + {/* Option to toggle transparency effects in-app. */} + + settings.get("appearance:transparency") ?? true + } + onChange={(v) => settings.set("appearance:transparency", v)} + title={ + + } + description={ + + } + /> + {/* Option to toggle seasonal effects. */} + settings.get("appearance:seasonal") ?? true} + onChange={(v) => settings.set("appearance:seasonal", v)} + title={ + + } + description={ + + } + /> + + + ); +} diff --git a/src/components/settings/appearance/ChatOptions.tsx b/src/components/settings/appearance/ChatOptions.tsx new file mode 100644 index 00000000..a6a0aa11 --- /dev/null +++ b/src/components/settings/appearance/ChatOptions.tsx @@ -0,0 +1,56 @@ +import { observer } from "mobx-react-lite"; + +import { Text } from "preact-i18n"; + +import { Column, ObservedInputElement } from "@revoltchat/ui"; + +import { useApplicationState } from "../../../mobx/State"; + +import { FONTS, Fonts, FONT_KEYS } from "../../../context/Theme"; + +import { ShimDisplayEmoji } from "../appearance_legacy/Shims"; + +export default observer(() => { + const settings = useApplicationState().settings; + + return ( + <> + + {/* Combo box of available fonts. */} +

+ +

+ settings.theme.getFont()} + onChange={(value) => settings.theme.setFont(value as Fonts)} + options={FONT_KEYS.map((value) => ({ + value, + name: FONTS[value as keyof typeof FONTS].name, + }))} + /> + {/* Option to toggle liagures for supported fonts. */} + {settings.theme.getFont() === "Inter" && ( + + settings.get("appearance:ligatures") ?? true + } + onChange={(v) => + settings.set("appearance:ligatures", v) + } + title={ + + } + description={ + + } + /> + )} +
+
+ {/* Emoji pack selector */} + + + ); +}); diff --git a/src/components/settings/appearance/Shims.tsx b/src/components/settings/appearance/Shims.tsx deleted file mode 100644 index 4eeab82c..00000000 --- a/src/components/settings/appearance/Shims.tsx +++ /dev/null @@ -1,290 +0,0 @@ -import { Brush } from "@styled-icons/boxicons-solid"; -import { observer } from "mobx-react-lite"; -import { Link } from "react-router-dom"; -// @ts-expect-error shade-blend-color does not have typings. -import pSBC from "shade-blend-color"; - -import { Text } from "preact-i18n"; - -import { - CategoryButton, - Checkbox, - ColourSwatches, - ComboBox, - Radio, -} from "@revoltchat/ui"; - -import TextAreaAutoSize from "../../../lib/TextAreaAutoSize"; - -import { useApplicationState } from "../../../mobx/State"; - -import { - Fonts, - FONTS, - FONT_KEYS, - MonospaceFonts, - MONOSPACE_FONTS, - MONOSPACE_FONT_KEYS, -} from "../../../context/Theme"; - -import { EmojiSelector } from "./EmojiSelector"; -import { ThemeBaseSelector } from "./ThemeBaseSelector"; - -/** - * Component providing a way to switch the base theme being used. - */ -export const ShimThemeBaseSelector = observer(() => { - const theme = useApplicationState().settings.theme; - return ( - { - theme.setBase(base); - theme.reset(); - }} - /> - ); -}); - -/** - * Component providing a link to the theme shop. - * Only appears if experiment is enabled. - */ -export const ShimThemeShop = () => { - return ( - - } - action="chevron" - description={ - - }> - - - - ); -}; - -/** - * Component providing a way to change current accent colour. - */ -export const ShimThemeAccent = observer(() => { - const theme = useApplicationState().settings.theme; - return ( - <> -

- -

- { - theme.setVariable("accent", colour as string); - theme.setVariable("scrollbar-thumb", pSBC(-0.2, colour)); - }} - /> - - ); -}); - -/** - * Component providing a way to edit custom CSS. - */ -export const ShimThemeCustomCSS = observer(() => { - const theme = useApplicationState().settings.theme; - return ( - <> -

- -

- theme.setCSS(ev.currentTarget.value)} - /> - - ); -}); - -/** - * Component providing a way to switch between compact and normal message view. - */ -export const ShimDisplayCompact = () => { - // TODO: WIP feature - return ( - <> -

- -

-
- - } - description={ - - } - value={true} - /> - - } - description={ - - } - value={false} - disabled - /> -
- - ); -}; - -/** - * Component providing a way to change primary text font. - */ -export const ShimDisplayFont = observer(() => { - const theme = useApplicationState().settings.theme; - return ( - <> -

- -

- theme.setFont(e.currentTarget.value as Fonts)}> - {FONT_KEYS.map((key) => ( - - ))} - - - ); -}); - -/** - * Component providing a way to change secondary, monospace text font. - */ -export const ShimDisplayMonospaceFont = observer(() => { - const theme = useApplicationState().settings.theme; - return ( - <> -

- -

- - theme.setMonospaceFont( - e.currentTarget.value as MonospaceFonts, - ) - }> - {MONOSPACE_FONT_KEYS.map((key) => ( - - ))} - - - ); -}); - -/** - * Component providing a way to toggle font ligatures. - */ -export const ShimDisplayLigatures = observer(() => { - const settings = useApplicationState().settings; - if (settings.theme.getFont() !== "Inter") return null; - - return ( - <> - settings.set("appearance:ligatures", v)} - title={} - description={ - - } - /> - - ); -}); - -/** - * Component providing a way to toggle showing the send button on desktop. - */ -export const ShimShowSendButton = observer(() => { - const settings = useApplicationState().settings; - - return ( - settings.set("appearance:show_send_button", v)} - title={ - - } - description={ - - } - /> - ); -}); - -/** - * Component providing a way to toggle seasonal themes. - */ -export const ShimDisplaySeasonal = observer(() => { - const settings = useApplicationState().settings; - - return ( - settings.set("appearance:seasonal", v)} - title={ - - } - description={ - - } - /> - ); -}); - -/** - * Component providing a way to toggle transparency effects. - */ -export const ShimDisplayTransparency = observer(() => { - const settings = useApplicationState().settings; - - return ( - settings.set("appearance:transparency", v)} - title={ - - } - description={ - - } - /> - ); -}); - -/** - * Component providing a way to change emoji pack. - */ -export const ShimDisplayEmoji = observer(() => { - const settings = useApplicationState().settings; - return ( - settings.set("appearance:emoji", v)} - /> - ); -}); diff --git a/src/components/settings/appearance/ThemeOverrides.tsx b/src/components/settings/appearance/ThemeOverrides.tsx index 9fb7b307..c92942ad 100644 --- a/src/components/settings/appearance/ThemeOverrides.tsx +++ b/src/components/settings/appearance/ThemeOverrides.tsx @@ -1,170 +1,11 @@ -import { Pencil } from "@styled-icons/boxicons-regular"; -import { observer } from "mobx-react-lite"; -import styled from "styled-components/macro"; - -import { InputBox } from "@revoltchat/ui"; - -import { useDebounceCallback } from "../../../lib/debounce"; - -import { useApplicationState } from "../../../mobx/State"; - -import { Variables } from "../../../context/Theme"; - -const Container = styled.div` - row-gap: 8px; - display: grid; - column-gap: 16px; - grid-template-columns: repeat(auto-fill, minmax(200px, 1fr)); - margin-bottom: 20px; - - .entry { - padding: 12px; - margin-top: 8px; - border: 1px solid black; - border-radius: var(--border-radius); - - span { - flex: 1; - display: block; - font-weight: 600; - font-size: 0.875rem; - margin-bottom: 8px; - text-transform: capitalize; - - background: inherit; - background-clip: text; - -webkit-background-clip: text; - } - - .override { - gap: 8px; - display: flex; - - .picker { - width: 38px; - height: 38px; - display: grid; - cursor: pointer; - place-items: center; - border-radius: var(--border-radius); - background: var(--primary-background); - } - - input[type="text"] { - width: 0; - min-width: 0; - flex-grow: 1; - } - } - - .input { - width: 0; - height: 0; - position: relative; - - input { - opacity: 0; - border: none; - display: block; - cursor: pointer; - position: relative; - - top: 48px; - } - } - } -`; - -export default observer(() => { - const theme = useApplicationState().settings.theme; - const setVariable = useDebounceCallback( - (data) => { - const { key, value } = data as { key: Variables; value: string }; - theme.setVariable(key, value); - }, - [theme], - 100, - ); +import Overrides from "./legacy/ThemeOverrides"; +import ThemeTools from "./legacy/ThemeTools"; +export default function ThemeOverrides() { return ( - - {( - [ - "accent", - "background", - "foreground", - "primary-background", - "primary-header", - "secondary-background", - "secondary-foreground", - "secondary-header", - "tertiary-background", - "tertiary-foreground", - "block", - "message-box", - "mention", - "scrollbar-thumb", - "scrollbar-track", - "status-online", - "status-away", - "status-busy", - "status-streaming", - "status-invisible", - "success", - "warning", - "error", - "hover", - ] as const - ).map((key) => ( -
-
- - setVariable({ - key, - value: el.currentTarget.value, - }) - } - /> -
- - {key} - -
-
- e.currentTarget.parentElement?.parentElement - ?.querySelector("input") - ?.click() - }> - -
- - setVariable({ - key, - value: el.currentTarget.value, - }) - } - /> -
-
- ))} -
+ <> + + + ); -}); +} diff --git a/src/components/settings/appearance/ThemeSelection.tsx b/src/components/settings/appearance/ThemeSelection.tsx new file mode 100644 index 00000000..a47b4e16 --- /dev/null +++ b/src/components/settings/appearance/ThemeSelection.tsx @@ -0,0 +1,64 @@ +import { Brush } from "@styled-icons/boxicons-solid"; +import { observer } from "mobx-react-lite"; +import { Link } from "react-router-dom"; +// @ts-expect-error shade-blend-color does not have typings. +import pSBC from "shade-blend-color"; + +import { Text } from "preact-i18n"; + +import { CategoryButton, ObservedInputElement } from "@revoltchat/ui"; + +import { useApplicationState } from "../../../mobx/State"; + +import { ThemeBaseSelector } from "./legacy/ThemeBaseSelector"; + +/** + * ! LEGACY + * Component providing a way to switch the base theme being used. + */ +export const ShimThemeBaseSelector = observer(() => { + const theme = useApplicationState().settings.theme; + return ( + { + theme.setBase(base); + theme.reset(); + }} + /> + ); +}); + +export default function ThemeSelection() { + const theme = useApplicationState().settings.theme; + + return ( + <> + {/** Allow users to change base theme */} + + {/** Provide a link to the theme shop */} + + } + action="chevron" + description={ + + }> + + + +
+

+ +

+ { + theme.setVariable("accent", colour as string); + theme.setVariable("scrollbar-thumb", pSBC(-0.2, colour)); + }} + /> + + ); +} diff --git a/src/components/settings/appearance/EmojiSelector.tsx b/src/components/settings/appearance/legacy/EmojiSelector.tsx similarity index 98% rename from src/components/settings/appearance/EmojiSelector.tsx rename to src/components/settings/appearance/legacy/EmojiSelector.tsx index 3dea0564..34bb370f 100644 --- a/src/components/settings/appearance/EmojiSelector.tsx +++ b/src/components/settings/appearance/legacy/EmojiSelector.tsx @@ -7,7 +7,7 @@ import notoSVG from "./assets/noto_emoji.svg"; import openmojiSVG from "./assets/openmoji_emoji.svg"; import twemojiSVG from "./assets/twemoji_emoji.svg"; -import { EmojiPack } from "../../common/Emoji"; +import { EmojiPack } from "../../../common/Emoji"; const Container = styled.div` gap: 12px; diff --git a/src/components/settings/appearance/legacy/README.md b/src/components/settings/appearance/legacy/README.md new file mode 100644 index 00000000..6a1782d5 --- /dev/null +++ b/src/components/settings/appearance/legacy/README.md @@ -0,0 +1 @@ +These components need to be ported to @revoltchat/ui. diff --git a/src/components/settings/appearance/ThemeBaseSelector.tsx b/src/components/settings/appearance/legacy/ThemeBaseSelector.tsx similarity index 100% rename from src/components/settings/appearance/ThemeBaseSelector.tsx rename to src/components/settings/appearance/legacy/ThemeBaseSelector.tsx diff --git a/src/components/settings/appearance/legacy/ThemeOverrides.tsx b/src/components/settings/appearance/legacy/ThemeOverrides.tsx new file mode 100644 index 00000000..3483b510 --- /dev/null +++ b/src/components/settings/appearance/legacy/ThemeOverrides.tsx @@ -0,0 +1,170 @@ +import { Pencil } from "@styled-icons/boxicons-regular"; +import { observer } from "mobx-react-lite"; +import styled from "styled-components/macro"; + +import { InputBox } from "@revoltchat/ui"; + +import { useDebounceCallback } from "../../../../lib/debounce"; + +import { useApplicationState } from "../../../../mobx/State"; + +import { Variables } from "../../../../context/Theme"; + +const Container = styled.div` + row-gap: 8px; + display: grid; + column-gap: 16px; + grid-template-columns: repeat(auto-fill, minmax(200px, 1fr)); + margin-bottom: 20px; + + .entry { + padding: 12px; + margin-top: 8px; + border: 1px solid black; + border-radius: var(--border-radius); + + span { + flex: 1; + display: block; + font-weight: 600; + font-size: 0.875rem; + margin-bottom: 8px; + text-transform: capitalize; + + background: inherit; + background-clip: text; + -webkit-background-clip: text; + } + + .override { + gap: 8px; + display: flex; + + .picker { + width: 38px; + height: 38px; + display: grid; + cursor: pointer; + place-items: center; + border-radius: var(--border-radius); + background: var(--primary-background); + } + + input[type="text"] { + width: 0; + min-width: 0; + flex-grow: 1; + } + } + + .input { + width: 0; + height: 0; + position: relative; + + input { + opacity: 0; + border: none; + display: block; + cursor: pointer; + position: relative; + + top: 48px; + } + } + } +`; + +export default observer(() => { + const theme = useApplicationState().settings.theme; + const setVariable = useDebounceCallback( + (data) => { + const { key, value } = data as { key: Variables; value: string }; + theme.setVariable(key, value); + }, + [theme], + 100, + ); + + return ( + + {( + [ + "accent", + "background", + "foreground", + "primary-background", + "primary-header", + "secondary-background", + "secondary-foreground", + "secondary-header", + "tertiary-background", + "tertiary-foreground", + "block", + "message-box", + "mention", + "scrollbar-thumb", + "scrollbar-track", + "status-online", + "status-away", + "status-busy", + "status-streaming", + "status-invisible", + "success", + "warning", + "error", + "hover", + ] as const + ).map((key) => ( +
+
+ + setVariable({ + key, + value: el.currentTarget.value, + }) + } + /> +
+ + {key} + +
+
+ e.currentTarget.parentElement?.parentElement + ?.querySelector("input") + ?.click() + }> + +
+ + setVariable({ + key, + value: el.currentTarget.value, + }) + } + /> +
+
+ ))} +
+ ); +}); diff --git a/src/components/settings/appearance/ThemeTools.tsx b/src/components/settings/appearance/legacy/ThemeTools.tsx similarity index 93% rename from src/components/settings/appearance/ThemeTools.tsx rename to src/components/settings/appearance/legacy/ThemeTools.tsx index 068f15f7..bd3a3f5b 100644 --- a/src/components/settings/appearance/ThemeTools.tsx +++ b/src/components/settings/appearance/legacy/ThemeTools.tsx @@ -5,11 +5,11 @@ import { Text } from "preact-i18n"; import { Button } from "@revoltchat/ui"; -import { useApplicationState } from "../../../mobx/State"; +import { useApplicationState } from "../../../../mobx/State"; -import { useIntermediate } from "../../../context/intermediate/Intermediate"; +import { useIntermediate } from "../../../../context/intermediate/Intermediate"; -import Tooltip from "../../common/Tooltip"; +import Tooltip from "../../../common/Tooltip"; const Actions = styled.div` gap: 8px; diff --git a/src/components/settings/appearance/assets/dark.svg b/src/components/settings/appearance/legacy/assets/dark.svg similarity index 100% rename from src/components/settings/appearance/assets/dark.svg rename to src/components/settings/appearance/legacy/assets/dark.svg diff --git a/src/components/settings/appearance/assets/light.svg b/src/components/settings/appearance/legacy/assets/light.svg similarity index 100% rename from src/components/settings/appearance/assets/light.svg rename to src/components/settings/appearance/legacy/assets/light.svg diff --git a/src/components/settings/appearance/assets/mutant_emoji.svg b/src/components/settings/appearance/legacy/assets/mutant_emoji.svg similarity index 100% rename from src/components/settings/appearance/assets/mutant_emoji.svg rename to src/components/settings/appearance/legacy/assets/mutant_emoji.svg diff --git a/src/components/settings/appearance/assets/noto_emoji.svg b/src/components/settings/appearance/legacy/assets/noto_emoji.svg similarity index 100% rename from src/components/settings/appearance/assets/noto_emoji.svg rename to src/components/settings/appearance/legacy/assets/noto_emoji.svg diff --git a/src/components/settings/appearance/assets/openmoji_emoji.svg b/src/components/settings/appearance/legacy/assets/openmoji_emoji.svg similarity index 100% rename from src/components/settings/appearance/assets/openmoji_emoji.svg rename to src/components/settings/appearance/legacy/assets/openmoji_emoji.svg diff --git a/src/components/settings/appearance/assets/twemoji_emoji.svg b/src/components/settings/appearance/legacy/assets/twemoji_emoji.svg similarity index 100% rename from src/components/settings/appearance/assets/twemoji_emoji.svg rename to src/components/settings/appearance/legacy/assets/twemoji_emoji.svg diff --git a/src/pages/settings/panes/Appearance.tsx b/src/pages/settings/panes/Appearance.tsx index c3c994f4..d964f187 100644 --- a/src/pages/settings/panes/Appearance.tsx +++ b/src/pages/settings/panes/Appearance.tsx @@ -3,65 +3,33 @@ import { observer } from "mobx-react-lite"; import styles from "./Panes.module.scss"; import { Text } from "preact-i18n"; -import { Column } from "@revoltchat/ui"; - import CollapsibleSection from "../../../components/common/CollapsibleSection"; -import { - ShimThemeBaseSelector, - ShimThemeShop, - ShimThemeAccent, - ShimDisplayFont, - ShimDisplayMonospaceFont, - ShimDisplayLigatures, - ShimDisplayEmoji, - ShimThemeCustomCSS, - ShimDisplaySeasonal, - ShimDisplayTransparency, - ShimShowSendButton, -} from "../../../components/settings/appearance/Shims"; +import AdvancedOptions from "../../../components/settings/appearance/AdvancedOptions"; +import AppearanceOptions from "../../../components/settings/appearance/AppearanceOptions"; +import ChatOptions from "../../../components/settings/appearance/ChatOptions"; import ThemeOverrides from "../../../components/settings/appearance/ThemeOverrides"; -import ThemeTools from "../../../components/settings/appearance/ThemeTools"; +import ThemeSelection from "../../../components/settings/appearance/ThemeSelection"; export const Appearance = observer(() => { return (
- - +
- +
-

- -

- -
-

- -

- - - - -
- - -
- +
}> - -

App

}> - - +
); diff --git a/yarn.lock b/yarn.lock index 4f43ba05..130f5d85 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2231,9 +2231,9 @@ __metadata: languageName: node linkType: hard -"@revoltchat/ui@npm:1.0.54": - version: 1.0.54 - resolution: "@revoltchat/ui@npm:1.0.54" +"@revoltchat/ui@npm:1.0.58": + version: 1.0.58 + resolution: "@revoltchat/ui@npm:1.0.58" dependencies: "@styled-icons/boxicons-logos": ^10.38.0 "@styled-icons/boxicons-regular": ^10.38.0 @@ -2247,7 +2247,7 @@ __metadata: react-virtuoso: ^2.12.0 peerDependencies: revolt.js: "*" - checksum: 57014aa30f36272825b34e2dfa327b17f3e2b4efeba78f48e1805b5b30027f7dad4431f1c852816762e10643a9ef53b51a211d48ffc3bd14d07966fd5b3b183f + checksum: 1ebdb3963c77fbad11427963e27f1be1beb480e80360a59f06a3a2fd2d3f5e335ff33fbb8bf99f533549696848ae1d1db5072ff4d45e98d25c9b1b372f25d795 languageName: node linkType: hard @@ -3554,7 +3554,7 @@ __metadata: "@hcaptcha/react-hcaptcha": ^0.3.6 "@insertish/vite-plugin-babel-macros": ^1.0.5 "@preact/preset-vite": ^2.0.0 - "@revoltchat/ui": 1.0.54 + "@revoltchat/ui": 1.0.58 "@rollup/plugin-replace": ^2.4.2 "@styled-icons/boxicons-logos": ^10.38.0 "@styled-icons/boxicons-regular": ^10.38.0 From 79c90c1b0052ae5899133648139e1a82bc1114b0 Mon Sep 17 00:00:00 2001 From: Paul Makles Date: Tue, 5 Jul 2022 17:53:41 +0100 Subject: [PATCH 120/151] feat: port input modals to new system --- package.json | 2 +- .../navigation/left/HomeSidebar.tsx | 4 +- .../navigation/left/ServerListSidebar.tsx | 7 +- .../settings/appearance/ChatOptions.tsx | 16 +++- src/context/intermediate/modals/Input.tsx | 90 ------------------- src/context/revoltjs/util.tsx | 5 ++ src/controllers/modals/ModalController.tsx | 10 +++ .../modals/components/AddFriend.tsx | 31 +++++++ .../modals/components/CreateGroup.tsx | 45 ++++++++++ .../modals/components/CreateRole.tsx | 35 ++++++++ .../modals/components/CreateServer.tsx | 57 ++++++++++++ .../modals/components/CustomStatus.tsx | 43 +++++++++ src/controllers/modals/types.ts | 16 +++- src/lib/ContextMenus.tsx | 5 +- src/pages/friends/Friends.tsx | 6 +- src/pages/home/Home.tsx | 4 +- src/pages/settings/Settings.tsx | 5 +- src/pages/settings/server/Roles.tsx | 11 +-- yarn.lock | 10 +-- 19 files changed, 275 insertions(+), 127 deletions(-) create mode 100644 src/controllers/modals/components/AddFriend.tsx create mode 100644 src/controllers/modals/components/CreateGroup.tsx create mode 100644 src/controllers/modals/components/CreateRole.tsx create mode 100644 src/controllers/modals/components/CreateServer.tsx create mode 100644 src/controllers/modals/components/CustomStatus.tsx diff --git a/package.json b/package.json index a22ed1cf..3f6fda66 100644 --- a/package.json +++ b/package.json @@ -73,7 +73,7 @@ "@hcaptcha/react-hcaptcha": "^0.3.6", "@insertish/vite-plugin-babel-macros": "^1.0.5", "@preact/preset-vite": "^2.0.0", - "@revoltchat/ui": "1.0.58", + "@revoltchat/ui": "1.0.61", "@rollup/plugin-replace": "^2.4.2", "@styled-icons/boxicons-logos": "^10.38.0", "@styled-icons/boxicons-regular": "^10.38.0", diff --git a/src/components/navigation/left/HomeSidebar.tsx b/src/components/navigation/left/HomeSidebar.tsx index 9dcfb8e9..b1f1d659 100644 --- a/src/components/navigation/left/HomeSidebar.tsx +++ b/src/components/navigation/left/HomeSidebar.tsx @@ -25,6 +25,7 @@ import { useIntermediate } from "../../../context/intermediate/Intermediate"; import placeholderSVG from "../items/placeholder.svg"; import { useClient } from "../../../controllers/client/ClientController"; +import { modalController } from "../../../controllers/modals/ModalController"; import { GenericSidebarBase, GenericSidebarList } from "../SidebarBase"; import ButtonItem, { ChannelButton } from "../items/ButtonItem"; import ConnectionStatus from "../items/ConnectionStatus"; @@ -131,8 +132,7 @@ export default observer(() => { - openScreen({ - id: "special_input", + modalController.push({ type: "create_group", }) }> diff --git a/src/components/navigation/left/ServerListSidebar.tsx b/src/components/navigation/left/ServerListSidebar.tsx index ed703a31..b9189b23 100644 --- a/src/components/navigation/left/ServerListSidebar.tsx +++ b/src/components/navigation/left/ServerListSidebar.tsx @@ -7,9 +7,8 @@ import { ServerList } from "@revoltchat/ui"; import { useApplicationState } from "../../../mobx/State"; -import { useIntermediate } from "../../../context/intermediate/Intermediate"; - import { useClient } from "../../../controllers/client/ClientController"; +import { modalController } from "../../../controllers/modals/ModalController"; /** * Server list sidebar shim component @@ -17,13 +16,11 @@ import { useClient } from "../../../controllers/client/ClientController"; export default observer(() => { const client = useClient(); const state = useApplicationState(); - const { openScreen } = useIntermediate(); const { server: server_id } = useParams<{ server?: string }>(); const createServer = useCallback( () => - openScreen({ - id: "special_input", + modalController.push({ type: "create_server", }), [], diff --git a/src/components/settings/appearance/ChatOptions.tsx b/src/components/settings/appearance/ChatOptions.tsx index a6a0aa11..df947383 100644 --- a/src/components/settings/appearance/ChatOptions.tsx +++ b/src/components/settings/appearance/ChatOptions.tsx @@ -8,7 +8,21 @@ import { useApplicationState } from "../../../mobx/State"; import { FONTS, Fonts, FONT_KEYS } from "../../../context/Theme"; -import { ShimDisplayEmoji } from "../appearance_legacy/Shims"; +import { EmojiSelector } from "./legacy/EmojiSelector"; + +/** + * ! LEGACY + * Component providing a way to change emoji pack. + */ +export const ShimDisplayEmoji = observer(() => { + const settings = useApplicationState().settings; + return ( + settings.set("appearance:emoji", v)} + /> + ); +}); export default observer(() => { const settings = useApplicationState().settings; diff --git a/src/context/intermediate/modals/Input.tsx b/src/context/intermediate/modals/Input.tsx index d4e20b69..f912b861 100644 --- a/src/context/intermediate/modals/Input.tsx +++ b/src/context/intermediate/modals/Input.tsx @@ -93,96 +93,6 @@ export function SpecialInputModal(props: SpecialProps) { const { onClose } = props; switch (props.type) { - case "create_group": { - return ( - } - field={} - callback={async (name) => { - const group = await client.channels.createGroup({ - name, - users: [], - }); - - history.push(`/channel/${group._id}`); - }} - /> - ); - } - case "create_server": { - return ( - } - field={} - description={ -
- By creating this server, you agree to the{" "} - - Acceptable Use Policy. - -
- } - callback={async (name) => { - const server = await client.servers.createServer({ - name, - }); - - history.push(`/server/${server._id}`); - }} - /> - ); - } - case "create_role": { - return ( - - } - field={} - callback={async (name) => { - const role = await props.server.createRole(name); - props.callback(role.id); - }} - /> - ); - } - case "set_custom_status": { - return ( - } - field={} - defaultValue={client.user?.status?.text ?? undefined} - callback={(text) => - client.users.edit({ - status: { - ...client.user?.status, - text: text.trim().length > 0 ? text : undefined, - }, - }) - } - /> - ); - } - case "add_friend": { - return ( - - client.api - .post(`/users/friend`, { username }) - .then(undefined) - } - /> - ); - } default: return null; } diff --git a/src/context/revoltjs/util.tsx b/src/context/revoltjs/util.tsx index e82713a4..2523ba0c 100644 --- a/src/context/revoltjs/util.tsx +++ b/src/context/revoltjs/util.tsx @@ -27,6 +27,11 @@ export function takeError(error: any): string { return "UnknownError"; } +// eslint-disable-next-line @typescript-eslint/no-explicit-any +export function mapError(error: any): never { + throw takeError(error); +} + export function getChannelName( channel: Channel, prefixType?: boolean, diff --git a/src/controllers/modals/ModalController.tsx b/src/controllers/modals/ModalController.tsx index a8169d0e..340e1021 100644 --- a/src/controllers/modals/ModalController.tsx +++ b/src/controllers/modals/ModalController.tsx @@ -16,9 +16,14 @@ import { getApplicationState } from "../../mobx/State"; import { history } from "../../context/history"; import { __thisIsAHack } from "../../context/intermediate/Intermediate"; +import AddFriend from "./components/AddFriend"; import Changelog from "./components/Changelog"; import ChannelInfo from "./components/ChannelInfo"; import Clipboard from "./components/Clipboard"; +import CreateGroup from "./components/CreateGroup"; +import CreateRole from "./components/CreateRole"; +import CreateServer from "./components/CreateServer"; +import CustomStatus from "./components/CustomStatus"; import Error from "./components/Error"; import ImageViewer from "./components/ImageViewer"; import LinkWarning from "./components/LinkWarning"; @@ -222,10 +227,15 @@ class ModalControllerExtended extends ModalController { } export const modalController = new ModalControllerExtended({ + add_friend: AddFriend, changelog: Changelog, channel_info: ChannelInfo, clipboard: Clipboard, + create_group: CreateGroup, + create_role: CreateRole, + create_server: CreateServer, create_bot: CreateBotModal, + custom_status: CustomStatus, error: Error, image_viewer: ImageViewer, link_warning: LinkWarning, diff --git a/src/controllers/modals/components/AddFriend.tsx b/src/controllers/modals/components/AddFriend.tsx new file mode 100644 index 00000000..e239012b --- /dev/null +++ b/src/controllers/modals/components/AddFriend.tsx @@ -0,0 +1,31 @@ +import { ModalForm } from "@revoltchat/ui"; + +import { noop } from "../../../lib/js"; + +import { useClient } from "../../client/ClientController"; +import { ModalProps } from "../types"; + +/** + * Add friend modal + */ +export default function AddFriend({ ...props }: ModalProps<"add_friend">) { + const client = useClient(); + + return ( + + client.api.post(`/users/friend`, { username }).then(noop) + } + /> + ); +} diff --git a/src/controllers/modals/components/CreateGroup.tsx b/src/controllers/modals/components/CreateGroup.tsx new file mode 100644 index 00000000..797e23e0 --- /dev/null +++ b/src/controllers/modals/components/CreateGroup.tsx @@ -0,0 +1,45 @@ +import { useHistory } from "react-router-dom"; + +import { Text } from "preact-i18n"; + +import { ModalForm } from "@revoltchat/ui"; + +import { mapError } from "../../../context/revoltjs/util"; + +import { useClient } from "../../client/ClientController"; +import { ModalProps } from "../types"; + +/** + * Group creation modal + */ +export default function CreateGroup({ ...props }: ModalProps<"create_group">) { + const history = useHistory(); + const client = useClient(); + + return ( + } + schema={{ + name: "text", + }} + data={{ + name: { + field: ( + + ) as React.ReactChild, + }, + }} + callback={async ({ name }) => { + const group = await client.channels + .createGroup({ + name, + users: [], + }) + .catch(mapError); + + history.push(`/channel/${group._id}`); + }} + /> + ); +} diff --git a/src/controllers/modals/components/CreateRole.tsx b/src/controllers/modals/components/CreateRole.tsx new file mode 100644 index 00000000..4ce5d038 --- /dev/null +++ b/src/controllers/modals/components/CreateRole.tsx @@ -0,0 +1,35 @@ +import { Text } from "preact-i18n"; + +import { ModalForm } from "@revoltchat/ui"; + +import { ModalProps } from "../types"; + +/** + * Role creation modal + */ +export default function CreateRole({ + server, + callback, + ...props +}: ModalProps<"create_role">) { + return ( + } + schema={{ + name: "text", + }} + data={{ + name: { + field: ( + + ) as React.ReactChild, + }, + }} + callback={async ({ name }) => { + const role = await server.createRole(name); + callback(role.id); + }} + /> + ); +} diff --git a/src/controllers/modals/components/CreateServer.tsx b/src/controllers/modals/components/CreateServer.tsx new file mode 100644 index 00000000..b6f6a374 --- /dev/null +++ b/src/controllers/modals/components/CreateServer.tsx @@ -0,0 +1,57 @@ +import { useHistory } from "react-router-dom"; + +import { Text } from "preact-i18n"; + +import { ModalForm } from "@revoltchat/ui"; + +import { mapError } from "../../../context/revoltjs/util"; + +import { useClient } from "../../client/ClientController"; +import { ModalProps } from "../types"; + +/** + * Server creation modal + */ +export default function CreateServer({ + ...props +}: ModalProps<"create_server">) { + const history = useHistory(); + const client = useClient(); + + return ( + } + description={ +
+ By creating this server, you agree to the{" "} + + Acceptable Use Policy. + +
+ } + schema={{ + name: "text", + }} + data={{ + name: { + field: ( + + ) as React.ReactChild, + }, + }} + callback={async ({ name }) => { + const server = await client.servers + .createServer({ + name, + }) + .catch(mapError); + + history.push(`/server/${server._id}`); + }} + /> + ); +} diff --git a/src/controllers/modals/components/CustomStatus.tsx b/src/controllers/modals/components/CustomStatus.tsx new file mode 100644 index 00000000..7db3ddac --- /dev/null +++ b/src/controllers/modals/components/CustomStatus.tsx @@ -0,0 +1,43 @@ +import { Text } from "preact-i18n"; + +import { ModalForm } from "@revoltchat/ui"; + +import { useClient } from "../../client/ClientController"; +import { ModalProps } from "../types"; + +/** + * Custom status modal + */ +export default function CustomStatus({ + ...props +}: ModalProps<"custom_status">) { + const client = useClient(); + + return ( + } + schema={{ + text: "text", + }} + defaults={{ + text: client.user?.status?.text as string, + }} + data={{ + text: { + field: ( + + ) as React.ReactChild, + }, + }} + callback={({ text }) => + client.users.edit({ + status: { + ...client.user?.status, + text: text.trim().length > 0 ? text : undefined, + }, + }) + } + /> + ); +} diff --git a/src/controllers/modals/types.ts b/src/controllers/modals/types.ts index 2471b82c..5ffc5fef 100644 --- a/src/controllers/modals/types.ts +++ b/src/controllers/modals/types.ts @@ -3,6 +3,14 @@ import { API, Client, User, Member, Channel, Server } from "revolt.js"; export type Modal = { key?: string; } & ( + | { + type: + | "signed_out" + | "create_group" + | "create_server" + | "custom_status" + | "add_friend"; + } | ({ type: "mfa_flow"; } & ( @@ -69,9 +77,6 @@ export type Modal = { type: "server_identity"; member: Member; } - | { - type: "signed_out"; - } | { type: "channel_info"; channel: Channel; @@ -107,6 +112,11 @@ export type Modal = { loginAfterSuccess?: true, ) => Promise; } + | { + type: "create_role"; + server: Server; + callback: (id: string) => void; + } ); export type ModalProps = Modal & { type: T } & { diff --git a/src/lib/ContextMenus.tsx b/src/lib/ContextMenus.tsx index 6c1d1ddc..67fc3018 100644 --- a/src/lib/ContextMenus.tsx +++ b/src/lib/ContextMenus.tsx @@ -368,9 +368,8 @@ export default function ContextMenus() { break; case "set_status": - openScreen({ - id: "special_input", - type: "set_custom_status", + modalController.push({ + type: "custom_status", }); break; diff --git a/src/pages/friends/Friends.tsx b/src/pages/friends/Friends.tsx index 3b6fa516..a479b46b 100644 --- a/src/pages/friends/Friends.tsx +++ b/src/pages/friends/Friends.tsx @@ -85,8 +85,7 @@ export default observer(() => { - openScreen({ - id: "special_input", + modalController.push({ type: "create_group", }) }> @@ -96,8 +95,7 @@ export default observer(() => { - openScreen({ - id: "special_input", + modalController.push({ type: "add_friend", }) }> diff --git a/src/pages/home/Home.tsx b/src/pages/home/Home.tsx index 9690af9d..67784147 100644 --- a/src/pages/home/Home.tsx +++ b/src/pages/home/Home.tsx @@ -29,6 +29,7 @@ import wideSVG from "/assets/wide.svg"; import { PageHeader } from "../../components/ui/Header"; import { useClient } from "../../controllers/client/ClientController"; +import { modalController } from "../../controllers/modals/ModalController"; const Overlay = styled.div` display: grid; @@ -98,8 +99,7 @@ export default observer(() => {
- openScreen({ - id: "special_input", + modalController.push({ type: "create_group", }) }> diff --git a/src/pages/settings/Settings.tsx b/src/pages/settings/Settings.tsx index cc697ccc..022f0c85 100644 --- a/src/pages/settings/Settings.tsx +++ b/src/pages/settings/Settings.tsx @@ -347,9 +347,8 @@ export default observer(() => { - openScreen({ - id: "special_input", - type: "set_custom_status", + modalController.push({ + type: "custom_status", }) }> Change your status... diff --git a/src/pages/settings/server/Roles.tsx b/src/pages/settings/server/Roles.tsx index aa70f81f..de43c626 100644 --- a/src/pages/settings/server/Roles.tsx +++ b/src/pages/settings/server/Roles.tsx @@ -16,10 +16,9 @@ import { Category, } from "@revoltchat/ui"; -import { useIntermediate } from "../../../context/intermediate/Intermediate"; - import { PermissionList } from "../../../components/settings/roles/PermissionList"; import { RoleOrDefault } from "../../../components/settings/roles/RoleSelection"; +import { modalController } from "../../../controllers/modals/ModalController"; interface Props { server: Server; @@ -54,18 +53,14 @@ export const Roles = observer(({ server }: Props) => { // Consolidate all permissions that we can change right now. const currentRoles = useRoles(server); - // Pull in modal context. - const { openScreen } = useIntermediate(); - return ( - openScreen({ - id: "special_input", + modalController.push({ type: "create_role", - server: server as any, + server, callback, }) } diff --git a/yarn.lock b/yarn.lock index 130f5d85..b674b8fd 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2231,9 +2231,9 @@ __metadata: languageName: node linkType: hard -"@revoltchat/ui@npm:1.0.58": - version: 1.0.58 - resolution: "@revoltchat/ui@npm:1.0.58" +"@revoltchat/ui@npm:1.0.61": + version: 1.0.61 + resolution: "@revoltchat/ui@npm:1.0.61" dependencies: "@styled-icons/boxicons-logos": ^10.38.0 "@styled-icons/boxicons-regular": ^10.38.0 @@ -2247,7 +2247,7 @@ __metadata: react-virtuoso: ^2.12.0 peerDependencies: revolt.js: "*" - checksum: 1ebdb3963c77fbad11427963e27f1be1beb480e80360a59f06a3a2fd2d3f5e335ff33fbb8bf99f533549696848ae1d1db5072ff4d45e98d25c9b1b372f25d795 + checksum: 678584f84cb3d43307507058eb434507cb1d201455f9da6618eb1731c6439240ddc16601dc636b5790a83393884770bf4e8fee75d235871f65ba5abcd8f685ac languageName: node linkType: hard @@ -3554,7 +3554,7 @@ __metadata: "@hcaptcha/react-hcaptcha": ^0.3.6 "@insertish/vite-plugin-babel-macros": ^1.0.5 "@preact/preset-vite": ^2.0.0 - "@revoltchat/ui": 1.0.58 + "@revoltchat/ui": 1.0.61 "@rollup/plugin-replace": ^2.4.2 "@styled-icons/boxicons-logos": ^10.38.0 "@styled-icons/boxicons-regular": ^10.38.0 From 160d71684fc073bad840697c30ad8d905ea86c6d Mon Sep 17 00:00:00 2001 From: Paul Makles Date: Tue, 5 Jul 2022 18:46:13 +0100 Subject: [PATCH 121/151] feat: port `DeleteMessage` and `Confirmation` --- package.json | 2 +- .../messaging/bars/MessageOverlayBar.tsx | 6 +- .../navigation/items/ButtonItem.tsx | 7 +- src/context/intermediate/modals/Prompt.tsx | 15 ++- src/controllers/modals/ModalController.tsx | 11 ++ .../modals/components/Confirmation.tsx | 101 ++++++++++++++++++ .../modals/components/DeleteMessage.tsx | 35 ++++++ src/controllers/modals/types.ts | 65 ++++++++++- src/lib/ContextMenus.tsx | 60 ++++++----- .../channels/messaging/MessageEditor.tsx | 5 +- src/pages/channels/voice/VoiceHeader.tsx | 18 ++-- src/pages/friends/Friend.tsx | 3 +- src/pages/settings/ServerSettings.tsx | 4 +- src/pages/settings/panes/MyBots.tsx | 13 +-- src/pages/settings/server/Categories.tsx | 4 +- yarn.lock | 10 +- 16 files changed, 290 insertions(+), 69 deletions(-) create mode 100644 src/controllers/modals/components/Confirmation.tsx create mode 100644 src/controllers/modals/components/DeleteMessage.tsx diff --git a/package.json b/package.json index 3f6fda66..5cf1afb3 100644 --- a/package.json +++ b/package.json @@ -73,7 +73,7 @@ "@hcaptcha/react-hcaptcha": "^0.3.6", "@insertish/vite-plugin-babel-macros": "^1.0.5", "@preact/preset-vite": "^2.0.0", - "@revoltchat/ui": "1.0.61", + "@revoltchat/ui": "1.0.63", "@rollup/plugin-replace": "^2.4.2", "@styled-icons/boxicons-logos": "^10.38.0", "@styled-icons/boxicons-regular": "^10.38.0", diff --git a/src/components/common/messaging/bars/MessageOverlayBar.tsx b/src/components/common/messaging/bars/MessageOverlayBar.tsx index bc05e834..9104593f 100644 --- a/src/components/common/messaging/bars/MessageOverlayBar.tsx +++ b/src/components/common/messaging/bars/MessageOverlayBar.tsx @@ -25,6 +25,7 @@ import { } from "../../../../context/intermediate/Intermediate"; import { useClient } from "../../../../controllers/client/ClientController"; +import { modalController } from "../../../../controllers/modals/ModalController"; import Tooltip from "../../../common/Tooltip"; interface Props { @@ -136,11 +137,10 @@ export const MessageOverlayBar = observer(({ message, queued }: Props) => { onClick={(e) => e.shiftKey ? message.delete() - : openScreen({ - id: "special_prompt", + : modalController.push({ type: "delete_message", target: message, - } as unknown as Screen) + }) }> diff --git a/src/components/navigation/items/ButtonItem.tsx b/src/components/navigation/items/ButtonItem.tsx index adcd1442..903d3775 100644 --- a/src/components/navigation/items/ButtonItem.tsx +++ b/src/components/navigation/items/ButtonItem.tsx @@ -15,6 +15,7 @@ import { stopPropagation } from "../../../lib/stopPropagation"; import { useIntermediate } from "../../../context/intermediate/Intermediate"; +import { modalController } from "../../../controllers/modals/ModalController"; import ChannelIcon from "../../common/ChannelIcon"; import Tooltip from "../../common/Tooltip"; import UserIcon from "../../common/user/UserIcon"; @@ -111,8 +112,7 @@ export const UserButton = observer((props: UserProps) => { className={styles.icon} onClick={(e) => stopPropagation(e) && - openScreen({ - id: "special_prompt", + modalController.push({ type: "close_dm", target: channel, }) @@ -195,8 +195,7 @@ export const ChannelButton = observer((props: ChannelProps) => { - openScreen({ - id: "special_prompt", + modalController.push({ type: "leave_group", target: channel, }) diff --git a/src/context/intermediate/modals/Prompt.tsx b/src/context/intermediate/modals/Prompt.tsx index fc7478fb..a6add7aa 100644 --- a/src/context/intermediate/modals/Prompt.tsx +++ b/src/context/intermediate/modals/Prompt.tsx @@ -58,19 +58,25 @@ export function PromptModal({ type SpecialProps = { onClose: () => void } & ( | { type: "leave_group"; target: Channel } | { type: "close_dm"; target: Channel } - | { type: "leave_server"; target: Server } - | { type: "delete_server"; target: Server } | { type: "delete_channel"; target: Channel } - | { type: "delete_bot"; target: string; name: string; cb?: () => void } - | { type: "delete_message"; target: MessageI } | { type: "create_invite"; target: Channel; } + + | { type: "leave_server"; target: Server } + | { type: "delete_server"; target: Server } + + | { type: "delete_bot"; target: string; name: string; cb?: () => void } + + | { type: "delete_message"; target: MessageI } + | { type: "kick_member"; target: Server; user: User } | { type: "ban_member"; target: Server; user: User } + | { type: "unfriend_user"; target: User } | { type: "block_user"; target: User } + | { type: "create_channel"; target: Server; @@ -80,6 +86,7 @@ type SpecialProps = { onClose: () => void } & ( }, ) => void; } + | { type: "create_category"; target: Server } ); diff --git a/src/controllers/modals/ModalController.tsx b/src/controllers/modals/ModalController.tsx index 340e1021..9e67b2d2 100644 --- a/src/controllers/modals/ModalController.tsx +++ b/src/controllers/modals/ModalController.tsx @@ -20,10 +20,12 @@ import AddFriend from "./components/AddFriend"; import Changelog from "./components/Changelog"; import ChannelInfo from "./components/ChannelInfo"; import Clipboard from "./components/Clipboard"; +import Confirmation from "./components/Confirmation"; import CreateGroup from "./components/CreateGroup"; import CreateRole from "./components/CreateRole"; import CreateServer from "./components/CreateServer"; import CustomStatus from "./components/CustomStatus"; +import DeleteMessage from "./components/DeleteMessage"; import Error from "./components/Error"; import ImageViewer from "./components/ImageViewer"; import LinkWarning from "./components/LinkWarning"; @@ -231,11 +233,20 @@ export const modalController = new ModalControllerExtended({ changelog: Changelog, channel_info: ChannelInfo, clipboard: Clipboard, + leave_group: Confirmation, + close_dm: Confirmation, + leave_server: Confirmation, + delete_server: Confirmation, + delete_channel: Confirmation, + delete_bot: Confirmation, + block_user: Confirmation, + unfriend_user: Confirmation, create_group: CreateGroup, create_role: CreateRole, create_server: CreateServer, create_bot: CreateBotModal, custom_status: CustomStatus, + delete_message: DeleteMessage, error: Error, image_viewer: ImageViewer, link_warning: LinkWarning, diff --git a/src/controllers/modals/components/Confirmation.tsx b/src/controllers/modals/components/Confirmation.tsx new file mode 100644 index 00000000..aba1c294 --- /dev/null +++ b/src/controllers/modals/components/Confirmation.tsx @@ -0,0 +1,101 @@ +import { useHistory } from "react-router-dom"; + +import { Text } from "preact-i18n"; + +import { ModalForm } from "@revoltchat/ui"; + +import { TextReact } from "../../../lib/i18n"; + +import { clientController } from "../../client/ClientController"; +import { ModalProps } from "../types"; + +/** + * Confirmation modal + */ +export default function Confirmation( + props: ModalProps< + | "leave_group" + | "close_dm" + | "leave_server" + | "delete_server" + | "delete_channel" + | "delete_bot" + | "block_user" + | "unfriend_user" + >, +) { + const history = useHistory(); + + const EVENTS = { + close_dm: ["confirm_close_dm", "close"], + delete_server: ["confirm_delete", "delete"], + delete_channel: ["confirm_delete", "delete"], + delete_bot: ["confirm_delete", "delete"], + leave_group: ["confirm_leave", "leave"], + leave_server: ["confirm_leave", "leave"], + unfriend_user: ["unfriend_user", "remove"], + block_user: ["block_user", "block"], + }; + + const event = EVENTS[props.type]; + let name; + switch (props.type) { + case "unfriend_user": + case "block_user": + name = props.target.username; + break; + case "close_dm": + name = props.target.recipient?.username; + break; + case "delete_bot": + name = props.name; + break; + default: + name = props.target.name; + } + + return ( + + } + description={ + {name} }} + /> + } + data={{}} + schema={{}} + callback={async () => { + switch (props.type) { + case "unfriend_user": + await props.target.removeFriend(); + break; + case "block_user": + await props.target.blockUser(); + break; + case "leave_group": + case "close_dm": + case "delete_channel": + case "leave_server": + case "delete_server": + if (props.type != "delete_channel") history.push("/"); + + props.target.delete(); + break; + case "delete_bot": + clientController + .getAvailableClient() + .bots.delete(props.target); + props.cb?.(); + break; + } + }} + /> + ); +} diff --git a/src/controllers/modals/components/DeleteMessage.tsx b/src/controllers/modals/components/DeleteMessage.tsx new file mode 100644 index 00000000..6dbf95c4 --- /dev/null +++ b/src/controllers/modals/components/DeleteMessage.tsx @@ -0,0 +1,35 @@ +import { Text } from "preact-i18n"; + +import { ModalForm } from "@revoltchat/ui"; + +import Message from "../../../components/common/messaging/Message"; +import { ModalProps } from "../types"; + +/** + * Delete message modal + */ +export default function DeleteMessage({ + target, + ...props +}: ModalProps<"delete_message">) { + return ( + } + description={ + + } + schema={{ + message: "custom", + }} + data={{ + message: { + element: , + }, + }} + callback={() => target.delete()} + /> + ); +} diff --git a/src/controllers/modals/types.ts b/src/controllers/modals/types.ts index 5ffc5fef..37b5e513 100644 --- a/src/controllers/modals/types.ts +++ b/src/controllers/modals/types.ts @@ -1,4 +1,4 @@ -import { API, Client, User, Member, Channel, Server } from "revolt.js"; +import { API, Client, User, Member, Channel, Server, Message } from "revolt.js"; export type Modal = { key?: string; @@ -117,6 +117,69 @@ export type Modal = { server: Server; callback: (id: string) => void; } + | { + type: "leave_group"; + target: Channel; + } + | { + type: "close_dm"; + target: Channel; + } + | { + type: "delete_channel"; + target: Channel; + } + | { + type: "create_invite"; + target: Channel; + } + | { + type: "leave_server"; + target: Server; + } + | { + type: "delete_server"; + target: Server; + } + | { + type: "delete_bot"; + target: string; + name: string; + cb?: () => void; + } + | { + type: "delete_message"; + target: Message; + } + | { + type: "kick_member"; + member: Member; + } + | { + type: "ban_member"; + member: Member; + } + | { + type: "unfriend_user"; + target: User; + } + | { + type: "block_user"; + target: User; + } + | { + type: "create_channel"; + target: Server; + cb?: ( + channel: Channel & { + channel_type: "TextChannel" | "VoiceChannel"; + }, + ) => void; + } + | { + type: "create_category"; + target: Server; + } ); export type ModalProps = Modal & { type: T } & { diff --git a/src/lib/ContextMenus.tsx b/src/lib/ContextMenus.tsx index 67fc3018..1fea137d 100644 --- a/src/lib/ContextMenus.tsx +++ b/src/lib/ContextMenus.tsx @@ -71,8 +71,8 @@ type Action = | { action: "open_link"; link: string } | { action: "copy_link"; link: string } | { action: "remove_member"; channel: Channel; user: User } - | { action: "kick_member"; target: Server; user: User } - | { action: "ban_member"; target: Server; user: User } + | { action: "kick_member"; target: Member } + | { action: "ban_member"; target: Member } | { action: "view_profile"; user: User } | { action: "message_user"; user: User } | { action: "block_user"; user: User } @@ -336,8 +336,7 @@ export default function ContextMenus() { break; case "block_user": - openScreen({ - id: "special_prompt", + modalController.push({ type: "block_user", target: data.user, }); @@ -346,8 +345,7 @@ export default function ContextMenus() { await data.user.unblockUser(); break; case "remove_friend": - openScreen({ - id: "special_prompt", + modalController.push({ type: "unfriend_user", target: data.user, }); @@ -374,26 +372,34 @@ export default function ContextMenus() { break; case "clear_status": - { - await client.users.edit({ remove: ["StatusText"] }); - } + await client.users.edit({ remove: ["StatusText"] }); + break; + + case "delete_message": + modalController.push({ + type: "delete_message", + target: data.target, + }); break; case "leave_group": case "close_dm": - case "leave_server": case "delete_channel": - case "delete_server": - case "delete_message": - case "create_channel": - case "create_category": case "create_invite": - // Typescript flattens the case types into a single type and type structure and specifity is lost - openScreen({ - id: "special_prompt", + modalController.push({ type: data.action, target: data.target, - } as unknown as Screen); + }); + break; + + case "leave_server": + case "delete_server": + case "create_channel": + case "create_category": + modalController.push({ + type: data.action, + target: data.target, + }); break; case "edit_identity": @@ -405,11 +411,9 @@ export default function ContextMenus() { case "ban_member": case "kick_member": - openScreen({ - id: "special_prompt", + modalController.push({ type: data.action, - target: data.target, - user: data.user, + member: data.target, }); break; @@ -669,8 +673,10 @@ export default function ContextMenus() { generateAction( { action: "kick_member", - target: server, - user: user!, + target: client.members.getKey({ + server: server._id, + user: user!._id, + })!, }, undefined, // this is needed because generateAction uses positional, not named parameters undefined, @@ -682,8 +688,10 @@ export default function ContextMenus() { generateAction( { action: "ban_member", - target: server, - user: user!, + target: client.members.getKey({ + server: server._id, + user: user!._id, + })!, }, undefined, undefined, diff --git a/src/pages/channels/messaging/MessageEditor.tsx b/src/pages/channels/messaging/MessageEditor.tsx index 8e64361a..cf5ed489 100644 --- a/src/pages/channels/messaging/MessageEditor.tsx +++ b/src/pages/channels/messaging/MessageEditor.tsx @@ -14,6 +14,7 @@ import { import AutoComplete, { useAutoComplete, } from "../../../components/common/AutoComplete"; +import { modalController } from "../../../controllers/modals/ModalController"; const EditorBase = styled.div` display: flex; @@ -50,14 +51,12 @@ interface Props { export default function MessageEditor({ message, finish }: Props) { const [content, setContent] = useState(message.content ?? ""); const { focusTaken } = useContext(IntermediateContext); - const { openScreen } = useIntermediate(); async function save() { finish(); if (content.length === 0) { - openScreen({ - id: "special_prompt", + modalController.push({ type: "delete_message", target: message, }); diff --git a/src/pages/channels/voice/VoiceHeader.tsx b/src/pages/channels/voice/VoiceHeader.tsx index 242e1041..89ef577c 100644 --- a/src/pages/channels/voice/VoiceHeader.tsx +++ b/src/pages/channels/voice/VoiceHeader.tsx @@ -21,6 +21,7 @@ import { useIntermediate } from "../../../context/intermediate/Intermediate"; import Tooltip from "../../../components/common/Tooltip"; import UserIcon from "../../../components/common/user/UserIcon"; import { useClient } from "../../../controllers/client/ClientController"; +import { modalController } from "../../../controllers/modals/ModalController"; interface Props { id: string; @@ -100,26 +101,27 @@ export default observer(({ id }: Props) => {
{users && users.length !== 0 ? users.map((user, index) => { - const id = keys![index]; + const user_id = keys![index]; return ( -
+
- openScreen({ - id: "profile", - user_id: id, + modalController.push({ + type: "user_profile", + user_id, }) } /> diff --git a/src/pages/friends/Friend.tsx b/src/pages/friends/Friend.tsx index f93d3531..7e3c1ef1 100644 --- a/src/pages/friends/Friend.tsx +++ b/src/pages/friends/Friend.tsx @@ -102,8 +102,7 @@ export const Friend = observer(({ user }: Props) => { stopPropagation( ev, user.relationship === "Friend" - ? openScreen({ - id: "special_prompt", + ? modalController.push({ type: "unfriend_user", target: user, }) diff --git a/src/pages/settings/ServerSettings.tsx b/src/pages/settings/ServerSettings.tsx index babd2755..72f2925f 100644 --- a/src/pages/settings/ServerSettings.tsx +++ b/src/pages/settings/ServerSettings.tsx @@ -20,6 +20,7 @@ import { useIntermediate } from "../../context/intermediate/Intermediate"; import ButtonItem from "../../components/navigation/items/ButtonItem"; import { useClient } from "../../controllers/client/ClientController"; import RequiresOnline from "../../controllers/client/jsx/RequiresOnline"; +import { modalController } from "../../controllers/modals/ModalController"; import { GenericSettings } from "./GenericSettings"; import { Bans } from "./server/Bans"; import { Categories } from "./server/Categories"; @@ -132,8 +133,7 @@ export default observer(() => { - openScreen({ - id: "special_prompt", + modalController.push({ type: "delete_server", target: server, }) diff --git a/src/pages/settings/panes/MyBots.tsx b/src/pages/settings/panes/MyBots.tsx index 4f3e26fd..158ca952 100644 --- a/src/pages/settings/panes/MyBots.tsx +++ b/src/pages/settings/panes/MyBots.tsx @@ -218,8 +218,8 @@ function BotCard({ bot, onDelete, onUpdate }: Props) { target={user} size={42} onClick={() => - openScreen({ - id: "profile", + modalController.push({ + type: "user_profile", user_id: user._id, }) } @@ -460,8 +460,7 @@ function BotCard({ bot, onDelete, onUpdate }: Props) { palette="error" onClick={async () => { setSaving(true); - openScreen({ - id: "special_prompt", + modalController.push({ type: "delete_bot", target: bot._id, name: user.username, @@ -508,16 +507,14 @@ export const MyBots = observer(() => { // eslint-disable-next-line }, []); - const { openScreen } = useIntermediate(); - return (
} onClick={() => - openScreen({ - id: "create_bot", + modalController.push({ + type: "create_bot", onCreate: (bot) => setBots([...(bots ?? []), bot]), }) } diff --git a/src/pages/settings/server/Categories.tsx b/src/pages/settings/server/Categories.tsx index 9af11841..cf3bb5c0 100644 --- a/src/pages/settings/server/Categories.tsx +++ b/src/pages/settings/server/Categories.tsx @@ -17,6 +17,7 @@ import { noop } from "../../../lib/js"; import { useIntermediate } from "../../../context/intermediate/Intermediate"; import ChannelIcon from "../../../components/common/ChannelIcon"; +import { modalController } from "../../../controllers/modals/ModalController"; const KanbanEntry = styled.div` padding: 2px 4px; @@ -449,8 +450,7 @@ function ListElement({ - openScreen({ - id: "special_prompt", + modalController.push({ type: "create_channel", target: server, cb: addChannel, diff --git a/yarn.lock b/yarn.lock index b674b8fd..621d8370 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2231,9 +2231,9 @@ __metadata: languageName: node linkType: hard -"@revoltchat/ui@npm:1.0.61": - version: 1.0.61 - resolution: "@revoltchat/ui@npm:1.0.61" +"@revoltchat/ui@npm:1.0.63": + version: 1.0.63 + resolution: "@revoltchat/ui@npm:1.0.63" dependencies: "@styled-icons/boxicons-logos": ^10.38.0 "@styled-icons/boxicons-regular": ^10.38.0 @@ -2247,7 +2247,7 @@ __metadata: react-virtuoso: ^2.12.0 peerDependencies: revolt.js: "*" - checksum: 678584f84cb3d43307507058eb434507cb1d201455f9da6618eb1731c6439240ddc16601dc636b5790a83393884770bf4e8fee75d235871f65ba5abcd8f685ac + checksum: 4e785798d31b503bb7777da7661656cbbeb17734257aa4578357e6acf2d053abeb40e406f1e04c7b1338822260d5614ffb8262cb64ae95ce3b1c7edda0c2125b languageName: node linkType: hard @@ -3554,7 +3554,7 @@ __metadata: "@hcaptcha/react-hcaptcha": ^0.3.6 "@insertish/vite-plugin-babel-macros": ^1.0.5 "@preact/preset-vite": ^2.0.0 - "@revoltchat/ui": 1.0.61 + "@revoltchat/ui": 1.0.63 "@rollup/plugin-replace": ^2.4.2 "@styled-icons/boxicons-logos": ^10.38.0 "@styled-icons/boxicons-regular": ^10.38.0 From ec347f585d16bc95c207b8eaf1e36be482496ebb Mon Sep 17 00:00:00 2001 From: Paul Makles Date: Tue, 5 Jul 2022 20:11:32 +0100 Subject: [PATCH 122/151] feat: port `CreateInvite` --- src/controllers/modals/ModalController.tsx | 11 ++- .../modals/components/CreateInvite.tsx | 76 +++++++++++++++++++ .../modals/components/UserPicker.tsx | 2 +- 3 files changed, 86 insertions(+), 3 deletions(-) create mode 100644 src/controllers/modals/components/CreateInvite.tsx diff --git a/src/controllers/modals/ModalController.tsx b/src/controllers/modals/ModalController.tsx index 9e67b2d2..8080f786 100644 --- a/src/controllers/modals/ModalController.tsx +++ b/src/controllers/modals/ModalController.tsx @@ -14,7 +14,6 @@ import { injectController } from "../../lib/window"; import { getApplicationState } from "../../mobx/State"; import { history } from "../../context/history"; -import { __thisIsAHack } from "../../context/intermediate/Intermediate"; import AddFriend from "./components/AddFriend"; import Changelog from "./components/Changelog"; @@ -22,6 +21,7 @@ import ChannelInfo from "./components/ChannelInfo"; import Clipboard from "./components/Clipboard"; import Confirmation from "./components/Confirmation"; import CreateGroup from "./components/CreateGroup"; +import CreateInvite from "./components/CreateInvite"; import CreateRole from "./components/CreateRole"; import CreateServer from "./components/CreateServer"; import CustomStatus from "./components/CustomStatus"; @@ -40,7 +40,7 @@ import ServerInfo from "./components/ServerInfo"; import ShowToken from "./components/ShowToken"; import SignOutSessions from "./components/SignOutSessions"; import SignedOut from "./components/SignedOut"; -import { UserPicker } from "./components/UserPicker"; +import UserPicker from "./components/UserPicker"; import { CreateBotModal } from "./components/legacy/CreateBot"; import { OnboardingModal } from "./components/legacy/Onboarding"; import { UserProfile } from "./components/legacy/UserProfile"; @@ -195,6 +195,12 @@ class ModalControllerExtended extends ModalController { } } + /** + * Safely open external or internal link + * @param href Raw URL + * @param trusted Whether we trust this link + * @returns Whether to cancel default event + */ openLink(href?: string, trusted?: boolean) { const link = determineLink(href); const settings = getApplicationState().settings; @@ -242,6 +248,7 @@ export const modalController = new ModalControllerExtended({ block_user: Confirmation, unfriend_user: Confirmation, create_group: CreateGroup, + create_invite: CreateInvite, create_role: CreateRole, create_server: CreateServer, create_bot: CreateBotModal, diff --git a/src/controllers/modals/components/CreateInvite.tsx b/src/controllers/modals/components/CreateInvite.tsx new file mode 100644 index 00000000..a2ddbd95 --- /dev/null +++ b/src/controllers/modals/components/CreateInvite.tsx @@ -0,0 +1,76 @@ +import styled from "styled-components"; + +import { Text } from "preact-i18n"; +import { useEffect, useState } from "preact/hooks"; + +import { ModalForm } from "@revoltchat/ui"; + +import { noopAsync } from "../../../lib/js"; + +import { takeError } from "../../../context/revoltjs/util"; + +import { modalController } from "../ModalController"; +import { ModalProps } from "../types"; + +/** + * Code block which displays invite + */ +const Invite = styled.div` + display: flex; + flex-direction: column; + + code { + padding: 1em; + user-select: all; + font-size: 1.4em; + text-align: center; + font-family: var(--monospace-font); + } +`; + +/** + * Create invite modal + */ +export default function CreateInvite({ + target, + ...props +}: ModalProps<"create_invite">) { + const [processing, setProcessing] = useState(false); + const [code, setCode] = useState("abcdef"); + + // Generate an invite code + useEffect(() => { + setProcessing(true); + + target + .createInvite() + .then(({ _id }) => setCode(_id)) + .catch((err) => + modalController.push({ type: "error", error: takeError(err) }), + ) + .finally(() => setProcessing(false)); + }, [target]); + + return ( + } + schema={{ + message: "custom", + }} + data={{ + message: { + element: processing ? ( + + ) : ( + + + {code} + + ), + }, + }} + callback={noopAsync} + /> + ); +} diff --git a/src/controllers/modals/components/UserPicker.tsx b/src/controllers/modals/components/UserPicker.tsx index b4cc6633..86727a00 100644 --- a/src/controllers/modals/components/UserPicker.tsx +++ b/src/controllers/modals/components/UserPicker.tsx @@ -15,7 +15,7 @@ const List = styled.div` overflow-y: scroll; `; -export function UserPicker({ +export default function UserPicker({ callback, omit, ...props From 4009b19f9c4ef7d0b62693e5cea8c80bb00d3dd8 Mon Sep 17 00:00:00 2001 From: Paul Makles Date: Tue, 5 Jul 2022 20:25:00 +0100 Subject: [PATCH 123/151] feat: port `BanMember`, `KickMember` modals --- src/controllers/modals/ModalController.tsx | 4 ++ .../modals/components/BanMember.tsx | 46 +++++++++++++++++++ .../modals/components/KickMember.tsx | 38 +++++++++++++++ 3 files changed, 88 insertions(+) create mode 100644 src/controllers/modals/components/BanMember.tsx create mode 100644 src/controllers/modals/components/KickMember.tsx diff --git a/src/controllers/modals/ModalController.tsx b/src/controllers/modals/ModalController.tsx index 8080f786..feebb85e 100644 --- a/src/controllers/modals/ModalController.tsx +++ b/src/controllers/modals/ModalController.tsx @@ -16,6 +16,7 @@ import { getApplicationState } from "../../mobx/State"; import { history } from "../../context/history"; import AddFriend from "./components/AddFriend"; +import BanMember from "./components/BanMember"; import Changelog from "./components/Changelog"; import ChannelInfo from "./components/ChannelInfo"; import Clipboard from "./components/Clipboard"; @@ -28,6 +29,7 @@ import CustomStatus from "./components/CustomStatus"; import DeleteMessage from "./components/DeleteMessage"; import Error from "./components/Error"; import ImageViewer from "./components/ImageViewer"; +import KickMember from "./components/KickMember"; import LinkWarning from "./components/LinkWarning"; import MFAEnableTOTP from "./components/MFAEnableTOTP"; import MFAFlow from "./components/MFAFlow"; @@ -236,6 +238,7 @@ class ModalControllerExtended extends ModalController { export const modalController = new ModalControllerExtended({ add_friend: AddFriend, + ban_member: BanMember, changelog: Changelog, channel_info: ChannelInfo, clipboard: Clipboard, @@ -256,6 +259,7 @@ export const modalController = new ModalControllerExtended({ delete_message: DeleteMessage, error: Error, image_viewer: ImageViewer, + kick_member: KickMember, link_warning: LinkWarning, mfa_flow: MFAFlow, mfa_recovery: MFARecovery, diff --git a/src/controllers/modals/components/BanMember.tsx b/src/controllers/modals/components/BanMember.tsx new file mode 100644 index 00000000..640d9ce9 --- /dev/null +++ b/src/controllers/modals/components/BanMember.tsx @@ -0,0 +1,46 @@ +import { Text } from "preact-i18n"; + +import { Column, ModalForm } from "@revoltchat/ui"; + +import UserIcon from "../../../components/common/user/UserIcon"; +import { ModalProps } from "../types"; + +/** + * Ban member modal + */ +export default function BanMember({ + member, + ...props +}: ModalProps<"ban_member">) { + return ( + } + schema={{ + member: "custom", + reason: "text", + }} + data={{ + member: { + element: ( + + + + + ), + }, + reason: { + field: ( + + ) as React.ReactChild, + }, + }} + callback={async ({ reason }) => + void (await member.server!.banUser(member._id.user, { reason })) + } + /> + ); +} diff --git a/src/controllers/modals/components/KickMember.tsx b/src/controllers/modals/components/KickMember.tsx new file mode 100644 index 00000000..8631d519 --- /dev/null +++ b/src/controllers/modals/components/KickMember.tsx @@ -0,0 +1,38 @@ +import { Text } from "preact-i18n"; + +import { Column, ModalForm } from "@revoltchat/ui"; + +import UserIcon from "../../../components/common/user/UserIcon"; +import { ModalProps } from "../types"; + +/** + * Kick member modal + */ +export default function KickMember({ + member, + ...props +}: ModalProps<"kick_member">) { + return ( + } + schema={{ + member: "custom", + }} + data={{ + member: { + element: ( + + + + + ), + }, + }} + callback={() => member.kick()} + /> + ); +} From 29fb8e0064a09ee4557043d27aa14faf13b70ddd Mon Sep 17 00:00:00 2001 From: Paul Makles Date: Tue, 5 Jul 2022 20:37:40 +0100 Subject: [PATCH 124/151] feat: port `CreateChannel` modal --- src/controllers/modals/ModalController.tsx | 2 + .../modals/components/CreateChannel.tsx | 69 +++++++++++++++++++ src/controllers/modals/types.ts | 6 +- 3 files changed, 72 insertions(+), 5 deletions(-) create mode 100644 src/controllers/modals/components/CreateChannel.tsx diff --git a/src/controllers/modals/ModalController.tsx b/src/controllers/modals/ModalController.tsx index feebb85e..cbb0dbb9 100644 --- a/src/controllers/modals/ModalController.tsx +++ b/src/controllers/modals/ModalController.tsx @@ -21,6 +21,7 @@ import Changelog from "./components/Changelog"; import ChannelInfo from "./components/ChannelInfo"; import Clipboard from "./components/Clipboard"; import Confirmation from "./components/Confirmation"; +import CreateChannel from "./components/CreateChannel"; import CreateGroup from "./components/CreateGroup"; import CreateInvite from "./components/CreateInvite"; import CreateRole from "./components/CreateRole"; @@ -250,6 +251,7 @@ export const modalController = new ModalControllerExtended({ delete_bot: Confirmation, block_user: Confirmation, unfriend_user: Confirmation, + create_channel: CreateChannel, create_group: CreateGroup, create_invite: CreateInvite, create_role: CreateRole, diff --git a/src/controllers/modals/components/CreateChannel.tsx b/src/controllers/modals/components/CreateChannel.tsx new file mode 100644 index 00000000..14d2e696 --- /dev/null +++ b/src/controllers/modals/components/CreateChannel.tsx @@ -0,0 +1,69 @@ +import { useHistory } from "react-router-dom"; + +import { Text } from "preact-i18n"; + +import { ModalForm } from "@revoltchat/ui"; + +import { ModalProps } from "../types"; + +/** + * Channel creation modal + */ +export default function CreateChannel({ + cb, + target, + ...props +}: ModalProps<"create_channel">) { + const history = useHistory(); + + return ( + } + schema={{ + name: "text", + type: "radio", + }} + data={{ + name: { + field: ( + + ) as React.ReactChild, + }, + type: { + field: ( + + ) as React.ReactChild, + choices: [ + { + name: ( + + ) as React.ReactChild, + value: "Text", + }, + { + name: ( + + ) as React.ReactChild, + value: "Voice", + }, + ], + }, + }} + callback={async ({ name, type }) => { + const channel = await target.createChannel({ + type: type as "Text" | "Voice", + name, + }); + + if (cb) { + cb(channel as any); + } else { + history.push( + `/server/${target._id}/channel/${channel._id}`, + ); + } + }} + /> + ); +} diff --git a/src/controllers/modals/types.ts b/src/controllers/modals/types.ts index 37b5e513..1bad3bb2 100644 --- a/src/controllers/modals/types.ts +++ b/src/controllers/modals/types.ts @@ -170,11 +170,7 @@ export type Modal = { | { type: "create_channel"; target: Server; - cb?: ( - channel: Channel & { - channel_type: "TextChannel" | "VoiceChannel"; - }, - ) => void; + cb?: (channel: Channel) => void; } | { type: "create_category"; From f7ff7d0dfe085a9f1e58318286fcc3d153d6f01d Mon Sep 17 00:00:00 2001 From: Paul Makles Date: Tue, 5 Jul 2022 20:55:24 +0100 Subject: [PATCH 125/151] feat: port `CreateCategory` / fix `Channel` --- package.json | 2 +- src/controllers/modals/ModalController.tsx | 2 + .../modals/components/CreateCategory.tsx | 44 +++++++++++++++++++ .../modals/components/CreateChannel.tsx | 3 ++ yarn.lock | 10 ++--- 5 files changed, 55 insertions(+), 6 deletions(-) create mode 100644 src/controllers/modals/components/CreateCategory.tsx diff --git a/package.json b/package.json index 5cf1afb3..ddc93912 100644 --- a/package.json +++ b/package.json @@ -73,7 +73,7 @@ "@hcaptcha/react-hcaptcha": "^0.3.6", "@insertish/vite-plugin-babel-macros": "^1.0.5", "@preact/preset-vite": "^2.0.0", - "@revoltchat/ui": "1.0.63", + "@revoltchat/ui": "1.0.65", "@rollup/plugin-replace": "^2.4.2", "@styled-icons/boxicons-logos": "^10.38.0", "@styled-icons/boxicons-regular": "^10.38.0", diff --git a/src/controllers/modals/ModalController.tsx b/src/controllers/modals/ModalController.tsx index cbb0dbb9..b2667f98 100644 --- a/src/controllers/modals/ModalController.tsx +++ b/src/controllers/modals/ModalController.tsx @@ -21,6 +21,7 @@ import Changelog from "./components/Changelog"; import ChannelInfo from "./components/ChannelInfo"; import Clipboard from "./components/Clipboard"; import Confirmation from "./components/Confirmation"; +import CreateCategory from "./components/CreateCategory"; import CreateChannel from "./components/CreateChannel"; import CreateGroup from "./components/CreateGroup"; import CreateInvite from "./components/CreateInvite"; @@ -251,6 +252,7 @@ export const modalController = new ModalControllerExtended({ delete_bot: Confirmation, block_user: Confirmation, unfriend_user: Confirmation, + create_category: CreateCategory, create_channel: CreateChannel, create_group: CreateGroup, create_invite: CreateInvite, diff --git a/src/controllers/modals/components/CreateCategory.tsx b/src/controllers/modals/components/CreateCategory.tsx new file mode 100644 index 00000000..23cf7298 --- /dev/null +++ b/src/controllers/modals/components/CreateCategory.tsx @@ -0,0 +1,44 @@ +import { ulid } from "ulid"; + +import { Text } from "preact-i18n"; + +import { ModalForm } from "@revoltchat/ui"; + +import { ModalProps } from "../types"; + +/** + * Category creation modal + */ +export default function CreateCategory({ + target, + ...props +}: ModalProps<"create_category">) { + return ( + } + schema={{ + name: "text", + }} + data={{ + name: { + field: ( + + ) as React.ReactChild, + }, + }} + callback={async ({ name }) => { + await target.edit({ + categories: [ + ...(target.categories ?? []), + { + id: ulid(), + title: name, + channels: [], + }, + ], + }); + }} + /> + ); +} diff --git a/src/controllers/modals/components/CreateChannel.tsx b/src/controllers/modals/components/CreateChannel.tsx index 14d2e696..c9ffbdad 100644 --- a/src/controllers/modals/components/CreateChannel.tsx +++ b/src/controllers/modals/components/CreateChannel.tsx @@ -50,6 +50,9 @@ export default function CreateChannel({ ], }, }} + defaults={{ + type: "Text", + }} callback={async ({ name, type }) => { const channel = await target.createChannel({ type: type as "Text" | "Voice", diff --git a/yarn.lock b/yarn.lock index 621d8370..5d8aca1b 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2231,9 +2231,9 @@ __metadata: languageName: node linkType: hard -"@revoltchat/ui@npm:1.0.63": - version: 1.0.63 - resolution: "@revoltchat/ui@npm:1.0.63" +"@revoltchat/ui@npm:1.0.65": + version: 1.0.65 + resolution: "@revoltchat/ui@npm:1.0.65" dependencies: "@styled-icons/boxicons-logos": ^10.38.0 "@styled-icons/boxicons-regular": ^10.38.0 @@ -2247,7 +2247,7 @@ __metadata: react-virtuoso: ^2.12.0 peerDependencies: revolt.js: "*" - checksum: 4e785798d31b503bb7777da7661656cbbeb17734257aa4578357e6acf2d053abeb40e406f1e04c7b1338822260d5614ffb8262cb64ae95ce3b1c7edda0c2125b + checksum: 34a52672aa3aca1c62d884c62f2c85c0100b6adb21b7d01053e74e96cfa95cb76f125e1ca1f46afc586a8730b273746486525199f66969338e6c84ab1f21e7cd languageName: node linkType: hard @@ -3554,7 +3554,7 @@ __metadata: "@hcaptcha/react-hcaptcha": ^0.3.6 "@insertish/vite-plugin-babel-macros": ^1.0.5 "@preact/preset-vite": ^2.0.0 - "@revoltchat/ui": 1.0.63 + "@revoltchat/ui": 1.0.65 "@rollup/plugin-replace": ^2.4.2 "@styled-icons/boxicons-logos": ^10.38.0 "@styled-icons/boxicons-regular": ^10.38.0 From f9c6f5cd9d320b3c3156371f63009b9e31ab51b3 Mon Sep 17 00:00:00 2001 From: Paul Makles Date: Tue, 5 Jul 2022 21:13:42 +0100 Subject: [PATCH 126/151] chore: delete intermediate --- src/components/common/messaging/Message.tsx | 3 - .../messaging/bars/MessageOverlayBar.tsx | 11 +- .../common/messaging/embed/Embed.tsx | 9 +- src/components/common/user/UserHeader.tsx | 9 +- src/components/markdown/Renderer.tsx | 66 +-- .../navigation/items/ButtonItem.tsx | 4 - .../navigation/left/HomeSidebar.tsx | 3 - .../settings/appearance/legacy/ThemeTools.tsx | 20 +- src/context/index.tsx | 7 +- src/context/intermediate/Intermediate.tsx | 219 ------- src/context/intermediate/Modals.tsx | 25 - src/context/intermediate/Popovers.tsx | 26 - src/context/intermediate/modals/Input.tsx | 99 ---- .../intermediate/modals/Prompt.module.scss | 18 - src/context/intermediate/modals/Prompt.tsx | 561 ------------------ .../popovers/UserPicker.module.scss | 5 - src/context/revoltjs/FileUploads.tsx | 4 +- src/controllers/client/Session.tsx | 2 - src/controllers/modals/ModalRenderer.tsx | 22 +- .../modals/components/ImportTheme.tsx | 32 + src/controllers/modals/types.ts | 3 + src/lib/ContextMenus.tsx | 18 +- src/pages/RevoltApp.tsx | 3 - src/pages/channels/actions/HeaderActions.tsx | 3 - src/pages/channels/messaging/MessageArea.tsx | 8 +- .../channels/messaging/MessageEditor.tsx | 10 +- src/pages/channels/voice/VoiceHeader.tsx | 4 - src/pages/discover/Discover.tsx | 6 +- src/pages/friends/Friend.tsx | 3 - src/pages/friends/Friends.tsx | 4 - src/pages/home/Home.tsx | 3 - src/pages/settings/ServerSettings.tsx | 3 - src/pages/settings/Settings.tsx | 3 - src/pages/settings/panes/MyBots.tsx | 10 +- src/pages/settings/server/Categories.tsx | 7 +- 35 files changed, 129 insertions(+), 1104 deletions(-) delete mode 100644 src/context/intermediate/Intermediate.tsx delete mode 100644 src/context/intermediate/Modals.tsx delete mode 100644 src/context/intermediate/Popovers.tsx delete mode 100644 src/context/intermediate/modals/Input.tsx delete mode 100644 src/context/intermediate/modals/Prompt.module.scss delete mode 100644 src/context/intermediate/modals/Prompt.tsx delete mode 100644 src/context/intermediate/popovers/UserPicker.module.scss create mode 100644 src/controllers/modals/components/ImportTheme.tsx diff --git a/src/components/common/messaging/Message.tsx b/src/components/common/messaging/Message.tsx index 8f684141..e7521660 100644 --- a/src/components/common/messaging/Message.tsx +++ b/src/components/common/messaging/Message.tsx @@ -13,7 +13,6 @@ import { isTouchscreenDevice } from "../../../lib/isTouchscreenDevice"; import { QueuedMessage } from "../../../mobx/stores/MessageQueue"; import { I18nError } from "../../../context/Locale"; -import { useIntermediate } from "../../../context/intermediate/Intermediate"; import { modalController } from "../../../controllers/modals/ModalController"; import Markdown from "../../markdown/Markdown"; @@ -55,8 +54,6 @@ const Message = observer( const client = message.client; const user = message.author; - const { openScreen } = useIntermediate(); - const content = message.content; const head = preferHead || (message.reply_ids && message.reply_ids.length > 0); diff --git a/src/components/common/messaging/bars/MessageOverlayBar.tsx b/src/components/common/messaging/bars/MessageOverlayBar.tsx index 9104593f..8f38e655 100644 --- a/src/components/common/messaging/bars/MessageOverlayBar.tsx +++ b/src/components/common/messaging/bars/MessageOverlayBar.tsx @@ -19,12 +19,6 @@ import { getRenderer } from "../../../../lib/renderer/Singleton"; import { QueuedMessage } from "../../../../mobx/stores/MessageQueue"; -import { - Screen, - useIntermediate, -} from "../../../../context/intermediate/Intermediate"; - -import { useClient } from "../../../../controllers/client/ClientController"; import { modalController } from "../../../../controllers/modals/ModalController"; import Tooltip from "../../../common/Tooltip"; @@ -89,7 +83,6 @@ const Divider = styled.div` export const MessageOverlayBar = observer(({ message, queued }: Props) => { const client = message.client; - const { openScreen, writeClipboard } = useIntermediate(); const isAuthor = message.author_id === client.user!._id; const [copied, setCopied] = useState<"link" | "id">(null!); @@ -189,7 +182,7 @@ export const MessageOverlayBar = observer(({ message, queued }: Props) => { { setCopied("link"); - writeClipboard(message.url); + modalController.writeText(message.url); }}> @@ -200,7 +193,7 @@ export const MessageOverlayBar = observer(({ message, queued }: Props) => { { setCopied("id"); - writeClipboard(message._id); + modalController.writeText(message._id); }}> diff --git a/src/components/common/messaging/embed/Embed.tsx b/src/components/common/messaging/embed/Embed.tsx index 822f30b2..3d04c44f 100644 --- a/src/components/common/messaging/embed/Embed.tsx +++ b/src/components/common/messaging/embed/Embed.tsx @@ -4,8 +4,6 @@ import styles from "./Embed.module.scss"; import classNames from "classnames"; import { useContext } from "preact/hooks"; -import { useIntermediate } from "../../../../context/intermediate/Intermediate"; - import { useClient } from "../../../../controllers/client/ClientController"; import { modalController } from "../../../../controllers/modals/ModalController"; import { MessageAreaWidthContext } from "../../../../pages/channels/messaging/MessageArea"; @@ -25,7 +23,6 @@ const MAX_PREVIEW_SIZE = 150; export default function Embed({ embed }: Props) { const client = useClient(); - const { openLink } = useIntermediate(); const maxWidth = Math.min( useContext(MessageAreaWidthContext) - CONTAINER_PADDING, MAX_EMBED_WIDTH, @@ -144,7 +141,7 @@ export default function Embed({ embed }: Props) { (ev.button === 0 || ev.button === 1) && - openLink(embed.url!) + modalController.openLink(embed.url!) } className={styles.title}> {embed.title} @@ -195,7 +192,9 @@ export default function Embed({ embed }: Props) { onClick={() => modalController.push({ type: "image_viewer", embed }) } - onMouseDown={(ev) => ev.button === 1 && openLink(embed.url)} + onMouseDown={(ev) => + ev.button === 1 && modalController.openLink(embed.url) + } /> ); } diff --git a/src/components/common/user/UserHeader.tsx b/src/components/common/user/UserHeader.tsx index 0ab85bfd..1c442799 100644 --- a/src/components/common/user/UserHeader.tsx +++ b/src/components/common/user/UserHeader.tsx @@ -11,8 +11,7 @@ import { Header, IconButton } from "@revoltchat/ui"; import { isTouchscreenDevice } from "../../../lib/isTouchscreenDevice"; -import { useIntermediate } from "../../../context/intermediate/Intermediate"; - +import { modalController } from "../../../controllers/modals/ModalController"; import Tooltip from "../Tooltip"; import UserStatus from "./UserStatus"; @@ -48,8 +47,6 @@ interface Props { } export default observer(({ user }: Props) => { - const { writeClipboard } = useIntermediate(); - return (
@@ -57,7 +54,9 @@ export default observer(({ user }: Props) => { }> writeClipboard(user.username)}> + onClick={() => + modalController.writeText(user.username) + }> @{user.username} diff --git a/src/components/markdown/Renderer.tsx b/src/components/markdown/Renderer.tsx index bcce42b8..73eaedd3 100644 --- a/src/components/markdown/Renderer.tsx +++ b/src/components/markdown/Renderer.tsx @@ -14,10 +14,10 @@ import { internalEmit } from "../../lib/eventEmitter"; import { determineLink } from "../../lib/links"; import { dayjs } from "../../context/Locale"; -import { useIntermediate } from "../../context/intermediate/Intermediate"; import { emojiDictionary } from "../../assets/emojis"; import { useClient } from "../../controllers/client/ClientController"; +import { modalController } from "../../controllers/modals/ModalController"; import { generateEmoji } from "../common/Emoji"; import { MarkdownProps } from "./Markdown"; import Prism from "./prism"; @@ -119,7 +119,6 @@ const RE_TIME = //g; export default function Renderer({ content, disallowBigEmoji }: MarkdownProps) { const client = useClient(); - const { openLink } = useIntermediate(); if (typeof content === "undefined") return null; if (!content || content.length === 0) return null; @@ -191,43 +190,40 @@ export default function Renderer({ content, disallowBigEmoji }: MarkdownProps) { } }, []); - const handleLink = useCallback( - (ev: MouseEvent) => { - if (ev.currentTarget) { - const element = ev.currentTarget as HTMLAnchorElement; + const handleLink = useCallback((ev: MouseEvent) => { + if (ev.currentTarget) { + const element = ev.currentTarget as HTMLAnchorElement; - if (ev.shiftKey) { - switch (element.dataset.type) { - case "mention": { - internalEmit( - "MessageBox", - "append", - `<@${element.dataset.mentionId}>`, - "mention", - ); - ev.preventDefault(); - return; - } - case "channel_mention": { - internalEmit( - "MessageBox", - "append", - `<#${element.dataset.mentionId}>`, - "channel_mention", - ); - ev.preventDefault(); - return; - } + if (ev.shiftKey) { + switch (element.dataset.type) { + case "mention": { + internalEmit( + "MessageBox", + "append", + `<@${element.dataset.mentionId}>`, + "mention", + ); + ev.preventDefault(); + return; + } + case "channel_mention": { + internalEmit( + "MessageBox", + "append", + `<#${element.dataset.mentionId}>`, + "channel_mention", + ); + ev.preventDefault(); + return; } } - - if (openLink(element.href)) { - ev.preventDefault(); - } } - }, - [openLink], - ); + + if (modalController.openLink(element.href)) { + ev.preventDefault(); + } + } + }, []); return ( { channel, ...divProps } = props; - const { openScreen } = useIntermediate(); return (
{ return ; } - const { openScreen } = useIntermediate(); const alerting = alert && !muted && !active; return ( diff --git a/src/components/navigation/left/HomeSidebar.tsx b/src/components/navigation/left/HomeSidebar.tsx index b1f1d659..e61439f9 100644 --- a/src/components/navigation/left/HomeSidebar.tsx +++ b/src/components/navigation/left/HomeSidebar.tsx @@ -20,8 +20,6 @@ import { isTouchscreenDevice } from "../../../lib/isTouchscreenDevice"; import { useApplicationState } from "../../../mobx/State"; -import { useIntermediate } from "../../../context/intermediate/Intermediate"; - import placeholderSVG from "../items/placeholder.svg"; import { useClient } from "../../../controllers/client/ClientController"; @@ -50,7 +48,6 @@ export default observer(() => { const client = useClient(); const state = useApplicationState(); const { channel: channel_id } = useParams<{ channel: string }>(); - const { openScreen } = useIntermediate(); const channels = [...client.channels.values()].filter( (x) => diff --git a/src/components/settings/appearance/legacy/ThemeTools.tsx b/src/components/settings/appearance/legacy/ThemeTools.tsx index bd3a3f5b..db5edff0 100644 --- a/src/components/settings/appearance/legacy/ThemeTools.tsx +++ b/src/components/settings/appearance/legacy/ThemeTools.tsx @@ -7,8 +7,7 @@ import { Button } from "@revoltchat/ui"; import { useApplicationState } from "../../../../mobx/State"; -import { useIntermediate } from "../../../../context/intermediate/Intermediate"; - +import { modalController } from "../../../../controllers/modals/ModalController"; import Tooltip from "../../../common/Tooltip"; const Actions = styled.div` @@ -38,7 +37,6 @@ const Actions = styled.div` `; export default function ThemeTools() { - const { writeClipboard, openScreen } = useIntermediate(); const theme = useApplicationState().settings.theme; return ( @@ -56,7 +54,9 @@ export default function ThemeTools() {
writeClipboard(JSON.stringify(theme))}> + onClick={() => + modalController.writeText(JSON.stringify(theme)) + }> }> {" "} {JSON.stringify(theme)} @@ -72,16 +72,8 @@ export default function ThemeTools() { const text = await navigator.clipboard.readText(); theme.hydrate(JSON.parse(text)); } catch (err) { - openScreen({ - id: "_input", - question: ( - - ), - field: ( - - ), - callback: async (text) => - theme.hydrate(JSON.parse(text)), + modalController.push({ + type: "import_theme", }); } }}> diff --git a/src/context/index.tsx b/src/context/index.tsx index 51b0564f..86464484 100644 --- a/src/context/index.tsx +++ b/src/context/index.tsx @@ -13,7 +13,6 @@ import ModalRenderer from "../controllers/modals/ModalRenderer"; import Locale from "./Locale"; import Theme from "./Theme"; import { history } from "./history"; -import Intermediate from "./intermediate/Intermediate"; const uiContext = { Link, @@ -39,10 +38,8 @@ export default function Context({ children }: { children: Children }) { - - {children} - - + <>{children} + diff --git a/src/context/intermediate/Intermediate.tsx b/src/context/intermediate/Intermediate.tsx deleted file mode 100644 index e67ca0ae..00000000 --- a/src/context/intermediate/Intermediate.tsx +++ /dev/null @@ -1,219 +0,0 @@ -import { Prompt } from "react-router"; -import { useHistory } from "react-router-dom"; -import { API, Channel, Message, Server, User } from "revolt.js"; - -import { createContext } from "preact"; -import { - StateUpdater, - useContext, - useEffect, - useMemo, - useState, -} from "preact/hooks"; - -import type { Action } from "@revoltchat/ui/esm/components/design/atoms/display/Modal"; - -import { internalSubscribe } from "../../lib/eventEmitter"; -import { determineLink } from "../../lib/links"; - -import { useApplicationState } from "../../mobx/State"; - -import { modalController } from "../../controllers/modals/ModalController"; -import Modals from "./Modals"; - -export type Screen = - | { id: "none" } - - // Modals - | { id: "signed_out" } - | { id: "error"; error: string } - | { id: "clipboard"; text: string } - | { id: "token_reveal"; token: string; username: string } - | { id: "external_link_prompt"; link: string } - | { id: "sessions"; confirm: () => void } - | { - id: "_prompt"; - question: Children; - content?: Children; - actions: Action[]; - } - | ({ id: "special_prompt" } & ( - | { type: "leave_group"; target: Channel } - | { type: "close_dm"; target: Channel } - | { type: "leave_server"; target: Server } - | { type: "delete_server"; target: Server } - | { type: "delete_channel"; target: Channel } - | { - type: "delete_bot"; - target: string; - name: string; - cb?: () => void; - } - | { type: "delete_message"; target: Message } - | { - type: "create_invite"; - target: Channel; - } - | { type: "kick_member"; target: Server; user: User } - | { type: "ban_member"; target: Server; user: User } - | { type: "unfriend_user"; target: User } - | { type: "block_user"; target: User } - | { - type: "create_channel"; - target: Server; - cb?: ( - channel: Channel & { - channel_type: "TextChannel" | "VoiceChannel"; - }, - ) => void; - } - | { type: "create_category"; target: Server } - )) - | ({ id: "special_input" } & ( - | { - type: - | "create_group" - | "create_server" - | "set_custom_status" - | "add_friend"; - } - | { - type: "create_role"; - server: Server; - callback: (id: string) => void; - } - )) - | { - id: "_input"; - question: Children; - field: Children; - defaultValue?: string; - callback: (value: string) => Promise; - } - | { - id: "onboarding"; - callback: ( - username: string, - loginAfterSuccess?: true, - ) => Promise; - } - - // Pop-overs - | { id: "profile"; user_id: string } - | { - id: "user_picker"; - omit?: string[]; - callback: (users: string[]) => Promise; - } - | { id: "image_viewer"; attachment?: API.File; embed?: API.Image } - | { id: "channel_info"; channel: Channel } - | { id: "pending_requests"; users: User[] } - | { id: "modify_account"; field: "username" | "email" | "password" } - | { id: "create_bot"; onCreate: (bot: API.Bot) => void } - | { - id: "server_identity"; - server: Server; - }; - -export const IntermediateContext = createContext({ - screen: { id: "none" }, - focusTaken: false, -}); - -export const IntermediateActionsContext = createContext<{ - openLink: (href?: string, trusted?: boolean) => boolean; - openScreen: (screen: Screen) => void; - writeClipboard: (text: string) => void; -}>({ - openLink: null!, - openScreen: null!, - writeClipboard: null!, -}); - -interface Props { - children: Children; -} - -export let __thisIsAHack: StateUpdater; - -export default function Intermediate(props: Props) { - const [screen, openScreen] = useState({ id: "none" }); - __thisIsAHack = openScreen; - const history = useHistory(); - - const value = { - screen, - focusTaken: screen.id !== "none", - }; - - const actions = useMemo(() => { - return { - openLink: (href?: string, trusted?: boolean) => { - return modalController.openLink(href, trusted); - }, - openScreen: (screen: Screen) => openScreen(screen), - writeClipboard: (a: string) => modalController.writeText(a), - }; - // eslint-disable-next-line - }, []); - - useEffect(() => { - const openProfile = (user_id: string) => - modalController.push({ type: "user_profile", user_id }); - const navigate = (path: string) => history.push(path); - - const subs = [ - internalSubscribe( - "Intermediate", - "openProfile", - openProfile as (...args: unknown[]) => void, - ), - internalSubscribe( - "Intermediate", - "navigate", - navigate as (...args: unknown[]) => void, - ), - ]; - - return () => subs.map((unsub) => unsub()); - }, [history]); - - return ( - - - {screen.id !== "onboarding" && props.children} - - { - if (action === "POP") { - openScreen({ id: "none" }); - setTimeout(() => history.push(history.location), 0); - - return false; - } - - return true; - }} - /> - - - ); -} - -export const useIntermediate = () => useContext(IntermediateActionsContext); diff --git a/src/context/intermediate/Modals.tsx b/src/context/intermediate/Modals.tsx deleted file mode 100644 index 48e18245..00000000 --- a/src/context/intermediate/Modals.tsx +++ /dev/null @@ -1,25 +0,0 @@ -//import { isModalClosing } from "../../components/ui/Modal"; -import { Screen } from "./Intermediate"; -import { InputModal } from "./modals/Input"; -import { PromptModal } from "./modals/Prompt"; - -export interface Props { - screen: Screen; - openScreen: (screen: Screen) => void; -} - -export default function Modals({ screen, openScreen }: Props) { - const onClose = () => - //isModalClosing || screen.id === "onboarding" - openScreen({ id: "none" }); - // : internalEmit("Modal", "close"); - - switch (screen.id) { - case "_prompt": - return ; - case "_input": - return ; - } - - return null; -} diff --git a/src/context/intermediate/Popovers.tsx b/src/context/intermediate/Popovers.tsx deleted file mode 100644 index 08386727..00000000 --- a/src/context/intermediate/Popovers.tsx +++ /dev/null @@ -1,26 +0,0 @@ -import { useContext } from "preact/hooks"; - -import { IntermediateContext, useIntermediate } from "./Intermediate"; -import { SpecialInputModal } from "./modals/Input"; -import { SpecialPromptModal } from "./modals/Prompt"; - -export default function Popovers() { - const { screen } = useContext(IntermediateContext); - const { openScreen } = useIntermediate(); - - const onClose = () => - //isModalClosing - openScreen({ id: "none" }); - //: internalEmit("Modal", "close"); - - switch (screen.id) { - case "special_prompt": - // @ts-expect-error someone figure this out :) - return ; - case "special_input": - // @ts-expect-error someone figure this out :) - return ; - } - - return null; -} diff --git a/src/context/intermediate/modals/Input.tsx b/src/context/intermediate/modals/Input.tsx deleted file mode 100644 index f912b861..00000000 --- a/src/context/intermediate/modals/Input.tsx +++ /dev/null @@ -1,99 +0,0 @@ -import { useHistory } from "react-router"; -import { Server } from "revolt.js"; - -import { Text } from "preact-i18n"; -import { useContext, useState } from "preact/hooks"; - -import { Category, InputBox, Modal } from "@revoltchat/ui"; - -import { useClient } from "../../../controllers/client/ClientController"; -import { I18nError } from "../../Locale"; -import { takeError } from "../../revoltjs/util"; - -interface Props { - onClose: () => void; - question: Children; - field?: Children; - description?: Children; - defaultValue?: string; - callback: (value: string) => Promise; -} - -export function InputModal({ - onClose, - question, - field, - description, - defaultValue, - callback, -}: Props) { - const [processing, setProcessing] = useState(false); - const [value, setValue] = useState(defaultValue ?? ""); - const [error, setError] = useState(undefined); - - return ( - , - onClick: () => { - setProcessing(true); - callback(value) - .then(onClose) - .catch((err) => { - setError(takeError(err)); - setProcessing(false); - }); - }, - }, - { - children: , - onClick: onClose, - }, - ]} - onClose={onClose}> - {field ? ( - - {field} - - ) : ( - error && ( - - - - ) - )} - setValue(e.currentTarget.value)} - /> - - ); -} - -type SpecialProps = { onClose: () => void } & ( - | { - type: - | "create_group" - | "create_server" - | "set_custom_status" - | "add_friend"; - } - | { type: "create_role"; server: Server; callback: (id: string) => void } -); - -export function SpecialInputModal(props: SpecialProps) { - const history = useHistory(); - const client = useClient(); - - const { onClose } = props; - switch (props.type) { - default: - return null; - } -} diff --git a/src/context/intermediate/modals/Prompt.module.scss b/src/context/intermediate/modals/Prompt.module.scss deleted file mode 100644 index 9a930faf..00000000 --- a/src/context/intermediate/modals/Prompt.module.scss +++ /dev/null @@ -1,18 +0,0 @@ -.invite { - display: flex; - flex-direction: column; - - code { - padding: 1em; - user-select: all; - font-size: 1.4em; - text-align: center; - font-family: var(--monospace-font); - } -} - -.column { - display: flex; - align-items: center; - flex-direction: column; -} diff --git a/src/context/intermediate/modals/Prompt.tsx b/src/context/intermediate/modals/Prompt.tsx deleted file mode 100644 index a6add7aa..00000000 --- a/src/context/intermediate/modals/Prompt.tsx +++ /dev/null @@ -1,561 +0,0 @@ -import { observer } from "mobx-react-lite"; -import { useHistory } from "react-router-dom"; -import { Channel, Message as MessageI, Server, User } from "revolt.js"; -import { ulid } from "ulid"; - -import styles from "./Prompt.module.scss"; -import { Text } from "preact-i18n"; -import { useEffect, useState } from "preact/hooks"; - -import { Category, Modal, InputBox, Radio } from "@revoltchat/ui"; -import type { Action } from "@revoltchat/ui/esm/components/design/atoms/display/Modal"; - -import { TextReact } from "../../../lib/i18n"; - -import Message from "../../../components/common/messaging/Message"; -import UserIcon from "../../../components/common/user/UserIcon"; -import { useClient } from "../../../controllers/client/ClientController"; -import { I18nError } from "../../Locale"; -import { takeError } from "../../revoltjs/util"; -import { useIntermediate } from "../Intermediate"; - -interface Props { - onClose: () => void; - question: Children; - description?: Children; - content?: Children; - disabled?: boolean; - actions: Action[]; - error?: string; -} - -export function PromptModal({ - onClose, - question, - description, - content, - actions, - disabled, - error, -}: Props) { - return ( - - {error && ( - - - - )} - {content} - - ); -} - -type SpecialProps = { onClose: () => void } & ( - | { type: "leave_group"; target: Channel } - | { type: "close_dm"; target: Channel } - | { type: "delete_channel"; target: Channel } - | { - type: "create_invite"; - target: Channel; - } - - | { type: "leave_server"; target: Server } - | { type: "delete_server"; target: Server } - - | { type: "delete_bot"; target: string; name: string; cb?: () => void } - - | { type: "delete_message"; target: MessageI } - - | { type: "kick_member"; target: Server; user: User } - | { type: "ban_member"; target: Server; user: User } - - | { type: "unfriend_user"; target: User } - | { type: "block_user"; target: User } - - | { - type: "create_channel"; - target: Server; - cb?: ( - channel: Channel & { - channel_type: "TextChannel" | "VoiceChannel"; - }, - ) => void; - } - - | { type: "create_category"; target: Server } -); - -export const SpecialPromptModal = observer((props: SpecialProps) => { - const client = useClient(); - const history = useHistory(); - const [processing, setProcessing] = useState(false); - const [error, setError] = useState(undefined); - - const { onClose } = props; - switch (props.type) { - case "leave_group": - case "close_dm": - case "leave_server": - case "delete_server": - case "delete_channel": - case "delete_bot": - case "unfriend_user": - case "block_user": { - const EVENTS = { - close_dm: ["confirm_close_dm", "close"], - delete_server: ["confirm_delete", "delete"], - delete_channel: ["confirm_delete", "delete"], - delete_bot: ["confirm_delete", "delete"], - leave_group: ["confirm_leave", "leave"], - leave_server: ["confirm_leave", "leave"], - unfriend_user: ["unfriend_user", "remove"], - block_user: ["block_user", "block"], - }; - - const event = EVENTS[props.type]; - let name; - switch (props.type) { - case "unfriend_user": - case "block_user": - name = props.target.username; - break; - case "close_dm": - name = props.target.recipient?.username; - break; - case "delete_bot": - name = props.name; - break; - default: - name = props.target.name; - } - - return ( - - } - description={ - {name} }} - /> - } - actions={[ - { - confirmation: true, - palette: "error", - children: ( - - ), - onClick: async () => { - setProcessing(true); - - try { - switch (props.type) { - case "unfriend_user": - await props.target.removeFriend(); - break; - case "block_user": - await props.target.blockUser(); - break; - case "leave_group": - case "close_dm": - case "delete_channel": - case "leave_server": - case "delete_server": - if (props.type != "delete_channel") - history.push("/"); - props.target.delete(); - break; - case "delete_bot": - client.bots.delete(props.target); - props.cb?.(); - break; - } - - return true; - } catch (err) { - setError(takeError(err)); - setProcessing(false); - return false; - } - }, - }, - { - children: ( - - ), - onClick: onClose, - }, - ]} - disabled={processing} - error={error} - /> - ); - } - case "delete_message": { - return ( - } - description={ - - } - actions={[ - { - confirmation: true, - palette: "error", - children: ( - - ), - onClick: async () => { - setProcessing(true); - - try { - props.target.delete(); - return true; - } catch (err) { - setError(takeError(err)); - setProcessing(false); - return false; - } - }, - }, - { - children: ( - - ), - onClick: onClose, - palette: "plain", - }, - ]} - content={ - - } - disabled={processing} - error={error} - /> - ); - } - case "create_invite": { - const [code, setCode] = useState("abcdef"); - const { writeClipboard } = useIntermediate(); - - useEffect(() => { - setProcessing(true); - - props.target - .createInvite() - .then(({ _id }) => setCode(_id)) - .catch((err) => setError(takeError(err))) - .finally(() => setProcessing(false)); - }, [props.target]); - - return ( - } - actions={[ - { - children: ( - - ), - confirmation: true, - onClick: onClose, - }, - { - children: , - onClick: () => - writeClipboard( - `${window.location.protocol}//${window.location.host}/invite/${code}`, - ), - }, - ]} - content={ - processing ? ( - - ) : ( -
- - {code} -
- ) - } - disabled={processing} - error={error} - /> - ); - } - case "kick_member": { - return ( - } - actions={[ - { - children: ( - - ), - palette: "error", - confirmation: true, - onClick: async () => { - setProcessing(true); - - try { - client.members - .getKey({ - server: props.target._id, - user: props.user._id, - }) - ?.kick(); - - return true; - } catch (err) { - setError(takeError(err)); - setProcessing(false); - return false; - } - }, - }, - { - children: ( - - ), - onClick: onClose, - }, - ]} - content={ -
- - -
- } - disabled={processing} - error={error} - /> - ); - } - case "ban_member": { - const [reason, setReason] = useState(undefined); - - return ( - } - actions={[ - { - children: ( - - ), - palette: "error", - - confirmation: true, - onClick: async () => { - setProcessing(true); - - try { - await props.target.banUser(props.user._id, { - reason, - }); - - return true; - } catch (err) { - setError(takeError(err)); - setProcessing(false); - return false; - } - }, - }, - { - children: ( - - ), - onClick: onClose, - }, - ]} - content={ -
- - - - - - - setReason(e.currentTarget.value) - } - /> -
- } - disabled={processing} - error={error} - /> - ); - } - case "create_channel": { - const [name, setName] = useState(""); - const [type, setType] = useState<"Text" | "Voice">("Text"); - const history = useHistory(); - - return ( - } - actions={[ - { - confirmation: true, - palette: "secondary", - - children: ( - - ), - onClick: async () => { - setProcessing(true); - - try { - const channel = - await props.target.createChannel({ - type, - name, - }); - - if (props.cb) { - props.cb(channel as any); - } else { - history.push( - `/server/${props.target._id}/channel/${channel._id}`, - ); - } - - return true; - } catch (err) { - setError(takeError(err)); - setProcessing(false); - return false; - } - }, - }, - { - children: ( - - ), - onClick: onClose, - }, - ]} - content={ - <> - - - - - } - value={type === "Text"} - onSelect={() => setType("Text")} - /> - - } - value={type === "Voice"} - onSelect={() => setType("Voice")} - /> - - - - setName(e.currentTarget.value)} - /> - - } - disabled={processing} - error={error} - /> - ); - } - case "create_category": { - const [name, setName] = useState(""); - - return ( - } - actions={[ - { - confirmation: true, - palette: "secondary", - children: ( - - ), - onClick: async () => { - setProcessing(true); - try { - props.target.edit({ - categories: [ - ...(props.target.categories ?? []), - { - id: ulid(), - title: name, - channels: [], - }, - ], - }); - - setProcessing(false); - return true; - } catch (err) { - setError(takeError(err)); - setProcessing(false); - return false; - } - }, - }, - { - children: ( - - ), - onClick: onClose, - }, - ]} - content={ - <> - - - - setName(e.currentTarget.value)} - /> - - } - disabled={processing} - error={error} - /> - ); - } - default: - return null; - } -}); diff --git a/src/context/intermediate/popovers/UserPicker.module.scss b/src/context/intermediate/popovers/UserPicker.module.scss deleted file mode 100644 index f0c1e9a5..00000000 --- a/src/context/intermediate/popovers/UserPicker.module.scss +++ /dev/null @@ -1,5 +0,0 @@ -.list { - max-width: 100%; - max-height: 360px; - overflow-y: scroll; -} diff --git a/src/context/revoltjs/FileUploads.tsx b/src/context/revoltjs/FileUploads.tsx index 4ab39cbc..f0887c90 100644 --- a/src/context/revoltjs/FileUploads.tsx +++ b/src/context/revoltjs/FileUploads.tsx @@ -13,7 +13,6 @@ import { determineFileSize } from "../../lib/fileSize"; import { useClient } from "../../controllers/client/ClientController"; import { modalController } from "../../controllers/modals/ModalController"; -import { useIntermediate } from "../intermediate/Intermediate"; import { takeError } from "./util"; type BehaviourType = @@ -112,7 +111,6 @@ export function grabFiles( export function FileUploader(props: Props) { const { fileType, maxFileSize, remove } = props; - const { openScreen } = useIntermediate(); const client = useClient(); const [uploading, setUploading] = useState(false); @@ -243,7 +241,7 @@ export function FileUploader(props: Props) { document.removeEventListener("dragover", dragover); document.removeEventListener("drop", drop); }; - }, [openScreen, props, props.append]); + }, [props, props.append]); } if (props.style === "icon" || props.style === "banner") { diff --git a/src/controllers/client/Session.tsx b/src/controllers/client/Session.tsx index 442b65d0..f1360e26 100644 --- a/src/controllers/client/Session.tsx +++ b/src/controllers/client/Session.tsx @@ -3,8 +3,6 @@ import { API, Client } from "revolt.js"; import { state } from "../../mobx/State"; -import { __thisIsAHack } from "../../context/intermediate/Intermediate"; - import { modalController } from "../modals/ModalController"; /** diff --git a/src/controllers/modals/ModalRenderer.tsx b/src/controllers/modals/ModalRenderer.tsx index 712a4dc4..1b51dcab 100644 --- a/src/controllers/modals/ModalRenderer.tsx +++ b/src/controllers/modals/ModalRenderer.tsx @@ -1,10 +1,13 @@ import { observer } from "mobx-react-lite"; +import { Prompt, useHistory } from "react-router-dom"; import { useEffect } from "preact/hooks"; import { modalController } from "./ModalController"; export default observer(() => { + const history = useHistory(); + useEffect(() => { function keyUp(event: KeyboardEvent) { if (event.key === "Escape") { @@ -18,5 +21,22 @@ export default observer(() => { return () => document.removeEventListener("keyup", keyUp); }, []); - return modalController.rendered; + return ( + <> + {modalController.rendered} + { + if (action === "POP") { + modalController.pop("close"); + setTimeout(() => history.push(history.location), 0); + + return false; + } + + return true; + }} + /> + + ); }); diff --git a/src/controllers/modals/components/ImportTheme.tsx b/src/controllers/modals/components/ImportTheme.tsx new file mode 100644 index 00000000..67071e61 --- /dev/null +++ b/src/controllers/modals/components/ImportTheme.tsx @@ -0,0 +1,32 @@ +import { Text } from "preact-i18n"; + +import { ModalForm } from "@revoltchat/ui"; + +import { state } from "../../../mobx/State"; + +import { ModalProps } from "../types"; + +/** + * Import theme modal + */ +export default function ImportTheme({ ...props }: ModalProps<"import_theme">) { + return ( + } + schema={{ + data: "text", + }} + data={{ + data: { + field: ( + + ) as React.ReactChild, + }, + }} + callback={async ({ data }) => + state.settings.theme.hydrate(JSON.parse(data)) + } + /> + ); +} diff --git a/src/controllers/modals/types.ts b/src/controllers/modals/types.ts index 1bad3bb2..1275fe4c 100644 --- a/src/controllers/modals/types.ts +++ b/src/controllers/modals/types.ts @@ -176,6 +176,9 @@ export type Modal = { type: "create_category"; target: Server; } + | { + type: "import_theme"; + } ); export type ModalProps = Modal & { type: T } & { diff --git a/src/lib/ContextMenus.tsx b/src/lib/ContextMenus.tsx index 1fea137d..ac080531 100644 --- a/src/lib/ContextMenus.tsx +++ b/src/lib/ContextMenus.tsx @@ -26,7 +26,6 @@ import { useApplicationState } from "../mobx/State"; import { QueuedMessage } from "../mobx/stores/MessageQueue"; import { NotificationState } from "../mobx/stores/NotificationOptions"; -import { Screen, useIntermediate } from "../context/intermediate/Intermediate"; import { takeError } from "../context/revoltjs/util"; import CMNotifications from "./contextmenu/CMNotifications"; @@ -116,7 +115,6 @@ type Action = // ! FIXME: I dare someone to re-write this // Tip: This should just be split into separate context menus per logical area. export default function ContextMenus() { - const { openScreen, writeClipboard } = useIntermediate(); const session = useSession()!; const client = session.client!; const userId = client.user!._id; @@ -130,7 +128,7 @@ export default function ContextMenus() { (async () => { switch (data.action) { case "copy_id": - writeClipboard(data.id); + modalController.writeText(data.id); break; case "copy_message_link": { @@ -139,11 +137,13 @@ export default function ContextMenus() { if (channel?.channel_type === "TextChannel") pathname = `/server/${channel.server_id}${pathname}`; - writeClipboard(window.origin + pathname); + modalController.writeText(window.origin + pathname); } break; case "copy_selection": - writeClipboard(document.getSelection()?.toString() ?? ""); + modalController.writeText( + document.getSelection()?.toString() ?? "", + ); break; case "mark_as_read": { @@ -227,7 +227,7 @@ export default function ContextMenus() { break; case "copy_text": - writeClipboard(data.content); + modalController.writeText(data.content); break; case "reply_message": @@ -286,7 +286,7 @@ export default function ContextMenus() { case "copy_file_link": { const { filename } = data.attachment; - writeClipboard( + modalController.writeText( // ! FIXME: do from r.js `${client.generateFileURL( data.attachment, @@ -303,7 +303,7 @@ export default function ContextMenus() { case "copy_link": { - writeClipboard(data.link); + modalController.writeText(data.link); } break; @@ -1022,7 +1022,7 @@ export default function ContextMenus() {
- writeClipboard( + modalController.writeText( client.user!.username, ) }> diff --git a/src/pages/RevoltApp.tsx b/src/pages/RevoltApp.tsx index c04917ab..23849dd6 100644 --- a/src/pages/RevoltApp.tsx +++ b/src/pages/RevoltApp.tsx @@ -7,8 +7,6 @@ import { useEffect, useState } from "preact/hooks"; import ContextMenus from "../lib/ContextMenus"; import { isTouchscreenDevice } from "../lib/isTouchscreenDevice"; -import Popovers from "../context/intermediate/Popovers"; - import { Titlebar } from "../components/native/Titlebar"; import BottomNavigation from "../components/navigation/BottomNavigation"; import LeftSidebar from "../components/navigation/LeftSidebar"; @@ -225,7 +223,6 @@ export default function App() { - diff --git a/src/pages/channels/actions/HeaderActions.tsx b/src/pages/channels/actions/HeaderActions.tsx index 04cbe198..cf13311b 100644 --- a/src/pages/channels/actions/HeaderActions.tsx +++ b/src/pages/channels/actions/HeaderActions.tsx @@ -21,8 +21,6 @@ import { voiceState, VoiceStatus } from "../../../lib/vortex/VoiceState"; import { useApplicationState } from "../../../mobx/State"; import { SIDEBAR_MEMBERS } from "../../../mobx/stores/Layout"; -import { useIntermediate } from "../../../context/intermediate/Intermediate"; - import UpdateIndicator from "../../../components/common/UpdateIndicator"; import { modalController } from "../../../controllers/modals/ModalController"; import { ChannelHeaderProps } from "../ChannelHeader"; @@ -74,7 +72,6 @@ const SearchBar = styled.div` export default function HeaderActions({ channel }: ChannelHeaderProps) { const layout = useApplicationState().layout; - const { openScreen } = useIntermediate(); const history = useHistory(); function slideOpen() { diff --git a/src/pages/channels/messaging/MessageArea.tsx b/src/pages/channels/messaging/MessageArea.tsx index c35878d9..d0ae8a5f 100644 --- a/src/pages/channels/messaging/MessageArea.tsx +++ b/src/pages/channels/messaging/MessageArea.tsx @@ -23,10 +23,9 @@ import { internalEmit, internalSubscribe } from "../../../lib/eventEmitter"; import { getRenderer } from "../../../lib/renderer/Singleton"; import { ScrollState } from "../../../lib/renderer/types"; -import { IntermediateContext } from "../../../context/intermediate/Intermediate"; - import { useSession } from "../../../controllers/client/ClientController"; import RequiresOnline from "../../../controllers/client/jsx/RequiresOnline"; +import { modalController } from "../../../controllers/modals/ModalController"; import ConversationStart from "./ConversationStart"; import MessageRenderer from "./MessageRenderer"; @@ -63,7 +62,6 @@ export const MESSAGE_AREA_PADDING = 82; export const MessageArea = observer(({ last_id, channel }: Props) => { const history = useHistory(); const session = useSession()!; - const { focusTaken } = useContext(IntermediateContext); // ? Required data for message links. const { message } = useParams<{ message: string }>(); @@ -303,7 +301,7 @@ export const MessageArea = observer(({ last_id, channel }: Props) => { // ? Scroll to bottom when pressing 'Escape'. useEffect(() => { function keyUp(e: KeyboardEvent) { - if (e.key === "Escape" && !focusTaken) { + if (e.key === "Escape" && !modalController.isVisible) { renderer.jumpToBottom(true); internalEmit("TextArea", "focus", "message"); } @@ -311,7 +309,7 @@ export const MessageArea = observer(({ last_id, channel }: Props) => { document.body.addEventListener("keyup", keyUp); return () => document.body.removeEventListener("keyup", keyUp); - }, [renderer, ref, focusTaken]); + }, [renderer, ref]); return ( { function keyUp(e: KeyboardEvent) { - if (e.key === "Escape" && !focusTaken) { + if (e.key === "Escape" && !modalController.isVisible) { finish(); } } document.body.addEventListener("keyup", keyUp); return () => document.body.removeEventListener("keyup", keyUp); - }, [focusTaken, finish]); + }, [finish]); const { onChange, diff --git a/src/pages/channels/voice/VoiceHeader.tsx b/src/pages/channels/voice/VoiceHeader.tsx index 89ef577c..618baaa2 100644 --- a/src/pages/channels/voice/VoiceHeader.tsx +++ b/src/pages/channels/voice/VoiceHeader.tsx @@ -16,8 +16,6 @@ import { Button } from "@revoltchat/ui"; import { voiceState, VoiceStatus } from "../../../lib/vortex/VoiceState"; -import { useIntermediate } from "../../../context/intermediate/Intermediate"; - import Tooltip from "../../../components/common/Tooltip"; import UserIcon from "../../../components/common/user/UserIcon"; import { useClient } from "../../../controllers/client/ClientController"; @@ -85,8 +83,6 @@ const VoiceBase = styled.div` export default observer(({ id }: Props) => { if (voiceState.roomId !== id) return null; - const { openScreen } = useIntermediate(); - const client = useClient(); const self = client.users.get(client.user!._id); diff --git a/src/pages/discover/Discover.tsx b/src/pages/discover/Discover.tsx index 66fb7268..666daeed 100644 --- a/src/pages/discover/Discover.tsx +++ b/src/pages/discover/Discover.tsx @@ -12,7 +12,8 @@ import { isTouchscreenDevice } from "../../lib/isTouchscreenDevice"; import { useApplicationState } from "../../mobx/State"; import { Overrides } from "../../context/Theme"; -import { useIntermediate } from "../../context/intermediate/Intermediate"; + +import { modalController } from "../../controllers/modals/ModalController"; const Container = styled.div` flex-grow: 1; @@ -85,7 +86,6 @@ const REMOTE = "https://rvlt.gg"; export default function Discover() { const state = useApplicationState(); - const { openLink } = useIntermediate(); const history = useHistory(); const { pathname, search } = useLocation(); @@ -137,7 +137,7 @@ export default function Discover() { break; } case "navigate": { - openLink(data.url); + modalController.openLink(data.url); break; } case "applyTheme": { diff --git a/src/pages/friends/Friend.tsx b/src/pages/friends/Friend.tsx index 7e3c1ef1..4bdd9c7c 100644 --- a/src/pages/friends/Friend.tsx +++ b/src/pages/friends/Friend.tsx @@ -14,8 +14,6 @@ import { IconButton } from "@revoltchat/ui"; import { stopPropagation } from "../../lib/stopPropagation"; import { voiceState } from "../../lib/vortex/VoiceState"; -import { useIntermediate } from "../../context/intermediate/Intermediate"; - import UserIcon from "../../components/common/user/UserIcon"; import UserStatus from "../../components/common/user/UserStatus"; import { modalController } from "../../controllers/modals/ModalController"; @@ -26,7 +24,6 @@ interface Props { export const Friend = observer(({ user }: Props) => { const history = useHistory(); - const { openScreen } = useIntermediate(); const actions: Children[] = []; let subtext: Children = null; diff --git a/src/pages/friends/Friends.tsx b/src/pages/friends/Friends.tsx index a479b46b..e4374a26 100644 --- a/src/pages/friends/Friends.tsx +++ b/src/pages/friends/Friends.tsx @@ -12,8 +12,6 @@ import { IconButton } from "@revoltchat/ui"; import { TextReact } from "../../lib/i18n"; import { isTouchscreenDevice } from "../../lib/isTouchscreenDevice"; -import { useIntermediate } from "../../context/intermediate/Intermediate"; - import CollapsibleSection from "../../components/common/CollapsibleSection"; import Tooltip from "../../components/common/Tooltip"; import UserIcon from "../../components/common/user/UserIcon"; @@ -23,8 +21,6 @@ import { modalController } from "../../controllers/modals/ModalController"; import { Friend } from "./Friend"; export default observer(() => { - const { openScreen } = useIntermediate(); - const client = useClient(); const users = [...client.users.values()]; users.sort((a, b) => a.username.localeCompare(b.username)); diff --git a/src/pages/home/Home.tsx b/src/pages/home/Home.tsx index 67784147..75609150 100644 --- a/src/pages/home/Home.tsx +++ b/src/pages/home/Home.tsx @@ -23,8 +23,6 @@ import { isTouchscreenDevice } from "../../lib/isTouchscreenDevice"; import { useApplicationState } from "../../mobx/State"; -import { useIntermediate } from "../../context/intermediate/Intermediate"; - import wideSVG from "/assets/wide.svg"; import { PageHeader } from "../../components/ui/Header"; @@ -45,7 +43,6 @@ const Overlay = styled.div` `; export default observer(() => { - const { openScreen } = useIntermediate(); const client = useClient(); const state = useApplicationState(); diff --git a/src/pages/settings/ServerSettings.tsx b/src/pages/settings/ServerSettings.tsx index 72f2925f..d008cf0a 100644 --- a/src/pages/settings/ServerSettings.tsx +++ b/src/pages/settings/ServerSettings.tsx @@ -15,8 +15,6 @@ import { Text } from "preact-i18n"; import { LineDivider } from "@revoltchat/ui"; -import { useIntermediate } from "../../context/intermediate/Intermediate"; - import ButtonItem from "../../components/navigation/items/ButtonItem"; import { useClient } from "../../controllers/client/ClientController"; import RequiresOnline from "../../controllers/client/jsx/RequiresOnline"; @@ -30,7 +28,6 @@ import { Overview } from "./server/Overview"; import { Roles } from "./server/Roles"; export default observer(() => { - const { openScreen } = useIntermediate(); const { server: sid } = useParams<{ server: string }>(); const client = useClient(); const server = client.servers.get(sid); diff --git a/src/pages/settings/Settings.tsx b/src/pages/settings/Settings.tsx index 022f0c85..18566e5c 100644 --- a/src/pages/settings/Settings.tsx +++ b/src/pages/settings/Settings.tsx @@ -33,8 +33,6 @@ import { LineDivider } from "@revoltchat/ui"; import { useApplicationState } from "../../mobx/State"; -import { useIntermediate } from "../../context/intermediate/Intermediate"; - import UserIcon from "../../components/common/user/UserIcon"; import { Username } from "../../components/common/user/UserShort"; import UserStatus from "../../components/common/user/UserStatus"; @@ -121,7 +119,6 @@ const AccountHeader = styled.div` export default observer(() => { const history = useHistory(); const client = useClient(); - const { openScreen } = useIntermediate(); const experiments = useApplicationState().experiments; function switchPage(to?: string) { diff --git a/src/pages/settings/panes/MyBots.tsx b/src/pages/settings/panes/MyBots.tsx index 158ca952..122ae7ff 100644 --- a/src/pages/settings/panes/MyBots.tsx +++ b/src/pages/settings/panes/MyBots.tsx @@ -23,7 +23,6 @@ import { internalEmit } from "../../../lib/eventEmitter"; import { useTranslation } from "../../../lib/i18n"; import { stopPropagation } from "../../../lib/stopPropagation"; -import { useIntermediate } from "../../../context/intermediate/Intermediate"; import { FileUploader } from "../../../context/revoltjs/FileUploads"; import AutoComplete, { @@ -88,7 +87,6 @@ function BotCard({ bot, onDelete, onUpdate }: Props) { ); const [interactionsRef, setInteractionsRef] = useState(null); - const { writeClipboard, openScreen } = useIntermediate(); const [profile, setProfile] = useState( undefined, @@ -267,7 +265,9 @@ function BotCard({ bot, onDelete, onUpdate }: Props) { }> - writeClipboard(user!._id) + modalController.writeText( + user!._id, + ) }> {user!._id} @@ -335,7 +335,7 @@ function BotCard({ bot, onDelete, onUpdate }: Props) { } - onClick={() => writeClipboard(bot.token)} + onClick={() => modalController.writeText(bot.token)} description={ <> {"••••• "} @@ -475,7 +475,7 @@ function BotCard({ bot, onDelete, onUpdate }: Props) { <>
, + category: , id: "overview", icon: , title: ( diff --git a/src/pages/settings/channel/Overview.tsx b/src/pages/settings/channel/Overview.tsx index 8b2dae96..392fb216 100644 --- a/src/pages/settings/channel/Overview.tsx +++ b/src/pages/settings/channel/Overview.tsx @@ -9,7 +9,7 @@ import { Button, Checkbox, InputBox } from "@revoltchat/ui"; import TextAreaAutoSize from "../../../lib/TextAreaAutoSize"; -import { FileUploader } from "../../../context/revoltjs/FileUploads"; +import { FileUploader } from "../../../controllers/client/jsx/legacy/FileUploads"; interface Props { channel: Channel; diff --git a/src/pages/settings/panes/MyBots.tsx b/src/pages/settings/panes/MyBots.tsx index 122ae7ff..03af49c0 100644 --- a/src/pages/settings/panes/MyBots.tsx +++ b/src/pages/settings/panes/MyBots.tsx @@ -23,8 +23,6 @@ import { internalEmit } from "../../../lib/eventEmitter"; import { useTranslation } from "../../../lib/i18n"; import { stopPropagation } from "../../../lib/stopPropagation"; -import { FileUploader } from "../../../context/revoltjs/FileUploads"; - import AutoComplete, { useAutoComplete, } from "../../../components/common/AutoComplete"; @@ -32,6 +30,7 @@ import CollapsibleSection from "../../../components/common/CollapsibleSection"; import Tooltip from "../../../components/common/Tooltip"; import UserIcon from "../../../components/common/user/UserIcon"; import { useClient } from "../../../controllers/client/ClientController"; +import { FileUploader } from "../../../controllers/client/jsx/legacy/FileUploads"; import { modalController } from "../../../controllers/modals/ModalController"; interface Data { diff --git a/src/pages/settings/panes/Profile.tsx b/src/pages/settings/panes/Profile.tsx index c0c9e0c3..76f68e37 100644 --- a/src/pages/settings/panes/Profile.tsx +++ b/src/pages/settings/panes/Profile.tsx @@ -12,12 +12,11 @@ import { Button, LineDivider, Tip } from "@revoltchat/ui"; import TextAreaAutoSize from "../../../lib/TextAreaAutoSize"; import { useTranslation } from "../../../lib/i18n"; -import { FileUploader } from "../../../context/revoltjs/FileUploads"; - import AutoComplete, { useAutoComplete, } from "../../../components/common/AutoComplete"; import { useSession } from "../../../controllers/client/ClientController"; +import { FileUploader } from "../../../controllers/client/jsx/legacy/FileUploads"; import { UserProfile } from "../../../controllers/modals/components/legacy/UserProfile"; export const Profile = observer(() => { diff --git a/src/pages/settings/server/Invites.tsx b/src/pages/settings/server/Invites.tsx index f20f8e53..d4256427 100644 --- a/src/pages/settings/server/Invites.tsx +++ b/src/pages/settings/server/Invites.tsx @@ -9,10 +9,9 @@ import { useEffect, useState } from "preact/hooks"; import { IconButton, Preloader } from "@revoltchat/ui"; -import { getChannelName } from "../../../context/revoltjs/util"; - import UserIcon from "../../../components/common/user/UserIcon"; import { Username } from "../../../components/common/user/UserShort"; +import { ChannelName } from "../../../controllers/client/jsx/ChannelName"; interface InnerProps { invite: API.Invite; @@ -33,7 +32,9 @@ const Inner = observer(({ invite, server, removeSelf }: InnerProps) => { {" "} - {channel ? getChannelName(channel, true) : "#??"} + + + { setDelete(true); diff --git a/src/pages/settings/server/Overview.tsx b/src/pages/settings/server/Overview.tsx index c04f1c06..7dc56c39 100644 --- a/src/pages/settings/server/Overview.tsx +++ b/src/pages/settings/server/Overview.tsx @@ -12,8 +12,8 @@ import { Button, ComboBox, InputBox } from "@revoltchat/ui"; import TextAreaAutoSize from "../../../lib/TextAreaAutoSize"; import { noop } from "../../../lib/js"; -import { FileUploader } from "../../../context/revoltjs/FileUploads"; -import { getChannelName } from "../../../context/revoltjs/util"; +import { ChannelName } from "../../../controllers/client/jsx/ChannelName"; +import { FileUploader } from "../../../controllers/client/jsx/legacy/FileUploads"; interface Props { server: Server; @@ -172,7 +172,7 @@ export const Overview = observer(({ server }: Props) => { ) .map((channel) => ( ))} From 47e3d0bdb5908ae30b3ca5f8ede107fe7710a374 Mon Sep 17 00:00:00 2001 From: Paul Makles Date: Wed, 6 Jul 2022 13:09:17 +0100 Subject: [PATCH 130/151] chore: update README to reflect project changes [skip ci] --- README.md | 2 -- 1 file changed, 2 deletions(-) diff --git a/README.md b/README.md index 0d5cd8c0..49d4ac3b 100644 --- a/README.md +++ b/README.md @@ -10,8 +10,6 @@ The following code is pending a partial or full rewrite: - `src/components`: components are being migrated to [revoltchat/components](https://github.com/revoltchat/components) - `src/styles`: needs to be migrated to [revoltchat/components](https://github.com/revoltchat/components) -- `src/context/intermediate`: modal system is being rewritten from scratch -- `src/context/revoltjs`: client state management needs to be rewritten and include support for concurrent clients - `src/lib`: this needs to be organised ## Stack From a766183f01d9977512b38c3975de7fd648ab9724 Mon Sep 17 00:00:00 2001 From: Paul Makles Date: Wed, 6 Jul 2022 13:15:33 +0100 Subject: [PATCH 131/151] feat: port `CreateBot` to modal form fixes #723 --- src/controllers/modals/ModalController.tsx | 4 +- .../modals/components/CreateBot.tsx | 42 +++++++++ .../modals/components/legacy/CreateBot.tsx | 86 ------------------- 3 files changed, 44 insertions(+), 88 deletions(-) create mode 100644 src/controllers/modals/components/CreateBot.tsx delete mode 100644 src/controllers/modals/components/legacy/CreateBot.tsx diff --git a/src/controllers/modals/ModalController.tsx b/src/controllers/modals/ModalController.tsx index b2667f98..0e4ac29a 100644 --- a/src/controllers/modals/ModalController.tsx +++ b/src/controllers/modals/ModalController.tsx @@ -21,6 +21,7 @@ import Changelog from "./components/Changelog"; import ChannelInfo from "./components/ChannelInfo"; import Clipboard from "./components/Clipboard"; import Confirmation from "./components/Confirmation"; +import CreateBot from "./components/CreateBot"; import CreateCategory from "./components/CreateCategory"; import CreateChannel from "./components/CreateChannel"; import CreateGroup from "./components/CreateGroup"; @@ -45,7 +46,6 @@ import ShowToken from "./components/ShowToken"; import SignOutSessions from "./components/SignOutSessions"; import SignedOut from "./components/SignedOut"; import UserPicker from "./components/UserPicker"; -import { CreateBotModal } from "./components/legacy/CreateBot"; import { OnboardingModal } from "./components/legacy/Onboarding"; import { UserProfile } from "./components/legacy/UserProfile"; import { Modal } from "./types"; @@ -258,7 +258,7 @@ export const modalController = new ModalControllerExtended({ create_invite: CreateInvite, create_role: CreateRole, create_server: CreateServer, - create_bot: CreateBotModal, + create_bot: CreateBot, custom_status: CustomStatus, delete_message: DeleteMessage, error: Error, diff --git a/src/controllers/modals/components/CreateBot.tsx b/src/controllers/modals/components/CreateBot.tsx new file mode 100644 index 00000000..e5bcf791 --- /dev/null +++ b/src/controllers/modals/components/CreateBot.tsx @@ -0,0 +1,42 @@ +import { Text } from "preact-i18n"; + +import { ModalForm } from "@revoltchat/ui"; + +import { useClient } from "../../client/ClientController"; +import { mapError } from "../../client/jsx/error"; +import { ModalProps } from "../types"; + +/** + * Bot creation modal + */ +export default function CreateBot({ + onCreate, + ...props +}: ModalProps<"create_bot">) { + const client = useClient(); + + return ( + } + schema={{ + name: "text", + }} + data={{ + name: { + field: () as React.ReactChild, + }, + }} + callback={async ({ name }) => { + const { bot } = await client.bots + .create({ name }) + .catch(mapError); + + onCreate(bot); + }} + submit={{ + children: , + }} + /> + ); +} diff --git a/src/controllers/modals/components/legacy/CreateBot.tsx b/src/controllers/modals/components/legacy/CreateBot.tsx deleted file mode 100644 index 7c977cb2..00000000 --- a/src/controllers/modals/components/legacy/CreateBot.tsx +++ /dev/null @@ -1,86 +0,0 @@ -import { SubmitHandler, useForm } from "react-hook-form"; -import { API } from "revolt.js"; - -import { Text } from "preact-i18n"; -import { useState } from "preact/hooks"; - -import { Category, Modal } from "@revoltchat/ui"; - -import { noopTrue } from "../../../../lib/js"; - -import { I18nError } from "../../../../context/Locale"; - -import FormField from "../../../../pages/login/FormField"; -import { useClient } from "../../../client/ClientController"; -import { takeError } from "../../../client/jsx/error"; -import { modalController } from "../../ModalController"; -import { ModalProps } from "../../types"; - -interface FormInputs { - name: string; -} - -export function CreateBotModal({ - onCreate, - ...props -}: ModalProps<"create_bot">) { - const client = useClient(); - const { handleSubmit, register, errors } = useForm(); - const [error, setError] = useState(undefined); - - const onSubmit: SubmitHandler = async ({ name }) => { - try { - const { bot } = await client.bots.create({ name }); - onCreate(bot); - modalController.close(); - } catch (err) { - setError(takeError(err)); - } - }; - - return ( - } - actions={[ - { - confirmation: true, - palette: "accent", - onClick: async () => { - await handleSubmit(onSubmit)(); - return true; - }, - children: , - }, - { - palette: "plain", - onClick: noopTrue, - children: , - }, - ]}> - {/* Preact / React typing incompatabilities */} - { - e.preventDefault(); - handleSubmit( - onSubmit, - // eslint-disable-next-line @typescript-eslint/no-explicit-any - )(e as any); - }}> - - {error && ( - - {" "} - · - - )} - - - ); -} From b541301cb135b9c7b43c42e04a444832526b8cb2 Mon Sep 17 00:00:00 2001 From: Paul Makles Date: Thu, 7 Jul 2022 17:33:33 +0100 Subject: [PATCH 132/151] feat: add test emoji page --- package.json | 9 ++- .../navigation/right/MemberList.tsx | 2 +- .../settings/customisation/EmojiUploader.tsx | 61 +++++++++++++++ .../client/jsx/legacy/FileUploads.tsx | 38 ++++++++-- src/pages/settings/ServerSettings.tsx | 15 ++++ src/pages/settings/server/Emojis.tsx | 74 +++++++++++++++++++ yarn.lock | 38 ++++------ 7 files changed, 205 insertions(+), 32 deletions(-) create mode 100644 src/components/settings/customisation/EmojiUploader.tsx create mode 100644 src/pages/settings/server/Emojis.tsx diff --git a/package.json b/package.json index 5fcf312b..0398dc47 100644 --- a/package.json +++ b/package.json @@ -125,7 +125,7 @@ "preact-context-menu": "0.4.1", "preact-i18n": "^2.4.0-preactx", "prettier": "^2.3.1", - "prismjs": "^1.23.0", + "prismjs": "^1.28.0", "qrcode.react": "^3.0.2", "react-beautiful-dnd": "^13.1.0", "react-device-detect": "2.2.2", @@ -135,7 +135,7 @@ "react-router-dom": "^5.2.0", "react-scroll": "^1.8.2", "react-virtuoso": "^2.12.0", - "revolt.js": "6.0.3", + "revolt.js": "6.0.4", "rimraf": "^3.0.2", "sass": "^1.35.1", "semver": "^7.3.7", @@ -156,5 +156,8 @@ "repository": "https://github.com/revoltchat/revite.git", "author": "Paul ", "license": "MIT", - "packageManager": "yarn@3.2.0" + "packageManager": "yarn@3.2.0", + "resolutions": { + "@revoltchat/ui": "portal:../components" + } } diff --git a/src/components/navigation/right/MemberList.tsx b/src/components/navigation/right/MemberList.tsx index 3913c20b..1163fea5 100644 --- a/src/components/navigation/right/MemberList.tsx +++ b/src/components/navigation/right/MemberList.tsx @@ -99,7 +99,7 @@ export default function MemberList({ )} {entry.type !== "no_offline" && ( <> - {" - "} + {" – "} {entry.users.length} )} diff --git a/src/components/settings/customisation/EmojiUploader.tsx b/src/components/settings/customisation/EmojiUploader.tsx new file mode 100644 index 00000000..4a1b42c5 --- /dev/null +++ b/src/components/settings/customisation/EmojiUploader.tsx @@ -0,0 +1,61 @@ +import { Server } from "revolt.js"; + +import { useState } from "preact/hooks"; + +import { Form } from "@revoltchat/ui"; + +import { FileUploader } from "../../../controllers/client/jsx/legacy/FileUploads"; + +interface Props { + server: Server; +} + +export function EmojiUploader({ server }: Props) { + const [fileId, setFileId] = useState(); + + return ( + <> +

Upload Emoji

+
void setFileId("")} + onUpload={async (id) => void setFileId(id)} + /> + ), + }, + }} + submitBtn={{ + children: "Save", + palette: "secondary", + disabled: !fileId, + }} + onSubmit={async ({ name }) => { + await server.client.api.put(`/custom/emoji/${fileId}`, { + name, + parent: { type: "Server", id: server._id }, + }); + + setFileId(""); + }} + /> + + ); +} diff --git a/src/controllers/client/jsx/legacy/FileUploads.tsx b/src/controllers/client/jsx/legacy/FileUploads.tsx index f6597af0..fe175ebc 100644 --- a/src/controllers/client/jsx/legacy/FileUploads.tsx +++ b/src/controllers/client/jsx/legacy/FileUploads.tsx @@ -17,7 +17,11 @@ import { takeError } from "../error"; type BehaviourType = | { behaviour: "ask"; onChange: (file: File) => void } - | { behaviour: "upload"; onUpload: (id: string) => Promise } + | { + behaviour: "upload"; + onUpload: (id: string) => Promise; + previewAfterUpload?: boolean; + } | { behaviour: "multi"; onChange: (files: File[]) => void; @@ -48,7 +52,8 @@ type Props = BehaviourType & | "icons" | "avatars" | "attachments" - | "banners"; + | "banners" + | "emojis"; maxFileSize: number; remove: () => Promise; }; @@ -114,6 +119,17 @@ export function FileUploader(props: Props) { const client = useClient(); const [uploading, setUploading] = useState(false); + const [previewFile, setPreviewFile] = useState(null!); + const [generatedPreviewURL, setGeneratedPreviewURL] = useState(""); + useEffect(() => { + if (previewFile) { + const url: string = URL.createObjectURL(previewFile); + setGeneratedPreviewURL(url); + return () => URL.revokeObjectURL(url); + } + + setGeneratedPreviewURL(""); + }, [previewFile]); function onClick() { if (uploading) return; @@ -136,6 +152,10 @@ export function FileUploader(props: Props) { files[0], ), ); + + if (props.previewAfterUpload) { + setPreviewFile(files[0]); + } } } catch (err) { return modalController.push({ @@ -164,7 +184,11 @@ export function FileUploader(props: Props) { } else { onClick(); } - } else if (props.previewURL) { + } else if (props.previewURL || previewFile) { + if (previewFile) { + setPreviewFile(null!); + } + props.remove(); } else { onClick(); @@ -266,7 +290,11 @@ export function FileUploader(props: Props) { style={{ backgroundImage: style === "icon" - ? `url('${previewURL ?? defaultPreview}')` + ? `url('${ + generatedPreviewURL ?? + previewURL ?? + defaultPreview + }')` : previewURL ? `linear-gradient( rgba(0, 0, 0, 0.5), rgba(0, 0, 0, 0.5) ), url('${previewURL}')` : "none", @@ -288,7 +316,7 @@ export function FileUploader(props: Props) { {uploading ? ( - ) : props.previewURL ? ( + ) : props.previewURL || previewFile ? ( ) : ( diff --git a/src/pages/settings/ServerSettings.tsx b/src/pages/settings/ServerSettings.tsx index d008cf0a..53a0780c 100644 --- a/src/pages/settings/ServerSettings.tsx +++ b/src/pages/settings/ServerSettings.tsx @@ -6,6 +6,7 @@ import { Envelope, UserX, Trash, + HappyBeaming, } from "@styled-icons/boxicons-solid"; import { observer } from "mobx-react-lite"; import { Route, Switch, useHistory, useParams } from "react-router-dom"; @@ -22,6 +23,7 @@ import { modalController } from "../../controllers/modals/ModalController"; import { GenericSettings } from "./GenericSettings"; import { Bans } from "./server/Bans"; import { Categories } from "./server/Categories"; +import { Emojis } from "./server/Emojis"; import { Invites } from "./server/Invites"; import { Members } from "./server/Members"; import { Overview } from "./server/Overview"; @@ -68,6 +70,14 @@ export default observer(() => { title: , hideTitle: true, }, + { + category: ( + + ), + id: "emojis", + icon: , + title: , + }, { category: ( @@ -116,6 +126,11 @@ export default observer(() => { + + + + + diff --git a/src/pages/settings/server/Emojis.tsx b/src/pages/settings/server/Emojis.tsx new file mode 100644 index 00000000..9cdabfed --- /dev/null +++ b/src/pages/settings/server/Emojis.tsx @@ -0,0 +1,74 @@ +import { observer } from "mobx-react-lite"; +import { Server } from "revolt.js"; +import styled from "styled-components"; + +import { Button, Column, Row, Stacked } from "@revoltchat/ui"; + +import UserShort from "../../../components/common/user/UserShort"; +import { EmojiUploader } from "../../../components/settings/customisation/EmojiUploader"; +import { modalController } from "../../../controllers/modals/ModalController"; + +interface Props { + server: Server; +} + +const List = styled.div` + gap: 8px; + display: flex; + flex-wrap: wrap; +`; + +const Emoji = styled(Column)` + padding: 8px; + border-radius: var(--border-radius); + background: var(--secondary-background); +`; + +const Preview = styled.img` + width: 72px; + height: 72px; + object-fit: contain; + border-radius: var(--border-radius); +`; + +const UserInfo = styled(Row)` + font-size: 12px; + + svg { + width: 14px; + height: 14px; + } +`; + +export const Emojis = observer(({ server }: Props) => { + const emoji = [...server.client.emojis.values()].filter( + (x) => x.parent.id === server._id, + ); + + return ( + + +

Emojis – {emoji.length}

+ + {emoji.map((emoji) => ( + + + + + {`:${emoji.name}:`} + + + + + + ))} + +
+ ); +}); diff --git a/yarn.lock b/yarn.lock index cd804219..8cc45b1d 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2231,9 +2231,9 @@ __metadata: languageName: node linkType: hard -"@revoltchat/ui@npm:1.0.69": - version: 1.0.69 - resolution: "@revoltchat/ui@npm:1.0.69" +"@revoltchat/ui@portal:../components::locator=client%40workspace%3A.": + version: 0.0.0-use.local + resolution: "@revoltchat/ui@portal:../components::locator=client%40workspace%3A." dependencies: "@styled-icons/boxicons-logos": ^10.38.0 "@styled-icons/boxicons-regular": ^10.38.0 @@ -2247,9 +2247,8 @@ __metadata: react-virtuoso: ^2.12.0 peerDependencies: revolt.js: "*" - checksum: bb67870911689d7dccd82f96affdb0e3e89b2e688d4a7403ee9bbb6a0245dfc296f35636ababd5ed6b43a3ce26777cee03c453728088b4a29fed350dcc89ea37 languageName: node - linkType: hard + linkType: soft "@rollup/plugin-babel@npm:^5.2.0": version: 5.3.0 @@ -3608,7 +3607,7 @@ __metadata: preact-context-menu: 0.4.1 preact-i18n: ^2.4.0-preactx prettier: ^2.3.1 - prismjs: ^1.23.0 + prismjs: ^1.28.0 qrcode.react: ^3.0.2 react-beautiful-dnd: ^13.1.0 react-device-detect: 2.2.2 @@ -3618,7 +3617,7 @@ __metadata: react-router-dom: ^5.2.0 react-scroll: ^1.8.2 react-virtuoso: ^2.12.0 - revolt.js: 6.0.3 + revolt.js: 6.0.4 rimraf: ^3.0.2 sass: ^1.35.1 semver: ^7.3.7 @@ -6546,13 +6545,6 @@ __metadata: languageName: node linkType: hard -"prismjs@npm:^1.23.0": - version: 1.24.1 - resolution: "prismjs@npm:1.24.1" - checksum: e5d14a4ba56773122039295bd760c72106acc964e04cb9831b9ae7e7a58f67ccac6c053e77e21f1018a3684f31d35bb065c0c81fd4ff00b73b1570c3ace4aef0 - languageName: node - linkType: hard - "prismjs@npm:^1.28.0": version: 1.28.0 resolution: "prismjs@npm:1.28.0" @@ -6995,20 +6987,20 @@ __metadata: languageName: node linkType: hard -"revolt-api@npm:0.5.3-7": - version: 0.5.3-7 - resolution: "revolt-api@npm:0.5.3-7" +"revolt-api@npm:0.5.4": + version: 0.5.4 + resolution: "revolt-api@npm:0.5.4" dependencies: "@insertish/oapi": 0.1.16 axios: ^0.26.1 lodash.defaultsdeep: ^4.6.1 - checksum: acc2f412d1be90f0cfa8a24ba715d8257813762c56ba0e48c649efff349674517036a8fa5939eda61ce31fd64a37f0c54af3d8fae56edf4596f05b10304e090e + checksum: bd40acabac1b6c5848b1d6e555297de5aa3e0950a4de67523c4cf986a8037380e3addc5e16babebc8dfa6570cd1d1957efe9a3aaa6a206b9286e5b7f5941d699 languageName: node linkType: hard -"revolt.js@npm:6.0.3": - version: 6.0.3 - resolution: "revolt.js@npm:6.0.3" +"revolt.js@npm:6.0.4": + version: 6.0.4 + resolution: "revolt.js@npm:6.0.4" dependencies: "@insertish/exponential-backoff": 3.1.0-patch.2 "@insertish/isomorphic-ws": ^4.0.1 @@ -7019,10 +7011,10 @@ __metadata: lodash.isequal: ^4.5.0 long: ^5.2.0 mobx: ^6.3.2 - revolt-api: 0.5.3-7 + revolt-api: 0.5.4 ulid: ^2.3.0 ws: ^8.2.2 - checksum: cfecbde7a9b795da75bfac3cec05f2aed5fd02cd14fcac1b2fad26285de1b887cf7ccb339e8a63a019f1e83058b041305927e029d7b5eedcc00823582d9a842a + checksum: 7e203388c3ca8d715c4fb5b18871f97a8b966ae080bed52b9cdc6f77fdaceafe56085076f55b5cc3a9380de70fffb5b575a5e25a69792d5cb9bfd02c7e0ed426 languageName: node linkType: hard From 34bb2bbc135a3799e002d1e2377ba95b1f1c170f Mon Sep 17 00:00:00 2001 From: Paul Makles Date: Fri, 8 Jul 2022 14:24:48 +0100 Subject: [PATCH 133/151] feat: switch to `remark` from `markdown-it` (big) * replaces old mentions with avatar and display name * renders things directly through React * replaces most of the markdown hacks with custom AST components * adds a tooltip to codeblock "copy to clipboard" --- package.json | 18 +- src/components/common/Emoji.tsx | 4 +- src/components/markdown/Markdown.module.scss | 220 --- src/components/markdown/Markdown.tsx | 2 +- src/components/markdown/RemarkRenderer.tsx | 192 +++ src/components/markdown/Renderer.tsx | 290 ---- src/components/markdown/plugins/Codeblock.tsx | 78 + src/components/markdown/plugins/anchors.tsx | 34 + src/components/markdown/plugins/channels.tsx | 21 + src/components/markdown/plugins/emoji.tsx | 43 + src/components/markdown/plugins/mentions.tsx | 42 + .../markdown/plugins/remarkRegexComponent.ts | 108 ++ src/components/markdown/plugins/spoiler.tsx | 45 + src/components/markdown/plugins/timestamps.ts | 39 + src/controllers/modals/ModalController.tsx | 4 - src/lib/links.ts | 28 +- yarn.lock | 1270 ++++++++++++++++- 17 files changed, 1815 insertions(+), 623 deletions(-) delete mode 100644 src/components/markdown/Markdown.module.scss create mode 100644 src/components/markdown/RemarkRenderer.tsx delete mode 100644 src/components/markdown/Renderer.tsx create mode 100644 src/components/markdown/plugins/Codeblock.tsx create mode 100644 src/components/markdown/plugins/anchors.tsx create mode 100644 src/components/markdown/plugins/channels.tsx create mode 100644 src/components/markdown/plugins/emoji.tsx create mode 100644 src/components/markdown/plugins/mentions.tsx create mode 100644 src/components/markdown/plugins/remarkRegexComponent.ts create mode 100644 src/components/markdown/plugins/spoiler.tsx create mode 100644 src/components/markdown/plugins/timestamps.ts diff --git a/package.json b/package.json index 5fcf312b..77435e5e 100644 --- a/package.json +++ b/package.json @@ -73,7 +73,7 @@ "@hcaptcha/react-hcaptcha": "^0.3.6", "@insertish/vite-plugin-babel-macros": "^1.0.5", "@preact/preset-vite": "^2.0.0", - "@revoltchat/ui": "1.0.69", + "@revoltchat/ui": "1.0.70", "@rollup/plugin-replace": "^2.4.2", "@styled-icons/boxicons-logos": "^10.38.0", "@styled-icons/boxicons-regular": "^10.38.0", @@ -86,7 +86,6 @@ "@types/lodash": "^4", "@types/lodash.defaultsdeep": "^4.6.6", "@types/lodash.isequal": "^4.5.5", - "@types/markdown-it": "^12.0.2", "@types/node": "^15.12.4", "@types/preact-i18n": "^2.3.0", "@types/prismjs": "^1.16.5", @@ -116,8 +115,7 @@ "lodash.defaultsdeep": "^4.6.1", "lodash.isequal": "^4.5.0", "long": "^5.2.0", - "markdown-it": "^12.0.6", - "markdown-it-emoji": "^2.0.0", + "mdast-util-to-hast": "^12.1.2", "mediasoup-client": "npm:@insertish/mediasoup-client@3.6.36-esnext", "mobx": "^6.6.0", "mobx-react-lite": "3.4.0", @@ -135,7 +133,15 @@ "react-router-dom": "^5.2.0", "react-scroll": "^1.8.2", "react-virtuoso": "^2.12.0", - "revolt.js": "6.0.3", + "rehype-katex": "^6.0.2", + "rehype-prism": "^2.1.3", + "rehype-react": "^7.1.1", + "remark-breaks": "^3.0.2", + "remark-gfm": "^3.0.1", + "remark-math": "^5.1.1", + "remark-parse": "^10.0.1", + "remark-rehype": "^10.1.0", + "revolt.js": "6.0.5", "rimraf": "^3.0.2", "sass": "^1.35.1", "semver": "^7.3.7", @@ -147,6 +153,8 @@ "styled-components": "^5.3.0", "typescript": "^4.4.2", "ulid": "^2.3.0", + "unified": "^10.1.2", + "unist-util-visit": "^4.1.0", "use-resize-observer": "^7.0.0", "vite-plugin-pwa": "^0.11.13", "workbox-precaching": "^6.1.5" diff --git a/src/components/common/Emoji.tsx b/src/components/common/Emoji.tsx index 9be2e8fd..b20ca7ae 100644 --- a/src/components/common/Emoji.tsx +++ b/src/components/common/Emoji.tsx @@ -1,3 +1,5 @@ +import { emojiDictionary } from "../../assets/emojis"; + export type EmojiPack = "mutant" | "twemoji" | "noto" | "openmoji"; let EMOJI_PACK: EmojiPack = "mutant"; @@ -40,7 +42,7 @@ function toCodePoint(rune: string) { .join("-"); } -function parseEmoji(emoji: string) { +export function parseEmoji(emoji: string) { if (emoji.startsWith("custom:")) { return `https://dl.insrt.uk/projects/revolt/emotes/${emoji.substring( 7, diff --git a/src/components/markdown/Markdown.module.scss b/src/components/markdown/Markdown.module.scss deleted file mode 100644 index cff232a1..00000000 --- a/src/components/markdown/Markdown.module.scss +++ /dev/null @@ -1,220 +0,0 @@ -.markdown { - user-select: text; - - :global(.emoji) { - object-fit: contain; - - height: 1.25em; - width: 1.25em; - margin: 0 0.05em 0 0.1em; - vertical-align: -0.2em; - } - - &[data-large-emojis="true"] :global(.emoji) { - width: 3rem; - height: 3rem; - margin-bottom: 0; - margin-top: 1px; - margin-right: 2px; - vertical-align: -0.3em; - } - - p, - pre { - margin: 0; - } - - a { - text-decoration: none; - - &[data-type="mention"] { - padding: 0 6px; - flex-shrink: 0; - font-weight: 600; - display: inline-block; - background: var(--secondary-background); - border-radius: calc(var(--border-radius) * 2); - - &:hover { - text-decoration: none; - } - } - - &:hover { - text-decoration: underline; - } - } - - h1, - h2, - h3, - h4, - h5, - h6, - ul, - ol, - blockquote { - margin: 0; - } - - h1, - h2, - h3, - h4, - h5, - h6 { - &:not(:first-child) { - margin-top: 12px; - } - } - - ul, - ol { - list-style-position: inside; - padding-left: 10px; - } - - blockquote { - margin: 2px 0; - padding: 2px 0; - background: var(--hover); - border-radius: var(--border-radius); - border-inline-start: 4px solid var(--tertiary-background); - - > * { - margin: 0 8px; - } - } - - pre { - padding: 1em; - overflow-x: scroll; - border-radius: var(--border-radius); - background: var(--block) !important; - } - - p > code { - padding: 1px 4px; - flex-shrink: 0; - } - - code { - color: white; - font-size: 90%; - background: var(--block); - border-radius: var(--border-radius); - font-family: var(--monospace-font), monospace; - border-radius: 3px; - -webkit-box-decoration-break: clone; - } - - input[type="checkbox"] { - margin-right: 4px; - pointer-events: none; - } - - table { - border-collapse: collapse; - - th, - td { - padding: 6px; - border: 1px solid var(--tertiary-foreground); - } - } - - :global(.katex-block) { - overflow-x: auto; - } - - :global(.spoiler) { - padding: 0 2px; - cursor: pointer; - user-select: none; - color: transparent; - background: #151515; - border-radius: var(--border-radius); - - > * { - opacity: 0; - pointer-events: none; - } - - &:global(.shown) { - cursor: auto; - user-select: all; - color: var(--foreground); - background: var(--secondary-background); - - > * { - opacity: 1; - pointer-events: unset; - } - } - } - - :global(.code) { - font-family: var(--monospace-font), monospace; - - :global(.lang) { - width: fit-content; - padding-bottom: 8px; - - div { - color: #111; - cursor: pointer; - padding: 2px 6px; - font-weight: 600; - user-select: none; - display: inline-block; - background: var(--accent); - - font-size: 10px; - text-transform: uppercase; - box-shadow: 0 2px #787676; - border-radius: calc(var(--border-radius) / 3); - - &:active { - transform: translateY(1px); - box-shadow: 0 1px #787676; - } - } - } - } - - input[type="checkbox"] { - width: 0; - opacity: 0; - pointer-events: none; - } - - label { - pointer-events: none; - } - - input[type="checkbox"] + label:before { - width: 12px; - height: 12px; - content: "a"; - font-size: 10px; - margin-right: 6px; - line-height: 12px; - background: white; - position: relative; - display: inline-block; - border-radius: var(--border-radius); - } - - input[type="checkbox"][checked="true"] + label:before { - content: "✓"; - align-items: center; - display: inline-flex; - justify-content: center; - background: var(--accent); - } - - input[type="checkbox"] + label { - line-height: 12px; - position: relative; - } -} diff --git a/src/components/markdown/Markdown.tsx b/src/components/markdown/Markdown.tsx index 986fbd2d..9ff974fb 100644 --- a/src/components/markdown/Markdown.tsx +++ b/src/components/markdown/Markdown.tsx @@ -1,6 +1,6 @@ import { Suspense, lazy } from "preact/compat"; -const Renderer = lazy(() => import("./Renderer")); +const Renderer = lazy(() => import("./RemarkRenderer")); export interface MarkdownProps { content?: string | null; diff --git a/src/components/markdown/RemarkRenderer.tsx b/src/components/markdown/RemarkRenderer.tsx new file mode 100644 index 00000000..06d1c3f1 --- /dev/null +++ b/src/components/markdown/RemarkRenderer.tsx @@ -0,0 +1,192 @@ +import "katex/dist/katex.min.css"; +import rehypeKatex from "rehype-katex"; +import rehypePrism from "rehype-prism"; +import rehypeReact from "rehype-react"; +import remarkBreaks from "remark-breaks"; +import remarkGfm from "remark-gfm"; +import remarkMath from "remark-math"; +import remarkParse from "remark-parse"; +import remarkRehype from "remark-rehype"; +import styled, { css } from "styled-components"; +import { unified } from "unified"; + +import { createElement } from "preact"; +import { memo } from "preact/compat"; +import { useEffect, useMemo, useState } from "preact/hooks"; + +import { MarkdownProps } from "./Markdown"; +import { RenderCodeblock } from "./plugins/Codeblock"; +import { RenderAnchor } from "./plugins/anchors"; +import { remarkChannels, RenderChannel } from "./plugins/channels"; +import { isOnlyEmoji, remarkEmoji, RenderEmoji } from "./plugins/emoji"; +import { remarkMention, RenderMention } from "./plugins/mentions"; +import { passThroughComponents } from "./plugins/remarkRegexComponent"; +import { remarkSpoiler, RenderSpoiler } from "./plugins/spoiler"; +import { remarkTimestamps, timestampHandler } from "./plugins/timestamps"; +import "./prism"; + +/** + * Null element + */ +const Null: React.FC = () => null; + +/** + * Custom Markdown components + */ +const components = { + emoji: RenderEmoji, + mention: RenderMention, + spoiler: RenderSpoiler, + channel: RenderChannel, + a: RenderAnchor, + p: styled.p` + margin: 0; + + > code { + padding: 1px 4px; + flex-shrink: 0; + } + `, + h1: styled.h1` + margin: 0.2em 0; + `, + h2: styled.h2` + margin: 0.2em 0; + `, + h3: styled.h3` + margin: 0.2em 0; + `, + h4: styled.h4` + margin: 0.2em 0; + `, + h5: styled.h5` + margin: 0.2em 0; + `, + h6: styled.h6` + margin: 0.2em 0; + `, + pre: RenderCodeblock, + code: styled.code` + color: white; + background: var(--block); + + font-size: 90%; + font-family: var(--monospace-font), monospace; + + border-radius: 3px; + box-decoration-break: clone; + `, + table: styled.table` + border-collapse: collapse; + + th, + td { + padding: 6px; + border: 1px solid var(--tertiary-foreground); + } + `, + ul: styled.ul` + list-style-position: inside; + padding-left: 10px; + margin: 0.2em 0; + `, + ol: styled.ol` + list-style-position: inside; + padding-left: 10px; + margin: 0.2em 0; + `, + li: styled.li` + ${(props) => + props.class === "task-list-item" && + css` + list-style-type: none; + `} + `, + blockquote: styled.blockquote` + margin: 2px 0; + padding: 2px 0; + background: var(--hover); + border-radius: var(--border-radius); + border-inline-start: 4px solid var(--tertiary-background); + + > * { + margin: 0 8px; + } + `, + // Block image elements + img: Null, + // Catch literally everything else just in case + video: Null, + figure: Null, + picture: Null, + source: Null, + audio: Null, + script: Null, + style: Null, +}; + +/** + * Unified Markdown renderer + */ +const render = unified() + .use(remarkParse) + .use(remarkBreaks) + .use(remarkGfm) + .use(remarkMath) + .use(remarkSpoiler) + .use(remarkChannels) + .use(remarkTimestamps) + .use(remarkEmoji) + .use(remarkMention) + .use(remarkRehype, { + handlers: { + ...passThroughComponents("emoji", "spoiler", "mention", "channel"), + timestamp: timestampHandler, + }, + }) + .use(rehypeKatex, { + maxSize: 10, + maxExpand: 0, + trust: false, + strict: false, + output: "html", + throwOnError: false, + errorColor: "var(--error)", + }) + .use(rehypePrism) + // @ts-expect-error typings do not + // match between Preact and React + .use(rehypeReact, { + createElement, + Fragment, + components, + }); + +/** + * Markdown parent container + */ +const Container = styled.div<{ largeEmoji: boolean }>` + .math-display { + overflow-x: auto; + } + + --emoji-size: ${(props) => (props.largeEmoji ? "3em" : "1.25em")}; +`; + +/** + * Remark renderer component + */ +export default memo(({ content, disallowBigEmoji }: MarkdownProps) => { + const [Content, setContent] = useState(null!); + + useEffect(() => { + render.process(content!).then((file) => setContent(file.result)); + }, [content]); + + const largeEmoji = useMemo( + () => !disallowBigEmoji && isOnlyEmoji(content!), + [content, disallowBigEmoji], + ); + + return {Content}; +}); diff --git a/src/components/markdown/Renderer.tsx b/src/components/markdown/Renderer.tsx deleted file mode 100644 index 73eaedd3..00000000 --- a/src/components/markdown/Renderer.tsx +++ /dev/null @@ -1,290 +0,0 @@ -/* eslint-disable react-hooks/rules-of-hooks */ -import MarkdownKatex from "@traptitech/markdown-it-katex"; -import MarkdownSpoilers from "@traptitech/markdown-it-spoiler"; -import "katex/dist/katex.min.css"; -import MarkdownIt from "markdown-it"; -// @ts-expect-error No typings. -import MarkdownEmoji from "markdown-it-emoji/dist/markdown-it-emoji-bare"; -import { RE_MENTIONS } from "revolt.js"; - -import styles from "./Markdown.module.scss"; -import { useCallback, useContext } from "preact/hooks"; - -import { internalEmit } from "../../lib/eventEmitter"; -import { determineLink } from "../../lib/links"; - -import { dayjs } from "../../context/Locale"; - -import { emojiDictionary } from "../../assets/emojis"; -import { useClient } from "../../controllers/client/ClientController"; -import { modalController } from "../../controllers/modals/ModalController"; -import { generateEmoji } from "../common/Emoji"; -import { MarkdownProps } from "./Markdown"; -import Prism from "./prism"; - -// TODO: global.d.ts file for defining globals -declare global { - interface Window { - copycode: (element: HTMLDivElement) => void; - } -} - -// Handler for code block copy. -if (typeof window !== "undefined") { - window.copycode = function (element: HTMLDivElement) { - try { - const code = element.parentElement?.parentElement?.children[1]; - if (code) { - navigator.clipboard.writeText(code.textContent?.trim() ?? ""); - } - } catch (e) {} - }; -} - -export const md: MarkdownIt = MarkdownIt({ - breaks: true, - linkify: true, - highlight: (str, lang) => { - const v = Prism.languages[lang]; - if (v) { - const out = Prism.highlight(str, v, lang); - return `
${lang}
${out}
`; - } - - return `
${md.utils.escapeHtml(
-            str,
-        )}
`; - }, -}) - .disable("image") - .use(MarkdownEmoji, { defs: emojiDictionary }) - .use(MarkdownSpoilers) - .use(MarkdownKatex, { - throwOnError: false, - maxExpand: 0, - maxSize: 10, - strict: false, - errorColor: "var(--error)", - }); - -md.linkify.set({ fuzzyLink: false }); - -// TODO: global.d.ts file for defining globals -declare global { - interface Window { - internalHandleURL: (element: HTMLAnchorElement) => void; - } -} - -// Include emojis. -md.renderer.rules.emoji = function (token, idx) { - return generateEmoji(token[idx].content); -}; - -// Force line breaks. -// https://github.com/markdown-it/markdown-it/issues/211#issuecomment-508380611 -const defaultParagraphRenderer = - md.renderer.rules.paragraph_open || - ((tokens, idx, options, env, self) => - self.renderToken(tokens, idx, options)); - -md.renderer.rules.paragraph_open = function (tokens, idx, options, env, self) { - let result = ""; - if (idx > 1) { - const inline = tokens[idx - 2]; - const paragraph = tokens[idx]; - if ( - inline.type === "inline" && - inline.map && - inline.map[1] && - paragraph.map && - paragraph.map[0] - ) { - const diff = paragraph.map[0] - inline.map[1]; - if (diff > 0) { - result = "
".repeat(diff); - } - } - } - - return result + defaultParagraphRenderer(tokens, idx, options, env, self); -}; - -const RE_TWEMOJI = /:(\w+):/g; - -// ! FIXME: Move to library -const RE_CHANNELS = /<#([A-z0-9]{26})>/g; - -const RE_TIME = //g; - -export default function Renderer({ content, disallowBigEmoji }: MarkdownProps) { - const client = useClient(); - - if (typeof content === "undefined") return null; - if (!content || content.length === 0) return null; - - // We replace the message with the mention at the time of render. - // We don't care if the mention changes. - const newContent = content - .replace(RE_TIME, (sub: string, ...args: unknown[]) => { - if (isNaN(args[0] as number)) return sub; - const date = dayjs.unix(args[0] as number); - const format = args[1] as string; - let final = ""; - switch (format) { - case "t": - final = date.format("hh:mm"); - break; - case "T": - final = date.format("hh:mm:ss"); - break; - case "R": - final = date.fromNow(); - break; - case "D": - final = date.format("DD MMMM YYYY"); - break; - case "F": - final = date.format("dddd, DD MMMM YYYY hh:mm"); - break; - default: - final = date.format("DD MMMM YYYY hh:mm"); - break; - } - return `\`${final}\``; - }) - .replace(RE_MENTIONS, (sub: string, ...args: unknown[]) => { - const id = args[0] as string, - user = client.users.get(id); - - if (user) { - return `[@${user.username}](/@${id})`; - } - - return sub; - }) - .replace(RE_CHANNELS, (sub: string, ...args: unknown[]) => { - const id = args[0] as string, - channel = client.channels.get(id); - - if ( - channel?.channel_type === "TextChannel" || - channel?.channel_type === "VoiceChannel" - ) { - return `[#${channel.name}](/server/${channel.server_id}/channel/${id})`; - } - - return sub; - }); - - const useLargeEmojis = disallowBigEmoji - ? false - : content.replace(RE_TWEMOJI, "").trim().length === 0; - - const toggle = useCallback((ev: MouseEvent) => { - if (ev.currentTarget) { - const element = ev.currentTarget as HTMLDivElement; - if (element.classList.contains("spoiler")) { - element.classList.add("shown"); - } - } - }, []); - - const handleLink = useCallback((ev: MouseEvent) => { - if (ev.currentTarget) { - const element = ev.currentTarget as HTMLAnchorElement; - - if (ev.shiftKey) { - switch (element.dataset.type) { - case "mention": { - internalEmit( - "MessageBox", - "append", - `<@${element.dataset.mentionId}>`, - "mention", - ); - ev.preventDefault(); - return; - } - case "channel_mention": { - internalEmit( - "MessageBox", - "append", - `<#${element.dataset.mentionId}>`, - "channel_mention", - ); - ev.preventDefault(); - return; - } - } - } - - if (modalController.openLink(element.href)) { - ev.preventDefault(); - } - } - }, []); - - return ( - { - if (el) { - el.querySelectorAll(".spoiler").forEach( - (element) => { - element.removeEventListener("click", toggle); - element.addEventListener("click", toggle); - }, - ); - - el.querySelectorAll("a").forEach( - (element) => { - element.removeEventListener("click", handleLink); - element.addEventListener("click", handleLink); - element.removeAttribute("data-type"); - element.removeAttribute("data-mention-id"); - element.removeAttribute("target"); - - const link = determineLink(element.href); - switch (link.type) { - case "profile": { - element.setAttribute( - "data-type", - "mention", - ); - element.setAttribute( - "data-mention-id", - link.id, - ); - break; - } - case "navigate": { - if (link.navigation_type === "channel") { - element.setAttribute( - "data-type", - "channel_mention", - ); - element.setAttribute( - "data-mention-id", - link.channel_id, - ); - } - break; - } - case "external": { - element.setAttribute("target", "_blank"); - element.setAttribute("rel", "noreferrer"); - break; - } - } - }, - ); - } - }} - className={styles.markdown} - dangerouslySetInnerHTML={{ - __html: md.render(newContent), - }} - data-large-emojis={useLargeEmojis} - /> - ); -} diff --git a/src/components/markdown/plugins/Codeblock.tsx b/src/components/markdown/plugins/Codeblock.tsx new file mode 100644 index 00000000..85f27ece --- /dev/null +++ b/src/components/markdown/plugins/Codeblock.tsx @@ -0,0 +1,78 @@ +import styled from "styled-components"; + +import { useCallback, useRef } from "preact/hooks"; + +import { Tooltip } from "@revoltchat/ui"; + +import { modalController } from "../../../controllers/modals/ModalController"; + +/** + * Base codeblock styles + */ +const Base = styled.pre` + padding: 1em; + overflow-x: scroll; + background: var(--block); + border-radius: var(--border-radius); +`; + +/** + * Copy codeblock contents button styles + */ +const Lang = styled.div` + width: fit-content; + padding-bottom: 8px; + + a { + color: #111; + cursor: pointer; + padding: 2px 6px; + font-weight: 600; + user-select: none; + display: inline-block; + background: var(--accent); + + font-size: 10px; + text-transform: uppercase; + box-shadow: 0 2px #787676; + border-radius: calc(var(--border-radius) / 3); + + &:active { + transform: translateY(1px); + box-shadow: 0 1px #787676; + } + } +`; + +/** + * Render a codeblock with copy text button + */ +export const RenderCodeblock: React.FC<{ class: string }> = ({ + children, + ...props +}) => { + const ref = useRef(null); + + let text = "text"; + if (props.class) { + text = props.class.split("-")[1]; + } + + const onCopy = useCallback(() => { + const text = ref.current?.querySelector("code")?.innerText; + text && modalController.writeText(text); + }, [ref]); + + return ( + + + + {/** + // @ts-expect-error Preact-React */} + {text} + + + {children} + + ); +}; diff --git a/src/components/markdown/plugins/anchors.tsx b/src/components/markdown/plugins/anchors.tsx new file mode 100644 index 00000000..6d0f52f0 --- /dev/null +++ b/src/components/markdown/plugins/anchors.tsx @@ -0,0 +1,34 @@ +import { Link } from "react-router-dom"; + +import { determineLink } from "../../../lib/links"; + +import { modalController } from "../../../controllers/modals/ModalController"; + +export function RenderAnchor({ + href, + ...props +}: JSX.HTMLAttributes) { + // Pass-through no href or if anchor + if (!href || href.startsWith("#")) return ; + + // Determine type of link + const link = determineLink(href); + if (link.type === "none") return ; + + // Render direct link if internal + if (link.type === "navigate") { + return ; + } + + return ( + + modalController.openLink(href) && ev.preventDefault() + } + /> + ); +} diff --git a/src/components/markdown/plugins/channels.tsx b/src/components/markdown/plugins/channels.tsx new file mode 100644 index 00000000..a4654fdc --- /dev/null +++ b/src/components/markdown/plugins/channels.tsx @@ -0,0 +1,21 @@ +import { Link } from "react-router-dom"; + +import { clientController } from "../../../controllers/client/ClientController"; +import { createComponent, CustomComponentProps } from "./remarkRegexComponent"; + +export function RenderChannel({ match }: CustomComponentProps) { + const channel = clientController.getAvailableClient().channels.get(match)!; + + return ( + {`#${channel.name}`} + ); +} + +export const remarkChannels = createComponent( + "channel", + /<#([A-z0-9]{26})>/g, + (match) => clientController.getAvailableClient().channels.has(match), +); diff --git a/src/components/markdown/plugins/emoji.tsx b/src/components/markdown/plugins/emoji.tsx new file mode 100644 index 00000000..f97e85bc --- /dev/null +++ b/src/components/markdown/plugins/emoji.tsx @@ -0,0 +1,43 @@ +import styled from "styled-components"; + +import { emojiDictionary } from "../../../assets/emojis"; +import { clientController } from "../../../controllers/client/ClientController"; +import { parseEmoji } from "../../common/Emoji"; +import { createComponent, CustomComponentProps } from "./remarkRegexComponent"; + +const Emoji = styled.img` + object-fit: contain; + + height: var(--emoji-size); + width: var(--emoji-size); + margin: 0 0.05em 0 0.1em; + vertical-align: -0.2em; +`; + +export function RenderEmoji({ match }: CustomComponentProps) { + return ( + + ); +} + +const RE_EMOJI = /:([a-zA-Z0-9_]+):/g; + +export const remarkEmoji = createComponent( + "emoji", + RE_EMOJI, + (match) => + match in emojiDictionary || + clientController.getAvailableClient().emojis?.has(match), +); + +export function isOnlyEmoji(text: string) { + return text.replaceAll(RE_EMOJI, "").trim().length === 0; +} diff --git a/src/components/markdown/plugins/mentions.tsx b/src/components/markdown/plugins/mentions.tsx new file mode 100644 index 00000000..1411dcf5 --- /dev/null +++ b/src/components/markdown/plugins/mentions.tsx @@ -0,0 +1,42 @@ +import { RE_MENTIONS } from "revolt.js"; +import styled from "styled-components"; + +import { clientController } from "../../../controllers/client/ClientController"; +import UserShort from "../../common/user/UserShort"; +import { createComponent, CustomComponentProps } from "./remarkRegexComponent"; + +const Mention = styled.a` + gap: 4px; + padding: 0 6px; + flex-shrink: 0; + align-items: center; + display: inline-flex; + + font-weight: 600; + background: var(--secondary-background); + border-radius: calc(var(--border-radius) * 2); + + &:hover { + text-decoration: none; + } + + svg { + width: 1em; + height: 1em; + } +`; + +export function RenderMention({ match }: CustomComponentProps) { + return ( + + + + ); +} + +export const remarkMention = createComponent("mention", RE_MENTIONS, (match) => + clientController.getAvailableClient().users.has(match), +); diff --git a/src/components/markdown/plugins/remarkRegexComponent.ts b/src/components/markdown/plugins/remarkRegexComponent.ts new file mode 100644 index 00000000..6693f7b5 --- /dev/null +++ b/src/components/markdown/plugins/remarkRegexComponent.ts @@ -0,0 +1,108 @@ +import type { Handler } from "mdast-util-to-hast"; +import type { Plugin } from "unified"; +import { visit } from "unist-util-visit"; + +/** + * Props given to custom components + */ +export interface CustomComponentProps { + type: string; + match: string; + arg1: string; +} + +/** + * Create a new custom component matched by a given RegExp + * @param type hast node type + * @param regex Regex to match (must have one capture group) + * @returns Unified Plugin + */ +export function createComponent( + type: string, + regex: RegExp, + validator?: (match: string) => boolean, +): Plugin { + /** + * Plugin which transforms a given RegExp into a custom component with given name. + */ + return () => { + return (tree) => { + visit( + tree, + "text", + ( + node: { value: string }, + index: number, + parent: { children: any[] }, + ) => { + const result = []; + let start = 0; + + regex.lastIndex = 0; + + let match = regex.exec(node.value); + + while (match) { + if (!validator || validator(match[1])) { + const position = match.index; + + if (start !== position) { + result.push({ + type: "text", + value: node.value.slice(start, position), + }); + } + + result.push({ + type, + match: match[1], + arg1: match[2], + }); + start = position + match[0].length; + } + + match = regex.exec(node.value); + } + + if ( + result.length > 0 && + parent && + typeof index === "number" + ) { + if (start < node.value.length) { + result.push({ + type: "text", + value: node.value.slice(start), + }); + } + + parent.children.splice(index, 1, ...result); + return index + result.length; + } + }, + ); + }; + }; +} + +/** + * Pass-through a component as-is from remark to rehype + * @param name Tag name + * @returns Handler + */ +export const passThroughRehype: (name: string) => Handler = + (name: string) => (h, node) => + h(node, name, node); + +/** + * Pass-through multiple components at once + * @param keys Tags + * @returns Handlers + */ +export const passThroughComponents = (...keys: string[]) => { + const obj: Record = {}; + for (const key of keys) { + obj[key] = passThroughRehype(key); + } + return obj; +}; diff --git a/src/components/markdown/plugins/spoiler.tsx b/src/components/markdown/plugins/spoiler.tsx new file mode 100644 index 00000000..1986bde3 --- /dev/null +++ b/src/components/markdown/plugins/spoiler.tsx @@ -0,0 +1,45 @@ +import styled, { css } from "styled-components"; + +import { useState } from "preact/hooks"; + +import { createComponent, CustomComponentProps } from "./remarkRegexComponent"; + +const Spoiler = styled.span<{ shown: boolean }>` + padding: 0 2px; + cursor: pointer; + user-select: none; + color: transparent; + background: #151515; + border-radius: var(--border-radius); + + > * { + opacity: 0; + pointer-events: none; + } + + ${(props) => + props.shown && + css` + cursor: auto; + user-select: all; + color: var(--foreground); + background: var(--secondary-background); + + > * { + opacity: 1; + pointer-events: unset; + } + `} +`; + +export function RenderSpoiler({ match }: CustomComponentProps) { + const [shown, setShown] = useState(false); + + return ( + setShown(true)}> + {match} + + ); +} + +export const remarkSpoiler = createComponent("spoiler", /!!([^!]+)!!/g); diff --git a/src/components/markdown/plugins/timestamps.ts b/src/components/markdown/plugins/timestamps.ts new file mode 100644 index 00000000..a6760b24 --- /dev/null +++ b/src/components/markdown/plugins/timestamps.ts @@ -0,0 +1,39 @@ +import type { Handler } from "mdast-util-to-hast"; + +import { dayjs } from "../../../context/Locale"; + +import { createComponent } from "./remarkRegexComponent"; + +export const timestampHandler: Handler = (h, { match, arg1 }) => { + if (isNaN(match)) return { type: "text", value: match }; + const date = dayjs.unix(match); + + let value = ""; + switch (arg1) { + case "t": + value = date.format("hh:mm"); + break; + case "T": + value = date.format("hh:mm:ss"); + break; + case "R": + value = date.fromNow(); + break; + case "D": + value = date.format("DD MMMM YYYY"); + break; + case "F": + value = date.format("dddd, DD MMMM YYYY hh:mm"); + break; + default: + value = date.format("DD MMMM YYYY hh:mm"); + break; + } + + return h(null, "code", {}, [{ type: "text", value }]); +}; + +export const remarkTimestamps = createComponent( + "timestamp", + //g, +); diff --git a/src/controllers/modals/ModalController.tsx b/src/controllers/modals/ModalController.tsx index 0e4ac29a..c70f0487 100644 --- a/src/controllers/modals/ModalController.tsx +++ b/src/controllers/modals/ModalController.tsx @@ -210,10 +210,6 @@ class ModalControllerExtended extends ModalController { const settings = getApplicationState().settings; switch (link.type) { - case "profile": { - this.push({ type: "user_profile", user_id: link.id }); - break; - } case "navigate": { history.push(link.path); break; diff --git a/src/lib/links.ts b/src/lib/links.ts index 15a696e4..2a3801fe 100644 --- a/src/lib/links.ts +++ b/src/lib/links.ts @@ -1,11 +1,7 @@ type LinkType = - | { type: "profile"; id: string } - | { type: "navigate"; path: string; navigation_type?: null } | { type: "navigate"; path: string; - navigation_type: "channel"; - channel_id: string; } | { type: "external"; href: string; url: URL } | { type: "none" }; @@ -17,9 +13,6 @@ const ALLOWED_ORIGINS = [ "local.revolt.chat", ]; -const CHANNEL_PATH_RE = - /^\/server\/[0123456789ABCDEFGHJKMNPQRSTVWXYZ]{26}\/channel\/[0123456789ABCDEFGHJKMNPQRSTVWXYZ]{26}$/; - export function determineLink(href?: string): LinkType { let internal, url: URL | null = null; @@ -30,29 +23,12 @@ export function determineLink(href?: string): LinkType { if (ALLOWED_ORIGINS.includes(url.hostname)) { const path = url.pathname; - if (path.startsWith("/@")) { - const id = path.substr(2); - if (/[0123456789ABCDEFGHJKMNPQRSTVWXYZ]{26}/.test(id)) { - return { type: "profile", id }; - } - } else { - if (CHANNEL_PATH_RE.test(path)) { - return { - type: "navigate", - path, - navigation_type: "channel", - channel_id: path.slice(43), - }; - } - return { type: "navigate", path }; - } - - internal = true; + return { type: "navigate", path }; } } catch (err) {} if (!internal && url) { - if (url.protocol !== "javascript") { + if (!url.protocol.startsWith("javascript")) { return { type: "external", href, url }; } } diff --git a/yarn.lock b/yarn.lock index cd804219..428bc875 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2109,6 +2109,15 @@ __metadata: languageName: node linkType: hard +"@mapbox/hast-util-table-cell-style@npm:^0.2.0": + version: 0.2.0 + resolution: "@mapbox/hast-util-table-cell-style@npm:0.2.0" + dependencies: + unist-util-visit: ^1.4.1 + checksum: 4b05edda2be32e3286860bd5b50eddc8fe7d64c88de49511c9188e0e1d7c2fcba3f589a279d87cf8eb42ed5ef9ef0e788d2fcb103613e547cb3143b1bd29c49a + languageName: node + linkType: hard + "@mdn/browser-compat-data@npm:^3.3.14": version: 3.3.14 resolution: "@mdn/browser-compat-data@npm:3.3.14" @@ -2231,9 +2240,9 @@ __metadata: languageName: node linkType: hard -"@revoltchat/ui@npm:1.0.69": - version: 1.0.69 - resolution: "@revoltchat/ui@npm:1.0.69" +"@revoltchat/ui@npm:1.0.70": + version: 1.0.70 + resolution: "@revoltchat/ui@npm:1.0.70" dependencies: "@styled-icons/boxicons-logos": ^10.38.0 "@styled-icons/boxicons-regular": ^10.38.0 @@ -2247,7 +2256,7 @@ __metadata: react-virtuoso: ^2.12.0 peerDependencies: revolt.js: "*" - checksum: bb67870911689d7dccd82f96affdb0e3e89b2e688d4a7403ee9bbb6a0245dfc296f35636ababd5ed6b43a3ce26777cee03c453728088b4a29fed350dcc89ea37 + checksum: 70eba00f8b2d4fed3f83cdd64488ab4eb99acc9dd0427333a41f203912d9664f878ec04300bc3eaea2a67fef1620472cce9ff7048ef1280f950825593cf316a7 languageName: node linkType: hard @@ -2490,7 +2499,7 @@ __metadata: languageName: node linkType: hard -"@types/debug@npm:^4.1.6": +"@types/debug@npm:^4.0.0, @types/debug@npm:^4.1.6": version: 4.1.7 resolution: "@types/debug@npm:4.1.7" dependencies: @@ -2513,6 +2522,15 @@ __metadata: languageName: node linkType: hard +"@types/hast@npm:*, @types/hast@npm:^2.0.0": + version: 2.3.4 + resolution: "@types/hast@npm:2.3.4" + dependencies: + "@types/unist": "*" + checksum: fff47998f4c11e21a7454b58673f70478740ecdafd95aaf50b70a3daa7da9cdc57315545bf9c039613732c40b7b0e9e49d11d03fe9a4304721cdc3b29a88141e + languageName: node + linkType: hard + "@types/history@npm:*": version: 4.7.9 resolution: "@types/history@npm:4.7.9" @@ -2544,10 +2562,10 @@ __metadata: languageName: node linkType: hard -"@types/linkify-it@npm:*": - version: 3.0.2 - resolution: "@types/linkify-it@npm:3.0.2" - checksum: dff8f10fafb885422474e456596f12d518ec4cdd6c33cca7a08e7c86b912d301ed91cf5a7613e148c45a12600dc9ab3d85ad16d5b48dc1aaeda151a68f16b536 +"@types/katex@npm:^0.11.0": + version: 0.11.1 + resolution: "@types/katex@npm:0.11.1" + checksum: 1e51988b4b386a1b6fa8e22826ab4705bf3e6c9fb03461f2c91d28cb31095232bdeff491069ac9bc74bc4c26110be6a11a41e12ca77a2e4169f3afd8cd349355 languageName: node linkType: hard @@ -2590,18 +2608,16 @@ __metadata: languageName: node linkType: hard -"@types/markdown-it@npm:^12.0.2": - version: 12.2.1 - resolution: "@types/markdown-it@npm:12.2.1" +"@types/mdast@npm:*, @types/mdast@npm:^3.0.0": + version: 3.0.10 + resolution: "@types/mdast@npm:3.0.10" dependencies: - "@types/linkify-it": "*" - "@types/mdurl": "*" - highlight.js: ^10.7.2 - checksum: e3f367b7006e4ade2b8faa7125f06f6e5dd24ff762faedad7c55d1c99dc9905213a1021c7ae019ed1693814ec31fef25be028415d4d3061186bbf2d67646ddcb + "@types/unist": "*" + checksum: 3f587bfc0a9a2403ecadc220e61031b01734fedaf82e27eb4d5ba039c0eb54db8c85681ccc070ab4df3f7ec711b736a82b990e69caa14c74bf7ac0ccf2ac7313 languageName: node linkType: hard -"@types/mdurl@npm:*": +"@types/mdurl@npm:^1.0.0": version: 1.0.2 resolution: "@types/mdurl@npm:1.0.2" checksum: 79c7e523b377f53cf1f5a240fe23d0c6cae856667692bd21bf1d064eafe5ccc40ae39a2aa0a9a51e8c94d1307228c8f6b121e847124591a9a828c3baf65e86e2 @@ -2622,6 +2638,13 @@ __metadata: languageName: node linkType: hard +"@types/node@npm:>12": + version: 18.0.3 + resolution: "@types/node@npm:18.0.3" + checksum: 5dec59fbbc1186c808b53df1ca717dad034dbd6a901c75f5b052c845618b531b05f27217122c6254db99529a68618e4cfc534ae3dbf4e88754e9e572df80defa + languageName: node + linkType: hard + "@types/node@npm:^15.12.4": version: 15.14.9 resolution: "@types/node@npm:15.14.9" @@ -2636,6 +2659,13 @@ __metadata: languageName: node linkType: hard +"@types/parse5@npm:^6.0.0": + version: 6.0.3 + resolution: "@types/parse5@npm:6.0.3" + checksum: ddb59ee4144af5dfcc508a8dcf32f37879d11e12559561e65788756b95b33e6f03ea027d88e1f5408f9b7bfb656bf630ace31a2169edf44151daaf8dd58df1b7 + languageName: node + linkType: hard + "@types/preact-i18n@npm:^2.3.0": version: 2.3.1 resolution: "@types/preact-i18n@npm:2.3.1" @@ -2652,6 +2682,13 @@ __metadata: languageName: node linkType: hard +"@types/prismjs@npm:^1.16.6": + version: 1.26.0 + resolution: "@types/prismjs@npm:1.26.0" + checksum: cd5e7a6214c1f4213ec512a5fcf6d8fe37a56b813fc57ac95b5ff5ee074742bfdbd2f2730d9fd985205bf4586728e09baa97023f739e5aa1c9735a7c1ecbd11a + languageName: node + linkType: hard + "@types/prop-types@npm:*": version: 15.7.4 resolution: "@types/prop-types@npm:15.7.4" @@ -2778,6 +2815,13 @@ __metadata: languageName: node linkType: hard +"@types/unist@npm:*, @types/unist@npm:^2.0.0": + version: 2.0.6 + resolution: "@types/unist@npm:2.0.6" + checksum: 25cb860ff10dde48b54622d58b23e66214211a61c84c0f15f88d38b61aa1b53d4d46e42b557924a93178c501c166aa37e28d7f6d994aba13d24685326272d5db + languageName: node + linkType: hard + "@typescript-eslint/eslint-plugin@npm:^4.27.0": version: 4.29.3 resolution: "@typescript-eslint/eslint-plugin@npm:4.29.3" @@ -3301,6 +3345,13 @@ __metadata: languageName: node linkType: hard +"bail@npm:^2.0.0": + version: 2.0.2 + resolution: "bail@npm:2.0.2" + checksum: aab4e8ccdc8d762bf3fdfce8e706601695620c0c2eda256dd85088dc0be3cfd7ff126f6e99c2bee1f24f5d418414aacf09d7f9702f16d6963df2fa488cda8824 + languageName: node + linkType: hard + "balanced-match@npm:^1.0.0": version: 1.0.2 resolution: "balanced-match@npm:1.0.2" @@ -3315,6 +3366,13 @@ __metadata: languageName: node linkType: hard +"boolbase@npm:^1.0.0": + version: 1.0.0 + resolution: "boolbase@npm:1.0.0" + checksum: 3e25c80ef626c3a3487c73dbfc70ac322ec830666c9ad915d11b701142fab25ec1e63eff2c450c74347acfd2de854ccde865cd79ef4db1683f7c7b046ea43bb0 + languageName: node + linkType: hard + "bowser@npm:^2.11.0": version: 2.11.0 resolution: "bowser@npm:2.11.0" @@ -3458,6 +3516,13 @@ __metadata: languageName: node linkType: hard +"ccount@npm:^2.0.0": + version: 2.0.1 + resolution: "ccount@npm:2.0.1" + checksum: 48193dada54c9e260e0acf57fc16171a225305548f9ad20d5471e0f7a8c026aedd8747091dccb0d900cde7df4e4ddbd235df0d8de4a64c71b12f0d3303eeafd4 + languageName: node + linkType: hard + "chalk@npm:^2.0.0, chalk@npm:^2.4.2": version: 2.4.2 resolution: "chalk@npm:2.4.2" @@ -3479,6 +3544,13 @@ __metadata: languageName: node linkType: hard +"character-entities@npm:^2.0.0": + version: 2.0.2 + resolution: "character-entities@npm:2.0.2" + checksum: cf1643814023697f725e47328fcec17923b8f1799102a8a79c1514e894815651794a2bffd84bb1b3a4b124b050154e4529ed6e81f7c8068a734aecf07a6d3def + languageName: node + linkType: hard + "charcodes@npm:^0.2.0": version: 0.2.0 resolution: "charcodes@npm:0.2.0" @@ -3554,7 +3626,7 @@ __metadata: "@hcaptcha/react-hcaptcha": ^0.3.6 "@insertish/vite-plugin-babel-macros": ^1.0.5 "@preact/preset-vite": ^2.0.0 - "@revoltchat/ui": 1.0.69 + "@revoltchat/ui": 1.0.70 "@rollup/plugin-replace": ^2.4.2 "@styled-icons/boxicons-logos": ^10.38.0 "@styled-icons/boxicons-regular": ^10.38.0 @@ -3567,7 +3639,6 @@ __metadata: "@types/lodash": ^4 "@types/lodash.defaultsdeep": ^4.6.6 "@types/lodash.isequal": ^4.5.5 - "@types/markdown-it": ^12.0.2 "@types/node": ^15.12.4 "@types/preact-i18n": ^2.3.0 "@types/prismjs": ^1.16.5 @@ -3599,8 +3670,7 @@ __metadata: lodash.defaultsdeep: ^4.6.1 lodash.isequal: ^4.5.0 long: ^5.2.0 - markdown-it: ^12.0.6 - markdown-it-emoji: ^2.0.0 + mdast-util-to-hast: ^12.1.2 mediasoup-client: "npm:@insertish/mediasoup-client@3.6.36-esnext" mobx: ^6.6.0 mobx-react-lite: 3.4.0 @@ -3618,7 +3688,15 @@ __metadata: react-router-dom: ^5.2.0 react-scroll: ^1.8.2 react-virtuoso: ^2.12.0 - revolt.js: 6.0.3 + rehype-katex: ^6.0.2 + rehype-prism: ^2.1.3 + rehype-react: ^7.1.1 + remark-breaks: ^3.0.2 + remark-gfm: ^3.0.1 + remark-math: ^5.1.1 + remark-parse: ^10.0.1 + remark-rehype: ^10.1.0 + revolt.js: 6.0.5 rimraf: ^3.0.2 sass: ^1.35.1 semver: ^7.3.7 @@ -3631,6 +3709,8 @@ __metadata: styled-components: ^5.3.0 typescript: ^4.4.2 ulid: ^2.3.0 + unified: ^10.1.2 + unist-util-visit: ^4.1.0 use-resize-observer: ^7.0.0 vite: ^2.6.14 vite-plugin-pwa: ^0.11.13 @@ -3712,6 +3792,13 @@ __metadata: languageName: node linkType: hard +"comma-separated-tokens@npm:^2.0.0": + version: 2.0.2 + resolution: "comma-separated-tokens@npm:2.0.2" + checksum: 8fa68ff2605233571536a802a7c712b0c766e0c5088e067be72740054e84d040865eea945c984924ae84932bcc3e25a99f71601220b438e875b5f42b87277767 + languageName: node + linkType: hard + "commander@npm:^2.20.0": version: 2.20.3 resolution: "commander@npm:2.20.3" @@ -3726,6 +3813,13 @@ __metadata: languageName: node linkType: hard +"commander@npm:^8.0.0": + version: 8.3.0 + resolution: "commander@npm:8.3.0" + checksum: 0f82321821fc27b83bd409510bb9deeebcfa799ff0bf5d102128b500b7af22872c0c92cb6a0ebc5a4cf19c6b550fba9cedfa7329d18c6442a625f851377bacf0 + languageName: node + linkType: hard + "comment-parser@npm:1.3.1": version: 1.3.1 resolution: "comment-parser@npm:1.3.1" @@ -3848,6 +3942,13 @@ __metadata: languageName: node linkType: hard +"css-selector-parser@npm:^1.0.0": + version: 1.4.1 + resolution: "css-selector-parser@npm:1.4.1" + checksum: 31948754e579eedb918c2fb2d5a4c643ec769ff4a0d03a7bd10b43b25d44973f8cbe86d7ec00c4494269f7ff38b3d2ab0f6ea801cece0ef0974e74469dff770c + languageName: node + linkType: hard + "css-to-react-native@npm:^3.0.0": version: 3.0.0 resolution: "css-to-react-native@npm:3.0.0" @@ -3873,7 +3974,7 @@ __metadata: languageName: node linkType: hard -"debug@npm:4, debug@npm:^4.3.4": +"debug@npm:4, debug@npm:^4.0.0, debug@npm:^4.3.4": version: 4.3.4 resolution: "debug@npm:4.3.4" dependencies: @@ -3909,6 +4010,15 @@ __metadata: languageName: node linkType: hard +"decode-named-character-reference@npm:^1.0.0": + version: 1.0.2 + resolution: "decode-named-character-reference@npm:1.0.2" + dependencies: + character-entities: ^2.0.0 + checksum: f4c71d3b93105f20076052f9cb1523a22a9c796b8296cd35eef1ca54239c78d182c136a848b83ff8da2071e3ae2b1d300bf29d00650a6d6e675438cc31b11d78 + languageName: node + linkType: hard + "deep-is@npm:^0.1.3": version: 0.1.3 resolution: "deep-is@npm:0.1.3" @@ -3946,6 +4056,13 @@ __metadata: languageName: node linkType: hard +"dequal@npm:^2.0.0": + version: 2.0.2 + resolution: "dequal@npm:2.0.2" + checksum: 86c7a2c59f7b0797ed397c74b5fcdb744e48fc19440b70ad6ac59f57550a96b0faef3f1cfd5760ec5e6d3f7cb101f634f1f80db4e727b1dc8389bf62d977c0a0 + languageName: node + linkType: hard + "detect-browser@npm:^5.2.0": version: 5.2.0 resolution: "detect-browser@npm:5.2.0" @@ -3953,6 +4070,13 @@ __metadata: languageName: node linkType: hard +"diff@npm:^5.0.0": + version: 5.1.0 + resolution: "diff@npm:5.1.0" + checksum: c7bf0df7c9bfbe1cf8a678fd1b2137c4fb11be117a67bc18a0e03ae75105e8533dbfb1cda6b46beb3586ef5aed22143ef9d70713977d5fb1f9114e21455fba90 + languageName: node + linkType: hard + "dir-glob@npm:^3.0.1": version: 3.0.1 resolution: "dir-glob@npm:3.0.1" @@ -4046,13 +4170,6 @@ __metadata: languageName: node linkType: hard -"entities@npm:~2.1.0": - version: 2.1.0 - resolution: "entities@npm:2.1.0" - checksum: a10a877e489586a3f6a691fe49bf3fc4e58f06c8e80522f08214a5150ba457e7017b447d4913a3fa041bda06ee4c92517baa4d8d75373eaa79369e9639225ffd - languageName: node - linkType: hard - "env-paths@npm:^2.2.0": version: 2.2.1 resolution: "env-paths@npm:2.2.1" @@ -4351,6 +4468,13 @@ __metadata: languageName: node linkType: hard +"escape-string-regexp@npm:^5.0.0": + version: 5.0.0 + resolution: "escape-string-regexp@npm:5.0.0" + checksum: 20daabe197f3cb198ec28546deebcf24b3dbb1a5a269184381b3116d12f0532e06007f4bc8da25669d6a7f8efb68db0758df4cd981f57bc5b57f521a3e12c59e + languageName: node + linkType: hard + "eslint-config-preact@npm:^1.1.4": version: 1.1.4 resolution: "eslint-config-preact@npm:1.1.4" @@ -4642,6 +4766,13 @@ __metadata: languageName: node linkType: hard +"extend@npm:^3.0.0": + version: 3.0.2 + resolution: "extend@npm:3.0.2" + checksum: a50a8309ca65ea5d426382ff09f33586527882cf532931cb08ca786ea3146c0553310bda688710ff61d7668eba9f96b923fe1420cdf56a2c3eaf30fcab87b515 + languageName: node + linkType: hard + "fake-mediastreamtrack@npm:^1.1.6": version: 1.1.6 resolution: "fake-mediastreamtrack@npm:1.1.6" @@ -5083,10 +5214,84 @@ __metadata: languageName: node linkType: hard -"highlight.js@npm:^10.7.2": - version: 10.7.3 - resolution: "highlight.js@npm:10.7.3" - checksum: defeafcd546b535d710d8efb8e650af9e3b369ef53e28c3dc7893eacfe263200bba4c5fcf43524ae66d5c0c296b1af0870523ceae3e3104d24b7abf6374a4fea +"hast-to-hyperscript@npm:^10.0.0": + version: 10.0.1 + resolution: "hast-to-hyperscript@npm:10.0.1" + dependencies: + "@types/unist": ^2.0.0 + comma-separated-tokens: ^2.0.0 + property-information: ^6.0.0 + space-separated-tokens: ^2.0.0 + style-to-object: ^0.3.0 + unist-util-is: ^5.0.0 + web-namespaces: ^2.0.0 + checksum: 0ec7a6f873312421c6cfa84f8c842fa00c74e96018c371ace4800fda6590e208db8e31d4e84b09e436fe6b9b87b2fd2968b30c27881ff82fc9fe466a0f59b922 + languageName: node + linkType: hard + +"hast-util-from-parse5@npm:^7.0.0": + version: 7.1.0 + resolution: "hast-util-from-parse5@npm:7.1.0" + dependencies: + "@types/hast": ^2.0.0 + "@types/parse5": ^6.0.0 + "@types/unist": ^2.0.0 + hastscript: ^7.0.0 + property-information: ^6.0.0 + vfile: ^5.0.0 + vfile-location: ^4.0.0 + web-namespaces: ^2.0.0 + checksum: 4a774700042e03aeecca6b6977f0e915069eefcf81c30d59ae0e1d2d7170e419065bcd8708504cb7b4d19b05367daee2177ddce47db1b5a654bb7ec19ba8d227 + languageName: node + linkType: hard + +"hast-util-is-element@npm:^2.0.0": + version: 2.1.2 + resolution: "hast-util-is-element@npm:2.1.2" + dependencies: + "@types/hast": ^2.0.0 + "@types/unist": ^2.0.0 + checksum: c5fe9f7cde3775d4cbe19a9a55631a80b7a4ea0131fc2e3d097ebe228a35f09b9219f64b788b7a9cf819e6dcb6d1fc7830fd2f10ad536649e436e8c83da41e00 + languageName: node + linkType: hard + +"hast-util-parse-selector@npm:^3.0.0": + version: 3.1.0 + resolution: "hast-util-parse-selector@npm:3.1.0" + dependencies: + "@types/hast": ^2.0.0 + checksum: 8be1a2334652866b40fde72a8b7d0867a791ce8a70d15fd7bb44b9a4f349913b77999e5add41900466bc9461c6b0fdea391875ef534b33cacf7a2aee9d8e447c + languageName: node + linkType: hard + +"hast-util-to-text@npm:^3.1.0": + version: 3.1.1 + resolution: "hast-util-to-text@npm:3.1.1" + dependencies: + "@types/hast": ^2.0.0 + hast-util-is-element: ^2.0.0 + unist-util-find-after: ^4.0.0 + checksum: 2312a818c8ec7b02307b04175357e5a7a9918f48624d05366668ba60918734ca62b0ee21006a2a448e0e5a198654cd1fa4ba8c813702b465cb487e2320db523a + languageName: node + linkType: hard + +"hast-util-whitespace@npm:^2.0.0": + version: 2.0.0 + resolution: "hast-util-whitespace@npm:2.0.0" + checksum: abeb5386075bfb0facfce89eed0e13d2cb27a0910cec8fd234b48821a1538387a73fa7f458842e8c404148dc69434acbc10488d75b02817e460652c2c894c024 + languageName: node + linkType: hard + +"hastscript@npm:^7.0.0": + version: 7.0.2 + resolution: "hastscript@npm:7.0.2" + dependencies: + "@types/hast": ^2.0.0 + comma-separated-tokens: ^2.0.0 + hast-util-parse-selector: ^3.0.0 + property-information: ^6.0.0 + space-separated-tokens: ^2.0.0 + checksum: ee33aff714b12f9f83049550956c7fb3e5ac7bdd20e77b57dc01b66de06e8bb0b3ba24153d4b6a1d7fa660bfef91125ac29e1bb04fb628e30d11097d28037235 languageName: node linkType: hard @@ -5242,6 +5447,13 @@ __metadata: languageName: node linkType: hard +"inline-style-parser@npm:0.1.1": + version: 0.1.1 + resolution: "inline-style-parser@npm:0.1.1" + checksum: 5d545056a3e1f2bf864c928a886a0e1656a3517127d36917b973de581bd54adc91b4bf1febcb0da054f204b4934763f1a4e09308b4d55002327cf1d48ac5d966 + languageName: node + linkType: hard + "internal-slot@npm:^1.0.3": version: 1.0.3 resolution: "internal-slot@npm:1.0.3" @@ -5295,6 +5507,13 @@ __metadata: languageName: node linkType: hard +"is-buffer@npm:^2.0.0": + version: 2.0.5 + resolution: "is-buffer@npm:2.0.5" + checksum: 764c9ad8b523a9f5a32af29bdf772b08eb48c04d2ad0a7240916ac2688c983bf5f8504bf25b35e66240edeb9d9085461f9b5dae1f3d2861c6b06a65fe983de42 + languageName: node + linkType: hard + "is-callable@npm:^1.1.4, is-callable@npm:^1.2.3, is-callable@npm:^1.2.4": version: 1.2.4 resolution: "is-callable@npm:1.2.4" @@ -5394,6 +5613,13 @@ __metadata: languageName: node linkType: hard +"is-plain-obj@npm:^4.0.0": + version: 4.1.0 + resolution: "is-plain-obj@npm:4.1.0" + checksum: 6dc45da70d04a81f35c9310971e78a6a3c7a63547ef782e3a07ee3674695081b6ca4e977fbb8efc48dae3375e0b34558d2bcd722aec9bddfa2d7db5b041be8ce + languageName: node + linkType: hard + "is-plain-object@npm:^5.0.0": version: 5.0.0 resolution: "is-plain-object@npm:5.0.0" @@ -5652,6 +5878,17 @@ __metadata: languageName: node linkType: hard +"katex@npm:^0.13.0": + version: 0.13.24 + resolution: "katex@npm:0.13.24" + dependencies: + commander: ^8.0.0 + bin: + katex: cli.js + checksum: 1b7c8295867073d0db4f6fb41ef1c0e3418b8e23924ff61b446b36134cb74cdadc7242dfbfb922d9c32f0b15eda6160a08cd30948c4e78141966ca2991a1726b + languageName: node + linkType: hard + "katex@npm:^0.13.9": version: 0.13.16 resolution: "katex@npm:0.13.16" @@ -5663,6 +5900,17 @@ __metadata: languageName: node linkType: hard +"katex@npm:^0.15.0": + version: 0.15.6 + resolution: "katex@npm:0.15.6" + dependencies: + commander: ^8.0.0 + bin: + katex: cli.js + checksum: 2da808bbd1d3be27715006cd86767dd3fcce3e317fb3bbd64d407328d2d90de17b5d83062b2cfd0e0d0de32e340efbac214862bc96892a5d1492462e553728d4 + languageName: node + linkType: hard + "klaw@npm:^3.0.0": version: 3.0.0 resolution: "klaw@npm:3.0.0" @@ -5679,6 +5927,13 @@ __metadata: languageName: node linkType: hard +"kleur@npm:^4.0.3": + version: 4.1.5 + resolution: "kleur@npm:4.1.5" + checksum: 1dc476e32741acf0b1b5b0627ffd0d722e342c1b0da14de3e8ae97821327ca08f9fb944542fb3c126d90ac5f27f9d804edbe7c585bf7d12ef495d115e0f22c12 + languageName: node + linkType: hard + "kolorist@npm:^1.2.10": version: 1.5.0 resolution: "kolorist@npm:1.5.0" @@ -5719,15 +5974,6 @@ __metadata: languageName: node linkType: hard -"linkify-it@npm:^3.0.1": - version: 3.0.2 - resolution: "linkify-it@npm:3.0.2" - dependencies: - uc.micro: ^1.0.1 - checksum: 08e14854ec3c29e3578311b1cd95e469952a27f191633189a23890628939fc45c6d84fa4495abb9f7f06e60f73a31b8881d834214864d46db914a09ffc7889ae - languageName: node - linkType: hard - "local-access@npm:^1.0.1": version: 1.1.0 resolution: "local-access@npm:1.1.0" @@ -5837,6 +6083,13 @@ __metadata: languageName: node linkType: hard +"longest-streak@npm:^3.0.0": + version: 3.0.1 + resolution: "longest-streak@npm:3.0.1" + checksum: 3b59c4c04ce3c70f137e339c10d574026fa3a711c45dc0e69a63a2c0ac981e57f837e1d5b64b991eee5234c4fa46fa10886a20626fb739ed3b04b77bcf6d14a8 + languageName: node + linkType: hard + "loose-envify@npm:^1.2.0, loose-envify@npm:^1.3.1, loose-envify@npm:^1.4.0": version: 1.4.0 resolution: "loose-envify@npm:1.4.0" @@ -5897,29 +6150,177 @@ __metadata: languageName: node linkType: hard -"markdown-it-emoji@npm:^2.0.0": - version: 2.0.0 - resolution: "markdown-it-emoji@npm:2.0.0" - checksum: 7d25844134d98a4e2cf70d9a14ea4ec5afc3f64740d69c88012795c8a24ed2f286850b72fd4437d60d2fe34f73cc1889a7e8e3ab4663f4fc3a4899991601bbba +"markdown-table@npm:^3.0.0": + version: 3.0.2 + resolution: "markdown-table@npm:3.0.2" + checksum: 7bd9eb54e7ac15165f79730ac6357b8194294552f727bcb34e29f3f1b72823c1220cb61153ebf0962c8faac4d25e49c62e8e9471cd6352a67cdca99928ecade1 languageName: node linkType: hard -"markdown-it@npm:^12.0.6": - version: 12.2.0 - resolution: "markdown-it@npm:12.2.0" +"mdast-util-definitions@npm:^5.0.0": + version: 5.1.1 + resolution: "mdast-util-definitions@npm:5.1.1" dependencies: - argparse: ^2.0.1 - entities: ~2.1.0 - linkify-it: ^3.0.1 - mdurl: ^1.0.1 - uc.micro: ^1.0.5 - bin: - markdown-it: bin/markdown-it.js - checksum: 8e3d6646edf8e7ef19ed707c59d16741bd40804f1e7567407efd2f346ae0f7ddcdeada83e7affebd21b9d7d947b27fc60fd795a970461785030a4e52e750122b + "@types/mdast": ^3.0.0 + "@types/unist": ^2.0.0 + unist-util-visit: ^4.0.0 + checksum: f8025e2c35f6f8641528037abe18f492ef100e00a48c92cf78b7a313f9ccdb0e30c6aed0b40539767a3f425be09e78cb0f2f9bc4131fff41ea4664a1a7314a14 languageName: node linkType: hard -"mdurl@npm:^1.0.1": +"mdast-util-find-and-replace@npm:^2.0.0": + version: 2.2.0 + resolution: "mdast-util-find-and-replace@npm:2.2.0" + dependencies: + escape-string-regexp: ^5.0.0 + unist-util-is: ^5.0.0 + unist-util-visit-parents: ^5.0.0 + checksum: 1ca772fcecc07a1c61c115df1185b4454c830f7f5c7c5bcf34957af58af6b93c355c6d324afa8f6de33c4ad1a338e426ff391ffb7e4686a6deeae091a4c0eeaa + languageName: node + linkType: hard + +"mdast-util-from-markdown@npm:^1.0.0": + version: 1.2.0 + resolution: "mdast-util-from-markdown@npm:1.2.0" + dependencies: + "@types/mdast": ^3.0.0 + "@types/unist": ^2.0.0 + decode-named-character-reference: ^1.0.0 + mdast-util-to-string: ^3.1.0 + micromark: ^3.0.0 + micromark-util-decode-numeric-character-reference: ^1.0.0 + micromark-util-decode-string: ^1.0.0 + micromark-util-normalize-identifier: ^1.0.0 + micromark-util-symbol: ^1.0.0 + micromark-util-types: ^1.0.0 + unist-util-stringify-position: ^3.0.0 + uvu: ^0.5.0 + checksum: fadc3521a3d95f4adbadad462ca27c28b3bfe08740ae158dc0c4a22329bf5593254d98b8fd4024ecad8c47c77ec275454dfacfb907ff1b98ff8f5de25c716d40 + languageName: node + linkType: hard + +"mdast-util-gfm-autolink-literal@npm:^1.0.0": + version: 1.0.2 + resolution: "mdast-util-gfm-autolink-literal@npm:1.0.2" + dependencies: + "@types/mdast": ^3.0.0 + ccount: ^2.0.0 + mdast-util-find-and-replace: ^2.0.0 + micromark-util-character: ^1.0.0 + checksum: 75e12f21ec24552ba33725f69a06cd703e5586d2296ca9d180927b2293c036e1bd39108adba83e8cbbefcc45ffd8821fb561b4c107684ed87bd9e5e286ba03bd + languageName: node + linkType: hard + +"mdast-util-gfm-footnote@npm:^1.0.0": + version: 1.0.1 + resolution: "mdast-util-gfm-footnote@npm:1.0.1" + dependencies: + "@types/mdast": ^3.0.0 + mdast-util-to-markdown: ^1.3.0 + micromark-util-normalize-identifier: ^1.0.0 + checksum: 4caf69058b438c9e34004acfb1d2b20d58306898d760b889f73d27ed5702cd940be9fcb2a08f6e58b8d9d8e2b1c886c549cd7d23b659da5fb2ed87a22f44c13c + languageName: node + linkType: hard + +"mdast-util-gfm-strikethrough@npm:^1.0.0": + version: 1.0.1 + resolution: "mdast-util-gfm-strikethrough@npm:1.0.1" + dependencies: + "@types/mdast": ^3.0.0 + mdast-util-to-markdown: ^1.3.0 + checksum: ce81222ab4c130516278f8db57be23bd529e9f8c30bb16ab5b2bf294c0dfd57f2dc7a010deede65f349a8d37be73f90dbaecd962f76f70befa8f43bcd32fe5b9 + languageName: node + linkType: hard + +"mdast-util-gfm-table@npm:^1.0.0": + version: 1.0.4 + resolution: "mdast-util-gfm-table@npm:1.0.4" + dependencies: + markdown-table: ^3.0.0 + mdast-util-from-markdown: ^1.0.0 + mdast-util-to-markdown: ^1.3.0 + checksum: 56d9f0376b3da3e4cc0f5047d62a4eefa765934a1084822bc7804e7cf93c458c4bff2a917fa4e89c917287431a7284b656bf23ef89553e943d7f853ffefae693 + languageName: node + linkType: hard + +"mdast-util-gfm-task-list-item@npm:^1.0.0": + version: 1.0.1 + resolution: "mdast-util-gfm-task-list-item@npm:1.0.1" + dependencies: + "@types/mdast": ^3.0.0 + mdast-util-to-markdown: ^1.3.0 + checksum: 9bb0f162532f8e11e571802ed19301572479fe9507652c8fb3f648279bbde3baa9f6377d9492dbba61eedd96755f8aff9c7c259287875544fb751907d79da69e + languageName: node + linkType: hard + +"mdast-util-gfm@npm:^2.0.0": + version: 2.0.1 + resolution: "mdast-util-gfm@npm:2.0.1" + dependencies: + mdast-util-from-markdown: ^1.0.0 + mdast-util-gfm-autolink-literal: ^1.0.0 + mdast-util-gfm-footnote: ^1.0.0 + mdast-util-gfm-strikethrough: ^1.0.0 + mdast-util-gfm-table: ^1.0.0 + mdast-util-gfm-task-list-item: ^1.0.0 + mdast-util-to-markdown: ^1.0.0 + checksum: 8b39e6694521094ae28d12cbeff074ef3ec3f7f7ec59fbddd4e8a45a275e092c6ba6ecee4c720938eb3ee072ebd41d743b08cc0ab9171612a5aeddc1e78ae882 + languageName: node + linkType: hard + +"mdast-util-math@npm:^2.0.0": + version: 2.0.1 + resolution: "mdast-util-math@npm:2.0.1" + dependencies: + "@types/mdast": ^3.0.0 + longest-streak: ^3.0.0 + mdast-util-to-markdown: ^1.3.0 + checksum: 7576b466276717198fc461501d40c930be50b1754ca82979bd16df08e5fb7b959587c2090fbe044f050cf3f24c5c6febf3756a96031ecabe4b82f11d38c74546 + languageName: node + linkType: hard + +"mdast-util-to-hast@npm:^12.1.0, mdast-util-to-hast@npm:^12.1.2": + version: 12.1.2 + resolution: "mdast-util-to-hast@npm:12.1.2" + dependencies: + "@types/hast": ^2.0.0 + "@types/mdast": ^3.0.0 + "@types/mdurl": ^1.0.0 + mdast-util-definitions: ^5.0.0 + mdurl: ^1.0.0 + micromark-util-sanitize-uri: ^1.0.0 + trim-lines: ^3.0.0 + unist-builder: ^3.0.0 + unist-util-generated: ^2.0.0 + unist-util-position: ^4.0.0 + unist-util-visit: ^4.0.0 + checksum: 7bb888e73ce2ffc67d57868f3f7190fc8f30dda7b1293a14dd9b8fe6e72432f63db5c1e3015f9743c9f5bb17b33f622e3cea60bd54679395c9bc1949c16e4ce7 + languageName: node + linkType: hard + +"mdast-util-to-markdown@npm:^1.0.0, mdast-util-to-markdown@npm:^1.3.0": + version: 1.3.0 + resolution: "mdast-util-to-markdown@npm:1.3.0" + dependencies: + "@types/mdast": ^3.0.0 + "@types/unist": ^2.0.0 + longest-streak: ^3.0.0 + mdast-util-to-string: ^3.0.0 + micromark-util-decode-string: ^1.0.0 + unist-util-visit: ^4.0.0 + zwitch: ^2.0.0 + checksum: 0ea4fc11b7a49b15d400d50044429c45222cb9bc583553288c7c54704d051f25049233817129ba56a6f581f1e20916e5c540870a80987318747a95b44a36ba3e + languageName: node + linkType: hard + +"mdast-util-to-string@npm:^3.0.0, mdast-util-to-string@npm:^3.1.0": + version: 3.1.0 + resolution: "mdast-util-to-string@npm:3.1.0" + checksum: f42ddd4e22f2215a75715b92ea6e3149c4ba356e7781d7b94fc86ded1c79cec3f986afeecef3a4a80068c9b224a6520099783a12146b957de24f020a3e47dd29 + languageName: node + linkType: hard + +"mdurl@npm:^1.0.0": version: 1.0.1 resolution: "mdurl@npm:1.0.1" checksum: 71731ecba943926bfbf9f9b51e28b5945f9411c4eda80894221b47cc105afa43ba2da820732b436f0798fd3edbbffcd1fc1415843c41a87fea08a41cc1e3d02b @@ -5965,6 +6366,352 @@ __metadata: languageName: node linkType: hard +"micromark-core-commonmark@npm:^1.0.0, micromark-core-commonmark@npm:^1.0.1": + version: 1.0.6 + resolution: "micromark-core-commonmark@npm:1.0.6" + dependencies: + decode-named-character-reference: ^1.0.0 + micromark-factory-destination: ^1.0.0 + micromark-factory-label: ^1.0.0 + micromark-factory-space: ^1.0.0 + micromark-factory-title: ^1.0.0 + micromark-factory-whitespace: ^1.0.0 + micromark-util-character: ^1.0.0 + micromark-util-chunked: ^1.0.0 + micromark-util-classify-character: ^1.0.0 + micromark-util-html-tag-name: ^1.0.0 + micromark-util-normalize-identifier: ^1.0.0 + micromark-util-resolve-all: ^1.0.0 + micromark-util-subtokenize: ^1.0.0 + micromark-util-symbol: ^1.0.0 + micromark-util-types: ^1.0.1 + uvu: ^0.5.0 + checksum: 4b483c46077f696ed310f6d709bb9547434c218ceb5c1220fde1707175f6f68b44da15ab8668f9c801e1a123210071e3af883a7d1215122c913fd626f122bfc2 + languageName: node + linkType: hard + +"micromark-extension-gfm-autolink-literal@npm:^1.0.0": + version: 1.0.3 + resolution: "micromark-extension-gfm-autolink-literal@npm:1.0.3" + dependencies: + micromark-util-character: ^1.0.0 + micromark-util-sanitize-uri: ^1.0.0 + micromark-util-symbol: ^1.0.0 + micromark-util-types: ^1.0.0 + uvu: ^0.5.0 + checksum: bb181972ac346ca73ca1ab0b80b80c9d6509ed149799d2217d5442670f499c38a94edff73d32fa52b390d89640974cfbd7f29e4ad7d599581d5e1cabcae636a2 + languageName: node + linkType: hard + +"micromark-extension-gfm-footnote@npm:^1.0.0": + version: 1.0.4 + resolution: "micromark-extension-gfm-footnote@npm:1.0.4" + dependencies: + micromark-core-commonmark: ^1.0.0 + micromark-factory-space: ^1.0.0 + micromark-util-character: ^1.0.0 + micromark-util-normalize-identifier: ^1.0.0 + micromark-util-sanitize-uri: ^1.0.0 + micromark-util-symbol: ^1.0.0 + micromark-util-types: ^1.0.0 + uvu: ^0.5.0 + checksum: 8daa203f5cf753338d5ecdbaae6b3ab6319d34b6013b90ea6860bed299418cecf86e69e48dabe42562e334760c738c77c5acdb47e75ae26f5f01f02f3bf0952d + languageName: node + linkType: hard + +"micromark-extension-gfm-strikethrough@npm:^1.0.0": + version: 1.0.4 + resolution: "micromark-extension-gfm-strikethrough@npm:1.0.4" + dependencies: + micromark-util-chunked: ^1.0.0 + micromark-util-classify-character: ^1.0.0 + micromark-util-resolve-all: ^1.0.0 + micromark-util-symbol: ^1.0.0 + micromark-util-types: ^1.0.0 + uvu: ^0.5.0 + checksum: f43d316b85fe93df1711cdcdc99a5320b941239349234bd262fc708cb67ad47bdfb41d1a7ebe2a5829816b0e9d3107380a5c1e558cb536a75354cbe4857823ba + languageName: node + linkType: hard + +"micromark-extension-gfm-table@npm:^1.0.0": + version: 1.0.5 + resolution: "micromark-extension-gfm-table@npm:1.0.5" + dependencies: + micromark-factory-space: ^1.0.0 + micromark-util-character: ^1.0.0 + micromark-util-symbol: ^1.0.0 + micromark-util-types: ^1.0.0 + uvu: ^0.5.0 + checksum: f0aab3b4333cc24b1534b08dc4cce986dd606df8b7ed913e5a1de9fe2d3ae67b2435663c0bc271b528874af4928e580e1ad540ea9117d7f2d74edb28859c97ef + languageName: node + linkType: hard + +"micromark-extension-gfm-tagfilter@npm:^1.0.0": + version: 1.0.1 + resolution: "micromark-extension-gfm-tagfilter@npm:1.0.1" + dependencies: + micromark-util-types: ^1.0.0 + checksum: 63e8d68f25871722900a67a8001d5da21f19ea707f3566fc7d0b2eb1f6d52476848bb6a41576cf22470565124af9497c5aae842355faa4c14ec19cb1847e71ec + languageName: node + linkType: hard + +"micromark-extension-gfm-task-list-item@npm:^1.0.0": + version: 1.0.3 + resolution: "micromark-extension-gfm-task-list-item@npm:1.0.3" + dependencies: + micromark-factory-space: ^1.0.0 + micromark-util-character: ^1.0.0 + micromark-util-symbol: ^1.0.0 + micromark-util-types: ^1.0.0 + uvu: ^0.5.0 + checksum: d320b0c5301f87e211c06a2330d1ee0fee6da14f0d6d44d5211055b465dadff34390cd6b258a5e0ca376fcda3364fef9a12fe6e26a0c858231fa3b98ddbf7785 + languageName: node + linkType: hard + +"micromark-extension-gfm@npm:^2.0.0": + version: 2.0.1 + resolution: "micromark-extension-gfm@npm:2.0.1" + dependencies: + micromark-extension-gfm-autolink-literal: ^1.0.0 + micromark-extension-gfm-footnote: ^1.0.0 + micromark-extension-gfm-strikethrough: ^1.0.0 + micromark-extension-gfm-table: ^1.0.0 + micromark-extension-gfm-tagfilter: ^1.0.0 + micromark-extension-gfm-task-list-item: ^1.0.0 + micromark-util-combine-extensions: ^1.0.0 + micromark-util-types: ^1.0.0 + checksum: b181479c87be38d5ae8d28e6dc52fab73c894fd2706876746f27a91fb186644ce03532a9c35dca2186327a0e2285cd5242ad0361dc89adedd4a50376ffd94e22 + languageName: node + linkType: hard + +"micromark-extension-math@npm:^2.0.0": + version: 2.0.2 + resolution: "micromark-extension-math@npm:2.0.2" + dependencies: + "@types/katex": ^0.11.0 + katex: ^0.13.0 + micromark-factory-space: ^1.0.0 + micromark-util-character: ^1.0.0 + micromark-util-symbol: ^1.0.0 + micromark-util-types: ^1.0.0 + uvu: ^0.5.0 + checksum: c604d2e75443cd20988c485ecf35ff6799497b038d24e5c680107ebb6756031225df9292e449914178f04b837379b5f95dea0ad3fe4ae77ce60d194f102576a5 + languageName: node + linkType: hard + +"micromark-factory-destination@npm:^1.0.0": + version: 1.0.0 + resolution: "micromark-factory-destination@npm:1.0.0" + dependencies: + micromark-util-character: ^1.0.0 + micromark-util-symbol: ^1.0.0 + micromark-util-types: ^1.0.0 + checksum: 8e733ae9c1c2342f14ff290bf09946e20f6f540117d80342377a765cac48df2ea5e748f33c8b07501ad7a43414b1a6597c8510ede2052b6bf1251fab89748e20 + languageName: node + linkType: hard + +"micromark-factory-label@npm:^1.0.0": + version: 1.0.2 + resolution: "micromark-factory-label@npm:1.0.2" + dependencies: + micromark-util-character: ^1.0.0 + micromark-util-symbol: ^1.0.0 + micromark-util-types: ^1.0.0 + uvu: ^0.5.0 + checksum: 957e9366bdc8dbc1437c0706ff96972fa985ab4b1274abcae12f6094f527cbf5c69e7f2304c23c7f4b96e311ff7911d226563b8b43dcfcd4091e8c985fb97ce6 + languageName: node + linkType: hard + +"micromark-factory-space@npm:^1.0.0": + version: 1.0.0 + resolution: "micromark-factory-space@npm:1.0.0" + dependencies: + micromark-util-character: ^1.0.0 + micromark-util-types: ^1.0.0 + checksum: 70d3aafde4e68ef4e509a3b644e9a29e4aada00801279e346577b008cbca06d78051bcd62aa7ea7425856ed73f09abd2b36607803055f726f52607ee7cb706b0 + languageName: node + linkType: hard + +"micromark-factory-title@npm:^1.0.0": + version: 1.0.2 + resolution: "micromark-factory-title@npm:1.0.2" + dependencies: + micromark-factory-space: ^1.0.0 + micromark-util-character: ^1.0.0 + micromark-util-symbol: ^1.0.0 + micromark-util-types: ^1.0.0 + uvu: ^0.5.0 + checksum: 9a9cf66babde0bad1e25d6c1087082bfde6dfc319a36cab67c89651cc1a53d0e21cdec83262b5a4c33bff49f0e3c8dc2a7bd464e991d40dbea166a8f9b37e5b2 + languageName: node + linkType: hard + +"micromark-factory-whitespace@npm:^1.0.0": + version: 1.0.0 + resolution: "micromark-factory-whitespace@npm:1.0.0" + dependencies: + micromark-factory-space: ^1.0.0 + micromark-util-character: ^1.0.0 + micromark-util-symbol: ^1.0.0 + micromark-util-types: ^1.0.0 + checksum: 0888386e6ea2dd665a5182c570d9b3d0a172d3f11694ca5a2a84e552149c9f1429f5b975ec26e1f0fa4388c55a656c9f359ce5e0603aff6175ba3e255076f20b + languageName: node + linkType: hard + +"micromark-util-character@npm:^1.0.0": + version: 1.1.0 + resolution: "micromark-util-character@npm:1.1.0" + dependencies: + micromark-util-symbol: ^1.0.0 + micromark-util-types: ^1.0.0 + checksum: 504a4e3321f69bddf3fec9f0c1058239fc23336bda5be31d532b150491eda47965a251b37f8a7a9db0c65933b3aaa49cf88044fb1028be3af7c5ee6212bf8d5f + languageName: node + linkType: hard + +"micromark-util-chunked@npm:^1.0.0": + version: 1.0.0 + resolution: "micromark-util-chunked@npm:1.0.0" + dependencies: + micromark-util-symbol: ^1.0.0 + checksum: c1efd56e8c4217bcf1c6f1a9fb9912b4a2a5503b00d031da902be922fb3fee60409ac53f11739991291357b2784fb0647ddfc74c94753a068646c0cb0fd71421 + languageName: node + linkType: hard + +"micromark-util-classify-character@npm:^1.0.0": + version: 1.0.0 + resolution: "micromark-util-classify-character@npm:1.0.0" + dependencies: + micromark-util-character: ^1.0.0 + micromark-util-symbol: ^1.0.0 + micromark-util-types: ^1.0.0 + checksum: 180446e6a1dec653f625ded028f244784e1db8d10ad05c5d70f08af9de393b4a03dc6cf6fa5ed8ccc9c24bbece7837abf3bf66681c0b4adf159364b7d5236dfd + languageName: node + linkType: hard + +"micromark-util-combine-extensions@npm:^1.0.0": + version: 1.0.0 + resolution: "micromark-util-combine-extensions@npm:1.0.0" + dependencies: + micromark-util-chunked: ^1.0.0 + micromark-util-types: ^1.0.0 + checksum: 5304a820ef75340e1be69d6ad167055b6ba9a3bafe8171e5945a935752f462415a9dd61eb3490220c055a8a11167209a45bfa73f278338b7d3d61fa1464d3f35 + languageName: node + linkType: hard + +"micromark-util-decode-numeric-character-reference@npm:^1.0.0": + version: 1.0.0 + resolution: "micromark-util-decode-numeric-character-reference@npm:1.0.0" + dependencies: + micromark-util-symbol: ^1.0.0 + checksum: f3ae2bb582a80f1e9d3face026f585c0c472335c064bd850bde152376f0394cb2831746749b6be6e0160f7d73626f67d10716026c04c87f402c0dd45a1a28633 + languageName: node + linkType: hard + +"micromark-util-decode-string@npm:^1.0.0": + version: 1.0.2 + resolution: "micromark-util-decode-string@npm:1.0.2" + dependencies: + decode-named-character-reference: ^1.0.0 + micromark-util-character: ^1.0.0 + micromark-util-decode-numeric-character-reference: ^1.0.0 + micromark-util-symbol: ^1.0.0 + checksum: 2dbb41c9691cc71505d39706405139fb7d6699429d577a524c7c248ac0cfd09d3dd212ad8e91c143a00b2896f26f81136edc67c5bda32d20446f0834d261b17a + languageName: node + linkType: hard + +"micromark-util-encode@npm:^1.0.0": + version: 1.0.1 + resolution: "micromark-util-encode@npm:1.0.1" + checksum: 9290583abfdc79ea3e7eb92c012c47a0e14327888f8aaa6f57ff79b3058d8e7743716b9d91abca3646f15ab3d78fdad9779fdb4ccf13349cd53309dfc845253a + languageName: node + linkType: hard + +"micromark-util-html-tag-name@npm:^1.0.0": + version: 1.1.0 + resolution: "micromark-util-html-tag-name@npm:1.1.0" + checksum: a9b783cec89ec813648d59799464c1950fe281ae797b2a965f98ad0167d7fa1a247718eff023b4c015f47211a172f9446b8e6b98aad50e3cd44a3337317dad2c + languageName: node + linkType: hard + +"micromark-util-normalize-identifier@npm:^1.0.0": + version: 1.0.0 + resolution: "micromark-util-normalize-identifier@npm:1.0.0" + dependencies: + micromark-util-symbol: ^1.0.0 + checksum: d7c09d5e8318fb72f194af72664bd84a48a2928e3550b2b21c8fbc0ec22524f2a72e0f6663d2b95dc189a6957d3d7759b60716e888909710767cd557be821f8b + languageName: node + linkType: hard + +"micromark-util-resolve-all@npm:^1.0.0": + version: 1.0.0 + resolution: "micromark-util-resolve-all@npm:1.0.0" + dependencies: + micromark-util-types: ^1.0.0 + checksum: 409667f2bd126ef8acce009270d2aecaaa5584c5807672bc657b09e50aa91bd2e552cf41e5be1e6469244a83349cbb71daf6059b746b1c44e3f35446fef63e50 + languageName: node + linkType: hard + +"micromark-util-sanitize-uri@npm:^1.0.0": + version: 1.0.0 + resolution: "micromark-util-sanitize-uri@npm:1.0.0" + dependencies: + micromark-util-character: ^1.0.0 + micromark-util-encode: ^1.0.0 + micromark-util-symbol: ^1.0.0 + checksum: 77448ec3a5d18f0ac975ea47591fbf0d5bd5568f9a0d033d9e318f90656031f037c5ff9137e93faf289480eaea70a5382e2571ebf9edcb1c1cd2a5187b6b3160 + languageName: node + linkType: hard + +"micromark-util-subtokenize@npm:^1.0.0": + version: 1.0.2 + resolution: "micromark-util-subtokenize@npm:1.0.2" + dependencies: + micromark-util-chunked: ^1.0.0 + micromark-util-symbol: ^1.0.0 + micromark-util-types: ^1.0.0 + uvu: ^0.5.0 + checksum: c32ee58a7e1384ab1161a9ee02fbb04ad7b6e96d0b8c93dba9803c329a53d07f22ab394c7a96b2e30d6b8fbe3585b85817dba07277b1317111fc234e166bd2d1 + languageName: node + linkType: hard + +"micromark-util-symbol@npm:^1.0.0": + version: 1.0.1 + resolution: "micromark-util-symbol@npm:1.0.1" + checksum: c6a3023b3a7432c15864b5e33a1bcb5042ac7aa097f2f452e587bef45433d42d39e0a5cce12fbea91e0671098ba0c3f62a2b30ce1cde66ecbb5e8336acf4391d + languageName: node + linkType: hard + +"micromark-util-types@npm:^1.0.0, micromark-util-types@npm:^1.0.1": + version: 1.0.2 + resolution: "micromark-util-types@npm:1.0.2" + checksum: 08dc901b7c06ee3dfeb54befca05cbdab9525c1cf1c1080967c3878c9e72cb9856c7e8ff6112816e18ead36ce6f99d55aaa91560768f2f6417b415dcba1244df + languageName: node + linkType: hard + +"micromark@npm:^3.0.0": + version: 3.0.10 + resolution: "micromark@npm:3.0.10" + dependencies: + "@types/debug": ^4.0.0 + debug: ^4.0.0 + decode-named-character-reference: ^1.0.0 + micromark-core-commonmark: ^1.0.1 + micromark-factory-space: ^1.0.0 + micromark-util-character: ^1.0.0 + micromark-util-chunked: ^1.0.0 + micromark-util-combine-extensions: ^1.0.0 + micromark-util-decode-numeric-character-reference: ^1.0.0 + micromark-util-encode: ^1.0.0 + micromark-util-normalize-identifier: ^1.0.0 + micromark-util-resolve-all: ^1.0.0 + micromark-util-sanitize-uri: ^1.0.0 + micromark-util-subtokenize: ^1.0.0 + micromark-util-symbol: ^1.0.0 + micromark-util-types: ^1.0.1 + uvu: ^0.5.0 + checksum: 04663fe0308cccfbf338111b41d3d82d6445d1d2b834c9fc1880e1ea3874c4a3b81adfafe62b0bc7708ba0a86889885ea31b4dbb39f1f72190c3aab46b743bb1 + languageName: node + linkType: hard + "micromatch@npm:^4.0.4": version: 4.0.4 resolution: "micromatch@npm:4.0.4" @@ -6247,6 +6994,15 @@ __metadata: languageName: node linkType: hard +"nth-check@npm:^2.0.0": + version: 2.1.1 + resolution: "nth-check@npm:2.1.1" + dependencies: + boolbase: ^1.0.0 + checksum: 5afc3dafcd1573b08877ca8e6148c52abd565f1d06b1eb08caf982e3fa289a82f2cae697ffb55b5021e146d60443f1590a5d6b944844e944714a5b549675bcd3 + languageName: node + linkType: hard + "object-assign@npm:^4.1.1": version: 4.1.1 resolution: "object-assign@npm:4.1.1" @@ -6401,6 +7157,13 @@ __metadata: languageName: node linkType: hard +"parse5@npm:^6.0.0": + version: 6.0.1 + resolution: "parse5@npm:6.0.1" + checksum: 7d569a176c5460897f7c8f3377eff640d54132b9be51ae8a8fa4979af940830b2b0c296ce75e5bd8f4041520aadde13170dbdec44889975f906098ea0002f4bd + languageName: node + linkType: hard + "path-exists@npm:^4.0.0": version: 4.0.0 resolution: "path-exists@npm:4.0.0" @@ -6553,7 +7316,7 @@ __metadata: languageName: node linkType: hard -"prismjs@npm:^1.28.0": +"prismjs@npm:^1.24.1, prismjs@npm:^1.28.0": version: 1.28.0 resolution: "prismjs@npm:1.28.0" checksum: bde93fb2beb45b7243219fc53855f59ee54b3fa179f315e8f9d66244d756ef984462e10561bbdc6713d3d7e051852472d7c284f5794a8791eeaefea2fb910b16 @@ -6595,6 +7358,13 @@ __metadata: languageName: node linkType: hard +"property-information@npm:^6.0.0": + version: 6.1.1 + resolution: "property-information@npm:6.1.1" + checksum: 654b1e5c3578e1d522bd22b7cf48881f5054789969ddbefea22e5359805fda5dbf0c5ef76bb26516da26fedac8752587ddc4c8f3b9e16bc0c6e7feb8b6086864 + languageName: node + linkType: hard + "punycode@npm:^2.1.0": version: 2.1.1 resolution: "punycode@npm:2.1.1" @@ -6920,6 +7690,127 @@ __metadata: languageName: node linkType: hard +"rehype-katex@npm:^6.0.2": + version: 6.0.2 + resolution: "rehype-katex@npm:6.0.2" + dependencies: + "@types/hast": ^2.0.0 + "@types/katex": ^0.11.0 + hast-util-to-text: ^3.1.0 + katex: ^0.15.0 + rehype-parse: ^8.0.0 + unified: ^10.0.0 + unist-util-remove-position: ^4.0.0 + unist-util-visit: ^4.0.0 + checksum: ac8b3486441697b8e22cb7ebf6ec58e06d190240f45b128fe60422b9eb887599f38406581e6e3356af967eb1d45d631b0c09387f060190641f402f56c78fa771 + languageName: node + linkType: hard + +"rehype-parse@npm:^7 || ^ 8, rehype-parse@npm:^8.0.0": + version: 8.0.4 + resolution: "rehype-parse@npm:8.0.4" + dependencies: + "@types/hast": ^2.0.0 + hast-util-from-parse5: ^7.0.0 + parse5: ^6.0.0 + unified: ^10.0.0 + checksum: e678a5f9fa7cb91d5957f5f38bc37bc9fb90b8011a1ed6a90541ba6fff9f243c752c88b7f422cba8f5ba83ccb22942b1825654e8c3040970c703b85a6037efdf + languageName: node + linkType: hard + +"rehype-prism@npm:^2.1.3": + version: 2.1.3 + resolution: "rehype-prism@npm:2.1.3" + dependencies: + "@types/hast": "*" + "@types/mdast": "*" + "@types/node": ">12" + "@types/prismjs": ^1.16.6 + "@types/unist": "*" + prismjs: ^1.24.1 + rehype-parse: ^7 || ^ 8 + unist-util-is: ^4 || ^5 + unist-util-select: ^4 + unist-util-visit: ^3 || ^4 + peerDependencies: + unified: ^10 + checksum: d75864bd5b0e4b43403453fa77302956ad4b9841dd0bf201fa2258f2236c343a07306d28d465c3063e2e6eab5067467df3d24f49240acaf0c618637a33a525ab + languageName: node + linkType: hard + +"rehype-react@npm:^7.1.1": + version: 7.1.1 + resolution: "rehype-react@npm:7.1.1" + dependencies: + "@mapbox/hast-util-table-cell-style": ^0.2.0 + "@types/hast": ^2.0.0 + hast-to-hyperscript: ^10.0.0 + hast-util-whitespace: ^2.0.0 + unified: ^10.0.0 + peerDependencies: + "@types/react": ">=17" + checksum: 218b5e13776c6c0f5b430fe4c57de391f8417789b570c751f00b7c88da94ef08f7f869695189646ebc683155b02e55ac20c8e4148d4f2cbbcc39d0b05a66dca6 + languageName: node + linkType: hard + +"remark-breaks@npm:^3.0.2": + version: 3.0.2 + resolution: "remark-breaks@npm:3.0.2" + dependencies: + "@types/mdast": ^3.0.0 + unified: ^10.0.0 + unist-util-visit: ^4.0.0 + checksum: 5f46b18818f8a77e4fbc607c99736eedbe1f8cbad3d7390ce8359f08e3b749de8778ec8812287a41f51ffef2524b0be0dd623fcdbcda5de7f13f9902851f80b3 + languageName: node + linkType: hard + +"remark-gfm@npm:^3.0.1": + version: 3.0.1 + resolution: "remark-gfm@npm:3.0.1" + dependencies: + "@types/mdast": ^3.0.0 + mdast-util-gfm: ^2.0.0 + micromark-extension-gfm: ^2.0.0 + unified: ^10.0.0 + checksum: 02254f74d67b3419c2c9cf62d799ec35f6c6cd74db25c001361751991552a7ce86049a972107bff8122d85d15ae4a8d1a0618f3bc01a7df837af021ae9b2a04e + languageName: node + linkType: hard + +"remark-math@npm:^5.1.1": + version: 5.1.1 + resolution: "remark-math@npm:5.1.1" + dependencies: + "@types/mdast": ^3.0.0 + mdast-util-math: ^2.0.0 + micromark-extension-math: ^2.0.0 + unified: ^10.0.0 + checksum: 1baec5862e36bbb8645144a73740e63a3aed2d547a64b731bb1a0162658319679378fd70f3d3d534655c2a0fcc3f941adba31cca33808e134fa22202a5d314f9 + languageName: node + linkType: hard + +"remark-parse@npm:^10.0.1": + version: 10.0.1 + resolution: "remark-parse@npm:10.0.1" + dependencies: + "@types/mdast": ^3.0.0 + mdast-util-from-markdown: ^1.0.0 + unified: ^10.0.0 + checksum: 505088e564ab53ff054433368adbb7b551f69240c7d9768975529837a86f1d0f085e72d6211929c5c42db315273df4afc94f3d3a8662ffdb69468534c6643d29 + languageName: node + linkType: hard + +"remark-rehype@npm:^10.1.0": + version: 10.1.0 + resolution: "remark-rehype@npm:10.1.0" + dependencies: + "@types/hast": ^2.0.0 + "@types/mdast": ^3.0.0 + mdast-util-to-hast: ^12.1.0 + unified: ^10.0.0 + checksum: b9ac8acff3383b204dfdc2599d0bdf86e6ca7e837033209584af2e6aaa6a9013e519a379afa3201299798cab7298c8f4b388de118c312c67234c133318aec084 + languageName: node + linkType: hard + "require-from-string@npm:^2.0.2": version: 2.0.2 resolution: "require-from-string@npm:2.0.2" @@ -6995,20 +7886,20 @@ __metadata: languageName: node linkType: hard -"revolt-api@npm:0.5.3-7": - version: 0.5.3-7 - resolution: "revolt-api@npm:0.5.3-7" +"revolt-api@npm:0.5.4": + version: 0.5.4 + resolution: "revolt-api@npm:0.5.4" dependencies: "@insertish/oapi": 0.1.16 axios: ^0.26.1 lodash.defaultsdeep: ^4.6.1 - checksum: acc2f412d1be90f0cfa8a24ba715d8257813762c56ba0e48c649efff349674517036a8fa5939eda61ce31fd64a37f0c54af3d8fae56edf4596f05b10304e090e + checksum: bd40acabac1b6c5848b1d6e555297de5aa3e0950a4de67523c4cf986a8037380e3addc5e16babebc8dfa6570cd1d1957efe9a3aaa6a206b9286e5b7f5941d699 languageName: node linkType: hard -"revolt.js@npm:6.0.3": - version: 6.0.3 - resolution: "revolt.js@npm:6.0.3" +"revolt.js@npm:6.0.5": + version: 6.0.5 + resolution: "revolt.js@npm:6.0.5" dependencies: "@insertish/exponential-backoff": 3.1.0-patch.2 "@insertish/isomorphic-ws": ^4.0.1 @@ -7019,10 +7910,10 @@ __metadata: lodash.isequal: ^4.5.0 long: ^5.2.0 mobx: ^6.3.2 - revolt-api: 0.5.3-7 + revolt-api: 0.5.4 ulid: ^2.3.0 ws: ^8.2.2 - checksum: cfecbde7a9b795da75bfac3cec05f2aed5fd02cd14fcac1b2fad26285de1b887cf7ccb339e8a63a019f1e83058b041305927e029d7b5eedcc00823582d9a842a + checksum: 54d7f3a9eeafc79ae5443e22cacd85acb3359aaacccfbb11e41c8cb4a888188b200224dd3ce78847682aabbd3737d99848f6f1fbf144973cd2928d2316cffd2d languageName: node linkType: hard @@ -7111,6 +8002,15 @@ __metadata: languageName: node linkType: hard +"sade@npm:^1.7.3": + version: 1.8.1 + resolution: "sade@npm:1.8.1" + dependencies: + mri: ^1.1.0 + checksum: 0756e5b04c51ccdc8221ebffd1548d0ce5a783a44a0fa9017a026659b97d632913e78f7dca59f2496aa996a0be0b0c322afd87ca72ccd909406f49dbffa0f45d + languageName: node + linkType: hard + "safe-buffer@npm:^5.1.0, safe-buffer@npm:~5.2.0": version: 5.2.1 resolution: "safe-buffer@npm:5.2.1" @@ -7457,6 +8357,13 @@ __metadata: languageName: node linkType: hard +"space-separated-tokens@npm:^2.0.0": + version: 2.0.1 + resolution: "space-separated-tokens@npm:2.0.1" + checksum: 66e30a6382d6e3ab0a6573d510235a198202071d4ebfef8c198f10433166f0cdced4dbf0946cad3c4b2ecc336896a11f98b2ec93047e140fe7aef6fd3a21365b + languageName: node + linkType: hard + "spdx-exceptions@npm:^2.1.0": version: 2.3.0 resolution: "spdx-exceptions@npm:2.3.0" @@ -7660,6 +8567,15 @@ __metadata: languageName: node linkType: hard +"style-to-object@npm:^0.3.0": + version: 0.3.0 + resolution: "style-to-object@npm:0.3.0" + dependencies: + inline-style-parser: 0.1.1 + checksum: 4d7084015207f2a606dfc10c29cb5ba569f2fe8005551df7396110dd694d6ff650f2debafa95bd5d147dfb4ca50f57868e2a7f91bf5d11ef734fe7ccbd7abf59 + languageName: node + linkType: hard + "styled-components@npm:^5.3.0": version: 5.3.1 resolution: "styled-components@npm:5.3.1" @@ -7869,6 +8785,20 @@ __metadata: languageName: node linkType: hard +"trim-lines@npm:^3.0.0": + version: 3.0.1 + resolution: "trim-lines@npm:3.0.1" + checksum: e241da104682a0e0d807222cc1496b92e716af4db7a002f4aeff33ae6a0024fef93165d49eab11aa07c71e1347c42d46563f91dfaa4d3fb945aa535cdead53ed + languageName: node + linkType: hard + +"trough@npm:^2.0.0": + version: 2.1.0 + resolution: "trough@npm:2.1.0" + checksum: a577bb561c2b401cc0e1d9e188fcfcdf63b09b151ff56a668da12197fe97cac15e3d77d5b51f426ccfd94255744a9118e9e9935afe81a3644fa1be9783c82886 + languageName: node + linkType: hard + "tslib@npm:^1.8.1": version: 1.14.1 resolution: "tslib@npm:1.14.1" @@ -7964,13 +8894,6 @@ __metadata: languageName: node linkType: hard -"uc.micro@npm:^1.0.1, uc.micro@npm:^1.0.5": - version: 1.0.6 - resolution: "uc.micro@npm:1.0.6" - checksum: 6898bb556319a38e9cf175e3628689347bd26fec15fc6b29fa38e0045af63075ff3fea4cf1fdba9db46c9f0cbf07f2348cd8844889dd31ebd288c29fe0d27e7a - languageName: node - linkType: hard - "ulid@npm:^2.3.0": version: 2.3.0 resolution: "ulid@npm:2.3.0" @@ -8030,6 +8953,21 @@ __metadata: languageName: node linkType: hard +"unified@npm:^10.0.0, unified@npm:^10.1.2": + version: 10.1.2 + resolution: "unified@npm:10.1.2" + dependencies: + "@types/unist": ^2.0.0 + bail: ^2.0.0 + extend: ^3.0.0 + is-buffer: ^2.0.0 + is-plain-obj: ^4.0.0 + trough: ^2.0.0 + vfile: ^5.0.0 + checksum: 053e7c65ede644607f87bd625a299e4b709869d2f76ec8138569e6e886903b6988b21cd9699e471eda42bee189527be0a9dac05936f1d069a5e65d0125d5d756 + languageName: node + linkType: hard + "unique-filename@npm:^1.1.1": version: 1.1.1 resolution: "unique-filename@npm:1.1.1" @@ -8057,6 +8995,126 @@ __metadata: languageName: node linkType: hard +"unist-builder@npm:^3.0.0": + version: 3.0.0 + resolution: "unist-builder@npm:3.0.0" + dependencies: + "@types/unist": ^2.0.0 + checksum: 80459ee3c2ece90bbc4f4b4faeed524d144c1a09ee07ff3e9004648d9b71a652e80a3b3ef60311a1e92f6ab915caf27c6f08062b5f8c84fa725bc0d7c5759e84 + languageName: node + linkType: hard + +"unist-util-find-after@npm:^4.0.0": + version: 4.0.0 + resolution: "unist-util-find-after@npm:4.0.0" + dependencies: + "@types/unist": ^2.0.0 + unist-util-is: ^5.0.0 + checksum: 8381ef0bad18a0b1fa1c7ee47f94a2578ab6bf572eb126a1f179526b9dca47584fc070976f2d83bbe381161fa33b9164a894d0279a30ec83e65433356d43df57 + languageName: node + linkType: hard + +"unist-util-generated@npm:^2.0.0": + version: 2.0.0 + resolution: "unist-util-generated@npm:2.0.0" + checksum: 3a806793fa24a75190c217740ce706340d6cb0d51eff677134253d628f8e4355ebd8a243fe8045c583463f6bebfd50f902d653161da87c1359fcd1a14b99c8e0 + languageName: node + linkType: hard + +"unist-util-is@npm:^3.0.0": + version: 3.0.0 + resolution: "unist-util-is@npm:3.0.0" + checksum: d24a5dd80c670f763b2ae608651cf062317456aa81be51f66f45cbd7d440a2ab18356e4f48aeac6b5e3d391c69d3c3452ade5fe5aa9574bec4a2de0b10122ed5 + languageName: node + linkType: hard + +"unist-util-is@npm:^4 || ^5, unist-util-is@npm:^5.0.0": + version: 5.1.1 + resolution: "unist-util-is@npm:5.1.1" + checksum: e8743a19a304d8a8f5684f3e5ddb5546f2655847b42123687277d76566a2aba89beb7b4a8a9e9ebc4d904cd1cecc285356d7923d973a43cfc19a1e10ff6bdee4 + languageName: node + linkType: hard + +"unist-util-position@npm:^4.0.0": + version: 4.0.3 + resolution: "unist-util-position@npm:4.0.3" + dependencies: + "@types/unist": ^2.0.0 + checksum: 0d89973628d40f19345cbcc50008f7f56d411afa54434bbe6c224b22d26aaf9d4500da2de363f1f01945acab1f1c31920c514253149eb546ff9b8bbc1ea94209 + languageName: node + linkType: hard + +"unist-util-remove-position@npm:^4.0.0": + version: 4.0.1 + resolution: "unist-util-remove-position@npm:4.0.1" + dependencies: + "@types/unist": ^2.0.0 + unist-util-visit: ^4.0.0 + checksum: 7d2808662ac65f2b2f615822b78060419f738fb3b074b10cec77c596ea966b8f5c47553d2d322822a5975c49d2b21cdd64c198ae9fb02a9d54d1afa6342cdd6a + languageName: node + linkType: hard + +"unist-util-select@npm:^4": + version: 4.0.1 + resolution: "unist-util-select@npm:4.0.1" + dependencies: + "@types/unist": ^2.0.0 + css-selector-parser: ^1.0.0 + nth-check: ^2.0.0 + unist-util-is: ^5.0.0 + zwitch: ^2.0.0 + checksum: da9a69edf03d0c1a633945aae2ddf7153b4d75ff9d51085c55bf742ac0a13df327e6f2925abe863dfb6c451a8865838f28dd029cfb841356b18df56cf8872877 + languageName: node + linkType: hard + +"unist-util-stringify-position@npm:^3.0.0": + version: 3.0.2 + resolution: "unist-util-stringify-position@npm:3.0.2" + dependencies: + "@types/unist": ^2.0.0 + checksum: 2dfd7a0fb2a55e99cc319c3bf7f9f1f73ed652978fa70d19117faa7245d20f21738ec926ecc47f341705ca1bb157e87ced0b6bb5ecaa666bd2ae6b2510d6a671 + languageName: node + linkType: hard + +"unist-util-visit-parents@npm:^2.0.0": + version: 2.1.2 + resolution: "unist-util-visit-parents@npm:2.1.2" + dependencies: + unist-util-is: ^3.0.0 + checksum: 048edbb590a8c4bc0043eec9f50d3fe76faa58f1ac663a7e6dee5e895ddd0ce8bc52f2cfe2e633849fa93671e8de021070667acb1518e3d40220768c7f70a3d3 + languageName: node + linkType: hard + +"unist-util-visit-parents@npm:^5.0.0": + version: 5.1.0 + resolution: "unist-util-visit-parents@npm:5.1.0" + dependencies: + "@types/unist": ^2.0.0 + unist-util-is: ^5.0.0 + checksum: 7c413dbb3dfcb679109fa8f0965d9abf117c3c53fa7b8823f68cac0ea53adbe98c1ce954d36c034e086c966b48b1d44d42c85f7bf6b42a032f728ac338929513 + languageName: node + linkType: hard + +"unist-util-visit@npm:^1.4.1": + version: 1.4.1 + resolution: "unist-util-visit@npm:1.4.1" + dependencies: + unist-util-visit-parents: ^2.0.0 + checksum: e9395205b6908c8d0fe71bc44e65d89d4781d1bb2d453a33cb67ed4124bad0b89d6b1d526ebaecb82a7c48e211bdf6f24351449b8cc115327b345f4617c18728 + languageName: node + linkType: hard + +"unist-util-visit@npm:^3 || ^4, unist-util-visit@npm:^4.0.0, unist-util-visit@npm:^4.1.0": + version: 4.1.0 + resolution: "unist-util-visit@npm:4.1.0" + dependencies: + "@types/unist": ^2.0.0 + unist-util-is: ^5.0.0 + unist-util-visit-parents: ^5.0.0 + checksum: 3521abee2ed4535092aac073d05f46255475c89781b8e9d8c951a473d91b5d6e4d5912ae4a68a4c1cf17a42ed0108cb93103c7f5c736977529969997451363fb + languageName: node + linkType: hard + "universalify@npm:^2.0.0": version: 2.0.0 resolution: "universalify@npm:2.0.0" @@ -8117,6 +9175,20 @@ __metadata: languageName: node linkType: hard +"uvu@npm:^0.5.0": + version: 0.5.6 + resolution: "uvu@npm:0.5.6" + dependencies: + dequal: ^2.0.0 + diff: ^5.0.0 + kleur: ^4.0.3 + sade: ^1.7.3 + bin: + uvu: bin.js + checksum: 09460a37975627de9fcad396e5078fb844d01aaf64a6399ebfcfd9e55f1c2037539b47611e8631f89be07656962af0cf48c334993db82b9ae9c3d25ce3862168 + languageName: node + linkType: hard + "v8-compile-cache@npm:^2.0.3": version: 2.3.0 resolution: "v8-compile-cache@npm:2.3.0" @@ -8131,6 +9203,38 @@ __metadata: languageName: node linkType: hard +"vfile-location@npm:^4.0.0": + version: 4.0.1 + resolution: "vfile-location@npm:4.0.1" + dependencies: + "@types/unist": ^2.0.0 + vfile: ^5.0.0 + checksum: cc0df62075c741beee699e651374aeb56c4c1f4333398c0ba924281c2b51d4b7669c69c5b837ea395775626ad030d6f1bd27fd0a7eaf3f9f1bbd55393948ad6c + languageName: node + linkType: hard + +"vfile-message@npm:^3.0.0": + version: 3.1.2 + resolution: "vfile-message@npm:3.1.2" + dependencies: + "@types/unist": ^2.0.0 + unist-util-stringify-position: ^3.0.0 + checksum: 96fbd9e9b5e0babb5ee61e3a716dc7a6a8c28f2c8c711837d95c88b782161b31549ad16059a78990d7b836d0f4d3b4d8c9ffde44370d48d9cac991fc1e3e17c5 + languageName: node + linkType: hard + +"vfile@npm:^5.0.0": + version: 5.3.4 + resolution: "vfile@npm:5.3.4" + dependencies: + "@types/unist": ^2.0.0 + is-buffer: ^2.0.0 + unist-util-stringify-position: ^3.0.0 + vfile-message: ^3.0.0 + checksum: 2382edc7c6e3502bca72bc95bc1ff0fe1852482e8a0ac257615f9ab12f32564d6f6a55da8756b74a900d26a247da5ca23a92ca7c9a18dbda2b0f87504ef0611f + languageName: node + linkType: hard + "vite-plugin-pwa@npm:^0.11.13": version: 0.11.13 resolution: "vite-plugin-pwa@npm:0.11.13" @@ -8178,6 +9282,13 @@ __metadata: languageName: node linkType: hard +"web-namespaces@npm:^2.0.0": + version: 2.0.1 + resolution: "web-namespaces@npm:2.0.1" + checksum: b6d9f02f1a43d0ef0848a812d89c83801d5bbad57d8bb61f02eb6d7eb794c3736f6cc2e1191664bb26136594c8218ac609f4069722c6f56d9fc2d808fa9271c6 + languageName: node + linkType: hard + "webidl-conversions@npm:^4.0.2": version: 4.0.2 resolution: "webidl-conversions@npm:4.0.2" @@ -8512,3 +9623,10 @@ __metadata: checksum: f77b3d8d00310def622123df93d4ee654fc6a0096182af8bd60679ddcdfb3474c56c6c7190817c84a2785648cdee9d721c0154eb45698c62176c322fb46fc700 languageName: node linkType: hard + +"zwitch@npm:^2.0.0": + version: 2.0.2 + resolution: "zwitch@npm:2.0.2" + checksum: 8edd7af8375f12f64d8dbef815af32cd77bd9237d0b013210ba4e3aef25fdc460fe264cd0a19deabe9f86ef0c607240ebac1a336bf4a70bf06ef53e0652de116 + languageName: node + linkType: hard From 4a85dd69cf66dcbf654cd67ba11db80c033ea90e Mon Sep 17 00:00:00 2001 From: Paul Makles Date: Fri, 8 Jul 2022 15:19:16 +0100 Subject: [PATCH 134/151] fix: limit maximum quote depth to 3 --- src/components/markdown/Markdown.tsx | 4 ++- src/components/markdown/RemarkRenderer.tsx | 32 ++++++++++++++++------ src/components/markdown/hast.ts | 7 +++++ 3 files changed, 34 insertions(+), 9 deletions(-) create mode 100644 src/components/markdown/hast.ts diff --git a/src/components/markdown/Markdown.tsx b/src/components/markdown/Markdown.tsx index 9ff974fb..77c61dc9 100644 --- a/src/components/markdown/Markdown.tsx +++ b/src/components/markdown/Markdown.tsx @@ -3,11 +3,13 @@ import { Suspense, lazy } from "preact/compat"; const Renderer = lazy(() => import("./RemarkRenderer")); export interface MarkdownProps { - content?: string | null; + content: string; disallowBigEmoji?: boolean; } export default function Markdown(props: MarkdownProps) { + if (!props.content) return null; + return ( // @ts-expect-error Typings mis-match. diff --git a/src/components/markdown/RemarkRenderer.tsx b/src/components/markdown/RemarkRenderer.tsx index 06d1c3f1..9501a66f 100644 --- a/src/components/markdown/RemarkRenderer.tsx +++ b/src/components/markdown/RemarkRenderer.tsx @@ -15,14 +15,14 @@ import { memo } from "preact/compat"; import { useEffect, useMemo, useState } from "preact/hooks"; import { MarkdownProps } from "./Markdown"; +import { handlers } from "./hast"; import { RenderCodeblock } from "./plugins/Codeblock"; import { RenderAnchor } from "./plugins/anchors"; import { remarkChannels, RenderChannel } from "./plugins/channels"; import { isOnlyEmoji, remarkEmoji, RenderEmoji } from "./plugins/emoji"; import { remarkMention, RenderMention } from "./plugins/mentions"; -import { passThroughComponents } from "./plugins/remarkRegexComponent"; import { remarkSpoiler, RenderSpoiler } from "./plugins/spoiler"; -import { remarkTimestamps, timestampHandler } from "./plugins/timestamps"; +import { remarkTimestamps } from "./plugins/timestamps"; import "./prism"; /** @@ -139,10 +139,7 @@ const render = unified() .use(remarkEmoji) .use(remarkMention) .use(remarkRehype, { - handlers: { - ...passThroughComponents("emoji", "spoiler", "mention", "channel"), - timestamp: timestampHandler, - }, + handlers, }) .use(rehypeKatex, { maxSize: 10, @@ -173,15 +170,34 @@ const Container = styled.div<{ largeEmoji: boolean }>` --emoji-size: ${(props) => (props.largeEmoji ? "3em" : "1.25em")}; `; +/** + * Regex for matching execessive blockquotes + */ +const RE_QUOTE = /(^[>\s][>\s])[>\s]+([^]+)$/gm; + +/** + * Sanitise Markdown input before rendering + * @param content Input string + * @returns Sanitised string + */ +function sanitise(content: string) { + // Strip excessive blockquote indentation + return content.replace(RE_QUOTE, (_, m0, m1) => m0 + m1); +} + /** * Remark renderer component */ export default memo(({ content, disallowBigEmoji }: MarkdownProps) => { + const sanitisedContent = useMemo(() => sanitise(content), [content]); + const [Content, setContent] = useState(null!); useEffect(() => { - render.process(content!).then((file) => setContent(file.result)); - }, [content]); + render + .process(sanitisedContent) + .then((file) => setContent(file.result)); + }, [sanitisedContent]); const largeEmoji = useMemo( () => !disallowBigEmoji && isOnlyEmoji(content!), diff --git a/src/components/markdown/hast.ts b/src/components/markdown/hast.ts new file mode 100644 index 00000000..d2054aff --- /dev/null +++ b/src/components/markdown/hast.ts @@ -0,0 +1,7 @@ +import { passThroughComponents } from "./plugins/remarkRegexComponent"; +import { timestampHandler } from "./plugins/timestamps"; + +export const handlers = { + ...passThroughComponents("emoji", "spoiler", "mention", "channel"), + timestamp: timestampHandler, +}; From 262b931810e874623977c2c8d0c6a9464fd1991d Mon Sep 17 00:00:00 2001 From: Paul Makles Date: Fri, 8 Jul 2022 15:36:18 +0100 Subject: [PATCH 135/151] fix: actually render HTML out instead of obliterating it --- src/components/markdown/RemarkRenderer.tsx | 18 +++++++++++++++--- src/components/markdown/plugins/mentions.tsx | 2 ++ 2 files changed, 17 insertions(+), 3 deletions(-) diff --git a/src/components/markdown/RemarkRenderer.tsx b/src/components/markdown/RemarkRenderer.tsx index 9501a66f..05025ff1 100644 --- a/src/components/markdown/RemarkRenderer.tsx +++ b/src/components/markdown/RemarkRenderer.tsx @@ -173,7 +173,12 @@ const Container = styled.div<{ largeEmoji: boolean }>` /** * Regex for matching execessive blockquotes */ -const RE_QUOTE = /(^[>\s][>\s])[>\s]+([^]+)$/gm; +const RE_QUOTE = /(^[>\s][>\s])[>\s]+([^]+$)/gm; + +/** + * Regex for matching open angled bracket + */ +const RE_OPEN_BRACKET = /\s][>\s])[>\s]+([^]+)$/gm; * @returns Sanitised string */ function sanitise(content: string) { - // Strip excessive blockquote indentation - return content.replace(RE_QUOTE, (_, m0, m1) => m0 + m1); + return ( + content + // Strip excessive blockquote indentation + .replace(RE_QUOTE, (_, m0, m1) => m0 + m1) + // Map < to HTML entity LT + // (otherwise all HTML is just obliterated, + // not even displayed as plain text) + .replace(RE_OPEN_BRACKET, "<") + ); } /** diff --git a/src/components/markdown/plugins/mentions.tsx b/src/components/markdown/plugins/mentions.tsx index 1411dcf5..917d8cda 100644 --- a/src/components/markdown/plugins/mentions.tsx +++ b/src/components/markdown/plugins/mentions.tsx @@ -12,6 +12,8 @@ const Mention = styled.a` align-items: center; display: inline-flex; + cursor: pointer; + font-weight: 600; background: var(--secondary-background); border-radius: calc(var(--border-radius) * 2); From 7e20d5029e76d769a7503633014ac39dd1cd63fb Mon Sep 17 00:00:00 2001 From: Paul Makles Date: Fri, 8 Jul 2022 15:45:16 +0100 Subject: [PATCH 136/151] fix: underline anchor; prevent jitter on render --- src/components/markdown/RemarkRenderer.tsx | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/src/components/markdown/RemarkRenderer.tsx b/src/components/markdown/RemarkRenderer.tsx index 05025ff1..26f03b37 100644 --- a/src/components/markdown/RemarkRenderer.tsx +++ b/src/components/markdown/RemarkRenderer.tsx @@ -12,7 +12,7 @@ import { unified } from "unified"; import { createElement } from "preact"; import { memo } from "preact/compat"; -import { useEffect, useMemo, useState } from "preact/hooks"; +import { useLayoutEffect, useMemo, useState } from "preact/hooks"; import { MarkdownProps } from "./Markdown"; import { handlers } from "./hast"; @@ -163,11 +163,18 @@ const render = unified() * Markdown parent container */ const Container = styled.div<{ largeEmoji: boolean }>` + // Allow scrolling block math .math-display { overflow-x: auto; } + // Set emoji size --emoji-size: ${(props) => (props.largeEmoji ? "3em" : "1.25em")}; + + // Underline link hover + a:hover { + text-decoration: underline; + } `; /** @@ -205,7 +212,7 @@ export default memo(({ content, disallowBigEmoji }: MarkdownProps) => { const [Content, setContent] = useState(null!); - useEffect(() => { + useLayoutEffect(() => { render .process(sanitisedContent) .then((file) => setContent(file.result)); From c12d40d0da0212d26039eaefd3c804e9511108f3 Mon Sep 17 00:00:00 2001 From: Paul Makles Date: Fri, 8 Jul 2022 16:58:21 +0100 Subject: [PATCH 137/151] fix: correct mention styling --- src/components/markdown/plugins/mentions.tsx | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/src/components/markdown/plugins/mentions.tsx b/src/components/markdown/plugins/mentions.tsx index 917d8cda..eae6938b 100644 --- a/src/components/markdown/plugins/mentions.tsx +++ b/src/components/markdown/plugins/mentions.tsx @@ -7,19 +7,28 @@ import { createComponent, CustomComponentProps } from "./remarkRegexComponent"; const Mention = styled.a` gap: 4px; - padding: 0 6px; flex-shrink: 0; + padding-left: 2px; + padding-right: 6px; align-items: center; display: inline-flex; + vertical-align: middle; cursor: pointer; font-weight: 600; + text-decoration: none !important; background: var(--secondary-background); border-radius: calc(var(--border-radius) * 2); + transition: 0.1s ease filter; + &:hover { - text-decoration: none; + filter: brightness(0.75); + } + + &:active { + filter: brightness(0.65); } svg { From ec96dde694f9de44b4039670ce33313807009d7d Mon Sep 17 00:00:00 2001 From: Paul Makles Date: Fri, 8 Jul 2022 17:14:15 +0100 Subject: [PATCH 138/151] feat: render custom emoji --- src/components/markdown/plugins/emoji.tsx | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/src/components/markdown/plugins/emoji.tsx b/src/components/markdown/plugins/emoji.tsx index f97e85bc..033af2de 100644 --- a/src/components/markdown/plugins/emoji.tsx +++ b/src/components/markdown/plugins/emoji.tsx @@ -15,15 +15,19 @@ const Emoji = styled.img` `; export function RenderEmoji({ match }: CustomComponentProps) { + const url = + match in emojiDictionary + ? parseEmoji(emojiDictionary[match as keyof typeof emojiDictionary]) + : clientController.getAvailableClient().emojis!.get(match)! + .imageURL; + return ( ); } From 354c22108eac4f4f198a582933b4f087767ae8c8 Mon Sep 17 00:00:00 2001 From: Paul Makles Date: Fri, 8 Jul 2022 18:38:39 +0100 Subject: [PATCH 139/151] feat: it's morbing time --- src/components/common/messaging/MessageBox.tsx | 13 +------------ src/components/common/user/UserBadges.tsx | 8 ++++++++ 2 files changed, 9 insertions(+), 12 deletions(-) diff --git a/src/components/common/messaging/MessageBox.tsx b/src/components/common/messaging/MessageBox.tsx index cdae5341..02fb1560 100644 --- a/src/components/common/messaging/MessageBox.tsx +++ b/src/components/common/messaging/MessageBox.tsx @@ -7,13 +7,7 @@ import { ulid } from "ulid"; import { Text } from "preact-i18n"; import { memo } from "preact/compat"; -import { - useCallback, - useContext, - useEffect, - useMemo, - useState, -} from "preact/hooks"; +import { useCallback, useEffect, useMemo, useState } from "preact/hooks"; import { IconButton, Picker } from "@revoltchat/ui"; @@ -659,11 +653,6 @@ export default observer(({ channel }: Props) => { onFocus={onFocus} onBlur={onBlur} /> - {/* - - - - */} {state.experiments.isEnabled("picker") && ( setPicker(!picker)}> diff --git a/src/components/common/user/UserBadges.tsx b/src/components/common/user/UserBadges.tsx index b8c36e44..b14d8c6a 100644 --- a/src/components/common/user/UserBadges.tsx +++ b/src/components/common/user/UserBadges.tsx @@ -16,6 +16,7 @@ enum Badges { Paw = 128, EarlyAdopter = 256, ReservedRelevantJokeBadge1 = 512, + ReservedRelevantJokeBadge2 = 1024, } const BadgesBase = styled.div` @@ -135,6 +136,13 @@ export default function UserBadges({ badges, uid }: Props) { ) : ( <> )} + {badges & Badges.ReservedRelevantJokeBadge2 ? ( + + + + ) : ( + <> + )} {badges & Badges.Paw ? ( From cb6d5a3828e4ee17652a898bcf469db180e7b7fd Mon Sep 17 00:00:00 2001 From: Paul Makles Date: Sat, 9 Jul 2022 17:53:40 +0100 Subject: [PATCH 140/151] feat: update emoji picker; move settings bhnd expr --- external/lang | 2 +- package.json | 2 +- .../common/messaging/MessageBox.tsx | 80 +++++++++++++------ src/mobx/stores/Experiments.ts | 5 +- src/pages/settings/ServerSettings.tsx | 3 + src/pages/settings/server/Emojis.tsx | 13 ++- yarn.lock | 10 +-- 7 files changed, 78 insertions(+), 37 deletions(-) diff --git a/external/lang b/external/lang index d4bc47b7..58408da6 160000 --- a/external/lang +++ b/external/lang @@ -1 +1 @@ -Subproject commit d4bc47b729c7e69ce97216469692b39f4cd1640e +Subproject commit 58408da6c4090dd3a7808a663eaa95b8b1da7603 diff --git a/package.json b/package.json index 2c8aa384..7e89c29a 100644 --- a/package.json +++ b/package.json @@ -73,7 +73,7 @@ "@hcaptcha/react-hcaptcha": "^0.3.6", "@insertish/vite-plugin-babel-macros": "^1.0.5", "@preact/preset-vite": "^2.0.0", - "@revoltchat/ui": "1.0.70", + "@revoltchat/ui": "1.0.72", "@rollup/plugin-replace": "^2.4.2", "@styled-icons/boxicons-logos": "^10.38.0", "@styled-icons/boxicons-regular": "^10.38.0", diff --git a/src/components/common/messaging/MessageBox.tsx b/src/components/common/messaging/MessageBox.tsx index 02fb1560..7b76aef9 100644 --- a/src/components/common/messaging/MessageBox.tsx +++ b/src/components/common/messaging/MessageBox.tsx @@ -22,7 +22,7 @@ import { SMOOTH_SCROLL_ON_RECEIVE, } from "../../../lib/renderer/Singleton"; -import { useApplicationState } from "../../../mobx/State"; +import { state, useApplicationState } from "../../../mobx/State"; import { Reply } from "../../../mobx/stores/MessageQueue"; import { emojiDictionary } from "../../../assets/emojis"; @@ -34,8 +34,8 @@ import { uploadFile, } from "../../../controllers/client/jsx/legacy/FileUploads"; import { modalController } from "../../../controllers/modals/ModalController"; +import { RenderEmoji } from "../../markdown/plugins/emoji"; import AutoComplete, { useAutoComplete } from "../AutoComplete"; -import Emoji from "../Emoji"; import { PermissionTooltip } from "../Tooltip"; import FilePreview from "./bars/FilePreview"; import ReplyBar from "./bars/ReplyBar"; @@ -143,6 +143,56 @@ const RE_SED = new RegExp("^s/([^])*/([^])*$"); // Tests for code block delimiters (``` at start of line) const RE_CODE_DELIMITER = new RegExp("^```", "gm"); +const HackAlertThisFileWillBeReplaced = observer(({ channel }: Props) => { + const renderEmoji = useMemo( + () => + memo(({ emoji }: { emoji: string }) => ( + { + const v = state.draft.get(channel._id); + state.draft.set( + channel._id, + `${v ? `${v} ` : ""}:${emoji}:`, + ); + }}> + + + )), + [], + ); + + const emojis: Record = { + default: Object.keys(emojiDictionary), + }; + + // ! FIXME: also expose typing from component + const categories: any[] = []; + + for (const server of state.ordering.orderedServers) { + // ! FIXME: add a separate map on each server for emoji + const list = [...channel.client.emojis.values()] + .filter((emoji) => emoji.parent.id === server._id) + .map((x) => x._id); + + if (list.length > 0) { + emojis[server._id] = list; + categories.push({ + id: server._id, + name: server.name, + iconURL: server.generateIconURL({ max_side: 256 }), + }); + } + } + + return ( + + ); +}); + // ! FIXME: add to app config and load from app config export const CAN_UPLOAD_AT_ONCE = 5; @@ -306,6 +356,7 @@ export default observer(({ channel }: Props) => { async function sendFile(content: string) { if (uploadState.type !== "attached") return; const attachments: string[] = []; + setMessage; const cancel = Axios.CancelToken.source(); const files = uploadState.files; @@ -464,26 +515,6 @@ export default observer(({ channel }: Props) => { : undefined, }); - const renderEmoji = useMemo( - () => - memo(({ emoji }: { emoji: string }) => ( - { - const v = state.draft.get(channel._id); - setMessage(`${v ? `${v} ` : ""}:${emoji}:`); - }}> - - - )), - [], - ); - return ( <> @@ -527,10 +558,7 @@ export default observer(({ channel }: Props) => { /> {picker && ( - + )} diff --git a/src/mobx/stores/Experiments.ts b/src/mobx/stores/Experiments.ts index a91318a5..c37ad5d6 100644 --- a/src/mobx/stores/Experiments.ts +++ b/src/mobx/stores/Experiments.ts @@ -43,8 +43,9 @@ export const EXPERIMENTS: { "This will enable the experimental plugin API. Only touch this if you know what you're doing.", }, picker: { - title: "Emoji Picker", - description: "This will enable a work-in-progress emoji picker.", + title: "Custom Emoji", + description: + "This will enable a work-in-progress emoji picker and custom emoji settings.", }, }; diff --git a/src/pages/settings/ServerSettings.tsx b/src/pages/settings/ServerSettings.tsx index 53a0780c..6cd688a7 100644 --- a/src/pages/settings/ServerSettings.tsx +++ b/src/pages/settings/ServerSettings.tsx @@ -16,6 +16,8 @@ import { Text } from "preact-i18n"; import { LineDivider } from "@revoltchat/ui"; +import { state } from "../../mobx/State"; + import ButtonItem from "../../components/navigation/items/ButtonItem"; import { useClient } from "../../controllers/client/ClientController"; import RequiresOnline from "../../controllers/client/jsx/RequiresOnline"; @@ -77,6 +79,7 @@ export default observer(() => { id: "emojis", icon: , title: , + hidden: !state.experiments.isEnabled("picker"), }, { category: ( diff --git a/src/pages/settings/server/Emojis.tsx b/src/pages/settings/server/Emojis.tsx index 9cdabfed..6acaa453 100644 --- a/src/pages/settings/server/Emojis.tsx +++ b/src/pages/settings/server/Emojis.tsx @@ -2,6 +2,8 @@ import { observer } from "mobx-react-lite"; import { Server } from "revolt.js"; import styled from "styled-components"; +import { Text } from "preact-i18n"; + import { Button, Column, Row, Stacked } from "@revoltchat/ui"; import UserShort from "../../../components/common/user/UserShort"; @@ -48,7 +50,11 @@ export const Emojis = observer(({ server }: Props) => { return ( -

Emojis – {emoji.length}

+

+ + {" – "} + {emoji.length} +

{emoji.map((emoji) => ( @@ -64,7 +70,10 @@ export const Emojis = observer(({ server }: Props) => { onClick={() => modalController.writeText(emoji._id) }> - Copy ID + + + ))} diff --git a/yarn.lock b/yarn.lock index e01c1ac8..61a6f15f 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2240,9 +2240,9 @@ __metadata: languageName: node linkType: hard -"@revoltchat/ui@npm:1.0.70": - version: 1.0.70 - resolution: "@revoltchat/ui@npm:1.0.70" +"@revoltchat/ui@npm:1.0.72": + version: 1.0.72 + resolution: "@revoltchat/ui@npm:1.0.72" dependencies: "@styled-icons/boxicons-logos": ^10.38.0 "@styled-icons/boxicons-regular": ^10.38.0 @@ -2256,7 +2256,7 @@ __metadata: react-virtuoso: ^2.12.0 peerDependencies: revolt.js: "*" - checksum: 70eba00f8b2d4fed3f83cdd64488ab4eb99acc9dd0427333a41f203912d9664f878ec04300bc3eaea2a67fef1620472cce9ff7048ef1280f950825593cf316a7 + checksum: 8ca6d68709591a9505cc62089ab42fe887fcd4ceaac81c7f673b41b7be1d850ba852d0c2dc09bb77c8e1bce22b3255ebfbf95922d57aa2c3d2288aacd7819a25 languageName: node linkType: hard @@ -3626,7 +3626,7 @@ __metadata: "@hcaptcha/react-hcaptcha": ^0.3.6 "@insertish/vite-plugin-babel-macros": ^1.0.5 "@preact/preset-vite": ^2.0.0 - "@revoltchat/ui": 1.0.70 + "@revoltchat/ui": 1.0.72 "@rollup/plugin-replace": ^2.4.2 "@styled-icons/boxicons-logos": ^10.38.0 "@styled-icons/boxicons-regular": ^10.38.0 From 448722225ea7a6591a95e1aa2b568acb0a7743ca Mon Sep 17 00:00:00 2001 From: Paul Makles Date: Sat, 9 Jul 2022 17:55:13 +0100 Subject: [PATCH 141/151] fix: change quote matching Regex --- src/components/markdown/RemarkRenderer.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/markdown/RemarkRenderer.tsx b/src/components/markdown/RemarkRenderer.tsx index 26f03b37..02ac472a 100644 --- a/src/components/markdown/RemarkRenderer.tsx +++ b/src/components/markdown/RemarkRenderer.tsx @@ -180,7 +180,7 @@ const Container = styled.div<{ largeEmoji: boolean }>` /** * Regex for matching execessive blockquotes */ -const RE_QUOTE = /(^[>\s][>\s])[>\s]+([^]+$)/gm; +const RE_QUOTE = /(^(?:>\s){3})[>\s]+(.*$)/gm; /** * Regex for matching open angled bracket From 0ec7e5e11674d9bd230142ad9610f73c721b3c5a Mon Sep 17 00:00:00 2001 From: Paul Makles Date: Sun, 10 Jul 2022 13:53:19 +0100 Subject: [PATCH 142/151] feat: try to load any 'valid' emoji --- external/lang | 2 +- src/components/markdown/plugins/emoji.tsx | 30 +++++++++++++++++------ 2 files changed, 24 insertions(+), 8 deletions(-) diff --git a/external/lang b/external/lang index 58408da6..8a5984ed 160000 --- a/external/lang +++ b/external/lang @@ -1 +1 @@ -Subproject commit 58408da6c4090dd3a7808a663eaa95b8b1da7603 +Subproject commit 8a5984ed8f336637e9aa60ef3934e2bc5b9023e9 diff --git a/src/components/markdown/plugins/emoji.tsx b/src/components/markdown/plugins/emoji.tsx index 033af2de..15ca52e4 100644 --- a/src/components/markdown/plugins/emoji.tsx +++ b/src/components/markdown/plugins/emoji.tsx @@ -1,5 +1,7 @@ import styled from "styled-components"; +import { useState } from "preact/hooks"; + import { emojiDictionary } from "../../../assets/emojis"; import { clientController } from "../../../controllers/client/ClientController"; import { parseEmoji } from "../../common/Emoji"; @@ -12,34 +14,48 @@ const Emoji = styled.img` width: var(--emoji-size); margin: 0 0.05em 0 0.1em; vertical-align: -0.2em; + + img:before { + content: " "; + display: block; + position: absolute; + height: 50px; + width: 50px; + background-image: url(ishere.jpg); + } `; export function RenderEmoji({ match }: CustomComponentProps) { + const [fail, setFail] = useState(false); const url = match in emojiDictionary ? parseEmoji(emojiDictionary[match as keyof typeof emojiDictionary]) - : clientController.getAvailableClient().emojis!.get(match)! - .imageURL; + : `${ + clientController.getAvailableClient().configuration?.features + .autumn.url + }/emojis/${match}`; + + if (fail) return {`:${match}:`}; return ( setFail(true)} /> ); } -const RE_EMOJI = /:([a-zA-Z0-9_]+):/g; +const RE_EMOJI = /:([a-zA-Z0-9_+]+):/g; +const RE_ULID = /^[0123456789ABCDEFGHJKMNPQRSTVWXYZ]{26}$/; export const remarkEmoji = createComponent( "emoji", RE_EMOJI, - (match) => - match in emojiDictionary || - clientController.getAvailableClient().emojis?.has(match), + (match) => match in emojiDictionary || RE_ULID.test(match), ); export function isOnlyEmoji(text: string) { From aad9a30266919064bcd0c0be866a560aebc742f2 Mon Sep 17 00:00:00 2001 From: Paul Makles Date: Sun, 10 Jul 2022 13:57:47 +0100 Subject: [PATCH 143/151] fix: correctly pass-through preview URLs --- src/controllers/client/jsx/legacy/FileUploads.tsx | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/controllers/client/jsx/legacy/FileUploads.tsx b/src/controllers/client/jsx/legacy/FileUploads.tsx index fe175ebc..ec36bb6f 100644 --- a/src/controllers/client/jsx/legacy/FileUploads.tsx +++ b/src/controllers/client/jsx/legacy/FileUploads.tsx @@ -120,7 +120,9 @@ export function FileUploader(props: Props) { const [uploading, setUploading] = useState(false); const [previewFile, setPreviewFile] = useState(null!); - const [generatedPreviewURL, setGeneratedPreviewURL] = useState(""); + const [generatedPreviewURL, setGeneratedPreviewURL] = useState< + string | undefined + >(undefined); useEffect(() => { if (previewFile) { const url: string = URL.createObjectURL(previewFile); @@ -291,8 +293,8 @@ export function FileUploader(props: Props) { backgroundImage: style === "icon" ? `url('${ - generatedPreviewURL ?? - previewURL ?? + generatedPreviewURL || + previewURL || defaultPreview }')` : previewURL From f79288826849d0a6e64cb4e1e85b2830ccecc47b Mon Sep 17 00:00:00 2001 From: Paul Makles Date: Sun, 10 Jul 2022 14:27:00 +0100 Subject: [PATCH 144/151] chore: bump UI library --- package.json | 2 +- .../common/messaging/MessageBox.tsx | 21 ++++++++++--------- yarn.lock | 10 ++++----- 3 files changed, 17 insertions(+), 16 deletions(-) diff --git a/package.json b/package.json index 7e89c29a..3b78027a 100644 --- a/package.json +++ b/package.json @@ -73,7 +73,7 @@ "@hcaptcha/react-hcaptcha": "^0.3.6", "@insertish/vite-plugin-babel-macros": "^1.0.5", "@preact/preset-vite": "^2.0.0", - "@revoltchat/ui": "1.0.72", + "@revoltchat/ui": "1.0.74", "@rollup/plugin-replace": "^2.4.2", "@styled-icons/boxicons-logos": "^10.38.0", "@styled-icons/boxicons-regular": "^10.38.0", diff --git a/src/components/common/messaging/MessageBox.tsx b/src/components/common/messaging/MessageBox.tsx index 7b76aef9..ec964227 100644 --- a/src/components/common/messaging/MessageBox.tsx +++ b/src/components/common/messaging/MessageBox.tsx @@ -147,16 +147,7 @@ const HackAlertThisFileWillBeReplaced = observer(({ channel }: Props) => { const renderEmoji = useMemo( () => memo(({ emoji }: { emoji: string }) => ( - { - const v = state.draft.get(channel._id); - state.draft.set( - channel._id, - `${v ? `${v} ` : ""}:${emoji}:`, - ); - }}> - - + )), [], ); @@ -184,11 +175,21 @@ const HackAlertThisFileWillBeReplaced = observer(({ channel }: Props) => { } } + categories.push({ + id: "default", + name: "Default", + emoji: "smiley", + }); + return ( { + const v = state.draft.get(channel._id); + state.draft.set(channel._id, `${v ? `${v} ` : ""}:${emoji}:`); + }} /> ); }); diff --git a/yarn.lock b/yarn.lock index 61a6f15f..46aebd82 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2240,9 +2240,9 @@ __metadata: languageName: node linkType: hard -"@revoltchat/ui@npm:1.0.72": - version: 1.0.72 - resolution: "@revoltchat/ui@npm:1.0.72" +"@revoltchat/ui@npm:1.0.74": + version: 1.0.74 + resolution: "@revoltchat/ui@npm:1.0.74" dependencies: "@styled-icons/boxicons-logos": ^10.38.0 "@styled-icons/boxicons-regular": ^10.38.0 @@ -2256,7 +2256,7 @@ __metadata: react-virtuoso: ^2.12.0 peerDependencies: revolt.js: "*" - checksum: 8ca6d68709591a9505cc62089ab42fe887fcd4ceaac81c7f673b41b7be1d850ba852d0c2dc09bb77c8e1bce22b3255ebfbf95922d57aa2c3d2288aacd7819a25 + checksum: 39a018702de6f72ddc640e5a26417ef560f304626a7a266c8710c75a93c36213ba84e341257b3dc3704dda79309a4738390ebf57d05af339c07e14875d5bf6f5 languageName: node linkType: hard @@ -3626,7 +3626,7 @@ __metadata: "@hcaptcha/react-hcaptcha": ^0.3.6 "@insertish/vite-plugin-babel-macros": ^1.0.5 "@preact/preset-vite": ^2.0.0 - "@revoltchat/ui": 1.0.72 + "@revoltchat/ui": 1.0.74 "@rollup/plugin-replace": ^2.4.2 "@styled-icons/boxicons-logos": ^10.38.0 "@styled-icons/boxicons-regular": ^10.38.0 From 80943afcf61b1e05e988846fb9755745b5733257 Mon Sep 17 00:00:00 2001 From: Paul Makles Date: Sun, 10 Jul 2022 14:37:11 +0100 Subject: [PATCH 145/151] fix: don't hide settings button behind bottom nav fixes #691 --- package.json | 2 +- yarn.lock | 10 +++++----- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/package.json b/package.json index 3b78027a..83937ff4 100644 --- a/package.json +++ b/package.json @@ -73,7 +73,7 @@ "@hcaptcha/react-hcaptcha": "^0.3.6", "@insertish/vite-plugin-babel-macros": "^1.0.5", "@preact/preset-vite": "^2.0.0", - "@revoltchat/ui": "1.0.74", + "@revoltchat/ui": "1.0.75", "@rollup/plugin-replace": "^2.4.2", "@styled-icons/boxicons-logos": "^10.38.0", "@styled-icons/boxicons-regular": "^10.38.0", diff --git a/yarn.lock b/yarn.lock index 46aebd82..c291ac27 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2240,9 +2240,9 @@ __metadata: languageName: node linkType: hard -"@revoltchat/ui@npm:1.0.74": - version: 1.0.74 - resolution: "@revoltchat/ui@npm:1.0.74" +"@revoltchat/ui@npm:1.0.75": + version: 1.0.75 + resolution: "@revoltchat/ui@npm:1.0.75" dependencies: "@styled-icons/boxicons-logos": ^10.38.0 "@styled-icons/boxicons-regular": ^10.38.0 @@ -2256,7 +2256,7 @@ __metadata: react-virtuoso: ^2.12.0 peerDependencies: revolt.js: "*" - checksum: 39a018702de6f72ddc640e5a26417ef560f304626a7a266c8710c75a93c36213ba84e341257b3dc3704dda79309a4738390ebf57d05af339c07e14875d5bf6f5 + checksum: cde9a737146f8fb48c03f75efc9ceae9ae780d9dc449af2eb06ebbed54d22aa06786bb75c73657b2845bcb1f47039fd13b27e6d0125c5839cf800d94d7b0328d languageName: node linkType: hard @@ -3626,7 +3626,7 @@ __metadata: "@hcaptcha/react-hcaptcha": ^0.3.6 "@insertish/vite-plugin-babel-macros": ^1.0.5 "@preact/preset-vite": ^2.0.0 - "@revoltchat/ui": 1.0.74 + "@revoltchat/ui": 1.0.75 "@rollup/plugin-replace": ^2.4.2 "@styled-icons/boxicons-logos": ^10.38.0 "@styled-icons/boxicons-regular": ^10.38.0 From 77c3f8d1bcb779b41d765e2ccbb82a432c7c7e4d Mon Sep 17 00:00:00 2001 From: Paul Makles Date: Sun, 10 Jul 2022 15:30:34 +0100 Subject: [PATCH 146/151] chore: bump @revoltchat/ui to 1.0.76 --- package.json | 2 +- yarn.lock | 10 +++++----- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/package.json b/package.json index 83937ff4..9e522a3e 100644 --- a/package.json +++ b/package.json @@ -73,7 +73,7 @@ "@hcaptcha/react-hcaptcha": "^0.3.6", "@insertish/vite-plugin-babel-macros": "^1.0.5", "@preact/preset-vite": "^2.0.0", - "@revoltchat/ui": "1.0.75", + "@revoltchat/ui": "1.0.76", "@rollup/plugin-replace": "^2.4.2", "@styled-icons/boxicons-logos": "^10.38.0", "@styled-icons/boxicons-regular": "^10.38.0", diff --git a/yarn.lock b/yarn.lock index c291ac27..064e0ae0 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2240,9 +2240,9 @@ __metadata: languageName: node linkType: hard -"@revoltchat/ui@npm:1.0.75": - version: 1.0.75 - resolution: "@revoltchat/ui@npm:1.0.75" +"@revoltchat/ui@npm:1.0.76": + version: 1.0.76 + resolution: "@revoltchat/ui@npm:1.0.76" dependencies: "@styled-icons/boxicons-logos": ^10.38.0 "@styled-icons/boxicons-regular": ^10.38.0 @@ -2256,7 +2256,7 @@ __metadata: react-virtuoso: ^2.12.0 peerDependencies: revolt.js: "*" - checksum: cde9a737146f8fb48c03f75efc9ceae9ae780d9dc449af2eb06ebbed54d22aa06786bb75c73657b2845bcb1f47039fd13b27e6d0125c5839cf800d94d7b0328d + checksum: b4051c759bd2e350eaab0f28c4d27b8df391086d63d58db14edf0c557d19da6d1f95df20327682086db46c6348ab7c7a0e6c696829e16d18fd341e4fbc8cbaf2 languageName: node linkType: hard @@ -3626,7 +3626,7 @@ __metadata: "@hcaptcha/react-hcaptcha": ^0.3.6 "@insertish/vite-plugin-babel-macros": ^1.0.5 "@preact/preset-vite": ^2.0.0 - "@revoltchat/ui": 1.0.75 + "@revoltchat/ui": 1.0.76 "@rollup/plugin-replace": ^2.4.2 "@styled-icons/boxicons-logos": ^10.38.0 "@styled-icons/boxicons-regular": ^10.38.0 From 924448dc2c2e8697e2729f72200d11f4c8c482af Mon Sep 17 00:00:00 2001 From: Paul Makles Date: Mon, 11 Jul 2022 15:33:34 +0100 Subject: [PATCH 147/151] fix: remove html entities using AST plugin --- src/components/markdown/RemarkRenderer.tsx | 6 ++---- src/components/markdown/plugins/htmlEntities.ts | 10 ++++++++++ 2 files changed, 12 insertions(+), 4 deletions(-) create mode 100644 src/components/markdown/plugins/htmlEntities.ts diff --git a/src/components/markdown/RemarkRenderer.tsx b/src/components/markdown/RemarkRenderer.tsx index 02ac472a..3a729e35 100644 --- a/src/components/markdown/RemarkRenderer.tsx +++ b/src/components/markdown/RemarkRenderer.tsx @@ -20,6 +20,7 @@ import { RenderCodeblock } from "./plugins/Codeblock"; import { RenderAnchor } from "./plugins/anchors"; import { remarkChannels, RenderChannel } from "./plugins/channels"; import { isOnlyEmoji, remarkEmoji, RenderEmoji } from "./plugins/emoji"; +import { remarkHtmlEntities } from "./plugins/htmlEntities"; import { remarkMention, RenderMention } from "./plugins/mentions"; import { remarkSpoiler, RenderSpoiler } from "./plugins/spoiler"; import { remarkTimestamps } from "./plugins/timestamps"; @@ -138,6 +139,7 @@ const render = unified() .use(remarkTimestamps) .use(remarkEmoji) .use(remarkMention) + .use(remarkHtmlEntities) .use(remarkRehype, { handlers, }) @@ -197,10 +199,6 @@ function sanitise(content: string) { content // Strip excessive blockquote indentation .replace(RE_QUOTE, (_, m0, m1) => m0 + m1) - // Map < to HTML entity LT - // (otherwise all HTML is just obliterated, - // not even displayed as plain text) - .replace(RE_OPEN_BRACKET, "<") ); } diff --git a/src/components/markdown/plugins/htmlEntities.ts b/src/components/markdown/plugins/htmlEntities.ts new file mode 100644 index 00000000..eae15f49 --- /dev/null +++ b/src/components/markdown/plugins/htmlEntities.ts @@ -0,0 +1,10 @@ +import { Plugin } from "unified"; +import { visit } from "unist-util-visit"; + +export const remarkHtmlEntities: Plugin = () => { + return (tree) => { + visit(tree, "text", (node: { value: string }) => { + node.value = node.value.replace(/ Date: Mon, 11 Jul 2022 15:33:57 +0100 Subject: [PATCH 148/151] chore: change quote depth limit to 5 from 3 --- src/components/markdown/RemarkRenderer.tsx | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/src/components/markdown/RemarkRenderer.tsx b/src/components/markdown/RemarkRenderer.tsx index 3a729e35..fff898ec 100644 --- a/src/components/markdown/RemarkRenderer.tsx +++ b/src/components/markdown/RemarkRenderer.tsx @@ -182,12 +182,7 @@ const Container = styled.div<{ largeEmoji: boolean }>` /** * Regex for matching execessive blockquotes */ -const RE_QUOTE = /(^(?:>\s){3})[>\s]+(.*$)/gm; - -/** - * Regex for matching open angled bracket - */ -const RE_OPEN_BRACKET = /\s){5})[>\s]+(.*$)/gm; /** * Sanitise Markdown input before rendering From 030c2112306928bd54819981355a924b630203ea Mon Sep 17 00:00:00 2001 From: Paul Makles Date: Tue, 12 Jul 2022 14:15:53 +0100 Subject: [PATCH 149/151] fix: internal links would not redirect properly --- external/lang | 2 +- src/components/markdown/plugins/anchors.tsx | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/external/lang b/external/lang index 8a5984ed..5af7326c 160000 --- a/external/lang +++ b/external/lang @@ -1 +1 @@ -Subproject commit 8a5984ed8f336637e9aa60ef3934e2bc5b9023e9 +Subproject commit 5af7326c286f729ac6dd4cabff9dfdf7c480b631 diff --git a/src/components/markdown/plugins/anchors.tsx b/src/components/markdown/plugins/anchors.tsx index 6d0f52f0..8d616247 100644 --- a/src/components/markdown/plugins/anchors.tsx +++ b/src/components/markdown/plugins/anchors.tsx @@ -17,7 +17,7 @@ export function RenderAnchor({ // Render direct link if internal if (link.type === "navigate") { - return ; + return ; } return ( From 2214efe1bcc49fc53b81fc4ce0a5b1e20ceac8c1 Mon Sep 17 00:00:00 2001 From: Paul Makles Date: Tue, 12 Jul 2022 14:21:44 +0100 Subject: [PATCH 150/151] chore: hide emoji settings if no perm --- package.json | 2 +- src/pages/settings/server/Emojis.tsx | 14 ++++++++++---- yarn.lock | 10 +++++----- 3 files changed, 16 insertions(+), 10 deletions(-) diff --git a/package.json b/package.json index 9e522a3e..ba735b78 100644 --- a/package.json +++ b/package.json @@ -141,7 +141,7 @@ "remark-math": "^5.1.1", "remark-parse": "^10.0.1", "remark-rehype": "^10.1.0", - "revolt.js": "6.0.5", + "revolt.js": "^6.0.6", "rimraf": "^3.0.2", "sass": "^1.35.1", "semver": "^7.3.7", diff --git a/src/pages/settings/server/Emojis.tsx b/src/pages/settings/server/Emojis.tsx index 6acaa453..1395e7d2 100644 --- a/src/pages/settings/server/Emojis.tsx +++ b/src/pages/settings/server/Emojis.tsx @@ -49,7 +49,9 @@ export const Emojis = observer(({ server }: Props) => { return ( - + {server.havePermission("ManageCustomisation") && ( + + )}

{" – "} @@ -72,9 +74,13 @@ export const Emojis = observer(({ server }: Props) => { }> - + {server.havePermission("ManageCustomisation") && ( + + )} ))} diff --git a/yarn.lock b/yarn.lock index 064e0ae0..c433932b 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3696,7 +3696,7 @@ __metadata: remark-math: ^5.1.1 remark-parse: ^10.0.1 remark-rehype: ^10.1.0 - revolt.js: 6.0.5 + revolt.js: ^6.0.6 rimraf: ^3.0.2 sass: ^1.35.1 semver: ^7.3.7 @@ -7890,9 +7890,9 @@ __metadata: languageName: node linkType: hard -"revolt.js@npm:6.0.5": - version: 6.0.5 - resolution: "revolt.js@npm:6.0.5" +"revolt.js@npm:^6.0.6": + version: 6.0.6 + resolution: "revolt.js@npm:6.0.6" dependencies: "@insertish/exponential-backoff": 3.1.0-patch.2 "@insertish/isomorphic-ws": ^4.0.1 @@ -7906,7 +7906,7 @@ __metadata: revolt-api: 0.5.4 ulid: ^2.3.0 ws: ^8.2.2 - checksum: 54d7f3a9eeafc79ae5443e22cacd85acb3359aaacccfbb11e41c8cb4a888188b200224dd3ce78847682aabbd3737d99848f6f1fbf144973cd2928d2316cffd2d + checksum: 079bdb983c650233378a617b771d7ff64396ce96fbd822fea20e9897fa14c2e589869e4a66f749dc74ce08218af425f97ab42fcaca7a3ab0f68f38f163484260 languageName: node linkType: hard From 4f3f6e26cf4dd62c883850020fd34f81b8d1eacf Mon Sep 17 00:00:00 2001 From: Paul Makles Date: Wed, 13 Jul 2022 12:32:39 +0100 Subject: [PATCH 151/151] feat: convert html AST nodes to text --- src/components/markdown/RemarkRenderer.tsx | 4 ++-- src/components/markdown/plugins/htmlEntities.ts | 10 ---------- src/components/markdown/plugins/htmlToText.ts | 10 ++++++++++ 3 files changed, 12 insertions(+), 12 deletions(-) delete mode 100644 src/components/markdown/plugins/htmlEntities.ts create mode 100644 src/components/markdown/plugins/htmlToText.ts diff --git a/src/components/markdown/RemarkRenderer.tsx b/src/components/markdown/RemarkRenderer.tsx index fff898ec..e3b2f3d6 100644 --- a/src/components/markdown/RemarkRenderer.tsx +++ b/src/components/markdown/RemarkRenderer.tsx @@ -20,7 +20,7 @@ import { RenderCodeblock } from "./plugins/Codeblock"; import { RenderAnchor } from "./plugins/anchors"; import { remarkChannels, RenderChannel } from "./plugins/channels"; import { isOnlyEmoji, remarkEmoji, RenderEmoji } from "./plugins/emoji"; -import { remarkHtmlEntities } from "./plugins/htmlEntities"; +import { remarkHtmlToText } from "./plugins/htmlToText"; import { remarkMention, RenderMention } from "./plugins/mentions"; import { remarkSpoiler, RenderSpoiler } from "./plugins/spoiler"; import { remarkTimestamps } from "./plugins/timestamps"; @@ -139,7 +139,7 @@ const render = unified() .use(remarkTimestamps) .use(remarkEmoji) .use(remarkMention) - .use(remarkHtmlEntities) + .use(remarkHtmlToText) .use(remarkRehype, { handlers, }) diff --git a/src/components/markdown/plugins/htmlEntities.ts b/src/components/markdown/plugins/htmlEntities.ts deleted file mode 100644 index eae15f49..00000000 --- a/src/components/markdown/plugins/htmlEntities.ts +++ /dev/null @@ -1,10 +0,0 @@ -import { Plugin } from "unified"; -import { visit } from "unist-util-visit"; - -export const remarkHtmlEntities: Plugin = () => { - return (tree) => { - visit(tree, "text", (node: { value: string }) => { - node.value = node.value.replace(/ { + return (tree) => { + visit(tree, "html", (node: { type: string; value: string }) => { + node.type = "text"; + }); + }; +};