×

Task Attachments

A task attachment is a representation for a file that is added (“attached”) to a task. Omni Automation in OmniFocus provides the ability to add, retrieve, and remove attachments to/from tasks.

Task Attachment Properties

There is one property regarding file attachments for the Task class:

Task Attachment Functions

The functions of the Task class dealing with the management of attachments.

Adding Attachments

Task attachments are instances of the FileWrapper class with each instance representing a file. FileWrappers can be generated from data of various FileTypes, such as images, and text, property list, and document files.

The following example plug-in demonstrates how create FileWrapper instances from chosen files and “attach” them to a selected task.

Add Chosen Files to Task as Attachments
 

/*{ "type": "action", "targets": ["omnifocus"], "author": "Otto Automaator", "identifier": "com.omni-automation.of.add-attachments-to-task", "version": "1.2", "description": "This action will add chosen files as attachments to the selected task.", "label": "Add Attachments to Task", "shortLabel": "Add Attachments" }*/ (() => { var action = new PlugIn.Action(function(selection, sender){ // action code // selection options: tasks, projects, folders, tags var task = selection.tasks[0] var picker = new FilePicker() picker.folders = false picker.multiple = true // Generic types: FileType.image, FileType.pdf, FileType.plainText, etc. // https://omni-automation.com/shared/filetypes.html picker.types = null // any file type picker.show().then(function(urlsArray){ urlsArray.forEach(url =>{ var urlStr = url.string var fileName = urlStr.substring(urlStr.lastIndexOf('/') + 1) url.fetch(function(data){ var wrapper = FileWrapper.withContents(decodeURIComponent(fileName), data) task.addAttachment(wrapper) }) }) }) }); action.validate = function(selection, sender){ // validation code // selection options: tasks, projects, folders, tags return (selection.tasks.length === 1) }; return action; })();

The following script creates a file attachment in JSON format to the selected task. Although there is no corresponding file on disk, the created attachment can be used to the store data passed to it.

(The following pair of JSON attachment scripts inspired by a plug-in example at graypegg.com)
omnifocus://localhost/omnijs-run?script=var%20tasks%20%3D%20document%2Ewindows%5B0%5D%2Eselection%2Etasks%0Aif%28tasks%2Elength%20%3D%3D%3D%201%29%7B%0A%09var%20%C6%92names%20%3D%20tasks%5B0%5D%2Eattachments%2Emap%28wrapper%20%3D%3E%20wrapper%2EpreferredFilename%29%0A%09var%20attachmentName%20%3D%20%22Storage%2Ejson%22%0A%09if%28%C6%92names%2Eincludes%28attachmentName%29%29%7B%0A%09%09console%2Eerror%28%22Existing%20Attachment%3A%22%2C%20attachmentName%29%0A%09%7D%20else%20%7B%0A%09%09var%20obj%20%3D%20%7B%22firstName%22%3A%22Otto%22%2C%22lastName%22%3A%22Automator%22%7D%0A%09%09var%20json%20%3D%20JSON%2Estringify%28obj%29%0A%09%09var%20attachmentData%20%3D%20Data%2EfromString%28json%29%0A%09%09var%20wrapper%20%3D%20FileWrapper%2EwithContents%28attachmentName%2C%20attachmentData%29%0A%09%09tasks%5B0%5D%2EaddAttachment%28wrapper%29%09%0A%09%7D%0A%7D
Create JSON File Attachment
 

var tasks = document.windows[0].selection.tasks if(tasks.length === 1){ var ƒnames = tasks[0].attachments.map(wrapper => wrapper.preferredFilename) var attachmentName = "Storage.json" if(ƒnames.includes(attachmentName)){ console.error("Existing Attachment:", attachmentName) } else { var obj = {"firstName":"Otto","lastName":"Automator"} var json = JSON.stringify(obj) var attachmentData = Data.fromString(json) var wrapper = FileWrapper.withContents(attachmentName, attachmentData) tasks[0].addAttachment(wrapper) } }

And here’s a companion script for reading the contents of the created JSON file attachment:

omnifocus://localhost/omnijs-run?script=var%20tasks%20%3D%20document%2Ewindows%5B0%5D%2Eselection%2Etasks%0Aif%28tasks%2Elength%20%3D%3D%3D%201%29%7B%0A%09var%20attachmentName%20%3D%20%22Storage%2Ejson%22%0A%09var%20storedData%20%3D%20null%0A%09var%20wrappers%20%3D%20tasks%5B0%5D%2Eattachments%0A%09for%20%28var%20i%20%3D%200%3B%20i%20%3C%20wrappers%2Elength%3B%20i%2B%2B%29%7B%20%0A%20%20%09%09if%20%28wrappers%5Bi%5D%2EpreferredFilename%20%3D%3D%3D%20attachmentName%29%7B%0A%20%20%09%09%09var%20storedData%20%3D%20wrappers%5Bi%5D%2Econtents%2EtoString%28%29%0A%20%20%09%09%09break%0A%20%20%09%09%7D%0A%09%7D%0A%09if%28storedData%29%7B%0A%09%09console%2Elog%28storedData%29%0A%09%09var%20json%20%3D%20JSON%2Eparse%28storedData%29%0A%09%09%2F%2F%20processing%20statements%0A%09%7D%20else%20%7B%0A%09%09console%2Eerror%28%22Missing%20Attachment%3A%22%2C%20attachmentName%29%0A%09%7D%0A%7D
Read Data of JSON Attachment
 

var tasks = document.windows[0].selection.tasks if(tasks.length === 1){ var attachmentName = "Storage.json" var storedData = null var wrappers = tasks[0].attachments for (var i = 0; i < wrappers.length; i++){ if (wrappers[i].preferredFilename === attachmentName){ var storedData = wrappers[i].contents.toString() break } } if(storedData){ console.log(storedData) var json = JSON.parse(storedData) // processing statements } else { console.error("Missing Attachment:", attachmentName) } }

Deleting Attachments

Should you wish to remove attachments from their assigned task, Omni Automation provides two mechanisms for deleting existing task attachments:

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

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

To delete specific attachments, use the removeAttachmentAtIndex(…) function combined with comparisons of the values of FileWrapper properties of each attachment.

For example, here's a script that will remove just the image attachments from the selected task:

omnifocus://localhost/omnijs-run?script=try%20%7B%0A%09var%20sel%20%3D%20document%2Ewindows%5B0%5D%2Eselection%0A%09var%20selCount%20%3D%20sel%2Etasks%2Elength%20%2B%20sel%2Eprojects%2Elength%20%0A%09if%28selCount%20%3D%3D%3D%201%29%7B%0A%09%09if%20%28sel%2Etasks%2Elength%20%3D%3D%3D%201%29%7B%0A%09%09%09var%20selectedItem%20%3D%20sel%2Etasks%5B0%5D%0A%09%09%7D%20else%20%7B%0A%09%09%09var%20selectedItem%20%3D%20sel%2Eprojects%5B0%5D%0A%09%09%7D%0A%09%7D%20else%20%7B%0A%09%09throw%20%7B%0A%09%09%09name%3A%20%22Selection%20Issue%22%2C%20%0A%09%09%09message%3A%20%22Please%20select%20a%20single%20project%20or%20task%2E%22%0A%09%09%7D%0A%09%7D%0A%09%0A%09var%20wrappers%20%3D%20selectedItem%2Eattachments%0A%09for%20%28var%20i%20%3D%20wrappers%2Elength%20%2D%201%3B%20i%20%3E%3D%200%3B%20i%2D%2D%29%7B%0A%09%09var%20wrapper%20%3D%20wrappers%5Bi%5D%0A%09%09if%20%28%0A%09%09%09wrapper%2Etype%20%3D%3D%3D%20FileWrapper%2EType%2EFile%20%26%26%0A%09%09%09%28%0A%09%09%09%09wrapper%2Efilename%2EtoUpperCase%28%29%2EendsWith%28%22%2EJPG%22%29%20%7C%7C%20%0A%09%09%09%09wrapper%2Efilename%2EtoUpperCase%28%29%2EendsWith%28%22%2EJPEG%22%29%20%7C%7C%20%0A%09%09%09%09wrapper%2Efilename%2EtoUpperCase%28%29%2EendsWith%28%22%2EPNG%22%29%20%7C%7C%0A%09%09%09%09wrapper%2Efilename%2EtoUpperCase%28%29%2EendsWith%28%22%2ETIFF%22%29%20%7C%7C%0A%09%09%09%09wrapper%2Efilename%2EtoUpperCase%28%29%2EendsWith%28%22%2ETIF%22%29%0A%09%09%09%29%0A%09%09%29%0A%09%09%7B%0A%09%09%09selectedItem%2EremoveAttachmentAtIndex%28i%29%0A%09%09%7D%0A%09%7D%0A%7D%0Acatch%28err%29%7B%0A%09new%20Alert%28err%2Ename%2C%20err%2Emessage%29%2Eshow%28%29%0A%7D
Remove Image Attachments
 

try { var sel = document.windows[0].selection var selCount = sel.tasks.length + sel.projects.length if(selCount === 1){ if (sel.tasks.length === 1){ var selectedItem = sel.tasks[0] } else { var selectedItem = sel.projects[0] } } else { throw { name: "Selection Issue", message: "Please select a single project or task." } } var wrappers = selectedItem.attachments for (var i = wrappers.length - 1; i >= 0; i--){ var wrapper = wrappers[i] if ( wrapper.type === FileWrapper.Type.File && ( wrapper.filename.toUpperCase().endsWith(".JPG") || wrapper.filename.toUpperCase().endsWith(".JPEG") || wrapper.filename.toUpperCase().endsWith(".PNG") || wrapper.filename.toUpperCase().endsWith(".TIFF") || wrapper.filename.toUpperCase().endsWith(".TIF") ) ) { selectedItem.removeAttachmentAtIndex(i) } } } catch(err){ new Alert(err.name, err.message).show() }

NOTE: the previous example uses a JavaScript for loop to iterate the list of attachments in reverse order, removing only those file attachments whose file name ends with specified file extensions.

Export All Attachments

The following plug-in will export all of the attachments of the selected tasks into a new folder placed within a chosen directory.

Export All Attachments of Selected Tasks
 

/*{ "type": "action", "targets": ["omnifocus"], "author": "Otto Automator", "identifier": "com.omni-automation.of.export-attachments-of-selected-tasks", "version": "1.0", "description": "This action will export all of the attachments of the selected tasks into a new folder placed in a user-chosen directory.", "label": "Export Attachments of Selected Tasks", "shortLabel": "Export Attachments" }*/ (() => { var action = new PlugIn.Action(function(selection, sender){ // action code var tasks = selection.tasks var fileWrappers = new Array() tasks.forEach(task =>{ if(task.attachments.length > 0){ fileWrappers = fileWrappers.concat(task.attachments) } }) if (fileWrappers.length === 0){ throw new Error("No attachments") } var filesaver = new FileSaver() var folderType = new FileType("public.folder") filesaver.types = [folderType] var fldrwrapper = FileWrapper.withChildren("Export Folder", fileWrappers) var fileSaverPromise = filesaver.show(fldrwrapper) fileSaverPromise.then(urlObj => { console.log(urlObj.string) urlObj.open() }) fileSaverPromise.catch(err => { console.log(err.message) }) }); action.validate = function(selection, sender){ // validation code return (selection.tasks.length > 0) }; return action; })();
 

Project/Task Documentation in Markdown Format

Here’s an example of using attachments to store, retrieve, and edit documentation in Markdown format for a selected task or project.

A set of Omni Automation plug-ins perform the following procedures:

TIP: qlmarkdown is a QuickLook plug-in from Phil Toland that when installed, enables the viewing of Markdown files in macOS using QuickLook. The following video demonstrates its use.

DOWNLOAD a ZIP archive of a folder containing the plug-ins. To install, place the folder containing the plug-ins in one of the plug-in folders designated in the OmniFocus Automation Configuration dialog. When installed, the plug-ins will appear as a sub-menu in the OmniFocus Automation menu.

Plug-Ins Sub-Menu

TIP: For easy access, add the plug-ins to the macOS OmniFocus toolbar.

Plug-Ins in OmniFocus Toolbar