From c29962174e5391df0c7c9c9b48d2eb55b06098ec Mon Sep 17 00:00:00 2001 From: Maksim Eltyshev Date: Fri, 30 May 2025 21:57:15 +0200 Subject: [PATCH] ref: Refactoring --- .../src/components/comments/Comments/Add.jsx | 104 +++++++++-------- .../comments/Comments/Add.module.scss | 106 ++++-------------- .../comments/Comments/Edit.module.scss | 1 - .../notifications/NotificationsStep/Item.jsx | 53 ++++----- .../users/UserAvatar/UserAvatar.jsx | 2 +- client/src/configs/markdown-plugins/index.js | 4 +- .../{mentions.js => mention.js} | 32 ++---- client/src/constants/Enums.js | 2 +- client/src/locales/en-GB/core.js | 2 + client/src/locales/en-US/core.js | 3 +- client/src/styles.module.scss | 32 ++++++ client/src/utils/formatters.js | 4 + server/api/helpers/comments/create-one.js | 35 ++++-- .../api/helpers/notifications/create-one.js | 82 +++++++------- server/api/models/Notification.js | 2 +- server/config/locales/en-GB.json | 2 + server/config/locales/en-US.json | 6 +- server/config/locales/ru-RU.json | 2 + server/utils/formatters.js | 7 ++ 19 files changed, 234 insertions(+), 247 deletions(-) rename client/src/configs/markdown-plugins/{mentions.js => mention.js} (64%) create mode 100644 client/src/utils/formatters.js create mode 100644 server/utils/formatters.js diff --git a/client/src/components/comments/Comments/Add.jsx b/client/src/components/comments/Comments/Add.jsx index 3608b8bc..24cdf11d 100755 --- a/client/src/components/comments/Comments/Add.jsx +++ b/client/src/components/comments/Comments/Add.jsx @@ -6,15 +6,15 @@ import React, { useCallback, useState, useRef } from 'react'; 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 { MentionsInput, Mention } from 'react-mentions'; import { useClickAwayListener, useDidUpdate, useToggle } from '../../../lib/hooks'; import selectors from '../../../selectors'; import entryActions from '../../../entry-actions'; import { useEscapeInterceptor, useForm, useNestedRef } from '../../../hooks'; import { isModifierKeyPressed } from '../../../utils/event-helpers'; -import UserAvatar, { Sizes } from '../../users/UserAvatar/UserAvatar'; +import UserAvatar from '../../users/UserAvatar'; import styles from './Add.module.scss'; @@ -23,32 +23,18 @@ const DEFAULT_DATA = { }; const Add = React.memo(() => { + const boardMemberships = useSelector(selectors.selectMembershipsForCurrentBoard); + const dispatch = useDispatch(); const [t] = useTranslation(); + const [data, , setData] = useForm(DEFAULT_DATA); const [isOpened, setIsOpened] = useState(false); - const [data, handleFieldChange, setData] = useForm(DEFAULT_DATA); const [selectTextFieldState, selectTextField] = useToggle(); - const textFieldRef = useRef(null); const mentionsInputRef = useRef(null); + const textFieldRef = useRef(null); const [buttonRef, handleButtonRef] = useNestedRef(); - const mentionsInputStyle = { - control: { - minHeight: isOpened ? '60px' : '36px', - }, - }; - - const renderSuggestion = useCallback( - (suggestion, search, highlightedDisplay) => ( -
- - {highlightedDisplay} -
- ), - [], - ); - const submit = useCallback(() => { const cleanData = { ...data, @@ -56,7 +42,7 @@ const Add = React.memo(() => { }; if (!cleanData.text) { - textFieldRef.current?.select(); + textFieldRef.current.select(); return; } @@ -66,12 +52,13 @@ const Add = React.memo(() => { }, [dispatch, data, setData, selectTextField, textFieldRef]); const handleEscape = useCallback(() => { - if (mentionsInputRef?.current?.isOpened()) { - mentionsInputRef?.current.clearSuggestions(); + if (mentionsInputRef.current.isOpened()) { + mentionsInputRef.current.clearSuggestions(); return; } + setIsOpened(false); - textFieldRef.current?.blur(); + textFieldRef.current.blur(); }, [textFieldRef]); const [activateEscapeInterceptor, deactivateEscapeInterceptor] = @@ -85,6 +72,15 @@ const Add = React.memo(() => { setIsOpened(true); }, []); + const handleFieldChange = useCallback( + (_, text) => { + setData({ + text, + }); + }, + [setData], + ); + const handleFieldKeyDown = useCallback( (event) => { if (isModifierKeyPressed(event) && event.key === 'Enter') { @@ -99,7 +95,7 @@ const Add = React.memo(() => { }, []); const handleClickAwayCancel = useCallback(() => { - textFieldRef.current?.focus(); + textFieldRef.current.focus(); }, [textFieldRef]); const clickAwayProps = useClickAwayListener( @@ -108,16 +104,14 @@ const Add = React.memo(() => { handleClickAwayCancel, ); - const users = useSelector(selectors.selectMembershipsForCurrentBoard); - - const handleFormFieldChange = useCallback( - (event, newValue) => { - handleFieldChange(null, { - name: 'text', - value: newValue, - }); - }, - [handleFieldChange], + const suggestionRenderer = useCallback( + (entry, _, highlightedDisplay) => ( +
+ + {highlightedDisplay} +
+ ), + [], ); useDidUpdate(() => { @@ -129,39 +123,41 @@ const Add = React.memo(() => { }, [isOpened]); useDidUpdate(() => { - textFieldRef.current?.focus(); + textFieldRef.current.focus(); }, [selectTextFieldState]); return (
({ - id: membership.user.id, - display: membership.user.username || membership.user.name, - }))} - markup="@[__display__](__id__)" appendSpaceOnAdd - displayTransform={(id, display) => `@${display}`} - renderSuggestion={renderSuggestion} + data={boardMemberships.map(({ user }) => ({ + id: user.id, + display: user.username || user.name, + }))} + displayTransform={(_, display) => `@${display}`} + renderSuggestion={suggestionRenderer} + className={styles.mention} />
diff --git a/client/src/components/comments/Comments/Add.module.scss b/client/src/components/comments/Comments/Add.module.scss index e3aafd2a..f05d66c5 100644 --- a/client/src/components/comments/Comments/Add.module.scss +++ b/client/src/components/comments/Comments/Add.module.scss @@ -16,94 +16,36 @@ } .field { - position: relative; - margin-bottom: 8px !important; background: #fff; - border-radius: 4px; + margin-bottom: 8px !important; - :global(.mentions-input) { - width: 100%; - - textarea { - background: #fff; - box-shadow: none; - border: 0; - box-sizing: border-box; - color: #333; - display: block; - line-height: 1.5; - font-size: 14px; - overflow: hidden; - padding: 8px 12px; - resize: none; - width: 100%; - - &:focus { - outline: none; - } - } - } - } -} - -:global { - .mentions-input { - width: 100%; - - &__suggestions { - position: absolute; - z-index: 1000; - margin-top: 4px; - background: white; - border: 1px solid rgba(34, 36, 38, 0.15); - border-radius: 4px; - box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1); - max-height: 200px; - overflow-y: auto; - - &__list { - margin: 0; - padding: 0; - list-style: none; - } - - &__item { - display: flex; - padding: 8px 12px; - cursor: pointer; - font-size: 14px; - - &--focused { - background-color: #f1f8ff; - } - } - } - - &__highlighter { - padding: 8px 12px; - font-size: 14px; - font-family: 'Open Sans', sans-serif; + textarea { + background: #fff; + border: 1px solid rgba(9, 30, 66, 0.13); + border-radius: 3px; + box-sizing: border-box; + color: #333; + display: block; line-height: 1.4; - } - - &__control { - position: relative; - } - - &__input { + font-size: 14px; overflow: hidden; + padding: 8px 12px; + resize: none; + + &:focus { + outline: none; + } } } -} -.suggestion { - display: flex; - align-items: center; - gap: 8px; -} + .mention { + background-color: #f1f8ff; + border-radius: 3px; + } -.suggestionText { - flex: 1; - font-size: 14px; - line-height: 1.4; + .suggestion { + align-items: center; + display: flex; + gap: 8px; + } } diff --git a/client/src/components/comments/Comments/Edit.module.scss b/client/src/components/comments/Comments/Edit.module.scss index 6816868d..73890c39 100644 --- a/client/src/components/comments/Comments/Edit.module.scss +++ b/client/src/components/comments/Comments/Edit.module.scss @@ -22,7 +22,6 @@ overflow: hidden; padding: 8px 12px; resize: none; - width: 100%; &:focus { outline: none; diff --git a/client/src/components/notifications/NotificationsStep/Item.jsx b/client/src/components/notifications/NotificationsStep/Item.jsx index b6c54647..892a75cf 100644 --- a/client/src/components/notifications/NotificationsStep/Item.jsx +++ b/client/src/components/notifications/NotificationsStep/Item.jsx @@ -13,6 +13,7 @@ import { Button } from 'semantic-ui-react'; import selectors from '../../../selectors'; import entryActions from '../../../entry-actions'; +import { formatTextWithMentions } from '../../../utils/formatters'; import Paths from '../../../constants/Paths'; import { StaticUserIds } from '../../../constants/StaticUsers'; import { NotificationTypes } from '../../../constants/Enums'; @@ -21,11 +22,6 @@ import UserAvatar from '../../users/UserAvatar'; 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 selectNotificationById = useMemo(() => selectors.makeSelectNotificationById(), []); const selectCreatorUserById = useMemo(() => selectors.makeSelectUserById(), []); @@ -88,7 +84,7 @@ const Item = React.memo(({ id, onClose }) => { break; } case NotificationTypes.COMMENT_CARD: { - const commentText = truncate(notification.data.text); + const commentText = truncate(formatTextWithMentions(notification.data.text)); contentNode = ( { break; } - case NotificationTypes.COMMENT_MENTION: { - const commentText = truncate(formatMentionText(notification.data.text)); - - contentNode = ( - - {creatorUserName} - {` mentioned you in `} - - {cardName} - - {`: «${commentText}»`} - - ); - - break; - } case NotificationTypes.ADD_MEMBER_TO_CARD: contentNode = ( { ); break; + case NotificationTypes.MENTION_IN_COMMENT: { + const commentText = truncate(formatTextWithMentions(notification.data.text)); + + contentNode = ( + + {creatorUserName} + {` mentioned you in «${commentText}» on `} + + {cardName} + + + ); + + break; + } default: contentNode = null; } diff --git a/client/src/components/users/UserAvatar/UserAvatar.jsx b/client/src/components/users/UserAvatar/UserAvatar.jsx index 005a5344..323e717b 100755 --- a/client/src/components/users/UserAvatar/UserAvatar.jsx +++ b/client/src/components/users/UserAvatar/UserAvatar.jsx @@ -17,7 +17,7 @@ import { StaticUserIds } from '../../../constants/StaticUsers'; import styles from './UserAvatar.module.scss'; -export const Sizes = { +const Sizes = { TINY: 'tiny', SMALL: 'small', MEDIUM: 'medium', diff --git a/client/src/configs/markdown-plugins/index.js b/client/src/configs/markdown-plugins/index.js index d9a43860..a1ee26aa 100644 --- a/client/src/configs/markdown-plugins/index.js +++ b/client/src/configs/markdown-plugins/index.js @@ -23,7 +23,7 @@ import { emojiDefs } from '@gravity-ui/markdown-editor/_/bundle/emoji'; /* eslint-enable import/no-unresolved */ import link from './link'; -import mentions from './mentions'; +import mention from './mention'; export default [ ins, @@ -42,5 +42,5 @@ export default [ meta, deflist, link, - mentions, + mention, ]; diff --git a/client/src/configs/markdown-plugins/mentions.js b/client/src/configs/markdown-plugins/mention.js similarity index 64% rename from client/src/configs/markdown-plugins/mentions.js rename to client/src/configs/markdown-plugins/mention.js index 2c006944..ec775c42 100644 --- a/client/src/configs/markdown-plugins/mentions.js +++ b/client/src/configs/markdown-plugins/mention.js @@ -3,23 +3,14 @@ * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md */ -const mentionsPlugin = (md) => { - const mentionRegex = /@\[(.*?)\]\((.*?)\)/g; +const MENTION_REGEX = /@\[(.*?)\]\((.*?)\)/g; - const renderMention = (tokens, idx) => { - const token = tokens[idx]; - const { display, userId } = token.meta; - - return `@${display}`; - }; - - md.core.ruler.push('mentions', (state) => { - const { tokens } = state; - - for (let i = 0; i < tokens.length; i += 1) { - const token = tokens[i]; +export default (md) => { + md.core.ruler.push('mention', ({ tokens }) => { + tokens.forEach((token) => { if (token.type === 'inline' && token.content) { - const matches = [...token.content.matchAll(mentionRegex)]; + const matches = [...token.content.matchAll(MENTION_REGEX)]; + if (matches.length > 0) { const newChildren = []; let lastIndex = 0; @@ -56,14 +47,15 @@ const mentionsPlugin = (md) => { }); } - token.children = newChildren; + token.children = newChildren; // eslint-disable-line no-param-reassign } } - } + }); }); // eslint-disable-next-line no-param-reassign - md.renderer.rules.mention = renderMention; + md.renderer.rules.mention = (tokens, index) => { + const { display, userId } = tokens[index].meta; + return `@${display}`; + }; }; - -export default mentionsPlugin; diff --git a/client/src/constants/Enums.js b/client/src/constants/Enums.js index ef120e24..ca4704ba 100755 --- a/client/src/constants/Enums.js +++ b/client/src/constants/Enums.js @@ -99,8 +99,8 @@ export const ActivityTypes = { export const NotificationTypes = { MOVE_CARD: 'moveCard', COMMENT_CARD: 'commentCard', - COMMENT_MENTION: 'commentMention', ADD_MEMBER_TO_CARD: 'addMemberToCard', + MENTION_IN_COMMENT: 'mentionInComment', }; export const NotificationServiceFormats = { diff --git a/client/src/locales/en-GB/core.js b/client/src/locales/en-GB/core.js index 37cbe476..02386925 100644 --- a/client/src/locales/en-GB/core.js +++ b/client/src/locales/en-GB/core.js @@ -303,6 +303,8 @@ export default { userMarkedTaskIncompleteOnCard: '<0>{{user}} marked {{task}} incomplete on <4>{{card}}', userMarkedTaskIncompleteOnThisCard: '<0>{{user}} marked {{task}} incomplete on this card', + userMentionedYouInCommentOnCard: + '<0>{{user}} mentioned you in a comment «{{comment}}» on <2>{{card}}', userMovedCardFromListToList: '<0>{{user}} moved <2>{{card}} from {{fromList}} to {{toList}}', userMovedThisCardFromListToList: diff --git a/client/src/locales/en-US/core.js b/client/src/locales/en-US/core.js index 0e347bf1..7475c7bf 100644 --- a/client/src/locales/en-US/core.js +++ b/client/src/locales/en-US/core.js @@ -293,12 +293,13 @@ export default { userJoinedThisCard: `<0>{{user}} joined this card`, userLeftNewCommentToCard: '<0>{{user}} left a new comment «{{comment}}» to <2>{{card}}', - userMentionedYouInCard: '<0>{{user}} mentioned you in <2>{{card}}: «{{comment}}»', userLeftCard: '<0>{{user}} left <2>{{card}}', userLeftThisCard: '<0>{{user}} left this card', userMarkedTaskIncompleteOnCard: '<0>{{user}} marked {{task}} incomplete on <4>{{card}}', userMarkedTaskIncompleteOnThisCard: '<0>{{user}} marked {{task}} incomplete on this card', + userMentionedYouInCommentOnCard: + '<0>{{user}} mentioned you in a comment «{{comment}}» on <2>{{card}}', userMovedCardFromListToList: '<0>{{user}} moved <2>{{card}} from {{fromList}} to {{toList}}', userMovedThisCardFromListToList: diff --git a/client/src/styles.module.scss b/client/src/styles.module.scss index 62a62d91..1b6acd9a 100644 --- a/client/src/styles.module.scss +++ b/client/src/styles.module.scss @@ -12,6 +12,31 @@ height: auto; } + .mentions-input { + &__highlighter { + line-height: 1.4; + padding: 8px 12px; + } + + &__suggestions { + border: 1px solid #d4d4d5; + border-radius: 3px; + box-shadow: 0 8px 16px -4px rgba(9, 45, 66, 0.25), + 0 0 0 1px rgba(9, 45, 66, 0.08); + max-height: 200px; + overflow-y: auto; + + &__item { + padding: 8px 12px; + + &--focused { + background-color: rgba(0, 0, 0, 0.05); + color: rgba(0, 0, 0, 0.95); + } + } + } + } + .react-datepicker { border: 0; color: #444444; @@ -192,6 +217,13 @@ font-size: .85em !important; } + .mention { + color: #0366d6; + background-color: #f1f8ff; + border-radius: 3px; + padding: 0 2px; + } + .yfm-clipboard:hover { .yfm-clipboard-button { min-height: auto; diff --git a/client/src/utils/formatters.js b/client/src/utils/formatters.js new file mode 100644 index 00000000..3c6a81ca --- /dev/null +++ b/client/src/utils/formatters.js @@ -0,0 +1,4 @@ +const MENTIONS_REGEX = /@\[(.*?)\]\(.*?\)/g; + +// eslint-disable-next-line import/prefer-default-export +export const formatTextWithMentions = (text) => text.replace(MENTIONS_REGEX, '@$1'); diff --git a/server/api/helpers/comments/create-one.js b/server/api/helpers/comments/create-one.js index 96278464..67f5b7c1 100644 --- a/server/api/helpers/comments/create-one.js +++ b/server/api/helpers/comments/create-one.js @@ -6,6 +6,8 @@ const escapeMarkdown = require('escape-markdown'); const escapeHtml = require('escape-html'); +const { formatTextWithMentions } = require('../../../utils/formatters'); + const extractMentionedUserIds = (text) => { const mentionRegex = /@\[.*?\]\((.*?)\)/g; const matches = [...text.matchAll(mentionRegex)]; @@ -15,7 +17,7 @@ const extractMentionedUserIds = (text) => { const buildAndSendNotifications = async (services, board, card, comment, actorUser, t) => { const markdownCardLink = `[${escapeMarkdown(card.name)}](${sails.config.custom.baseUrl}/cards/${card.id})`; const htmlCardLink = `${escapeHtml(card.name)}`; - const commentText = _.truncate(comment.text); + const commentText = _.truncate(formatTextWithMentions(comment.text)); await sails.helpers.utils.sendNotifications(services, t('New Comment'), { text: `${t( @@ -97,6 +99,19 @@ module.exports = { user: values.user, }); + let mentionedUserIds = extractMentionedUserIds(values.text); + + if (mentionedUserIds.length > 0) { + const boardMemberUserIds = await sails.helpers.boards.getMemberUserIds(inputs.board.id); + + mentionedUserIds = _.difference( + _.intersection(mentionedUserIds, boardMemberUserIds), + comment.userId, + ); + } + + const mentionedUserIdsSet = new Set(mentionedUserIds); + const cardSubscriptionUserIds = await sails.helpers.cards.getSubscriptionUserIds( comment.cardId, comment.userId, @@ -107,14 +122,11 @@ module.exports = { comment.userId, ); - 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); + const notifiableUserIds = _.union( + mentionedUserIds, + cardSubscriptionUserIds, + boardSubscriptionUserIds, + ); await Promise.all( notifiableUserIds.map((userId) => @@ -122,13 +134,12 @@ module.exports = { values: { userId, comment, - type: mentionedUserIds.includes(userId) - ? Notification.Types.COMMENT_MENTION + type: mentionedUserIdsSet.has(userId) + ? Notification.Types.MENTION_IN_COMMENT : Notification.Types.COMMENT_CARD, data: { card: _.pick(values.card, ['name']), text: comment.text, - wasMentioned: mentionedUserIds.includes(userId), }, creatorUser: values.user, card: values.card, diff --git a/server/api/helpers/notifications/create-one.js b/server/api/helpers/notifications/create-one.js index f6de485c..33a49e64 100644 --- a/server/api/helpers/notifications/create-one.js +++ b/server/api/helpers/notifications/create-one.js @@ -6,16 +6,18 @@ const escapeMarkdown = require('escape-markdown'); const escapeHtml = require('escape-html'); +const { formatTextWithMentions } = require('../../../utils/formatters'); + const buildTitle = (notification, t) => { switch (notification.type) { case Notification.Types.MOVE_CARD: return t('Card Moved'); case Notification.Types.COMMENT_CARD: return t('New Comment'); - case Notification.Types.COMMENT_MENTION: - return t('You Were Mentioned in a Comment'); case Notification.Types.ADD_MEMBER_TO_CARD: return t('You Were Added to Card'); + case Notification.Types.MENTION_IN_COMMENT: + return t('You Were Mentioned in Comment'); default: return null; } @@ -58,7 +60,7 @@ const buildBodyByFormat = (board, card, notification, actorUser, t) => { }; } case Notification.Types.COMMENT_CARD: { - const commentText = _.truncate(notification.data.text); + const commentText = _.truncate(formatTextWithMentions(notification.data.text)); return { text: `${t( @@ -81,30 +83,6 @@ const buildBodyByFormat = (board, card, notification, actorUser, t) => { )}:\n\n${escapeHtml(commentText)}`, }; } - 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${escapeHtml(commentText)}`, - }; - } case Notification.Types.ADD_MEMBER_TO_CARD: return { text: t('%s added you to %s on %s', actorUser.name, card.name, board.name), @@ -121,6 +99,30 @@ const buildBodyByFormat = (board, card, notification, actorUser, t) => { escapeHtml(board.name), ), }; + case Notification.Types.MENTION_IN_COMMENT: { + const commentText = _.truncate(formatTextWithMentions(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${escapeHtml(commentText)}`, + }; + } default: return null; } @@ -164,15 +166,6 @@ const buildAndSendEmail = async (board, card, notification, actorUser, notifiabl boardLink, )}

${escapeHtml(notification.data.text)}

`; - break; - case Notification.Types.COMMENT_MENTION: - html = `

${t( - '%s mentioned you in %s on %s', - escapeHtml(actorUser.name), - cardLink, - boardLink, - )}

${escapeHtml(notification.data.text)}

`; - break; case Notification.Types.ADD_MEMBER_TO_CARD: html = `

${t( @@ -182,6 +175,15 @@ const buildAndSendEmail = async (board, card, notification, actorUser, notifiabl boardLink, )}

`; + break; + case Notification.Types.MENTION_IN_COMMENT: + html = `

${t( + '%s mentioned you in %s on %s', + escapeHtml(actorUser.name), + cardLink, + boardLink, + )}

${escapeHtml(notification.data.text)}

`; + break; default: return; @@ -221,11 +223,11 @@ module.exports = { values.userId = values.user.id; } - const isCommentNotification = + const isCommentRelated = values.type === Notification.Types.COMMENT_CARD || - values.type === Notification.Types.COMMENT_MENTION; + values.type === Notification.Types.MENTION_IN_COMMENT; - if (isCommentNotification) { + if (isCommentRelated) { values.commentId = values.comment.id; } else { values.actionId = values.action.id; @@ -254,7 +256,7 @@ module.exports = { boards: [inputs.board], lists: [inputs.list], cards: [values.card], - ...(isCommentNotification + ...(isCommentRelated ? { comments: [values.comment], } diff --git a/server/api/models/Notification.js b/server/api/models/Notification.js index 2b37966a..2d9763a6 100755 --- a/server/api/models/Notification.js +++ b/server/api/models/Notification.js @@ -13,8 +13,8 @@ const Types = { MOVE_CARD: 'moveCard', COMMENT_CARD: 'commentCard', - COMMENT_MENTION: 'commentMention', ADD_MEMBER_TO_CARD: 'addMemberToCard', + MENTION_IN_COMMENT: 'mentionInComment', }; module.exports = { diff --git a/server/config/locales/en-GB.json b/server/config/locales/en-GB.json index 9e9c1322..1a7b7420 100644 --- a/server/config/locales/en-GB.json +++ b/server/config/locales/en-GB.json @@ -7,8 +7,10 @@ "This is a *test* **markdown** `message`!": "This is a *test* **markdown** `message`!", "This is a test html message": "This is a test html message", "You Were Added to Card": "Your Were Added to Card", + "You Were Mentioned in Comment": "You Were Mentioned in Comment", "%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 left a new comment to %s on %s": "%s left a new comment to %s on %s", + "%s mentioned you in %s on %s": "%s mentioned you in %s on %s", "%s moved %s from %s to %s on %s": "%s moved %s from %s to %s on %s" } diff --git a/server/config/locales/en-US.json b/server/config/locales/en-US.json index f0bedb9c..edea5f0c 100644 --- a/server/config/locales/en-US.json +++ b/server/config/locales/en-US.json @@ -2,15 +2,15 @@ "Card Created": "Card Created", "Card Moved": "Card Moved", "New Comment": "New Comment", - "You Were Mentioned in a Comment": "You Were Mentioned in a Comment", "Test Title": "Test Title", "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 html message": "This is a test html message", "You Were Added to Card": "Your Were Added to Card", + "You Were Mentioned in Comment": "You Were Mentioned in Comment", "%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 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 mentioned you in %s on %s": "%s mentioned you in %s on %s" + "%s mentioned you in %s on %s": "%s mentioned you in %s on %s", + "%s moved %s from %s to %s on %s": "%s moved %s from %s to %s on %s" } diff --git a/server/config/locales/ru-RU.json b/server/config/locales/ru-RU.json index 6f300160..fbd67b79 100644 --- a/server/config/locales/ru-RU.json +++ b/server/config/locales/ru-RU.json @@ -7,8 +7,10 @@ "This is a *test* **markdown** `message`!": "Это *тестовое* **markdown** `сообщение`!", "This is a test html message": "Это тестовое html сообщение", "You Were Added to Card": "Вы были добавлены к карточке", + "You Were Mentioned in Comment": "Вы были упомянуты в комментарии", "%s added you to %s on %s": "%s добавил(а) вас к %s на %s", "%s created %s in %s on %s": "%s создал(а) %s в %s на %s", "%s left a new comment to %s on %s": "%s оставил(а) новый комментарий к %s на %s", + "%s mentioned you in %s on %s": "%s упомянул(а) вас в %s на %s", "%s moved %s from %s to %s on %s": "%s переместил(а) %s из %s в %s на %s" } diff --git a/server/utils/formatters.js b/server/utils/formatters.js new file mode 100644 index 00000000..6c266c6b --- /dev/null +++ b/server/utils/formatters.js @@ -0,0 +1,7 @@ +const MENTIONS_REGEX = /@\[(.*?)\]\(.*?\)/g; + +const formatTextWithMentions = (text) => text.replace(MENTIONS_REGEX, '@$1'); + +module.exports = { + formatTextWithMentions, +};