From 6c98f1e78d53b04d660d3af3f0820183a9e64290 Mon Sep 17 00:00:00 2001 From: thislight Date: Wed, 25 Sep 2024 19:30:05 +0800 Subject: [PATCH 01/10] ReplyEditor: added only UI --- src/material/BottomSheet.module.css | 10 +- src/material/BottomSheet.tsx | 25 ++- src/material/Scaffold.tsx | 14 ++ src/timelines/ReplyEditor.tsx | 236 ++++++++++++++++++++++++++ src/timelines/TootBottomSheet.tsx | 140 ++++++++------- src/timelines/TootComposer.module.css | 11 ++ 6 files changed, 363 insertions(+), 73 deletions(-) create mode 100644 src/timelines/ReplyEditor.tsx create mode 100644 src/timelines/TootComposer.module.css diff --git a/src/material/BottomSheet.module.css b/src/material/BottomSheet.module.css index d096fff..6c31a44 100644 --- a/src/material/BottomSheet.module.css +++ b/src/material/BottomSheet.module.css @@ -1,5 +1,7 @@ .bottomSheet { - composes: surface from "material.module.css"; + composes: surface from "./material.module.css"; + composes: cardGutSkip from "./cards.module.css"; + composes: cardNoPad from "./cards.module.css"; border: none; position: absolute; left: 50%; @@ -47,4 +49,10 @@ opacity: 0; } } + + &.bottom { + top: unset; + transform: translateX(-50%); + bottom: 0; + } } diff --git a/src/material/BottomSheet.tsx b/src/material/BottomSheet.tsx index d37a0e2..cb3adc4 100644 --- a/src/material/BottomSheet.tsx +++ b/src/material/BottomSheet.tsx @@ -13,9 +13,12 @@ import { } from "solid-js"; import styles from "./BottomSheet.module.css"; import { useHeroSignal } from "../platform/anim"; +import { makeEventListener } from "@solid-primitives/event-listener"; export type BottomSheetProps = { open?: boolean; + bottomUp?: boolean; + onClose?(reason: "backdrop"): void; }; export const HERO = Symbol("BottomSheet Hero Symbol"); @@ -123,8 +126,28 @@ const BottomSheet: ParentComponent = (props) => { } }); + onMount(() => { + makeEventListener(element, "click", (event) => { + const rect = element.getBoundingClientRect(); + const isInDialog = + rect.top <= event.clientY && + event.clientY <= rect.top + rect.height && + rect.left <= event.clientX && + event.clientX <= rect.left + rect.width; + if (!isInDialog) { + props.onClose?.("backdrop"); + } + }); + }); + return ( - + {ochildren() ?? cache()} ); diff --git a/src/material/Scaffold.tsx b/src/material/Scaffold.tsx index f1f9d34..cebcda7 100644 --- a/src/material/Scaffold.tsx +++ b/src/material/Scaffold.tsx @@ -12,6 +12,7 @@ import { css } from "solid-styled"; interface ScaffoldProps { topbar?: JSX.Element; fab?: JSX.Element; + bottom?: JSX.Element; } const Scaffold: ParentComponent = (props) => { @@ -36,6 +37,16 @@ const Scaffold: ParentComponent = (props) => { right: 40px; z-index: var(--tutu-zidx-nav, auto); } + + .bottom-dock { + position: sticky; + bottom: 0; + left: 0; + right: 0; + z-index: var(--tutu-zidx-nav, auto); + padding-bottom: var(--safe-area-inset-bottom, 0); + + } `; return ( <> @@ -48,6 +59,9 @@ const Scaffold: ParentComponent = (props) => {
{props.fab}
{props.children}
+ +
{props.bottom}
+
); }; diff --git a/src/timelines/ReplyEditor.tsx b/src/timelines/ReplyEditor.tsx new file mode 100644 index 0000000..d40ac77 --- /dev/null +++ b/src/timelines/ReplyEditor.tsx @@ -0,0 +1,236 @@ +import { + createSignal, + createUniqueId, + onMount, + type Component, + type Setter, +} from "solid-js"; +import Scaffold from "../material/Scaffold"; +import { + Avatar, + Button, + IconButton, + List, + ListItemButton, + ListItemIcon, + ListItemSecondaryAction, + ListItemText, + Radio, + Switch, + Divider, +} from "@suid/material"; +import { + ArrowDropDown, + Public as PublicIcon, + Send, + People as PeopleIcon, + ThreeP as ThreePIcon, + ListAlt as ListAltIcon, +} from "@suid/icons-material"; +import type { Account } from "../accounts/stores"; +import tootComposers from "./TootComposer.module.css"; +import { makeEventListener } from "@solid-primitives/event-listener"; +import BottomSheet from "../material/BottomSheet"; + +type TootVisibility = "public" | "unlisted" | "private" | "direct"; + +const TootVisibilityPickerDialog: Component<{ + open?: boolean; + onClose: () => void; + visibility: TootVisibility; + onVisibilityChange: (value: TootVisibility) => void; +}> = (props) => { + type Kind = "public" | "private" | "direct"; + + const kind = () => + props.visibility === "public" || props.visibility === "unlisted" + ? "public" + : props.visibility; + + const setKind = (nv: Kind) => { + if (nv == "public") { + props.onVisibilityChange(discoverable() ? "public" : "unlisted"); + } else { + props.onVisibilityChange(nv); + } + }; + + const discoverable = () => { + return props.visibility === "public"; + } + + const setDiscoverable = (setter: (v: boolean) => boolean) => { + const nval = setter(discoverable()) + props.onVisibilityChange(nval ? "public" : "unlisted"); // trigger change + }; + + + + return ( + + + + + } + > + + + + + + + + + + + + setDiscoverable((x) => !x)} + > + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + ); +}; + +const ReplyEditor: Component<{ + profile: Account; + replyToDisplayName: string; +}> = (props) => { + let inputRef: HTMLTextAreaElement; + const buttonId = createUniqueId(); + const menuId = createUniqueId(); + + const [typing, setTyping] = createSignal(false); + const [visibility, setVisibility] = createSignal("public"); + const [permPicker, setPermPicker] = createSignal(false); + + onMount(() => { + makeEventListener(inputRef, "focus", () => setTyping(true)); + makeEventListener(inputRef, "blur", () => setTyping(false)); + }); + + const containerStyle = () => + typing() + ? { + position: "sticky" as const, + top: "var(--scaffold-topbar-height, 0)", + bottom: "var(--safe-area-inset-bottom, 0)", + "z-index": 1, + } + : undefined; + + const visibilityText = () => { + switch (visibility()) { + case "public": + return "Discoverable"; + case "unlisted": + return "Public"; + case "private": + return "Only Followers"; + case "direct": + return "Only Mentions"; + } + }; + + return ( +
setTyping(true)} + > +
+ + + + + +
+ +
+ +
+ + setPermPicker(false)} + visibility={visibility()} + onVisibilityChange={setVisibility} + /> +
+ ); +}; + +export default ReplyEditor; diff --git a/src/timelines/TootBottomSheet.tsx b/src/timelines/TootBottomSheet.tsx index ff075d3..71f6119 100644 --- a/src/timelines/TootBottomSheet.tsx +++ b/src/timelines/TootBottomSheet.tsx @@ -8,12 +8,16 @@ import { type Component, } from "solid-js"; import Scaffold from "../material/Scaffold"; -import { AppBar, Avatar, CircularProgress, IconButton, Toolbar } from "@suid/material"; +import { + AppBar, + CircularProgress, + IconButton, + Toolbar, +} from "@suid/material"; import { Title } from "../material/typography"; import { ArrowBack as BackIcon, Close as CloseIcon, - Send, } from "@suid/icons-material"; import { createUnauthorizedClient, useSessions } from "../masto/clients"; import { resolveCustomEmoji } from "../masto/toot"; @@ -22,6 +26,8 @@ import type { mastodon } from "masto"; import cards from "../material/cards.module.css"; import { css } from "solid-styled"; import { vibrate } from "../platform/hardware"; +import { createTimeSource, TimeSourceProvider } from "../platform/timesrc"; +import ReplyEditor from "./ReplyEditor"; let cachedEntry: [string, mastodon.v1.Status] | undefined; @@ -35,11 +41,14 @@ function getCache(acct: string, id: string) { } } + + const TootBottomSheet: Component = (props) => { const params = useParams<{ acct: string; id: string }>(); const location = useLocation<{ tootBottomSheetPushedCount?: number }>(); const navigate = useNavigate(); const allSession = useSessions(); + const time = createTimeSource(); const acctText = () => decodeURIComponent(params.acct); const session = () => { const [inputUsername, inputSite] = acctText().split("@", 2); @@ -164,12 +173,6 @@ const TootBottomSheet: Component = (props) => { }; css` - .bottom-dock { - position: sticky; - bottom: 0; - z-index: var(--tutu-zidx-nav); - } - .name :global(img) { max-height: 1em; } @@ -203,74 +206,69 @@ const TootBottomSheet: Component = (props) => { } > - - {(item) => ( - - )} - + + + {(item) => ( + + )} + -
- - - -
+
+ + + +
- -
- -
-
- - - {(item) => ( - - )} - - -
-
- - - - - + + + + +
+
-
+ + + {(item) => ( + + )} + + ); }; 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; +} From a1a587a77f32c490c1c0b12fb1b4388bb43bc939 Mon Sep 17 00:00:00 2001 From: thislight Date: Wed, 25 Sep 2024 21:24:39 +0800 Subject: [PATCH 02/10] ReplyEditor: promote the blur condition up --- src/timelines/ReplyEditor.tsx | 27 ++++++++++++++------------- src/timelines/TootBottomSheet.tsx | 17 +++++++++-------- 2 files changed, 23 insertions(+), 21 deletions(-) diff --git a/src/timelines/ReplyEditor.tsx b/src/timelines/ReplyEditor.tsx index d40ac77..0f146c4 100644 --- a/src/timelines/ReplyEditor.tsx +++ b/src/timelines/ReplyEditor.tsx @@ -1,4 +1,5 @@ import { + batch, createSignal, createUniqueId, onMount, @@ -57,14 +58,12 @@ const TootVisibilityPickerDialog: Component<{ const discoverable = () => { return props.visibility === "public"; - } - - const setDiscoverable = (setter: (v: boolean) => boolean) => { - const nval = setter(discoverable()) - props.onVisibilityChange(nval ? "public" : "unlisted"); // trigger change }; - + const setDiscoverable = (setter: (v: boolean) => boolean) => { + const nval = setter(discoverable()); + props.onVisibilityChange(nval ? "public" : "unlisted"); // trigger change + }; return ( @@ -83,7 +82,7 @@ const TootVisibilityPickerDialog: Component<{
} > - + @@ -155,27 +154,29 @@ const TootVisibilityPickerDialog: Component<{ const ReplyEditor: Component<{ profile: Account; replyToDisplayName: string; + isTyping?: boolean + onTypingChange: (value: boolean) => void }> = (props) => { let inputRef: HTMLTextAreaElement; const buttonId = createUniqueId(); const menuId = createUniqueId(); - const [typing, setTyping] = createSignal(false); + const typing = () => props.isTyping + const setTyping = (v: boolean) => props.onTypingChange(v) const [visibility, setVisibility] = createSignal("public"); const [permPicker, setPermPicker] = createSignal(false); onMount(() => { makeEventListener(inputRef, "focus", () => setTyping(true)); - makeEventListener(inputRef, "blur", () => setTyping(false)); }); const containerStyle = () => - typing() + typing() || permPicker() ? { position: "sticky" as const, top: "var(--scaffold-topbar-height, 0)", bottom: "var(--safe-area-inset-bottom, 0)", - "z-index": 1, + "z-index": 2, } : undefined; @@ -196,7 +197,7 @@ const ReplyEditor: Component<{
setTyping(true)} + onClick={(e) => inputRef.focus()} >
@@ -219,7 +220,7 @@ const ReplyEditor: Component<{ >
diff --git a/src/timelines/TootBottomSheet.tsx b/src/timelines/TootBottomSheet.tsx index 71f6119..4e86928 100644 --- a/src/timelines/TootBottomSheet.tsx +++ b/src/timelines/TootBottomSheet.tsx @@ -3,17 +3,13 @@ import { createEffect, createRenderEffect, createResource, + createSignal, For, Show, type Component, } from "solid-js"; import Scaffold from "../material/Scaffold"; -import { - AppBar, - CircularProgress, - IconButton, - Toolbar, -} from "@suid/material"; +import { AppBar, CircularProgress, IconButton, Toolbar } from "@suid/material"; import { Title } from "../material/typography"; import { ArrowBack as BackIcon, @@ -41,14 +37,13 @@ function getCache(acct: string, id: string) { } } - - const TootBottomSheet: Component = (props) => { const params = useParams<{ acct: string; id: string }>(); const location = useLocation<{ tootBottomSheetPushedCount?: number }>(); const navigate = useNavigate(); const allSession = useSessions(); const time = createTimeSource(); + const [isInTyping, setInTyping] = createSignal(false); const acctText = () => decodeURIComponent(params.acct); const session = () => { const [inputUsername, inputSite] = acctText().split("@", 2); @@ -164,6 +159,10 @@ const TootBottomSheet: Component = (props) => { }; const switchContext = (status: mastodon.v1.Status) => { + if (isInTyping()) { + setInTyping(false); + return; + } setCache(params.acct, status); navigate(`/${params.acct}/${status.id}`, { state: { @@ -240,6 +239,8 @@ const TootBottomSheet: Component = (props) => { From 4cf065fe1f8b0ca1e0006d8bfe0190fe2606ae34 Mon Sep 17 00:00:00 2001 From: thislight Date: Fri, 27 Sep 2024 14:53:28 +0800 Subject: [PATCH 03/10] ReplyEditor: added language picker --- src/timelines/ChooseTootLang.tsx | 86 ++++++++++++++++++++++++++++++++ src/timelines/ReplyEditor.tsx | 32 ++++++++++-- src/timelines/i18n/en.json | 3 ++ src/timelines/i18n/zh-Hans.json | 3 ++ 4 files changed, 120 insertions(+), 4 deletions(-) create mode 100644 src/timelines/ChooseTootLang.tsx create mode 100644 src/timelines/i18n/en.json create mode 100644 src/timelines/i18n/zh-Hans.json diff --git a/src/timelines/ChooseTootLang.tsx b/src/timelines/ChooseTootLang.tsx new file mode 100644 index 0000000..8405bb1 --- /dev/null +++ b/src/timelines/ChooseTootLang.tsx @@ -0,0 +1,86 @@ +import { + For, + onMount, + type Component, + type JSX, +} from "solid-js"; +import Scaffold from "../material/Scaffold"; +import { + AppBar, + IconButton, + List, + ListItemButton, + ListItemSecondaryAction, + ListItemText, + Radio, + Toolbar, +} from "@suid/material"; +import { Close as CloseIcon } from "@suid/icons-material"; +import iso639_1 from "iso-639-1"; +import { createTranslator } from "../platform/i18n"; +import { Title } from "../material/typography"; + +type ChooseTootLangProps = { + code: string; + onCodeChange: (ncode?: string) => void; + onClose?: JSX.EventHandlerUnion; +}; + +const ChooseTootLang: Component = (props) => { + let listRef: HTMLUListElement; + const [t] = createTranslator( + (code) => + import(`./i18n/${code}.json`) as Promise<{ + default: Record; + }>, + ); + + onMount(() => { + const code = props.code; + const el = listRef.querySelector(`[data-langcode="${code}"]`); + if (el) { + el.scrollIntoView({ behavior: "auto" }); + } + }); + + return ( + + + + + + {t("Choose Language")} + + + } + > + + + {(code) => ( + props.onCodeChange(code)} + > + {iso639_1.getNativeName(code)} + + + + + )} + + + + ); +}; + +export default ChooseTootLang; diff --git a/src/timelines/ReplyEditor.tsx b/src/timelines/ReplyEditor.tsx index 0f146c4..51259b9 100644 --- a/src/timelines/ReplyEditor.tsx +++ b/src/timelines/ReplyEditor.tsx @@ -27,11 +27,16 @@ import { People as PeopleIcon, ThreeP as ThreePIcon, ListAlt as ListAltIcon, + Visibility, + Translate, } from "@suid/icons-material"; import type { Account } from "../accounts/stores"; import tootComposers from "./TootComposer.module.css"; import { makeEventListener } from "@solid-primitives/event-listener"; import BottomSheet from "../material/BottomSheet"; +import { useLanguage } from "../platform/i18n"; +import iso639_1 from "iso-639-1"; +import ChooseTootLang from "./ChooseTootLang"; type TootVisibility = "public" | "unlisted" | "private" | "direct"; @@ -154,17 +159,19 @@ const TootVisibilityPickerDialog: Component<{ const ReplyEditor: Component<{ profile: Account; replyToDisplayName: string; - isTyping?: boolean - onTypingChange: (value: boolean) => void + isTyping?: boolean; + onTypingChange: (value: boolean) => void; }> = (props) => { let inputRef: HTMLTextAreaElement; const buttonId = createUniqueId(); const menuId = createUniqueId(); - const typing = () => props.isTyping - const setTyping = (v: boolean) => props.onTypingChange(v) + const typing = () => props.isTyping; + const setTyping = (v: boolean) => props.onTypingChange(v); const [visibility, setVisibility] = createSignal("public"); const [permPicker, setPermPicker] = createSignal(false); + const [language, setLanguage] = createSignal(useLanguage()().split("-")[0]); + const [langPickerOpen, setLangPickerOpen] = createSignal(false); onMount(() => { makeEventListener(inputRef, "focus", () => setTyping(true)); @@ -218,7 +225,13 @@ const ReplyEditor: Component<{ "margin-top": "8px", }} > + @@ -230,6 +243,17 @@ const ReplyEditor: Component<{ visibility={visibility()} onVisibilityChange={setVisibility} /> + + setLangPickerOpen(false)} + > + +
); }; diff --git a/src/timelines/i18n/en.json b/src/timelines/i18n/en.json new file mode 100644 index 0000000..f9bed6a --- /dev/null +++ b/src/timelines/i18n/en.json @@ -0,0 +1,3 @@ +{ + "Choose Language": "Choose Language" +} \ No newline at end of file diff --git a/src/timelines/i18n/zh-Hans.json b/src/timelines/i18n/zh-Hans.json new file mode 100644 index 0000000..06d6bfb --- /dev/null +++ b/src/timelines/i18n/zh-Hans.json @@ -0,0 +1,3 @@ +{ + "Choose Language": "选择语言" +} \ No newline at end of file From e5b220aef574a269eabf868b556fafb2c7523602 Mon Sep 17 00:00:00 2001 From: thislight Date: Fri, 27 Sep 2024 18:04:09 +0800 Subject: [PATCH 04/10] TootBottomSheet: fix the skipped animation --- src/material/BottomSheet.module.css | 6 ++++ src/material/BottomSheet.tsx | 38 +++++++++++----------- src/timelines/ChooseTootLang.tsx | 2 +- src/timelines/ReplyEditor.tsx | 49 ++++++++++++++++++++++------- 4 files changed, 64 insertions(+), 31 deletions(-) diff --git a/src/material/BottomSheet.module.css b/src/material/BottomSheet.module.css index 6c31a44..adddddb 100644 --- a/src/material/BottomSheet.module.css +++ b/src/material/BottomSheet.module.css @@ -44,10 +44,16 @@ &.animated { position: absolute; transform: none; + overflow: hidden; + will-change: width, height, top, left; &::backdrop { opacity: 0; } + + & * { + overflow: hidden; + } } &.bottom { diff --git a/src/material/BottomSheet.tsx b/src/material/BottomSheet.tsx index cb3adc4..3191426 100644 --- a/src/material/BottomSheet.tsx +++ b/src/material/BottomSheet.tsx @@ -55,7 +55,7 @@ const BottomSheet: ParentComponent = (props) => { createEffect(() => { if (props.open) { if (!element.open && !pending()) { - animatedOpen(); + requestAnimationFrame(animatedOpen); setCache(ochildren()); } } else { @@ -66,15 +66,16 @@ const BottomSheet: ParentComponent = (props) => { } }); + const onClose = () => { + element.close(); + setHero(); + }; + const animatedClose = () => { const endRect = hero(); if (endRect) { const startRect = element.getBoundingClientRect(); const animation = animateHero(startRect, endRect, element, true); - const onClose = () => { - element.close(); - setHero(); - }; animation.addEventListener("finish", onClose); animation.addEventListener("cancel", onClose); } else { @@ -126,19 +127,19 @@ const BottomSheet: ParentComponent = (props) => { } }); - onMount(() => { - makeEventListener(element, "click", (event) => { - const rect = element.getBoundingClientRect(); - const isInDialog = - rect.top <= event.clientY && - event.clientY <= rect.top + rect.height && - rect.left <= event.clientX && - event.clientX <= rect.left + rect.width; - if (!isInDialog) { - props.onClose?.("backdrop"); - } - }); - }); + const onDialogClick = ( + event: MouseEvent & { currentTarget: HTMLDialogElement }, + ) => { + const rect = event.currentTarget.getBoundingClientRect(); + const isInDialog = + rect.top <= event.clientY && + event.clientY <= rect.top + rect.height && + rect.left <= event.clientX && + event.clientX <= rect.left + rect.width; + if (!isInDialog) { + props.onClose?.("backdrop"); + } + }; return ( = (props) => { [styles.bottomSheet]: true, [styles.bottom]: props.bottomUp, }} + onClick={onDialogClick} ref={element!} > {ochildren() ?? cache()} diff --git a/src/timelines/ChooseTootLang.tsx b/src/timelines/ChooseTootLang.tsx index 8405bb1..9d5cbb7 100644 --- a/src/timelines/ChooseTootLang.tsx +++ b/src/timelines/ChooseTootLang.tsx @@ -22,7 +22,7 @@ import { Title } from "../material/typography"; type ChooseTootLangProps = { code: string; - onCodeChange: (ncode?: string) => void; + onCodeChange: (ncode: string) => void; onClose?: JSX.EventHandlerUnion; }; diff --git a/src/timelines/ReplyEditor.tsx b/src/timelines/ReplyEditor.tsx index 51259b9..0d9995a 100644 --- a/src/timelines/ReplyEditor.tsx +++ b/src/timelines/ReplyEditor.tsx @@ -1,8 +1,12 @@ import { batch, + createEffect, + createRenderEffect, createSignal, createUniqueId, + lazy, onMount, + Show, type Component, type Setter, } from "solid-js"; @@ -156,6 +160,25 @@ const TootVisibilityPickerDialog: Component<{ ); }; +const TootLanguagePickerDialog: Component<{ + open?: boolean; + onClose: () => void; + code: string; + onCodeChange: (nval: string) => void; +}> = (props) => { + return ( + + + + + + ); +}; + const ReplyEditor: Component<{ profile: Account; replyToDisplayName: string; @@ -163,15 +186,19 @@ const ReplyEditor: Component<{ onTypingChange: (value: boolean) => void; }> = (props) => { let inputRef: HTMLTextAreaElement; - const buttonId = createUniqueId(); - const menuId = createUniqueId(); const typing = () => props.isTyping; const setTyping = (v: boolean) => props.onTypingChange(v); const [visibility, setVisibility] = createSignal("public"); const [permPicker, setPermPicker] = createSignal(false); - const [language, setLanguage] = createSignal(useLanguage()().split("-")[0]); + const [language, setLanguage] = createSignal("en"); const [langPickerOpen, setLangPickerOpen] = createSignal(false); + const appLanguage = useLanguage(); + + createEffect(() => { + const lang = appLanguage().split("-")[0]; + setLanguage(lang); + }); onMount(() => { makeEventListener(inputRef, "focus", () => setTyping(true)); @@ -223,6 +250,8 @@ const ReplyEditor: Component<{ display: "flex", "justify-content": "flex-end", "margin-top": "8px", + gap: "16px", + "flex-flow": "row wrap" }} > - - - + +
+ + +
- setPermPicker(false)} - visibility={visibility()} - onVisibilityChange={setVisibility} - /> + setPermPicker(false)} + visibility={visibility()} + onVisibilityChange={setVisibility} + /> - setLangPickerOpen(false)} - code={language()} - onCodeChange={setLanguage} - /> + setLangPickerOpen(false)} + code={language()} + onCodeChange={setLanguage} + /> +
); }; diff --git a/src/timelines/TootBottomSheet.tsx b/src/timelines/TootBottomSheet.tsx index 4e86928..87d1bd1 100644 --- a/src/timelines/TootBottomSheet.tsx +++ b/src/timelines/TootBottomSheet.tsx @@ -270,6 +270,7 @@ const TootBottomSheet: Component = (props) => { )}
+
); }; From 74770649c388eadb3d361f39709faa57a6ae219d Mon Sep 17 00:00:00 2001 From: thislight Date: Sat, 28 Sep 2024 14:39:20 +0800 Subject: [PATCH 08/10] ReplyEditor: supports reply --- src/platform/polyfills.ts | 16 +++++- src/timelines/MediaAttachmentGrid.tsx | 25 ++++++--- src/timelines/ReplyEditor.tsx | 78 +++++++++++++++++++++++++-- src/timelines/TootBottomSheet.tsx | 22 ++++++-- 4 files changed, 126 insertions(+), 15 deletions(-) diff --git a/src/platform/polyfills.ts b/src/platform/polyfills.ts index 6897af7..0e9d53e 100644 --- a/src/platform/polyfills.ts +++ b/src/platform/polyfills.ts @@ -2,7 +2,21 @@ //! It recommended to include the module by