From 5742932c8609dee1de254a5f0bee5f957592408c Mon Sep 17 00:00:00 2001 From: thislight Date: Thu, 26 Dec 2024 22:06:15 +0800 Subject: [PATCH] fix #35: pass correct value to TootActionGroup * add createDefaultTootEnv --- src/accounts/stores.ts | 4 +- src/masto/clients.ts | 6 +- src/profiles/Profile.tsx | 14 +- src/timelines/RegularToot.tsx | 104 +++++++++++++- src/timelines/TootBottomSheet.tsx | 181 +++++++----------------- src/timelines/TootList.tsx | 97 +------------ src/timelines/toots/TootActionGroup.tsx | 8 +- 7 files changed, 179 insertions(+), 235 deletions(-) diff --git a/src/accounts/stores.ts b/src/accounts/stores.ts index 6a9fdca..006ea9e 100644 --- a/src/accounts/stores.ts +++ b/src/accounts/stores.ts @@ -26,8 +26,8 @@ export type Account = AccountKey & { inf?: mastodon.v1.AccountCredentials; }; -export function isAccount(object: AccountKey) { - return !!(object as Record)["tokenType"]; +export function isAccount(object: RemoteServer) { + return isAccountKey(object) && !!(object as Record)["tokenType"]; } export const $accounts = persistentAtom("accounts", [], { diff --git a/src/masto/clients.ts b/src/masto/clients.ts index 35789ef..7cd22e7 100644 --- a/src/masto/clients.ts +++ b/src/masto/clients.ts @@ -7,7 +7,7 @@ import { untrack, useContext, } from "solid-js"; -import { Account } from "../accounts/stores"; +import { Account, type RemoteServer } from "../accounts/stores"; import { createRestAPIClient, mastodon } from "masto"; import { useLocation } from "@solidjs/router"; import { useNavigator } from "~platform/StackedRouter"; @@ -131,8 +131,8 @@ export function useSessionForAcctStr(acct: Accessor) { return ( authedSession ?? { client: createUnauthorizedClient(inputSite), - account: { site: inputSite }, // TODO: we need some security checks here? - } + account: { site: inputSite } as RemoteServer, // TODO: we need some security checks here? + } as const ); }); } diff --git a/src/profiles/Profile.tsx b/src/profiles/Profile.tsx index 6e24cbf..b743acd 100644 --- a/src/profiles/Profile.tsx +++ b/src/profiles/Profile.tsx @@ -1,6 +1,5 @@ import { catchError, - createRenderEffect, createResource, createSignal, createUniqueId, @@ -14,7 +13,6 @@ import { } from "solid-js"; import Scaffold from "~material/Scaffold"; import { - AppBar, Avatar, Button, Checkbox, @@ -26,7 +24,6 @@ import { ListItemSecondaryAction, ListItemText, MenuItem, - Toolbar, } from "@suid/material"; import { Close, @@ -63,6 +60,7 @@ import { default as ItemSelectionProvider, } from "../timelines/toots/ItemSelectionProvider"; import AppTopBar from "~material/AppTopBar"; +import type { Account } from "../accounts/stores"; const Profile: Component = () => { const { pop } = useNavigator(); @@ -117,7 +115,7 @@ const Profile: Component = () => { }; const isCurrentSessionProfile = () => { - return session().account?.inf?.url === profile()?.url; + return (session().account as Account).inf?.url === profile()?.url; }; const [recentTootFilter, setRecentTootFilter] = createSignal({ @@ -172,8 +170,8 @@ const Profile: Component = () => { const sessionDisplayName = createMemo(() => resolveCustomEmoji( - session().account?.inf?.displayName || "", - session().account?.inf?.emojis ?? [], + (session().account as Account).inf?.displayName || "", + (session().account as Account).inf?.emojis ?? [], ), ); @@ -244,7 +242,7 @@ const Profile: Component = () => { - + @@ -367,7 +365,7 @@ const Profile: Component = () => { aria-label={`${relationship()?.following ? "Unfollow" : "Follow"} on your home timeline`} > - + void; @@ -52,6 +53,107 @@ export function useTootEnv() { return env; } +/** + * Create default toot env. + * + * This function does not provides the "reply" action. + */ +export function createDefaultTootEnv( + client: () => mastodon.rest.Client | undefined, + setToot: (id: string, status: mastodon.v1.Status) => void, +): TootEnv { + return { + async bookmark(status: mastodon.v1.Status) { + const c = client(); + if (!c) return; + + const result = await (status.bookmarked + ? c.v1.statuses.$select(status.id).unbookmark() + : c.v1.statuses.$select(status.id).bookmark()); + + setToot(result.id, result); + }, + + async boost(status: mastodon.v1.Status) { + const c = client(); + if (!c) return; + + vibrate(50); + const rootStatus = status.reblog ? status.reblog : status; + const reblogged = rootStatus.reblogged; + if (status.reblog) { + setToot(status.id, { + ...status, + reblog: { ...status.reblog!, reblogged: !reblogged }, + reblogged: !reblogged, + }); + } else { + setToot(status.id, { + ...status, + reblogged: !reblogged, + }); + } + // modified the original + + const result = reblogged + ? await c.v1.statuses.$select(status.id).unreblog() + : (await c.v1.statuses.$select(status.id).reblog()); + + if (status.reblog && !reblogged) { + // When calling /reblog, the result is the boost object (the actor + // is the calling account); for /unreblog, the result is the original + // toot. So we only do this trick only on the reblogging. + setToot(status.id, { + ...status, + reblog: result.reblog, + }); + } else { + setToot(status.id, result); + } + }, + + async favourite(status: mastodon.v1.Status) { + const c = client(); + if (!c) return; + + const ovalue = status.favourited; + setToot(status.id, { ...status, favourited: !ovalue }); + + const result = ovalue + ? await c.v1.statuses.$select(status.id).unfavourite() + : await c.v1.statuses.$select(status.id).favourite(); + setToot(status.id, result); + }, + + async vote(status: mastodon.v1.Status, votes: readonly number[]) { + const c = client(); + if (!c) return; + + const toot = status.reblog ?? status; + if (!toot.poll) return; + + const npoll = await c.v1.polls.$select(toot.poll.id).votes.create({ + choices: votes, + }); + + if (status.reblog) { + setToot(status.id, { + ...status, + reblog: { + ...status.reblog, + poll: npoll, + }, + }); + } else { + setToot(status.id, { + ...status, + poll: npoll, + }); + } + }, + }; +} + type RegularTootProps = { status: mastodon.v1.Status; actionable?: boolean; @@ -203,7 +305,7 @@ const RegularToot: Component = (oprops) => { class={cardStyle.cardNoPad} style={{ "margin-top": "8px" }} /> - + diff --git a/src/timelines/TootBottomSheet.tsx b/src/timelines/TootBottomSheet.tsx index 08301de..79fed0f 100644 --- a/src/timelines/TootBottomSheet.tsx +++ b/src/timelines/TootBottomSheet.tsx @@ -1,23 +1,17 @@ import { useParams } from "@solidjs/router"; -import { - catchError, - createResource, - Show, - type Component, -} from "solid-js"; +import { catchError, createResource, Show, type Component } from "solid-js"; import Scaffold from "~material/Scaffold"; import { CircularProgress } from "@suid/material"; import { Title } from "~material/typography"; import { useSessionForAcctStr } from "../masto/clients"; import { resolveCustomEmoji } from "../masto/toot"; import RegularToot, { + createDefaultTootEnv, findElementActionable, TootEnvProvider, } from "./RegularToot"; -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 TootComposer from "./TootComposer"; import { useDocumentTitle } from "../utils"; @@ -53,7 +47,7 @@ const TootBottomSheet: Component = (props) => { const [tootContextErrorUncaught, { refetch: refetchContext }] = createResource( - () => [session().client, params.id] as const, + () => [session().client, toot()?.reblog?.id ?? params.id] as const, async ([client, id]) => { return await client.v1.statuses.$select(id).context.fetch(); }, @@ -89,49 +83,10 @@ const TootBottomSheet: Component = (props) => { return s.account ? s : undefined; }; - const onBookmark = async () => { - const status = remoteToot()!; - const client = actSession()!.client; - setRemoteToot( - Object.assign({}, status, { - bookmarked: !status.bookmarked, - }), - ); - const result = await (status.bookmarked - ? client.v1.statuses.$select(status.id).unbookmark() - : client.v1.statuses.$select(status.id).bookmark()); - setRemoteToot(result); - }; - - const onBoost = async () => { - const status = remoteToot()!; - const client = actSession()!.client; - vibrate(50); - setRemoteToot( - Object.assign({}, status, { - reblogged: !status.reblogged, - }), - ); - const result = await (status.reblogged - ? client.v1.statuses.$select(status.id).unreblog() - : client.v1.statuses.$select(status.id).reblog()); - vibrate([20, 30]); - setRemoteToot(result.reblog!); - }; - - const onFav = async () => { - const status = remoteToot()!; - const client = actSession()!.client; - setRemoteToot( - Object.assign({}, status, { - favourited: !status.favourited, - }), - ); - const result = await (status.favourited - ? client.v1.statuses.$select(status.id).favourite() - : client.v1.statuses.$select(status.id).unfavourite()); - setRemoteToot(result); - }; + const mainTootEnv = createDefaultTootEnv( + () => actSession()?.client, + (_, status) => setRemoteToot(status), + ); const defaultMentions = () => { const tootAcct = remoteToot()?.reblog?.account ?? remoteToot()?.account; @@ -145,33 +100,6 @@ const TootBottomSheet: Component = (props) => { return Array.from(new Set(values).keys()); }; - const vote = async (status: mastodon.v1.Status, votes: readonly number[]) => { - const client = session()?.client; - if (!client) return; - - const toot = status.reblog ?? status; - if (!toot.poll) return; - - const npoll = await client.v1.polls.$select(toot.poll.id).votes.create({ - choices: votes, - }); - - if (status.reblog) { - setRemoteToot({ - ...status, - reblog: { - ...status.reblog, - poll: npoll, - }, - }); - } else { - setRemoteToot({ - ...status, - poll: npoll, - }); - } - }; - const handleMainTootClick = ( event: MouseEvent & { currentTarget: HTMLElement }, ) => { @@ -237,58 +165,51 @@ const TootBottomSheet: Component = (props) => { >
- - -
- - - - - -
- - - refetchContext()} - inReplyToId={remoteToot()?.reblog?.id ?? remoteToot()?.id} - /> - - - -
- -
-
- + + +
+ + + + + +
+ + + refetchContext()} + inReplyToId={remoteToot()?.reblog?.id ?? remoteToot()?.id} + /> + + + +
+ +
+
+ { - const client = session()?.client; - if (!client) return; + const tootEnv = createDefaultTootEnv( + () => session()?.client, + (...args) => props.onChangeToot(...args), + ); - const result = await (status.bookmarked - ? client.v1.statuses.$select(status.id).unbookmark() - : client.v1.statuses.$select(status.id).bookmark()); - props.onChangeToot(result.id, result); - }; - - const toggleBoost = async (status: mastodon.v1.Status) => { - const client = session()?.client; - if (!client) return; - - vibrate(50); - const rootStatus = status.reblog ? status.reblog : status; - const reblogged = rootStatus.reblogged; - if (status.reblog) { - props.onChangeToot(status.id, { - ...status, - reblog: { ...status.reblog, reblogged: !reblogged }, - }); - } else { - props.onChangeToot(status.id, { - ...status, - reblogged: !reblogged, - }); - } - - const result = reblogged - ? await client.v1.statuses.$select(status.id).unreblog() - : (await client.v1.statuses.$select(status.id).reblog()).reblog!; - - if (status.reblog) { - props.onChangeToot(status.id, { - ...status, - reblog: result, - }); - } else { - props.onChangeToot(status.id, result); - } - }; - - const toggleFavourite = async (status: mastodon.v1.Status) => { - const client = session()?.client; - if (!client) return; - const ovalue = status.favourited; - props.onChangeToot(status.id, { ...status, favourited: !ovalue }); - - const result = ovalue - ? await client.v1.statuses.$select(status.id).unfavourite() - : await client.v1.statuses.$select(status.id).favourite(); - props.onChangeToot(status.id, result); - }; - - const openFullScreenToot = ( + const openFullScreenToot = async ( toot: mastodon.v1.Status, srcElement: HTMLElement, reply?: boolean, @@ -235,33 +182,6 @@ const TootList: Component<{ openFullScreenToot(status, element, true); }; - const vote = async (status: mastodon.v1.Status, votes: readonly number[]) => { - const client = session()?.client; - if (!client) return; - - const toot = status.reblog ?? status; - if (!toot.poll) return; - - const npoll = await client.v1.polls.$select(toot.poll.id).votes.create({ - choices: votes, - }); - - if (status.reblog) { - props.onChangeToot(status.id, { - ...status, - reblog: { - ...status.reblog, - poll: npoll, - }, - }); - } else { - props.onChangeToot(status.id, { - ...status, - poll: npoll, - }); - } - }; - return ( { @@ -271,11 +191,8 @@ const TootList: Component<{ >
diff --git a/src/timelines/toots/TootActionGroup.tsx b/src/timelines/toots/TootActionGroup.tsx index fef75ef..280de36 100644 --- a/src/timelines/toots/TootActionGroup.tsx +++ b/src/timelines/toots/TootActionGroup.tsx @@ -24,13 +24,19 @@ function isolatedCallback(e: MouseEvent) { e.stopPropagation(); } +/** + * The actions of the toot card. + * + * The `value` must be the original toot (contains `reblog` if + * it's a boost), since the value will be passed to the callbacks. + */ function TootActionGroup(props: { value: T; class?: string; }) { const { reply, boost, favourite, bookmark } = useTootEnv(); let actGrpElement: HTMLDivElement; - const toot = () => props.value; + const toot = () => props.value.reblog ?? props.value; return (