feat(workspace): intelligent multi-line clipboard paste with markdown prefix & indentation parsing
MindSift CI & Docker Release / Build, Test in Container & Push (push) Successful in 1m2s
MindSift CI & Docker Release / Build, Test in Container & Push (push) Successful in 1m2s
This commit is contained in:
1 parent
fdbf691a8d
commit
6a261e3453
3 files changed
+204
No files matched your search
@@ -69,6 +69,7 @@ export interface OutlinerBlockProps {
|
||||
isDragTarget?: boolean;
|
||||
isTargeted?: boolean;
|
||||
registerTextareaRef: (blockId: string, el: HTMLTextAreaElement | null) => void;
|
||||
onPasteMultiLines?: (blockId: string, textBefore: string, textAfter: string, rawLines: string[]) => void;
|
||||
}
|
||||
|
||||
export const OutlinerBlock: React.FC<OutlinerBlockProps> = ({
|
||||
@@ -97,6 +98,7 @@ export const OutlinerBlock: React.FC<OutlinerBlockProps> = ({
|
||||
isDragTarget,
|
||||
isTargeted,
|
||||
registerTextareaRef,
|
||||
onPasteMultiLines,
|
||||
}) => {
|
||||
const [localContent, setLocalContent] = useState<string>(block.content || '');
|
||||
const [isLocallyFocused, setIsLocallyFocused] = useState<boolean>(false);
|
||||
@@ -118,12 +120,15 @@ export const OutlinerBlock: React.FC<OutlinerBlockProps> = ({
|
||||
onUpdateBlockRef.current = onUpdateBlock;
|
||||
const onCreateBlockBelowRef = useRef(onCreateBlockBelow);
|
||||
onCreateBlockBelowRef.current = onCreateBlockBelow;
|
||||
const onPasteMultiLinesRef = useRef(onPasteMultiLines);
|
||||
onPasteMultiLinesRef.current = onPasteMultiLines;
|
||||
const onDeleteBlockRef = useRef(onDeleteBlock);
|
||||
onDeleteBlockRef.current = onDeleteBlock;
|
||||
const onIndentBlockRef = useRef(onIndentBlock);
|
||||
onIndentBlockRef.current = onIndentBlock;
|
||||
const onNavigateFocusRef = useRef(onNavigateFocus);
|
||||
onNavigateFocusRef.current = onNavigateFocus;
|
||||
const isPastingRef = useRef<boolean>(false);
|
||||
|
||||
// Track if this input currently has actual focus in an active browser window
|
||||
const isFocusedRef = useRef<boolean>(false);
|
||||
@@ -315,6 +320,10 @@ export const OutlinerBlock: React.FC<OutlinerBlockProps> = ({
|
||||
const handleBlur = () => {
|
||||
isFocusedRef.current = false;
|
||||
setIsLocallyFocused(false);
|
||||
if (isPastingRef.current) {
|
||||
isPastingRef.current = false;
|
||||
return;
|
||||
}
|
||||
if (debounceTimerRef.current !== null) {
|
||||
window.clearTimeout(debounceTimerRef.current);
|
||||
debounceTimerRef.current = null;
|
||||
@@ -324,6 +333,29 @@ export const OutlinerBlock: React.FC<OutlinerBlockProps> = ({
|
||||
}
|
||||
};
|
||||
|
||||
const handlePaste = (e: React.ClipboardEvent<HTMLTextAreaElement>) => {
|
||||
if (!canEdit || block.type === 'code') return;
|
||||
const pastedText = e.clipboardData.getData('text');
|
||||
if (!pastedText || !pastedText.includes('\n')) return;
|
||||
|
||||
e.preventDefault();
|
||||
const rawLines = pastedText.split(/\r?\n/);
|
||||
if (rawLines.length <= 1) return;
|
||||
|
||||
const start = e.currentTarget.selectionStart ?? localContent.length;
|
||||
const end = e.currentTarget.selectionEnd ?? start;
|
||||
const textBefore = localContent.slice(0, start);
|
||||
const textAfter = localContent.slice(end);
|
||||
|
||||
if (debounceTimerRef.current !== null) {
|
||||
window.clearTimeout(debounceTimerRef.current);
|
||||
debounceTimerRef.current = null;
|
||||
}
|
||||
|
||||
isPastingRef.current = true;
|
||||
onPasteMultiLinesRef.current?.(block.id, textBefore, textAfter, rawLines);
|
||||
};
|
||||
|
||||
const handleKeyDown = (e: React.KeyboardEvent<HTMLTextAreaElement>) => {
|
||||
if (!canEdit) return;
|
||||
|
||||
@@ -742,6 +774,7 @@ export const OutlinerBlock: React.FC<OutlinerBlockProps> = ({
|
||||
onBlur={handleBlur}
|
||||
onChange={handleContentChange}
|
||||
onKeyDown={handleKeyDown}
|
||||
onPaste={handlePaste}
|
||||
placeholder={
|
||||
index === 0 && level === 0
|
||||
? 'Schreibe einen Punkt… (Enter = Neu, Tab = Einrücken, Shift+Tab = Ausrücken, / für Befehle)'
|
||||
|
||||
Reference in new issue
Block a user