Compare commits

..

No commits in common. "66366e648626c841839b3af2c92b0e6ecf0e66e2" and "cd02dc205351d9a0eb957228788f35b95f8b765c" have entirely different histories.

31 changed files with 265 additions and 381 deletions

BIN
bun.lockb

Binary file not shown.

View file

@ -40,8 +40,7 @@
"masto": "^6.8.0", "masto": "^6.8.0",
"nanostores": "^0.9.5", "nanostores": "^0.9.5",
"solid-js": "^1.8.18", "solid-js": "^1.8.18",
"solid-styled": "^0.11.1", "solid-styled": "^0.11.1"
"stacktrace-js": "^2.0.2"
}, },
"packageManager": "bun@1.1.21" "packageManager": "bun@1.1.21"
} }

View file

@ -9,4 +9,4 @@
.custom-emoji { .custom-emoji {
width: 1.25em; width: 1.25em;
} }

View file

@ -53,13 +53,11 @@ const App: Component = () => {
); );
}); });
const UnexpectedError = lazy(() => import("./UnexpectedError.js"))
return ( return (
<ErrorBoundary <ErrorBoundary
fallback={(err, reset) => { fallback={(err, reset) => {
console.error(err); console.error(err);
return <UnexpectedError error={err} />; return <></>;
}} }}
> >
<ThemeProvider theme={theme()}> <ThemeProvider theme={theme()}>

View file

@ -1,40 +0,0 @@
import { Button } from '@suid/material';
import {Component, createResource} from 'solid-js'
import { css } from 'solid-styled';
const UnexpectedError: Component<{error?: any}> = (props) => {
const [errorMsg] = createResource(() => props.error, async (err) => {
if (err instanceof Error) {
const mod = await import('stacktrace-js')
const stacktrace = await mod.fromError(err)
const strackMsg = stacktrace.map(entry => `${entry.functionName ?? "<unknown>"}@${entry.fileName}:(${entry.lineNumber}:${entry.columnNumber})`).join('\n')
return `${err.name}: ${err.message}\n${strackMsg}`
}
return err.toString()
})
css`
main {
padding: calc(var(--safe-area-inset-top) + 20px) calc(var(--safe-area-inset-right) + 20px) calc(var(--safe-area-inset-bottom) + 20px) calc(var(--safe-area-inset-left) + 20px);
}
`
return <main>
<h1>Oh, it is our fault.</h1>
<p>There is an unexpected error in our app, and it's not your fault.</p>
<p>You can reload to see if this guy is gone. If you meet this guy repeatly, please report to us.</p>
<div>
<Button onClick={() => window.location.reload()}>Reload</Button>
</div>
<details>
<summary>{errorMsg.loading ? 'Generating ' : " "}Technical Infomation (Bring to us if you report the problem)</summary>
<pre>
{errorMsg()}
</pre>
</details>
</main>
}
export default UnexpectedError;

View file

@ -44,12 +44,12 @@ const MastodonOAuth2Callback: Component = () => {
setDocumentTitle(`Back from ${ins.title}...`); setDocumentTitle(`Back from ${ins.title}...`);
setSiteTitle(ins.title); setSiteTitle(ins.title);
const srcset = []; const srcset = []
if (ins.thumbnail.versions["@1x"]) { if (ins.thumbnail.versions["@1x"]) {
srcset.push(`${ins.thumbnail.versions["@1x"]} 1x`); srcset.push(`${ins.thumbnail.versions["@1x"]} 1x`)
} }
if (ins.thumbnail.versions["@2x"]) { if (ins.thumbnail.versions["@2x"]) {
srcset.push(`${ins.thumbnail.versions["@2x"]} 2x`); srcset.push(`${ins.thumbnail.versions["@2x"]} 2x`)
} }
setSiteImg({ setSiteImg({
@ -66,8 +66,8 @@ const MastodonOAuth2Callback: Component = () => {
onGoingOAuth2Process, onGoingOAuth2Process,
params.code, params.code,
); );
$settings.setKey("onGoingOAuth2Process", undefined); $settings.setKey('onGoingOAuth2Process', undefined)
navigate("/", { replace: true }); navigate('/', {replace: true})
return; return;
} }
@ -95,27 +95,18 @@ const MastodonOAuth2Callback: Component = () => {
<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
class={[cards.cardNoPad, cards.cardGutSkip].join(" ")} class={[cards.cardNoPad, cards.cardGutSkip].join(' ')}
id={progressId} id={progressId}
aria-labelledby={titleId} aria-labelledby={titleId}
/> />
<Show <Show when={siteImg()} fallback={<i aria-busy="true" aria-label="Preparing image..." style={{"height": "235px", display: "block"}}></i>}>
when={siteImg()}
fallback={
<i
aria-busy="true"
aria-label="Preparing image..."
style={{ height: "235px", display: "block" }}
></i>
}
>
<Img <Img
src={siteImg()?.src} src={siteImg()?.src}
srcset={siteImg()?.srcset} srcset={siteImg()?.srcset}
blurhash={siteImg()?.blurhash} blurhash={siteImg()?.blurhash}
class={[cards.cardNoPad, cards.cardGutSkip].join(" ")} class={[cards.cardNoPad, cards.cardGutSkip].join(' ')}
alt={`Banner image for ${siteTitle()}`} alt={`Banner image for ${siteTitle()}`}
style={{ height: "235px", display: "block" }} style={{"height": "235px", "display": "block"}}
/> />
</Show> </Show>
@ -123,8 +114,7 @@ const MastodonOAuth2Callback: Component = () => {
Contracting {siteTitle}... Contracting {siteTitle}...
</Title> </Title>
<p> <p>
If this page stays too long, you can close this page and sign in If this page stays too long, you can close this page and sign in again.
again.
</p> </p>
</div> </div>
</div> </div>

View file

@ -69,8 +69,8 @@ const SignIn: Component = () => {
}); });
onMount(() => { onMount(() => {
$settings.setKey("onGoingOAuth2Process", undefined); $settings.setKey('onGoingOAuth2Process', undefined)
}); })
const onStartOAuth2 = async (e: Event) => { const onStartOAuth2 = async (e: Event) => {
e.preventDefault(); e.preventDefault();
@ -107,7 +107,7 @@ const SignIn: Component = () => {
for (const [k, v] of Object.entries(args)) { for (const [k, v] of Object.entries(args)) {
searches.set(k, v); searches.set(k, v);
} }
$settings.setKey("onGoingOAuth2Process", url); $settings.setKey("onGoingOAuth2Process", url)
window.location.href = authStart.toString(); window.location.href = authStart.toString();
} catch (e) { } catch (e) {
setServerUrlHelperText( setServerUrlHelperText(

View file

@ -95,13 +95,9 @@ export const updateAcctInf = action(
}, },
); );
export const signOut = action( export const signOut = action($accounts, "signOut", ($store, predicate: (acct: Account) => boolean) => {
$accounts, $store.set($store.get().filter(a => !predicate(a)));
"signOut", });
($store, predicate: (acct: Account) => boolean) => {
$store.set($store.get().filter((a) => !predicate(a)));
},
);
export type RegisteredApp = { export type RegisteredApp = {
site: string; site: string;

View file

@ -1,5 +1,5 @@
import { render } from "solid-js/web"; import {render} from 'solid-js/web'
import App from "./App.js"; import App from './App.js'
import "./material/theme.css"; import "./material/theme.css"
render(() => <App />, document.getElementById("root")!); render(() => <App />, document.getElementById("root")!)

View file

@ -12,7 +12,9 @@ type Timeline = {
}): mastodon.Paginator<mastodon.v1.Status[], unknown>; }): mastodon.Paginator<mastodon.v1.Status[], unknown>;
}; };
export function useTimeline(timeline: Accessor<Timeline>) { export function useTimeline(
timeline: Accessor<Timeline>,
) {
let minId: string | undefined; let minId: string | undefined;
let maxId: string | undefined; let maxId: string | undefined;
let otl: Timeline | undefined; let otl: Timeline | undefined;

View file

@ -1,5 +1,5 @@
.bottomSheet { .bottomSheet {
composes: surface from "material.module.css"; composes: surface from 'material.module.css';
border: none; border: none;
position: absolute; position: absolute;
left: 50%; left: 50%;
@ -22,11 +22,11 @@
@media (max-width: 560px) { @media (max-width: 560px) {
& { & {
left: 0; left: 0;
top: var(--safe-area-inset-top, 0); top: 0;
transform: none; transform: none;
bottom: 0; bottom: 0;
height: 100vh; height: 100vh;
height: 100dvh; height: 100dvh;
} }
} }
} }

View file

@ -1,5 +1,5 @@
import { createEffect, type ParentComponent } from "solid-js"; import { createEffect, type ParentComponent } from "solid-js";
import styles from "./BottomSheet.module.css"; import styles from './BottomSheet.module.css'
export type BottomSheetProps = { export type BottomSheetProps = {
open?: boolean; open?: boolean;
@ -20,11 +20,7 @@ const BottomSheet: ParentComponent<BottomSheetProps> = (props) => {
} }
}); });
return ( return <dialog class={styles.bottomSheet} ref={element!}>{props.children}</dialog>;
<dialog class={styles.bottomSheet} ref={element!}>
{props.children}
</dialog>
);
}; };
export default BottomSheet; export default BottomSheet;

