Applescript for 2Do app

Hi,

I’m wondering if anyone has any applescript advice for me re: sending items in FT or TP to a URL scheme for the 2Do app? Although, I’m thinking this approach could be fairly extensible to any app with an URL scheme, right?

Anyway, I’m looking for a way to take some bit of data like example below and running an applescript and having it piped to a URL scheme to create items in the 2Do Mac app:

  • do the first thing @read
    my first note
  • do the second thing @computer @home
  • do the third thing @work

Here’s the URL scheme:

twodo:///add?task=[title]&forlist=&note=[your note]&tags=[comma separated names]

link to documentation

It seems it would be more complex to differentiate between @start and @due tags, but 2do URL scheme doesn’t take due or start dates anyway (although the app supports them).

I don’t really have any Applescript writing chops, but I’ve had good luck cut and pasting various bits from other people’s working examples. Just haven’t seen or understood any applescripts for FT or TP3 that might fit the bill here.

Don’t have a copy of 2Do here, so typing a little in the dark, but this rough and untested draft may give you an idea:

property pblnDebug : false
property precOptions : {listname:"From FoldingText"}

-- twodo:///add?task=[title]&forlist=&note=[your note]&priority=[0,1,2,3]&tags=[comma separated names]

property pstrJS : "
	function (editor, options) {
		'use strict';
		var	tree=editor.tree(),
			dctTags, lstKeys,
			lstSelns = editor.selectedRanges(),
			lstSeen = [],
			lstURL=[], lstNoteParas, lstPriorities=['0','1','2','3'],
			strNodePath,
			strID, strValue, strNote,
			strPrefix='twodo:///add?task=',
			strListName=encodeURIComponent(options.listname),
			strURL='', lngKeys, iIndex,blnPriority;
	
		lstSelns.forEach(function (rngSeln) {
			rngSeln.forEachNodeInRange(function (oNode) {
	
				if (oNode.type() !== 'empty') {
					strID = oNode.id;
					// Unless we have already processed this node as note for a preceding line
					if (lstSeen.indexOf(strID) == -1) {
						
						// get text and target list
						strURL=strPrefix + encodeURIComponent(oNode.text()) + '&forlist=' + strListName;
						strNodePath='//@id=' + strID + '/@type=body/descendant-or-self::*';
						lstNoteParas=tree.evaluateNodePath(strNodePath);
		
						// collect any notes
						if (lstNoteParas.length) {
							strURL+='&note=';
							strNote='';
							lstNoteParas.forEach(function(oChild) {
								strNote+=(oChild.text() + '\\n');
								lstSeen.push(oChild.id); // record this node as already processed
							});
							strURL+=encodeURIComponent(strNote);
						}
		
						// get any priorities
						blnPriority=oNode.hasTag('priority');
						if (blnPriority) {
							strValue=oNode.tag('priority');
							if (lstPriorities.indexOf(strValue) !== -1) {
								strURL+= ('&priority=' + strValue);
							}
						}
		
						// and any other remaining tag keys
						dctTags=oNode.tags();
						lstKeys = Object.keys(dctTags);
						lngKeys = lstKeys.length;
						if (lngKeys) {
							if ((!blnPriority) || (lngKeys > 1)) {
								strURL+='&tags=';
								iIndex=lstKeys.indexOf('priority');
								if (iIndex !== -1) {
									lstKeys.splice(iIndex, 1); // remove any @priority tag
								}
								strURL+=encodeURIComponent(lstKeys.join('\\n'));
							}
						}
						lstURL.push(strURL);
					}
				}
			});
		});
		lstURL.forEach(function (strURL) {
			editor.openLink(strURL);
		});
		return lstURL;
	}
"

on run
	set varResult to missing value
	tell application "FoldingText"
		if not pblnDebug then
			set lstDocs to documents
			if lstDocs ≠ {} then
				tell item 1 of lstDocs
					set varResult to (evaluate script pstrJS with options precOptions)
				end tell
			end if
		else
			-- debug script automatically refers to the SDK version of the editor
			-- which must be open: FoldingText > Help > SDK > Run Editor
			set varResult to (debug script pstrJS with options precOptions)
		end if
	end tell
	return varResult
end run

Works like a champ! Thanks so much.

I assume it will also work great in Taskpaper once the dev version reaches parity with the current FoldingText (I’m thinking FT is a bit ahead now–I got an error when I switched the "tell application “Taskpaper”: "TypeError: ‘undefined’ is not a function (evaluating ‘rngSeln.forEachNodeInRange’)

At any rate, your work with all these scripts for FT and TP is so useful to me. Thanks again!

Here is a version which I have edited a little for TaskPaper 3 (Dev):

  • forEachNodeInRangeforEachLineInRange
  • body"note"
property pblnDebug : false
property precOptions : {listname:"From TaskPaper"}

-- twodo:///add?task=[title]&forlist=&note=[your note]&priority=[0,1,2,3]&tags=[comma separated names]

property pstrJS : "
	function (editor, options) {
		'use strict';
		var	tree=editor.tree(),
			dctTags, lstKeys,
			lstSelns = editor.selectedRanges(),
			lstSeen = [],
			lstURL=[], lstNoteParas, lstPriorities=['0','1','2','3'],
			strNodePath,
			strID, strValue, strNote,
			strPrefix='twodo:///add?task=',
			strListName=options.listname,
			strURL='', lngKeys, iIndex,blnPriority;
	
		lstSelns.forEach(function (rngSeln) {
			rngSeln.forEachLineInRange(function (oNode) {
	
				if (oNode.type() !== 'empty') {
					strID = oNode.id;
					// Unless we have already processed this node as note for a preceding line
					if (lstSeen.indexOf(strID) == -1) {
						
						// get text and target list
						debugger;
						strURL=strPrefix + oNode.text() + '&forlist=' + strListName;
						strNodePath='//@id=' + strID + '/@type=\"note\"/descendant-or-self::*';
						lstNoteParas=tree.evaluateNodePath(strNodePath);
		
						// collect any notes
						if (lstNoteParas.length) {
							strURL+='&note=';
							strNote='';
							lstNoteParas.forEach(function(oChild) {
								strNote+=(oChild.text() + '\\n');
								lstSeen.push(oChild.id); // record this node as already processed
							});
							strURL+=strNote;
						}
		
						// get any priorities
						blnPriority=oNode.hasTag('priority');
						if (blnPriority) {
							strValue=oNode.tag('priority');
							if (lstPriorities.indexOf(strValue) !== -1) {
								strURL+= ('&priority=' + strValue);
							}
						}
		
						// and any other remaining tag keys
						dctTags=oNode.tags();
						lstKeys = Object.keys(dctTags);
						lngKeys = lstKeys.length;
						if (lngKeys) {
							if ((!blnPriority) || (lngKeys > 1)) {
								strURL+='&tags=';
								iIndex=lstKeys.indexOf('priority');
								if (iIndex !== -1) {
									lstKeys.splice(iIndex, 1); // remove any @priority tag
								}
								strURL+=lstKeys.join(',');
							}
						}
						lstURL.push(encodeURI(strURL));
					}
				}
			});
		});
		lstURL.forEach(function (strEncodedURL) {
			editor.openLink(strEncodedURL);
		});
		return lstURL;
	}
"

on run
	set varResult to missing value
	tell application "TaskPaper"
		if not pblnDebug then
			set lstDocs to documents
			if lstDocs ≠ {} then
				tell item 1 of lstDocs
					set varResult to (evaluate script pstrJS with options precOptions)
				end tell
			end if
		else
			-- debug script automatically refers to the SDK version of the editor
			-- which must be open: FoldingText > Help > SDK > Run Editor
			set varResult to (debug script pstrJS with options precOptions)
		end if
	end tell
	return varResult
end run

Hey, that’s great. Works good. I did have to change a line because the tags were getting separated by a new line instead of a comma:

// and any other remaining tag keys
					dctTags=oNode.tags();
					lstKeys = Object.keys(dctTags);
					lngKeys = lstKeys.length;
					if (lngKeys) {
						if ((!blnPriority) || (lngKeys > 1)) {
							strURL+='&tags=';
							iIndex=lstKeys.indexOf('priority');
							if (iIndex !== -1) {
								lstKeys.splice(iIndex, 1); // remove any @priority tag
							}
							strURL+=lstKeys.join(',');

(basically, just changed the \n to a comma in the last line above.

Thanks - I’ve amended that

Hi, Rob

I’m now using foldingtext as well as omnifocus. Sometimes I need transfer some part of foldingtext file to OF. I found you’ve written some scripts two years ago. But it no longer works for FT3 & OF2. I know you’ve no longer use OF, but I think it may be a not-so-hard work to rewrite it again. I know nothing about coding. Do you or anybody else have interest to do this work?

Thx

I don’t have a copy of OF2, so I couldn’t test anything, but a couple of things come to mind:

  • Can it import TaskPaper files intelligently (mapping tags values to its own field values) ?
  • Can it import OPML ?

We could certainly write something to convert a FoldingText selection to a TaskPaper format, and there is already a script for saving tagged FoldingText outlines as OPML.

Failing that – it should be something which Omni Support could undertake, or help you with.

Thank you for your reply. I’ll try. The new Omni forum seems not so active as the old forum.

I think they, quite understandably, treat their forums more as a peer-to-peer help framework than as an arm of their support system. It might be worth logging a direct support request to them.

this may be the silliest question imaginable, but for the life of me I can’t figure it out – how do you USE this applescript to transfer Taskpaper to 2Do? Do I turn it into a service, or what?

Thanks so much for any info.

To test it, you can:

  • paste into Script Editor.app
  • set the language (top left) to JavaScript
  • Run

For more regular use, you could attach it to a keystroke with something like FastScripts or Keyboard Maestro, or just run it from a script menu.

I can not get the Taskpaper to 2do Script to compile in Script Editor. I’m on TaskPaper 3.3.1

I get Syntax Error: Expected “,” but found “script”.
on line: set varResult to (…

Also, 2do’s url scheme needs to be sent with an x-callback url to work. I added x-callback to the FoldingText script, and it works now.

Hey, can you drop the modified version?
I don’t know what X-callback is.