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; })();