mirror of
https://github.com/getpaseo/paseo.git
synced 2026-11-10 18:35:17 +00:00
* fix(markdown): restore link hover underline and code block spacing on web The web renderer swap dropped both. Links: the library styles anchors inline with text-decoration: none, so the link renderer has to track hover itself; a stylesheet :hover rule cannot win. Spacing: the patched renderers.codeBlock override returned the custom block bare, so it lost the library's codeBlock margins and whatever followed sat flush against it. The override now keeps those margins, including the zeroed trailing margin of the last block, so web and native share one spacing config. * test(desktop): expect Find to return focus to the chat viewport #7263 moved focus on closing Find from the no-drag focus scope to the scroll viewport so Page Up and Page Down keep working. The viewport emits no window region by design (#5233), so the old assertion that the focused element is no-drag failed on every run. Assert the new focus target and re-check that the titlebar still has no drag exclusions.
97 lines
3.3 KiB
TypeScript
97 lines
3.3 KiB
TypeScript
import { Text } from "react-native";
|
|
import { forwardRef, useCallback, useMemo, useState, type ComponentProps } from "react";
|
|
import type {
|
|
CodeBlockRendererProps,
|
|
LinkRendererProps,
|
|
MarkdownRenderers,
|
|
} from "react-native-enriched-markdown";
|
|
import { StyleSheet, withUnistyles } from "react-native-unistyles";
|
|
import { useFileLink } from "@/assistant-file-links";
|
|
import { Tooltip, TooltipTrigger, TooltipContent } from "@/components/ui/tooltip";
|
|
import { HighlightedCodeBlock } from "@/components/highlighted-code-block";
|
|
import type { Theme } from "@/styles/theme";
|
|
import { MermaidFence } from "./fence/mermaid";
|
|
import { markdownLinkSource } from "./source";
|
|
|
|
const ThemedCodeBlock = withUnistyles(HighlightedCodeBlock);
|
|
const codeStyle = (theme: Theme) => ({
|
|
inheritedStyles: {
|
|
color: theme.colors.foreground,
|
|
fontFamily: theme.fontFamily.mono,
|
|
fontSize: theme.fontSize.code,
|
|
},
|
|
textStyle: { backgroundColor: theme.colors.surface2, padding: 12, borderRadius: 8 },
|
|
});
|
|
function WebCodeBlock({ code, language }: CodeBlockRendererProps) {
|
|
if (language?.toLowerCase() === "mermaid") return <MermaidFence code={code} />;
|
|
return <ThemedCodeBlock code={code} language={language} uniProps={codeStyle} />;
|
|
}
|
|
|
|
// The library styles links inline, so a stylesheet :hover rule cannot underline them.
|
|
const LINK_HOVER_STYLE = { textDecoration: "underline" } as const;
|
|
|
|
function WebLink({ link, anchorProps, children }: LinkRendererProps) {
|
|
const { tooltipPath, onHoverIn } = useFileLink(markdownLinkSource(link.url, link.sourceType));
|
|
const [hovered, setHovered] = useState(false);
|
|
const handleMouseEnter = useCallback(() => setHovered(true), []);
|
|
const handleMouseLeave = useCallback(() => setHovered(false), []);
|
|
const anchorStyle = useMemo(
|
|
() => ({
|
|
...anchorProps.style,
|
|
overflowWrap: "anywhere" as const,
|
|
...(hovered ? LINK_HOVER_STYLE : null),
|
|
}),
|
|
[anchorProps.style, hovered],
|
|
);
|
|
const anchor = (
|
|
<a
|
|
{...anchorProps}
|
|
style={anchorStyle}
|
|
onMouseEnter={handleMouseEnter}
|
|
onMouseLeave={handleMouseLeave}
|
|
>
|
|
{children}
|
|
</a>
|
|
);
|
|
return (
|
|
<Tooltip delayDuration={400}>
|
|
<TooltipTrigger asChild onHoverIn={onHoverIn}>
|
|
<InlineLinkTrigger>{anchor}</InlineLinkTrigger>
|
|
</TooltipTrigger>
|
|
{tooltipPath ? (
|
|
<TooltipContent side="top" align="start" maxWidth={520}>
|
|
<Text selectable={false} style={styles.tooltipPath}>
|
|
{tooltipPath}
|
|
</Text>
|
|
</TooltipContent>
|
|
) : null}
|
|
</Tooltip>
|
|
);
|
|
}
|
|
// TooltipTrigger also supplies native Pressable handlers; forward only DOM events.
|
|
// A View renders a div, which is invalid inside the library's prose paragraphs.
|
|
const InlineLinkTrigger = forwardRef<HTMLSpanElement, ComponentProps<"span">>(
|
|
function InlineLinkTrigger({ children, onMouseEnter, onMouseLeave, onFocus, onBlur }, ref) {
|
|
return (
|
|
<span
|
|
ref={ref}
|
|
onMouseEnter={onMouseEnter}
|
|
onMouseLeave={onMouseLeave}
|
|
onFocus={onFocus}
|
|
onBlur={onBlur}
|
|
>
|
|
{children}
|
|
</span>
|
|
);
|
|
},
|
|
);
|
|
const styles = StyleSheet.create((theme) => ({
|
|
tooltipPath: {
|
|
color: theme.colors.foreground,
|
|
fontSize: theme.fontSize.sm,
|
|
fontWeight: theme.fontWeight.normal,
|
|
},
|
|
}));
|
|
|
|
export const WEB_RENDERERS: MarkdownRenderers = { codeBlock: WebCodeBlock, link: WebLink };
|