You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
Task Sorting: allow to run from templates with parameters (without asking user) #618
The Todo List for the plugin says "Sort tasks via template call". I'm asking for some of that to be included. In particular for just the "Sort tasks under heading (choose)" command.
At the moment running this template tag <% await DataStore.invokePluginCommandByName("sortTasksUnderHeading","dwertheimer.TaskSorting",["Home"]) -%>
requires me to select Heading and then sort order.
I want to be able to pass these in as parameters to avoid these questions popping up when running in template.
Currently running v1.1.0.
Related bugs
The documentation doesn't mention the available command "Sort tasks on the page "
Note that in the plugin.json the command name includes a space at the end. This will make it difficult to use it correctly from template tags.
@jgclark I made a tweak that will allow it to work as an xcallback. See: here
That said, there may challenges with running commands like this in a Template render, because this command acts on Editor but Editor is in flux during a template rendering. The note content is effectively read and re-rendered and then replaced. So I'm not sure that you can reliably run a template command that rewrites the Editor contents (like this one does) and get consistent results.
Please build dwertheimer.TaskSorting and try it out. Happy to discuss further based on what you find.
Thanks. As it happens my first use is from another plugin command, not actually a template tag.
I want /generate repeat to call /tsd after it has made changes. So I have set that up, and rebuilt TS&S to 1.2.0.
It's logging a lot (most of which I don't understand), but it's not appearing to have any effect.
I've now compared the output from the Repeats run to when I invoke it manually on the same short ### Main section with 7 tasks -- attached. They're almost identical, with the most differences being single digits on how many lines etc., which I think are a factor of my testing. They both start the same, and have correctly picked up my settings: Starting sortTasks(withUserInput:false, default sortFields:["due","-priority","content"], withHeadings:false, byHeading:true):
FYI there are things that don't make a lot of sense to me in the logs are:
why when I have byHeading set, does it seem to do the same level of processing on the sections I'm not interested in?
what the Queuing 21 open tasks for deletion from note means, given I'm not asking for anything to be deleted.
But they're probably incidental to the main point.
@jgclark In the OP, you said you wanted to use "sortTasksUnderHeading" (/sth) and you gave an example of passing a heading, so that's the command that I refactored and wanted you to test. But in your most recent post, you say you wanted to use /tsd (Task Sort by Defaults), which is a different command and works on a whole note, and which I have not updated. It would make sense to me that /tsd would list all the tasks in the note, because it is sorting each heading's tasks separately under it's heading.
So before I dig into try to figure out what is and isn't working, can you please restate the use case that you are trying to solve for?
BTW, yes the "open tasks for deletion" language is a little unsettling, but it's technically correct, because all the tasks are removed from the note so that they can be rewritten in a different order.
I've done more on this, and pushed commit of WIP with RepeatExtensions 0.9, including a few minor tweaks to TaskSorting 1.2.0.
I've now tried using both 'sortTasks' and 'sortTasksUnderHeading' functions via await DataStore.invokePluginCommandByName (using their command names) and both appear not to make changes in the Editor, despite lots of sensible-looking logging. (I emphasise that this is not from a Template, so we shouldn't be having race conditions from that.)
I've tried putting an updateCache(Editor.note) before the sort calls, but that's not making a difference.
I'm sure it's something very simple now.
In case it's relevant I've also now tried calling directly to sortTasks() rather than via DataStore.invokePluginCommandByName('Sort tasks on the page', ...). Same outcomes.
@jgclark I have looked into this. It is definitely a classic Editor vs Editor.note race condition thing. Without changing any of the initial code...
I created this test template, which functions like any plugin code sequence:
<%- Editor.prependParagraph(`1. this content was prepended to Editor`,`text`) -%>
<%- Editor.appendParagraph(`2. this content was appended to Editor`,`text`) -%>
<% await DataStore.invokePluginCommandByName("Sort tasks under heading (choose)","dwertheimer.TaskSorting",["Tasks",`["due","-priority","content"]`]) %>
When I invoke this code, it appears that the document has not been sorted. However, I can look at the revisions and see what happened.
New content was added to Editor by the first two lines of code (this would be equivalent to your repeats plugin making its changes to Editor)
Then, Task Sorter runs. tasks were deleted/added moved around by the TS plugin (which operates on Editor.note), and were sorted per the invoke/callback settings. You can actually see them sorted correctly.
Then Editor saves and overwrites the changes with the original content (e.g. that your plugin wrote to the Editor), wiping out the sorted version. It happens so fast, you never see the sorted ones, because you are left with the original Editor save.
If I change the commands that write to the note to Editor.note, then everything works exactly as you want. This is annoying and causes us to burn a lot of time because we never know what a plugin is using under the hood (Editor or Editor.note or just a DataStore.note). I hope you will add some comments to the request I made to @EduardMe to have an waitForCommit function.
For this particular use-case I have added a work-around.
@jgclark I am adding a 3rd parameter to this command (and will look at adding it to the others). This is the note in the README:
NOTE: If you are calling this command from a plugin or a template and want to sort tasks under a heading in a specific note (or the Editor) you have been working on, you should pass the note as a third parameter to ensure that taskSorting is working on the same Object you have been working on, e.g. ("myHeading",["-priority","content"], Editor). For clarity, Task Sorting plugin works on Editor.note, but if you are using Editor (and not Note) you should pass Editor in as the noteOverride parameter, else the delayed write from Editor will overwrite any task sorting you do.
@jgclark I have tested and this all works. However, the problem is that then, when you are running in the Editor (not on a note), then running on your example with @repeats doesn't work quite right because NP insists on popping up the "Are you sure you want to delete the repeating XXX" message for each of them, because in order to move them around, I delete them and re-insert them in the proper place. But the delays with the popup and waiting for an answer somehow result in odd duplications of content. It works perfectly if you have no @repeats.
I was able to use the work-around that Eduard gave us Editor.skipNextRepeatDeletionCheck = true and I think that is properly suppressing the nag popups. So I will re-release and have you test it.
Please let me know your thoughts. I will look at the rest of the functions on the plane this weekend.
awaitDataStore.invokePluginCommandByName("Sort tasks under heading (choose)","dwertheimer.TaskSorting",["Tasks",`["due","-priority","content"]`,Editor])
This looks like a race condition, because changing something in Editor is
not writing it immediately to the note.
It happens after a second or so, so that the note content is not being
written to the file on every keystroke.
We would need something like await Editor.flush() or await
Editor.writeIfNeeded()
On Fri 29. Aug 2025 at 9:14 PM, David Wertheimer ***@***.***> wrote:
*dwertheimer* left a comment (NotePlan/plugins#618)
<#618 (comment)>
@jgclark <https://github.com/jgclark> I have looked into this. It is
definitely a classic Editor vs Editor.note race condition thing. Without
changing any of the initial code...
I created this test template, which functions like any plugin code
sequence:
<%- Editor.prependParagraph(`1. this content was prepended to Editor`,`text`) -%>
<%- Editor.appendParagraph(`2. this content was appended to Editor`,`text`) -%>
<% await DataStore.invokePluginCommandByName("Sort tasks under heading (choose)","dwertheimer.TaskSorting",["Tasks",`["due","-priority","content"]`]) %>
When I invoke this code, it appears that the document has not been sorted.
However, I can look at the revisions and see what happened.
1.
Some content was added to Editor by some plugin
2.
The tasks were deleted/added moved around by the TS plugin (which
operates on Editor.note), and were sorted per the invoke/callback
settings
3.
Then Editor saves and overwrites the changes with the original
content. It happens so fast, you never see the sorted ones, because you are
left with the original Editor.save.
Screen.Cap.2025-08-29.at.18.02.15.gif (view on web)
<https://github.com/user-attachments/assets/05daead4-0429-4442-9c6b-5bc6f6d5b77d>
If I change the commands that write to the note to Editor.note, then
everything works exactly as you want. This is annoying and causes us to
burn a lot of time because we never know what a plugin is using under the
hood (Editor or Editor.note or just a DataStore.note). I hope you will add
some comments to the request I made to @EduardMe
<https://github.com/EduardMe> to have an waitForCommit function.
For this particular use-case I have added a work-around.
—
Reply to this email directly, view it on GitHub
<#618 (comment)>,
or unsubscribe
<https://github.com/notifications/unsubscribe-auth/AAZCAYRH7QXMVSKGI6NU4KL3QECHTAVCNFSM6AAAAACFF2IXVGVHI2DSMVQWIX3LMV43OSLTON2WKQ3PNVWWK3TUHMZTEMZYHAYDQOBWGY>
.
You are receiving this because you were mentioned.Message ID:
***@***.***>
Actually we do have await Editor.save()
filename)
/**
* Note: Available from NotePlan v3.9.3+
* Saves the content of the editor into the note file, so you can work
with the updated cache later (you might need to call
DataStore.updateCache(note, true) to update tags etc.).
* @return {Promise}
*/
.save()
On Sun 31. Aug 2025 at 9:10 AM, Eduard Metzger ***@***.***>
wrote:
This looks like a race condition, because changing something in Editor is
not writing it immediately to the note.
It happens after a second or so, so that the note content is not being
written to the file on every keystroke.
We would need something like await Editor.flush() or await
Editor.writeIfNeeded()
On Fri 29. Aug 2025 at 9:14 PM, David Wertheimer ***@***.***>
wrote:
> *dwertheimer* left a comment (NotePlan/plugins#618)
> <#618 (comment)>
>
> @jgclark <https://github.com/jgclark> I have looked into this. It is
> definitely a classic Editor vs Editor.note race condition thing. Without
> changing any of the initial code...
>
> I created this test template, which functions like any plugin code
> sequence:
>
> <%- Editor.prependParagraph(`1. this content was prepended to Editor`,`text`) -%>
> <%- Editor.appendParagraph(`2. this content was appended to Editor`,`text`) -%>
> <% await DataStore.invokePluginCommandByName("Sort tasks under heading (choose)","dwertheimer.TaskSorting",["Tasks",`["due","-priority","content"]`]) %>
>
> When I invoke this code, it appears that the document has not been
> sorted. However, I can look at the revisions and see what happened.
>
> 1.
>
> Some content was added to Editor by some plugin
> 2.
>
> The tasks were deleted/added moved around by the TS plugin (which
> operates on Editor.note), and were sorted per the invoke/callback
> settings
> 3.
>
> Then Editor saves and overwrites the changes with the original
> content. It happens so fast, you never see the sorted ones, because you are
> left with the original Editor.save.
>
> Screen.Cap.2025-08-29.at.18.02.15.gif (view on web)
> <https://github.com/user-attachments/assets/05daead4-0429-4442-9c6b-5bc6f6d5b77d>
>
> If I change the commands that write to the note to Editor.note, then
> everything works exactly as you want. This is annoying and causes us to
> burn a lot of time because we never know what a plugin is using under the
> hood (Editor or Editor.note or just a DataStore.note). I hope you will add
> some comments to the request I made to @EduardMe
> <https://github.com/EduardMe> to have an waitForCommit function.
>
> For this particular use-case I have added a work-around.
>
> —
> Reply to this email directly, view it on GitHub
> <#618 (comment)>,
> or unsubscribe
> <https://github.com/notifications/unsubscribe-auth/AAZCAYRH7QXMVSKGI6NU4KL3QECHTAVCNFSM6AAAAACFF2IXVGVHI2DSMVQWIX3LMV43OSLTON2WKQ3PNVWWK3TUHMZTEMZYHAYDQOBWGY>
> .
> You are receiving this because you were mentioned.Message ID:
> ***@***.***>
>
Thanks for working on this. I've now been able to bring it into my template. An observation and a question on this:
Suggestion: Could you make a template-only entry point for this so we don't have to encode Sort tasks under heading (choose). I first tried sortTasksViaTemplate as that's what was mentioned in the CHANGELOG. This is what I do now to provide a specific easier way in for automation.
Question: I ran it as <% await DataStore.invokePluginCommandByName("Sort tasks under heading (choose)","dwertheimer.TaskSorting", ["Home", ["-priority", "content"]]) -%>
This works, but lands up sorting all checklists under all tasks. This isn't what I was expecting, which was higher priority tasks then checklists, then lower priority tasks then checklists.
How can I do this?
@jgclark I will have a look at this. To make sure I deliver on your expectations, can you please show me an example of what you would hope for instead? For instance if this is the source note:
## my heading
+ [x] a completed checklist
* [x] a completed task
+ 01 a checklist
* 02 a task
some indented text under 02 a task
+ 03 !! a high priority checklist under 02 a task
* 04 a task under 03 checklist
* !! a high priority task
* [x] !!! a super high priority completed task
What would you want the output to be?
Please feel free to add any other permutations that you think would be good to point out.
## my heading* !! a high priority task
* 02 a task
some indented text under 02 a task
+ 03 !! a high priority checklist under 02 a task
* 04 a task under 03 checklist
+ 01 a checklist
*[x] !!! a super high priority completed task
+[x] a completed checklist
*[x] a completed task
I.e. priority > open things > completed things > cancelled things. At the moment the function seems to always sort all sorts of checklists lower than any sort of task. But I'm wanting all open tasks or checklists to sort ahead of completed ahead of cancelled tasks/checklists.
I think maybe my test case was not what we needed to properly solve for your needs, because when I run /tsh on the test case I wrote above, I get exactly what you said you were hoping for.
But I think I understand now what you were looking for, but I'm not 100% sure how to get it done.
In this example:
I notice that it doesn't seem that Eduard (or maybe just the theme) considers an indented checklist with !!! to actually be a priority.
What would you expect to happen in this sort?
I think you would expect the checklist with three !!! to sort to the very top
but what should happen to the checklist that has three !!! but is indented under another item?
It's always been the edges of sorting that have caused me challenges...
Please let me know. Thanks!
@jgclark The fact that tasks are sorted first by type and then by the given sort order is rather fundamental to how task sorter works. It may take some major surgery to interleave them. I will give this some thought. But the more info you can give me on the above the better.
@jgclark Update: I made some major changes; have not had a change to fully test all the functions. Would appreciate it if you could use it for a bit before we unleash it.
New Feature: Task Type Interleaving (Default Behavior)
By default, tasks are interleaved: compatible task types are combined and sorted together by priority
Within each priority level, open tasks appear before checklists
This allows tasks to be sorted by priority first, then by type (open before checklist)
To use traditional grouping (all open tasks together, then all checklists together), set interleaveTaskTypes=false
I made "your way" of interleaving open+checklists the default, so you should start to see it working without changing anything in your template/callback calls.
Activity
dwertheimer commentedon Jan 20, 2025
@jgclark I made a tweak that will allow it to work as an xcallback. See: here
That said, there may challenges with running commands like this in a Template render, because this command acts on Editor but Editor is in flux during a template rendering. The note content is effectively read and re-rendered and then replaced. So I'm not sure that you can reliably run a template command that rewrites the Editor contents (like this one does) and get consistent results.
Please build dwertheimer.TaskSorting and try it out. Happy to discuss further based on what you find.
jgclark commentedon Jan 24, 2025
Thanks. As it happens my first use is from another plugin command, not actually a template tag.
I want /generate repeat to call /tsd after it has made changes. So I have set that up, and rebuilt TS&S to 1.2.0.
It's logging a lot (most of which I don't understand), but it's not appearing to have any effect.
I've now compared the output from the Repeats run to when I invoke it manually on the same short
### Mainsection with 7 tasks -- attached. They're almost identical, with the most differences being single digits on how many lines etc., which I think are a factor of my testing. They both start the same, and have correctly picked up my settings:Starting sortTasks(withUserInput:false, default sortFields:["due","-priority","content"], withHeadings:false, byHeading:true):pluginCommandCall.log
UserCall.log
FYI there are things that don't make a lot of sense to me in the logs are:
Queuing 21 open tasks for deletion from notemeans, given I'm not asking for anything to be deleted.But they're probably incidental to the main point.
dwertheimer commentedon Jan 25, 2025
@jgclark In the OP, you said you wanted to use "sortTasksUnderHeading" (/sth) and you gave an example of passing a heading, so that's the command that I refactored and wanted you to test. But in your most recent post, you say you wanted to use /tsd (Task Sort by Defaults), which is a different command and works on a whole note, and which I have not updated. It would make sense to me that /tsd would list all the tasks in the note, because it is sorting each heading's tasks separately under it's heading.
So before I dig into try to figure out what is and isn't working, can you please restate the use case that you are trying to solve for?
BTW, yes the "open tasks for deletion" language is a little unsettling, but it's technically correct, because all the tasks are removed from the note so that they can be rewritten in a different order.
dwertheimer commentedon Jan 25, 2025
Also, just noticing that you wrote:
Can u pls send me a screenshot? I am not seeing this in plugin.json
jgclark commentedon Jan 25, 2025
Sorry, my bad. I had both of these ideas in my head originally, and I thought I'd asked about both of them.
I'm giving this another go at my end.
jgclark commentedon Jan 27, 2025
I've done more on this, and pushed commit of WIP with RepeatExtensions 0.9, including a few minor tweaks to TaskSorting 1.2.0.
I've now tried using both 'sortTasks' and 'sortTasksUnderHeading' functions via
await DataStore.invokePluginCommandByName(using their command names) and both appear not to make changes in the Editor, despite lots of sensible-looking logging. (I emphasise that this is not from a Template, so we shouldn't be having race conditions from that.)I've tried putting an
updateCache(Editor.note)before the sort calls, but that's not making a difference.I'm sure it's something very simple now.
jgclark commentedon Jan 27, 2025
For the following test section, which doesn't change after the sortTasks call (though the screen flickers, showing it is updated):
The logged output is
jgclark commentedon Jan 27, 2025
In case it's relevant I've also now tried calling directly to
sortTasks()rather than viaDataStore.invokePluginCommandByName('Sort tasks on the page', ...). Same outcomes.jgclark commentedon Aug 29, 2025
Can I ping you about this, please @dwertheimer ? I'm still keen to use /sth programmatically.
dwertheimer commentedon Aug 30, 2025
@jgclark I have looked into this. It is definitely a classic Editor vs Editor.note race condition thing. Without changing any of the initial code...
I created this test template, which functions like any plugin code sequence:
When I invoke this code, it appears that the document has not been sorted. However, I can look at the revisions and see what happened.
New content was added to
Editorby the first two lines of code (this would be equivalent to your repeats plugin making its changes to Editor)Then, Task Sorter runs. tasks were deleted/added moved around by the TS plugin (which operates on
Editor.note), and were sorted per the invoke/callback settings. You can actually see them sorted correctly.Then
Editorsaves and overwrites the changes with the original content (e.g. that your plugin wrote to the Editor), wiping out the sorted version. It happens so fast, you never see the sorted ones, because you are left with the original Editor save.If I change the commands that write to the note to Editor.note, then everything works exactly as you want. This is annoying and causes us to burn a lot of time because we never know what a plugin is using under the hood (Editor or Editor.note or just a DataStore.note). I hope you will add some comments to the request I made to @EduardMe to have an
waitForCommitfunction.For this particular use-case I have added a work-around.
dwertheimer commentedon Aug 30, 2025
@jgclark I am adding a 3rd parameter to this command (and will look at adding it to the others). This is the note in the README:
dwertheimer commentedon Aug 30, 2025
@jgclark I have tested and this all works. However, the problem is that then, when you are running in the Editor (not on a note), then running on your example with @repeats doesn't work quite right because NP insists on popping up the "Are you sure you want to delete the repeating XXX" message for each of them, because in order to move them around, I delete them and re-insert them in the proper place. But the delays with the popup and waiting for an answer somehow result in odd duplications of content. It works perfectly if you have no @repeats.
I was able to use the work-around that Eduard gave us
Editor.skipNextRepeatDeletionCheck = trueand I think that is properly suppressing the nag popups. So I will re-release and have you test it.Please let me know your thoughts. I will look at the rest of the functions on the plane this weekend.
dwertheimer commentedon Aug 30, 2025
Here's the command I am running:
EduardMe commentedon Aug 31, 2025
EduardMe commentedon Aug 31, 2025
jgclark commentedon Sep 1, 2025
Thanks for working on this. I've now been able to bring it into my template. An observation and a question on this:
Suggestion: Could you make a template-only entry point for this so we don't have to encode
Sort tasks under heading (choose). I first triedsortTasksViaTemplateas that's what was mentioned in the CHANGELOG. This is what I do now to provide a specific easier way in for automation.Question: I ran it as
<% await DataStore.invokePluginCommandByName("Sort tasks under heading (choose)","dwertheimer.TaskSorting", ["Home", ["-priority", "content"]]) -%>This works, but lands up sorting all checklists under all tasks. This isn't what I was expecting, which was higher priority tasks then checklists, then lower priority tasks then checklists.
How can I do this?
dwertheimer commentedon Sep 18, 2025
@jgclark I will have a look at this. To make sure I deliver on your expectations, can you please show me an example of what you would hope for instead? For instance if this is the source note:
What would you want the output to be?
Please feel free to add any other permutations that you think would be good to point out.
jgclark commentedon Sep 18, 2025
Thanks.
In your cases I'd hope for:
I.e. priority > open things > completed things > cancelled things. At the moment the function seems to always sort all sorts of checklists lower than any sort of task. But I'm wanting all open tasks or checklists to sort ahead of completed ahead of cancelled tasks/checklists.
dwertheimer commentedon Sep 19, 2025
@jgclark I am updating /tsh to allow for arguments for each param.
sample x-callback:
I think maybe my test case was not what we needed to properly solve for your needs, because when I run /tsh on the test case I wrote above, I get exactly what you said you were hoping for.
But I think I understand now what you were looking for, but I'm not 100% sure how to get it done.
In this example:
I notice that it doesn't seem that Eduard (or maybe just the theme) considers an indented checklist with !!! to actually be a priority.
What would you expect to happen in this sort?
I think you would expect the checklist with three !!! to sort to the very top
but what should happen to the checklist that has three !!! but is indented under another item?
It's always been the edges of sorting that have caused me challenges...
Please let me know. Thanks!
dwertheimer commentedon Sep 19, 2025
@jgclark The fact that tasks are sorted first by type and then by the given sort order is rather fundamental to how task sorter works. It may take some major surgery to interleave them. I will give this some thought. But the more info you can give me on the above the better.
dwertheimer commentedon Sep 19, 2025
@jgclark I made some changes. sending to you to test.
dwertheimer commentedon Sep 19, 2025
@jgclark Update: I made some major changes; have not had a change to fully test all the functions. Would appreciate it if you could use it for a bit before we unleash it.
New Feature: Task Type Interleaving (Default Behavior)
interleaveTaskTypes=falseI made "your way" of interleaving open+checklists the default, so you should start to see it working without changing anything in your template/callback calls.
New version:
[1.2.5] - 2025-09-13 (@dwertheimer)
dwertheimer.TaskSorting-v1.2.5.zip