1
0
Fork 0
mirror of https://github.com/plankanban/planka.git synced 2025-08-10 16:05:35 +02:00

feat: improve mention

This commit is contained in:
Albert Buenaventura 2024-04-25 22:57:46 +08:00
parent 1c9a42781c
commit 5a270f51e6
3 changed files with 86 additions and 26 deletions

View file

@ -8,7 +8,8 @@ import { useDidUpdate, useToggle } from '../../../lib/hooks';
import { useClosableForm, useForm } from '../../../hooks';
import styles from './CommentAdd.module.scss';
import Tag, { TagRegex } from '../../Tag/Tag';
import Tag from '../../Tag/Tag';
import useMention from '../../../hooks/use-mention';
const DEFAULT_DATA = {
text: '',
@ -21,12 +22,12 @@ const CommentAdd = React.memo(({ onCreate, boardMemberships }) => {
const [selectTextFieldState, selectTextField] = useToggle();
const [cursor, setCursor] = useState(0);
const textField = useRef(null);
const [showTag, setShowTag] = useState(false);
const [mention, setMention] = useState('');
const { mention, onChange, onSelectMention, isMentioning } = useMention();
const close = useCallback(() => {
if (isMentioning) return;
setIsOpened(false);
}, []);
}, [isMentioning]);
const submit = useCallback(() => {
const cleanData = {
@ -52,13 +53,14 @@ const CommentAdd = React.memo(({ onCreate, boardMemberships }) => {
const calculatePosition = (position) => Math.min(position, 22) * 8;
const handleFieldKeyDown = useCallback(
(event) => {
if (event.ctrlKey && event.key === 'Enter') {
(ev) => {
if (ev.ctrlKey && ev.key === 'Enter') {
submit();
}
setCursor(calculatePosition(event.target.selectionStart));
setCursor(calculatePosition(ev.target.selectionStart));
onChange(ev);
},
[submit],
[submit, onChange],
);
const [handleFieldBlur, handleControlMouseOver, handleControlMouseOut] = useClosableForm(close);
@ -67,21 +69,15 @@ const CommentAdd = React.memo(({ onCreate, boardMemberships }) => {
submit();
}, [submit]);
const handleChange = (e, updatedData) => {
handleFieldChange(e, updatedData);
const text = e.target.value;
const handleChange = (ev, updatedData) => {
handleFieldChange(ev, updatedData);
onChange(ev);
};
TagRegex.lastIndex = 0;
if (TagRegex.test(text)) {
// Extract the mention after @ symbol
const mentionSymbols = text.split('@');
const mentionName = mentionSymbols[mentionSymbols.length - 1].split(' ')[0].toLowerCase();
setMention(mentionName);
setShowTag(true);
} else {
setMention('');
setShowTag(false);
}
const handleMention = (user) => {
setData({
text: onSelectMention(data.text, user),
});
};
useDidUpdate(() => {
@ -104,7 +100,7 @@ const CommentAdd = React.memo(({ onCreate, boardMemberships }) => {
onChange={handleChange}
onBlur={handleFieldBlur}
/>
{showTag && (
{isMentioning && (
<div
style={{
marginLeft: `${cursor}px`,
@ -114,7 +110,11 @@ const CommentAdd = React.memo(({ onCreate, boardMemberships }) => {
}}
>
{' '}
<Tag search={mention} boardMemberships={boardMemberships} />
<Tag
search={mention}
boardMemberships={boardMemberships}
handleUserSelect={handleMention}
/>
</div>
)}
{isOpened && (

View file

@ -7,7 +7,7 @@ import UserItem from '../Memberships/AddStep/UserItem';
export const TagRegex = /(^|\s)@/gi;
const Tag = React.memo(({ search, boardMemberships }) => {
const Tag = React.memo(({ search, boardMemberships, handleUserSelect }) => {
const cleanSearch = useMemo(() => search.trim().toLowerCase(), [search]);
const filteredUsers = useMemo(
@ -26,10 +26,11 @@ const Tag = React.memo(({ search, boardMemberships }) => {
{filteredUsers.map((member) => {
return (
<UserItem
isActive={false}
key={member.user.id}
name={member.user.name}
avatarUrl={member.user.avatarUrl}
onSelect={() => handleUserSelect(member.user.id)}
onSelect={() => handleUserSelect(member.user)}
/>
);
})}
@ -44,6 +45,7 @@ Tag.defaultProps = {
Tag.propTypes = {
search: PropTypes.string,
boardMemberships: PropTypes.array.isRequired, // eslint-disable-line react/forbid-prop-types
handleUserSelect: PropTypes.func.isRequired,
};
export default Tag;

View file

@ -0,0 +1,58 @@
import { useState } from 'react';
import { TagRegex } from '../components/Tag/Tag';
export default () => {
const [mention, setMention] = useState('');
const [isMentioning, setIsMentioning] = useState(false);
const [currentPos, setCurrentPos] = useState(null);
const getWord = (s, pos) => {
const n = s.substring(pos).match(/^[^ ]+/);
const p = s.substring(0, pos).match(/[^ ]+$/);
// if you really only want the word if you click at start or between
// but not at end instead use if (!n) return
if (!p && !n) return '';
return (p || '') + (n || '');
};
const onChange = (ev) => {
TagRegex.lastIndex = 0;
const position = ev.target.selectionStart;
const word = getWord(ev.target.value, position);
setCurrentPos(position);
if (TagRegex.test(word)) {
// Extract the mention after @ symbol
const mentionSymbols = word.split('@');
const mentionName = mentionSymbols[mentionSymbols.length - 1].split(' ')[0].toLowerCase();
setMention(mentionName);
setIsMentioning(true);
} else {
setMention('');
setIsMentioning(false);
}
};
const onSelectMention = (text, user) => {
if (!isMentioning) return text;
const { username } = user;
const wordLength = getWord(text, currentPos).length;
const n = text.substring(currentPos).match(/^[^ ]+/);
const p = text.substring(0, currentPos).match(/[^ ]+$/);
const startIndex = p ? p.index : n.index;
setIsMentioning(false);
return `${text.substring(0, startIndex)}@mention[${username}]${text.substring(startIndex + wordLength)}`;
};
return {
mention,
isMentioning,
onChange,
onSelectMention,
};
};