Sort at current depth (sort currently selected node and its siblings)


#1

As a generalization of @complexpoint’s fine script for Sorting top level projects alphabetically, which comes bundled with a generous master class on scripting, here is a script which sorts any arbitrary hierarchical level of the outline (by which I mean the currently selected node and its siblings).

// Sort currently selected node and its siblings.

(function() {
    function TaskPaperContextScript(editor, options) {
        var selection = editor.selection.startItem, // The selected node (or the starting item of a selected range).
            selection_parent = selection.parent;    // The parent of the selected node.

                editor.outline.groupUndoAndChanges(
                        function() {
                                var sorted_siblings = editor
                                                .outline
                                                .evaluateItemPath('following-sibling::* union (descendant-or-self::* intersect ancestor-or-self::*) union preceding-sibling::*', selection) // Obtain the item path across all siblings of the selected node...
                                                .sort(function (a, b) { // ... and sort them. (Sort callback reused from a script by @complexpoint.)
                                                                var strA = a.bodyString.toLowerCase(),
                                                                strB = b.bodyString.toLowerCase();
                                                            return (strA !== strB) ? (strA < strB ? -1 : 1) : 0;
                                                        })

                                sorted_siblings.forEach(        // Uproot the unsorted nodes from their common parent...
                                        function(x) {x.removeFromParent()}
                                );

                                selection_parent.appendChildren(sorted_siblings); //... and graft them back in the right order.
                        }
                );
    }

    tp3 = Application('TaskPaper');
    ds = tp3.documents;
    return ds.length ? ds[0].evaluate({
        script: TaskPaperContextScript.toString()
    }) : undefined;
})();

This is my first TaskPaper script (or JXA script for that matter); feedback on style or content warmly welcome.


TaskPaper Extensions Wiki
Script levels?
#2

And a variant which does the same sort of sorting, but by due date rather than alphabetically.

(Nodes lacking a due date are assumed to have a 1900-01 due date, effectively ending up at the top of the sorted list.)

// Sort currently selected node and its siblings by due date.
(function() {
    function TaskPaperContextScript(editor, options) {
        var selection = editor.selection.startItem, // Get the selected node (or the starting item of a selected range)...
            selection_parent = selection.parent;    // ... and the parent of the selected node.

        editor.outline.groupUndoAndChanges(
            function() {
                var sorted_siblings = editor
                        .outline
                        .evaluateItemPath('following-sibling::* union (descendant-or-self::* intersect ancestor-or-self::*) union preceding-sibling::*', selection)     // Obtain the item path across all siblings of the selected node...
                        .sort(function (a, b) {                 // ... and sort said siblings by due date.
                                var strA = a.hasAttribute('data-due') ? a.getAttribute('data-due', Date, false) : new Date (00, 0);
                                    strB = b.hasAttribute('data-due') ? b.getAttribute('data-due', Date, false) : new Date (00, 0);
                                return (strA !== strB) ? (strA < strB ? -1 : 1) : 0;
                            })

                sorted_siblings.forEach(    // Uproot the unsorted nodes from their common parent...
                    function(x) {x.removeFromParent()}
                );

                selection_parent.appendChildren(sorted_siblings); //... and graft them back in the right order.
            }
        );
    }

    tp3 = Application('TaskPaper');
    ds = tp3.documents;
    return ds.length ? ds[0].evaluate({
        script: TaskPaperContextScript.toString()
    }) : undefined;
})();

#3

This is my first use of any script in TaskPaper. it is very nice, thank you. Would it be possible to sort the list by tags and then alphabetically? That would be very helpful.


#4

Thanks, @mylevelbest, and apologies for the late reply. I’m sure it’s possible but sadly have absolutely no time to implement it these days. Will be keeping your request in mind though.


#5

I think it may be possible to generalize this very helpful code in two stages:

To work with any tag:
(ISO 8601 date strings are sortable, so fetching a string value should be enough, I think)

Edit the value of strTagName in this script:

// Sort currently selected node and its siblings by due date.
(() => {
    'use strict';

    const tp3JSContext = (editor, options) => {
        'use strict';
        const
            selection = editor.selection.startItem, // Get the selected node (or the starting item of a selected range)...
            selection_parent = selection.parent; // ... and the parent of the selected node.

        editor.outline.groupUndoAndChanges(
            () => {
                // comparing :: (a -> b) -> (a -> a -> Ordering)
                const comparing = f =>
                    (x, y) => {
                        const
                            a = f(x),
                            b = f(y);
                        return a < b ? -1 : (a > b ? 1 : 0);
                    };

                const
                    tagValue = strTag => item =>
                    item.hasAttribute('data-' + strTag) ? (
                        item.getAttribute('data-' + strTag)
                    ) : '';

                 // CHANGE TAG NAME HERE (e.g. 'done')
                const strTagName = 'due';

                const sorted_siblings =
                    selection_parent.children
                    .sort(comparing(tagValue(strTagName)))

                sorted_siblings.forEach( // Uproot the unsorted nodes from their common parent...
                    x => x.removeFromParent()
                );

                selection_parent.appendChildren(sorted_siblings); //... and graft them back in the right order.
            }
        );
    }

    const
        tp3 = Application('TaskPaper'),
        ds = tp3.documents;
    return ds.length ? ds[0].evaluate({
        script: tp3JSContext.toString()
    }) : undefined;
})();

To allow for secondary and tertiary sorts
(each value-getting function paired with either true for AZ, or false for ZA)

(edit the value of tagName at the bottom of the script to sort, for example, by ‘done’ rather than ‘due’)

// Sort currently selected node and its siblings by due date.
(options => {
    'use strict';

    const tp3JSContext = (editor, options) => {
        'use strict';

        // compare :: a -> a -> Ordering
        const compare = (a, b) => a < b ? -1 : (a > b ? 1 : 0);

        // mappendComparing :: [((a -> b), Bool)] -> (a -> a -> Ordering)
        const mappendComparing = fboolPairs =>
            (x, y) => fboolPairs.reduce(
                (ord, fb) => {
                    const f = fb[0];
                    return ord !== 0 ? (
                        ord
                    ) : fb[1] ? (
                        compare(f(x), f(y))
                    ) : compare(f(y), f(x));
                }, 0
            );

        const
            tagValue = strTag => item =>
            item.hasAttribute('data-' + strTag) ? (
                item.getAttribute('data-' + strTag)
            ) : '';

        const
            // The parent of the selected node.
            selection_parent = editor.selection.startItem.parent,
            sorted_siblings =
            selection_parent.children
            .sort(
                mappendComparing(
                    [ // Sorted by rising date,
                        [tagValue(options.tagName), true],
                        // then by text AZ
                        [x => x.bodyContentString, true]
                    ]
                )
            );

        return (
            editor.outline.groupUndoAndChanges(() => {
                // Uproot the unsorted nodes from their common parent...
                sorted_siblings.forEach(
                    x => x.removeFromParent()
                );

                //... and graft them back in the right order.
                selection_parent.appendChildren(sorted_siblings);
            }),
            sorted_siblings.map(x => x.bodyString)
            .join('\n')
        );
    };

    // JXA CONTEXT -----------------------------------------------------------

    const
        tp3 = Application('TaskPaper'),
        ds = tp3.documents;
    return ds.length ? ds[0].evaluate({
        script: tp3JSContext.toString(),
        withOptions: options
    }) : undefined;
})({
    tagName: 'due'
});