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