Create a shortcut to assign task to project

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.

Sin_título_4

To run it in Keyboard Maestro, copy it to an Execute Javascript action

Keyboard_Maestro_Editor copia

(() => {
	// 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))
	}

})();
2 Likes

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.