Lack of Export = Is this the right tool for me?

Does anyone know if there are any plans to increase the built in export options? Such as:

  • Export to OmniOutliner
  • Export to Markdown?

I’ve had a long relationship with the OmniGroup. I can trace it back to trying to use OmniOutliner and then, like magic, Ethan Schoonover invented Kinkless GTD. I’ve been through every beta and every single version. I’ve often left the app to try other things, but I’ve always come back. OmniFocus has always been rock solid and a trusted system.

My central concern is the lack of adequate export. I’m tired of cobbling scripts trying to find ways of getting my data out of OmniFocus.

For example, I have 20 items in my inbox that I want to copy/paste into another app.

  1. A simply copy/paste loses any notes
  2. Copy as Taskpaper adds all costs of cruft such as " @parallel(true) @autodone(false)"

I’m not looking for a script or workaround-- I’m looking for something baked in. I’ve wrote about this way back in 2008 and it hasn’t changed.

I just can’t see sticking around any longer unless there are more export options such as “export to markdown” or export to OmniOutliner.

Maybe I’m missing something and others don’t struggle with this or have found a work around.

Well, I am not sure this would be useful for you :-)

FWIW, here is a OmniJS (iOS/Mac) script that opens the share sheet with a text representation of selected tasks preserving only task name and notes.

