Here’s a first draft (behind the disclosure triangle below).
The simplest approach – in scripting terms – is to let the script open the target document (if it isn’t already open).
Ver 6 (behind the disclosure triangle below), activates the target document, and gives focus to the target project.
(various other approaches to final focus are possible)
You will need to adjust the value of fpTarget
(the filePath of the target file) near the top of the script source below.
[Using Scripts]
(Using Scripts · GitBook)
Copy the whole of the source behind the disclosure triangle:
JS Source
(() => {
'use strict';
// Rob Trew @2020
// Ver 0.06
// Updated to allow for proper handling of
// square brackets in project names.
//
// main :: IO ()
const main = () => {
const fpTarget = '~/Desktop/overview.taskpaper';
const
taskpaper = Application('TaskPaper'),
docs = taskpaper.documents;
return either(
alert('Problem')
)(
([sourceDoc, targetDoc, projectName, taskName]) => (
//appWinRaisedByNameLR('TaskPaper')(sourceDoc.name()),
`${taskName} copied to ${projectName}:\n` + (
`in ${targetDoc.file()}`
)
)
)(
// Are any documents open in TaskPaper ?
bindLR(
0 < docs.length ? (
Right(docs.at(0))
) : Left('No TaskPaper documents open.')
)(
// Is any task within a project selected ?
sourceDoc => bindLR(
sourceDoc.evaluate({
script: `${tp3SelectedItem}`
})
)(
// Can we find or create a file at the given path ?
([projectName, taskText]) => bindLR(
doesFileExist(fpTarget) ? (
Right(fpTarget)
) : writeFileLR(fpTarget)(
`${projectName}:\n`
)
)(
// Can we place a copy of the task in a matching
// project in the target file ?
fp => {
const
fpFull = filePath(fp),
targetDocs = docs().filter(
x => fpFull === Path(x.file()).toString()
).length,
targetDoc = 0 < targetDocs.length ? (
targetDocs[0]
) : (
taskpaper.open(fpFull)
);
return bindLR(
targetDoc.evaluate({
script: `${tp3PlacedInProject}`,
withOptions: {
projectName: projectName,
taskText: taskText
}
})
)(
taskName => Right([
sourceDoc,
targetDoc,
projectName,
taskName
])
);
}
)
)
)
);
};
// -------------- TASKPAPER CONTEXT --------------
// tp3SelectedItem :: Editor -> Either String (String, String)
const tp3SelectedItem = editor => {
const main = () => {
const
selectedItem = editor.selection.startItem,
enclosingProjects = selectedItem.ancestors
.filter(x => 'project' === x.getAttribute('data-type'));
return bindLR(
0 < enclosingProjects.length ? (
Right(
last(enclosingProjects).bodyContentString
)
) : Left('Selected item not enclosed by a project.')
)(
projectName => Right([
projectName, selectedItem.bodyString
])
);
};
// ----------------- GENERIC -----------------
// Left :: a -> Either a b
const Left = x => ({
type: 'Either',
Left: x
});
// Right :: b -> Either a b
const Right = x => ({
type: 'Either',
Right: x
});
// bindLR (>>=) :: Either a ->
// (a -> Either b) -> Either b
const bindLR = m =>
mf => undefined !== m.Left ? (
m
) : mf(m.Right);
// last :: [a] -> a
const last = xs =>
// The last item of a list.
0 < xs.length ? (
xs.slice(-1)[0]
) : undefined;
// tp3SelectedItem :: main
return main();
};
// tp3PlacedInProject :: Editor -> Dict ->
// Either String String
const tp3PlacedInProject = (editor, options) => {
const main = () => {
const
outline = editor.outline,
project = projectFoundOrCreated(
outline
)(
options.projectName
);
return (
outline.groupUndoAndChanges(
() => project.insertChildrenBefore(
outline.createItem(options.taskText),
project.hasChildren ? (
project.firstChild
) : undefined
)
),
editor.focusedItem = project,
Right(options.taskText)
);
};
// ----------------- GENERIC -----------------
// Left :: a -> Either a b
const Left = x => ({
type: 'Either',
Left: x
});
// Right :: b -> Either a b
const Right = x => ({
type: 'Either',
Right: x
});
// projectFoundOrCreated :: TP3Outline ->
// String -> Bool -> TP3Item
const projectFoundOrCreated = outline =>
// A reference to a TaskPaper Project item,
// either found in the outline, or created
// at the top of it.
projectName => {
const
k = projectName.trim(),
matches = outline.evaluateItemPath(
`//project "${k}"`
),
blnFound = 0 < matches.length,
project = blnFound ? (
matches[0]
) : outline.createItem(k + ':');
return (
blnFound || outline.insertItemsBefore(
project,
outline.root.firstChild
),
project
);
};
// tp3PlacedInProject :: main
return main();
};
// ---------- JAVASCRIPT FOR AUTOMATION ----------
// alert :: String => String -> IO String
const alert = title => s => {
const
sa = Object.assign(Application('System Events'), {
includeStandardAdditions: true
});
return (
sa.activate(),
sa.displayDialog(s, {
withTitle: title,
buttons: ['OK'],
defaultButton: 'OK',
withIcon: sa.pathToResource('TaskPaper.icns', {
inBundle: 'Applications/TaskPaper.app'
})
}),
s
);
};
// appWinRaisedByNameLR :: String -> String -> IO String
const appWinRaisedByNameLR = appName =>
strName => {
const
se = Application('System Events'),
ps = se.applicationProcesses.where({
name: appName
});
return bindLR(
0 < ps.length ? (
Right(ps.at(0))
) : Left('Application process not found')
)(proc => {
const
ws = proc.windows.where({
name: strName
});
return 0 < ws.length ? (
Right((
se.perform(ws.at(0).actions.byName('AXRaise')),
strName
))
) : Left('Window not found by name: ' + strName);
})
};
// --------------------- GENERIC ---------------------
// Left :: a -> Either a b
const Left = x => ({
type: 'Either',
Left: x
});
// Right :: b -> Either a b
const Right = x => ({
type: 'Either',
Right: x
});
// bindLR (>>=) :: Either a ->
// (a -> Either b) -> Either b
const bindLR = m =>
mf => undefined !== m.Left ? (
m
) : mf(m.Right);
// doesFileExist :: FilePath -> IO Bool
const doesFileExist = fp => {
const ref = Ref();
return $.NSFileManager.defaultManager
.fileExistsAtPathIsDirectory(
$(fp)
.stringByStandardizingPath, ref
) && 1 !== ref[0];
};
// either :: (a -> c) -> (b -> c) -> Either a b -> c
const either = fl =>
// Application of the function fl to the
// contents of any Left value in e, or
// the application of fr to its Right value.
fr => e => 'Either' === e.type ? (
undefined !== e.Left ? (
fl(e.Left)
) : fr(e.Right)
) : undefined;
// filePath :: String -> FilePath
const filePath = s =>
// The given file path with any tilde expanded
// to the full user directory path.
ObjC.unwrap(ObjC.wrap(s)
.stringByStandardizingPath);
// writeFileLR :: FilePath -> Either String IO FilePath
const writeFileLR = fp =>
s => {
const
e = $(),
efp = $(fp)
.stringByStandardizingPath;
return $.NSString.alloc.initWithUTF8String(s)
.writeToFileAtomicallyEncodingError(
efp, false,
$.NSUTF8StringEncoding, e
) ? (
Right(ObjC.unwrap(efp))
) : Left(ObjC.unwrap(e.localizedDescription));
};
return main();
})();