Those are unicode characters, so the approach is essentially that same as in this script:
Script :: Toggling checked ⇄ unchecked box in selected rows - Bike - Hog Bay Software Support
(in which, if you wanted, you could replace the "☐☑ "
used by default with one of the unicode character pairs adopted by Tot, for example "○● "
or "□■ "
)
The main drawback of special unicode characters is, of course, that in other plain text contexts they may be hard for people to find and type.
(Even inside Tot there appears to be no way of toggling the check state of several selected lines together)
I think that’s probably why some users express a preference for the Markdown convention of more easily typed prefix strings like [ ]
and [x]
, which you can toggle (in one or more rows) with this script:
BIKE Outliner – Toggle a Markdown checkbox in selected lines - Macro Library - Keyboard Maestro Discourse
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.02
// --------------------- 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();
})();
(Both scripts can be found in the Bike Extensions Wiki)