To generalize tag-value cycling a bit, we could specify:
- one or more tag names, and
- a list of values for each tag name
in the withOptions
key-value dictionary passed to document.evaluate
in this kind of pattern:
e.g. for @place or @grade or @sprint
docs.at(0).evaluate({
script: `${TaskPaperContext}`,
withOptions: {
"place": [
"Berlin", "Dublin",
"London", "Paris"
],
"grade": "ABCD",
"sprint": [10, 25, 55]
}
})
Generalized tag-value cycling:
Expand disclosure triangle to view JS Source
/* eslint-disable max-lines-per-function */
(() => {
"use strict";
// Cycle tag value of:
// - First tag in selection(s) which matches a key in
// options,
// - to the next value in the list given in options
// for that key.
// Rob Trew @2021
// Ver 0.05
// (Where a line has more than one of the tags with
// cycle lists given in options, the *leftmost* of
// these tags in the selected line will be cycled)
// ------------------- JXA CONTEXT -------------------
// main :: IO ()
const main = () => {
const docs = Application("TaskPaper").documents;
return 0 < docs.length ? (
docs.at(0).evaluate({
script: `${TaskPaperContext}`,
withOptions: {
"place": [
"Berlin", "Dublin",
"London", "Paris"
],
"grade": "ABCD",
"sprint": [10, 25, 55]
}
})
) : "No documents open in TaskPaper.";
};
// ---------------- TASKPAPER CONTEXT ----------------
const TaskPaperContext = (editor, options) => {
const tp3Main = () => {
const
selection = editor.selection,
items = selection.selectedItems,
startLocn = selection.location,
endItem = selection.endItem,
endOffset = selection.endOffset,
cyclableNames = Object.keys(options || {})
.map(k => `data-${k}`);
let report = "";
return 0 < items.length ? (
0 < cyclableNames.length ? (
editor.outline.groupUndoAndChanges(() => {
report = items.filter(
x => Boolean(
x.bodyString.trim()
)
)
.map(valueCycled(cyclableNames))
.join("\n");
}),
editor.moveSelectionToRange(
startLocn,
editor.getLocationForItemOffset(
endItem, endOffset
)
),
report
) : "No name list pairs supplied in options."
) : "No TaskPaper items selected";
};
// ---------------- VALUE CYCLING ----------------
// valueCycled :: [String] -> Item -> IO String
const valueCycled = cyclableNames =>
item => {
const
overlap = intersect(
item.attributeNames
)(
cyclableNames
),
tagName = 0 < overlap.length ? (
overlap[0]
) : cyclableNames[0],
shortName = tagName.slice(5),
newValue = nextValue(
// Values as [String] array.
Array.from(options[shortName])
.map(x => `${x}`)
)(
item.getAttribute(tagName)
);
return (
item.setAttribute(tagName, newValue),
`@${shortName} -> ${newValue}`
);
};
// nextValue :: [String] -> String -> String
const nextValue = ks =>
k => {
const iPosn = ks.findIndex(x => k === x);
return -1 !== iPosn ? (
ks[(1 + iPosn) % ks.length]
) : ks[0];
};
// ------------------- GENERIC -------------------
// intersect :: (Eq a) => [a] -> [a] -> [a]
const intersect = xs =>
// The intersection of lists xs and ys.
ys => xs.filter(x => ys.includes(x));
return tp3Main();
};
// JXA main
return main();
})();