diff --git a/frontend/src/features/workspace/WorkspaceView.tsx b/frontend/src/features/workspace/WorkspaceView.tsx index f8bcac3..2b9161e 100644 --- a/frontend/src/features/workspace/WorkspaceView.tsx +++ b/frontend/src/features/workspace/WorkspaceView.tsx @@ -2190,23 +2190,20 @@ export const WorkspaceView: React.FC = ({ if (el instanceof HTMLTextAreaElement) { autoResizeTextarea(el); } - if ('setSelectionRange' in el && typeof el.value === 'string') { + // Only programmatically adjust selection if the element is NOT already the actively focused element! + if (document.activeElement !== el && 'setSelectionRange' in el && typeof el.value === 'string') { const segStartAttr = el.getAttribute('data-seg-start'); const segStart = segStartAttr !== null ? parseInt(segStartAttr, 10) || 0 : 0; const desiredOffset = activeCursorOffsetRef.current; const localPos = Math.max(0, Math.min(el.value.length, desiredOffset - segStart)); - if (document.activeElement !== el) { - el.focus(); - } + el.focus(); try { - if (el.selectionStart !== localPos || el.selectionEnd !== localPos) { - el.setSelectionRange(localPos, localPos); - } + el.setSelectionRange(localPos, localPos); } catch { // Ignore selection range errors on unsupported inputs } } - }, [activeEditingBlockId, activeCursorOffset, selectedDoc]); + }, [activeEditingBlockId]); // Keyboard Navigation: Enter, Shift+Enter, Tab, Shift+Tab, Backspace, / Slash Commands const handleKeyDown = ( @@ -3073,7 +3070,29 @@ export const WorkspaceView: React.FC = ({ onClick={(e) => { if (isReadOnly) return; if ((e.target as HTMLElement).closest('button, a')) return; - focusBlock(block.id); + let clickOffset: number | undefined = undefined; + try { + if (document.caretRangeFromPoint) { + const range = document.caretRangeFromPoint(e.clientX, e.clientY); + if (range && e.currentTarget.contains(range.startContainer)) { + const preCaretRange = range.cloneRange(); + preCaretRange.selectNodeContents(e.currentTarget); + preCaretRange.setEnd(range.endContainer, range.endOffset); + clickOffset = preCaretRange.toString().length; + } + } else if ((document as any).caretPositionFromPoint) { + const pos = (document as any).caretPositionFromPoint(e.clientX, e.clientY); + if (pos && pos.offsetNode && e.currentTarget.contains(pos.offsetNode)) { + const range = document.createRange(); + range.selectNodeContents(e.currentTarget); + range.setEnd(pos.offsetNode, pos.offset); + clickOffset = range.toString().length; + } + } + } catch { + // fallback to end of text + } + focusBlock(block.id, clickOffset); }} className={`w-full text-sm leading-relaxed break-words whitespace-pre-wrap min-h-[22px] cursor-text ${extraTextClass}`} > @@ -3099,7 +3118,7 @@ export const WorkspaceView: React.FC = ({ value={block.content || ''} placeholder="Notiz eingeben… (/ für Befehle)" onFocus={() => { - handleBlockFocus(); + handleBlockFocus(block.id); }} onBlur={() => { setActiveEditingBlockId((prev) => (prev === block.id ? null : prev)); @@ -3107,8 +3126,24 @@ export const WorkspaceView: React.FC = ({ }} onChange={(e) => { autoResizeTextarea(e.target); + const pos = e.target.selectionStart; + if (typeof pos === 'number') { + activeCursorOffsetRef.current = pos; + } handleContentChange(block, e.target.value); }} + onSelect={(e) => { + const target = e.target as HTMLTextAreaElement; + if (typeof target.selectionStart === 'number') { + activeCursorOffsetRef.current = target.selectionStart; + } + }} + onClick={(e) => { + const target = e.target as HTMLTextAreaElement; + if (typeof target.selectionStart === 'number') { + activeCursorOffsetRef.current = target.selectionStart; + } + }} onKeyDown={(e) => handleKeyDown(e, block, originalIndex)} className={`w-full bg-transparent text-sm leading-relaxed focus:outline-none resize-none overflow-hidden break-words whitespace-pre-wrap placeholder:text-faint p-0 m-0 border-0 ${extraTextClass}`} /> @@ -4642,12 +4677,18 @@ export const WorkspaceView: React.FC = ({ rows={1} readOnly={isReadOnly} value={block.content} - onFocus={handleBlockFocus} + onFocus={() => handleBlockFocus(block.id)} onBlur={handleBlockBlur} onChange={(e) => { autoResizeTextarea(e.target); + const pos = e.target.selectionStart; + if (typeof pos === 'number') activeCursorOffsetRef.current = pos; handleContentChange(block, e.target.value); }} + onSelect={(e) => { + const target = e.target as HTMLTextAreaElement; + if (typeof target.selectionStart === 'number') activeCursorOffsetRef.current = target.selectionStart; + }} onKeyDown={(e) => handleKeyDown(e, block, originalIndex)} className={`w-full bg-transparent text-lg font-bold text-main focus:outline-none resize-none overflow-hidden break-words whitespace-pre-wrap p-0 m-0 border-0 ${ isReadOnly ? 'cursor-default' : '' @@ -4665,12 +4706,18 @@ export const WorkspaceView: React.FC = ({ rows={1} readOnly={isReadOnly} value={block.content} - onFocus={handleBlockFocus} + onFocus={() => handleBlockFocus(block.id)} onBlur={handleBlockBlur} onChange={(e) => { autoResizeTextarea(e.target); + const pos = e.target.selectionStart; + if (typeof pos === 'number') activeCursorOffsetRef.current = pos; handleContentChange(block, e.target.value); }} + onSelect={(e) => { + const target = e.target as HTMLTextAreaElement; + if (typeof target.selectionStart === 'number') activeCursorOffsetRef.current = target.selectionStart; + }} onKeyDown={(e) => handleKeyDown(e, block, originalIndex)} className={`w-full bg-transparent text-base font-semibold text-accent focus:outline-none resize-none overflow-hidden break-words whitespace-pre-wrap p-0 m-0 border-0 ${ isReadOnly ? 'cursor-default' : '' @@ -4688,12 +4735,18 @@ export const WorkspaceView: React.FC = ({ rows={1} readOnly={isReadOnly} value={block.content} - onFocus={handleBlockFocus} + onFocus={() => handleBlockFocus(block.id)} onBlur={handleBlockBlur} onChange={(e) => { autoResizeTextarea(e.target); + const pos = e.target.selectionStart; + if (typeof pos === 'number') activeCursorOffsetRef.current = pos; handleContentChange(block, e.target.value); }} + onSelect={(e) => { + const target = e.target as HTMLTextAreaElement; + if (typeof target.selectionStart === 'number') activeCursorOffsetRef.current = target.selectionStart; + }} onKeyDown={(e) => handleKeyDown(e, block, originalIndex)} className={`w-full bg-transparent text-sm font-semibold text-main focus:outline-none resize-none overflow-hidden break-words whitespace-pre-wrap p-0 m-0 border-0 ${ isReadOnly ? 'cursor-default' : ''