Script: move a @now or @next tag on to the next item

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);