Show number of children tasks when parent task/project collapsed

Is there a way to display the number of children tasks a parent task/project has when it is collapsed?

thanks

This is what I came up with:

function TaskPaperContextScript(editor, options) {
var selection = editor.selection.selectedItems.filter(function(x) {
	return x.getAttribute('data-type') === 'project';
});
for(var i in selection) {
	editor.outline.groupUndoAndChanges(function() {
		var item = selection[i];
		var idx = item.bodyString.search(/ \[.+\/.+\]:/);
		if (idx > -1) {
			item.bodyString = item.bodyString.substring(0, idx) + ':';
		}
		else {
			if (item.hasChildren) {
				var done = item.children.filter(function(x) {
					return x.hasAttribute('data-done');
				});
				item.bodyString = item.bodyString.substring(0, item.bodyString.length-1) + " [" + done.length + "/" + item.children.length + "]:";
				//item.appendBody(" [" + done.length + "/" + item.children.length + "]");
			}
		}
	});	
}
}

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

There is a side effect of this code which is quite annoying though: cursor is moved to the next line.

2 Likes

Not really. With a script (as you’ve nicely done) you can insert the answer into your document content. But that’s a big problematic as a general solution, because you likely don’t really want that data saved in your document. Longer term TaskPaper might get the ability to display decorations in the next big release (4) (similar to what FoldingText does) attached to items in your outline. With that in place you could make a pretty nice script to do what you want, but until then the route you’ve taken is best.

Any ideas why the cursor moves to the next line with the code above? (by now this is driving me nuts already :slight_smile:)

… and the solution is:

editor.moveSelectionToItems(startSelection);
1 Like

sorry old topic, where did you put this line of code?

I guess another approach might be to generate a summary report of (tasks completed / total number of tasks) in each project and subproject, perhaps:

  • copying the indented report to the clipboard,
  • and displaying it in a dialog.

A sketch:

(() => {
    'use strict';

    // TaskPaper 3 script

    // Copy to clipboard (and display in dialog)
    // an indented breakdown of the
    // (number of tasks completed / total tasks)
    // in each project and subproject.

    // Rob Trew 2019
    // Ver 0.02


    // JS FOR AUTOMATION EVALUATION CONTEXT ---------------
    const main = () => {
        const ds = Application('TaskPaper').documents;

        return either(alert('Problem'))(
            dct => {
                const
                    strHeadLine = str(dct.done) + ' tasks of ' +
                    dct.total + ' completed',
                    strOutline = unlines(dct.breakdown);
                return (
                    Object.assign(
                        Application.currentApplication(), {
                            includeStandardAdditions: true
                        }
                    ).setTheClipboardTo(strHeadLine + '\n\n' + strOutline),
                    alert(strHeadLine)(strOutline)
                );
            }
        )(bindLR(
            ds.length > 0 ? (
                Right(ds.at(0))
            ) : Left('No TaskPaper documents open')
        )(d => Right(d.evaluate({
            script: tp3Context.toString()
        }))));
    };

    // TASKPAPER JS EVALUATION CONTEXT --------------------
    const tp3Context = (editor, options) => {
        const main = () =>
            foldTree(
                x => xs => 0 < xs.length ? (() => {
                    const [intDone, intTotal] = updatedTuple(
                        add(plusDone(x))
                    )(
                        add(succTotal(x))
                    )(
                        xs.reduce(
                            (tupleDoneTotal, p) =>
                            updatedTuple(add(p.done))(add(p.total))(
                                tupleDoneTotal
                            ),
                            [0, 0]
                        )
                    );
                    return {
                        breakdown: (
                            isProject(x) ? [
                                title(x) + ' (' +
                                str(intDone) + '/' +
                                str(intTotal) + ')'
                            ] : []
                        ).concat(xs.flatMap(
                            p => p.breakdown.map(v => '    ' + v)
                        )),
                        done: intDone,
                        total: intTotal
                    }
                })() : {
                    breakdown: isProject(x) ? (
                        [title(x)]
                    ) : [],
                    done: plusDone(x),
                    total: succTotal(x)
                })(
                pureTreeTP3(
                    editor.outline.root
                )
            );

        // TASKPAPER --------------------------------------

        // plusDone :: Item -> Int
        const plusDone = x =>
            (isTask(x) && isDone(x)) ? (
                1
            ) : 0;

        // succTotal :: Item -> Int
        const succTotal = x =>
            isTask(x) ? (
                1
            ) : 0

        // isProject :: Item -> Bool
        const isProject = x =>
            'project' === x.getAttribute('data-type');

        // isTask :: Item -> Bool
        const isTask = x =>
            'task' === x.getAttribute('data-type');

        // isDone :: Item -> Bool
        const isDone = x =>
            x.hasAttribute('data-done');

        // str :: a -> String
        const str = x => x.toString();

        // title :: Item -> String
        const title = x => x.bodyContentString + ':';


        // GENERIC FUNCTIONS FOR TASKPAPER CONTEXT --------

        // Node :: a -> [Tree a] -> Tree a
        const Node = v => xs => ({
            type: 'Node',
            root: v, // any type of value (consistent across tree)
            nest: xs || []
        });

        // add :: Int -> Int -> Int
        const add = a => b => a + b;

        // foldTree :: (a -> [b] -> b) -> Tree a -> b
        const foldTree = f => tree => {
            const go = node => f(node.root)(
                node.nest.map(go)
            );
            return go(tree);
        };

        // pureTreeTP3 :: TP3Item  -> Tree TP3Item
        const pureTreeTP3 = item => {
            const go = x =>
                Node(x)(x.hasChildren ? x.children.map(go) : []);
            return go(item);
        };

        // updatedTuple (***) :: (a -> b) -> (c -> d) -> ((a, c) -> (b, d))
        const updatedTuple = f => g => tpl => [f(tpl[0]), g(tpl[1])];

        // TASKPAPER CONTEXT MAIN
        return main();
    };

    // JXA FUNCTIONS --------------------------------------

    // alert :: String => String -> IO String
    const alert = title => s => {
        const
            sa = Object.assign(Application('System Events'), {
                includeStandardAdditions: true
            });
        return (
            sa.activate(),
            sa.displayDialog(s, {
                withTitle: title,
                buttons: ['OK'],
                defaultButton: 'OK',
                withIcon: sa.pathToResource('TaskPaper.icns', {
                    inBundle: 'Applications/TaskPaper.app'
                })
            }),
            s
        );
    };

    // GENERIC FUNCTIONS ------------------------------

    // Left :: a -> Either a b
    const Left = x => ({
        type: 'Either',
        Left: x
    });

    // Right :: b -> Either a b
    const Right = x => ({
        type: 'Either',
        Right: x
    });

    // bindLR (>>=) :: Either a -> (a -> Either b) -> Either b
    const bindLR = m => mf =>
        undefined !== m.Left ? (
            m
        ) : mf(m.Right);

    // either :: (a -> c) -> (b -> c) -> Either a b -> c
    const either = fl => fr => e =>
        'Either' === e.type ? (
            undefined !== e.Left ? (
                fl(e.Left)
            ) : fr(e.Right)
        ) : undefined;

    // str :: a -> String
    const str = x => x.toString();

    // unlines :: [String] -> String
    const unlines = xs => xs.join('\n');

    // JXA CONTEXT MAIN
    return main();
})()