Send Tasks to Omnifocus/Entire Note to Bear

omnifocus
#1

I’m trying to make a script that takes any lines with “Omnifocus:” and creates an Omnifocus task out of it, and then simply sends the entire note to Bear with the top line as the heading. I know this is probably easy for you guys but I have really little experience with Javascript and can’t figure out what’s going wrong. I tried to combine the “Tasks in Omnifocus” script by agile tortoise and Tim Nanchuck’s personal script but I’m doing something wrong. If anybody is able to either make an independent script for add as Omnifocus if this task has “Omnifocus:” or can tell me what I’m doing wrong, that’d be great. -Thanks, Sam

// split draft into lines
const lines = draft.content.split("\n");
const baseURL = “omnifocus://x-callback-url/add”;

// loop over lines and send each to Fantastical
for(var line of lines) {
if (line.includes("!")) { continue; }
// create and configure callback object
var cb = CallbackURL.create();
cb.baseURL = baseURL;
cb.addParameter(“name”, line);
// open and wait for result
var success = cb.open();
if (success) {
console.log(“Task created in Omnifocus”);
}
else {
context.fail();
}
}

Alternatively, I tried to make @TiffanyW_412 Add multiple tasks to Omnifocus script work, but was unsuccessful.

// See online documentation for examples
// http://drafts5-help.agiletortoise.com/

// Script steps run short Javascripts
// For documentation and examples, visit:
// http://help.agiletortoise.com

var text = “”;
var tasks = draft.content.split("\n");
for (var task in tasks) {
if (task.includes(“Omnifocus:”)
{
text += "- " + tasks[i] + “\n”;
}};

draft.setTemplateTag(“OFTasks”, text);

#2

I have been searching for the exact solution Sam is attempting to create—it would tremendously simplify my notetaking workflow. Might anyone with experience in JavaScript be able to lend us a hand?

#3

Hi Sam and KJ13,

I do exactly this, take lines that start with OmniFocus: (I actually use OF: for brevity, but have changed this script) and then send the rest of the note to Bear with the tag “meeting notes”. This script deletes the lines prefixed with OmniFocus from the final note in Bear, but it should be pretty straightforward to not delete those lines and send the entire draft unchanged to Bear.

This was adapted from another script I found, but I am having difficulty finding the original source. There is a great community out there, and sometimes with just a little modification you can make it do what you need.

I’ve been using this at work since Drafts 5 was released, and it’s working great. Let me know how it works for you.

Script:

// Process Meeting Notes

const taskPrefix = "OmniFocus: ";
const noteTag = "meeting notes";

// Function for removing the task prefix
function removeTaskPrefix(s) {
  var f       = (taskPrefix),
      r       = "",
      re      = new RegExp(f, "g"),
      matches = s.match(re);

  if (matches) {
    return s.replace(re,r);
  }
}

// Function to perform the callback url
function doCallbackURL(url, params) {
  var cb = CallbackURL.create();
  cb.baseURL = url;

  for(var key in params) {
   cb.addParameter(key, params[key]);
  }

  var success = cb.open();
  if (success) {
    console.log("Event created");
  } else {
    console.log(cb.status);
    if (cb.status == "cancel") {
      context.cancel();
    } else {
      context.fail();
    }
  }
}

// Scan for the task prefix in the draft
var d = draft.content;
var lines = d.split("\n");
var n = '';

for (var line of lines) {
  // If the line includes the task prefix, 
  // we remove exclude it from the final notes
  if (line.includes(taskPrefix)) {

    // Remove the trigger from the line
    var task = removeTaskPrefix(line);

    // OmniFocus URL Action
    doCallbackURL("omnifocus://x-callback-url/paste", {
      "content": task,
      "target": "inbox"
    });
  } else {
    n += line + "\n";
  }
}
draft.content = n;
draft.update();

// Send the note to Bear
doCallbackURL("bear://x-callback-url/create", {
  "text": draft.content,
  "tags": noteTag
});
2 Likes
#4

Hi Clarke,

Thank you so very much for your quick response! This works BRILLIANTLY!!

By the way, I tried to manipulate the code to retain in Bear the original lines with the “OF:” tasks, but I am consistently getting various error messages or missing tasks. Would you happen to know what in particular I would need to change so Bear keeps the original note in its entirety?

Gratefully,
KJ

#5

I remembered where I found the original version, from which I derived this. It was in the MacStories Drafts 5 review:

Drafts 5: The MacStories Review – MacStories

1 Like
#6

Hi KJ,

To keep the original OF, or OmniFocus in this case, lines in the note, there are a few changes needed.

What the loop over the lines is currently doing is going line by line looking for lines that start with the trigger phrase, OmniFocus. Then when it finds the trigger, it extracts the task and sends it off to OmniFocus. For all other lines that do not start with the trigger, it adds to a copy of the note so that it can use that for Bear. We can simplify all of this by not building the new content, the “n” variable, and not modify draft.content. The full version of this would be:

// Process Meeting Notes

const taskPrefix = "OmniFocus: ";
const noteTag = "meeting notes";

// Function for removing the task prefix
function removeTaskPrefix(s) {
  var f       = (taskPrefix),
      r       = "",
      re      = new RegExp(f, "g"),
      matches = s.match(re);

  if (matches) {
    return s.replace(re,r);
  }
}

// Function to perform the callback url
function doCallbackURL(url, params) {
  var cb = CallbackURL.create();
  cb.baseURL = url;

  for(var key in params) {
   cb.addParameter(key, params[key]);
  }

  var success = cb.open();
  if (success) {
    console.log("Event created");
  } else {
    console.log(cb.status);
    if (cb.status == "cancel") {
      context.cancel();
    } else {
      context.fail();
    }
  }
}

// Scan for the task prefix in the draft
var lines = draft.content.split("\n");

for (var line of lines) {
  // If the line includes the task prefix, 
  // we remove exclude it from the final notes
  if (line.includes(taskPrefix)) {

    // Remove the trigger from the line
    var task = removeTaskPrefix(line);

    // OmniFocus URL Action
    doCallbackURL("omnifocus://x-callback-url/paste", {
      "content": task,
      "target": "inbox"
    });
 }
}

// Send the note to Bear
doCallbackURL("bear://x-callback-url/create", {
  "text": draft.content,
  "tags": noteTag
});

I’m happy to explain things further if something doesn’t make sense.

Clarke

#7

Another thing to note is that I am using the TaskPaper style URL for OmniFocus so that it adds the task to the Inbox and immediately jumps back to Drafts. You could also just use a simpler URL to OmniFocus if you’d like it to create a new task there and wait for you to hit the “Save” button.

#8

Hi Clarke—yes! This is perfect!! I can’t believe my eyes when this thing kicks off for a note chock-full of tasks.

This will be a boon for productivity!

And thank you for your lucid explanation and commentary. I’m definately saving this thread for future reference.

Cheers to you my friend!

With gratitude,
KJ

#9

Great, I’m glad it works for you! It’s been great for me with meetings at work.

Clarke

#10

I’m just starting our with drafts, and tried to look around the forum but I could not find a solution for what I want.

I’m running @clarke s script and it’s amazing. But I’m trying to add a email component to it.

When I’m holding a meeting, and we decide different actions for different people I like to send an email to everyone with their actions. The meeting always consists of the same people.

Is there a way to send it directly from the note. I’m thinking something like this:

EA-A( Email Action for Anders): Move X
EA-A: Tell Y
EA-B (Email Action for Billy): Bring Z

Anders task will be added up and then sent to his email.
And the one task that Billy had would be sent directly!

Gratefully,
David

#11

Hello,

I am the new guy on the block. Pretty new to Draft and long term GTD/Omnifocus advocat.

Let me ask you one question. Why do you write a note in Draft, process it and export tasks to OF and than store the minutes in bear? What’s the need for bear here and why not leaving it in draft?

I ask BC I am currently evaluating draft vs bear vs Ulysses and I am not decided yet.

Your thoughts are much appreciated

Regards
rak

#12

Probably because you can accumulate notes for a task in a Bear note (or a Drafts draft) which enable you to do the task. Linking in both directions is the difficult bit. I once did it for Editorial but have now abandoned that script as I never used it.

#13

Hello Community,
The OF to Bear script is so close to meeting my needs. I’m trying to adapt the script @clarke shared to send tasks to Things via a callback URL and then Agenda.

The script works but all the tasks are title Undefined in Things.

I’m scoured the code and it all makes sense how it works but, I’m at a loss for what is causing the tasks to get named Undefined.

It’s probably worth mentioning I am using the Markdown checkbox - [ ] as a task prefix.

Any ideas how to correct the Undefined issue?

Thanks so much!

#14

Just to check, as you haven’t shared your adapted script, but do you have the taskPrefix constant defined like this?

const taskPrefix = "- [ ] ";

If you do, can you share the script you do have so we can see what it is doing and why the variable you are passing in to Things for a title is undefined?

#15

@sylumer

Here’s a copy of the code as I have adapted it.

// Process Meeting Notes
const taskPrefix = "- [ ] ";

// Function for removing the task prefix
function removeTaskPrefix(s) {
 
	var f       = (taskPrefix),
  r       = "",
  re      = new RegExp(f, "gm"),
  matches = s.match(re);

  if (matches) {
return s.replace(re,r);
  }

}

// Function to perform the callback url
function doCallbackURL(url, params) {
  var cb = CallbackURL.create();
  cb.baseURL = url;

  for(var key in params) {
   cb.addParameter(key, params[key]);
  }

  var success = cb.open();
  if (success) {
console.log("Event created");
  } else {
console.log(cb.status);
if (cb.status == "cancel") {
  context.cancel();
} else {
  context.fail();
}
  }
 
}

// Scan for the task prefix in the draft
var lines = draft.content.split("\n");

for (var line of lines) {
  // If the line includes the task prefix, 
  // we remove exclude it from the final notes
  if (line.includes(taskPrefix)) {

// Remove the trigger from the line
var task = removeTaskPrefix(line);

// Things URL Action
 
doCallbackURL("things://x-callback-url/add", {
  "title": task,
  "list": "inbox"
});
   
  }
}



//draft.content = n;
//draft.update();

/* Send the note to Bear
doCallbackURL("bear://x-callback-url/create", {
  "text": draft.content,
  "tags": noteTag
});*/ 

Thanks again for your help.

#16

See if this works for you.

// Process Meeting Notes
const taskPrefix = "- [ ] ";

// Function to perform the callback url
function doCallbackURL(url, params)
{
	var cb = CallbackURL.create();
	cb.baseURL = url;

	for (var key in params)
	{
		cb.addParameter(key, params[key]);
	}

	var success = cb.open();
	if (success)
	{
		console.log("Event created");
	}
	else
	{
		console.log(cb.status);
		if (cb.status == "cancel")
		{
			context.cancel();
		}
		else
		{
			context.fail();
		}
	}

}

// Scan for the task prefix in the draft
var lines = draft.content.split("\n");

for (var line of lines)
{
	// If the line includes the task prefix, 
	// we remove exclude it from the final notes
	if (line.includes(taskPrefix))
	{
		// Remove the trigger from the line
		//Split the line on the task prefix and it'll be in array index 1.
		var task = line.split(taskPrefix)[1];

		// Things URL Action
		doCallbackURL("things://x-callback-url/add",
		{
			"title": task,
			"list": "inbox"
		});
	}
}
#17

@sylumer, yes!

I see you used split() and dropped the removeTaskPrefix() function. What within the function was causing the problem? I want to learn.

Thanks again for your help!

#18

Technically the underlying issue isn’t in that function, it is in the nature of how it worked and the task prefix.

It essentially comes down to square brackets having special meaning in regular expressions (character classes).

Rather than try and explain that, I figured it might be better to simplify the entire matching process. In addition, the original match function only removes the prefix and returns the remainder of the line, which means any lead padding used for indenting tasks would also be picked up. Whilst that may not matter for you and your particular scenario, I figured it was actually better to return the remainder of the line after the task marker just in case it did.

But it is still possible to use the regular expression approach. You just have to escape your square brackets.

Run the JavaScript below. It will hopefully help you get an appreciation for what you would need to do and about the pre-padding point I mentioned above.

function removeTaskPrefix(s) {
 
  var f       = (taskPrefix),
  r       = "",
  re      = new RegExp(f, "gm"),
  matches = s.match(re);

  if (matches) {
  alert("matched '" + matches + "'");
return s.replace(re,r);
  }
  else alert("not matched '" + matches + "'");
}

//TEST 1 - a single alphabetic character
let taskPrefix = "x ";
alert("TEST 1 returned '" + removeTaskPrefix("x test one") + "'");

//TEST 2 - a single symbol followed by a space character
taskPrefix = "- ";
alert("TEST 2 returned '" + removeTaskPrefix("- test two") + "'");

//TEST 3 - as test 2, but now with an escaped opening square bracket and space
taskPrefix = "- \\[ ";
alert("TEST 3 returned '" + removeTaskPrefix("- [ test three") + "'");

//TEST 4 - as test 3, but now with an escaped closing square bracket and space
taskPrefix = "- \\[ \\] ";
alert("TEST 4 returned '" + removeTaskPrefix("- [ ] test four") + "'");

//TEST 5 - as test 4, but now with some additional padding on the test line
taskPrefix = "- \\[ \\] ";
alert("TEST 5 returned '" + removeTaskPrefix("    - [ ] test five") + "'");

Hope that covers everything for you.