How do I find a Hook, out of the cold...?

HI, brand newbie, just started exploring Hook two days ago – so forgive the ignorance of this question.

How do I find an existing Hook-set? That is, I know it exists linking (say) several webpages together but I don’t remember any of the specific webpages to access the set.

Thanks much!

I think you may have hit the central weakness of the current design.

This, FWIW, is the core of what I do:

and as a temporary replacement for the missing set map, I sometimes also run a script to create a menu of all existing links in the Hook database.

This one is in JavaScript for Automation, and can be run from something like Keyboard Maestro, or from Script Editor with the language tab at top left set to JavaScript

JS Script - menu of all links (by name)
(() => {
    'use strict';

    ObjC.import('sqlite3');

    // HOOK: SLIGHTLY FULLER EXAMPLE OF TABLE-READING
    // FROM JAVASCRIPT FOR AUTOMATION
    // (Menu shows labels, follows links)

    // Rob Trew 2019

    const main = () => {
        const multipleSelections = true;
        return sj(either(
            msg => msg,
            xs => {
                const
                    links = nubBy(
                        a => b => snd(a) === snd(b),
                        xs
                    ),
                    addrs = map(fst, links),
                    labels = map(snd, links),
                    sa = standardSEAdditions();
                return bindLR(
                    showMenuLR(true, 'Hook links', labels),
                    choices => map(
                        x => {
                            const
                                strAddr = addrs[elemIndex(x, labels).Just],
                                strURL = strAddr.startsWith('/') ? (
                                    'file://' + strAddr + '/' + x
                                ) : strAddr;
                            return (
                                sa.activate(),
                                sa.openLocation(strURL),
                                strURL
                            );
                        },
                        choices
                    )
                );
            },
            linksFromHooKDBPathLR(
                '~/Library/Application Support/' +
                'com.cogsciapps.hook/hook.sqlite'
            )
        ));
    };


    // HOOK.APP - SIMPLEST LISTING OF LINKS VIA SQLITE

    // linkAndLabelFromMeta :: String -> [String]
    const linkAndLabelFromMeta = s => {
        const xs = s.split('$$$');
        return 1 < xs.length ? (
            [xs[0], base64decode(xs[1])]
        ) : [s, ''];
    };

    // linksFromHooKDBPathLR :: FilePath -> Either String [String]
    const linksFromHooKDBPathLR = strDBPath => {
        const
            SQLITE_OK = parseInt($.SQLITE_OK, 10),
            SQLITE_ROW = parseInt($.SQLITE_ROW, 10),
            ppDb = Ref(),
            strSQL =
            'SELECT srcMetaString, destMetaString, ' +
            'COALESCE(path, "") as folder, ' +
            'COALESCE(name, "") as fileName ' +
            'FROM link l LEFT JOIN fileinfo f ' +
            'ON l.dest=f.fileid ' +
            'ORDER by src',
            colText = curry($.sqlite3_column_text);

        return bindLR(
            bindLR(
                SQLITE_OK !== $.sqlite3_open(filePath(strDBPath), ppDb) ? (
                    Left($.sqlite3_errmsg(fst(ppDb)))
                ) : Right(fst(ppDb)),
                db => {
                    const ppStmt = Ref();
                    return SQLITE_OK !== $.sqlite3_prepare_v2(
                        db, strSQL, -1, ppStmt, Ref()
                    ) ? (
                        Left($.sqlite3_errmsg(db))
                    ) : Right(Tuple3(
                        db,
                        fst(ppStmt),
                        enumFromTo(
                            0,
                            $.sqlite3_column_count(ppStmt[0]) - 1
                        )
                    ));
                }
            ),
            // (Link, labe) from all available rows in the table:
            tpl => Right(
                sortBy(mappendComparing([snd]),
                    concatMap(
                        x => {
                            const [from, to] = map(
                                linkAndLabelFromMeta,
                                x.slice(0, 1)
                            ).concat([x.slice(2)]);
                            return (0 < (
                                fst(to).length + snd(to).length)) ? (
                                [from, to]
                            ) : [from];
                        },
                        unfoldr(
                            stmt => SQLITE_ROW !== $.sqlite3_step(stmt) ? (
                                $.sqlite3_finalize(stmt),
                                $.sqlite3_close(fst(tpl)),
                                Nothing()
                            ) : Just(
                                Tuple(
                                    map(colText(stmt), tpl[2]),
                                    stmt
                                )
                            ),
                            snd(tpl)
                        )
                    )
                )
            )
        );
    };

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

    // base64decode :: String -> String
    const base64decode = s =>
        ObjC.unwrap(
            $.NSString.alloc.initWithDataEncoding(
                $.NSData.alloc.initWithBase64EncodedStringOptions(
                    s, 0
                ),
                $.NSUTF8StringEncoding
            )
        );

    // showMenuLR :: Bool -> String -> [String] -> Either String [String]
    const showMenuLR = (blnMult, title, xs) =>
        0 < xs.length ? (() => {
            const sa = standardSEAdditions();
            sa.activate();
            const v = sa.chooseFromList(xs, {
                withTitle: title,
                withPrompt: 'Select' + (
                    blnMult ? ' one or more of ' +
                    xs.length.toString() : ':'
                ),
                defaultItems: xs[0],
                okButtonName: 'OK',
                cancelButtonName: 'Cancel',
                multipleSelectionsAllowed: blnMult,
                emptySelectionAllowed: false
            });
            return Array.isArray(v) ? (
                Right(v)
            ) : Left('User cancelled ' + title + ' menu.');
        })() : Left(title + ': No items to choose from.');

    // standardSEAdditions :: () -> Application
    const standardSEAdditions = () =>
        Object.assign(Application('System Events'), {
            includeStandardAdditions: true
        });

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

    // Just :: a -> Maybe a
    const Just = x => ({
        type: 'Maybe',
        Nothing: false,
        Just: x
    });

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

    // Nothing :: Maybe a
    const Nothing = () => ({
        type: 'Maybe',
        Nothing: true,
    });

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

    // Tuple (,) :: a -> b -> (a, b)
    const Tuple = (a, b) => ({
        type: 'Tuple',
        '0': a,
        '1': b,
        length: 2
    });

    // Tuple3 (,,) :: a -> b -> c -> (a, b, c)
    const Tuple3 = (a, b, c) => ({
        type: 'Tuple3',
        '0': a,
        '1': b,
        '2': c,
        length: 3
    });

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

    // compare :: a -> a -> Ordering
    const compare = (a, b) =>
        a < b ? -1 : (a > b ? 1 : 0);

    // comparing :: (a -> b) -> (a -> a -> Ordering)
    const comparing = f =>
        (x, y) => {
            const
                a = f(x),
                b = f(y);
            return a < b ? -1 : (a > b ? 1 : 0);
        };

    // concatMap :: (a -> [b]) -> [a] -> [b]
    const concatMap = (f, xs) =>
        xs.flatMap(f);

    // curry :: ((a, b) -> c) -> a -> b -> c
    const curry = f => a => b => f(a, b);

    // either :: (a -> c) -> (b -> c) -> Either a b -> c
    const either = (fl, fr, e) =>
        'Either' === e.type ? (
            undefined !== e.Left ? (
                fl(e.Left)
            ) : fr(e.Right)
        ) : undefined;

    // elemIndex :: Eq a => a -> [a] -> Maybe Int
    const elemIndex = (x, xs) => {
        const i = xs.indexOf(x);
        return -1 === i ? (
            Nothing()
        ) : Just(i);
    };

    // enumFromTo :: Int -> Int -> [Int]
    const enumFromTo = (m, n) =>
        Array.from({
            length: 1 + n - m
        }, (_, i) => m + i);

    // filePath :: String -> FilePath
    const filePath = s =>
        ObjC.unwrap(ObjC.wrap(s)
            .stringByStandardizingPath);

    // fst :: (a, b) -> a
    const fst = tpl => tpl[0];

    // identity :: a -> a
    const identity = x => x;

    // intercalate :: String -> [String] -> String
    const intercalate = s => xs =>
        xs.join(s);

    // mappendComparing :: [(a -> b)] -> (a -> a -> Ordering)
    const mappendComparing = fs =>
        (x, y) => fs.reduce(
            (ordr, f) => (ordr || compare(f(x), f(y))),
            0
        );

    // map :: (a -> b) -> [a] -> [b]
    const map = (f, xs) =>
        (Array.isArray(xs) ? (
            xs
        ) : xs.split('')).map(f);

    // Default value (v) if m.Nothing, or f(m.Just)

    // maybe :: b -> (a -> b) -> Maybe a -> b
    const maybe = v => f => m =>
        m.Nothing ? v : f(m.Just);

    // nubBy :: (a -> a -> Bool) -> [a] -> [a]
    const nubBy = (fEq, xs) => {
        const go = xs => 0 < xs.length ? (() => {
            const x = xs[0];
            return [x].concat(
                go(xs.slice(1)
                    .filter(y => !fEq(x)(y))
                )
            )
        })() : [];
        return go(xs);
    };

    // showJSON :: a -> String
    const sj = x => JSON.stringify(x, null, 2);

    // showLog :: a -> IO ()
    const showLog = (...args) =>
        console.log(
            args
            .map(JSON.stringify)
            .join(' -> ')
        );

    // snd :: (a, b) -> b
    const snd = tpl => tpl[1];

    // sortBy :: (a -> a -> Ordering) -> [a] -> [a]
    const sortBy = (f, xs) =>
        xs.slice()
        .sort(f);

    // unfoldr :: (b -> Maybe (a, b)) -> b -> [a]
    const unfoldr = (f, v) => {
        let
            xr = [v, v],
            xs = [];
        while (true) {
            const mb = f(xr[1]);
            if (mb.Nothing) {
                return xs
            } else {
                xr = mb.Just;
                xs.push(xr[0])
            }
        }
    };

    // unlines :: [String] -> String
    const unlines = xs => xs.join('\n');

    // until :: (a -> Bool) -> (a -> a) -> a -> a
    const until = p => f => x => {
        let v = x;
        while (!p(v)) v = f(v);
        return v;
    };

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

Welcome to the forum, @MattN.

Rob is right that this is an important function that is currently missing from Hook. It is on the product road map. We are working on an interesting way to support this. We expect to produce more than one way to support this.

For searching for Hook-linked files, you can use Spotlight tag:Hook, assuming you enabled Hook tagging, which is a default Hook preference (currently in Hook’s General tab, but we expect this setting to migrate to a new tab later).

Thanks for the replies – I understand.

Basically what I need is an “anchor” of some sort. For my purposes, and until something better is developed by the team, I am pretty sure that a note in the Notes app, or perhaps a simple text file, will suffice.

Another possibility might be to “Make Hook File” but that doesn’t seem to do anything. For example, I am trying to use an email (in Mac Mail). I invoke Hook and then click “Make Hook File” but the Hook window just disappears and, as far as I can tell, nothing else happens. Definitely no file is created in the “Hook Files” folder of my Hook folder. And a search for *.hook returns no hits.

Am I doing something wrong? Also, to be honest, the help page on Make Hook File (https://hookproductivity.com/help/hook-window/make-hook-file/) is pretty opaque. But, that might be because it’s not working for me.

We are big fans of Pinboard here. That is how I keep track of web pages worth delving/remembering. If something is worth re-reading, I bookmark it with Pinboard.

Cognitive Productivity books and Pinboard

There’s some academic research on bookmarking services to suggest they are mainly used for later recall. However, in my book Cognitive Productivity with macOS®: 7 Principles for Getting Smarter with Knowledge , I described other uses for bookmarking services. Here are videos that are part of my Cognitive Productivity with macOS book that deal with Pinboard:

Here’s a coupon for the entire book. Unfortunately the lowest discount price I can set on Leanpub for a package id $4.99: Cognitive Productivity with… by Luc P. Beaudoin [PDF/iPad/Kindle]. The coupon expires Nov 30. Anyone on the forum or elsewhere is welcome to use the coupon. We’ll call it an early “cyber day” deal.

Pocket is an alternative to Pinboard. Personally, I use both. I like the “Pocket Worthy” recommendations. And I like having an alternative. And, well, I’m an information processing nut.

More on Pinboard

We’ll have more to say about Pinboard early in the new year. And depending on what comes out of this discussion, I could write a help page on using Pinboard.

Hook files

I use “Make Hook File” a lot and haven’t encountered this problem. And I haven’t heard reports of the like either. But it’s probably not a highly used feature unfortunately.

It’s normal for the Hook window to go away after the “Make Hook File” command is issued.

Is it possible that you do not have write access to the folder? Could you please try dragging a file in there?

I’ll discuss with others here at CogSci Apps to see if they have ideas what might be going on there.

I would love to hear how others are using “Make Hook File”.

This may be worthy of its own topic (bug report and/or discussion).

thanks for the feedback. We are currently updating the documentation, so we will look at that one again.