mirror of
https://github.com/plankanban/planka.git
synced 2025-07-19 13:19:44 +02:00
fix: Add mentions support when editing comments
This commit is contained in:
parent
6c2999044b
commit
fc7863aaaf
3 changed files with 84 additions and 32 deletions
|
@ -20,7 +20,6 @@
|
||||||
margin-bottom: 8px !important;
|
margin-bottom: 8px !important;
|
||||||
|
|
||||||
textarea {
|
textarea {
|
||||||
background: #fff;
|
|
||||||
border: 1px solid rgba(9, 30, 66, 0.13);
|
border: 1px solid rgba(9, 30, 66, 0.13);
|
||||||
border-radius: 3px;
|
border-radius: 3px;
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
|
|
|
@ -4,12 +4,12 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { dequal } from 'dequal';
|
import { dequal } from 'dequal';
|
||||||
import React, { useCallback, useEffect, useMemo } from 'react';
|
import React, { useCallback, useEffect, useMemo, useRef } from 'react';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import { useDispatch, useSelector } from 'react-redux';
|
import { useDispatch, useSelector } from 'react-redux';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import TextareaAutosize from 'react-textarea-autosize';
|
import { Mention, MentionsInput } from 'react-mentions';
|
||||||
import { Button, Form, TextArea } from 'semantic-ui-react';
|
import { Button, Form } from 'semantic-ui-react';
|
||||||
import { useClickAwayListener } from '../../../lib/hooks';
|
import { useClickAwayListener } from '../../../lib/hooks';
|
||||||
|
|
||||||
import selectors from '../../../selectors';
|
import selectors from '../../../selectors';
|
||||||
|
@ -17,6 +17,7 @@ import entryActions from '../../../entry-actions';
|
||||||
import { useForm, useNestedRef } from '../../../hooks';
|
import { useForm, useNestedRef } from '../../../hooks';
|
||||||
import { focusEnd } from '../../../utils/element-helpers';
|
import { focusEnd } from '../../../utils/element-helpers';
|
||||||
import { isModifierKeyPressed } from '../../../utils/event-helpers';
|
import { isModifierKeyPressed } from '../../../utils/event-helpers';
|
||||||
|
import UserAvatar from '../../users/UserAvatar';
|
||||||
|
|
||||||
import styles from './Edit.module.scss';
|
import styles from './Edit.module.scss';
|
||||||
|
|
||||||
|
@ -24,6 +25,7 @@ const Edit = React.memo(({ commentId, onClose }) => {
|
||||||
const selectCommentById = useMemo(() => selectors.makeSelectCommentById(), []);
|
const selectCommentById = useMemo(() => selectors.makeSelectCommentById(), []);
|
||||||
|
|
||||||
const comment = useSelector((state) => selectCommentById(state, commentId));
|
const comment = useSelector((state) => selectCommentById(state, commentId));
|
||||||
|
const boardMemberships = useSelector(selectors.selectMembershipsForCurrentBoard);
|
||||||
|
|
||||||
const dispatch = useDispatch();
|
const dispatch = useDispatch();
|
||||||
const [t] = useTranslation();
|
const [t] = useTranslation();
|
||||||
|
@ -35,12 +37,13 @@ const Edit = React.memo(({ commentId, onClose }) => {
|
||||||
[comment.text],
|
[comment.text],
|
||||||
);
|
);
|
||||||
|
|
||||||
const [data, handleFieldChange] = useForm(() => ({
|
const [data, , setData] = useForm(() => ({
|
||||||
text: '',
|
text: '',
|
||||||
...defaultData,
|
...defaultData,
|
||||||
}));
|
}));
|
||||||
|
|
||||||
const [textFieldRef, handleTextFieldRef] = useNestedRef();
|
const mentionsInputRef = useRef(null);
|
||||||
|
const textFieldRef = useRef(null);
|
||||||
const [buttonRef, handleButtonRef] = useNestedRef();
|
const [buttonRef, handleButtonRef] = useNestedRef();
|
||||||
|
|
||||||
const submit = useCallback(() => {
|
const submit = useCallback(() => {
|
||||||
|
@ -60,6 +63,15 @@ const Edit = React.memo(({ commentId, onClose }) => {
|
||||||
submit();
|
submit();
|
||||||
}, [submit]);
|
}, [submit]);
|
||||||
|
|
||||||
|
const handleFieldChange = useCallback(
|
||||||
|
(_, text) => {
|
||||||
|
setData({
|
||||||
|
text,
|
||||||
|
});
|
||||||
|
},
|
||||||
|
[setData],
|
||||||
|
);
|
||||||
|
|
||||||
const handleFieldKeyDown = useCallback(
|
const handleFieldKeyDown = useCallback(
|
||||||
(event) => {
|
(event) => {
|
||||||
if (event.key === 'Enter') {
|
if (event.key === 'Enter') {
|
||||||
|
@ -83,25 +95,53 @@ const Edit = React.memo(({ commentId, onClose }) => {
|
||||||
handleClickAwayCancel,
|
handleClickAwayCancel,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const suggestionRenderer = useCallback(
|
||||||
|
(entry, _, highlightedDisplay) => (
|
||||||
|
<div className={styles.suggestion}>
|
||||||
|
<UserAvatar id={entry.id} size="tiny" />
|
||||||
|
{highlightedDisplay}
|
||||||
|
</div>
|
||||||
|
),
|
||||||
|
[],
|
||||||
|
);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
focusEnd(textFieldRef.current);
|
focusEnd(textFieldRef.current);
|
||||||
}, [textFieldRef]);
|
}, [textFieldRef]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Form onSubmit={handleSubmit}>
|
<Form onSubmit={handleSubmit}>
|
||||||
<TextArea
|
<div className={styles.field}>
|
||||||
{...clickAwayProps} // eslint-disable-line react/jsx-props-no-spreading
|
<MentionsInput
|
||||||
ref={handleTextFieldRef}
|
{...clickAwayProps} // eslint-disable-line react/jsx-props-no-spreading
|
||||||
as={TextareaAutosize}
|
allowSpaceInQuery
|
||||||
name="text"
|
allowSuggestionsAboveCursor
|
||||||
value={data.text}
|
ref={mentionsInputRef}
|
||||||
maxLength={1048576}
|
inputRef={textFieldRef}
|
||||||
minRows={3}
|
value={data.text}
|
||||||
spellCheck={false}
|
maxLength={1048576}
|
||||||
className={styles.field}
|
rows={3}
|
||||||
onKeyDown={handleFieldKeyDown}
|
className="mentions-input"
|
||||||
onChange={handleFieldChange}
|
style={{
|
||||||
/>
|
control: {
|
||||||
|
minHeight: '79px',
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
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>
|
||||||
<div className={styles.controls}>
|
<div className={styles.controls}>
|
||||||
<Button
|
<Button
|
||||||
{...clickAwayProps} // eslint-disable-line react/jsx-props-no-spreading
|
{...clickAwayProps} // eslint-disable-line react/jsx-props-no-spreading
|
||||||
|
|
|
@ -11,20 +11,33 @@
|
||||||
|
|
||||||
.field {
|
.field {
|
||||||
background: #fff;
|
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;
|
|
||||||
margin-bottom: 4px;
|
|
||||||
overflow: hidden;
|
|
||||||
padding: 8px 12px;
|
|
||||||
resize: none;
|
|
||||||
|
|
||||||
&:focus {
|
textarea {
|
||||||
outline: none;
|
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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue