Skip to content

Commit

Permalink
Display a popover when hovering over highlighted mentions
Browse files Browse the repository at this point in the history
  • Loading branch information
acelaya committed Feb 12, 2025
1 parent 1e1103f commit 2616ac2
Showing 1 changed file with 72 additions and 4 deletions.
76 changes: 72 additions & 4 deletions src/sidebar/components/MarkdownView.tsx
Original file line number Diff line number Diff line change
@@ -1,11 +1,25 @@
import { useEffect, useMemo, useRef } from 'preact/hooks';
import { ListenerCollection, Popover } from '@hypothesis/frontend-shared';
import classnames from 'classnames';
import { useEffect, useMemo, useRef, useState } from 'preact/hooks';

import type { Mention } from '../../types/api';
import type { InvalidUsername } from '../helpers/mentions';
import { renderMentionTags } from '../helpers/mentions';
import { replaceLinksWithEmbeds } from '../media-embedder';
import { renderMathAndMarkdown } from '../render-markdown';
import StyledText from './StyledText';

function MentionPopoverContent({ mention }: { mention: Mention }) {
return (

Check warning on line 13 in src/sidebar/components/MarkdownView.tsx

View check run for this annotation

Codecov / codecov/patch

src/sidebar/components/MarkdownView.tsx#L12-L13

Added lines #L12 - L13 were not covered by tests
<div className="flex flex-col gap-y-1.5">
<div className="text-md font-bold">@{mention.username}</div>
{mention.display_name && (
<div className="text-color-text-light">{mention.display_name}</div>

Check warning on line 17 in src/sidebar/components/MarkdownView.tsx

View check run for this annotation

Codecov / codecov/patch

src/sidebar/components/MarkdownView.tsx#L16-L17

Added lines #L16 - L17 were not covered by tests
)}
</div>
);
}

export type MarkdownViewProps = {
/** The string of markdown to display as HTML. */
markdown: string;
Expand All @@ -22,14 +36,19 @@ export default function MarkdownView({
markdown,
classes,
style,
mentions,
mentions = [],
}: MarkdownViewProps) {
const html = useMemo(
() => (markdown ? renderMathAndMarkdown(markdown) : ''),
[markdown],
);
const content = useRef<HTMLDivElement | null>(null);

const [popoverContent, setPopoverContent] = useState<
Mention | InvalidUsername | null
>(null);
const mentionsPopoverAnchorRef = useRef<HTMLElement | null>(null);

useEffect(() => {
replaceLinksWithEmbeds(content.current!, {
// Make embeds the full width of the sidebar, unless the sidebar has been
Expand All @@ -40,7 +59,34 @@ export default function MarkdownView({
}, [markdown]);

useEffect(() => {
renderMentionTags(content.current!, mentions ?? []);
const listenerCollection = new ListenerCollection();
const foundMentions = renderMentionTags(content.current!, mentions);

listenerCollection.add(
content.current!,
'mouseenter',
({ target }) => {
const element = target as HTMLElement;
const mention = foundMentions.get(element) ?? null;

Check warning on line 70 in src/sidebar/components/MarkdownView.tsx

View check run for this annotation

Codecov / codecov/patch

src/sidebar/components/MarkdownView.tsx#L68-L70

Added lines #L68 - L70 were not covered by tests

if (mention) {
setPopoverContent(mention);
mentionsPopoverAnchorRef.current = element;

Check warning on line 74 in src/sidebar/components/MarkdownView.tsx

View check run for this annotation

Codecov / codecov/patch

src/sidebar/components/MarkdownView.tsx#L72-L74

Added lines #L72 - L74 were not covered by tests
}
},
{ capture: true },
);
listenerCollection.add(
content.current!,
'mouseleave',
() => {
setPopoverContent(null);
mentionsPopoverAnchorRef.current = null;

Check warning on line 84 in src/sidebar/components/MarkdownView.tsx

View check run for this annotation

Codecov / codecov/patch

src/sidebar/components/MarkdownView.tsx#L82-L84

Added lines #L82 - L84 were not covered by tests
},
{ capture: true },
);

return () => listenerCollection.removeAll();
}, [mentions]);

// NB: The following could be implemented by setting attribute props directly
Expand All @@ -50,14 +96,36 @@ export default function MarkdownView({
// a review in the future.
return (
<div className="w-full break-anywhere cursor-text">
<StyledText>
<StyledText
classes={classnames(
// A `relative` wrapper around the `Popover` component is needed for
// when the native Popover API is not supported.
'relative',
)}
>
<div
className={classes}
data-testid="markdown-text"
ref={content}
dangerouslySetInnerHTML={{ __html: html }}
style={style}
/>
<Popover
open={!!popoverContent}
onClose={() => setPopoverContent(null)}

Check warning on line 115 in src/sidebar/components/MarkdownView.tsx

View check run for this annotation

Codecov / codecov/patch

src/sidebar/components/MarkdownView.tsx#L115

Added line #L115 was not covered by tests
anchorElementRef={mentionsPopoverAnchorRef}
classes="px-3 py-2"
>
{typeof popoverContent === 'string' && (
<>

Check warning on line 120 in src/sidebar/components/MarkdownView.tsx

View check run for this annotation

Codecov / codecov/patch

src/sidebar/components/MarkdownView.tsx#L120

Added line #L120 was not covered by tests
No user with username{' '}
<span className="font-bold">{popoverContent}</span> exists
</>
)}
{popoverContent !== null && typeof popoverContent === 'object' && (
<MentionPopoverContent mention={popoverContent} />

Check warning on line 126 in src/sidebar/components/MarkdownView.tsx

View check run for this annotation

Codecov / codecov/patch

src/sidebar/components/MarkdownView.tsx#L126

Added line #L126 was not covered by tests
)}
</Popover>
</StyledText>
</div>
);
Expand Down

0 comments on commit 2616ac2

Please sign in to comment.