When I process the inbox, 90% of my tasks generally go to either of two projects: Next or Available. Is it possible to create two keyboard shortcut to easily send the tasks to either of those two, without having to type in the project names under each task? Maybe add a script + button for that?
Yes, this seems like the perfect use of an AppleScript, but you’ll need a third party utility to activate your script with a keyboard shortcut. I’ll let someone who has experience with this workflow chime in and suggest one.
Maybe this Javascript for Automation script helps.
To run it in Script Editor, set language to Javascript.
To run it in Keyboard Maestro, copy it to an Execute Javascript action
(() => {
// Change this value to reflect your desired project
const strProject = 'Next';
// Data List JS ----------------------------------------------------------
// bindMay (>>=) :: Maybe a -> (a -> Maybe b) -> Maybe b
const bindMay = (mb, mf) =>
mb.nothing ? mb : mf(mb.just);
// Handles two or more arguments
// curry :: ((a, b) -> c) -> a -> b -> c
const curry = (f, ...args) => {
const go = xs => xs.length >= f.length ? (f.apply(null, xs)) :
function() {
return go(xs.concat(Array.from(arguments)));
};
return go([].slice.call(args));
};
// Data List JS ----------------------------------------------------------
// just :: a -> Just a
const just = x => ({
nothing: false,
just: x
});
// nothing :: () -> Nothing
const nothing = (optionalMsg) => ({
nothing: true,
msg: optionalMsg
});
// OF Functions
// setProject :: OF Project -> OF Task -> ()
const setProject = (oProject, oTask) => {
return oTask.assignedContainer = oProject
};
const windowMay = app => {
return (
app.defaultDocument.documentWindows().length == 0 ?
nothing('No windows open') :
just(app.defaultDocument.documentWindows()[0])
)
}
const tasksMay = win => {
return (
win.content.selectedTrees().length == 0 ?
nothing('No tasks selected') :
just(win.content.selectedTrees.value())
)
}
const projectMay = x => {
const xs = (
oDoc.flattenedProjects.whose({
name: x
}))();
return (xs.length == 0) ?
nothing('No project with name: ' + x) :
just(xs[0]);
}
const
ca = Application.currentApplication(),
sa = (ca.includeStandardAdditions = true, ca),
of = Application('OmniFocus'),
oDoc = of.defaultDocument,
oWin = oDoc.documentWindows[0];
const mbProject = projectMay(strProject)
if (mbProject.nothing) {
return ca.displayDialog(mbProject.msg)
}
const mbSeln = bindMay(windowMay(of), tasksMay)
if (mbSeln.nothing) {
return ca.displayDialog(mbSeln.msg)
} else {
return mbSeln.just.map(curry(setProject, mbProject.just))
}
})();
If you prefer an AppleScript (to place it in the toolbar, for example), here it is:
-- unlocked2412
-- This script assigns a project to the currently selected inbox tasks.
property strProject : "Next"
on run
tell application "OmniFocus"
set oDoc to front document
set oWin to front document window of oDoc
end tell
set mbProject to projectMay(oDoc, strProject)
if nothing of mbProject then
display dialog msg of mbProject
return
end if
set mbSelection to inboxTasksMay(oWin)
if nothing of mbSelection then
display dialog msg of mbSelection
else
set apply to |λ|(just of mbProject) of curry(assignProject)
map(apply, just of mbSelection)
end if
end run
on inboxTasksMay(oWin)
using terms from application "OmniFocus"
set xs to selected trees of content of oWin where class of its value = inbox task
if xs = {} then
nothing("No inbox tasks selected")
else
just(xs)
end if
end using terms from
end inboxTasksMay
on projectMay(oDoc, str)
using terms from application "OmniFocus"
set xs to flattened projects of oDoc whose name = str
if xs = {} then
nothing("No projects named: " & str)
else
just(item 1 of xs)
end if
end using terms from
end projectMay
on assignProject(oProject, oItem)
using terms from application "OmniFocus"
set assigned container of value of oItem to oProject
end using terms from
end assignProject
-- Data List AppleScript -------------------------------------------------
-- bindMay (>>=) :: Maybe a -> (a -> Maybe b) -> Maybe b
on bindMay(mb, mf)
if nothing of mb then
mb
else
tell mReturn(mf) to |λ|(just of mb)
end if
end bindMay
-- curry :: ((a, b) -> c) -> a -> b -> c
on curry(f)
script
on |λ|(a)
script
on |λ|(b)
|λ|(a, b) of mReturn(f)
end |λ|
end script
end |λ|
end script
end curry
-- nothing :: (Optional String) -> Nothing
on nothing(msg)
{nothing:true, msg:msg}
end nothing
-- just :: a -> Just a
on just(x)
{nothing:false, just:x}
end just
-- map :: (a -> b) -> [a] -> [b]
on map(f, xs)
tell mReturn(f)
set lng to length of xs
set lst to {}
repeat with i from 1 to lng
set end of lst to |λ|(item i of xs, i, xs)
end repeat
return lst
end tell
end map
-- Lift 2nd class handler function into 1st class script wrapper
-- mReturn :: First-class m => (a -> b) -> m (a -> b)
on mReturn(f)
if class of f is script then
f
else
script
property |λ| : f
end script
end if
end mReturn
I enjoy working in applescript, but since @unlocked2412 beat me to it.
Something else anyone looking at this thread might consider if they aren’t as familiar with editing scripts, have more complex project hierarchies and/or have a lot of sorting into projects that come and go, is to use text expansion software to speed things up without going fully to keyboard shortcuts.
I tab over to the projects field, type something like (making this up) “;wap1” which expands to “work : active projects : project 1” and then tab into the contexts field.
This solution is especially handy when working with Quick Entry instead of from an Inbox.
You can experiment with this using OS X’s built in text expansion, but might consider a more dedicated expander if it works for you.