Bike Outliner – Toggle empty rows between outline sections

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:

  1. Pruning out empty childless lines
  2. 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
ScreenFlow


Other Keyboard Maestro macros for BIKE Outliner

1 Like