fix(web): Editor

This commit is contained in:
zhaoying
2025-12-31 12:15:33 +08:00
parent d476d92b7d
commit 4c706048de
3 changed files with 94 additions and 86 deletions

View File

@@ -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

View File

@@ -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;

View File

@@ -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
}
});