Compare commits
No commits in common. "66366e648626c841839b3af2c92b0e6ecf0e66e2" and "cd02dc205351d9a0eb957228788f35b95f8b765c" have entirely different histories.
66366e6486
...
cd02dc2053
31 changed files with 265 additions and 381 deletions
BIN
bun.lockb
BIN
bun.lockb
Binary file not shown.
|
@ -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"
|
||||
}
|
||||
|
|
|
@ -9,4 +9,4 @@
|
|||
|
||||
.custom-emoji {
|
||||
width: 1.25em;
|
||||
}
|
||||
}
|
|
@ -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()}>
|
||||
|
|
|
@ -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;
|
|
@ -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>
|
||||
|
|
|
@ -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(
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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")!)
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
|
|
|
@ -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>;
|
||||
};
|
||||
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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>
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -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
|
||||
})
|
||||
|
|
|
@ -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>
|
||||
);
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
|
|
@ -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}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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()!}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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(' ')
|
||||
}
|
||||
|
|
Loading…
Add table
Reference in a new issue