mirror of
https://github.com/plankanban/planka.git
synced 2025-07-18 20:59:44 +02:00
hook with backend
This commit is contained in:
parent
3f67d9e8bb
commit
946dfea5dd
8 changed files with 104 additions and 15 deletions
|
@ -3,7 +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 React, { useCallback, useState } from 'react';
|
import React, { useCallback, useState, useRef } from 'react';
|
||||||
import { useDispatch, useSelector } from 'react-redux';
|
import { useDispatch, useSelector } from 'react-redux';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import { Button, Form } from 'semantic-ui-react';
|
import { Button, Form } from 'semantic-ui-react';
|
||||||
|
@ -29,7 +29,8 @@ const Add = React.memo(() => {
|
||||||
const [data, handleFieldChange, setData] = useForm(DEFAULT_DATA);
|
const [data, handleFieldChange, setData] = useForm(DEFAULT_DATA);
|
||||||
const [selectTextFieldState, selectTextField] = useToggle();
|
const [selectTextFieldState, selectTextField] = useToggle();
|
||||||
|
|
||||||
const textFieldRef = React.createRef();
|
const textFieldRef = useRef(null);
|
||||||
|
const mentionsInputRef = useRef(null);
|
||||||
const [buttonRef, handleButtonRef] = useNestedRef();
|
const [buttonRef, handleButtonRef] = useNestedRef();
|
||||||
|
|
||||||
const mentionsInputStyle = {
|
const mentionsInputStyle = {
|
||||||
|
@ -65,8 +66,12 @@ const Add = React.memo(() => {
|
||||||
}, [dispatch, data, setData, selectTextField, textFieldRef]);
|
}, [dispatch, data, setData, selectTextField, textFieldRef]);
|
||||||
|
|
||||||
const handleEscape = useCallback(() => {
|
const handleEscape = useCallback(() => {
|
||||||
|
if (mentionsInputRef?.current?.isOpened()) {
|
||||||
|
mentionsInputRef?.current.clearSuggestions();
|
||||||
|
return;
|
||||||
|
}
|
||||||
setIsOpened(false);
|
setIsOpened(false);
|
||||||
textFieldRef.current.blur();
|
textFieldRef.current?.blur();
|
||||||
}, [textFieldRef]);
|
}, [textFieldRef]);
|
||||||
|
|
||||||
const [activateEscapeInterceptor, deactivateEscapeInterceptor] =
|
const [activateEscapeInterceptor, deactivateEscapeInterceptor] =
|
||||||
|
@ -89,10 +94,7 @@ const Add = React.memo(() => {
|
||||||
[submit],
|
[submit],
|
||||||
);
|
);
|
||||||
|
|
||||||
const handleAwayClick = useCallback((event) => {
|
const handleAwayClick = useCallback(() => {
|
||||||
if (event?.target?.closest?.('.mentions-input')) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
setIsOpened(false);
|
setIsOpened(false);
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
|
@ -134,6 +136,9 @@ const Add = React.memo(() => {
|
||||||
<Form onSubmit={handleSubmit}>
|
<Form onSubmit={handleSubmit}>
|
||||||
<div className={styles.field}>
|
<div className={styles.field}>
|
||||||
<MentionsInput
|
<MentionsInput
|
||||||
|
// eslint-disable-next-line react/jsx-props-no-spreading
|
||||||
|
{...clickAwayProps}
|
||||||
|
ref={mentionsInputRef}
|
||||||
inputRef={textFieldRef}
|
inputRef={textFieldRef}
|
||||||
value={data.text}
|
value={data.text}
|
||||||
placeholder={t('common.writeComment')}
|
placeholder={t('common.writeComment')}
|
||||||
|
@ -142,8 +147,6 @@ const Add = React.memo(() => {
|
||||||
onFocus={handleFieldFocus}
|
onFocus={handleFieldFocus}
|
||||||
onChange={handleFormFieldChange}
|
onChange={handleFormFieldChange}
|
||||||
onKeyDown={handleFieldKeyDown}
|
onKeyDown={handleFieldKeyDown}
|
||||||
onMouseDown={clickAwayProps.onMouseDown}
|
|
||||||
onTouchStart={clickAwayProps.onTouchStart}
|
|
||||||
allowSpaceInQuery
|
allowSpaceInQuery
|
||||||
singleLine={false}
|
singleLine={false}
|
||||||
rows={isOpened ? 3 : 1}
|
rows={isOpened ? 3 : 1}
|
||||||
|
|
|
@ -21,6 +21,11 @@ import UserAvatar from '../../users/UserAvatar';
|
||||||
|
|
||||||
import styles from './Item.module.scss';
|
import styles from './Item.module.scss';
|
||||||
|
|
||||||
|
const formatMentionText = (text) => {
|
||||||
|
// Replace @[username](userId) with @username
|
||||||
|
return text.replace(/@\[(.*?)\]\(.*?\)/g, '@$1');
|
||||||
|
};
|
||||||
|
|
||||||
const Item = React.memo(({ id, onClose }) => {
|
const Item = React.memo(({ id, onClose }) => {
|
||||||
const selectNotificationById = useMemo(() => selectors.makeSelectNotificationById(), []);
|
const selectNotificationById = useMemo(() => selectors.makeSelectNotificationById(), []);
|
||||||
const selectCreatorUserById = useMemo(() => selectors.makeSelectUserById(), []);
|
const selectCreatorUserById = useMemo(() => selectors.makeSelectUserById(), []);
|
||||||
|
@ -104,6 +109,29 @@ const Item = React.memo(({ id, onClose }) => {
|
||||||
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
case NotificationTypes.COMMENT_MENTION: {
|
||||||
|
const commentText = truncate(formatMentionText(notification.data.text));
|
||||||
|
|
||||||
|
contentNode = (
|
||||||
|
<Trans
|
||||||
|
i18nKey="common.userMentionedYouInCard"
|
||||||
|
values={{
|
||||||
|
user: creatorUserName,
|
||||||
|
comment: commentText,
|
||||||
|
card: cardName,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<span className={styles.author}>{creatorUserName}</span>
|
||||||
|
{` mentioned you in `}
|
||||||
|
<Link to={Paths.CARDS.replace(':id', notification.cardId)} onClick={onClose}>
|
||||||
|
{cardName}
|
||||||
|
</Link>
|
||||||
|
{`: «${commentText}»`}
|
||||||
|
</Trans>
|
||||||
|
);
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
case NotificationTypes.ADD_MEMBER_TO_CARD:
|
case NotificationTypes.ADD_MEMBER_TO_CARD:
|
||||||
contentNode = (
|
contentNode = (
|
||||||
<Trans
|
<Trans
|
||||||
|
|
|
@ -99,6 +99,7 @@ export const ActivityTypes = {
|
||||||
export const NotificationTypes = {
|
export const NotificationTypes = {
|
||||||
MOVE_CARD: 'moveCard',
|
MOVE_CARD: 'moveCard',
|
||||||
COMMENT_CARD: 'commentCard',
|
COMMENT_CARD: 'commentCard',
|
||||||
|
COMMENT_MENTION: 'commentMention',
|
||||||
ADD_MEMBER_TO_CARD: 'addMemberToCard',
|
ADD_MEMBER_TO_CARD: 'addMemberToCard',
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -293,6 +293,7 @@ export default {
|
||||||
userJoinedThisCard: `<0>{{user}}</0> joined this card`,
|
userJoinedThisCard: `<0>{{user}}</0> joined this card`,
|
||||||
userLeftNewCommentToCard:
|
userLeftNewCommentToCard:
|
||||||
'<0>{{user}}</0> left a new comment «{{comment}}» to <2>{{card}}</2>',
|
'<0>{{user}}</0> left a new comment «{{comment}}» to <2>{{card}}</2>',
|
||||||
|
userMentionedYouInCard: '<0>{{user}}</0> mentioned you in <2>{{card}}</2>: «{{comment}}»',
|
||||||
userLeftCard: '<0>{{user}}</0> left <2>{{card}}</2>',
|
userLeftCard: '<0>{{user}}</0> left <2>{{card}}</2>',
|
||||||
userLeftThisCard: '<0>{{user}}</0> left this card',
|
userLeftThisCard: '<0>{{user}}</0> left this card',
|
||||||
userMarkedTaskIncompleteOnCard:
|
userMarkedTaskIncompleteOnCard:
|
||||||
|
|
|
@ -6,6 +6,12 @@
|
||||||
const escapeMarkdown = require('escape-markdown');
|
const escapeMarkdown = require('escape-markdown');
|
||||||
const escapeHtml = require('escape-html');
|
const escapeHtml = require('escape-html');
|
||||||
|
|
||||||
|
const extractMentionedUserIds = (text) => {
|
||||||
|
const mentionRegex = /@\[.*?\]\((.*?)\)/g;
|
||||||
|
const matches = [...text.matchAll(mentionRegex)];
|
||||||
|
return matches.map((match) => match[1]);
|
||||||
|
};
|
||||||
|
|
||||||
const buildAndSendNotifications = async (services, board, card, comment, actorUser, t) => {
|
const buildAndSendNotifications = async (services, board, card, comment, actorUser, t) => {
|
||||||
const markdownCardLink = `[${escapeMarkdown(card.name)}](${sails.config.custom.baseUrl}/cards/${card.id})`;
|
const markdownCardLink = `[${escapeMarkdown(card.name)}](${sails.config.custom.baseUrl}/cards/${card.id})`;
|
||||||
const htmlCardLink = `<a href="${sails.config.custom.baseUrl}/cards/${card.id}}">${escapeHtml(card.name)}</a>`;
|
const htmlCardLink = `<a href="${sails.config.custom.baseUrl}/cards/${card.id}}">${escapeHtml(card.name)}</a>`;
|
||||||
|
@ -101,7 +107,14 @@ module.exports = {
|
||||||
comment.userId,
|
comment.userId,
|
||||||
);
|
);
|
||||||
|
|
||||||
const notifiableUserIds = _.union(cardSubscriptionUserIds, boardSubscriptionUserIds);
|
const mentionedUserIds = extractMentionedUserIds(values.text);
|
||||||
|
|
||||||
|
// Combine all user IDs, removing duplicates and the comment author
|
||||||
|
const notifiableUserIds = [
|
||||||
|
...cardSubscriptionUserIds,
|
||||||
|
...boardSubscriptionUserIds,
|
||||||
|
...mentionedUserIds,
|
||||||
|
].filter((id) => id !== comment.userId);
|
||||||
|
|
||||||
await Promise.all(
|
await Promise.all(
|
||||||
notifiableUserIds.map((userId) =>
|
notifiableUserIds.map((userId) =>
|
||||||
|
@ -109,10 +122,13 @@ module.exports = {
|
||||||
values: {
|
values: {
|
||||||
userId,
|
userId,
|
||||||
comment,
|
comment,
|
||||||
type: Notification.Types.COMMENT_CARD,
|
type: mentionedUserIds.includes(userId)
|
||||||
|
? Notification.Types.COMMENT_MENTION
|
||||||
|
: Notification.Types.COMMENT_CARD,
|
||||||
data: {
|
data: {
|
||||||
card: _.pick(values.card, ['name']),
|
card: _.pick(values.card, ['name']),
|
||||||
text: comment.text,
|
text: comment.text,
|
||||||
|
wasMentioned: mentionedUserIds.includes(userId),
|
||||||
},
|
},
|
||||||
creatorUser: values.user,
|
creatorUser: values.user,
|
||||||
card: values.card,
|
card: values.card,
|
||||||
|
|
|
@ -12,6 +12,8 @@ const buildTitle = (notification, t) => {
|
||||||
return t('Card Moved');
|
return t('Card Moved');
|
||||||
case Notification.Types.COMMENT_CARD:
|
case Notification.Types.COMMENT_CARD:
|
||||||
return t('New Comment');
|
return t('New Comment');
|
||||||
|
case Notification.Types.COMMENT_MENTION:
|
||||||
|
return t('You Were Mentioned in a Comment');
|
||||||
case Notification.Types.ADD_MEMBER_TO_CARD:
|
case Notification.Types.ADD_MEMBER_TO_CARD:
|
||||||
return t('You Were Added to Card');
|
return t('You Were Added to Card');
|
||||||
default:
|
default:
|
||||||
|
@ -79,6 +81,30 @@ const buildBodyByFormat = (board, card, notification, actorUser, t) => {
|
||||||
)}:\n\n<i>${escapeHtml(commentText)}</i>`,
|
)}:\n\n<i>${escapeHtml(commentText)}</i>`,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
case Notification.Types.COMMENT_MENTION: {
|
||||||
|
const commentText = _.truncate(notification.data.text);
|
||||||
|
|
||||||
|
return {
|
||||||
|
text: `${t(
|
||||||
|
'%s mentioned you in %s on %s',
|
||||||
|
actorUser.name,
|
||||||
|
card.name,
|
||||||
|
board.name,
|
||||||
|
)}:\n${commentText}`,
|
||||||
|
markdown: `${t(
|
||||||
|
'%s mentioned you in %s on %s',
|
||||||
|
escapeMarkdown(actorUser.name),
|
||||||
|
markdownCardLink,
|
||||||
|
escapeMarkdown(board.name),
|
||||||
|
)}:\n\n*${escapeMarkdown(commentText)}*`,
|
||||||
|
html: `${t(
|
||||||
|
'%s mentioned you in %s on %s',
|
||||||
|
escapeHtml(actorUser.name),
|
||||||
|
htmlCardLink,
|
||||||
|
escapeHtml(board.name),
|
||||||
|
)}:\n\n<i>${escapeHtml(commentText)}</i>`,
|
||||||
|
};
|
||||||
|
}
|
||||||
case Notification.Types.ADD_MEMBER_TO_CARD:
|
case Notification.Types.ADD_MEMBER_TO_CARD:
|
||||||
return {
|
return {
|
||||||
text: t('%s added you to %s on %s', actorUser.name, card.name, board.name),
|
text: t('%s added you to %s on %s', actorUser.name, card.name, board.name),
|
||||||
|
@ -138,6 +164,15 @@ const buildAndSendEmail = async (board, card, notification, actorUser, notifiabl
|
||||||
boardLink,
|
boardLink,
|
||||||
)}</p><p>${escapeHtml(notification.data.text)}</p>`;
|
)}</p><p>${escapeHtml(notification.data.text)}</p>`;
|
||||||
|
|
||||||
|
break;
|
||||||
|
case Notification.Types.COMMENT_MENTION:
|
||||||
|
html = `<p>${t(
|
||||||
|
'%s mentioned you in %s on %s',
|
||||||
|
escapeHtml(actorUser.name),
|
||||||
|
cardLink,
|
||||||
|
boardLink,
|
||||||
|
)}</p><p>${escapeHtml(notification.data.text)}</p>`;
|
||||||
|
|
||||||
break;
|
break;
|
||||||
case Notification.Types.ADD_MEMBER_TO_CARD:
|
case Notification.Types.ADD_MEMBER_TO_CARD:
|
||||||
html = `<p>${t(
|
html = `<p>${t(
|
||||||
|
@ -186,9 +221,11 @@ module.exports = {
|
||||||
values.userId = values.user.id;
|
values.userId = values.user.id;
|
||||||
}
|
}
|
||||||
|
|
||||||
const isCommentCard = values.type === Notification.Types.COMMENT_CARD;
|
const isCommentNotification =
|
||||||
|
values.type === Notification.Types.COMMENT_CARD ||
|
||||||
|
values.type === Notification.Types.COMMENT_MENTION;
|
||||||
|
|
||||||
if (isCommentCard) {
|
if (isCommentNotification) {
|
||||||
values.commentId = values.comment.id;
|
values.commentId = values.comment.id;
|
||||||
} else {
|
} else {
|
||||||
values.actionId = values.action.id;
|
values.actionId = values.action.id;
|
||||||
|
@ -217,7 +254,7 @@ module.exports = {
|
||||||
boards: [inputs.board],
|
boards: [inputs.board],
|
||||||
lists: [inputs.list],
|
lists: [inputs.list],
|
||||||
cards: [values.card],
|
cards: [values.card],
|
||||||
...(isCommentCard
|
...(isCommentNotification
|
||||||
? {
|
? {
|
||||||
comments: [values.comment],
|
comments: [values.comment],
|
||||||
}
|
}
|
||||||
|
|
|
@ -13,6 +13,7 @@
|
||||||
const Types = {
|
const Types = {
|
||||||
MOVE_CARD: 'moveCard',
|
MOVE_CARD: 'moveCard',
|
||||||
COMMENT_CARD: 'commentCard',
|
COMMENT_CARD: 'commentCard',
|
||||||
|
COMMENT_MENTION: 'commentMention',
|
||||||
ADD_MEMBER_TO_CARD: 'addMemberToCard',
|
ADD_MEMBER_TO_CARD: 'addMemberToCard',
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -2,6 +2,7 @@
|
||||||
"Card Created": "Card Created",
|
"Card Created": "Card Created",
|
||||||
"Card Moved": "Card Moved",
|
"Card Moved": "Card Moved",
|
||||||
"New Comment": "New Comment",
|
"New Comment": "New Comment",
|
||||||
|
"You Were Mentioned in a Comment": "You Were Mentioned in a Comment",
|
||||||
"Test Title": "Test Title",
|
"Test Title": "Test Title",
|
||||||
"This is a test text message!": "This is a test text message!",
|
"This is a test text message!": "This is a test text message!",
|
||||||
"This is a *test* **markdown** `message`!": "This is a *test* **markdown** `message`!",
|
"This is a *test* **markdown** `message`!": "This is a *test* **markdown** `message`!",
|
||||||
|
@ -10,5 +11,6 @@
|
||||||
"%s added you to %s on %s": "%s added you to %s on %s",
|
"%s added you to %s on %s": "%s added you to %s on %s",
|
||||||
"%s created %s in %s on %s": "%s created %s in %s on %s",
|
"%s created %s in %s on %s": "%s created %s in %s on %s",
|
||||||
"%s left a new comment to %s on %s": "%s left a new comment to %s on %s",
|
"%s left a new comment to %s on %s": "%s left a new comment to %s on %s",
|
||||||
"%s moved %s from %s to %s on %s": "%s moved %s from %s to %s on %s"
|
"%s moved %s from %s to %s on %s": "%s moved %s from %s to %s on %s",
|
||||||
|
"%s mentioned you in %s on %s": "%s mentioned you in %s on %s"
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue