initial commit
This commit is contained in:
commit
5449e361d5
46 changed files with 8309 additions and 0 deletions
259
src/timelines/RegularToot.tsx
Normal file
259
src/timelines/RegularToot.tsx
Normal file
|
@ -0,0 +1,259 @@
|
|||
import type { mastodon } from "masto";
|
||||
import {
|
||||
splitProps,
|
||||
type Component,
|
||||
type JSX,
|
||||
Show,
|
||||
createRenderEffect,
|
||||
} from "solid-js";
|
||||
import tootStyle from "./toot.module.css";
|
||||
import { formatRelative } from "date-fns";
|
||||
import Img from "../material/Img.js";
|
||||
import { Body2 } from "../material/typography.js";
|
||||
import { css } from "solid-styled";
|
||||
import {
|
||||
BookmarkAddOutlined,
|
||||
Repeat,
|
||||
ReplyAll,
|
||||
Star,
|
||||
StarOutline,
|
||||
Bookmark,
|
||||
Reply,
|
||||
} from "@suid/icons-material";
|
||||
import { useTimeSource } from "../platform/timesrc.js";
|
||||
import { resolveCustomEmoji } from "../masto/toot.js";
|
||||
import { Divider } from "@suid/material";
|
||||
import cardStyle from "../material/cards.module.css";
|
||||
import Button from "../material/Button.js";
|
||||
import MediaAttachmentGrid from "./MediaAttachmentGrid.js";
|
||||
|
||||
type TootContentViewProps = {
|
||||
source?: string;
|
||||
emojis?: mastodon.v1.CustomEmoji[];
|
||||
} & JSX.HTMLAttributes<HTMLDivElement>;
|
||||
|
||||
const TootContentView: Component<TootContentViewProps> = (props) => {
|
||||
const [managed, rest] = splitProps(props, ["source", "emojis"]);
|
||||
return (
|
||||
<div
|
||||
ref={(ref) => {
|
||||
createRenderEffect(() => {
|
||||
ref.innerHTML = managed.source
|
||||
? managed.emojis
|
||||
? resolveCustomEmoji(managed.source, managed.emojis)
|
||||
: managed.source
|
||||
: "";
|
||||
});
|
||||
}}
|
||||
{...rest}
|
||||
></div>
|
||||
);
|
||||
};
|
||||
|
||||
const RetootIcon: Component<JSX.HTMLElementTags["i"]> = (props) => {
|
||||
const [managed, rest] = splitProps(props, ["class"]);
|
||||
css`
|
||||
.retoot-icon {
|
||||
padding: 0;
|
||||
display: inline-block;
|
||||
border-radius: 2px;
|
||||
|
||||
> :global(svg) {
|
||||
color: green;
|
||||
font-size: 1rem;
|
||||
vertical-align: middle;
|
||||
}
|
||||
}
|
||||
`;
|
||||
return (
|
||||
<i class={["retoot-icon", managed.class].join(" ")} {...rest}>
|
||||
<Repeat />
|
||||
</i>
|
||||
);
|
||||
};
|
||||
|
||||
const ReplyIcon: Component<JSX.HTMLElementTags["i"]> = (props) => {
|
||||
const [managed, rest] = splitProps(props, ["class"]);
|
||||
css`
|
||||
.retoot-icon {
|
||||
padding: 0;
|
||||
display: inline-block;
|
||||
border-radius: 2px;
|
||||
|
||||
> :global(svg) {
|
||||
color: var(--tutu-color-primary);
|
||||
font-size: 1rem;
|
||||
vertical-align: middle;
|
||||
}
|
||||
}
|
||||
`;
|
||||
return (
|
||||
<i class={["retoot-icon", managed.class].join(" ")} {...rest}>
|
||||
<Reply />
|
||||
</i>
|
||||
);
|
||||
};
|
||||
|
||||
type TootActionGroupProps<T extends mastodon.v1.Status> = {
|
||||
onRetoot?: (value: T) => void;
|
||||
onFavourite?: (value: T) => void;
|
||||
onBookmark?: (value: T) => void;
|
||||
onReply?: (value: T) => void;
|
||||
};
|
||||
|
||||
type TootCardProps = {
|
||||
status: mastodon.v1.Status;
|
||||
actionable?: boolean;
|
||||
evaluated?: boolean;
|
||||
} & TootActionGroupProps<mastodon.v1.Status> &
|
||||
JSX.HTMLElementTags["article"];
|
||||
|
||||
function isolatedCallback(e: MouseEvent) {
|
||||
e.stopPropagation();
|
||||
}
|
||||
|
||||
function TootActionGroup<T extends mastodon.v1.Status>(
|
||||
props: TootActionGroupProps<T> & { value: T },
|
||||
) {
|
||||
const toot = () => props.value;
|
||||
return (
|
||||
<div class={tootStyle.tootBottomActionGrp} onClick={isolatedCallback}>
|
||||
<Button
|
||||
class={tootStyle.tootActionWithCount}
|
||||
onClick={() => props.onReply?.(toot())}
|
||||
>
|
||||
<ReplyAll />
|
||||
<span>{toot().repliesCount}</span>
|
||||
</Button>
|
||||
<Button
|
||||
class={tootStyle.tootActionWithCount}
|
||||
style={{
|
||||
color: toot().reblogged ? "var(--tutu-color-primary)" : undefined,
|
||||
}}
|
||||
onClick={() => props.onRetoot?.(toot())}
|
||||
>
|
||||
<Repeat />
|
||||
<span>{toot().reblogsCount}</span>
|
||||
</Button>
|
||||
<Button
|
||||
class={tootStyle.tootActionWithCount}
|
||||
style={{
|
||||
color: toot().favourited ? "var(--tutu-color-primary)" : undefined,
|
||||
}}
|
||||
onClick={() => props.onFavourite?.(toot())}
|
||||
>
|
||||
{toot().favourited ? <Star /> : <StarOutline />}
|
||||
<span>{toot().favouritesCount}</span>
|
||||
</Button>
|
||||
<Button
|
||||
class={tootStyle.tootAction}
|
||||
style={{
|
||||
color: toot().bookmarked ? "var(--tutu-color-primary)" : undefined,
|
||||
}}
|
||||
onClick={() => props.onBookmark?.(toot())}
|
||||
>
|
||||
{toot().bookmarked ? <Bookmark /> : <BookmarkAddOutlined />}
|
||||
</Button>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function TootAuthorGroup(props: { status: mastodon.v1.Status; now: Date }) {
|
||||
const toot = () => props.status;
|
||||
|
||||
return (
|
||||
<div class={tootStyle.tootAuthorGrp}>
|
||||
<Img src={toot().account.avatar} class={tootStyle.tootAvatar} />
|
||||
<div class={tootStyle.tootAuthorNameGrp}>
|
||||
<Body2
|
||||
class={tootStyle.tootAuthorNamePrimary}
|
||||
ref={(e: { innerHTML: string }) => {
|
||||
createRenderEffect(() => {
|
||||
e.innerHTML = resolveCustomEmoji(
|
||||
toot().account.displayName,
|
||||
toot().account.emojis,
|
||||
);
|
||||
});
|
||||
}}
|
||||
/>
|
||||
<time datetime={toot().createdAt}>
|
||||
{formatRelative(toot().createdAt, props.now)}
|
||||
</time>
|
||||
<span>
|
||||
@{toot().account.username}@{new URL(toot().account.url).hostname}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
const RegularToot: Component<TootCardProps> = (props) => {
|
||||
let rootRef: HTMLElement;
|
||||
const [managed, managedActionGroup, rest] = splitProps(
|
||||
props,
|
||||
["status", "lang", "class", "actionable", "evaluated"],
|
||||
["onRetoot", "onFavourite", "onBookmark", "onReply"],
|
||||
);
|
||||
const now = useTimeSource();
|
||||
const status = () => managed.status;
|
||||
const toot = () => status().reblog ?? status();
|
||||
|
||||
css`
|
||||
.reply-sep {
|
||||
margin-left: calc(var(--toot-avatar-size) + var(--card-pad) + 8px);
|
||||
margin-block: 8px;
|
||||
}
|
||||
`;
|
||||
|
||||
return (
|
||||
<>
|
||||
<section
|
||||
classList={{
|
||||
[tootStyle.toot]: true,
|
||||
[tootStyle.expanded]: managed.evaluated,
|
||||
[managed.class || ""]: true
|
||||
}}
|
||||
ref={rootRef!}
|
||||
lang={toot().language || managed.lang}
|
||||
{...rest}
|
||||
>
|
||||
<Show when={!!status().reblog}>
|
||||
<div class={tootStyle.tootRetootGrp}>
|
||||
<RetootIcon />
|
||||
<span>
|
||||
<Body2
|
||||
ref={(e: { innerHTML: string }) => {
|
||||
createRenderEffect(() => {
|
||||
e.innerHTML = resolveCustomEmoji(
|
||||
status().account.displayName,
|
||||
toot().emojis,
|
||||
);
|
||||
});
|
||||
}}
|
||||
></Body2>{" "}
|
||||
boosted
|
||||
</span>
|
||||
</div>
|
||||
</Show>
|
||||
<TootAuthorGroup status={toot()} now={now()} />
|
||||
<TootContentView
|
||||
source={toot().content}
|
||||
emojis={toot().emojis}
|
||||
class={tootStyle.tootContent}
|
||||
/>
|
||||
<Show when={toot().mediaAttachments.length > 0}>
|
||||
<MediaAttachmentGrid attachments={toot().mediaAttachments} />
|
||||
</Show>
|
||||
<Show when={managed.actionable}>
|
||||
<Divider
|
||||
class={cardStyle.cardNoPad}
|
||||
style={{ "margin-top": "8px" }}
|
||||
/>
|
||||
<TootActionGroup value={toot()} {...managedActionGroup} />
|
||||
</Show>
|
||||
</section>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default RegularToot;
|
Loading…
Add table
Add a link
Reference in a new issue