A macro for Jesse Grosjean's Bike Outliner.
For printing, deleting empty lines in an outline can be a good way of avoiding empty bullets.
For work on screen, however, a separating space between outline sections can make it easier to see structure at a glance.
This macro toggles between:
- Pruning out empty childless lines
- Adding a blank line after every trailing leaf (every childless row which is the last in a sequence of siblings)
The scope of the macro is limited to:
- EITHER lines which are visible and selected (if the selection is extended)
- OR all visible lines (if the selection is collapsed)
BIKE – Toggle empty rows between sections of outline.kmmacros (28 KB)
Expand disclosure triangle to view JS source
(() => {
"use strict";
// TOGGLE EMPTY ROWS IN BIKE OUTLINE
// EITHER Delete any childless empty rows in the
// visible part of the front document.
//
// OR (if no childless empty rows are seen)
// Add blank row after each trailing leaf row.
//
// i.e. after any leaf row which is the last
// of its siblings.
// If the SELECTION IS EXTENDED,
// then only the selected range of lines is affected
//
// Otherwise, *all* rows in any visible
// part of the document are affected out.
// Addition and pruning of spaces both
// reversible with ⌘Z
// Rob Trew @2022
// Ver 0.05
// const main :: IO ()
const main = () => {
const
bike = Application("Bike"),
doc = bike.documents.at(0);
return doc.exists() ? (() => {
const
selectionExtended = Boolean(
doc.selectedText()
),
childlessEmptyRows = doc.rows.where((
selectionExtended ? (
inSelection
) : (x => x)
)({
_and: [
{visible: true},
{name: ""},
{containsRows: false}
]
}));
return 0 < childlessEmptyRows.length ? (
// EITHER prune out blank rows
pruneRows(bike)(doc)(selectionExtended)(
childlessEmptyRows
)
// OR add blank rows after trailing leaves.
) : spaceAfterTrailingLeaves(bike)(doc);
})() : "No documents open in Bike";
};
// pruneRows :: Application -> Document -> Bool ->
// Rows -> IO String
const pruneRows = bike =>
doc => selectionExtended => childlessEmptyRows => {
const n = childlessEmptyRows.length;
return (
// Effect
bike.delete(childlessEmptyRows),
// Value (message string)
[
[`Deleted ${n} empty rows in`],
selectionExtended ? (
["extended selection of "]
) : [],
[`document: "${doc.name()}"`],
0 < n ? ["(⌘Z to undo)"] : []
]
.flat()
.join("\n")
);
};
// spaceAfterTrailingLeaves :: Application ->
// Document -> IO String
const spaceAfterTrailingLeaves = bike =>
doc => {
const
isVisibleLeaf = {
_and: [
{visible: true},
{_not: [{containsRows: true}]}
]
},
leaves = doc.rows.where(
Boolean(doc.selectedText()) ? (
inSelection(isVisibleLeaf)
) : isVisibleLeaf),
lastLeaves = leaves().filter(
x => null === x.nextSiblingRow()
),
n = lastLeaves.length;
return (
lastLeaves.forEach(
x => x.containerRow.rows.push(
new bike.Row({name: ""})
)
),
`Added ${n} blank rows, to space sections.`
);
};
// inSelecton :: Dict -> Dict
const inSelection = match =>
// JXA Where/Whose condition for Bike Rows
// further restricted to selected rows only.
({
_and: [
{selected: true},
match
]
});
// MAIN ---
return main();
})();
Expand disclosure triangle to view animation
See play button at bottom right