Skip to content

Commit eaf5137

Browse files
Merge branch 'beta'
2 parents 5c4cc80 + e03033f commit eaf5137

File tree

6 files changed

+178
-6
lines changed

6 files changed

+178
-6
lines changed

README.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -193,6 +193,10 @@ Quickly locate text within your canvas using a familiar search experience. Advan
193193
* Swap two selected nodes with each other (x, y and width, height will be swapped)
194194
* `Advanced Canvas: Copy wikilink to node`
195195
* Copy the wikilink to the selected node to the clipboard
196+
* `Advanced Canvas: Pull outgoing links to canvas`
197+
* Create file nodes for all outgoing links of the selected nodes / the whole canvas if no node is selected
198+
* `Advanced Canvas: Pull backlinks to canvas`
199+
* Create file nodes for all backlinks of the selected nodes / the whole canvas if no node is selected
196200
</details>
197201

198202
## Node Styles

src/@types/Obsidian.d.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -103,6 +103,7 @@ declare module "obsidian" {
103103
saveFileCache: (filepath: string, cache: FileCacheEntry) => void
104104
linkResolver: () => void
105105
resolveLinks: (filepath: string, /* custom */ cachedContent: any) => void
106+
getBacklinksForFile: (file: TFile) => { data: Map<string, LinkCache[]> }
106107

107108
// Custom
108109
registerInternalLinkAC: (canvasName: string, from: string, to: string) => void

src/canvas-extensions/better-default-settings-canvas-extension.ts

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import { Canvas, CanvasEdge, CanvasNode } from "src/@types/Canvas"
22
import CanvasHelper from "src/utils/canvas-helper"
33
import { FileSelectModal } from "src/utils/modal-helper"
44
import CanvasExtension from "./canvas-extension"
5-
import { CanvasFileNodeData } from "src/@types/AdvancedJsonCanvas"
5+
import { CanvasColor, CanvasFileNodeData } from "src/@types/AdvancedJsonCanvas"
66

77
export default class BetterDefaultSettingsCanvasExtension extends CanvasExtension {
88
isEnabled() { return true }
@@ -103,8 +103,12 @@ export default class BetterDefaultSettingsCanvasExtension extends CanvasExtensi
103103
const nodeData = node.getData()
104104
if (nodeData.type !== 'text') return
105105

106+
let color: CanvasColor | undefined = this.plugin.settings.getSetting('defaultTextNodeColor').toString() as CanvasColor
107+
if (color === "0") color = undefined
108+
106109
node.setData({
107110
...nodeData,
111+
color: color,
108112
styleAttributes: {
109113
...nodeData.styleAttributes,
110114
...this.plugin.settings.getSetting('defaultTextNodeStyleAttributes')
@@ -115,8 +119,12 @@ export default class BetterDefaultSettingsCanvasExtension extends CanvasExtensi
115119
private async applyDefaultEdgeStyles(canvas: Canvas, edge: CanvasEdge) {
116120
const edgeData = edge.getData()
117121

122+
let color: CanvasColor | undefined = this.plugin.settings.getSetting('defaultEdgeColor').toString() as CanvasColor
123+
if (color === "0") color = undefined
124+
118125
edge.setData({
119126
...edgeData,
127+
color: color,
120128
styleAttributes: {
121129
...edgeData.styleAttributes,
122130
...this.plugin.settings.getSetting('defaultEdgeStyleAttributes')

src/canvas-extensions/commands-canvas-extension.ts

Lines changed: 120 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,10 @@ import BBoxHelper from "src/utils/bbox-helper"
33
import CanvasHelper from "src/utils/canvas-helper"
44
import { FileSelectModal } from "src/utils/modal-helper"
55
import CanvasExtension from "./canvas-extension"
6-
import { Notice } from "obsidian"
6+
import { Notice, TFile } from "obsidian"
77
import TextHelper from "src/utils/text-helper"
8+
import { CanvasFileNodeData } from "src/@types/AdvancedJsonCanvas"
9+
import { ExtendedCachedMetadata } from "src/@types/Obsidian"
810

911
type Direction = 'up' | 'down' | 'left' | 'right'
1012
const 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) {

src/canvas-extensions/export-canvas-extension.ts

Lines changed: 28 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -100,6 +100,19 @@ export default class ExportCanvasExtension extends CanvasExtension {
100100
.onChange(value => noFontExport = value)
101101
)
102102

103+
let theme: 'light' | 'dark' = document.body.classList.contains('theme-dark') ? 'dark' : 'light'
104+
new Setting(modal.contentEl)
105+
.setName('Theme')
106+
.setDesc('The theme used for the export.')
107+
.addDropdown(dropdown => dropdown
108+
.addOptions({
109+
light: 'Light',
110+
dark: 'Dark'
111+
} as Record<'light' | 'dark', string>)
112+
.setValue(theme)
113+
.onChange(value => theme = value as 'light' | 'dark')
114+
)
115+
103116
let watermark = false
104117
new Setting(modal.contentEl)
105118
.setName('Show logo')
@@ -140,6 +153,7 @@ export default class ExportCanvasExtension extends CanvasExtension {
140153
svg,
141154
svg ? 1 : pixelRatioFactor,
142155
svg ? noFontExport : false,
156+
theme,
143157
watermark,
144158
garbledText,
145159
svg ? true : transparentBackground
@@ -151,7 +165,14 @@ export default class ExportCanvasExtension extends CanvasExtension {
151165
modal.open()
152166
}
153167

154-
private async exportImage(canvas: Canvas, nodesToExport: CanvasNode[] | null, svg: boolean, pixelRatioFactor: number, noFontExport: boolean, watermark: boolean, garbledText: boolean, transparentBackground: boolean) {
168+
private async exportImage(canvas: Canvas, nodesToExport: CanvasNode[] | null, svg: boolean, pixelRatioFactor: number, noFontExport: boolean, theme: 'light' | 'dark', watermark: boolean, garbledText: boolean, transparentBackground: boolean) {
169+
// Set theme
170+
const cachedTheme = document.body.classList.contains('theme-dark') ? 'dark' : 'light'
171+
if (theme !== cachedTheme) {
172+
document.body.classList.toggle('theme-dark', theme === 'dark')
173+
document.body.classList.toggle('theme-light', theme === 'light')
174+
}
175+
155176
const isWholeCanvas = nodesToExport === null
156177
if (!nodesToExport) nodesToExport = [...canvas.nodes.values()]
157178

@@ -325,6 +346,12 @@ export default class ExportCanvasExtension extends CanvasExtension {
325346

326347
// Remove the loading overlay
327348
interactionBlocker.remove()
349+
350+
// Restore theme
351+
if (theme !== cachedTheme) {
352+
document.body.classList.toggle('theme-dark', cachedTheme === 'dark')
353+
document.body.classList.toggle('theme-light', cachedTheme === 'light')
354+
}
328355
}
329356
}
330357

src/settings.ts

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,10 +29,12 @@ export interface AdvancedCanvasPluginSettingsValues {
2929

3030
nodeStylingFeatureEnabled: boolean
3131
customNodeStyleAttributes: StyleAttribute[]
32+
defaultTextNodeColor: number
3233
defaultTextNodeStyleAttributes: { [key: string]: string }
3334

3435
edgesStylingFeatureEnabled: boolean
3536
customEdgeStyleAttributes: StyleAttribute[]
37+
defaultEdgeColor: number
3638
defaultEdgeLineDirection: keyof typeof SETTINGS.edgesStylingFeatureEnabled.children.defaultEdgeLineDirection.options
3739
defaultEdgeStyleAttributes: { [key: string]: string }
3840
edgeStyleUpdateWhileDragging: boolean
@@ -119,10 +121,12 @@ export const DEFAULT_SETTINGS_VALUES: AdvancedCanvasPluginSettingsValues = {
119121

120122
nodeStylingFeatureEnabled: true,
121123
customNodeStyleAttributes: [],
124+
defaultTextNodeColor: 0,
122125
defaultTextNodeStyleAttributes: {},
123126

124127
edgesStylingFeatureEnabled: true,
125128
customEdgeStyleAttributes: [],
129+
defaultEdgeColor: 0,
126130
defaultEdgeLineDirection: 'unidirectional',
127131
defaultEdgeStyleAttributes: {},
128132
edgeStyleUpdateWhileDragging: false,
@@ -352,6 +356,12 @@ export const SETTINGS = {
352356
type: 'button',
353357
onClick: () => window.open("https://github.com/Developer-Mike/obsidian-advanced-canvas/blob/main/README.md#custom-styles")
354358
} as ButtonSetting,
359+
defaultTextNodeColor: {
360+
label: 'Default text node color',
361+
description: 'The default color of a text node. The default range is from 0 to 6, where 0 is no color. The range can be extended by using the Custom Colors feature of Advanced Canvas.',
362+
type: 'number',
363+
parse: (value: string) => Math.max(0, parseInt(value) || 0)
364+
},
355365
defaultTextNodeStyleAttributes: {
356366
label: 'Default text node style attributes',
357367
type: 'styles',
@@ -373,6 +383,12 @@ export const SETTINGS = {
373383
type: 'button',
374384
onClick: () => window.open("https://github.com/Developer-Mike/obsidian-advanced-canvas/blob/main/README.md#custom-styles")
375385
} as ButtonSetting,
386+
defaultEdgeColor: {
387+
label: 'Default edge color',
388+
description: 'The default color of an edge. The default range is from 0 to 6, where 0 is no color. The range can be extended by using the Custom Colors feature of Advanced Canvas.',
389+
type: 'number',
390+
parse: (value: string) => Math.max(0, parseInt(value) || 0)
391+
},
376392
defaultEdgeLineDirection: {
377393
label: 'Default edge line direction',
378394
description: 'The default line direction of an edge.',

0 commit comments

Comments
 (0)