A first draft of a macro for sending text on the fly to the Inbox project of a specific TaskPaper 3 file
Quick Entry for TaskPaper 3 .kmmacros (32.6 KB)
Source of the Execute JavaScript for Automation action:
// SEND TEXT LINES TO INBOX: PROJECT OF FRONT TASKPAPER DOCUMENT
// Draft 0.06
// Adds experimental options to:
// - add items to top of Inbox, rather than end
// - append an @added(yyy-mm-dd HH:MM) task to top level items
// - parse any informal date content of tags in the line
// ( Set options at end of script )
// This version just gets its text from the clipboard
// Could be adapted for Keyboard Maestro, LaunchBar, Alfred etc
// var a = Application.currentApplication(),
// sa = (a.includeStandardAdditions = true, a);
var kmVars = Application("Keyboard Maestro Engine")
.variables;
// N.B. you need to give a filepath to an existing TaskPaper document
// in the:
// OPTIONS SETTINGS AT BOTTOM OF SCRIPT
(function (dctOptions) {
'use strict';
// TASKPAPER CONTEXT ***************************************
function TaskPaperContext(editor, options) {
// FIND OR MAKE A PROJECT TO HOLD INCOMING TEXT
// String -> String -> {project: tpItem, new: Bool}
function projectAtPath(strProjectPath, strDefaultName) {
var strDefault = strDefaultName || 'Inbox:',
outline = editor.outline,
lstMatch = outline.evaluateItemPath(strProjectPath),
blnFound = lstMatch.length > 0;
return {
project: blnFound ? lstMatch[0] : (function () {
var defaultProject = outline.createItem(
strDefault)
outline.groupUndoAndChanges(function () {
outline.root.appendChildren(
defaultProject
);
});
return defaultProject;
})(),
new: !blnFound
};
}
// parsedLines :: String -> [{indent: Int, text:String, bulleted:Bool}]
function parsedLines(strText) {
// tagDatesAsISO :: String -> String
function tagDatesAsISO(s) {
function maybeTrans(str) {
var strParse = dt.format(dt.parse(str.slice(1, -1)));
return strParse === "Invalid date" ? str : (
'(' + strParse.substr(0, 16) + ')'
);
}
return s.split(rgxTag)
.map(function (x, i) {
return ((i + 1) % 3) === 0 ? maybeTrans(x) : x;
})
.join('');
}
var rgxLine = /^(\s*)([\-\*\+]*)(\s*)/,
rgxTag = /(\s+\@\w+)(\([^\)]+\))/g,
dt = DateTime;
return strText.split(/[\n\r]+/)
.map(function (x) {
var ms = rgxLine.exec(x),
lngIndent = (ms ? ms[1] : '')
.replace(/ /g, '\t')
.length,
strRaw = (x.slice(
(ms ? ms[0] : '')
.length
)),
strText = blnDates ? tagDatesAsISO(strRaw) :
strRaw;
return {
indent: lngIndent,
text: strText + (
(blnTimeStamp && (lngIndent < 1)) ?
strNow :
''
),
bulleted: ms[2].length > 0
};
});
}
// textNest :: [{indent:Int, text:String}, bulleted:Bool]
// -> Tree {text:String, nest:[Tree]}
function textNest(xs) {
var h = xs.length > 0 ? xs[0] : undefined,
lstLevels = [{
text: undefined,
nest: []
}];
if (h) {
var lngBase = h.indent;
xs.forEach(function (x) {
var lngMax = lstLevels.length - 1,
lngLevel = x.indent - lngBase,
dctParent = lngLevel > lngMax ? lstLevels[
lngMax] : lstLevels[lngLevel],
dctNew = {
bullet: lngLevel === 0 || x.bulleted,
text: x.text,
nest: []
};
dctParent.nest.push(dctNew);
if (lngLevel > lngMax) lstLevels.push(dctNew);
else lstLevels[lngLevel + 1] = dctNew;
});
}
return lstLevels[0];
}
// insertNest :: tp3Node ->
// Tree {text:String, nest:[Tree], bullet:Bool} -> ()
function insertNest(oParent, oTextNest, blnAtTop) {
// placeSubNest :: tp3Node -> Tree {text:String, nest:[Tree], bullet:Bool} -> ()
function placeSubNest(oParent, lstNest, blnTop) {
// IMMEDATE CHILD NODES CREATED,
if (lstNest.length > 0) {
var lstChiln = lstNest.map(function (dct) {
return outline.createItem((dct.bullet ?
'- ' : '') +
(dct.text || ''));
});
// AND PLACED UNDER EXISTING PARENT LINE
outline.groupUndoAndChanges(function () {
oParent[
blnTop ? 'insertChildrenBefore' :
'appendChildren'
](lstChiln, oParent.firstChild);
});
// THEN RECURSION WITH EACH CHILD FOR
// ITS DESCENDANTS, IF ANY
zipWith(function (dctNest, oNode) {
var lstSub = dctNest.nest;
if (lstSub.length > 0) {
placeSubNest(oNode, lstSub, false);
}
}, lstNest, lstChiln);
}
}
// Ensure that the nest has a virtual root
var outline = editor.outline;
// Place the nest beneath the parent
placeSubNest(oParent, oTextNest.nest, blnAtTop)
}
// zipWith :: (a -> b -> c) -> [a] -> [b] -> [c]
function zipWith(f, xs, ys) {
var ny = ys.length;
return (xs.length <= ny ? xs : xs.slice(0, ny))
.map(function (x, i) {
return f(x, ys[i]);
});
}
// TASKPAPER CONTEXT MAIN:
// 1. FIND OR CREATE AN INBOX
var outline = editor.outline,
mInbox = projectAtPath(options.inboxPath, 'Inbox:'),
itemInbox = mInbox.project;
// 2. PARSE THE INCOMING TEXT TO A NEST
var blnTimeStamp = options.withTimeStamp,
strAdded = blnTimeStamp ? options.timeStampTag : '',
strNow = blnTimeStamp ? (' @' + strAdded +
'(' + DateTime.format(new Date())
.substr(0, 16) + ')') : '',
blnDates = options.parseTagDates,
dctNest = textNest(
parsedLines(
options.textLines
)
);
// 3. INSERT THE TEXT NEST IN THE INBOX
insertNest(
itemInbox,
dctNest,
options.atTopOfList
);
return options.textLines
}
// JAVASCRIPT FOR AUTOMATION CONTEXT ***********************
// fileExists :: String -> Bool
function fileExists(strPath) {
var error = $();
$.NSFileManager.defaultManager
.attributesOfItemAtPathError(
ObjC.unwrap($(strPath)
.stringByExpandingTildeInPath),
error
);
return (error.code === undefined);
}
// JSA MAIN ***********************************************
var a = Application.currentApplication(),
sa = (a.includeStandardAdditions = true, a);
//1. DOES THE FILE EXIST ?
var strFullPath = ObjC.unwrap(
$(dctOptions.filePath)
.stringByExpandingTildeInPath
);
if (fileExists(strFullPath)) {
var tp3 = Application("TaskPaper"),
d = tp3.open(Path(strFullPath));
if (d) {
var varResult = d.evaluate({
script: TaskPaperContext.toString(),
withOptions: dctOptions
});
if (dctOptions.saveAfterAdding) d.save();
return varResult;
}
} else {
sa.activate();
sa.displayDialog('File not found:\n\n' + strFullPath, {
//defaultAnswer: undefined,
//buttons : undefined,
defaultButton: 'OK',
//cancelButton : undefined,
withTitle: "Quick entry",
withIcon: sa.pathToResource('TaskPaperAppIcon.icns', {
inBundle: 'Applications/TaskPaper.app'
}), // 'note'
givingUpAfter: 30
})
}
// SCRIPT OPTIONS
})({
inboxPath: kmVars.TP3QEItemPath.value(), //'//Inbox and @type=project[-1]',
filePath: kmVars.TP3QEFilePath.value(), //'~/Notes/general.taskpaper',
// items to append to top (rather than end) of Inbox ?
atTopOfList: kmVars['To top of list'].value() === '1',
// convert date-time tag contents to yyyy-mm-dd HH:MM ?
parseTagDates: kmVars['Parsing tag dates'].value() === '1',
// Appending a tag like @added(2016-03-24 20:05)
withTimeStamp: kmVars['With time stamp'].value() === '1',
timeStampTag: 'added', // specify a tag name like 'added' any time-stamping
textLines: kmVars['Inbox note'].value(),
saveAfterAdding: false
});