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