OO-PG0017

Plug-In: Export Outline to JSON File

Exports the contents of the outline to a file in JSON format.

The source outline can contain multiple custom columns and notes, and a cell (Row/Column intersecton) value can be a Number, Text, Date, Checkbox, Enumeration, or Duration. Durations can be expressed in either their short-form text versions: 3d 2.25h or verbose forms, while the value of checkboxes can be expressed as either the integers or terms: 0 = unchecked, 1 = checked, and 2 = mixed.

The contents of the outline will be exported row-by-row from the top-down, and will include a numeric hierarchy level index for each row.

Some of the export parameters can be changed by adjusting the plug-in’s Preferences Dialog (see image). To access this plug-in’s preferences dialog, hold down the Shift key when selecting the plug-in from the Automation Menu.

JSON Export Plug-in Preferences Dialog

Return to: OmniOutliner Plug-In Collection

Export Outline to JSON File
  

/*{ "type": "action", "targets": ["omnioutliner"], "author": "Otto Automator", "identifier": "com.omni-automation.oo.export-to-json-file", "version": "1.1", "description": "Exports the contents of the outline to a file in JSON format. Outline can contain multiple custom columns and notes. A cell (Row/Column intersecton) value can be a Number, Text, Date, Checkbox, Enumeration, or Duration. Durations can be expressed in either their short-form text versions: 3d 2.25h or verbose forms, while the value of checkboxes can be expressed as either the integers or terms: 0 = unchecked, 1 = checked, and 2 = mixed. The contents of the outline will be exported row-by-row from the top-down, and will include a numeric hierarchy level parameter for each row. To access this plug-in’s preferences dialog, hold down the Shift key when selecting the plug-in from the Automation Menu.", "label": "Export to JSON File", "shortLabel": "JSON Export", "paletteLabel": "JSON Export", "image": "square.and.arrow.up" }*/ (() => { function storeDataInAppFolder(dataObj, fileName, endingActionIndicator){ try { dataObjStr = JSON.stringify(dataObj) data = Data.fromString(dataObjStr) folderURL = URL.documentsDirectory fileURL = folderURL.appendingPathComponent(fileName) wrapper = FileWrapper.withContents(fileName, data) wrapper.write(fileURL, [FileWrapper.WritingOptions.Atomic], null) if(endingActionIndicator !== 0 && app.platformName === "macOS"){ if (endingActionIndicator === 1){ fileURL.open() } else { folderURL.open() } } } catch(err){ new Alert(err.name, err.message).show() } } function logToConsole(title, data){ if(enableLogging){ console.log(title, data) } } var preferences = new Preferences() // NO ID = PLUG-IN ID var enableLogging const action = new PlugIn.Action(async function(selection, sender){ try { // PLUG-IN PREFERENCES exportDurationsInLongForm = preferences.readBoolean("exportDurationsInLongForm") if(!exportDurationsInLongForm instanceof Boolean){ exportDurationsInLongForm = false // 3 days 2.5 hours vs. 3d 2.25h } logToConsole("STATE: exportDurationsInLongForm", exportDurationsInLongForm) exportCheckboxStateNumerically = preferences.readBoolean("exportCheckboxStateNumerically") if(!exportCheckboxStateNumerically instanceof Boolean){ exportCheckboxStateNumerically = false // 0 = "unchecked", 1 = "checked", 2 = "mixed" } logToConsole("STATE: exportCheckboxStateNumerically", exportCheckboxStateNumerically) exportUsingDocumentName = preferences.readBoolean("exportUsingDocumentName") if(!exportUsingDocumentName instanceof Boolean){ exportUsingDocumentName = false // name the export document the outline name with “.json” file extension } logToConsole("STATE: exportUsingDocumentName", exportUsingDocumentName) exportToDocumentsFolder = preferences.readBoolean("exportToDocumentsFolder") if(!exportToDocumentsFolder instanceof Boolean){ exportToDocumentsFolder = true // automatically export to local OmniOutliner Documents } logToConsole("STATE: exportToDocumentsFolder", exportToDocumentsFolder) openExportedFile = preferences.readBoolean("openExportedFile") if(!openExportedFile instanceof Boolean){ openExportedFile = true // (macOS) open the exported file in the default JSON Editor app } logToConsole("STATE: openExportedFile", openExportedFile) openExportDirectory = preferences.readBoolean("openExportDirectory") if(!openExportDirectory instanceof Boolean){ openExportDirectory = false // (macOS) open the folder containing the exported file } logToConsole("STATE: openExportDirectory", openExportDirectory) enableLogging = preferences.readBoolean("enableLogging") if(!enableLogging instanceof Boolean){ enableLogging = true // log the plug-in’s actions and results } logToConsole("STATE: enableLogging", enableLogging) if(app.shiftKeyDown){ durationsCheckSwitchField = new Form.Field.Checkbox( "exportDurationsInLongForm", "Export duration values in verbose form", exportDurationsInLongForm ) checkboxesCheckSwitchField = new Form.Field.Checkbox( "exportCheckboxStateNumerically", "Export checkbox states as integers", exportCheckboxStateNumerically ) filenameCheckSwitchField = new Form.Field.Checkbox( "exportUsingDocumentName", "Name export file using outline title", exportUsingDocumentName ) destinationCheckSwitchField = new Form.Field.Checkbox( "exportToDocumentsFolder", "Export file to local OmniOutliner Documents folder", exportToDocumentsFolder ) openFileCheckSwitchField = new Form.Field.Checkbox( "openExportedFile", "(macOS) Open exported JSON file in default editor application", openExportedFile ) openFolderCheckSwitchField = new Form.Field.Checkbox( "openExportDirectory", "(macOS) Open folder containing the exported JSON file", openExportDirectory ) loggingCheckSwitchField = new Form.Field.Checkbox( "enableLogging", "Log the plug-in’s actions and results to the OmniOutliner Console", enableLogging ) inputForm = new Form() inputForm.addField(durationsCheckSwitchField) inputForm.addField(checkboxesCheckSwitchField) inputForm.addField(filenameCheckSwitchField) inputForm.addField(destinationCheckSwitchField) inputForm.addField(openFileCheckSwitchField) inputForm.addField(openFolderCheckSwitchField) inputForm.addField(loggingCheckSwitchField) formObject = await inputForm.show("Plug-in Preferences:","Continue") exportDurationsInLongForm = formObject.values['exportDurationsInLongForm'] exportCheckboxStateNumerically = formObject.values['exportCheckboxStateNumerically'] exportUsingDocumentName = formObject.values['exportUsingDocumentName'] exportToDocumentsFolder = formObject.values['exportToDocumentsFolder'] openExportedFile = formObject.values['openExportedFile'] openExportDirectory = formObject.values['openExportDirectory'] enableLogging = formObject.values['enableLogging'] preferences.write("exportDurationsInLongForm", exportDurationsInLongForm) preferences.write("exportCheckboxStateNumerically", exportCheckboxStateNumerically) preferences.write("exportUsingDocumentName", exportUsingDocumentName) preferences.write("exportToDocumentsFolder", exportToDocumentsFolder) preferences.write("openExportedFile", openExportedFile) preferences.write("openExportDirectory", openExportDirectory) preferences.write("enableLogging", enableLogging) logToConsole("NEW: exportDurationsInLongForm", exportDurationsInLongForm) logToConsole("NEW: exportCheckboxStateNumerically", exportCheckboxStateNumerically) logToConsole("NEW: exportUsingDocumentName", exportUsingDocumentName) logToConsole("NEW: exportToDocumentsFolder", exportToDocumentsFolder) logToConsole("NEW: openExportedFile", openExportedFile) logToConsole("NEW: openExportDirectory", openExportDirectory) logToConsole("NEW: enableLogging", enableLogging) return "plug-in preferences set" } // DECLARE TOP LEVEL OBJECTS: EDITOR, OUTLINE (TREE) editor = document.editors[0] tree = document.outline // IS STATUS COLUMN VISIBLE? checkboxVisible = editor.visibilityOfColumn(tree.statusColumn) // CREATE FORMATTERS AS NEEDED for (column of columns){ if(column.type === Column.Type.Duration){ logToConsole("NOTE: ", "CREATING DURATION FORMATTER") var durationFmtr = new Formatter.Duration() durationFmtr.useVerboseFormat = exportDurationsInLongForm break } } // BEGIN EXPORT logToConsole("NOTE: ", "BEGINNING EXPORT") exportObj = new Array() for ([index, aRow] of rootItem.descendants.entries()){ logToConsole("INCREMENT: ", "ROW " + index) // GET THE CORREPSONDING NODE FOR THE ROW aNode = editor.nodeForObject(aRow) // CREATE STORAGE CONTAINER FOR ROW DATA itemObj = new Object() // ADD ROW LEVEL INFO itemObj.level = aRow.level // PROCESS EACH COLUMN OF THE ROW for ([index, aColumn] of columns.entries()){ logToConsole("COLUMN INDEX", index) if(checkboxVisible && aColumn === statusColumn){ logToConsole("NOTE: ", "STATUS COLUMN") state = aNode.state if(state === State.Checked){ stateValue = (exportCheckboxStateNumerically === true) ? 1:"checked" itemObj.status = stateValue } else if (state === State.Mixed){ stateValue = (exportCheckboxStateNumerically === true) ? 2:"mixed" itemObj.status = stateValue } else if (state === State.Unchecked){ stateValue = (exportCheckboxStateNumerically === true) ? 0:"unchecked" itemObj.status = stateValue } } else if(aColumn === noteColumn){ logToConsole("NOTE: ", "NOTE COLUMN") columnTitle = aColumn.title if(columnTitle === ""){ itemObj.note = aRow.note } else { itemObj[columnTitle] = aRow.note } } else if(aColumn === outlineColumn){ logToConsole("NOTE: ", "TOPIC COLUMN") columnTitle = aColumn.title if(columnTitle === ""){ itemObj.topic = aRow.topic } else { itemObj[columnTitle] = aRow.topic } } else { logToConsole("NOTE: ", "CUSTOM COLUMN") columnTitle = aColumn.title if(columnTitle === ""){ columnTitle = `COL-${index}` } columnType = aColumn.type // [ CHECKBOX, DATE, DURATION, ENUMERATION, NUMBER, TEXT ] // GET VALUE OF COLUMN/ROW CELL cellValue = aRow.valueForColumn(aColumn) logToConsole("CELL VALUE", cellValue) // ADD TO DATA OBJECT switch (columnType) { case Column.Type.Checkbox: logToConsole("NOTE: ", "COLUMN TYPE: CHECKBOX") state = aNode.state if(state === State.Checked){ stateValue = (exportCheckboxStateNumerically === true) ? 1:"checked" } else if (state === State.Mixed){ stateValue = (exportCheckboxStateNumerically === true) ? 2:"mixed" } else if (state === State.Unchecked){ stateValue = (exportCheckboxStateNumerically === true) ? 0:"unchecked" } itemObj[columnTitle] = stateValue break; case Column.Type.Date: logToConsole("NOTE: ", "COLUMN TYPE: DATE") cellValue = cellValue.toISOString() itemObj[columnTitle] = cellValue break; case Column.Type.Duration: logToConsole("NOTE: ", "COLUMN TYPE: DURATION") cellValue = durationFmtr.stringFromDecimal(cellValue) itemObj[columnTitle] = cellValue break; case Column.Type.Enumeration: logToConsole("NOTE: ", "COLUMN TYPE: ENUMERATION") cellValue = cellValue.name itemObj[columnTitle] = cellValue break; case Column.Type.Number: logToConsole("NOTE: ", "COLUMN TYPE: NUMBER") itemObj[columnTitle] = cellValue break; case Column.Type.Text: logToConsole("NOTE: ", "COLUMN TYPE: TEXT") // GET STRING OF TEXT OBJECT cellValue = cellValue.string itemObj[columnTitle] = cellValue break; default: logToConsole("NOTE: ", "COLUMN TYPE: UNKNOWN") cellValue = cellValue.toString() itemObj[columnTitle] = cellValue } } } logToConsole("ROW OBJECT: ", JSON.stringify(itemObj)) exportObj.push(itemObj) } logToConsole("OUTLINE: ", JSON.stringify(exportObj, null, 2)) documentName = document.name.replace(/\.[^/.]+$/, "") logToConsole("DOCUMENT NAME:", documentName) exportFileName = documentName + ".json" if(exportToDocumentsFolder === true){ if(exportUsingDocumentName == false){ textInputField = new Form.Field.String( "textInput", null, exportFileName ) inputForm = new Form() inputForm.addField(textInputField) inputForm.validate = function(formObject){ inputText = formObject.values['textInput'] return ((!inputText)?false:true) } formPrompt = "Name for export file:" buttonTitle = "Continue" formObject = await inputForm.show(formPrompt, buttonTitle) exportFileName = formObject.values['textInput'] if(!exportFileName.endsWith(".json")){ exportFileName = exportFileName + ".json" } logToConsole('exportFileName: ',exportFileName) } endingActionIndicator = 0 if (openExportedFile){endingActionIndicator = 1} if (openExportDirectory){endingActionIndicator = 2} storeDataInAppFolder(exportObj, exportFileName, endingActionIndicator) } else { data = Data.fromString(JSON.stringify(exportObj)) wrapper = FileWrapper.withContents(exportFileName, data) filesaver = new FileSaver() urlObj = await filesaver.show(wrapper) logToConsole("FILE:", urlObj.path) if(openExportedFile){ urlObj.open() } else if (openExportDirectory){ parentDirectoryURL = urlObj.deletingLastPathComponent( ) parentDirectoryURL.open() } } } catch(err){ if(!causedByUserCancelling){ new Alert(err.name, err.message).show() } } }); action.validate = function(selection, sender){ return true }; return action; })();