Script to toggle tag value and link parent and child tag values

Continuing the discussion from @done and subtasks:

This script toggles a provided tag/value for selected items. In addition to toggling value for the selected items it also:

  1. Sets all descendants of the selected items to match the new value.

  2. If all siblings of the selected items have the same value then the parent is given that same value, recursively.

In “maybe” more easy to understand terms. As the script is setup now it toggles the @done tag. If you toggle an item such that all items in the list become @done, then the parent is also marked @done.

var TaskPaper = Application('TaskPaper')

function TPContextGetCurrentPath(editor, options) {
  var selection = editor.selection;
  var selectedItems = selection.selectedItemsCommonAncestors;

  editor.outline.groupUndoAndChanges(function() {
	var attributeName = 'data-' + options.tag
    var value = options.value;

    selectedItems.forEach(function(each) {
      if (each.hasAttribute(attributeName)) {
        value = null;
    selectedItems.forEach(function(each) {
      each.setAttribute(attributeName, value);
      each.descendants.forEach(function(eachDescendant) {
        eachDescendant.setAttribute(attributeName, value);
      parent = each.parent;
      while (parent) {
        var siblingsMatch = true;
        parent.children.forEach(function(each) {
          if (each.getAttribute(attributeName) != value) {
            siblingsMatch = false;
        if (siblingsMatch) {
          parent.setAttribute(attributeName, value);
        } else {
          parent.setAttribute(attributeName, null);
        parent = parent.parent;


var path = TaskPaper.documents[0].evaluate({
  script: TPContextGetCurrentPath.toString(),
  withOptions: { tag: 'done', value: '' }

Vaguely-relatedly, I’ve made an update to TaskPaperRuby today that adds a total_tag_values method to both TaskPaperDocument and TaskPaperItem. The details are here, but essentially it lets you recursively total-up the (numerical) values of a given tag. For example, given this document:

    - Take the rubbish out @takes(5)
    - Go to supermarket @takes(30)
    - Have lunch @takes(20)

If you were to load and process the file as follows:

doc ="~/Desktop/inbox.taskpaper")
puts doc.total_tag_values("takes")

You’d get the result 55, as the total of all “takes” tag-values, recursively. You can also pass a second (boolean) parameter to the total_tag_values method, which if true will update/add the appropriate sub-totals at each level of the hierarchy.

I find this useful for estimating the time required for a series of tasks, and getting an idea of the total time needed. I use the “takes” tag in the sense of “it takes 5 minutes to do this”. You can specify whichever tag you like, of course. You can also call the method directly on any TaskPaperItem in the hierarchy, to just get/update totals for that portion of the tree.

1 Like

FWIW a less ambitious variant on this theme which only toggles the first selected item and its descendants.

The options at the end:

    tag: 'done',
    value: 'now',
    parseValueAsDateTime: true,
    notify: true

allow for notification, other tags, and alternative tag values, including other relative time values.


    tag: 'due',
    value: 'next week',
    parseValueAsDateTime: true,
    notify: true



JavaScript for Automation source:

(function (dctOptions) {
    'use strict';

    function tagToggleSubTree(editor, options) {
        'use strict';

        var strAttrib = 'data-' + options.tag,
            v = options.parseValueAsDateTime ? (
                .substr(0, 16)
            ) : options.value,

            selection = editor.selection,
            item = selection.startItem,

            toggled = item.hasAttribute(strAttrib) ? undefined : v;
        var strMsg;

        editor.outline.groupUndoAndChanges(function () {
            strMsg = [item].concat(item.descendants)
                .map(function (x) {
                    return x.setAttribute(strAttrib, toggled),
                .length + (typeof toggled !== 'undefined' ? (
                        ' items tagged @' + options.tag + '(' +
                        toggled + ')'
                    ) : ' items cleared (@' + options.tag +
                    ' tag removed)');


        return strMsg;

    var tp3 = Application('com.hogbaysoftware.TaskPaper3'),
        ds = tp3.documents,
        d = ds.length ? ds[0] : (
            ds.push(tp3.Document()), ds[0]
        strMsg = d.evaluate({
            script: tagToggleSubTree.toString(),
            withOptions: dctOptions

    if (dctOptions.notify) {
        var a = Application.currentApplication(),
            sa = (a.includeStandardAdditions = true, a),
            blnTagged = strMsg.indexOf('tagged') !== -1;

            'Selected TaskPaper 3 line & descendants toggled', {
                withTitle: (blnTagged ? '+' : '-') +
                    ' @' + dctOptions.tag +
                    (blnTagged ? strMsg.slice(-18) : ''),
                subtitle: strMsg.substr(0, strMsg.indexOf('('))

    return strMsg;
    tag: 'done',
    value: 'now',
    parseValueAsDateTime: true,
    notify: true