Script :: Toggle spacing after outline sections

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:

  1. 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)
  2. 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
ScreenFlow


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:

2 Likes