Skip to content
Merged
Show file tree
Hide file tree
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion components/common/OverlappingAvatars.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import type { MouseEvent } from "react";
import { useId } from "react";
import { Tooltip } from "react-tooltip";

export interface OverlappingAvatarItem {
interface OverlappingAvatarItem {
readonly key: string;
readonly pfpUrl: string | null;
readonly href?: string;
Expand Down
58 changes: 38 additions & 20 deletions components/drops/view/DropsList.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import Drop, { DropLocation } from "@/components/waves/drops/Drop";
import LightDrop from "@/components/waves/drops/LightDrop";
import VirtualScrollWrapper from "@/components/waves/drops/VirtualScrollWrapper";
import { TweetPreviewModeProvider } from "@/components/tweets/TweetPreviewModeContext";
import type { ApiDrop } from "@/generated/models/ApiDrop";
import type {
Drop as DropType,
Expand Down Expand Up @@ -46,6 +47,7 @@ interface DropsListProps {
readonly unreadDividerSerialNo?: number | null | undefined;
readonly boostedDrops?: ApiDrop[] | undefined;
readonly onBoostedDropClick?: ((serialNo: number) => void) | undefined;
readonly autoCollapseSerials?: ReadonlySet<number> | undefined;
}

const MemoizedDrop = memo(Drop);
Expand All @@ -70,6 +72,7 @@ const DropsList = memo(
unreadDividerSerialNo,
boostedDrops,
onBoostedDropClick,
autoCollapseSerials,
}: DropsListProps) => {
const handleReply = useCallback<DropActionHandler>(
({ drop, partId }) => onReply({ drop, partId }),
Expand Down Expand Up @@ -218,6 +221,39 @@ const DropsList = memo(
const boostCard = renderBoostCard(i);
const unreadDivider = renderUnreadDivider(drop.serial_no);

const dropContent =
drop.type === DropSize.FULL ? (
<MemoizedDrop
dropViewDropId={getItemData.dropViewDropId}
onReplyClick={getItemData.handleReplyClick}
drop={drop}
previousDrop={previousDrop}
nextDrop={nextDrop}
showWaveInfo={getItemData.showWaveInfo}
activeDrop={getItemData.activeDrop}
onReply={getItemData.handleReply}
onQuote={getItemData.handleQuote}
location={location}
showReplyAndQuote={getItemData.showReplyAndQuote}
onQuoteClick={getItemData.onQuoteClick}
parentContainerRef={getItemData.parentContainerRef}
onDropContentClick={getItemData.onDropContentClick}
/>
) : (
<MemoizedLightDrop drop={drop} />
);

const dropContentWithPreviewMode =
drop.type === DropSize.FULL && autoCollapseSerials ? (
<TweetPreviewModeProvider
mode={autoCollapseSerials.has(drop.serial_no) ? "auto" : "never"}
>
{dropContent}
</TweetPreviewModeProvider>
) : (
dropContent
);

const dropElement = (
<HighlightDropWrapper
key={drop.stableKey}
Expand All @@ -242,26 +278,7 @@ const DropsList = memo(
waveId={drop.type === DropSize.FULL ? drop.wave.id : drop.waveId}
type={drop.type}
>
{drop.type === DropSize.FULL ? (
<MemoizedDrop
dropViewDropId={getItemData.dropViewDropId}
onReplyClick={getItemData.handleReplyClick}
drop={drop}
previousDrop={previousDrop}
nextDrop={nextDrop}
showWaveInfo={getItemData.showWaveInfo}
activeDrop={getItemData.activeDrop}
onReply={getItemData.handleReply}
onQuote={getItemData.handleQuote}
location={location}
showReplyAndQuote={getItemData.showReplyAndQuote}
onQuoteClick={getItemData.onQuoteClick}
parentContainerRef={getItemData.parentContainerRef}
onDropContentClick={getItemData.onDropContentClick}
/>
) : (
<MemoizedLightDrop drop={drop} />
)}
{dropContentWithPreviewMode}
</VirtualScrollWrapper>
</HighlightDropWrapper>
);
Expand All @@ -274,6 +291,7 @@ const DropsList = memo(
location,
renderBoostCard,
renderUnreadDivider,
autoCollapseSerials,
]);
}
);
Expand Down
5 changes: 4 additions & 1 deletion components/drops/view/part/DropPartMarkdown.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import type { ApiDrop } from "@/generated/models/ApiDrop";
import type { ApiDropMentionedUser } from "@/generated/models/ApiDropMentionedUser";
import type { ApiDropReferencedNFT } from "@/generated/models/ApiDropReferencedNFT";
import useIsMobileScreen from "@/hooks/isMobileScreen";
import { useTweetPreviewMode } from "@/components/tweets/TweetPreviewModeContext";

import {
DropContentPartType,
Expand Down Expand Up @@ -240,6 +241,7 @@ function DropPartMarkdown({
}: DropPartMarkdownProps) {
const isMobile = useIsMobileScreen();
const { emojiMap, findNativeEmoji } = useEmoji();
const tweetPreviewMode = useTweetPreviewMode();

const textSizeClass = useMemo(() => {
switch (textSize) {
Expand All @@ -256,8 +258,9 @@ function DropPartMarkdown({
onQuoteClick,
currentDropId,
hideLinkPreviews,
tweetPreviewMode,
}),
[onQuoteClick, currentDropId, hideLinkPreviews]
[onQuoteClick, currentDropId, hideLinkPreviews, tweetPreviewMode]
);

const { customRenderer, renderParagraph, processContent } = useMemo(
Expand Down
7 changes: 5 additions & 2 deletions components/drops/view/part/dropPartMarkdown/handlers/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,14 +10,17 @@ import { createTikTokHandler } from "./tiktok";
import { createTwitterHandler } from "./twitter";
import { createWikimediaHandler } from "./wikimedia";
import { createYoutubeHandler } from "./youtube";
import type { TweetPreviewMode } from "@/components/tweets/TweetPreviewModeContext";

export const createLinkHandlers = (): LinkHandler[] => [
export const createLinkHandlers = (options?: {
readonly tweetPreviewMode?: TweetPreviewMode;
}): LinkHandler[] => [
createYoutubeHandler(),
createTikTokHandler(),
createGoogleWorkspaceHandler(),
createEnsHandler(),
createCompoundHandler(),
createTwitterHandler(),
createTwitterHandler(options),
createWikimediaHandler(),
createGifHandler(),
createArtBlocksHandler(),
Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,12 @@
import { renderTweetEmbed } from "../renderers";
import { isTwitterLink } from "../twitter";
import type { LinkHandler } from "../linkTypes";
import type { TweetPreviewMode } from "@/components/tweets/TweetPreviewModeContext";

export const createTwitterHandler = (): LinkHandler => ({
export const createTwitterHandler = (options?: {
readonly tweetPreviewMode?: TweetPreviewMode;
}): LinkHandler => ({
match: isTwitterLink,
render: (href) => renderTweetEmbed(href),
render: (href) => renderTweetEmbed(href, options),
display: "block",
});
7 changes: 6 additions & 1 deletion components/drops/view/part/dropPartMarkdown/linkHandlers.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import { ensureStableSeizeLink } from "@/helpers/SeizeLinkParser";

import LinkPreviewCard from "@/components/waves/LinkPreviewCard";
import DropPartMarkdownImage from "../DropPartMarkdownImage";
import type { TweetPreviewMode } from "@/components/tweets/TweetPreviewModeContext";

import { createLinkHandlers, createSeizeHandlers } from "./handlers";
import type { LinkHandler } from "./linkTypes";
Expand All @@ -26,6 +27,7 @@ interface LinkRendererConfig {
readonly onQuoteClick: (drop: ApiDrop) => void;
readonly currentDropId?: string | undefined;
readonly hideLinkPreviews?: boolean | undefined;
readonly tweetPreviewMode?: TweetPreviewMode | undefined;
}

interface LinkRenderer {
Expand Down Expand Up @@ -59,9 +61,12 @@ export const createLinkRenderer = ({
onQuoteClick,
currentDropId,
hideLinkPreviews = false,
tweetPreviewMode,
}: LinkRendererConfig): LinkRenderer => {
const seizeHandlers = createSeizeHandlers({ onQuoteClick, currentDropId });
const handlers = createLinkHandlers();
const handlers = createLinkHandlers(
tweetPreviewMode ? { tweetPreviewMode } : undefined
);

const renderImage: LinkRenderer["renderImage"] = ({ src }) => {
if (typeof src !== "string") {
Expand Down
52 changes: 23 additions & 29 deletions components/drops/view/part/dropPartMarkdown/renderers.tsx
Original file line number Diff line number Diff line change
@@ -1,55 +1,48 @@
import type { ReactElement } from "react";
import { ErrorBoundary } from "react-error-boundary";
import type { TweetProps, TwitterComponents } from "react-tweet";

import type { ApiDrop } from "@/generated/models/ApiDrop";
import type { SeizeQuoteLinkInfo } from "@/helpers/SeizeLinkParser";

import LinkHandlerFrame from "@/components/waves/LinkHandlerFrame";
import WaveDropQuoteWithDropId from "@/components/waves/drops/WaveDropQuoteWithDropId";
import WaveDropQuoteWithSerialNo from "@/components/waves/drops/WaveDropQuoteWithSerialNo";
import dynamic from "next/dynamic";
import ExpandableTweetPreview from "@/components/tweets/ExpandableTweetPreview";
import type { TweetPreviewMode } from "@/components/tweets/TweetPreviewModeContext";
import { ensureTwitterLink } from "./twitter";

const TweetEmbedSkeleton = () => (
<div
data-testid="tweet-embed-loading"
className="tw-flex tw-items-center tw-justify-center tw-w-full tw-min-h-[10rem] tw-rounded-lg tw-bg-iron-900/60 tw-animate-pulse tw-text-iron-300">
Loading tweet…
</div>
);

const TweetEmbed = dynamic<TweetProps>(
() => import("react-tweet").then((mod) => mod.Tweet),
{
ssr: false,
loading: TweetEmbedSkeleton,
}
);

const TweetFallback = ({ href }: { href: string }) => (
<a
href={href}
target="_blank"
rel="noopener noreferrer"
className="tw-flex tw-h-full tw-w-full tw-flex-col tw-justify-center tw-gap-y-1 tw-rounded-xl tw-border tw-border-solid tw-border-iron-700 tw-bg-iron-900 tw-p-4 tw-text-left tw-no-underline tw-transition-colors tw-duration-200 hover:tw-border-iron-500 focus-visible:tw-outline focus-visible:tw-outline-2 focus-visible:tw-outline-offset-2 focus-visible:tw-outline-primary-400">
className="tw-flex tw-h-full tw-w-full tw-flex-col tw-justify-center tw-gap-y-1 tw-rounded-xl tw-border tw-border-solid tw-border-iron-700 tw-bg-iron-900 tw-p-4 tw-text-left tw-no-underline tw-transition-colors tw-duration-200 hover:tw-border-iron-500 focus-visible:tw-outline focus-visible:tw-outline-2 focus-visible:tw-outline-offset-2 focus-visible:tw-outline-primary-400"
>
<span className="tw-text-sm tw-font-medium tw-text-iron-100">
Tweet unavailable
</span>
<span className="tw-text-xs tw-text-iron-400">Open on X</span>
</a>
);

const renderTweetEmbed = (href: string) => {
const result = ensureTwitterLink(href);
const renderFallback = () => <TweetFallback href={result.href} />;
const TweetNotFound: TwitterComponents["TweetNotFound"] = () =>
renderFallback();
const renderTweetEmbed = (
href: string,
options?: { readonly tweetPreviewMode?: TweetPreviewMode }
) => {
const { tweetId, href: normalizedHref } = ensureTwitterLink(href);
const renderFallback = () => <TweetFallback href={normalizedHref} />;
return (
<LinkHandlerFrame href={result.href}>
<div className="tw-flex-1 tw-min-w-0" data-theme="dark">
<LinkHandlerFrame href={normalizedHref}>
<div className="tw-min-w-0 tw-flex-1">
<ErrorBoundary fallbackRender={() => renderFallback()}>
<TweetEmbed id={result.tweetId} components={{ TweetNotFound }} />
<ExpandableTweetPreview
href={normalizedHref}
tweetId={tweetId}
compactMode={options?.tweetPreviewMode}
renderFallback={(fallbackHref) => (
<TweetFallback href={fallbackHref} />
)}
/>
</ErrorBoundary>
</div>
</LinkHandlerFrame>
Expand All @@ -60,7 +53,7 @@ const renderGifEmbed = (url: string): ReactElement => (
<img
src={url}
alt={url}
className="tw-max-h-[25rem] tw-max-w-[100%] tw-h-auto"
className="tw-h-auto tw-max-h-[25rem] tw-max-w-[100%]"
/>
);

Expand All @@ -86,7 +79,8 @@ const renderSeizeQuote = (
return (
<LinkHandlerFrame
href={href}
relativeHref={`/waves?wave=${waveId}&drop=${dropId}`}>
relativeHref={`/waves?wave=${waveId}&drop=${dropId}`}
>
<WaveDropQuoteWithDropId
dropId={dropId}
partId={1}
Expand Down
33 changes: 0 additions & 33 deletions components/home/boosted/BoostedTweetPreview.module.css

This file was deleted.

39 changes: 6 additions & 33 deletions components/home/boosted/BoostedTweetPreview.tsx
Original file line number Diff line number Diff line change
@@ -1,16 +1,6 @@
"use client";

import { useMemo } from "react";
import {
TweetBody,
TweetContainer,
TweetHeader,
TweetSkeleton,
enrichTweet,
useTweet,
} from "react-tweet";

import styles from "./BoostedTweetPreview.module.css";
import ExpandableTweetPreview from "@/components/tweets/ExpandableTweetPreview";

interface BoostedTweetPreviewProps {
readonly href: string;
Expand Down Expand Up @@ -49,28 +39,11 @@ export default function BoostedTweetPreview({
href,
tweetId,
}: BoostedTweetPreviewProps) {
const { data, isLoading } = useTweet(tweetId);

const tweet = useMemo(() => (data ? enrichTweet(data) : null), [data]);

if (isLoading) {
return (
<div className={styles["root"]} data-theme="dark">
<TweetSkeleton />
</div>
);
}

if (!tweet) {
return <TweetFallback href={href} />;
}

return (
<div className={styles["root"]} data-theme="dark">
<TweetContainer className="tw-w-full tw-min-w-0 tw-max-w-full">
<TweetHeader tweet={tweet} />
<TweetBody tweet={tweet} />
</TweetContainer>
</div>
<ExpandableTweetPreview
href={href}
tweetId={tweetId}
renderFallback={(fallbackHref) => <TweetFallback href={fallbackHref} />}
/>
);
}
Loading