Add interactive task lists and table insertion to markdown editor

- Enable task list checkbox toggling in preview mode with live content updates
- Add task list and table insertion buttons to InsertToolbar
- Implement smart block snippet insertion with automatic newline handling
- Add horizontal scroll wrapper for wide tables in preview
- Fix editor scroll position preservation during content updates
- Use useLayoutEffect to prevent scroll jumps when textarea auto-resizes
- Update task list styling
This commit is contained in:
drelich
2026-04-06 16:15:43 +02:00
parent aba090a8ad
commit 6bc67a3118
5 changed files with 275 additions and 33 deletions

View File

@@ -3,6 +3,8 @@ import { useEffect, useState, useRef, RefObject } from 'react';
interface InsertToolbarProps {
textareaRef: RefObject<HTMLTextAreaElement | null>;
onInsertLink: (text: string, url: string) => void;
onInsertTodoItem: () => void;
onInsertTable: () => void;
onInsertFile: () => void;
isUploading?: boolean;
}
@@ -13,7 +15,7 @@ interface LinkModalState {
url: string;
}
export function InsertToolbar({ textareaRef, onInsertLink, onInsertFile, isUploading }: InsertToolbarProps) {
export function InsertToolbar({ textareaRef, onInsertLink, onInsertTodoItem, onInsertTable, onInsertFile, isUploading }: InsertToolbarProps) {
const [position, setPosition] = useState<{ top: number; left: number } | null>(null);
const [isVisible, setIsVisible] = useState(false);
const [linkModal, setLinkModal] = useState<LinkModalState>({ isOpen: false, text: '', url: '' });
@@ -58,7 +60,7 @@ export function InsertToolbar({ textareaRef, onInsertLink, onInsertFile, isUploa
const left = textareaRect.left + paddingLeft + (currentLineText.length * charWidth) + 20;
// Keep toolbar within viewport
const toolbarWidth = 100;
const toolbarWidth = 196;
const adjustedLeft = Math.min(left, window.innerWidth - toolbarWidth - 20);
let adjustedTop = top - 16; // Center vertically with cursor line
@@ -137,6 +139,16 @@ export function InsertToolbar({ textareaRef, onInsertLink, onInsertFile, isUploa
setIsVisible(false);
};
const handleTodoClick = () => {
onInsertTodoItem();
setIsVisible(false);
};
const handleTableClick = () => {
onInsertTable();
setIsVisible(false);
};
if (!isVisible || !position) return null;
// Link Modal
@@ -217,6 +229,28 @@ export function InsertToolbar({ textareaRef, onInsertLink, onInsertFile, isUploa
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M13.828 10.172a4 4 0 00-5.656 0l-4 4a4 4 0 105.656 5.656l1.102-1.101m-.758-4.899a4 4 0 005.656 0l4-4a4 4 0 00-5.656-5.656l-1.1 1.1" />
</svg>
</button>
<button
onClick={handleTodoClick}
className="p-2 rounded hover:bg-gray-700 dark:hover:bg-gray-600 text-white transition-colors"
title="Insert To-Do Item"
>
<svg className="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M9 7h11M9 12h11M9 17h11M4 7h.01M4 12h.01M4 17h.01" />
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="m3.5 12.5 1.5 1.5 3-3" />
</svg>
</button>
<button
onClick={handleTableClick}
className="p-2 rounded hover:bg-gray-700 dark:hover:bg-gray-600 text-white transition-colors"
title="Insert Table"
>
<svg className="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M4 5h16v14H4V5Z" />
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M4 10h16M10 5v14" />
</svg>
</button>
<button
onClick={handleFileClick}