nikitavoloboev / ama

Ask me anything
https://nikiv.dev
35 stars 2 forks source link

Safari keyboard navigation #43

Closed nojusmickus closed 5 years ago

nojusmickus commented 5 years ago

Hi Nikita, could you share how you open links after a google search in Safari using your keyboard? The native tab navigation from accessibility feature is too slow and I couldn't find anything similar to Keyboard navigation for Safari, thanks!

nikitavoloboev commented 5 years ago

I have macros that will open 1st/2nd/3rd link when I am on Google for me.

For example I have bound this macro to ctrl+v:

2019-04-02 at 19 28

And it will instantly open first result on Google for me.

I also use sVim and have bindings configured. Pressing d will open link hints and I can choose results by typing letters:

2019-04-02 at 19 30

nikitavoloboev commented 5 years ago

The macro calls this JXA:

(() => {
    'use strict';

    const main = () => {
        const
            linkIndex = 0,

            strXPath = "//*[@class='r']/a",
            saf = Application("Safari"),
            ws = saf.windows;
        return bindLR(
            0 < ws.length ? (
                Right(ws.at(0))
            ) : Left('No window open in Safari'),
            w => {
                const
                    xs = pageXPathHarvest(
                        saf, w, strXPath
                    );
                return 0 < xs.length ? (
                    // Safari effect

                    //tabsOpened(saf, w, xs),
                    nthLinkOpened(saf, xs, linkIndex),

                    // Keyboard Maestro value
                    Right(
                        xs.reduce(
                            (a, link) =>
                            `${a}[${link[0]}](${link[1]})\n`,
                            ''
                        )
                    )
                ) : Left(
                    'Perhaps not a Google search page ?\n' +
                    '(No links matching "' + strXPath + '")'
                );
            }
        );
    };

    // SAFARI ---------------------------------------------

    // Harvest elements from Safari by XPath pattern
    const pageXPathHarvest = (browser, oWin, strXPath) =>
        browser.doJavaScript(
            `(${xpathHarvest})("${strXPath}")`, { in
                : oWin.currentTab
            }
        );

    // tabsOpened :: Application -> Window -> (String, String) -> IO()
    const tabsOpened = (safari, oWin, links) => {
        const winTabs = oWin.tabs;
        links.map(link => winTabs.push(safari.Tab({
            url: link[1]
        })))
    };

    // nthLinkOpened :: Application -> (String, String) -> Int -> IO()
    const nthLinkOpened = (safari, links, i) =>
        (ds =>
            (
                ds.length < 1 && ds.push(safari.Document()),
                ds.at(0)
            )
            .url = links[i][1]
        )(safari.documents);

    // Harvesting function to run in the browser context
    const xpathHarvest = strPath => {
        const
            r = document.evaluate(strPath, document, null, 0, null),
            xs = [];
        let oNode;
        while (oNode = r.iterateNext()) {
            xs.push([oNode.text, oNode.href]);
        }
        return xs;
    };

    // GENERIC FUNCTIONS ----------------------------------

    // https://github.com/RobTrew/prelude-jxa

    // 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.Right !== undefined ? (
            mf(m.Right)
        ) : m;

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

I share all macros I use on Gumorad. 🙂

nojusmickus commented 5 years ago

Thank you :)