Skip to content

Commit

Permalink
Merge commit 'ff1216477242401139fee2031b28ebec9422334c' into sytone/i…
Browse files Browse the repository at this point in the history
…ssue666
  • Loading branch information
sytone committed May 22, 2022
2 parents d5976ba + ff12164 commit 292f684
Show file tree
Hide file tree
Showing 23 changed files with 1,226 additions and 29 deletions.
31 changes: 31 additions & 0 deletions .github/PULL_REQUEST_TEMPLATE/pull_request_template.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
<!--- Provide a general summary of your changes in the Title above -->

## Description
<!--- Describe your changes in detail -->

## Motivation and Context
<!--- Why is this change required? What problem does it solve? -->
<!--- If it fixes an open issue, please link to the issue here. -->

## How has this been tested?
<!--- Please describe in detail how you tested your changes. -->
<!--- Include details of your testing environment, tests ran to see how -->
<!--- your change affects other areas of the code, etc. -->

## Screenshots (if appropriate):

## Types of changes
<!--- What types of changes does your code introduce? Put an `x` in all the boxes that apply: -->
- [ ] Bug fix (non-breaking change which fixes an issue)
- [ ] New feature (non-breaking change which adds functionality)
- [ ] Breaking change (fix or feature that would cause existing functionality to not work as expected)

## Checklist:
<!--- Go over all the following points, and put an `x` in all the boxes that apply. -->
<!--- If you're unsure about any of these, don't hesitate to ask. We're here to help! -->
- [ ] My code follows the code style of this project and passes `yarn run lint`.
- [ ] My change requires a change to the documentation.
- [ ] I have updated the documentation accordingly.
- [ ] My change has adequate Unit Test coverage.

