remove utils
All checks were successful
/ depoly (push) Successful in 1m22s

* add ~platform/DocumentTitle
* add titles for some pages
This commit is contained in:
thislight 2025-01-02 22:43:37 +08:00
parent 1115135380
commit 1c8a3f0bbb
No known key found for this signature in database
GPG key ID: FCFE5192241CCD4E
12 changed files with 843 additions and 823 deletions

View file

@ -8,13 +8,13 @@ import {
} from "solid-js"; } from "solid-js";
import { acceptAccountViaAuthCode } from "./stores"; import { acceptAccountViaAuthCode } from "./stores";
import { $settings } from "../settings/stores"; import { $settings } from "../settings/stores";
import { useDocumentTitle } from "../utils";
import cards from "~material/cards.module.css"; import cards from "~material/cards.module.css";
import { LinearProgress } from "@suid/material"; import { LinearProgress } from "@suid/material";
import Img from "~material/Img"; import Img from "~material/Img";
import { createRestAPIClient } from "masto"; import { createRestAPIClient } from "masto";
import { Title } from "~material/typography"; import { Title } from "~material/typography";
import { useNavigator } from "~platform/StackedRouter"; import { useNavigator } from "~platform/StackedRouter";
import DocumentTitle from "~platform/DocumentTitle";
type OAuth2CallbackParams = { type OAuth2CallbackParams = {
code?: string; code?: string;
@ -27,13 +27,12 @@ const MastodonOAuth2Callback: Component = () => {
const titleId = createUniqueId(); const titleId = createUniqueId();
const [params] = useSearchParams<OAuth2CallbackParams>(); const [params] = useSearchParams<OAuth2CallbackParams>();
const { push: navigate } = useNavigator(); const { push: navigate } = useNavigator();
const setDocumentTitle = useDocumentTitle("Back from Mastodon...");
const [siteImg, setSiteImg] = createSignal<{ const [siteImg, setSiteImg] = createSignal<{
src: string; src: string;
srcset?: string; srcset?: string;
blurhash: string; blurhash: string;
}>(); }>();
const [siteTitle, setSiteTitle] = createSignal("the Mastodon server"); const [siteTitle, setSiteTitle] = createSignal("Mastodon");
onMount(async () => { onMount(async () => {
const onGoingOAuth2Process = $settings.get().onGoingOAuth2Process; const onGoingOAuth2Process = $settings.get().onGoingOAuth2Process;
@ -42,7 +41,6 @@ const MastodonOAuth2Callback: Component = () => {
url: onGoingOAuth2Process, url: onGoingOAuth2Process,
}); });
const ins = await client.v2.instance.fetch(); const ins = await client.v2.instance.fetch();
setDocumentTitle(`Back from ${ins.title}...`);
setSiteTitle(ins.title); setSiteTitle(ins.title);
const srcset = []; const srcset = [];
@ -93,6 +91,8 @@ const MastodonOAuth2Callback: Component = () => {
}); });
}); });
return ( return (
<>
<DocumentTitle>Back from {siteTitle()}</DocumentTitle>
<div class={cards.layoutCentered}> <div class={cards.layoutCentered}>
<div class={cards.card} aria-busy="true" aria-describedby={progressId}> <div class={cards.card} aria-busy="true" aria-describedby={progressId}>
<LinearProgress <LinearProgress
@ -129,6 +129,7 @@ const MastodonOAuth2Callback: Component = () => {
</p> </p>
</div> </div>
</div> </div>
</>
); );
}; };

View file

