diff --git a/src/platform/SizedTextarea.css b/src/platform/SizedTextarea.css deleted file mode 100644 index 14184f6..0000000 --- a/src/platform/SizedTextarea.css +++ /dev/null @@ -1,5 +0,0 @@ -.SizedTextarea { - overflow-y: hidden; - width: 100%; - resize: vertical; -} diff --git a/src/platform/SizedTextarea.tsx b/src/platform/SizedTextarea.tsx deleted file mode 100644 index 4f97ff1..0000000 --- a/src/platform/SizedTextarea.tsx +++ /dev/null @@ -1,63 +0,0 @@ -import { splitProps, type Component, type JSX } from "solid-js"; -import "./SizedTextarea.css"; - -function isBoundEventHandler( - handler: JSX.EventHandlerUnion, -): handler is JSX.BoundEventHandler { - return Array.isArray(handler); -} - -function callEventHandlerUnion( - handler: JSX.EventHandlerUnion, - event: E & { currentTarget: T; target: Element }, -) { - if (isBoundEventHandler(handler)) { - const fn = handler[0], - value = handler[1]; - fn(value, event); - } else { - (handler as (e: typeof event) => void).bind(event.target)(event); - } -} - -function onTextareaRefreshHeight< - E extends Event & { - currentTarget: HTMLTextAreaElement; - target: HTMLTextAreaElement; - }, ->( - ocallback: JSX.EventHandlerUnion | undefined, - event: E, -) { - const element = event.currentTarget; - element.style.removeProperty("height"); - element.style.height = `${element.scrollHeight + 2}px`; - - if (ocallback) { - callEventHandlerUnion(ocallback, event); - } -} - -/** - * The - ); -}; - -export default SizedTextarea; diff --git a/src/timelines/TimelinePanel.tsx b/src/timelines/TimelinePanel.tsx index 150ca0e..edd1806 100644 --- a/src/timelines/TimelinePanel.tsx +++ b/src/timelines/TimelinePanel.tsx @@ -33,6 +33,7 @@ const TimelinePanel: Component<{ () => props.client.v1.timelines[props.name], () => ({ limit: 20 }), ); + const [typing, setTyping] = createSignal(false); const tlEndObserver = new IntersectionObserver(() => { if (untrack(() => props.prefetch) && !snapshot.loading) @@ -64,6 +65,8 @@ const TimelinePanel: Component<{ style={{ "--scaffold-topbar-height": "0px", }} + isTyping={typing()} + onTypingChange={setTyping} client={props.client} onSent={() => refetchTimeline("prev")} /> diff --git a/src/timelines/TootBottomSheet.tsx b/src/timelines/TootBottomSheet.tsx index 92a44c3..40d04ad 100644 --- a/src/timelines/TootBottomSheet.tsx +++ b/src/timelines/TootBottomSheet.tsx @@ -46,6 +46,7 @@ const TootBottomSheet: Component = (props) => { }>(); const navigate = useNavigate(); const time = createTimeSource(); + const [isInTyping, setInTyping] = createSignal(false); const acctText = () => decodeURIComponent(params.acct); const session = useSessionForAcctStr(acctText); @@ -69,6 +70,12 @@ const TootBottomSheet: Component = (props) => { return tootId; }); + createEffect(() => { + if (location.state?.tootReply) { + setInTyping(true); + } + }); + const [tootContextErrorUncaught, { refetch: refetchContext }] = createResource( () => [session().client, params.id] as const, @@ -275,6 +282,8 @@ const TootBottomSheet: Component = (props) => { .MuiToolbar-root { - justify-content: space-between; - - > :first-child { - margin-left: -0.5em; - } - - > :last-child { - margin-right: -0.5em; - } - } - - .reply-input { - display: flex; - align-items: flex-start; - gap: 8px; - } - - .options { - display: flex; - justify-content: flex-end; - gap: 16px; - flex-flow: row wrap; - padding-top: 16px; - padding-bottom: 8px; - margin-left: -0.5em; - margin-right: -0.5em; - - animation: TootComposerFadeIn 110ms var(--tutu-anim-curve-sharp) both; - } -} - -@keyframes TootComposerFadeIn { - 0% { - opacity: 0.5; - } - - 100% { - opacity: 1; - } -} diff --git a/src/timelines/TootComposer.module.css b/src/timelines/TootComposer.module.css new file mode 100644 index 0000000..f752e7c --- /dev/null +++ b/src/timelines/TootComposer.module.css @@ -0,0 +1,11 @@ + +.composer { + composes: card from "../material/cards.module.css"; + --card-gut: 8px; +} + +.replyInput { + display: flex; + align-items: flex-start; + gap: 8px; +} diff --git a/src/timelines/TootComposer.tsx b/src/timelines/TootComposer.tsx index 60e6bc5..01e0b81 100644 --- a/src/timelines/TootComposer.tsx +++ b/src/timelines/TootComposer.tsx @@ -1,12 +1,10 @@ import { createEffect, createMemo, - createRenderEffect, createSignal, createUniqueId, onMount, Show, - type Accessor, type Component, type JSX, type Ref, @@ -25,9 +23,6 @@ import { Switch, Divider, CircularProgress, - Toolbar, - MenuItem, - ListItemAvatar, } from "@suid/material"; import { ArrowDropDown, @@ -38,11 +33,9 @@ import { ListAlt as ListAltIcon, Visibility, Translate, - Close, - MoreVert, } from "@suid/icons-material"; import type { Account } from "../accounts/stores"; -import "./TootComposer.css"; +import tootComposers from "./TootComposer.module.css"; import { makeEventListener } from "@solid-primitives/event-listener"; import BottomSheet from "../material/BottomSheet"; import { useLanguage } from "../platform/i18n"; @@ -50,11 +43,6 @@ import iso639_1 from "iso-639-1"; import ChooseTootLang from "./ChooseTootLang"; import type { mastodon } from "masto"; import cardStyles from "../material/cards.module.css"; -import { Title } from "../material/typography"; -import Menu, { createManagedMenuState } from "../material/Menu"; -import { useDefaultSession } from "../masto/clients"; -import { resolveCustomEmoji } from "../masto/toot"; -import SizedTextarea from "../platform/SizedTextarea"; type TootVisibility = "public" | "unlisted" | "private" | "direct"; @@ -208,10 +196,6 @@ function randomChoose( return K[idx]; } -function useRandomChoice(choices: () => T[]): Accessor { - return createMemo(() => randomChoose(Math.random(), choices())); -} - function cancelEvent(event: Event) { event.stopPropagation(); } @@ -222,27 +206,27 @@ const TootComposer: Component<{ profile?: Account; replyToDisplayName?: string; mentions?: readonly string[]; + isTyping?: boolean; + onTypingChange: (value: boolean) => void; client?: mastodon.rest.Client; inReplyToId?: string; onSent?: (status: mastodon.v1.Status) => void; }> = (props) => { let inputRef: HTMLTextAreaElement; + let sendKey: string | undefined; - const session = useDefaultSession(); - - const [active, setActive] = createSignal(false); + const typing = () => props.isTyping; + const setTyping = (v: boolean) => props.onTypingChange(v); const [sending, setSending] = createSignal(false); const [visibility, setVisibility] = createSignal("public"); const [permPicker, setPermPicker] = createSignal(false); const [language, setLanguage] = createSignal("en"); const [langPickerOpen, setLangPickerOpen] = createSignal(false); const appLanguage = useLanguage(); - const [openMenu, menuState] = createManagedMenuState(); - const randomPlaceholder = useRandomChoice(() => [ - "What's happening?", - "What do you think?", - ]); + const randomPlaceholder = createMemo(() => + randomChoose(Math.random(), ["What's happening?", "What do your think?"]), + ); createEffect(() => { const lang = appLanguage().split("-")[0]; @@ -250,11 +234,15 @@ const TootComposer: Component<{ }); createEffect(() => { - if (active()) { + if (typing()) { setTimeout(() => inputRef.focus(), 0); } }); + onMount(() => { + makeEventListener(inputRef, "focus", () => setTyping(true)); + }); + createEffect(() => { if (inputRef.value !== "") return; if (props.mentions) { @@ -264,7 +252,7 @@ const TootComposer: Component<{ }); const containerStyle = () => - active() || permPicker() + typing() || permPicker() ? { position: "sticky" as const, top: "var(--scaffold-topbar-height, 0)", @@ -287,15 +275,17 @@ const TootComposer: Component<{ } }; - const idempotencyKey = createMemo(() => window.crypto.randomUUID()); + const getOrGenSendKey = () => { + if (sendKey === undefined) { + sendKey = window.crypto.randomUUID(); + } + return sendKey; + }; const send = async () => { - const client = session()?.client; - if (!client) return; - setSending(true); try { - const status = await client.v1.statuses.create( + const status = await props.client!.v1.statuses.create( { status: inputRef.value, language: language(), @@ -305,7 +295,7 @@ const TootComposer: Component<{ { requestInit: { headers: { - ["Idempotency-Key"]: idempotencyKey(), + ["Idempotency-Key"]: getOrGenSendKey(), }, }, }, @@ -321,8 +311,11 @@ const TootComposer: Component<{ return (
{ + inputRef.focus(); + }} on:touchend={ cancelEvent /* on: is required to register the event handler on the exact element */ @@ -330,63 +323,23 @@ const TootComposer: Component<{ on:touchmove={cancelEvent} on:wheel={cancelEvent} > - - - - - - openMenu(e.currentTarget.getBoundingClientRect())} - > - - - -
- - - - - - - { - createRenderEffect(() => { - const inf = session()?.account.inf; - return (e.innerHTML = resolveCustomEmoji( - inf?.displayName || "", - inf?.emojis ?? [], - )); - }); - }} - > - - - -
-
- -
+
- + > } > - +
- -
- -