import { CameraEnhance, PhotoFilter } from '@mui/icons-material' import { IconButton, Tooltip, useColorScheme } from '@mui/joy' import { useEffect, useRef, useState } from 'react' import AutocompleteDropdown from '../TestView/AutocompleteDropdown' import './SmartTaskTitleInput.css' const renderHighlightedText = (text, cursorPosition) => { const parts = [] let lastIndex = 0 const regex = /(Tomorrow)|(Every\s+\d+\s+\w+s?)|(#\w+)|(P\d+)/gi let match while ((match = regex.exec(text)) !== null) { const matchedText = match[0] const matchIndex = match.index if (matchIndex > lastIndex) { parts.push(text.substring(lastIndex, matchIndex)) } let className = '' if (matchedText.toLowerCase() === 'tomorrow') { className = 'highlight-date' } else if (matchedText.toLowerCase().startsWith('every')) { className = 'highlight-repeat' } else if (matchedText.startsWith('#')) { className = 'highlight-label' } else if (matchedText.startsWith('P')) { className = 'highlight-priority' } parts.push( {matchedText} , ) lastIndex = regex.lastIndex } if (lastIndex < text.length) { parts.push(text.substring(lastIndex)) } return parts } const SmartTaskTitleInput = ({ value, placeholder, autoFocus, onChange, suggestions, onEnterPressed, onShiftEnterPressed, customRenderer, isNativeScanner, onScanClick, onPhotoSelected, }) => { const { mode, setMode } = useColorScheme() const titleInputRef = useRef(null) const photoInputRef = useRef(null) const [cursorPosition, setCursorPosition] = useState(value?.length) const dropdownRef = useRef(null) const [lastWord, setLastWord] = useState('') const [suggestionTrigger, setSuggestionTrigger] = useState('P') const [showSuggestions, setShowSuggestions] = useState(false) const [selectedSuggestionIndex, setSelectedSuggestionIndex] = useState(0) useEffect(() => { setCursorPosition(prevPos => Math.min(prevPos, value.length)) if ( titleInputRef.current && document.activeElement === titleInputRef.current ) { requestAnimationFrame(() => { titleInputRef.current.setSelectionRange(cursorPosition, cursorPosition) }) } }, [value]) useEffect(() => { // set focus on the input when the component is mounted: if (titleInputRef.current) { titleInputRef.current.focus() titleInputRef.current.setSelectionRange(cursorPosition, cursorPosition) } }, []) const handleSuggestionChange = text => { // if the last word start with '@' or '#' or 'P': const lastWord = text.split(' ').pop() if ( lastWord.startsWith('@') || lastWord.startsWith('#') || lastWord.startsWith('!') ) { setSuggestionTrigger(lastWord[0]) // last word without the first character: setLastWord(lastWord.slice(1)) setShowSuggestions(true) } else { setShowSuggestions(false) } } const handleTextareaChange = e => { handleSuggestionChange(e.target.value) onChange(e.target.value) setCursorPosition(e.target.selectionStart) } const handleTextareaKeyDown = e => { if (showSuggestions) { const currentSuggestions = suggestions[suggestionTrigger].options.filter( option => { if (typeof option === 'string') { return option.toLowerCase().includes(lastWord.toLowerCase()) } return option[suggestions[suggestionTrigger].display] .toLowerCase() .includes(lastWord.toLowerCase()) }, ) if (e.key === 'ArrowDown' || e.key === 'ArrowUp') { e.preventDefault() const newIndex = e.key === 'ArrowDown' ? (selectedSuggestionIndex + 1) % currentSuggestions.length : (selectedSuggestionIndex - 1 + currentSuggestions.length) % currentSuggestions.length setSelectedSuggestionIndex(newIndex) } else if (e.key === 'Enter' || e.key === 'Tab') { e.preventDefault() const selectedSuggestion = currentSuggestions[selectedSuggestionIndex] const suggestionValue = suggestions[suggestionTrigger].display ? selectedSuggestion[suggestions[suggestionTrigger].display] : selectedSuggestion if (suggestionValue) { const newValue = `${value.slice(0, cursorPosition - lastWord.length)}${suggestionValue} ${value.slice(cursorPosition)}` onChange(newValue) titleInputRef.current.value = newValue setShowSuggestions(false) const newCursorPosition = cursorPosition + suggestionValue.length + 1 titleInputRef.current.setSelectionRange( newCursorPosition, newCursorPosition, ) } } else if (e.key === 'Escape') { e.preventDefault() setShowSuggestions(false) } } else { if (e.key === 'Enter') { e.preventDefault() if (e.shiftKey) { if (onShiftEnterPressed) { onShiftEnterPressed(value) } } else if (onEnterPressed) { onEnterPressed(value) } } } const currentPos = e.target.selectionStart setCursorPosition(currentPos) } const handleTextareaClick = e => { const currentPos = e.target.selectionStart setCursorPosition(currentPos) } const handleDisplayClick = e => { const range = document.caretRangeFromPoint(e.clientX, e.clientY) if (range) { const offset = range.startOffset setCursorPosition(offset) if (titleInputRef.current) { titleInputRef.current.focus() titleInputRef.current.setSelectionRange(offset, offset) } } } const handlePhotoInputChange = e => { const file = e.target.files?.[0] if (!file || !onPhotoSelected) return const reader = new FileReader() reader.onload = ev => onPhotoSelected(ev.target.result) reader.readAsDataURL(file) e.target.value = '' } const showNativeButtons = isNativeScanner && !value const MIC_BUTTON_WIDTH = showNativeButtons && onPhotoSelected && onScanClick ? '5rem' : showNativeButtons ? '2.5rem' : '0rem' return (