* add ~platform/DocumentTitle * add titles for some pages
This commit is contained in:
		
							parent
							
								
									1115135380
								
							
						
					
					
						commit
						1c8a3f0bbb
					
				
					 12 changed files with 843 additions and 823 deletions
				
			
		| 
						 | 
				
			
			@ -8,13 +8,13 @@ import {
 | 
			
		|||
} from "solid-js";
 | 
			
		||||
import { acceptAccountViaAuthCode } from "./stores";
 | 
			
		||||
import { $settings } from "../settings/stores";
 | 
			
		||||
import { useDocumentTitle } from "../utils";
 | 
			
		||||
import cards from "~material/cards.module.css";
 | 
			
		||||
import { LinearProgress } from "@suid/material";
 | 
			
		||||
import Img from "~material/Img";
 | 
			
		||||
import { createRestAPIClient } from "masto";
 | 
			
		||||
import { Title } from "~material/typography";
 | 
			
		||||
import { useNavigator } from "~platform/StackedRouter";
 | 
			
		||||
import DocumentTitle from "~platform/DocumentTitle";
 | 
			
		||||
 | 
			
		||||
type OAuth2CallbackParams = {
 | 
			
		||||
  code?: string;
 | 
			
		||||
| 
						 | 
				
			
			@ -27,13 +27,12 @@ const MastodonOAuth2Callback: Component = () => {
 | 
			
		|||
  const titleId = createUniqueId();
 | 
			
		||||
  const [params] = useSearchParams<OAuth2CallbackParams>();
 | 
			
		||||
  const { push: navigate } = useNavigator();
 | 
			
		||||
  const setDocumentTitle = useDocumentTitle("Back from Mastodon...");
 | 
			
		||||
  const [siteImg, setSiteImg] = createSignal<{
 | 
			
		||||
    src: string;
 | 
			
		||||
    srcset?: string;
 | 
			
		||||
    blurhash: string;
 | 
			
		||||
  }>();
 | 
			
		||||
  const [siteTitle, setSiteTitle] = createSignal("the Mastodon server");
 | 
			
		||||
  const [siteTitle, setSiteTitle] = createSignal("Mastodon");
 | 
			
		||||
 | 
			
		||||
  onMount(async () => {
 | 
			
		||||
    const onGoingOAuth2Process = $settings.get().onGoingOAuth2Process;
 | 
			
		||||
| 
						 | 
				
			
			@ -42,7 +41,6 @@ const MastodonOAuth2Callback: Component = () => {
 | 
			
		|||
      url: onGoingOAuth2Process,
 | 
			
		||||
    });
 | 
			
		||||
    const ins = await client.v2.instance.fetch();
 | 
			
		||||
    setDocumentTitle(`Back from ${ins.title}...`);
 | 
			
		||||
    setSiteTitle(ins.title);
 | 
			
		||||
 | 
			
		||||
    const srcset = [];
 | 
			
		||||
| 
						 | 
				
			
			@ -93,42 +91,45 @@ const MastodonOAuth2Callback: Component = () => {
 | 
			
		|||
    });
 | 
			
		||||
  });
 | 
			
		||||
  return (
 | 
			
		||||
    <div class={cards.layoutCentered}>
 | 
			
		||||
      <div class={cards.card} aria-busy="true" aria-describedby={progressId}>
 | 
			
		||||
        <LinearProgress
 | 
			
		||||
          class={[cards.cardNoPad, cards.cardGutSkip].join(" ")}
 | 
			
		||||
          id={progressId}
 | 
			
		||||
          aria-labelledby={titleId}
 | 
			
		||||
        />
 | 
			
		||||
        <Show
 | 
			
		||||
          when={siteImg()}
 | 
			
		||||
          fallback={
 | 
			
		||||
            <i
 | 
			
		||||
              aria-busy="true"
 | 
			
		||||
              aria-label="Preparing image..."
 | 
			
		||||
              style={{ height: "235px", display: "block" }}
 | 
			
		||||
            ></i>
 | 
			
		||||
          }
 | 
			
		||||
        >
 | 
			
		||||
          <Img
 | 
			
		||||
            src={siteImg()?.src}
 | 
			
		||||
            srcset={siteImg()?.srcset}
 | 
			
		||||
            blurhash={siteImg()?.blurhash}
 | 
			
		||||
    <>
 | 
			
		||||
    <DocumentTitle>Back from {siteTitle()}</DocumentTitle>
 | 
			
		||||
      <div class={cards.layoutCentered}>
 | 
			
		||||
        <div class={cards.card} aria-busy="true" aria-describedby={progressId}>
 | 
			
		||||
          <LinearProgress
 | 
			
		||||
            class={[cards.cardNoPad, cards.cardGutSkip].join(" ")}
 | 
			
		||||
            alt={`Banner image for ${siteTitle()}`}
 | 
			
		||||
            style={{ height: "235px", display: "block" }}
 | 
			
		||||
            id={progressId}
 | 
			
		||||
            aria-labelledby={titleId}
 | 
			
		||||
          />
 | 
			
		||||
        </Show>
 | 
			
		||||
          <Show
 | 
			
		||||
            when={siteImg()}
 | 
			
		||||
            fallback={
 | 
			
		||||
              <i
 | 
			
		||||
                aria-busy="true"
 | 
			
		||||
                aria-label="Preparing image..."
 | 
			
		||||
                style={{ height: "235px", display: "block" }}
 | 
			
		||||
              ></i>
 | 
			
		||||
            }
 | 
			
		||||
          >
 | 
			
		||||
            <Img
 | 
			
		||||
              src={siteImg()?.src}
 | 
			
		||||
              srcset={siteImg()?.srcset}
 | 
			
		||||
              blurhash={siteImg()?.blurhash}
 | 
			
		||||
              class={[cards.cardNoPad, cards.cardGutSkip].join(" ")}
 | 
			
		||||
              alt={`Banner image for ${siteTitle()}`}
 | 
			
		||||
              style={{ height: "235px", display: "block" }}
 | 
			
		||||
            />
 | 
			
		||||
          </Show>
 | 
			
		||||
 | 
			
		||||
        <Title component="h6" id={titleId}>
 | 
			
		||||
          Contracting {siteTitle}...
 | 
			
		||||
        </Title>
 | 
			
		||||
        <p>
 | 
			
		||||
          If this page stays too long, you can close this page and sign in
 | 
			
		||||
          again.
 | 
			
		||||
        </p>
 | 
			
		||||
          <Title component="h6" id={titleId}>
 | 
			
		||||
            Contracting {siteTitle}...
 | 
			
		||||
          </Title>
 | 
			
		||||
          <p>
 | 
			
		||||
            If this page stays too long, you can close this page and sign in
 | 
			
		||||
            again.
 | 
			
		||||
          </p>
 | 
			
		||||
        </div>
 | 
			
		||||
      </div>
 | 
			
		||||
    </div>
 | 
			
		||||
    </>
 | 
			
		||||
  );
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -2,7 +2,6 @@ import {
 | 
			
		|||
  Component,
 | 
			
		||||
  Show,
 | 
			
		||||
  createEffect,
 | 
			
		||||
  createSelector,
 | 
			
		||||
  createSignal,
 | 
			
		||||
  createUniqueId,
 | 
			
		||||
  onMount,
 | 
			
		||||
| 
						 | 
				
			
			@ -10,15 +9,14 @@ import {
 | 
			
		|||
import cards from "~material/cards.module.css";
 | 
			
		||||
import TextField from "~material/TextField.js";
 | 
			
		||||
import Button from "~material/Button.js";
 | 
			
		||||
import { useDocumentTitle } from "../utils";
 | 
			
		||||
import { Title } from "~material/typography";
 | 
			
		||||
import { css } from "solid-styled";
 | 
			
		||||
import { LinearProgress } from "@suid/material";
 | 
			
		||||
import { createRestAPIClient } from "masto";
 | 
			
		||||
import { getOrRegisterApp } from "./stores";
 | 
			
		||||
import { useSearchParams } from "@solidjs/router";
 | 
			
		||||
import { $settings } from "../settings/stores";
 | 
			
		||||
import "./SignIn.css";
 | 
			
		||||
import DocumentTitle from "~platform/DocumentTitle";
 | 
			
		||||
 | 
			
		||||
type ErrorParams = {
 | 
			
		||||
  error: string;
 | 
			
		||||
| 
						 | 
				
			
			@ -36,8 +34,6 @@ const SignIn: Component = () => {
 | 
			
		|||
  const [serverUrlError, setServerUrlError] = createSignal(false);
 | 
			
		||||
  const [targetSiteTitle, setTargetSiteTitle] = createSignal("");
 | 
			
		||||
 | 
			
		||||
  useDocumentTitle("Sign In");
 | 
			
		||||
 | 
			
		||||
  const serverUrl = () => {
 | 
			
		||||
    const url = rawServerUrl();
 | 
			
		||||
    if (url.length === 0 || /^%w:/.test(url)) {
 | 
			
		||||
| 
						 | 
				
			
			@ -115,55 +111,58 @@ const SignIn: Component = () => {
 | 
			
		|||
  };
 | 
			
		||||
 | 
			
		||||
  return (
 | 
			
		||||
    <main class="SignIn">
 | 
			
		||||
      <Show when={params.error || params.errorDescription}>
 | 
			
		||||
        <div class={cards.card} style={{ "margin-bottom": "20px" }}>
 | 
			
		||||
          <p>Authorization is failed.</p>
 | 
			
		||||
          <p>{params.errorDescription}</p>
 | 
			
		||||
          <p>
 | 
			
		||||
            Please try again later. If the problem persists, you can ask for
 | 
			
		||||
            help from the server administrator.
 | 
			
		||||
          </p>
 | 
			
		||||
        </div>
 | 
			
		||||
      </Show>
 | 
			
		||||
      <div
 | 
			
		||||
        class={`${cards.card} key-content`}
 | 
			
		||||
        aria-busy={currentState() !== "inactive" ? "true" : "false"}
 | 
			
		||||
        aria-describedby={
 | 
			
		||||
          currentState() !== "inactive" ? progressId : undefined
 | 
			
		||||
        }
 | 
			
		||||
        style={{
 | 
			
		||||
          padding: `var(--safe-area-inset-top) var(--safe-area-inset-right) var(--safe-area-inset-bottom) var(--safe-area-inset-left)`,
 | 
			
		||||
        }}
 | 
			
		||||
      >
 | 
			
		||||
        <LinearProgress
 | 
			
		||||
          class={[cards.cardNoPad, cards.cardGutSkip].join(" ")}
 | 
			
		||||
          id={progressId}
 | 
			
		||||
          sx={currentState() === "inactive" ? { display: "none" } : undefined}
 | 
			
		||||
        />
 | 
			
		||||
        <form onSubmit={onStartOAuth2}>
 | 
			
		||||
          <Title component="h6">Sign in with Your Mastodon Account</Title>
 | 
			
		||||
          <TextField
 | 
			
		||||
            label="Mastodon Server"
 | 
			
		||||
            name="serverUrl"
 | 
			
		||||
            onInput={setRawServerUrl}
 | 
			
		||||
            required
 | 
			
		||||
            helperText={serverUrlHelperText()}
 | 
			
		||||
            error={!!serverUrlError()}
 | 
			
		||||
          />
 | 
			
		||||
 | 
			
		||||
          <div style={{ display: "flex", "justify-content": "end" }}>
 | 
			
		||||
            <Button type="submit" disabled={currentState() !== "inactive"}>
 | 
			
		||||
              {currentState() == "inactive"
 | 
			
		||||
                ? "Continue"
 | 
			
		||||
                : currentState() == "contracting"
 | 
			
		||||
                  ? `Contracting ${new URL(serverUrl()).host}...`
 | 
			
		||||
                  : `Moving to ${targetSiteTitle}`}
 | 
			
		||||
            </Button>
 | 
			
		||||
    <>
 | 
			
		||||
      <DocumentTitle>Sign In</DocumentTitle>
 | 
			
		||||
      <main class="SignIn">
 | 
			
		||||
        <Show when={params.error || params.errorDescription}>
 | 
			
		||||
          <div class={cards.card} style={{ "margin-bottom": "20px" }}>
 | 
			
		||||
            <p>Authorization is failed.</p>
 | 
			
		||||
            <p>{params.errorDescription}</p>
 | 
			
		||||
            <p>
 | 
			
		||||
              Please try again later. If the problem persists, you can ask for
 | 
			
		||||
              help from the server administrator.
 | 
			
		||||
            </p>
 | 
			
		||||
          </div>
 | 
			
		||||
        </form>
 | 
			
		||||
      </div>
 | 
			
		||||
    </main>
 | 
			
		||||
        </Show>
 | 
			
		||||
        <div
 | 
			
		||||
          class={`${cards.card} key-content`}
 | 
			
		||||
          aria-busy={currentState() !== "inactive" ? "true" : "false"}
 | 
			
		||||
          aria-describedby={
 | 
			
		||||
            currentState() !== "inactive" ? progressId : undefined
 | 
			
		||||
          }
 | 
			
		||||
          style={{
 | 
			
		||||
            padding: `var(--safe-area-inset-top) var(--safe-area-inset-right) var(--safe-area-inset-bottom) var(--safe-area-inset-left)`,
 | 
			
		||||
          }}
 | 
			
		||||
        >
 | 
			
		||||
          <LinearProgress
 | 
			
		||||
            class={[cards.cardNoPad, cards.cardGutSkip].join(" ")}
 | 
			
		||||
            id={progressId}
 | 
			
		||||
            sx={currentState() === "inactive" ? { display: "none" } : undefined}
 | 
			
		||||
          />
 | 
			
		||||
          <form onSubmit={onStartOAuth2}>
 | 
			
		||||
            <Title component="h6">Sign in with Your Mastodon Account</Title>
 | 
			
		||||
            <TextField
 | 
			
		||||
              label="Mastodon Server"
 | 
			
		||||
              name="serverUrl"
 | 
			
		||||
              onInput={setRawServerUrl}
 | 
			
		||||
              required
 | 
			
		||||
              helperText={serverUrlHelperText()}
 | 
			
		||||
              error={!!serverUrlError()}
 | 
			
		||||
            />
 | 
			
		||||
 | 
			
		||||
            <div style={{ display: "flex", "justify-content": "end" }}>
 | 
			
		||||
              <Button type="submit" disabled={currentState() !== "inactive"}>
 | 
			
		||||
                {currentState() == "inactive"
 | 
			
		||||
                  ? "Continue"
 | 
			
		||||
                  : currentState() == "contracting"
 | 
			
		||||
                    ? `Contracting ${new URL(serverUrl()).host}...`
 | 
			
		||||
                    : `Moving to ${targetSiteTitle}`}
 | 
			
		||||
              </Button>
 | 
			
		||||
            </div>
 | 
			
		||||
          </form>
 | 
			
		||||
        </div>
 | 
			
		||||
      </main>
 | 
			
		||||
    </>
 | 
			
		||||
  );
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -3,14 +3,12 @@ import {
 | 
			
		|||
  splitProps,
 | 
			
		||||
  Component,
 | 
			
		||||
  createSignal,
 | 
			
		||||
  createEffect,
 | 
			
		||||
  onMount,
 | 
			
		||||
  createRenderEffect,
 | 
			
		||||
  Show,
 | 
			
		||||
} from "solid-js";
 | 
			
		||||
import { css } from "solid-styled";
 | 
			
		||||
import { decode } from "blurhash";
 | 
			
		||||
import { mergeClass } from "../utils";
 | 
			
		||||
 | 
			
		||||
type ImgProps = {
 | 
			
		||||
  blurhash?: string;
 | 
			
		||||
| 
						 | 
				
			
			@ -24,6 +22,7 @@ const Img: Component<ImgProps> = (props) => {
 | 
			
		|||
    "blurhash",
 | 
			
		||||
    "keepBlur",
 | 
			
		||||
    "class",
 | 
			
		||||
    "classList",
 | 
			
		||||
    "style",
 | 
			
		||||
  ]);
 | 
			
		||||
  const [isImgLoaded, setIsImgLoaded] = createSignal(false);
 | 
			
		||||
| 
						 | 
				
			
			@ -61,21 +60,21 @@ const Img: Component<ImgProps> = (props) => {
 | 
			
		|||
  const onImgLoaded = () => {
 | 
			
		||||
    setIsImgLoaded(true);
 | 
			
		||||
    setImgSize({
 | 
			
		||||
      width: imgE.width,
 | 
			
		||||
      height: imgE.height,
 | 
			
		||||
      width: imgE!.width,
 | 
			
		||||
      height: imgE!.height,
 | 
			
		||||
    });
 | 
			
		||||
  };
 | 
			
		||||
 | 
			
		||||
  const onMetadataLoaded = () => {
 | 
			
		||||
    setImgSize({
 | 
			
		||||
      width: imgE.width,
 | 
			
		||||
      height: imgE.height,
 | 
			
		||||
      width: imgE!.width,
 | 
			
		||||
      height: imgE!.height,
 | 
			
		||||
    });
 | 
			
		||||
  };
 | 
			
		||||
 | 
			
		||||
  onMount(() => {
 | 
			
		||||
    setImgSize((x) => {
 | 
			
		||||
      const parent = imgE.parentElement;
 | 
			
		||||
      const parent = imgE!.parentElement;
 | 
			
		||||
      if (!parent) return x;
 | 
			
		||||
      return x
 | 
			
		||||
        ? x
 | 
			
		||||
| 
						 | 
				
			
			@ -87,7 +86,14 @@ const Img: Component<ImgProps> = (props) => {
 | 
			
		|||
  });
 | 
			
		||||
 | 
			
		||||
  return (
 | 
			
		||||
    <div class={mergeClass(managed.class, "img-root")} style={managed.style}>
 | 
			
		||||
    <div
 | 
			
		||||
      classList={{
 | 
			
		||||
        ...managed.classList,
 | 
			
		||||
        [managed.class ?? ""]: true,
 | 
			
		||||
        "img-root": true,
 | 
			
		||||
      }}
 | 
			
		||||
      style={managed.style}
 | 
			
		||||
    >
 | 
			
		||||
      <Show when={managed.blurhash}>
 | 
			
		||||
        <canvas
 | 
			
		||||
          ref={(canvas) => {
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
							
								
								
									
										22
									
								
								src/platform/DocumentTitle.tsx
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										22
									
								
								src/platform/DocumentTitle.tsx
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,22 @@
 | 
			
		|||
import { children, createRenderEffect, onCleanup, type JSX } from "solid-js";
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Document title.
 | 
			
		||||
 *
 | 
			
		||||
 * The `children` must be plain text.
 | 
			
		||||
 */
 | 
			
		||||
export default function (props: { children?: JSX.Element }) {
 | 
			
		||||
  let otitle: string | undefined;
 | 
			
		||||
 | 
			
		||||
  createRenderEffect(() => (otitle = document.title));
 | 
			
		||||
 | 
			
		||||
  const title = children(() => props.children);
 | 
			
		||||
 | 
			
		||||
  createRenderEffect(
 | 
			
		||||
    () => (document.title = (title.toArray() as string[]).join("")),
 | 
			
		||||
  );
 | 
			
		||||
 | 
			
		||||
  onCleanup(() => (document.title = otitle!));
 | 
			
		||||
 | 
			
		||||
  return <></>;
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -65,6 +65,7 @@ import {
 | 
			
		|||
} from "../timelines/toots/ItemSelectionProvider";
 | 
			
		||||
import AppTopBar from "~material/AppTopBar";
 | 
			
		||||
import type { Account } from "../accounts/stores";
 | 
			
		||||
import DocumentTitle from "~platform/DocumentTitle";
 | 
			
		||||
 | 
			
		||||
const Profile: Component = () => {
 | 
			
		||||
  const { pop } = useNavigator();
 | 
			
		||||
| 
						 | 
				
			
			@ -216,355 +217,360 @@ const Profile: Component = () => {
 | 
			
		|||
  };
 | 
			
		||||
 | 
			
		||||
  return (
 | 
			
		||||
    <Scaffold
 | 
			
		||||
      topbar={
 | 
			
		||||
        <AppTopBar
 | 
			
		||||
          role="navigation"
 | 
			
		||||
          position="static"
 | 
			
		||||
          color={scrolledPastBanner() ? "primary" : "transparent"}
 | 
			
		||||
          elevation={scrolledPastBanner() ? undefined : 0}
 | 
			
		||||
          style={{
 | 
			
		||||
            color: scrolledPastBanner()
 | 
			
		||||
              ? undefined
 | 
			
		||||
              : bannerSampledColors()?.text,
 | 
			
		||||
          }}
 | 
			
		||||
        >
 | 
			
		||||
          <IconButton color="inherit" onClick={[pop, 1]} aria-label="Close">
 | 
			
		||||
            <Close />
 | 
			
		||||
          </IconButton>
 | 
			
		||||
          <Title
 | 
			
		||||
            class="Profile__page-title"
 | 
			
		||||
    <>
 | 
			
		||||
      <DocumentTitle>{profile()?.displayName ?? "Someone"}</DocumentTitle>
 | 
			
		||||
      <Scaffold
 | 
			
		||||
        topbar={
 | 
			
		||||
          <AppTopBar
 | 
			
		||||
            role="navigation"
 | 
			
		||||
            position="static"
 | 
			
		||||
            color={scrolledPastBanner() ? "primary" : "transparent"}
 | 
			
		||||
            elevation={scrolledPastBanner() ? undefined : 0}
 | 
			
		||||
            style={{
 | 
			
		||||
              visibility: scrolledPastBanner() ? undefined : "hidden",
 | 
			
		||||
              color: scrolledPastBanner()
 | 
			
		||||
                ? undefined
 | 
			
		||||
                : bannerSampledColors()?.text,
 | 
			
		||||
            }}
 | 
			
		||||
            innerHTML={displayName()}
 | 
			
		||||
          ></Title>
 | 
			
		||||
 | 
			
		||||
          <IconButton
 | 
			
		||||
            id={menuButId}
 | 
			
		||||
            aria-controls={optMenuId}
 | 
			
		||||
            color="inherit"
 | 
			
		||||
            onClick={[setMenuOpen, true]}
 | 
			
		||||
            aria-label="Open Options for the Profile"
 | 
			
		||||
          >
 | 
			
		||||
            <MoreVert />
 | 
			
		||||
          </IconButton>
 | 
			
		||||
        </AppTopBar>
 | 
			
		||||
      }
 | 
			
		||||
      class="Profile"
 | 
			
		||||
    >
 | 
			
		||||
      <div class="details" role="presentation">
 | 
			
		||||
        <Menu
 | 
			
		||||
          id={optMenuId}
 | 
			
		||||
          open={menuOpen()}
 | 
			
		||||
          onClose={[setMenuOpen, false]}
 | 
			
		||||
          anchor={() =>
 | 
			
		||||
            document.getElementById(menuButId)!.getBoundingClientRect()
 | 
			
		||||
          }
 | 
			
		||||
          aria-label="Options for the Profile"
 | 
			
		||||
        >
 | 
			
		||||
          <Show when={session().account}>
 | 
			
		||||
            <MenuItem>
 | 
			
		||||
              <ListItemAvatar>
 | 
			
		||||
                <Avatar src={(session().account as Account).inf?.avatar} />
 | 
			
		||||
              </ListItemAvatar>
 | 
			
		||||
              <ListItemText secondary={"Default account"}>
 | 
			
		||||
                <span innerHTML={sessionDisplayName()}></span>
 | 
			
		||||
              </ListItemText>
 | 
			
		||||
              {/* <ArrowRight /> // for future */}
 | 
			
		||||
            </MenuItem>
 | 
			
		||||
          </Show>
 | 
			
		||||
          <Show when={session().account && profile()}>
 | 
			
		||||
            <Show
 | 
			
		||||
              when={isCurrentSessionProfile()}
 | 
			
		||||
              fallback={
 | 
			
		||||
                <MenuItem
 | 
			
		||||
                  onClick={(event) => {
 | 
			
		||||
                    const { left, right, top } =
 | 
			
		||||
                      event.currentTarget.getBoundingClientRect();
 | 
			
		||||
                    openSubscribeMenu({
 | 
			
		||||
                      left,
 | 
			
		||||
                      right,
 | 
			
		||||
                      top,
 | 
			
		||||
                      e: 1,
 | 
			
		||||
                    });
 | 
			
		||||
                  }}
 | 
			
		||||
                >
 | 
			
		||||
                  <ListItemIcon>
 | 
			
		||||
                    <PlaylistAdd />
 | 
			
		||||
                  </ListItemIcon>
 | 
			
		||||
                  <ListItemText>Subscribe...</ListItemText>
 | 
			
		||||
                </MenuItem>
 | 
			
		||||
              }
 | 
			
		||||
            <IconButton color="inherit" onClick={[pop, 1]} aria-label="Close">
 | 
			
		||||
              <Close />
 | 
			
		||||
            </IconButton>
 | 
			
		||||
            <Title
 | 
			
		||||
              class="Profile__page-title"
 | 
			
		||||
              style={{
 | 
			
		||||
                visibility: scrolledPastBanner() ? undefined : "hidden",
 | 
			
		||||
              }}
 | 
			
		||||
              innerHTML={displayName()}
 | 
			
		||||
            ></Title>
 | 
			
		||||
 | 
			
		||||
            <IconButton
 | 
			
		||||
              id={menuButId}
 | 
			
		||||
              aria-controls={optMenuId}
 | 
			
		||||
              color="inherit"
 | 
			
		||||
              onClick={[setMenuOpen, true]}
 | 
			
		||||
              aria-label="Open Options for the Profile"
 | 
			
		||||
            >
 | 
			
		||||
              <MenuItem disabled>
 | 
			
		||||
                <ListItemIcon>
 | 
			
		||||
                  <Edit />
 | 
			
		||||
                </ListItemIcon>
 | 
			
		||||
                <ListItemText>Edit...</ListItemText>
 | 
			
		||||
              <MoreVert />
 | 
			
		||||
            </IconButton>
 | 
			
		||||
          </AppTopBar>
 | 
			
		||||
        }
 | 
			
		||||
        class="Profile"
 | 
			
		||||
      >
 | 
			
		||||
        <div class="details" role="presentation">
 | 
			
		||||
          <Menu
 | 
			
		||||
            id={optMenuId}
 | 
			
		||||
            open={menuOpen()}
 | 
			
		||||
            onClose={[setMenuOpen, false]}
 | 
			
		||||
            anchor={() =>
 | 
			
		||||
              document.getElementById(menuButId)!.getBoundingClientRect()
 | 
			
		||||
            }
 | 
			
		||||
            aria-label="Options for the Profile"
 | 
			
		||||
          >
 | 
			
		||||
            <Show when={session().account}>
 | 
			
		||||
              <MenuItem>
 | 
			
		||||
                <ListItemAvatar>
 | 
			
		||||
                  <Avatar src={(session().account as Account).inf?.avatar} />
 | 
			
		||||
                </ListItemAvatar>
 | 
			
		||||
                <ListItemText secondary={"Default account"}>
 | 
			
		||||
                  <span innerHTML={sessionDisplayName()}></span>
 | 
			
		||||
                </ListItemText>
 | 
			
		||||
                {/* <ArrowRight /> // for future */}
 | 
			
		||||
              </MenuItem>
 | 
			
		||||
            </Show>
 | 
			
		||||
            <Divider />
 | 
			
		||||
          </Show>
 | 
			
		||||
          <MenuItem disabled>
 | 
			
		||||
            <ListItemIcon>
 | 
			
		||||
              <Group />
 | 
			
		||||
            </ListItemIcon>
 | 
			
		||||
            <ListItemText>Followers</ListItemText>
 | 
			
		||||
            <ListItemSecondaryAction>
 | 
			
		||||
              <span aria-label="The number of the account follower">
 | 
			
		||||
                {profile()?.followersCount ?? ""}
 | 
			
		||||
              </span>
 | 
			
		||||
            </ListItemSecondaryAction>
 | 
			
		||||
          </MenuItem>
 | 
			
		||||
          <MenuItem disabled>
 | 
			
		||||
            <ListItemIcon>
 | 
			
		||||
              <Subject />
 | 
			
		||||
            </ListItemIcon>
 | 
			
		||||
            <ListItemText>Following</ListItemText>
 | 
			
		||||
            <ListItemSecondaryAction>
 | 
			
		||||
              <span aria-label="The number the account following">
 | 
			
		||||
                {profile()?.followingCount ?? ""}
 | 
			
		||||
              </span>
 | 
			
		||||
            </ListItemSecondaryAction>
 | 
			
		||||
          </MenuItem>
 | 
			
		||||
          <MenuItem disabled>
 | 
			
		||||
            <ListItemIcon>
 | 
			
		||||
              <PersonOff />
 | 
			
		||||
            </ListItemIcon>
 | 
			
		||||
            <ListItemText>Blocklist</ListItemText>
 | 
			
		||||
          </MenuItem>
 | 
			
		||||
          <MenuItem disabled>
 | 
			
		||||
            <ListItemIcon>
 | 
			
		||||
              <Send />
 | 
			
		||||
            </ListItemIcon>
 | 
			
		||||
            <ListItemText>Mention in...</ListItemText>
 | 
			
		||||
          </MenuItem>
 | 
			
		||||
          <Divider />
 | 
			
		||||
          <MenuItem
 | 
			
		||||
            component={"a"}
 | 
			
		||||
            href={profile()?.url}
 | 
			
		||||
            target="_blank"
 | 
			
		||||
            rel="noopener noreferrer"
 | 
			
		||||
          >
 | 
			
		||||
            <ListItemIcon>
 | 
			
		||||
              <OpenInBrowser />
 | 
			
		||||
            </ListItemIcon>
 | 
			
		||||
            <ListItemText>Open in browser...</ListItemText>
 | 
			
		||||
          </MenuItem>
 | 
			
		||||
          <MenuItem onClick={() => share({ url: profile()?.url })}>
 | 
			
		||||
            <ListItemIcon>
 | 
			
		||||
              <Share />
 | 
			
		||||
            </ListItemIcon>
 | 
			
		||||
            <ListItemText>Share...</ListItemText>
 | 
			
		||||
          </MenuItem>
 | 
			
		||||
        </Menu>
 | 
			
		||||
        <div
 | 
			
		||||
          style={{
 | 
			
		||||
            height: `${268 * (Math.min(560, windowSize.width) / 560)}px`,
 | 
			
		||||
          }}
 | 
			
		||||
          class="banner"
 | 
			
		||||
          role="presentation"
 | 
			
		||||
        >
 | 
			
		||||
          <img
 | 
			
		||||
            ref={(e) => obx.observe(e)}
 | 
			
		||||
            src={bannerImg()}
 | 
			
		||||
            crossOrigin="anonymous"
 | 
			
		||||
            alt={`Banner image for ${profile()?.displayName || "the user"}`}
 | 
			
		||||
            onLoad={(event) => {
 | 
			
		||||
              const ins = new FastAverageColor();
 | 
			
		||||
              const colors = ins.getColor(event.currentTarget);
 | 
			
		||||
              setBannerSampledColors({
 | 
			
		||||
                average: colors.hex,
 | 
			
		||||
                text: colors.isDark ? "white" : "black",
 | 
			
		||||
              });
 | 
			
		||||
              ins.destroy();
 | 
			
		||||
            }}
 | 
			
		||||
          ></img>
 | 
			
		||||
        </div>
 | 
			
		||||
 | 
			
		||||
        <Menu {...subscribeMenuState}>
 | 
			
		||||
          <MenuItem
 | 
			
		||||
            onClick={toggleSubscribeHome}
 | 
			
		||||
            aria-label={`${relationship()?.following ? "Unfollow" : "Follow"} on your home timeline`}
 | 
			
		||||
          >
 | 
			
		||||
            <ListItemAvatar>
 | 
			
		||||
              <Avatar src={(session().account as Account).inf?.avatar}></Avatar>
 | 
			
		||||
            </ListItemAvatar>
 | 
			
		||||
            <ListItemText
 | 
			
		||||
              secondary={
 | 
			
		||||
                relationship()?.following
 | 
			
		||||
                  ? undefined
 | 
			
		||||
                  : profile()?.locked
 | 
			
		||||
                    ? "A request will be sent"
 | 
			
		||||
                    : undefined
 | 
			
		||||
              }
 | 
			
		||||
            >
 | 
			
		||||
              <span innerHTML={sessionDisplayName()}></span>
 | 
			
		||||
              <span>'s Home</span>
 | 
			
		||||
            </ListItemText>
 | 
			
		||||
 | 
			
		||||
            <Checkbox checked={relationship()?.following ?? false} />
 | 
			
		||||
          </MenuItem>
 | 
			
		||||
        </Menu>
 | 
			
		||||
 | 
			
		||||
        <div
 | 
			
		||||
          class="intro"
 | 
			
		||||
          style={{
 | 
			
		||||
            "background-color": bannerSampledColors()?.average,
 | 
			
		||||
            color: bannerSampledColors()?.text,
 | 
			
		||||
          }}
 | 
			
		||||
        >
 | 
			
		||||
          <section class="acct-grp">
 | 
			
		||||
            <Avatar
 | 
			
		||||
              src={avatarImg()}
 | 
			
		||||
              alt={`${profile()?.displayName || "the user"}'s avatar`}
 | 
			
		||||
              sx={{
 | 
			
		||||
                marginTop: "calc(-16px - 72px / 2)",
 | 
			
		||||
                width: "72px",
 | 
			
		||||
                height: "72px",
 | 
			
		||||
              }}
 | 
			
		||||
            ></Avatar>
 | 
			
		||||
            <div class="name-grp">
 | 
			
		||||
              <div class="display-name">
 | 
			
		||||
                <Show when={profile()?.bot}>
 | 
			
		||||
                  <SmartToySharp class="acct-mark" aria-label="Bot" />
 | 
			
		||||
                </Show>
 | 
			
		||||
                <Show when={profile()?.locked}>
 | 
			
		||||
                  <Lock class="acct-mark" aria-label="Locked" />
 | 
			
		||||
                </Show>
 | 
			
		||||
                <Body2
 | 
			
		||||
                  component="span"
 | 
			
		||||
                  innerHTML={displayName()}
 | 
			
		||||
                  aria-label="Display name"
 | 
			
		||||
                ></Body2>
 | 
			
		||||
              </div>
 | 
			
		||||
              <span aria-label="Complete username" class="username">
 | 
			
		||||
                {fullUsername()}
 | 
			
		||||
              </span>
 | 
			
		||||
            </div>
 | 
			
		||||
            <div role="presentation">
 | 
			
		||||
              <Switch>
 | 
			
		||||
                <Match
 | 
			
		||||
                  when={
 | 
			
		||||
                    !session().account ||
 | 
			
		||||
                    profileUncaught.loading ||
 | 
			
		||||
                    profileUncaught.error
 | 
			
		||||
                  }
 | 
			
		||||
                >
 | 
			
		||||
                  {<></>}
 | 
			
		||||
                </Match>
 | 
			
		||||
                <Match when={isCurrentSessionProfile()}>
 | 
			
		||||
                  <IconButton color="inherit">
 | 
			
		||||
                    <Edit />
 | 
			
		||||
                  </IconButton>
 | 
			
		||||
                </Match>
 | 
			
		||||
                <Match when={true}>
 | 
			
		||||
                  <Button
 | 
			
		||||
                    variant="contained"
 | 
			
		||||
                    color="secondary"
 | 
			
		||||
            <Show when={session().account && profile()}>
 | 
			
		||||
              <Show
 | 
			
		||||
                when={isCurrentSessionProfile()}
 | 
			
		||||
                fallback={
 | 
			
		||||
                  <MenuItem
 | 
			
		||||
                    onClick={(event) => {
 | 
			
		||||
                      openSubscribeMenu(
 | 
			
		||||
                        event.currentTarget.getBoundingClientRect(),
 | 
			
		||||
                      );
 | 
			
		||||
                      const { left, right, top } =
 | 
			
		||||
                        event.currentTarget.getBoundingClientRect();
 | 
			
		||||
                      openSubscribeMenu({
 | 
			
		||||
                        left,
 | 
			
		||||
                        right,
 | 
			
		||||
                        top,
 | 
			
		||||
                        e: 1,
 | 
			
		||||
                      });
 | 
			
		||||
                    }}
 | 
			
		||||
                  >
 | 
			
		||||
                    {relationship()?.following ? "Subscribed" : "Subscribe"}
 | 
			
		||||
                  </Button>
 | 
			
		||||
                </Match>
 | 
			
		||||
              </Switch>
 | 
			
		||||
            </div>
 | 
			
		||||
          </section>
 | 
			
		||||
          <section
 | 
			
		||||
            class="description"
 | 
			
		||||
            aria-label={`${profile()?.displayName || "the user"}'s description`}
 | 
			
		||||
            innerHTML={description() || ""}
 | 
			
		||||
          ></section>
 | 
			
		||||
 | 
			
		||||
          <table
 | 
			
		||||
            class="acct-fields"
 | 
			
		||||
            aria-label={`${profile()?.displayName || "the user"}'s 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 innerHTML={item.value}></td>
 | 
			
		||||
                    </tr>
 | 
			
		||||
                  );
 | 
			
		||||
                }}
 | 
			
		||||
              </For>
 | 
			
		||||
            </tbody>
 | 
			
		||||
          </table>
 | 
			
		||||
        </div>
 | 
			
		||||
      </div>
 | 
			
		||||
 | 
			
		||||
      <div class="recent-toots" role="presentation">
 | 
			
		||||
        <div class="toot-list-toolbar">
 | 
			
		||||
          <TootFilterButton
 | 
			
		||||
            options={{
 | 
			
		||||
              pinned: "Pinneds",
 | 
			
		||||
              boost: "Boosts",
 | 
			
		||||
              reply: "Replies",
 | 
			
		||||
              original: "Originals",
 | 
			
		||||
            }}
 | 
			
		||||
            applied={recentTootFilter()}
 | 
			
		||||
            onApply={setRecentTootFilter}
 | 
			
		||||
            disabledKeys={["original"]}
 | 
			
		||||
          ></TootFilterButton>
 | 
			
		||||
        </div>
 | 
			
		||||
 | 
			
		||||
        <ItemSelectionProvider value={selectionState}>
 | 
			
		||||
          <TimeSourceProvider value={time}>
 | 
			
		||||
            <Show
 | 
			
		||||
              when={recentTootFilter().pinned && pinnedToots.list.length > 0}
 | 
			
		||||
            >
 | 
			
		||||
              <TootList
 | 
			
		||||
                threads={pinnedToots.list}
 | 
			
		||||
                onUnknownThread={pinnedToots.getPath}
 | 
			
		||||
                onChangeToot={pinnedToots.set}
 | 
			
		||||
              />
 | 
			
		||||
                    <ListItemIcon>
 | 
			
		||||
                      <PlaylistAdd />
 | 
			
		||||
                    </ListItemIcon>
 | 
			
		||||
                    <ListItemText>Subscribe...</ListItemText>
 | 
			
		||||
                  </MenuItem>
 | 
			
		||||
                }
 | 
			
		||||
              >
 | 
			
		||||
                <MenuItem disabled>
 | 
			
		||||
                  <ListItemIcon>
 | 
			
		||||
                    <Edit />
 | 
			
		||||
                  </ListItemIcon>
 | 
			
		||||
                  <ListItemText>Edit...</ListItemText>
 | 
			
		||||
                </MenuItem>
 | 
			
		||||
              </Show>
 | 
			
		||||
              <Divider />
 | 
			
		||||
            </Show>
 | 
			
		||||
            <TootList
 | 
			
		||||
              id={recentTootListId}
 | 
			
		||||
              threads={recentToots.list}
 | 
			
		||||
              onUnknownThread={recentToots.getPath}
 | 
			
		||||
              onChangeToot={recentToots.set}
 | 
			
		||||
            />
 | 
			
		||||
          </TimeSourceProvider>
 | 
			
		||||
        </ItemSelectionProvider>
 | 
			
		||||
 | 
			
		||||
        <Show when={!recentTootChunk()?.done}>
 | 
			
		||||
            <MenuItem disabled>
 | 
			
		||||
              <ListItemIcon>
 | 
			
		||||
                <Group />
 | 
			
		||||
              </ListItemIcon>
 | 
			
		||||
              <ListItemText>Followers</ListItemText>
 | 
			
		||||
              <ListItemSecondaryAction>
 | 
			
		||||
                <span aria-label="The number of the account follower">
 | 
			
		||||
                  {profile()?.followersCount ?? ""}
 | 
			
		||||
                </span>
 | 
			
		||||
              </ListItemSecondaryAction>
 | 
			
		||||
            </MenuItem>
 | 
			
		||||
            <MenuItem disabled>
 | 
			
		||||
              <ListItemIcon>
 | 
			
		||||
                <Subject />
 | 
			
		||||
              </ListItemIcon>
 | 
			
		||||
              <ListItemText>Following</ListItemText>
 | 
			
		||||
              <ListItemSecondaryAction>
 | 
			
		||||
                <span aria-label="The number the account following">
 | 
			
		||||
                  {profile()?.followingCount ?? ""}
 | 
			
		||||
                </span>
 | 
			
		||||
              </ListItemSecondaryAction>
 | 
			
		||||
            </MenuItem>
 | 
			
		||||
            <MenuItem disabled>
 | 
			
		||||
              <ListItemIcon>
 | 
			
		||||
                <PersonOff />
 | 
			
		||||
              </ListItemIcon>
 | 
			
		||||
              <ListItemText>Blocklist</ListItemText>
 | 
			
		||||
            </MenuItem>
 | 
			
		||||
            <MenuItem disabled>
 | 
			
		||||
              <ListItemIcon>
 | 
			
		||||
                <Send />
 | 
			
		||||
              </ListItemIcon>
 | 
			
		||||
              <ListItemText>Mention in...</ListItemText>
 | 
			
		||||
            </MenuItem>
 | 
			
		||||
            <Divider />
 | 
			
		||||
            <MenuItem
 | 
			
		||||
              component={"a"}
 | 
			
		||||
              href={profile()?.url}
 | 
			
		||||
              target="_blank"
 | 
			
		||||
              rel="noopener noreferrer"
 | 
			
		||||
            >
 | 
			
		||||
              <ListItemIcon>
 | 
			
		||||
                <OpenInBrowser />
 | 
			
		||||
              </ListItemIcon>
 | 
			
		||||
              <ListItemText>Open in browser...</ListItemText>
 | 
			
		||||
            </MenuItem>
 | 
			
		||||
            <MenuItem onClick={() => share({ url: profile()?.url })}>
 | 
			
		||||
              <ListItemIcon>
 | 
			
		||||
                <Share />
 | 
			
		||||
              </ListItemIcon>
 | 
			
		||||
              <ListItemText>Share...</ListItemText>
 | 
			
		||||
            </MenuItem>
 | 
			
		||||
          </Menu>
 | 
			
		||||
          <div
 | 
			
		||||
            style={{
 | 
			
		||||
              "text-align": "center",
 | 
			
		||||
              "padding-bottom": "var(--safe-area-inset-bottom)",
 | 
			
		||||
              height: `${268 * (Math.min(560, windowSize.width) / 560)}px`,
 | 
			
		||||
            }}
 | 
			
		||||
            class="banner"
 | 
			
		||||
            role="presentation"
 | 
			
		||||
          >
 | 
			
		||||
            <img
 | 
			
		||||
              ref={(e) => obx.observe(e)}
 | 
			
		||||
              src={bannerImg()}
 | 
			
		||||
              crossOrigin="anonymous"
 | 
			
		||||
              alt={`Banner image for ${profile()?.displayName || "the user"}`}
 | 
			
		||||
              onLoad={(event) => {
 | 
			
		||||
                const ins = new FastAverageColor();
 | 
			
		||||
                const colors = ins.getColor(event.currentTarget);
 | 
			
		||||
                setBannerSampledColors({
 | 
			
		||||
                  average: colors.hex,
 | 
			
		||||
                  text: colors.isDark ? "white" : "black",
 | 
			
		||||
                });
 | 
			
		||||
                ins.destroy();
 | 
			
		||||
              }}
 | 
			
		||||
            ></img>
 | 
			
		||||
          </div>
 | 
			
		||||
 | 
			
		||||
          <Menu {...subscribeMenuState}>
 | 
			
		||||
            <MenuItem
 | 
			
		||||
              onClick={toggleSubscribeHome}
 | 
			
		||||
              aria-label={`${relationship()?.following ? "Unfollow" : "Follow"} on your home timeline`}
 | 
			
		||||
            >
 | 
			
		||||
              <ListItemAvatar>
 | 
			
		||||
                <Avatar
 | 
			
		||||
                  src={(session().account as Account).inf?.avatar}
 | 
			
		||||
                ></Avatar>
 | 
			
		||||
              </ListItemAvatar>
 | 
			
		||||
              <ListItemText
 | 
			
		||||
                secondary={
 | 
			
		||||
                  relationship()?.following
 | 
			
		||||
                    ? undefined
 | 
			
		||||
                    : profile()?.locked
 | 
			
		||||
                      ? "A request will be sent"
 | 
			
		||||
                      : undefined
 | 
			
		||||
                }
 | 
			
		||||
              >
 | 
			
		||||
                <span innerHTML={sessionDisplayName()}></span>
 | 
			
		||||
                <span>'s Home</span>
 | 
			
		||||
              </ListItemText>
 | 
			
		||||
 | 
			
		||||
              <Checkbox checked={relationship()?.following ?? false} />
 | 
			
		||||
            </MenuItem>
 | 
			
		||||
          </Menu>
 | 
			
		||||
 | 
			
		||||
          <div
 | 
			
		||||
            class="intro"
 | 
			
		||||
            style={{
 | 
			
		||||
              "background-color": bannerSampledColors()?.average,
 | 
			
		||||
              color: bannerSampledColors()?.text,
 | 
			
		||||
            }}
 | 
			
		||||
          >
 | 
			
		||||
            <IconButton
 | 
			
		||||
              aria-label="Load More"
 | 
			
		||||
              aria-controls={recentTootListId}
 | 
			
		||||
              size="large"
 | 
			
		||||
              color="primary"
 | 
			
		||||
              onClick={[refetchRecentToots, "prev"]}
 | 
			
		||||
              disabled={isTootListLoading()}
 | 
			
		||||
            <section class="acct-grp">
 | 
			
		||||
              <Avatar
 | 
			
		||||
                src={avatarImg()}
 | 
			
		||||
                alt={`${profile()?.displayName || "the user"}'s avatar`}
 | 
			
		||||
                sx={{
 | 
			
		||||
                  marginTop: "calc(-16px - 72px / 2)",
 | 
			
		||||
                  width: "72px",
 | 
			
		||||
                  height: "72px",
 | 
			
		||||
                }}
 | 
			
		||||
              ></Avatar>
 | 
			
		||||
              <div class="name-grp">
 | 
			
		||||
                <div class="display-name">
 | 
			
		||||
                  <Show when={profile()?.bot}>
 | 
			
		||||
                    <SmartToySharp class="acct-mark" aria-label="Bot" />
 | 
			
		||||
                  </Show>
 | 
			
		||||
                  <Show when={profile()?.locked}>
 | 
			
		||||
                    <Lock class="acct-mark" aria-label="Locked" />
 | 
			
		||||
                  </Show>
 | 
			
		||||
                  <Body2
 | 
			
		||||
                    component="span"
 | 
			
		||||
                    innerHTML={displayName()}
 | 
			
		||||
                    aria-label="Display name"
 | 
			
		||||
                  ></Body2>
 | 
			
		||||
                </div>
 | 
			
		||||
                <span aria-label="Complete username" class="username">
 | 
			
		||||
                  {fullUsername()}
 | 
			
		||||
                </span>
 | 
			
		||||
              </div>
 | 
			
		||||
              <div role="presentation">
 | 
			
		||||
                <Switch>
 | 
			
		||||
                  <Match
 | 
			
		||||
                    when={
 | 
			
		||||
                      !session().account ||
 | 
			
		||||
                      profileUncaught.loading ||
 | 
			
		||||
                      profileUncaught.error
 | 
			
		||||
                    }
 | 
			
		||||
                  >
 | 
			
		||||
                    {<></>}
 | 
			
		||||
                  </Match>
 | 
			
		||||
                  <Match when={isCurrentSessionProfile()}>
 | 
			
		||||
                    <IconButton color="inherit">
 | 
			
		||||
                      <Edit />
 | 
			
		||||
                    </IconButton>
 | 
			
		||||
                  </Match>
 | 
			
		||||
                  <Match when={true}>
 | 
			
		||||
                    <Button
 | 
			
		||||
                      variant="contained"
 | 
			
		||||
                      color="secondary"
 | 
			
		||||
                      onClick={(event) => {
 | 
			
		||||
                        openSubscribeMenu(
 | 
			
		||||
                          event.currentTarget.getBoundingClientRect(),
 | 
			
		||||
                        );
 | 
			
		||||
                      }}
 | 
			
		||||
                    >
 | 
			
		||||
                      {relationship()?.following ? "Subscribed" : "Subscribe"}
 | 
			
		||||
                    </Button>
 | 
			
		||||
                  </Match>
 | 
			
		||||
                </Switch>
 | 
			
		||||
              </div>
 | 
			
		||||
            </section>
 | 
			
		||||
            <section
 | 
			
		||||
              class="description"
 | 
			
		||||
              aria-label={`${profile()?.displayName || "the user"}'s description`}
 | 
			
		||||
              innerHTML={description() || ""}
 | 
			
		||||
            ></section>
 | 
			
		||||
 | 
			
		||||
            <table
 | 
			
		||||
              class="acct-fields"
 | 
			
		||||
              aria-label={`${profile()?.displayName || "the user"}'s fields`}
 | 
			
		||||
            >
 | 
			
		||||
              <Show when={isTootListLoading()} fallback={<ExpandMore />}>
 | 
			
		||||
                <CircularProgress sx={{ width: "24px", height: "24px" }} />
 | 
			
		||||
              </Show>
 | 
			
		||||
            </IconButton>
 | 
			
		||||
              <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 innerHTML={item.value}></td>
 | 
			
		||||
                      </tr>
 | 
			
		||||
                    );
 | 
			
		||||
                  }}
 | 
			
		||||
                </For>
 | 
			
		||||
              </tbody>
 | 
			
		||||
            </table>
 | 
			
		||||
          </div>
 | 
			
		||||
        </Show>
 | 
			
		||||
      </div>
 | 
			
		||||
    </Scaffold>
 | 
			
		||||
        </div>
 | 
			
		||||
 | 
			
		||||
        <div class="recent-toots" role="presentation">
 | 
			
		||||
          <div class="toot-list-toolbar">
 | 
			
		||||
            <TootFilterButton
 | 
			
		||||
              options={{
 | 
			
		||||
                pinned: "Pinneds",
 | 
			
		||||
                boost: "Boosts",
 | 
			
		||||
                reply: "Replies",
 | 
			
		||||
                original: "Originals",
 | 
			
		||||
              }}
 | 
			
		||||
              applied={recentTootFilter()}
 | 
			
		||||
              onApply={setRecentTootFilter}
 | 
			
		||||
              disabledKeys={["original"]}
 | 
			
		||||
            ></TootFilterButton>
 | 
			
		||||
          </div>
 | 
			
		||||
 | 
			
		||||
          <ItemSelectionProvider value={selectionState}>
 | 
			
		||||
            <TimeSourceProvider value={time}>
 | 
			
		||||
              <Show
 | 
			
		||||
                when={recentTootFilter().pinned && pinnedToots.list.length > 0}
 | 
			
		||||
              >
 | 
			
		||||
                <TootList
 | 
			
		||||
                  threads={pinnedToots.list}
 | 
			
		||||
                  onUnknownThread={pinnedToots.getPath}
 | 
			
		||||
                  onChangeToot={pinnedToots.set}
 | 
			
		||||
                />
 | 
			
		||||
                <Divider />
 | 
			
		||||
              </Show>
 | 
			
		||||
              <TootList
 | 
			
		||||
                id={recentTootListId}
 | 
			
		||||
                threads={recentToots.list}
 | 
			
		||||
                onUnknownThread={recentToots.getPath}
 | 
			
		||||
                onChangeToot={recentToots.set}
 | 
			
		||||
              />
 | 
			
		||||
            </TimeSourceProvider>
 | 
			
		||||
          </ItemSelectionProvider>
 | 
			
		||||
 | 
			
		||||
          <Show when={!recentTootChunk()?.done}>
 | 
			
		||||
            <div
 | 
			
		||||
              style={{
 | 
			
		||||
                "text-align": "center",
 | 
			
		||||
                "padding-bottom": "var(--safe-area-inset-bottom)",
 | 
			
		||||
              }}
 | 
			
		||||
            >
 | 
			
		||||
              <IconButton
 | 
			
		||||
                aria-label="Load More"
 | 
			
		||||
                aria-controls={recentTootListId}
 | 
			
		||||
                size="large"
 | 
			
		||||
                color="primary"
 | 
			
		||||
                onClick={[refetchRecentToots, "prev"]}
 | 
			
		||||
                disabled={isTootListLoading()}
 | 
			
		||||
              >
 | 
			
		||||
                <Show when={isTootListLoading()} fallback={<ExpandMore />}>
 | 
			
		||||
                  <CircularProgress sx={{ width: "24px", height: "24px" }} />
 | 
			
		||||
                </Show>
 | 
			
		||||
              </IconButton>
 | 
			
		||||
            </div>
 | 
			
		||||
          </Show>
 | 
			
		||||
        </div>
 | 
			
		||||
      </Scaffold>
 | 
			
		||||
    </>
 | 
			
		||||
  );
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -26,6 +26,7 @@ import { useStore } from "@nanostores/solid";
 | 
			
		|||
import { $settings } from "./stores";
 | 
			
		||||
import { useNavigator } from "~platform/StackedRouter";
 | 
			
		||||
import AppTopBar from "~material/AppTopBar";
 | 
			
		||||
import DocumentTitle from "~platform/DocumentTitle";
 | 
			
		||||
 | 
			
		||||
const ChooseLang: Component = () => {
 | 
			
		||||
  const { pop } = useNavigator();
 | 
			
		||||
| 
						 | 
				
			
			@ -53,67 +54,70 @@ const ChooseLang: Component = () => {
 | 
			
		|||
  };
 | 
			
		||||
 | 
			
		||||
  return (
 | 
			
		||||
    <Scaffold
 | 
			
		||||
      topbar={
 | 
			
		||||
        <AppTopBar>
 | 
			
		||||
          <IconButton color="inherit" onClick={[pop, 1]} disableRipple>
 | 
			
		||||
            <ArrowBack />
 | 
			
		||||
          </IconButton>
 | 
			
		||||
          <Title>{t("Choose Language")}</Title>
 | 
			
		||||
        </AppTopBar>
 | 
			
		||||
      }
 | 
			
		||||
    >
 | 
			
		||||
      <List
 | 
			
		||||
        sx={{
 | 
			
		||||
          paddingBottom: "var(--safe-area-inset-bottom, 0)",
 | 
			
		||||
        }}
 | 
			
		||||
    <>
 | 
			
		||||
      <DocumentTitle>{t("Choose Language")}</DocumentTitle>
 | 
			
		||||
      <Scaffold
 | 
			
		||||
        topbar={
 | 
			
		||||
          <AppTopBar>
 | 
			
		||||
            <IconButton color="inherit" onClick={[pop, 1]} disableRipple>
 | 
			
		||||
              <ArrowBack />
 | 
			
		||||
            </IconButton>
 | 
			
		||||
            <Title>{t("Choose Language")}</Title>
 | 
			
		||||
          </AppTopBar>
 | 
			
		||||
        }
 | 
			
		||||
      >
 | 
			
		||||
        <ListItemButton
 | 
			
		||||
          onClick={() => {
 | 
			
		||||
            onCodeChange(code() ? undefined : matchedLangCode());
 | 
			
		||||
        <List
 | 
			
		||||
          sx={{
 | 
			
		||||
            paddingBottom: "var(--safe-area-inset-bottom, 0)",
 | 
			
		||||
          }}
 | 
			
		||||
        >
 | 
			
		||||
          <ListItemText>
 | 
			
		||||
            {t("lang.auto", {
 | 
			
		||||
              detected: t(`lang.${matchedLangCode()}`) ?? matchedLangCode(),
 | 
			
		||||
            })}
 | 
			
		||||
          </ListItemText>
 | 
			
		||||
          <ListItemSecondaryAction>
 | 
			
		||||
            <Switch checked={typeof code() === "undefined"} />
 | 
			
		||||
          </ListItemSecondaryAction>
 | 
			
		||||
        </ListItemButton>
 | 
			
		||||
        <List subheader={<ListSubheader>{t("Supported")}</ListSubheader>}>
 | 
			
		||||
          <For each={SUPPORTED_LANGS}>
 | 
			
		||||
            {(c) => (
 | 
			
		||||
              <ListItemButton
 | 
			
		||||
                disabled={typeof code() === "undefined"}
 | 
			
		||||
                onClick={[onCodeChange, c]}
 | 
			
		||||
              >
 | 
			
		||||
                <ListItemText>{t(`lang.${c}`)}</ListItemText>
 | 
			
		||||
                <ListItemSecondaryAction>
 | 
			
		||||
                  <Radio
 | 
			
		||||
                    checked={
 | 
			
		||||
                      code() === c ||
 | 
			
		||||
                      (code() === undefined && matchedLangCode() == c)
 | 
			
		||||
                    }
 | 
			
		||||
                  />
 | 
			
		||||
                </ListItemSecondaryAction>
 | 
			
		||||
              </ListItemButton>
 | 
			
		||||
            )}
 | 
			
		||||
          </For>
 | 
			
		||||
        </List>
 | 
			
		||||
          <ListItemButton
 | 
			
		||||
            onClick={() => {
 | 
			
		||||
              onCodeChange(code() ? undefined : matchedLangCode());
 | 
			
		||||
            }}
 | 
			
		||||
          >
 | 
			
		||||
            <ListItemText>
 | 
			
		||||
              {t("lang.auto", {
 | 
			
		||||
                detected: t(`lang.${matchedLangCode()}`) ?? matchedLangCode(),
 | 
			
		||||
              })}
 | 
			
		||||
            </ListItemText>
 | 
			
		||||
            <ListItemSecondaryAction>
 | 
			
		||||
              <Switch checked={typeof code() === "undefined"} />
 | 
			
		||||
            </ListItemSecondaryAction>
 | 
			
		||||
          </ListItemButton>
 | 
			
		||||
          <List subheader={<ListSubheader>{t("Supported")}</ListSubheader>}>
 | 
			
		||||
            <For each={SUPPORTED_LANGS}>
 | 
			
		||||
              {(c) => (
 | 
			
		||||
                <ListItemButton
 | 
			
		||||
                  disabled={typeof code() === "undefined"}
 | 
			
		||||
                  onClick={[onCodeChange, c]}
 | 
			
		||||
                >
 | 
			
		||||
                  <ListItemText>{t(`lang.${c}`)}</ListItemText>
 | 
			
		||||
                  <ListItemSecondaryAction>
 | 
			
		||||
                    <Radio
 | 
			
		||||
                      checked={
 | 
			
		||||
                        code() === c ||
 | 
			
		||||
                        (code() === undefined && matchedLangCode() == c)
 | 
			
		||||
                      }
 | 
			
		||||
                    />
 | 
			
		||||
                  </ListItemSecondaryAction>
 | 
			
		||||
                </ListItemButton>
 | 
			
		||||
              )}
 | 
			
		||||
            </For>
 | 
			
		||||
          </List>
 | 
			
		||||
 | 
			
		||||
        <List subheader={<ListSubheader>{t("Unsupported")}</ListSubheader>}>
 | 
			
		||||
          <For each={unsupportedLangCodes()}>
 | 
			
		||||
            {(code) => (
 | 
			
		||||
              <ListItem>
 | 
			
		||||
                <ListItemText>{iso639_1.getNativeName(code)}</ListItemText>
 | 
			
		||||
              </ListItem>
 | 
			
		||||
            )}
 | 
			
		||||
          </For>
 | 
			
		||||
          <List subheader={<ListSubheader>{t("Unsupported")}</ListSubheader>}>
 | 
			
		||||
            <For each={unsupportedLangCodes()}>
 | 
			
		||||
              {(code) => (
 | 
			
		||||
                <ListItem>
 | 
			
		||||
                  <ListItemText>{iso639_1.getNativeName(code)}</ListItemText>
 | 
			
		||||
                </ListItem>
 | 
			
		||||
              )}
 | 
			
		||||
            </For>
 | 
			
		||||
          </List>
 | 
			
		||||
        </List>
 | 
			
		||||
      </List>
 | 
			
		||||
    </Scaffold>
 | 
			
		||||
      </Scaffold>
 | 
			
		||||
    </>
 | 
			
		||||
  );
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -19,9 +19,10 @@ import { useStore } from "@nanostores/solid";
 | 
			
		|||
import { $settings } from "./stores";
 | 
			
		||||
import { useNavigator } from "~platform/StackedRouter";
 | 
			
		||||
import AppTopBar from "~material/AppTopBar";
 | 
			
		||||
import DocumentTitle from "~platform/DocumentTitle";
 | 
			
		||||
 | 
			
		||||
const Motions: Component = () => {
 | 
			
		||||
  const {pop} = useNavigator();
 | 
			
		||||
  const { pop } = useNavigator();
 | 
			
		||||
  const [t] = createTranslator(
 | 
			
		||||
    (code) =>
 | 
			
		||||
      import(`./i18n/${code}.json`) as Promise<{
 | 
			
		||||
| 
						 | 
				
			
			@ -30,55 +31,58 @@ const Motions: Component = () => {
 | 
			
		|||
  );
 | 
			
		||||
  const settings = useStore($settings);
 | 
			
		||||
  return (
 | 
			
		||||
    <Scaffold
 | 
			
		||||
      topbar={
 | 
			
		||||
        <AppTopBar>
 | 
			
		||||
          <IconButton color="inherit" onClick={[pop, 1]} disableRipple>
 | 
			
		||||
    <>
 | 
			
		||||
      <DocumentTitle>{t("motions")}</DocumentTitle>
 | 
			
		||||
      <Scaffold
 | 
			
		||||
        topbar={
 | 
			
		||||
          <AppTopBar>
 | 
			
		||||
            <IconButton color="inherit" onClick={[pop, 1]} disableRipple>
 | 
			
		||||
              <ArrowBack />
 | 
			
		||||
            </IconButton>
 | 
			
		||||
            <Title>{t("motions")}</Title>
 | 
			
		||||
        </AppTopBar>
 | 
			
		||||
      }
 | 
			
		||||
    >
 | 
			
		||||
      <List
 | 
			
		||||
        sx={{
 | 
			
		||||
          paddingBottom: "calc(var(--safe-area-inset-bottom, 0px) + 16px)",
 | 
			
		||||
        }}
 | 
			
		||||
          </AppTopBar>
 | 
			
		||||
        }
 | 
			
		||||
      >
 | 
			
		||||
        <li>
 | 
			
		||||
          <ul style={{ "padding-left": 0 }}>
 | 
			
		||||
            <ListSubheader>{t("motions.gifs")}</ListSubheader>
 | 
			
		||||
            <ListItemButton
 | 
			
		||||
              onClick={() =>
 | 
			
		||||
                $settings.setKey("autoPlayGIFs", !settings().autoPlayGIFs)
 | 
			
		||||
              }
 | 
			
		||||
            >
 | 
			
		||||
              <ListItemText>{t("motions.gifs.autoplay")}</ListItemText>
 | 
			
		||||
              <ListItemSecondaryAction>
 | 
			
		||||
                <Switch checked={settings().autoPlayGIFs}></Switch>
 | 
			
		||||
              </ListItemSecondaryAction>
 | 
			
		||||
            </ListItemButton>
 | 
			
		||||
            <Divider />
 | 
			
		||||
          </ul>
 | 
			
		||||
        </li>
 | 
			
		||||
        <li>
 | 
			
		||||
          <ul style={{ "padding-left": 0 }}>
 | 
			
		||||
            <ListSubheader>{t("motions.vids")}</ListSubheader>
 | 
			
		||||
            <ListItemButton
 | 
			
		||||
              onClick={() =>
 | 
			
		||||
                $settings.setKey("autoPlayVideos", !settings().autoPlayVideos)
 | 
			
		||||
              }
 | 
			
		||||
            >
 | 
			
		||||
              <ListItemText>{t("motions.vids.autoplay")}</ListItemText>
 | 
			
		||||
              <ListItemSecondaryAction>
 | 
			
		||||
                <Switch checked={settings().autoPlayVideos}></Switch>
 | 
			
		||||
              </ListItemSecondaryAction>
 | 
			
		||||
            </ListItemButton>
 | 
			
		||||
            <Divider />
 | 
			
		||||
          </ul>
 | 
			
		||||
        </li>
 | 
			
		||||
      </List>
 | 
			
		||||
    </Scaffold>
 | 
			
		||||
        <List
 | 
			
		||||
          sx={{
 | 
			
		||||
            paddingBottom: "calc(var(--safe-area-inset-bottom, 0px) + 16px)",
 | 
			
		||||
          }}
 | 
			
		||||
        >
 | 
			
		||||
          <li>
 | 
			
		||||
            <ul style={{ "padding-left": 0 }}>
 | 
			
		||||
              <ListSubheader>{t("motions.gifs")}</ListSubheader>
 | 
			
		||||
              <ListItemButton
 | 
			
		||||
                onClick={() =>
 | 
			
		||||
                  $settings.setKey("autoPlayGIFs", !settings().autoPlayGIFs)
 | 
			
		||||
                }
 | 
			
		||||
              >
 | 
			
		||||
                <ListItemText>{t("motions.gifs.autoplay")}</ListItemText>
 | 
			
		||||
                <ListItemSecondaryAction>
 | 
			
		||||
                  <Switch checked={settings().autoPlayGIFs}></Switch>
 | 
			
		||||
                </ListItemSecondaryAction>
 | 
			
		||||
              </ListItemButton>
 | 
			
		||||
              <Divider />
 | 
			
		||||
            </ul>
 | 
			
		||||
          </li>
 | 
			
		||||
          <li>
 | 
			
		||||
            <ul style={{ "padding-left": 0 }}>
 | 
			
		||||
              <ListSubheader>{t("motions.vids")}</ListSubheader>
 | 
			
		||||
              <ListItemButton
 | 
			
		||||
                onClick={() =>
 | 
			
		||||
                  $settings.setKey("autoPlayVideos", !settings().autoPlayVideos)
 | 
			
		||||
                }
 | 
			
		||||
              >
 | 
			
		||||
                <ListItemText>{t("motions.vids.autoplay")}</ListItemText>
 | 
			
		||||
                <ListItemSecondaryAction>
 | 
			
		||||
                  <Switch checked={settings().autoPlayVideos}></Switch>
 | 
			
		||||
                </ListItemSecondaryAction>
 | 
			
		||||
              </ListItemButton>
 | 
			
		||||
              <Divider />
 | 
			
		||||
            </ul>
 | 
			
		||||
          </li>
 | 
			
		||||
        </List>
 | 
			
		||||
      </Scaffold>
 | 
			
		||||
    </>
 | 
			
		||||
  );
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -24,6 +24,7 @@ import { $settings } from "./stores";
 | 
			
		|||
import { useStore } from "@nanostores/solid";
 | 
			
		||||
import { useNavigator } from "~platform/StackedRouter";
 | 
			
		||||
import AppTopBar from "~material/AppTopBar";
 | 
			
		||||
import DocumentTitle from "~platform/DocumentTitle";
 | 
			
		||||
 | 
			
		||||
const ChooseRegion: Component = () => {
 | 
			
		||||
  const { pop } = useNavigator();
 | 
			
		||||
| 
						 | 
				
			
			@ -48,59 +49,62 @@ const ChooseRegion: Component = () => {
 | 
			
		|||
  };
 | 
			
		||||
 | 
			
		||||
  return (
 | 
			
		||||
    <Scaffold
 | 
			
		||||
      topbar={
 | 
			
		||||
        <AppTopBar>
 | 
			
		||||
          <IconButton color="inherit" onClick={[pop, 1]} disableRipple>
 | 
			
		||||
            <ArrowBack />
 | 
			
		||||
          </IconButton>
 | 
			
		||||
          <Title>{t("Choose Region")}</Title>
 | 
			
		||||
        </AppTopBar>
 | 
			
		||||
      }
 | 
			
		||||
    >
 | 
			
		||||
      <List
 | 
			
		||||
        sx={{
 | 
			
		||||
          paddingBottom: "var(--safe-area-inset-bottom, 0)",
 | 
			
		||||
        }}
 | 
			
		||||
    <>
 | 
			
		||||
      <DocumentTitle>{t("Choose Region")}</DocumentTitle>
 | 
			
		||||
      <Scaffold
 | 
			
		||||
        topbar={
 | 
			
		||||
          <AppTopBar>
 | 
			
		||||
            <IconButton color="inherit" onClick={[pop, 1]} disableRipple>
 | 
			
		||||
              <ArrowBack />
 | 
			
		||||
            </IconButton>
 | 
			
		||||
            <Title>{t("Choose Region")}</Title>
 | 
			
		||||
          </AppTopBar>
 | 
			
		||||
        }
 | 
			
		||||
      >
 | 
			
		||||
        <ListItemButton
 | 
			
		||||
          onClick={() => {
 | 
			
		||||
            onCodeChange(region() ? undefined : matchedRegionCode());
 | 
			
		||||
        <List
 | 
			
		||||
          sx={{
 | 
			
		||||
            paddingBottom: "var(--safe-area-inset-bottom, 0)",
 | 
			
		||||
          }}
 | 
			
		||||
        >
 | 
			
		||||
          <ListItemText>
 | 
			
		||||
            {t("region.auto", {
 | 
			
		||||
              detected:
 | 
			
		||||
                t(`region.${matchedRegionCode()}`) ?? matchedRegionCode(),
 | 
			
		||||
            })}
 | 
			
		||||
          </ListItemText>
 | 
			
		||||
          <ListItemSecondaryAction>
 | 
			
		||||
            <Switch checked={typeof region() === "undefined"} />
 | 
			
		||||
          </ListItemSecondaryAction>
 | 
			
		||||
        </ListItemButton>
 | 
			
		||||
          <ListItemButton
 | 
			
		||||
            onClick={() => {
 | 
			
		||||
              onCodeChange(region() ? undefined : matchedRegionCode());
 | 
			
		||||
            }}
 | 
			
		||||
          >
 | 
			
		||||
            <ListItemText>
 | 
			
		||||
              {t("region.auto", {
 | 
			
		||||
                detected:
 | 
			
		||||
                  t(`region.${matchedRegionCode()}`) ?? matchedRegionCode(),
 | 
			
		||||
              })}
 | 
			
		||||
            </ListItemText>
 | 
			
		||||
            <ListItemSecondaryAction>
 | 
			
		||||
              <Switch checked={typeof region() === "undefined"} />
 | 
			
		||||
            </ListItemSecondaryAction>
 | 
			
		||||
          </ListItemButton>
 | 
			
		||||
 | 
			
		||||
        <List subheader={<ListSubheader>{t("Supported")}</ListSubheader>}>
 | 
			
		||||
          <For each={SUPPORTED_REGIONS}>
 | 
			
		||||
            {(code) => (
 | 
			
		||||
              <ListItemButton
 | 
			
		||||
                disabled={typeof region() === "undefined"}
 | 
			
		||||
                onClick={[onCodeChange, code]}
 | 
			
		||||
              >
 | 
			
		||||
                <ListItemText>{t(`region.${code}`)}</ListItemText>
 | 
			
		||||
                <ListItemSecondaryAction>
 | 
			
		||||
                  <Radio
 | 
			
		||||
                    checked={
 | 
			
		||||
                      region() === code ||
 | 
			
		||||
                      (region() === undefined && matchedRegionCode() == code)
 | 
			
		||||
                    }
 | 
			
		||||
                  />
 | 
			
		||||
                </ListItemSecondaryAction>
 | 
			
		||||
              </ListItemButton>
 | 
			
		||||
            )}
 | 
			
		||||
          </For>
 | 
			
		||||
          <List subheader={<ListSubheader>{t("Supported")}</ListSubheader>}>
 | 
			
		||||
            <For each={SUPPORTED_REGIONS}>
 | 
			
		||||
              {(code) => (
 | 
			
		||||
                <ListItemButton
 | 
			
		||||
                  disabled={typeof region() === "undefined"}
 | 
			
		||||
                  onClick={[onCodeChange, code]}
 | 
			
		||||
                >
 | 
			
		||||
                  <ListItemText>{t(`region.${code}`)}</ListItemText>
 | 
			
		||||
                  <ListItemSecondaryAction>
 | 
			
		||||
                    <Radio
 | 
			
		||||
                      checked={
 | 
			
		||||
                        region() === code ||
 | 
			
		||||
                        (region() === undefined && matchedRegionCode() == code)
 | 
			
		||||
                      }
 | 
			
		||||
                    />
 | 
			
		||||
                  </ListItemSecondaryAction>
 | 
			
		||||
                </ListItemButton>
 | 
			
		||||
              )}
 | 
			
		||||
            </For>
 | 
			
		||||
          </List>
 | 
			
		||||
        </List>
 | 
			
		||||
      </List>
 | 
			
		||||
    </Scaffold>
 | 
			
		||||
      </Scaffold>
 | 
			
		||||
    </>
 | 
			
		||||
  );
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -41,6 +41,7 @@ import { makeAcctText, useSessions } from "../masto/clients.js";
 | 
			
		|||
import { useNavigator } from "~platform/StackedRouter.jsx";
 | 
			
		||||
import AppTopBar from "~material/AppTopBar.jsx";
 | 
			
		||||
import MastodonLogo from "./MastodonLogo.jsx";
 | 
			
		||||
import DocumentTitle from "~platform/DocumentTitle.jsx";
 | 
			
		||||
 | 
			
		||||
type Inset = {
 | 
			
		||||
  top?: number;
 | 
			
		||||
| 
						 | 
				
			
			@ -197,225 +198,228 @@ const Settings: Component = () => {
 | 
			
		|||
    }
 | 
			
		||||
  `;
 | 
			
		||||
  return (
 | 
			
		||||
    <Scaffold
 | 
			
		||||
      topbar={
 | 
			
		||||
        <AppTopBar>
 | 
			
		||||
          <IconButton color="inherit" onClick={[pop, 1]} disableRipple>
 | 
			
		||||
            <CloseIcon />
 | 
			
		||||
          </IconButton>
 | 
			
		||||
          <Title>{t("Settings")}</Title>
 | 
			
		||||
        </AppTopBar>
 | 
			
		||||
      }
 | 
			
		||||
    >
 | 
			
		||||
      <List class="setting-list" use:solid-styled>
 | 
			
		||||
        <li>
 | 
			
		||||
          <ul>
 | 
			
		||||
            <ListSubheader>{t("Accounts")}</ListSubheader>
 | 
			
		||||
            <ListItemButton disabled>
 | 
			
		||||
              <ListItemText>{t("All Notifications")}</ListItemText>
 | 
			
		||||
              <ListItemSecondaryAction>
 | 
			
		||||
                <Switch value={false} disabled />
 | 
			
		||||
              </ListItemSecondaryAction>
 | 
			
		||||
            </ListItemButton>
 | 
			
		||||
            <Divider />
 | 
			
		||||
            <ListItemButton disabled>
 | 
			
		||||
              <ListItemText>{t("Sign in...")}</ListItemText>
 | 
			
		||||
            </ListItemButton>
 | 
			
		||||
            <Divider />
 | 
			
		||||
          </ul>
 | 
			
		||||
          <For each={profiles()}>
 | 
			
		||||
            {({ account: acct }) => (
 | 
			
		||||
              <ul data-site={acct.site} data-username={acct.inf?.username}>
 | 
			
		||||
                <ListSubheader>{`@${acct.inf?.username ?? "..."}@${new URL(acct.site).host}`}</ListSubheader>
 | 
			
		||||
                <ListItemButton disabled>
 | 
			
		||||
                  <ListItemText>{t("Notifications")}</ListItemText>
 | 
			
		||||
                  <ListItemSecondaryAction>
 | 
			
		||||
                    <Switch value={false} disabled />
 | 
			
		||||
                  </ListItemSecondaryAction>
 | 
			
		||||
                </ListItemButton>
 | 
			
		||||
                <Divider />
 | 
			
		||||
                <ListItemButton onClick={[doSignOut, acct]}>
 | 
			
		||||
                  <ListItemIcon>
 | 
			
		||||
                    <Logout />
 | 
			
		||||
                  </ListItemIcon>
 | 
			
		||||
                  <ListItemText>{t("Sign out")}</ListItemText>
 | 
			
		||||
                </ListItemButton>
 | 
			
		||||
                <Divider />
 | 
			
		||||
              </ul>
 | 
			
		||||
            )}
 | 
			
		||||
          </For>
 | 
			
		||||
        </li>
 | 
			
		||||
        <li>
 | 
			
		||||
          <ListSubheader>{t("timelines")}</ListSubheader>
 | 
			
		||||
          <ListItemButton
 | 
			
		||||
            onClick={(e) =>
 | 
			
		||||
              $settings.setKey(
 | 
			
		||||
                "prefetchTootsDisabled",
 | 
			
		||||
                !settings$().prefetchTootsDisabled,
 | 
			
		||||
              )
 | 
			
		||||
            }
 | 
			
		||||
          >
 | 
			
		||||
            <ListItemText secondary={t("Prefetch Toots.2nd")}>
 | 
			
		||||
              {t("Prefetch Toots")}
 | 
			
		||||
            </ListItemText>
 | 
			
		||||
            <ListItemSecondaryAction>
 | 
			
		||||
              <Switch checked={!settings$().prefetchTootsDisabled} />
 | 
			
		||||
            </ListItemSecondaryAction>
 | 
			
		||||
          </ListItemButton>
 | 
			
		||||
          <Divider />
 | 
			
		||||
          <ListItemButton component={A} href="./motions">
 | 
			
		||||
            <ListItemIcon>
 | 
			
		||||
              <AnimationIcon></AnimationIcon>
 | 
			
		||||
            </ListItemIcon>
 | 
			
		||||
            <ListItemText>{t("motions")}</ListItemText>
 | 
			
		||||
          </ListItemButton>
 | 
			
		||||
          <Divider />
 | 
			
		||||
        </li>
 | 
			
		||||
        <li>
 | 
			
		||||
          <ListSubheader>{t("storage")}</ListSubheader>
 | 
			
		||||
          <ListItemButton disabled>
 | 
			
		||||
            <ListItemIcon>
 | 
			
		||||
              <DeleteForever />
 | 
			
		||||
            </ListItemIcon>
 | 
			
		||||
            <ListItemText secondary={t("storage.cache.UA-managed")}>
 | 
			
		||||
              {t("storage.cache.clear")}
 | 
			
		||||
            </ListItemText>
 | 
			
		||||
          </ListItemButton>
 | 
			
		||||
        </li>
 | 
			
		||||
        <li>
 | 
			
		||||
          <ListSubheader>{t("This Application")}</ListSubheader>
 | 
			
		||||
          <ListItemButton component={A} href="./language">
 | 
			
		||||
            <ListItemIcon>
 | 
			
		||||
              <TranslateIcon />
 | 
			
		||||
            </ListItemIcon>
 | 
			
		||||
            <ListItemText
 | 
			
		||||
              secondary={
 | 
			
		||||
                settings$().language === undefined
 | 
			
		||||
                  ? t("lang.auto", {
 | 
			
		||||
                      detected:
 | 
			
		||||
                        t("lang." + autoMatchLangTag()) ?? autoMatchLangTag(),
 | 
			
		||||
                    })
 | 
			
		||||
                  : t("lang." + settings$().language)
 | 
			
		||||
              }
 | 
			
		||||
            >
 | 
			
		||||
              {t("Language")}
 | 
			
		||||
            </ListItemText>
 | 
			
		||||
          </ListItemButton>
 | 
			
		||||
          <Divider />
 | 
			
		||||
          <ListItemButton component={A} href="./region">
 | 
			
		||||
            <ListItemIcon>
 | 
			
		||||
              <PublicIcon />
 | 
			
		||||
            </ListItemIcon>
 | 
			
		||||
            <ListItemText
 | 
			
		||||
              secondary={
 | 
			
		||||
                settings$().region === undefined
 | 
			
		||||
                  ? t("region.auto", {
 | 
			
		||||
                      detected:
 | 
			
		||||
                        t("region." + autoMatchRegion()) ?? autoMatchRegion(),
 | 
			
		||||
                    })
 | 
			
		||||
                  : t("region." + settings$().region)
 | 
			
		||||
              }
 | 
			
		||||
            >
 | 
			
		||||
              {t("Region")}
 | 
			
		||||
            </ListItemText>
 | 
			
		||||
          </ListItemButton>
 | 
			
		||||
          <Divider />
 | 
			
		||||
          <ListItem
 | 
			
		||||
            secondaryAction={
 | 
			
		||||
              <IconButton
 | 
			
		||||
                component={A}
 | 
			
		||||
                aria-label={t("mastodonlink.open")}
 | 
			
		||||
                href={`/${encodeURIComponent(profiles().length > 0 ? makeAcctText(profiles()[0]) : "@")}/profile/@tutu@indieweb.social`}
 | 
			
		||||
              >
 | 
			
		||||
                <MastodonLogo />
 | 
			
		||||
              </IconButton>
 | 
			
		||||
            }
 | 
			
		||||
          >
 | 
			
		||||
            <ListItemText secondary={t("About Tutu.2nd")}>
 | 
			
		||||
              {t("About Tutu")}
 | 
			
		||||
            </ListItemText>
 | 
			
		||||
          </ListItem>
 | 
			
		||||
          <Divider />
 | 
			
		||||
          <ListItem>
 | 
			
		||||
            <ListItemText
 | 
			
		||||
              secondary={t("version", {
 | 
			
		||||
                packageVersion: import.meta.env.PACKAGE_VERSION,
 | 
			
		||||
                builtAt: format(
 | 
			
		||||
                  import.meta.env.BUILT_AT,
 | 
			
		||||
                  t("datefmt") || "yyyy/MM/dd",
 | 
			
		||||
                  { locale: dateFnLocale() },
 | 
			
		||||
                ),
 | 
			
		||||
                buildMode: import.meta.env.MODE,
 | 
			
		||||
              })}
 | 
			
		||||
            >
 | 
			
		||||
              {needRefresh() ? t("updates.ready") : t("updates.no")}
 | 
			
		||||
            </ListItemText>
 | 
			
		||||
            <Show when={needRefresh()}>
 | 
			
		||||
              <ListItemSecondaryAction>
 | 
			
		||||
                <IconButton
 | 
			
		||||
                  aria-label="Restart Now"
 | 
			
		||||
                  onClick={() => window.location.reload()}
 | 
			
		||||
                >
 | 
			
		||||
                  <RefreshIcon />
 | 
			
		||||
                </IconButton>
 | 
			
		||||
              </ListItemSecondaryAction>
 | 
			
		||||
            </Show>
 | 
			
		||||
          </ListItem>
 | 
			
		||||
          <Divider />
 | 
			
		||||
          {import.meta.env.VITE_CODE_VERSION ? (
 | 
			
		||||
            <>
 | 
			
		||||
              <ListItem>
 | 
			
		||||
                <ListItemText secondary={import.meta.env.VITE_CODE_VERSION}>
 | 
			
		||||
                  {t("version.code")}
 | 
			
		||||
                </ListItemText>
 | 
			
		||||
              </ListItem>
 | 
			
		||||
              <Divider />
 | 
			
		||||
            </>
 | 
			
		||||
          ) : (
 | 
			
		||||
            <></>
 | 
			
		||||
          )}
 | 
			
		||||
        </li>
 | 
			
		||||
        {import.meta.env.DEV ? (
 | 
			
		||||
    <>
 | 
			
		||||
      <DocumentTitle>{t("Settings")}</DocumentTitle>
 | 
			
		||||
      <Scaffold
 | 
			
		||||
        topbar={
 | 
			
		||||
          <AppTopBar>
 | 
			
		||||
            <IconButton color="inherit" onClick={[pop, 1]} disableRipple>
 | 
			
		||||
              <CloseIcon />
 | 
			
		||||
            </IconButton>
 | 
			
		||||
            <Title>{t("Settings")}</Title>
 | 
			
		||||
          </AppTopBar>
 | 
			
		||||
        }
 | 
			
		||||
      >
 | 
			
		||||
        <List class="setting-list" use:solid-styled>
 | 
			
		||||
          <li>
 | 
			
		||||
            <ListSubheader>Developer Tools</ListSubheader>
 | 
			
		||||
            <ListItem
 | 
			
		||||
              secondaryAction={
 | 
			
		||||
                window.screen?.orientation ? (
 | 
			
		||||
                  <NativeSelect
 | 
			
		||||
                    sx={{ maxWidth: "40vw" }}
 | 
			
		||||
                    onChange={(event) => {
 | 
			
		||||
                      const k = event.currentTarget.value;
 | 
			
		||||
                      setupSafeAreaEmulation(k);
 | 
			
		||||
                    }}
 | 
			
		||||
                  >
 | 
			
		||||
                    <option>Don't change</option>
 | 
			
		||||
                    <option value={"ua"}>User agent</option>
 | 
			
		||||
                    <option value={"iphone15"}>
 | 
			
		||||
                      iPhone 15 and Plus, Pro, Pro Max
 | 
			
		||||
                    </option>
 | 
			
		||||
                    <option value={"iphone12"}>iPhone 12, 13 and 14</option>
 | 
			
		||||
                    <option value={"iphone13mini"}>iPhone 13 mini</option>
 | 
			
		||||
                  </NativeSelect>
 | 
			
		||||
                ) : undefined
 | 
			
		||||
            <ul>
 | 
			
		||||
              <ListSubheader>{t("Accounts")}</ListSubheader>
 | 
			
		||||
              <ListItemButton disabled>
 | 
			
		||||
                <ListItemText>{t("All Notifications")}</ListItemText>
 | 
			
		||||
                <ListItemSecondaryAction>
 | 
			
		||||
                  <Switch value={false} disabled />
 | 
			
		||||
                </ListItemSecondaryAction>
 | 
			
		||||
              </ListItemButton>
 | 
			
		||||
              <Divider />
 | 
			
		||||
              <ListItemButton disabled>
 | 
			
		||||
                <ListItemText>{t("Sign in...")}</ListItemText>
 | 
			
		||||
              </ListItemButton>
 | 
			
		||||
              <Divider />
 | 
			
		||||
            </ul>
 | 
			
		||||
            <For each={profiles()}>
 | 
			
		||||
              {({ account: acct }) => (
 | 
			
		||||
                <ul data-site={acct.site} data-username={acct.inf?.username}>
 | 
			
		||||
                  <ListSubheader>{`@${acct.inf?.username ?? "..."}@${new URL(acct.site).host}`}</ListSubheader>
 | 
			
		||||
                  <ListItemButton disabled>
 | 
			
		||||
                    <ListItemText>{t("Notifications")}</ListItemText>
 | 
			
		||||
                    <ListItemSecondaryAction>
 | 
			
		||||
                      <Switch value={false} disabled />
 | 
			
		||||
                    </ListItemSecondaryAction>
 | 
			
		||||
                  </ListItemButton>
 | 
			
		||||
                  <Divider />
 | 
			
		||||
                  <ListItemButton onClick={[doSignOut, acct]}>
 | 
			
		||||
                    <ListItemIcon>
 | 
			
		||||
                      <Logout />
 | 
			
		||||
                    </ListItemIcon>
 | 
			
		||||
                    <ListItemText>{t("Sign out")}</ListItemText>
 | 
			
		||||
                  </ListItemButton>
 | 
			
		||||
                  <Divider />
 | 
			
		||||
                </ul>
 | 
			
		||||
              )}
 | 
			
		||||
            </For>
 | 
			
		||||
          </li>
 | 
			
		||||
          <li>
 | 
			
		||||
            <ListSubheader>{t("timelines")}</ListSubheader>
 | 
			
		||||
            <ListItemButton
 | 
			
		||||
              onClick={(e) =>
 | 
			
		||||
                $settings.setKey(
 | 
			
		||||
                  "prefetchTootsDisabled",
 | 
			
		||||
                  !settings$().prefetchTootsDisabled,
 | 
			
		||||
                )
 | 
			
		||||
              }
 | 
			
		||||
            >
 | 
			
		||||
              <ListItemText secondary={t("Prefetch Toots.2nd")}>
 | 
			
		||||
                {t("Prefetch Toots")}
 | 
			
		||||
              </ListItemText>
 | 
			
		||||
              <ListItemSecondaryAction>
 | 
			
		||||
                <Switch checked={!settings$().prefetchTootsDisabled} />
 | 
			
		||||
              </ListItemSecondaryAction>
 | 
			
		||||
            </ListItemButton>
 | 
			
		||||
            <Divider />
 | 
			
		||||
            <ListItemButton component={A} href="./motions">
 | 
			
		||||
              <ListItemIcon>
 | 
			
		||||
                <AnimationIcon></AnimationIcon>
 | 
			
		||||
              </ListItemIcon>
 | 
			
		||||
              <ListItemText>{t("motions")}</ListItemText>
 | 
			
		||||
            </ListItemButton>
 | 
			
		||||
            <Divider />
 | 
			
		||||
          </li>
 | 
			
		||||
          <li>
 | 
			
		||||
            <ListSubheader>{t("storage")}</ListSubheader>
 | 
			
		||||
            <ListItemButton disabled>
 | 
			
		||||
              <ListItemIcon>
 | 
			
		||||
                <DeleteForever />
 | 
			
		||||
              </ListItemIcon>
 | 
			
		||||
              <ListItemText secondary={t("storage.cache.UA-managed")}>
 | 
			
		||||
                {t("storage.cache.clear")}
 | 
			
		||||
              </ListItemText>
 | 
			
		||||
            </ListItemButton>
 | 
			
		||||
          </li>
 | 
			
		||||
          <li>
 | 
			
		||||
            <ListSubheader>{t("This Application")}</ListSubheader>
 | 
			
		||||
            <ListItemButton component={A} href="./language">
 | 
			
		||||
              <ListItemIcon>
 | 
			
		||||
                <TranslateIcon />
 | 
			
		||||
              </ListItemIcon>
 | 
			
		||||
              <ListItemText
 | 
			
		||||
                secondary={
 | 
			
		||||
                  window.screen?.orientation
 | 
			
		||||
                    ? undefined
 | 
			
		||||
                    : "Unsupported on This Platform"
 | 
			
		||||
                  settings$().language === undefined
 | 
			
		||||
                    ? t("lang.auto", {
 | 
			
		||||
                        detected:
 | 
			
		||||
                          t("lang." + autoMatchLangTag()) ?? autoMatchLangTag(),
 | 
			
		||||
                      })
 | 
			
		||||
                    : t("lang." + settings$().language)
 | 
			
		||||
                }
 | 
			
		||||
              >
 | 
			
		||||
                Safe Area Insets
 | 
			
		||||
                {t("Language")}
 | 
			
		||||
              </ListItemText>
 | 
			
		||||
            </ListItemButton>
 | 
			
		||||
            <Divider />
 | 
			
		||||
            <ListItemButton component={A} href="./region">
 | 
			
		||||
              <ListItemIcon>
 | 
			
		||||
                <PublicIcon />
 | 
			
		||||
              </ListItemIcon>
 | 
			
		||||
              <ListItemText
 | 
			
		||||
                secondary={
 | 
			
		||||
                  settings$().region === undefined
 | 
			
		||||
                    ? t("region.auto", {
 | 
			
		||||
                        detected:
 | 
			
		||||
                          t("region." + autoMatchRegion()) ?? autoMatchRegion(),
 | 
			
		||||
                      })
 | 
			
		||||
                    : t("region." + settings$().region)
 | 
			
		||||
                }
 | 
			
		||||
              >
 | 
			
		||||
                {t("Region")}
 | 
			
		||||
              </ListItemText>
 | 
			
		||||
            </ListItemButton>
 | 
			
		||||
            <Divider />
 | 
			
		||||
            <ListItem
 | 
			
		||||
              secondaryAction={
 | 
			
		||||
                <IconButton
 | 
			
		||||
                  component={A}
 | 
			
		||||
                  aria-label={t("mastodonlink.open")}
 | 
			
		||||
                  href={`/${encodeURIComponent(profiles().length > 0 ? makeAcctText(profiles()[0]) : "@")}/profile/@tutu@indieweb.social`}
 | 
			
		||||
                >
 | 
			
		||||
                  <MastodonLogo />
 | 
			
		||||
                </IconButton>
 | 
			
		||||
              }
 | 
			
		||||
            >
 | 
			
		||||
              <ListItemText secondary={t("About Tutu.2nd")}>
 | 
			
		||||
                {t("About Tutu")}
 | 
			
		||||
              </ListItemText>
 | 
			
		||||
            </ListItem>
 | 
			
		||||
            <Divider />
 | 
			
		||||
            <ListItem>
 | 
			
		||||
              <ListItemText
 | 
			
		||||
                secondary={t("version", {
 | 
			
		||||
                  packageVersion: import.meta.env.PACKAGE_VERSION,
 | 
			
		||||
                  builtAt: format(
 | 
			
		||||
                    import.meta.env.BUILT_AT,
 | 
			
		||||
                    t("datefmt") || "yyyy/MM/dd",
 | 
			
		||||
                    { locale: dateFnLocale() },
 | 
			
		||||
                  ),
 | 
			
		||||
                  buildMode: import.meta.env.MODE,
 | 
			
		||||
                })}
 | 
			
		||||
              >
 | 
			
		||||
                {needRefresh() ? t("updates.ready") : t("updates.no")}
 | 
			
		||||
              </ListItemText>
 | 
			
		||||
              <Show when={needRefresh()}>
 | 
			
		||||
                <ListItemSecondaryAction>
 | 
			
		||||
                  <IconButton
 | 
			
		||||
                    aria-label="Restart Now"
 | 
			
		||||
                    onClick={() => window.location.reload()}
 | 
			
		||||
                  >
 | 
			
		||||
                    <RefreshIcon />
 | 
			
		||||
                  </IconButton>
 | 
			
		||||
                </ListItemSecondaryAction>
 | 
			
		||||
              </Show>
 | 
			
		||||
            </ListItem>
 | 
			
		||||
            <Divider />
 | 
			
		||||
            {import.meta.env.VITE_CODE_VERSION ? (
 | 
			
		||||
              <>
 | 
			
		||||
                <ListItem>
 | 
			
		||||
                  <ListItemText secondary={import.meta.env.VITE_CODE_VERSION}>
 | 
			
		||||
                    {t("version.code")}
 | 
			
		||||
                  </ListItemText>
 | 
			
		||||
                </ListItem>
 | 
			
		||||
                <Divider />
 | 
			
		||||
              </>
 | 
			
		||||
            ) : (
 | 
			
		||||
              <></>
 | 
			
		||||
            )}
 | 
			
		||||
          </li>
 | 
			
		||||
        ) : (
 | 
			
		||||
          <></>
 | 
			
		||||
        )}
 | 
			
		||||
      </List>
 | 
			
		||||
    </Scaffold>
 | 
			
		||||
          {import.meta.env.DEV ? (
 | 
			
		||||
            <li>
 | 
			
		||||
              <ListSubheader>Developer Tools</ListSubheader>
 | 
			
		||||
              <ListItem
 | 
			
		||||
                secondaryAction={
 | 
			
		||||
                  window.screen?.orientation ? (
 | 
			
		||||
                    <NativeSelect
 | 
			
		||||
                      sx={{ maxWidth: "40vw" }}
 | 
			
		||||
                      onChange={(event) => {
 | 
			
		||||
                        const k = event.currentTarget.value;
 | 
			
		||||
                        setupSafeAreaEmulation(k);
 | 
			
		||||
                      }}
 | 
			
		||||
                    >
 | 
			
		||||
                      <option>Don't change</option>
 | 
			
		||||
                      <option value={"ua"}>User agent</option>
 | 
			
		||||
                      <option value={"iphone15"}>
 | 
			
		||||
                        iPhone 15 and Plus, Pro, Pro Max
 | 
			
		||||
                      </option>
 | 
			
		||||
                      <option value={"iphone12"}>iPhone 12, 13 and 14</option>
 | 
			
		||||
                      <option value={"iphone13mini"}>iPhone 13 mini</option>
 | 
			
		||||
                    </NativeSelect>
 | 
			
		||||
                  ) : undefined
 | 
			
		||||
                }
 | 
			
		||||
              >
 | 
			
		||||
                <ListItemText
 | 
			
		||||
                  secondary={
 | 
			
		||||
                    window.screen?.orientation
 | 
			
		||||
                      ? undefined
 | 
			
		||||
                      : "Unsupported on This Platform"
 | 
			
		||||
                  }
 | 
			
		||||
                >
 | 
			
		||||
                  Safe Area Insets
 | 
			
		||||
                </ListItemText>
 | 
			
		||||
              </ListItem>
 | 
			
		||||
              <Divider />
 | 
			
		||||
            </li>
 | 
			
		||||
          ) : (
 | 
			
		||||
            <></>
 | 
			
		||||
          )}
 | 
			
		||||
        </List>
 | 
			
		||||
      </Scaffold>
 | 
			
		||||
    </>
 | 
			
		||||
  );
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -5,7 +5,6 @@ import {
 | 
			
		|||
  createEffect,
 | 
			
		||||
  useTransition,
 | 
			
		||||
} from "solid-js";
 | 
			
		||||
import { useDocumentTitle } from "../utils";
 | 
			
		||||
import Scaffold from "~material/Scaffold";
 | 
			
		||||
import {
 | 
			
		||||
  ListItemSecondaryAction,
 | 
			
		||||
| 
						 | 
				
			
			@ -30,6 +29,7 @@ import {
 | 
			
		|||
import AppTopBar from "~material/AppTopBar";
 | 
			
		||||
import { createTranslator } from "~platform/i18n";
 | 
			
		||||
import { useWindowSize } from "@solid-primitives/resize-observer";
 | 
			
		||||
import DocumentTitle from "~platform/DocumentTitle";
 | 
			
		||||
 | 
			
		||||
type StringRes = Record<
 | 
			
		||||
  "tabs.home" | "tabs.trending" | "tabs.public" | "set.prefetch-toots",
 | 
			
		||||
| 
						 | 
				
			
			@ -38,7 +38,6 @@ type StringRes = Record<
 | 
			
		|||
 | 
			
		||||
const Home: ParentComponent = (props) => {
 | 
			
		||||
  let panelList: HTMLDivElement;
 | 
			
		||||
  useDocumentTitle("Timelines");
 | 
			
		||||
  const [t] = createTranslator(
 | 
			
		||||
    (code) => import(`./i18n/${code}.json`) as Promise<{ default: StringRes }>,
 | 
			
		||||
  );
 | 
			
		||||
| 
						 | 
				
			
			@ -179,6 +178,7 @@ const Home: ParentComponent = (props) => {
 | 
			
		|||
 | 
			
		||||
  return (
 | 
			
		||||
    <>
 | 
			
		||||
    <DocumentTitle>Timelines</DocumentTitle>
 | 
			
		||||
      <Scaffold
 | 
			
		||||
        topbar={
 | 
			
		||||
          <AppTopBar>
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -14,7 +14,6 @@ import cards from "~material/cards.module.css";
 | 
			
		|||
import { css } from "solid-styled";
 | 
			
		||||
import { createTimeSource, TimeSourceProvider } from "~platform/timesrc";
 | 
			
		||||
import TootComposer from "./TootComposer";
 | 
			
		||||
import { useDocumentTitle } from "../utils";
 | 
			
		||||
import { createTimelineControlsForArray } from "../masto/timelines";
 | 
			
		||||
import TootList from "./TootList";
 | 
			
		||||
import "./TootBottomSheet.css";
 | 
			
		||||
| 
						 | 
				
			
			@ -26,6 +25,7 @@ import ItemSelectionProvider, {
 | 
			
		|||
import AppTopBar from "~material/AppTopBar";
 | 
			
		||||
import { fetchStatus } from "../masto/statuses";
 | 
			
		||||
import { type Account } from "../accounts/stores";
 | 
			
		||||
import DocumentTitle from "~platform/DocumentTitle";
 | 
			
		||||
 | 
			
		||||
const TootBottomSheet: Component = (props) => {
 | 
			
		||||
  const params = useParams<{ acct: string; id: string }>();
 | 
			
		||||
| 
						 | 
				
			
			@ -65,11 +65,11 @@ const TootBottomSheet: Component = (props) => {
 | 
			
		|||
    () => tootContext()?.descendants,
 | 
			
		||||
  );
 | 
			
		||||
 | 
			
		||||
  useDocumentTitle(() => {
 | 
			
		||||
  const documentTitle = () => {
 | 
			
		||||
    const t = toot()?.reblog ?? toot();
 | 
			
		||||
    const name = t?.account.displayName ?? "Someone";
 | 
			
		||||
    return `${name}'s toot`;
 | 
			
		||||
  });
 | 
			
		||||
  };
 | 
			
		||||
 | 
			
		||||
  const tootDisplayName = () => {
 | 
			
		||||
    const t = toot()?.reblog ?? toot();
 | 
			
		||||
| 
						 | 
				
			
			@ -163,6 +163,7 @@ const TootBottomSheet: Component = (props) => {
 | 
			
		|||
      }
 | 
			
		||||
      class="TootBottomSheet"
 | 
			
		||||
    >
 | 
			
		||||
      <DocumentTitle>{documentTitle()}</DocumentTitle>
 | 
			
		||||
      <div class="Scrollable">
 | 
			
		||||
        <TimeSourceProvider value={time}>
 | 
			
		||||
          <ItemSelectionProvider value={selectionState}>
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,31 +0,0 @@
 | 
			
		|||
import {
 | 
			
		||||
  createRenderEffect,
 | 
			
		||||
  onCleanup,
 | 
			
		||||
  type Accessor,
 | 
			
		||||
} from "solid-js";
 | 
			
		||||
 | 
			
		||||
export function useDocumentTitle(newTitle?: string | Accessor<string>) {
 | 
			
		||||
  const capturedTitle = document.title;
 | 
			
		||||
 | 
			
		||||
  createRenderEffect(() => {
 | 
			
		||||
    if (newTitle)
 | 
			
		||||
      document.title = typeof newTitle === "string" ? newTitle : newTitle();
 | 
			
		||||
  });
 | 
			
		||||
 | 
			
		||||
  onCleanup(() => {
 | 
			
		||||
    document.title = capturedTitle;
 | 
			
		||||
  });
 | 
			
		||||
 | 
			
		||||
  return (x: ((x: string) => string) | string) =>
 | 
			
		||||
    (document.title = typeof x === "string" ? x : x(document.title));
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export function mergeClass(c1: string | undefined, c2: string | undefined) {
 | 
			
		||||
  if (!c1) {
 | 
			
		||||
    return c2;
 | 
			
		||||
  }
 | 
			
		||||
  if (!c2) {
 | 
			
		||||
    return c1;
 | 
			
		||||
  }
 | 
			
		||||
  return [c1, c2].join(" ");
 | 
			
		||||
}
 | 
			
		||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue