@@ -647,7 +647,7 @@ const updateMarkdownAssetLinks = ({
647647 }
648648 }
649649
650- // Only resolve to files under import/{spaceName}/ so we don't point at the wrong vault's files; leave other links unchanged so they resolve from this folder when the target is created
650+ // Only resolve to files under import/{spaceName}/ so we don't point at the wrong vault's files
651651 const resolvedFile = app . metadataCache . getFirstLinkpathDest (
652652 linkPath ,
653653 targetFile . path ,
@@ -665,7 +665,21 @@ const updateMarkdownAssetLinks = ({
665665 return `[[${ linkText } ]]` ;
666666 }
667667
668- // No resolved file in import folder: keep link as-is. Using ./ prefix breaks Obsidian (getParentPrefix null, "Folder already exists"); where new notes are created is controlled by app settings.
668+ // Unresolved (dead) link from another vault: rewrite so that when the user creates the file from this link, it is created under import/{vaultName}/ in the same relative position as in the source vault
669+ if ( importFolder && originalNodePath ) {
670+ // Vault-relative link (e.g. "Discourse Nodes/EVD - no relation testing") -> use as-is. Path-from-current-file (e.g. "EVD - no relation testing") -> resolve relative to source note dir
671+ const canonicalSourcePath =
672+ linkPath . includes ( "/" ) && ! linkPath . startsWith ( "." ) && ! linkPath . startsWith ( "/" )
673+ ? normalizePathForLookup ( linkPath )
674+ : ( getCanonicalFromOriginalNote ( linkPath ) ??
675+ normalizePathForLookup ( linkPath ) ) ;
676+ const linkUnderImport = `${ importFolder } /${ canonicalSourcePath } ` ;
677+ if ( alias ) {
678+ return `[[${ linkUnderImport } |${ alias } ]]` ;
679+ }
680+ return `[[${ linkUnderImport } ]]` ;
681+ }
682+
669683 return match ;
670684 } ,
671685 ) ;
@@ -939,6 +953,15 @@ const sanitizeFileName = (fileName: string): string => {
939953 . trim ( ) ;
940954} ;
941955
956+ /** Sanitize each path segment for use under import folder (preserves source vault folder structure). */
957+ const sanitizePathForImport = ( path : string ) : string => {
958+ return path
959+ . split ( "/" )
960+ . map ( ( segment ) => sanitizeFileName ( segment ) )
961+ . filter ( Boolean )
962+ . join ( "/" ) ;
963+ } ;
964+
942965type ParsedFrontmatter = {
943966 nodeTypeId ?: string ;
944967 nodeInstanceId ?: string ;
@@ -1209,11 +1232,13 @@ export const importSelectedNodes = async ({
12091232 content,
12101233 createdAt : contentCreatedAt ,
12111234 modifiedAt : contentModifiedAt ,
1212- filePath,
1235+ filePath : contentFilePath ,
12131236 } = nodeContent ;
12141237 const createdAt = node . createdAt ?? contentCreatedAt ;
12151238 const modifiedAt = node . modifiedAt ?? contentModifiedAt ;
1216- const originalNodePath : string | undefined = node . filePath ;
1239+ // Use source vault path from Content direct variant metadata for wikilink rewriting and asset placement
1240+ const originalNodePath : string | undefined =
1241+ contentFilePath ?? node . filePath ;
12171242
12181243 // Sanitize file name
12191244 const sanitizedFileName = sanitizeFileName ( fileName ) ;
@@ -1223,13 +1248,29 @@ export const importSelectedNodes = async ({
12231248 // Update existing file - use its current path
12241249 finalFilePath = existingFile . path ;
12251250 } else {
1226- // Create new file in the import folder
1227- finalFilePath = `${ importFolderPath } /${ sanitizedFileName } .md` ;
1251+ // Preserve source vault folder structure under import/{vaultName} when we have filePath from Content
1252+ const pathUnderImport =
1253+ contentFilePath && contentFilePath . includes ( "/" )
1254+ ? sanitizePathForImport ( contentFilePath )
1255+ : `${ sanitizedFileName } .md` ;
1256+ finalFilePath = `${ importFolderPath } /${ pathUnderImport } ` ;
1257+
1258+ // Ensure parent folder exists (e.g. import/VaultName/Discourse Nodes)
1259+ const parentDir = finalFilePath . replace ( / \/ [ ^ / ] * $ / , "" ) ;
1260+ if ( parentDir !== importFolderPath ) {
1261+ const folderExists =
1262+ await plugin . app . vault . adapter . exists ( parentDir ) ;
1263+ if ( ! folderExists ) {
1264+ await plugin . app . vault . createFolder ( parentDir ) ;
1265+ }
1266+ }
12281267
12291268 // Check if file path already exists (edge case: same title but different nodeInstanceId)
12301269 let counter = 1 ;
12311270 while ( await plugin . app . vault . adapter . exists ( finalFilePath ) ) {
1232- finalFilePath = `${ importFolderPath } /${ sanitizedFileName } (${ counter } ).md` ;
1271+ const baseWithoutExt = finalFilePath . replace ( / \. m d $ / i, "" ) ;
1272+ const base = baseWithoutExt . replace ( / \s * \( \d + \) $ / , "" ) ;
1273+ finalFilePath = `${ base } (${ counter } ).md` ;
12331274 counter ++ ;
12341275 }
12351276 }
@@ -1242,7 +1283,7 @@ export const importSelectedNodes = async ({
12421283 sourceSpaceId : spaceId ,
12431284 sourceSpaceUri : spaceUri ,
12441285 rawContent : content ,
1245- originalFilePath : filePath ,
1286+ originalFilePath : contentFilePath ,
12461287 filePath : finalFilePath ,
12471288 importedCreatedAt : createdAt ,
12481289 importedModifiedAt : modifiedAt ,
0 commit comments