The TaskPaper > Outline
menu gives us a rich set of keystrokes for either expanding or collapsing all or part of TaskPaper outlines to some degree.
For some reason (perhaps just laziness) I like to cycle all the things, rather than using one key for collapse and another for expand.
Here is a script, which you can attach to a single key (I happen to use ⌘L) for cycling selected outline items between three states:
Fully collapsed → all children visible → all descendants visible (-> fully collapsed again)
Or, as it is described in the manual of emacs OrgMode, which also uses this pattern:
,-> FOLDED -> CHILDREN -> SUBTREE --.
'-----------------------------------'
https://orgmode.org/guide/Visibility-cycling.html
JavaScript source
(() => {
'use strict';
// Outline folding cycle for TaskPaper 3
// In the style of OrgMode subtree cycling
// Rotate current subtree among the states:
// ,-> FOLDED -> CHILDREN -> SUBTREE --.
// '-----------------------------------'
// https://orgmode.org/guide/Visibility-cycling.html
// Rob Trew 2020
// Ver 0.01
// main :: IO ()
const main = () =>
bindLR(frontDocLR('TaskPaper'))(
doc => doc.evaluate({
script: tp3FoldCycle.toString(),
})
);
// tp3FoldCycle :: Editor -> Either String IO String
const tp3FoldCycle = editor => {
const
xs = editor.selection.selectedItems,
parentDepth = Math.min(
...xs.map(x => x.depth)
),
selectedParents = xs.filter(
x => parentDepth === x.depth && (
Boolean(x.bodyString)
)
);
return 0 < selectedParents.length ? (() => {
const
foldUp = xs => editor.setCollapsed(xs),
reveal = xs => editor.setExpanded(xs),
folded = x => editor.isCollapsed(x),
sample = selectedParents[0];
// THREE-STAGE OUTLINE-FOLDING CYCLE
// FOR SELECTED ITEMS.
return folded(sample) ? (
// CHILDREN visible but folded.
selectedParents.forEach(reveal),
selectedParents.flatMap(
x => x.children
).forEach(foldUp),
'-> Children'
) : sample.descendants.some(folded) ? (
// SUBTREE completely unfolded.
selectedParents.flatMap(
x => x.descendants
).forEach(reveal),
'-> SubTree'
) : (
// FOLDED selections.
selectedParents.forEach(foldUp),
'-> Folded'
);
})() : 'Nothing selected in TaskPaper.'
};
// ------------------------JXA------------------------
// fontDocLR :: IO () -> Either String Doc
const frontDocLR = appName => {
// Either the front document of the named app,
// or an explanatory message if no documents
// are open, or the application is not running.
const app = Application(appName);
return app.running() ? (() => {
const ds = app.documents;
return 0 < ds.length ? (
Right(ds.at(0))
) : Left('No documents open in ' + appName);
})() : Left(appName + ' is not running.');
};
// -----------------GENERIC FUNCTIONS------------------
// https://github.com/RobTrew/prelude-jxa
// Left :: a -> Either a b
const Left = x => ({
type: 'Either',
Left: x
});
// Right :: b -> Either a b
const Right = x => ({
type: 'Either',
Right: x
});
// bindLR (>>=) :: Either a ->
// (a -> Either b) -> Either b
const bindLR = m =>
mf => undefined !== m.Left ? (
m
) : mf(m.Right);
// MAIN ---
return main();
})();
Or as a Keyboard Maestro macro: