This has a lot of code to remove
Signed-off-by: marcin mikołajczak <git@mkljczk.pl>
This commit is contained in:
parent
c953da7c78
commit
8e31499763
2 changed files with 77 additions and 111 deletions
|
@ -48,6 +48,8 @@ const ComposeEditor = React.forwardRef<string, any>(({ composeId, condensed, onF
|
||||||
const dispatch = useAppDispatch();
|
const dispatch = useAppDispatch();
|
||||||
const features = useFeatures();
|
const features = useFeatures();
|
||||||
|
|
||||||
|
const [suggestionsHidden, setSuggestionsHidden] = useState(true);
|
||||||
|
|
||||||
const initialConfig: InitialConfigType = useMemo(function() {
|
const initialConfig: InitialConfigType = useMemo(function() {
|
||||||
return {
|
return {
|
||||||
namespace: 'ComposeForm',
|
namespace: 'ComposeForm',
|
||||||
|
@ -138,7 +140,7 @@ const ComposeEditor = React.forwardRef<string, any>(({ composeId, condensed, onF
|
||||||
/>
|
/>
|
||||||
<HistoryPlugin />
|
<HistoryPlugin />
|
||||||
<HashtagPlugin />
|
<HashtagPlugin />
|
||||||
<MentionPlugin />
|
<MentionPlugin composeId={composeId} suggestionsHidden={suggestionsHidden} setSuggestionsHidden={setSuggestionsHidden} />
|
||||||
{features.richText && <LinkPlugin />}
|
{features.richText && <LinkPlugin />}
|
||||||
{features.richText && floatingAnchorElem && (
|
{features.richText && floatingAnchorElem && (
|
||||||
<>
|
<>
|
||||||
|
|
|
@ -13,14 +13,19 @@ import {
|
||||||
useBasicTypeaheadTriggerMatch,
|
useBasicTypeaheadTriggerMatch,
|
||||||
} from '@lexical/react/LexicalTypeaheadMenuPlugin';
|
} from '@lexical/react/LexicalTypeaheadMenuPlugin';
|
||||||
import { useLexicalTextEntity } from '@lexical/react/useLexicalTextEntity';
|
import { useLexicalTextEntity } from '@lexical/react/useLexicalTextEntity';
|
||||||
import React, { useCallback, useEffect, useMemo, useState } from 'react';
|
import clsx from 'clsx';
|
||||||
|
import React, { useCallback, useEffect, useState } from 'react';
|
||||||
import ReactDOM from 'react-dom';
|
import ReactDOM from 'react-dom';
|
||||||
|
|
||||||
|
import { fetchComposeSuggestions } from 'soapbox/actions/compose';
|
||||||
|
import { useAppDispatch, useCompose } from 'soapbox/hooks';
|
||||||
|
|
||||||
|
import AutosuggestAccount from '../../components/autosuggest-account';
|
||||||
import { $createMentionNode, MentionNode } from '../nodes/mention-node';
|
import { $createMentionNode, MentionNode } from '../nodes/mention-node';
|
||||||
|
|
||||||
import { TypeaheadMenuPlugin } from './typeahead-menu-plugin';
|
import { TypeaheadMenuPlugin } from './typeahead-menu-plugin';
|
||||||
|
|
||||||
import type { TextNode } from 'lexical';
|
import type { RangeSelection, TextNode } from 'lexical';
|
||||||
|
|
||||||
const REGEX = new RegExp('(^|$|(?:^|\\s))([@])([a-z\\d_-]+(?:@[^@\\s]+)?)', 'i');
|
const REGEX = new RegExp('(^|$|(?:^|\\s))([@])([a-z\\d_-]+(?:@[^@\\s]+)?)', 'i');
|
||||||
|
|
||||||
|
@ -44,32 +49,7 @@ const TRIGGERS = ['@'].join('');
|
||||||
// Chars we expect to see in a mention (non-space, non-punctuation).
|
// Chars we expect to see in a mention (non-space, non-punctuation).
|
||||||
const VALID_CHARS = '[^' + TRIGGERS + PUNC + '\\s]';
|
const VALID_CHARS = '[^' + TRIGGERS + PUNC + '\\s]';
|
||||||
|
|
||||||
// Non-standard series of chars. Each series must be preceded and followed by
|
const AtSignMentionsRegex = REGEX;
|
||||||
// a valid char.
|
|
||||||
// const VALID_JOINS =
|
|
||||||
// '(?:' +
|
|
||||||
// '\\.[ |$]|' + // E.g. "r. " in "Mr. Smith"
|
|
||||||
// ' |' + // E.g. " " in "Josh Duck"
|
|
||||||
// '[' +
|
|
||||||
// PUNC +
|
|
||||||
// ']|' + // E.g. "-' in "Salier-Hellendag"
|
|
||||||
// ')';
|
|
||||||
|
|
||||||
// const LENGTH_LIMIT = 75;
|
|
||||||
|
|
||||||
const AtSignMentionsRegex = REGEX; /* new RegExp(
|
|
||||||
'(^|\\s|\\()(' +
|
|
||||||
'[' +
|
|
||||||
TRIGGERS +
|
|
||||||
']' +
|
|
||||||
'((?:' +
|
|
||||||
VALID_CHARS +
|
|
||||||
VALID_JOINS +
|
|
||||||
'){0,' +
|
|
||||||
LENGTH_LIMIT +
|
|
||||||
'})' +
|
|
||||||
')$',
|
|
||||||
); */
|
|
||||||
|
|
||||||
// 50 is the longest alias length limit.
|
// 50 is the longest alias length limit.
|
||||||
const ALIAS_LENGTH_LIMIT = 50;
|
const ALIAS_LENGTH_LIMIT = 50;
|
||||||
|
@ -88,9 +68,6 @@ const AtSignMentionsRegexAliasRegex = new RegExp(
|
||||||
')$',
|
')$',
|
||||||
);
|
);
|
||||||
|
|
||||||
// At most, 5 suggestions are shown in the popup.
|
|
||||||
const SUGGESTION_LIST_LENGTH_LIMIT = 5;
|
|
||||||
|
|
||||||
const mentionsCache = new Map();
|
const mentionsCache = new Map();
|
||||||
|
|
||||||
const dummyMentionsData = ['Test'];
|
const dummyMentionsData = ['Test'];
|
||||||
|
@ -199,45 +176,20 @@ class MentionTypeaheadOption extends TypeaheadOption {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const MentionsTypeaheadMenuItem = ({
|
export const MentionPlugin: React.FC<{
|
||||||
index,
|
composeId: string
|
||||||
isSelected,
|
suggestionsHidden: boolean
|
||||||
onClick,
|
setSuggestionsHidden: (value: boolean) => void
|
||||||
onMouseEnter,
|
}> = ({
|
||||||
option,
|
composeId, suggestionsHidden, setSuggestionsHidden,
|
||||||
}: {
|
}): JSX.Element | null => {
|
||||||
index: number
|
const { suggestions } = useCompose(composeId);
|
||||||
isSelected: boolean
|
const dispatch = useAppDispatch();
|
||||||
onClick: () => void
|
|
||||||
onMouseEnter: () => void
|
|
||||||
option: MentionTypeaheadOption
|
|
||||||
}) => {
|
|
||||||
let className = 'item';
|
|
||||||
if (isSelected) {
|
|
||||||
className += ' selected';
|
|
||||||
}
|
|
||||||
return (
|
|
||||||
<li
|
|
||||||
key={option.key}
|
|
||||||
tabIndex={-1}
|
|
||||||
className={className}
|
|
||||||
ref={option.setRefElement}
|
|
||||||
role='option'
|
|
||||||
aria-selected={isSelected}
|
|
||||||
id={'typeahead-item-' + index}
|
|
||||||
onMouseEnter={onMouseEnter}
|
|
||||||
onClick={onClick}
|
|
||||||
>
|
|
||||||
{option.picture}
|
|
||||||
<span className='text'>{option.name}</span>
|
|
||||||
</li>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export const MentionPlugin = (): JSX.Element | null => {
|
|
||||||
const [editor] = useLexicalComposerContext();
|
const [editor] = useLexicalComposerContext();
|
||||||
|
|
||||||
const [queryString, setQueryString] = useState<string | null>(null);
|
const [queryString, setQueryString] = useState<string | null>(null);
|
||||||
|
const [selectedSuggestion] = useState(0);
|
||||||
|
|
||||||
const results = useMentionLookupService(queryString);
|
const results = useMentionLookupService(queryString);
|
||||||
|
|
||||||
|
@ -245,34 +197,28 @@ export const MentionPlugin = (): JSX.Element | null => {
|
||||||
minLength: 0,
|
minLength: 0,
|
||||||
});
|
});
|
||||||
|
|
||||||
const options = useMemo(
|
const options = [new MentionTypeaheadOption('', <i className='icon user' />)];
|
||||||
() =>
|
|
||||||
results
|
const onSelectOption = useCallback(() => { }, [editor]);
|
||||||
.map(
|
|
||||||
(result) =>
|
const onSelectSuggestion: React.MouseEventHandler<HTMLDivElement> = (e) => {
|
||||||
new MentionTypeaheadOption(result, <i className='icon user' />),
|
e.preventDefault();
|
||||||
)
|
|
||||||
.slice(0, SUGGESTION_LIST_LENGTH_LIMIT),
|
const suggestion = suggestions.get(e.currentTarget.getAttribute('data-index') as any);
|
||||||
[results],
|
|
||||||
);
|
|
||||||
|
|
||||||
const onSelectOption = useCallback(
|
|
||||||
(
|
|
||||||
selectedOption: MentionTypeaheadOption,
|
|
||||||
nodeToReplace: TextNode | null,
|
|
||||||
closeMenu: () => void,
|
|
||||||
) => {
|
|
||||||
editor.update(() => {
|
editor.update(() => {
|
||||||
const mentionNode = $createMentionNode(selectedOption.name);
|
|
||||||
if (nodeToReplace) {
|
dispatch((_, getState) => {
|
||||||
nodeToReplace.replace(mentionNode);
|
const state = editor.getEditorState();
|
||||||
}
|
const node = (state._selection as RangeSelection)?.anchor?.getNode();
|
||||||
mentionNode.select();
|
|
||||||
closeMenu();
|
const content = getState().accounts.get(suggestion)!.acct;
|
||||||
|
|
||||||
|
node.setTextContent(`@${content} `);
|
||||||
|
node.select();
|
||||||
});
|
});
|
||||||
},
|
});
|
||||||
[editor],
|
};
|
||||||
);
|
|
||||||
|
|
||||||
const checkForMentionMatch = useCallback(
|
const checkForMentionMatch = useCallback(
|
||||||
(text: string) => {
|
(text: string) => {
|
||||||
|
@ -300,6 +246,8 @@ export const MentionPlugin = (): JSX.Element | null => {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
dispatch(fetchComposeSuggestions(composeId, matchArr[0]));
|
||||||
|
|
||||||
const mentionLength = matchArr[3].length + 1;
|
const mentionLength = matchArr[3].length + 1;
|
||||||
const startOffset = matchArr.index + matchArr[1].length;
|
const startOffset = matchArr.index + matchArr[1].length;
|
||||||
const endOffset = startOffset + mentionLength;
|
const endOffset = startOffset + mentionLength;
|
||||||
|
@ -315,6 +263,31 @@ export const MentionPlugin = (): JSX.Element | null => {
|
||||||
createMentionNode,
|
createMentionNode,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const renderSuggestion = (suggestion: string, i: number) => {
|
||||||
|
const inner = <AutosuggestAccount id={suggestion} />;
|
||||||
|
const key = suggestion;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
role='button'
|
||||||
|
tabIndex={0}
|
||||||
|
key={key}
|
||||||
|
data-index={i}
|
||||||
|
className={clsx({
|
||||||
|
'px-4 py-2.5 text-sm text-gray-700 dark:text-gray-500 hover:bg-gray-100 dark:hover:bg-gray-800 focus:bg-gray-100 dark:focus:bg-primary-800 group': true,
|
||||||
|
'bg-gray-100 dark:bg-gray-800 hover:bg-gray-100 dark:hover:bg-gray-800': i === selectedSuggestion,
|
||||||
|
})}
|
||||||
|
onMouseDown={onSelectSuggestion}
|
||||||
|
>
|
||||||
|
{inner}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (suggestions && suggestions.size > 0) setSuggestionsHidden(false);
|
||||||
|
}, [suggestions]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<TypeaheadMenuPlugin<MentionTypeaheadOption>
|
<TypeaheadMenuPlugin<MentionTypeaheadOption>
|
||||||
onQueryChange={setQueryString}
|
onQueryChange={setQueryString}
|
||||||
|
@ -327,24 +300,15 @@ export const MentionPlugin = (): JSX.Element | null => {
|
||||||
) =>
|
) =>
|
||||||
anchorElementRef.current && results.length
|
anchorElementRef.current && results.length
|
||||||
? ReactDOM.createPortal(
|
? ReactDOM.createPortal(
|
||||||
<div className='typeahead-popover mentions-menu'>
|
<div
|
||||||
<ul>
|
// style={setPortalPosition()}
|
||||||
{options.map((option, i: number) => (
|
className={clsx({
|
||||||
<MentionsTypeaheadMenuItem
|
'mt-6 fixed z-1000 shadow bg-white dark:bg-gray-900 rounded-lg py-1 space-y-0 dark:ring-2 dark:ring-primary-700 focus:outline-none': true,
|
||||||
index={i}
|
hidden: suggestionsHidden || suggestions.isEmpty(),
|
||||||
isSelected={selectedIndex === i}
|
block: !suggestionsHidden && !suggestions.isEmpty(),
|
||||||
onClick={() => {
|
})}
|
||||||
setHighlightedIndex(i);
|
>
|
||||||
selectOptionAndCleanUp(option);
|
{suggestions.map(renderSuggestion)}
|
||||||
}}
|
|
||||||
onMouseEnter={() => {
|
|
||||||
setHighlightedIndex(i);
|
|
||||||
}}
|
|
||||||
key={option.key}
|
|
||||||
option={option}
|
|
||||||
/>
|
|
||||||
))}
|
|
||||||
</ul>
|
|
||||||
</div>,
|
</div>,
|
||||||
anchorElementRef.current,
|
anchorElementRef.current,
|
||||||
)
|
)
|
||||||
|
|
Loading…
Reference in a new issue