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.
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.documentsDirectoryfileURL = 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 IDvar enableLoggingconst action = new PlugIn.Action(async function(selection, sender){try {// PLUG-IN PREFERENCESexportDurationsInLongForm = 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 NEEDEDfor (column of columns){if(column.type === Column.Type.Duration){logToConsole("NOTE: ", "CREATING DURATION FORMATTER")var durationFmtr = new Formatter.Duration()durationFmtr.useVerboseFormat = exportDurationsInLongFormbreak}}// BEGIN EXPORTlogToConsole("NOTE: ", "BEGINNING EXPORT")exportObj = new Array()for ([index, aRow] of rootItem.descendants.entries()){logToConsole("INCREMENT: ", "ROW " + index)// GET THE CORREPSONDING NODE FOR THE ROWaNode = editor.nodeForObject(aRow)// CREATE STORAGE CONTAINER FOR ROW DATAitemObj = new Object()// ADD ROW LEVEL INFOitemObj.level = aRow.level// PROCESS EACH COLUMN OF THE ROWfor ([index, aColumn] of columns.entries()){logToConsole("COLUMN INDEX", index)if(checkboxVisible && aColumn === statusColumn){logToConsole("NOTE: ", "STATUS COLUMN")state = aNode.stateif(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.titleif(columnTitle === ""){itemObj.note = aRow.note} else {itemObj[columnTitle] = aRow.note}} else if(aColumn === outlineColumn){logToConsole("NOTE: ", "TOPIC COLUMN")columnTitle = aColumn.titleif(columnTitle === ""){itemObj.topic = aRow.topic} else {itemObj[columnTitle] = aRow.topic}} else {logToConsole("NOTE: ", "CUSTOM COLUMN")columnTitle = aColumn.titleif(columnTitle === ""){columnTitle = `COL-${index}`}columnType = aColumn.type// [ CHECKBOX, DATE, DURATION, ENUMERATION, NUMBER, TEXT ]// GET VALUE OF COLUMN/ROW CELLcellValue = aRow.valueForColumn(aColumn)logToConsole("CELL VALUE", cellValue)// ADD TO DATA OBJECTswitch (columnType) {case Column.Type.Checkbox:logToConsole("NOTE: ", "COLUMN TYPE: CHECKBOX")state = aNode.stateif(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] = stateValuebreak;case Column.Type.Date:logToConsole("NOTE: ", "COLUMN TYPE: DATE")cellValue = cellValue.toISOString()itemObj[columnTitle] = cellValuebreak;case Column.Type.Duration:logToConsole("NOTE: ", "COLUMN TYPE: DURATION")cellValue = durationFmtr.stringFromDecimal(cellValue)itemObj[columnTitle] = cellValuebreak;case Column.Type.Enumeration:logToConsole("NOTE: ", "COLUMN TYPE: ENUMERATION")cellValue = cellValue.nameitemObj[columnTitle] = cellValuebreak;case Column.Type.Number:logToConsole("NOTE: ", "COLUMN TYPE: NUMBER")itemObj[columnTitle] = cellValuebreak;case Column.Type.Text:logToConsole("NOTE: ", "COLUMN TYPE: TEXT")// GET STRING OF TEXT OBJECTcellValue = cellValue.stringitemObj[columnTitle] = cellValuebreak;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 = 0if (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;})();