View file

@ -9,12 +9,12 @@ import materialStyles from "./material.module.css";
const Button: Component<JSX.ButtonHTMLAttributes<HTMLButtonElement>> = ( const Button: Component<JSX.ButtonHTMLAttributes<HTMLButtonElement>> = (
props, props,
) => { ) => {
const [managed, passthough] = splitProps(props, ["class", "type"]); const [managed, passthough] = splitProps(props, ["class", 'type']);
const classes = () => const classes = () =>
managed.class managed.class
? [materialStyles.button, managed.class].join(" ") ? [materialStyles.button, managed.class].join(" ")
: materialStyles.button; : materialStyles.button;
const type = () => managed.type ?? "button"; const type = () => managed.type ?? 'button'
return <button type={type()} class={classes()} {...passthough}></button>; return <button type={type()} class={classes()} {...passthough}></button>;
}; };

View file

@ -1,5 +1,5 @@
.card { .card {
composes: surface from "material.module.css"; composes: surface from 'material.module.css';
border-radius: 2px; border-radius: 2px;
box-shadow: var(--tutu-shadow-e2); box-shadow: var(--tutu-shadow-e2);
transition: var(--tutu-transition-shadow); transition: var(--tutu-transition-shadow);
@ -12,7 +12,7 @@
} }
&:not(.manualMargin) { &:not(.manualMargin) {
& > :not(.cardNoPad) { &>:not(.cardNoPad) {
margin-inline: var(--card-pad, 20px); margin-inline: var(--card-pad, 20px);
} }
@ -20,7 +20,7 @@
margin-top: var(--card-gut, 20px); margin-top: var(--card-gut, 20px);
} }
> .cardGutSkip + *:not(.cardGutSkip) { >.cardGutSkip+*:not(.cardGutSkip) {
margin-top: var(--card-gut, 20px); margin-top: var(--card-gut, 20px);
} }
@ -28,6 +28,7 @@
margin-bottom: var(--card-gut, 20px); margin-bottom: var(--card-gut, 20px);
} }
} }
} }
.layoutCentered { .layoutCentered {
@ -51,4 +52,4 @@
overflow: auto; overflow: auto;
} }
} }
} }

