CHOOSE MACRO from N with (Oldest | Newest) (Created | Modified | Used) Dates

A variant with a checkbox for ascending vs descending (most recent first) sort by some date type.

Otherwise similar to N Most Recent Macros (run, edited, or created)


CHOOSE MACRO from N with (Oldest | Newest) (Created | Modified | Used) Dates.kmmacros (17 KB)


Expand disclosure triangle to view JS source
const
    nMRU = Number(kmvar.Number_of_Macros),
    dateType = kmvar.Date_Type,
    boolAscending = !Boolean(Number(
        kmvar.Most_Recent_First
    )),
    timeLabel = boolAscending
        ? "oldest"
        : "newest";

return (() => {
    "use strict";

    // N oldest or newest macros in terms of: 

    // modificationDate, or
    // creationDate, or
    // usedDate

    // Rob Trew @2024
    // Ver 0.3

    // ---------------------- MAIN -----------------------
    const main = () =>
        either(
            alert("First N macros by date of creation edit or use")
        )(
            xs => [
                `${nMRU} ${timeLabel} by ${dateType} Date:`,
                ...xs.map(x => {
                    const when = taskPaperDateString(x.someDate);

                    return `${x.id}__${when}    ${x.macroName} `;
                })
            ]
                .join("\n")
        )(
            oldestOrNewestMacrosLR(dateType)(
                boolAscending
            )(nMRU)
        );


    // ----------------- KM MACRO DATES ------------------

    // oldestOrNewestMacrosLR :: ("Creation" | "Modification" | "Used") ->
    // Bool -> Int -> String
    const oldestOrNewestMacrosLR = dateType =>
        ascending => n => {
            const
                dateTypes = {
                    "Creation": "creationDate",
                    "Used": "usedDate",
                    "Modification": "modificationDate"
                },
                keys = Object.keys(dateTypes);

            return fmapLR(
                macros => (
                    ascending
                        ? sortOn
                        : sortDownOn
                )(
                    dict => dict.someDate
                )(
                    zipWithN(
                        id => groupName => macroName => someDate =>
                            ({ id, groupName, macroName, someDate }),
                        macros.id(),
                        macros.macroGroup.name(),
                        macros.name(),
                        macros[dateTypes[dateType]]()
                    )
                )
                    .slice(0, n)
            )(
                dateType in dateTypes
                    ? Right(Application("Keyboard Maestro").macros)
                    : Left(`dateType should be drawn from {${keys} } `)
            );
        };

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

    // ------------------- DATE STRING -------------------

    // taskPaperDateString :: Date -> String
    const taskPaperDateString = dte =>
        [...second(t => t.slice(0, 5))(
            iso8601Local(dte).split("T")
        )].join(" ");

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


    // Tuple (,) :: a -> b -> (a, b)
    const Tuple = a =>
        // A pair of values, possibly of
        // different types.
        b => ({
            type: "Tuple",
            "0": a,
            "1": b,
            length: 2,
            *[Symbol.iterator]() {
                for (const k in this) {
                    if (!isNaN(k)) {
                        yield this[k];
                    }
                }
            }
        });


    // 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 => "Left" in e
            ? 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));


    // comparing :: Ord a => (b -> a) -> b -> b -> Ordering
    const comparing = f =>
        // The ordering of f(x) and f(y) as a value
        // drawn from {-1, 0, 1}, representing {LT, EQ, GT}.
        x => y => {
            const
                a = f(x),
                b = f(y);

            return a < b
                ? -1
                : a > b
                    ? 1
                    : 0;
        };


    // flip :: (a -> b -> c) -> b -> a -> c
    const flip = op =>
        // The binary function op with
        // its arguments reversed.
        1 !== op.length
            ? (a, b) => op(b, a)
            : (a => b => op(b)(a));


    // iso8601Local :: Date -> String
    const iso8601Local = dte =>
        new Date(dte - (6E4 * dte.getTimezoneOffset()))
            .toISOString();


    // second :: (a -> b) -> ((c, a) -> (c, b))
    const second = f =>
        // A function over a simple value lifted
        // to a function over a tuple.
        // f (a, b) -> (a, f(b))
        xy => Tuple(
            xy[0]
        )(
            f(xy[1])
        );


    // sortBy :: (a -> a -> Ordering) -> [a] -> [a]
    const sortBy = f =>
        // A copy of xs sorted by the comparator function f.
        xs => xs.slice()
            .sort((a, b) => f(a)(b));


    // sortDownOn :: Ord b => (a -> b) -> [a] -> [a]
    const sortDownOn = f =>
        // Equivalent to sortBy(comparing(f)), but with f(x)
        // evaluated only once for each x in xs.
        // ('Schwartzian' decorate-sort-undecorate).
        xs => sortBy(
            flip(comparing(x => x[0]))
        )(
            xs.map(x => [f(x), x])
        )
            .map(x => x[1]);


    // sortOn :: Ord b => (a -> b) -> [a] -> [a]
    const sortOn = f =>
        // Equivalent to sortBy(comparing(f)), but with f(x)
        // evaluated only once for each x in xs.
        // ('Schwartzian' decorate-sort-undecorate).
        xs => sortBy(
            comparing(x => x[0])
        )(
            xs.map(x => [f(x), x])
        )
            .map(x => x[1]);


    // toUpper :: String -> String
    const toUpper = s =>
        s.toLocaleUpperCase();


    // apZip :: [(a -> b)] -> [a] -> [b]
    const apZip = (fs, xs) =>
        // Each function in fs applied to the value
        // in the corresponding position in xs.
        fs.map(
            (f, i) => f(xs[i])
        )
            .slice(
                0, Math.min(fs.length, xs.length)
            );


    // zipWithN :: (a -> b -> ... -> c) -> ([a], [b] ...) -> [c]
    const zipWithN = (...args) =>
        // Uncurried function of which the first argument is a
        // curried function, and all remaining arguments are lists.
        1 < args.length
            ? (
                ([f, ...xs]) => xs.slice(1).reduce(
                    apZip,
                    xs[0].map(f)
                )
            )(args)
            : [];

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