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",
"nanostores": "^0.9.5",
"solid-js": "^1.8.18",
"solid-styled": "^0.11.1",
"stacktrace-js": "^2.0.2"
"solid-styled": "^0.11.1"
},
"packageManager": "bun@1.1.21"
}

View file

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

View file

@ -53,13 +53,11 @@ const App: Component = () => {
);
});
const UnexpectedError = lazy(() => import("./UnexpectedError.js"))
return (
<ErrorBoundary
fallback={(err, reset) => {
console.error(err);
return <UnexpectedError error={err} />;
return <></>;
}}
>
<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}...`);
setSiteTitle(ins.title);
const srcset = [];
const srcset = []
if (ins.thumbnail.versions["@1x"]) {
srcset.push(`${ins.thumbnail.versions["@1x"]} 1x`);
srcset.push(`${ins.thumbnail.versions["@1x"]} 1x`)
}
if (ins.thumbnail.versions["@2x"]) {
srcset.push(`${ins.thumbnail.versions["@2x"]} 2x`);
srcset.push(`${ins.thumbnail.versions["@2x"]} 2x`)
}
setSiteImg({
@ -66,8 +66,8 @@ const MastodonOAuth2Callback: Component = () => {
onGoingOAuth2Process,
params.code,
);
$settings.setKey("onGoingOAuth2Process", undefined);
navigate("/", { replace: true });
$settings.setKey('onGoingOAuth2Process', undefined)
navigate('/', {replace: true})
return;
}
@ -95,27 +95,18 @@ const MastodonOAuth2Callback: Component = () => {
<div class={cards.layoutCentered}>
<div class={cards.card} aria-busy="true" aria-describedby={progressId}>
<LinearProgress
class={[cards.cardNoPad, cards.cardGutSkip].join(" ")}
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>
}
>
<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(" ")}
class={[cards.cardNoPad, cards.cardGutSkip].join(' ')}
alt={`Banner image for ${siteTitle()}`}
style={{ height: "235px", display: "block" }}
style={{"height": "235px", "display": "block"}}
/>
</Show>
@ -123,8 +114,7 @@ const MastodonOAuth2Callback: Component = () => {
Contracting {siteTitle}...
</Title>
<p>
If this page stays too long, you can close this page and sign in
again.
If this page stays too long, you can close this page and sign in again.
</p>
</div>
</div>

View file

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

View file

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

View file

@ -1,5 +1,5 @@
import { render } from "solid-js/web";
import App from "./App.js";
import "./material/theme.css";
import {render} from 'solid-js/web'
import App from './App.js'
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>;
};
export function useTimeline(timeline: Accessor<Timeline>) {
export function useTimeline(
timeline: Accessor<Timeline>,
) {
let minId: string | undefined;
let maxId: string | undefined;
let otl: Timeline | undefined;

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -10,7 +10,7 @@
}
.button {
composes: buttonText from "./typography.module.css";
composes: buttonText from './typography.module.css';
composes: touchTarget;
border: none;
@ -18,34 +18,28 @@
color: var(--tutu-color-primary);
font-family: inherit;
&:focus,
&:hover,
&:focus-visible {
background-color: var(--tutu-color-surface-dd);
&:focus,&:hover,&:focus-visible {
background-color: var(--tutu-color-surface-dd);
}
&.pressed {
background-color: var(--tutu-color-surface-d);
background-color: var(--tutu-color-surface-d);
}
&.raised {
background-color: var(--tutu-color-primary);
color: var(--tutu-color-on-primary);
background-color: var(--tutu-color-primary);
color: var(--tutu-color-on-primary);
}
&:disabled,
&[aria-disabled]:not([aria-disabled="false"]) {
color: #9e9e9e;
&:disabled, &[aria-disabled]:not([aria-disabled="false"]) {
color: #9e9e9e;
&:focus,
&:hover,
&:focus-visible {
background-color: transparent;
}
&:focus,&:hover,&:focus-visible {
background-color: transparent;
}
}
.toolbar &,
.appbar & {
.toolbar &, .appbar & {
height: 100%;
margin-block: 0;
padding-block: 0;
@ -55,9 +49,7 @@
.appbar & {
color: var(--tutu-color-on-primary);
&:focus,
&:hover,
&:focus-visible {
&:focus,&:hover,&:focus-visible {
background-color: var(--tutu-color-primary-ll);
}
@ -70,3 +62,4 @@
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 { Accessor } from "solid-js";
export function useRootTheme(): Accessor<Theme> {
return () =>
createTheme({
palette: {
primary: {
main: deepPurple[500],
},
secondary: {
main: amber.A200,
},
export function useRootTheme() : Accessor<Theme> {
return () => createTheme({
palette: {
primary: {
main: deepPurple[500]
},
});
secondary: {
main: amber.A200
}
}
})
}

View file

@ -1,6 +1,5 @@
:root,
[lang^="en"],
[lang="en"] {
[lang^="en"], [lang="en"] {
--md-typography-type: "regular";
--title-size: 1.25rem;
--title-weight: 500;
@ -20,12 +19,9 @@
}
}
[lang^="zh"],
[lang="zh"],
[lang^="kr"],
[lang="kr"],
[lang^="ja"],
[lang="ja"] {
[lang^="zh"], [lang="zh"],
[lang^="kr"], [lang="kr"],
[lang^="ja"], [lang="ja"] {
--md-typography-type: "dense";
--title-size: 1.4375rem;
--subheading-size: 1.1875rem;
@ -99,6 +95,7 @@
--tutu-anim-curve-sharp: cubic-bezier(0.4, 0, 0.6, 1);
@media (max-width: 300px) {
/* XS screen, like wearables */
& {
--tutu-transition-shadow: box-shadow 157.5ms var(--tutu-anim-curve-std);
@ -106,6 +103,7 @@
}
@media (max-width: 600px) {
/* Mobile */
& {
--tutu-transition-shadow: box-shadow 225ms var(--tutu-anim-curve-std);
@ -113,6 +111,7 @@
}
@media (max-width: 1200px) {
/* Tablet */
& {
--tutu-transition-shadow: box-shadow 292.5ms var(--tutu-anim-curve-std);
@ -126,17 +125,11 @@
}
* {
font-family:
Roboto,
"Noto Sans",
system-ui,
-apple-system,
BlinkMacSystemFont,
sans-serif;
font-family: Roboto, "Noto Sans", system-ui, -apple-system, BlinkMacSystemFont, sans-serif;
box-sizing: border-box;
margin: 0;
}
body {
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 { mergeClass } from "../utils";
type AnyElement = keyof JSX.IntrinsicElements | ParentComponent<any>;
type AnyElement = keyof JSX.IntrinsicElements | ParentComponent<any>
type PropsOf<E extends AnyElement> =
E extends ParentComponent<infer Props>
@ -12,7 +12,9 @@ type PropsOf<E extends AnyElement> =
? JSX.IntrinsicElements[E]
: JSX.HTMLAttributes<HTMLElement>;
export type TypographyProps<E extends AnyElement> = {
export type TypographyProps<
E extends AnyElement,
> = {
ref?: Ref<E>;
component?: E;
class?: string;
@ -31,9 +33,7 @@ type TypographyKind =
| "caption"
| "buttonText";
export function Typography<T extends AnyElement>(
props: { typography: TypographyKind } & TypographyProps<T>,
) {
export function Typography<T extends AnyElement>(props: {typography: TypographyKind } & TypographyProps<T>) {
const [managed, passthough] = splitProps(props, [
"ref",
"component",
@ -50,38 +50,38 @@ export function Typography<T extends AnyElement>(
{...passthough}
></Dynamic>
);
}
};
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>) {
return <Typography typography={"display3"} {...props}></Typography>;
return <Typography typography={"display3"} {...props}></Typography>
}
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>) {
return <Typography typography={"display1"} {...props}></Typography>;
return <Typography typography={"display1"} {...props}></Typography>
}
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>) {
return <Typography typography={"title"} {...props}></Typography>;
return <Typography typography={"title"} {...props}></Typography>
}
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>) {
return <Typography typography={"body1"} {...props}></Typography>;
return <Typography typography={"body1"} {...props}></Typography>
}
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>) {
return <Typography typography={"caption"} {...props}></Typography>;
return <Typography typography={"caption"} {...props}></Typography>
}
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";
export type HeroSource = {
[key: string | symbol | number]: HTMLElement | undefined;
};
export type HeroSource = {[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() {
return useContext(HeroSourceContext);
return useContext(HeroSourceContext)
}

View file

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

View file

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

View file

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

View file

@ -11,7 +11,7 @@ const MediaAttachmentGrid: Component<{
}> = (props) => {
let rootRef: HTMLElement;
const [viewerIndex, setViewerIndex] = createSignal<number>();
const viewerOpened = () => typeof viewerIndex() !== "undefined";
const viewerOpened = () => typeof viewerIndex() !== "undefined"
const gridTemplateColumns = () => {
const l = props.attachments.length;
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) {
return Math.min(Math.max(input, min), max);
return Math.min(Math.max(input, min), max)
}
const MediaViewer: ParentComponent<MediaViewerProps> = (props) => {
@ -128,13 +128,6 @@ const MediaViewer: ParentComponent<MediaViewerProps> = (props) => {
left: 0;
z-index: 1;
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 {
@ -214,13 +207,7 @@ const MediaViewer: ParentComponent<MediaViewerProps> = (props) => {
move: number,
idx: number,
) => {
const {
ref,
top: otop,
left: oleft,
scale: oscale,
osize: [owidth, oheight],
} = state[idx];
const { ref, top: otop, left: oleft, scale: oscale, osize: [owidth, oheight] } = state[idx];
const [cx, cy] = center;
const iy = clamp(cy - otop, 0, oheight),
ix = clamp(cx - oleft, 0, owidth); // in image coordinate system

View file

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

View file

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

View file

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

View file

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

View file

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