Script to paste Safari URL& Title to Bike

I use the script below with FastScripts to copy the current Safari web page title & URL
I activate the script with a FastScripts keyboard command.

I would like to extend the script to paste the result directly in Bike or Drafts in Rich Text format.
A big bonus - the ability to add highlighted text (if needed) from the webpage at the same time

Thank you.

tell application "Safari" to tell document 1
	set the clipboard to "[" & name & "]{{ video & " URL & "}}"
end tell

Drafts is really outside the scope of this forum – I don’t know much about it, though my impression is that it is more a vehicle for plain text markup than for rich text.


If you copy a url and label from Safari or elsewhere with the Keyboard Maestro macro below, it will place Bike-pasteable rich text in the clipboard as well as a plain text MD link.

(The latest version is kept at RobTrew/copy-as-md-link: macOS Keyboard Maestro macro group – single keystroke to copy MD links from different applications. – click the green Code button to download from the Github page.


If you wanted a script which combined copying from a browser with pasting directly to a specified position in a specified Bike document, you would probably need to tell us more about how you would identify which document (and within it, where) to paste to.

1 Like

Here’s a vanilla version (for testing in Script Editor, set the language at top left to JavaScript)

It:

  • copies the page name and url of the front Safari document both as Bike rich text and as MD plain text, and
  • creates a rich text version of the link at the top of the front Bike document, if one is open.

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

    ObjC.import("AppKit");

    // Copy Bike and MD versions of a link to the
    // front Safari page,
    // and add rich text link to top of
    // front Bike document.

    // RobTrew @2023
    // Ver 0.01

    const main = () => {
        const page = Application("Safari").documents.at(0);

        return either(
            alert("Safari page name and url to Bike")
        )(
            mdVersion => mdVersion
        )(
            bindLR(
                page.exists() ? (() => {
                    const
                        label = page.name(),
                        url = page.url(),
                        md = `[${label}](${url})`,
                        xml = linkFromBike(url)(label);

                    return (
                        setClipOfTextType(true)(
                            "com.hogbaysoftware.bike.xml"
                        )(xml),
                        setClipOfTextType(false)(
                            "public.utf8-plain-text"
                        )(md),
                        Right([md, xml])
                    );
                })() : Left("No pages open in Safari.")
            )(
                ([md, xml]) => {
                    const
                        bike = Application("Bike"),
                        doc = bike.documents.at(0);

                    return doc.exists() ? (
                        doc.import({
                            from: xml,
                            as: "bike format"
                        }),
                        Right(md)
                    ) : Left("No documents open in Bike.");
                }
            )
        );
    };


    // linkFromBike :: URL String -> String -> XML String
    const linkFromBike = url =>
        label => (
            `<?xml version="1.0" encoding="UTF-8"?>
            <html>
            <head><meta charset="utf-8"/></head>
            <body><ul><li>
                <p><a href="${url}">${label}</a></p>
            </li></ul></body>
            </html>`
        );


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

    // alert :: String => String -> IO String
    const alert = title =>
        s => {
            const sa = Object.assign(
                Application("System Events"), {
                    includeStandardAdditions: true
                });

            return (
                sa.activate(),
                sa.displayDialog(s, {
                    withTitle: title,
                    buttons: ["OK"],
                    defaultButton: "OK"
                }),
                s
            );
        };

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

                return (
                    clear && 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);

    // either :: (a -> c) -> (b -> c) -> Either a b -> c
    const either = fl =>
        // Application of the function fl to the
        // contents of any Left value in e, or
        // the application of fr to its Right value.
        fr => e => e.Left ? (
            fl(e.Left)
        ) : fr(e.Right);

    // fmapLR (<$>) :: (b -> c) -> Either a b -> Either a c
    const fmapLR = f =>
        // Either f mapped into the contents of any Right
        // value in e, or e unchanged if is a Left value.
        e => "Left" in e ? (
            e
        ) : Right(f(e.Right));


    return main();
})();

See: Using Scripts - Bike

2 Likes

@complexpoint
Thank you; works great.
I will use it in my workflow to document research online.
I appreciate you sharing your skills.
Stan

2 Likes