Merge branch 'main' into main

This commit is contained in:
github-actions[bot]
2026-04-29 01:16:17 +00:00
committed by GitHub
2 changed files with 81 additions and 11 deletions

View File

@@ -43,6 +43,16 @@ describe('resolveImageUrls', () => {
)
})
it('converts Windows relative attachment paths without mixed separators', () => {
tauriMode = true
const vaultPath = 'C:\\Users\\lnq12\\Documents\\tolaria-test\\Getting Started'
const markdown = '![BlockNote image](attachments/1776508281809-CleanShot.png)'
expect(resolveImageUrls(markdown, vaultPath)).toBe(
`![BlockNote image](${assetUrl('C:\\Users\\lnq12\\Documents\\tolaria-test\\Getting Started\\attachments\\1776508281809-CleanShot.png')})`,
)
})
it('leaves already-correct asset URLs unchanged', () => {
tauriMode = true
const url = assetUrl('/vault/attachments/file.png')
@@ -61,6 +71,16 @@ describe('resolveImageUrls', () => {
)
})
it('rewrites Windows legacy asset URLs from a different vault', () => {
tauriMode = true
const legacyUrl = httpAssetUrl('C:\\Users\\old\\Workspace\\tolaria-getting-started\\attachments\\CleanShot.png')
const markdown = `![CleanShot](${legacyUrl})`
expect(resolveImageUrls(markdown, 'C:\\Users\\john\\Documents\\Getting Started')).toBe(
`![CleanShot](${assetUrl('C:\\Users\\john\\Documents\\Getting Started\\attachments\\CleanShot.png')})`,
)
})
it('leaves already-correct http asset URLs unchanged', () => {
tauriMode = true
const url = httpAssetUrl('/vault/attachments/file.png')
@@ -125,6 +145,15 @@ describe('portableImageUrls', () => {
)
})
it('converts Windows extended-length asset URLs to relative paths', () => {
const url = httpAssetUrl('\\\\?\\C:\\Users\\lnq12\\Documents\\tolaria-test\\Getting Started\\attachments\\1777388840027-shot.png')
const markdown = `![screenshot](${url})`
expect(portableImageUrls(markdown, 'C:\\Users\\lnq12\\Documents\\tolaria-test\\Getting Started')).toBe(
'![screenshot](attachments/1777388840027-shot.png)',
)
})
it('is a no-op when vaultPath is empty', () => {
const url = assetUrl('/vault/attachments/file.png')
const markdown = `![alt](${url})`

View File

@@ -6,6 +6,9 @@ const HTTP_ASSET_URL_PREFIX = 'http://asset.localhost/'
const ASSET_URL_PREFIXES = [ASSET_URL_PREFIX, HTTP_ASSET_URL_PREFIX]
const ATTACHMENTS_SEGMENT = '/attachments/'
const RELATIVE_ATTACHMENTS_PREFIX = 'attachments/'
const WINDOWS_EXTENDED_PATH_PREFIX = '\\\\?\\'
const WINDOWS_EXTENDED_UNC_PREFIX = '\\\\?\\UNC\\'
const WINDOWS_DRIVE_PATH_PATTERN = /^[A-Za-z]:[\\/]/
type Markdown = string
type VaultPath = string
@@ -20,15 +23,47 @@ function assetUrl(path: AbsolutePath): MarkdownImageUrl {
return convertFileSrc(path)
}
function usesWindowsSeparators(path: string): boolean {
return WINDOWS_DRIVE_PATH_PATTERN.test(path) || path.startsWith('\\\\')
}
function relativePathForVault(vaultPath: VaultPath, attachmentPath: AttachmentPath): AttachmentPath {
return usesWindowsSeparators(vaultPath)
? attachmentPath.replace(/\//g, '\\')
: attachmentPath.replace(/\\/g, '/')
}
function vaultAttachmentPath(vaultPath: VaultPath, attachmentPath: AttachmentPath): AbsolutePath {
return `${vaultPath}/${attachmentPath}`
const separator = usesWindowsSeparators(vaultPath) ? '\\' : '/'
const normalizedAttachmentPath = relativePathForVault(vaultPath, attachmentPath)
const joiner = vaultPath.endsWith('/') || vaultPath.endsWith('\\') ? '' : separator
return `${vaultPath}${joiner}${normalizedAttachmentPath}`
}
function removeWindowsExtendedPrefix(path: AbsolutePath): AbsolutePath {
if (path.startsWith(WINDOWS_EXTENDED_UNC_PREFIX)) {
return `\\\\${path.slice(WINDOWS_EXTENDED_UNC_PREFIX.length)}`
}
if (path.startsWith(WINDOWS_EXTENDED_PATH_PREFIX)) {
return path.slice(WINDOWS_EXTENDED_PATH_PREFIX.length)
}
return path
}
function normalizedFilesystemPath(path: AbsolutePath): AbsolutePath {
return removeWindowsExtendedPrefix(path).replace(/\\/g, '/')
}
function withoutTrailingSlash(path: AbsolutePath): AbsolutePath {
return path.replace(/\/+$/, '')
}
function extractAttachmentPath(absolutePath: AbsolutePath): AttachmentPath | null {
const index = absolutePath.lastIndexOf(ATTACHMENTS_SEGMENT)
const normalizedPath = normalizedFilesystemPath(absolutePath)
const index = normalizedPath.lastIndexOf(ATTACHMENTS_SEGMENT)
if (index === -1) return null
const filename = absolutePath.slice(index + ATTACHMENTS_SEGMENT.length)
const filename = normalizedPath.slice(index + ATTACHMENTS_SEGMENT.length)
return filename ? `${RELATIVE_ATTACHMENTS_PREFIX}${filename}` : null
}
@@ -46,8 +81,19 @@ function isAssetUrl(url: MarkdownImageUrl): boolean {
}
function isCurrentVaultAsset(url: MarkdownImageUrl, vaultPath: VaultPath): boolean {
const absolutePath = decodeAssetPath(url)
return absolutePath === vaultPath || absolutePath.startsWith(`${vaultPath}/`)
const absolutePath = withoutTrailingSlash(normalizedFilesystemPath(decodeAssetPath(url)))
const normalizedVaultPath = withoutTrailingSlash(normalizedFilesystemPath(vaultPath))
return absolutePath === normalizedVaultPath || absolutePath.startsWith(`${normalizedVaultPath}/`)
}
function currentVaultAttachmentPath(url: MarkdownImageUrl, vaultPath: VaultPath): AttachmentPath | null {
const absolutePath = normalizedFilesystemPath(decodeAssetPath(url))
const normalizedVaultPath = withoutTrailingSlash(normalizedFilesystemPath(vaultPath))
const attachmentsPrefix = `${normalizedVaultPath}/${RELATIVE_ATTACHMENTS_PREFIX}`
if (!absolutePath.startsWith(attachmentsPrefix)) return null
const filename = absolutePath.slice(attachmentsPrefix.length)
return filename ? `${RELATIVE_ATTACHMENTS_PREFIX}${filename}` : null
}
function rewriteMarkdownImages(
@@ -80,14 +126,9 @@ export function resolveImageUrls(markdown: Markdown, vaultPath: VaultPath): Mark
export function portableImageUrls(markdown: Markdown, vaultPath: VaultPath): Markdown {
if (!vaultPath) return markdown
const attachmentsPrefix = `${vaultPath}/${RELATIVE_ATTACHMENTS_PREFIX}`
return rewriteMarkdownImages(markdown, (url) => {
if (!isAssetUrl(url)) return null
const absolutePath = decodeAssetPath(url)
if (!absolutePath.startsWith(attachmentsPrefix)) return null
return `${RELATIVE_ATTACHMENTS_PREFIX}${absolutePath.slice(attachmentsPrefix.length)}`
return currentVaultAttachmentPath(url, vaultPath)
})
}