Script: Sorting top level projects alphabetically

PS you can also use console.log in Script Editor:

To see the console.log output stream:

  • choose the Show or Hide the log icon at the bottom edge of the Script Editor window, then
  • click the Message panel.

You will still get the final expression evaluation results,
but they will be preceded by the console.log stream

It seems as if I use console.log every other line in the code using Script Editor and the debug window in Taskpaper. Does CodeRunner offer something else that I don’t know about?

Not sure what @pslobo’s view would be but I think my own view is that CodeRunner may be most useful if you are already using it for other things, and would like to experiment with core JavaScript examples in a familiar environment.

The main advantages which come to mind are some editing facilities like toggling the comment status of a line on and off, block indent and outdent etc. etc.

(another option for editing functions like that within Script Editor is to add them in the form of keyboard assignments to a few JXA scripts for Script Editor itself).

@complexpoint hit the nail on the head. CodeRunner is a great app for experimentation IF you already have and use it for other things (as is my case). You don’t need to go out and buy it just for this since it is possible to use ScripEditor and debugger as has already been mentioned.

A few things I appreciate about CodeRunner are:

  1. Syntax highlighting;
  2. More easily manipulate text (in/outdent), comment, etc.;
  3. Code completion helps a great deal, especially when learning (take a look at the image below)
  4. Tabs. Makes it easy to have multiple documents open and quickly experiment with various snippets;

@pslobo Thank you.

1 Like

@complexpoint This script seems to crash my TP every second or third time I use it nowadays. This started happening around 3.0. Is that just me?

Well caught – the final API did go through some changes during the Preview stages.

I’ll sketch a more up to date version of that.

Updated draft, using the release API’s outline.groupUndoAndChanges() and outline.insertItemsBefore()

// SORT TOP LEVEL PROJECTS
(function () {
    'use strict';

    function sortTopLevelProjects(editor) {

        var outline = editor.outline;

        // PREPARE TO UPDATE UNDOABLY

        outline.groupUndoAndChanges(function () {

            outline.evaluateItemPath(
                '/@type=project'
            )

            .sort(function (a, b) {
                var strA = a.bodyString.toLowerCase(),
                    strB = b.bodyString.toLowerCase();

                return (strA !== strB) ? (strA < strB ? -1 :
                    1) : 0;
            })

            .reduceRight(
                function (nextSibling, oProj) {
                    outline.insertItemsBefore(oProj,
                        nextSibling);

                    return oProj; // binds it to the name 'nextSibling' for next item
                },
                outline.root.firstchild // initialize nextSibling for the rightmost item
            );

        });

        return true;
    }

    var ds = Application("TaskPaper")
        .documents;

    return ds.length ? ds[0].evaluate({
        script: sortTopLevelProjects.toString()
    }) : 'no document found in TaskPaper ...';

})();
1 Like

@complexpoint Thanks so much! Super useful.

2 Likes

@complexpoint, I tried running this code without selecting Archive and searches, and for some reason, it keeps placing the “Archive” project all the way up there. My approach was to change,

outline.evaluateItemPath( '/@type=project' )

to something like,

outline.evaluateItemPath( '/* except (archive union @search)' )

  1. Do you have any idea of how to change that behavior?

  2. I was also wondering if it would be possible to change the behavior of the sort to sort by the second element of the string. I have first and last names combined as a project name (so the first and last name are separated by a simple space), but ideally, I would like to sort by last name. I would think that this would be something a lot more complicated, but just wondering if you have something like this already done somewhere in your quiver.

without selecting Archive and searches

Not quite sure what you mean there – this was just a basic demonstration of scripting an alphabetic sort of top level projects.

Selection state is not referenced in the code – presumably ‘Archive’ is high in the A-Z sequence of your projects ?

the second element of the string

You may have to expand a bit there …

The name of the project has two elements ?

Sorry about the obtuseness. If I run the script, the saved searches in the document and the Archive project are moved to the top. This is confusing since by default they are added at the end of the document. So that, I wanted to sort top level projects without including the “Archive” project the search queries.

I figured that since you had this on the code,

outline.evaluateItemPath( '/@type=project' )

I could just select the top level projects except the search functions and the Archive project. That was the reason of the code I included,

outline.evaluateItemPath( '/* except (archive union @search)' )

Now. Onto my second question. I have some projects organized like this,

John Lennon:
Tim Apple:
Victor Gutierrez:

But I wanted to be able to organize it alphabetically by last names. Something like,

Tim Apple
Victor Gutierrez
John Lennon

I think it could be fairly tricky to script sorting of only the selected subset of paragraphs …

Feels intuitively like the kind of thing that could easily go wrong and drop a para or two somewhere between the cracks – I’m afraid my personal appetite for scripting is more or less limited to rather safer and less ambitious operations : - )

1 Like

Re family-name sorting, it’s possible that something like this might work:

