Using Clipboard in Omni Automation

I want to get and set the contents of system clipboard in an OmniOutliner Automation JavaScript. I am aware of the Pasteboard page at omni-automation.com; however, it only shows how to use Editor.copyNodes to copy the currently selected nodes to the system clipboard.

I would like to know how to perform basic operations in JavaScript in an Omni Automation script (for Omni Outliner):

  1. Given str, a String, put str on the system clipboard.
  2. Set str to the contents of the clipboard, which contains a single unformatted text item.

An example of (1) why I want (1) is as part of a Keyboard Maestro macro put the topic of the current item of an outline on the system clipboard, switch to another application, and paste. Similarly, an example of using (2) would be a Keyboard Maestro macro that executed some steps that include copying a selection from another application, switching to Omni Outliner, and using the contents of the system clipboard to construct some text that will go in an outline item.

2 Likes

I’m keen to do something similar to this. I have a slightly strange document format and once I have the outlines finished I’m keen to get it into my editor (or a series of web forms). While I could try to find a series of integration points for these apps and corporate tools, it’s pretty hard.

The simpler solution for me is to generate markdown and put it straight into the pasteboard. Which I can then drop into the array of tools I need to work with.

I second this. Is there any word on an existing or upcoming way to get data in and out of the system clipboard from OmniOutliner Automation scripts?

The only way I could figure out how to do this was to wrap the OmniJS script in AppleScript. You can see what I did here:

This only copies from OmniJS to the clipboard, but it might be able to give you an idea how to do the reverse. I’m not sure.

(I’m literally at this very moment wishing I could run this on iOS, but if there’s a way to get things in and out of OmniJS on that platform I can’t find it.)

For OmniOutliner there does seem to be PasteBoard object.

https://omni-automation.com/omnioutliner/pasteboard.html

This seems to only be able to copy nodes; anyone have an idea of whether or not this will copy a string?

anyone have an idea of whether or not this will copy a string?

When you copy nodes, various types of data are placed in the clipboard, including a stringification of those nodes, which you can paste into a text editor.

The osascript evaluateJavascript method does give finer control (tho only available on macOS)

I personally wouldn’t call it from AppleScript though (in the way that @chipotle has bravely done, quoting the JS code as a string, which can get fiddly) – it’s a lot more straightforward to do from JXA. You just define a JS function in the JXA code, and pass its name to .evaluateJavaScript( … ) with a .toString() suffix to automatically obtain its source code as an oven-ready string, as in:

Application('OmniOutliner').evaluateJavascript(
    '(' + myOmniJSFunction.toString() + ')()'
)

The value returned by .evaluateJavascript() can then be placed in the clipboard by JXA.

Example: .evaluateJavascript() from JXA
(() => {
    'use strict';

    // OMNIJS CONTEXT -------------------------------------

    // fountainExport :: OmniJS () -> String
    function fountainExport() {
        return rootItem.descendants.map(
            item => '#'.repeat(item.level) + ' ' +
            item.topic + (
                item.note ? (
                    '\n= ' + item.note
                ) : ''
            )
        ).join('\n\n');
    }

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

    // main :: IO ()
    const main = () => {
        const
            oo = Application('OmniOutliner'),
            docs = oo.documents,
            sa = Object.assign(Application.currentApplication(), {
                includeStandardAdditions: true
            });

        return 0 < docs.length ? (() => {
            const strFountain = oo.evaluateJavascript(
                `(${fountainExport})()`
            );

            return Boolean(strFountain) ? (() => {
                const dct = sa.displayDialog(
                    'Export Fountain text to: ', {
                        buttons: ['Cancel', 'Clipboard', 'File'],
                        defaultButton: 'Clipboard',
                        cancelButton: 'Cancel'
                    }
                );
                return dct.gaveUp ? (
                    ''
                ) : 'Clipboard' === dct.buttonReturned ? (
                    sa.setTheClipboardTo(strFountain),
                    'Copied to clipboard'
                ) : (() => {
                    const fp = sa.chooseFileName({
                        withPrompt: 'Save as',
                        defaultName: (
                            takeBaseName(docs.at(0).name())
                        ) + ".fountain"
                    });
                    return alert('Fountain export')(
                        (fp instanceof Path ? (
                            writeFile(fp.toString())(strFountain) ? (
                                'written to:'
                            ) : 'could not be written to:'
                        ) : 'File could not be written to: ') + '\n' +
                        fp.toString()
                    )
                })();

            })() : 'No export string returned.'
        })() : 'No documents open in OmniOutliner';
    };

    // ----------------------------JXA----------------------------

    // alert :: String -> String -> IO String
    const alert = title => s => {
        const
            sa = Object.assign(Application('System Events'), {
                includeStandardAdditions: true
            });
        return (
            sa.activate(),
            sa.displayDialog(s, {
                withTitle: title,
                buttons: ['OK'],
                defaultButton: 'OK'
            }),
            s
        );
    };

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

    // takeBaseName :: FilePath -> String
    const takeBaseName = strPath =>
        ('' !== strPath) ? (
            ('/' !== strPath[strPath.length - 1]) ? (() => {
                const fn = strPath.split('/').slice(-1)[0];
                return fn.includes('.') ? (
                    fn.split('.').slice(0, -1).join('.')
                ) : fn;
            })() : ''
        ) : '';

    // writeFile :: FilePath -> String -> IO ()
    const writeFile = strPath => strText =>
        $.NSString.alloc.initWithUTF8String(strText)
        .writeToFileAtomicallyEncodingError(
            $(strPath)
            .stringByStandardizingPath, false,
            $.NSUTF8StringEncoding, null
        );

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

Hope this isn’t inappropriate, but I’ve been seeking a scripting solution that’s related to what’s being discussed here in this recent post re: a “Script to copy paste tags to select columns.”

Would appreciate any ideas / suggestions… Thanks!

I’m not well placed to get into the detail of that – only a very casual and occasional user of OO – but I think you are more likely to get help if you show rather than tell.

It’s always much more difficult than we expect for others to follow our verbal descriptions of these things – a pair of before and after screenshots might help people to recognise what you are trying to do.

2 Likes

Terrific idea. I just updated the post with those screenshots. Lets hope they can help move things forward!

Thanks, again!