diff --git a/bun.lockb b/bun.lockb index bab2499..8acbc1a 100755 Binary files a/bun.lockb and b/bun.lockb differ diff --git a/package.json b/package.json index 6e1fdb9..70973f6 100644 --- a/package.json +++ b/package.json @@ -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.47", + "postcss": "^8.4.49", "prettier": "^3.3.3", "typescript": "^5.6.3", - "vite": "^5.4.10", + "vite": "^5.4.11", "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.84.1" + "wrangler": "^3.86.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.14.10", + "@solidjs/router": "^0.15.1", "@suid/icons-material": "^0.8.1", "@suid/material": "^0.18.0", "blurhash": "^2.0.5", diff --git a/src/timelines/MediaAttachmentGrid.css b/src/timelines/MediaAttachmentGrid.css new file mode 100644 index 0000000..6b659c4 --- /dev/null +++ b/src/timelines/MediaAttachmentGrid.css @@ -0,0 +1,27 @@ +.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)); + } + } +} \ No newline at end of file diff --git a/src/timelines/MediaAttachmentGrid.tsx b/src/timelines/MediaAttachmentGrid.tsx index 3f7147c..2b9db57 100644 --- a/src/timelines/MediaAttachmentGrid.tsx +++ b/src/timelines/MediaAttachmentGrid.tsx @@ -10,8 +10,6 @@ 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 { @@ -21,6 +19,8 @@ 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,13 +114,6 @@ 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); @@ -134,16 +127,11 @@ const MediaAttachmentGrid: Component<{ accentColor ? { "--media-color-accent": accentColor } : {}, ); }; - - css` - .attachments { - column-count: ${columnCount().toString()}; - } - `; return (
{ if (e.target !== e.currentTarget) { e.stopImmediatePropagation(); diff --git a/src/timelines/RegularToot.tsx b/src/timelines/RegularToot.tsx index ffcae6a..f72e4fc 100644 --- a/src/timelines/RegularToot.tsx +++ b/src/timelines/RegularToot.tsx @@ -5,13 +5,11 @@ 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 { Body1, Body2, Title } from "../material/typography.js"; +import { Body2 } from "../material/typography.js"; import { css } from "solid-styled"; import { BookmarkAddOutlined, @@ -30,13 +28,12 @@ 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 { averageColorHex } from "../platform/blurhash"; +import PreviewCard from "./toot-components/PreviewCard"; type TootActionGroupProps = { 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 ( - - - - - {props.src.title} - {props.src.description} - - ); -} - /** * find bottom-to-top the element with `data-action`. */ @@ -420,7 +328,7 @@ const RegularToot: Component = (props) => { class={tootStyle.tootContent} /> - + 0}> diff --git a/src/timelines/toot-components/PreviewCard.css b/src/timelines/toot-components/PreviewCard.css new file mode 100644 index 0000000..17d9f2b --- /dev/null +++ b/src/timelines/toot-components/PreviewCard.css @@ -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; + } + } +} diff --git a/src/timelines/toot-components/PreviewCard.tsx b/src/timelines/toot-components/PreviewCard.tsx new file mode 100644 index 0000000..ed20eb8 --- /dev/null +++ b/src/timelines/toot-components/PreviewCard.tsx @@ -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 ( + + + + + {props.src.title} + {props.src.description} + + ); +} + +export default PreviewCard; diff --git a/src/timelines/toot.module.css b/src/timelines/toot.module.css index cb3c70d..ecd74a1 100644 --- a/src/timelines/toot.module.css +++ b/src/timelines/toot.module.css @@ -238,35 +238,6 @@ } } -.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);