TaskPaper 3 - toggle Expand All for sidebar Projects tree

Opens the TaskPaper 3 sidebar if it is closed.

Each successive use toggles the Expand AllCollapse All state of the Projects tree in the sidebar.

TaskPaper 3 - toggle Expand All for sidebar Projects tree.kmmacros (31.1 KB)

(Source code of JXA action below - requires Sierra macOS onwards, and KM 8)

(() => {

    // Click-Toggle Show Project in Sidebar of TaskPaper 3
    // DEPENDENCIES:
    //  Assumes installation of Keyboard Maestro 8, and TaskPaper 3
    // USE: This is a JavaScript for Automation script, which can be
    // tested in Script Editor (with the language tab set to JavaScript)
    // From Script Menu, or as a Keyboard Maestro
    // 'Execute a JavaScript for Automation' action

    // Rob Trew (c) 2018
    // MIT License
    // Ver 0.5 2018-01-11

    // 0.5 In case of wider sidebars set clicked X to W + (h-10)
    // 0.4 (Finally posted the correct version :-)
    // 0.3 (Simplified (>>=) bindMay chain)
    // 0.2 Added the Opt to Opt-Click

    // main :: JS Function -> FilePath -> a

    'use strict';

    // GENERIC FUNCTIONS --------------------------------------

    // Just :: a -> Just a
    const Just = x => ({
        type: 'Maybe',
        Nothing: false,
        Just: x
    });

    // Nothing :: () -> Nothing
    const Nothing = (optionalMsg) => ({
        type: 'Maybe',
        Nothing: true,
        msg: optionalMsg
    });

    // append (++) :: [a] -> [a] -> [a]
    const append = (xs, ys) => xs.concat(ys);

    // bindMay (>>=) :: Maybe a -> (a -> Maybe b) -> Maybe b
    const bindMay = (mb, mf) =>
        mb.Nothing ? mb : mf(mb.Just);

    // isMaybe :: a -> Bool
    const isMaybe = x =>
        x.type === 'Maybe';

    // map :: (a -> b) -> [a] -> [b]
    const map = (f, xs) => xs.map(f);

    // readFile :: FilePath -> IO String
    const readFile = strPath => {
        var error = $(),
            str = ObjC.unwrap(
                $.NSString.stringWithContentsOfFileEncodingError(
                    $(strPath)
                    .stringByStandardizingPath,
                    $.NSUTF8StringEncoding,
                    error
                )
            );
        return Boolean(error.code) ? (
            ObjC.unwrap(error.localizedDescription)
        ) : str;
    };

    // takeBaseName :: FilePath -> String
    const takeBaseName = strPath =>
        strPath !== '' ? (
            strPath[strPath.length - 1] !== '/' ? (
                strPath.split('/')
                .slice(-1)[0].split('.')[0]
            ) : ''
        ) : '';

    // takeExtension :: FilePath -> String
    const takeExtension = strPath => {
        const
            xs = strPath.split('.'),
            lng = xs.length;
        return lng > 1 ? (
            '.' + xs[lng - 1]
        ) : '';
    };

    // File name template -> temporary path
    // (Random digit sequence inserted between template base and extension)
    // tempFilePath :: String -> IO FilePath
    const tempFilePath = template =>
        ObjC.unwrap($.NSTemporaryDirectory()) +
        takeBaseName(template) + Math.random()
        .toString()
        .substring(3) + takeExtension(template);

    // MAIN ---------------------------------------------------

    // jsoDoScript :: Object (Dict | Array) -> IO ()
    const jsoDoScript = jso => {
        const strPath = tempFilePath('tmp.plist');
        return (
            Application('Keyboard Maestro Engine')
            .doScript((
                $(Array.isArray(jso) ? jso : [jso])
                .writeToFileAtomically(
                    $(strPath)
                    .stringByStandardizingPath,
                    true
                ),
                readFile(strPath)
            )),
            true
        );
    };

    const
        se = Application('System Events'),
        procs = se.applicationProcesses.where({
            name: 'TaskPaper'
        }),
        mbResult =
        bindMay(
            bindMay(
                bindMay(
                    procs.length > 0 ? (
                        Just(procs.at(0))
                    ) : Nothing('TaskPaper not running.'),
                    proc => {
                        const ws = proc.windows;
                        return ws.length > 0 ? (
                            Just(ws.at(0)
                                .splitterGroups.at(0)
                                .scrollAreas)
                        ) : Nothing('No TaskPaper windows open.')
                    }
                ),
                areas => areas.length > 1 ? (
                    Just(areas.at(0)
                        .outlines.at(0)
                        .rows.at(1)
                        .uiElements.at(0))
                ) : Nothing('Sidebar not open.')
            ),
            cell => {
                const [
                    x, y, w, h
                ] = append(cell.position(), cell.size());
                const [
                    sx, sy
                ] = map(
                    n => n.toString(), [x + (w - 10), y + (h / 2)]
                );
                return (
                    // Effect
                    jsoDoScript([{
                            "ReopenWindows": false,
                            "AllWindows": true,
                            "MacroActionType": "ActivateApplication",
                            "AlreadyActivatedActionType": "Normal",
                            "TimeOutAbortsMacro": true,
                            "Application": {
                                "NewFile": "/Applications/TaskPaper.app",
                                "BundleIdentifier": "com.hogbaysoftware.TaskPaper3.direct",
                                "Name": "TaskPaper"
                            }
                        }, {
                            "MacroActionType": "PauseUntil",
                            "TimeOutAbortsMacro": true,
                            "Conditions": {
                                "ConditionList": [{
                                    "ApplicationConditionType": "Active",
                                    "ConditionType": "Application",
                                    "Application": {
                                        "NewFile": "/Applications/TaskPaper.app",
                                        "BundleIdentifier": "com.hogbaysoftware.TaskPaper3.direct",
                                        "Name": "TaskPaper"
                                    }
                                }],
                                "ConditionListMatch": "Any"
                            }
                        },
                        {
                            "UseFormat": false,
                            "MacroActionType": "SetVariableToCalculation",
                            "Variable": "instanceMouseX",
                            "Text": "MOUSEX()"
                        },
                        {
                            "UseFormat": false,
                            "MacroActionType": "SetVariableToCalculation",
                            "Variable": "instanceMouseY",
                            "Text": "MOUSEY()"
                        },
                        {
                            "ClickCount": 0,
                            "DragVerticalPosition": "0",
                            "MacroActionType": "MouseMoveAndClick",
                            "Modifiers": 0,
                            "Button": 0,
                            "Fuzz": 15,
                            "Action": "Move",
                            "DisplayMatches": false,
                            "DragHorizontalPosition": "0",
                            "RestoreMouseLocation": false,
                            "VerticalPositionExpression": sy,
                            "RelativeCorner": "TopLeft",
                            "HorizontalPositionExpression": sx,
                            "Relative": "Absolute",
                            "MouseDrag": "None"
                        },
                        {
                            "Time": ".1",
                            "MacroActionType": "Pause",
                            "TimeOutAbortsMacro": true
                        },
                        {
                            "ClickCount": 1,
                            "DragVerticalPosition": "0",
                            "MacroActionType": "MouseMoveAndClick",
                            "Modifiers": 2048,
                            "Button": 0,
                            "Fuzz": 15,
                            "Action": "MoveAndClick",
                            "DisplayMatches": false,
                            "DragHorizontalPosition": "0",
                            "RestoreMouseLocation": false,
                            "VerticalPositionExpression": sy,
                            "RelativeCorner": "TopLeft",
                            "HorizontalPositionExpression": sx,
                            "Relative": "Absolute",
                            "MouseDrag": "None"
                        },
                        {
                            "ClickCount": 0,
                            "DragVerticalPosition": "0",
                            "MacroActionType": "MouseMoveAndClick",
                            "Modifiers": 0,
                            "Button": 0,
                            "Fuzz": 15,
                            "Action": "Move",
                            "DisplayMatches": false,
                            "DragHorizontalPosition": "0",
                            "RestoreMouseLocation": false,
                            "VerticalPositionExpression": "instanceMouseY",
                            "RelativeCorner": "TopLeft",
                            "HorizontalPositionExpression": "instanceMouseX",
                            "Relative": "Absolute",
                            "MouseDrag": "None"
                        }
                    ]),
                    Just([sx, sy])
                )
            }
        );
    return mbResult.Nothing ? (
        mbResult.msg
    ) : ('Clicked TaskPaper sidebar at: ' + mbResult.Just);
})();