From 3f67d9e8bbf19d1763bc17d83af4dbd35d71f842 Mon Sep 17 00:00:00 2001 From: Roman Zavarnitsyn Date: Thu, 29 May 2025 20:55:39 +0200 Subject: [PATCH] WIP --- client/package-lock.json | 45 +++++++++ client/package.json | 1 + .../src/components/comments/Comments/Add.jsx | 94 +++++++++++++----- .../comments/Comments/Add.module.scss | 98 ++++++++++++++++--- .../users/UserAvatar/UserAvatar.jsx | 2 +- client/src/configs/markdown-plugins/index.js | 2 + .../src/configs/markdown-plugins/mentions.js | 69 +++++++++++++ 7 files changed, 275 insertions(+), 36 deletions(-) create mode 100644 client/src/configs/markdown-plugins/mentions.js diff --git a/client/package-lock.json b/client/package-lock.json index 6f912566..b9891269 100644 --- a/client/package-lock.json +++ b/client/package-lock.json @@ -69,6 +69,7 @@ "react-i18next": "^15.5.1", "react-input-mask": "^2.0.4", "react-intersection-observer": "^9.16.0", + "react-mentions": "^4.4.10", "react-photoswipe-gallery": "^2.2.7", "react-redux": "^8.1.3", "react-router-dom": "^6.30.0", @@ -12296,6 +12297,31 @@ "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz", "integrity": "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==" }, + "node_modules/react-mentions": { + "version": "4.4.10", + "resolved": "https://registry.npmjs.org/react-mentions/-/react-mentions-4.4.10.tgz", + "integrity": "sha512-JHiQlgF1oSZR7VYPjq32wy97z1w1oE4x10EuhKjPr4WUKhVzG1uFQhQjKqjQkbVqJrmahf+ldgBTv36NrkpKpA==", + "license": "BSD-3-Clause", + "dependencies": { + "@babel/runtime": "7.4.5", + "invariant": "^2.2.4", + "prop-types": "^15.5.8", + "substyle": "^9.1.0" + }, + "peerDependencies": { + "react": ">=16.8.3", + "react-dom": ">=16.8.3" + } + }, + "node_modules/react-mentions/node_modules/@babel/runtime": { + "version": "7.4.5", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.4.5.tgz", + "integrity": "sha512-TuI4qpWZP6lGOGIuGWtp9sPluqYICmbk8T/1vpSysqJxRPkudh/ofFWyqdcMsDf2s7KvDL4/YHgKyvcS3g9CJQ==", + "license": "MIT", + "dependencies": { + "regenerator-runtime": "^0.13.2" + } + }, "node_modules/react-onclickoutside": { "version": "6.13.2", "resolved": "https://registry.npmjs.org/react-onclickoutside/-/react-onclickoutside-6.13.2.tgz", @@ -12677,6 +12703,12 @@ "node": ">=4" } }, + "node_modules/regenerator-runtime": { + "version": "0.13.11", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.11.tgz", + "integrity": "sha512-kY1AZVr2Ra+t+piVaJ4gxaFaReZVH40AKNo7UCX6W+dEwBo/2oZJzqfuN1qLq1oL45o56cPaTXELwrTh8Fpggg==", + "license": "MIT" + }, "node_modules/regexp-match-indices": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/regexp-match-indices/-/regexp-match-indices-1.0.2.tgz", @@ -14183,6 +14215,19 @@ "resolved": "https://registry.npmjs.org/stylis/-/stylis-4.3.6.tgz", "integrity": "sha512-yQ3rwFWRfwNUY7H5vpU0wfdkNSnvnJinhF9830Swlaxl03zsOjCfmX0ugac+3LtK0lYSgwL/KXc8oYL3mG4YFQ==" }, + "node_modules/substyle": { + "version": "9.4.1", + "resolved": "https://registry.npmjs.org/substyle/-/substyle-9.4.1.tgz", + "integrity": "sha512-VOngeq/W1/UkxiGzeqVvDbGDPM8XgUyJVWjrqeh+GgKqspEPiLYndK+XRcsKUHM5Muz/++1ctJ1QCF/OqRiKWA==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.3.4", + "invariant": "^2.2.4" + }, + "peerDependencies": { + "react": ">=16.8.3" + } + }, "node_modules/supports-color": { "version": "8.1.1", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", diff --git a/client/package.json b/client/package.json index cf8921fd..c429160a 100755 --- a/client/package.json +++ b/client/package.json @@ -140,6 +140,7 @@ "react-i18next": "^15.5.1", "react-input-mask": "^2.0.4", "react-intersection-observer": "^9.16.0", + "react-mentions": "^4.4.10", "react-photoswipe-gallery": "^2.2.7", "react-redux": "^8.1.3", "react-router-dom": "^6.30.0", diff --git a/client/src/components/comments/Comments/Add.jsx b/client/src/components/comments/Comments/Add.jsx index 56e8b139..cc5f18a0 100755 --- a/client/src/components/comments/Comments/Add.jsx +++ b/client/src/components/comments/Comments/Add.jsx @@ -4,15 +4,17 @@ */ import React, { useCallback, useState } from 'react'; -import { useDispatch } from 'react-redux'; +import { useDispatch, useSelector } from 'react-redux'; import { useTranslation } from 'react-i18next'; -import TextareaAutosize from 'react-textarea-autosize'; -import { Button, Form, TextArea } from 'semantic-ui-react'; +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 styles from './Add.module.scss'; @@ -27,9 +29,25 @@ const Add = React.memo(() => { const [data, handleFieldChange, setData] = useForm(DEFAULT_DATA); const [selectTextFieldState, selectTextField] = useToggle(); - const [textFieldRef, handleTextFieldRef] = useNestedRef(); + const textFieldRef = React.createRef(); const [buttonRef, handleButtonRef] = useNestedRef(); + const mentionsInputStyle = { + control: { + minHeight: isOpened ? '60px' : '36px', + }, + }; + + const renderSuggestion = useCallback( + (suggestion, search, highlightedDisplay) => ( +
+ + {highlightedDisplay} +
+ ), + [], + ); + const submit = useCallback(() => { const cleanData = { ...data, @@ -37,7 +55,7 @@ const Add = React.memo(() => { }; if (!cleanData.text) { - textFieldRef.current.select(); + textFieldRef.current?.focus(); return; } @@ -71,12 +89,15 @@ const Add = React.memo(() => { [submit], ); - const handleAwayClick = useCallback(() => { + const handleAwayClick = useCallback((event) => { + if (event?.target?.closest?.('.mentions-input')) { + return; + } setIsOpened(false); }, []); const handleClickAwayCancel = useCallback(() => { - textFieldRef.current.focus(); + textFieldRef.current?.focus(); }, [textFieldRef]); const clickAwayProps = useClickAwayListener( @@ -85,6 +106,18 @@ const Add = React.memo(() => { handleClickAwayCancel, ); + const users = useSelector(selectors.selectMembershipsForCurrentBoard); + + const handleFormFieldChange = useCallback( + (event, newValue) => { + handleFieldChange(null, { + name: 'text', + value: newValue, + }); + }, + [handleFieldChange], + ); + useDidUpdate(() => { if (isOpened) { activateEscapeInterceptor(); @@ -94,26 +127,41 @@ const Add = React.memo(() => { }, [isOpened]); useDidUpdate(() => { - textFieldRef.current.focus(); + textFieldRef.current?.focus(); }, [selectTextFieldState]); return (
-