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:
attachments (Array of FileWrapper or null) • An array of FileWrapper objects representing the attachments associated with the task.
Task Attachment Functions
The functions of the Task class dealing with the management of attachments.
addAttachment(attachment:FileWrapper) • Adds attachment as an attachment to the task.
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",
"paletteLabel": "Add Attachments",
"image": "paperclip.badge.ellipsis"
}*/
(() => {
const action = new PlugIn.Action(async function(selection, sender){
task = selection.tasks[0]
picker = new FilePicker()
picker.folders = false
picker.multiple = true
picker.types = null // any file type
urlsArray = await picker.show()
urlsArray.forEach(url =>{
urlStr = url.string
fileName = urlStr.substring(urlStr.lastIndexOf('/') + 1)
url.fetch(function(data){
wrapper = FileWrapper.withContents(decodeURIComponent(fileName), data)
task.addAttachment(wrapper)
})
})
});
action.validate = function(selection, sender){
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)Create JSON File Attachment
tasks = document.windows[0].selection.tasks
if(tasks.length === 1){
ƒnames = tasks[0].attachments.map(wrapper => wrapper.preferredFilename)
attachmentName = "Storage.json"
if(ƒnames.includes(attachmentName)){
console.error("Existing Attachment:", attachmentName)
} else {
obj = {"firstName":"Otto","lastName":"Automator"}
json = JSON.stringify(obj)
attachmentData = Data.fromString(json)
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:
Read Data of JSON Attachment
tasks = document.windows[0].selection.tasks
if(tasks.length === 1){
attachmentName = "Storage.json"
storedData = null
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)
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:
Deleting All Attachments of Selected Tasks
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.
removeAttachmentAtIndex(index:Number) • Removes the attachment at index from this task’s attachments array.
For example, here's a script that will remove just the image attachments from the selected task:
Remove Image Attachments
try {
sel = document.windows[0].selection
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."
}
}
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.1",
"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",
"paletteLabel": "Export Attachments",
"image": "square.and.arrow.up.circle.fill"
}*/
(() => {
const action = new PlugIn.Action(async function(selection, sender){
tasks = selection.tasks
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")}
filesaver = new FileSaver()
folderType = new FileType("public.folder")
filesaver.types = [folderType]
fldrwrapper = FileWrapper.withChildren("Export Folder", fileWrappers)
urlObj = await filesaver.show(fldrwrapper)
console.log(urlObj.string)
urlObj.open()
});
action.validate = function(selection, sender){
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:
- Create Abstract Attachment • This plug-in adds a new Markdown file as an attachment to the selected project or task. By default, the Markdown file will be named “Abstract.md” and will contain this element: # NAME-OF-PROJECT/TASK
- Read Abstract in BBEdit • This plug-in reads the contents of the “Abstract.md” attachment and opens a new Markdown document in the BBEdit application containing the extracted Markdown content.
- Read Abstract in Drafts • This plug-in reads the contents of the “Abstract.md” attachment and opens a new document in the Drafts application containing the extracted Markdown content.
- Write Abstract Attachment • This plug-in writes the text content of the clipboard to the “Abstract.md” attachment file.
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.
TIP: For easy access, add the plug-ins to the macOS OmniFocus toolbar.