Help creating OmniOutliner Rows with RTF content from DT

Hello,

I use DEVONthink and OmniOutliner 5- but would like to improve their interoperability. What I am looking for is a script that can add the contents of a selected RTF to a new row in OmniOutliner. It would also be useful if the script could copy over the filename, URL, and spotlight comment for the selected RTF to the note field belonging to the same row in OmniOutliner.

If this script could work on multiple selections in DT (i.e. create multiple rows in OmniOutliner with RTF content from DT)…I would be in heaven!

Thanks for the help DT community.

Alex

Could you provide a screencapture what you are actually talking about? This sounds like a lot of data.

In omniOutliner 5, the most actively supported scripting API is now omniJS, rather than AppleScript or JXA.

As Korm explains, there are various mature and well-developed AppleScript solutions, but in case it’s of any interest, or in case the gradual sunsetting of the other APIs is relevant on your project timescale, here is the working kernel of an omniJS solution.

(It submits the omniJS script from JXA, (and reads the DT selection via JXA) so you can test this in Script Editor, if you want).

(The omniJS interface is fast – code runs in a JS interpreter embedded inside OmniOutliner, side-stepping the slower Apple Event traffic – they’ve adopted the same approach as TaskPaper)

(() => {

    // Rob Trew (c) 2018

    // Copy selected DEVONthink items to omniOutliner 5

    // (using the omniJS API, and submitting the
    // omniJS script from JXA)

    // Can be run from Script Editor with the language
    // drop-down set to Javascript,
    // from Keyboard Maestro in an Execute a Javascript for
    // Automation action, or from the Script Menu etc.

    // Topic: record plain text, or folder name
    // Note:  fileName + referenceURL + comment

    const main = () => {

        // DEVONTHINK SELECTION READ INTO A GENERIC TREE STRUCTURE

        // tree :: Tree {topic :: String, note :: String}
        const tree =
            map(fmapPureDT(
                    x => ({
                        // Text of file, or name of folder.
                        topic: x.plainText() || x.name(),
                        note: [
                            x.filename(),
                            x.referenceURL(),
                            x.comment()
                        ].join('\n')
                    })
                ),
                Application('DEVONthink Pro').selection()
            );

        // TREE STRUCTURE RECREATED AS OMNIOUTLINER ROWS

        const
            oo = Application('OmniOutliner'),
            ds = oo.documents;
        return (
            // Either the existing front OO document,
            // or a new OO document, if none open.
            1 > ds.length && ds.push(oo.Document()),
            oo.activate(),
            outlineOmniJSWithArgs(
                ooFromTree,
                JSON.stringify(tree)
            )
        );
    };

    // OO JS CONTEXT --------------------------------------
    const ooFromTree = jsonTree => {

        const main = () => {
            const
                tree = JSON.parse(jsonTree),
                strJSON = JSON.stringify(
                    rowsFromForest(
                        rootItem,
                        Array.isArray(tree) ? (
                            tree
                        ) : [tree]
                    )
                );
            return (
                document.editors[0].rootNode.expand(true),
                strJSON
            );
        };

        // GENERIC WRITING TO OO --------------------------

        // rowsFromForest :: OO Item -> [Tree] -> [OO Item]
        const rowsFromForest = (parent, trees) => {
            const go = parent => tree => {
                const
                    item = parent.addChild(
                        null,
                        x => Object.assign(
                            x, tree.root
                        )
                    );
                return (
                    tree.nest.map(go(item)),
                    item
                );
            };
            return trees.map(go(parent));
        };

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

    // JXA JS CONTEXT -------------------------------------

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

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

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

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

    // OMNIOUTLINER GENERIC -------------------------------

    // outlineOmniJSWithArgs :: Function -> [...OptionalArgs] -> a
    function outlineOmniJSWithArgs(f) {
        return Application('omniOutliner')
            .evaluateJavascript(
                `(${f})(${Array.from(arguments)
                .slice(1).map(JSON.stringify)})`
            );
    };

    // DEVONTHINK GENERIC ---------------------------------

    // fmapPureDT :: (DTItem -> a) -> DTItem  -> Tree a
    const fmapPureDT = f => item => {
        const go = x => Node(
            f(x),
            x.children()
            .map(go)
        );
        return go(item);
    };

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

1 Like

Thank you houthakker!
I appreciate the time you took to pull this together… it does exactly what I need!

I stumbled across the Omni-Automation documentation, but I wasn’t sure where to start. This is a great example and I am sure others will find it exceedingly helpful!

Thanks again,

Alex