This script opens the share sheet with a text representation of the Database in the Taskpaper format documented here:
https://support.omnigroup.com/omnifocus-taskpaper-reference/
In order to set desired tag, change the value in USER OPTIONS
.
Used some of Rob Trew’s wonderful generics available at: https://github.com/RobTrew/prelude-jxa
Link:
Code:
/*{
"type": "action"
}*/
// Twitter: @unlocked2412
(() => Object.assign(
new PlugIn.Action(selection => {
// USER OPTIONS --------------------------------------
const options = {
tag: 'Communications'
};
// OMNI JS CODE ---------------------------------------
const omniJSContext = () => {
// main :: IO ()
const main = () => {
const
tagName = options.tag,
tpText = ofTaskPaperFromTree(
fmapTree(
either(dictFromLeft)(
dictFromRight
)
)(
filteredTree(
either(constant(true))(
x => elem(tagName)(
x.tags.map(x => x.name)
)
)
)(
pureOFdbLR(library)
)
)
)
return new SharePanel([tpText]).show()
};
// OMNIFOCUS FUNCTIONS ------------------------------
const dictFromLeft = x => ({
text: x
})
const dictFromRight = x => {
const v = 'Project' !== x.constructor.name ? (
x
) : x.task
return ({
text: v.name,
note: v.note,
tags: v.tags.map(x => x.name)
})
}
// pureOFdbLR :: OF Item -> Tree Either String OF Item
const pureOFdbLR = item => {
const go = x => {
const k = x.constructor.name;
return ['Project', 'Task'].includes(k) ? (
fmapPureOF(Right)(x)
) : 'Folder' !== k ? (
Node(Left(k))((
'Database' !== k ? (
x
) : [x.inbox, x.library]
).map(go))
) : Node(Left('Folder: ' + x.name))(
x.children.map(go)
);
};
return go(item);
};
// fmapPureOF :: (OF Item -> a) -> OF Item -> Tree a
const fmapPureOF = f => item => {
const go = x => Node(f(x))((
'Project' !== x.constructor.name ? (
x
) : x.task
).children.map(go));
return go(item);
};
// ofTaskPaperFromTree :: Tree -> String
const ofTaskPaperFromTree = 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' ? (
'- ' + txt
) : txt + ':') +
(
blnTags ? ` @tags(${
intercalateS(',')(tags)
})` : ''
) +
(blnNotes ? (
'\n' + unlines(map(
s => strNext + s
)(lines(note)))
) : '') + (blnNest ? (
'\n' + unlines(map(go(strNext))(nest))
) : '')
) : '';
};
return go('')(x);
};
// GENERIC FUNCTIONS --------------------------------------------
// https://github.com/RobTrew/prelude-jxa
// 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 || []
});
// Right :: b -> Either a b
const Right = x => ({
type: 'Either',
Right: x
});
// constant :: a -> b -> a
const constant = k =>
_ => k;
// either :: (a -> c) -> (b -> c) -> Either a b -> c
const either = fl =>
fr => e => 'Either' === e.type ? (
undefined !== e.Left ? (
fl(e.Left)
) : fr(e.Right)
) : undefined;
// elem :: Eq a => a -> [a] -> Bool
// elem :: Char -> String -> Bool
const elem = x =>
xs => {
const t = xs.constructor.name;
return 'Array' !== t ? (
xs['Set' !== t ? 'includes' : 'has'](x)
) : xs.some(eq(x));
};
// eq (==) :: Eq a => a -> a -> Bool
const eq = a =>
// True when a and b are equivalent in the terms
// defined below for their shared data type.
b => {
const t = typeof a;
return t !== typeof b ? (
false
) : 'object' !== t ? (
'function' !== t ? (
a === b
) : a.toString() === b.toString()
) : (() => {
const kvs = Object.entries(a);
return kvs.length !== Object.keys(b).length ? (
false
) : kvs.every(([k, v]) => eq(v)(b[k]));
})();
};
// filteredTree (a -> Bool) -> Tree a -> Tree a
const filteredTree = p =>
// A tree including only those children
// which either match the predicate p, or have
// descendants which match the predicate p.
foldTree(x => xs =>
Node(x)(xs.filter(
tree => (0 < tree.nest.length) || (
p(tree.root)
)
))
);
// fmapTree :: (a -> b) -> Tree a -> Tree b
const fmapTree = f => {
// A new tree. The result of a structure-preserving
// application of f to each root in the existing tree.
const go = tree => Node(f(tree.root))(
tree.nest.map(go)
);
return go;
};
// foldTree :: (a -> [b] -> b) -> Tree a -> b
const foldTree = f => {
// The catamorphism on trees. A summary
// value obtained by a depth-first fold.
const go = tree => f(tree.root)(
tree.nest.map(go)
);
return go;
};
// intercalateS :: String -> [String] -> String
const intercalateS = s =>
// The concatenation of xs
// interspersed with copies of s.
xs => xs.join(s);
// lines :: String -> [String]
const lines = s =>
// A list of strings derived from a single
// newline-delimited string.
0 < s.length ? (
s.split(/[\r\n]/)
) : [];
// 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 => (
Array.isArray(xs) ? (
xs
) : xs.split('')
).map(f);
// or :: [Bool] -> Bool
const or = xs =>
xs.some(Boolean);
// 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');
// MAIN -----------------------------------------
return main()
};
return omniJSContext()
}), {
validate: selection => true
}
))();