For print, deleting blank rows in an outline can be a good way of avoiding empty bullet points.
For working on the screen, however, the structure of an outline may be clearer at a glance if outline sections are separated by a blank line.
Here draft script which alternates between:
- Pruning out empty childless rows in the visible part of the document, and (either only in selected rows, or in all visible rows if the selection is not extended)
- adding an empty row after each trailing leaf (each leaf which is the last of its siblings.
Expand disclosure triangle to view animation
See play button at bottom right
To test in Script Editor, set language selector at top left to JavaScript
rather than AppleScript
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();
})();
Keyboard Maestro version: