2026-04-20 14:43:52 +02:00
|
|
|
import type { VaultEntry } from '../types'
|
2026-05-04 04:59:50 +02:00
|
|
|
import { findByNotePath, joinVaultPath, normalizeNotePathForIdentity, notePathsMatch } from './notePathIdentity'
|
2026-04-20 14:43:52 +02:00
|
|
|
|
|
|
|
|
interface PulledVaultRefreshOptions {
|
|
|
|
|
activeTabPath: string | null
|
2026-04-30 10:22:02 +02:00
|
|
|
getActiveTabPath?: () => string | null
|
2026-04-20 14:43:52 +02:00
|
|
|
closeAllTabs: () => void
|
|
|
|
|
hasUnsavedChanges: (path: string) => boolean
|
2026-05-05 02:00:28 +02:00
|
|
|
shouldKeepActiveEditorMounted?: () => boolean
|
2026-04-20 14:43:52 +02:00
|
|
|
reloadFolders: () => Promise<unknown> | unknown
|
|
|
|
|
reloadVault: () => Promise<VaultEntry[]>
|
|
|
|
|
reloadViews: () => Promise<unknown> | unknown
|
|
|
|
|
replaceActiveTab: (entry: VaultEntry) => Promise<void>
|
|
|
|
|
updatedFiles: string[]
|
|
|
|
|
vaultPath: string
|
|
|
|
|
}
|
|
|
|
|
|
2026-05-14 08:15:49 +02:00
|
|
|
function resolveUpdatedFilePath(options: { path: string; vaultPath: string }): string {
|
|
|
|
|
const { path, vaultPath } = options
|
2026-05-04 04:59:50 +02:00
|
|
|
if (path.startsWith('/')) return normalizeNotePathForIdentity(path)
|
|
|
|
|
return normalizeNotePathForIdentity(joinVaultPath(vaultPath, path))
|
2026-04-20 16:04:11 +02:00
|
|
|
}
|
|
|
|
|
|
2026-05-14 08:15:49 +02:00
|
|
|
function didPullUpdateActiveNote(options: {
|
|
|
|
|
updatedFiles: string[]
|
|
|
|
|
vaultPath: string
|
|
|
|
|
activeTabPath: string
|
|
|
|
|
}): boolean {
|
|
|
|
|
const { activeTabPath, updatedFiles, vaultPath } = options
|
|
|
|
|
return updatedFiles.some((path) => notePathsMatch(resolveUpdatedFilePath({ path, vaultPath }), activeTabPath))
|
2026-04-20 16:04:11 +02:00
|
|
|
}
|
|
|
|
|
|
2026-05-14 08:15:49 +02:00
|
|
|
function didActivePathChange(options: { initialPath: string; latestPath: string }): boolean {
|
|
|
|
|
const { initialPath, latestPath } = options
|
2026-05-04 04:59:50 +02:00
|
|
|
return !notePathsMatch(initialPath, latestPath)
|
2026-04-30 10:22:02 +02:00
|
|
|
}
|
|
|
|
|
|
2026-05-14 08:15:49 +02:00
|
|
|
function findExternallyMovedActiveEntry(options: {
|
|
|
|
|
activeTabPath: string
|
|
|
|
|
entries: VaultEntry[]
|
|
|
|
|
updatedFiles: string[]
|
|
|
|
|
vaultPath: string
|
|
|
|
|
}): VaultEntry | null {
|
|
|
|
|
const { activeTabPath, entries, updatedFiles, vaultPath } = options
|
|
|
|
|
if (updatedFiles.length === 0) return null
|
|
|
|
|
const activeFilename = normalizeNotePathForIdentity(activeTabPath).split('/').pop()
|
|
|
|
|
const updatedPaths = new Set(updatedFiles.map((path) => resolveUpdatedFilePath({ path, vaultPath })))
|
|
|
|
|
const candidates = entries.filter((entry) =>
|
|
|
|
|
!notePathsMatch(entry.path, activeTabPath)
|
|
|
|
|
&& entry.filename === activeFilename
|
|
|
|
|
&& updatedPaths.has(normalizeNotePathForIdentity(entry.path)),
|
|
|
|
|
)
|
|
|
|
|
|
2026-05-14 16:09:54 +02:00
|
|
|
const [candidate] = candidates
|
|
|
|
|
return candidates.length === 1 && candidate ? candidate : null
|
2026-05-14 08:15:49 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function isActivePathBlocked(options: {
|
|
|
|
|
activeTabPath: string | null
|
|
|
|
|
latestActiveTabPath: string | null
|
|
|
|
|
hasUnsavedChanges: PulledVaultRefreshOptions['hasUnsavedChanges']
|
|
|
|
|
}): boolean {
|
|
|
|
|
const { activeTabPath, latestActiveTabPath, hasUnsavedChanges } = options
|
|
|
|
|
if (!activeTabPath) return true
|
|
|
|
|
if (!latestActiveTabPath) return true
|
|
|
|
|
if (didActivePathChange({ initialPath: activeTabPath, latestPath: latestActiveTabPath })) return true
|
|
|
|
|
return hasUnsavedChanges(latestActiveTabPath)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function shouldReplaceActiveEntry(options: {
|
|
|
|
|
activePath: string
|
|
|
|
|
movedEntry: VaultEntry | null
|
|
|
|
|
refreshedEntry: VaultEntry | null
|
|
|
|
|
updatedFiles: string[]
|
|
|
|
|
vaultPath: string
|
|
|
|
|
}): boolean {
|
|
|
|
|
const {
|
|
|
|
|
activePath,
|
|
|
|
|
movedEntry,
|
|
|
|
|
refreshedEntry,
|
|
|
|
|
updatedFiles,
|
|
|
|
|
vaultPath,
|
|
|
|
|
} = options
|
|
|
|
|
if (movedEntry) return true
|
|
|
|
|
if (!refreshedEntry) return false
|
|
|
|
|
return didPullUpdateActiveNote({ updatedFiles, vaultPath, activeTabPath: activePath })
|
2026-05-14 05:53:37 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export function getPulledVaultUpdateOptions(): { preserveFocusedEditor: true } {
|
|
|
|
|
return { preserveFocusedEditor: true }
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-20 14:43:52 +02:00
|
|
|
export async function refreshPulledVaultState(options: PulledVaultRefreshOptions): Promise<VaultEntry[]> {
|
|
|
|
|
const {
|
|
|
|
|
activeTabPath,
|
|
|
|
|
closeAllTabs,
|
2026-04-30 10:22:02 +02:00
|
|
|
getActiveTabPath,
|
2026-04-20 14:43:52 +02:00
|
|
|
hasUnsavedChanges,
|
|
|
|
|
reloadFolders,
|
|
|
|
|
reloadVault,
|
|
|
|
|
reloadViews,
|
|
|
|
|
replaceActiveTab,
|
2026-04-20 16:04:11 +02:00
|
|
|
updatedFiles,
|
|
|
|
|
vaultPath,
|
2026-04-20 14:43:52 +02:00
|
|
|
} = options
|
|
|
|
|
|
|
|
|
|
const [entries] = await Promise.all([
|
|
|
|
|
reloadVault(),
|
|
|
|
|
Promise.resolve(reloadFolders()),
|
|
|
|
|
Promise.resolve(reloadViews()),
|
|
|
|
|
])
|
|
|
|
|
|
2026-04-30 10:22:02 +02:00
|
|
|
const latestActiveTabPath = getActiveTabPath?.() ?? activeTabPath
|
2026-05-14 08:15:49 +02:00
|
|
|
if (isActivePathBlocked({ activeTabPath, latestActiveTabPath, hasUnsavedChanges })) return entries
|
|
|
|
|
|
|
|
|
|
const activePath = latestActiveTabPath as string
|
2026-05-14 16:09:54 +02:00
|
|
|
const refreshedEntry = findByNotePath(entries, activePath) ?? null
|
2026-05-14 08:15:49 +02:00
|
|
|
const movedEntry = refreshedEntry ? null : findExternallyMovedActiveEntry({
|
|
|
|
|
activeTabPath: activePath,
|
|
|
|
|
entries,
|
|
|
|
|
updatedFiles,
|
|
|
|
|
vaultPath,
|
|
|
|
|
})
|
|
|
|
|
const replacementEntry = refreshedEntry ?? movedEntry
|
2026-04-20 14:43:52 +02:00
|
|
|
|
2026-05-14 08:15:49 +02:00
|
|
|
if (replacementEntry && shouldReplaceActiveEntry({
|
|
|
|
|
activePath,
|
|
|
|
|
movedEntry,
|
|
|
|
|
refreshedEntry,
|
|
|
|
|
updatedFiles,
|
|
|
|
|
vaultPath,
|
|
|
|
|
})) {
|
2026-04-20 14:43:52 +02:00
|
|
|
closeAllTabs()
|
2026-05-14 08:15:49 +02:00
|
|
|
await replaceActiveTab(replacementEntry)
|
2026-04-20 14:43:52 +02:00
|
|
|
return entries
|
|
|
|
|
}
|
|
|
|
|
|
2026-05-14 08:15:49 +02:00
|
|
|
if (!replacementEntry) closeAllTabs()
|
2026-04-20 14:43:52 +02:00
|
|
|
return entries
|
|
|
|
|
}
|