Plug-In: Show Latest Active Object
Shows the most recently added or modified active/available OmniFocus folder, project, task, or tag.
- For Folders, only those with Active status are considered.
- For Projects, only those with Active status are considered.
- For Tasks, only those whose status is Available, Due Soon, or Next, are considered.
- For Tags, only those with Active status are considered.
Here's the interface presented when the plug-in is launched:

Return to: OmniFocus Plug-In Collection
Show Latest Active Object
/*{
"type": "action",
"targets": ["omnifocus"],
"author": "Otto Automator",
"identifier": "com.omni-automation.of.show-latest-database-object",
"version": "1.0",
"description": "Shows the most recently added or modified active OmniFocus folder, project, task, or tag.",
"label": "Show the Latest…",
"shortLabel": "Show Latest…",
"paletteLabel": "Show Latest…",
"image": "1.magnifyingglass"
}*/
(() => {
var preferences = new Preferences() // NO ID = PLUG-IN ID
function createUtterance(textToSpeak){
langCode = Speech.Voice.currentLanguageCode
voiceObj = Speech.Voice.withLanguage(langCode)
utterance = new Speech.Utterance(textToSpeak)
utterance.voice = voiceObj
utterance.rate = Speech.Utterance.defaultSpeechRate
return utterance
}
const action = new PlugIn.Action(async function(selection, sender){
try {
synthesizer = new Speech.Synthesizer()
objectTypeIdx = preferences.readNumber("objectTypeIdx")
if(!objectTypeIdx){
objectTypeIdx = 0
}
console.log("objectTypeIdx", objectTypeIdx)
objectType = ["Folder","Project","Task","Tag"]
objectTypeMenu = new Form.Field.Option(
"objectTypeIdx",
"Object Type",
[0,1,2,3],
objectType,
objectTypeIdx
)
addedOrModifiedIdx = preferences.readNumber("addedOrModifiedIdx")
if(!addedOrModifiedIdx){
addedOrModifiedIdx = 0
}
console.log("addedOrModifiedIdx", addedOrModifiedIdx)
searchParams = ["Added","Modified"]
searchParamMenu = new Form.Field.Option(
"addedOrModifiedIdx",
"Date Parameter",
[0,1],
searchParams,
addedOrModifiedIdx
)
shouldSpeakAlert = preferences.readBoolean("shouldSpeakAlert")
if(shouldSpeakAlert === null || shouldSpeakAlert === undefined){
shouldSpeakAlert = true
}
console.log("shouldSpeakAlert", shouldSpeakAlert)
spokenAlertCheckbox = new Form.Field.Checkbox(
"shouldSpeakAlert",
"Use spoken result alert",
shouldSpeakAlert
)
inputForm = new Form()
inputForm.addField(objectTypeMenu)
inputForm.addField(searchParamMenu)
inputForm.addField(spokenAlertCheckbox)
formPrompt = "Search parameters:"
buttonTitle = "Continue"
formObject = await inputForm.show(formPrompt, buttonTitle)
objectTypeIdx = formObject.values["objectTypeIdx"]
preferences.write("objectTypeIdx", objectTypeIdx)
objectTypeTerm = objectType[objectTypeIdx]
console.log("Object Type:", objectType[objectTypeIdx])
addedOrModifiedIdx = formObject.values["addedOrModifiedIdx"]
preferences.write("addedOrModifiedIdx", addedOrModifiedIdx)
addedOrModifiedTerm = searchParams[addedOrModifiedIdx]
console.log("Date Parameter:", searchParams[addedOrModifiedIdx])
shouldSpeakAlert = formObject.values["shouldSpeakAlert"]
preferences.write("shouldSpeakAlert", shouldSpeakAlert)
console.log("shouldSpeakAlert:", shouldSpeakAlert)
switch (objectTypeIdx) {
case 0:
// FILTER FOLDERS
var filteredObjects = flattenedFolders.filter(folder => {
status = folder.status
return (status === Folder.Status.Active)
})
if(filteredObjects.length === 0){
throw {
name: "Missing Object",
message: "There are no active folders in the database."
}
}
// SORT FOLDERS
if(addedOrModifiedIdx === 0){
filteredObjects.sort((a, b) => b.added - a.added)
} else {
filteredObjects.sort((a, b) => b.modified - a.modified)
}
latestObject = filteredObjects[0]
latestObject.url.open()
break;
case 1:
// FILTER PROJECTS
filteredObjects = flattenedProjects.filter(project => {
status = project.status
return (status === Project.Status.Active)
})
if(filteredObjects.length === 0){
throw {
name: "Missing Object",
message: "There are no active projects in the database."
}
}
// SORT PROJECTS
if(addedOrModifiedIdx === 0){
filteredObjects.sort((a, b) => b.task.added - a.task.added)
} else {
filteredObjects.sort((a, b) => b.task.modified - a.task.modified)
}
latestObject = filteredObjects[0]
latestObject.url.open()
break;
case 2:
// FILTER TASKS
filteredObjects = flattenedTasks.filter(task => {
// AVOID ROOT TASKS OF PROJECTS
if(task.containingProject !== null){
if(task.id.primaryKey === task.containingProject.id.primaryKey){
return false
}
}
status = task.taskStatus
return (
status === Task.Status.Available ||
status === Task.Status.DueSoon ||
status === Task.Status.Next
)
})
if(filteredObjects.length === 0){
throw {
name: "Missing Object",
message: "There are no available tasks in the database."
}
}
// SORT TASKS
if(addedOrModifiedIdx === 0){
filteredObjects.sort((a, b) => b.added - a.added)
} else {
filteredObjects.sort((a, b) => b.modified - a.modified)
}
latestObject = filteredObjects[0]
latestObject.url.open()
break;
default:
// FILTER TAGS
var filteredObjects = flattenedTags.filter(tag => {
status = tag.status
return (status === Tag.Status.Active)
})
if(filteredObjects.length === 0){
throw {
name: "Missing Object",
message: "There are no active tags in the database."
}
}
// SORT TAGS
if(addedOrModifiedIdx === 0){
filteredObjects.sort((a, b) => b.added - a.added)
} else {
filteredObjects.sort((a, b) => b.modified - a.modified)
}
latestObject = filteredObjects[0]
latestObject.url.open()
break;
}
// ALERT USER
if(shouldSpeakAlert){
utterance = createUtterance(`The most recently ${addedOrModifiedTerm} ${objectTypeTerm}.`)
synthesizer.speakUtterance(utterance)
}
}
catch(err){
if(!err.causedByUserCancelling){
utterance = createUtterance(err.message)
synthesizer.speakUtterance(utterance)
console.error(err.name, err.message)
new Alert(err.name, err.message).show()
}
}
});
action.validate = function(selection, sender){
// selection options: tasks, projects, folders, tags, databaseObjects, allObjects
return true
};
return action;
})();