fix(workspace): fix outliner cursor focus, enhanced task linking, nav order and Hauptebene drop zone
MindSift CI & Docker Release / Build, Test in Container & Push (push) Failing after 19s

This commit is contained in:
sandrodach committed 2026-10-27 20:26:45 +01:00
1 parent d39d1d1122
commit 94acb2638e
7 files changed
+300 -182

No files matched your search

@@ -1,8 +1,12 @@
import React, { useState, useRef, useEffect, useCallback, useMemo } from 'react';
import { Plus } from 'lucide-react';
import React, { useState, useRef, useEffect, useCallback, useMemo, forwardRef, useImperativeHandle } from 'react';
import { DocumentBlock, BlockType } from '../../lib/types';
import { OutlinerBlock } from './OutlinerBlock';
export interface OutlinerTreeHandle {
focusFirstBlock: () => void;
focusBlock: (blockId: string, cursor?: 'start' | 'end') => void;
}
export interface OutlinerTreeProps {
docId: string;
blocks: DocumentBlock[];
@@ -14,12 +18,13 @@ export interface OutlinerTreeProps {
onCreateBlock: (newBlock: DocumentBlock, afterBlockId?: string) => void;
onDeleteBlock: (blockId: string) => void;
onMoveBlock: (blockId: string, newIndex: number, level?: number) => void;
onOpenTask?: (taskId: string) => void;
onTagClick?: (tag: string) => void;
onWikiLinkClick?: (pageTitle: string) => void;
initialFocusedBlockId?: string | null;
}
export const OutlinerTree: React.FC<OutlinerTreeProps> = ({
export const OutlinerTree = forwardRef<OutlinerTreeHandle, OutlinerTreeProps>(({
docId,
blocks,
canEdit,
@@ -30,10 +35,11 @@ export const OutlinerTree: React.FC<OutlinerTreeProps> = ({
onCreateBlock,
onDeleteBlock,
onMoveBlock,
onOpenTask,
onTagClick,
onWikiLinkClick,
initialFocusedBlockId,
}) => {
}, ref) => {
const [focusedBlockId, setFocusedBlockId] = useState<string | null>(null);
const [highlightedBlockId, setHighlightedBlockId] = useState<string | null>(
initialFocusedBlockId || null
@@ -70,6 +76,32 @@ export const OutlinerTree: React.FC<OutlinerTreeProps> = ({
[]
);
useImperativeHandle(
ref,
() => ({
focusFirstBlock: () => {
if (blocks.length > 0) {
focusBlockInput(blocks[0].id, 'start');
} else {
const firstBlock: DocumentBlock = {
id: `blk_${Date.now().toString(36)}_0`,
documentId: docId,
type: 'text',
content: '',
level: levelOffset || 0,
position: 1000,
};
pendingFocusRef.current = { blockId: firstBlock.id, cursor: 'start' };
onCreateBlock(firstBlock);
}
},
focusBlock: (blockId: string, cursor: 'start' | 'end' = 'end') => {
focusBlockInput(blockId, cursor);
},
}),
[blocks, docId, levelOffset, onCreateBlock, focusBlockInput]
);
const registerTextareaRef = useCallback((blockId: string, el: HTMLTextAreaElement | null) => {
if (el) {
textareaMapRef.current.set(blockId, el);
@@ -380,6 +412,7 @@ export const OutlinerTree: React.FC<OutlinerTreeProps> = ({
return (
<OutlinerBlock
key={block.id}
docId={docId}
block={block}
index={index}
isLast={isLast}
@@ -394,6 +427,8 @@ export const OutlinerTree: React.FC<OutlinerTreeProps> = ({
onDeleteBlock={handleDeleteBlock}
onIndentBlock={handleIndentBlock}
onNavigateFocus={handleNavigateFocus}
onFocusBlock={setFocusedBlockId}
onOpenTask={onOpenTask}
onTagClick={onTagClick}
onWikiLinkClick={onWikiLinkClick}
onDragStartBlock={handleDragStartBlock}
@@ -408,9 +443,33 @@ export const OutlinerTree: React.FC<OutlinerTreeProps> = ({
{canEdit && (
<div
className={`pt-2 pl-2 transition-colors ${
className={`min-h-[14rem] pt-2 pl-2 transition-colors cursor-text ${
dragOverIndex === blocks.length ? 'border-t-2 border-accent bg-accent/10 rounded' : ''
}`}
onClick={(e) => {
if (e.target !== e.currentTarget && (e.target as HTMLElement).closest('button, a, input, textarea, select')) {
return;
}
if (blocks.length === 0) {
const firstBlock: DocumentBlock = {
id: `blk_${Date.now().toString(36)}_0`,
documentId: docId,
type: 'text',
content: '',
level: levelOffset || 0,
position: 1000,
};
pendingFocusRef.current = { blockId: firstBlock.id, cursor: 'start' };
onCreateBlock(firstBlock);
} else {
const lastBlock = blocks[blocks.length - 1];
if (lastBlock.content && lastBlock.content.trim() !== '') {
handleCreateBlockBelow(lastBlock.id, lastBlock.content);
} else {
focusBlockInput(lastBlock.id, 'end');
}
}
}}
onDragOver={(e) => {
e.preventDefault();
if (draggedIndex !== null) {
@@ -418,33 +477,8 @@ export const OutlinerTree: React.FC<OutlinerTreeProps> = ({
}
}}
onDrop={() => handleDropBlock(blocks.length)}
>
<button
type="button"
onClick={() => {
const lastBlock = blocks[blocks.length - 1];
if (lastBlock) {
handleCreateBlockBelow(lastBlock.id, lastBlock.content);
} else {
const firstBlock: DocumentBlock = {
id: `blk_${Date.now().toString(36)}_0`,
documentId: docId,
type: 'text',
content: '',
level: levelOffset || 0,
position: 1000,
};
pendingFocusRef.current = { blockId: firstBlock.id, cursor: 'start' };
onCreateBlock(firstBlock);
}
}}
className="btn-action inline-flex items-center gap-1.5 px-2.5 py-1 text-xs text-muted hover:text-main"
>
<Plus className="w-3.5 h-3.5" />
<span>Neuer Punkt</span>
</button>
</div>
/>
)}
</div>
);
};
});