Sorting

Any chance of “sort siblings” becoming a Bike command, or will that be left for users to script?

Thanks.

Yes, I think so.

Not for the 1.0 release, but I think it’s good to add afterward. Might still be implemented as a script, but I think it make sense to start shipping scripts embedded into the app so that people can use them without learning how to install.

In either case (implemented as script, or in Bike app code) I think I want commands like this limited to a special “Commands” menu. I think they are great to have, but I don’t want them to drown out Bike’s core commands in menu listings. When an app starts getting long menus everywhere I start feeling like I don’t understand it.

4 Likes

I totally agree. Thanks.

Here is a first draft of a script for sorting the children of the selected row.

(As always, test carefully on dummy data before deciding that you want to use it on working material)

One could keep two copies:

  • one for AZ ascending sort,
  • one for ZA descending sort.

and these would differ only in the option line near the top, which should read either

const orderZA = false;

(for a vanilla ascending sort)

or:

const orderZA = true;

(to get a descending sort).


Expand disclosure triangle to view JS source
(() => {
    "use strict";

    // First draft of a `Sort children of selected line`
    // for Bike.

    // RobTrew @2022
    // Ver 0.02

    // --------------------- OPTION ----------------------

    // Edit the option value below to `true` if you need a
    // descending sort, or to `false` for ascending.

    const orderZA = false;

    // -------------- SORTED BIKE SIBLINGS ---------------
    // main :: IO ()
    const main = () => {
        const doc = Application("Bike").documents.at(0);

        return doc.exists() ? (() => {
            const
                rows = doc.rows,
                parentRow = rows.where({
                    _and: [
                        {selected: true},
                        {_not: [{
                            name: ""
                        }]}
                    ]
                }).at(0);

            return parentRow.exists() && (
                0 < parentRow.rows.length
            ) ? (() => {
                    const
                        children = parentRow.rows.where(
                            {_not: [{
                                name: ""
                            }]}
                        ),
                        sortOrder = orderZA ? (
                            flip(comparing(fst))
                        ) : comparing(fst);

                    return sortBy(sortOrder)(
                        zip(
                            children.name()
                        )(
                            children.id()
                        )
                    )
                    .map(
                        nameID => (
                            rows.byId(nameID[1]).move({
                                to: parentRow
                            }),
                            nameID[0]
                        )
                    )
                    .join("\n");
                })() : "No row with children selected.";
        })() : "No documents open in Bike";
    };

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

    // comparing :: Ord a => (b -> a) -> b -> b -> Ordering
    const comparing = f =>
    // The ordering of f(x) and f(y) as a value
    // drawn from {-1, 0, 1}, representing {LT, EQ, GT}.
        x => y => {
            const
                a = f(x),
                b = f(y);

            return a < b ? -1 : (a > b ? 1 : 0);
        };


    // flip :: (a -> b -> c) -> b -> a -> c
    const flip = op =>
    // The binary function op with
    // its arguments reversed.
        1 !== op.length ? (
            (a, b) => op(b, a)
        ) : (a => b => op(b)(a));


    // fst :: (a, b) -> a
    const fst = tpl =>
    // First member of a pair.
        tpl[0];


    // sortBy :: (a -> a -> Ordering) -> [a] -> [a]
    const sortBy = f =>
    // A copy of xs sorted by the comparator function f.
        xs => xs.slice()
        .sort((a, b) => f(a)(b));


    // zip :: [a] -> [b] -> [(a, b)]
    const zip = xs =>
    // The paired members of xs and ys, up to
    // the length of the shorter of the two lists.
        ys => Array.from({
            length: Math.min(xs.length, ys.length)
        }, (_, i) => [xs[i], ys[i]]);

    // MAIN ---
    return main();
})();

To test in Script Editor, set the language selector at top left to JavaScript.

See: Using Scripts - Bike

2 Likes

Keyboard Maestro versions here:

1 Like

@complexpoint Hmm … this KM macro is not working for me. (All previous ones supplied by you work just fine.) Maybe I’m doing something wrong. When I use the key commands nothing at all seems to happen (and, yes, the macro is activated).

If you’ve got both of those KM macros installed, and both have the same key-binding (as here), then the idea is to get a choice popping up (resolved by tapping A or Z)

If you can’t see a popup waiting for your interaction, then I would just try assigning different keybindings for each of the two macros.

(and/or experiment with the Keyboard Maestro Preferences > Palettes settings for Conflict palettes)

Note, incidentally that you need to be selecting a row in Bike. The sort applies to its immediate children, which will take their descendants with them.

My bad. KM was working fine. The macros were activated but … somehow I managed to deactivate the whole Bike group and did not notice. All fixed and KM macros work perfectly. Sorry about the false alarm!

1 Like

Good to hear :slight_smile:

Thanks for letting me know.

Sorry for digging up this thread.

How to sort selected row directly, not children?

I know I can group rows up and run the script, then ungroup rows… I still want a simple way.

Sorry to be slow – could you expand a bit ?

( I’m not sure what sorting one single row would mean )

Perhaps you want to sort only the highest level rows in the selection,
leaving the children in their existing order ?

Thanks for the quick reply! Sorry, I didn’t make it clear.

If I have a list like this and selected rows b, d, a.
image

I want to sort current level rows in the selection to a, b, d, not them child rows.
image

1 Like

I don’t think that JXA will easily enable us to sort only a selected subset of the children of row e in your diagrams. ( JXA doesn’t fully implement the move destination locations that are defined for AppleScript )

That would leave us with two routes:

  1. AppleScript (workable, but not very scaleable – terribly slow if your selected lists are long)
  2. Extracting the XML for those rows, deriving and inserting a sorted version, before deleting the original copies.

Version two will take me a little more thought, so unless anyone else gets there first, I will try to sketch something this (EU) evening.

1 Like

Thanks. I would find this functionality useful as well. This is essentially the way it works in OmniOutliner.

I still think sorting–defined this way–is fundamental enough that it would be worthwhile adding as a native feature in the future.

Interim report – more success than I expected with JXA where the subset of selected lines are at levels 2 and downwards (i.e. they have a parent row), but I’m hitting a type error with sorting selected subsets of top level rows in this way.

Might try a different route over the weekend.

2 Likes
  1. No rush on my part.
  2. Thanks!
1 Like

I don’t understand JXA, So I simply thought it was just a matter of changing the operation object in the script. Sorry…

And I tried to made a Keyboard Maestro macro in a ‘dirty way’. (befor a clean script comes out…


Sort selected rows A-Z.kmmacros.zip (2.5 KB)

1 Like

:+1: Indenting, sorting, and outdenting a subset does seem like a sensible working solution for the moment.

Shall we wait and see, before reaching for XSLT or XQuery etc, whether @jessegrosjean is thinking about adding any sorting to the menu system ?

1 Like

The native way will be the best!

I’m going to try and dig into this today. Providing a native solution would be easy enough, but it seems like a problem that scripting can’t do this sort of thing. So first approach will when I need to change to allow this to be scripted.