mirror of
https://github.com/block/buzz.git
synced 2026-08-18 06:50:31 +02:00
Fix desktop glass chrome and inbox previews (#793)
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -93,7 +93,7 @@ export function AgentSessionThreadPanel({
|
|||||||
{!isOverlay ? (
|
{!isOverlay ? (
|
||||||
<div
|
<div
|
||||||
aria-hidden="true"
|
aria-hidden="true"
|
||||||
className="pointer-events-none absolute inset-x-0 top-0 z-40 h-[76px] bg-background/45 backdrop-blur-xl after:absolute after:bottom-0 after:left-0 after:top-10 after:w-px after:bg-border/80 supports-[backdrop-filter]:bg-background/35"
|
className="pointer-events-none absolute inset-x-0 top-0 z-40 h-[76px] bg-background/75 backdrop-blur-md after:absolute after:bottom-0 after:left-0 after:top-10 after:w-px after:bg-border/80 supports-[backdrop-filter]:bg-background/65 dark:bg-background/45 dark:backdrop-blur-xl dark:supports-[backdrop-filter]:bg-background/35"
|
||||||
/>
|
/>
|
||||||
) : null}
|
) : null}
|
||||||
|
|
||||||
@@ -101,7 +101,7 @@ export function AgentSessionThreadPanel({
|
|||||||
className={cn(
|
className={cn(
|
||||||
"z-50 flex cursor-default select-none items-center gap-3 px-4",
|
"z-50 flex cursor-default select-none items-center gap-3 px-4",
|
||||||
isOverlay
|
isOverlay
|
||||||
? "relative min-h-[44px] shrink-0 border-b border-border/70 bg-background/70 py-2.5 backdrop-blur-xl supports-[backdrop-filter]:bg-background/55"
|
? "relative min-h-[44px] shrink-0 border-b border-border/70 bg-background/80 py-2.5 backdrop-blur-md supports-[backdrop-filter]:bg-background/70 dark:bg-background/70 dark:backdrop-blur-xl dark:supports-[backdrop-filter]:bg-background/55"
|
||||||
: "absolute inset-x-0 top-11 min-h-[32px] py-[4px]",
|
: "absolute inset-x-0 top-11 min-h-[32px] py-[4px]",
|
||||||
)}
|
)}
|
||||||
data-tauri-drag-region
|
data-tauri-drag-region
|
||||||
|
|||||||
@@ -150,7 +150,7 @@ export function ChatHeader({
|
|||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="relative z-30 h-[76px] -mb-[76px] bg-background/70 pt-[42px] backdrop-blur-xl supports-[backdrop-filter]:bg-background/55">
|
<div className="relative z-30 h-[76px] -mb-[76px] bg-background/80 pt-[42px] backdrop-blur-md supports-[backdrop-filter]:bg-background/70 dark:bg-background/70 dark:backdrop-blur-xl dark:supports-[backdrop-filter]:bg-background/55">
|
||||||
{header}
|
{header}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -214,7 +214,7 @@ export function InboxDetailPane({
|
|||||||
<div className="relative min-h-0 flex-1 overflow-hidden">
|
<div className="relative min-h-0 flex-1 overflow-hidden">
|
||||||
<div
|
<div
|
||||||
aria-hidden="true"
|
aria-hidden="true"
|
||||||
className="pointer-events-none absolute inset-x-0 top-0 z-40 h-[76px] bg-background/45 backdrop-blur-xl supports-[backdrop-filter]:bg-background/35"
|
className="pointer-events-none absolute inset-x-0 top-0 z-40 h-[76px] bg-background/75 backdrop-blur-md supports-[backdrop-filter]:bg-background/65 dark:bg-background/45 dark:backdrop-blur-xl dark:supports-[backdrop-filter]:bg-background/35"
|
||||||
/>
|
/>
|
||||||
<div className="absolute inset-x-0 top-[38px] z-50 flex min-h-[32px] items-center justify-between gap-3 py-[4px] pl-6 pr-3">
|
<div className="absolute inset-x-0 top-[38px] z-50 flex min-h-[32px] items-center justify-between gap-3 py-[4px] pl-6 pr-3">
|
||||||
<div className="min-w-0">
|
<div className="min-w-0">
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import {
|
|||||||
} from "@/features/home/lib/inbox";
|
} from "@/features/home/lib/inbox";
|
||||||
import { cn } from "@/shared/lib/cn";
|
import { cn } from "@/shared/lib/cn";
|
||||||
import { Button } from "@/shared/ui/button";
|
import { Button } from "@/shared/ui/button";
|
||||||
|
import { Markdown } from "@/shared/ui/markdown";
|
||||||
import {
|
import {
|
||||||
DropdownMenu,
|
DropdownMenu,
|
||||||
DropdownMenuContent,
|
DropdownMenuContent,
|
||||||
@@ -47,7 +48,7 @@ export function InboxListPane({
|
|||||||
<section className="relative flex min-h-0 min-w-0 flex-col overflow-hidden bg-background/60">
|
<section className="relative flex min-h-0 min-w-0 flex-col overflow-hidden bg-background/60">
|
||||||
<div
|
<div
|
||||||
aria-hidden="true"
|
aria-hidden="true"
|
||||||
className="pointer-events-none absolute inset-x-0 top-0 z-40 h-[76px] bg-background/45 backdrop-blur-xl supports-[backdrop-filter]:bg-background/35"
|
className="pointer-events-none absolute inset-x-0 top-0 z-40 h-[76px] bg-background/75 backdrop-blur-md supports-[backdrop-filter]:bg-background/65 dark:bg-background/45 dark:backdrop-blur-xl dark:supports-[backdrop-filter]:bg-background/35"
|
||||||
/>
|
/>
|
||||||
<div className="absolute inset-x-0 top-[42px] z-50 min-h-[32px] px-5 py-[4px]">
|
<div className="absolute inset-x-0 top-[42px] z-50 min-h-[32px] px-5 py-[4px]">
|
||||||
<div className="flex min-w-0 items-center justify-between gap-3">
|
<div className="flex min-w-0 items-center justify-between gap-3">
|
||||||
@@ -159,16 +160,22 @@ export function InboxListPane({
|
|||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<p
|
<div
|
||||||
className={cn(
|
className={cn(
|
||||||
"mt-0.5 line-clamp-2 text-sm leading-5",
|
"mt-0.5 line-clamp-2 text-sm leading-5 [&_*]:inline [&_a]:font-medium [&_a]:text-current [&_br]:hidden [&_p]:inline",
|
||||||
isDone
|
isDone
|
||||||
? "font-normal text-muted-foreground"
|
? "font-normal text-muted-foreground"
|
||||||
: "font-semibold text-foreground",
|
: "font-semibold text-foreground",
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
{item.preview}
|
<Markdown
|
||||||
</p>
|
className="inline max-w-full text-inherit"
|
||||||
|
content={item.preview}
|
||||||
|
interactive={false}
|
||||||
|
mentionNames={item.mentionNames}
|
||||||
|
tight
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div className="mt-1 flex flex-wrap items-center gap-2 text-xs text-muted-foreground">
|
<div className="mt-1 flex flex-wrap items-center gap-2 text-xs text-muted-foreground">
|
||||||
<span
|
<span
|
||||||
|
|||||||
@@ -654,7 +654,7 @@ export function MessageComposer({
|
|||||||
/>
|
/>
|
||||||
<div className="relative flex w-full flex-col gap-3">
|
<div className="relative flex w-full flex-col gap-3">
|
||||||
<form
|
<form
|
||||||
className="relative isolate rounded-2xl border border-border/50 bg-background/70 px-3 pb-2 pt-3 shadow-[0_4px_24px_rgba(0,0,0,0.08)] backdrop-blur-xl supports-[backdrop-filter]:bg-background/55 dark:shadow-[0_4px_24px_rgba(0,0,0,0.35)] sm:px-4"
|
className="relative isolate rounded-2xl border border-border/50 bg-background/80 px-3 pb-2 pt-3 shadow-[0_4px_24px_rgba(0,0,0,0.08)] backdrop-blur-md supports-[backdrop-filter]:bg-background/70 dark:bg-background/70 dark:backdrop-blur-xl dark:shadow-[0_4px_24px_rgba(0,0,0,0.35)] dark:supports-[backdrop-filter]:bg-background/55 sm:px-4"
|
||||||
data-testid="message-composer"
|
data-testid="message-composer"
|
||||||
onDragEnter={media.handleDragEnter}
|
onDragEnter={media.handleDragEnter}
|
||||||
onDragLeave={media.handleDragLeave}
|
onDragLeave={media.handleDragLeave}
|
||||||
|
|||||||
@@ -198,7 +198,7 @@ export function MessageThreadPanel({
|
|||||||
className={cn(
|
className={cn(
|
||||||
"z-50 flex cursor-default select-none items-center gap-3 px-3",
|
"z-50 flex cursor-default select-none items-center gap-3 px-3",
|
||||||
isOverlay
|
isOverlay
|
||||||
? "relative min-h-[44px] shrink-0 bg-background/70 py-[6px] backdrop-blur-xl supports-[backdrop-filter]:bg-background/55"
|
? "relative min-h-[44px] shrink-0 bg-background/80 py-[6px] backdrop-blur-md supports-[backdrop-filter]:bg-background/70 dark:bg-background/70 dark:backdrop-blur-xl dark:supports-[backdrop-filter]:bg-background/55"
|
||||||
: "absolute inset-x-0 top-11 min-h-[32px] py-[4px]",
|
: "absolute inset-x-0 top-11 min-h-[32px] py-[4px]",
|
||||||
)}
|
)}
|
||||||
data-tauri-drag-region
|
data-tauri-drag-region
|
||||||
|
|||||||
@@ -149,7 +149,10 @@ export const MessageTimeline = React.memo(function MessageTimeline({
|
|||||||
onScroll={syncScrollState}
|
onScroll={syncScrollState}
|
||||||
ref={scrollContainerRef}
|
ref={scrollContainerRef}
|
||||||
>
|
>
|
||||||
<div className="flex w-full flex-col gap-2 pt-12" ref={contentRef}>
|
<div
|
||||||
|
className="flex w-full flex-col gap-2 pt-[76px]"
|
||||||
|
ref={contentRef}
|
||||||
|
>
|
||||||
<div ref={topSentinelRef} aria-hidden className="h-px" />
|
<div ref={topSentinelRef} aria-hidden className="h-px" />
|
||||||
|
|
||||||
{isFetchingOlder ? (
|
{isFetchingOlder ? (
|
||||||
|
|||||||
@@ -600,7 +600,7 @@ export function AppSidebar({
|
|||||||
</SidebarMenu>
|
</SidebarMenu>
|
||||||
</SidebarHeader>
|
</SidebarHeader>
|
||||||
|
|
||||||
<div className="relative flex min-h-0 flex-1 flex-col">
|
<div className="relative flex min-h-0 flex-1 flex-col overflow-hidden">
|
||||||
{unreadAboveCount > 0 ? (
|
{unreadAboveCount > 0 ? (
|
||||||
<MoreUnreadButton
|
<MoreUnreadButton
|
||||||
count={unreadAboveCount}
|
count={unreadAboveCount}
|
||||||
@@ -610,7 +610,7 @@ export function AppSidebar({
|
|||||||
testId="sidebar-more-unread-above"
|
testId="sidebar-more-unread-above"
|
||||||
/>
|
/>
|
||||||
) : null}
|
) : null}
|
||||||
<SidebarContent ref={scrollRef}>
|
<SidebarContent className="pb-32" ref={scrollRef}>
|
||||||
{isLoading ? (
|
{isLoading ? (
|
||||||
<SidebarGroup>
|
<SidebarGroup>
|
||||||
<SidebarGroupLabel>Channels</SidebarGroupLabel>
|
<SidebarGroupLabel>Channels</SidebarGroupLabel>
|
||||||
@@ -713,6 +713,7 @@ export function AppSidebar({
|
|||||||
|
|
||||||
{unreadBelowCount > 0 ? (
|
{unreadBelowCount > 0 ? (
|
||||||
<MoreUnreadButton
|
<MoreUnreadButton
|
||||||
|
bottomClassName="bottom-28"
|
||||||
count={unreadBelowCount}
|
count={unreadBelowCount}
|
||||||
icon={<ArrowDown />}
|
icon={<ArrowDown />}
|
||||||
onClick={scrollToNextBelow}
|
onClick={scrollToNextBelow}
|
||||||
@@ -720,88 +721,88 @@ export function AppSidebar({
|
|||||||
testId="sidebar-more-unread-below"
|
testId="sidebar-more-unread-below"
|
||||||
/>
|
/>
|
||||||
) : null}
|
) : null}
|
||||||
</div>
|
|
||||||
|
|
||||||
<SidebarFooter>
|
<SidebarFooter className="absolute inset-x-0 bottom-0 z-30 bg-sidebar/55 shadow-[0_-16px_40px_hsl(var(--sidebar)/0.55)] backdrop-blur-xl supports-[backdrop-filter]:bg-sidebar/45 dark:bg-sidebar/45 dark:shadow-[0_-16px_44px_rgba(0,0,0,0.42)] dark:supports-[backdrop-filter]:bg-sidebar/35">
|
||||||
<SidebarMenu>
|
<SidebarMenu>
|
||||||
<SidebarMenuItem>
|
<SidebarMenuItem>
|
||||||
<div
|
<div
|
||||||
className="rounded-xl px-2 py-2 transition-colors hover:bg-sidebar-accent/70 focus-within:bg-sidebar-accent/70"
|
className="rounded-xl px-2 py-2 transition-colors hover:bg-sidebar-accent/35 focus-within:bg-sidebar-accent/35 dark:hover:bg-sidebar-accent/25 dark:focus-within:bg-sidebar-accent/25"
|
||||||
data-testid="sidebar-profile-card"
|
data-testid="sidebar-profile-card"
|
||||||
>
|
>
|
||||||
<div className="flex min-w-0 items-center gap-3">
|
<div className="flex min-w-0 items-center gap-3">
|
||||||
<div className="relative shrink-0">
|
<div className="relative shrink-0">
|
||||||
<ProfileAvatar
|
<ProfileAvatar
|
||||||
avatarUrl={profile?.avatarUrl ?? null}
|
avatarUrl={profile?.avatarUrl ?? null}
|
||||||
className="h-10 w-10 rounded-2xl text-sm"
|
className="h-10 w-10 rounded-2xl text-sm"
|
||||||
iconClassName="h-5 w-5"
|
iconClassName="h-5 w-5"
|
||||||
label={resolvedDisplayName}
|
label={resolvedDisplayName}
|
||||||
testId="sidebar-profile-avatar"
|
testId="sidebar-profile-avatar"
|
||||||
/>
|
|
||||||
<span
|
|
||||||
aria-label={getPresenceLabel(selfPresenceStatus)}
|
|
||||||
className="absolute -bottom-0.5 -right-0.5 flex h-4 w-4 items-center justify-center rounded-full bg-sidebar"
|
|
||||||
data-testid="self-presence-badge"
|
|
||||||
role="img"
|
|
||||||
>
|
|
||||||
<PresenceDot
|
|
||||||
className="h-2.5 w-2.5"
|
|
||||||
status={selfPresenceStatus}
|
|
||||||
/>
|
/>
|
||||||
</span>
|
<span
|
||||||
</div>
|
aria-label={getPresenceLabel(selfPresenceStatus)}
|
||||||
<div className="min-w-0 flex-1">
|
className="absolute -bottom-0.5 -right-0.5 flex h-4 w-4 items-center justify-center rounded-full bg-sidebar"
|
||||||
<ProfilePopover
|
data-testid="self-presence-badge"
|
||||||
open={profilePopoverOpen}
|
role="img"
|
||||||
onOpenChange={setProfilePopoverOpen}
|
|
||||||
displayName={resolvedDisplayName}
|
|
||||||
nip05={profile?.nip05Handle}
|
|
||||||
avatarUrl={profile?.avatarUrl ?? null}
|
|
||||||
currentStatus={selfPresenceStatus}
|
|
||||||
isStatusPending={isPresencePending}
|
|
||||||
userStatusText={selfUserStatus?.text}
|
|
||||||
userStatusEmoji={selfUserStatus?.emoji}
|
|
||||||
onSetStatus={onSetPresenceStatus ?? (() => {})}
|
|
||||||
onSetUserStatus={onSetUserStatus}
|
|
||||||
onClearUserStatus={onClearUserStatus}
|
|
||||||
onOpenSettings={onSelectSettings}
|
|
||||||
>
|
|
||||||
<button
|
|
||||||
className="block w-full min-w-0 text-left text-sidebar-foreground"
|
|
||||||
data-testid="open-settings"
|
|
||||||
type="button"
|
|
||||||
>
|
>
|
||||||
<p
|
<PresenceDot
|
||||||
className="truncate text-sm font-semibold text-current"
|
className="h-2.5 w-2.5"
|
||||||
data-testid="sidebar-profile-name"
|
status={selfPresenceStatus}
|
||||||
|
/>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<div className="min-w-0 flex-1">
|
||||||
|
<ProfilePopover
|
||||||
|
open={profilePopoverOpen}
|
||||||
|
onOpenChange={setProfilePopoverOpen}
|
||||||
|
displayName={resolvedDisplayName}
|
||||||
|
nip05={profile?.nip05Handle}
|
||||||
|
avatarUrl={profile?.avatarUrl ?? null}
|
||||||
|
currentStatus={selfPresenceStatus}
|
||||||
|
isStatusPending={isPresencePending}
|
||||||
|
userStatusText={selfUserStatus?.text}
|
||||||
|
userStatusEmoji={selfUserStatus?.emoji}
|
||||||
|
onSetStatus={onSetPresenceStatus ?? (() => {})}
|
||||||
|
onSetUserStatus={onSetUserStatus}
|
||||||
|
onClearUserStatus={onClearUserStatus}
|
||||||
|
onOpenSettings={onSelectSettings}
|
||||||
|
>
|
||||||
|
<button
|
||||||
|
className="block w-full min-w-0 text-left text-sidebar-foreground"
|
||||||
|
data-testid="open-settings"
|
||||||
|
type="button"
|
||||||
>
|
>
|
||||||
{resolvedDisplayName}
|
<p
|
||||||
|
className="truncate text-sm font-semibold text-current"
|
||||||
|
data-testid="sidebar-profile-name"
|
||||||
|
>
|
||||||
|
{resolvedDisplayName}
|
||||||
|
</p>
|
||||||
|
</button>
|
||||||
|
</ProfilePopover>
|
||||||
|
<WorkspaceSwitcher
|
||||||
|
activeWorkspace={activeWorkspace}
|
||||||
|
onAddWorkspace={onOpenAddWorkspace}
|
||||||
|
onRemoveWorkspace={onRemoveWorkspace}
|
||||||
|
onSwitchWorkspace={onSwitchWorkspace}
|
||||||
|
onUpdateWorkspace={onUpdateWorkspace}
|
||||||
|
variant="profile"
|
||||||
|
workspaces={workspaces}
|
||||||
|
/>
|
||||||
|
{selfUserStatus?.text || selfUserStatus?.emoji ? (
|
||||||
|
<p className="mt-0.5 truncate text-xs text-sidebar-foreground/50">
|
||||||
|
{selfUserStatus.emoji ? (
|
||||||
|
<span className="mr-1">{selfUserStatus.emoji}</span>
|
||||||
|
) : null}
|
||||||
|
{selfUserStatus.text}
|
||||||
</p>
|
</p>
|
||||||
</button>
|
) : null}
|
||||||
</ProfilePopover>
|
</div>
|
||||||
<WorkspaceSwitcher
|
|
||||||
activeWorkspace={activeWorkspace}
|
|
||||||
onAddWorkspace={onOpenAddWorkspace}
|
|
||||||
onRemoveWorkspace={onRemoveWorkspace}
|
|
||||||
onSwitchWorkspace={onSwitchWorkspace}
|
|
||||||
onUpdateWorkspace={onUpdateWorkspace}
|
|
||||||
variant="profile"
|
|
||||||
workspaces={workspaces}
|
|
||||||
/>
|
|
||||||
{selfUserStatus?.text || selfUserStatus?.emoji ? (
|
|
||||||
<p className="mt-0.5 truncate text-xs text-sidebar-foreground/50">
|
|
||||||
{selfUserStatus.emoji ? (
|
|
||||||
<span className="mr-1">{selfUserStatus.emoji}</span>
|
|
||||||
) : null}
|
|
||||||
{selfUserStatus.text}
|
|
||||||
</p>
|
|
||||||
) : null}
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</SidebarMenuItem>
|
||||||
</SidebarMenuItem>
|
</SidebarMenu>
|
||||||
</SidebarMenu>
|
</SidebarFooter>
|
||||||
</SidebarFooter>
|
</div>
|
||||||
|
|
||||||
<CreateChannelDialog
|
<CreateChannelDialog
|
||||||
channelKind={createDialogKind}
|
channelKind={createDialogKind}
|
||||||
|
|||||||
@@ -6,12 +6,14 @@ const MORE_UNREAD_BUTTON_CLASS =
|
|||||||
"h-7 min-h-7 gap-1.5 rounded-full border-0 bg-primary px-2.5 text-[11px] font-medium text-primary-foreground shadow-md hover:bg-primary/90 [&_svg]:size-3.5";
|
"h-7 min-h-7 gap-1.5 rounded-full border-0 bg-primary px-2.5 text-[11px] font-medium text-primary-foreground shadow-md hover:bg-primary/90 [&_svg]:size-3.5";
|
||||||
|
|
||||||
export function MoreUnreadButton({
|
export function MoreUnreadButton({
|
||||||
|
bottomClassName = "bottom-0",
|
||||||
count,
|
count,
|
||||||
icon,
|
icon,
|
||||||
onClick,
|
onClick,
|
||||||
position,
|
position,
|
||||||
testId,
|
testId,
|
||||||
}: {
|
}: {
|
||||||
|
bottomClassName?: string;
|
||||||
count: number;
|
count: number;
|
||||||
icon: React.ReactNode;
|
icon: React.ReactNode;
|
||||||
onClick: () => void;
|
onClick: () => void;
|
||||||
@@ -20,7 +22,7 @@ export function MoreUnreadButton({
|
|||||||
}) {
|
}) {
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
className={`pointer-events-none absolute inset-x-0 z-10 flex justify-center py-1 ${position === "top" ? "top-0" : "bottom-0"}`}
|
className={`pointer-events-none absolute inset-x-0 z-10 flex justify-center py-1 ${position === "top" ? "top-0" : bottomClassName}`}
|
||||||
>
|
>
|
||||||
<Button
|
<Button
|
||||||
className={`pointer-events-auto ${MORE_UNREAD_BUTTON_CLASS}`}
|
className={`pointer-events-auto ${MORE_UNREAD_BUTTON_CLASS}`}
|
||||||
|
|||||||
@@ -59,6 +59,7 @@ type MarkdownProps = {
|
|||||||
compact?: boolean;
|
compact?: boolean;
|
||||||
content: string;
|
content: string;
|
||||||
imetaByUrl?: ImetaLookup;
|
imetaByUrl?: ImetaLookup;
|
||||||
|
interactive?: boolean;
|
||||||
mentionNames?: string[];
|
mentionNames?: string[];
|
||||||
mentionPubkeysByName?: Record<string, string>;
|
mentionPubkeysByName?: Record<string, string>;
|
||||||
searchQuery?: string;
|
searchQuery?: string;
|
||||||
@@ -204,6 +205,7 @@ function createMarkdownComponents(
|
|||||||
onOpenMessageLink: (link: ParsedMessageLink) => void,
|
onOpenMessageLink: (link: ParsedMessageLink) => void,
|
||||||
imetaByUrl?: ImetaLookup,
|
imetaByUrl?: ImetaLookup,
|
||||||
mentionPubkeysByName?: Record<string, string>,
|
mentionPubkeysByName?: Record<string, string>,
|
||||||
|
interactive = true,
|
||||||
): Components {
|
): Components {
|
||||||
const paragraphClassName =
|
const paragraphClassName =
|
||||||
variant === "tight"
|
variant === "tight"
|
||||||
@@ -220,6 +222,10 @@ function createMarkdownComponents(
|
|||||||
|
|
||||||
return {
|
return {
|
||||||
a: ({ children, href, ...props }) => {
|
a: ({ children, href, ...props }) => {
|
||||||
|
if (!interactive) {
|
||||||
|
return <span className="font-medium text-current">{children}</span>;
|
||||||
|
}
|
||||||
|
|
||||||
// Intercept `sprout://message?channel=…&id=…` links so a click navigates
|
// Intercept `sprout://message?channel=…&id=…` links so a click navigates
|
||||||
// in-app instead of opening the URL in the OS browser. http(s) links
|
// in-app instead of opening the URL in the OS browser. http(s) links
|
||||||
// continue to use the existing target="_blank" behavior.
|
// continue to use the existing target="_blank" behavior.
|
||||||
@@ -312,6 +318,13 @@ function createMarkdownComponents(
|
|||||||
hr: () => <hr className="border-border/80" />,
|
hr: () => <hr className="border-border/80" />,
|
||||||
img: ({ alt, src }) => {
|
img: ({ alt, src }) => {
|
||||||
const resolvedSrc = src ? rewriteRelayUrl(src) : src;
|
const resolvedSrc = src ? rewriteRelayUrl(src) : src;
|
||||||
|
if (!interactive) {
|
||||||
|
const fallbackLabel = resolvedSrc?.endsWith(".mp4")
|
||||||
|
? "Video attachment"
|
||||||
|
: "Image attachment";
|
||||||
|
return <span>{alt?.trim() || fallbackLabel}</span>;
|
||||||
|
}
|
||||||
|
|
||||||
if (resolvedSrc?.endsWith(".mp4")) {
|
if (resolvedSrc?.endsWith(".mp4")) {
|
||||||
// Look up poster frame from imeta tags (NIP-71 `image` field).
|
// Look up poster frame from imeta tags (NIP-71 `image` field).
|
||||||
// Fall back to `thumb` for compatibility with older events.
|
// Fall back to `thumb` for compatibility with older events.
|
||||||
@@ -423,7 +436,12 @@ function createMarkdownComponents(
|
|||||||
|
|
||||||
return <p className={paragraphClassName}>{children}</p>;
|
return <p className={paragraphClassName}>{children}</p>;
|
||||||
},
|
},
|
||||||
pre: ({ children }) => <MarkdownCodeBlock>{children}</MarkdownCodeBlock>,
|
pre: ({ children }) =>
|
||||||
|
interactive ? (
|
||||||
|
<MarkdownCodeBlock>{children}</MarkdownCodeBlock>
|
||||||
|
) : (
|
||||||
|
<span>{children}</span>
|
||||||
|
),
|
||||||
strong: ({ children }) => (
|
strong: ({ children }) => (
|
||||||
<strong className="font-semibold">{children}</strong>
|
<strong className="font-semibold">{children}</strong>
|
||||||
),
|
),
|
||||||
@@ -463,6 +481,10 @@ function createMarkdownComponents(
|
|||||||
</span>
|
</span>
|
||||||
);
|
);
|
||||||
|
|
||||||
|
if (!interactive) {
|
||||||
|
return mentionNode;
|
||||||
|
}
|
||||||
|
|
||||||
return pubkey ? (
|
return pubkey ? (
|
||||||
<UserProfilePopover pubkey={pubkey} triggerElement="span">
|
<UserProfilePopover pubkey={pubkey} triggerElement="span">
|
||||||
{mentionNode}
|
{mentionNode}
|
||||||
@@ -480,7 +502,7 @@ function createMarkdownComponents(
|
|||||||
c.name.toLowerCase() === channelName.toLowerCase(),
|
c.name.toLowerCase() === channelName.toLowerCase(),
|
||||||
);
|
);
|
||||||
|
|
||||||
if (channel) {
|
if (channel && interactive) {
|
||||||
return (
|
return (
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
@@ -519,6 +541,14 @@ function createMarkdownComponents(
|
|||||||
const channelLabel = channel?.name ?? "channel";
|
const channelLabel = channel?.name ?? "channel";
|
||||||
const shortId = messageId.slice(0, 6);
|
const shortId = messageId.slice(0, 6);
|
||||||
|
|
||||||
|
if (!interactive) {
|
||||||
|
return (
|
||||||
|
<span data-message-link="">
|
||||||
|
#{channelLabel} · {shortId}
|
||||||
|
</span>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
@@ -543,6 +573,7 @@ function MarkdownInner({
|
|||||||
compact = false,
|
compact = false,
|
||||||
content,
|
content,
|
||||||
imetaByUrl,
|
imetaByUrl,
|
||||||
|
interactive = true,
|
||||||
mentionNames,
|
mentionNames,
|
||||||
mentionPubkeysByName,
|
mentionPubkeysByName,
|
||||||
searchQuery,
|
searchQuery,
|
||||||
@@ -576,8 +607,16 @@ function MarkdownInner({
|
|||||||
},
|
},
|
||||||
imetaByUrl,
|
imetaByUrl,
|
||||||
mentionPubkeysByName,
|
mentionPubkeysByName,
|
||||||
|
interactive,
|
||||||
),
|
),
|
||||||
[goChannel, variant, channels, imetaByUrl, mentionPubkeysByName],
|
[
|
||||||
|
goChannel,
|
||||||
|
variant,
|
||||||
|
channels,
|
||||||
|
imetaByUrl,
|
||||||
|
mentionPubkeysByName,
|
||||||
|
interactive,
|
||||||
|
],
|
||||||
);
|
);
|
||||||
|
|
||||||
// biome-ignore lint/suspicious/noExplicitAny: PluggableList type not directly importable
|
// biome-ignore lint/suspicious/noExplicitAny: PluggableList type not directly importable
|
||||||
@@ -686,6 +725,7 @@ export const Markdown = React.memo(
|
|||||||
prev.content === next.content &&
|
prev.content === next.content &&
|
||||||
prev.className === next.className &&
|
prev.className === next.className &&
|
||||||
prev.compact === next.compact &&
|
prev.compact === next.compact &&
|
||||||
|
prev.interactive === next.interactive &&
|
||||||
prev.tight === next.tight &&
|
prev.tight === next.tight &&
|
||||||
prev.mentionPubkeysByName === next.mentionPubkeysByName &&
|
prev.mentionPubkeysByName === next.mentionPubkeysByName &&
|
||||||
shallowArrayEqual(prev.mentionNames, next.mentionNames) &&
|
shallowArrayEqual(prev.mentionNames, next.mentionNames) &&
|
||||||
|
|||||||
Reference in New Issue
Block a user