diff --git a/frontend/src/features/workspace/WorkspaceView.tsx b/frontend/src/features/workspace/WorkspaceView.tsx index 0cf0570..efd4747 100644 --- a/frontend/src/features/workspace/WorkspaceView.tsx +++ b/frontend/src/features/workspace/WorkspaceView.tsx @@ -996,12 +996,17 @@ export const WorkspaceView: React.FC = ({ }; // Insert a new bullet immediately after index, inheriting level - const insertNewBulletAfter = (afterIndex: number, level: number, type: BlockType = 'bullet') => { + const insertNewBulletAfter = ( + afterIndex: number, + level: number, + type: BlockType = 'bullet', + initialContent: string = '' + ) => { if (isReadOnly || !selectedDoc) return; const newBlock: DocumentBlock = { id: `blk_${Date.now()}_${Math.random().toString(36).slice(2, 6)}`, type, - content: '', + content: initialContent, level, checked: false, }; @@ -1016,6 +1021,9 @@ export const WorkspaceView: React.FC = ({ const el = blockInputRefs.current[newBlock.id]; if (el) { el.focus(); + if ('setSelectionRange' in el && typeof (el as any).value === 'string') { + (el as any).setSelectionRange(0, 0); + } } }, 20); }; @@ -1179,7 +1187,20 @@ export const WorkspaceView: React.FC = ({ if (e.key === 'Enter' && !e.shiftKey) { e.preventDefault(); setSlashBlockId(null); - insertNewBulletAfter(index, block.level || 0); + const target = e.target as HTMLTextAreaElement | HTMLInputElement; + let afterContent = ''; + if (target && typeof target.selectionStart === 'number') { + const cursor = target.selectionStart; + const fullContent = block.content || ''; + const before = fullContent.slice(0, cursor); + const after = fullContent.slice(cursor); + if (after.length > 0) { + updateBlock(block.id, { content: before }); + afterContent = after; + } + } + insertNewBulletAfter(index, block.level || 0, block.type === 'todo' ? 'todo' : 'bullet', afterContent); + return; } else if (e.key === 'Tab') { e.preventDefault(); if (e.shiftKey) { @@ -1202,6 +1223,34 @@ export const WorkspaceView: React.FC = ({ const handleContentChange = (block: DocumentBlock, newContent: string) => { if (isReadOnly) return; ensureLock(); + + // Markdown shortcut: typing '---' creates a divider and inserts a new bullet below + if (newContent.trim() === '---') { + const blkIdx = selectedDoc ? selectedDoc.blocks.findIndex((b) => b.id === block.id) : -1; + if (selectedDoc && blkIdx !== -1) { + const curLevel = block.level || 0; + const newBlockId = `blk_${Date.now()}_${Math.random().toString(36).slice(2, 6)}`; + const newBullet: DocumentBlock = { + id: newBlockId, + type: 'bullet', + content: '', + level: curLevel, + checked: false, + }; + const nextBlocks = [...selectedDoc.blocks]; + nextBlocks[blkIdx] = { + ...block, + type: 'divider', + content: '', + }; + nextBlocks.splice(blkIdx + 1, 0, newBullet); + persistDocument({ ...selectedDoc, blocks: nextBlocks }); + setSlashBlockId(null); + focusBlock(newBlockId); + return; + } + } + updateBlock(block.id, { content: newContent }); // Check if user just typed '/' @@ -1270,6 +1319,21 @@ export const WorkspaceView: React.FC = ({ }, 20); }; + const handleCanvasBottomClick = () => { + if (isReadOnly || !selectedDoc) return; + if (selectedDoc.blocks.length === 0) { + insertNewBulletAfter(-1, 0); + return; + } + const lastIndex = selectedDoc.blocks.length - 1; + const lastBlock = selectedDoc.blocks[lastIndex]; + if (lastBlock && lastBlock.type !== 'divider' && (!lastBlock.content || lastBlock.content.trim() === '')) { + focusBlock(lastBlock.id); + } else { + insertNewBulletAfter(lastIndex, 0); + } + }; + const applySlashCommand = (blockId: string, cmd: SlashCommandItem) => { if (isReadOnly || !selectedDoc) return; const block = selectedDoc.blocks.find((b) => b.id === blockId); @@ -1290,6 +1354,29 @@ export const WorkspaceView: React.FC = ({ } if (cmd.type) { + if (cmd.type === 'divider') { + const blkIdx = selectedDoc.blocks.findIndex((b) => b.id === blockId); + const curLevel = block.level || 0; + const newBlockId = `blk_${Date.now()}_${Math.random().toString(36).slice(2, 6)}`; + const newBullet: DocumentBlock = { + id: newBlockId, + type: 'bullet', + content: '', + level: curLevel, + checked: false, + }; + const nextBlocks = [...selectedDoc.blocks]; + nextBlocks[blkIdx] = { + ...block, + type: 'divider', + content: '', + }; + nextBlocks.splice(blkIdx + 1, 0, newBullet); + persistDocument({ ...selectedDoc, blocks: nextBlocks }); + focusBlock(newBlockId); + return; + } + updateBlock(blockId, { type: cmd.type, content: cleanContent, checked: cmd.type === 'todo' ? false : undefined }); focusBlock(blockId); } @@ -2266,7 +2353,14 @@ export const WorkspaceView: React.FC = ({ {/* Right Logseq Outliner Editor */}
{selectedDoc ? ( -
+
{ + if (e.target === e.currentTarget) { + handleCanvasBottomClick(); + } + }} + > {/* Document Header & Metadata */}
@@ -2519,15 +2613,10 @@ export const WorkspaceView: React.FC = ({ {/* LOGSEQ HIERARCHICAL BULLET LIST CANVAS */} {/* =========================================================== */}
{ if (e.target === e.currentTarget) { - if (selectedDoc.blocks.length === 0) { - insertNewBulletAfter(-1, 0); - } else { - const lastBlock = selectedDoc.blocks[selectedDoc.blocks.length - 1]; - blockInputRefs.current[lastBlock.id]?.focus(); - } + handleCanvasBottomClick(); } }} > @@ -2822,8 +2911,34 @@ export const WorkspaceView: React.FC = ({ {/* TYPE: DIVIDER */} {block.type === 'divider' && ( -
-
+
{ + if (!isReadOnly && e.key === 'Enter') { + e.preventDefault(); + insertNewBulletAfter(originalIndex, block.level || 0); + } else if (!isReadOnly && (e.key === 'Backspace' || e.key === 'Delete')) { + e.preventDefault(); + deleteBlockAndFocusPrev(originalIndex); + } + }} + onClick={() => { + if (!isReadOnly) { + insertNewBulletAfter(originalIndex, block.level || 0); + } + }} + className="py-2.5 my-1 group/divider relative cursor-pointer focus:outline-none" + title={isReadOnly ? undefined : "Klicken oder Enter drücken, um darunter einen neuen Aufzählungspunkt einzufügen"} + > +
+ {!isReadOnly && ( +
+ + + Punkt einfügen + +
+ )}
)} @@ -2899,6 +3014,17 @@ export const WorkspaceView: React.FC = ({
); })} + + {/* Explicit bottom clickable area */} + {!isReadOnly && ( +
+ + Neuen Punkt hinzufügen... +
+ )}
) : (