How can I copy rows without hiddened/collapsed rows

:black_large_square:︎ If I copied content like this:

image

:black_large_square:︎ I want to get/paste text like this: (only visible rows)

:black_large_square:︎ not to get/paste text like this: (include all rows)

1 Like

As a script, perhaps:

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

    ObjC.import("AppKit");

    // BIKE :: Copy visible rows in selection only
    // (Skipping hidden descendants)
    // Rob Trew @2023

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

        return doc.exists() ? (() => {
            const
                selectedRows = doc.rows.where({
                    selected: true,
                    visible: true
                }),
                report = [
                    [
                        "bike format",
                        "com.hogbaysoftware.bike.xml"
                    ],
                    [
                        "OPML format",
                        "org.opml.opml"
                    ],
                    [
                        "plain text format",
                        "public.utf8-plain-text"
                    ]
                ]
                .map(
                    ([formatName, uti], i) => (
                        setClipOfTextType(0 === i)(uti)(
                            doc.export({
                                from: selectedRows,
                                as: formatName,
                                all: false
                            })
                        ),
                        formatName
                    )
                )
                .join(", ");

            return `Copied visible selection as ${report}`;
        })() : "No document open in Bike.";
    };

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

    // setClipOfTextType :: String -> String -> IO String
    const setClipOfTextType = withClearing =>
        utiOrBundleID =>
            txt => {
                const pb = $.NSPasteboard.generalPasteboard;

                return (
                    withClearing && pb.clearContents,
                    pb.setStringForType(
                        $(txt),
                        utiOrBundleID
                    ),
                    txt
                );
            };

    // MAIN ---
    return main();
})();

To test in Script Editor, set language selector at top left to JavaScript, rather than AppleScript.

Using Scripts - Bike


I notice that RTF is not directly supported in the scripting interface at the moment for that purpose, but if needed, we could expand the script above to derive RTF from the HTML.

2 Likes

FWIW, draft of a version which adds RTF pasteboard content to the existing Bike, OPML, and plain text:

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

    ObjC.import("AppKit");

    // BIKE :: Copy visible rows in selection only
    // (Skipping hidden descendants)
    // Rob Trew @2023

    // 0.02
    // Added RTF pasteboard content to the existing
    // bike, opml, plain.

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

        return doc.exists() ? (() => {
            const
                selectedRows = doc.rows.where({
                    selected: true,
                    visible: true
                }),
                visibleContent = formatName =>
                    visibleRowsInFormat(doc)(
                        selectedRows
                    )(formatName),
                report = [
                    [
                        "bike format",
                        "com.hogbaysoftware.bike.xml"
                    ],
                    [
                        "OPML format",
                        "org.opml.opml"
                    ],
                    [
                        "plain text format",
                        "public.utf8-plain-text"
                    ],
                    [
                        "rich text format",
                        "public.rtf"
                    ]
                ].map(
                    ([formatName, uti], i) => (
                        setClipOfTextType(0 === i)(uti)(
                            "public.rtf" !== uti
                                ? visibleContent(formatName)
                                : rtfFromHTML(
                                    visibleContent(
                                        "bike format"
                                    )
                                ).Right || ""
                        ),
                        formatName.slice(0, -7)
                    )
                ).join(", ");

            return `Copied visible selection as ${report}`;
        })() : "No document open in Bike.";
    };

    // visibleRowsInFormat :: Bike Doc -> Bike Rows ->
    // String -> String
    const visibleRowsInFormat = doc =>
        rows => formatName => doc.export({
            from: rows,
            as: formatName,
            all: false
        });

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

    // rtfFromHTML :: String -> Either String String
    const rtfFromHTML = strHTML => {
        const
            as = $.NSAttributedString.alloc
            .initWithHTMLDocumentAttributes(
                $(strHTML)
                .dataUsingEncoding($.NSUTF8StringEncoding),
                0
            );

        return bindLR(
            "function" !== typeof as
            .dataFromRangeDocumentAttributesError ? (
                    Left(
                        "String could not be parsed as HTML"
                    )
                ) : Right(as)
        )(
        // Function bound if Right value obtained above:
            htmlAS => {
                const
                    error = $(),
                    rtfData = htmlAS
                    .dataFromRangeDocumentAttributesError({
                        "location": 0,
                        "length": htmlAS.length
                    }, {
                        DocumentType: "NSRTF"
                    },
                    error
                    );

                return Boolean(
                    ObjC.unwrap(rtfData) && !error.code
                ) ? Right(
                        ObjC.unwrap($.NSString.alloc
                        .initWithDataEncoding(
                            rtfData,
                            $.NSUTF8StringEncoding
                        ))
                    ) : Left(ObjC.unwrap(
                        error.localizedDescription
                    ));
            }
        );
    };


    // setClipOfTextType :: String -> String -> IO String
    const setClipOfTextType = withClearing =>
        utiOrBundleID =>
            txt => {
                const pb = $.NSPasteboard.generalPasteboard;

                return (
                    withClearing && pb.clearContents,
                    pb.setStringForType(
                        $(txt),
                        utiOrBundleID
                    ),
                    txt
                );
            };


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

    // Left :: a -> Either a b
    const Left = x => ({
        type: "Either",
        Left: x
    });

    // Right :: b -> Either a b
    const Right = x => ({
        type: "Either",
        Right: x
    });


    // bindLR (>>=) :: Either a ->
    // (a -> Either b) -> Either b
    const bindLR = m =>
        mf => m.Left ? (
            m
        ) : mf(m.Right);

    // MAIN ---
    return main();
})();

( Only tested on macOS 11.7.4, Bike 1.9 – 112 )

1 Like

A script is the only way now. I could add a “Copy Visible” command, but I worry it would rarely be found/used. Do any other outliner apps provide a command for copying only visible items?

I noticed that WorkFlowy and OmniOutliner both copy only the visible nodes by default, without a specific “copy visible” command.

Let make sure we are talking about the same thing. For me it seems like WorkFlowy is copying the folded rows. For example:

https://vimeo.com/808072323

Testing OmniOutliner in viewer mode does seem to copy only visible rows. I can see how that might be wanted sometimes, but it seems a weird default for me at the moment. If I have a row fully selected and it contains child rows it seems logical to me that they are copied.

OmniOutliner also copies all hidden descendants with Row selection.

(It also has a text selection mode which only copies text selected in the modal text editing field for a given row)

It’s certainly a confusing feature decision all round… for me it seems like OmniOutliner is only copying visible rows :slight_smile:

I think I see what is happening in OmniOutliner …

  1. If a row is collapsed it will only copy that row and no children no matter the selection
  2. If a row is expanded it will copy the row and children if the parent is select or if the parent and children are selected. So for example the fact that children are selected doesn’t seem to make a difference.

It seems important to me that the default would be to retain folded lines …

(In an editor of outline structures, one wants to be able to select and move a folded part without fearing that its contents might be lost :slight_smile: )

Is that the same as what I’m seeing ?

In the earlier screenshot, I selected the the folded Alpha (clicking the left of the disclosure triangle for a row selection), and pasted further, getting both parent and descendants …

Anyway, not much turns on it :slight_smile:

Looking at TaskPaper I added “Copy Displayed” option. I guess I will take that same approach unless someone has a better idea.

3 Likes

The latest Bike preview release adds Copy > Copy Displayed to handle this case:

3 Likes