import React, { useEffect } from 'react'; import { areEqual } from 'react-window'; import tw, { TwStyle } from 'twin.macro'; import Linkify from 'linkify-it'; import { cellTypeMap } from '../store'; import { DashIcon, DiffModifiedIcon, PlusIcon } from '@primer/octicons-react'; import DOMPurify from 'dompurify'; import { EditableCell } from './editable-cell'; const linkify = Linkify().add('ftp:', null).add('mailto:', null); interface CellProps { type: string; value: any; rawValue: any; formattedValue?: any; categoryColor?: string | TwStyle; style?: {}; status?: string; isNearRightEdge?: boolean; isNearBottomEdge?: boolean; isFirstColumn: boolean; isExtraBlankRow: boolean; hasStatusIndicator?: boolean; background?: string; isEditable: boolean; onCellChange?: (value: any) => void; onRowDelete?: () => void; isFocused: boolean; onFocusChange: (value: [number, number] | null) => void; onMouseEnter?: Function; } export const Cell = React.memo(function (props: CellProps) { const { type, value, rawValue, formattedValue, categoryColor, status, isFirstColumn, isExtraBlankRow, isNearRightEdge, isNearBottomEdge, isEditable, onCellChange, onRowDelete, isFocused, onFocusChange, background, style = {}, onMouseEnter = () => {}, } = props; // @ts-ignore const cellInfo = cellTypeMap[type]; const { cell: CellComponent } = cellInfo || {}; const displayValue = (formattedValue || value || '').toString(); const isLongValue = (displayValue || '').length > 23; const stringWithLinks = React.useMemo(() => { if (!displayValue) return ''; const sanitized = DOMPurify.sanitize(displayValue); // Does the sanitized string contain any links? if (!linkify.test(sanitized)) return sanitized; // If so, we need to linkify it. const matches = linkify.match(sanitized); // If there are no matches, we can just return the sanitized string. if (!matches || matches.length === 0) return sanitized; // Otherwise, let's naively use the first match. return ` ${matches[0].url} `; }, [value]); useEffect(() => { if (!isFocused) return; onMouseEnter(); }, [isFocused]); if (!cellInfo) return null; const StatusIcon = isFirstColumn && // @ts-ignore { new: PlusIcon, old: DashIcon, modified: DiffModifiedIcon, 'modified-row': DiffModifiedIcon, }[status || '']; const statusColor = (isFirstColumn && // @ts-ignore { new: 'text-green-400', old: 'text-pink-400', modified: 'text-yellow-500', 'modified-row': 'text-yellow-500', }[status || '']) || ''; return (