feat(workspace): add focus mode and hoisting zoom-in for outliner blocks
MindSift CI & Docker Release / Build, Test in Container & Push (push) Successful in 58s
MindSift CI & Docker Release / Build, Test in Container & Push (push) Successful in 58s
- Click bullet point (•) or hover zoom icon to zoom into any block branch - Render hoisted block as prominent heading in focus mode with direct editing - Display clickable breadcrumb path leading back to ancestors or root document - Render isolated subtree with visual level offset and bound indentation
This commit is contained in:
1 parent
95977ba15a
commit
177e24fdc8
3 files changed
+346
-78
No files matched your search
@@ -15,6 +15,7 @@ import {
|
||||
MessageSquare,
|
||||
Hash,
|
||||
ExternalLink,
|
||||
Maximize2,
|
||||
} from 'lucide-react';
|
||||
import { DocumentBlock, BlockType } from '../../lib/types';
|
||||
import { RemoteCursor } from '../../lib/useDocumentSync';
|
||||
@@ -46,6 +47,8 @@ export interface OutlinerBlockProps {
|
||||
canEdit: boolean;
|
||||
activeRemoteCursors?: RemoteCursor[];
|
||||
sendCursorMoved?: (blockId: string | null, selectionStart?: number, selectionEnd?: number) => void;
|
||||
levelOffset?: number;
|
||||
onZoomBlock?: (blockId: string) => void;
|
||||
onFocusBlock: (blockId: string) => void;
|
||||
onUpdateBlock: (updated: DocumentBlock, immediate?: boolean) => void;
|
||||
onCreateBlockBelow: (afterBlockId: string, currentContent: string) => void;
|
||||
@@ -69,6 +72,8 @@ export const OutlinerBlock: React.FC<OutlinerBlockProps> = ({
|
||||
canEdit,
|
||||
activeRemoteCursors = [],
|
||||
sendCursorMoved,
|
||||
levelOffset = 0,
|
||||
onZoomBlock,
|
||||
onFocusBlock,
|
||||
onUpdateBlock,
|
||||
onCreateBlockBelow,
|
||||
@@ -334,6 +339,7 @@ export const OutlinerBlock: React.FC<OutlinerBlockProps> = ({
|
||||
};
|
||||
|
||||
const level = Math.max(0, block.level || 0);
|
||||
const visualLevel = Math.max(0, level - (levelOffset || 0));
|
||||
|
||||
// Parse rich tokens (WikiLinks [[Page]] and #Tags)
|
||||
const tokens = useMemo(() => {
|
||||
@@ -355,7 +361,7 @@ export const OutlinerBlock: React.FC<OutlinerBlockProps> = ({
|
||||
: 'hover:bg-panel'
|
||||
}`}
|
||||
style={{
|
||||
paddingLeft: `${level * 22 + 4}px`,
|
||||
paddingLeft: `${visualLevel * 22 + 4}px`,
|
||||
...(primaryRemoteCursor
|
||||
? { borderLeft: `2px solid ${primaryRemoteCursor.userColor}` }
|
||||
: {}),
|
||||
@@ -379,8 +385,8 @@ export const OutlinerBlock: React.FC<OutlinerBlockProps> = ({
|
||||
)}
|
||||
|
||||
{/* Indentation Guide Line */}
|
||||
{level > 0 &&
|
||||
Array.from({ length: level }).map((_, lvlIdx) => (
|
||||
{visualLevel > 0 &&
|
||||
Array.from({ length: visualLevel }).map((_, lvlIdx) => (
|
||||
<div
|
||||
key={lvlIdx}
|
||||
className="pointer-events-none absolute top-0 bottom-0 w-px border-l border-border"
|
||||
@@ -440,15 +446,9 @@ export const OutlinerBlock: React.FC<OutlinerBlockProps> = ({
|
||||
) : (
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => {
|
||||
if (canEdit) {
|
||||
flushDebounce(localContent, {
|
||||
type: block.type === 'todo' ? 'bullet' : 'todo',
|
||||
});
|
||||
}
|
||||
}}
|
||||
className="w-4 h-4 flex items-center justify-center rounded-btn hover:bg-panel-hover"
|
||||
title="Klick: Zu To-Do / Bullet umschalten"
|
||||
onClick={() => onZoomBlock?.(block.id)}
|
||||
className="w-4 h-4 flex items-center justify-center rounded-btn hover:bg-panel-hover group/bullet transition-transform"
|
||||
title="Klick: In diesen Ast hineinzoomen (Fokus-Modus)"
|
||||
>
|
||||
<span
|
||||
className={`w-1.5 h-1.5 rounded-full transition-all ${
|
||||
@@ -456,7 +456,7 @@ export const OutlinerBlock: React.FC<OutlinerBlockProps> = ({
|
||||
? 'bg-accent ring-2 ring-accent/30'
|
||||
: isFocused
|
||||
? 'bg-accent scale-125'
|
||||
: 'bg-faint group-hover:bg-accent'
|
||||
: 'bg-faint group-hover/bullet:bg-accent group-hover/bullet:scale-125'
|
||||
}`}
|
||||
/>
|
||||
</button>
|
||||
@@ -670,6 +670,16 @@ export const OutlinerBlock: React.FC<OutlinerBlockProps> = ({
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Zoom / Focus button on hover */}
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => onZoomBlock?.(block.id)}
|
||||
className="opacity-0 group-hover:opacity-100 focus:opacity-100 p-0.5 text-faint hover:text-accent rounded-btn transition-opacity shrink-0"
|
||||
title="In diesen Ast hineinzoomen (Fokus-Modus)"
|
||||
>
|
||||
<Maximize2 className="w-3.5 h-3.5" />
|
||||
</button>
|
||||
|
||||
{/* Delete button on hover */}
|
||||
{canEdit && (
|
||||
<button
|
||||
|
||||
Reference in new issue
Block a user