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 { useEffect, useState, type FC } from 'react';
import { useLexicalComposerContext } from '@lexical/react/LexicalComposerContext'; import { useLexicalComposerContext } from '@lexical/react/LexicalComposerContext';
import { $getRoot, $getSelection } from 'lexical'; import { $getSelection, $isRangeSelection } from 'lexical';
import { INSERT_VARIABLE_COMMAND } from '../commands'; import { INSERT_VARIABLE_COMMAND } from '../commands';
import type { NodeProperties } from '../../../types' import type { NodeProperties } from '../../../types'
@@ -23,14 +23,27 @@ const AutocompletePlugin: FC<{ options: Suggestion[] }> = ({ options }) => {
useEffect(() => { useEffect(() => {
return editor.registerUpdateListener(({ editorState }) => { return editor.registerUpdateListener(({ editorState }) => {
editorState.read(() => { editorState.read(() => {
const root = $getRoot(); const selection = $getSelection();
const text = root.getTextContent();
const shouldShow = text.includes('/'); 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); setShowSuggestions(shouldShow);
if (shouldShow) { if (shouldShow) {
const selection = $getSelection();
if (selection) {
const domSelection = window.getSelection(); const domSelection = window.getSelection();
if (domSelection && domSelection.rangeCount > 0) { if (domSelection && domSelection.rangeCount > 0) {
const range = domSelection.getRangeAt(0); const range = domSelection.getRangeAt(0);
@@ -61,7 +74,6 @@ const AutocompletePlugin: FC<{ options: Suggestion[] }> = ({ options }) => {
setPopupPosition({ top, left }); setPopupPosition({ top, left });
} }
} }
}
}); });
}); });
}, [editor]); }, [editor]);
@@ -112,7 +124,7 @@ const AutocompletePlugin: FC<{ options: Suggestion[] }> = ({ options }) => {
<div style={{ padding: '4px 12px', fontSize: '12px', color: '#999', fontWeight: 'bold' }}> <div style={{ padding: '4px 12px', fontSize: '12px', color: '#999', fontWeight: 'bold' }}>
{nodeName} {nodeName}
</div> </div>
{nodeOptions.map((option, index) => { {nodeOptions.map((option) => {
const globalIndex = Object.values(groupedSuggestions).flat().indexOf(option); const globalIndex = Object.values(groupedSuggestions).flat().indexOf(option);
return ( return (
<div <div

View File

@@ -4,10 +4,11 @@ import {
$createParagraphNode, $createParagraphNode,
$createTextNode, $createTextNode,
$getRoot, $getRoot,
$getSelection,
$setSelection, $setSelection,
$createRangeSelection, $createRangeSelection,
$isParagraphNode,
$isTextNode, $isTextNode,
$isRangeSelection,
} from 'lexical'; } from 'lexical';
import { $createVariableNode } from '../nodes/VariableNode'; import { $createVariableNode } from '../nodes/VariableNode';
@@ -26,64 +27,45 @@ const CommandPlugin = () => {
INSERT_VARIABLE_COMMAND, INSERT_VARIABLE_COMMAND,
(payload: InsertVariableCommandPayload) => { (payload: InsertVariableCommandPayload) => {
editor.update(() => { editor.update(() => {
const root = $getRoot(); const selection = $getSelection();
const text = root.getTextContent(); if (!selection || !$isRangeSelection(selection)) return;
const lastSlashIndex = text.lastIndexOf('/');
// Find the paragraph and the position to insert const anchorNode = selection.anchor.getNode();
const paragraph = root.getFirstChild(); const anchorOffset = selection.anchor.offset;
if (!paragraph || !$isParagraphNode(paragraph)) return;
const children = paragraph.getChildren(); if ($isTextNode(anchorNode)) {
let insertPosition = 0; const nodeText = anchorNode.getTextContent();
let currentTextLength = 0; const textBeforeCursor = nodeText.substring(0, anchorOffset);
const textAfterCursor = nodeText.substring(anchorOffset);
// Find where to insert the new tag // Find the last '/' position
for (let i = 0; i < children.length; i++) { const lastSlashIndex = textBeforeCursor.lastIndexOf('/');
const child = children[i];
const childText = child.getTextContent();
if (currentTextLength + childText.length > lastSlashIndex) { if (lastSlashIndex !== -1) {
// Split this text node if needed // Split the text: before slash, insert variable, after cursor
if ($isTextNode(child)) { const beforeSlash = textBeforeCursor.substring(0, lastSlashIndex);
const beforeSlash = childText.substring(0, lastSlashIndex - currentTextLength);
const afterSlash = childText.substring(lastSlashIndex - currentTextLength + 1);
if (beforeSlash) { // Update the current text node with text before slash
child.setTextContent(beforeSlash); anchorNode.setTextContent(beforeSlash);
insertPosition = i + 1;
} else {
insertPosition = i;
child.remove();
}
// Insert tag and space // Create and insert the variable node
const tagNode = $createVariableNode(payload.data); const tagNode = $createVariableNode(payload.data);
const spaceNode = $createTextNode(' '); const spaceNode = $createTextNode(' ');
if (insertPosition < paragraph.getChildrenSize()) { anchorNode.insertAfter(tagNode);
paragraph.getChildAtIndex(insertPosition)?.insertBefore(tagNode);
tagNode.insertAfter(spaceNode); tagNode.insertAfter(spaceNode);
} else {
paragraph.append(tagNode);
paragraph.append(spaceNode);
}
if (afterSlash) { // Add remaining text if any
spaceNode.insertAfter($createTextNode(afterSlash)); if (textAfterCursor) {
spaceNode.insertAfter($createTextNode(textAfterCursor));
} }
// Set cursor after space // Set cursor after space
const selection = $createRangeSelection(); const newSelection = $createRangeSelection();
selection.anchor.set(spaceNode.getKey(), 1, 'text'); newSelection.anchor.set(spaceNode.getKey(), 1, 'text');
selection.focus.set(spaceNode.getKey(), 1, 'text'); newSelection.focus.set(spaceNode.getKey(), 1, 'text');
$setSelection(selection); $setSelection(newSelection);
} }
break;
}
currentTextLength += childText.length;
insertPosition = i + 1;
} }
}); });
return true; return true;

View File

@@ -237,6 +237,20 @@ const Properties: FC<PropertiesProps> = ({
}); });
} }
break 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
} }
}); });