×

“Actions” or “Tasks”

When interacting with the OmniFocus application’s user-interface (UI for short), you will encounter the term “Action” which refers to a new to-do item for a project. However, in the OmniPlan application, such a time-related element is called a “Task.”

The Omni Automation support in OmniFocus considers the terms “action” and “task” to be functionally synonymous, and refers to the scriptable element representing an “action” as a “task.”

In other words: a “task” is a something that needs doing, and “action” is the element in the OmniFocus interface that represents that need or to-do item. Scripts reference tasks, and the graphical user-interface of OmniFocus references actions. In the following documentation, consider tasks to be equivalent to actions.

Properties

An instance of the Task class is defined by the value of its properties.

IMPORTANT: some of these properties have values that are JavaScript date objects. An overview of some of the techniques for generating data objects in Omni Automation are detailed in the shared Date class documentation.

The following script shows how to drop a task:

omnifocus://localhost/omnijs-run?script=try%7Bvar%20win%20%3D%20document%2Ewindows%5B0%5D%0Avar%20tasks%20%3D%20win%2Eselection%2Etasks%0Atasks%2EforEach%28task%20%3D%3E%20%7B%0A%09task%2Eactive%20%3D%20false%0A%7D%29%7Dcatch%28err%29%7Bconsole%2Elog%28err%29%7D
Drop Selected Tasks
 

var win = document.windows[0] var tasks = win.selection.tasks tasks.forEach(task => { task.active = false })

The following script uses the dropDate property of the Task class and the flattenedTasks property of the Database class to revive tasks that were dropped since the start of the day (12:00 AM):

omnifocus://localhost/omnijs-run?script=try%7Bvar%20midnightToday%20%3D%20Calendar%2Ecurrent%2EstartOfDay%28new%20Date%29%0AflattenedTasks%2EforEach%28task%20%3D%3E%20%7B%0A%09if%20%28task%2EdropDate%20%3E%3D%20midnightToday%29%7B%0A%09%09task%2Eactive%20%3D%20true%0A%09%7D%0A%7D%29%7Dcatch%28err%29%7Bconsole%2Elog%28err%29%7D
Un-Drop Tasks Dropped Today
 

var cal = Calendar.current var now = new Date() var midnightToday = cal.startOfDay(now) var dc = cal.dateComponentsFromDate(midnightToday) dc.day = dc.day + 1 var midnightTomorrow = cal.dateFromDateComponents(dc) flattenedTasks.forEach(task => { var tdDate = task.dropDate if (tdDate >= midnightToday && tdDate < midnightTomorrow){ task.active = true } })
omnifocus://localhost/omnijs-run?script=try%7Bvar%20cal%20%3D%20Calendar%2Ecurrent%0Avar%20now%20%3D%20new%20Date%28%29%0Avar%20midnightToday%20%3D%20cal%2EstartOfDay%28now%29%0Avar%20dc%20%3D%20cal%2EdateComponentsFromDate%28midnightToday%29%0Adc%2Eday%20%3D%20dc%2Eday%20%2D%201%0Avar%20midnightYesterday%20%3D%20cal%2EdateFromDateComponents%28dc%29%0AflattenedTasks%2EforEach%28task%20%3D%3E%20%7B%0A%09var%20tdDate%20%3D%20task%2EdropDate%0A%09if%20%28tdDate%20%3E%3D%20midnightYesterday%20%26%26%20tdDate%20%3C%20midnightToday%29%7B%0A%09%09task%2Eactive%20%3D%20true%0A%09%7D%0A%7D%29%7Dcatch%28err%29%7Bconsole%2Elog%28err%29%7D
Un-Drop Tasks Dropped Yesterday
 

var cal = Calendar.current var now = new Date() var midnightToday = cal.startOfDay(now) var dc = cal.dateComponentsFromDate(midnightToday) dc.day = dc.day - 1 var midnightYesterday = cal.dateFromDateComponents(dc) flattenedTasks.forEach(task => { var tdDate = task.dropDate if (tdDate >= midnightYesterday && tdDate < midnightToday){ task.active = true } })

NOTE: Both of the previous script examples use properties and functions of the Calendar class to perform date calculations.

Set Floating Timezone Property

The following plug-in will set the value of the shouldUseFloatingTimeZone property for all existing tasks:

Set Time Zone Type for All Tasks
 

/*{ "type": "action", "targets": ["omnifocus"], "author": "Otto Automator", "identifier": "com.omni-automation.setTimeZoneTypeForAllTasks", "version": "1.0", "description": "Converts the time zone type for all tasks to the chosen type.", "label": "Set Time Zone Type for All Tasks", "shortLabel": "Set Time Zone Types" }*/ (() => { var action = new PlugIn.Action(function(selection, sender){ var timezoneTypesMenu = new Form.Field.Option( "timezoneType", null, [0,1], ["Current Time Zone", "Floating Time Zone"], 0 ) var inputForm = new Form() inputForm.addField(timezoneTypesMenu) var formPrompt = "Choose the time zone type for all tasks:" var buttonTitle = "Continue" formPromise = inputForm.show(formPrompt,buttonTitle) inputForm.validate = function(formObject){ return true } formPromise.then(function(formObject){ var typeIndex = formObject.values['timezoneType'] flattenedTasks.forEach((task) => { task.shouldUseFloatingTimeZone = [false, true][typeIndex] }) }) formPromise.catch(function(err){ console.error("form cancelled", err.message) }) }); action.validate = function(selection, sender){ return (flattenedTasks.length > 0) }; return action; })();

The TaskArray Class

An Array containing Task objects. Some of the properties of the Task class have values that are instances of the TaskArray class.

The TaskArray class offers a function for locating a task within the array by the name of the task.

Here is an example script that uses the flattenedTasks of the Database class to reveal the first occurence of a task by name in the database:

Reveal First Named Task


var task = flattenedTasks.byName("Clean Gutters") if(task){ var id = task.id.primaryKey URL.fromString("omnifocus:///task/" + id).open() }

To locate all tasks named with a specified name, use the JavaScript filter() function called on an instance of the TaskArray class, such as the flattenedTasks property of the Database class:

Locate Tasks with Specified Name


var taskName = "Summary" var matchedTasks = flattenedTasks.filter(task => { return task.name === taskName })

RELATED: In addition to the name property, database objects are sometimes identified by their unique identifier string. The Task class offers a global function for locating a task by identifier:

omnifocus://localhost/omnijs-run?script=try%7Bvar%20win%20%3D%20document%2Ewindows%5B0%5D%0Avar%20tasks%20%3D%20win%2Eselection%2Etasks%0Aif%20%28tasks%2Elength%20%3D%3D%3D%201%29%7B%0A%09var%20taskID%20%3D%20tasks%5B0%5D%2Eid%2EprimaryKey%0A%09console%2Elog%28taskID%29%0A%09new%20Alert%28%22Task%20ID%22%2CtaskID%29%2Eshow%28%29%0A%7D%7Dcatch%28err%29%7Bconsole%2Elog%28err%29%7D
ID of Selected Task
 

var win = document.windows[0] var tasks = win.selection.tasks if (tasks.length === 1){ var taskID = tasks[0].id.primaryKey console.log(taskID) new Alert("Task ID",taskID).show() }
Reveal Task by ID


var taskID = "dXL1Kdp4XCx" var task = Task.byIdentifier(taskID) if(task){ var urlStr = "omnifocus:///task/" + taskID URL.fromString(urlStr).open() }

The Task.Status Class

The values for the taskStatus property of the Task class:

