I created one that does this. In OmniOutliner, I created some styles to indicate different markdown styles. Each style has a different color fo easy visual indication, and I’ve mapped each to a function key for easy assignment. My conversion script only works on the first style found, so if you assign an OmniOutliner row both “Ordered List” and “Code”, it’ll only take the first style. (I have no idea which one the script will grab, lol)
I created the following styles:
- F1 – Ordered List
- F2 – Unordered List
- F3 – Paragraph
- F4 – Blockquote
- F5 – Code
The code is below; sorry about the strange discourse styling, but all of the below are part of the script.
It spits it out into Drafts 5, which is available on iOS and macOS for free, so I just copy and paste my Markdown out of there.
/*{
"type": "action",
"targets": ["omnioutliner"],
"author": "Macdork, based on work by Marc A. Kastner",
"description": "Create Markdown & export it to Drafts",
"label": "Markdown to Drafts",
"paletteLabel": "Drafts Doc"
}*/
function msg(msg) {
console.log(msg)
}
function checkCodeBlockSiblings(item) {
/* use item.followingSiblings and item.precedingSiblings to get
arrays of other siblings to check for more code blocks before assigning
pre and post markdown. Returns array of pre and post markdown */
var newline = '\n'
var pre = ''
var post = ''
var precededByCode = false
var followedByCode = false
msg("--------------\nProcessing " + item.topic)
var precedingArray = item.precedingSiblings
if (typeof precedingArray != 'undefined' && precedingArray instanceof Array) {
// we know it has a sibling
var precItem = precedingArray[precedingArray.length - 1]
if (precItem != null) {
// probably redundant
msg("preceding topic: " + precItem.topic)
if (typeof (precItem.style.namedStyles[0]) != 'undefined') {
// this item is styled; let's see what it is
precStyle = precItem.style.namedStyles[0].name
if (precStyle == "Code") {
precededByCode = true
msg("prec was code, so precededByCode = " + precededByCode)
}
} // end of block that checked for named styles
} // possibly redundant null check
}
if (precededByCode) {
// preceded by code block, so don't add more markdown
pre = ''
} else {
// wasn't preceded by a code block, so add the markdown
pre = newline + "```" + newline
}
// repeat the process for the post markdown
var followingArray = item.followingSiblings
if (typeof followingArray != 'undefined' && followingArray instanceof Array) {
var folItem = followingArray[0]
if (folItem != null) {
if (typeof (folItem.style.namedStyles[0]) != 'undefined') {
// this item is styled; let's see what it is
folStyle = folItem.style.namedStyles[0].name
if (folStyle == "Code") {
followedByCode = true
msg("following is code, so followedByCode = " + followedByCode)
}
}
}
}
if (followedByCode) {
// followed by code block, so don't add more markdown
post = ''
} else {
// not followed by a code block, so add the markdown
post = newline + "```" + newline
}
return [pre, post]
}
function getMarkdown(item) {
/*
Given an item, return value array with all the mardown needed, and
info needed to determine the level of indention
*/
var isList = false
var pre = ''
var post = ''
var numPounds = item.level + 1 // +1 since only the doc title should have 1
var newline = '\n'
// check the item to see if it's styled first
if (typeof (item.style.namedStyles[0]) !== "undefined") {
styleName = item.style.namedStyles[0].name // first named style
switch(styleName) {
case "Ordered List":
pre = "1. "
isList = true
break;
case "Unordered List":
pre = "* "
isList = true
break;
case "Paragraph":
pre = newline
post = newline
break;
case "Blockquote":
pre = "> "
// post = newline
break;
case "Code":
codeMarkdown = checkCodeBlockSiblings(item)
pre = codeMarkdown[0]
post = codeMarkdown[1]
// pre = "```" + newline
// post = newline + "```"
break;
} // end of switch statement
} else {
/* Handle un-styled item as a heading; level depth = num of #'s + 1.
I'm adding one additional # to account for my feeling that the doc
title should have 1 #, and every other heading should be smaller than
the title.
*/
pre = newline + '#'.repeat(numPounds) + " "
post = newline
}
return [pre, post, isList]
}
function getNumTabs (item) {
/* Given an item, returns the number of tabs to indent it
Recursively calls itself on its parent to gather parent's info. End
condition is when we reach a parent whose number of tabs is 0.
Using documentation from this page: https://omni-automation.com/omnioutliner/item.html
item.parent: (Item or nil r/o) • Returns the item that contains this item, or null
if this is the root item.
*/
var parent = item.parent
// check to be sure we haven't returned a null parent
if (parent != null) {
var parentMD = getMarkdown(parent)
parentIsList = parentMD[2] //
// if parent's not a list, return 0; if it is, we need to go higher
if (!parentIsList) {
return 0
} else if (parentIsList) {
// parent was a list, so let's recurse higher
return 1 + getNumTabs(parent)
}
} else {
// we've reached the root item; return 0
return 0
}
}
var _ = function() {
var action = new PlugIn.Action(function(selection, sender) {
var topics = new Array()
var tab = '\t'
var newline = '\n'
var noteString = ''
var numTabs = 0
var tabs = ''
console.clear()
// add document title
docTitle = "# " + document.name
topics.push(docTitle)
// loop through the document's items
rootItem.descendants.forEach(function(item) {
// reset variables for this iteration
var mdInfo = getMarkdown(item)
var pre = mdInfo[0]
var post = mdInfo[1]
var isList = mdInfo[2]
//msg("index: " + item.index)
// now find out how far to indent this item
if (isList) {
numTabs = getNumTabs(item)
tabs = tab.repeat(numTabs)
}
// create the string w/ all its markdown
mdTopic = tabs + pre + item.topic + post
topics.push(mdTopic)
// handle item notes as a paragraph
if (item.note) {
noteString = item.note + newline
topics.push(noteString)
noteString = ''
}
})
topics.join(newline) //convert the array to newline-separated string
encodedStr = encodeURIComponent(topics.join(newline))
urlStr = "drafts5://x-callback-url/create?text=" + encodedStr
url = URL.fromString(urlStr)
url.call(function(result) {
console.log(result)
})
});
action.validate = function(selection, sender) {
if (rootItem.descendants.length > 0) {
return true
} else {
return false
}
}
return action
}();
_;