fix(web): Editor
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
import { useEffect, useState, type FC } from 'react';
|
||||
import { useLexicalComposerContext } from '@lexical/react/LexicalComposerContext';
|
||||
import { $getRoot, $getSelection } from 'lexical';
|
||||
import { $getSelection, $isRangeSelection } from 'lexical';
|
||||
|
||||
import { INSERT_VARIABLE_COMMAND } from '../commands';
|
||||
import type { NodeProperties } from '../../../types'
|
||||
@@ -23,43 +23,55 @@ const AutocompletePlugin: FC<{ options: Suggestion[] }> = ({ options }) => {
|
||||
useEffect(() => {
|
||||
return editor.registerUpdateListener(({ editorState }) => {
|
||||
editorState.read(() => {
|
||||
const root = $getRoot();
|
||||
const text = root.getTextContent();
|
||||
const shouldShow = text.includes('/');
|
||||
const selection = $getSelection();
|
||||
|
||||
if (!selection || !$isRangeSelection(selection)) {
|
||||
setShowSuggestions(false);
|
||||
return;
|
||||
}
|
||||
|
||||
const anchorNode = selection.anchor.getNode();
|
||||
const anchorOffset = selection.anchor.offset;
|
||||
|
||||
// Get the text content of the current node
|
||||
const nodeText = anchorNode.getTextContent();
|
||||
|
||||
// Check if we have a '/' at the current position or after line break
|
||||
const textBeforeCursor = nodeText.substring(0, anchorOffset);
|
||||
const shouldShow = textBeforeCursor.endsWith('/') ||
|
||||
(textBeforeCursor === '/' && anchorOffset === 1);
|
||||
|
||||
setShowSuggestions(shouldShow);
|
||||
|
||||
if (shouldShow) {
|
||||
const selection = $getSelection();
|
||||
if (selection) {
|
||||
const domSelection = window.getSelection();
|
||||
if (domSelection && domSelection.rangeCount > 0) {
|
||||
const range = domSelection.getRangeAt(0);
|
||||
const rect = range.getBoundingClientRect();
|
||||
const domSelection = window.getSelection();
|
||||
if (domSelection && domSelection.rangeCount > 0) {
|
||||
const range = domSelection.getRangeAt(0);
|
||||
const rect = range.getBoundingClientRect();
|
||||
|
||||
const popupWidth = 280;
|
||||
const popupHeight = 200;
|
||||
const viewportWidth = window.innerWidth;
|
||||
const viewportHeight = window.innerHeight;
|
||||
const popupWidth = 280;
|
||||
const popupHeight = 200;
|
||||
const viewportWidth = window.innerWidth;
|
||||
const viewportHeight = window.innerHeight;
|
||||
|
||||
let left = rect.left;
|
||||
let top = rect.top - 10;
|
||||
let left = rect.left;
|
||||
let top = rect.top - 10;
|
||||
|
||||
if (left + popupWidth > viewportWidth) {
|
||||
left = viewportWidth - popupWidth - 10;
|
||||
}
|
||||
if (left < 10) {
|
||||
left = 10;
|
||||
}
|
||||
|
||||
if (top - popupHeight < 10) {
|
||||
top = rect.bottom + 10;
|
||||
if (top + popupHeight > viewportHeight) {
|
||||
top = viewportHeight - popupHeight - 10;
|
||||
}
|
||||
}
|
||||
|
||||
setPopupPosition({ top, left });
|
||||
if (left + popupWidth > viewportWidth) {
|
||||
left = viewportWidth - popupWidth - 10;
|
||||
}
|
||||
if (left < 10) {
|
||||
left = 10;
|
||||
}
|
||||
|
||||
if (top - popupHeight < 10) {
|
||||
top = rect.bottom + 10;
|
||||
if (top + popupHeight > viewportHeight) {
|
||||
top = viewportHeight - popupHeight - 10;
|
||||
}
|
||||
}
|
||||
|
||||
setPopupPosition({ top, left });
|
||||
}
|
||||
}
|
||||
});
|
||||
@@ -112,7 +124,7 @@ const AutocompletePlugin: FC<{ options: Suggestion[] }> = ({ options }) => {
|
||||
<div style={{ padding: '4px 12px', fontSize: '12px', color: '#999', fontWeight: 'bold' }}>
|
||||
{nodeName}
|
||||
</div>
|
||||
{nodeOptions.map((option, index) => {
|
||||
{nodeOptions.map((option) => {
|
||||
const globalIndex = Object.values(groupedSuggestions).flat().indexOf(option);
|
||||
return (
|
||||
<div
|
||||
|
||||
@@ -4,10 +4,11 @@ import {
|
||||
$createParagraphNode,
|
||||
$createTextNode,
|
||||
$getRoot,
|
||||
$getSelection,
|
||||
$setSelection,
|
||||
$createRangeSelection,
|
||||
$isParagraphNode,
|
||||
$isTextNode,
|
||||
$isRangeSelection,
|
||||
} from 'lexical';
|
||||
|
||||
import { $createVariableNode } from '../nodes/VariableNode';
|
||||
@@ -26,64 +27,45 @@ const CommandPlugin = () => {
|
||||
INSERT_VARIABLE_COMMAND,
|
||||
(payload: InsertVariableCommandPayload) => {
|
||||
editor.update(() => {
|
||||
const root = $getRoot();
|
||||
const text = root.getTextContent();
|
||||
const lastSlashIndex = text.lastIndexOf('/');
|
||||
const selection = $getSelection();
|
||||
if (!selection || !$isRangeSelection(selection)) return;
|
||||
|
||||
// Find the paragraph and the position to insert
|
||||
const paragraph = root.getFirstChild();
|
||||
if (!paragraph || !$isParagraphNode(paragraph)) return;
|
||||
const anchorNode = selection.anchor.getNode();
|
||||
const anchorOffset = selection.anchor.offset;
|
||||
|
||||
const children = paragraph.getChildren();
|
||||
let insertPosition = 0;
|
||||
let currentTextLength = 0;
|
||||
|
||||
// Find where to insert the new tag
|
||||
for (let i = 0; i < children.length; i++) {
|
||||
const child = children[i];
|
||||
const childText = child.getTextContent();
|
||||
if ($isTextNode(anchorNode)) {
|
||||
const nodeText = anchorNode.getTextContent();
|
||||
const textBeforeCursor = nodeText.substring(0, anchorOffset);
|
||||
const textAfterCursor = nodeText.substring(anchorOffset);
|
||||
|
||||
if (currentTextLength + childText.length > lastSlashIndex) {
|
||||
// Split this text node if needed
|
||||
if ($isTextNode(child)) {
|
||||
const beforeSlash = childText.substring(0, lastSlashIndex - currentTextLength);
|
||||
const afterSlash = childText.substring(lastSlashIndex - currentTextLength + 1);
|
||||
|
||||
if (beforeSlash) {
|
||||
child.setTextContent(beforeSlash);
|
||||
insertPosition = i + 1;
|
||||
} else {
|
||||
insertPosition = i;
|
||||
child.remove();
|
||||
}
|
||||
|
||||
// Insert tag and space
|
||||
const tagNode = $createVariableNode(payload.data);
|
||||
const spaceNode = $createTextNode(' ');
|
||||
|
||||
if (insertPosition < paragraph.getChildrenSize()) {
|
||||
paragraph.getChildAtIndex(insertPosition)?.insertBefore(tagNode);
|
||||
tagNode.insertAfter(spaceNode);
|
||||
} else {
|
||||
paragraph.append(tagNode);
|
||||
paragraph.append(spaceNode);
|
||||
}
|
||||
|
||||
if (afterSlash) {
|
||||
spaceNode.insertAfter($createTextNode(afterSlash));
|
||||
}
|
||||
|
||||
// Set cursor after space
|
||||
const selection = $createRangeSelection();
|
||||
selection.anchor.set(spaceNode.getKey(), 1, 'text');
|
||||
selection.focus.set(spaceNode.getKey(), 1, 'text');
|
||||
$setSelection(selection);
|
||||
// Find the last '/' position
|
||||
const lastSlashIndex = textBeforeCursor.lastIndexOf('/');
|
||||
|
||||
if (lastSlashIndex !== -1) {
|
||||
// Split the text: before slash, insert variable, after cursor
|
||||
const beforeSlash = textBeforeCursor.substring(0, lastSlashIndex);
|
||||
|
||||
// Update the current text node with text before slash
|
||||
anchorNode.setTextContent(beforeSlash);
|
||||
|
||||
// Create and insert the variable node
|
||||
const tagNode = $createVariableNode(payload.data);
|
||||
const spaceNode = $createTextNode(' ');
|
||||
|
||||
anchorNode.insertAfter(tagNode);
|
||||
tagNode.insertAfter(spaceNode);
|
||||
|
||||
// Add remaining text if any
|
||||
if (textAfterCursor) {
|
||||
spaceNode.insertAfter($createTextNode(textAfterCursor));
|
||||
}
|
||||
break;
|
||||
|
||||
// Set cursor after space
|
||||
const newSelection = $createRangeSelection();
|
||||
newSelection.anchor.set(spaceNode.getKey(), 1, 'text');
|
||||
newSelection.focus.set(spaceNode.getKey(), 1, 'text');
|
||||
$setSelection(newSelection);
|
||||
}
|
||||
|
||||
currentTextLength += childText.length;
|
||||
insertPosition = i + 1;
|
||||
}
|
||||
});
|
||||
return true;
|
||||
|
||||
@@ -237,6 +237,20 @@ const Properties: FC<PropertiesProps> = ({
|
||||
});
|
||||
}
|
||||
break
|
||||
case 'knowledge-retrieval':
|
||||
const knowledgeKey = `${nodeId}_message`;
|
||||
if (!addedKeys.has(knowledgeKey)) {
|
||||
addedKeys.add(knowledgeKey);
|
||||
variableList.push({
|
||||
key: knowledgeKey,
|
||||
label: 'message',
|
||||
type: 'variable',
|
||||
dataType: 'String',
|
||||
value: `${nodeId}.message`,
|
||||
nodeData: nodeData,
|
||||
});
|
||||
}
|
||||
break
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
Reference in New Issue
Block a user