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,
 | 
			
		||||
  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<T extends mastodon.v1.Status> = {
 | 
			
		||||
  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`.
 | 
			
		||||
 */
 | 
			
		||||
| 
						 | 
				
			
			@ -420,7 +328,7 @@ const RegularToot: Component<TootCardProps> = (props) => {
 | 
			
		|||
          class={tootStyle.tootContent}
 | 
			
		||||
        />
 | 
			
		||||
        <Show when={toot().card}>
 | 
			
		||||
          <TootPreviewCard src={toot().card!} />
 | 
			
		||||
          <PreviewCard src={toot().card!} />
 | 
			
		||||
        </Show>
 | 
			
		||||
        <Show when={toot().mediaAttachments.length > 0}>
 | 
			
		||||
          <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…
	
	Add table
		Add a link
		
	
		Reference in a new issue