mirror of
https://github.com/plankanban/planka.git
synced 2025-12-25 09:15:00 +03:00
fix: Improve mentions behavior
This commit is contained in:
@@ -3,7 +3,8 @@
|
||||
* Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md
|
||||
*/
|
||||
|
||||
import React, { useCallback, useState, useRef } from 'react';
|
||||
import keyBy from 'lodash/keyBy';
|
||||
import React, { useCallback, useState, useRef, useMemo } from 'react';
|
||||
import { useDispatch, useSelector } from 'react-redux';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { Mention, MentionsInput } from 'react-mentions';
|
||||
@@ -13,6 +14,7 @@ import { useClickAwayListener, useDidUpdate, useToggle } from '../../../lib/hook
|
||||
import selectors from '../../../selectors';
|
||||
import entryActions from '../../../entry-actions';
|
||||
import { useEscapeInterceptor, useForm, useNestedRef } from '../../../hooks';
|
||||
import { isUsernameChar, mentionTextToMarkup } from '../../../utils/mentions';
|
||||
import { isModifierKeyPressed } from '../../../utils/event-helpers';
|
||||
import UserAvatar from '../../users/UserAvatar';
|
||||
|
||||
@@ -36,10 +38,19 @@ const Add = React.memo(() => {
|
||||
const textInputRef = useRef(null);
|
||||
const [buttonRef, handleButtonRef] = useNestedRef();
|
||||
|
||||
const userByUsername = useMemo(
|
||||
() =>
|
||||
keyBy(
|
||||
boardMemberships.flatMap(({ user }) => (user.username ? user : [])),
|
||||
'username',
|
||||
),
|
||||
[boardMemberships],
|
||||
);
|
||||
|
||||
const submit = useCallback(() => {
|
||||
const cleanData = {
|
||||
...data,
|
||||
text: data.text.trim(),
|
||||
text: mentionTextToMarkup(data.text.trim(), userByUsername),
|
||||
};
|
||||
|
||||
if (!cleanData.text) {
|
||||
@@ -50,7 +61,7 @@ const Add = React.memo(() => {
|
||||
dispatch(entryActions.createCommentInCurrentCard(cleanData));
|
||||
setData(DEFAULT_DATA);
|
||||
selectTextField();
|
||||
}, [dispatch, data, setData, selectTextField]);
|
||||
}, [dispatch, data, setData, selectTextField, userByUsername]);
|
||||
|
||||
const handleEscape = useCallback(() => {
|
||||
if (textMentionsRef.current.isOpened()) {
|
||||
@@ -76,10 +87,10 @@ const Add = React.memo(() => {
|
||||
const handleFieldChange = useCallback(
|
||||
(_, text) => {
|
||||
setData({
|
||||
text,
|
||||
text: !isUsernameChar(text.slice(-1)) ? mentionTextToMarkup(text, userByUsername) : text,
|
||||
});
|
||||
},
|
||||
[setData],
|
||||
[setData, userByUsername],
|
||||
);
|
||||
|
||||
const handleFieldKeyDown = useCallback(
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
*/
|
||||
|
||||
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';
|
||||
@@ -15,6 +16,7 @@ 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';
|
||||
@@ -48,10 +50,19 @@ const Edit = React.memo(({ commentId, onClose }) => {
|
||||
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: data.text.trim(),
|
||||
text: mentionTextToMarkup(data.text.trim(), userByUsername),
|
||||
};
|
||||
|
||||
if (cleanData.text && !dequal(cleanData, defaultData)) {
|
||||
@@ -59,7 +70,7 @@ const Edit = React.memo(({ commentId, onClose }) => {
|
||||
}
|
||||
|
||||
onClose();
|
||||
}, [commentId, onClose, dispatch, defaultData, data]);
|
||||
}, [commentId, onClose, dispatch, defaultData, data, userByUsername]);
|
||||
|
||||
const handleSubmit = useCallback(() => {
|
||||
submit();
|
||||
@@ -68,10 +79,10 @@ const Edit = React.memo(({ commentId, onClose }) => {
|
||||
const handleFieldChange = useCallback(
|
||||
(_, text) => {
|
||||
setData({
|
||||
text,
|
||||
text: !isUsernameChar(text.slice(-1)) ? mentionTextToMarkup(text, userByUsername) : text,
|
||||
});
|
||||
},
|
||||
[setData],
|
||||
[setData, userByUsername],
|
||||
);
|
||||
|
||||
const handleFieldKeyDown = useCallback(
|
||||
|
||||
@@ -13,7 +13,7 @@ import { Button } from 'semantic-ui-react';
|
||||
|
||||
import selectors from '../../../selectors';
|
||||
import entryActions from '../../../entry-actions';
|
||||
import { formatTextWithMentions } from '../../../utils/mentions';
|
||||
import { mentionMarkupToText } from '../../../utils/mentions';
|
||||
import Paths from '../../../constants/Paths';
|
||||
import { StaticUserIds } from '../../../constants/StaticUsers';
|
||||
import { NotificationTypes } from '../../../constants/Enums';
|
||||
@@ -84,7 +84,7 @@ const Item = React.memo(({ id, onClose }) => {
|
||||
break;
|
||||
}
|
||||
case NotificationTypes.COMMENT_CARD: {
|
||||
const commentText = truncate(formatTextWithMentions(notification.data.text));
|
||||
const commentText = truncate(mentionMarkupToText(notification.data.text));
|
||||
|
||||
contentNode = (
|
||||
<Trans
|
||||
@@ -124,7 +124,7 @@ const Item = React.memo(({ id, onClose }) => {
|
||||
|
||||
break;
|
||||
case NotificationTypes.MENTION_IN_COMMENT: {
|
||||
const commentText = truncate(formatTextWithMentions(notification.data.text));
|
||||
const commentText = truncate(mentionMarkupToText(notification.data.text));
|
||||
|
||||
contentNode = (
|
||||
<Trans
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
* Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md
|
||||
*/
|
||||
|
||||
import { orderBy } from 'lodash';
|
||||
import orderBy from 'lodash/orderBy';
|
||||
import { attr } from 'redux-orm';
|
||||
|
||||
import BaseModel from './BaseModel';
|
||||
|
||||
@@ -3,7 +3,23 @@
|
||||
* Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md
|
||||
*/
|
||||
|
||||
export const MENTION_REGEX = /@\[(.*?)\]\((.*?)\)/g;
|
||||
export const MENTION_NAME_REGEX = /@\[(.*?)\]\(.*?\)/g;
|
||||
const USERNAME_CHAR_CLASS = 'a-zA-Z0-9._';
|
||||
const USERNAME_CHAR_REGEX = new RegExp(`^[${USERNAME_CHAR_CLASS}]$`);
|
||||
|
||||
export const formatTextWithMentions = (text) => text.replace(MENTION_NAME_REGEX, '@$1');
|
||||
const MENTION_TEXT_REGEX = new RegExp(
|
||||
`(^|[^${USERNAME_CHAR_CLASS}])@([${USERNAME_CHAR_CLASS}]+)`,
|
||||
'gi',
|
||||
);
|
||||
|
||||
const MENTION_MARKUP_REGEX = /@\[(.*?)\]\((.*?)\)/g;
|
||||
|
||||
export const mentionTextToMarkup = (text, userByUsername) =>
|
||||
text.replace(MENTION_TEXT_REGEX, (match, before, username) => {
|
||||
const user = userByUsername[username.toLowerCase()];
|
||||
return user ? `${before}@[${user.username}](${user.id})` : match;
|
||||
});
|
||||
|
||||
export const mentionMarkupToText = (markup) =>
|
||||
markup.replace(MENTION_MARKUP_REGEX, (_, username) => `@${username}`);
|
||||
|
||||
export const isUsernameChar = (char) => USERNAME_CHAR_REGEX.test(char);
|
||||
|
||||
Reference in New Issue
Block a user