Compare commits
9 commits
02d883e53b
...
6705b754d1
Author | SHA1 | Date | |
---|---|---|---|
|
6705b754d1 | ||
|
97a1fb9cf1 | ||
|
c22860a7e3 | ||
|
8205ff661b | ||
|
9da4a3a4b3 | ||
|
bdaaeadba6 | ||
|
dde0c249f4 | ||
|
832a59031a | ||
|
a0811ed6c4 |
11 changed files with 167 additions and 91 deletions
|
@ -18,7 +18,16 @@ const UnexpectedError: Component<{ error?: any }> = (props) => {
|
||||||
.join("\n");
|
.join("\n");
|
||||||
return `${err.name}: ${err.message}\n${strackMsg}`;
|
return `${err.name}: ${err.message}\n${strackMsg}`;
|
||||||
} catch (reason) {
|
} catch (reason) {
|
||||||
return `<failed to build the stacktrace of "${err}"...>\n${reason}`;
|
return `<failed to build the stacktrace of "${err}"...>\n${reason}\n${JSON.stringify(
|
||||||
|
{
|
||||||
|
name: err.name,
|
||||||
|
stack: err.stack,
|
||||||
|
cause: err.cause,
|
||||||
|
message: err.message,
|
||||||
|
},
|
||||||
|
undefined,
|
||||||
|
2,
|
||||||
|
)}`;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,28 +0,0 @@
|
||||||
import { Accessor, createResource } from "solid-js";
|
|
||||||
import type { mastodon } from "masto";
|
|
||||||
import { useSessions } from "./clients";
|
|
||||||
import { updateAcctInf } from "../accounts/stores";
|
|
||||||
|
|
||||||
export function useSignedInProfiles() {
|
|
||||||
const sessions = useSessions();
|
|
||||||
const [accessor, tools] = createResource(sessions, async (all) => {
|
|
||||||
return Promise.all(
|
|
||||||
all.map(async (x, i) => ({ ...x, inf: await updateAcctInf(i) })),
|
|
||||||
);
|
|
||||||
});
|
|
||||||
return [
|
|
||||||
() => {
|
|
||||||
try {
|
|
||||||
const value = accessor();
|
|
||||||
if (value) {
|
|
||||||
return value;
|
|
||||||
}
|
|
||||||
} catch (reason) {
|
|
||||||
console.error("useSignedInProfiles: update acct info failed", reason);
|
|
||||||
}
|
|
||||||
|
|
||||||
return sessions().map((x) => ({ ...x, inf: x.account.inf }));
|
|
||||||
},
|
|
||||||
tools,
|
|
||||||
] as const;
|
|
||||||
}
|
|
|
@ -26,7 +26,9 @@ export function createTimelineControlsForArray(
|
||||||
const [threads, setThreads] = createStore([] as mastodon.v1.Status["id"][]);
|
const [threads, setThreads] = createStore([] as mastodon.v1.Status["id"][]);
|
||||||
|
|
||||||
createEffect(() => {
|
createEffect(() => {
|
||||||
const nls = status();
|
const nls = catchError(status, (e) => {
|
||||||
|
console.error(e);
|
||||||
|
});
|
||||||
if (!nls) return;
|
if (!nls) return;
|
||||||
|
|
||||||
setThreads([]);
|
setThreads([]);
|
||||||
|
@ -226,9 +228,7 @@ export type TimelineControls = {
|
||||||
set(id: string, value: mastodon.v1.Status): void;
|
set(id: string, value: mastodon.v1.Status): void;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type TimelineResource<
|
export type TimelineResource<R> = [
|
||||||
R,
|
|
||||||
> = [
|
|
||||||
TimelineControls,
|
TimelineControls,
|
||||||
Resource<R>,
|
Resource<R>,
|
||||||
{ refetch(info?: TimelineFetchDirection): void },
|
{ refetch(info?: TimelineFetchDirection): void },
|
||||||
|
@ -238,7 +238,7 @@ export type TimelineResource<
|
||||||
* Create auto managed timeline controls.
|
* Create auto managed timeline controls.
|
||||||
*
|
*
|
||||||
* The error from the resource is not thrown in the
|
* The error from the resource is not thrown in the
|
||||||
* {@link TimelineControls.list} and {@link TimelineControls}.get*.
|
* {@link TimelineControls["list"]} and {@link TimelineControls}.get*.
|
||||||
* Use the second value from {@link TimelineResource} to catch the error.
|
* Use the second value from {@link TimelineResource} to catch the error.
|
||||||
*/
|
*/
|
||||||
export function createTimeline<
|
export function createTimeline<
|
||||||
|
|
|
@ -228,6 +228,8 @@ const BottomSheet: ParentComponent<BottomSheetProps> = (props) => {
|
||||||
}}
|
}}
|
||||||
onClick={onDialogClick}
|
onClick={onDialogClick}
|
||||||
ref={element!}
|
ref={element!}
|
||||||
|
tabIndex={-1}
|
||||||
|
role="presentation"
|
||||||
>
|
>
|
||||||
{ochildren() ?? cache()}
|
{ochildren() ?? cache()}
|
||||||
</dialog>
|
</dialog>
|
||||||
|
|
|
@ -23,6 +23,11 @@
|
||||||
&.e4 {
|
&.e4 {
|
||||||
box-shadow: var(--tutu-shadow-e12);
|
box-shadow: var(--tutu-shadow-e12);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
&>.container {
|
||||||
|
background: var(--tutu-color-surface);
|
||||||
|
display: contents;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
dialog.Menu::backdrop {
|
dialog.Menu::backdrop {
|
||||||
|
|
|
@ -3,6 +3,7 @@ import { MenuList } from "@suid/material";
|
||||||
import {
|
import {
|
||||||
createEffect,
|
createEffect,
|
||||||
createSignal,
|
createSignal,
|
||||||
|
splitProps,
|
||||||
type Component,
|
type Component,
|
||||||
type JSX,
|
type JSX,
|
||||||
type ParentProps,
|
type ParentProps,
|
||||||
|
@ -16,11 +17,15 @@ import {
|
||||||
|
|
||||||
export type Anchor = Pick<DOMRect, "top" | "left" | "right"> & { e?: number };
|
export type Anchor = Pick<DOMRect, "top" | "left" | "right"> & { e?: number };
|
||||||
|
|
||||||
export type MenuProps = ParentProps<{
|
export type MenuProps = ParentProps<
|
||||||
|
{
|
||||||
open?: boolean;
|
open?: boolean;
|
||||||
onClose?: JSX.EventHandlerUnion<HTMLDialogElement, Event>;
|
onClose?: JSX.EventHandlerUnion<HTMLDialogElement, Event>;
|
||||||
anchor: () => Anchor;
|
anchor: () => Anchor;
|
||||||
}>;
|
|
||||||
|
id?: string;
|
||||||
|
} & JSX.AriaAttributes
|
||||||
|
>;
|
||||||
|
|
||||||
function px(n?: number) {
|
function px(n?: number) {
|
||||||
if (n) {
|
if (n) {
|
||||||
|
@ -74,6 +79,7 @@ export function createManagedMenuState() {
|
||||||
const Menu: Component<MenuProps> = (props) => {
|
const Menu: Component<MenuProps> = (props) => {
|
||||||
let root: HTMLDialogElement;
|
let root: HTMLDialogElement;
|
||||||
const windowSize = useWindowSize();
|
const windowSize = useWindowSize();
|
||||||
|
const [, rest] = splitProps(props, ["open", "onClose", "anchor"]);
|
||||||
|
|
||||||
const [anchorPos, setAnchorPos] = createSignal<{
|
const [anchorPos, setAnchorPos] = createSignal<{
|
||||||
left?: number;
|
left?: number;
|
||||||
|
@ -83,11 +89,12 @@ const Menu: Component<MenuProps> = (props) => {
|
||||||
|
|
||||||
if (import.meta.env.DEV) {
|
if (import.meta.env.DEV) {
|
||||||
createEffect(() => {
|
createEffect(() => {
|
||||||
|
if (anchorPos().e)
|
||||||
switch (anchorPos().e) {
|
switch (anchorPos().e) {
|
||||||
case 1:
|
case 1:
|
||||||
case 2:
|
case 2:
|
||||||
case 3:
|
case 3:
|
||||||
case 3:
|
case 4:
|
||||||
return;
|
return;
|
||||||
default:
|
default:
|
||||||
console.warn('value %s is invalid for param "e"', anchorPos().e);
|
console.warn('value %s is invalid for param "e"', anchorPos().e);
|
||||||
|
@ -202,12 +209,13 @@ const Menu: Component<MenuProps> = (props) => {
|
||||||
top: px(anchorPos().top),
|
top: px(anchorPos().top),
|
||||||
/* FIXME: the content may be overflow */
|
/* FIXME: the content may be overflow */
|
||||||
}}
|
}}
|
||||||
|
role="presentation"
|
||||||
|
tabIndex={-1}
|
||||||
|
{...rest}
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
style={{
|
class="container"
|
||||||
background: "var(--tutu-color-surface)",
|
role="presentation"
|
||||||
display: "contents"
|
|
||||||
}}
|
|
||||||
>
|
>
|
||||||
<MenuList>{props.children}</MenuList>
|
<MenuList>{props.children}</MenuList>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -36,12 +36,12 @@ const Scaffold: Component<ScaffoldProps> = (props) => {
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Show when={props.topbar}>
|
<Show when={props.topbar}>
|
||||||
<div class="Scaffold__topbar" ref={setTopbarElement}>
|
<div class="Scaffold__topbar" ref={setTopbarElement} role="presentation">
|
||||||
{props.topbar}
|
{props.topbar}
|
||||||
</div>
|
</div>
|
||||||
</Show>
|
</Show>
|
||||||
<Show when={props.fab}>
|
<Show when={props.fab}>
|
||||||
<div class="Scaffold__fab-dock">{props.fab}</div>
|
<div class="Scaffold__fab-dock" role="presentation">{props.fab}</div>
|
||||||
</Show>
|
</Show>
|
||||||
<div
|
<div
|
||||||
ref={(e) => {
|
ref={(e) => {
|
||||||
|
@ -61,7 +61,7 @@ const Scaffold: Component<ScaffoldProps> = (props) => {
|
||||||
{managed.children}
|
{managed.children}
|
||||||
</div>
|
</div>
|
||||||
<Show when={props.bottom}>
|
<Show when={props.bottom}>
|
||||||
<div class="Scaffold__bottom-dock">{props.bottom}</div>
|
<div class="Scaffold__bottom-dock" role="presentation">{props.bottom}</div>
|
||||||
</Show>
|
</Show>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
|
|
|
@ -10,12 +10,14 @@ import {
|
||||||
onCleanup,
|
onCleanup,
|
||||||
Show,
|
Show,
|
||||||
type Component,
|
type Component,
|
||||||
|
createMemo,
|
||||||
} from "solid-js";
|
} from "solid-js";
|
||||||
import Scaffold from "../material/Scaffold";
|
import Scaffold from "../material/Scaffold";
|
||||||
import {
|
import {
|
||||||
AppBar,
|
AppBar,
|
||||||
Avatar,
|
Avatar,
|
||||||
Button,
|
Button,
|
||||||
|
Checkbox,
|
||||||
CircularProgress,
|
CircularProgress,
|
||||||
Divider,
|
Divider,
|
||||||
IconButton,
|
IconButton,
|
||||||
|
@ -66,6 +68,8 @@ const Profile: Component = () => {
|
||||||
const time = createTimeSource();
|
const time = createTimeSource();
|
||||||
|
|
||||||
const menuButId = createUniqueId();
|
const menuButId = createUniqueId();
|
||||||
|
const recentTootListId = createUniqueId();
|
||||||
|
const optMenuId = createUniqueId();
|
||||||
|
|
||||||
const [menuOpen, setMenuOpen] = createSignal(false);
|
const [menuOpen, setMenuOpen] = createSignal(false);
|
||||||
|
|
||||||
|
@ -87,17 +91,20 @@ const Profile: Component = () => {
|
||||||
);
|
);
|
||||||
onCleanup(() => obx.disconnect());
|
onCleanup(() => obx.disconnect());
|
||||||
|
|
||||||
const [profileErrorUncaught] = createResource(
|
const [profileUncaught] = createResource(
|
||||||
() => [session().client, params.id] as const,
|
() => [session().client, params.id] as const,
|
||||||
async ([client, id]) => {
|
async ([client, id]) => {
|
||||||
return await client.v1.accounts.$select(id).fetch();
|
return await client.v1.accounts.$select(id).fetch();
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
const profile = () =>
|
const profile = () => {
|
||||||
catchError(profileErrorUncaught, (err) => {
|
try {
|
||||||
console.error(err);
|
return profileUncaught();
|
||||||
});
|
} catch (reason) {
|
||||||
|
console.error(reason);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
const isCurrentSessionProfile = () => {
|
const isCurrentSessionProfile = () => {
|
||||||
return session().account?.inf?.url === profile()?.url;
|
return session().account?.inf?.url === profile()?.url;
|
||||||
|
@ -126,6 +133,22 @@ const Profile: Component = () => {
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const [relationshipUncaught, { mutate: mutateRelationship }] = createResource(
|
||||||
|
() => [session(), params.id] as const,
|
||||||
|
async ([sess, id]) => {
|
||||||
|
if (!sess.account) return; // No account, no relation
|
||||||
|
const relations = await session().client.v1.accounts.relationships.fetch({
|
||||||
|
id: [id],
|
||||||
|
});
|
||||||
|
return relations.length > 0 ? relations[0] : undefined;
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
const relationship = () =>
|
||||||
|
catchError(relationshipUncaught, (reason) => {
|
||||||
|
console.error(reason);
|
||||||
|
});
|
||||||
|
|
||||||
const bannerImg = () => profile()?.header;
|
const bannerImg = () => profile()?.header;
|
||||||
const avatarImg = () => profile()?.avatar;
|
const avatarImg = () => profile()?.avatar;
|
||||||
const displayName = () =>
|
const displayName = () =>
|
||||||
|
@ -137,10 +160,38 @@ const Profile: Component = () => {
|
||||||
recentTootChunk.loading ||
|
recentTootChunk.loading ||
|
||||||
(recentTootFilter().pinned && pinnedTootChunk.loading);
|
(recentTootFilter().pinned && pinnedTootChunk.loading);
|
||||||
|
|
||||||
|
const sessionDisplayName = createMemo(() =>
|
||||||
|
resolveCustomEmoji(
|
||||||
|
session().account?.inf?.displayName || "",
|
||||||
|
session().account?.inf?.emojis ?? [],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
const useSessionDisplayName = (e: HTMLElement) => {
|
||||||
|
createRenderEffect(() => (e.innerHTML = sessionDisplayName()));
|
||||||
|
};
|
||||||
|
|
||||||
|
const toggleSubscribeHome = async () => {
|
||||||
|
const client = session().client;
|
||||||
|
if (!session().account) return;
|
||||||
|
const isSubscribed = relationship()?.following ?? false;
|
||||||
|
mutateRelationship((x) => Object.assign({ following: !isSubscribed }, x));
|
||||||
|
subscribeMenuState.onClose();
|
||||||
|
|
||||||
|
if (isSubscribed) {
|
||||||
|
const nrel = await client.v1.accounts.$select(params.id).unfollow();
|
||||||
|
mutateRelationship(nrel);
|
||||||
|
} else {
|
||||||
|
const nrel = await client.v1.accounts.$select(params.id).follow();
|
||||||
|
mutateRelationship(nrel);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Scaffold
|
<Scaffold
|
||||||
topbar={
|
topbar={
|
||||||
<AppBar
|
<AppBar
|
||||||
|
role="navigation"
|
||||||
position="static"
|
position="static"
|
||||||
color={scrolledPastBanner() ? "primary" : "transparent"}
|
color={scrolledPastBanner() ? "primary" : "transparent"}
|
||||||
elevation={scrolledPastBanner() ? undefined : 0}
|
elevation={scrolledPastBanner() ? undefined : 0}
|
||||||
|
@ -174,8 +225,10 @@ const Profile: Component = () => {
|
||||||
|
|
||||||
<IconButton
|
<IconButton
|
||||||
id={menuButId}
|
id={menuButId}
|
||||||
|
aria-controls={optMenuId}
|
||||||
color="inherit"
|
color="inherit"
|
||||||
onClick={[setMenuOpen, true]}
|
onClick={[setMenuOpen, true]}
|
||||||
|
aria-label="Open Options for the Profile"
|
||||||
>
|
>
|
||||||
<MoreVert />
|
<MoreVert />
|
||||||
</IconButton>
|
</IconButton>
|
||||||
|
@ -185,12 +238,25 @@ const Profile: Component = () => {
|
||||||
class="Profile"
|
class="Profile"
|
||||||
>
|
>
|
||||||
<Menu
|
<Menu
|
||||||
|
id={optMenuId}
|
||||||
open={menuOpen()}
|
open={menuOpen()}
|
||||||
onClose={[setMenuOpen, false]}
|
onClose={[setMenuOpen, false]}
|
||||||
anchor={() =>
|
anchor={() =>
|
||||||
document.getElementById(menuButId)!.getBoundingClientRect()
|
document.getElementById(menuButId)!.getBoundingClientRect()
|
||||||
}
|
}
|
||||||
|
aria-label="Options for the Profile"
|
||||||
>
|
>
|
||||||
|
<Show when={session().account}>
|
||||||
|
<MenuItem>
|
||||||
|
<ListItemAvatar>
|
||||||
|
<Avatar src={session().account?.inf?.avatar} />
|
||||||
|
</ListItemAvatar>
|
||||||
|
<ListItemText secondary={"Default account"}>
|
||||||
|
<span ref={useSessionDisplayName}></span>
|
||||||
|
</ListItemText>
|
||||||
|
{/* <ArrowRight /> // for future */}
|
||||||
|
</MenuItem>
|
||||||
|
</Show>
|
||||||
<Show when={session().account && profile()}>
|
<Show when={session().account && profile()}>
|
||||||
<Show
|
<Show
|
||||||
when={isCurrentSessionProfile()}
|
when={isCurrentSessionProfile()}
|
||||||
|
@ -273,6 +339,7 @@ const Profile: Component = () => {
|
||||||
"margin-top":
|
"margin-top":
|
||||||
"calc(-1 * (var(--scaffold-topbar-height) + var(--safe-area-inset-top)))",
|
"calc(-1 * (var(--scaffold-topbar-height) + var(--safe-area-inset-top)))",
|
||||||
}}
|
}}
|
||||||
|
role="presentation"
|
||||||
>
|
>
|
||||||
<img
|
<img
|
||||||
ref={(e) => obx.observe(e)}
|
ref={(e) => obx.observe(e)}
|
||||||
|
@ -283,7 +350,8 @@ const Profile: Component = () => {
|
||||||
height: "100%",
|
height: "100%",
|
||||||
}}
|
}}
|
||||||
crossOrigin="anonymous"
|
crossOrigin="anonymous"
|
||||||
onLoad={async (event) => {
|
alt={`Banner image for ${profile()?.displayName || "the user"}`}
|
||||||
|
onLoad={(event) => {
|
||||||
const ins = new FastAverageColor();
|
const ins = new FastAverageColor();
|
||||||
const colors = ins.getColor(event.currentTarget);
|
const colors = ins.getColor(event.currentTarget);
|
||||||
setBannerSampledColors({
|
setBannerSampledColors({
|
||||||
|
@ -296,23 +364,21 @@ const Profile: Component = () => {
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<Menu {...subscribeMenuState}>
|
<Menu {...subscribeMenuState}>
|
||||||
<MenuItem disabled>
|
<MenuItem
|
||||||
|
onClick={toggleSubscribeHome}
|
||||||
|
aria-details="Subscribe or Unsubscribe this account on your home timeline"
|
||||||
|
>
|
||||||
<ListItemAvatar>
|
<ListItemAvatar>
|
||||||
<Avatar src={session().account?.inf?.avatar}></Avatar>
|
<Avatar src={session().account?.inf?.avatar}></Avatar>
|
||||||
</ListItemAvatar>
|
</ListItemAvatar>
|
||||||
<ListItemText>
|
<ListItemText
|
||||||
<span
|
secondary={relationship()?.following ? "Subscribed" : undefined}
|
||||||
ref={(e) =>
|
>
|
||||||
createRenderEffect(() => {
|
<span ref={useSessionDisplayName}></span>
|
||||||
e.innerHTML = resolveCustomEmoji(
|
|
||||||
session().account?.inf?.displayName || "",
|
|
||||||
session().account?.inf?.emojis ?? [],
|
|
||||||
);
|
|
||||||
})
|
|
||||||
}
|
|
||||||
></span>
|
|
||||||
<span>'s Home</span>
|
<span>'s Home</span>
|
||||||
</ListItemText>
|
</ListItemText>
|
||||||
|
|
||||||
|
<Checkbox checked={relationship()?.following ?? false} />
|
||||||
</MenuItem>
|
</MenuItem>
|
||||||
</Menu>
|
</Menu>
|
||||||
|
|
||||||
|
@ -323,9 +389,10 @@ const Profile: Component = () => {
|
||||||
color: bannerSampledColors()?.text,
|
color: bannerSampledColors()?.text,
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<div class="acct-grp">
|
<section class="acct-grp">
|
||||||
<Avatar
|
<Avatar
|
||||||
src={avatarImg()}
|
src={avatarImg()}
|
||||||
|
alt={`${profile()?.displayName || "the user"}'s avatar`}
|
||||||
sx={{
|
sx={{
|
||||||
marginTop: "calc(-16px - 72px / 2)",
|
marginTop: "calc(-16px - 72px / 2)",
|
||||||
width: "72px",
|
width: "72px",
|
||||||
|
@ -337,12 +404,19 @@ const Profile: Component = () => {
|
||||||
ref={(e) =>
|
ref={(e) =>
|
||||||
createRenderEffect(() => (e.innerHTML = displayName()))
|
createRenderEffect(() => (e.innerHTML = displayName()))
|
||||||
}
|
}
|
||||||
|
aria-label="Display name"
|
||||||
></span>
|
></span>
|
||||||
<span>{fullUsername()}</span>
|
<span aria-label="Complete username">{fullUsername()}</span>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<Switch>
|
<Switch>
|
||||||
<Match when={!session().account || profileErrorUncaught.loading}>
|
<Match
|
||||||
|
when={
|
||||||
|
!session().account ||
|
||||||
|
profileUncaught.loading ||
|
||||||
|
profileUncaught.error
|
||||||
|
}
|
||||||
|
>
|
||||||
{<></>}
|
{<></>}
|
||||||
</Match>
|
</Match>
|
||||||
<Match when={isCurrentSessionProfile()}>
|
<Match when={isCurrentSessionProfile()}>
|
||||||
|
@ -360,20 +434,24 @@ const Profile: Component = () => {
|
||||||
);
|
);
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
Subscribe
|
{relationship()?.following ? "Subscribed" : "Subscribe"}
|
||||||
</Button>
|
</Button>
|
||||||
</Match>
|
</Match>
|
||||||
</Switch>
|
</Switch>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</section>
|
||||||
<div
|
<section
|
||||||
class="description"
|
class="description"
|
||||||
|
aria-label={`${profile()?.displayName || "the user"}'s description`}
|
||||||
ref={(e) =>
|
ref={(e) =>
|
||||||
createRenderEffect(() => (e.innerHTML = description() || ""))
|
createRenderEffect(() => (e.innerHTML = description() || ""))
|
||||||
}
|
}
|
||||||
></div>
|
></section>
|
||||||
|
|
||||||
<table class="acct-fields">
|
<table
|
||||||
|
class="acct-fields"
|
||||||
|
aria-label={`${profile()?.displayName || "the user"}'s fields`}
|
||||||
|
>
|
||||||
<tbody>
|
<tbody>
|
||||||
<For each={profile()?.fields ?? []}>
|
<For each={profile()?.fields ?? []}>
|
||||||
{(item, index) => {
|
{(item, index) => {
|
||||||
|
@ -422,6 +500,7 @@ const Profile: Component = () => {
|
||||||
<Divider />
|
<Divider />
|
||||||
</Show>
|
</Show>
|
||||||
<TootList
|
<TootList
|
||||||
|
id={recentTootListId}
|
||||||
threads={recentToots.list}
|
threads={recentToots.list}
|
||||||
onUnknownThread={recentToots.getPath}
|
onUnknownThread={recentToots.getPath}
|
||||||
onChangeToot={recentToots.set}
|
onChangeToot={recentToots.set}
|
||||||
|
@ -437,6 +516,7 @@ const Profile: Component = () => {
|
||||||
>
|
>
|
||||||
<IconButton
|
<IconButton
|
||||||
aria-label="Load More"
|
aria-label="Load More"
|
||||||
|
aria-controls={recentTootListId}
|
||||||
size="large"
|
size="large"
|
||||||
color="primary"
|
color="primary"
|
||||||
onClick={[refetchRecentToots, "prev"]}
|
onClick={[refetchRecentToots, "prev"]}
|
||||||
|
|
|
@ -31,12 +31,10 @@ import {
|
||||||
import { A, useNavigate } from "@solidjs/router";
|
import { A, useNavigate } from "@solidjs/router";
|
||||||
import { Title } from "../material/typography.jsx";
|
import { Title } from "../material/typography.jsx";
|
||||||
import { css } from "solid-styled";
|
import { css } from "solid-styled";
|
||||||
import { useSignedInProfiles } from "../masto/acct.js";
|
|
||||||
import { signOut, type Account } from "../accounts/stores.js";
|
import { signOut, type Account } from "../accounts/stores.js";
|
||||||
import { format } from "date-fns";
|
import { format } from "date-fns";
|
||||||
import { useStore } from "@nanostores/solid";
|
import { useStore } from "@nanostores/solid";
|
||||||
import { $settings } from "./stores.js";
|
import { $settings } from "./stores.js";
|
||||||
import { useRegisterSW } from "virtual:pwa-register/solid";
|
|
||||||
import {
|
import {
|
||||||
autoMatchLangTag,
|
autoMatchLangTag,
|
||||||
autoMatchRegion,
|
autoMatchRegion,
|
||||||
|
@ -46,6 +44,7 @@ import {
|
||||||
import { type Template } from "@solid-primitives/i18n";
|
import { type Template } from "@solid-primitives/i18n";
|
||||||
import BottomSheet from "../material/BottomSheet.jsx";
|
import BottomSheet from "../material/BottomSheet.jsx";
|
||||||
import { useServiceWorker } from "../platform/host.js";
|
import { useServiceWorker } from "../platform/host.js";
|
||||||
|
import { useSessions } from "../masto/clients.js";
|
||||||
|
|
||||||
type Strings = {
|
type Strings = {
|
||||||
["lang.auto"]: Template<{ detected: string }>;
|
["lang.auto"]: Template<{ detected: string }>;
|
||||||
|
@ -64,7 +63,7 @@ const Settings: ParentComponent = (props) => {
|
||||||
const { needRefresh, offlineReady } = useServiceWorker();
|
const { needRefresh, offlineReady } = useServiceWorker();
|
||||||
const dateFnLocale = useDateFnLocale();
|
const dateFnLocale = useDateFnLocale();
|
||||||
|
|
||||||
const [profiles] = useSignedInProfiles();
|
const profiles = useSessions();
|
||||||
|
|
||||||
const doSignOut = (acct: Account) => {
|
const doSignOut = (acct: Account) => {
|
||||||
signOut((a) => a.site === acct.site && a.accessToken === acct.accessToken);
|
signOut((a) => a.site === acct.site && a.accessToken === acct.accessToken);
|
||||||
|
@ -118,9 +117,9 @@ const Settings: ParentComponent = (props) => {
|
||||||
<Divider />
|
<Divider />
|
||||||
</ul>
|
</ul>
|
||||||
<For each={profiles()}>
|
<For each={profiles()}>
|
||||||
{({ account: acct, inf }) => (
|
{({ account: acct }) => (
|
||||||
<ul data-site={acct.site} data-username={inf?.username}>
|
<ul data-site={acct.site} data-username={acct.inf?.username}>
|
||||||
<ListSubheader>{`@${inf?.username ?? "..."}@${new URL(acct.site).host}`}</ListSubheader>
|
<ListSubheader>{`@${acct.inf?.username ?? "..."}@${new URL(acct.site).host}`}</ListSubheader>
|
||||||
<ListItemButton disabled>
|
<ListItemButton disabled>
|
||||||
<ListItemText>{t("Notifications")}</ListItemText>
|
<ListItemText>{t("Notifications")}</ListItemText>
|
||||||
<ListItemSecondaryAction>
|
<ListItemSecondaryAction>
|
||||||
|
|
|
@ -30,10 +30,10 @@ import { $settings } from "../settings/stores";
|
||||||
import { useStore } from "@nanostores/solid";
|
import { useStore } from "@nanostores/solid";
|
||||||
import { HeroSourceProvider, type HeroSource } from "../platform/anim";
|
import { HeroSourceProvider, type HeroSource } from "../platform/anim";
|
||||||
import { useNavigate } from "@solidjs/router";
|
import { useNavigate } from "@solidjs/router";
|
||||||
import { useSignedInProfiles } from "../masto/acct";
|
|
||||||
import { setCache as setTootBottomSheetCache } from "./TootBottomSheet";
|
import { setCache as setTootBottomSheetCache } from "./TootBottomSheet";
|
||||||
import TrendTimelinePanel from "./TrendTimelinePanel";
|
import TrendTimelinePanel from "./TrendTimelinePanel";
|
||||||
import TimelinePanel from "./TimelinePanel";
|
import TimelinePanel from "./TimelinePanel";
|
||||||
|
import { useSessions } from "../masto/clients";
|
||||||
|
|
||||||
const Home: ParentComponent = (props) => {
|
const Home: ParentComponent = (props) => {
|
||||||
let panelList: HTMLDivElement;
|
let panelList: HTMLDivElement;
|
||||||
|
@ -42,11 +42,11 @@ const Home: ParentComponent = (props) => {
|
||||||
|
|
||||||
const settings$ = useStore($settings);
|
const settings$ = useStore($settings);
|
||||||
|
|
||||||
const [profiles] = useSignedInProfiles();
|
const profiles = useSessions();
|
||||||
const profile = () => {
|
const profile = () => {
|
||||||
const all = profiles();
|
const all = profiles();
|
||||||
if (all.length > 0) {
|
if (all.length > 0) {
|
||||||
return all[0].inf;
|
return all[0].account.inf;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
const client = () => {
|
const client = () => {
|
||||||
|
|
|
@ -18,6 +18,7 @@ import { findElementActionable } from "./RegularToot";
|
||||||
|
|
||||||
const TootList: Component<{
|
const TootList: Component<{
|
||||||
ref?: Ref<HTMLDivElement>;
|
ref?: Ref<HTMLDivElement>;
|
||||||
|
id?: string;
|
||||||
threads: readonly string[];
|
threads: readonly string[];
|
||||||
onUnknownThread: (id: string) => { value: mastodon.v1.Status }[] | undefined;
|
onUnknownThread: (id: string) => { value: mastodon.v1.Status }[] | undefined;
|
||||||
onChangeToot: (id: string, value: mastodon.v1.Status) => void;
|
onChangeToot: (id: string, value: mastodon.v1.Status) => void;
|
||||||
|
@ -149,7 +150,7 @@ const TootList: Component<{
|
||||||
return <p>Oops: {String(err)}</p>;
|
return <p>Oops: {String(err)}</p>;
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<div ref={props.ref} class="toot-list">
|
<div ref={props.ref} id={props.id} class="toot-list">
|
||||||
<For each={props.threads}>
|
<For each={props.threads}>
|
||||||
{(itemId, index) => {
|
{(itemId, index) => {
|
||||||
const path = props.onUnknownThread(itemId)!;
|
const path = props.onUnknownThread(itemId)!;
|
||||||
|
|
Loading…
Reference in a new issue