TootActionGroup: added share button (closes #18)

This commit is contained in:
thislight 2024-09-28 17:51:37 +08:00
parent 6506a60022
commit 161c72fea5
3 changed files with 129 additions and 6 deletions

View file

@ -1,13 +1,15 @@
//! This module has side effect. //! This module has side effect.
//! It recommended to include the module by <script> tag. //! It recommended to include the module by <script> tag.
if (!document.body.animate) { if (typeof document.body.animate === "undefined") {
// @ts-ignore: this file is polyfill, no exposed decls // @ts-ignore: this file is polyfill, no exposed decls
import("web-animations-js").then(() => { // all target platforms supported, prepared to remove import("web-animations-js").then(() => {
// all target platforms supported, prepared to remove
console.warn("web animation polyfill is included"); console.warn("web animation polyfill is included");
}); });
} }
if (!window.crypto.randomUUID) { // Chrome/Edge 92+ if (typeof window.crypto.randomUUID === "undefined") {
// Chrome/Edge 92+
// https://stackoverflow.com/a/2117523/2800218 // https://stackoverflow.com/a/2117523/2800218
// LICENSE: https://creativecommons.org/licenses/by-sa/4.0/legalcode // LICENSE: https://creativecommons.org/licenses/by-sa/4.0/legalcode
window.crypto.randomUUID = window.crypto.randomUUID =

104
src/platform/share.tsx Normal file
View file

@ -0,0 +1,104 @@
import {
createEffect,
createSignal,
onCleanup,
type Component,
} from "solid-js";
import Scaffold from "../material/Scaffold";
import BottomSheet from "../material/BottomSheet";
import {
Button,
IconButton,
Input,
ThemeProvider,
Toolbar,
} from "@suid/material";
import { Close as CloseIcon, ContentCopy } from "@suid/icons-material";
import { Title } from "../material/typography";
import { render } from "solid-js/web";
import { useRootTheme } from "../material/mui";
const ShareBottomSheet: Component<{
data?: ShareData;
open?: boolean;
onClose: () => void;
}> = (props) => {
return (
<BottomSheet open={props.open} onClose={props.onClose} bottomUp>
<Scaffold
topbar={
<Toolbar>
<IconButton onClick={props.onClose}>
<CloseIcon />
</IconButton>
<Title>Share...</Title>
</Toolbar>
}
>
<div
style={{
"padding": "8px 8px calc(var(--safe-area-inset-bottom, 0px) + 40px)",
display: "flex",
}}
>
<Input
value={props.data?.url}
onChange={() => undefined}
fullWidth
/>
<Button
onClick={() => {
navigator.clipboard.writeText(props.data?.url ?? "");
}}
>
<ContentCopy sx={{ marginTop: "-.25em", marginRight: ".25em" }} />
Copy
</Button>
</div>
</Scaffold>
</BottomSheet>
);
};
export function canShare(data?: ShareData) {
if (navigator.canShare) {
return navigator.canShare(data);
}
return !!data?.url;
}
export async function share(data?: ShareData): Promise<void> {
if (navigator.share) {
return await navigator.share(data);
}
return new Promise((resolve) => {
const element = document.createElement("div");
document.body.appendChild(element);
const dispose = render(() => {
const [open, setOpen] = createSignal(true);
const theme = useRootTheme();
onCleanup(() => {
element.remove();
resolve();
});
createEffect(() => {
if (!open()) {
dispose();
}
});
return (
<ThemeProvider theme={theme()}>
<ShareBottomSheet
data={data}
open={open()}
onClose={() => setOpen(false)}
/>
</ThemeProvider>
);
}, element);
});
}

View file

@ -27,16 +27,18 @@ import {
StarOutline, StarOutline,
Bookmark, Bookmark,
Reply, Reply,
Share,
} from "@suid/icons-material"; } from "@suid/icons-material";
import { useTimeSource } from "../platform/timesrc.js"; import { useTimeSource } from "../platform/timesrc.js";
import { resolveCustomEmoji } from "../masto/toot.js"; import { resolveCustomEmoji } from "../masto/toot.js";
import { Divider } from "@suid/material"; import { Divider, IconButton } from "@suid/material";
import cardStyle from "../material/cards.module.css"; import cardStyle from "../material/cards.module.css";
import Button from "../material/Button.js"; import Button from "../material/Button.js";
import MediaAttachmentGrid from "./MediaAttachmentGrid.js"; import MediaAttachmentGrid from "./MediaAttachmentGrid.js";
import { FastAverageColor } from "fast-average-color"; import { FastAverageColor } from "fast-average-color";
import Color from "colorjs.io"; import Color from "colorjs.io";
import { useDateFnLocale } from "../platform/i18n"; import { useDateFnLocale } from "../platform/i18n";
import { canShare, share } from "../platform/share";
type TootContentViewProps = { type TootContentViewProps = {
source?: string; source?: string;
@ -165,13 +167,26 @@ function TootActionGroup<T extends mastodon.v1.Status>(
> >
{toot().bookmarked ? <Bookmark /> : <BookmarkAddOutlined />} {toot().bookmarked ? <Bookmark /> : <BookmarkAddOutlined />}
</Button> </Button>
<Show when={canShare({ url: toot().url ?? undefined })}>
<Button
class={tootStyle.tootAction}
aria-label="Share"
onClick={async () => {
await share({
url: toot().url ?? undefined,
});
}}
>
<Share />
</Button>
</Show>
</div> </div>
); );
} }
function TootAuthorGroup(props: { status: mastodon.v1.Status; now: Date }) { function TootAuthorGroup(props: { status: mastodon.v1.Status; now: Date }) {
const toot = () => props.status; const toot = () => props.status;
const dateFnLocale = useDateFnLocale() const dateFnLocale = useDateFnLocale();
return ( return (
<div class={tootStyle.tootAuthorGrp}> <div class={tootStyle.tootAuthorGrp}>
@ -189,7 +204,9 @@ function TootAuthorGroup(props: { status: mastodon.v1.Status; now: Date }) {
}} }}
/> />
<time datetime={toot().createdAt}> <time datetime={toot().createdAt}>
{formatRelative(toot().createdAt, props.now, {locale: dateFnLocale()})} {formatRelative(toot().createdAt, props.now, {
locale: dateFnLocale(),
})}
</time> </time>
<span> <span>
@{toot().account.username}@{new URL(toot().account.url).hostname} @{toot().account.username}@{new URL(toot().account.url).hostname}