Skip to content

Commit

Permalink
Populate list of suggestions matching @mentions
Browse files Browse the repository at this point in the history
  • Loading branch information
acelaya committed Dec 17, 2024
1 parent 0ac0205 commit c01a9ad
Showing 1 changed file with 75 additions and 3 deletions.
78 changes: 75 additions & 3 deletions src/sidebar/components/MarkdownEditor.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ import { useEffect, useMemo, useRef, useState } from 'preact/hooks';

import { ListenerCollection } from '../../shared/listener-collection';
import { isMacOS } from '../../shared/user-agent';
import { username } from '../helpers/account-id';
import {
LinkType,
convertSelectionToLink,
Expand Down Expand Up @@ -206,9 +207,45 @@ function TextArea({
...restProps
}: TextAreaProps & JSX.TextareaHTMLAttributes<HTMLTextAreaElement>) {
const [popoverOpen, setPopoverOpen] = useState(false);
const [activeMention, setActiveMention] = useState<string>();
const textareaRef = useSyncedRef(containerRef);
const store = useSidebarStore();
const atMentionsEnabled = store.isFeatureEnabled('at_mentions');
const allAnnos = store.allAnnotations();
const suggestions = useMemo(() => {
if (!atMentionsEnabled || activeMention === undefined) {
return [];
}

const usersMap = new Map<
string,
{ user: string; displayName: string | null }
>();
allAnnos.forEach(anno => {
const user = username(anno.user);
const displayName = anno.user_info?.display_name ?? null;

if (
// Keep a unique list of users
!usersMap.has(user) &&
// Filter users by the active mention as long as it is not empty
(!activeMention || `${user} ${displayName ?? ''}`.match(activeMention))
) {
usersMap.set(user, { user, displayName });
}
});

// Sort users by username and pick the top 10
return [...usersMap.values()]
.sort((a, b) => {
const lowerAUsername = a.user.toLowerCase();
const lowerBUsername = b.user.toLowerCase();

return lowerAUsername.localeCompare(lowerBUsername);
})
.slice(0, 10);
}, [activeMention, allAnnos, atMentionsEnabled]);
const [selectedSuggestion, setSelectedSuggestion] = useState<string>();

useEffect(() => {
if (!atMentionsEnabled) {
Expand All @@ -226,7 +263,19 @@ function TextArea({
if (e.key === 'Escape') {
return;
}
setPopoverOpen(getTermBeforeCaret(textarea).startsWith('@'));

const termBeforeCaret = getTermBeforeCaret(textarea);
const isAtMention = termBeforeCaret.startsWith('@');

setPopoverOpen(isAtMention);
setActiveMention(isAtMention ? termBeforeCaret.substring(1) : undefined);
});

listenerCollection.add(textarea, 'keydown', e => {
if (popoverOpen && ['ArrowDown', 'ArrowUp'].includes(e.key)) {
e.preventDefault();
e.stopPropagation();
}
});

return () => {
Expand All @@ -251,9 +300,32 @@ function TextArea({
open={popoverOpen}
onClose={() => setPopoverOpen(false)}
anchorElementRef={textareaRef}
classes="p-2"
classes="p-1"
>
Suggestions
<ul className="flex-col gap-y-0.5">
{suggestions.map((s, index) => (
<li
key={s.user}
className={classnames(
'flex justify-between items-center',
'rounded p-2 hover:bg-grey-2',
{
'bg-grey-2':
selectedSuggestion === s.user ||
(index === 0 && !selectedSuggestion),
},
)}
>
<span className="truncate">{s.user}</span>
<span className="text-color-text-light">{s.displayName}</span>
</li>
))}
{suggestions.length === 0 && (
<li className="italic p-2">
No matches. You can still write the username
</li>
)}
</ul>
</Popover>
)}
</div>
Expand Down

0 comments on commit c01a9ad

Please sign in to comment.