import * as React from 'react'; import { TaskTooltip } from '@renderer/components/team/TaskTooltip'; import { useStore } from '@renderer/store'; import { calculateInlineMatchPositions } from '@renderer/utils/chipUtils'; import { findTaskReferenceMatches } from '@renderer/utils/taskReferenceUtils'; import type { MentionSuggestion } from '@renderer/types/mention'; import type { InlineMatchPosition } from '@renderer/utils/chipUtils'; interface TaskReferenceInteractionLayerProps { taskSuggestions: MentionSuggestion[]; value: string; textareaRef: React.RefObject; scrollTop: number; } type PositionedTaskReference = InlineMatchPosition; function areTaskSuggestionsEquivalent(a: MentionSuggestion, b: MentionSuggestion): boolean { return ( a.id === b.id && a.name === b.name && a.taskId === b.taskId && a.teamName === b.teamName && a.teamDisplayName === b.teamDisplayName && a.ownerName === b.ownerName && a.ownerColor === b.ownerColor ); } function areTaskReferencePositionsEquivalent( current: PositionedTaskReference[], next: PositionedTaskReference[] ): boolean { if (current.length !== next.length) return false; return current.every((position, index) => { const nextPosition = next[index]; return ( position.start === nextPosition.start && position.end === nextPosition.end && position.token === nextPosition.token && position.top === nextPosition.top && position.left === nextPosition.left && position.width === nextPosition.width && position.height === nextPosition.height && areTaskSuggestionsEquivalent(position.item, nextPosition.item) ); }); } export const TaskReferenceInteractionLayer = ({ taskSuggestions, value, textareaRef, scrollTop, }: TaskReferenceInteractionLayerProps): React.JSX.Element | null => { const [positions, setPositions] = React.useState([]); const positionsRef = React.useRef([]); const openGlobalTaskDetail = useStore((s) => s.openGlobalTaskDetail); const commitPositions = React.useCallback((nextPositions: PositionedTaskReference[]) => { if (areTaskReferencePositionsEquivalent(positionsRef.current, nextPositions)) return; positionsRef.current = nextPositions; setPositions(nextPositions); }, []); React.useLayoutEffect(() => { if (taskSuggestions.length === 0 || !value.includes('#')) { commitPositions([]); return; } const textarea = textareaRef.current; if (!textarea) return; const matches = findTaskReferenceMatches(value, taskSuggestions).map((match) => ({ item: match.suggestion, start: match.start, end: match.end, token: match.raw, })); commitPositions(calculateInlineMatchPositions(textarea, value, matches)); }, [commitPositions, taskSuggestions, textareaRef, value]); if (positions.length === 0) return null; return (
{positions.map((position, index) => { const suggestion = position.item; const taskId = suggestion.taskId; const teamName = suggestion.teamName; if (!taskId) return null; return (
); };