From 8fe1ce345066b063bd856f4d1f1ff83af60c5e7f Mon Sep 17 00:00:00 2001
From: Paul <paulmakles@gmail.com>
Date: Tue, 22 Jun 2021 10:59:06 +0100
Subject: [PATCH] Show errors / queue on message. Focus editor / box properly.

---
 external/lang                                 |   2 +-
 src/components/common/messaging/Message.tsx   |  12 +-
 .../common/messaging/MessageBase.tsx          |   4 +-
 .../common/messaging/MessageBox.tsx           |  12 ++
 src/components/ui/TextArea.tsx                | 139 ------------------
 src/lib/TextAreaAutoSize.tsx                  |  28 +++-
 .../channels/messaging/MessageEditor.tsx      |   2 +-
 .../channels/messaging/MessageRenderer.tsx    |  26 +---
 8 files changed, 52 insertions(+), 173 deletions(-)

diff --git a/external/lang b/external/lang
index 210172d..a84270a 160000
--- a/external/lang
+++ b/external/lang
@@ -1 +1 @@
-Subproject commit 210172de724fcd5adeacec221bd9da30350afc06
+Subproject commit a84270a2b941a51f4785e543c0882ce9f7f004a6
diff --git a/src/components/common/messaging/Message.tsx b/src/components/common/messaging/Message.tsx
index 800c1df..87dfeed 100644
--- a/src/components/common/messaging/Message.tsx
+++ b/src/components/common/messaging/Message.tsx
@@ -6,18 +6,21 @@ import { Children } from "../../../types/Preact";
 import Attachment from "./attachments/Attachment";
 import { attachContextMenu } from "preact-context-menu";
 import { useUser } from "../../../context/revoltjs/hooks";
+import { QueuedMessage } from "../../../redux/reducers/queue";
 import { MessageObject } from "../../../context/revoltjs/util";
 import MessageBase, { MessageContent, MessageDetail, MessageInfo } from "./MessageBase";
+import Overline from "../../ui/Overline";
 
 interface Props {
     attachContext?: boolean
+    queued?: QueuedMessage
     message: MessageObject
     contrast?: boolean
     content?: Children
     head?: boolean
 }
 
