Script :: Toggle numbering in selected rows

Adds or clears simple row numbering in a series of selected rows.

Notes:
1. Descendants of the selected rows are ignored.
       (for example this line would be unchanged)
2. Blank lines are ignored.

A Keyboard Maestro version:

BIKE Outliner – Toggle numbering of selected rows - Macro Library - Keyboard Maestro Discourse


And the script itself for testing in Script Editor.app
(with language selector at top left set to JavaScript)
or for using in other launchers like FastScripts and Alfred.

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

    // Toggle numbering of selected rows.
    // Adds or clears simple numbering for a
    // sequence of selected peers.

    // Notes:
    //   1. Descendants of the selected rows are ignored.
    //        (for example this line would be unchanged)
    //   2. Blank lines are ignored.

    // Rob Trew @2022
    //
    // Ver 0.03

    // -------- TOGGLE NUMBERING OF SELECTED ROWS --------
    // main :: IO ()
    const main = () => {
        const
            doc = Application("Bike").documents.at(0);

        return doc.exists() ? (() => {
            const
                inSelection = selected(),
                rows = doc.rows.where(inSelection),
                minIndent = Math.min(...rows.level()),
                topPeers = rows.where({
                    _and: [
                        minimumLevel(minIndent),
                        ...inSelection._and
                    ]
                });

            return numbersCleared(topPeers) ? (
                "Numbering cleared."
            ) : numberedPeers(topPeers);
        })() : "No documents open in Bike.";
    };


    // numbersCleared :: Bike Rows -> IO Bool
    const numbersCleared = peerRows => {
        const rgxNumbering = /^\s*\d+\. /u;

        return 0 < peerRows().flatMap(x => {
            const s = x.name();

            return rgxNumbering.test(s) ? [(
                x.name = s.replace(
                    rgxNumbering, ""
                ),
                1
            )] : [];
        }).length;
    };


    // numberedPeers :: Bike Rows -> IO String
    const numberedPeers = peerRows => {
        const nDigits = (`${peerRows.length}`).length;

        return (
            zipWith(
                row => k => row.name = k
            )(peerRows())(
                peerRows.name().map((s, i) => {
                    const
                        prefix = `${1 + i}`
                        .padStart(nDigits, " ");

                    return `${prefix}. ${s}`;
                })
            ),
            `Numbered up to ${peerRows.length}.`
        );
    };

    // selected :: () -> JXA Condition
    const selected = () => ({
        _and: [
            {selected: true},
            {_not: [{
                name: ""
            }]}
        ]
    });


    // minimumLevel :: n -> JXA Condition
    const minimumLevel = n => ({
        level: {
            _lessThanEquals: n
        }
    });


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

    // zipWith :: (a -> b -> c) -> [a] -> [b] -> [c]
    const zipWith = f =>
    // A list constructed by zipping with a
    // custom function, rather than with the
    // default tuple constructor.
        xs => ys => xs.map(
            (x, i) => f(x)(ys[i])
        ).slice(
            0, Math.min(xs.length, ys.length)
        );


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


See: Using Scripts - Bike

1 Like