diff --git a/client/src/components/cards/CardModal/ProjectContent.jsx b/client/src/components/cards/CardModal/ProjectContent.jsx index 9acf5d42..61680c1c 100644 --- a/client/src/components/cards/CardModal/ProjectContent.jsx +++ b/client/src/components/cards/CardModal/ProjectContent.jsx @@ -3,6 +3,7 @@ * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md */ +import keyBy from 'lodash/keyBy'; import React, { useCallback, useContext, useMemo, useState } from 'react'; import classNames from 'classnames'; import { shallowEqual, useDispatch, useSelector } from 'react-redux'; @@ -16,6 +17,7 @@ import entryActions from '../../../entry-actions'; import { usePopupInClosableContext } from '../../../hooks'; import { startStopwatch, stopStopwatch } from '../../../utils/stopwatch'; import { isUsableMarkdownElement } from '../../../utils/element-helpers'; +import { mentionTextToMarkup } from '../../../utils/mentions'; import { BoardMembershipRoles, CardTypes, ListTypes } from '../../../constants/Enums'; import { CardTypeIcons } from '../../../constants/Icons'; import { ClosableContext } from '../../../contexts'; @@ -50,6 +52,7 @@ const ProjectContent = React.memo(() => { const card = useSelector(selectors.selectCurrentCard); const board = useSelector(selectors.selectCurrentBoard); + const boardMemberships = useSelector(selectors.selectMembershipsForCurrentBoard); const userIds = useSelector(selectors.selectUserIdsForCurrentCard); const labelIds = useSelector(selectors.selectLabelIdsForCurrentCard); const attachmentIds = useSelector(selectors.selectAttachmentIdsForCurrentCard); @@ -142,6 +145,15 @@ const ProjectContent = React.memo(() => { }, shallowEqual); const dispatch = useDispatch(); + const userByUsername = useMemo( + () => + keyBy( + boardMemberships.flatMap(({ user }) => (user.username ? user : [])), + ({ username }) => username.toLowerCase(), + ), + [boardMemberships], + ); + const [t] = useTranslation(); const [descriptionDraft, setDescriptionDraft] = useState(null); const [isEditDescriptionOpened, setIsEditDescriptionOpened] = useState(false); @@ -169,11 +181,11 @@ const ProjectContent = React.memo(() => { (description) => { dispatch( entryActions.updateCurrentCard({ - description, + description: description && mentionTextToMarkup(description, userByUsername), }), ); }, - [dispatch], + [dispatch, userByUsername], ); const handleDueCompletionChange = useCallback(() => { diff --git a/client/src/components/cards/CardModal/StoryContent.jsx b/client/src/components/cards/CardModal/StoryContent.jsx index 5e6b7b60..0577f48f 100644 --- a/client/src/components/cards/CardModal/StoryContent.jsx +++ b/client/src/components/cards/CardModal/StoryContent.jsx @@ -3,6 +3,7 @@ * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md */ +import keyBy from 'lodash/keyBy'; import React, { useCallback, useContext, useMemo, useState } from 'react'; import classNames from 'classnames'; import { shallowEqual, useDispatch, useSelector } from 'react-redux'; @@ -16,6 +17,7 @@ import selectors from '../../../selectors'; import entryActions from '../../../entry-actions'; import { usePopupInClosableContext } from '../../../hooks'; import { isUsableMarkdownElement } from '../../../utils/element-helpers'; +import { mentionTextToMarkup } from '../../../utils/mentions'; import { BoardMembershipRoles, CardTypes, ListTypes } from '../../../constants/Enums'; import { CardTypeIcons } from '../../../constants/Icons'; import { ClosableContext } from '../../../contexts'; @@ -46,6 +48,7 @@ const StoryContent = React.memo(() => { const card = useSelector(selectors.selectCurrentCard); const board = useSelector(selectors.selectCurrentBoard); + const boardMemberships = useSelector(selectors.selectMembershipsForCurrentBoard); const userIds = useSelector(selectors.selectUserIdsForCurrentCard); const labelIds = useSelector(selectors.selectLabelIdsForCurrentCard); const attachmentIds = useSelector(selectors.selectAttachmentIdsForCurrentCard); @@ -137,6 +140,15 @@ const StoryContent = React.memo(() => { }, shallowEqual); const dispatch = useDispatch(); + const userByUsername = useMemo( + () => + keyBy( + boardMemberships.flatMap(({ user }) => (user.username ? user : [])), + ({ username }) => username.toLowerCase(), + ), + [boardMemberships], + ); + const [t] = useTranslation(); const [descriptionDraft, setDescriptionDraft] = useState(null); const [isEditDescriptionOpened, setIsEditDescriptionOpened] = useState(false); @@ -164,11 +176,11 @@ const StoryContent = React.memo(() => { (description) => { dispatch( entryActions.updateCurrentCard({ - description, + description: description && mentionTextToMarkup(description, userByUsername), }), ); }, - [dispatch], + [dispatch, userByUsername], ); const handleRestoreClick = useCallback(() => { diff --git a/client/src/components/comments/Comments/Add.jsx b/client/src/components/comments/Comments/Add.jsx index c40768aa..00d9b47b 100755 --- a/client/src/components/comments/Comments/Add.jsx +++ b/client/src/components/comments/Comments/Add.jsx @@ -5,6 +5,7 @@ import keyBy from 'lodash/keyBy'; import React, { useCallback, useState, useRef, useMemo } from 'react'; +import PropTypes from 'prop-types'; import { useDispatch, useSelector } from 'react-redux'; import { useTranslation } from 'react-i18next'; import { Mention, MentionsInput } from 'react-mentions'; @@ -24,7 +25,7 @@ const DEFAULT_DATA = { text: '', }; -const Add = React.memo(() => { +const Add = React.memo(({ initialText, onInitialTextConsumed }) => { const boardMemberships = useSelector(selectors.selectMembershipsForCurrentBoard); const dispatch = useDispatch(); @@ -138,6 +139,19 @@ const Add = React.memo(() => { textInputRef.current.focus(); }, [selectTextFieldState]); + useDidUpdate(() => { + if (!initialText) { + return; + } + + setData({ + text: initialText, + }); + setIsOpened(true); + selectTextField(); + onInitialTextConsumed(); + }, [initialText, onInitialTextConsumed, selectTextField, setData]); + return (