composing toots #21

Merged
Rubicon merged 11 commits from feat-toot-composer into master 2024-09-28 07:30:40 +00:00
3 changed files with 55 additions and 15 deletions
Showing only changes of commit dd6051aea7 - Show all commits

View file

@ -11,7 +11,7 @@ import {
Suspense, Suspense,
Match, Match,
Switch as JsSwitch, Switch as JsSwitch,
ErrorBoundary ErrorBoundary,
} from "solid-js"; } from "solid-js";
import { useDocumentTitle } from "../utils"; import { useDocumentTitle } from "../utils";
import { type mastodon } from "masto"; import { type mastodon } from "masto";
@ -47,6 +47,7 @@ import { HeroSourceProvider, type HeroSource } from "../platform/anim";
import { useNavigate } from "@solidjs/router"; import { useNavigate } from "@solidjs/router";
import { useSignedInProfiles } from "../masto/acct"; import { useSignedInProfiles } from "../masto/acct";
import { setCache as setTootBottomSheetCache } from "./TootBottomSheet"; import { setCache as setTootBottomSheetCache } from "./TootBottomSheet";
import TootComposer from "./TootComposer";
const TimelinePanel: Component<{ const TimelinePanel: Component<{
client: mastodon.rest.Client; client: mastodon.rest.Client;
@ -72,6 +73,7 @@ const TimelinePanel: Component<{
{ fullRefresh: props.fullRefetch }, { fullRefresh: props.fullRefetch },
); );
const [expandedThreadId, setExpandedThreadId] = createSignal<string>(); const [expandedThreadId, setExpandedThreadId] = createSignal<string>();
const [typing, setTyping] = createSignal(false);
const tlEndObserver = new IntersectionObserver(() => { const tlEndObserver = new IntersectionObserver(() => {
if (untrack(() => props.prefetch) && !snapshot.loading) if (untrack(() => props.prefetch) && !snapshot.loading)
@ -126,9 +128,11 @@ const TimelinePanel: Component<{
}; };
return ( return (
<ErrorBoundary fallback={(err, reset) => { <ErrorBoundary
return <p>Oops: {String(err)}</p> fallback={(err, reset) => {
}}> return <p>Oops: {String(err)}</p>;
}}
>
<PullDownToRefresh <PullDownToRefresh
linkedElement={scrollLinked()} linkedElement={scrollLinked()}
loading={snapshot.loading} loading={snapshot.loading}
@ -141,6 +145,17 @@ const TimelinePanel: Component<{
}, 0) }, 0)
} }
> >
<Show when={props.name === "home"}>
<TootComposer
style={{
"--scaffold-topbar-height": "0px",
}}
isTyping={typing()}
onTypingChange={setTyping}
client={props.client}
onSent={() => refetchTimeline({direction: "new"})}
/>
</Show>
<For each={timeline}> <For each={timeline}>
{(item, index) => { {(item, index) => {
let element: HTMLElement | undefined; let element: HTMLElement | undefined;

View file

@ -23,7 +23,7 @@ import cards from "../material/cards.module.css";
import { css } from "solid-styled"; import { css } from "solid-styled";
import { vibrate } from "../platform/hardware"; import { vibrate } from "../platform/hardware";
import { createTimeSource, TimeSourceProvider } from "../platform/timesrc"; import { createTimeSource, TimeSourceProvider } from "../platform/timesrc";
import ReplyEditor from "./ReplyEditor"; import TootComposer from "./TootComposer";
let cachedEntry: [string, mastodon.v1.Status] | undefined; let cachedEntry: [string, mastodon.v1.Status] | undefined;
@ -250,7 +250,7 @@ const TootBottomSheet: Component = (props) => {
</article> </article>
<Show when={session()!.account}> <Show when={session()!.account}>
<ReplyEditor <TootComposer
isTyping={isInTyping()} isTyping={isInTyping()}
onTypingChange={setInTyping} onTypingChange={setInTyping}
mentions={defaultMentions()} mentions={defaultMentions()}

View file

@ -5,6 +5,8 @@ import {
onMount, onMount,
Show, Show,
type Component, type Component,
type JSX,
type Ref,
} from "solid-js"; } from "solid-js";
import Scaffold from "../material/Scaffold"; import Scaffold from "../material/Scaffold";
import { import {
@ -177,15 +179,26 @@ const TootLanguagePickerDialog: Component<{
); );
}; };
const ReplyEditor: Component<{ function randomChoose<T extends any[]>(
profile: Account; rn: number,
replyToDisplayName: string; K: T,
): T extends Array<infer E> ? E : never {
const idx = Math.round(rn * K.length);
return K[idx];
}
const TootComposer: Component<{
ref?: Ref<HTMLDivElement>;
style?: JSX.CSSProperties;
profile?: Account;
replyToDisplayName?: string;
mentions?: readonly string[]; mentions?: readonly string[];
isTyping?: boolean; isTyping?: boolean;
onTypingChange: (value: boolean) => void; onTypingChange: (value: boolean) => void;
client?: mastodon.rest.Client; client?: mastodon.rest.Client;
inReplyToId?: string; inReplyToId?: string;
onSent?: (status: mastodon.v1.Status) => void; onSent?: (status: mastodon.v1.Status) => void;
inputProps?: JSX.TextareaHTMLAttributes<HTMLTextAreaElement>;
}> = (props) => { }> = (props) => {
let inputRef: HTMLTextAreaElement; let inputRef: HTMLTextAreaElement;
let sendKey: string | undefined; let sendKey: string | undefined;
@ -223,6 +236,7 @@ const ReplyEditor: Component<{
top: "var(--scaffold-topbar-height, 0)", top: "var(--scaffold-topbar-height, 0)",
bottom: "var(--safe-area-inset-bottom, 0)", bottom: "var(--safe-area-inset-bottom, 0)",
"z-index": 2, "z-index": 2,
...props.style,
} }
: undefined; : undefined;
@ -274,20 +288,31 @@ const ReplyEditor: Component<{
return ( return (
<div <div
ref={props.ref}
class={tootComposers.composer} class={tootComposers.composer}
style={containerStyle()} style={containerStyle()}
onClick={(e) => inputRef.focus()} onClick={(e) => inputRef.focus()}
> >
<div class={tootComposers.replyInput}> <div class={tootComposers.replyInput}>
<Show when={props.profile}>
<Avatar <Avatar
src={props.profile.inf?.avatar} src={props.profile!.inf?.avatar}
sx={{ marginLeft: "-0.25em" }} sx={{ marginLeft: "-0.25em" }}
/> />
</Show>
<textarea <textarea
ref={inputRef!} ref={inputRef!}
placeholder={`Reply to ${props.replyToDisplayName}...`} placeholder={
props.replyToDisplayName
? `Reply to ${props.replyToDisplayName}...`
: randomChoose(Math.random(), [
"What's happening?",
"What do your think?",
])
}
style={{ width: "100%", border: "none" }} style={{ width: "100%", border: "none" }}
disabled={sending()} disabled={sending()}
{...props.inputProps}
></textarea> ></textarea>
<Show when={props.client}> <Show when={props.client}>
<Show <Show
@ -351,4 +376,4 @@ const ReplyEditor: Component<{
); );
}; };
export default ReplyEditor; export default TootComposer;