Copying From Rich Text Area to Named Clipboard - Formatting Lost

I have a macro that involves a fair bit of JS to focus on an area, do a CMD+A to select all, CMD+C to copy and this goes into a named clipboard. Despite the source being rich text, when it runs, I can see in the clipboard it is set as plain text, immediately.

If I copy that same text myself, then paste it directly into the clipboard in KM preferences, I can see the the formatting is retained.

I see no options here that might force it to strip the rich text that is copied. Copy to named clipboard macro seems pretty basic, not really obvious why this is happening. Any thoughts?

When text is copied to the NSPasteboard:

  • the copying application typically creates several pasteBoard items in different formats (e.g. plain text, RTF, HTML etc)
  • the pasting or consuming application (here KM) selects whatever it considers the most appropriate of those pasteboard items to work with.

My understanding of named Keyboard Maestro clipboards is that they are essentially Keyboard Maestro variables. All KM variables have the type text, and will take the plain text pasteboard item rather than any other.

To inspect the different pasteboard items when you copy something:

Clipboard Viewer (all textually representable data as JSON).kmmacros (4.9 KB)

Your understanding is mistaken...

action:Copy to Named Clipboard [Keyboard Maestro Wiki]

“Named Clipboards allow you to permanently store clipboard contents (like images or styled text) that can later be used to place on the System Clipboard.

The Cut, Copy, and Paste to/from Named Clipboard actions let you store or retrieve clipboard data via the System Clipboard.”

1 Like

Hey Lloyd,

  1. What web browser are you using?

  2. Please provide a specific example of text in a web page that's failing, so we can test directly.

-Chris

Ah, OK, so it's Google Chrome, but the rich text editor is CKEditor running in a Chrome extension. If I copy directly from that editor and paste into an RTF file, or into the named clipboard in the preferences panel, the formatting is retained. It is the action of running that command in KM that appears not to work.

I can't provide a URL for an example as this is an extension that is not public (internal company tool), but I could record a quick screencast to demo it happening tomorrow. Any suggestions for things I can try to include in the vide by way of diagnosis?

So, here is a video showing me copying the rich text into an RTF file and then directly in to the clipboard in KM:

Then, here is the same content which I highlight and then copy to KM clipboard using a hotkey to run that macro. As you can see formatting is not retained doing it this way:

Note - no audio/speech on these videos.

Once you have copied from CKEditor, but before you paste, try running the clipboard viewer macro in post 2 of this thread, and pasting here the JSON view of clipboard contents which it creates.

(If there is something about the formatted pasteboard item(s) which KM is not recognizing, then it might be possible to add a macro stage which pre-cooked the CKEditor formatted clipboard contents into a form more readily recognised by Keyboard Maestro)

I find, for example, copying some formatted text from this CKEditor demo:

That it creates pasteboard items of types:

  • public.utf8-plain-text
  • public.html

It doesn't create any public.rtf content, but the public.html pastes without any obvious problem into a KM named clipboard.

It seems that at some stage the public.utf8-plain-text content is getting selected rather than the public.html.

The script below replaces the public.utf8-plain-text and public.html pasteboard items with a single public.rtf pasteboard item, which does seem to paste OK into named variables.

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

    ObjC.import("AppKit");


    // Rob Trew @2022

    // Sketch of creating a clipboard containing only a
    // public.rtf pasteboard item, based on any existing
    // public.html.

    // No plain text pasteboard item is retained.

    const main = () =>
        bindLR(
            clipOfTypeLR("public.html")
        )(
            html => bindLR(
                rtfFromHTML(html)
            )(
                rtf => Right(
                    setClipOfTextType("public.rtf")(rtf)
                )
            )
        );


    // --------------------- 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);


    // clipOfTypeLR :: String -> Either String String
    const clipOfTypeLR = utiOrBundleID => {
        // ObjC.import("AppKit")
        const
            clip = ObjC.deepUnwrap(
                $.NSString.alloc.initWithDataEncoding(
                    $.NSPasteboard.generalPasteboard
                    .dataForType(utiOrBundleID),
                    $.NSUTF8StringEncoding
                )
            );


        return 0 < clip.length ? (
            Right(clip)
        ) : Left(
            "No clipboard content found " + (
                `for type '${utiOrBundleID}'`
            )
        );
    };


    // 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 = utiOrBundleID =>
        txt => {
            const pb = $.NSPasteboard.generalPasteboard;

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

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

It might be worth experimenting with placing this:

  • in an Execute JavaScript for Automation action in your macro,
  • after a general Copy action,
  • and before a set named clipboard to past clipboard 1 action

Though here, I notice, just this already suffices to copy formatted content from the CKEditor demo to a named KM clipboard:

Do they store a single pasteboard item, rather than the multiple items (different UTIs) typically contained in an NSPasteboard ?

Is it possible that selection of a particular pasteBoard item is affected by the type of any existing content in a named clipboard ?

i.e. if the existing contents is public.utf8-plain-text, might that affect selection between public.html and public.utf8-plain-text ?

This looks to be the problem.

Hey @Lloydi – try this:

Convert HTML Data on the Clipboard to RTF on the Clipboard v1.00.kmmacros (6.5 KB)
Keyboard Maestro Export

1 Like

Thank you for this. I tried hooking it up into my larger macro but it was still not working for me. So I created a simple macro that has a Copy command, your script, then it sets a named clipboard, like this:

When I run it, the named clipboard is still getting plain text, but if I then paste into an RTF file, I get the styled text. Same if I paste directly into the clipboard in KM prefs>Clipboards panel.

Real head-scratcher for me.

Can I ask some other questions on a related note?

In my current macro there are two processes where it copies from the CKEditor, pastes into two different named clipboards. Then I write to a file (with nothing, to make it blank), then I append text to a file from these two clipboards. When I look at how I do this with Rich Text, I note that if I choose Append text, the option for RTF is greyed out:

In fact, every option other than plain text is not available. Do you know why that is?

It seems unlikely that the "append" operation – straightforward enough over a simple linear structure like plain text – would prove readily definable for any of those more composite (tree-like) format definitions.

You need to copy the System Clipboard to your Named Clipboard.

See: action:Write to a File [Keyboard Maestro Wiki]

“Write or append the current system clipboard, a named clipboard, a variable, some text or some styled text (you can write but cannot append styled text).”

It's always a good idea to read the documentation...

I Googled around for “concatenating RTF files”, and there's not much of value to find. It would appear the task is not so easily accomplished...

I did find one promising resource – it looks like you might be able to concatenate RTF files using the textutil command line utility.