Toggling a prototype on/off with a Keyboard Maestro keystroke

For quick application/clearing of frequently used prototypes, we can set up a Keyboard Maestro shortcut, defined in terms of JavaScript for Automation or AppleScript:

JavaScript for Automation source:

(() => {
    'use strict';

    // RobTrew (Twitter @ComplexPoint) 2019
    // Ver 0.02 (Keyboard Maestro version)

    // Name of prototype to toggle on/off for all selected notes.
    // Edit to exactly match the name of a prototype in your document:
    const
        kme = Application('Keyboard Maestro Engine'),
        strPrototype = kme.getvariable('prototypeName');

    // OR for use outside Keyboard Maestro, comment out the above,
    // and set the value of strPrototype here directly, e.g.

    // const strPrototype = 'MyCustomPrototype'

    // main :: Tbx IO ()
    const main = () =>
        // Named prototype toggled on or off for
        // all the selected notes.
        either(
            x => 'Not toggled: (' + x + ')',
            x => 0 < x.length ? ( // Name string not empty
                'Prototype now: ' + x
            ) : ('Prototype cleared'),
            bindLR(
                frontDocLR(),
                d => bindLR(
                    allSelectedNotesLR(d),
                    notes => {
                        const k = toggledValue(notes)(strPrototype);
                        return (
                            prototypesUpdated(notes)(k),
                            Right(k)
                        );
                    }
                )
            )
        );


    // TINDERBOX FUNCTIONS --------------------------------

    // allSelectedNotesLR :: Tbx Doc -> Either String [Tbx Note]
    const allSelectedNotesLR = doc => {
        // Either the selected notes, or an
        // explanatory message if none are selected.
        const selns = doc.selections;
        return 0 < selns.length ? (
            Right(selns())
        ) : Left('No note selected in ' + doc.name());
    };

    // frontDocLR :: Tbx IO () -> Either String Tbx Doc
    const frontDocLR = () => {
        // Either the front document in Tinderbox, or an
        // explanatory message if no documents are open.
        const ds = Application('Tinderbox').documents;
        return 0 < ds.length ? (
            Right(ds.at(0))
        ) : Left('No documents open in Tinderbox');
    };

    // prototypesUpdated :: [Tbx Note] -> String -> Tbx IO ()
    const prototypesUpdated = notes => k =>
        notes.forEach(x => {
            showLog('Changed:', x.name(), 'prototype', k);
            x.attributes.byName('Prototype').value = k;
        });

    // toggledValue :: [Tbx Note] -> String -> String
    const toggledValue = notes => k =>
        notes[0].attributes.byName('Prototype').value() !== k ? k : '';


    // GENERIC FUNCTIONS ----------------------------------
    // https://github.com/RobTrew/prelude-jxa

    // Left :: a -> Either a b
    const Left = x => ({
        type: 'Either',
        Left: x
    });

    // Right :: b -> Either a b
    const Right = x => ({
        type: 'Either',
        Right: x
    });

    // bindLR (>>=) :: Either a -> (a -> Either b) -> Either b
    const bindLR = (m, mf) =>
        undefined !== m.Left ? (
            m
        ) : mf(m.Right);

    // concat :: [[a]] -> [a]
    // concat :: [String] -> String
    const concat = xs =>
        0 < xs.length ? (() => {
            const unit = 'string' !== typeof xs[0] ? (
                []
            ) : '';
            return unit.concat.apply(unit, xs);
        })() : [];

    // either :: (a -> c) -> (b -> c) -> Either a b -> c
    const either = (fl, fr, e) =>
        'Either' === e.type ? (
            undefined !== e.Left ? (
                fl(e.Left)
            ) : fr(e.Right)
        ) : undefined;

    // showLog :: a -> IO ()
    const showLog = (...args) =>
        console.log(
            args
            .map(JSON.stringify)
            .join(' -> ')
        );

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

1 Like