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