omnifocus://localhost/omnijs-run?script=try%7Bvar%20tasks%20%3D%20flattenedTasks%2Efilter%28task%20%3D%3E%20%7B%0A%09return%20task%2EtaskStatus%20%3D%3D%3D%20Task%2EStatus%2EDueSoon%0A%7D%29%0Aif%20%28tasks%2Elength%20%20%3E%200%29%7B%0A%09tasks%2EforEach%28task%20%3D%3E%20%7Btask%2Eflagged%20%3D%20true%7D%29%0A%09document%2Ewindows%5B0%5D%2Eperspective%20%3D%20Perspective%2EBuiltIn%2EFlagged%0A%7D%7Dcatch%28err%29%7Bconsole%2Elog%28err%29%7D
Flag Tasks that are Due Soon
 

var tasks = flattenedTasks.filter(task => { return task.taskStatus === Task.Status.DueSoon }) if (tasks.length > 0){ tasks.forEach(task => {task.flagged = true}) document.windows[0].perspective = Perspective.BuiltIn.Flagged }

Creating Tasks

To create an instance of the Task class, the standard JavaScript new item constructor is used:

omnifocus://localhost/omnijs-run?script=try%7Btask%20%3D%20new%20Task%28%22My%20Task%22%29%7Dcatch%28err%29%7Bconsole%2Elog%28err%29%7D
New Task at End of Inbox
 

var task = new Task("My Task")
omnifocus://localhost/omnijs-run?script=try%7Btask%20%3D%20new%20Task%28%22My%20Task%22%2C%20inbox%2Ebeginning%29%7Dcatch%28err%29%7Bconsole%2Elog%28err%29%7D
New Task at Beginning of Inbox
 

var task = new Task("My Task", inbox.beginning)

Once a task instance has been created, the value of its properties can be set:

omnifocus://localhost/omnijs-run?script=try%7Btask%20%3D%20new%20Task%28%22My%20Task%22%2C%20inbox%2Ebeginning%29%0Atask%2Enote%20%3D%20%22This%20is%20the%20note%20for%20the%20created%20task%2E%22%0Atask%2Eflagged%20%3D%20true%7Dcatch%28err%29%7Bconsole%2Elog%28err%29%7D
New Task with Properties
 

var task = new Task("My Task", inbox.beginning) task.note = "This is the note for the created task." task.flagged = true

Here's an example script that creates and then displays a task in the application interface:

omnifocus://localhost/omnijs-run?script=try%7Btask%20%3D%20new%20Task%28%22My%20New%20Task%22%2C%20inbox%2Ebeginning%29%0AurlString%20%3D%20%22omnifocus%3A%2F%2F%2Ftask%2F%22%20%2B%20task%2Eid%2EprimaryKey%0AURL%2EfromString%28urlString%29%2Ecall%28%28%29%3D%3E%7B%7D%29%7Dcatch%28err%29%7Bconsole%2Elog%28err%29%7D
Make and Show New Task
 

var task = new Task("My New Task", inbox.beginning) urlString = "omnifocus:///task/" + task.id.primaryKey URL.fromString(urlString).open()

(1) The JavaScript new item constructor takes the name for the new task as input, and optionally the insertion postion in the task’s parent container, as a second input parameter. The result is an object reference to the newly created task, which is stored in the variable: task

(2) Every element of the OmniFocus database, including tasks, projects, and folders, shares the id and primaryKey properties, which generate a unique identifier string for each element. This identifer can be appended to an OmniFocus URL that will cause the identified item to display in the app UI when the URL is called.

(3) The URL string is converted into a URL object using the fromString(…) method of the URL class, which is then called using the open( ) method of the same class.

Transport Text Parsing

Omni Automation support in OmniFocus offers a special class function for converting text instructions into new tasks. The syntax for the task creation instructions uses common characters and constructs that you can use as shorthand way of indicating tasks. When the text containing the instructions is parsed using the byParsingTransportText(…) function, new tasks are created.

In the following example, a new task is created in the specified existing project. The due date/time are assigned along with a specified existing tag.

Create Task(s) by Parsing Text


