Compare commits
5 commits
cd02dc2053
...
66366e6486
Author | SHA1 | Date | |
---|---|---|---|
|
66366e6486 | ||
|
94088768ba | ||
|
4b17c426ab | ||
|
93b4cd065a | ||
|
f06a7a6da1 |
31 changed files with 380 additions and 264 deletions
BIN
bun.lockb
BIN
bun.lockb
Binary file not shown.
|
@ -40,7 +40,8 @@
|
||||||
"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"
|
||||||
}
|
}
|
||||||
|
|
|
@ -53,11 +53,13 @@ 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 <></>;
|
return <UnexpectedError error={err} />;
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<ThemeProvider theme={theme()}>
|
<ThemeProvider theme={theme()}>
|
||||||
|
|
40
src/UnexpectedError.tsx
Normal file
40
src/UnexpectedError.tsx
Normal file
|
@ -0,0 +1,40 @@
|
||||||
|
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}...`);
|
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,18 +95,27 @@ 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 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
|
<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>
|
||||||
|
|
||||||
|
@ -114,7 +123,8 @@ 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 again.
|
If this page stays too long, you can close this page and sign in
|
||||||
|
again.
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -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(
|
||||||
|
|
|
@ -95,9 +95,13 @@ export const updateAcctInf = action(
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
export const signOut = action($accounts, "signOut", ($store, predicate: (acct: Account) => boolean) => {
|
export const signOut = action(
|
||||||
$store.set($store.get().filter(a => !predicate(a)));
|
$accounts,
|
||||||
});
|
"signOut",
|
||||||
|
($store, predicate: (acct: Account) => boolean) => {
|
||||||
|
$store.set($store.get().filter((a) => !predicate(a)));
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
export type RegisteredApp = {
|
export type RegisteredApp = {
|
||||||
site: string;
|
site: string;
|
||||||
|
|
|
@ -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")!);
|
||||||
|
|
|
@ -12,9 +12,7 @@ type Timeline = {
|
||||||
}): mastodon.Paginator<mastodon.v1.Status[], unknown>;
|
}): mastodon.Paginator<mastodon.v1.Status[], unknown>;
|
||||||
};
|
};
|
||||||
|
|
||||||
export function useTimeline(
|
export function useTimeline(timeline: Accessor<Timeline>) {
|
||||||
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;
|
||||||
|
|
|
@ -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,7 +22,7 @@
|
||||||
@media (max-width: 560px) {
|
@media (max-width: 560px) {
|
||||||
& {
|
& {
|
||||||
left: 0;
|
left: 0;
|
||||||
top: 0;
|
top: var(--safe-area-inset-top, 0);
|
||||||
transform: none;
|
transform: none;
|
||||||
bottom: 0;
|
bottom: 0;
|
||||||
height: 100vh;
|
height: 100vh;
|
||||||
|
|
|
@ -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,7 +20,11 @@ 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;
|
export default BottomSheet;
|
||||||
|
|
|
@ -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>;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -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,7 +28,6 @@
|
||||||
margin-bottom: var(--card-gut, 20px);
|
margin-bottom: var(--card-gut, 20px);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.layoutCentered {
|
.layoutCentered {
|
||||||
|
|
|
@ -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,74 +7,78 @@
|
||||||
--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, &:has(>input[aria-invalid="true"]) {
|
&.error,
|
||||||
&:not(:focus-within) {
|
&:has(> input[aria-invalid="true"]) {
|
||||||
--border-color: var(--tutu-color-error-on-surface);
|
&:not(:focus-within) {
|
||||||
--label-color: var(--tutu-color-error-on-surface);
|
--border-color: var(--tutu-color-error-on-surface);
|
||||||
--helper-text-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 {
|
&: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: bottom .2s ease-in-out, font-size .2s ease-in-out, color .2s ease-in-out;
|
transition:
|
||||||
cursor: text;
|
bottom 0.2s ease-in-out,
|
||||||
font-size: 0.8125rem;
|
font-size 0.2s ease-in-out,
|
||||||
|
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, &.float-label>label {
|
&:focus-within > label,
|
||||||
bottom: calc(100% - 0.8125rem);
|
&.float-label > label {
|
||||||
color: var(--active-label-color);
|
bottom: calc(100% - 0.8125rem);
|
||||||
|
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 .2s ease-in-out;
|
transition: border-color 0.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;
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -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,28 +18,34 @@
|
||||||
color: var(--tutu-color-primary);
|
color: var(--tutu-color-primary);
|
||||||
font-family: inherit;
|
font-family: inherit;
|
||||||
|
|
||||||
&:focus,&:hover,&:focus-visible {
|
&:focus,
|
||||||
background-color: var(--tutu-color-surface-dd);
|
&:hover,
|
||||||
|
&: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, &[aria-disabled]:not([aria-disabled="false"]) {
|
&:disabled,
|
||||||
color: #9e9e9e;
|
&[aria-disabled]:not([aria-disabled="false"]) {
|
||||||
|
color: #9e9e9e;
|
||||||
|
|
||||||
&:focus,&:hover,&:focus-visible {
|
&:focus,
|
||||||
background-color: transparent;
|
&:hover,
|
||||||
}
|
&:focus-visible {
|
||||||
|
background-color: transparent;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.toolbar &, .appbar & {
|
.toolbar &,
|
||||||
|
.appbar & {
|
||||||
height: 100%;
|
height: 100%;
|
||||||
margin-block: 0;
|
margin-block: 0;
|
||||||
padding-block: 0;
|
padding-block: 0;
|
||||||
|
@ -49,7 +55,9 @@
|
||||||
.appbar & {
|
.appbar & {
|
||||||
color: var(--tutu-color-on-primary);
|
color: var(--tutu-color-on-primary);
|
||||||
|
|
||||||
&:focus,&:hover,&:focus-visible {
|
&:focus,
|
||||||
|
&:hover,
|
||||||
|
&:focus-visible {
|
||||||
background-color: var(--tutu-color-primary-ll);
|
background-color: var(--tutu-color-primary-ll);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -62,4 +70,3 @@
|
||||||
color: var(--tutu-color-on-surface);
|
color: var(--tutu-color-on-surface);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -2,15 +2,16 @@ 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 () => createTheme({
|
return () =>
|
||||||
palette: {
|
createTheme({
|
||||||
primary: {
|
palette: {
|
||||||
main: deepPurple[500]
|
primary: {
|
||||||
|
main: deepPurple[500],
|
||||||
|
},
|
||||||
|
secondary: {
|
||||||
|
main: amber.A200,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
secondary: {
|
});
|
||||||
main: amber.A200
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
: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;
|
||||||
|
@ -19,9 +20,12 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
[lang^="zh"], [lang="zh"],
|
[lang^="zh"],
|
||||||
[lang^="kr"], [lang="kr"],
|
[lang="zh"],
|
||||||
[lang^="ja"], [lang="ja"] {
|
[lang^="kr"],
|
||||||
|
[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;
|
||||||
|
@ -95,7 +99,6 @@
|
||||||
--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);
|
||||||
|
@ -103,7 +106,6 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
@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);
|
||||||
|
@ -111,7 +113,6 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
@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);
|
||||||
|
@ -125,7 +126,13 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
* {
|
* {
|
||||||
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;
|
box-sizing: border-box;
|
||||||
margin: 0;
|
margin: 0;
|
||||||
}
|
}
|
||||||
|
|
|
@ -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,9 +12,7 @@ type PropsOf<E extends AnyElement> =
|
||||||
? JSX.IntrinsicElements[E]
|
? JSX.IntrinsicElements[E]
|
||||||
: JSX.HTMLAttributes<HTMLElement>;
|
: JSX.HTMLAttributes<HTMLElement>;
|
||||||
|
|
||||||
export type TypographyProps<
|
export type TypographyProps<E extends AnyElement> = {
|
||||||
E extends AnyElement,
|
|
||||||
> = {
|
|
||||||
ref?: Ref<E>;
|
ref?: Ref<E>;
|
||||||
component?: E;
|
component?: E;
|
||||||
class?: string;
|
class?: string;
|
||||||
|
@ -33,7 +31,9 @@ type TypographyKind =
|
||||||
| "caption"
|
| "caption"
|
||||||
| "buttonText";
|
| "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, [
|
const [managed, passthough] = splitProps(props, [
|
||||||
"ref",
|
"ref",
|
||||||
"component",
|
"component",
|
||||||
|
@ -50,38 +50,38 @@ export function Typography<T extends AnyElement>(props: {typography: TypographyK
|
||||||
{...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>;
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,11 +1,13 @@
|
||||||
import { createContext, useContext, type Accessor } from "solid-js";
|
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() {
|
export function useHeroSource() {
|
||||||
return useContext(HeroSourceContext)
|
return useContext(HeroSourceContext);
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,11 +1,15 @@
|
||||||
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>("settings::", {}, {
|
export const $settings = persistentMap<Settings>(
|
||||||
encode: JSON.stringify,
|
"settings::",
|
||||||
decode: JSON.parse
|
{},
|
||||||
})
|
{
|
||||||
|
encode: JSON.stringify,
|
||||||
|
decode: JSON.parse,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
|
@ -18,16 +18,14 @@ 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={[
|
class={[tootStyle.tootAvatar].join(" ")}
|
||||||
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(
|
||||||
|
@ -48,7 +46,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>
|
||||||
);
|
);
|
||||||
|
|
|
@ -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: "nearest", behavior: "smooth" });
|
items.item(idx).scrollIntoView({ block: "start", behavior: "smooth" });
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -269,7 +269,11 @@ const Home: ParentComponent = (props) => {
|
||||||
<Scaffold
|
<Scaffold
|
||||||
topbar={
|
topbar={
|
||||||
<AppBar position="static">
|
<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()}>
|
<Tabs onFocusChanged={setCurrentFocusOn} offset={panelOffset()}>
|
||||||
<Tab focus={isTabFocus(0)} onClick={[onTabClick, 0]}>
|
<Tab focus={isTabFocus(0)} onClick={[onTabClick, 0]}>
|
||||||
Home
|
Home
|
||||||
|
@ -282,7 +286,14 @@ const Home: ParentComponent = (props) => {
|
||||||
</Tab>
|
</Tab>
|
||||||
</Tabs>
|
</Tabs>
|
||||||
<ProfileMenuButton profile={profile()}>
|
<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>
|
<ListItemText>Prefetch Toots</ListItemText>
|
||||||
<ListItemSecondaryAction>
|
<ListItemSecondaryAction>
|
||||||
<Switch checked={prefetching()}></Switch>
|
<Switch checked={prefetching()}></Switch>
|
||||||
|
|
|
@ -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) {
|
||||||
|
|
|
@ -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,6 +128,13 @@ 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 {
|
||||||
|
@ -207,7 +214,13 @@ const MediaViewer: ParentComponent<MediaViewerProps> = (props) => {
|
||||||
move: number,
|
move: number,
|
||||||
idx: 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 [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
|
||||||
|
|
|
@ -8,7 +8,13 @@ import {
|
||||||
Menu,
|
Menu,
|
||||||
MenuItem,
|
MenuItem,
|
||||||
} from "@suid/material";
|
} from "@suid/material";
|
||||||
import { Show, createSignal, createUniqueId, type ParentComponent } from "solid-js";
|
import {
|
||||||
|
ErrorBoundary,
|
||||||
|
Show,
|
||||||
|
createSignal,
|
||||||
|
createUniqueId,
|
||||||
|
type ParentComponent,
|
||||||
|
} from "solid-js";
|
||||||
import {
|
import {
|
||||||
Settings as SettingsIcon,
|
Settings as SettingsIcon,
|
||||||
Bookmark as BookmarkIcon,
|
Bookmark as BookmarkIcon,
|
||||||
|
@ -42,79 +48,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>
|
<Show when={props.children}>
|
||||||
<MenuItem component={A} href="/settings" onClick={onClose}>
|
{props.children}
|
||||||
<ListItemIcon>
|
<Divider />
|
||||||
<SettingsIcon />
|
</Show>
|
||||||
</ListItemIcon>
|
<MenuItem component={A} href="/settings" onClick={onClose}>
|
||||||
<ListItemText>Settings</ListItemText>
|
<ListItemIcon>
|
||||||
</MenuItem>
|
<SettingsIcon />
|
||||||
</Menu>
|
</ListItemIcon>
|
||||||
|
<ListItemText>Settings</ListItemText>
|
||||||
|
</MenuItem>
|
||||||
|
</Menu>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
|
@ -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}
|
||||||
|
|
|
@ -1,8 +1,7 @@
|
||||||
import type { Component } from "solid-js";
|
import type { Component } from "solid-js";
|
||||||
|
|
||||||
|
|
||||||
const TootBottomSheet: Component = (props) => {
|
const TootBottomSheet: Component = (props) => {
|
||||||
return <></>
|
return <></>;
|
||||||
}
|
};
|
||||||
|
|
||||||
export default TootBottomSheet
|
export default TootBottomSheet;
|
||||||
|
|
|
@ -38,7 +38,9 @@ const TootThread: Component<TootThreadProps> = (props) => {
|
||||||
|
|
||||||
css`
|
css`
|
||||||
article {
|
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;
|
user-select: none;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
}
|
}
|
||||||
|
@ -64,7 +66,10 @@ const TootThread: Component<TootThreadProps> = (props) => {
|
||||||
`;
|
`;
|
||||||
|
|
||||||
return (
|
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()}>
|
<Show when={inReplyTo()}>
|
||||||
<CompactToot
|
<CompactToot
|
||||||
status={inReplyTo()!}
|
status={inReplyTo()!}
|
||||||
|
|
|
@ -6,13 +6,14 @@
|
||||||
|
|
||||||
&.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: margin-block 125ms var(--tutu-anim-curve-std),
|
transition:
|
||||||
|
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;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -46,11 +47,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;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -80,7 +81,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;
|
||||||
|
@ -150,14 +151,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;
|
||||||
|
@ -168,7 +169,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;
|
||||||
|
@ -176,7 +177,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;
|
||||||
|
|
||||||
|
|
|
@ -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(" ");
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue