Dev #3
@@ -1069,6 +1069,84 @@ export const WorkspaceView: React.FC<WorkspaceViewProps> = ({
|
|||||||
// LOGSEQ OUTLINER CORE LOGIC
|
// LOGSEQ OUTLINER CORE LOGIC
|
||||||
// ============================================================================
|
// ============================================================================
|
||||||
|
|
||||||
|
// Zoom-in / Hoisting Calculation
|
||||||
|
const { visibleBlocks, breadcrumbBlocks, zoomedBlock } = useMemo(() => {
|
||||||
|
if (!selectedDoc) return { visibleBlocks: [], breadcrumbBlocks: [], zoomedBlock: null };
|
||||||
|
const all = selectedDoc.blocks;
|
||||||
|
|
||||||
|
if (!zoomedBlockId) {
|
||||||
|
// Normal view: filter out children of collapsed blocks
|
||||||
|
const list: { block: DocumentBlock; originalIndex: number }[] = [];
|
||||||
|
let hideUntilLevel = -1;
|
||||||
|
|
||||||
|
for (let i = 0; i < all.length; i++) {
|
||||||
|
const b = all[i];
|
||||||
|
const lvl = b.level || 0;
|
||||||
|
|
||||||
|
if (hideUntilLevel !== -1) {
|
||||||
|
if (lvl > hideUntilLevel) {
|
||||||
|
continue; // Skip collapsed child
|
||||||
|
} else {
|
||||||
|
hideUntilLevel = -1; // End of collapsed subtree
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
list.push({ block: b, originalIndex: i });
|
||||||
|
|
||||||
|
if (b.collapsed) {
|
||||||
|
hideUntilLevel = lvl;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return { visibleBlocks: list, breadcrumbBlocks: [], zoomedBlock: null };
|
||||||
|
}
|
||||||
|
|
||||||
|
// Zoomed view: find zoomed block and its descendants
|
||||||
|
const zoomIdx = all.findIndex((b) => b.id === zoomedBlockId);
|
||||||
|
if (zoomIdx === -1) {
|
||||||
|
return { visibleBlocks: [], breadcrumbBlocks: [], zoomedBlock: null };
|
||||||
|
}
|
||||||
|
|
||||||
|
const target = all[zoomIdx];
|
||||||
|
const targetLevel = target.level || 0;
|
||||||
|
|
||||||
|
// Build breadcrumbs: find parents with strictly decreasing levels
|
||||||
|
const crumbs: DocumentBlock[] = [];
|
||||||
|
let curLevel = targetLevel;
|
||||||
|
for (let i = zoomIdx - 1; i >= 0; i--) {
|
||||||
|
const b = all[i];
|
||||||
|
const lvl = b.level || 0;
|
||||||
|
if (lvl < curLevel) {
|
||||||
|
crumbs.unshift(b);
|
||||||
|
curLevel = lvl;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Descendants of zoomed block (respecting collapsed sub-branches inside the zoomed view)
|
||||||
|
const descendants: { block: DocumentBlock; originalIndex: number }[] = [];
|
||||||
|
let hideUntilLevel = -1;
|
||||||
|
for (let i = zoomIdx + 1; i < all.length; i++) {
|
||||||
|
const b = all[i];
|
||||||
|
const lvl = b.level || 0;
|
||||||
|
if (lvl <= targetLevel) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if (hideUntilLevel !== -1) {
|
||||||
|
if (lvl > hideUntilLevel) {
|
||||||
|
continue;
|
||||||
|
} else {
|
||||||
|
hideUntilLevel = -1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
descendants.push({ block: b, originalIndex: i });
|
||||||
|
if (b.collapsed) {
|
||||||
|
hideUntilLevel = lvl;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return { visibleBlocks: descendants, breadcrumbBlocks: crumbs, zoomedBlock: target };
|
||||||
|
}, [selectedDoc, zoomedBlockId]);
|
||||||
|
|
||||||
// Helper: check if a block has children (i.e. next block has greater level)
|
// Helper: check if a block has children (i.e. next block has greater level)
|
||||||
const hasChildren = (blocks: DocumentBlock[], index: number): boolean => {
|
const hasChildren = (blocks: DocumentBlock[], index: number): boolean => {
|
||||||
if (index + 1 >= blocks.length) return false;
|
if (index + 1 >= blocks.length) return false;
|
||||||
@@ -1076,6 +1154,78 @@ export const WorkspaceView: React.FC<WorkspaceViewProps> = ({
|
|||||||
return (blocks[index + 1].level || 0) > currentLevel;
|
return (blocks[index + 1].level || 0) > currentLevel;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Zoom into a bullet (or back out to parent/root) and ensure childless bullets get an editable sub-bullet
|
||||||
|
const handleZoomTo = (targetBlockId: string | null) => {
|
||||||
|
if (!selectedDoc) {
|
||||||
|
setZoomedBlockId(targetBlockId);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let workingBlocks = [...selectedDoc.blocks];
|
||||||
|
let blocksChanged = false;
|
||||||
|
|
||||||
|
// If zooming out/away from a zoomed block whose only descendant is a completely empty bullet, clean it up
|
||||||
|
if (!isReadOnly && zoomedBlock && targetBlockId !== zoomedBlock.id) {
|
||||||
|
const curZoomIdx = workingBlocks.findIndex((b) => b.id === zoomedBlock.id);
|
||||||
|
if (curZoomIdx !== -1) {
|
||||||
|
const curZoomLevel = workingBlocks[curZoomIdx].level || 0;
|
||||||
|
const descIndices: number[] = [];
|
||||||
|
for (let i = curZoomIdx + 1; i < workingBlocks.length; i++) {
|
||||||
|
if ((workingBlocks[i].level || 0) > curZoomLevel) {
|
||||||
|
descIndices.push(i);
|
||||||
|
} else {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (descIndices.length === 1) {
|
||||||
|
const onlyChild = workingBlocks[descIndices[0]];
|
||||||
|
if (
|
||||||
|
onlyChild.id !== targetBlockId &&
|
||||||
|
onlyChild.type === 'bullet' &&
|
||||||
|
(!onlyChild.content || onlyChild.content.trim() === '')
|
||||||
|
) {
|
||||||
|
workingBlocks.splice(descIndices[0], 1);
|
||||||
|
blocksChanged = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (targetBlockId && !isReadOnly) {
|
||||||
|
const targetIdx = workingBlocks.findIndex((b) => b.id === targetBlockId);
|
||||||
|
if (targetIdx !== -1 && !hasChildren(workingBlocks, targetIdx)) {
|
||||||
|
const childLevel = (workingBlocks[targetIdx].level || 0) + 1;
|
||||||
|
const newChild: DocumentBlock = {
|
||||||
|
id: `blk_${Date.now()}_${Math.random().toString(36).slice(2, 6)}`,
|
||||||
|
type: 'bullet',
|
||||||
|
content: '',
|
||||||
|
level: childLevel,
|
||||||
|
checked: false,
|
||||||
|
};
|
||||||
|
workingBlocks.splice(targetIdx + 1, 0, newChild);
|
||||||
|
persistDocument({ ...selectedDoc, blocks: workingBlocks });
|
||||||
|
setZoomedBlockId(targetBlockId);
|
||||||
|
setLastFocusedBlockId(newChild.id);
|
||||||
|
setBlockCursor(newChild.id, 0);
|
||||||
|
setTimeout(() => {
|
||||||
|
const el = blockInputRefs.current[newChild.id];
|
||||||
|
if (el) {
|
||||||
|
el.focus();
|
||||||
|
if ('setSelectionRange' in el && typeof (el as any).value === 'string') {
|
||||||
|
(el as any).setSelectionRange(0, 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, 30);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (blocksChanged) {
|
||||||
|
persistDocument({ ...selectedDoc, blocks: workingBlocks });
|
||||||
|
}
|
||||||
|
setZoomedBlockId(targetBlockId);
|
||||||
|
};
|
||||||
|
|
||||||
// Insert a new bullet immediately after index, inheriting level
|
// Insert a new bullet immediately after index, inheriting level
|
||||||
const insertNewBulletAfter = (
|
const insertNewBulletAfter = (
|
||||||
afterIndex: number,
|
afterIndex: number,
|
||||||
@@ -1127,13 +1277,14 @@ export const WorkspaceView: React.FC<WorkspaceViewProps> = ({
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// Outdent block (Shift+Tab): decrease level by 1
|
// Outdent block (Shift+Tab): decrease level by 1 (without leaving active zoomed subtree)
|
||||||
const outdentBlock = (blockId: string) => {
|
const outdentBlock = (blockId: string) => {
|
||||||
if (isReadOnly || !selectedDoc) return;
|
if (isReadOnly || !selectedDoc) return;
|
||||||
const block = selectedDoc.blocks.find((b) => b.id === blockId);
|
const block = selectedDoc.blocks.find((b) => b.id === blockId);
|
||||||
if (!block) return;
|
if (!block) return;
|
||||||
const currentLevel = block.level || 0;
|
const currentLevel = block.level || 0;
|
||||||
if (currentLevel > 0) {
|
const minLevel = zoomedBlock ? (zoomedBlock.level || 0) + 1 : 0;
|
||||||
|
if (currentLevel > minLevel) {
|
||||||
updateBlock(blockId, { level: currentLevel - 1 });
|
updateBlock(blockId, { level: currentLevel - 1 });
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@@ -1185,7 +1336,20 @@ export const WorkspaceView: React.FC<WorkspaceViewProps> = ({
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Determine fallback block to focus
|
// Determine fallback block to focus
|
||||||
const prevBlock = index > 0 ? blocks[index - 1] : nextBlocks[0];
|
let prevBlock: DocumentBlock | undefined;
|
||||||
|
if (zoomedBlock) {
|
||||||
|
const zoomIdx = blocks.findIndex((b) => b.id === zoomedBlock.id);
|
||||||
|
if (index > zoomIdx + 1) {
|
||||||
|
prevBlock = blocks[index - 1];
|
||||||
|
} else {
|
||||||
|
const nextFirstChild = nextBlocks[zoomIdx + 1];
|
||||||
|
if (nextFirstChild && (nextFirstChild.level || 0) > (zoomedBlock.level || 0)) {
|
||||||
|
prevBlock = nextFirstChild;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
prevBlock = index > 0 ? blocks[index - 1] : nextBlocks[0];
|
||||||
|
}
|
||||||
|
|
||||||
persistDocument({ ...selectedDoc, blocks: nextBlocks });
|
persistDocument({ ...selectedDoc, blocks: nextBlocks });
|
||||||
|
|
||||||
@@ -1358,10 +1522,15 @@ export const WorkspaceView: React.FC<WorkspaceViewProps> = ({
|
|||||||
const target = e.target as HTMLTextAreaElement | HTMLInputElement;
|
const target = e.target as HTMLTextAreaElement | HTMLInputElement;
|
||||||
const fullContent = block.content || '';
|
const fullContent = block.content || '';
|
||||||
if (!fullContent || fullContent === '') {
|
if (!fullContent || fullContent === '') {
|
||||||
if ((block.level || 0) > 0) {
|
const minLevel = zoomedBlock ? (zoomedBlock.level || 0) + 1 : 0;
|
||||||
|
if ((block.level || 0) > minLevel) {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
outdentBlock(block.id);
|
outdentBlock(block.id);
|
||||||
} else if (selectedDoc?.blocks && selectedDoc.blocks.length > 1) {
|
} else if (
|
||||||
|
zoomedBlock
|
||||||
|
? visibleBlocks.length > 1
|
||||||
|
: selectedDoc?.blocks && selectedDoc.blocks.length > 1
|
||||||
|
) {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
setSlashBlockId(null);
|
setSlashBlockId(null);
|
||||||
deleteBlockAndFocusPrev(index);
|
deleteBlockAndFocusPrev(index);
|
||||||
@@ -1564,6 +1733,39 @@ export const WorkspaceView: React.FC<WorkspaceViewProps> = ({
|
|||||||
|
|
||||||
const handleCanvasBottomClick = () => {
|
const handleCanvasBottomClick = () => {
|
||||||
if (isReadOnly || !selectedDoc) return;
|
if (isReadOnly || !selectedDoc) return;
|
||||||
|
|
||||||
|
if (zoomedBlock) {
|
||||||
|
const zoomIdx = selectedDoc.blocks.findIndex((b) => b.id === zoomedBlock.id);
|
||||||
|
if (zoomIdx === -1) return;
|
||||||
|
const minChildLevel = (zoomedBlock.level || 0) + 1;
|
||||||
|
|
||||||
|
let lastDescendantIndex = zoomIdx;
|
||||||
|
for (let i = zoomIdx + 1; i < selectedDoc.blocks.length; i++) {
|
||||||
|
if ((selectedDoc.blocks[i].level || 0) >= minChildLevel) {
|
||||||
|
lastDescendantIndex = i;
|
||||||
|
} else {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (lastDescendantIndex === zoomIdx) {
|
||||||
|
insertNewBulletAfter(zoomIdx, minChildLevel);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const lastVisible = visibleBlocks[visibleBlocks.length - 1];
|
||||||
|
if (
|
||||||
|
lastVisible &&
|
||||||
|
lastVisible.block.type !== 'divider' &&
|
||||||
|
(!lastVisible.block.content || lastVisible.block.content.trim() === '')
|
||||||
|
) {
|
||||||
|
focusBlock(lastVisible.block.id);
|
||||||
|
} else {
|
||||||
|
insertNewBulletAfter(lastDescendantIndex, minChildLevel);
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (selectedDoc.blocks.length === 0) {
|
if (selectedDoc.blocks.length === 0) {
|
||||||
insertNewBulletAfter(-1, 0);
|
insertNewBulletAfter(-1, 0);
|
||||||
return;
|
return;
|
||||||
@@ -1739,73 +1941,6 @@ export const WorkspaceView: React.FC<WorkspaceViewProps> = ({
|
|||||||
setDraggingBlockId(null);
|
setDraggingBlockId(null);
|
||||||
};
|
};
|
||||||
|
|
||||||
// Zoom-in / Hoisting Calculation
|
|
||||||
const { visibleBlocks, breadcrumbBlocks, zoomedBlock } = useMemo(() => {
|
|
||||||
if (!selectedDoc) return { visibleBlocks: [], breadcrumbBlocks: [], zoomedBlock: null };
|
|
||||||
const all = selectedDoc.blocks;
|
|
||||||
|
|
||||||
if (!zoomedBlockId) {
|
|
||||||
// Normal view: filter out children of collapsed blocks
|
|
||||||
const list: { block: DocumentBlock; originalIndex: number }[] = [];
|
|
||||||
let hideUntilLevel = -1;
|
|
||||||
|
|
||||||
for (let i = 0; i < all.length; i++) {
|
|
||||||
const b = all[i];
|
|
||||||
const lvl = b.level || 0;
|
|
||||||
|
|
||||||
if (hideUntilLevel !== -1) {
|
|
||||||
if (lvl > hideUntilLevel) {
|
|
||||||
continue; // Skip collapsed child
|
|
||||||
} else {
|
|
||||||
hideUntilLevel = -1; // End of collapsed subtree
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
list.push({ block: b, originalIndex: i });
|
|
||||||
|
|
||||||
if (b.collapsed) {
|
|
||||||
hideUntilLevel = lvl;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return { visibleBlocks: list, breadcrumbBlocks: [], zoomedBlock: null };
|
|
||||||
}
|
|
||||||
|
|
||||||
// Zoomed view: find zoomed block and its descendants
|
|
||||||
const zoomIdx = all.findIndex((b) => b.id === zoomedBlockId);
|
|
||||||
if (zoomIdx === -1) {
|
|
||||||
return { visibleBlocks: [], breadcrumbBlocks: [], zoomedBlock: null };
|
|
||||||
}
|
|
||||||
|
|
||||||
const target = all[zoomIdx];
|
|
||||||
const targetLevel = target.level || 0;
|
|
||||||
|
|
||||||
// Build breadcrumbs: find parents with strictly decreasing levels
|
|
||||||
const crumbs: DocumentBlock[] = [];
|
|
||||||
let curLevel = targetLevel;
|
|
||||||
for (let i = zoomIdx - 1; i >= 0; i--) {
|
|
||||||
const b = all[i];
|
|
||||||
const lvl = b.level || 0;
|
|
||||||
if (lvl < curLevel) {
|
|
||||||
crumbs.unshift(b);
|
|
||||||
curLevel = lvl;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Descendants of zoomed block
|
|
||||||
const descendants: { block: DocumentBlock; originalIndex: number }[] = [];
|
|
||||||
for (let i = zoomIdx + 1; i < all.length; i++) {
|
|
||||||
const b = all[i];
|
|
||||||
if ((b.level || 0) > targetLevel) {
|
|
||||||
descendants.push({ block: b, originalIndex: i });
|
|
||||||
} else {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return { visibleBlocks: descendants, breadcrumbBlocks: crumbs, zoomedBlock: target };
|
|
||||||
}, [selectedDoc, zoomedBlockId]);
|
|
||||||
|
|
||||||
// Export full document as portable Logseq-indented Markdown
|
// Export full document as portable Logseq-indented Markdown
|
||||||
const handleExportMarkdown = () => {
|
const handleExportMarkdown = () => {
|
||||||
if (!selectedDoc) return;
|
if (!selectedDoc) return;
|
||||||
@@ -3112,7 +3247,7 @@ export const WorkspaceView: React.FC<WorkspaceViewProps> = ({
|
|||||||
<div className="flex flex-wrap items-center gap-1.5 min-w-0">
|
<div className="flex flex-wrap items-center gap-1.5 min-w-0">
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
onClick={() => setZoomedBlockId(null)}
|
onClick={() => handleZoomTo(null)}
|
||||||
className="text-muted hover:text-main flex items-center gap-1 font-medium transition-colors"
|
className="text-muted hover:text-main flex items-center gap-1 font-medium transition-colors"
|
||||||
>
|
>
|
||||||
<span>{selectedDoc.icon || '📄'}</span>
|
<span>{selectedDoc.icon || '📄'}</span>
|
||||||
@@ -3124,7 +3259,7 @@ export const WorkspaceView: React.FC<WorkspaceViewProps> = ({
|
|||||||
<ChevronRight className="w-3 h-3 text-faint flex-shrink-0" />
|
<ChevronRight className="w-3 h-3 text-faint flex-shrink-0" />
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
onClick={() => setZoomedBlockId(b.id)}
|
onClick={() => handleZoomTo(b.id)}
|
||||||
className="text-muted hover:text-main truncate max-w-[140px] font-medium transition-colors"
|
className="text-muted hover:text-main truncate max-w-[140px] font-medium transition-colors"
|
||||||
>
|
>
|
||||||
{b.content || 'Bullet'}
|
{b.content || 'Bullet'}
|
||||||
@@ -3210,18 +3345,36 @@ export const WorkspaceView: React.FC<WorkspaceViewProps> = ({
|
|||||||
<span className="w-2.5 h-2.5 rounded-pill bg-accent flex-shrink-0" />
|
<span className="w-2.5 h-2.5 rounded-pill bg-accent flex-shrink-0" />
|
||||||
<input
|
<input
|
||||||
type="text"
|
type="text"
|
||||||
|
readOnly={isReadOnly}
|
||||||
value={zoomedBlock.content}
|
value={zoomedBlock.content}
|
||||||
onChange={(e) => updateBlock(zoomedBlock.id, { content: e.target.value })}
|
onFocus={handleBlockFocus}
|
||||||
|
onBlur={handleBlockBlur}
|
||||||
|
onChange={(e) => {
|
||||||
|
if (!isReadOnly) {
|
||||||
|
ensureLock();
|
||||||
|
updateBlock(zoomedBlock.id, { content: e.target.value });
|
||||||
|
}
|
||||||
|
}}
|
||||||
onKeyDown={(e) => {
|
onKeyDown={(e) => {
|
||||||
if (e.key === 'Enter') {
|
if (e.key === 'Enter' && !isReadOnly) {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
|
if (
|
||||||
|
visibleBlocks.length > 0 &&
|
||||||
|
(!visibleBlocks[0].block.content || visibleBlocks[0].block.content.trim() === '')
|
||||||
|
) {
|
||||||
|
focusBlock(visibleBlocks[0].block.id);
|
||||||
|
return;
|
||||||
|
}
|
||||||
const zoomIdx = selectedDoc.blocks.findIndex((b) => b.id === zoomedBlock.id);
|
const zoomIdx = selectedDoc.blocks.findIndex((b) => b.id === zoomedBlock.id);
|
||||||
if (zoomIdx !== -1) {
|
if (zoomIdx !== -1) {
|
||||||
insertNewBulletAfter(zoomIdx, (zoomedBlock.level || 0) + 1);
|
insertNewBulletAfter(zoomIdx, (zoomedBlock.level || 0) + 1);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
className="w-full bg-transparent text-xl font-bold text-main focus:outline-none placeholder:text-faint"
|
placeholder="Aufzählungspunkt…"
|
||||||
|
className={`w-full bg-transparent text-xl font-bold text-main focus:outline-none placeholder:text-faint ${
|
||||||
|
isReadOnly ? 'cursor-default' : ''
|
||||||
|
}`}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
@@ -3237,6 +3390,25 @@ export const WorkspaceView: React.FC<WorkspaceViewProps> = ({
|
|||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
|
{zoomedBlock && visibleBlocks.length === 0 && !isReadOnly && (
|
||||||
|
<div
|
||||||
|
onClick={(e) => {
|
||||||
|
e.stopPropagation();
|
||||||
|
handleCanvasBottomClick();
|
||||||
|
}}
|
||||||
|
className="group flex items-center gap-1 py-1 cursor-text text-faint hover:text-muted transition-colors"
|
||||||
|
>
|
||||||
|
<div className="w-3 flex-shrink-0" />
|
||||||
|
<div className="w-4 flex-shrink-0" />
|
||||||
|
<div className="w-4 h-4 rounded-pill flex items-center justify-center flex-shrink-0">
|
||||||
|
<span className="w-1.5 h-1.5 rounded-pill bg-muted group-hover:bg-accent transition-colors" />
|
||||||
|
</div>
|
||||||
|
<span className="text-sm pl-0.5 select-none">
|
||||||
|
Klicken, um einen Unterpunkt zu erstellen…
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
{visibleBlocks.map(({ block, originalIndex }) => {
|
{visibleBlocks.map(({ block, originalIndex }) => {
|
||||||
const blockLevel = Math.max(0, (block.level || 0) - (zoomedBlock ? (zoomedBlock.level || 0) + 1 : 0));
|
const blockLevel = Math.max(0, (block.level || 0) - (zoomedBlock ? (zoomedBlock.level || 0) + 1 : 0));
|
||||||
const isChildrenPresent = hasChildren(selectedDoc.blocks, originalIndex);
|
const isChildrenPresent = hasChildren(selectedDoc.blocks, originalIndex);
|
||||||
@@ -3279,7 +3451,7 @@ export const WorkspaceView: React.FC<WorkspaceViewProps> = ({
|
|||||||
onDragStart={(e) => !isReadOnly && handleDragStart(e, block.id)}
|
onDragStart={(e) => !isReadOnly && handleDragStart(e, block.id)}
|
||||||
onDragOver={(e) => e.preventDefault()}
|
onDragOver={(e) => e.preventDefault()}
|
||||||
onDrop={(e) => !isReadOnly && handleDropOnBlock(e, block.id)}
|
onDrop={(e) => !isReadOnly && handleDropOnBlock(e, block.id)}
|
||||||
onClick={() => setZoomedBlockId(block.id)}
|
onClick={() => handleZoomTo(block.id)}
|
||||||
className={`w-4 h-4 mt-1 rounded-pill flex items-center justify-center transition-colors flex-shrink-0 ${
|
className={`w-4 h-4 mt-1 rounded-pill flex items-center justify-center transition-colors flex-shrink-0 ${
|
||||||
isReadOnly
|
isReadOnly
|
||||||
? 'cursor-pointer text-muted'
|
? 'cursor-pointer text-muted'
|
||||||
|
|||||||
Reference in new issue
Block a user