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",
|
"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"
|
||||||
}
|
}
|
||||||
|
|
|
@ -9,4 +9,4 @@
|
||||||
|
|
||||||
.custom-emoji {
|
.custom-emoji {
|
||||||
width: 1.25em;
|
width: 1.25em;
|
||||||
}
|
}
|
|
@ -53,13 +53,11 @@ const App: Component = () => {
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
const UnexpectedError = lazy(() => import("./UnexpectedError.js"))
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ErrorBoundary
|
<ErrorBoundary
|
||||||
fallback={(err, reset) => {
|
fallback={(err, reset) => {
|
||||||
console.error(err);
|
console.error(err);
|
||||||
return <UnexpectedError error={err} />;
|
return <></>;
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<ThemeProvider theme={theme()}>
|
<ThemeProvider theme={theme()}>
|
||||||
|
|
|
@ -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}...`);
|
setDocumentTitle(`Back from ${ins.title}...`);
|
||||||
setSiteTitle(ins.title);
|
setSiteTitle(ins.title);
|
||||||
|
|
||||||
const srcset = [];
|
const srcset = []
|
||||||
if (ins.thumbnail.versions["@1x"]) {
|
if (ins.thumbnail.versions["@1x"]) {
|
||||||
srcset.push(`${ins.thumbnail.versions["@1x"]} 1x`);
|
srcset.push(`${ins.thumbnail.versions["@1x"]} 1x`)
|
||||||
}
|
}
|
||||||
if (ins.thumbnail.versions["@2x"]) {
|
if (ins.thumbnail.versions["@2x"]) {
|
||||||
srcset.push(`${ins.thumbnail.versions["@2x"]} 2x`);
|
srcset.push(`${ins.thumbnail.versions["@2x"]} 2x`)
|
||||||
}
|
}
|
||||||
|
|
||||||
setSiteImg({
|
setSiteImg({
|
||||||
|
@ -66,8 +66,8 @@ const MastodonOAuth2Callback: Component = () => {
|
||||||
onGoingOAuth2Process,
|
onGoingOAuth2Process,
|
||||||
params.code,
|
params.code,
|
||||||
);
|
);
|
||||||
$settings.setKey("onGoingOAuth2Process", undefined);
|
$settings.setKey('onGoingOAuth2Process', undefined)
|
||||||
navigate("/", { replace: true });
|
navigate('/', {replace: true})
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -95,27 +95,18 @@ const MastodonOAuth2Callback: Component = () => {
|
||||||
<div class={cards.layoutCentered}>
|
<div class={cards.layoutCentered}>
|
||||||
<div class={cards.card} aria-busy="true" aria-describedby={progressId}>
|
<div class={cards.card} aria-busy="true" aria-describedby={progressId}>
|
||||||
<LinearProgress
|
<LinearProgress
|
||||||
class={[cards.cardNoPad, cards.cardGutSkip].join(" ")}
|
class={[cards.cardNoPad, cards.cardGutSkip].join(' ')}
|
||||||
id={progressId}
|
id={progressId}
|
||||||
aria-labelledby={titleId}
|
aria-labelledby={titleId}
|
||||||
/>
|
/>
|
||||||
<Show
|
<Show when={siteImg()} fallback={<i aria-busy="true" aria-label="Preparing image..." style={{"height": "235px", display: "block"}}></i>}>
|
||||||
when={siteImg()}
|
|
||||||
fallback={
|
|
||||||
<i
|
|
||||||
aria-busy="true"
|
|
||||||
aria-label="Preparing image..."
|
|
||||||
style={{ height: "235px", display: "block" }}
|
|
||||||
></i>
|
|
||||||
}
|
|
||||||
>
|
|
||||||
<Img
|
<Img
|
||||||
src={siteImg()?.src}
|
src={siteImg()?.src}
|
||||||
srcset={siteImg()?.srcset}
|
srcset={siteImg()?.srcset}
|
||||||
blurhash={siteImg()?.blurhash}
|
blurhash={siteImg()?.blurhash}
|
||||||
class={[cards.cardNoPad, cards.cardGutSkip].join(" ")}
|
class={[cards.cardNoPad, cards.cardGutSkip].join(' ')}
|
||||||
alt={`Banner image for ${siteTitle()}`}
|
alt={`Banner image for ${siteTitle()}`}
|
||||||
style={{ height: "235px", display: "block" }}
|
style={{"height": "235px", "display": "block"}}
|
||||||
/>
|
/>
|
||||||
</Show>
|
</Show>
|
||||||
|
|
||||||
|
@ -123,8 +114,7 @@ const MastodonOAuth2Callback: Component = () => {
|
||||||
Contracting {siteTitle}...
|
Contracting {siteTitle}...
|
||||||
</Title>
|
</Title>
|
||||||
<p>
|
<p>
|
||||||
If this page stays too long, you can close this page and sign in
|
If this page stays too long, you can close this page and sign in again.
|
||||||
again.
|
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -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,13 +95,9 @@ export const updateAcctInf = action(
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
export const signOut = action(
|
export const signOut = action($accounts, "signOut", ($store, predicate: (acct: Account) => boolean) => {
|
||||||
$accounts,
|
$store.set($store.get().filter(a => !predicate(a)));
|
||||||
"signOut",
|
});
|
||||||
($store, predicate: (acct: Account) => boolean) => {
|
|
||||||
$store.set($store.get().filter((a) => !predicate(a)));
|
|
||||||
},
|
|
||||||
);
|
|
||||||
|
|
||||||
export type RegisteredApp = {
|
export type RegisteredApp = {
|
||||||
site: string;
|
site: string;
|
||||||
|
|
|
@ -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,7 +12,9 @@ type Timeline = {
|
||||||
}): mastodon.Paginator<mastodon.v1.Status[], unknown>;
|
}): mastodon.Paginator<mastodon.v1.Status[], unknown>;
|
||||||
};
|
};
|
||||||
|
|
||||||
export function useTimeline(timeline: Accessor<Timeline>) {
|
export function useTimeline(
|
||||||
|
timeline: Accessor<Timeline>,
|
||||||
|
) {
|
||||||
let minId: string | undefined;
|
let minId: string | undefined;
|
||||||
let maxId: string | undefined;
|
let maxId: string | undefined;
|
||||||
let otl: Timeline | undefined;
|
let otl: Timeline | undefined;
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
.bottomSheet {
|
.bottomSheet {
|
||||||
composes: surface from "material.module.css";
|
composes: surface from 'material.module.css';
|
||||||
border: none;
|
border: none;
|
||||||
position: absolute;
|
position: absolute;
|
||||||
left: 50%;
|
left: 50%;
|
||||||
|
@ -22,11 +22,11 @@
|
||||||
@media (max-width: 560px) {
|
@media (max-width: 560px) {
|
||||||
& {
|
& {
|
||||||
left: 0;
|
left: 0;
|
||||||
top: var(--safe-area-inset-top, 0);
|
top: 0;
|
||||||
transform: none;
|
transform: none;
|
||||||
bottom: 0;
|
bottom: 0;
|
||||||
height: 100vh;
|
height: 100vh;
|
||||||
height: 100dvh;
|
height: 100dvh;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -1,5 +1,5 @@
|
||||||
import { createEffect, type ParentComponent } from "solid-js";
|
import { createEffect, type ParentComponent } from "solid-js";
|
||||||
import styles from "./BottomSheet.module.css";
|
import styles from './BottomSheet.module.css'
|
||||||
|
|
||||||
export type BottomSheetProps = {
|
export type BottomSheetProps = {
|
||||||
open?: boolean;
|
open?: boolean;
|
||||||
|
@ -20,11 +20,7 @@ const BottomSheet: ParentComponent<BottomSheetProps> = (props) => {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
return (
|
return <dialog class={styles.bottomSheet} ref={element!}>{props.children}</dialog>;
|
||||||
<dialog class={styles.bottomSheet} ref={element!}>
|
|
||||||
{props.children}
|
|
||||||
</dialog>
|
|
||||||
);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export default BottomSheet;
|
export default BottomSheet;
|
||||||
|
|
|
@ -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,6 +28,7 @@
|
||||||
margin-bottom: var(--card-gut, 20px);
|
margin-bottom: var(--card-gut, 20px);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.layoutCentered {
|
.layoutCentered {
|
||||||
|
@ -51,4 +52,4 @@
|
||||||
overflow: auto;
|
overflow: auto;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -1,5 +1,5 @@
|
||||||
.textfield {
|
.textfield {
|
||||||
composes: touchTarget from "material.module.css";
|
composes: touchTarget from 'material.module.css';
|
||||||
|
|
||||||
--border-color: var(--tutu-color-inactive-on-surface);
|
--border-color: var(--tutu-color-inactive-on-surface);
|
||||||
--active-border-color: var(--tutu-color-primary);
|
--active-border-color: var(--tutu-color-primary);
|
||||||
|
@ -7,78 +7,74 @@
|
||||||
--active-label-color: var(--tutu-color-primary);
|
--active-label-color: var(--tutu-color-primary);
|
||||||
--helper-text-color: var(--tutu-color-inactive-on-surface);
|
--helper-text-color: var(--tutu-color-inactive-on-surface);
|
||||||
|
|
||||||
& > * {
|
&>* {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
&.error,
|
&.error, &:has(>input[aria-invalid="true"]) {
|
||||||
&:has(> input[aria-invalid="true"]) {
|
&:not(:focus-within) {
|
||||||
&:not(:focus-within) {
|
--border-color: var(--tutu-color-error-on-surface);
|
||||||
--border-color: var(--tutu-color-error-on-surface);
|
--label-color: var(--tutu-color-error-on-surface);
|
||||||
--label-color: var(--tutu-color-error-on-surface);
|
--helper-text-color: var(--tutu-color-error-on-surface);
|
||||||
--helper-text-color: var(--tutu-color-error-on-surface);
|
}
|
||||||
}
|
|
||||||
|
|
||||||
&:focus-within {
|
&:focus-within {
|
||||||
--helper-text-color: var(--tutu-color-error-on-surface);
|
--helper-text-color: var(--tutu-color-error-on-surface);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
position: relative;
|
position: relative;
|
||||||
|
|
||||||
& > label {
|
&>label {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
left: 0;
|
left: 0;
|
||||||
bottom: calc(10px + var(--bottom-height, 0px));
|
bottom: calc(10px + var(--bottom-height, 0px));
|
||||||
color: var(--label-color);
|
color: var(--label-color);
|
||||||
transition:
|
transition: bottom .2s ease-in-out, font-size .2s ease-in-out, color .2s ease-in-out;
|
||||||
bottom 0.2s ease-in-out,
|
cursor: text;
|
||||||
font-size 0.2s ease-in-out,
|
font-size: 0.8125rem;
|
||||||
color 0.2s ease-in-out;
|
|
||||||
cursor: text;
|
|
||||||
font-size: 0.8125rem;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
& > label:has(+ input:not(:placeholder-shown)) {
|
&>label:has(+ input:not(:placeholder-shown)) {
|
||||||
bottom: calc(100% - 0.8125rem);
|
bottom: calc(100% - 0.8125rem);
|
||||||
}
|
}
|
||||||
|
|
||||||
&:focus-within > label,
|
&:focus-within>label, &.float-label>label {
|
||||||
&.float-label > label {
|
bottom: calc(100% - 0.8125rem);
|
||||||
bottom: calc(100% - 0.8125rem);
|
color: var(--active-label-color);
|
||||||
color: var(--active-label-color);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
& > input[type="text"],
|
&>input[type='text'],
|
||||||
& > input[type="password"] {
|
&>input[type='password'] {
|
||||||
border: none;
|
border: none;
|
||||||
outline: none;
|
outline: none;
|
||||||
border-bottom: 1px solid var(--border-color);
|
border-bottom: 1px solid var(--border-color);
|
||||||
background-color: transparent;
|
background-color: transparent;
|
||||||
padding-top: 16px;
|
padding-top: 16px;
|
||||||
padding-bottom: 8px;
|
padding-bottom: 8px;
|
||||||
margin-bottom: 1px;
|
margin-bottom: 1px;
|
||||||
transition: border-color 0.2s ease-in-out;
|
transition: border-color .2s ease-in-out;
|
||||||
|
|
||||||
&:focus {
|
&:focus {
|
||||||
border-bottom: 2px solid var(--active-border-color);
|
border-bottom: 2px solid var(--active-border-color);
|
||||||
margin-bottom: 0;
|
margin-bottom: 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
&.withHelperText {
|
&.withHelperText {
|
||||||
--bottom-height: 0.8125rem;
|
--bottom-height: 0.8125rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
& .helperText {
|
& .helperText {
|
||||||
color: var(--helper-text-color);
|
color: var(--helper-text-color);
|
||||||
font-size: 0.8125rem;
|
font-size: 0.8125rem;
|
||||||
line-height: 100%;
|
line-height: 100%;
|
||||||
-webkit-line-clamp: 1;
|
-webkit-line-clamp: 1;
|
||||||
line-clamp: 1;
|
line-clamp: 1;
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
min-height: 0.8125rem;
|
min-height: 0.8125rem;
|
||||||
cursor: auto;
|
cursor: auto;
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -10,7 +10,7 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
.button {
|
.button {
|
||||||
composes: buttonText from "./typography.module.css";
|
composes: buttonText from './typography.module.css';
|
||||||
composes: touchTarget;
|
composes: touchTarget;
|
||||||
|
|
||||||
border: none;
|
border: none;
|
||||||
|
@ -18,34 +18,28 @@
|
||||||
color: var(--tutu-color-primary);
|
color: var(--tutu-color-primary);
|
||||||
font-family: inherit;
|
font-family: inherit;
|
||||||
|
|
||||||
&:focus,
|
&:focus,&:hover,&:focus-visible {
|
||||||
&:hover,
|
background-color: var(--tutu-color-surface-dd);
|
||||||
&:focus-visible {
|
|
||||||
background-color: var(--tutu-color-surface-dd);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
&.pressed {
|
&.pressed {
|
||||||
background-color: var(--tutu-color-surface-d);
|
background-color: var(--tutu-color-surface-d);
|
||||||
}
|
}
|
||||||
|
|
||||||
&.raised {
|
&.raised {
|
||||||
background-color: var(--tutu-color-primary);
|
background-color: var(--tutu-color-primary);
|
||||||
color: var(--tutu-color-on-primary);
|
color: var(--tutu-color-on-primary);
|
||||||
}
|
}
|
||||||
|
|
||||||
&:disabled,
|
&:disabled, &[aria-disabled]:not([aria-disabled="false"]) {
|
||||||
&[aria-disabled]:not([aria-disabled="false"]) {
|
color: #9e9e9e;
|
||||||
color: #9e9e9e;
|
|
||||||
|
|
||||||
&:focus,
|
&:focus,&:hover,&:focus-visible {
|
||||||
&:hover,
|
background-color: transparent;
|
||||||
&:focus-visible {
|
}
|
||||||
background-color: transparent;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.toolbar &,
|
.toolbar &, .appbar & {
|
||||||
.appbar & {
|
|
||||||
height: 100%;
|
height: 100%;
|
||||||
margin-block: 0;
|
margin-block: 0;
|
||||||
padding-block: 0;
|
padding-block: 0;
|
||||||
|
@ -55,9 +49,7 @@
|
||||||
.appbar & {
|
.appbar & {
|
||||||
color: var(--tutu-color-on-primary);
|
color: var(--tutu-color-on-primary);
|
||||||
|
|
||||||
&:focus,
|
&:focus,&:hover,&:focus-visible {
|
||||||
&:hover,
|
|
||||||
&:focus-visible {
|
|
||||||
background-color: var(--tutu-color-primary-ll);
|
background-color: var(--tutu-color-primary-ll);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -70,3 +62,4 @@
|
||||||
color: var(--tutu-color-on-surface);
|
color: var(--tutu-color-on-surface);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -2,16 +2,15 @@ import { Theme, createTheme } from "@suid/material/styles";
|
||||||
import { deepPurple, amber } from "@suid/material/colors";
|
import { deepPurple, amber } from "@suid/material/colors";
|
||||||
import { Accessor } from "solid-js";
|
import { Accessor } from "solid-js";
|
||||||
|
|
||||||
export function useRootTheme(): Accessor<Theme> {
|
export function useRootTheme() : Accessor<Theme> {
|
||||||
return () =>
|
return () => createTheme({
|
||||||
createTheme({
|
palette: {
|
||||||
palette: {
|
primary: {
|
||||||
primary: {
|
main: deepPurple[500]
|
||||||
main: deepPurple[500],
|
|
||||||
},
|
|
||||||
secondary: {
|
|
||||||
main: amber.A200,
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
});
|
secondary: {
|
||||||
|
main: amber.A200
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,5 @@
|
||||||
:root,
|
:root,
|
||||||
[lang^="en"],
|
[lang^="en"], [lang="en"] {
|
||||||
[lang="en"] {
|
|
||||||
--md-typography-type: "regular";
|
--md-typography-type: "regular";
|
||||||
--title-size: 1.25rem;
|
--title-size: 1.25rem;
|
||||||
--title-weight: 500;
|
--title-weight: 500;
|
||||||
|
@ -20,12 +19,9 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
[lang^="zh"],
|
[lang^="zh"], [lang="zh"],
|
||||||
[lang="zh"],
|
[lang^="kr"], [lang="kr"],
|
||||||
[lang^="kr"],
|
[lang^="ja"], [lang="ja"] {
|
||||||
[lang="kr"],
|
|
||||||
[lang^="ja"],
|
|
||||||
[lang="ja"] {
|
|
||||||
--md-typography-type: "dense";
|
--md-typography-type: "dense";
|
||||||
--title-size: 1.4375rem;
|
--title-size: 1.4375rem;
|
||||||
--subheading-size: 1.1875rem;
|
--subheading-size: 1.1875rem;
|
||||||
|
@ -99,6 +95,7 @@
|
||||||
--tutu-anim-curve-sharp: cubic-bezier(0.4, 0, 0.6, 1);
|
--tutu-anim-curve-sharp: cubic-bezier(0.4, 0, 0.6, 1);
|
||||||
|
|
||||||
@media (max-width: 300px) {
|
@media (max-width: 300px) {
|
||||||
|
|
||||||
/* XS screen, like wearables */
|
/* XS screen, like wearables */
|
||||||
& {
|
& {
|
||||||
--tutu-transition-shadow: box-shadow 157.5ms var(--tutu-anim-curve-std);
|
--tutu-transition-shadow: box-shadow 157.5ms var(--tutu-anim-curve-std);
|
||||||
|
@ -106,6 +103,7 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
@media (max-width: 600px) {
|
@media (max-width: 600px) {
|
||||||
|
|
||||||
/* Mobile */
|
/* Mobile */
|
||||||
& {
|
& {
|
||||||
--tutu-transition-shadow: box-shadow 225ms var(--tutu-anim-curve-std);
|
--tutu-transition-shadow: box-shadow 225ms var(--tutu-anim-curve-std);
|
||||||
|
@ -113,6 +111,7 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
@media (max-width: 1200px) {
|
@media (max-width: 1200px) {
|
||||||
|
|
||||||
/* Tablet */
|
/* Tablet */
|
||||||
& {
|
& {
|
||||||
--tutu-transition-shadow: box-shadow 292.5ms var(--tutu-anim-curve-std);
|
--tutu-transition-shadow: box-shadow 292.5ms var(--tutu-anim-curve-std);
|
||||||
|
@ -126,17 +125,11 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
* {
|
* {
|
||||||
font-family:
|
font-family: Roboto, "Noto Sans", system-ui, -apple-system, BlinkMacSystemFont, sans-serif;
|
||||||
Roboto,
|
|
||||||
"Noto Sans",
|
|
||||||
system-ui,
|
|
||||||
-apple-system,
|
|
||||||
BlinkMacSystemFont,
|
|
||||||
sans-serif;
|
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
margin: 0;
|
margin: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
body {
|
body {
|
||||||
font-size: var(--body-size, 1rem);
|
font-size: var(--body-size, 1rem);
|
||||||
}
|
}
|
|
@ -3,7 +3,7 @@ import { Dynamic } from "solid-js/web";
|
||||||
import typography from "./typography.module.css";
|
import typography from "./typography.module.css";
|
||||||
import { mergeClass } from "../utils";
|
import { mergeClass } from "../utils";
|
||||||
|
|
||||||
type AnyElement = keyof JSX.IntrinsicElements | ParentComponent<any>;
|
type AnyElement = keyof JSX.IntrinsicElements | ParentComponent<any>
|
||||||
|
|
||||||
type PropsOf<E extends AnyElement> =
|
type PropsOf<E extends AnyElement> =
|
||||||
E extends ParentComponent<infer Props>
|
E extends ParentComponent<infer Props>
|
||||||
|
@ -12,7 +12,9 @@ type PropsOf<E extends AnyElement> =
|
||||||
? JSX.IntrinsicElements[E]
|
? JSX.IntrinsicElements[E]
|
||||||
: JSX.HTMLAttributes<HTMLElement>;
|
: JSX.HTMLAttributes<HTMLElement>;
|
||||||
|
|
||||||
export type TypographyProps<E extends AnyElement> = {
|
export type TypographyProps<
|
||||||
|
E extends AnyElement,
|
||||||
|
> = {
|
||||||
ref?: Ref<E>;
|
ref?: Ref<E>;
|
||||||
component?: E;
|
component?: E;
|
||||||
class?: string;
|
class?: string;
|
||||||
|
@ -31,9 +33,7 @@ type TypographyKind =
|
||||||
| "caption"
|
| "caption"
|
||||||
| "buttonText";
|
| "buttonText";
|
||||||
|
|
||||||
export function Typography<T extends AnyElement>(
|
export function Typography<T extends AnyElement>(props: {typography: TypographyKind } & TypographyProps<T>) {
|
||||||
props: { typography: TypographyKind } & TypographyProps<T>,
|
|
||||||
) {
|
|
||||||
const [managed, passthough] = splitProps(props, [
|
const [managed, passthough] = splitProps(props, [
|
||||||
"ref",
|
"ref",
|
||||||
"component",
|
"component",
|
||||||
|
@ -50,38 +50,38 @@ export function Typography<T extends AnyElement>(
|
||||||
{...passthough}
|
{...passthough}
|
||||||
></Dynamic>
|
></Dynamic>
|
||||||
);
|
);
|
||||||
}
|
};
|
||||||
|
|
||||||
export function Display4<E extends AnyElement>(props: TypographyProps<E>) {
|
export function Display4<E extends AnyElement>(props: TypographyProps<E>) {
|
||||||
return <Typography typography={"display4"} {...props}></Typography>;
|
return <Typography typography={"display4"} {...props}></Typography>
|
||||||
}
|
}
|
||||||
export function Display3<E extends AnyElement>(props: TypographyProps<E>) {
|
export function Display3<E extends AnyElement>(props: TypographyProps<E>) {
|
||||||
return <Typography typography={"display3"} {...props}></Typography>;
|
return <Typography typography={"display3"} {...props}></Typography>
|
||||||
}
|
}
|
||||||
export function Display2<E extends AnyElement>(props: TypographyProps<E>) {
|
export function Display2<E extends AnyElement>(props: TypographyProps<E>) {
|
||||||
return <Typography typography={"display2"} {...props}></Typography>;
|
return <Typography typography={"display2"} {...props}></Typography>
|
||||||
}
|
}
|
||||||
export function Display1<E extends AnyElement>(props: TypographyProps<E>) {
|
export function Display1<E extends AnyElement>(props: TypographyProps<E>) {
|
||||||
return <Typography typography={"display1"} {...props}></Typography>;
|
return <Typography typography={"display1"} {...props}></Typography>
|
||||||
}
|
}
|
||||||
export function Headline<E extends AnyElement>(props: TypographyProps<E>) {
|
export function Headline<E extends AnyElement>(props: TypographyProps<E>) {
|
||||||
return <Typography typography={"headline"} {...props}></Typography>;
|
return <Typography typography={"headline"} {...props}></Typography>
|
||||||
}
|
}
|
||||||
export function Title<E extends AnyElement>(props: TypographyProps<E>) {
|
export function Title<E extends AnyElement>(props: TypographyProps<E>) {
|
||||||
return <Typography typography={"title"} {...props}></Typography>;
|
return <Typography typography={"title"} {...props}></Typography>
|
||||||
}
|
}
|
||||||
export function Subheading<E extends AnyElement>(props: TypographyProps<E>) {
|
export function Subheading<E extends AnyElement>(props: TypographyProps<E>) {
|
||||||
return <Typography typography={"subheading"} {...props}></Typography>;
|
return <Typography typography={"subheading"} {...props}></Typography>
|
||||||
}
|
}
|
||||||
export function Body1<E extends AnyElement>(props: TypographyProps<E>) {
|
export function Body1<E extends AnyElement>(props: TypographyProps<E>) {
|
||||||
return <Typography typography={"body1"} {...props}></Typography>;
|
return <Typography typography={"body1"} {...props}></Typography>
|
||||||
}
|
}
|
||||||
export function Body2<E extends AnyElement>(props: TypographyProps<E>) {
|
export function Body2<E extends AnyElement>(props: TypographyProps<E>) {
|
||||||
return <Typography typography={"body2"} {...props}></Typography>;
|
return <Typography typography={"body2"} {...props}></Typography>
|
||||||
}
|
}
|
||||||
export function Caption<E extends AnyElement>(props: TypographyProps<E>) {
|
export function Caption<E extends AnyElement>(props: TypographyProps<E>) {
|
||||||
return <Typography typography={"caption"} {...props}></Typography>;
|
return <Typography typography={"caption"} {...props}></Typography>
|
||||||
}
|
}
|
||||||
export function ButtonText<E extends AnyElement>(props: TypographyProps<E>) {
|
export function ButtonText<E extends AnyElement>(props: TypographyProps<E>) {
|
||||||
return <Typography typography={"buttonText"} {...props}></Typography>;
|
return <Typography typography={"buttonText"} {...props}></Typography>
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,13 +1,11 @@
|
||||||
import { createContext, useContext, type Accessor } from "solid-js";
|
import { createContext, useContext, type Accessor } from "solid-js";
|
||||||
|
|
||||||
export type HeroSource = {
|
export type HeroSource = {[key: string | symbol | number]: HTMLElement | undefined}
|
||||||
[key: string | symbol | number]: HTMLElement | undefined;
|
|
||||||
};
|
|
||||||
|
|
||||||
const HeroSourceContext = createContext<Accessor<HeroSource>>(() => ({}));
|
const HeroSourceContext = createContext<Accessor<HeroSource>>(() => ({}))
|
||||||
|
|
||||||
export const HeroSourceProvider = HeroSourceContext.Provider;
|
export const HeroSourceProvider = HeroSourceContext.Provider
|
||||||
|
|
||||||
export function useHeroSource() {
|
export function useHeroSource() {
|
||||||
return useContext(HeroSourceContext);
|
return useContext(HeroSourceContext)
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,15 +1,11 @@
|
||||||
import { persistentMap } from "@nanostores/persistent";
|
import { persistentMap } from "@nanostores/persistent";
|
||||||
|
|
||||||
type Settings = {
|
type Settings = {
|
||||||
onGoingOAuth2Process?: string;
|
onGoingOAuth2Process?: string
|
||||||
prefetchTootsDisabled?: boolean;
|
prefetchTootsDisabled?: boolean
|
||||||
};
|
}
|
||||||
|
|
||||||
export const $settings = persistentMap<Settings>(
|
export const $settings = persistentMap<Settings>("settings::", {}, {
|
||||||
"settings::",
|
encode: JSON.stringify,
|
||||||
{},
|
decode: JSON.parse
|
||||||
{
|
})
|
||||||
encode: JSON.stringify,
|
|
||||||
decode: JSON.parse,
|
|
||||||
},
|
|
||||||
);
|
|
||||||
|
|
|
@ -18,14 +18,16 @@ const CompactToot: Component<CompactTootProps> = (props) => {
|
||||||
const toot = () => props.status;
|
const toot = () => props.status;
|
||||||
return (
|
return (
|
||||||
<section
|
<section
|
||||||
class={[tootStyle.compact, props.class || ""].join(" ")}
|
class={[tootStyle.compact, props.class || ""].join(" ")}
|
||||||
lang={toot().language || undefined}
|
lang={toot().language || undefined}
|
||||||
>
|
>
|
||||||
<Img
|
<Img
|
||||||
src={toot().account.avatar}
|
src={toot().account.avatar}
|
||||||
class={[tootStyle.tootAvatar].join(" ")}
|
class={[
|
||||||
|
tootStyle.tootAvatar,
|
||||||
|
].join(" ")}
|
||||||
/>
|
/>
|
||||||
<div class={[tootStyle.compactAuthorGroup].join(" ")}>
|
<div class={[tootStyle.compactAuthorGroup].join(' ')}>
|
||||||
<Body2
|
<Body2
|
||||||
ref={(e: { innerHTML: string }) => {
|
ref={(e: { innerHTML: string }) => {
|
||||||
appliedCustomEmoji(
|
appliedCustomEmoji(
|
||||||
|
@ -46,7 +48,7 @@ const CompactToot: Component<CompactTootProps> = (props) => {
|
||||||
ref={(e: { innerHTML: string }) => {
|
ref={(e: { innerHTML: string }) => {
|
||||||
appliedCustomEmoji(e, toot().content, toot().emojis);
|
appliedCustomEmoji(e, toot().content, toot().emojis);
|
||||||
}}
|
}}
|
||||||
class={[tootStyle.compactTootContent].join(" ")}
|
class={[tootStyle.compactTootContent].join(' ')}
|
||||||
></div>
|
></div>
|
||||||
</section>
|
</section>
|
||||||
);
|
);
|
||||||
|
|
|
@ -155,20 +155,20 @@ const Home: ParentComponent = (props) => {
|
||||||
useDocumentTitle("Timelines");
|
useDocumentTitle("Timelines");
|
||||||
const now = createTimeSource();
|
const now = createTimeSource();
|
||||||
|
|
||||||
const settings$ = useStore($settings);
|
const settings$ = useStore($settings)
|
||||||
const sessions = useSessions();
|
const sessions = useSessions();
|
||||||
const client = () => sessions()[0].client;
|
const client = () => sessions()[0].client;
|
||||||
const [profile] = useAcctProfile(client);
|
const [profile] = useAcctProfile(client);
|
||||||
|
|
||||||
const [panelOffset, setPanelOffset] = createSignal(0);
|
const [panelOffset, setPanelOffset] = createSignal(0);
|
||||||
const prefetching = () => !settings$().prefetchTootsDisabled;
|
const prefetching = () => !settings$().prefetchTootsDisabled
|
||||||
const [currentFocusOn, setCurrentFocusOn] = createSignal<HTMLElement[]>([]);
|
const [currentFocusOn, setCurrentFocusOn] = createSignal<HTMLElement[]>([]);
|
||||||
const [focusRange, setFocusRange] = createSignal([0, 0] as readonly [
|
const [focusRange, setFocusRange] = createSignal([0, 0] as readonly [
|
||||||
number,
|
number,
|
||||||
number,
|
number,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
const child = children(() => props.children);
|
const child = children(() => props.children)
|
||||||
|
|
||||||
let scrollEventLockReleased = true;
|
let scrollEventLockReleased = true;
|
||||||
|
|
||||||
|
@ -229,7 +229,7 @@ const Home: ParentComponent = (props) => {
|
||||||
const onTabClick = (idx: number) => {
|
const onTabClick = (idx: number) => {
|
||||||
const items = panelList.querySelectorAll(".tab-panel");
|
const items = panelList.querySelectorAll(".tab-panel");
|
||||||
if (items.length > idx) {
|
if (items.length > idx) {
|
||||||
items.item(idx).scrollIntoView({ block: "start", behavior: "smooth" });
|
items.item(idx).scrollIntoView({ block: "nearest", behavior: "smooth" });
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -269,11 +269,7 @@ const Home: ParentComponent = (props) => {
|
||||||
<Scaffold
|
<Scaffold
|
||||||
topbar={
|
topbar={
|
||||||
<AppBar position="static">
|
<AppBar position="static">
|
||||||
<Toolbar
|
<Toolbar variant="dense" class="responsive" sx={{ paddingTop: "var(--safe-area-inset-top, 0px)" }}>
|
||||||
variant="dense"
|
|
||||||
class="responsive"
|
|
||||||
sx={{ paddingTop: "var(--safe-area-inset-top, 0px)" }}
|
|
||||||
>
|
|
||||||
<Tabs onFocusChanged={setCurrentFocusOn} offset={panelOffset()}>
|
<Tabs onFocusChanged={setCurrentFocusOn} offset={panelOffset()}>
|
||||||
<Tab focus={isTabFocus(0)} onClick={[onTabClick, 0]}>
|
<Tab focus={isTabFocus(0)} onClick={[onTabClick, 0]}>
|
||||||
Home
|
Home
|
||||||
|
@ -286,14 +282,7 @@ const Home: ParentComponent = (props) => {
|
||||||
</Tab>
|
</Tab>
|
||||||
</Tabs>
|
</Tabs>
|
||||||
<ProfileMenuButton profile={profile()}>
|
<ProfileMenuButton profile={profile()}>
|
||||||
<MenuItem
|
<MenuItem onClick={(e) => $settings.setKey("prefetchTootsDisabled", !$settings.get().prefetchTootsDisabled)}>
|
||||||
onClick={(e) =>
|
|
||||||
$settings.setKey(
|
|
||||||
"prefetchTootsDisabled",
|
|
||||||
!$settings.get().prefetchTootsDisabled,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
>
|
|
||||||
<ListItemText>Prefetch Toots</ListItemText>
|
<ListItemText>Prefetch Toots</ListItemText>
|
||||||
<ListItemSecondaryAction>
|
<ListItemSecondaryAction>
|
||||||
<Switch checked={prefetching()}></Switch>
|
<Switch checked={prefetching()}></Switch>
|
||||||
|
|
|
@ -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,13 +128,6 @@ const MediaViewer: ParentComponent<MediaViewerProps> = (props) => {
|
||||||
left: 0;
|
left: 0;
|
||||||
z-index: 1;
|
z-index: 1;
|
||||||
cursor: ${dragging() ? "grabbing" : "grab"};
|
cursor: ${dragging() ? "grabbing" : "grab"};
|
||||||
padding-left: var(--safe-area-inset-left, 0);
|
|
||||||
padding-right: var(--safe-area-inset-right, 0);
|
|
||||||
padding-bottom: var(--safe-area-inset-bottom, 0);
|
|
||||||
|
|
||||||
:global(> .MuiToolbar-root) {
|
|
||||||
padding-top: var(--safe-area-inset-top, 0);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.left-dock {
|
.left-dock {
|
||||||
|
@ -214,13 +207,7 @@ const MediaViewer: ParentComponent<MediaViewerProps> = (props) => {
|
||||||
move: number,
|
move: number,
|
||||||
idx: number,
|
idx: number,
|
||||||
) => {
|
) => {
|
||||||
const {
|
const { ref, top: otop, left: oleft, scale: oscale, osize: [owidth, oheight] } = state[idx];
|
||||||
ref,
|
|
||||||
top: otop,
|
|
||||||
left: oleft,
|
|
||||||
scale: oscale,
|
|
||||||
osize: [owidth, oheight],
|
|
||||||
} = state[idx];
|
|
||||||
const [cx, cy] = center;
|
const [cx, cy] = center;
|
||||||
const iy = clamp(cy - otop, 0, oheight),
|
const iy = clamp(cy - otop, 0, oheight),
|
||||||
ix = clamp(cx - oleft, 0, owidth); // in image coordinate system
|
ix = clamp(cx - oleft, 0, owidth); // in image coordinate system
|
||||||
|
|
|
@ -8,13 +8,7 @@ import {
|
||||||
Menu,
|
Menu,
|
||||||
MenuItem,
|
MenuItem,
|
||||||
} from "@suid/material";
|
} from "@suid/material";
|
||||||
import {
|
import { Show, createSignal, createUniqueId, type ParentComponent } from "solid-js";
|
||||||
ErrorBoundary,
|
|
||||||
Show,
|
|
||||||
createSignal,
|
|
||||||
createUniqueId,
|
|
||||||
type ParentComponent,
|
|
||||||
} from "solid-js";
|
|
||||||
import {
|
import {
|
||||||
Settings as SettingsIcon,
|
Settings as SettingsIcon,
|
||||||
Bookmark as BookmarkIcon,
|
Bookmark as BookmarkIcon,
|
||||||
|
@ -48,79 +42,79 @@ const ProfileMenuButton: ParentComponent<{
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<ButtonBase
|
<ButtonBase
|
||||||
aria-haspopup="true"
|
aria-haspopup="true"
|
||||||
sx={{ borderRadius: "50%" }}
|
sx={{ borderRadius: "50%" }}
|
||||||
id={buttonId}
|
id={buttonId}
|
||||||
onClick={onClick}
|
onClick={onClick}
|
||||||
aria-controls={open() ? menuId : undefined}
|
aria-controls={open() ? menuId : undefined}
|
||||||
aria-expanded={open() ? "true" : undefined}
|
aria-expanded={open() ? "true" : undefined}
|
||||||
>
|
>
|
||||||
<Avatar
|
<Avatar
|
||||||
alt={`${props.profile?.displayName}'s avatar`}
|
alt={`${props.profile?.displayName}'s avatar`}
|
||||||
src={props.profile?.avatar}
|
src={props.profile?.avatar}
|
||||||
></Avatar>
|
></Avatar>
|
||||||
</ButtonBase>
|
</ButtonBase>
|
||||||
<Menu
|
<Menu
|
||||||
id={menuId}
|
id={menuId}
|
||||||
anchorEl={anchor()}
|
anchorEl={anchor()}
|
||||||
open={open()}
|
open={open()}
|
||||||
onClose={onClose}
|
onClose={onClose}
|
||||||
MenuListProps={{
|
MenuListProps={{
|
||||||
"aria-labelledby": buttonId,
|
"aria-labelledby": buttonId,
|
||||||
sx: {
|
sx: {
|
||||||
minWidth: "220px",
|
minWidth: "220px",
|
||||||
},
|
}
|
||||||
}}
|
}}
|
||||||
anchorOrigin={{
|
anchorOrigin={{
|
||||||
vertical: "top",
|
vertical: "top",
|
||||||
horizontal: "right",
|
horizontal: "right",
|
||||||
}}
|
}}
|
||||||
transformOrigin={{
|
transformOrigin={{
|
||||||
vertical: "top",
|
vertical: "top",
|
||||||
horizontal: "right",
|
horizontal: "right",
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<MenuItem>
|
<MenuItem>
|
||||||
<ListItemAvatar>
|
<ListItemAvatar>
|
||||||
<Avatar src={props.profile?.avatar}></Avatar>
|
<Avatar src={props.profile?.avatar}></Avatar>
|
||||||
</ListItemAvatar>
|
</ListItemAvatar>
|
||||||
<ListItemText
|
<ListItemText
|
||||||
primary={props.profile?.displayName}
|
primary={props.profile?.displayName}
|
||||||
secondary={`@${props.profile?.username}`}
|
secondary={`@${props.profile?.username}`}
|
||||||
></ListItemText>
|
></ListItemText>
|
||||||
</MenuItem>
|
</MenuItem>
|
||||||
|
|
||||||
<MenuItem>
|
<MenuItem>
|
||||||
<ListItemIcon>
|
<ListItemIcon>
|
||||||
<BookmarkIcon />
|
<BookmarkIcon />
|
||||||
</ListItemIcon>
|
</ListItemIcon>
|
||||||
<ListItemText>Bookmarks</ListItemText>
|
<ListItemText>Bookmarks</ListItemText>
|
||||||
</MenuItem>
|
</MenuItem>
|
||||||
<MenuItem>
|
<MenuItem>
|
||||||
<ListItemIcon>
|
<ListItemIcon>
|
||||||
<LikeIcon />
|
<LikeIcon />
|
||||||
</ListItemIcon>
|
</ListItemIcon>
|
||||||
<ListItemText>Likes</ListItemText>
|
<ListItemText>Likes</ListItemText>
|
||||||
</MenuItem>
|
</MenuItem>
|
||||||
<MenuItem>
|
<MenuItem>
|
||||||
<ListItemIcon>
|
<ListItemIcon>
|
||||||
<ListIcon />
|
<ListIcon />
|
||||||
</ListItemIcon>
|
</ListItemIcon>
|
||||||
<ListItemText>Lists</ListItemText>
|
<ListItemText>Lists</ListItemText>
|
||||||
</MenuItem>
|
</MenuItem>
|
||||||
|
<Divider />
|
||||||
|
<Show when={props.children}>
|
||||||
|
{props.children}
|
||||||
<Divider />
|
<Divider />
|
||||||
<Show when={props.children}>
|
</Show>
|
||||||
{props.children}
|
<MenuItem component={A} href="/settings" onClick={onClose}>
|
||||||
<Divider />
|
<ListItemIcon>
|
||||||
</Show>
|
<SettingsIcon />
|
||||||
<MenuItem component={A} href="/settings" onClick={onClose}>
|
</ListItemIcon>
|
||||||
<ListItemIcon>
|
<ListItemText>Settings</ListItemText>
|
||||||
<SettingsIcon />
|
</MenuItem>
|
||||||
</ListItemIcon>
|
</Menu>
|
||||||
<ListItemText>Settings</ListItemText>
|
|
||||||
</MenuItem>
|
|
||||||
</Menu>
|
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
|
@ -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,7 +1,8 @@
|
||||||
import type { Component } from "solid-js";
|
import type { Component } from "solid-js";
|
||||||
|
|
||||||
const TootBottomSheet: Component = (props) => {
|
|
||||||
return <></>;
|
|
||||||
};
|
|
||||||
|
|
||||||
export default TootBottomSheet;
|
const TootBottomSheet: Component = (props) => {
|
||||||
|
return <></>
|
||||||
|
}
|
||||||
|
|
||||||
|
export default TootBottomSheet
|
||||||
|
|
|
@ -38,9 +38,7 @@ const TootThread: Component<TootThreadProps> = (props) => {
|
||||||
|
|
||||||
css`
|
css`
|
||||||
article {
|
article {
|
||||||
transition:
|
transition: margin 90ms var(--tutu-anim-curve-sharp), var(--tutu-transition-shadow);
|
||||||
margin 90ms var(--tutu-anim-curve-sharp),
|
|
||||||
var(--tutu-transition-shadow);
|
|
||||||
user-select: none;
|
user-select: none;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
}
|
}
|
||||||
|
@ -66,10 +64,7 @@ const TootThread: Component<TootThreadProps> = (props) => {
|
||||||
`;
|
`;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<article
|
<article classList={{ "thread-line": !!inReplyTo(), "expanded": expanded() }} onClick={() => setExpanded((x) => !x)}>
|
||||||
classList={{ "thread-line": !!inReplyTo(), expanded: expanded() }}
|
|
||||||
onClick={() => setExpanded((x) => !x)}
|
|
||||||
>
|
|
||||||
<Show when={inReplyTo()}>
|
<Show when={inReplyTo()}>
|
||||||
<CompactToot
|
<CompactToot
|
||||||
status={inReplyTo()!}
|
status={inReplyTo()!}
|
||||||
|
|
|
@ -6,14 +6,13 @@
|
||||||
|
|
||||||
&.toot {
|
&.toot {
|
||||||
/* fix composition ordering: I think the css module processor should aware the overriding and behaves, but no */
|
/* fix composition ordering: I think the css module processor should aware the overriding and behaves, but no */
|
||||||
transition:
|
transition: margin-block 125ms var(--tutu-anim-curve-std),
|
||||||
margin-block 125ms var(--tutu-anim-curve-std),
|
|
||||||
height 225ms var(--tutu-anim-curve-std),
|
height 225ms var(--tutu-anim-curve-std),
|
||||||
var(--tutu-transition-shadow);
|
var(--tutu-transition-shadow);
|
||||||
border-radius: 0;
|
border-radius: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
& > .toot {
|
&>.toot {
|
||||||
box-shadow: none;
|
box-shadow: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -47,11 +46,11 @@
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-template-columns: 1fr auto;
|
grid-template-columns: 1fr auto;
|
||||||
|
|
||||||
> * {
|
>* {
|
||||||
color: var(--tutu-color-secondary-text-on-surface);
|
color: var(--tutu-color-secondary-text-on-surface);
|
||||||
}
|
}
|
||||||
|
|
||||||
> :last-child {
|
>:last-child {
|
||||||
grid-column: 1 /3;
|
grid-column: 1 /3;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -81,7 +80,7 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
.tootContent {
|
.tootContent {
|
||||||
composes: cardNoPad from "../material/cards.module.css";
|
composes: cardNoPad from '../material/cards.module.css';
|
||||||
margin-left: calc(var(--card-pad, 0) + var(--toot-avatar-size, 0) + 8px);
|
margin-left: calc(var(--card-pad, 0) + var(--toot-avatar-size, 0) + 8px);
|
||||||
margin-right: var(--card-pad, 0);
|
margin-right: var(--card-pad, 0);
|
||||||
line-height: 1.5;
|
line-height: 1.5;
|
||||||
|
@ -151,14 +150,14 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
.tootAttachmentGrp {
|
.tootAttachmentGrp {
|
||||||
composes: cardNoPad from "../material/cards.module.css";
|
composes: cardNoPad from '../material/cards.module.css';
|
||||||
margin-top: 1em;
|
margin-top: 1em;
|
||||||
margin-left: calc(var(--card-pad, 0) + var(--toot-avatar-size, 0) + 8px);
|
margin-left: calc(var(--card-pad, 0) + var(--toot-avatar-size, 0) + 8px);
|
||||||
margin-right: var(--card-pad, 0);
|
margin-right: var(--card-pad, 0);
|
||||||
display: grid;
|
display: grid;
|
||||||
gap: 4px;
|
gap: 4px;
|
||||||
|
|
||||||
> :where(img) {
|
>:where(img) {
|
||||||
max-height: 35vh;
|
max-height: 35vh;
|
||||||
min-height: 40px;
|
min-height: 40px;
|
||||||
object-fit: none;
|
object-fit: none;
|
||||||
|
@ -169,7 +168,7 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
.tootBottomActionGrp {
|
.tootBottomActionGrp {
|
||||||
composes: cardGutSkip from "../material/cards.module.css";
|
composes: cardGutSkip from '../material/cards.module.css';
|
||||||
padding-block: calc((var(--card-gut) - 10px) / 2);
|
padding-block: calc((var(--card-gut) - 10px) / 2);
|
||||||
|
|
||||||
animation: 225ms var(--tutu-anim-curve-std) tootBottomExpanding;
|
animation: 225ms var(--tutu-anim-curve-std) tootBottomExpanding;
|
||||||
|
@ -177,7 +176,7 @@
|
||||||
flex-flow: row wrap;
|
flex-flow: row wrap;
|
||||||
justify-content: space-evenly;
|
justify-content: space-evenly;
|
||||||
|
|
||||||
> button {
|
> button{
|
||||||
color: var(--tutu-color-on-surface);
|
color: var(--tutu-color-on-surface);
|
||||||
padding: 10px 8px;
|
padding: 10px 8px;
|
||||||
|
|
||||||
|
@ -207,4 +206,4 @@
|
||||||
100% {
|
100% {
|
||||||
opacity: 1;
|
opacity: 1;
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -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…
Add table
Reference in a new issue