Making a start on that here – this version just takes its text from the clipboard, and puts it in the Inbox: project of the specified TaskPaper 3 file.
(It’s not tested yet, so probably better to wait a bit : - )
(Over the weekend I’ll look at adapting it for Keyboard Maestro and LaunchBar)
(If you want to experiment with it, you will need to edit the filepath in the options at the bottom of this script)
Written in JavaScript for Automation:
// SEND TEXT LINES TO INBOX: PROJECT OF FRONT TASKPAPER DOCUMENT
// Predraft version 0.02
// 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);
// 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) {
var rgxLine = /^(\s*)([\-\*\+]*)(\s*)/;
return strText.split(/[\n\r]+/)
.map(function (x) {
var ms = rgxLine.exec(x);
return {
indent: (ms ? ms[1] : '')
.replace(/ /g, '\t')
.length,
text: x.slice((ms ? ms[0] : '')
.length),
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) {
// placeSubNest :: tp3Node -> Tree {text:String, nest:[Tree], bullet:Bool} -> ()
function placeSubNest(oParent, lstNest) {
// 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.appendChildren(lstChiln);
});
// 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);
}
}, lstNest, lstChiln);
}
}
// Ensure that the nest has a virtual root
var outline = editor.outline;
// Place the nest beneath the parent
placeSubNest(oParent, oTextNest.nest)
}
// 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 dctNest = textNest(
parsedLines(
options.textLines
)
);
// 3. INSERT THE TEXT NEST IN THE INBOX
insertNest(
itemInbox,
dctNest
);
}
// 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));
//2. And an inbox in this file
//3. and place the text
if (d) {
return d.evaluate({
script: TaskPaperContext.toString(),
withOptions: dctOptions
});
}
} 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: '//Inbox and @type=project[-1]',
filePath: '~/Notes/general.taskpaper',
textLines: sa.theClipboard()
});