By creating a Pull Request you agree to our [Code of Conduct](https://github.com/schemar/obsidian-tasks/blob/main/CODE_OF_CONDUCT.md). For further guidance on contributing please see [contributing guide](https://github.com/schemar/obsidian-tasks/blob/main/CONTRIBUTING.md)
27 changes: 27 additions & 0 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,33 @@ Discussion will take place inside the PR.

If you can, please add/update tests and documentation where appropriate.

## Maintaining the tests

The tests use the [ts-jest](https://www.npmjs.com/package/ts-jest) wrapper around the
[jest](https://jestjs.io) test framework.

The [Expect](https://jestjs.io/docs/expect) page is a good reference for the many jest testing features.

### Snapshot Tests

For testing more complex objects, some of the tests here use Jest's
[Snapshot Testing](https://jestjs.io/docs/snapshot-testing) facility, which is similar to
[Approval Tests](https://approvaltests.com) but easier to use in JavaScript.

For readability of snapshots, we favour [Inline Snapshots](https://jestjs.io/docs/snapshot-testing#inline-snapshots),
which are saved in the source code. See that documentation for how to easily update the inline
snapshot, if the output is intended to be changed.

### Jest and the WebStorm IDE

The WebStorm IDE has a [helpful page](https://www.jetbrains.com/help/webstorm/running-unit-tests-on-jest.html)
on how it makes testing with jest easy.

Note in particular the
[Snapshot testing section](https://www.jetbrains.com/help/webstorm/running-unit-tests-on-jest.html#ws_jest_snapshot_testing)
for how to view the diffs in the event of snapshot test failures, and also how to update the saved snapshot
of one or all snapshot failures.

## Setting up build environment

This project uses Node 14.x, if you need to use a different version, look at using `nvm` to manage your Node versions. If you are using `nvm`, you can install the 14.x version of Node with `nvm install 14.19.1; nvm use 14.19.1`.
Expand Down
4 changes: 2 additions & 2 deletions docs/Gemfile.lock
Original file line number Diff line number Diff line change
Expand Up @@ -226,10 +226,10 @@ GEM
jekyll-seo-tag (~> 2.1)
minitest (5.14.4)
multipart-post (2.1.1)
nokogiri (1.13.4)
nokogiri (1.13.6)
mini_portile2 (~> 2.8.0)
racc (~> 1.4)
nokogiri (1.13.4-x86_64-linux)
nokogiri (1.13.6-x86_64-linux)
racc (~> 1.4)
octokit (4.21.0)
faraday (>= 0.9)
Expand Down
5 changes: 3 additions & 2 deletions docs/advanced/styling.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,8 @@ has_toc: false

# Styling Tasks

Each task entry has CSS styles that allow you to change the look and feel of how the tasks are displayed. The
following styles are available.
Each task entry has CSS styles that allow you to change the look and feel of how the tasks are displayed. The
following styles are available.

| Class | Usage |
| ------------------------ | -------------------------------------------------------------------------------------------------------------- |
Expand All @@ -18,4 +18,5 @@ following styles are available.
| tasks-backlink | This is applied to the SPAN that wraps the backlink if displayed on the task. |
| tasks-edit | This is applied to the SPAN that wraps the edit button/icon shown next to the task that opens the task edit UI.|
| task-list-item-checkbox | This is applied to the INPUT element for the task. |
| tasks-group-heading | This is applied to H4, H5 and H6 group headings |

2 changes: 1 addition & 1 deletion docs/queries/comments.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
---
layout: default
title: Comments
nav_order: 5
nav_order: 6
parent: Queries
has_toc: false
---
Expand Down
14 changes: 13 additions & 1 deletion docs/queries/examples.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
---
layout: default
title: Examples
nav_order: 6
nav_order: 7
parent: Queries
has_toc: false
---
Expand Down Expand Up @@ -74,3 +74,15 @@ Show one task that is due on the 5th of May and includes `#prio1` in its descrip
description includes #prio1
limit to 1 tasks
```

---

All open tasks that are due today or earlier, sorted by due date, then grouped together by the folder containing the task:

```tasks
not done
due before tomorrow
sort by due
group by folder
```

110 changes: 110 additions & 0 deletions docs/queries/grouping.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
---
layout: default
title: Grouping
nav_order: 3
parent: Queries
---

# Grouping
{: .no_toc }

<details open markdown="block">
<summary>
Table of contents
</summary>
{: .text-delta }
1. TOC
{:toc}
</details>

---

## Basics

By default, Tasks displays tasks in a single list.

To divide the matching tasks up with headings, you can add `group by` lines to the query.

### Available grouping properties

You can group by the following properties:

File locations:

1. `path` (the path to the file that contains the task, that is, the folder and the filename)
1. `folder` (the folder to the file that contains the task, which will be `/` for files in root of the vault)
1. `filename` (the filename of the file that contains the task, without the `.md` extension)
* Note that tasks from different notes with the same file name will be grouped together in the same group.

File contents:

1. `backlink` (the text that would be shown in the task's backlink, combining the task's file name and heading, but with no link added)
1. `heading` (the heading preceding the task, or `(No heading)` if there are no headings in the file)

Task properties:

1. `status` (Done or Todo, which is capitalized for visibility in the headings)
* Note that the Done group is displayed before the Todo group,
which differs from the Sorting ordering of this property.

### Multiple groups

You can add multiple `group by` query options, each on an extra line.
This will create nested groups.
The first group has the highest priority.

Each subsequent `group by` will generate a new heading-level within the existing grouping:

- First `group by` is displayed as `h4` headings
- Second `group by` is displayed as `h5` headings
- Third and subsequent `group by` are displayed as `h6` headings

See the [screenshots below](#screenshots) for how this looks in practice.

<div class="code-example" markdown="1">
Info
{: .label .label-blue }
Headings are displayed in case-sensitive alphabetical order, not the original order.

---

Info
{: .label .label-blue }
The order of operations ensures that grouping does not modify which tasks are displayed, for example when the `limit` option is used:

1. all the filter instructions are run
1. then any sorting instructions are run
1. then any `limit` instructions are run
1. then finally any grouping instructions are run

</div>


---

## Screenshots

### Before

Here is an example Tasks result, without any `group by` commands:

![Tasks Ungrouped](https://github.com/schemar/obsidian-tasks/raw/main/resources/screenshots/tasks_ungrouped.png)
Tasks not grouped.

### After

And here is what this might look like, when grouped by folder, filename and heading:

![Tasks Grouped](https://github.com/schemar/obsidian-tasks/raw/main/resources/screenshots/tasks_grouped.png)
Tasks grouped.

---

## Examples

```tasks
not done
group by folder
group by filename
group by heading
```
2 changes: 1 addition & 1 deletion docs/queries/layout.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
---
layout: default
title: Layout
nav_order: 3
nav_order: 4
parent: Queries
---

Expand Down
2 changes: 1 addition & 1 deletion docs/queries/limit.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
---
layout: default
title: Limiting
nav_order: 4
nav_order: 5
parent: Queries
has_toc: false
---
Expand Down
Binary file added resources/screenshots/tasks_grouped.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added resources/screenshots/tasks_ungrouped.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
40 changes: 37 additions & 3 deletions src/Query.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
import * as chrono from 'chrono-node';
import { Group } from './Query/Group';
import type { TaskGroups } from './Query/TaskGroups';

import { getSettings } from './Settings';
import { LayoutOptions } from './LayoutOptions';
Expand All @@ -24,12 +26,22 @@ type Sorting = {
propertyInstance: number;
};

export type GroupingProperty =
| 'backlink'
| 'filename'
| 'folder'
| 'heading'
| 'path'
| 'status';
export type Grouping = { property: GroupingProperty };

export class Query {
private _limit: number | undefined = undefined;
private _layoutOptions: LayoutOptions = new LayoutOptions();
private _filters: ((task: Task) => boolean)[] = [];
private _error: string | undefined = undefined;
private _sorting: Sorting[] = [];
private _grouping: Grouping[] = [];

private readonly priorityRegexp =
/^priority (is )?(above|below)? ?(low|none|medium|high)/;
Expand Down Expand Up @@ -67,6 +79,9 @@ export class Query {
private readonly sortByRegexp =
/^sort by (urgency|status|priority|start|scheduled|due|done|path|description|tag)( reverse)?[\s]*(\d+)?/;

private readonly groupByRegexp =
/^group by (backlink|filename|folder|heading|path|status)/;

private readonly headingRegexp =
/^heading (includes|does not include) (.*)/;

Expand Down Expand Up @@ -173,14 +188,17 @@ export class Query {
case this.sortByRegexp.test(line):
this.parseSortBy({ line });
break;
case this.groupByRegexp.test(line):
this.parseGroupBy({ line });
break;
case this.hideOptionsRegexp.test(line):
this.parseHideOptions({ line });
break;
case this.commentRegexp.test(line):
// Comment lines are ignored
break;
default:
this._error = 'do not understand query';
this._error = `do not understand query: ${line}`;
}
});
}
Expand All @@ -201,16 +219,21 @@ export class Query {
return this._sorting;
}

public get grouping() {
return this._grouping;
}

public get error(): string | undefined {
return this._error;
}

public applyQueryToTasks(tasks: Task[]): Task[] {
public applyQueryToTasks(tasks: Task[]): TaskGroups {
this.filters.forEach((filter) => {
tasks = tasks.filter(filter);
});

return Sort.by(this, tasks).slice(0, this.limit);
const tasksSortedLimited = Sort.by(this, tasks).slice(0, this.limit);
return Group.by(this.grouping, tasksSortedLimited);
}

private parseHideOptions({ line }: { line: string }): void {
Expand Down Expand Up @@ -643,6 +666,17 @@ export class Query {
}
}

private parseGroupBy({ line }: { line: string }): void {
const fieldMatch = line.match(this.groupByRegexp);
if (fieldMatch !== null) {
this._grouping.push({
property: fieldMatch[1] as GroupingProperty,
});
} else {
this._error = 'do not understand query grouping';
}
}

private static parseDate(input: string): moment.Moment {
// Using start of day to correctly match on comparison with other dates (like equality).
return window.moment(chrono.parseDate(input)).startOf('day');
Expand Down
Loading

0 comments on commit 292f684

Please sign in to comment.