This commit is contained in:
parent
3cacf64c8e
commit
4d9c2b3aa8
3 changed files with 167 additions and 95 deletions
|
@ -5,13 +5,11 @@ import {
|
||||||
type JSX,
|
type JSX,
|
||||||
Show,
|
Show,
|
||||||
createRenderEffect,
|
createRenderEffect,
|
||||||
createEffect,
|
|
||||||
createMemo,
|
|
||||||
} from "solid-js";
|
} from "solid-js";
|
||||||
import tootStyle from "./toot.module.css";
|
import tootStyle from "./toot.module.css";
|
||||||
import { formatRelative } from "date-fns";
|
import { formatRelative } from "date-fns";
|
||||||
import Img from "../material/Img.js";
|
import Img from "../material/Img.js";
|
||||||
import { Body1, Body2, Title } from "../material/typography.js";
|
import { Body2 } from "../material/typography.js";
|
||||||
import { css } from "solid-styled";
|
import { css } from "solid-styled";
|
||||||
import {
|
import {
|
||||||
BookmarkAddOutlined,
|
BookmarkAddOutlined,
|
||||||
|
@ -30,13 +28,12 @@ import { Divider } 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 Color from "colorjs.io";
|
|
||||||
import { useDateFnLocale } from "../platform/i18n";
|
import { useDateFnLocale } from "../platform/i18n";
|
||||||
import { canShare, share } from "../platform/share";
|
import { canShare, share } from "../platform/share";
|
||||||
import { makeAcctText, useDefaultSession } from "../masto/clients";
|
import { makeAcctText, useDefaultSession } from "../masto/clients";
|
||||||
import TootContent from "./toot-components/TootContent";
|
import TootContent from "./toot-components/TootContent";
|
||||||
import BoostIcon from "./toot-components/BoostIcon";
|
import BoostIcon from "./toot-components/BoostIcon";
|
||||||
import { averageColorHex } from "../platform/blurhash";
|
import PreviewCard from "./toot-components/PreviewCard";
|
||||||
|
|
||||||
type TootActionGroupProps<T extends mastodon.v1.Status> = {
|
type TootActionGroupProps<T extends mastodon.v1.Status> = {
|
||||||
onRetoot?: (value: T) => void;
|
onRetoot?: (value: T) => void;
|
||||||
|
@ -186,95 +183,6 @@ function TootAuthorGroup(
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function TootPreviewCard(props: {
|
|
||||||
src: mastodon.v1.PreviewCard;
|
|
||||||
alwaysCompact?: boolean;
|
|
||||||
}) {
|
|
||||||
let root: HTMLAnchorElement;
|
|
||||||
|
|
||||||
createEffect(() => {
|
|
||||||
if (props.alwaysCompact) {
|
|
||||||
root.classList.add(tootStyle.compact);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (!props.src.width) return;
|
|
||||||
const width = root.getBoundingClientRect().width;
|
|
||||||
if (width > props.src.width) {
|
|
||||||
root.classList.add(tootStyle.compact);
|
|
||||||
} else {
|
|
||||||
root.classList.remove(tootStyle.compact);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
const imgAverageColor = createMemo(() => {
|
|
||||||
if (!props.src.image) return;
|
|
||||||
return new Color(averageColorHex(props.src.blurhash));
|
|
||||||
});
|
|
||||||
|
|
||||||
const prefersWhiteText = createMemo(() => {
|
|
||||||
const oc = imgAverageColor();
|
|
||||||
if (!oc) return;
|
|
||||||
const colorWhite = new Color("white");
|
|
||||||
|
|
||||||
return colorWhite.luminance / oc.luminance > 3.5;
|
|
||||||
});
|
|
||||||
|
|
||||||
const focusSurfaceColor = createMemo(() => {
|
|
||||||
const oc = imgAverageColor();
|
|
||||||
if (!oc) return;
|
|
||||||
if (prefersWhiteText()) {
|
|
||||||
return new Color(oc).darken(0.2);
|
|
||||||
} else {
|
|
||||||
return new Color(oc).lighten(0.2);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
const textColorName = createMemo(() => {
|
|
||||||
const useWhiteText = prefersWhiteText();
|
|
||||||
if (typeof useWhiteText === "undefined") {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
return useWhiteText ? "white" : "black";
|
|
||||||
});
|
|
||||||
|
|
||||||
const secondaryTextColor = createMemo(() => {
|
|
||||||
const tcn = textColorName();
|
|
||||||
if (!tcn) return;
|
|
||||||
const tc = new Color(tcn);
|
|
||||||
tc.alpha = 0.75;
|
|
||||||
return tc;
|
|
||||||
});
|
|
||||||
|
|
||||||
return (
|
|
||||||
<a
|
|
||||||
ref={root!}
|
|
||||||
class={tootStyle.previewCard}
|
|
||||||
href={props.src.url}
|
|
||||||
target="_blank"
|
|
||||||
referrerPolicy="unsafe-url"
|
|
||||||
style={{
|
|
||||||
"--tutu-color-surface": imgAverageColor()?.toString(),
|
|
||||||
"--tutu-color-surface-d": focusSurfaceColor()?.toString(),
|
|
||||||
"--tutu-color-on-surface": textColorName(),
|
|
||||||
"--tutu-color-secondary-text-on-surface":
|
|
||||||
secondaryTextColor()?.toString(),
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<Show when={props.src.image}>
|
|
||||||
<img
|
|
||||||
crossOrigin="anonymous"
|
|
||||||
src={props.src.image!}
|
|
||||||
width={props.src.width || undefined}
|
|
||||||
height={props.src.height || undefined}
|
|
||||||
loading="lazy"
|
|
||||||
/>
|
|
||||||
</Show>
|
|
||||||
<Title component="h1">{props.src.title}</Title>
|
|
||||||
<Body1 component="p">{props.src.description}</Body1>
|
|
||||||
</a>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* find bottom-to-top the element with `data-action`.
|
* find bottom-to-top the element with `data-action`.
|
||||||
*/
|
*/
|
||||||
|
@ -420,7 +328,7 @@ const RegularToot: Component<TootCardProps> = (props) => {
|
||||||
class={tootStyle.tootContent}
|
class={tootStyle.tootContent}
|
||||||
/>
|
/>
|
||||||
<Show when={toot().card}>
|
<Show when={toot().card}>
|
||||||
<TootPreviewCard src={toot().card!} />
|
<PreviewCard src={toot().card!} />
|
||||||
</Show>
|
</Show>
|
||||||
<Show when={toot().mediaAttachments.length > 0}>
|
<Show when={toot().mediaAttachments.length > 0}>
|
||||||
<MediaAttachmentGrid attachments={toot().mediaAttachments} />
|
<MediaAttachmentGrid attachments={toot().mediaAttachments} />
|
||||||
|
|
67
src/timelines/toot-components/PreviewCard.css
Normal file
67
src/timelines/toot-components/PreviewCard.css
Normal file
|
@ -0,0 +1,67 @@
|
||||||
|
.PreviewCard {
|
||||||
|
display: block;
|
||||||
|
border: 1px solid #eeeeee;
|
||||||
|
background-color: var(--tutu-color-surface);
|
||||||
|
text-decoration: none;
|
||||||
|
border-radius: 4px;
|
||||||
|
overflow: hidden;
|
||||||
|
margin-top: 1em;
|
||||||
|
margin-bottom: 1.5em;
|
||||||
|
color: var(--tutu-color-secondary-text-on-surface);
|
||||||
|
transition: color 220ms var(--tutu-anim-curve-std), background-color 220ms var(--tutu-anim-curve-std);
|
||||||
|
padding-bottom: 8px;
|
||||||
|
overflow: hidden;
|
||||||
|
z-index: 1;
|
||||||
|
position: relative;
|
||||||
|
|
||||||
|
>img {
|
||||||
|
background-color: #eeeeee;
|
||||||
|
max-width: 100%;
|
||||||
|
height: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:hover,
|
||||||
|
&:focus-visible {
|
||||||
|
background-color: var(--tutu-color-surface-d);
|
||||||
|
color: var(--tutu-color-on-surface);
|
||||||
|
|
||||||
|
>h1 {
|
||||||
|
text-decoration: underline;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
>h1 {
|
||||||
|
color: var(--tutu-color-on-surface);
|
||||||
|
max-height: calc(4 * var(--title-line-height) * var(--title-size));
|
||||||
|
}
|
||||||
|
|
||||||
|
>p {
|
||||||
|
max-height: calc(8 * var(--body-line-height) * var(--body-size));
|
||||||
|
}
|
||||||
|
|
||||||
|
>h1,
|
||||||
|
>p {
|
||||||
|
margin-left: 16px;
|
||||||
|
margin-right: 16px;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.compact {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: minmax(10%, 30%) 1fr;
|
||||||
|
padding-left: 16px;
|
||||||
|
padding-right: 16px;
|
||||||
|
padding-top: 8px;
|
||||||
|
|
||||||
|
>img:first-child {
|
||||||
|
grid-row: 1 / 3;
|
||||||
|
object-fit: contain;
|
||||||
|
}
|
||||||
|
|
||||||
|
>h1,
|
||||||
|
>p {
|
||||||
|
margin-right: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
97
src/timelines/toot-components/PreviewCard.tsx
Normal file
97
src/timelines/toot-components/PreviewCard.tsx
Normal file
|
@ -0,0 +1,97 @@
|
||||||
|
import Color from "colorjs.io";
|
||||||
|
import type { mastodon } from "masto";
|
||||||
|
import { createEffect, createMemo, Show } from "solid-js";
|
||||||
|
import { Title, Body1 } from "../../material/typography";
|
||||||
|
import { averageColorHex } from "../../platform/blurhash";
|
||||||
|
import "./PreviewCard.css";
|
||||||
|
|
||||||
|
export function PreviewCard(props: {
|
||||||
|
src: mastodon.v1.PreviewCard;
|
||||||
|
alwaysCompact?: boolean;
|
||||||
|
}) {
|
||||||
|
let root: HTMLAnchorElement;
|
||||||
|
|
||||||
|
createEffect(() => {
|
||||||
|
if (props.alwaysCompact) {
|
||||||
|
root.classList.add("compact");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!props.src.width) return;
|
||||||
|
const width = root.getBoundingClientRect().width;
|
||||||
|
if (width > props.src.width) {
|
||||||
|
root.classList.add("compact");
|
||||||
|
} else {
|
||||||
|
root.classList.remove("compact");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const imgAverageColor = createMemo(() => {
|
||||||
|
if (!props.src.image) return;
|
||||||
|
return new Color(averageColorHex(props.src.blurhash));
|
||||||
|
});
|
||||||
|
|
||||||
|
const prefersWhiteText = createMemo(() => {
|
||||||
|
const oc = imgAverageColor();
|
||||||
|
if (!oc) return;
|
||||||
|
const colorWhite = new Color("white");
|
||||||
|
|
||||||
|
return colorWhite.luminance / oc.luminance > 3.5;
|
||||||
|
});
|
||||||
|
|
||||||
|
const focusSurfaceColor = createMemo(() => {
|
||||||
|
const oc = imgAverageColor();
|
||||||
|
if (!oc) return;
|
||||||
|
if (prefersWhiteText()) {
|
||||||
|
return new Color(oc).darken(0.2);
|
||||||
|
} else {
|
||||||
|
return new Color(oc).lighten(0.2);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const textColorName = createMemo(() => {
|
||||||
|
const useWhiteText = prefersWhiteText();
|
||||||
|
if (typeof useWhiteText === "undefined") {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
return useWhiteText ? "white" : "black";
|
||||||
|
});
|
||||||
|
|
||||||
|
const secondaryTextColor = createMemo(() => {
|
||||||
|
const tcn = textColorName();
|
||||||
|
if (!tcn) return;
|
||||||
|
const tc = new Color(tcn);
|
||||||
|
tc.alpha = 0.75;
|
||||||
|
return tc;
|
||||||
|
});
|
||||||
|
|
||||||
|
return (
|
||||||
|
<a
|
||||||
|
ref={root!}
|
||||||
|
class={"PreviewCard"}
|
||||||
|
href={props.src.url}
|
||||||
|
target="_blank"
|
||||||
|
referrerPolicy="unsafe-url"
|
||||||
|
style={{
|
||||||
|
"--tutu-color-surface": imgAverageColor()?.toString(),
|
||||||
|
"--tutu-color-surface-d": focusSurfaceColor()?.toString(),
|
||||||
|
"--tutu-color-on-surface": textColorName(),
|
||||||
|
"--tutu-color-secondary-text-on-surface":
|
||||||
|
secondaryTextColor()?.toString(),
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Show when={props.src.image}>
|
||||||
|
<img
|
||||||
|
crossOrigin="anonymous"
|
||||||
|
src={props.src.image!}
|
||||||
|
width={props.src.width || undefined}
|
||||||
|
height={props.src.height || undefined}
|
||||||
|
loading="lazy"
|
||||||
|
/>
|
||||||
|
</Show>
|
||||||
|
<Title component="h1">{props.src.title}</Title>
|
||||||
|
<Body1 component="p">{props.src.description}</Body1>
|
||||||
|
</a>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default PreviewCard;
|
Loading…
Reference in a new issue