From 2833bf4458dcf2feff179abeae6a20a5a03db707 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?marcin=20miko=C5=82ajczak?= Date: Thu, 23 Mar 2023 22:35:57 +0100 Subject: [PATCH] Work on mentions suggestions MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: marcin mikołajczak --- app/soapbox/features/compose/editor/index.tsx | 2 - .../editor/plugins/typeahead-menu-plugin.tsx | 38 ++------ app/styles/components/lexical.scss | 96 +++++++++++++++++++ 3 files changed, 104 insertions(+), 32 deletions(-) diff --git a/app/soapbox/features/compose/editor/index.tsx b/app/soapbox/features/compose/editor/index.tsx index 1b6ba47c5..89ba043fe 100644 --- a/app/soapbox/features/compose/editor/index.tsx +++ b/app/soapbox/features/compose/editor/index.tsx @@ -35,8 +35,6 @@ const StatePlugin = ({ composeId }: { composeId: string }) => { const dispatch = useAppDispatch(); const [editor] = useLexicalComposerContext(); - (window as any).xd = editor; - useEffect(() => { editor.registerUpdateListener(({ editorState }) => { dispatch(setEditorState(composeId, editorState.isEmpty() ? null : JSON.stringify(editorState.toJSON()))); diff --git a/app/soapbox/features/compose/editor/plugins/typeahead-menu-plugin.tsx b/app/soapbox/features/compose/editor/plugins/typeahead-menu-plugin.tsx index 601c8acdc..93b206b9c 100644 --- a/app/soapbox/features/compose/editor/plugins/typeahead-menu-plugin.tsx +++ b/app/soapbox/features/compose/editor/plugins/typeahead-menu-plugin.tsx @@ -103,19 +103,6 @@ const scrollIntoViewIfNeeded = (target: HTMLElement) => { target.scrollIntoView({ block: 'nearest' }); }; -function getTextUpToAnchor(selection: RangeSelection): string | null { - const anchor = selection.anchor; - if (!['mention', 'text'].includes(anchor.type)) { - return null; - } - const anchorNode = anchor.getNode(); - if (anchor.type === 'text' && !anchorNode.isSimpleText()) { - return null; - } - const anchorOffset = anchor.offset; - return anchorNode.getTextContent().slice(0, anchorOffset); -} - function tryToPositionRange(leadOffset: number, range: Range): boolean { const domSelection = window.getSelection(); if (domSelection === null || !domSelection.isCollapsed) { @@ -140,15 +127,12 @@ function tryToPositionRange(leadOffset: number, range: Range): boolean { } function getQueryTextForSearch(editor: LexicalEditor): string | null { - let text = null; - editor.getEditorState().read(() => { - const selection = $getSelection(); - if (!$isRangeSelection(selection)) { - return; - } - text = getTextUpToAnchor(selection); - }); - return text; + const state = editor.getEditorState(); + const node = (state._selection as RangeSelection)?.anchor?.getNode(); + + if (node && node.getType() === 'mention') return node.getTextContent(); + + return null; } /** @@ -173,10 +157,7 @@ function getFullMatchOffset( * Split Lexical TextNode and return a new TextNode only containing matched text. * Common use cases include: removing the node, replacing with a new node. */ -function splitNodeContainingQuery( - editor: LexicalEditor, - match: QueryMatch, -): TextNode | null { +function splitNodeContainingQuery(match: QueryMatch): TextNode | null { const selection = $getSelection(); if (!$isRangeSelection(selection) || !selection.isCollapsed()) { return null; @@ -369,10 +350,7 @@ function LexicalPopoverMenu({ const selectOptionAndCleanUp = useCallback( (selectedEntry: TOption) => { editor.update(() => { - const textNodeContainingQuery = splitNodeContainingQuery( - editor, - resolution.match, - ); + const textNodeContainingQuery = splitNodeContainingQuery(resolution.match); onSelectOption( selectedEntry, diff --git a/app/styles/components/lexical.scss b/app/styles/components/lexical.scss index 94163d1ff..999352e7c 100644 --- a/app/styles/components/lexical.scss +++ b/app/styles/components/lexical.scss @@ -228,3 +228,99 @@ border-radius: 4px; } } + +.typeahead-popover { + background: #fff; + box-shadow: 0 5px 10px rgba(0, 0, 0, 0.3); + border-radius: 8px; + margin-top: 25px; +} + +.typeahead-popover ul { + padding: 0; + list-style: none; + margin: 0; + border-radius: 8px; + max-height: 200px; + overflow-y: scroll; +} + +.typeahead-popover ul::-webkit-scrollbar { + display: none; +} + +.typeahead-popover ul { + -ms-overflow-style: none; + scrollbar-width: none; +} + +.typeahead-popover ul li { + margin: 0; + min-width: 180px; + font-size: 14px; + outline: none; + cursor: pointer; + border-radius: 8px; +} + +.typeahead-popover ul li.selected { + background: #eee; +} + +.typeahead-popover li { + margin: 0 8px; + padding: 8px; + color: #050505; + cursor: pointer; + line-height: 16px; + font-size: 15px; + display: flex; + align-content: center; + flex-direction: row; + flex-shrink: 0; + background-color: #fff; + border-radius: 8px; + border: 0; +} + +.typeahead-popover li.active { + display: flex; + width: 20px; + height: 20px; + background-size: contain; +} + +.typeahead-popover li:first-child { + border-radius: 8px 8px 0 0; +} + +.typeahead-popover li:last-child { + border-radius: 0 0 8px 8px; +} + +.typeahead-popover li:hover { + background-color: #eee; +} + +.typeahead-popover li .text { + display: flex; + line-height: 20px; + flex-grow: 1; + min-width: 150px; +} + +.typeahead-popover li .icon { + display: flex; + width: 20px; + height: 20px; + user-select: none; + margin-right: 8px; + line-height: 16px; + background-size: contain; + background-repeat: no-repeat; + background-position: center; +} + +.mentions-menu { + width: 250px; +}