BIKE Outliner – Toggle a checked ⇄ unchecked box prefix in selected lines

A checkbox (and checkbox state) toggling macro for Jesse Grosjean's Bike outliner

checkBoxCycle

BIKE – Toggle a checked ⇄ unchecked box prefix in selected lines.kmmacros (6.1 KB)


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

    // Cycle a set of prefix characters in selected
    // (non-empty) lines of Bike 1.3

    // Rob Trew @2022
    // Ver 0.01

    // --------------------- OPTION ----------------------
    // Which single-character prefixes to cycle ?
    // (Any space character is interpreted as a
    //  no-prefix stage in the cycle).
    const prefixCycle = "☐☑ ";

    // Application("Keyboard Maestro Engine")
    // .getvariable("prefixCycle");

    // ------------ PREFIX CHARACTER TOGGLED -------------
    // main :: IO ()
    const main = () => {
        const
            bike = Application("Bike"),
            doc = bike.documents.at(0);

        return doc.exists() ? (() => {
            const
                selectedNonEmptyRows = doc.rows.where({
                    selected: true,
                    _not: [{
                        name: ""
                    }]
                }),
                n = selectedNonEmptyRows.length;

            return Boolean(n) ? (
                bikeRowsPrefixCycled([...prefixCycle])(
                    selectedNonEmptyRows
                )
            ) : "No non-empty rows selected in Bike.";
        })() : "No documents open in Bike.";
    };

    // ------------------ PREFIX CYCLED ------------------

    // bikeRowsPrefixCycled :: [Char] -> Int ->
    // IO Bike Rows -> IO String
    const bikeRowsPrefixCycled = prefixList =>
        // Any selected non-empty rows in Bike cycled
        // to the the next prefix in prefixList
        selectedNonEmptyRows => {
            const
                n = selectedNonEmptyRows.length,
                plural = 1 < n ? "s" : "",
                [f, change] = (() => {
                    const
                        iNow = prefixList
                        .findIndex(
                            c => c === (
                                selectedNonEmptyRows
                                .at(0).name()[0]
                            )
                        ),
                        iNext = -1 !== iNow ? (
                            (1 + iNow) % prefixList.length
                        ) : 0,
                        nextPrefix = prefixList[iNext];

                    return [
                        updatedLine(prefixList)(iNext),
                        " " !== nextPrefix ? (
                            `Set ${nextPrefix}`
                        ) : "Cleared"
                    ];
                })();

            return (
                zipWith(row => s => row.name = s)(
                    selectedNonEmptyRows()
                )(
                    selectedNonEmptyRows.name().map(f)
                ),
                [
                    `${change} prefix`,
                    `in ${n} selected line${plural}.`
                ]
                .join("\n")
            );
        };


    // updatedLine :: [Char] -> Char -> String -> String
    const updatedLine = prefixes =>
        // A line in which the prefix has been
        // replaced by the next option in a cycle.
        // A space character in the prefixes list is
        // interpreted as no-prefix stage in the cycle.
        iNext => s => {
            const c = prefixes[iNext];

            return Boolean(s) ? (
                prefixes.includes(s[0]) ? (
                    c + s.slice(1)
                ) : `${c} ${s}`
            ).trimStart() : `${c} `;
        };


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

Other Keyboard Maestro macros for BIKE Outliner