Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
69 commits
Select commit Hold shift + click to select a range
00e64c4
vault: Sample files for #3627 - with hooks disabled
claremacrae Sep 22, 2025
dbc32a3
feat: Initial implementation of TasksFile.propertyAsLink() and proper…
claremacrae Sep 23, 2025
aaf1d2c
test: - Test link.destinationPath
claremacrae Sep 23, 2025
6128fbe
feat: - Add Link.destinationFile - does not yet access metadata
claremacrae Sep 23, 2025
7239b97
refactor: . Slide line up
claremacrae Sep 24, 2025
31ff538
refactor: . Inline app variable
claremacrae Sep 24, 2025
8156b87
refactor: . Extract QueryRenderer.getFileCache()
claremacrae Sep 24, 2025
d57a540
refactor: ! Add uninitialiased app member to LinkResolver
claremacrae Sep 24, 2025
bda23a3
refactor: ! Add setter and getter for Link.app
claremacrae Sep 24, 2025
cdf315e
refactor: ! Make setter for Link.app explicit
claremacrae Sep 24, 2025
7f2aaf2
refactor: ! In the built-plugin, allow LinkResolver to access app
claremacrae Sep 24, 2025
ea0ae1e
refactor: . Extract app variable in QueryRenderer.getFileCache()
claremacrae Sep 24, 2025
150a197
refactor: . Extract globalGetFileCache()
claremacrae Sep 24, 2025
b4b55a9
refactor: . Inline app variable
claremacrae Sep 24, 2025
5dc73c7
refactor: . Move globalGetFileCache to CacheReader.ts
claremacrae Sep 24, 2025
30a5e06
feat: Teach Link.destinationFile() to read properties, in plugin at l…
claremacrae Sep 24, 2025
f10ec6c
vault: Show how to use task.file.propertyAsLink() - no hooks
claremacrae Sep 24, 2025
e3a1633
vault: Show use of task.file.propertyAsLink() when no link
claremacrae Sep 24, 2025
72c3e35
vault: Show alternative filtering option - no hook
claremacrae Sep 24, 2025
cfa5f80
vault: Use consistent quote characters
claremacrae Sep 24, 2025
6f75fd5
vault: Fix 'yarn lint:markdown'
claremacrae Sep 24, 2025
72c6d9e
vault: Use headings to group sections - and add simpler 'group by' ex…
claremacrae Sep 24, 2025
53edb85
vault: Add section to show dataview equivalents
claremacrae Sep 24, 2025
13935ac
vault: Add global filter to sample tasks from user
claremacrae Sep 24, 2025
7de37f6
refactor: ! Introduce manually-tested getFileCacheFn mechanism to Lin…
claremacrae Sep 24, 2025
c745558
refactor: ! main.ts now sets up LinkResolver.getInstance().getFileCac…
claremacrae Sep 24, 2025
66cf68b
refactor: ! Remove LinkResolver.app, as no longer needed
claremacrae Sep 24, 2025
f579873
vault: Show how to get dataview to chain property values
claremacrae Sep 25, 2025
73a8097
Merge branch 'main' into discussion-3627
claremacrae Sep 27, 2025
1356ef0
test: - Make MockDataLoader.findDataFromMarkdownPath() find files not…
claremacrae Sep 27, 2025
46beb84
test: - Simplify MockDataLoader.findDataFromMarkdownPath()
claremacrae Sep 27, 2025
57efcd1
test: - Use more general implementation to resolve links
claremacrae Sep 27, 2025
15c758e
test: - Write a test of following a link to get property from another…
claremacrae Sep 27, 2025
d40d207
test: - Simplify a previously-experimental test
claremacrae Sep 27, 2025
9451d52
vault: Add series of 4 notes that link to each other
claremacrae Sep 27, 2025
36d51e3
tests: - Add series of 4 notes that link to each other
claremacrae Sep 27, 2025
2d08942
tests: - Update __test_data__/ for non-test markdown files
claremacrae Sep 27, 2025
845fa6e
tests: - Demonstrate (un)-usability of following a series of property…
claremacrae Sep 27, 2025
3563497
tests: - Remove unnecessary/duplicate test
claremacrae Sep 27, 2025
edde0db
tests: - Try manually formatting test for readability
claremacrae Sep 27, 2025
03ee6ed
tests: - Use prettier-ignore for control over formatting of repetitio…
claremacrae Sep 27, 2025
cb53a16
jsdoc: Update description of Link.destinationFile()
claremacrae Sep 27, 2025
41b1a86
refactor: . Rename Link.destinationFile() to asFile()
claremacrae Sep 30, 2025
800d85f
vault: . Rename Link.destinationFile() to asFile()
claremacrae Sep 30, 2025
685cb82
test: . Update tests for rename of destinationFile to asFile
claremacrae Sep 30, 2025
f44a907
refactor: - Make Link.asFile() a function instead of a getter
claremacrae Sep 30, 2025
c752484
vault: Add demo of chaining links together
claremacrae Sep 30, 2025
8d80230
test: - Automatically set up LinkResolver for all tests
claremacrae Oct 3, 2025
e0a7c94
test: - Fix failing tests
claremacrae Oct 3, 2025
8beec1b
test: - Remove duplicate test
claremacrae Oct 3, 2025
cff2127
test: - Prepare to remove LinkResolver.resetGetFirstLinkpathDestFn
claremacrae Oct 3, 2025
62bf347
test: - Prepare to remove LinkResolver.resetGetFirstLinkpathDestFn
claremacrae Oct 3, 2025
1773d92
test: . Add explanatory comment explaining setGetFirstLinkpathDestFn(…
claremacrae Oct 3, 2025
9ed9c17
test: ! Include the path in an MockDataLoader error message
claremacrae Oct 3, 2025
1fa7f54
test: - We no longer need to reset LinkResolver - it's done in LinkRe…
claremacrae Oct 3, 2025
2d42e90
test: - We no longer need to configure LinkResolver - it's done in Li…
claremacrae Oct 3, 2025
73214be
test: - We no longer need to configure LinkResolver - it's done in Li…
claremacrae Oct 3, 2025
44c33f9
test: - remove LinkResolver.resetGetFirstLinkpathDestFn() - no longer…
claremacrae Oct 3, 2025
76e4525
test: - remove LinkResolver.resetGetFileCacheFn() - no longer needed
claremacrae Oct 3, 2025
48ec486
test: Update jsdoc for LinkResolver
claremacrae Oct 3, 2025
3e03ee5
test: - Move LinkResolver.setup.ts to same dir as LinkResolver.ts
claremacrae Oct 3, 2025
15dfbbe
vault: Link.asFile() is a function now, instead of a getter
claremacrae Oct 3, 2025
a3f8402
vault: Fix broken line in sample file
claremacrae Oct 3, 2025
493e2a0
vault: Show how to tabulate project status in a Base
claremacrae Oct 3, 2025
6b6e449
vault: Remove confusing double space in file name
claremacrae Oct 3, 2025
fab60a6
vault: Add views for active and idea notes
claremacrae Oct 3, 2025
325f3cf
vault: Divide dataview examples out in to separate file
claremacrae Oct 3, 2025
a8a1382
vault: Divide bases example out in to separate file
claremacrae Oct 3, 2025
645888f
vault: Rename 'Homepage.md' to 'Homepage - Tasks.md'
claremacrae Oct 3, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 5 additions & 2 deletions jest.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,9 @@ module.exports = {

// A list of paths to modules that run some code to configure or
// set up the testing framework before each test.
setupFilesAfterEnv: ['<rootDir>/tests/CustomMatchers/jest.custom_matchers.setup.ts'],
globalSetup: "./tests/global-setup.js"
setupFilesAfterEnv: [
'<rootDir>/tests/CustomMatchers/jest.custom_matchers.setup.ts',
'<rootDir>/tests/Task/LinkResolver.setup.ts',
],
globalSetup: './tests/global-setup.js',
};
7 changes: 7 additions & 0 deletions resources/sample_vaults/Tasks-Demo/Test Data/chain_link1.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
link_to_file: "[[chain_link2]]"
---

# chain_link1

- [ ] #task Task in 'chain_link1'
7 changes: 7 additions & 0 deletions resources/sample_vaults/Tasks-Demo/Test Data/chain_link2.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
link_to_file: "[[chain_link3]]"
---

# chain_link2

- [ ] #task Task in 'chain_link2'
7 changes: 7 additions & 0 deletions resources/sample_vaults/Tasks-Demo/Test Data/chain_link3.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
link_to_file: "[[chain_link4]]"
---

# chain_link3

- [ ] #task Task in 'chain_link3'
7 changes: 7 additions & 0 deletions resources/sample_vaults/Tasks-Demo/Test Data/chain_link4.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
link_to_file: "[[chain_link1]]"
---

# chain_link4

- [ ] #task Task in 'chain_link4'
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
---
created: 2025-09-22
project: "[[Active Project]]"
themes:
---

# note

# Active Project note

---

- [ ] #task Active task
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
---
created: 2025-09-22
project: "[[Active Project]]"
themes:
status: active
priority: 5 medium
due:
dependencies:
---

# project #note

# Active Project

---

- [[Active Project note]]
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
# Chaining links together

## Chaining links together in 'group by' instructions

```tasks
filename includes chain_link

# TODO Make 'group by' detect if the object is a link, and group by its markdown

group by function 'Level 1 link: ' + \
task.file.\
propertyAsLink('link_to_file').markdown

group by function 'Level 2 link: ' + \
task.file.\
propertyAsLink('link_to_file').asFile().\
propertyAsLink('link_to_file').markdown

group by function 'Level 3 link: ' + \
task.file.\
propertyAsLink('link_to_file').asFile().\
propertyAsLink('link_to_file').asFile().\
propertyAsLink('link_to_file').markdown

group by function 'Level 4 link: ' + \
task.file.\
propertyAsLink('link_to_file').asFile().\
propertyAsLink('link_to_file').asFile().\
propertyAsLink('link_to_file').asFile().\
propertyAsLink('link_to_file').markdown

```
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
# Homepage - Bases

With Obsidian 1.10.0.

```base
filters:
and:
- file.folder == this.file.folder
formulas:
project status: project.asFile().properties.status
views:
- type: table
name: All Notes
groupBy:
property: formula.project status
direction: ASC
order:
- file.name
- project
- formula.project status
- type: table
name: Active Project Notes
filters:
and:
- formula["project status"] == "active"
groupBy:
property: formula.project status
direction: ASC
order:
- file.name
- project
- formula.project status
- type: table
name: Ideas
filters:
and:
- formula["project status"] == "idea"
groupBy:
property: formula.project status
direction: ASC
order:
- file.name
- project
- formula.project status

```
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
# Homepage - Dataview

## Dataview - group by project

If **either** note could have inline properties:

```dataview
task
WHERE startswith(file.folder, this.file.folder)
GROUP BY file.frontmatter.project
```

If **neither** note could have inline properties:

```dataview
task
WHERE startswith(file.folder, this.file.folder)
GROUP BY project
```

## Dataview - group by project status

If **either** note could have inline properties:

```dataview
task
WHERE startswith(file.folder, this.file.folder)
GROUP BY project.file.frontmatter.status
```

If **neither** note could have inline properties:

```dataview
task
WHERE startswith(file.folder, this.file.folder)
GROUP BY project.status
```
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
# Homepage - Tasks

## Tasks queries to visualise behaviour

### Use 'group by' to visualise behaviour - see raw text

```tasks
not done
preset this_folder

group by function '1 `' + JSON.stringify(task.file.property("project")) + '`'
group by function '2 `' + JSON.stringify(task.file.propertyAsLink("project")?.destinationPath) + '`'
group by function '3 `' + JSON.stringify(task.file.propertyAsLink("project")?.asFile().property("status")) + '`'
```

### Use 'group by' to visualise behaviour - see rendered values

```tasks
not done
preset this_folder

group by function task.file.propertyAsLink("project")?.markdown ?? ''
```

## What the user requested

I want to only filter tasks that:

- are in active project (property status), every project has file representing it,
- but there are also other notes related to that project via project property.

So in the example i provided, i only want Active task to be present.

## Tasks searches

### One instruction

```tasks
not done
preset this_folder

filter by function task.file.propertyAsLink("project")?.asFile()?.property("status") === "active"
```

### Two instructions

Possibly slightly faster version?

```tasks
not done
preset this_folder

filter by function task.file.hasProperty("project")
filter by function task.file.propertyAsLink("project")?.asFile()?.property("status") === "active"
```
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
---
created: 2025-09-22
project: "[[Inactive project]]"
themes:
---

# note

# Inactive Project note

---

- [ ] #task Inactive task
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
---
created: 2025-09-22
project: "[[Inactive project]]"
themes:
status: idea
priority: 5 medium
due:
dependencies:
---

# project #note

# Inactive project

---

- [[Inactive Project note]]
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
---
created: 2025-09-22
themes:
---

# note

# No Project note

---

- [ ] #task No project task
10 changes: 10 additions & 0 deletions src/Obsidian/CacheReader.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import { App, type CachedMetadata, TFile } from 'obsidian';

export function globalGetFileCache(app: App, filePath: string) {
const tFile = app.vault.getAbstractFileByPath(filePath);
let fileCache: CachedMetadata | null = null;
if (tFile && tFile instanceof TFile) {
fileCache = app.metadataCache.getFileCache(tFile);
}
return fileCache;
}
14 changes: 7 additions & 7 deletions src/Renderer/QueryRenderer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import type { TasksEvents } from '../Obsidian/TasksEvents';
import { TasksFile } from '../Scripting/TasksFile';
import { DateFallback } from '../DateTime/DateFallback';
import type { Task } from '../Task/Task';
import { globalGetFileCache } from '../Obsidian/CacheReader';
import { type BacklinksEventHandler, type EditButtonClickHandler, QueryResultsRenderer } from './QueryResultsRenderer';
import { createAndAppendElement } from './TaskLineRenderer';

Expand Down Expand Up @@ -57,17 +58,12 @@ export class QueryRenderer {
// not yet available, so empty.
// - Multi-line properties are supported, but they cannot contain
// continuation lines.
const app = this.app;
const filePath = context.sourcePath;
const tFile = app.vault.getAbstractFileByPath(filePath);
let fileCache: CachedMetadata | null = null;
if (tFile && tFile instanceof TFile) {
fileCache = app.metadataCache.getFileCache(tFile);
}
const fileCache = this.getFileCache(filePath);
const tasksFile = new TasksFile(filePath, fileCache ?? {});

const queryRenderChild = new QueryRenderChild({
app: app,
app: this.app,
plugin: this.plugin,
events: this.events,
container: element,
Expand All @@ -77,6 +73,10 @@ export class QueryRenderer {
context.addChild(queryRenderChild);
queryRenderChild.load();
}

private getFileCache(filePath: string) {
return globalGetFileCache(this.app, filePath);
}
}

/**
Expand Down
Loading