An update, after some years, of a Keyboard Maestro macro for TaskPaper which I thought I had posted here but can't immediately track down:
The macro (requires TaskPaper 3, macOS Sierra onwards) just:
- moves a @now tag on the the next available TaskPaper 3 item,
- optionally flags the previous @now item as @done(timestamped),
- optionally updates a TextBar display of the current @now item and its context (parent, and any grandparent)
- optionally issues a macOS notification and sound. (One sound for a new available item, another sound for a completed outline)
Next available item means, for example, the next outline leaf (childless item) which is not @done
The movement through the outline is 'bottom up', or 'preorder' (children before parents). The @now tag moves to parent items only after all of their descendants are @done (so that the parent itself can be marked @done, if that is what is wanted)
( Items of all types are visited in the same way β projects, tasks and notes β I seem to be working in TaskPaper these days with very few bullets and project colons β mostly just unadorned outlines )
TaskPaper move NOW mark DONE.kmmacros (29.4 KB)
For reference, the shell script which I am personally using with TextBar is:
osascript -l JavaScript <<JXA_END 2>/dev/null
((intMaxParts) => {
'use strict';
// ver 0.5 Requires TaskPaper 3
// NB This custom version makes particular assumptions about
// folders, filenames, and which file we want to query
// Clue (not necessarily TP3's front document)
// (FIRST LINE TAGGED WITH @NOW and optionally a path to it)
// Increase the value of intMaxParts (argument at end of script)
// to lengthen the path displayed
// EDIT AS REQUIRED:
// NB See also strToday and dayDocName at start of MAIN below
const notePath = '~/Notes';
// Left :: a -> Either a b
const Left = x => ({
type: 'Either',
Left: x
});
// Right :: b -> Either a b
const Right = x => ({
type: 'Either',
Right: x
});
// bindEither (>>=) :: Either a -> (a -> Either b) -> Either b
const bindEither = (m, mf) =>
m.Right !== undefined ? (
mf(m.Right)
) : m;
// concatMap :: (a -> [b]) -> [a] -> [b]
const concatMap = (f, xs) =>
xs.length > 0 ? [].concat.apply([], xs.map(f)) : [];
// filePath :: String -> FilePath
const filePath = s =>
ObjC.unwrap(ObjC.wrap(s)
.stringByStandardizingPath);
// isoLocal :: Date -> String
const isoLocal = dte => {
const xs = ['FullYear', 'Month', 'Date',
'Hours', 'Minutes' //, 'Seconds', 'Milliseconds'
]
.map((k, i) => {
const s = (dte['get' + k]() + (i === 1 ? 1 : 0))
.toString();
return (s.length === 1 ? '0' : '') + s;
});
return xs.slice(0, 3)
.join('-') + ((xs[3] !== '00' || xs[4] !== '00') ? (
' ' + xs.slice(3)
.join(':')
) : '');
};
// take :: Int -> [a] -> [a]
const take = (n, xs) => xs.slice(0, n);
// standardAdditions :: () -> Library Object
const standardAdditions = () =>
Object.assign(
Application.currentApplication(), {
includeStandardAdditions: true
}
);
// MAIN -----------------------------------------------------------------
const
strToday = take(10, isoLocal(new Date())),
dayDocName = 'notes' + strToday + '.taskpaper',
fpDayNotes = filePath(notePath) + '/' + dayDocName,
intMax = intMaxParts || 99,
strSteps = concatMap(
x => x ? [x.replace(/^- /, '')] : [],
(() => {
const
tp3 = Application("TaskPaper"),
ds = tp3
.documents.where({
name: dayDocName
}),
docs = ds.length > 0 ? (
ds
) : [(() => (
standardAdditions()
.doShellScript(
'touch "' + fpDayNotes + '"; ' +
'open -a TaskPaper "' + fpDayNotes + '";' +
'sleep 0.75'
),
tp3
.documents.at(0)
))()];
return bindEither(
docs.length > 0 ? Right(docs[0]) : Left(''),
d => Right(d.evaluate({
script: ((editor, options) => {
// htmlEncoded :: String -> String
const htmlEncoded = s => {
const rgx = /[\w\s]/;
return ''.concat.apply('',
s.split('')
.map(c => rgx.test(c) ? (
c
) : '&#' + c.charCodeAt(0) + ';')
);
};
return editor.outline.evaluateItemPath(
options.query
)
.map(x => htmlEncoded(
x.bodyContentString
))
})
.toString(),
withOptions: {
query: '//@now[0]/ancestor-or-self::*[-' +
intMax + ':]'
}
}))
.Right
);
})())
.join(' <span style=color:#8B4513>-></span> '); // Arrow
// 8B4513
return strSteps ? '<html><font style=color:#FFA361; font-family:"Menlo">' +
strSteps + '</font></html>' : 'β';
})(3); // MAX NUMBER OF PATH SEGMENTS TO INCLUDE
JXA_END