Script to auto-collapse projects based on their tag

I have a @defer tag for projects that I can’t work on immediately. I have a CSS style that tints these a lighter grey so they’re muted compared to the rest of the document:

item[data-defer], run[tag=data-defer] {
  color: mix(@text-color, @background-color, 50%);
}

but this style only affects the top-level item that has the @defer tag, not the child items. Although the overall project is muted, it still takes up quite a lot of space.

  • It would be nice if I could apply styles to a node based on the tags on its parents, but it sounds like that’s not possible right now. Even if I could, it still takes up a chunk of vertical space in the outline.
  • I could add the @defer tag to every sub-item. Then I get the styling, but also lots of extra tags I’d have to add and remove.
  • I could fold the deferred project. Then it’s easy to toggle the visibility of all the sub-items, and I save that vertical space. But when you close and reopen a document, all the items are expanded – whether or not they were collapsed when you closed the document.

So I wrote a script that walks the outline, and collapses any task it finds with the @defer tag.

This is the script:

function TaskPaperCollapseDeferredItems(editor, options) {
    var projects = editor.outline.evaluateItemPath('//@type=project');
    for (idx in projects) {
        var project = projects[idx];
        if (project.hasAttribute("data-defer")) {
            editor.setCollapsed(project);
        }
    }
    return 0;
}

Application("TaskPaper").documents[0].evaluate({
    script: TaskPaperCollapseDeferredItems.toString()
})

I have this set to run whenever I open TaskPaper, so my deferred projects are collapsed by default, and I selectively expand them – not the other way around.

This approach can be generalised to auto-collapse tasks based on other conditions – change the if (project.hasAttribute("data-defer")) condition to a different check. For example, I have some projects that I can only work on during weekdays, so I’ll probably write a modified version of this script to collapse them on weekends.

I am slowly dipping my toe into the water of TaskPaper scripting, so this is quite a simple first entry. Hopefully some more interesting stuff soon. :slight_smile:

The master version of this script is on GitHub.

1 Like

This is a great idea! How would I change the code so that anything with child tasks collapses if it’s tagged @defer?

Sorry, I think I’m a bit tired, and I’m not entirely sure what you mean. Do you mean collapsing the child items of any tasks that have the defer tag?

Yep! Right now the script is limited to projects that have the @defer tag — how would I change the script so that it’s not limited to projects but can also apply to tasks and notes? (Or even just tasks — I uses tasks more than projects)

Okay, I didn’t test this script, but barring some spelling error, I am assuming it will work. Let me know if it doesn’t.

function TaskPaperCollapseDeferredItems(editor, options) {
    var projects = editor.outline.evaluateItemPath('//@type=project');
    for (idx in projects) {
        var project = projects[idx];
        if (project.hasAttribute("data-defer")) {
            editor.setCollapsed(project);
        }
    }
    var tasks = editor.outline.evaluateItemPath('//@type=task');
    for (idx in tasks) {
        var task = tasks[idx];
        if (task.hasAttribute("data-defer")) {
            editor.setCollapsed(task);
        }
    }
    var notes = editor.outline.evaluateItemPath('//@type=note');
    for (idx in notes) {
        var note = notes[idx];
        if (note.hasAttribute("data-defer")) {
            editor.setCollapsed(note);
        }
    }
    return 0;
}

Application("TaskPaper").documents[0].evaluate({
    script: TaskPaperCollapseDeferredItems.toString()
})

I think there’s a slightly more compact way to do what Victor wrote:

function TaskPaperCollapseDeferredItems(editor, options) {
    var items = editor.outline.evaluateItemPath('//@type=project or @type=task or @type=note');
    for (idx in items) {
        var item = items[idx];
        if (item.hasAttribute("data-defer")) {
            editor.setCollapsed(item);
        }
    }

    return 0;
}

Application("TaskPaper").documents[0].evaluate({
    script: TaskPaperCollapseDeferredItems.toString()
})

Somebody who knows more about TaskPaper scripting than me may know a better way to get all the items in the document.

If you only want to get some types (e.g. tasks and projects, but not notes), you need to edit this string:

'//@type=project or @type=task or @type=note'

removing or adding types as you see fit.

3 Likes

You both are brilliant — thanks! :slight_smile:

Nice! How do you run it whenever you open Taskpaper?

FYI, I think that you missed that “project” since I am thinking you meant “item”.

Indeed, corrected, thanks!

How do you run it whenever you open Taskpaper?

I have a Keyboard Maestro macro that runs whenever I launch TaskPaper. There are two steps:

  1. An “Open a File, Folder or Application” step that points to my master todo list.
  2. An “Execute a JavaScript For Automation” step that runs this particular script.

Here’s what the workflow looks like:

1 Like