Tags
In OmniFocus, a tag represents an association that a task has to the world around it. A tag could represent a person, place, or thing most relevant to completion of a project, or it could represent a mindset most applicable to execution of an action item.
An item can have as many tags as you find useful, and there is no specific purpose assigned to them; use tags to assign priority, group items by energy level or required equipment, or don’t use them at all.
The Tags Perspective displays a list of your tags in the sidebar, and a list of all your actions grouped by the tags they belong to in the outline. Here is a script for setting the value of the perspective property of the Window class so that the Tags perspective is displayed:
Show Tags PerspectiveTo derive an object reference to a tag by name, use the flattenedTags property of the Database class in a conditional statement that creates a new tag with a specific name if an existing tag does not exist. The result will be an object reference to the named tag:
document.windows[0].perspective = Perspective.BuiltIn.Tags
Tag Reference by Name
var tag = flattenedTags.byName("Conifer") || new Tag("Conifer")
Class Properties of the Tag Class
Here are the properties of the Tag class:
forecastTag (Tag or null r/o) • The Forecast Tag, if it is set.
In the View Options panel for the Forecast perspective, you can assign an existing tag to used as the “forecast tag.” Once this tag is assigned to an item, the tagged item will appear in the Forecast view. Using Omni Automation, you can add the forecast tag to a task or project.
Assign Forecast Tag to Task
var task = inbox.byName("Car Repair")
if(task && Tag.forecastTag){task.addTag(Tag.forecastTag)}
If no tag has been assigned to be the “forecast tag” the value of this property will be returned as: null. Also note that the forecastTag property is read-only and its value cannot be set via a script.
Instance Properties of the Tag Class
Here are the properties of an instance of the Tag class:
after (Tag.ChildInsertionLocation r/o) • Returns a location refering to position just after this tag.
allowsNextAction (Boolean) • If set and the tag is active, tasks with this tag applied cannot be the next task of a project.
availableTasks (TaskArray r/o) • Returns a sorted list of the tasks associated with this tag that are currently available. Recent changes may not be reflected until the cleanUp() function of the Database class is called on the database.
before (Tag.ChildInsertionLocation r/o) • Returns a location refering to position just before this tag.
beginning (Tag.ChildInsertionLocation r/o) • Returns a location refering to the beginning of the contained tags in this tag.
children (TagArray r/o) • Returns a sorted list of the tags contained within this tag.
ending (Tag.ChildInsertionLocation r/o) • Returns a location refering to the ending of the contained tags in this tag.
flattenedChildren (TagArray) • An alias for flattenedTags.
flattenedTags (TagArray) • Returns a flat array of all tags contained within this tag. Tags are sorted by their order in the database.
name (String) • The name of this tag.
parent (Tag or null r/o) • The parent Tag containing this tag.
projects (ProjectArray r/o) • A convenience property that returns only Projects for the root tasks associated with this Tag.
remainingTasks (TaskArray r/o) • Returns a sorted list of the tasks associated with this tag that remaing to be completed. Recent changes may not be reflected until a cleanUp() is performed on the database.
status (Tag.Status) • The current status of the tag as a whole, which is derived from allowsNextAction and active.
tags (TagArray) • Returns a sorted list of the tags contained directly within this tag, sorted by their library order.
tasks (TaskArray r/o) • Returns a sorted list of the tasks associated with this tag.
Properties of the Tag.Status class:
Active (Tag.Status r/o) • The tag is active.
Dropped (Tag.Status r/o) • The tag has been dropped.
OnHold (Tag.Status r/o) • The tag has been put on-hold.
all (Array of Tag.Status r/o) • An array of all items of this enumeration. Often used when creating action forms.
A script that gets the value of the availableTasks property of the selected tag:
Available Tasks of Selected Tag
var tgs = document.windows[0].selection.tags
tsks = (tgs.length === 1) ? tgs[0].availableTasks : null
A script that gets the value of the projects property of the selected tag:
Projects of Selected Tag
var tgs = document.windows[0].selection.tags
projects = (tgs.length === 1) ? tgs[0].projects : null
Iterating Tags
Tag Groups are tags that contain other tags. References to top-level tags are retrieved through the use of the tags property of the Database class, as in this example for processing the top-level tags. Since the OmniFocus is the implied topmost scripting element, the values of one of its property can be accessed by simply entering the property name (such as tags) in the console:
The “tags” Database Property
tags
//--> [[object Tag: Ionian],[object Tag: Phrygian],[object Tag: Lydian],[object Tag: Mixolydian],[object Tag: Aeolian],[object Tag: Locrian]]
A script example using the JavaScript forEach() function to iterate the array of top-level tags:
Iterate Top-Level Tags
tags.forEach(tag =>{
// processing statements go here
})
A script example using the JavaScript map() function to retrieve the names of the top-level tags:
Names of Top-Level TagsTo return the name of all tags in the database, iterate the value of the flattenedTags property of the Database class;
tagNames = tags.map(tag => tag.name)
console.log(tagNames)
Names of All Tags
tagNames = flattenedTags.map(tag => tag.name)
console.log(tagNames)
Since the existence of multiple tags sharing the same name is allowed in OmniFocus, here’s a script that creates an instance of the Set class to remove all duplicates from the tag name list. NOTE: this does not remove duplicate tags from the database, just duplicate tag tiltes in the generated name list:
Get All Unique Tag Names
tagNames = flattenedTags.map(tag => {return tag.name})
tagNames = Array.from(new Set(tagNames))
console.log(tagNames)
Reference Tag by Name
To provide the ability to reference a specific tag by name, the tagNamed(…) function is included with both the OmniFocus Database class and the Tag class:
tagNamed(name:String) → (Tag or null r/o) • Returns the first top-level Tag with the given name, or null.
Referencing Tags by Name
// Top-Level Not Existing
tagNamed("Zebra")
//--> null
// Top-Level Exists
tagNamed("Locrian")
//--> [object Tag: Locrian]
// 1st Tag in Database Matching Name
flattenedTags.byName("Locrian")
//--> [object Tag: Locrian]
// Tags within Tag Group
tagNamed("Locrian").children
//--> [[object Tag: Minor],[object Tag: Major]]
// Tag within Tag Group
tagNamed("Locrian").tagNamed("Major")
//--> [object Tag: Major]
// 1st Matching Tag within Tag Group
tagNamed("Locrian").flattenedTags.byName("Major")
Create New Tag
Instances of the Tag class are created using the standard JavaScript new item constructor.
new Tag(name:String, position:Tag or Tag.ChildInsertionLocation or null) → (Tag r/o) • The new Tag object titled with the provided string. Unless otherwise specified, tags are added to the end of the array of the tags within their parent container.
New Top-Level Tag at End of Tags
tag = new Tag("LAST BY DEFAULT")
New Top-Level Tag at Beginning of Tags
tag = new Tag("FIRST TAG", tags.beginning)
Using a JavaScript logical operator (||) to reference a top-level tag by name, or creating it if it does not exist. As explanation, A || B returns the value A if A can be coerced into true; otherwise, it returns B. Therefore, in the example below, if the tag “ZEBRA” exists, a reference to it will be returned, otherwise a new tag named “ZEBRA” will be created and a reference to the created tag will be the result that is stored in the variable.
Reference to Tag Either Existing or Created
tag = tagNamed("ZEBRA") || new Tag("ZEBRA")
And a variation of the previous script that searches the entire database for an existing tag, creating a new one if a match is not found:
Reference to Tag Either Existing or Created
tag = flattenedTags.byName("ZEBRA") || new Tag("ZEBRA")
Using the previous technique to add tags to either an existing named tag or a newly created tag with the target name:
Adding Tags to a Unique Top-Level Tag Group
tagGroup = tagNamed("Fall Colors") || new Tag("Fall Colors")
colors = ["Pale Blue","Harvest Gold","Autumn Gray"]
colors.forEach(color => {
tagGroup.tagNamed(color) || new Tag(color, tagGroup.beginning)
})
And here’s a related script that uses the moveTags() function of the Database class to sort the tags in the previously created tag group by name:
Sort Tags in Tag Group by Name
var tagGroup = tagNamed("Fall Colors")
if(tagGroup){
tgs = tagGroup.children
if (tgs.length > 1){
tgs.sort((a, b) => {
var x = a.name.toLowerCase();
var y = b.name.toLowerCase();
if (x < y) {return -1;}
if (x > y) {return 1;}
return 0;
})
moveTags(tgs, tagGroup)
}
}
The following script demonstrates how to generate an array of tag object references to the tags corresponding to an array of tag titles:
Object References for Tags
tagNames = ["Bow","Stern","Port","Starboard"]
tagObjects = new Array()
tagNames.forEach(title => {
tag = flattenedTags.byName(title) || new Tag(title)
tagObjects.push(tag)
})
console.log(tagObjects)
Assigned/Unassigned Tags
“Assigned” tags are those that have a positive value for the remainingTasks property.
Assigned Tags
assignedTags = flattenedTags.filter(tag => {
if(tag.remainingTasks.length > 0){
return tag
}
})
Unassigned Tags
unassignedTags = flattenedTags.filter(tag => {
if(tag.remainingTasks.length === 0){
return tag
}
})
Creating Tag Groups
A Tag Group is a tag that contains other tags. Omni Automation is useful for quickly creating one or more tag groups. In the example below, tag groups are created for regions of the United States, and the appropriate states are added to their respective region tag group.
Creating Tag Groups
regions = ["Northeast", "Southeast", "Midwest", "Southwest", "West"]
stateGroups = [["Maine", "Massachusetts", "Rhode Island", "Connecticut", "New Hampshire", "Vermont", "New York", "Pennsylvania", "New Jersey", "Delaware", "Maryland"],["West Virginia", "Virginia", "Kentucky", "Tennessee", "North Carolina", "South Carolina", "Georgia", "Alabama", "Mississippi", "Arkansas", "Louisiana", "Florida"],["Ohio", "Indiana", "Michigan", "Illinois", "Missouri", "Wisconsin", "Minnesota", "Iowa", "Kansas", "Nebraska", "South Dakota", "North Dakota"],["Texas", "Oklahoma", "New Mexico", "Arizona"],["Colorado", "Wyoming", "Montana", "Idaho", "Washington", "Oregon", "Utah", "Nevada", "California", "Alaska", "Hawaii"]]
regions.forEach((region, index)=>{
tagGroup = flattenedTags.byName(region) || new Tag(region)
states = stateGroups[index]
states.forEach((state)=>{
tagGroup.flattenedTags.byName(state) || new Tag(state, tagGroup.ending)
})
})
Here's a variation of the previous script that sorts the tags within groups:
Creating Sorted Tag Groups
regions = ["Northeast", "Southeast", "Midwest", "Southwest", "West"]
stateGroups = [["Maine", "Massachusetts", "Rhode Island", "Connecticut", "New Hampshire", "Vermont", "New York", "Pennsylvania", "New Jersey", "Delaware", "Maryland"],["West Virginia", "Virginia", "Kentucky", "Tennessee", "North Carolina", "South Carolina", "Georgia", "Alabama", "Mississippi", "Arkansas", "Louisiana", "Florida"],["Ohio", "Indiana", "Michigan", "Illinois", "Missouri", "Wisconsin", "Minnesota", "Iowa", "Kansas", "Nebraska", "South Dakota", "North Dakota"],["Texas", "Oklahoma", "New Mexico", "Arizona"],["Colorado", "Wyoming", "Montana", "Idaho", "Washington", "Oregon", "Utah", "Nevada", "California", "Alaska", "Hawaii"]]
regions.forEach((region, index)=>{
tagGroup = flattenedTags.byName(region) || new Tag(region)
states = stateGroups[index]
states.forEach((state)=>{
tagGroup.flattenedTags.byName(state) || new Tag(state, tagGroup.ending)
})
tgs = tagGroup.children
if (tgs.length > 1){
tgs.sort((a, b) => {
var x = a.name.toLowerCase();
var y = b.name.toLowerCase();
if (x < y) {return -1;}
if (x > y) {return 1;}
return 0;
})
moveTags(tgs, tagGroup)
}
})
Here’s an example script that creates a unique tag set, and warns the user if the main tag already exists:
Create Unique Tag Set
targetTag = flattenedTags.byName("Seasons")
if(targetTag){
title = "Item Exists"
msg = "A tag titled “Seasons” already exists."
tagID = targetTag.id.primaryKey
new Alert(title, msg).show(result => {
URL.fromString("omnifocus:///tag/" + tagID).open()
})
console.error(msg)
} else {
SeasonsTag = new Tag("Seasons")
tagID = SeasonsTag.id.primaryKey
tagTitles = ["Spring", "Summer", "Winter", "Fall"]
tagTitles.forEach(title => {
new Tag(title, SeasonsTag)
})
URL.fromString("omnifocus:///tag/" + tagID).open()
}
And a related script for switching to the Tags perspective and selecting the children of the tag set:
Select Tag Set Tags
targetTag = flattenedTags.byName("Seasons")
if(targetTag){
tagIDs = targetTag.children.map(tag => tag.id.primaryKey)
tagIDsString = tagIDs.join(",")
URL.fromString("omnifocus:///tag/" + tagIDsString).open()
}
Example Plug-Ins
This action plug-in will “adopt” the tags contained in tag groups that are assigned to the selected task or project:
Adopt Tags from Tag Groups
/*{
"type": "action",
"targets": ["omnifocus"],
"author": "Otto Automator",
"identifier": "com.omni-automation.of.adopt-child-tags",
"version": "1.2",
"description": "This action will assign the child tags of the tag groups of the selected task|project, to the selected task|project.",
"label": "Adopt Tags from Tag Groups",
"shortLabel": "Adopt Child Tags",
"paletteLabel": "Adopt Child Tags",
"image": "tag.fill"
}*/
(() => {
const action = new PlugIn.Action(async function(selection, sender){
if(selection.tasks[0]){
var task = selection.tasks[0]
} else {
var task = selection.projects[0].task
}
task.tags.forEach((tag)=>{
task.addTags(tag.children)
})
});
action.validate = function(selection, sender){
return (
selection.tasks.length === 1 ||
selection.projects.length === 1
)
};
return action;
})();
|
The following action plug-in will select all tags whose name matches the title entered in the form. If the tag is not found, an option to create and reveal it will be offered:
Tag Check
/*{
"type": "action",
"targets": ["omnifocus"],
"author": "Otto Automator",
"identifier": "com.omni-automation.of.tag-check",
"description": "This action will select all of the tags sharing the provided title.",
"version": "1.6",
"label": "Tag Check",
"shortLabel": "Tag Check",
"paletteLabel": "Tag Check",
"image":"tag"
}*/
(() => {
const action = new PlugIn.Action(async function(selection, sender){
try {
inputForm = new Form()
textField = new Form.Field.String("tagName", null, null)
inputForm.addField(textField)
formPrompt = "Enter the title of the tag to check:"
buttonTitle = "Continue"
inputForm.validate = function(formObject){
textValue = formObject.values['tagName']
return (!textValue) ? false:true
}
formObject = await inputForm.show(formPrompt, buttonTitle)
targetTagName = formObject.values['tagName']
console.log("targetTagName", targetTagName)
foundTag = flattenedTags.find(tag => {return tag.name === targetTagName})
if(foundTag !== undefined){
tagID = foundTag.id.primaryKey
URL.fromString("omnifocus:///tag/" + tagID).open()
} else {
alert = new Alert("Tag Not Found", "The tag “" + targetTagName + "” was not found.\n\nDo you want to create it?")
alert.addOption("Create")
alert.addOption("Stop")
alert.show(buttonIndex => {
console.log("buttonIndex", buttonIndex)
if(buttonIndex === 0){
newTag = new Tag(targetTagName)
tagID = newTag.id.primaryKey
URL.fromString("omnifocus:///tag/" + tagID).open()
}
})
}
}
catch(err){
new Alert(err.name, err.message).show()
}
});
return action;
})();
Deleting Tags
To delete one or more tags from the database, the deleteObject() function of the Database class is used, as in this script that deletes all tags from the database:
Deleting All Tags
tags.forEach(tag => {deleteObject(tag)})
And here’s a script that removes duplicate tags from the entire tag heirarchy:
Remove Duplicate Tags
var tagNames = new Array()
tags.apply(function(tag){
if (tagNames.includes(tag.name)){
deleteObject(tag)
} else {
tagNames.push(tag.name)
}
})
NOTE: The previous script isn’t designed to examine any task relationships of the tags, it simply removes duplicate tags based upon the order they are encountered.
The following plug-in will delete all tags that have not been associated with items. Parent tags of tags who have been assigned will be retained.
Delete Unassigned Tags
/*{
"type": "action",
"targets": ["omnifocus"],
"author": "Otto Automator",
"identifier": "com.omni-automation.of-remove-unused-tags",
"version": "2.1",
"description": "This action will delete all tags that have not been associated with items. NOTE: Because of possible tag hierarchies, there may be a difference between the number of unassigned tags and the number of deleted tags.",
"label": "Delete Unassigned Tags",
"shortLabel": "Delete Unassigned Tags",
"paletteLabel": "Delete Unassigned Tags",
"image":"tag.slash.fill"
}*/
(() => {
const action = new PlugIn.Action(async function(selection, sender){
try {
unassignedTags = flattenedTags.filter(tag => {
if(tag.remainingTasks.length === 0){
return tag
}
})
unassignedCount = unassignedTags.length
if (unassignedCount === 0){
throw {
name: "No Matches",
message: "There are no unassigned tags."
}
}
isAre = (unassignedCount === 1) ? "is":"are"
tagTags = (unassignedCount === 1) ? "tag":"tags"
thisThose = (unassignedCount === 1) ? "this":"those"
alertTitle = "Confirmation Required"
alertMessage = `There ${isAre} ${unassignedCount} unassigned ${tagTags}.\nDelete or log ${thisThose} ${tagTags}?`
alert = new Alert(alertTitle, alertMessage)
alert.addOption("Delete Tags")
alert.addOption("Log Tags")
alert.addOption("Stop")
buttonIndex = await alert.show()
if (buttonIndex === 0){
var deletionCount = 0
var deletedTagTitles = []
do {
tagsToDelete = flattenedTags.filter(tag => {
if(tag.flattenedTags.length === 0 && tag.remainingTasks.length === 0){
return tag
}
})
if (tagsToDelete.length > 0){
deletionCount = deletionCount + tagsToDelete.length
tagTitles = tagsToDelete.map(tag => tag.name)
deletedTagTitles = deletedTagTitles.concat(tagTitles)
tagsToDelete.forEach(tag => deleteObject(tag))
}
}
while (tagsToDelete.length > 0);
responseMessage = `${deletionCount} deleted tags.\n\nThe names of the deleted tags have been logged to the Automation Console.`
console.clear()
console.log(deletedTagTitles.join("\n"))
new Alert("Completed", responseMessage).show()
} else if (buttonIndex === 1){
unusedTags = flattenedTags.filter(tag => {
if(tag.remainingTasks.length === 0){
return tag
}
})
console.clear()
console.log("Tag Count: ", flattenedTags.length)
console.log("Unassigned Tag Count: ", unusedTags.length)
tagNames = unusedTags.map(tag => tag.name)
console.log("Unassigned Tags:", "\n" + tagNames.join("\n"))
}
}
catch(err){
new Alert(err.name, err.message).show()
}
});
action.validate = function(selection, sender){
return (tags.length > 0)
};
return action;
})();
Clearing Tags from Tasks
To remove an assigned tag from its host task, use the removeTag(…) or removeTags(…) functions of the Task class.
removeTag(tag:Tag) • Removes a Tag from this task. If the tag is not associated with this task, no change is made.
removeTags(tags:TagArray) • Removes multiple Tags from this task. If a tag is not associated with this task, no change is made.
As an alternative, you can use the clearTags() function of both the Project and Task classes to remove applied tags:
clearTags() • Removes multiple Tags from this project. If a tag is not associated with this project, no change is made.
clearTags() • Removes multiple Tags from this task. If a tag is not associated with this task, no change is made.
Clear Tags from Selected Tasks
/*{
"type": "action",
"targets": ["omnifocus"],
"author": "Otto Automator",
"identifier": "com.omni-automation.of.clear-task-tags",
"version": "1.6",
"description": "This action will clear all tags assigned to the selected tasks.",
"label": "Clear Tags from Tasks",
"shortLabel": "Clear Task Tags",
"paletteLabel": "Clear Task Tags",
"image": "tag.slash"
}*/
(() => {
const action = new PlugIn.Action(async function(selection, sender){
checkboxSwitch = new Form.Field.Checkbox(
"shouldClearFromChildren",
"Also clear tags from tasks’s children",
false
)
inputForm = new Form()
inputForm.addField(checkboxSwitch)
formObject = await inputForm.show("Clear Tasks’s Tags:","Continue")
shouldClearFromChildren = formObject.values['shouldClearFromChildren']
if (shouldClearFromChildren){
selection.tasks.forEach(task => {
task.clearTags()
task.flattenedTasks.forEach(child => child.clearTags())
})
} else {
selection.tasks.forEach(task => task.clearTags())
}
});
action.validate = function(selection, sender){
return (selection.tasks.length > 0)
};
return action;
})();
For example, here is a plug-in that uses the clearTags() function to remove assigned tags from the selecte projects:
Clear Tags from Selected Projects
/*{
"type": "action",
"targets": ["omnifocus"],
"author": "Otto Automator",
"identifier": "com.omni-automation.of.clear-project-tags",
"version": "1.6",
"description": "This action will clear all tags assigned to the selected projects.",
"label": "Clear Tags from Projects",
"shortLabel": "Clear Project Tags",
"paletteLabel": "Clear Project Tags",
"image": "tag.slash"
}*/
(() => {
const action = new PlugIn.Action(async function(selection, sender){
checkboxSwitch = new Form.Field.Checkbox(
"shouldClearFromTasks",
"Also clear tags from project’s tasks",
false
)
inputForm = new Form()
inputForm.addField(checkboxSwitch)
formObject = await inputForm.show("Clear Project’s Tags:","Continue")
shouldClearFromTasks = formObject.values['shouldClearFromTasks']
if (shouldClearFromTasks){
selection.projects.forEach(project => {
project.clearTags()
project.flattenedTasks.forEach(task => task.clearTags())
})
} else {
selection.projects.forEach(project => project.clearTags())
}
});
action.validate = function(selection, sender){
return (selection.projects.length > 0)
};
return action;
})();
Importing Tags from Text File
An important aspect of the Omni Automation support in OmniFocus is in its ability to import data from sources outside of the application.
In the following example action plug-in, the user is prompted to choose a text file whose paragraphs comprise a list of tags to be added to the database. The chosen file is parsed, and each paragraph of the imported text is used to create a new top-level tag if an existing matching one does not exist.
Import Tags from File
/*{
"type": "action",
"targets": ["omnifocus"],
"author": "Otto Automator",
"identifier": "com.omni-automation.import-text-to-tags",
"version": "1.5",
"description": "This action will convert the paragraphs of the chosen text file into OmniFocus tags.",
"label": "Import Text to Tags",
"paletteLabel": "Import Tags",
"image": "square.and.arrow.down.on.square"
}*/
(() => {
const action = new PlugIn.Action(async function(selection, sender){
// get the names of existing tags
tagTitles = flattenedTags.map(tag => {return tag.name})
// show the file picker
picker = new FilePicker()
picker.folders = false
picker.multiple = false
picker.types = [FileType.plainText]
urlsArray = await picker.show()
// process the results of the file picker
aURL = urlsArray[0]
aURL.fetch(data => {
textArray = data.toString().split('\n')
newTagObjs = new Array()
textArray.forEach(item =>{
if(item != "" && !tagTitles.includes(item)){
tag = new Tag(item)
tagTitles.push(item)
newTagObjs.push(tag)
}
})
document.windows[0].perspective = Perspective.BuiltIn.Tags
document.windows[0].selectObjects(newTagObjs)
})
});
action.validate = function(selection, sender){
return true
};
return action;
})();
Copy Tags Between Selected Tasks
The following Omni Automaton plug-in uses the addTags(…) function of the Task class to copy the tags of a chosen task to the other tasks selected in the OmniFocus window.
To use, select both the tasks to receive the tags, and the task containing the set of tags to be copied, and run the action.
Copy Tags Between Selected Tasks
/*{
"type": "action",
"targets": ["omnifocus"],
"author": "Otto Automator",
"identifier": "com.omni-automation.of.copy-tags-between-tasks",
"version": "2.0",
"description": "This plug-in will copy the tags of the chosen task to the other selected tasks.",
"label": "Copy Tags between Selected Tasks",
"shortLabel": "Copy Tags",
"paletteLabel": "Copy Tags",
"image": "tag.square"
}*/
(() => {
const action = new PlugIn.Action(async function(selection, sender){
tasks = selection.tasks
// sort the task objects by name
tasks.sort((a, b) => {
var x = a.name.toLowerCase();
var y = b.name.toLowerCase();
if (x < y) {return -1;}
if (x > y) {return 1;}
return 0;
})
// generate a list of task names
menuItems = tasks.map(task => {return task.name})
// generate a list of matching indexes
menuIndexes = menuItems.map((item, index) => index)
menuLabel = "Selected Tasks"
multiOptionMenu = new Form.Field.MultipleOptions(
"menuKey",
menuLabel,
menuIndexes,
menuItems,
[]
)
checkboxSwitch = new Form.Field.Checkbox(
"shouldReplaceTags",
"Replace existing tags",
false
)
inputForm = new Form()
inputForm.addField(multiOptionMenu)
inputForm.addField(checkboxSwitch)
inputForm.validate = function(formObject){
var indexes = formObject.values["menuKey"]
// ensure at least one item is selected
return (indexes.length === 1)?true:false
}
formPrompt = "Select task to copy tags from:"
buttonTitle = "Continue"
formObject = await inputForm.show(formPrompt,buttonTitle)
indexes = formObject.values["menuKey"]
shouldReplaceTags = formObject.values["shouldReplaceTags"]
sourceTask = tasks[indexes[0]]
tagsToCopy = sourceTask.tags
tasks.forEach(task => {
if (shouldReplaceTags){task.clearTags()}
task.addTags(tagsToCopy)
})
});
action.validate = function(selection, sender){
// validation code
return (selection.tasks.length > 1)
};
return action;
})();
Assigning Tags to Selected Tasks
This action will display a list of all tags (sorted), any or all of which may selected to be assigned to the selected tasks. The plug-in is designed to work only on iPadOS and iOS as their form interfaces scroll and allow for the display of dozens of tags.
Assign Tags to Selected Tasks (iPadOS/iOS)
/*{
"type": "action",
"targets": ["omnifocus"],
"author": "Otto Automator",
"identifier": "com.omni-automation.of.assign-tags-ipados-ios",
"version": "1.7",
"description": "(iPadOS/iOS) This action will display a list of all tags (sorted), any or all of which may selected to be assigned to the selected tasks.",
"label": "Assign Tags",
"shortLabel": "Assign Tags",
"paletteLabel": "Assign Tags",
"image": "tag.circle.fill"
}*/
(() => {
const action = new PlugIn.Action(async function(selection, sender){
allTags = flattenedTags
if (allTags.length > 1){
allTags.sort((a, b) => {
var x = a.name.toLowerCase();
var y = b.name.toLowerCase();
if (x < y) {return -1;}
if (x > y) {return 1;}
return 0;
})
}
menuItems = allTags.map(tag => {return tag.name})
menuIndexes = menuItems.map((item, index) => index)
menuLabel = "Assign Tags"
multiOptionMenu = new Form.Field.MultipleOptions(
"menuKey",
menuLabel,
menuIndexes,
menuItems,
[]
)
inputForm = new Form()
inputForm.addField(multiOptionMenu)
inputForm.validate = function(formObject){
var indexes = formObject.values["menuKey"]
return (indexes.length > 0)?true:false
}
formPrompt = "Select one or more tags to apply to selected tasks:"
buttonTitle = "Continue"
formObject = await inputForm.show(formPrompt,buttonTitle)
indexes = formObject.values["menuKey"]
tags = new Array()
indexes.forEach(index => {
tags.push(allTags[index])
})
selection.tasks.forEach(function(task){
task.addTags(tags)
})
});
action.validate = function(selection, sender){
return (
Device.current.iOS &&
selection.tasks.length > 0 &&
tags.length > 0
)
};
return action;
})();
Toggle Status of Selected Tag
This plug-in will toggle the status of the selected tag between active and on hold. If the selected tag is a tag group, the status of all of the group tags will changed accordingly.
NOTE: this plug-in uses the sender parameter of the action handlers to toggle the text of the plug-in menu item.
Toggle Status of Selected Tag
/*{
"type": "action",
"targets": ["omnifocus"],
"author": "Otto Automator",
"identifier": "com.omni-automation.of.toggle-tag-status",
"version": "1.1",
"description": "This plug-in will toggle the status of the selected tag between active and on hold. If the selected tag is a tag group, the status of all of the group tags will changed accordingly.",
"label": "Toggle Tag Status",
"shortLabel": "Toggle Tag",
"paletteLabel": "Toggle Tag",
"image": "tag"
}*/
(() => {
const action = new PlugIn.Action(function(selection, sender){
tag = selection.tags[0]
if(tag.status === Tag.Status.Active){
tag.status = Tag.Status.OnHold
tag.flattenedTags.forEach(tg => {
tg.status = Tag.Status.OnHold
})
} else {
tag.status = Tag.Status.Active
tag.flattenedTags.forEach(tg => {
tg.status = Tag.Status.Active
})
}
});
action.validate = function(selection, sender){
// sender can be: MenuItem, ToolbarItem, or undefined (remote script)
if (selection.tags.length === 1){
if (sender instanceof MenuItem){
menuText = (selection.tags[0].status === Tag.Status.Active)?'Set Status to On Hold':'Set Status to Active'
sender.label = menuText
}
return true
}
if (sender instanceof MenuItem){sender.label = 'Toggle Tag Status'}
return false
};
return action;
})();
Tag-Based Kanban Board
A plug-in for creating and maintaining a tag-based Kanban Board in OmniFocus. Based upon a creative concept and project by Serena (www.sleepyowl.ink) where the Kanban Board is OmniFocus tag-based and displayed as stack of horizontal elements (categories).
TAP|CLICK for full description and download of plug-in.
Show Tasks Tagged with Indicated Tags
Here's a script that simulates a “focus” for tasks by tagging tasks that have been already tagged with all of the indicated tags, and then showing the “focus” tag:. You can delete the “focus” tag when done.
“Sudo-Focus” Tasks Tagged with Specified Tags
tag1 = flattenedTags.byName("Weekend") || new Tag("Weekend")
tag2 = flattenedTags.byName("Hiking") || new Tag("Hiking")
focusTag = flattenedTags.byName("Focus") || new Tag("Focus")
matched = flattenedTasks.filter(task => {
tgs = task.tags
return tgs.includes(tag1) && tgs.includes(tag2)
})
if(matched.length > 0){
matched.forEach(task => {task.addTag(focusTag)})
id = focusTag.id.primaryKey
URL.fromString("omnifocus:///tag/" + id).open()
}
Sorting a Tag’s Tasks
Omni Automation provides a special set of positional indicators for working with the tasks belonging to a tag group.
Tag.TaskInsertionLocation: A location specifying the order of a Task within a Tag. These cannot be instantiated directly, rather they are returned from properties like Tag.beforeTask() or Tag.endingOfTasks. (For a complete list of locations, open the navigation sidebar and use its filter to search for Tag.TaskInsertionLocation.)
For an instance of the Tag class:
Instance Function
beforeTask(task: Task or null) → (Tag.TaskInsertionLocation) • Returns a location indicating the position before an existing task in the Tag’s tasks. If no peer Task is specified, or the the specified task is not in the tag’s tasks, this is equivalent to beginningOfTasks.
Instance Properties
beginningOfTasks (Tag.TaskInsertionLocation read-only) • Returns a location indicating the position before all of the Tags tasks.
Here's an example script that sorts a tag’s tasks by due date:
Sorting a Tag’s Tasks
tag = flattenedTags.byName("Green") || new Tag("Green")
tagTasks = tag.availableTasks
if (tagTasks.length > 1){
tagTasks.sort((a, b) => {
var x = a.dueDate
var y = b.dueDate
if (x < y) {return -1;}
if (x > y) {return 1;}
return 0;
})
tag.moveTasks(tagTasks, tag.beginningOfTasks)
}