OO-PG0004
Plug-In: Outline to Project
This plug-in creates a new OmniFocus project using the contents of the outline document.
The plug-in works by converting the outline data into a JSON record and then sending the created record to OmniFocus to be processed as input by a specialized function that creates a project with a hierarchical task structure matching the source outline. Row topic columns and their corresponding notes are transfered. Any user-added columns are ignored.
Return to: OmniOutliner Plug-In Collection
Outline to Project
/*{
"type": "action",
"targets": ["omnioutliner"],
"author": "Otto Automator",
"identifier": "com.omni-automation.oo.outline-to-project",
"version": "1.0",
"description": "Create a new OmniFocus project containing the contents of the outline.",
"label": "Outline to OmniFocus Project",
"shortLabel": "Outline to Project",
"paletteLabel": "Outline to Project",
"image":"archivebox.fill"
}*/
(() => {
const action = new PlugIn.Action(async function(selection, sender){
try {
function createUtterance(textToSpeak){
voiceObj = Speech.Voice.allVoices.find(
voice => voice.name.startsWith("Alex")
)
voiceRate = 0.4
utterance = new Speech.Utterance(textToSpeak)
utterance.voice = voiceObj
utterance.rate = voiceRate
return utterance
}
var synthesizer = new Speech.Synthesizer()
try {document.name} catch(err){
throw {name:"Missing Resource", message:"No outline document is open."}
}
documentName = document.name.replace(/\.[^/.]+$/, "")
console.log("DOCUMENT NAME:", documentName)
// GET DATA AS JSON RECORD ARRAY
itemData = rootItem.descendants.map(item => {
itemObj = new Object()
itemObj.level = item.level
itemObj.topic = item.topic
itemObj.note = item.note
return itemObj
})
console.log("ITEM DATA:", JSON.stringify(itemData))
// CONSTRUCT AND DISPLAY FORM
textInputField = new Form.Field.String(
"projectTitle",
"Project Title",
documentName
)
menuItems = ["Parallel", "Sequential", "Single Actions"]
menuIndexes = [0,1,2]
menuElement = new Form.Field.Option(
"projectTypeIndex",
"Project Type",
menuIndexes,
menuItems,
0
)
completedByChildrenCheckbox = new Form.Field.Checkbox(
"completedByActions",
"Complete with last action",
null
)
inputForm = new Form()
inputForm.addField(textInputField)
inputForm.addField(menuElement)
inputForm.addField(completedByChildrenCheckbox)
inputForm.validate = function(formObject){
projectTitle = formObject.values['projectTitle']
return ((!projectTitle)?false:true)
}
formPrompt = "Title and settings for new project:"
buttonTitle = "Continue"
utterance = createUtterance(formPrompt)
synthesizer.speakUtterance(utterance)
formObject = await inputForm.show(formPrompt, buttonTitle)
projectTitle = formObject.values['projectTitle']
projectTypeIndex = formObject.values['projectTypeIndex']
completedByActions = formObject.values['completedByActions']
// THE OPTIONAL ARGUMENT: string, number, date, array, or object
var targetFunctionArgument = {
"items": itemData,
"title": projectTitle,
"projectTypeIndex": projectTypeIndex,
"completedByActions": completedByActions
}
var targetAppName = "omnifocus"
// THE FUNCTION TO BE EXECUTED ON THE TARGET APP
function targetAppFunction(targetFunctionArgument){
try {
dataItems = targetFunctionArgument["items"]
projectTitle = targetFunctionArgument["title"]
projectTypeIndex = targetFunctionArgument["projectTypeIndex"]
completedByActions = targetFunctionArgument["completedByActions"]
var project = new Project(projectTitle)
project.status = Project.Status.Active
if (projectTypeIndex === 1){
project.sequential = true
} else if (projectTypeIndex === 2){
project.containsSingletonActions = true
}
project.completedByChildren = completedByActions
// link-back to project
projectID = project.id.primaryKey
var linkURL = "omnifocus:///task/" + projectID
console.log("PROJECT LINK:", linkURL)
var previousItem = null
var previousLevel = null
var insertionLocation = null
dataItems.forEach((dataItem, index, dataArray) => {
itemLevel = dataItem.level
itemTopic = dataItem.topic
itemNote = dataItem.note
if(index === 0){ // first top-level item
insertionLocation = project.ending
} else if (itemLevel === 1){ // top level item
insertionLocation = project.ending
} else if (itemLevel > 1){ // child or sibling of previous item
if(itemLevel === previousLevel){ // sibling
insertionLocation = previousItem.parent.ending
} else if (itemLevel > previousLevel){ // child
insertionLocation = previousItem.ending
} else if (itemLevel < previousLevel){ // calculate previous parent
var parentItem = project.task
for(var i = 0; i < (itemLevel -1); i++){
parentItem = parentItem.tasks.pop()
}
insertionLocation = parentItem.ending
}
}
task = new Task(itemTopic, insertionLocation)
task.note = itemNote
previousItem = task
previousLevel = itemLevel
})
document.windows[0].focus = [project]
// return link to project to calling script
return linkURL
}
catch(err){
console.error(err.name, err.message)
throw {
name: err.name,
message: err.message
}
}
}
// CREATE SCRIPT URL WITH FUNCTION AND PASSED DATA
scriptURL = URL.tellFunction(
targetAppName,
targetAppFunction,
targetFunctionArgument
)
// CALL THE SCRIPT URL, PROCESS RESULTS OR ERROR
scriptURL.call(reply => {
// PROCESS RESULTS OF SCRIPT
if(reply){
console.log("CALL RESULT:", reply)
// open returned link to show new project
URL.fromString(reply).open()
}
utterance = createUtterance("Done!")
synthesizer.speakUtterance(utterance)
}, error => {
// PROCESS SCRIPT ERROR
console.error("CALL ERROR:", error.errorMessage)
throw error
})
}
catch(err){
if(!err.causedByUserCancelling){
utterance = createUtterance(err.message)
synthesizer.speakUtterance(utterance)
//new Alert(err.name, err.message).show()
}
}
});
action.validate = function(selection, sender){
return true
};
return action;
})();