fix: pass tab content via URL params instead of IPC

- Pass content, unsavedContent, isEditing via URL query params
- PopoutPage reads directly from URL params on mount
- Eliminates IPC race condition entirely
- Direct store update with loaded:true to prevent auto-reload
This commit is contained in:
2026-03-22 00:09:07 +08:00
parent 2d87c267cf
commit 3b1f99951e
2 changed files with 59 additions and 30 deletions

View File

@@ -122,21 +122,12 @@ export const NoteBrowser = () => {
const tabState = tabs.get(file.path) const tabState = tabs.get(file.path)
if (!tabState) return if (!tabState) return
const tabData = { const content = tabState.content || ''
file: file, const unsavedContent = tabState.unsavedContent || content
content: tabState.content, const isEditing = tabState.isEditing
unsavedContent: tabState.unsavedContent,
isEditing: tabState.isEditing,
loading: tabState.loading,
loaded: tabState.loaded,
}
const route = `/popout?path=${encodeURIComponent(file.path)}&name=${encodeURIComponent(file.name)}` const route = `/popout?path=${encodeURIComponent(file.path)}&name=${encodeURIComponent(file.name)}&content=${encodeURIComponent(content)}&unsaved=${encodeURIComponent(unsavedContent)}&editing=${isEditing}`
const result = await window.electronAPI?.createWindow({ route, title: file.name }) await window.electronAPI?.createWindow({ route, title: file.name })
if (result?.success && result.windowId) {
await window.electronAPI?.transferTabData(result.windowId!, tabData)
}
closeFile(file) closeFile(file)
}, [tabs, closeFile]) }, [tabs, closeFile])

View File

@@ -6,39 +6,85 @@ import { MarkdownTabPage } from '@/components/tabs/MarkdownTabPage'
import { RemoteTabPage } from '@/modules/remote/RemoteTabPage' import { RemoteTabPage } from '@/modules/remote/RemoteTabPage'
import { FileTransferPage } from '@/modules/remote/components/file-transfer/FileTransferPage' import { FileTransferPage } from '@/modules/remote/components/file-transfer/FileTransferPage'
import { Minus, Square, X, Maximize2 } from 'lucide-react' import { Minus, Square, X, Maximize2 } from 'lucide-react'
import { usePopOutTab } from '@/hooks/domain/usePopOutTab' import { useTabStore } from '@/stores'
export const PopoutPage = () => { export const PopoutPage = () => {
const [searchParams] = useSearchParams() const [searchParams] = useSearchParams()
const [file, setFile] = useState<FileItemDTO | null>(null) const [file, setFile] = useState<FileItemDTO | null>(null)
const [error, setError] = useState<string | null>(null) const [content, setContent] = useState<string>('')
const [unsavedContent, setUnsavedContent] = useState<string>('')
const [isEditing, setIsEditing] = useState(false)
const [isMaximized, setIsMaximized] = useState(false) const [isMaximized, setIsMaximized] = useState(false)
const [ready, setReady] = useState(false)
usePopOutTab() const { selectFile } = useTabStore()
useEffect(() => { useEffect(() => {
const path = searchParams.get('path') const path = searchParams.get('path')
const name = searchParams.get('name') const name = searchParams.get('name')
const contentParam = searchParams.get('content')
const unsavedParam = searchParams.get('unsaved')
const editingParam = searchParams.get('editing')
if (!path || !name) { if (!path || !name) {
setError('Missing path or name parameter')
return return
} }
const decodedPath = decodeURIComponent(path)
const decodedName = decodeURIComponent(name)
const decodedContent = contentParam ? decodeURIComponent(contentParam) : ''
const decodedUnsaved = unsavedParam ? decodeURIComponent(unsavedParam) : decodedContent
const decodedEditing = editingParam === 'true'
setFile({ setFile({
name: decodeURIComponent(name), name: decodedName,
path: decodeURIComponent(path), path: decodedPath,
type: 'file', type: 'file',
size: 0, size: 0,
modified: new Date().toISOString(), modified: new Date().toISOString(),
}) })
setContent(decodedContent)
setUnsavedContent(decodedUnsaved)
setIsEditing(decodedEditing)
useTabStore.setState((state) => {
const newTabs = new Map(state.tabs)
newTabs.set(decodedPath, {
file: {
name: decodedName,
path: decodedPath,
type: 'file' as const,
size: 0,
modified: new Date().toISOString(),
},
content: decodedContent,
unsavedContent: decodedUnsaved,
isEditing: decodedEditing,
loading: false,
loaded: true,
})
return {
tabs: newTabs,
activeTabId: decodedPath,
}
})
selectFile({
name: decodedName,
path: decodedPath,
type: 'file',
size: 0,
modified: new Date().toISOString(),
})
setReady(true)
window.electronAPI?.windowIsMaximized().then((result) => { window.electronAPI?.windowIsMaximized().then((result) => {
if (result.success) { if (result.success) {
setIsMaximized(result.isMaximized) setIsMaximized(result.isMaximized)
} }
}) })
}, [searchParams]) }, [searchParams, selectFile])
const handleMinimize = () => { const handleMinimize = () => {
window.electronAPI?.windowMinimize() window.electronAPI?.windowMinimize()
@@ -55,15 +101,7 @@ export const PopoutPage = () => {
window.electronAPI?.windowClose() window.electronAPI?.windowClose()
} }
if (error) { if (!file || !ready) {
return (
<div className="flex items-center justify-center h-screen bg-white dark:bg-gray-900">
<div className="text-red-500">{error}</div>
</div>
)
}
if (!file) {
return ( return (
<div className="flex items-center justify-center h-screen bg-white dark:bg-gray-900"> <div className="flex items-center justify-center h-screen bg-white dark:bg-gray-900">
<div className="text-gray-500">Loading...</div> <div className="text-gray-500">Loading...</div>