Compare commits
	
		
			No commits in common. "bea1d6abfa47b1cbfbca3b8a02a9d18c9a470d70" and "5b72160cdb79f73e0c379430ff8024ec900a781b" have entirely different histories.
		
	
	
		
			bea1d6abfa
			...
			5b72160cdb
		
	
		
					 9 changed files with 112 additions and 515 deletions
				
			
		| 
						 | 
					@ -24,7 +24,9 @@ import {
 | 
				
			||||||
  ResultDispatcher,
 | 
					  ResultDispatcher,
 | 
				
			||||||
  type JSONRPC,
 | 
					  type JSONRPC,
 | 
				
			||||||
} from "./serviceworker/workerrpc.js";
 | 
					} from "./serviceworker/workerrpc.js";
 | 
				
			||||||
import { Service } from "./serviceworker/services.js";
 | 
					import {
 | 
				
			||||||
 | 
					  Service
 | 
				
			||||||
 | 
					} from "./serviceworker/services.js"
 | 
				
			||||||
import { makeEventListener } from "@solid-primitives/event-listener";
 | 
					import { makeEventListener } from "@solid-primitives/event-listener";
 | 
				
			||||||
import { ServiceWorkerProvider } from "./platform/host.js";
 | 
					import { ServiceWorkerProvider } from "./platform/host.js";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -39,7 +41,6 @@ const MotionSettings = lazy(() => import("./settings/Motions.js"));
 | 
				
			||||||
const LanguageSettings = lazy(() => import("./settings/Language.js"));
 | 
					const LanguageSettings = lazy(() => import("./settings/Language.js"));
 | 
				
			||||||
const RegionSettings = lazy(() => import("./settings/Region.jsx"));
 | 
					const RegionSettings = lazy(() => import("./settings/Region.jsx"));
 | 
				
			||||||
const UnexpectedError = lazy(() => import("./UnexpectedError.js"));
 | 
					const UnexpectedError = lazy(() => import("./UnexpectedError.js"));
 | 
				
			||||||
const Profile = lazy(() => import("./profiles/Profile.js"));
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
const Routing: Component = () => {
 | 
					const Routing: Component = () => {
 | 
				
			||||||
  return (
 | 
					  return (
 | 
				
			||||||
| 
						 | 
					@ -52,8 +53,7 @@ const Routing: Component = () => {
 | 
				
			||||||
          <Route path="/region" component={RegionSettings}></Route>
 | 
					          <Route path="/region" component={RegionSettings}></Route>
 | 
				
			||||||
          <Route path="/motions" component={MotionSettings}></Route>
 | 
					          <Route path="/motions" component={MotionSettings}></Route>
 | 
				
			||||||
        </Route>
 | 
					        </Route>
 | 
				
			||||||
        <Route path="/:acct/toot/:id" component={TootBottomSheet}></Route>
 | 
					        <Route path="/:acct/:id" component={TootBottomSheet}></Route>
 | 
				
			||||||
        <Route path="/:acct/profile/:id" component={Profile}></Route>
 | 
					 | 
				
			||||||
      </Route>
 | 
					      </Route>
 | 
				
			||||||
      <Route path={"/accounts"}>
 | 
					      <Route path={"/accounts"}>
 | 
				
			||||||
        <Route path={"/sign-in"} component={AccountSignIn} />
 | 
					        <Route path={"/sign-in"} component={AccountSignIn} />
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1,9 +1,9 @@
 | 
				
			||||||
import {
 | 
					import {
 | 
				
			||||||
  Accessor,
 | 
					  Accessor,
 | 
				
			||||||
  createContext,
 | 
					  createContext,
 | 
				
			||||||
  createMemo,
 | 
					 | 
				
			||||||
  createRenderEffect,
 | 
					  createRenderEffect,
 | 
				
			||||||
  createResource,
 | 
					  createResource,
 | 
				
			||||||
 | 
					  Signal,
 | 
				
			||||||
  useContext,
 | 
					  useContext,
 | 
				
			||||||
} from "solid-js";
 | 
					} from "solid-js";
 | 
				
			||||||
import { Account } from "../accounts/stores";
 | 
					import { Account } from "../accounts/stores";
 | 
				
			||||||
| 
						 | 
					@ -76,60 +76,3 @@ function useSessionsRaw() {
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
  return store;
 | 
					  return store;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					 | 
				
			||||||
const DefaultSessionContext = /* @__PURE__ */ createContext<Accessor<number>>(() => 0)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
export const DefaultSessionProvider = DefaultSessionContext.Provider;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
/**
 | 
					 | 
				
			||||||
 * Return the default session (the first session).
 | 
					 | 
				
			||||||
 *
 | 
					 | 
				
			||||||
 * This function may return `undefined`, but it will try to redirect the user to the sign in.
 | 
					 | 
				
			||||||
 */
 | 
					 | 
				
			||||||
export function useDefaultSession() {
 | 
					 | 
				
			||||||
  const sessions = useSessions()
 | 
					 | 
				
			||||||
  const sessionIndex = useContext(DefaultSessionContext)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  return () => {
 | 
					 | 
				
			||||||
    if (sessions().length > 0) {
 | 
					 | 
				
			||||||
      return sessions()[sessionIndex()]
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
/**
 | 
					 | 
				
			||||||
 * Get a session for the specific acct string.
 | 
					 | 
				
			||||||
 *
 | 
					 | 
				
			||||||
 * Acct string is a string in the pattern of `{username}@{site_with_protocol}`,
 | 
					 | 
				
			||||||
 * like `@thislight@https://mastodon.social`, can be used to identify (tempoarily)
 | 
					 | 
				
			||||||
 * an session on the tutu instance.
 | 
					 | 
				
			||||||
 *
 | 
					 | 
				
			||||||
 * The `site_with_protocol` is required.
 | 
					 | 
				
			||||||
 *
 | 
					 | 
				
			||||||
 * - If the username is present, the session matches the username and the site is returned; or,
 | 
					 | 
				
			||||||
 * - If the username is not present, any session on the site is returned; or,
 | 
					 | 
				
			||||||
 * - If no available session available for the pattern, an unauthorised session is returned.
 | 
					 | 
				
			||||||
 *
 | 
					 | 
				
			||||||
 * In an unauthorised session, the `.account` is `undefined` and the `client` is an
 | 
					 | 
				
			||||||
 * unauthorised client for the site. This client may not available for some operations.
 | 
					 | 
				
			||||||
 */
 | 
					 | 
				
			||||||
export function useSessionForAcctStr(acct: Accessor<string>) {
 | 
					 | 
				
			||||||
  const allSessions = useSessions()
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  return createMemo(() => {
 | 
					 | 
				
			||||||
    const [inputUsername, inputSite] = acct().split("@", 2);
 | 
					 | 
				
			||||||
    const authedSession = allSessions().find(
 | 
					 | 
				
			||||||
      (x) =>
 | 
					 | 
				
			||||||
        x.account.site === inputSite &&
 | 
					 | 
				
			||||||
        x.account.inf?.username === inputUsername,
 | 
					 | 
				
			||||||
    );
 | 
					 | 
				
			||||||
    return (
 | 
					 | 
				
			||||||
      authedSession ?? {
 | 
					 | 
				
			||||||
        client: createUnauthorizedClient(inputSite),
 | 
					 | 
				
			||||||
        account: undefined,
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
    );
 | 
					 | 
				
			||||||
  });
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -23,20 +23,9 @@
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  box-shadow: var(--tutu-shadow-e16);
 | 
					  box-shadow: var(--tutu-shadow-e16);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  :global(.MuiToolbar-root) {
 | 
					  :global(.MuiToolbar-root) > :global(.MuiButtonBase-root):first-child {
 | 
				
			||||||
    > :global(.MuiButtonBase-root) {
 | 
					    margin-left: -0.5em;
 | 
				
			||||||
 | 
					    margin-right: 24px;
 | 
				
			||||||
      &:first-child {
 | 
					 | 
				
			||||||
        margin-left: -0.5em;
 | 
					 | 
				
			||||||
        margin-right: 24px;
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
      &:last-child {
 | 
					 | 
				
			||||||
        margin-right: -0.5em;
 | 
					 | 
				
			||||||
        margin-left: 24px;
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  @media (max-width: 560px) {
 | 
					  @media (max-width: 560px) {
 | 
				
			||||||
| 
						 | 
					@ -54,6 +43,7 @@
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  &.animated {
 | 
					  &.animated {
 | 
				
			||||||
    position: absolute;
 | 
					    position: absolute;
 | 
				
			||||||
 | 
					    transform: translateY(-50%);
 | 
				
			||||||
    overflow: hidden;
 | 
					    overflow: hidden;
 | 
				
			||||||
    will-change: width, height, top, left;
 | 
					    will-change: width, height, top, left;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -64,6 +54,12 @@
 | 
				
			||||||
    & * {
 | 
					    & * {
 | 
				
			||||||
      overflow: hidden;
 | 
					      overflow: hidden;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    @media (max-width: 560px) {
 | 
				
			||||||
 | 
					      & {
 | 
				
			||||||
 | 
					        transform: none;
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  &.bottom {
 | 
					  &.bottom {
 | 
				
			||||||
| 
						 | 
					@ -75,6 +71,7 @@
 | 
				
			||||||
      & {
 | 
					      & {
 | 
				
			||||||
        transform: none;
 | 
					        transform: none;
 | 
				
			||||||
        height: unset;
 | 
					        height: unset;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -118,9 +118,12 @@ const BottomSheet: ParentComponent<BottomSheetProps> = (props) => {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    animation = element.animate(
 | 
					    animation = element.animate(
 | 
				
			||||||
      {
 | 
					      {
 | 
				
			||||||
 | 
					        top: [`${rect.top}px`, `${rect.top}px`],
 | 
				
			||||||
        left: reserve
 | 
					        left: reserve
 | 
				
			||||||
          ? [`${rect.left}px`, `${window.innerWidth}px`]
 | 
					          ? [`${rect.left}px`, `${window.innerWidth}px`]
 | 
				
			||||||
          : [`${window.innerWidth}px`, `${rect.left}px`],
 | 
					          : [`${window.innerWidth}px`, `${rect.left}px`],
 | 
				
			||||||
 | 
					        width: [`${rect.width}px`, `${rect.width}px`],
 | 
				
			||||||
 | 
					        height: [`${rect.height}px`, `${rect.height}px`],
 | 
				
			||||||
      },
 | 
					      },
 | 
				
			||||||
      { easing, duration },
 | 
					      { easing, duration },
 | 
				
			||||||
    );
 | 
					    );
 | 
				
			||||||
| 
						 | 
					@ -148,9 +151,12 @@ const BottomSheet: ParentComponent<BottomSheetProps> = (props) => {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    animation = element.animate(
 | 
					    animation = element.animate(
 | 
				
			||||||
      {
 | 
					      {
 | 
				
			||||||
 | 
					        left: [`${rect.left}px`, `${rect.left}px`],
 | 
				
			||||||
        top: reserve
 | 
					        top: reserve
 | 
				
			||||||
          ? [`${rect.top}px`, `${window.innerHeight}px`]
 | 
					          ? [`${rect.top}px`, `${window.innerHeight}px`]
 | 
				
			||||||
          : [`${window.innerHeight}px`, `${rect.top}px`],
 | 
					          : [`${window.innerHeight}px`, `${rect.top}px`],
 | 
				
			||||||
 | 
					        width: [`${rect.width}px`, `${rect.width}px`],
 | 
				
			||||||
 | 
					        height: [`${rect.height}px`, `${rect.height}px`],
 | 
				
			||||||
      },
 | 
					      },
 | 
				
			||||||
      { easing, duration },
 | 
					      { easing, duration },
 | 
				
			||||||
    );
 | 
					    );
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1,252 +0,0 @@
 | 
				
			||||||
import {
 | 
					 | 
				
			||||||
  createRenderEffect,
 | 
					 | 
				
			||||||
  createResource,
 | 
					 | 
				
			||||||
  createSignal,
 | 
					 | 
				
			||||||
  For,
 | 
					 | 
				
			||||||
  onCleanup,
 | 
					 | 
				
			||||||
  Show,
 | 
					 | 
				
			||||||
  type Component,
 | 
					 | 
				
			||||||
} from "solid-js";
 | 
					 | 
				
			||||||
import Scaffold from "../material/Scaffold";
 | 
					 | 
				
			||||||
import { AppBar, Avatar, Button, IconButton, Toolbar } from "@suid/material";
 | 
					 | 
				
			||||||
import { Close, MoreVert, Verified } from "@suid/icons-material";
 | 
					 | 
				
			||||||
import { Title } from "../material/typography";
 | 
					 | 
				
			||||||
import { useNavigate, useParams } from "@solidjs/router";
 | 
					 | 
				
			||||||
import { useSessionForAcctStr } from "../masto/clients";
 | 
					 | 
				
			||||||
import { resolveCustomEmoji } from "../masto/toot";
 | 
					 | 
				
			||||||
import { FastAverageColor } from "fast-average-color";
 | 
					 | 
				
			||||||
import { useWindowSize } from "@solid-primitives/resize-observer";
 | 
					 | 
				
			||||||
import { css } from "solid-styled";
 | 
					 | 
				
			||||||
import { createTimeline } from "../masto/timelines";
 | 
					 | 
				
			||||||
import TootList from "../timelines/TootList";
 | 
					 | 
				
			||||||
import { createIntersectionObserver } from "@solid-primitives/intersection-observer";
 | 
					 | 
				
			||||||
import { createTimeSource, TimeSourceProvider } from "../platform/timesrc";
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
const Profile: Component = () => {
 | 
					 | 
				
			||||||
  const navigate = useNavigate();
 | 
					 | 
				
			||||||
  const params = useParams<{ acct: string; id: string }>();
 | 
					 | 
				
			||||||
  const acctText = () => decodeURIComponent(params.acct);
 | 
					 | 
				
			||||||
  const session = useSessionForAcctStr(acctText);
 | 
					 | 
				
			||||||
  const [bannerSampledColors, setBannerSampledColors] = createSignal<{
 | 
					 | 
				
			||||||
    average: string;
 | 
					 | 
				
			||||||
    text: string;
 | 
					 | 
				
			||||||
  }>();
 | 
					 | 
				
			||||||
  const windowSize = useWindowSize();
 | 
					 | 
				
			||||||
  const time = createTimeSource();
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  const [scrolledPastBanner, setScrolledPastBanner] = createSignal(false);
 | 
					 | 
				
			||||||
  const obx = new IntersectionObserver(
 | 
					 | 
				
			||||||
    (entries) => {
 | 
					 | 
				
			||||||
      const ent = entries[0];
 | 
					 | 
				
			||||||
      if (ent.intersectionRatio < 0.1) {
 | 
					 | 
				
			||||||
        setScrolledPastBanner(true);
 | 
					 | 
				
			||||||
      } else {
 | 
					 | 
				
			||||||
        setScrolledPastBanner(false);
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
    },
 | 
					 | 
				
			||||||
    {
 | 
					 | 
				
			||||||
      threshold: 0.1,
 | 
					 | 
				
			||||||
    },
 | 
					 | 
				
			||||||
  );
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  onCleanup(() => obx.disconnect());
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  const [profile] = createResource(
 | 
					 | 
				
			||||||
    () => [session().client, params.id] as const,
 | 
					 | 
				
			||||||
    async ([client, id]) => {
 | 
					 | 
				
			||||||
      return await client.v1.accounts.$select(id).fetch();
 | 
					 | 
				
			||||||
    },
 | 
					 | 
				
			||||||
  );
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  const [recentToots] = createTimeline(
 | 
					 | 
				
			||||||
    () => session().client.v1.accounts.$select(params.id).statuses,
 | 
					 | 
				
			||||||
    () => 20,
 | 
					 | 
				
			||||||
  );
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  const bannerImg = () => profile()?.header;
 | 
					 | 
				
			||||||
  const avatarImg = () => profile()?.avatar;
 | 
					 | 
				
			||||||
  const displayName = () =>
 | 
					 | 
				
			||||||
    resolveCustomEmoji(profile()?.displayName || "", profile()?.emojis ?? []);
 | 
					 | 
				
			||||||
  const fullUsername = () => `@${profile()?.acct ?? "..."}`; // TODO: full user name
 | 
					 | 
				
			||||||
  const description = () => profile()?.note;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  css`
 | 
					 | 
				
			||||||
    .intro {
 | 
					 | 
				
			||||||
      background-color: var(--tutu-color-surface-d);
 | 
					 | 
				
			||||||
      color: var(--tutu-color-on-surface);
 | 
					 | 
				
			||||||
      padding: 16px 12px;
 | 
					 | 
				
			||||||
      display: flex;
 | 
					 | 
				
			||||||
      flex-flow: column nowrap;
 | 
					 | 
				
			||||||
      gap: 16px;
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    .acct-grp {
 | 
					 | 
				
			||||||
      display: grid;
 | 
					 | 
				
			||||||
      grid-template-columns: auto 1fr auto;
 | 
					 | 
				
			||||||
      gap: 16px;
 | 
					 | 
				
			||||||
      align-items: center;
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    .name-grp {
 | 
					 | 
				
			||||||
      display: flex;
 | 
					 | 
				
			||||||
      flex-flow: column nowrap;
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    table.acct-fields {
 | 
					 | 
				
			||||||
      & td > :global(a) {
 | 
					 | 
				
			||||||
        display: inline-flex;
 | 
					 | 
				
			||||||
        min-height: 44px;
 | 
					 | 
				
			||||||
        align-items: center;
 | 
					 | 
				
			||||||
        color: inherit;
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
      & :global(a > .invisible) {
 | 
					 | 
				
			||||||
        display: none;
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
      & :global(svg) {
 | 
					 | 
				
			||||||
        vertical-align: middle;
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    .page-title {
 | 
					 | 
				
			||||||
      flex-grow: 1;
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
  `;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  return (
 | 
					 | 
				
			||||||
    <Scaffold
 | 
					 | 
				
			||||||
      topbar={
 | 
					 | 
				
			||||||
        <AppBar
 | 
					 | 
				
			||||||
          position="static"
 | 
					 | 
				
			||||||
          color={scrolledPastBanner() ? "primary" : "transparent"}
 | 
					 | 
				
			||||||
          elevation={scrolledPastBanner() ? undefined : 0}
 | 
					 | 
				
			||||||
        >
 | 
					 | 
				
			||||||
          <Toolbar
 | 
					 | 
				
			||||||
            variant="dense"
 | 
					 | 
				
			||||||
            sx={{
 | 
					 | 
				
			||||||
              display: "flex",
 | 
					 | 
				
			||||||
              color: bannerSampledColors()?.text,
 | 
					 | 
				
			||||||
              paddingTop: "var(--safe-area-inset-top)",
 | 
					 | 
				
			||||||
            }}
 | 
					 | 
				
			||||||
          >
 | 
					 | 
				
			||||||
            <IconButton color="inherit" onClick={[navigate, -1]}>
 | 
					 | 
				
			||||||
              <Close />
 | 
					 | 
				
			||||||
            </IconButton>
 | 
					 | 
				
			||||||
            <Title
 | 
					 | 
				
			||||||
              use:solid-styled
 | 
					 | 
				
			||||||
              class="page-title"
 | 
					 | 
				
			||||||
              style={{
 | 
					 | 
				
			||||||
                visibility: scrolledPastBanner() ? undefined : "hidden",
 | 
					 | 
				
			||||||
              }}
 | 
					 | 
				
			||||||
              ref={(e: HTMLElement) =>
 | 
					 | 
				
			||||||
                createRenderEffect(() => (e.innerHTML = displayName()))
 | 
					 | 
				
			||||||
              }
 | 
					 | 
				
			||||||
            ></Title>
 | 
					 | 
				
			||||||
            <IconButton color="inherit">
 | 
					 | 
				
			||||||
              <MoreVert />
 | 
					 | 
				
			||||||
            </IconButton>
 | 
					 | 
				
			||||||
          </Toolbar>
 | 
					 | 
				
			||||||
        </AppBar>
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
    >
 | 
					 | 
				
			||||||
      <div
 | 
					 | 
				
			||||||
        style={{
 | 
					 | 
				
			||||||
          width: "100%",
 | 
					 | 
				
			||||||
          height: `${268 * (Math.min(560, windowSize.width) / 560)}px`,
 | 
					 | 
				
			||||||
          "margin-top":
 | 
					 | 
				
			||||||
            "calc(-1 * (var(--scaffold-topbar-height) + var(--safe-area-inset-top)))",
 | 
					 | 
				
			||||||
        }}
 | 
					 | 
				
			||||||
      >
 | 
					 | 
				
			||||||
        <img
 | 
					 | 
				
			||||||
          ref={(e) => obx.observe(e)}
 | 
					 | 
				
			||||||
          src={bannerImg()}
 | 
					 | 
				
			||||||
          style={{
 | 
					 | 
				
			||||||
            "object-fit": "contain",
 | 
					 | 
				
			||||||
            width: "100%",
 | 
					 | 
				
			||||||
            height: "100%",
 | 
					 | 
				
			||||||
          }}
 | 
					 | 
				
			||||||
          crossOrigin="anonymous"
 | 
					 | 
				
			||||||
          onLoad={async (event) => {
 | 
					 | 
				
			||||||
            const ins = new FastAverageColor();
 | 
					 | 
				
			||||||
            const colors = ins.getColor(event.currentTarget);
 | 
					 | 
				
			||||||
            setBannerSampledColors({
 | 
					 | 
				
			||||||
              average: colors.hex,
 | 
					 | 
				
			||||||
              text: colors.isDark ? "white" : "black",
 | 
					 | 
				
			||||||
            });
 | 
					 | 
				
			||||||
          }}
 | 
					 | 
				
			||||||
        ></img>
 | 
					 | 
				
			||||||
      </div>
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
      <div
 | 
					 | 
				
			||||||
        class="intro"
 | 
					 | 
				
			||||||
        style={{
 | 
					 | 
				
			||||||
          "background-color": bannerSampledColors()?.average,
 | 
					 | 
				
			||||||
          color: bannerSampledColors()?.text,
 | 
					 | 
				
			||||||
        }}
 | 
					 | 
				
			||||||
      >
 | 
					 | 
				
			||||||
        <div class="acct-grp">
 | 
					 | 
				
			||||||
          <Avatar
 | 
					 | 
				
			||||||
            src={avatarImg()}
 | 
					 | 
				
			||||||
            sx={{
 | 
					 | 
				
			||||||
              marginTop: "calc(-16px - 72px / 2)",
 | 
					 | 
				
			||||||
              width: "72px",
 | 
					 | 
				
			||||||
              height: "72px",
 | 
					 | 
				
			||||||
            }}
 | 
					 | 
				
			||||||
          ></Avatar>
 | 
					 | 
				
			||||||
          <div class="name-grp">
 | 
					 | 
				
			||||||
            <span
 | 
					 | 
				
			||||||
              ref={(e) =>
 | 
					 | 
				
			||||||
                createRenderEffect(() => (e.innerHTML = displayName()))
 | 
					 | 
				
			||||||
              }
 | 
					 | 
				
			||||||
            ></span>
 | 
					 | 
				
			||||||
            <span>{fullUsername()}</span>
 | 
					 | 
				
			||||||
          </div>
 | 
					 | 
				
			||||||
          <div>
 | 
					 | 
				
			||||||
            <Button variant="contained" color="secondary">
 | 
					 | 
				
			||||||
              Subscribe
 | 
					 | 
				
			||||||
            </Button>
 | 
					 | 
				
			||||||
          </div>
 | 
					 | 
				
			||||||
        </div>
 | 
					 | 
				
			||||||
        <div
 | 
					 | 
				
			||||||
          ref={(e) =>
 | 
					 | 
				
			||||||
            createRenderEffect(() => (e.innerHTML = description() || ""))
 | 
					 | 
				
			||||||
          }
 | 
					 | 
				
			||||||
        ></div>
 | 
					 | 
				
			||||||
        <table class="acct-fields">
 | 
					 | 
				
			||||||
          <tbody>
 | 
					 | 
				
			||||||
            <For each={profile()?.fields ?? []}>
 | 
					 | 
				
			||||||
              {(item, index) => {
 | 
					 | 
				
			||||||
                return (
 | 
					 | 
				
			||||||
                  <tr data-field-index={index()}>
 | 
					 | 
				
			||||||
                    <td>{item.name}</td>
 | 
					 | 
				
			||||||
                    <td>
 | 
					 | 
				
			||||||
                      <Show when={item.verifiedAt}>
 | 
					 | 
				
			||||||
                        <Verified />
 | 
					 | 
				
			||||||
                      </Show>
 | 
					 | 
				
			||||||
                    </td>
 | 
					 | 
				
			||||||
                    <td
 | 
					 | 
				
			||||||
                      ref={(e) => {
 | 
					 | 
				
			||||||
                        createRenderEffect(() => (e.innerHTML = item.value));
 | 
					 | 
				
			||||||
                      }}
 | 
					 | 
				
			||||||
                    ></td>
 | 
					 | 
				
			||||||
                  </tr>
 | 
					 | 
				
			||||||
                );
 | 
					 | 
				
			||||||
              }}
 | 
					 | 
				
			||||||
            </For>
 | 
					 | 
				
			||||||
          </tbody>
 | 
					 | 
				
			||||||
        </table>
 | 
					 | 
				
			||||||
      </div>
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
      <TimeSourceProvider value={time}>
 | 
					 | 
				
			||||||
        <TootList
 | 
					 | 
				
			||||||
          threads={recentToots.list}
 | 
					 | 
				
			||||||
          onUnknownThread={recentToots.getPath}
 | 
					 | 
				
			||||||
          onChangeToot={recentToots.set}
 | 
					 | 
				
			||||||
        />
 | 
					 | 
				
			||||||
      </TimeSourceProvider>
 | 
					 | 
				
			||||||
    </Scaffold>
 | 
					 | 
				
			||||||
  );
 | 
					 | 
				
			||||||
};
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
export default Profile;
 | 
					 | 
				
			||||||
| 
						 | 
					@ -151,7 +151,7 @@ const Home: ParentComponent = (props) => {
 | 
				
			||||||
    );
 | 
					    );
 | 
				
			||||||
    const acct = `${inf.username}@${p.account.site}`;
 | 
					    const acct = `${inf.username}@${p.account.site}`;
 | 
				
			||||||
    setTootBottomSheetCache(acct, toot);
 | 
					    setTootBottomSheetCache(acct, toot);
 | 
				
			||||||
    navigate(`/${encodeURIComponent(acct)}/toot/${toot.id}`, {
 | 
					    navigate(`/${encodeURIComponent(acct)}/${toot.id}`, {
 | 
				
			||||||
      state: reply
 | 
					      state: reply
 | 
				
			||||||
        ? {
 | 
					        ? {
 | 
				
			||||||
            tootReply: true,
 | 
					            tootReply: true,
 | 
				
			||||||
| 
						 | 
					@ -213,7 +213,7 @@ const Home: ParentComponent = (props) => {
 | 
				
			||||||
                  Public
 | 
					                  Public
 | 
				
			||||||
                </Tab>
 | 
					                </Tab>
 | 
				
			||||||
              </Tabs>
 | 
					              </Tabs>
 | 
				
			||||||
              <ProfileMenuButton profile={profiles()[0]}>
 | 
					              <ProfileMenuButton profile={profile()}>
 | 
				
			||||||
                <MenuItem
 | 
					                <MenuItem
 | 
				
			||||||
                  onClick={(e) =>
 | 
					                  onClick={(e) =>
 | 
				
			||||||
                    $settings.setKey(
 | 
					                    $settings.setKey(
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -24,10 +24,7 @@ import {
 | 
				
			||||||
import { A } from "@solidjs/router";
 | 
					import { A } from "@solidjs/router";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const ProfileMenuButton: ParentComponent<{
 | 
					const ProfileMenuButton: ParentComponent<{
 | 
				
			||||||
  profile?: {
 | 
					  profile?: { displayName: string; avatar: string; username: string };
 | 
				
			||||||
    account: { site: string };
 | 
					 | 
				
			||||||
    inf?: { displayName: string; avatar: string; username: string; id: string };
 | 
					 | 
				
			||||||
  };
 | 
					 | 
				
			||||||
  onClick?: () => void;
 | 
					  onClick?: () => void;
 | 
				
			||||||
  onClose?: () => void;
 | 
					  onClose?: () => void;
 | 
				
			||||||
}> = (props) => {
 | 
					}> = (props) => {
 | 
				
			||||||
| 
						 | 
					@ -51,83 +48,79 @@ const ProfileMenuButton: ParentComponent<{
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  return (
 | 
					  return (
 | 
				
			||||||
    <>
 | 
					    <>
 | 
				
			||||||
      <ButtonBase
 | 
					        <ButtonBase
 | 
				
			||||||
        aria-haspopup="true"
 | 
					          aria-haspopup="true"
 | 
				
			||||||
        sx={{ borderRadius: "50%" }}
 | 
					          sx={{ borderRadius: "50%" }}
 | 
				
			||||||
        id={buttonId}
 | 
					          id={buttonId}
 | 
				
			||||||
        onClick={onClick}
 | 
					          onClick={onClick}
 | 
				
			||||||
        aria-controls={open() ? menuId : undefined}
 | 
					          aria-controls={open() ? menuId : undefined}
 | 
				
			||||||
        aria-expanded={open() ? "true" : undefined}
 | 
					          aria-expanded={open() ? "true" : undefined}
 | 
				
			||||||
      >
 | 
					 | 
				
			||||||
        <Avatar
 | 
					 | 
				
			||||||
          alt={`${props.profile?.inf?.displayName}'s avatar`}
 | 
					 | 
				
			||||||
          src={props.profile?.inf?.avatar}
 | 
					 | 
				
			||||||
        ></Avatar>
 | 
					 | 
				
			||||||
      </ButtonBase>
 | 
					 | 
				
			||||||
      <Menu
 | 
					 | 
				
			||||||
        id={menuId}
 | 
					 | 
				
			||||||
        anchorEl={anchor()}
 | 
					 | 
				
			||||||
        open={open()}
 | 
					 | 
				
			||||||
        onClose={onClose}
 | 
					 | 
				
			||||||
        MenuListProps={{
 | 
					 | 
				
			||||||
          "aria-labelledby": buttonId,
 | 
					 | 
				
			||||||
          sx: {
 | 
					 | 
				
			||||||
            minWidth: "220px",
 | 
					 | 
				
			||||||
          },
 | 
					 | 
				
			||||||
        }}
 | 
					 | 
				
			||||||
        anchorOrigin={{
 | 
					 | 
				
			||||||
          vertical: "top",
 | 
					 | 
				
			||||||
          horizontal: "right",
 | 
					 | 
				
			||||||
        }}
 | 
					 | 
				
			||||||
        transformOrigin={{
 | 
					 | 
				
			||||||
          vertical: "top",
 | 
					 | 
				
			||||||
          horizontal: "right",
 | 
					 | 
				
			||||||
        }}
 | 
					 | 
				
			||||||
      >
 | 
					 | 
				
			||||||
        <MenuItem
 | 
					 | 
				
			||||||
          component={A}
 | 
					 | 
				
			||||||
          href={`/${encodeURIComponent(`${props.profile?.inf?.username}@${props.profile?.account.site}`)}/profile/${props.profile?.inf?.id}`}
 | 
					 | 
				
			||||||
          disabled={!props.profile}
 | 
					 | 
				
			||||||
        >
 | 
					        >
 | 
				
			||||||
          <ListItemAvatar>
 | 
					          <Avatar
 | 
				
			||||||
            <Avatar src={props.profile?.inf?.avatar}></Avatar>
 | 
					            alt={`${props.profile?.displayName}'s avatar`}
 | 
				
			||||||
          </ListItemAvatar>
 | 
					            src={props.profile?.avatar}
 | 
				
			||||||
          <ListItemText
 | 
					          ></Avatar>
 | 
				
			||||||
            primary={props.profile?.inf?.displayName}
 | 
					        </ButtonBase>
 | 
				
			||||||
            secondary={`@${props.profile?.inf?.username}`}
 | 
					        <Menu
 | 
				
			||||||
          ></ListItemText>
 | 
					          id={menuId}
 | 
				
			||||||
        </MenuItem>
 | 
					          anchorEl={anchor()}
 | 
				
			||||||
 | 
					          open={open()}
 | 
				
			||||||
 | 
					          onClose={onClose}
 | 
				
			||||||
 | 
					          MenuListProps={{
 | 
				
			||||||
 | 
					            "aria-labelledby": buttonId,
 | 
				
			||||||
 | 
					            sx: {
 | 
				
			||||||
 | 
					              minWidth: "220px",
 | 
				
			||||||
 | 
					            },
 | 
				
			||||||
 | 
					          }}
 | 
				
			||||||
 | 
					          anchorOrigin={{
 | 
				
			||||||
 | 
					            vertical: "top",
 | 
				
			||||||
 | 
					            horizontal: "right",
 | 
				
			||||||
 | 
					          }}
 | 
				
			||||||
 | 
					          transformOrigin={{
 | 
				
			||||||
 | 
					            vertical: "top",
 | 
				
			||||||
 | 
					            horizontal: "right",
 | 
				
			||||||
 | 
					          }}
 | 
				
			||||||
 | 
					        >
 | 
				
			||||||
 | 
					          <MenuItem>
 | 
				
			||||||
 | 
					            <ListItemAvatar>
 | 
				
			||||||
 | 
					              <Avatar src={props.profile?.avatar}></Avatar>
 | 
				
			||||||
 | 
					            </ListItemAvatar>
 | 
				
			||||||
 | 
					            <ListItemText
 | 
				
			||||||
 | 
					              primary={props.profile?.displayName}
 | 
				
			||||||
 | 
					              secondary={`@${props.profile?.username}`}
 | 
				
			||||||
 | 
					            ></ListItemText>
 | 
				
			||||||
 | 
					          </MenuItem>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        <MenuItem>
 | 
					          <MenuItem>
 | 
				
			||||||
          <ListItemIcon>
 | 
					            <ListItemIcon>
 | 
				
			||||||
            <BookmarkIcon />
 | 
					              <BookmarkIcon />
 | 
				
			||||||
          </ListItemIcon>
 | 
					            </ListItemIcon>
 | 
				
			||||||
          <ListItemText>Bookmarks</ListItemText>
 | 
					            <ListItemText>Bookmarks</ListItemText>
 | 
				
			||||||
        </MenuItem>
 | 
					          </MenuItem>
 | 
				
			||||||
        <MenuItem>
 | 
					          <MenuItem>
 | 
				
			||||||
          <ListItemIcon>
 | 
					            <ListItemIcon>
 | 
				
			||||||
            <LikeIcon />
 | 
					              <LikeIcon />
 | 
				
			||||||
          </ListItemIcon>
 | 
					            </ListItemIcon>
 | 
				
			||||||
          <ListItemText>Likes</ListItemText>
 | 
					            <ListItemText>Likes</ListItemText>
 | 
				
			||||||
        </MenuItem>
 | 
					          </MenuItem>
 | 
				
			||||||
        <MenuItem>
 | 
					          <MenuItem>
 | 
				
			||||||
          <ListItemIcon>
 | 
					            <ListItemIcon>
 | 
				
			||||||
            <ListIcon />
 | 
					              <ListIcon />
 | 
				
			||||||
          </ListItemIcon>
 | 
					            </ListItemIcon>
 | 
				
			||||||
          <ListItemText>Lists</ListItemText>
 | 
					            <ListItemText>Lists</ListItemText>
 | 
				
			||||||
        </MenuItem>
 | 
					          </MenuItem>
 | 
				
			||||||
        <Divider />
 | 
					 | 
				
			||||||
        <Show when={props.children}>
 | 
					 | 
				
			||||||
          {props.children}
 | 
					 | 
				
			||||||
          <Divider />
 | 
					          <Divider />
 | 
				
			||||||
        </Show>
 | 
					          <Show when={props.children}>
 | 
				
			||||||
        <MenuItem component={A} href="/settings" onClick={onClose}>
 | 
					            {props.children}
 | 
				
			||||||
          <ListItemIcon>
 | 
					            <Divider />
 | 
				
			||||||
            <SettingsIcon />
 | 
					          </Show>
 | 
				
			||||||
          </ListItemIcon>
 | 
					          <MenuItem component={A} href="/settings" onClick={onClose}>
 | 
				
			||||||
          <ListItemText>Settings</ListItemText>
 | 
					            <ListItemIcon>
 | 
				
			||||||
        </MenuItem>
 | 
					              <SettingsIcon />
 | 
				
			||||||
      </Menu>
 | 
					            </ListItemIcon>
 | 
				
			||||||
 | 
					            <ListItemText>Settings</ListItemText>
 | 
				
			||||||
 | 
					          </MenuItem>
 | 
				
			||||||
 | 
					        </Menu>
 | 
				
			||||||
    </>
 | 
					    </>
 | 
				
			||||||
  );
 | 
					  );
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -15,7 +15,7 @@ import {
 | 
				
			||||||
  ArrowBack as BackIcon,
 | 
					  ArrowBack as BackIcon,
 | 
				
			||||||
  Close as CloseIcon,
 | 
					  Close as CloseIcon,
 | 
				
			||||||
} from "@suid/icons-material";
 | 
					} from "@suid/icons-material";
 | 
				
			||||||
import { useSessionForAcctStr } from "../masto/clients";
 | 
					import { createUnauthorizedClient, useSessions } from "../masto/clients";
 | 
				
			||||||
import { resolveCustomEmoji } from "../masto/toot";
 | 
					import { resolveCustomEmoji } from "../masto/toot";
 | 
				
			||||||
import RegularToot from "./RegularToot";
 | 
					import RegularToot from "./RegularToot";
 | 
				
			||||||
import type { mastodon } from "masto";
 | 
					import type { mastodon } from "masto";
 | 
				
			||||||
| 
						 | 
					@ -45,10 +45,24 @@ const TootBottomSheet: Component = (props) => {
 | 
				
			||||||
    tootReply?: boolean;
 | 
					    tootReply?: boolean;
 | 
				
			||||||
  }>();
 | 
					  }>();
 | 
				
			||||||
  const navigate = useNavigate();
 | 
					  const navigate = useNavigate();
 | 
				
			||||||
 | 
					  const allSession = useSessions();
 | 
				
			||||||
  const time = createTimeSource();
 | 
					  const time = createTimeSource();
 | 
				
			||||||
  const [isInTyping, setInTyping] = createSignal(false);
 | 
					  const [isInTyping, setInTyping] = createSignal(false);
 | 
				
			||||||
  const acctText = () => decodeURIComponent(params.acct);
 | 
					  const acctText = () => decodeURIComponent(params.acct);
 | 
				
			||||||
  const session = useSessionForAcctStr(acctText)
 | 
					  const session = () => {
 | 
				
			||||||
 | 
					    const [inputUsername, inputSite] = acctText().split("@", 2);
 | 
				
			||||||
 | 
					    const authedSession = allSession().find(
 | 
				
			||||||
 | 
					      (x) =>
 | 
				
			||||||
 | 
					        x.account.site === inputSite &&
 | 
				
			||||||
 | 
					        x.account.inf?.username === inputUsername,
 | 
				
			||||||
 | 
					    );
 | 
				
			||||||
 | 
					    return (
 | 
				
			||||||
 | 
					      authedSession ?? {
 | 
				
			||||||
 | 
					        client: createUnauthorizedClient(inputSite),
 | 
				
			||||||
 | 
					        account: undefined,
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    );
 | 
				
			||||||
 | 
					  };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  const pushedCount = () => {
 | 
					  const pushedCount = () => {
 | 
				
			||||||
    return location.state?.tootBottomSheetPushedCount || 0;
 | 
					    return location.state?.tootBottomSheetPushedCount || 0;
 | 
				
			||||||
| 
						 | 
					@ -161,7 +175,7 @@ const TootBottomSheet: Component = (props) => {
 | 
				
			||||||
      return;
 | 
					      return;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
    setCache(params.acct, status);
 | 
					    setCache(params.acct, status);
 | 
				
			||||||
    navigate(`/${params.acct}/toot/${status.id}`, {
 | 
					    navigate(`/${params.acct}/${status.id}`, {
 | 
				
			||||||
      state: {
 | 
					      state: {
 | 
				
			||||||
        tootBottomSheetPushedCount: pushedCount() + 1,
 | 
					        tootBottomSheetPushedCount: pushedCount() + 1,
 | 
				
			||||||
      },
 | 
					      },
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1,104 +0,0 @@
 | 
				
			||||||
import {
 | 
					 | 
				
			||||||
  Component,
 | 
					 | 
				
			||||||
  For,
 | 
					 | 
				
			||||||
  onCleanup,
 | 
					 | 
				
			||||||
  createSignal,
 | 
					 | 
				
			||||||
  Show,
 | 
					 | 
				
			||||||
  untrack,
 | 
					 | 
				
			||||||
  Match,
 | 
					 | 
				
			||||||
  Switch as JsSwitch,
 | 
					 | 
				
			||||||
  ErrorBoundary,
 | 
					 | 
				
			||||||
  type Ref,
 | 
					 | 
				
			||||||
} from "solid-js";
 | 
					 | 
				
			||||||
import { type mastodon } from "masto";
 | 
					 | 
				
			||||||
import { Button, LinearProgress } from "@suid/material";
 | 
					 | 
				
			||||||
import { createTimeline } from "../masto/timelines";
 | 
					 | 
				
			||||||
import { vibrate } from "../platform/hardware";
 | 
					 | 
				
			||||||
import PullDownToRefresh from "./PullDownToRefresh";
 | 
					 | 
				
			||||||
import TootComposer from "./TootComposer";
 | 
					 | 
				
			||||||
import Thread from "./Thread.jsx";
 | 
					 | 
				
			||||||
import { useDefaultSession } from "../masto/clients";
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
const TootList: Component<{
 | 
					 | 
				
			||||||
  ref?: Ref<HTMLDivElement>;
 | 
					 | 
				
			||||||
  threads: string[];
 | 
					 | 
				
			||||||
  onUnknownThread: (id: string) => { value: mastodon.v1.Status }[] | undefined;
 | 
					 | 
				
			||||||
  onChangeToot: (id: string, value: mastodon.v1.Status) => void;
 | 
					 | 
				
			||||||
}> = (props) => {
 | 
					 | 
				
			||||||
  const session = useDefaultSession();
 | 
					 | 
				
			||||||
  const [expandedThreadId, setExpandedThreadId] = createSignal<string>();
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  const onBookmark = async (
 | 
					 | 
				
			||||||
    client: mastodon.rest.Client,
 | 
					 | 
				
			||||||
    status: mastodon.v1.Status,
 | 
					 | 
				
			||||||
  ) => {
 | 
					 | 
				
			||||||
    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 onBoost = async (
 | 
					 | 
				
			||||||
    client: mastodon.rest.Client,
 | 
					 | 
				
			||||||
    status: mastodon.v1.Status,
 | 
					 | 
				
			||||||
  ) => {
 | 
					 | 
				
			||||||
    vibrate(50);
 | 
					 | 
				
			||||||
    const rootStatus = status.reblog ? status.reblog : status;
 | 
					 | 
				
			||||||
    const reblogged = rootStatus.reblogged;
 | 
					 | 
				
			||||||
    if (status.reblog) {
 | 
					 | 
				
			||||||
      status.reblog = { ...status.reblog, reblogged: !reblogged };
 | 
					 | 
				
			||||||
      props.onChangeToot(status.id, status);
 | 
					 | 
				
			||||||
    } else {
 | 
					 | 
				
			||||||
      props.onChangeToot(
 | 
					 | 
				
			||||||
        status.id,
 | 
					 | 
				
			||||||
        Object.assign(status, {
 | 
					 | 
				
			||||||
          reblogged: !reblogged,
 | 
					 | 
				
			||||||
        }),
 | 
					 | 
				
			||||||
      );
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
    const result = reblogged
 | 
					 | 
				
			||||||
      ? await client.v1.statuses.$select(status.id).unreblog()
 | 
					 | 
				
			||||||
      : (await client.v1.statuses.$select(status.id).reblog()).reblog!;
 | 
					 | 
				
			||||||
    props.onChangeToot(
 | 
					 | 
				
			||||||
      status.id,
 | 
					 | 
				
			||||||
      Object.assign(status.reblog ?? status, result.reblog),
 | 
					 | 
				
			||||||
    );
 | 
					 | 
				
			||||||
  };
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  return (
 | 
					 | 
				
			||||||
    <ErrorBoundary
 | 
					 | 
				
			||||||
      fallback={(err, reset) => {
 | 
					 | 
				
			||||||
        return <p>Oops: {String(err)}</p>;
 | 
					 | 
				
			||||||
      }}
 | 
					 | 
				
			||||||
    >
 | 
					 | 
				
			||||||
      <div ref={props.ref}>
 | 
					 | 
				
			||||||
        <For each={props.threads}>
 | 
					 | 
				
			||||||
          {(itemId, index) => {
 | 
					 | 
				
			||||||
            const path = props.onUnknownThread(itemId)!;
 | 
					 | 
				
			||||||
            const toots = path.reverse().map((x) => x.value);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            return (
 | 
					 | 
				
			||||||
              <Thread
 | 
					 | 
				
			||||||
                toots={toots}
 | 
					 | 
				
			||||||
                onBoost={onBoost}
 | 
					 | 
				
			||||||
                onBookmark={onBookmark}
 | 
					 | 
				
			||||||
                onReply={({ status }, element) => {}}
 | 
					 | 
				
			||||||
                client={session()?.client!}
 | 
					 | 
				
			||||||
                isExpended={(status) => status.id === expandedThreadId()}
 | 
					 | 
				
			||||||
                onItemClick={(status, event) => {
 | 
					 | 
				
			||||||
                  if (status.id !== expandedThreadId()) {
 | 
					 | 
				
			||||||
                    setExpandedThreadId((x) => (x ? undefined : status.id));
 | 
					 | 
				
			||||||
                  } else {
 | 
					 | 
				
			||||||
                    // TODO: open full-screen toot
 | 
					 | 
				
			||||||
                  }
 | 
					 | 
				
			||||||
                }}
 | 
					 | 
				
			||||||
              />
 | 
					 | 
				
			||||||
            );
 | 
					 | 
				
			||||||
          }}
 | 
					 | 
				
			||||||
        </For>
 | 
					 | 
				
			||||||
      </div>
 | 
					 | 
				
			||||||
    </ErrorBoundary>
 | 
					 | 
				
			||||||
  );
 | 
					 | 
				
			||||||
};
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
export default TootList;
 | 
					 | 
				
			||||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue