Skip to content

Commit 547a174

Browse files
fix: read cache.embeds alongside cache.links in typed_link and list_note
Obsidian stores `![[embed]]` links separately from `[[wikilink]]` links in CachedMetadata (cache.embeds vs cache.links). Both the typed_link Dataview-field parser and the list_note builder only read cache.links, so an embedded wikilink used as a field value or list item silently produced no edge. Refs #760
1 parent 128375a commit 547a174

5 files changed

Lines changed: 82 additions & 3 deletions

File tree

src/graph/builders/explicit/list_note.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,11 @@ function build_native_list_items(
5151
section?: string,
5252
): NativeListItem[] {
5353
let list_item_caches = cache.listItems ?? [];
54-
const links = cache.links ?? [];
54+
// `[[wikilink]]` and `![[embed]]` land in separate cache arrays, but a list
55+
// item's outlink can be either (e.g. `- ![[Note]]`).
56+
const links = [...(cache.links ?? []), ...(cache.embeds ?? [])].sort(
57+
(a, b) => a.position.start.col - b.position.start.col,
58+
);
5559
const lines = content.split("\n");
5660

5761
if (section) {

src/graph/builders/explicit/typed_link.ts

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -157,14 +157,18 @@ export const _add_explicit_edges_typed_link: ExplicitEdgeBuilder = async (
157157
(all_files.obsidian ?? []).map(
158158
async ({ file, cache }) => {
159159
if (file.extension !== "md") return;
160-
if (!cache?.links?.length) return;
160+
161+
// `[[wikilink]]` and `![[embed]]` land in separate cache arrays, but
162+
// a Dataview field can hold either (e.g. `down:: ![[note]]`).
163+
const links = [...(cache?.links ?? []), ...(cache?.embeds ?? [])];
164+
if (!links.length) return;
161165

162166
const content = await plugin.app.vault.cachedRead(file);
163167
const lines = content.split("\n");
164168

165169
const fields_by_line = new Map<number, InlineField[]>();
166170

167-
for (const link_cache of cache.links) {
171+
for (const link_cache of links) {
168172
const line_num = link_cache.position.start.line;
169173

170174
let fields = fields_by_line.get(line_num);

tests/graph/builders/helpers.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,8 @@ export function mock_file(
2424
listItems?: { line: number; col: number; parent: number }[];
2525
/** Body wikilinks keyed by line (list_note, typed_link builders) */
2626
links?: { line: number; link: string; col?: number }[];
27+
/** Body embeds keyed by line — `![[note]]` (typed_link builder) */
28+
embeds?: { line: number; link: string; col?: number }[];
2729
/** Headings (list_note section scoping): one per line */
2830
headings?: { line: number; level: number; heading: string }[];
2931
} = {},
@@ -49,6 +51,7 @@ export function mock_file(
4951
opts.frontmatterLinks ||
5052
opts.listItems ||
5153
opts.links ||
54+
opts.embeds ||
5255
opts.headings;
5356

5457
const cache = has_cache
@@ -73,6 +76,13 @@ export function mock_file(
7376
end: { line: l.line, col: l.col ?? 0, offset: 0 },
7477
},
7578
})),
79+
embeds: opts.embeds?.map((l) => ({
80+
link: l.link,
81+
position: {
82+
start: { line: l.line, col: l.col ?? 0, offset: 0 },
83+
end: { line: l.line, col: l.col ?? 0, offset: 0 },
84+
},
85+
})),
7686
headings: opts.headings?.map((h) => ({
7787
heading: h.heading,
7888
level: h.level,

tests/graph/builders/list_note.test.ts

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,35 @@ describe("list_note builder", () => {
4545
t.expect(r.edges[0]!.edge_source).toBe("list_note");
4646
});
4747

48+
// #760: `![[Note]]` (an embed) lands in cache.embeds, not cache.links — a
49+
// list item's outlink must still be picked up.
50+
test("embedded wikilink list item becomes a child edge", async (t) => {
51+
const content = "- ![[A]]\n- [[B]]";
52+
const list = mock_file("list.md", {
53+
frontmatter: { "BC-list-note-field": "down" },
54+
listItems: [
55+
{ line: 0, col: 0, parent: -1 },
56+
{ line: 1, col: 0, parent: -1 },
57+
],
58+
embeds: [{ line: 0, link: "A", col: 2 }],
59+
links: [{ line: 1, link: "B" }],
60+
});
61+
62+
const r = await _add_explicit_edges_list_note(
63+
make_plugin(
64+
{ edge_fields: EDGE_FIELDS, explicit_edge_sources: {} as never },
65+
[],
66+
link_resolver(["A", "B"]),
67+
{ cachedRead: async () => content },
68+
),
69+
make_all_files([list]),
70+
);
71+
72+
const pairs = r.edges.map((e) => [e.source, e.target]);
73+
t.expect(pairs).toContainEqual(["list.md", "A.md"]);
74+
t.expect(pairs).toContainEqual(["list.md", "B.md"]);
75+
});
76+
4877
test("note without BC-list-note-field is skipped", async (t) => {
4978
const r = await _add_explicit_edges_list_note(
5079
make_plugin(

tests/graph/builders/typed_link.test.ts

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -228,6 +228,38 @@ describe("typed_link builder — body inline fields", () => {
228228
t.expect(r.edges[0]!.edge_type).toBe("up");
229229
t.expect(r.edges[0]!.target).toBe("Note.md");
230230
});
231+
232+
// #760: `![[note]]` (an embed) lands in cache.embeds, not cache.links —
233+
// it must still be picked up as a field value.
234+
test("embedded wikilink value → edge", async (t) => {
235+
const body = "down:: ![[Convolutions]]";
236+
const plugin = inline_plugin(body);
237+
const all_files = make_all_files([
238+
mock_file("a.md", {
239+
embeds: [{ line: 0, link: "Convolutions", col: body.indexOf("!") }],
240+
}),
241+
]);
242+
const r = await _add_explicit_edges_typed_link(plugin, all_files);
243+
244+
t.expect(r.edges).toHaveLength(1);
245+
t.expect(r.edges[0]!.edge_type).toBe("down");
246+
t.expect(r.edges[0]!.target).toBe("Convolutions.md");
247+
});
248+
249+
test("wrapped field with embedded wikilink → edge", async (t) => {
250+
const body = "(down:: ![[Convolutions]])";
251+
const plugin = inline_plugin(body);
252+
const all_files = make_all_files([
253+
mock_file("a.md", {
254+
embeds: [{ line: 0, link: "Convolutions", col: body.indexOf("!") }],
255+
}),
256+
]);
257+
const r = await _add_explicit_edges_typed_link(plugin, all_files);
258+
259+
t.expect(r.edges).toHaveLength(1);
260+
t.expect(r.edges[0]!.edge_type).toBe("down");
261+
t.expect(r.edges[0]!.target).toBe("Convolutions.md");
262+
});
231263
});
232264

233265
describe("parse_inline_fields", () => {

0 commit comments

Comments
 (0)