Repository: obsidian-tasks-group/obsidian-tasks
Pull request #1139 (2022-09-11 22:53:57)

feat: Add option to show urgency. More space before pencil icon.
<!--- Provide a general summary of your changes in the Title above -->

# Description

This PR adds an option that allows showing the urgency of each task (#1111).

It also extends the existing syntax in a backward compatible way: In addition to the command `hide`, you can now also use the command `show`. The urgency is hidden by default, so to show it, you need to use the command `show urgency`. If you use `show` with an options that is shown by default, or `hide` with an option that is hidden by default, this will ne a no-op.

This PR also slightly simplifies the HTML elements that are postponed to the task. Before, the edit button was inside the span for the backlink, which does not make much sense. The elements for the backlink, the edit button and the urgency score are now simply appended as childs of the existing list item, not nested into each other.

I also removed an unnecessary `trim()` and `toLowerCase()` when evaluating the options, since the used regex guarantees the strings to be lower case and trimmed already.

Let me know if I shall add tests as well and what would be the best way to do it.

Also, mabye the order of the edit button and the urgency score should be swapped, but that would be a very simple change.

## Motivation and Context

See #1111.

## How has this been tested?

I have been using the sample vault to check this out and added and removed `show urgency` to the queries to test.

## Screenshots

![sample-show-urgency](https://user-images.githubusercontent.com/464599/189551946-77fb1155-fbbd-4774-9802-f118bb27b1ce.png)

## Types of changes

<!--- What types of changes does your code introduce? Put an `x` in all the boxes that apply: -->

Changes visible to users:

- [ ] **Bug fix** (prefix: `fix` - non-breaking change which fixes an issue)
- [X] **New feature** (prefix: `feat` - non-breaking change which adds functionality)
- [ ] **Breaking change** (prefix: `feat!!` or `fix!!` - fix or feature that would cause existing functionality to not work as expected)
- [x] **Documentation** (prefix: `docs` - improvements to any documentation content)
- [ ] **Sample vault** (prefix: `vault` - improvements to the [Tasks-Demo sample vault](https://github.com/obsidian-tasks-group/obsidian-tasks/tree/main/resources/sample_vaults/Tasks-Demo))

Internal changes:

- [X] **Refactor** (prefix: `refactor` - non-breaking change which only improves the design or structure of existing code, and making no changes to its external behaviour)
- [ ] **Tests** (prefix: `test` - additions and improvements to unit tests and the smoke tests)
- [ ] **Infrastructure** (prefix: `chore` - examples include GitHub Actions, issue templates)

## 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! -->

- [X] My code follows the code style of this project and passes `yarn run lint`.
- [X] My change requires a change to the documentation.
- [X] I have [updated the documentation](https://github.com/obsidian-tasks-group/obsidian-tasks/blob/main/CONTRIBUTING.md#updating-documentation) accordingly.
- [ ] My change has adequate [Unit Test coverage](https://github.com/obsidian-tasks-group/obsidian-tasks/blob/main/CONTRIBUTING.md#maintaining-the-tests).

## Terms

<!--
By submitting this pull request, you must agree to follow our
[contributing guide](https://github.com/obsidian-tasks-group/obsidian-tasks/blob/main/CONTRIBUTING.md) and
[Code of Conduct](https://github.com/obsidian-tasks-group/obsidian-tasks/blob/main/CODE_OF_CONDUCT.md).
Put an x in the boxes to confirm you agree.
-->

- [X] My contribution follow this project's [contributing guide](https://github.com/obsidian-tasks-group/obsidian-tasks/blob/main/CONTRIBUTING.md)

- [X] I agree to follow this project's [Code of Conduct](https://github.com/obsidian-tasks-group/obsidian-tasks/blob/main/CODE_OF_CONDUCT.md)


claremacrae: Hi @Cito this is great - thank you very much indeed.

I won't have time for a full code review yet, but I did try it out in the Tasks-Demo vault, and have a few observations... 

## Presentation

1. Can the default styling be made so that it has more contrast in Light mod:

  ![1139 - feat - show urgency - unreadable in light mode](https://user-images.githubusercontent.com/4840096/189592472-f2a9b28f-b2fd-4525-a249-2c4ce7d492e5.png)

  - I think there is some accessibility score scheme for contrast in colours for readability - it would be good to satisfy that.

2. Can the score for done tasks be just as readable as the score for not-done tasks.

  ![1139 - feat - show urgency - done in light](https://user-images.githubusercontent.com/4840096/189592924-f5b4c747-737b-4f3e-b09a-870f04357e7f.png)

  ![1139 - feat - show urgency - done in dark](https://user-images.githubusercontent.com/4840096/189592947-4661f208-46be-49a1-9462-5103f8201173.png)

3. Please keep the pencil as the right-most thing on the line, and I think as backlinks vary so much in length, it would also be better if the score went before the backlink... so that it will be easier to locate the score with a glance.

Hahah - I see you thought much the same:

> Also, mabye the order of the edit button and the urgency score should be swapped, but that would be a very simple change.

## Docs

 >It also extends the existing syntax in a backward compatible way: In addition to the command `hide`, you can now also use the command `show`. The urgency is hidden by default, so to show it, you need to use the command `show urgency`. If you use `show` with an options that is shown by default, or `hide` with an option that is hidden by default, this will ne a no-op.

That is useful information for users - would be good to see that in the docs somehow.

It would also be good for the Quick Reference docs to be updated.

## HTML elements

> This PR also slightly simplifies the HTML elements that are postponed to the task. Before, the edit button was inside the span for the backlink, which does not make much sense. The elements for the backlink, the edit button and the urgency score are now simply appended as childs of the existing list item, not nested into each other.

Will the change in nesting of elements:

- affect any existing user CSS customisations in any observable way?
- affect the rendering speed?


## Tests

> Let me know if I shall add tests as well and what would be the best way to do it.

Yes please. There is a test - probably in Query.test.ts - that confirms that all the `hide` commands parse without error.

This should at a minimum have the new `show` command added - but better still would be for it to also test the opposite variants as well.


Cito: Thanks for the quick feedback @claremacrae - all very good points, I'll try to address them this week.
Cito: One thing I currently cannot reproduce is the missing score for "done" tasks. They are visible (stroke through) with both default light and dark themes, tested on Win and Mac. Are you using a different theme?
claremacrae: > One thing I currently cannot reproduce is the missing score for "done" tasks. They are visible (stroke through) with both default light and dark themes, tested on Win and Mac. Are you using a different theme?

Odd. I was using the demo vault, with light theme. On macOS but 2 or 3 year old OS. 
Cito: Strange, but I guess it will be fixed when I set an explicit color on that element which I want to do anyway to ensure the colors pass the [conrast checker](https://webaim.org/resources/contrastchecker/).
claremacrae: Oh sorry I’ve just seen that you did update the quick reference page. Thank you. 

I was presuming it would instead go on the same row in the big table as the sort by urgency instruction, to fill in a gap. 
Cito: @claremacrae I think I have addressed all your feedback now. Can you have another look?

Regarding rendering speed, I don't see how this should impact performance negatively. Of course, if the scores are shown, they all need to be calculated once, even if the lists are not sorted. But if the urgency scores are not shown, which is the default, there should be no difference. Not sure how I can measure rendering speed easily though.

Regarding CSS, I kept the existing class names and only added one new class `tasks-score` for the urgency score.
claremacrae: > @claremacrae I think I have addressed all your feedback now. Can you have another look?

Thank you for the improvements.

I will review it as soon as I can. Because of the CSS changes, it's going to take longer to review than for single-topic changes - including to figure out how to test the impact of the changes.
claremacrae: The scores look great now - thank you!

![image](https://user-images.githubusercontent.com/4840096/190878433-effed934-72ff-4443-827d-3bb324df4b5c.png)

![image](https://user-images.githubusercontent.com/4840096/190878438-2c1b849d-a9a7-4b9b-8b66-91da972c5951.png)


Patch:
diff --git a/docs/advanced/styling.md b/docs/advanced/styling.md
index e559b7d1..78ac7244 100644
--- a/docs/advanced/styling.md
+++ b/docs/advanced/styling.md
@@ -17,6 +17,7 @@ following styles are available.
 | plugin-tasks-list-item    | This is applied to the LI that holds each task and the INPUT element for it.                                    |
 | 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. |
+| tasks-urgency             | This is applied to the SPAN that wraps the urgency score if displayed on the task.                              |
 | 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                                                                 |
 
diff --git a/docs/advanced/urgency.md b/docs/advanced/urgency.md
index 9fb22cbd..16740de5 100644
--- a/docs/advanced/urgency.md
+++ b/docs/advanced/urgency.md
@@ -3,11 +3,23 @@ layout: default
 title: Urgency
 nav_order: 3
 parent: Advanced
-has_toc: false
 ---
 
 # Urgency
 
+{: .no_toc }
+
+<details open markdown="block">
+  <summary>
+    Table of contents
+  </summary>
+  {: .text-delta }
+1. TOC
+{:toc}
+</details>
+
+## Introduction
+
 By default, Tasks [sorts]({{ site.baseurl }}{% link queries/sorting.md %}) query results by decreasing urgency.
 Tasks tries to calculate urgency based on what you should likely work on next.
 
@@ -120,3 +132,7 @@ urgency = 0.0 + 6.0 + 5.0 + 0.0 = 11.0
 A task that has no due date, a "high" priority, is scheduled for tomorrow, and starts tomorrow:
 urgency = 0.0 + 6.0 + 0.0 - 3.0 = 3.0
 ```
+
+## How to Display the Urgency Score
+
+You can display the calculated Urgency score in your task list using the `show urgency` option.
diff --git a/docs/queries/layout.md b/docs/queries/layout.md
index c4e0d87a..29414e2e 100644
--- a/docs/queries/layout.md
+++ b/docs/queries/layout.md
@@ -5,7 +5,7 @@ nav_order: 6
 parent: Queries
 ---
 
-# Layout options
+# Layout commands
 
 {: .no_toc }
 
@@ -18,14 +18,16 @@ parent: Queries
 {:toc}
 </details>
 
-## Hiding Elements
+## Hiding/Showing Elements
 
-You can hide certain elements of the rendered list with the "hide" option.
+You can hide and show individual elements of the rendered list with the "hide" and "show" commands
+together with the name of the element.
 
-The following options exist:
+The following elements exist:
 
 - `edit button`
 - `backlink`
+- `urgency`
 - `priority`
 - `start date`
 - `scheduled date`
@@ -34,6 +36,13 @@ The following options exist:
 - `recurrence rule`
 - `task count`
 
+> `urgency` was introduced in Tasks 1.14.0.
+
+All of these elements except `urgency` are shown by default, so you will use the command `hide`
+if you do not want to show any of them, or the command `show` to show the urgency score.
+
+> The `show` commands were introduced in Tasks 1.14.0.
+
 Example:
 
     ```tasks
@@ -42,6 +51,7 @@ Example:
     hide recurrence rule
     hide task count
     hide backlink
+    show urgency
     ```
 
 ---
@@ -51,7 +61,7 @@ Example:
 In short mode, query results will only show the emojis, but not the concrete recurrence rule or dates.
 You can hover over the task to see the rule and dates in a tooltip.
 
-The option is `short mode`.
+The command is `short mode`.
 
 Example:
 
diff --git a/docs/quick-reference/index.md b/docs/quick-reference/index.md
index 363f17c1..62ff8059 100644
--- a/docs/quick-reference/index.md
+++ b/docs/quick-reference/index.md
@@ -25,7 +25,7 @@ This table summarizes the filters and other options available inside a `tasks` b
 | `is recurring`<br>`is not recurring`                                                                                                                                                                |                                             | `group by recurring`  |                        |
 |                                                                                                                                                                                                     |                                             | `group by recurrence` | `hide recurrence rule` |
 | `priority is (above, below)? (low, none, medium, high)`                                                                                                                                             | `sort by priority`                          | `group by priority`   | `hide priority`        |
-|                                                                                                                                                                                                     | `sort by urgency`                           |                       |                        |
+|                                                                                                                                                                                                     | `sort by urgency`                           |                       | `show urgency`         |
 | `path (includes, does not include) <path>`<br>`path (regex matches, regex does not match) /regex/i`                                                                                                 | `sort by path`                              | `group by path`       |                        |
 |                                                                                                                                                                                                     |                                             | `group by root`       |                        |
 |                                                                                                                                                                                                     |                                             | `group by folder`     |                        |
diff --git a/resources/sample_vaults/Tasks-Demo/.obsidian/plugins/obsidian-tasks-plugin/styles.css b/resources/sample_vaults/Tasks-Demo/.obsidian/plugins/obsidian-tasks-plugin/styles.css
index 4d988c1d..58713852 100644
--- a/resources/sample_vaults/Tasks-Demo/.obsidian/plugins/obsidian-tasks-plugin/styles.css
+++ b/resources/sample_vaults/Tasks-Demo/.obsidian/plugins/obsidian-tasks-plugin/styles.css
@@ -13,10 +13,22 @@
     width: 1em;
     height: 1em;
     vertical-align: middle;
-    margin-left: 0.3em;
+    margin-left: 0.5em;
     cursor: pointer;
 }
 
+/* Urgency score */
+.tasks-urgency {
+    font-size: var(--font-ui-smaller);
+    font-family: var(--font-interface);
+    padding: 2px 6px;
+    border-radius: var(--radius-s);
+    color: var(--text-normal);
+    background-color: var(--background-secondary);
+    margin-left: 0.5em;
+    line-height: 1;
+}
+
 .internal-link.internal-link-short-mode {
     text-decoration: none;
 }
diff --git a/src/LayoutOptions.ts b/src/LayoutOptions.ts
index c16873b0..cf0660e5 100644
--- a/src/LayoutOptions.ts
+++ b/src/LayoutOptions.ts
@@ -8,5 +8,6 @@ export class LayoutOptions {
     hideDueDate: boolean = false;
     hideRecurrenceRule: boolean = false;
     hideEditButton: boolean = false;
+    hideUrgency: boolean = true;
     shortMode: boolean = false;
 }
diff --git a/src/Query/Query.ts b/src/Query/Query.ts
index 68449a20..5b84b8c8 100644
--- a/src/Query/Query.ts
+++ b/src/Query/Query.ts
@@ -61,7 +61,7 @@ export class Query implements IQuery {
         /^group by (backlink|done|due|filename|folder|happens|heading|path|priority|recurrence|recurring|root|scheduled|start|status|tags)/;
 
     private readonly hideOptionsRegexp =
-        /^hide (task count|backlink|priority|start date|scheduled date|done date|due date|recurrence rule|edit button)/;
+        /^(hide|show) (task count|backlink|priority|start date|scheduled date|done date|due date|recurrence rule|edit button|urgency)/;
     private readonly shortModeRegexp = /^short/;
 
     private readonly limitRegexp = /^limit (to )?(\d+)( tasks?)?/;
@@ -139,38 +139,42 @@ export class Query implements IQuery {
     private parseHideOptions({ line }: { line: string }): void {
         const hideOptionsMatch = line.match(this.hideOptionsRegexp);
         if (hideOptionsMatch !== null) {
-            const option = hideOptionsMatch[1].trim().toLowerCase();
+            const hide = hideOptionsMatch[1] === 'hide';
+            const option = hideOptionsMatch[2];
 
             switch (option) {
                 case 'task count':
-                    this._layoutOptions.hideTaskCount = true;
+                    this._layoutOptions.hideTaskCount = hide;
                     break;
                 case 'backlink':
-                    this._layoutOptions.hideBacklinks = true;
+                    this._layoutOptions.hideBacklinks = hide;
                     break;
                 case 'priority':
-                    this._layoutOptions.hidePriority = true;
+                    this._layoutOptions.hidePriority = hide;
                     break;
                 case 'start date':
-                    this._layoutOptions.hideStartDate = true;
+                    this._layoutOptions.hideStartDate = hide;
                     break;
                 case 'scheduled date':
-                    this._layoutOptions.hideScheduledDate = true;
+                    this._layoutOptions.hideScheduledDate = hide;
                     break;
                 case 'due date':
-                    this._layoutOptions.hideDueDate = true;
+                    this._layoutOptions.hideDueDate = hide;
                     break;
                 case 'done date':
-                    this._layoutOptions.hideDoneDate = true;
+                    this._layoutOptions.hideDoneDate = hide;
                     break;
                 case 'recurrence rule':
-                    this._layoutOptions.hideRecurrenceRule = true;
+                    this._layoutOptions.hideRecurrenceRule = hide;
                     break;
                 case 'edit button':
-                    this._layoutOptions.hideEditButton = true;
+                    this._layoutOptions.hideEditButton = hide;
+                    break;
+                case 'urgency':
+                    this._layoutOptions.hideUrgency = hide;
                     break;
                 default:
-                    this._error = 'do not understand hide option';
+                    this._error = 'do not understand hide/show option';
             }
         }
     }
diff --git a/src/QueryRenderer.ts b/src/QueryRenderer.ts
index f849ace2..67c48065 100644
--- a/src/QueryRenderer.ts
+++ b/src/QueryRenderer.ts
@@ -186,15 +186,18 @@ class QueryRenderChild extends MarkdownRenderChild {
             const footnotes = listItem.querySelectorAll('[data-footnote-id]');
             footnotes.forEach((footnote) => footnote.remove());
 
-            const postInfo = listItem.createSpan();
             const shortMode = this.query.layoutOptions.shortMode;
 
+            if (!this.query.layoutOptions.hideUrgency) {
+                this.addUrgency(listItem, task);
+            }
+
             if (!this.query.layoutOptions.hideBacklinks) {
-                this.addBacklinks(postInfo, task, shortMode, isFilenameUnique);
+                this.addBacklinks(listItem, task, shortMode, isFilenameUnique);
             }
 
             if (!this.query.layoutOptions.hideEditButton) {
-                this.addEditButton(postInfo, task);
+                this.addEditButton(listItem, task);
             }
 
             taskList.appendChild(listItem);
@@ -203,8 +206,8 @@ class QueryRenderChild extends MarkdownRenderChild {
         return { taskList, tasksCount };
     }
 
-    private addEditButton(postInfo: HTMLSpanElement, task: Task) {
-        const editTaskPencil = postInfo.createEl('a', {
+    private addEditButton(listItem: HTMLLIElement, task: Task) {
+        const editTaskPencil = listItem.createEl('a', {
             cls: 'tasks-edit',
         });
         editTaskPencil.onClickEvent((event: MouseEvent) => {
@@ -227,6 +230,11 @@ class QueryRenderChild extends MarkdownRenderChild {
         });
     }
 
+    private addUrgency(listItem: HTMLLIElement, task: Task) {
+        const text = new Intl.NumberFormat().format(task.urgency);
+        listItem.createSpan({ text, cls: 'tasks-urgency' });
+    }
+
     /**
      * Display headings for a group of tasks.
      * @param content
@@ -263,16 +271,18 @@ class QueryRenderChild extends MarkdownRenderChild {
     }
 
     private addBacklinks(
-        postInfo: HTMLSpanElement,
+        listItem: HTMLLIElement,
         task: Task,
         shortMode: boolean,
         isFilenameUnique: boolean | undefined,
     ) {
-        postInfo.addClass('tasks-backlink');
+        const backLink = listItem.createSpan({ cls: 'tasks-backlink' });
+
         if (!shortMode) {
-            postInfo.append(' (');
+            backLink.append(' (');
         }
-        const link = postInfo.createEl('a');
+
+        const link = backLink.createEl('a');
 
         link.href = task.path;
         link.setAttribute('data-href', task.path);
@@ -297,8 +307,9 @@ class QueryRenderChild extends MarkdownRenderChild {
         }
 
         link.setText(linkText);
+
         if (!shortMode) {
-            postInfo.append(')');
+            backLink.append(')');
         }
     }
 
diff --git a/styles.css b/styles.css
index 7ca54271..95157cf5 100644
--- a/styles.css
+++ b/styles.css
@@ -13,10 +13,22 @@
     width: 1em;
     height: 1em;
     vertical-align: middle;
-    margin-left: 0.3em;
+    margin-left: 0.5em;
     cursor: pointer;
 }
 
+/* Urgency score */
+.tasks-urgency {
+    font-size: var(--font-ui-smaller);
+    font-family: var(--font-interface);
+    padding: 2px 6px;
+    border-radius: var(--radius-s);
+    color: var(--text-normal);
+    background-color: var(--background-secondary);
+    margin-left: 0.5em;
+    line-height: 1;
+}
+
 .internal-link.internal-link-short-mode {
     text-decoration: none;
 }


Test patch:
diff --git a/tests/Query.test.ts b/tests/Query.test.ts
index ccd2de60..9d13449f 100644
--- a/tests/Query.test.ts
+++ b/tests/Query.test.ts
@@ -197,10 +197,21 @@ describe('Query parsing', () => {
             'hide scheduled date',
             'hide start date',
             'hide task count',
+            'hide urgency',
             'limit 42',
             'limit to 42 tasks',
             'short mode',
             'short',
+            'show backlink',
+            'show done date',
+            'show due date',
+            'show edit button',
+            'show priority',
+            'show recurrence rule',
+            'show scheduled date',
+            'show start date',
+            'show task count',
+            'show urgency',
         ];
         test.concurrent.each<string>(filters)('recognises %j', (filter) => {
             // Arrange