diff --git a/TestMasterUI/src/components/client-views/HTTPClientView.jsx b/TestMasterUI/src/components/client-views/HTTPClientView.jsx index 4efea0b..79169fa 100644 --- a/TestMasterUI/src/components/client-views/HTTPClientView.jsx +++ b/TestMasterUI/src/components/client-views/HTTPClientView.jsx @@ -77,8 +77,7 @@ export const HTTPClientView = ({model, index}) => { })); }; - const handlePathChange = (e) => { - const path = e.target.value; + const handlePathChange = async (path) => { if (!path) { setCurrentModel(prev => ({ ...prev, @@ -87,12 +86,14 @@ export const HTTPClientView = ({model, index}) => { })); return; } - const queryParams = parseQueryParams(path); - setCurrentModel(prev => ({ - ...prev, - path, - queryParams - })); + setCurrentModel(prev => { + const queryParams = parseQueryParams(path); + return { + ...prev, + path, + queryParams + }; + }); }; diff --git a/TestMasterUI/src/components/shared/EditableInput.jsx b/TestMasterUI/src/components/shared/EditableInput.jsx index 29ade5e..bae33df 100644 --- a/TestMasterUI/src/components/shared/EditableInput.jsx +++ b/TestMasterUI/src/components/shared/EditableInput.jsx @@ -106,21 +106,67 @@ const EditableInput = ({ return parts; }; - // useEffect(() => { - // if (value !== internalValue) { - // setInternalValue(value); - // } - // }, [value]); + const getCurrentCursor = () => { + const selection = window.getSelection(); + if (selection.rangeCount === 0) return null; + + const range = selection.getRangeAt(0); + const preCaretRange = range.cloneRange(); + preCaretRange.selectNodeContents(contentRef.current); + preCaretRange.setEnd(range.endContainer, range.endOffset); + const content = preCaretRange.toString().trim(); + if (content === '\u200B') { + return 0; + } + return content.replace('\u200B', '').length; + }; + + const restoreCursor = (position) => { + const element = contentRef.current; + const selection = window.getSelection(); + const range = document.createRange(); + range.setStart(element, 0); + range.collapse(true); + + const nodeStack = [element]; + let node, foundStart = false, stop = false; + let charIndex = 0; + + while (!stop && (node = nodeStack.pop())) { + if (node.nodeType === 3) { + const nextCharIndex = charIndex + node.length; + if (!foundStart && position >= charIndex && position <= nextCharIndex) { + range.setStart(node, position - charIndex); + foundStart = true; + } + if (foundStart && position >= charIndex && position <= nextCharIndex) { + range.setEnd(node, position - charIndex); + stop = true; + } + charIndex = nextCharIndex; + } else { + let i = node.childNodes.length; + while (i--) { + nodeStack.push(node.childNodes[i]); + } + } + } + + selection.removeAllRanges(); + selection.addRange(range); + }; const handleInput = (e) => { - console.log('handleInput', e); const newValue = e.target.textContent; - const newSelection = saveSelection(); - - console.log(newValue); + const cursorPosition = getCurrentCursor(); setInternalValue(newValue); - setSelection(newSelection); onChange(newValue); + + requestAnimationFrame(() => { + if (contentRef.current) { + restoreCursor(cursorPosition); + } + }); }; const handlePaste = (e) => { @@ -130,22 +176,18 @@ const EditableInput = ({ if (selection.rangeCount > 0) { const range = selection.getRangeAt(0); + const cursorPosition = getCurrentCursor(); range.deleteContents(); + const textNode = document.createTextNode(text); + range.insertNode(textNode); - const processedContent = parseContent(text); - const fragment = document.createDocumentFragment(); + const newValue = contentRef.current.textContent; + setInternalValue(newValue); + onChange(newValue); - processedContent.forEach(({ type, content }) => { - const span = document.createElement('span'); - span.textContent = content; - if (type === 'variable') { - span.className = 'bg-blue-100 text-blue-800 rounded px-1'; - } - fragment.appendChild(span); + requestAnimationFrame(() => { + restoreCursor(cursorPosition + text.length); }); - - range.insertNode(fragment); - handleInput({ target: contentRef.current }); } }; @@ -195,7 +237,9 @@ const EditableInput = ({ data-value={internalValue} data-name={name} suppressContentEditableWarning={true} - /> + > + {internalValue || '\u200B'} + ); };