mirror of
https://github.com/plankanban/planka.git
synced 2025-07-24 07:39:44 +02:00
feat: Add ability to mention users in comments (#1162)
This commit is contained in:
parent
eb2a3a2875
commit
c0b0436851
20 changed files with 357 additions and 42 deletions
|
@ -3,16 +3,18 @@
|
|||
* Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md
|
||||
*/
|
||||
|
||||
import React, { useCallback, useState } from 'react';
|
||||
import { useDispatch } from 'react-redux';
|
||||
import React, { useCallback, useState, useRef } from 'react';
|
||||
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 { Mention, MentionsInput } from 'react-mentions';
|
||||
import { Button, Form } from 'semantic-ui-react';
|
||||
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 from '../../users/UserAvatar';
|
||||
|
||||
import styles from './Add.module.scss';
|
||||
|
||||
|
@ -21,13 +23,16 @@ 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, handleTextFieldRef] = useNestedRef();
|
||||
const mentionsInputRef = useRef(null);
|
||||
const textFieldRef = useRef(null);
|
||||
const [buttonRef, handleButtonRef] = useNestedRef();
|
||||
|
||||
const submit = useCallback(() => {
|
||||
|
@ -47,6 +52,11 @@ const Add = React.memo(() => {
|
|||
}, [dispatch, data, setData, selectTextField, textFieldRef]);
|
||||
|
||||
const handleEscape = useCallback(() => {
|
||||
if (mentionsInputRef.current.isOpened()) {
|
||||
mentionsInputRef.current.clearSuggestions();
|
||||
return;
|
||||
}
|
||||
|
||||
setIsOpened(false);
|
||||
textFieldRef.current.blur();
|
||||
}, [textFieldRef]);
|
||||
|
@ -62,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') {
|
||||
|
@ -85,6 +104,16 @@ const Add = React.memo(() => {
|
|||
handleClickAwayCancel,
|
||||
);
|
||||
|
||||
const suggestionRenderer = useCallback(
|
||||
(entry, _, highlightedDisplay) => (
|
||||
<div className={styles.suggestion}>
|
||||
<UserAvatar id={entry.id} size="tiny" />
|
||||
{highlightedDisplay}
|
||||
</div>
|
||||
),
|
||||
[],
|
||||
);
|
||||
|
||||
useDidUpdate(() => {
|
||||
if (isOpened) {
|
||||
activateEscapeInterceptor();
|
||||
|
@ -99,21 +128,39 @@ const Add = React.memo(() => {
|
|||
|
||||
return (
|
||||
<Form onSubmit={handleSubmit}>
|
||||
<TextArea
|
||||
{...clickAwayProps} // eslint-disable-line react/jsx-props-no-spreading
|
||||
ref={handleTextFieldRef}
|
||||
as={TextareaAutosize}
|
||||
name="text"
|
||||
value={data.text}
|
||||
placeholder={t('common.writeComment')}
|
||||
maxLength={1048576}
|
||||
minRows={isOpened ? 3 : 1}
|
||||
spellCheck={false}
|
||||
className={styles.field}
|
||||
onFocus={handleFieldFocus}
|
||||
onKeyDown={handleFieldKeyDown}
|
||||
onChange={handleFieldChange}
|
||||
/>
|
||||
<div className={styles.field}>
|
||||
<MentionsInput
|
||||
{...clickAwayProps} // eslint-disable-line react/jsx-props-no-spreading
|
||||
allowSpaceInQuery
|
||||
allowSuggestionsAboveCursor
|
||||
ref={mentionsInputRef}
|
||||
inputRef={textFieldRef}
|
||||
value={data.text}
|
||||
placeholder={t('common.writeComment')}
|
||||
maxLength={1048576}
|
||||
rows={isOpened ? 3 : 1}
|
||||
className="mentions-input"
|
||||
style={{
|
||||
control: {
|
||||
minHeight: isOpened ? '79px' : '37px',
|
||||
},
|
||||
}}
|
||||
onFocus={handleFieldFocus}
|
||||
onChange={handleFieldChange}
|
||||
onKeyDown={handleFieldKeyDown}
|
||||
>
|
||||
<Mention
|
||||
appendSpaceOnAdd
|
||||
data={boardMemberships.map(({ user }) => ({
|
||||
id: user.id,
|
||||
display: user.username || user.name,
|
||||
}))}
|
||||
displayTransform={(_, display) => `@${display}`}
|
||||
renderSuggestion={suggestionRenderer}
|
||||
className={styles.mention}
|
||||
/>
|
||||
</MentionsInput>
|
||||
</div>
|
||||
{isOpened && (
|
||||
<div className={styles.controls}>
|
||||
<Button
|
||||
|
|
|
@ -17,19 +17,35 @@
|
|||
|
||||
.field {
|
||||
background: #fff;
|
||||
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%;
|
||||
margin-bottom: 8px !important;
|
||||
|
||||
&:focus {
|
||||
outline: none;
|
||||
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;
|
||||
font-size: 14px;
|
||||
overflow: hidden;
|
||||
padding: 8px 12px;
|
||||
resize: none;
|
||||
|
||||
&:focus {
|
||||
outline: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.mention {
|
||||
background-color: #f1f8ff;
|
||||
border-radius: 3px;
|
||||
}
|
||||
|
||||
.suggestion {
|
||||
align-items: center;
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -22,7 +22,6 @@
|
|||
overflow: hidden;
|
||||
padding: 8px 12px;
|
||||
resize: none;
|
||||
width: 100%;
|
||||
|
||||
&:focus {
|
||||
outline: none;
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue