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
MindSift CI & Docker Release / Build, Test in Container & Push (push) Failing after 19s
This commit is contained in:
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>
|
||||
);
|
||||
};
|
||||
});
|
||||
Reference in new issue
Block a user