A checkbox (and checkbox state) toggling macro for Jesse Grosjean's Bike outliner
BIKE – Toggle a checked ⇄ unchecked box prefix in selected lines.kmmacros (6.1 KB)
Expand disclosure triangle to view JS Source
(() => {
"use strict";
// Cycle a set of prefix characters in selected
// (non-empty) lines of Bike 1.3
// Rob Trew @2022
// Ver 0.01
// --------------------- OPTION ----------------------
// Which single-character prefixes to cycle ?
// (Any space character is interpreted as a
// no-prefix stage in the cycle).
const prefixCycle = "☐☑ ";
// Application("Keyboard Maestro Engine")
// .getvariable("prefixCycle");
// ------------ PREFIX CHARACTER TOGGLED -------------
// main :: IO ()
const main = () => {
const
bike = Application("Bike"),
doc = bike.documents.at(0);
return doc.exists() ? (() => {
const
selectedNonEmptyRows = doc.rows.where({
selected: true,
_not: [{
name: ""
}]
}),
n = selectedNonEmptyRows.length;
return Boolean(n) ? (
bikeRowsPrefixCycled([...prefixCycle])(
selectedNonEmptyRows
)
) : "No non-empty rows selected in Bike.";
})() : "No documents open in Bike.";
};
// ------------------ PREFIX CYCLED ------------------
// bikeRowsPrefixCycled :: [Char] -> Int ->
// IO Bike Rows -> IO String
const bikeRowsPrefixCycled = prefixList =>
// Any selected non-empty rows in Bike cycled
// to the the next prefix in prefixList
selectedNonEmptyRows => {
const
n = selectedNonEmptyRows.length,
plural = 1 < n ? "s" : "",
[f, change] = (() => {
const
iNow = prefixList
.findIndex(
c => c === (
selectedNonEmptyRows
.at(0).name()[0]
)
),
iNext = -1 !== iNow ? (
(1 + iNow) % prefixList.length
) : 0,
nextPrefix = prefixList[iNext];
return [
updatedLine(prefixList)(iNext),
" " !== nextPrefix ? (
`Set ${nextPrefix}`
) : "Cleared"
];
})();
return (
zipWith(row => s => row.name = s)(
selectedNonEmptyRows()
)(
selectedNonEmptyRows.name().map(f)
),
[
`${change} prefix`,
`in ${n} selected line${plural}.`
]
.join("\n")
);
};
// updatedLine :: [Char] -> Char -> String -> String
const updatedLine = prefixes =>
// A line in which the prefix has been
// replaced by the next option in a cycle.
// A space character in the prefixes list is
// interpreted as no-prefix stage in the cycle.
iNext => s => {
const c = prefixes[iNext];
return Boolean(s) ? (
prefixes.includes(s[0]) ? (
c + s.slice(1)
) : `${c} ${s}`
).trimStart() : `${c} `;
};
// --------------------- GENERIC ---------------------
// zipWith :: (a -> b -> c) -> [a] -> [b] -> [c]
const zipWith = f =>
// A list constructed by zipping with a
// custom function, rather than with the
// default tuple constructor.
xs => ys => xs.map(
(x, i) => f(x)(ys[i])
).slice(
0, Math.min(xs.length, ys.length)
);
// MAIN
return main();
})();