@@ -3,8 +3,10 @@ import BBoxHelper from "src/utils/bbox-helper"
33import CanvasHelper from "src/utils/canvas-helper"
44import { FileSelectModal } from "src/utils/modal-helper"
55import CanvasExtension from "./canvas-extension"
6- import { Notice } from "obsidian"
6+ import { Notice , TFile } from "obsidian"
77import TextHelper from "src/utils/text-helper"
8+ import { CanvasFileNodeData } from "src/@types/AdvancedJsonCanvas"
9+ import { ExtendedCachedMetadata } from "src/@types/Obsidian"
810
911type Direction = 'up' | 'down' | 'left' | 'right'
1012const DIRECTIONS = [ 'up' , 'down' , 'left' , 'right' ] as Direction [ ]
@@ -171,6 +173,120 @@ export default class CommandsCanvasExtension extends CanvasExtension {
171173 }
172174 )
173175 } )
176+
177+ this . plugin . addCommand ( {
178+ id : 'pull-outgoing-links-to-canvas' ,
179+ name : 'Pull outgoing links to canvas' ,
180+ checkCallback : CanvasHelper . canvasCommand (
181+ this . plugin ,
182+ ( canvas : Canvas ) => ! canvas . readonly ,
183+ ( canvas : Canvas ) => {
184+ const canvasFile = canvas . view . file
185+ if ( ! canvasFile ) return
186+
187+ let selectedNodeIds = canvas . getSelectionData ( ) . nodes . map ( node => node . id )
188+ if ( selectedNodeIds . length === 0 ) selectedNodeIds = [ ...canvas . nodes . keys ( ) ]
189+
190+ const metadata = this . plugin . app . metadataCache . getFileCache ( canvasFile ) as ExtendedCachedMetadata
191+ if ( ! metadata ) return
192+
193+ // Get outgoing links for all selected nodes
194+ const outgoingLinks : Set < TFile > = new Set ( )
195+ for ( const nodeId of selectedNodeIds ) {
196+ let relativeFile = canvasFile
197+
198+ let nodeOutgoingLinks = metadata . nodes ?. [ nodeId ] ?. links
199+ if ( ! nodeOutgoingLinks ) {
200+ const file = canvas . nodes . get ( nodeId ) ?. file
201+ if ( ! file ) continue
202+
203+ const fileMetadata = this . plugin . app . metadataCache . getFileCache ( file )
204+ nodeOutgoingLinks = fileMetadata ?. links
205+ relativeFile = file
206+ }
207+ if ( ! nodeOutgoingLinks ) continue
208+
209+ for ( const nodeOutgoingLink of nodeOutgoingLinks ) {
210+ const resolvedLink = this . plugin . app . metadataCache . getFirstLinkpathDest ( nodeOutgoingLink . link , relativeFile . path )
211+ if ( ! ( resolvedLink instanceof TFile ) ) continue
212+
213+ outgoingLinks . add ( resolvedLink )
214+ }
215+ }
216+
217+ // Create outgoing links filter for those that are already on the canvas
218+ const existingFileNodes : Set < TFile > = new Set ( [ canvas . view . file ] )
219+ for ( const node of canvas . nodes . values ( ) ) {
220+ if ( node . getData ( ) . type !== 'file' || ! node . file ) continue
221+ existingFileNodes . add ( node . file )
222+ }
223+
224+ for ( const outgoingLink of outgoingLinks ) {
225+ if ( existingFileNodes . has ( outgoingLink ) ) continue
226+ this . createFileNode ( canvas , outgoingLink )
227+ }
228+ }
229+ )
230+ } )
231+
232+ this . plugin . addCommand ( {
233+ id : 'pull-backlinks-to-canvas' ,
234+ name : 'Pull backlinks to canvas' ,
235+ checkCallback : CanvasHelper . canvasCommand (
236+ this . plugin ,
237+ ( canvas : Canvas ) => ! canvas . readonly ,
238+ ( canvas : Canvas ) => {
239+ const canvasFile = canvas . view . file
240+ if ( ! canvasFile ) return
241+
242+ let selectedNodesData = canvas . getSelectionData ( ) . nodes . map ( node => node )
243+ const backlinks : Set < TFile > = new Set ( )
244+
245+ if ( selectedNodesData . length > 0 ) {
246+ // Get backlinks for all selected nodes
247+ for ( const nodeData of selectedNodesData ) {
248+ if ( nodeData . type !== 'file' || ! ( nodeData as CanvasFileNodeData ) . file ) continue
249+
250+ const file = this . plugin . app . vault . getFileByPath ( ( nodeData as CanvasFileNodeData ) . file )
251+ if ( ! file ) continue
252+
253+ const nodeBacklinks = this . plugin . app . metadataCache . getBacklinksForFile ( file )
254+ if ( ! nodeBacklinks ) continue
255+
256+ for ( const nodeBacklink of nodeBacklinks . data . keys ( ) ) {
257+ const resolvedLink = this . plugin . app . metadataCache . getFirstLinkpathDest ( nodeBacklink , file . path )
258+ if ( ! ( resolvedLink instanceof TFile ) ) continue
259+
260+ backlinks . add ( resolvedLink )
261+ }
262+ }
263+ } else {
264+ // Get all backlinks for the canvas file
265+ const canvasBacklinks = this . plugin . app . metadataCache . getBacklinksForFile ( canvasFile )
266+ if ( ! canvasBacklinks ) return
267+
268+ for ( const canvasBacklink of canvasBacklinks . data . keys ( ) ) {
269+ const resolvedLink = this . plugin . app . metadataCache . getFirstLinkpathDest ( canvasBacklink , canvasFile . path )
270+ if ( ! ( resolvedLink instanceof TFile ) ) continue
271+
272+ backlinks . add ( resolvedLink )
273+ }
274+ }
275+
276+ // Create backlinks filter for those that are already on the canvas
277+ const existingFileNodes : Set < TFile > = new Set ( [ canvas . view . file ] )
278+ for ( const node of canvas . nodes . values ( ) ) {
279+ if ( node . getData ( ) . type !== 'file' || ! node . file ) continue
280+ existingFileNodes . add ( node . file )
281+ }
282+
283+ for ( const backlink of backlinks ) {
284+ if ( existingFileNodes . has ( backlink ) ) continue
285+ this . createFileNode ( canvas , backlink )
286+ }
287+ }
288+ )
289+ } )
174290 }
175291
176292 private createTextNode ( canvas : Canvas ) {
@@ -180,12 +296,12 @@ export default class CommandsCanvasExtension extends CanvasExtension {
180296 canvas . createTextNode ( { pos : pos , size : size } )
181297 }
182298
183- private async createFileNode ( canvas : Canvas ) {
299+ private async createFileNode ( canvas : Canvas , file ?: TFile ) {
184300 const size = canvas . config . defaultFileNodeDimensions
185301 const pos = CanvasHelper . getCenterCoordinates ( canvas , size )
186- const file = await new FileSelectModal ( this . plugin . app , undefined , true ) . awaitInput ( )
302+ file ?? = await new FileSelectModal ( this . plugin . app , undefined , true ) . awaitInput ( )
187303
188- canvas . createFileNode ( { pos : pos , size : size , file : file } )
304+ canvas . createFileNode ( { pos : pos , size : size , file } )
189305 }
190306
191307 private cloneNode ( canvas : Canvas , cloneDirection : Direction ) {
0 commit comments