mirror of
https://github.com/rowboatlabs/rowboat.git
synced 2026-05-16 18:25:17 +02:00
fix: cmd+z behaviour on notes
This commit is contained in:
parent
a3e74931bf
commit
72534052e0
2 changed files with 87 additions and 2 deletions
|
|
@ -452,6 +452,7 @@ function ContentHeader({
|
|||
|
||||
function App() {
|
||||
type ShortcutPane = 'left' | 'right'
|
||||
type MarkdownHistoryHandlers = { undo: () => boolean; redo: () => boolean }
|
||||
|
||||
// File browser state (for Knowledge section)
|
||||
const [selectedPath, setSelectedPath] = useState<string | null>(null)
|
||||
|
|
@ -596,6 +597,8 @@ function App() {
|
|||
// File tab state
|
||||
const [fileTabs, setFileTabs] = useState<FileTab[]>([])
|
||||
const [activeFileTabId, setActiveFileTabId] = useState<string | null>(null)
|
||||
const [editorSessionByTabId, setEditorSessionByTabId] = useState<Record<string, number>>({})
|
||||
const fileHistoryHandlersRef = useRef<Map<string, MarkdownHistoryHandlers>>(new Map())
|
||||
const fileTabIdCounterRef = useRef(0)
|
||||
const newFileTabId = () => `file-tab-${++fileTabIdCounterRef.current}`
|
||||
|
||||
|
|
@ -2036,6 +2039,13 @@ function App() {
|
|||
}
|
||||
return next
|
||||
})
|
||||
setEditorSessionByTabId((prev) => {
|
||||
if (!(tabId in prev)) return prev
|
||||
const next = { ...prev }
|
||||
delete next[tabId]
|
||||
return next
|
||||
})
|
||||
fileHistoryHandlersRef.current.delete(tabId)
|
||||
}, [activeFileTabId, fileTabs, removeEditorCacheForPath])
|
||||
|
||||
const handleNewChatTab = useCallback(() => {
|
||||
|
|
@ -2136,6 +2146,11 @@ function App() {
|
|||
setFileTabs((prev) => prev.map((tab) => (
|
||||
tab.id === activeFileTabId ? { ...tab, path } : tab
|
||||
)))
|
||||
// Rebinds this tab to a different note path: reset editor session to clear undo history.
|
||||
setEditorSessionByTabId((prev) => ({
|
||||
...prev,
|
||||
[activeFileTabId]: (prev[activeFileTabId] ?? 0) + 1,
|
||||
}))
|
||||
return
|
||||
}
|
||||
}
|
||||
|
|
@ -2352,6 +2367,46 @@ function App() {
|
|||
return () => document.removeEventListener('keydown', handleKeyDown)
|
||||
}, [])
|
||||
|
||||
// Route undo/redo to the active markdown tab only (prevents cross-tab browser undo behavior).
|
||||
useEffect(() => {
|
||||
const handleHistoryKeyDown = (e: KeyboardEvent) => {
|
||||
const mod = e.metaKey || e.ctrlKey
|
||||
if (!mod || e.altKey) return
|
||||
|
||||
const key = e.key.toLowerCase()
|
||||
const wantsUndo = key === 'z' && !e.shiftKey
|
||||
const wantsRedo = (key === 'z' && e.shiftKey) || (!isMac && key === 'y')
|
||||
if (!wantsUndo && !wantsRedo) return
|
||||
|
||||
if (!selectedPath || !selectedPath.endsWith('.md') || !activeFileTabId) return
|
||||
|
||||
const target = e.target as EventTarget | null
|
||||
if (target instanceof HTMLElement) {
|
||||
const inTipTapEditor = Boolean(target.closest('.tiptap-editor'))
|
||||
const inOtherTextInput = (
|
||||
target instanceof HTMLInputElement
|
||||
|| target instanceof HTMLTextAreaElement
|
||||
|| target.isContentEditable
|
||||
) && !inTipTapEditor
|
||||
if (inOtherTextInput) return
|
||||
}
|
||||
|
||||
const handlers = fileHistoryHandlersRef.current.get(activeFileTabId)
|
||||
if (!handlers) return
|
||||
|
||||
e.preventDefault()
|
||||
e.stopPropagation()
|
||||
if (wantsUndo) {
|
||||
handlers.undo()
|
||||
} else {
|
||||
handlers.redo()
|
||||
}
|
||||
}
|
||||
|
||||
document.addEventListener('keydown', handleHistoryKeyDown, true)
|
||||
return () => document.removeEventListener('keydown', handleHistoryKeyDown, true)
|
||||
}, [activeFileTabId, isMac, selectedPath])
|
||||
|
||||
// Keyboard shortcuts for tab management
|
||||
useEffect(() => {
|
||||
const handleTabKeyDown = (e: KeyboardEvent) => {
|
||||
|
|
@ -3136,6 +3191,14 @@ function App() {
|
|||
placeholder="Start writing..."
|
||||
wikiLinks={wikiLinkConfig}
|
||||
onImageUpload={handleImageUpload}
|
||||
editorSessionKey={editorSessionByTabId[tab.id] ?? 0}
|
||||
onHistoryHandlersChange={(handlers) => {
|
||||
if (handlers) {
|
||||
fileHistoryHandlersRef.current.set(tab.id, handlers)
|
||||
} else {
|
||||
fileHistoryHandlersRef.current.delete(tab.id)
|
||||
}
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
|
|
|
|||
|
|
@ -195,6 +195,8 @@ interface MarkdownEditorProps {
|
|||
placeholder?: string
|
||||
wikiLinks?: WikiLinkConfig
|
||||
onImageUpload?: (file: File) => Promise<string | null>
|
||||
editorSessionKey?: number
|
||||
onHistoryHandlersChange?: (handlers: { undo: () => boolean; redo: () => boolean } | null) => void
|
||||
}
|
||||
|
||||
type WikiLinkMatch = {
|
||||
|
|
@ -278,6 +280,8 @@ export function MarkdownEditor({
|
|||
placeholder = 'Start writing...',
|
||||
wikiLinks,
|
||||
onImageUpload,
|
||||
editorSessionKey = 0,
|
||||
onHistoryHandlersChange,
|
||||
}: MarkdownEditorProps) {
|
||||
const isInternalUpdate = useRef(false)
|
||||
const wrapperRef = useRef<HTMLDivElement>(null)
|
||||
|
|
@ -400,7 +404,7 @@ export function MarkdownEditor({
|
|||
return false
|
||||
},
|
||||
},
|
||||
})
|
||||
}, [editorSessionKey])
|
||||
|
||||
const orderedFiles = useMemo(() => {
|
||||
if (!wikiLinks) return []
|
||||
|
|
@ -489,12 +493,30 @@ export function MarkdownEditor({
|
|||
isInternalUpdate.current = true
|
||||
// Pre-process to preserve blank lines
|
||||
const preprocessed = preprocessMarkdown(content)
|
||||
editor.commands.setContent(preprocessed)
|
||||
// Treat tab-open content as baseline: do not add hydration to undo history.
|
||||
editor.chain().setMeta('addToHistory', false).setContent(preprocessed).run()
|
||||
isInternalUpdate.current = false
|
||||
}
|
||||
}
|
||||
}, [editor, content])
|
||||
|
||||
useEffect(() => {
|
||||
if (!onHistoryHandlersChange) return
|
||||
if (!editor) {
|
||||
onHistoryHandlersChange(null)
|
||||
return
|
||||
}
|
||||
|
||||
onHistoryHandlersChange({
|
||||
undo: () => editor.chain().focus().undo().run(),
|
||||
redo: () => editor.chain().focus().redo().run(),
|
||||
})
|
||||
|
||||
return () => {
|
||||
onHistoryHandlersChange(null)
|
||||
}
|
||||
}, [editor, onHistoryHandlersChange])
|
||||
|
||||
// Force re-render decorations when selection highlight changes
|
||||
useEffect(() => {
|
||||
if (editor) {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue