diff --git a/CHANGELOG.md b/CHANGELOG.md index d8420943..d32de6a8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +### Fixed + +* Inline Dataview fields are now recognized inside blockquotes and callouts — `> up:: [[Note]]` (and `> - up:: [[Note]]`) now creates the `up` edge, matching the behavior already shipped on the 1.12 maintenance line (4.14.3, #724). A trailing link on the same line still stays ordinary prose, per the #731 fix. + ## 4.X ### [4.21.3](https://github.com/michaelpporter/breadcrumbs/compare/4.21.2...4.21.3) (2026-07-25) diff --git a/src/graph/builders/explicit/typed_link.ts b/src/graph/builders/explicit/typed_link.ts index 67bf8956..45fc1e6d 100644 --- a/src/graph/builders/explicit/typed_link.ts +++ b/src/graph/builders/explicit/typed_link.ts @@ -14,9 +14,11 @@ export interface InlineField { value_end: number; } -// A bare inline field opening a line: "same:: ...", "- same:: ...", "up :: ...". +// A bare inline field opening a line, optionally behind blockquote and/or list +// markers: "same:: ...", "- same:: ...", "> same:: ...", "up :: ...". // Its value runs to the end of the line. -const LINE_FIELD_REGEX = /^(?:\s*[-*+\d.]+\s+)?([\w][\w\s-]*)\s*::\s*/; +const LINE_FIELD_REGEX = + /^(?:\s*>+\s*)?(?:\s*[-*+\d.]+\s+)?([\w][\w\s-]*)\s*::\s*/; // Dataview's bracket/paren wrappers, anywhere on the line: "(down:: ...)" / // "[down:: ...]". The value ends at the matching close bracket. diff --git a/tests/graph/builders/typed_link.test.ts b/tests/graph/builders/typed_link.test.ts index 254e2d46..113b8922 100644 --- a/tests/graph/builders/typed_link.test.ts +++ b/tests/graph/builders/typed_link.test.ts @@ -217,6 +217,17 @@ describe("typed_link builder — body inline fields", () => { t.expect(r.edges).toHaveLength(1); t.expect(r.edges[0]!.target).toBe("y.md"); }); + + // A callout/blockquote line (e.g. Obsidian's `> [!note]`) still opens with + // a bare inline field. + test("blockquote field builds an edge, trailing link stays prose", async (t) => { + const { plugin, all_files } = inline_case("> (up:: [[Note]]) [[Other]]"); + const r = await _add_explicit_edges_typed_link(plugin, all_files); + + t.expect(r.edges).toHaveLength(1); + t.expect(r.edges[0]!.edge_type).toBe("up"); + t.expect(r.edges[0]!.target).toBe("Note.md"); + }); }); describe("parse_inline_fields", () => { @@ -234,6 +245,8 @@ describe("parse_inline_fields", () => { ["paren wrapper", "(down:: [[X]])", "down"], ["bracket wrapper", "[down:: [[X]]]", "down"], ["indented list marker", " - down:: [[X]]", "down"], + ["blockquote", "> down:: [[X]]", "down"], + ["blockquote with list marker", "> - down:: [[X]]", "down"], ["hyphenated field", "my-field:: [[X]]", "my-field"], // Pinned behaviour: the field-name char class allows internal spaces, // so a prose prefix ending in `word:: ` is captured verbatim. Harmless @@ -247,7 +260,6 @@ describe("parse_inline_fields", () => { test.each([ ["single colon", "down: [[X]]"], ["no field name", ":: [[X]]"], - ["non-word line start (blockquote)", "> down:: [[X]]"], ["plain prose", "just a normal sentence"], ["empty line", ""], ])("returns null — %s", (_label, line) => {