View file

@ -1,5 +1,5 @@
.textfield { .textfield {
composes: touchTarget from "material.module.css"; composes: touchTarget from 'material.module.css';
--border-color: var(--tutu-color-inactive-on-surface); --border-color: var(--tutu-color-inactive-on-surface);
--active-border-color: var(--tutu-color-primary); --active-border-color: var(--tutu-color-primary);
@ -7,78 +7,74 @@
--active-label-color: var(--tutu-color-primary); --active-label-color: var(--tutu-color-primary);
--helper-text-color: var(--tutu-color-inactive-on-surface); --helper-text-color: var(--tutu-color-inactive-on-surface);
& > * { &>* {
width: 100%; width: 100%;
} }
&.error, &.error, &:has(>input[aria-invalid="true"]) {
&:has(> input[aria-invalid="true"]) { &:not(:focus-within) {
&:not(:focus-within) { --border-color: var(--tutu-color-error-on-surface);
--border-color: var(--tutu-color-error-on-surface); --label-color: var(--tutu-color-error-on-surface);
--label-color: var(--tutu-color-error-on-surface); --helper-text-color: var(--tutu-color-error-on-surface);
--helper-text-color: var(--tutu-color-error-on-surface); }
}
&:focus-within { &:focus-within {
--helper-text-color: var(--tutu-color-error-on-surface); --helper-text-color: var(--tutu-color-error-on-surface);
} }
} }
position: relative; position: relative;
& > label { &>label {
position: absolute; position: absolute;
left: 0; left: 0;
bottom: calc(10px + var(--bottom-height, 0px)); bottom: calc(10px + var(--bottom-height, 0px));
color: var(--label-color); color: var(--label-color);
transition: transition: bottom .2s ease-in-out, font-size .2s ease-in-out, color .2s ease-in-out;
bottom 0.2s ease-in-out, cursor: text;
font-size 0.2s ease-in-out, font-size: 0.8125rem;
color 0.2s ease-in-out;
cursor: text;
font-size: 0.8125rem;
} }
& > label:has(+ input:not(:placeholder-shown)) { &>label:has(+ input:not(:placeholder-shown)) {
bottom: calc(100% - 0.8125rem); bottom: calc(100% - 0.8125rem);
} }
&:focus-within > label, &:focus-within>label, &.float-label>label {
&.float-label > label { bottom: calc(100% - 0.8125rem);
bottom: calc(100% - 0.8125rem); color: var(--active-label-color);
color: var(--active-label-color);
} }
& > input[type="text"], &>input[type='text'],
& > input[type="password"] { &>input[type='password'] {
border: none; border: none;
outline: none; outline: none;
border-bottom: 1px solid var(--border-color); border-bottom: 1px solid var(--border-color);
background-color: transparent; background-color: transparent;
padding-top: 16px; padding-top: 16px;
padding-bottom: 8px; padding-bottom: 8px;
margin-bottom: 1px; margin-bottom: 1px;
transition: border-color 0.2s ease-in-out; transition: border-color .2s ease-in-out;
&:focus { &:focus {
border-bottom: 2px solid var(--active-border-color); border-bottom: 2px solid var(--active-border-color);
margin-bottom: 0; margin-bottom: 0;
} }
} }
&.withHelperText { &.withHelperText {
--bottom-height: 0.8125rem; --bottom-height: 0.8125rem;
} }
& .helperText { & .helperText {
color: var(--helper-text-color); color: var(--helper-text-color);
font-size: 0.8125rem; font-size: 0.8125rem;
line-height: 100%; line-height: 100%;
-webkit-line-clamp: 1; -webkit-line-clamp: 1;
line-clamp: 1; line-clamp: 1;
display: flex; display: flex;
justify-content: space-between; justify-content: space-between;
min-height: 0.8125rem; min-height: 0.8125rem;
cursor: auto; cursor: auto;
} }
} }

View file

