OO JS Context: extend selection to all items at same level

A script, using the new JSContext in OO 5.3 Pro, which can be run (on the macOS side) from Script Editor, Keyboard Maestro etc.

USE

  • select one or more rows (at one or more levels) in an OO Outline
  • run the script to extend the selection to all items at the same level(s) right across the outline.

TESTING ON MACOS

To test on macOS:

  • Copy the whole of the source code below, and paste it into Script Editor,
  • check that the Script Editor language selector (top left) is set to JavaScript
  • check that an outline is open in OO, and run.

The selection will be extended, and a list of selected topic texts will be returned.

(() => {
    'use strict';

    // ooJSContext :: () -> String
    const ooJSContext = () => {

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

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

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

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

        // Node :: a -> [Tree a] -> Tree a
        const Node = (v, xs) => ({
            type: 'Node',
            root: v, // any type of value (but must be consistent across tree)
            nest: xs
        });

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

        // concatMap :: (a -> [b]) -> [a] -> [b]
        const concatMap = (f, xs) =>
            xs.length > 0 ? [].concat.apply([], xs.map(f)) : [];

        // iterateUntil :: (a -> Bool) -> (a -> a) -> a -> [a]
        const iterateUntil = (p, f, x) => {
            const go = x => p(x) ? x : [x].concat(go(f(x)));
            return go(x);
        };

        // levels :: Tree a -> [[a]]
        const levels = tree =>
            map(xs => map(x => x.root, xs),
                iterateUntil(
                    xs => xs.length < 1,
                    xs => concatMap(x => x.nest, xs), [tree]
                )
            );

        // nub :: [a] -> [a]
        const nub = xs => nubBy((a, b) => a === b, xs);

        // nubBy :: (a -> a -> Bool) -> [a] -> [a]
        const nubBy = (p, xs) => {
            const mbx = xs.length > 0 ? (
                Just(xs[0])
            ) : Nothing();
            return mbx.Nothing ? [] : [mbx.Just].concat(
                nubBy(p, xs.slice(1)
                    .filter(y => !p(mbx.Just, y)))
            );
        };

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

        // showJSON :: a -> String
        const showJSON = x => JSON.stringify(x, null, 2);

        // unlines :: [String] -> String
        const unlines = xs => xs.join('\n');

        // OO ----------------------------------------------------------------

        // fmapPureOO :: (OOItem -> a) -> OOItem -> Tree OOItem
        const fmapPureOO = (f, item) => {
            const go = x => Node(f(x),
                x.hasChildren ? (
                    x.children.map(go)
                ) : []);
            return go(item);
        };

        // MAIN --------------------------------------------------------------
        const
            editor = document.editors[0],
            selns = editor.selectedNodes,
            lrNewSeln = bindLR(
                selns.length > 0 ? Right(
                    nubBy(
                        (a, b) => a === b,
                        map(x => x.level, selns)
                    )
                ) : Left('Nothing selected in OmniOutliner'),
                xs => {
                    const levelNodes = levels(
                        fmapPureOO(
                            x => editor.nodeForItem(x),
                            rootItem
                        )
                    );
                    return Right(unlines(concatMap(
                        iLevel => {
                            const ts = levelNodes[iLevel];
                            return (
                                editor.select(ts, true),
                                unlines(map(x => x.object.topic, ts))
                            );
                        },
                        xs
                    )));
                }
            );
        return lrNewSeln.Right || lrNewSeln.Left;
    };

    return Application('OmniOutliner')
        .evaluateJavascript(
            '(' + ooJSContext + ')()'
        );
})();
1 Like