├── .eslintignore ├── .eslintrc.json ├── .gitignore ├── .prettierignore ├── .prettierrc ├── HISTORY.md ├── LICENSE.md ├── README.md ├── index.js ├── jsdoc.json ├── package-lock.json ├── package.json └── test ├── jira2md.js ├── md2jira.js ├── test.html ├── test.jira ├── test.md └── to_html.js /.eslintignore: -------------------------------------------------------------------------------- 1 | /node_modules/ 2 | test.html 3 | -------------------------------------------------------------------------------- /.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "env": { 3 | "browser": false, 4 | "node": true, 5 | "es6": true, 6 | "mocha": true 7 | }, 8 | "extends": ["airbnb-base", "plugin:prettier/recommended", "plugin:chai-friendly/recommended"], 9 | "plugins": ["prettier", "chai-friendly", "jsdoc"], 10 | "globals": { 11 | "Atomics": "readonly", 12 | "SharedArrayBuffer": "readonly" 13 | }, 14 | "parserOptions": { 15 | "ecmaVersion": 2020, 16 | "sourceType": "module" 17 | }, 18 | "rules": { 19 | "prettier/prettier": "error", 20 | "no-underscore-dangle": "off", 21 | "class-methods-use-this": "off", 22 | "require-jsdoc": "error", 23 | "valid-jsdoc": "off", 24 | "global-require": "warn", 25 | "lines-between-class-members": "off", 26 | "jsdoc/check-alignment": 1, // Recommended 27 | "jsdoc/check-indentation": 1, 28 | "jsdoc/check-param-names": 1, // Recommended 29 | "jsdoc/check-syntax": 1, 30 | "jsdoc/check-tag-names": [ 31 | "warn", 32 | { 33 | "definedTags": ["typicalname"] 34 | } 35 | ], 36 | "jsdoc/check-types": 1, // Recommended 37 | "jsdoc/implements-on-classes": 1, // Recommended 38 | "jsdoc/match-description": 1, 39 | "jsdoc/newline-after-description": 1, // Recommended 40 | "jsdoc/require-description": 1, 41 | "jsdoc/require-hyphen-before-param-description": 1, 42 | "jsdoc/require-jsdoc": 1, // Recommended 43 | "jsdoc/require-param": 1, // Recommended 44 | "jsdoc/require-param-description": 1, // Recommended 45 | "jsdoc/require-param-name": 1, // Recommended 46 | "jsdoc/require-param-type": 1, // Recommended 47 | "jsdoc/require-returns": 1, // Recommended 48 | "jsdoc/require-returns-check": 1, // Recommended 49 | "jsdoc/require-returns-description": 1, // Recommended 50 | "jsdoc/require-returns-type": 1, // Recommended 51 | "jsdoc/valid-types": 1 // Recommended 52 | } 53 | } -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | 6 | # Runtime data 7 | pids 8 | *.pid 9 | *.seed 10 | 11 | # Directory for instrumented libs generated by jscoverage/JSCover 12 | lib-cov 13 | 14 | # Coverage directory used by tools like istanbul 15 | coverage 16 | 17 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 18 | .grunt 19 | 20 | # node-waf configuration 21 | .lock-wscript 22 | 23 | # Compiled binary addons (http://nodejs.org/api/addons.html) 24 | build/Release 25 | 26 | # Dependency directory 27 | # https://docs.npmjs.com/misc/faq#should-i-check-my-node-modules-folder-into-git 28 | node_modules 29 | 30 | .DS_Store 31 | -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | /node_modules/ 2 | test.html 3 | -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "printWidth": 120, 3 | "semi": true, 4 | "singleQuote": true, 5 | "tabWidth": 4, 6 | "trailingComma": "es5", 7 | "useTabs": false 8 | } 9 | -------------------------------------------------------------------------------- /HISTORY.md: -------------------------------------------------------------------------------- 1 | # Changes 2 | 3 | This file is a manually maintained list of changes for each release. Feel free to add your changes here when sending pull requests. Also send corrections if you spot any mistakes. 4 | 5 | ### 1.4.0 6 | 7 | * Initial public release 8 | 9 | ### 1.5.0 (2016-01-29) 10 | 11 | * Additional features added: tables and panels. 12 | * Color is now stripped when converting from jira to markdown as standard markdown does not support color. 13 | 14 | ### 2.0.0 (2016-07-07) 15 | 16 | * Removed citations due to a bug 17 | 18 | ## 2.0.2 (2017-08-22) 19 | 20 | * Fixed a bug in how titled code blocks were rendered 21 | 22 | ## 2.0.3 (2017-09-15) 23 | 24 | * Fixed a bug where dashes were being converted to strike throughs in situations they shouldn't have been. Before the fix, the string "ABC-1234 and DEF-5678" would have been converted to (HTML): "ABC1234 and DEV5678" or (Markdown): "ABC~~1234 and DEV~~5678" which is invalid. After the fix, no strikethroughs will be added to the converted output unless there is at least 1 space before the first dash (or double tilde) and after the last dash (or double tilde). 25 | 26 | ## NOTE: All versions from here, refer to Releases page on github 27 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | Copyright 2013 Fokke Zandbergen 2 | 3 | Licensed under the Apache License, Version 2.0 (the "License"); 4 | you may not use this file except in compliance with the License. 5 | You may obtain a copy of the License at 6 | 7 | http://www.apache.org/licenses/LICENSE-2.0 8 | 9 | Unless required by applicable law or agreed to in writing, software 10 | distributed under the License is distributed on an "AS IS" BASIS, 11 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | See the License for the specific language governing permissions and 13 | limitations under the License. 14 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # jira2md 2 | 3 | ## JIRA to MarkDown text format converter 4 | Convert from JIRA text formatting to GitHub Flavored MarkDown and back again. Also allows for both to be converted to HTML. 5 | 6 | ## Credits 7 | This module was heavily inspired by the J2M project by Fokke Zandbergen (http://j2m.fokkezb.nl/). Major credit to Fokke (and other contributors) for establishing a lot of the fundamental RexExp patterns for this module to work. 8 | 9 | ## Installation 10 | ``` 11 | npm install jira2md 12 | ``` 13 | 14 | ## Supported Conversions 15 | NOTE: All conversion work bi-directionally (from jira to markdown and back again). 16 | 17 | * Headers (H1-H6) 18 | * Bold 19 | * Italic 20 | * Bold + Italic 21 | * Un-ordered lists 22 | * Ordered lists (with help from [aarbanas](https://github.com/aarbanas)) 23 | * Programming Language-specific code blocks (with help from herbert-venancio) 24 | * Inline preformatted text spans 25 | * Un-named links 26 | * Named links 27 | * Monospaced Text 28 | * ~~Citations~~ (Removed in 2.0.0) 29 | * Strikethroughs 30 | * Inserts 31 | * Superscripts 32 | * Subscripts 33 | * Single-paragraph blockquotes 34 | * Tables (thanks to erykwarren) 35 | * Panels (thanks to erykwarren) 36 | 37 | 38 | ## How to Use 39 | 40 | ### Markdown String 41 | 42 | We'll refer to this as the `md` variable in the examples below. 43 | 44 | ``` 45 | **Some bold things** 46 | *Some italic stuff* 47 | ## H2 48 | 49 | ``` 50 | 51 | ### Atlassian Wiki Syntax 52 | 53 | We'll refer to this as the `jira` variable in the examples below. 54 | 55 | ``` 56 | *Some bold things** 57 | _Some italic stuff_ 58 | h2. H2 59 | [http://google.com] 60 | ``` 61 | 62 | ### Examples 63 | 64 | ```javascript 65 | // Include the module 66 | const j2m = require('jira2md'); 67 | 68 | // If converting from Mardown to Jira Wiki Syntax: 69 | const jira = j2m.to_jira(md); 70 | 71 | // If converting from Jira Wiki Syntax to Markdown: 72 | const md = j2m.to_markdown(jira); 73 | 74 | // If converting from Markdown to HTML: 75 | const html = j2m.md_to_html(md); 76 | 77 | // If converting from JIRA Wiki Syntax to HTML: 78 | const html = j2m.jira_to_html(jira); 79 | ``` 80 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | const { marked } = require('marked'); 2 | 3 | marked.setOptions({ breaks: true, smartyPants: true }); 4 | 5 | class J2M { 6 | /** 7 | * Converts a Markdown string into HTML (just a wrapper to Marked's parse method). 8 | * 9 | * @static 10 | * @param {string} str - String to convert from Markdown to HTML 11 | * @returns {string} The HTML result 12 | */ 13 | static md_to_html(str) { 14 | return marked.parse(str); 15 | } 16 | 17 | /** 18 | * Converts a Jira Wiki string into HTML. 19 | * 20 | * @static 21 | * @param {string} str - String to convert from Jira Wiki syntax to HTML 22 | * @returns {string} The HTML result 23 | */ 24 | static jira_to_html(str) { 25 | return marked.parse(J2M.to_markdown(str)); 26 | } 27 | 28 | /** 29 | * Converts a Jira Wiki string into Markdown. 30 | * 31 | * @static 32 | * @param {string} str - Jira Wiki string to convert to Markdown 33 | * @returns {string} The Markdown result 34 | */ 35 | static to_markdown(str) { 36 | return ( 37 | str 38 | // Un-Ordered Lists 39 | .replace(/^[ \t]*(\*+)\s+/gm, (match, stars) => { 40 | return `${Array(stars.length).join(' ')}* `; 41 | }) 42 | // Ordered lists 43 | .replace(/^[ \t]*(#+)\s+/gm, (match, nums) => { 44 | return `${Array(nums.length).join(' ')}1. `; 45 | }) 46 | // Headers 1-6 47 | .replace(/^h([0-6])\.(.*)$/gm, (match, level, content) => { 48 | return Array(parseInt(level, 10) + 1).join('#') + content; 49 | }) 50 | // Bold 51 | .replace(/\*(\S.*)\*/g, '**$1**') 52 | // Italic 53 | .replace(/_(\S.*)_/g, '*$1*') 54 | // Monospaced text 55 | .replace(/\{\{([^}]+)\}\}/g, '`$1`') 56 | // Citations (buggy) 57 | // .replace(/\?\?((?:.[^?]|[^?].)+)\?\?/g, '$1') 58 | // Inserts 59 | .replace(/\+([^+]*)\+/g, '$1') 60 | // Superscript 61 | .replace(/\^([^^]*)\^/g, '$1') 62 | // Subscript 63 | .replace(/~([^~]*)~/g, '$1') 64 | // Strikethrough 65 | .replace(/(\s+)-(\S+.*?\S)-(\s+)/g, '$1~~$2~~$3') 66 | // Code Block 67 | .replace( 68 | /\{code(:([a-z]+))?([:|]?(title|borderStyle|borderColor|borderWidth|bgColor|titleBGColor)=.+?)*\}([^]*?)\n?\{code\}/gm, 69 | '```$2$5\n```' 70 | ) 71 | // Pre-formatted text 72 | .replace(/{noformat}/g, '```') 73 | // Un-named Links 74 | .replace(/\[([^|]+?)\]/g, '<$1>') 75 | // Images 76 | .replace(/!(.+)!/g, '![]($1)') 77 | // Named Links 78 | .replace(/\[(.+?)\|(.+?)\]/g, '[$1]($2)') 79 | // Single Paragraph Blockquote 80 | .replace(/^bq\.\s+/gm, '> ') 81 | // Remove color: unsupported in md 82 | .replace(/\{color:[^}]+\}([^]*?)\{color\}/gm, '$1') 83 | // panel into table 84 | .replace(/\{panel:title=([^}]*)\}\n?([^]*?)\n?\{panel\}/gm, '\n| $1 |\n| --- |\n| $2 |') 85 | // table header 86 | .replace(/^[ \t]*((?:\|\|.*?)+\|\|)[ \t]*$/gm, (match, headers) => { 87 | const singleBarred = headers.replace(/\|\|/g, '|'); 88 | return `\n${singleBarred}\n${singleBarred.replace(/\|[^|]+/g, '| --- ')}`; 89 | }) 90 | // remove leading-space of table headers and rows 91 | .replace(/^[ \t]*\|/gm, '|') 92 | ); 93 | // // remove unterminated inserts across table cells 94 | // .replace(/\|([^<]*)(?![^|]*<\/ins>)([^|]*)\|/g, (_, preceding, following) => { 95 | // return `|${preceding}+${following}|`; 96 | // }) 97 | // // remove unopened inserts across table cells 98 | // .replace(/\|(?)([^<]*)<\/ins>([^|]*)\|/g, (_, preceding, following) => { 99 | // return `|${preceding}+${following}|`; 100 | // }); 101 | } 102 | 103 | /** 104 | * Converts a Markdown string into Jira Wiki syntax. 105 | * 106 | * @static 107 | * @param {string} str - Markdown string to convert to Jira Wiki syntax 108 | * @returns {string} The Jira Wiki syntax result 109 | */ 110 | static to_jira(str) { 111 | const map = { 112 | // cite: '??', 113 | del: '-', 114 | ins: '+', 115 | sup: '^', 116 | sub: '~', 117 | }; 118 | 119 | return ( 120 | str 121 | // Tables 122 | .replace( 123 | /^\n((?:\|.*?)+\|)[ \t]*\n((?:\|\s*?-{3,}\s*?)+\|)[ \t]*\n((?:(?:\|.*?)+\|[ \t]*\n)*)$/gm, 124 | (match, headerLine, separatorLine, rowstr) => { 125 | const headers = headerLine.match(/[^|]+(?=\|)/g); 126 | const separators = separatorLine.match(/[^|]+(?=\|)/g); 127 | if (headers.length !== separators.length) return match; 128 | 129 | const rows = rowstr.split('\n'); 130 | if (rows.length === 2 && headers.length === 1) 131 | // Panel 132 | return `{panel:title=${headers[0].trim()}}\n${rowstr 133 | .replace(/^\|(.*)[ \t]*\|/, '$1') 134 | .trim()}\n{panel}\n`; 135 | 136 | return `||${headers.join('||')}||\n${rowstr}`; 137 | } 138 | ) 139 | // Bold, Italic, and Combined (bold+italic) 140 | .replace(/([*_]+)(\S.*?)\1/g, (match, wrapper, content) => { 141 | switch (wrapper.length) { 142 | case 1: 143 | return `_${content}_`; 144 | case 2: 145 | return `*${content}*`; 146 | case 3: 147 | return `_*${content}*_`; 148 | default: 149 | return wrapper + content + wrapper; 150 | } 151 | }) 152 | // All Headers (# format) 153 | .replace(/^([#]+)(.*?)$/gm, (match, level, content) => { 154 | return `h${level.length}.${content}`; 155 | }) 156 | // Headers (H1 and H2 underlines) 157 | .replace(/^(.*?)\n([=-]+)$/gm, (match, content, level) => { 158 | return `h${level[0] === '=' ? 1 : 2}. ${content}`; 159 | }) 160 | // Ordered lists 161 | .replace(/^([ \t]*)\d+\.\s+/gm, (match, spaces) => { 162 | return `${Array(Math.floor(spaces.length / 3) + 1) 163 | .fill('#') 164 | .join('')} `; 165 | }) 166 | // Un-Ordered Lists 167 | .replace(/^([ \t]*)\*\s+/gm, (match, spaces) => { 168 | return `${Array(Math.floor(spaces.length / 2 + 1)) 169 | .fill('*') 170 | .join('')} `; 171 | }) 172 | // Headers (h1 or h2) (lines "underlined" by ---- or =====) 173 | // Citations, Inserts, Subscripts, Superscripts, and Strikethroughs 174 | .replace(new RegExp(`<(${Object.keys(map).join('|')})>(.*?)`, 'g'), (match, from, content) => { 175 | const to = map[from]; 176 | return to + content + to; 177 | }) 178 | // Other kind of strikethrough 179 | .replace(/(\s+)~~(.*?)~~(\s+)/g, '$1-$2-$3') 180 | // Named/Un-Named Code Block 181 | .replace(/```(.+\n)?((?:.|\n)*?)```/g, (match, synt, content) => { 182 | let code = '{code}'; 183 | if (synt) { 184 | code = `{code:${synt.replace(/\n/g, '')}}\n`; 185 | } 186 | return `${code}${content}{code}`; 187 | }) 188 | // Inline-Preformatted Text 189 | .replace(/`([^`]+)`/g, '{{$1}}') 190 | // Images 191 | .replace(/!\[[^\]]*\]\(([^)]+)\)/g, '!$1!') 192 | // Named Link 193 | .replace(/\[([^\]]+)\]\(([^)]+)\)/g, '[$1|$2]') 194 | // Un-Named Link 195 | .replace(/<([^>]+)>/g, '[$1]') 196 | // Single Paragraph Blockquote 197 | .replace(/^>/gm, 'bq.') 198 | ); 199 | } 200 | } 201 | 202 | module.exports = J2M; 203 | -------------------------------------------------------------------------------- /jsdoc.json: -------------------------------------------------------------------------------- 1 | { 2 | "recurseDepth": 10, 3 | "source": { 4 | "includePattern": ".+\\.js(doc|x)?$", 5 | "excludePattern": "(^|\\/|\\\\)_" 6 | }, 7 | "sourceType": "module", 8 | "tags": { 9 | "allowUnknownTags": true, 10 | "dictionaries": ["jsdoc", "closure"] 11 | }, 12 | "templates": { 13 | "cleverLinks": false, 14 | "monospaceLinks": false 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "jira2md", 3 | "version": "3.0.1", 4 | "keywords": [ 5 | "jira", 6 | "atlassian", 7 | "wiki", 8 | "markdown" 9 | ], 10 | "homepage": "http://github.com/kylefarris/J2M.git", 11 | "repository": { 12 | "type": "git", 13 | "url": "http://github.com/kylefarris/J2M.git" 14 | }, 15 | "description": "JIRA to MarkDown text format converter.", 16 | "main": "index.js", 17 | "devDependencies": { 18 | "chai": "^4.1.2", 19 | "eslint": "^8.24.0", 20 | "eslint-config-airbnb": "^19.0.4", 21 | "eslint-config-prettier": "^8.5.0", 22 | "eslint-plugin-chai-friendly": "^0.7.2", 23 | "eslint-plugin-jsdoc": "^39.3.6", 24 | "eslint-plugin-prettier": "^4.2.1", 25 | "mocha": "^9.2.2", 26 | "prettier": "^2.7.1" 27 | }, 28 | "scripts": { 29 | "format": "node_modules/.bin/prettier '**/*.{js,json}' --write", 30 | "lint": "node_modules/.bin/eslint '**/*.js'", 31 | "test": "mocha --timeout 5000 --check-leaks --reporter spec test/" 32 | }, 33 | "author": "Fokke Zandbergen ", 34 | "contributors": [ 35 | "Kyle Farris ", 36 | "Eryk Warren ", 37 | "Andrej Arbanas ", 38 | "Tommi Laukkanen " 39 | ], 40 | "license": "Apache-2.0", 41 | "dependencies": { 42 | "marked": "^4.0.12" 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /test/jira2md.js: -------------------------------------------------------------------------------- 1 | const should = require('chai').should(); 2 | const fs = require('fs'); 3 | const path = require('path'); 4 | 5 | const j2m = require('../index'); 6 | 7 | describe('to_markdown', () => { 8 | it('should exist', () => { 9 | should.exist(j2m.to_markdown); 10 | }); 11 | it('should be a function', () => { 12 | j2m.to_markdown.should.be.a('function'); 13 | }); 14 | it('should convert bolds properly', () => { 15 | const markdown = j2m.to_markdown('*bold*'); 16 | markdown.should.eql('**bold**'); 17 | }); 18 | it('should convert italics properly', () => { 19 | const markdown = j2m.to_markdown('_italic_'); 20 | markdown.should.eql('*italic*'); 21 | }); 22 | it('should convert monospaced content properly', () => { 23 | const markdown = j2m.to_markdown('{{monospaced}}'); 24 | markdown.should.eql('`monospaced`'); 25 | }); 26 | // it('should convert citations properly', () => { 27 | // const markdown = j2m.to_markdown('??citation??'); 28 | // markdown.should.eql('citation'); 29 | // }); 30 | it('should convert strikethroughs properly', () => { 31 | const markdown = j2m.to_markdown(' -deleted- '); 32 | markdown.should.eql(' ~~deleted~~ '); 33 | }); 34 | it('should convert inserts properly', () => { 35 | const markdown = j2m.to_markdown('+inserted+'); 36 | markdown.should.eql('inserted'); 37 | }); 38 | it('should convert superscript properly', () => { 39 | const markdown = j2m.to_markdown('^superscript^'); 40 | markdown.should.eql('superscript'); 41 | }); 42 | it('should convert subscript properly', () => { 43 | const markdown = j2m.to_markdown('~subscript~'); 44 | markdown.should.eql('subscript'); 45 | }); 46 | it('should convert preformatted blocks properly', () => { 47 | const markdown = j2m.to_markdown('{noformat}\nso *no* further _formatting_ is done here\n{noformat}'); 48 | markdown.should.eql('```\nso **no** further *formatting* is done here\n```'); 49 | }); 50 | it('should convert language-specific code blocks properly', () => { 51 | const markdown = j2m.to_markdown("{code:javascript}\nconst hello = 'world';\n{code}"); 52 | markdown.should.eql("```javascript\nconst hello = 'world';\n```"); 53 | }); 54 | it('should convert code without language-specific and with title into code block', () => { 55 | const markdown = j2m.to_markdown( 56 | '{code:title=Foo.java}\nclass Foo {\n public static void main() {\n }\n}\n{code}' 57 | ); 58 | markdown.should.eql('```\nclass Foo {\n public static void main() {\n }\n}\n```'); 59 | }); 60 | it('should convert code without line feed before the end code block', () => { 61 | const markdown = j2m.to_markdown('{code:java}\njava code{code}'); 62 | markdown.should.eql('```java\njava code\n```'); 63 | }); 64 | it('should convert fully configured code block', () => { 65 | const markdown = j2m.to_markdown( 66 | '{code:xml|title=My Title|borderStyle=dashed|borderColor=#ccc|titleBGColor=#F7D6C1|bgColor=#FFFFCE}' + 67 | '\n ' + 68 | '\n ' + 69 | '\n ' + 70 | '\n{code}' 71 | ); 72 | markdown.should.eql('```xml\n \n \n \n```'); 73 | }); 74 | it('should convert images properly', () => { 75 | const markdown = j2m.to_markdown('!http://google.com/image!'); 76 | markdown.should.eql('![](http://google.com/image)'); 77 | }); 78 | it('should convert linked images properly', () => { 79 | const markdown = j2m.to_markdown('[!http://google.com/image!|http://google.com/link]'); 80 | markdown.should.eql('[![](http://google.com/image)](http://google.com/link)'); 81 | }); 82 | it('should convert unnamed links properly', () => { 83 | const markdown = j2m.to_markdown('[http://google.com]'); 84 | markdown.should.eql(''); 85 | }); 86 | it('should convert named links properly', () => { 87 | const markdown = j2m.to_markdown('[Google|http://google.com]'); 88 | markdown.should.eql('[Google](http://google.com)'); 89 | }); 90 | it('should convert headers properly', () => { 91 | const h1 = j2m.to_markdown('h1. Biggest heading'); 92 | const h2 = j2m.to_markdown('h2. Bigger heading'); 93 | const h3 = j2m.to_markdown('h3. Big heading'); 94 | const h4 = j2m.to_markdown('h4. Normal heading'); 95 | const h5 = j2m.to_markdown('h5. Small heading'); 96 | const h6 = j2m.to_markdown('h6. Smallest heading'); 97 | h1.should.eql('# Biggest heading'); 98 | h2.should.eql('## Bigger heading'); 99 | h3.should.eql('### Big heading'); 100 | h4.should.eql('#### Normal heading'); 101 | h5.should.eql('##### Small heading'); 102 | h6.should.eql('###### Smallest heading'); 103 | }); 104 | it('should convert blockquotes properly', () => { 105 | const markdown = j2m.to_markdown('bq. This is a long blockquote type thingy that needs to be converted.'); 106 | markdown.should.eql('> This is a long blockquote type thingy that needs to be converted.'); 107 | }); 108 | it('should convert un-ordered lists properly', () => { 109 | const markdown = j2m.to_markdown('* Foo\n* Bar\n* Baz\n** FooBar\n** BarBaz\n*** FooBarBaz\n* Starting Over'); 110 | markdown.should.eql('* Foo\n* Bar\n* Baz\n * FooBar\n * BarBaz\n * FooBarBaz\n* Starting Over'); 111 | }); 112 | it('should convert ordered lists properly', () => { 113 | const markdown = j2m.to_markdown('# Foo\n# Bar\n# Baz\n## FooBar\n## BarBaz\n### FooBarBaz\n# Starting Over'); 114 | markdown.should.eql('1. Foo\n1. Bar\n1. Baz\n 1. FooBar\n 1. BarBaz\n 1. FooBarBaz\n1. Starting Over'); 115 | }); 116 | it('should handle bold AND italic (combined) correctly', () => { 117 | const markdown = j2m.to_markdown('This is _*emphatically bold*_!'); 118 | markdown.should.eql('This is ***emphatically bold***!'); 119 | }); 120 | it('should handle bold within a un-ordered list item', () => { 121 | const markdown = j2m.to_markdown('* This is not bold!\n** This is *bold*.'); 122 | markdown.should.eql('* This is not bold!\n * This is **bold**.'); 123 | }); 124 | it('should be able to handle a complicated multi-line jira-wiki string and convert it to markdown', () => { 125 | const jiraStr = fs.readFileSync(path.resolve(__dirname, 'test.jira'), 'utf8'); 126 | const mdStr = fs.readFileSync(path.resolve(__dirname, 'test.md'), 'utf8'); 127 | const markdown = j2m.to_markdown(jiraStr); 128 | markdown.should.eql(mdStr); 129 | }); 130 | it('should not recognize strikethroughs over multiple lines', () => { 131 | const markdown = j2m.to_markdown( 132 | "* Here's an un-ordered list line\n* Multi-line strikethroughs shouldn't work." 133 | ); 134 | markdown.should.eql("* Here's an un-ordered list line\n* Multi-line strikethroughs shouldn't work."); 135 | }); 136 | it('should remove color attributes', () => { 137 | const markdown = j2m.to_markdown('A text with{color:blue} blue \n lines {color} is not necessary.'); 138 | markdown.should.eql('A text with blue \n lines is not necessary.'); 139 | }); 140 | it('should remove multiple color attributes', () => { 141 | const markdown = j2m.to_markdown( 142 | 'A text with{color:blue} blue \n lines {color} is not necessary. {color:red} red {color}' 143 | ); 144 | markdown.should.eq('A text with blue \n lines is not necessary. red '); 145 | }); 146 | // it('should not recognize inserts across multiple table cells', () => { 147 | // const markdown = j2m.to_markdown('||Heading 1||Heading 2||\n|Col+A1|Col+A2|'); 148 | // markdown.should.eql('\n|Heading 1|Heading 2|\n| --- | --- |\n|Col+A1|Col+A2|'); 149 | // }); 150 | }); 151 | -------------------------------------------------------------------------------- /test/md2jira.js: -------------------------------------------------------------------------------- 1 | const should = require('chai').should(); 2 | const fs = require('fs'); 3 | const path = require('path'); 4 | 5 | const j2m = require('../index'); 6 | 7 | describe('to_jira', () => { 8 | it('should exist', () => { 9 | should.exist(j2m.to_jira); 10 | }); 11 | it('should be a function', () => { 12 | j2m.to_jira.should.be.a('function'); 13 | }); 14 | it('should convert bolds properly', () => { 15 | const jira = j2m.to_jira('**bold**'); 16 | jira.should.eql('*bold*'); 17 | }); 18 | it('should convert italics properly', () => { 19 | const jira = j2m.to_jira('*italic*'); 20 | jira.should.eql('_italic_'); 21 | }); 22 | it('should convert monospaced content properly', () => { 23 | const jira = j2m.to_jira('`monospaced`'); 24 | jira.should.eql('{{monospaced}}'); 25 | }); 26 | // it('should convert citations properly', () => { 27 | // const jira = j2m.to_jira('citation'); 28 | // jira.should.eql('??citation??'); 29 | // }); 30 | it('should convert strikethroughs properly', () => { 31 | const jira = j2m.to_jira(' ~~deleted~~ '); 32 | jira.should.eql(' -deleted- '); 33 | }); 34 | it('should convert inserts properly', () => { 35 | const jira = j2m.to_jira('inserted'); 36 | jira.should.eql('+inserted+'); 37 | }); 38 | it('should convert superscript properly', () => { 39 | const jira = j2m.to_jira('superscript'); 40 | jira.should.eql('^superscript^'); 41 | }); 42 | it('should convert subscript properly', () => { 43 | const jira = j2m.to_jira('subscript'); 44 | jira.should.eql('~subscript~'); 45 | }); 46 | it('should convert preformatted blocks properly', () => { 47 | const jira = j2m.to_jira('```\nso *no* further **formatting** is done here\n```'); 48 | jira.should.eql('{code}\nso _no_ further *formatting* is done here\n{code}'); 49 | }); 50 | it('should convert language-specific code blocks properly', () => { 51 | const jira = j2m.to_jira("```javascript\nconst hello = 'world';\n```"); 52 | jira.should.eql("{code:javascript}\nconst hello = 'world';\n{code}"); 53 | }); 54 | it('should convert unnamed images properly', () => { 55 | const jira = j2m.to_jira('![](http://google.com/image)'); 56 | jira.should.eql('!http://google.com/image!'); 57 | }); 58 | it('should convert named images properly', () => { 59 | const jira = j2m.to_jira('![Google](http://google.com/image)'); 60 | jira.should.eql('!http://google.com/image!'); 61 | }); 62 | it('should convert linked images properly', () => { 63 | const jira = j2m.to_jira('[![Google](http://google.com/image)](http://google.com/link)'); 64 | jira.should.eql('[!http://google.com/image!|http://google.com/link]'); 65 | }); 66 | it('should convert unnamed links properly', () => { 67 | const jira = j2m.to_jira(''); 68 | jira.should.eql('[http://google.com]'); 69 | }); 70 | it('should convert named links properly', () => { 71 | const jira = j2m.to_jira('[Google](http://google.com)'); 72 | jira.should.eql('[Google|http://google.com]'); 73 | }); 74 | it('should convert headers properly', () => { 75 | const h1 = j2m.to_jira('# Biggest heading'); 76 | const h2 = j2m.to_jira('## Bigger heading'); 77 | const h3 = j2m.to_jira('### Big heading'); 78 | const h4 = j2m.to_jira('#### Normal heading'); 79 | const h5 = j2m.to_jira('##### Small heading'); 80 | const h6 = j2m.to_jira('###### Smallest heading'); 81 | h1.should.eql('h1. Biggest heading'); 82 | h2.should.eql('h2. Bigger heading'); 83 | h3.should.eql('h3. Big heading'); 84 | h4.should.eql('h4. Normal heading'); 85 | h5.should.eql('h5. Small heading'); 86 | h6.should.eql('h6. Smallest heading'); 87 | }); 88 | it('should convert underline-style headers properly', () => { 89 | const h1 = j2m.to_jira('Biggest heading\n======='); 90 | const h2 = j2m.to_jira('Bigger heading\n------'); 91 | h1.should.eql('h1. Biggest heading'); 92 | h2.should.eql('h2. Bigger heading'); 93 | }); 94 | it('should convert blockquotes properly', () => { 95 | const jira = j2m.to_jira('> This is a long blockquote type thingy that needs to be converted.'); 96 | jira.should.eql('bq. This is a long blockquote type thingy that needs to be converted.'); 97 | }); 98 | it('should convert un-ordered lists properly', () => { 99 | const jira = j2m.to_jira('* Foo\n* Bar\n* Baz\n * FooBar\n * BarBaz\n * FooBarBaz\n* Starting Over'); 100 | jira.should.eql('* Foo\n* Bar\n* Baz\n** FooBar\n** BarBaz\n*** FooBarBaz\n* Starting Over'); 101 | }); 102 | it('should convert ordered lists properly', () => { 103 | const jira = j2m.to_jira( 104 | '1. Foo\n1. Bar\n1. Baz\n 1. FooBar\n 1. BarBaz\n 1. FooBarBaz\n1. Starting Over' 105 | ); 106 | jira.should.eql('# Foo\n# Bar\n# Baz\n## FooBar\n## BarBaz\n### FooBarBaz\n# Starting Over'); 107 | }); 108 | it('should handle bold AND italic (combined) correctly', () => { 109 | const jira = j2m.to_jira('This is ***emphatically bold***!'); 110 | jira.should.eql('This is _*emphatically bold*_!'); 111 | }); 112 | it('should handle bold within a un-ordered list item', () => { 113 | const jira = j2m.to_jira('* This is not bold!\n * This is **bold**.'); 114 | jira.should.eql('* This is not bold!\n** This is *bold*.'); 115 | }); 116 | it('should be able to handle a complicated multi-line markdown string and convert it to markdown', () => { 117 | const jiraStr = fs.readFileSync(path.resolve(__dirname, 'test.jira'), 'utf8'); 118 | const mdStr = fs.readFileSync(path.resolve(__dirname, 'test.md'), 'utf8'); 119 | const jira = j2m.to_jira(mdStr); 120 | jira.should.eql(jiraStr); 121 | }); 122 | }); 123 | -------------------------------------------------------------------------------- /test/test.html: -------------------------------------------------------------------------------- 1 |

Biggest heading

2 |

Bigger heading

3 |

Biggest heading

4 |

Bigger heading

5 |

Big heading

6 |

Normal heading

7 |
Small heading
8 |
Smallest heading
9 |

strong
emphasis
monospaced
deleted
inserted
superscript
subscript

10 |
var hello = 'world';
11 | 
12 |


13 |

http://google.com
Google

14 |

GitHub Flavor
deleted

15 |
  preformatted piece of text
16 |   so *no_ further _formatting* is done here
17 | 
18 |

Should be bold AND italic

19 |
    20 |
  • First li
  • 21 |
  • Second li
      22 |
    • Indented li
        23 |
      • Three columns in li
      • 24 |
      25 |
    • 26 |
    27 |
  • 28 |
  • Back to first level li
  • 29 |
30 |
    31 |
  1. First li
  2. 32 |
  3. Second li
      33 |
    1. Indented li
        34 |
      1. Three columns in li
      2. 35 |
      36 |
    2. 37 |
    38 |
  4. 39 |
  5. Back to first level li
  6. 40 |
41 |
    42 |
  • Here's italic inside li
  • 43 |
  • here's bold inside li
  • 44 |
  • Here's bold + italic inside li
      45 |
    • Here they are in one line indented: italic bold
    • 46 |
    47 |
  • 48 |
49 |
50 |

Here's a long single-paragraph block quote. It should look pretty and stuff.

51 |
52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 |
A title
Panel text
62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 |
Heading 1Heading 2
Col A1Col A2
Col B1Col B2
78 | -------------------------------------------------------------------------------- /test/test.jira: -------------------------------------------------------------------------------- 1 | h1. Biggest heading 2 | 3 | h2. Bigger heading 4 | 5 | h1. Biggest heading 6 | h2. Bigger heading 7 | h3. Big heading 8 | h4. Normal heading 9 | h5. Small heading 10 | h6. Smallest heading 11 | 12 | *strong* 13 | _emphasis_ 14 | {{monospaced}} 15 | -deleted- 16 | +inserted+ 17 | ^superscript^ 18 | ~subscript~ 19 | 20 | {code:javascript} 21 | var hello = 'world'; 22 | {code} 23 | 24 | !http://google.com/image! 25 | [!http://google.com/image!|http://google.com/link] 26 | 27 | [http://google.com] 28 | [Google|http://google.com] 29 | 30 | GitHub Flavor 31 | -deleted- 32 | 33 | {code} 34 | preformatted piece of text 35 | so _no_ further _formatting_ is done here 36 | {code} 37 | 38 | _*Should be bold AND italic*_ 39 | 40 | * First li 41 | * Second li 42 | ** Indented li 43 | *** Three columns in li 44 | * Back to first level li 45 | 46 | # First li 47 | # Second li 48 | ## Indented li 49 | ### Three columns in li 50 | # Back to first level li 51 | 52 | * Here's _italic_ inside li 53 | * here's *bold* inside li 54 | * Here's _*bold + italic*_ inside li 55 | ** Here they are in one line indented: _italic_ *bold* 56 | 57 | bq. Here's a long single-paragraph block quote. It should look pretty and stuff. 58 | 59 | {panel:title=A title} 60 | Panel text 61 | {panel} 62 | 63 | ||Heading 1||Heading 2|| 64 | |Col A1|Col A2| 65 | |Col B1|Col B2| 66 | -------------------------------------------------------------------------------- /test/test.md: -------------------------------------------------------------------------------- 1 | # Biggest heading 2 | 3 | ## Bigger heading 4 | 5 | # Biggest heading 6 | ## Bigger heading 7 | ### Big heading 8 | #### Normal heading 9 | ##### Small heading 10 | ###### Smallest heading 11 | 12 | **strong** 13 | *emphasis* 14 | `monospaced` 15 | ~~deleted~~ 16 | inserted 17 | superscript 18 | subscript 19 | 20 | ```javascript 21 | var hello = 'world'; 22 | ``` 23 | 24 | ![](http://google.com/image) 25 | [![](http://google.com/image)](http://google.com/link) 26 | 27 | 28 | [Google](http://google.com) 29 | 30 | GitHub Flavor 31 | ~~deleted~~ 32 | 33 | ``` 34 | preformatted piece of text 35 | so *no_ further _formatting* is done here 36 | ``` 37 | 38 | ***Should be bold AND italic*** 39 | 40 | * First li 41 | * Second li 42 | * Indented li 43 | * Three columns in li 44 | * Back to first level li 45 | 46 | 1. First li 47 | 1. Second li 48 | 1. Indented li 49 | 1. Three columns in li 50 | 1. Back to first level li 51 | 52 | * Here's *italic* inside li 53 | * here's **bold** inside li 54 | * Here's ***bold + italic*** inside li 55 | * Here they are in one line indented: *italic* **bold** 56 | 57 | > Here's a long single-paragraph block quote. It should look pretty and stuff. 58 | 59 | 60 | | A title | 61 | | --- | 62 | | Panel text | 63 | 64 | 65 | |Heading 1|Heading 2| 66 | | --- | --- | 67 | |Col A1|Col A2| 68 | |Col B1|Col B2| 69 | -------------------------------------------------------------------------------- /test/to_html.js: -------------------------------------------------------------------------------- 1 | const should = require('chai').should(); 2 | const fs = require('fs'); 3 | const path = require('path'); 4 | 5 | const j2m = require('../index'); 6 | 7 | describe('md_to_html', () => { 8 | it('should exist', () => { 9 | should.exist(j2m.md_to_html('')); 10 | }); 11 | 12 | it('should be a function', () => { 13 | j2m.md_to_html.should.be.a('function'); 14 | }); 15 | 16 | it('should provide html from md', () => { 17 | const mdStr = fs.readFileSync(path.resolve(__dirname, 'test.md'), 'utf8'); 18 | const htmlStr = fs.readFileSync(path.resolve(__dirname, 'test.html'), 'utf8'); 19 | 20 | const html = j2m.md_to_html(mdStr); 21 | html.should.eql(htmlStr); 22 | }); 23 | }); 24 | 25 | describe('jira_to_html', () => { 26 | it('should exist', () => { 27 | should.exist(j2m.jira_to_html('')); 28 | }); 29 | 30 | it('should be a function', () => { 31 | j2m.jira_to_html.should.be.a('function'); 32 | }); 33 | 34 | it('should provide html from md', () => { 35 | const jiraStr = fs.readFileSync(path.resolve(__dirname, 'test.jira'), 'utf8'); 36 | const htmlStr = fs.readFileSync(path.resolve(__dirname, 'test.html'), 'utf8'); 37 | 38 | const html = j2m.jira_to_html(jiraStr); 39 | html.should.eql(htmlStr); 40 | }); 41 | }); 42 | --------------------------------------------------------------------------------