Compare commits
No commits in common. "4d9c2b3aa8f4f25342afcd10809965b937e686eb" and "2aa2cf21da3ed75a6b6e998cee2dcc89cf179761" have entirely different histories.
4d9c2b3aa8
...
2aa2cf21da
8 changed files with 144 additions and 202 deletions
BIN
bun.lockb
BIN
bun.lockb
Binary file not shown.
|
@ -17,16 +17,16 @@
|
|||
"@suid/vite-plugin": "^0.3.1",
|
||||
"@types/hammerjs": "^2.0.46",
|
||||
"@vite-pwa/assets-generator": "^0.2.6",
|
||||
"postcss": "^8.4.49",
|
||||
"postcss": "^8.4.47",
|
||||
"prettier": "^3.3.3",
|
||||
"typescript": "^5.6.3",
|
||||
"vite": "^5.4.11",
|
||||
"vite": "^5.4.10",
|
||||
"vite-plugin-package-version": "^1.1.0",
|
||||
"vite-plugin-pwa": "^0.20.5",
|
||||
"vite-plugin-solid": "^2.10.2",
|
||||
"vite-plugin-solid-styled": "^0.11.1",
|
||||
"workbox-build": "^7.3.0",
|
||||
"wrangler": "^3.86.1"
|
||||
"wrangler": "^3.84.1"
|
||||
},
|
||||
"dependencies": {
|
||||
"@formatjs/intl-localematcher": "^0.5.7",
|
||||
|
@ -38,7 +38,7 @@
|
|||
"@solid-primitives/map": "^0.4.13",
|
||||
"@solid-primitives/page-visibility": "^2.0.17",
|
||||
"@solid-primitives/resize-observer": "^2.0.26",
|
||||
"@solidjs/router": "^0.15.1",
|
||||
"@solidjs/router": "^0.14.10",
|
||||
"@suid/icons-material": "^0.8.1",
|
||||
"@suid/material": "^0.18.0",
|
||||
"blurhash": "^2.0.5",
|
||||
|
|
|
@ -1,27 +0,0 @@
|
|||
.MediaAttachmentGrid {
|
||||
/* Note: MeidaAttachmentGrid has hard-coded layout calcalation */
|
||||
margin-top: 1em;
|
||||
margin-left: calc(var(--card-pad, 0) + var(--toot-avatar-size, 0) + 8px);
|
||||
margin-right: var(--card-pad, 0);
|
||||
gap: 4px;
|
||||
|
||||
> :where(img, video) {
|
||||
max-height: 35vh;
|
||||
min-height: 40px;
|
||||
min-width: 40px;
|
||||
object-fit: contain;
|
||||
max-width: 100%;
|
||||
background-color: var(--tutu-color-surface-d);
|
||||
border-radius: 2px;
|
||||
border: 1px solid var(--tutu-color-surface-d);
|
||||
transition: outline-width 60ms var(--tutu-anim-curve-std), border-color 60ms var(--tutu-anim-curve-std);
|
||||
contain: strict;
|
||||
content-visibility: auto;
|
||||
|
||||
&:hover,
|
||||
&:focus-visible {
|
||||
outline: 8px solid var(--media-color-accent, var(--tutu-color-surface-d));
|
||||
border-color: var(--media-color-accent, var(--tutu-color-surface-d));
|
||||
}
|
||||
}
|
||||
}
|
|
@ -10,6 +10,8 @@ import {
|
|||
createSignal,
|
||||
onCleanup,
|
||||
} from "solid-js";
|
||||
import { css } from "solid-styled";
|
||||
import tootStyle from "./toot.module.css";
|
||||
import MediaViewer from "./MediaViewer";
|
||||
import { render } from "solid-js/web";
|
||||
import {
|
||||
|
@ -19,8 +21,6 @@ import {
|
|||
import { useStore } from "@nanostores/solid";
|
||||
import { $settings } from "../settings/stores";
|
||||
import { averageColorHex } from "../platform/blurhash";
|
||||
import "./MediaAttachmentGrid.css";
|
||||
import cardStyle from "../material/cards.module.css";
|
||||
|
||||
type ElementSize = { width: number; height: number };
|
||||
|
||||
|
@ -114,6 +114,13 @@ const MediaAttachmentGrid: Component<{
|
|||
itemMaxSize(),
|
||||
);
|
||||
|
||||
// I don't know why mastodon does not return this
|
||||
// and the condition for it to return this.
|
||||
// Anyway, It is useless now.
|
||||
// My hope is the FastAverageColor, but
|
||||
// we may need better tool to manage the performance impact
|
||||
// before using this. See #37.
|
||||
// TODO: use fast average color to extract accent color
|
||||
const accentColor =
|
||||
item.meta?.colors?.accent ??
|
||||
(item.blurhash ? averageColorHex(item.blurhash) : undefined);
|
||||
|
@ -127,11 +134,16 @@ const MediaAttachmentGrid: Component<{
|
|||
accentColor ? { "--media-color-accent": accentColor } : {},
|
||||
);
|
||||
};
|
||||
|
||||
css`
|
||||
.attachments {
|
||||
column-count: ${columnCount().toString()};
|
||||
}
|
||||
`;
|
||||
return (
|
||||
<section
|
||||
ref={setRootRef}
|
||||
class={`MediaAttachmentGrid ${cardStyle.cardNoPad}`}
|
||||
style={{ "column-count": columnCount() }}
|
||||
class={[tootStyle.tootAttachmentGrp, "attachments"].join(" ")}
|
||||
onClick={(e) => {
|
||||
if (e.target !== e.currentTarget) {
|
||||
e.stopImmediatePropagation();
|
||||
|
|
|
@ -5,11 +5,13 @@ import {
|
|||
type JSX,
|
||||
Show,
|
||||
createRenderEffect,
|
||||
createEffect,
|
||||
createMemo,
|
||||
} 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 { Body1, Body2, Title } from "../material/typography.js";
|
||||
import { css } from "solid-styled";
|
||||
import {
|
||||
BookmarkAddOutlined,
|
||||
|
@ -28,12 +30,13 @@ import { Divider } from "@suid/material";
|
|||
import cardStyle from "../material/cards.module.css";
|
||||
import Button from "../material/Button.js";
|
||||
import MediaAttachmentGrid from "./MediaAttachmentGrid.js";
|
||||
import Color from "colorjs.io";
|
||||
import { useDateFnLocale } from "../platform/i18n";
|
||||
import { canShare, share } from "../platform/share";
|
||||
import { makeAcctText, useDefaultSession } from "../masto/clients";
|
||||
import TootContent from "./toot-components/TootContent";
|
||||
import BoostIcon from "./toot-components/BoostIcon";
|
||||
import PreviewCard from "./toot-components/PreviewCard";
|
||||
import { averageColorHex } from "../platform/blurhash";
|
||||
|
||||
type TootActionGroupProps<T extends mastodon.v1.Status> = {
|
||||
onRetoot?: (value: T) => void;
|
||||
|
@ -183,6 +186,95 @@ 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`.
|
||||
*/
|
||||
|
@ -328,7 +420,7 @@ const RegularToot: Component<TootCardProps> = (props) => {
|
|||
class={tootStyle.tootContent}
|
||||
/>
|
||||
<Show when={toot().card}>
|
||||
<PreviewCard src={toot().card!} />
|
||||
<TootPreviewCard src={toot().card!} />
|
||||
</Show>
|
||||
<Show when={toot().mediaAttachments.length > 0}>
|
||||
<MediaAttachmentGrid attachments={toot().mediaAttachments} />
|
||||
|
|
|
@ -1,67 +0,0 @@
|
|||
.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;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,97 +0,0 @@
|
|||
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;
|
|
@ -238,6 +238,35 @@
|
|||
}
|
||||
}
|
||||
|
||||
.tootAttachmentGrp {
|
||||
/* Note: MeidaAttachmentGrid has hard-coded layout calcalation */
|
||||
composes: cardNoPad from "../material/cards.module.css";
|
||||
margin-top: 1em;
|
||||
margin-left: calc(var(--card-pad, 0) + var(--toot-avatar-size, 0) + 8px);
|
||||
margin-right: var(--card-pad, 0);
|
||||
gap: 4px;
|
||||
|
||||
> :where(img, video) {
|
||||
max-height: 35vh;
|
||||
min-height: 40px;
|
||||
min-width: 40px;
|
||||
object-fit: contain;
|
||||
max-width: 100%;
|
||||
background-color: var(--tutu-color-surface-d);
|
||||
border-radius: 2px;
|
||||
border: 1px solid var(--tutu-color-surface-d);
|
||||
transition: outline-width 60ms var(--tutu-anim-curve-std), border-color 60ms var(--tutu-anim-curve-std);
|
||||
contain: strict;
|
||||
content-visibility: auto;
|
||||
|
||||
&:hover,
|
||||
&:focus-visible {
|
||||
outline: 8px solid var(--media-color-accent, var(--tutu-color-surface-d));
|
||||
border: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.tootBottomActionGrp {
|
||||
composes: cardGutSkip from "../material/cards.module.css";
|
||||
padding-block: calc((var(--card-gut) - 10px) / 2);
|
||||
|
|
Loading…
Reference in a new issue