@ -10,7 +10,7 @@
} }
.button { .button {
composes: buttonText from "./typography.module.css"; composes: buttonText from './typography.module.css';
composes: touchTarget; composes: touchTarget;
border: none; border: none;
@ -18,34 +18,28 @@
color: var(--tutu-color-primary); color: var(--tutu-color-primary);
font-family: inherit; font-family: inherit;
&:focus, &:focus,&:hover,&:focus-visible {
&:hover, background-color: var(--tutu-color-surface-dd);
&:focus-visible {
background-color: var(--tutu-color-surface-dd);
} }
&.pressed { &.pressed {
background-color: var(--tutu-color-surface-d); background-color: var(--tutu-color-surface-d);
} }
&.raised { &.raised {
background-color: var(--tutu-color-primary); background-color: var(--tutu-color-primary);
color: var(--tutu-color-on-primary); color: var(--tutu-color-on-primary);
} }
&:disabled, &:disabled, &[aria-disabled]:not([aria-disabled="false"]) {
&[aria-disabled]:not([aria-disabled="false"]) { color: #9e9e9e;
color: #9e9e9e;
&:focus, &:focus,&:hover,&:focus-visible {
&:hover, background-color: transparent;
&:focus-visible { }
background-color: transparent;
}
} }
.toolbar &, .toolbar &, .appbar & {
.appbar & {
height: 100%; height: 100%;
margin-block: 0; margin-block: 0;
padding-block: 0; padding-block: 0;
@ -55,9 +49,7 @@
.appbar & { .appbar & {
color: var(--tutu-color-on-primary); color: var(--tutu-color-on-primary);
&:focus, &:focus,&:hover,&:focus-visible {
&:hover,
&:focus-visible {
background-color: var(--tutu-color-primary-ll); background-color: var(--tutu-color-primary-ll);
} }
@ -70,3 +62,4 @@
color: var(--tutu-color-on-surface); color: var(--tutu-color-on-surface);
} }
} }

View file

@ -2,16 +2,15 @@ import { Theme, createTheme } from "@suid/material/styles";
import { deepPurple, amber } from "@suid/material/colors"; import { deepPurple, amber } from "@suid/material/colors";
import { Accessor } from "solid-js"; import { Accessor } from "solid-js";
export function useRootTheme(): Accessor<Theme> { export function useRootTheme() : Accessor<Theme> {
return () => return () => createTheme({
createTheme({ palette: {
palette: { primary: {
primary: { main: deepPurple[500]
main: deepPurple[500],
},
secondary: {
main: amber.A200,
},
}, },
}); secondary: {
main: amber.A200
}
}
})
} }

View file

@ -1,6 +1,5 @@
:root, :root,
[lang^="en"], [lang^="en"], [lang="en"] {
[lang="en"] {
--md-typography-type: "regular"; --md-typography-type: "regular";
--title-size: 1.25rem; --title-size: 1.25rem;
--title-weight: 500; --title-weight: 500;
@ -20,12 +19,9 @@
} }
} }
[lang^="zh"], [lang^="zh"], [lang="zh"],
[lang="zh"], [lang^="kr"], [lang="kr"],
[lang^="kr"], [lang^="ja"], [lang="ja"] {
[lang="kr"],
[lang^="ja"],
[lang="ja"] {
--md-typography-type: "dense"; --md-typography-type: "dense";
--title-size: 1.4375rem; --title-size: 1.4375rem;
--subheading-size: 1.1875rem; --subheading-size: 1.1875rem;
@ -99,6 +95,7 @@
--tutu-anim-curve-sharp: cubic-bezier(0.4, 0, 0.6, 1); --tutu-anim-curve-sharp: cubic-bezier(0.4, 0, 0.6, 1);
@media (max-width: 300px) { @media (max-width: 300px) {
/* XS screen, like wearables */ /* XS screen, like wearables */
& { & {
--tutu-transition-shadow: box-shadow 157.5ms var(--tutu-anim-curve-std); --tutu-transition-shadow: box-shadow 157.5ms var(--tutu-anim-curve-std);
@ -106,6 +103,7 @@
} }
@media (max-width: 600px) { @media (max-width: 600px) {
/* Mobile */ /* Mobile */
& { & {
--tutu-transition-shadow: box-shadow 225ms var(--tutu-anim-curve-std); --tutu-transition-shadow: box-shadow 225ms var(--tutu-anim-curve-std);
@ -113,6 +111,7 @@
} }
@media (max-width: 1200px) { @media (max-width: 1200px) {
/* Tablet */ /* Tablet */
& { & {
--tutu-transition-shadow: box-shadow 292.5ms var(--tutu-anim-curve-std); --tutu-transition-shadow: box-shadow 292.5ms var(--tutu-anim-curve-std);
@ -126,17 +125,11 @@
} }
* { * {
font-family: font-family: Roboto, "Noto Sans", system-ui, -apple-system, BlinkMacSystemFont, sans-serif;
Roboto,
"Noto Sans",
system-ui,
-apple-system,
BlinkMacSystemFont,
sans-serif;
box-sizing: border-box; box-sizing: border-box;
margin: 0; margin: 0;
} }
body { body {
font-size: var(--body-size, 1rem); font-size: var(--body-size, 1rem);
} }

View file

@ -3,7 +3,7 @@ import { Dynamic } from "solid-js/web";
import typography from "./typography.module.css"; import typography from "./typography.module.css";
import { mergeClass } from "../utils"; import { mergeClass } from "../utils";
type AnyElement = keyof JSX.IntrinsicElements | ParentComponent<any>; type AnyElement = keyof JSX.IntrinsicElements | ParentComponent<any>
type PropsOf<E extends AnyElement> = type PropsOf<E extends AnyElement> =
E extends ParentComponent<infer Props> E extends ParentComponent<infer Props>
@ -12,7 +12,9 @@ type PropsOf<E extends AnyElement> =
? JSX.IntrinsicElements[E] ? JSX.IntrinsicElements[E]
: JSX.HTMLAttributes<HTMLElement>; : JSX.HTMLAttributes<HTMLElement>;
export type TypographyProps<E extends AnyElement> = { export type TypographyProps<
E extends AnyElement,
> = {
ref?: Ref<E>; ref?: Ref<E>;
component?: E; component?: E;
class?: string; class?: string;
@ -31,9 +33,7 @@ type TypographyKind =
| "caption" | "caption"
| "buttonText"; | "buttonText";
export function Typography<T extends AnyElement>( export function Typography<T extends AnyElement>(props: {typography: TypographyKind } & TypographyProps<T>) {
props: { typography: TypographyKind } & TypographyProps<T>,
) {
const [managed, passthough] = splitProps(props, [ const [managed, passthough] = splitProps(props, [
"ref", "ref",
"component", "component",
@ -50,38 +50,38 @@ export function Typography<T extends AnyElement>(
{...passthough} {...passthough}
></Dynamic> ></Dynamic>
); );
} };
export function Display4<E extends AnyElement>(props: TypographyProps<E>) { export function Display4<E extends AnyElement>(props: TypographyProps<E>) {
return <Typography typography={"display4"} {...props}></Typography>; return <Typography typography={"display4"} {...props}></Typography>
} }
export function Display3<E extends AnyElement>(props: TypographyProps<E>) { export function Display3<E extends AnyElement>(props: TypographyProps<E>) {
return <Typography typography={"display3"} {...props}></Typography>; return <Typography typography={"display3"} {...props}></Typography>
} }
export function Display2<E extends AnyElement>(props: TypographyProps<E>) { export function Display2<E extends AnyElement>(props: TypographyProps<E>) {
return <Typography typography={"display2"} {...props}></Typography>; return <Typography typography={"display2"} {...props}></Typography>
} }
export function Display1<E extends AnyElement>(props: TypographyProps<E>) { export function Display1<E extends AnyElement>(props: TypographyProps<E>) {
return <Typography typography={"display1"} {...props}></Typography>; return <Typography typography={"display1"} {...props}></Typography>
} }
export function Headline<E extends AnyElement>(props: TypographyProps<E>) { export function Headline<E extends AnyElement>(props: TypographyProps<E>) {
return <Typography typography={"headline"} {...props}></Typography>; return <Typography typography={"headline"} {...props}></Typography>
} }
export function Title<E extends AnyElement>(props: TypographyProps<E>) { export function Title<E extends AnyElement>(props: TypographyProps<E>) {
return <Typography typography={"title"} {...props}></Typography>; return <Typography typography={"title"} {...props}></Typography>
} }
export function Subheading<E extends AnyElement>(props: TypographyProps<E>) { export function Subheading<E extends AnyElement>(props: TypographyProps<E>) {
return <Typography typography={"subheading"} {...props}></Typography>; return <Typography typography={"subheading"} {...props}></Typography>
} }
export function Body1<E extends AnyElement>(props: TypographyProps<E>) { export function Body1<E extends AnyElement>(props: TypographyProps<E>) {
return <Typography typography={"body1"} {...props}></Typography>; return <Typography typography={"body1"} {...props}></Typography>
} }
export function Body2<E extends AnyElement>(props: TypographyProps<E>) { export function Body2<E extends AnyElement>(props: TypographyProps<E>) {
return <Typography typography={"body2"} {...props}></Typography>; return <Typography typography={"body2"} {...props}></Typography>
} }
export function Caption<E extends AnyElement>(props: TypographyProps<E>) { export function Caption<E extends AnyElement>(props: TypographyProps<E>) {
return <Typography typography={"caption"} {...props}></Typography>; return <Typography typography={"caption"} {...props}></Typography>
} }
export function ButtonText<E extends AnyElement>(props: TypographyProps<E>) { export function ButtonText<E extends AnyElement>(props: TypographyProps<E>) {
return <Typography typography={"buttonText"} {...props}></Typography>; return <Typography typography={"buttonText"} {...props}></Typography>
} }

View file

@ -1,13 +1,11 @@
import { createContext, useContext, type Accessor } from "solid-js"; import { createContext, useContext, type Accessor } from "solid-js";
export type HeroSource = { export type HeroSource = {[key: string | symbol | number]: HTMLElement | undefined}
[key: string | symbol | number]: HTMLElement | undefined;
};
const HeroSourceContext = createContext<Accessor<HeroSource>>(() => ({})); const HeroSourceContext = createContext<Accessor<HeroSource>>(() => ({}))
export const HeroSourceProvider = HeroSourceContext.Provider; export const HeroSourceProvider = HeroSourceContext.Provider
export function useHeroSource() { export function useHeroSource() {
return useContext(HeroSourceContext); return useContext(HeroSourceContext)
} }

View file

@ -1,15 +1,11 @@
import { persistentMap } from "@nanostores/persistent"; import { persistentMap } from "@nanostores/persistent";
type Settings = { type Settings = {
onGoingOAuth2Process?: string; onGoingOAuth2Process?: string
prefetchTootsDisabled?: boolean; prefetchTootsDisabled?: boolean
}; }
export const $settings = persistentMap<Settings>( export const $settings = persistentMap<Settings>("settings::", {}, {
"settings::", encode: JSON.stringify,
{}, decode: JSON.parse
{ })
encode: JSON.stringify,
decode: JSON.parse,
},
);

View file

@ -18,14 +18,16 @@ const CompactToot: Component<CompactTootProps> = (props) => {
const toot = () => props.status; const toot = () => props.status;
return ( return (
<section <section
class={[tootStyle.compact, props.class || ""].join(" ")} class={[tootStyle.compact, props.class || ""].join(" ")}
lang={toot().language || undefined} lang={toot().language || undefined}
> >
<Img <Img
src={toot().account.avatar} src={toot().account.avatar}
class={[tootStyle.tootAvatar].join(" ")} class={[
tootStyle.tootAvatar,
].join(" ")}
/> />
<div class={[tootStyle.compactAuthorGroup].join(" ")}> <div class={[tootStyle.compactAuthorGroup].join(' ')}>
<Body2 <Body2
ref={(e: { innerHTML: string }) => { ref={(e: { innerHTML: string }) => {
appliedCustomEmoji( appliedCustomEmoji(
@ -46,7 +48,7 @@ const CompactToot: Component<CompactTootProps> = (props) => {
ref={(e: { innerHTML: string }) => { ref={(e: { innerHTML: string }) => {
appliedCustomEmoji(e, toot().content, toot().emojis); appliedCustomEmoji(e, toot().content, toot().emojis);
}} }}
class={[tootStyle.compactTootContent].join(" ")} class={[tootStyle.compactTootContent].join(' ')}
></div> ></div>
</section> </section>
); );

View file

@ -155,20 +155,20 @@ const Home: ParentComponent = (props) => {
useDocumentTitle("Timelines"); useDocumentTitle("Timelines");
const now = createTimeSource(); const now = createTimeSource();
const settings$ = useStore($settings); const settings$ = useStore($settings)
const sessions = useSessions(); const sessions = useSessions();
const client = () => sessions()[0].client; const client = () => sessions()[0].client;
const [profile] = useAcctProfile(client); const [profile] = useAcctProfile(client);
const [panelOffset, setPanelOffset] = createSignal(0); const [panelOffset, setPanelOffset] = createSignal(0);
const prefetching = () => !settings$().prefetchTootsDisabled; const prefetching = () => !settings$().prefetchTootsDisabled
const [currentFocusOn, setCurrentFocusOn] = createSignal<HTMLElement[]>([]); const [currentFocusOn, setCurrentFocusOn] = createSignal<HTMLElement[]>([]);
const [focusRange, setFocusRange] = createSignal([0, 0] as readonly [ const [focusRange, setFocusRange] = createSignal([0, 0] as readonly [
number, number,
number, number,
]); ]);
const child = children(() => props.children); const child = children(() => props.children)
let scrollEventLockReleased = true; let scrollEventLockReleased = true;
@ -229,7 +229,7 @@ const Home: ParentComponent = (props) => {
const onTabClick = (idx: number) => { const onTabClick = (idx: number) => {
const items = panelList.querySelectorAll(".tab-panel"); const items = panelList.querySelectorAll(".tab-panel");
if (items.length > idx) { if (items.length > idx) {
items.item(idx).scrollIntoView({ block: "start", behavior: "smooth" }); items.item(idx).scrollIntoView({ block: "nearest", behavior: "smooth" });
} }
}; };
@ -269,11 +269,7 @@ const Home: ParentComponent = (props) => {
<Scaffold <Scaffold
topbar={ topbar={
<AppBar position="static"> <AppBar position="static">
<Toolbar <Toolbar variant="dense" class="responsive" sx={{ paddingTop: "var(--safe-area-inset-top, 0px)" }}>
variant="dense"
class="responsive"
sx={{ paddingTop: "var(--safe-area-inset-top, 0px)" }}
>
<Tabs onFocusChanged={setCurrentFocusOn} offset={panelOffset()}> <Tabs onFocusChanged={setCurrentFocusOn} offset={panelOffset()}>
<Tab focus={isTabFocus(0)} onClick={[onTabClick, 0]}> <Tab focus={isTabFocus(0)} onClick={[onTabClick, 0]}>
Home Home
@ -286,14 +282,7 @@ const Home: ParentComponent = (props) => {
</Tab> </Tab>
</Tabs> </Tabs>
<ProfileMenuButton profile={profile()}> <ProfileMenuButton profile={profile()}>
<MenuItem <MenuItem onClick={(e) => $settings.setKey("prefetchTootsDisabled", !$settings.get().prefetchTootsDisabled)}>
onClick={(e) =>
$settings.setKey(
"prefetchTootsDisabled",
!$settings.get().prefetchTootsDisabled,
)
}
>
<ListItemText>Prefetch Toots</ListItemText> <ListItemText>Prefetch Toots</ListItemText>
<ListItemSecondaryAction> <ListItemSecondaryAction>
<Switch checked={prefetching()}></Switch> <Switch checked={prefetching()}></Switch>

View file

@ -11,7 +11,7 @@ const MediaAttachmentGrid: Component<{
}> = (props) => { }> = (props) => {
let rootRef: HTMLElement; let rootRef: HTMLElement;
const [viewerIndex, setViewerIndex] = createSignal<number>(); const [viewerIndex, setViewerIndex] = createSignal<number>();
const viewerOpened = () => typeof viewerIndex() !== "undefined"; const viewerOpened = () => typeof viewerIndex() !== "undefined"
const gridTemplateColumns = () => { const gridTemplateColumns = () => {
const l = props.attachments.length; const l = props.attachments.length;
if (l < 2) { if (l < 2) {

View file

@ -36,7 +36,7 @@ function within(n: number, target: number, range: number) {
} }
function clamp(input: number, min: number, max: number) { function clamp(input: number, min: number, max: number) {
return Math.min(Math.max(input, min), max); return Math.min(Math.max(input, min), max)
} }
const MediaViewer: ParentComponent<MediaViewerProps> = (props) => { const MediaViewer: ParentComponent<MediaViewerProps> = (props) => {
@ -128,13 +128,6 @@ const MediaViewer: ParentComponent<MediaViewerProps> = (props) => {
left: 0; left: 0;
z-index: 1; z-index: 1;
cursor: ${dragging() ? "grabbing" : "grab"}; cursor: ${dragging() ? "grabbing" : "grab"};
padding-left: var(--safe-area-inset-left, 0);
padding-right: var(--safe-area-inset-right, 0);
padding-bottom: var(--safe-area-inset-bottom, 0);
:global(> .MuiToolbar-root) {
padding-top: var(--safe-area-inset-top, 0);
}
} }
.left-dock { .left-dock {
@ -214,13 +207,7 @@ const MediaViewer: ParentComponent<MediaViewerProps> = (props) => {
move: number, move: number,
idx: number, idx: number,
) => { ) => {
const { const { ref, top: otop, left: oleft, scale: oscale, osize: [owidth, oheight] } = state[idx];
ref,
top: otop,
left: oleft,
scale: oscale,
osize: [owidth, oheight],
} = state[idx];
const [cx, cy] = center; const [cx, cy] = center;
const iy = clamp(cy - otop, 0, oheight), const iy = clamp(cy - otop, 0, oheight),
ix = clamp(cx - oleft, 0, owidth); // in image coordinate system ix = clamp(cx - oleft, 0, owidth); // in image coordinate system

View file

@ -8,13 +8,7 @@ import {
Menu, Menu,
MenuItem, MenuItem,
} from "@suid/material"; } from "@suid/material";
import { import { Show, createSignal, createUniqueId, type ParentComponent } from "solid-js";
ErrorBoundary,
Show,
createSignal,
createUniqueId,
type ParentComponent,
} from "solid-js";
import { import {
Settings as SettingsIcon, Settings as SettingsIcon,
Bookmark as BookmarkIcon, Bookmark as BookmarkIcon,
@ -48,79 +42,79 @@ const ProfileMenuButton: ParentComponent<{
return ( return (
<> <>
<ButtonBase <ButtonBase
aria-haspopup="true" aria-haspopup="true"
sx={{ borderRadius: "50%" }} sx={{ borderRadius: "50%" }}
id={buttonId} id={buttonId}
onClick={onClick} onClick={onClick}
aria-controls={open() ? menuId : undefined} aria-controls={open() ? menuId : undefined}
aria-expanded={open() ? "true" : undefined} aria-expanded={open() ? "true" : undefined}
> >
<Avatar <Avatar
alt={`${props.profile?.displayName}'s avatar`} alt={`${props.profile?.displayName}'s avatar`}
src={props.profile?.avatar} src={props.profile?.avatar}
></Avatar> ></Avatar>
</ButtonBase> </ButtonBase>
<Menu <Menu
id={menuId} id={menuId}
anchorEl={anchor()} anchorEl={anchor()}
open={open()} open={open()}
onClose={onClose} onClose={onClose}
MenuListProps={{ MenuListProps={{
"aria-labelledby": buttonId, "aria-labelledby": buttonId,
sx: { sx: {
minWidth: "220px", minWidth: "220px",
}, }
}} }}
anchorOrigin={{ anchorOrigin={{
vertical: "top", vertical: "top",
horizontal: "right", horizontal: "right",
}} }}
transformOrigin={{ transformOrigin={{
vertical: "top", vertical: "top",
horizontal: "right", horizontal: "right",
}} }}
> >
<MenuItem> <MenuItem>
<ListItemAvatar> <ListItemAvatar>
<Avatar src={props.profile?.avatar}></Avatar> <Avatar src={props.profile?.avatar}></Avatar>
</ListItemAvatar> </ListItemAvatar>
<ListItemText <ListItemText
primary={props.profile?.displayName} primary={props.profile?.displayName}
secondary={`@${props.profile?.username}`} secondary={`@${props.profile?.username}`}
></ListItemText> ></ListItemText>
</MenuItem> </MenuItem>
<MenuItem> <MenuItem>
<ListItemIcon> <ListItemIcon>
<BookmarkIcon /> <BookmarkIcon />
</ListItemIcon> </ListItemIcon>
<ListItemText>Bookmarks</ListItemText> <ListItemText>Bookmarks</ListItemText>
</MenuItem> </MenuItem>
<MenuItem> <MenuItem>
<ListItemIcon> <ListItemIcon>
<LikeIcon /> <LikeIcon />
</ListItemIcon> </ListItemIcon>
<ListItemText>Likes</ListItemText> <ListItemText>Likes</ListItemText>
</MenuItem> </MenuItem>
<MenuItem> <MenuItem>
<ListItemIcon> <ListItemIcon>
<ListIcon /> <ListIcon />
</ListItemIcon> </ListItemIcon>
<ListItemText>Lists</ListItemText> <ListItemText>Lists</ListItemText>
</MenuItem> </MenuItem>
<Divider />
<Show when={props.children}>
{props.children}
<Divider /> <Divider />
<Show when={props.children}> </Show>
{props.children} <MenuItem component={A} href="/settings" onClick={onClose}>
<Divider /> <ListItemIcon>
</Show> <SettingsIcon />
<MenuItem component={A} href="/settings" onClick={onClose}> </ListItemIcon>
<ListItemIcon> <ListItemText>Settings</ListItemText>
<SettingsIcon /> </MenuItem>
</ListItemIcon> </Menu>
<ListItemText>Settings</ListItemText>
</MenuItem>
</Menu>
</> </>
); );
}; };

View file

@ -211,7 +211,7 @@ const RegularToot: Component<TootCardProps> = (props) => {
classList={{ classList={{
[tootStyle.toot]: true, [tootStyle.toot]: true,
[tootStyle.expanded]: managed.evaluated, [tootStyle.expanded]: managed.evaluated,
[managed.class || ""]: true, [managed.class || ""]: true
}} }}
ref={rootRef!} ref={rootRef!}
lang={toot().language || managed.lang} lang={toot().language || managed.lang}

View file

@ -1,7 +1,8 @@
import type { Component } from "solid-js"; import type { Component } from "solid-js";
const TootBottomSheet: Component = (props) => {
return <></>;
};
export default TootBottomSheet; const TootBottomSheet: Component = (props) => {
return <></>
}
export default TootBottomSheet

View file

@ -38,9 +38,7 @@ const TootThread: Component<TootThreadProps> = (props) => {
css` css`
article { article {
transition: transition: margin 90ms var(--tutu-anim-curve-sharp), var(--tutu-transition-shadow);
margin 90ms var(--tutu-anim-curve-sharp),
var(--tutu-transition-shadow);
user-select: none; user-select: none;
cursor: pointer; cursor: pointer;
} }
@ -66,10 +64,7 @@ const TootThread: Component<TootThreadProps> = (props) => {
`; `;
return ( return (
<article <article classList={{ "thread-line": !!inReplyTo(), "expanded": expanded() }} onClick={() => setExpanded((x) => !x)}>
classList={{ "thread-line": !!inReplyTo(), expanded: expanded() }}
onClick={() => setExpanded((x) => !x)}
>
<Show when={inReplyTo()}> <Show when={inReplyTo()}>
<CompactToot <CompactToot
status={inReplyTo()!} status={inReplyTo()!}

View file

@ -6,14 +6,13 @@
&.toot { &.toot {
/* fix composition ordering: I think the css module processor should aware the overriding and behaves, but no */ /* fix composition ordering: I think the css module processor should aware the overriding and behaves, but no */
transition: transition: margin-block 125ms var(--tutu-anim-curve-std),
margin-block 125ms var(--tutu-anim-curve-std),
height 225ms var(--tutu-anim-curve-std), height 225ms var(--tutu-anim-curve-std),
var(--tutu-transition-shadow); var(--tutu-transition-shadow);
border-radius: 0; border-radius: 0;
} }
& > .toot { &>.toot {
box-shadow: none; box-shadow: none;
} }
@ -47,11 +46,11 @@
display: grid; display: grid;
grid-template-columns: 1fr auto; grid-template-columns: 1fr auto;
> * { >* {
color: var(--tutu-color-secondary-text-on-surface); color: var(--tutu-color-secondary-text-on-surface);
} }
> :last-child { >:last-child {
grid-column: 1 /3; grid-column: 1 /3;
} }
@ -81,7 +80,7 @@
} }
.tootContent { .tootContent {
composes: cardNoPad from "../material/cards.module.css"; composes: cardNoPad from '../material/cards.module.css';
margin-left: calc(var(--card-pad, 0) + var(--toot-avatar-size, 0) + 8px); margin-left: calc(var(--card-pad, 0) + var(--toot-avatar-size, 0) + 8px);
margin-right: var(--card-pad, 0); margin-right: var(--card-pad, 0);
line-height: 1.5; line-height: 1.5;
@ -151,14 +150,14 @@
} }
.tootAttachmentGrp { .tootAttachmentGrp {
composes: cardNoPad from "../material/cards.module.css"; composes: cardNoPad from '../material/cards.module.css';
margin-top: 1em; margin-top: 1em;
margin-left: calc(var(--card-pad, 0) + var(--toot-avatar-size, 0) + 8px); margin-left: calc(var(--card-pad, 0) + var(--toot-avatar-size, 0) + 8px);
margin-right: var(--card-pad, 0); margin-right: var(--card-pad, 0);
display: grid; display: grid;
gap: 4px; gap: 4px;
> :where(img) { >:where(img) {
max-height: 35vh; max-height: 35vh;
min-height: 40px; min-height: 40px;
object-fit: none; object-fit: none;
@ -169,7 +168,7 @@
} }
.tootBottomActionGrp { .tootBottomActionGrp {
composes: cardGutSkip from "../material/cards.module.css"; composes: cardGutSkip from '../material/cards.module.css';
padding-block: calc((var(--card-gut) - 10px) / 2); padding-block: calc((var(--card-gut) - 10px) / 2);
animation: 225ms var(--tutu-anim-curve-std) tootBottomExpanding; animation: 225ms var(--tutu-anim-curve-std) tootBottomExpanding;
@ -177,7 +176,7 @@
flex-flow: row wrap; flex-flow: row wrap;
justify-content: space-evenly; justify-content: space-evenly;
> button { > button{
color: var(--tutu-color-on-surface); color: var(--tutu-color-on-surface);
padding: 10px 8px; padding: 10px 8px;
@ -207,4 +206,4 @@
100% { 100% {
opacity: 1; opacity: 1;
} }
} }

View file

@ -1,26 +1,26 @@
import { createRenderEffect, createSignal, onCleanup } from "solid-js"; import { createRenderEffect, createSignal, onCleanup } from "solid-js";
export function useDocumentTitle(newTitle?: string) { export function useDocumentTitle(newTitle?: string) {
const capturedTitle = document.title; const capturedTitle = document.title
const [title, setTitle] = createSignal(newTitle ?? capturedTitle); const [title, setTitle] = createSignal(newTitle ?? capturedTitle)
createRenderEffect(() => { createRenderEffect(() => {
document.title = title(); document.title = title()
}); })
onCleanup(() => { onCleanup(() => {
document.title = capturedTitle; document.title = capturedTitle
}); })
return setTitle; return setTitle
} }
export function mergeClass(c1: string | undefined, c2: string | undefined) { export function mergeClass(c1: string | undefined, c2: string | undefined) {
if (!c1) { if (!c1) {
return c2; return c2
} }
if (!c2) { if (!c2) {
return c1; return c1
} }
return [c1, c2].join(" "); return [c1, c2].join(' ')
} }