-export default function Message({ attachContext, message, contrast, content: replacement, head }: Props) {
+export default function Message({ attachContext, message, contrast, content: replacement, head, queued }: Props) {
     // TODO: Can improve re-renders here by providing a list
     // TODO: of dependencies. We only need to update on u/avatar.
     let user = useUser(message.author);
@@ -25,9 +28,11 @@ export default function Message({ attachContext, message, contrast, content: rep
     const content = message.content as string;
     return (
         <MessageBase id={message._id}
-            contrast={contrast}
             head={head}
-            onContextMenu={attachContext ? attachContextMenu('Menu', { message, contextualChannel: message.channel }) : undefined}>
+            contrast={contrast}
+            sending={typeof queued !== 'undefined'}
+            failed={typeof queued?.error !== 'undefined'}
+            onContextMenu={attachContext ? attachContextMenu('Menu', { message, contextualChannel: message.channel, queued }) : undefined}>
             <MessageInfo>
                 { head ?
                     <UserIcon target={user} size={36} /> :
@@ -39,6 +44,7 @@ export default function Message({ attachContext, message, contrast, content: rep
                     <MessageDetail message={message} position="top" />
                 </span> }
                 { replacement ?? <Markdown content={content} /> }
+                { queued?.error && <Overline type="error" error={queued.error} /> }
                 { message.attachments?.map((attachment, index) =>
                     <Attachment key={index} attachment={attachment} hasContent={ index > 0 || content.length > 0 } />) }
                 { message.embeds?.map((embed, index) =>
diff --git a/src/components/common/messaging/MessageBase.tsx b/src/components/common/messaging/MessageBase.tsx
index 7284dba..2047338 100644
--- a/src/components/common/messaging/MessageBase.tsx
+++ b/src/components/common/messaging/MessageBase.tsx
@@ -7,7 +7,7 @@ import { MessageObject } from "../../../context/revoltjs/util";
 
 export interface BaseMessageProps {
     head?: boolean,
-    status?: boolean,
+    failed?: boolean,
     mention?: boolean,
     blocked?: boolean,
     sending?: boolean,
@@ -49,7 +49,7 @@ export default styled.div<BaseMessageProps>`
         color: var(--tertiary-foreground);
     ` }
 
-    ${ props => props.status && css`
+    ${ props => props.failed && css`
         color: var(--error);
     ` }
 
diff --git a/src/components/common/messaging/MessageBox.tsx b/src/components/common/messaging/MessageBox.tsx
index f0e6a8a..ed0f78e 100644
--- a/src/components/common/messaging/MessageBox.tsx
+++ b/src/components/common/messaging/MessageBox.tsx
@@ -19,6 +19,7 @@ import { SingletonMessageRenderer, SMOOTH_SCROLL_ON_RECEIVE } from "../../../lib
 
 import FilePreview from './bars/FilePreview';
 import { debounce } from "../../../lib/debounce";
+import { internalEmit } from "../../../lib/eventEmitter";
 
 type Props = WithDispatcher & {
     channel: Channel;
@@ -237,11 +238,22 @@ function MessageBox({ channel, draft, dispatcher }: Props) {
                     />
                 </Action>
                 <TextAreaAutoSize
+                    autoFocus
                     hideBorder
                     maxRows={5}
                     padding={15}
+                    id="message"
                     value={draft ?? ''}
                     onKeyDown={e => {
+                        if (
+                            e.key === "ArrowUp" &&
+                            (!draft || draft.length === 0)
+                        ) {
+                            e.preventDefault();
+                            internalEmit("MessageRenderer", "edit_last");
+                            return;
+                        }
+
                         if (!e.shiftKey && e.key === "Enter" && !isTouchscreenDevice) {
                             e.preventDefault();
                             return send();
diff --git a/src/components/ui/TextArea.tsx b/src/components/ui/TextArea.tsx
index 7cdde08..f91b428 100644
--- a/src/components/ui/TextArea.tsx
+++ b/src/components/ui/TextArea.tsx
@@ -48,142 +48,3 @@ export default styled.textarea<TextAreaProps>`
         font-family: 'Open Sans', sans-serif;
     ` }
 `;
-
-/*export interface TextAreaProps {
-    id?: string;
-    value: string;
-    maxRows?: number;
-    padding?: number;
-    minHeight?: number;
-    disabled?: boolean;
-    maxLength?: number;
-    className?: string;
-    autoFocus?: boolean;
-    forceFocus?: boolean;
-    placeholder?: string;
-    onKeyDown?: (ev: KeyboardEvent) => void;
-    onKeyUp?: (ev: KeyboardEvent) => void;
-    onChange: (
-        value: string,
-        ev: JSX.TargetedEvent<HTMLTextAreaElement, Event>
-    ) => void;
-    onFocus?: (current: HTMLTextAreaElement) => void;
-    onBlur?: () => void;
-}
-
-const lineHeight = 20;
-
-export const TextAreaB = memo((props: TextAreaProps) => {
-    const padding = props.padding ? props.padding * 2 : 0;
-
-    const [height, setHeightState] = useState(
-        props.minHeight ?? lineHeight + padding
-    );
-    const ghost = useRef<HTMLDivElement>();
-    const ref = useRef<HTMLTextAreaElement>();
-
-    function setHeight(h: number = lineHeight) {
-        let newHeight = Math.min(
-            Math.max(
-                lineHeight,
-                props.maxRows ? Math.min(h, props.maxRows * lineHeight) : h
-            ),
-            props.minHeight ?? Infinity
-        );
-
-        if (props.padding) newHeight += padding;
-        if (height !== newHeight) {
-            setHeightState(newHeight);
-        }
-    }
-
-    function onChange(ev: JSX.TargetedEvent<HTMLTextAreaElement, Event>) {
-        props.onChange(ev.currentTarget.value, ev);
-    }
-
-    useLayoutEffect(() => {
-        setHeight(ghost.current.clientHeight);
-    }, [ghost, props.value]);
-
-    useEffect(() => {
-        if (props.autoFocus) ref.current.focus();
-    }, [props.value]);
-
-    const inputSelected = () =>
-        ["TEXTAREA", "INPUT"].includes(document.activeElement?.nodeName ?? "");
-
-    useEffect(() => {
-        if (props.forceFocus) {
-            ref.current.focus();
-        }
-
-        if (props.autoFocus && !inputSelected()) {
-            ref.current.focus();
-        }
-
-        // ? if you are wondering what this is
-        // ? it is a quick and dirty hack to fix
-        // ? value not setting correctly
-        // ? I have no clue what's going on
-        ref.current.value = props.value;
-
-        if (!props.autoFocus) return;
-        function keyDown(e: KeyboardEvent) {
-            if ((e.ctrlKey && e.key !== "v") || e.altKey || e.metaKey) return;
-            if (e.key.length !== 1) return;
-            if (ref && !inputSelected()) {
-                ref.current.focus();
-            }
-        }
-
-        document.body.addEventListener("keydown", keyDown);
-        return () => document.body.removeEventListener("keydown", keyDown);
-    }, [ref]);
-
-    useEffect(() => {
-        function focus(textarea_id: string) {
-            if (props.id === textarea_id) {
-                ref.current.focus();
-            }
-        }
-
-        // InternalEventEmitter.addListener("focus_textarea", focus);
-        // return () =>
-            // InternalEventEmitter.removeListener("focus_textarea", focus);
-    }, [ref]);
-
-    return (
-        <div className={classNames(styles.container, props.className)}>
-            <textarea
-                id={props.id}
-                name={props.id}
-                style={{ height }}
-                value={props.value}
-                onChange={onChange}
-                disabled={props.disabled}
-                maxLength={props.maxLength}
-                className={styles.textarea}
-                onKeyDown={props.onKeyDown}
-                placeholder={props.placeholder}
-                onContextMenu={e => e.stopPropagation()}
-                onKeyUp={ev => {
-                    setHeight(ghost.current.clientHeight);
-                    props.onKeyUp && props.onKeyUp(ev);
-                }}
-                ref={ref}
-                onFocus={() => props.onFocus && props.onFocus(ref.current)}
-                onBlur={props.onBlur}
-            />
-            <div className={styles.hide}>
-                <div className={styles.ghost} ref={ghost}>
-                    {props.value
-                        ? props.value
-                              .split("\n")
-                              .map(x => `‎${x}`)
-                              .join("\n")
-                        : undefined ?? "‎\n"}
-                </div>
-            </div>
-        </div>
-    );
-});*/
diff --git a/src/lib/TextAreaAutoSize.tsx b/src/lib/TextAreaAutoSize.tsx
index deca435..46c4e9f 100644
--- a/src/lib/TextAreaAutoSize.tsx
+++ b/src/lib/TextAreaAutoSize.tsx
@@ -1,20 +1,24 @@
 import TextArea, { DEFAULT_LINE_HEIGHT, DEFAULT_TEXT_AREA_PADDING, TextAreaProps, TEXT_AREA_BORDER_WIDTH } from "../components/ui/TextArea";
 import { useEffect, useRef } from "preact/hooks";
+import { internalSubscribe } from "./eventEmitter";
 
 type TextAreaAutoSizeProps = Omit<JSX.HTMLAttributes<HTMLTextAreaElement>, 'style' | 'value'> & TextAreaProps & {
-    autoFocus?: boolean,
-    minHeight?: number,
-    maxRows?: number,
+    forceFocus?: boolean
+    autoFocus?: boolean
+    minHeight?: number
+    maxRows?: number
     value: string
+
+    id?: string
 };
 
 export default function TextAreaAutoSize(props: TextAreaAutoSizeProps) {
-    const { autoFocus, minHeight, maxRows, value, padding, lineHeight, hideBorder, children, as, ...textAreaProps } = props;
+    const { autoFocus, minHeight, maxRows, value, padding, lineHeight, hideBorder, forceFocus, children, as, ...textAreaProps } = props;
     const line = lineHeight ?? DEFAULT_LINE_HEIGHT;
 
     const heightPadding = ((padding ?? DEFAULT_TEXT_AREA_PADDING) + (hideBorder ? 0 : TEXT_AREA_BORDER_WIDTH)) * 2;
     const height = Math.max(Math.min(value.split('\n').length, maxRows ?? Infinity) * line + heightPadding, minHeight ?? 0);
-    console.log(value.split('\n').length, line, heightPadding, height);
+
     const ref = useRef<HTMLTextAreaElement>();
 
     useEffect(() => {
@@ -25,9 +29,9 @@ export default function TextAreaAutoSize(props: TextAreaAutoSizeProps) {
         ["TEXTAREA", "INPUT"].includes(document.activeElement?.nodeName ?? "");
 
     useEffect(() => {
-        /* if (props.forceFocus) { // figure out what needed force focus
+        if (forceFocus) {
             ref.current.focus();
-        } */
+        }
 
         if (autoFocus && !inputSelected()) {
             ref.current.focus();
@@ -52,6 +56,16 @@ export default function TextAreaAutoSize(props: TextAreaAutoSizeProps) {
         return () => document.body.removeEventListener("keydown", keyDown);
     }, [ref]);
 
+    useEffect(() => {
+        function focus(id: string) {
+            if (id === props.id) {
+                ref.current.focus();
+            }
+        }
+
+        return internalSubscribe("TextArea", "focus", focus);
+    }, [ref]);
+
     return <TextArea
         ref={ref}
         value={value}
diff --git a/src/pages/channels/messaging/MessageEditor.tsx b/src/pages/channels/messaging/MessageEditor.tsx
index 065eb4c..3d93a75 100644
--- a/src/pages/channels/messaging/MessageEditor.tsx
+++ b/src/pages/channels/messaging/MessageEditor.tsx
@@ -58,6 +58,7 @@ export default function MessageEditor({ message, finish }: Props) {
     return (
         <EditorBase>
             <TextAreaAutoSize
+                forceFocus
                 maxRows={3}
                 padding={12}
                 value={content}
@@ -73,7 +74,6 @@ export default function MessageEditor({ message, finish }: Props) {
                         save();
                     }
                 }}
-                // forceFocus
             />
             <span className="caption">
                 escape to <a onClick={finish}>cancel</a> &middot;
diff --git a/src/pages/channels/messaging/MessageRenderer.tsx b/src/pages/channels/messaging/MessageRenderer.tsx
index 4f30cd9..ab53edf 100644
--- a/src/pages/channels/messaging/MessageRenderer.tsx
+++ b/src/pages/channels/messaging/MessageRenderer.tsx
@@ -1,17 +1,17 @@
 import { decodeTime } from "ulid";
 import MessageEditor from "./MessageEditor";
 import { Children } from "../../../types/Preact";
-import { useEffect, useState } from "preact/hooks";
 import ConversationStart from "./ConversationStart";
 import { connectState } from "../../../redux/connector";
 import Preloader from "../../../components/ui/Preloader";
 import { RenderState } from "../../../lib/renderer/types";
 import DateDivider from "../../../components/ui/DateDivider";
 import { QueuedMessage } from "../../../redux/reducers/queue";
+import { useContext, useEffect, useState } from "preact/hooks";
 import { MessageObject } from "../../../context/revoltjs/util";
 import Message from "../../../components/common/messaging/Message";
+import { AppContext } from "../../../context/revoltjs/RevoltClient";
 import RequiresOnline from "../../../context/revoltjs/RequiresOnline";
-import { useForceUpdate, useUsers } from "../../../context/revoltjs/hooks";
 import { internalSubscribe, internalEmit } from "../../../lib/eventEmitter";
 import { SystemMessage } from "../../../components/common/messaging/SystemMessage";
 
@@ -24,17 +24,13 @@ interface Props {
 function MessageRenderer({ id, state, queue }: Props) {
     if (state.type !== 'RENDER') return null;
 
-    const ctx = useForceUpdate();
-    const users = useUsers();
-    const userId = ctx.client.user!._id;
-
-    /*
-    const view = useView(id);*/
+    const client = useContext(AppContext);
+    const userId = client.user!._id;
 
     const [editing, setEditing] = useState<string | undefined>(undefined);
     const stopEditing = () => {
         setEditing(undefined);
-        internalEmit("MessageBox", "focus");
+        internalEmit("TextArea", "focus", "message");
     };
 
     useEffect(() => {
@@ -141,24 +137,14 @@ function MessageRenderer({ id, state, queue }: Props) {
                 } as any;
             }
 
-            /*render.push(
-                <Message
-                    user={users.find(x => x?._id === userId)}
-                    message={msg.data}
-                    queued={msg}
-                    key={msg.id}
-                    head={head}
-                />
-            );*/
             render.push(
                 <Message message={msg.data}
                     key={msg.id}
+                    queued={msg}
                     head={head}
                     attachContext />
             );
         }
-
-        // render.push(<div>end</div>);
     } else {
         render.push(
             <RequiresOnline>
-- 
GitLab