Toggle a Markdown checkbox in selected rows of Jesse Grosjean's Bike Outliner.
An earlier script toggled single Unicode prefix characters.
This version is based on a general script for toggling prefixes of any length.
In this case it cycles between two Markdown checkbox states and one no-prefix state:
[ ] -> [x] -> '' -> [ ] ...
BIKE – Toggle a Markdown checkbox in selected lines.kmmacros (6.3 KB)
Expand disclosure triangle to view JS Source
(() => {
"use strict";
// Cycle a set of prefixes (single or multi character)
// in selected (non-empty) lines of Bike 1.3
// Rob Trew @2022
// Ver 0.01
// --------------------- OPTION ----------------------
// Which prefixes to cycle ?
// (An empty string is interpreted as a
// no-prefix stage in the cycle).
const prefixCycle = ["[ ]", "[x]", ""];
// ----------------- PREFIX 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: ""
}]
});
return Boolean(selectedNonEmptyRows.length) ? (
bikeRowsPrefixCycled([...prefixCycle])(
selectedNonEmptyRows
)
) : "No non-empty rows selected in Bike.";
})() : "No documents open in Bike.";
};
// ------------------ PREFIX CYCLED ------------------
// bikeRowsPrefixCycled :: [String] ->
// IO Bike Rows -> IO String
const bikeRowsPrefixCycled = prefixes =>
// Any selected non-empty rows in Bike cycled
// to the the next prefix in prefixes
selectedNonEmptyRows => {
const
n = selectedNonEmptyRows.length,
plural = 1 < n ? "s" : "",
[f, change] = (() => {
const
iPrefix = prefixes
.findIndex(
k => selectedNonEmptyRows
.at(0).name()
.startsWith(k)
),
nextPrefix = prefixes[
-1 !== iPrefix ? (
(1 + iPrefix) % prefixes
.length
) : 0
];
return [
updatedLine(prefixes)(nextPrefix),
"" !== 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 :: [String] -> String -> String -> String
const updatedLine = prefixes =>
// A line in which the prefix has been
// replaced by the next option in a cycle.
// An empty string in the prefixes list is
// interpreted as no-prefix stage in the cycle.
pfx => s => {
const pre = Boolean(pfx) ? `${pfx} ` : "";
return Boolean(s) ? (() => {
const
iExisting = prefixes.findIndex(
k => Boolean(k) && s.startsWith(k)
);
return -1 !== iExisting ? (
pre + s.slice(
1 + prefixes[iExisting].length
)
) : (pre + s.trim());
})() : pre;
};
// --------------------- 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();
})();