feat: map @username mentions when saving card descriptions

This commit is contained in:
lustsazeus-lab
2026-03-06 09:48:20 +08:00
parent f23ce6e025
commit 2413ddd1cd
2 changed files with 28 additions and 4 deletions
@@ -3,6 +3,7 @@
* Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md * 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 React, { useCallback, useContext, useMemo, useState } from 'react';
import classNames from 'classnames'; import classNames from 'classnames';
import { shallowEqual, useDispatch, useSelector } from 'react-redux'; import { shallowEqual, useDispatch, useSelector } from 'react-redux';
@@ -15,6 +16,7 @@ import entryActions from '../../../entry-actions';
import { usePopupInClosableContext } from '../../../hooks'; import { usePopupInClosableContext } from '../../../hooks';
import { startStopwatch, stopStopwatch } from '../../../utils/stopwatch'; import { startStopwatch, stopStopwatch } from '../../../utils/stopwatch';
import { isUsableMarkdownElement } from '../../../utils/element-helpers'; import { isUsableMarkdownElement } from '../../../utils/element-helpers';
import { mentionTextToMarkup } from '../../../utils/mentions';
import { BoardMembershipRoles, CardTypes, ListTypes } from '../../../constants/Enums'; import { BoardMembershipRoles, CardTypes, ListTypes } from '../../../constants/Enums';
import { CardTypeIcons } from '../../../constants/Icons'; import { CardTypeIcons } from '../../../constants/Icons';
import { ClosableContext } from '../../../contexts'; import { ClosableContext } from '../../../contexts';
@@ -49,6 +51,7 @@ const ProjectContent = React.memo(() => {
const card = useSelector(selectors.selectCurrentCard); const card = useSelector(selectors.selectCurrentCard);
const board = useSelector(selectors.selectCurrentBoard); const board = useSelector(selectors.selectCurrentBoard);
const boardMemberships = useSelector(selectors.selectMembershipsForCurrentBoard);
const userIds = useSelector(selectors.selectUserIdsForCurrentCard); const userIds = useSelector(selectors.selectUserIdsForCurrentCard);
const labelIds = useSelector(selectors.selectLabelIdsForCurrentCard); const labelIds = useSelector(selectors.selectLabelIdsForCurrentCard);
const attachmentIds = useSelector(selectors.selectAttachmentIdsForCurrentCard); const attachmentIds = useSelector(selectors.selectAttachmentIdsForCurrentCard);
@@ -141,6 +144,15 @@ const ProjectContent = React.memo(() => {
}, shallowEqual); }, shallowEqual);
const dispatch = useDispatch(); const dispatch = useDispatch();
const userByUsername = useMemo(
() =>
keyBy(
boardMemberships.flatMap(({ user }) => (user.username ? user : [])),
({ username }) => username.toLowerCase(),
),
[boardMemberships],
);
const [t] = useTranslation(); const [t] = useTranslation();
const [descriptionDraft, setDescriptionDraft] = useState(null); const [descriptionDraft, setDescriptionDraft] = useState(null);
const [isEditDescriptionOpened, setIsEditDescriptionOpened] = useState(false); const [isEditDescriptionOpened, setIsEditDescriptionOpened] = useState(false);
@@ -168,11 +180,11 @@ const ProjectContent = React.memo(() => {
(description) => { (description) => {
dispatch( dispatch(
entryActions.updateCurrentCard({ entryActions.updateCurrentCard({
description, description: description && mentionTextToMarkup(description, userByUsername),
}), }),
); );
}, },
[dispatch], [dispatch, userByUsername],
); );
const handleDueCompletionChange = useCallback(() => { const handleDueCompletionChange = useCallback(() => {
@@ -3,6 +3,7 @@
* Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md * 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 React, { useCallback, useContext, useMemo, useState } from 'react';
import classNames from 'classnames'; import classNames from 'classnames';
import { shallowEqual, useDispatch, useSelector } from 'react-redux'; import { shallowEqual, useDispatch, useSelector } from 'react-redux';
@@ -15,6 +16,7 @@ import selectors from '../../../selectors';
import entryActions from '../../../entry-actions'; import entryActions from '../../../entry-actions';
import { usePopupInClosableContext } from '../../../hooks'; import { usePopupInClosableContext } from '../../../hooks';
import { isUsableMarkdownElement } from '../../../utils/element-helpers'; import { isUsableMarkdownElement } from '../../../utils/element-helpers';
import { mentionTextToMarkup } from '../../../utils/mentions';
import { BoardMembershipRoles, CardTypes, ListTypes } from '../../../constants/Enums'; import { BoardMembershipRoles, CardTypes, ListTypes } from '../../../constants/Enums';
import { CardTypeIcons } from '../../../constants/Icons'; import { CardTypeIcons } from '../../../constants/Icons';
import { ClosableContext } from '../../../contexts'; import { ClosableContext } from '../../../contexts';
@@ -45,6 +47,7 @@ const StoryContent = React.memo(() => {
const card = useSelector(selectors.selectCurrentCard); const card = useSelector(selectors.selectCurrentCard);
const board = useSelector(selectors.selectCurrentBoard); const board = useSelector(selectors.selectCurrentBoard);
const boardMemberships = useSelector(selectors.selectMembershipsForCurrentBoard);
const userIds = useSelector(selectors.selectUserIdsForCurrentCard); const userIds = useSelector(selectors.selectUserIdsForCurrentCard);
const labelIds = useSelector(selectors.selectLabelIdsForCurrentCard); const labelIds = useSelector(selectors.selectLabelIdsForCurrentCard);
const attachmentIds = useSelector(selectors.selectAttachmentIdsForCurrentCard); const attachmentIds = useSelector(selectors.selectAttachmentIdsForCurrentCard);
@@ -136,6 +139,15 @@ const StoryContent = React.memo(() => {
}, shallowEqual); }, shallowEqual);
const dispatch = useDispatch(); const dispatch = useDispatch();
const userByUsername = useMemo(
() =>
keyBy(
boardMemberships.flatMap(({ user }) => (user.username ? user : [])),
({ username }) => username.toLowerCase(),
),
[boardMemberships],
);
const [t] = useTranslation(); const [t] = useTranslation();
const [descriptionDraft, setDescriptionDraft] = useState(null); const [descriptionDraft, setDescriptionDraft] = useState(null);
const [isEditDescriptionOpened, setIsEditDescriptionOpened] = useState(false); const [isEditDescriptionOpened, setIsEditDescriptionOpened] = useState(false);
@@ -163,11 +175,11 @@ const StoryContent = React.memo(() => {
(description) => { (description) => {
dispatch( dispatch(
entryActions.updateCurrentCard({ entryActions.updateCurrentCard({
description, description: description && mentionTextToMarkup(description, userByUsername),
}), }),
); );
}, },
[dispatch], [dispatch, userByUsername],
); );
const handleRestoreClick = useCallback(() => { const handleRestoreClick = useCallback(() => {