/*! * Copyright (c) 2024 PLANKA Software GmbH * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md */ import { dequal } from 'dequal'; import { keyBy } from 'lodash'; import React, { useCallback, useEffect, useMemo, useRef } from 'react'; import PropTypes from 'prop-types'; import { useDispatch, useSelector } from 'react-redux'; import { useTranslation } from 'react-i18next'; import { Mention, MentionsInput } from 'react-mentions'; import { Button, Form } from 'semantic-ui-react'; import { useClickAwayListener } from '../../../lib/hooks'; import selectors from '../../../selectors'; import entryActions from '../../../entry-actions'; import { useForm, useNestedRef } from '../../../hooks'; import { isUsernameChar, mentionTextToMarkup } from '../../../utils/mentions'; import { focusEnd } from '../../../utils/element-helpers'; import { isModifierKeyPressed } from '../../../utils/event-helpers'; import UserAvatar from '../../users/UserAvatar'; import styles from './Edit.module.scss'; const Edit = React.memo(({ commentId, onClose }) => { const selectCommentById = useMemo(() => selectors.makeSelectCommentById(), []); const comment = useSelector((state) => selectCommentById(state, commentId)); const boardMemberships = useSelector(selectors.selectMembershipsForCurrentBoard); const dispatch = useDispatch(); const [t] = useTranslation(); const defaultData = useMemo( () => ({ text: comment.text, }), [comment.text], ); const [data, , setData] = useForm(() => ({ text: '', ...defaultData, })); const textFieldRef = useRef(null); const textMentionsRef = useRef(null); const textInputRef = useRef(null); const [submitButtonRef, handleSubmitButtonRef] = useNestedRef(); const [cancelButtonRef, handleCancelButtonRef] = useNestedRef(); const userByUsername = useMemo( () => keyBy( boardMemberships.flatMap(({ user }) => (user.username ? user : [])), 'username', ), [boardMemberships], ); const submit = useCallback(() => { const cleanData = { ...data, text: mentionTextToMarkup(data.text.trim(), userByUsername), }; if (cleanData.text && !dequal(cleanData, defaultData)) { dispatch(entryActions.updateComment(commentId, cleanData)); } onClose(); }, [commentId, onClose, dispatch, defaultData, data, userByUsername]); const handleSubmit = useCallback(() => { submit(); }, [submit]); const handleFieldChange = useCallback( (_, text) => { setData({ text: !isUsernameChar(text.slice(-1)) ? mentionTextToMarkup(text, userByUsername) : text, }); }, [setData, userByUsername], ); const handleFieldKeyDown = useCallback( (event) => { if (event.key === 'Enter') { if (isModifierKeyPressed(event)) { submit(); } } else if (event.key === 'Escape') { if (textMentionsRef.current.isOpened()) { textMentionsRef.current.clearSuggestions(); return; } onClose(); } }, [onClose, submit], ); const handleCancelClick = useCallback(() => { onClose(); }, [onClose]); const handleClickAwayCancel = useCallback(() => { textInputRef.current.focus(); }, []); const clickAwayProps = useClickAwayListener( [textFieldRef, submitButtonRef, cancelButtonRef], submit, handleClickAwayCancel, ); const suggestionRenderer = useCallback( (entry, _, highlightedDisplay) => (