var txt = `New Task!::Existing Project Title #12/12/20 8.30a $3h @existing-tag-name//This is the task note` var newTasks = Task.byParsingTransportText(txt, true) var taskID = newTasks[0].id.primaryKey URL.fromString("omnifocus:///task/" + taskID).open()

Here are the syntax rules for the task-creation shorthand:

Task Instance Functions

Here are the functions that can be performed with an instance of the Task class:

Working with Tasks

The following script examples demonstrate how to use some of the functions of the Task class:

This first script demonstrates how to retrieve references to the currently selected tasks. The result will be an array of object references, or an empty array if no tasks are selected.

Get Selected Tasks


document.windows[0].selection.tasks

The value of the flattenedTasks property of the Database class is used to retrieve object references to all tasks in the database:

Retrieve References to All Tasks


flattenedTasks
Getting object references to all tasks in the Inbox:
Get All Tasks in Inbox


var masterTaskArray = new Array() inbox.apply(task => masterTaskArray.push(task)) console.log(masterTaskArray)

The next script uses the apply(…) function to generate an array of object references to all of the tasks contained within the entire hierarchy of folders and projects:

Get All Tasks Contained in Projects in the Library


var masterTaskArray = new Array() library.apply(function(item){ if (item instanceof Project && item.task.hasChildren){ masterTaskArray.push(item.task.children) } }) console.log(masterTaskArray)

The following script will focus any projects that contain tasks that are due soon:

omnifocus://localhost/omnijs-run?script=try%7Bvar%20parentProjects%20%3D%20new%20Array%28%29%0AflattenedProjects%2EforEach%28project%20%3D%3E%20%7B%0A%09if%20%28project%2EhasChildren%29%7B%0A%09%09project%2Echildren%2EforEach%28task%20%3D%3E%20%7B%0A%09%09%09if%20%28task%2EtaskStatus%20%3D%3D%3D%20Task%2EStatus%2EDueSoon%29%7B%0A%09%09%09%09if%20%28%21parentProjects%2Eincludes%28project%29%29%7B%0A%09%09%09%09%09parentProjects%2Epush%28project%29%0A%09%09%09%09%7D%09%09%09%0A%09%09%09%7D%0A%09%09%7D%29%0A%09%7D%0A%7D%29%0Aif%20%28parentProjects%2Elength%20%3E%200%29%7B%0A%09document%2Ewindows%5B0%5D%2Eperspective%20%3D%20Perspective%2EBuiltIn%2EProjects%0A%09document%2Ewindows%5B0%5D%2Efocus%20%3D%20parentProjects%0A%7D%7Dcatch%28err%29%7Bconsole%2Elog%28err%29%7D
Focus Projects with Tasks Due Soon
 

var parentProjects = new Array() flattenedProjects.forEach(project => { if (project.hasChildren){ project.children.forEach(task => { if (task.taskStatus === Task.Status.DueSoon){ if (!parentProjects.includes(project)){ parentProjects.push(project) } } }) } }) if (parentProjects.length > 0){ document.windows[0].perspective = Perspective.BuiltIn.Projects document.windows[0].focus = parentProjects }

Assigning Tags to Tasks

The addTag(…) and addTags(…) functions of the Task class are used to assign tags to tasks. These functions require object references to the tags to add as the function input. The following script uses the flattenedTags property of the Database class to generate a reference to an existing tag, creating a new tag if needed:

Get Tag Reference by Name


var tag = flattenedTags.byName("Conifer") || new Tag("Conifer")

The previous technique for generating a tag reference is used to apply a specified tag to the selected tasks:

Apply Tag to Selected Tasks


var tagName = "Redwood" var tag = flattenedTags.byName(tagName) || new Tag(tagName) var tasks = document.windows[0].selection.tasks tasks.forEach(task => {task.addTag(tag)})

Using similar techniques and the addTags(…) function to apply an array of tags to each of the selected tasks:

Apply Tags to Selected Tasks


