Skip to content

Commit

Permalink
Add support for controlling metadata rendering in the query (#278)
Browse files Browse the repository at this point in the history
* refactor query & parsing

Refactor query parsing to be more reusable and structured. Also convert
the sorting to an enum to make it easier to use.

* add 'show' to query definition and parser

This enum will be define which elements of the task to render in a given
query. By default, we render all of them (due date, tags, project,
description).

* make query available via Svelte context

This means we can avoid prop-drilling it

* use query show parameters in task renderer

This allows finer grained control of what gets rendered in the task list

* misc bugfix around datetime parsing

If there was no datetime, then some of the parsing would fail

* update readme & changelog
  • Loading branch information
jamiebrynes7 authored Feb 1, 2024
1 parent 6edcde6 commit 91aeb03
Show file tree
Hide file tree
Showing 15 changed files with 405 additions and 205 deletions.
5 changes: 5 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,12 +9,17 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

> Note: the style changes in this release mean that you may need to tweak any custom CSS or themes. The changes are based on the default theme.
### Deprecated

- Please note that the "Render Descriptions", "Render Dates", "Render project & section", and "Render labels" settings are now deprecated. You can now control whether these render or not via the `show` property in the query itself. These will be removed in a future release.

### ✨ Features

- Added the option to wrap page links in parenthesis when creating tasks with the command. You may find this useful if you primarily use Todoist on mobile where links are less obvious. Thanks to [@ThDag](https://github.com/ThDag) for the contribution!
- You can now use the `{{filename}}` placeholder in the filter property. This will be replaced with the name of the file where the query is defined.
- For example, if the full file path is `My Vault/Notes/How to Take Smart Notes.md` then the replaced file name will be `How to Take Smart Notes`.
- Added "Add item" button to rendered queries. This will open the task creation modal.
- Added the ability to control which elements of the task metadata render with tasks inside the query with the `show` keyword.

### 🔁 Changes

Expand Down
29 changes: 21 additions & 8 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,13 +26,14 @@ An [Obsidian](https://obsidian.md/) plugin to materialize [Todoist](https://todo

The query is defined in YAML and accepts the following keys:

| Name | Required | Description | Type | Default |
| ------------- | :------: | ------------------------------------------------------------------------------------------------------------------------------------------------------ | -------- | ------- |
| `name` || The title for the materialized query. You can use the `{task_count}` template which will be replaced by the number of tasks returned by the query. | string | |
| `filter` || A valid [Todoist filter](https://get.todoist.help/hc/en-us/articles/205248842-Filters)<sup>[1](#footnote-1)</sup> | string | |
| `autorefresh` | | The number of seconds between auto-refreshing. If omitted, the query use the default global settings. | number | null |
| `sorting` | | Describes how to order the tasks in the query. Can be any of 'priority', 'dateAscending' (aliased as 'date'), 'dateDescending', or multiple of these. | string[] | [] |
| `group` | | Denotes whether this query should have its task grouped by project & section. | bool | false |
| Name | Required | Description | Type | Default |
| ------------- | :------: | ----------------------------------------------------------------------------------------------------------------------------------------------------- | -------- | ------------ |
| `name` || The title for the materialized query. You can use the `{task_count}` template which will be replaced by the number of tasks returned by the query. | string | |
| `filter` || A valid [Todoist filter](https://get.todoist.help/hc/en-us/articles/205248842-Filters)<sup>[1](#footnote-1)</sup> | string | |
| `autorefresh` | | The number of seconds between auto-refreshing. If omitted, the query use the default global settings. | number | null |
| `sorting` | | Describes how to order the tasks in the query. Can be any of 'priority', 'dateAscending' (aliased as 'date'), 'dateDescending', or multiple of these. | string[] | [] |
| `group` | | Denotes whether this query should have its task grouped by project & section. | bool | false |
| `show` | | Describes which elements of the task metadata to render. Possible values: 'due' (aliased as 'date'), 'description', 'project, and 'labels'. | string[] | All metadata |

## Examples

Expand All @@ -42,7 +43,7 @@ Show current and overdue tasks, ordered by date and then priority, and finally g
```todoist
name: Highest Priority & Date
filter: "today | overdue"
sorting:
sorting:
- date
- priority
group: true
Expand All @@ -58,6 +59,18 @@ filter: "#Inbox"
```
````

Show items from the "Chores" project, showing only due date and description:

````
```todoist
name: Chores
filter: "#Chores"
show:
- due
- description
```
````

## Commands

There are also a few commands bundled in with the plugin:
Expand Down
1 change: 0 additions & 1 deletion src/api/domain/dueDate.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,6 @@ export function getDueDateInfo(dueDate: DueDate | undefined): DueDateInfo {
const isToday = date.isSame(new Date(), "day");
const isOverdue = hasTime ? date.isBefore() : date.clone().add(1, "day").isBefore()


return {
hasDate: true,
hasTime: hasTime,
Expand Down
2 changes: 1 addition & 1 deletion src/data/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -111,7 +111,7 @@ export class TodoistAdapter {
labels: apiTask.labels,
priority: apiTask.priority,

due: apiTask.due,
due: apiTask.due ?? undefined,
order: apiTask.order
};
}
Expand Down
18 changes: 9 additions & 9 deletions src/data/transformations.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import type { Task } from "./task";
import type { Project } from "../api/domain/project";
import { UnknownProject, groupByProject, type GroupedTasks, sortTasks, type TaskTree, buildTaskTree } from "./transformations";
import { assert } from "chai";
import type { Sort } from "../query/query";
import { SortingVariant } from "../query/query";

function makeTask(id: string, opts?: Partial<Task>): Task {
return {
Expand Down Expand Up @@ -107,15 +107,15 @@ describe("sortTasks", () => {
type Testcase = {
description: string,
input: Task[],
sortingOpts: Sort[],
sortingOpts: SortingVariant[],
expectedOutput: Task[],
};

const testcases: Testcase[] = [
{
description: "should not error for empty input",
input: [],
sortingOpts: ["priority"],
sortingOpts: [SortingVariant.Priority],
expectedOutput: [],
},
{
Expand All @@ -125,7 +125,7 @@ describe("sortTasks", () => {
makeTask("b", { priority: 1 }),
makeTask("c", { priority: 4 }),
],
sortingOpts: ["priority"],
sortingOpts: [SortingVariant.Priority],
expectedOutput: [
makeTask("c", { priority: 4 }),
makeTask("a", { priority: 2 }),
Expand All @@ -139,7 +139,7 @@ describe("sortTasks", () => {
makeTask("b", { priority: 1 }),
makeTask("c", { priority: 4 }),
],
sortingOpts: ["priorityDescending"],
sortingOpts: [SortingVariant.PriorityDescending],
expectedOutput: [
makeTask("b", { priority: 1 }),
makeTask("a", { priority: 2 }),
Expand All @@ -153,7 +153,7 @@ describe("sortTasks", () => {
makeTask("b", { order: 3 }),
makeTask("c", { order: 1 }),
],
sortingOpts: ["order"],
sortingOpts: [SortingVariant.Order],
expectedOutput: [
makeTask("c", { order: 1 }),
makeTask("a", { order: 2 }),
Expand Down Expand Up @@ -191,7 +191,7 @@ describe("sortTasks", () => {
},
}),
],
sortingOpts: ["date"],
sortingOpts: [SortingVariant.Date],
expectedOutput: [
makeTask("e", {
due: {
Expand Down Expand Up @@ -253,7 +253,7 @@ describe("sortTasks", () => {
}),
makeTask("a"),
],
sortingOpts: ["dateDescending"],
sortingOpts: [SortingVariant.DateDescending],
expectedOutput: [
makeTask("a"),
makeTask("b", {
Expand Down Expand Up @@ -291,7 +291,7 @@ describe("sortTasks", () => {
makeTask("b", { priority: 2, due: { recurring: false, date: "2020-03-19" } }),
makeTask("c", { priority: 3, due: { recurring: false, date: "2020-03-25" } }),
],
sortingOpts: ["priority", "date"],
sortingOpts: [SortingVariant.Priority, SortingVariant.Date],
expectedOutput: [
makeTask("c", { priority: 3, due: { recurring: false, date: "2020-03-25" } }),
makeTask("b", { priority: 2, due: { recurring: false, date: "2020-03-19" } }),
Expand Down
18 changes: 8 additions & 10 deletions src/data/transformations.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { getDueDateInfo } from "../api/domain/dueDate";
import type { Project } from "../api/domain/project";
import type { TaskId } from "../api/domain/task";
import type { Sort } from "../query/query";
import { SortingVariant } from "../query/query";
import type { Task } from "./task";

export const UnknownProject: Project = {
Expand Down Expand Up @@ -33,7 +33,7 @@ export function groupByProject(tasks: Task[]): GroupedTasks[] {
return Array.from(projects.entries()).map(([project, tasks]) => { return { project: project, tasks: tasks }; })
}

export function sortTasks<T extends Task>(tasks: T[], sort: Sort[]) {
export function sortTasks<T extends Task>(tasks: T[], sort: SortingVariant[]) {
tasks.sort((first, second) => {
for (const sorting of sort) {
const cmp = compareTask(first, second, sorting);
Expand All @@ -51,20 +51,18 @@ export function sortTasks<T extends Task>(tasks: T[], sort: Sort[]) {
// Result of "LT zero" means that self is before other,
// Result of '0' means that they are equal
// Result of "GT zero" means that self is after other
function compareTask<T extends Task>(self: T, other: T, sorting: Sort): number {
function compareTask<T extends Task>(self: T, other: T, sorting: SortingVariant): number {
switch (sorting) {
case "priority":
case "priorityAscending":
case SortingVariant.Priority:
// Note that priority in the API is reversed to that of in the app.
return other.priority - self.priority;
case "priorityDescending":
case SortingVariant.PriorityDescending:
return self.priority - other.priority;
case "date":
case "dateAscending":
case SortingVariant.Date:
return compareTaskDate(self, other);
case "dateDescending":
case SortingVariant.DateDescending:
return -compareTaskDate(self, other);
case "order":
case SortingVariant.Order:
return self.order - other.order;
default:
throw new Error(`Unexpected sorting type: '${sorting}'`)
Expand Down
Loading

0 comments on commit 91aeb03

Please sign in to comment.