Skip to content

Commit 81ed8d8

Browse files
fix: find tasks in blockquotes and Obsidian callouts (#953)
* fix: find tasks in blockquotes and Obsidian callouts * fix: Update comment about changed regex * fix: CreateOrEdit should treat blockquotes or callouts the same as fromLine * test: Add indentation to initial blockquote parsing test * fix: More regexes needed updating. * fix: attempt at logical definition of subitems inside blockquote or callout * Temporarily change the last test in Query.test.ts to put input in nested blockquote * fix: typo, since I am reading. No functionality change * Undo change to Query.test.ts * fix: Properly detect sections of tasks inside callouts or blockquotes in Cache * fix: Add warning to LivePreview that clicking checkbox inside callout not supported * docs: Document this feature and LP caveat * test: Update Smoke Test to test tasks in callouts and blockquotes. * fix: Better explanation in comments about LP issues. * Add some tests with * as listmarker instead of - * fix: Lengthen Notice in LP for callouts from 3s to 30s * fix: Wording in Smoke Test file Commit from the web editor to avoid lefthook issues due to space in filename. Signed-off-by: Anna Kornfeld Simpson <AnnaKornfeldSimpson@users.noreply.github.com> * fix: remove change to cache that is unrelated to callouts * fix: Clearer warning notice in LP about callouts
1 parent bf74189 commit 81ed8d8

File tree

12 files changed

+146
-22
lines changed

12 files changed

+146
-22
lines changed

docs/getting-started/index.md

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,22 @@ The following _does not work:_
7878

7979
---
8080

81+
Warning
82+
{: .label .label-yellow}
83+
Tasks can read tasks that are inside blockquotes or [Obsidian's built-in callouts](https://help.obsidian.md/How+to/Use+callouts).
84+
However, in the Live Preview editor mode (pencil icon in lower right corner), tasks written inside callouts cannot be completed by clicking the checkbox.
85+
You will see a warning; use the command `Tasks: Toggle Done`, or switch to Reading View (book icon in lower right corner) to click the checkbox.
86+
Completing a task by clicking its checkbox from a `tasks` query block _will_ work in any editor mode, even if the query is inside a callout.
87+
88+
---
89+
90+
Warning
91+
{: .label .label-yellow}
92+
93+
Tasks cannot read tasks that are inside code blocks, such as the ones used by the Admonitions plugin. Use Obsidian's built-in callouts instead.
94+
95+
---
96+
8197
Warning
8298
{: .label .label-yellow}
8399
Tasks can only render inline footnotes. Regular footnotes are not supported.

resources/sample_vaults/Tasks-Demo/Manual Testing/Smoke Testing the Tasks Plugin.md

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -63,12 +63,17 @@ Work through all the tasks below, until zero tasks remain in this query:
6363
6464
### Recurring Tasks
6565
66-
Confirm that when a recurring task is completed, a new task is created and all the date fields are incremented.
66+
Confirm that when a recurring task is completed, a new task is created, all the date fields are incremented, and the indentation is unchanged.
67+
68+
> [!Todo]
69+
>
70+
> - [ ] #task Complete this recurring task in **Source view** using **Tasks: Toggle task done** command 🔁 every day 🛫 2022-02-17 ⏳ 2022-02-18 📅 2022-02-19
71+
>
72+
> > - [ ] #task Complete this recurring task in **Reading view**🔁 every day 🛫 2022-02-17 ⏳ 2022-02-18 📅 2022-02-19
6773
68-
- [ ] #task Complete this recurring task in **Source view** using **Tasks: Toggle task done** command 🔁 every day 🛫 2022-02-17 ⏳ 2022-02-18 📅 2022-02-19
69-
- [ ] #task Complete this recurring task in **Reading view**🔁 every day 🛫 2022-02-17 ⏳ 2022-02-18 📅 2022-02-19
7074
- [ ] #task Complete this recurring task in **Live Preview**🔁 every day 🛫 2022-02-17 ⏳ 2022-02-18 📅 2022-02-19
71-
- [ ] #task **check**: Checked all above steps for **recurring tasks** worked
75+
76+
> - [ ] #task **check**: Checked all above steps for **recurring tasks** worked
7277
7378
### Rendering of Task Blocks
7479

src/Cache.ts

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { MetadataCache, TAbstractFile, TFile, Vault } from 'obsidian';
2-
import type { CachedMetadata, ListItemCache } from 'obsidian';
3-
import type { EventRef, SectionCache } from 'obsidian';
2+
import type { CachedMetadata, EventRef } from 'obsidian';
3+
import type { ListItemCache, SectionCache } from 'obsidian';
44
import { Mutex } from 'async-mutex';
55

66
import { Task } from './Task';
@@ -320,7 +320,6 @@ export class Cache {
320320

321321
for (const section of sections) {
322322
if (
323-
section.type === 'list' &&
324323
section.position.start.line <= lineNumberTask &&
325324
section.position.end.line >= lineNumberTask
326325
) {

src/Commands/CreateOrEdit.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,7 @@ const taskFromLine = ({ line, path }: { line: string; path: string }): Task => {
5858

5959
// If we are not on a line of a task, we take what we have.
6060
// The non-task line can still be a checklist, for example if it is lacking the global filter.
61-
const nonTaskRegex: RegExp = /^([\s\t]*)[-*]? *(\[(.)\])? *(.*)/u;
61+
const nonTaskRegex: RegExp = /^([\s\t>]*)[-*]? *(\[(.)\])? *(.*)/u;
6262
const nonTaskMatch = line.match(nonTaskRegex);
6363
if (nonTaskMatch === null) {
6464
// Should never happen; everything in the regex is optional.

src/Commands/ToggleDone.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -72,13 +72,13 @@ const toggleLine = ({ line, path }: { line: string; path: string }): string => {
7272
// 1. a list item
7373
// 2. a simple text line
7474

75-
const listItemRegex = /^([\s\t]*)([-*])/;
75+
const listItemRegex = /^([\s\t>]*)([-*])/;
7676
if (listItemRegex.test(line)) {
7777
// Let's convert the list item to a checklist item.
7878
toggledLine = line.replace(listItemRegex, '$1$2 [ ]');
7979
} else {
8080
// Let's convert the line to a list item.
81-
toggledLine = line.replace(/^([\s\t]*)/, '$1- ');
81+
toggledLine = line.replace(/^([\s\t>]*)/, '$1- ');
8282
}
8383
}
8484
}

src/Events.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,8 +16,8 @@ interface CacheUpdateData {
1616
export class Events {
1717
private obsidianEvents: ObsidianEvents;
1818

19-
constructor({ obsidianEents }: { obsidianEents: ObsidianEvents }) {
20-
this.obsidianEvents = obsidianEents;
19+
constructor({ obsidianEvents }: { obsidianEvents: ObsidianEvents }) {
20+
this.obsidianEvents = obsidianEvents;
2121
}
2222

2323
public onCacheUpdate(

src/LivePreviewExtension.ts

Lines changed: 30 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import { EditorView, ViewPlugin } from '@codemirror/view';
22
import type { PluginValue } from '@codemirror/view';
3+
import { Notice } from 'obsidian';
34

45
import { Task } from './Task';
56

@@ -33,8 +34,31 @@ class LivePreviewExtension implements PluginValue {
3334
return false;
3435
}
3536

37+
/* Right now Obsidian API does not give us a way to handle checkbox clicks inside rendered-widgets-in-LP such as
38+
* callouts, tables, and transclusions because `this.view.posAtDOM` will return the beginning of the widget
39+
* as the position for any click inside the widget.
40+
* For callouts, this means that the task will never be found, since the `lineAt` will be the beginning of the callout.
41+
* Therefore, produce an error message pop-up using Obsidian's "Notice" feature, log a console warning, then return.
42+
*/
43+
44+
// Tasks from "task" query codeblocks handle themselves thanks to `toLi`, so be specific about error messaging, but still return.
45+
const ancestor = target.closest(
46+
'ul.plugin-tasks-query-result, div.callout-content',
47+
);
48+
if (ancestor) {
49+
if (ancestor.matches('div.callout-content')) {
50+
// Error message for now.
51+
const msg =
52+
'obsidian-tasks-plugin warning: Tasks cannot add or remove completion dates or make the next copy of a recurring task for tasks written inside a callout when you click their checkboxes in Live Preview. \n' +
53+
'If you wanted Tasks to do these things, please undo your change, then either click the line of the task and use the "Toggle Task Done" command, or switch to Reading View to click the checkbox.';
54+
console.warn(msg);
55+
new Notice(msg, 45000);
56+
}
57+
return false;
58+
}
59+
3660
const { state } = this.view;
37-
const position = this.view.posAtDOM(target as Node);
61+
const position = this.view.posAtDOM(target);
3862
const line = state.doc.lineAt(position);
3963
const task = Task.fromLine({
4064
line: line.text,
@@ -47,6 +71,10 @@ class LivePreviewExtension implements PluginValue {
4771
precedingHeader: null,
4872
});
4973

74+
console.debug(
75+
`Live Preview Extension: toggle called. Position: ${position} Line: ${line.text}`,
76+
);
77+
5078
// Only handle checkboxes of tasks.
5179
if (task === null) {
5280
return false;
@@ -58,7 +86,7 @@ class LivePreviewExtension implements PluginValue {
5886
// Clicked on a task's checkbox. Toggle the task and set it.
5987
const toggled = task.toggle();
6088
const toggledString = toggled
61-
.map((task) => task.toFileLineString())
89+
.map((t) => t.toFileLineString())
6290
.join(state.lineBreak);
6391

6492
// Creates a CodeMirror transaction in order to update the document.

src/Query/Filter/ExcludeSubItemsField.ts

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7,10 +7,15 @@ export class ExcludeSubItemsField extends FilterInstructionsBasedField {
77
constructor() {
88
super();
99

10-
this._filters.add(
11-
'exclude sub-items',
12-
(task) => task.indentation === '',
13-
);
10+
this._filters.add('exclude sub-items', (task) => {
11+
if (task.indentation === '') return true; // no indentation, not a subitem
12+
13+
const lastBlockquoteMark = task.indentation.lastIndexOf('>');
14+
if (lastBlockquoteMark === -1) return false; // indentation present, not in a blockquote, subitem
15+
16+
// Up to one space allowed after last > in blockquote/callout, otherwise subitem
17+
return /^ ?$/.test(task.indentation.slice(lastBlockquoteMark + 1));
18+
});
1419
}
1520

1621
protected fieldName(): string {

src/Task.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -85,10 +85,10 @@ export class Task {
8585
public static readonly dateFormat = 'YYYY-MM-DD';
8686

8787
// Main regex for parsing a line. It matches the following:
88-
// - Indentation
88+
// - Indentation (including > for potentially nested blockquotes or Obsidian callouts)
8989
// - Status character
9090
// - Rest of task after checkbox markdown
91-
public static readonly taskRegex = /^([\s\t]*)[-*] +\[(.)\] *(.*)/u;
91+
public static readonly taskRegex = /^([\s\t>]*)[-*] +\[(.)\] *(.*)/u;
9292

9393
// Match on block link at end.
9494
public static readonly blockLinkRegex = / \^[a-zA-Z0-9-]+$/u;

src/main.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ export default class TasksPlugin extends Plugin {
2727
vault: this.app.vault,
2828
});
2929

30-
const events = new Events({ obsidianEents: this.app.workspace });
30+
const events = new Events({ obsidianEvents: this.app.workspace });
3131
this.cache = new Cache({
3232
metadataCache: this.app.metadataCache,
3333
vault: this.app.vault,

0 commit comments

Comments
 (0)