@ -2,7 +2,6 @@ import {
Component, Component,
Show, Show,
createEffect, createEffect,
createSelector,
createSignal, createSignal,
createUniqueId, createUniqueId,
onMount, onMount,
@ -10,15 +9,14 @@ import {
import cards from "~material/cards.module.css"; import cards from "~material/cards.module.css";
import TextField from "~material/TextField.js"; import TextField from "~material/TextField.js";
import Button from "~material/Button.js"; import Button from "~material/Button.js";
import { useDocumentTitle } from "../utils";
import { Title } from "~material/typography"; import { Title } from "~material/typography";
import { css } from "solid-styled";
import { LinearProgress } from "@suid/material"; import { LinearProgress } from "@suid/material";
import { createRestAPIClient } from "masto"; import { createRestAPIClient } from "masto";
import { getOrRegisterApp } from "./stores"; import { getOrRegisterApp } from "./stores";
import { useSearchParams } from "@solidjs/router"; import { useSearchParams } from "@solidjs/router";
import { $settings } from "../settings/stores"; import { $settings } from "../settings/stores";
import "./SignIn.css"; import "./SignIn.css";
import DocumentTitle from "~platform/DocumentTitle";
type ErrorParams = { type ErrorParams = {
error: string; error: string;
@ -36,8 +34,6 @@ const SignIn: Component = () => {
const [serverUrlError, setServerUrlError] = createSignal(false); const [serverUrlError, setServerUrlError] = createSignal(false);
const [targetSiteTitle, setTargetSiteTitle] = createSignal(""); const [targetSiteTitle, setTargetSiteTitle] = createSignal("");
useDocumentTitle("Sign In");
const serverUrl = () => { const serverUrl = () => {
const url = rawServerUrl(); const url = rawServerUrl();
if (url.length === 0 || /^%w:/.test(url)) { if (url.length === 0 || /^%w:/.test(url)) {
@ -115,6 +111,8 @@ const SignIn: Component = () => {
}; };
return ( return (
<>
<DocumentTitle>Sign In</DocumentTitle>
<main class="SignIn"> <main class="SignIn">
<Show when={params.error || params.errorDescription}> <Show when={params.error || params.errorDescription}>
<div class={cards.card} style={{ "margin-bottom": "20px" }}> <div class={cards.card} style={{ "margin-bottom": "20px" }}>
@ -164,6 +162,7 @@ const SignIn: Component = () => {
</form> </form>
</div> </div>
</main> </main>
</>
); );
}; };

View file

@ -3,14 +3,12 @@ import {
splitProps, splitProps,
Component, Component,
createSignal, createSignal,
createEffect,
onMount, onMount,
createRenderEffect, createRenderEffect,
Show, Show,
} from "solid-js"; } from "solid-js";
import { css } from "solid-styled"; import { css } from "solid-styled";
import { decode } from "blurhash"; import { decode } from "blurhash";
import { mergeClass } from "../utils";
type ImgProps = { type ImgProps = {
blurhash?: string; blurhash?: string;
@ -24,6 +22,7 @@ const Img: Component<ImgProps> = (props) => {
"blurhash", "blurhash",
"keepBlur", "keepBlur",
"class", "class",
"classList",
"style", "style",
]); ]);
const [isImgLoaded, setIsImgLoaded] = createSignal(false); const [isImgLoaded, setIsImgLoaded] = createSignal(false);
@ -61,21 +60,21 @@ const Img: Component<ImgProps> = (props) => {
const onImgLoaded = () => { const onImgLoaded = () => {
setIsImgLoaded(true); setIsImgLoaded(true);
setImgSize({ setImgSize({
width: imgE.width, width: imgE!.width,
height: imgE.height, height: imgE!.height,
}); });
}; };
const onMetadataLoaded = () => { const onMetadataLoaded = () => {
setImgSize({ setImgSize({
width: imgE.width, width: imgE!.width,
height: imgE.height, height: imgE!.height,
}); });
}; };
onMount(() => { onMount(() => {
setImgSize((x) => { setImgSize((x) => {
const parent = imgE.parentElement; const parent = imgE!.parentElement;
if (!parent) return x; if (!parent) return x;
return x return x
? x ? x
@ -87,7 +86,14 @@ const Img: Component<ImgProps> = (props) => {
}); });
return ( 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}> <Show when={managed.blurhash}>
<canvas <canvas
ref={(canvas) => { ref={(canvas) => {

View 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 <></>;
}

View file

@ -65,6 +65,7 @@ import {
} from "../timelines/toots/ItemSelectionProvider"; } from "../timelines/toots/ItemSelectionProvider";
import AppTopBar from "~material/AppTopBar"; import AppTopBar from "~material/AppTopBar";
import type { Account } from "../accounts/stores"; import type { Account } from "../accounts/stores";
import DocumentTitle from "~platform/DocumentTitle";
const Profile: Component = () => { const Profile: Component = () => {
const { pop } = useNavigator(); const { pop } = useNavigator();
@ -216,6 +217,8 @@ const Profile: Component = () => {
}; };
return ( return (
<>
<DocumentTitle>{profile()?.displayName ?? "Someone"}</DocumentTitle>
<Scaffold <Scaffold
topbar={ topbar={
<AppTopBar <AppTopBar
@ -389,7 +392,9 @@ const Profile: Component = () => {
aria-label={`${relationship()?.following ? "Unfollow" : "Follow"} on your home timeline`} aria-label={`${relationship()?.following ? "Unfollow" : "Follow"} on your home timeline`}
> >
<ListItemAvatar> <ListItemAvatar>
<Avatar src={(session().account as Account).inf?.avatar}></Avatar> <Avatar
src={(session().account as Account).inf?.avatar}
></Avatar>
</ListItemAvatar> </ListItemAvatar>
<ListItemText <ListItemText
secondary={ secondary={
@ -565,6 +570,7 @@ const Profile: Component = () => {
</Show> </Show>
</div> </div>
</Scaffold> </Scaffold>
</>
); );
}; };

View file

@ -26,6 +26,7 @@ import { useStore } from "@nanostores/solid";
import { $settings } from "./stores"; import { $settings } from "./stores";
import { useNavigator } from "~platform/StackedRouter"; import { useNavigator } from "~platform/StackedRouter";
import AppTopBar from "~material/AppTopBar"; import AppTopBar from "~material/AppTopBar";
import DocumentTitle from "~platform/DocumentTitle";
const ChooseLang: Component = () => { const ChooseLang: Component = () => {
const { pop } = useNavigator(); const { pop } = useNavigator();
@ -53,6 +54,8 @@ const ChooseLang: Component = () => {
}; };
return ( return (
<>
<DocumentTitle>{t("Choose Language")}</DocumentTitle>
<Scaffold <Scaffold
topbar={ topbar={
<AppTopBar> <AppTopBar>
@ -114,6 +117,7 @@ const ChooseLang: Component = () => {
</List> </List>
</List> </List>
</Scaffold> </Scaffold>
</>
); );
}; };

View file

@ -19,6 +19,7 @@ import { useStore } from "@nanostores/solid";
import { $settings } from "./stores"; import { $settings } from "./stores";
import { useNavigator } from "~platform/StackedRouter"; import { useNavigator } from "~platform/StackedRouter";
import AppTopBar from "~material/AppTopBar"; import AppTopBar from "~material/AppTopBar";
import DocumentTitle from "~platform/DocumentTitle";
const Motions: Component = () => { const Motions: Component = () => {
const { pop } = useNavigator(); const { pop } = useNavigator();
@ -30,6 +31,8 @@ const Motions: Component = () => {
); );
const settings = useStore($settings); const settings = useStore($settings);
return ( return (
<>
<DocumentTitle>{t("motions")}</DocumentTitle>
<Scaffold <Scaffold
topbar={ topbar={
<AppTopBar> <AppTopBar>
@ -79,6 +82,7 @@ const Motions: Component = () => {
</li> </li>
</List> </List>
</Scaffold> </Scaffold>
</>
); );
}; };

View file

@ -24,6 +24,7 @@ import { $settings } from "./stores";
import { useStore } from "@nanostores/solid"; import { useStore } from "@nanostores/solid";
import { useNavigator } from "~platform/StackedRouter"; import { useNavigator } from "~platform/StackedRouter";
import AppTopBar from "~material/AppTopBar"; import AppTopBar from "~material/AppTopBar";
import DocumentTitle from "~platform/DocumentTitle";
const ChooseRegion: Component = () => { const ChooseRegion: Component = () => {
const { pop } = useNavigator(); const { pop } = useNavigator();
@ -48,6 +49,8 @@ const ChooseRegion: Component = () => {
}; };
return ( return (
<>
<DocumentTitle>{t("Choose Region")}</DocumentTitle>
<Scaffold <Scaffold
topbar={ topbar={
<AppTopBar> <AppTopBar>
@ -101,6 +104,7 @@ const ChooseRegion: Component = () => {
</List> </List>
</List> </List>
</Scaffold> </Scaffold>
</>
); );
}; };

View file

@ -41,6 +41,7 @@ import { makeAcctText, useSessions } from "../masto/clients.js";
import { useNavigator } from "~platform/StackedRouter.jsx"; import { useNavigator } from "~platform/StackedRouter.jsx";
import AppTopBar from "~material/AppTopBar.jsx"; import AppTopBar from "~material/AppTopBar.jsx";
import MastodonLogo from "./MastodonLogo.jsx"; import MastodonLogo from "./MastodonLogo.jsx";
import DocumentTitle from "~platform/DocumentTitle.jsx";
type Inset = { type Inset = {
top?: number; top?: number;
@ -197,6 +198,8 @@ const Settings: Component = () => {
} }
`; `;
return ( return (
<>
<DocumentTitle>{t("Settings")}</DocumentTitle>
<Scaffold <Scaffold
topbar={ topbar={
<AppTopBar> <AppTopBar>
@ -416,6 +419,7 @@ const Settings: Component = () => {
)} )}
</List> </List>
</Scaffold> </Scaffold>
</>
); );
}; };

View file

@ -5,7 +5,6 @@ import {
createEffect, createEffect,
useTransition, useTransition,
} from "solid-js"; } from "solid-js";
import { useDocumentTitle } from "../utils";
import Scaffold from "~material/Scaffold"; import Scaffold from "~material/Scaffold";
import { import {
ListItemSecondaryAction, ListItemSecondaryAction,
@ -30,6 +29,7 @@ import {
import AppTopBar from "~material/AppTopBar"; import AppTopBar from "~material/AppTopBar";
import { createTranslator } from "~platform/i18n"; import { createTranslator } from "~platform/i18n";
import { useWindowSize } from "@solid-primitives/resize-observer"; import { useWindowSize } from "@solid-primitives/resize-observer";
import DocumentTitle from "~platform/DocumentTitle";
type StringRes = Record< type StringRes = Record<
"tabs.home" | "tabs.trending" | "tabs.public" | "set.prefetch-toots", "tabs.home" | "tabs.trending" | "tabs.public" | "set.prefetch-toots",
@ -38,7 +38,6 @@ type StringRes = Record<
const Home: ParentComponent = (props) => { const Home: ParentComponent = (props) => {
let panelList: HTMLDivElement; let panelList: HTMLDivElement;
useDocumentTitle("Timelines");
const [t] = createTranslator( const [t] = createTranslator(
(code) => import(`./i18n/${code}.json`) as Promise<{ default: StringRes }>, (code) => import(`./i18n/${code}.json`) as Promise<{ default: StringRes }>,
); );
@ -179,6 +178,7 @@ const Home: ParentComponent = (props) => {
return ( return (
<> <>
<DocumentTitle>Timelines</DocumentTitle>
<Scaffold <Scaffold
topbar={ topbar={
<AppTopBar> <AppTopBar>

View file

@ -14,7 +14,6 @@ import cards from "~material/cards.module.css";
import { css } from "solid-styled"; import { css } from "solid-styled";
import { createTimeSource, TimeSourceProvider } from "~platform/timesrc"; import { createTimeSource, TimeSourceProvider } from "~platform/timesrc";
import TootComposer from "./TootComposer"; import TootComposer from "./TootComposer";
import { useDocumentTitle } from "../utils";
import { createTimelineControlsForArray } from "../masto/timelines"; import { createTimelineControlsForArray } from "../masto/timelines";
import TootList from "./TootList"; import TootList from "./TootList";
import "./TootBottomSheet.css"; import "./TootBottomSheet.css";
@ -26,6 +25,7 @@ import ItemSelectionProvider, {
import AppTopBar from "~material/AppTopBar"; import AppTopBar from "~material/AppTopBar";
import { fetchStatus } from "../masto/statuses"; import { fetchStatus } from "../masto/statuses";
import { type Account } from "../accounts/stores"; import { type Account } from "../accounts/stores";
import DocumentTitle from "~platform/DocumentTitle";
const TootBottomSheet: Component = (props) => { const TootBottomSheet: Component = (props) => {
const params = useParams<{ acct: string; id: string }>(); const params = useParams<{ acct: string; id: string }>();
@ -65,11 +65,11 @@ const TootBottomSheet: Component = (props) => {
() => tootContext()?.descendants, () => tootContext()?.descendants,
); );
useDocumentTitle(() => { const documentTitle = () => {
const t = toot()?.reblog ?? toot(); const t = toot()?.reblog ?? toot();
const name = t?.account.displayName ?? "Someone"; const name = t?.account.displayName ?? "Someone";
return `${name}'s toot`; return `${name}'s toot`;
}); };
const tootDisplayName = () => { const tootDisplayName = () => {
const t = toot()?.reblog ?? toot(); const t = toot()?.reblog ?? toot();
@ -163,6 +163,7 @@ const TootBottomSheet: Component = (props) => {
} }
class="TootBottomSheet" class="TootBottomSheet"
> >
<DocumentTitle>{documentTitle()}</DocumentTitle>
<div class="Scrollable"> <div class="Scrollable">
<TimeSourceProvider value={time}> <TimeSourceProvider value={time}>
<ItemSelectionProvider value={selectionState}> <ItemSelectionProvider value={selectionState}>

View file

@ -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(" ");
}