In another thread, I was asked about how some sections of this script work, so …
here is an alternative version, with one or two sections rewritten to break them down into stages, with a few more comments, and a few more named variables (to replace direct composition)
// USABLE ALTERNATIVE VERSION WITH A FEW SECTIONS BROKEN DOWN INTO
// COMMENTED STAGES
// Move @now or @next tag to next available item in file
// Ver 0.9 version B, for teaching
// Requires TP3 Preview (build 166+)
// Skips @done items
// If necessary, unfolds to make newly tagged item visible
// If blnMarkDone=true:
// 1. Marks the current item as @done(yyyy-mm-dd HH:mm)
// before moving the @now/@next tag
// 2. If the parent project is now completed, marks that as @done too
(function (strNowTag, blnDown, blnTextBar, blnMarkDone) {
// Adjust value for blnDown at foot of script:
// 0/false: Move UP (skipping @done items)
// 1/true : Move DOWN (skipping @done items)
// blnTextBar: true:force refresh of TextBar,
// false:not using Textbar, or don't force refresh
'use strict';
// Edit here to move a differently named bookmarker tag,
// like 'next' (--> @next)
var strTag = strNowTag || 'now';
var ds = Application("TaskPaper").documents,
blnMoved = ds.length ? ds[0].evaluate({
script: (
// Evaluated in TP3 ********************
function (editor, options) {
// projects ancestral to this item
// (plural because projects can nest)
// tpItem -> [tpProject]
function itemProjects(oItem) {
return oItem.evaluateItemPath(
'ancestor-or-self::@type=project'
);
}
// descendant tasks not @done ?
// tpItem -> [tpItem]
function tasksRemaining(oItem) {
return oItem.evaluateItemPath(
'descendant::(@type=task and not @done)'
);
}
// monadic bind/chain for lists
// [a] -> (a -> [b]) -> [b]
function chain(xs, f) {
return [].concat.apply([], xs.map(f));
}
var strTag = options.tag,
strAttrib = 'data-' + strTag,
oOutline = editor.itemBuffer.outline,
lstNow = oOutline.evaluateItemPath('//@' + strTag + '[0]');
if (lstNow.length) {
var oNow = lstNow[0],
blnFound = false,
blnDown = options.downward,
lstNext = chain(
blnDown ? [
'following', 'ancestor'
] : [
'preceding', 'ancestor/descendant'
],
function (x) {
if (blnFound) return [];
else {
var lst = oNow.evaluateItemPath(
x + '::(@type=task and not @done)[' +
(blnDown ? '0' : '-1') + ']'
),
lng = lst.length,
oNext = lng ? [lst[0]] : [];
blnFound = blnFound || lng;
return oNext;
}
}
),
oNext = lstNext.length ? lstNext[0] : null;
if (options.markDone) {
if (oNow.getAttribute('data-type') === 'task') {
var strTime = moment().format('YYYY-MM-DD HH:mm');
oNow.setAttribute('data-done', strTime);
// ORIGINAL CODE
//also mark any projects finished by this as done
// itemProjects(oNow).filter(function (p) {
// return tasksRemaining(p).length === 0;
// }).forEach(function (x) {
// x.setAttribute('data-done', strTime);
// });
// REWRITTEN INTO STAGES WITH NAMED VARIABLES
// ( rather than using direct composition, as above )
// STAGE ONE
var lstItemProjects = itemProjects(oNow),
// STAGE TWO
// Collect any projects that are now full completed
lstFullyCompleted = lstItemProjects.filter(
function (p) {
return tasksRemaining(p).length === 0;
}
);
// STAGE THREE
// Mark each project that now has no remaining tasks as done
lstFullyCompleted.forEach(
function (x) {
x.setAttribute('data-done', strTime);
}
);
}
}
// clear any existing @now tags
lstNow.forEach(function (x) {
return x.removeAttribute(strAttrib);
});
}
// ORIGINAL CODE
// var oTaggable = (oNext && !oNext.isRoot) ? oNext : (
// function () {
// var lstDoable = oOutline.evaluateItemPath(
// '//(not @done)[' + (blnDown ? '0' : '-1') + ']'
// );
// return lstDoable.length ? lstDoable[0] : null;
// }()
// );
//
// return oTaggable ? (
// oTaggable.setAttribute(strAttrib, ''),
// editor.isVisible(
// oTaggable
// ) || editor.makeVisible(oTaggable),
// true
// ) : false;
// BROKEN DOWN INTO STAGES WITH NAMED VARIABLES
// We now have a value for oNext (the next line in the sequence)
// and need to check whether it can be directly tagged,
// or whether it has any uncompleted children which should be tagged first
var oTaggable;
if (!oNext || oNext.isRoot) {
// if oNext is null, or is the virtual/invisible 'root' of the outline,
// then it can't be tagged.
// We need to find the next non-completed line where 'next' means nearest top of doc if blnDown
// or nearest end of doc if blnUp
var strQuery,
lstDoable;
if (blnDown) {
strQuery = '//(not @done)[0]'; // first match
} else {
strQuery = '//(not @done)[-1]'; // last match
}
lstDoable = oOutline.evaluateItemPath(strQuery);
// if we have a match then use it (the list will only be one item long)
if (lstDoable.length) { // non-zero evaluates to boolean true
var oTaggable = lstDoable[0];
}
} else {
// We can use oNext
oTaggable = oNext;
}
if (oTaggable) {
// We have found something. Let's tag it
oTaggable.setAttribute(strAttrib, ''); // its not @now(value) just @now('') = @now
// Let's also make sure that it's not hidden by outline folding/filtering)
if (!editor.isVisible(oTaggable)) {
editor.makeVisible(oTaggable);
}
return true; // done
} else {
return false; // there was nothing to tag
}
}
//**************************************
).toString(),
withOptions: {
downward: blnDown,
tag: strTag,
markDone: blnMarkDone
}
}) : false;
// If you are using [TextBar](http://www.richsomerfield.com/apps/)
// to display the active TaskPaper task on the OS X menu bar, as described in:
// http://support.hogbaysoftware.com/t/script-displaying-the-active-task-in-the-os-x-menu-bar/1290
// then uncommenting the following 4 lines
// will force an immediate update of TextBar
//*****************************************
// COMMENT OUT 12 LINES (TO NEXT LINE OF ASTERISKS) IF NOT USING TEXTBAR
if (blnTextBar && blnMoved) {
try {
var a = Application('TextBar');
['quit', 'activate'].forEach(
function (m) {
a[m]();
}
)
} catch (_) {
return 'Textbar not running';
}
}
//******************************************
if (!blnMoved) {
var a = Application.currentApplication();
(a.includeStandardAdditions = true, a).displayNotification(
'All done in active TP3 file !', {
withTitle: "TaskPaper 3 active tasks",
soundName: "Glass"
}
);
};
return blnMoved;
// EDIT VALUE FOR blnDown (see top of script)
// 0: Move tag up (skipping @done items)
// 1: Move tag down (skipping @done items)
// strTagName, blnDown, blnTextBar, blnMarkDone (1: true, 0:false)
})('now', true, false, true);