diff --git a/Scripts/JiraToMarkdown.js b/Scripts/JiraToMarkdown.js new file mode 100644 index 0000000..85fa7f4 --- /dev/null +++ b/Scripts/JiraToMarkdown.js @@ -0,0 +1,106 @@ +/** + { + "id": "co.ameba.Esse.ExternalFunctions.JiraToMD", + "name": "Jira to Markdown", + "description": "Converts Jira markup to GitHub flavored markdown", + "category":"Convert", + "author":"Ameba Labs", + } +**/ + +//adopted from https://github.com/FokkeZB/J2M + +function main(input) { + input = input.replace(/^bq\.(.*)$/gm, function (match, content) { + return '> ' + content + "\n"; + }); + + input = input.replace(/([*_])(.*)\1/g, function (match,wrapper,content) { + var to = (wrapper === '*') ? '**' : '*'; + return to + content + to; + }); + + // multi-level numbered list + input = input.replace(/^((?:#|-|\+|\*)+) (.*)$/gm, function (match, level, content) { + var len = 2; + var prefix = '1.'; + if (level.length > 1) { + len = parseInt((level.length - 1) * 4) + 2; + } + + // take the last character of the level to determine the replacement + var prefix = level[level.length - 1]; + if (prefix == '#') prefix = '1.'; + + return Array(len).join(" ") + prefix + ' ' + content; + }); + + // headers, must be after numbered lists + input = input.replace(/^h([0-6])\.(.*)$/gm, function (match,level,content) { + return Array(parseInt(level) + 1).join('#') + content; + }); + + input = input.replace(/\{\{([^}]+)\}\}/g, '`$1`'); + input = input.replace(/\?\?((?:.[^?]|[^?].)+)\?\?/g, '$1'); + input = input.replace(/\+([^+]*)\+/g, '$1'); + input = input.replace(/\^([^^]*)\^/g, '$1'); + input = input.replace(/~([^~]*)~/g, '$1'); + input = input.replace(/-([^-]*)-/g, '-$1-'); + + input = input.replace(/\{code(:([a-z]+))?\}([^]*?)\{code\}/gm, '```$2$3```'); + input = input.replace(/\{quote\}([^]*)\{quote\}/gm, function(match, content) { + lines = content.split(/\r?\n/gm); + + for (var i = 0; i < lines.length; i++) { + lines[i] = '> ' + lines[i]; + } + + return lines.join("\n"); + }); + + // Images with alt= among their parameters + input = input.replace(/!([^|\n\s]+)\|([^\n!]*)alt=([^\n!\,]+?)(,([^\n!]*))?!/g, '![$3]($1)'); + // Images with just other parameters (ignore them) + input = input.replace(/!([^|\n\s]+)\|([^\n!]*)!/g, '![]($1)'); + // Images without any parameters or alt + input = input.replace(/!([^\n\s!]+)!/g, '![]($1)'); + + input = input.replace(/\[([^|]+)\|(.+?)\]/g, '[$1]($2)'); + input = input.replace(/\[(.+?)\]([^\(]+)/g, '<$1>$2'); + + input = input.replace(/{noformat}/g, '```'); + input = input.replace(/{color:([^}]+)}([^]*?){color}/gm, '$2'); + + // Convert header rows of tables by splitting input on lines + lines = input.split(/\r?\n/gm); + lines_to_remove = [] + for (var i = 0; i < lines.length; i++) { + line_content = lines[i]; + + seperators = line_content.match(/\|\|/g); + if (seperators != null) { + lines[i] = lines[i].replace(/\|\|/g, "|"); + console.log(seperators) + + // Add a new line to mark the header in Markdown, + // we require that at least 3 -'s are between each | + header_line = ""; + for (var j = 0; j < seperators.length-1; j++) { + header_line += "|---"; + } + + header_line += "|"; + + lines.splice(i+1, 0, header_line); + + } + } + + // Join the split lines back + input = "" + for (var i = 0; i < lines.length; i++) { + input += lines[i] + "\n" + } + + return input; +} \ No newline at end of file diff --git a/Scripts/MarkdownToJira.js b/Scripts/MarkdownToJira.js new file mode 100644 index 0000000..8c4b16a --- /dev/null +++ b/Scripts/MarkdownToJira.js @@ -0,0 +1,122 @@ +/** + { + "id": "co.ameba.Esse.ExternalFunctions.MDToJira", + "name": "Markdown to Jira", + "description": "Converts GitHub flavored markdown to Jira Markup", + "category":"Convert", + "author":"Ameba Labs", + } +**/ + +//adopted from https://github.com/FokkeZB/J2M + +function main(input) { + // remove sections that shouldn't be recursively processed + var START = 'J2MBLOCKPLACEHOLDER'; + var replacementsList = []; + var counter = 0; + + input = input.replace(/`{3,}(\w+)?((?:\n|.)+?)`{3,}/g, function(match, synt, content) { + var code = '{code'; + + if (synt) { + code += ':' + synt; + } + + code += '}' + content + '{code}'; + var key = START + counter++ + '%%'; + replacementsList.push({key: key, value: code}); + return key; + }); + + input = input.replace(/`([^`]+)`/g, function(match, content) { + var code = '{{'+ content + '}}'; + var key = START + counter++ + '%%'; + replacementsList.push({key: key, value: code}); + return key; + }); + + input = input.replace(/`([^`]+)`/g, '{{$1}}'); + + input = input.replace(/^(.*?)\n([=-])+$/gm, function (match,content,level) { + return 'h' + (level[0] === '=' ? 1 : 2) + '. ' + content; + }); + + input = input.replace(/^([#]+)(.*?)$/gm, function (match,level,content) { + return 'h' + level.length + '.' + content; + }); + + input = input.replace(/([*_]+)(.*?)\1/g, function (match,wrapper,content) { + var to = (wrapper.length === 1) ? '_' : '*'; + return to + content + to; + }); + + // multi-level bulleted list + input = input.replace(/^(\s*)- (.*)$/gm, function (match,level,content) { + var len = 2; + if(level.length > 0) { + len = parseInt(level.length/4.0) + 2; + } + return Array(len).join("-") + ' ' + content; + }); + + // multi-level numbered list + input = input.replace(/^(\s+)1. (.*)$/gm, function (match, level, content) { + var len = 2; + if (level.length > 1) { + len = parseInt(level.length / 4) + 2; + } + return Array(len).join("#") + ' ' + content; + }); + + var map = { + cite: '??', + del: '-', + ins: '+', + sup: '^', + sub: '~' + }; + + input = input.replace(new RegExp('<(' + Object.keys(map).join('|') + ')>(.*?)<\/\\1>', 'g'), function (match,from,content) { + //console.log(from); + var to = map[from]; + return to + content + to; + }); + + input = input.replace(/([^]*?)<\/span>/gm, '{color:$1}$2{color}'); + + input = input.replace(/~~(.*?)~~/g, '-$1-'); + + // Images without alt + input = input.replace(/!\[\]\(([^)\n\s]+)\)/g, '!$1!'); + // Images with alt + input = input.replace(/!\[([^\]\n]+)\]\(([^)\n\s]+)\)/g, '!$2|alt=$1!'); + + input = input.replace(/\[([^\]]+)\]\(([^)]+)\)/g, '[$1|$2]'); + input = input.replace(/<([^>]+)>/g, '[$1]'); + + // restore extracted sections + for(var i =0; i < replacementsList.length; i++){ + var sub = replacementsList[i]; + input = input.replace(sub["key"], sub["value"]); + } + + // Convert header rows of tables by splitting input on lines + lines = input.split(/\r?\n/gm); + lines_to_remove = [] + for (var i = 0; i < lines.length; i++) { + line_content = lines[i]; + + if (line_content.match(/\|---/g) != null) { + lines[i-1] = lines[i-1].replace(/\|/g, "||") + lines.splice(i, 1) + } + } + + // Join the split lines back + input = "" + for (var i = 0; i < lines.length; i++) { + input += lines[i] + "\n" + } + return input; +} \ No newline at end of file