Two Questions: Quick Look / Paste as Bulleted List

You will find some earlier discussion of progress towards QuickLook support here:

Quicklook support

To copy a Bike document (or just selected rows, if the selection is extended) as a bulleted plain text (tab-indented) outline, there is a Keyboard Maestro macro here:

BIKE Outliner - Copy as Bulleted Plain Text - Keyboard Maestro Discourse

and if you don’t use Keyboard Maestro, you can also use something like FastScripts to bind a keystroke to the Copy as Bulleted Plain Text script below:

Expand disclosure triangle to view JS Source
(() => {
    "use strict";

    ObjC.import("AppKit");

    // Copy Bike document
    // (or Bike selected rows, if selected is extended)
    // as bulleted plain text.

    // RobTrew @2022
    // Ver 0.01

    // main :: IO ()
    const main = () => {
        const doc = Application("Bike").documents.at(0);

        return doc.exists() ? (
            copyText(
                bulletedTextFromBikeDoc(doc)
            )
        ) : "No document open in Bike";
    };

    // ---------------------- BIKE -----------------------

    // bulletedTextFromBikeDoc :: Bike Doc -> IO String
    const bulletedTextFromBikeDoc = doc => {
        const
            rows = Boolean(doc.selectedText()) ? (
                doc.rows.where({selected: true})
            ) : doc.rows;

        return bulletOutlineFromForest(
            forestFromIndentedLines(
                zip(
                    rows.level()
                )(
                    rows.name()
                )
            )
        );
    };

    // ----------------------- JXA -----------------------

    // copyText :: String -> IO String
    const copyText = s => {
        const pb = $.NSPasteboard.generalPasteboard;

        return (
            pb.clearContents,
            pb.setStringForType(
                $(s),
                $.NSPasteboardTypeString
            ),
            s
        );
    };

    // --------------------- GENERIC ---------------------

    // Node :: a -> [Tree a] -> Tree a
    const Node = v =>
    // Constructor for a Tree node which connects a
    // value of some kind to a list of zero or
    // more child trees.
        xs => ({
            type: "Node",
            root: v,
            nest: xs || []
        });


    // Tuple (,) :: a -> b -> (a, b)
    const Tuple = a =>
    // A pair of values, possibly of
    // different types.
        b => ({
            type: "Tuple",
            "0": a,
            "1": b,
            length: 2,
            *[Symbol.iterator]() {
                for (const k in this) {
                    if (!isNaN(k)) {
                        yield this[k];
                    }
                }
            }
        });


    // bulletOutlineFromForest :: Forest String -> String
    const bulletOutlineFromForest = trees => {
        const go = tabs => tree => {
            const txt = tree.root.text;

            return [
                Boolean(txt) ? (
                    `${tabs}- ${txt}`
                ) : `${tabs}`,
                ...tree.nest.flatMap(go(`\t${tabs}`))
            ];
        };

        return trees.flatMap(go("")).join("\n");
    };


    // forestFromIndentedLines :: [(Int, String)] ->
    // [Tree {text:String, body:Int}]
    const forestFromIndentedLines = tuples => {
        const go = xs =>
            0 < xs.length ? (() => {
            // First line and its sub-tree,
                const [depth, body] = xs[0],
                    [tree, rest] = span(x => depth < x[0])(
                        xs.slice(1)
                    );

                return [
                    Node({
                        text: body,
                        level: depth
                    })(go(tree))
                ]
                // followed by the rest.
                    .concat(go(rest));
            })() : [];

        return go(tuples);
    };


    // span :: (a -> Bool) -> [a] -> ([a], [a])
    const span = p =>
    // Longest prefix of xs consisting of elements which
    // all satisfy p, tupled with the remainder of xs.
        xs => {
            const i = xs.findIndex(x => !p(x));

            return -1 !== i ? (
                Tuple(xs.slice(0, i))(
                    xs.slice(i)
                )
            ) : Tuple(xs)([]);
        };


    // zip :: [a] -> [b] -> [(a, b)]
    const zip = xs =>
    // The paired members of xs and ys, up to
    // the length of the shorter of the two lists.
        ys => Array.from({
            length: Math.min(xs.length, ys.length)
        }, (_, i) => [xs[i], ys[i]]);

    return main();
})();

If you test the script in Script Editor.app, (making sure, of course, to copy the whole of it, scrolling right down to the bottom), you will need to set the language selector at top left to JavaScript rather than AppleScript.

See: Using Scripts - Bike

2 Likes