var tagTitles = ["Northeast","Atlantic Seaboard","New England","Rhode Island"] var tagRefs = new Array() tagTitles.forEach(title => { var tagObj = flattenedTags.byName(title) || new Tag(title) tagRefs.push(tagObj) }) var tasks = document.windows[0].selection.tasks tasks.forEach(task => {task.addTags(tagRefs)})

Plug-In: Open Link in Note

This Omni Automaton plug-in will scan the note text of the selected project or task, and if it locates an URL in the text, will open the found URL.

Open Project|Action Note URL
 

/*{ "type": "action", "targets": ["omnifocus"], "author": "Otto Automator", "identifier": "com.omni-automation.call-note-url", "version": "1.5", "description": "This action will open the URL string that is the value of the note of the selected action.", "label": "Open Note URL", "shortLabel": "Open Note URL" }*/ (() => { var action = new PlugIn.Action(function(selection, sender){ var note = (selection.projects.length === 1 ? selection.projects[0].task.note : selection.tasks[0].note); if (note && note.includes("://")) { var urlStr = note.match(/[^<\s][^\s]+\/\/[^\s>]+/)[0] var url = URL.fromString(urlStr) if(url){ url.open() } else { console.error("ERROR: \"" + urlStr + "\" is not a valid URL.") } } }); action.validate = function(selection, sender){ return ( selection.projects.length === 1 || selection.tasks.length === 1 ) }; return action; })();

Move Tasks

Here are two Omni Automation Plug-Ins for moving selected tasks into either a new task group or a new project.

Move Selected Tasks into New Project
  

