From 1c8a3f0bbbf27ee1f2b44394b281d0a0e4cc1258 Mon Sep 17 00:00:00 2001 From: thislight Date: Thu, 2 Jan 2025 22:43:37 +0800 Subject: [PATCH] remove utils * add ~platform/DocumentTitle * add titles for some pages --- src/accounts/MastodonOAuth2Callback.tsx | 73 +-- src/accounts/SignIn.tsx | 105 ++-- src/material/Img.tsx | 22 +- src/platform/DocumentTitle.tsx | 22 + src/profiles/Profile.tsx | 670 ++++++++++++------------ src/settings/Language.tsx | 116 ++-- src/settings/Motions.tsx | 96 ++-- src/settings/Region.tsx | 100 ++-- src/settings/Settings.tsx | 420 +++++++-------- src/timelines/Home.tsx | 4 +- src/timelines/TootBottomSheet.tsx | 7 +- src/utils.tsx | 31 -- 12 files changed, 843 insertions(+), 823 deletions(-) create mode 100644 src/platform/DocumentTitle.tsx delete mode 100644 src/utils.tsx diff --git a/src/accounts/MastodonOAuth2Callback.tsx b/src/accounts/MastodonOAuth2Callback.tsx index 398d79d..ae7255c 100644 --- a/src/accounts/MastodonOAuth2Callback.tsx +++ b/src/accounts/MastodonOAuth2Callback.tsx @@ -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(); 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 ( -
-
- - - } - > - + Back from {siteTitle()} +
+
+ - + + } + > + {`Banner + - - Contracting {siteTitle}... - -

- If this page stays too long, you can close this page and sign in - again. -

+ + Contracting {siteTitle}... + +

+ If this page stays too long, you can close this page and sign in + again. +

+
-
+ ); }; diff --git a/src/accounts/SignIn.tsx b/src/accounts/SignIn.tsx index 628e490..1e37517 100644 --- a/src/accounts/SignIn.tsx +++ b/src/accounts/SignIn.tsx @@ -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 ( -
- -
-

Authorization is failed.

-

{params.errorDescription}

-

- Please try again later. If the problem persists, you can ask for - help from the server administrator. -

-
-
-
- -
- Sign in with Your Mastodon Account - - -
- + <> + Sign In +
+ +
+

Authorization is failed.

+

{params.errorDescription}

+

+ Please try again later. If the problem persists, you can ask for + help from the server administrator. +

- -
-
+ +
+ +
+ Sign in with Your Mastodon Account + + +
+ +
+ +
+ + ); }; diff --git a/src/material/Img.tsx b/src/material/Img.tsx index 774460c..8cb33c9 100644 --- a/src/material/Img.tsx +++ b/src/material/Img.tsx @@ -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 = (props) => { "blurhash", "keepBlur", "class", + "classList", "style", ]); const [isImgLoaded, setIsImgLoaded] = createSignal(false); @@ -61,21 +60,21 @@ const Img: Component = (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 = (props) => { }); return ( -
+
{ diff --git a/src/platform/DocumentTitle.tsx b/src/platform/DocumentTitle.tsx new file mode 100644 index 0000000..bf1e51f --- /dev/null +++ b/src/platform/DocumentTitle.tsx @@ -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 <>; +} diff --git a/src/profiles/Profile.tsx b/src/profiles/Profile.tsx index 02bd5de..fd4b3f8 100644 --- a/src/profiles/Profile.tsx +++ b/src/profiles/Profile.tsx @@ -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 ( - - - - - + <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()} - > - - - - - - } - class="Profile" - > - - +
+ + + + ); }; diff --git a/src/settings/Language.tsx b/src/settings/Language.tsx index 4a72cb1..190624f 100644 --- a/src/settings/Language.tsx +++ b/src/settings/Language.tsx @@ -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 ( - - - - - {t("Choose Language")} - - } - > - + {t("Choose Language")} + + + + + {t("Choose Language")} + + } > - { - onCodeChange(code() ? undefined : matchedLangCode()); + - - {t("lang.auto", { - detected: t(`lang.${matchedLangCode()}`) ?? matchedLangCode(), - })} - - - - - - {t("Supported")}}> - - {(c) => ( - - {t(`lang.${c}`)} - - - - - )} - - + { + onCodeChange(code() ? undefined : matchedLangCode()); + }} + > + + {t("lang.auto", { + detected: t(`lang.${matchedLangCode()}`) ?? matchedLangCode(), + })} + + + + + + {t("Supported")}}> + + {(c) => ( + + {t(`lang.${c}`)} + + + + + )} + + - {t("Unsupported")}}> - - {(code) => ( - - {iso639_1.getNativeName(code)} - - )} - + {t("Unsupported")}}> + + {(code) => ( + + {iso639_1.getNativeName(code)} + + )} + + - - + + ); }; diff --git a/src/settings/Motions.tsx b/src/settings/Motions.tsx index 93b900c..c8cf84c 100644 --- a/src/settings/Motions.tsx +++ b/src/settings/Motions.tsx @@ -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 ( - - + <> + {t("motions")} + + {t("motions")} - - } - > - + } > -
  • -
      - {t("motions.gifs")} - - $settings.setKey("autoPlayGIFs", !settings().autoPlayGIFs) - } - > - {t("motions.gifs.autoplay")} - - - - - -
    -
  • -
  • -
      - {t("motions.vids")} - - $settings.setKey("autoPlayVideos", !settings().autoPlayVideos) - } - > - {t("motions.vids.autoplay")} - - - - - -
    -
  • -
    -
    + +
  • +
      + {t("motions.gifs")} + + $settings.setKey("autoPlayGIFs", !settings().autoPlayGIFs) + } + > + {t("motions.gifs.autoplay")} + + + + + +
    +
  • +
  • +
      + {t("motions.vids")} + + $settings.setKey("autoPlayVideos", !settings().autoPlayVideos) + } + > + {t("motions.vids.autoplay")} + + + + + +
    +
  • +
    +
    + ); }; diff --git a/src/settings/Region.tsx b/src/settings/Region.tsx index 3c87e49..ebcad31 100644 --- a/src/settings/Region.tsx +++ b/src/settings/Region.tsx @@ -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 ( - - - - - {t("Choose Region")} - - } - > - + {t("Choose Region")} + + + + + {t("Choose Region")} + + } > - { - onCodeChange(region() ? undefined : matchedRegionCode()); + - - {t("region.auto", { - detected: - t(`region.${matchedRegionCode()}`) ?? matchedRegionCode(), - })} - - - - - + { + onCodeChange(region() ? undefined : matchedRegionCode()); + }} + > + + {t("region.auto", { + detected: + t(`region.${matchedRegionCode()}`) ?? matchedRegionCode(), + })} + + + + + - {t("Supported")}}> - - {(code) => ( - - {t(`region.${code}`)} - - - - - )} - + {t("Supported")}}> + + {(code) => ( + + {t(`region.${code}`)} + + + + + )} + + - - + + ); }; diff --git a/src/settings/Settings.tsx b/src/settings/Settings.tsx index d349227..9f75457 100644 --- a/src/settings/Settings.tsx +++ b/src/settings/Settings.tsx @@ -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 ( - - - - - {t("Settings")} - - } - > - -
  • -
      - {t("Accounts")} - - {t("All Notifications")} - - - - - - - {t("Sign in...")} - - -
    - - {({ account: acct }) => ( -
      - {`@${acct.inf?.username ?? "..."}@${new URL(acct.site).host}`} - - {t("Notifications")} - - - - - - - - - - {t("Sign out")} - - -
    - )} -
    -
  • -
  • - {t("timelines")} - - $settings.setKey( - "prefetchTootsDisabled", - !settings$().prefetchTootsDisabled, - ) - } - > - - {t("Prefetch Toots")} - - - - - - - - - - - {t("motions")} - - -
  • -
  • - {t("storage")} - - - - - - {t("storage.cache.clear")} - - -
  • -
  • - {t("This Application")} - - - - - - {t("Language")} - - - - - - - - - {t("Region")} - - - - 0 ? makeAcctText(profiles()[0]) : "@")}/profile/@tutu@indieweb.social`} - > - - - } - > - - {t("About Tutu")} - - - - - - {needRefresh() ? t("updates.ready") : t("updates.no")} - - - - window.location.reload()} - > - - - - - - - {import.meta.env.VITE_CODE_VERSION ? ( - <> - - - {t("version.code")} - - - - - ) : ( - <> - )} -
  • - {import.meta.env.DEV ? ( + <> + {t("Settings")} + + + + + {t("Settings")} + + } + > +
  • - Developer Tools - { - const k = event.currentTarget.value; - setupSafeAreaEmulation(k); - }} - > - - - - - - - ) : undefined +
      + {t("Accounts")} + + {t("All Notifications")} + + + + + + + {t("Sign in...")} + + +
    + + {({ account: acct }) => ( +
      + {`@${acct.inf?.username ?? "..."}@${new URL(acct.site).host}`} + + {t("Notifications")} + + + + + + + + + + {t("Sign out")} + + +
    + )} +
    +
  • +
  • + {t("timelines")} + + $settings.setKey( + "prefetchTootsDisabled", + !settings$().prefetchTootsDisabled, + ) } > + + {t("Prefetch Toots")} + + + + + + + + + + + {t("motions")} + + +
  • +
  • + {t("storage")} + + + + + + {t("storage.cache.clear")} + + +
  • +
  • + {t("This Application")} + + + + - Safe Area Insets + {t("Language")} + + + + + + + + + {t("Region")} + + + + 0 ? makeAcctText(profiles()[0]) : "@")}/profile/@tutu@indieweb.social`} + > + + + } + > + + {t("About Tutu")} + + + {needRefresh() ? t("updates.ready") : t("updates.no")} + + + + window.location.reload()} + > + + + + + + + {import.meta.env.VITE_CODE_VERSION ? ( + <> + + + {t("version.code")} + + + + + ) : ( + <> + )}
  • - ) : ( - <> - )} -
    -
    + {import.meta.env.DEV ? ( +
  • + Developer Tools + { + const k = event.currentTarget.value; + setupSafeAreaEmulation(k); + }} + > + + + + + + + ) : undefined + } + > + + Safe Area Insets + + + +
  • + ) : ( + <> + )} +
    +
    + ); }; diff --git a/src/timelines/Home.tsx b/src/timelines/Home.tsx index 3153f52..e7774bc 100644 --- a/src/timelines/Home.tsx +++ b/src/timelines/Home.tsx @@ -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 ( <> + Timelines diff --git a/src/timelines/TootBottomSheet.tsx b/src/timelines/TootBottomSheet.tsx index 79fed0f..889762c 100644 --- a/src/timelines/TootBottomSheet.tsx +++ b/src/timelines/TootBottomSheet.tsx @@ -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()}
    diff --git a/src/utils.tsx b/src/utils.tsx deleted file mode 100644 index 167df79..0000000 --- a/src/utils.tsx +++ /dev/null @@ -1,31 +0,0 @@ -import { - createRenderEffect, - onCleanup, - type Accessor, -} from "solid-js"; - -export function useDocumentTitle(newTitle?: string | Accessor) { - 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(" "); -}