diff --git a/src/timelines/RegularToot.tsx b/src/timelines/RegularToot.tsx index 68c0f72..3ddaeb3 100644 --- a/src/timelines/RegularToot.tsx +++ b/src/timelines/RegularToot.tsx @@ -7,6 +7,8 @@ import { createRenderEffect, createSignal, type Setter, + createContext, + useContext, } from "solid-js"; import tootStyle from "./toot.module.css"; import { formatRelative, parseISO } from "date-fns"; @@ -38,27 +40,38 @@ import BoostIcon from "./toots/BoostIcon"; import PreviewCard from "./toots/PreviewCard"; import TootPoll from "./toots/TootPoll"; -type TootActionGroupProps = { - onRetoot?: (value: T) => void; - onFavourite?: (value: T) => void; - onBookmark?: (value: T) => void; - onReply?: ( - value: T, +export type TootEnv = { + boost: (value: mastodon.v1.Status) => void; + favourite: (value: mastodon.v1.Status) => void; + bookmark: (value: mastodon.v1.Status) => void; + reply?: ( + value: mastodon.v1.Status, event: MouseEvent & { currentTarget: HTMLButtonElement }, ) => void; + vote: ( + status: mastodon.v1.Status, + votes: readonly number[], + ) => void | Promise; }; +const TootEnvContext = /* @__PURE__ */ createContext(); + +export const TootEnvProvider = TootEnvContext.Provider; + +export function useTootEnv() { + const env = useContext(TootEnvContext); + if (!env) { + throw new TypeError("environment not found, use TootEnvProvider to provide") + } + return env +} + type RegularTootProps = { status: mastodon.v1.Status; actionable?: boolean; evaluated?: boolean; thread?: "top" | "bottom" | "middle"; - - onVote?: (value: { - status: mastodon.v1.Status; - votes: readonly number[]; - }) => void | Promise; -} & TootActionGroupProps & +} & JSX.HTMLElementTags["article"]; function isolatedCallback(e: MouseEvent) { @@ -79,8 +92,9 @@ export function findRootToot(element: HTMLElement) { } function TootActionGroup( - props: TootActionGroupProps & { value: T }, + props: { value: T }, ) { + const {reply, boost, favourite, bookmark} = useTootEnv() let actGrpElement: HTMLDivElement; const toot = () => props.value; return ( @@ -89,10 +103,10 @@ function TootActionGroup( class={tootStyle.tootBottomActionGrp} onClick={isolatedCallback} > - + @@ -243,11 +257,10 @@ function onToggleReveal(setValue: Setter, event: Event) { */ const RegularToot: Component = (props) => { let rootRef: HTMLElement; - const [managed, managedActionGroup, pollProps, rest] = splitProps( + const {vote} = useTootEnv() + const [managed, rest] = splitProps( props, ["status", "lang", "class", "actionable", "evaluated", "thread"], - ["onRetoot", "onFavourite", "onBookmark", "onReply"], - ["onVote"], ); const now = useTimeSource(); const status = () => managed.status; @@ -361,18 +374,8 @@ const RegularToot: Component = (props) => { pollProps.onVote?.({ status: status(), votes })} + value={toot().poll!} + status={toot()} /> @@ -380,7 +383,7 @@ const RegularToot: Component = (props) => { class={cardStyle.cardNoPad} style={{ "margin-top": "8px" }} /> - + diff --git a/src/timelines/TootBottomSheet.tsx b/src/timelines/TootBottomSheet.tsx index d38d34f..09465db 100644 --- a/src/timelines/TootBottomSheet.tsx +++ b/src/timelines/TootBottomSheet.tsx @@ -13,7 +13,10 @@ import { Title } from "~material/typography"; import { Close as CloseIcon } from "@suid/icons-material"; import { useSessionForAcctStr } from "../masto/clients"; import { resolveCustomEmoji } from "../masto/toot"; -import RegularToot, { findElementActionable } from "./RegularToot"; +import RegularToot, { + findElementActionable, + TootEnvProvider, +} from "./RegularToot"; import type { mastodon } from "masto"; import cards from "~material/cards.module.css"; import { css } from "solid-styled"; @@ -169,6 +172,33 @@ 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 }, ) => { @@ -255,23 +285,29 @@ const TootBottomSheet: Component = (props) => {
- + > + +
diff --git a/src/timelines/TootList.tsx b/src/timelines/TootList.tsx index e72c360..3408e0e 100644 --- a/src/timelines/TootList.tsx +++ b/src/timelines/TootList.tsx @@ -14,6 +14,7 @@ import { setCache as setTootBottomSheetCache } from "./TootBottomSheet"; import RegularToot, { findElementActionable, findRootToot, + TootEnvProvider, } from "./RegularToot"; import cardStyle from "~material/cards.module.css"; import type { ThreadNode } from "../masto/timelines"; @@ -232,13 +233,10 @@ const TootList: Component<{ openFullScreenToot(status, element, true); }; - const vote = async ({ - status, - votes, - }: { - status: mastodon.v1.Status; - votes: readonly number[]; - }) => { + const vote = async ( + status: mastodon.v1.Status, + votes: readonly number[] + ) => { const client = session()?.client; if (!client) return; @@ -263,7 +261,6 @@ const TootList: Component<{ poll: npoll, }); } - }; return ( @@ -273,49 +270,50 @@ const TootList: Component<{ return

Oops: {String(err)}

; }} > -
- - {(threadId, threadIdx) => { - const thread = createMemo(() => - props.onUnknownThread(threadId())?.reverse(), - ); + +
+ + {(threadId, threadIdx) => { + const thread = createMemo(() => + props.onUnknownThread(threadId())?.reverse(), + ); - const threadLength = () => thread()?.length ?? 0; + const threadLength = () => thread()?.length ?? 0; - return ( - - {(threadNode, index) => { - const status = () => threadNode().value; + return ( + + {(threadNode, index) => { + const status = () => threadNode().value; - return ( - 1 - ? positionTootInThread(index, threadLength()) - : undefined - } - class={cardStyle.card} - evaluated={checkIsExpended(status())} - actionable={checkIsExpended(status())} - onBookmark={onBookmark} - onRetoot={toggleBoost} - onFavourite={toggleFavourite} - onReply={reply} - onVote={vote} - onClick={[onItemClick, status()]} - /> - ); - }} - - ); - }} - -
+ return ( + 1 + ? positionTootInThread(index, threadLength()) + : undefined + } + class={cardStyle.card} + evaluated={checkIsExpended(status())} + actionable={checkIsExpended(status())} + onClick={[onItemClick, status()]} + /> + ); + }} +
+ ); + }} + +
+ ); }; diff --git a/src/timelines/toots/TootPoll.tsx b/src/timelines/toots/TootPoll.tsx index ce972b0..a148fcb 100644 --- a/src/timelines/toots/TootPoll.tsx +++ b/src/timelines/toots/TootPoll.tsx @@ -28,21 +28,17 @@ import { useTimeSource } from "~platform/timesrc"; import { useDateFnLocale } from "~platform/i18n"; import TootPollDialog from "./TootPollDialog"; import { ANIM_CURVE_STD } from "~material/theme"; +import { useTootEnv } from "../RegularToot"; type TootPollProps = { - options: Readonly; - multiple?: boolean; - votesCount: number; - expired?: boolean; - expiredAt?: Date; - voted?: boolean; - ownVotes?: readonly number[]; - - onVote(votes: readonly number[]): void | Promise; + value: mastodon.v1.Poll + status: mastodon.v1.Status }; const TootPoll: Component = (props) => { let list: HTMLUListElement; + const {vote}= useTootEnv() + const now = useTimeSource(); const dateFnLocale = useDateFnLocale(); const [mustShowResult, setMustShowResult] = createSignal(); @@ -50,24 +46,26 @@ const TootPoll: Component = (props) => { const [initialVote, setInitialVote] = createSignal(0); + const poll = () => props.value + const isShowResult = () => { const n = mustShowResult(); if (typeof n !== "undefined") { return n; } - return props.expired || props.voted; + return poll().expired || poll().voted; }; const isOwnVote = createSelector( - () => props.ownVotes, + () => poll().ownVotes, (idx: number, votes) => votes?.includes(idx) || false, ); const openVote = (i: number, event: Event) => { event.stopPropagation(); - if (props.expired || props.voted) { + if (poll().expired || poll().voted) { return; } @@ -100,13 +98,13 @@ const TootPoll: Component = (props) => { return (
- {props.votesCount} votes in total - + {poll().votesCount} votes in total + Poll is ended
- + {(option, index) => { return ( <> @@ -140,7 +138,7 @@ const TootPoll: Component = (props) => { = (props) => { - + - {isBefore(now(), props.expiredAt!) ? "Expire in" : "Expired"} + {isBefore(now(), poll().expiresAt!) ? "Expire in" : "Expired"} -