/*{ "type": "action", "targets": ["omnifocus"], "author": "Otto Automator", "identifier": "com.omni-automation.of.move-selected-tasks-into-project", "version": "1.2", "description": "Move the selected tasks into a new top-level project.", "label": "Move Selected Tasks into New Project", "shortLabel": "Move Tasks" }*/ (() => { var action = new PlugIn.Action(function(selection, sender){ // selection options: tasks, projects, folders, tags // CONSTRUCT THE FORM var inputForm = new Form() // CREATE FORM ELEMENT: TEXT INPUT textField = new Form.Field.String("projectName", "Project Name", null) // CREATE FORM ELEMENT: OPTION MENU popupMenu = new Form.Field.Option( "projectType", "Project Type", [0, 1, 2], ["Parallel","Sequential","Single Actions"], 0 ) // ADD THE ELEMENTS TO THE FORM inputForm.addField(textField) inputForm.addField(popupMenu) // DIALOG PROMPT AND OK BUTTON TITLE let formPrompt = "Enter the name for the new top-level project and select its project type:" let buttonTitle = "Continue" // DISPLAY THE FORM DIALOG formPromise = inputForm.show(formPrompt, buttonTitle) // VALIDATE FORM CONTENT inputForm.validate = function(formObject){ // EXTRACT VALUES FROM THE FORM’S VALUES OBJECT textValue = formObject.values['projectName'] return ((textValue) ? true:false) } // PERFORM PROCESSES USING FORM DATA formPromise.then(function(formObject){ textValue = formObject.values['projectName'] menuItemIndex = formObject.values['projectType'] // CREATE PROJECT AND MOVE TASKS project = new Project(textValue) moveTasks(selection.tasks, project) // SET THE PROJECT TYPE if (menuItemIndex === 1){ project.task.sequential = true } else if (menuItemIndex === 2){ project.containsSingletonActions = true } // SHOW THE PROJECT projID = project.id.primaryKey urlStr = "omnifocus:///task/" + projID URL.fromString(urlStr).open() }) // PROCESS FORM CANCELLATION formPromise.catch(function(error){ console.log("form cancelled", error) }) }); action.validate = function(selection, sender){ // selection options: tasks, projects, folders, tags return (selection.tasks.length > 0) }; return action; })();
Move Selected Tasks into New Action Group
 

/*{ "type": "action", "targets": ["omnifocus"], "author": "Otto Automator", "identifier": "com.omni-automation.of.move-selected-tasks-into-new-action", "version": "1.2", "description": "Move the selected tasks into a new top-level action group.", "label": "Move Selected Tasks into New Action Group", "shortLabel": "Move Tasks" }*/ (() => { var action = new PlugIn.Action(function(selection, sender){ // selection options: tasks, projects, folders, tags // CONSTRUCT THE FORM var inputForm = new Form() // CREATE FORM ELEMENTS: TEXT INPUT textField = new Form.Field.String("groupName", null, null) // CREATE FORM ELEMENT: OPTION MENU popupMenu = new Form.Field.Option( "actionType", "Action Type", [0, 1], ["Parallel","Sequential"], 0 ) // ADD THE ELEMENTS TO THE FORM inputForm.addField(textField) inputForm.addField(popupMenu) // DIALOG PROMPT AND OK BUTTON TITLE let formPrompt = "Provide name and type for new top-level action group:" let buttonTitle = "Continue" // DISPLAY THE FORM DIALOG formPromise = inputForm.show(formPrompt, buttonTitle) // VALIDATE FORM CONTENT inputForm.validate = function(formObject){ // EXTRACT VALUES FROM THE FORM’S VALUES OBJECT textValue = formObject.values['groupName'] return ((textValue) ? true:false) } // PERFORM PROCESSES USING FORM DATA formPromise.then(function(formObject){ textValue = formObject.values['groupName'] menuItemIndex = formObject.values['actionType'] taskGroup = new Task(textValue) moveTasks(selection.tasks, taskGroup) // SET THE PROJECT TYPE if (menuItemIndex === 1){ taskGroup.sequential = true } // SHOW THE ACTION taskID = taskGroup.id.primaryKey urlStr = "omnifocus:///task/" + taskID URL.fromString(urlStr).open() }) // PROCESS FORM CANCELLATION formPromise.catch(function(err){ console.log("form cancelled", err.message) }) }); action.validate = function(selection, sender){ // selection options: tasks, projects, folders, tags return (selection.tasks.length > 0) }; return action; })();

Display Host Project

If you have a project task selected, this plug-in will feature the containing project in the OmniFocus window:

Display Host Project
 

/*{ "type": "action", "targets": ["omnifocus"], "author": "Otto Automator", "identifier": "com.omni-automation.of.display-host-project", "version": "1.3", "description": "This action will display the host project of the selected task.", "label": "Display Host Project", "shortLabel": "Display Project" }*/ (() => { var action = new PlugIn.Action(function(selection, sender){ // action code // selection options: tasks, projects, folders, tags project = selection.tasks[0].containingProject projID = project.id.primaryKey urlStr = "omnifocus:///task/" + projID URL.fromString(urlStr).open() }); action.validate = function(selection, sender){ // validation code // selection options: tasks, projects, folders, tags return (selection.tasks.length === 1 && selection.tasks[0].containingProject) }; return action; })();

Delete All Tasks Tagged with Specific Tag

Here’s an Omni Automation action that will delete all of the tasks that are tagged with the tag specified by the user.

Delete All Tasks Tagged with Tag
 

/*{ "type": "action", "targets": ["omnifocus"], "author": "Otto Automator", "identifier": "com.omni-automation.com.of.delete-tasks-with-tag", "version": "1.0", "description": "This action will delete all tasks that have been tagged with the specified tag.", "label": "Delete All Tasks with Tag", "shortLabel": "Delete Tasks with Tag" }*/ (() => { var action = new PlugIn.Action(function(selection, sender){ // action code // selection options: tasks, projects, folders, tags try { var tagNames = new Array() tags.apply(tag => tagNames.push(tag.name)) if (tagNames.length === 0){throw Error("No tags in database")} var textInputField = new Form.Field.String( "textInput", null, null ) var inputForm = new Form() inputForm.addField(textInputField) var formPrompt = "Enter the title of the tag whose tasks will be deleted:" var buttonTitle = "Continue" formPromise = inputForm.show(formPrompt,buttonTitle) inputForm.validate = function(formObject){ var inputText = formObject.values['textInput'] var textStatus = ((!inputText)?false:true) return (textStatus && tagNames.includes(inputText)) ? true : false } formPromise.then(function(formObject){ var tagTitle = formObject.values['textInput'] var targetTag = null tags.apply(function(tag){ if(tag.name == tagTitle){ targetTag = tag return ApplyResult.Stop } }) var targetTasks = targetTag.tasks targetTasks.forEach(task => { deleteObject(task) }) var msg = String(targetTasks.length) + " task(s) have been deleted." new Alert("STATUS", msg).show() }) formPromise.catch(function(err){ console.error("form cancelled", err.message) }) } catch(err){ new Alert('SCRIPT ERROR', err.message).show() } }); action.validate = function(selection, sender){ // validation code // selection options: tasks, projects, folders, tags return true }; return action; })();

Save Task as Calendar File

This action will save the data of the selected task to a standard iCalendar file.

iCal File for Task
 

/*{ "type": "action", "targets": ["omnifocus"], "author": "Otto Automator", "identifier": "com.omni-automation.of.ics-file-of-task", "version": "1.0", "description": "This action will create an iCal (ics) file matching the parameters of the currently selected task.", "label": "iCal File for Task", "shortLabel": "iCal File" }*/ (() => { var action = new PlugIn.Action(function(selection, sender){ // action code // selection options: tasks, projects, folders, tags var task = selection.tasks[0] var taskID = task.id.primaryKey var taskURL = "omnifocus:///task/" + taskID var taskTitle = task.name var taskNote = task.note if (!taskNote){taskNote = ""} var taskDueDateStamp = dateToCalDateStamp(task.dueDate) + "Z" var taskDuration = task.estimatedMinutes if (!taskDuration){taskDuration = 60} var taskEndDate = task.dueDate taskEndDate = new Date(taskEndDate.setMinutes(taskEndDate.getMinutes() + taskDuration)) var taskEndDateStamp = dateToCalDateStamp(taskEndDate) + "Z" var dateStamp = dateToCalDateStamp(new Date()) + "Z" var uid = uuidv4() var calEventString = `BEGIN:VCALENDAR VERSION:2.0 PRODID:-//ZContent.net//Zap Calendar 1.0//EN CALSCALE:GREGORIAN METHOD:PUBLISH BEGIN:VEVENT SUMMARY:${taskTitle} UID:${uid} SEQUENCE:0 STATUS:CONFIRMED TRANSP:TRANSPARENT RRULE: DTSTART:${taskDueDateStamp} DTEND:${taskEndDateStamp} DTSTAMP:${dateStamp} CATEGORIES: LOCATION: GEO: DESCRIPTION:${taskNote} URL:${taskURL} END:VEVENT END:VCALENDAR` calEventString = String(calEventString) console.log(calEventString) textData = Data.fromString(calEventString) wrapper = FileWrapper.withContents('event.ics', textData) filesaver = new FileSaver() fileSaverPromise = filesaver.show(wrapper) fileSaverPromise.then(function(urlObj){ console.log(urlObj.string) urlObj.open() }) fileSaverPromise.catch(function(err){ console.log(err.message) }) }); action.validate = function(selection, sender){ // validation code // selection options: tasks, projects, folders, tags return (selection.tasks.length === 1 && selection.tasks[0].dueDate != null) }; return action; })(); function uuidv4() { return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function(c) { var r = Math.random() * 16 | 0, v = c == 'x' ? r : (r & 0x3 | 0x8); return v.toString(16); }); } function dateToCalDateStamp(date){ var dateStamp = date.toISOString().split("-").join("").split(":").join("").replace(".","") dateStamp = dateStamp.substring(0, dateStamp.length - 4) return dateStamp }