diff --git a/docs/Getting Started/Frontmatter Properties.md b/docs/Getting Started/Frontmatter Properties.md index 7f536007dc..25c6b82c6a 100644 --- a/docs/Getting Started/Frontmatter Properties.md +++ b/docs/Getting Started/Frontmatter Properties.md @@ -20,10 +20,7 @@ publish: true Obsidian offers a facility called [Properties](https://help.obsidian.md/Editing+and+formatting/Properties). -The Obsidian documentation says: - -> [!Quote] -> Properties allow you to organize information about a note. Properties contain structured data such as text, links, dates, checkboxes, and numbers. Properties can also be used in combination with [Community plugins](https://help.obsidian.md/Extending+Obsidian/Community+plugins) that can do useful things with your structured data. +Properties allow you to organize information about a note. Properties contain structured data such as text, links, dates, checkboxes, and numbers. This is an example property section, and it *must* appear on the very first line of the markdown file: @@ -35,6 +32,10 @@ name: value In the Tasks documentation, we refer to these as Frontmatter Properties, to distinguish them from Task and Query properties. +## Why use Frontmatter Properties in Tasks queries? + +For example, if you associate a tag with a project, you might want to put that tag in one place at the top of the file, instead of having to remember to add it on every single task line in the file. + ## How does Tasks treat Frontmatter Properties? - Frontmatter property values can be used in the following instructions: @@ -53,65 +54,10 @@ In the Tasks documentation, we refer to these as Frontmatter Properties, to dist - `property name` will find `Property Name`, for example. - Tags in Frontmatter can be accessed with `task.file.property('tags')` - `TAG` and `TAGS` are standardised to `tags`. - - The `#` prefix is added to all tag values in frontmatter. + - The `#` prefix is added to all tag values returned by this function. - Aliases in Frontmatter are not yet standardised. - If your vault contains a mixture of `alias`, `ALIAS` and `ALIASES`, your queries will need to be coded to handle both spellings, for now. -- Tasks reads both YAML and [JSON](https://help.obsidian.md/Editing+and+formatting/Properties#JSON+Properties) properties. - -## How does Tasks interpret Frontmatter Properties? - -Consider a file with the following example properties (or "Frontmatter"): - - - -```yaml ---- -sample_checkbox_property: true -sample_date_property: 2024-07-21 -sample_date_and_time_property: 2024-07-21T12:37:00 -sample_list_property: - - Sample - - List - - Value -sample_number_property: 246 -sample_text_property: Sample Text Value -sample_text_multiline_property: | - Sample - Text - Value -sample_link_property: "[[yaml_all_property_types_populated]]" -sample_link_list_property: - - "[[yaml_all_property_types_populated]]" - - "[[yaml_all_property_types_empty]]" -aliases: - - YAML All Property Types Populated -tags: - - tag-from-file-properties -creation date: 2024-05-25T15:17:00 -project: Secret Project ---- -``` - -The following table shows how most of those properties are interpreted in Tasks queries: - - - -| Field | Type 1 | Example 1 | -| ----- | ----- | ----- | -| `task.file.hasProperty('creation date')` | `boolean` | `true` | -| `task.file.property('creation date')` | `string` | `'2024-05-25T15:17:00'` | -| `task.file.property('sample_checkbox_property')` | `boolean` | `true` | -| `task.file.property('sample_date_property')` | `string` | `'2024-07-21'` | -| `task.file.property('sample_date_and_time_property')` | `string` | `'2024-07-21T12:37:00'` | -| `task.file.property('sample_list_property')` | `string[]` | `['Sample', 'List', 'Value']` | -| `task.file.property('sample_number_property')` | `number` | `246` | -| `task.file.property('sample_text_property')` | `string` | `'Sample Text Value'` | -| `task.file.property('sample_text_multiline_property')` | `string` | `'Sample\nText\nValue\n'` | -| `task.file.property('sample_link_property')` | `string` | `'[[yaml_all_property_types_populated]]'` | -| `task.file.property('sample_link_list_property')` | `string[]` | `['[[yaml_all_property_types_populated]]', '[[yaml_all_property_types_empty]]']` | -| `task.file.property('tags')` | `string[]` | `['#tag-from-file-properties']` | - - +- Tasks reads both YAML and [JSON](https://help.obsidian.md/Editing+and+formatting/Properties#JSON+Properties) formats. ## Frontmatter Properties Examples @@ -147,53 +93,140 @@ filter by function task.file.hasProperty('kanban-plugin') filter by function ! task.file.hasProperty('kanban-plugin') ``` -### More filtering examples +### Tracking projects - +#### Use a `project` property + +Suppose you have multiple files associated with a project, spread throughout your vault, and they all have a `project` property like this: + +```yaml +--- +project: Project 1 +--- +``` + +This search will find all tasks in those files: ```javascript -filter by function task.file.hasProperty('kanban-plugin') +filter by function task.file.property('project') === 'Project 1' +``` + +#### Use `#project/...` tag values + +Some people prefer to use properties tags to identify projects. One advantage of tags is it is easy to add multiple values. + +```yaml +--- +tags: + - project/project-1 +--- ``` -- find tasks in [Kanban Plugin](https://github.com/mgmeyers/obsidian-kanban) boards. +This exact-match search will find all tasks in such files: ```javascript -filter by function task.file.property("sample_list_property")?.length > 0 +filter by function task.file.property('tags').includes('#project/project-1') ``` -- find tasks in files where the list property 'sample_list_property' exists and has at least one list item. +If you wanted to use a sub-string search to find all tasks in files with any properties tag beginning `#project/` you could use [optional chaining (?.)](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Optional_chaining) and the [nullish coalescing operator (??)](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Nullish_coalescing) like this: ```javascript -filter by function task.file.property("sample_list_property")?.length === 0 +filter by function task.file.property('tags')?.join(',').includes('#project/') ?? false ``` -- find tasks in files where the list property 'sample_list_property' exists and has no list items. +Or you could use [template literals (Template strings)](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Template_literals) like this: ```javascript -filter by function task.file.property('creation date')?.includes('2024') ?? false +filter by function `${task.file.property('tags')}`.includes('#project/') ``` -- find tasks in files where the date property 'creation date' includes string '2024'. +### Using date values - +Obsidian supports [Date](https://help.obsidian.md/Editing+and+formatting/Properties#^date-time) and [Date & time](https://help.obsidian.md/Editing+and+formatting/Properties#^date-time) property values. -### More grouping examples +It stores them in the format shown in these examples: - +```yaml +--- +date: 2020-08-21 +time: 2020-08-21T10:30:00 +--- +``` + +Currently, Tasks does nothing special with these, seeing them as string values. + +#### Grouping by raw date values + +A `creation date` property might be used like this, to group tasks by the date their file was created, according to the stored property values: ```javascript group by function task.file.property('creation date') ?? 'no creation date' ``` -- group tasks by 'creation date' date property. +#### Formatting date values using Moment.js + +If you want to do date calculations on `Date` or `Date & time` values, you can use `window.moment(value)` to create a [Moment.js](https://momentjs.com) object. + +For example: ```javascript group by function \ const value = task.file.property('creation date'); \ - return value ? window.moment(value).format('MMMM') : 'no month' + return value ? window.moment(value).format('YYYY MMMM') : 'no date' +``` + +## How does Tasks interpret Frontmatter Properties? + +Consider a file with the following example properties (or "Frontmatter"): + + + +```yaml +--- +sample_checkbox_property: true +sample_date_property: 2024-07-21 +sample_date_and_time_property: 2024-07-21T12:37:00 +sample_list_property: + - Sample + - List + - Value +sample_number_property: 246 +sample_text_property: Sample Text Value +sample_text_multiline_property: | + Sample + Text + Value +sample_link_property: "[[yaml_all_property_types_populated]]" +sample_link_list_property: + - "[[yaml_all_property_types_populated]]" + - "[[yaml_all_property_types_empty]]" +aliases: + - YAML All Property Types Populated +tags: + - tag-from-file-properties +creation date: 2024-05-25T15:17:00 +project: Secret Project +--- ``` -- group tasks by month in 'creation date' date property. +The following table shows how most of those properties are interpreted in Tasks queries: + + + +| Field | Type 1 | Example 1 | +| ----- | ----- | ----- | +| `task.file.hasProperty('creation date')` | `boolean` | `true` | +| `task.file.property('creation date')` | `string` | `'2024-05-25T15:17:00'` | +| `task.file.property('sample_checkbox_property')` | `boolean` | `true` | +| `task.file.property('sample_date_property')` | `string` | `'2024-07-21'` | +| `task.file.property('sample_date_and_time_property')` | `string` | `'2024-07-21T12:37:00'` | +| `task.file.property('sample_list_property')` | `string[]` | `['Sample', 'List', 'Value']` | +| `task.file.property('sample_number_property')` | `number` | `246` | +| `task.file.property('sample_text_property')` | `string` | `'Sample Text Value'` | +| `task.file.property('sample_text_multiline_property')` | `string` | `'Sample\nText\nValue\n'` | +| `task.file.property('sample_link_property')` | `string` | `'[[yaml_all_property_types_populated]]'` | +| `task.file.property('sample_link_list_property')` | `string[]` | `['[[yaml_all_property_types_populated]]', '[[yaml_all_property_types_empty]]']` | +| `task.file.property('tags')` | `string[]` | `['#tag-from-file-properties']` | diff --git a/docs/Getting Started/Tags.md b/docs/Getting Started/Tags.md index 1284e3e1d5..0840ad79e9 100644 --- a/docs/Getting Started/Tags.md +++ b/docs/Getting Started/Tags.md @@ -68,9 +68,12 @@ tags: --- ``` -Tasks does not currently read this data. We are tracking this in [discussion #232](https://github.com/obsidian-tasks-group/obsidian-tasks/discussions/232). +Since Tasks X.Y.Z, Tasks **does** now read this data. -For now, see [Find tasks in notes with particular tag](https://github.com/obsidian-tasks-group/obsidian-tasks/blob/main/resources/sample_vaults/Tasks-Demo/How%20To/Find%20tasks%20in%20notes%20with%20particular%20tag.md) for a workaround using [[Dataview]] and Tasks together. +You can learn more in: + +- [[Frontmatter Properties]], and the examples in that file +- [Find tasks in notes with particular tag](https://github.com/obsidian-tasks-group/obsidian-tasks/blob/main/resources/sample_vaults/Tasks-Demo/How%20To/Find%20tasks%20in%20notes%20with%20particular%20tag.md). ### Order of tags in task lines diff --git a/docs/Quick Reference.md b/docs/Quick Reference.md index e4e474aa52..83dfe9cf63 100644 --- a/docs/Quick Reference.md +++ b/docs/Quick Reference.md @@ -44,6 +44,7 @@ This table summarizes the filters and other options available inside a `tasks` b | `filename (includes, does not include) `
`filename (regex matches, regex does not match) /regex/i`
`filename includes {{query.file.filename}}`
`filename includes {{query.file.filenameWithoutExtension}}` | `sort by filename` | `group by filename` | | `task.file.filename`
`task.file.filenameWithoutExtension`
`query.file.filename`
`query.file.filenameWithoutExtension` | | `heading (includes, does not include) `
`heading (regex matches, regex does not match) /regex/i` | `sort by heading` | `group by heading` | | `task.hasHeading`
`task.heading` | | | | `group by backlink` | `hide backlink` | | +| **[[Frontmatter Properties]]** | | | | `task.file.hasProperty('property name')`
`task.file.property('property name')` | | **[[Filters#Description\|Description]]**, **[[Filters#Tags\|Tags]]** and other odds and ends | | | | | | `description (includes, does not include) `
`description (regex matches, regex does not match) /regex/i` | `sort by description` | | | `task.description`
`task.descriptionWithoutTags` | | `has tags`
`no tags`
`tag (includes, does not include) `
`tags (include, do not include) `
`tag (regex matches, regex does not match) /regex/i`
`tags (regex matches, regex does not match) /regex/i` | `sort by tag`
`sort by tag ` | `group by tags` | `hide tags` | `task.tags` | diff --git a/docs/Scripting/Task Properties.md b/docs/Scripting/Task Properties.md index 4f60944832..24fa23dad5 100644 --- a/docs/Scripting/Task Properties.md +++ b/docs/Scripting/Task Properties.md @@ -205,7 +205,7 @@ For more information, including adding your own customised statuses, see [[Statu > [!released] > Access to the frontmatter properties was introduced in Tasks X.Y.Z. -These values are described in [[Frontmatter Properties]]. +These are described in full in [[Frontmatter Properties]]. @@ -225,3 +225,7 @@ These values are described in [[Frontmatter Properties]]. | `task.file.property('tags')` | `string[]` | `['#tag-from-file-properties']` | + +1. `task.file.hasProperty()` and `task.file.property()` were added in Tasks X.Y.Z +1. `task.file.hasProperty('property name')` returns true if the property `'property name'` is both present in the file and has a non-`null` value. +1. `task.file.property('property name')` returns either the value in the file, or `null` if there is no value. diff --git a/resources/sample_vaults/Tasks-Demo/How To/Access properties in frontmatter.md b/resources/sample_vaults/Tasks-Demo/How To/Access properties in frontmatter.md index 940c374907..f3d60e4a6c 100644 --- a/resources/sample_vaults/Tasks-Demo/How To/Access properties in frontmatter.md +++ b/resources/sample_vaults/Tasks-Demo/How To/Access properties in frontmatter.md @@ -1,15 +1,43 @@ # Access properties in frontmatter -## Accessing a custom property +These examples work with Tasks X.Y.Z and later. + +## Accessing a custom property for grouping + +Group by property `custom_number_prop`, with tasks from files without that property being un-grouped: ```tasks folder includes Test Data -group by function task.file.frontmatter.custom_number_prop ?? 'not set' +group by function task.file.property('custom_number_prop') + +limit groups 5 +``` + +Group by property `custom_number_prop`, with a fixed gorup name for tasks from files without that property: + +```tasks +folder includes Test Data +group by function task.file.property('custom_number_prop') ?? 'no "custom_number_prop" value' + +limit groups 5 ``` ## Accessing tags +Group by each tag property value, and tasks with more than one tag are listed multiple times: + +```tasks +folder includes Test Data +group by function task.file.property('tags') + +limit groups 5 +``` + +Group by tag property values, and tasks with more than one tag are listed only once + ```tasks folder includes Test Data -group by function task.file.frontmatter.tags +group by function task.file.property('tags').sort().join(', ') + +limit groups 5 ``` diff --git a/resources/sample_vaults/Tasks-Demo/How To/Find tasks in notes with particular tag.md b/resources/sample_vaults/Tasks-Demo/How To/Find tasks in notes with particular tag.md index c918098c9e..7168212a47 100644 --- a/resources/sample_vaults/Tasks-Demo/How To/Find tasks in notes with particular tag.md +++ b/resources/sample_vaults/Tasks-Demo/How To/Find tasks in notes with particular tag.md @@ -1,77 +1,9 @@ # Find tasks in notes with particular tag -Suppose we wanted to find all tasks in notes that had a particular tag in the frontmatter. +Suppose we wanted to find all tasks in notes that had a particular tag `#examples` in the frontmatter. -## Dataview approach - -This is not currently possible in Tasks directly, but we could get dataview to do the search for us and create a Tasks query. - -Note that the following finds all tasks where the tag is present anywhere in the file, not just in the frontmatter. - -```dataviewjs -const tag = '#examples' -const matching_files = dv.pagePaths(tag) -if ( matching_files.length > 0 ) { - const query = ` - not done - (path includes ${matching_files.join(') OR (path includes ')}) - - # you can add any number of extra Tasks instructions, for example: - group by path -`; - - dv.paragraph('```tasks\n' + query + '\n```'); -} else { - const message = `No files found with tag ${tag}` - dv.paragraph(message) -} -``` - -Credit: jonlemon in [this Obsidian Forum thread](https://forum.obsidian.md/t/how-can-i-list-tasks-from-all-notes-with-a-certain-tag-using-the-tasks-plugin/44634). - -## Tasks experimental approach - -### Tasks in files that have a Tag in frontmatter - -#### Presence of tag - in frontmatter - -```tasks -filter by function task.file.frontmatter.tags.includes('#examples') -``` - -### Tasks in files that have a Tag anywhere - in frontmatter or body - -#### Presence of tag - in frontmatter or body +Since Tasks X.Y.Z, this is now possible in Tasks queries: ```tasks -filter by function task.file.tags.includes('#examples') -``` - -#### Absence of tag - in frontmatter or body - -```tasks -filter by function ! task.file.tags.includes('#examples') - -limit 20 -``` - -#### Group by tags - in frontmatter or body - -```tasks -group by function task.file.tags -folder does not include Test Data - -limit groups 3 -limit 100 -``` - -#### Group by tags - in frontmatter or body, ignoring the global filter - -```tasks -# TODO Provide task.file.tagsWithoutGlobalFilter -group by function task.file.tags.filter( (t) => t !== '#task' ) -folder does not include Test Data - -limit groups 3 -limit 100 +filter by function task.file.property('tags').includes('#examples') ```