// SORT TOP LEVEL PROJECTS
(() => {
    'use strict';

    const sortTopLevelProjects = (editor, options) => {

        const rgxSpace = /\s+/;

        // secondWord :: TP Item -> String
        const secondWord = x => {
            const ks = x.bodyString.split(rgxSpace);
            return (
                1 < ks.length ? (
                    ks[1]
                ) : ks[0]
            ).toLowerCase();
        };

        // projName :: TP Item -> String
        const projName = x => x.bodyString.toLowerCase();

        // fKey :: TP Item ->String
        const fKey = options.sortKey.startsWith('second') ? (
            secondWord
        ) : projName;

        const main = () => {
            const outline = editor.outline;

            // PREPARE TO UPDATE UNDOABLY
            outline.groupUndoAndChanges(() => {

                outline.evaluateItemPath(
                        '/@type=project'
                    )
                    .sort(comparing(fKey))
                    .reduceRight(
                        (nextSibling, oProj) => {
                            outline.insertItemsBefore(
                                oProj,
                                nextSibling
                            );
                            // binds it to the name 'nextSibling'
                            // for next item
                            return oProj;
                        },
                        // initialize nextSibling for the rightmost item
                        outline.root.firstchild
                    );
            });
            return JSON.stringify(options);
        };

        // GENERIC -------------------------------------------------

        // 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);
            };

        // main :: IO ()
        return main();
    };

    const ds = Application('TaskPaper').documents;
    return ds.length ? ds[0].evaluate({
        script: sortTopLevelProjects.toString(),
        withOptions: {
            sortKey: 'second'
        }
    }) : 'no document found in TaskPaper ...';
})();
1 Like

Thank you so much. This is super useful. I was trying to play with the code a little bit to understand it better. I am not sure what this part of the code does.

outline.evaluateItemPath(
                        '/@type=project'
                    )

Since I have tried to modified that to focus on different parts of the project to no avail. Am I misreading something into what is the job of that evaluate?

This is important to me, because I would still like to not include the Archive project into the sort or if possible, even change that evaluation to include some more complex cases. Is this something possible?
Again, Thank you very much.

That search path defines the set of objects that are sorted, so you could technically exclude any project named archive by editing that section of the code to:

outline.evaluateItemPath(
    '/(@type=project) and (not archive)'
)

but

In this implementation, excluding that project from the AZ order would, I think, invariably place it at the top of the file, before anything starting with aa or ab, ac etc, and I’m not sure if that’s really what you want.

1 Like

Thank you, that explains why no matter what I tried there, everything I didn’t want to sort ended up at the top of the file.

I know this is an old thread, but I am finally ready to understand it. I am baffled that TaskPaper cannot sort. But, with this script apparently, I can. The problem I have always had with scripts in TaskPaper is that, I don’t understand where they go and where I would find them to apply them. Sorry to be so dense but, I really do not understand. There is no script menu in TaskPaper. There is a command menu, but it makes no sense to have to paste in a whole JavaScript every time I want to use a TaskPaper script in a command menu??

I am interested in sorting the top level only and keeping the sub levels intact. That is, I want to collapse everything to the top level, sort, and then still be able to expand each top level without changing what was under it.

I am building a timeline of events that occurred over a five yer period. Some of the events are a paragraph and will sort easily. Many of the events have many paragraphs and lists and will not sort easily - unless, using TaskPaper, I can place them under the top idea and only sort the top idea. I hope I am making sense here?

Each event I am writing begins with a date formatted as Year/Month/Day. This allows for an alphabetical sort which is what I want to do. As I add new events to the timeline, I want to resort the timeline to make sure I am not missing anything or repeating myself.

Thanks

Scripting can be quite overwhelming. Just start with little steps. Remember, “The man who moves a mountain begins by carrying away small stones.”

I would recommend starting by following Jesse’s instructions in his manual. Then go to the page where I have been putting all the scripts together. Start with something easy. Run it and see if it works.

@jessegrosjean started a series of screencasts about using TaskPaper. Maybe he can use a break from coding his new app and create a new one in running and using scripts in TaskPaper. But if he is in the zone and cannot take time to make one, just ask another question in another post. Maybe someone will answer. I will go be out for a week, but I can help you once I get back.

Hope you are doing well my friend. God bless you.

Victor Gutierrez.

Thanks. I am connecting online when I choose to for the time being. Most of the time it is not so easy to check online manuals and videos as I don’t have easy access to what is online.

The only really important question that I am asking here though is, what is meant by a TaskPaper script? I can understand if a JavaScript is written that will work well in a TaskPaper document. I can use Keyboard Maestro to apply to selected text in a TaskPaper document.

But, when I hear that there is TaskPaper scripting, and I keep looking for some built in menu or built in trigger in TaskPaper to use such a script, and I can’t find one, that is why I get confused.

My only question really is, how are these scripts stored and, how are these scripts triggered? Again, if it as I just said and the scripts are intended to go in any app that will run java script on selected text in TaskPaper, such as Keyboard Maestro, then we are golden here. I already know what to do with scripts if this is what is meant here.

Thanks.

Post Script: From reviewing this page, my understandng was correct. Keyboard maestro will do nicely for exploring using scripting with TaskPaper.