/*{
	"type": "action"
}*/
// Twitter: @unlocked2412
(() => Object.assign(
    new PlugIn.Action(selection => {

        // OMNI JS CODE ---------------------------------------
        const omniJSContext = () => {
            // main :: IO ()
            const main = () => {
                const
                    ts = selection.tasks,
                    strText = compose(
                        unlines,
                        map(taskPaperFromTree),
                        map(
                            fmapPureOF(x => ({
                                text: x.name,
                                note: x.note
                            }))
                        )
                    )(ts)
                return (
                    new SharePanel([strText]).show(),
                    strText
                )
            };


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

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

            // Node :: a -> [Tree a] -> Tree a
            const Node = v =>
                // Constructor for a Tree node which connects a
                // value of some kind to a list of zero or
                // more child trees.
                xs => ({
                    type: 'Node',
                    root: v,
                    nest: xs || []
                });

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

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

            // Tuple (,) :: a -> b -> (a, b)
            const Tuple = a =>
                b => ({
                    type: 'Tuple',
                    '0': a,
                    '1': b,
                    length: 2
                });

            // append (++) :: [a] -> [a] -> [a]
            const append = xs =>
                // A list obtained by the
                // concatenation of two others.
                ys => xs.concat(ys);

            // bind (>>=) :: Monad m => m a -> (a -> m b) -> m b
            const bind = m =>
                mf => (Array.isArray(m) ? (
                    bindList
                ) : (() => {
                    const t = m.type;
                    return 'Either' === t ? (
                        bindLR
                    ) : 'Maybe' === t ? (
                        bindMay
                    ) : 'Tuple' === t ? (
                        bindTuple
                    ) : ('function' === typeof m) ? (
                        bindFn
                    ) : undefined;
                })()(m)(mf));

            // bindFn (>>=) :: (a -> b) -> (b -> a -> c) -> a -> c
            const bindFn = f =>
                // Binary operator applied over f x and x.
                bop => x => bop(f(x))(x);

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

            // bindList (>>=) :: [a] -> (a -> [b]) -> [b]
            const bindList = xs =>
                mf => [...xs].flatMap(mf);

            // bindMay (>>=) :: Maybe a -> (a -> Maybe b) -> Maybe b
            const bindMay = mb =>
                // Nothing if mb is Nothing, or the application of the
                // (a -> Maybe b) function mf to the contents of mb.
                mf => mb.Nothing ? (
                    mb
                ) : mf(mb.Just);

            // bindTuple (>>=) :: Monoid a => (a, a) -> (a -> (a, b)) -> (a, b)
            const bindTuple = tpl =>
                f => {
                    const t2 = f(tpl[1]);
                    return Tuple(mappend(tpl[0])(t2[0]))(
                        t2[1]
                    );
                };

            // compose (<<<) :: (b -> c) -> (a -> b) -> a -> c
            const compose = (...fs) =>
                // A function defined by the right-to-left
                // composition of all the functions in fs.
                fs.reduce(
                    (f, g) => x => f(g(x)),
                    x => x
                );

            // concat :: [[a]] -> [a]
            // concat :: [String] -> String
            const concat = xs => (
                ys => 0 < ys.length ? (
                    ys.every(Array.isArray) ? (
                        []
                    ) : ''
                ).concat(...ys) : ys
            )(list(xs));

            // cons :: a -> [a] -> [a]
            const cons = x =>
                // A list constructed from the item x,
                // followed by the existing list xs.
                xs => Array.isArray(xs) ? (
                    [x].concat(xs)
                ) : 'GeneratorFunction' !== xs
                .constructor.constructor.name ? (
                    x + xs
                ) : ( // cons(x)(Generator)
                    function* () {
                        yield x;
                        let nxt = xs.next();
                        while (!nxt.done) {
                            yield nxt.value;
                            nxt = xs.next();
                        }
                    }
                )();

            // dropAround :: (a -> Bool) -> [a] -> [a]
            // dropAround :: (Char -> Bool) -> String -> String
            const dropAround = p =>
                xs => dropWhile(p)(
                    dropWhileEnd(p)(xs)
                );

            // dropWhile :: (a -> Bool) -> [a] -> [a]
            // dropWhile :: (Char -> Bool) -> String -> String
            const dropWhile = p =>
                xs => {
                    const n = xs.length;
                    return xs.slice(
                        0 < n ? until(
                            i => n === i || !p(xs[i])
                        )(i => 1 + i)(0) : 0
                    );
                };

            // dropWhileEnd :: (a -> Bool) -> [a] -> [a]
            // dropWhileEnd :: (Char -> Bool) -> String -> String
            const dropWhileEnd = p =>
                xs => {
                    let i = xs.length;
                    while (i-- && p(xs[i])) {}
                    return xs.slice(0, i + 1);
                };

            // enumFromTo :: Int -> Int -> [Int]
            const enumFromTo = m =>
                n => !isNaN(m) ? (
                    Array.from({
                        length: 1 + n - m
                    }, (_, i) => m + i)
                ) : enumFromTo_(m)(n);

            // enumFromTo_ :: Enum a => a -> a -> [a]
            const enumFromTo_ = m => n => {
                const [x, y] = [m, n].map(fromEnum),
                    b = x + (isNaN(m) ? 0 : m - x);
                return Array.from({
                    length: 1 + (y - x)
                }, (_, i) => toEnum(m)(b + i));
            };

            // foldl :: (a -> b -> a) -> a -> [b] -> a
            const foldl = f =>
                a => xs => [...xs].reduce(
                    (x, y) => f(x)(y),
                    a
                );

            // fromEnum :: Enum a => a -> Int
            const fromEnum = x =>
                typeof x !== 'string' ? (
                    x.constructor === Object ? (
                        x.value
                    ) : parseInt(Number(x))
                ) : x.codePointAt(0);

            // identity :: a -> a
            const identity = x =>
                // The identity function. (`id`, in Haskell)
                x;

            // isDigit :: Char -> Bool
            const isDigit = c => {
                const n = c.codePointAt(0);
                return 48 <= n && 57 >= n;
            };

            // join :: Monad m => m (m a) -> m a
            const join = x =>
                bind(x)(identity);

            // keys :: Dict -> [String]
            const keys = Object.keys;

            // length :: [a] -> Int
            const length = xs =>
                // Returns Infinity over objects without finite
                // length. This enables zip and zipWith to choose
                // the shorter argument when one is non-finite,
                // like cycle, repeat etc
                'GeneratorFunction' !== xs.constructor.constructor.name ? (
                    xs.length
                ) : Infinity;

            // lines :: String -> [String]
            const lines = s =>
                // A list of strings derived from a single
                // newline-delimited string.
                0 < s.length ? (
                    s.split(/[\r\n]/)
                ) : [];

            // list :: StringOrArrayLike b => b -> [a]
            const list = xs =>
                // xs itself, if it is an Array,
                // or an Array derived from xs.
                Array.isArray(xs) ? (
                    xs
                ) : Array.from(xs || []);

            // map :: (a -> b) -> [a] -> [b]
            const map = f =>
                // The list obtained by applying f
                // to each element of xs.
                // (The image of xs under f).
                xs => [...xs].map(f);

            // mappend (<>) :: Monoid a => a -> a -> a
            const mappend = a =>
                // Associative operation 
                // defined for various monoid types.
                b => (t => (Boolean(t) ? (
                    'Maybe' === t ? (
                        mappendMaybe
                    ) : mappendTuple
                ) : 'function' !== typeof a ? (
                    append
                ) : a.toString() !== 'x => y => f(y)(x)' ? (
                    mappendFn
                ) : mappendOrd)(a)(b))(a.type);

            // mappendFn :: Monoid b => (a -> b) -> (a -> b) -> (a -> b)
            const mappendFn = f =>
                g => x => mappend(f(x))(
                    g(x)
                );

            // mappendMaybe (<>) :: Maybe a -> Maybe a -> Maybe a
            const mappendMaybe = a =>
                b => a.Nothing ? (
                    b
                ) : b.Nothing ? (
                    a
                ) : Just(
                    mappend(a.Just)(
                        b.Just
                    )
                );

            // mappendOrd (<>) :: Ordering -> Ordering -> Ordering
            const mappendOrd = cmp =>
                cmp1 => a => b => {
                    const x = cmp(a)(b);
                    return 0 !== x ? (
                        x
                    ) : cmp1(a)(b);
                };

            // mappendTuple (<>) :: (a, b) -> (a, b) -> (a, b)
            const mappendTuple = t => t2 =>
                Tuple(
                    mappend(t[0])(
                        t1[0]
                    )
                )(mappend(t[1])(
                    t1[1]
                ));

            // min :: Ord a => a -> a -> a
            const min = a => b => b < a ? b : a;

            // negate :: Num -> Num
            const negate = n =>
                -n;

            // nest :: Tree a -> [a]
            const nest = tree => {
                // Allowing for lazy (on-demand) evaluation.
                // If the nest turns out to be a function –
                // rather than a list – that function is applied
                // here to the root, and returns a list.
                const xs = tree.nest;
                return 'function' !== typeof xs ? (
                    xs
                ) : xs(root(x));
            };

            // or :: [Bool] -> Bool
            const or = xs =>
                xs.some(Boolean);

            // read :: Read a => String -> a
            const read = JSON.parse;

            // replace :: String -> String -> String -> String
            // replace :: Regex -> String -> String -> String
            const replace = needle => strNew => strHaystack =>
                strHaystack.replace(
                    'string' !== typeof needle ? (
                        needle
                    ) : new RegExp(needle, 'g'),
                    strNew
                );

            // root :: Tree a -> a
            const root = tree => tree.root;

            // show :: a -> String
            // show :: a -> Int -> Indented String
            const show = x => {
                const
                    e = ('function' !== typeof x) ? (
                        x
                    ) : {
                        type: 'Function',
                        f: x
                    };
                return JSON.stringify(e, (_, v) => {
                    const
                        f = ((null !== v) && (undefined !== v)) ? (() => {
                            const t = v.type;
                            return 'Either' === t ? (
                                showLR
                            ) : 'Function' === t ? (
                                dct => 'λ' + dct.f.toString()
                            ) : 'Maybe' === t ? (
                                showMaybe
                            ) : 'Ordering' === t ? (
                                showOrdering
                            ) : 'Ratio' === t ? (
                                showRatio
                            ) : 'string' === typeof t && t.startsWith('Tuple') ? (
                                showTuple
                            ) : Array.isArray(v) ? (
                                showList
                            ) : undefined;
                        })() : showUndefined;
                    return Boolean(f) ? (
                        f(v)
                    ) : 'string' !== typeof v ? (
                        v
                    ) : v;
                })
            };

            // showLR :: Either a b -> String
            const showLR = lr => {
                const k = undefined !== lr.Left ? (
                    'Left'
                ) : 'Right';
                return k + '(' + unQuoted(show(lr[k])) + ')';
            };

            // showList :: [a] -> String
            const showList = xs =>
                '[' + xs.map(show)
                .join(', ')
                .replace(/[\"]/g, '') + ']';

            // showMaybe :: Maybe a -> String
            const showMaybe = mb =>
                mb.Nothing ? (
                    'Nothing'
                ) : 'Just(' + unQuoted(show(mb.Just)) + ')';

            // showOrdering :: Ordering -> String
            const showOrdering = e =>
                0 < e.value ? (
                    'GT'
                ) : 0 > e.value ? (
                    'LT'
                ) : 'EQ';

            // showRatio :: Ratio -> String
            const showRatio = r =>
                'Ratio' !== r.type ? (
                    r.toString()
                ) : r.n.toString() + (
                    1 !== r.d ? (
                        '/' + r.d.toString()
                    ) : ''
                );

            // showTuple :: Tuple -> String
            const showTuple = tpl =>
                '(' + enumFromTo(0)(tpl.length - 1)
                .map(x => unQuoted(show(tpl[x])))
                .join(',') + ')';

            // showUndefined :: () -> String
            const showUndefined = () => '(⊥)';

            // toEnum :: a -> Int -> a
            const toEnum = e =>
                // The first argument is a sample of the type
                // allowing the function to make the right mapping
                x => ({
                    'number': Number,
                    'string': String.fromCodePoint,
                    'boolean': Boolean,
                    'object': v => e.min + v
                } [typeof e])(x);

            // unQuoted :: String -> String
            const unQuoted = s =>
                dropAround(x => 34 === x.codePointAt(0))(
                    s
                );

            // unlines :: [String] -> String
            const unlines = xs =>
                // A single string formed by the intercalation
                // of a list of strings with the newline character.
                xs.join('\n');

            // until :: (a -> Bool) -> (a -> a) -> a -> a
            const until = p => f => x => {
                let v = x;
                while (!p(v)) v = f(v);
                return v;
            };

            // JS Trees ----------------------------------------------------
            // taskPaperFromTree :: Tree -> String
            const taskPaperFromTree = x => {
                const
                    rgxSpace = /\s+/g,
                    go = strIndent => x => {
                        const
                            nest = x.nest,
                            root = x.root,
                            txt = root.text || '',
                            tags = root.tags,
                            ks = Boolean(tags) ? (
                                Object.keys(tags)
                            ) : [],
                            note = root.note,
                            blnNotes = Boolean(note),
                            blnTags = ks.length > 0,
                            blnNest = nest.length > 0,
                            strNext = '\t' + strIndent;

                        return or([Boolean(txt), blnTags, blnNotes, blnNest]) ? (
                            strIndent + (root.type !== 'project' ? (
                                root.type !== 'note' ? (
                                    '- ' + txt
                                ) : txt
                            ) : txt + ':') +
                            (blnTags ? foldl(
                                t => k => {
                                    const v = tags[k];
                                    return t + (Boolean(v) ? (
                                        ' @' + k.replace(rgxSpace, '_') +
                                        (Boolean(v) && v !== true ? (
                                            '(' + v + ')'
                                        ) : '')
                                    ) : '');
                                })('')(
                                ks
                            ) : '') +
                            (blnNotes ? (
                                '\n' + unlines(map(
                                    s => strNext + s
                                )(lines(note)))
                            ) : '') + (blnNest ? (
                                '\n' + unlines(map(go(strNext))(nest))
                            ) : '')
                        ) : '';
                    };
                return go('')(x);
            };

            // OmniFocus OmniJS --------------------------------------------
            // fmapPureOF :: (OF Item -> a) -> OF Item -> Tree a
            const fmapPureOF = f => item => {
                const go = x => Node(f(x))(
                    x.children.map(go)
                );
                return go(item);
            };

            return main();
        };

        return omniJSContext()

    }), {
        validate: selection => selection.tasks.length > 0
    }
))();
3 Likes

@unlocked2412. I do appreciate you reaching out to share the script! Your script was easy to install, worked seamlessly, and was exactly what I wanted.

I just wish these were baked into the program or officially supported. I’ve had so many different scripts break over the years.

2 Likes

Could one of you post a sample of what the output looks like? Thanks!

Sure. I will do it as soon as I reach my desk.

1 Like

@MartinPacker, I added screenshots (and sample output), here:

1 Like

This topic was automatically closed 30 days after the last reply. New replies are no longer allowed.