Request: Copy Selected Rows in Markdown

@unlocked2412
I’ve modified it to just copy to the clipboard, and added “Task” as a style now, too. Task prepends a line with 1. [ ] and treats it like list.

/*{
 "type": "action",
 "targets": ["omnioutliner"],
 "author": "MacDork, based on work by Marc A. Kastner",
 "description": "Create Markdown & copy it to the clipboard",
 "label": "Markdown to Clipboard",
 "paletteLabel": "MarkDown"
}*/

/* https://discourse.omnigroup.com/t/request-copy-selected-rows-in-markdown/44714/2
*/

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 "Task":
                pre = "1. [ ] "
                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 = ''            
            }
        })
        mdText = topics.join(newline) //convert array to string
        Pasteboard.general.string = mdText
    });

    action.validate = function(selection, sender) {
        if (rootItem.descendants.length > 0) {
            return true
        } else {
            return false
        }
    }

    return action
}();
_;
3 Likes